All of lore.kernel.org
 help / color / mirror / Atom feed
* [KVM-AUTOTEST PATCH 01/14] KVM test: tests_base.cfg.sample: remove inline comments
@ 2010-06-13 14:33 Michael Goldish
  2010-06-13 14:33 ` [KVM-AUTOTEST PATCH 02/14] KVM test: tests_base.cfg.sample: style modifications Michael Goldish
  0 siblings, 1 reply; 18+ messages in thread
From: Michael Goldish @ 2010-06-13 14:33 UTC (permalink / raw)
  To: autotest, kvm

Inline comments are not supported (yet?) and break the parsing of
tests_base.cfg.sample.

Signed-off-by: Michael Goldish <mgoldish@redhat.com>
---
 client/tests/kvm/tests_base.cfg.sample |   12 ++++++++----
 1 files changed, 8 insertions(+), 4 deletions(-)

diff --git a/client/tests/kvm/tests_base.cfg.sample b/client/tests/kvm/tests_base.cfg.sample
index ab8922b..84a903b 100644
--- a/client/tests/kvm/tests_base.cfg.sample
+++ b/client/tests/kvm/tests_base.cfg.sample
@@ -86,22 +86,26 @@ variants:
         initrd = initrd.img
         nic_mode = tap
         variants:
-            - cdrom: # install guest from cdrom 
+            # Install guest from cdrom 
+            - cdrom:
                 medium = cdrom
                 nic_mode = user
                 redirs += " unattended_install"
                 kernel =
                 initrd = 
-            - url: # install guest from http/ftp url
+            # Install guest from http/ftp url
+            - url:
                 medium = url
                 extra_params += " --append ks=floppy"
                 url = REPLACE_THIS_WITH_TREE_URL
-            - nfs: # install guest from nfs nfs_server:nfs_dir
+            # Install guest from nfs nfs_server:nfs_dir
+            - nfs:
                 medium = nfs
                 extra_params += " --append ks=floppy"
                 nfs_server = REPLACE_THIS_WITH_NFS_SERVER
                 nfs_dir = REPLACE_THIS_WITH_NFS_DIRECTORY
-            - remote_ks: # install guest with a remote kickstart
+            # Install guest with a remote kickstart
+            - remote_ks:
                 medium = url
                 extra_params += " --append ks=REPLACE_THIS_WITH_URL_OF_KS"
                 url = REPLACE_THIS_WITH_TREE_URL
-- 
1.5.4.1

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

* [KVM-AUTOTEST PATCH 02/14] KVM test: tests_base.cfg.sample: style modifications
  2010-06-13 14:33 [KVM-AUTOTEST PATCH 01/14] KVM test: tests_base.cfg.sample: remove inline comments Michael Goldish
@ 2010-06-13 14:33 ` Michael Goldish
  2010-06-13 14:33   ` [KVM-AUTOTEST PATCH 03/14] KVM test: kvm_utils.py: warn about exceptions raised during unpickling of env Michael Goldish
  0 siblings, 1 reply; 18+ messages in thread
From: Michael Goldish @ 2010-06-13 14:33 UTC (permalink / raw)
  To: autotest, kvm

Try to exclude tests as soon as possible.
(Also remove broken linux_s3 exception at the same time.)

Signed-off-by: Michael Goldish <mgoldish@redhat.com>
---
 client/tests/kvm/tests_base.cfg.sample |   22 ++++++----------------
 1 files changed, 6 insertions(+), 16 deletions(-)

diff --git a/client/tests/kvm/tests_base.cfg.sample b/client/tests/kvm/tests_base.cfg.sample
index 84a903b..b302557 100644
--- a/client/tests/kvm/tests_base.cfg.sample
+++ b/client/tests/kvm/tests_base.cfg.sample
@@ -982,7 +982,7 @@ variants:
 
     # Windows section
     - @Windows:
-        no autotest linux_s3 vlan_tag
+        no autotest linux_s3 vlan_tag ioquit unattended_install.(url|nfs|remote_ks)
         shutdown_command = shutdown /s /f /t 0
         reboot_command = shutdown /r /f /t 0
         status_test_command = echo %errorlevel%
@@ -1368,13 +1368,6 @@ variants:
                 md5sum = 9fae22f2666369968a76ef59e9a81ced
 
 
-linux_s3
-    only Linux
-
-unattended_install.url|unattended_install.nfs|unattended_install.remote_ks:
-    only Linux
-
-
 variants:
     - @up:
         no autotest.npb autotest.tsc
@@ -1414,17 +1407,20 @@ virtio|virtio_blk|e1000|balloon_check:
 variants:
     - @qcow2:
         image_format = qcow2
-        post_command = " python scripts/check_image.py;"
-        remove_image = no
+        post_command += " python scripts/check_image.py;"
         post_command_timeout = 600
         post_command_noncritical = yes
+        ioquit:
+            post_command_noncritical = no
     - vmdk:
+        no ioquit
         only Fedora Ubuntu Windows
         only smp2
         only rtl8139
         only acpi
         image_format = vmdk
     - raw:
+        no ioquit
         only Fedora Ubuntu Windows
         only smp2
         only rtl8139
@@ -1439,12 +1435,6 @@ variants:
         extra_params += " -mem-path /mnt/kvm_hugepage"
 
 
-ioquit:
-    post_command_noncritical = no
-    only qcow2
-    only Linux
-
-
 variants:
     - @no_pci_assignable:
         pci_assignable = no
-- 
1.5.4.1

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

* [KVM-AUTOTEST PATCH 03/14] KVM test: kvm_utils.py: warn about exceptions raised during unpickling of env
  2010-06-13 14:33 ` [KVM-AUTOTEST PATCH 02/14] KVM test: tests_base.cfg.sample: style modifications Michael Goldish
