blob: 2024e8b1b10bed826b3703d46ffed692e147b4a4 [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
280 if self._console_socket is not None:
281 self._console_socket.close()
282 self._console_socket = None
283
284 if self._temp_dir is not None:
285 shutil.rmtree(self._temp_dir)
286 self._temp_dir = None
287
Max Reitz32558ce2019-10-17 15:31:34 +0200288 while len(self._remove_files) > 0:
289 self._remove_if_exists(self._remove_files.pop())
290
John Snowabf0bf92019-06-27 17:28:14 -0400291 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
Max Reitz46871332019-07-19 11:26:17 +0200341 def shutdown(self, has_quit=False):
John Snowabf0bf92019-06-27 17:28:14 -0400342 """
343 Terminate the VM and clean up
344 """
345 if self.is_running():
346 try:
Max Reitz46871332019-07-19 11:26:17 +0200347 if not has_quit:
348 self._qmp.cmd('quit')
John Snowabf0bf92019-06-27 17:28:14 -0400349 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 = ''
John Snow306dfcd2019-06-27 17:28:15 -0400364 LOG.warning(msg, -exitcode, command)
John Snowabf0bf92019-06-27 17:28:14 -0400365
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 """
John Snow306dfcd2019-06-27 17:28:15 -0400398 if self._events:
John Snowabf0bf92019-06-27 17:28:14 -0400399 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 @staticmethod
413 def event_match(event, match=None):
414 """
415 Check if an event matches optional match criteria.
416
417 The match criteria takes the form of a matching subdict. The event is
418 checked to be a superset of the subdict, recursively, with matching
419 values whenever the subdict values are not None.
420
421 This has a limitation that you cannot explicitly check for None values.
422
423 Examples, with the subdict queries on the left:
424 - None matches any object.
425 - {"foo": None} matches {"foo": {"bar": 1}}
426 - {"foo": None} matches {"foo": 5}
427 - {"foo": {"abc": None}} does not match {"foo": {"bar": 1}}
428 - {"foo": {"rab": 2}} matches {"foo": {"bar": 1, "rab": 2}}
429 """
430 if match is None:
431 return True
432
433 try:
434 for key in match:
435 if key in event:
436 if not QEMUMachine.event_match(event[key], match[key]):
437 return False
438 else:
439 return False
440 return True
441 except TypeError:
442 # either match or event wasn't iterable (not a dict)
443 return match == event
444
445 def event_wait(self, name, timeout=60.0, match=None):
446 """
447 event_wait waits for and returns a named event from QMP with a timeout.
448
449 name: The event to wait for.
450 timeout: QEMUMonitorProtocol.pull_event timeout parameter.
451 match: Optional match criteria. See event_match for details.
452 """
453 return self.events_wait([(name, match)], timeout)
454
455 def events_wait(self, events, timeout=60.0):
456 """
457 events_wait waits for and returns a named event from QMP with a timeout.
458
459 events: a sequence of (name, match_criteria) tuples.
460 The match criteria are optional and may be None.
461 See event_match for details.
462 timeout: QEMUMonitorProtocol.pull_event timeout parameter.
463 """
464 def _match(event):
465 for name, match in events:
John Snow306dfcd2019-06-27 17:28:15 -0400466 if event['event'] == name and self.event_match(event, match):
John Snowabf0bf92019-06-27 17:28:14 -0400467 return True
468 return False
469
470 # Search cached events
471 for event in self._events:
472 if _match(event):
473 self._events.remove(event)
474 return event
475
476 # Poll for new events
477 while True:
478 event = self._qmp.pull_event(wait=timeout)
479 if _match(event):
480 return event
481 self._events.append(event)
482
483 return None
484
485 def get_log(self):
486 """
487 After self.shutdown or failed qemu execution, this returns the output
488 of the qemu process.
489 """
490 return self._iolog
491
492 def add_args(self, *args):
493 """
494 Adds to the list of extra arguments to be given to the QEMU binary
495 """
496 self._args.extend(args)
497
498 def set_machine(self, machine_type):
499 """
500 Sets the machine type
501
502 If set, the machine type will be added to the base arguments
503 of the resulting QEMU command line.
504 """
505 self._machine = machine_type
506
507 def set_console(self, device_type=None):
508 """
509 Sets the device type for a console device
510
511 If set, the console device and a backing character device will
512 be added to the base arguments of the resulting QEMU command
513 line.
514
515 This is a convenience method that will either use the provided
516 device type, or default to a "-serial chardev:console" command
517 line argument.
518
519 The actual setting of command line arguments will be be done at
520 machine launch time, as it depends on the temporary directory
521 to be created.
522
523 @param device_type: the device type, such as "isa-serial". If
524 None is given (the default value) a "-serial
525 chardev:console" command line argument will
526 be used instead, resorting to the machine's
527 default device type.
528 """
529 self._console_set = True
530 self._console_device_type = device_type
531
532 @property
533 def console_socket(self):
534 """
535 Returns a socket connected to the console
536 """
537 if self._console_socket is None:
538 self._console_socket = socket.socket(socket.AF_UNIX,
539 socket.SOCK_STREAM)
540 self._console_socket.connect(self._console_address)
541 return self._console_socket