Exemplo n.º 1
0
def test_api_vsock(bin_cloner_path):
    """
    Test vsock related API commands.

    @type: functional
    """
    builder = MicrovmBuilder(bin_cloner_path)
    artifacts = ArtifactCollection(_test_images_s3_bucket())

    # Test with the current build.
    vm_instance = builder.build_vm_nano()
    _test_vsock(vm_instance.vm)

    # Fetch 1.0.0 and older firecracker binaries.
    # Create a vsock device with each FC binary
    # artifact.
    firecracker_artifacts = artifacts.firecrackers(
        # v1.0.0 deprecated `vsock_id`.
        min_version="1.0.0")

    for firecracker in firecracker_artifacts:
        firecracker.download()
        jailer = firecracker.jailer()
        jailer.download()

        vm_instance = builder.build_vm_nano(fc_binary=firecracker.local_path(),
                                            jailer_binary=jailer.local_path())

        _test_vsock(vm_instance.vm)
Exemplo n.º 2
0
def test_mmds_snapshot(bin_cloner_path,  version):
    """
    Test MMDS behavior by restoring a snapshot on current and past FC versions.

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

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

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

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

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

        _validate_mmds_snapshot(
            vm_instance,
            vm_builder,
            mmds_version,
            target_fc_version=target_version,
            fc_path=firecracker.local_path(),
            jailer_path=jailer.local_path()
        )
def test_negative_snapshot_create(bin_cloner_path):
    """
    Test create snapshot before pause.

    @type: negative
    """
    vm_builder = MicrovmBuilder(bin_cloner_path)
    vm_instance = vm_builder.build_vm_nano()
    vm = vm_instance.vm

    vm.start()

    response = vm.snapshot.create(mem_file_path="memfile",
                                  snapshot_path="statefile",
                                  diff=False)

    assert vm.api_session.is_status_bad_request(response.status_code)
    assert "save/restore unavailable while running" in response.text

    response = vm.vm.patch(state="Paused")
    assert vm.api_session.is_status_no_content(response.status_code)

    # Try diff with dirty pages tracking disabled.
    response = vm.snapshot.create(mem_file_path="memfile",
                                  snapshot_path="statefile",
                                  diff=True)
    msg = "Diff snapshots are not allowed on uVMs with dirty page" " tracking disabled"
    assert msg in response.text
    assert not os.path.exists("statefile")
    assert not os.path.exists("memfile")

    vm.kill()
Exemplo n.º 4
0
def test_create_invalid_version(bin_cloner_path):
    """Test scenario: create snapshot targeting invalid version."""
    # Use a predefined vm instance.
    builder = MicrovmBuilder(bin_cloner_path)
    test_microvm = builder.build_vm_nano().vm
    test_microvm.start()

    try:
        # Target an invalid Firecracker version string.
        test_microvm.pause_to_snapshot(mem_file_path="/vm.mem",
                                       snapshot_path="/vm.vmstate",
                                       diff=False,
                                       version="invalid")
    except AssertionError as error:
        # Check if proper error is returned.
        assert "Invalid microVM version format" in \
            str(error)
    else:
        assert False, "Negative test failed"

    try:
        # Target a valid version string but with no snapshot support.
        test_microvm.pause_to_snapshot(mem_file_path="/vm.mem",
                                       snapshot_path="/vm.vmstate",
                                       diff=False,
                                       version="0.22.0")
    except AssertionError as error:
        # Check if proper error is returned.
        assert "Cannot translate microVM version to snapshot data version" in \
            str(error)
    else:
        assert False, "Negative test failed"
Exemplo n.º 5
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
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()
Exemplo n.º 7
0
def test_mmds_older_snapshot(bin_cloner_path):
    """
    Test MMDS behavior restoring older snapshots in the current version.

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

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

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

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

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

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

        _validate_mmds_snapshot(
            vm_instance,
            vm_builder,
            mmds_version,
            target_fc_version=fc_version
        )
