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())
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()
Beispiel #3
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])
Beispiel #4
0
def test_rescan_dev(test_microvm_with_ssh, network_config):
    """Verify that rescan works with a device-backed virtio device."""
    test_microvm = test_microvm_with_ssh
    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_ssh.ssh_network_config(network_config, '1')

    # Add a scratch block device.
    fs1 = drive_tools.FilesystemFile(os.path.join(test_microvm.fsfiles, 'fs1'))
    response = test_microvm.drive.put(
        drive_id='scratch',
        path_on_host=test_microvm.create_jailed_resource(fs1.path),
        is_root_device=False,
        is_read_only=False)
    assert session.is_status_no_content(response.status_code)

    test_microvm.start()

    ssh_connection = net_tools.SSHConnection(test_microvm.ssh_config)

    _check_scratch_size(ssh_connection, fs1.size())

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

    losetup = ['losetup', '--find', '--show', fs2.path]
    loopback_device = None
    try:
        result = run(losetup, check=True, stdout=PIPE, stderr=PIPE)
        loopback_device = result.stdout.decode('utf-8').rstrip()
    except CalledProcessError as error:
        pytest.skip('failed to create a lookback device: ' +
                    f'stdout={error.stdout}, stderr={error.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_scratch_size(ssh_connection, fs2.size())
    finally:
        if loopback_device:
            run(['losetup', '--detach', loopback_device], check=True)
def test_device_ordering(test_microvm_with_api, 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.

    @type: functional
    """
    test_microvm = test_microvm_with_api
    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_api.ssh_network_config(network_config, "1")

    test_microvm.start()

    # Determine the size of the microVM rootfs in bytes.
    rc, stdout, stderr = utils.run_cmd(
        "du --apparent-size --block-size=1 {}".format(
            test_microvm.rootfs_file), )
    assert rc == 0, f"Failed to get microVM rootfs size: {stderr}"

    assert len(stdout.split()) == 2
    rootfs_size = 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())
Beispiel #6
0
def test_api_patch_post_boot(test_microvm_with_api):
    """Test PATCH updates after the microvm boots."""
    test_microvm = test_microvm_with_api
    test_microvm.spawn()

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

    fs1 = 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(fs1.path),
        is_root_device=False,
        is_read_only=False
    )
    assert test_microvm.api_session.is_status_no_content(response.status_code)

    # Configure logging.
    log_fifo_path = os.path.join(test_microvm.path, 'log_fifo')
    metrics_fifo_path = os.path.join(test_microvm.path, 'metrics_fifo')
    log_fifo = log_tools.Fifo(log_fifo_path)
    metrics_fifo = log_tools.Fifo(metrics_fifo_path)

    response = test_microvm.logger.put(
        log_fifo=test_microvm.create_jailed_resource(log_fifo.path),
        metrics_fifo=test_microvm.create_jailed_resource(metrics_fifo.path)
    )
    assert test_microvm.api_session.is_status_no_content(response.status_code)

    iface_id = '1'
    tapname = test_microvm.id[:8] + 'tap' + iface_id
    tap1 = net_tools.Tap(tapname, test_microvm.jailer.netns)
    response = test_microvm.network.put(
        iface_id=iface_id,
        host_dev_name=tap1.name,
        guest_mac='06:00:00:00:00:01'
    )
    assert test_microvm.api_session.is_status_no_content(response.status_code)

    test_microvm.start()

    # Partial updates to the boot source are not allowed.
    response = test_microvm.boot.patch(
        kernel_image_path='otherfile'
    )
    assert test_microvm.api_session.is_status_bad_request(response.status_code)
    assert "Invalid request method" in response.text

    # Partial updates to the machine configuration are not allowed.
    response = test_microvm.machine_cfg.patch(vcpu_count=4)
    assert test_microvm.api_session.is_status_bad_request(response.status_code)
    assert "Invalid request method" in response.text

    # Partial updates to the logger configuration are not allowed.
    response = test_microvm.logger.patch(level='Error')
    assert test_microvm.api_session.is_status_bad_request(response.status_code)
    assert "Invalid request method" in response.text
Beispiel #7
0
def test_drive_patch(test_microvm_with_api):
    """Test drive PATCH before and after boot."""
    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()

    # 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
    )
    assert test_microvm.api_session.is_good_response(response.status_code)

    _drive_patch(test_microvm)

    test_microvm.start()

    _drive_patch(test_microvm)
Beispiel #8
0
def test_non_partuuid_boot(test_microvm_with_ssh, network_config):
    """Test the output reported by blockdev when booting from /dev/vda."""
    test_microvm = test_microvm_with_ssh
    test_microvm.spawn()

    # Sets up the microVM with 1 vCPUs, 256 MiB of RAM, no 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=1)

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

    # Add another read-only block device.
    fs = drive_tools.FilesystemFile(
        os.path.join(test_microvm.fsfiles, 'readonly'))
    test_microvm.add_drive('scratch', fs.path, is_read_only=True)

    test_microvm.start()

    # Prepare the input for doing the assertion
    assert_dict = {}
    # Keep an array of strings specifying the location where some string
    # from the output is located.
    # 1-0 means line 1, column 0.
    keys_array = ['1-0', '1-8', '2-0']
    # Keep a dictionary where the keys are the location and the values
    # represent the input to assert against.
    assert_dict[keys_array[0]] = 'rw'
    assert_dict[keys_array[1]] = '/dev/vda'
    assert_dict[keys_array[2]] = 'ro'
    _check_drives(test_microvm, assert_dict, keys_array)
Beispiel #9
0
def test_drive_patch(test_microvm_with_api):
    """Test drive PATCH before and after boot."""
    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()

    # 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
    )
    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)
Beispiel #10
0
def test_api_patch_pre_boot(test_microvm_with_api):
    """
    Test that PATCH updates are not allowed before the microvm boots.

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

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

    fs1 = drive_tools.FilesystemFile(
        os.path.join(test_microvm.fsfiles, 'scratch'))
    drive_id = 'scratch'
    response = test_microvm.drive.put(
        drive_id=drive_id,
        path_on_host=test_microvm.create_jailed_resource(fs1.path),
        is_root_device=False,
        is_read_only=False)
    assert test_microvm.api_session.is_status_no_content(response.status_code)

    iface_id = '1'
    tapname = test_microvm.id[:8] + 'tap' + iface_id
    tap1 = net_tools.Tap(tapname, test_microvm.jailer.netns)
    response = test_microvm.network.put(iface_id=iface_id,
                                        host_dev_name=tap1.name,
                                        guest_mac='06:00:00:00:00:01')
    assert test_microvm.api_session.is_status_no_content(response.status_code)

    # Partial updates to the boot source are not allowed.
    response = test_microvm.boot.patch(kernel_image_path='otherfile')
    assert test_microvm.api_session.is_status_bad_request(response.status_code)
    assert "Invalid request method" in response.text

    # Partial updates to the machine configuration are allowed before boot.
    response = test_microvm.machine_cfg.patch(vcpu_count=4)
    assert test_microvm.api_session.is_status_no_content(response.status_code)
    response_json = test_microvm.machine_cfg.get().json()
    assert response_json['vcpu_count'] == 4

    # Partial updates to the logger configuration are not allowed.
    response = test_microvm.logger.patch(level='Error')
    assert test_microvm.api_session.is_status_bad_request(response.status_code)
    assert "Invalid request method" in response.text

    # Patching drive before boot is not allowed.
    response = test_microvm.drive.patch(drive_id=drive_id,
                                        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

    # Patching net before boot is not allowed.
    response = test_microvm.network.patch(iface_id=iface_id)
    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
Beispiel #11
0
def construct_scratch_drives():
    """Create an array of scratch disks."""
    scratchdisks = ["vdb", "vdc", "vdd", "vde"]
    disk_files = [
        drive_tools.FilesystemFile(tempfile.mktemp(), size=64)
        for _ in scratchdisks
    ]
    return zip(scratchdisks, disk_files)
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]
    rc, stdout, _ = utils.run_cmd(losetup)
    assert rc == 0
    loopback_device = stdout.rstrip()

    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])
