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