Exemplo n.º 8
0
def test_create_with_newer_virtio_features(bin_cloner_path):
    """
    Attempt to create a snapshot with newer virtio features.

    @type: functional
    """
    builder = MicrovmBuilder(bin_cloner_path)
    test_microvm = builder.build_vm_nano().vm
    test_microvm.start()

    # Init a ssh connection in order to wait for the VM to boot. This way
    # we can be sure that the block device was activated.
    iface = NetIfaceConfig()
    test_microvm.ssh_config['hostname'] = iface.guest_ip
    _ssh_connection = net_tools.SSHConnection(test_microvm.ssh_config)

    # Create directory and files for saving snapshot state and memory.
    snapshot_builder = SnapshotBuilder(test_microvm)
    _snapshot_dir = snapshot_builder.create_snapshot_dir()

    # Pause microVM for snapshot.
    response = test_microvm.vm.patch(state='Paused')
    assert test_microvm.api_session.is_status_no_content(response.status_code)

    # We try to create a snapshot to a target version < 0.26.0.
    # This should fail because Fc versions < 0.26.0 don't support
    # virtio notification suppression.
    target_fc_versions = ["0.24.0", "0.25.0"]
    if platform.machine() == "x86_64":
        target_fc_versions.insert(0, "0.23.0")
    for target_fc_version in target_fc_versions:
        response = test_microvm.snapshot.create(
            mem_file_path="/snapshot/vm.mem",
            snapshot_path="/snapshot/vm.vmstate",
            version=target_fc_version
        )
        assert test_microvm.api_session.is_status_bad_request(
            response.status_code
        )
        assert "The virtio devices use a features that is incompatible " \
               "with older versions of Firecracker: notification suppression" \
               in response.text

        # It should work when we target a version >= 0.26.0
        response = test_microvm.snapshot.create(
            mem_file_path="/snapshot/vm.mem",
            snapshot_path="/snapshot/vm.vmstate",
            version="0.26.0"
        )
        assert test_microvm.api_session.is_status_no_content(
            response.status_code
        )
Exemplo n.º 9
0
def test_negative_postload_api(bin_cloner_path):
    """
    Test APIs fail after loading from snapshot.

    @type: negative
    """
    logger = logging.getLogger("snapshot_api_fail")

    vm_builder = MicrovmBuilder(bin_cloner_path)
    vm_instance = vm_builder.build_vm_nano(diff_snapshots=True)
    basevm = vm_instance.vm
    root_disk = vm_instance.disks[0]
    ssh_key = vm_instance.ssh_key

    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

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

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

    basevm.kill()

    logger.info("Load snapshot, mem %s", snapshot.mem)
    # Do not resume, just load, so we can still call APIs that work.
    microvm, _ = vm_builder.build_from_snapshot(snapshot,
                                                resume=False,
                                                diff_snapshots=True)
    fail_msg = "The requested operation is not supported after starting " \
        "the microVM"

    response = microvm.actions.put(action_type='InstanceStart')
    assert fail_msg in response.text

    try:
        microvm.basic_config()
    except AssertionError as error:
        assert fail_msg in str(error)
    else:
        assert False, "Negative test failed"

    microvm.kill()
Exemplo n.º 10
0
def test_serial_after_snapshot(bin_cloner_path):
    """
    Serial I/O after restoring from a snapshot.

    @type: functional
    """
    vm_builder = MicrovmBuilder(bin_cloner_path)
    vm_instance = vm_builder.build_vm_nano(
        diff_snapshots=False,
        daemonize=False,
    )
    microvm = vm_instance.vm
    root_disk = vm_instance.disks[0]
    ssh_key = vm_instance.ssh_key

    microvm.start()
    serial = Serial(microvm)
    serial.open()

    # Image used for tests on aarch64 has autologon
    if platform.machine() == "x86_64":
        serial.rx(token="login: "******"root")
        serial.rx("Password: "******"root")
    # Make sure that at the time we snapshot the vm, the user is logged in.
    serial.rx("#")

    snapshot_builder = SnapshotBuilder(microvm)
    disks = [root_disk.local_path()]
    # Create diff snapshot.
    snapshot = snapshot_builder.create(disks, ssh_key, SnapshotType.FULL)
    # Kill base microVM.
    microvm.kill()

    # Load microVM clone from snapshot.
    test_microvm, _ = vm_builder.build_from_snapshot(snapshot,
                                                     resume=True,
                                                     diff_snapshots=False,
                                                     daemonize=False)
    serial = Serial(test_microvm)
    serial.open()
    # We need to send a newline to signal the serial to flush
    # the login content.
    serial.tx("")
    serial.rx("#")
    serial.tx("pwd")
    res = serial.rx("#")
    assert "/root" in res
