Просмотр исходного кода

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).
Lucian Petrut 3 дней назад
Родитель
Сommit
c0f578b2df
3 измененных файлов с 84 добавлено и 13 удалено
  1. 23 5
      coriolis/osmorphing/base.py
  2. 12 0
      coriolis/osmorphing/windows.py
  3. 49 8
      coriolis/tests/osmorphing/test_base.py

+ 23 - 5
coriolis/osmorphing/base.py

@@ -1008,6 +1008,9 @@ class BaseLinuxOSMorphingTools(BaseOSMorphingTools):
 
         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 @@ class BaseLinuxOSMorphingTools(BaseOSMorphingTools):
                 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)

+ 12 - 0
coriolis/osmorphing/windows.py

@@ -708,8 +708,20 @@ class BaseWindowsMorphingTools(base.BaseOSMorphingTools):
     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()}

+ 49 - 8
coriolis/tests/osmorphing/test_base.py

@@ -1894,6 +1894,38 @@ class BaseLinuxOSMorphingToolsTestBase(test_base.CoriolisBaseTestCase):
                 "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 @@ class BaseLinuxOSMorphingToolsTestBase(test_base.CoriolisBaseTestCase):
 
             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)