QMP: Revamp the qmp-shell script

This commit updates the qmp-shell script to use the new interface
introduced by the last commit.

Additionally, the following fixes/features are also introduced:

 o TCP sockets support
 o Update/add documentation
 o Simple command-line completion
 o Fix a number of unhandled errors

Signed-off-by: Luiz Capitulino <lcapitulino@redhat.com>
This commit is contained in:
Luiz Capitulino 2010-10-27 17:57:51 -02:00
parent 1d00a07de9
commit 9bed0d0d1c

View File

@ -1,8 +1,8 @@
#!/usr/bin/python #!/usr/bin/python
# #
# Simple QEMU shell on top of QMP # Low-level QEMU shell on top of QMP.
# #
# Copyright (C) 2009 Red Hat Inc. # Copyright (C) 2009, 2010 Red Hat Inc.
# #
# Authors: # Authors:
# Luiz Capitulino <lcapitulino@redhat.com> # Luiz Capitulino <lcapitulino@redhat.com>
@ -14,11 +14,11 @@
# #
# Start QEMU with: # Start QEMU with:
# #
# $ qemu [...] -monitor control,unix:./qmp,server # # qemu [...] -qmp unix:./qmp-sock,server
# #
# Run the shell: # Run the shell:
# #
# $ qmp-shell ./qmp # $ qmp-shell ./qmp-sock
# #
# Commands have the following format: # Commands have the following format:
# #
@ -26,48 +26,157 @@
# #
# For example: # For example:
# #
# (QEMU) info item=network # (QEMU) device_add driver=e1000 id=net1
# {u'return': {}}
# (QEMU)
import qmp import qmp
import readline import readline
from sys import argv,exit import sys
def shell_help(): class QMPCompleter(list):
print 'bye exit from the shell' def complete(self, text, state):
for cmd in self:
if cmd.startswith(text):
if not state:
return cmd
else:
state -= 1
def main(): class QMPShellError(Exception):
if len(argv) != 2: pass
print 'qemu-shell <unix-socket>'
exit(1)
qemu = qmp.QEMUMonitorProtocol(argv[1]) class QMPShellBadPort(QMPShellError):
qemu.connect() pass
qemu.send("qmp_capabilities")
print 'Connected!' # TODO: QMPShell's interface is a bit ugly (eg. _fill_completion() and
# _execute_cmd()). Let's design a better one.
class QMPShell(qmp.QEMUMonitorProtocol):
def __init__(self, address):
qmp.QEMUMonitorProtocol.__init__(self, self.__get_address(address))
self._greeting = None
self._completer = None
while True: def __get_address(self, arg):
"""
Figure out if the argument is in the port:host form, if it's not it's
probably a file path.
"""
addr = arg.split(':')
if len(addr) == 2:
try: try:
cmd = raw_input('(QEMU) ') port = int(addr[1])
except ValueError:
raise QMPShellBadPort
return ( addr[0], port )
# socket path
return arg
def _fill_completion(self):
for cmd in self.cmd('query-commands')['return']:
self._completer.append(cmd['name'])
def __completer_setup(self):
self._completer = QMPCompleter()
self._fill_completion()
readline.set_completer(self._completer.complete)
readline.parse_and_bind("tab: complete")
# XXX: default delimiters conflict with some command names (eg. query-),
# clearing everything as it doesn't seem to matter
readline.set_completer_delims('')
def __build_cmd(self, cmdline):
"""
Build a QMP input object from a user provided command-line in the
following format:
< command-name > [ arg-name1=arg1 ] ... [ arg-nameN=argN ]
"""
cmdargs = cmdline.split()
qmpcmd = { 'execute': cmdargs[0], 'arguments': {} }
for arg in cmdargs[1:]:
opt = arg.split('=')
try:
value = int(opt[1])
except ValueError:
value = opt[1]
qmpcmd['arguments'][opt[0]] = value
return qmpcmd
def _execute_cmd(self, cmdline):
try:
qmpcmd = self.__build_cmd(cmdline)
except:
print 'command format: <command-name> ',
print '[arg-name1=arg1] ... [arg-nameN=argN]'
return True
resp = self.cmd_obj(qmpcmd)
if resp is None:
print 'Disconnected'
return False
print resp
return True
def connect(self):
self._greeting = qmp.QEMUMonitorProtocol.connect(self)
self.__completer_setup()
def show_banner(self, msg='Welcome to the QMP low-level shell!'):
print msg
version = self._greeting['QMP']['version']['qemu']
print 'Connected to QEMU %d.%d.%d\n' % (version['major'],version['minor'],version['micro'])
def read_exec_command(self, prompt):
"""
Read and execute a command.
@return True if execution was ok, return False if disconnected.
"""
try:
cmdline = raw_input(prompt)
except EOFError: except EOFError:
print print
break return False
if cmd == '': if cmdline == '':
continue for ev in self.get_events():
elif cmd == 'bye': print ev
break self.clear_events()
elif cmd == 'help': return True
shell_help()
else: else:
return self._execute_cmd(cmdline)
def die(msg):
sys.stderr.write('ERROR: %s\n' % msg)
sys.exit(1)
def fail_cmdline(option=None):
if option:
sys.stderr.write('ERROR: bad command-line option \'%s\'\n' % option)
sys.stderr.write('qemu-shell [ -H ] < UNIX socket path> | < TCP address:port >\n')
sys.exit(1)
def main():
try: try:
resp = qemu.send(cmd) if len(sys.argv) == 2:
if resp == None: qemu = QMPShell(sys.argv[1])
print 'Disconnected' else:
break fail_cmdline()
print resp except QMPShellBadPort:
except IndexError: die('bad port number in command-line')
print '-> command format: <command-name> ',
print '[arg-name1=arg1] ... [arg-nameN=argN]' try:
qemu.connect()
except qmp.QMPConnectError:
die('Didn\'t get QMP greeting message')
except qmp.QMPCapabilitiesError:
die('Could not negotiate capabilities')
except qemu.error:
die('Could not connect to %s' % sys.argv[1])
qemu.show_banner()
while qemu.read_exec_command('(QEMU) '):
pass
qemu.close()
if __name__ == '__main__': if __name__ == '__main__':
main() main()