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