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@xxxxxxxxxx> --- 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 -- 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