qemu/scripts/qmp/qemu-ga-client
<<
>>
Prefs
   1#!/usr/bin/python
   2
   3# QEMU Guest Agent Client
   4#
   5# Copyright (C) 2012 Ryota Ozaki <ozaki.ryota@gmail.com>
   6#
   7# This work is licensed under the terms of the GNU GPL, version 2.  See
   8# the COPYING file in the top-level directory.
   9#
  10# Usage:
  11#
  12# Start QEMU with:
  13#
  14# # qemu [...] -chardev socket,path=/tmp/qga.sock,server,nowait,id=qga0 \
  15#   -device virtio-serial -device virtserialport,chardev=qga0,name=org.qemu.guest_agent.0
  16#
  17# Run the script:
  18#
  19# $ qemu-ga-client --address=/tmp/qga.sock <command> [args...]
  20#
  21# or
  22#
  23# $ export QGA_CLIENT_ADDRESS=/tmp/qga.sock
  24# $ qemu-ga-client <command> [args...]
  25#
  26# For example:
  27#
  28# $ qemu-ga-client cat /etc/resolv.conf
  29# # Generated by NetworkManager
  30# nameserver 10.0.2.3
  31# $ qemu-ga-client fsfreeze status
  32# thawed
  33# $ qemu-ga-client fsfreeze freeze
  34# 2 filesystems frozen
  35#
  36# See also: http://wiki.qemu-project.org/Features/QAPI/GuestAgent
  37#
  38
  39import base64
  40import random
  41
  42import qmp
  43
  44
  45class QemuGuestAgent(qmp.QEMUMonitorProtocol):
  46    def __getattr__(self, name):
  47        def wrapper(**kwds):
  48            return self.command('guest-' + name.replace('_', '-'), **kwds)
  49        return wrapper
  50
  51
  52class QemuGuestAgentClient:
  53    error = QemuGuestAgent.error
  54
  55    def __init__(self, address):
  56        self.qga = QemuGuestAgent(address)
  57        self.qga.connect(negotiate=False)
  58
  59    def sync(self, timeout=3):
  60        # Avoid being blocked forever
  61        if not self.ping(timeout):
  62            raise EnvironmentError('Agent seems not alive')
  63        uid = random.randint(0, (1 << 32) - 1)
  64        while True:
  65            ret = self.qga.sync(id=uid)
  66            if isinstance(ret, int) and int(ret) == uid:
  67                break
  68
  69    def __file_read_all(self, handle):
  70        eof = False
  71        data = ''
  72        while not eof:
  73            ret = self.qga.file_read(handle=handle, count=1024)
  74            _data = base64.b64decode(ret['buf-b64'])
  75            data += _data
  76            eof = ret['eof']
  77        return data
  78
  79    def read(self, path):
  80        handle = self.qga.file_open(path=path)
  81        try:
  82            data = self.__file_read_all(handle)
  83        finally:
  84            self.qga.file_close(handle=handle)
  85        return data
  86
  87    def info(self):
  88        info = self.qga.info()
  89
  90        msgs = []
  91        msgs.append('version: ' + info['version'])
  92        msgs.append('supported_commands:')
  93        enabled = [c['name'] for c in info['supported_commands'] if c['enabled']]
  94        msgs.append('\tenabled: ' + ', '.join(enabled))
  95        disabled = [c['name'] for c in info['supported_commands'] if not c['enabled']]
  96        msgs.append('\tdisabled: ' + ', '.join(disabled))
  97
  98        return '\n'.join(msgs)
  99
 100    def __gen_ipv4_netmask(self, prefixlen):
 101        mask = int('1' * prefixlen + '0' * (32 - prefixlen), 2)
 102        return '.'.join([str(mask >> 24),
 103                         str((mask >> 16) & 0xff),
 104                         str((mask >> 8) & 0xff),
 105                         str(mask & 0xff)])
 106
 107    def ifconfig(self):
 108        nifs = self.qga.network_get_interfaces()
 109
 110        msgs = []
 111        for nif in nifs:
 112            msgs.append(nif['name'] + ':')
 113            if 'ip-addresses' in nif:
 114                for ipaddr in nif['ip-addresses']:
 115                    if ipaddr['ip-address-type'] == 'ipv4':
 116                        addr = ipaddr['ip-address']
 117                        mask = self.__gen_ipv4_netmask(int(ipaddr['prefix']))
 118                        msgs.append("\tinet %s  netmask %s" % (addr, mask))
 119                    elif ipaddr['ip-address-type'] == 'ipv6':
 120                        addr = ipaddr['ip-address']
 121                        prefix = ipaddr['prefix']
 122                        msgs.append("\tinet6 %s  prefixlen %s" % (addr, prefix))
 123            if nif['hardware-address'] != '00:00:00:00:00:00':
 124                msgs.append("\tether " + nif['hardware-address'])
 125
 126        return '\n'.join(msgs)
 127
 128    def ping(self, timeout):
 129        self.qga.settimeout(timeout)
 130        try:
 131            self.qga.ping()
 132        except self.qga.timeout:
 133            return False
 134        return True
 135
 136    def fsfreeze(self, cmd):
 137        if cmd not in ['status', 'freeze', 'thaw']:
 138            raise StandardError('Invalid command: ' + cmd)
 139
 140        return getattr(self.qga, 'fsfreeze' + '_' + cmd)()
 141
 142    def fstrim(self, minimum=0):
 143        return getattr(self.qga, 'fstrim')(minimum=minimum)
 144
 145    def suspend(self, mode):
 146        if mode not in ['disk', 'ram', 'hybrid']:
 147            raise StandardError('Invalid mode: ' + mode)
 148
 149        try:
 150            getattr(self.qga, 'suspend' + '_' + mode)()
 151            # On error exception will raise
 152        except self.qga.timeout:
 153            # On success command will timed out
 154            return
 155
 156    def shutdown(self, mode='powerdown'):
 157        if mode not in ['powerdown', 'halt', 'reboot']:
 158            raise StandardError('Invalid mode: ' + mode)
 159
 160        try:
 161            self.qga.shutdown(mode=mode)
 162        except self.qga.timeout:
 163            return
 164
 165
 166def _cmd_cat(client, args):
 167    if len(args) != 1:
 168        print('Invalid argument')
 169        print('Usage: cat <file>')
 170        sys.exit(1)
 171    print(client.read(args[0]))
 172
 173
 174def _cmd_fsfreeze(client, args):
 175    usage = 'Usage: fsfreeze status|freeze|thaw'
 176    if len(args) != 1:
 177        print('Invalid argument')
 178        print(usage)
 179        sys.exit(1)
 180    if args[0] not in ['status', 'freeze', 'thaw']:
 181        print('Invalid command: ' + args[0])
 182        print(usage)
 183        sys.exit(1)
 184    cmd = args[0]
 185    ret = client.fsfreeze(cmd)
 186    if cmd == 'status':
 187        print(ret)
 188    elif cmd == 'freeze':
 189        print("%d filesystems frozen" % ret)
 190    else:
 191        print("%d filesystems thawed" % ret)
 192
 193
 194def _cmd_fstrim(client, args):
 195    if len(args) == 0:
 196        minimum = 0
 197    else:
 198        minimum = int(args[0])
 199    print(client.fstrim(minimum))
 200
 201
 202def _cmd_ifconfig(client, args):
 203    print(client.ifconfig())
 204
 205
 206def _cmd_info(client, args):
 207    print(client.info())
 208
 209
 210def _cmd_ping(client, args):
 211    if len(args) == 0:
 212        timeout = 3
 213    else:
 214        timeout = float(args[0])
 215    alive = client.ping(timeout)
 216    if not alive:
 217        print("Not responded in %s sec" % args[0])
 218        sys.exit(1)
 219
 220
 221def _cmd_suspend(client, args):
 222    usage = 'Usage: suspend disk|ram|hybrid'
 223    if len(args) != 1:
 224        print('Less argument')
 225        print(usage)
 226        sys.exit(1)
 227    if args[0] not in ['disk', 'ram', 'hybrid']:
 228        print('Invalid command: ' + args[0])
 229        print(usage)
 230        sys.exit(1)
 231    client.suspend(args[0])
 232
 233
 234def _cmd_shutdown(client, args):
 235    client.shutdown()
 236_cmd_powerdown = _cmd_shutdown
 237
 238
 239def _cmd_halt(client, args):
 240    client.shutdown('halt')
 241
 242
 243def _cmd_reboot(client, args):
 244    client.shutdown('reboot')
 245
 246
 247commands = [m.replace('_cmd_', '') for m in dir() if '_cmd_' in m]
 248
 249
 250def main(address, cmd, args):
 251    if not os.path.exists(address):
 252        print('%s not found' % address)
 253        sys.exit(1)
 254
 255    if cmd not in commands:
 256        print('Invalid command: ' + cmd)
 257        print('Available commands: ' + ', '.join(commands))
 258        sys.exit(1)
 259
 260    try:
 261        client = QemuGuestAgentClient(address)
 262    except QemuGuestAgent.error as e:
 263        import errno
 264
 265        print(e)
 266        if e.errno == errno.ECONNREFUSED:
 267            print('Hint: qemu is not running?')
 268        sys.exit(1)
 269
 270    if cmd == 'fsfreeze' and args[0] == 'freeze':
 271        client.sync(60)
 272    elif cmd != 'ping':
 273        client.sync()
 274
 275    globals()['_cmd_' + cmd](client, args)
 276
 277
 278if __name__ == '__main__':
 279    import sys
 280    import os
 281    import optparse
 282
 283    address = os.environ['QGA_CLIENT_ADDRESS'] if 'QGA_CLIENT_ADDRESS' in os.environ else None
 284
 285    usage = "%prog [--address=<unix_path>|<ipv4_address>] <command> [args...]\n"
 286    usage += '<command>: ' + ', '.join(commands)
 287    parser = optparse.OptionParser(usage=usage)
 288    parser.add_option('--address', action='store', type='string',
 289                      default=address, help='Specify a ip:port pair or a unix socket path')
 290    options, args = parser.parse_args()
 291
 292    address = options.address
 293    if address is None:
 294        parser.error('address is not specified')
 295        sys.exit(1)
 296
 297    if len(args) == 0:
 298        parser.error('Less argument')
 299        sys.exit(1)
 300
 301    main(address, args[0], args[1:])
 302