All of lore.kernel.org
 help / color / mirror / Atom feed
* [KVM-AUTOTEST PATCH v2 1/3] KVM test: add kvm_monitor.py, an interface to QEMU monitors
@ 2010-06-14 16:54 Michael Goldish
  2010-06-14 16:55 ` [KVM-AUTOTEST PATCH v2 2/3] KVM test: use new monitor interface Michael Goldish
  2010-06-17 20:40 ` [KVM-AUTOTEST PATCH v2 1/3] KVM test: add kvm_monitor.py, an interface to QEMU monitors Lucas Meneghel Rodrigues
  0 siblings, 2 replies; 4+ messages in thread
From: Michael Goldish @ 2010-06-14 16:54 UTC (permalink / raw)
  To: autotest, kvm; +Cc: Michael Goldish

This module should replace vm.send_monitor_cmd().  Instead of connecting to the
monitor each time a command is issued, this module maintains a continuous
connection to the monitor.  It disconnects when a test terminates and
reconnects as soon as the next test begins (upon unpickling).

It currently contains only an interface to the human monitor.  A QMP interface
will be introduced in a future patch.

Changes from v1:
- Add name parameter to __init__()
- Remove help() method
- Rename help attribute to _help_str to indicate private use
- Rename lock to _lock
- Rename socket to _socket

Signed-off-by: Michael Goldish <mgoldish@redhat.com>
---
 client/tests/kvm/kvm_monitor.py |  354 +++++++++++++++++++++++++++++++++++++++
 1 files changed, 354 insertions(+), 0 deletions(-)
 create mode 100644 client/tests/kvm/kvm_monitor.py

diff --git a/client/tests/kvm/kvm_monitor.py b/client/tests/kvm/kvm_monitor.py
new file mode 100644
index 0000000..27045a4
--- /dev/null
+++ b/client/tests/kvm/kvm_monitor.py
@@ -0,0 +1,354 @@
+"""
+Interfaces to the QEMU monitor.
+
+@copyright: 2008-2010 Red Hat Inc.
+"""
+
+import socket, time, threading, logging
+import kvm_utils
+
+
+class MonitorError(Exception):
+    pass
+
+
+class MonitorConnectError(MonitorError):
+    pass
+
+
+class MonitorSendError(MonitorError):
+    pass
+
+
+class MonitorLockError(MonitorError):
+    pass
+
+
+class MonitorProtocolError(MonitorError):
+    pass
+
+
+class Monitor:
+    """
+    Common code for monitor classes.
+    """
+
+    def __init__(self, name, filename):
+        """
+        Initialize the instance.
+
+        @param name: Monitor identifier (a string)
+        @param filename: Monitor socket filename
+        @raise MonitorConnectError: Raised if the connection fails
+        """
+        self.name = name
+        self.filename = filename
+        self._lock = threading.RLock()
+        self._socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+        self._socket.setblocking(False)
+
+        try:
+            self._socket.connect(filename)
+        except socket.error:
+            raise MonitorConnectError("Could not connect to monitor socket")
+
+
+    def __del__(self):
+        # Automatically close the connection when the instance is garbage
+        # collected
+        try:
+            self._socket.shutdown(socket.SHUT_RDWR)
+        except socket.error:
+            pass
+        self._socket.close()
+
+
+    # The following two functions are defined to make sure the state is set
+    # exclusively by the constructor call as specified in __getinitargs__().
+
+    def __getstate__(self):
+        pass
+
+
+    def __setstate__(self, state):
+        pass
+
+
+    def __getinitargs__(self):
+        # Save some information when pickling -- will be passed to the
+        # constructor upon unpickling
+        return self.name, self.filename, True
+
+
+    def _acquire_lock(self, timeout=20):
+        end_time = time.time() + timeout
+        while time.time() < end_time:
+            if self._lock.acquire(False):
+                return True
+            time.sleep(0.05)
+        return False
+
+
+    def _recvall(self):
+        s = ""
+        while True:
+            try:
+                data = self._socket.recv(1024)
+            except socket.error:
+                break
+            if not data:
+                break
+            s += data
+        return s
+
+
+class HumanMonitor(Monitor):
+    """
+    Wraps "human monitor" commands.
+    """
+
+    def __init__(self, name, filename, suppress_exceptions=False):
+        """
+        Connect to the monitor socket and find the (qemu) prompt.
+
+        @param name: Monitor identifier (a string)
+        @param filename: Monitor socket filename
+        @raise MonitorConnectError: Raised if the connection fails and
+                suppress_exceptions is False
+        @raise MonitorProtocolError: Raised if the initial (qemu) prompt isn't
+                found and suppress_exceptions is False
+        @note: Other exceptions may be raised.  See _get_command_output's
+                docstring.
+        """
+        try:
+            Monitor.__init__(self, name, filename)
+
+            self.protocol = "human"
+
+            # Find the initial (qemu) prompt
+            s, o = self._read_up_to_qemu_prompt(20)
+            if not s:
+                raise MonitorProtocolError("Could not find (qemu) prompt "
+                                           "after connecting to monitor. "
+                                           "Output so far: %r" % o)
+
+            # Save the output of 'help' for future use
+            self._help_str = self._get_command_output("help")
+
+        except MonitorError, e:
+            if suppress_exceptions:
+                logging.warn(e)
+            else:
+                raise
+
+
+    # Private methods
+
+    def _read_up_to_qemu_prompt(self, timeout=20):
+        o = ""
+        end_time = time.time() + timeout
+        while time.time() < end_time:
+            try:
+                data = self._socket.recv(1024)
+                if not data:
+                    break
+                o += data
+                if o.splitlines()[-1].split()[-1] == "(qemu)":
+                    return True, "\n".join(o.splitlines()[:-1])
+            except (socket.error, IndexError):
+                time.sleep(0.01)
+        return False, "\n".join(o.splitlines())
+
+
+    def _send_command(self, command):
+        """
+        Send a command without waiting for output.
+
+        @param command: Command to send
+        @return: True if successful, False otherwise
+        @raise MonitorLockError: Raised if the lock cannot be acquired
+        @raise MonitorSendError: Raised if the command cannot be sent
+        """
+        if not self._acquire_lock(20):
+            raise MonitorLockError("Could not acquire exclusive lock to send "
+                                   "monitor command '%s'" % command)
+
+        try:
+            try:
+                self._socket.sendall(command + "\n")
+            except socket.error:
+                raise MonitorSendError("Could not send monitor command '%s'" %
+                                       command)
+
+        finally:
+            self._lock.release()
+
+
+    def _get_command_output(self, command, timeout=20):
+        """
+        Send command to the monitor.
+
+        @param command: Command to send to the monitor
+        @param timeout: Time duration to wait for the (qemu) prompt to return
+        @return: Output received from the monitor
+        @raise MonitorLockError: Raised if the lock cannot be acquired
+        @raise MonitorSendError: Raised if the command cannot be sent
+        @raise MonitorProtocolError: Raised if the (qemu) prompt cannot be
+                found after sending the command
+        """
+        if not self._acquire_lock(20):
+            raise MonitorLockError("Could not acquire exclusive lock to send "
+                                   "monitor command '%s'" % command)
+
+        try:
+            # Read any data that might be available
+            self._recvall()
+            # Send command
+            self._send_command(command)
+            # Read output
+            s, o = self._read_up_to_qemu_prompt(timeout)
+            # Remove command echo from output
+            o = "\n".join(o.splitlines()[1:])
+            # Report success/failure
+            if s:
+                return o
+            else:
+                msg = ("Could not find (qemu) prompt after command '%s'. "
+                       "Output so far: %r" % (command, o))
+                raise MonitorProtocolError(msg)
+
+        finally:
+            self._lock.release()
+
+
+    # Public methods
+
+    def is_responsive(self):
+        """
+        Make sure the monitor is responsive by sending a command.
+
+        @return: True if responsive, False otherwise
+        """
+        try:
+            self._get_command_output("help")
+            return True
+        except MonitorError:
+            return False
+
+
+    # Command wrappers
+    # Notes:
+    # - All of the following commands raise exceptions in a similar manner to
+    #   cmd() and _get_command_output().
+    # - A command wrapper should use self._help_str if it requires information
+    #   about the monitor's capabilities.
+
+    def cmd(self, command, timeout=20):
+        """
+        Send a simple command with no parameters and return its output.
+        Should only be used for commands that take no parameters and are
+        implemented under the same name for both the human and QMP monitors.
+
+        @param command: Command to send
+        @param timeout: Time duration to wait for (qemu) prompt after command
+        @return: The output of the command
+        @raise MonitorLockError: Raised if the lock cannot be acquired
+        @raise MonitorSendError: Raised if the command cannot be sent
+        @raise MonitorProtocolError: Raised if the (qemu) prompt cannot be
+                found after sending the command
+        """
+        return self._get_command_output(command, timeout)
+
+
+    def quit(self):
+        """
+        Send "quit" without waiting for output.
+        """
+        self._send_command("quit")
+
+
+    def info(self, what):
+        """
+        Request info about something and return the output.
+        """
+        return self._get_command_output("info %s" % what)
+
+
+    def query(self, what):
+        """
+        Alias for info.
+        """
+        return self.info(what)
+
+
+    def screendump(self, filename):
+        """
+        Request a screendump.
+
+        @param filename: Location for the screendump
+        @return: The command's output
+        """
+        return self._get_command_output("screendump %s" % filename)
+
+
+    def migrate(self, uri, full_copy=False, incremental_copy=False, wait=False):
+        """
+        Migrate.
+
+        @param uri: destination URI
+        @param full_copy: If true, migrate with full disk copy
+        @param incremental_copy: If true, migrate with incremental disk copy
+        @param wait: If true, wait for completion
+        @return: The command's output
+        """
+        cmd = "migrate"
+        if not wait:
+            cmd += " -d"
+        if full_copy:
+            cmd += " -b"
+        if incremental_copy:
+            cmd += " -i"
+        cmd += " %s" % uri
+        return self._get_command_output(cmd)
+
+
+    def migrate_set_speed(self, value):
+        """
+        Set maximum speed (in bytes/sec) for migrations.
+
+        @param value: Speed in bytes/sec
+        @return: The command's output
+        """
+        return self._get_command_output("migrate_set_speed %s" % value)
+
+
+    def sendkey(self, keystr, hold_time=1):
+        """
+        Send key combination to VM.
+
+        @param keystr: Key combination string
+        @param hold_time: Hold time in ms (should normally stay 1 ms)
+        @return: The command's output
+        """
+        return self._get_command_output("sendkey %s %s" % (keystr, hold_time))
+
+
+    def mouse_move(self, dx, dy):
+        """
+        Move mouse.
+
+        @param dx: X amount
+        @param dy: Y amount
+        @return: The command's output
+        """
+        return self._get_command_output("mouse_move %d %d" % (dx, dy))
+
+
+    def mouse_button(self, state):
+        """
+        Set mouse button state.
+
+        @param state: Button state (1=L, 2=M, 4=R)
+        @return: The command's output
+        """
+        return self._get_command_output("mouse_button %d" % state)
-- 
1.5.4.1


