Prechádzať zdrojové kódy

Change set_dhcp=true to create nmconnection files for Redhat 9+

Fabian Fulga 1 mesiac pred
rodič
commit
6d68b0bc72

+ 33 - 0
coriolis/osmorphing/base.py

@@ -220,6 +220,8 @@ class BaseOSMorphingTools(object, with_metaclass(abc.ABCMeta)):
 class BaseLinuxOSMorphingTools(BaseOSMorphingTools):
 
     _packages = {}
+    _NETWORK_SCRIPTS_PATH = "etc/sysconfig/network-scripts"
+    _NM_CONNECTIONS_PATH = "etc/NetworkManager/system-connections"
 
     def __init__(self, conn, os_root_dir, os_root_dev, hypervisor,
                  event_manager, detected_os_info, osmorphing_parameters,
@@ -434,6 +436,37 @@ class BaseLinuxOSMorphingTools(BaseOSMorphingTools):
         config = utils.parse_ini_config(content)
         return config
 
+    def _get_net_config_files(self, network_scripts_path):
+        dir_content = self._list_dir(network_scripts_path)
+        return [os.path.join(network_scripts_path, f) for f in
+                dir_content if re.match("^ifcfg-(.*)", f)]
+
+    def _get_ifcfgs_by_type(self, ifcfg_type, network_scripts_path):
+        ifcfgs = []
+        for ifcfg_file in self._get_net_config_files(network_scripts_path):
+            ifcfg = self._read_config_file_sudo(ifcfg_file)
+            detected_type = ifcfg.get('TYPE')
+            if not detected_type:
+                detected_type = "Ethernet"
+                ifcfg["TYPE"] = detected_type
+            if detected_type.lower() == ifcfg_type.lower():
+                ifcfgs.append((ifcfg_file, ifcfg))
+        return ifcfgs
+
+    def _get_nmconnection_files(self, network_scripts_path):
+        dir_content = self._list_dir(network_scripts_path)
+        return [os.path.join(network_scripts_path, f)
+                for f in dir_content if re.match(
+                    r"^(.*\.nmconnection)$", f)]
+
+    def _get_keyfiles_by_type(self, nmconnection_type, network_scripts_path):
+        keyfiles = []
+        for file in self._get_nmconnection_files(network_scripts_path):
+            keyfile = self._read_config_file_sudo(file)
+            if keyfile.get("type") == nmconnection_type:
+                keyfiles.append((file, keyfile))
+        return keyfiles
+
     def _copy_resolv_conf(self):
         resolv_conf = "etc/resolv.conf"
         resolv_conf_path = os.path.join(self._os_root_dir, resolv_conf)

+ 11 - 28
coriolis/osmorphing/netpreserver/ifcfg.py

@@ -1,7 +1,6 @@
 # Copyright 2025 Cloudbase Solutions Srl
 # All Rights Reserved.
 
-import os
 import re
 
 from oslo_log import log as logging
@@ -13,23 +12,24 @@ LOG = logging.getLogger(__name__)
 
 class IfcfgNetPreserver(base.BaseNetPreserver):
 
-    def __init__(self, osmorphing_tool):
-        super(IfcfgNetPreserver, self).__init__(osmorphing_tool)
-        self.network_scripts_path = "etc/sysconfig/network-scripts"
-
     def check_net_preserver(self):
-        if self.osmorphing_tool._test_path(self.network_scripts_path):
-            ifcfg_files = self._get_net_config_files(self.network_scripts_path)
+        network_scripts_path = (
+            self.osmorphing_tool._NETWORK_SCRIPTS_PATH)
+        if self.osmorphing_tool._test_path(network_scripts_path):
+            ifcfg_files = self.osmorphing_tool._get_net_config_files(
+                network_scripts_path)
             if ifcfg_files:
-                ifcfgs_ethernet = self._get_ifcfgs_by_type(
-                    "Ethernet", self.network_scripts_path)
+                ifcfgs_ethernet = self.osmorphing_tool._get_ifcfgs_by_type(
+                    "Ethernet", network_scripts_path)
                 if ifcfgs_ethernet:
                     return True
         return False
 
     def parse_network(self):
-        ifcfgs_ethernet = self._get_ifcfgs_by_type(
-            "Ethernet", self.network_scripts_path)
+        network_scripts_path = (
+            self.osmorphing_tool._NETWORK_SCRIPTS_PATH)
+        ifcfgs_ethernet = self.osmorphing_tool._get_ifcfgs_by_type(
+            "Ethernet", network_scripts_path)
         if ifcfgs_ethernet:
             for ifcfg_file, ifcfg in ifcfgs_ethernet:
                 name = ifcfg.get("DEVICE")
@@ -42,20 +42,3 @@ class IfcfgNetPreserver(base.BaseNetPreserver):
                     "mac_address": mac_address,
                     "ip_addresses": [ip_address] if ip_address else []
                 }
-
-    def _get_net_config_files(self, network_scripts_path):
-        dir_content = self.osmorphing_tool._list_dir(network_scripts_path)
-        return [os.path.join(network_scripts_path, f) for f in
-                dir_content if re.match("^ifcfg-(.*)", f)]
-
-    def _get_ifcfgs_by_type(self, ifcfg_type, network_scripts_path):
-        ifcfgs = []
-        for ifcfg_file in self._get_net_config_files(network_scripts_path):
-            ifcfg = self.osmorphing_tool._read_config_file_sudo(ifcfg_file)
-            detected_type = ifcfg.get('TYPE')
-            if not detected_type:
-                detected_type = "Ethernet"
-                ifcfg["TYPE"] = detected_type
-            if detected_type.lower() == ifcfg_type.lower():
-                ifcfgs.append((ifcfg_file, ifcfg))
-        return ifcfgs

+ 10 - 26
coriolis/osmorphing/netpreserver/nmconnection.py

@@ -1,7 +1,6 @@
 # Copyright 2025 Cloudbase Solutions Srl
 # All Rights Reserved.
 
-import os
 import re
 
 from oslo_log import log as logging
@@ -13,24 +12,23 @@ LOG = logging.getLogger(__name__)
 
 class NmconnectionNetPreserver(base.BaseNetPreserver):
 
