1 """ QEMU Monitor Protocol Python class """
2 # Copyright (C) 2009, 2010 Red Hat Inc.
5 # Luiz Capitulino <lcapitulino@redhat.com>
7 # This work is licensed under the terms of the GNU GPL, version 2. See
8 # the COPYING file in the top-level directory.
14 from types
import TracebackType
28 # QMPMessage is a QMP Message of any kind.
31 # QMPReturnValue is the inner value of return values only.
32 # {'return': {}} is the QMPMessage,
33 # {} is the QMPReturnValue.
34 QMPMessage
= Dict
[str, Any
]
35 QMPReturnValue
= Dict
[str, Any
]
37 InternetAddrT
= Tuple
[str, str]
39 SocketAddrT
= Union
[InternetAddrT
, UnixAddrT
]
42 class QMPError(Exception):
48 class QMPConnectError(QMPError
):
50 QMP connection exception
54 class QMPCapabilitiesError(QMPError
):
56 QMP negotiate capabilities exception
60 class QMPTimeoutError(QMPError
):
66 class QMPProtocolError(QMPError
):
68 QMP protocol error; unexpected response
72 class QMPResponseError(QMPError
):
74 Represents erroneous QMP monitor reply
76 def __init__(self
, reply
: QMPMessage
):
78 desc
= reply
['error']['desc']
81 super().__init
__(desc
)
85 class QEMUMonitorProtocol
:
87 Provide an API to connect to QEMU via QEMU Monitor Protocol (QMP) and then
88 allow to handle commands and events.
91 #: Logger object for debugging messages
92 logger
= logging
.getLogger('QMP')
94 def __init__(self
, address
: SocketAddrT
,
96 nickname
: Optional
[str] = None):
98 Create a QEMUMonitorProtocol class.
100 @param address: QEMU address, can be either a unix socket path (string)
101 or a tuple in the form ( address, port ) for a TCP
103 @param server: server mode listens on the socket (bool)
104 @raise OSError on socket connection errors
105 @note No connection is established, this is done by the connect() or
108 self
.__events
: List
[QMPMessage
] = []
109 self
.__address
= address
110 self
.__sock
= self
.__get
_sock
()
111 self
.__sockfile
: Optional
[TextIO
] = None
112 self
._nickname
= nickname
114 self
.logger
= logging
.getLogger('QMP').getChild(self
._nickname
)
116 self
.__sock
.setsockopt(socket
.SOL_SOCKET
, socket
.SO_REUSEADDR
, 1)
117 self
.__sock
.bind(self
.__address
)
118 self
.__sock
.listen(1)
120 def __get_sock(self
) -> socket
.socket
:
121 if isinstance(self
.__address
, tuple):
122 family
= socket
.AF_INET
124 family
= socket
.AF_UNIX
125 return socket
.socket(family
, socket
.SOCK_STREAM
)
127 def __negotiate_capabilities(self
) -> QMPMessage
:
128 greeting
= self
.__json
_read
()
129 if greeting
is None or "QMP" not in greeting
:
130 raise QMPConnectError
131 # Greeting seems ok, negotiate capabilities
132 resp
= self
.cmd('qmp_capabilities')
133 if resp
and "return" in resp
:
135 raise QMPCapabilitiesError
137 def __json_read(self
, only_event
: bool = False) -> Optional
[QMPMessage
]:
138 assert self
.__sockfile
is not None
140 data
= self
.__sockfile
.readline()
143 # By definition, any JSON received from QMP is a QMPMessage,
144 # and we are asserting only at static analysis time that it
145 # has a particular shape.
146 resp
: QMPMessage
= json
.loads(data
)
148 self
.logger
.debug("<<< %s", resp
)
149 self
.__events
.append(resp
)
154 def __get_events(self
, wait
: Union
[bool, float] = False) -> None:
156 Check for new events in the stream and cache them in __events.
158 @param wait (bool): block until an event is available.
159 @param wait (float): If wait is a float, treat it as a timeout value.
161 @raise QMPTimeoutError: If a timeout float is provided and the timeout
163 @raise QMPConnectError: If wait is True but no events could be
164 retrieved or if some other error occurred.
167 # Current timeout and blocking status
168 current_timeout
= self
.__sock
.gettimeout()
170 # Check for new events regardless and pull them into the cache:
171 self
.__sock
.settimeout(0) # i.e. setblocking(False)
174 except OSError as err
:
175 # EAGAIN: No data available; not critical
176 if err
.errno
!= errno
.EAGAIN
:
179 self
.__sock
.settimeout(current_timeout
)
181 # Wait for new events, if needed.
182 # if wait is 0.0, this means "no wait" and is also implicitly false.
183 if not self
.__events
and wait
:
184 if isinstance(wait
, float):
185 self
.__sock
.settimeout(wait
)
187 ret
= self
.__json
_read
(only_event
=True)
188 except socket
.timeout
as err
:
189 raise QMPTimeoutError("Timeout waiting for event") from err
190 except Exception as err
:
191 msg
= "Error while reading from socket"
192 raise QMPConnectError(msg
) from err
194 self
.__sock
.settimeout(current_timeout
)
197 raise QMPConnectError("Error while reading from socket")
199 def __enter__(self
) -> 'QEMUMonitorProtocol':
200 # Implement context manager enter function.
204 # pylint: disable=duplicate-code
205 # see https://github.com/PyCQA/pylint/issues/3619
206 exc_type
: Optional
[Type
[BaseException
]],
207 exc_val
: Optional
[BaseException
],
208 exc_tb
: Optional
[TracebackType
]) -> None:
209 # Implement context manager exit function.
212 def connect(self
, negotiate
: bool = True) -> Optional
[QMPMessage
]:
214 Connect to the QMP Monitor and perform capabilities negotiation.
216 @return QMP greeting dict, or None if negotiate is false
217 @raise OSError on socket connection errors
218 @raise QMPConnectError if the greeting is not received
219 @raise QMPCapabilitiesError if fails to negotiate capabilities
221 self
.__sock
.connect(self
.__address
)
222 self
.__sockfile
= self
.__sock
.makefile(mode
='r')
224 return self
.__negotiate
_capabilities
()
227 def accept(self
, timeout
: Optional
[float] = 15.0) -> QMPMessage
:
229 Await connection from QMP Monitor and perform capabilities negotiation.
231 @param timeout: timeout in seconds (nonnegative float number, or
232 None). The value passed will set the behavior of the
233 underneath QMP socket as described in [1].
234 Default value is set to 15.0.
235 @return QMP greeting dict
236 @raise OSError on socket connection errors
237 @raise QMPConnectError if the greeting is not received
238 @raise QMPCapabilitiesError if fails to negotiate capabilities
241 https://docs.python.org/3/library/socket.html#socket.socket.settimeout
243 self
.__sock
.settimeout(timeout
)
244 self
.__sock
, _
= self
.__sock
.accept()
245 self
.__sockfile
= self
.__sock
.makefile(mode
='r')
246 return self
.__negotiate
_capabilities
()
248 def cmd_obj(self
, qmp_cmd
: QMPMessage
) -> QMPMessage
:
250 Send a QMP command to the QMP Monitor.
252 @param qmp_cmd: QMP command to be sent as a Python dict
253 @return QMP response as a Python dict
255 self
.logger
.debug(">>> %s", qmp_cmd
)
256 self
.__sock
.sendall(json
.dumps(qmp_cmd
).encode('utf-8'))
257 resp
= self
.__json
_read
()
259 raise QMPConnectError("Unexpected empty reply from server")
260 self
.logger
.debug("<<< %s", resp
)
263 def cmd(self
, name
: str,
264 args
: Optional
[Dict
[str, Any
]] = None,
265 cmd_id
: Optional
[Any
] = None) -> QMPMessage
:
267 Build a QMP command and send it to the QMP Monitor.
269 @param name: command name (string)
270 @param args: command arguments (dict)
271 @param cmd_id: command id (dict, list, string or int)
273 qmp_cmd
: QMPMessage
= {'execute': name
}
275 qmp_cmd
['arguments'] = args
277 qmp_cmd
['id'] = cmd_id
278 return self
.cmd_obj(qmp_cmd
)
280 def command(self
, cmd
: str, **kwds
: Any
) -> QMPReturnValue
:
282 Build and send a QMP command to the monitor, report errors if any
284 ret
= self
.cmd(cmd
, kwds
)
286 raise QMPResponseError(ret
)
287 if 'return' not in ret
:
288 raise QMPProtocolError(
289 "'return' key not found in QMP response '{}'".format(str(ret
))
291 return cast(QMPReturnValue
, ret
['return'])
294 wait
: Union
[bool, float] = False) -> Optional
[QMPMessage
]:
296 Pulls a single event.
298 @param wait (bool): block until an event is available.
299 @param wait (float): If wait is a float, treat it as a timeout value.
301 @raise QMPTimeoutError: If a timeout float is provided and the timeout
303 @raise QMPConnectError: If wait is True but no events could be
304 retrieved or if some other error occurred.
306 @return The first available QMP event, or None.
308 self
.__get
_events
(wait
)
311 return self
.__events
.pop(0)
314 def get_events(self
, wait
: bool = False) -> List
[QMPMessage
]:
316 Get a list of available QMP events.
318 @param wait (bool): block until an event is available.
319 @param wait (float): If wait is a float, treat it as a timeout value.
321 @raise QMPTimeoutError: If a timeout float is provided and the timeout
323 @raise QMPConnectError: If wait is True but no events could be
324 retrieved or if some other error occurred.
326 @return The list of available QMP events.
328 self
.__get
_events
(wait
)
331 def clear_events(self
) -> None:
333 Clear current list of pending events.
337 def close(self
) -> None:
339 Close the socket and socket file.
344 self
.__sockfile
.close()
346 def settimeout(self
, timeout
: Optional
[float]) -> None:
348 Set the socket timeout.
350 @param timeout (float): timeout in seconds (non-zero), or None.
351 @note This is a wrap around socket.settimeout
353 @raise ValueError: if timeout was set to 0.
356 msg
= "timeout cannot be 0; this engages non-blocking mode."
357 msg
+= " Use 'None' instead to disable timeouts."
358 raise ValueError(msg
)
359 self
.__sock
.settimeout(timeout
)
361 def get_sock_fd(self
) -> int:
363 Get the socket file descriptor.
365 @return The file descriptor number.
367 return self
.__sock
.fileno()
369 def is_scm_available(self
) -> bool:
371 Check if the socket allows for SCM_RIGHTS.
373 @return True if SCM_RIGHTS is available, otherwise False.
375 return self
.__sock
.family
== socket
.AF_UNIX