Exemplo n.º 11
0
def test_negative_api_lifecycle(bin_cloner_path):
    """Test some vm lifecycle error scenarios."""
    builder = MicrovmBuilder(bin_cloner_path)
    vm_instance = builder.build_vm_nano()
    basevm = vm_instance.vm

    # Try to pause microvm when not running, it must fail.
    response = basevm.vm.patch(state='Paused')
    assert "not supported before starting the microVM" \
        in response.text

    # Try to resume microvm when not running, it must fail.
    response = basevm.vm.patch(state='Resumed')
    assert "not supported before starting the microVM" \
        in response.text
Exemplo n.º 12
0
def test_save_tsc_old_version(bin_cloner_path):
    """Test TSC warning message when saving old snapshot."""
    vm_builder = MicrovmBuilder(bin_cloner_path)
    vm_instance = vm_builder.build_vm_nano()
    vm = vm_instance.vm

    vm.start()

    vm.pause_to_snapshot(mem_file_path='memfile',
                         snapshot_path='statefile',
                         diff=False,
                         version='0.24.0')

    log_data = vm.log_data
    assert "Saving to older snapshot version, TSC freq" in log_data
    vm.kill()
def test_pause_resume_preboot(bin_cloner_path):
    """
    Test pause/resume operations are not allowed pre-boot.

    @type: negative
    """
    builder = MicrovmBuilder(bin_cloner_path)
    vm_instance = builder.build_vm_nano()
    basevm = vm_instance.vm

    # Try to pause microvm when not running, it must fail.
    response = basevm.vm.patch(state="Paused")
    assert "not supported before starting the microVM" in response.text

    # Try to resume microvm when not running, it must fail.
    response = basevm.vm.patch(state="Resumed")
    assert "not supported before starting the microVM" in response.text
def test_describe_instance(bin_cloner_path):
    """
    Test scenario: DescribeInstance different states.

    @type: functional
    """
    builder = MicrovmBuilder(bin_cloner_path)
    vm_instance = builder.build_vm_nano()
    microvm = vm_instance.vm
    descr_inst = DescribeInstance(microvm.api_socket, microvm.api_session)

    # Check MicroVM state is "Not started"
    response = descr_inst.get()
    assert microvm.api_session.is_status_ok(response.status_code)
    assert "Not started" in response.text

    # Start MicroVM
    microvm.start()

    # Check MicroVM state is "Running"
    response = descr_inst.get()
    assert microvm.api_session.is_status_ok(response.status_code)
    assert "Running" in response.text

    # Pause MicroVM
    response = microvm.vm.patch(state="Paused")
    assert microvm.api_session.is_status_no_content(response.status_code)

    # Check MicroVM state is "Paused"
    response = descr_inst.get()
    assert microvm.api_session.is_status_ok(response.status_code)
    assert "Paused" in response.text

    # Resume MicroVM
    response = microvm.vm.patch(state="Resumed")
    assert microvm.api_session.is_status_no_content(response.status_code)

    # Check MicroVM state is "Running" after VM is resumed
    response = descr_inst.get()
    assert microvm.api_session.is_status_ok(response.status_code)
    assert "Running" in response.text

    microvm.kill()
def test_save_tsc_old_version(bin_cloner_path):
    """
    Test TSC warning message when saving old snapshot.

    @type: functional
    """
    vm_builder = MicrovmBuilder(bin_cloner_path)
    vm_instance = vm_builder.build_vm_nano()
    vm = vm_instance.vm

    vm.start()

    vm.pause_to_snapshot(mem_file_path="memfile",
                         snapshot_path="statefile",
                         diff=False,
                         version="0.24.0")

    vm.check_log_message("Saving to older snapshot version, TSC freq")
    vm.kill()
