cloud-init/backport-net-skip-duplicate-mac-check-for-netvsc-nic-and-its-.patch
2023-08-26 14:46:22 +08:00

162 lines
6.7 KiB
Diff

From 24bf6147712655fc36a5d714a081853ea37e0312 Mon Sep 17 00:00:00 2001
From: Anh Vo <anhvo@microsoft.com>
Date: Fri, 18 Nov 2022 14:31:27 -0500
Subject: [PATCH] net: skip duplicate mac check for netvsc nic and its VF
(#1853)
Reference:https://github.com/canonical/cloud-init/commit/24bf6147712655fc36a5d714a081853ea37e0312
Conflict:format diff.
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
---
cloudinit/net/__init__.py | 39 +++++++++++++++++++++++++++++++++----
tests/unittests/test_net.py | 32 ++++++++++++++++++++++++++----
2 files changed, 63 insertions(+), 8 deletions(-)
diff --git a/cloudinit/net/__init__.py b/cloudinit/net/__init__.py
index fba133e..96ce6f5 100644
--- a/cloudinit/net/__init__.py
+++ b/cloudinit/net/__init__.py
@@ -896,13 +896,44 @@ def get_interfaces_by_mac_on_linux(blacklist_drivers=None) -> dict:
Bridges and any devices that have a 'stolen' mac are excluded."""
ret = {}
- 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 ce19498..0db4442 100644
--- a/tests/unittests/test_net.py
+++ b/tests/unittests/test_net.py
@@ -5519,7 +5519,8 @@ class TestGetInterfacesByMac(CiTestCase):
'bridges': ['bridge1'],
'vlans': ['bond1.101'],
'own_macs': ['enp0s1', 'enp0s2', 'bridge1-nic', 'bridge1',
- 'bond1.101', 'lo'],
+ 'bond1.101', 'lo',
+ "netvsc0-vf", "netvsc0", "netvsc1","netvsc1-vf"],
'macs': {'enp0s1': 'aa:aa:aa:aa:aa:01',
'enp0s2': 'aa:aa:aa:aa:aa:02',
'bond1': 'aa:aa:aa:aa:aa:01',
@@ -5528,12 +5529,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',
- 'tun0': None}}
+ "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 = {}
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]
@@ -5553,7 +5569,7 @@ class TestGetInterfacesByMac(CiTestCase):
def _mock_setup(self):
self.data = copy.deepcopy(self._data)
self.data['devices'] = set(list(self.data['macs'].keys()))
- mocks = ('get_devicelist', 'get_interface_mac', 'is_bridge',
+ mocks = ('get_devicelist', "device_driver", 'get_interface_mac', 'is_bridge',
'interface_has_own_mac', 'is_vlan', 'get_ib_interface_hwaddr')
self.mocks = {}
for n in mocks:
@@ -5567,6 +5583,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()
@@ -5580,7 +5601,10 @@ class TestGetInterfacesByMac(CiTestCase):
[mock.call('enp0s1'), mock.call('bond1')], any_order=True)
self.assertEqual(
{'aa:aa:aa:aa:aa:01': 'enp0s1', '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: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)
def test_excludes_bridges(self):
--
2.33.0