|  | #!/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.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, e: | 
|  | import errno | 
|  |  | 
|  | print(e) | 
|  | if e.errno == errno.ECONNREFUSED: | 
|  | print('Hint: qemu is not running?') | 
|  | sys.exit(1) | 
|  |  | 
|  | if 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:]) |