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)