]>
git.proxmox.com Git - mirror_qemu.git/blob - python/qemu/qmp/__init__.py
2 QEMU Monitor Protocol (QMP) development library & tooling.
4 This package provides a fairly low-level class for communicating to QMP
5 protocol servers, as implemented by QEMU, the QEMU Guest Agent, and the
6 QEMU Storage Daemon. This library is not intended for production use.
8 `QEMUMonitorProtocol` is the primary class of interest, and all errors
9 raised derive from `QMPError`.
12 # Copyright (C) 2009, 2010 Red Hat Inc.
15 # Luiz Capitulino <lcapitulino@redhat.com>
17 # This work is licensed under the terms of the GNU GPL, version 2. See
18 # the COPYING file in the top-level directory.
24 from types
import TracebackType
39 #: QMPMessage is an entire QMP message of any kind.
40 QMPMessage
= Dict
[str, Any
]
42 #: QMPReturnValue is the 'return' value of a command.
43 QMPReturnValue
= object
45 #: QMPObject is any object in a QMP message.
46 QMPObject
= Dict
[str, object]
48 # QMPMessage can be outgoing commands or incoming events/returns.
49 # QMPReturnValue is usually a dict/json object, but due to QAPI's
50 # 'returns-whitelist', it can actually be anything.
52 # {'return': {}} is a QMPMessage,
53 # {} is the QMPReturnValue.
56 InternetAddrT
= Tuple
[str, int]
58 SocketAddrT
= Union
[InternetAddrT
, UnixAddrT
]
61 class QMPError(Exception):
67 class QMPConnectError(QMPError
):
69 QMP connection exception
73 class QMPCapabilitiesError(QMPError
):
75 QMP negotiate capabilities exception
79 class QMPTimeoutError(QMPError
):
85 class QMPProtocolError(QMPError
):
87 QMP protocol error; unexpected response
91 class QMPResponseError(QMPError
):
93 Represents erroneous QMP monitor reply
95 def __init__(self
, reply
: QMPMessage
):
97 desc
= reply
['error']['desc']
100 super().__init
__(desc
)
104 class QMPBadPortError(QMPError
):
106 Unable to parse socket address: Port was non-numerical.
110 class QEMUMonitorProtocol
:
112 Provide an API to connect to QEMU via QEMU Monitor Protocol (QMP) and then
113 allow to handle commands and events.
116 #: Logger object for debugging messages
117 logger
= logging
.getLogger('QMP')
119 def __init__(self
, address
: SocketAddrT
,
120 server
: bool = False,
121 nickname
: Optional
[str] = None):
123 Create a QEMUMonitorProtocol class.
125 @param address: QEMU address, can be either a unix socket path (string)
126 or a tuple in the form ( address, port ) for a TCP
128 @param server: server mode listens on the socket (bool)
129 @raise OSError on socket connection errors
130 @note No connection is established, this is done by the connect() or
133 self
.__events
: List
[QMPMessage
] = []
134 self
.__address
= address
135 self
.__sock
= self
.__get
_sock
()
136 self
.__sockfile
: Optional
[TextIO
] = None
137 self
._nickname
= nickname
139 self
.logger
= logging
.getLogger('QMP').getChild(self
._nickname
)
141 self
.__sock
.setsockopt(socket
.SOL_SOCKET
, socket
.SO_REUSEADDR
, 1)
142 self
.__sock
.bind(self
.__address
)
143 self
.__sock
.listen(1)
145 def __get_sock(self
) -> socket
.socket
:
146 if isinstance(self
.__address
, tuple):
147 family
= socket
.AF_INET
149 family
= socket
.AF_UNIX
150 return socket
.socket(family
, socket
.SOCK_STREAM
)
152 def __negotiate_capabilities(self
) -> QMPMessage
:
153 greeting
= self
.__json
_read
()
154 if greeting
is None or "QMP" not in greeting
:
155 raise QMPConnectError
156 # Greeting seems ok, negotiate capabilities
157 resp
= self
.cmd('qmp_capabilities')
158 if resp
and "return" in resp
:
160 raise QMPCapabilitiesError
162 def __json_read(self
, only_event
: bool = False) -> Optional
[QMPMessage
]:
163 assert self
.__sockfile
is not None
165 data
= self
.__sockfile
.readline()
168 # By definition, any JSON received from QMP is a QMPMessage,
169 # and we are asserting only at static analysis time that it
170 # has a particular shape.
171 resp
: QMPMessage
= json
.loads(data
)
173 self
.logger
.debug("<<< %s", resp
)
174 self
.__events
.append(resp
)
179 def __get_events(self
, wait
: Union
[bool, float] = False) -> None:
181 Check for new events in the stream and cache them in __events.
183 @param wait (bool): block until an event is available.
184 @param wait (float): If wait is a float, treat it as a timeout value.
186 @raise QMPTimeoutError: If a timeout float is provided and the timeout
188 @raise QMPConnectError: If wait is True but no events could be
189 retrieved or if some other error occurred.
192 # Current timeout and blocking status
193 current_timeout
= self
.__sock
.gettimeout()
195 # Check for new events regardless and pull them into the cache:
196 self
.__sock
.settimeout(0) # i.e. setblocking(False)
199 except OSError as err
:
200 # EAGAIN: No data available; not critical
201 if err
.errno
!= errno
.EAGAIN
:
204 self
.__sock
.settimeout(current_timeout
)
206 # Wait for new events, if needed.
207 # if wait is 0.0, this means "no wait" and is also implicitly false.
208 if not self
.__events
and wait
:
209 if isinstance(wait
, float):
210 self
.__sock
.settimeout(wait
)
212 ret
= self
.__json
_read
(only_event
=True)
213 except socket
.timeout
as err
:
214 raise QMPTimeoutError("Timeout waiting for event") from err
215 except Exception as err
:
216 msg
= "Error while reading from socket"
217 raise QMPConnectError(msg
) from err
219 self
.__sock
.settimeout(current_timeout
)
222 raise QMPConnectError("Error while reading from socket")
226 def __enter__(self
: T
) -> T
:
227 # Implement context manager enter function.
231 # pylint: disable=duplicate-code
232 # see https://github.com/PyCQA/pylint/issues/3619
233 exc_type
: Optional
[Type
[BaseException
]],
234 exc_val
: Optional
[BaseException
],
235 exc_tb
: Optional
[TracebackType
]) -> None:
236 # Implement context manager exit function.
240 def parse_address(cls
, address
: str) -> SocketAddrT
:
242 Parse a string into a QMP address.
244 Figure out if the argument is in the port:host form.
245 If it's not, it's probably a file path.
247 components
= address
.split(':')
248 if len(components
) == 2:
250 port
= int(components
[1])
252 msg
= f
"Bad port: '{components[1]}' in '{address}'."
253 raise QMPBadPortError(msg
) from None
254 return (components
[0], port
)
259 def connect(self
, negotiate
: bool = True) -> Optional
[QMPMessage
]:
261 Connect to the QMP Monitor and perform capabilities negotiation.
263 @return QMP greeting dict, or None if negotiate is false
264 @raise OSError on socket connection errors
265 @raise QMPConnectError if the greeting is not received
266 @raise QMPCapabilitiesError if fails to negotiate capabilities
268 self
.__sock
.connect(self
.__address
)
269 self
.__sockfile
= self
.__sock
.makefile(mode
='r')
271 return self
.__negotiate
_capabilities
()
274 def accept(self
, timeout
: Optional
[float] = 15.0) -> QMPMessage
:
276 Await connection from QMP Monitor and perform capabilities negotiation.
278 @param timeout: timeout in seconds (nonnegative float number, or
279 None). The value passed will set the behavior of the
280 underneath QMP socket as described in [1].
281 Default value is set to 15.0.
283 @return QMP greeting dict
284 @raise OSError on socket connection errors
285 @raise QMPConnectError if the greeting is not received
286 @raise QMPCapabilitiesError if fails to negotiate capabilities
289 https://docs.python.org/3/library/socket.html#socket.socket.settimeout
291 self
.__sock
.settimeout(timeout
)
292 self
.__sock
, _
= self
.__sock
.accept()
293 self
.__sockfile
= self
.__sock
.makefile(mode
='r')
294 return self
.__negotiate
_capabilities
()
296 def cmd_obj(self
, qmp_cmd
: QMPMessage
) -> QMPMessage
:
298 Send a QMP command to the QMP Monitor.
300 @param qmp_cmd: QMP command to be sent as a Python dict
301 @return QMP response as a Python dict
303 self
.logger
.debug(">>> %s", qmp_cmd
)
304 self
.__sock
.sendall(json
.dumps(qmp_cmd
).encode('utf-8'))
305 resp
= self
.__json
_read
()
307 raise QMPConnectError("Unexpected empty reply from server")
308 self
.logger
.debug("<<< %s", resp
)
311 def cmd(self
, name
: str,
312 args
: Optional
[Dict
[str, object]] = None,
313 cmd_id
: Optional
[object] = None) -> QMPMessage
:
315 Build a QMP command and send it to the QMP Monitor.
317 @param name: command name (string)
318 @param args: command arguments (dict)
319 @param cmd_id: command id (dict, list, string or int)
321 qmp_cmd
: QMPMessage
= {'execute': name
}
323 qmp_cmd
['arguments'] = args
325 qmp_cmd
['id'] = cmd_id
326 return self
.cmd_obj(qmp_cmd
)
328 def command(self
, cmd
: str, **kwds
: object) -> QMPReturnValue
:
330 Build and send a QMP command to the monitor, report errors if any
332 ret
= self
.cmd(cmd
, kwds
)
334 raise QMPResponseError(ret
)
335 if 'return' not in ret
:
336 raise QMPProtocolError(
337 "'return' key not found in QMP response '{}'".format(str(ret
))
339 return cast(QMPReturnValue
, ret
['return'])
342 wait
: Union
[bool, float] = False) -> Optional
[QMPMessage
]:
344 Pulls a single event.
346 @param wait (bool): block until an event is available.
347 @param wait (float): If wait is a float, treat it as a timeout value.
349 @raise QMPTimeoutError: If a timeout float is provided and the timeout
351 @raise QMPConnectError: If wait is True but no events could be
352 retrieved or if some other error occurred.
354 @return The first available QMP event, or None.
356 self
.__get
_events
(wait
)
359 return self
.__events
.pop(0)
362 def get_events(self
, wait
: bool = False) -> List
[QMPMessage
]:
364 Get a list of available QMP events.
366 @param wait (bool): block until an event is available.
367 @param wait (float): If wait is a float, treat it as a timeout value.
369 @raise QMPTimeoutError: If a timeout float is provided and the timeout
371 @raise QMPConnectError: If wait is True but no events could be
372 retrieved or if some other error occurred.
374 @return The list of available QMP events.
376 self
.__get
_events
(wait
)
379 def clear_events(self
) -> None:
381 Clear current list of pending events.
385 def close(self
) -> None:
387 Close the socket and socket file.
392 self
.__sockfile
.close()
394 def settimeout(self
, timeout
: Optional
[float]) -> None:
396 Set the socket timeout.
398 @param timeout (float): timeout in seconds (non-zero), or None.
399 @note This is a wrap around socket.settimeout
401 @raise ValueError: if timeout was set to 0.
404 msg
= "timeout cannot be 0; this engages non-blocking mode."
405 msg
+= " Use 'None' instead to disable timeouts."
406 raise ValueError(msg
)
407 self
.__sock
.settimeout(timeout
)
409 def get_sock_fd(self
) -> int:
411 Get the socket file descriptor.
413 @return The file descriptor number.
415 return self
.__sock
.fileno()
417 def is_scm_available(self
) -> bool:
419 Check if the socket allows for SCM_RIGHTS.
421 @return True if SCM_RIGHTS is available, otherwise False.
423 return self
.__sock
.family
== socket
.AF_UNIX