summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAnh Vo <anhvo@microsoft.com>2022-11-18 14:31:27 -0500
committerJames Falcon <james.falcon@canonical.com>2022-11-18 17:31:13 -0600
commit25f8d835c26ea4436aef1b741851c37835eba4ae (patch)
tree434071523de9cc1bb6b8ed371eb117ba08c172f5
parent1cfca968b749577277cfa7d2848b1645e9ed8293 (diff)
downloadcloud-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__.py38
-rw-r--r--tests/unittests/test_net.py25
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,
)