PEP 8 calls for it, because it's forward compatible with Python 3. Supported since Python 2.6, which we require (commit fec2103). Signed-off-by: Markus Armbruster <armbru@redhat.com> Reviewed-by: Eric Blake <eblake@redhat.com> Acked-by: Stefan Hajnoczi <stefanha@redhat.com> Message-Id: <1450425164-24969-3-git-send-email-armbru@redhat.com>
		
			
				
	
	
		
			302 lines
		
	
	
		
			8.1 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			302 lines
		
	
	
		
			8.1 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
#!/usr/bin/python
 | 
						|
 | 
						|
# QEMU Guest Agent Client
 | 
						|
#
 | 
						|
# Copyright (C) 2012 Ryota Ozaki <ozaki.ryota@gmail.com>
 | 
						|
#
 | 
						|
# This work is licensed under the terms of the GNU GPL, version 2.  See
 | 
						|
# the COPYING file in the top-level directory.
 | 
						|
#
 | 
						|
# Usage:
 | 
						|
#
 | 
						|
# Start QEMU with:
 | 
						|
#
 | 
						|
# # qemu [...] -chardev socket,path=/tmp/qga.sock,server,nowait,id=qga0 \
 | 
						|
#   -device virtio-serial -device virtserialport,chardev=qga0,name=org.qemu.guest_agent.0
 | 
						|
#
 | 
						|
# Run the script:
 | 
						|
#
 | 
						|
# $ qemu-ga-client --address=/tmp/qga.sock <command> [args...]
 | 
						|
#
 | 
						|
# or
 | 
						|
#
 | 
						|
# $ export QGA_CLIENT_ADDRESS=/tmp/qga.sock
 | 
						|
# $ qemu-ga-client <command> [args...]
 | 
						|
#
 | 
						|
# For example:
 | 
						|
#
 | 
						|
# $ qemu-ga-client cat /etc/resolv.conf
 | 
						|
# # Generated by NetworkManager
 | 
						|
# nameserver 10.0.2.3
 | 
						|
# $ qemu-ga-client fsfreeze status
 | 
						|
# thawed
 | 
						|
# $ qemu-ga-client fsfreeze freeze
 | 
						|
# 2 filesystems frozen
 | 
						|
#
 | 
						|
# See also: http://wiki.qemu-project.org/Features/QAPI/GuestAgent
 | 
						|
#
 | 
						|
 | 
						|
import base64
 | 
						|
import random
 | 
						|
 | 
						|
import qmp
 | 
						|
 | 
						|
 | 
						|
class QemuGuestAgent(qmp.QEMUMonitorProtocol):
 | 
						|
    def __getattr__(self, name):
 | 
						|
        def wrapper(**kwds):
 | 
						|
            return self.command('guest-' + name.replace('_', '-'), **kwds)
 | 
						|
        return wrapper
 | 
						|
 | 
						|
 | 
						|
