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