Beispiel #13
0
def test_patch_drive(test_microvm_with_api, network_config):
    """
    Test replacing the backing filesystem after guest boot works.

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

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

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

    fs1 = drive_tools.FilesystemFile(
        os.path.join(test_microvm.fsfiles, 'scratch')
    )
    test_microvm.add_drive(
        'scratch',
        fs1.path
    )

    test_microvm.start()

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

    ssh_connection = net_tools.SSHConnection(test_microvm.ssh_config)

    # The `lsblk` command should output 2 lines to STDOUT: "SIZE" and the size
    # of the device, in bytes.
    blksize_cmd = "lsblk -b /dev/vdb --output SIZE"
    size_bytes_str = "536870912"  # = 512 MiB
    _, stdout, stderr = ssh_connection.execute_command(blksize_cmd)
    assert stderr.read() == ''
    stdout.readline()  # skip "SIZE"
    assert stdout.readline().strip() == size_bytes_str
Beispiel #14
0
def test_rescan_file(test_microvm_with_api, network_config):
    """
    Verify that rescan works with a file-backed virtio device.

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

    # 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')

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

    test_microvm.start()

    ssh_connection = net_tools.SSHConnection(test_microvm.ssh_config)
    _check_block_size(ssh_connection, '/dev/vdb', fs.size())

    # Check if reading from the entire disk results in a file of the same size
    # or errors out, after a truncate on the host.
    truncated_size = block_size//2
    utils.run_cmd(f"truncate --size {truncated_size}M {fs.path}")
    block_copy_name = "dev_vdb_copy"
    _, _, stderr = ssh_connection.execute_command(
        f"dd if=/dev/vdb of={block_copy_name} bs=1M count={block_size}")
    assert "dd: error reading '/dev/vdb': Input/output error" in stderr.read()
    _check_file_size(ssh_connection, f'{block_copy_name}',
                     truncated_size * MB)

    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)

    _check_block_size(
        ssh_connection,
        '/dev/vdb',
        fs.size()
    )
