3 # QEMU Guest Agent Client
5 # Copyright (C) 2012 Ryota Ozaki <ozaki.ryota@gmail.com>
7 # This work is licensed under the terms of the GNU GPL, version 2. See
8 # the COPYING file in the top-level directory.
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
19 # $ qemu-ga-client --address=/tmp/qga.sock <command> [args...]
23 # $ export QGA_CLIENT_ADDRESS=/tmp/qga.sock
24 # $ qemu-ga-client <command> [args...]
28 # $ qemu-ga-client cat /etc/resolv.conf
29 # # Generated by NetworkManager
31 # $ qemu-ga-client fsfreeze status
33 # $ qemu-ga-client fsfreeze freeze
34 # 2 filesystems frozen
36 # See also: https://wiki.qemu.org/Features/QAPI/GuestAgent
39 from __future__
import print_function
46 class QemuGuestAgent(qmp
.QEMUMonitorProtocol
):
47 def __getattr__(self
, name
):
49 return self
.command('guest-' + name
.replace('_', '-'), **kwds
)
53 class QemuGuestAgentClient
:
54 error
= QemuGuestAgent
.error
56 def __init__(self
, address
):
57 self
.qga
= QemuGuestAgent(address
)
58 self
.qga
.connect(negotiate
=False)
60 def sync(self
, timeout
=3):
61 # Avoid being blocked forever
62 if not self
.ping(timeout
):
63 raise EnvironmentError('Agent seems not alive')
64 uid
= random
.randint(0, (1 << 32) - 1)
66 ret
= self
.qga
.sync(id=uid
)
67 if isinstance(ret
, int) and int(ret
) == uid
:
70 def __file_read_all(self
, handle
):
74 ret
= self
.qga
.file_read(handle
=handle
, count
=1024)
75 _data
= base64
.b64decode(ret
['buf-b64'])
81 handle
= self
.qga
.file_open(path
=path
)
83 data
= self
.__file
_read
_all
(handle
)
85 self
.qga
.file_close(handle
=handle
)
89 info
= self
.qga
.info()
92 msgs
.append('version: ' + info
['version'])
93 msgs
.append('supported_commands:')
94 enabled
= [c
['name'] for c
in info
['supported_commands'] if c
['enabled']]
95 msgs
.append('\tenabled: ' + ', '.join(enabled
))
96 disabled
= [c
['name'] for c
in info
['supported_commands'] if not c
['enabled']]
97 msgs
.append('\tdisabled: ' + ', '.join(disabled
))
99 return '\n'.join(msgs
)
101 def __gen_ipv4_netmask(self
, prefixlen
):
102 mask
= int('1' * prefixlen
+ '0' * (32 - prefixlen
), 2)
103 return '.'.join([str(mask
>> 24),
104 str((mask
>> 16) & 0xff),
105 str((mask
>> 8) & 0xff),
109 nifs
= self
.qga
.network_get_interfaces()
113 msgs
.append(nif
['name'] + ':')
114 if 'ip-addresses' in nif
:
115 for ipaddr
in nif
['ip-addresses']:
116 if ipaddr
['ip-address-type'] == 'ipv4':
117 addr
= ipaddr
['ip-address']
118 mask
= self
.__gen
_ipv
4_netmask
(int(ipaddr
['prefix']))
119 msgs
.append("\tinet %s netmask %s" % (addr
, mask
))
120 elif ipaddr
['ip-address-type'] == 'ipv6':
121 addr
= ipaddr
['ip-address']
122 prefix
= ipaddr
['prefix']
123 msgs
.append("\tinet6 %s prefixlen %s" % (addr
, prefix
))
124 if nif
['hardware-address'] != '00:00:00:00:00:00':
125 msgs
.append("\tether " + nif
['hardware-address'])
127 return '\n'.join(msgs
)
129 def ping(self
, timeout
):
130 self
.qga
.settimeout(timeout
)
133 except self
.qga
.timeout
:
137 def fsfreeze(self
, cmd
):
138 if cmd
not in ['status', 'freeze', 'thaw']:
139 raise StandardError('Invalid command: ' + cmd
)
141 return getattr(self
.qga
, 'fsfreeze' + '_' + cmd
)()
143 def fstrim(self
, minimum
=0):
144 return getattr(self
.qga
, 'fstrim')(minimum
=minimum
)
146 def suspend(self
, mode
):
147 if mode
not in ['disk', 'ram', 'hybrid']:
148 raise StandardError('Invalid mode: ' + mode
)
151 getattr(self
.qga
, 'suspend' + '_' + mode
)()
152 # On error exception will raise
153 except self
.qga
.timeout
:
154 # On success command will timed out
157 def shutdown(self
, mode
='powerdown'):
158 if mode
not in ['powerdown', 'halt', 'reboot']:
159 raise StandardError('Invalid mode: ' + mode
)
162 self
.qga
.shutdown(mode
=mode
)
163 except self
.qga
.timeout
:
167 def _cmd_cat(client
, args
):
169 print('Invalid argument')
170 print('Usage: cat <file>')
172 print(client
.read(args
[0]))
175 def _cmd_fsfreeze(client
, args
):
176 usage
= 'Usage: fsfreeze status|freeze|thaw'
178 print('Invalid argument')
181 if args
[0] not in ['status', 'freeze', 'thaw']:
182 print('Invalid command: ' + args
[0])
186 ret
= client
.fsfreeze(cmd
)
189 elif cmd
== 'freeze':
190 print("%d filesystems frozen" % ret
)
192 print("%d filesystems thawed" % ret
)
195 def _cmd_fstrim(client
, args
):
199 minimum
= int(args
[0])
200 print(client
.fstrim(minimum
))
203 def _cmd_ifconfig(client
, args
):
204 print(client
.ifconfig())
207 def _cmd_info(client
, args
):
211 def _cmd_ping(client
, args
):
215 timeout
= float(args
[0])
216 alive
= client
.ping(timeout
)
218 print("Not responded in %s sec" % args
[0])
222 def _cmd_suspend(client
, args
):
223 usage
= 'Usage: suspend disk|ram|hybrid'
225 print('Less argument')
228 if args
[0] not in ['disk', 'ram', 'hybrid']:
229 print('Invalid command: ' + args
[0])
232 client
.suspend(args
[0])
235 def _cmd_shutdown(client
, args
):
237 _cmd_powerdown
= _cmd_shutdown
240 def _cmd_halt(client
, args
):
241 client
.shutdown('halt')
244 def _cmd_reboot(client
, args
):
245 client
.shutdown('reboot')
248 commands
= [m
.replace('_cmd_', '') for m
in dir() if '_cmd_' in m
]
251 def main(address
, cmd
, args
):
252 if not os
.path
.exists(address
):
253 print('%s not found' % address
)
256 if cmd
not in commands
:
257 print('Invalid command: ' + cmd
)
258 print('Available commands: ' + ', '.join(commands
))
262 client
= QemuGuestAgentClient(address
)
263 except QemuGuestAgent
.error
as e
:
267 if e
.errno
== errno
.ECONNREFUSED
:
268 print('Hint: qemu is not running?')
271 if cmd
== 'fsfreeze' and args
[0] == 'freeze':
276 globals()['_cmd_' + cmd
](client
, args
)
279 if __name__
== '__main__':
284 address
= os
.environ
['QGA_CLIENT_ADDRESS'] if 'QGA_CLIENT_ADDRESS' in os
.environ
else None
286 usage
= "%prog [--address=<unix_path>|<ipv4_address>] <command> [args...]\n"
287 usage
+= '<command>: ' + ', '.join(commands
)
288 parser
= optparse
.OptionParser(usage
=usage
)
289 parser
.add_option('--address', action
='store', type='string',
290 default
=address
, help='Specify a ip:port pair or a unix socket path')
291 options
, args
= parser
.parse_args()
293 address
= options
.address
295 parser
.error('address is not specified')
299 parser
.error('Less argument')
302 main(address
, args
[0], args
[1:])