@ 2010-06-13 14:33   ` Michael Goldish
  2010-06-13 14:33     ` [KVM-AUTOTEST PATCH 04/14] KVM test: kvm_utils.py: remove unnecessary imports Michael Goldish
  0 siblings, 1 reply; 18+ messages in thread
From: Michael Goldish @ 2010-06-13 14:33 UTC (permalink / raw)
  To: autotest, kvm

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

diff --git a/client/tests/kvm/kvm_utils.py b/client/tests/kvm/kvm_utils.py
index 0da7015..82ecb77 100644
--- a/client/tests/kvm/kvm_utils.py
+++ b/client/tests/kvm/kvm_utils.py
@@ -35,7 +35,8 @@ def load_env(filename, default={}):
         return obj
     # Almost any exception can be raised during unpickling, so let's catch
     # them all
-    except:
+    except Exception, e:
+        logging.warn(e)
         return default
 
 
-- 
1.5.4.1

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

* [KVM-AUTOTEST PATCH 04/14] KVM test: kvm_utils.py: remove unnecessary imports
  2010-06-13 14:33   ` [KVM-AUTOTEST PATCH 03/14] KVM test: kvm_utils.py: warn about exceptions raised during unpickling of env Michael Goldish
@ 2010-06-13 14:33     ` Michael Goldish
  2010-06-13 14:33       ` [KVM-AUTOTEST PATCH 05/14] KVM test: kvm_vm.py: make get_pid() return the pid of the VM itself Michael Goldish
  0 siblings, 1 reply; 18+ messages in thread
From: Michael Goldish @ 2010-06-13 14:33 UTC (permalink / raw)
  To: autotest, kvm

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

diff --git a/client/tests/kvm/kvm_utils.py b/client/tests/kvm/kvm_utils.py
index 82ecb77..0ea5a8a 100644
--- a/client/tests/kvm/kvm_utils.py
+++ b/client/tests/kvm/kvm_utils.py
@@ -4,8 +4,7 @@ KVM test utility functions.
 @copyright: 2008-2009 Red Hat Inc.
 """
 
-import thread, subprocess, time, string, random, socket, os, signal
-import select, re, logging, commands, cPickle, pty
+import time, string, random, socket, os, signal, re, logging, commands, cPickle
 from autotest_lib.client.bin import utils
 from autotest_lib.client.common_lib import error, logging_config
 import kvm_subprocess
-- 
1.5.4.1

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

* [KVM-AUTOTEST PATCH 05/14] KVM test: kvm_vm.py: make get_pid() return the pid of the VM itself
  2010-06-13 14:33     ` [KVM-AUTOTEST PATCH 04/14] KVM test: kvm_utils.py: remove unnecessary imports Michael Goldish
@ 2010-06-13 14:33       ` Michael Goldish
  2010-06-13 14:33         ` [KVM-AUTOTEST PATCH 06/14] KVM test: kvm_vm.py: correct get_shared_meminfo() Michael Goldish
  0 siblings, 1 reply; 18+ messages in thread
From: Michael Goldish @ 2010-06-13 14:33 UTC (permalink / raw)
  To: autotest, kvm

Currently get_pid() returns the pid of the parent shell process.  Make it
return the pid of the VM itself, and use vm.get_shell_pid() when the pid
of the parent shell process is required.

Signed-off-by: Michael Goldish <mgoldish@redhat.com>
---
 client/tests/kvm/kvm_vm.py          |   26 +++++++++++++++++++++-----
 client/tests/kvm/tests/timedrift.py |    2 +-
 2 files changed, 22 insertions(+), 6 deletions(-)

diff --git a/client/tests/kvm/kvm_vm.py b/client/tests/kvm/kvm_vm.py
index 67a13d7..9d3539f 100755
--- a/client/tests/kvm/kvm_vm.py
+++ b/client/tests/kvm/kvm_vm.py
@@ -594,8 +594,7 @@ class VM:
                 self.destroy()
                 return False
 
-            logging.debug("VM appears to be alive with PID %d",
-                          self.process.get_pid())
+            logging.debug("VM appears to be alive with PID %s", self.get_pid())
             return True
 
         finally:
@@ -704,8 +703,7 @@ class VM:
                 logging.debug("VM is already down")
                 return
 
-            logging.debug("Destroying VM with PID %d..." %
-                          self.process.get_pid())
+            logging.debug("Destroying VM with PID %s...", self.get_pid())
 
             if gracefully and self.params.get("shutdown_command"):
                 # Try to destroy with shell command
@@ -853,7 +851,25 @@ class VM:
 
     def get_pid(self):
         """
-        Return the VM's PID.
+        Return the VM's PID.  If the VM is dead return None.
+
+        @note: This works under the assumption that self.process.get_pid()
+        returns the PID of the parent shell process.
+        """
+        try:
+            children = commands.getoutput("ps --ppid=%d -o pid=" %
+                                          self.process.get_pid()).split()
+            return int(children[0])
+        except (TypeError, IndexError, ValueError):
+            return None
+
+
+    def get_shell_pid(self):
+        """
+        Return the PID of the parent shell process.
+
+        @note: This works under the assumption that self.process.get_pid()
+        returns the PID of the parent shell process.
         """
         return self.process.get_pid()
 
diff --git a/client/tests/kvm/tests/timedrift.py b/client/tests/kvm/tests/timedrift.py
index 9cb7489..bb23830 100644
--- a/client/tests/kvm/tests/timedrift.py
+++ b/client/tests/kvm/tests/timedrift.py
@@ -80,7 +80,7 @@ def run_timedrift(test, params, env):
 
     try:
         # Set the VM's CPU affinity
