def test_block_performance_async(bin_cloner_path, results_file_dumper):
    """
    Test block performance for multiple vm configurations.

    @type: performance
    """
    logger = logging.getLogger(TEST_ID)

    if not is_io_uring_supported():
        logger.info("io_uring is not supported. Skipping..")
        pytest.skip('Cannot run async if io_uring is not supported')

    artifacts = ArtifactCollection(_test_images_s3_bucket())
    vm_artifacts = ArtifactSet(artifacts.microvms(keyword="1vcpu_1024mb"))
    vm_artifacts.insert(artifacts.microvms(keyword="2vcpu_1024mb"))

    kernel_artifacts = ArtifactSet(artifacts.kernels())
    disk_artifacts = ArtifactSet(artifacts.disks(keyword="ubuntu"))

    logger.info("Testing on processor %s", get_cpu_model_name())

    # Create a test context and add builder, logger, network.
    test_context = TestContext()
    test_context.custom = {
        'builder': MicrovmBuilder(bin_cloner_path),
        'logger': logger,
        'name': TEST_ID,
        'results_file_dumper': results_file_dumper,
        'io_engine': 'Async'
    }

    test_matrix = TestMatrix(
        context=test_context,
        artifact_sets=[vm_artifacts, kernel_artifacts, disk_artifacts])
    test_matrix.run_test(fio_workload)
Example #2
0
def test_drive_io_engine(test_microvm_with_api, network_config):
    """
    Test io_engine configuration.

    Test that the io_engine can be configured via the API on kernels that
    support the given type and that FC returns an error otherwise.

    @type: functional
    """
    test_microvm = test_microvm_with_api
    test_microvm.spawn()

    test_microvm.basic_config(add_root_device=False)
    test_microvm.ssh_network_config(network_config, '1')

    supports_io_uring = is_io_uring_supported()

    response = test_microvm.drive.put(
        drive_id='rootfs',
        path_on_host=test_microvm.create_jailed_resource(
            test_microvm.rootfs_file),
        is_root_device=True,
        is_read_only=False,
        # Set the opposite of the default backend type.
        io_engine="Sync" if supports_io_uring else "Async"
    )

    if not supports_io_uring:
        # The Async engine is not supported for older kernels.
        assert test_microvm.api_session.is_status_bad_request(
            response.status_code)

        test_microvm.check_log_message(
            "Received Error. Status code: 400 Bad Request. Message: Unable"
            " to create the block device FileEngine(UnsupportedEngine(Async))")

        # Now configure the default engine type and check that it works.
        response = test_microvm.drive.put_with_default_io_engine(
            drive_id='rootfs',
            path_on_host=test_microvm.create_jailed_resource(
                test_microvm.rootfs_file),
            is_root_device=True,
            is_read_only=False,
        )

    assert test_microvm.api_session.is_status_no_content(
        response.status_code)

    test_microvm.start()

    ssh_conn = net_tools.SSHConnection(test_microvm.ssh_config)

    # Execute a simple command to check that the guest booted successfully.
    rc, _, stderr = ssh_conn.execute_command("sync")
    assert rc == 0
    assert stderr.read() == ''

    assert test_microvm.full_cfg.get().json(
    )['drives'][0]['io_engine'] == "Sync"
Example #3
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 #4
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 #5
0
def test_drive_patch(test_microvm_with_api):
    """
    Extensively test drive PATCH scenarios before and after boot.

    @type: functional
    """
    test_microvm = test_microvm_with_api
    test_microvm.spawn()

    # Sets up the microVM with 2 vCPUs, 256 MiB of RAM and
    # a root file system with the rw permission.
    test_microvm.basic_config(rootfs_io_engine="Sync")

    # The drive to be patched.
    fs = drive_tools.FilesystemFile(
        os.path.join(test_microvm.fsfiles, 'scratch')
    )
    response = test_microvm.drive.put(
        drive_id='scratch',
        path_on_host=test_microvm.create_jailed_resource(fs.path),
        is_root_device=False,
        is_read_only=False,
        io_engine="Async" if is_io_uring_supported() else "Sync"
    )
    assert test_microvm.api_session.is_status_no_content(response.status_code)

    # Patching drive before boot is not allowed.
    response = test_microvm.drive.patch(
        drive_id='scratch',
        path_on_host='foo.bar'
    )
    assert test_microvm.api_session.is_status_bad_request(response.status_code)
    assert "The requested operation is not supported before starting the " \
        "microVM." in response.text

    test_microvm.start()

    _drive_patch(test_microvm)
Example #6
0
import pytest

from framework import utils
import host_tools.cargo_build as host  # pylint: disable=import-error
from host_tools import proc

# We have different coverages based on the host kernel version. This is
# caused by io_uring, which is only supported by FC for kernels newer
# than 5.10.

# AMD has a slightly different coverage due to
# the appearance of the brand string. On Intel,
# this contains the frequency while on AMD it does not.
# Checkout the cpuid crate. In the future other
# differences may appear.
if utils.is_io_uring_supported():
    COVERAGE_DICT = {"Intel": 84.89, "AMD": 84.38, "ARM": 84.06}
