def test_create_invalid_version(bin_cloner_path): """Test scenario: create snapshot targeting invalid version.""" # Use a predefined vm instance. test_microvm = VMNano.spawn(bin_cloner_path).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 "Cannot translate microVM version to snapshot data version" 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"
def test_patch_drive_snapshot(bin_cloner_path): """Test scenario: 5 full sequential snapshots.""" logger = logging.getLogger("snapshot_sequence") vm_builder = MicrovmBuilder(bin_cloner_path) snapshot_type = SnapshotType.FULL enable_diff_snapshots = False # Use a predefined vm instance. vm_instance = VMNano.spawn(bin_cloner_path) 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, 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 test_pause_resume(bin_cloner_path): """Test scenario: boot/pause/resume.""" vm_instance = VMNano.spawn(bin_cloner_path) 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) microvm.start() ssh_connection = net_tools.SSHConnection(microvm.ssh_config) # Verify guest is active. exit_code, _, _ = ssh_connection.execute_command("ls") assert exit_code == 0 # Pausing the microVM after it's been started is successful. response = microvm.vm.patch(state='Paused') assert microvm.api_session.is_status_no_content(response.status_code) # Verify guest is no longer active. exit_code, _, _ = ssh_connection.execute_command("ls") assert exit_code != 0 # Pausing the microVM when it is already `Paused` is allowed # (microVM remains in `Paused` state). response = microvm.vm.patch(state='Paused') assert microvm.api_session.is_status_no_content(response.status_code) # Resuming the microVM is successful. response = microvm.vm.patch(state='Resumed') assert microvm.api_session.is_status_no_content(response.status_code) # Verify guest is active again. exit_code, _, _ = ssh_connection.execute_command("ls") assert exit_code == 0 # Resuming the microVM when it is already `Resumed` is allowed # (microVM remains in the running state). response = microvm.vm.patch(state='Resumed') assert microvm.api_session.is_status_no_content(response.status_code) # Verify guest is still active. exit_code, _, _ = ssh_connection.execute_command("ls") assert exit_code == 0 microvm.kill()
def test_negative_postload_api(bin_cloner_path): """Test APIs fail after loading from snapshot.""" logger = logging.getLogger("snapshot_api_fail") vm_builder = MicrovmBuilder(bin_cloner_path) vm_instance = VMNano.spawn(bin_cloner_path, 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, False, True) fail_msg = "The requested operation is not supported after starting " \ "the microVM" try: microvm.start() except AssertionError as error: assert fail_msg in str(error) else: assert False, "Negative test failed" try: microvm.basic_config() except AssertionError as error: assert fail_msg in str(error) else: assert False, "Negative test failed" microvm.kill()
def test_negative_api_lifecycle(bin_cloner_path): """Test some vm lifecycle error scenarios.""" vm_instance = VMNano.spawn(bin_cloner_path) 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_negative_snapshot_create(bin_cloner_path): """Test create snapshot before pause.""" vm_instance = VMNano.spawn(bin_cloner_path) 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) assert "Cannot get dirty bitmap" in response.text vm.kill()
def create_snapshot_helper(bin_cloner_path, logger, target_version=None, drives=None, ifaces=None, fc_binary=None, jailer_binary=None, balloon=False, diff_snapshots=False): """Create a snapshot with many devices.""" vm_instance = VMNano.spawn(bin_cloner_path, False, fc_binary, jailer_binary, net_ifaces=ifaces, diff_snapshots=diff_snapshots) vm = vm_instance.vm if diff_snapshots is False: snapshot_type = SnapshotType.FULL else: # Version 0.24 and greater has Diff and ballon support. snapshot_type = SnapshotType.DIFF if balloon: # Copy balloon test util. copy_util_to_rootfs(vm_instance.disks[0].local_path(), 'fillmem') # Add a memory balloon with stats enabled. response = vm.balloon.put( amount_mb=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 net_ifaces: vm.ssh_config['hostname'] = iface.guest_ip ssh_connection = net_tools.SSHConnection(vm.ssh_config) exit_code, _, _ = ssh_connection.execute_command("sync") assert exit_code == 0 # Mount scratch drives in guest. for blk in scratch_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=net_ifaces) logger.debug("========== Firecracker create snapshot log ==========") logger.debug(vm.log_data) vm.kill() return snapshot
def test_negative_snapshot_permissions(bin_cloner_path): """Test missing permission error scenarios.""" logger = logging.getLogger("snapshot_negative") vm_builder = MicrovmBuilder(bin_cloner_path) # Use a predefined vm instance. vm_instance = VMNano.spawn(bin_cloner_path) 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, True, True) except AssertionError as error: # Check if proper error is returned. assert "Cannot open 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, True, True) except AssertionError as error: # Check if proper error is returned. assert "Cannot open snapshot 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, True, True) except AssertionError as error: # Check if proper error is returned. assert "Block(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.""" vm_instance = VMNano.spawn(bin_cloner_path) 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()