Beispiel #15
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 "Required key path_on_host not present in the json." \
           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 "Invalid PATCH payload. Only updates on path_on_host are allowed." \
           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 "Invalid PATCH payload. Only updates on path_on_host are allowed." \
           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: 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)
Beispiel #16
0
def test_api_actions(test_microvm_with_api):
    """Test PUTs to /actions beyond InstanceStart and InstanceHalt."""
    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()

    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
    )
    assert test_microvm.api_session.is_good_response(response.status_code)

    # Rescan operations before the guest boots are not allowed.
    response = test_microvm.actions.put(
        action_type='BlockDeviceRescan',
        payload='scratch'
    )
    assert not test_microvm.api_session.is_good_response(response.status_code)
    assert "Operation not allowed pre-boot" in response.text

    test_microvm.start()

    # Rescan operations after the guest boots are allowed.
    response = test_microvm.actions.put(
        action_type='BlockDeviceRescan',
        payload='scratch'
    )
    assert test_microvm.api_session.is_good_response(response.status_code)

    # Rescan operations on non-existent drives are not allowed.
    response = test_microvm.actions.put(
        action_type='BlockDeviceRescan',
        payload='foobar'
    )
    assert not test_microvm.api_session.is_good_response(response.status_code)
    assert "Invalid block device ID" in response.text
Beispiel #17
0
def test_rescan(test_microvm_with_ssh, network_config):
    """Verify that a block device rescan has guest seeing changes."""
    test_microvm = test_microvm_with_ssh
    test_microvm.spawn()

    # 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_ssh.ssh_network_config(network_config, '1')

    # Add a scratch block device.
    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
    )
    assert test_microvm.api_session.is_status_no_content(response.status_code)

    test_microvm.start()

    ssh_connection = net_tools.SSHConnection(test_microvm.ssh_config)

    _check_scratch_size(ssh_connection, fs.size())

    # Resize the filesystem from 256 MiB (default) to 512 MiB.
    fs.resize(512)

    # Rescan operations after the guest boots are allowed.
    response = test_microvm.actions.put(
        action_type='BlockDeviceRescan',
        payload='scratch'
    )
    assert test_microvm.api_session.is_status_no_content(response.status_code)

    _check_scratch_size(
        ssh_connection,
        fs.size()
    )
def test_rescan_file(test_microvm_with_ssh, network_config):
    """Verify that rescan works with a file-backed virtio device."""
    test_microvm = test_microvm_with_ssh
    test_microvm.spawn()

    # 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_ssh.ssh_network_config(network_config, '1')

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

    test_microvm.start()

    ssh_connection = net_tools.SSHConnection(test_microvm.ssh_config)

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

    # Resize the filesystem from 256 MiB (default) to 512 MiB.
    fs.resize(512)

    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)

    _check_block_size(
        ssh_connection,
        '/dev/vdb',
        fs.size()
    )