-        prev_affinity = set_cpu_affinity(vm.get_pid(), cpu_mask)
+        prev_affinity = set_cpu_affinity(vm.get_shell_pid(), cpu_mask)
 
         try:
             # Open shell sessions with the guest
-- 
1.5.4.1

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

* [KVM-AUTOTEST PATCH 06/14] KVM test: kvm_vm.py: correct get_shared_meminfo()
  2010-06-13 14:33       ` [KVM-AUTOTEST PATCH 05/14] KVM test: kvm_vm.py: make get_pid() return the pid of the VM itself Michael Goldish
@ 2010-06-13 14:33         ` Michael Goldish
  2010-06-13 14:33           ` [KVM-AUTOTEST PATCH 07/14] KVM test: kvm_vm.py: correct add_smp() mistake in make_qemu_command() Michael Goldish
  0 siblings, 1 reply; 18+ messages in thread
From: Michael Goldish @ 2010-06-13 14:33 UTC (permalink / raw)
  To: autotest, kvm

- Use open("filename") instead of os.popen("cat filename")
- Use self.get_pid() to obtain the PID
- Return the result as a float

Signed-off-by: Michael Goldish <mgoldish@redhat.com>
---
 client/tests/kvm/kvm_vm.py |    7 +++----
 1 files changed, 3 insertions(+), 4 deletions(-)

diff --git a/client/tests/kvm/kvm_vm.py b/client/tests/kvm/kvm_vm.py
index 9d3539f..f65d967 100755
--- a/client/tests/kvm/kvm_vm.py
+++ b/client/tests/kvm/kvm_vm.py
@@ -884,11 +884,10 @@ class VM:
             logging.error("Could not get shared memory info from dead VM.")
             return None
 
-        cmd = "cat /proc/%d/statm" % self.params.get('pid_' + self.name)
-        shm = int(os.popen(cmd).readline().split()[2])
+        filename = "/proc/%d/statm" % self.get_pid()
+        shm = int(open(filename).read().split()[2])
         # statm stores informations in pages, translate it to MB
-        shm = shm * 4 / 1024
-        return shm
+        return shm * 4.0 / 1024
 
 
     def remote_login(self, nic_index=0, timeout=10):
-- 
1.5.4.1

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

* [KVM-AUTOTEST PATCH 07/14] KVM test: kvm_vm.py: correct add_smp() mistake in make_qemu_command()
  2010-06-13 14:33         ` [KVM-AUTOTEST PATCH 06/14] KVM test: kvm_vm.py: correct get_shared_meminfo() Michael Goldish
@ 2010-06-13 14:33           ` Michael Goldish
  2010-06-13 14:33             ` [KVM-AUTOTEST PATCH 08/14] KVM test: kvm_vm.py: use shell quoting in make_qemu_command() where appropriate Michael Goldish
  0 siblings, 1 reply; 18+ messages in thread
From: Michael Goldish @ 2010-06-13 14:33 UTC (permalink / raw)
  To: autotest, kvm

add_smp() is defined but not used.

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

diff --git a/client/tests/kvm/kvm_vm.py b/client/tests/kvm/kvm_vm.py
index f65d967..039fbff 100755
--- a/client/tests/kvm/kvm_vm.py
+++ b/client/tests/kvm/kvm_vm.py
@@ -350,7 +350,7 @@ class VM:
 
         smp = params.get("smp")
         if smp:
-            qemu_cmd += " -smp %s" % smp
+            qemu_cmd += add_smp(help, smp)
 
         iso = params.get("cdrom")
         if iso:
-- 
1.5.4.1

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

