def firecrackers(self, keyword=None, min_version=None, max_version=None):
        """Return fc/jailer artifacts for the current arch."""
        firecrackers = self._fetch_artifacts(
            ArtifactCollection.ARTIFACTS_BINARIES,
            ArtifactCollection.FC_EXTENSION,
            ArtifactType.FC,
            FirecrackerArtifact,
            keyword=keyword,
        )

        # Filter out binaries with versions older than the `min_version` arg.
        if min_version is not None:
            return list(
                filter(
                    lambda fc: compare_versions(fc.version, min_version) >= 0,
                    firecrackers,
                ))

        # Filter out binaries with versions newer than the `max_version` arg.
        if max_version is not None:
            return list(
                filter(
                    lambda fc: compare_versions(fc.version, max_version) <= 0,
                    firecrackers,
                ))

        return firecrackers
Example #2
0
def test_mmds_older_snapshot(bin_cloner_path):
    """
    Test MMDS behavior restoring older snapshots in the current version.

    Ensures that the MMDS version is persisted or initialised with the default
    if the FC version does not support this feature.

    @type: functional
    """
    vm_builder = MicrovmBuilder(bin_cloner_path)

    # Validate restoring a past snapshot in the current version.
    artifacts = ArtifactCollection(_test_images_s3_bucket())
    # Fetch all firecracker binaries.
    firecracker_artifacts = artifacts.firecrackers(
        max_version=get_firecracker_version_from_toml())
    for firecracker in firecracker_artifacts:
        firecracker.download()
        jailer = firecracker.jailer()
        jailer.download()

        net_iface = NetIfaceConfig()
        vm_instance = vm_builder.build_vm_nano(
            net_ifaces=[net_iface],
            fc_binary=firecracker.local_path(),
            jailer_binary=jailer.local_path()
        )

        fc_version = firecracker.base_name()[1:]
        # If the version is smaller or equal to 1.0.0, we expect that
        # MMDS will be initialised with V1 by default.
        # Otherwise, we may configure V2.
        if compare_versions(fc_version, "1.0.0") <= 0:
            mmds_version = "V1"
        else:
            mmds_version = "V2"

        # Check if we need to configure MMDS the old way, by
        # setting `allow_mmds_requests`.
        # If we do (for v0.25), reissue the network PUT api call.
        if compare_versions(fc_version, "1.0.0") < 0:
            basevm = vm_instance.vm
            guest_mac = net_tools.mac_from_ip(net_iface.guest_ip)
            response = basevm.network.put(
                iface_id=net_iface.dev_name,
                host_dev_name=net_iface.tap_name,
                guest_mac=guest_mac,
                allow_mmds_requests=True
            )
            assert basevm.api_session.is_status_no_content(
                response.status_code)

        _validate_mmds_snapshot(
            vm_instance,
            vm_builder,
            mmds_version,
            target_fc_version=fc_version
        )
Example #3
0
    def create_json(
        self,
        vcpu_count=None,
        mem_size_mib=None,
        smt=None,
        cpu_template=None,
        track_dirty_pages=None,
    ):
        """Compose the json associated to this type of API request."""
        datax = {}
        if vcpu_count is not None:
            datax["vcpu_count"] = vcpu_count

        if mem_size_mib is not None:
            datax["mem_size_mib"] = mem_size_mib

        if compare_versions(self._firecracker_version, "0.25.0") <= 0:
            datax["ht_enabled"] = False if smt is None else smt
        elif smt is not None:
            datax["smt"] = smt

        if cpu_template is not None:
            datax["cpu_template"] = cpu_template

        if track_dirty_pages is not None:
            datax["track_dirty_pages"] = track_dirty_pages

        return datax