Exemplo n.º 16
0
def test_describe_snapshot_all_versions(bin_cloner_path):
    """
    Test `--describe-snapshot` correctness for all snapshot versions.

    @type: functional
    """
    logger = logging.getLogger("describe_snapshot")
    builder = MicrovmBuilder(bin_cloner_path)
    artifacts = ArtifactCollection(_test_images_s3_bucket())
    # Fetch all firecracker binaries.
    # For each binary create a snapshot and verify the data version
    # of the snapshot state file.

    firecracker_artifacts = artifacts.firecrackers(
        max_version=get_firecracker_version_from_toml())

    for firecracker in firecracker_artifacts:
        firecracker.download()
        jailer = firecracker.jailer()
        jailer.download()

        target_version = firecracker.base_name()[1:]
        # Skip for aarch64, since the snapshotting feature
        # was introduced in v0.24.0.
        if platform.machine() == "aarch64" and "v0.23" in target_version:
            continue

        logger.info("Creating snapshot with Firecracker: %s",
                    firecracker.local_path())
        logger.info("Using Jailer: %s", jailer.local_path())
        logger.info("Using target version: %s", target_version)

        # v0.23 does not support creating diff snapshots.
        diff_snapshots = "0.23" not in target_version
        vm_instance = builder.build_vm_nano(fc_binary=firecracker.local_path(),
                                            jailer_binary=jailer.local_path(),
                                            diff_snapshots=diff_snapshots)
        vm = vm_instance.vm
        vm.start()

        # Create a snapshot builder from a microvm.
        snapshot_builder = SnapshotBuilder(vm)
        disks = [vm_instance.disks[0].local_path()]

        # Version 0.24 and greater have Diff support.
        snap_type = SnapshotType.DIFF if diff_snapshots else SnapshotType.FULL

        snapshot = snapshot_builder.create(disks,
                                           vm_instance.ssh_key,
                                           target_version=target_version,
                                           snapshot_type=snap_type)
        logger.debug("========== Firecracker create snapshot log ==========")
        logger.debug(vm.log_data)
        vm.kill()

        # Fetch Firecracker binary for the latest version
        fc_binary, _ = get_firecracker_binaries()
        # Verify the output of `--describe-snapshot` command line parameter
        cmd = [fc_binary] + ["--describe-snapshot", snapshot.vmstate]

        code, stdout, stderr = run_cmd(cmd)
        assert code == 0
        assert stderr == ''
        assert target_version in stdout
