PageRenderTime 51ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 0ms

/python/qemu/qmp/legacy.py

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