Path: blob/main/tests/integration_tests/functional/test_balloon.py
1958 views
# Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.1# SPDX-License-Identifier: Apache-2.02"""Tests for guest-side operations on /balloon resources."""34import logging5import os6import platform7import subprocess8import time910from retry import retry1112from conftest import _test_images_s3_bucket13from framework.artifacts import ArtifactCollection, ArtifactSet14from framework.builder import MicrovmBuilder, SnapshotBuilder, SnapshotType15from framework.matrix import TestMatrix, TestContext16from framework.utils import get_free_mem_ssh, run_cmd1718import host_tools.network as net_tools # pylint: disable=import-error192021MB_TO_PAGES = 256222324@retry(delay=0.5, tries=10)25def get_stable_rss_mem_by_pid(pid, percentage_delta=0.5):26"""27Get the RSS memory that a guest uses, given the pid of the guest.2829Wait till the fluctuations in RSS drop below percentage_delta. If timeout30is reached before the fluctuations drop, raise an exception.31"""3233def get_rss_from_pmap():34_, output, _ = run_cmd("pmap -X {}".format(pid))35return int(output.split('\n')[-2].split()[1], 10)3637first_rss = get_rss_from_pmap()38time.sleep(1)39second_rss = get_rss_from_pmap()4041delta = (abs(first_rss - second_rss)/float(first_rss)) * 10042assert delta < percentage_delta4344return second_rss454647def make_guest_dirty_memory(ssh_connection, should_oom=False, amount=8192):48"""Tell the guest, over ssh, to dirty `amount` pages of memory."""49amount_in_mbytes = amount / MB_TO_PAGES5051exit_code, _, _ = ssh_connection.execute_command(52"/sbin/fillmem {}".format(amount_in_mbytes)53)5455cmd = "cat /tmp/fillmem_output.txt"56_, stdout, _ = ssh_connection.execute_command(cmd)57if should_oom:58assert exit_code == 0 and (59"OOM Killer stopped the program with "60"signal 9, exit code 0"61) in stdout.read()62else:63assert exit_code == 0 and (64"Memory filling was "65"successful"66) in stdout.read()676869def build_test_matrix(network_config, bin_cloner_path, logger):70"""Build a test matrix using the kernel with the balloon driver."""71artifacts = ArtifactCollection(_test_images_s3_bucket())72# Testing matrix:73# - Guest kernel: Linux 4.1474# - Rootfs: Ubuntu 18.0475# - Microvm: 2vCPU with 256 MB RAM76# TODO: Multiple microvm sizes must be tested in the async pipeline.77microvm_artifacts = ArtifactSet(artifacts.microvms(keyword="2vcpu_256mb"))78kernel_artifacts = ArtifactSet(artifacts.kernels(79keyword="vmlinux-4.14"80))81disk_artifacts = ArtifactSet(artifacts.disks(keyword="ubuntu"))8283# Create a test context and add builder, logger, network.84test_context = TestContext()85test_context.custom = {86'builder': MicrovmBuilder(bin_cloner_path),87'network_config': network_config,88'logger': logger,89'snapshot_type': SnapshotType.FULL,90'seq_len': 591}9293# Create the test matrix.94return TestMatrix(95context=test_context,96artifact_sets=[97microvm_artifacts,98kernel_artifacts,99disk_artifacts100]101)102103104def copy_util_to_rootfs(rootfs_path, util):105"""Build and copy the 'memfill' program to the rootfs."""106subprocess.check_call(107"gcc ./host_tools/{util}.c -o {util}".format(util=util),108shell=True109)110subprocess.check_call("mkdir tmpfs", shell=True)111subprocess.check_call("mount {} tmpfs".format(rootfs_path), shell=True)112subprocess.check_call(113"cp {util} tmpfs/sbin/{util}".format(util=util),114shell=True115)116subprocess.check_call("rm {}".format(util), shell=True)117subprocess.check_call("umount tmpfs", shell=True)118subprocess.check_call("rmdir tmpfs", shell=True)119120121def _test_rss_memory_lower(test_microvm):122"""Check inflating the balloon makes guest use less rss memory."""123# Get the firecracker pid, and open an ssh connection.124firecracker_pid = test_microvm.jailer_clone_pid125ssh_connection = net_tools.SSHConnection(test_microvm.ssh_config)126127# Using deflate_on_oom, get the RSS as low as possible128response = test_microvm.balloon.patch(amount_mib=200)129assert test_microvm.api_session.is_status_no_content(130response.status_code131)132133# Get initial rss consumption.134init_rss = get_stable_rss_mem_by_pid(firecracker_pid)135136# Get the balloon back to 0.137response = test_microvm.balloon.patch(amount_mib=0)138assert test_microvm.api_session.is_status_no_content(139response.status_code140)141# This call will internally wait for rss to become stable.142_ = get_stable_rss_mem_by_pid(firecracker_pid)143144# Dirty memory, then inflate balloon and get ballooned rss consumption.145make_guest_dirty_memory(ssh_connection)146147response = test_microvm.balloon.patch(amount_mib=200)148assert test_microvm.api_session.is_status_no_content(149response.status_code150)151balloon_rss = get_stable_rss_mem_by_pid(firecracker_pid)152153# Check that the ballooning reclaimed the memory.154assert balloon_rss - init_rss <= 15000155156157# pylint: disable=C0103158def test_rss_memory_lower(test_microvm_with_ssh_and_balloon, network_config):159"""Check inflating the balloon makes guest use less rss memory."""160test_microvm = test_microvm_with_ssh_and_balloon161test_microvm.spawn()162test_microvm.basic_config()163_tap, _, _ = test_microvm.ssh_network_config(network_config, '1')164test_microvm.ssh_config['ssh_key_path'] = os.path.join(165test_microvm.fsfiles,166'debian.rootfs.id_rsa'167)168169# Add a memory balloon.170response = test_microvm.balloon.put(171amount_mib=0,172deflate_on_oom=True,173stats_polling_interval_s=0174)175assert test_microvm.api_session.is_status_no_content(response.status_code)176177# Start the microvm.178test_microvm.start()179180_test_rss_memory_lower(test_microvm)181182183# pylint: disable=C0103184def test_inflate_reduces_free(test_microvm_with_ssh_and_balloon,185network_config):186"""Check that the output of free in guest changes with inflate."""187test_microvm = test_microvm_with_ssh_and_balloon188test_microvm.spawn()189test_microvm.basic_config()190_tap, _, _ = test_microvm.ssh_network_config(network_config, '1')191test_microvm.ssh_config['ssh_key_path'] = os.path.join(192test_microvm.fsfiles,193'debian.rootfs.id_rsa'194)195196# Install deflated balloon.197response = test_microvm.balloon.put(198amount_mib=0,199deflate_on_oom=False,200stats_polling_interval_s=1201)202assert test_microvm.api_session.is_status_no_content(response.status_code)203204# Start the microvm205test_microvm.start()206207# Get and open an ssh connection.208firecracker_pid = test_microvm.jailer_clone_pid209ssh_connection = net_tools.SSHConnection(test_microvm.ssh_config)210211# Get the free memory before ballooning.212available_mem_deflated = get_free_mem_ssh(ssh_connection)213214# Inflate 64 MB == 16384 page balloon.215response = test_microvm.balloon.patch(amount_mib=64)216assert test_microvm.api_session.is_status_no_content(response.status_code)217# This call will internally wait for rss to become stable.218_ = get_stable_rss_mem_by_pid(firecracker_pid)219220# Get the free memory after ballooning.221available_mem_inflated = get_free_mem_ssh(ssh_connection)222223# Assert that ballooning reclaimed about 64 MB of memory.224assert available_mem_inflated <= available_mem_deflated - 85 * 64000 / 100225226227# pylint: disable=C0103228def test_deflate_on_oom_true(test_microvm_with_ssh_and_balloon,229network_config):230"""Verify that setting the `deflate_on_oom` to True works correctly."""231test_microvm = test_microvm_with_ssh_and_balloon232test_microvm.spawn()233test_microvm.basic_config()234_tap, _, _ = test_microvm.ssh_network_config(network_config, '1')235test_microvm.ssh_config['ssh_key_path'] = os.path.join(236test_microvm.fsfiles,237'debian.rootfs.id_rsa'238)239240# Add a deflated memory balloon.241response = test_microvm.balloon.put(242amount_mib=0,243deflate_on_oom=True,244stats_polling_interval_s=0245)246assert test_microvm.api_session.is_status_no_content(response.status_code)247248# Start the microvm.249test_microvm.start()250251# Get an ssh connection to the microvm.252firecracker_pid = test_microvm.jailer_clone_pid253ssh_connection = net_tools.SSHConnection(test_microvm.ssh_config)254255# Inflate the balloon256response = test_microvm.balloon.patch(amount_mib=180)257assert test_microvm.api_session.is_status_no_content(response.status_code)258# This call will internally wait for rss to become stable.259_ = get_stable_rss_mem_by_pid(firecracker_pid)260261# Check that using memory doesn't lead to an out of memory error.262# Note that due to `test_deflate_on_oom_false`, we know that263# if `deflate_on_oom` were set to False, then such an error264# would have happened.265make_guest_dirty_memory(ssh_connection)266267268# pylint: disable=C0103269def test_deflate_on_oom_false(test_microvm_with_ssh_and_balloon,270network_config):271"""Verify that setting the `deflate_on_oom` to False works correctly."""272test_microvm = test_microvm_with_ssh_and_balloon273test_microvm.spawn()274test_microvm.basic_config()275_tap, _, _ = test_microvm.ssh_network_config(network_config, '1')276test_microvm.ssh_config['ssh_key_path'] = os.path.join(277test_microvm.fsfiles,278'debian.rootfs.id_rsa'279)280281# Add a memory balloon.282response = test_microvm.balloon.put(283amount_mib=0,284deflate_on_oom=False,285stats_polling_interval_s=0286)287assert test_microvm.api_session.is_status_no_content(response.status_code)288289# Start the microvm.290test_microvm.start()291292# Get an ssh connection to the microvm.293firecracker_pid = test_microvm.jailer_clone_pid294ssh_connection = net_tools.SSHConnection(test_microvm.ssh_config)295296# Inflate the balloon.297response = test_microvm.balloon.patch(amount_mib=180)298assert test_microvm.api_session.is_status_no_content(response.status_code)299# This call will internally wait for rss to become stable.300_ = get_stable_rss_mem_by_pid(firecracker_pid)301302# Check that using memory does lead to an out of memory error.303make_guest_dirty_memory(ssh_connection, should_oom=True)304305306# pylint: disable=C0103307def test_reinflate_balloon(test_microvm_with_ssh_and_balloon, network_config):308"""Verify that repeatedly inflating and deflating the balloon works."""309test_microvm = test_microvm_with_ssh_and_balloon310test_microvm.spawn()311test_microvm.basic_config()312_tap, _, _ = test_microvm.ssh_network_config(network_config, '1')313test_microvm.ssh_config['ssh_key_path'] = os.path.join(314test_microvm.fsfiles,315'debian.rootfs.id_rsa'316)317318# Add a deflated memory balloon.319response = test_microvm.balloon.put(320amount_mib=0,321deflate_on_oom=True,322stats_polling_interval_s=0323)324assert test_microvm.api_session.is_status_no_content(response.status_code)325326# Start the microvm.327test_microvm.start()328329# Get the firecracker pid, and open an ssh connection, get the RSS.330firecracker_pid = test_microvm.jailer_clone_pid331ssh_connection = net_tools.SSHConnection(test_microvm.ssh_config)332333# First inflate the balloon to free up the uncertain amount of memory334# used by the kernel at boot and establish a baseline, then give back335# the memory.336response = test_microvm.balloon.patch(amount_mib=200)337assert test_microvm.api_session.is_status_no_content(response.status_code)338# This call will internally wait for rss to become stable.339_ = get_stable_rss_mem_by_pid(firecracker_pid)340341response = test_microvm.balloon.patch(amount_mib=0)342assert test_microvm.api_session.is_status_no_content(response.status_code)343# This call will internally wait for rss to become stable.344_ = get_stable_rss_mem_by_pid(firecracker_pid)345346# Get the guest to dirty memory.347make_guest_dirty_memory(ssh_connection)348first_reading = get_stable_rss_mem_by_pid(firecracker_pid)349350# Now inflate the balloon.351response = test_microvm.balloon.patch(amount_mib=200)352assert test_microvm.api_session.is_status_no_content(response.status_code)353second_reading = get_stable_rss_mem_by_pid(firecracker_pid)354355# Now deflate the balloon.356response = test_microvm.balloon.patch(amount_mib=0)357assert test_microvm.api_session.is_status_no_content(response.status_code)358# This call will internally wait for rss to become stable.359_ = get_stable_rss_mem_by_pid(firecracker_pid)360361# Now have the guest dirty memory again.362make_guest_dirty_memory(ssh_connection)363third_reading = get_stable_rss_mem_by_pid(firecracker_pid)364365# Now inflate the balloon again.366response = test_microvm.balloon.patch(amount_mib=200)367assert test_microvm.api_session.is_status_no_content(response.status_code)368fourth_reading = get_stable_rss_mem_by_pid(firecracker_pid)369370# Check that the memory used is the same after regardless of the previous371# inflate history of the balloon (with the third reading being allowed372# to be smaller than the first, since memory allocated at booting up373# is probably freed after the first inflation.374assert (third_reading - first_reading) <= 20000375assert abs(second_reading - fourth_reading) <= 20000376377378# pylint: disable=C0103379def test_size_reduction(test_microvm_with_ssh_and_balloon, network_config):380"""Verify that ballooning reduces RSS usage on a newly booted guest."""381test_microvm = test_microvm_with_ssh_and_balloon382test_microvm.spawn()383test_microvm.basic_config()384_tap, _, _ = test_microvm.ssh_network_config(network_config, '1')385test_microvm.ssh_config['ssh_key_path'] = os.path.join(386test_microvm.fsfiles,387'debian.rootfs.id_rsa'388)389390# Add a memory balloon.391response = test_microvm.balloon.put(392amount_mib=0,393deflate_on_oom=True,394stats_polling_interval_s=0395)396assert test_microvm.api_session.is_status_no_content(response.status_code)397398# Start the microvm.399test_microvm.start()400401# Get the firecracker pid, and open an ssh connection.402firecracker_pid = test_microvm.jailer_clone_pid403ssh_connection = net_tools.SSHConnection(test_microvm.ssh_config)404405# Check memory usage.406first_reading = get_stable_rss_mem_by_pid(firecracker_pid)407408# Have the guest drop its caches.409ssh_connection.execute_command('sync; echo 3 > /proc/sys/vm/drop_caches')410time.sleep(5)411412# Now inflate the balloon.413response = test_microvm.balloon.patch(amount_mib=40)414assert test_microvm.api_session.is_status_no_content(response.status_code)415416# Check memory usage again.417second_reading = get_stable_rss_mem_by_pid(firecracker_pid)418419# There should be a reduction of at least 10MB.420assert first_reading - second_reading >= 10000421422423# pylint: disable=C0103424def test_stats(test_microvm_with_ssh_and_balloon, network_config):425"""Verify that balloon stats work as expected."""426test_microvm = test_microvm_with_ssh_and_balloon427test_microvm.spawn()428test_microvm.basic_config()429_tap, _, _ = test_microvm.ssh_network_config(network_config, '1')430test_microvm.ssh_config['ssh_key_path'] = os.path.join(431test_microvm.fsfiles,432'debian.rootfs.id_rsa'433)434435# Add a memory balloon with stats enabled.436response = test_microvm.balloon.put(437amount_mib=0,438deflate_on_oom=True,439stats_polling_interval_s=1440)441assert test_microvm.api_session.is_status_no_content(response.status_code)442443# Start the microvm.444test_microvm.start()445446# Open an ssh connection to the microvm.447firecracker_pid = test_microvm.jailer_clone_pid448ssh_connection = net_tools.SSHConnection(test_microvm.ssh_config)449450# Get an initial reading of the stats.451initial_stats = test_microvm.balloon.get_stats().json()452453# Dirty 10MB of pages.454make_guest_dirty_memory(ssh_connection, amount=(10 * MB_TO_PAGES))455time.sleep(1)456# This call will internally wait for rss to become stable.457_ = get_stable_rss_mem_by_pid(firecracker_pid)458459# Make sure that the stats catch the page faults.460after_workload_stats = test_microvm.balloon.get_stats().json()461assert initial_stats['minor_faults'] < after_workload_stats['minor_faults']462assert initial_stats['major_faults'] < after_workload_stats['major_faults']463464# Now inflate the balloon with 10MB of pages.465response = test_microvm.balloon.patch(amount_mib=10)466assert test_microvm.api_session.is_status_no_content(response.status_code)467# This call will internally wait for rss to become stable.468_ = get_stable_rss_mem_by_pid(firecracker_pid)469470# Get another reading of the stats after the polling interval has passed.471inflated_stats = test_microvm.balloon.get_stats().json()472473# Ensure the stats reflect inflating the balloon.474assert (475after_workload_stats['free_memory'] >476inflated_stats['free_memory']477)478assert (479after_workload_stats['available_memory'] >480inflated_stats['available_memory']481)482483# Deflate the balloon.check that the stats show the increase in484# available memory.485response = test_microvm.balloon.patch(amount_mib=0)486assert test_microvm.api_session.is_status_no_content(response.status_code)487# This call will internally wait for rss to become stable.488_ = get_stable_rss_mem_by_pid(firecracker_pid)489490# Get another reading of the stats after the polling interval has passed.491deflated_stats = test_microvm.balloon.get_stats().json()492493# Ensure the stats reflect deflating the balloon.494assert (495inflated_stats['free_memory'] <496deflated_stats['free_memory']497)498assert (499inflated_stats['available_memory'] <500deflated_stats['available_memory']501)502503504def test_stats_update(test_microvm_with_ssh_and_balloon, network_config):505"""Verify that balloon stats update correctly."""506test_microvm = test_microvm_with_ssh_and_balloon507test_microvm.spawn()508test_microvm.basic_config()509_tap, _, _ = test_microvm.ssh_network_config(network_config, '1')510test_microvm.ssh_config['ssh_key_path'] = os.path.join(511test_microvm.fsfiles,512'debian.rootfs.id_rsa'513)514515# Add a memory balloon with stats enabled.516response = test_microvm.balloon.put(517amount_mib=0,518deflate_on_oom=True,519stats_polling_interval_s=1520)521assert test_microvm.api_session.is_status_no_content(response.status_code)522523# Start the microvm.524test_microvm.start()525526# Open an ssh connection to the microvm.527firecracker_pid = test_microvm.jailer_clone_pid528ssh_connection = net_tools.SSHConnection(test_microvm.ssh_config)529530# Dirty 30MB of pages.531make_guest_dirty_memory(ssh_connection, amount=(30 * MB_TO_PAGES))532533# This call will internally wait for rss to become stable.534_ = get_stable_rss_mem_by_pid(firecracker_pid)535536# Get an initial reading of the stats.537initial_stats = test_microvm.balloon.get_stats().json()538539# Inflate the balloon to trigger a change in the stats.540response = test_microvm.balloon.patch(amount_mib=10)541assert test_microvm.api_session.is_status_no_content(response.status_code)542543# Wait out the polling interval, then get the updated stats.544time.sleep(1)545next_stats = test_microvm.balloon.get_stats().json()546assert initial_stats['available_memory'] != next_stats['available_memory']547548# Inflate the balloon more to trigger a change in the stats.549response = test_microvm.balloon.patch(amount_mib=30)550assert test_microvm.api_session.is_status_no_content(response.status_code)551552# Change the polling interval.553response = test_microvm.balloon.patch_stats(stats_polling_interval_s=60)554assert test_microvm.api_session.is_status_no_content(response.status_code)555556# The polling interval change should update the stats.557final_stats = test_microvm.balloon.get_stats().json()558assert next_stats['available_memory'] != final_stats['available_memory']559560561def test_balloon_snapshot(562network_config,563bin_cloner_path564):565"""Test that the balloon works after pause/resume."""566logger = logging.getLogger("snapshot_sequence")567568# Create the test matrix.569test_matrix = build_test_matrix(network_config, bin_cloner_path, logger)570571test_matrix.run_test(_test_balloon_snapshot)572573574def _test_balloon_snapshot(context):575logger = context.custom['logger']576vm_builder = context.custom['builder']577snapshot_type = context.custom['snapshot_type']578diff_snapshots = snapshot_type == SnapshotType.DIFF579580logger.info("Testing {} with microvm: \"{}\", kernel {}, disk {} "581.format(snapshot_type,582context.microvm.name(),583context.kernel.name(),584context.disk.name()))585586# Create a rw copy artifact.587root_disk = context.disk.copy()588589# Get ssh key from read-only artifact.590ssh_key = context.disk.ssh_key()591# Create a fresh microvm from aftifacts.592vm_instance = vm_builder.build(kernel=context.kernel,593disks=[root_disk],594ssh_key=ssh_key,595config=context.microvm,596diff_snapshots=diff_snapshots)597basevm = vm_instance.vm598copy_util_to_rootfs(root_disk.local_path(), 'fillmem')599600# Add a memory balloon with stats enabled.601response = basevm.balloon.put(602amount_mib=0,603deflate_on_oom=True,604stats_polling_interval_s=1605)606assert basevm.api_session.is_status_no_content(response.status_code)607608basevm.start()609ssh_connection = net_tools.SSHConnection(basevm.ssh_config)610611# Dirty 60MB of pages.612make_guest_dirty_memory(ssh_connection, amount=(60 * MB_TO_PAGES))613time.sleep(1)614615# Get the firecracker pid, and open an ssh connection.616firecracker_pid = basevm.jailer_clone_pid617618# Check memory usage.619first_reading = get_stable_rss_mem_by_pid(firecracker_pid)620621# Now inflate the balloon with 20MB of pages.622response = basevm.balloon.patch(amount_mib=20)623assert basevm.api_session.is_status_no_content(response.status_code)624625# Check memory usage again.626second_reading = get_stable_rss_mem_by_pid(firecracker_pid)627628# There should be a reduction in RSS, but it's inconsistent.629# We only test that the reduction happens.630assert first_reading > second_reading631632logger.info("Create {} #0.".format(snapshot_type))633# Create a snapshot builder from a microvm.634snapshot_builder = SnapshotBuilder(basevm)635636# Create base snapshot.637snapshot = snapshot_builder.create([root_disk.local_path()],638ssh_key,639snapshot_type)640641basevm.kill()642643logger.info("Load snapshot #{}, mem {}".format(1, snapshot.mem))644microvm, _ = vm_builder.build_from_snapshot(snapshot,645True,646diff_snapshots)647# Attempt to connect to resumed microvm.648ssh_connection = net_tools.SSHConnection(microvm.ssh_config)649650# Get the firecracker from snapshot pid, and open an ssh connection.651firecracker_pid = microvm.jailer_clone_pid652653# Get the stats right after we take a snapshot.654stats_after_snap = microvm.balloon.get_stats().json()655656# Check memory usage.657third_reading = get_stable_rss_mem_by_pid(firecracker_pid)658659# Dirty 60MB of pages.660make_guest_dirty_memory(ssh_connection, amount=(60 * MB_TO_PAGES))661662# Check memory usage.663fourth_reading = get_stable_rss_mem_by_pid(firecracker_pid)664665assert fourth_reading > third_reading666667# Inflate the balloon with another 20MB of pages.668response = microvm.balloon.patch(amount_mib=40)669assert microvm.api_session.is_status_no_content(response.status_code)670671fifth_reading = get_stable_rss_mem_by_pid(firecracker_pid)672673# There should be a reduction in RSS, but it's inconsistent.674# We only test that the reduction happens.675assert fourth_reading > fifth_reading676677# Get the stats after we take a snapshot and dirty some memory,678# then reclaim it.679latest_stats = microvm.balloon.get_stats().json()680681# Ensure the stats are still working after restore and show682# that the balloon inflated.683assert (684stats_after_snap['available_memory'] >685latest_stats['available_memory']686)687688microvm.kill()689690691def test_snapshot_compatibility(692network_config,693bin_cloner_path694):695"""Test that the balloon serializes correctly."""696logger = logging.getLogger("snapshot_sequence")697698# Create the test matrix.699test_matrix = build_test_matrix(network_config, bin_cloner_path, logger)700701test_matrix.run_test(_test_snapshot_compatibility)702703704def _test_snapshot_compatibility(context):705logger = context.custom['logger']706vm_builder = context.custom['builder']707snapshot_type = context.custom['snapshot_type']708diff_snapshots = snapshot_type == SnapshotType.DIFF709710logger.info("Testing {} with microvm: \"{}\", kernel {}, disk {} "711.format(snapshot_type,712context.microvm.name(),713context.kernel.name(),714context.disk.name()))715716# Create a rw copy artifact.717root_disk = context.disk.copy()718# Get ssh key from read-only artifact.719ssh_key = context.disk.ssh_key()720# Create a fresh microvm from aftifacts.721vm_instance = vm_builder.build(722kernel=context.kernel,723disks=[root_disk],724ssh_key=ssh_key,725config=context.microvm,726diff_snapshots=diff_snapshots727)728microvm = vm_instance.vm729# Add a memory balloon with stats enabled.730response = microvm.balloon.put(731amount_mib=0,732deflate_on_oom=True,733stats_polling_interval_s=1734)735assert microvm.api_session.is_status_no_content(response.status_code)736737microvm.start()738739logger.info("Create {} #0.".format(snapshot_type))740741# Pause the microVM in order to allow snapshots742response = microvm.vm.patch(state='Paused')743assert microvm.api_session.is_status_no_content(response.status_code)744745# Try to create a snapshot with a balloon on version 0.23.0.746# This is skipped for aarch64, since the snapshotting feature747# was introduced in v0.24.0.748if platform.machine() == "x86_64":749response = microvm.snapshot.create(750mem_file_path='memfile',751snapshot_path='dummy',752diff=False,753version='0.23.0'754)755756# This should fail as the balloon was introduced in 0.24.0.757assert microvm.api_session.is_status_bad_request(response.status_code)758assert (759'Target version does not implement the '760'virtio-balloon device'761) in response.json()['fault_message']762763# Create a snapshot builder from a microvm.764snapshot_builder = SnapshotBuilder(microvm)765766# Check we can create a snapshot with a balloon on current version.767snapshot_builder.create(768[root_disk.local_path()],769ssh_key,770snapshot_type771)772773microvm.kill()774775776def test_memory_scrub(777network_config,778bin_cloner_path779):780"""Test that the memory is zeroed after deflate."""781logger = logging.getLogger()782783# Create the test matrix.784test_matrix = build_test_matrix(network_config, bin_cloner_path, logger)785786test_matrix.run_test(_test_memory_scrub)787788789def _test_memory_scrub(context):790vm_builder = context.custom['builder']791792# Create a rw copy artifact.793root_disk = context.disk.copy()794# Get ssh key from read-only artifact.795ssh_key = context.disk.ssh_key()796# Create a fresh microvm from aftifacts.797vm_instance = vm_builder.build(798kernel=context.kernel,799disks=[root_disk],800ssh_key=ssh_key,801config=context.microvm802)803microvm = vm_instance.vm804805copy_util_to_rootfs(root_disk.local_path(), 'fillmem')806copy_util_to_rootfs(root_disk.local_path(), 'readmem')807808# Add a memory balloon with stats enabled.809response = microvm.balloon.put(810amount_mib=0,811deflate_on_oom=True,812stats_polling_interval_s=1813)814assert microvm.api_session.is_status_no_content(response.status_code)815816microvm.start()817818ssh_connection = net_tools.SSHConnection(microvm.ssh_config)819820# Dirty 60MB of pages.821make_guest_dirty_memory(ssh_connection, amount=(60 * MB_TO_PAGES))822823# Now inflate the balloon with 60MB of pages.824response = microvm.balloon.patch(amount_mib=60)825assert microvm.api_session.is_status_no_content(response.status_code)826827# Get the firecracker pid, and open an ssh connection.828firecracker_pid = microvm.jailer_clone_pid829830# Wait for the inflate to complete.831_ = get_stable_rss_mem_by_pid(firecracker_pid)832833# Deflate the balloon completely.834response = microvm.balloon.patch(amount_mib=0)835assert microvm.api_session.is_status_no_content(response.status_code)836837# Wait for the deflate to complete.838_ = get_stable_rss_mem_by_pid(firecracker_pid)839840exit_code, _, _ = ssh_connection.execute_command(841"/sbin/readmem {} {}".format(60, 1)842)843assert exit_code == 0844845microvm.kill()846847848