def test_patch_drive_snapshot(bin_cloner_path):
    """
    Test that a patched drive is correctly used by guests loaded from snapshot.

    @type: functional
    """
    logger = logging.getLogger("snapshot_sequence")

    vm_builder = MicrovmBuilder(bin_cloner_path)
    snapshot_type = SnapshotType.FULL
    diff_snapshots = False

    # Use a predefined vm instance.
    vm_instance = vm_builder.build_vm_nano()
    basevm = vm_instance.vm
    root_disk = vm_instance.disks[0]
    ssh_key = vm_instance.ssh_key

    # Add a scratch 128MB RW non-root block device.
    scratchdisk1 = drive_tools.FilesystemFile(tempfile.mktemp(), size=128)
    basevm.add_drive("scratch", scratchdisk1.path)

    basevm.start()
    ssh_connection = net_tools.SSHConnection(basevm.ssh_config)

    # Verify if guest can run commands.
    exit_code, _, _ = ssh_connection.execute_command("sync")
    assert exit_code == 0

    # Update drive to have another backing file, double in size.
    new_file_size_mb = 2 * int(scratchdisk1.size() / (1024 * 1024))
    logger.info("Patch drive, new file: size %sMB.", new_file_size_mb)
    scratchdisk1 = drive_tools.FilesystemFile(tempfile.mktemp(),
                                              new_file_size_mb)
    basevm.patch_drive("scratch", scratchdisk1)

    logger.info("Create %s #0.", snapshot_type)
    # Create a snapshot builder from a microvm.
    snapshot_builder = SnapshotBuilder(basevm)

    disks = [root_disk.local_path(), scratchdisk1.path]
    # Create base snapshot.
    snapshot = snapshot_builder.create(disks, ssh_key, snapshot_type)

    basevm.kill()

    # Load snapshot in a new Firecracker microVM.
    logger.info("Load snapshot, mem %s", snapshot.mem)
    microvm, _ = vm_builder.build_from_snapshot(snapshot,
                                                resume=True,
                                                diff_snapshots=diff_snapshots)
    # Attempt to connect to resumed microvm.
    ssh_connection = net_tools.SSHConnection(microvm.ssh_config)

    # Verify the new microVM has the right scratch drive.
    guest_drive_size = _get_guest_drive_size(ssh_connection)
    assert guest_drive_size == str(scratchdisk1.size())

    microvm.kill()
Example #2
0
def snapshot_resume_producer(logger, vm_builder, snapshot, snapshot_type,
                             use_ramdisk):
    """Produce results for snapshot resume tests."""
    microvm, metrics_fifo = vm_builder.build_from_snapshot(
        snapshot,
        True,
        snapshot_type == SnapshotType.DIFF,
        use_ramdisk=use_ramdisk)

    # Attempt to connect to resumed microvm.
    ssh_connection = net_tools.SSHConnection(microvm.ssh_config)

    # Verify if guest can run commands.
    exit_code, _, _ = ssh_connection.execute_command("ls")
    assert exit_code == 0

    value = 0
    # Parse all metric data points in search of load_snapshot time.
    metrics = microvm.get_all_metrics(metrics_fifo)
    for data_point in metrics:
        metrics = json.loads(data_point)
        cur_value = metrics['latencies_us']['load_snapshot'] / USEC_IN_MSEC
        if cur_value > 0:
            value = cur_value
            break

    logger.info("Latency {} ms".format(value))

    return value
Example #3
0
def test_attach_maximum_devices(test_microvm_with_api, network_config):
    """
    Test attaching maximum number of devices to the microVM.

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

    # Set up a basic microVM.
    test_microvm.basic_config()

    # Add (`MAX_DEVICES_ATTACHED` - 1) devices because the rootfs
    # has already been configured in the `basic_config()`function.
    guest_ips = []
    for i in range(MAX_DEVICES_ATTACHED - 1):
        # Create tap before configuring interface.
        _tap, _host_ip, guest_ip = test_microvm.ssh_network_config(
            network_config, str(i))
        guest_ips.append(guest_ip)

    test_microvm.start()

    # Test that network devices attached are operational.
    for i in range(MAX_DEVICES_ATTACHED - 1):
        test_microvm.ssh_config['hostname'] = guest_ips[i]
        ssh_connection = net_tools.SSHConnection(test_microvm.ssh_config)
        # Verify if guest can run commands.
        exit_code, _, _ = ssh_connection.execute_command("sync")
        assert exit_code == 0
Example #4
0
def test_deflate_on_oom_true(test_microvm_with_ssh_and_balloon,
                             network_config):
    """Verify that setting the `deflate_on_oom` to True works correctly."""
    test_microvm = test_microvm_with_ssh_and_balloon
    test_microvm.spawn()
    test_microvm.basic_config()
    _tap, _, _ = test_microvm.ssh_network_config(network_config, '1')
    test_microvm.ssh_config['ssh_key_path'] = os.path.join(
        test_microvm.fsfiles, 'debian.rootfs.id_rsa')

    # Add a deflated memory balloon.
    response = test_microvm.balloon.put(amount_mb=0,
                                        deflate_on_oom=True,
                                        must_tell_host=False,
                                        stats_polling_interval_s=0)
    assert test_microvm.api_session.is_status_no_content(response.status_code)

    # Start the microvm.
    test_microvm.start()

    # Get an ssh connection to the microvm.
    ssh_connection = net_tools.SSHConnection(test_microvm.ssh_config)

    # Inflate the balloon
    response = test_microvm.balloon.patch(amount_mb=172)
    assert test_microvm.api_session.is_status_no_content(response.status_code)
    time.sleep(5)

    # Check that using memory doesn't lead to an out of memory error.
    # Note that due to `test_deflate_on_oom_false`, we know that
    # if `deflate_on_oom` were set to False, then such an error
    # would have happened.
    make_guest_dirty_memory(ssh_connection)
Example #5
0
def test_handled_signals(test_microvm_with_ssh, network_config):
    """Test that handled signals don't kill the microVM."""
    microvm = test_microvm_with_ssh
    microvm.spawn()

    # We don't need to monitor the memory for this test.
    microvm.memory_events_queue = None

    microvm.basic_config(vcpu_count=2)

    # Configure a network interface.
    _tap, _, _ = microvm.ssh_network_config(network_config, '1')

    microvm.start()
    firecracker_pid = int(microvm.jailer_clone_pid)

    # Open a SSH connection to validate the microVM stays alive.
    ssh_connection = net_tools.SSHConnection(microvm.ssh_config)
    # Just validate a simple command: `nproc`
    cmd = "nproc"
    _, stdout, stderr = ssh_connection.execute_command(cmd)
    assert stderr.read().decode("utf-8") == ""
    assert int(stdout.read().decode("utf-8")) == 2

    # We have a handler installed for this signal.
    os.kill(firecracker_pid, SIGRTMIN+1)

    # Validate the microVM is still up and running.
    _, stdout, stderr = ssh_connection.execute_command(cmd)
    assert stderr.read().decode("utf-8") == ""
    assert int(stdout.read().decode("utf-8")) == 2