Exemplo n.º 17
0
def test_mmds_snapshot(bin_cloner_path):
    """
    Exercise MMDS behavior with snapshots.

    Ensures that MMDS V2 behavior is not affected by taking a snapshot
    and that MMDS V2 is not available after snapshot load.

    @type: functional
    """
    vm_builder = MicrovmBuilder(bin_cloner_path)
    net_iface = NetIfaceConfig()
    vm_instance = vm_builder.build_vm_nano(
        net_ifaces=[net_iface],
        diff_snapshots=True
    )
    test_microvm = vm_instance.vm
    root_disk = vm_instance.disks[0]
    ssh_key = vm_instance.ssh_key

    ipv4_address = '169.254.169.250'
    # Configure MMDS version with custom IPv4 address.
    _configure_mmds(
        test_microvm,
        version='V2',
        iface_id=DEFAULT_DEV_NAME,
        ipv4_address=ipv4_address
    )

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

    test_microvm.start()

    snapshot_builder = SnapshotBuilder(test_microvm)
    disks = [root_disk.local_path()]

    ssh_connection = net_tools.SSHConnection(test_microvm.ssh_config)
    cmd = 'ip route add {} dev eth0'.format(ipv4_address)
    _, stdout, stderr = ssh_connection.execute_command(cmd)
    _assert_out(stdout, stderr, '')

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

    pre = 'curl -m 2 -s'
    pre += ' -X GET'
    pre += ' -H  "X-metadata-token: {}"'.format(token)
    pre += ' http://{}/'.format(ipv4_address)

    # Fetch metadata.
    cmd = pre + 'latest/meta-data/'
    _, stdout, stderr = ssh_connection.execute_command(cmd)
    _assert_out(stdout, stderr, "ami-id")

    # Create diff snapshot.
    snapshot = snapshot_builder.create(disks,
                                       ssh_key,
                                       SnapshotType.DIFF)

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

    _, stdout, stderr = ssh_connection.execute_command(
        pre + 'latest/meta-data/'
    )
    _assert_out(stdout, stderr, "ami-id")

    # Kill base microVM.
    test_microvm.kill()

    # Load microVM clone from snapshot.
    test_microvm, _ = vm_builder.build_from_snapshot(snapshot,
                                                     resume=True,
                                                     diff_snapshots=True)
    _populate_data_store(test_microvm, data_store)
    ssh_connection = net_tools.SSHConnection(test_microvm.ssh_config)

    # Mmds V2 is not available with snapshots.
    # Test that `PUT` requests are not allowed.
    cmd = 'curl -m 2 -s'
    cmd += ' -X PUT'
    cmd += ' -H  "X-metadata-token-ttl-seconds: 1"'
    cmd += ' http://{}/latest/api/token'.format(ipv4_address)
    _, stdout, stderr = ssh_connection.execute_command(cmd)
    expected = "Not allowed HTTP method."
    _assert_out(stdout, stderr, expected)

    # Fetch metadata using V1 requests and ensure IPv4 configuration
    # is persistent between snapshots.
    cmd = 'curl -s http://{}/latest/meta-data/ami-id/'.format(ipv4_address)
    _, stdout, stderr = ssh_connection.execute_command(cmd)
    _assert_out(stdout, stderr, 'ami-12345678')
Exemplo n.º 18
0
def test_vsock_transport_reset(bin_cloner_path, bin_vsock_path,
                               test_fc_session_root_path):
    """
    Vsock transport reset test.

    Steps:
    1. Start echo server on the guest
    2. Start host workers that ping-pong data between guest and host,
    without closing any of them
    3. Pause VM -> Create snapshot -> Resume VM
    4. Check that worker sockets no longer work by setting a timeout
    so the sockets won't block and do a recv operation.
    5. If the recv operation timeouts, the connection was closed.
       Else, the connection was not closed and the test fails.
    6. Close VM -> Load VM from Snapshot -> check that vsock
       device is still working.

    @type: functional
    """
    vm_builder = MicrovmBuilder(bin_cloner_path)
    vm_instance = vm_builder.build_vm_nano()
    test_vm = vm_instance.vm
    root_disk = vm_instance.disks[0]
    ssh_key = vm_instance.ssh_key

    test_vm.vsock.put(vsock_id="vsock0",
                      guest_cid=3,
                      uds_path="/{}".format(VSOCK_UDS_PATH))

    test_vm.start()

    snapshot_builder = SnapshotBuilder(test_vm)
    disks = [root_disk.local_path()]

    # Generate the random data blob file.
    blob_path, blob_hash = make_blob(test_fc_session_root_path)
    vm_blob_path = "/tmp/vsock/test.blob"

    conn = SSHConnection(test_vm.ssh_config)
    # Set up a tmpfs drive on the guest, so we can copy the blob there.
    # Guest-initiated connections (echo workers) will use this blob.
    _copy_vsock_data_to_guest(conn, blob_path, vm_blob_path, bin_vsock_path)

    # Start guest echo server.
    path = os.path.join(test_vm.jailer.chroot_path(), VSOCK_UDS_PATH)
    conn = SSHConnection(test_vm.ssh_config)
    cmd = "vsock_helper echosrv -d {}".format(ECHO_SERVER_PORT)
    ecode, _, _ = conn.execute_command(cmd)
    assert ecode == 0

    # Start host workers that connect to the guest server.
    workers = []
    for _ in range(TEST_WORKER_COUNT):
        worker = HostEchoWorker(path, blob_path)
        workers.append(worker)
        worker.start()

    for wrk in workers:
        wrk.join()

    # Create snapshot.
    snapshot = snapshot_builder.create(disks, ssh_key, SnapshotType.FULL)
    response = test_vm.vm.patch(state="Resumed")
    assert test_vm.api_session.is_status_no_content(response.status_code)

    # Check that sockets are no longer working on workers.
    for worker in workers:
        # Whatever we send to the server, it should return the same
        # value.
        buf = bytearray("TEST\n".encode("utf-8"))
        worker.sock.send(buf)
        try:
            # Arbitrary timeout, we set this so the socket won't block as
            # it shouldn't receive anything.
            worker.sock.settimeout(0.25)
            response = worker.sock.recv(32)
            # If we reach here, it means the connection did not close.
            assert False, "Connection not closed: {}".format(
                response.decode("utf-8"))
        except SocketTimeout as exc:
            assert True, exc

    # Terminate VM.
    test_vm.kill()

    # Load snapshot.
    test_vm, _ = vm_builder.build_from_snapshot(snapshot,
                                                resume=True,
                                                diff_snapshots=False)

    # Check that vsock device still works.
    # Test guest-initiated connections.
    path = os.path.join(test_vm.path,
                        make_host_port_path(VSOCK_UDS_PATH, ECHO_SERVER_PORT))
    check_guest_connections(test_vm, path, vm_blob_path, blob_hash)

    # Test host-initiated connections.
    path = os.path.join(test_vm.jailer.chroot_path(), VSOCK_UDS_PATH)
    check_host_connections(test_vm, path, blob_path, blob_hash)
