Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
aos
GitHub Repository: aos/firecracker
Path: blob/main/tests/integration_tests/functional/test_vsock.py
1958 views
1
# Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
# SPDX-License-Identifier: Apache-2.0
3
"""Tests for the virtio-vsock device.
4
5
In order to test the vsock device connection state machine, these tests will:
6
- Generate a 20MiB random data blob;
7
- Use `host_tools/vsock_helper.c` to start a listening echo server inside the
8
guest VM;
9
- Run 50, concurrent, host-initiated connections, each transfering the random
10
blob to and from the guest echo server;
11
- For every connection, check that the data received back from the echo server
12
hashes to the same value as the data sent;
13
- Start a host echo server, and repeat the process for the same number of
14
guest-initiated connections.
15
"""
16
17
import os.path
18
19
from socket import timeout as SocketTimeout
20
from framework.utils_vsock import make_blob, \
21
check_host_connections, check_guest_connections, \
22
HostEchoWorker
23
from framework.builder import MicrovmBuilder, SnapshotBuilder, SnapshotType
24
25
from host_tools.network import SSHConnection
26
import host_tools.logging as log_tools
27
28
VSOCK_UDS_PATH = "v.sock"
29
ECHO_SERVER_PORT = 5252
30
BLOB_SIZE = 20 * 1024 * 1024
31
NEGATIVE_TEST_CONNECTION_COUNT = 100
32
TEST_WORKER_COUNT = 10
33
34
35
def test_vsock(
36
test_microvm_with_ssh,
37
network_config,
38
bin_vsock_path,
39
test_fc_session_root_path
40
):
41
"""Vsock tests. See the module docstring for a high-level description."""
42
vm = test_microvm_with_ssh
43
vm.spawn()
44
45
vm.basic_config()
46
_tap, _, _ = vm.ssh_network_config(network_config, '1')
47
vm.vsock.put(
48
vsock_id="vsock0",
49
guest_cid=3,
50
uds_path="/{}".format(VSOCK_UDS_PATH)
51
)
52
53
vm.start()
54
55
# Generate the random data blob file.
56
blob_path, blob_hash = make_blob(test_fc_session_root_path)
57
vm_blob_path = "/tmp/vsock/test.blob"
58
59
# Set up a tmpfs drive on the guest, so we can copy the blob there.
60
# Guest-initiated connections (echo workers) will use this blob.
61
conn = SSHConnection(vm.ssh_config)
62
cmd = "mkdir -p /tmp/vsock"
63
cmd += " && mount -t tmpfs tmpfs -o size={} /tmp/vsock".format(
64
BLOB_SIZE + 1024*1024
65
)
66
ecode, _, _ = conn.execute_command(cmd)
67
assert ecode == 0
68
69
# Copy `vsock_helper` and the random blob to the guest.
70
vsock_helper = bin_vsock_path
71
conn.scp_file(vsock_helper, '/bin/vsock_helper')
72
conn.scp_file(blob_path, vm_blob_path)
73
74
# Test guest-initiated connections.
75
path = os.path.join(
76
vm.path,
77
_make_host_port_path(VSOCK_UDS_PATH, ECHO_SERVER_PORT)
78
)
79
check_guest_connections(vm, path, vm_blob_path, blob_hash)
80
81
# Test host-initiated connections.
82
path = os.path.join(vm.jailer.chroot_path(), VSOCK_UDS_PATH)
83
check_host_connections(vm, path, blob_path, blob_hash)
84
85
86
def _make_host_port_path(uds_path, port):
87
"""Build the path for a Unix socket, mapped to host vsock port `port`."""
88
return "{}_{}".format(uds_path, port)
89
90
91
def negative_test_host_connections(vm, uds_path, blob_path, blob_hash):
92
"""Negative test for host-initiated connections.
93
94
This will start a daemonized echo server on the guest VM, and then spawn
95
`NEGATIVE_TEST_CONNECTION_COUNT` `HostEchoWorker` threads.
96
Closes the UDS sockets while data is in flight.
97
"""
98
conn = SSHConnection(vm.ssh_config)
99
cmd = "vsock_helper echosrv -d {}". format(ECHO_SERVER_PORT)
100
ecode, _, _ = conn.execute_command(cmd)
101
assert ecode == 0
102
103
workers = []
104
for _ in range(NEGATIVE_TEST_CONNECTION_COUNT):
105
worker = HostEchoWorker(uds_path, blob_path)
106
workers.append(worker)
107
worker.start()
108
109
for wrk in workers:
110
wrk.close_uds()
111
wrk.join()
112
113
# Validate that Firecracker is still up and running.
114
ecode, _, _ = conn.execute_command("sync")
115
# Should fail if Firecracker exited from SIGPIPE handler.
116
assert ecode == 0
117
118
# Validate vsock emulation still accepts connections and works
119
# as expected.
120
check_host_connections(vm, uds_path, blob_path, blob_hash)
121
122
123
def test_vsock_epipe(
124
test_microvm_with_ssh,
125
network_config,
126
bin_vsock_path,
127
test_fc_session_root_path
128
):
129
"""Vsock negative test to validate SIGPIPE/EPIPE handling."""
130
vm = test_microvm_with_ssh
131
vm.spawn()
132
133
vm.basic_config()
134
_tap, _, _ = vm.ssh_network_config(network_config, '1')
135
vm.vsock.put(
136
vsock_id="vsock0",
137
guest_cid=3,
138
uds_path="/{}".format(VSOCK_UDS_PATH)
139
)
140
141
# Configure metrics to assert against `sigpipe` count.
142
metrics_fifo_path = os.path.join(vm.path, 'metrics_fifo')
143
metrics_fifo = log_tools.Fifo(metrics_fifo_path)
144
response = vm.metrics.put(
145
metrics_path=vm.create_jailed_resource(metrics_fifo.path)
146
)
147
assert vm.api_session.is_status_no_content(response.status_code)
148
149
vm.start()
150
151
# Generate the random data blob file.
152
blob_path, blob_hash = make_blob(test_fc_session_root_path)
153
vm_blob_path = "/tmp/vsock/test.blob"
154
155
# Set up a tmpfs drive on the guest, so we can copy the blob there.
156
# Guest-initiated connections (echo workers) will use this blob.
157
conn = SSHConnection(vm.ssh_config)
158
cmd = "mkdir -p /tmp/vsock"
159
cmd += " && mount -t tmpfs tmpfs -o size={} /tmp/vsock".format(
160
BLOB_SIZE + 1024*1024
161
)
162
ecode, _, _ = conn.execute_command(cmd)
163
assert ecode == 0
164
165
# Copy `vsock_helper` and the random blob to the guest.
166
vsock_helper = bin_vsock_path
167
conn.scp_file(vsock_helper, '/bin/vsock_helper')
168
conn.scp_file(blob_path, vm_blob_path)
169
170
path = os.path.join(vm.jailer.chroot_path(), VSOCK_UDS_PATH)
171
# Negative test for host-initiated connections that
172
# are closed with in flight data.
173
negative_test_host_connections(vm, path, blob_path, blob_hash)
174
175
metrics = vm.flush_metrics(metrics_fifo)
176
# Validate that at least 1 `SIGPIPE` signal was received.
177
# Since we are reusing the existing echo server which triggers
178
# reads/writes on the UDS backend connections, these might be closed
179
# before a read() or a write() is about to be performed by the emulation.
180
# The test uses 100 connections it is enough to close at least one
181
# before write().
182
#
183
# If this ever fails due to 100 closes before read() we must
184
# add extra tooling that will trigger only writes().
185
assert metrics['signals']['sigpipe'] > 0
186
187
188
def test_vsock_transport_reset(
189
bin_cloner_path,
190
bin_vsock_path,
191
test_fc_session_root_path
192
):
193
"""
194
Vsock transport reset test.
195
196
Steps:
197
1. Start echo server on the guest
198
2. Start host workers that ping-pong data between guest and host,
199
without closing any of them
200
3. Pause VM -> Create snapshot -> Resume VM
201
4. Check that worker sockets no longer work by setting a timeout
202
so the sockets won't block and do a recv operation.
203
5. If the recv operation timeouts, the connection was closed.
204
Else, the connection was not closed and the test fails.
205
6. Close VM -> Load VM from Snapshot -> check that vsock
206
device is still working.
207
"""
208
vm_builder = MicrovmBuilder(bin_cloner_path)
209
vm_instance = vm_builder.build_vm_nano()
210
test_vm = vm_instance.vm
211
root_disk = vm_instance.disks[0]
212
ssh_key = vm_instance.ssh_key
213
214
test_vm.vsock.put(
215
vsock_id="vsock0",
216
guest_cid=3,
217
uds_path="/{}".format(VSOCK_UDS_PATH)
218
)
219
220
test_vm.start()
221
222
snapshot_builder = SnapshotBuilder(test_vm)
223
disks = [root_disk.local_path()]
224
225
# Generate the random data blob file.
226
blob_path, blob_hash = make_blob(test_fc_session_root_path)
227
vm_blob_path = "/tmp/vsock/test.blob"
228
229
# Set up a tmpfs drive on the guest, so we can copy the blob there.
230
# Guest-initiated connections (echo workers) will use this blob.
231
conn = SSHConnection(test_vm.ssh_config)
232
cmd = "mkdir -p /tmp/vsock"
233
cmd += " && mount -t tmpfs tmpfs -o size={} /tmp/vsock".format(
234
BLOB_SIZE + 1024*1024
235
)
236
ecode, _, _ = conn.execute_command(cmd)
237
assert ecode == 0
238
239
# Copy `vsock_helper` and the random blob to the guest.
240
vsock_helper = bin_vsock_path
241
conn.scp_file(vsock_helper, '/bin/vsock_helper')
242
conn.scp_file(blob_path, vm_blob_path)
243
244
# Start guest echo server.
245
path = os.path.join(test_vm.jailer.chroot_path(), VSOCK_UDS_PATH)
246
conn = SSHConnection(test_vm.ssh_config)
247
cmd = "vsock_helper echosrv -d {}". format(ECHO_SERVER_PORT)
248
ecode, _, _ = conn.execute_command(cmd)
249
assert ecode == 0
250
251
# Start host workers that connect to the guest server.
252
workers = []
253
for _ in range(TEST_WORKER_COUNT):
254
worker = HostEchoWorker(path, blob_path)
255
workers.append(worker)
256
worker.start()
257
258
for wrk in workers:
259
wrk.join()
260
261
# Create snapshot.
262
snapshot = snapshot_builder.create(disks,
263
ssh_key,
264
SnapshotType.FULL)
265
response = test_vm.vm.patch(state='Resumed')
266
assert test_vm.api_session.is_status_no_content(response.status_code)
267
268
# Check that sockets are no longer working on workers.
269
for worker in workers:
270
# Whatever we send to the server, it should return the same
271
# value.
272
buf = bytearray("TEST\n".encode('utf-8'))
273
worker.sock.send(buf)
274
try:
275
# Arbitrary timeout, we set this so the socket won't block as
276
# it shouldn't receive anything.
277
worker.sock.settimeout(0.25)
278
response = worker.sock.recv(32)
279
# If we reach here, it means the connection did not close.
280
assert False, "Connection not closed: {}" \
281
.format(response.decode('utf-8'))
282
except SocketTimeout as exc:
283
assert True, exc
284
285
# Terminate VM.
286
test_vm.kill()
287
288
# Load snapshot.
289
test_vm, _ = vm_builder.build_from_snapshot(snapshot,
290
True,
291
False)
292
293
# Check that vsock device still works.
294
# Test guest-initiated connections.
295
path = os.path.join(
296
test_vm.path,
297
_make_host_port_path(VSOCK_UDS_PATH, ECHO_SERVER_PORT)
298
)
299
check_guest_connections(test_vm, path, vm_blob_path, blob_hash)
300
301
# Test host-initiated connections.
302
path = os.path.join(test_vm.jailer.chroot_path(), VSOCK_UDS_PATH)
303
check_host_connections(test_vm, path, blob_path, blob_hash)
304
305