def test_with_config_and_metadata_no_api(test_microvm_with_api, vm_config_file,
                                         metadata_file):
    """
    Test microvm start when config/mmds and API server thread is disabled.

    Ensures the metadata is stored successfully inside the MMDS and
    is available to reach from the guest's side.

    @type: functional
    """
    test_microvm = test_microvm_with_api

    _configure_vm_from_json(test_microvm, vm_config_file)
    _add_metadata_file(test_microvm, metadata_file)
    _configure_network_interface(test_microvm)
    test_microvm.jailer.extra_args.update({'no-api': None})

    test_microvm.spawn()

    ssh_connection = net_tools.SSHConnection(test_microvm.ssh_config)

    # Get MMDS version and IPv4 address configured from the file.
    version, ipv4_address = _get_optional_fields_from_file(vm_config_file)

    cmd = 'ip route add {} dev eth0'.format(ipv4_address)
    _, stdout, stderr = ssh_connection.execute_command(cmd)
    assert stderr.read() == stdout.read() == ''

    # Fetch data from MMDS from the guest's side.
    cmd = _build_cmd_to_fetch_metadata(ssh_connection, version, ipv4_address)
    _, stdout, _ = ssh_connection.execute_command(cmd)

    # Compare response against the expected MMDS contents.
    with open(metadata_file, encoding='utf-8') as metadata:
        assert json.load(stdout) == json.load(metadata)
Example #7
0
def _check_drives(test_microvm, assert_dict, keys_array):
    ssh_connection = net_tools.SSHConnection(test_microvm.ssh_config)

    _, stdout, stderr = ssh_connection.execute_command('blockdev --report')
    assert stderr.read().decode('utf-8') == ''
    _process_blockdev_output(stdout.read().decode('utf-8'), assert_dict,
                             keys_array)
Example #8
0
def create_snapshot(bin_cloner_path):
    """Create a snapshot of a microVM."""
    vm_builder = MicrovmBuilder(bin_cloner_path)
    vm_instance = vm_builder.build_vm_nano()
    basevm = vm_instance.vm
    root_disk = vm_instance.disks[0]
    ssh_key = vm_instance.ssh_key

    # Add a memory balloon.
    response = basevm.balloon.put(amount_mib=0,
                                  deflate_on_oom=True,
                                  stats_polling_interval_s=0)
    assert basevm.api_session.is_status_no_content(response.status_code)

    basevm.start()
    ssh_connection = net_tools.SSHConnection(basevm.ssh_config)

    # Verify if guest can run commands.
    exit_code, _, _ = ssh_connection.execute_command("sync")
    assert exit_code == 0

    # Create a snapshot builder from a microvm.
    snapshot_builder = SnapshotBuilder(basevm)

    # Create base snapshot.
    snapshot = snapshot_builder.create([root_disk.local_path()], ssh_key)

    basevm.kill()

    return snapshot
Example #9
0
def test_deflate_on_oom_false(test_microvm_with_ssh_and_balloon,
                              network_config):
    """Verify that setting the `deflate_on_oom` to False works correctly."""
    test_microvm = test_microvm_with_ssh_and_balloon
    test_microvm.spawn()
    test_microvm.basic_config()
    _tap, _, _ = test_microvm.ssh_network_config(network_config, '1')
    test_microvm.ssh_config['ssh_key_path'] = os.path.join(
        test_microvm.fsfiles, 'debian.rootfs.id_rsa')

    # Add a memory balloon.
    response = test_microvm.balloon.put(amount_mib=0,
                                        deflate_on_oom=False,
                                        stats_polling_interval_s=0)
    assert test_microvm.api_session.is_status_no_content(response.status_code)

    # Start the microvm.
    test_microvm.start()

    # Get an ssh connection to the microvm.
    firecracker_pid = test_microvm.jailer_clone_pid
    ssh_connection = net_tools.SSHConnection(test_microvm.ssh_config)

    # Inflate the balloon.
    response = test_microvm.balloon.patch(amount_mib=172)
    assert test_microvm.api_session.is_status_no_content(response.status_code)
    # This call will internally wait for rss to become stable.
    _ = get_stable_rss_mem_by_pid(firecracker_pid)

    # Check that using memory does lead to an out of memory error.
    make_guest_dirty_memory(ssh_connection, should_oom=True)
def create_512mb_full_snapshot(bin_cloner_path,
                               target_version: str = None,
                               fc_binary=None,
                               jailer_binary=None):
    """Create a snapshoft from a 2vcpu 512MB microvm."""
    vm_instance = C3micro.spawn(bin_cloner_path, True, fc_binary,
                                jailer_binary)
    # Attempt to connect to the fresh microvm.
    ssh_connection = net_tools.SSHConnection(vm_instance.vm.ssh_config)

    # Run a fio workload and validate succesfull execution.
    fio = """fio --filename=/dev/vda --direct=1 --rw=randread --bs=4k \
    --ioengine=libaio --iodepth=16 --runtime=2 --numjobs=4 --time_based \
    --group_reporting --name=iops-test-job --eta-newline=1 --readonly"""

    exit_code, _, _ = ssh_connection.execute_command(fio)
    assert exit_code == 0

    # Create a snapshot builder from a microvm.
    snapshot_builder = SnapshotBuilder(vm_instance.vm)

    # The snapshot builder expects disks as paths, not artifacts.
    disks = []
    for disk in vm_instance.disks:
        disks.append(disk.local_path())

    snapshot = snapshot_builder.create(disks, vm_instance.ssh_key,
                                       SnapshotType.FULL, target_version)

    vm_instance.vm.kill()
    return snapshot
