From 4962d7db1404de1f0725a1be3beb33b45d127846 Mon Sep 17 00:00:00 2001 From: Lucian Petrut Date: Sat, 22 Aug 2026 11:42:03 +0000 Subject: [PATCH] os-morphing: fix static ip configuration if the mac changes When "use_dhcp" is disabled, Coriolis tries to apply static IP configuration on Windows, while on Linux it defines udev rules for preserving the interface names, identifying interfaces by MAC address. However, it expects the MAC address of the source instance to be preserved, which isn't always the case. This change updates the Linux and Windows OS morphing code to use the new MAC address, if provided. The import provider is expected to set "new_mac_address" with the nic info if the original MAC hasn't been preserved. Note that Coriolis cannot modify network configuration files at the moment. An exception will be raised if the original MAC address is explicitly set by the network configuration (e.g. netplan). --- coriolis/osmorphing/base.py | 28 ++++++++++--- coriolis/osmorphing/windows.py | 12 ++++++ coriolis/tests/osmorphing/test_base.py | 57 ++++++++++++++++++++++---- 3 files changed, 84 insertions(+), 13 deletions(-) diff --git a/coriolis/osmorphing/base.py b/coriolis/osmorphing/base.py index efc31876..f0c99c96 100644 --- a/coriolis/osmorphing/base.py +++ b/coriolis/osmorphing/base.py @@ -1008,6 +1008,9 @@ def _setup_network_preservation(self, nics_info) -> None: for nic in nics_info: nic_mac = nic.get('mac_address') + new_nic_mac = nic.get('new_mac_address') or nic_mac + if new_nic_mac != nic_mac: + LOG.info("MAC address changed: %s -> %s", nic_mac, new_nic_mac) nic_ips = nic.get('ip_addresses') if not nic_mac: LOG.warning( @@ -1020,22 +1023,37 @@ def _setup_network_preservation(self, nics_info) -> None: mac_address = info.get('mac_address') ip_addresses = info.get('ip_addresses', []) if mac_address and mac_address == nic_mac: + if new_nic_mac != nic_mac: + raise exception.CoriolisException( + "The NIC '%s' configuration explicitly targets " + "MAC '%s', however its MAC changed to '%s'. " + "Coriolis defines udev rules for preserving the " + "interface name, mapping it to the new MAC " + "address but DOES NOT modify Linux network " + "configuration files. Depending on the target " + "platform capabilities, consider using DHCP, " + "enabling MAC address preservation or removing " + "the explicit MAC filter from the NIC " + "configuration." + % (nic.get('name'), nic_mac, new_nic_mac)) LOG.info( - "Found matching interface for NIC '%s' with MAC '%s'", + "Found matching interface for NIC '%s' with MAC '%s'.", nic.get('name'), nic_mac) matching_ifaces[iface] = nic_mac break if ip_addresses and nic_ips: if set(ip_addresses) & set(nic_ips): LOG.info( - "Found matching interface for NIC '%s' with MAC " - "'%s'", nic.get('name'), nic_mac) - matching_ifaces[iface] = nic_mac + "Found matching interface for NIC '%s' " + "with new MAC '%s', identified by IP.", + nic.get('name'), new_nic_mac) + matching_ifaces[iface] = new_nic_mac break if not matching_ifaces: LOG.warning( "Could not find a matching guest interface for NIC '%s' " - "with MAC address '%s'", nic, nic_mac) + "with MAC address '%s' (old MAC address: %s)", + nic, new_nic_mac, nic_mac) net_ifaces_info.update(matching_ifaces) self._add_net_udev_rules(net_ifaces_info) diff --git a/coriolis/osmorphing/windows.py b/coriolis/osmorphing/windows.py index 653afad3..d219f179 100644 --- a/coriolis/osmorphing/windows.py +++ b/coriolis/osmorphing/windows.py @@ -708,8 +708,20 @@ def _get_static_nics_info(self, nics_info, ips_info): def _write_static_ip_script(self, base_dir, nics_info, ips_info): scripts_dir = self._get_cbslinit_scripts_dir(base_dir) script_path = "%s\\01-static-ip-config.ps1" % scripts_dir + + nics_info = copy.deepcopy(nics_info) + for nic in nics_info: + # The static ip configuration script only requires the new + # MAC address. + if nic.get("new_mac_address"): + LOG.info( + "Updated mac address: %s -> %s", + nic["mac_address"], nic["new_mac_address"]) + nic["mac_address"] = nic["new_mac_address"] + nics_info_dump = json.dumps(nics_info) ips_info_dump = json.dumps(ips_info) + contents = STATIC_IP_SCRIPT_TEMPLATE % { 'nics_info': base64.b64encode(nics_info_dump.encode()).decode(), 'ips_info': base64.b64encode(ips_info_dump.encode()).decode()} diff --git a/coriolis/tests/osmorphing/test_base.py b/coriolis/tests/osmorphing/test_base.py index 5edff3d6..b5ec36bc 100644 --- a/coriolis/tests/osmorphing/test_base.py +++ b/coriolis/tests/osmorphing/test_base.py @@ -1894,6 +1894,38 @@ def test__add_net_udev_rules(self, mock_write_file_sudo, mock_test_path): "eth2": "FF:FF:FF:FF:FF:FF", } ), + # MAC changed but the network config contains an explicit MAC + # assignment. An exception is expected since Coriolis cannot + # modify Linux network config files at the moment. + ( + [ + {"mac_address": "00:11:22:33:44:55", + "new_mac_address": "ff:aa:11:22:33:44", + "ip_addresses": ["192.168.1.10"]}, + ], + { + "eth0": {"mac_address": "00:11:22:33:44:55", + "ip_addresses": []}, + }, + exception.CoriolisException, + ), + # The MAC address changed but the network config files did not + # contain explicit MAC assignments, we can rely on the udev rules + # that preserve the interface names. The new MAC will be returned. + ( + [ + {"mac_address": "00:11:22:33:44:55", + "new_mac_address": "ff:aa:11:22:33:44", + "ip_addresses": ["192.168.1.10"]}, + ], + { + "eth0": {"mac_address": None, + "ip_addresses": ["192.168.1.10"]}, + }, + { + "eth0": "ff:aa:11:22:33:44", + } + ), ) @ddt.unpack def test__setup_network_preservation( @@ -1912,13 +1944,22 @@ def parse_network(self): self.os_morphing_tools._add_net_udev_rules = mock.MagicMock() - with self.assertLogs( - 'coriolis.osmorphing.base', level=logging.INFO): - self.os_morphing_tools._setup_network_preservation(nics_info) + if isinstance(expected_net_ifaces, type) and issubclass( + expected_net_ifaces, Exception): + self.assertRaises( + expected_net_ifaces, + self.os_morphing_tools._setup_network_preservation, + nics_info, + ) + else: + with self.assertLogs( + 'coriolis.osmorphing.base', level=logging.INFO): + self.os_morphing_tools._setup_network_preservation( + nics_info) - result_net_ifaces = dict( - self.os_morphing_tools._add_net_udev_rules.call_args[0][0]) + result_net_ifaces = dict( + self.os_morphing_tools._add_net_udev_rules.call_args[0][0]) - mock_get_np.assert_called_once_with(self.os_morphing_tools) - self.os_morphing_tools._add_net_udev_rules.assert_called_once() - self.assertEqual(expected_net_ifaces, result_net_ifaces) + mock_get_np.assert_called_once_with(self.os_morphing_tools) + self.os_morphing_tools._add_net_udev_rules.assert_called_once() + self.assertEqual(expected_net_ifaces, result_net_ifaces)