]> git.proxmox.com Git - mirror_qemu.git/blame - python/qemu/qmp/legacy.py
python/qmp/legacy: allow using sockets for connect()
[mirror_qemu.git] / python / qemu / qmp / legacy.py
CommitLineData
f122be60 1"""
b0654f4f 2(Legacy) Sync QMP Wrapper
f122be60 3
b0654f4f
JS
4This module provides the `QEMUMonitorProtocol` class, which is a
5synchronous wrapper around `QMPClient`.
6
7Its design closely resembles that of the original QEMUMonitorProtocol
8class, originally written by Luiz Capitulino. It is provided here for
9compatibility with scripts inside the QEMU source tree that expect the
10old interface.
f122be60
JS
11"""
12
380fc8f3
JS
13#
14# Copyright (C) 2009-2022 Red Hat Inc.
15#
16# Authors:
17# Luiz Capitulino <lcapitulino@redhat.com>
18# John Snow <jsnow@redhat.com>
19#
20# This work is licensed under the terms of the GNU GPL, version 2. See
21# the COPYING file in the top-level directory.
22#
23
f122be60 24import asyncio
603a3bad 25import socket
0c78ebf7 26from types import TracebackType
f122be60 27from typing import (
0e6bfd8b 28 Any,
f122be60 29 Awaitable,
0e6bfd8b 30 Dict,
f122be60
JS
31 List,
32 Optional,
0c78ebf7 33 Type,
f122be60
JS
34 TypeVar,
35 Union,
36)
37
6e7751dc 38from .error import QMPError
0e6bfd8b 39from .protocol import Runstate, SocketAddrT
f122be60
JS
40from .qmp_client import QMPClient
41
42
0e6bfd8b
JS
43#: QMPMessage is an entire QMP message of any kind.
44QMPMessage = Dict[str, Any]
45
46#: QMPReturnValue is the 'return' value of a command.
47QMPReturnValue = object
48
49#: QMPObject is any object in a QMP message.
50QMPObject = Dict[str, object]
51
52# QMPMessage can be outgoing commands or incoming events/returns.
53# QMPReturnValue is usually a dict/json object, but due to QAPI's
9b0ecfab 54# 'command-returns-exceptions', it can actually be anything.
0e6bfd8b
JS
55#
56# {'return': {}} is a QMPMessage,
57# {} is the QMPReturnValue.
58
59
9fcd3930
JS
60class QMPBadPortError(QMPError):
61 """
62 Unable to parse socket address: Port was non-numerical.
63 """
64
65
0c78ebf7 66class QEMUMonitorProtocol:
b0654f4f
JS
67 """
68 Provide an API to connect to QEMU via QEMU Monitor Protocol (QMP)
69 and then allow to handle commands and events.
70
71 :param address: QEMU address, can be either a unix socket path (string)
72 or a tuple in the form ( address, port ) for a TCP
603a3bad
MAL
73 connection or None
74 :param sock: a socket or None
b0654f4f
JS
75 :param server: Act as the socket server. (See 'accept')
76 :param nickname: Optional nickname used for logging.
77 """
78
603a3bad
MAL
79 def __init__(self,
80 address: Optional[SocketAddrT] = None,
81 sock: Optional[socket.socket] = None,
f122be60
JS
82 server: bool = False,
83 nickname: Optional[str] = None):
84
603a3bad 85 assert address or sock
37094b6d 86 self._qmp = QMPClient(nickname)
f122be60
JS
87 self._aloop = asyncio.get_event_loop()
88 self._address = address
603a3bad 89 self._sock = sock
f122be60
JS
90 self._timeout: Optional[float] = None
91
b0b662bb 92 if server:
603a3bad
MAL
93 if sock:
94 assert self._sock is not None
95 self._sync(self._qmp.open_with_socket(self._sock))
96 else:
97 assert self._address is not None
98 self._sync(self._qmp.start_server(self._address))
b0b662bb 99
f122be60
JS
100 _T = TypeVar('_T')
101
102 def _sync(
103 self, future: Awaitable[_T], timeout: Optional[float] = None
104 ) -> _T:
105 return self._aloop.run_until_complete(
106 asyncio.wait_for(future, timeout=timeout)
107 )
108
109 def _get_greeting(self) -> Optional[QMPMessage]:
37094b6d 110 if self._qmp.greeting is not None:
f122be60 111 # pylint: disable=protected-access
37094b6d 112 return self._qmp.greeting._asdict()
f122be60
JS
113 return None
114
0c78ebf7
JS
115 def __enter__(self: _T) -> _T:
116 # Implement context manager enter function.
117 return self
118
119 def __exit__(self,
0c78ebf7
JS
120 exc_type: Optional[Type[BaseException]],
121 exc_val: Optional[BaseException],
122 exc_tb: Optional[TracebackType]) -> None:
123 # Implement context manager exit function.
124 self.close()
9fcd3930
JS
125
126 @classmethod
127 def parse_address(cls, address: str) -> SocketAddrT:
128 """
129 Parse a string into a QMP address.
130
131 Figure out if the argument is in the port:host form.
132 If it's not, it's probably a file path.
133 """
134 components = address.split(':')
135 if len(components) == 2:
136 try:
137 port = int(components[1])
138 except ValueError:
139 msg = f"Bad port: '{components[1]}' in '{address}'."
140 raise QMPBadPortError(msg) from None
141 return (components[0], port)
142
143 # Treat as filepath.
144 return address
f122be60
JS
145
146 def connect(self, negotiate: bool = True) -> Optional[QMPMessage]:
b0654f4f
JS
147 """
148 Connect to the QMP Monitor and perform capabilities negotiation.
149
150 :return: QMP greeting dict, or None if negotiate is false
151 :raise ConnectError: on connection errors
152 """
b8d4ca18
JS
153 addr_or_sock = self._address or self._sock
154 assert addr_or_sock is not None
37094b6d
JS
155 self._qmp.await_greeting = negotiate
156 self._qmp.negotiate = negotiate
f122be60
JS
157
158 self._sync(
b8d4ca18 159 self._qmp.connect(addr_or_sock)
f122be60
JS
160 )
161 return self._get_greeting()
162
163 def accept(self, timeout: Optional[float] = 15.0) -> QMPMessage:
b0654f4f
JS
164 """
165 Await connection from QMP Monitor and perform capabilities negotiation.
166
167 :param timeout:
168 timeout in seconds (nonnegative float number, or None).
169 If None, there is no timeout, and this may block forever.
170
171 :return: QMP greeting dict
172 :raise ConnectError: on connection errors
173 """
37094b6d
JS
174 self._qmp.await_greeting = True
175 self._qmp.negotiate = True
f122be60 176
37094b6d 177 self._sync(self._qmp.accept(), timeout)
f122be60
JS
178
179 ret = self._get_greeting()
180 assert ret is not None
181 return ret
182
183 def cmd_obj(self, qmp_cmd: QMPMessage) -> QMPMessage:
b0654f4f
JS
184 """
185 Send a QMP command to the QMP Monitor.
186
187 :param qmp_cmd: QMP command to be sent as a Python dict
188 :return: QMP response as a Python dict
189 """
f122be60
JS
190 return dict(
191 self._sync(
192 # pylint: disable=protected-access
193
194 # _raw() isn't a public API, because turning off
195 # automatic ID assignment is discouraged. For
196 # compatibility with iotests *only*, do it anyway.
37094b6d 197 self._qmp._raw(qmp_cmd, assign_id=False),
f122be60
JS
198 self._timeout
199 )
200 )
201
0c78ebf7
JS
202 def cmd(self, name: str,
203 args: Optional[Dict[str, object]] = None,
204 cmd_id: Optional[object] = None) -> QMPMessage:
205 """
206 Build a QMP command and send it to the QMP Monitor.
207
b0654f4f
JS
208 :param name: command name (string)
209 :param args: command arguments (dict)
210 :param cmd_id: command id (dict, list, string or int)
0c78ebf7
JS
211 """
212 qmp_cmd: QMPMessage = {'execute': name}
213 if args:
214 qmp_cmd['arguments'] = args
215 if cmd_id:
216 qmp_cmd['id'] = cmd_id
217 return self.cmd_obj(qmp_cmd)
f122be60
JS
218
219 def command(self, cmd: str, **kwds: object) -> QMPReturnValue:
b0654f4f
JS
220 """
221 Build and send a QMP command to the monitor, report errors if any
222 """
f122be60 223 return self._sync(
37094b6d 224 self._qmp.execute(cmd, kwds),
f122be60
JS
225 self._timeout
226 )
227
228 def pull_event(self,
229 wait: Union[bool, float] = False) -> Optional[QMPMessage]:
b0654f4f
JS
230 """
231 Pulls a single event.
232
233 :param wait:
234 If False or 0, do not wait. Return None if no events ready.
235 If True, wait forever until the next event.
236 Otherwise, wait for the specified number of seconds.
237
238 :raise asyncio.TimeoutError:
239 When a timeout is requested and the timeout period elapses.
240
241 :return: The first available QMP event, or None.
242 """
f122be60
JS
243 if not wait:
244 # wait is False/0: "do not wait, do not except."
37094b6d 245 if self._qmp.events.empty():
f122be60
JS
246 return None
247
248 # If wait is 'True', wait forever. If wait is False/0, the events
249 # queue must not be empty; but it still needs some real amount
250 # of time to complete.
251 timeout = None
252 if wait and isinstance(wait, float):
253 timeout = wait
254
255 return dict(
256 self._sync(
37094b6d 257 self._qmp.events.get(),
f122be60
JS
258 timeout
259 )
260 )
261
262 def get_events(self, wait: Union[bool, float] = False) -> List[QMPMessage]:
b0654f4f
JS
263 """
264 Get a list of QMP events and clear all pending events.
265
266 :param wait:
267 If False or 0, do not wait. Return None if no events ready.
268 If True, wait until we have at least one event.
269 Otherwise, wait for up to the specified number of seconds for at
270 least one event.
271
272 :raise asyncio.TimeoutError:
273 When a timeout is requested and the timeout period elapses.
274
275 :return: A list of QMP events.
276 """
37094b6d 277 events = [dict(x) for x in self._qmp.events.clear()]
f122be60
JS
278 if events:
279 return events
280
281 event = self.pull_event(wait)
282 return [event] if event is not None else []
283
284 def clear_events(self) -> None:
b0654f4f 285 """Clear current list of pending events."""
37094b6d 286 self._qmp.events.clear()
f122be60
JS
287
288 def close(self) -> None:
b0654f4f 289 """Close the connection."""
f122be60 290 self._sync(
37094b6d 291 self._qmp.disconnect()
f122be60
JS
292 )
293
294 def settimeout(self, timeout: Optional[float]) -> None:
b0654f4f
JS
295 """
296 Set the timeout for QMP RPC execution.
297
298 This timeout affects the `cmd`, `cmd_obj`, and `command` methods.
299 The `accept`, `pull_event` and `get_event` methods have their
300 own configurable timeouts.
301
302 :param timeout:
303 timeout in seconds, or None.
304 None will wait indefinitely.
305 """
f122be60
JS
306 self._timeout = timeout
307
308 def send_fd_scm(self, fd: int) -> None:
b0654f4f
JS
309 """
310 Send a file descriptor to the remote via SCM_RIGHTS.
311 """
37094b6d 312 self._qmp.send_fd_scm(fd)
3bc72e3a
JS
313
314 def __del__(self) -> None:
37094b6d 315 if self._qmp.runstate == Runstate.IDLE:
3bc72e3a
JS
316 return
317
318 if not self._aloop.is_running():
319 self.close()
320 else:
321 # Garbage collection ran while the event loop was running.
322 # Nothing we can do about it now, but if we don't raise our
323 # own error, the user will be treated to a lot of traceback
324 # they might not understand.
6e7751dc 325 raise QMPError(
3bc72e3a
JS
326 "QEMUMonitorProtocol.close()"
327 " was not called before object was garbage collected"
328 )