class QemuGuestAgentClient:
 | 
						|
    error = QemuGuestAgent.error
 | 
						|
 | 
						|
    def __init__(self, address):
 | 
						|
        self.qga = QemuGuestAgent(address)
 | 
						|
        self.qga.connect(negotiate=False)
 | 
						|
 | 
						|
    def sync(self, timeout=3):
 | 
						|
        # Avoid being blocked forever
 | 
						|
        if not self.ping(timeout):
 | 
						|
            raise EnvironmentError('Agent seems not alive')
 | 
						|
        uid = random.randint(0, (1 << 32) - 1)
 | 
						|
        while True:
 | 
						|
            ret = self.qga.sync(id=uid)
 | 
						|
            if isinstance(ret, int) and int(ret) == uid:
 | 
						|
                break
 | 
						|
 | 
						|
    def __file_read_all(self, handle):
 | 
						|
        eof = False
 | 
						|
        data = ''
 | 
						|
        while not eof:
 | 
						|
            ret = self.qga.file_read(handle=handle, count=1024)
 | 
						|
            _data = base64.b64decode(ret['buf-b64'])
 | 
						|
            data += _data
 | 
						|
            eof = ret['eof']
 | 
						|
        return data
 | 
						|
 | 
						|
    def read(self, path):
 | 
						|
        handle = self.qga.file_open(path=path)
 | 
						|
        try:
 | 
						|
            data = self.__file_read_all(handle)
 | 
						|
        finally:
 | 
						|
            self.qga.file_close(handle=handle)
 | 
						|
        return data
 | 
						|
 | 
						|
    def info(self):
 | 
						|
        info = self.qga.info()
 | 
						|
 | 
						|
        msgs = []
 | 
						|
        msgs.append('version: ' + info['version'])
 | 
						|
        msgs.append('supported_commands:')
 | 
						|
        enabled = [c['name'] for c in info['supported_commands'] if c['enabled']]
 | 
						|
        msgs.append('\tenabled: ' + ', '.join(enabled))
 | 
						|
        disabled = [c['name'] for c in info['supported_commands'] if not c['enabled']]
 | 
						|
        msgs.append('\tdisabled: ' + ', '.join(disabled))
 | 
						|
 | 
						|
        return '\n'.join(msgs)
 | 
						|
 | 
						|
    def __gen_ipv4_netmask(self, prefixlen):
 | 
						|
        mask = int('1' * prefixlen + '0' * (32 - prefixlen), 2)
 | 
						|
        return '.'.join([str(mask >> 24),
 | 
						|
                         str((mask >> 16) & 0xff),
 | 
						|
                         str((mask >> 8) & 0xff),
 | 
						|
                         str(mask & 0xff)])
 | 
						|
 | 
						|
    def ifconfig(self):
 | 
						|
        nifs = self.qga.network_get_interfaces()
 | 
						|
 | 
						|
        msgs = []
 | 
						|
        for nif in nifs:
 | 
						|
            msgs.append(nif['name'] + ':')
 | 
						|
            if 'ip-addresses' in nif:
 | 
						|
                for ipaddr in nif['ip-addresses']:
 | 
						|
                    if ipaddr['ip-address-type'] == 'ipv4':
 | 
						|
                        addr = ipaddr['ip-address']
 | 
						|
                        mask = self.__gen_ipv4_netmask(int(ipaddr['prefix']))
 | 
						|
                        msgs.append("\tinet %s  netmask %s" % (addr, mask))
 | 
						|
                    elif ipaddr['ip-address-type'] == 'ipv6':
 | 
						|
                        addr = ipaddr['ip-address']
 | 
						|
                        prefix = ipaddr['prefix']
 | 
						|
                        msgs.append("\tinet6 %s  prefixlen %s" % (addr, prefix))
 | 
						|
            if nif['hardware-address'] != '00:00:00:00:00:00':
 | 
						|
                msgs.append("\tether " + nif['hardware-address'])
 | 
						|
 | 
						|
        return '\n'.join(msgs)
 | 
						|
 | 
						|
    def ping(self, timeout):
 | 
						|
        self.qga.settimeout(timeout)
 | 
						|
        try:
 | 
						|
            self.qga.ping()
 | 
						|
        except self.qga.timeout:
 | 
						|
            return False
 | 
						|
        return True
 | 
						|
 | 
						|
    def fsfreeze(self, cmd):
 | 
						|
        if cmd not in ['status', 'freeze', 'thaw']:
 | 
						|
            raise StandardError('Invalid command: ' + cmd)
 | 
						|
 | 
						|
        return getattr(self.qga, 'fsfreeze' + '_' + cmd)()
 | 
						|
 | 
						|
    def fstrim(self, minimum=0):
 | 
						|
        return getattr(self.qga, 'fstrim')(minimum=minimum)
 | 
						|
 | 
						|
    def suspend(self, mode):
 | 
						|
        if mode not in ['disk', 'ram', 'hybrid']:
 | 
						|
            raise StandardError('Invalid mode: ' + mode)
 | 
						|
 | 
						|
        try:
 | 
						|
            getattr(self.qga, 'suspend' + '_' + mode)()
 | 
						|
            # On error exception will raise
 | 
						|
        except self.qga.timeout:
 | 
						|
            # On success command will timed out
 | 
						|
            return
 | 
						|
 | 
						|
    def shutdown(self, mode='powerdown'):
 | 
						|
        if mode not in ['powerdown', 'halt', 'reboot']:
 | 
						|
            raise StandardError('Invalid mode: ' + mode)
 | 
						|
 | 
						|
        try:
 | 
						|
            self.qga.shutdown(mode=mode)
 | 
						|
        except self.qga.timeout:
 | 
						|
            return
 | 
						|
 | 
						|
 | 
						|
def _cmd_cat(client, args):
 | 
						|
    if len(args) != 1:
 | 
						|
        print('Invalid argument')
 | 
						|
        print('Usage: cat <file>')
 | 
						|
        sys.exit(1)
 | 
						|
    print(client.read(args[0]))
 | 
						|
 | 
						|
 | 
						|
def _cmd_fsfreeze(client, args):
 | 
						|
    usage = 'Usage: fsfreeze status|freeze|thaw'
 | 
						|
    if len(args) != 1:
 | 
						|
        print('Invalid argument')
 | 
						|
        print(usage)
 | 
						|
        sys.exit(1)
 | 
						|
    if args[0] not in ['status', 'freeze', 'thaw']:
 | 
						|
        print('Invalid command: ' + args[0])
 | 
						|
        print(usage)
 | 
						|
        sys.exit(1)
 | 
						|
    cmd = args[0]
 | 
						|
    ret = client.fsfreeze(cmd)
 | 
						|
    if cmd == 'status':
 | 
						|
        print(ret)
 | 
						|
    elif cmd == 'freeze':
 | 
						|
        print("%d filesystems frozen" % ret)
 | 
						|
    else:
 | 
						|
        print("%d filesystems thawed" % ret)
 | 
						|
 | 
						|
 | 
						|
