Revision 22f3946b
/dev/null | ||
---|---|---|
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: http://wiki.qemu.org/Features/QAPI/GuestAgent |
|
37 |
# |
|
38 |
|
|
39 |
import base64 |
|
40 |
import random |
|
41 |
|
|
42 |
import qmp |
|
43 |
|
|
44 |
|
|
45 |
class QemuGuestAgent(qmp.QEMUMonitorProtocol): |
|
46 |
def __getattr__(self, name): |
|
47 |
def wrapper(**kwds): |
|
48 |
return self.command('guest-' + name.replace('_', '-'), **kwds) |
|
49 |
return wrapper |
|
50 |
|
|
51 |
|
|
52 |
class QemuGuestAgentClient: |
|
53 |
error = QemuGuestAgent.error |
|
54 |
|
|
55 |
def __init__(self, address): |
|
56 |
self.qga = QemuGuestAgent(address) |
|
57 |
self.qga.connect(negotiate=False) |
|
58 |
|
|
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) |
|
64 |
while True: |
|
65 |
ret = self.qga.sync(id=uid) |
|
66 |
if isinstance(ret, int) and int(ret) == uid: |
|
67 |
break |
|
68 |
|
|
69 |
def __file_read_all(self, handle): |
|
70 |
eof = False |
|
71 |
data = '' |
|
72 |
while not eof: |
|
73 |
ret = self.qga.file_read(handle=handle, count=1024) |
|
74 |
_data = base64.b64decode(ret['buf-b64']) |
|
75 |
data += _data |
|
76 |
eof = ret['eof'] |
|
77 |
return data |
|
78 |
|
|
79 |
def read(self, path): |
|
80 |
handle = self.qga.file_open(path=path) |
|
81 |
try: |
|
82 |
data = self.__file_read_all(handle) |
|
83 |
finally: |
|
84 |
self.qga.file_close(handle=handle) |
|
85 |
return data |
|
86 |
|
|
87 |
def info(self): |
|
88 |
info = self.qga.info() |
|
89 |
|
|
90 |
msgs = [] |
|
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)) |
|
97 |
|
|
98 |
return '\n'.join(msgs) |
|
99 |
|
|
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), |
|
105 |
str(mask & 0xff)]) |
|
106 |
|
|
107 |
def ifconfig(self): |
|
108 |
nifs = self.qga.network_get_interfaces() |
|
109 |
|
|
110 |
msgs = [] |
|
111 |
for nif in nifs: |
|
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_ipv4_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']) |
|
125 |
|
|
126 |
return '\n'.join(msgs) |
|
127 |
|
|
128 |
def ping(self, timeout): |
|
129 |
self.qga.settimeout(timeout) |
|
130 |
try: |
|
131 |
self.qga.ping() |
|
132 |
except self.qga.timeout: |
|
133 |
return False |
|
134 |
return True |
|
135 |
|
|
136 |
def fsfreeze(self, cmd): |
|
137 |
if cmd not in ['status', 'freeze', 'thaw']: |
|
138 |
raise StandardError('Invalid command: ' + cmd) |
|
139 |
|
|
140 |
return getattr(self.qga, 'fsfreeze' + '_' + cmd)() |
|
141 |
|
|
142 |
def fstrim(self, minimum=0): |
|
143 |
return getattr(self.qga, 'fstrim')(minimum=minimum) |
|
144 |
|
|
145 |
def suspend(self, mode): |
|
146 |
if mode not in ['disk', 'ram', 'hybrid']: |
|
147 |
raise StandardError('Invalid mode: ' + mode) |
|
148 |
|
|
149 |
try: |
|
150 |
getattr(self.qga, 'suspend' + '_' + mode)() |
|
151 |
# On error exception will raise |
|
152 |
except self.qga.timeout: |
|
153 |
# On success command will timed out |
|
154 |
return |
|
155 |
|
|
156 |
def shutdown(self, mode='powerdown'): |
|
157 |
if mode not in ['powerdown', 'halt', 'reboot']: |
|
158 |
raise StandardError('Invalid mode: ' + mode) |
|
159 |
|
|
160 |
try: |
|
161 |
self.qga.shutdown(mode=mode) |
|
162 |
except self.qga.timeout: |
|
163 |
return |
|
164 |
|
|
165 |
|
|
166 |
def _cmd_cat(client, args): |
|
167 |
if len(args) != 1: |
|
168 |
print('Invalid argument') |
|
169 |
print('Usage: cat <file>') |
|
170 |
sys.exit(1) |
|
171 |
print(client.read(args[0])) |
|
172 |
|
|
173 |
|
|
174 |
def _cmd_fsfreeze(client, args): |
|
175 |
usage = 'Usage: fsfreeze status|freeze|thaw' |
|
176 |
if len(args) != 1: |
|
177 |
print('Invalid argument') |
|
178 |
print(usage) |
|
179 |
sys.exit(1) |
|
180 |
if args[0] not in ['status', 'freeze', 'thaw']: |
|
181 |
print('Invalid command: ' + args[0]) |
|
182 |
print(usage) |
|
183 |
sys.exit(1) |
|
184 |
cmd = args[0] |
|
185 |
ret = client.fsfreeze(cmd) |
|
186 |
if cmd == 'status': |
|
187 |
print(ret) |
|
188 |
elif cmd == 'freeze': |
|
189 |
print("%d filesystems frozen" % ret) |
|
190 |
else: |
|
191 |
print("%d filesystems thawed" % ret) |
|
192 |
|
|
193 |
|
|
194 |
def _cmd_fstrim(client, args): |
|
195 |
if len(args) == 0: |
|
196 |
minimum = 0 |
|
197 |
else: |
|
198 |
minimum = int(args[0]) |
|
199 |
print(client.fstrim(minimum)) |
|
200 |
|
|
201 |
|
|
202 |
def _cmd_ifconfig(client, args): |
|
203 |
print(client.ifconfig()) |
|
204 |
|
|
205 |
|
|
206 |
def _cmd_info(client, args): |
|
207 |
print(client.info()) |
|
208 |
|
|
209 |
|
|
210 |
def _cmd_ping(client, args): |
|
211 |
if len(args) == 0: |
|
212 |
timeout = 3 |
|
213 |
else: |
|
214 |
timeout = float(args[0]) |
|
215 |
alive = client.ping(timeout) |
|
216 |
if not alive: |
|
217 |
print("Not responded in %s sec" % args[0]) |
|
218 |
sys.exit(1) |
|
219 |
|
|
220 |
|
|
221 |
def _cmd_suspend(client, args): |
|
222 |
usage = 'Usage: suspend disk|ram|hybrid' |
|
223 |
if len(args) != 1: |
|
224 |
print('Less argument') |
|
225 |
print(usage) |
|
226 |
sys.exit(1) |
|
227 |
if args[0] not in ['disk', 'ram', 'hybrid']: |
|
228 |
print('Invalid command: ' + args[0]) |
|
229 |
print(usage) |
|
230 |
sys.exit(1) |
|
231 |
client.suspend(args[0]) |
|
232 |
|
|
233 |
|
|
234 |
def _cmd_shutdown(client, args): |
|
235 |
client.shutdown() |
|
236 |
_cmd_powerdown = _cmd_shutdown |
|
237 |
|
|
238 |
|
|
239 |
def _cmd_halt(client, args): |
|
240 |
client.shutdown('halt') |
|
241 |
|
|
242 |
|
|
243 |
def _cmd_reboot(client, args): |
|
244 |
client.shutdown('reboot') |
|
245 |
|
|
246 |
|
|
247 |
commands = [m.replace('_cmd_', '') for m in dir() if '_cmd_' in m] |
|
248 |
|
|
249 |
|
|
250 |
def main(address, cmd, args): |
|
251 |
if not os.path.exists(address): |
|
252 |
print('%s not found' % address) |
|
253 |
sys.exit(1) |
|
254 |
|
|
255 |
if cmd not in commands: |
|
256 |
print('Invalid command: ' + cmd) |
|
257 |
print('Available commands: ' + ', '.join(commands)) |
|
258 |
sys.exit(1) |
|
259 |
|
|
260 |
try: |
|
261 |
client = QemuGuestAgentClient(address) |
|
262 |
except QemuGuestAgent.error, e: |
|
263 |
import errno |
|
264 |
|
|
265 |
print(e) |
|
266 |
if e.errno == errno.ECONNREFUSED: |
|
267 |
print('Hint: qemu is not running?') |
|
268 |
sys.exit(1) |
|
269 |
|
|
270 |
if cmd == 'fsfreeze' and args[0] == 'freeze': |
|
271 |
client.sync(60) |
|
272 |
elif cmd != 'ping': |
|
273 |
client.sync() |
|
274 |
|
|
275 |
globals()['_cmd_' + cmd](client, args) |
|
276 |
|
|
277 |
|
|
278 |
if __name__ == '__main__': |
|
279 |
import sys |
|
280 |
import os |
|
281 |
import optparse |
|
282 |
|
|
283 |
address = os.environ['QGA_CLIENT_ADDRESS'] if 'QGA_CLIENT_ADDRESS' in os.environ else None |
|
284 |
|
|
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() |
|
291 |
|
|
292 |
address = options.address |
|
293 |
if address is None: |
|
294 |
parser.error('address is not specified') |
|
295 |
sys.exit(1) |
|
296 |
|
|
297 |
if len(args) == 0: |
|
298 |
parser.error('Less argument') |
|
299 |
sys.exit(1) |
|
300 |
|
|
301 |
main(address, args[0], args[1:]) |
/dev/null | ||
---|---|---|
1 |
#!/usr/bin/python |
|
2 |
# |
|
3 |
# QMP command line tool |
|
4 |
# |
|
5 |
# Copyright IBM, Corp. 2011 |
|
6 |
# |
|
7 |
# Authors: |
|
8 |
# Anthony Liguori <aliguori@us.ibm.com> |
|
9 |
# |
|
10 |
# This work is licensed under the terms of the GNU GPLv2 or later. |
|
11 |
# See the COPYING file in the top-level directory. |
|
12 |
|
|
13 |
import sys, os |
|
14 |
from qmp import QEMUMonitorProtocol |
|
15 |
|
|
16 |
def print_response(rsp, prefix=[]): |
|
17 |
if type(rsp) == list: |
|
18 |
i = 0 |
|
19 |
for item in rsp: |
|
20 |
if prefix == []: |
|
21 |
prefix = ['item'] |
|
22 |
print_response(item, prefix[:-1] + ['%s[%d]' % (prefix[-1], i)]) |
|
23 |
i += 1 |
|
24 |
elif type(rsp) == dict: |
|
25 |
for key in rsp.keys(): |
|
26 |
print_response(rsp[key], prefix + [key]) |
|
27 |
else: |
|
28 |
if len(prefix): |
|
29 |
print '%s: %s' % ('.'.join(prefix), rsp) |
|
30 |
else: |
|
31 |
print '%s' % (rsp) |
|
32 |
|
|
33 |
def main(args): |
|
34 |
path = None |
|
35 |
|
|
36 |
# Use QMP_PATH if it's set |
|
37 |
if os.environ.has_key('QMP_PATH'): |
|
38 |
path = os.environ['QMP_PATH'] |
|
39 |
|
|
40 |
while len(args): |
|
41 |
arg = args[0] |
|
42 |
|
|
43 |
if arg.startswith('--'): |
|
44 |
arg = arg[2:] |
|
45 |
if arg.find('=') == -1: |
|
46 |
value = True |
|
47 |
else: |
|
48 |
arg, value = arg.split('=', 1) |
|
49 |
|
|
50 |
if arg in ['path']: |
|
51 |
if type(value) == str: |
|
52 |
path = value |
|
53 |
elif arg in ['help']: |
|
54 |
os.execlp('man', 'man', 'qmp') |
|
55 |
else: |
|
56 |
print 'Unknown argument "%s"' % arg |
|
57 |
|
|
58 |
args = args[1:] |
|
59 |
else: |
|
60 |
break |
|
61 |
|
|
62 |
if not path: |
|
63 |
print "QMP path isn't set, use --path=qmp-monitor-address or set QMP_PATH" |
|
64 |
return 1 |
|
65 |
|
|
66 |
if len(args): |
|
67 |
command, args = args[0], args[1:] |
|
68 |
else: |
|
69 |
print 'No command found' |
|
70 |
print 'Usage: "qmp [--path=qmp-monitor-address] qmp-cmd arguments"' |
|
71 |
return 1 |
|
72 |
|
|
73 |
if command in ['help']: |
|
74 |
os.execlp('man', 'man', 'qmp') |
|
75 |
|
|
76 |
srv = QEMUMonitorProtocol(path) |
|
77 |
srv.connect() |
|
78 |
|
|
79 |
def do_command(srv, cmd, **kwds): |
|
80 |
rsp = srv.cmd(cmd, kwds) |
|
81 |
if rsp.has_key('error'): |
|
82 |
raise Exception(rsp['error']['desc']) |
|
83 |
return rsp['return'] |
|
84 |
|
|
85 |
commands = map(lambda x: x['name'], do_command(srv, 'query-commands')) |
|
86 |
|
|
87 |
srv.close() |
|
88 |
|
|
89 |
if command not in commands: |
|
90 |
fullcmd = 'qmp-%s' % command |
|
91 |
try: |
|
92 |
os.environ['QMP_PATH'] = path |
|
93 |
os.execvp(fullcmd, [fullcmd] + args) |
|
94 |
except OSError, (errno, msg): |
|
95 |
if errno == 2: |
|
96 |
print 'Command "%s" not found.' % (fullcmd) |
|
97 |
return 1 |
|
98 |
raise |
|
99 |
return 0 |
|
100 |
|
|
101 |
srv = QEMUMonitorProtocol(path) |
|
102 |
srv.connect() |
|
103 |
|
|
104 |
arguments = {} |
|
105 |
for arg in args: |
|
106 |
if not arg.startswith('--'): |
|
107 |
print 'Unknown argument "%s"' % arg |
|
108 |
return 1 |
|
109 |
|
|
110 |
arg = arg[2:] |
|
111 |
if arg.find('=') == -1: |
|
112 |
value = True |
|
113 |
else: |
|
114 |
arg, value = arg.split('=', 1) |
|
115 |
|
|
116 |
if arg in ['help']: |
|
117 |
os.execlp('man', 'man', 'qmp-%s' % command) |
|
118 |
return 1 |
|
119 |
|
|
120 |
arguments[arg] = value |
|
121 |
|
|
122 |
rsp = do_command(srv, command, **arguments) |
|
123 |
print_response(rsp) |
|
124 |
|
|
125 |
if __name__ == '__main__': |
|
126 |
sys.exit(main(sys.argv[1:])) |
/dev/null | ||
---|---|---|
1 |
#!/usr/bin/python |
|
2 |
# |
|
3 |
# Low-level QEMU shell on top of QMP. |
|
4 |
# |
|
5 |
# Copyright (C) 2009, 2010 Red Hat Inc. |
|
6 |
# |
|
7 |
# Authors: |
|
8 |
# Luiz Capitulino <lcapitulino@redhat.com> |
|
9 |
# |
|
10 |
# This work is licensed under the terms of the GNU GPL, version 2. See |
|
11 |
# the COPYING file in the top-level directory. |
|
12 |
# |
|
13 |
# Usage: |
|
14 |
# |
|
15 |
# Start QEMU with: |
|
16 |
# |
|
17 |
# # qemu [...] -qmp unix:./qmp-sock,server |
|
18 |
# |
|
19 |
# Run the shell: |
|
20 |
# |
|
21 |
# $ qmp-shell ./qmp-sock |
|
22 |
# |
|
23 |
# Commands have the following format: |
|
24 |
# |
|
25 |
# < command-name > [ arg-name1=arg1 ] ... [ arg-nameN=argN ] |
|
26 |
# |
|
27 |
# For example: |
|
28 |
# |
|
29 |
# (QEMU) device_add driver=e1000 id=net1 |
|
30 |
# {u'return': {}} |
|
31 |
# (QEMU) |
|
32 |
|
|
33 |
import qmp |
|
34 |
import readline |
|
35 |
import sys |
|
36 |
import pprint |
|
37 |
|
|
38 |
class QMPCompleter(list): |
|
39 |
def complete(self, text, state): |
|
40 |
for cmd in self: |
|
41 |
if cmd.startswith(text): |
|
42 |
if not state: |
|
43 |
return cmd |
|
44 |
else: |
|
45 |
state -= 1 |
|
46 |
|
|
47 |
class QMPShellError(Exception): |
|
48 |
pass |
|
49 |
|
|
50 |
class QMPShellBadPort(QMPShellError): |
|
51 |
pass |
|
52 |
|
|
53 |
# TODO: QMPShell's interface is a bit ugly (eg. _fill_completion() and |
|
54 |
# _execute_cmd()). Let's design a better one. |
|
55 |
class QMPShell(qmp.QEMUMonitorProtocol): |
|
56 |
def __init__(self, address, pp=None): |
|
57 |
qmp.QEMUMonitorProtocol.__init__(self, self.__get_address(address)) |
|
58 |
self._greeting = None |
|
59 |
self._completer = None |
|
60 |
self._pp = pp |
|
61 |
|
|
62 |
def __get_address(self, arg): |
|
63 |
""" |
|
64 |
Figure out if the argument is in the port:host form, if it's not it's |
|
65 |
probably a file path. |
|
66 |
""" |
|
67 |
addr = arg.split(':') |
|
68 |
if len(addr) == 2: |
|
69 |
try: |
|
70 |
port = int(addr[1]) |
|
71 |
except ValueError: |
|
72 |
raise QMPShellBadPort |
|
73 |
return ( addr[0], port ) |
|
74 |
# socket path |
|
75 |
return arg |
|
76 |
|
|
77 |
def _fill_completion(self): |
|
78 |
for cmd in self.cmd('query-commands')['return']: |
|
79 |
self._completer.append(cmd['name']) |
|
80 |
|
|
81 |
def __completer_setup(self): |
|
82 |
self._completer = QMPCompleter() |
|
83 |
self._fill_completion() |
|
84 |
readline.set_completer(self._completer.complete) |
|
85 |
readline.parse_and_bind("tab: complete") |
|
86 |
# XXX: default delimiters conflict with some command names (eg. query-), |
|
87 |
# clearing everything as it doesn't seem to matter |
|
88 |
readline.set_completer_delims('') |
|
89 |
|
|
90 |
def __build_cmd(self, cmdline): |
|
91 |
""" |
|
92 |
Build a QMP input object from a user provided command-line in the |
|
93 |
following format: |
|
94 |
|
|
95 |
< command-name > [ arg-name1=arg1 ] ... [ arg-nameN=argN ] |
|
96 |
""" |
|
97 |
cmdargs = cmdline.split() |
|
98 |
qmpcmd = { 'execute': cmdargs[0], 'arguments': {} } |
|
99 |
for arg in cmdargs[1:]: |
|
100 |
opt = arg.split('=') |
|
101 |
try: |
|
102 |
if(len(opt) > 2): |
|
103 |
opt[1] = '='.join(opt[1:]) |
|
104 |
value = int(opt[1]) |
|
105 |
except ValueError: |
|
106 |
if opt[1] == 'true': |
|
107 |
value = True |
|
108 |
elif opt[1] == 'false': |
|
109 |
value = False |
|
110 |
else: |
|
111 |
value = opt[1] |
|
112 |
qmpcmd['arguments'][opt[0]] = value |
|
113 |
return qmpcmd |
|
114 |
|
|
115 |
def _execute_cmd(self, cmdline): |
|
116 |
try: |
|
117 |
qmpcmd = self.__build_cmd(cmdline) |
|
118 |
except: |
|
119 |
print 'command format: <command-name> ', |
|
120 |
print '[arg-name1=arg1] ... [arg-nameN=argN]' |
|
121 |
return True |
|
122 |
resp = self.cmd_obj(qmpcmd) |
|
123 |
if resp is None: |
|
124 |
print 'Disconnected' |
|
125 |
return False |
|
126 |
|
|
127 |
if self._pp is not None: |
|
128 |
self._pp.pprint(resp) |
|
129 |
else: |
|
130 |
print resp |
|
131 |
return True |
|
132 |
|
|
133 |
def connect(self): |
|
134 |
self._greeting = qmp.QEMUMonitorProtocol.connect(self) |
|
135 |
self.__completer_setup() |
|
136 |
|
|
137 |
def show_banner(self, msg='Welcome to the QMP low-level shell!'): |
|
138 |
print msg |
|
139 |
version = self._greeting['QMP']['version']['qemu'] |
|
140 |
print 'Connected to QEMU %d.%d.%d\n' % (version['major'],version['minor'],version['micro']) |
|
141 |
|
|
142 |
def read_exec_command(self, prompt): |
|
143 |
""" |
|
144 |
Read and execute a command. |
|
145 |
|
|
146 |
@return True if execution was ok, return False if disconnected. |
|
147 |
""" |
|
148 |
try: |
|
149 |
cmdline = raw_input(prompt) |
|
150 |
except EOFError: |
|
151 |
|
|
152 |
return False |
|
153 |
if cmdline == '': |
|
154 |
for ev in self.get_events(): |
|
155 |
print ev |
|
156 |
self.clear_events() |
|
157 |
return True |
|
158 |
else: |
|
159 |
return self._execute_cmd(cmdline) |
|
160 |
|
|
161 |
class HMPShell(QMPShell): |
|
162 |
def __init__(self, address): |
|
163 |
QMPShell.__init__(self, address) |
|
164 |
self.__cpu_index = 0 |
|
165 |
|
|
166 |
def __cmd_completion(self): |
|
167 |
for cmd in self.__cmd_passthrough('help')['return'].split('\r\n'): |
|
168 |
if cmd and cmd[0] != '[' and cmd[0] != '\t': |
|
169 |
name = cmd.split()[0] # drop help text |
|
170 |
if name == 'info': |
|
171 |
continue |
|
172 |
if name.find('|') != -1: |
|
173 |
# Command in the form 'foobar|f' or 'f|foobar', take the |
|
174 |
# full name |
|
175 |
opt = name.split('|') |
|
176 |
if len(opt[0]) == 1: |
|
177 |
name = opt[1] |
|
178 |
else: |
|
179 |
name = opt[0] |
|
180 |
self._completer.append(name) |
|
181 |
self._completer.append('help ' + name) # help completion |
|
182 |
|
|
183 |
def __info_completion(self): |
|
184 |
for cmd in self.__cmd_passthrough('info')['return'].split('\r\n'): |
|
185 |
if cmd: |
|
186 |
self._completer.append('info ' + cmd.split()[1]) |
|
187 |
|
|
188 |
def __other_completion(self): |
|
189 |
# special cases |
|
190 |
self._completer.append('help info') |
|
191 |
|
|
192 |
def _fill_completion(self): |
|
193 |
self.__cmd_completion() |
|
194 |
self.__info_completion() |
|
195 |
self.__other_completion() |
|
196 |
|
|
197 |
def __cmd_passthrough(self, cmdline, cpu_index = 0): |
|
198 |
return self.cmd_obj({ 'execute': 'human-monitor-command', 'arguments': |
|
199 |
{ 'command-line': cmdline, |
|
200 |
'cpu-index': cpu_index } }) |
|
201 |
|
|
202 |
def _execute_cmd(self, cmdline): |
|
203 |
if cmdline.split()[0] == "cpu": |
|
204 |
# trap the cpu command, it requires special setting |
|
205 |
try: |
|
206 |
idx = int(cmdline.split()[1]) |
|
207 |
if not 'return' in self.__cmd_passthrough('info version', idx): |
|
208 |
print 'bad CPU index' |
|
209 |
return True |
|
210 |
self.__cpu_index = idx |
|
211 |
except ValueError: |
|
212 |
print 'cpu command takes an integer argument' |
|
213 |
return True |
|
214 |
resp = self.__cmd_passthrough(cmdline, self.__cpu_index) |
|
215 |
if resp is None: |
|
216 |
print 'Disconnected' |
|
217 |
return False |
|
218 |
assert 'return' in resp or 'error' in resp |
|
219 |
if 'return' in resp: |
|
220 |
# Success |
|
221 |
if len(resp['return']) > 0: |
|
222 |
print resp['return'], |
|
223 |
else: |
|
224 |
# Error |
|
225 |
print '%s: %s' % (resp['error']['class'], resp['error']['desc']) |
|
226 |
return True |
|
227 |
|
|
228 |
def show_banner(self): |
|
229 |
QMPShell.show_banner(self, msg='Welcome to the HMP shell!') |
|
230 |
|
|
231 |
def die(msg): |
|
232 |
sys.stderr.write('ERROR: %s\n' % msg) |
|
233 |
sys.exit(1) |
|
234 |
|
|
235 |
def fail_cmdline(option=None): |
|
236 |
if option: |
|
237 |
sys.stderr.write('ERROR: bad command-line option \'%s\'\n' % option) |
|
238 |
sys.stderr.write('qemu-shell [ -p ] [ -H ] < UNIX socket path> | < TCP address:port >\n') |
|
239 |
sys.exit(1) |
|
240 |
|
|
241 |
def main(): |
|
242 |
addr = '' |
|
243 |
qemu = None |
|
244 |
hmp = False |
|
245 |
pp = None |
|
246 |
|
|
247 |
try: |
|
248 |
for arg in sys.argv[1:]: |
|
249 |
if arg == "-H": |
|
250 |
if qemu is not None: |
|
251 |
fail_cmdline(arg) |
|
252 |
hmp = True |
|
253 |
elif arg == "-p": |
|
254 |
if pp is not None: |
|
255 |
fail_cmdline(arg) |
|
256 |
pp = pprint.PrettyPrinter(indent=4) |
|
257 |
else: |
|
258 |
if qemu is not None: |
|
259 |
fail_cmdline(arg) |
|
260 |
if hmp: |
|
261 |
qemu = HMPShell(arg) |
|
262 |
else: |
|
263 |
qemu = QMPShell(arg, pp) |
|
264 |
addr = arg |
|
265 |
|
|
266 |
if qemu is None: |
|
267 |
fail_cmdline() |
|
268 |
except QMPShellBadPort: |
|
269 |
die('bad port number in command-line') |
|
270 |
|
|
271 |
try: |
|
272 |
qemu.connect() |
|
273 |
except qmp.QMPConnectError: |
|
274 |
die('Didn\'t get QMP greeting message') |
|
275 |
except qmp.QMPCapabilitiesError: |
|
276 |
die('Could not negotiate capabilities') |
|
277 |
except qemu.error: |
|
278 |
die('Could not connect to %s' % addr) |
|
279 |
|
|
280 |
qemu.show_banner() |
|
281 |
while qemu.read_exec_command('(QEMU) '): |
|
282 |
pass |
|
283 |
qemu.close() |
|
284 |
|
|
285 |
if __name__ == '__main__': |
|
286 |
main() |
/dev/null | ||
---|---|---|
1 |
# QEMU Monitor Protocol Python class |
|
2 |
# |
|
3 |
# Copyright (C) 2009, 2010 Red Hat Inc. |
|
4 |
# |
|
5 |
# Authors: |
|
6 |
# Luiz Capitulino <lcapitulino@redhat.com> |
|
7 |
# |
|
8 |
# This work is licensed under the terms of the GNU GPL, version 2. See |
|
9 |
# the COPYING file in the top-level directory. |
|
10 |
|
|
11 |
import json |
|
12 |
import errno |
|
13 |
import socket |
|
14 |
|
|
15 |
class QMPError(Exception): |
|
16 |
pass |
|
17 |
|
|
18 |
class QMPConnectError(QMPError): |
|
19 |
pass |
|
20 |
|
|
21 |
class QMPCapabilitiesError(QMPError): |
|
22 |
pass |
|
23 |
|
|
24 |
class QEMUMonitorProtocol: |
|
25 |
def __init__(self, address, server=False): |
|
26 |
""" |
|
27 |
Create a QEMUMonitorProtocol class. |
|
28 |
|
|
29 |
@param address: QEMU address, can be either a unix socket path (string) |
|
30 |
or a tuple in the form ( address, port ) for a TCP |
|
31 |
connection |
|
32 |
@param server: server mode listens on the socket (bool) |
|
33 |
@raise socket.error on socket connection errors |
|
34 |
@note No connection is established, this is done by the connect() or |
|
35 |
accept() methods |
|
36 |
""" |
|
37 |
self.__events = [] |
|
38 |
self.__address = address |
|
39 |
self.__sock = self.__get_sock() |
|
40 |
if server: |
|
41 |
self.__sock.bind(self.__address) |
|
42 |
self.__sock.listen(1) |
|
43 |
|
|
44 |
def __get_sock(self): |
|
45 |
if isinstance(self.__address, tuple): |
|
46 |
family = socket.AF_INET |
|
47 |
else: |
|
48 |
family = socket.AF_UNIX |
|
49 |
return socket.socket(family, socket.SOCK_STREAM) |
|
50 |
|
|
51 |
def __negotiate_capabilities(self): |
|
52 |
greeting = self.__json_read() |
|
53 |
if greeting is None or not greeting.has_key('QMP'): |
|
54 |
raise QMPConnectError |
|
55 |
# Greeting seems ok, negotiate capabilities |
|
56 |
resp = self.cmd('qmp_capabilities') |
|
57 |
if "return" in resp: |
|
58 |
return greeting |
|
59 |
raise QMPCapabilitiesError |
|
60 |
|
|
61 |
def __json_read(self, only_event=False): |
|
62 |
while True: |
|
63 |
data = self.__sockfile.readline() |
|
64 |
if not data: |
|
65 |
return |
|
66 |
resp = json.loads(data) |
|
67 |
if 'event' in resp: |
|
68 |
self.__events.append(resp) |
|
69 |
if not only_event: |
|
70 |
continue |
|
71 |
return resp |
|
72 |
|
|
73 |
error = socket.error |
|
74 |
|
|
75 |
def connect(self, negotiate=True): |
|
76 |
""" |
|
77 |
Connect to the QMP Monitor and perform capabilities negotiation. |
|
78 |
|
|
79 |
@return QMP greeting dict |
|
80 |
@raise socket.error on socket connection errors |
|
81 |
@raise QMPConnectError if the greeting is not received |
|
82 |
@raise QMPCapabilitiesError if fails to negotiate capabilities |
|
83 |
""" |
|
84 |
self.__sock.connect(self.__address) |
|
85 |
self.__sockfile = self.__sock.makefile() |
|
86 |
if negotiate: |
|
87 |
return self.__negotiate_capabilities() |
|
88 |
|
|
89 |
def accept(self): |
|
90 |
""" |
|
91 |
Await connection from QMP Monitor and perform capabilities negotiation. |
|
92 |
|
|
93 |
@return QMP greeting dict |
|
94 |
@raise socket.error on socket connection errors |
|
95 |
@raise QMPConnectError if the greeting is not received |
|
96 |
@raise QMPCapabilitiesError if fails to negotiate capabilities |
|
97 |
""" |
|
98 |
self.__sock, _ = self.__sock.accept() |
|
99 |
self.__sockfile = self.__sock.makefile() |
|
100 |
return self.__negotiate_capabilities() |
|
101 |
|
|
102 |
def cmd_obj(self, qmp_cmd): |
|
103 |
""" |
|
104 |
Send a QMP command to the QMP Monitor. |
|
105 |
|
|
106 |
@param qmp_cmd: QMP command to be sent as a Python dict |
|
107 |
@return QMP response as a Python dict or None if the connection has |
|
108 |
been closed |
|
109 |
""" |
|
110 |
try: |
|
111 |
self.__sock.sendall(json.dumps(qmp_cmd)) |
|
112 |
except socket.error, err: |
|
113 |
if err[0] == errno.EPIPE: |
|
114 |
return |
|
115 |
raise socket.error(err) |
|
116 |
return self.__json_read() |
|
117 |
|
|
118 |
def cmd(self, name, args=None, id=None): |
|
119 |
""" |
|
120 |
Build a QMP command and send it to the QMP Monitor. |
|
121 |
|
|
122 |
@param name: command name (string) |
|
123 |
@param args: command arguments (dict) |
|
124 |
@param id: command id (dict, list, string or int) |
|
125 |
""" |
|
126 |
qmp_cmd = { 'execute': name } |
|
127 |
if args: |
|
128 |
qmp_cmd['arguments'] = args |
|
129 |
if id: |
|
130 |
qmp_cmd['id'] = id |
|
131 |
return self.cmd_obj(qmp_cmd) |
|
132 |
|
|
133 |
def command(self, cmd, **kwds): |
|
134 |
ret = self.cmd(cmd, kwds) |
|
135 |
if ret.has_key('error'): |
|
136 |
raise Exception(ret['error']['desc']) |
|
137 |
return ret['return'] |
|
138 |
|
|
139 |
def pull_event(self, wait=False): |
|
140 |
""" |
|
141 |
Get and delete the first available QMP event. |
|
142 |
|
|
143 |
@param wait: block until an event is available (bool) |
|
144 |
""" |
|
145 |
self.__sock.setblocking(0) |
|
146 |
try: |
|
147 |
self.__json_read() |
|
148 |
except socket.error, err: |
|
149 |
if err[0] == errno.EAGAIN: |
|
150 |
# No data available |
|
151 |
pass |
|
152 |
self.__sock.setblocking(1) |
|
153 |
if not self.__events and wait: |
|
154 |
self.__json_read(only_event=True) |
|
155 |
event = self.__events[0] |
|
156 |
del self.__events[0] |
|
157 |
return event |
|
158 |
|
|
159 |
def get_events(self, wait=False): |
|
160 |
""" |
|
161 |
Get a list of available QMP events. |
|
162 |
|
|
163 |
@param wait: block until an event is available (bool) |
|
164 |
""" |
|
165 |
self.__sock.setblocking(0) |
|
166 |
try: |
|
167 |
self.__json_read() |
|
168 |
except socket.error, err: |
|
169 |
if err[0] == errno.EAGAIN: |
|
170 |
# No data available |
|
171 |
pass |
|
172 |
self.__sock.setblocking(1) |
|
173 |
if not self.__events and wait: |
|
174 |
self.__json_read(only_event=True) |
|
175 |
return self.__events |
|
176 |
|
|
177 |
def clear_events(self): |
|
178 |
""" |
|
179 |
Clear current list of pending events. |
|
180 |
""" |
|
181 |
self.__events = [] |
|
182 |
|
|
183 |
def close(self): |
|
184 |
self.__sock.close() |
|
185 |
self.__sockfile.close() |
|
186 |
|
|
187 |
timeout = socket.timeout |
|
188 |
|
|
189 |
def settimeout(self, timeout): |
|
190 |
self.__sock.settimeout(timeout) |
|
191 |
|
|
192 |
def get_sock_fd(self): |
|
193 |
return self.__sock.fileno() |
|
194 |
|
|
195 |
def is_scm_available(self): |
|
196 |
return self.__sock.family == socket.AF_UNIX |
/dev/null | ||
---|---|---|
1 |
#!/usr/bin/python |
|
2 |
## |
|
3 |
# QEMU Object Model test tools |
|
4 |
# |
|
5 |
# Copyright IBM, Corp. 2012 |
|
6 |
# |
|
7 |
# Authors: |
|
8 |
# Anthony Liguori <aliguori@us.ibm.com> |
|
9 |
# |
|
10 |
# This work is licensed under the terms of the GNU GPL, version 2 or later. See |
|
11 |
# the COPYING file in the top-level directory. |
|
12 |
## |
|
13 |
|
|
14 |
import fuse, stat |
|
15 |
from fuse import Fuse |
|
16 |
import os, posix |
|
17 |
from errno import * |
|
18 |
from qmp import QEMUMonitorProtocol |
|
19 |
|
|
20 |
fuse.fuse_python_api = (0, 2) |
|
21 |
|
|
22 |
class QOMFS(Fuse): |
|
23 |
def __init__(self, qmp, *args, **kwds): |
|
24 |
Fuse.__init__(self, *args, **kwds) |
|
25 |
self.qmp = qmp |
|
26 |
self.qmp.connect() |
|
27 |
self.ino_map = {} |
|
28 |
self.ino_count = 1 |
|
29 |
|
|
30 |
def get_ino(self, path): |
|
31 |
if self.ino_map.has_key(path): |
|
32 |
return self.ino_map[path] |
|
33 |
self.ino_map[path] = self.ino_count |
|
34 |
self.ino_count += 1 |
|
35 |
return self.ino_map[path] |
|
36 |
|
|
37 |
def is_object(self, path): |
|
38 |
try: |
|
39 |
items = self.qmp.command('qom-list', path=path) |
|
40 |
return True |
|
41 |
except: |
|
42 |
return False |
|
43 |
|
|
44 |
def is_property(self, path): |
|
45 |
try: |
|
46 |
path, prop = path.rsplit('/', 1) |
|
47 |
for item in self.qmp.command('qom-list', path=path): |
|
48 |
if item['name'] == prop: |
|
49 |
return True |
|
50 |
return False |
|
51 |
except: |
|
52 |
return False |
|
53 |
|
|
54 |
def is_link(self, path): |
|
55 |
try: |
|
56 |
path, prop = path.rsplit('/', 1) |
|
57 |
for item in self.qmp.command('qom-list', path=path): |
|
58 |
if item['name'] == prop: |
|
59 |
if item['type'].startswith('link<'): |
|
60 |
return True |
|
61 |
return False |
|
62 |
return False |
|
63 |
except: |
|
64 |
return False |
|
65 |
|
|
66 |
def read(self, path, length, offset): |
|
67 |
if not self.is_property(path): |
|
68 |
return -ENOENT |
|
69 |
|
|
70 |
path, prop = path.rsplit('/', 1) |
|
71 |
try: |
|
72 |
data = str(self.qmp.command('qom-get', path=path, property=prop)) |
|
73 |
data += '\n' # make values shell friendly |
|
74 |
except: |
|
75 |
return -EPERM |
|
76 |
|
|
77 |
if offset > len(data): |
|
78 |
return '' |
|
79 |
|
|
80 |
return str(data[offset:][:length]) |
|
81 |
|
|
82 |
def readlink(self, path): |
|
83 |
if not self.is_link(path): |
|
84 |
return False |
|
85 |
path, prop = path.rsplit('/', 1) |
|
86 |
prefix = '/'.join(['..'] * (len(path.split('/')) - 1)) |
|
87 |
return prefix + str(self.qmp.command('qom-get', path=path, |
|
88 |
property=prop)) |
|
89 |
|
|
90 |
def getattr(self, path): |
|
91 |
if self.is_link(path): |
|
92 |
value = posix.stat_result((0755 | stat.S_IFLNK, |
|
93 |
self.get_ino(path), |
|
94 |
0, |
|
95 |
2, |
|
96 |
1000, |
|
97 |
1000, |
|
98 |
4096, |
|
99 |
0, |
|
100 |
0, |
|
101 |
0)) |
|
102 |
elif self.is_object(path): |
|
103 |
value = posix.stat_result((0755 | stat.S_IFDIR, |
|
104 |
self.get_ino(path), |
|
105 |
0, |
|
106 |
2, |
|
107 |
1000, |
|
108 |
1000, |
|
109 |
4096, |
|
110 |
0, |
|
111 |
0, |
|
112 |
0)) |
|
113 |
elif self.is_property(path): |
|
114 |
value = posix.stat_result((0644 | stat.S_IFREG, |
|
115 |
self.get_ino(path), |
|
116 |
0, |
|
117 |
1, |
|
118 |
1000, |
|
119 |
1000, |
|
120 |
4096, |
|
121 |
0, |
|
122 |
0, |
|
123 |
0)) |
|
124 |
else: |
|
125 |
value = -ENOENT |
|
126 |
return value |
|
127 |
|
|
128 |
def readdir(self, path, offset): |
|
129 |
yield fuse.Direntry('.') |
|
130 |
yield fuse.Direntry('..') |
|
131 |
for item in self.qmp.command('qom-list', path=path): |
|
132 |
yield fuse.Direntry(str(item['name'])) |
|
133 |
|
|
134 |
if __name__ == '__main__': |
|
135 |
import sys, os |
|
136 |
|
|
137 |
fs = QOMFS(QEMUMonitorProtocol(os.environ['QMP_SOCKET'])) |
|
138 |
fs.main(sys.argv) |
/dev/null | ||
---|---|---|
1 |
#!/usr/bin/python |
|
2 |
## |
|
3 |
# QEMU Object Model test tools |
|
4 |
# |
|
5 |
# Copyright IBM, Corp. 2011 |
|
6 |
# |
|
7 |
# Authors: |
|
8 |
# Anthony Liguori <aliguori@us.ibm.com> |
|
9 |
# |
|
10 |
# This work is licensed under the terms of the GNU GPL, version 2 or later. See |
|
11 |
# the COPYING file in the top-level directory. |
|
12 |
## |
|
13 |
|
|
14 |
import sys |
|
15 |
import os |
|
16 |
from qmp import QEMUMonitorProtocol |
|
17 |
|
|
18 |
cmd, args = sys.argv[0], sys.argv[1:] |
|
19 |
socket_path = None |
|
20 |
path = None |
|
21 |
prop = None |
|
22 |
|
|
23 |
def usage(): |
|
24 |
return '''environment variables: |
|
25 |
QMP_SOCKET=<path | addr:port> |
|
26 |
usage: |
|
27 |
%s [-h] [-s <QMP socket path | addr:port>] <path>.<property> |
|
28 |
''' % cmd |
|
29 |
|
|
30 |
def usage_error(error_msg = "unspecified error"): |
|
31 |
sys.stderr.write('%s\nERROR: %s\n' % (usage(), error_msg)) |
|
32 |
exit(1) |
|
33 |
|
|
34 |
if len(args) > 0: |
|
35 |
if args[0] == "-h": |
|
36 |
print usage() |
|
37 |
exit(0); |
|
38 |
elif args[0] == "-s": |
|
39 |
try: |
|
40 |
socket_path = args[1] |
|
41 |
except: |
|
42 |
usage_error("missing argument: QMP socket path or address"); |
|
43 |
args = args[2:] |
|
44 |
|
|
45 |
if not socket_path: |
|
46 |
if os.environ.has_key('QMP_SOCKET'): |
|
47 |
socket_path = os.environ['QMP_SOCKET'] |
|
48 |
else: |
|
49 |
usage_error("no QMP socket path or address given"); |
|
50 |
|
|
51 |
if len(args) > 0: |
|
52 |
try: |
|
53 |
path, prop = args[0].rsplit('.', 1) |
|
54 |
except: |
|
55 |
usage_error("invalid format for path/property/value") |
|
56 |
else: |
|
57 |
usage_error("not enough arguments") |
|
58 |
|
|
59 |
srv = QEMUMonitorProtocol(socket_path) |
|
60 |
srv.connect() |
|
61 |
|
|
62 |
rsp = srv.command('qom-get', path=path, property=prop) |
|
63 |
if type(rsp) == dict: |
|
64 |
for i in rsp.keys(): |
|
65 |
print '%s: %s' % (i, rsp[i]) |
|
66 |
else: |
|
67 |
print rsp |
/dev/null | ||
---|---|---|
1 |
#!/usr/bin/python |
|
2 |
## |
|
3 |
# QEMU Object Model test tools |
|
4 |
# |
|
5 |
# Copyright IBM, Corp. 2011 |
|
6 |
# |
|
7 |
# Authors: |
|
8 |
# Anthony Liguori <aliguori@us.ibm.com> |
|
9 |
# |
|
10 |
# This work is licensed under the terms of the GNU GPL, version 2 or later. See |
|
11 |
# the COPYING file in the top-level directory. |
|
12 |
## |
|
13 |
|
|
14 |
import sys |
|
15 |
import os |
|
16 |
from qmp import QEMUMonitorProtocol |
|
17 |
|
|
18 |
cmd, args = sys.argv[0], sys.argv[1:] |
|
19 |
socket_path = None |
|
20 |
path = None |
|
21 |
prop = None |
|
22 |
|
|
23 |
def usage(): |
|
24 |
return '''environment variables: |
|
25 |
QMP_SOCKET=<path | addr:port> |
|
26 |
usage: |
|
27 |
%s [-h] [-s <QMP socket path | addr:port>] [<path>] |
|
28 |
''' % cmd |
|
29 |
|
|
30 |
def usage_error(error_msg = "unspecified error"): |
|
31 |
sys.stderr.write('%s\nERROR: %s\n' % (usage(), error_msg)) |
|
32 |
exit(1) |
|
33 |
|
|
34 |
if len(args) > 0: |
|
35 |
if args[0] == "-h": |
|
36 |
print usage() |
|
37 |
exit(0); |
|
38 |
elif args[0] == "-s": |
|
39 |
try: |
|
40 |
socket_path = args[1] |
|
41 |
except: |
|
42 |
usage_error("missing argument: QMP socket path or address"); |
|
43 |
args = args[2:] |
|
44 |
|
|
45 |
if not socket_path: |
|
46 |
if os.environ.has_key('QMP_SOCKET'): |
|
47 |
socket_path = os.environ['QMP_SOCKET'] |
|
48 |
else: |
|
49 |
usage_error("no QMP socket path or address given"); |
|
50 |
|
|
51 |
srv = QEMUMonitorProtocol(socket_path) |
|
52 |
srv.connect() |
|
53 |
|
|
54 |
if len(args) == 0: |
|
55 |
print '/' |
|
56 |
sys.exit(0) |
|
57 |
|
|
58 |
for item in srv.command('qom-list', path=args[0]): |
|
59 |
if item['type'].startswith('child<'): |
|
60 |
print '%s/' % item['name'] |
|
61 |
elif item['type'].startswith('link<'): |
|
62 |
print '@%s/' % item['name'] |
|
63 |
else: |
|
64 |
print '%s' % item['name'] |
/dev/null | ||
---|---|---|
1 |
#!/usr/bin/python |
|
2 |
## |
|
3 |
# QEMU Object Model test tools |
|
4 |
# |
|
5 |
# Copyright IBM, Corp. 2011 |
|
6 |
# |
|
7 |
# Authors: |
|
8 |
# Anthony Liguori <aliguori@us.ibm.com> |
|
9 |
# |
|
10 |
# This work is licensed under the terms of the GNU GPL, version 2 or later. See |
|
11 |
# the COPYING file in the top-level directory. |
|
12 |
## |
|
13 |
|
|
14 |
import sys |
|
15 |
import os |
|
16 |
from qmp import QEMUMonitorProtocol |
|
17 |
|
|
18 |
cmd, args = sys.argv[0], sys.argv[1:] |
|
19 |
socket_path = None |
|
20 |
path = None |
|
21 |
prop = None |
|
22 |
value = None |
|
23 |
|
|
24 |
def usage(): |
|
25 |
return '''environment variables: |
|
26 |
QMP_SOCKET=<path | addr:port> |
|
27 |
usage: |
|
28 |
%s [-h] [-s <QMP socket path | addr:port>] <path>.<property> <value> |
|
29 |
''' % cmd |
|
30 |
|
|
31 |
def usage_error(error_msg = "unspecified error"): |
|
32 |
sys.stderr.write('%s\nERROR: %s\n' % (usage(), error_msg)) |
|
33 |
exit(1) |
|
34 |
|
|
35 |
if len(args) > 0: |
|
36 |
if args[0] == "-h": |
|
37 |
print usage() |
|
38 |
exit(0); |
|
39 |
elif args[0] == "-s": |
|
40 |
try: |
|
41 |
socket_path = args[1] |
|
42 |
except: |
|
43 |
usage_error("missing argument: QMP socket path or address"); |
|
44 |
args = args[2:] |
|
45 |
|
|
46 |
if not socket_path: |
|
47 |
if os.environ.has_key('QMP_SOCKET'): |
|
48 |
socket_path = os.environ['QMP_SOCKET'] |
|
49 |
else: |
|
50 |
usage_error("no QMP socket path or address given"); |
|
51 |
|
|
52 |
if len(args) > 1: |
|
53 |
try: |
|
54 |
path, prop = args[0].rsplit('.', 1) |
|
55 |
except: |
|
56 |
usage_error("invalid format for path/property/value") |
|
57 |
value = args[1] |
|
58 |
else: |
|
59 |
usage_error("not enough arguments") |
|
60 |
|
|
61 |
srv = QEMUMonitorProtocol(socket_path) |
|
62 |
srv.connect() |
|
63 |
|
|
64 |
print srv.command('qom-set', path=path, property=prop, value=sys.argv[2]) |
b/scripts/qmp/qemu-ga-client | ||
---|---|---|
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: http://wiki.qemu.org/Features/QAPI/GuestAgent |
|
37 |
# |
|
38 |
|
|
39 |
import base64 |
|
40 |
import random |
|
41 |
|
|
42 |
import qmp |
|
43 |
|
|
44 |
|
|
45 |
class QemuGuestAgent(qmp.QEMUMonitorProtocol): |
|
46 |
def __getattr__(self, name): |
|
47 |
def wrapper(**kwds): |
|
48 |
return self.command('guest-' + name.replace('_', '-'), **kwds) |
|
49 |
return wrapper |
|
50 |
|
|
51 |
|
|
52 |
class QemuGuestAgentClient: |
|
53 |
error = QemuGuestAgent.error |
|
54 |
|
|
55 |
def __init__(self, address): |
|
56 |
self.qga = QemuGuestAgent(address) |
|
57 |
self.qga.connect(negotiate=False) |
|
58 |
|
|
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) |
|
64 |
while True: |
|
65 |
ret = self.qga.sync(id=uid) |
|
66 |
if isinstance(ret, int) and int(ret) == uid: |
|
67 |
break |
|
68 |
|
|
69 |
def __file_read_all(self, handle): |
|
70 |
eof = False |
|
71 |
data = '' |
|
72 |
while not eof: |
|
73 |
ret = self.qga.file_read(handle=handle, count=1024) |
|
74 |
_data = base64.b64decode(ret['buf-b64']) |
|
75 |
data += _data |
|
76 |
eof = ret['eof'] |
|
77 |
return data |
|
78 |
|
|
79 |
def read(self, path): |
|
80 |
handle = self.qga.file_open(path=path) |
|
81 |
try: |
|
82 |
data = self.__file_read_all(handle) |
|
83 |
finally: |
|
84 |
self.qga.file_close(handle=handle) |
|
85 |
return data |
|
86 |
|
|
87 |
def info(self): |
|
88 |
info = self.qga.info() |
|
89 |
|
|
90 |
msgs = [] |
|
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)) |
|
97 |
|
|
98 |
return '\n'.join(msgs) |
|
99 |
|
|
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), |
|
105 |
str(mask & 0xff)]) |
|
106 |
|
|
107 |
def ifconfig(self): |
|
108 |
nifs = self.qga.network_get_interfaces() |
|
109 |
|
|
110 |
msgs = [] |
|
111 |
for nif in nifs: |
|
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_ipv4_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']) |
|
125 |
|
|
126 |
return '\n'.join(msgs) |
|
127 |
|
|
128 |
def ping(self, timeout): |
|
129 |
self.qga.settimeout(timeout) |
|
130 |
try: |
|
131 |
self.qga.ping() |
|
132 |
except self.qga.timeout: |
|
133 |
return False |
|
134 |
return True |
|
135 |
|
|
136 |
def fsfreeze(self, cmd): |
|
137 |
if cmd not in ['status', 'freeze', 'thaw']: |
|
138 |
raise StandardError('Invalid command: ' + cmd) |
|
139 |
|
|
140 |
return getattr(self.qga, 'fsfreeze' + '_' + cmd)() |
|
141 |
|
|
142 |
def fstrim(self, minimum=0): |
|
143 |
return getattr(self.qga, 'fstrim')(minimum=minimum) |
|
144 |
|
|
145 |
def suspend(self, mode): |
|
146 |
if mode not in ['disk', 'ram', 'hybrid']: |
|
147 |
raise StandardError('Invalid mode: ' + mode) |
|
148 |
|
|
149 |
try: |
|
150 |
getattr(self.qga, 'suspend' + '_' + mode)() |
|
151 |
# On error exception will raise |
|
152 |
except self.qga.timeout: |
|
153 |
# On success command will timed out |
|
154 |
return |
|
155 |
|
|
156 |
def shutdown(self, mode='powerdown'): |
|
157 |
if mode not in ['powerdown', 'halt', 'reboot']: |
|
158 |
raise StandardError('Invalid mode: ' + mode) |
|
159 |
|
|
160 |
try: |
|
161 |
self.qga.shutdown(mode=mode) |
|
162 |
except self.qga.timeout: |
|
163 |
return |
|
164 |
|
|
165 |
|
|
166 |
def _cmd_cat(client, args): |
|
167 |
if len(args) != 1: |
|
168 |
print('Invalid argument') |
|
169 |
print('Usage: cat <file>') |
|
170 |
sys.exit(1) |
|
171 |
print(client.read(args[0])) |
|
172 |
|
|
173 |
|
|
174 |
def _cmd_fsfreeze(client, args): |
|
175 |
usage = 'Usage: fsfreeze status|freeze|thaw' |
|
176 |
if len(args) != 1: |
|
177 |
print('Invalid argument') |
|
178 |
print(usage) |
|
179 |
sys.exit(1) |
|
180 |
if args[0] not in ['status', 'freeze', 'thaw']: |
|
181 |
print('Invalid command: ' + args[0]) |
|
182 |
print(usage) |
|
183 |
sys.exit(1) |
|
184 |
cmd = args[0] |
|
185 |
ret = client.fsfreeze(cmd) |
|
186 |
if cmd == 'status': |
|
187 |
print(ret) |
|
188 |
elif cmd == 'freeze': |
|
189 |
print("%d filesystems frozen" % ret) |
|
190 |
else: |
|
191 |
print("%d filesystems thawed" % ret) |
|
192 |
|
|
193 |
|
Also available in: Unified diff