def test_restore_in_past_versions(bin_cloner_path):
    """Test scenario: create a snapshot and restore in previous versions."""
    logger = logging.getLogger("snapshot_version")

    artifacts = ArtifactCollection(_test_images_s3_bucket())
    # Fetch all snapshots artifacts.
    # "fc_release" is the key that should be used for per release snapshot
    # artifacts. Such snapshots are created at release time and target the
    # current version. We are going to restore all these snapshots with current
    # testing build.
    firecracker_artifacts = artifacts.firecrackers()
    for firecracker in firecracker_artifacts:
        firecracker.download()
        jailer = firecracker.jailer()
        jailer.download()
        # The target version is in the name of the firecracker binary from S3.
        # We also strip the "v" as fc expects X.Y.Z version string.
        target_version = firecracker.base_name()[1:]
        logger.info("Creating snapshot for version: %s", target_version)

        # Create a fresh snapshot targeted at the binary artifact version.
        snapshot = create_512mb_full_snapshot(bin_cloner_path, target_version)

        builder = MicrovmBuilder(bin_cloner_path, firecracker.local_path(),
                                 jailer.local_path())
        microvm, _ = builder.build_from_snapshot(snapshot, True, False)

        logger.info("Using Firecracker: %s", firecracker.local_path())
        logger.info("Using Jailer: %s", jailer.local_path())

        # Attempt to connect to resumed microvm.
        ssh_connection = net_tools.SSHConnection(microvm.ssh_config)

        exit_code, _, _ = ssh_connection.execute_command("sleep 1 && sync")
        assert exit_code == 0
Example #12
0
def _g2h_send_ping(context):
    """Send ping from guest to host."""
    logger = context.custom['logger']
    vm_builder = context.custom['builder']
    interval_between_req = context.custom['interval']
    name = context.custom['name']
    file_dumper = context.custom['results_file_dumper']

    logger.info("Testing {} with microvm: \"{}\", kernel {}, disk {} ".format(
        name, context.microvm.name(), context.kernel.name(),
        context.disk.name()))

    # Create a rw copy artifact.
    rw_disk = context.disk.copy()
    # Get ssh key from read-only artifact.
    ssh_key = context.disk.ssh_key()
    # Create a fresh microvm from aftifacts.
    basevm = vm_builder.build(kernel=context.kernel,
                              disks=[rw_disk],
                              ssh_key=ssh_key,
                              config=context.microvm)

    basevm.start()

    # Check if the needed CPU cores are available. We have the API thread, VMM
    # thread and then one thread for each configured vCPU.
    assert CpuMap.len() >= 2 + basevm.vcpus_count

    # Pin uVM threads to physical cores.
    current_cpu_id = 0
    assert basevm.pin_vmm(current_cpu_id), \
        "Failed to pin firecracker thread."
    current_cpu_id += 1
    assert basevm.pin_api(current_cpu_id), \
        "Failed to pin fc_api thread."
    for i in range(basevm.vcpus_count):
        current_cpu_id += 1
        assert basevm.pin_vcpu(i, current_cpu_id + i), \
            f"Failed to pin fc_vcpu {i} thread."

    custom = {
        "microvm": context.microvm.name(),
        "kernel": context.kernel.name(),
        "disk": context.disk.name(),
        "cpu_model_name": get_cpu_model_name()
    }

    st_core = core.Core(name="network_latency", iterations=1, custom=custom)
    cons = consumer.LambdaConsumer(
        func=consume_ping_output,
        func_kwargs={"requests": context.custom['requests']})
    cmd = PING.format(context.custom['requests'], interval_between_req,
                      DEFAULT_HOST_IP)
    prod = producer.SSHCommand(cmd, net_tools.SSHConnection(basevm.ssh_config))
    st_core.add_pipe(producer=prod, consumer=cons, tag="ping")

    # Gather results and verify pass criteria.
    result = st_core.run_exercise(file_dumper is None)
    if file_dumper:
        file_dumper.writeln(json.dumps(result))
def test_restore_from_past_versions(bin_cloner_path):
    """Test scenario: restore all previous version snapshots."""
    logger = logging.getLogger("snapshot_version")

    artifacts = ArtifactCollection(_test_images_s3_bucket())
    # Fetch all firecracker binaries.
    # With each binary create a snapshot and try to restore in current
    # version.
    firecracker_artifacts = artifacts.firecrackers()
    for firecracker in firecracker_artifacts:
        firecracker.download()
        jailer = firecracker.jailer()
        jailer.download()

        logger.info("Source Firecracker: %s", firecracker.local_path())
        logger.info("Source Jailer: %s", jailer.local_path())
        # Create a fresh snapshot using the binary artifacts.
        builder = MicrovmBuilder(bin_cloner_path, firecracker.local_path(),
                                 jailer.local_path())
        snapshot = create_512mb_full_snapshot(bin_cloner_path, None,
                                              firecracker.local_path(),
                                              jailer.local_path())
        microvm, _ = builder.build_from_snapshot(snapshot, True, False)
        ssh_connection = net_tools.SSHConnection(microvm.ssh_config)
        exit_code, _, _ = ssh_connection.execute_command("sleep 1 && sync")

        assert exit_code == 0