* [KVM-AUTOTEST PATCH 08/14] KVM test: kvm_vm.py: use shell quoting in make_qemu_command() where appropriate
  2010-06-13 14:33           ` [KVM-AUTOTEST PATCH 07/14] KVM test: kvm_vm.py: correct add_smp() mistake in make_qemu_command() Michael Goldish
@ 2010-06-13 14:33             ` Michael Goldish
  2010-06-13 14:33               ` [KVM-AUTOTEST PATCH 09/14] KVM test: remove reference to _screendump_thread at postprocessing Michael Goldish
  0 siblings, 1 reply; 18+ messages in thread
From: Michael Goldish @ 2010-06-13 14:33 UTC (permalink / raw)
  To: autotest, kvm

Signed-off-by: Michael Goldish <mgoldish@redhat.com>
---
 client/tests/kvm/kvm_vm.py |   36 ++++++++++++++++++------------------
 1 files changed, 18 insertions(+), 18 deletions(-)

diff --git a/client/tests/kvm/kvm_vm.py b/client/tests/kvm/kvm_vm.py
index 039fbff..e40abb4 100755
--- a/client/tests/kvm/kvm_vm.py
+++ b/client/tests/kvm/kvm_vm.py
@@ -204,7 +204,7 @@ class VM:
             return " -name '%s'" % name
 
         def add_unix_socket_monitor(help, filename):
-            return " -monitor unix:%s,server,nowait" % filename
+            return " -monitor unix:'%s',server,nowait" % filename
 
         def add_mem(help, mem):
             return " -m %s" % mem
@@ -214,18 +214,18 @@ class VM:
 
         def add_cdrom(help, filename, index=2):
             if has_option(help, "drive"):
-                return " -drive file=%s,index=%d,media=cdrom" % (filename,
-                                                                 index)
+                return " -drive file='%s',index=%d,media=cdrom" % (filename,
+                                                                   index)
             else:
-                return " -cdrom %s" % filename
+                return " -cdrom '%s'" % filename
 
         def add_drive(help, filename, format=None, cache=None, werror=None,
                       serial=None, snapshot=False, boot=False):
-            cmd = " -drive file=%s" % filename
+            cmd = " -drive file='%s'" % filename
             if format: cmd += ",if=%s" % format
             if cache: cmd += ",cache=%s" % cache
             if werror: cmd += ",werror=%s" % werror
-            if serial: cmd += ",serial=%s" % serial
+            if serial: cmd += ",serial='%s'" % serial
             if snapshot: cmd += ",snapshot=on"
             if boot: cmd += ",boot=on"
             return cmd
@@ -233,23 +233,23 @@ class VM:
         def add_nic(help, vlan, model=None, mac=None):
             cmd = " -net nic,vlan=%d" % vlan
             if model: cmd += ",model=%s" % model
-            if mac: cmd += ",macaddr=%s" % mac
+            if mac: cmd += ",macaddr='%s'" % mac
             return cmd
 
         def add_net(help, vlan, mode, ifname=None, script=None,
                     downscript=None):
             cmd = " -net %s,vlan=%d" % (mode, vlan)
             if mode == "tap":
-                if ifname: cmd += ",ifname=%s" % ifname
-                if script: cmd += ",script=%s" % script
-                cmd += ",downscript=%s" % (downscript or "no")
+                if ifname: cmd += ",ifname='%s'" % ifname
+                if script: cmd += ",script='%s'" % script
+                cmd += ",downscript='%s'" % (downscript or "no")
             return cmd
 
         def add_floppy(help, filename):
-            return " -fda %s" % filename
+            return " -fda '%s'" % filename
 
         def add_tftp(help, filename):
-            return " -tftp %s" % filename
+            return " -tftp '%s'" % filename
 
         def add_tcp_redir(help, host_port, guest_port):
             return " -redir tcp:%s::%s" % (host_port, guest_port)
@@ -267,22 +267,22 @@ class VM:
             return " -nographic"
 
         def add_uuid(help, uuid):
-            return " -uuid %s" % uuid
+            return " -uuid '%s'" % uuid
 
         def add_pcidevice(help, host):
-            return " -pcidevice host=%s" % host
+            return " -pcidevice host='%s'" % host
 
         def add_initrd(help, filename):
-            return " -initrd %s" % filename
+            return " -initrd '%s'" % filename
 
         def add_kernel(help, filename):
-            return " -kernel %s" % filename
+            return " -kernel '%s'" % filename
 
         def add_kernel_cmdline(help, cmdline):
-            return " -append %s" % cmdline
+            return " -append '%s'" % cmdline
 
         def add_testdev(help, filename):
-            return (" -chardev file,id=testlog,path=%s"
+            return (" -chardev file,id=testlog,path='%s'"
                     " -device testdev,chardev=testlog" % filename)
 
         # End of command line option wrappers
-- 
1.5.4.1

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

* [KVM-AUTOTEST PATCH 09/14] KVM test: remove reference to _screendump_thread at postprocessing
  2010-06-13 14:33             ` [KVM-AUTOTEST PATCH 08/14] KVM test: kvm_vm.py: use shell quoting in make_qemu_command() where appropriate Michael Goldish
@ 2010-06-13 14:33               ` Michael Goldish
  2010-06-13 14:33                 ` [KVM-AUTOTEST PATCH 10/14] KVM test: kvm_subprocess.py: use sys.executable instead of hardcoded 'python' Michael Goldish
  0 siblings, 1 reply; 18+ messages in thread
From: Michael Goldish @ 2010-06-13 14:33 UTC (permalink / raw)
  To: autotest, kvm

_screendump_thread contains a reference to 'env' which prevents VMs from being
garbage collected.  This makes a difference for multi-iteration tests where
several tests run consecutively in the same process.  Removing the reference
to _screendump_thread also removes a reference to VMs, thus allowing them to be
garbage collected.  This is mainly important for the new monitor classes.

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

diff --git a/client/tests/kvm/kvm_preprocessing.py b/client/tests/kvm/kvm_preprocessing.py
index 318bf3f..76c8268 100644
--- a/client/tests/kvm/kvm_preprocessing.py
+++ b/client/tests/kvm/kvm_preprocessing.py
@@ -285,6 +285,8 @@ def postprocess(test, params, env):
         logging.debug("Terminating screendump thread...")
         _screendump_thread_termination_event.set()
         _screendump_thread.join(10)
+        _screendump_thread = None
+        _screendump_thread_termination_event = None
 
     # Warn about corrupt PPM files
     for f in glob.glob(os.path.join(test.debugdir, "*.ppm")):
-- 
1.5.4.1

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

* [KVM-AUTOTEST PATCH 10/14] KVM test: kvm_subprocess.py: use sys.executable instead of hardcoded 'python'
  2010-06-13 14:33               ` [KVM-AUTOTEST PATCH 09/14] KVM test: remove reference to _screendump_thread at postprocessing Michael Goldish
@ 2010-06-13 14:33                 ` Michael Goldish
  2010-06-13 14:33                   ` [KVM-AUTOTEST PATCH 11/14] KVM test: add kvm_monitor.py, an interface to QEMU monitors Michael Goldish
  0 siblings, 1 reply; 18+ messages in thread
From: Michael Goldish @ 2010-06-13 14:33 UTC (permalink / raw)
  To: autotest, kvm

When running the server part of kvm_subprocess, use sys.executable instead of
'python'.  'python' might refer to a different Python version from the
currently running one, e.g. if autotest is executed using 'python26' instead
of the default python interpreter.

Signed-off-by: Michael Goldish <mgoldish@redhat.com>
---
 client/tests/kvm/kvm_subprocess.py |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)

diff --git a/client/tests/kvm/kvm_subprocess.py b/client/tests/kvm/kvm_subprocess.py
index 9222d33..2d70146 100755
--- a/client/tests/kvm/kvm_subprocess.py
+++ b/client/tests/kvm/kvm_subprocess.py
@@ -337,7 +337,7 @@ class kvm_spawn:
 
         # Start the server (which runs the command)
         if command:
-            sub = subprocess.Popen("python %s" % __file__,
+            sub = subprocess.Popen("%s %s" % (sys.executable, __file__),
                                    shell=True,
                                    stdin=subprocess.PIPE,
                                    stdout=subprocess.PIPE,
-- 
1.5.4.1

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

* [KVM-AUTOTEST PATCH 11/14] KVM test: add kvm_monitor.py, an interface to QEMU monitors
  2010-06-13 14:33                 ` [KVM-AUTOTEST PATCH 10/14] KVM test: kvm_subprocess.py: use sys.executable instead of hardcoded 'python' Michael Goldish
