3 # Copyright (C) 2015-2016 Red Hat Inc.
4 # Copyright (C) 2012 IBM Corp.
9 # This work is licensed under the terms of the GNU GPL, version 2. See
10 # the COPYING file in the top-level directory.
27 LOG = logging.getLogger(__name__)
29 # Mapping host architecture to any additional architectures it can
30 # support which often includes its 32 bit cousin.
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):
41 return os.access("/dev/kvm", os.R_OK | os.W_OK)
44 #: Maps machine types to the preferred console device 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):
57 Exception called when an error in QEMUMachine happens.
61 class QEMUMachineAddDeviceError(QEMUMachineError):
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.
70 class MonitorResponseError(qmp.QMPError):
72 Represents erroneous QMP monitor reply
74 def __init__(self, reply):
76 desc = reply["error"]["desc"]
79 super(MonitorResponseError, self).__init__(desc)
83 class QEMUMachine(object):
87 Use this object as a context manager to ensure the QEMU process terminates::
89 with VM(binary) as vm:
91 # vm is guaranteed to be shut down here
94 def __init__(self, binary, args=None, wrapper=None, name=None,
95 test_dir="/var/tmp", monitor_address=None,
96 socket_scm_helper=None):
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.
114 name = "qemu-%d" % os.getpid()
116 self._monitor_address = monitor_address
117 self._vm_monitor = None
118 self._qemu_log_path = None
119 self._qemu_log_file = None
121 self._binary = binary
122 self._args = list(args) # Force copy args in case we modify them
123 self._wrapper = wrapper
126 self._socket_scm_helper = socket_scm_helper
128 self._qemu_full_args = None
129 self._test_dir = test_dir
130 self._temp_dir = None
131 self._launched = False
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()
143 def __exit__(self, exc_type, exc_val, exc_tb):
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')
152 def add_fd(self, fd, fdset, opaque, opts=''):
154 Pass a file descriptor to the VM
156 options = ['fd=%d' % fd,
158 'opaque=%s' % opaque]
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)
167 self._args.append('-add-fd')
168 self._args.append(','.join(options))
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
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)
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)
188 os.set_inheritable(fd, True)
190 fd_param = ["%s" % self._socket_scm_helper,
191 "%d" % self._qmp.get_sock_fd()]
193 if file_path is not None:
195 fd_param.append(file_path)
197 assert fd is not None
198 fd_param.append(str(fd))
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]
207 return proc.returncode
210 def _remove_if_exists(path):
212 Remove file object at path if it exists
216 except OSError as exception:
217 if exception.errno == errno.ENOENT:
221 def is_running(self):
222 return self._popen is not None and self._popen.poll() is None
225 if self._popen is None:
227 return self._popen.poll()
230 if not self.is_running():
232 return self._popen.pid
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()
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])
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])
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
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')
270 self._qmp = qmp.QEMUMonitorProtocol(self._vm_monitor,
273 def _post_launch(self):
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
281 self._qemu_log_path = None
283 if self._console_socket is not None:
284 self._console_socket.close()
285 self._console_socket = None
287 if self._temp_dir is not None:
288 shutil.rmtree(self._temp_dir)
289 self._temp_dir = None
293 Launch the VM and make sure we cleanup and expose the
294 command line/output in case of exception
298 raise QEMUMachineError('VM already launched')
301 self._qemu_full_args = None
304 self._launched = True
308 LOG.debug('Error launching VM')
309 if self._qemu_full_args:
310 LOG.debug('Command: %r', ' '.join(self._qemu_full_args))
312 LOG.debug('Output: %r', self._iolog)
317 Launch the VM and establish a QMP connection
319 devnull = open(os.path.devnull, 'rb')
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,
326 stdout=self._qemu_log_file,
327 stderr=subprocess.STDOUT,
334 Wait for the VM to power off
339 self._post_shutdown()
343 Terminate the VM and clean up
345 if self.is_running():
347 self._qmp.cmd('quit')
354 self._post_shutdown()
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)
363 LOG.warn(msg, -exitcode, command)
365 self._launched = False
367 def qmp(self, cmd, conv_keys=True, **args):
369 Invoke a QMP command and return the response dict
372 for key, value in args.items():
374 qmp_args[key.replace('_', '-')] = value
376 qmp_args[key] = value
378 return self._qmp.cmd(cmd, args=qmp_args)
380 def command(self, cmd, conv_keys=True, **args):
382 Invoke a QMP command.
383 On success return the response dict.
384 On failure raise an exception.
386 reply = self.qmp(cmd, conv_keys, **args)
388 raise qmp.QMPError("Monitor is closed")
390 raise MonitorResponseError(reply)
391 return reply["return"]
393 def get_qmp_event(self, wait=False):
395 Poll for one queued QMP events and return it
397 if len(self._events) > 0:
398 return self._events.pop(0)
399 return self._qmp.pull_event(wait=wait)
401 def get_qmp_events(self, wait=False):
403 Poll for queued QMP events and return a list of dicts
405 events = self._qmp.get_events(wait=wait)
406 events.extend(self._events)
408 self._qmp.clear_events()
411 def event_wait(self, name, timeout=60.0, match=None):
413 Wait for specified timeout on named event in QMP; optionally filter
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}}
421 def event_match(event, match=None):
427 if isinstance(event[key], dict):
428 if not event_match(event[key], match[key]):
430 elif event[key] != match[key]:
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)
443 # Poll for new events
445 event = self._qmp.pull_event(wait=timeout)
446 if (event['event'] == name) and event_match(event, match):
448 self._events.append(event)
454 After self.shutdown or failed qemu execution, this returns the output
459 def add_args(self, *args):
461 Adds to the list of extra arguments to be given to the QEMU binary
463 self._args.extend(args)
465 def set_machine(self, machine_type):
467 Sets the machine type
469 If set, the machine type will be added to the base arguments
470 of the resulting QEMU command line.
472 self._machine = machine_type
474 def set_console(self, device_type=None):
476 Sets the device type for a console device
478 If set, the console device and a backing character device will
479 be added to the base arguments of the resulting QEMU command
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.
486 The actual setting of command line arguments will be be done at
487 machine launch time, as it depends on the temporary directory
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.
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):
503 if device_type is None:
504 raise QEMUMachineAddDeviceError("Can not add a console device:"
505 " no matching console device "
507 self._console_device_type = device_type
510 def console_socket(self):
512 Returns a socket connected to the console
514 if self._console_socket is None:
515 self._console_socket = socket.socket(socket.AF_UNIX,
517 self._console_socket.connect(self._console_address)
518 return self._console_socket