On Fri, 2010-06-18 at 00:39 -0300, Lucas Meneghel Rodrigues 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.
> 
> Changes from v2:
> - Turn the base monitor class into a new style class
> - Replace ParentClass.__init__() calls with super calls.
> 
> Changes from v1:
> - Add name parameter to __init__()
> - Remove help() method
> - Rename help attribute to _help_str to indicate private use
> - Rename lock to _lock
> - Rename socket to _socket

Ok, QMP patchset finished, tested and applied.

> Signed-off-by: Michael Goldish <[email protected]>
> ---
>  client/tests/kvm/kvm_monitor.py |  355 
> +++++++++++++++++++++++++++++++++++++++
>  1 files changed, 355 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..2f05d95
> --- /dev/null
> +++ b/client/tests/kvm/kvm_monitor.py
> @@ -0,0 +1,355 @@
> +"""
> +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, name, filename):
> +        """
> +        Initialize the instance.
> +
> +        @param name: Monitor identifier (a string)
> +        @param filename: Monitor socket filename
> +        @raise MonitorConnectError: Raised if the connection fails
> +        """
> +        self.name = name
> +        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.name, 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, name, filename, suppress_exceptions=False):
> +        """
> +        Connect to the monitor socket and find the (qemu) prompt.
> +
> +        @param name: Monitor identifier (a string)
> +        @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
> +        @note: Other exceptions may be raised.  See _get_command_output's
> +                docstring.
> +        """
> +        try:
> +            Monitor.__init__(self, name, 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_str = 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_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")
> +
> +
> +    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
> +        """
> +        logging.debug("Migrating to: %s" % uri)
> +        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)


_______________________________________________
Autotest mailing list
[email protected]
http://test.kernel.org/cgi-bin/mailman/listinfo/autotest

Reply via email to