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 )
def build(self, kernel: Artifact, disks: [DiskArtifact], ssh_key: Artifact, config: Artifact, enable_diff_snapshots=False, cpu_template=None): """Build a fresh microvm.""" vm = init_microvm(self.root_path, self.bin_cloner_path, self._fc_binary, self._jailer_binary) # Link the microvm to kernel, rootfs, ssh_key artifacts. vm.kernel_file = kernel.local_path() vm.rootfs_file = disks[0].local_path() # Start firecracker. vm.spawn() # Download ssh key into the microvm root. ssh_key.download(self.root_path) vm.ssh_config['ssh_key_path'] = ssh_key.local_path() os.chmod(vm.ssh_config['ssh_key_path'], 0o400) vm.create_tap_and_ssh_config(host_ip=DEFAULT_HOST_IP, guest_ip=DEFAULT_GUEST_IP, netmask_len=DEFAULT_NETMASK, tapname=DEFAULT_TAP_NAME) # TODO: propper network configuraiton with artifacts. guest_mac = net_tools.mac_from_ip(DEFAULT_GUEST_IP) response = vm.network.put( iface_id=DEFAULT_DEV_NAME, host_dev_name=DEFAULT_TAP_NAME, guest_mac=guest_mac, allow_mmds_requests=True, ) assert vm.api_session.is_status_no_content(response.status_code) with open(config.local_path()) as microvm_config_file: microvm_config = json.load(microvm_config_file) response = vm.basic_config(boot_args='console=ttyS0 reboot=k panic=1') # Apply the microvm artifact configuration and template. response = vm.machine_cfg.put( vcpu_count=int(microvm_config['vcpu_count']), mem_size_mib=int(microvm_config['mem_size_mib']), ht_enabled=bool(microvm_config['ht_enabled']), track_dirty_pages=enable_diff_snapshots, cpu_template=cpu_template, ) assert vm.api_session.is_status_no_content(response.status_code) # Reset root path so next microvm is built some place else. self.init_root_path() return vm
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 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_good_response(response.status_code) self.ssh_config['hostname'] = guest_ip return tap, host_ip, guest_ip
def ssh_network_config( self, network_config, iface_id, tx_rate_limiter=None, rx_rate_limiter=None, tapname=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 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 = tapname or (self.id[:8] + "tap" + iface_id) (host_ip, guest_ip) = network_config.get_next_available_ips(2) tap = self.create_tap_and_ssh_config(host_ip, guest_ip, network_config.get_netmask_len(), tapname) 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, tx_rate_limiter=tx_rate_limiter, rx_rate_limiter=rx_rate_limiter, ) assert self._api_session.is_status_no_content(response.status_code) return tap, host_ip, guest_ip
def build(self, kernel: Artifact, disks: [DiskArtifact], ssh_key: Artifact, config: Artifact, net_ifaces=None, enable_diff_snapshots=False, cpu_template=None, use_ramdisk=False): """Build a fresh microvm.""" vm = init_microvm(self.root_path, self.bin_cloner_path, self._fc_binary, self._jailer_binary) # Start firecracker. vm.spawn(use_ramdisk=use_ramdisk) # Link the microvm to kernel, rootfs, ssh_key artifacts. vm.kernel_file = kernel.local_path() vm.rootfs_file = disks[0].local_path() # copy rootfs to ramdisk if needed jailed_rootfs_path = vm.copy_to_jail_ramfs(vm.rootfs_file) if \ use_ramdisk else vm.create_jailed_resource(vm.rootfs_file) # Download ssh key into the microvm root. ssh_key.download(self.root_path) vm.ssh_config['ssh_key_path'] = ssh_key.local_path() os.chmod(vm.ssh_config['ssh_key_path'], 0o400) # Provide a default network configuration. if net_ifaces is None or len(net_ifaces) == 0: ifaces = [NetIfaceConfig()] else: ifaces = net_ifaces # Configure network interfaces using artifacts. for iface in ifaces: vm.create_tap_and_ssh_config(host_ip=iface.host_ip, guest_ip=iface.guest_ip, netmask_len=iface.netmask, tapname=iface.tap_name) guest_mac = net_tools.mac_from_ip(iface.guest_ip) response = vm.network.put( iface_id=iface.dev_name, host_dev_name=iface.tap_name, guest_mac=guest_mac, allow_mmds_requests=True, ) assert vm.api_session.is_status_no_content(response.status_code) with open(config.local_path()) as microvm_config_file: microvm_config = json.load(microvm_config_file) response = vm.basic_config( add_root_device=False, boot_args='console=ttyS0 reboot=k panic=1' ) # Add the root file system with rw permissions. response = vm.drive.put( drive_id='rootfs', path_on_host=jailed_rootfs_path, is_root_device=True, is_read_only=False ) assert vm.api_session \ .is_status_no_content(response.status_code), \ response.text # Apply the microvm artifact configuration and template. response = vm.machine_cfg.put( vcpu_count=int(microvm_config['vcpu_count']), mem_size_mib=int(microvm_config['mem_size_mib']), ht_enabled=bool(microvm_config['ht_enabled']), track_dirty_pages=enable_diff_snapshots, cpu_template=cpu_template, ) assert vm.api_session.is_status_no_content(response.status_code) vm.vcpus_count = int(microvm_config['vcpu_count']) # Reset root path so next microvm is built some place else. self.init_root_path() return vm