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 <mgold...@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 majord...@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html

--
To unsubscribe from this list: send the line "unsubscribe kvm" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to