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:
parent
31e3caf21b
commit
439125293c
@ -86,6 +86,7 @@ import logging
|
|||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import readline
|
import readline
|
||||||
|
from subprocess import Popen
|
||||||
import sys
|
import sys
|
||||||
from typing import (
|
from typing import (
|
||||||
Iterator,
|
Iterator,
|
||||||
@ -167,8 +168,10 @@ class QMPShell(QEMUMonitorProtocol):
|
|||||||
:param verbose: Echo outgoing QMP messages to console.
|
:param verbose: Echo outgoing QMP messages to console.
|
||||||
"""
|
"""
|
||||||
def __init__(self, address: SocketAddrT,
|
def __init__(self, address: SocketAddrT,
|
||||||
pretty: bool = False, verbose: bool = False):
|
pretty: bool = False,
|
||||||
super().__init__(address)
|
verbose: bool = False,
|
||||||
|
server: bool = False):
|
||||||
|
super().__init__(address, server=server)
|
||||||
self._greeting: Optional[QMPMessage] = None
|
self._greeting: Optional[QMPMessage] = None
|
||||||
self._completer = QMPCompleter()
|
self._completer = QMPCompleter()
|
||||||
self._transmode = False
|
self._transmode = False
|
||||||
@ -409,8 +412,10 @@ class HMPShell(QMPShell):
|
|||||||
:param verbose: Echo outgoing QMP messages to console.
|
:param verbose: Echo outgoing QMP messages to console.
|
||||||
"""
|
"""
|
||||||
def __init__(self, address: SocketAddrT,
|
def __init__(self, address: SocketAddrT,
|
||||||
pretty: bool = False, verbose: bool = False):
|
pretty: bool = False,
|
||||||
super().__init__(address, pretty, verbose)
|
verbose: bool = False,
|
||||||
|
server: bool = False):
|
||||||
|
super().__init__(address, pretty, verbose, server)
|
||||||
self._cpu_index = 0
|
self._cpu_index = 0
|
||||||
|
|
||||||
def _cmd_completion(self) -> None:
|
def _cmd_completion(self) -> None:
|
||||||
@ -533,5 +538,57 @@ def main() -> None:
|
|||||||
pass
|
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__':
|
if __name__ == '__main__':
|
||||||
main()
|
main()
|
||||||
|
@ -68,6 +68,7 @@ console_scripts =
|
|||||||
qom-fuse = qemu.utils.qom_fuse:QOMFuse.entry_point [fuse]
|
qom-fuse = qemu.utils.qom_fuse:QOMFuse.entry_point [fuse]
|
||||||
qemu-ga-client = qemu.utils.qemu_ga_client:main
|
qemu-ga-client = qemu.utils.qemu_ga_client:main
|
||||||
qmp-shell = qemu.aqmp.qmp_shell: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]
|
aqmp-tui = qemu.aqmp.aqmp_tui:main [tui]
|
||||||
|
|
||||||
[flake8]
|
[flake8]
|
||||||
|
11
scripts/qmp/qmp-shell-wrap
Executable file
11
scripts/qmp/qmp-shell-wrap
Executable 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()
|
Loading…
Reference in New Issue
Block a user