Example #14
0
def _test_rss_memory_lower(test_microvm):
    """Check inflating the balloon makes guest use less rss memory."""
    # Get the firecracker pid, and open an ssh connection.
    firecracker_pid = test_microvm.jailer_clone_pid
    ssh_connection = net_tools.SSHConnection(test_microvm.ssh_config)

    # Using deflate_on_oom, get the RSS as low as possible
    response = test_microvm.balloon.patch(amount_mib=200)
    assert test_microvm.api_session.is_status_no_content(response.status_code)

    # Get initial rss consumption.
    init_rss = get_stable_rss_mem_by_pid(firecracker_pid)

    # Get the balloon back to 0.
    response = test_microvm.balloon.patch(amount_mib=0)
    assert test_microvm.api_session.is_status_no_content(response.status_code)
    # This call will internally wait for rss to become stable.
    _ = get_stable_rss_mem_by_pid(firecracker_pid)

    # Dirty memory, then inflate balloon and get ballooned rss consumption.
    make_guest_dirty_memory(ssh_connection)

    response = test_microvm.balloon.patch(amount_mib=200)
    assert test_microvm.api_session.is_status_no_content(response.status_code)
    balloon_rss = get_stable_rss_mem_by_pid(firecracker_pid)

    # Check that the ballooning reclaimed the memory.
    assert balloon_rss - init_rss <= 15000
Example #15
0
def test_handled_signals(test_microvm_with_ssh, network_config):
    """Test that handled signals don't kill the microVM."""
    microvm = test_microvm_with_ssh
    microvm.spawn()

    # We don't need to monitor the memory for this test.
    microvm.memory_monitor = None

    microvm.basic_config(vcpu_count=2)

    # Configure a network interface.
    _tap, _, _ = microvm.ssh_network_config(network_config, '1')

    microvm.start()
    firecracker_pid = int(microvm.jailer_clone_pid)

    # Open a SSH connection to validate the microVM stays alive.
    ssh_connection = net_tools.SSHConnection(microvm.ssh_config)
    # Just validate a simple command: `nproc`
    cmd = "nproc"
    _, stdout, stderr = ssh_connection.execute_command(cmd)
    assert stderr.read() == ""
    assert int(stdout.read()) == 2

    # We have a handler installed for this signal.
    # The 35 is the SIGRTMIN for musl libc.
    # We hardcode this value since the SIGRTMIN python reports
    # is 34, which is likely the one for glibc.
    os.kill(firecracker_pid, 35)

    # Validate the microVM is still up and running.
    _, stdout, stderr = ssh_connection.execute_command(cmd)
    assert stderr.read() == ""
    assert int(stdout.read()) == 2
Example #16
0
def test_reboot(test_microvm_with_ssh, network_config):
    """Test reboot from guest kernel."""
    test_microvm = test_microvm_with_ssh
    test_microvm.jailer.daemonize = False
    test_microvm.spawn()

    # We don't need to monitor the memory for this test because we are
    # just rebooting and the process dies before pmap gets the RSS.
    test_microvm.memory_monitor = None

    # Set up the microVM with 4 vCPUs, 256 MiB of RAM, 0 network ifaces, and
    # a root file system with the rw permission. The network interfaces is
    # added after we get a unique MAC and IP.
    test_microvm.basic_config(vcpu_count=4)
    _tap, _, _ = test_microvm.ssh_network_config(network_config, '1')

    # Configure metrics system.
    metrics_fifo_path = os.path.join(test_microvm.path, 'metrics_fifo')
    metrics_fifo = log_tools.Fifo(metrics_fifo_path)
    response = test_microvm.metrics.put(
        metrics_path=test_microvm.create_jailed_resource(metrics_fifo.path)
    )
    assert test_microvm.api_session.is_status_no_content(response.status_code)

    test_microvm.start()

    # Get Firecracker PID so we can count the number of threads.
    firecracker_pid = test_microvm.jailer_clone_pid

    # Get number of threads in Firecracker
    cmd = 'ps -o nlwp {} | tail -1 | awk \'{{print $1}}\''.format(
        firecracker_pid
    )
    _, stdout, _ = utils.run_cmd(cmd)
    nr_of_threads = stdout.rstrip()
    assert int(nr_of_threads) == 6

    # Consume existing metrics
    lines = metrics_fifo.sequential_reader(100)
    assert len(lines) == 1
    # Rebooting Firecracker sends an exit event and should gracefully kill.
    # the instance.
    ssh_connection = net_tools.SSHConnection(test_microvm.ssh_config)

    ssh_connection.execute_command("reboot")

    while True:
        # Pytest's timeout will kill the test even if the loop doesn't exit.
        try:
            os.kill(firecracker_pid, 0)
            time.sleep(0.01)
        except OSError:
            break

    # Consume existing metrics
    lines = metrics_fifo.sequential_reader(100)
    assert len(lines) == 1

    # Make sure that the FC process was not killed by a seccomp fault
    assert json.loads(lines[0])["seccomp"]["num_faults"] == 0
Example #17
0
def test_device_ordering(test_microvm_with_ssh, network_config):
    """Verify device ordering.

    The root device should correspond to /dev/vda in the guest and
    the order of the other devices should match their configuration order.
    """
    test_microvm = test_microvm_with_ssh
    test_microvm.spawn()

    # Add first scratch block device.
    fs1 = drive_tools.FilesystemFile(
        os.path.join(test_microvm.fsfiles, 'scratch1'),
        size=128
    )
    test_microvm.add_drive(
        'scratch1',
        fs1.path
    )

    # Set up the microVM with 1 vCPUs, 256 MiB of RAM, 0 network ifaces,
    # a read-write root file system (this is the second block device added).
    # The network interface is added after we get a unique MAC and IP.
    test_microvm.basic_config()

    # Add the third block device.
    fs2 = drive_tools.FilesystemFile(
        os.path.join(test_microvm.fsfiles, 'scratch2'),
        size=512
    )
    test_microvm.add_drive(
        'scratch2',
        fs2.path
    )

    _tap, _, _ = test_microvm_with_ssh.ssh_network_config(network_config, '1')

    test_microvm.start()

    # Determine the size of the microVM rootfs in bytes.
    try:
        result = utils.run_cmd(
            'du --apparent-size --block-size=1 {}'
            .format(test_microvm.rootfs_file),
        )
    except ChildProcessError:
        pytest.skip('Failed to get microVM rootfs size: {}'
                    .format(result.stderr))

    assert len(result.stdout.split()) == 2
    rootfs_size = result.stdout.split('\t')[0]

    # The devices were added in this order: fs1, rootfs, fs2.
    # However, the rootfs is the root device and goes first,
    # so we expect to see this order: rootfs, fs1, fs2.
    # The devices are identified by their size.
    ssh_connection = net_tools.SSHConnection(test_microvm.ssh_config)
    _check_block_size(ssh_connection, '/dev/vda', rootfs_size)
    _check_block_size(ssh_connection, '/dev/vdb', fs1.size())
    _check_block_size(ssh_connection, '/dev/vdc', fs2.size())
