xref: /qemu/scripts/qmp/qemu-ga-client (revision 52f2b896)
1#!/usr/bin/python
2
3# QEMU Guest Agent Client
4#
5# Copyright (C) 2012 Ryota Ozaki <ozaki.ryota@gmail.com>
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#
36# See also: https://wiki.qemu.org/Features/QAPI/GuestAgent
37#
38
39from __future__ import print_function
40import os
41import sys
42import base64
43import random
44
45sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', 'python'))
46from qemu import qmp
47
48
49class 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
56class 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']:
142            raise Exception('Invalid command: ' + cmd)
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']:
151            raise Exception('Invalid mode: ' + mode)
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']:
162            raise Exception('Invalid mode: ' + mode)
163
164        try:
165            self.qga.shutdown(mode=mode)
166        except self.qga.timeout:
167            return
168
169
170def _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
178def _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
198def _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
206def _cmd_ifconfig(client, args):
207    print(client.ifconfig())
208
209
210def _cmd_info(client, args):
211    print(client.info())
212
213
214def _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
225def _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
238def _cmd_shutdown(client, args):
239    client.shutdown()
240_cmd_powerdown = _cmd_shutdown
241
242
243def _cmd_halt(client, args):
244    client.shutdown('halt')
245
246
247def _cmd_reboot(client, args):
248    client.shutdown('reboot')
249
250
251commands = [m.replace('_cmd_', '') for m in dir() if '_cmd_' in m]
252
253
254def 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)
266    except QemuGuestAgent.error as e:
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
274    if cmd == 'fsfreeze' and args[0] == 'freeze':
275        client.sync(60)
276    elif cmd != 'ping':
277        client.sync()
278
279    globals()['_cmd_' + cmd](client, args)
280
281
282if __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:])
306