]> git.ipfire.org Git - thirdparty/qemu.git/blame - python/qemu/qmp/legacy.py
python/qemu: rename command() to cmd()
[thirdparty/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
5bbc5936
JS
71 :param address: QEMU address, can be a unix socket path (string), a tuple
72 in the form ( address, port ) for a TCP connection, or an
73 existing `socket.socket` object.
b0654f4f 74 :param server: Act as the socket server. (See 'accept')
5bbc5936 75 Not applicable when passing a socket directly.
b0654f4f
JS
76 :param nickname: Optional nickname used for logging.
77 """
78
603a3bad 79 def __init__(self,
5bbc5936 80 address: Union[SocketAddrT, socket.socket],
f122be60
JS
81 server: bool = False,
82 nickname: Optional[str] = None):
83
5bbc5936
JS
84 if server and isinstance(address, socket.socket):
85 raise ValueError(
86 "server argument should be False when passing a socket")
87
37094b6d 88 self._qmp = QMPClient(nickname)
f122be60
JS
89 self._aloop = asyncio.get_event_loop()
90 self._address = address
91 self._timeout: Optional[float] = None
92
b0b662bb 93 if server:
5bbc5936
JS
94 assert not isinstance(self._address, socket.socket)
95 self._sync(self._qmp.start_server(self._address))
b0b662bb 96
f122be60
JS
97 _T = TypeVar('_T')
98
99 def _sync(
100 self, future: Awaitable[_T], timeout: Optional[float] = None
101 ) -> _T:
102 return self._aloop.run_until_complete(
103 asyncio.wait_for(future, timeout=timeout)
104 )
105
106 def _get_greeting(self) -> Optional[QMPMessage]:
37094b6d 107 if self._qmp.greeting is not None:
f122be60 108 # pylint: disable=protected-access
37094b6d 109 return self._qmp.greeting._asdict()
f122be60
JS
110 return None
111
0c78ebf7
JS
112 def __enter__(self: _T) -> _T:
113 # Implement context manager enter function.
114 return self
115
116 def __exit__(self,
0c78ebf7
JS
117 exc_type: Optional[Type[BaseException]],
118 exc_val: Optional[BaseException],
119 exc_tb: Optional[TracebackType]) -> None:
120 # Implement context manager exit function.
121 self.close()
9fcd3930
JS
122
123 @classmethod
124 def parse_address(cls, address: str) -> SocketAddrT:
125 """
126 Parse a string into a QMP address.
127
128 Figure out if the argument is in the port:host form.
129 If it's not, it's probably a file path.
130 """
131 components = address.split(':')
132 if len(components) == 2:
133 try:
134 port = int(components[1])
135 except ValueError:
136 msg = f"Bad port: '{components[1]}' in '{address}'."
137 raise QMPBadPortError(msg) from None
138 return (components[0], port)
139
140 # Treat as filepath.
141 return address
f122be60
JS
142
143 def connect(self, negotiate: bool = True) -> Optional[QMPMessage]:
b0654f4f
JS
144 """
145 Connect to the QMP Monitor and perform capabilities negotiation.
146
147 :return: QMP greeting dict, or None if negotiate is false
148 :raise ConnectError: on connection errors
149 """
37094b6d
JS
150 self._qmp.await_greeting = negotiate
151 self._qmp.negotiate = negotiate
f122be60
JS
152
153 self._sync(
5bbc5936 154 self._qmp.connect(self._address)
f122be60
JS
155 )
156 return self._get_greeting()
157
158 def accept(self, timeout: Optional[float] = 15.0) -> QMPMessage:
b0654f4f
JS
159 """
160 Await connection from QMP Monitor and perform capabilities negotiation.
161
162 :param timeout:
163 timeout in seconds (nonnegative float number, or None).
164 If None, there is no timeout, and this may block forever.
165
166 :return: QMP greeting dict
167 :raise ConnectError: on connection errors
168 """
37094b6d
JS
169 self._qmp.await_greeting = True
170 self._qmp.negotiate = True
f122be60 171
37094b6d 172 self._sync(self._qmp.accept(), timeout)
f122be60
JS
173
174 ret = self._get_greeting()
175 assert ret is not None
176 return ret
177
178 def cmd_obj(self, qmp_cmd: QMPMessage) -> QMPMessage:
b0654f4f
JS
179 """
180 Send a QMP command to the QMP Monitor.
181
182 :param qmp_cmd: QMP command to be sent as a Python dict
183 :return: QMP response as a Python dict
184 """
f122be60
JS
185 return dict(
186 self._sync(
187 # pylint: disable=protected-access
188
189 # _raw() isn't a public API, because turning off
190 # automatic ID assignment is discouraged. For
191 # compatibility with iotests *only*, do it anyway.
37094b6d 192 self._qmp._raw(qmp_cmd, assign_id=False),
f122be60
JS
193 self._timeout
194 )
195 )
196
37274707
VSO
197 def cmd_raw(self, name: str,
198 args: Optional[Dict[str, object]] = None) -> QMPMessage:
0c78ebf7
JS
199 """
200 Build a QMP command and send it to the QMP Monitor.
201
b0654f4f
JS
202 :param name: command name (string)
203 :param args: command arguments (dict)
0c78ebf7
JS
204 """
205 qmp_cmd: QMPMessage = {'execute': name}
206 if args:
207 qmp_cmd['arguments'] = args
0c78ebf7 208 return self.cmd_obj(qmp_cmd)
f122be60 209
684750ab 210 def cmd(self, cmd: str, **kwds: object) -> QMPReturnValue:
b0654f4f
JS
211 """
212 Build and send a QMP command to the monitor, report errors if any
213 """
f122be60 214 return self._sync(
37094b6d 215 self._qmp.execute(cmd, kwds),
f122be60
JS
216 self._timeout
217 )
218
219 def pull_event(self,
220 wait: Union[bool, float] = False) -> Optional[QMPMessage]:
b0654f4f
JS
221 """
222 Pulls a single event.
223
224 :param wait:
225 If False or 0, do not wait. Return None if no events ready.
226 If True, wait forever until the next event.
227 Otherwise, wait for the specified number of seconds.
228
229 :raise asyncio.TimeoutError:
230 When a timeout is requested and the timeout period elapses.
231
232 :return: The first available QMP event, or None.
233 """
f122be60
JS
234 if not wait:
235 # wait is False/0: "do not wait, do not except."
37094b6d 236 if self._qmp.events.empty():
f122be60
JS
237 return None
238
239 # If wait is 'True', wait forever. If wait is False/0, the events
240 # queue must not be empty; but it still needs some real amount
241 # of time to complete.
242 timeout = None
243 if wait and isinstance(wait, float):
244 timeout = wait
245
246 return dict(
247 self._sync(
37094b6d 248 self._qmp.events.get(),
f122be60
JS
249 timeout
250 )
251 )
252
253 def get_events(self, wait: Union[bool, float] = False) -> List[QMPMessage]:
b0654f4f
JS
254 """
255 Get a list of QMP events and clear all pending events.
256
257 :param wait:
258 If False or 0, do not wait. Return None if no events ready.
259 If True, wait until we have at least one event.
260 Otherwise, wait for up to the specified number of seconds for at
261 least one event.
262
263 :raise asyncio.TimeoutError:
264 When a timeout is requested and the timeout period elapses.
265
266 :return: A list of QMP events.
267 """
37094b6d 268 events = [dict(x) for x in self._qmp.events.clear()]
f122be60
JS
269 if events:
270 return events
271
272 event = self.pull_event(wait)
273 return [event] if event is not None else []
274
275 def clear_events(self) -> None:
b0654f4f 276 """Clear current list of pending events."""
37094b6d 277 self._qmp.events.clear()
f122be60
JS
278
279 def close(self) -> None:
b0654f4f 280 """Close the connection."""
f122be60 281 self._sync(
37094b6d 282 self._qmp.disconnect()
f122be60
JS
283 )
284
285 def settimeout(self, timeout: Optional[float]) -> None:
b0654f4f
JS
286 """
287 Set the timeout for QMP RPC execution.
288
289 This timeout affects the `cmd`, `cmd_obj`, and `command` methods.
290 The `accept`, `pull_event` and `get_event` methods have their
291 own configurable timeouts.
292
293 :param timeout:
294 timeout in seconds, or None.
295 None will wait indefinitely.
296 """
f122be60
JS
297 self._timeout = timeout
298
299 def send_fd_scm(self, fd: int) -> None:
b0654f4f
JS
300 """
301 Send a file descriptor to the remote via SCM_RIGHTS.
302 """
37094b6d 303 self._qmp.send_fd_scm(fd)
3bc72e3a
JS
304
305 def __del__(self) -> None:
37094b6d 306 if self._qmp.runstate == Runstate.IDLE:
3bc72e3a
JS
307 return
308
309 if not self._aloop.is_running():
310 self.close()
311 else:
312 # Garbage collection ran while the event loop was running.
313 # Nothing we can do about it now, but if we don't raise our
314 # own error, the user will be treated to a lot of traceback
315 # they might not understand.
6e7751dc 316 raise QMPError(
3bc72e3a
JS
317 "QEMUMonitorProtocol.close()"
318 " was not called before object was garbage collected"
319 )