300 lines
		
	
	
		
			8.0 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
		
		
			
		
	
	
			300 lines
		
	
	
		
			8.0 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
|   | #!/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:]) |