blob: 8e4ecd1837f1501d63eff0da34552d236e634298 [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
John Snow9b8ccd62020-05-28 18:21:28 -040061class QEMUMachine:
John Snowabf0bf92019-06-27 17:28:14 -040062 """
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
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500107 self._qmp_set = True # Enable QMP monitor by default.
John Snowabf0bf92019-06-27 17:28:14 -0400108 self._qmp = None
109 self._qemu_full_args = None
110 self._test_dir = test_dir
111 self._temp_dir = None
Max Reitz32558ce2019-10-17 15:31:34 +0200112 self._sock_dir = sock_dir
John Snowabf0bf92019-06-27 17:28:14 -0400113 self._launched = False
114 self._machine = None
Philippe Mathieu-Daudé746f2442020-01-21 00:51:56 +0100115 self._console_index = 0
John Snowabf0bf92019-06-27 17:28:14 -0400116 self._console_set = False
117 self._console_device_type = None
118 self._console_address = None
119 self._console_socket = None
Max Reitz32558ce2019-10-17 15:31:34 +0200120 self._remove_files = []
John Snowabf0bf92019-06-27 17:28:14 -0400121
John Snowabf0bf92019-06-27 17:28:14 -0400122 def __enter__(self):
123 return self
124
125 def __exit__(self, exc_type, exc_val, exc_tb):
126 self.shutdown()
127 return False
128
John Snowabf0bf92019-06-27 17:28:14 -0400129 def add_monitor_null(self):
John Snow306dfcd2019-06-27 17:28:15 -0400130 """
131 This can be used to add an unused monitor instance.
132 """
John Snowabf0bf92019-06-27 17:28:14 -0400133 self._args.append('-monitor')
134 self._args.append('null')
135
136 def add_fd(self, fd, fdset, opaque, opts=''):
137 """
138 Pass a file descriptor to the VM
139 """
140 options = ['fd=%d' % fd,
141 'set=%d' % fdset,
142 'opaque=%s' % opaque]
143 if opts:
144 options.append(opts)
145
146 # This did not exist before 3.4, but since then it is
147 # mandatory for our purpose
148 if hasattr(os, 'set_inheritable'):
149 os.set_inheritable(fd, True)
150
151 self._args.append('-add-fd')
152 self._args.append(','.join(options))
153 return self
154
John Snowabf0bf92019-06-27 17:28:14 -0400155 def send_fd_scm(self, fd=None, file_path=None):
John Snow306dfcd2019-06-27 17:28:15 -0400156 """
157 Send an fd or file_path to socket_scm_helper.
158
159 Exactly one of fd and file_path must be given.
160 If it is file_path, the helper will open that file and pass its own fd.
161 """
John Snowabf0bf92019-06-27 17:28:14 -0400162 # In iotest.py, the qmp should always use unix socket.
163 assert self._qmp.is_scm_available()
164 if self._socket_scm_helper is None:
165 raise QEMUMachineError("No path to socket_scm_helper set")
166 if not os.path.exists(self._socket_scm_helper):
167 raise QEMUMachineError("%s does not exist" %
168 self._socket_scm_helper)
169
170 # This did not exist before 3.4, but since then it is
171 # mandatory for our purpose
172 if hasattr(os, 'set_inheritable'):
173 os.set_inheritable(self._qmp.get_sock_fd(), True)
174 if fd is not None:
175 os.set_inheritable(fd, True)
176
177 fd_param = ["%s" % self._socket_scm_helper,
178 "%d" % self._qmp.get_sock_fd()]
179
180 if file_path is not None:
181 assert fd is None
182 fd_param.append(file_path)
183 else:
184 assert fd is not None
185 fd_param.append(str(fd))
186
187 devnull = open(os.path.devnull, 'rb')
188 proc = subprocess.Popen(fd_param, stdin=devnull, stdout=subprocess.PIPE,
189 stderr=subprocess.STDOUT, close_fds=False)
190 output = proc.communicate()[0]
191 if output:
192 LOG.debug(output)
193
194 return proc.returncode
195
196 @staticmethod
197 def _remove_if_exists(path):
198 """
199 Remove file object at path if it exists
200 """
201 try:
202 os.remove(path)
203 except OSError as exception:
204 if exception.errno == errno.ENOENT:
205 return
206 raise
207
208 def is_running(self):
John Snow306dfcd2019-06-27 17:28:15 -0400209 """Returns true if the VM is running."""
John Snowabf0bf92019-06-27 17:28:14 -0400210 return self._popen is not None and self._popen.poll() is None
211
212 def exitcode(self):
John Snow306dfcd2019-06-27 17:28:15 -0400213 """Returns the exit code if possible, or None."""
John Snowabf0bf92019-06-27 17:28:14 -0400214 if self._popen is None:
215 return None
216 return self._popen.poll()
217
218 def get_pid(self):
John Snow306dfcd2019-06-27 17:28:15 -0400219 """Returns the PID of the running process, or None."""
John Snowabf0bf92019-06-27 17:28:14 -0400220 if not self.is_running():
221 return None
222 return self._popen.pid
223
224 def _load_io_log(self):
225 if self._qemu_log_path is not None:
226 with open(self._qemu_log_path, "r") as iolog:
227 self._iolog = iolog.read()
228
229 def _base_args(self):
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500230 args = ['-display', 'none', '-vga', 'none']
231 if self._qmp_set:
232 if isinstance(self._monitor_address, tuple):
233 moncdev = "socket,id=mon,host=%s,port=%s" % (
234 self._monitor_address[0],
235 self._monitor_address[1])
236 else:
237 moncdev = 'socket,id=mon,path=%s' % self._vm_monitor
238 args.extend(['-chardev', moncdev, '-mon',
239 'chardev=mon,mode=control'])
John Snowabf0bf92019-06-27 17:28:14 -0400240 if self._machine is not None:
241 args.extend(['-machine', self._machine])
John Snow9b8ccd62020-05-28 18:21:28 -0400242 for _ in range(self._console_index):
Philippe Mathieu-Daudé746f2442020-01-21 00:51:56 +0100243 args.extend(['-serial', 'null'])
John Snowabf0bf92019-06-27 17:28:14 -0400244 if self._console_set:
Max Reitz32558ce2019-10-17 15:31:34 +0200245 self._console_address = os.path.join(self._sock_dir,
John Snowabf0bf92019-06-27 17:28:14 -0400246 self._name + "-console.sock")
Max Reitz32558ce2019-10-17 15:31:34 +0200247 self._remove_files.append(self._console_address)
John Snowabf0bf92019-06-27 17:28:14 -0400248 chardev = ('socket,id=console,path=%s,server,nowait' %
249 self._console_address)
250 args.extend(['-chardev', chardev])
251 if self._console_device_type is None:
252 args.extend(['-serial', 'chardev:console'])
253 else:
254 device = '%s,chardev=console' % self._console_device_type
255 args.extend(['-device', device])
256 return args
257
258 def _pre_launch(self):
259 self._temp_dir = tempfile.mkdtemp(dir=self._test_dir)
John Snowabf0bf92019-06-27 17:28:14 -0400260 self._qemu_log_path = os.path.join(self._temp_dir, self._name + ".log")
261 self._qemu_log_file = open(self._qemu_log_path, 'wb')
262
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500263 if self._qmp_set:
264 if self._monitor_address is not None:
265 self._vm_monitor = self._monitor_address
266 else:
267 self._vm_monitor = os.path.join(self._sock_dir,
268 self._name + "-monitor.sock")
269 self._remove_files.append(self._vm_monitor)
Oksana Vohchana566054a2020-03-16 12:32:03 +0200270 self._qmp = qmp.QEMUMonitorProtocol(self._vm_monitor, server=True,
271 nickname=self._name)
John Snowabf0bf92019-06-27 17:28:14 -0400272
273 def _post_launch(self):
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500274 if self._qmp:
275 self._qmp.accept()
John Snowabf0bf92019-06-27 17:28:14 -0400276
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
281
282 self._qemu_log_path = None
283
John Snowabf0bf92019-06-27 17:28:14 -0400284 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()
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500337 if self._qmp:
338 self._qmp.close()
John Snowabf0bf92019-06-27 17:28:14 -0400339 self._load_io_log()
340 self._post_shutdown()
341
Vladimir Sementsov-Ogievskiye0e925a2020-02-17 18:02:42 +0300342 def shutdown(self, has_quit=False, hard=False):
John Snowabf0bf92019-06-27 17:28:14 -0400343 """
344 Terminate the VM and clean up
345 """
Cleber Rosa08580962019-10-28 19:04:04 -0400346 # If we keep the console socket open, we may deadlock waiting
347 # for QEMU to exit, while QEMU is waiting for the socket to
348 # become writeable.
349 if self._console_socket is not None:
350 self._console_socket.close()
351 self._console_socket = None
352
John Snowabf0bf92019-06-27 17:28:14 -0400353 if self.is_running():
Vladimir Sementsov-Ogievskiye0e925a2020-02-17 18:02:42 +0300354 if hard:
355 self._popen.kill()
356 elif self._qmp:
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500357 try:
358 if not has_quit:
359 self._qmp.cmd('quit')
360 self._qmp.close()
Kevin Wolfcd87f5e2020-03-13 09:36:16 +0100361 self._popen.wait(timeout=3)
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500362 except:
363 self._popen.kill()
John Snowabf0bf92019-06-27 17:28:14 -0400364 self._popen.wait()
365
366 self._load_io_log()
367 self._post_shutdown()
368
369 exitcode = self.exitcode()
Vladimir Sementsov-Ogievskiye0e925a2020-02-17 18:02:42 +0300370 if exitcode is not None and exitcode < 0 and \
371 not (exitcode == -9 and hard):
John Snowabf0bf92019-06-27 17:28:14 -0400372 msg = 'qemu received signal %i: %s'
373 if self._qemu_full_args:
374 command = ' '.join(self._qemu_full_args)
375 else:
376 command = ''
John Snow9b8ccd62020-05-28 18:21:28 -0400377 LOG.warning(msg, -int(exitcode), command)
John Snowabf0bf92019-06-27 17:28:14 -0400378
379 self._launched = False
380
Vladimir Sementsov-Ogievskiye0e925a2020-02-17 18:02:42 +0300381 def kill(self):
382 self.shutdown(hard=True)
383
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500384 def set_qmp_monitor(self, enabled=True):
385 """
386 Set the QMP monitor.
387
388 @param enabled: if False, qmp monitor options will be removed from
389 the base arguments of the resulting QEMU command
390 line. Default is True.
391 @note: call this function before launch().
392 """
393 if enabled:
394 self._qmp_set = True
395 else:
396 self._qmp_set = False
397 self._qmp = None
398
John Snowabf0bf92019-06-27 17:28:14 -0400399 def qmp(self, cmd, conv_keys=True, **args):
400 """
401 Invoke a QMP command and return the response dict
402 """
403 qmp_args = dict()
404 for key, value in args.items():
405 if conv_keys:
406 qmp_args[key.replace('_', '-')] = value
407 else:
408 qmp_args[key] = value
409
410 return self._qmp.cmd(cmd, args=qmp_args)
411
412 def command(self, cmd, conv_keys=True, **args):
413 """
414 Invoke a QMP command.
415 On success return the response dict.
416 On failure raise an exception.
417 """
418 reply = self.qmp(cmd, conv_keys, **args)
419 if reply is None:
420 raise qmp.QMPError("Monitor is closed")
421 if "error" in reply:
422 raise MonitorResponseError(reply)
423 return reply["return"]
424
425 def get_qmp_event(self, wait=False):
426 """
427 Poll for one queued QMP events and return it
428 """
John Snow306dfcd2019-06-27 17:28:15 -0400429 if self._events:
John Snowabf0bf92019-06-27 17:28:14 -0400430 return self._events.pop(0)
431 return self._qmp.pull_event(wait=wait)
432
433 def get_qmp_events(self, wait=False):
434 """
435 Poll for queued QMP events and return a list of dicts
436 """
437 events = self._qmp.get_events(wait=wait)
438 events.extend(self._events)
439 del self._events[:]
440 self._qmp.clear_events()
441 return events
442
443 @staticmethod
444 def event_match(event, match=None):
445 """
446 Check if an event matches optional match criteria.
447
448 The match criteria takes the form of a matching subdict. The event is
449 checked to be a superset of the subdict, recursively, with matching
450 values whenever the subdict values are not None.
451
452 This has a limitation that you cannot explicitly check for None values.
453
454 Examples, with the subdict queries on the left:
455 - None matches any object.
456 - {"foo": None} matches {"foo": {"bar": 1}}
457 - {"foo": None} matches {"foo": 5}
458 - {"foo": {"abc": None}} does not match {"foo": {"bar": 1}}
459 - {"foo": {"rab": 2}} matches {"foo": {"bar": 1, "rab": 2}}
460 """
461 if match is None:
462 return True
463
464 try:
465 for key in match:
466 if key in event:
467 if not QEMUMachine.event_match(event[key], match[key]):
468 return False
469 else:
470 return False
471 return True
472 except TypeError:
473 # either match or event wasn't iterable (not a dict)
474 return match == event
475
476 def event_wait(self, name, timeout=60.0, match=None):
477 """
478 event_wait waits for and returns a named event from QMP with a timeout.
479
480 name: The event to wait for.
481 timeout: QEMUMonitorProtocol.pull_event timeout parameter.
482 match: Optional match criteria. See event_match for details.
483 """
484 return self.events_wait([(name, match)], timeout)
485
486 def events_wait(self, events, timeout=60.0):
487 """
488 events_wait waits for and returns a named event from QMP with a timeout.
489
490 events: a sequence of (name, match_criteria) tuples.
491 The match criteria are optional and may be None.
492 See event_match for details.
493 timeout: QEMUMonitorProtocol.pull_event timeout parameter.
494 """
495 def _match(event):
496 for name, match in events:
John Snow306dfcd2019-06-27 17:28:15 -0400497 if event['event'] == name and self.event_match(event, match):
John Snowabf0bf92019-06-27 17:28:14 -0400498 return True
499 return False
500
501 # Search cached events
502 for event in self._events:
503 if _match(event):
504 self._events.remove(event)
505 return event
506
507 # Poll for new events
508 while True:
509 event = self._qmp.pull_event(wait=timeout)
510 if _match(event):
511 return event
512 self._events.append(event)
513
514 return None
515
516 def get_log(self):
517 """
518 After self.shutdown or failed qemu execution, this returns the output
519 of the qemu process.
520 """
521 return self._iolog
522
523 def add_args(self, *args):
524 """
525 Adds to the list of extra arguments to be given to the QEMU binary
526 """
527 self._args.extend(args)
528
529 def set_machine(self, machine_type):
530 """
531 Sets the machine type
532
533 If set, the machine type will be added to the base arguments
534 of the resulting QEMU command line.
535 """
536 self._machine = machine_type
537
Philippe Mathieu-Daudé746f2442020-01-21 00:51:56 +0100538 def set_console(self, device_type=None, console_index=0):
John Snowabf0bf92019-06-27 17:28:14 -0400539 """
540 Sets the device type for a console device
541
542 If set, the console device and a backing character device will
543 be added to the base arguments of the resulting QEMU command
544 line.
545
546 This is a convenience method that will either use the provided
547 device type, or default to a "-serial chardev:console" command
548 line argument.
549
550 The actual setting of command line arguments will be be done at
551 machine launch time, as it depends on the temporary directory
552 to be created.
553
554 @param device_type: the device type, such as "isa-serial". If
555 None is given (the default value) a "-serial
556 chardev:console" command line argument will
557 be used instead, resorting to the machine's
558 default device type.
Philippe Mathieu-Daudé746f2442020-01-21 00:51:56 +0100559 @param console_index: the index of the console device to use.
560 If not zero, the command line will create
561 'index - 1' consoles and connect them to
562 the 'null' backing character device.
John Snowabf0bf92019-06-27 17:28:14 -0400563 """
564 self._console_set = True
565 self._console_device_type = device_type
Philippe Mathieu-Daudé746f2442020-01-21 00:51:56 +0100566 self._console_index = console_index
John Snowabf0bf92019-06-27 17:28:14 -0400567
568 @property
569 def console_socket(self):
570 """
571 Returns a socket connected to the console
572 """
573 if self._console_socket is None:
574 self._console_socket = socket.socket(socket.AF_UNIX,
575 socket.SOCK_STREAM)
576 self._console_socket.connect(self._console_address)
577 return self._console_socket