From 6e23c4e3b9feaf4edf7552e7c817e06b6da0bc68 Mon Sep 17 00:00:00 2001 From: Chad Smith Date: Wed, 10 Jan 2024 23:11:38 -0700 Subject: [PATCH] tests: integration test for network-config schema on netplan systems On Noble, python netplan bindings are available in python3-netplan deb package. The presence of they python bindings and netplan.Parser import allow cloud-int for parse and validate network-config version 2 schema. Add an integration tests asserting both schema validation and annotation of invalid network configuration on Ubuntu Noble. On non-Noble environments, expect Skipping of network-config version: 2 message. --- tests/integration_tests/cmd/test_schema.py | 102 +++++++++++++++++++++ tests/integration_tests/test_networking.py | 52 +++++++++++ 2 files changed, 154 insertions(+) diff --git a/tests/integration_tests/cmd/test_schema.py b/tests/integration_tests/cmd/test_schema.py index c239901e96b4..a9cd07a54a78 100644 --- a/tests/integration_tests/cmd/test_schema.py +++ b/tests/integration_tests/cmd/test_schema.py @@ -4,6 +4,7 @@ import pytest from tests.integration_tests.instances import IntegrationInstance +from tests.integration_tests.releases import CURRENT_RELEASE from tests.integration_tests.util import verify_clean_log USER_DATA = """\ @@ -13,6 +14,48 @@ apt_reboot_if_required: false """ +NET_CFG_V1 = """\ +network: + version: 1 + config: + - type: physical + name: eth0 + subnets: + - type: dhcp +""" +NET_CFG_V1_INVALID = NET_CFG_V1.replace("config", "junk") +NET_V1_ANNOTATED = """\ +network: # E1,E2 + version: 1 + junk: + - type: physical + name: eth0 + subnets: + - type: dhcp + +# Errors: ------------- +# E1: 'config' is a required property +# E2: Additional properties are not allowed ('junk' was unexpected)""" + +NET_CFG_V2 = """\ +version: 2 +ethernets: + eth0: + dhcp4: true +""" +NET_CFG_V2_INVALID = NET_CFG_V2.replace("true", "bogus") +NET_V2_ANNOTATED = """\ +--- +network: + ethernets: + eth0: + dhcp4: bogus # E1 + version: 2 +... + +# Errors: ------------- +# E1: Invalid netplan schema. Error in network definition: invalid boolean value 'bogus'""" # noqa: E501 + @pytest.mark.user_data(USER_DATA) class TestSchemaDeprecations: @@ -24,6 +67,65 @@ def test_clean_log(self, class_client: IntegrationInstance): assert "apt_update: Default: ``false``. Deprecated in version" in log assert "apt_upgrade: Default: ``false``. Deprecated in version" in log + def test_network_config_schema_validation( + self, class_client: IntegrationInstance + ): + content_responses = { + NET_CFG_V1: {"out": "Valid schema /root/net.yaml"}, + NET_CFG_V1_INVALID: { + "out": "Invalid network-config /root/net.yaml", + "err": ( + "network: Additional properties are not allowed" + " ('junk' was unexpected)" + ), + "annotate": NET_V1_ANNOTATED, + }, + } + if CURRENT_RELEASE.series == "noble": + # Support for python3-netplan API available + content_responses[NET_CFG_V2] = { + "out": "Valid schema /root/net.yaml" + } + content_responses[NET_CFG_V2_INVALID] = { + "out": "Invalid network-config /root/net.yaml", + "err": ( + "Cloud config schema errors: format-l5.c20:" + " Invalid netplan schema. Error in network definition:" + " invalid boolean value 'bogus'" + ), + "annotate": NET_V2_ANNOTATED, + } + else: + # No python3-netplan API available skips validation + content_responses[NET_CFG_V2] = { + "out": ( + "Skipping network-config schema validation." + " No network schema for version: 2" + ) + } + content_responses[NET_CFG_V2_INVALID] = { + "out": ( + "Skipping network-config schema validation." + " No network schema for version: 2" + ) + } + + for content, responses in content_responses.items(): + class_client.write_to_file("/root/net.yaml", content) + result = class_client.execute( + "cloud-init schema --schema-type network-config" + " --config-file /root/net.yaml" + ) + assert responses["out"] == result.stdout + if responses.get("err"): + assert responses["err"] in result.stderr + if responses.get("annotate"): + result = class_client.execute( + "cloud-init schema --schema-type network-config" + " --config-file /root/net.yaml --annotate" + ) + assert responses["annotate"] in result.stdout + def test_schema_deprecations(self, class_client: IntegrationInstance): """Test schema behavior with deprecated configs.""" user_data_fn = "/root/user-data" diff --git a/tests/integration_tests/test_networking.py b/tests/integration_tests/test_networking.py index cde69afcd179..8576f55751ec 100644 --- a/tests/integration_tests/test_networking.py +++ b/tests/integration_tests/test_networking.py @@ -2,6 +2,8 @@ import pytest import yaml +from tests.integration_tests import random_mac_address +from tests.integration_tests.clouds import IntegrationCloud from tests.integration_tests.instances import IntegrationInstance from tests.integration_tests.integration_settings import PLATFORM @@ -91,3 +93,53 @@ def test_applied(self, client: IntegrationInstance): client.execute("cat /etc/netplan/50-cloud-init.yaml") ) assert netplan != netplan_new, "changes expected in netplan config" + + +BAD_NETWORK_V2 = """\ +version: 2 +ethernets: + eth0: + dhcp4: badval + match: + {match_condition} +""" + + +@pytest.mark.skipif( + PLATFORM not in ["lxd_container", "lxd_vm"], + reason="Test requires lxc exec feature due to broken network config", +) +def test_invalid_network_v2_netplan(session_cloud: IntegrationCloud): + mac_addr = random_mac_address() + if PLATFORM == "lxd_vm": + config_dict = { + "cloud-init.network-config": BAD_NETWORK_V2.format( + match_condition=f"macaddress: {mac_addr}" + ), + "volatile.eth0.hwaddr": mac_addr, + } + else: + config_dict = { + "cloud-init.network-config": BAD_NETWORK_V2.format( + match_condition="name: eth0" + ) + } + with session_cloud.launch( + launch_kwargs={ + "execute_via_ssh": False, + "config_dict": config_dict, + } + ) as client: + status_json = client.execute("cloud-init status --format=json") + assert ( + "Invalid network-config provided: Please run " + "'sudo cloud-init schema --system' to see the schema errors." + ) in status_json + schema_out = client.execute("cloud-init schema --system") + assert "Invalid network-config /var/lib/cloud/instances/" in schema_out + annotate_out = client.execute("cloud-init schema --system --annotate") + assert ( + "# E1: Invalid netplan schema. Error in network definition:" + " invalid boolean value 'badval" + in annotate_out + )