Ejemplo n.º 1
0
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()
Ejemplo n.º 2
0
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()
Ejemplo n.º 3
0
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)
Ejemplo n.º 4
0
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)
Ejemplo n.º 5
0
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
Ejemplo n.º 6
0
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)
Ejemplo n.º 7
0
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)
Ejemplo n.º 8
0
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)
Ejemplo n.º 9
0
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}")
Ejemplo n.º 10
0
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
Ejemplo n.º 11
0
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
Ejemplo n.º 12
0
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')
Ejemplo n.º 13
0
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)
Ejemplo n.º 15
0
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
Ejemplo n.º 16
0
 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)
Ejemplo n.º 19
0
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]),
        ))
Ejemplo n.º 20
0
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]),
            ))