^ permalink raw reply related	[flat|nested] 4+ messages in thread

* [KVM-AUTOTEST PATCH v2 2/3] KVM test: use new monitor interface
  2010-06-14 16:54 [KVM-AUTOTEST PATCH v2 1/3] KVM test: add kvm_monitor.py, an interface to QEMU monitors Michael Goldish
@ 2010-06-14 16:55 ` Michael Goldish
  2010-06-14 16:55   ` [KVM-AUTOTEST PATCH v2 3/3] KVM test: kvm_monitor.py: add QMP interface Michael Goldish
  2010-06-17 20:40 ` [KVM-AUTOTEST PATCH v2 1/3] KVM test: add kvm_monitor.py, an interface to QEMU monitors Lucas Meneghel Rodrigues
  1 sibling, 1 reply; 4+ messages in thread
From: Michael Goldish @ 2010-06-14 16:55 UTC (permalink / raw)
  To: autotest, kvm; +Cc: Michael Goldish

- 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 v1:
- Turn VM.monitor into a property

Signed-off-by: Michael Goldish <mgoldish@redhat.com>
---
 client/tests/kvm/kvm_preprocessing.py              |   33 ++--
 client/tests/kvm/kvm_test_utils.py                 |   35 +--
 client/tests/kvm/kvm_vm.py                         |  236 +++++++++-----------
 client/tests/kvm/tests/balloon_check.py            |   12 +-
 client/tests/kvm/tests/boot_savevm.py              |   41 ++--
 client/tests/kvm/tests/ksm_overcommit.py           |    8 +-
 client/tests/kvm/tests/pci_hotplug.py              |   13 +-
 client/tests/kvm/tests/physical_resources_check.py |   40 ++--
 client/tests/kvm/tests/shutdown.py                 |    2 +-
 client/tests/kvm/tests/stepmaker.py                |   44 ++--
 client/tests/kvm/tests/steps.py                    |   12 +-
 client/tests/kvm/tests_base.cfg.sample             |    7 +-
 12 files changed, 241 insertions(+), 242 deletions(-)

diff --git a/client/tests/kvm/kvm_preprocessing.py b/client/tests/kvm/kvm_preprocessing.py
index 76c8268..ee3e9b2 100644
--- a/client/tests/kvm/kvm_preprocessing.py
+++ b/client/tests/kvm/kvm_preprocessing.py
@@ -1,7 +1,7 @@
 import sys, os, time, commands, re, logging, signal, glob, threading, shutil
 from autotest_lib.client.bin import test, utils
 from autotest_lib.client.common_lib import error
-import kvm_vm, kvm_utils, kvm_subprocess, ppm_utils
+import kvm_vm, kvm_utils, kvm_subprocess, kvm_monitor, ppm_utils
 try:
     import PIL.Image
 except ImportError:
@@ -83,7 +83,11 @@ def preprocess_vm(test, params, env, name):
         raise error.TestError("Could not start VM")
 
     scrdump_filename = os.path.join(test.debugdir, "pre_%s.ppm" % name)
-    vm.send_monitor_cmd("screendump %s" % scrdump_filename)
+    try:
+        if vm.monitor:
+            vm.monitor.screendump(scrdump_filename)
+    except kvm_monitor.MonitorError, e:
+        logging.warn(e)
 
 
 def postprocess_image(test, params):
@@ -117,7 +121,11 @@ def postprocess_vm(test, params, env, name):
         return
 
     scrdump_filename = os.path.join(test.debugdir, "post_%s.ppm" % name)
-    vm.send_monitor_cmd("screendump %s" % scrdump_filename)
+    try:
+        if vm.monitor:
+            vm.monitor.screendump(scrdump_filename)
+    except kvm_monitor.MonitorError, e:
+        logging.warn(e)
 
     if params.get("kill_vm") == "yes":
         kill_vm_timeout = float(params.get("kill_vm_timeout", 0))
@@ -356,8 +364,9 @@ def postprocess(test, params, env):
         for vm in kvm_utils.env_get_all_vms(env):
             if not vm.is_dead():
                 logging.info("VM '%s' is alive.", vm.name)
-                logging.info("The monitor unix socket of '%s' is: %s",
-                             vm.name, vm.monitor_file_name)
+                for m in vm.monitors:
+                    logging.info("'%s' has a %s monitor unix socket at: %s",
+                                 vm.name, m.protocol, m.filename)
                 logging.info("The command line used to start '%s' was:\n%s",
                              vm.name, vm.make_qemu_command())
         raise error.JobError("Abort requested (%s)" % exc_string)
@@ -403,10 +412,6 @@ def _take_screendumps(test, params, env):
                                  kvm_utils.generate_random_string(6))
     delay = float(params.get("screendump_delay", 5))
     quality = int(params.get("screendump_quality", 30))
-    if params.get("screendump_verbose") == 'yes':
-        screendump_verbose = True
-    else:
-        screendump_verbose = False
 
     cache = {}
 
@@ -414,11 +419,11 @@ def _take_screendumps(test, params, env):
         for vm in kvm_utils.env_get_all_vms(env):
             if vm.is_dead():
                 continue
-            if screendump_verbose:
-                vm.send_monitor_cmd("screendump %s" % temp_filename)
-            else:
-                vm.send_monitor_cmd("screendump %s" % temp_filename,
-                                    verbose=False)
+            try:
+                vm.monitor.screendump(temp_filename)
+            except kvm_monitor.MonitorError, e:
+                logging.warn(e)
+                continue
             if not os.path.exists(temp_filename):
                 logging.warn("VM '%s' failed to produce a screendump", vm.name)
                 continue
diff --git a/client/tests/kvm/kvm_test_utils.py b/client/tests/kvm/kvm_test_utils.py
index 24e2bf5..c3b6b8a 100644
--- a/client/tests/kvm/kvm_test_utils.py
+++ b/client/tests/kvm/kvm_test_utils.py
@@ -85,9 +85,9 @@ def reboot(vm, session, method="shell", sleep_before_reset=10, nic_index=0,
         # Sleep for a while before sending the command
         time.sleep(sleep_before_reset)
         # Send a system_reset monitor command
-        vm.send_monitor_cmd("system_reset")
+        vm.monitor.cmd("system_reset")
         logging.info("Monitor command system_reset sent. Waiting for guest to "
-                     "go down")
+                     "go down...")
     else:
         logging.error("Unknown reboot method: %s", method)
 
@@ -119,21 +119,16 @@ def migrate(vm, env=None):
     """
     # Helper functions
     def mig_finished():
-        s, o = vm.send_monitor_cmd("info migrate")
-        return s == 0 and not "Migration status: active" in o
+        o = vm.monitor.info("migrate")
+        return "status: active" not in o
 
     def mig_succeeded():
-        s, o = vm.send_monitor_cmd("info migrate")
-        return s == 0 and "Migration status: completed" in o
+        o = vm.monitor.info("migrate")
+        return "status: completed" in o
 
     def mig_failed():
-        s, o = vm.send_monitor_cmd("info migrate")
-        return s == 0 and "Migration status: failed" in o
-
-    # See if migration is supported
-    s, o = vm.send_monitor_cmd("help info")
-    if not "info migrate" in o:
-        raise error.TestError("Migration is not supported")
+        o = vm.monitor.info("migrate")
+        return "status: failed" in o
 
     # Clone the source VM and ask the clone to wait for incoming migration
     dest_vm = vm.clone()
@@ -141,21 +136,15 @@ def migrate(vm, env=None):
         raise error.TestError("Could not create dest VM")
 
     try:
-        # Define the migration command
-        cmd = "migrate -d tcp:localhost:%d" % dest_vm.migration_port
-        logging.debug("Migrating with command: %s" % cmd)
-
         # Migrate
-        s, o = vm.send_monitor_cmd(cmd)
-        if s:
-            logging.error("Migration command failed (command: %r, output: %r)"
-                          % (cmd, o))
-            raise error.TestFail("Migration command failed")
+        uri = "tcp:localhost:%d" % dest_vm.migration_port
+        logging.debug("Migrating to: %s" % uri)
+        o = vm.monitor.migrate(uri)
 
         # Wait for migration to finish
         if not kvm_utils.wait_for(mig_finished, 90, 2, 2,
                                   "Waiting for migration to finish..."):
-            raise error.TestFail("Timeout elapsed while waiting for migration "
+            raise error.TestFail("Timeout expired while waiting for migration "
                                  "to finish")
 
         # Report migration status
diff --git a/client/tests/kvm/kvm_vm.py b/client/tests/kvm/kvm_vm.py
index e40abb4..db5f8e0 100755
--- a/client/tests/kvm/kvm_vm.py
+++ b/client/tests/kvm/kvm_vm.py
@@ -5,8 +5,8 @@ Utility classes and functions to handle Virtual Machine creation using qemu.
 @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,25 +109,19 @@ class VM:
         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
 
-        # Find available monitor filename
+        # Find a unique identifier for this VM
         while True:
-            # A unique identifier for this VM
             self.instance = (time.strftime("%Y%m%d-%H%M%S-") +
                              kvm_utils.generate_random_string(4))
-            # Monitor
-            self.monitor_file_name = "/tmp/monitor-" + self.instance
-            # Test log for unit tests
-            self.testlog_file_name = "/tmp/testlog-" + self.instance
-            # Verify uniqueness
-            if True not in map(os.path.exists, [self.monitor_file_name,
-                                                self.testlog_file_name]):
+            if not glob.glob("/tmp/*%s" % self.instance):
                 break
 
 
@@ -203,9 +197,12 @@ class VM:
         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
 
@@ -307,8 +304,14 @@ class VM:
         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)
@@ -410,7 +413,7 @@ class VM:
             qemu_cmd += add_uuid(help, params.get("uuid"))
 
         if params.get("testdev") == "yes":
-            qemu_cmd += add_testdev(help, self.testlog_file_name)
+            qemu_cmd += add_testdev(help, self.get_testlog_filename())
 
         # If the PCI assignment step went OK, add each one of the PCI assigned
         # devices to the qemu command line.
@@ -567,6 +570,7 @@ class VM:
             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)
@@ -576,11 +580,36 @@ class VM:
                 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.
@@ -602,89 +631,6 @@ class VM:
             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)
-            return (0, data)
-
-        # Clean up before exiting
-        finally:
-            s.shutdown(socket.SHUT_RDWR)
-            s.close()
-
-
     def destroy(self, gracefully=True):
         """
         Destroy the VM.
@@ -721,15 +667,18 @@ class VM:
                     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 "
@@ -743,29 +692,43 @@ class VM:
             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()
-            for f in [self.monitor_file_name, self.testlog_file_name]:
+            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 main 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):
@@ -791,6 +754,29 @@ class VM:
         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.
@@ -988,12 +974,12 @@ class VM:
         # 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)
 
 
diff --git a/client/tests/kvm/tests/balloon_check.py b/client/tests/kvm/tests/balloon_check.py
index 2d483c6..bbab95f 100644
--- a/client/tests/kvm/tests/balloon_check.py
+++ b/client/tests/kvm/tests/balloon_check.py
@@ -1,6 +1,6 @@
 import re, string, logging, random, time
 from autotest_lib.client.common_lib import error
-import kvm_test_utils, kvm_utils
+import kvm_test_utils, kvm_utils, kvm_monitor
 
 def run_balloon_check(test, params, env):
     """
@@ -21,9 +21,10 @@ def run_balloon_check(test, params, env):
         @return: Number of failures occurred during operation.
         """
         fail = 0
-        status, output = vm.send_monitor_cmd("info balloon")
-        if status != 0:
-            logging.error("qemu monitor command failed: info balloon")
+        try:
+            vm.monitor.info("balloon")
+        except kvm_monitor.MonitorError, e:
+            logging.error(e)
             fail += 1
             return 0
         return int(re.findall("\d+", output)[0]), fail
@@ -39,7 +40,8 @@ def run_balloon_check(test, params, env):
         """
         fail = 0
         logging.info("Changing VM memory to %s", new_mem)
-        vm.send_monitor_cmd("balloon %s" % new_mem)
+        # This should be replaced by proper monitor method call
+        vm.monitor.cmd("balloon %s" % new_mem)
         time.sleep(20)
 
         ballooned_mem, cfail = check_ballooned_memory()
diff --git a/client/tests/kvm/tests/boot_savevm.py b/client/tests/kvm/tests/boot_savevm.py
index e8ea724..3305695 100644
--- a/client/tests/kvm/tests/boot_savevm.py
+++ b/client/tests/kvm/tests/boot_savevm.py
@@ -1,6 +1,6 @@
 import logging, time
 from autotest_lib.client.common_lib import error
-import kvm_subprocess, kvm_test_utils, kvm_utils
+import kvm_test_utils, kvm_utils, kvm_monitor
 
 def run_boot_savevm(test, params, env):
     """
@@ -23,23 +23,30 @@ def run_boot_savevm(test, params, env):
     while time.time() < end_time:
         time.sleep(savevm_delay)
 
-        s, o = vm.send_monitor_cmd("stop")
-        if s:
-            logging.error("stop failed: %r" % o)
-        s, o = vm.send_monitor_cmd("savevm 1")
-        if s:
-            logging.error("savevm failed: %r" % o)
-        s, o = vm.send_monitor_cmd("system_reset")
-        if s:
-            logging.error("system_reset: %r" % o)
-        s, o = vm.send_monitor_cmd("loadvm 1")
-        if s:
-            logging.error("loadvm failed: %r" % o)
-        s, o = vm.send_monitor_cmd("cont")
-        if s:
-            logging.error("cont failed: %r" % o)
+        try:
+            vm.monitor.cmd("stop")
+        except kvm_monitor.MonitorError, e:
+            logging.error(e)
+        try:
+            # This should be replaced by a proper monitor method call
+            vm.monitor.cmd("savevm 1")
+        except kvm_monitor.MonitorError, e:
+            logging.error(e)
+        try:
+            vm.monitor.cmd("system_reset")
+        except kvm_monitor.MonitorError, e:
+            logging.error(e)
+        try:
+            # This should be replaced by a proper monitor method call
+            vm.monitor.cmd("loadvm 1")
+        except kvm_monitor.MonitorError, e:
+            logging.error(e)
+        try:
+            vm.monitor.cmd("cont")
+        except kvm_monitor.MonitorError, e:
+            logging.error(e)
 
-         # Log in
+        # Log in
         if (time.time() > login_expire):
             login_expire = time.time() + savevm_login_delay
             logging.info("Logging in after loadvm...")
diff --git a/client/tests/kvm/tests/ksm_overcommit.py b/client/tests/kvm/tests/ksm_overcommit.py
index 2b791f1..ddf1670 100644
--- a/client/tests/kvm/tests/ksm_overcommit.py
+++ b/client/tests/kvm/tests/ksm_overcommit.py
@@ -173,11 +173,11 @@ def run_ksm_overcommit(test, params, env):
                     # We need to keep some memory for python to run.
                     if (free_mem < 64000) or (ksm_swap and
                                               free_mem < (450000 * perf_ratio)):
-                        vm.send_monitor_cmd('stop')
+                        vm.monitor.cmd("stop")
                         for j in range(0, i):
                             lvms[j].destroy(gracefully = False)
                         time.sleep(20)
-                        vm.send_monitor_cmd('c')
+                        vm.monitor.cmd("c")
                         logging.debug("Only %s free memory, killing %d guests" %
                                       (free_mem, (i-1)))
                         last_vm = i
@@ -188,12 +188,12 @@ def run_ksm_overcommit(test, params, env):
                 logging.debug("Only %s host free memory, killing %d guests" %
                               (free_mem, (i - 1)))
                 logging.debug("Stopping %s", vm.name)
-                vm.send_monitor_cmd('stop')
+                vm.monitor.cmd("stop")
                 for j in range(0, i):
                     logging.debug("Destroying %s", lvms[j].name)
                     lvms[j].destroy(gracefully = False)
                 time.sleep(20)
-                vm.send_monitor_cmd('c')
+                vm.monitor.cmd("c")
                 last_vm = i
 
             if last_vm != 0:
diff --git a/client/tests/kvm/tests/pci_hotplug.py b/client/tests/kvm/tests/pci_hotplug.py
index d8f34f8..f50a67e 100644
--- a/client/tests/kvm/tests/pci_hotplug.py
+++ b/client/tests/kvm/tests/pci_hotplug.py
@@ -27,7 +27,7 @@ def run_pci_hotplug(test, params, env):
             raise error.TestError("Modprobe module '%s' failed" % module)
 
     # Get output of command 'info pci' as reference
-    s, info_pci_ref = vm.send_monitor_cmd("info pci")
+    info_pci_ref = vm.monitor.info("pci")
 
     # Get output of command as reference
     reference = session.get_command_output(params.get("reference_cmd"))
@@ -43,21 +43,22 @@ def run_pci_hotplug(test, params, env):
         pci_add_cmd = ("pci_add pci_addr=auto storage file=%s,if=%s" %
                        (image_filename, tested_model))
 
-    # Implement pci_add
-    s, add_output = vm.send_monitor_cmd(pci_add_cmd)
+    # Execute pci_add (should be replaced by a proper monitor method call)
+    add_output = vm.monitor.cmd(pci_add_cmd)
     if not "OK domain" in add_output:
         raise error.TestFail("Add device failed. Hypervisor command is: %s. "
                              "Output: %r" % (pci_add_cmd, add_output))
-    s, after_add = vm.send_monitor_cmd("info pci")
+    after_add = vm.monitor.info("pci")
 
     # Define a helper function to delete the device
     def pci_del(ignore_failure=False):
         slot_id = "0" + add_output.split(",")[2].split()[1]
         cmd = "pci_del pci_addr=%s" % slot_id
-        vm.send_monitor_cmd(cmd)
+        # This should be replaced by a proper monitor method call
+        vm.monitor.cmd(cmd)
 
         def device_removed():
-            s, after_del = vm.send_monitor_cmd("info pci")
+            after_del = vm.monitor.info("pci")
             return after_del != after_add
 
         if (not kvm_utils.wait_for(device_removed, 10, 0, 1)
diff --git a/client/tests/kvm/tests/physical_resources_check.py b/client/tests/kvm/tests/physical_resources_check.py
index af9613e..946760f 100644
--- a/client/tests/kvm/tests/physical_resources_check.py
+++ b/client/tests/kvm/tests/physical_resources_check.py
@@ -1,6 +1,6 @@
 import re, string, logging
 from autotest_lib.client.common_lib import error
-import kvm_test_utils, kvm_utils
+import kvm_test_utils, kvm_utils, kvm_monitor
 
 
 def run_physical_resources_check(test, params, env):
@@ -48,13 +48,15 @@ def run_physical_resources_check(test, params, env):
         logging.error("    Reported by OS: %s" % actual_mem)
 
     # Define a function for checking number of hard drivers & NICs
-    def check_num(devices, cmd, check_str):
+    def check_num(devices, info_cmd, check_str):
         f_fail = 0
         expected_num = kvm_utils.get_sub_dict_names(params, devices).__len__()
-        s, o = vm.send_monitor_cmd(cmd)
-        if s != 0:
+        try:
+            o = vm.monitor.info(info_cmd)
+        except kvm_monitor.MonitorError, e:
             f_fail += 1
-            logging.error("qemu monitor command failed: %s" % cmd)
+            logging.error(e)
+            logging.error("info/query monitor command failed (%s)", info_cmd)
 
         actual_num = string.count(o, check_str)
         if expected_num != actual_num:
@@ -65,25 +67,28 @@ def run_physical_resources_check(test, params, env):
         return expected_num, f_fail
 
     logging.info("Hard drive count check")
-    drives_num, f_fail = check_num("images", "info block", "type=hd")
+    drives_num, f_fail = check_num("images", "block", "type=hd")
     n_fail += f_fail
 
     logging.info("NIC count check")
-    nics_num, f_fail = check_num("nics", "info network", "model=")
+    nics_num, f_fail = check_num("nics", "network", "model=")
     n_fail += f_fail
 
     # Define a function for checking hard drives & NICs' model
-    def chk_fmt_model(device, fmt_model, cmd, str):
+    def chk_fmt_model(device, fmt_model, info_cmd, str):
         f_fail = 0
         devices = kvm_utils.get_sub_dict_names(params, device)
         for chk_device in devices:
             expected = kvm_utils.get_sub_dict(params, chk_device).get(fmt_model)
             if not expected:
                 expected = "rtl8139"
-            s, o = vm.send_monitor_cmd(cmd)
-            if s != 0:
+            try:
+                o = vm.monitor.info(info_cmd)
+            except kvm_monitor.MonitorError, e:
                 f_fail += 1
-                logging.error("qemu monitor command failed: %s" % cmd)
+                logging.error(e)
+                logging.error("info/query monitor command failed (%s)",
+                              info_cmd)
 
             device_found = re.findall(str, o)
             logging.debug("Found devices: %s" % device_found)
@@ -100,19 +105,20 @@ def run_physical_resources_check(test, params, env):
         return f_fail
 
     logging.info("NICs model check")
-    f_fail = chk_fmt_model("nics", "nic_model", "info network", "model=(.*),")
+    f_fail = chk_fmt_model("nics", "nic_model", "network", "model=(.*),")
     n_fail += f_fail
 
     logging.info("Drive format check")
-    f_fail = chk_fmt_model("images", "drive_format", "info block",
-                           "(.*)\: type=hd")
+    f_fail = chk_fmt_model("images", "drive_format", "block", "(.*)\: type=hd")
     n_fail += f_fail
 
     logging.info("Network card MAC check")
-    s, o = vm.send_monitor_cmd("info network")
-    if s != 0:
+    try:
+        o = vm.monitor.info("network")
+    except kvm_monitor.MonitorError, e:
         n_fail += 1
-        logging.error("qemu monitor command failed: info network")
+        logging.error(e)
+        logging.error("info/query monitor command failed (network)")
     found_mac_addresses = re.findall("macaddr=(.*)", o)
     logging.debug("Found MAC adresses: %s" % found_mac_addresses)
 
diff --git a/client/tests/kvm/tests/shutdown.py b/client/tests/kvm/tests/shutdown.py
index 8a252d9..3cbdd79 100644
--- a/client/tests/kvm/tests/shutdown.py
+++ b/client/tests/kvm/tests/shutdown.py
@@ -28,7 +28,7 @@ def run_shutdown(test, params, env):
             # Sleep for a while -- give the guest a chance to finish booting
             time.sleep(float(params.get("sleep_before_powerdown", 10)))
             # Send a system_powerdown monitor command
-            vm.send_monitor_cmd("system_powerdown")
+            vm.monitor.cmd("system_powerdown")
             logging.info("system_powerdown monitor command sent; waiting for "
                          "guest to go down...")
 
diff --git a/client/tests/kvm/tests/stepmaker.py b/client/tests/kvm/tests/stepmaker.py
index 24060db..ee0ed92 100755
--- a/client/tests/kvm/tests/stepmaker.py
+++ b/client/tests/kvm/tests/stepmaker.py
@@ -10,7 +10,7 @@ Step file creator/editor.
 import pygtk, gtk, gobject, time, os, commands
 import common
 from autotest_lib.client.common_lib import error
-import kvm_utils, logging, ppm_utils, stepeditor
+import kvm_utils, logging, ppm_utils, stepeditor, kvm_monitor
 pygtk.require('2.0')
 
 
@@ -84,7 +84,7 @@ class StepMaker(stepeditor.StepMakerWindow):
 
 
     def destroy(self, widget):
-        self.vm.send_monitor_cmd("cont")
+        self.vm.monitor.cmd("cont")
         self.steps_file.close()
         self.vars_file.close()
         stepeditor.StepMakerWindow.destroy(self, widget)
@@ -112,7 +112,7 @@ class StepMaker(stepeditor.StepMakerWindow):
         # Start the screendump timer
         self.redirect_timer(100, self.update)
         # Resume the VM
-        self.vm.send_monitor_cmd("cont")
+        self.vm.monitor.cmd("cont")
 
 
     def switch_to_step_mode(self):
@@ -127,7 +127,7 @@ class StepMaker(stepeditor.StepMakerWindow):
         # Start the screendump timer
         self.redirect_timer()
         # Stop the VM
-        self.vm.send_monitor_cmd("stop")
+        self.vm.monitor.cmd("stop")
 
 
     # Events in step mode
@@ -137,10 +137,10 @@ class StepMaker(stepeditor.StepMakerWindow):
         if os.path.exists(self.screendump_filename):
             os.unlink(self.screendump_filename)
 
-        (status, output) = self.vm.send_monitor_cmd("screendump " +
-                                                    self.screendump_filename)
-        if status: # Failure
-            logging.info("Could not fetch screendump")
+        try:
+            self.vm.monitor.screendump(self.screendump_filename)
+        except kvm_monitor.MonitorError, e:
+            logging.warn(e)
         else:
             self.set_image_from_file(self.screendump_filename)
 
@@ -228,15 +228,14 @@ class StepMaker(stepeditor.StepMakerWindow):
                     continue
                 self.vm.send_string(val)
             elif words[0] == "mousemove":
-                self.vm.send_monitor_cmd("mouse_move %d %d" % (-8000,-8000))
+                self.vm.monitor.mouse_move(-8000, -8000)
                 time.sleep(0.5)
-                self.vm.send_monitor_cmd("mouse_move %s %s" % (words[1],
-                                                               words[2]))
+                self.vm.monitor.mouse_move(words[1], words[2])
                 time.sleep(0.5)
             elif words[0] == "mouseclick":
-                self.vm.send_monitor_cmd("mouse_button %s" % words[1])
+                self.vm.monitor.mouse_button(words[1])
                 time.sleep(0.1)
-                self.vm.send_monitor_cmd("mouse_button 0")
+                self.vm.monitor.mouse_button(0)
 
         # Remember the current time
         self.time_when_actions_completed = time.time()
@@ -267,7 +266,7 @@ class StepMaker(stepeditor.StepMakerWindow):
                 self.event_capture_button_release,
                 self.event_capture_scroll)
         self.redirect_timer(10, self.update_capture)
-        self.vm.send_monitor_cmd("cont")
+        self.vm.monitor.cmd("cont")
 
     # Events in mouse capture mode
 
@@ -280,11 +279,10 @@ class StepMaker(stepeditor.StepMakerWindow):
 
         delay = self.spin_latency.get_value() / 1000
         if (x, y) != (self.prev_x, self.prev_y):
-            self.vm.send_monitor_cmd("mouse_move %d %d" % (-8000, -8000))
+            self.vm.monitor.mouse_move(-8000, -8000)
             time.sleep(delay)
-            self.vm.send_monitor_cmd("mouse_move %d %d" %
-                                     (self.mouse_click_coords[0],
-                                      self.mouse_click_coords[1]))
+            self.vm.monitor.mouse_move(self.mouse_click_coords[0],
+                                       self.mouse_click_coords[1])
             time.sleep(delay)
 
         self.prev_x = x
@@ -293,10 +291,10 @@ class StepMaker(stepeditor.StepMakerWindow):
         if os.path.exists(self.screendump_filename):
             os.unlink(self.screendump_filename)
 
-        (status, output) = self.vm.send_monitor_cmd("screendump " +
-                                                    self.screendump_filename)
-        if status: # Failure
-            logging.info("Could not fetch screendump")
+        try:
+            self.vm.monitor.screendump(self.screendump_filename)
+        except kvm_monitor.MonitorError, e:
+            logging.warn(e)
         else:
             self.set_image_from_file(self.screendump_filename)
 
@@ -317,7 +315,7 @@ class StepMaker(stepeditor.StepMakerWindow):
                 None,
                 self.event_expose)
         self.redirect_timer()
-        self.vm.send_monitor_cmd("stop")
+        self.vm.monitor.cmd("stop")
         self.mouse_click_captured = True
         self.mouse_click_button = event.button
         self.set_image(self.image_width_backup, self.image_height_backup,
diff --git a/client/tests/kvm/tests/steps.py b/client/tests/kvm/tests/steps.py
index 8ebe7c1..6f782f5 100644
--- a/client/tests/kvm/tests/steps.py
+++ b/client/tests/kvm/tests/steps.py
@@ -6,7 +6,7 @@ Utilities to perform automatic guest installation using step files.
 
 import os, time, re, shutil, logging
 from autotest_lib.client.common_lib import utils, error
-import kvm_utils, ppm_utils, kvm_subprocess
+import kvm_utils, ppm_utils, kvm_subprocess, kvm_monitor
 try:
     import PIL.Image
 except ImportError:
@@ -85,10 +85,10 @@ def barrier_2(vm, words, params, debug_dir, data_scrdump_filename,
             break
 
         # Request screendump
-        (status, output) = vm.send_monitor_cmd("screendump %s" %
-                                               scrdump_filename)
-        if status:
-            logging.error("Could not fetch screendump")
+        try:
+            vm.monitor.screendump(scrdump_filename)
+        except kvm_monitor.MonitorError, e:
+            logging.warn(e)
             continue
 
         # Read image file
@@ -199,7 +199,7 @@ def run_steps(test, params, env):
     lines = sf.readlines()
     sf.close()
 
-    vm.send_monitor_cmd("cont")
+    vm.monitor.cmd("cont")
 
     current_step_num = 0
     current_screendump = None
diff --git a/client/tests/kvm/tests_base.cfg.sample b/client/tests/kvm/tests_base.cfg.sample
index b302557..cabeb4a 100644
--- a/client/tests/kvm/tests_base.cfg.sample
+++ b/client/tests/kvm/tests_base.cfg.sample
@@ -4,9 +4,11 @@
 vms = vm1
 images = image1
 nics = nic1
+monitors = humanmonitor1
 
-# Choose the main VM
+# Choose the main VM and monitor
 main_vm = vm1
+main_monitor = humanmonitor1
 
 # Some preprocessor/postprocessor params
 start_vm = yes
@@ -34,6 +36,9 @@ image_size = 10G
 shell_port = 22
 display = vnc
 
+# Monitor params
+monitor_type = human
+
 # Default scheduler params
 used_cpus = 1
 used_mem = 512
-- 
1.5.4.1


^ permalink raw reply related	[flat|nested] 4+ messages in thread

* [KVM-AUTOTEST PATCH v2 3/3] KVM test: kvm_monitor.py: add QMP interface
  2010-06-14 16:55 ` [KVM-AUTOTEST PATCH v2 2/3] KVM test: use new monitor interface Michael Goldish
