def test_update_default_password(setup_image, session_cloud: IntegrationCloud): os_profile = { "os_profile": { "admin_password": "", "linux_configuration": { "disable_password_authentication": False }, } } os_profile["os_profile"]["admin_password"] = OLD_PASSWORD instance1 = session_cloud.launch(launch_kwargs={"vm_params": os_profile}) _check_password(instance1, OLD_PASSWORD) snapshot_id = instance1.cloud.cloud_instance.snapshot( instance1.instance, delete_provisioned_user=False) os_profile["os_profile"]["admin_password"] = NEW_PASSWORD try: with session_cloud.launch(launch_kwargs={ "image_id": snapshot_id, "vm_params": os_profile, }) as instance2: _check_password(instance2, NEW_PASSWORD) finally: session_cloud.cloud_instance.delete_image(snapshot_id) instance1.destroy()
def test_update_default_password(setup_image, session_cloud: IntegrationCloud): os_profile = { 'os_profile': { 'admin_password': '', 'linux_configuration': { 'disable_password_authentication': False } } } os_profile['os_profile']['admin_password'] = OLD_PASSWORD instance1 = session_cloud.launch(launch_kwargs={'vm_params': os_profile}) _check_password(instance1, OLD_PASSWORD) snapshot_id = instance1.cloud.cloud_instance.snapshot( instance1.instance, delete_provisioned_user=False) os_profile['os_profile']['admin_password'] = NEW_PASSWORD try: with session_cloud.launch(launch_kwargs={ 'image_id': snapshot_id, 'vm_params': os_profile, }) as instance2: _check_password(instance2, NEW_PASSWORD) finally: session_cloud.cloud_instance.delete_image(snapshot_id) instance1.destroy()
def client_with_secondary_vnic( session_cloud: IntegrationCloud, ) -> Iterator[IntegrationInstance]: """Create an instance client and attach a temporary vnic""" with session_cloud.launch() as client: ip_address = client.instance.add_network_interface() yield client client.instance.remove_network_interface(ip_address)
def _client(request, fixture_utils, session_cloud: IntegrationCloud): """Fixture implementation for the client fixtures. Launch the dynamic IntegrationClient instance using any provided userdata, yield to the test, then cleanup """ getter = functools.partial(fixture_utils.closest_marker_first_arg_or, request, default=None) user_data = getter('user_data') name = getter('instance_name') lxd_config_dict = getter('lxd_config_dict') launch_kwargs = {} if name is not None: launch_kwargs["name"] = name if lxd_config_dict is not None: if not isinstance(session_cloud, _LxdIntegrationCloud): pytest.skip("lxd_config_dict requires LXD") launch_kwargs["config_dict"] = lxd_config_dict with session_cloud.launch(user_data=user_data, launch_kwargs=launch_kwargs) as instance: previous_failures = request.session.testsfailed yield instance test_failed = request.session.testsfailed - previous_failures > 0 _collect_logs(instance, request.node.nodeid, test_failed)
def test_wait_when_no_datasource(session_cloud: IntegrationCloud, setup_image): """Ensure that when no datasource is found, we get status: disabled LP: #1966085 """ with session_cloud.launch( launch_kwargs= { # On Jammy and above, we detect the LXD datasource using a # socket available to the container. This prevents the socket # from being exposed in the container, causing datasource detection # to fail. ds-identify will then have failed to detect a datasource "config_dict": { "security.devlxd": False }, "wait": False, # to prevent cloud-init status --wait }) as client: # We know this will be an LXD instance due to our pytest mark client.instance.execute_via_ssh = False # pyright: ignore # No ubuntu user if cloud-init didn't run client.instance.username = "******" # Jammy and above will use LXD datasource by default if ImageSpecification.from_os_image().release in [ "bionic", "focal", "impish", ]: _remove_nocloud_dir_and_reboot(client) status_out = _wait_for_cloud_init(client).stdout.strip() assert "status: disabled" in status_out assert client.execute("cloud-init status --wait").ok
def test_azure_kernel_upgrade_case_insensitive_uuid( session_cloud: IntegrationCloud ): cfg_image_spec = ImageSpecification.from_os_image() if (cfg_image_spec.os, cfg_image_spec.release) != ("ubuntu", "bionic"): pytest.skip( "Test only supports ubuntu:bionic not {0.os}:{0.release}".format( cfg_image_spec ) ) source = get_validated_source(session_cloud) if not source.installs_new_version(): pytest.skip( "Provide CLOUD_INIT_SOURCE to install expected working cloud-init" ) image_id = IMG_AZURE_UBUNTU_PRO_FIPS_BIONIC with session_cloud.launch( launch_kwargs={"image_id": image_id} ) as instance: # We can't use setup_image fixture here because we want to avoid # taking a snapshot or cleaning the booted machine after cloud-init # upgrade. instance.install_new_cloud_init( source, take_snapshot=False, clean=False ) _check_iid_insensitive_across_kernel_upgrade(instance)
def test_upgrade(session_cloud: IntegrationCloud): source = get_validated_source(session_cloud) if not source.installs_new_version(): pytest.skip( "Install method '{}' not supported for this test".format(source)) return # type checking doesn't understand that skip raises launch_kwargs = { 'image_id': session_cloud._get_initial_image(), } image = ImageSpecification.from_os_image() # Get the paths to write test logs output_dir = Path(session_cloud.settings.LOCAL_LOG_PATH) output_dir.mkdir(parents=True, exist_ok=True) base_filename = 'test_upgrade_{platform}_{os}_{{stage}}_{time}.log'.format( platform=session_cloud.settings.PLATFORM, os=image.release, time=session_start_time, ) before_path = output_dir / base_filename.format(stage='before') after_path = output_dir / base_filename.format(stage='after') # Get the network cfg file netcfg_path = '/dev/null' if image.os == 'ubuntu': netcfg_path = '/etc/netplan/50-cloud-init.yaml' if image.release == 'xenial': netcfg_path = '/etc/network/interfaces.d/50-cloud-init.cfg' with session_cloud.launch( launch_kwargs=launch_kwargs, user_data=USER_DATA, wait=True, ) as instance: _output_to_compare(instance, before_path, netcfg_path) instance.install_new_cloud_init(source, take_snapshot=False) instance.execute('hostname something-else') _restart(instance) _output_to_compare(instance, after_path, netcfg_path) log.info('Wrote upgrade test logs to %s and %s', before_path, after_path)
def _client(request, fixture_utils, session_cloud: IntegrationCloud): """Fixture implementation for the client fixtures. Launch the dynamic IntegrationClient instance using any provided userdata, yield to the test, then cleanup """ getter = functools.partial(fixture_utils.closest_marker_first_arg_or, request, default=None) user_data = getter('user_data') name = getter('instance_name') lxd_config_dict = getter('lxd_config_dict') lxd_setup = getter('lxd_setup') lxd_use_exec = fixture_utils.closest_marker_args_or( request, 'lxd_use_exec', None) launch_kwargs = {} if name is not None: launch_kwargs["name"] = name if lxd_config_dict is not None: if not isinstance(session_cloud, _LxdIntegrationCloud): pytest.skip("lxd_config_dict requires LXD") launch_kwargs["config_dict"] = lxd_config_dict if lxd_use_exec is not None: if not isinstance(session_cloud, _LxdIntegrationCloud): pytest.skip("lxd_use_exec requires LXD") if isinstance(session_cloud, LxdVmCloud): image_spec = ImageSpecification.from_os_image() if image_spec.release == image_spec.image_id == "xenial": # Why fail instead of skip? We expect that skipped tests will # be run in a different one of our usual battery of test runs # (e.g. LXD-only tests are skipped on EC2 but will run in our # normal LXD test runs). This is not true of this test: it # can't run in our usual xenial LXD VM test run, and it may not # run anywhere else. A failure flags up this discrepancy. pytest.fail(XENIAL_LXD_VM_EXEC_MSG) launch_kwargs["execute_via_ssh"] = False local_launch_kwargs = {} if lxd_setup is not None: if not isinstance(session_cloud, _LxdIntegrationCloud): pytest.skip('lxd_setup requires LXD') local_launch_kwargs['lxd_setup'] = lxd_setup with session_cloud.launch(user_data=user_data, launch_kwargs=launch_kwargs, **local_launch_kwargs) as instance: if lxd_use_exec is not None: # Existing instances are not affected by the launch kwargs, so # ensure it here; we still need the launch kwarg so waiting works instance.execute_via_ssh = False previous_failures = request.session.testsfailed yield instance test_failed = request.session.testsfailed - previous_failures > 0 _collect_logs(instance, request.node.nodeid, test_failed)
def test_ubuntu_drivers_installed(session_cloud: IntegrationCloud): with session_cloud.launch(launch_kwargs={"instance_type": "VM.GPU2.1"}, user_data=USER_DATA) as client: log = client.read_from_file("/var/log/cloud-init.log") verify_clean_log(log) assert 1 == log.count("Installing and activating NVIDIA drivers " "(nvidia/license-accepted=True, version=latest)") result = client.execute("dpkg -l | grep nvidia") assert result.ok, "No nvidia packages found" assert re.search( r"ii\s+linux-modules-nvidia-\d+-server", result.stdout), ( f"Did not find specific nvidia drivers packages in:" f" {result.stdout}")
def test_network_activation_disabled(session_cloud: IntegrationCloud): """Test that the network is not activated during init mode.""" _setup_custom_image(session_cloud) with session_cloud.launch() as client: result = client.execute('systemctl status google-guest-agent.service') if not result.ok: raise AssertionError('google-guest-agent is not active:\n%s', result.stdout) log = client.read_from_file('/var/log/cloud-init.log') assert "Running command ['netplan', 'apply']" not in log assert 'Not bringing up newly configured network interfaces' in log assert 'Bringing up newly configured network interfaces' not in log
def test_upgrade_package(session_cloud: IntegrationCloud): if get_validated_source(session_cloud) != CloudInitSource.DEB_PACKAGE: not_run_message = 'Test only supports upgrading to build deb' if os.environ.get('TRAVIS'): # If this isn't running on CI, we should know pytest.fail(not_run_message) else: pytest.skip(not_run_message) launch_kwargs = {'image_id': session_cloud.released_image_id} with session_cloud.launch(launch_kwargs=launch_kwargs) as instance: instance.install_deb() instance.restart() assert instance.execute('cloud-init status --wait --long').ok
def setup_image(session_cloud: IntegrationCloud): """Setup the target environment with the correct version of cloud-init. So we can launch instances / run tests with the correct image """ source = get_validated_source(session_cloud) if not source.installs_new_version(): return log.info('Setting up environment for %s', session_cloud.datasource) client = session_cloud.launch() client.install_new_cloud_init(source) # Even if we're keeping instances, we don't want to keep this # one around as it was just for image creation client.destroy() log.info('Done with environment setup')
def _setup_custom_image(session_cloud: IntegrationCloud): """Like `setup_image` in conftest.py, but with customized content.""" source = get_validated_source(session_cloud) if not source.installs_new_version(): return client = session_cloud.launch() # Insert our "disable_network_activation" file here client.write_to_file( '/etc/cloud/cloud.cfg.d/99-disable-network-activation.cfg', 'disable_network_activation: true\n', ) client.install_new_cloud_init(source) # Even if we're keeping instances, we don't want to keep this # one around as it was just for image creation client.destroy()
def _client(request, fixture_utils, session_cloud: IntegrationCloud): """Fixture implementation for the client fixtures. Launch the dynamic IntegrationClient instance using any provided userdata, yield to the test, then cleanup """ getter = functools.partial(fixture_utils.closest_marker_first_arg_or, request, default=None) user_data = getter("user_data") name = getter("instance_name") lxd_config_dict = getter("lxd_config_dict") lxd_setup = getter("lxd_setup") lxd_use_exec = fixture_utils.closest_marker_args_or( request, "lxd_use_exec", None) launch_kwargs = {} if name is not None: launch_kwargs["name"] = name if lxd_config_dict is not None: if not isinstance(session_cloud, _LxdIntegrationCloud): pytest.skip("lxd_config_dict requires LXD") launch_kwargs["config_dict"] = lxd_config_dict if lxd_use_exec is not None: if not isinstance(session_cloud, _LxdIntegrationCloud): pytest.skip("lxd_use_exec requires LXD") launch_kwargs["execute_via_ssh"] = False local_launch_kwargs = {} if lxd_setup is not None: if not isinstance(session_cloud, _LxdIntegrationCloud): pytest.skip("lxd_setup requires LXD") local_launch_kwargs["lxd_setup"] = lxd_setup with session_cloud.launch(user_data=user_data, launch_kwargs=launch_kwargs, **local_launch_kwargs) as instance: if lxd_use_exec is not None and isinstance(instance.instance, LXDInstance): # Existing instances are not affected by the launch kwargs, so # ensure it here; we still need the launch kwarg so waiting works instance.instance.execute_via_ssh = False previous_failures = request.session.testsfailed yield instance test_failed = request.session.testsfailed - previous_failures > 0 _collect_logs(instance, request.node.nodeid, test_failed)
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 this isn't running on CI, we should know pytest.fail(UNSUPPORTED_INSTALL_METHOD_MSG.format(source)) else: pytest.skip(UNSUPPORTED_INSTALL_METHOD_MSG.format(source)) return # type checking doesn't understand that skip raises launch_kwargs = {'image_id': session_cloud.released_image_id} with session_cloud.launch(launch_kwargs=launch_kwargs) as instance: instance.install_new_cloud_init( source, take_snapshot=False, clean=False ) instance.restart() assert instance.execute('cloud-init status --wait --long').ok
def test_poweroff(self, session_cloud: IntegrationCloud, mode, delay, timeout, expected): with session_cloud.launch( user_data=USER_DATA.format( delay=delay, mode=mode, timeout=timeout, condition='true'), wait=False ) as instance: if mode == 'reboot': _detect_reboot(instance) else: instance.instance.wait_for_stop() instance.instance.start(wait=True) log = instance.read_from_file('/var/log/cloud-init.log') assert _can_connect(instance) lines_to_check = [ 'Running module power-state-change', expected, "running 'init-local'", 'config-power-state-change already ran', ] verify_ordered_items_in_text(lines_to_check, log)
def setup_image(session_cloud: IntegrationCloud, request): """Setup the target environment with the correct version of cloud-init. So we can launch instances / run tests with the correct image """ source = get_validated_source(session_cloud) if not source.installs_new_version(): return log.info("Setting up environment for %s", session_cloud.datasource) client = session_cloud.launch() client.install_new_cloud_init(source) # Even if we're keeping instances, we don't want to keep this # one around as it was just for image creation client.destroy() log.info("Done with environment setup") # For some reason a yield here raises a # ValueError: setup_image did not yield a value # during setup so use a finalizer instead. request.addfinalizer(session_cloud.delete_snapshot)
def test_ephemeral(instance_type, is_ephemeral, session_cloud: IntegrationCloud, setup_image): if is_ephemeral: expected_log = ( "Ephemeral resource disk '/dev/disk/cloud/azure_resource' exists. " "Merging default Azure cloud ephemeral disk configs.") else: expected_log = ( "Ephemeral resource disk '/dev/disk/cloud/azure_resource' does " "not exist. Not merging default Azure cloud ephemeral disk " "configs.") with session_cloud.launch( launch_kwargs={"instance_type": instance_type}) as client: # Verify log file log = client.read_from_file("/var/log/cloud-init.log") assert expected_log in log # Verify devices dev_links = client.execute("ls /dev/disk/cloud") assert "azure_root" in dev_links assert "azure_root-part1" in dev_links if is_ephemeral: assert "azure_resource" in dev_links assert "azure_resource-part1" in dev_links # Verify mounts blks = client.execute("lsblk -pPo NAME,TYPE,MOUNTPOINT") root_device = client.execute( "realpath /dev/disk/cloud/azure_root-part1") assert ('NAME="{}" TYPE="part" MOUNTPOINT="/"'.format(root_device) in blks) if is_ephemeral: ephemeral_device = client.execute( "realpath /dev/disk/cloud/azure_resource-part1") assert ('NAME="{}" TYPE="part" MOUNTPOINT="/mnt"'.format( ephemeral_device) in blks)
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.released_image_id, } with session_cloud.launch( launch_kwargs=launch_kwargs, user_data=USER_DATA, ) as instance: # get pre values pre_hostname = instance.execute('hostname') pre_cloud_id = instance.execute('cloud-id') pre_result = instance.execute('cat /run/cloud-init/result.json') pre_network = instance.execute('cat /etc/netplan/50-cloud-init.yaml') pre_systemd_analyze = instance.execute('systemd-analyze') pre_systemd_blame = instance.execute('systemd-analyze blame') pre_cloud_analyze = instance.execute('cloud-init analyze show') pre_cloud_blame = instance.execute('cloud-init analyze blame') # Ensure no issues pre-upgrade assert not json.loads(pre_result)['v1']['errors'] log = instance.read_from_file('/var/log/cloud-init.log') assert 'Traceback' not in log assert 'WARN' not in log # Upgrade and reboot instance.install_new_cloud_init(source, take_snapshot=False) instance.execute('hostname something-else') instance.restart() assert instance.execute('cloud-init status --wait --long').ok # 'cloud-init init' helps us understand if our pickling upgrade paths # have broken across re-constitution of a cached datasource. Some # platforms invalidate their datasource cache on reboot, so we run # it here to ensure we get a dirty run. assert instance.execute('cloud-init init').ok # get post values post_hostname = instance.execute('hostname') post_cloud_id = instance.execute('cloud-id') post_result = instance.execute('cat /run/cloud-init/result.json') post_network = instance.execute('cat /etc/netplan/50-cloud-init.yaml') post_systemd_analyze = instance.execute('systemd-analyze') post_systemd_blame = instance.execute('systemd-analyze blame') post_cloud_analyze = instance.execute('cloud-init analyze show') post_cloud_blame = instance.execute('cloud-init analyze blame') # Ensure no issues post-upgrade assert not json.loads(pre_result)['v1']['errors'] log = instance.read_from_file('/var/log/cloud-init.log') assert 'Traceback' not in log assert 'WARN' not in log # Ensure important things stayed the same assert pre_hostname == post_hostname assert pre_cloud_id == post_cloud_id assert pre_result == post_result assert pre_network == post_network # Calculate and log all the boot numbers pre_analyze_totals = [ x for x in pre_cloud_analyze.splitlines() if x.startswith('Finished stage') or x.startswith('Total Time') ] post_analyze_totals = [ x for x in post_cloud_analyze.splitlines() if x.startswith('Finished stage') or x.startswith('Total Time') ] # pylint: disable=logging-format-interpolation LOG.info(LOG_TEMPLATE.format( pre_systemd_analyze=pre_systemd_analyze, post_systemd_analyze=post_systemd_analyze, pre_systemd_blame='\n'.join(pre_systemd_blame.splitlines()[:10]), post_systemd_blame='\n'.join(post_systemd_blame.splitlines()[:10]), pre_analyze_totals='\n'.join(pre_analyze_totals), post_analyze_totals='\n'.join(post_analyze_totals), pre_cloud_blame='\n'.join(pre_cloud_blame.splitlines()[:10]), post_cloud_blame='\n'.join(post_cloud_blame.splitlines()[:10]), ))
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.released_image_id, } with session_cloud.launch( launch_kwargs=launch_kwargs, user_data=USER_DATA, ) as instance: # get pre values pre_hostname = instance.execute("hostname") pre_cloud_id = instance.execute("cloud-id") pre_result = instance.execute("cat /run/cloud-init/result.json") pre_network = instance.execute("cat /etc/netplan/50-cloud-init.yaml") pre_systemd_analyze = instance.execute("systemd-analyze") pre_systemd_blame = instance.execute("systemd-analyze blame") pre_cloud_analyze = instance.execute("cloud-init analyze show") pre_cloud_blame = instance.execute("cloud-init analyze blame") # Ensure no issues pre-upgrade log = instance.read_from_file("/var/log/cloud-init.log") assert not json.loads(pre_result)["v1"]["errors"] try: verify_clean_log(log) except AssertionError: LOG.warning("There were errors/warnings/tracebacks pre-upgrade. " "Any failures may be due to pre-upgrade problem") # Upgrade instance.install_new_cloud_init(source, take_snapshot=False) # 'cloud-init init' helps us understand if our pickling upgrade paths # have broken across re-constitution of a cached datasource. Some # platforms invalidate their datasource cache on reboot, so we run # it here to ensure we get a dirty run. assert instance.execute("cloud-init init").ok # Reboot instance.execute("hostname something-else") instance.restart() assert instance.execute("cloud-init status --wait --long").ok # get post values post_hostname = instance.execute("hostname") post_cloud_id = instance.execute("cloud-id") post_result = instance.execute("cat /run/cloud-init/result.json") post_network = instance.execute("cat /etc/netplan/50-cloud-init.yaml") post_systemd_analyze = instance.execute("systemd-analyze") post_systemd_blame = instance.execute("systemd-analyze blame") post_cloud_analyze = instance.execute("cloud-init analyze show") post_cloud_blame = instance.execute("cloud-init analyze blame") # Ensure no issues post-upgrade assert not json.loads(pre_result)["v1"]["errors"] log = instance.read_from_file("/var/log/cloud-init.log") verify_clean_log(log) # Ensure important things stayed the same assert pre_hostname == post_hostname assert pre_cloud_id == post_cloud_id try: assert pre_result == post_result except AssertionError: if instance.settings.PLATFORM == "azure": pre_json = json.loads(pre_result) post_json = json.loads(post_result) assert pre_json["v1"]["datasource"].startswith( "DataSourceAzure") assert post_json["v1"]["datasource"].startswith( "DataSourceAzure") assert pre_network == post_network # Calculate and log all the boot numbers pre_analyze_totals = [ x for x in pre_cloud_analyze.splitlines() if x.startswith("Finished stage") or x.startswith("Total Time") ] post_analyze_totals = [ x for x in post_cloud_analyze.splitlines() if x.startswith("Finished stage") or x.startswith("Total Time") ] # pylint: disable=logging-format-interpolation LOG.info( LOG_TEMPLATE.format( pre_systemd_analyze=pre_systemd_analyze, post_systemd_analyze=post_systemd_analyze, pre_systemd_blame="\n".join( pre_systemd_blame.splitlines()[:10]), post_systemd_blame="\n".join( post_systemd_blame.splitlines()[:10]), pre_analyze_totals="\n".join(pre_analyze_totals), post_analyze_totals="\n".join(post_analyze_totals), pre_cloud_blame="\n".join(pre_cloud_blame.splitlines()[:10]), post_cloud_blame="\n".join(post_cloud_blame.splitlines()[:10]), ))