]>
Commit | Line | Data |
---|---|---|
66613974 DB |
1 | # QEMU library |
2 | # | |
3 | # Copyright (C) 2015-2016 Red Hat Inc. | |
4 | # Copyright (C) 2012 IBM Corp. | |
5 | # | |
6 | # Authors: | |
7 | # Fam Zheng <[email protected]> | |
8 | # | |
9 | # This work is licensed under the terms of the GNU GPL, version 2. See | |
10 | # the COPYING file in the top-level directory. | |
11 | # | |
12 | # Based on qmp.py. | |
13 | # | |
14 | ||
15 | import errno | |
16 | import string | |
17 | import os | |
18 | import sys | |
19 | import subprocess | |
20 | import qmp.qmp | |
21 | ||
22 | ||
23 | class QEMUMachine(object): | |
24 | '''A QEMU VM''' | |
25 | ||
26 | def __init__(self, binary, args=[], wrapper=[], name=None, test_dir="/var/tmp", | |
4c44b4a4 | 27 | monitor_address=None, socket_scm_helper=None, debug=False): |
66613974 DB |
28 | if name is None: |
29 | name = "qemu-%d" % os.getpid() | |
30 | if monitor_address is None: | |
31 | monitor_address = os.path.join(test_dir, name + "-monitor.sock") | |
32 | self._monitor_address = monitor_address | |
33 | self._qemu_log_path = os.path.join(test_dir, name + ".log") | |
34 | self._popen = None | |
35 | self._binary = binary | |
4c44b4a4 | 36 | self._args = list(args) # Force copy args in case we modify them |
66613974 DB |
37 | self._wrapper = wrapper |
38 | self._events = [] | |
39 | self._iolog = None | |
4c44b4a4 | 40 | self._socket_scm_helper = socket_scm_helper |
66613974 DB |
41 | self._debug = debug |
42 | ||
43 | # This can be used to add an unused monitor instance. | |
44 | def add_monitor_telnet(self, ip, port): | |
45 | args = 'tcp:%s:%d,server,nowait,telnet' % (ip, port) | |
46 | self._args.append('-monitor') | |
47 | self._args.append(args) | |
48 | ||
49 | def add_fd(self, fd, fdset, opaque, opts=''): | |
50 | '''Pass a file descriptor to the VM''' | |
51 | options = ['fd=%d' % fd, | |
52 | 'set=%d' % fdset, | |
53 | 'opaque=%s' % opaque] | |
54 | if opts: | |
55 | options.append(opts) | |
56 | ||
57 | self._args.append('-add-fd') | |
58 | self._args.append(','.join(options)) | |
59 | return self | |
60 | ||
61 | def send_fd_scm(self, fd_file_path): | |
62 | # In iotest.py, the qmp should always use unix socket. | |
63 | assert self._qmp.is_scm_available() | |
4c44b4a4 DB |
64 | if self._socket_scm_helper is None: |
65 | print >>sys.stderr, "No path to socket_scm_helper set" | |
66613974 | 66 | return -1 |
4c44b4a4 DB |
67 | if os.path.exists(self._socket_scm_helper) == False: |
68 | print >>sys.stderr, "%s does not exist" % self._socket_scm_helper | |
69 | return -1 | |
70 | fd_param = ["%s" % self._socket_scm_helper, | |
66613974 DB |
71 | "%d" % self._qmp.get_sock_fd(), |
72 | "%s" % fd_file_path] | |
73 | devnull = open('/dev/null', 'rb') | |
74 | p = subprocess.Popen(fd_param, stdin=devnull, stdout=sys.stdout, | |
75 | stderr=sys.stderr) | |
76 | return p.wait() | |
77 | ||
78 | @staticmethod | |
79 | def _remove_if_exists(path): | |
80 | '''Remove file object at path if it exists''' | |
81 | try: | |
82 | os.remove(path) | |
83 | except OSError as exception: | |
84 | if exception.errno == errno.ENOENT: | |
85 | return | |
86 | raise | |
87 | ||
88 | def get_pid(self): | |
89 | if not self._popen: | |
90 | return None | |
91 | return self._popen.pid | |
92 | ||
93 | def _load_io_log(self): | |
94 | with open(self._qemu_log_path, "r") as fh: | |
95 | self._iolog = fh.read() | |
96 | ||
97 | def _base_args(self): | |
98 | if isinstance(self._monitor_address, tuple): | |
99 | moncdev = "socket,id=mon,host=%s,port=%s" % ( | |
100 | self._monitor_address[0], | |
101 | self._monitor_address[1]) | |
102 | else: | |
103 | moncdev = 'socket,id=mon,path=%s' % self._monitor_address | |
104 | return ['-chardev', moncdev, | |
105 | '-mon', 'chardev=mon,mode=control', | |
106 | '-display', 'none', '-vga', 'none'] | |
107 | ||
108 | def _pre_launch(self): | |
109 | self._qmp = qmp.qmp.QEMUMonitorProtocol(self._monitor_address, server=True, | |
110 | debug=self._debug) | |
111 | ||
112 | def _post_launch(self): | |
113 | self._qmp.accept() | |
114 | ||
115 | def _post_shutdown(self): | |
116 | if not isinstance(self._monitor_address, tuple): | |
117 | self._remove_if_exists(self._monitor_address) | |
118 | self._remove_if_exists(self._qemu_log_path) | |
119 | ||
120 | def launch(self): | |
121 | '''Launch the VM and establish a QMP connection''' | |
122 | devnull = open('/dev/null', 'rb') | |
123 | qemulog = open(self._qemu_log_path, 'wb') | |
124 | try: | |
125 | self._pre_launch() | |
126 | args = self._wrapper + [self._binary] + self._base_args() + self._args | |
127 | self._popen = subprocess.Popen(args, stdin=devnull, stdout=qemulog, | |
128 | stderr=subprocess.STDOUT, shell=False) | |
129 | self._post_launch() | |
130 | except: | |
131 | if self._popen: | |
132 | self._popen.kill() | |
133 | self._load_io_log() | |
134 | self._post_shutdown() | |
135 | self._popen = None | |
136 | raise | |
137 | ||
138 | def shutdown(self): | |
139 | '''Terminate the VM and clean up''' | |
140 | if not self._popen is None: | |
141 | try: | |
142 | self._qmp.cmd('quit') | |
143 | self._qmp.close() | |
144 | except: | |
145 | self._popen.kill() | |
146 | ||
147 | exitcode = self._popen.wait() | |
148 | if exitcode < 0: | |
149 | sys.stderr.write('qemu received signal %i: %s\n' % (-exitcode, ' '.join(self._args))) | |
150 | self._load_io_log() | |
151 | self._post_shutdown() | |
152 | self._popen = None | |
153 | ||
154 | underscore_to_dash = string.maketrans('_', '-') | |
155 | def qmp(self, cmd, conv_keys=True, **args): | |
156 | '''Invoke a QMP command and return the result dict''' | |
157 | qmp_args = dict() | |
158 | for k in args.keys(): | |
159 | if conv_keys: | |
160 | qmp_args[k.translate(self.underscore_to_dash)] = args[k] | |
161 | else: | |
162 | qmp_args[k] = args[k] | |
163 | ||
164 | return self._qmp.cmd(cmd, args=qmp_args) | |
165 | ||
166 | def command(self, cmd, conv_keys=True, **args): | |
167 | reply = self.qmp(cmd, conv_keys, **args) | |
168 | if reply is None: | |
169 | raise Exception("Monitor is closed") | |
170 | if "error" in reply: | |
171 | raise Exception(reply["error"]["desc"]) | |
172 | return reply["return"] | |
173 | ||
174 | def get_qmp_event(self, wait=False): | |
175 | '''Poll for one queued QMP events and return it''' | |
176 | if len(self._events) > 0: | |
177 | return self._events.pop(0) | |
178 | return self._qmp.pull_event(wait=wait) | |
179 | ||
180 | def get_qmp_events(self, wait=False): | |
181 | '''Poll for queued QMP events and return a list of dicts''' | |
182 | events = self._qmp.get_events(wait=wait) | |
183 | events.extend(self._events) | |
184 | del self._events[:] | |
185 | self._qmp.clear_events() | |
186 | return events | |
187 | ||
188 | def event_wait(self, name, timeout=60.0, match=None): | |
4c44b4a4 DB |
189 | # Test if 'match' is a recursive subset of 'event' |
190 | def event_match(event, match=None): | |
191 | if match is None: | |
192 | return True | |
193 | ||
194 | for key in match: | |
195 | if key in event: | |
196 | if isinstance(event[key], dict): | |
197 | if not event_match(event[key], match[key]): | |
198 | return False | |
199 | elif event[key] != match[key]: | |
200 | return False | |
201 | else: | |
202 | return False | |
203 | ||
204 | return True | |
205 | ||
66613974 DB |
206 | # Search cached events |
207 | for event in self._events: | |
208 | if (event['event'] == name) and event_match(event, match): | |
209 | self._events.remove(event) | |
210 | return event | |
211 | ||
212 | # Poll for new events | |
213 | while True: | |
214 | event = self._qmp.pull_event(wait=timeout) | |
215 | if (event['event'] == name) and event_match(event, match): | |
216 | return event | |
217 | self._events.append(event) | |
218 | ||
219 | return None | |
220 | ||
221 | def get_log(self): | |
222 | return self._iolog |