blob: a4631d69341addacfdb29e06d4b6bd82054a7ab1 [file] [log] [blame]
John Snow306dfcd2019-06-27 17:28:15 -04001"""
2QEMU machine module:
3
4The machine module primarily provides the QEMUMachine class,
5which provides facilities for managing the lifetime of a QEMU VM.
6"""
7
John Snowabf0bf92019-06-27 17:28:14 -04008# Copyright (C) 2015-2016 Red Hat Inc.
9# Copyright (C) 2012 IBM Corp.
10#
11# Authors:
12# Fam Zheng <famz@redhat.com>
13#
14# This work is licensed under the terms of the GNU GPL, version 2. See
15# the COPYING file in the top-level directory.
16#
17# Based on qmp.py.
18#
19
20import errno
21import logging
22import os
23import subprocess
John Snowabf0bf92019-06-27 17:28:14 -040024import shutil
25import socket
26import tempfile
27
28from . import qmp
29
30LOG = logging.getLogger(__name__)
31
32class QEMUMachineError(Exception):
33 """
34 Exception called when an error in QEMUMachine happens.
35 """
36
37
38class QEMUMachineAddDeviceError(QEMUMachineError):
39 """
40 Exception raised when a request to add a device can not be fulfilled
41
42 The failures are caused by limitations, lack of information or conflicting
43 requests on the QEMUMachine methods. This exception does not represent
44 failures reported by the QEMU binary itself.
45 """
46
47
48class MonitorResponseError(qmp.QMPError):
49 """
50 Represents erroneous QMP monitor reply
51 """
52 def __init__(self, reply):
53 try:
54 desc = reply["error"]["desc"]
55 except KeyError:
56 desc = reply
57 super(MonitorResponseError, self).__init__(desc)
58 self.reply = reply
59
60
61class QEMUMachine(object):
62 """
63 A QEMU VM
64
65 Use this object as a context manager to ensure the QEMU process terminates::
66
67 with VM(binary) as vm:
68 ...
69 # vm is guaranteed to be shut down here
70 """
71
72 def __init__(self, binary, args=None, wrapper=None, name=None,
73 test_dir="/var/tmp", monitor_address=None,
Max Reitz32558ce2019-10-17 15:31:34 +020074 socket_scm_helper=None, sock_dir=None):
John Snowabf0bf92019-06-27 17:28:14 -040075 '''
76 Initialize a QEMUMachine
77
78 @param binary: path to the qemu binary
79 @param args: list of extra arguments
80 @param wrapper: list of arguments used as prefix to qemu binary
81 @param name: prefix for socket and log file names (default: qemu-PID)
82 @param test_dir: where to create socket and log file
83 @param monitor_address: address for QMP monitor
84 @param socket_scm_helper: helper program, required for send_fd_scm()
85 @note: Qemu process is not started until launch() is used.
86 '''
87 if args is None:
88 args = []
89 if wrapper is None:
90 wrapper = []
91 if name is None:
92 name = "qemu-%d" % os.getpid()
Max Reitz32558ce2019-10-17 15:31:34 +020093 if sock_dir is None:
94 sock_dir = test_dir
John Snowabf0bf92019-06-27 17:28:14 -040095 self._name = name
96 self._monitor_address = monitor_address
97 self._vm_monitor = None
98 self._qemu_log_path = None
99 self._qemu_log_file = None
100 self._popen = None
101 self._binary = binary
102 self._args = list(args) # Force copy args in case we modify them
103 self._wrapper = wrapper
104 self._events = []
105 self._iolog = None
106 self._socket_scm_helper = socket_scm_helper
107 self._qmp = None
108 self._qemu_full_args = None
109 self._test_dir = test_dir
110 self._temp_dir = None
Max Reitz32558ce2019-10-17 15:31:34 +0200111 self._sock_dir = sock_dir
John Snowabf0bf92019-06-27 17:28:14 -0400112 self._launched = False
113 self._machine = None
114 self._console_set = False
115 self._console_device_type = None
116 self._console_address = None
117 self._console_socket = None
Max Reitz32558ce2019-10-17 15:31:34 +0200118 self._remove_files = []
John Snowabf0bf92019-06-27 17:28:14 -0400119
120 # just in case logging wasn't configured by the main script:
121 logging.basicConfig()
122
123 def __enter__(self):
124 return self
125
126 def __exit__(self, exc_type, exc_val, exc_tb):
127 self.shutdown()
128 return False
129
John Snowabf0bf92019-06-27 17:28:14 -0400130 def add_monitor_null(self):
John Snow306dfcd2019-06-27 17:28:15 -0400131 """
132 This can be used to add an unused monitor instance.
133 """
John Snowabf0bf92019-06-27 17:28:14 -0400134 self._args.append('-monitor')
135 self._args.append('null')
136
137 def add_fd(self, fd, fdset, opaque, opts=''):
138 """
139 Pass a file descriptor to the VM
140 """
141 options = ['fd=%d' % fd,
142 'set=%d' % fdset,
143 'opaque=%s' % opaque]
144 if opts:
145 options.append(opts)
146
147 # This did not exist before 3.4, but since then it is
148 # mandatory for our purpose
149 if hasattr(os, 'set_inheritable'):
150 os.set_inheritable(fd, True)
151
152 self._args.append('-add-fd')
153 self._args.append(','.join(options))
154 return self
155
John Snowabf0bf92019-06-27 17:28:14 -0400156 def send_fd_scm(self, fd=None, file_path=None):
John Snow306dfcd2019-06-27 17:28:15 -0400157 """
158 Send an fd or file_path to socket_scm_helper.
159
160 Exactly one of fd and file_path must be given.
161 If it is file_path, the helper will open that file and pass its own fd.
162 """
John Snowabf0bf92019-06-27 17:28:14 -0400163 # In iotest.py, the qmp should always use unix socket.
164 assert self._qmp.is_scm_available()
165 if self._socket_scm_helper is None:
166 raise QEMUMachineError("No path to socket_scm_helper set")
167 if not os.path.exists(self._socket_scm_helper):
168 raise QEMUMachineError("%s does not exist" %
169 self._socket_scm_helper)
170
171 # This did not exist before 3.4, but since then it is
172 # mandatory for our purpose
173 if hasattr(os, 'set_inheritable'):
174 os.set_inheritable(self._qmp.get_sock_fd(), True)
175 if fd is not None:
176 os.set_inheritable(fd, True)
177
178 fd_param = ["%s" % self._socket_scm_helper,
179 "%d" % self._qmp.get_sock_fd()]
180
181 if file_path is not None:
182 assert fd is None
183 fd_param.append(file_path)
184 else:
185 assert fd is not None
186 fd_param.append(str(fd))
187
188 devnull = open(os.path.devnull, 'rb')
189 proc = subprocess.Popen(fd_param, stdin=devnull, stdout=subprocess.PIPE,
190 stderr=subprocess.STDOUT, close_fds=False)
191 output = proc.communicate()[0]
192 if output:
193 LOG.debug(output)
194
195 return proc.returncode
196
197 @staticmethod
198 def _remove_if_exists(path):
199 """
200 Remove file object at path if it exists
201 """
202 try:
203 os.remove(path)
204 except OSError as exception:
205 if exception.errno == errno.ENOENT:
206 return
207 raise
208
209 def is_running(self):
John Snow306dfcd2019-06-27 17:28:15 -0400210 """Returns true if the VM is running."""
John Snowabf0bf92019-06-27 17:28:14 -0400211 return self._popen is not None and self._popen.poll() is None
212
213 def exitcode(self):
John Snow306dfcd2019-06-27 17:28:15 -0400214 """Returns the exit code if possible, or None."""
John Snowabf0bf92019-06-27 17:28:14 -0400215 if self._popen is None:
216 return None
217 return self._popen.poll()
218
219 def get_pid(self):
John Snow306dfcd2019-06-27 17:28:15 -0400220 """Returns the PID of the running process, or None."""
John Snowabf0bf92019-06-27 17:28:14 -0400221 if not self.is_running():
222 return None
223 return self._popen.pid
224
225 def _load_io_log(self):
226 if self._qemu_log_path is not None:
227 with open(self._qemu_log_path, "r") as iolog:
228 self._iolog = iolog.read()
229
230 def _base_args(self):
231 if isinstance(self._monitor_address, tuple):
232 moncdev = "socket,id=mon,host=%s,port=%s" % (
233 self._monitor_address[0],
234 self._monitor_address[1])
235 else:
236 moncdev = 'socket,id=mon,path=%s' % self._vm_monitor
237 args = ['-chardev', moncdev,
238 '-mon', 'chardev=mon,mode=control',
239 '-display', 'none', '-vga', 'none']
240 if self._machine is not None:
241 args.extend(['-machine', self._machine])
242 if self._console_set:
Max Reitz32558ce2019-10-17 15:31:34 +0200243 self._console_address = os.path.join(self._sock_dir,
John Snowabf0bf92019-06-27 17:28:14 -0400244 self._name + "-console.sock")
Max Reitz32558ce2019-10-17 15:31:34 +0200245 self._remove_files.append(self._console_address)
John Snowabf0bf92019-06-27 17:28:14 -0400246 chardev = ('socket,id=console,path=%s,server,nowait' %
247 self._console_address)
248 args.extend(['-chardev', chardev])
249 if self._console_device_type is None:
250 args.extend(['-serial', 'chardev:console'])
251 else:
252 device = '%s,chardev=console' % self._console_device_type
253 args.extend(['-device', device])
254 return args
255
256 def _pre_launch(self):
257 self._temp_dir = tempfile.mkdtemp(dir=self._test_dir)
258 if self._monitor_address is not None:
259 self._vm_monitor = self._monitor_address
260 else:
Max Reitz32558ce2019-10-17 15:31:34 +0200261 self._vm_monitor = os.path.join(self._sock_dir,
John Snowabf0bf92019-06-27 17:28:14 -0400262 self._name + "-monitor.sock")
Max Reitz32558ce2019-10-17 15:31:34 +0200263 self._remove_files.append(self._vm_monitor)
John Snowabf0bf92019-06-27 17:28:14 -0400264 self._qemu_log_path = os.path.join(self._temp_dir, self._name + ".log")
265 self._qemu_log_file = open(self._qemu_log_path, 'wb')
266
267 self._qmp = qmp.QEMUMonitorProtocol(self._vm_monitor,
268 server=True)
269
270 def _post_launch(self):
271 self._qmp.accept()
272
273 def _post_shutdown(self):
274 if self._qemu_log_file is not None:
275 self._qemu_log_file.close()
276 self._qemu_log_file = None
277
278 self._qemu_log_path = None
279
John Snowabf0bf92019-06-27 17:28:14 -0400280 if self._temp_dir is not None:
281 shutil.rmtree(self._temp_dir)
282 self._temp_dir = None
283
Max Reitz32558ce2019-10-17 15:31:34 +0200284 while len(self._remove_files) > 0:
285 self._remove_if_exists(self._remove_files.pop())
286
John Snowabf0bf92019-06-27 17:28:14 -0400287 def launch(self):
288 """
289 Launch the VM and make sure we cleanup and expose the
290 command line/output in case of exception
291 """
292
293 if self._launched:
294 raise QEMUMachineError('VM already launched')
295
296 self._iolog = None
297 self._qemu_full_args = None
298 try:
299 self._launch()
300 self._launched = True
301 except:
302 self.shutdown()
303
304 LOG.debug('Error launching VM')
305 if self._qemu_full_args:
306 LOG.debug('Command: %r', ' '.join(self._qemu_full_args))
307 if self._iolog:
308 LOG.debug('Output: %r', self._iolog)
309 raise
310
311 def _launch(self):
312 """
313 Launch the VM and establish a QMP connection
314 """
315 devnull = open(os.path.devnull, 'rb')
316 self._pre_launch()
317 self._qemu_full_args = (self._wrapper + [self._binary] +
318 self._base_args() + self._args)
319 LOG.debug('VM launch command: %r', ' '.join(self._qemu_full_args))
320 self._popen = subprocess.Popen(self._qemu_full_args,
321 stdin=devnull,
322 stdout=self._qemu_log_file,
323 stderr=subprocess.STDOUT,
324 shell=False,
325 close_fds=False)
326 self._post_launch()
327
328 def wait(self):
329 """
330 Wait for the VM to power off
331 """
332 self._popen.wait()
333 self._qmp.close()
334 self._load_io_log()
335 self._post_shutdown()
336
Max Reitz46871332019-07-19 11:26:17 +0200337 def shutdown(self, has_quit=False):
John Snowabf0bf92019-06-27 17:28:14 -0400338 """
339 Terminate the VM and clean up
340 """
Cleber Rosa08580962019-10-28 19:04:04 -0400341 # If we keep the console socket open, we may deadlock waiting
342 # for QEMU to exit, while QEMU is waiting for the socket to
343 # become writeable.
344 if self._console_socket is not None:
345 self._console_socket.close()
346 self._console_socket = None
347
John Snowabf0bf92019-06-27 17:28:14 -0400348 if self.is_running():
349 try:
Max Reitz46871332019-07-19 11:26:17 +0200350 if not has_quit:
351 self._qmp.cmd('quit')
John Snowabf0bf92019-06-27 17:28:14 -0400352 self._qmp.close()
353 except:
354 self._popen.kill()
355 self._popen.wait()
356
357 self._load_io_log()
358 self._post_shutdown()
359
360 exitcode = self.exitcode()
361 if exitcode is not None and exitcode < 0:
362 msg = 'qemu received signal %i: %s'
363 if self._qemu_full_args:
364 command = ' '.join(self._qemu_full_args)
365 else:
366 command = ''
John Snow306dfcd2019-06-27 17:28:15 -0400367 LOG.warning(msg, -exitcode, command)
John Snowabf0bf92019-06-27 17:28:14 -0400368
369 self._launched = False
370
371 def qmp(self, cmd, conv_keys=True, **args):
372 """
373 Invoke a QMP command and return the response dict
374 """
375 qmp_args = dict()
376 for key, value in args.items():
377 if conv_keys:
378 qmp_args[key.replace('_', '-')] = value
379 else:
380 qmp_args[key] = value
381
382 return self._qmp.cmd(cmd, args=qmp_args)
383
384 def command(self, cmd, conv_keys=True, **args):
385 """
386 Invoke a QMP command.
387 On success return the response dict.
388 On failure raise an exception.
389 """
390 reply = self.qmp(cmd, conv_keys, **args)
391 if reply is None:
392 raise qmp.QMPError("Monitor is closed")
393 if "error" in reply:
394 raise MonitorResponseError(reply)
395 return reply["return"]
396
397 def get_qmp_event(self, wait=False):
398 """
399 Poll for one queued QMP events and return it
400 """
John Snow306dfcd2019-06-27 17:28:15 -0400401 if self._events:
John Snowabf0bf92019-06-27 17:28:14 -0400402 return self._events.pop(0)
403 return self._qmp.pull_event(wait=wait)
404
405 def get_qmp_events(self, wait=False):
406 """
407 Poll for queued QMP events and return a list of dicts
408 """
409 events = self._qmp.get_events(wait=wait)
410 events.extend(self._events)
411 del self._events[:]
412 self._qmp.clear_events()
413 return events
414
415 @staticmethod
416 def event_match(event, match=None):
417 """
418 Check if an event matches optional match criteria.
419
420 The match criteria takes the form of a matching subdict. The event is
421 checked to be a superset of the subdict, recursively, with matching
422 values whenever the subdict values are not None.
423
424 This has a limitation that you cannot explicitly check for None values.
425
426 Examples, with the subdict queries on the left:
427 - None matches any object.
428 - {"foo": None} matches {"foo": {"bar": 1}}
429 - {"foo": None} matches {"foo": 5}
430 - {"foo": {"abc": None}} does not match {"foo": {"bar": 1}}
431 - {"foo": {"rab": 2}} matches {"foo": {"bar": 1, "rab": 2}}
432 """
433 if match is None:
434 return True
435
436 try:
437 for key in match:
438 if key in event:
439 if not QEMUMachine.event_match(event[key], match[key]):
440 return False
441 else:
442 return False
443 return True
444 except TypeError:
445 # either match or event wasn't iterable (not a dict)
446 return match == event
447
448 def event_wait(self, name, timeout=60.0, match=None):
449 """
450 event_wait waits for and returns a named event from QMP with a timeout.
451
452 name: The event to wait for.
453 timeout: QEMUMonitorProtocol.pull_event timeout parameter.
454 match: Optional match criteria. See event_match for details.
455 """
456 return self.events_wait([(name, match)], timeout)
457
458 def events_wait(self, events, timeout=60.0):
459 """
460 events_wait waits for and returns a named event from QMP with a timeout.
461
462 events: a sequence of (name, match_criteria) tuples.
463 The match criteria are optional and may be None.
464 See event_match for details.
465 timeout: QEMUMonitorProtocol.pull_event timeout parameter.
466 """
467 def _match(event):
468 for name, match in events:
John Snow306dfcd2019-06-27 17:28:15 -0400469 if event['event'] == name and self.event_match(event, match):
John Snowabf0bf92019-06-27 17:28:14 -0400470 return True
471 return False
472
473 # Search cached events
474 for event in self._events:
475 if _match(event):
476 self._events.remove(event)
477 return event
478
479 # Poll for new events
480 while True:
481 event = self._qmp.pull_event(wait=timeout)
482 if _match(event):
483 return event
484 self._events.append(event)
485
486 return None
487
488 def get_log(self):
489 """
490 After self.shutdown or failed qemu execution, this returns the output
491 of the qemu process.
492 """
493 return self._iolog
494
495 def add_args(self, *args):
496 """
497 Adds to the list of extra arguments to be given to the QEMU binary
498 """
499 self._args.extend(args)
500
501 def set_machine(self, machine_type):
502 """
503 Sets the machine type
504
505 If set, the machine type will be added to the base arguments
506 of the resulting QEMU command line.
507 """
508 self._machine = machine_type
509
510 def set_console(self, device_type=None):
511 """
512 Sets the device type for a console device
513
514 If set, the console device and a backing character device will
515 be added to the base arguments of the resulting QEMU command
516 line.
517
518 This is a convenience method that will either use the provided
519 device type, or default to a "-serial chardev:console" command
520 line argument.
521
522 The actual setting of command line arguments will be be done at
523 machine launch time, as it depends on the temporary directory
524 to be created.
525
526 @param device_type: the device type, such as "isa-serial". If
527 None is given (the default value) a "-serial
528 chardev:console" command line argument will
529 be used instead, resorting to the machine's
530 default device type.
531 """
532 self._console_set = True
533 self._console_device_type = device_type
534
535 @property
536 def console_socket(self):
537 """
538 Returns a socket connected to the console
539 """
540 if self._console_socket is None:
541 self._console_socket = socket.socket(socket.AF_UNIX,
542 socket.SOCK_STREAM)
543 self._console_socket.connect(self._console_address)
544 return self._console_socket