diff --git a/cloudbaseinit/plugins/common/networkconfig.py b/cloudbaseinit/plugins/common/networkconfig.py index 768212aa..b1dddecf 100644 --- a/cloudbaseinit/plugins/common/networkconfig.py +++ b/cloudbaseinit/plugins/common/networkconfig.py @@ -13,6 +13,7 @@ # under the License. import re +import time import netaddr from oslo_log import log as oslo_logging @@ -198,11 +199,27 @@ def _process_physical_links(osutils, network_details): link.mac_address) if adapter_name != link.name: - LOG.info( - "Renaming network adapter \"%(old_name)s\" to " - "\"%(new_name)s\"", - {"old_name": adapter_name, "new_name": link.name}) - osutils.rename_network_adapter(adapter_name, link.name) + if link.id != link.name: + # set-name was explicitly provided, honor the rename + LOG.info( + "Renaming network adapter \"%(old_name)s\" to " + "\"%(new_name)s\"", + {"old_name": adapter_name, "new_name": link.name}) + osutils.rename_network_adapter(adapter_name, link.name) + else: + # No set-name, use the actual adapter name to avoid + # unreliable WMI rename (see issues #82, #101, #151) + LOG.info( + "Network adapter \"%(adapter_name)s\" found by " + "MAC for config name \"%(config_name)s\", " + "skipping rename", + {"adapter_name": adapter_name, + "config_name": link.name}) + for idx, net in enumerate(network_details.networks): + if net.link == link.name: + network_details.networks[idx] = net._replace( + link=adapter_name) + link = link._replace(name=adapter_name) NetworkConfigPlugin._process_link_common(osutils, link) @@ -259,7 +276,16 @@ def _process_networks(osutils, network_details): ipv4_ns, ipv6_ns = NetworkConfigPlugin._get_default_dns_nameservers( network_details) + # Build name -> MAC map for re-lookup on adapter disappearance + # (handles vDPA/SR-IOV reactivation during boot) + mac_by_name = {} + for link in network_details.links: + if link.mac_address and \ + link.type == network_model.LINK_TYPE_PHYSICAL: + mac_by_name[link.name] = link.mac_address + for net in network_details.networks: + adapter_name = net.link ip_address, prefix_len = net.address_cidr.split("/") gateway = None @@ -276,14 +302,45 @@ def _process_networks(osutils, network_details): else: nameservers = ipv4_ns + # Re-lookup adapter by MAC if available, with retry for + # transient disappearance (e.g. vDPA/SR-IOV reactivation) + if adapter_name in mac_by_name: + mac = mac_by_name[adapter_name] + for attempt in range(10): + try: + current_name = \ + osutils.get_network_adapter_name_by_mac_address( + mac) + if current_name != adapter_name: + LOG.info( + "Adapter name changed from " + "\"%(old)s\" to \"%(new)s\"", + {"old": adapter_name, "new": current_name}) + adapter_name = current_name + break + except exception.ItemNotFoundException: + if attempt < 9: + LOG.warning( + "Adapter with MAC %(mac)s not found " + "(attempt %(n)d/10), retrying in 3s", + {"mac": mac, "n": attempt + 1}) + time.sleep(3) + else: + LOG.error( + "Adapter with MAC %(mac)s not found " + "after 10 attempts", + {"mac": mac}) + raise + LOG.info( "Setting static IP configuration on network adapter " "\"%(name)s\". IP: %(ip)s, prefix length: %(prefix_len)s, " "gateway: %(gateway)s, dns: %(dns)s", - {"name": net.link, "ip": ip_address, "prefix_len": prefix_len, + {"name": adapter_name, "ip": ip_address, + "prefix_len": prefix_len, "gateway": gateway, "dns": nameservers}) reboot = osutils.set_static_network_config( - net.link, ip_address, prefix_len, gateway, nameservers) + adapter_name, ip_address, prefix_len, gateway, nameservers) reboot_required = reboot or reboot_required return reboot_required diff --git a/cloudbaseinit/tests/plugins/common/test_networkconfig.py b/cloudbaseinit/tests/plugins/common/test_networkconfig.py index ceebe8ce..8636742b 100644 --- a/cloudbaseinit/tests/plugins/common/test_networkconfig.py +++ b/cloudbaseinit/tests/plugins/common/test_networkconfig.py @@ -473,3 +473,96 @@ def test_execute_network_details_v2_ipv4_dns_list(self): def test_execute_network_details_v2_ipv6_dns_list(self): self._test_execute_network_details_v2(both_ipv6_dns_list=True) + + @mock.patch("cloudbaseinit.osutils.factory.get_os_utils") + def test_execute_network_details_v2_no_rename_without_set_name( + self, mock_get_os_utils): + """When link.id == link.name (no set-name), skip adapter rename.""" + link1 = network_model.Link( + id="eth0", + name="eth0", + type=network_model.LINK_TYPE_PHYSICAL, + enabled=True, + mac_address=u"00:00:00:00:00:01", + mtu=1500, + bond=None, + vlan_link=None, + vlan_id=None) + + route1 = network_model.Route( + network_cidr=u"0.0.0.0/0", + gateway=u"10.0.0.254") + + network1 = network_model.Network( + link="eth0", + address_cidr=u"10.0.0.1/24", + dns_nameservers=["10.0.0.1"], + routes=[route1]) + + network_details = network_model.NetworkDetailsV2( + links=[link1], + networks=[network1], + services=[]) + + service = mock.Mock() + service.get_network_details_v2.return_value = network_details + + mock_os_utils = mock.Mock() + mock_get_os_utils.return_value = mock_os_utils + mock_os_utils.get_network_adapter_name_by_mac_address.return_value = \ + "Ethernet 3" + + plugin = networkconfig.NetworkConfigPlugin() + plugin.execute(service, {}) + + # Should NOT rename when link.id == link.name (no set-name) + mock_os_utils.rename_network_adapter.assert_not_called() + + # Should use actual adapter name for all operations + mock_os_utils.enable_network_adapter.assert_called_once_with( + "Ethernet 3", True) + mock_os_utils.set_network_adapter_mtu.assert_called_once_with( + "Ethernet 3", 1500) + mock_os_utils.set_static_network_config.assert_called_once_with( + "Ethernet 3", "10.0.0.1", "24", "10.0.0.254", ["10.0.0.1"]) + + @mock.patch("cloudbaseinit.osutils.factory.get_os_utils") + def test_execute_network_details_v2_rename_with_set_name( + self, mock_get_os_utils): + """When link.id != link.name (set-name used), rename adapter.""" + link1 = network_model.Link( + id="eth0", + name="my-nic", + type=network_model.LINK_TYPE_PHYSICAL, + enabled=True, + mac_address=u"00:00:00:00:00:01", + mtu=None, + bond=None, + vlan_link=None, + vlan_id=None) + + network1 = network_model.Network( + link="my-nic", + address_cidr=u"10.0.0.1/24", + dns_nameservers=["10.0.0.1"], + routes=[]) + + network_details = network_model.NetworkDetailsV2( + links=[link1], + networks=[network1], + services=[]) + + service = mock.Mock() + service.get_network_details_v2.return_value = network_details + + mock_os_utils = mock.Mock() + mock_get_os_utils.return_value = mock_os_utils + mock_os_utils.get_network_adapter_name_by_mac_address.return_value = \ + "Ethernet" + + plugin = networkconfig.NetworkConfigPlugin() + plugin.execute(service, {}) + + # Should rename when link.id != link.name (set-name was used) + mock_os_utils.rename_network_adapter.assert_called_once_with( + "Ethernet", "my-nic")