KVM test: use new monitor interface
- Add new monitor definition syntax that allows definition of multiple
monitors.
Monitors are now defined like other objects in the config file:
monitors = MyMonitor SomeOtherMonitor YetAnotherMonitor # defines 3 monitors
monitor_type = human # default for all monitors
monitor_type_SomeOtherMonitor = qmp # applies only to SomeOtherMonitor
monitor_type_YetAnotherMonitor = qmp # applies only to YetAnotherMonitor
main_monitor = MyMonitor # defines the main monitor to use
# in the test
- Use the new syntax in tests_base.cfg.sample.
- Establish monitor connections using kvm_monitor in VM.create().
Store all monitors in self.monitors. Store main monitor in
self.monitor.
- Replace calls to send_monitor_cmd() with appropriate calls to methods
of self.monitor (the main monitor).
- For now, ignore the parameter screendump_verbose because currently
monitor commands are always silent (when successful).
Changes from v2:
- Rebased against the latest upstream
- Corrected a few bugs left on tests
Changes from v1:
- Turn VM.monitor into a property
Signed-off-by: Michael Goldish <mgoldish@redhat.com>
Signed-off-by: Lucas Meneghel Rodrigues <lmr@redhat.com>
git-svn-id: http://test.kernel.org/svn/autotest/trunk@4639 592f7852-d20e-0410-864c-8624ca9c26a4
diff --git a/client/tests/kvm/kvm_vm.py b/client/tests/kvm/kvm_vm.py
index ed8e7a3..cb4666b 100755
--- a/client/tests/kvm/kvm_vm.py
+++ b/client/tests/kvm/kvm_vm.py
@@ -5,8 +5,8 @@
@copyright: 2008-2009 Red Hat Inc.
"""
-import time, socket, os, logging, fcntl, re, commands
-import kvm_utils, kvm_subprocess
+import time, socket, os, logging, fcntl, re, commands, glob
+import kvm_utils, kvm_subprocess, kvm_monitor
from autotest_lib.client.common_lib import error
from autotest_lib.client.bin import utils
@@ -109,24 +109,22 @@
self.redirs = {}
self.vnc_port = 5900
self.uuid = None
+ self.monitors = []
+ self.pci_assignable = None
self.name = name
self.params = params
self.root_dir = root_dir
self.address_cache = address_cache
- self.pci_assignable = None
self.netdev_id = []
for nic in params.get("nics").split():
self.netdev_id.append(kvm_utils.generate_random_string(4))
- # Find available monitor filename
+ # Find a unique identifier for this VM
while True:
- # The monitor filename should be unique
self.instance = (time.strftime("%Y%m%d-%H%M%S-") +
kvm_utils.generate_random_string(4))
- self.monitor_file_name = os.path.join("/tmp",
- "monitor-" + self.instance)
- if not os.path.exists(self.monitor_file_name):
+ if not glob.glob("/tmp/*%s" % self.instance):
break
@@ -202,9 +200,12 @@
def add_name(help, name):
return " -name '%s'" % name
- def add_unix_socket_monitor(help, filename):
+ def add_human_monitor(help, filename):
return " -monitor unix:'%s',server,nowait" % filename
+ def add_qmp_monitor(help, filename):
+ return " -qmp unix:'%s',server,nowait" % filename
+
def add_mem(help, mem):
return " -m %s" % mem
@@ -304,8 +305,14 @@
qemu_cmd += qemu_binary
# Add the VM's name
qemu_cmd += add_name(help, name)
- # Add the monitor socket parameter
- qemu_cmd += add_unix_socket_monitor(help, self.monitor_file_name)
+ # Add monitors
+ for monitor_name in kvm_utils.get_sub_dict_names(params, "monitors"):
+ monitor_params = kvm_utils.get_sub_dict(params, monitor_name)
+ monitor_filename = self.get_monitor_filename(monitor_name)
+ if monitor_params.get("monitor_type") == "qmp":
+ qemu_cmd += add_qmp_monitor(help, monitor_filename)
+ else:
+ qemu_cmd += add_human_monitor(help, monitor_filename)
for image_name in kvm_utils.get_sub_dict_names(params, "images"):
image_params = kvm_utils.get_sub_dict(params, image_name)
@@ -565,6 +572,7 @@
self.process = kvm_subprocess.run_bg(qemu_command, None,
logging.debug, "(qemu) ")
+ # Make sure the process was started successfully
if not self.process.is_alive():
logging.error("VM could not be created; "
"qemu command failed:\n%s" % qemu_command)
@@ -574,11 +582,36 @@
self.destroy()
return False
- if not kvm_utils.wait_for(self.is_alive, timeout, 0, 1):
- logging.error("VM is not alive for some reason; "
- "qemu command:\n%s" % qemu_command)
- self.destroy()
- return False
+ # Establish monitor connections
+ self.monitors = []
+ for monitor_name in kvm_utils.get_sub_dict_names(params,
+ "monitors"):
+ monitor_params = kvm_utils.get_sub_dict(params, monitor_name)
+ # Wait for monitor connection to succeed
+ end_time = time.time() + timeout
+ while time.time() < end_time:
+ try:
+ if monitor_params.get("monitor_type") == "qmp":
+ # Add a QMP monitor: not implemented yet
+ monitor = None
+ else:
+ # Add a "human" monitor
+ monitor = kvm_monitor.HumanMonitor(
+ monitor_name,
+ self.get_monitor_filename(monitor_name))
+ except kvm_monitor.MonitorError, e:
+ logging.warn(e)
+ else:
+ if monitor and monitor.is_responsive():
+ break
+ time.sleep(1)
+ else:
+ logging.error("Could not connect to monitor '%s'" %
+ monitor_name)
+ self.destroy()
+ return False
+ # Add this monitor to the list
+ self.monitors += [monitor]
# Get the output so far, to see if we have any problems with
# hugepage setup.
@@ -600,90 +633,6 @@
lockfile.close()
- def send_monitor_cmd(self, command, block=True, timeout=20.0, verbose=True):
- """
- Send command to the QEMU monitor.
-
- Connect to the VM's monitor socket and wait for the (qemu) prompt.
- If block is True, read output from the socket until the (qemu) prompt
- is found again, or until timeout expires.
-
- Return a tuple containing an integer indicating success or failure,
- and the data read so far. The integer is 0 on success and 1 on failure.
- A failure is any of the following cases: connection to the socket
- failed, or the first (qemu) prompt could not be found, or block is
- True and the second prompt could not be found.
-
- @param command: Command that will be sent to the monitor
- @param block: Whether the output from the socket will be read until
- the timeout expires
- @param timeout: Timeout (seconds) before giving up on reading from
- socket
- """
- def read_up_to_qemu_prompt(s, timeout):
- """
- Read data from socket s until the (qemu) prompt is found.
-
- If the prompt is found before timeout expires, return a tuple
- containing True and the data read. Otherwise return a tuple
- containing False and the data read so far.
-
- @param s: Socket object
- @param timeout: Time (seconds) before giving up trying to get the
- qemu prompt.
- """
- o = ""
- end_time = time.time() + timeout
- while time.time() < end_time:
- try:
- o += s.recv(1024)
- if o.splitlines()[-1].split()[-1] == "(qemu)":
- return (True, o)
- except:
- time.sleep(0.01)
- return (False, o)
-
- # In certain conditions printing this debug output might be too much
- # Just print it if verbose is enabled (True by default)
- if verbose:
- logging.debug("Sending monitor command: %s" % command)
- # Connect to monitor
- try:
- s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
- s.setblocking(False)
- s.connect(self.monitor_file_name)
- except:
- logging.debug("Could not connect to monitor socket")
- return (1, "")
-
- # Send the command and get the resulting output
- try:
- status, data = read_up_to_qemu_prompt(s, timeout)
- if not status:
- logging.debug("Could not find (qemu) prompt; output so far:" +
- kvm_utils.format_str_for_message(data))
- return (1, "")
- # Send command
- s.sendall(command + "\n")
- # Receive command output
- data = ""
- if block:
- status, data = read_up_to_qemu_prompt(s, timeout)
- data = "\n".join(data.splitlines()[1:])
- if not status:
- logging.debug("Could not find (qemu) prompt after command; "
- "output so far:" +
- kvm_utils.format_str_for_message(data))
- return (1, data)
- data = "".join(data.rstrip().splitlines(True)[:-1])
- return (0, data)
-
- # Clean up before exiting
- finally:
- s.shutdown(socket.SHUT_RDWR)
- s.close()
-
-
def destroy(self, gracefully=True):
"""
Destroy the VM.
@@ -720,15 +669,18 @@
finally:
session.close()
- # Try to destroy with a monitor command
- logging.debug("Trying to kill VM with monitor command...")
- status, output = self.send_monitor_cmd("quit", block=False)
- # Was the command sent successfully?
- if status == 0:
- # Wait for the VM to be really dead
- if kvm_utils.wait_for(self.is_dead, 5, 0.5, 0.5):
- logging.debug("VM is down")
- return
+ if self.monitor:
+ # Try to destroy with a monitor command
+ logging.debug("Trying to kill VM with monitor command...")
+ try:
+ self.monitor.quit()
+ except kvm_monitor.MonitorError, e:
+ logging.warn(e)
+ else:
+ # Wait for the VM to be really dead
+ if kvm_utils.wait_for(self.is_dead, 5, 0.5, 0.5):
+ logging.debug("VM is down")
+ return
# If the VM isn't dead yet...
logging.debug("Cannot quit normally; sending a kill to close the "
@@ -742,28 +694,43 @@
logging.error("Process %s is a zombie!" % self.process.get_pid())
finally:
+ self.monitors = []
if self.pci_assignable:
self.pci_assignable.release_devs()
if self.process:
self.process.close()
- try:
- os.unlink(self.monitor_file_name)
- except OSError:
- pass
+ for f in ([self.get_testlog_filename()] +
+ self.get_monitor_filenames()):
+ try:
+ os.unlink(f)
+ except OSError:
+ pass
+
+
+ @property
+ def monitor(self):
+ """
+ Return the main monitor object, selected by the parameter main_monitor.
+ If main_monitor isn't defined, return the first monitor.
+ If no monitors exist, or if main_monitor refers to a nonexistent
+ monitor, return None.
+ """
+ for m in self.monitors:
+ if m.name == self.params.get("main_monitor"):
+ return m
+ if self.monitors and not self.params.get("main_monitor"):
+ return self.monitors[0]
def is_alive(self):
"""
- Return True if the VM's monitor is responsive.
+ Return True if the VM is alive and its monitor is responsive.
"""
# Check if the process is running
if self.is_dead():
return False
# Try sending a monitor command
- (status, output) = self.send_monitor_cmd("help")
- if status:
- return False
- return True
+ return bool(self.monitor) and self.monitor.is_responsive()
def is_dead(self):
@@ -789,6 +756,29 @@
return self.params
+ def get_monitor_filename(self, monitor_name):
+ """
+ Return the filename corresponding to a given monitor name.
+ """
+ return "/tmp/monitor-%s-%s" % (monitor_name, self.instance)
+
+
+ def get_monitor_filenames(self):
+ """
+ Return a list of all monitor filenames (as specified in the VM's
+ params).
+ """
+ return [self.get_monitor_filename(m) for m in
+ kvm_utils.get_sub_dict_names(self.params, "monitors")]
+
+
+ def get_testlog_filename(self):
+ """
+ Return the testlog filename.
+ """
+ return "/tmp/testlog-%s" % self.instance
+
+
def get_address(self, index=0):
"""
Return the address of a NIC of the guest, in host space.
@@ -986,12 +976,12 @@
# For compatibility with versions of QEMU that do not recognize all
# key names: replace keyname with the hex value from the dict, which
# QEMU will definitely accept
- dict = { "comma": "0x33",
- "dot": "0x34",
- "slash": "0x35" }
- for key in dict.keys():
- keystr = keystr.replace(key, dict[key])
- self.send_monitor_cmd("sendkey %s 1" % keystr)
+ dict = {"comma": "0x33",
+ "dot": "0x34",
+ "slash": "0x35"}
+ for key, value in dict.items():
+ keystr = keystr.replace(key, value)
+ self.monitor.sendkey(keystr)
time.sleep(0.2)