Skip to content

Commit

Permalink
apply: Support overriding interface configuration without merging
Browse files Browse the repository at this point in the history
When using `nmstatectl gc`, unmentioned IP section means disabled, but
in `nmstatectl service` or `nmstatectl apply`, unmentioned IP means
merging from current. In order to be consistent when migrating from
`nmstatectl gc` to `nmstatectl service`, we need to introduce a option
to skip merging IP.

We also have user request us to not merging existing route by only
apply route found in desired state.

So we introduce option to skip merging current state:

 * Rust: `NetworkState.set_override_iface(true)`
 * Python: `libnmstate.apply(state, override_iface=True)`
 * C: `nmstate_net_state_apply()` with `NMSTATE_FLAG_OVERRIDE_IFACE` flag.
 * CLI: `nmstatectl apply --override-iface <state_file>`
 * nmstate.service: `/etc/nmstate/nmstate.conf` with:

    ```toml
    [service]
    override_iface = true
    ```

When in `override_iface` mode, nmstate will:

 * For `interfaces` section, any defined interface will be configured by
   desire state information only.
 * Other sections will ignore this `override_iface` mode and still
   merging as it was.

Technical detail:
 * When `override_iface: true`, we purge current network to only keep
   interface name and type before merging it with desire state.
 * When `override_iface: true`, we do not re-use existing NetworkManager
   connection.

Integration test cases included.

Resolves: https://issues.redhat.com/browse/RHEL-59935

Signed-off-by: Gris Ge <[email protected]>
  • Loading branch information
cathay4t committed Jan 26, 2025
1 parent 8bf6cc7 commit 216bf43
Show file tree
Hide file tree
Showing 15 changed files with 224 additions and 25 deletions.
30 changes: 29 additions & 1 deletion doc/nmstate.service.8.in
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,48 @@ nmstate\&.service
nmstate\&.service invokes \fBnmstatectl service\fR command which
apply all network state files ending with \fB.yml\fR in
\fB/etc/nmstate\fR folder.

.PP
By default, the network state file will be renamed with the suffix
\fB.applied\fR after applying it. This is to prevent it being applied again
when the service runs next.

With \fB/etc/nmstate/nmstate.conf\fR holding below content:

.EX
\fB
[service]
[service]\n
keep_state_file_after_apply = true
\fR
.EE

The nmstate.service will not remove network state file, just copy applied
network stata to file with the suffix \fB.applied\fR after applying it.

.PP
By default, nmstate merge network state file with current network state,
if you want to override interface settings without merging, please set
`override_iface = true` in `[service]` section in
\fB/etc/nmstate/nmstate.conf\fR:

.EX
\fB
[service]\n
override_iface = true
\fR
.EE

If `override_iface = true`, for network state to apply, nmstate.service will:

* For `interfaces` section, any defined interface will be configured by
desired state information only, undefined properties of this interface will
use default value instead of merging from current state.

* For other sections, no changes to their original behavior.

For example, if desired interface has no IPv4 section defined, nmstate
will treat it as disabled regardless current network status.

.SH BUG REPORTS
Report bugs on nmstate GitHub issues <https://github.com/nmstate/nmstate>.
.SH COPYRIGHT
Expand Down
12 changes: 12 additions & 0 deletions doc/nmstatectl.8.in
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,18 @@ rebooting.
.IP \fB--timeout\fR=<\fITIMEOUT\fR>
the user must commit the changes within \fItimeout\fR, or they will be
automatically rolled back. Default: 60 seconds.
.IP \fB--override-iface\fR
If defined when applying network state:

* For `interfaces` section, any defined interface will be configured by
desired state information only, undefined properties of this interface will
use default value instead of merging from current state.

* For other sections, no changes to their original behavior.

For example, if desired interface has no IPv4 section defined, nmstate
will treat it as disabled regardless current network status.

