Path: blob/main/tests/integration_tests/functional/test_vsock.py
1958 views
# Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.1# SPDX-License-Identifier: Apache-2.02"""Tests for the virtio-vsock device.34In order to test the vsock device connection state machine, these tests will:5- Generate a 20MiB random data blob;6- Use `host_tools/vsock_helper.c` to start a listening echo server inside the7guest VM;8- Run 50, concurrent, host-initiated connections, each transfering the random9blob to and from the guest echo server;10- For every connection, check that the data received back from the echo server11hashes to the same value as the data sent;12- Start a host echo server, and repeat the process for the same number of13guest-initiated connections.14"""1516import os.path1718from socket import timeout as SocketTimeout19from framework.utils_vsock import make_blob, \20check_host_connections, check_guest_connections, \21HostEchoWorker22from framework.builder import MicrovmBuilder, SnapshotBuilder, SnapshotType2324from host_tools.network import SSHConnection25import host_tools.logging as log_tools2627VSOCK_UDS_PATH = "v.sock"28ECHO_SERVER_PORT = 525229BLOB_SIZE = 20 * 1024 * 102430NEGATIVE_TEST_CONNECTION_COUNT = 10031TEST_WORKER_COUNT = 10323334def test_vsock(35test_microvm_with_ssh,36network_config,37bin_vsock_path,38test_fc_session_root_path39):40"""Vsock tests. See the module docstring for a high-level description."""41vm = test_microvm_with_ssh42vm.spawn()4344vm.basic_config()45_tap, _, _ = vm.ssh_network_config(network_config, '1')46vm.vsock.put(47vsock_id="vsock0",48guest_cid=3,49uds_path="/{}".format(VSOCK_UDS_PATH)50)5152vm.start()5354# Generate the random data blob file.55blob_path, blob_hash = make_blob(test_fc_session_root_path)56vm_blob_path = "/tmp/vsock/test.blob"5758# Set up a tmpfs drive on the guest, so we can copy the blob there.59# Guest-initiated connections (echo workers) will use this blob.60conn = SSHConnection(vm.ssh_config)61cmd = "mkdir -p /tmp/vsock"62cmd += " && mount -t tmpfs tmpfs -o size={} /tmp/vsock".format(63BLOB_SIZE + 1024*102464)65ecode, _, _ = conn.execute_command(cmd)66assert ecode == 06768# Copy `vsock_helper` and the random blob to the guest.69vsock_helper = bin_vsock_path70conn.scp_file(vsock_helper, '/bin/vsock_helper')71conn.scp_file(blob_path, vm_blob_path)7273# Test guest-initiated connections.74path = os.path.join(75vm.path,76_make_host_port_path(VSOCK_UDS_PATH, ECHO_SERVER_PORT)77)78check_guest_connections(vm, path, vm_blob_path, blob_hash)7980# Test host-initiated connections.81path = os.path.join(vm.jailer.chroot_path(), VSOCK_UDS_PATH)82check_host_connections(vm, path, blob_path, blob_hash)838485def _make_host_port_path(uds_path, port):86"""Build the path for a Unix socket, mapped to host vsock port `port`."""87return "{}_{}".format(uds_path, port)888990def negative_test_host_connections(vm, uds_path, blob_path, blob_hash):91"""Negative test for host-initiated connections.9293This will start a daemonized echo server on the guest VM, and then spawn94`NEGATIVE_TEST_CONNECTION_COUNT` `HostEchoWorker` threads.95Closes the UDS sockets while data is in flight.96"""97conn = SSHConnection(vm.ssh_config)98cmd = "vsock_helper echosrv -d {}". format(ECHO_SERVER_PORT)99ecode, _, _ = conn.execute_command(cmd)100assert ecode == 0101102workers = []103for _ in range(NEGATIVE_TEST_CONNECTION_COUNT):104worker = HostEchoWorker(uds_path, blob_path)105workers.append(worker)106worker.start()107108for wrk in workers:109wrk.close_uds()110wrk.join()111112# Validate that Firecracker is still up and running.113ecode, _, _ = conn.execute_command("sync")114# Should fail if Firecracker exited from SIGPIPE handler.115assert ecode == 0116117# Validate vsock emulation still accepts connections and works118# as expected.119check_host_connections(vm, uds_path, blob_path, blob_hash)120121122def test_vsock_epipe(123test_microvm_with_ssh,124network_config,125bin_vsock_path,126test_fc_session_root_path127):128"""Vsock negative test to validate SIGPIPE/EPIPE handling."""129vm = test_microvm_with_ssh130vm.spawn()131132vm.basic_config()133_tap, _, _ = vm.ssh_network_config(network_config, '1')134vm.vsock.put(135vsock_id="vsock0",136guest_cid=3,137uds_path="/{}".format(VSOCK_UDS_PATH)138)139140# Configure metrics to assert against `sigpipe` count.141metrics_fifo_path = os.path.join(vm.path, 'metrics_fifo')142metrics_fifo = log_tools.Fifo(metrics_fifo_path)143response = vm.metrics.put(144metrics_path=vm.create_jailed_resource(metrics_fifo.path)145)146assert vm.api_session.is_status_no_content(response.status_code)147148vm.start()149150# Generate the random data blob file.151blob_path, blob_hash = make_blob(test_fc_session_root_path)152vm_blob_path = "/tmp/vsock/test.blob"153154# Set up a tmpfs drive on the guest, so we can copy the blob there.155# Guest-initiated connections (echo workers) will use this blob.156conn = SSHConnection(vm.ssh_config)157cmd = "mkdir -p /tmp/vsock"158cmd += " && mount -t tmpfs tmpfs -o size={} /tmp/vsock".format(159BLOB_SIZE + 1024*1024160)161ecode, _, _ = conn.execute_command(cmd)162assert ecode == 0163164# Copy `vsock_helper` and the random blob to the guest.165vsock_helper = bin_vsock_path166conn.scp_file(vsock_helper, '/bin/vsock_helper')167conn.scp_file(blob_path, vm_blob_path)168169path = os.path.join(vm.jailer.chroot_path(), VSOCK_UDS_PATH)170# Negative test for host-initiated connections that171# are closed with in flight data.172negative_test_host_connections(vm, path, blob_path, blob_hash)173174metrics = vm.flush_metrics(metrics_fifo)175# Validate that at least 1 `SIGPIPE` signal was received.176# Since we are reusing the existing echo server which triggers177# reads/writes on the UDS backend connections, these might be closed178# before a read() or a write() is about to be performed by the emulation.179# The test uses 100 connections it is enough to close at least one180# before write().181#182# If this ever fails due to 100 closes before read() we must183# add extra tooling that will trigger only writes().184assert metrics['signals']['sigpipe'] > 0185186187def test_vsock_transport_reset(188bin_cloner_path,189bin_vsock_path,190test_fc_session_root_path191):192"""193Vsock transport reset test.194195Steps:1961. Start echo server on the guest1972. Start host workers that ping-pong data between guest and host,198without closing any of them1993. Pause VM -> Create snapshot -> Resume VM2004. Check that worker sockets no longer work by setting a timeout201so the sockets won't block and do a recv operation.2025. If the recv operation timeouts, the connection was closed.203Else, the connection was not closed and the test fails.2046. Close VM -> Load VM from Snapshot -> check that vsock205device is still working.206"""207vm_builder = MicrovmBuilder(bin_cloner_path)208vm_instance = vm_builder.build_vm_nano()209test_vm = vm_instance.vm210root_disk = vm_instance.disks[0]211ssh_key = vm_instance.ssh_key212213test_vm.vsock.put(214vsock_id="vsock0",215guest_cid=3,216uds_path="/{}".format(VSOCK_UDS_PATH)217)218219test_vm.start()220221snapshot_builder = SnapshotBuilder(test_vm)222disks = [root_disk.local_path()]223224# Generate the random data blob file.225blob_path, blob_hash = make_blob(test_fc_session_root_path)226vm_blob_path = "/tmp/vsock/test.blob"227228# Set up a tmpfs drive on the guest, so we can copy the blob there.229# Guest-initiated connections (echo workers) will use this blob.230conn = SSHConnection(test_vm.ssh_config)231cmd = "mkdir -p /tmp/vsock"232cmd += " && mount -t tmpfs tmpfs -o size={} /tmp/vsock".format(233BLOB_SIZE + 1024*1024234)235ecode, _, _ = conn.execute_command(cmd)236assert ecode == 0237238# Copy `vsock_helper` and the random blob to the guest.239vsock_helper = bin_vsock_path240conn.scp_file(vsock_helper, '/bin/vsock_helper')241conn.scp_file(blob_path, vm_blob_path)242243# Start guest echo server.244path = os.path.join(test_vm.jailer.chroot_path(), VSOCK_UDS_PATH)245conn = SSHConnection(test_vm.ssh_config)246cmd = "vsock_helper echosrv -d {}". format(ECHO_SERVER_PORT)247ecode, _, _ = conn.execute_command(cmd)248assert ecode == 0249250# Start host workers that connect to the guest server.251workers = []252for _ in range(TEST_WORKER_COUNT):253worker = HostEchoWorker(path, blob_path)254workers.append(worker)255worker.start()256257for wrk in workers:258wrk.join()259260# Create snapshot.261snapshot = snapshot_builder.create(disks,262ssh_key,263SnapshotType.FULL)264response = test_vm.vm.patch(state='Resumed')265assert test_vm.api_session.is_status_no_content(response.status_code)266267# Check that sockets are no longer working on workers.268for worker in workers:269# Whatever we send to the server, it should return the same270# value.271buf = bytearray("TEST\n".encode('utf-8'))272worker.sock.send(buf)273try:274# Arbitrary timeout, we set this so the socket won't block as275# it shouldn't receive anything.276worker.sock.settimeout(0.25)277response = worker.sock.recv(32)278# If we reach here, it means the connection did not close.279assert False, "Connection not closed: {}" \280.format(response.decode('utf-8'))281except SocketTimeout as exc:282assert True, exc283284# Terminate VM.285test_vm.kill()286287# Load snapshot.288test_vm, _ = vm_builder.build_from_snapshot(snapshot,289True,290False)291292# Check that vsock device still works.293# Test guest-initiated connections.294path = os.path.join(295test_vm.path,296_make_host_port_path(VSOCK_UDS_PATH, ECHO_SERVER_PORT)297)298check_guest_connections(test_vm, path, vm_blob_path, blob_hash)299300# Test host-initiated connections.301path = os.path.join(test_vm.jailer.chroot_path(), VSOCK_UDS_PATH)302check_host_connections(test_vm, path, blob_path, blob_hash)303304305