-
Notifications
You must be signed in to change notification settings - Fork 517
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
netdog: Add networkd devices and builders for config #3220
netdog: Add networkd devices and builders for config #3220
Conversation
This commit adds adds constants for the networkd config directory, file prefix. Network and netdev config each get an associated constant for the file extension, and each gain a method `write_config_file()` that writes an appropriately named file to the proper path.
fn netdev_from_bond(bond: NetworkDBond) -> NetDevConfig { | ||
let mut netdev = NetDevBuilder::new_bond(bond.name.clone()); | ||
netdev.with_mode(bond.mode); | ||
bond.min_links.map(|m| netdev.with_min_links(m)); | ||
match bond.monitoring_config { | ||
BondMonitoringConfigV1::MiiMon(miimon) => netdev.with_miimon_config(miimon), | ||
BondMonitoringConfigV1::ArpMon(arpmon) => netdev.with_arpmon_config(arpmon), | ||
} | ||
netdev.build() | ||
} | ||
|
||
fn netdev_from_vlan(vlan: NetworkDVlan) -> NetDevConfig { | ||
let mut netdev = NetDevBuilder::new_vlan(vlan.name.clone()); | ||
netdev.with_vlan_id(vlan.id); | ||
netdev.build() | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This logic will most likely go away once it's integrated into the NetworkD*
device code. It exists to enable unit testing now before that code is available.
fn network_from_interface(iface: NetworkDInterface) -> NetworkConfig { | ||
let mut network = NetworkBuilder::new_interface(iface.name); | ||
network.with_dhcp(iface.dhcp4, iface.dhcp6); | ||
iface.static4.map(|s| network.with_static_config(s)); | ||
iface.static6.map(|s| network.with_static_config(s)); | ||
iface.routes.map(|r| network.with_routes(r)); | ||
network.build() | ||
} | ||
|
||
fn network_from_vlan(vlan: NetworkDVlan) -> NetworkConfig { | ||
let mut network = NetworkBuilder::new_vlan(vlan.name); | ||
network.with_dhcp(vlan.dhcp4, vlan.dhcp6); | ||
vlan.static4.map(|s| network.with_static_config(s)); | ||
vlan.static6.map(|s| network.with_static_config(s)); | ||
vlan.routes.map(|r| network.with_routes(r)); | ||
network.build() | ||
} | ||
|
||
fn network_from_bond(bond: NetworkDBond) -> NetworkConfig { | ||
let mut network = NetworkBuilder::new_bond(bond.name.clone()); | ||
network.with_dhcp(bond.dhcp4, bond.dhcp6); | ||
bond.static4.map(|s| network.with_static_config(s)); | ||
bond.static6.map(|s| network.with_static_config(s)); | ||
bond.routes.map(|r| network.with_routes(r)); | ||
network.build() | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This logic will most likely go away once it's integrated into the NetworkD*
device code. It exists to enable unit testing now before that code is available.
const CONFIG_FILE_PREFIX: &str = "10-"; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How is this prefix selected? How do we know it doesn't conflict with other files?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Netplan uses a similar 10-
prefix, and I didn't see a compelling reason to change prefix.
The reason for the prefix is that systemd will process all of these files in lexicographical order. We should be the only process putting files here.
|
||
impl NetDevBuilder<Bond> { | ||
/// Create a new .netdev config for a bond. | ||
pub(crate) fn new_bond(name: InterfaceName) -> Self { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
naming nit: I might just refer to this as new
, calling it new_bond
implied to me an ability to create multiple new bonds at once, but this is instantiating the builder.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortunately, a NetDevBuilder<T>
can't have multiple new()
methods (I was a little surprised too...), hence making the constructors a bit more descriptive about what will be returned.
} | ||
|
||
/// Add bond mode | ||
pub(crate) fn with_mode(&mut self, mode: BondModeV1) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: You might be able to avoid maintaining this match statement using Into
.
pub(crate) fn with_mode(&mut self, mode: BondModeV1) { | |
pub(crate) fn with_mode<B: Into<BondMode>>(&mut self, mode: B) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought about that and decided against it for no reason other than it felt like an odd place to toss in a slightly different interface and another trait to impl, which would contain the match
statement anyway. Happy to make the change if you feel it's important.
static ref DEFAULT_ROUTE_IPV4: IpNet = "0.0.0.0/0".parse().unwrap(); | ||
static ref DEFAULT_ROUTE_IPV6: IpNet = "::/0".parse().unwrap(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: It's probably safer/better practice to use the proper constructors for these, rather than parsing a string. While we can guarantee these are correct in code review, this would theoretically panic at runtime and can't be checked by the compiler.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unless the constructor is a const function then it's unavoidable, right? What you could do is move the constants to the same module that defines IpNet and put these strings directly into the struct bypassing the parsing constructor.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's unavoidable, even if we use the proper constructors.
move the constants to the same module that defines IpNet
IpNet is an external crate, so I don't think that will be possible.
impl NetworkBuilder<Interface> { | ||
/// Create a new .network config for an interface; not meant for an interface that will be a | ||
/// bond worker. See `.new_bond_worker()` for that case. | ||
pub(crate) fn new_interface<I>(id: I) -> Self |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same nit here as with bonds.
This commit adds a module that contains the structs representing the network devices we currently configure: interfaces, bonds, and vlans. These structures are meant to contain the latest versions of all the underlying config.
This commit adds attributes to the networkd device structs to derive Deserialize to enable unit testing. Since we never directly deserialize into these device structs during normal use, gating the Deserialize implementation behind the test attribute keeps the binary size a bit smaller for "normal" builds.
/// Build the proper prefixed path for the config file | ||
fn config_path(&self) -> Result<PathBuf> { | ||
let match_section = self | ||
.r#match |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
woah what is this syntax?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The r#match
? Since match
is a rust keyword you need to "escape" it in order to use it as an identifier. "match" makes the most sense in this context, so I opted not to rename it.
let netdev = NetDevConfig { | ||
netdev: Some(NetDevSection { | ||
name: Some(name), | ||
kind: Some(NetDevKind::Bond), | ||
}), | ||
..Default::default() | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried to think of a way you could have a single new
function and somehow specialize on type, but that is not possible. Overall the idea of specializing on type in order to expose only the correct functions seems consistent with Rust idioms.
static ref DEFAULT_ROUTE_IPV4: IpNet = "0.0.0.0/0".parse().unwrap(); | ||
static ref DEFAULT_ROUTE_IPV6: IpNet = "::/0".parse().unwrap(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unless the constructor is a const function then it's unavoidable, right? What you could do is move the constants to the same module that defines IpNet and put these strings directly into the struct bypassing the parsing constructor.
9b67a53
to
bfad4a1
Compare
The above pushes address all comments other than those still being discussed:
|
This commit adds a buidler for the systemd-networkd NetDevConfig struct. The builder contains the logic to translate the structures deserialized from `net.toml` into NetDev structures representing systemd-networkd .netdev files. Since NetDevConfig will be created for bonds and vlans (and potentially more later), using a builder centralizes the logic to avoid duplication. Use a builder also means methods can be limited based on the type of device being built, providing safety at development time.
This commit adds a builder for the systemd-networkd NetworkConfig struct. The builder contains the logic to translate the structures deserialized from `net.toml` into NetworkConfig structures representing systemd-networkd .network files. Since NetworkConfig will be created for interfaces, bonds, and vlans (and potentially more later), using a builder centralizes the logic to avoid duplication. Using a builder also means methods can limited based on the type of device being built, providing safety at development time.
bfad4a1
to
e1b4e28
Compare
^ Uses |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM!
Issue number:
Related to #2449
Description of changes:
This PR is best reviewed in commit order! :)
This is the second in a series of changes to
netdog
that add the ability to generatesystemd-networkd
config files. This PR adds the structs that represent thesystemd-networkd
devices we currently use; bonds, vlans, and interfaces. In code, these structs represent the latest version of the underlying config structures.The bulk of this PR is the addition of builders for the network and netdev structs that represent config files. Using builders has a few advantages. First, a builder allows for centralization of the translation logic that is required to get from our network config related structs to the
systemd-networkd
structs. Second, a builder allows us to limit the creation methods that can be called, making creation of these structs safer for developers. (We use type parameters under the hood. Putting them on the builder keeps them from proliferating all over the code, another nice bonus)Lastly, unit tests were added for the builders. They become somewhat redundant once the full integration tests are in place, but I wanted to ensure that everything stayed sane through this development process. (Another nice side effect is we can use the reference files for the integration tests as well.)
So a small diagram of the config generation process and where we are:
Testing done:
aws-k8s-1.24
image to ensure nothing bled into existing imagesTerms of contribution:
By submitting this pull request, I agree that this contribution is dual-licensed under the terms of both the Apache License, version 2.0, and the MIT license.