Example #18
0
def test_cpu_template(test_microvm_with_ssh, network_config, cpu_template):
    """Check that AVX2 & AVX512 instructions are disabled.

    This is a rather dummy test for checking that some features are not
    exposed by mistake. It is a first step into checking the t2 & c3
    templates. In a next iteration we should check **all** cpuid entries, not
    just these features. We can achieve this with a template
    containing all features on a t2/c3 instance and check that the cpuid in
    the guest is an exact match of the template.
    """
    common_masked_features = ["avx512", "mpx", "clflushopt", "clwb", "xsavec",
                              "xgetbv1", "xsaves", "pku", "ospke"]
    c3_masked_features = ["avx2"]

    test_microvm = test_microvm_with_ssh
    test_microvm.spawn()

    test_microvm.basic_config(vcpu_count=1)
    # Set template as specified in the `cpu_template` parameter.
    response = test_microvm.machine_cfg.put(
        vcpu_count=1,
        mem_size_mib=256,
        ht_enabled=False,
        cpu_template=cpu_template,
    )
    assert test_microvm.api_session.is_status_no_content(response.status_code)
    _tap, _, _ = test_microvm.ssh_network_config(network_config, '1')

    response = test_microvm.actions.put(action_type='InstanceStart')
    if get_cpu_vendor() != CpuVendor.INTEL:
        # We shouldn't be able to apply Intel templates on AMD hosts
        assert test_microvm.api_session.is_status_bad_request(
            response.status_code)
        return

    assert test_microvm.api_session.is_status_no_content(
            response.status_code)

    ssh_connection = net_tools.SSHConnection(test_microvm.ssh_config)
    guest_cmd = "cat /proc/cpuinfo | grep 'flags' | head -1"
    _, stdout, stderr = ssh_connection.execute_command(guest_cmd)
    assert stderr.read() == ''

    cpu_flags_output = stdout.readline().rstrip()

    if cpu_template == "C3":
        for feature in c3_masked_features:
            assert feature not in cpu_flags_output
    # Check that all features in `common_masked_features` are properly masked.
    for feature in common_masked_features:
        assert feature not in cpu_flags_output

    # Check if XSAVE PKRU is masked for T3/C2.
    expected_cpu_features = {
        "XCR0 supported: PKRU state": "false"
    }

    _check_guest_cmd_output(test_microvm, "cpuid -1", None, '=',
                            expected_cpu_features)
Example #19
0
def test_guest_mmds_hang(test_microvm_with_ssh, network_config):
    """Test the MMDS json response."""
    test_microvm = test_microvm_with_ssh
    test_microvm.spawn()

    response = test_microvm.mmds.get()
    assert test_microvm.api_session.is_status_ok(response.status_code)
    assert response.json() == {}

    data_store = {
        'latest': {
            'meta-data': {
                'ami-id': 'ami-12345678'
            }
        }
    }
    response = test_microvm.mmds.put(json=data_store)
    assert test_microvm.api_session.is_status_no_content(response.status_code)

    response = test_microvm.mmds.get()
    assert test_microvm.api_session.is_status_ok(response.status_code)
    assert response.json() == data_store

    test_microvm.basic_config(vcpu_count=1)
    _tap = test_microvm.ssh_network_config(
         network_config,
         '1',
         allow_mmds_requests=True
    )

    test_microvm.start()
    ssh_connection = net_tools.SSHConnection(test_microvm.ssh_config)

    cmd = 'ip route add 169.254.169.254 dev eth0'
    _, stdout, stderr = ssh_connection.execute_command(cmd)
    _assert_out(stdout, stderr, '')

    # Test for a GET request with a content length longer than
    # the actual length of the body.
    cmd = 'curl -m 2 -s'
    cmd += ' -X GET'
    cmd += ' -H  "Content-Length: 100"'
    cmd += ' -H "Accept: application/json"'
    cmd += ' -d "some body"'
    cmd += ' http://169.254.169.254/'

    _, stdout, _ = ssh_connection.execute_command(cmd)
    assert 'Invalid request' in stdout.read()

    # Do the same for a PUT request.
    cmd = 'curl -m 2 -s'
    cmd += ' -X PUT'
    cmd += ' -H  "Content-Length: 100"'
    cmd += ' -H "Accept: application/json"'
    cmd += ' -d "some body"'
    cmd += ' http://169.254.169.254/'

    _, stdout, _ = ssh_connection.execute_command(cmd)
    assert 'Invalid request' in stdout.read()
Example #20
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 #21
0
def _test_pause_resume(context):
    logger = context.custom['logger']
    vm_builder = context.custom['builder']

    logger.info("Testing microvm: \"{}\" with kernel {} and disk {} ".format(
        context.microvm.name(), context.kernel.name(), context.disk.name()))

    microvm = vm_builder.build(context.kernel, [context.disk], context.microvm)
    tap = microvm.ssh_network_config(context.custom['network_config'], '1')

    # Pausing the microVM before being started is not allowed.
    response = microvm.vm.patch(state='Paused')
    assert microvm.api_session.is_status_bad_request(response.status_code)

    # Resuming the microVM before being started is also not allowed.
    response = microvm.vm.patch(state='Resumed')
    assert microvm.api_session.is_status_bad_request(response.status_code)

    microvm.start()

    ssh_connection = net_tools.SSHConnection(microvm.ssh_config)

    # Verify guest is active.
    exit_code, _, _ = ssh_connection.execute_command("ls")
    assert exit_code == 0

    # Pausing the microVM after it's been started is successful.
    response = microvm.vm.patch(state='Paused')
    assert microvm.api_session.is_status_no_content(response.status_code)

    # Verify guest is no longer active.
    exit_code, _, _ = ssh_connection.execute_command("ls")
    assert exit_code != 0

    # Pausing the microVM when it is already `Paused` is allowed
    # (microVM remains in `Paused` state).
    response = microvm.vm.patch(state='Paused')
    assert microvm.api_session.is_status_no_content(response.status_code)

    # Resuming the microVM is successful.
    response = microvm.vm.patch(state='Resumed')
    assert microvm.api_session.is_status_no_content(response.status_code)

    # Verify guest is active again.
    exit_code, _, _ = ssh_connection.execute_command("ls")
    assert exit_code == 0

    # Resuming the microVM when it is already `Resumed` is allowed
    # (microVM remains in the running state).
    response = microvm.vm.patch(state='Resumed')
    assert microvm.api_session.is_status_no_content(response.status_code)

    # Verify guest is still active.
    exit_code, _, _ = ssh_connection.execute_command("ls")
    assert exit_code == 0

    microvm.kill()
    del tap
