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