Beispiel #19
0
def test_rate_limiters_api_config(test_microvm_with_api):
    """Test the Firecracker IO rate limiter API."""
    test_microvm = test_microvm_with_api
    test_microvm.spawn()

    # Test the DRIVE rate limiting API.

    # Test drive with bw rate-limiting.
    fs1 = drive_tools.FilesystemFile(os.path.join(test_microvm.fsfiles, 'bw'))
    response = test_microvm.drive.put(
        drive_id='bw',
        path_on_host=test_microvm.create_jailed_resource(fs1.path),
        is_read_only=False,
        is_root_device=False,
        rate_limiter={
            'bandwidth': {
                'size': 1000000,
                'refill_time': 100
            }
        }
    )
    assert test_microvm.api_session.is_status_no_content(response.status_code)

    # Test drive with ops rate-limiting.
    fs2 = drive_tools.FilesystemFile(os.path.join(test_microvm.fsfiles, 'ops'))
    response = test_microvm.drive.put(
        drive_id='ops',
        path_on_host=test_microvm.create_jailed_resource(fs2.path),
        is_read_only=False,
        is_root_device=False,
        rate_limiter={
            'ops': {
                'size': 1,
                'refill_time': 100
            }
        }
    )
    assert test_microvm.api_session.is_status_no_content(response.status_code)

    # Test drive with bw and ops rate-limiting.
    fs3 = drive_tools.FilesystemFile(
        os.path.join(test_microvm.fsfiles, 'bwops')
    )
    response = test_microvm.drive.put(
        drive_id='bwops',
        path_on_host=test_microvm.create_jailed_resource(fs3.path),
        is_read_only=False,
        is_root_device=False,
        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)

    # Test drive with 'empty' rate-limiting (same as not specifying the field)
    fs4 = drive_tools.FilesystemFile(os.path.join(
        test_microvm.fsfiles, 'nada'
    ))
    response = test_microvm.drive.put(
        drive_id='nada',
        path_on_host=test_microvm.create_jailed_resource(fs4.path),
        is_read_only=False,
        is_root_device=False,
        rate_limiter={}
    )
    assert test_microvm.api_session.is_status_no_content(response.status_code)

    # Test the NET rate limiting API.

    # Test network with tx bw rate-limiting.
    iface_id = '1'
    tapname = test_microvm.id[:8] + 'tap' + iface_id
    tap1 = net_tools.Tap(tapname, test_microvm.jailer.netns)

    response = test_microvm.network.put(
        iface_id=iface_id,
        guest_mac='06:00:00:00:00:01',
        host_dev_name=tap1.name,
        tx_rate_limiter={
            'bandwidth': {
                'size': 1000000,
                'refill_time': 100
            }
        }
    )
    assert test_microvm.api_session.is_status_no_content(response.status_code)

    # Test network with rx bw rate-limiting.
    iface_id = '2'
    tapname = test_microvm.id[:8] + 'tap' + iface_id
    tap2 = net_tools.Tap(tapname, test_microvm.jailer.netns)
    response = test_microvm.network.put(
        iface_id=iface_id,
        guest_mac='06:00:00:00:00:02',
        host_dev_name=tap2.name,
        rx_rate_limiter={
            'bandwidth': {
                'size': 1000000,
                'refill_time': 100
            }
        }
    )
    assert test_microvm.api_session.is_status_no_content(response.status_code)

    # Test network with tx and rx bw and ops rate-limiting.
    iface_id = '3'
    tapname = test_microvm.id[:8] + 'tap' + iface_id
    tap3 = net_tools.Tap(tapname, test_microvm.jailer.netns)
    response = test_microvm.network.put(
        iface_id=iface_id,
        guest_mac='06:00:00:00:00:03',
        host_dev_name=tap3.name,
        rx_rate_limiter={
            'bandwidth': {
                'size': 1000000,
                'refill_time': 100
            },
            'ops': {
                'size': 1,
                'refill_time': 100
            }
        },
        tx_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)