Example #4
0
def test_mmds_snapshot(bin_cloner_path,  version):
    """
    Test MMDS behavior by restoring a snapshot on current and past FC versions.

    Ensures that the version is persisted or initialised with the default if
    the firecracker version does not support it.

    @type: functional
    """
    vm_builder = MicrovmBuilder(bin_cloner_path)
    vm_instance = vm_builder.build_vm_nano(
        net_ifaces=[NetIfaceConfig()]
    )

    # Validate current version.
    _validate_mmds_snapshot(
        vm_instance, vm_builder, version)

    # Validate restoring in past versions.
    artifacts = ArtifactCollection(_test_images_s3_bucket())
    # Fetch all firecracker binaries.
    # Create a snapshot with current build and restore with each FC binary
    # artifact.
    firecracker_artifacts = artifacts.firecrackers(
        # v1.1.0 breaks snapshot compatibility with older versions.
        min_version="1.1.0",
        max_version=get_firecracker_version_from_toml())
    for firecracker in firecracker_artifacts:
        vm_instance = vm_builder.build_vm_nano(
            net_ifaces=[NetIfaceConfig()]
        )
        firecracker.download()
        jailer = firecracker.jailer()
        jailer.download()

        target_version = firecracker.base_name()[1:]
        # If the version is smaller or equal to 1.0.0, we expect that
        # MMDS will be initialised with V1 by default.
        if compare_versions(target_version, "1.0.0") <= 0:
            mmds_version = "V1"
        else:
            mmds_version = version

        _validate_mmds_snapshot(
            vm_instance,
            vm_builder,
            mmds_version,
            target_fc_version=target_version,
            fc_path=firecracker.local_path(),
            jailer_path=jailer.local_path()
        )
Example #5
0
    def put(self, **args):
        """Attach a block device or update the details of a previous one."""
        # Default the io engine to Async on kernels > 5.10 so that we
        # make sure to exercise both Sync and Async behaviour in the CI.
        # Also check the FC version to make sure that it has support for
        # configurable io_engine.
        if (is_io_uring_supported()
                and compare_versions(self._firecracker_version, "0.25.0") > 0
                and ("io_engine" not in args or args["io_engine"] is None)):
            args["io_engine"] = "Async"

        datax = self.create_json(**args)

        return self._api_session.put("{}/{}".format(self._drive_cfg_url,
                                                    args["drive_id"]),
                                     json=datax)
Example #6
0
def snapshot_resume_measurements(vm_type):
    """Define measurements for snapshot resume tests."""
    load_latency = LOAD_LATENCY_BASELINES[platform.machine()][vm_type]
    # Host kernels >= 5.4 add an up to ~30ms latency.
    # See: https://github.com/firecracker-microvm/firecracker/issues/2129
    linux_version = platform.release()
    for idx, char in enumerate(linux_version):
        if not char.isdigit() and char != '.':
            linux_version = linux_version[0:idx]
            break
    if compare_versions(linux_version, "5.4.0") > 0:
        load_latency["target"] += 30

    latency = types.MeasurementDef.create_measurement(
        "latency", "ms", [function.Max("max")],
        {"max": criteria.LowerThan(load_latency)})

    return [latency]
Example #7
0
def snapshot_resume_measurements(vm_type):
    """Define measurements for snapshot resume tests."""
    load_latency = LOAD_LATENCY_BASELINES[platform.machine()][vm_type]

    if is_io_uring_supported():
        # There is added latency caused by the io_uring syscalls used by the
        # block device.
        load_latency["target"] += 115
    if compare_versions(get_kernel_version(), "5.4.0") > 0:
        # Host kernels >= 5.4 add an up to ~30ms latency.
        # See: https://github.com/firecracker-microvm/firecracker/issues/2129
        load_latency["target"] += 30

    latency = types.MeasurementDef.create_measurement(
        "latency", "ms", [function.Max("max")],
        {"max": criteria.LowerThan(load_latency)})

    return [latency]
