Skip to content

Commit

Permalink
Merge remote-tracking branch 'remotes/jsnow-gitlab/tags/python-pull-r…
Browse files Browse the repository at this point in the history
…equest' into staging

Python Pull request

Moves QMP-related tools not used for build or automatic testing from
scripts/ to python/qemu/qmp/ where they will be protected from bitrot by
the check-python-* CI jobs.

stub forwarders are left in the old locations for now.

# gpg: Signature made Sat 19 Jun 2021 00:02:40 BST
# gpg:                using RSA key F9B7ABDBBCACDF95BE76CBD07DEF8106AAFC390E
# gpg: Good signature from "John Snow (John Huston) <jsnow@redhat.com>" [full]
# Primary key fingerprint: FAEB 9711 A12C F475 812F  18F2 88A9 064D 1835 61EB
#      Subkey fingerprint: F9B7 ABDB BCAC DF95 BE76  CBD0 7DEF 8106 AAFC 390E

* remotes/jsnow-gitlab/tags/python-pull-request: (72 commits)
  scripts/qmp-shell: add redirection shim
  python: add qmp-shell entry point
  scripts/qmp-shell: move to python/qemu/qmp/qmp_shell.py
  scripts/qmp-shell: add docstrings
  scripts/qmp-shell: make QMPShellError inherit QMPError
  scripts/qmp-shell: remove double-underscores
  scripts/qmp-shell: convert usage comment to docstring
  scripts/qmp-shell: Remove too-broad-exception
  scripts/qmp-shell: Fix empty-transaction invocation
  scripts/qmp-shell: remove TODO
  scripts/qmp-shell: use logging to show warnings
  scripts/qmp-shell: Use context manager instead of atexit
  python/qmp: return generic type from context manager
  scripts/qmp-shell: unprivatize 'pretty' property
  scripts/qmp-shell: Accept SocketAddrT instead of string
  scripts/qmp-shell: add mypy types
  python/qmp: add QMPObject type alias
  scripts/qmp-shell: initialize completer early
  scripts/qmp-shell: refactor QMPCompleter
  scripts/qmp-shell: Fix "FuzzyJSON" parser
  ...

Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
  • Loading branch information
pm215 committed Jun 21, 2021
2 parents 53f306f + d08caef commit 0add99e
Show file tree
Hide file tree
Showing 15 changed files with 1,712 additions and 1,154 deletions.
97 changes: 94 additions & 3 deletions python/Pipfile.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

59 changes: 48 additions & 11 deletions python/qemu/qmp/__init__.py
Expand Up @@ -30,21 +30,30 @@
TextIO,
Tuple,
Type,
TypeVar,
Union,
cast,
)


# QMPMessage is a QMP Message of any kind.
# e.g. {'yee': 'haw'}
#: QMPMessage is an entire QMP message of any kind.
QMPMessage = Dict[str, Any]

#: QMPReturnValue is the 'return' value of a command.
QMPReturnValue = object

#: QMPObject is any object in a QMP message.
QMPObject = Dict[str, object]

# QMPMessage can be outgoing commands or incoming events/returns.
# QMPReturnValue is usually a dict/json object, but due to QAPI's
# 'returns-whitelist', it can actually be anything.
#
# QMPReturnValue is the inner value of return values only.
# {'return': {}} is the QMPMessage,
# {'return': {}} is a QMPMessage,
# {} is the QMPReturnValue.
QMPMessage = Dict[str, Any]
QMPReturnValue = Dict[str, Any]

InternetAddrT = Tuple[str, str]

InternetAddrT = Tuple[str, int]
UnixAddrT = str
SocketAddrT = Union[InternetAddrT, UnixAddrT]

Expand Down Expand Up @@ -92,6 +101,12 @@ def __init__(self, reply: QMPMessage):
self.reply = reply


class QMPBadPortError(QMPError):
"""
Unable to parse socket address: Port was non-numerical.
"""


class QEMUMonitorProtocol:
"""
Provide an API to connect to QEMU via QEMU Monitor Protocol (QMP) and then
Expand Down Expand Up @@ -206,7 +221,9 @@ def __get_events(self, wait: Union[bool, float] = False) -> None:
if ret is None:
raise QMPConnectError("Error while reading from socket")

def __enter__(self) -> 'QEMUMonitorProtocol':
T = TypeVar('T')

def __enter__(self: T) -> T:
# Implement context manager enter function.
return self

Expand All @@ -219,6 +236,26 @@ def __exit__(self,
# Implement context manager exit function.
self.close()

@classmethod
def parse_address(cls, address: str) -> SocketAddrT:
"""
Parse a string into a QMP address.
Figure out if the argument is in the port:host form.
If it's not, it's probably a file path.
"""
components = address.split(':')
if len(components) == 2:
try:
port = int(components[1])
except ValueError:
msg = f"Bad port: '{components[1]}' in '{address}'."
raise QMPBadPortError(msg) from None
return (components[0], port)

# Treat as filepath.
return address

def connect(self, negotiate: bool = True) -> Optional[QMPMessage]:
"""
Connect to the QMP Monitor and perform capabilities negotiation.
Expand Down Expand Up @@ -271,8 +308,8 @@ def cmd_obj(self, qmp_cmd: QMPMessage) -> QMPMessage:
return resp

def cmd(self, name: str,
args: Optional[Dict[str, Any]] = None,
cmd_id: Optional[Any] = None) -> QMPMessage:
args: Optional[Dict[str, object]] = None,
cmd_id: Optional[object] = None) -> QMPMessage:
"""
Build a QMP command and send it to the QMP Monitor.
Expand All @@ -287,7 +324,7 @@ def cmd(self, name: str,
qmp_cmd['id'] = cmd_id
return self.cmd_obj(qmp_cmd)

def command(self, cmd: str, **kwds: Any) -> QMPReturnValue:
def command(self, cmd: str, **kwds: object) -> QMPReturnValue:
"""
Build and send a QMP command to the monitor, report errors if any
"""
Expand Down

0 comments on commit 0add99e

Please sign in to comment.