Skip to content

Commit 89e90b5

Browse files
committed
add "dump memory file from guest" feature
- Add guest_dump_file_manager.py - Add dump_file.py - Update env_process.py Add "dump memory file" step in postprocess_vm function The guest_dump_file_manager.py implements the main function The dump_file.py implements "prepare the environment" Signed-off-by: Houqi (Nick) Zuo <hzuo@redhat.com>
1 parent 7b5b0ef commit 89e90b5

3 files changed

Lines changed: 342 additions & 0 deletions

File tree

virttest/env_process.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
cpu,
2525
data_dir,
2626
error_context,
27+
guest_dump_file_manager,
2728
libvirt_version,
2829
png_utils,
2930
ppm_utils,
@@ -45,6 +46,7 @@
4546
from virttest._wrappers import lazy_import
4647
from virttest.test_setup.aexpect import KillTailThreads
4748
from virttest.test_setup.core import SetupManager
49+
from virttest.test_setup.dump_file import DumpFileSetup
4850
from virttest.test_setup.gcov import ResetQemuGCov
4951
from virttest.test_setup.kernel import KSMSetup, ReloadKVMModules
5052
from virttest.test_setup.libvirt_setup import LibvirtdDebugLogConfig
@@ -533,6 +535,13 @@ def postprocess_image(test, params, image_name, vm_process_status=None):
533535
base_dir = params.get("images_base_dir", data_dir.get_data_dir())
534536
image = None
535537

