def firecrackers(self, keyword=None, min_version=None, max_version=None): """Return fc/jailer artifacts for the current arch.""" firecrackers = self._fetch_artifacts( ArtifactCollection.ARTIFACTS_BINARIES, ArtifactCollection.FC_EXTENSION, ArtifactType.FC, FirecrackerArtifact, keyword=keyword, ) # Filter out binaries with versions older than the `min_version` arg. if min_version is not None: return list( filter( lambda fc: compare_versions(fc.version, min_version) >= 0, firecrackers, )) # Filter out binaries with versions newer than the `max_version` arg. if max_version is not None: return list( filter( lambda fc: compare_versions(fc.version, max_version) <= 0, firecrackers, )) return firecrackers
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 create_json( self, vcpu_count=None, mem_size_mib=None, smt=None, cpu_template=None, track_dirty_pages=None, ): """Compose the json associated to this type of API request.""" datax = {} if vcpu_count is not None: datax["vcpu_count"] = vcpu_count if mem_size_mib is not None: datax["mem_size_mib"] = mem_size_mib if compare_versions(self._firecracker_version, "0.25.0") <= 0: datax["ht_enabled"] = False if smt is None else smt elif smt is not None: datax["smt"] = smt if cpu_template is not None: datax["cpu_template"] = cpu_template if track_dirty_pages is not None: datax["track_dirty_pages"] = track_dirty_pages return datax
def test_mmds_snapshot(bin_cloner_path, version): """ Test MMDS behavior by restoring a snapshot on current and past FC versions. Ensures that the version is persisted or initialised with the default if the firecracker version does not support it. @type: functional """ vm_builder = MicrovmBuilder(bin_cloner_path) vm_instance = vm_builder.build_vm_nano( net_ifaces=[NetIfaceConfig()] ) # Validate current version. _validate_mmds_snapshot( vm_instance, vm_builder, version) # Validate restoring in past versions. artifacts = ArtifactCollection(_test_images_s3_bucket()) # Fetch all firecracker binaries. # Create a snapshot with current build and restore with each FC binary # artifact. firecracker_artifacts = artifacts.firecrackers( # v1.1.0 breaks snapshot compatibility with older versions. min_version="1.1.0", max_version=get_firecracker_version_from_toml()) for firecracker in firecracker_artifacts: vm_instance = vm_builder.build_vm_nano( net_ifaces=[NetIfaceConfig()] ) firecracker.download() jailer = firecracker.jailer() jailer.download() target_version = firecracker.base_name()[1:] # If the version is smaller or equal to 1.0.0, we expect that # MMDS will be initialised with V1 by default. if compare_versions(target_version, "1.0.0") <= 0: mmds_version = "V1" else: mmds_version = version _validate_mmds_snapshot( vm_instance, vm_builder, mmds_version, target_fc_version=target_version, fc_path=firecracker.local_path(), jailer_path=jailer.local_path() )
def put(self, **args): """Attach a block device or update the details of a previous one.""" # Default the io engine to Async on kernels > 5.10 so that we # make sure to exercise both Sync and Async behaviour in the CI. # Also check the FC version to make sure that it has support for # configurable io_engine. if (is_io_uring_supported() and compare_versions(self._firecracker_version, "0.25.0") > 0 and ("io_engine" not in args or args["io_engine"] is None)): args["io_engine"] = "Async" datax = self.create_json(**args) return self._api_session.put("{}/{}".format(self._drive_cfg_url, args["drive_id"]), json=datax)
def snapshot_resume_measurements(vm_type): """Define measurements for snapshot resume tests.""" load_latency = LOAD_LATENCY_BASELINES[platform.machine()][vm_type] # Host kernels >= 5.4 add an up to ~30ms latency. # See: https://github.com/firecracker-microvm/firecracker/issues/2129 linux_version = platform.release() for idx, char in enumerate(linux_version): if not char.isdigit() and char != '.': linux_version = linux_version[0:idx] break if compare_versions(linux_version, "5.4.0") > 0: load_latency["target"] += 30 latency = types.MeasurementDef.create_measurement( "latency", "ms", [function.Max("max")], {"max": criteria.LowerThan(load_latency)}) return [latency]
def snapshot_resume_measurements(vm_type): """Define measurements for snapshot resume tests.""" load_latency = LOAD_LATENCY_BASELINES[platform.machine()][vm_type] if is_io_uring_supported(): # There is added latency caused by the io_uring syscalls used by the # block device. load_latency["target"] += 115 if compare_versions(get_kernel_version(), "5.4.0") > 0: # Host kernels >= 5.4 add an up to ~30ms latency. # See: https://github.com/firecracker-microvm/firecracker/issues/2129 load_latency["target"] += 30 latency = types.MeasurementDef.create_measurement( "latency", "ms", [function.Max("max")], {"max": criteria.LowerThan(load_latency)}) return [latency]
def _validate_mmds_snapshot( vm_instance, vm_builder, version, target_fc_version=None, fc_path=None, jailer_path=None ): """Test MMDS behaviour across snap-restore.""" basevm = vm_instance.vm root_disk = vm_instance.disks[0] disks = [root_disk.local_path()] ssh_key = vm_instance.ssh_key ipv4_address = '169.254.169.250' # Configure MMDS version with custom IPv4 address. configure_mmds( basevm, version=version, iface_ids=[DEFAULT_DEV_NAME], ipv4_address=ipv4_address, fc_version=target_fc_version ) # Check if the FC version supports the latest format for mmds-config. # If target_fc_version is None, we assume the current version is used. if target_fc_version is None or \ (target_fc_version is not None and compare_versions(target_fc_version, "1.0.0") >= 0): expected_mmds_config = { "version": version, "ipv4_address": ipv4_address, "network_interfaces": [DEFAULT_DEV_NAME] } response = basevm.full_cfg.get() assert basevm.api_session.is_status_ok(response.status_code) assert response.json()["mmds-config"] == expected_mmds_config data_store = { 'latest': { 'meta-data': { 'ami-id': 'ami-12345678' } } } _populate_data_store(basevm, data_store) basevm.start() snapshot_builder = SnapshotBuilder(basevm) ssh_connection = net_tools.SSHConnection(basevm.ssh_config) _run_guest_cmd(ssh_connection, f'ip route add {ipv4_address} dev eth0', '') # Generate token if needed. token = None if version == "V2": token = generate_mmds_session_token( ssh_connection, ipv4_address, token_ttl=60 ) # Fetch metadata. cmd = generate_mmds_get_request( ipv4_address, token=token, ) _run_guest_cmd(ssh_connection, cmd, data_store, use_json=True) # Create snapshot. snapshot = snapshot_builder.create(disks, ssh_key, SnapshotType.FULL, target_version=target_fc_version) # Resume microVM and ensure session token is still valid on the base. response = basevm.vm.patch(state='Resumed') assert basevm.api_session.is_status_no_content(response.status_code) # Fetch metadata again using the same token. _run_guest_cmd(ssh_connection, cmd, data_store, use_json=True) # Kill base microVM. basevm.kill() # Load microVM clone from snapshot. microvm, _ = vm_builder.build_from_snapshot(snapshot, resume=True, fc_binary=fc_path, jailer_binary=jailer_path) ssh_connection = net_tools.SSHConnection(microvm.ssh_config) # Check the reported mmds config. In versions up to (including) v1.0.0 this # was not populated after restore. if target_fc_version is not None and \ compare_versions("1.0.0", target_fc_version) < 0: response = microvm.full_cfg.get() assert microvm.api_session.is_status_ok(response.status_code) assert response.json()["mmds-config"] == expected_mmds_config if version == 'V1': # Verify that V2 requests don't work assert generate_mmds_session_token( ssh_connection, ipv4_address, token_ttl=60 ) == "Not allowed HTTP method." token = None else: # Attempting to reuse the token across a restore must fail. cmd = generate_mmds_get_request(ipv4_address, token=token) _run_guest_cmd(ssh_connection, cmd, 'MMDS token not valid.') # Generate token. token = generate_mmds_session_token( ssh_connection, ipv4_address, token_ttl=60 ) # Data store is empty after a restore. cmd = generate_mmds_get_request(ipv4_address, token=token) _run_guest_cmd(ssh_connection, cmd, 'null') # Now populate the store. _populate_data_store(microvm, data_store) # Fetch metadata. _run_guest_cmd(ssh_connection, cmd, data_store, use_json=True)