def test_security(self, class_client: IntegrationInstance): """Test apt default security sources. Ported from tests/cloud_tests/testcases/modules/apt_configure_security.py """ 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" if class_client.settings.PLATFORM == "azure": sec_url = ( f"deb http://azure.archive.ubuntu.com/ubuntu/" f" {release}-security" ) sec_src_url = sec_url.replace("deb ", "# deb-src ") assert 3 == sources_list.count(sec_url) assert 3 == sources_list.count(sec_src_url)
def _test_network_config_applied_on_reboot(client: IntegrationInstance): log = client.read_from_file('/var/log/cloud-init.log') if 'network config is disabled' in log: pytest.skip("network config disabled. Test doesn't apply") assert 'Applying network configuration' in log assert 'dummy0' not in client.execute('ls /sys/class/net') _add_dummy_bridge_to_netplan(client) client.execute('echo "" > /var/log/cloud-init.log') client.restart() log = client.read_from_file('/var/log/cloud-init.log') if 'cache invalid in datasource' in log: # Invalid cache will get cleared, meaning we'll create a new # "instance" and apply networking config, so events aren't # really relevant here pytest.skip("Test only valid for existing instances") assert 'Event Allowed: scope=network EventType=boot' in log assert 'Applying network configuration' in log assert 'dummy0' not in client.execute('ls /sys/class/net')
def _detect_reboot(instance: IntegrationInstance): # We'll wait for instance up here, but we don't know if we're # detecting the first boot or second boot, so we also check # the logs to ensure we've booted twice. If the logs show we've # only booted once, wait until we've booted twice instance.instance.wait() for _ in range(600): try: log = instance.read_from_file("/var/log/cloud-init.log") boot_count = log.count("running 'init-local'") if boot_count == 1: instance.instance.wait() elif boot_count > 1: break except Exception: pass time.sleep(1) else: raise Exception("Could not detect reboot")
def test_ppa_source(self, class_client: IntegrationInstance): """Test the apt ppa functionality. 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) ) assert ( "://ppa.launchpad.net/simplestreams-dev/trunk/ubuntu" in ppa_path_contents or "://ppa.launchpadcontent.net/simplestreams-dev/trunk/ubuntu" in ppa_path_contents ) assert TEST_PPA_KEY in self.get_keys(class_client)
def test_boot_event_disabled_by_default(client: IntegrationInstance): log = client.read_from_file('/var/log/cloud-init.log') assert 'Applying network configuration' in log assert 'dummy0' not in client.execute('ls /sys/class/net') _add_dummy_bridge_to_netplan(client) client.execute('rm /var/log/cloud-init.log') client.restart() log2 = client.read_from_file('/var/log/cloud-init.log') # We attempt to apply network config twice on every boot. # Ensure neither time works. assert 2 == len( re.findall(r"Event Denied: scopes=\['network'\] EventType=boot[^-]", log2)) assert 2 == log2.count( "Event Denied: scopes=['network'] EventType=boot-legacy") assert 2 == log2.count("No network config applied. Neither a new instance" " nor datasource network update allowed") assert 'dummy0' in client.execute('ls /sys/class/net')
def test_log_message_on_missing_version_file(client: IntegrationInstance): client.push_file(TEST_PICKLE, PICKLE_PATH) client.restart() assert client.execute("cloud-init status --wait").ok log = client.read_from_file("/var/log/cloud-init.log") verify_ordered_items_in_text( [ "Unable to unpickle datasource: 'MIMEMultipart' object has no " "attribute 'policy'. Ignoring current cache.", "no cache found", "Searching for local data source", r"SUCCESS: found local data from DataSource(NoCloud|LXD)", ], log, )
def test_per_freq(client: IntegrationInstance): # Sanity test for scripts folder cmd = "test -d /var/lib/cloud/scripts" assert client.execute(cmd).ok # Test per-boot cmd = "test -f /var/lib/cloud/scripts/per-boot/always.sh" assert client.execute(cmd).ok cmd = "test -f /tmp/test_per_freq_always" assert client.execute(cmd).ok # Test per-instance cmd = "test -f /var/lib/cloud/scripts/per-instance/instance.sh" assert client.execute(cmd).ok cmd = "test -f /tmp/test_per_freq_instance" assert client.execute(cmd).ok # Test per-once cmd = "test -f /var/lib/cloud/scripts/per-once/once.sh" assert client.execute(cmd).ok cmd = "test -f /tmp/test_per_freq_once" assert client.execute(cmd).ok
def test_dual_stack(client: IntegrationInstance): # Drop IPv4 responses assert client.execute("iptables -I INPUT -s 169.254.169.254 -j DROP").ok _test_crawl(client, "http://[fd00:ec2::254]") # Block IPv4 requests assert client.execute("iptables -I OUTPUT -d 169.254.169.254 -j REJECT").ok _test_crawl(client, "http://[fd00:ec2::254]") # Re-enable IPv4 assert client.execute("iptables -D OUTPUT -d 169.254.169.254 -j REJECT").ok assert client.execute("iptables -D INPUT -s 169.254.169.254 -j DROP").ok # Drop IPv6 responses assert client.execute("ip6tables -I INPUT -s fd00:ec2::254 -j DROP").ok _test_crawl(client, "http://169.254.169.254") # Block IPv6 requests assert client.execute("ip6tables -I OUTPUT -d fd00:ec2::254 -j REJECT").ok _test_crawl(client, "http://169.254.169.254")
def test_disk_setup_when_mounted( self, create_disk, client: IntegrationInstance ): """Test lp-1920939. We insert an extra disk into our VM, format it to have two partitions, modify our cloud config to mount devices before disk setup, and modify our userdata to setup a single partition on the disk. This allows cloud-init to attempt disk setup on a mounted partition. When blockdev is in use, it will fail with "blockdev: ioctl error on BLKRRPART: Device or resource busy" along with a warning and a traceback. When partprobe is in use, everything should work successfully. """ log = client.read_from_file("/var/log/cloud-init.log") self._verify_first_disk_setup(client, log) # Ensure NoCloud gets detected on reboot client.execute("mkdir -p /var/lib/cloud/seed/nocloud-net/") client.execute("touch /var/lib/cloud/seed/nocloud-net/meta-data") client.write_to_file( "/etc/cloud/cloud.cfg.d/99_nocloud.cfg", "datasource_list: [ NoCloud ]\n", ) # Update our userdata and cloud.cfg to mount then perform new disk # setup client.write_to_file( "/var/lib/cloud/seed/nocloud-net/user-data", UPDATED_PARTPROBE_USERDATA, ) client.execute( "sed -i 's/write-files/write-files\\n - mounts/' " "/etc/cloud/cloud.cfg" ) client.execute("cloud-init clean --logs") client.restart() # Assert new setup works as expected verify_clean_log(log) lsblk = json.loads(client.execute("lsblk --json")) sdb = [x for x in lsblk["blockdevices"] if x["name"] == "sdb"][0] assert len(sdb["children"]) == 1 assert sdb["children"][0]["name"] == "sdb1" if "mountpoint" in sdb["children"][0]: assert sdb["children"][0]["mountpoint"] == "/mnt3" else: assert sdb["children"][0]["mountpoints"] == ["/mnt3"]
def test_ntp_installed(self, class_client: IntegrationInstance): """Test that `ntpd --version` succeeds, indicating installation.""" assert class_client.execute("ntpd --version").ok
def test_ntpq_servers(self, class_client: IntegrationInstance): result = class_client.execute("ntpq -p -w -n") assert result.ok for expected_server_or_pool in [*EXPECTED_SERVERS, *EXPECTED_POOLS]: assert expected_server_or_pool in result.stdout
def test_bad_key(self, class_client: IntegrationInstance): """Test the apt signed-by functionality. """ with pytest.raises(OSError): class_client.read_from_file( '/etc/apt/trusted.list.d/test_bad_key.gpg')
def _customize_envionment(client: IntegrationInstance): # Assert our platform can detect LXD during systemd generator timeframe. ds_id_log = client.execute("cat /run/cloud-init/ds-identify.log").stdout assert "check for 'LXD' returned found" in ds_id_log if client.settings.PLATFORM == "lxd_vm": # ds-identify runs at systemd generator time before /dev/lxd/sock. # Assert we can expected artifact which indicates LXD is viable. result = client.execute("cat /sys/class/dmi/id/board_name") if not result.ok: raise AssertionError( "Missing expected /sys/class/dmi/id/board_name") if "LXD" != result.stdout: raise AssertionError(f"DMI board_name is not LXD: {result.stdout}") # Having multiple datasources prevents ds-identify from short-circuiting # detection logic with a log like: # single entry in datasource_list (LXD) use that. # Also, NoCloud is detected during init-local timeframe. # If there is a race on VMs where /dev/lxd/sock is not setup in init-local # cloud-init will fallback to NoCloud and fail this test. client.write_to_file( "/etc/cloud/cloud.cfg.d/99-detect-lxd-first.cfg", "datasource_list: [LXD, NoCloud]\n", ) # This is also to ensure that NoCloud can be detected if ImageSpecification.from_os_image().release == "jammy": # Add nocloud-net seed files because Jammy no longer delivers NoCloud # (LP: #1958460). client.execute("mkdir -p /var/lib/cloud/seed/nocloud-net") client.write_to_file("/var/lib/cloud/seed/nocloud-net/meta-data", "") client.write_to_file("/var/lib/cloud/seed/nocloud-net/user-data", "#cloud-config\n{}") client.execute("cloud-init clean --logs") client.restart()
def test_poweroff_false_condition(self, client: IntegrationInstance): log = client.read_from_file("/var/log/cloud-init.log") assert _can_connect(client) assert "Condition was false. Will not perform state change" in log
def test_chef_license(client: IntegrationInstance): log = client.read_from_file('/var/log/cloud-init.log') assert 'Traceback' not in log
def test_lxd_datasource_discovery(client: IntegrationInstance): """Test that DataSourceLXD is detected instead of NoCloud.""" _customize_envionment(client) result = client.execute("cloud-init status --wait --long") if not result.ok: raise AssertionError("cloud-init failed:\n%s", result.stderr) if "DataSourceLXD" not in result.stdout: raise AssertionError("cloud-init did not discover DataSourceLXD", result.stdout) netplan_yaml = client.execute("cat /etc/netplan/50-cloud-init.yaml") netplan_cfg = yaml.safe_load(netplan_yaml) platform = client.settings.PLATFORM nic_dev = "eth0" if platform == "lxd_container" else "enp5s0" assert { "network": { "ethernets": { nic_dev: { "dhcp4": True } }, "version": 2 } } == netplan_cfg log = client.read_from_file("/var/log/cloud-init.log") verify_clean_log(log) result = client.execute("cloud-id") if result.stdout != "lxd": raise AssertionError("cloud-id didn't report lxd. Result: %s", result.stdout) # Validate config instance data represented data = json.loads( client.read_from_file("/run/cloud-init/instance-data.json")) v1 = data["v1"] ds_cfg = data["ds"] assert "lxd" == v1["platform"] assert "LXD socket API v. 1.0 (/dev/lxd/sock)" == v1["subplatform"] ds_cfg = json.loads(client.execute("cloud-init query ds").stdout) assert ["_doc", "_metadata_api_version", "config", "meta-data"] == sorted(list(ds_cfg.keys())) if (client.settings.PLATFORM == "lxd_vm" and ImageSpecification.from_os_image().release == "bionic"): # pycloudlib injects user.vendor_data for lxd_vm on bionic # to start the lxd-agent. # https://github.com/canonical/pycloudlib/blob/main/pycloudlib/\ # lxd/defaults.py#L13-L27 # Underscore-delimited aliases exist for any keys containing hyphens or # dots. lxd_config_keys = ["user.meta-data", "user.vendor-data"] else: lxd_config_keys = ["user.meta-data"] assert "1.0" == ds_cfg["_metadata_api_version"] assert lxd_config_keys == list(ds_cfg["config"].keys()) assert { "public-keys": v1["public_ssh_keys"][0] } == (yaml.safe_load(ds_cfg["config"]["user.meta-data"])) assert "#cloud-config\ninstance-id" in ds_cfg["meta-data"] # Assert NoCloud seed data is still present in cloud image metadata # This will start failing if we redact metadata templates from # https://cloud-images.ubuntu.com/daily/server/jammy/current/\ # jammy-server-cloudimg-amd64-lxd.tar.xz nocloud_metadata = yaml.safe_load( client.read_from_file("/var/lib/cloud/seed/nocloud-net/meta-data")) assert client.instance.name == nocloud_metadata["instance-id"] assert ( nocloud_metadata["instance-id"] == nocloud_metadata["local-hostname"]) assert v1["public_ssh_keys"][0] == nocloud_metadata["public-keys"]
def test_static_route_to_host(client: IntegrationInstance): route = client.execute("ip route | grep {}".format(DESTINATION_IP)) assert route.startswith(EXPECTED_ROUTE)
def test_timesyncd(client: IntegrationInstance): contents = client.read_from_file( '/etc/systemd/timesyncd.conf.d/cloud-init.conf' ) assert '.pool.ntp.org' in contents
def _remove_nocloud_dir_and_reboot(client: IntegrationInstance): # On Impish and below, NoCloud will be detected on an LXD container. # If we remove this directory, it will no longer be detected. client.execute("rm -rf /var/lib/cloud/seed/nocloud-net") client.execute("cloud-init clean --logs --reboot")
def test_puppet_service(client: IntegrationInstance): """Basic test that puppet gets installed and runs.""" log = client.read_from_file("/var/log/cloud-init.log") verify_clean_log(log) assert client.execute("systemctl is-active puppet").ok assert "Running command ['puppet', 'agent'" not in log
def get_instance(self, cloud_instance, settings=integration_settings): return IntegrationInstance(self, cloud_instance, settings)
def test_pupet_exec(client: IntegrationInstance): """Basic test that puppet gets installed and runs.""" log = client.read_from_file("/var/log/cloud-init.log") assert "Running command ['puppet', 'agent', '--noop']" in log
def test_chef_license(client: IntegrationInstance): log = client.read_from_file("/var/log/cloud-init.log") verify_clean_log(log)
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
def test_cloud_id_file_symlink(self, class_client: IntegrationInstance): cloud_id = class_client.execute("cloud-id").stdout expected_link_output = ("'/run/cloud-init/cloud-id' -> " f"'/run/cloud-init/cloud-id-{cloud_id}'") assert expected_link_output == str( class_client.execute("stat -c %N /run/cloud-init/cloud-id"))
def test_timesyncd(client: IntegrationInstance): contents = client.read_from_file( "/etc/systemd/timesyncd.conf.d/cloud-init.conf") assert "NTP=172.16.15.14" in contents
def test_runcmd(client: IntegrationInstance): log = client.read_from_file('/var/log/cloud-init-test-output') assert 'should be last line in cloud-init-test-output file' in log
def test_empty_ntp(client: IntegrationInstance): assert client.execute("ntpd --version").ok assert client.execute("test -f /etc/ntp.conf.dist").failed assert "pool.ntp.org iburst" in client.execute( 'grep -v "^#" /etc/ntp.conf')