diff --git a/coriolis/osmorphing/base.py b/coriolis/osmorphing/base.py index 9dcdf1adc..d45f5f41b 100644 --- a/coriolis/osmorphing/base.py +++ b/coriolis/osmorphing/base.py @@ -220,6 +220,8 @@ def set_environment(self, environment): 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, @@ -432,6 +434,37 @@ def _read_config_file_sudo(self, chroot_path, check_exists=False): 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) diff --git a/coriolis/osmorphing/netpreserver/ifcfg.py b/coriolis/osmorphing/netpreserver/ifcfg.py index ad2f01b8b..4525fb8ad 100644 --- a/coriolis/osmorphing/netpreserver/ifcfg.py +++ b/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 @@ 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 @@ def parse_network(self): "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 diff --git a/coriolis/osmorphing/netpreserver/nmconnection.py b/coriolis/osmorphing/netpreserver/nmconnection.py index b371a3193..7e354a653 100644 --- a/coriolis/osmorphing/netpreserver/nmconnection.py +++ b/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 @@ 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 @@ def parse_network(self): "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 diff --git a/coriolis/osmorphing/redhat.py b/coriolis/osmorphing/redhat.py index 6b64afbae..869a2d214 100644 --- a/coriolis/osmorphing/redhat.py +++ b/coriolis/osmorphing/redhat.py @@ -35,12 +35,29 @@ 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 @@ def _has_systemd(self): 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 @@ def _set_dhcp_net_config(self, ifcfgs_ethernet): 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 @@ def _comment_keys_from_ifcfg_files( 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 @@ def _comment_keys_from_ifcfg_files( 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") diff --git a/coriolis/tests/osmorphing/netpreserver/test_ifcfg.py b/coriolis/tests/osmorphing/netpreserver/test_ifcfg.py index 0c7cc4769..40f3cdc63 100644 --- a/coriolis/tests/osmorphing/netpreserver/test_ifcfg.py +++ b/coriolis/tests/osmorphing/netpreserver/test_ifcfg.py @@ -33,43 +33,8 @@ def setUp(self): 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 @@ def test_check_net_preserver_True(self, mock_test_path, 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 @@ def test_check_net_preserver_no_ifcfg_files(self, mock_test_path, 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 = { diff --git a/coriolis/tests/osmorphing/netpreserver/test_nmconnection.py b/coriolis/tests/osmorphing/netpreserver/test_nmconnection.py index 0948a785d..942c098b6 100644 --- a/coriolis/tests/osmorphing/netpreserver/test_nmconnection.py +++ b/coriolis/tests/osmorphing/netpreserver/test_nmconnection.py @@ -30,63 +30,44 @@ def setUp(self): 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 @@ def test_check_net_preserver_no_files(self, mock_get_nmconnection_files, 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 @@ def test_parse_network(self, mock_get_keyfiles_by_type): "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", diff --git a/coriolis/tests/osmorphing/test_base.py b/coriolis/tests/osmorphing/test_base.py index 2d121a7b7..f456e7465 100644 --- a/coriolis/tests/osmorphing/test_base.py +++ b/coriolis/tests/osmorphing/test_base.py @@ -496,6 +496,94 @@ def test__read_config_file_sudo_raises( 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): diff --git a/coriolis/tests/osmorphing/test_redhat.py b/coriolis/tests/osmorphing/test_redhat.py index 0b3bdab19..8d9d054a7 100644 --- a/coriolis/tests/osmorphing/test_redhat.py +++ b/coriolis/tests/osmorphing/test_redhat.py @@ -149,32 +149,317 @@ def test__set_dhcp_net_config(self, mock_read_config_file, 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 @@ def test__comment_keys_from_ifcfg_files_no_interfaces( ), ]) - @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' )