diff options
author | James Falcon <james.falcon@canonical.com> | 2023-03-29 10:37:46 -0500 |
---|---|---|
committer | James Falcon <james.falcon@canonical.com> | 2023-03-29 10:37:46 -0500 |
commit | 247bba7aa3afa18880f642734f5fb18bda5c4e80 (patch) | |
tree | 3bd84cd60953efaa8998084993e3e7b201bc39ff | |
parent | d68a0d7b06844f00cb93ecb1bc3e64b5d51cff0e (diff) | |
parent | d7bdba6f941ed04dc39e97af215e46e2baf07507 (diff) | |
download | cloud-init-git-247bba7aa3afa18880f642734f5fb18bda5c4e80.tar.gz |
merge from upstream/main at 23.1-41-gd7bdba6f
60 files changed, 527 insertions, 386 deletions
diff --git a/.github/workflows/unit.yml b/.github/workflows/unit.yml index 26c278d5..2c72a2a4 100644 --- a/.github/workflows/unit.yml +++ b/.github/workflows/unit.yml @@ -12,15 +12,24 @@ jobs: strategy: matrix: python-version: [ "3.6", "3.7", "3.8", "3.9", "3.10", "3.11" ] - name: Python ${{matrix.python-version}} unittest + toxenv: [ py3 ] + experimental: [false] + include: + - python-version: "3.12-dev" + toxenv: py3 + experimental: true + - python-version: "3.6" + toxenv: lowest-supported + experimental: false + name: unittest / ${{ matrix.toxenv }} / python ${{matrix.python-version}} runs-on: ubuntu-20.04 + continue-on-error: ${{ matrix.experimental }} steps: - - name: "Checkout #1" - uses: actions/checkout@v3.0.0 - - name: "Checkout #2 (for tools/read-version)" - run: | - git fetch --unshallow - git remote add upstream https://git.launchpad.net/cloud-init + - name: "Checkout" + uses: actions/checkout@v3 + with: + # Fetch all tags for tools/read-version + fetch-depth: 0 - name: Install Python ${{matrix.python-version}} uses: actions/setup-python@v4 with: @@ -29,6 +38,5 @@ jobs: run: pip install tox - name: Run unittest env: - TOXENV: py3 PYTEST_ADDOPTS: -v - run: tox + run: tox -e ${{ matrix.toxenv }} diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 7422c2cb..00000000 --- a/.travis.yml +++ /dev/null @@ -1,34 +0,0 @@ -language: python -dist: focal - -cache: pip - -install: - # Required so `git describe` will definitely find a tag; see - # https://github.com/travis-ci/travis-ci/issues/7422 - - git fetch --unshallow - # Not pinning setuptools can cause failures on python 3.7 and 3.8 builds - # See https://github.com/pypa/setuptools/issues/3118 - - pip install setuptools==59.6.0 - - pip install tox - -script: - - tox - -env: - TOXENV=py3 - PYTEST_ADDOPTS=-v # List all tests run by pytest - -matrix: - fast_finish: true - include: - - python: 3.6 - env: - TOXENV=lowest-supported - PYTEST_ADDOPTS=-v # List all tests run by pytest - dist: bionic - # Test all supported Python versions (but at the end, so we schedule - # longer-running jobs first) - - python: 3.12-dev - allow_failures: - - python: 3.12-dev diff --git a/cloudinit/config/schema.py b/cloudinit/config/schema.py index 0669defc..9886bde6 100644 --- a/cloudinit/config/schema.py +++ b/cloudinit/config/schema.py @@ -319,8 +319,7 @@ def get_jsonschema_validator(): # type jsonschema attributes in cloud-init's schema. # This allows #cloud-config to provide valid yaml "content: !!binary | ..." - strict_metaschema = deepcopy(Draft4Validator.META_SCHEMA) - strict_metaschema["additionalProperties"] = False + meta_schema = deepcopy(Draft4Validator.META_SCHEMA) # This additional label allows us to specify a different name # than the property key when generating docs. @@ -328,10 +327,11 @@ def get_jsonschema_validator(): # otherwise the property label in the generated docs will be a # regular expression. # http://json-schema.org/understanding-json-schema/reference/object.html#pattern-properties - strict_metaschema["properties"]["label"] = {"type": "string"} + meta_schema["properties"]["label"] = {"type": "string"} validator_kwargs = {} if hasattr(Draft4Validator, "TYPE_CHECKER"): # jsonschema 3.0+ + meta_schema["additionalProperties"] = False # Unsupported in 2.6.0 type_checker = Draft4Validator.TYPE_CHECKER.redefine( "string", is_schema_byte_string ) @@ -339,6 +339,7 @@ def get_jsonschema_validator(): "type_checker": type_checker, } else: # jsonschema 2.6 workaround + # pylint:disable-next=no-member types = Draft4Validator.DEFAULT_TYPES # pylint: disable=E1101 # Allow bytes as well as string (and disable a spurious unsupported # assignment-operation pylint warning which appears because this @@ -354,7 +355,7 @@ def get_jsonschema_validator(): validators["anyOf"] = _anyOf cloudinitValidator = create( - meta_schema=strict_metaschema, + meta_schema=meta_schema, validators=validators, version="draft4", **validator_kwargs, diff --git a/cloudinit/sources/azure/imds.py b/cloudinit/sources/azure/imds.py index 5e4046d0..1f5cf008 100644 --- a/cloudinit/sources/azure/imds.py +++ b/cloudinit/sources/azure/imds.py @@ -51,13 +51,14 @@ class ReadUrlRetryHandler: # Check for connection errors which may occur early boot, but # are otherwise indicative that we are not connecting with the # primary NIC. - if isinstance( - exception.cause, (requests.ConnectionError, requests.Timeout) - ): + if isinstance(exception.cause, requests.ConnectionError): self.max_connection_errors -= 1 if self.max_connection_errors < 0: retry = False - elif exception.code not in self.retry_codes: + elif ( + exception.code is not None + and exception.code not in self.retry_codes + ): retry = False if self._request_count >= self._logging_threshold: diff --git a/integration-requirements.txt b/integration-requirements.txt index 03d4fc25..85e2efcb 100644 --- a/integration-requirements.txt +++ b/integration-requirements.txt @@ -3,3 +3,4 @@ # pycloudlib==1!1 pytest +packaging diff --git a/tests/integration_tests/bugs/test_gh626.py b/tests/integration_tests/bugs/test_gh626.py index b80b677a..6df52d9c 100644 --- a/tests/integration_tests/bugs/test_gh626.py +++ b/tests/integration_tests/bugs/test_gh626.py @@ -9,6 +9,7 @@ import yaml from tests.integration_tests import random_mac_address from tests.integration_tests.instances import IntegrationInstance +from tests.integration_tests.integration_settings import PLATFORM MAC_ADDRESS = random_mac_address() NETWORK_CONFIG = """\ @@ -28,8 +29,10 @@ iface eth0 inet dhcp ethernet-wol g""" -@pytest.mark.lxd_container -@pytest.mark.lxd_vm +@pytest.mark.skipif( + PLATFORM not in ["lxd_container", "lxd_vm"], + reason="Test requires custom networking provided by LXD", +) @pytest.mark.lxd_config_dict( { "user.network-config": NETWORK_CONFIG, diff --git a/tests/integration_tests/bugs/test_gh632.py b/tests/integration_tests/bugs/test_gh632.py index d83d244b..9e67fe59 100644 --- a/tests/integration_tests/bugs/test_gh632.py +++ b/tests/integration_tests/bugs/test_gh632.py @@ -6,12 +6,15 @@ no traceback if the metadata disk cannot be found. import pytest from tests.integration_tests.instances import IntegrationInstance +from tests.integration_tests.integration_settings import PLATFORM 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 +@pytest.mark.skipif( + PLATFORM not in ["lxd_container", "lxd_vm"], + reason="Tested behavior is emulated using NoCloud", +) def test_datasource_rbx_no_stacktrace(client: IntegrationInstance): client.write_to_file( "/etc/cloud/cloud.cfg.d/90_dpkg.cfg", diff --git a/tests/integration_tests/bugs/test_gh668.py b/tests/integration_tests/bugs/test_gh668.py index 95edb48d..2a97c488 100644 --- a/tests/integration_tests/bugs/test_gh668.py +++ b/tests/integration_tests/bugs/test_gh668.py @@ -9,6 +9,7 @@ import pytest from tests.integration_tests import random_mac_address from tests.integration_tests.instances import IntegrationInstance +from tests.integration_tests.integration_settings import PLATFORM DESTINATION_IP = "172.16.0.10" GATEWAY_IP = "10.0.0.100" @@ -32,8 +33,10 @@ ethernets: EXPECTED_ROUTE = "{} via {}".format(DESTINATION_IP, GATEWAY_IP) -@pytest.mark.lxd_container -@pytest.mark.lxd_vm +@pytest.mark.skipif( + PLATFORM not in ["lxd_container", "lxd_vm"], + reason="Test requires custom networking provided by LXD", +) @pytest.mark.lxd_config_dict( { "user.network-config": NETWORK_CONFIG, diff --git a/tests/integration_tests/bugs/test_gh671.py b/tests/integration_tests/bugs/test_gh671.py index 2d7c8118..e93bd70b 100644 --- a/tests/integration_tests/bugs/test_gh671.py +++ b/tests/integration_tests/bugs/test_gh671.py @@ -10,6 +10,7 @@ import crypt import pytest from tests.integration_tests.clouds import IntegrationCloud +from tests.integration_tests.integration_settings import PLATFORM OLD_PASSWORD = "DoIM33tTheComplexityRequirements!??" NEW_PASSWORD = "DoIM33tTheComplexityRequirementsNow!??" @@ -22,7 +23,7 @@ def _check_password(instance, unhashed_password): assert shadow_password == hashed_password -@pytest.mark.azure +@pytest.mark.skipif(PLATFORM != "azure", reason="Test is Azure specific") def test_update_default_password(setup_image, session_cloud: IntegrationCloud): os_profile = { "os_profile": { diff --git a/tests/integration_tests/bugs/test_gh868.py b/tests/integration_tests/bugs/test_gh868.py index a62e8b36..67ac9b3a 100644 --- a/tests/integration_tests/bugs/test_gh868.py +++ b/tests/integration_tests/bugs/test_gh868.py @@ -2,6 +2,7 @@ import pytest from tests.integration_tests.instances import IntegrationInstance +from tests.integration_tests.integration_settings import PLATFORM from tests.integration_tests.util import verify_clean_log USERDATA = """\ @@ -15,12 +16,9 @@ chef: @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.skipif( + "openstack" == PLATFORM, reason="Firewall preventing openstack run" +) @pytest.mark.user_data(USERDATA) def test_chef_license(client: IntegrationInstance): log = client.read_from_file("/var/log/cloud-init.log") diff --git a/tests/integration_tests/bugs/test_lp1835584.py b/tests/integration_tests/bugs/test_lp1835584.py index 4e732446..7f110379 100644 --- a/tests/integration_tests/bugs/test_lp1835584.py +++ b/tests/integration_tests/bugs/test_lp1835584.py @@ -30,9 +30,11 @@ import re import pytest from pycloudlib.cloud import ImageType -from tests.integration_tests.clouds import ImageSpecification, IntegrationCloud +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 def _check_iid_insensitive_across_kernel_upgrade( @@ -67,17 +69,15 @@ def _check_iid_insensitive_across_kernel_upgrade( assert 1 == ssh_runs, "config_ssh ran too many times {}".format(ssh_runs) -@pytest.mark.azure +@pytest.mark.skipif(PLATFORM != "azure", reason="Test is Azure specific") @pytest.mark.integration_cloud_args(image_type=ImageType.PRO_FIPS) 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"): + if (CURRENT_RELEASE.os, CURRENT_RELEASE.series) != ("ubuntu", "bionic"): pytest.skip( - "Test only supports ubuntu:bionic not {0.os}:{0.release}".format( - cfg_image_spec - ) + f"Test only supports ubuntu:bionic not {CURRENT_RELEASE.os}: " + f"{CURRENT_RELEASE.series}" ) source = get_validated_source(session_cloud) if not source.installs_new_version(): @@ -87,7 +87,7 @@ def test_azure_kernel_upgrade_case_insensitive_uuid( with session_cloud.launch( launch_kwargs={ "image_id": session_cloud.cloud_instance.daily_image( - cfg_image_spec.image_id, image_type=ImageType.PRO_FIPS + CURRENT_RELEASE.image_id, image_type=ImageType.PRO_FIPS ) } ) as instance: diff --git a/tests/integration_tests/bugs/test_lp1897099.py b/tests/integration_tests/bugs/test_lp1897099.py index 1f5030ce..b0981790 100644 --- a/tests/integration_tests/bugs/test_lp1897099.py +++ b/tests/integration_tests/bugs/test_lp1897099.py @@ -7,6 +7,8 @@ https://bugs.launchpad.net/cloud-init/+bug/1897099 import pytest +from tests.integration_tests.integration_settings import PLATFORM + USER_DATA = """\ #cloud-config bootcmd: @@ -19,7 +21,9 @@ swap: @pytest.mark.user_data(USER_DATA) -@pytest.mark.no_container("Containers cannot configure swap") +@pytest.mark.skipif( + PLATFORM == "lxd_container", reason="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") diff --git a/tests/integration_tests/bugs/test_lp1898997.py b/tests/integration_tests/bugs/test_lp1898997.py index d8ea54c3..ec92aeb6 100644 --- a/tests/integration_tests/bugs/test_lp1898997.py +++ b/tests/integration_tests/bugs/test_lp1898997.py @@ -12,6 +12,8 @@ default gateway. import pytest from tests.integration_tests import random_mac_address +from tests.integration_tests.integration_settings import PLATFORM +from tests.integration_tests.releases import CURRENT_RELEASE, FOCAL from tests.integration_tests.util import verify_clean_log MAC_ADDRESS = random_mac_address() @@ -44,10 +46,14 @@ version: 2 "volatile.eth0.hwaddr": MAC_ADDRESS, } ) -@pytest.mark.lxd_vm +@pytest.mark.skipif( + PLATFORM != "lxd_vm", + reason="Test requires custom networking provided by LXD", +) +@pytest.mark.skipif( + CURRENT_RELEASE < FOCAL, reason="Tested on Focal and above" +) @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 diff --git a/tests/integration_tests/bugs/test_lp1901011.py b/tests/integration_tests/bugs/test_lp1901011.py index 7de8bd77..e94caf9b 100644 --- a/tests/integration_tests/bugs/test_lp1901011.py +++ b/tests/integration_tests/bugs/test_lp1901011.py @@ -7,9 +7,10 @@ See https://github.com/canonical/cloud-init/pull/800 import pytest from tests.integration_tests.clouds import IntegrationCloud +from tests.integration_tests.integration_settings import PLATFORM -@pytest.mark.azure +@pytest.mark.skipif(PLATFORM != "azure", reason="Test is Azure specific") @pytest.mark.parametrize( "instance_type,is_ephemeral", [ diff --git a/tests/integration_tests/bugs/test_lp1910835.py b/tests/integration_tests/bugs/test_lp1910835.py index 1844594c..aa0fb75c 100644 --- a/tests/integration_tests/bugs/test_lp1910835.py +++ b/tests/integration_tests/bugs/test_lp1910835.py @@ -19,13 +19,15 @@ will match. """ import pytest +from tests.integration_tests.integration_settings import PLATFORM + USER_DATA_TMPL = """\ #cloud-config ssh_authorized_keys: - {}""" -@pytest.mark.azure +@pytest.mark.skipif(PLATFORM != "azure", reason="Test is Azure specific") 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 diff --git a/tests/integration_tests/bugs/test_lp1912844.py b/tests/integration_tests/bugs/test_lp1912844.py index 55511ed2..b5aafa76 100644 --- a/tests/integration_tests/bugs/test_lp1912844.py +++ b/tests/integration_tests/bugs/test_lp1912844.py @@ -17,6 +17,7 @@ the traceback that they cause. We work around this by calling import pytest from tests.integration_tests import random_mac_address +from tests.integration_tests.integration_settings import PLATFORM MAC_ADDRESS = random_mac_address() @@ -85,7 +86,10 @@ def ovs_enabled_session_cloud(session_cloud): session_cloud.snapshot_id = old_snapshot_id -@pytest.mark.lxd_vm +@pytest.mark.skipif( + PLATFORM != "lxd_vm", + reason="Test requires custom networking provided by LXD", +) 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 = { diff --git a/tests/integration_tests/clouds.py b/tests/integration_tests/clouds.py index 945a5fb6..404d0a08 100644 --- a/tests/integration_tests/clouds.py +++ b/tests/integration_tests/clouds.py @@ -7,7 +7,7 @@ import re import string from abc import ABC, abstractmethod from copy import deepcopy -from typing import Optional, Type +from typing import Type from uuid import UUID from pycloudlib import ( @@ -28,6 +28,7 @@ import cloudinit from cloudinit.subp import ProcessExecutionError, subp from tests.integration_tests import integration_settings from tests.integration_tests.instances import IntegrationInstance +from tests.integration_tests.releases import CURRENT_RELEASE from tests.integration_tests.util import emit_dots_on_travis log = logging.getLogger("integration_testing") @@ -46,52 +47,6 @@ def _get_ubuntu_series() -> list: return out.splitlines() -class ImageSpecification: - """A specification of an image to launch for testing. - - If either of ``os`` and ``release`` are not specified, an attempt will be - made to infer the correct values for these on instantiation. - - :param image_id: - The image identifier used by the rest of the codebase to launch this - image. - :param os: - An optional string describing the operating system this image is for - (e.g. "ubuntu", "rhel", "freebsd"). - :param release: - A optional string describing the operating system release (e.g. - "focal", "8"; the exact values here will depend on the OS). - """ - - def __init__( - self, - image_id: str, - os: Optional[str] = None, - release: Optional[str] = None, - ): - if image_id in _get_ubuntu_series(): - if os is None: - os = "ubuntu" - if release is None: - release = image_id - - self.image_id = image_id - self.os = os - self.release = release - log.info( - "Detected image: image_id=%s os=%s release=%s", - self.image_id, - self.os, - self.release, - ) - - @classmethod - def from_os_image(cls): - """Return an ImageSpecification for integration_settings.OS_IMAGE.""" - parts = integration_settings.OS_IMAGE.split("::", 2) - return cls(*parts) - - class IntegrationCloud(ABC): datasource: str cloud_instance: BaseCloud @@ -127,12 +82,9 @@ class IntegrationCloud(ABC): raise NotImplementedError def _get_initial_image(self, **kwargs) -> str: - image = ImageSpecification.from_os_image() - try: - return self.cloud_instance.daily_image(image.image_id, **kwargs) - except (ValueError, IndexError) as ex: - log.debug("Exception while executing `daily_image`: %s", ex) - return image.image_id + return CURRENT_RELEASE.image_id or self.cloud_instance.daily_image( + CURRENT_RELEASE.series, **kwargs + ) def _perform_launch(self, launch_kwargs, **kwargs) -> BaseInstance: pycloudlib_instance = self.cloud_instance.launch(**launch_kwargs) @@ -398,17 +350,16 @@ class OpenstackCloud(IntegrationCloud): ) def _get_initial_image(self, **kwargs): - image = ImageSpecification.from_os_image() try: - UUID(image.image_id) + UUID(CURRENT_RELEASE.image_id) except ValueError as e: raise RuntimeError( "When using Openstack, `OS_IMAGE` MUST be specified with " "a 36-character UUID image ID. Passing in a release name is " "not valid here.\n" - "OS image id: {}".format(image.image_id) + "OS image id: {}".format(CURRENT_RELEASE.image_id) ) from e - return image.image_id + return CURRENT_RELEASE.image_id class IbmCloud(IntegrationCloud): diff --git a/tests/integration_tests/cmd/test_status.py b/tests/integration_tests/cmd/test_status.py index 2582855d..bc65bff5 100644 --- a/tests/integration_tests/cmd/test_status.py +++ b/tests/integration_tests/cmd/test_status.py @@ -3,8 +3,10 @@ from time import sleep import pytest -from tests.integration_tests.clouds import ImageSpecification, IntegrationCloud +from tests.integration_tests.clouds import IntegrationCloud from tests.integration_tests.instances import IntegrationInstance +from tests.integration_tests.integration_settings import PLATFORM +from tests.integration_tests.releases import CURRENT_RELEASE, IS_UBUNTU, JAMMY # We're implementing our own here in case cloud-init status --wait @@ -37,8 +39,11 @@ def _remove_nocloud_dir_and_reboot(client: IntegrationInstance): client.instance._wait_for_execute(old_boot_id=old_boot_id) -@pytest.mark.ubuntu -@pytest.mark.lxd_container +@pytest.mark.skipif(not IS_UBUNTU, reason="Only ever tested on Ubuntu") +@pytest.mark.skipif( + PLATFORM != "lxd_container", + reason="Test is LXD specific", +) def test_wait_when_no_datasource(session_cloud: IntegrationCloud, setup_image): """Ensure that when no datasource is found, we get status: disabled @@ -59,10 +64,7 @@ def test_wait_when_no_datasource(session_cloud: IntegrationCloud, setup_image): # No ubuntu user if cloud-init didn't run client.instance.username = "root" # Jammy and above will use LXD datasource by default - if ImageSpecification.from_os_image().release in [ - "bionic", - "focal", - ]: + if CURRENT_RELEASE < JAMMY: _remove_nocloud_dir_and_reboot(client) status_out = _wait_for_cloud_init(client).stdout.strip() assert "status: disabled" in status_out diff --git a/tests/integration_tests/conftest.py b/tests/integration_tests/conftest.py index fabeb608..a4815f42 100644 --- a/tests/integration_tests/conftest.py +++ b/tests/integration_tests/conftest.py @@ -18,7 +18,6 @@ from tests.integration_tests.clouds import ( Ec2Cloud, GceCloud, IbmCloud, - ImageSpecification, IntegrationCloud, LxdContainerCloud, LxdVmCloud, @@ -58,34 +57,10 @@ def pytest_runtest_setup(item): platform, then skip the test. If platform specific marks are not specified, then we assume the test can be run anywhere. """ - all_platforms = platforms.keys() test_marks = [mark.name for mark in item.iter_markers()] - supported_platforms = set(all_platforms).intersection(test_marks) - current_platform = integration_settings.PLATFORM - unsupported_message = "Cannot run on platform {}".format(current_platform) - if "no_container" in test_marks: - if "lxd_container" in test_marks: - raise RuntimeError( - "lxd_container and no_container marks simultaneously set " - "on test" - ) - if current_platform == "lxd_container": - pytest.skip(unsupported_message) - if supported_platforms and current_platform not in supported_platforms: - pytest.skip(unsupported_message) - - image = ImageSpecification.from_os_image() - current_os = image.os - supported_os_set = set(os_list).intersection(test_marks) - if current_os and supported_os_set and current_os not in supported_os_set: - pytest.skip("Cannot run on OS {}".format(current_os)) if "unstable" in test_marks and not integration_settings.RUN_UNSTABLE: pytest.skip("Test marked unstable. Manually remove mark to run it") - current_release = image.release - if "not_{}".format(current_release) in test_marks: - pytest.skip("Cannot run on release {}".format(current_release)) - # disable_subp_usage is defined at a higher level, but we don't # want it applied here diff --git a/tests/integration_tests/datasources/test_detect_openstack.py b/tests/integration_tests/datasources/test_detect_openstack.py index c70e9815..73246f8f 100644 --- a/tests/integration_tests/datasources/test_detect_openstack.py +++ b/tests/integration_tests/datasources/test_detect_openstack.py @@ -1,10 +1,11 @@ import pytest from tests.integration_tests.instances import IntegrationInstance +from tests.integration_tests.integration_settings import PLATFORM -@pytest.mark.lxd_vm @pytest.mark.lxd_use_exec +@pytest.mark.skipif(PLATFORM != "lxd_vm", reason="Modifies grub config") def test_lxd_datasource_kernel_override(client: IntegrationInstance): """This test is twofold: it tests kernel commandline override, which also validates OpenStack Ironic requirements. OpenStack Ironic does not diff --git a/tests/integration_tests/datasources/test_ec2_ipv6.py b/tests/integration_tests/datasources/test_ec2_ipv6.py index 7bb45b40..aff7ddd2 100644 --- a/tests/integration_tests/datasources/test_ec2_ipv6.py +++ b/tests/integration_tests/datasources/test_ec2_ipv6.py @@ -3,6 +3,7 @@ import re import pytest from tests.integration_tests.instances import IntegrationInstance +from tests.integration_tests.integration_settings import PLATFORM def _test_crawl(client, ip): @@ -18,7 +19,7 @@ def _test_crawl(client, ip): assert float(result[0]) < 20 -@pytest.mark.ec2 +@pytest.mark.skipif(PLATFORM != "ec2", reason="test is ec2 specific") def test_dual_stack(client: IntegrationInstance): # Drop IPv4 responses assert client.execute("iptables -I INPUT -s 169.254.169.254 -j DROP").ok diff --git a/tests/integration_tests/datasources/test_lxd_discovery.py b/tests/integration_tests/datasources/test_lxd_discovery.py index f3ca7158..4f907434 100644 --- a/tests/integration_tests/datasources/test_lxd_discovery.py +++ b/tests/integration_tests/datasources/test_lxd_discovery.py @@ -3,8 +3,9 @@ import json import pytest import yaml -from tests.integration_tests.clouds import ImageSpecification from tests.integration_tests.instances import IntegrationInstance +from tests.integration_tests.integration_settings import PLATFORM +from tests.integration_tests.releases import CURRENT_RELEASE, IS_UBUNTU from tests.integration_tests.util import lxd_has_nocloud, verify_clean_log @@ -36,7 +37,7 @@ def _customize_environment(client: IntegrationInstance): "datasource_list: [LXD, NoCloud]\n", ) # This is also to ensure that NoCloud can be detected - if ImageSpecification.from_os_image().release == "jammy": + if CURRENT_RELEASE.series == "jammy": # Add nocloud-net seed files because Jammy no longer delivers NoCloud # (LP: #1958460). client.execute("mkdir -p /var/lib/cloud/seed/nocloud-net") @@ -48,9 +49,11 @@ def _customize_environment(client: IntegrationInstance): client.restart() -@pytest.mark.lxd_container -@pytest.mark.lxd_vm -@pytest.mark.ubuntu # Because netplan +@pytest.mark.skipif(not IS_UBUNTU, reason="Netplan usage") +@pytest.mark.skipif( + PLATFORM not in ["lxd_container", "lxd_vm"], + reason="Test is LXD specific", +) def test_lxd_datasource_discovery(client: IntegrationInstance): """Test that DataSourceLXD is detected instead of NoCloud.""" @@ -95,7 +98,7 @@ def test_lxd_datasource_discovery(client: IntegrationInstance): ] == sorted(list(ds_cfg.keys())) if ( client.settings.PLATFORM == "lxd_vm" - and ImageSpecification.from_os_image().release == "bionic" + and CURRENT_RELEASE.series == "bionic" ): # pycloudlib injects user.vendor_data for lxd_vm on bionic # to start the lxd-agent. diff --git a/tests/integration_tests/datasources/test_lxd_hotplug.py b/tests/integration_tests/datasources/test_lxd_hotplug.py index 81cff252..536f4e36 100644 --- a/tests/integration_tests/datasources/test_lxd_hotplug.py +++ b/tests/integration_tests/datasources/test_lxd_hotplug.py @@ -5,9 +5,10 @@ import pytest from cloudinit import safeyaml from cloudinit.subp import subp from cloudinit.util import is_true -from tests.integration_tests.clouds import ImageSpecification from tests.integration_tests.decorators import retry from tests.integration_tests.instances import IntegrationInstance +from tests.integration_tests.integration_settings import PLATFORM +from tests.integration_tests.releases import CURRENT_RELEASE, JAMMY from tests.integration_tests.util import ( get_feature_flag_value, lxd_has_nocloud, @@ -62,8 +63,11 @@ def _prefer_lxd_datasource_over_nocloud(client: IntegrationInstance): # TODO: Once LXD adds MACs to the devices endpoint, support LXD VMs here # Currently the names are too unpredictable to be worth testing on VMs. -@pytest.mark.lxd_container @pytest.mark.user_data(USER_DATA) +@pytest.mark.skipif( + PLATFORM != "lxd_container", + reason="Test is LXD specific", +) class TestLxdHotplug: @pytest.fixture(autouse=True, scope="class") def class_teardown(self, class_client: IntegrationInstance): @@ -113,14 +117,7 @@ class TestLxdHotplug: assert "eth2" not in client.execute("ip address") pre_netplan = client.read_from_file("/etc/netplan/50-cloud-init.yaml") assert "eth2" not in pre_netplan - if ImageSpecification.from_os_image().release in [ - "bionic", - "focal", - ]: # pyright: ignore - top_key = "user" - else: - top_key = "cloud-init" - + top_key = "user" if CURRENT_RELEASE < JAMMY else "cloud-init" assert subp( [ "lxc", diff --git a/tests/integration_tests/datasources/test_network_dependency.py b/tests/integration_tests/datasources/test_network_dependency.py index bd7fe658..7b692047 100644 --- a/tests/integration_tests/datasources/test_network_dependency.py +++ b/tests/integration_tests/datasources/test_network_dependency.py @@ -1,6 +1,8 @@ import pytest from tests.integration_tests.instances import IntegrationInstance +from tests.integration_tests.integration_settings import PLATFORM +from tests.integration_tests.releases import IS_UBUNTU def _customize_environment(client: IntegrationInstance): @@ -15,8 +17,10 @@ def _customize_environment(client: IntegrationInstance): # This test should be able to work on any cloud whose datasource specifies # a NETWORK dependency -@pytest.mark.gce -@pytest.mark.ubuntu # Because netplan +@pytest.mark.skipif(not IS_UBUNTU, reason="Netplan usage") +@pytest.mark.skipif( + PLATFORM != "gce", reason="Datasource doesn't specify a NETWORK dependency" +) def test_network_activation_disabled(client: IntegrationInstance): """Test that the network is not activated during init mode.""" _customize_environment(client) diff --git a/tests/integration_tests/datasources/test_nocloud.py b/tests/integration_tests/datasources/test_nocloud.py index d9410410..1b4c1abc 100644 --- a/tests/integration_tests/datasources/test_nocloud.py +++ b/tests/integration_tests/datasources/test_nocloud.py @@ -4,6 +4,7 @@ from pycloudlib.lxd.instance import LXDInstance from cloudinit.subp import subp from tests.integration_tests.instances import IntegrationInstance +from tests.integration_tests.integration_settings import PLATFORM VENDOR_DATA = """\ #cloud-config @@ -57,10 +58,12 @@ def setup_nocloud(instance: LXDInstance): ) -# Only running on LXD container because we need NoCloud with custom setup -@pytest.mark.lxd_container @pytest.mark.lxd_setup.with_args(setup_nocloud) @pytest.mark.lxd_use_exec +@pytest.mark.skipif( + PLATFORM != "lxd_container", + reason="Requires NoCloud with custom setup", +) def test_nocloud_seedfrom_vendordata(client: IntegrationInstance): """Integration test for #570. diff --git a/tests/integration_tests/datasources/test_oci_networking.py b/tests/integration_tests/datasources/test_oci_networking.py index dc0d343b..802e8ec7 100644 --- a/tests/integration_tests/datasources/test_oci_networking.py +++ b/tests/integration_tests/datasources/test_oci_networking.py @@ -6,6 +6,7 @@ import yaml from tests.integration_tests.clouds import IntegrationCloud from tests.integration_tests.instances import IntegrationInstance +from tests.integration_tests.integration_settings import PLATFORM from tests.integration_tests.util import verify_clean_log DS_CFG = """\ @@ -43,7 +44,7 @@ def extract_interface_names(network_config: dict) -> Set[str]: return set(interfaces) -@pytest.mark.oci +@pytest.mark.skipif(PLATFORM != "oci", reason="Test is OCI specific") def test_oci_networking_iscsi_instance(client: IntegrationInstance, tmpdir): customize_environment(client, tmpdir, configure_secondary_nics=False) result_net_files = client.execute("ls /run/net-*.conf") @@ -92,7 +93,7 @@ def client_with_secondary_vnic( client.instance.remove_network_interface(ip_address) -@pytest.mark.oci +@pytest.mark.skipif(PLATFORM != "oci", reason="Test is OCI specific") def test_oci_networking_iscsi_instance_secondary_vnics( client_with_secondary_vnic: IntegrationInstance, tmpdir ): @@ -142,7 +143,7 @@ def customize_netcfg( client.restart() -@pytest.mark.oci +@pytest.mark.skipif(PLATFORM != "oci", reason="Test is OCI specific") def test_oci_networking_system_cfg(client: IntegrationInstance, tmpdir): customize_netcfg(client, tmpdir) log = client.read_from_file("/var/log/cloud-init.log") diff --git a/tests/integration_tests/datasources/test_tmp_noexec.py b/tests/integration_tests/datasources/test_tmp_noexec.py index a060e20f..5aa8537d 100644 --- a/tests/integration_tests/datasources/test_tmp_noexec.py +++ b/tests/integration_tests/datasources/test_tmp_noexec.py @@ -1,6 +1,7 @@ import pytest from tests.integration_tests.instances import IntegrationInstance +from tests.integration_tests.integration_settings import PLATFORM from tests.integration_tests.util import verify_clean_log @@ -14,11 +15,10 @@ def customize_client(client: IntegrationInstance): @pytest.mark.adhoc -@pytest.mark.azure -@pytest.mark.ec2 -@pytest.mark.gce -@pytest.mark.oci -@pytest.mark.openstack +@pytest.mark.skipif( + PLATFORM not in ["azure", "ec2", "gce", "oci", "openstack"], + reason=f"Test hasn't been tested on {PLATFORM}", +) def test_dhcp_tmp_noexec(client: IntegrationInstance): customize_client(client) assert ( diff --git a/tests/integration_tests/integration_settings.py b/tests/integration_tests/integration_settings.py index c4f28fcb..aca5bf91 100644 --- a/tests/integration_tests/integration_settings.py +++ b/tests/integration_tests/integration_settings.py @@ -33,9 +33,9 @@ INSTANCE_TYPE: Optional[str] = None # Determines the base image to use or generate new images from. # # This can be the name of an Ubuntu release, or in the format -# <image_id>[::<os>[::<release>]]. If given, os and release should describe -# the image specified by image_id. (Ubuntu releases are converted to this -# format internally; in this case, to "focal::ubuntu::focal".) +# <image_id>[::<os>[::<release>[::<version>]]. If given, os and release should +# describe the image specified by image_id. (Ubuntu releases are converted +# to this format internally; in this case, to "None::ubuntu::focal::20.04".) OS_IMAGE = "focal" # Populate if you want to use a pre-launched instance instead of diff --git a/tests/integration_tests/modules/test_ansible.py b/tests/integration_tests/modules/test_ansible.py index 3d0f96cb..aea29b7d 100644 --- a/tests/integration_tests/modules/test_ansible.py +++ b/tests/integration_tests/modules/test_ansible.py @@ -1,5 +1,7 @@ import pytest +from tests.integration_tests.integration_settings import PLATFORM +from tests.integration_tests.releases import CURRENT_RELEASE, FOCAL from tests.integration_tests.util import verify_clean_log # This works by setting up a local repository and web server @@ -291,7 +293,9 @@ def test_ansible_pull_pip(client): # Ansible packaged in bionic is 2.5.1. This test relies on ansible collections, # which requires Ansible 2.9+, so no bionic. The functionality is covered # in `test_ansible_pull_pip` using pip rather than the bionic package. -@pytest.mark.not_bionic +@pytest.mark.skipif( + CURRENT_RELEASE < FOCAL, reason="Test requires Ansible 2.9+" +) @pytest.mark.user_data( USER_DATA + INSTALL_METHOD.format(package="ansible", method="distro") ) @@ -300,10 +304,14 @@ def test_ansible_pull_distro(client): @pytest.mark.user_data(ANSIBLE_CONTROL) -@pytest.mark.lxd_vm -# Not bionic because test uses pip install and version in pip is removing -# support for python version in bionic -@pytest.mark.not_bionic +@pytest.mark.skipif( + PLATFORM != "lxd_vm", + reason="Test requires starting LXD containers", +) +@pytest.mark.skipif( + CURRENT_RELEASE < FOCAL, + reason="Pip install is not supported for Ansible on release", +) def test_ansible_controller(client): log = client.read_from_file("/var/log/cloud-init.log") verify_clean_log(log) diff --git a/tests/integration_tests/modules/test_apt.py b/tests/integration_tests/modules/test_apt.py index 27ce2c5a..45c7a45a 100644 --- a/tests/integration_tests/modules/test_apt.py +++ b/tests/integration_tests/modules/test_apt.py @@ -5,8 +5,9 @@ import pytest from cloudinit import gpg from cloudinit.config import cc_apt_configure -from tests.integration_tests.clouds import ImageSpecification from tests.integration_tests.instances import IntegrationInstance +from tests.integration_tests.integration_settings import PLATFORM +from tests.integration_tests.releases import CURRENT_RELEASE, IS_UBUNTU USER_DATA = """\ #cloud-config @@ -114,7 +115,7 @@ TEST_KEY = "1FF0 D853 5EF7 E719 E5C8 1B9C 083D 06FB E4D3 04DF" TEST_SIGNED_BY_KEY = "A2EB 2DEC 0BD7 519B 7B38 BE38 376A 290E C806 8B11" -@pytest.mark.ubuntu +@pytest.mark.skipif(not IS_UBUNTU, reason="Apt usage") @pytest.mark.user_data(USER_DATA) class TestApt: def get_keys(self, class_client: IntegrationInstance): @@ -165,10 +166,11 @@ class TestApt: Ported from tests/cloud_tests/testcases/modules/apt_configure_sources_ppa.py """ - release = ImageSpecification.from_os_image().release ppa_path_contents = class_client.read_from_file( "/etc/apt/sources.list.d/" - "simplestreams-dev-ubuntu-trunk-{}.list".format(release) + "simplestreams-dev-ubuntu-trunk-{}.list".format( + CURRENT_RELEASE.series + ) ) assert ( "://ppa.launchpad.net/simplestreams-dev/trunk/ubuntu" @@ -181,11 +183,10 @@ class TestApt: def test_signed_by(self, class_client: IntegrationInstance): """Test the apt signed-by functionality.""" - release = ImageSpecification.from_os_image().release source = ( "deb [signed-by=/etc/apt/cloud-init.gpg.d/test_signed_by.gpg] " "http://ppa.launchpad.net/juju/stable/ubuntu" - " {} main".format(release) + " {} main".format(CURRENT_RELEASE.series) ) path_contents = class_client.read_from_file( "/etc/apt/sources.list.d/test_signed_by.list" @@ -251,27 +252,27 @@ class TestApt: def test_sources_write(self, class_client: IntegrationInstance): """Test overwrite or append to sources file""" - release = ImageSpecification.from_os_image().release test_write_content = class_client.read_from_file( "/etc/apt/sources.list.d/test_write.list" ) expected_contents = ( "deb [signed-by=/etc/apt/cloud-init.gpg.d/test_write.gpg] " - f"http://ppa.launchpad.net/juju/devel/ubuntu {release} main" + "http://ppa.launchpad.net/juju/devel/ubuntu " + f"{CURRENT_RELEASE.series} main" ) assert expected_contents.strip() == test_write_content.strip() def test_sources_append(self, class_client: IntegrationInstance): - release = ImageSpecification.from_os_image().release + series = CURRENT_RELEASE.series test_append_content = class_client.read_from_file( "/etc/apt/sources.list.d/test_append.list" ) expected_contents = ( "deb [signed-by=/etc/apt/cloud-init.gpg.d/test_append.gpg] " - f"http://ppa.launchpad.net/juju/stable/ubuntu {release} main\n" + f"http://ppa.launchpad.net/juju/stable/ubuntu {series} main\n" "deb [signed-by=/etc/apt/cloud-init.gpg.d/test_append.gpg] " - f"http://ppa.launchpad.net/juju/devel/ubuntu {release} main" + f"http://ppa.launchpad.net/juju/devel/ubuntu {series} main" ) assert expected_contents.strip() == test_append_content.strip() @@ -290,10 +291,12 @@ apt: DEFAULT_DATA = _DEFAULT_DATA.format(uri="") -@pytest.mark.ubuntu +@pytest.mark.skipif(not IS_UBUNTU, reason="Apt usage") @pytest.mark.user_data(DEFAULT_DATA) class TestDefaults: - @pytest.mark.openstack + @pytest.mark.skipif( + PLATFORM != "openstack", reason="Test is Openstack specific" + ) def test_primary_on_openstack(self, class_client: IntegrationInstance): """Test apt default primary source on openstack. @@ -312,12 +315,12 @@ class TestDefaults: sources_list = class_client.read_from_file("/etc/apt/sources.list") # 3 lines from main, universe, and multiverse - release = ImageSpecification.from_os_image().release - sec_url = f"deb http://security.ubuntu.com/ubuntu {release}-security" + series = CURRENT_RELEASE.series + sec_url = f"deb http://security.ubuntu.com/ubuntu {series}-security" if class_client.settings.PLATFORM == "azure": sec_url = ( f"deb http://azure.archive.ubuntu.com/ubuntu/" - f" {release}-security" + f" {series}-security" ) sec_src_url = sec_url.replace("deb ", "# deb-src ") assert 3 == sources_list.count(sec_url) @@ -354,7 +357,7 @@ apt_pipelining: false """ -@pytest.mark.ubuntu +@pytest.mark.skipif(not IS_UBUNTU, reason="Apt usage") @pytest.mark.user_data(DISABLED_DATA) class TestDisabled: def test_disable_suites(self, class_client: IntegrationInstance): @@ -390,7 +393,7 @@ apt: """ -@pytest.mark.ubuntu +@pytest.mark.skipif(not IS_UBUNTU, reason="Apt usage") @pytest.mark.user_data(APT_PROXY_DATA) def test_apt_proxy(client: IntegrationInstance): """Test the apt proxy data gets written correctly.""" diff --git a/tests/integration_tests/modules/test_ca_certs.py b/tests/integration_tests/modules/test_ca_certs.py index 2baedda9..65f8f4d7 100644 --- a/tests/integration_tests/modules/test_ca_certs.py +++ b/tests/integration_tests/modules/test_ca_certs.py @@ -11,6 +11,7 @@ import os.path import pytest from tests.integration_tests.instances import IntegrationInstance +from tests.integration_tests.releases import IS_UBUNTU from tests.integration_tests.util import get_inactive_modules, verify_clean_log USER_DATA = """\ @@ -57,7 +58,9 @@ ca_certs: """ -@pytest.mark.ubuntu +@pytest.mark.skipif( + not IS_UBUNTU, reason="CA cert functionality is distro specific" +) @pytest.mark.user_data(USER_DATA) class TestCaCerts: def test_certs_updated(self, class_client: IntegrationInstance): diff --git a/tests/integration_tests/modules/test_combined.py b/tests/integration_tests/modules/test_combined.py index 8481b454..3c238e1f 100644 --- a/tests/integration_tests/modules/test_combined.py +++ b/tests/integration_tests/modules/test_combined.py @@ -16,9 +16,10 @@ import pytest import cloudinit.config from cloudinit.util import is_true -from tests.integration_tests.clouds import ImageSpecification from tests.integration_tests.decorators import retry from tests.integration_tests.instances import IntegrationInstance +from tests.integration_tests.integration_settings import PLATFORM +from tests.integration_tests.releases import CURRENT_RELEASE, IS_UBUNTU from tests.integration_tests.util import ( get_feature_flag_value, get_inactive_modules, @@ -83,7 +84,7 @@ timezone: US/Aleutian @pytest.mark.ci @pytest.mark.user_data(USER_DATA) class TestCombined: - @pytest.mark.ubuntu # Because netplan + @pytest.mark.skipif(not IS_UBUNTU, reason="Uses netplan") def test_netplan_permissions(self, class_client: IntegrationInstance): """ Test that netplan config file is generated with proper permissions @@ -303,19 +304,20 @@ class TestCombined: assert data["base64_encoded_keys"] == [] assert data["merged_cfg"] == "redacted for non-root user" - image_spec = ImageSpecification.from_os_image() - image_spec = ImageSpecification.from_os_image() - assert data["sys_info"]["dist"][0] == image_spec.os + assert data["sys_info"]["dist"][0] == CURRENT_RELEASE.os v1_data = data["v1"] assert re.match(r"\d\.\d+\.\d+-\d+", v1_data["kernel_release"]) - assert v1_data["variant"] == image_spec.os - assert v1_data["distro"] == image_spec.os - assert v1_data["distro_release"] == image_spec.release + assert v1_data["variant"] == CURRENT_RELEASE.os + assert v1_data["distro"] == CURRENT_RELEASE.os + assert v1_data["distro_release"] == CURRENT_RELEASE.series assert v1_data["machine"] == "x86_64" assert re.match(r"3.\d+\.\d+", v1_data["python_version"]) - @pytest.mark.lxd_container + @pytest.mark.skipif( + PLATFORM != "lxd_container", + reason="Test is LXD container specific", + ) def test_instance_json_lxd(self, class_client: IntegrationInstance): client = class_client instance_json_file = client.read_from_file( @@ -351,7 +353,7 @@ class TestCombined: assert v1_data["local_hostname"] == client.instance.name assert v1_data["region"] is None - @pytest.mark.lxd_vm + @pytest.mark.skipif(PLATFORM != "lxd_vm", reason="Test is LXD VM specific") def test_instance_json_lxd_vm(self, class_client: IntegrationInstance): client = class_client instance_json_file = client.read_from_file( @@ -396,7 +398,7 @@ class TestCombined: assert v1_data["local_hostname"] == client.instance.name assert v1_data["region"] is None - @pytest.mark.ec2 + @pytest.mark.skipif(PLATFORM != "ec2", reason="Test is ec2 specific") def test_instance_json_ec2(self, class_client: IntegrationInstance): client = class_client instance_json_file = client.read_from_file( @@ -419,7 +421,7 @@ class TestCombined: assert v1_data["local_hostname"].startswith("ip-") assert v1_data["region"] == client.cloud.cloud_instance.region - @pytest.mark.gce + @pytest.mark.skipif(PLATFORM != "gce", reason="Test is GCE specific") def test_instance_json_gce(self, class_client: IntegrationInstance): client = class_client instance_json_file = client.read_from_file( @@ -438,10 +440,13 @@ class TestCombined: assert v1_data["instance_id"] == client.instance.instance_id assert v1_data["local_hostname"] == client.instance.name - @pytest.mark.lxd_container - @pytest.mark.azure - @pytest.mark.gce - @pytest.mark.ec2 + @pytest.mark.skipif( + PLATFORM not in ["lxd_container", "azure", "gce", "ec2"], + reason=( + f"Test was written for {PLATFORM} but can likely run on " + "other platforms." + ), + ) def test_instance_cloud_id_across_reboot( self, class_client: IntegrationInstance ): diff --git a/tests/integration_tests/modules/test_disk_setup.py b/tests/integration_tests/modules/test_disk_setup.py index 1aa22ef0..084f03f5 100644 --- a/tests/integration_tests/modules/test_disk_setup.py +++ b/tests/integration_tests/modules/test_disk_setup.py @@ -7,6 +7,8 @@ from pycloudlib.lxd.instance import LXDInstance from cloudinit.subp import subp from tests.integration_tests.instances import IntegrationInstance +from tests.integration_tests.integration_settings import PLATFORM +from tests.integration_tests.releases import CURRENT_RELEASE, FOCAL, IS_UBUNTU from tests.integration_tests.util import verify_clean_log DISK_PATH = "/tmp/test_disk_setup_{}".format(uuid4()) @@ -52,8 +54,10 @@ mounts: @pytest.mark.user_data(ALIAS_USERDATA) @pytest.mark.lxd_setup.with_args(setup_and_mount_lxd_disk) -@pytest.mark.ubuntu -@pytest.mark.lxd_vm +@pytest.mark.skipif(not IS_UBUNTU, reason="Only ever tested on Ubuntu") +@pytest.mark.skipif( + PLATFORM != "lxd_vm", reason="Test requires additional mounted device" +) class TestDeviceAliases: """Test devices aliases work on disk setup/mount""" @@ -124,8 +128,10 @@ mounts: @pytest.mark.user_data(PARTPROBE_USERDATA) @pytest.mark.lxd_setup.with_args(setup_and_mount_lxd_disk) -@pytest.mark.ubuntu -@pytest.mark.lxd_vm +@pytest.mark.skipif(not IS_UBUNTU, reason="Only ever tested on Ubuntu") +@pytest.mark.skipif( + PLATFORM != "lxd_vm", reason="Test requires additional mounted device" +) class TestPartProbeAvailability: """Test disk setup works with partprobe @@ -149,9 +155,10 @@ class TestPartProbeAvailability: assert sdb["children"][0]["mountpoints"] == ["/mnt1"] assert sdb["children"][1]["mountpoints"] == ["/mnt2"] - # Not bionic because the LXD agent gets in the way of us - # changing the userdata - @pytest.mark.not_bionic + @pytest.mark.skipif( + CURRENT_RELEASE < FOCAL, + reason="LXD agent gets in the way of changing userdata", + ) def test_disk_setup_when_mounted( self, create_disk, client: IntegrationInstance ): diff --git a/tests/integration_tests/modules/test_growpart.py b/tests/integration_tests/modules/test_growpart.py index 67251817..b42c016c 100644 --- a/tests/integration_tests/modules/test_growpart.py +++ b/tests/integration_tests/modules/test_growpart.py @@ -8,6 +8,8 @@ from pycloudlib.lxd.instance import LXDInstance from cloudinit.subp import subp from tests.integration_tests.instances import IntegrationInstance +from tests.integration_tests.integration_settings import PLATFORM +from tests.integration_tests.releases import IS_UBUNTU DISK_PATH = "/tmp/test_disk_setup_{}".format(uuid4()) @@ -48,8 +50,10 @@ runcmd: @pytest.mark.user_data(ALIAS_USERDATA) @pytest.mark.lxd_setup.with_args(setup_and_mount_lxd_disk) -@pytest.mark.ubuntu -@pytest.mark.lxd_vm +@pytest.mark.skipif(not IS_UBUNTU, reason="Only ever tested on Ubuntu") +@pytest.mark.skipif( + PLATFORM != "lxd_vm", reason="Test requires additional mounted device" +) class TestGrowPart: """Test growpart""" diff --git a/tests/integration_tests/modules/test_hotplug.py b/tests/integration_tests/modules/test_hotplug.py index 0bad761e..120715e4 100644 --- a/tests/integration_tests/modules/test_hotplug.py +++ b/tests/integration_tests/modules/test_hotplug.py @@ -5,6 +5,8 @@ import pytest import yaml from tests.integration_tests.instances import IntegrationInstance +from tests.integration_tests.integration_settings import PLATFORM +from tests.integration_tests.releases import CURRENT_RELEASE, FOCAL USER_DATA = """\ #cloud-config @@ -40,11 +42,17 @@ def _get_ip_addr(client): return ips -@pytest.mark.openstack -# On Bionic, we traceback when attempting to detect the hotplugged -# device in the updated metadata. This is because Bionic is specifically -# configured not to provide network metadata. -@pytest.mark.not_bionic +@pytest.mark.skipif( + PLATFORM != "openstack", + reason=( + f"Test was written for {PLATFORM} but can likely run on " + "other platforms." + ), +) +@pytest.mark.skipif( + CURRENT_RELEASE < FOCAL, + reason="Openstack network metadata support was added in focal.", +) @pytest.mark.user_data(USER_DATA) def test_hotplug_add_remove(client: IntegrationInstance): ips_before = _get_ip_addr(client) @@ -85,7 +93,13 @@ def test_hotplug_add_remove(client: IntegrationInstance): ) -@pytest.mark.openstack +@pytest.mark.skipif( + PLATFORM != "openstack", + reason=( + f"Test was written for {PLATFORM} but can likely run on " + "other platforms." + ), +) def test_no_hotplug_in_userdata(client: IntegrationInstance): ips_before = _get_ip_addr(client) log = client.read_from_file("/var/log/cloud-init.log") diff --git a/tests/integration_tests/modules/test_keys_to_console.py b/tests/integration_tests/modules/test_keys_to_console.py index 5e2b3645..b10cbc60 100644 --- a/tests/integration_tests/modules/test_keys_to_console.py +++ b/tests/integration_tests/modules/test_keys_to_console.py @@ -6,6 +6,7 @@ import pytest from tests.integration_tests.decorators import retry from tests.integration_tests.instances import IntegrationInstance +from tests.integration_tests.integration_settings import PLATFORM from tests.integration_tests.util import get_console_log BLACKLIST_USER_DATA = """\ @@ -88,11 +89,13 @@ class TestKeysToConsoleDisabled: @pytest.mark.user_data(ENABLE_KEYS_TO_CONSOLE_USER_DATA) @retry(tries=30, delay=1) -@pytest.mark.ec2 -@pytest.mark.lxd_container -@pytest.mark.oci -@pytest.mark.openstack -# No Azure because no console log on Azure +@pytest.mark.skipif( + PLATFORM not in ["ec2", "lxd_container", "oci", "openstack"], + reason=( + "No Azure because no console log on Azure. " + "Other platforms need testing." + ), +) def test_duplicate_messaging_console_log(client: IntegrationInstance): """Test that output can be enabled disabled.""" assert ( diff --git a/tests/integration_tests/modules/test_lxd.py b/tests/integration_tests/modules/test_lxd.py index f84cdff6..40941ab4 100644 --- a/tests/integration_tests/modules/test_lxd.py +++ b/tests/integration_tests/modules/test_lxd.py @@ -8,8 +8,10 @@ import warnings import pytest import yaml -from tests.integration_tests.clouds import ImageSpecification, IntegrationCloud +from tests.integration_tests.clouds import IntegrationCloud from tests.integration_tests.instances import IntegrationInstance +from tests.integration_tests.integration_settings import PLATFORM +from tests.integration_tests.releases import CURRENT_RELEASE, FOCAL from tests.integration_tests.util import verify_clean_log BRIDGE_USER_DATA = """\ @@ -149,7 +151,9 @@ lxd: """ -@pytest.mark.no_container +@pytest.mark.skipif( + PLATFORM == "lxd_container", reason="Containers cannot run LXD" +) @pytest.mark.user_data(BRIDGE_USER_DATA) class TestLxdBridge: @pytest.mark.parametrize("binary_name", ["lxc", "lxd"]) @@ -202,7 +206,7 @@ def validate_preseed_storage_pools(client, preseed_cfg): def validate_preseed_projects(client: IntegrationInstance, preseed_cfg): # Support for projects by lxd init --preseed was added in lxd 4.12 # https://discuss.linuxcontainers.org/t/lxd-4-12-has-been-released/10424#projects-now-supported-by-lxd-init-dump-and-preseed-9 - if ImageSpecification.from_os_image().release in ("bionic", "focal"): + if CURRENT_RELEASE.series in ("bionic", "focal"): return for src_project in preseed_cfg.get("projects", []): proj_name = src_project["name"] @@ -226,17 +230,23 @@ def validate_preseed_projects(client: IntegrationInstance, preseed_cfg): assert project == src_project -@pytest.mark.no_container +@pytest.mark.skipif( + PLATFORM == "lxd_container", reason="Containers cannot manipulate storage" +) @pytest.mark.user_data(STORAGE_USER_DATA.format("btrfs")) def test_storage_btrfs(client): validate_storage(client, "btrfs-progs", "mkfs.btrfs") -@pytest.mark.no_container -@pytest.mark.not_bionic +@pytest.mark.skipif( + PLATFORM == "lxd_container", reason="Containers cannot manipulate LXD" +) +@pytest.mark.skipif( + CURRENT_RELEASE < FOCAL, reason="tested on Focal and later" +) def test_storage_preseed_btrfs(setup_image, session_cloud: IntegrationCloud): - cfg_image_spec = ImageSpecification.from_os_image() - if cfg_image_spec.release in ("bionic",): + # TODO: If test is marked as not bionic, why is there a bionic section? + if CURRENT_RELEASE.series in ("bionic",): nictype = "nictype: bridged" parent = "parent: lxdbr0" network = "" @@ -256,7 +266,9 @@ def test_storage_preseed_btrfs(setup_image, session_cloud: IntegrationCloud): validate_preseed_projects(client, preseed_cfg) -@pytest.mark.no_container +@pytest.mark.skipif( + PLATFORM == "lxd_container", reason="Containers cannot manipulate LVM" +) @pytest.mark.user_data(STORAGE_USER_DATA.format("lvm")) def test_storage_lvm(client): log = client.read_from_file("/var/log/cloud-init.log") @@ -281,17 +293,23 @@ def test_basic_preseed(client): validate_preseed_projects(client, preseed_cfg) -@pytest.mark.no_container +@pytest.mark.skipif( + PLATFORM == "lxd_container", reason="Containers cannot manipulate ZFS" +) @pytest.mark.user_data(STORAGE_USER_DATA.format("zfs")) def test_storage_zfs(client): validate_storage(client, "zfsutils-linux", "zpool") -@pytest.mark.no_container -@pytest.mark.not_bionic +@pytest.mark.skipif( + PLATFORM == "lxd_container", reason="Containers cannot manipulate LXD" +) +@pytest.mark.skipif( + CURRENT_RELEASE < FOCAL, reason="Tested on focal and later" +) def test_storage_preseed_zfs(setup_image, session_cloud: IntegrationCloud): - cfg_image_spec = ImageSpecification.from_os_image() - if cfg_image_spec.release in ("bionic",): + # TODO: If test is marked as not bionic, why is there a bionic section? + if CURRENT_RELEASE.series in ("bionic",): nictype = "nictype: bridged" parent = "parent: lxdbr0" network = "" diff --git a/tests/integration_tests/modules/test_package_update_upgrade_install.py b/tests/integration_tests/modules/test_package_update_upgrade_install.py index d668d81c..c54365b8 100644 --- a/tests/integration_tests/modules/test_package_update_upgrade_install.py +++ b/tests/integration_tests/modules/test_package_update_upgrade_install.py @@ -16,6 +16,8 @@ import re import pytest +from tests.integration_tests.releases import IS_UBUNTU + USER_DATA = """\ #cloud-config packages: @@ -26,7 +28,7 @@ package_upgrade: true """ -@pytest.mark.ubuntu +@pytest.mark.skipif(not IS_UBUNTU, reason="Uses Apt") @pytest.mark.user_data(USER_DATA) class TestPackageUpdateUpgradeInstall: def assert_package_installed(self, pkg_out, name, version=None): diff --git a/tests/integration_tests/modules/test_persistence.py b/tests/integration_tests/modules/test_persistence.py index 9979cc06..67f48284 100644 --- a/tests/integration_tests/modules/test_persistence.py +++ b/tests/integration_tests/modules/test_persistence.py @@ -5,6 +5,7 @@ from pathlib import Path import pytest from tests.integration_tests.instances import IntegrationInstance +from tests.integration_tests.integration_settings import PLATFORM from tests.integration_tests.util import ( ASSETS_DIR, verify_ordered_items_in_text, @@ -14,7 +15,9 @@ PICKLE_PATH = Path("/var/lib/cloud/instance/obj.pkl") TEST_PICKLE = ASSETS_DIR / "trusty_with_mime.pkl" -@pytest.mark.lxd_container +@pytest.mark.skipif( + PLATFORM != "lxd_container", reason=f"Not tested on {PLATFORM}" +) def test_log_message_on_missing_version_file(client: IntegrationInstance): client.push_file(TEST_PICKLE, PICKLE_PATH) client.restart() diff --git a/tests/integration_tests/modules/test_power_state_change.py b/tests/integration_tests/modules/test_power_state_change.py index 5cd19764..dd5bd478 100644 --- a/tests/integration_tests/modules/test_power_state_change.py +++ b/tests/integration_tests/modules/test_power_state_change.py @@ -9,6 +9,8 @@ import pytest from tests.integration_tests.clouds import IntegrationCloud from tests.integration_tests.instances import IntegrationInstance +from tests.integration_tests.integration_settings import PLATFORM +from tests.integration_tests.releases import IS_UBUNTU from tests.integration_tests.util import verify_ordered_items_in_text USER_DATA = """\ @@ -51,8 +53,11 @@ def _can_connect(instance): # run anywhere, I can only get it to run in an lxd container, and even then # occasionally some timing issues will crop up. @pytest.mark.unstable -@pytest.mark.ubuntu -@pytest.mark.lxd_container +@pytest.mark.skipif(not IS_UBUNTU, reason="Only ever tested on Ubuntu") +@pytest.mark.skipif( + PLATFORM != "lxd_container", + reason="Test is unstable but most stable on lxd containers", +) class TestPowerChange: @pytest.mark.parametrize( "mode,delay,timeout,expected", diff --git a/tests/integration_tests/modules/test_set_password.py b/tests/integration_tests/modules/test_set_password.py index 765dd30c..48fe536c 100644 --- a/tests/integration_tests/modules/test_set_password.py +++ b/tests/integration_tests/modules/test_set_password.py @@ -11,8 +11,8 @@ only specify one user-data per instance. import pytest import yaml -from tests.integration_tests.clouds import ImageSpecification from tests.integration_tests.decorators import retry +from tests.integration_tests.releases import CURRENT_RELEASE, IS_UBUNTU from tests.integration_tests.util import get_console_log COMMON_USER_DATA = """\ @@ -182,7 +182,7 @@ class Mixin: def test_sshd_config_file(self, class_client): """Test that SSH config is written in the correct file.""" - if ImageSpecification.from_os_image().release in {"bionic"}: + if CURRENT_RELEASE.series == "bionic": sshd_file_target = "/etc/ssh/sshd_config" else: sshd_file_target = "/etc/ssh/sshd_config.d/50-cloud-init.conf" @@ -191,7 +191,7 @@ class Mixin: # We look for the exact line match, to avoid a commented line matching assert "PasswordAuthentication yes" in sshd_config.splitlines() - @pytest.mark.ubuntu + @pytest.mark.skipif(not IS_UBUNTU, reason="Use of systemctl") def test_check_ssh_service(self, class_client): """Ensure we check the sshd status because we modified the config""" log = class_client.read_from_file("/var/log/cloud-init.log") diff --git a/tests/integration_tests/modules/test_ssh_keys_provided.py b/tests/integration_tests/modules/test_ssh_keys_provided.py index b6069376..576b78eb 100644 --- a/tests/integration_tests/modules/test_ssh_keys_provided.py +++ b/tests/integration_tests/modules/test_ssh_keys_provided.py @@ -9,7 +9,7 @@ system. import pytest -from tests.integration_tests.clouds import ImageSpecification +from tests.integration_tests.releases import CURRENT_RELEASE USER_DATA = """\ #cloud-config @@ -143,7 +143,7 @@ class TestSshKeysProvided: "HostCertificate /etc/ssh/ssh_host_rsa_key-cert.pub", "HostCertificate /etc/ssh/ssh_host_ed25519_key-cert.pub", ) - if ImageSpecification.from_os_image().release in {"bionic"}: + if CURRENT_RELEASE.series == "bionic": sshd_config_path = "/etc/ssh/sshd_config" else: sshd_config_path = "/etc/ssh/sshd_config.d/50-cloud-init.conf" diff --git a/tests/integration_tests/modules/test_ssh_keysfile.py b/tests/integration_tests/modules/test_ssh_keysfile.py index 8330a1ce..628ad91f 100644 --- a/tests/integration_tests/modules/test_ssh_keysfile.py +++ b/tests/integration_tests/modules/test_ssh_keysfile.py @@ -4,8 +4,8 @@ import paramiko import pytest from paramiko.ssh_exception import SSHException -from tests.integration_tests.clouds import ImageSpecification from tests.integration_tests.instances import IntegrationInstance +from tests.integration_tests.releases import CURRENT_RELEASE, IS_UBUNTU from tests.integration_tests.util import get_test_rsa_keypair TEST_USER1_KEYS = get_test_rsa_keypair("test1") @@ -91,7 +91,7 @@ def common_verify(client, expected_keys): home_dir = "/home/{}".format(user) # Home permissions aren't consistent between releases. On ubuntu # this can change to 750 once focal is unsupported. - if ImageSpecification.from_os_image().release in ("bionic", "focal"): + if CURRENT_RELEASE.series in ("bionic", "focal"): home_perms = "755" else: home_perms = "750" @@ -125,7 +125,9 @@ def common_verify(client, expected_keys): DEFAULT_KEYS_USERDATA = _USERDATA.format(bootcmd='""') -@pytest.mark.ubuntu +@pytest.mark.skipif( + not IS_UBUNTU, reason="Tests permissions specific to Ubuntu releases" +) @pytest.mark.user_data(DEFAULT_KEYS_USERDATA) def test_authorized_keys_default(client: IntegrationInstance): expected_keys = [ @@ -154,7 +156,9 @@ AUTHORIZED_KEYS2_USERDATA = _USERDATA.format( ) -@pytest.mark.ubuntu +@pytest.mark.skipif( + not IS_UBUNTU, reason="Tests permissions specific to Ubuntu releases" +) @pytest.mark.user_data(AUTHORIZED_KEYS2_USERDATA) def test_authorized_keys2(client: IntegrationInstance): expected_keys = [ @@ -183,7 +187,9 @@ NESTED_KEYS_USERDATA = _USERDATA.format( ) -@pytest.mark.ubuntu +@pytest.mark.skipif( + not IS_UBUNTU, reason="Tests permissions specific to Ubuntu releases" +) @pytest.mark.user_data(NESTED_KEYS_USERDATA) def test_nested_keys(client: IntegrationInstance): expected_keys = [ @@ -204,7 +210,9 @@ EXTERNAL_KEYS_USERDATA = _USERDATA.format( ) -@pytest.mark.ubuntu +@pytest.mark.skipif( + not IS_UBUNTU, reason="Tests permissions specific to Ubuntu releases" +) @pytest.mark.user_data(EXTERNAL_KEYS_USERDATA) def test_external_keys(client: IntegrationInstance): expected_keys = [ diff --git a/tests/integration_tests/modules/test_ubuntu_advantage.py b/tests/integration_tests/modules/test_ubuntu_advantage.py index 547ec9e7..0b3ed2d7 100644 --- a/tests/integration_tests/modules/test_ubuntu_advantage.py +++ b/tests/integration_tests/modules/test_ubuntu_advantage.py @@ -5,12 +5,20 @@ import os import pytest from pycloudlib.cloud import ImageType -from tests.integration_tests.clouds import ImageSpecification, IntegrationCloud +from tests.integration_tests.clouds import IntegrationCloud from tests.integration_tests.conftest import get_validated_source from tests.integration_tests.instances import ( CloudInitSource, IntegrationInstance, ) +from tests.integration_tests.integration_settings import PLATFORM +from tests.integration_tests.releases import ( + BIONIC, + CURRENT_RELEASE, + FOCAL, + IS_UBUNTU, + JAMMY, +) from tests.integration_tests.util import verify_clean_log LOG = logging.getLogger("integration_testing.test_ubuntu_advantage") @@ -109,7 +117,7 @@ def get_services_status(client: IntegrationInstance) -> dict: @pytest.mark.adhoc -@pytest.mark.ubuntu +@pytest.mark.skipif(not IS_UBUNTU, reason="Test is Ubuntu specific") @pytest.mark.skipif( not CLOUD_INIT_UA_TOKEN, reason="CLOUD_INIT_UA_TOKEN env var not provided" ) @@ -136,12 +144,11 @@ class TestUbuntuAdvantage: def maybe_install_cloud_init(session_cloud: IntegrationCloud): - cfg_image_spec = ImageSpecification.from_os_image() source = get_validated_source(session_cloud) launch_kwargs = { "image_id": session_cloud.cloud_instance.daily_image( - cfg_image_spec.image_id, image_type=ImageType.PRO + CURRENT_RELEASE.image_id, image_type=ImageType.PRO ) } @@ -192,16 +199,16 @@ def maybe_install_cloud_init(session_cloud: IntegrationCloud): return {"image_id": session_cloud.snapshot_id} -@pytest.mark.azure -@pytest.mark.ec2 -@pytest.mark.gce -@pytest.mark.ubuntu +@pytest.mark.skipif( + not all([IS_UBUNTU, CURRENT_RELEASE in [BIONIC, FOCAL, JAMMY]]), + reason="Test runs on Ubuntu LTS releases only", +) +@pytest.mark.skipif( + PLATFORM not in ["azure", "ec2", "gce"], + reason=f"Pro isn't offered on {PLATFORM}.", +) class TestUbuntuAdvantagePro: def test_custom_services(self, session_cloud: IntegrationCloud): - release = ImageSpecification.from_os_image().release - if release not in {"bionic", "focal", "jammy"}: - pytest.skip(f"Cannot run on non LTS release: {release}") - launch_kwargs = maybe_install_cloud_init(session_cloud) with session_cloud.launch( user_data=AUTO_ATTACH_CUSTOM_SERVICES, diff --git a/tests/integration_tests/modules/test_ubuntu_autoinstall.py b/tests/integration_tests/modules/test_ubuntu_autoinstall.py index d340afc5..ad077977 100644 --- a/tests/integration_tests/modules/test_ubuntu_autoinstall.py +++ b/tests/integration_tests/modules/test_ubuntu_autoinstall.py @@ -2,6 +2,8 @@ import pytest +from tests.integration_tests.releases import IS_UBUNTU + USER_DATA = """\ #cloud-config autoinstall: @@ -16,7 +18,7 @@ snap: LOG_MSG = "Valid autoinstall schema. Config will be processed by subiquity" -@pytest.mark.ubuntu +@pytest.mark.skipif(not IS_UBUNTU, reason="Test is Ubuntu specific") @pytest.mark.user_data(USER_DATA) class TestUbuntuAutoinstall: def test_autoinstall_schema_valid_when_snap_present(self, class_client): diff --git a/tests/integration_tests/modules/test_ubuntu_drivers.py b/tests/integration_tests/modules/test_ubuntu_drivers.py index 4fbfba3c..66649c17 100644 --- a/tests/integration_tests/modules/test_ubuntu_drivers.py +++ b/tests/integration_tests/modules/test_ubuntu_drivers.py @@ -3,6 +3,7 @@ import re import pytest from tests.integration_tests.clouds import IntegrationCloud +from tests.integration_tests.integration_settings import PLATFORM from tests.integration_tests.util import verify_clean_log USER_DATA = """\ @@ -16,7 +17,7 @@ drivers: @pytest.mark.adhoc # Expensive instance type -@pytest.mark.oci +@pytest.mark.skipif(PLATFORM != "oci", reason="Test is OCI specific") def test_ubuntu_drivers_installed(session_cloud: IntegrationCloud): with session_cloud.launch( launch_kwargs={"instance_type": "VM.GPU2.1"}, user_data=USER_DATA diff --git a/tests/integration_tests/modules/test_user_events.py b/tests/integration_tests/modules/test_user_events.py index e4a4241f..79d88022 100644 --- a/tests/integration_tests/modules/test_user_events.py +++ b/tests/integration_tests/modules/test_user_events.py @@ -9,6 +9,7 @@ import pytest import yaml from tests.integration_tests.instances import IntegrationInstance +from tests.integration_tests.integration_settings import PLATFORM def _add_dummy_bridge_to_netplan(client: IntegrationInstance): @@ -26,12 +27,11 @@ def _add_dummy_bridge_to_netplan(client: IntegrationInstance): client.write_to_file("/etc/netplan/50-cloud-init.yaml", dumped_netplan) -@pytest.mark.lxd_container -@pytest.mark.lxd_vm -@pytest.mark.ec2 -@pytest.mark.gce -@pytest.mark.oci -@pytest.mark.openstack +@pytest.mark.skipif( + PLATFORM + not in ["lxd_container", "lxd_vm", "ec2", "gce", "oci", "openstack"], + reason="Default boot events testing is datasource specific", +) def test_boot_event_disabled_by_default(client: IntegrationInstance): log = client.read_from_file("/var/log/cloud-init.log") if "network config is disabled" in log: @@ -92,7 +92,13 @@ def _test_network_config_applied_on_reboot(client: IntegrationInstance): assert "dummy0" not in client.execute("ls /sys/class/net") -@pytest.mark.azure +@pytest.mark.skipif( + PLATFORM != "azure", + reason=( + f"{PLATFORM} doesn't support updates every boot event by default " + "(or hasn't been testing for it)." + ), +) def test_boot_event_enabled_by_default(client: IntegrationInstance): _test_network_config_applied_on_reboot(client) diff --git a/tests/integration_tests/modules/test_users_groups.py b/tests/integration_tests/modules/test_users_groups.py index 91eca345..cc6a4bfb 100644 --- a/tests/integration_tests/modules/test_users_groups.py +++ b/tests/integration_tests/modules/test_users_groups.py @@ -8,8 +8,8 @@ import re import pytest -from tests.integration_tests.clouds import ImageSpecification from tests.integration_tests.instances import IntegrationInstance +from tests.integration_tests.releases import CURRENT_RELEASE, IS_UBUNTU, JAMMY from tests.integration_tests.util import verify_clean_log USER_DATA = """\ @@ -56,7 +56,7 @@ class TestUsersGroups: confirms that they have been configured correctly in the system under test. """ - @pytest.mark.ubuntu + @pytest.mark.skipif(not IS_UBUNTU, reason="Test assumes 'ubuntu' user") @pytest.mark.parametrize( "getent_args,regex", [ @@ -108,6 +108,10 @@ class TestUsersGroups: @pytest.mark.user_data(USER_DATA) +@pytest.mark.skipif( + CURRENT_RELEASE < JAMMY, + reason="Requires version of sudo not available in older releases", +) def test_sudoers_includedir(client: IntegrationInstance): """Ensure we don't add additional #includedir to sudoers. @@ -117,13 +121,6 @@ def test_sudoers_includedir(client: IntegrationInstance): https://github.com/canonical/cloud-init/pull/783 """ - if ImageSpecification.from_os_image().release in [ - "bionic", - "focal", - ]: - raise pytest.skip( - "Test requires version of sudo installed on groovy and later" - ) client.execute("sed -i 's/#include/@include/g' /etc/sudoers") sudoers = client.read_from_file("/etc/sudoers") diff --git a/tests/integration_tests/modules/test_version_change.py b/tests/integration_tests/modules/test_version_change.py index 3168cd60..9df436b8 100644 --- a/tests/integration_tests/modules/test_version_change.py +++ b/tests/integration_tests/modules/test_version_change.py @@ -3,6 +3,7 @@ from pathlib import Path import pytest from tests.integration_tests.instances import IntegrationInstance +from tests.integration_tests.integration_settings import PLATFORM from tests.integration_tests.util import ASSETS_DIR, verify_clean_log PICKLE_PATH = Path("/var/lib/cloud/instance/obj.pkl") @@ -41,12 +42,12 @@ def test_reboot_without_version_change(client: IntegrationInstance): ) -@pytest.mark.ec2 -@pytest.mark.gce -@pytest.mark.oci -@pytest.mark.openstack -@pytest.mark.lxd_container -@pytest.mark.lxd_vm +@pytest.mark.skipif( + PLATFORM + not in ["ec2", "gce", "oci", "openstack", "lxd_container", "lxd_vm"], + reason=f"Test hasn't been tested on {PLATFORM}.", +) +# TODO: The below comment likely isn't true anymore # No Azure because the cache gets purged every reboot, so we'll never # get to the point where we need to purge cache due to version change def test_cache_purged_on_version_change(client: IntegrationInstance): diff --git a/tests/integration_tests/modules/test_wireguard.py b/tests/integration_tests/modules/test_wireguard.py index e658a9df..e685a269 100644 --- a/tests/integration_tests/modules/test_wireguard.py +++ b/tests/integration_tests/modules/test_wireguard.py @@ -4,6 +4,8 @@ from pycloudlib.lxd.instance import LXDInstance from cloudinit.subp import subp from tests.integration_tests.instances import IntegrationInstance +from tests.integration_tests.integration_settings import PLATFORM +from tests.integration_tests.releases import IS_UBUNTU ASCII_TEXT = "ASCII text" @@ -55,13 +57,13 @@ def load_wireguard_kernel_module_lxd(instance: LXDInstance): @pytest.mark.ci @pytest.mark.user_data(USER_DATA) -@pytest.mark.lxd_vm -@pytest.mark.gce -@pytest.mark.ec2 -@pytest.mark.azure -@pytest.mark.openstack -@pytest.mark.oci -@pytest.mark.ubuntu +@pytest.mark.skipif( + not IS_UBUNTU, reason="Hasn't been tested on other distros" +) +@pytest.mark.skipif( + PLATFORM not in ["lxd_vm", "gce", "ec2", "azure", "openstack", "oci"], + reason=f"Test hasn't been tested on {PLATFORM}", +) class TestWireguard: @pytest.mark.parametrize( "cmd,expected_out", @@ -119,8 +121,10 @@ class TestWireguard: @pytest.mark.ci @pytest.mark.user_data(USER_DATA) @pytest.mark.lxd_setup.with_args(load_wireguard_kernel_module_lxd) -@pytest.mark.lxd_container -@pytest.mark.ubuntu +@pytest.mark.skipif( + PLATFORM != "lxd_container", reason=f"Not testing on {PLATFORM}" +) +@pytest.mark.skipif(not IS_UBUNTU, reason="Has only been tested on Ubuntu") class TestWireguardWithoutKmod: def test_wireguard_tools_installed( self, class_client: IntegrationInstance diff --git a/tests/integration_tests/releases.py b/tests/integration_tests/releases.py new file mode 100644 index 00000000..b2ab9cee --- /dev/null +++ b/tests/integration_tests/releases.py @@ -0,0 +1,92 @@ +import functools +import logging +from typing import Optional + +from packaging import version + +from cloudinit import subp +from cloudinit.subp import ProcessExecutionError +from tests.integration_tests import integration_settings + +log = logging.getLogger("integration_testing") + + +def get_all_ubuntu_series() -> list: + """Use distro-info-data's ubuntu.csv to get a list of Ubuntu series""" + out = "" + try: + out, _err = subp.subp(["ubuntu-distro-info", "-a"]) + except ProcessExecutionError: + log.info( + "ubuntu-distro-info (from the distro-info package) must be" + " installed to guess Ubuntu os/release" + ) + return out.splitlines() + + +def ubuntu_version_from_series(series) -> str: + try: + out, _err = subp.subp( + ["ubuntu-distro-info", "--release", "--series", series] + ) + except subp.ProcessExecutionError as e: + raise ValueError( + f"'{series}' is not a recognized Ubuntu release" + ) from e + return out.strip().rstrip(" LTS") + + +@functools.total_ordering +class Release: + def __init__( + self, + os: str, + series: str, + version: str, + image_id: Optional[str] = None, + ): + self.os = os + self.series = series + self.version = version + self.image_id = image_id + + def __repr__(self): + return f"Release({self.os}, {self.version})" + + def __lt__(self, other: "Release"): + if self.os != other.os: + raise ValueError(f"{self.os} cannot be compared to {other.os}!") + return version.parse(self.version) < version.parse(other.version) + + @classmethod + def from_os_image( + cls, + os_image: str = integration_settings.OS_IMAGE, + ) -> "Release": + """Get the individual parts from an OS_IMAGE definition. + + Returns a namedtuple containing id, os, and release of the image.""" + parts = os_image.split("::", 2) + if len(parts) == 1: + image_id = None + os = "ubuntu" + series = parts[0] + version = ubuntu_version_from_series(series) + elif len(parts) == 4: + image_id, os, series, version = parts + else: + raise ValueError( + "OS_IMAGE must either contain release name or be in the form " + "of <image_id>[::<os>[::<release>[::<version>]]]" + ) + return cls(os, series, version, image_id) + + +BIONIC = Release("ubuntu", "bionic", "18.04") +FOCAL = Release("ubuntu", "focal", "20.04") +JAMMY = Release("ubuntu", "jammy", "22.04") +KINETIC = Release("ubuntu", "kinetic", "22.10") +LUNAR = Release("ubuntu", "lunar", "23.04") + +CURRENT_RELEASE = Release.from_os_image() +IS_UBUNTU = CURRENT_RELEASE.os == "ubuntu" diff --git a/tests/integration_tests/test_paths.py b/tests/integration_tests/test_paths.py index 14513c82..b63da5a4 100644 --- a/tests/integration_tests/test_paths.py +++ b/tests/integration_tests/test_paths.py @@ -10,6 +10,7 @@ from cloudinit.cmd.devel.logs import ( INSTALLER_APPORT_SENSITIVE_FILES, ) from tests.integration_tests.instances import IntegrationInstance +from tests.integration_tests.releases import CURRENT_RELEASE, FOCAL from tests.integration_tests.util import verify_clean_log DEFAULT_CLOUD_DIR = "/var/lib/cloud" @@ -111,7 +112,10 @@ class TestHonorCloudDir: # because the test ensures nothing is running under /var/lib/cloud. # Since LXD is doing this and not cloud-init, we should just not run # on Bionic to avoid it. - @pytest.mark.not_bionic + @pytest.mark.skipif( + CURRENT_RELEASE < FOCAL, + reason="LXD inserts conflicting setup on releases prior to focal", + ) def test_honor_cloud_dir(self, custom_client: IntegrationInstance): """Integration test for LP: #1976564 diff --git a/tests/integration_tests/test_upgrade.py b/tests/integration_tests/test_upgrade.py index 5ef82e88..787bda44 100644 --- a/tests/integration_tests/test_upgrade.py +++ b/tests/integration_tests/test_upgrade.py @@ -4,8 +4,10 @@ import os import pytest -from tests.integration_tests.clouds import ImageSpecification, IntegrationCloud +from tests.integration_tests.clouds import IntegrationCloud from tests.integration_tests.conftest import get_validated_source +from tests.integration_tests.integration_settings import PLATFORM +from tests.integration_tests.releases import CURRENT_RELEASE, FOCAL, IS_UBUNTU from tests.integration_tests.util import verify_clean_log LOG = logging.getLogger("integration_testing.test_upgrade") @@ -41,23 +43,20 @@ hostname: SRU-worked """ +# The issues that we see on Bionic VMs don't appear anywhere +# else, including when calling KVM directly. It likely has to +# do with the extra lxd-agent setup happening on bionic. +# Given that we still have Bionic covered on all other platforms, +# the risk of skipping bionic here seems low enough. +@pytest.mark.skipif( + PLATFORM == "lxd_vm" and CURRENT_RELEASE < FOCAL, + reason="Update test doesn't run on Bionic LXD VMs", +) def test_clean_boot_of_upgraded_package(session_cloud: IntegrationCloud): source = get_validated_source(session_cloud) if not source.installs_new_version(): pytest.skip(UNSUPPORTED_INSTALL_METHOD_MSG.format(source)) return # type checking doesn't understand that skip raises - if ( - ImageSpecification.from_os_image().release == "bionic" - and session_cloud.settings.PLATFORM == "lxd_vm" - ): - # The issues that we see on Bionic VMs don't appear anywhere - # else, including when calling KVM directly. It likely has to - # do with the extra lxd-agent setup happening on bionic. - # Given that we still have Bionic covered on all other platforms, - # the risk of skipping bionic here seems low enough. - pytest.skip("Upgrade test doesn't run on LXD VMs and bionic") - return - launch_kwargs = { "image_id": session_cloud.initial_image_id, } @@ -172,11 +171,11 @@ def test_clean_boot_of_upgraded_package(session_cloud: IntegrationCloud): @pytest.mark.ci -@pytest.mark.ubuntu +@pytest.mark.skipif(not IS_UBUNTU, reason="Only ever tested on Ubuntu") def test_subsequent_boot_of_upgraded_package(session_cloud: IntegrationCloud): source = get_validated_source(session_cloud) if not source.installs_new_version(): - if os.environ.get("TRAVIS"): + if os.environ.get("GITHUB_ACTIONS"): # If this isn't running on CI, we should know pytest.fail(UNSUPPORTED_INSTALL_METHOD_MSG.format(source)) else: diff --git a/tests/unittests/config/test_schema.py b/tests/unittests/config/test_schema.py index 8276511d..0e49dbf3 100644 --- a/tests/unittests/config/test_schema.py +++ b/tests/unittests/config/test_schema.py @@ -51,6 +51,7 @@ from tests.unittests.helpers import ( mock, skipUnlessHypothesisJsonSchema, skipUnlessJsonSchema, + skipUnlessJsonSchemaVersionGreaterThan, ) from tests.unittests.util import FakeDataSource @@ -422,7 +423,7 @@ class TestValidateCloudConfigSchema: context_mgr.value ) - @skipUnlessJsonSchema() + @skipUnlessJsonSchemaVersionGreaterThan(version=(3, 0, 0)) def test_validateconfig_strict_metaschema_do_not_raise_exception( self, caplog ): @@ -1770,7 +1771,7 @@ class TestStrictMetaschema: else: logging.warning("module %s has no schema definition", name) - @skipUnlessJsonSchema() + @skipUnlessJsonSchemaVersionGreaterThan(version=(3, 0, 0)) def test_validate_bad_module(self): """Throw exception by default, don't throw if throw=False diff --git a/tests/unittests/helpers.py b/tests/unittests/helpers.py index 32503fb8..0656da33 100644 --- a/tests/unittests/helpers.py +++ b/tests/unittests/helpers.py @@ -491,9 +491,23 @@ try: import jsonschema assert jsonschema # avoid pyflakes error F401: import unused + _jsonschema_version = tuple( + int(part) for part in jsonschema.__version__.split(".") # type: ignore + ) _missing_jsonschema_dep = False except ImportError: _missing_jsonschema_dep = True + _jsonschema_version = (0, 0, 0) + + +def skipUnlessJsonSchemaVersionGreaterThan(version=(0, 0, 0)): + return skipIf( + _jsonschema_version <= version, + reason=( + f"python3-jsonschema {_jsonschema_version} not greater than" + f" {version}" + ), + ) def skipUnlessJsonSchema(): diff --git a/tests/unittests/net/test_dhcp.py b/tests/unittests/net/test_dhcp.py index 2b680e1f..4c30dd64 100644 --- a/tests/unittests/net/test_dhcp.py +++ b/tests/unittests/net/test_dhcp.py @@ -802,7 +802,7 @@ class TestEphemeralDhcpNoNetworkSetup(ResponsesTestCase): ) as lease: self.assertEqual(fake_lease, lease) # Ensure that dhcp discovery occurs - m_dhcp.called_once_with() + m_dhcp.assert_called_once() @pytest.mark.parametrize( diff --git a/tests/unittests/sources/azure/test_imds.py b/tests/unittests/sources/azure/test_imds.py index 03f66502..9a8aad88 100644 --- a/tests/unittests/sources/azure/test_imds.py +++ b/tests/unittests/sources/azure/test_imds.py @@ -549,7 +549,6 @@ class TestFetchReprovisionData: "terminal_error", [ requests.ConnectionError("Fake connection error"), - requests.Timeout("Fake connection timeout"), ], ) def test_retry_until_failure( diff --git a/tests/unittests/sources/test_azure.py b/tests/unittests/sources/test_azure.py index 9815c913..6c3e9281 100644 --- a/tests/unittests/sources/test_azure.py +++ b/tests/unittests/sources/test_azure.py @@ -2921,8 +2921,8 @@ class TestPreprovisioningHotAttachNics(CiTestCase): # Re-run tests to verify max connection error retries. m_request.reset_mock() m_request.side_effect = [ - requests.Timeout("Fake connection timeout") - ] * 9 + [requests.ConnectionError("Fake Network Unreachable")] * 9 + requests.ConnectionError("Fake Network Unreachable") + ] * 15 dsa = dsaz.DataSourceAzure({}, distro=distro, paths=self.paths) diff --git a/tests/unittests/test_util.py b/tests/unittests/test_util.py index 865f202a..e5243ef3 100644 --- a/tests/unittests/test_util.py +++ b/tests/unittests/test_util.py @@ -854,64 +854,66 @@ class TestBlkid(CiTestCase): ) -@mock.patch("cloudinit.subp.which") -@mock.patch("cloudinit.subp.subp") +@mock.patch("cloudinit.util.subp.which") +@mock.patch("cloudinit.util.subp.subp") class TestUdevadmSettle(CiTestCase): - def test_with_no_params(self, m_which, m_subp): + def test_with_no_params(self, m_subp, m_which): """called with no parameters.""" m_which.side_effect = lambda m: m in ("udevadm",) util.udevadm_settle() - m_subp.called_once_with(mock.call(["udevadm", "settle"])) + m_subp.assert_called_once_with(["udevadm", "settle"]) - def test_udevadm_not_present(self, m_which, m_subp): + def test_udevadm_not_present(self, m_subp, m_which): """where udevadm program does not exist should not invoke subp.""" m_which.side_effect = lambda m: m in ("",) util.udevadm_settle() - m_subp.called_once_with(["which", "udevadm"]) + m_which.assert_called_once_with("udevadm") + m_subp.assert_not_called() - def test_with_exists_and_not_exists(self, m_which, m_subp): + def test_with_exists_and_not_exists(self, m_subp, m_which): """with exists=file where file does not exist should invoke subp.""" m_which.side_effect = lambda m: m in ("udevadm",) mydev = self.tmp_path("mydev") util.udevadm_settle(exists=mydev) - m_subp.called_once_with( + m_subp.assert_called_once_with( ["udevadm", "settle", "--exit-if-exists=%s" % mydev] ) - def test_with_exists_and_file_exists(self, m_which, m_subp): + def test_with_exists_and_file_exists(self, m_subp, m_which): """with exists=file where file does exist should only invoke subp once for 'which' call.""" m_which.side_effect = lambda m: m in ("udevadm",) mydev = self.tmp_path("mydev") util.write_file(mydev, "foo\n") util.udevadm_settle(exists=mydev) - m_subp.called_once_with(["which", "udevadm"]) + m_which.assert_called_once_with("udevadm") + m_subp.assert_not_called() - def test_with_timeout_int(self, m_which, m_subp): + def test_with_timeout_int(self, m_subp, m_which): """timeout can be an integer.""" m_which.side_effect = lambda m: m in ("udevadm",) timeout = 9 util.udevadm_settle(timeout=timeout) - m_subp.called_once_with( + m_subp.assert_called_once_with( ["udevadm", "settle", "--timeout=%s" % timeout] ) - def test_with_timeout_string(self, m_which, m_subp): + def test_with_timeout_string(self, m_subp, m_which): """timeout can be a string.""" m_which.side_effect = lambda m: m in ("udevadm",) timeout = "555" util.udevadm_settle(timeout=timeout) - m_subp.called_once_with( + m_subp.assert_called_once_with( ["udevadm", "settle", "--timeout=%s" % timeout] ) - def test_with_exists_and_timeout(self, m_which, m_subp): + def test_with_exists_and_timeout(self, m_subp, m_which): """test call with both exists and timeout.""" m_which.side_effect = lambda m: m in ("udevadm",) mydev = self.tmp_path("mydev") timeout = "3" - util.udevadm_settle(exists=mydev) - m_subp.called_once_with( + util.udevadm_settle(exists=mydev, timeout=timeout) + m_subp.assert_called_once_with( [ "udevadm", "settle", @@ -920,7 +922,7 @@ class TestUdevadmSettle(CiTestCase): ] ) - def test_subp_exception_raises_to_caller(self, m_which, m_subp): + def test_subp_exception_raises_to_caller(self, m_subp, m_which): m_which.side_effect = lambda m: m in ("udevadm",) m_subp.side_effect = subp.ProcessExecutionError("BOOM") self.assertRaises(subp.ProcessExecutionError, util.udevadm_settle) @@ -296,27 +296,16 @@ markers = adhoc: only run on adhoc basis, not in any CI environment (travis or jenkins) allow_all_subp: allow all subp usage (disable_subp_usage) allow_subp_for: allow subp usage for the given commands (disable_subp_usage) - azure: test will only run on Azure platform ci: run this integration test as part of CI test runs ds_sys_cfg: a sys_cfg dict to be used by datasource fixtures - ec2: test will only run on EC2 platform - gce: test will only run on GCE platform hypothesis_slow: hypothesis test too slow to run as unit test - ibm: test will only run on IBM platform instance_name: the name to be used for the test instance integration_cloud_args: args for IntegrationCloud customization is_iscsi: whether is an instance has iscsi net cfg or not lxd_config_dict: set the config_dict passed on LXD instance creation - lxd_container: test will only run in LXD container lxd_setup: specify callable to be called between init and start lxd_use_exec: `execute` will use `lxc exec` instead of SSH - lxd_vm: test will only run in LXD VM - no_container: test cannot run in a container - not_bionic: test cannot run on the bionic release - oci: test will only run on OCI platform - openstack: test will only run on openstack platform serial: tests that do not work in parallel, skipped with py3-fast - ubuntu: this test should run on Ubuntu unstable: skip this test because it is flakey user_data: the user data to be passed to the test instance allow_dns_lookup: disable autochecking for host network configuration |