| #!/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: https://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 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:]) |