@ 2010-06-13 14:33                   ` Michael Goldish
  2010-06-13 14:33                     ` [KVM-AUTOTEST PATCH 12/14] KVM test: use new monitor interface Michael Goldish
  2010-06-14  1:32                     ` [Autotest] [KVM-AUTOTEST PATCH 11/14] KVM test: add kvm_monitor.py, an interface to QEMU monitors Amos Kong
  0 siblings, 2 replies; 18+ messages in thread
From: Michael Goldish @ 2010-06-13 14:33 UTC (permalink / raw)
  To: autotest, kvm

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.

Signed-off-by: Michael Goldish <mgoldish@redhat.com>
---
 client/tests/kvm/kvm_monitor.py |  356 +++++++++++++++++++++++++++++++++++++++
 1 files changed, 356 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..c5cf9c3
--- /dev/null
+++ b/client/tests/kvm/kvm_monitor.py
@@ -0,0 +1,356 @@
+"""
+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, filename):
+        """
+        Initialize the instance.
+
+        @param filename: Monitor socket filename.
+        @raise MonitorConnectError: Raised if the connection fails
+        """
+        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.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, filename, suppress_exceptions=False):
+        """
+        Connect to the monitor socket and find the (qemu) prompt.
+
+        @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
+        """
+        try:
+            Monitor.__init__(self, 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 = 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 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 help(self):
+        """
+        Send "help" and return the output.
+        """
+        return self._get_command_output("help")
+
+
+    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] 18+ messages in thread

* [KVM-AUTOTEST PATCH 12/14] KVM test: use new monitor interface
  2010-06-13 14:33                   ` [KVM-AUTOTEST PATCH 11/14] KVM test: add kvm_monitor.py, an interface to QEMU monitors Michael Goldish
@ 2010-06-13 14:33                     ` Michael Goldish
  2010-06-13 14:33                       ` [KVM-AUTOTEST PATCH 13/14] KVM test: kvm_monitor.py: add QMP interface Michael Goldish
  2010-06-14  1:32                     ` [Autotest] [KVM-AUTOTEST PATCH 11/14] KVM test: add kvm_monitor.py, an interface to QEMU monitors Amos Kong
  1 sibling, 1 reply; 18+ messages in thread
From: Michael Goldish @ 2010-06-13 14:33 UTC (permalink / raw)
  To: autotest, kvm

- 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).

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                         |  226 +++++++++-----------
 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, 232 insertions(+), 241 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..6aae053 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,20 @@ class VM:
         self.redirs = {}
         self.vnc_port = 5900
         self.uuid = None
+        self.monitors = []
+        self.monitor = None
+        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 +198,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 +305,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 +414,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 +571,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 +581,41 @@ 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 = []
+            self.monitor = None
+            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(
+                                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]
+                # Define the main monitor
+                if params.get("main_monitor") == monitor_name:
+                    self.monitor = monitor
+            # If the main monitor hasn't been defined yet, use the first one
+            if self.monitors and not self.monitor:
+                self.monitor = self.monitors[0]
 
             # Get the output so far, to see if we have any problems with
             # hugepage setup.
@@ -602,89 +637,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 +673,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 "
@@ -747,7 +702,10 @@ class VM:
                 self.pci_assignable.release_devs()
             if self.process:
                 self.process.close()
-            for f in [self.monitor_file_name, self.testlog_file_name]:
+            self.monitors = []
+            self.monitor = None
+            for f in ([self.get_testlog_filename()] +
+                      self.get_monitor_filenames()):
                 try:
                     os.unlink(f)
                 except OSError:
@@ -762,10 +720,7 @@ class VM:
         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 +746,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 +966,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] 18+ messages in thread

* [KVM-AUTOTEST PATCH 13/14] KVM test: kvm_monitor.py: add QMP interface
  2010-06-13 14:33                     ` [KVM-AUTOTEST PATCH 12/14] KVM test: use new monitor interface Michael Goldish
