| Index: client/tests/kvm/kvm_monitor.py
|
| diff --git a/client/tests/kvm/kvm_monitor.py b/client/tests/kvm/kvm_monitor.py
|
| index 8440835fd2487aaf0ddc810a7b114a10c08cb0e1..7e6b594bc242d10574f42a0aac1e869348ccd339 100644
|
| --- a/client/tests/kvm/kvm_monitor.py
|
| +++ b/client/tests/kvm/kvm_monitor.py
|
| @@ -4,7 +4,7 @@ Interfaces to the QEMU monitor.
|
| @copyright: 2008-2010 Red Hat Inc.
|
| """
|
|
|
| -import socket, time, threading, logging
|
| +import socket, time, threading, logging, select
|
| import kvm_utils
|
| try:
|
| import json
|
| @@ -21,7 +21,7 @@ class MonitorConnectError(MonitorError):
|
| pass
|
|
|
|
|
| -class MonitorSendError(MonitorError):
|
| +class MonitorSocketError(MonitorError):
|
| pass
|
|
|
|
|
| @@ -38,7 +38,15 @@ class MonitorNotSupportedError(MonitorError):
|
|
|
|
|
| class QMPCmdError(MonitorError):
|
| - pass
|
| + def __init__(self, cmd, qmp_args, data):
|
| + MonitorError.__init__(self, cmd, qmp_args, data)
|
| + self.cmd = cmd
|
| + self.qmp_args = qmp_args
|
| + self.data = data
|
| +
|
| + def __str__(self):
|
| + return ("QMP command %r failed (arguments: %r, error message: %r)" %
|
| + (self.cmd, self.qmp_args, self.data))
|
|
|
|
|
| class Monitor:
|
| @@ -58,7 +66,6 @@ class Monitor:
|
| 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)
|
| @@ -102,13 +109,19 @@ class Monitor:
|
| return False
|
|
|
|
|
| + def _data_available(self, timeout=0):
|
| + timeout = max(0, timeout)
|
| + return bool(select.select([self._socket], [], [], timeout)[0])
|
| +
|
| +
|
| def _recvall(self):
|
| s = ""
|
| - while True:
|
| + while self._data_available():
|
| try:
|
| data = self._socket.recv(1024)
|
| - except socket.error:
|
| - break
|
| + except socket.error, (errno, msg):
|
| + raise MonitorSocketError("Could not receive data from monitor "
|
| + "(%s)" % msg)
|
| if not data:
|
| break
|
| s += data
|
| @@ -130,7 +143,7 @@ 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
|
| + @note: Other exceptions may be raised. See cmd()'s
|
| docstring.
|
| """
|
| try:
|
| @@ -146,7 +159,7 @@ class HumanMonitor(Monitor):
|
| "Output so far: %r" % o)
|
|
|
| # Save the output of 'help' for future use
|
| - self._help_str = self._get_command_output("help")
|
| + self._help_str = self.cmd("help")
|
|
|
| except MonitorError, e:
|
| if suppress_exceptions:
|
| @@ -158,46 +171,47 @@ class HumanMonitor(Monitor):
|
| # Private methods
|
|
|
| def _read_up_to_qemu_prompt(self, timeout=20):
|
| - o = ""
|
| + s = ""
|
| end_time = time.time() + timeout
|
| - while time.time() < end_time:
|
| + while self._data_available(end_time - time.time()):
|
| + data = self._recvall()
|
| + if not data:
|
| + break
|
| + s += data
|
| 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())
|
| + if s.splitlines()[-1].split()[-1] == "(qemu)":
|
| + return True, "\n".join(s.splitlines()[:-1])
|
| + except IndexError:
|
| + continue
|
| + return False, "\n".join(s.splitlines())
|
|
|
|
|
| - def _send_command(self, command):
|
| + def _send(self, cmd):
|
| """
|
| Send a command without waiting for output.
|
|
|
| - @param command: Command to send
|
| - @return: True if successful, False otherwise
|
| + @param cmd: Command to send
|
| @raise MonitorLockError: Raised if the lock cannot be acquired
|
| - @raise MonitorSendError: Raised if the command cannot be sent
|
| + @raise MonitorSocketError: Raised if a socket error occurs
|
| """
|
| if not self._acquire_lock(20):
|
| raise MonitorLockError("Could not acquire exclusive lock to send "
|
| - "monitor command '%s'" % command)
|
| + "monitor command '%s'" % cmd)
|
|
|
| try:
|
| try:
|
| - self._socket.sendall(command + "\n")
|
| - except socket.error:
|
| - raise MonitorSendError("Could not send monitor command '%s'" %
|
| - command)
|
| + self._socket.sendall(cmd + "\n")
|
| + except socket.error, (errno, msg):
|
| + raise MonitorSocketError("Could not send monitor command '%s' "
|
| + "(%s)" % (cmd, msg))
|
|
|
| finally:
|
| self._lock.release()
|
|
|
|
|
| - def _get_command_output(self, command, timeout=20):
|
| + # Public methods
|
| +
|
| + def cmd(self, command, timeout=20):
|
| """
|
| Send command to the monitor.
|
|
|
| @@ -205,7 +219,7 @@ class HumanMonitor(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 MonitorSocketError: Raised if a socket error occurs
|
| @raise MonitorProtocolError: Raised if the (qemu) prompt cannot be
|
| found after sending the command
|
| """
|
| @@ -217,7 +231,7 @@ class HumanMonitor(Monitor):
|
| # Read any data that might be available
|
| self._recvall()
|
| # Send command
|
| - self._send_command(command)
|
| + self._send(command)
|
| # Read output
|
| s, o = self._read_up_to_qemu_prompt(timeout)
|
| # Remove command echo from output
|
| @@ -234,8 +248,6 @@ class HumanMonitor(Monitor):
|
| self._lock.release()
|
|
|
|
|
| - # Public methods
|
| -
|
| def is_responsive(self):
|
| """
|
| Make sure the monitor is responsive by sending a command.
|
| @@ -243,7 +255,7 @@ class HumanMonitor(Monitor):
|
| @return: True if responsive, False otherwise
|
| """
|
| try:
|
| - self._get_command_output("help")
|
| + self.cmd("info status")
|
| return True
|
| except MonitorError:
|
| return False
|
| @@ -252,39 +264,22 @@ class HumanMonitor(Monitor):
|
| # Command wrappers
|
| # Notes:
|
| # - All of the following commands raise exceptions in a similar manner to
|
| - # cmd() and _get_command_output().
|
| + # cmd().
|
| # - A command wrapper should use self._help_str if it requires information
|
| # about the monitor's capabilities.
|
|
|
| - def cmd(self, command, timeout=20):
|
| - """
|
| - Send a simple command with no parameters and return its output.
|
| - Should only be used for commands that take no parameters and are
|
| - implemented under the same name for both the human and QMP monitors.
|
| -
|
| - @param command: Command to send
|
| - @param timeout: Time duration to wait for (qemu) prompt after command
|
| - @return: The output of the command
|
| - @raise MonitorLockError: Raised if the lock cannot be acquired
|
| - @raise MonitorSendError: Raised if the command cannot be sent
|
| - @raise MonitorProtocolError: Raised if the (qemu) prompt cannot be
|
| - found after sending the command
|
| - """
|
| - return self._get_command_output(command, timeout)
|
| -
|
| -
|
| def quit(self):
|
| """
|
| Send "quit" without waiting for output.
|
| """
|
| - self._send_command("quit")
|
| + self._send("quit")
|
|
|
|
|
| def info(self, what):
|
| """
|
| Request info about something and return the output.
|
| """
|
| - return self._get_command_output("info %s" % what)
|
| + return self.cmd("info %s" % what)
|
|
|
|
|
| def query(self, what):
|
| @@ -301,7 +296,7 @@ class HumanMonitor(Monitor):
|
| @param filename: Location for the screendump
|
| @return: The command's output
|
| """
|
| - return self._get_command_output("screendump %s" % filename)
|
| + return self.cmd("screendump %s" % filename)
|
|
|
|
|
| def migrate(self, uri, full_copy=False, incremental_copy=False, wait=False):
|
| @@ -323,7 +318,7 @@ class HumanMonitor(Monitor):
|
| if incremental_copy:
|
| cmd += " -i"
|
| cmd += " %s" % uri
|
| - return self._get_command_output(cmd)
|
| + return self.cmd(cmd)
|
|
|
|
|
| def migrate_set_speed(self, value):
|
| @@ -333,7 +328,7 @@ class HumanMonitor(Monitor):
|
| @param value: Speed in bytes/sec
|
| @return: The command's output
|
| """
|
| - return self._get_command_output("migrate_set_speed %s" % value)
|
| + return self.cmd("migrate_set_speed %s" % value)
|
|
|
|
|
| def sendkey(self, keystr, hold_time=1):
|
| @@ -344,7 +339,7 @@ class HumanMonitor(Monitor):
|
| @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))
|
| + return self.cmd("sendkey %s %s" % (keystr, hold_time))
|
|
|
|
|
| def mouse_move(self, dx, dy):
|
| @@ -355,7 +350,7 @@ class HumanMonitor(Monitor):
|
| @param dy: Y amount
|
| @return: The command's output
|
| """
|
| - return self._get_command_output("mouse_move %d %d" % (dx, dy))
|
| + return self.cmd("mouse_move %d %d" % (dx, dy))
|
|
|
|
|
| def mouse_button(self, state):
|
| @@ -365,7 +360,7 @@ class HumanMonitor(Monitor):
|
| @param state: Button state (1=L, 2=M, 4=R)
|
| @return: The command's output
|
| """
|
| - return self._get_command_output("mouse_button %d" % state)
|
| + return self.cmd("mouse_button %d" % state)
|
|
|
|
|
| class QMPMonitor(Monitor):
|
| @@ -387,7 +382,7 @@ class QMPMonitor(Monitor):
|
| @raise MonitorNotSupportedError: Raised if json isn't available and
|
| suppress_exceptions is False
|
| @note: Other exceptions may be raised if the qmp_capabilities command
|
| - fails. See _get_command_output's docstring.
|
| + fails. See cmd()'s docstring.
|
| """
|
| try:
|
| Monitor.__init__(self, name, filename)
|
| @@ -408,7 +403,7 @@ class QMPMonitor(Monitor):
|
| while time.time() < end_time:
|
| for obj in self._read_objects():
|
| if "QMP" in obj:
|
| - self._greeting = obj["QMP"]
|
| + self._greeting = obj
|
| break
|
| if self._greeting:
|
| break
|
| @@ -417,7 +412,7 @@ class QMPMonitor(Monitor):
|
| raise MonitorProtocolError("No QMP greeting message received")
|
|
|
| # Issue qmp_capabilities
|
| - self._get_command_output("qmp_capabilities")
|
| + self.cmd("qmp_capabilities")
|
|
|
| except MonitorError, e:
|
| if suppress_exceptions:
|
| @@ -439,7 +434,7 @@ class QMPMonitor(Monitor):
|
|
|
| def _read_objects(self, timeout=5):
|
| """
|
| - Read lines from monitor and try to decode them.
|
| + Read lines from the 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.
|
| @@ -447,99 +442,178 @@ class QMPMonitor(Monitor):
|
| @param timeout: Time to wait for all lines to decode successfully
|
| @return: A list of objects
|
| """
|
| + if not self._data_available():
|
| + return []
|
| s = ""
|
| - objs = []
|
| end_time = time.time() + timeout
|
| - while time.time() < end_time:
|
| + while self._data_available(end_time - time.time()):
|
| s += self._recvall()
|
| + # Make sure all lines are decodable
|
| 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]
|
| + if line:
|
| + try:
|
| + json.loads(line)
|
| + except:
|
| + # Found an incomplete or broken line -- keep reading
|
| + break
|
| else:
|
| # All lines are OK -- stop reading
|
| break
|
| - time.sleep(0.1)
|
| + # Decode all decodable lines
|
| + objs = []
|
| + for line in s.splitlines():
|
| + try:
|
| + objs += [json.loads(line)]
|
| + except:
|
| + pass
|
| # 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, id=None):
|
| + def _send(self, data):
|
| + """
|
| + Send raw data without waiting for response.
|
| +
|
| + @param data: Data to send
|
| + @raise MonitorSocketError: Raised if a socket error occurs
|
| + """
|
| + try:
|
| + self._socket.sendall(data)
|
| + except socket.error, (errno, msg):
|
| + raise MonitorSocketError("Could not send data: %r (%s)" %
|
| + (data, msg))
|
| +
|
| +
|
| + def _get_response(self, id=None, timeout=20):
|
| """
|
| - Send command without waiting for response.
|
| + Read a response from the QMP monitor.
|
| +
|
| + @param id: If not None, look for a response with this id
|
| + @param timeout: Time duration to wait for response
|
| + @return: The response dict, or None if none was found
|
| + """
|
| + end_time = time.time() + timeout
|
| + while self._data_available(end_time - time.time()):
|
| + for obj in self._read_objects():
|
| + if isinstance(obj, dict):
|
| + if id is not None and obj.get("id") != id:
|
| + continue
|
| + if "return" in obj or "error" in obj:
|
| + return obj
|
| +
|
| +
|
| + # Public methods
|
| +
|
| + def cmd(self, cmd, args=None, timeout=20):
|
| + """
|
| + Send a QMP monitor command and return the response.
|
| +
|
| + Note: an id is automatically assigned to the command and the response
|
| + is checked for the presence of the same id.
|
|
|
| @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 MonitorSocketError: Raised if a socket error occurs
|
| + @raise MonitorProtocolError: Raised if no response is received
|
| + @raise QMPCmdError: Raised if the response is an error message
|
| + (the exception's args are (cmd, args, data) where 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:
|
| - cmdobj = self._build_cmd(cmd, args, id)
|
| - try:
|
| - self._socket.sendall(json.dumps(cmdobj) + "\n")
|
| - except socket.error:
|
| - raise MonitorSendError("Could not send QMP command '%s'" % cmd)
|
| + # Read any data that might be available
|
| + self._read_objects()
|
| + # Send command
|
| + id = kvm_utils.generate_random_string(8)
|
| + self._send(json.dumps(self._build_cmd(cmd, args, id)) + "\n")
|
| + # Read response
|
| + r = self._get_response(id, timeout)
|
| + if r is None:
|
| + raise MonitorProtocolError("Received no response to QMP "
|
| + "command '%s', or received a "
|
| + "response with an incorrect id"
|
| + % cmd)
|
| + if "return" in r:
|
| + return r["return"]
|
| + if "error" in r:
|
| + raise QMPCmdError(cmd, args, r["error"])
|
|
|
| finally:
|
| self._lock.release()
|
|
|
|
|
| - def _get_command_output(self, cmd, args=None, timeout=20):
|
| + def cmd_raw(self, data, timeout=20):
|
| """
|
| - Send monitor command and wait for response.
|
| + Send a raw string to the QMP monitor and return the response.
|
| + Unlike cmd(), return the raw response dict without performing any
|
| + checks on it.
|
|
|
| - @param cmd: Command to send
|
| - @param args: A dict containing command arguments, or None
|
| + @param data: The data to send
|
| @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 MonitorSocketError: Raised if a socket error occurs
|
| @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)
|
| + "data: %r" % data)
|
|
|
| try:
|
| - # Read any data that might be available
|
| self._read_objects()
|
| - # Send command
|
| - id = kvm_utils.generate_random_string(8)
|
| - self._send_command(cmd, args, id)
|
| - # Read response
|
| - end_time = time.time() + timeout
|
| - while time.time() < end_time:
|
| - for obj in self._read_objects():
|
| - if isinstance(obj, dict) and obj.get("id") == id:
|
| - 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', or received a response with an "
|
| - "incorrect id" % cmd)
|
| + self._send(data)
|
| + r = self._get_response(None, timeout)
|
| + if r is None:
|
| + raise MonitorProtocolError("Received no response to data: %r" %
|
| + data)
|
| + return r
|
|
|
| finally:
|
| self._lock.release()
|
|
|
|
|
| - # Public methods
|
| + def cmd_obj(self, obj, timeout=20):
|
| + """
|
| + Transform a Python object to JSON, send the resulting string to the QMP
|
| + monitor, and return the response.
|
| + Unlike cmd(), return the raw response dict without performing any
|
| + checks on it.
|
| +
|
| + @param obj: The object to send
|
| + @param timeout: Time duration to wait for response
|
| + @return: The response received
|
| + @raise MonitorLockError: Raised if the lock cannot be acquired
|
| + @raise MonitorSocketError: Raised if a socket error occurs
|
| + @raise MonitorProtocolError: Raised if no response is received
|
| + """
|
| + return self.cmd_raw(json.dumps(obj) + "\n")
|
| +
|
| +
|
| + def cmd_qmp(self, cmd, args=None, id=None, timeout=20):
|
| + """
|
| + Build a QMP command from the passed arguments, send it to the monitor
|
| + and return the response.
|
| + Unlike cmd(), return the raw response dict without performing any
|
| + checks on it.
|
| +
|
| + @param cmd: Command to send
|
| + @param args: A dict containing command arguments, or None
|
| + @param id: An id for the command, or None
|
| + @param timeout: Time duration to wait for response
|
| + @return: The response received
|
| + @raise MonitorLockError: Raised if the lock cannot be acquired
|
| + @raise MonitorSocketError: Raised if a socket error occurs
|
| + @raise MonitorProtocolError: Raised if no response is received
|
| + """
|
| + return self.cmd_obj(self._build_cmd(cmd, args, id), timeout)
|
| +
|
|
|
| def is_responsive(self):
|
| """
|
| @@ -548,7 +622,7 @@ class QMPMonitor(Monitor):
|
| @return: True if responsive, False otherwise
|
| """
|
| try:
|
| - self._get_command_output("query-version")
|
| + self.cmd("query-status")
|
| return True
|
| except MonitorError:
|
| return False
|
| @@ -597,38 +671,29 @@ class QMPMonitor(Monitor):
|
| 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):
|
| + def get_greeting(self):
|
| """
|
| - 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 QMP greeting message.
|
| """
|
| - return self._get_command_output(command, timeout=timeout)
|
| + return self._greeting
|
|
|
|
|
| + # Command wrappers
|
| + # Note: all of the following functions raise exceptions in a similar manner
|
| + # to cmd().
|
| +
|
| def quit(self):
|
| """
|
| Send "quit" and return the response.
|
| """
|
| - return self._get_command_output("quit")
|
| + return self.cmd("quit")
|
|
|
|
|
| def info(self, what):
|
| """
|
| Request info about something and return the response.
|
| """
|
| - return self._get_command_output("query-%s" % what)
|
| + return self.cmd("query-%s" % what)
|
|
|
|
|
| def query(self, what):
|
| @@ -646,7 +711,7 @@ class QMPMonitor(Monitor):
|
| @return: The response to the command
|
| """
|
| args = {"filename": filename}
|
| - return self._get_command_output("screendump", args)
|
| + return self.cmd("screendump", args)
|
|
|
|
|
| def migrate(self, uri, full_copy=False, incremental_copy=False, wait=False):
|
| @@ -662,7 +727,7 @@ class QMPMonitor(Monitor):
|
| args = {"uri": uri,
|
| "blk": full_copy,
|
| "inc": incremental_copy}
|
| - return self._get_command_output("migrate", args)
|
| + return self.cmd("migrate", args)
|
|
|
|
|
| def migrate_set_speed(self, value):
|
| @@ -673,4 +738,4 @@ class QMPMonitor(Monitor):
|
| @return: The response to the command
|
| """
|
| args = {"value": value}
|
| - return self._get_command_output("migrate_set_speed", args)
|
| + return self.cmd("migrate_set_speed", args)
|
|
|