Compare commits
No commits in common. "c8" and "c9-beta" have entirely different histories.
4
.gitignore
vendored
4
.gitignore
vendored
@ -1,2 +1,2 @@
|
||||
SOURCES/nmstate-1.4.6.tar.gz
|
||||
SOURCES/nmstate-vendor-1.4.6.tar.xz
|
||||
SOURCES/nmstate-2.2.61.tar.gz
|
||||
SOURCES/nmstate-vendor-2.2.61.tar.xz
|
||||
|
||||
@ -1,2 +1,2 @@
|
||||
2c5d46ae03fe2d836e165aa3562f03386d215fdf SOURCES/nmstate-1.4.6.tar.gz
|
||||
4735ef08c31684624a7844832cc2ba20e67983f6 SOURCES/nmstate-vendor-1.4.6.tar.xz
|
||||
e7c3a7490392927f2bc80f899b7a62be61016e9e SOURCES/nmstate-2.2.61.tar.gz
|
||||
6243b20ec53da6985c8cf696b5b47dbf103be4ef SOURCES/nmstate-vendor-2.2.61.tar.xz
|
||||
|
||||
@ -1,60 +0,0 @@
|
||||
From 0b530d4c8e75f60015d13c225b1c634389fbe798 Mon Sep 17 00:00:00 2001
|
||||
From: Gris Ge <fge@redhat.com>
|
||||
Date: Fri, 17 May 2024 13:12:14 +0800
|
||||
Subject: [PATCH] clib: Use build.rs to fix SONAME
|
||||
|
||||
Use [`cargo:rustc-cdylib-link-arg`][1] to `build.rs` to fix the SONAME issue
|
||||
of cargo.
|
||||
|
||||
Removed workarounds in rpm spec and `.cargo/config.toml`.
|
||||
|
||||
Changed Makefile to place `-lnmstate` after the source file to fix
|
||||
compile issue on ubuntu 20.04 old gcc.
|
||||
|
||||
[1]: https://doc.rust-lang.org/cargo/reference/build-scripts.html#rustc-cdylib-link-arg
|
||||
|
||||
Signed-off-by: Gris Ge <fge@redhat.com>
|
||||
---
|
||||
rust/.cargo/config.toml | 3 ---
|
||||
rust/src/clib/Cargo.toml | 1 +
|
||||
rust/src/clib/build.rs | 6 ++++++
|
||||
3 files changed, 7 insertions(+), 3 deletions(-)
|
||||
create mode 100644 rust/src/clib/build.rs
|
||||
|
||||
diff --git a/rust/.cargo/config.toml b/rust/.cargo/config.toml
|
||||
index 018e59d4..ca6c72f7 100644
|
||||
--- a/rust/.cargo/config.toml
|
||||
+++ b/rust/.cargo/config.toml
|
||||
@@ -1,5 +1,2 @@
|
||||
-[build]
|
||||
-rustflags = "-Clink-arg=-Wl,-soname=libnmstate.so.1"
|
||||
-
|
||||
[target.x86_64-unknown-linux-gnu]
|
||||
runner = 'sudo -E'
|
||||
diff --git a/rust/src/clib/Cargo.toml b/rust/src/clib/Cargo.toml
|
||||
index 462757ca..0eb00922 100644
|
||||
--- a/rust/src/clib/Cargo.toml
|
||||
+++ b/rust/src/clib/Cargo.toml
|
||||
@@ -6,6 +6,7 @@ authors = ["Gris Ge <fge@redhat.com>"]
|
||||
license = "Apache-2.0"
|
||||
edition = "2018"
|
||||
rust-version = "1.58"
|
||||
+build = "build.rs"
|
||||
|
||||
[lib]
|
||||
name = "nmstate"
|
||||
diff --git a/rust/src/clib/build.rs b/rust/src/clib/build.rs
|
||||
new file mode 100644
|
||||
index 00000000..74ad7e48
|
||||
--- /dev/null
|
||||
+++ b/rust/src/clib/build.rs
|
||||
@@ -0,0 +1,6 @@
|
||||
+// SPDX-License-Identifier: Apache-2.0
|
||||
+
|
||||
+fn main() {
|
||||
+ #[cfg(target_os = "linux")]
|
||||
+ println!("cargo:rustc-cdylib-link-arg=-Wl,-soname=libnmstate.so.1");
|
||||
+}
|
||||
--
|
||||
2.45.1
|
||||
|
||||
63
SOURCES/0001-nispor-fix-ipoib-iface-type.patch
Normal file
63
SOURCES/0001-nispor-fix-ipoib-iface-type.patch
Normal file
@ -0,0 +1,63 @@
|
||||
From 18fce5de48c2687131eab604c31c4738ffc2554c Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?=C3=8D=C3=B1igo=20Huguet?= <ihuguet@riseup.net>
|
||||
Date: Mon, 4 May 2026 07:24:15 +0200
|
||||
Subject: [PATCH] infiniband: restore detection of Ipoib iface type
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
|
||||
Fixes 5e13d3f "Prefer LinkInfo::Kind as interface type". This commit
|
||||
made to always prefer InfoKind over anything for Ethernet, but at the
|
||||
same time it made to prefer LinkLayerType over InfoKind for Infiniband.
|
||||
This causes that the Ipoib iface typeis not detected anymore, causing
|
||||
two problems:
|
||||
- Interfaces previously detected as Ipoib are now detected as
|
||||
Infiniband, breaking some clients like nmstate.
|
||||
- The Ipoib data is not parsed, as it is only done for Ipoib iface type.
|
||||
|
||||
Fix it by prefering the "Infiniband" generic type only if no InfoKind
|
||||
was detected, or InfoKind::Other was detected.
|
||||
|
||||
Signed-off-by: Íñigo Huguet <ihuguet@riseup.net>
|
||||
---
|
||||
src/lib/query/iface.rs | 23 ++++++++++++++++-------
|
||||
1 file changed, 16 insertions(+), 7 deletions(-)
|
||||
|
||||
diff --git a/src/lib/query/iface.rs b/src/lib/query/iface.rs
|
||||
index 9e21070..cce4f40 100644
|
||||
--- a/vendor/nispor/query/iface.rs
|
||||
+++ b/vendor/nispor/query/iface.rs
|
||||
@@ -420,14 +420,23 @@ pub(crate) fn parse_nl_msg_to_iface(
|
||||
},
|
||||
_ => IfaceType::Other(format!("{t:?}").to_lowercase()),
|
||||
};
|
||||
- // Always prefer InfoKind unless link type is loopback or
|
||||
- // infiniband.
|
||||
- if !matches!(
|
||||
- iface_state.iface_type,
|
||||
- IfaceType::Loopback | IfaceType::Infiniband
|
||||
- ) {
|
||||
- iface_state.iface_type = iface_type;
|
||||
+
|
||||
+ // We prefer LinkLayerType over InfoKind for loopback, as it
|
||||
+ // is more accurate in this case. We also prefer it for
|
||||
+ // infiniband, but only if we didn't detect a specific
|
||||
+ // InfoKind (we detected Other). For example, Ipoib is more
|
||||
+ // specific than Infiniband, but Other is not.
|
||||
+ if iface_state.iface_type == IfaceType::Loopback
|
||||
+ || (iface_state.iface_type == IfaceType::Infiniband
|
||||
+ && matches!(iface_type, IfaceType::Other(_)))
|
||||
+ {
|
||||
+ continue;
|
||||
}
|
||||
+
|
||||
+ // For any other case, we prefer InfoKind over LinkLayerType
|
||||
+ // as it is almost always more accurate. LinkLayerType is
|
||||
+ // set as ethernet for most device types.
|
||||
+ iface_state.iface_type = iface_type;
|
||||
}
|
||||
}
|
||||
for info in infos {
|
||||
--
|
||||
2.53.0
|
||||
|
||||
@ -0,0 +1,134 @@
|
||||
From 69df418c0a918163d84cddaa0cf45e6c302c8103 Mon Sep 17 00:00:00 2001
|
||||
From: Jan Vaclav <jvaclav@redhat.com>
|
||||
Date: Wed, 29 Jul 2026 11:07:22 +0200
|
||||
Subject: [PATCH] iface: include existing interfaces in name search
|
||||
|
||||
InterfaceNameSearch filtered kernel_nics to only up interfaces, which
|
||||
excluded existing interfaces that were not UP in the merged state,
|
||||
which broke copy-mac-from when referencing an interface that exists in
|
||||
current state but is being added as a bond/bridge port in the same
|
||||
transaction. The port's merged state isn't Up, so it gets filtered out
|
||||
of kernel_nics.
|
||||
|
||||
Include all existing interfaces in kernel_nics, and only filter desired
|
||||
interfaces to those that will be up.
|
||||
|
||||
Fixes: aaf11db38e97 ("iface: support matching against alt-name in copy-mac-from")
|
||||
Signed-off-by: Jan Vaclav <jvaclav@redhat.com>
|
||||
Assisted-by: Claude Sonnet 4.5
|
||||
---
|
||||
rust/src/lib/ifaces/inter_ifaces.rs | 3 +-
|
||||
rust/src/lib/unit_tests/alt_name.rs | 82 +++++++++++++++++++++++++++++
|
||||
2 files changed, 84 insertions(+), 1 deletion(-)
|
||||
|
||||
diff --git a/rust/src/lib/ifaces/inter_ifaces.rs b/rust/src/lib/ifaces/inter_ifaces.rs
|
||||
index 09c5a10f82..76b83a273b 100644
|
||||
--- a/rust/src/lib/ifaces/inter_ifaces.rs
|
||||
+++ b/rust/src/lib/ifaces/inter_ifaces.rs
|
||||
@@ -1248,7 +1248,7 @@ impl InterfaceNameSearch {
|
||||
{
|
||||
let mut kernel_nics: HashSet<String> = HashSet::new();
|
||||
|
||||
- for iface in merged_ifaces.clone().filter(|i| i.merged.is_up()) {
|
||||
+ for iface in merged_ifaces.clone() {
|
||||
if let Some(cur_iface) = iface.current.as_ref() {
|
||||
// Existing kernel interface
|
||||
kernel_nics.insert(cur_iface.name().to_string());
|
||||
@@ -1256,6 +1256,7 @@ impl InterfaceNameSearch {
|
||||
// Creating new kernel interface
|
||||
if des_iface.base_iface().identifier.as_ref()
|
||||
!= Some(&InterfaceIdentifier::MacAddress)
|
||||
+ && des_iface.is_up()
|
||||
{
|
||||
kernel_nics.insert(des_iface.name().to_string());
|
||||
}
|
||||
diff --git a/rust/src/lib/unit_tests/alt_name.rs b/rust/src/lib/unit_tests/alt_name.rs
|
||||
index 4fa417df7c..8ff35f7d96 100644
|
||||
--- a/rust/src/lib/unit_tests/alt_name.rs
|
||||
+++ b/rust/src/lib/unit_tests/alt_name.rs
|
||||
@@ -420,3 +420,85 @@ fn test_do_not_resolve_mac_identifer_iface_to_alt_name() {
|
||||
assert!(merged_iface.for_verify.is_none());
|
||||
assert!(merged_iface.desired.is_none());
|
||||
}
|
||||
+
|
||||
+#[test]
|
||||
+fn test_copy_mac_from_existing_down_port() {
|
||||
+ let desired: NetworkState = serde_yaml::from_str(
|
||||
+ r"---
|
||||
+ interfaces:
|
||||
+ - name: bond99
|
||||
+ type: bond
|
||||
+ state: up
|
||||
+ copy-mac-from: eth1
|
||||
+ link-aggregation:
|
||||
+ mode: balance-rr
|
||||
+ port:
|
||||
+ - eth1
|
||||
+ - eth2",
|
||||
+ )
|
||||
+ .unwrap();
|
||||
+ let current: NetworkState = serde_yaml::from_str(
|
||||
+ r"---
|
||||
+ interfaces:
|
||||
+ - name: eth1
|
||||
+ type: ethernet
|
||||
+ state: down
|
||||
+ mac-address: AA:BB:CC:DD:EE:FF
|
||||
+ - name: eth2
|
||||
+ type: ethernet
|
||||
+ state: up
|
||||
+ mac-address: 11:22:33:44:55:66",
|
||||
+ )
|
||||
+ .unwrap();
|
||||
+
|
||||
+ let merged_state =
|
||||
+ MergedNetworkState::new(desired, current, Default::default(), false)
|
||||
+ .unwrap();
|
||||
+
|
||||
+ let bond_iface =
|
||||
+ merged_state.interfaces.kernel_ifaces.get("bond99").unwrap();
|
||||
+ let bond_mac = bond_iface
|
||||
+ .for_apply
|
||||
+ .as_ref()
|
||||
+ .and_then(|i| i.base_iface().mac_address.as_ref());
|
||||
+
|
||||
+ assert_eq!(bond_mac, Some(&"AA:BB:CC:DD:EE:FF".to_string()));
|
||||
+}
|
||||
+
|
||||
+#[test]
|
||||
+fn test_copy_mac_from_new_down_interface_fails() {
|
||||
+ let desired: NetworkState = serde_yaml::from_str(
|
||||
+ r"---
|
||||
+ interfaces:
|
||||
+ - name: eth1
|
||||
+ type: ethernet
|
||||
+ state: down
|
||||
+ mac-address: AA:BB:CC:DD:EE:FF
|
||||
+ - name: bond99
|
||||
+ type: bond
|
||||
+ state: up
|
||||
+ copy-mac-from: eth1
|
||||
+ link-aggregation:
|
||||
+ mode: balance-rr
|
||||
+ port:
|
||||
+ - eth2",
|
||||
+ )
|
||||
+ .unwrap();
|
||||
+ let current: NetworkState = serde_yaml::from_str(
|
||||
+ r"---
|
||||
+ interfaces:
|
||||
+ - name: eth2
|
||||
+ type: ethernet
|
||||
+ state: up
|
||||
+ mac-address: 11:22:33:44:55:66",
|
||||
+ )
|
||||
+ .unwrap();
|
||||
+
|
||||
+ let result =
|
||||
+ MergedNetworkState::new(desired, current, Default::default(), false);
|
||||
+
|
||||
+ let e = result.unwrap_err();
|
||||
+ assert_eq!(e.kind(), ErrorKind::InvalidArgument);
|
||||
+ assert!(e.msg().contains("eth1"));
|
||||
+ assert!(e.msg().contains("copy-mac-from"));
|
||||
+}
|
||||
@ -0,0 +1,275 @@
|
||||
From 45e5ed38d1f157e5963628041a461f4073c3295d Mon Sep 17 00:00:00 2001
|
||||
From: Josephine Pfeiffer <josie@redhat.com>
|
||||
Date: Mon, 6 Jul 2026 20:02:47 +0200
|
||||
Subject: [PATCH 3/6] nispor: derive alt-name link match from interface
|
||||
identifier
|
||||
|
||||
Route systemd `.link` `[Match]` generation through a single
|
||||
`gen_systemd_match_rules` that dispatches on the interface identifier,
|
||||
resolve the identifier in `effective_identifier`, and move the shared
|
||||
`PermanentMACAddress`/`MACAddress`/`Driver` formatting into
|
||||
`mac_match_rules`.
|
||||
|
||||
When the desired state omits the identifier, `effective_identifier`
|
||||
falls back to the current interface's identifier. The guard that makes
|
||||
this safe for stacked interfaces comes next.
|
||||
|
||||
Signed-off-by: Josephine Pfeiffer <josie@redhat.com>
|
||||
---
|
||||
rust/src/lib/nispor/alt_name.rs | 213 +++++++++++++++++---------------
|
||||
1 file changed, 116 insertions(+), 97 deletions(-)
|
||||
|
||||
diff --git a/rust/src/lib/nispor/alt_name.rs b/rust/src/lib/nispor/alt_name.rs
|
||||
index a8188934..3251f31c 100644
|
||||
--- a/rust/src/lib/nispor/alt_name.rs
|
||||
+++ b/rust/src/lib/nispor/alt_name.rs
|
||||
@@ -6,7 +6,7 @@ use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
|
||||
use crate::{
|
||||
BaseInterface, ErrorKind, InterfaceIdentifier, InterfaceState,
|
||||
- InterfaceType, MergedInterfaces, NmstateError,
|
||||
+ MergedInterfaces, NmstateError,
|
||||
};
|
||||
|
||||
/// Comment added into our generated link files
|
||||
@@ -199,70 +199,133 @@ async fn remove_systemd_network_link_file(
|
||||
}
|
||||
}
|
||||
|
||||
-// The `OriginalName` match rule is unstable because kernel interface
|
||||
-// name can not be predicted.
|
||||
-// For ethernet interface, using `OriginalName` match rule is unstable because
|
||||
-// kernel interface name cannot be predicted. Hence use try in these order:
|
||||
-// * permanent MAC address + driver name[1]
|
||||
-// * permanent MAC address
|
||||
-// * PCI address
|
||||
-// * MAC address + driver defined in current
|
||||
-// * MAC address defined in desired
|
||||
-// * Interface name defined in desired
|
||||
-// For non-ethernet interface, we use `OriginalName` match rule.
|
||||
-// [1]: Azure VM with `Microsoft Azure Network Adapter` has two
|
||||
-// NICs holding the same MAC address. We need to make sure we matched
|
||||
-// the correct by including driver information also.
|
||||
-fn gen_systemd_network_link_match_rules(
|
||||
+fn gen_systemd_match_rules(
|
||||
+ des_iface: &BaseInterface,
|
||||
+ cur_iface: Option<&BaseInterface>,
|
||||
+) -> Result<String, NmstateError> {
|
||||
+ let (identifier, src_iface) = effective_identifier(des_iface, cur_iface);
|
||||
+
|
||||
+ match identifier {
|
||||
+ InterfaceIdentifier::MacAddress => {
|
||||
+ // Include driver in case two NICs hold the same MAC.
|
||||
+ // (e.g. Microsoft Azure Network Adapter)
|
||||
+ let driver = cur_iface.and_then(|i| i.driver.as_ref());
|
||||
+ mac_match_rules(
|
||||
+ src_iface.permanent_mac_address.as_ref(),
|
||||
+ src_iface.mac_address.as_ref(),
|
||||
+ driver,
|
||||
+ )
|
||||
+ .ok_or_else(|| {
|
||||
+ NmstateError::new(
|
||||
+ ErrorKind::Bug,
|
||||
+ format!(
|
||||
+ "Got no MAC address for `identifier:mac-address` on \
|
||||
+ interface {}, should be failed by previous checker",
|
||||
+ des_iface.name
|
||||
+ ),
|
||||
+ )
|
||||
+ })
|
||||
+ }
|
||||
+ InterfaceIdentifier::PciAddress => {
|
||||
+ if let Some(pci_addr) = src_iface.pci_address.as_ref() {
|
||||
+ Ok(format!("Path=pci-{pci_addr}"))
|
||||
+ } else {
|
||||
+ Err(NmstateError::new(
|
||||
+ ErrorKind::Bug,
|
||||
+ format!(
|
||||
+ "Got no PCI address for `identifier:pci-address` on \
|
||||
+ interface {}, should be failed by previous checker",
|
||||
+ des_iface.name
|
||||
+ ),
|
||||
+ ))
|
||||
+ }
|
||||
+ }
|
||||
+ InterfaceIdentifier::Name => {
|
||||
+ Ok(gen_systemd_name_match_rules(des_iface, cur_iface))
|
||||
+ }
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+// Resolve the identifier to match on and the interface that supplies its
|
||||
+// MAC/PCI value. Desired state's explicit identifier is honoured as-is; when it
|
||||
+// is absent the current interface's identifier is inherited.
|
||||
+fn effective_identifier<'a>(
|
||||
+ des_iface: &'a BaseInterface,
|
||||
+ cur_iface: Option<&'a BaseInterface>,
|
||||
+) -> (InterfaceIdentifier, &'a BaseInterface) {
|
||||
+ if let Some(id) = des_iface.identifier {
|
||||
+ return (id, des_iface);
|
||||
+ }
|
||||
+ if let Some(cur) = cur_iface {
|
||||
+ match cur.identifier {
|
||||
+ Some(InterfaceIdentifier::MacAddress) => {
|
||||
+ return (InterfaceIdentifier::MacAddress, cur);
|
||||
+ }
|
||||
+ Some(InterfaceIdentifier::PciAddress) => {
|
||||
+ return (InterfaceIdentifier::PciAddress, cur);
|
||||
+ }
|
||||
+ _ => {}
|
||||
+ }
|
||||
+ }
|
||||
+ (InterfaceIdentifier::Name, des_iface)
|
||||
+}
|
||||
+
|
||||
+// Fallback used when the resolved identifier is `name` (the default).
|
||||
+//
|
||||
+// The kernel name is unpredictable, so match on the most stable attribute
|
||||
+// available: permanent MAC, then PCI path, then current MAC, each with driver
|
||||
+// when known to disambiguate NICs sharing a MAC (e.g. Azure's Microsoft Azure
|
||||
+// Network Adapter).
|
||||
+fn gen_systemd_name_match_rules(
|
||||
des_iface: &BaseInterface,
|
||||
cur_iface: Option<&BaseInterface>,
|
||||
) -> String {
|
||||
if let Some(cur_iface) = cur_iface {
|
||||
- match (
|
||||
+ if let Some(rules) = mac_match_rules(
|
||||
cur_iface.permanent_mac_address.as_ref(),
|
||||
+ None,
|
||||
cur_iface.driver.as_ref(),
|
||||
- cur_iface.pci_address.as_ref(),
|
||||
+ ) {
|
||||
+ rules
|
||||
+ } else if let Some(pci_addr) = cur_iface.pci_address.as_ref() {
|
||||
+ format!("Path=pci-{pci_addr}")
|
||||
+ } else if let Some(rules) = mac_match_rules(
|
||||
+ None,
|
||||
cur_iface.mac_address.as_ref(),
|
||||
+ cur_iface.driver.as_ref(),
|
||||
) {
|
||||
- (Some(perm_mac), Some(driver), _pci_addr, _mac) => {
|
||||
- format!("PermanentMACAddress={perm_mac}\nDriver={driver}")
|
||||
- }
|
||||
- (Some(perm_mac), None, _pci_addr, _mac) => {
|
||||
- format!("PermanentMACAddress={perm_mac}")
|
||||
- }
|
||||
- (None, _driver, Some(pci_addr), _mac) => {
|
||||
- format!("Path=pci-{pci_addr}")
|
||||
- }
|
||||
- (None, Some(driver), None, Some(mac)) => {
|
||||
- format!("MACAddress={mac}\nDriver={driver}")
|
||||
- }
|
||||
- (None, None, None, Some(mac)) => {
|
||||
- format!("MACAddress={mac}")
|
||||
- }
|
||||
- _ => {
|
||||
- format!("OriginalName={}", cur_iface.name)
|
||||
- }
|
||||
- }
|
||||
- } else {
|
||||
- // User is creating software interface or gen_conf mode
|
||||
- if des_iface.iface_type == InterfaceType::Ethernet {
|
||||
- match (des_iface.mac_address.as_ref(), des_iface.driver.as_ref()) {
|
||||
- (Some(mac), Some(driver)) => {
|
||||
- format!("MACAddress={mac}\nDriver={driver}")
|
||||
- }
|
||||
- (Some(mac), None) => {
|
||||
- format!("MACAddress={mac}")
|
||||
- }
|
||||
- (None, _) => {
|
||||
- format!("OriginalName={}", des_iface.name)
|
||||
- }
|
||||
- }
|
||||
+ rules
|
||||
} else {
|
||||
- format!("OriginalName={}", des_iface.name)
|
||||
+ format!("OriginalName={}", cur_iface.name)
|
||||
}
|
||||
+ } else {
|
||||
+ // User is creating the ethernet interface or gen_conf mode
|
||||
+ mac_match_rules(
|
||||
+ None,
|
||||
+ des_iface.mac_address.as_ref(),
|
||||
+ des_iface.driver.as_ref(),
|
||||
+ )
|
||||
+ .unwrap_or_else(|| format!("OriginalName={}", des_iface.name))
|
||||
}
|
||||
}
|
||||
|
||||
+fn mac_match_rules(
|
||||
+ permanent_mac: Option<&String>,
|
||||
+ mac: Option<&String>,
|
||||
+ driver: Option<&String>,
|
||||
+) -> Option<String> {
|
||||
+ let mut rules = if let Some(mac) = permanent_mac {
|
||||
+ format!("PermanentMACAddress={mac}")
|
||||
+ } else {
|
||||
+ let mac = mac?;
|
||||
+ format!("MACAddress={mac}")
|
||||
+ };
|
||||
+ if let Some(driver) = driver {
|
||||
+ write!(rules, "\nDriver={driver}").ok();
|
||||
+ }
|
||||
+ Some(rules)
|
||||
+}
|
||||
+
|
||||
async fn save_systemd_network_link_file(
|
||||
des_base_iface: &BaseInterface,
|
||||
cur_base_iface: Option<&BaseInterface>,
|
||||
@@ -283,51 +346,7 @@ async fn save_systemd_network_link_file(
|
||||
}
|
||||
let iface_name = des_base_iface.name.as_str();
|
||||
|
||||
- let match_rules = match des_base_iface.identifier.as_ref() {
|
||||
- Some(InterfaceIdentifier::MacAddress) => {
|
||||
- let mut match_rules = if let Some(mac) =
|
||||
- des_base_iface.permanent_mac_address.as_ref()
|
||||
- {
|
||||
- format!("PermanentMACAddress={mac}")
|
||||
- } else if let Some(mac) = des_base_iface.mac_address.as_ref() {
|
||||
- format!("MACAddress={mac}")
|
||||
- } else {
|
||||
- return Err(NmstateError::new(
|
||||
- ErrorKind::Bug,
|
||||
- format!(
|
||||
- "Got no MAC address for `identifier:mac-address` on \
|
||||
- interface {}, should be failed by previous checker",
|
||||
- des_base_iface.name
|
||||
- ),
|
||||
- ));
|
||||
- };
|
||||
- // Include driver in match rule in case we have two NIC holding the
|
||||
- // same MAC. (e.g. M$ Azure - Microsoft Azure Network Adapter)
|
||||
- if let Some(cur_base_iface) = cur_base_iface.as_ref()
|
||||
- && let Some(driver) = cur_base_iface.driver.as_ref()
|
||||
- {
|
||||
- write!(match_rules, "\nDriver={driver}").ok();
|
||||
- }
|
||||
- match_rules
|
||||
- }
|
||||
- Some(InterfaceIdentifier::PciAddress) => {
|
||||
- if let Some(pci_addr) = des_base_iface.pci_address.as_ref() {
|
||||
- format!("Path=pci-{pci_addr}")
|
||||
- } else {
|
||||
- return Err(NmstateError::new(
|
||||
- ErrorKind::Bug,
|
||||
- format!(
|
||||
- "Got no PCI address for `identifier:pci-address` on \
|
||||
- interface {}, should be failed by previous checker",
|
||||
- des_base_iface.name
|
||||
- ),
|
||||
- ));
|
||||
- }
|
||||
- }
|
||||
- _ => {
|
||||
- gen_systemd_network_link_match_rules(des_base_iface, cur_base_iface)
|
||||
- }
|
||||
- };
|
||||
+ let match_rules = gen_systemd_match_rules(des_base_iface, cur_base_iface)?;
|
||||
|
||||
let file_path = gen_systemd_link_file_path(iface_name);
|
||||
let mut content = {
|
||||
--
|
||||
2.54.0
|
||||
|
||||
@ -0,0 +1,405 @@
|
||||
From 43a8ad166ac0abffd1c0df047819b538bd68c3d5 Mon Sep 17 00:00:00 2001
|
||||
From: Josephine Pfeiffer <josie@redhat.com>
|
||||
Date: Mon, 6 Jul 2026 20:03:33 +0200
|
||||
Subject: [PATCH 4/6] nispor: keep OriginalName link match for stacked
|
||||
interfaces
|
||||
|
||||
A VLAN takes the MAC of its lower device, and on a bond that MAC is
|
||||
borrowed from the first port and shifts with the port set. On an
|
||||
alt-names-only reapply the interface already exists, so falling back to
|
||||
its identifier would pin the persisted `.link` `[Match]` to that
|
||||
borrowed MAC and lose the match once the ports change.
|
||||
|
||||
Fall back to a current mac-address/pci-address identifier only when the
|
||||
live interface owns it (a permanent MAC or a PCI address); otherwise use
|
||||
`OriginalName` on non-ethernet interfaces. An explicit
|
||||
`identifier: mac-address` is still honoured.
|
||||
|
||||
Resolves: https://redhat.atlassian.net/browse/RHEL-167955
|
||||
Resolves: https://redhat.atlassian.net/browse/NMT-2193
|
||||
Signed-off-by: Josephine Pfeiffer <josie@redhat.com>
|
||||
---
|
||||
rust/src/lib/nispor/alt_name.rs | 269 ++++++++++++++++++++++++++++-
|
||||
tests/integration/alt_name_test.py | 41 +++++
|
||||
2 files changed, 301 insertions(+), 9 deletions(-)
|
||||
|
||||
diff --git a/rust/src/lib/nispor/alt_name.rs b/rust/src/lib/nispor/alt_name.rs
|
||||
index 3251f31c..4471e9e4 100644
|
||||
--- a/rust/src/lib/nispor/alt_name.rs
|
||||
+++ b/rust/src/lib/nispor/alt_name.rs
|
||||
@@ -6,7 +6,7 @@ use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
|
||||
use crate::{
|
||||
BaseInterface, ErrorKind, InterfaceIdentifier, InterfaceState,
|
||||
- MergedInterfaces, NmstateError,
|
||||
+ InterfaceType, MergedInterfaces, NmstateError,
|
||||
};
|
||||
|
||||
/// Comment added into our generated link files
|
||||
@@ -247,8 +247,13 @@ fn gen_systemd_match_rules(
|
||||
}
|
||||
|
||||
// Resolve the identifier to match on and the interface that supplies its
|
||||
-// MAC/PCI value. Desired state's explicit identifier is honoured as-is; when it
|
||||
-// is absent the current interface's identifier is inherited.
|
||||
+// MAC/PCI value. Desired state's explicit identifier is honoured as-is. When it
|
||||
+// is absent, a current `mac-address`/`pci-address` identifier is inherited only
|
||||
+// if the live interface owns that attribute (a permanent MAC or a PCI address),
|
||||
+// so a stacked interface never inherits a borrowed, unstable MAC; it falls
|
||||
+// through to the name match instead. A persisted `.link` file outlives the
|
||||
+// current port set, so it must never pin to a borrowed MAC even though NM
|
||||
+// profile matching may.
|
||||
fn effective_identifier<'a>(
|
||||
des_iface: &'a BaseInterface,
|
||||
cur_iface: Option<&'a BaseInterface>,
|
||||
@@ -258,10 +263,14 @@ fn effective_identifier<'a>(
|
||||
}
|
||||
if let Some(cur) = cur_iface {
|
||||
match cur.identifier {
|
||||
- Some(InterfaceIdentifier::MacAddress) => {
|
||||
+ Some(InterfaceIdentifier::MacAddress)
|
||||
+ if cur.permanent_mac_address.is_some() =>
|
||||
+ {
|
||||
return (InterfaceIdentifier::MacAddress, cur);
|
||||
}
|
||||
- Some(InterfaceIdentifier::PciAddress) => {
|
||||
+ Some(InterfaceIdentifier::PciAddress)
|
||||
+ if cur.pci_address.is_some() =>
|
||||
+ {
|
||||
return (InterfaceIdentifier::PciAddress, cur);
|
||||
}
|
||||
_ => {}
|
||||
@@ -272,14 +281,20 @@ fn effective_identifier<'a>(
|
||||
|
||||
// Fallback used when the resolved identifier is `name` (the default).
|
||||
//
|
||||
-// The kernel name is unpredictable, so match on the most stable attribute
|
||||
-// available: permanent MAC, then PCI path, then current MAC, each with driver
|
||||
-// when known to disambiguate NICs sharing a MAC (e.g. Azure's Microsoft Azure
|
||||
-// Network Adapter).
|
||||
+// For ethernet the kernel name is unpredictable, so match on the most stable
|
||||
+// attribute available: permanent MAC, then PCI path, then current MAC, each
|
||||
+// with driver when known to disambiguate NICs sharing a MAC (e.g. Azure's
|
||||
+// Microsoft Azure Network Adapter). For non-ethernet the kernel name is
|
||||
+// user-assigned and the MAC is borrowed from a lower device (a VLAN takes its
|
||||
+// bond's MAC, itself from the first enslaved port), so use `OriginalName`.
|
||||
fn gen_systemd_name_match_rules(
|
||||
des_iface: &BaseInterface,
|
||||
cur_iface: Option<&BaseInterface>,
|
||||
) -> String {
|
||||
+ if des_iface.iface_type != InterfaceType::Ethernet {
|
||||
+ return format!("OriginalName={}", des_iface.name);
|
||||
+ }
|
||||
+
|
||||
if let Some(cur_iface) = cur_iface {
|
||||
if let Some(rules) = mac_match_rules(
|
||||
cur_iface.permanent_mac_address.as_ref(),
|
||||
@@ -391,3 +406,239 @@ async fn save_systemd_network_link_file(
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
+
|
||||
+#[cfg(test)]
|
||||
+mod tests {
|
||||
+ use super::*;
|
||||
+
|
||||
+ // Desired VLAN has no MAC; the live one carries the inherited bond MAC.
|
||||
+ #[test]
|
||||
+ fn vlan_reapply_uses_original_name() {
|
||||
+ let mut des = BaseInterface::new();
|
||||
+ des.name = "external0".to_string();
|
||||
+ des.iface_type = InterfaceType::Vlan;
|
||||
+
|
||||
+ let mut cur = des.clone();
|
||||
+ cur.mac_address = Some("52:54:00:80:77:C9".to_string());
|
||||
+
|
||||
+ let rules = gen_systemd_match_rules(&des, Some(&cur)).unwrap();
|
||||
+ assert_eq!(rules, "OriginalName=external0");
|
||||
+ }
|
||||
+
|
||||
+ // Create path (no current iface), non-VLAN software type, MAC ignored.
|
||||
+ #[test]
|
||||
+ fn software_iface_create_uses_original_name() {
|
||||
+ let mut des = BaseInterface::new();
|
||||
+ des.name = "bond0".to_string();
|
||||
+ des.iface_type = InterfaceType::Bond;
|
||||
+ des.mac_address = Some("52:54:00:80:77:C9".to_string());
|
||||
+
|
||||
+ let rules = gen_systemd_match_rules(&des, None).unwrap();
|
||||
+ assert_eq!(rules, "OriginalName=bond0");
|
||||
+ }
|
||||
+
|
||||
+ #[test]
|
||||
+ fn ethernet_reapply_uses_permanent_mac() {
|
||||
+ let mut des = BaseInterface::new();
|
||||
+ des.name = "eth0".to_string();
|
||||
+ des.iface_type = InterfaceType::Ethernet;
|
||||
+
|
||||
+ let mut cur = des.clone();
|
||||
+ cur.permanent_mac_address = Some("52:54:00:11:22:33".to_string());
|
||||
+
|
||||
+ let rules = gen_systemd_match_rules(&des, Some(&cur)).unwrap();
|
||||
+ assert_eq!(rules, "PermanentMACAddress=52:54:00:11:22:33");
|
||||
+ }
|
||||
+
|
||||
+ // A live mac-address identifier without a permanent MAC (borrowed from a
|
||||
+ // lower device) is not inherited.
|
||||
+ #[test]
|
||||
+ fn inherited_mac_ignored_for_virtual_iface() {
|
||||
+ let mut des = BaseInterface::new();
|
||||
+ des.name = "vlan0".to_string();
|
||||
+ des.iface_type = InterfaceType::Vlan;
|
||||
+
|
||||
+ let mut cur = des.clone();
|
||||
+ cur.identifier = Some(InterfaceIdentifier::MacAddress);
|
||||
+ cur.mac_address = Some("52:54:00:80:77:C9".to_string());
|
||||
+
|
||||
+ let rules = gen_systemd_match_rules(&des, Some(&cur)).unwrap();
|
||||
+ assert_eq!(rules, "OriginalName=vlan0");
|
||||
+ }
|
||||
+
|
||||
+ // A physical NIC with a live mac-address identifier owns a permanent MAC,
|
||||
+ // so an alt-names-only reapply inherits it and matches by permanent MAC.
|
||||
+ #[test]
|
||||
+ fn inherited_mac_uses_permanent_for_physical() {
|
||||
+ let mut des = BaseInterface::new();
|
||||
+ des.name = "eth0".to_string();
|
||||
+ des.iface_type = InterfaceType::Ethernet;
|
||||
+
|
||||
+ let mut cur = des.clone();
|
||||
+ cur.identifier = Some(InterfaceIdentifier::MacAddress);
|
||||
+ cur.permanent_mac_address = Some("52:54:00:11:22:33".to_string());
|
||||
+
|
||||
+ let rules = gen_systemd_match_rules(&des, Some(&cur)).unwrap();
|
||||
+ assert_eq!(rules, "PermanentMACAddress=52:54:00:11:22:33");
|
||||
+ }
|
||||
+
|
||||
+ // A live pci-address identifier is inherited over the name fallback's
|
||||
+ // default order, pinning the PCI path the user originally chose.
|
||||
+ #[test]
|
||||
+ fn inherited_pci_identifier_preserved() {
|
||||
+ let mut des = BaseInterface::new();
|
||||
+ des.name = "eth0".to_string();
|
||||
+ des.iface_type = InterfaceType::Ethernet;
|
||||
+
|
||||
+ let mut cur = des.clone();
|
||||
+ cur.identifier = Some(InterfaceIdentifier::PciAddress);
|
||||
+ cur.permanent_mac_address = Some("52:54:00:11:22:33".to_string());
|
||||
+ cur.pci_address =
|
||||
+ Some(crate::PciAddress::try_from("0000:00:1f.6").unwrap());
|
||||
+
|
||||
+ let rules = gen_systemd_match_rules(&des, Some(&cur)).unwrap();
|
||||
+ assert_eq!(rules, "Path=pci-0000:00:1f.6");
|
||||
+ }
|
||||
+
|
||||
+ // Explicit `identifier: name` wins over the live mac-address identifier.
|
||||
+ #[test]
|
||||
+ fn explicit_name_identifier_keeps_original_name() {
|
||||
+ let mut des = BaseInterface::new();
|
||||
+ des.name = "vlan0".to_string();
|
||||
+ des.iface_type = InterfaceType::Vlan;
|
||||
+ des.identifier = Some(InterfaceIdentifier::Name);
|
||||
+
|
||||
+ let mut cur = des.clone();
|
||||
+ cur.identifier = Some(InterfaceIdentifier::MacAddress);
|
||||
+ cur.mac_address = Some("52:54:00:80:77:C9".to_string());
|
||||
+
|
||||
+ let rules = gen_systemd_match_rules(&des, Some(&cur)).unwrap();
|
||||
+ assert_eq!(rules, "OriginalName=vlan0");
|
||||
+ }
|
||||
+
|
||||
+ // mac-address identifier appends the live driver to disambiguate two NICs
|
||||
+ // sharing one MAC.
|
||||
+ #[test]
|
||||
+ fn mac_identifier_appends_current_driver() {
|
||||
+ let mut des = BaseInterface::new();
|
||||
+ des.name = "eth0".to_string();
|
||||
+ des.iface_type = InterfaceType::Ethernet;
|
||||
+ des.identifier = Some(InterfaceIdentifier::MacAddress);
|
||||
+ des.mac_address = Some("52:54:00:11:22:33".to_string());
|
||||
+
|
||||
+ let mut cur = des.clone();
|
||||
+ cur.driver = Some("e1000e".to_string());
|
||||
+
|
||||
+ let rules = gen_systemd_match_rules(&des, Some(&cur)).unwrap();
|
||||
+ assert_eq!(rules, "MACAddress=52:54:00:11:22:33\nDriver=e1000e");
|
||||
+ }
|
||||
+
|
||||
+ #[test]
|
||||
+ fn pci_identifier_uses_path() {
|
||||
+ let mut des = BaseInterface::new();
|
||||
+ des.name = "eth0".to_string();
|
||||
+ des.iface_type = InterfaceType::Ethernet;
|
||||
+ des.identifier = Some(InterfaceIdentifier::PciAddress);
|
||||
+ des.pci_address =
|
||||
+ Some(crate::PciAddress::try_from("0000:00:1f.6").unwrap());
|
||||
+
|
||||
+ let rules = gen_systemd_match_rules(&des, None).unwrap();
|
||||
+ assert_eq!(rules, "Path=pci-0000:00:1f.6");
|
||||
+ }
|
||||
+
|
||||
+ #[test]
|
||||
+ fn mac_identifier_without_mac_is_bug() {
|
||||
+ let mut des = BaseInterface::new();
|
||||
+ des.name = "eth0".to_string();
|
||||
+ des.iface_type = InterfaceType::Ethernet;
|
||||
+ des.identifier = Some(InterfaceIdentifier::MacAddress);
|
||||
+
|
||||
+ let e = gen_systemd_match_rules(&des, None).unwrap_err();
|
||||
+ assert_eq!(e.kind(), ErrorKind::Bug);
|
||||
+ }
|
||||
+
|
||||
+ #[test]
|
||||
+ fn pci_identifier_without_pci_is_bug() {
|
||||
+ let mut des = BaseInterface::new();
|
||||
+ des.name = "eth0".to_string();
|
||||
+ des.iface_type = InterfaceType::Ethernet;
|
||||
+ des.identifier = Some(InterfaceIdentifier::PciAddress);
|
||||
+
|
||||
+ let e = gen_systemd_match_rules(&des, None).unwrap_err();
|
||||
+ assert_eq!(e.kind(), ErrorKind::Bug);
|
||||
+ }
|
||||
+
|
||||
+ #[test]
|
||||
+ fn name_fallback_uses_permanent_mac_and_driver() {
|
||||
+ let mut des = BaseInterface::new();
|
||||
+ des.name = "eth0".to_string();
|
||||
+ des.iface_type = InterfaceType::Ethernet;
|
||||
+
|
||||
+ let mut cur = des.clone();
|
||||
+ cur.permanent_mac_address = Some("52:54:00:11:22:33".to_string());
|
||||
+ cur.driver = Some("e1000e".to_string());
|
||||
+
|
||||
+ let rules = gen_systemd_match_rules(&des, Some(&cur)).unwrap();
|
||||
+ assert_eq!(
|
||||
+ rules,
|
||||
+ "PermanentMACAddress=52:54:00:11:22:33\nDriver=e1000e"
|
||||
+ );
|
||||
+ }
|
||||
+
|
||||
+ #[test]
|
||||
+ fn name_fallback_uses_current_mac_and_driver() {
|
||||
+ let mut des = BaseInterface::new();
|
||||
+ des.name = "eth0".to_string();
|
||||
+ des.iface_type = InterfaceType::Ethernet;
|
||||
+
|
||||
+ let mut cur = des.clone();
|
||||
+ cur.mac_address = Some("52:54:00:11:22:33".to_string());
|
||||
+ cur.driver = Some("e1000e".to_string());
|
||||
+
|
||||
+ let rules = gen_systemd_match_rules(&des, Some(&cur)).unwrap();
|
||||
+ assert_eq!(rules, "MACAddress=52:54:00:11:22:33\nDriver=e1000e");
|
||||
+ }
|
||||
+
|
||||
+ // Explicit desired mac-address identifier is honoured on a non-ethernet
|
||||
+ // iface too, matching by MAC rather than falling back to OriginalName.
|
||||
+ #[test]
|
||||
+ fn non_eth_explicit_mac_identifier_uses_mac() {
|
||||
+ let mut des = BaseInterface::new();
|
||||
+ des.name = "vlan0".to_string();
|
||||
+ des.iface_type = InterfaceType::Vlan;
|
||||
+ des.identifier = Some(InterfaceIdentifier::MacAddress);
|
||||
+ des.mac_address = Some("52:54:00:11:22:33".to_string());
|
||||
+
|
||||
+ let rules = gen_systemd_match_rules(&des, None).unwrap();
|
||||
+ assert_eq!(rules, "MACAddress=52:54:00:11:22:33");
|
||||
+ }
|
||||
+
|
||||
+ // Permanent MAC wins when both a permanent and a current MAC are present.
|
||||
+ #[test]
|
||||
+ fn permanent_mac_preferred_over_current() {
|
||||
+ let mut des = BaseInterface::new();
|
||||
+ des.name = "eth0".to_string();
|
||||
+ des.iface_type = InterfaceType::Ethernet;
|
||||
+
|
||||
+ let mut cur = des.clone();
|
||||
+ cur.permanent_mac_address = Some("52:54:00:11:22:33".to_string());
|
||||
+ cur.mac_address = Some("52:54:00:AA:BB:CC".to_string());
|
||||
+
|
||||
+ let rules = gen_systemd_match_rules(&des, Some(&cur)).unwrap();
|
||||
+ assert_eq!(rules, "PermanentMACAddress=52:54:00:11:22:33");
|
||||
+ }
|
||||
+
|
||||
+ // Ethernet reapply with no MAC, PCI, or permanent MAC on the live iface
|
||||
+ // falls back to its current kernel name.
|
||||
+ #[test]
|
||||
+ fn eth_name_fallback_with_no_attributes() {
|
||||
+ let mut des = BaseInterface::new();
|
||||
+ des.name = "eth0".to_string();
|
||||
+ des.iface_type = InterfaceType::Ethernet;
|
||||
+
|
||||
+ let cur = des.clone();
|
||||
+
|
||||
+ let rules = gen_systemd_match_rules(&des, Some(&cur)).unwrap();
|
||||
+ assert_eq!(rules, "OriginalName=eth0");
|
||||
+ }
|
||||
+}
|
||||
diff --git a/tests/integration/alt_name_test.py b/tests/integration/alt_name_test.py
|
||||
index a72d4ad0..f53bfe62 100644
|
||||
--- a/tests/integration/alt_name_test.py
|
||||
+++ b/tests/integration/alt_name_test.py
|
||||
@@ -1,6 +1,7 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import copy
|
||||
+import os
|
||||
|
||||
import pytest
|
||||
|
||||
@@ -73,6 +74,13 @@ def eth1_with_alt_names(eth1_up):
|
||||
assert get_ip_link_alt_names("eth1") == []
|
||||
|
||||
|
||||
+def read_systemd_link_file(iface_name):
|
||||
+ path = f"/etc/systemd/network/98-nmstate-{iface_name}.link"
|
||||
+ assert os.path.exists(path), f"link file {path} not found"
|
||||
+ with open(path) as f:
|
||||
+ return f.read()
|
||||
+
|
||||
+
|
||||
def udev_trigger_check_alt_names(iface_name, expected_alt_names):
|
||||
exec_cmd(
|
||||
"udevadm trigger --settle --action add "
|
||||
@@ -235,6 +243,39 @@ class TestAltNames:
|
||||
assert iface[VLAN.CONFIG_SUBTREE][VLAN.BASE_IFACE] == "eth1"
|
||||
assert iface[VLAN.CONFIG_SUBTREE][VLAN.ID] == 101
|
||||
|
||||
+ # https://issues.redhat.com/browse/RHEL-167955
|
||||
+ @pytest.mark.tier1
|
||||
+ def test_vlan_on_bond_alt_name_uses_original_name_on_reapply(
|
||||
+ self, eth1_up, eth2_up
|
||||
+ ):
|
||||
+ vlan_name = f"{TEST_BOND_NIC}.101"
|
||||
+ alt_name = "altone"
|
||||
+ with bond_interface(TEST_BOND_NIC, ["eth1", "eth2"]), vlan_interface(
|
||||
+ vlan_name, 101, TEST_BOND_NIC
|
||||
+ ) as vlan_state:
|
||||
+ desired_state = copy.deepcopy(vlan_state)
|
||||
+ iface = desired_state[Interface.KEY][0]
|
||||
+ iface[InterfaceAltName.KEY] = [{InterfaceAltName.NAME: alt_name}]
|
||||
+ # The VLAN already exists, so persisting alt-names takes the
|
||||
+ # current-state path. Apply twice: the reapply, not the first
|
||||
+ # write, is what must keep OriginalName.
|
||||
+ for _ in range(2):
|
||||
+ libnmstate.apply(desired_state)
|
||||
+ content = read_systemd_link_file(vlan_name)
|
||||
+ lines = content.splitlines()
|
||||
+ assert f"OriginalName={vlan_name}" in lines
|
||||
+ assert f"Name={vlan_name}" in lines
|
||||
+ # Any hardware key shifts the match off the kernel name;
|
||||
+ # "MACAddress=" also catches "PermanentMACAddress=".
|
||||
+ for key in ("MACAddress=", "Path=", "Driver="):
|
||||
+ assert key not in content
|
||||
+ assert retry_till_true_or_timeout(
|
||||
+ RETRY_TIMEOUT,
|
||||
+ udev_trigger_check_alt_names,
|
||||
+ vlan_name,
|
||||
+ [alt_name],
|
||||
+ )
|
||||
+
|
||||
# https://issues.redhat.com/browse/RHEL-126508
|
||||
@pytest.mark.tier1
|
||||
def test_ref_alt_name_in_route(self, eth1_with_alt_names):
|
||||
--
|
||||
2.54.0
|
||||
|
||||
@ -0,0 +1,192 @@
|
||||
From a1f5311a0bdc3f582dd54004ee8b67e90733bd0e Mon Sep 17 00:00:00 2001
|
||||
From: Josephine Pfeiffer <josie@redhat.com>
|
||||
Date: Tue, 7 Jul 2026 08:20:26 +0200
|
||||
Subject: [PATCH 5/6] nispor: pin physical non-ethernet alt-name match to owned
|
||||
identifier
|
||||
|
||||
The name fallback matched every non-ethernet interface on OriginalName,
|
||||
on the assumption that a non-ethernet kernel name is user-assigned and
|
||||
its MAC borrowed. That holds for VLAN, bond, and bridge, but not for
|
||||
physical InfiniBand: its kernel name comes from probe order and udev can
|
||||
rename it, so an OriginalName match can silently break after a reboot.
|
||||
|
||||
Prefer a permanent MAC, then a PCI address the live interface owns,
|
||||
before falling back to OriginalName, and never match on a borrowed
|
||||
current MAC. A VLAN over a bond owns neither, so it still matches on
|
||||
OriginalName; a physical InfiniBand pins to its permanent MAC or PCI
|
||||
path.
|
||||
|
||||
Signed-off-by: Josephine Pfeiffer <josie@redhat.com>
|
||||
---
|
||||
rust/src/lib/nispor/alt_name.rs | 119 +++++++++++++++++++++++---------
|
||||
1 file changed, 85 insertions(+), 34 deletions(-)
|
||||
|
||||
diff --git a/rust/src/lib/nispor/alt_name.rs b/rust/src/lib/nispor/alt_name.rs
|
||||
index 4471e9e4..220e6919 100644
|
||||
--- a/rust/src/lib/nispor/alt_name.rs
|
||||
+++ b/rust/src/lib/nispor/alt_name.rs
|
||||
@@ -246,14 +246,10 @@ fn gen_systemd_match_rules(
|
||||
}
|
||||
}
|
||||
|
||||
-// Resolve the identifier to match on and the interface that supplies its
|
||||
-// MAC/PCI value. Desired state's explicit identifier is honoured as-is. When it
|
||||
-// is absent, a current `mac-address`/`pci-address` identifier is inherited only
|
||||
-// if the live interface owns that attribute (a permanent MAC or a PCI address),
|
||||
-// so a stacked interface never inherits a borrowed, unstable MAC; it falls
|
||||
-// through to the name match instead. A persisted `.link` file outlives the
|
||||
-// current port set, so it must never pin to a borrowed MAC even though NM
|
||||
-// profile matching may.
|
||||
+// Pick the identifier and the interface holding its value. An explicit desired
|
||||
+// identifier wins; a live mac/pci identifier is inherited only when the iface
|
||||
+// owns it (permanent MAC or PCI), never a stacked iface's borrowed MAC, since
|
||||
+// the persisted `.link` file outlives the port set that MAC came from.
|
||||
fn effective_identifier<'a>(
|
||||
des_iface: &'a BaseInterface,
|
||||
cur_iface: Option<&'a BaseInterface>,
|
||||
@@ -279,40 +275,33 @@ fn effective_identifier<'a>(
|
||||
(InterfaceIdentifier::Name, des_iface)
|
||||
}
|
||||
|
||||
-// Fallback used when the resolved identifier is `name` (the default).
|
||||
-//
|
||||
-// For ethernet the kernel name is unpredictable, so match on the most stable
|
||||
-// attribute available: permanent MAC, then PCI path, then current MAC, each
|
||||
-// with driver when known to disambiguate NICs sharing a MAC (e.g. Azure's
|
||||
-// Microsoft Azure Network Adapter). For non-ethernet the kernel name is
|
||||
-// user-assigned and the MAC is borrowed from a lower device (a VLAN takes its
|
||||
-// bond's MAC, itself from the first enslaved port), so use `OriginalName`.
|
||||
+// Fallback for the `name` identifier: match the most stable attribute the live
|
||||
+// interface owns (permanent MAC, then PCI, with driver to disambiguate NICs
|
||||
+// sharing a MAC). A borrowed MAC is never used, so software interfaces fall
|
||||
+// through to `OriginalName`; ethernet also accepts its own current MAC first.
|
||||
fn gen_systemd_name_match_rules(
|
||||
des_iface: &BaseInterface,
|
||||
cur_iface: Option<&BaseInterface>,
|
||||
) -> String {
|
||||
if des_iface.iface_type != InterfaceType::Ethernet {
|
||||
- return format!("OriginalName={}", des_iface.name);
|
||||
+ return cur_iface.and_then(owned_hw_match_rules).unwrap_or_else(|| {
|
||||
+ let name = cur_iface
|
||||
+ .map(|i| i.name.as_str())
|
||||
+ .unwrap_or(des_iface.name.as_str());
|
||||
+ format!("OriginalName={name}")
|
||||
+ });
|
||||
}
|
||||
|
||||
if let Some(cur_iface) = cur_iface {
|
||||
- if let Some(rules) = mac_match_rules(
|
||||
- cur_iface.permanent_mac_address.as_ref(),
|
||||
- None,
|
||||
- cur_iface.driver.as_ref(),
|
||||
- ) {
|
||||
- rules
|
||||
- } else if let Some(pci_addr) = cur_iface.pci_address.as_ref() {
|
||||
- format!("Path=pci-{pci_addr}")
|
||||
- } else if let Some(rules) = mac_match_rules(
|
||||
- None,
|
||||
- cur_iface.mac_address.as_ref(),
|
||||
- cur_iface.driver.as_ref(),
|
||||
- ) {
|
||||
- rules
|
||||
- } else {
|
||||
- format!("OriginalName={}", cur_iface.name)
|
||||
- }
|
||||
+ owned_hw_match_rules(cur_iface)
|
||||
+ .or_else(|| {
|
||||
+ mac_match_rules(
|
||||
+ None,
|
||||
+ cur_iface.mac_address.as_ref(),
|
||||
+ cur_iface.driver.as_ref(),
|
||||
+ )
|
||||
+ })
|
||||
+ .unwrap_or_else(|| format!("OriginalName={}", cur_iface.name))
|
||||
} else {
|
||||
// User is creating the ethernet interface or gen_conf mode
|
||||
mac_match_rules(
|
||||
@@ -324,6 +313,22 @@ fn gen_systemd_name_match_rules(
|
||||
}
|
||||
}
|
||||
|
||||
+// A stable hardware identifier the interface owns: permanent MAC (with driver)
|
||||
+// or PCI path. None for a borrowed MAC (e.g. a VLAN), which matches by name.
|
||||
+fn owned_hw_match_rules(cur_iface: &BaseInterface) -> Option<String> {
|
||||
+ mac_match_rules(
|
||||
+ cur_iface.permanent_mac_address.as_ref(),
|
||||
+ None,
|
||||
+ cur_iface.driver.as_ref(),
|
||||
+ )
|
||||
+ .or_else(|| {
|
||||
+ cur_iface
|
||||
+ .pci_address
|
||||
+ .as_ref()
|
||||
+ .map(|pci_addr| format!("Path=pci-{pci_addr}"))
|
||||
+ })
|
||||
+}
|
||||
+
|
||||
fn mac_match_rules(
|
||||
permanent_mac: Option<&String>,
|
||||
mac: Option<&String>,
|
||||
@@ -425,6 +430,21 @@ mod tests {
|
||||
assert_eq!(rules, "OriginalName=external0");
|
||||
}
|
||||
|
||||
+ // The name fallback keys on the live kernel name, not the desired name, so
|
||||
+ // udev matches the interface it names.
|
||||
+ #[test]
|
||||
+ fn non_eth_original_name_uses_current_name() {
|
||||
+ let mut des = BaseInterface::new();
|
||||
+ des.name = "bond-vlan".to_string();
|
||||
+ des.iface_type = InterfaceType::Vlan;
|
||||
+
|
||||
+ let mut cur = des.clone();
|
||||
+ cur.name = "external0".to_string();
|
||||
+
|
||||
+ let rules = gen_systemd_match_rules(&des, Some(&cur)).unwrap();
|
||||
+ assert_eq!(rules, "OriginalName=external0");
|
||||
+ }
|
||||
+
|
||||
// Create path (no current iface), non-VLAN software type, MAC ignored.
|
||||
#[test]
|
||||
fn software_iface_create_uses_original_name() {
|
||||
@@ -450,6 +470,37 @@ mod tests {
|
||||
assert_eq!(rules, "PermanentMACAddress=52:54:00:11:22:33");
|
||||
}
|
||||
|
||||
+ // A physical non-ethernet iface (InfiniBand) owns a permanent MAC, so an
|
||||
+ // alt-names-only reapply pins to it instead of its renameable kernel name.
|
||||
+ #[test]
|
||||
+ fn infiniband_reapply_uses_permanent_mac() {
|
||||
+ let mut des = BaseInterface::new();
|
||||
+ des.name = "mlx0".to_string();
|
||||
+ des.iface_type = InterfaceType::InfiniBand;
|
||||
+
|
||||
+ let mut cur = des.clone();
|
||||
+ cur.permanent_mac_address = Some("52:54:00:11:22:33".to_string());
|
||||
+
|
||||
+ let rules = gen_systemd_match_rules(&des, Some(&cur)).unwrap();
|
||||
+ assert_eq!(rules, "PermanentMACAddress=52:54:00:11:22:33");
|
||||
+ }
|
||||
+
|
||||
+ // A physical non-ethernet iface owning a PCI address but no permanent MAC
|
||||
+ // pins to the PCI path rather than the kernel name.
|
||||
+ #[test]
|
||||
+ fn non_eth_reapply_uses_pci_path() {
|
||||
+ let mut des = BaseInterface::new();
|
||||
+ des.name = "mlx0".to_string();
|
||||
+ des.iface_type = InterfaceType::InfiniBand;
|
||||
+
|
||||
+ let mut cur = des.clone();
|
||||
+ cur.pci_address =
|
||||
+ Some(crate::PciAddress::try_from("0000:00:1f.6").unwrap());
|
||||
+
|
||||
+ let rules = gen_systemd_match_rules(&des, Some(&cur)).unwrap();
|
||||
+ assert_eq!(rules, "Path=pci-0000:00:1f.6");
|
||||
+ }
|
||||
+
|
||||
// A live mac-address identifier without a permanent MAC (borrowed from a
|
||||
// lower device) is not inherited.
|
||||
#[test]
|
||||
--
|
||||
2.54.0
|
||||
|
||||
@ -0,0 +1,102 @@
|
||||
From 1527741c551a7ac54d5111ee264b3557fd3ad110 Mon Sep 17 00:00:00 2001
|
||||
From: Josephine Pfeiffer <josie@redhat.com>
|
||||
Date: Fri, 10 Jul 2026 14:56:09 +0200
|
||||
Subject: [PATCH 6/6] nispor: skip alt-name removal for deleted virtual
|
||||
interfaces
|
||||
|
||||
Deleting a virtual interface (e.g. a VLAN) that carries an nmstate-managed
|
||||
alt-name failed because NetworkManager removes the interface first, then the
|
||||
nispor alt-name removal runs against the now-absent interface and the kernel
|
||||
rejects it with EOPNOTSUPP.
|
||||
|
||||
A deleted virtual interface takes its alt-names with it, so skip the removal.
|
||||
Physical interfaces survive state:absent, so they still get their alt-names
|
||||
cleared.
|
||||
|
||||
Signed-off-by: Josephine Pfeiffer <josie@redhat.com>
|
||||
---
|
||||
rust/src/lib/nispor/alt_name.rs | 16 +++++++++----
|
||||
tests/integration/alt_name_test.py | 36 ++++++++++++++++++++++++++++++
|
||||
2 files changed, 48 insertions(+), 4 deletions(-)
|
||||
|
||||
diff --git a/rust/src/lib/nispor/alt_name.rs b/rust/src/lib/nispor/alt_name.rs
|
||||
index 220e6919..3fc575f4 100644
|
||||
--- a/rust/src/lib/nispor/alt_name.rs
|
||||
+++ b/rust/src/lib/nispor/alt_name.rs
|
||||
@@ -80,14 +80,22 @@ pub(crate) async fn apply_ifaces_alt_names(
|
||||
|
||||
for merged_iface in merged_ifaces.kernel_ifaces.values() {
|
||||
let apply_iface = if let Some(i) = merged_iface.for_apply.as_ref() {
|
||||
- i.base_iface()
|
||||
+ i
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
- let cur_iface = merged_iface.current.as_ref().map(|i| i.base_iface());
|
||||
- if let Some(np_iface) =
|
||||
- gen_nispor_iface_conf_for_alt_name(apply_iface, cur_iface)?
|
||||
+ // A virtual interface being deleted takes its alt-names with it, so
|
||||
+ // skip the nispor removal, which would otherwise race the deletion.
|
||||
+ if apply_iface.base_iface().state == InterfaceState::Absent
|
||||
+ && apply_iface.is_virtual()
|
||||
{
|
||||
+ continue;
|
||||
+ }
|
||||
+ let cur_iface = merged_iface.current.as_ref().map(|i| i.base_iface());
|
||||
+ if let Some(np_iface) = gen_nispor_iface_conf_for_alt_name(
|
||||
+ apply_iface.base_iface(),
|
||||
+ cur_iface,
|
||||
+ )? {
|
||||
np_ifaces.push(np_iface);
|
||||
}
|
||||
}
|
||||
diff --git a/tests/integration/alt_name_test.py b/tests/integration/alt_name_test.py
|
||||
index f53bfe62..df41f18e 100644
|
||||
--- a/tests/integration/alt_name_test.py
|
||||
+++ b/tests/integration/alt_name_test.py
|
||||
@@ -276,6 +276,42 @@ class TestAltNames:
|
||||
[alt_name],
|
||||
)
|
||||
|
||||
+ # https://issues.redhat.com/browse/RHEL-167955
|
||||
+ @pytest.mark.tier1
|
||||
+ def test_delete_vlan_on_bond_with_alt_name(self, eth1_up, eth2_up):
|
||||
+ vlan_name = f"{TEST_BOND_NIC}.102"
|
||||
+ link_file = f"/etc/systemd/network/98-nmstate-{vlan_name}.link"
|
||||
+ with bond_interface(TEST_BOND_NIC, ["eth1", "eth2"]):
|
||||
+ libnmstate.apply(
|
||||
+ load_yaml(
|
||||
+ f"""---
|
||||
+ interfaces:
|
||||
+ - name: {vlan_name}
|
||||
+ type: vlan
|
||||
+ state: up
|
||||
+ vlan:
|
||||
+ base-iface: {TEST_BOND_NIC}
|
||||
+ id: 102
|
||||
+ alt-names:
|
||||
+ - name: altdel
|
||||
+ """
|
||||
+ )
|
||||
+ )
|
||||
+ assert os.path.exists(link_file)
|
||||
+ # Deleting the VLAN must not fail trying to remove an alt-name
|
||||
+ # from an interface it just deleted; the link file goes with it.
|
||||
+ libnmstate.apply(
|
||||
+ load_yaml(
|
||||
+ f"""---
|
||||
+ interfaces:
|
||||
+ - name: {vlan_name}
|
||||
+ type: vlan
|
||||
+ state: absent
|
||||
+ """
|
||||
+ )
|
||||
+ )
|
||||
+ assert not os.path.exists(link_file)
|
||||
+
|
||||
# https://issues.redhat.com/browse/RHEL-126508
|
||||
@pytest.mark.tier1
|
||||
def test_ref_alt_name_in_route(self, eth1_with_alt_names):
|
||||
--
|
||||
2.54.0
|
||||
|
||||
@ -1,31 +0,0 @@
|
||||
From 248cd0bff6e3d030ee72b62a8a8b0e37e9f2ef80 Mon Sep 17 00:00:00 2001
|
||||
From: Fernando Fernandez Mancera <ffmancera@riseup.net>
|
||||
Date: Tue, 29 Nov 2022 23:56:13 +0100
|
||||
Subject: [PATCH] nm: reverse IPv6 order before adding them to setting
|
||||
|
||||
This is a downstream patch that needs to be applied before any other
|
||||
patch. Please check:
|
||||
|
||||
https://github.com/nmstate/nmstate/commit/2d0cfd5ad8e049f30cad10d977a5fae8bc4e6b64
|
||||
|
||||
Signed-off-by: Fernando Fernandez Mancera <ffmancera@riseup.net>
|
||||
---
|
||||
libnmstate/nm/ipv6.py | 2 +-
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
diff --git a/libnmstate/nm/ipv6.py b/libnmstate/nm/ipv6.py
|
||||
index 8e01fd70..7eb3196c 100644
|
||||
--- a/libnmstate/nm/ipv6.py
|
||||
+++ b/libnmstate/nm/ipv6.py
|
||||
@@ -157,7 +157,7 @@ def _set_dynamic(setting_ip, is_dhcp, is_autoconf):
|
||||
|
||||
|
||||
def _set_static(setting_ip, ip_addresses):
|
||||
- for address in ip_addresses:
|
||||
+ for address in reversed(ip_addresses):
|
||||
if iplib.is_ipv6_link_local_addr(
|
||||
address[InterfaceIPv6.ADDRESS_IP],
|
||||
address[InterfaceIPv6.ADDRESS_PREFIX_LENGTH],
|
||||
--
|
||||
2.38.1
|
||||
|
||||
@ -1,184 +0,0 @@
|
||||
From daf5e4e2282312a80ade85ac5728babf8b9af8b5 Mon Sep 17 00:00:00 2001
|
||||
From: Lubomir Rintel <lkundrak@v3.sk>
|
||||
Date: Mon, 17 Jun 2024 19:25:07 +0200
|
||||
Subject: [PATCH] nm: don't clear connection DNS if global DNS is not specified
|
||||
|
||||
If the global DNS state is not specified, let's not overwrite it in
|
||||
Networkmanager profiles while doing unrelated changes.
|
||||
|
||||
This is consistent with mainline (Rust) version of nmstate.
|
||||
|
||||
Resolves: https://issues.redhat.com/browse/RHEL-31095
|
||||
Signed-off-by: Lubomir Rintel <lkundrak@v3.sk>
|
||||
Signed-off-by: Gris Ge <fge@redhat.com>
|
||||
---
|
||||
libnmstate/dns.py | 2 +-
|
||||
libnmstate/nm/connection.py | 10 +++++++---
|
||||
libnmstate/nm/ipv4.py | 11 ++++++-----
|
||||
libnmstate/nm/ipv6.py | 11 ++++++-----
|
||||
libnmstate/nm/profile.py | 8 +++++---
|
||||
libnmstate/nm/profiles.py | 7 ++++++-
|
||||
6 files changed, 31 insertions(+), 18 deletions(-)
|
||||
|
||||
diff --git a/libnmstate/dns.py b/libnmstate/dns.py
|
||||
index 5bb512e8..f50b9bfd 100644
|
||||
--- a/libnmstate/dns.py
|
||||
+++ b/libnmstate/dns.py
|
||||
@@ -51,7 +51,7 @@ class DnsState:
|
||||
self._config_changed = False
|
||||
self._cur_dns_state = deepcopy(cur_dns_state) if cur_dns_state else {}
|
||||
self._dns_state = merge_dns(des_dns_state, cur_dns_state or {})
|
||||
- if self._dns_state == REMOVE_DNS_CONFIG:
|
||||
+ if des_dns_state is not None and self._dns_state == REMOVE_DNS_CONFIG:
|
||||
self._config_changed = True
|
||||
elif des_dns_state and des_dns_state.get(DNS.CONFIG):
|
||||
if cur_dns_state:
|
||||
diff --git a/libnmstate/nm/connection.py b/libnmstate/nm/connection.py
|
||||
index 6448e372..a6aac82c 100644
|
||||
--- a/libnmstate/nm/connection.py
|
||||
+++ b/libnmstate/nm/connection.py
|
||||
@@ -104,11 +104,15 @@ class _ConnectionSetting:
|
||||
return self._setting
|
||||
|
||||
|
||||
-def create_new_nm_simple_conn(iface, nm_profile):
|
||||
+def create_new_nm_simple_conn(iface, nm_profile, clear_dns=False):
|
||||
nm_iface_type = Api2Nm.get_iface_type(iface.type)
|
||||
iface_info = iface.to_dict()
|
||||
- ipv4_set = create_ipv4_setting(iface_info.get(Interface.IPV4), nm_profile)
|
||||
- ipv6_set = create_ipv6_setting(iface_info.get(Interface.IPV6), nm_profile)
|
||||
+ ipv4_set = create_ipv4_setting(
|
||||
+ iface_info.get(Interface.IPV4), nm_profile, clear_dns
|
||||
+ )
|
||||
+ ipv6_set = create_ipv6_setting(
|
||||
+ iface_info.get(Interface.IPV6), nm_profile, clear_dns
|
||||
+ )
|
||||
set_wait_ip(ipv4_set, ipv6_set, iface_info.get(Interface.WAIT_IP))
|
||||
settings = [ipv4_set, ipv6_set]
|
||||
con_setting = _ConnectionSetting()
|
||||
diff --git a/libnmstate/nm/ipv4.py b/libnmstate/nm/ipv4.py
|
||||
index 32b3428e..3f4de059 100644
|
||||
--- a/libnmstate/nm/ipv4.py
|
||||
+++ b/libnmstate/nm/ipv4.py
|
||||
@@ -13,7 +13,7 @@ from .common import NM
|
||||
INT32_MAX = 2**31 - 1
|
||||
|
||||
|
||||
-def create_setting(config, base_con_profile):
|
||||
+def create_setting(config, base_con_profile, clear_dns=True):
|
||||
setting_ipv4 = None
|
||||
if base_con_profile and config and config.get(InterfaceIPv4.ENABLED):
|
||||
setting_ipv4 = base_con_profile.get_setting_ip4_config()
|
||||
@@ -28,10 +28,11 @@ def create_setting(config, base_con_profile):
|
||||
setting_ipv4.props.route_metric = Route.USE_DEFAULT_METRIC
|
||||
setting_ipv4.clear_routes()
|
||||
setting_ipv4.clear_routing_rules()
|
||||
- setting_ipv4.clear_dns()
|
||||
- setting_ipv4.clear_dns_searches()
|
||||
- setting_ipv4.clear_dns_options(False)
|
||||
- setting_ipv4.props.dns_priority = nm_dns.DEFAULT_DNS_PRIORITY
|
||||
+ if clear_dns:
|
||||
+ setting_ipv4.clear_dns()
|
||||
+ setting_ipv4.clear_dns_searches()
|
||||
+ setting_ipv4.clear_dns_options(False)
|
||||
+ setting_ipv4.props.dns_priority = nm_dns.DEFAULT_DNS_PRIORITY
|
||||
|
||||
if not setting_ipv4:
|
||||
setting_ipv4 = NM.SettingIP4Config.new()
|
||||
diff --git a/libnmstate/nm/ipv6.py b/libnmstate/nm/ipv6.py
|
||||
index f84d895c..fb3cdcf0 100644
|
||||
--- a/libnmstate/nm/ipv6.py
|
||||
+++ b/libnmstate/nm/ipv6.py
|
||||
@@ -67,7 +67,7 @@ def get_info(active_connection, applied_config):
|
||||
return info
|
||||
|
||||
|
||||
-def create_setting(config, base_con_profile):
|
||||
+def create_setting(config, base_con_profile, clear_dns=True):
|
||||
setting_ip = None
|
||||
if base_con_profile and config and config.get(InterfaceIPv6.ENABLED):
|
||||
setting_ip = base_con_profile.get_setting_ip6_config()
|
||||
@@ -82,10 +82,11 @@ def create_setting(config, base_con_profile):
|
||||
setting_ip.props.gateway = None
|
||||
setting_ip.props.route_table = Route.USE_DEFAULT_ROUTE_TABLE
|
||||
setting_ip.props.route_metric = Route.USE_DEFAULT_METRIC
|
||||
- setting_ip.clear_dns()
|
||||
- setting_ip.clear_dns_searches()
|
||||
- setting_ip.clear_dns_options(False)
|
||||
- setting_ip.props.dns_priority = nm_dns.DEFAULT_DNS_PRIORITY
|
||||
+ if clear_dns:
|
||||
+ setting_ip.clear_dns()
|
||||
+ setting_ip.clear_dns_searches()
|
||||
+ setting_ip.clear_dns_options(False)
|
||||
+ setting_ip.props.dns_priority = nm_dns.DEFAULT_DNS_PRIORITY
|
||||
|
||||
if not setting_ip:
|
||||
setting_ip = NM.SettingIP6Config.new()
|
||||
diff --git a/libnmstate/nm/profile.py b/libnmstate/nm/profile.py
|
||||
index a0b1c8f8..acb849b6 100644
|
||||
--- a/libnmstate/nm/profile.py
|
||||
+++ b/libnmstate/nm/profile.py
|
||||
@@ -273,7 +273,9 @@ class NmProfile:
|
||||
self._iface.type == InterfaceType.ETHERNET and self._iface.is_peer
|
||||
)
|
||||
|
||||
- def prepare_config(self, save_to_disk, gen_conf_mode=False):
|
||||
+ def prepare_config(
|
||||
+ self, save_to_disk, gen_conf_mode=False, clear_dns=True
|
||||
+ ):
|
||||
if self._iface.is_absent or (
|
||||
self._iface.is_down
|
||||
and not gen_conf_mode
|
||||
@@ -307,7 +309,7 @@ class NmProfile:
|
||||
# of nmstate should provide full/merged configure.
|
||||
if self._iface.is_changed or self._iface.is_desired:
|
||||
self._nm_simple_conn = create_new_nm_simple_conn(
|
||||
- self._iface, self._nm_profile
|
||||
+ self._iface, self._nm_profile, clear_dns
|
||||
)
|
||||
elif self._nm_profile:
|
||||
self._nm_simple_conn = NM.SimpleConnection.new_clone(
|
||||
@@ -316,7 +318,7 @@ class NmProfile:
|
||||
else:
|
||||
try:
|
||||
self._nm_simple_conn = create_new_nm_simple_conn(
|
||||
- self._iface, self._nm_profile
|
||||
+ self._iface, self._nm_profile, clear_dns
|
||||
)
|
||||
# No error for undesired interface
|
||||
except NmstateError:
|
||||
diff --git a/libnmstate/nm/profiles.py b/libnmstate/nm/profiles.py
|
||||
index e68efdf3..9ce21938 100644
|
||||
--- a/libnmstate/nm/profiles.py
|
||||
+++ b/libnmstate/nm/profiles.py
|
||||
@@ -52,6 +52,7 @@ class NmProfiles:
|
||||
|
||||
def apply_config(self, net_state, save_to_disk):
|
||||
if net_state.dns.config_changed:
|
||||
+ clear_profile_dns = True
|
||||
if net_state.use_global_dns:
|
||||
apply_global_dns(
|
||||
net_state.dns.config_servers,
|
||||
@@ -60,6 +61,8 @@ class NmProfiles:
|
||||
)
|
||||
else:
|
||||
apply_global_dns([], [], [])
|
||||
+ else:
|
||||
+ clear_profile_dns = False
|
||||
|
||||
self._prepare_state_for_profiles(net_state)
|
||||
# The activation order on bridge/bond ports determins their controler's
|
||||
@@ -74,7 +77,9 @@ class NmProfiles:
|
||||
|
||||
for profile in all_profiles:
|
||||
profile.import_current()
|
||||
- profile.prepare_config(save_to_disk, gen_conf_mode=False)
|
||||
+ profile.prepare_config(
|
||||
+ save_to_disk, gen_conf_mode=False, clear_dns=clear_profile_dns
|
||||
+ )
|
||||
_use_uuid_as_controller_and_parent(all_profiles)
|
||||
|
||||
changed_ovs_bridges_and_ifaces = {}
|
||||
--
|
||||
2.45.2
|
||||
|
||||
@ -1,70 +0,0 @@
|
||||
From 364842c0c09f9799a2c48a1bc3ce4debb1a3ddc2 Mon Sep 17 00:00:00 2001
|
||||
From: Gris Ge <fge@redhat.com>
|
||||
Date: Tue, 18 Jun 2024 13:44:55 +0800
|
||||
Subject: [PATCH] dns: Do not touch iface DNS when apply identical DNS state
|
||||
|
||||
When applying the same DNS only desire state again, nmstate incorrectly
|
||||
purged interface DNS.
|
||||
|
||||
The root cause is we only set `self.use_global_dns` to True when
|
||||
DNS changed. The fix is set `self.use_global_dns` to True always unless
|
||||
iface DNS is required.
|
||||
|
||||
Signed-off-by: Gris Ge <fge@redhat.com>
|
||||
---
|
||||
libnmstate/net_state.py | 12 +++++-------
|
||||
1 file changed, 5 insertions(+), 7 deletions(-)
|
||||
|
||||
diff --git a/libnmstate/net_state.py b/libnmstate/net_state.py
|
||||
index 7b208daa..fe6fc31d 100644
|
||||
--- a/libnmstate/net_state.py
|
||||
+++ b/libnmstate/net_state.py
|
||||
@@ -32,7 +32,7 @@ class NetState:
|
||||
gen_conf_mode=False,
|
||||
ignored_dns_ifaces=None,
|
||||
):
|
||||
- self.use_global_dns = False
|
||||
+ self.use_global_dns = True
|
||||
if current_state is None:
|
||||
current_state = {}
|
||||
self._ifaces = Ifaces(
|
||||
@@ -76,17 +76,17 @@ class NetState:
|
||||
"interface profile, using global DNS"
|
||||
)
|
||||
logging.warning(
|
||||
- "Storing DNS to NetworkManager via global dns API, "
|
||||
- "this will cause __all__ interface level DNS settings "
|
||||
- "been ignored"
|
||||
+ "Storing DNS to NetworkManager via global DNS "
|
||||
+ "API, this will cause __all__ interface level "
|
||||
+ "DNS settings been ignored"
|
||||
)
|
||||
- self.use_global_dns = True
|
||||
else:
|
||||
if self.dns.is_purge() or self._is_iface_dns_prefered():
|
||||
try:
|
||||
self._ifaces.gen_dns_metadata(
|
||||
self._dns, self._route, ignored_dns_ifaces
|
||||
)
|
||||
+ self.use_global_dns = False
|
||||
except NmstateValueError as e:
|
||||
if (
|
||||
gen_conf_mode
|
||||
@@ -99,14 +99,12 @@ class NetState:
|
||||
"API, this will cause __all__ interface level "
|
||||
"DNS settings been ignored"
|
||||
)
|
||||
- self.use_global_dns = True
|
||||
elif self.dns.config_changed:
|
||||
logging.warning(
|
||||
"Storing DNS to NetworkManager via global DNS "
|
||||
"API, this will cause __all__ interface level "
|
||||
"DNS settings been ignored"
|
||||
)
|
||||
- self.use_global_dns = True
|
||||
|
||||
self._ifaces.gen_route_metadata(self._route)
|
||||
self._ifaces.gen_route_rule_metadata(self._route_rule, self._route)
|
||||
--
|
||||
2.45.2
|
||||
|
||||
@ -1,16 +0,0 @@
|
||||
-----BEGIN PGP SIGNATURE-----
|
||||
|
||||
iQIzBAABCAAdFiEESP1vrlFad7SENoIch4lWe4cVzrwFAmZFuTcACgkQh4lWe4cV
|
||||
zrx0Jg/9FaBh+ine42rKdD+vSITPnGMB2pNSgU8RMdb35fpypVE6Fx4zavvlN14r
|
||||
rqO+DA57n2NAQ0Cj+n1HOaCxzbfYG2hAX8NwuFN3iY/KadqUdjdPx9G8vEzZDOFu
|
||||
MCRZxUVIyrHBB9eoTLbhF6hd0XqWminHK43xaLZFBXZAe7DO9QEz+VxM77qsZaE5
|
||||
EhxASs2mUERvjuY61h9lJb41DOxGLZBM5950S9lDM8cE9ZOi7H/8Q/8fhBPdHh5T
|
||||
ubPZxsgEBQPEBPz6lE5g4Wcc8ggiCROEuUqRmOTpkb6smgFDsLtCBTakv1Z2U5Q0
|
||||
YKdnDBfw7T9EV3dhkHR/AHvrLDUZ/bTwe6vO0GQBaSLG/WhvcftzuiDKTcqpAVOm
|
||||
yUdxla1tinB0cDXohGb50VV9aHd9gFisoLGsPE7BJTqSKhCoQL2zToLHNaMnxNtz
|
||||
/fJTOyGq1bBtchjbXjaoQO8sa/cxrllWlBYVjaTH7vqwgActrznos/N1sASrMfTw
|
||||
H1VCKRgwurnomWoIbFNKxPnXNW0Lo31paWhW9wVD78J7Kf9xHrfYiOK2siYgSP7x
|
||||
nVMuMHZ/j9/EOgHZAPZN3Aod0LWK0/WNwTyFB4IQcLWLaBYVhqlFhShtPo8MeIsJ
|
||||
Wy7UQmGhtnP8Mt1DQTsfWCH8lQnWpIb84xHYXSEs+2e39GRDCq8=
|
||||
=lwjx
|
||||
-----END PGP SIGNATURE-----
|
||||
7
SOURCES/nmstate-2.2.61.tar.gz.asc
Normal file
7
SOURCES/nmstate-2.2.61.tar.gz.asc
Normal file
@ -0,0 +1,7 @@
|
||||
-----BEGIN PGP SIGNATURE-----
|
||||
|
||||
iHUEABYKAB0WIQTGmN6Via2HbWGLA7QVfQLvTdXXUgUCalS78AAKCRAVfQLvTdXX
|
||||
UvPxAP9b5ZS5+KfZdtW1Opd7GUKOjKR2oBM7JQrhFKYMokEekwD/a/660BsaS7Qg
|
||||
SMpCCHKa8Y0erlIBVc8yBisFb2dCoAs=
|
||||
=c6+E
|
||||
-----END PGP SIGNATURE-----
|
||||
@ -1,3 +1,4 @@
|
||||
Gris Ge (cathay4t):
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
mQINBGRA/64BEADubtKFxtanzR3/oa3+/krHUWFcPhUvJNl9kuHY5YykyLG8O8L9
|
||||
@ -47,6 +48,12 @@ E9NlXivSZRblfY9DEa4v0Zr7L9uyp2JU7taIexoLmPbefORThtGmNoc8DwzLlH8s
|
||||
SJXEX1ckgzCUNUPQs37ZmV7q4pXh2yYtcZwufH10o02nl67Yuv+43II3vuvEg9CO
|
||||
qOF1CIUdWB9SZwkAz4MeAjtw5d/YBSqYv9s0pSobvGuo7wBW7MTJ6PkGBzTvdR3H
|
||||
aOfE6WjbuPjr5H0J1oWyWa0/VB7i1OQ7/55IChT137OnVHRENP8HaGmRZYhxwLsI
|
||||
=PdCG
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
Fernando Fernández Mancera (ffmancera):
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
mQINBGRBGdIBEACbSCdhXJBuJnXoENxJSw4TjSiVi20p2jHxJvX5PnesFc+Vt/L4
|
||||
tbM90JEK93tXSe0SykYxS++UGGsXKwXllBKRy9VG3TpxeajXu6AnnKiuUxV+o+Bq
|
||||
TT49i8elT4XvoD3eTs5GU3ulzwdCJBcZYgtYWVFIRema1ZCjpOPtu2nl7VCo6/px
|
||||
@ -95,5 +102,56 @@ YjVo4IPCmJLRb9b8kPX9JuJWDnYWd0SOB00ImaGeXd/kV8W30Lss1OeQ7iya/Ej7
|
||||
t878uw4RVPKsgCQTWKOWhC0r0DNE/bskGrWZAJGC3M7yqzAErxiIOBKRwH2haegT
|
||||
syMyW5sNgF43zvxzEHACZnbx+qzHYf+SeQg4pRxLlZj6/Udc3hM/j1cGkMMiwl23
|
||||
i2QY7dEEs/uMRtq8C8kSWg==
|
||||
=259x
|
||||
=Byy/
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
Íñigo Huguet (ihuguet):
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
mQENBGC54gsBCACtCdyHeyKmLLyrS7K60vBA/ZzBjdNWo2gVzbp3uuXMYmYaVo4E
|
||||
pIFxfkgtflefM8lZU//pFgCbj4l9uCyNrcMv5SDwR6T0JFoUyQgNVP2tc3d3Zzks
|
||||
ktHOBxLyI0HSusKVXcYUSORcMLgW1QfpWWE01zND+Kur+9EQcKhkSVeh/APcDKh3
|
||||
3OhiqKoo1zRQw/GyiXB8NY+/+zYmJc/Ag4ZL4zwTYfCcFYk2bUD3XWgYICqoY647
|
||||
wlb398PdyJfKN3SNQdRUZ0zvMBxh6L5uN/CT92W5gey/m6UKfSiwmyzUcxtKvktA
|
||||
g6DbWlIhjBhNssx/yJM/WWW8FE3piYpXhOQrABEBAAG0I8ONw7FpZ28gSHVndWV0
|
||||
IDxpaHVndWV0QHJlZGhhdC5jb20+iQFOBBMBCAA4FiEEB/muyGFEOG2VdiELZqRH
|
||||
gbTrwtAFAmC54gsCGwMFCwkIBwIGFQoJCAsCBBYCAwECHgECF4AACgkQZqRHgbTr
|
||||
wtCGSQf+K/yt1oxk85ehjjmT/O+4PhMxPLDTTIbPWwJpSQ7paRkOa53VeA1gmI+m
|
||||
miDK4CllrO90ZD6ZvzXjbXQP/MA1PDUNNOnCbXX+Tk9uZEx4GdWmkRbdEAOuBK8b
|
||||
Vg7sH/2a/mtwAW/AYY/K8rcRGTDsNJAcNNfcXl61wGYWHuqyeNXmMB+Y3dXGUxKR
|
||||
iJOcA0sO8d6DMCDMyeyAdREThbKT2pUfnXN9HdASBU8HHTmf2nyDAD6u/8EEMDfu
|
||||
2Yb4VtCZ0k92mp/043JRbwJ56AUxdLbWFbBmYgrIeKAtyHaACv06PQXZpQ2dz+WP
|
||||
S3URA/n5JvntMbIkarU7ftLuYTkn/LkBDQRgueILAQgAvm9vMG73bYZ5dp/TDKMk
|
||||
y6E6mxi0TXVILfQV8bO2H1S2k19wNOIavqGtbH15QzsL6B2OuwHeAPkUkwQwV8Tz
|
||||
NCbUFeQb8z5jWjdFVBi42JXwBQmQcYHKYjaOu/7kW+v/5pyfFRLPLfimYCF+YbhY
|
||||
vTqSMrW4sInUXhUOL1mg0yxWEoZ6Qv3AlUsVc91Ok2j5f1UzjFg+7T4BEYc/pE+d
|
||||
oP5y/mvYoUt+o6Ma6ENcBgsJ8JFRJTb1OTrXmzebCAW7Md+6rdXGz5mMeqW0QFGV
|
||||
LgHS1euGza0Vm9wfjA6l3LMxW5jrmxLWsEiugbm4QPIxBR2u8HFVk/G6kBYxsNwd
|
||||
3QARAQABiQE2BBgBCAAgFiEEB/muyGFEOG2VdiELZqRHgbTrwtAFAmC54gsCGwwA
|
||||
CgkQZqRHgbTrwtD33wf+ORu+FgSV6m/5M8bsLNA2uh6jp3lFNT6CZy3yza/8Uxqt
|
||||
RsucUc0qlWmnHbQmwCjAoHpapEtQNvisDgYGAo8ICw+Ij12psxOyFgh0inF7p2ms
|
||||
86g1Km7DHzFE6B8gwakkVhN6QR+wzr3msJMfj+gfKCbmbuD7PQLjgJL4ITRbMj0u
|
||||
XUuxEMD5z7mtWDDVscCna0bgaxX0KZC71ViWsPx7Wp1v6+kUzL4KLK1R/7xt6kim
|
||||
2nJubv+VVuNkx8THxh6chgux1ev8z8fOKiPWcyYZ+wWhbr1Bt98Z24IV4BYY2Ee8
|
||||
fOao91QN8xXpO4C1MljjqaWb3DWqvdBsTx/nUBPOlw==
|
||||
=hJlJ
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
Ján Václav (jvaclav-rh):
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
mDMEajE0ZRYJKwYBBAHaRw8BAQdA7Fk1giKb1EBdPYNPxMevbIyl/aQeX7YgQFZI
|
||||
kf+/Vsy0IUrDoW4gVsOhY2xhdiA8anZhY2xhdkByZWRoYXQuY29tPoiZBBMWCgBB
|
||||
FiEExpjelYmth21hiwO0FX0C703V11IFAmoxNGUCGwMFCQlmAYAFCwkIBwICIgIG
|
||||
FQoJCAsCBBYCAwECHgcCF4AACgkQFX0C703V11JAjwD9Gy5pSmovjX9GO0S2YTsn
|
||||
vAZLkWs9YOBYRTXOBsvwjgEBAN6fHaz5RV9tTWby89eMVzuHT3M6xZICMmjWd2rX
|
||||
510DuDMEajE0ZRYJKwYBBAHaRw8BAQdAa4fXIUTilCDWoD91ffCywxo6lA++3CRO
|
||||
OASdohzzaYKIfgQYFgoAJhYhBMaY3pWJrYdtYYsDtBV9Au9N1ddSBQJqMTRlAhsg
|
||||
BQkJZgGAAAoJEBV9Au9N1ddSCeIA/2Ke9zfnnnZtC5/i02JPgAB2E7E4WrZd9+F4
|
||||
5jqTz8zZAQCtDpVd5tLpMyaFpReIaQd14B8wdEx20lbSWxy/UYXoA7g4BGoxNGUS
|
||||
CisGAQQBl1UBBQEBB0ChZvD4wSXrHC7GGMDkYo4GZUGHg0IPY16w1JJ5ixn/GgMB
|
||||
CAeIfgQYFgoAJhYhBMaY3pWJrYdtYYsDtBV9Au9N1ddSBQJqMTRlAhsMBQkJZgGA
|
||||
AAoJEBV9Au9N1ddSrIIA/RqB58MMw7gEX7zZ06eTXiMc70uXPA0pDkKsLPujVWr4
|
||||
AQCQ5DdXWt0T15KenMELqNMfz+C3Rq7kGXi9gu+l0ndiAQ==
|
||||
=N3Ce
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user