Blob Blame History Raw
From ccdcd8f86544a6364109a0c0142d05a5afacf64e Mon Sep 17 00:00:00 2001
From: Gris Ge <fge@redhat.com>
Date: Tue, 2 Mar 2021 15:31:20 +0800
Subject: [PATCH] nm: Don't touch unmanaged interface unless desired

We should ignore NetworkManager unmanaged interface when applying and
verifying unless certain interface listed in desired state explicitly.

Introduced new plugin interface
`NmstatePlugin.get_ignored_kernel_interface_names()` where plugin may
include a list of interface names which should be ignored during
verification stage.

Integration test case added to simulate CNV usage on partial editing
a linux bridge holding NM unmanaged interface.

Signed-off-by: Gris Ge <fge@redhat.com>
---
 libnmstate/ifaces/base_iface.py           |  3 ++
 libnmstate/ifaces/ifaces.py               | 26 ++++++++--------
 libnmstate/netapplier.py                  |  6 ++++
 libnmstate/nispor/plugin.py               |  6 +++-
 libnmstate/nm/plugin.py                   | 25 ++++++++++++++++
 libnmstate/plugin.py                      |  7 +++++
 tests/integration/linux_bridge_test.py    |  8 +----
 tests/integration/nm/linux_bridge_test.py | 36 ++++++++++++++++++++++-
 8 files changed, 95 insertions(+), 22 deletions(-)

diff --git a/libnmstate/ifaces/base_iface.py b/libnmstate/ifaces/base_iface.py
index 227c1d20..e3f2a1ca 100644
--- a/libnmstate/ifaces/base_iface.py
+++ b/libnmstate/ifaces/base_iface.py
@@ -322,6 +322,9 @@ class BaseIface:
     def mark_as_up(self):
         self.raw[Interface.STATE] = InterfaceState.UP
 
+    def mark_as_ignored(self):
+        self.raw[Interface.STATE] = InterfaceState.IGNORE
+
     @property
     def is_controller(self):
         return False
diff --git a/libnmstate/ifaces/ifaces.py b/libnmstate/ifaces/ifaces.py
index 6c94a986..efa24aa3 100644
--- a/libnmstate/ifaces/ifaces.py
+++ b/libnmstate/ifaces/ifaces.py
@@ -95,7 +95,6 @@ class Ifaces:
         self._kernel_ifaces = {}
         self._user_space_ifaces = _UserSpaceIfaces()
         self._cur_user_space_ifaces = _UserSpaceIfaces()
-        self._ignored_ifaces = set()
         if cur_iface_infos:
             for iface_info in cur_iface_infos:
                 cur_iface = _to_specific_iface_obj(iface_info, save_to_disk)
@@ -143,10 +142,6 @@ class Ifaces:
                 ):
                     # Ignore interface with unknown type
                     continue
-                if iface.is_ignore:
-                    self._ignored_ifaces.add(
-                        (iface.name, iface.type, iface.is_user_space_only)
-                    )
                 if cur_iface:
                     iface.merge(cur_iface)
                 iface.mark_as_desired()
@@ -169,6 +164,10 @@ class Ifaces:
 
             self._pre_edit_validation_and_cleanup()
 