else:
    COVERAGE_DICT = {"Intel": 81.89, "AMD": 81.43, "ARM": 81.05}

PROC_MODEL = proc.proc_type()

COVERAGE_MAX_DELTA = 0.05

CARGO_KCOV_REL_PATH = os.path.join(host.CARGO_BUILD_REL_PATH, 'kcov')

KCOV_COVERAGE_FILE = 'index.js'
"""kcov will aggregate coverage data in this file."""

KCOV_COVERED_LINES_REGEX = r'"covered_lines":"(\d+)"'
"""Regex for extracting number of total covered lines found by kcov."""
Example #7
0
def _drive_patch(test_microvm):
    """Exercise drive patch test scenarios."""
    # Patches without mandatory fields are not allowed.
    response = test_microvm.drive.patch(
        drive_id='scratch'
    )
    assert test_microvm.api_session.is_status_bad_request(response.status_code)
    assert "at least one property to patch: path_on_host, rate_limiter" \
           in response.text

    # Cannot patch drive permissions post boot.
    response = test_microvm.drive.patch(
        drive_id='scratch',
        path_on_host='foo.bar',
        is_read_only=True
    )
    assert test_microvm.api_session.is_status_bad_request(response.status_code)
    assert "unknown field `is_read_only`" in response.text

    # Cannot patch io_engine post boot.
    response = test_microvm.drive.patch(
        drive_id='scratch',
        path_on_host='foo.bar',
        io_engine='Sync'
    )
    assert test_microvm.api_session.is_status_bad_request(response.status_code)
    assert "unknown field `io_engine`" in response.text

    # Updates to `is_root_device` with a valid value are not allowed.
    response = test_microvm.drive.patch(
        drive_id='scratch',
        path_on_host='foo.bar',
        is_root_device=False
    )
    assert test_microvm.api_session.is_status_bad_request(response.status_code)
    assert "unknown field `is_root_device`" in response.text

    # Updates to `path_on_host` with an invalid path are not allowed.
    response = test_microvm.drive.patch(
        drive_id='scratch',
        path_on_host='foo.bar'
    )
    assert test_microvm.api_session.is_status_bad_request(response.status_code)
    assert "drive update (patch): device error: BackingFile(Os { code: 2, " \
        "kind: NotFound, message: \\\"No such file or directory\\\" })" \
        in response.text

    fs = drive_tools.FilesystemFile(
        os.path.join(test_microvm.fsfiles, 'scratch_new')
    )
    # Updates to `path_on_host` with a valid path are allowed.
    response = test_microvm.drive.patch(
        drive_id='scratch',
        path_on_host=test_microvm.create_jailed_resource(fs.path)
    )
    assert test_microvm.api_session.is_status_no_content(response.status_code)

    # Updates to valid `path_on_host` and `rate_limiter` are allowed.
    response = test_microvm.drive.patch(
        drive_id='scratch',
        path_on_host=test_microvm.create_jailed_resource(fs.path),
        rate_limiter={
            'bandwidth': {
                'size': 1000000,
                'refill_time': 100
            },
            'ops': {
                'size': 1,
                'refill_time': 100
            }
        }
    )
    assert test_microvm.api_session.is_status_no_content(response.status_code)

    # Updates to `rate_limiter` only are allowed.
    response = test_microvm.drive.patch(
        drive_id='scratch',
        rate_limiter={
            'bandwidth': {
                'size': 5000,
                'refill_time': 100
            },
            'ops': {
                'size': 500,
                'refill_time': 100
            }
        }
    )
    assert test_microvm.api_session.is_status_no_content(response.status_code)

    # Updates to `rate_limiter` and invalid path fail.
    response = test_microvm.drive.patch(
        drive_id='scratch',
        path_on_host='foo.bar',
        rate_limiter={
            'bandwidth': {
                'size': 5000,
                'refill_time': 100
            },
            'ops': {
                'size': 500,
                'refill_time': 100
            }
        }
    )
    assert test_microvm.api_session.is_status_bad_request(response.status_code)
    assert "No such file or directory" in response.text

    # Validate full vm configuration after patching drives.
    response = test_microvm.full_cfg.get()
    assert test_microvm.api_session.is_status_ok(response.status_code)
    assert response.json()['drives'] == [{
        'drive_id': 'rootfs',
        'path_on_host': '/bionic.rootfs.ext4',
        'is_root_device': True,
        'partuuid': None,
        'is_read_only': False,
        'cache_type': 'Unsafe',
        'io_engine': 'Sync',
        'rate_limiter': None
    }, {
        'drive_id': 'scratch',
        'path_on_host': '/scratch_new.ext4',
        'is_root_device': False,
        'partuuid': None,
        'is_read_only': False,
        'cache_type': 'Unsafe',
        'io_engine': 'Async' if is_io_uring_supported() else 'Sync',
        'rate_limiter': {
            'bandwidth': {
                'size': 5000,
                'one_time_burst': None,
                'refill_time': 100
            },
            'ops': {
                'size': 500,
                'one_time_burst': None,
                'refill_time': 100
            }
        }
    }]