Skip to content

Commit 7e90988

Browse files
committed
refactor(test): introduce utils_uffd.py
Currently, all tests that want to make use of UFFD import UFFD related functions from test_uffd.py. That's a bit awkward, so move them into their own utility module. Signed-off-by: Patrick Roy <[email protected]>
1 parent 51167e6 commit 7e90988

File tree

4 files changed

+95
-84
lines changed

4 files changed

+95
-84
lines changed

tests/framework/utils.py

Lines changed: 0 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
import re
1111
import select
1212
import signal
13-
import stat
1413
import subprocess
1514
import time
1615
import typing
@@ -133,69 +132,6 @@ def chroot(path):
133132
os.chdir(working_dir)
134133

135134

136-
class UffdHandler:
137-
"""Describe the UFFD page fault handler process."""
138-
139-
def __init__(self, name, socket_path, mem_file, chroot_path, log_file_name):
140-
"""Instantiate the handler process with arguments."""
141-
self._proc = None
142-
self._handler_name = name
143-
self._socket_path = socket_path
144-
self._mem_file = mem_file
145-
self._chroot = chroot_path
146-
self._log_file = log_file_name
147-
148-
def spawn(self, uid, gid):
149-
"""Spawn handler process using arguments provided."""
150-
151-
with chroot(self._chroot):
152-
st = os.stat(self._handler_name)
153-
os.chmod(self._handler_name, st.st_mode | stat.S_IEXEC)
154-
155-
chroot_log_file = Path("/") / self._log_file
156-
with open(chroot_log_file, "w", encoding="utf-8") as logfile:
157-
args = [f"/{self._handler_name}", self._socket_path, self._mem_file]
158-
self._proc = subprocess.Popen(
159-
args, stdout=logfile, stderr=subprocess.STDOUT
160-
)
161-
162-
# Give it time start and fail, if it really has too (bad things happen).
163-
time.sleep(1)
164-
if not self.is_running():
165-
print(chroot_log_file.read_text(encoding="utf-8"))
166-
assert False, "Could not start PF handler!"
167-
168-
# The page fault handler will create the socket path with root rights.
169-
# Change rights to the jailer's.
170-
os.chown(self._socket_path, uid, gid)
171-
172-
@property
173-
def proc(self):
174-
"""Return UFFD handler process."""
175-
return self._proc
176-
177-
def is_running(self):
178-
"""Check if UFFD process is running"""
179-
return self.proc is not None and self.proc.poll() is None
180-
181-
@property
182-
def log_file(self):
183-
"""Return the path to the UFFD handler's log file"""
184-
return Path(self._chroot) / Path(self._log_file)
185-
186-
@property
187-
def log_data(self):
188-
"""Return the log data of the UFFD handler"""
189-
if self.log_file is None:
190-
return ""
191-
return self.log_file.read_text(encoding="utf-8")
192-
193-
def __del__(self):
194-
"""Tear down the UFFD handler process."""
195-
if self.proc is not None:
196-
self.proc.kill()
197-
198-
199135
class CpuMap:
200136
"""Cpu map from real cpu cores to containers visible cores.
201137

tests/framework/utils_uffd.py

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
# Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
"""UFFD related utility functions"""
4+
5+
import os
6+
import stat
7+
import subprocess
8+
import time
9+
from pathlib import Path
10+
11+
from framework.utils import chroot
12+
13+
SOCKET_PATH = "/firecracker-uffd.sock"
14+
15+
16+
class UffdHandler:
17+
"""Describe the UFFD page fault handler process."""
18+
19+
def __init__(self, name, socket_path, mem_file, chroot_path, log_file_name):
20+
"""Instantiate the handler process with arguments."""
21+
self._proc = None
22+
self._handler_name = name
23+
self._socket_path = socket_path
24+
self._mem_file = mem_file
25+
self._chroot = chroot_path
26+
self._log_file = log_file_name
27+
28+
def spawn(self, uid, gid):
29+
"""Spawn handler process using arguments provided."""
30+
31+
with chroot(self._chroot):
32+
st = os.stat(self._handler_name)
33+
os.chmod(self._handler_name, st.st_mode | stat.S_IEXEC)
34+
35+
chroot_log_file = Path("/") / self._log_file
36+
with open(chroot_log_file, "w", encoding="utf-8") as logfile:
37+
args = [f"/{self._handler_name}", self._socket_path, self._mem_file]
38+
self._proc = subprocess.Popen(
39+
args, stdout=logfile, stderr=subprocess.STDOUT
40+
)
41+
42+
# Give it time start and fail, if it really has too (bad things happen).
43+
time.sleep(1)
44+
if not self.is_running():
45+
print(chroot_log_file.read_text(encoding="utf-8"))
46+
assert False, "Could not start PF handler!"
47+
48+
# The page fault handler will create the socket path with root rights.
49+
# Change rights to the jailer's.
50+
os.chown(self._socket_path, uid, gid)
51+
52+
@property
53+
def proc(self):
54+
"""Return UFFD handler process."""
55+
return self._proc
56+
57+
def is_running(self):
58+
"""Check if UFFD process is running"""
59+
return self.proc is not None and self.proc.poll() is None
60+
61+
@property
62+
def log_file(self):
63+
"""Return the path to the UFFD handler's log file"""
64+
return Path(self._chroot) / Path(self._log_file)
65+
66+
@property
67+
def log_data(self):
68+
"""Return the log data of the UFFD handler"""
69+
if self.log_file is None:
70+
return ""
71+
return self.log_file.read_text(encoding="utf-8")
72+
73+
def __del__(self):
74+
"""Tear down the UFFD handler process."""
75+
if self.proc is not None:
76+
self.proc.kill()
77+
78+
79+
def spawn_pf_handler(vm, handler_path, mem_path):
80+
"""Spawn page fault handler process."""
81+
# Copy snapshot memory file into chroot of microVM.
82+
jailed_mem = vm.create_jailed_resource(mem_path)
83+
# Copy the valid page fault binary into chroot of microVM.
84+
jailed_handler = vm.create_jailed_resource(handler_path)
85+
handler_name = os.path.basename(jailed_handler)
86+
87+
uffd_handler = UffdHandler(
88+
handler_name, SOCKET_PATH, jailed_mem, vm.chroot(), "uffd.log"
89+
)
90+
uffd_handler.spawn(vm.jailer.uid, vm.jailer.gid)
91+
92+
return uffd_handler

