python: introduce qmp-shell-wrap convenience tool

With the current 'qmp-shell' tool developers must first spawn QEMU with
a suitable -qmp arg and then spawn qmp-shell in a separate terminal
pointing to the right socket.

With 'qmp-shell-wrap' developers can ignore QMP sockets entirely and
just pass the QEMU command and arguments they want. The program will
listen on a UNIX socket and tell QEMU to connect QMP to that.

For example, this:

 # qmp-shell-wrap -- qemu-system-x86_64 -display none

Is roughly equivalent of running:

 # qemu-system-x86_64 -display none -qmp qmp-shell-1234 &
 # qmp-shell qmp-shell-1234

Except that 'qmp-shell-wrap' switches the socket peers around so that
it is the UNIX socket server and QEMU is the socket client. This makes
QEMU reliably go away when qmp-shell-wrap exits, closing the server
socket.

Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
Message-id: 20220128161157.36261-2-berrange@redhat.com
[Edited for rebase. --js]
Signed-off-by: John Snow <jsnow@redhat.com>
This commit is contained in:
Daniel P. Berrangé 2022-01-28 16:11:56 +00:00 committed by John Snow
parent 31e3caf21b
commit 439125293c
3 changed files with 73 additions and 4 deletions

View File

@ -86,6 +86,7 @@ import logging
import os
import re
import readline
from subprocess import Popen
import sys
from typing import (
Iterator,
@ -167,8 +168,10 @@ class QMPShell(QEMUMonitorProtocol):
:param verbose: Echo outgoing QMP messages to console.
"""
def __init__(self, address: SocketAddrT,
pretty: bool = False, verbose: bool = False):
super().__init__(address)
pretty: bool = False,
verbose: bool = False,
server: bool = False):
super().__init__(address, server=server)
self._greeting: Optional[QMPMessage] = None
self._completer = QMPCompleter()
self._transmode = False
@ -409,8 +412,10 @@ class HMPShell(QMPShell):
:param verbose: Echo outgoing QMP messages to console.
"""
def __init__(self, address: SocketAddrT,
pretty: bool = False, verbose: bool = False):
super().__init__(address, pretty, verbose)
pretty: bool = False,
verbose: bool = False,
server: bool = False):
super().__init__(address, pretty, verbose, server)
self._cpu_index = 0
def _cmd_completion(self) -> None:
@ -533,5 +538,57 @@ def main() -> None:
pass
def main_wrap() -> None:
"""
qmp-shell-wrap entry point: parse command line arguments and
start the REPL.
"""
parser = argparse.ArgumentParser()
parser.add_argument('-H', '--hmp', action='store_true',
help='Use HMP interface')
parser.add_argument('-v', '--verbose', action='store_true',
help='Verbose (echo commands sent and received)')
parser.add_argument('-p', '--pretty', action='store_true',
help='Pretty-print JSON')
parser.add_argument('command', nargs=argparse.REMAINDER,
help='QEMU command line to invoke')
args = parser.parse_args()
cmd = args.command
if len(cmd) != 0 and cmd[0] == '--':
cmd = cmd[1:]
if len(cmd) == 0:
cmd = ["qemu-system-x86_64"]
sockpath = "qmp-shell-wrap-%d" % os.getpid()
cmd += ["-qmp", "unix:%s" % sockpath]
shell_class = HMPShell if args.hmp else QMPShell
try:
address = shell_class.parse_address(sockpath)
except QMPBadPortError:
parser.error(f"Bad port number: {sockpath}")
return # pycharm doesn't know error() is noreturn
try:
with shell_class(address, args.pretty, args.verbose, True) as qemu:
with Popen(cmd):
try:
qemu.accept()
except ConnectError as err:
if isinstance(err.exc, OSError):
die(f"Couldn't connect to {args.qmp_server}: {err!s}")
die(str(err))
for _ in qemu.repl():
pass
finally:
os.unlink(sockpath)
if __name__ == '__main__':
main()

View File

@ -68,6 +68,7 @@ console_scripts =
qom-fuse = qemu.utils.qom_fuse:QOMFuse.entry_point [fuse]
qemu-ga-client = qemu.utils.qemu_ga_client:main
qmp-shell = qemu.aqmp.qmp_shell:main
qmp-shell-wrap = qemu.aqmp.qmp_shell:main_wrap
aqmp-tui = qemu.aqmp.aqmp_tui:main [tui]
[flake8]

11
scripts/qmp/qmp-shell-wrap Executable file
View File

@ -0,0 +1,11 @@
#!/usr/bin/env python3
import os
import sys
sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', 'python'))
from qemu.qmp import qmp_shell
if __name__ == '__main__':
qmp_shell.main_wrap()