Skip to content

Commit 00b5d05

Browse files
committed
feat(virtio-pmem): add integration tests
Add functional and API tests for virtio-pmem device and its configuration fields Signed-off-by: Egor Lazarchuk <[email protected]>
1 parent 0772975 commit 00b5d05

File tree

5 files changed

+240
-1
lines changed

5 files changed

+240
-1
lines changed

tests/framework/http_api.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,4 +132,5 @@ def __init__(self, api_usocket_full_name, *, on_error=None):
132132
self.snapshot_load = Resource(self, "/snapshot/load")
133133
self.cpu_config = Resource(self, "/cpu-config")
134134
self.entropy = Resource(self, "/entropy")
135+
self.pmem = Resource(self, "/pmem", "id")
135136
self.serial = Resource(self, "/serial")

tests/framework/microvm.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -973,6 +973,24 @@ def add_net_iface(self, iface=None, api=True, **kwargs):
973973

974974
return iface
975975

976+
def add_pmem(
977+
self,
978+
pmem_id,
979+
path_on_host,
980+
root_device=False,
981+
read_only=False,
982+
):
983+
"""Add a pmem device."""
984+
985+
path_on_jail = self.create_jailed_resource(path_on_host)
986+
self.api.pmem.put(
987+
id=pmem_id,
988+
path_on_host=path_on_jail,
989+
root_device=root_device,
990+
read_only=read_only,
991+
)
992+
self.disks[pmem_id] = path_on_host
993+
976994
def start(self):
977995
"""Start the microvm.
978996

tests/framework/vm_config.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,5 +31,6 @@
3131
"logger": null,
3232
"metrics": null,
3333
"mmds-config": null,
34-
"entropy": null
34+
"entropy": null,
35+
"pmem": []
3536
}

tests/integration_tests/functional/test_api.py

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1044,6 +1044,67 @@ def test_api_balloon(uvm_nano):
10441044
test_microvm.api.balloon.patch(amount_mib=33554432)
10451045

10461046

1047+
def test_pmem_api(uvm_plain_any, rootfs):
1048+
"""
1049+
Test virtio-pmem API commands
1050+
"""
1051+
1052+
vm = uvm_plain_any
1053+
vm.spawn()
1054+
vm.basic_config(add_root_device=False)
1055+
1056+
invalid_pmem_path_on_host = os.path.join(vm.fsfiles, "invalid_scratch")
1057+
utils.check_output(f"touch {invalid_pmem_path_on_host}")
1058+
invalid_pmem_file_path = vm.create_jailed_resource(str(invalid_pmem_path_on_host))
1059+
1060+
pmem_size_mb = 2
1061+
pmem_path_on_host = drive_tools.FilesystemFile(
1062+
os.path.join(vm.fsfiles, "scratch"), size=pmem_size_mb
1063+
)
1064+
pmem_file_path = vm.create_jailed_resource(pmem_path_on_host.path)
1065+
1066+
# Try to add pmem without setting `path_on_host`
1067+
expected_msg = re.escape(
1068+
"An error occurred when deserializing the json body of a request: missing field `path_on_host`"
1069+
)
1070+
with pytest.raises(RuntimeError, match=expected_msg):
1071+
vm.api.pmem.put(id="pmem")
1072+
1073+
# Try to add pmem with 0 sized backing file
1074+
expected_msg = re.escape("Error backing file size is 0")
1075+
with pytest.raises(RuntimeError, match=expected_msg):
1076+
vm.api.pmem.put(id="pmem", path_on_host=invalid_pmem_file_path)
1077+
1078+
# Try to add pmem as root while block is set as root
1079+
vm.api.drive.put(drive_id="drive", path_on_host=pmem_file_path, is_root_device=True)
1080+
expected_msg = re.escape(
1081+
"Attempt to add pmem as a root device while the root device defined as a block device"
1082+
)
1083+
with pytest.raises(RuntimeError, match=expected_msg):
1084+
vm.api.pmem.put(id="pmem", path_on_host=pmem_file_path, root_device=True)
1085+
1086+
# Reset block from being root
1087+
vm.api.drive.put(
1088+
drive_id="drive", path_on_host=pmem_file_path, is_root_device=False
1089+
)
1090+
1091+
# Try to add pmem as root twice
1092+
vm.api.pmem.put(id="pmem", path_on_host=pmem_file_path, root_device=True)
1093+
expected_msg = re.escape("A root pmem device already exist")
1094+
with pytest.raises(RuntimeError, match=expected_msg):
1095+
vm.api.pmem.put(id="pmem2", path_on_host=pmem_file_path, root_device=True)
1096+
1097+
# Reset pmem from being root
1098+
vm.api.pmem.put(id="pmem", path_on_host=pmem_file_path, root_device=False)
1099+
1100+
# Add a rootfs to boot a vm
1101+
vm.add_pmem("rootfs", rootfs, True, True)
1102+
1103+
# No post boot API calls to pmem
1104+
with pytest.raises(RuntimeError):
1105+
vm.api.pmem.put(id="pmem")
1106+
1107+
10471108
def test_get_full_config_after_restoring_snapshot(microvm_factory, uvm_nano):
10481109
"""
10491110
Test the configuration of a microVM after restoring from a snapshot.
@@ -1085,6 +1146,21 @@ def test_get_full_config_after_restoring_snapshot(microvm_factory, uvm_nano):
10851146
}
10861147
]
10871148

