Skip to content

Conversation

@ashley-cui
Copy link
Member

The netavark create command is a new command that takes a partially filled out network config, and some options, via a json on stdin. The command will validate the network and fill out incompleted fields in the config. It then will return a complete network config json. The behavior is to match and replace https://github.com/containers/container-libs/blob/1e46b0756b39f76f287c64aa46c41107ad1110bb/common/libnetwork/netavark/config.go#L118

@ashley-cui
Copy link
Member Author

Container libs part lives here as a draft, but the tests won't pass until this PR merges, unless there's another way to throw it at tests? The config tests are updated there and pass locally, but if there's good ideas for testing, I'm all ears.

@TomSweeneyRedHat
Copy link
Member

@ashley-cui you have some test unhappiness

The netavark create command is a new command that takes a partially filled out network config, and some options, via a json on stdin.
The command will validate the network and fill out incompleted fields in the config. It then will return a complete network config json.
The behavior is to match and replace https://github.com/containers/container-libs/blob/1e46b0756b39f76f287c64aa46c41107ad1110bb/common/libnetwork/netavark/config.go#L118

Signed-off-by: Ashley Cui <acui@redhat.com>
@packit-as-a-service
Copy link

Ephemeral COPR build failed. @containers/packit-build please check.

@ashley-cui
Copy link
Member Author

@containers/netavark-maintainers PTAL

@TomSweeneyRedHat
Copy link
Member

LGTM
but I'd like at least one person with some serious rust chops to look at this.

Copy link
Member

@Luap99 Luap99 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not a full review but some comments

I did not carefully check all the validation logic matches common but I guess we can run the common libnetwork unit tests with your new code to ensure we have proper coverage if you haven't done already

}
}

pub fn new_network(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like to much logic in mod.rs files, just polutes my editor navigation if all the files are called mod.rs.

just move it into driver.rs IMO

"" => Ok(String::from("false")),
"1" => Ok(String::from("true")),
"0" => Ok(String::from("false")),
"strict" | "true" | "false" => Ok(ndr.to_string()),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that does not seem right here for routes?

In general with all these functions here I am a bit worried about duplication with the already existing parsing in, i.e. bridge.rs file in validation there.

I think this option parsing should likely be streamlined to have one proper function that can parse all options based on the driver and return it as a sane type? Like this I find it to messy if we parse things differently in two places.

) -> NetavarkResult<()> {
// Find the plugin binary
let driver_name = &network.driver;
let plugin_path = if let Some(dirs) = plugin_directories {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this here can likely be shared with the other location where we lookup the plugin?

};

// Clone the network since we need to send it via JSON
let input = network.clone();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure why we would need to clone here

Comment on lines +411 to +461
let mut child = Command::new(&plugin_path)
.arg("create")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::inherit())
.spawn()?;

let stdin = child.stdin.take().unwrap();
serde_json::to_writer(&stdin, &input)?;
// Close stdin here to avoid that the plugin waits forever for an EOF.
// And then we would wait for the child to exit which would cause a hang.
drop(stdin);

// Note: We need to buffer the output and then deserialize into the correct type after
// the plugin exits, since the plugin can return two different json types depending on
// the exit code.
let mut buffer: Vec<u8> = Vec::new();

let mut stdout = child.stdout.take().unwrap();
// Do not handle error here, we have to wait for the child first.
let result = stdout.read_to_end(&mut buffer);

let exit_status = wrap!(child.wait(), "wait for plugin to exit")?;
if let Some(rc) = exit_status.code() {
// make sure the buffer is correct
wrap!(result, "read into buffer")?;
if rc == 0 {
// read the modified network config
let updated_network: Network = serde_json::from_slice(&buffer)?;
// Update the network with the plugin's response
*network = updated_network;
Ok(())
} else {
// exit code not 0 => error
let err: JsonError = serde_json::from_slice(&buffer)?;
Err(NetavarkError::msg(format!(
"plugin {:?} failed with exit code {}, message: {}",
plugin_path.file_name().unwrap_or_default(),
rc,
err.error
)))
}
} else {
// If we could not get the exit code then the process was killed by a signal.
// I don't think it is necessary to read and return the signal so we just return a generic error.
Err(NetavarkError::msg(format!(
"plugin {:?} killed by signal",
plugin_path.file_name().unwrap_or_default()
)))
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this feels like copy pasted from the other location so this must be shared as well

match key.as_str() {
constants::OPTION_MODE => {
if is_macvlan {
if !constants::VALID_MACVLAN_MODES.contains(&value.as_str()) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we already have get_macvlan_mode_from_string() so this new code is not needed, see prior comment about consolidating option parsing into once central place ideally

value
)));
}
} else if !constants::VALID_IPVLAN_MODES.contains(&value.as_str()) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here get_ipvlan_mode_from_string

tower = { version = "0.5.3", features = ["util"] }
hyper-util = "0.1.19"
regex = "1.12.2"
pnet = "0.35.0"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we already have our own netlink libs so we should not add more dependencies for this.

let prefix = default_interface_name
.as_deref()
.ok_or_else(|| NetavarkError::msg("default interface name is not set"))?;
let mut names: Vec<String> = datalink::interfaces()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we need a new function to get all interface names via our netlink lib, ideally similar to

fn getLinkNames() -> Option<Vec<String>> {
    let mut sock = netlink::Socket::<NetlinkRoute>::new().ok()?;
    let links = sock.dump_links(&mut vec![]).ok()?;

    let mut names = Vec::with_capacity(links.len());

    for link in links {
        for attribute in link.attributes.into_iter() {
            if let LinkAttribute::IfName(name) = attribute {
                names.push(name);
                break;
            }
        }
    }

    Some(names)
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants