1""" QEMU Monitor Protocol Python class """ 2# Copyright (C) 2009, 2010 Red Hat Inc. 3# 4# Authors: 5# Luiz Capitulino <lcapitulino@redhat.com> 6# 7# This work is licensed under the terms of the GNU GPL, version 2. See 8# the COPYING file in the top-level directory. 9 10import json 11import errno 12import socket 13import logging 14 15 16class QMPError(Exception): 17 """ 18 QMP base exception 19 """ 20 21 22class QMPConnectError(QMPError): 23 """ 24 QMP connection exception 25 """ 26 27 28class QMPCapabilitiesError(QMPError): 29 """ 30 QMP negotiate capabilities exception 31 """ 32 33 34class QMPTimeoutError(QMPError): 35 """ 36 QMP timeout exception 37 """ 38 39 40class QEMUMonitorProtocol: 41 """ 42 Provide an API to connect to QEMU via QEMU Monitor Protocol (QMP) and then 43 allow to handle commands and events. 44 """ 45 46 #: Logger object for debugging messages 47 logger = logging.getLogger('QMP') 48 49 def __init__(self, address, server=False, nickname=None): 50 """ 51 Create a QEMUMonitorProtocol class. 52 53 @param address: QEMU address, can be either a unix socket path (string) 54 or a tuple in the form ( address, port ) for a TCP 55 connection 56 @param server: server mode listens on the socket (bool) 57 @raise OSError on socket connection errors 58 @note No connection is established, this is done by the connect() or 59 accept() methods 60 """ 61 self.__events = [] 62 self.__address = address 63 self.__sock = self.__get_sock() 64 self.__sockfile = None 65 self._nickname = nickname 66 if self._nickname: 67 self.logger = logging.getLogger('QMP').getChild(self._nickname) 68 if server: 69 self.__sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 70 self.__sock.bind(self.__address) 71 self.__sock.listen(1) 72 73 def __get_sock(self): 74 if isinstance(self.__address, tuple): 75 family = socket.AF_INET 76 else: 77 family = socket.AF_UNIX 78 return socket.socket(family, socket.SOCK_STREAM) 79 80 def __negotiate_capabilities(self): 81 greeting = self.__json_read() 82 if greeting is None or "QMP" not in greeting: 83 raise QMPConnectError 84 # Greeting seems ok, negotiate capabilities 85 resp = self.cmd('qmp_capabilities') 86 if resp and "return" in resp: 87 return greeting 88 raise QMPCapabilitiesError 89 90 def __json_read(self, only_event=False): 91 while True: 92 data = self.__sockfile.readline() 93 if not data: 94 return None 95 resp = json.loads(data) 96 if 'event' in resp: 97 self.logger.debug("<<< %s", resp) 98 self.__events.append(resp) 99 if not only_event: 100 continue 101 return resp 102 103 def __get_events(self, wait=False): 104 """ 105 Check for new events in the stream and cache them in __events. 106 107 @param wait (bool): block until an event is available. 108 @param wait (float): If wait is a float, treat it as a timeout value. 109 110 @raise QMPTimeoutError: If a timeout float is provided and the timeout 111 period elapses. 112 @raise QMPConnectError: If wait is True but no events could be 113 retrieved or if some other error occurred. 114 """ 115 116 # Check for new events regardless and pull them into the cache: 117 self.__sock.setblocking(0) 118 try: 119 self.__json_read() 120 except OSError as err: 121 if err.errno == errno.EAGAIN: 122 # No data available 123 pass 124 self.__sock.setblocking(1) 125 126 # Wait for new events, if needed. 127 # if wait is 0.0, this means "no wait" and is also implicitly false. 128 if not self.__events and wait: 129 if isinstance(wait, float): 130 self.__sock.settimeout(wait) 131 try: 132 ret = self.__json_read(only_event=True) 133 except socket.timeout: 134 raise QMPTimeoutError("Timeout waiting for event") 135 except: 136 raise QMPConnectError("Error while reading from socket") 137 if ret is None: 138 raise QMPConnectError("Error while reading from socket") 139 self.__sock.settimeout(None) 140 141 def __enter__(self): 142 # Implement context manager enter function. 143 return self 144 145 def __exit__(self, exc_type, exc_value, exc_traceback): 146 # Implement context manager exit function. 147 self.close() 148 return False 149 150 def connect(self, negotiate=True): 151 """ 152 Connect to the QMP Monitor and perform capabilities negotiation. 153 154 @return QMP greeting dict, or None if negotiate is false 155 @raise OSError on socket connection errors 156 @raise QMPConnectError if the greeting is not received 157 @raise QMPCapabilitiesError if fails to negotiate capabilities 158 """ 159 self.__sock.connect(self.__address) 160 self.__sockfile = self.__sock.makefile() 161 if negotiate: 162 return self.__negotiate_capabilities() 163 return None 164 165 def accept(self, timeout=15.0): 166 """ 167 Await connection from QMP Monitor and perform capabilities negotiation. 168 169 @param timeout: timeout in seconds (nonnegative float number, or 170 None). The value passed will set the behavior of the 171 underneath QMP socket as described in [1]. Default value 172 is set to 15.0. 173 @return QMP greeting dict 174 @raise OSError on socket connection errors 175 @raise QMPConnectError if the greeting is not received 176 @raise QMPCapabilitiesError if fails to negotiate capabilities 177 178 [1] 179 https://docs.python.org/3/library/socket.html#socket.socket.settimeout 180 """ 181 self.__sock.settimeout(timeout) 182 self.__sock, _ = self.__sock.accept() 183 self.__sockfile = self.__sock.makefile() 184 return self.__negotiate_capabilities() 185 186 def cmd_obj(self, qmp_cmd): 187 """ 188 Send a QMP command to the QMP Monitor. 189 190 @param qmp_cmd: QMP command to be sent as a Python dict 191 @return QMP response as a Python dict or None if the connection has 192 been closed 193 """ 194 self.logger.debug(">>> %s", qmp_cmd) 195 try: 196 self.__sock.sendall(json.dumps(qmp_cmd).encode('utf-8')) 197 except OSError as err: 198 if err.errno == errno.EPIPE: 199 return None 200 raise err 201 resp = self.__json_read() 202 self.logger.debug("<<< %s", resp) 203 return resp 204 205 def cmd(self, name, args=None, cmd_id=None): 206 """ 207 Build a QMP command and send it to the QMP Monitor. 208 209 @param name: command name (string) 210 @param args: command arguments (dict) 211 @param cmd_id: command id (dict, list, string or int) 212 """ 213 qmp_cmd = {'execute': name} 214 if args: 215 qmp_cmd['arguments'] = args 216 if cmd_id: 217 qmp_cmd['id'] = cmd_id 218 return self.cmd_obj(qmp_cmd) 219 220 def command(self, cmd, **kwds): 221 """ 222 Build and send a QMP command to the monitor, report errors if any 223 """ 224 ret = self.cmd(cmd, kwds) 225 if "error" in ret: 226 raise Exception(ret['error']['desc']) 227 return ret['return'] 228 229 def pull_event(self, wait=False): 230 """ 231 Pulls a single event. 232 233 @param wait (bool): block until an event is available. 234 @param wait (float): If wait is a float, treat it as a timeout value. 235 236 @raise QMPTimeoutError: If a timeout float is provided and the timeout 237 period elapses. 238 @raise QMPConnectError: If wait is True but no events could be 239 retrieved or if some other error occurred. 240 241 @return The first available QMP event, or None. 242 """ 243 self.__get_events(wait) 244 245 if self.__events: 246 return self.__events.pop(0) 247 return None 248 249 def get_events(self, wait=False): 250 """ 251 Get a list of available QMP events. 252 253 @param wait (bool): block until an event is available. 254 @param wait (float): If wait is a float, treat it as a timeout value. 255 256 @raise QMPTimeoutError: If a timeout float is provided and the timeout 257 period elapses. 258 @raise QMPConnectError: If wait is True but no events could be 259 retrieved or if some other error occurred. 260 261 @return The list of available QMP events. 262 """ 263 self.__get_events(wait) 264 return self.__events 265 266 def clear_events(self): 267 """ 268 Clear current list of pending events. 269 """ 270 self.__events = [] 271 272 def close(self): 273 """ 274 Close the socket and socket file. 275 """ 276 if self.__sock: 277 self.__sock.close() 278 if self.__sockfile: 279 self.__sockfile.close() 280 281 def settimeout(self, timeout): 282 """ 283 Set the socket timeout. 284 285 @param timeout (float): timeout in seconds, or None. 286 @note This is a wrap around socket.settimeout 287 """ 288 self.__sock.settimeout(timeout) 289 290 def get_sock_fd(self): 291 """ 292 Get the socket file descriptor. 293 294 @return The file descriptor number. 295 """ 296 return self.__sock.fileno() 297 298 def is_scm_available(self): 299 """ 300 Check if the socket allows for SCM_RIGHTS. 301 302 @return True if SCM_RIGHTS is available, otherwise False. 303 """ 304 return self.__sock.family == socket.AF_UNIX 305