Skip to content

Commit d28f380

Browse files
roypatzulinx86
authored andcommitted
fix(test): do not double copy snapshot memory in uffd tests
The restore_from_snapshot function did not integrate well with uffd-based snapshot restore: Even if a UFFD path was specified, it still created a copy of the snapshot memory file inside the chroot, even though the UFFD handler set this up long ago in space_pf_handler. Fix this, and while we're at it, also remove the need for passing in uffd handler and snapshot file explicitly when using uffd-based restore, as the spawn_pf_handler sets the uffd_handler field of the microvm object, and can also easily be made to actually contain the snapshot from which page faults are being served. Signed-off-by: Patrick Roy <[email protected]>
1 parent b38ec33 commit d28f380

File tree

4 files changed

+47
-41
lines changed

4 files changed

+47
-41
lines changed

tests/framework/microvm.py

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -971,33 +971,43 @@ def snapshot_full(self, *, mem_path: str = "mem", vmstate_path="vmstate"):
971971

972972
def restore_from_snapshot(
973973
self,
974-
snapshot: Snapshot,
974+
snapshot: Snapshot = None,
975975
resume: bool = False,
976-
uffd_path: Path = None,
977976
rename_interfaces: dict = None,
978977
):
979978
"""Restore a snapshot"""
980-
jailed_snapshot = snapshot.copy_to_chroot(Path(self.chroot()))
979+
if self.uffd_handler is None:
980+
assert (
981+
snapshot is not None
982+
), "snapshot file must be provided if no uffd handler is attached!"
983+
984+
jailed_snapshot = snapshot.copy_to_chroot(Path(self.chroot()))
985+
else:
986+
jailed_snapshot = self.uffd_handler.snapshot
987+
981988
jailed_mem = Path("/") / jailed_snapshot.mem.name
982989
jailed_vmstate = Path("/") / jailed_snapshot.vmstate.name
983990

984-
snapshot_disks = [v for k, v in snapshot.disks.items()]
991+
snapshot_disks = [v for k, v in jailed_snapshot.disks.items()]
985992
assert len(snapshot_disks) > 0, "Snapshot requires at least one disk."
986993
jailed_disks = []
987994
for disk in snapshot_disks:
988995
jailed_disks.append(self.create_jailed_resource(disk))
989-
self.disks = snapshot.disks
990-
self.ssh_key = snapshot.ssh_key
996+
self.disks = jailed_snapshot.disks
997+
self.ssh_key = jailed_snapshot.ssh_key
991998

992999
# Create network interfaces.
993-
for iface in snapshot.net_ifaces:
1000+
for iface in jailed_snapshot.net_ifaces:
9941001
self.add_net_iface(iface, api=False)
9951002

9961003
mem_backend = {"backend_type": "File", "backend_path": str(jailed_mem)}
997-
if uffd_path is not None:
998-
mem_backend = {"backend_type": "Uffd", "backend_path": str(uffd_path)}
1004+
if self.uffd_handler is not None:
1005+
mem_backend = {
1006+
"backend_type": "Uffd",
1007+
"backend_path": str(self.uffd_handler.socket_path),
1008+
}
9991009

