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
45 class QemuGuestAgent(qmp
.QEMUMonitorProtocol
):
46 def __getattr__(self
, name
):
48 return self
.command('guest-' + name
.replace('_', '-'), **kwds
)
52 class QemuGuestAgentClient
:
53 error
= QemuGuestAgent
.error
55 def __init__(self
, address
):
56 self
.qga
= QemuGuestAgent(address
)
57 self
.qga
.connect(negotiate
=False)
59 def sync(self
, timeout
=3):
60 # Avoid being blocked forever
61 if not self
.ping(timeout
):
62 raise EnvironmentError('Agent seems not alive')
63 uid
= random
.randint(0, (1 << 32) - 1)
65 ret
= self
.qga
.sync(id=uid
)
66 if isinstance(ret
, int) and int(ret
) == uid
:
69 def __file_read_all(self
, handle
):
73 ret
= self
.qga
.file_read(handle
=handle
, count
=1024)
74 _data
= base64
.b64decode(ret
['buf-b64'])
80 handle
= self
.qga
.file_open(path
=path
)
82 data
= self
.__file
_read
_all
(handle
)
84 self
.qga
.file_close(handle
=handle
)
88 info
= self
.qga
.info()
91 msgs
.append('version: ' + info
['version'])
92 msgs
.append('supported_commands:')
93 enabled
= [c
['name'] for c
in info
['supported_commands'] if c
['enabled']]
94 msgs
.append('\tenabled: ' + ', '.join(enabled
))
95 disabled
= [c
['name'] for c
in info
['supported_commands'] if not c
['enabled']]
96 msgs
.append('\tdisabled: ' + ', '.join(disabled
))
98 return '\n'.join(msgs
)
100 def __gen_ipv4_netmask(self
, prefixlen
):
101 mask
= int('1' * prefixlen
+ '0' * (32 - prefixlen
), 2)
102 return '.'.join([str(mask
>> 24),
103 str((mask
>> 16) & 0xff),
104 str((mask
>> 8) & 0xff),
108 nifs
= self
.qga
.network_get_interfaces()
112 msgs
.append(nif
['name'] + ':')
113 if 'ip-addresses' in nif
:
114 for ipaddr
in nif
['ip-addresses']:
115 if ipaddr
['ip-address-type'] == 'ipv4':
116 addr
= ipaddr
['ip-address']
117 mask
= self
.__gen
_ipv
4_netmask
(int(ipaddr
['prefix']))
118 msgs
.append("\tinet %s netmask %s" % (addr
, mask
))
119 elif ipaddr
['ip-address-type'] == 'ipv6':
120 addr
= ipaddr
['ip-address']
121 prefix
= ipaddr
['prefix']
122 msgs
.append("\tinet6 %s prefixlen %s" % (addr
, prefix
))
123 if nif
['hardware-address'] != '00:00:00:00:00:00':
124 msgs
.append("\tether " + nif
['hardware-address'])
126 return '\n'.join(msgs
)
128 def ping(self
, timeout
):
129 self
.qga
.settimeout(timeout
)
132 except self
.qga
.timeout
:
136 def fsfreeze(self
, cmd
):
137 if cmd
not in ['status', 'freeze', 'thaw']:
138 raise StandardError('Invalid command: ' + cmd
)
140 return getattr(self
.qga
, 'fsfreeze' + '_' + cmd
)()
142 def fstrim(self
, minimum
=0):
143 return getattr(self
.qga
, 'fstrim')(minimum
=minimum
)
145 def suspend(self
, mode
):
146 if mode
not in ['disk', 'ram', 'hybrid']:
147 raise StandardError('Invalid mode: ' + mode
)
150 getattr(self
.qga
, 'suspend' + '_' + mode
)()
151 # On error exception will raise
152 except self
.qga
.timeout
:
153 # On success command will timed out
156 def shutdown(self
, mode
='powerdown'):
157 if mode
not in ['powerdown', 'halt', 'reboot']:
158 raise StandardError('Invalid mode: ' + mode
)
161 self
.qga
.shutdown(mode
=mode
)
162 except self
.qga
.timeout
:
166 def _cmd_cat(client
, args
):
168 print('Invalid argument')
169 print('Usage: cat <file>')
171 print(client
.read(args
[0]))
174 def _cmd_fsfreeze(client
, args
):
175 usage
= 'Usage: fsfreeze status|freeze|thaw'
177 print('Invalid argument')
180 if args
[0] not in ['status', 'freeze', 'thaw']:
181 print('Invalid command: ' + args
[0])
185 ret
= client
.fsfreeze(cmd
)
188 elif cmd
== 'freeze':
189 print("%d filesystems frozen" % ret
)
191 print("%d filesystems thawed" % ret
)
194 def _cmd_fstrim(client
, args
):
198 minimum
= int(args
[0])
199 print(client
.fstrim(minimum
))
202 def _cmd_ifconfig(client
, args
):
203 print(client
.ifconfig())
206 def _cmd_info(client
, args
):
210 def _cmd_ping(client
, args
):
214 timeout
= float(args
[0])
215 alive
= client
.ping(timeout
)
217 print("Not responded in %s sec" % args
[0])
221 def _cmd_suspend(client
, args
):
222 usage
= 'Usage: suspend disk|ram|hybrid'
224 print('Less argument')
227 if args
[0] not in ['disk', 'ram', 'hybrid']:
228 print('Invalid command: ' + args
[0])
231 client
.suspend(args
[0])
234 def _cmd_shutdown(client
, args
):
236 _cmd_powerdown
= _cmd_shutdown
239 def _cmd_halt(client
, args
):
240 client
.shutdown('halt')
243 def _cmd_reboot(client
, args
):
244 client
.shutdown('reboot')
247 commands
= [m
.replace('_cmd_', '') for m
in dir() if '_cmd_' in m
]
250 def main(address
, cmd
, args
):
251 if not os
.path
.exists(address
):
252 print('%s not found' % address
)
255 if cmd
not in commands
:
256 print('Invalid command: ' + cmd
)
257 print('Available commands: ' + ', '.join(commands
))
261 client
= QemuGuestAgentClient(address
)
262 except QemuGuestAgent
.error
as e
:
266 if e
.errno
== errno
.ECONNREFUSED
:
267 print('Hint: qemu is not running?')
270 if cmd
== 'fsfreeze' and args
[0] == 'freeze':
275 globals()['_cmd_' + cmd
](client
, args
)
278 if __name__
== '__main__':
283 address
= os
.environ
['QGA_CLIENT_ADDRESS'] if 'QGA_CLIENT_ADDRESS' in os
.environ
else None
285 usage
= "%prog [--address=<unix_path>|<ipv4_address>] <command> [args...]\n"
286 usage
+= '<command>: ' + ', '.join(commands
)
287 parser
= optparse
.OptionParser(usage
=usage
)
288 parser
.add_option('--address', action
='store', type='string',
289 default
=address
, help='Specify a ip:port pair or a unix socket path')
290 options
, args
= parser
.parse_args()
292 address
= options
.address
294 parser
.error('address is not specified')
298 parser
.error('Less argument')
301 main(address
, args
[0], args
[1:])