def test_net_api_put_update_pre_boot(test_microvm_with_api): """ Test PUT updates on network configurations before the microvm boots. @type: functional """ test_microvm = test_microvm_with_api test_microvm.spawn() first_if_name = 'first_tap' tap1 = net_tools.Tap(first_if_name, test_microvm.jailer.netns) response = test_microvm.network.put(iface_id='1', guest_mac='06:00:00:00:00:01', host_dev_name=tap1.name) assert test_microvm.api_session.is_status_no_content(response.status_code) # Adding new network interfaces is allowed. second_if_name = 'second_tap' tap2 = net_tools.Tap(second_if_name, test_microvm.jailer.netns) response = test_microvm.network.put(iface_id='2', guest_mac='07:00:00:00:00:01', host_dev_name=tap2.name) assert test_microvm.api_session.is_status_no_content(response.status_code) # Updates to a network interface with an unavailable MAC are not allowed. guest_mac = '06:00:00:00:00:01' response = test_microvm.network.put(iface_id='2', host_dev_name=second_if_name, guest_mac=guest_mac) assert test_microvm.api_session.is_status_bad_request(response.status_code) assert \ "The guest MAC address {} is already in use.".format(guest_mac) \ in response.text # Updates to a network interface with an available MAC are allowed. response = test_microvm.network.put(iface_id='2', host_dev_name=second_if_name, guest_mac='08:00:00:00:00:01') assert test_microvm.api_session.is_status_no_content(response.status_code) # Updates to a network interface with an unavailable name are not allowed. response = test_microvm.network.put(iface_id='1', host_dev_name=second_if_name, guest_mac='06:00:00:00:00:01') assert test_microvm.api_session.is_status_bad_request(response.status_code) assert "Could not create Network Device" \ in response.text # Updates to a network interface with an available name are allowed. iface_id = '1' 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, host_dev_name=tap3.name, guest_mac='06:00:00:00:00:01') assert test_microvm.api_session.is_status_no_content(response.status_code)
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_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 test_api_put_update_post_boot(test_microvm_with_api): """ Test that PUT updates are rejected after the microvm boots. @type: negative """ 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() 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() expected_err = "The requested operation is not supported " \ "after starting the microVM" # Valid updates to `kernel_image_path` are not allowed after boot. response = test_microvm.boot.put( kernel_image_path=test_microvm.get_jailed_resource( test_microvm.kernel_file)) assert test_microvm.api_session.is_status_bad_request(response.status_code) assert expected_err in response.text # Valid updates to the machine configuration are not allowed after boot. response = test_microvm.machine_cfg.patch(vcpu_count=4) assert test_microvm.api_session.is_status_bad_request(response.status_code) assert expected_err in response.text response = test_microvm.machine_cfg.put(vcpu_count=4, ht_enabled=False, mem_size_mib=128) assert test_microvm.api_session.is_status_bad_request(response.status_code) assert expected_err in response.text # Network interface update is not allowed after boot. response = test_microvm.network.put(iface_id='1', host_dev_name=tap1.name, guest_mac='06:00:00:00:00:02') assert test_microvm.api_session.is_status_bad_request(response.status_code) assert expected_err in response.text # Block device update is not allowed after boot. response = test_microvm.drive.put( drive_id='rootfs', path_on_host=test_microvm.jailer.jailed_path(test_microvm.rootfs_file), is_read_only=False, is_root_device=True) assert test_microvm.api_session.is_status_bad_request(response.status_code) assert expected_err in response.text
def create_tap_and_ssh_config(self, host_ip, guest_ip, netmask_len, tapname=None): """Create tap device and configure ssh.""" assert tapname is not None tap = net_tools.Tap(tapname, self._jailer.netns, ip="{}/{}".format(host_ip, netmask_len)) self.config_ssh(guest_ip) return tap
def ssh_network_config( self, network_config, iface_id, allow_mmds_requests=False, tx_rate_limiter=None, rx_rate_limiter=None ): """Create a host tap device and a guest network interface. 'network_config' is used to generate 2 IPs: one for the tap device and one for the microvm. Adds the hostname of the microvm to the ssh_config dictionary. :param network_config: UniqueIPv4Generator instance :param iface_id: the interface id for the API request :param allow_mmds_requests: specifies whether requests sent from the guest on this interface towards the MMDS address are intercepted and processed by the device model. :param tx_rate_limiter: limit the tx rate :param rx_rate_limiter: limit the rx rate :return: an instance of the tap which needs to be kept around until cleanup is desired, the configured guest and host ips, respectively. """ # Create tap before configuring interface. tapname = self.id[:8] + 'tap' + iface_id (host_ip, guest_ip) = network_config.get_next_available_ips(2) tap = net_tools.Tap( tapname, self._jailer.netns, ip="{}/{}".format( host_ip, network_config.get_netmask_len() ) ) guest_mac = net_tools.mac_from_ip(guest_ip) response = self.network.put( iface_id=iface_id, host_dev_name=tapname, guest_mac=guest_mac, allow_mmds_requests=allow_mmds_requests, tx_rate_limiter=tx_rate_limiter, rx_rate_limiter=rx_rate_limiter ) assert self._api_session.is_status_no_content(response.status_code) self.ssh_config['hostname'] = guest_ip return tap, host_ip, guest_ip
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_get_full_config(test_microvm_with_api): """ Test the reported configuration of a microVM configured with all resources. @type: functional """ test_microvm = test_microvm_with_api expected_cfg = {} test_microvm.spawn() # Basic config also implies a root block device. test_microvm.basic_config() expected_cfg['machine-config'] = { 'vcpu_count': 2, 'mem_size_mib': 256, 'ht_enabled': False, 'track_dirty_pages': False } expected_cfg['boot-source'] = { 'kernel_image_path': '/vmlinux.bin', 'initrd_path': None } expected_cfg['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 }] # Add a memory balloon device. response = test_microvm.balloon.put(amount_mib=1, deflate_on_oom=True) assert test_microvm.api_session.is_status_no_content(response.status_code) expected_cfg['balloon'] = { 'amount_mib': 1, 'deflate_on_oom': True, 'stats_polling_interval_s': 0 } # Add a vsock device. response = test_microvm.vsock.put(guest_cid=15, uds_path='vsock.sock') assert test_microvm.api_session.is_status_no_content(response.status_code) expected_cfg['vsock'] = {'guest_cid': 15, 'uds_path': 'vsock.sock'} # Add a net device. iface_id = '1' tapname = test_microvm.id[:8] + 'tap' + iface_id tap1 = net_tools.Tap(tapname, test_microvm.jailer.netns) guest_mac = '06:00:00:00:00:01' tx_rl = { 'bandwidth': { 'size': 1000000, 'refill_time': 100, 'one_time_burst': None }, 'ops': None } response = test_microvm.network.put(iface_id=iface_id, guest_mac=guest_mac, host_dev_name=tap1.name, tx_rate_limiter=tx_rl) assert test_microvm.api_session.is_status_no_content(response.status_code) expected_cfg['network-interfaces'] = [{ 'iface_id': iface_id, 'host_dev_name': tap1.name, 'guest_mac': '06:00:00:00:00:01', 'rx_rate_limiter': None, 'tx_rate_limiter': tx_rl, 'allow_mmds_requests': False }] expected_cfg['logger'] = None expected_cfg['metrics'] = None expected_cfg['mmds-config'] = None # Getting full vm configuration should be available pre-boot. response = test_microvm.full_cfg.get() assert test_microvm.api_session.is_status_ok(response.status_code) assert response.json() == expected_cfg # Start the microvm. test_microvm.start() # Validate full vm configuration post-boot as well. response = test_microvm.full_cfg.get() assert test_microvm.api_session.is_status_ok(response.status_code) assert response.json() == expected_cfg