Beispiel #20
0
def test_api_put_update_pre_boot(test_microvm_with_api):
    """Test that PUT updates are allowed before the microvm boots."""
    test_microvm = test_microvm_with_api
    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()

    fs1 = 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(fs1.path),
        is_root_device=False,
        is_read_only=False
    )
    assert test_microvm.api_session.is_status_no_content(response.status_code)

    # Updates to `kernel_image_path` with an invalid path are not allowed.
    response = test_microvm.boot.put(
        kernel_image_path='foo.bar'
    )
    assert test_microvm.api_session.is_status_bad_request(response.status_code)
    assert "The kernel file cannot be opened: No such file or directory " \
           "(os error 2)" in response.text

    # Updates to `kernel_image_path` with a valid path are allowed.
    response = test_microvm.boot.put(
        kernel_image_path=test_microvm.get_jailed_resource(
            test_microvm.kernel_file
        )
    )
    assert test_microvm.api_session.is_status_no_content(response.status_code)

    # Updates to `path_on_host` with an invalid path are not allowed.
    response = test_microvm.drive.put(
        drive_id='rootfs',
        path_on_host='foo.bar',
        is_read_only=True,
        is_root_device=True
    )
    assert test_microvm.api_session.is_status_bad_request(response.status_code)
    assert "Invalid block device path" in response.text

    # Updates to `is_root_device` that result in two root block devices are not
    # allowed.
    response = test_microvm.drive.put(
        drive_id='scratch',
        path_on_host=test_microvm.get_jailed_resource(fs1.path),
        is_read_only=False,
        is_root_device=True
    )
    assert test_microvm.api_session.is_status_bad_request(response.status_code)
    assert "A root block device already exists" in response.text

    # Valid updates to `path_on_host` and `is_read_only` are allowed.
    fs2 = drive_tools.FilesystemFile(
        os.path.join(test_microvm.fsfiles, 'otherscratch')
    )
    response = test_microvm.drive.put(
        drive_id='scratch',
        path_on_host=test_microvm.create_jailed_resource(fs2.path),
        is_read_only=True,
        is_root_device=False
    )
    assert test_microvm.api_session.is_status_no_content(response.status_code)

    # Valid updates to all fields in the machine configuration are allowed.
    # The machine configuration has a default value, so all PUTs are updates.
    microvm_config_json = {
        'vcpu_count': 4,
        'ht_enabled': True,
        'mem_size_mib': 256,
        'cpu_template': 'C3',
        'track_dirty_pages': True
    }
    response = test_microvm.machine_cfg.put(
        vcpu_count=microvm_config_json['vcpu_count'],
        ht_enabled=microvm_config_json['ht_enabled'],
        mem_size_mib=microvm_config_json['mem_size_mib'],
        cpu_template=microvm_config_json['cpu_template'],
        track_dirty_pages=microvm_config_json['track_dirty_pages']
    )
    assert test_microvm.api_session.is_status_no_content(response.status_code)

    response = test_microvm.machine_cfg.get()
    assert test_microvm.api_session.is_status_ok(response.status_code)
    response_json = response.json()

    vcpu_count = microvm_config_json['vcpu_count']
    assert response_json['vcpu_count'] == vcpu_count

    ht_enabled = microvm_config_json['ht_enabled']
    assert response_json['ht_enabled'] == ht_enabled

    mem_size_mib = microvm_config_json['mem_size_mib']
    assert response_json['mem_size_mib'] == mem_size_mib

    cpu_template = str(microvm_config_json['cpu_template'])
    assert response_json['cpu_template'] == cpu_template

    track_dirty_pages = microvm_config_json['track_dirty_pages']
    assert response_json['track_dirty_pages'] == track_dirty_pages
Beispiel #21
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

    # 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: 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
def test_patch_drive_limiter(test_microvm_with_api, network_config):
    """
    Test replacing the drive rate-limiter after guest boot works.

    @type: functional
    """
    test_microvm = test_microvm_with_api
    test_microvm.jailer.daemonize = False
    test_microvm.spawn()
    # Set up the microVM with 2 vCPUs, 512 MiB of RAM, 1 network iface, a root
    # file system with the rw permission, and a scratch drive.
    test_microvm.basic_config(vcpu_count=2,
                              mem_size_mib=512,
                              boot_args="console=ttyS0 reboot=k panic=1")

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

    fs1 = drive_tools.FilesystemFile(os.path.join(test_microvm.fsfiles,
                                                  "scratch"),
                                     size=512)
    response = test_microvm.drive.put(
        drive_id="scratch",
        path_on_host=test_microvm.create_jailed_resource(fs1.path),
        is_root_device=False,
        is_read_only=False,
        rate_limiter={
            "bandwidth": {
                "size": 10 * MB,
                "refill_time": 100
            },
            "ops": {
                "size": 100,
                "refill_time": 100
            },
        },
    )
    assert test_microvm.api_session.is_status_no_content(response.status_code)
    test_microvm.start()
    ssh_connection = net_tools.SSHConnection(test_microvm.ssh_config)

    # Validate IOPS stays within above configured limits.
    # For example, the below call will validate that reading 1000 blocks
    # of 512b will complete in at 0.8-1.2 seconds ('dd' is not very accurate,
    # so we target to stay within 30% error).
    check_iops_limit(ssh_connection, 512, 1000, 0.7, 1.3)
    check_iops_limit(ssh_connection, 4096, 1000, 0.7, 1.3)

    # Patch ratelimiter
    response = test_microvm.drive.patch(
        drive_id="scratch",
        rate_limiter={
            "bandwidth": {
                "size": 100 * MB,
                "refill_time": 100
            },
            "ops": {
                "size": 200,
                "refill_time": 100
            },
        },
    )
    assert test_microvm.api_session.is_status_no_content(response.status_code)

    check_iops_limit(ssh_connection, 512, 2000, 0.7, 1.3)
    check_iops_limit(ssh_connection, 4096, 2000, 0.7, 1.3)

    # Patch ratelimiter
    response = test_microvm.drive.patch(
        drive_id="scratch",
        rate_limiter={"ops": {
            "size": 1000,
            "refill_time": 100
        }})
    assert test_microvm.api_session.is_status_no_content(response.status_code)

    check_iops_limit(ssh_connection, 512, 10000, 0.7, 1.3)
    check_iops_limit(ssh_connection, 4096, 10000, 0.7, 1.3)