def _cmd_fstrim(client, args):
 | 
						|
    if len(args) == 0:
 | 
						|
        minimum = 0
 | 
						|
    else:
 | 
						|
        minimum = int(args[0])
 | 
						|
    print(client.fstrim(minimum))
 | 
						|
 | 
						|
 | 
						|
def _cmd_ifconfig(client, args):
 | 
						|
    print(client.ifconfig())
 | 
						|
 | 
						|
 | 
						|
def _cmd_info(client, args):
 | 
						|
    print(client.info())
 | 
						|
 | 
						|
 | 
						|
def _cmd_ping(client, args):
 | 
						|
    if len(args) == 0:
 | 
						|
        timeout = 3
 | 
						|
    else:
 | 
						|
        timeout = float(args[0])
 | 
						|
    alive = client.ping(timeout)
 | 
						|
    if not alive:
 | 
						|
        print("Not responded in %s sec" % args[0])
 | 
						|
        sys.exit(1)
 | 
						|
 | 
						|
 | 
						|
def _cmd_suspend(client, args):
 | 
						|
    usage = 'Usage: suspend disk|ram|hybrid'
 | 
						|
    if len(args) != 1:
 | 
						|
        print('Less argument')
 | 
						|
        print(usage)
 | 
						|
        sys.exit(1)
 | 
						|
    if args[0] not in ['disk', 'ram', 'hybrid']:
 | 
						|
        print('Invalid command: ' + args[0])
 | 
						|
        print(usage)
 | 
						|
        sys.exit(1)
 | 
						|
    client.suspend(args[0])
 | 
						|
 | 
						|
 | 
						|
def _cmd_shutdown(client, args):
 | 
						|
    client.shutdown()
 | 
						|
_cmd_powerdown = _cmd_shutdown
 | 
						|
 | 
						|
 | 
						|
def _cmd_halt(client, args):
 | 
						|
    client.shutdown('halt')
 | 
						|
 | 
						|
 | 
						|
def _cmd_reboot(client, args):
 | 
						|
    client.shutdown('reboot')
 | 
						|
 | 
						|
 | 
						|
commands = [m.replace('_cmd_', '') for m in dir() if '_cmd_' in m]
 | 
						|
 | 
						|
 | 
						|
def main(address, cmd, args):
 | 
						|
    if not os.path.exists(address):
 | 
						|
        print('%s not found' % address)
 | 
						|
        sys.exit(1)
 | 
						|
 | 
						|
    if cmd not in commands:
 | 
						|
        print('Invalid command: ' + cmd)
 | 
						|
        print('Available commands: ' + ', '.join(commands))
 | 
						|
        sys.exit(1)
 | 
						|
 | 
						|
    try:
 | 
						|
        client = QemuGuestAgentClient(address)
 | 
						|
    except QemuGuestAgent.error as e:
 | 
						|
        import errno
 | 
						|
 | 
						|
        print(e)
 | 
						|
        if e.errno == errno.ECONNREFUSED:
 | 
						|
            print('Hint: qemu is not running?')
 | 
						|
        sys.exit(1)
 | 
						|
 | 
						|
    if cmd == 'fsfreeze' and args[0] == 'freeze':
 | 
						|
        client.sync(60)
 | 
						|
    elif cmd != 'ping':
 | 
						|
        client.sync()
 | 
						|
 | 
						|
    globals()['_cmd_' + cmd](client, args)
 | 
						|
 | 
						|
 | 
						|
if __name__ == '__main__':
 | 
						|
    import sys
 | 
						|
    import os
 | 
						|
    import optparse
 | 
						|
 | 
						|
    address = os.environ['QGA_CLIENT_ADDRESS'] if 'QGA_CLIENT_ADDRESS' in os.environ else None
 | 
						|
 | 
						|
    usage = "%prog [--address=<unix_path>|<ipv4_address>] <command> [args...]\n"
 | 
						|
    usage += '<command>: ' + ', '.join(commands)
 | 
						|
    parser = optparse.OptionParser(usage=usage)
 | 
						|
    parser.add_option('--address', action='store', type='string',
 | 
						|
                      default=address, help='Specify a ip:port pair or a unix socket path')
 | 
						|
    options, args = parser.parse_args()
 | 
						|
 | 
						|
    address = options.address
 | 
						|
    if address is None:
 | 
						|
        parser.error('address is not specified')
 | 
						|
        sys.exit(1)
 | 
						|
 | 
						|
    if len(args) == 0:
 | 
						|
        parser.error('Less argument')
 | 
						|
        sys.exit(1)
 | 
						|
 | 
						|
    main(address, args[0], args[1:])
 |