tests/integration_tests/functional/test_uffd.py

Lines changed: 2 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,8 @@
88
import pytest
99
import requests
1010

11-
from framework.utils import Timeout, UffdHandler, check_output
12-
13-
SOCKET_PATH = "/firecracker-uffd.sock"
11+
from framework.utils import Timeout, check_output
12+
from framework.utils_uffd import SOCKET_PATH, spawn_pf_handler
1413

1514

1615
@pytest.fixture(scope="function", name="snapshot")
@@ -36,22 +35,6 @@ def snapshot_fxt(microvm_factory, guest_kernel_linux_5_10, rootfs):
3635
yield snapshot
3736

3837

39-
def spawn_pf_handler(vm, handler_path, mem_path):
40-
"""Spawn page fault handler process."""
41-
# Copy snapshot memory file into chroot of microVM.
42-
jailed_mem = vm.create_jailed_resource(mem_path)
43-
# Copy the valid page fault binary into chroot of microVM.
44-
jailed_handler = vm.create_jailed_resource(handler_path)
45-
handler_name = os.path.basename(jailed_handler)
46-
47-
uffd_handler = UffdHandler(
48-
handler_name, SOCKET_PATH, jailed_mem, vm.chroot(), "uffd.log"
49-
)
50-
uffd_handler.spawn(vm.jailer.uid, vm.jailer.gid)
51-
52-
return uffd_handler
53-
54-
5538
def test_bad_socket_path(uvm_plain, snapshot):
5639
"""
5740
Test error scenario when socket path does not exist.

tests/integration_tests/performance/test_huge_pages.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from framework.microvm import HugePagesConfig
1111
from framework.properties import global_props
1212
from framework.utils_ftrace import ftrace_events
13-
from integration_tests.functional.test_uffd import SOCKET_PATH, spawn_pf_handler
13+
from framework.utils_uffd import SOCKET_PATH, spawn_pf_handler
1414

1515

1616
def check_hugetlbfs_in_use(pid: int, allocation_name: str):

0 commit comments

Comments
 (0)