]> Git Repo - qemu.git/blob - scripts/qemu.py
qapi/common: Fix guardname() for funny filenames
[qemu.git] / scripts / qemu.py
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 logging
17 import os
18 import subprocess
19 import qmp.qmp
20 import shutil
21 import tempfile
22
23
24 LOG = logging.getLogger(__name__)
25
26
27 class QEMUMachineError(Exception):
28     """
29     Exception called when an error in QEMUMachine happens.
30     """
31
32
33 class MonitorResponseError(qmp.qmp.QMPError):
34     '''
35     Represents erroneous QMP monitor reply
36     '''
37     def __init__(self, reply):
38         try:
39             desc = reply["error"]["desc"]
40         except KeyError:
41             desc = reply
42         super(MonitorResponseError, self).__init__(desc)
43         self.reply = reply
44
45
46 class QEMUMachine(object):
47     '''A QEMU VM
48
49     Use this object as a context manager to ensure the QEMU process terminates::
50
51         with VM(binary) as vm:
52             ...
53         # vm is guaranteed to be shut down here
54     '''
55
56     def __init__(self, binary, args=None, wrapper=None, name=None,
57                  test_dir="/var/tmp", monitor_address=None,
58                  socket_scm_helper=None):
59         '''
60         Initialize a QEMUMachine
61
62         @param binary: path to the qemu binary
63         @param args: list of extra arguments
64         @param wrapper: list of arguments used as prefix to qemu binary
65         @param name: prefix for socket and log file names (default: qemu-PID)
66         @param test_dir: where to create socket and log file
67         @param monitor_address: address for QMP monitor
68         @param socket_scm_helper: helper program, required for send_fd_scm()"
69         @note: Qemu process is not started until launch() is used.
70         '''
71         if args is None:
72             args = []
73         if wrapper is None:
74             wrapper = []
75         if name is None:
76             name = "qemu-%d" % os.getpid()
77         self._name = name
78         self._monitor_address = monitor_address
79         self._vm_monitor = None
80         self._qemu_log_path = None
81         self._qemu_log_file = None
82         self._popen = None
83         self._binary = binary
84         self._args = list(args)     # Force copy args in case we modify them
85         self._wrapper = wrapper
86         self._events = []
87         self._iolog = None
88         self._socket_scm_helper = socket_scm_helper
89         self._qmp = None
90         self._qemu_full_args = None
91         self._test_dir = test_dir
92         self._temp_dir = None
93         self._launched = False
94
95         # just in case logging wasn't configured by the main script:
96         logging.basicConfig()
97
98     def __enter__(self):
99         return self
100
101     def __exit__(self, exc_type, exc_val, exc_tb):
102         self.shutdown()
103         return False
104
105     # This can be used to add an unused monitor instance.
106     def add_monitor_telnet(self, ip, port):
107         args = 'tcp:%s:%d,server,nowait,telnet' % (ip, port)
108         self._args.append('-monitor')
109         self._args.append(args)
110
111     def add_fd(self, fd, fdset, opaque, opts=''):
112         '''Pass a file descriptor to the VM'''
113         options = ['fd=%d' % fd,
114                    'set=%d' % fdset,
115                    'opaque=%s' % opaque]
116         if opts:
117             options.append(opts)
118
119         self._args.append('-add-fd')
120         self._args.append(','.join(options))
121         return self
122
123     def send_fd_scm(self, fd_file_path):
124         # In iotest.py, the qmp should always use unix socket.
125         assert self._qmp.is_scm_available()
126         if self._socket_scm_helper is None:
127             raise QEMUMachineError("No path to socket_scm_helper set")
128         if not os.path.exists(self._socket_scm_helper):
129             raise QEMUMachineError("%s does not exist" %
130                                    self._socket_scm_helper)
131         fd_param = ["%s" % self._socket_scm_helper,
132                     "%d" % self._qmp.get_sock_fd(),
133                     "%s" % fd_file_path]
134         devnull = open(os.path.devnull, 'rb')
135         proc = subprocess.Popen(fd_param, stdin=devnull, stdout=subprocess.PIPE,
136                                 stderr=subprocess.STDOUT)
137         output = proc.communicate()[0]
138         if output:
139             LOG.debug(output)
140
141         return proc.returncode
142
143     @staticmethod
144     def _remove_if_exists(path):
145         '''Remove file object at path if it exists'''
146         try:
147             os.remove(path)
148         except OSError as exception:
149             if exception.errno == errno.ENOENT:
150                 return
151             raise
152
153     def is_running(self):
154         return self._popen is not None and self._popen.poll() is None
155
156     def exitcode(self):
157         if self._popen is None:
158             return None
159         return self._popen.poll()
160
161     def get_pid(self):
162         if not self.is_running():
163             return None
164         return self._popen.pid
165
166     def _load_io_log(self):
167         if self._qemu_log_path is not None:
168             with open(self._qemu_log_path, "r") as iolog:
169                 self._iolog = iolog.read()
170
171     def _base_args(self):
172         if isinstance(self._monitor_address, tuple):
173             moncdev = "socket,id=mon,host=%s,port=%s" % (
174                 self._monitor_address[0],
175                 self._monitor_address[1])
176         else:
177             moncdev = 'socket,id=mon,path=%s' % self._vm_monitor
178         return ['-chardev', moncdev,
179                 '-mon', 'chardev=mon,mode=control',
180                 '-display', 'none', '-vga', 'none']
181
182     def _pre_launch(self):
183         self._temp_dir = tempfile.mkdtemp(dir=self._test_dir)
184         if self._monitor_address is not None:
185             self._vm_monitor = self._monitor_address
186         else:
187             self._vm_monitor = os.path.join(self._temp_dir,
188                                             self._name + "-monitor.sock")
189         self._qemu_log_path = os.path.join(self._temp_dir, self._name + ".log")
190         self._qemu_log_file = open(self._qemu_log_path, 'wb')
191
192         self._qmp = qmp.qmp.QEMUMonitorProtocol(self._vm_monitor,
193                                                 server=True)
194
195     def _post_launch(self):
196         self._qmp.accept()
197
198     def _post_shutdown(self):
199         if self._qemu_log_file is not None:
200             self._qemu_log_file.close()
201             self._qemu_log_file = None
202
203         self._qemu_log_path = None
204
205         if self._temp_dir is not None:
206             shutil.rmtree(self._temp_dir)
207             self._temp_dir = None
208
209     def launch(self):
210         """
211         Launch the VM and make sure we cleanup and expose the
212         command line/output in case of exception
213         """
214
215         if self._launched:
216             raise QEMUMachineError('VM already launched')
217
218         self._iolog = None
219         self._qemu_full_args = None
220         try:
221             self._launch()
222             self._launched = True
223         except:
224             self.shutdown()
225
226             LOG.debug('Error launching VM')
227             if self._qemu_full_args:
228                 LOG.debug('Command: %r', ' '.join(self._qemu_full_args))
229             if self._iolog:
230                 LOG.debug('Output: %r', self._iolog)
231             raise
232
233     def _launch(self):
234         '''Launch the VM and establish a QMP connection'''
235         devnull = open(os.path.devnull, 'rb')
236         self._pre_launch()
237         self._qemu_full_args = (self._wrapper + [self._binary] +
238                                 self._base_args() + self._args)
239         self._popen = subprocess.Popen(self._qemu_full_args,
240                                        stdin=devnull,
241                                        stdout=self._qemu_log_file,
242                                        stderr=subprocess.STDOUT,
243                                        shell=False)
244         self._post_launch()
245
246     def wait(self):
247         '''Wait for the VM to power off'''
248         self._popen.wait()
249         self._qmp.close()
250         self._load_io_log()
251         self._post_shutdown()
252
253     def shutdown(self):
254         '''Terminate the VM and clean up'''
255         if self.is_running():
256             try:
257                 self._qmp.cmd('quit')
258                 self._qmp.close()
259             except:
260                 self._popen.kill()
261             self._popen.wait()
262
263         self._load_io_log()
264         self._post_shutdown()
265
266         exitcode = self.exitcode()
267         if exitcode is not None and exitcode < 0:
268             msg = 'qemu received signal %i: %s'
269             if self._qemu_full_args:
270                 command = ' '.join(self._qemu_full_args)
271             else:
272                 command = ''
273             LOG.warn(msg, exitcode, command)
274
275         self._launched = False
276
277     def qmp(self, cmd, conv_keys=True, **args):
278         '''Invoke a QMP command and return the response dict'''
279         qmp_args = dict()
280         for key, value in args.iteritems():
281             if conv_keys:
282                 qmp_args[key.replace('_', '-')] = value
283             else:
284                 qmp_args[key] = value
285
286         return self._qmp.cmd(cmd, args=qmp_args)
287
288     def command(self, cmd, conv_keys=True, **args):
289         '''
290         Invoke a QMP command.
291         On success return the response dict.
292         On failure raise an exception.
293         '''
294         reply = self.qmp(cmd, conv_keys, **args)
295         if reply is None:
296             raise qmp.qmp.QMPError("Monitor is closed")
297         if "error" in reply:
298             raise MonitorResponseError(reply)
299         return reply["return"]
300
301     def get_qmp_event(self, wait=False):
302         '''Poll for one queued QMP events and return it'''
303         if len(self._events) > 0:
304             return self._events.pop(0)
305         return self._qmp.pull_event(wait=wait)
306
307     def get_qmp_events(self, wait=False):
308         '''Poll for queued QMP events and return a list of dicts'''
309         events = self._qmp.get_events(wait=wait)
310         events.extend(self._events)
311         del self._events[:]
312         self._qmp.clear_events()
313         return events
314
315     def event_wait(self, name, timeout=60.0, match=None):
316         '''
317         Wait for specified timeout on named event in QMP; optionally filter
318         results by match.
319
320         The 'match' is checked to be a recursive subset of the 'event'; skips
321         branch processing on match's value None
322            {"foo": {"bar": 1}} matches {"foo": None}
323            {"foo": {"bar": 1}} does not matches {"foo": {"baz": None}}
324         '''
325         def event_match(event, match=None):
326             if match is None:
327                 return True
328
329             for key in match:
330                 if key in event:
331                     if isinstance(event[key], dict):
332                         if not event_match(event[key], match[key]):
333                             return False
334                     elif event[key] != match[key]:
335                         return False
336                 else:
337                     return False
338
339             return True
340
341         # Search cached events
342         for event in self._events:
343             if (event['event'] == name) and event_match(event, match):
344                 self._events.remove(event)
345                 return event
346
347         # Poll for new events
348         while True:
349             event = self._qmp.pull_event(wait=timeout)
350             if (event['event'] == name) and event_match(event, match):
351                 return event
352             self._events.append(event)
353
354         return None
355
356     def get_log(self):
357         '''
358         After self.shutdown or failed qemu execution, this returns the output
359         of the qemu process.
360         '''
361         return self._iolog
This page took 0.044681 seconds and 4 git commands to generate.