From patchwork Sun Jun 13 14:33:42 2010 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Michael Goldish X-Patchwork-Id: 105820 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by demeter.kernel.org (8.14.3/8.14.3) with ESMTP id o5DEYeUk027086 for ; Sun, 13 Jun 2010 14:34:41 GMT Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1753932Ab0FMOee (ORCPT ); Sun, 13 Jun 2010 10:34:34 -0400 Received: from mx1.redhat.com ([209.132.183.28]:33225 "EHLO mx1.redhat.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1753897Ab0FMOeW (ORCPT ); Sun, 13 Jun 2010 10:34:22 -0400 Received: from int-mx04.intmail.prod.int.phx2.redhat.com (int-mx04.intmail.prod.int.phx2.redhat.com [10.5.11.17]) by mx1.redhat.com (8.13.8/8.13.8) with ESMTP id o5DEYLI1019973 (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-SHA bits=256 verify=OK); Sun, 13 Jun 2010 10:34:21 -0400 Received: from ns3.rdu.redhat.com (ns3.rdu.redhat.com [10.11.255.199]) by int-mx04.intmail.prod.int.phx2.redhat.com (8.13.8/8.13.8) with ESMTP id o5DEYLDW019564; Sun, 13 Jun 2010 10:34:21 -0400 Received: from localhost.localdomain (dhcp-1-188.tlv.redhat.com [10.35.1.188]) by ns3.rdu.redhat.com (8.13.8/8.13.8) with ESMTP id o5DEY6v6030030; Sun, 13 Jun 2010 10:34:19 -0400 From: Michael Goldish To: autotest@test.kernel.org, kvm@vger.kernel.org Cc: Michael Goldish Subject: [KVM-AUTOTEST PATCH 11/14] KVM test: add kvm_monitor.py, an interface to QEMU monitors Date: Sun, 13 Jun 2010 17:33:42 +0300 Message-Id: <1276439625-32472-11-git-send-email-mgoldish@redhat.com> In-Reply-To: <1276439625-32472-10-git-send-email-mgoldish@redhat.com> References: <1276439625-32472-1-git-send-email-mgoldish@redhat.com> <1276439625-32472-2-git-send-email-mgoldish@redhat.com> <1276439625-32472-3-git-send-email-mgoldish@redhat.com> <1276439625-32472-4-git-send-email-mgoldish@redhat.com> <1276439625-32472-5-git-send-email-mgoldish@redhat.com> <1276439625-32472-6-git-send-email-mgoldish@redhat.com> <1276439625-32472-7-git-send-email-mgoldish@redhat.com> <1276439625-32472-8-git-send-email-mgoldish@redhat.com> <1276439625-32472-9-git-send-email-mgoldish@redhat.com> <1276439625-32472-10-git-send-email-mgoldish@redhat.com> X-Scanned-By: MIMEDefang 2.67 on 10.5.11.17 Sender: kvm-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: kvm@vger.kernel.org X-Greylist: IP, sender and recipient auto-whitelisted, not delayed by milter-greylist-4.2.3 (demeter.kernel.org [140.211.167.41]); Sun, 13 Jun 2010 14:34:41 +0000 (UTC) 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)