Example #22
0
def test_pause_resume(test_microvm_with_ssh, network_config):
    """Test pausing and resuming the vCPUs."""
    test_microvm = test_microvm_with_ssh
    test_microvm.spawn()

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

    # Create tap before configuring interface.
    _tap1, _, _ = test_microvm.ssh_network_config(network_config, '1')

    # Pausing the microVM before being started is not allowed.
    response = test_microvm.vm.patch(state='Paused')
    assert test_microvm.api_session.is_status_bad_request(response.status_code)

    # Resuming the microVM before being started is also not allowed.
    response = test_microvm.vm.patch(state='Resumed')
    assert test_microvm.api_session.is_status_bad_request(response.status_code)

    # Start microVM.
    test_microvm.start()

    ssh_connection = net_tools.SSHConnection(test_microvm.ssh_config)

    # Verify guest is active.
    exit_code, _, _ = ssh_connection.execute_command("ls")
    assert exit_code == 0

    # Pausing the microVM after it's been started is successful.
    response = test_microvm.vm.patch(state='Paused')
    assert test_microvm.api_session.is_status_no_content(response.status_code)

    # Verify guest is no longer active.
    exit_code, _, _ = ssh_connection.execute_command("ls")
    assert exit_code != 0

    # Pausing the microVM when it is already `Paused` is allowed
    # (microVM remains in `Paused` state).
    response = test_microvm.vm.patch(state='Paused')
    assert test_microvm.api_session.is_status_no_content(response.status_code)

    # Resuming the microVM is successful.
    response = test_microvm.vm.patch(state='Resumed')
    assert test_microvm.api_session.is_status_no_content(response.status_code)

    # Verify guest is active again.
    exit_code, _, _ = ssh_connection.execute_command("ls")
    assert exit_code == 0

    # Resuming the microVM when it is already `Resumed` is allowed
    # (microVM remains in the running state).
    response = test_microvm.vm.patch(state='Resumed')
    assert test_microvm.api_session.is_status_no_content(response.status_code)

    # Verify guest is still active.
    exit_code, _, _ = ssh_connection.execute_command("ls")
    assert exit_code == 0
Example #23
0
def check_masked_features(test_microvm, cpu_template):
    """Check that AVX2 & AVX512 instructions are disabled."""
    common_masked_features_lscpu = ["dtes64", "monitor", "ds_cpl", "tm2",
                                    "cnxt-id", "sdbg", "xtpr", "pdcm",
                                    "osxsave",
                                    "psn", "ds", "acpi", "tm", "ss", "pbe",
                                    "fpdp", "rdt_m", "rdt_a", "mpx", "avx512f",
                                    "intel_pt",
                                    "avx512_vpopcntdq",
                                    "3dnowprefetch", "pdpe1gb"]

    common_masked_features_cpuid = {"SGX": "false", "HLE": "false",
                                    "RTM": "false", "RDSEED": "false",
                                    "ADX": "false", "AVX512IFMA": "false",
                                    "CLFLUSHOPT": "false", "CLWB": "false",
                                    "AVX512PF": "false", "AVX512ER": "false",
                                    "AVX512CD": "false", "SHA": "false",
                                    "AVX512BW": "false", "AVX512VL": "false",
                                    "AVX512VBMI": "false", "PKU": "false",
                                    "OSPKE": "false", "RDPID": "false",
                                    "SGX_LC": "false",
                                    "AVX512_4VNNIW": "false",
                                    "AVX512_4FMAPS": "false",
                                    "XSAVEC": "false", "XGETBV": "false",
                                    "XSAVES": "false"}

    # These are all discoverable by cpuid -1.
    c3_masked_features = {"FMA": "false", "MOVBE": "false", "BMI": "false",
                          "AVX2": "false", "BMI2": "false", "INVPCID": "false"}
    # Check that all common features discoverable with lscpu
    # are properly masked.
    ssh_connection = net_tools.SSHConnection(test_microvm.ssh_config)
    guest_cmd = "cat /proc/cpuinfo | grep 'flags' | head -1"
    _, stdout, stderr = ssh_connection.execute_command(guest_cmd)
    assert stderr.read() == ''

    cpu_flags_output = stdout.readline().rstrip().split(' ')

    for feature in common_masked_features_lscpu:
        assert feature not in cpu_flags_output, feature

    # Check that all common features discoverable with cpuid
    # are properly masked.
    utils.check_guest_cpuid_output(test_microvm, "cpuid -1", None, '=',
                                   common_masked_features_cpuid)

    if cpu_template == "C3":
        utils.check_guest_cpuid_output(test_microvm, "cpuid -1", None, '=',
                                       c3_masked_features)

    # Check if XSAVE PKRU is masked for T3/C2.
    expected_cpu_features = {
        "XCR0 supported: PKRU state": "false"
    }

    utils.check_guest_cpuid_output(test_microvm, "cpuid -1", None, '=',
                                   expected_cpu_features)
Example #24
0
def _run_iperf_on_guest(test_microvm, iperf_cmd, hostname):
    """Run a client related iperf command through an SSH connection."""
    test_microvm.ssh_config['hostname'] = hostname
    ssh_connection = net_tools.SSHConnection(test_microvm.ssh_config)
    _, stdout, stderr = ssh_connection.execute_command(iperf_cmd)
    assert stderr.read() == ''

    out = stdout.read()
    return out