@ 2010-06-13 14:33                       ` Michael Goldish
  2010-06-13 14:33                         ` [KVM-AUTOTEST PATCH 14/14] KVM test: migration: support QMP Michael Goldish
  2010-06-14  2:39                         ` [Autotest] [KVM-AUTOTEST PATCH 13/14] KVM test: kvm_monitor.py: add QMP interface Amos Kong
  0 siblings, 2 replies; 18+ messages in thread
From: Michael Goldish @ 2010-06-13 14:33 UTC (permalink / raw)
  To: autotest, kvm

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.

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

diff --git a/client/tests/kvm/kvm_monitor.py b/client/tests/kvm/kvm_monitor.py
index c5cf9c3..76a1a83 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,10 @@ class MonitorProtocolError(MonitorError):
     pass
 
 
+class QMPCmdError(MonitorError):
+    pass
+
+
 class Monitor:
     """
     Common code for monitor classes.
@@ -114,6 +123,8 @@ class HumanMonitor(Monitor):
                 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, filename)
@@ -354,3 +365,267 @@ 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, filename, suppress_exceptions=False):
+        """
+        Connect to the monitor socket and issue the qmp_capabilities command
+
+        @param filename: Monitor socket filename
+        @raise MonitorConnectError: Raised if the connection fails 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, filename)
+
+            self.protocol = "qmp"
+            self.events = []
+
+            # 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 "
+                                   "events from monitor")
+        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 "
+                                   "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 6aae053..a77cb9c 100755
--- a/client/tests/kvm/kvm_vm.py
+++ b/client/tests/kvm/kvm_vm.py
@@ -584,7 +584,8 @@ class VM:
             # Establish monitor connections
             self.monitors = []
             self.monitor = None
-            for monitor_name in kvm_utils.get_sub_dict_names(params, "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
@@ -592,7 +593,8 @@ class VM:
                     try:
                         if monitor_params.get("monitor_type") == "qmp":
                             # Add a QMP monitor: not implemented yet
-                            monitor = None
+                            monitor = kvm_monitor.QMPMonitor(
+                                self.get_monitor_filename(monitor_name))
                         else:
                             # Add a "human" monitor
                             monitor = kvm_monitor.HumanMonitor(
-- 
1.5.4.1

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

* [KVM-AUTOTEST PATCH 14/14] KVM test: migration: support QMP
  2010-06-13 14:33                       ` [KVM-AUTOTEST PATCH 13/14] KVM test: kvm_monitor.py: add QMP interface Michael Goldish
@ 2010-06-13 14:33                         ` Michael Goldish
  2010-06-14  2:39                         ` [Autotest] [KVM-AUTOTEST PATCH 13/14] KVM test: kvm_monitor.py: add QMP interface Amos Kong
  1 sibling, 0 replies; 18+ messages in thread
From: Michael Goldish @ 2010-06-13 14:33 UTC (permalink / raw)
  To: autotest, kvm

If the value returned from a monitor method call is a string, treat it as
human monitor output.  Otherwise treat it as QMP output.

Signed-off-by: Michael Goldish <mgoldish@redhat.com>
---
 client/tests/kvm/kvm_test_utils.py |   15 ++++++++++++---
 1 files changed, 12 insertions(+), 3 deletions(-)

diff --git a/client/tests/kvm/kvm_test_utils.py b/client/tests/kvm/kvm_test_utils.py
index c3b6b8a..9fdea87 100644
--- a/client/tests/kvm/kvm_test_utils.py
+++ b/client/tests/kvm/kvm_test_utils.py
@@ -120,15 +120,24 @@ def migrate(vm, env=None):
     # Helper functions
     def mig_finished():
         o = vm.monitor.info("migrate")
-        return "status: active" not in o
+        if isinstance(o, str):
+            return "status: active" not in o
+        else:
+            return o.get("status") != "active"
 
     def mig_succeeded():
         o = vm.monitor.info("migrate")
-        return "status: completed" in o
+        if isinstance(o, str):
+            return "status: completed" in o
+        else:
+            return o.get("status") == "completed"
 
     def mig_failed():
         o = vm.monitor.info("migrate")
-        return "status: failed" in o
+        if isinstance(o, str):
+            return "status: failed" in o
+        else:
+            return o.get("status") == "failed"
 
     # Clone the source VM and ask the clone to wait for incoming migration
     dest_vm = vm.clone()
-- 
1.5.4.1

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

* Re: [Autotest] [KVM-AUTOTEST PATCH 11/14] KVM test: add kvm_monitor.py, an interface to QEMU monitors
  2010-06-13 14:33                   ` [KVM-AUTOTEST PATCH 11/14] KVM test: add kvm_monitor.py, an interface to QEMU monitors Michael Goldish
  2010-06-13 14:33                     ` [KVM-AUTOTEST PATCH 12/14] KVM test: use new monitor interface Michael Goldish
@ 2010-06-14  1:32                     ` Amos Kong
  2010-06-14  5:59                       ` Michael Goldish
  1 sibling, 1 reply; 18+ messages in thread
From: Amos Kong @ 2010-06-14  1:32 UTC (permalink / raw)
  To: Michael Goldish; +Cc: autotest, kvm

On Sun, Jun 13, 2010 at 05:33:42PM +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.
> 
> Signed-off-by: Michael Goldish <mgoldish@redhat.com>
> ---
>  client/tests/kvm/kvm_monitor.py |  356 +++++++++++++++++++++++++++++++++++++++
>  1 files changed, 356 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..c5cf9c3
> --- /dev/null
> +++ b/client/tests/kvm/kvm_monitor.py
> @@ -0,0 +1,356 @@
> +"""
> +Interfaces to the QEMU monitor.
> +
> +@copyright: 2008-2010 Red Hat Inc.
> +"""
> +

...

