scripts/qemu.py: log QEMU launch command line
[qemu/ar7.git] / python / qemu / __init__.py
blob585cd2a1a393006c248d570b695dcdbce8a505ca
1 # QEMU library
3 # Copyright (C) 2015-2016 Red Hat Inc.
4 # Copyright (C) 2012 IBM Corp.
6 # Authors:
7 # Fam Zheng <famz@redhat.com>
9 # This work is licensed under the terms of the GNU GPL, version 2. See
10 # the COPYING file in the top-level directory.
12 # Based on qmp.py.
15 import errno
16 import logging
17 import os
18 import subprocess
19 import re
20 import shutil
21 import socket
22 import tempfile
24 from . import qmp
27 LOG = logging.getLogger(__name__)
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"
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)
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',
55 class QEMUMachineError(Exception):
56 """
57 Exception called when an error in QEMUMachine happens.
58 """
61 class QEMUMachineAddDeviceError(QEMUMachineError):
62 """
63 Exception raised when a request to add a device can not be fulfilled
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 """
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
83 class QEMUMachine(object):
84 """
85 A QEMU VM
87 Use this object as a context manager to ensure the QEMU process terminates::
89 with VM(binary) as vm:
90 ...
91 # vm is guaranteed to be shut down here
92 """
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
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.
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
137 # just in case logging wasn't configured by the main script:
138 logging.basicConfig()
140 def __enter__(self):
141 return self
143 def __exit__(self, exc_type, exc_val, exc_tb):
144 self.shutdown()
145 return False
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)
153 def add_fd(self, fd, fdset, opaque, opts=''):
155 Pass a file descriptor to the VM
157 options = ['fd=%d' % fd,
158 'set=%d' % fdset,
159 'opaque=%s' % opaque]
160 if opts:
161 options.append(opts)
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)
168 self._args.append('-add-fd')
169 self._args.append(','.join(options))
170 return self
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)
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)
191 fd_param = ["%s" % self._socket_scm_helper,
192 "%d" % self._qmp.get_sock_fd()]
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))
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)
208 return proc.returncode
210 @staticmethod
211 def _remove_if_exists(path):
213 Remove file object at path if it exists
215 try:
216 os.remove(path)
217 except OSError as exception:
218 if exception.errno == errno.ENOENT:
219 return
220 raise
222 def is_running(self):
223 return self._popen is not None and self._popen.poll() is None
225 def exitcode(self):
226 if self._popen is None:
227 return None
228 return self._popen.poll()
230 def get_pid(self):
231 if not self.is_running():
232 return None
233 return self._popen.pid
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()
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
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')
271 self._qmp = qmp.QEMUMonitorProtocol(self._vm_monitor,
272 server=True)
274 def _post_launch(self):
275 self._qmp.accept()
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
282 self._qemu_log_path = None
284 if self._console_socket is not None:
285 self._console_socket.close()
286 self._console_socket = None
288 if self._temp_dir is not None:
289 shutil.rmtree(self._temp_dir)
290 self._temp_dir = None
292 def launch(self):
294 Launch the VM and make sure we cleanup and expose the
295 command line/output in case of exception
298 if self._launched:
299 raise QEMUMachineError('VM already launched')
301 self._iolog = None
302 self._qemu_full_args = None
303 try:
304 self._launch()
305 self._launched = True
306 except:
307 self.shutdown()
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
316 def _launch(self):
318 Launch the VM and establish a QMP connection
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()
333 def wait(self):
335 Wait for the VM to power off
337 self._popen.wait()
338 self._qmp.close()
339 self._load_io_log()
340 self._post_shutdown()
342 def shutdown(self):
344 Terminate the VM and clean up
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()
354 self._load_io_log()
355 self._post_shutdown()
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)
366 self._launched = False
368 def qmp(self, cmd, conv_keys=True, **args):
370 Invoke a QMP command and return the response dict
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
379 return self._qmp.cmd(cmd, args=qmp_args)
381 def command(self, cmd, conv_keys=True, **args):
383 Invoke a QMP command.
384 On success return the response dict.
385 On failure raise an exception.
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"]
394 def get_qmp_event(self, wait=False):
396 Poll for one queued QMP events and return it
398 if len(self._events) > 0:
399 return self._events.pop(0)
400 return self._qmp.pull_event(wait=wait)
402 def get_qmp_events(self, wait=False):
404 Poll for queued QMP events and return a list of dicts
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
412 def event_wait(self, name, timeout=60.0, match=None):
414 Wait for specified timeout on named event in QMP; optionally filter
415 results by match.
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}}
422 def event_match(event, match=None):
423 if match is None:
424 return True
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
436 return True
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
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)
451 return None
453 def get_log(self):
455 After self.shutdown or failed qemu execution, this returns the output
456 of the qemu process.
458 return self._iolog
460 def add_args(self, *args):
462 Adds to the list of extra arguments to be given to the QEMU binary
464 self._args.extend(args)
466 def set_machine(self, machine_type):
468 Sets the machine type
470 If set, the machine type will be added to the base arguments
471 of the resulting QEMU command line.
473 self._machine = machine_type
475 def set_console(self, device_type=None):
477 Sets the device type for a console device
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.
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.
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.
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.
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
510 @property
511 def console_socket(self):
513 Returns a socket connected to the console
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