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 # Check for new events regardless and pull them into the cache:
168 self
.__sock
.setblocking(False)
171 except OSError as err
:
172 if err
.errno
== errno
.EAGAIN
:
175 self
.__sock
.setblocking(True)
177 # Wait for new events, if needed.
178 # if wait is 0.0, this means "no wait" and is also implicitly false.
179 if not self
.__events
and wait
:
180 if isinstance(wait
, float):
181 self
.__sock
.settimeout(wait
)
183 ret
= self
.__json
_read
(only_event
=True)
184 except socket
.timeout
:
185 raise QMPTimeoutError("Timeout waiting for event")
187 raise QMPConnectError("Error while reading from socket")
189 raise QMPConnectError("Error while reading from socket")
190 self
.__sock
.settimeout(None)
192 def __enter__(self
) -> 'QEMUMonitorProtocol':
193 # Implement context manager enter function.
197 # pylint: disable=duplicate-code
198 # see https://github.com/PyCQA/pylint/issues/3619
199 exc_type
: Optional
[Type
[BaseException
]],
200 exc_val
: Optional
[BaseException
],
201 exc_tb
: Optional
[TracebackType
]) -> None:
202 # Implement context manager exit function.
205 def connect(self
, negotiate
: bool = True) -> Optional
[QMPMessage
]:
207 Connect to the QMP Monitor and perform capabilities negotiation.
209 @return QMP greeting dict, or None if negotiate is false
210 @raise OSError on socket connection errors
211 @raise QMPConnectError if the greeting is not received
212 @raise QMPCapabilitiesError if fails to negotiate capabilities
214 self
.__sock
.connect(self
.__address
)
215 self
.__sockfile
= self
.__sock
.makefile(mode
='r')
217 return self
.__negotiate
_capabilities
()
220 def accept(self
, timeout
: float = 15.0) -> QMPMessage
:
222 Await connection from QMP Monitor and perform capabilities negotiation.
224 @param timeout: timeout in seconds (nonnegative float number, or
225 None). The value passed will set the behavior of the
226 underneath QMP socket as described in [1].
227 Default value is set to 15.0.
228 @return QMP greeting dict
229 @raise OSError on socket connection errors
230 @raise QMPConnectError if the greeting is not received
231 @raise QMPCapabilitiesError if fails to negotiate capabilities
234 https://docs.python.org/3/library/socket.html#socket.socket.settimeout
236 self
.__sock
.settimeout(timeout
)
237 self
.__sock
, _
= self
.__sock
.accept()
238 self
.__sockfile
= self
.__sock
.makefile(mode
='r')
239 return self
.__negotiate
_capabilities
()
241 def cmd_obj(self
, qmp_cmd
: QMPMessage
) -> QMPMessage
:
243 Send a QMP command to the QMP Monitor.
245 @param qmp_cmd: QMP command to be sent as a Python dict
246 @return QMP response as a Python dict
248 self
.logger
.debug(">>> %s", qmp_cmd
)
249 self
.__sock
.sendall(json
.dumps(qmp_cmd
).encode('utf-8'))
250 resp
= self
.__json
_read
()
252 raise QMPConnectError("Unexpected empty reply from server")
253 self
.logger
.debug("<<< %s", resp
)
256 def cmd(self
, name
: str,
257 args
: Optional
[Dict
[str, Any
]] = None,
258 cmd_id
: Optional
[Any
] = None) -> QMPMessage
:
260 Build a QMP command and send it to the QMP Monitor.
262 @param name: command name (string)
263 @param args: command arguments (dict)
264 @param cmd_id: command id (dict, list, string or int)
266 qmp_cmd
: QMPMessage
= {'execute': name
}
268 qmp_cmd
['arguments'] = args
270 qmp_cmd
['id'] = cmd_id
271 return self
.cmd_obj(qmp_cmd
)
273 def command(self
, cmd
: str, **kwds
: Any
) -> QMPReturnValue
:
275 Build and send a QMP command to the monitor, report errors if any
277 ret
= self
.cmd(cmd
, kwds
)
279 raise QMPResponseError(ret
)
280 if 'return' not in ret
:
281 raise QMPProtocolError(
282 "'return' key not found in QMP response '{}'".format(str(ret
))
284 return cast(QMPReturnValue
, ret
['return'])
287 wait
: Union
[bool, float] = False) -> Optional
[QMPMessage
]:
289 Pulls a single event.
291 @param wait (bool): block until an event is available.
292 @param wait (float): If wait is a float, treat it as a timeout value.
294 @raise QMPTimeoutError: If a timeout float is provided and the timeout
296 @raise QMPConnectError: If wait is True but no events could be
297 retrieved or if some other error occurred.
299 @return The first available QMP event, or None.
301 self
.__get
_events
(wait
)
304 return self
.__events
.pop(0)
307 def get_events(self
, wait
: bool = False) -> List
[QMPMessage
]:
309 Get a list of available QMP events.
311 @param wait (bool): block until an event is available.
312 @param wait (float): If wait is a float, treat it as a timeout value.
314 @raise QMPTimeoutError: If a timeout float is provided and the timeout
316 @raise QMPConnectError: If wait is True but no events could be
317 retrieved or if some other error occurred.
319 @return The list of available QMP events.
321 self
.__get
_events
(wait
)
324 def clear_events(self
) -> None:
326 Clear current list of pending events.
330 def close(self
) -> None:
332 Close the socket and socket file.
337 self
.__sockfile
.close()
339 def settimeout(self
, timeout
: float) -> None:
341 Set the socket timeout.
343 @param timeout (float): timeout in seconds, or None.
344 @note This is a wrap around socket.settimeout
346 self
.__sock
.settimeout(timeout
)
348 def get_sock_fd(self
) -> int:
350 Get the socket file descriptor.
352 @return The file descriptor number.
354 return self
.__sock
.fileno()
356 def is_scm_available(self
) -> bool:
358 Check if the socket allows for SCM_RIGHTS.
360 @return True if SCM_RIGHTS is available, otherwise False.
362 return self
.__sock
.family
== socket
.AF_UNIX