> +class HumanMonitor(Monitor):
> +    """
> +    Wraps "human monitor" commands.
> +    """
> +
> +    def __init__(self, filename, suppress_exceptions=False):
> +        """
> +        Connect to the monitor socket and find the (qemu) prompt.
> +
> +        @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
> +        """
> +        try:
> +            Monitor.__init__(self, 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 = self._get_command_output("help")

Hi Michael,

Here, self.help is a string type.
But you repeatedly define a sub function self.help() below.

If I call vm.monitor.help() in testcase, will touch this err:
 TypeError: 'str' object is not callable

How about using self.help_str = self._get_command_output("help")?
and remove help sub function, Not only repeated name, but repeated function.


Amos

> +
> +        except MonitorError, e:
> +            if suppress_exceptions:
> +                logging.warn(e)
> +            else:
> +                raise
> +

...

> +
> +    def help(self):
> +        """
> +        Send "help" and return the output.
> +        """
> +        return self._get_command_output("help")

...

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

* Re: [Autotest] [KVM-AUTOTEST PATCH 13/14] KVM test: kvm_monitor.py: add QMP interface
  2010-06-13 14:33                       ` [KVM-AUTOTEST PATCH 13/14] KVM test: kvm_monitor.py: add QMP interface Michael Goldish
  2010-06-13 14:33                         ` [KVM-AUTOTEST PATCH 14/14] KVM test: migration: support QMP Michael Goldish
@ 2010-06-14  2:39                         ` Amos Kong
  2010-06-14  6:34                           ` Michael Goldish
  1 sibling, 1 reply; 18+ messages in thread
From: Amos Kong @ 2010-06-14  2:39 UTC (permalink / raw)
  To: Michael Goldish; +Cc: autotest, kvm

On Sun, Jun 13, 2010 at 05:33:44PM +0300, Michael Goldish wrote:
> 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.
> 
> Signed-off-by: Michael Goldish <mgoldish@redhat.com>
> ---
>  client/tests/kvm/kvm_monitor.py |  275 +++++++++++++++++++++++++++++++++++++++
>  client/tests/kvm/kvm_vm.py      |    6 +-
>  2 files changed, 279 insertions(+), 2 deletions(-)
> 
> diff --git a/client/tests/kvm/kvm_monitor.py b/client/tests/kvm/kvm_monitor.py
> index c5cf9c3..76a1a83 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,10 @@ class MonitorProtocolError(MonitorError):
>      pass
>  
>  
> +class QMPCmdError(MonitorError):
> +    pass
> +
> +
>  class Monitor:
>      """
>      Common code for monitor classes.
> @@ -114,6 +123,8 @@ class HumanMonitor(Monitor):
>                  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, filename)
> @@ -354,3 +365,267 @@ 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, filename, suppress_exceptions=False):
> +        """
> +        Connect to the monitor socket and issue the qmp_capabilities command
> +
> +        @param filename: Monitor socket filename
> +        @raise MonitorConnectError: Raised if the connection fails 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, filename)
> +
> +            self.protocol = "qmp"
> +            self.events = []
> +
> +            # 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 "
> +                                   "events from monitor")
> +        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 "
> +                                   "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)

Fail to execute get a screendump.  qmp hasn't support this cmd ?

09:55:14 WARNI| ("QMP command 'screendump' failed", {u'data': {u'name': u'screendump'}, u'class': u'CommandNotFound', u'desc': u'The command screendump has not been found'})


> +
> +    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

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

* Re: [Autotest] [KVM-AUTOTEST PATCH 11/14] KVM test: add kvm_monitor.py, an interface to QEMU monitors
  2010-06-14  1:32                     ` [Autotest] [KVM-AUTOTEST PATCH 11/14] KVM test: add kvm_monitor.py, an interface to QEMU monitors Amos Kong
@ 2010-06-14  5:59                       ` Michael Goldish
  0 siblings, 0 replies; 18+ messages in thread
From: Michael Goldish @ 2010-06-14  5:59 UTC (permalink / raw)
  To: Amos Kong; +Cc: autotest, kvm