.IP \fB--version
displays nmstate version.
.SH LIMITATIONS
Expand Down
4 changes: 4 additions & 0 deletions rust/src/cli/apply.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ where
let kernel_only = matches.try_contains_id("KERNEL").unwrap_or_default();
let no_verify = matches.try_contains_id("NO_VERIFY").unwrap_or_default();
let no_commit = matches.try_contains_id("NO_COMMIT").unwrap_or_default();
let override_iface = matches
.try_contains_id("OVERRIDE_IFACE")
.unwrap_or_default();
let timeout = if matches.try_contains_id("TIMEOUT").unwrap_or_default() {
match matches.try_get_one::<String>("TIMEOUT") {
Ok(Some(t)) => match u32::from_str(t) {
Expand Down Expand Up @@ -65,6 +68,7 @@ where
net_state.set_verify_change(!no_verify);
net_state.set_commit(!no_commit);
net_state.set_timeout(timeout);
net_state.set_override_iface(override_iface);
net_state.set_memory_only(
matches.try_contains_id("MEMORY_ONLY").unwrap_or_default(),
);
Expand Down
9 changes: 9 additions & 0 deletions rust/src/cli/ncl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,15 @@ fn main() {
.takes_value(false)
.help("Do not make the state persistent"),
)
.arg(
clap::Arg::new("OVERRIDE_IFACE")
.long("override-iface")
.takes_value(false)
.help(
"Override interface settings \
without merge current network state"
),
)
)
.subcommand(
clap::Command::new(SUB_CMD_GEN_CONF)
Expand Down
8 changes: 7 additions & 1 deletion rust/src/cli/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ struct Config {
struct ServiceConfig {
#[serde(default)]
keep_state_file_after_apply: bool,
#[serde(default)]
override_iface: bool,
}

#[derive(Eq, Hash, PartialEq, Clone, PartialOrd, Ord)]
Expand Down Expand Up @@ -85,7 +87,11 @@ pub(crate) fn ncl_service(
continue;
}
};
let state = state_from_fd(&mut fd)?;
let mut state = state_from_fd(&mut fd)?;

if config.service.override_iface {
state.set_override_iface(true);
}

match apply_state(&state, false) {
Ok(_) => {
Expand Down
6 changes: 6 additions & 0 deletions rust/src/clib/apply.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ use crate::{
NMSTATE_FAIL, NMSTATE_PASS,
};

const NMSTATE_FLAG_OVERRIDE_IFACE: u32 = 1 << 9;

#[allow(clippy::not_unsafe_ptr_arg_deref)]
#[no_mangle]
pub extern "C" fn nmstate_net_state_apply(
Expand Down Expand Up @@ -75,6 +77,10 @@ pub extern "C" fn nmstate_net_state_apply(
net_state.set_memory_only(true);
}

if (flags & NMSTATE_FLAG_OVERRIDE_IFACE) > 0 {
net_state.set_override_iface(true);
}

net_state.set_timeout(rollback_timeout);

let result = net_state.apply();
Expand Down
3 changes: 3 additions & 0 deletions rust/src/clib/nmstate.h.in
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ extern "C" {
#define NMSTATE_FLAG_MEMORY_ONLY 1 << 6
#define NMSTATE_FLAG_RUNNING_CONFIG_ONLY 1 << 7
#define NMSTATE_FLAG_YAML_OUTPUT 1 << 8
#define NMSTATE_FLAG_OVERRIDE_IFACE 1 << 9

/**
* nmstate_net_state_retrieve - Retrieve network state
Expand Down Expand Up @@ -97,6 +98,8 @@ int nmstate_net_state_retrieve(uint32_t flags, char **state, char **log,
* Do not commit new state after verification
* * NMSTATE_FLAG_MEMORY_ONLY
* No not store network state to persistent.
* * NMSTATE_FLAG_OVERRIDE_IFACE
* Override interface configuration without merging from current.
* @state:
* Pointer of char array for network state in json format.
* @log:
Expand Down
9 changes: 9 additions & 0 deletions rust/src/lib/ifaces/inter_ifaces.rs
Original file line number Diff line number Diff line change
Expand Up @@ -604,6 +604,15 @@ impl Interfaces {
}
Ok(())
}

pub(crate) fn clone_name_type_only(&self) -> Self {
let mut ret = Self::new();

for iface in self.to_vec() {
ret.push(iface.clone_name_type_only())
}
ret
}
}

fn is_opt_str_empty(opt_string: &Option<String>) -> bool {
Expand Down
25 changes: 25 additions & 0 deletions rust/src/lib/net_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,8 @@ pub struct NetworkState {
pub(crate) running_config_only: bool,
#[serde(skip)]
pub(crate) memory_only: bool,
#[serde(skip)]
pub(crate) override_iface: bool,
}

impl NetworkState {
Expand Down Expand Up @@ -212,6 +214,22 @@ impl NetworkState {
self
}

/// Nmstate by default will merge information from current state.
/// When set to true, for network state to apply, nmstate will:
///
/// * For `interfaces` section, any defined interface will be configured by
/// desired state information only, undefined properties of this interface
/// will use default value instead of merging from current state.
///
/// * For other sections, no changes to their original behavior.
///
/// For example, if desired interface has no IPv4 section defined, nmstate
/// will treat it as disabled regardless current network status.
pub fn set_override_iface(&mut self, value: bool) -> &mut Self {
self.override_iface = value;
self
}

/// Create empty [NetworkState]
pub fn new() -> Self {
Default::default()
Expand Down Expand Up @@ -326,6 +344,7 @@ pub(crate) struct MergedNetworkState {
pub(crate) routes: MergedRoutes,
pub(crate) rules: MergedRouteRules,
pub(crate) memory_only: bool,
pub(crate) override_iface: bool,
}

impl MergedNetworkState {
Expand All @@ -335,6 +354,11 @@ impl MergedNetworkState {
gen_conf_mode: bool,
memory_only: bool,
) -> Result<Self, NmstateError> {
let mut current = current;
if desired.override_iface {
current.interfaces = current.interfaces.clone_name_type_only();
};

let interfaces = MergedInterfaces::new(
desired.interfaces,
current.interfaces,
Expand Down Expand Up @@ -373,6 +397,7 @@ impl MergedNetworkState {
ovsdb,
hostname,
memory_only,
override_iface: desired.override_iface,
};
ret.validate_ipv6_link_local_address_dns_srv()?;

Expand Down
4 changes: 3 additions & 1 deletion rust/src/lib/nm/settings/connection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,9 @@ pub(crate) fn iface_to_nm_connections(
return Ok(ret);
};

let exist_nm_conn = if iface.base_iface().identifier
let exist_nm_conn = if merged_state.override_iface {
None
} else if iface.base_iface().identifier
== Some(InterfaceIdentifier::MacAddress)
{
get_exist_profile_by_profile_name(
Expand Down
1 change: 1 addition & 0 deletions rust/src/lib/query_apply/ethernet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ impl NetworkState {
for pf_iface in pf_ifaces {
pf_state.interfaces.push(pf_iface);
}
pf_state.set_override_iface(self.override_iface);
Some(pf_state)
}
}
Expand Down
5 changes: 5 additions & 0 deletions rust/src/python/libnmstate/clib_wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
NMSTATE_FLAG_NO_COMMIT = 1 << 5
NMSTATE_FLAG_MEMORY_ONLY = 1 << 6
NMSTATE_FLAG_RUNNING_CONFIG_ONLY = 1 << 7
NMSTATE_FLAG_OVERRIDE_IFACE = 1 << 9
NMSTATE_PASS = 0


Expand Down Expand Up @@ -91,6 +92,7 @@ def apply_net_state(
verify_change=True,
save_to_disk=True,
commit=True,
override_iface=False,
rollback_timeout=60,
):
c_err_msg = c_char_p()
Expand All @@ -110,6 +112,9 @@ def apply_net_state(
if not save_to_disk:
flags |= NMSTATE_FLAG_MEMORY_ONLY

if override_iface:
flags |= NMSTATE_FLAG_OVERRIDE_IFACE

rc = lib.nmstate_net_state_apply(
flags,
c_state,
Expand Down
2 changes: 2 additions & 0 deletions rust/src/python/libnmstate/netapplier.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ def apply(
save_to_disk=True,
commit=True,
rollback_timeout=60,
override_iface=False,
):
return apply_net_state(
desired_state,
Expand All @@ -35,6 +36,7 @@ def apply(
save_to_disk=save_to_disk,
commit=commit,
rollback_timeout=rollback_timeout,
override_iface=override_iface,
)


Expand Down
Loading

0 comments on commit 216bf43

Please sign in to comment.