def fio_workload(context):
    """Execute block device emulation benchmarking scenarios."""
    vm_builder = context.custom['builder']
    logger = context.custom["logger"]
    file_dumper = context.custom["results_file_dumper"]

    # 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 artifacts.
    vm_instance = vm_builder.build(kernel=context.kernel,
                                   disks=[rw_disk],
                                   ssh_key=ssh_key,
                                   config=context.microvm)
    basevm = vm_instance.vm

    # Add a secondary block device for benchmark tests.
    fs = drive_tools.FilesystemFile(os.path.join(basevm.fsfiles, 'scratch'),
                                    CONFIG["block_device_size"])
    basevm.add_drive('scratch', fs.path)
    basevm.start()

    # Get names of threads in Firecracker.
    current_cpu_id = 0
    basevm.pin_vmm(current_cpu_id)
    current_cpu_id += 1
    basevm.pin_api(current_cpu_id)
    for vcpu_id in range(basevm.vcpus_count):
        current_cpu_id += 1
        basevm.pin_vcpu(vcpu_id, current_cpu_id)

    st_core = core.Core(name=TEST_ID,
                        iterations=1,
                        custom={
                            "microvm": context.microvm.name(),
                            "kernel": context.kernel.name(),
                            "disk": context.disk.name(),
                            "cpu_model_name": get_cpu_model_name()
                        })

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

    ssh_connection = net_tools.SSHConnection(basevm.ssh_config)
    env_id = f"{context.kernel.name()}/{context.disk.name()}/" \
             f"{context.microvm.name()}"

    for mode in CONFIG["fio_modes"]:
        for bs in CONFIG["fio_blk_sizes"]:
            fio_id = f"{mode}-bs{bs}"
            st_prod = st.producer.LambdaProducer(func=run_fio,
                                                 func_kwargs={
                                                     "env_id": env_id,
                                                     "basevm": basevm,
                                                     "ssh_conn":
                                                     ssh_connection,
                                                     "mode": mode,
                                                     "bs": bs
                                                 })
            st_cons = st.consumer.LambdaConsumer(
                metadata_provider=DictMetadataProvider(
                    CONFIG["measurements"],
                    BlockBaselinesProvider(env_id, fio_id)),
                func=consume_fio_output,
                func_kwargs={
                    "numjobs": basevm.vcpus_count,
                    "mode": mode,
                    "bs": bs,
                    "env_id": env_id,
                    "logs_path": basevm.jailer.chroot_base_with_id()
                })
            st_core.add_pipe(st_prod, st_cons, tag=f"{env_id}/{fio_id}")

    # Gather results and verify pass criteria.
    try:
        result = st_core.run_exercise()
    except core.CoreException as err:
        handle_failure(file_dumper, err)

    dump_test_result(file_dumper, result)