Example #8
0
def _validate_mmds_snapshot(
    vm_instance,
    vm_builder,
    version,
    target_fc_version=None,
    fc_path=None,
    jailer_path=None
):
    """Test MMDS behaviour across snap-restore."""
    basevm = vm_instance.vm
    root_disk = vm_instance.disks[0]
    disks = [root_disk.local_path()]
    ssh_key = vm_instance.ssh_key
    ipv4_address = '169.254.169.250'

    # Configure MMDS version with custom IPv4 address.
    configure_mmds(
        basevm,
        version=version,
        iface_ids=[DEFAULT_DEV_NAME],
        ipv4_address=ipv4_address,
        fc_version=target_fc_version
    )

    # Check if the FC version supports the latest format for mmds-config.
    # If target_fc_version is None, we assume the current version is used.
    if target_fc_version is None or \
            (target_fc_version is not None and
             compare_versions(target_fc_version, "1.0.0") >= 0):
        expected_mmds_config = {
            "version": version,
            "ipv4_address": ipv4_address,
            "network_interfaces": [DEFAULT_DEV_NAME]
        }
        response = basevm.full_cfg.get()
        assert basevm.api_session.is_status_ok(response.status_code)
        assert response.json()["mmds-config"] == expected_mmds_config

    data_store = {
        'latest': {
            'meta-data': {
                'ami-id': 'ami-12345678'
            }
        }
    }
    _populate_data_store(basevm, data_store)

    basevm.start()

    snapshot_builder = SnapshotBuilder(basevm)

    ssh_connection = net_tools.SSHConnection(basevm.ssh_config)
    _run_guest_cmd(ssh_connection, f'ip route add {ipv4_address} dev eth0', '')

    # Generate token if needed.
    token = None
    if version == "V2":
        token = generate_mmds_session_token(
            ssh_connection,
            ipv4_address,
            token_ttl=60
        )

    # Fetch metadata.
    cmd = generate_mmds_get_request(
        ipv4_address,
        token=token,
    )
    _run_guest_cmd(ssh_connection, cmd, data_store, use_json=True)

    # Create snapshot.
    snapshot = snapshot_builder.create(disks,
                                       ssh_key,
                                       SnapshotType.FULL,
                                       target_version=target_fc_version)

    # Resume microVM and ensure session token is still valid on the base.
    response = basevm.vm.patch(state='Resumed')
    assert basevm.api_session.is_status_no_content(response.status_code)

    # Fetch metadata again using the same token.
    _run_guest_cmd(ssh_connection, cmd, data_store, use_json=True)

    # Kill base microVM.
    basevm.kill()

    # Load microVM clone from snapshot.
    microvm, _ = vm_builder.build_from_snapshot(snapshot,
                                                resume=True,
                                                fc_binary=fc_path,
                                                jailer_binary=jailer_path)

    ssh_connection = net_tools.SSHConnection(microvm.ssh_config)

    # Check the reported mmds config. In versions up to (including) v1.0.0 this
    # was not populated after restore.
    if target_fc_version is not None and \
            compare_versions("1.0.0", target_fc_version) < 0:
        response = microvm.full_cfg.get()
        assert microvm.api_session.is_status_ok(response.status_code)
        assert response.json()["mmds-config"] == expected_mmds_config

    if version == 'V1':
        # Verify that V2 requests don't work
        assert generate_mmds_session_token(
            ssh_connection,
            ipv4_address,
            token_ttl=60
        ) == "Not allowed HTTP method."

        token = None
    else:
        # Attempting to reuse the token across a restore must fail.
        cmd = generate_mmds_get_request(ipv4_address, token=token)
        _run_guest_cmd(ssh_connection, cmd, 'MMDS token not valid.')

        # Generate token.
        token = generate_mmds_session_token(
            ssh_connection,
            ipv4_address,
            token_ttl=60
        )

    # Data store is empty after a restore.
    cmd = generate_mmds_get_request(ipv4_address, token=token)
    _run_guest_cmd(ssh_connection, cmd, 'null')

    # Now populate the store.
    _populate_data_store(microvm, data_store)

    # Fetch metadata.
    _run_guest_cmd(ssh_connection, cmd, data_store, use_json=True)