]> Git Repo - qemu.git/blob - python/qemu/__init__.py
Merge remote-tracking branch 'remotes/cleber/tags/python-next-pull-request' into...
[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_null(self):
149         self._args.append('-monitor')
150         self._args.append('null')
151
152     def add_fd(self, fd, fdset, opaque, opts=''):
153         """
154         Pass a file descriptor to the VM
155         """
156         options = ['fd=%d' % fd,
157                    'set=%d' % fdset,
158                    'opaque=%s' % opaque]
159         if opts:
160             options.append(opts)
161
162         # This did not exist before 3.4, but since then it is
163         # mandatory for our purpose
164         if hasattr(os, 'set_inheritable'):
165             os.set_inheritable(fd, True)
166
167         self._args.append('-add-fd')
168         self._args.append(','.join(options))
169         return self
170
171     # Exactly one of fd and file_path must be given.
172     # (If it is file_path, the helper will open that file and pass its
173     # own fd)
174     def send_fd_scm(self, fd=None, file_path=None):
175         # In iotest.py, the qmp should always use unix socket.
176         assert self._qmp.is_scm_available()
177         if self._socket_scm_helper is None:
178             raise QEMUMachineError("No path to socket_scm_helper set")
179         if not os.path.exists(self._socket_scm_helper):
180             raise QEMUMachineError("%s does not exist" %
181                                    self._socket_scm_helper)
182
183         # This did not exist before 3.4, but since then it is
184         # mandatory for our purpose
185         if hasattr(os, 'set_inheritable'):
186             os.set_inheritable(self._qmp.get_sock_fd(), True)
187             if fd is not None:
188                 os.set_inheritable(fd, True)
189
190         fd_param = ["%s" % self._socket_scm_helper,
191                     "%d" % self._qmp.get_sock_fd()]
192
193         if file_path is not None:
194             assert fd is None
195             fd_param.append(file_path)
196         else:
197             assert fd is not None
198             fd_param.append(str(fd))
199
200         devnull = open(os.path.devnull, 'rb')
201         proc = subprocess.Popen(fd_param, stdin=devnull, stdout=subprocess.PIPE,
202                                 stderr=subprocess.STDOUT, close_fds=False)
203         output = proc.communicate()[0]
204         if output:
205             LOG.debug(output)
206
207         return proc.returncode
208
209     @staticmethod
210     def _remove_if_exists(path):
211         """
212         Remove file object at path if it exists
213         """
214         try:
215             os.remove(path)
216         except OSError as exception:
217             if exception.errno == errno.ENOENT:
218                 return
219             raise
220
221     def is_running(self):
222         return self._popen is not None and self._popen.poll() is None
223
224     def exitcode(self):
225         if self._popen is None:
226             return None
227         return self._popen.poll()
228
229     def get_pid(self):
230         if not self.is_running():
231             return None
232         return self._popen.pid
233
234     def _load_io_log(self):
235         if self._qemu_log_path is not None:
236             with open(self._qemu_log_path, "r") as iolog:
237                 self._iolog = iolog.read()
238
239     def _base_args(self):
240         if isinstance(self._monitor_address, tuple):
241             moncdev = "socket,id=mon,host=%s,port=%s" % (
242                 self._monitor_address[0],
243                 self._monitor_address[1])
244         else:
245             moncdev = 'socket,id=mon,path=%s' % self._vm_monitor
246         args = ['-chardev', moncdev,
247                 '-mon', 'chardev=mon,mode=control',
248                 '-display', 'none', '-vga', 'none']
249         if self._machine is not None:
250             args.extend(['-machine', self._machine])
251         if self._console_device_type is not None:
252             self._console_address = os.path.join(self._temp_dir,
253                                                  self._name + "-console.sock")
254             chardev = ('socket,id=console,path=%s,server,nowait' %
255                        self._console_address)
256             device = '%s,chardev=console' % self._console_device_type
257             args.extend(['-chardev', chardev, '-device', device])
258         return args
259
260     def _pre_launch(self):
261         self._temp_dir = tempfile.mkdtemp(dir=self._test_dir)
262         if self._monitor_address is not None:
263             self._vm_monitor = self._monitor_address
264         else:
265             self._vm_monitor = os.path.join(self._temp_dir,
266                                             self._name + "-monitor.sock")
267         self._qemu_log_path = os.path.join(self._temp_dir, self._name + ".log")
268         self._qemu_log_file = open(self._qemu_log_path, 'wb')
269
270         self._qmp = qmp.QEMUMonitorProtocol(self._vm_monitor,
271                                             server=True)
272
273     def _post_launch(self):
274         self._qmp.accept()
275
276     def _post_shutdown(self):
277         if self._qemu_log_file is not None:
278             self._qemu_log_file.close()
279             self._qemu_log_file = None
280
281         self._qemu_log_path = None
282
283         if self._console_socket is not None:
284             self._console_socket.close()
285             self._console_socket = None
286
287         if self._temp_dir is not None:
288             shutil.rmtree(self._temp_dir)
289             self._temp_dir = None
290
291     def launch(self):
292         """
293         Launch the VM and make sure we cleanup and expose the
294         command line/output in case of exception
295         """
296
297         if self._launched:
298             raise QEMUMachineError('VM already launched')
299
300         self._iolog = None
301         self._qemu_full_args = None
302         try:
303             self._launch()
304             self._launched = True
305         except:
306             self.shutdown()
307
308             LOG.debug('Error launching VM')
309             if self._qemu_full_args:
310                 LOG.debug('Command: %r', ' '.join(self._qemu_full_args))
311             if self._iolog:
312                 LOG.debug('Output: %r', self._iolog)
313             raise
314
315     def _launch(self):
316         """
317         Launch the VM and establish a QMP connection
318         """
319         devnull = open(os.path.devnull, 'rb')
320         self._pre_launch()
321         self._qemu_full_args = (self._wrapper + [self._binary] +
322                                 self._base_args() + self._args)
323         LOG.debug('VM launch command: %r', ' '.join(self._qemu_full_args))
324         self._popen = subprocess.Popen(self._qemu_full_args,
325                                        stdin=devnull,
326                                        stdout=self._qemu_log_file,
327                                        stderr=subprocess.STDOUT,
328                                        shell=False,
329                                        close_fds=False)
330         self._post_launch()
331
332     def wait(self):
333         """
334         Wait for the VM to power off
335         """
336         self._popen.wait()
337         self._qmp.close()
338         self._load_io_log()
339         self._post_shutdown()
340
341     def shutdown(self):
342         """
343         Terminate the VM and clean up
344         """
345         if self.is_running():
346             try:
347                 self._qmp.cmd('quit')
348                 self._qmp.close()
349             except:
350                 self._popen.kill()
351             self._popen.wait()
352
353         self._load_io_log()
354         self._post_shutdown()
355
356         exitcode = self.exitcode()
357         if exitcode is not None and exitcode < 0:
358             msg = 'qemu received signal %i: %s'
359             if self._qemu_full_args:
360                 command = ' '.join(self._qemu_full_args)
361             else:
362                 command = ''
363             LOG.warn(msg, -exitcode, command)
364
365         self._launched = False
366
367     def qmp(self, cmd, conv_keys=True, **args):
368         """
369         Invoke a QMP command and return the response dict
370         """
371         qmp_args = dict()
372         for key, value in args.items():
373             if conv_keys:
374                 qmp_args[key.replace('_', '-')] = value
375             else:
376                 qmp_args[key] = value
377
378         return self._qmp.cmd(cmd, args=qmp_args)
379
380     def command(self, cmd, conv_keys=True, **args):
381         """
382         Invoke a QMP command.
383         On success return the response dict.
384         On failure raise an exception.
385         """
386         reply = self.qmp(cmd, conv_keys, **args)
387         if reply is None:
388             raise qmp.QMPError("Monitor is closed")
389         if "error" in reply:
390             raise MonitorResponseError(reply)
391         return reply["return"]
392
393     def get_qmp_event(self, wait=False):
394         """
395         Poll for one queued QMP events and return it
396         """
397         if len(self._events) > 0:
398             return self._events.pop(0)
399         return self._qmp.pull_event(wait=wait)
400
401     def get_qmp_events(self, wait=False):
402         """
403         Poll for queued QMP events and return a list of dicts
404         """
405         events = self._qmp.get_events(wait=wait)
406         events.extend(self._events)
407         del self._events[:]
408         self._qmp.clear_events()
409         return events
410
411     def event_wait(self, name, timeout=60.0, match=None):
412         """
413         Wait for specified timeout on named event in QMP; optionally filter
414         results by match.
415
416         The 'match' is checked to be a recursive subset of the 'event'; skips
417         branch processing on match's value None
418            {"foo": {"bar": 1}} matches {"foo": None}
419            {"foo": {"bar": 1}} does not matches {"foo": {"baz": None}}
420         """
421         def event_match(event, match=None):
422             if match is None:
423                 return True
424
425             for key in match:
426                 if key in event:
427                     if isinstance(event[key], dict):
428                         if not event_match(event[key], match[key]):
429                             return False
430                     elif event[key] != match[key]:
431                         return False
432                 else:
433                     return False
434
435             return True
436
437         # Search cached events
438         for event in self._events:
439             if (event['event'] == name) and event_match(event, match):
440                 self._events.remove(event)
441                 return event
442
443         # Poll for new events
444         while True:
445             event = self._qmp.pull_event(wait=timeout)
446             if (event['event'] == name) and event_match(event, match):
447                 return event
448             self._events.append(event)
449
450         return None
451
452     def get_log(self):
453         """
454         After self.shutdown or failed qemu execution, this returns the output
455         of the qemu process.
456         """
457         return self._iolog
458
459     def add_args(self, *args):
460         """
461         Adds to the list of extra arguments to be given to the QEMU binary
462         """
463         self._args.extend(args)
464
465     def set_machine(self, machine_type):
466         """
467         Sets the machine type
468
469         If set, the machine type will be added to the base arguments
470         of the resulting QEMU command line.
471         """
472         self._machine = machine_type
473
474     def set_console(self, device_type=None):
475         """
476         Sets the device type for a console device
477
478         If set, the console device and a backing character device will
479         be added to the base arguments of the resulting QEMU command
480         line.
481
482         This is a convenience method that will either use the provided
483         device type, of if not given, it will used the device type set
484         on CONSOLE_DEV_TYPES.
485
486         The actual setting of command line arguments will be be done at
487         machine launch time, as it depends on the temporary directory
488         to be created.
489
490         @param device_type: the device type, such as "isa-serial"
491         @raises: QEMUMachineAddDeviceError if the device type is not given
492                  and can not be determined.
493         """
494         if device_type is None:
495             if self._machine is None:
496                 raise QEMUMachineAddDeviceError("Can not add a console device:"
497                                                 " QEMU instance without a "
498                                                 "defined machine type")
499             for regex, device in CONSOLE_DEV_TYPES.items():
500                 if re.match(regex, self._machine):
501                     device_type = device
502                     break
503             if device_type is None:
504                 raise QEMUMachineAddDeviceError("Can not add a console device:"
505                                                 " no matching console device "
506                                                 "type definition")
507         self._console_device_type = device_type
508
509     @property
510     def console_socket(self):
511         """
512         Returns a socket connected to the console
513         """
514         if self._console_socket is None:
515             self._console_socket = socket.socket(socket.AF_UNIX,
516                                                  socket.SOCK_STREAM)
517             self._console_socket.connect(self._console_address)
518         return self._console_socket
This page took 0.056966 seconds and 4 git commands to generate.