Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
aos
GitHub Repository: aos/firecracker
Path: blob/main/tests/integration_tests/functional/test_snapshot_advanced.py
1958 views
1
# Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
# SPDX-License-Identifier: Apache-2.0
3
"""Advanced tests scenarios for snapshot save/restore."""
4
5
import logging
6
import platform
7
import tempfile
8
import pytest
9
from test_balloon import _test_rss_memory_lower, copy_util_to_rootfs
10
from conftest import _test_images_s3_bucket
11
from framework.artifacts import ArtifactCollection, NetIfaceConfig
12
from framework.builder import MicrovmBuilder, SnapshotBuilder, SnapshotType
13
from framework.utils import get_firecracker_version_from_toml
14
import host_tools.network as net_tools # pylint: disable=import-error
15
import host_tools.drive as drive_tools
16
17
18
# Define 4 net device configurations.
19
net_ifaces = [NetIfaceConfig(),
20
NetIfaceConfig(host_ip="192.168.1.1",
21
guest_ip="192.168.1.2",
22
tap_name="tap1",
23
dev_name="eth1"),
24
NetIfaceConfig(host_ip="192.168.2.1",
25
guest_ip="192.168.2.2",
26
tap_name="tap2",
27
dev_name="eth2"),
28
NetIfaceConfig(host_ip="192.168.3.1",
29
guest_ip="192.168.3.2",
30
tap_name="tap3",
31
dev_name="eth3")]
32
# Define 4 scratch drives.
33
scratch_drives = ["vdb", "vdc", "vdd", "vde", "vdf"]
34
35
36
@pytest.mark.skipif(
37
platform.machine() != "x86_64",
38
reason="Not supported yet."
39
)
40
def test_restore_old_snapshot_all_devices(bin_cloner_path):
41
"""Test scenario: restore previous version snapshots in current version."""
42
# Microvm: 2vCPU 256MB RAM, balloon, 4 disks and 4 net devices.
43
logger = logging.getLogger("old_snapshot_many_devices")
44
builder = MicrovmBuilder(bin_cloner_path)
45
46
artifacts = ArtifactCollection(_test_images_s3_bucket())
47
# Fetch all firecracker binaries.
48
# With each binary create a snapshot and try to restore in current
49
# version.
50
firecracker_artifacts = artifacts.firecrackers(
51
older_than=get_firecracker_version_from_toml())
52
for firecracker in firecracker_artifacts:
53
firecracker.download()
54
jailer = firecracker.jailer()
55
jailer.download()
56
57
logger.info("Creating snapshot with Firecracker: %s",
58
firecracker.local_path())
59
logger.info("Using Jailer: %s", jailer.local_path())
60
61
target_version = firecracker.base_name()[1:]
62
63
# v0.23 does not support creating diff snapshots.
64
# v0.23 does not support balloon.
65
diff_snapshots = "0.23" not in target_version
66
67
# Create a snapshot.
68
snapshot = create_snapshot_helper(builder,
69
logger,
70
drives=scratch_drives,
71
ifaces=net_ifaces,
72
fc_binary=firecracker.local_path(),
73
jailer_binary=jailer.local_path(),
74
diff_snapshots=diff_snapshots,
75
balloon=diff_snapshots)
76
77
# Resume microvm using current build of FC/Jailer.
78
microvm, _ = builder.build_from_snapshot(snapshot,
79
resume=True,
80
diff_snapshots=False)
81
validate_all_devices(logger, microvm, net_ifaces, scratch_drives,
82
diff_snapshots)
83
logger.debug("========== Firecracker restore snapshot log ==========")
84
logger.debug(microvm.log_data)
85
86
87
@pytest.mark.skipif(
88
platform.machine() != "x86_64",
89
reason="Not supported yet."
90
)
91
def test_restore_old_version_all_devices(bin_cloner_path):
92
"""Test scenario: restore snapshot in previous versions of Firecracker."""
93
# Microvm: 2vCPU 256MB RAM, balloon, 4 disks and 4 net devices.
94
logger = logging.getLogger("old_snapshot_version_many_devices")
95
builder = MicrovmBuilder(bin_cloner_path)
96
97
artifacts = ArtifactCollection(_test_images_s3_bucket())
98
# Fetch all firecracker binaries.
99
# Create a snapshot with current build and restore with each FC binary
100
# artifact.
101
firecracker_artifacts = artifacts.firecrackers(
102
older_than=get_firecracker_version_from_toml())
103
for firecracker in firecracker_artifacts:
104
firecracker.download()
105
jailer = firecracker.jailer()
106
jailer.download()
107
108
logger.info("Creating snapshot with local build")
109
110
# Old version from artifact.
111
target_version = firecracker.base_name()[1:]
112
# v0.23 does not have a balloon device.
113
balloon = "0.23" not in target_version
114
115
# Create a snapshot with current FC version targeting the old version.
116
snapshot = create_snapshot_helper(builder,
117
logger,
118
target_version=target_version,
119
drives=scratch_drives,
120
ifaces=net_ifaces,
121
balloon=balloon,
122
diff_snapshots=True)
123
124
logger.info("Restoring snapshot with Firecracker: %s",
125
firecracker.local_path())
126
logger.info("Using Jailer: %s", jailer.local_path())
127
128
# Resume microvm using FC/Jailer binary artifacts.
129
vm, _ = builder.build_from_snapshot(snapshot,
130
resume=True,
131
diff_snapshots=False,
132
fc_binary=firecracker.local_path(),
133
jailer_binary=jailer.local_path())
134
validate_all_devices(logger, vm, net_ifaces, scratch_drives,
135
balloon)
136
logger.debug("========== Firecracker restore snapshot log ==========")
137
logger.debug(vm.log_data)
138
139
140
@pytest.mark.skipif(
141
platform.machine() != "x86_64",
142
reason="TSC is x86_64 specific."
143
)
144
def test_restore_no_tsc(bin_cloner_path):
145
"""Test scenario: restore a snapshot without TSC in current version."""
146
logger = logging.getLogger("no_tsc_snapshot")
147
builder = MicrovmBuilder(bin_cloner_path)
148
149
artifacts = ArtifactCollection(_test_images_s3_bucket())
150
# Fetch the v0.24.0 firecracker binary as that one does not have
151
# the TSC frequency in the snapshot file.
152
firecracker_artifacts = artifacts.firecrackers(
153
keyword="v0.24.0"
154
)
155
firecracker = firecracker_artifacts[0]
156
firecracker.download()
157
jailer = firecracker.jailer()
158
jailer.download()
159
diff_snapshots = True
160
161
# Create a snapshot.
162
snapshot = create_snapshot_helper(
163
builder,
164
logger,
165
drives=scratch_drives,
166
ifaces=net_ifaces,
167
fc_binary=firecracker.local_path(),
168
jailer_binary=jailer.local_path(),
169
diff_snapshots=diff_snapshots,
170
balloon=True
171
)
172
173
# Resume microvm using current build of FC/Jailer.
174
# The resume should be successful because the CPU model
175
# in the snapshot state is the same as this host's.
176
microvm, _ = builder.build_from_snapshot(
177
snapshot,
178
resume=True,
179
diff_snapshots=False
180
)
181
validate_all_devices(
182
logger,
183
microvm,
184
net_ifaces,
185
scratch_drives,
186
diff_snapshots
187
)
188
logger.debug("========== Firecracker restore snapshot log ==========")
189
logger.debug(microvm.log_data)
190
191
192
@pytest.mark.skipif(
193
platform.machine() != "x86_64",
194
reason="TSC is x86_64 specific."
195
)
196
def test_save_tsc_old_version(bin_cloner_path):
197
"""Test TSC warning message when saving old snapshot."""
198
vm_builder = MicrovmBuilder(bin_cloner_path)
199
vm_instance = vm_builder.build_vm_nano()
200
vm = vm_instance.vm
201
202
vm.start()
203
204
vm.pause_to_snapshot(
205
mem_file_path='memfile',
206
snapshot_path='statefile',
207
diff=False,
208
version='0.24.0'
209
)
210
211
log_data = vm.log_data
212
assert "Saving to older snapshot version, TSC freq" in log_data
213
vm.kill()
214
215
216
def validate_all_devices(
217
logger,
218
microvm,
219
ifaces,
220
drives,
221
balloon
222
):
223
"""Perform a basic validation for all devices of a microvm."""
224
# Test that net devices have connectivity after restore.
225
for iface in ifaces:
226
logger.info("Testing net device %s", iface.dev_name)
227
microvm.ssh_config['hostname'] = iface.guest_ip
228
ssh_connection = net_tools.SSHConnection(microvm.ssh_config)
229
exit_code, _, _ = ssh_connection.execute_command("sync")
230
231
# Drop page cache.
232
# Ensure further reads are going to be served from emulation layer.
233
cmd = "sync; echo 1 > /proc/sys/vm/drop_caches"
234
exit_code, _, _ = ssh_connection.execute_command(cmd)
235
assert exit_code == 0
236
237
# Validate checksum of /dev/vdX/test.
238
# Should be ab893875d697a3145af5eed5309bee26 for 10 pages
239
# of zeroes.
240
for drive in drives:
241
# Mount block device.
242
logger.info("Testing drive %s", drive)
243
cmd = "mount /dev/{drive} /mnt/{drive}".format(drive=drive)
244
exit_code, _, _ = ssh_connection.execute_command(cmd)
245
assert exit_code == 0
246
247
# Validate checksum.
248
cmd = "md5sum /mnt/{}/test | cut -d ' ' -f 1".format(drive)
249
exit_code, stdout, _ = ssh_connection.execute_command(cmd)
250
assert exit_code == 0
251
assert stdout.read().strip() == "ab893875d697a3145af5eed5309bee26"
252
logger.info("* checksum OK.")
253
254
if balloon is True:
255
logger.info("Testing balloon memory reclaim.")
256
# Call helper fn from balloon integration tests.
257
_test_rss_memory_lower(microvm)
258
259
260
def create_snapshot_helper(builder, logger, target_version=None,
261
drives=None, ifaces=None,
262
balloon=False, diff_snapshots=False,
263
fc_binary=None, jailer_binary=None):
264
"""Create a snapshot with many devices."""
265
vm_instance = builder.build_vm_nano(net_ifaces=ifaces,
266
diff_snapshots=diff_snapshots,
267
fc_binary=fc_binary,
268
jailer_binary=jailer_binary)
269
vm = vm_instance.vm
270
271
if diff_snapshots is False:
272
snapshot_type = SnapshotType.FULL
273
else:
274
# Version 0.24 and greater has Diff and balloon support.
275
snapshot_type = SnapshotType.DIFF
276
277
if balloon:
278
# Copy balloon test util.
279
copy_util_to_rootfs(vm_instance.disks[0].local_path(), 'fillmem')
280
281
# Add a memory balloon with stats enabled.
282
response = vm.balloon.put(
283
amount_mib=0,
284
deflate_on_oom=True,
285
stats_polling_interval_s=1
286
)
287
assert vm.api_session.is_status_no_content(response.status_code)
288
289
# Disk path array needed when creating the snapshot later.
290
disks = [vm_instance.disks[0].local_path()]
291
test_drives = [] if drives is None else drives
292
293
# Add disks.
294
for scratch in test_drives:
295
# Add a scratch 64MB RW non-root block device.
296
scratchdisk = drive_tools.FilesystemFile(tempfile.mktemp(), size=64)
297
vm.add_drive(scratch, scratchdisk.path)
298
disks.append(scratchdisk.path)
299
300
# Workaround FilesystemFile destructor removal of file.
301
scratchdisk.path = None
302
303
vm.start()
304
305
# Iterate and validate connectivity on all ifaces after boot.
306
for iface in net_ifaces:
307
vm.ssh_config['hostname'] = iface.guest_ip
308
ssh_connection = net_tools.SSHConnection(vm.ssh_config)
309
exit_code, _, _ = ssh_connection.execute_command("sync")
310
assert exit_code == 0
311
312
# Mount scratch drives in guest.
313
for blk in scratch_drives:
314
# Create mount point and mount each device.
315
cmd = "mkdir -p /mnt/{blk} && mount /dev/{blk} /mnt/{blk}".format(
316
blk=blk
317
)
318
exit_code, _, _ = ssh_connection.execute_command(cmd)
319
assert exit_code == 0
320
321
# Create file using dd using O_DIRECT.
322
# After resume we will compute md5sum on these files.
323
dd = "dd if=/dev/zero of=/mnt/{}/test bs=4096 count=10 oflag=direct"
324
exit_code, _, _ = ssh_connection.execute_command(dd.format(blk))
325
assert exit_code == 0
326
327
# Unmount the device.
328
cmd = "umount /dev/{}".format(blk)
329
exit_code, _, _ = ssh_connection.execute_command(cmd)
330
assert exit_code == 0
331
332
# Create a snapshot builder from a microvm.
333
snapshot_builder = SnapshotBuilder(vm)
334
335
snapshot = snapshot_builder.create(disks,
336
vm_instance.ssh_key,
337
target_version=target_version,
338
snapshot_type=snapshot_type,
339
net_ifaces=net_ifaces)
340
logger.debug("========== Firecracker create snapshot log ==========")
341
logger.debug(vm.log_data)
342
vm.kill()
343
return snapshot
344
345