-    def __init__(self, osmorphing_tool):
-        super(NmconnectionNetPreserver, self).__init__(osmorphing_tool)
-        self.nmconnection_file = "etc/NetworkManager/system-connections"
-
     def check_net_preserver(self):
-        if self.osmorphing_tool._test_path(self.nmconnection_file):
-            nmconnection_files = self._get_nmconnection_files(
-                self.nmconnection_file)
+        nmconnection_path = self.osmorphing_tool._NM_CONNECTIONS_PATH
+        if self.osmorphing_tool._test_path(nmconnection_path):
+            nmconnection_files = self.osmorphing_tool._get_nmconnection_files(
+                nmconnection_path)
             if nmconnection_files:
-                nmconnection_ethernet = self._get_keyfiles_by_type(
-                    "ethernet", self.nmconnection_file)
+                nmconnection_ethernet = (
+                    self.osmorphing_tool._get_keyfiles_by_type(
+                        "ethernet", nmconnection_path))
                 if nmconnection_ethernet:
                     return True
         return False
 
     def parse_network(self):
-        nmconnection_ethernet = self._get_keyfiles_by_type(
-            "ethernet", self.nmconnection_file)
+        nmconnection_path = self.osmorphing_tool._NM_CONNECTIONS_PATH
+        nmconnection_ethernet = self.osmorphing_tool._get_keyfiles_by_type(
+            "ethernet", nmconnection_path)
         if nmconnection_ethernet:
             for nmconn_file, nmconn in nmconnection_ethernet:
                 name = nmconn.get("interface-name", nmconn.get("id"))
@@ -56,17 +54,3 @@ class NmconnectionNetPreserver(base.BaseNetPreserver):
                         "Could not find MAC address or IP addresses for "
                         "interface '%s' in nmconnection configuration "
                         "'%s'", name, nmconn_file)
-
-    def _get_nmconnection_files(self, network_scripts_path):
-        dir_content = self.osmorphing_tool._list_dir(network_scripts_path)
-        return [os.path.join(network_scripts_path, f)
-                for f in dir_content if re.match(
-                    r"^(.*\.nmconnection)$", f)]
-
-    def _get_keyfiles_by_type(self, nmconnection_type, network_scripts_path):
-        keyfiles = []
-        for file in self._get_nmconnection_files(network_scripts_path):
-            keyfile = self.osmorphing_tool._read_config_file_sudo(file)
-            if keyfile.get("type") == nmconnection_type:
-                keyfiles.append((file, keyfile))
-        return keyfiles

+ 103 - 24
coriolis/osmorphing/redhat.py

@@ -35,12 +35,29 @@ IPV6_FAILURE_FATAL=no
 NAME=%(device_name)s
 DEVICE=%(device_name)s
 ONBOOT=yes