def test_brand_string(test_microvm_with_api, network_config):
    """
    Ensure good formatting for the guest brand string.

    * For Intel CPUs, the guest brand string should be:
        Intel(R) Xeon(R) Processor @ {host frequency}
    where {host frequency} is the frequency reported by the host CPUID
    (e.g. 4.01GHz)
    * For AMD CPUs, the guest brand string should be:
        AMD EPYC
    * For other CPUs, the guest brand string should be:
        ""

    @type: functional
    """
    cif = open("/proc/cpuinfo", "r", encoding="utf-8")
    host_brand_string = None
    while True:
        line = cif.readline()
        if line == "":
            break
        mo = re.search("^model name\\s+:\\s+(.+)$", line)
        if mo:
            host_brand_string = mo.group(1)
    cif.close()
    assert host_brand_string is not None

    test_microvm = test_microvm_with_api
    test_microvm.spawn()

    test_microvm.basic_config(vcpu_count=1)
    _tap, _, _ = test_microvm.ssh_network_config(network_config, "1")
    test_microvm.start()

    ssh_connection = net_tools.SSHConnection(test_microvm.ssh_config)

    guest_cmd = "cat /proc/cpuinfo | grep 'model name' | head -1"
    _, stdout, stderr = ssh_connection.execute_command(guest_cmd)
    assert stderr.read() == ""

    line = stdout.readline().rstrip()
    mo = re.search("^model name\\s+:\\s+(.+)$", line)
    assert mo
    guest_brand_string = mo.group(1)
    assert guest_brand_string

    cpu_vendor = utils.get_cpu_vendor()
    expected_guest_brand_string = ""
    if cpu_vendor == utils.CpuVendor.AMD:
        expected_guest_brand_string += "AMD EPYC"
    elif cpu_vendor == utils.CpuVendor.INTEL:
        expected_guest_brand_string = "Intel(R) Xeon(R) Processor"
        mo = re.search("[.0-9]+[MG]Hz", host_brand_string)
        if mo:
            expected_guest_brand_string += " @ " + mo.group(0)

    assert guest_brand_string == expected_guest_brand_string
Example #26
0
def create_snapshots(context):
    """Snapshot microVM built from vm configuration file."""
    vm = setup_vm(context)

    # Get ssh key from read-only artifact.
    ssh_key = context.disk.ssh_key()
    ssh_key.download(vm.path)
    vm.ssh_config['ssh_key_path'] = ssh_key.local_path()
    os.chmod(vm.ssh_config['ssh_key_path'], 0o400)
    cpu_template = context.custom['cpu_template']

    fn = partial(add_cpu_template, cpu_template)
    _configure_vm_from_json(vm, VM_CONFIG_FILE, json_xform=fn)
    configure_network_interfaces(vm)
    vm.spawn()

    # Ensure the microVM has started.
    response = vm.machine_cfg.get()
    assert vm.api_session.is_status_ok(response.status_code)
    assert vm.state == "Running"

    # Populate MMDS.
    data_store = {
        'latest': {
            'meta-data': {
                'ami-id': 'ami-12345678',
                'reservation-id': 'r-fea54097',
                'local-hostname': 'ip-10-251-50-12.ec2.internal',
                'public-hostname': 'ec2-203-0-113-25.compute-1.amazonaws.com'
            }
        }
    }
    populate_mmds(vm, data_store)

    # Iterate and validate connectivity on all ifaces after boot.
    for iface in net_ifaces:
        vm.ssh_config['hostname'] = iface.guest_ip
        ssh_connection = net_tools.SSHConnection(vm.ssh_config)
        exit_code, _, _ = ssh_connection.execute_command("sync")
        assert exit_code == 0

    # Validate MMDS.
    validate_mmds(ssh_connection, data_store)

    # Create a snapshot builder from a microVM.
    snapshot_builder = SnapshotBuilder(vm)

    # Snapshot the microVM.
    snapshot = snapshot_builder.create([vm.rootfs_file],
                                       ssh_key,
                                       SnapshotType.DIFF,
                                       net_ifaces=net_ifaces)

    copy_snapshot_artifacts(snapshot, vm.rootfs_file, context.kernel.name(),
                            ssh_key, cpu_template)

    vm.kill()
