]> Git Repo - qemu.git/blob - scripts/qemu.py
Merge remote-tracking branch 'remotes/rth/tags/pull-tcg-20181031' into staging
[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 re
21 import shutil
22 import socket
23 import tempfile
24
25
26 LOG = logging.getLogger(__name__)
27
28
29 def kvm_available(target_arch=None):
30     if target_arch and target_arch != os.uname()[4]:
31         return False
32     return os.access("/dev/kvm", os.R_OK | os.W_OK)
33
34
35 #: Maps machine types to the preferred console device types
36 CONSOLE_DEV_TYPES = {
37     r'^clipper$': 'isa-serial',
38     r'^malta': 'isa-serial',
39     r'^(pc.*|q35.*|isapc)$': 'isa-serial',
40     r'^(40p|powernv|prep)$': 'isa-serial',
41     r'^pseries.*': 'spapr-vty',
42     r'^s390-ccw-virtio.*': 'sclpconsole',
43     }
44
45
46 class QEMUMachineError(Exception):
47     """
48     Exception called when an error in QEMUMachine happens.
49     """
50
51
52 class QEMUMachineAddDeviceError(QEMUMachineError):
53     """
54     Exception raised when a request to add a device can not be fulfilled
55
56     The failures are caused by limitations, lack of information or conflicting
57     requests on the QEMUMachine methods.  This exception does not represent
58     failures reported by the QEMU binary itself.
59     """
60
61 class MonitorResponseError(qmp.qmp.QMPError):
62     """
63     Represents erroneous QMP monitor reply
64     """
65     def __init__(self, reply):
66         try:
67             desc = reply["error"]["desc"]
68         except KeyError:
69             desc = reply
70         super(MonitorResponseError, self).__init__(desc)
71         self.reply = reply
72
73
74 class QEMUMachine(object):
75     """
76     A QEMU VM
77
78     Use this object as a context manager to ensure the QEMU process terminates::
79
80         with VM(binary) as vm:
81             ...
82         # vm is guaranteed to be shut down here
83     """
84
85     def __init__(self, binary, args=None, wrapper=None, name=None,
86                  test_dir="/var/tmp", monitor_address=None,
87                  socket_scm_helper=None):
88         '''
89         Initialize a QEMUMachine
90
91         @param binary: path to the qemu binary
92         @param args: list of extra arguments
93         @param wrapper: list of arguments used as prefix to qemu binary
94         @param name: prefix for socket and log file names (default: qemu-PID)
95         @param test_dir: where to create socket and log file
96         @param monitor_address: address for QMP monitor
97         @param socket_scm_helper: helper program, required for send_fd_scm()
98         @note: Qemu process is not started until launch() is used.
99         '''
100         if args is None:
101             args = []
102         if wrapper is None:
103             wrapper = []
104         if name is None:
105             name = "qemu-%d" % os.getpid()
106         self._name = name
107         self._monitor_address = monitor_address
108         self._vm_monitor = None
109         self._qemu_log_path = None
110         self._qemu_log_file = None
111         self._popen = None
112         self._binary = binary
113         self._args = list(args)     # Force copy args in case we modify them
114         self._wrapper = wrapper
115         self._events = []
116         self._iolog = None
117         self._socket_scm_helper = socket_scm_helper
118         self._qmp = None
119         self._qemu_full_args = None
120         self._test_dir = test_dir
121         self._temp_dir = None
122         self._launched = False
123         self._machine = None
124         self._console_device_type = None
125         self._console_address = None
126         self._console_socket = None
127
128         # just in case logging wasn't configured by the main script:
129         logging.basicConfig()
130
131     def __enter__(self):
132         return self
133
134     def __exit__(self, exc_type, exc_val, exc_tb):
135         self.shutdown()
136         return False
137
138     # This can be used to add an unused monitor instance.
139     def add_monitor_telnet(self, ip, port):
140         args = 'tcp:%s:%d,server,nowait,telnet' % (ip, port)
141         self._args.append('-monitor')
142         self._args.append(args)
143
144     def add_fd(self, fd, fdset, opaque, opts=''):
145         """
146         Pass a file descriptor to the VM
147         """
148         options = ['fd=%d' % fd,
149                    'set=%d' % fdset,
150                    'opaque=%s' % opaque]
151         if opts:
152             options.append(opts)
153
154         # This did not exist before 3.4, but since then it is
155         # mandatory for our purpose
156         if hasattr(os, 'set_inheritable'):
157             os.set_inheritable(fd, True)
158
159         self._args.append('-add-fd')
160         self._args.append(','.join(options))
161         return self
162
163     # Exactly one of fd and file_path must be given.
164     # (If it is file_path, the helper will open that file and pass its
165     # own fd)
166     def send_fd_scm(self, fd=None, file_path=None):
167         # In iotest.py, the qmp should always use unix socket.
168         assert self._qmp.is_scm_available()
169         if self._socket_scm_helper is None:
170             raise QEMUMachineError("No path to socket_scm_helper set")
171         if not os.path.exists(self._socket_scm_helper):
172             raise QEMUMachineError("%s does not exist" %
173                                    self._socket_scm_helper)
174
175         # This did not exist before 3.4, but since then it is
176         # mandatory for our purpose
177         if hasattr(os, 'set_inheritable'):
178             os.set_inheritable(self._qmp.get_sock_fd(), True)
179             if fd is not None:
180                 os.set_inheritable(fd, True)
181
182         fd_param = ["%s" % self._socket_scm_helper,
183                     "%d" % self._qmp.get_sock_fd()]
184
185         if file_path is not None:
186             assert fd is None
187             fd_param.append(file_path)
188         else:
189             assert fd is not None
190             fd_param.append(str(fd))
191
192         devnull = open(os.path.devnull, 'rb')
193         proc = subprocess.Popen(fd_param, stdin=devnull, stdout=subprocess.PIPE,
194                                 stderr=subprocess.STDOUT, close_fds=False)
195         output = proc.communicate()[0]
196         if output:
197             LOG.debug(output)
198
199         return proc.returncode
200
201     @staticmethod
202     def _remove_if_exists(path):
203         """
204         Remove file object at path if it exists
205         """
206         try:
207             os.remove(path)
208         except OSError as exception:
209             if exception.errno == errno.ENOENT:
210                 return
211             raise
212
213     def is_running(self):
214         return self._popen is not None and self._popen.poll() is None
215
216     def exitcode(self):
217         if self._popen is None:
218             return None
219         return self._popen.poll()
220
221     def get_pid(self):
222         if not self.is_running():
223             return None
224         return self._popen.pid
225
226     def _load_io_log(self):
227         if self._qemu_log_path is not None:
228             with open(self._qemu_log_path, "r") as iolog:
229                 self._iolog = iolog.read()
230
231     def _base_args(self):
232         if isinstance(self._monitor_address, tuple):
233             moncdev = "socket,id=mon,host=%s,port=%s" % (
234                 self._monitor_address[0],
235                 self._monitor_address[1])
236         else:
237             moncdev = 'socket,id=mon,path=%s' % self._vm_monitor
238         args = ['-chardev', moncdev,
239                 '-mon', 'chardev=mon,mode=control',
240                 '-display', 'none', '-vga', 'none']
241         if self._machine is not None:
242             args.extend(['-machine', self._machine])
243         if self._console_device_type is not None:
244             self._console_address = os.path.join(self._temp_dir,
245                                                  self._name + "-console.sock")
246             chardev = ('socket,id=console,path=%s,server,nowait' %
247                        self._console_address)
248             device = '%s,chardev=console' % self._console_device_type
249             args.extend(['-chardev', chardev, '-device', device])
250         return args
251
252     def _pre_launch(self):
253         self._temp_dir = tempfile.mkdtemp(dir=self._test_dir)
254         if self._monitor_address is not None:
255             self._vm_monitor = self._monitor_address
256         else:
257             self._vm_monitor = os.path.join(self._temp_dir,
258                                             self._name + "-monitor.sock")
259         self._qemu_log_path = os.path.join(self._temp_dir, self._name + ".log")
260         self._qemu_log_file = open(self._qemu_log_path, 'wb')
261
262         self._qmp = qmp.qmp.QEMUMonitorProtocol(self._vm_monitor,
263                                                 server=True)
264
265     def _post_launch(self):
266         self._qmp.accept()
267
268     def _post_shutdown(self):
269         if self._qemu_log_file is not None:
270             self._qemu_log_file.close()
271             self._qemu_log_file = None
272
273         self._qemu_log_path = None
274
275         if self._console_socket is not None:
276             self._console_socket.close()
277             self._console_socket = None
278
279         if self._temp_dir is not None:
280             shutil.rmtree(self._temp_dir)
281             self._temp_dir = None
282
283     def launch(self):
284         """
285         Launch the VM and make sure we cleanup and expose the
286         command line/output in case of exception
287         """
288
289         if self._launched:
290             raise QEMUMachineError('VM already launched')
291
292         self._iolog = None
293         self._qemu_full_args = None
294         try:
295             self._launch()
296             self._launched = True
297         except:
298             self.shutdown()
299
300             LOG.debug('Error launching VM')
301             if self._qemu_full_args:
302                 LOG.debug('Command: %r', ' '.join(self._qemu_full_args))
303             if self._iolog:
304                 LOG.debug('Output: %r', self._iolog)
305             raise
306
307     def _launch(self):
308         """
309         Launch the VM and establish a QMP connection
310         """
311         devnull = open(os.path.devnull, 'rb')
312         self._pre_launch()
313         self._qemu_full_args = (self._wrapper + [self._binary] +
314                                 self._base_args() + self._args)
315         self._popen = subprocess.Popen(self._qemu_full_args,
316                                        stdin=devnull,
317                                        stdout=self._qemu_log_file,
318                                        stderr=subprocess.STDOUT,
319                                        shell=False,
320                                        close_fds=False)
321         self._post_launch()
322
323     def wait(self):
324         """
325         Wait for the VM to power off
326         """
327         self._popen.wait()
328         self._qmp.close()
329         self._load_io_log()
330         self._post_shutdown()
331
332     def shutdown(self):
333         """
334         Terminate the VM and clean up
335         """
336         if self.is_running():
337             try:
338                 self._qmp.cmd('quit')
339                 self._qmp.close()
340             except:
341                 self._popen.kill()
342             self._popen.wait()
343
344         self._load_io_log()
345         self._post_shutdown()
346
347         exitcode = self.exitcode()
348         if exitcode is not None and exitcode < 0:
349             msg = 'qemu received signal %i: %s'
350             if self._qemu_full_args:
351                 command = ' '.join(self._qemu_full_args)
352             else:
353                 command = ''
354             LOG.warn(msg, exitcode, command)
355
356         self._launched = False
357
358     def qmp(self, cmd, conv_keys=True, **args):
359         """
360         Invoke a QMP command and return the response dict
361         """
362         qmp_args = dict()
363         for key, value in args.items():
364             if conv_keys:
365                 qmp_args[key.replace('_', '-')] = value
366             else:
367                 qmp_args[key] = value
368
369         return self._qmp.cmd(cmd, args=qmp_args)
370
371     def command(self, cmd, conv_keys=True, **args):
372         """
373         Invoke a QMP command.
374         On success return the response dict.
375         On failure raise an exception.
376         """
377         reply = self.qmp(cmd, conv_keys, **args)
378         if reply is None:
379             raise qmp.qmp.QMPError("Monitor is closed")
380         if "error" in reply:
381             raise MonitorResponseError(reply)
382         return reply["return"]
383
384     def get_qmp_event(self, wait=False):
385         """
386         Poll for one queued QMP events and return it
387         """
388         if len(self._events) > 0:
389             return self._events.pop(0)
390         return self._qmp.pull_event(wait=wait)
391
392     def get_qmp_events(self, wait=False):
393         """
394         Poll for queued QMP events and return a list of dicts
395         """
396         events = self._qmp.get_events(wait=wait)
397         events.extend(self._events)
398         del self._events[:]
399         self._qmp.clear_events()
400         return events
401
402     def event_wait(self, name, timeout=60.0, match=None):
403         """
404         Wait for specified timeout on named event in QMP; optionally filter
405         results by match.
406
407         The 'match' is checked to be a recursive subset of the 'event'; skips
408         branch processing on match's value None
409            {"foo": {"bar": 1}} matches {"foo": None}
410            {"foo": {"bar": 1}} does not matches {"foo": {"baz": None}}
411         """
412         def event_match(event, match=None):
413             if match is None:
414                 return True
415
416             for key in match:
417                 if key in event:
418                     if isinstance(event[key], dict):
419                         if not event_match(event[key], match[key]):
420                             return False
421                     elif event[key] != match[key]:
422                         return False
423                 else:
424                     return False
425
426             return True
427
428         # Search cached events
429         for event in self._events:
430             if (event['event'] == name) and event_match(event, match):
431                 self._events.remove(event)
432                 return event
433
434         # Poll for new events
435         while True:
436             event = self._qmp.pull_event(wait=timeout)
437             if (event['event'] == name) and event_match(event, match):
438                 return event
439             self._events.append(event)
440
441         return None
442
443     def get_log(self):
444         """
445         After self.shutdown or failed qemu execution, this returns the output
446         of the qemu process.
447         """
448         return self._iolog
449
450     def add_args(self, *args):
451         """
452         Adds to the list of extra arguments to be given to the QEMU binary
453         """
454         self._args.extend(args)
455
456     def set_machine(self, machine_type):
457         """
458         Sets the machine type
459
460         If set, the machine type will be added to the base arguments
461         of the resulting QEMU command line.
462         """
463         self._machine = machine_type
464
465     def set_console(self, device_type=None):
466         """
467         Sets the device type for a console device
468
469         If set, the console device and a backing character device will
470         be added to the base arguments of the resulting QEMU command
471         line.
472
473         This is a convenience method that will either use the provided
474         device type, of if not given, it will used the device type set
475         on CONSOLE_DEV_TYPES.
476
477         The actual setting of command line arguments will be be done at
478         machine launch time, as it depends on the temporary directory
479         to be created.
480
481         @param device_type: the device type, such as "isa-serial"
482         @raises: QEMUMachineAddDeviceError if the device type is not given
483                  and can not be determined.
484         """
485         if device_type is None:
486             if self._machine is None:
487                 raise QEMUMachineAddDeviceError("Can not add a console device:"
488                                                 " QEMU instance without a "
489                                                 "defined machine type")
490             for regex, device in CONSOLE_DEV_TYPES.items():
491                 if re.match(regex, self._machine):
492                     device_type = device
493                     break
494             if device_type is None:
495                 raise QEMUMachineAddDeviceError("Can not add a console device:"
496                                                 " no matching console device "
497                                                 "type definition")
498         self._console_device_type = device_type
499
500     @property
501     def console_socket(self):
502         """
503         Returns a socket connected to the console
504         """
505         if self._console_socket is None:
506             self._console_socket = socket.socket(socket.AF_UNIX,
507                                                  socket.SOCK_STREAM)
508             self._console_socket.connect(self._console_address)
509         return self._console_socket
This page took 0.05391 seconds and 4 git commands to generate.