-NM_CONTROLLED=no
+NM_CONTROLLED=%(nm_controlled)s
+"""
+
+NMCONNECTION_TEMPLATE = """[connection]
+id=%(device_name)s
+uuid=%(connection_uuid)s
+type=ethernet
+interface-name=%(device_name)s
+autoconnect=true
+
+[ethernet]
+
+[ipv4]
+method=auto
+may-fail=false
+
+[ipv6]
+method=auto
+addr-gen-mode=default
 """
 
 
 class BaseRedHatMorphingTools(base.BaseLinuxOSMorphingTools):
-    _NETWORK_SCRIPTS_PATH = "etc/sysconfig/network-scripts"
     BIOS_GRUB_LOCATION = "/boot/grub2"
     UEFI_GRUB_LOCATION = "/boot/efi/EFI/redhat"
 
@@ -95,22 +112,27 @@ class BaseRedHatMorphingTools(base.BaseLinuxOSMorphingTools):
         except Exception:
             return False
 
+    def _get_ifcfg_nm_controlled(self):
+        if self._version_supported_util(self._version, minimum=8):
+            return "yes"
+        return "no"
+
     def _set_dhcp_net_config(self, ifcfgs_ethernet):
-        for ifcfg_file, ifcfg in ifcfgs_ethernet:
-            if ifcfg.get("BOOTPROTO") == "none":
-                ifcfg["BOOTPROTO"] = "dhcp"
-                ifcfg["UUID"] = str(uuid.uuid4())
-
-                if 'IPADDR' in ifcfg:
-                    del ifcfg['IPADDR']
-                if 'GATEWAY' in ifcfg:
-                    del ifcfg['GATEWAY']
-                if 'NETMASK' in ifcfg:
-                    del ifcfg['NETMASK']
-                if 'NETWORK' in ifcfg:
-                    del ifcfg['NETWORK']
-
-                self._write_config_file(ifcfg_file, ifcfg)
+        for ifcfg_file, iface_cfg in ifcfgs_ethernet:
+            if iface_cfg.get("BOOTPROTO") == "none":
+                iface_cfg["BOOTPROTO"] = "dhcp"
+                iface_cfg["UUID"] = str(uuid.uuid4())
+
+                if 'IPADDR' in iface_cfg:
+                    del iface_cfg['IPADDR']
+                if 'GATEWAY' in iface_cfg:
+                    del iface_cfg['GATEWAY']
+                if 'NETMASK' in iface_cfg:
+                    del iface_cfg['NETMASK']
+                if 'NETWORK' in iface_cfg:
+                    del iface_cfg['NETWORK']
+
+                self._write_config_file(ifcfg_file, iface_cfg)
 
         network_cfg_file = "etc/sysconfig/network"
         network_cfg = self._read_config_file(network_cfg_file,
@@ -119,19 +141,67 @@ class BaseRedHatMorphingTools(base.BaseLinuxOSMorphingTools):
             del network_cfg["GATEWAY"]
             self._write_config_file(network_cfg_file, network_cfg)
 
+    def _get_existing_ethernet_nmconnection_files(self):
+        if not self._test_path(self._NM_CONNECTIONS_PATH):
+            return []
+        return [cfg_path for cfg_path, _ in self._get_keyfiles_by_type(
+            "ethernet", self._NM_CONNECTIONS_PATH)]
+
+    def _backup_nmconnection_files(self, nmconnection_files=None,
+                                   backup_file_suffix=".bak"):
+        if nmconnection_files is None:
+            nmconnection_files = (
+                self._get_existing_ethernet_nmconnection_files())
+        for cfg_path in nmconnection_files:
+            self._exec_cmd_chroot(
+                'mv "%s" "%s%s"' % (cfg_path, cfg_path, backup_file_suffix))
+            LOG.debug("Backed up nmconnection profile '%s'", cfg_path)
+
+    def _backup_all_ifcfg_configs(self, backup_file_suffix=".bak"):
+        if not self._test_path(self._NETWORK_SCRIPTS_PATH):
+            return
+        for cfg_path, _ in self._get_ifcfgs_by_type(
+                "Ethernet", self._NETWORK_SCRIPTS_PATH):
+            if os.path.basename(cfg_path) == "ifcfg-lo":
+                continue
+            self._exec_cmd_chroot(
+                'mv "%s" "%s%s"' % (cfg_path, cfg_path, backup_file_suffix))
+            LOG.debug("Backed up ifcfg profile '%s'", cfg_path)
+
     def _write_nic_configs(self, nics_info):
+        self._backup_all_ifcfg_configs()
         for idx, _ in enumerate(nics_info or []):
             dev_name = "eth%d" % idx
-            cfg_path = "etc/sysconfig/network-scripts/ifcfg-%s" % dev_name
-            if self._test_path(cfg_path):
-                self._exec_cmd_chroot(
-                    "cp %s %s.bak" % (cfg_path, cfg_path)
-                )
+            cfg_path = "%s/ifcfg-%s" % (self._NETWORK_SCRIPTS_PATH, dev_name)
             self._write_file_sudo(
                 cfg_path,
                 IFCFG_TEMPLATE % {
                     "device_name": dev_name,
+                    "nm_controlled": self._get_ifcfg_nm_controlled(),
+                })
+
+    def _write_nmconnection_configs(self, nics_info, nmconnection_files):
+        nics_info = nics_info or []
+        if not nics_info:
+            return
+
+        # Red Hat-based systems may have both nmconnection keyfiles and legacy
+        # ifcfg profiles; back up Ethernet profiles from both so stale source
+        # configs cannot override the freshly written DHCP profiles.
+        self._backup_nmconnection_files(nmconnection_files)
+        self._backup_all_ifcfg_configs()
+
+        for idx, _ in enumerate(nics_info):
+            dev_name = "eth%d" % idx
+            cfg_path = "%s/%s.nmconnection" % (
+                self._NM_CONNECTIONS_PATH, dev_name)
+            self._write_file_sudo(
+                cfg_path,
+                NMCONNECTION_TEMPLATE % {
+                    "device_name": dev_name,
+                    "connection_uuid": str(uuid.uuid4()),
                 })
+            self._exec_cmd_chroot("chmod 600 /%s" % cfg_path)
 
     def _comment_keys_from_ifcfg_files(
             self, keys, interfaces=None, backup_file_suffix=".bak"):
@@ -141,7 +211,7 @@ class BaseRedHatMorphingTools(base.BaseLinuxOSMorphingTools):
         if not interfaces:
             interfaces = []
         scripts_dir = os.path.join(
-            self._os_root_dir, "etc/sysconfig/network-scripts")
+            self._os_root_dir, self._NETWORK_SCRIPTS_PATH)
         all_ifcfg_files = utils.list_ssh_dir(self._ssh, scripts_dir)
         regex = "^(ifcfg-[a-z0-9]+)$"
 
@@ -169,8 +239,17 @@ class BaseRedHatMorphingTools(base.BaseLinuxOSMorphingTools):
 
     def set_net_config(self, nics_info, dhcp):
         if dhcp:
+            nics_info = nics_info or []
+            if not nics_info:
+                return
             self.disable_predictable_nic_names()
-            self._write_nic_configs(nics_info)
+            nmconnection_files = (
+                self._get_existing_ethernet_nmconnection_files())
+            if nmconnection_files:
+                self._write_nmconnection_configs(
+                    nics_info, nmconnection_files)
+            else:
+                self._write_nic_configs(nics_info)
             return
 
         LOG.info("Setting static IP configuration")

+ 5 - 40
coriolis/tests/osmorphing/netpreserver/test_ifcfg.py

@@ -33,43 +33,8 @@ class IfcfgNetPreserverTestCase(test_base.CoriolisBaseTestCase):
                 mock.sentinel.osmorphing_parameters,
                 mock.sentinel.operation_timeout))
 
-    @mock.patch.object(base.BaseLinuxOSMorphingTools, '_read_config_file_sudo')
-    @mock.patch.object(ifcfg.IfcfgNetPreserver, '_get_net_config_files')
-    def test_get_ifcfgs_by_type(self, mock_get_net_config_files,
-                                mock_read_config_file_sudo):
-        mock_get_net_config_files.return_value = [mock.sentinel.ifcfg_file]
-        mock_read_config_file_sudo.side_effect = [{"TYPE": "Ethernet"}]
-
-        result = self.netpreserver._get_ifcfgs_by_type(
-            "Ethernet", self.netpreserver.network_scripts_path)
-
-        mock_read_config_file_sudo.assert_called_once_with(
-            mock.sentinel.ifcfg_file)
-        mock_get_net_config_files.assert_called_once_with(
-            self.netpreserver.network_scripts_path)
-
-        self.assertEqual(
-            result, [(mock.sentinel.ifcfg_file, {"TYPE": "Ethernet"})])
-
-    @mock.patch.object(base.BaseLinuxOSMorphingTools, '_list_dir')
-    def test_get_net_config_files(self, mock_list_dir):
-        mock_list_dir.return_value = ['ifcfg-eth0', 'ifcfg-lo', 'other-file']
-
-        result = self.netpreserver._get_net_config_files(
-            self.netpreserver.network_scripts_path)
-
-        expected_result = [
-            'etc/sysconfig/network-scripts/ifcfg-eth0',
-            'etc/sysconfig/network-scripts/ifcfg-lo'
-        ]
-
-        mock_list_dir.assert_called_once_with(
-            self.netpreserver.network_scripts_path)
-
-        self.assertEqual(result, expected_result)
-
-    @mock.patch.object(ifcfg.IfcfgNetPreserver, '_get_ifcfgs_by_type')
-    @mock.patch.object(ifcfg.IfcfgNetPreserver, '_get_net_config_files')
+    @mock.patch.object(base.BaseLinuxOSMorphingTools, '_get_ifcfgs_by_type')
+    @mock.patch.object(base.BaseLinuxOSMorphingTools, '_get_net_config_files')
     @mock.patch.object(base.BaseLinuxOSMorphingTools, '_test_path')
     def test_check_net_preserver_True(self, mock_test_path,
                                       mock_get_net_config_files,
@@ -97,8 +62,8 @@ class IfcfgNetPreserverTestCase(test_base.CoriolisBaseTestCase):
 
         self.assertTrue(result)
 
-    @mock.patch.object(ifcfg.IfcfgNetPreserver, '_get_ifcfgs_by_type')
-    @mock.patch.object(ifcfg.IfcfgNetPreserver, '_get_net_config_files')
+    @mock.patch.object(base.BaseLinuxOSMorphingTools, '_get_ifcfgs_by_type')
+    @mock.patch.object(base.BaseLinuxOSMorphingTools, '_get_net_config_files')
     @mock.patch.object(base.BaseLinuxOSMorphingTools, '_test_path')
     def test_check_net_preserver_no_ifcfg_files(self, mock_test_path,
                                                 mock_get_net_config_files,
@@ -114,7 +79,7 @@ class IfcfgNetPreserverTestCase(test_base.CoriolisBaseTestCase):
 
         self.assertFalse(result)
 
-    @mock.patch.object(ifcfg.IfcfgNetPreserver, '_get_ifcfgs_by_type')
+    @mock.patch.object(base.BaseLinuxOSMorphingTools, '_get_ifcfgs_by_type')
     def test_parse_network(self, mock_get_ifcfgs_by_type):
         ifcfg_file_with_device = "etc/sysconfig/network-scripts/ifcfg-eth0"
         ifcfg_with_device = {

+ 43 - 54
coriolis/tests/osmorphing/netpreserver/test_nmconnection.py

@@ -30,63 +30,44 @@ class NmconnectionNetPreserverTestCase(test_base.CoriolisBaseTestCase):
                 mock.sentinel.operation_timeout)
         )
 
-    @mock.patch.object(base.BaseLinuxOSMorphingTools, '_list_dir')
-    def test_get_nmconnection_files(self, mock_list_dir):
-        mock_list_dir.return_value = [
-            'eth0.nmconnection', 'eth1.nmconnection', 'other-file']
-        result = self.netpreserver._get_nmconnection_files(
-            self.netpreserver.nmconnection_file)
-        expected_result = [
-            'etc/NetworkManager/system-connections/eth0.nmconnection',
-            'etc/NetworkManager/system-connections/eth1.nmconnection'
-        ]
-        mock_list_dir.assert_called_once_with(
-            self.netpreserver.nmconnection_file)
-        self.assertEqual(result, expected_result)
-
-    @mock.patch.object(base.BaseLinuxOSMorphingTools, '_read_config_file_sudo')
-    @mock.patch.object(nmconnection.NmconnectionNetPreserver,
-                       '_get_nmconnection_files')
-    def test_get_keyfiles_by_type(self, mock_get_nmconnection_files,
-                                  mock_read_config_file_sudo):
-        mock_get_nmconnection_files.return_value = [mock.sentinel.nmconn_file]
-        mock_read_config_file_sudo.side_effect = [{"type": "ethernet"}]
-
-        result = self.netpreserver._get_keyfiles_by_type(
-            "ethernet", self.netpreserver.nmconnection_file)
-
-        mock_get_nmconnection_files.assert_called_once_with(
-            self.netpreserver.nmconnection_file)
-        mock_read_config_file_sudo.assert_called_once_with(
-            mock.sentinel.nmconn_file)
-        self.assertEqual(result, [(mock.sentinel.nmconn_file,
-                                   {"type": "ethernet"})])
-
+    @mock.patch.object(base.BaseLinuxOSMorphingTools, '_get_keyfiles_by_type')
+    @mock.patch.object(
+        base.BaseLinuxOSMorphingTools, '_get_nmconnection_files')
     @mock.patch.object(base.BaseLinuxOSMorphingTools, '_test_path')
-    @mock.patch.object(nmconnection.NmconnectionNetPreserver,
-                       '_get_nmconnection_files')
-    @mock.patch.object(nmconnection.NmconnectionNetPreserver,
-                       '_get_keyfiles_by_type')
-    def test_check_net_preserver_True(self, mock_get_keyfiles_by_type,
+    def test_check_net_preserver_True(self, mock_test_path,
                                       mock_get_nmconnection_files,
-                                      mock_test_path):
+                                      mock_get_keyfiles_by_type):
         mock_test_path.return_value = True
-        mock_get_nmconnection_files.return_value = ["eth0.nmconnection",
-                                                    "eth1.nmconnection"]
+        mock_get_nmconnection_files.return_value = [
+            'etc/NetworkManager/system-connections/eth0.nmconnection']
         mock_get_keyfiles_by_type.return_value = [
-            (mock.sentinel.nmconn_file, {"type": "ethernet",
-                                         "connection": {"id": "eth0"}})
-        ]
+            (mock.sentinel.nmconn_file, {"type": "ethernet"})]
 
         result = self.netpreserver.check_net_preserver()
 
         self.assertTrue(result)
 
+    @mock.patch.object(base.BaseLinuxOSMorphingTools, '_get_keyfiles_by_type')
+    @mock.patch.object(
+        base.BaseLinuxOSMorphingTools, '_get_nmconnection_files')
+    @mock.patch.object(base.BaseLinuxOSMorphingTools, '_test_path')
+    def test_check_net_preserver_no_ethernet_files(
+            self, mock_test_path, mock_get_nmconnection_files,
+            mock_get_keyfiles_by_type):
+        mock_test_path.return_value = True
+        mock_get_nmconnection_files.return_value = [
+            'etc/NetworkManager/system-connections/vpn.nmconnection']
+        mock_get_keyfiles_by_type.return_value = []
+
+        result = self.netpreserver.check_net_preserver()
+
+        self.assertFalse(result)
+
+    @mock.patch.object(
+        base.BaseLinuxOSMorphingTools, '_get_nmconnection_files')
     @mock.patch.object(base.BaseLinuxOSMorphingTools, '_test_path')
-    @mock.patch.object(nmconnection.NmconnectionNetPreserver,
-                       '_get_nmconnection_files')
-    def test_check_net_preserver_no_files(self, mock_get_nmconnection_files,
-                                          mock_test_path):
+    def test_check_net_preserver_no_files(self, mock_test_path,
+                                          mock_get_nmconnection_files):
         mock_test_path.return_value = True
         mock_get_nmconnection_files.return_value = []
 
@@ -94,11 +75,19 @@ class NmconnectionNetPreserverTestCase(test_base.CoriolisBaseTestCase):
 
         self.assertFalse(result)
 
-    @mock.patch.object(nmconnection.NmconnectionNetPreserver,
-                       '_get_keyfiles_by_type')
+    @mock.patch.object(base.BaseLinuxOSMorphingTools, '_test_path')
+    def test_check_net_preserver_no_dir(self, mock_test_path):
+        mock_test_path.return_value = False
+
+        result = self.netpreserver.check_net_preserver()
+
+        self.assertFalse(result)
+
+    @mock.patch.object(base.BaseLinuxOSMorphingTools, '_get_keyfiles_by_type')
     def test_parse_network(self, mock_get_keyfiles_by_type):
+        nmconnection_path = base.BaseLinuxOSMorphingTools._NM_CONNECTIONS_PATH
         nmconn_file_with_id = (
-            self.netpreserver.nmconnection_file + "/eth0.nmconnection"
+            nmconnection_path + "/eth0.nmconnection"
         )
         nmconn_with_id = {
             "id": "eth0",
@@ -106,28 +95,28 @@ class NmconnectionNetPreserverTestCase(test_base.CoriolisBaseTestCase):
             "address1": "192.168.1.10/24"
         }
         nmconn_file_without_id = (
-            self.netpreserver.nmconnection_file + "/eth1.nmconnection"
+            nmconnection_path + "/eth1.nmconnection"
         )
         nmconn_without_id = {
             "mac-address": "AA:BB:CC:DD:EE:FF",
             "Address2": "192.168.1.20/24, 192.168.1.21/24"
         }
         nmconn_file_without_mac_address = (
-            self.netpreserver.nmconnection_file + "/eth2.nmconnection"
+            nmconnection_path + "/eth2.nmconnection"
         )
         nmconn_without_mac_address = {
             "id": "eth2",
             "address1": "192.168.1.30/24",
         }
         nmconn_file_without_mac_address_ip_address = (
-            self.netpreserver.nmconnection_file + "/eth3.nmconnection"
+            nmconnection_path + "/eth3.nmconnection"
         )
         nmconn_without_mac_address_ip_address = {
             "id": "id_eth3",
             "address": "192.168.1.40/24",
         }
         nmconn_file_with_space = (
-            self.netpreserver.nmconnection_file + "/System ethx.nmconnection"
+            nmconnection_path + "/System ethx.nmconnection"
         )
         nmconn_without_mac_address_and_id = {
             "address1": "192.168.1.50/24",

+ 88 - 0
coriolis/tests/osmorphing/test_base.py

@@ -500,6 +500,94 @@ class BaseLinuxOSMorphingToolsTestBase(test_base.CoriolisBaseTestCase):
 
         mock_read_file_sudo.assert_not_called()
 
+    @mock.patch.object(base.BaseLinuxOSMorphingTools, '_list_dir')
+    def test__get_net_config_files(self, mock_list_dir):
+        network_scripts_path = (
+            base.BaseLinuxOSMorphingTools._NETWORK_SCRIPTS_PATH)
+        mock_list_dir.return_value = ['ifcfg-eth0', 'ifcfg-lo', 'other-file']
+
+        result = self.os_morphing_tools._get_net_config_files(
+            network_scripts_path)
+
+        mock_list_dir.assert_called_once_with(network_scripts_path)
+        self.assertEqual(
+            result,
+            [
+                'etc/sysconfig/network-scripts/ifcfg-eth0',
+                'etc/sysconfig/network-scripts/ifcfg-lo',
+            ])
+
+    @mock.patch.object(base.BaseLinuxOSMorphingTools, '_read_config_file_sudo')
+    @mock.patch.object(base.BaseLinuxOSMorphingTools, '_get_net_config_files')
+    def test__get_ifcfgs_by_type(self, mock_get_net_config_files,
+                                 mock_read_config_file_sudo):
+        network_scripts_path = (
+            base.BaseLinuxOSMorphingTools._NETWORK_SCRIPTS_PATH)
+        mock_get_net_config_files.return_value = [mock.sentinel.ifcfg_file]
+        mock_read_config_file_sudo.side_effect = [{"TYPE": "Ethernet"}]
+
+        result = self.os_morphing_tools._get_ifcfgs_by_type(
+            "Ethernet", network_scripts_path)
+
+        mock_get_net_config_files.assert_called_once_with(network_scripts_path)
+        mock_read_config_file_sudo.assert_called_once_with(
+            mock.sentinel.ifcfg_file)
+        self.assertEqual(
+            result, [(mock.sentinel.ifcfg_file, {"TYPE": "Ethernet"})])
+
+    @mock.patch.object(base.BaseLinuxOSMorphingTools, '_read_config_file_sudo')
+    @mock.patch.object(base.BaseLinuxOSMorphingTools, '_get_net_config_files')
+    def test__get_ifcfgs_by_type_default_type(self, mock_get_net_config_files,
+                                              mock_read_config_file_sudo):
+        network_scripts_path = (
+            base.BaseLinuxOSMorphingTools._NETWORK_SCRIPTS_PATH)
+        mock_get_net_config_files.return_value = [mock.sentinel.ifcfg_file]
+        mock_read_config_file_sudo.side_effect = [{}]
+
+        result = self.os_morphing_tools._get_ifcfgs_by_type(
+            "Ethernet", network_scripts_path)
+
+        self.assertEqual(
+            result, [(mock.sentinel.ifcfg_file, {"TYPE": "Ethernet"})])
+
+    @mock.patch.object(base.BaseLinuxOSMorphingTools, '_list_dir')
+    def test__get_nmconnection_files(self, mock_list_dir):
+        network_scripts_path = (
+            base.BaseLinuxOSMorphingTools._NM_CONNECTIONS_PATH)
+        mock_list_dir.return_value = [
+            'eth0.nmconnection', 'eth1.nmconnection', 'other-file']
+
+        result = self.os_morphing_tools._get_nmconnection_files(
+            network_scripts_path)
+
+        mock_list_dir.assert_called_once_with(network_scripts_path)
+        self.assertEqual(
+            result,
+            [
+                'etc/NetworkManager/system-connections/eth0.nmconnection',
+                'etc/NetworkManager/system-connections/eth1.nmconnection',
+            ])
+
+    @mock.patch.object(base.BaseLinuxOSMorphingTools, '_read_config_file_sudo')
+    @mock.patch.object(
+        base.BaseLinuxOSMorphingTools, '_get_nmconnection_files')
+    def test__get_keyfiles_by_type(self, mock_get_nmconnection_files,
+                                   mock_read_config_file_sudo):
+        network_scripts_path = (
+            base.BaseLinuxOSMorphingTools._NM_CONNECTIONS_PATH)
+        mock_get_nmconnection_files.return_value = [mock.sentinel.nmconn_file]
+        mock_read_config_file_sudo.side_effect = [{"type": "ethernet"}]
+
+        result = self.os_morphing_tools._get_keyfiles_by_type(
+            "ethernet", network_scripts_path)
+
+        mock_get_nmconnection_files.assert_called_once_with(
+            network_scripts_path)
+        mock_read_config_file_sudo.assert_called_once_with(
+            mock.sentinel.nmconn_file)
+        self.assertEqual(
+            result, [(mock.sentinel.nmconn_file, {"type": "ethernet"})])
+
     @mock.patch.object(base.BaseLinuxOSMorphingTools, '_test_path')
     @mock.patch.object(base.BaseLinuxOSMorphingTools, '_exec_cmd')
     def test__copy_resolv_conf(self, mock_exec_cmd, mock_test_path):

+ 295 - 26
coriolis/tests/osmorphing/test_redhat.py

@@ -149,32 +149,317 @@ class BaseRedHatMorphingToolsTestCase(test_base.CoriolisBaseTestCase):
                       mock_read_config_file.return_value)
         ])
 
+    @mock.patch.object(
+        redhat.BaseRedHatMorphingTools, '_backup_all_ifcfg_configs')
     @mock.patch.object(base.BaseLinuxOSMorphingTools, '_write_file_sudo')
-    @mock.patch.object(base.BaseLinuxOSMorphingTools, '_exec_cmd_chroot')
-    @mock.patch.object(base.BaseLinuxOSMorphingTools, '_test_path')
     def test_write_nic_configs(
-            self, mock_test_path, mock_exec_cmd_chroot, mock_write_file_sudo):
+            self, mock_write_file_sudo, mock_backup_all_ifcfg_configs):
         nics_info = [{'name': 'eth0'}, {'name': 'eth1'}]
-        mock_test_path.return_value = True
 
         self.morphing_tools._write_nic_configs(nics_info)
 
+        mock_backup_all_ifcfg_configs.assert_called_once_with()
         mock_write_file_sudo.assert_has_calls([
             mock.call(
                 "etc/sysconfig/network-scripts/ifcfg-eth0",
-                redhat.IFCFG_TEMPLATE % {"device_name": "eth0"},
+                redhat.IFCFG_TEMPLATE % {
+                    "device_name": "eth0",
+                    "nm_controlled": "no",
+                },
             ),
             mock.call(
                 "etc/sysconfig/network-scripts/ifcfg-eth1",
-                redhat.IFCFG_TEMPLATE % {"device_name": "eth1"},
+                redhat.IFCFG_TEMPLATE % {
+                    "device_name": "eth1",
+                    "nm_controlled": "no",
+                },
             )
         ])
+
+    @mock.patch.object(
+        redhat.BaseRedHatMorphingTools, '_backup_all_ifcfg_configs')
+    @mock.patch.object(base.BaseLinuxOSMorphingTools, '_write_file_sudo')
+    def test_write_nic_configs_rhel8(
+            self, mock_write_file_sudo, mock_backup_all_ifcfg_configs):
+        self.morphing_tools._version = '8.10'
+        nics_info = [{'name': 'eth0'}]
+
+        self.morphing_tools._write_nic_configs(nics_info)
+
+        mock_backup_all_ifcfg_configs.assert_called_once_with()
+        mock_write_file_sudo.assert_called_once_with(
+            "etc/sysconfig/network-scripts/ifcfg-eth0",
+            redhat.IFCFG_TEMPLATE % {
+                "device_name": "eth0",
+                "nm_controlled": "yes",
+            },
+        )
+
+    @ddt.data(('6', 'no'), ('7.9', 'no'), ('8.10', 'yes'))
+    @ddt.unpack
+    def test__get_ifcfg_nm_controlled(self, release_version, expected):
+        self.morphing_tools._version = release_version
+
+        result = self.morphing_tools._get_ifcfg_nm_controlled()
+
+        self.assertEqual(expected, result)
+
+    @mock.patch.object(
+        redhat.BaseRedHatMorphingTools, '_backup_all_ifcfg_configs'
+    )
+    @mock.patch.object(
+        redhat.BaseRedHatMorphingTools, '_backup_nmconnection_files'
+    )
+    @mock.patch.object(base.BaseLinuxOSMorphingTools, '_write_file_sudo')
+    @mock.patch.object(base.BaseLinuxOSMorphingTools, '_exec_cmd_chroot')
+    def test_write_nmconnection_configs(
+            self, mock_exec_cmd_chroot, mock_write_file_sudo,
+            mock_backup_nmconnection_files,
+            mock_backup_all_ifcfg_configs):
+        nics_info = [{'name': 'eth0'}]
+        nmconnection_files = [
+            'etc/NetworkManager/system-connections/eth0.nmconnection']
+
+        self.morphing_tools._write_nmconnection_configs(
+            nics_info, nmconnection_files)
+
+        mock_backup_nmconnection_files.assert_called_once_with(
+            nmconnection_files)
+        mock_backup_all_ifcfg_configs.assert_called_once_with()
+        mock_write_file_sudo.assert_called_once()
+        args, _ = mock_write_file_sudo.call_args
+        self.assertEqual(
+            args[0],
+            "etc/NetworkManager/system-connections/eth0.nmconnection")
+        self.assertIn("[connection]", args[1])
+        self.assertIn("interface-name=eth0", args[1])
+        self.assertIn("method=auto", args[1])
+        self.assertIn("may-fail=false", args[1])
+        mock_exec_cmd_chroot.assert_called_once_with(
+            "chmod 600 /etc/NetworkManager/system-connections/"
+            "eth0.nmconnection"
+        )
+
+    @mock.patch.object(
+        redhat.BaseRedHatMorphingTools, '_backup_all_ifcfg_configs'
+    )
+    @mock.patch.object(
+        redhat.BaseRedHatMorphingTools, '_backup_nmconnection_files'
+    )
+    @mock.patch.object(base.BaseLinuxOSMorphingTools, '_write_file_sudo')
+    @mock.patch.object(base.BaseLinuxOSMorphingTools, '_exec_cmd_chroot')
+    def test_write_nmconnection_configs_no_nics(
+            self, mock_exec_cmd_chroot, mock_write_file_sudo,
+            mock_backup_nmconnection_files,
+            mock_backup_all_ifcfg_configs):
+        self.morphing_tools._write_nmconnection_configs(
+            None, ['etc/NetworkManager/system-connections/eth0.nmconnection'])
+
+        mock_backup_nmconnection_files.assert_not_called()
+        mock_backup_all_ifcfg_configs.assert_not_called()
+        mock_write_file_sudo.assert_not_called()
+        mock_exec_cmd_chroot.assert_not_called()
+
+    @mock.patch.object(base.BaseLinuxOSMorphingTools, '_exec_cmd_chroot')
+    def test__backup_nmconnection_files(self, mock_exec_cmd_chroot):
+        nmconnection_files = [
+            'etc/NetworkManager/system-connections/ens192.nmconnection',
+        ]
+
+        self.morphing_tools._backup_nmconnection_files(nmconnection_files)
+
+        mock_exec_cmd_chroot.assert_called_once_with(
+            'mv "etc/NetworkManager/system-connections/'
+            'ens192.nmconnection" '
+            '"etc/NetworkManager/system-connections/'
+            'ens192.nmconnection.bak"'
+        )
+
+    @mock.patch.object(
+        redhat.BaseRedHatMorphingTools,
+        '_get_existing_ethernet_nmconnection_files')
+    @mock.patch.object(base.BaseLinuxOSMorphingTools, '_exec_cmd_chroot')
+    def test__backup_nmconnection_files_fetches_files(
+            self, mock_exec_cmd_chroot,
+            mock_get_ethernet_nm_files):
+        mock_get_ethernet_nm_files.return_value = [
+            'etc/NetworkManager/system-connections/eth0.nmconnection']
+
+        self.morphing_tools._backup_nmconnection_files()
+
+        mock_get_ethernet_nm_files.assert_called_once_with()
+        mock_exec_cmd_chroot.assert_called_once()
+
+    @mock.patch.object(
+        redhat.BaseRedHatMorphingTools,
+        '_get_existing_ethernet_nmconnection_files')
+    @mock.patch.object(base.BaseLinuxOSMorphingTools, '_exec_cmd_chroot')
+    def test__backup_nmconnection_files_no_files(
+            self, mock_exec_cmd_chroot,
+            mock_get_ethernet_nm_files):
+        mock_get_ethernet_nm_files.return_value = []
+
+        self.morphing_tools._backup_nmconnection_files()
+
+        mock_exec_cmd_chroot.assert_not_called()
+
+    @mock.patch.object(base.BaseLinuxOSMorphingTools, '_exec_cmd_chroot')
+    @mock.patch.object(base.BaseLinuxOSMorphingTools, '_get_ifcfgs_by_type')
+    @mock.patch.object(base.BaseLinuxOSMorphingTools, '_test_path')
+    def test__backup_all_ifcfg_configs(self, mock_test_path,
+                                       mock_get_ifcfgs_by_type,
+                                       mock_exec_cmd_chroot):
+        mock_test_path.return_value = True
+        mock_get_ifcfgs_by_type.return_value = [
+            ("etc/sysconfig/network-scripts/ifcfg-ens33",
+             {"TYPE": "Ethernet"}),
+            ("etc/sysconfig/network-scripts/ifcfg-eth0",
+             {"TYPE": "Ethernet"}),
+        ]
+
+        self.morphing_tools._backup_all_ifcfg_configs()
+
+        mock_get_ifcfgs_by_type.assert_called_once_with(
+            "Ethernet", self.morphing_tools._NETWORK_SCRIPTS_PATH)
         mock_exec_cmd_chroot.assert_has_calls([
-            mock.call("cp etc/sysconfig/network-scripts/ifcfg-eth0 "
-                      "etc/sysconfig/network-scripts/ifcfg-eth0.bak"),
-            mock.call("cp etc/sysconfig/network-scripts/ifcfg-eth1 "
-                      "etc/sysconfig/network-scripts/ifcfg-eth1.bak")
+            mock.call(
+                'mv "etc/sysconfig/network-scripts/ifcfg-ens33" '
+                '"etc/sysconfig/network-scripts/ifcfg-ens33.bak"'
+            ),
+            mock.call(
+                'mv "etc/sysconfig/network-scripts/ifcfg-eth0" '
+                '"etc/sysconfig/network-scripts/ifcfg-eth0.bak"'
+            ),
         ])
+        self.assertEqual(mock_exec_cmd_chroot.call_count, 2)
+
+    @mock.patch.object(base.BaseLinuxOSMorphingTools, '_exec_cmd_chroot')
+    @mock.patch.object(base.BaseLinuxOSMorphingTools, '_get_ifcfgs_by_type')
+    @mock.patch.object(base.BaseLinuxOSMorphingTools, '_test_path')
+    def test__backup_all_ifcfg_configs_no_dir(self, mock_test_path,
+                                              mock_get_ifcfgs_by_type,
+                                              mock_exec_cmd_chroot):
+        mock_test_path.return_value = False
+
+        self.morphing_tools._backup_all_ifcfg_configs()
+
+        mock_get_ifcfgs_by_type.assert_not_called()
+        mock_exec_cmd_chroot.assert_not_called()
+
+    @mock.patch.object(
+        base.BaseLinuxOSMorphingTools, '_get_keyfiles_by_type')
+    @mock.patch.object(base.BaseLinuxOSMorphingTools, '_test_path')
+    def test__get_existing_ethernet_nmconnection_files(
+            self, mock_test_path, mock_get_keyfiles_by_type):
+        mock_test_path.return_value = True
+        mock_get_keyfiles_by_type.return_value = [
+            ('etc/NetworkManager/system-connections/eth0.nmconnection',
+             {'type': 'ethernet'})]
+
+        result = (
+            self.morphing_tools._get_existing_ethernet_nmconnection_files())
+
+        mock_get_keyfiles_by_type.assert_called_once_with(
+            "ethernet", self.morphing_tools._NM_CONNECTIONS_PATH)
+        self.assertEqual(
+            result,
+            ['etc/NetworkManager/system-connections/eth0.nmconnection'])
+
+    @mock.patch.object(base.BaseLinuxOSMorphingTools, '_test_path')
+    def test__get_existing_ethernet_nmconnection_files_no_dir(
+            self, mock_test_path):
+        mock_test_path.return_value = False
+
+        result = (
+            self.morphing_tools._get_existing_ethernet_nmconnection_files())
+
+        self.assertEqual(result, [])
+
+    @mock.patch.object(
+        redhat.BaseRedHatMorphingTools, 'disable_predictable_nic_names'
+    )
+    @mock.patch.object(redhat.BaseRedHatMorphingTools, '_write_nic_configs')
+    @mock.patch.object(
+        redhat.BaseRedHatMorphingTools, '_write_nmconnection_configs'
+    )
+    @mock.patch.object(
+        redhat.BaseRedHatMorphingTools,
+        '_get_existing_ethernet_nmconnection_files',
+    )
+    def test_set_net_config_dhcp(
+            self, mock_get_existing_ethernet_nmconnection_files,
+            mock_write_nmconnection_configs,
+            mock_write_nic_configs,
+            mock_disable_predictable_nic_names):
+        mock_get_existing_ethernet_nmconnection_files.return_value = []
+        nics_info = [{
+            'mac_address': mock.sentinel.mac_address,
+        }]
+        dhcp = True
+
+        self.morphing_tools.set_net_config(nics_info, dhcp)
+
+        mock_get_existing_ethernet_nmconnection_files.assert_called_once_with()
+        mock_write_nmconnection_configs.assert_not_called()
+        mock_disable_predictable_nic_names.assert_called_once()
+        mock_write_nic_configs.assert_called_once_with(nics_info)
+
+    @mock.patch.object(
+        redhat.BaseRedHatMorphingTools, 'disable_predictable_nic_names'
+    )
+    @mock.patch.object(
+        redhat.BaseRedHatMorphingTools, '_write_nmconnection_configs'
+    )
+    @mock.patch.object(redhat.BaseRedHatMorphingTools, '_write_nic_configs')
+    @mock.patch.object(
+        redhat.BaseRedHatMorphingTools,
+        '_get_existing_ethernet_nmconnection_files',
+    )
+    def test_set_net_config_dhcp_nmconnection(
+            self, mock_get_existing_ethernet_nmconnection_files,
+            mock_write_nic_configs,
+            mock_write_nmconnection_configs,
+            mock_disable_predictable_nic_names):
+        nm_files = [
+            'etc/NetworkManager/system-connections/eth0.nmconnection']
+        mock_get_existing_ethernet_nmconnection_files.return_value = nm_files
+        nics_info = [{
+            'mac_address': mock.sentinel.mac_address,
+        }]
+        dhcp = True
+
+        self.morphing_tools.set_net_config(nics_info, dhcp)
+
+        mock_disable_predictable_nic_names.assert_called_once()
+        mock_write_nmconnection_configs.assert_called_once_with(
+            nics_info, nm_files)
+        mock_write_nic_configs.assert_not_called()
+
+    @mock.patch.object(
+        redhat.BaseRedHatMorphingTools, 'disable_predictable_nic_names'
+    )
+    @mock.patch.object(
+        redhat.BaseRedHatMorphingTools, '_write_nmconnection_configs'
+    )
+    @mock.patch.object(redhat.BaseRedHatMorphingTools, '_write_nic_configs')
+    @mock.patch.object(
+        redhat.BaseRedHatMorphingTools,
+        '_get_existing_ethernet_nmconnection_files',
+    )
+    def test_set_net_config_dhcp_nmconnection_no_nics(
+            self, mock_get_existing_ethernet_nmconnection_files,
+            mock_write_nic_configs,
+            mock_write_nmconnection_configs,
+            mock_disable_predictable_nic_names):
+        mock_get_existing_ethernet_nmconnection_files.return_value = [
+            'etc/NetworkManager/system-connections/eth0.nmconnection']
+
+        self.morphing_tools.set_net_config(None, True)
+
+        mock_get_existing_ethernet_nmconnection_files.assert_not_called()
+        mock_disable_predictable_nic_names.assert_not_called()
+        mock_write_nmconnection_configs.assert_not_called()
+        mock_write_nic_configs.assert_not_called()
 
     @mock.patch.object(base.BaseLinuxOSMorphingTools, '_exec_cmd')
     @mock.patch.object(redhat.utils, 'list_ssh_dir')
@@ -240,22 +525,6 @@ class BaseRedHatMorphingToolsTestCase(test_base.CoriolisBaseTestCase):
             ),
         ])
 
-    @mock.patch.object(
-        redhat.BaseRedHatMorphingTools, 'disable_predictable_nic_names'
-    )
-    @mock.patch.object(redhat.BaseRedHatMorphingTools, '_write_nic_configs')
-    def test_set_net_config_dhcp(self, mock_write_nic_configs,
-                                 mock_disable_predictable_nic_names):
-        nics_info = [{
-            'mac_address': mock.sentinel.mac_address,
-        }]
-        dhcp = True
-
-        self.morphing_tools.set_net_config(nics_info, dhcp)
-
-        mock_disable_predictable_nic_names.assert_called_once()
-        mock_write_nic_configs.assert_called_once_with(nics_info)
-
     @mock.patch.object(
         redhat.BaseRedHatMorphingTools, 'disable_predictable_nic_names'
     )