@ 2010-06-14 16:55   ` Michael Goldish
  0 siblings, 0 replies; 4+ messages in thread
From: Michael Goldish @ 2010-06-14 16:55 UTC (permalink / raw)
  To: autotest, kvm; +Cc: Michael Goldish

An initial QMP client implementation.
Should be fully functional and supports asynchronous events.
However, most tests must be modified to support it, because it returns output
in a different format from the human monitor (the human monitor returns strings
and the QMP one returns dicts or lists).

To enable QMP, set main_monitor to a monitor whose monitor_type is "qmp".

For example (a single QMP monitor):

    monitors = monitor1
    monitor_type_monitor1 = qmp
    main_monitor = monitor1

Another example (multiple monitors, both human and QMP):

    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 = SomeOtherMonitor         # the main monitor is a QMP one, so
                                            # the test will use QMP

Note:
Monitor methods now raise exceptions such as MonitorLockError and QMPCmdError.
If this turns out to be a bad idea, it shouldn't be hard to revert to the old
convention of returning a (status, output) tuple.

Changes from v1:
- Upon connection make sure the QMP greeting is received (otherwise raise an
  exception)
- Upon connection make sure the json module is available
- Rename the events attribute to _events to indicate private use
- Use _lock instead of lock
- Use _socket instead of socket

Signed-off-by: Michael Goldish <mgoldish@redhat.com>
---
 client/tests/kvm/kvm_monitor.py |  305 +++++++++++++++++++++++++++++++++++++++
 client/tests/kvm/kvm_vm.py      |    8 +-
 2 files changed, 310 insertions(+), 3 deletions(-)

diff --git a/client/tests/kvm/kvm_monitor.py b/client/tests/kvm/kvm_monitor.py
index 27045a4..12cf773 100644
--- a/client/tests/kvm/kvm_monitor.py
+++ b/client/tests/kvm/kvm_monitor.py
@@ -6,6 +6,11 @@ Interfaces to the QEMU monitor.
 
 import socket, time, threading, logging
 import kvm_utils
+try:
+    import json
+except ImportError:
+    logging.warning("Could not import json module. "
+                    "QMP monitor functionality disabled.")
 
 
 class MonitorError(Exception):
@@ -28,6 +33,14 @@ class MonitorProtocolError(MonitorError):
     pass
 
 
+class MonitorNotSupportedError(MonitorError):
+    pass
+
+
+class QMPCmdError(MonitorError):
+    pass
+
+
 class Monitor:
     """
     Common code for monitor classes.
@@ -352,3 +365,295 @@ class HumanMonitor(Monitor):
         @return: The command's output
         """
         return self._get_command_output("mouse_button %d" % state)
+
+
+class QMPMonitor(Monitor):
+    """
+    Wraps QMP monitor commands.
+    """
+
+    def __init__(self, name, filename, suppress_exceptions=False):
+        """
+        Connect to the monitor socket, read the greeting message and issue the
+        qmp_capabilities command.  Also make sure the json module is available.
+
+        @param name: Monitor identifier (a string)
+        @param filename: Monitor socket filename
+        @raise MonitorConnectError: Raised if the connection fails and
+                suppress_exceptions is False
+        @raise MonitorProtocolError: Raised if the no QMP greeting message is
+                received and suppress_exceptions is False
+        @raise MonitorNotSupportedError: Raised if json isn't available and
+                suppress_exceptions is False
+        @note: Other exceptions may be raised if the qmp_capabilities command
+                fails.  See _get_command_output's docstring.
+        """
+        try:
+            Monitor.__init__(self, name, filename)
+
+            self.protocol = "qmp"
+            self._greeting = None
+            self._events = []
+
+            # Make sure json is available
+            try:
+                json
+            except NameError:
+                raise MonitorNotSupportedError("QMP requires the json module "
+                                               "(Python 2.6 and up)")
+
+            # Read greeting message
+            end_time = time.time() + 20
+            while time.time() < end_time:
+                for obj in self._read_objects():
+                    if "QMP" in obj:
+                        self._greeting = obj["QMP"]
+                        break
+                if self._greeting:
+                    break
+                time.sleep(0.1)
+            else:
+                raise MonitorProtocolError("No QMP greeting message received")
+
+            # Issue qmp_capabilities
+            self._get_command_output("qmp_capabilities")
+
+        except MonitorError, e:
+            if suppress_exceptions:
+                logging.warn(e)
+            else:
+                raise
+
+
+    # Private methods
+
+    def _build_cmd(self, cmd, args=None):
+        obj = {"execute": cmd}
+        if args:
+            obj["arguments"] = args
+        return obj
+
+
+    def _read_objects(self, timeout=5):
+        """
+        Read lines from monitor and try to decode them.
+        Stop when all available lines have been successfully decoded, or when
+        timeout expires.  If any decoded objects are asynchronous events, store
+        them in self._events.  Return all decoded objects.
+
+        @param timeout: Time to wait for all lines to decode successfully
+        @return: A list of objects
+        """
+        s = ""
+        objs = []
+        end_time = time.time() + timeout
+        while time.time() < end_time:
+            s += self._recvall()
+            for line in s.splitlines():
+                if not line:
+                    continue
+                try:
+                    obj = json.loads(line)
+                except:
+                    # Found an incomplete or broken line -- keep reading
+                    break
+                objs += [obj]
+            else:
+                # All lines are OK -- stop reading
+                break
+            time.sleep(0.1)
+        # Keep track of asynchronous events
+        self._events += [obj for obj in objs if "event" in obj]
+        return objs
+
+
+    def _send_command(self, cmd, args=None):
+        """
+        Send command without waiting for response.
+
+        @param cmd: Command to send
+        @param args: A dict containing command arguments, or None
+        @raise MonitorLockError: Raised if the lock cannot be acquired
+        @raise MonitorSendError: Raised if the command cannot be sent
+        """
+        if not self._acquire_lock(20):
+            raise MonitorLockError("Could not acquire exclusive lock to send "
+                                   "QMP command '%s'" % cmd)
+
+        try:
+            cmdobj = self._build_cmd(cmd, args)
+            try:
+                self._socket.sendall(json.dumps(cmdobj) + "\n")
+            except socket.error:
+                raise MonitorSendError("Could not send QMP command '%s'" % cmd)
+
+        finally:
+            self._lock.release()
+
+
+    def _get_command_output(self, cmd, args=None, timeout=20):
+        """
+        Send monitor command and wait for response.
+
+        @param cmd: Command to send
+        @param args: A dict containing command arguments, or None
+        @param timeout: Time duration to wait for response
+        @return: The response received
+        @raise MonitorLockError: Raised if the lock cannot be acquired
+        @raise MonitorSendError: Raised if the command cannot be sent
+        @raise MonitorProtocolError: Raised if no response is received
+        @raise QMPCmdError: Raised if the response is an error message
+                (the exception's args are (msg, data) where msg is a string and
+                data is the error data)
+        """
+        if not self._acquire_lock(20):
+            raise MonitorLockError("Could not acquire exclusive lock to send "
+                                   "QMP command '%s'" % cmd)
+
+        try:
+            # Read any data that might be available
+            self._read_objects()
+            # Send command
+            self._send_command(cmd, args)
+            # Read response
+            end_time = time.time() + timeout
+            while time.time() < end_time:
+                for obj in self._read_objects():
+                    if "return" in obj:
+                        return obj["return"]
+                    elif "error" in obj:
+                        raise QMPCmdError("QMP command '%s' failed" % cmd,
+                                          obj["error"])
+                time.sleep(0.1)
+            # No response found
+            raise MonitorProtocolError("Received no response to QMP command "
+                                       "'%s'" % cmd)
+
+        finally:
+            self._lock.release()
+
+
+    # Public methods
+
+    def is_responsive(self):
+        """
+        Make sure the monitor is responsive by sending a command.
+
+        @return: True if responsive, False otherwise
+        """
+        try:
+            self._get_command_output("query-version")
+            return True
+        except MonitorError:
+            return False
+
+
+    def get_events(self):
+        """
+        Return a list of the asynchronous events received since the last
+        clear_events() call.
+
+        @return: A list of events (the objects returned have an "event" key)
+        @raise MonitorLockError: Raised if the lock cannot be acquired
+        """
+        if not self._acquire_lock(20):
+            raise MonitorLockError("Could not acquire exclusive lock to read "
+                                   "QMP events")
+        try:
+            self._read_objects()
+            return self._events[:]
+        finally:
+            self._lock.release()
+
+
+    def clear_events(self):
+        """
+        Clear the list of asynchronous events.
+
+        @raise MonitorLockError: Raised if the lock cannot be acquired
+        """
+        if not self._acquire_lock(20):
+            raise MonitorLockError("Could not acquire exclusive lock to clear "
+                                   "QMP event list")
+        self._events = []
+        self._lock.release()
+
+
+    # Command wrappers
+    # Note: all of the following functions raise exceptions in a similar manner
+    # to cmd() and _get_command_output().
+
+    def cmd(self, command, timeout=20):
+        """
+        Send a simple command with no parameters and return its output.
+        Should only be used for commands that take no parameters and are
+        implemented under the same name for both the human and QMP monitors.
+
+        @param command: Command to send
+        @param timeout: Time duration to wait for response
+        @return: The response to the command
+        @raise MonitorLockError: Raised if the lock cannot be acquired
+        @raise MonitorSendError: Raised if the command cannot be sent
+        @raise MonitorProtocolError: Raised if no response is received
+        """
+        return self._get_command_output(command, timeout=timeout)
+
+
+    def quit(self):
+        """
+        Send "quit" and return the response.
+        """
+        return self._get_command_output("quit")
+
+
+    def info(self, what):
+        """
+        Request info about something and return the response.
+        """
+        return self._get_command_output("query-%s" % what)
+
+
+    def query(self, what):
+        """
+        Alias for info.
+        """
+        return self.info(what)
+
+
+    def screendump(self, filename):
+        """
+        Request a screendump.
+
+        @param filename: Location for the screendump
+        @return: The response to the command
+        """
+        args = {"filename": filename}
+        return self._get_command_output("screendump", args)
+
+
+    def migrate(self, uri, full_copy=False, incremental_copy=False, wait=False):
+        """
+        Migrate.
+
+        @param uri: destination URI
+        @param full_copy: If true, migrate with full disk copy
+        @param incremental_copy: If true, migrate with incremental disk copy
+        @param wait: If true, wait for completion
+        @return: The response to the command
+        """
+        args = {"uri": uri,
+                "blk": full_copy,
+                "inc": incremental_copy}
+        return self._get_command_output("migrate", args)
+
+
+    def migrate_set_speed(self, value):
+        """
+        Set maximum speed (in bytes/sec) for migrations.
+
+        @param value: Speed in bytes/sec
+        @return: The response to the command
+        """
+        args = {"value": value}
+        return self._get_command_output("migrate_set_speed", args)
+
diff --git a/client/tests/kvm/kvm_vm.py b/client/tests/kvm/kvm_vm.py
index db5f8e0..8113a45 100755
--- a/client/tests/kvm/kvm_vm.py
+++ b/client/tests/kvm/kvm_vm.py
@@ -590,8 +590,10 @@ class VM:
                 while time.time() < end_time:
                     try:
                         if monitor_params.get("monitor_type") == "qmp":
-                            # Add a QMP monitor: not implemented yet
-                            monitor = None
+                            # Add a QMP monitor
+                            monitor = kvm_monitor.QMPMonitor(
+                                monitor_name,
+                                self.get_monitor_filename(monitor_name))
                         else:
                             # Add a "human" monitor
                             monitor = kvm_monitor.HumanMonitor(
@@ -600,7 +602,7 @@ class VM:
                     except kvm_monitor.MonitorError, e:
                         logging.warn(e)
                     else:
-                        if monitor and monitor.is_responsive():
+                        if monitor.is_responsive():
                             break
                     time.sleep(1)
                 else:
-- 
1.5.4.1


^ permalink raw reply related	[flat|nested] 4+ messages in thread

* Re: [KVM-AUTOTEST PATCH v2 1/3] KVM test: add kvm_monitor.py, an interface to QEMU monitors
  2010-06-14 16:54 [KVM-AUTOTEST PATCH v2 1/3] KVM test: add kvm_monitor.py, an interface to QEMU monitors Michael Goldish
  2010-06-14 16:55 ` [KVM-AUTOTEST PATCH v2 2/3] KVM test: use new monitor interface Michael Goldish
