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, }