diff options
author | Anh Vo <anhvo@microsoft.com> | 2022-11-18 14:31:27 -0500 |
---|---|---|
committer | James Falcon <james.falcon@canonical.com> | 2022-11-18 17:31:13 -0600 |
commit | 25f8d835c26ea4436aef1b741851c37835eba4ae (patch) | |
tree | 434071523de9cc1bb6b8ed371eb117ba08c172f5 | |
parent | 1cfca968b749577277cfa7d2848b1645e9ed8293 (diff) | |
download | cloud-init-git-25f8d835c26ea4436aef1b741851c37835eba4ae.tar.gz |
net: skip duplicate mac check for netvsc nic and its VF (#1853)
When accelerated network is enabled on Azure, the host presents
two network interfaces with the same mac address to the VM:
a synthetic nic (netvsc) and a VF nic, which is enslaved to the synthetic
nic.
The net module is already excluding slave nics when enumerating
interfaces. However, if cloud-init starts enumerating after the kernel
makes the VF visible to userspace, but before the enslaving has finished,
cloud-init will see two nics with duplicate mac.
This change will skip the duplicate mac error if one of the two nics
with duplicate mac is a netvsc nic
LP: #1844191
-rw-r--r-- | cloudinit/net/__init__.py | 38 | ||||
-rw-r--r-- | tests/unittests/test_net.py | 25 |
2 files changed, 59 insertions, 4 deletions
diff --git a/cloudinit/net/__init__.py b/cloudinit/net/__init__.py index 4bc48676..0a41a2d4 100644 --- a/cloudinit/net/__init__.py +++ b/cloudinit/net/__init__.py @@ -1000,15 +1000,45 @@ def get_interfaces_by_mac_on_linux(blacklist_drivers=None) -> dict: Bridges and any devices that have a 'stolen' mac are excluded.""" ret: dict = {} - for name, mac, _driver, _devid in get_interfaces( + driver_map: dict = {} + for name, mac, driver, _devid in get_interfaces( blacklist_drivers=blacklist_drivers ): if mac in ret: - raise RuntimeError( - "duplicate mac found! both '%s' and '%s' have mac '%s'" - % (name, ret[mac], mac) + raise_duplicate_mac_error = True + msg = "duplicate mac found! both '%s' and '%s' have mac '%s'." % ( + name, + ret[mac], + mac, ) + # Hyper-V netvsc driver will register a VF with the same mac + # + # The VF will be enslaved to the master nic shortly after + # registration. If cloud-init starts enumerating the interfaces + # before the completion of the enslaving process, it will see + # two different nics with duplicate mac. Cloud-init should ignore + # the slave nic (which does not have hv_netvsc driver). + if driver != driver_map[mac]: + if driver_map[mac] == "hv_netvsc": + LOG.warning( + msg + " Ignoring '%s' due to driver '%s' and " + "'%s' having driver hv_netvsc." + % (name, driver, ret[mac]) + ) + continue + if driver == "hv_netvsc": + raise_duplicate_mac_error = False + LOG.warning( + msg + " Ignoring '%s' due to driver '%s' and " + "'%s' having driver hv_netvsc." + % (ret[mac], driver_map[mac], name) + ) + + if raise_duplicate_mac_error: + raise RuntimeError(msg) + ret[mac] = name + driver_map[mac] = driver # Pretend that an Infiniband GUID is an ethernet address for Openstack # configuration purposes diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py index 525706d1..e61ef4b6 100644 --- a/tests/unittests/test_net.py +++ b/tests/unittests/test_net.py @@ -7683,6 +7683,10 @@ class TestGetInterfacesByMac(CiTestCase): "bridge1", "bond1.101", "lo", + "netvsc0-vf", + "netvsc0", + "netvsc1", + "netvsc1-vf", ], "macs": { "enp0s1": "aa:aa:aa:aa:aa:01", @@ -7693,14 +7697,27 @@ class TestGetInterfacesByMac(CiTestCase): "bridge1-nic": "aa:aa:aa:aa:aa:03", "lo": "00:00:00:00:00:00", "greptap0": "00:00:00:00:00:00", + "netvsc0-vf": "aa:aa:aa:aa:aa:04", + "netvsc0": "aa:aa:aa:aa:aa:04", + "netvsc1-vf": "aa:aa:aa:aa:aa:05", + "netvsc1": "aa:aa:aa:aa:aa:05", "tun0": None, }, + "drivers": { + "netvsc0": "hv_netvsc", + "netvsc0-vf": "foo", + "netvsc1": "hv_netvsc", + "netvsc1-vf": "bar", + }, } data: dict = {} def _se_get_devicelist(self): return list(self.data["devices"]) + def _se_device_driver(self, name): + return self.data["drivers"].get(name, None) + def _se_get_interface_mac(self, name): return self.data["macs"][name] @@ -7722,6 +7739,7 @@ class TestGetInterfacesByMac(CiTestCase): self.data["devices"] = set(list(self.data["macs"].keys())) mocks = ( "get_devicelist", + "device_driver", "get_interface_mac", "is_bridge", "interface_has_own_mac", @@ -7741,6 +7759,11 @@ class TestGetInterfacesByMac(CiTestCase): self.data["macs"]["bridge1-nic"] = self.data["macs"]["enp0s1"] self.assertRaises(RuntimeError, net.get_interfaces_by_mac) + def test_raise_exception_on_duplicate_netvsc_macs(self): + self._mock_setup() + self.data["macs"]["netvsc0"] = self.data["macs"]["netvsc1"] + self.assertRaises(RuntimeError, net.get_interfaces_by_mac) + def test_excludes_any_without_mac_address(self): self._mock_setup() ret = net.get_interfaces_by_mac() @@ -7759,6 +7782,8 @@ class TestGetInterfacesByMac(CiTestCase): "aa:aa:aa:aa:aa:02": "enp0s2", "aa:aa:aa:aa:aa:03": "bridge1-nic", "00:00:00:00:00:00": "lo", + "aa:aa:aa:aa:aa:04": "netvsc0", + "aa:aa:aa:aa:aa:05": "netvsc1", }, ret, ) |