| 
									
										
										
										
											2012-09-14 21:44:22 +09:00
										 |  |  | #!/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 | 
					
						
							|  |  |  | # | 
					
						
							| 
									
										
										
										
											2017-11-21 12:04:35 +00:00
										 |  |  | # See also: https://wiki.qemu.org/Features/QAPI/GuestAgent | 
					
						
							| 
									
										
										
										
											2012-09-14 21:44:22 +09:00
										 |  |  | # | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 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) | 
					
						
							| 
									
										
										
										
											2015-12-18 08:52:42 +01:00
										 |  |  |     except QemuGuestAgent.error as e: | 
					
						
							| 
									
										
										
										
											2012-09-14 21:44:22 +09:00
										 |  |  |         import errno | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         print(e) | 
					
						
							|  |  |  |         if e.errno == errno.ECONNREFUSED: | 
					
						
							|  |  |  |             print('Hint: qemu is not running?') | 
					
						
							|  |  |  |         sys.exit(1) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-08-07 11:40:39 -04:00
										 |  |  |     if cmd == 'fsfreeze' and args[0] == 'freeze': | 
					
						
							|  |  |  |         client.sync(60) | 
					
						
							|  |  |  |     elif cmd != 'ping': | 
					
						
							| 
									
										
										
										
											2012-09-14 21:44:22 +09:00
										 |  |  |         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:]) |