From 0a06fb58071e97a0b0b9dd3e3d70a9b6db9b7372 Mon Sep 17 00:00:00 2001 From: Calvin Mwadime Date: Wed, 17 Apr 2024 12:52:01 +0300 Subject: [PATCH] fix(azure): Reconfigure secondary NICs by removing route to DNS via them. As from Bionic, systemd has associated DNS server with secondary NICs, this causes delay in the DNS resolution from Jammy and forwards. We see it evidently in the cloud-init logs where resolving the url takes way more seconds. --- cloudinit/sources/DataSourceAzure.py | 39 ++++++++++++++-- .../datasources/test_azure.py | 45 +++++++++++++++++- tests/unittests/sources/test_azure.py | 46 +++++++++++++++++-- 3 files changed, 121 insertions(+), 9 deletions(-) diff --git a/cloudinit/sources/DataSourceAzure.py b/cloudinit/sources/DataSourceAzure.py index 259905db6fe..55c61b4f49e 100644 --- a/cloudinit/sources/DataSourceAzure.py +++ b/cloudinit/sources/DataSourceAzure.py @@ -1482,18 +1482,23 @@ def availability_zone(self): def _generate_network_config(self): """Generate network configuration according to configuration.""" # Use IMDS network metadata, if configured. + fxn = add_dhcp_config_for_primary_and_secondary_nics_to_network_config if ( self._metadata_imds and self._metadata_imds != sources.UNSET and self.ds_cfg.get("apply_network_config") ): try: - return generate_network_config_from_instance_network_metadata( - self._metadata_imds["network"], - apply_network_config_for_secondary_ips=self.ds_cfg.get( - "apply_network_config_for_secondary_ips" - ), + netcfg = ( + generate_network_config_from_instance_network_metadata( + self._metadata_imds["network"], + apply_network_config_for_secondary_ips=self.ds_cfg.get( + "apply_network_config_for_secondary_ips" + ), + ) ) + net_cfg = fxn(netcfg) + return net_cfg except Exception as e: LOG.error( "Failed generating network config " @@ -1945,6 +1950,30 @@ def load_azure_ds_dir(source_dir): return (md, ud, cfg, {"ovf-env.xml": contents}) +def add_dhcp_config_for_primary_and_secondary_nics_to_network_config( + netconfig: dict, +) -> dict: + """ + Configure the ephemeral and hotplug details. + parameters: + netconfig: dict, Network config details with other ethernet devices. + + returns the updated network configuration + """ + netconfig["ethernets"]["primary"] = { + "dhcp4": True, + "match": {"driver": "hv_netvsc", "name": "eth0"}, + } + netconfig["ethernets"]["secondary"] = { + "dhcp4": True, + "dhcp4-overrides": {"use-dns": False}, + "optional": True, + "match": {"driver": "hv_netvsc", "name": "!eth0"}, + } + + return netconfig + + @azure_ds_telemetry_reporter def generate_network_config_from_instance_network_metadata( network_metadata: dict, diff --git a/tests/integration_tests/datasources/test_azure.py b/tests/integration_tests/datasources/test_azure.py index 8e663ac21d6..6919015371c 100644 --- a/tests/integration_tests/datasources/test_azure.py +++ b/tests/integration_tests/datasources/test_azure.py @@ -1,11 +1,15 @@ +import datetime + import pytest +import yaml +from pycloudlib.azure.util import AzureCreateParams, AzureParams from pycloudlib.cloud import ImageType from tests.integration_tests.clouds import IntegrationCloud from tests.integration_tests.conftest import get_validated_source from tests.integration_tests.instances import IntegrationInstance from tests.integration_tests.integration_settings import PLATFORM -from tests.integration_tests.releases import CURRENT_RELEASE +from tests.integration_tests.releases import BIONIC, CURRENT_RELEASE def _check_for_eject_errors( @@ -45,3 +49,42 @@ def test_azure_eject(session_cloud: IntegrationCloud): session_cloud.cloud_instance.delete_image(snapshot_id) else: _check_for_eject_errors(instance) + + +@pytest.mark.skipif(PLATFORM != "azure", reason="Test is Azure specific") +@pytest.mark.skipif( + CURRENT_RELEASE < BIONIC, reason="Easier to test on Bionic+" +) +def test_azure_multi_nic_setup( + setup_image, session_cloud: IntegrationCloud +) -> None: + """Integration test for https://warthogs.atlassian.net/browse/CPC-3999. + + Azure should have the primary NIC only route to DNS. + Ensure other NICs do not have route to DNS. + """ + us = datetime.datetime.now().strftime("%f") + rg_params = AzureParams(f"ci-test-multi-nic-setup-{us}", None) + nic_one = AzureCreateParams(f"ci-nic1-test-{us}", rg_params.name, None) + nic_two = AzureCreateParams(f"ci-nic2-test-{us}", rg_params.name, None) + with session_cloud.launch( + launch_kwargs={ + "resource_group_params": rg_params, + "network_interfaces_params": [nic_one, nic_two], + } + ) as snapshot_instance: + _check_for_eject_errors(snapshot_instance) + if CURRENT_RELEASE.series == "bionic": + ret = snapshot_instance.execute("systemd-resolve --status") + assert ret.ok, ret.stderr + assert ret.stdout.count("Current Scopes: DNS") == 1 + else: + ret = snapshot_instance.execute("resolvectl dns") + assert ret.ok, ret.stderr + routes = yaml.safe_load(ret.stdout) + routes_devices = list(routes.keys()) + eth1_dev = [dev for dev in routes_devices if "(eth1)" in dev][0] + assert routes[eth1_dev] is None + # check the instance can resolve something + res = snapshot_instance.execute("resolvectl query google.com") + assert res.ok, res.stderr diff --git a/tests/unittests/sources/test_azure.py b/tests/unittests/sources/test_azure.py index a7954a26a3b..b49aa788092 100644 --- a/tests/unittests/sources/test_azure.py +++ b/tests/unittests/sources/test_azure.py @@ -976,7 +976,17 @@ def test_single_ipv4_nic_configuration( "dhcp6": False, "match": {"macaddress": "00:0d:3a:04:75:98"}, "set-name": "eth0", - } + }, + "primary": { + "dhcp4": True, + "match": {"driver": "hv_netvsc", "name": "eth0"}, + }, + "secondary": { + "dhcp4": True, + "dhcp4-overrides": {"use-dns": False}, + "optional": True, + "match": {"driver": "hv_netvsc", "name": "!eth0"}, + }, }, "version": 2, } @@ -1557,7 +1567,17 @@ def test_network_config_set_from_imds(self): "dhcp6": False, "dhcp4": True, "dhcp4-overrides": {"route-metric": 100}, - } + }, + "primary": { + "dhcp4": True, + "match": {"driver": "hv_netvsc", "name": "eth0"}, + }, + "secondary": { + "dhcp4": True, + "dhcp4-overrides": {"use-dns": False}, + "optional": True, + "match": {"driver": "hv_netvsc", "name": "!eth0"}, + }, }, "version": 2, } @@ -1595,6 +1615,16 @@ def test_network_config_set_from_imds_route_metric_for_secondary_nic(self): "dhcp4": True, "dhcp4-overrides": {"route-metric": 300}, }, + "primary": { + "dhcp4": True, + "match": {"driver": "hv_netvsc", "name": "eth0"}, + }, + "secondary": { + "dhcp4": True, + "dhcp4-overrides": {"use-dns": False}, + "optional": True, + "match": {"driver": "hv_netvsc", "name": "!eth0"}, + }, }, "version": 2, } @@ -1626,7 +1656,17 @@ def test_network_config_set_from_imds_for_secondary_nic_no_ip(self): "dhcp6": False, "dhcp4": True, "dhcp4-overrides": {"route-metric": 100}, - } + }, + "primary": { + "dhcp4": True, + "match": {"driver": "hv_netvsc", "name": "eth0"}, + }, + "secondary": { + "dhcp4": True, + "dhcp4-overrides": {"use-dns": False}, + "optional": True, + "match": {"driver": "hv_netvsc", "name": "!eth0"}, + }, }, "version": 2, }