# QEMU Monitor Protocol Python class # # Copyright (C) 2009, 2010 Red Hat Inc. # # Authors: # Luiz Capitulino <lcapitulino@redhat.com> # # This work is licensed under the terms of the GNU GPL, version 2. See # the COPYING file in the top-level directory. import json import errno import socket class QMPError(Exception): pass class QMPConnectError(QMPError): pass class QMPCapabilitiesError(QMPError): pass class QEMUMonitorProtocol: def __init__(self, address, server=False): """ Create a QEMUMonitorProtocol class. @param address: QEMU address, can be either a unix socket path (string) or a tuple in the form ( address, port ) for a TCP connection @param server: server mode listens on the socket (bool) @raise socket.error on socket connection errors @note No connection is established, this is done by the connect() or accept() methods """ self.__events = [] self.__address = address self.__sock = self.__get_sock() if server: self.__sock.bind(self.__address) self.__sock.listen(1) def __get_sock(self): if isinstance(self.__address, tuple): family = socket.AF_INET else: family = socket.AF_UNIX return socket.socket(family, socket.SOCK_STREAM) def __negotiate_capabilities(self): greeting = self.__json_read() if greeting is None or not greeting.has_key('QMP'): raise QMPConnectError # Greeting seems ok, negotiate capabilities resp = self.cmd('qmp_capabilities') if "return" in resp: return greeting raise QMPCapabilitiesError def __json_read(self, only_event=False): while True: data = self.__sockfile.readline() if not data: return resp = json.loads(data) if 'event' in resp: self.__events.append(resp) if not only_event: continue return resp error = socket.error def connect(self, negotiate=True): """ Connect to the QMP Monitor and perform capabilities negotiation. @return QMP greeting dict @raise socket.error on socket connection errors @raise QMPConnectError if the greeting is not received @raise QMPCapabilitiesError if fails to negotiate capabilities """ self.__sock.connect(self.__address) self.__sockfile = self.__sock.makefile() if negotiate: return self.__negotiate_capabilities() def accept(self): """ Await connection from QMP Monitor and perform capabilities negotiation. @return QMP greeting dict @raise socket.error on socket connection errors @raise QMPConnectError if the greeting is not received @raise QMPCapabilitiesError if fails to negotiate capabilities """ self.__sock, _ = self.__sock.accept() self.__sockfile = self.__sock.makefile() return self.__negotiate_capabilities() def cmd_obj(self, qmp_cmd): """ Send a QMP command to the QMP Monitor. @param qmp_cmd: QMP command to be sent as a Python dict @return QMP response as a Python dict or None if the connection has been closed """ try: self.__sock.sendall(json.dumps(qmp_cmd)) except socket.error, err: if err[0] == errno.EPIPE: return raise socket.error(err) return self.__json_read() def cmd(self, name, args=None, id=None): """ Build a QMP command and send it to the QMP Monitor. @param name: command name (string) @param args: command arguments (dict) @param id: command id (dict, list, string or int) """ qmp_cmd = { 'execute': name } if args: qmp_cmd['arguments'] = args if id: qmp_cmd['id'] = id return self.cmd_obj(qmp_cmd) def command(self, cmd, **kwds): ret = self.cmd(cmd, kwds) if ret.has_key('error'): raise Exception(ret['error']['desc']) return ret['return'] def pull_event(self, wait=False): """ Get and delete the first available QMP event. @param wait: block until an event is available (bool) """ self.__sock.setblocking(0) try: self.__json_read() except socket.error, err: if err[0] == errno.EAGAIN: # No data available pass self.__sock.setblocking(1) if not self.__events and wait: self.__json_read(only_event=True) event = self.__events[0] del self.__events[0] return event def get_events(self, wait=False): """ Get a list of available QMP events. @param wait: block until an event is available (bool) """ self.__sock.setblocking(0) try: self.__json_read() except socket.error, err: if err[0] == errno.EAGAIN: # No data available pass self.__sock.setblocking(1) if not self.__events and wait: self.__json_read(only_event=True) return self.__events def clear_events(self): """ Clear current list of pending events. """ self.__events = [] def close(self): self.__sock.close() self.__sockfile.close() timeout = socket.timeout def settimeout(self, timeout): self.__sock.settimeout(timeout)