def test_negative_snapshot_permissions(bin_cloner_path):
    """
    Test missing permission error scenarios.

    @type: negative
    """
    logger = logging.getLogger("snapshot_negative")
    vm_builder = MicrovmBuilder(bin_cloner_path)

    # 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

    basevm.start()

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

    disks = [root_disk.local_path()]

    # Remove write permissions.
    os.chmod(basevm.jailer.chroot_path(), 0o444)

    try:
        _ = snapshot_builder.create(disks, ssh_key, SnapshotType.FULL)
    except AssertionError as error:
        # Check if proper error is returned.
        assert "Permission denied" in str(error)
    else:
        assert False, "Negative test failed"

    # Restore proper permissions.
    os.chmod(basevm.jailer.chroot_path(), 0o744)

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

    logger.info("Load snapshot, mem %s", snapshot.mem)

    basevm.kill()

    # Remove permissions for mem file.
    os.chmod(snapshot.mem, 0o000)

    try:
        _, _ = vm_builder.build_from_snapshot(snapshot,
                                              resume=True,
                                              diff_snapshots=True)
    except AssertionError as error:
        # Check if proper error is returned.
        assert "Cannot open the memory file: Permission denied" in str(error)
    else:
        assert False, "Negative test failed"

    # Remove permissions for state file.
    os.chmod(snapshot.vmstate, 0o000)

    try:
        _, _ = vm_builder.build_from_snapshot(snapshot,
                                              resume=True,
                                              diff_snapshots=True)
    except AssertionError as error:
        # Check if proper error is returned.
        assert ("Cannot perform open on the snapshot backing file:"
                " Permission denied" in str(error))
    else:
        assert False, "Negative test failed"

    # Restore permissions for state file.
    os.chmod(snapshot.vmstate, 0o744)
    os.chmod(snapshot.mem, 0o744)

    # Remove permissions for block file.
    os.chmod(snapshot.disks[0], 0o000)

    try:
        _, _ = vm_builder.build_from_snapshot(snapshot,
                                              resume=True,
                                              diff_snapshots=True)
    except AssertionError as error:
        # Check if proper error is returned.
        assert "Block(BackingFile(Os { code: 13, kind: PermissionDenied" in str(
            error)
    else:
        assert False, "Negative test failed"