1149+
uvm_nano.api.pmem.put(
1150+
id="pmem",
1151+
path_on_host="/" + uvm_nano.rootfs_file.name,
1152+
root_device=False,
1153+
read_only=False,
1154+
)
1155+
setup_cfg["pmem"] = [
1156+
{
1157+
"id": "pmem",
1158+
"path_on_host": "/" + uvm_nano.rootfs_file.name,
1159+
"root_device": False,
1160+
"read_only": False,
1161+
}
1162+
]
1163+
10881164
# Add a memory balloon device.
10891165
uvm_nano.api.balloon.put(amount_mib=1, deflate_on_oom=True)
10901166
setup_cfg["balloon"] = {
@@ -1196,6 +1272,21 @@ def test_get_full_config(uvm_plain):
11961272
}
11971273
]
11981274

1275+
test_microvm.api.pmem.put(
1276+
id="pmem",
1277+
path_on_host="/" + test_microvm.rootfs_file.name,
1278+
root_device=False,
1279+
read_only=False,
1280+
)
1281+
expected_cfg["pmem"] = [
1282+
{
1283+
"id": "pmem",
1284+
"path_on_host": "/" + test_microvm.rootfs_file.name,
1285+
"root_device": False,
1286+
"read_only": False,
1287+
}
1288+
]
1289+
11991290
# Add a memory balloon device.
12001291
test_microvm.api.balloon.put(amount_mib=1, deflate_on_oom=True)
12011292
expected_cfg["balloon"] = {
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
# Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
"""Tests for the virtio-pmem device."""
4+
5+
import json
6+
import os
7+
8+
import host_tools.drive as drive_tools
9+
10+
ALIGNMENT = 2 << 20
11+
12+
13+
def align(size: int) -> int:
14+
"""
15+
Align the value to ALIGNMENT
16+
"""
17+
return (size + ALIGNMENT - 1) & ~(ALIGNMENT - 1)
18+
19+
20+
def check_pmem_exist(vm, index, root, read_only, size, extension):
21+
"""
22+
Check the pmem exist with correct parameters
23+
"""
24+
code, _, _ = vm.ssh.run(f"ls /dev/pmem{index}")
25+
assert code == 0
26+
27+
if root:
28+
code, stdout, _ = vm.ssh.run("mount")
29+
assert code == 0
30+
if read_only:
31+
assert f"/dev/pmem0 on / type {extension} (ro" in stdout
32+
else:
33+
assert f"/dev/pmem0 on / type {extension} (rw" in stdout
34+
35+
code, stdout, _ = vm.ssh.run("lsblk -J")
36+
assert code == 0
37+
38+
j = json.loads(stdout)
39+
blocks = j["blockdevices"]
40+
for block in blocks:
41+
if block["name"] == f"pmem{index}":
42+
assert block["size"][-1] == "M"
43+
block_size_mb = int(block["size"][:-1])
44+
assert int(block_size_mb << 20) == size
45+
if root:
46+
assert "/" in block["mountpoints"]
47+
return
48+
assert False
49+
50+
51+
def test_pmem_add(uvm_plain_any, microvm_factory):
52+
"""
53+
Test addition of a single non root pmem device
54+
"""
55+
56+
vm = uvm_plain_any
57+
vm.spawn()
58+
vm.basic_config(add_root_device=True)
59+
vm.add_net_iface()
60+
61+
# Pmem should work with non 2MB aligned files as well
62+
pmem_size_mb_1 = 1
63+
fs_1 = drive_tools.FilesystemFile(
64+
os.path.join(vm.fsfiles, "scratch_1"), size=pmem_size_mb_1
65+
)
66+
pmem_size_mb_2 = 2
67+
fs_2 = drive_tools.FilesystemFile(
68+
os.path.join(vm.fsfiles, "scratch_2"), size=pmem_size_mb_2
69+
)
70+
vm.add_pmem("pmem_1", fs_1.path, False, False)
71+
vm.add_pmem("pmem_2", fs_2.path, False, True)
72+
vm.start()
73+
74+
# Both 1MB and 2MB block will show as 2MB because of
75+
# the aligment
76+
check_pmem_exist(vm, 0, False, False, align(pmem_size_mb_1 << 20), "ext4")
77+
check_pmem_exist(vm, 1, False, True, align(pmem_size_mb_2 << 20), "ext4")
78+
79+
snapshot = vm.snapshot_full()
80+
restored_vm = microvm_factory.build_from_snapshot(snapshot)
81+
check_pmem_exist(restored_vm, 0, False, False, align(pmem_size_mb_1 << 20), "ext4")
82+
check_pmem_exist(restored_vm, 1, False, True, align(pmem_size_mb_2 << 20), "ext4")
83+
84+
85+
def test_pmem_add_as_root_rw(uvm_plain_any, rootfs_rw, microvm_factory):
86+
"""
87+
Test addition of a single root pmem device in read-write mode
88+
"""
89+
90+
vm = uvm_plain_any
91+
vm.memory_monitor = None
92+
vm.monitors = []
93+
vm.spawn()
94+
vm.basic_config(add_root_device=False)
95+
vm.add_net_iface()
96+
97+
rootfs_size = os.path.getsize(rootfs_rw)
98+
vm.add_pmem("pmem", rootfs_rw, True, False)
99+
vm.start()
100+
101+
check_pmem_exist(vm, 0, True, False, align(rootfs_size), "ext4")
102+
103+
snapshot = vm.snapshot_full()
104+
restored_vm = microvm_factory.build_from_snapshot(snapshot)
105+
check_pmem_exist(restored_vm, 0, True, False, align(rootfs_size), "ext4")
106+
107+
108+
def test_pmem_add_as_root_ro(uvm_plain_any, rootfs, microvm_factory):
109+
"""
110+
Test addition of a single root pmem device in read-only mode
111+
"""
112+
113+
vm = uvm_plain_any
114+
vm.memory_monitor = None
115+
vm.monitors = []
116+
vm.spawn()
117+
vm.basic_config(add_root_device=False)
118+
vm.add_net_iface()
119+
120+
rootfs_size = os.path.getsize(rootfs)
121+
vm.add_pmem("pmem", rootfs, True, True)
122+
vm.start()
123+
124+
check_pmem_exist(vm, 0, True, True, align(rootfs_size), "squashfs")
125+
126+
snapshot = vm.snapshot_full()
127+
restored_vm = microvm_factory.build_from_snapshot(snapshot)
128+
check_pmem_exist(restored_vm, 0, True, True, align(rootfs_size), "squashfs")

0 commit comments

Comments
 (0)