1# QEMU Monitor Protocol Python class 2# 3# Copyright (C) 2009, 2010 Red Hat Inc. 4# 5# Authors: 6# Luiz Capitulino <lcapitulino@redhat.com> 7# 8# This work is licensed under the terms of the GNU GPL, version 2. See 9# the COPYING file in the top-level directory. 10 11import json 12import errno 13import socket 14 15class QMPError(Exception): 16 pass 17 18class QMPConnectError(QMPError): 19 pass 20 21class QMPCapabilitiesError(QMPError): 22 pass 23 24class QMPTimeoutError(QMPError): 25 pass 26 27class QEMUMonitorProtocol: 28 def __init__(self, address, server=False): 29 """ 30 Create a QEMUMonitorProtocol class. 31 32 @param address: QEMU address, can be either a unix socket path (string) 33 or a tuple in the form ( address, port ) for a TCP 34 connection 35 @param server: server mode listens on the socket (bool) 36 @raise socket.error on socket connection errors 37 @note No connection is established, this is done by the connect() or 38 accept() methods 39 """ 40 self.__events = [] 41 self.__address = address 42 self.__sock = self.__get_sock() 43 if server: 44 self.__sock.bind(self.__address) 45 self.__sock.listen(1) 46 47 def __get_sock(self): 48 if isinstance(self.__address, tuple): 49 family = socket.AF_INET 50 else: 51 family = socket.AF_UNIX 52 return socket.socket(family, socket.SOCK_STREAM) 53 54 def __negotiate_capabilities(self): 55 greeting = self.__json_read() 56 if greeting is None or not greeting.has_key('QMP'): 57 raise QMPConnectError 58 # Greeting seems ok, negotiate capabilities 59 resp = self.cmd('qmp_capabilities') 60 if "return" in resp: 61 return greeting 62 raise QMPCapabilitiesError 63 64 def __json_read(self, only_event=False): 65 while True: 66 data = self.__sockfile.readline() 67 if not data: 68 return 69 resp = json.loads(data) 70 if 'event' in resp: 71 self.__events.append(resp) 72 if not only_event: 73 continue 74 return resp 75 76 error = socket.error 77 78 def __get_events(self, wait=False): 79 """ 80 Check for new events in the stream and cache them in __events. 81 82 @param wait (bool): block until an event is available. 83 @param wait (float): If wait is a float, treat it as a timeout value. 84 85 @raise QMPTimeoutError: If a timeout float is provided and the timeout 86 period elapses. 87 @raise QMPConnectError: If wait is True but no events could be retrieved 88 or if some other error occurred. 89 """ 90 91 # Check for new events regardless and pull them into the cache: 92 self.__sock.setblocking(0) 93 try: 94 self.__json_read() 95 except socket.error as err: 96 if err[0] == errno.EAGAIN: 97 # No data available 98 pass 99 self.__sock.setblocking(1) 100 101 # Wait for new events, if needed. 102 # if wait is 0.0, this means "no wait" and is also implicitly false. 103 if not self.__events and wait: 104 if isinstance(wait, float): 105 self.__sock.settimeout(wait) 106 try: 107 ret = self.__json_read(only_event=True) 108 except socket.timeout: 109 raise QMPTimeoutError("Timeout waiting for event") 110 except: 111 raise QMPConnectError("Error while reading from socket") 112 if ret is None: 113 raise QMPConnectError("Error while reading from socket") 114 self.__sock.settimeout(None) 115 116 def connect(self, negotiate=True): 117 """ 118 Connect to the QMP Monitor and perform capabilities negotiation. 119 120 @return QMP greeting dict 121 @raise socket.error on socket connection errors 122 @raise QMPConnectError if the greeting is not received 123 @raise QMPCapabilitiesError if fails to negotiate capabilities 124 """ 125 self.__sock.connect(self.__address) 126 self.__sockfile = self.__sock.makefile() 127 if negotiate: 128 return self.__negotiate_capabilities() 129 130 def accept(self): 131 """ 132 Await connection from QMP Monitor and perform capabilities negotiation. 133 134 @return QMP greeting dict 135 @raise socket.error on socket connection errors 136 @raise QMPConnectError if the greeting is not received 137 @raise QMPCapabilitiesError if fails to negotiate capabilities 138 """ 139 self.__sock, _ = self.__sock.accept() 140 self.__sockfile = self.__sock.makefile() 141 return self.__negotiate_capabilities() 142 143 def cmd_obj(self, qmp_cmd): 144 """ 145 Send a QMP command to the QMP Monitor. 146 147 @param qmp_cmd: QMP command to be sent as a Python dict 148 @return QMP response as a Python dict or None if the connection has 149 been closed 150 """ 151 try: 152 self.__sock.sendall(json.dumps(qmp_cmd)) 153 except socket.error as err: 154 if err[0] == errno.EPIPE: 155 return 156 raise socket.error(err) 157 return self.__json_read() 158 159 def cmd(self, name, args=None, id=None): 160 """ 161 Build a QMP command and send it to the QMP Monitor. 162 163 @param name: command name (string) 164 @param args: command arguments (dict) 165 @param id: command id (dict, list, string or int) 166 """ 167 qmp_cmd = { 'execute': name } 168 if args: 169 qmp_cmd['arguments'] = args 170 if id: 171 qmp_cmd['id'] = id 172 return self.cmd_obj(qmp_cmd) 173 174 def command(self, cmd, **kwds): 175 ret = self.cmd(cmd, kwds) 176 if ret.has_key('error'): 177 raise Exception(ret['error']['desc']) 178 return ret['return'] 179 180 def pull_event(self, wait=False): 181 """ 182 Get and delete the first available QMP event. 183 184 @param wait (bool): block until an event is available. 185 @param wait (float): If wait is a float, treat it as a timeout value. 186 187 @raise QMPTimeoutError: If a timeout float is provided and the timeout 188 period elapses. 189 @raise QMPConnectError: If wait is True but no events could be retrieved 190 or if some other error occurred. 191 192 @return The first available QMP event, or None. 193 """ 194 self.__get_events(wait) 195 196 if self.__events: 197 return self.__events.pop(0) 198 return None 199 200 def get_events(self, wait=False): 201 """ 202 Get a list of available QMP events. 203 204 @param wait (bool): block until an event is available. 205 @param wait (float): If wait is a float, treat it as a timeout value. 206 207 @raise QMPTimeoutError: If a timeout float is provided and the timeout 208 period elapses. 209 @raise QMPConnectError: If wait is True but no events could be retrieved 210 or if some other error occurred. 211 212 @return The list of available QMP events. 213 """ 214 self.__get_events(wait) 215 return self.__events 216 217 def clear_events(self): 218 """ 219 Clear current list of pending events. 220 """ 221 self.__events = [] 222 223 def close(self): 224 self.__sock.close() 225 self.__sockfile.close() 226 227 timeout = socket.timeout 228 229 def settimeout(self, timeout): 230 self.__sock.settimeout(timeout) 231 232 def get_sock_fd(self): 233 return self.__sock.fileno() 234 235 def is_scm_available(self): 236 return self.__sock.family == socket.AF_UNIX 237