Example #27
0
def test_rescan_dev(test_microvm_with_api, network_config):
    """
    Verify that rescan works with a device-backed virtio device.

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

    # Set up the microVM with 1 vCPUs, 256 MiB of RAM, 0 network ifaces and
    # a root file system with the rw permission. The network interface is
    # added after we get a unique MAC and IP.
    test_microvm.basic_config()

    _tap, _, _ = test_microvm_with_api.ssh_network_config(network_config, '1')

    # Add a scratch block device.
    fs1 = drive_tools.FilesystemFile(os.path.join(test_microvm.fsfiles, 'fs1'))
    test_microvm.add_drive(
        'scratch',
        fs1.path
    )

    test_microvm.start()

    ssh_connection = net_tools.SSHConnection(test_microvm.ssh_config)

    _check_block_size(ssh_connection, '/dev/vdb', fs1.size())

    fs2 = drive_tools.FilesystemFile(
        os.path.join(test_microvm.fsfiles, 'fs2'),
        size=512
    )

    losetup = ['losetup', '--find', '--show', fs2.path]
    loopback_device = None
    result = None
    try:
        result = utils.run_cmd(losetup)
        loopback_device = result.stdout.rstrip()
    except ChildProcessError:
        pytest.skip('failed to create a lookback device: ' +
                    f'stdout={result.stdout}, stderr={result.stderr}')

    try:
        response = test_microvm.drive.patch(
            drive_id='scratch',
            path_on_host=test_microvm.create_jailed_resource(loopback_device),
        )
        assert session.is_status_no_content(response.status_code)

        _check_block_size(ssh_connection, '/dev/vdb', fs2.size())
    finally:
        if loopback_device:
            utils.run_cmd(['losetup', '--detach', loopback_device])
Example #28
0
def _test_memory_scrub(context):
    vm_builder = context.custom['builder']

    # Create a rw copy artifact.
    root_disk = context.disk.copy()
    # Get ssh key from read-only artifact.
    ssh_key = context.disk.ssh_key()
    # Create a fresh microvm from aftifacts.
    microvm = vm_builder.build(
        kernel=context.kernel,
        disks=[root_disk],
        ssh_key=ssh_key,
        config=context.microvm
    )

    copy_util_to_rootfs(root_disk.local_path(), 'fillmem')
    copy_util_to_rootfs(root_disk.local_path(), 'readmem')

    # Add a memory balloon with stats enabled.
    response = microvm.balloon.put(
        amount_mib=0,
        deflate_on_oom=True,
        stats_polling_interval_s=1
    )
    assert microvm.api_session.is_status_no_content(response.status_code)

    microvm.start()

    ssh_connection = net_tools.SSHConnection(microvm.ssh_config)

    # Dirty 60MB of pages.
    make_guest_dirty_memory(ssh_connection, amount=(60 * MB_TO_PAGES))

    # Now inflate the balloon with 60MB of pages.
    response = microvm.balloon.patch(amount_mib=60)
    assert microvm.api_session.is_status_no_content(response.status_code)

    # Get the firecracker pid, and open an ssh connection.
    firecracker_pid = microvm.jailer_clone_pid

    # Wait for the inflate to complete.
    _ = get_stable_rss_mem_by_pid(firecracker_pid)

    # Deflate the balloon completely.
    response = microvm.balloon.patch(amount_mib=0)
    assert microvm.api_session.is_status_no_content(response.status_code)

    # Wait for the deflate to complete.
    _ = get_stable_rss_mem_by_pid(firecracker_pid)

    exit_code, _, _ = ssh_connection.execute_command(
        "/sbin/readmem {} {}".format(60, 1)
    )
    assert exit_code == 0

    microvm.kill()
def test_brand_string(test_microvm_with_ssh, network_config):
    """Ensure good formatting for the guest band string.

    * For Intel CPUs, the guest brand string should be:
        Intel(R) Xeon(R) Processor @ {host frequency}
    where {host frequency} is the frequency reported by the host CPUID
    (e.g. 4.01GHz)
    * For non-Intel CPUs, the guest brand string should be:
        Intel(R) Xeon(R) Processor
    """
    cif = open('/proc/cpuinfo', 'r')
    host_brand_string = None
    host_vendor_id = None
    while True:
        line = cif.readline()
        if line == '':
            break
        mo = re.search("^vendor_id\\s+:\\s+(.+)$", line)
        if mo:
            host_vendor_id = mo.group(1)
        mo = re.search("^model name\\s+:\\s+(.+)$", line)
        if mo:
            host_brand_string = mo.group(1)
    cif.close()
    assert host_vendor_id is not None
    assert host_brand_string is not None

    test_microvm = test_microvm_with_ssh
    test_microvm.spawn()

    test_microvm.basic_config(vcpu_count=1)
    _tap, _, _ = test_microvm.ssh_network_config(network_config, '1')
    test_microvm.start()

    ssh_connection = net_tools.SSHConnection(test_microvm.ssh_config)

    guest_cmd = "cat /proc/cpuinfo | grep 'model name' | head -1"
    _, stdout, stderr = ssh_connection.execute_command(guest_cmd)
    assert stderr.read().decode("utf-8") == ''

    line = stdout.readline().decode('utf-8').rstrip()
    mo = re.search("^model name\\s+:\\s+(.+)$", line)
    assert mo
    guest_brand_string = mo.group(1)
    assert guest_brand_string

    expected_guest_brand_string = ""
    if host_vendor_id == "GenuineIntel":
        expected_guest_brand_string = "Intel(R) Xeon(R) Processor"
        mo = re.search("[.0-9]+[MG]Hz", host_brand_string)
        if mo:
            expected_guest_brand_string += " @ " + mo.group(0)
    elif host_vendor_id == "AuthenticAMD":
        expected_guest_brand_string += "AMD EPYC"

    assert guest_brand_string == expected_guest_brand_string
Example #30
0
def test_serial_block(test_microvm_with_api, network_config):
    """
    Test that writing to stdout never blocks the vCPU thread.

    @type: functional
    """
    test_microvm = test_microvm_with_api
    test_microvm.jailer.daemonize = False
    test_microvm.spawn()
    # Set up the microVM with 1 vCPU so we make sure the vCPU thread
    # responsible for the SSH connection will also run the serial.
    test_microvm.basic_config(
        vcpu_count=1,
        mem_size_mib=512,
        boot_args="console=ttyS0 reboot=k panic=1 pci=off",
    )

    _tap, _, _ = test_microvm.ssh_network_config(network_config, "1")

    # Configure the metrics.
    metrics_fifo_path = os.path.join(test_microvm.path, "metrics_fifo")
    metrics_fifo = log_tools.Fifo(metrics_fifo_path)
    response = test_microvm.metrics.put(
        metrics_path=test_microvm.create_jailed_resource(metrics_fifo.path))
    assert test_microvm.api_session.is_status_no_content(response.status_code)

    test_microvm.start()
    ssh_connection = net_tools.SSHConnection(test_microvm.ssh_config)

    # Get an initial reading of missed writes to the serial.
    fc_metrics = test_microvm.flush_metrics(metrics_fifo)
    init_count = fc_metrics["uart"]["missed_write_count"]

    screen_pid = test_microvm.screen_pid
    # Stop `screen` process which captures stdout so we stop consuming stdout.
    subprocess.check_call("kill -s STOP {}".format(screen_pid), shell=True)

    # Generate a random text file.
    exit_code, _, _ = ssh_connection.execute_command(
        "base64 /dev/urandom | head -c 100000 > file.txt")

    # Dump output to terminal
    exit_code, _, _ = ssh_connection.execute_command(
        "cat file.txt > /dev/ttyS0")
    assert exit_code == 0

    # Check that the vCPU isn't blocked.
    exit_code, _, _ = ssh_connection.execute_command("cd /")
    assert exit_code == 0

    # Check the metrics to see if the serial missed bytes.
    fc_metrics = test_microvm.flush_metrics(metrics_fifo)
    last_count = fc_metrics["uart"]["missed_write_count"]

    # Should be significantly more than before the `cat` command.
    assert last_count - init_count > 10000