Before upstream qemu commit
commit 56c4bfb3f07f3107894c00281276aea4f5e8834d
Author: Laszlo Ersek <[email protected]>
Date: Tue Aug 6 12:37:11 2013 +0200
dump: rebase from host-private RAMBlock offsets to guest-physical
addresses
the dump-guest-memory QMP command used to write incorrect vmcores for
x86_64 guests with more than 3.5GB RAM. Add a testcase that creates a 4GB
guest, dumps its core, copies it into the guest, and analyzes it there
with the "crash" utility.
Related RHBZ: https://bugzilla.redhat.com/show_bug.cgi?id=981582
Related RHBZ: https://bugzilla.redhat.com/show_bug.cgi?id=990118
Suggested-by: Ademar de Souza Reis Jr. <[email protected]>
Signed-off-by: Laszlo Ersek <[email protected]>
---
qemu/tests/cfg/guest_memory_dump_analysis.cfg | 7 +
qemu/tests/guest_memory_dump_analysis.py | 293 +++++++++++++++++++++++++
2 files changed, 300 insertions(+), 0 deletions(-)
create mode 100644 qemu/tests/cfg/guest_memory_dump_analysis.cfg
create mode 100644 qemu/tests/guest_memory_dump_analysis.py
diff --git a/qemu/tests/cfg/guest_memory_dump_analysis.cfg
b/qemu/tests/cfg/guest_memory_dump_analysis.cfg
new file mode 100644
index 0000000..c9572d0
--- /dev/null
+++ b/qemu/tests/cfg/guest_memory_dump_analysis.cfg
@@ -0,0 +1,7 @@
+- guest_memory_dump_analysis: install setup image_copy unattended_install.cdrom
+ only x86_64
+ only JeOS, Fedora, RHEL
+ type = guest_memory_dump_analysis
+ mem = 4096
+ monitors = qmp1
+ monitor_type = qmp
diff --git a/qemu/tests/guest_memory_dump_analysis.py
b/qemu/tests/guest_memory_dump_analysis.py
new file mode 100644
index 0000000..8f4bf53
--- /dev/null
+++ b/qemu/tests/guest_memory_dump_analysis.py
@@ -0,0 +1,293 @@
+"""
+Integrity test of a big guest vmcore, using the dump-guest-memory QMP
+command and the "crash" utility.
+
+@copyright: 2013 Red Hat, Inc.
+@author: Laszlo Ersek <[email protected]>
+
+Related RHBZ: https://bugzilla.redhat.com/show_bug.cgi?id=990118
+"""
+
+import logging
+from virttest.aexpect import ShellCmdError
+from autotest.client.shared import error
+import string
+import os
+import gzip
+import threading
+
+REQ_GUEST_MEM = 4096 # exact size of guest RAM required
+REQ_GUEST_ARCH = "x86_64" # the only supported guest arch
+REQ_GUEST_DF = 6144 # minimum guest disk space required
+ # after package installation
+LONG_TIMEOUT = 10*60 # timeout for long operations
+VMCORE_BASE = "vmcore" # basename of the host-side file the
+ # guest vmcore is written to, .gz
+ # suffix will be appended. No
+ # metacharacters or leading dashes
+ # please.
+VMCORE_FD_NAME = "vmcore_fd" # fd identifier used in the monitor
+CRASH_SCRIPT = "crash.cmd" # guest-side filename of the minimal
+ # crash script
+
+def run_guest_memory_dump_analysis(test, params, env):
+ """
+ Verify the vmcore written by dump-guest-memory for a big guests.
+
+ @param test: QEMU test object.
+ @param params: Dictionary with the test parameters.
+ @param env: Dictionary with test environment.
+ """
+ def check_requirements(vm, session):
+ """
+ Check guest RAM size and guest architecture.
+
+ @param vm: virtual machine.
+ @param session: login shell session.
+ @raise: error.TestError if the test is misconfigured.
+ """
+ mem_size = vm.get_memory_size()
+ if (mem_size != REQ_GUEST_MEM):
+ raise error.TestError("the guest must have %d MB RAM exactly "
+ "(current: %d MB)" % (REQ_GUEST_MEM,
+ mem_size))
+ arch = session.cmd("uname -m").rstrip()
+ if (arch != REQ_GUEST_ARCH):
+ raise error.TestError("this test only supports %s guests "
+ "(current: %s)" % (REQ_GUEST_ARCH, arch))
+
+ def install_kernel_debuginfo(vm, session, login_timeout):
+ """
+ In the guest, install a kernel debuginfo package that matches
+ the running kernel.
+
+ Debuginfo packages are available for the most recent kernels
+ only, so this step may need a kernel upgrade and a corresponding
+ VM reboot. Also, the "debuginfo-install" yum utility is not good
+ enough for this, because its exit status doesn't seem to reflect
+ any failure to find a matching debuginfo package. Only "yum
+ install" seems to do that, and only if an individual package is
+ requested.
+
+ @param vm: virtual machine. Can be None if the caller demands a
+ debuginfo package for the running kernel.
+ @param session: login shell session.
+ @param login_timeout: passed to vm.reboot() as timeout. Can be
+ None if vm is None.
+ @return: If the debuginfo package has been successfully
+ installed, None is returned. If no debuginfo package matching
+ the running guest kernel is available: if vm is None, an
+ exception is raised; otherwise, the guest kernel is upgraded,
+ and a new session is returned for the rebooted guest. In this
+ case the next call to this function should succeed, using the
+ new session and with vm=None.
+ @raise: error.TestError (guest uname command failed),
+ ShellCmdError (unexpected guest yum command failure), exceptions
+ from vm.reboot().
+ """
+ def install_matching_debuginfo(session):
+ try:
+ guest_kernel = session.cmd("uname -r").rstrip()
+ except ShellCmdError, details:
+ raise error.TestError("guest uname command failed: %s" %
+ details)
+ return session.cmd("yum -y install --enablerepo='*debuginfo' "
+ "kernel-debuginfo-%s" % guest_kernel,
+ timeout=LONG_TIMEOUT)
+
+ try:
+ output = install_matching_debuginfo(session)
+ logging.debug("%s", output)
+ new_sess = None
+ except ShellCmdError, details:
+ if (vm is None):
+ raise
+ logging.info("failed to install matching debuginfo, "
+ "upgrading kernel")
+ logging.debug("shell error was: %s", details)
+ output = session.cmd("yum -y upgrade kernel",
+ timeout=LONG_TIMEOUT)
+ logging.debug("%s", output)
+ new_sess = vm.reboot(session, timeout=login_timeout)
+ return new_sess
+
+ def install_crash(session):
+ """
+ Install the "crash" utility in the guest.
+
+ @param session: login shell session.
+ @raise: exceptions from session.cmd().
+ """
+ output = session.cmd("yum -y install crash")
+ logging.debug("%s", output)
+
+ def check_disk_space(session):
+ """
+ Check free disk space in the guest before uploading,
+ uncompressing and analyzing the vmcore.
+
+ @param session: login shell session.
+ @raise: exceptions from session.cmd(); error.TestError if free
+ space is insufficient.
+ """
+ output = session.cmd("rm -f -v %s %s.gz" % (VMCORE_BASE, VMCORE_BASE))
+ logging.debug("%s", output)
+ output = session.cmd("yum clean all")
+ logging.debug("%s", output)
+ output = session.cmd("LC_ALL=C df --portability --block-size=1M .")
+ logging.debug("%s", output)
+ df_megs = int(string.split(output)[10])
+ if (df_megs < REQ_GUEST_DF):
+ raise error.TestError("insufficient free disk space: %d < %d" %
+ (df_megs, REQ_GUEST_DF))
+
+ def dump_and_compress(qmp_monitor, vmcore_host):
+ """
+ Dump the guest vmcore on the host side and compress it.
+
+ Use the "dump-guest-memory" QMP command with paging=false. Start
+ a new Python thread that compresses data from a file descriptor
+ to a host file. Create a pipe and pass its writeable end to qemu
+ for vmcore dumping. Pass the pipe's readable end (with full
+ ownership) to the compressor thread. Track references to the
+ file descriptions underlying the pipe end fds carefully.
+
+ Compressing the vmcore on the fly, then copying it to the guest,
+ then decompressing it inside the guest should be much faster
+ than dumping and copying a huge plaintext vmcore, especially on
+ rotational media.
+
+ @param qmp_monitor: QMP monitor for the guest.
+ @param vmcore_host: absolute pathname of gzipped destination
+ file.
+ @raise: all sorts of exceptions. No resources should be leaked.
+ """
+ def compress_from_fd(input_fd, gzfile):
+ # Run in a separate thread, take ownership of input_fd.
+ try:
+ buf = os.read(input_fd, 4096)
+ while (buf):
+ gzfile.write(buf)
+ buf = os.read(input_fd, 4096)
+ finally:
+ # If we've run into a problem, this causes an EPIPE in
+ # the qemu process, preventing it from blocking in
+ # write() forever.
+ os.close(input_fd)
+
+ def dump_vmcore(qmp_monitor, vmcore_fd):
+ # Temporarily create another reference to vmcore_fd, in the
+ # qemu process. We own the duplicate.
+ qmp_monitor.cmd(cmd="getfd",
+ args={"fdname": "%s" % VMCORE_FD_NAME},
+ fd=vmcore_fd)
+ try:
+ # Includes ownership transfer on success, no need to
+ # call the "closefd" command then.
+ qmp_monitor.cmd(cmd="dump-guest-memory",
+ args={"paging": False,
+ "protocol": "fd:%s" % VMCORE_FD_NAME},
+ timeout=LONG_TIMEOUT)
+ except:
+ qmp_monitor.cmd(cmd="closefd",
+ args={"fdname": "%s" % VMCORE_FD_NAME})
+ raise
+
+ gzfile = gzip.open(vmcore_host, "wb", 1)
+ try:
+ try:
+ (read_by_gzip, written_by_qemu) = os.pipe()
+ try:
+ compressor = threading.Thread(target=compress_from_fd,
+ name="compressor",
+ args=(read_by_gzip, gzfile))
+ compressor.start()
+ # Compressor running, ownership of readable end has
+ # been transferred.
+ read_by_gzip = -1
+ try:
+ dump_vmcore(qmp_monitor, written_by_qemu)
+ finally:
+ # Close Python's own reference to the writeable
+ # end as well, so that the compressor can
+ # experience EOF before we try to join it.
+ os.close(written_by_qemu)
+ written_by_qemu = -1
+ compressor.join()
+ finally:
+ if (read_by_gzip != -1):
+ os.close(read_by_gzip)
+ if (written_by_qemu != -1):
+ os.close(written_by_qemu)
+ finally:
+ # Close the gzipped file first, *then* delete it if
+ # there was an error.
+ gzfile.close()
+ except:
+ os.unlink(vmcore_host)
+ raise
+
+ def verify_vmcore(vm, session, host_compr, guest_compr, guest_plain):
+ """
+ Verify the vmcore with the "crash" utility in the guest.
+
+ Standard output needs to be searched for "crash:" and "WARNING:"
+ strings; the test is successful iff there are no matches and
+ "crash" exits successfully.
+
+ @param vm: virtual machine.
+ @param session: login shell session.
+ @param host_compr: absolute pathname of gzipped vmcore on host,
+ source file.
+ @param guest_compr: single-component filename of gzipped vmcore
+ on guest, destination file.
+ @param guest_plain: single-component filename of gunzipped
+ vmcore on guest that guest-side gunzip is expected to create.
+ @raise: vm.copy_files_to() and session.cmd() exceptions;
+ error.TestFail if "crash" meets trouble in the vmcore.
+ """
+ vm.copy_files_to(host_compr, guest_compr)
+ output = session.cmd("gzip -d -v %s" % guest_compr,
+ timeout=LONG_TIMEOUT)
+ logging.debug("%s", output)
+
+ session.cmd("{ echo bt; echo quit; } > %s" % CRASH_SCRIPT)
+ output = session.cmd("crash -i %s "
+ "/usr/lib/debug/lib/modules/$(uname -r)/vmlinux "
+ "%s" % (CRASH_SCRIPT, guest_plain))
+ logging.debug("%s", output)
+ if (string.find(output, "crash:") >= 0 or
+ string.find(output, "WARNING:") >= 0):
+ raise error.TestFail("vmcore corrupt")
+
+ vm = env.get_vm(params["main_vm"])
+ vm.verify_alive()
+
+ qmp_monitor = vm.get_monitors_by_type("qmp")
+ if qmp_monitor:
+ qmp_monitor = qmp_monitor[0]
+ else:
+ raise error.TestError('Could not find a QMP monitor, aborting test')
+
+ login_timeout = int(params.get("login_timeout", 240))
+ session = vm.wait_for_login(timeout=login_timeout)
+ try:
+ check_requirements(vm, session)
+
+ new_sess = install_kernel_debuginfo(vm, session, login_timeout)
+ if (new_sess is not None):
+ session = new_sess
+ install_kernel_debuginfo(None, session, None)
+
+ install_crash(session)
+ check_disk_space(session)
+
+ vmcore_compr = "%s.gz" % VMCORE_BASE
+ vmcore_host = os.path.join(test.tmpdir, vmcore_compr)
+ dump_and_compress(qmp_monitor, vmcore_host)
+ try:
+ verify_vmcore(vm, session, vmcore_host, vmcore_compr, VMCORE_BASE)
+ finally:
+ os.unlink(vmcore_host)
+ finally:
+ session.close()
--
1.7.1
_______________________________________________
Virt-test-devel mailing list
[email protected]
https://www.redhat.com/mailman/listinfo/virt-test-devel