Beispiel #24
0
def test_patch_drive_limiter(test_microvm_with_ssh, network_config):
    """Test replacing the drive rate-limiter after guest boot works."""
    test_microvm = test_microvm_with_ssh
    test_microvm.jailer.daemonize = False
    test_microvm.spawn()
    # Set up the microVM with 2 vCPUs, 512 MiB of RAM, 1 network iface, a root
    # file system with the rw permission, and a scratch drive.
    test_microvm.basic_config(vcpu_count=2,
                              mem_size_mib=512,
                              boot_args='console=ttyS0 reboot=k panic=1')

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

    fs1 = drive_tools.FilesystemFile(os.path.join(test_microvm.fsfiles,
                                                  'scratch'),
                                     size=512)
    response = test_microvm.drive.put(
        drive_id='scratch',
        path_on_host=test_microvm.create_jailed_resource(fs1.path),
        is_root_device=False,
        is_read_only=False,
        rate_limiter={
            'bandwidth': {
                'size': 10 * MB,
                'refill_time': 100
            },
            'ops': {
                'size': 100,
                'refill_time': 100
            }
        })
    assert test_microvm.api_session.is_status_no_content(response.status_code)
    test_microvm.start()
    ssh_connection = net_tools.SSHConnection(test_microvm.ssh_config)

    # Validate IOPS stays within above configured limits.
    # For example, the below call will validate that reading 1000 blocks
    # of 512b will complete in at 0.85-1.25 seconds.
    check_iops_limit(ssh_connection, 512, 1000, 0.85, 1.25)
    check_iops_limit(ssh_connection, 4096, 1000, 0.85, 1.25)

    # Patch ratelimiter
    response = test_microvm.drive.patch(drive_id='scratch',
                                        rate_limiter={
                                            'bandwidth': {
                                                'size': 100 * MB,
                                                'refill_time': 100
                                            },
                                            'ops': {
                                                'size': 200,
                                                'refill_time': 100
                                            }
                                        })
    assert test_microvm.api_session.is_status_no_content(response.status_code)

    check_iops_limit(ssh_connection, 512, 2000, 0.85, 1.5)
    check_iops_limit(ssh_connection, 4096, 2000, 0.85, 1.5)

    # Patch ratelimiter
    response = test_microvm.drive.patch(
        drive_id='scratch',
        rate_limiter={'ops': {
            'size': 1250,
            'refill_time': 100
        }})
    assert test_microvm.api_session.is_status_no_content(response.status_code)

    check_iops_limit(ssh_connection, 512, 10000, 0.85, 1.5)
    check_iops_limit(ssh_connection, 4096, 10000, 0.85, 1.5)
Beispiel #25
0
def create_snapshot_helper(builder, logger, target_version=None,
                           drives=None, ifaces=None,
                           balloon=False, diff_snapshots=False,
                           fc_binary=None, jailer_binary=None):
    """Create a snapshot with many devices."""
    vm_instance = builder.build_vm_nano(net_ifaces=ifaces,
                                        diff_snapshots=diff_snapshots,
                                        fc_binary=fc_binary,
                                        jailer_binary=jailer_binary)
    vm = vm_instance.vm

    if diff_snapshots is False:
        snapshot_type = SnapshotType.FULL
    else:
        # Version 0.24 and greater has Diff and balloon support.
        snapshot_type = SnapshotType.DIFF

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

    # Disk path array needed when creating the snapshot later.
    disks = [vm_instance.disks[0].local_path()]
    test_drives = [] if drives is None else drives

    # Add disks.
    for scratch in test_drives:
        # Add a scratch 64MB RW non-root block device.
        scratchdisk = drive_tools.FilesystemFile(tempfile.mktemp(), size=64)
        vm.add_drive(scratch, scratchdisk.path)
        disks.append(scratchdisk.path)

        # Workaround FilesystemFile destructor removal of file.
        scratchdisk.path = None

    vm.start()

    # Iterate and validate connectivity on all ifaces after boot.
    for iface in 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

    # Mount scratch drives in guest.
    for blk in test_drives:
        # Create mount point and mount each device.
        cmd = "mkdir -p /mnt/{blk} && mount /dev/{blk} /mnt/{blk}".format(
            blk=blk
        )
        exit_code, _, _ = ssh_connection.execute_command(cmd)
        assert exit_code == 0

        # Create file using dd using O_DIRECT.
        # After resume we will compute md5sum on these files.
        dd = "dd if=/dev/zero of=/mnt/{}/test bs=4096 count=10 oflag=direct"
        exit_code, _, _ = ssh_connection.execute_command(dd.format(blk))
        assert exit_code == 0

        # Unmount the device.
        cmd = "umount /dev/{}".format(blk)
        exit_code, _, _ = ssh_connection.execute_command(cmd)
        assert exit_code == 0

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

    snapshot = snapshot_builder.create(disks,
                                       vm_instance.ssh_key,
                                       target_version=target_version,
                                       snapshot_type=snapshot_type,
                                       net_ifaces=ifaces)
    logger.debug("========== Firecracker create snapshot log ==========")
    logger.debug(vm.log_data)
    vm.kill()
    return snapshot
