Path: blob/main/tests/integration_tests/functional/test_api.py
1958 views
# Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.1# SPDX-License-Identifier: Apache-2.02"""Tests that ensure the correctness of the Firecracker API."""34# Disable pylint C0302: Too many lines in module5# pylint: disable=C03026import os7import platform8import resource9import time1011import pytest1213from framework.builder import MicrovmBuilder14import framework.utils_cpuid as utils15import host_tools.drive as drive_tools16import host_tools.logging as log_tools17import host_tools.network as net_tools1819MEM_LIMIT = 1000000000202122def test_api_happy_start(test_microvm_with_api):23"""Test a regular microvm API start sequence."""24test_microvm = test_microvm_with_api25test_microvm.spawn()2627# Set up the microVM with 2 vCPUs, 256 MiB of RAM and28# a root file system with the rw permission.29test_microvm.basic_config()3031test_microvm.start()323334def test_api_put_update_pre_boot(test_microvm_with_api):35"""Test that PUT updates are allowed before the microvm boots."""36test_microvm = test_microvm_with_api37test_microvm.spawn()3839# Set up the microVM with 2 vCPUs, 256 MiB of RAM and40# a root file system with the rw permission.41test_microvm.basic_config()4243fs1 = drive_tools.FilesystemFile(44os.path.join(test_microvm.fsfiles, 'scratch')45)46response = test_microvm.drive.put(47drive_id='scratch',48path_on_host=test_microvm.create_jailed_resource(fs1.path),49is_root_device=False,50is_read_only=False51)52assert test_microvm.api_session.is_status_no_content(response.status_code)5354# Updates to `kernel_image_path` with an invalid path are not allowed.55response = test_microvm.boot.put(56kernel_image_path='foo.bar'57)58assert test_microvm.api_session.is_status_bad_request(response.status_code)59assert "The kernel file cannot be opened: No such file or directory " \60"(os error 2)" in response.text6162# Updates to `kernel_image_path` with a valid path are allowed.63response = test_microvm.boot.put(64kernel_image_path=test_microvm.get_jailed_resource(65test_microvm.kernel_file66)67)68assert test_microvm.api_session.is_status_no_content(response.status_code)6970# Updates to `path_on_host` with an invalid path are not allowed.71response = test_microvm.drive.put(72drive_id='rootfs',73path_on_host='foo.bar',74is_read_only=True,75is_root_device=True76)77assert test_microvm.api_session.is_status_bad_request(response.status_code)78assert "Invalid block device path" in response.text7980# Updates to `is_root_device` that result in two root block devices are not81# allowed.82response = test_microvm.drive.put(83drive_id='scratch',84path_on_host=test_microvm.get_jailed_resource(fs1.path),85is_read_only=False,86is_root_device=True87)88assert test_microvm.api_session.is_status_bad_request(response.status_code)89assert "A root block device already exists" in response.text9091# Valid updates to `path_on_host` and `is_read_only` are allowed.92fs2 = drive_tools.FilesystemFile(93os.path.join(test_microvm.fsfiles, 'otherscratch')94)95response = test_microvm.drive.put(96drive_id='scratch',97path_on_host=test_microvm.create_jailed_resource(fs2.path),98is_read_only=True,99is_root_device=False100)101assert test_microvm.api_session.is_status_no_content(response.status_code)102103# Valid updates to all fields in the machine configuration are allowed.104# The machine configuration has a default value, so all PUTs are updates.105microvm_config_json = {106'vcpu_count': 4,107'ht_enabled': True,108'mem_size_mib': 256,109'track_dirty_pages': True110}111if platform.machine() == 'x86_64':112microvm_config_json['cpu_template'] = 'C3'113114if platform.machine() == 'aarch64':115response = test_microvm.machine_cfg.put(116vcpu_count=microvm_config_json['vcpu_count'],117ht_enabled=microvm_config_json['ht_enabled'],118mem_size_mib=microvm_config_json['mem_size_mib'],119track_dirty_pages=microvm_config_json['track_dirty_pages']120)121else:122response = test_microvm.machine_cfg.put(123vcpu_count=microvm_config_json['vcpu_count'],124ht_enabled=microvm_config_json['ht_enabled'],125mem_size_mib=microvm_config_json['mem_size_mib'],126cpu_template=microvm_config_json['cpu_template'],127track_dirty_pages=microvm_config_json['track_dirty_pages']128)129130assert test_microvm.api_session.is_status_no_content(response.status_code)131132response = test_microvm.machine_cfg.get()133assert test_microvm.api_session.is_status_ok(response.status_code)134response_json = response.json()135136vcpu_count = microvm_config_json['vcpu_count']137assert response_json['vcpu_count'] == vcpu_count138139ht_enabled = microvm_config_json['ht_enabled']140assert response_json['ht_enabled'] == ht_enabled141142mem_size_mib = microvm_config_json['mem_size_mib']143assert response_json['mem_size_mib'] == mem_size_mib144145if platform.machine() == 'x86_64':146cpu_template = str(microvm_config_json['cpu_template'])147assert response_json['cpu_template'] == cpu_template148149track_dirty_pages = microvm_config_json['track_dirty_pages']150assert response_json['track_dirty_pages'] == track_dirty_pages151152153def test_net_api_put_update_pre_boot(test_microvm_with_api):154"""Test PUT updates on network configurations before the microvm boots."""155test_microvm = test_microvm_with_api156test_microvm.spawn()157158first_if_name = 'first_tap'159tap1 = net_tools.Tap(first_if_name, test_microvm.jailer.netns)160response = test_microvm.network.put(161iface_id='1',162guest_mac='06:00:00:00:00:01',163host_dev_name=tap1.name164)165assert test_microvm.api_session.is_status_no_content(response.status_code)166167# Adding new network interfaces is allowed.168second_if_name = 'second_tap'169tap2 = net_tools.Tap(second_if_name, test_microvm.jailer.netns)170response = test_microvm.network.put(171iface_id='2',172guest_mac='07:00:00:00:00:01',173host_dev_name=tap2.name174)175assert test_microvm.api_session.is_status_no_content(response.status_code)176177# Updates to a network interface with an unavailable MAC are not allowed.178guest_mac = '06:00:00:00:00:01'179response = test_microvm.network.put(180iface_id='2',181host_dev_name=second_if_name,182guest_mac=guest_mac183)184assert test_microvm.api_session.is_status_bad_request(response.status_code)185assert \186"The guest MAC address {} is already in use.".format(guest_mac) \187in response.text188189# Updates to a network interface with an available MAC are allowed.190response = test_microvm.network.put(191iface_id='2',192host_dev_name=second_if_name,193guest_mac='08:00:00:00:00:01'194)195assert test_microvm.api_session.is_status_no_content(response.status_code)196197# Updates to a network interface with an unavailable name are not allowed.198response = test_microvm.network.put(199iface_id='1',200host_dev_name=second_if_name,201guest_mac='06:00:00:00:00:01'202)203assert test_microvm.api_session.is_status_bad_request(response.status_code)204assert "Could not create Network Device" \205in response.text206207# Updates to a network interface with an available name are allowed.208iface_id = '1'209tapname = test_microvm.id[:8] + 'tap' + iface_id210211tap3 = net_tools.Tap(tapname, test_microvm.jailer.netns)212response = test_microvm.network.put(213iface_id=iface_id,214host_dev_name=tap3.name,215guest_mac='06:00:00:00:00:01'216)217assert test_microvm.api_session.is_status_no_content(response.status_code)218219220def test_api_put_machine_config(test_microvm_with_api):221"""Test /machine_config PUT scenarios that unit tests can't cover."""222test_microvm = test_microvm_with_api223test_microvm.spawn()224225# Test invalid vcpu count < 0.226response = test_microvm.machine_cfg.put(227vcpu_count='-2'228)229assert test_microvm.api_session.is_status_bad_request(response.status_code)230231# Test invalid type for ht_enabled flag.232response = test_microvm.machine_cfg.put(233ht_enabled='random_string'234)235assert test_microvm.api_session.is_status_bad_request(response.status_code)236237# Test invalid CPU template.238response = test_microvm.machine_cfg.put(239cpu_template='random_string'240)241assert test_microvm.api_session.is_status_bad_request(response.status_code)242243response = test_microvm.machine_cfg.patch(244track_dirty_pages=True245)246assert test_microvm.api_session.is_status_bad_request(response.status_code)247248response = test_microvm.machine_cfg.patch(249cpu_template='C3'250)251if platform.machine() == "x86_64":252assert test_microvm.api_session.is_status_no_content(253response.status_code254)255else:256assert test_microvm.api_session.is_status_bad_request(257response.status_code258)259assert "CPU templates are not supported on aarch64" in response.text260261# Test invalid mem_size_mib < 0.262response = test_microvm.machine_cfg.put(263mem_size_mib='-2'264)265assert test_microvm.api_session.is_status_bad_request(response.status_code)266267# Test invalid mem_size_mib > usize::MAX.268bad_size = 1 << 64269response = test_microvm.machine_cfg.put(270mem_size_mib=bad_size271)272fail_msg = "error occurred when deserializing the json body of a " \273"request: invalid type"274assert test_microvm.api_session.is_status_bad_request(response.status_code)275assert fail_msg in response.text276277# Test mem_size_mib of valid type, but too large.278test_microvm.basic_config()279firecracker_pid = int(test_microvm.jailer_clone_pid)280resource.prlimit(281firecracker_pid,282resource.RLIMIT_AS,283(MEM_LIMIT, resource.RLIM_INFINITY)284)285286bad_size = (1 << 64) - 1287response = test_microvm.machine_cfg.patch(288mem_size_mib=bad_size289)290assert test_microvm.api_session.is_status_no_content(response.status_code)291292response = test_microvm.actions.put(action_type='InstanceStart')293fail_msg = "Invalid Memory Configuration: MmapRegion(Mmap(Os { code: " \294"12, kind: Other, message: Out of memory }))"295assert test_microvm.api_session.is_status_bad_request(response.status_code)296assert fail_msg in response.text297298# Test invalid mem_size_mib = 0.299response = test_microvm.machine_cfg.patch(300mem_size_mib=0301)302assert test_microvm.api_session.is_status_bad_request(response.status_code)303assert "The memory size (MiB) is invalid." in response.text304305# Test valid mem_size_mib.306response = test_microvm.machine_cfg.patch(307mem_size_mib=256308)309assert test_microvm.api_session.is_status_no_content(response.status_code)310311response = test_microvm.actions.put(action_type='InstanceStart')312if utils.get_cpu_vendor() != utils.CpuVendor.INTEL:313# We shouldn't be able to apply Intel templates on AMD hosts314fail_msg = "Internal error while starting microVM: Error configuring" \315" the vcpu for boot: Cpuid error: InvalidVendor"316assert test_microvm.api_session.is_status_bad_request(317response.status_code)318assert fail_msg in response.text319else:320assert test_microvm.api_session.is_status_no_content(321response.status_code)322323# Validate full vm configuration after patching machine config.324response = test_microvm.full_cfg.get()325assert test_microvm.api_session.is_status_ok(response.status_code)326assert response.json()['machine-config']['vcpu_count'] == 2327assert response.json()['machine-config']['mem_size_mib'] == 256328329330def test_api_put_update_post_boot(test_microvm_with_api):331"""Test that PUT updates are rejected after the microvm boots."""332test_microvm = test_microvm_with_api333test_microvm.spawn()334335# Set up the microVM with 2 vCPUs, 256 MiB of RAM and336# a root file system with the rw permission.337test_microvm.basic_config()338339iface_id = '1'340tapname = test_microvm.id[:8] + 'tap' + iface_id341tap1 = net_tools.Tap(tapname, test_microvm.jailer.netns)342response = test_microvm.network.put(343iface_id=iface_id,344host_dev_name=tap1.name,345guest_mac='06:00:00:00:00:01'346)347assert test_microvm.api_session.is_status_no_content(response.status_code)348349test_microvm.start()350351expected_err = "The requested operation is not supported " \352"after starting the microVM"353354# Valid updates to `kernel_image_path` are not allowed after boot.355response = test_microvm.boot.put(356kernel_image_path=test_microvm.get_jailed_resource(357test_microvm.kernel_file358)359)360assert test_microvm.api_session.is_status_bad_request(response.status_code)361assert expected_err in response.text362363# Valid updates to the machine configuration are not allowed after boot.364response = test_microvm.machine_cfg.patch(365vcpu_count=4366)367assert test_microvm.api_session.is_status_bad_request(response.status_code)368assert expected_err in response.text369370response = test_microvm.machine_cfg.put(371vcpu_count=4,372ht_enabled=False,373mem_size_mib=128374)375assert test_microvm.api_session.is_status_bad_request(response.status_code)376assert expected_err in response.text377378# Network interface update is not allowed after boot.379response = test_microvm.network.put(380iface_id='1',381host_dev_name=tap1.name,382guest_mac='06:00:00:00:00:02'383)384assert test_microvm.api_session.is_status_bad_request(response.status_code)385assert expected_err in response.text386387# Block device update is not allowed after boot.388response = test_microvm.drive.put(389drive_id='rootfs',390path_on_host=test_microvm.jailer.jailed_path(test_microvm.rootfs_file),391is_read_only=False,392is_root_device=True393)394assert test_microvm.api_session.is_status_bad_request(response.status_code)395assert expected_err in response.text396397398def test_rate_limiters_api_config(test_microvm_with_api):399"""Test the Firecracker IO rate limiter API."""400test_microvm = test_microvm_with_api401test_microvm.spawn()402403# Test the DRIVE rate limiting API.404405# Test drive with bw rate-limiting.406fs1 = drive_tools.FilesystemFile(os.path.join(test_microvm.fsfiles, 'bw'))407response = test_microvm.drive.put(408drive_id='bw',409path_on_host=test_microvm.create_jailed_resource(fs1.path),410is_read_only=False,411is_root_device=False,412rate_limiter={413'bandwidth': {414'size': 1000000,415'refill_time': 100416}417}418)419assert test_microvm.api_session.is_status_no_content(response.status_code)420421# Test drive with ops rate-limiting.422fs2 = drive_tools.FilesystemFile(os.path.join(test_microvm.fsfiles, 'ops'))423response = test_microvm.drive.put(424drive_id='ops',425path_on_host=test_microvm.create_jailed_resource(fs2.path),426is_read_only=False,427is_root_device=False,428rate_limiter={429'ops': {430'size': 1,431'refill_time': 100432}433}434)435assert test_microvm.api_session.is_status_no_content(response.status_code)436437# Test drive with bw and ops rate-limiting.438fs3 = drive_tools.FilesystemFile(439os.path.join(test_microvm.fsfiles, 'bwops')440)441response = test_microvm.drive.put(442drive_id='bwops',443path_on_host=test_microvm.create_jailed_resource(fs3.path),444is_read_only=False,445is_root_device=False,446rate_limiter={447'bandwidth': {448'size': 1000000,449'refill_time': 100450},451'ops': {452'size': 1,453'refill_time': 100454}455}456)457assert test_microvm.api_session.is_status_no_content(response.status_code)458459# Test drive with 'empty' rate-limiting (same as not specifying the field)460fs4 = drive_tools.FilesystemFile(os.path.join(461test_microvm.fsfiles, 'nada'462))463response = test_microvm.drive.put(464drive_id='nada',465path_on_host=test_microvm.create_jailed_resource(fs4.path),466is_read_only=False,467is_root_device=False,468rate_limiter={}469)470assert test_microvm.api_session.is_status_no_content(response.status_code)471472# Test the NET rate limiting API.473474# Test network with tx bw rate-limiting.475iface_id = '1'476tapname = test_microvm.id[:8] + 'tap' + iface_id477tap1 = net_tools.Tap(tapname, test_microvm.jailer.netns)478479response = test_microvm.network.put(480iface_id=iface_id,481guest_mac='06:00:00:00:00:01',482host_dev_name=tap1.name,483tx_rate_limiter={484'bandwidth': {485'size': 1000000,486'refill_time': 100487}488}489)490assert test_microvm.api_session.is_status_no_content(response.status_code)491492# Test network with rx bw rate-limiting.493iface_id = '2'494tapname = test_microvm.id[:8] + 'tap' + iface_id495tap2 = net_tools.Tap(tapname, test_microvm.jailer.netns)496response = test_microvm.network.put(497iface_id=iface_id,498guest_mac='06:00:00:00:00:02',499host_dev_name=tap2.name,500rx_rate_limiter={501'bandwidth': {502'size': 1000000,503'refill_time': 100504}505}506)507assert test_microvm.api_session.is_status_no_content(response.status_code)508509# Test network with tx and rx bw and ops rate-limiting.510iface_id = '3'511tapname = test_microvm.id[:8] + 'tap' + iface_id512tap3 = net_tools.Tap(tapname, test_microvm.jailer.netns)513response = test_microvm.network.put(514iface_id=iface_id,515guest_mac='06:00:00:00:00:03',516host_dev_name=tap3.name,517rx_rate_limiter={518'bandwidth': {519'size': 1000000,520'refill_time': 100521},522'ops': {523'size': 1,524'refill_time': 100525}526},527tx_rate_limiter={528'bandwidth': {529'size': 1000000,530'refill_time': 100531},532'ops': {533'size': 1,534'refill_time': 100535}536}537)538assert test_microvm.api_session.is_status_no_content(response.status_code)539540541def test_api_patch_pre_boot(test_microvm_with_api):542"""Tests PATCH updates before the microvm boots."""543test_microvm = test_microvm_with_api544test_microvm.spawn()545546# Sets up the microVM with 2 vCPUs, 256 MiB of RAM, 1 network interface547# and a root file system with the rw permission.548test_microvm.basic_config()549550fs1 = drive_tools.FilesystemFile(551os.path.join(test_microvm.fsfiles, 'scratch')552)553drive_id = 'scratch'554response = test_microvm.drive.put(555drive_id=drive_id,556path_on_host=test_microvm.create_jailed_resource(fs1.path),557is_root_device=False,558is_read_only=False559)560assert test_microvm.api_session.is_status_no_content(response.status_code)561562# Configure metrics.563metrics_fifo_path = os.path.join(test_microvm.path, 'metrics_fifo')564metrics_fifo = log_tools.Fifo(metrics_fifo_path)565566response = test_microvm.metrics.put(567metrics_path=test_microvm.create_jailed_resource(metrics_fifo.path)568)569assert test_microvm.api_session.is_status_no_content(response.status_code)570571iface_id = '1'572tapname = test_microvm.id[:8] + 'tap' + iface_id573tap1 = net_tools.Tap(tapname, test_microvm.jailer.netns)574response = test_microvm.network.put(575iface_id=iface_id,576host_dev_name=tap1.name,577guest_mac='06:00:00:00:00:01'578)579assert test_microvm.api_session.is_status_no_content(response.status_code)580581# Partial updates to the boot source are not allowed.582response = test_microvm.boot.patch(583kernel_image_path='otherfile'584)585assert test_microvm.api_session.is_status_bad_request(response.status_code)586assert "Invalid request method" in response.text587588# Partial updates to the machine configuration are allowed before boot.589response = test_microvm.machine_cfg.patch(vcpu_count=4)590assert test_microvm.api_session.is_status_no_content(response.status_code)591response_json = test_microvm.machine_cfg.get().json()592assert response_json['vcpu_count'] == 4593594# Partial updates to the logger configuration are not allowed.595response = test_microvm.logger.patch(level='Error')596assert test_microvm.api_session.is_status_bad_request(response.status_code)597assert "Invalid request method" in response.text598599# Patching drive before boot is not allowed.600response = test_microvm.drive.patch(601drive_id=drive_id,602path_on_host='foo.bar'603)604assert test_microvm.api_session.is_status_bad_request(response.status_code)605assert "The requested operation is not supported before starting the " \606"microVM." in response.text607608# Patching net before boot is not allowed.609response = test_microvm.network.patch(610iface_id=iface_id611)612assert test_microvm.api_session.is_status_bad_request(response.status_code)613assert "The requested operation is not supported before starting the " \614"microVM." in response.text615616617def test_api_patch_post_boot(test_microvm_with_api):618"""Test PATCH updates after the microvm boots."""619test_microvm = test_microvm_with_api620test_microvm.spawn()621622# Sets up the microVM with 2 vCPUs, 256 MiB of RAM, 1 network iface and623# a root file system with the rw permission.624test_microvm.basic_config()625626fs1 = drive_tools.FilesystemFile(627os.path.join(test_microvm.fsfiles, 'scratch')628)629response = test_microvm.drive.put(630drive_id='scratch',631path_on_host=test_microvm.create_jailed_resource(fs1.path),632is_root_device=False,633is_read_only=False634)635assert test_microvm.api_session.is_status_no_content(response.status_code)636637# Configure metrics.638metrics_fifo_path = os.path.join(test_microvm.path, 'metrics_fifo')639metrics_fifo = log_tools.Fifo(metrics_fifo_path)640641response = test_microvm.metrics.put(642metrics_path=test_microvm.create_jailed_resource(metrics_fifo.path)643)644assert test_microvm.api_session.is_status_no_content(response.status_code)645646iface_id = '1'647tapname = test_microvm.id[:8] + 'tap' + iface_id648tap1 = net_tools.Tap(tapname, test_microvm.jailer.netns)649response = test_microvm.network.put(650iface_id=iface_id,651host_dev_name=tap1.name,652guest_mac='06:00:00:00:00:01'653)654assert test_microvm.api_session.is_status_no_content(response.status_code)655656test_microvm.start()657658# Partial updates to the boot source are not allowed.659response = test_microvm.boot.patch(660kernel_image_path='otherfile'661)662assert test_microvm.api_session.is_status_bad_request(response.status_code)663assert "Invalid request method" in response.text664665# Partial updates to the machine configuration are not allowed after boot.666expected_err = "The requested operation is not supported " \667"after starting the microVM"668response = test_microvm.machine_cfg.patch(vcpu_count=4)669assert test_microvm.api_session.is_status_bad_request(response.status_code)670assert expected_err in response.text671672# Partial updates to the logger configuration are not allowed.673response = test_microvm.logger.patch(level='Error')674assert test_microvm.api_session.is_status_bad_request(response.status_code)675assert "Invalid request method" in response.text676677678def test_drive_patch(test_microvm_with_api):679"""Test drive PATCH before and after boot."""680test_microvm = test_microvm_with_api681test_microvm.spawn()682683# Sets up the microVM with 2 vCPUs, 256 MiB of RAM and684# a root file system with the rw permission.685test_microvm.basic_config()686687# The drive to be patched.688fs = drive_tools.FilesystemFile(689os.path.join(test_microvm.fsfiles, 'scratch')690)691response = test_microvm.drive.put(692drive_id='scratch',693path_on_host=test_microvm.create_jailed_resource(fs.path),694is_root_device=False,695is_read_only=False696)697assert test_microvm.api_session.is_status_no_content(response.status_code)698699# Patching drive before boot is not allowed.700response = test_microvm.drive.patch(701drive_id='scratch',702path_on_host='foo.bar'703)704assert test_microvm.api_session.is_status_bad_request(response.status_code)705assert "The requested operation is not supported before starting the " \706"microVM." in response.text707708test_microvm.start()709710_drive_patch(test_microvm)711712713@pytest.mark.skipif(714platform.machine() != "x86_64",715reason="not yet implemented on aarch64"716)717def test_send_ctrl_alt_del(test_microvm_with_api):718"""Test shutting down the microVM gracefully, by sending CTRL+ALT+DEL.719720This relies on i8042 and AT Keyboard support being present in the guest721kernel.722"""723test_microvm = test_microvm_with_api724test_microvm.spawn()725726test_microvm.basic_config()727test_microvm.start()728729# Wait around for the guest to boot up and initialize the user space730time.sleep(2)731732response = test_microvm.actions.put(733action_type='SendCtrlAltDel'734)735assert test_microvm.api_session.is_status_no_content(response.status_code)736737firecracker_pid = test_microvm.jailer_clone_pid738739# If everyting goes as expected, the guest OS will issue a reboot,740# causing Firecracker to exit.741# We'll keep poking Firecracker for at most 30 seconds, waiting for it742# to die.743start_time = time.time()744shutdown_ok = False745while time.time() - start_time < 30:746try:747os.kill(firecracker_pid, 0)748time.sleep(0.01)749except OSError:750shutdown_ok = True751break752753assert shutdown_ok754755756def _drive_patch(test_microvm):757"""Exercise drive patch test scenarios."""758# Patches without mandatory fields are not allowed.759response = test_microvm.drive.patch(760drive_id='scratch'761)762assert test_microvm.api_session.is_status_bad_request(response.status_code)763assert "at least one property to patch: path_on_host, rate_limiter" \764in response.text765766# Cannot patch drive permissions post boot.767response = test_microvm.drive.patch(768drive_id='scratch',769path_on_host='foo.bar',770is_read_only=True771)772assert test_microvm.api_session.is_status_bad_request(response.status_code)773assert "unknown field `is_read_only`" in response.text774775# Updates to `is_root_device` with a valid value are not allowed.776response = test_microvm.drive.patch(777drive_id='scratch',778path_on_host='foo.bar',779is_root_device=False780)781assert test_microvm.api_session.is_status_bad_request(response.status_code)782assert "unknown field `is_root_device`" in response.text783784# Updates to `path_on_host` with an invalid path are not allowed.785response = test_microvm.drive.patch(786drive_id='scratch',787path_on_host='foo.bar'788)789assert test_microvm.api_session.is_status_bad_request(response.status_code)790assert "drive update (patch): device error: No such file or directory" \791in response.text792793fs = drive_tools.FilesystemFile(794os.path.join(test_microvm.fsfiles, 'scratch_new')795)796# Updates to `path_on_host` with a valid path are allowed.797response = test_microvm.drive.patch(798drive_id='scratch',799path_on_host=test_microvm.create_jailed_resource(fs.path)800)801assert test_microvm.api_session.is_status_no_content(response.status_code)802803# Updates to valid `path_on_host` and `rate_limiter` are allowed.804response = test_microvm.drive.patch(805drive_id='scratch',806path_on_host=test_microvm.create_jailed_resource(fs.path),807rate_limiter={808'bandwidth': {809'size': 1000000,810'refill_time': 100811},812'ops': {813'size': 1,814'refill_time': 100815}816}817)818assert test_microvm.api_session.is_status_no_content(response.status_code)819820# Updates to `rate_limiter` only are allowed.821response = test_microvm.drive.patch(822drive_id='scratch',823rate_limiter={824'bandwidth': {825'size': 5000,826'refill_time': 100827},828'ops': {829'size': 500,830'refill_time': 100831}832}833)834assert test_microvm.api_session.is_status_no_content(response.status_code)835836# Updates to `rate_limiter` and invalid path fail.837response = test_microvm.drive.patch(838drive_id='scratch',839path_on_host='foo.bar',840rate_limiter={841'bandwidth': {842'size': 5000,843'refill_time': 100844},845'ops': {846'size': 500,847'refill_time': 100848}849}850)851assert test_microvm.api_session.is_status_bad_request(response.status_code)852assert "No such file or directory" in response.text853854# Validate full vm configuration after patching drives.855response = test_microvm.full_cfg.get()856assert test_microvm.api_session.is_status_ok(response.status_code)857assert response.json()['drives'] == [{858'drive_id': 'rootfs',859'path_on_host': '/xenial.rootfs.ext4',860'is_root_device': True,861'partuuid': None,862'is_read_only': False,863'cache_type': 'Unsafe',864'rate_limiter': None865}, {866'drive_id': 'scratch',867'path_on_host': '/scratch_new.ext4',868'is_root_device': False,869'partuuid': None,870'is_read_only': False,871'cache_type': 'Unsafe',872'rate_limiter': {873'bandwidth': {874'size': 5000,875'one_time_burst': None,876'refill_time': 100877},878'ops': {879'size': 500,880'one_time_burst': None,881'refill_time': 100882}883}884}]885886887def test_api_vsock(test_microvm_with_api):888"""Test vsock related API commands."""889test_microvm = test_microvm_with_api890test_microvm.spawn()891test_microvm.basic_config()892893response = test_microvm.vsock.put(894vsock_id='vsock1',895guest_cid=15,896uds_path='vsock.sock'897)898assert test_microvm.api_session.is_status_no_content(response.status_code)899900# Updating an existing vsock is currently fine.901response = test_microvm.vsock.put(902vsock_id='vsock1',903guest_cid=166,904uds_path='vsock.sock'905)906assert test_microvm.api_session.is_status_no_content(response.status_code)907908# No other vsock action is allowed after booting the VM.909test_microvm.start()910911# Updating an existing vsock should not be fine at this point.912response = test_microvm.vsock.put(913vsock_id='vsock1',914guest_cid=17,915uds_path='vsock.sock'916)917assert test_microvm.api_session.is_status_bad_request(response.status_code)918919# Attaching a new vsock device should not be fine at this point.920response = test_microvm.vsock.put(921vsock_id='vsock3',922guest_cid=18,923uds_path='vsock.sock'924)925assert test_microvm.api_session.is_status_bad_request(response.status_code)926927response = test_microvm.vm.patch(state='Paused')928assert test_microvm.api_session.is_status_no_content(response.status_code)929930931def test_api_balloon(test_microvm_with_ssh_and_balloon):932"""Test balloon related API commands."""933test_microvm = test_microvm_with_ssh_and_balloon934test_microvm.spawn()935test_microvm.basic_config()936937# Updating an inexistent balloon device should give an error.938response = test_microvm.balloon.patch(amount_mib=0)939assert test_microvm.api_session.is_status_bad_request(response.status_code)940941# Adding a memory balloon should be OK.942response = test_microvm.balloon.put(943amount_mib=1,944deflate_on_oom=True945)946assert test_microvm.api_session.is_status_no_content(response.status_code)947948# As is overwriting one.949response = test_microvm.balloon.put(950amount_mib=0,951deflate_on_oom=False,952stats_polling_interval_s=5953)954assert test_microvm.api_session.is_status_no_content(response.status_code)955956# Getting the device configuration should be available pre-boot.957response = test_microvm.balloon.get()958assert test_microvm.api_session.is_status_ok(response.status_code)959assert response.json()['amount_mib'] == 0960assert response.json()['deflate_on_oom'] is False961assert response.json()['stats_polling_interval_s'] == 5962963# Updating an existing balloon device is forbidden before boot.964response = test_microvm.balloon.patch(amount_mib=2)965assert test_microvm.api_session.is_status_bad_request(response.status_code)966967# We can't have a balloon device with a target size greater than968# the available amount of memory.969response = test_microvm.balloon.put(970amount_mib=1024,971deflate_on_oom=False,972stats_polling_interval_s=5973)974assert test_microvm.api_session.is_status_bad_request(response.status_code)975976# Start the microvm.977test_microvm.start()978979# Updating should fail as driver didn't have time to initialize.980response = test_microvm.balloon.patch(amount_mib=4)981assert test_microvm.api_session.is_status_bad_request(response.status_code)982983# Overwriting the existing device should give an error now.984response = test_microvm.balloon.put(985amount_mib=3,986deflate_on_oom=False,987stats_polling_interval_s=3988)989assert test_microvm.api_session.is_status_bad_request(response.status_code)990991# Give the balloon driver time to initialize.992# 500 ms is the maximum acceptable boot time.993time.sleep(0.5)994995# But updating should be OK.996response = test_microvm.balloon.patch(amount_mib=4)997assert test_microvm.api_session.is_status_no_content(response.status_code)998999# Check we can't request more than the total amount of VM memory.1000response = test_microvm.balloon.patch(amount_mib=300)1001assert test_microvm.api_session.is_status_bad_request(response.status_code)10021003# Check we can't disable statistics as they were enabled at boot.1004# We can, however, change the interval to a non-zero value.1005response = test_microvm.balloon.patch_stats(stats_polling_interval_s=5)1006assert test_microvm.api_session.is_status_no_content(response.status_code)10071008# Getting the device configuration should be available post-boot.1009response = test_microvm.balloon.get()1010assert test_microvm.api_session.is_status_ok(response.status_code)1011assert response.json()['amount_mib'] == 41012assert response.json()['deflate_on_oom'] is False1013assert response.json()['stats_polling_interval_s'] == 510141015# Check we can't overflow the `num_pages` field in the config space by1016# requesting too many MB. There are 256 4K pages in a MB. Here, we are1017# requesting u32::MAX / 128.1018response = test_microvm.balloon.patch(amount_mib=33554432)1019assert test_microvm.api_session.is_status_bad_request(response.status_code)102010211022def test_get_full_config(test_microvm_with_ssh_and_balloon):1023"""Configure microVM with all resources and get configuration."""1024test_microvm = test_microvm_with_ssh_and_balloon10251026expected_cfg = {}10271028test_microvm.spawn()1029# Basic config also implies a root block device.1030test_microvm.basic_config()1031expected_cfg['machine-config'] = {1032'vcpu_count': 2,1033'mem_size_mib': 256,1034'ht_enabled': False,1035'track_dirty_pages': False1036}1037expected_cfg['boot-source'] = {1038'kernel_image_path': '/vmlinux.bin',1039'initrd_path': None1040}1041expected_cfg['drives'] = [{1042'drive_id': 'rootfs',1043'path_on_host': '/debian.rootfs.ext4',1044'is_root_device': True,1045'partuuid': None,1046'is_read_only': False,1047'cache_type': 'Unsafe',1048'rate_limiter': None1049}]10501051# Add a memory balloon device.1052response = test_microvm.balloon.put(amount_mib=1, deflate_on_oom=True)1053assert test_microvm.api_session.is_status_no_content(response.status_code)1054expected_cfg['balloon'] = {1055'amount_mib': 1,1056'deflate_on_oom': True,1057'stats_polling_interval_s': 01058}10591060# Add a vsock device.1061response = test_microvm.vsock.put(1062vsock_id='vsock',1063guest_cid=15,1064uds_path='vsock.sock'1065)1066assert test_microvm.api_session.is_status_no_content(response.status_code)1067expected_cfg['vsock'] = {1068'vsock_id': 'vsock',1069'guest_cid': 15,1070'uds_path': 'vsock.sock'1071}10721073# Add a net device.1074iface_id = '1'1075tapname = test_microvm.id[:8] + 'tap' + iface_id1076tap1 = net_tools.Tap(tapname, test_microvm.jailer.netns)1077guest_mac = '06:00:00:00:00:01'1078tx_rl = {1079'bandwidth': {1080'size': 1000000,1081'refill_time': 100,1082'one_time_burst': None1083},1084'ops': None1085}1086response = test_microvm.network.put(1087iface_id=iface_id,1088guest_mac=guest_mac,1089host_dev_name=tap1.name,1090tx_rate_limiter=tx_rl1091)1092assert test_microvm.api_session.is_status_no_content(response.status_code)1093expected_cfg['network-interfaces'] = [{1094'iface_id': iface_id,1095'host_dev_name': tap1.name,1096'guest_mac': '06:00:00:00:00:01',1097'rx_rate_limiter': None,1098'tx_rate_limiter': tx_rl,1099'allow_mmds_requests': False1100}]11011102expected_cfg['logger'] = None1103expected_cfg['metrics'] = None1104expected_cfg['mmds-config'] = None11051106# Getting full vm configuration should be available pre-boot.1107response = test_microvm.full_cfg.get()1108assert test_microvm.api_session.is_status_ok(response.status_code)1109assert response.json() == expected_cfg11101111# Start the microvm.1112test_microvm.start()11131114# Validate full vm configuration post-boot as well.1115response = test_microvm.full_cfg.get()1116assert test_microvm.api_session.is_status_ok(response.status_code)1117assert response.json() == expected_cfg111811191120def test_negative_api_lifecycle(bin_cloner_path):1121"""Test some vm lifecycle error scenarios."""1122builder = MicrovmBuilder(bin_cloner_path)1123vm_instance = builder.build_vm_nano()1124basevm = vm_instance.vm11251126# Try to pause microvm when not running, it must fail.1127response = basevm.vm.patch(state='Paused')1128assert "not supported before starting the microVM" \1129in response.text11301131# Try to resume microvm when not running, it must fail.1132response = basevm.vm.patch(state='Resumed')1133assert "not supported before starting the microVM" \1134in response.text113511361137