diff options
author | zsdc <taras@vyos.io> | 2022-03-25 20:58:01 +0200 |
---|---|---|
committer | zsdc <taras@vyos.io> | 2022-03-25 21:42:00 +0200 |
commit | 31448cccedd8f841fb3ac7d0f2e3cdefe08a53ba (patch) | |
tree | 349631a02467dae0158f6f663cc8aa8537974a97 /tests/integration_tests/bugs | |
parent | 5c4b3943343a85fbe517e5ec1fc670b3a8566b4b (diff) | |
parent | 8537237d80a48c8f0cbf8e66aa4826bbc882b022 (diff) | |
download | vyos-cloud-init-31448cccedd8f841fb3ac7d0f2e3cdefe08a53ba.tar.gz vyos-cloud-init-31448cccedd8f841fb3ac7d0f2e3cdefe08a53ba.zip |
T2117: Cloud-init updated to 22.1
Merged with 22.1 tag from the upstream Cloud-init repository.
Our modules were slightly modified for compatibility with the new
version.
Diffstat (limited to 'tests/integration_tests/bugs')
-rw-r--r-- | tests/integration_tests/bugs/test_gh570.py | 39 | ||||
-rw-r--r-- | tests/integration_tests/bugs/test_gh626.py | 43 | ||||
-rw-r--r-- | tests/integration_tests/bugs/test_gh632.py | 33 | ||||
-rw-r--r-- | tests/integration_tests/bugs/test_gh668.py | 46 | ||||
-rw-r--r-- | tests/integration_tests/bugs/test_gh671.py | 53 | ||||
-rw-r--r-- | tests/integration_tests/bugs/test_gh868.py | 27 | ||||
-rw-r--r-- | tests/integration_tests/bugs/test_lp1813396.py | 31 | ||||
-rw-r--r-- | tests/integration_tests/bugs/test_lp1835584.py | 101 | ||||
-rw-r--r-- | tests/integration_tests/bugs/test_lp1886531.py | 4 | ||||
-rw-r--r-- | tests/integration_tests/bugs/test_lp1897099.py | 14 | ||||
-rw-r--r-- | tests/integration_tests/bugs/test_lp1898997.py | 77 | ||||
-rw-r--r-- | tests/integration_tests/bugs/test_lp1900837.py | 5 | ||||
-rw-r--r-- | tests/integration_tests/bugs/test_lp1901011.py | 67 | ||||
-rw-r--r-- | tests/integration_tests/bugs/test_lp1910835.py | 64 | ||||
-rw-r--r-- | tests/integration_tests/bugs/test_lp1912844.py | 105 |
15 files changed, 696 insertions, 13 deletions
diff --git a/tests/integration_tests/bugs/test_gh570.py b/tests/integration_tests/bugs/test_gh570.py new file mode 100644 index 00000000..e98ab5d0 --- /dev/null +++ b/tests/integration_tests/bugs/test_gh570.py @@ -0,0 +1,39 @@ +"""Integration test for #570. + +Test that we can add optional vendor-data to the seedfrom file in a +NoCloud environment +""" + +import pytest + +from tests.integration_tests.instances import IntegrationInstance + +VENDOR_DATA = """\ +#cloud-config +runcmd: + - touch /var/tmp/seeded_vendordata_test_file +""" + + +# Only running on LXD because we need NoCloud for this test +@pytest.mark.lxd_container +@pytest.mark.lxd_vm +def test_nocloud_seedfrom_vendordata(client: IntegrationInstance): + seed_dir = "/var/tmp/test_seed_dir" + result = client.execute( + "mkdir {seed_dir} && " + "touch {seed_dir}/user-data && " + "touch {seed_dir}/meta-data && " + "echo 'seedfrom: {seed_dir}/' > " + "/var/lib/cloud/seed/nocloud-net/meta-data".format(seed_dir=seed_dir) + ) + assert result.return_code == 0 + + client.write_to_file( + "{}/vendor-data".format(seed_dir), + VENDOR_DATA, + ) + client.execute("cloud-init clean --logs") + client.restart() + assert client.execute("cloud-init status").ok + assert "seeded_vendordata_test_file" in client.execute("ls /var/tmp") diff --git a/tests/integration_tests/bugs/test_gh626.py b/tests/integration_tests/bugs/test_gh626.py new file mode 100644 index 00000000..b80b677a --- /dev/null +++ b/tests/integration_tests/bugs/test_gh626.py @@ -0,0 +1,43 @@ +"""Integration test for gh-626. + +Ensure if wakeonlan is specified in the network config that it is rendered +in the /etc/network/interfaces or netplan config. +""" + +import pytest +import yaml + +from tests.integration_tests import random_mac_address +from tests.integration_tests.instances import IntegrationInstance + +MAC_ADDRESS = random_mac_address() +NETWORK_CONFIG = """\ +version: 2 +ethernets: + eth0: + dhcp4: true + wakeonlan: true + match: + macaddress: {} +""".format( + MAC_ADDRESS +) + +EXPECTED_ENI_END = """\ +iface eth0 inet dhcp + ethernet-wol g""" + + +@pytest.mark.lxd_container +@pytest.mark.lxd_vm +@pytest.mark.lxd_config_dict( + { + "user.network-config": NETWORK_CONFIG, + "volatile.eth0.hwaddr": MAC_ADDRESS, + } +) +def test_wakeonlan(client: IntegrationInstance): + netplan_cfg = client.execute("cat /etc/netplan/50-cloud-init.yaml") + netplan_yaml = yaml.safe_load(netplan_cfg) + assert "wakeonlan" in netplan_yaml["network"]["ethernets"]["eth0"] + assert netplan_yaml["network"]["ethernets"]["eth0"]["wakeonlan"] is True diff --git a/tests/integration_tests/bugs/test_gh632.py b/tests/integration_tests/bugs/test_gh632.py new file mode 100644 index 00000000..c7a897c6 --- /dev/null +++ b/tests/integration_tests/bugs/test_gh632.py @@ -0,0 +1,33 @@ +"""Integration test for gh-632. + +Verify that if cloud-init is using DataSourceRbxCloud, there is +no traceback if the metadata disk cannot be found. +""" +import pytest + +from tests.integration_tests.instances import IntegrationInstance +from tests.integration_tests.util import verify_clean_log + + +# With some datasource hacking, we can run this on a NoCloud instance +@pytest.mark.lxd_container +@pytest.mark.lxd_vm +def test_datasource_rbx_no_stacktrace(client: IntegrationInstance): + client.write_to_file( + "/etc/cloud/cloud.cfg.d/90_dpkg.cfg", + "datasource_list: [ RbxCloud, NoCloud ]\n", + ) + client.write_to_file( + "/etc/cloud/ds-identify.cfg", + "policy: enabled\n", + ) + client.execute("cloud-init clean --logs") + client.restart() + + log = client.read_from_file("/var/log/cloud-init.log") + verify_clean_log(log) + assert "Failed to load metadata and userdata" not in log + assert ( + "Getting data from <class 'cloudinit.sources.DataSourceRbxCloud." + "DataSourceRbxCloud'> failed" not in log + ) diff --git a/tests/integration_tests/bugs/test_gh668.py b/tests/integration_tests/bugs/test_gh668.py new file mode 100644 index 00000000..95edb48d --- /dev/null +++ b/tests/integration_tests/bugs/test_gh668.py @@ -0,0 +1,46 @@ +"""Integration test for gh-668. + +Ensure that static route to host is working correctly. +The original problem is specific to the ENI renderer but that test is suitable +for all network configuration outputs. +""" + +import pytest + +from tests.integration_tests import random_mac_address +from tests.integration_tests.instances import IntegrationInstance + +DESTINATION_IP = "172.16.0.10" +GATEWAY_IP = "10.0.0.100" +MAC_ADDRESS = random_mac_address() + +NETWORK_CONFIG = """\ +version: 2 +ethernets: + eth0: + addresses: [10.0.0.10/8] + dhcp4: false + routes: + - to: {}/32 + via: {} + match: + macaddress: {} +""".format( + DESTINATION_IP, GATEWAY_IP, MAC_ADDRESS +) + +EXPECTED_ROUTE = "{} via {}".format(DESTINATION_IP, GATEWAY_IP) + + +@pytest.mark.lxd_container +@pytest.mark.lxd_vm +@pytest.mark.lxd_config_dict( + { + "user.network-config": NETWORK_CONFIG, + "volatile.eth0.hwaddr": MAC_ADDRESS, + } +) +@pytest.mark.lxd_use_exec +def test_static_route_to_host(client: IntegrationInstance): + route = client.execute("ip route | grep {}".format(DESTINATION_IP)) + assert route.startswith(EXPECTED_ROUTE) diff --git a/tests/integration_tests/bugs/test_gh671.py b/tests/integration_tests/bugs/test_gh671.py new file mode 100644 index 00000000..2d7c8118 --- /dev/null +++ b/tests/integration_tests/bugs/test_gh671.py @@ -0,0 +1,53 @@ +"""Integration test for gh-671. + +Verify that on Azure that if a default user and password are specified +through the Azure API that a change in the default password overwrites +the old password +""" + +import crypt + +import pytest + +from tests.integration_tests.clouds import IntegrationCloud + +OLD_PASSWORD = "DoIM33tTheComplexityRequirements!??" +NEW_PASSWORD = "DoIM33tTheComplexityRequirementsNow!??" + + +def _check_password(instance, unhashed_password): + shadow_password = instance.execute("getent shadow ubuntu").split(":")[1] + salt = shadow_password.rsplit("$", 1)[0] + hashed_password = crypt.crypt(unhashed_password, salt) + assert shadow_password == hashed_password + + +@pytest.mark.azure +def test_update_default_password(setup_image, session_cloud: IntegrationCloud): + os_profile = { + "os_profile": { + "admin_password": "", + "linux_configuration": {"disable_password_authentication": False}, + } + } + os_profile["os_profile"]["admin_password"] = OLD_PASSWORD + instance1 = session_cloud.launch(launch_kwargs={"vm_params": os_profile}) + + _check_password(instance1, OLD_PASSWORD) + + snapshot_id = instance1.cloud.cloud_instance.snapshot( + instance1.instance, delete_provisioned_user=False + ) + + os_profile["os_profile"]["admin_password"] = NEW_PASSWORD + try: + with session_cloud.launch( + launch_kwargs={ + "image_id": snapshot_id, + "vm_params": os_profile, + } + ) as instance2: + _check_password(instance2, NEW_PASSWORD) + finally: + session_cloud.cloud_instance.delete_image(snapshot_id) + instance1.destroy() diff --git a/tests/integration_tests/bugs/test_gh868.py b/tests/integration_tests/bugs/test_gh868.py new file mode 100644 index 00000000..a62e8b36 --- /dev/null +++ b/tests/integration_tests/bugs/test_gh868.py @@ -0,0 +1,27 @@ +"""Ensure no Traceback when 'chef_license' is set""" +import pytest + +from tests.integration_tests.instances import IntegrationInstance +from tests.integration_tests.util import verify_clean_log + +USERDATA = """\ +#cloud-config +chef: + install_type: omnibus + chef_license: accept + server_url: https://chef.yourorg.invalid + validation_name: some-validator +""" + + +@pytest.mark.adhoc # Can't be regularly reaching out to chef install script +@pytest.mark.ec2 +@pytest.mark.gce +@pytest.mark.azure +@pytest.mark.oci +@pytest.mark.lxd_container +@pytest.mark.lxd_vm +@pytest.mark.user_data(USERDATA) +def test_chef_license(client: IntegrationInstance): + log = client.read_from_file("/var/log/cloud-init.log") + verify_clean_log(log) diff --git a/tests/integration_tests/bugs/test_lp1813396.py b/tests/integration_tests/bugs/test_lp1813396.py new file mode 100644 index 00000000..ddae02f5 --- /dev/null +++ b/tests/integration_tests/bugs/test_lp1813396.py @@ -0,0 +1,31 @@ +"""Integration test for lp-1813396 + +Ensure gpg is called with no tty flag. +""" + +import pytest + +from tests.integration_tests.instances import IntegrationInstance +from tests.integration_tests.util import verify_ordered_items_in_text + +USER_DATA = """\ +#cloud-config +apt: + sources: + cloudinit: + source: 'deb [arch=amd64] http://ppa.launchpad.net/cloud-init-dev/daily/ubuntu focal main' + keyserver: keyserver.ubuntu.com + keyid: E4D304DF +""" # noqa: E501 + + +@pytest.mark.user_data(USER_DATA) +def test_gpg_no_tty(client: IntegrationInstance): + log = client.read_from_file("/var/log/cloud-init.log") + to_verify = [ + "Running command ['gpg', '--no-tty', " + "'--keyserver=keyserver.ubuntu.com', '--recv-keys', 'E4D304DF'] " + "with allowed return codes [0] (shell=False, capture=True)", + "Imported key 'E4D304DF' from keyserver 'keyserver.ubuntu.com'", + ] + verify_ordered_items_in_text(to_verify, log) diff --git a/tests/integration_tests/bugs/test_lp1835584.py b/tests/integration_tests/bugs/test_lp1835584.py new file mode 100644 index 00000000..765d73ef --- /dev/null +++ b/tests/integration_tests/bugs/test_lp1835584.py @@ -0,0 +1,101 @@ +""" Integration test for LP #1835584 + +Upstream linux kernels prior to 4.15 provide DMI product_uuid in uppercase. +More recent kernels switched to lowercase for DMI product_uuid. Azure +datasource uses this product_uuid as the instance-id for cloud-init. + +The linux-azure-fips kernel installed in PRO FIPs images, that product UUID is +uppercase whereas the linux-azure cloud-optimized kernel reports the UUID as +lowercase. + +In cases where product_uuid changes case, ensure cloud-init doesn't +recreate ssh hostkeys across reboot (due to detecting an instance_id change). + +This currently only affects linux-azure-fips -> linux-azure on Bionic. +This test won't run on Xenial because both linux-azure-fips and linux-azure +report uppercase product_uuids. + +The test will launch a specific Bionic Ubuntu PRO FIPS image which has a +linux-azure-fips kernel known to report product_uuid as uppercase. Then upgrade +and reboot into linux-azure kernel which is known to report product_uuid as +lowercase. + +Across the reboot, assert that we didn't re-run config_ssh by virtue of +seeing only one semaphore creation log entry of type: + + Writing to /var/lib/cloud/instances/<UUID>/sem/config_ssh - + +https://bugs.launchpad.net/cloud-init/+bug/1835584 +""" +import re + +import pytest + +from tests.integration_tests.clouds import ImageSpecification, IntegrationCloud +from tests.integration_tests.conftest import get_validated_source +from tests.integration_tests.instances import IntegrationInstance + +IMG_AZURE_UBUNTU_PRO_FIPS_BIONIC = ( + "Canonical:0001-com-ubuntu-pro-bionic-fips:pro-fips-18_04:18.04.202010201" +) + + +def _check_iid_insensitive_across_kernel_upgrade( + instance: IntegrationInstance, +): + uuid = instance.read_from_file("/sys/class/dmi/id/product_uuid") + assert ( + uuid.isupper() + ), "Expected uppercase UUID on Ubuntu FIPS image {}".format(uuid) + orig_kernel = instance.execute("uname -r").strip() + assert "azure-fips" in orig_kernel + result = instance.execute("apt-get update") + # Install a 5.4+ kernel which provides lowercase product_uuid + result = instance.execute("apt-get install linux-azure --assume-yes") + if not result.ok: + pytest.fail("Unable to install linux-azure kernel: {}".format(result)) + # Remove ubuntu-azure-fips metapkg which mandates FIPS-flavour kernel + result = instance.execute("ua disable fips --assume-yes") + assert result.ok, "Unable to disable fips: {}".format(result) + instance.restart() + new_kernel = instance.execute("uname -r").strip() + assert orig_kernel != new_kernel + assert "azure-fips" not in new_kernel + assert "azure" in new_kernel + new_uuid = instance.read_from_file("/sys/class/dmi/id/product_uuid") + assert ( + uuid.lower() == new_uuid + ), "Expected UUID on linux-azure to be lowercase of FIPS: {}".format(uuid) + log = instance.read_from_file("/var/log/cloud-init.log") + RE_CONFIG_SSH_SEMAPHORE = r"Writing.*sem/config_ssh " + ssh_runs = len(re.findall(RE_CONFIG_SSH_SEMAPHORE, log)) + assert 1 == ssh_runs, "config_ssh ran too many times {}".format(ssh_runs) + + +@pytest.mark.azure +def test_azure_kernel_upgrade_case_insensitive_uuid( + session_cloud: IntegrationCloud, +): + cfg_image_spec = ImageSpecification.from_os_image() + if (cfg_image_spec.os, cfg_image_spec.release) != ("ubuntu", "bionic"): + pytest.skip( + "Test only supports ubuntu:bionic not {0.os}:{0.release}".format( + cfg_image_spec + ) + ) + source = get_validated_source(session_cloud) + if not source.installs_new_version(): + pytest.skip( + "Provide CLOUD_INIT_SOURCE to install expected working cloud-init" + ) + image_id = IMG_AZURE_UBUNTU_PRO_FIPS_BIONIC + with session_cloud.launch( + launch_kwargs={"image_id": image_id} + ) as instance: + # We can't use setup_image fixture here because we want to avoid + # taking a snapshot or cleaning the booted machine after cloud-init + # upgrade. + instance.install_new_cloud_init( + source, take_snapshot=False, clean=False + ) + _check_iid_insensitive_across_kernel_upgrade(instance) diff --git a/tests/integration_tests/bugs/test_lp1886531.py b/tests/integration_tests/bugs/test_lp1886531.py index 058ea8bb..d56ca320 100644 --- a/tests/integration_tests/bugs/test_lp1886531.py +++ b/tests/integration_tests/bugs/test_lp1886531.py @@ -11,6 +11,7 @@ https://bugs.launchpad.net/ubuntu/+source/cloud-init/+bug/1886531 """ import pytest +from tests.integration_tests.util import verify_clean_log USER_DATA = """\ #cloud-config @@ -20,8 +21,7 @@ bootcmd: class TestLp1886531: - @pytest.mark.user_data(USER_DATA) def test_lp1886531(self, client): log_content = client.read_from_file("/var/log/cloud-init.log") - assert "WARNING" not in log_content + verify_clean_log(log_content) diff --git a/tests/integration_tests/bugs/test_lp1897099.py b/tests/integration_tests/bugs/test_lp1897099.py index 27c8927f..1f5030ce 100644 --- a/tests/integration_tests/bugs/test_lp1897099.py +++ b/tests/integration_tests/bugs/test_lp1897099.py @@ -7,7 +7,6 @@ https://bugs.launchpad.net/cloud-init/+bug/1897099 import pytest - USER_DATA = """\ #cloud-config bootcmd: @@ -19,13 +18,12 @@ swap: """ -@pytest.mark.sru_2020_11 @pytest.mark.user_data(USER_DATA) -@pytest.mark.no_container('Containers cannot configure swap') +@pytest.mark.no_container("Containers cannot configure swap") def test_fallocate_fallback(client): - log = client.read_from_file('/var/log/cloud-init.log') - assert '/swap.img' in client.execute('cat /proc/swaps') - assert '/swap.img' in client.execute('cat /etc/fstab') - assert 'fallocate swap creation failed, will attempt with dd' in log + log = client.read_from_file("/var/log/cloud-init.log") + assert "/swap.img" in client.execute("cat /proc/swaps") + assert "/swap.img" in client.execute("cat /etc/fstab") + assert "fallocate swap creation failed, will attempt with dd" in log assert "Running command ['dd', 'if=/dev/zero', 'of=/swap.img'" in log - assert 'SUCCESS: config-mounts ran successfully' in log + assert "SUCCESS: config-mounts ran successfully" in log diff --git a/tests/integration_tests/bugs/test_lp1898997.py b/tests/integration_tests/bugs/test_lp1898997.py new file mode 100644 index 00000000..d8ea54c3 --- /dev/null +++ b/tests/integration_tests/bugs/test_lp1898997.py @@ -0,0 +1,77 @@ +"""Integration test for LP: #1898997 + +cloud-init was incorrectly excluding Open vSwitch bridge members from its list +of interfaces. This meant that instances which had only one interface which +was in an Open vSwitch bridge would not boot correctly: cloud-init would not +find the expected physical interfaces, so would not apply network config. + +This test checks that cloud-init believes it has successfully applied the +network configuration, and confirms that the bridge can be used to ping the +default gateway. +""" +import pytest + +from tests.integration_tests import random_mac_address +from tests.integration_tests.util import verify_clean_log + +MAC_ADDRESS = random_mac_address() + + +NETWORK_CONFIG = """\ +bridges: + ovs-br: + dhcp4: true + interfaces: + - enp5s0 + macaddress: 52:54:00:d9:08:1c + mtu: 1500 + openvswitch: {{}} +ethernets: + enp5s0: + mtu: 1500 + set-name: enp5s0 + match: + macaddress: {} +version: 2 +""".format( + MAC_ADDRESS +) + + +@pytest.mark.lxd_config_dict( + { + "user.network-config": NETWORK_CONFIG, + "volatile.eth0.hwaddr": MAC_ADDRESS, + } +) +@pytest.mark.lxd_vm +@pytest.mark.lxd_use_exec +@pytest.mark.not_bionic +@pytest.mark.ubuntu +class TestInterfaceListingWithOpenvSwitch: + def test_ovs_member_interfaces_not_excluded(self, client): + # We need to install openvswitch for our provided network configuration + # to apply (on next boot), so DHCP on our default interface to fetch it + client.execute("dhclient enp5s0") + client.execute("apt update -qqy") + client.execute("apt-get install -qqy openvswitch-switch") + + # Now our networking config should successfully apply on a clean reboot + client.execute("cloud-init clean --logs") + client.restart() + + cloudinit_output = client.read_from_file("/var/log/cloud-init.log") + + # Confirm that the network configuration was applied successfully + verify_clean_log(cloudinit_output) + # Confirm that the applied network config created the OVS bridge + assert "ovs-br" in client.execute("ip addr") + + # Test that we can ping our gateway using our bridge + gateway = client.execute( + "ip -4 route show default | awk '{ print $3 }'" + ) + ping_result = client.execute( + "ping -c 1 -W 1 -I ovs-br {}".format(gateway) + ) + assert ping_result.ok diff --git a/tests/integration_tests/bugs/test_lp1900837.py b/tests/integration_tests/bugs/test_lp1900837.py index 3fe7d0d0..d9ef18aa 100644 --- a/tests/integration_tests/bugs/test_lp1900837.py +++ b/tests/integration_tests/bugs/test_lp1900837.py @@ -4,14 +4,12 @@ This test mirrors the reproducing steps from the reported bug: it changes the permissions on cloud-init.log to 600 and confirms that they remain 600 after a reboot. """ -import pytest def _get_log_perms(client): return client.execute("stat -c %a /var/log/cloud-init.log") -@pytest.mark.sru_2020_11 class TestLogPermissionsNotResetOnReboot: def test_permissions_unchanged(self, client): # Confirm that the current permissions aren't 600 @@ -22,7 +20,8 @@ class TestLogPermissionsNotResetOnReboot: assert "600" == _get_log_perms(client) # Reboot - client.instance.restart() + client.restart() + assert client.execute("cloud-init status").ok # Check that permissions are not reset on reboot assert "600" == _get_log_perms(client) diff --git a/tests/integration_tests/bugs/test_lp1901011.py b/tests/integration_tests/bugs/test_lp1901011.py new file mode 100644 index 00000000..7de8bd77 --- /dev/null +++ b/tests/integration_tests/bugs/test_lp1901011.py @@ -0,0 +1,67 @@ +"""Integration test for LP: #1901011 + +Ensure an ephemeral disk exists after boot. + +See https://github.com/canonical/cloud-init/pull/800 +""" +import pytest + +from tests.integration_tests.clouds import IntegrationCloud + + +@pytest.mark.azure +@pytest.mark.parametrize( + "instance_type,is_ephemeral", + [ + ("Standard_DS1_v2", True), + ("Standard_D2s_v4", False), + ], +) +def test_ephemeral( + instance_type, is_ephemeral, session_cloud: IntegrationCloud, setup_image +): + if is_ephemeral: + expected_log = ( + "Ephemeral resource disk '/dev/disk/cloud/azure_resource' exists. " + "Merging default Azure cloud ephemeral disk configs." + ) + else: + expected_log = ( + "Ephemeral resource disk '/dev/disk/cloud/azure_resource' does " + "not exist. Not merging default Azure cloud ephemeral disk " + "configs." + ) + + with session_cloud.launch( + launch_kwargs={"instance_type": instance_type} + ) as client: + # Verify log file + log = client.read_from_file("/var/log/cloud-init.log") + assert expected_log in log + + # Verify devices + dev_links = client.execute("ls /dev/disk/cloud") + assert "azure_root" in dev_links + assert "azure_root-part1" in dev_links + if is_ephemeral: + assert "azure_resource" in dev_links + assert "azure_resource-part1" in dev_links + + # Verify mounts + blks = client.execute("lsblk -pPo NAME,TYPE,MOUNTPOINT") + root_device = client.execute( + "realpath /dev/disk/cloud/azure_root-part1" + ) + assert ( + 'NAME="{}" TYPE="part" MOUNTPOINT="/"'.format(root_device) in blks + ) + if is_ephemeral: + ephemeral_device = client.execute( + "realpath /dev/disk/cloud/azure_resource-part1" + ) + assert ( + 'NAME="{}" TYPE="part" MOUNTPOINT="/mnt"'.format( + ephemeral_device + ) + in blks + ) diff --git a/tests/integration_tests/bugs/test_lp1910835.py b/tests/integration_tests/bugs/test_lp1910835.py new file mode 100644 index 00000000..1844594c --- /dev/null +++ b/tests/integration_tests/bugs/test_lp1910835.py @@ -0,0 +1,64 @@ +"""Integration test for LP: #1910835. + +If users do not provide an SSH key and instead ask Azure to generate a key for +them, the key material available in the IMDS may include CRLF sequences. Prior +to e56b55452549cb037da0a4165154ffa494e9678a, the Azure datasource handled keys +via a certificate, the tooling for which removed these sequences. This test +ensures that cloud-init does not regress support for this Azure behaviour. + +This test provides the SSH key configured for tests to the instance in two +ways: firstly, with CRLFs to mimic the generated keys, via the Azure API; +secondly, as user-data in unmodified form. This means that even on systems +which exhibit the bug fetching the platform's metadata, we can SSH into the SUT +to confirm this (instead of having to assert SSH failure; there are lots of +reasons SSH might fail). + +Once SSH'd in, we check that the two keys in .ssh/authorized_keys have the same +material: if the Azure datasource has removed the CRLFs correctly, then they +will match. +""" +import pytest + +USER_DATA_TMPL = """\ +#cloud-config +ssh_authorized_keys: + - {}""" + + +@pytest.mark.azure +def test_crlf_in_azure_metadata_ssh_keys(session_cloud, setup_image): + authorized_keys_path = "/home/{}/.ssh/authorized_keys".format( + session_cloud.cloud_instance.username + ) + # Pass in user-data to allow us to access the instance when the normal + # path fails + key_data = session_cloud.cloud_instance.key_pair.public_key_content + user_data = USER_DATA_TMPL.format(key_data) + # Throw a CRLF into the otherwise good key data, to emulate Azure's + # behaviour for generated keys + key_data = key_data[:20] + "\r\n" + key_data[20:] + vm_params = { + "os_profile": { + "linux_configuration": { + "ssh": { + "public_keys": [ + {"path": authorized_keys_path, "key_data": key_data} + ] + } + } + } + } + with session_cloud.launch( + launch_kwargs={"vm_params": vm_params, "user_data": user_data} + ) as client: + authorized_keys = ( + client.read_from_file(authorized_keys_path).strip().splitlines() + ) + # We expect one key from the cloud, one from user-data + assert 2 == len(authorized_keys) + # And those two keys should be the same, except for a possible key + # comment, which Azure strips out + assert ( + authorized_keys[0].rsplit(" ")[:2] + == authorized_keys[1].split(" ")[:2] + ) diff --git a/tests/integration_tests/bugs/test_lp1912844.py b/tests/integration_tests/bugs/test_lp1912844.py new file mode 100644 index 00000000..55511ed2 --- /dev/null +++ b/tests/integration_tests/bugs/test_lp1912844.py @@ -0,0 +1,105 @@ +"""Integration test for LP: #1912844 + +cloud-init should ignore OVS-internal interfaces when performing its own +interface determination: these interfaces are handled fully by OVS, so +cloud-init should never need to touch them. + +This test is a semi-synthetic reproducer for the bug. It uses a similar +network configuration, tweaked slightly to DHCP in a way that will succeed even +on "failed" boots. The exact bug doesn't reproduce with the NoCloud +datasource, because it runs at init-local time (whereas the MAAS datasource, +from the report, runs only at init (network) time): this means that the +networking code runs before OVS creates its interfaces (which happens after +init-local but, of course, before networking is up), and so doesn't generate +the traceback that they cause. We work around this by calling +``get_interfaces_by_mac` directly in the test code. +""" +import pytest + +from tests.integration_tests import random_mac_address + +MAC_ADDRESS = random_mac_address() + +NETWORK_CONFIG = """\ +bonds: + bond0: + interfaces: + - enp5s0 + macaddress: {0} + mtu: 1500 +bridges: + ovs-br: + interfaces: + - bond0 + macaddress: {0} + mtu: 1500 + openvswitch: {{}} + dhcp4: true +ethernets: + enp5s0: + mtu: 1500 + set-name: enp5s0 + match: + macaddress: {0} +version: 2 +vlans: + ovs-br.100: + id: 100 + link: ovs-br + mtu: 1500 + ovs-br.200: + id: 200 + link: ovs-br + mtu: 1500 +""".format( + MAC_ADDRESS +) + + +SETUP_USER_DATA = """\ +#cloud-config +packages: +- openvswitch-switch +""" + + +@pytest.fixture +def ovs_enabled_session_cloud(session_cloud): + """A session_cloud wrapper, to use an OVS-enabled image for tests. + + This implementation is complicated by wanting to use ``session_cloud``s + snapshot cleanup/retention logic, to avoid having to reimplement that here. + """ + old_snapshot_id = session_cloud.snapshot_id + with session_cloud.launch( + user_data=SETUP_USER_DATA, + ) as instance: + instance.instance.clean() + session_cloud.snapshot_id = instance.snapshot() + + yield session_cloud + + try: + session_cloud.delete_snapshot() + finally: + session_cloud.snapshot_id = old_snapshot_id + + +@pytest.mark.lxd_vm +def test_get_interfaces_by_mac_doesnt_traceback(ovs_enabled_session_cloud): + """Launch our OVS-enabled image and confirm the bug doesn't reproduce.""" + launch_kwargs = { + "config_dict": { + "user.network-config": NETWORK_CONFIG, + "volatile.eth0.hwaddr": MAC_ADDRESS, + }, + } + with ovs_enabled_session_cloud.launch( + launch_kwargs=launch_kwargs, + ) as client: + result = client.execute( + "python3 -c" + "'from cloudinit.net import get_interfaces_by_mac;" + "get_interfaces_by_mac()'" + ) + assert result.ok |