@ 2010-06-17 20:40 ` Lucas Meneghel Rodrigues
  1 sibling, 0 replies; 4+ messages in thread
From: Lucas Meneghel Rodrigues @ 2010-06-17 20:40 UTC (permalink / raw)
  To: Michael Goldish; +Cc: autotest, kvm

On Mon, 2010-06-14 at 19:54 +0300, Michael Goldish wrote:
> This module should replace vm.send_monitor_cmd().  Instead of connecting to the
> monitor each time a command is issued, this module maintains a continuous
> connection to the monitor.  It disconnects when a test terminates and
> reconnects as soon as the next test begins (upon unpickling).
> 
> It currently contains only an interface to the human monitor.  A QMP interface
> will be introduced in a future patch.

Other than some very small comments with regards to the use of new style
classes and super() below, this code looks good, the improvements from
v1 are very welcome. 

I am a bit afraid that both monitors have different sets of commands, so
we can't guarantee a monitor public interface. What I mean by this is
that list of public methods of QmpMonitor and HumanMonitor will differ.
But there's not much we can do about it right now, perhaps have a fixed
set of 'allowed' common functions to be used, that have to be
implemented for every monitor, and have only those functions available
for test writers.

Just loud thinking here. Your approach is fine, due to some changes on
the migration test I am rebasing the 2nd patch of the series and will
re-send.

> Changes from v1:
> - Add name parameter to __init__()
> - Remove help() method
> - Rename help attribute to _help_str to indicate private use
> - Rename lock to _lock
> - Rename socket to _socket
> 
> Signed-off-by: Michael Goldish <mgoldish@redhat.com>
> ---
>  client/tests/kvm/kvm_monitor.py |  354 +++++++++++++++++++++++++++++++++++++++
>  1 files changed, 354 insertions(+), 0 deletions(-)
>  create mode 100644 client/tests/kvm/kvm_monitor.py
> 
> diff --git a/client/tests/kvm/kvm_monitor.py b/client/tests/kvm/kvm_monitor.py
> new file mode 100644
> index 0000000..27045a4
> --- /dev/null
> +++ b/client/tests/kvm/kvm_monitor.py
> @@ -0,0 +1,354 @@
> +"""
> +Interfaces to the QEMU monitor.
> +
> +@copyright: 2008-2010 Red Hat Inc.
> +"""
> +
> +import socket, time, threading, logging
> +import kvm_utils
> +
> +
> +class MonitorError(Exception):
> +    pass
> +
> +
> +class MonitorConnectError(MonitorError):
> +    pass
> +
> +
> +class MonitorSendError(MonitorError):
> +    pass
> +
> +
> +class MonitorLockError(MonitorError):
> +    pass
> +
> +
> +class MonitorProtocolError(MonitorError):
> +    pass
> +
> +
> +class Monitor:

^ Small thing here - let's make this a new-style py class by making it
subclass (object). 

> +    """
> +    Common code for monitor classes.
> +    """
> +
> +    def __init__(self, name, filename):
> +        """
> +        Initialize the instance.
> +
> +        @param name: Monitor identifier (a string)
> +        @param filename: Monitor socket filename
> +        @raise MonitorConnectError: Raised if the connection fails
> +        """
> +        self.name = name
> +        self.filename = filename
> +        self._lock = threading.RLock()
> +        self._socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
> +        self._socket.setblocking(False)
> +
> +        try:
> +            self._socket.connect(filename)
> +        except socket.error:
> +            raise MonitorConnectError("Could not connect to monitor socket")
> +
> +
> +    def __del__(self):
> +        # Automatically close the connection when the instance is garbage
> +        # collected
> +        try:
> +            self._socket.shutdown(socket.SHUT_RDWR)
> +        except socket.error:
> +            pass
> +        self._socket.close()
> +
> +
> +    # The following two functions are defined to make sure the state is set
> +    # exclusively by the constructor call as specified in __getinitargs__().
> +
> +    def __getstate__(self):
> +        pass
> +
> +
> +    def __setstate__(self, state):
> +        pass
> +
> +
> +    def __getinitargs__(self):
> +        # Save some information when pickling -- will be passed to the
> +        # constructor upon unpickling
> +        return self.name, self.filename, True
> +
> +
> +    def _acquire_lock(self, timeout=20):
> +        end_time = time.time() + timeout
> +        while time.time() < end_time:
> +            if self._lock.acquire(False):
> +                return True
> +            time.sleep(0.05)
> +        return False
> +
> +
> +    def _recvall(self):
> +        s = ""
> +        while True:
> +            try:
> +                data = self._socket.recv(1024)
> +            except socket.error:
> +                break
> +            if not data:
> +                break
> +            s += data
> +        return s
> +
> +
> +class HumanMonitor(Monitor):
> +    """
> +    Wraps "human monitor" commands.
> +    """
> +
> +    def __init__(self, name, filename, suppress_exceptions=False):
> +        """
> +        Connect to the monitor socket and find the (qemu) prompt.
> +
> +        @param name: Monitor identifier (a string)
> +        @param filename: Monitor socket filename
> +        @raise MonitorConnectError: Raised if the connection fails and
> +                suppress_exceptions is False
> +        @raise MonitorProtocolError: Raised if the initial (qemu) prompt isn't
> +                found and suppress_exceptions is False
> +        @note: Other exceptions may be raised.  See _get_command_output's
> +                docstring.
> +        """
> +        try:
> +            Monitor.__init__(self, name, filename)

^ Here, let's use 

super(HumanMonitor, self).__init__(name, filename)

since we'll use new style classes and, if by some reason we change the
name of the parent class we don't have to change this statement.

> +            self.protocol = "human"
> +
> +            # Find the initial (qemu) prompt
> +            s, o = self._read_up_to_qemu_prompt(20)
> +            if not s:
> +                raise MonitorProtocolError("Could not find (qemu) prompt "
> +                                           "after connecting to monitor. "
> +                                           "Output so far: %r" % o)
> +
> +            # Save the output of 'help' for future use
> +            self._help_str = self._get_command_output("help")
> +
> +        except MonitorError, e:
> +            if suppress_exceptions:
> +                logging.warn(e)
> +            else:
> +                raise
> +
> +
> +    # Private methods
> +
> +    def _read_up_to_qemu_prompt(self, timeout=20):
> +        o = ""
> +        end_time = time.time() + timeout
> +        while time.time() < end_time:
> +            try:
> +                data = self._socket.recv(1024)
> +                if not data:
> +                    break
> +                o += data
> +                if o.splitlines()[-1].split()[-1] == "(qemu)":
> +                    return True, "\n".join(o.splitlines()[:-1])
> +            except (socket.error, IndexError):
> +                time.sleep(0.01)
> +        return False, "\n".join(o.splitlines())
> +
> +
> +    def _send_command(self, command):
> +        """
> +        Send a command without waiting for output.
> +
> +        @param command: Command to send
> +        @return: True if successful, False otherwise
> +        @raise MonitorLockError: Raised if the lock cannot be acquired
> +        @raise MonitorSendError: Raised if the command cannot be sent
> +        """
> +        if not self._acquire_lock(20):
> +            raise MonitorLockError("Could not acquire exclusive lock to send "
> +                                   "monitor command '%s'" % command)
> +
> +        try:
> +            try:
> +                self._socket.sendall(command + "\n")
> +            except socket.error:
> +                raise MonitorSendError("Could not send monitor command '%s'" %
> +                                       command)
> +
> +        finally:
> +            self._lock.release()
> +
> +
> +    def _get_command_output(self, command, timeout=20):
> +        """
> +        Send command to the monitor.
> +
> +        @param command: Command to send to the monitor
> +        @param timeout: Time duration to wait for the (qemu) prompt to return
> +        @return: Output received from the monitor
> +        @raise MonitorLockError: Raised if the lock cannot be acquired
> +        @raise MonitorSendError: Raised if the command cannot be sent
> +        @raise MonitorProtocolError: Raised if the (qemu) prompt cannot be
> +                found after sending the command
> +        """
> +        if not self._acquire_lock(20):
> +            raise MonitorLockError("Could not acquire exclusive lock to send "
> +                                   "monitor command '%s'" % command)
> +
> +        try:
> +            # Read any data that might be available
> +            self._recvall()
> +            # Send command
> +            self._send_command(command)
> +            # Read output
> +            s, o = self._read_up_to_qemu_prompt(timeout)
> +            # Remove command echo from output
> +            o = "\n".join(o.splitlines()[1:])
> +            # Report success/failure
> +            if s:
> +                return o
> +            else:
> +                msg = ("Could not find (qemu) prompt after command '%s'. "
> +                       "Output so far: %r" % (command, o))
> +                raise MonitorProtocolError(msg)
> +
> +        finally:
> +            self._lock.release()
> +
> +
> +    # Public methods
> +
> +    def is_responsive(self):
> +        """
> +        Make sure the monitor is responsive by sending a command.
> +
> +        @return: True if responsive, False otherwise
> +        """
> +        try:
> +            self._get_command_output("help")
> +            return True
> +        except MonitorError:
> +            return False
> +
> +
> +    # Command wrappers
> +    # Notes:
> +    # - All of the following commands raise exceptions in a similar manner to
> +    #   cmd() and _get_command_output().
> +    # - A command wrapper should use self._help_str if it requires information
> +    #   about the monitor's capabilities.
> +
> +    def cmd(self, command, timeout=20):
> +        """
> +        Send a simple command with no parameters and return its output.
> +        Should only be used for commands that take no parameters and are
> +        implemented under the same name for both the human and QMP monitors.
> +
> +        @param command: Command to send
> +        @param timeout: Time duration to wait for (qemu) prompt after command
> +        @return: The output of the command
> +        @raise MonitorLockError: Raised if the lock cannot be acquired
> +        @raise MonitorSendError: Raised if the command cannot be sent
> +        @raise MonitorProtocolError: Raised if the (qemu) prompt cannot be
> +                found after sending the command
> +        """
> +        return self._get_command_output(command, timeout)
> +
> +
> +    def quit(self):
> +        """
> +        Send "quit" without waiting for output.
> +        """
> +        self._send_command("quit")
> +
> +
> +    def info(self, what):
> +        """
> +        Request info about something and return the output.
> +        """
> +        return self._get_command_output("info %s" % what)
> +
> +
> +    def query(self, what):
> +        """
> +        Alias for info.
> +        """
> +        return self.info(what)
> +
> +
> +    def screendump(self, filename):
> +        """
> +        Request a screendump.
> +
> +        @param filename: Location for the screendump
> +        @return: The command's output
> +        """
> +        return self._get_command_output("screendump %s" % filename)
> +
> +
> +    def migrate(self, uri, full_copy=False, incremental_copy=False, wait=False):
> +        """
> +        Migrate.
> +
> +        @param uri: destination URI
> +        @param full_copy: If true, migrate with full disk copy
> +        @param incremental_copy: If true, migrate with incremental disk copy
> +        @param wait: If true, wait for completion
> +        @return: The command's output
> +        """
> +        cmd = "migrate"
> +        if not wait:
> +            cmd += " -d"
> +        if full_copy:
> +            cmd += " -b"
> +        if incremental_copy:
> +            cmd += " -i"
> +        cmd += " %s" % uri
> +        return self._get_command_output(cmd)
> +
> +
> +    def migrate_set_speed(self, value):
> +        """
> +        Set maximum speed (in bytes/sec) for migrations.
> +
> +        @param value: Speed in bytes/sec
> +        @return: The command's output
> +        """
> +        return self._get_command_output("migrate_set_speed %s" % value)
> +
> +
> +    def sendkey(self, keystr, hold_time=1):
> +        """
> +        Send key combination to VM.
> +
> +        @param keystr: Key combination string
> +        @param hold_time: Hold time in ms (should normally stay 1 ms)
> +        @return: The command's output
> +        """
> +        return self._get_command_output("sendkey %s %s" % (keystr, hold_time))
> +
> +
> +    def mouse_move(self, dx, dy):
> +        """
> +        Move mouse.
> +
> +        @param dx: X amount
> +        @param dy: Y amount
> +        @return: The command's output
> +        """
> +        return self._get_command_output("mouse_move %d %d" % (dx, dy))
> +
> +
> +    def mouse_button(self, state):
> +        """
> +        Set mouse button state.
> +
> +        @param state: Button state (1=L, 2=M, 4=R)
> +        @return: The command's output
> +        """
> +        return self._get_command_output("mouse_button %d" % state)

^ permalink raw reply	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2010-06-17 20:40 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2010-06-14 16:54 [KVM-AUTOTEST PATCH v2 1/3] KVM test: add kvm_monitor.py, an interface to QEMU monitors Michael Goldish
2010-06-14 16:55 ` [KVM-AUTOTEST PATCH v2 2/3] KVM test: use new monitor interface Michael Goldish
2010-06-14 16:55   ` [KVM-AUTOTEST PATCH v2 3/3] KVM test: kvm_monitor.py: add QMP interface Michael Goldish
2010-06-17 20:40 ` [KVM-AUTOTEST PATCH v2 1/3] KVM test: add kvm_monitor.py, an interface to QEMU monitors Lucas Meneghel Rodrigues

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.