def test_pause_resume(bin_cloner_path):
    """
    Test scenario: boot/pause/resume.

    @type: functional
    """
    builder = MicrovmBuilder(bin_cloner_path)
    vm_instance = builder.build_vm_nano()
    microvm = vm_instance.vm

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

    # Configure metrics system and start microVM.
    metrics_fifo_path = os.path.join(microvm.path, "metrics_fifo")
    metrics_fifo = log_tools.Fifo(metrics_fifo_path)
    response = microvm.metrics.put(
        metrics_path=microvm.create_jailed_resource(metrics_fifo.path)
    )
    assert microvm.api_session.is_status_no_content(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)

    # Flush and reset metrics as they contain pre-pause data.
    microvm.flush_metrics(metrics_fifo)

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

    # Verify emulation was indeed paused and no events from either
    # guest or host side were handled.
    verify_net_emulation_paused(microvm.flush_metrics(metrics_fifo))

    # 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()
Exemplo n.º 21
0
def test_mmds_snapshot(bin_cloner_path):
    """
    Exercise tokens' behavior with snapshots.

    Ensures that valid tokens created on a base microVM
    are not accepted on the clone VM.

    @type: functional
    """
    vm_builder = MicrovmBuilder(bin_cloner_path)
    vm_instance = vm_builder.build_vm_nano(diff_snapshots=True)
    test_microvm = vm_instance.vm
    root_disk = vm_instance.disks[0]
    ssh_key = vm_instance.ssh_key

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

    test_microvm.start()

    snapshot_builder = SnapshotBuilder(test_microvm)
    disks = [root_disk.local_path()]

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

    # Generate token.
    token = _generate_mmds_session_token(
        ssh_connection,
        ipv4_address="169.254.169.254",
        token_ttl=60
    )

    pre = 'curl -m 2 -s'
    pre += ' -X GET'
    pre += ' -H  "X-metadata-token: {}"'.format(token)
    pre += ' http://169.254.169.254/'

    cmd = pre + 'latest/meta-data/'
    _, stdout, stderr = ssh_connection.execute_command(cmd)
    _assert_out(stdout, stderr, "ami-id")

    # Setting MMDS version to V2 when V2 is already in use should
    # have no effect on tokens generated so far.
    _set_mmds_version(test_microvm, version='V2')
    _, stdout, stderr = ssh_connection.execute_command(cmd)
    _assert_out(stdout, stderr, "ami-id")

    # Create diff snapshot.
    snapshot = snapshot_builder.create(disks,
                                       ssh_key,
                                       SnapshotType.DIFF)

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

    _, stdout, stderr = ssh_connection.execute_command(
        pre + 'latest/meta-data/'
    )
    _assert_out(stdout, stderr, "ami-id")

    # Kill base microVM.
    test_microvm.kill()

    # Load microVM clone from snapshot.
    test_microvm, _ = vm_builder.build_from_snapshot(snapshot,
                                                     resume=True,
                                                     diff_snapshots=True)
    _populate_data_store(test_microvm, data_store)
    ssh_connection = net_tools.SSHConnection(test_microvm.ssh_config)

    # Ensure that token created on the baseVM is not valid inside the clone.
    cmd = 'curl -m 2 -s'
    cmd += ' -X GET'
    cmd += ' -H  "X-metadata-token: {}"'.format(token)
    cmd += ' http://169.254.169.254/latest/meta-data/'
    _, stdout, stderr = ssh_connection.execute_command(cmd)
    _assert_out(stdout, stderr, "MMDS token not valid.")

    # Generate new session token.
    token = _generate_mmds_session_token(
        ssh_connection,
        ipv4_address="169.254.169.254",
        token_ttl=60
    )

    # Ensure the newly created token is valid.
    cmd = 'curl -m 2 -s'
    cmd += ' -X GET'
    cmd += ' -H  "X-metadata-token: {}"'.format(token)
    cmd += ' http://169.254.169.254/latest/meta-data/'
    _, stdout, stderr = ssh_connection.execute_command(cmd)
    _assert_out(stdout, stderr, "ami-id")