On 06/14/2010 04:32 AM, Amos Kong wrote:
> On Sun, Jun 13, 2010 at 05:33:42PM +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.
>>
>> Signed-off-by: Michael Goldish <mgoldish@redhat.com>
>> ---
>>  client/tests/kvm/kvm_monitor.py |  356 +++++++++++++++++++++++++++++++++++++++
>>  1 files changed, 356 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..c5cf9c3
>> --- /dev/null
>> +++ b/client/tests/kvm/kvm_monitor.py
>> @@ -0,0 +1,356 @@
>> +"""
>> +Interfaces to the QEMU monitor.
>> +
>> +@copyright: 2008-2010 Red Hat Inc.
>> +"""
>> +
> 
> ...
> 
>> +class HumanMonitor(Monitor):
>> +    """
>> +    Wraps "human monitor" commands.
>> +    """
>> +
>> +    def __init__(self, filename, suppress_exceptions=False):
>> +        """
>> +        Connect to the monitor socket and find the (qemu) prompt.
>> +
>> +        @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
>> +        """
>> +        try:
>> +            Monitor.__init__(self, 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 = self._get_command_output("help")
> 
> Hi Michael,
> 
> Here, self.help is a string type.
> But you repeatedly define a sub function self.help() below.
> 
> If I call vm.monitor.help() in testcase, will touch this err:
>  TypeError: 'str' object is not callable

Oops.  I'll fix that.

> How about using self.help_str = self._get_command_output("help")?
> and remove help sub function, Not only repeated name, but repeated function.
> 
> 
> Amos

OK, it might be a good idea to remove help() altogether, especially
since QMP doesn't support it AFAIK.  BTW, testcases are supposed to use
vm.monitor.cmd(), not vm.monitor._get_command_output().

>> +
>> +        except MonitorError, e:
>> +            if suppress_exceptions:
>> +                logging.warn(e)
>> +            else:
>> +                raise
>> +
> 
> ...
> 
>> +
>> +    def help(self):
>> +        """
>> +        Send "help" and return the output.
>> +        """
>> +        return self._get_command_output("help")
> 
> ...
> --
> To unsubscribe from this list: send the line "unsubscribe kvm" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html


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

* Re: [Autotest] [KVM-AUTOTEST PATCH 13/14] KVM test: kvm_monitor.py: add QMP interface
  2010-06-14  2:39                         ` [Autotest] [KVM-AUTOTEST PATCH 13/14] KVM test: kvm_monitor.py: add QMP interface Amos Kong
@ 2010-06-14  6:34                           ` Michael Goldish
  0 siblings, 0 replies; 18+ messages in thread
From: Michael Goldish @ 2010-06-14  6:34 UTC (permalink / raw)
  To: Amos Kong; +Cc: autotest, kvm

On 06/14/2010 05:39 AM, Amos Kong wrote:
> On Sun, Jun 13, 2010 at 05:33:44PM +0300, Michael Goldish wrote:
>> 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.
>>
>> Signed-off-by: Michael Goldish <mgoldish@redhat.com>
>> ---
>>  client/tests/kvm/kvm_monitor.py |  275 +++++++++++++++++++++++++++++++++++++++
>>  client/tests/kvm/kvm_vm.py      |    6 +-
>>  2 files changed, 279 insertions(+), 2 deletions(-)
>>
>> diff --git a/client/tests/kvm/kvm_monitor.py b/client/tests/kvm/kvm_monitor.py
>> index c5cf9c3..76a1a83 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,10 @@ class MonitorProtocolError(MonitorError):
>>      pass
>>  
>>  
>> +class QMPCmdError(MonitorError):
>> +    pass
>> +
>> +
>>  class Monitor:
>>      """
>>      Common code for monitor classes.
>> @@ -114,6 +123,8 @@ class HumanMonitor(Monitor):
>>                  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, filename)
>> @@ -354,3 +365,267 @@ 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, filename, suppress_exceptions=False):
>> +        """
>> +        Connect to the monitor socket and issue the qmp_capabilities command
>> +
>> +        @param filename: Monitor socket filename
>> +        @raise MonitorConnectError: Raised if the connection fails 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, filename)
>> +
>> +            self.protocol = "qmp"
>> +            self.events = []
>> +
>> +            # 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 "
>> +                                   "events from monitor")
>> +        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 "
>> +                                   "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)
> 
> Fail to execute get a screendump.  qmp hasn't support this cmd ?
> 
> 09:55:14 WARNI| ("QMP command 'screendump' failed", {u'data': {u'name': u'screendump'}, u'class': u'CommandNotFound', u'desc': u'The command screendump has not been found'})

The latest git has screendump support.

BTW, if you're interested in the error data, you can use this syntax:

try:
    ...
except QMPCmdError, (msg, error):
    # here msg is "QMP command ... failed"
    # and error is a dict with keys 'data', 'class' and 'desc'

>> +
>> +    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
> --
> To unsubscribe from this list: send the line "unsubscribe kvm" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html


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

end of thread, other threads:[~2010-06-14  6:35 UTC | newest]

Thread overview: 18+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2010-06-13 14:33 [KVM-AUTOTEST PATCH 01/14] KVM test: tests_base.cfg.sample: remove inline comments Michael Goldish
2010-06-13 14:33 ` [KVM-AUTOTEST PATCH 02/14] KVM test: tests_base.cfg.sample: style modifications Michael Goldish
2010-06-13 14:33   ` [KVM-AUTOTEST PATCH 03/14] KVM test: kvm_utils.py: warn about exceptions raised during unpickling of env Michael Goldish
2010-06-13 14:33     ` [KVM-AUTOTEST PATCH 04/14] KVM test: kvm_utils.py: remove unnecessary imports Michael Goldish
2010-06-13 14:33       ` [KVM-AUTOTEST PATCH 05/14] KVM test: kvm_vm.py: make get_pid() return the pid of the VM itself Michael Goldish
2010-06-13 14:33         ` [KVM-AUTOTEST PATCH 06/14] KVM test: kvm_vm.py: correct get_shared_meminfo() Michael Goldish
2010-06-13 14:33           ` [KVM-AUTOTEST PATCH 07/14] KVM test: kvm_vm.py: correct add_smp() mistake in make_qemu_command() Michael Goldish
2010-06-13 14:33             ` [KVM-AUTOTEST PATCH 08/14] KVM test: kvm_vm.py: use shell quoting in make_qemu_command() where appropriate Michael Goldish
2010-06-13 14:33               ` [KVM-AUTOTEST PATCH 09/14] KVM test: remove reference to _screendump_thread at postprocessing Michael Goldish
2010-06-13 14:33                 ` [KVM-AUTOTEST PATCH 10/14] KVM test: kvm_subprocess.py: use sys.executable instead of hardcoded 'python' Michael Goldish
2010-06-13 14:33                   ` [KVM-AUTOTEST PATCH 11/14] KVM test: add kvm_monitor.py, an interface to QEMU monitors Michael Goldish
2010-06-13 14:33                     ` [KVM-AUTOTEST PATCH 12/14] KVM test: use new monitor interface Michael Goldish
2010-06-13 14:33                       ` [KVM-AUTOTEST PATCH 13/14] KVM test: kvm_monitor.py: add QMP interface Michael Goldish
2010-06-13 14:33                         ` [KVM-AUTOTEST PATCH 14/14] KVM test: migration: support QMP Michael Goldish
2010-06-14  2:39                         ` [Autotest] [KVM-AUTOTEST PATCH 13/14] KVM test: kvm_monitor.py: add QMP interface Amos Kong
2010-06-14  6:34                           ` Michael Goldish
2010-06-14  1:32                     ` [Autotest] [KVM-AUTOTEST PATCH 11/14] KVM test: add kvm_monitor.py, an interface to QEMU monitors Amos Kong
2010-06-14  5:59                       ` Michael Goldish

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.