Beispiel #26
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

    # 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: 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',
        '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',
        'rate_limiter': {
            'bandwidth': {
                'size': 5000,
                'one_time_burst': None,
                'refill_time': 100
            },
            'ops': {
                'size': 500,
                'one_time_burst': None,
                'refill_time': 100
            }
        }
    }]
Beispiel #27
0
def _test_patch_drive_snapshot(context):
    logger = context.custom['logger']
    vm_builder = context.custom['builder']
    snapshot_type = SnapshotType.FULL
    enable_diff_snapshots = False

    logger.info(
        "Testing patch drive snapshot on \"{}\", kernel {}, disk {} ".format(
            context.microvm.name(), context.kernel.name(),
            context.disk.name()))

    # 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.
    basevm = vm_builder.build(kernel=context.kernel,
                              disks=[root_disk],
                              ssh_key=ssh_key,
                              config=context.microvm,
                              enable_diff_snapshots=enable_diff_snapshots)

    network_config = net_tools.UniqueIPv4Generator.instance()
    _, host_ip, guest_ip = basevm.ssh_network_config(network_config,
                                                     '1',
                                                     tapname="tap0")
    logger.debug("Host IP: {}, Guest IP: {}".format(host_ip, guest_ip))

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

    # We will need netmask_len in build_from_snapshot() call later.
    netmask_len = network_config.get_netmask_len()
    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 {}MB.".format(new_file_size_mb))
    scratchdisk1 = drive_tools.FilesystemFile(tempfile.mktemp(),
                                              new_file_size_mb)
    basevm.patch_drive('scratch', scratchdisk1)

    logger.info("Create {} #0.".format(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 {}".format(snapshot.mem))
    microvm, _ = vm_builder.build_from_snapshot(snapshot, host_ip, guest_ip,
                                                netmask_len, True,
                                                enable_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()
def fio_workload(context):
    """Execute block device emulation benchmarking scenarios."""
    vm_builder = context.custom['builder']
    logger = context.custom["logger"]

    # 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 artifacts.
    basevm = vm_builder.build(kernel=context.kernel,
                              disks=[rw_disk],
                              ssh_key=ssh_key,
                              config=context.microvm)

    # Add a secondary block device for benchmark tests.
    fs = drive_tools.FilesystemFile(
        os.path.join(basevm.fsfiles, 'scratch'),
        CONFIG["block_device_size"]
    )
    basevm.add_drive('scratch', fs.path)
    basevm.start()

    # Get names of threads in Firecracker.
    current_cpu_id = 0
    basevm.pin_vmm(current_cpu_id)
    current_cpu_id += 1
    basevm.pin_api(current_cpu_id)
    for vcpu_id in range(basevm.vcpus_count):
        current_cpu_id += 1
        basevm.pin_vcpu(vcpu_id, current_cpu_id)

    st_core = core.Core(name=TEST_ID,
                        iterations=1,
                        custom={"microvm": context.microvm.name(),
                                "kernel": context.kernel.name(),
                                "disk": context.disk.name()})

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

    ssh_connection = net_tools.SSHConnection(basevm.ssh_config)
    env_id = f"{context.kernel.name()}/{context.disk.name()}"
    for mode in CONFIG["fio_modes"]:
        ms_defs = measurements(mode)
        for bs in CONFIG["fio_blk_sizes"]:
            fio_id = f"{mode}-bs{bs}-{basevm.vcpus_count}vcpu"
            st_defs = statistics(mode, env_id, fio_id)
            st_prod = st.producer.LambdaProducer(
                func=run_fio,
                func_kwargs={
                    "env_id": env_id,
                    "basevm": basevm,
                    "ssh_conn": ssh_connection,
                    "mode": mode,
                    "bs": bs
                }
            )

            numjobs = CONFIG['load_factor'] * basevm.vcpus_count
            st_cons = st.consumer.LambdaConsumer(
                consume_stats=False,
                func=consume_fio_output,
                func_kwargs={"numjobs": numjobs,
                             "mode": mode,
                             "bs": bs,
                             "env_id": env_id})
            eager_map(st_cons.set_measurement_def, ms_defs)
            eager_map(st_cons.set_stat_def, st_defs)
            st_core.add_pipe(st_prod, st_cons, tag=f"{env_id}/{fio_id}")

    st_core.run_exercise()
    basevm.kill()