Skip to content

Commit 4fe5c9d

Browse files
authored
Merge pull request #6491 from BulaYoungR/snapshot_250
Test case automation creating 250 internal/external snapshot
2 parents 8abf7b8 + 0ea92b0 commit 4fe5c9d

File tree

2 files changed

+269
-0
lines changed

2 files changed

+269
-0
lines changed
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
- virsh_snapshot_stress:
2+
type = virsh_snapshot_stress
3+
work_dir = /home/libvirt/work
4+
snapshot_count = 250
5+
start_vm = no
6+
snapshot_interval = 1
7+
8+
variants:
9+
- internal:
10+
only aarch64
11+
snapshot_type = internal
12+
- external:
13+
snapshot_type = external
Lines changed: 256 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,256 @@
1+
import shutil
2+
import time
3+
import logging
4+
import tempfile
5+
from pathlib import Path
6+
7+
from avocado.utils import process
8+
from virttest import virsh, utils_libvirtd, utils_selinux
9+
from virttest.utils_libvirt import libvirt_disk, libvirt_vmxml
10+
from virttest.libvirt_xml import vm_xml
11+
12+
13+
def _cmd_ok(test, res, what):
14+
"""Fail the test if a virsh or shell command did not exit successfully."""
15+
if res.exit_status != 0:
16+
test.fail(f"{what} failed: rc={res.exit_status}, err={res.stderr_text}")
17+
18+
19+
def _is_selinux_enforcing():
20+
"""Return True if SELinux is currently enforcing mode."""
21+
return str(utils_selinux.get_status()).strip().lower() == "enforcing"
22+
23+
24+
def _selinux_relabel_path(p):
25+
"""Apply SELinux relabeling to a given path if SELinux is enforcing."""
26+
if _is_selinux_enforcing():
27+
process.run(f"chcon -R -t virt_image_t {p}", shell=True, ignore_status=True)
28+
29+
30+
def _start_vm_and_login(vm):
31+
"""Ensure the VM is running and reachable via guest login."""
32+
if not vm.is_alive():
33+
vm.start()
34+
vm.wait_for_login().close()
35+
36+
37+
def _get_disk_path(env, vm_name):
38+
"""Retrieve the file path of the first disk device for the given VM."""
39+
vm = env.get_vm(vm_name)
40+
return Path(libvirt_disk.get_first_disk_source(vm))
41+
42+
43+
def _define_from_xml_str(test, xml_str):
44+
"""Define a VM temporarily using XML string and validate it via virsh define."""
45+
with tempfile.NamedTemporaryFile("w", delete=False) as f:
46+
f.write(xml_str)
47+
tmp = f.name
48+
try:
49+
res = virsh.define(tmp, debug=True, ignore_status=True)
50+
_cmd_ok(test, res, f"virsh define {tmp}")
51+
finally:
52+
Path(tmp).unlink(missing_ok=True)
53+
54+
55+
def _retarget_disk_persistent_xml(test, vm_name, target_dev, new_path):
56+
"""Change a VM's disk path in persistent XML to the given new path."""
57+
disk_dict = {
58+
"target": {"dev": target_dev},
59+
"source": {"attrs": {"file": str(new_path)}},
60+
}
61+
libvirt_vmxml.modify_vm_device(
62+
vm_xml.VMXML.new_from_inactive_dumpxml(vm_name), "disk", disk_dict
63+
)
64+
65+
66+
def _list_snapshot_names(test, vm_name):
67+
"""
68+
Return a list of snapshot names for a VM (metadata snapshots).
69+
Run via virttest.virsh.command to always get a CmdResult.
70+
"""
71+
res = virsh.command(f"snapshot-list {vm_name} --name",
72+
ignore_status=False, debug=True)
73+
_cmd_ok(test, res, f"virsh snapshot-list {vm_name} --name")
74+
names = [ln.strip() for ln in res.stdout_text.splitlines() if ln.strip()]
75+
logging.info("Snapshot names for %s: %s", vm_name, names)
76+
return names
77+
78+
79+
def _verify_snapshot_count(test, vm_name, expected):
80+
"""Fail with explicit list if actual count != expected."""
81+
names = _list_snapshot_names(test, vm_name)
82+
if len(names) != int(expected):
83+
test.fail(
84+
f"Snapshot count mismatch: expected {expected}, got {len(names)}; "
85+
f"names={names}"
86+
)
87+
88+
89+
def _check_virsh_commands(test, vm_name, target_dev, expected_snap_count=None):
90+
"""
91+
Run basic virsh checks to validate VM and disk state.
92+
Optionally verify snapshot count equals expected_snap_count.
93+
"""
94+
virsh.domstats(vm_name, ignore_status=False, debug=True)
95+
virsh.domblkinfo(vm_name, target_dev, ignore_status=False, debug=True)
96+
virsh.snapshot_list(vm_name, ignore_status=False, debug=True)
97+
98+
if expected_snap_count is not None:
99+
# Do a strict count using raw virsh before teardown can start:
100+
_verify_snapshot_count(test, vm_name, expected_snap_count)
101+
102+
103+
def setup_test(test, env, vm_name, target_dev, work_dir, images_dir):
104+
"""
105+
Prepare the test environment:
106+
- Copy VM disk image to a working directory.
107+
- Apply SELinux relabeling if required.
108+
- Update the VM’s XML to point to the copied image.
109+
- Start the VM and verify login.
110+
Returns (original_path, copied_path).
111+
"""
112+
original_path = _get_disk_path(env, vm_name)
113+
logging.info(
114+
"Original disk %s:%s -> %s", vm_name, target_dev, original_path
115+
)
116+
117+
work_dir = Path(work_dir)
118+
images_dir = Path(images_dir)
119+
work_dir.mkdir(parents=True, exist_ok=True)
120+
121+
dest = work_dir / original_path.name
122+
123+
try:
124+
dest.unlink(missing_ok=True)
125+
shutil.copyfile(original_path, dest)
126+
except Exception as e:
127+
test.fail(f"Failed to prepare working image '{dest}': {e}")
128+
copied_path = dest
129+
130+
_selinux_relabel_path(work_dir)
131+
_selinux_relabel_path(copied_path)
132+
133+
_retarget_disk_persistent_xml(test, vm_name, target_dev, copied_path)
134+
135+
if not copied_path.exists():
136+
test.fail(
137+
f"Expected image at '{copied_path}' after copy/restore, but file is missing"
138+
)
139+
140+
vm = env.get_vm(vm_name)
141+
_start_vm_and_login(vm)
142+
143+
return original_path, copied_path
144+
145+
146+
def run_test(test, vm_name, snap_type, count, interval, target_dev):
147+
"""
148+
Run snapshot stress test:
149+
- Create N snapshots with a delay between each.
150+
- Restart libvirtd and run verification commands.
151+
"""
152+
snapshot_opt = "--disk-only" if snap_type == "external" else ""
153+
for _ in range(count):
154+
virsh.snapshot_create(
155+
vm_name, options=snapshot_opt, debug=True, ignore_status=False
156+
)
157+
if interval > 0:
158+
time.sleep(interval)
159+
_verify_snapshot_count(test, vm_name, count)
160+
utils_libvirtd.Libvirtd().restart()
161+
time.sleep(2)
162+
_check_virsh_commands(test, vm_name, target_dev, expected_snap_count=count)
163+
164+
165+
def _delete_all_snapshots(vm_name, external):
166+
"""Delete all snapshots of a given VM (metadata only for external ones)."""
167+
while True:
168+
cur = virsh.snapshot_current(
169+
vm_name, options="--name", ignore_status=True, debug=True
170+
)
171+
snap = cur.stdout_text.strip() if cur.exit_status == 0 else ""
172+
if not snap:
173+
break
174+
if external:
175+
virsh.snapshot_delete(
176+
vm_name,
177+
snap,
178+
options="--metadata",
179+
ignore_status=False,
180+
debug=True,
181+
)
182+
else:
183+
virsh.snapshot_delete(
184+
vm_name, snap, ignore_status=False, debug=True
185+
)
186+
187+
188+
def teardown_test(test, vm, vm_name, external, copied_path, original_path, bkxml):
189+
"""
190+
Clean up after test:
191+
- Delete all snapshots.
192+
- Destroy VM if running.
193+
- Remove copied image.
194+
- Restore original XML configuration.
195+
"""
196+
try:
197+
_delete_all_snapshots(vm_name, external)
198+
finally:
199+
try:
200+
if vm is not None and vm.is_alive():
201+
vm.destroy(gracefully=False)
202+
if copied_path and original_path and copied_path != original_path:
203+
Path(copied_path).unlink(missing_ok=True)
204+
if bkxml is not None:
205+
try:
206+
bkxml.sync()
207+
except Exception as e:
208+
test.fail(f"Failed to restore VM XML via VMXML: {e}")
209+
except Exception:
210+
raise
211+
212+
213+
def run(test, params, env):
214+
"""
215+
Main test entry point:
216+
- Prepare working copy of VM disk.
217+
- Run snapshot stress test.
218+
- Clean up and restore VM configuration.
219+
"""
220+
vm_name = params.get("main_vm")
221+
vm = env.get_vm(vm_name)
222+
if vm is None:
223+
test.fail(f"VM '{vm_name}' not found in env")
224+
225+
snap_type = params.get("snapshot_type", "internal").strip().lower()
226+
count = int(params.get("snapshot_count", 250))
227+
interval = int(params.get("snapshot_interval", 1))
228+
target_dev = params.get("target_dev", "vda")
229+
work_dir = Path(params.get("work_dir", "/home/libvirt-work"))
230+
images_dir = Path(
231+
params.get(
232+
"images_dir", "/var/lib/avocado/data/avocado-vt/images"
233+
)
234+
)
235+
236+
work_dir.mkdir(parents=True, exist_ok=True)
237+
238+
bkxml = vm_xml.VMXML.new_from_inactive_dumpxml(vm_name).copy()
239+
240+
original_path = None
241+
copied_path = None
242+
try:
243+
original_path, copied_path = setup_test(
244+
test, env, vm_name, target_dev, work_dir, images_dir
245+
)
246+
run_test(test, vm_name, snap_type, count, interval, target_dev)
247+
finally:
248+
teardown_test(
249+
test,
250+
vm,
251+
vm_name,
252+
snap_type == "external",
253+
copied_path,
254+
original_path,
255+
bkxml,
256+
)

0 commit comments

Comments
 (0)