538+
_mgr = guest_dump_file_manager.get_dump_file_mgr(params.get("vm_type"))
539+
if _mgr:
540+
image = (
541+
qemu_storage.QemuImg(params, base_dir, image_name) if not image else image
542+
)
543+
_mgr.run(image.image_filename, test.outputdir)
544+
536545
if params.get("img_check_failed") == "yes":
537546
image = (
538547
qemu_storage.QemuImg(params, base_dir, image_name) if not image else image
@@ -1029,6 +1038,7 @@ def preprocess(test, params, env):
10291038
_setup_manager.register(TransparentHugePagesSetup)
10301039
_setup_manager.register(KSMSetup)
10311040
_setup_manager.register(EGDSetup)
1041+
_setup_manager.register(DumpFileSetup)
10321042
_setup_manager.do_setup()
10331043

10341044
vm_type = params.get("vm_type")
Lines changed: 297 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,297 @@
1+
# This program is free software; you can redistribute it and/or modify
2+
# it under the terms of the GNU General Public License as published by
3+
# the Free Software Foundation; either version 2 of the License, or
4+
# (at your option) any later version.
5+
#
6+
# This program is distributed in the hope that it will be useful,
7+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
8+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
9+
#
10+
# See LICENSE for more details.
11+
#
12+
# Copyright: Red Hat Inc. 2026
13+
# Authors: Houqi (Nick) Zuo <hzuo@redhat.com>
14+
15+
import logging
16+
import os
17+
18+
from avocado.utils import linux_modules, process
19+
20+
from virttest import utils_misc
21+
22+
LOG = logging.getLogger(__name__)
23+
24+
25+
class GuestDumpFileManager(object):
26+
"""
27+
Base class for managing dump files from guest.
28+
29+
This class provides the basic structure and common functionality
30+
for extracting dump files from guest disk images.
31+
"""
32+
33+
def __init__(self):
34+
"""
35+
Initialize the GuestDumpFileManager.
36+
37+
Sets up the mount point, NBD device variables, guest type,
38+
partition information, filesystem support mapping, and
39+
directories to scan for dump files.
40+
"""
41+
self._mount_pt = "/mnt/guest"
42+
self._nbd_dev = "" # nbd15
43+
self._nbd_dev_partition_path = "" # nbd15p3
44+
self._guest_type = "" # Linux or Windows
45+
self._partition_list = []
46+
self._partition_fstype = ""
47+
self._fs_support_mapping = {
48+
"ntfs": self._ntfs,
49+
"LVM2_member": self._lvm2_member,
50+
}
51+
self._dmp_dirs_scaned = {
52+
"Windows": [
53+
"%s/Windows" % self._mount_pt,
54+
"%s/Windows/Minidump" % self._mount_pt,
55+
"%s/Windows/Temp" % self._mount_pt,
56+
"%s/Windows/LiveKernelReports" % self._mount_pt,
57+
"%s/Windows/ProgramData/Microsoft/Windows/WER" % self._mount_pt,
58+
],
59+
"Linux": [],
60+
}
61+
62+
def run(self, image_path, res_dir):
63+
"""
64+
Execute the dump file collection process.
65+
66+
This method should be implemented by subclasses to perform
67+
the complete workflow of mounting the image and extracting dump files.
68+
69+
:param image_path: Path to the guest disk image.
70+
:type image_path: str
71+
:param res_dir: Directory where results should be stored.
72+
:type res_dir: str
73+
"""
74+
raise NotImplementedError
75+
76+
def _ntfs(self):
77+
"""
78+
Get the filesystem type for NTFS partitions.
79+
80+
:return: String representing the NTFS filesystem type ("ntfs-3g").
81+
"""
82+
# NOTE:
83+
# ntfs-3g permission means rw
84+
# ntfs permission means read-only
85+
return "ntfs-3g"
86+
87+
def _lvm2_member(self):
88+
"""
89+
Get the filesystem type for LVM2 partitions.
90+
91+
Note: LVM2_member cannot be mounted directly and requires
92+
additional steps to access the logical volumes.
93+
94+
:return: Empty string (not yet implemented).
95+
"""
96+
# The lvm2_member type (guest type is Linux) is generated by vt default
97+
# TODO: Support the linux guest. lvm2_member can NOT be used directly. And some steps are needed.
98+
return ""
99+
100+
101+
class QemuDumpFileManager(GuestDumpFileManager):
102+
"""
103+
Dump file manager for QEMU/KVM virtual machine.
104+
105+
This class implements the dump file extraction process for
106+
QEMU disk images using NBD (Network Block Device).
107+
"""
108+
109+
def __init__(self):
110+
"""
111+
Initialize the QemuDumpFileManager.
112+
113+
Sets up the command for finding available NBD devices.
114+
"""
115+
super(QemuDumpFileManager, self).__init__()
116+
self._nbd_dev_cmd = "lsblk --output NAME,SIZE,MOUNTPOINT --noheadings"
117+
118+
def _process_dump_file_from_guest(self, res_dir):
119+
"""
120+
Process dump files from the mounted guest disk filesystem.
121+
122+
Searches for dump files in predefined directories and moves
123+
them to the results directory.
124+
125+
:param res_dir: Directory where results should be stored.
126+
:type res_dir: str
127+
"""
128+
_dirs = " ".join(self._dmp_dirs_scaned[self._guest_type])
129+
_cmd = 'find %s -maxdepth 1 -type f -iname "*.dmp" 2>/dev/null' % _dirs
130+
_find_dmp_in_path = process.run(_cmd).stdout_text
131+
if _find_dmp_in_path:
132+
_find_dmp_in_path = _find_dmp_in_path.strip().splitlines()
133+
res_dir = os.path.join(res_dir, "dump_file_from_guest")
134+
if not os.path.exists(res_dir):
135+
os.makedirs(res_dir)
136+
for _path in _find_dmp_in_path:
137+
process.run("mv %s %s/" % (_path, res_dir))
138+
139+
def run(self, image_path, res_dir):
140+
"""
141+
Execute the dump file collection process for qcow2 images.
142+
143+
The process includes:
144+
1. Finding an available NBD device
145+
2. Detecting partitions on the NBD device
146+
3. Determining guest type and appropriate partition
147+
4. Detecting filesystem type
148+
5. Mounting the partition
149+
6. Processing dump files
150+
7. Cleanup (unmounting and disconnecting NBD)
151+
152+
:param image_path: Path to the QEMU disk image.
153+
:type image_path: str
154+
:param res_dir: Directory where results should be stored.
155+
:type res_dir: str
156+
"""
157+
# Choose the nbd device (Such as nbd15, nbd14)
158+
_output = process.run(self._nbd_dev_cmd).stdout_text
159+
for row in _output.strip().splitlines()[::-1]:
160+
if "nbd" in row and "0B" in row and "/" not in row:
161+
self._nbd_dev = row.split()[0]
162+
break
163+
LOG.debug("Find the available nbd device: %s", self._nbd_dev)
164+
165+
try:
166+
qemu_nbd_cmd = "qemu-nbd -c /dev/%s %s" % (
167+
self._nbd_dev,
168+
image_path,
169+
)
170+
process.run(qemu_nbd_cmd)
171+
LOG.debug("Connect the nbd device and image: %s", image_path)
172+
173+
# Detect the partition list (such as ["/dev/nba15p1", "/dev/nba15p2", "/dev/nba15p3"] )
174+
self._partition_list = (
175+
process.run("find /dev -name \"%sp*\"" % self._nbd_dev)
176+
.stdout_text.strip()
177+
.split()
178+
)
179+
LOG.debug("Detect the nbd partition list: %s", self._partition_list)
180+
181+
# Detect the guest type( Linux or Windows ) and partition
182+
_partition = "/dev/" + self._nbd_dev + "p" + str(len(self._partition_list))
183+
_mapping = {"Microsoft basic data": "Windows", "Linux LVM": "Linux"}
184+
for des, platform in _mapping.items():
185+
_output = (
186+
process.run("fdisk -l /dev/%s" % self._nbd_dev)
187+
.stdout_text.strip()
188+
.splitlines()
189+
)
190+
for row in _output:
191+
if des in row:
192+
self._guest_type = platform
193+
self._nbd_dev_partition_path = row.split()[0]
194+
195+
LOG.debug("Detect the guest type: %s", self._guest_type)
196+
197+
# Detect the filesystem
198+
self._partition_fstype = process.run(
199+
"blkid -o value -s TYPE %s" % self._nbd_dev_partition_path
200+
).stdout_text
201+
LOG.debug("Detect the guest fs type: %s", self._partition_fstype)
202+
203+
if not os.path.exists(self._mount_pt):
204+
os.makedirs(self._mount_pt)
205+
206+
# mount the nbd device partition path
207+
utils_misc.mount(
208+
"%s" % self._nbd_dev_partition_path,
209+
self._mount_pt,
210+
self._fs_support_mapping[self._partition_fstype](),
211+
)
212+
self._process_dump_file_from_guest(res_dir)
213+
finally:
214+
try:
215+
utils_misc.umount(
216+
"%s" % self._nbd_dev_partition_path, self._mount_pt, None
217+
)
218+
except Exception as e:
219+
LOG.debug("Failed to unmount: %s", e)
220+
221+
try:
222+
process.run("qemu-nbd -d /dev/%s" % self._nbd_dev)
223+
except Exception as e:
224+
LOG.debug("Failed to disconnect NBD: %s", e)
225+
226+
227+
class LibvirtDumpFileManager(GuestDumpFileManager):
228+
"""
229+
Dump file manager for libvirt virtual machines.
230+
231+
This class is a placeholder for future implementation of
232+
dump file extraction for libvirt-managed VMs.
233+
"""
234+
235+
def __init__(self):
236+
"""
237+
Initialize the LibvirtDumpFileManager.
238+
"""
239+
super(LibvirtDumpFileManager, self).__init__()
240+
241+
def run(self, image_path, res_dir):
242+
"""
243+
Execute dump file collection for libvirt.
244+
245+
This method is not yet implemented.
246+
247+
:param image_path: Path to the guest disk image.
248+
:type image_path: str
249+
:param res_dir: Directory where results should be stored.
250+
:type res_dir: str
251+
"""
252+
pass
253+
254+
255+
class DumpFileManagerFactory(object):
256+
"""
257+
Factory class for creating dump file manager instances.
258+
259+
Implements the singleton pattern to ensure only one instance
260+
of each manager type is created.
261+
"""
262+
263+
_GDF_MGR_INS = None
264+
265+
@classmethod
266+
def get_mgr(cls, vm_type):
267+
"""
268+
Get the dump file manager instance for the specified VM type.
269+
270+
:param vm_type: Type of virtual machine ("qemu" or "libvirt").
271+
:type vm_type: str
272+
:return: Instance of the appropriate dump file manager.
273+
:raises ValueError: If the VM type is not supported.
274+
"""
275+
if not cls._GDF_MGR_INS:
276+
_type_mgr_mapping = {
277+
"qemu": QemuDumpFileManager,
278+
"libvirt": LibvirtDumpFileManager,
279+
}
280+
if vm_type not in _type_mgr_mapping:
281+
raise ValueError(f"Unsupported vm_type: {vm_type}")
282+
cls._GDF_MGR_INS = _type_mgr_mapping[vm_type]()
283+
return cls._GDF_MGR_INS
284+
285+
286+
def get_dump_file_mgr(vm_type):
287+
"""
288+
Get dump file manager instance for the specified VM type.
289+
290+
This is a convenience function that delegates to the
291+
DumpFileManagerFactory.
292+
293+
:param vm_type: Type of virtual machine ("qemu" or "libvirt").
294+
:type vm_type: str
295+
:return: Instance of the appropriate dump file manager.
296+
"""
297+
return DumpFileManagerFactory.get_mgr(vm_type)

virttest/test_setup/dump_file.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import logging
2+
3+
from avocado.utils import linux_modules
4+
5+
from virttest import utils_package
6+
from virttest.test_setup.core import Setuper
7+
8+
LOG = logging.getLogger(__name__)
9+
10+
11+
class DumpFileSetup(Setuper):
12+
_nbd_is_loaded_by_vt = False
13+
14+
def setup(self):
15+
# "qemu-nbd" is required by qemu in vm_type.
16+
# "ntfs-3g" is required by ntfs filesystem.
17+
# TODO: add the required packages by libvirt.
18+
_pkg = ["ntfs-3g"]
19+
try:
20+
utils_package.package_install(_pkg)
21+
22+
if not linux_modules.module_is_loaded("nbd"):
23+
linux_modules.load_module("nbd")
24+
self._nbd_is_loaded_by_vt = True
25+
26+
LOG.debug("The dump_file is set up successfully.")
27+
except Exception as e:
28+
LOG.error("The dump_file failed to set up.", exc_info=e)
29+
30+
def cleanup(self):
31+
if self._nbd_is_loaded_by_vt:
32+
linux_modules.unload_module("nbd")
33+
LOG.debug("The dump_file is cleanup successfully.")
34+
else:
35+
LOG.debug("Skip the dump_file cleanup.")

0 commit comments

Comments
 (0)