1000-
for key, value in snapshot.meta.items():
1010+
for key, value in jailed_snapshot.meta.items():
10011011
setattr(self, key, value)
10021012
# Adjust things just in case
10031013
self.kernel_file = Path(self.kernel_file)
@@ -1020,12 +1030,12 @@ def restore_from_snapshot(
10201030
self.api.snapshot_load.put(
10211031
mem_backend=mem_backend,
10221032
snapshot_path=str(jailed_vmstate),
1023-
enable_diff_snapshots=snapshot.is_diff,
1033+
enable_diff_snapshots=jailed_snapshot.is_diff,
10241034
resume_vm=resume,
10251035
**optional_kwargs,
10261036
)
10271037
# This is not a "wait for boot", but rather a "VM still works after restoration"
1028-
if snapshot.net_ifaces and resume:
1038+
if jailed_snapshot.net_ifaces and resume:
10291039
self.wait_for_ssh_up()
10301040
return jailed_snapshot
10311041

@@ -1162,18 +1172,14 @@ def build_n_from_snapshot(
11621172
microvm = self.build()
11631173
microvm.spawn()
11641174

1165-
uffd_path = None
11661175
if uffd_handler_name is not None:
1167-
pf_handler = spawn_pf_handler(
1176+
spawn_pf_handler(
11681177
microvm,
11691178
uffd_handler(uffd_handler_name, binary_dir=self.binary_path),
1170-
snapshot.mem,
1179+
snapshot,
11711180
)
1172-
uffd_path = pf_handler.socket_path
11731181

1174-
snapshot_copy = microvm.restore_from_snapshot(
1175-
snapshot, resume=True, uffd_path=uffd_path
1176-
)
1182+
snapshot_copy = microvm.restore_from_snapshot(snapshot, resume=True)
11771183

11781184
yield microvm
11791185

tests/framework/utils_uffd.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,14 @@
1717
class UffdHandler:
1818
"""Describe the UFFD page fault handler process."""
1919

20-
def __init__(self, name, socket_path, mem_file, chroot_path, log_file_name):
20+
def __init__(
21+
self, name, socket_path, snapshot: "Snapshot", chroot_path, log_file_name
22+
):
2123
"""Instantiate the handler process with arguments."""
2224
self._proc = None
2325
self._handler_name = name
2426
self.socket_path = socket_path
25-
self._mem_file = mem_file
27+
self.snapshot = snapshot
2628
self._chroot = chroot_path
2729
self._log_file = log_file_name
2830

@@ -35,7 +37,11 @@ def spawn(self, uid, gid):
3537

3638
chroot_log_file = Path("/") / self._log_file
3739
with open(chroot_log_file, "w", encoding="utf-8") as logfile:
38-
args = [f"/{self._handler_name}", self.socket_path, self._mem_file]
40+
args = [
41+
f"/{self._handler_name}",
42+
self.socket_path,
43+
self.snapshot.mem.name,
44+
]
3945
self._proc = subprocess.Popen(
4046
args, stdout=logfile, stderr=subprocess.STDOUT
4147
)
@@ -77,16 +83,16 @@ def __del__(self):
7783
self.proc.kill()
7884

7985

80-
def spawn_pf_handler(vm, handler_path, mem_path):
86+
def spawn_pf_handler(vm, handler_path, snapshot):
8187
"""Spawn page fault handler process."""
8288
# Copy snapshot memory file into chroot of microVM.
83-
jailed_mem = vm.create_jailed_resource(mem_path)
89+
jailed_snapshot = snapshot.copy_to_chroot(Path(vm.chroot()))
8490
# Copy the valid page fault binary into chroot of microVM.
8591
jailed_handler = vm.create_jailed_resource(handler_path)
8692
handler_name = os.path.basename(jailed_handler)
8793

8894
uffd_handler = UffdHandler(
89-
handler_name, SOCKET_PATH, jailed_mem, vm.chroot(), "uffd.log"
95+
handler_name, SOCKET_PATH, jailed_snapshot, vm.chroot(), "uffd.log"
9096
)
9197
uffd_handler.spawn(vm.jailer.uid, vm.jailer.gid)
9298
vm.uffd_handler = uffd_handler

tests/integration_tests/functional/test_uffd.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -92,9 +92,9 @@ def test_valid_handler(uvm_plain, snapshot):
9292
vm.spawn()
9393

9494
# Spawn page fault handler process.
95-
pf_handler = spawn_pf_handler(vm, uffd_handler("on_demand"), snapshot.mem)
95+
spawn_pf_handler(vm, uffd_handler("on_demand"), snapshot)
9696

97-
vm.restore_from_snapshot(snapshot, resume=True, uffd_path=pf_handler.socket_path)
97+
vm.restore_from_snapshot(resume=True)
9898

9999
# Inflate balloon.
100100
vm.api.balloon.patch(amount_mib=200)
@@ -125,15 +125,13 @@ def test_malicious_handler(uvm_plain, snapshot):
125125
vm.spawn()
126126

127127
# Spawn page fault handler process.
128-
pf_handler = spawn_pf_handler(vm, uffd_handler("malicious"), snapshot.mem)
128+
spawn_pf_handler(vm, uffd_handler("malicious"), snapshot)
129129

130130
# We expect Firecracker to freeze while resuming from a snapshot
131131
# due to the malicious handler's unavailability.
132132
try:
133133
with Timeout(seconds=30):
134-
vm.restore_from_snapshot(
135-
snapshot, resume=True, uffd_path=pf_handler.socket_path
136-
)
134+
vm.restore_from_snapshot(resume=True)
137135
assert False, "Firecracker should freeze"
138136
except (TimeoutError, requests.exceptions.ReadTimeout):
139137
pass

tests/integration_tests/performance/test_huge_pages.py

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -93,9 +93,9 @@ def test_hugetlbfs_snapshot(microvm_factory, guest_kernel_linux_5_10, rootfs):
9393
vm.spawn()
9494

9595
# Spawn page fault handler process.
96-
pf_handler = spawn_pf_handler(vm, uffd_handler("on_demand"), snapshot.mem)
96+
spawn_pf_handler(vm, uffd_handler("on_demand"), snapshot)
9797

98-
vm.restore_from_snapshot(snapshot, resume=True, uffd_path=pf_handler.socket_path)
98+
vm.restore_from_snapshot(resume=True)
9999

100100
check_hugetlbfs_in_use(vm.firecracker_pid, "/anon_hugepage")
101101

@@ -135,11 +135,9 @@ def test_hugetlbfs_diff_snapshot(microvm_factory, uvm_plain):
135135
vm.spawn()
136136

137137
# Spawn page fault handler process.
138-
pf_handler = spawn_pf_handler(vm, uffd_handler("on_demand"), snapshot_merged.mem)
138+
spawn_pf_handler(vm, uffd_handler("on_demand"), snapshot_merged)
139139

140-
vm.restore_from_snapshot(
141-
snapshot_merged, resume=True, uffd_path=pf_handler.socket_path
142-
)
140+
vm.restore_from_snapshot(resume=True)
143141

144142
# Verify if the restored microvm works.
145143

@@ -195,12 +193,10 @@ def test_ept_violation_count(
195193
vm.spawn()
196194

197195
# Spawn page fault handler process.
198-
pf_handler = spawn_pf_handler(vm, uffd_handler("fault_all"), snapshot.mem)
196+
spawn_pf_handler(vm, uffd_handler("fault_all"), snapshot)
199197

200198
with ftrace_events("kvm:*"):
201-
vm.restore_from_snapshot(
202-
snapshot, resume=True, uffd_path=pf_handler.socket_path
203-
)
199+
vm.restore_from_snapshot(resume=True)
204200

205201
# Verify if guest can run commands, and also wake up the fast page fault helper to trigger page faults.
206202
vm.ssh.check_output(f"kill -s {signal.SIGUSR1} {pid}")

0 commit comments

Comments
 (0)