+    @property
+    def _ignored_ifaces(self):
+        return [iface for iface in self.all_ifaces() if iface.is_ignore]
+
     def _apply_copy_mac_from(self):
         for iface in self.all_kernel_ifaces.values():
             if iface.type not in (
@@ -284,7 +283,7 @@ class Ifaces:
         if not defiend in desire state
         """
         for iface in self.all_ifaces():
-            if iface.is_up and iface.is_controller:
+            if iface.is_desired and iface.is_up and iface.is_controller:
                 for port_name in iface.port:
                     port_iface = self._kernel_ifaces[port_name]
                     if not port_iface.is_desired and not port_iface.is_up:
@@ -550,13 +549,14 @@ class Ifaces:
         return None
 
     def _remove_iface(self, iface_name, iface_type):
-        cur_iface = self._cur_kernel_ifaces.get(iface_name, iface_type)
+        cur_iface = self._user_space_ifaces.get(iface_name, iface_type)
         if cur_iface:
             self._user_space_ifaces.remove(cur_iface)
         else:
             cur_iface = self._kernel_ifaces.get(iface_name)
             if (
-                iface_type
+                cur_iface
+                and iface_type
                 and iface_type != InterfaceType.UNKNOWN
                 and iface_type == cur_iface.type
             ):
@@ -813,14 +813,14 @@ class Ifaces:
                     port_controller_map[port_name] = iface.name
 
     def _remove_ignore_interfaces(self, ignored_ifaces):
-        for iface_name, iface_type, _ in ignored_ifaces:
-            self._remove_iface(iface_name, iface_type)
+        for iface in ignored_ifaces:
+            self._remove_iface(iface.name, iface.type)
 
         # Only kernel interface can be used as port
         ignored_kernel_iface_names = set(
-            iface_name
-            for iface_name, _, is_user_space_only in ignored_ifaces
-            if not is_user_space_only
+            iface.name
+            for iface in ignored_ifaces
+            if not iface.is_user_space_only
         )
 
         # Remove ignored port
diff --git a/libnmstate/netapplier.py b/libnmstate/netapplier.py
index 3c5759b4..a020f003 100644
--- a/libnmstate/netapplier.py
+++ b/libnmstate/netapplier.py
@@ -107,8 +107,14 @@ def rollback(*, checkpoint=None):
 
 
 def _apply_ifaces_state(plugins, net_state, verify_change, save_to_disk):
+    for plugin in plugins:
+        for iface_name in plugin.get_ignored_kernel_interface_names():
+            iface = net_state.ifaces.all_kernel_ifaces.get(iface_name)
+            if iface and not iface.is_desired:
+                iface.mark_as_ignored()
     for plugin in plugins:
         plugin.apply_changes(net_state, save_to_disk)
+
     verified = False
     if verify_change:
         if _net_state_contains_sriov_interface(net_state):
diff --git a/libnmstate/nispor/plugin.py b/libnmstate/nispor/plugin.py
index dc0ea760..19b21d56 100644
--- a/libnmstate/nispor/plugin.py
+++ b/libnmstate/nispor/plugin.py
@@ -159,7 +159,11 @@ class NisporPlugin(NmstatePlugin):
         np_state = NisporNetState.retrieve()
         logging.debug(f"Nispor: current network state {np_state}")
         for iface in net_state.ifaces.all_ifaces():
-            if iface.is_desired:
+            if iface.is_ignore:
+                logging.debug(
+                    f"Nispor: Interface {iface.name} {iface.type} ignored"
+                )
+            elif iface.is_desired:
                 logging.debug(
                     f"Nispor: desired network state {iface.to_dict()}"
                 )
diff --git a/libnmstate/nm/plugin.py b/libnmstate/nm/plugin.py
index 302b4cca..335d93c7 100644
--- a/libnmstate/nm/plugin.py
+++ b/libnmstate/nm/plugin.py
@@ -36,6 +36,7 @@ from .checkpoint import get_checkpoints
 from .common import NM
 from .context import NmContext
 from .device import get_device_common_info
+from .device import get_iface_type
 from .device import list_devices
 from .dns import get_running as get_dns_running
 from .dns import get_running_config as get_dns_running_config
@@ -268,6 +269,21 @@ class NetworkManagerPlugin(NmstatePlugin):
             )
         return NmProfiles(None).generate_config_strings(net_state)
 
+    def get_ignored_kernel_interface_names(self):
+        """
+        Return a list of unmanged kernel interface names.
+        """
+        ignored_ifaces = set()
+        for nm_dev in list_devices(self.client):
+            if (
+                nm_dev
+                and nm_dev.get_iface()
+                and not nm_dev.get_managed()
+                and _is_kernel_iface(nm_dev)
+            ):
+                ignored_ifaces.add(nm_dev.get_iface())
+        return list(ignored_ifaces)
+
 
 def _remove_ovs_bridge_unsupported_entries(iface_info):
     """
@@ -283,3 +299,12 @@ def _remove_ovs_bridge_unsupported_entries(iface_info):
 
 def _nm_utils_decode_version():
     return f"{NM.MAJOR_VERSION}.{NM.MINOR_VERSION}.{NM.MICRO_VERSION}"
+
+
+def _is_kernel_iface(nm_dev):
+    iface_type = get_iface_type(nm_dev)
+    return iface_type != InterfaceType.UNKNOWN and iface_type not in (
+        InterfaceType.OVS_BRIDGE,
+        InterfaceType.OVS_INTERFACE,
+        InterfaceType.OVS_PORT,
+    )
diff --git a/libnmstate/plugin.py b/libnmstate/plugin.py
index ef3874ff..e1d9ad58 100644
--- a/libnmstate/plugin.py
+++ b/libnmstate/plugin.py
@@ -128,3 +128,10 @@ class NmstatePlugin(metaclass=ABCMeta):
         persistently.
         """
         return []
+
+    def get_ignored_kernel_interface_names(self):
+        """
+        Return a list of kernel interface names which should be ignored
+        during verification stage.
+        """
+        return []
-- 
2.29.2