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()
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])
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())
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
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)
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)
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)
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
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])
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
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() )
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)
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
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() )
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)
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
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)
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)
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
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 } } }]
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()