blob: 041c615052e4d7e2577cdc071be647f59bc76ffa [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
John Snow1dda0402020-05-14 01:53:44 -040027from typing import Optional, Type
28from types import TracebackType
John Snowabf0bf92019-06-27 17:28:14 -040029
30from . import qmp
31
32LOG = logging.getLogger(__name__)
33
John Snow8dfac2e2020-05-28 18:21:29 -040034
John Snowabf0bf92019-06-27 17:28:14 -040035class QEMUMachineError(Exception):
36 """
37 Exception called when an error in QEMUMachine happens.
38 """
39
40
41class QEMUMachineAddDeviceError(QEMUMachineError):
42 """
43 Exception raised when a request to add a device can not be fulfilled
44
45 The failures are caused by limitations, lack of information or conflicting
46 requests on the QEMUMachine methods. This exception does not represent
47 failures reported by the QEMU binary itself.
48 """
49
50
51class MonitorResponseError(qmp.QMPError):
52 """
53 Represents erroneous QMP monitor reply
54 """
55 def __init__(self, reply):
56 try:
57 desc = reply["error"]["desc"]
58 except KeyError:
59 desc = reply
John Snow3797dbc2020-05-14 01:53:42 -040060 super().__init__(desc)
John Snowabf0bf92019-06-27 17:28:14 -040061 self.reply = reply
62
63
John Snow9b8ccd62020-05-28 18:21:28 -040064class QEMUMachine:
John Snowabf0bf92019-06-27 17:28:14 -040065 """
66 A QEMU VM
67
John Snow8dfac2e2020-05-28 18:21:29 -040068 Use this object as a context manager to ensure
69 the QEMU process terminates::
John Snowabf0bf92019-06-27 17:28:14 -040070
71 with VM(binary) as vm:
72 ...
73 # vm is guaranteed to be shut down here
74 """
75
76 def __init__(self, binary, args=None, wrapper=None, name=None,
77 test_dir="/var/tmp", monitor_address=None,
Max Reitz32558ce2019-10-17 15:31:34 +020078 socket_scm_helper=None, sock_dir=None):
John Snowabf0bf92019-06-27 17:28:14 -040079 '''
80 Initialize a QEMUMachine
81
82 @param binary: path to the qemu binary
83 @param args: list of extra arguments
84 @param wrapper: list of arguments used as prefix to qemu binary
85 @param name: prefix for socket and log file names (default: qemu-PID)
86 @param test_dir: where to create socket and log file
87 @param monitor_address: address for QMP monitor
88 @param socket_scm_helper: helper program, required for send_fd_scm()
89 @note: Qemu process is not started until launch() is used.
90 '''
91 if args is None:
92 args = []
93 if wrapper is None:
94 wrapper = []
95 if name is None:
96 name = "qemu-%d" % os.getpid()
Max Reitz32558ce2019-10-17 15:31:34 +020097 if sock_dir is None:
98 sock_dir = test_dir
John Snowabf0bf92019-06-27 17:28:14 -040099 self._name = name
100 self._monitor_address = monitor_address
101 self._vm_monitor = None
102 self._qemu_log_path = None
103 self._qemu_log_file = None
104 self._popen = None
105 self._binary = binary
106 self._args = list(args) # Force copy args in case we modify them
107 self._wrapper = wrapper
108 self._events = []
109 self._iolog = None
110 self._socket_scm_helper = socket_scm_helper
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500111 self._qmp_set = True # Enable QMP monitor by default.
John Snowabf0bf92019-06-27 17:28:14 -0400112 self._qmp = None
113 self._qemu_full_args = None
114 self._test_dir = test_dir
115 self._temp_dir = None
Max Reitz32558ce2019-10-17 15:31:34 +0200116 self._sock_dir = sock_dir
John Snowabf0bf92019-06-27 17:28:14 -0400117 self._launched = False
118 self._machine = None
Philippe Mathieu-Daudé746f2442020-01-21 00:51:56 +0100119 self._console_index = 0
John Snowabf0bf92019-06-27 17:28:14 -0400120 self._console_set = False
121 self._console_device_type = None
122 self._console_address = None
123 self._console_socket = None
Max Reitz32558ce2019-10-17 15:31:34 +0200124 self._remove_files = []
John Snowabf0bf92019-06-27 17:28:14 -0400125
John Snowabf0bf92019-06-27 17:28:14 -0400126 def __enter__(self):
127 return self
128
John Snow1dda0402020-05-14 01:53:44 -0400129 def __exit__(self,
130 exc_type: Optional[Type[BaseException]],
131 exc_val: Optional[BaseException],
132 exc_tb: Optional[TracebackType]) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400133 self.shutdown()
John Snowabf0bf92019-06-27 17:28:14 -0400134
John Snowabf0bf92019-06-27 17:28:14 -0400135 def add_monitor_null(self):
John Snow306dfcd2019-06-27 17:28:15 -0400136 """
137 This can be used to add an unused monitor instance.
138 """
John Snowabf0bf92019-06-27 17:28:14 -0400139 self._args.append('-monitor')
140 self._args.append('null')
141
142 def add_fd(self, fd, fdset, opaque, opts=''):
143 """
144 Pass a file descriptor to the VM
145 """
146 options = ['fd=%d' % fd,
147 'set=%d' % fdset,
148 'opaque=%s' % opaque]
149 if opts:
150 options.append(opts)
151
152 # This did not exist before 3.4, but since then it is
153 # mandatory for our purpose
154 if hasattr(os, 'set_inheritable'):
155 os.set_inheritable(fd, True)
156
157 self._args.append('-add-fd')
158 self._args.append(','.join(options))
159 return self
160
John Snowabf0bf92019-06-27 17:28:14 -0400161 def send_fd_scm(self, fd=None, file_path=None):
John Snow306dfcd2019-06-27 17:28:15 -0400162 """
163 Send an fd or file_path to socket_scm_helper.
164
165 Exactly one of fd and file_path must be given.
166 If it is file_path, the helper will open that file and pass its own fd.
167 """
John Snowabf0bf92019-06-27 17:28:14 -0400168 # In iotest.py, the qmp should always use unix socket.
169 assert self._qmp.is_scm_available()
170 if self._socket_scm_helper is None:
171 raise QEMUMachineError("No path to socket_scm_helper set")
172 if not os.path.exists(self._socket_scm_helper):
173 raise QEMUMachineError("%s does not exist" %
174 self._socket_scm_helper)
175
176 # This did not exist before 3.4, but since then it is
177 # mandatory for our purpose
178 if hasattr(os, 'set_inheritable'):
179 os.set_inheritable(self._qmp.get_sock_fd(), True)
180 if fd is not None:
181 os.set_inheritable(fd, True)
182
183 fd_param = ["%s" % self._socket_scm_helper,
184 "%d" % self._qmp.get_sock_fd()]
185
186 if file_path is not None:
187 assert fd is None
188 fd_param.append(file_path)
189 else:
190 assert fd is not None
191 fd_param.append(str(fd))
192
193 devnull = open(os.path.devnull, 'rb')
John Snow8dfac2e2020-05-28 18:21:29 -0400194 proc = subprocess.Popen(
195 fd_param, stdin=devnull, stdout=subprocess.PIPE,
196 stderr=subprocess.STDOUT, close_fds=False
197 )
John Snowabf0bf92019-06-27 17:28:14 -0400198 output = proc.communicate()[0]
199 if output:
200 LOG.debug(output)
201
202 return proc.returncode
203
204 @staticmethod
205 def _remove_if_exists(path):
206 """
207 Remove file object at path if it exists
208 """
209 try:
210 os.remove(path)
211 except OSError as exception:
212 if exception.errno == errno.ENOENT:
213 return
214 raise
215
216 def is_running(self):
John Snow306dfcd2019-06-27 17:28:15 -0400217 """Returns true if the VM is running."""
John Snowabf0bf92019-06-27 17:28:14 -0400218 return self._popen is not None and self._popen.poll() is None
219
220 def exitcode(self):
John Snow306dfcd2019-06-27 17:28:15 -0400221 """Returns the exit code if possible, or None."""
John Snowabf0bf92019-06-27 17:28:14 -0400222 if self._popen is None:
223 return None
224 return self._popen.poll()
225
226 def get_pid(self):
John Snow306dfcd2019-06-27 17:28:15 -0400227 """Returns the PID of the running process, or None."""
John Snowabf0bf92019-06-27 17:28:14 -0400228 if not self.is_running():
229 return None
230 return self._popen.pid
231
232 def _load_io_log(self):
233 if self._qemu_log_path is not None:
234 with open(self._qemu_log_path, "r") as iolog:
235 self._iolog = iolog.read()
236
237 def _base_args(self):
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500238 args = ['-display', 'none', '-vga', 'none']
239 if self._qmp_set:
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])
244 else:
245 moncdev = 'socket,id=mon,path=%s' % self._vm_monitor
246 args.extend(['-chardev', moncdev, '-mon',
247 'chardev=mon,mode=control'])
John Snowabf0bf92019-06-27 17:28:14 -0400248 if self._machine is not None:
249 args.extend(['-machine', self._machine])
John Snow9b8ccd62020-05-28 18:21:28 -0400250 for _ in range(self._console_index):
Philippe Mathieu-Daudé746f2442020-01-21 00:51:56 +0100251 args.extend(['-serial', 'null'])
John Snowabf0bf92019-06-27 17:28:14 -0400252 if self._console_set:
Max Reitz32558ce2019-10-17 15:31:34 +0200253 self._console_address = os.path.join(self._sock_dir,
John Snowabf0bf92019-06-27 17:28:14 -0400254 self._name + "-console.sock")
Max Reitz32558ce2019-10-17 15:31:34 +0200255 self._remove_files.append(self._console_address)
John Snowabf0bf92019-06-27 17:28:14 -0400256 chardev = ('socket,id=console,path=%s,server,nowait' %
257 self._console_address)
258 args.extend(['-chardev', chardev])
259 if self._console_device_type is None:
260 args.extend(['-serial', 'chardev:console'])
261 else:
262 device = '%s,chardev=console' % self._console_device_type
263 args.extend(['-device', device])
264 return args
265
266 def _pre_launch(self):
267 self._temp_dir = tempfile.mkdtemp(dir=self._test_dir)
John Snowabf0bf92019-06-27 17:28:14 -0400268 self._qemu_log_path = os.path.join(self._temp_dir, self._name + ".log")
269 self._qemu_log_file = open(self._qemu_log_path, 'wb')
270
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500271 if self._qmp_set:
272 if self._monitor_address is not None:
273 self._vm_monitor = self._monitor_address
274 else:
275 self._vm_monitor = os.path.join(self._sock_dir,
276 self._name + "-monitor.sock")
277 self._remove_files.append(self._vm_monitor)
Oksana Vohchana566054a2020-03-16 12:32:03 +0200278 self._qmp = qmp.QEMUMonitorProtocol(self._vm_monitor, server=True,
279 nickname=self._name)
John Snowabf0bf92019-06-27 17:28:14 -0400280
281 def _post_launch(self):
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500282 if self._qmp:
283 self._qmp.accept()
John Snowabf0bf92019-06-27 17:28:14 -0400284
285 def _post_shutdown(self):
286 if self._qemu_log_file is not None:
287 self._qemu_log_file.close()
288 self._qemu_log_file = None
289
290 self._qemu_log_path = None
291
John Snowabf0bf92019-06-27 17:28:14 -0400292 if self._temp_dir is not None:
293 shutil.rmtree(self._temp_dir)
294 self._temp_dir = None
295
Max Reitz32558ce2019-10-17 15:31:34 +0200296 while len(self._remove_files) > 0:
297 self._remove_if_exists(self._remove_files.pop())
298
John Snowabf0bf92019-06-27 17:28:14 -0400299 def launch(self):
300 """
301 Launch the VM and make sure we cleanup and expose the
302 command line/output in case of exception
303 """
304
305 if self._launched:
306 raise QEMUMachineError('VM already launched')
307
308 self._iolog = None
309 self._qemu_full_args = None
310 try:
311 self._launch()
312 self._launched = True
313 except:
314 self.shutdown()
315
316 LOG.debug('Error launching VM')
317 if self._qemu_full_args:
318 LOG.debug('Command: %r', ' '.join(self._qemu_full_args))
319 if self._iolog:
320 LOG.debug('Output: %r', self._iolog)
321 raise
322
323 def _launch(self):
324 """
325 Launch the VM and establish a QMP connection
326 """
327 devnull = open(os.path.devnull, 'rb')
328 self._pre_launch()
329 self._qemu_full_args = (self._wrapper + [self._binary] +
330 self._base_args() + self._args)
331 LOG.debug('VM launch command: %r', ' '.join(self._qemu_full_args))
332 self._popen = subprocess.Popen(self._qemu_full_args,
333 stdin=devnull,
334 stdout=self._qemu_log_file,
335 stderr=subprocess.STDOUT,
336 shell=False,
337 close_fds=False)
338 self._post_launch()
339
340 def wait(self):
341 """
342 Wait for the VM to power off
343 """
344 self._popen.wait()
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500345 if self._qmp:
346 self._qmp.close()
John Snowabf0bf92019-06-27 17:28:14 -0400347 self._load_io_log()
348 self._post_shutdown()
349
Vladimir Sementsov-Ogievskiye0e925a2020-02-17 18:02:42 +0300350 def shutdown(self, has_quit=False, hard=False):
John Snowabf0bf92019-06-27 17:28:14 -0400351 """
352 Terminate the VM and clean up
353 """
Cleber Rosa08580962019-10-28 19:04:04 -0400354 # If we keep the console socket open, we may deadlock waiting
355 # for QEMU to exit, while QEMU is waiting for the socket to
356 # become writeable.
357 if self._console_socket is not None:
358 self._console_socket.close()
359 self._console_socket = None
360
John Snowabf0bf92019-06-27 17:28:14 -0400361 if self.is_running():
Vladimir Sementsov-Ogievskiye0e925a2020-02-17 18:02:42 +0300362 if hard:
363 self._popen.kill()
364 elif self._qmp:
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500365 try:
366 if not has_quit:
367 self._qmp.cmd('quit')
368 self._qmp.close()
Kevin Wolfcd87f5e2020-03-13 09:36:16 +0100369 self._popen.wait(timeout=3)
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500370 except:
371 self._popen.kill()
John Snowabf0bf92019-06-27 17:28:14 -0400372 self._popen.wait()
373
374 self._load_io_log()
375 self._post_shutdown()
376
377 exitcode = self.exitcode()
Vladimir Sementsov-Ogievskiye0e925a2020-02-17 18:02:42 +0300378 if exitcode is not None and exitcode < 0 and \
379 not (exitcode == -9 and hard):
John Snowabf0bf92019-06-27 17:28:14 -0400380 msg = 'qemu received signal %i: %s'
381 if self._qemu_full_args:
382 command = ' '.join(self._qemu_full_args)
383 else:
384 command = ''
John Snow9b8ccd62020-05-28 18:21:28 -0400385 LOG.warning(msg, -int(exitcode), command)
John Snowabf0bf92019-06-27 17:28:14 -0400386
387 self._launched = False
388
Vladimir Sementsov-Ogievskiye0e925a2020-02-17 18:02:42 +0300389 def kill(self):
390 self.shutdown(hard=True)
391
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500392 def set_qmp_monitor(self, enabled=True):
393 """
394 Set the QMP monitor.
395
396 @param enabled: if False, qmp monitor options will be removed from
397 the base arguments of the resulting QEMU command
398 line. Default is True.
399 @note: call this function before launch().
400 """
401 if enabled:
402 self._qmp_set = True
403 else:
404 self._qmp_set = False
405 self._qmp = None
406
John Snowabf0bf92019-06-27 17:28:14 -0400407 def qmp(self, cmd, conv_keys=True, **args):
408 """
409 Invoke a QMP command and return the response dict
410 """
411 qmp_args = dict()
412 for key, value in args.items():
413 if conv_keys:
414 qmp_args[key.replace('_', '-')] = value
415 else:
416 qmp_args[key] = value
417
418 return self._qmp.cmd(cmd, args=qmp_args)
419
420 def command(self, cmd, conv_keys=True, **args):
421 """
422 Invoke a QMP command.
423 On success return the response dict.
424 On failure raise an exception.
425 """
426 reply = self.qmp(cmd, conv_keys, **args)
427 if reply is None:
428 raise qmp.QMPError("Monitor is closed")
429 if "error" in reply:
430 raise MonitorResponseError(reply)
431 return reply["return"]
432
433 def get_qmp_event(self, wait=False):
434 """
435 Poll for one queued QMP events and return it
436 """
John Snow306dfcd2019-06-27 17:28:15 -0400437 if self._events:
John Snowabf0bf92019-06-27 17:28:14 -0400438 return self._events.pop(0)
439 return self._qmp.pull_event(wait=wait)
440
441 def get_qmp_events(self, wait=False):
442 """
443 Poll for queued QMP events and return a list of dicts
444 """
445 events = self._qmp.get_events(wait=wait)
446 events.extend(self._events)
447 del self._events[:]
448 self._qmp.clear_events()
449 return events
450
451 @staticmethod
452 def event_match(event, match=None):
453 """
454 Check if an event matches optional match criteria.
455
456 The match criteria takes the form of a matching subdict. The event is
457 checked to be a superset of the subdict, recursively, with matching
458 values whenever the subdict values are not None.
459
460 This has a limitation that you cannot explicitly check for None values.
461
462 Examples, with the subdict queries on the left:
463 - None matches any object.
464 - {"foo": None} matches {"foo": {"bar": 1}}
465 - {"foo": None} matches {"foo": 5}
466 - {"foo": {"abc": None}} does not match {"foo": {"bar": 1}}
467 - {"foo": {"rab": 2}} matches {"foo": {"bar": 1, "rab": 2}}
468 """
469 if match is None:
470 return True
471
472 try:
473 for key in match:
474 if key in event:
475 if not QEMUMachine.event_match(event[key], match[key]):
476 return False
477 else:
478 return False
479 return True
480 except TypeError:
481 # either match or event wasn't iterable (not a dict)
482 return match == event
483
484 def event_wait(self, name, timeout=60.0, match=None):
485 """
486 event_wait waits for and returns a named event from QMP with a timeout.
487
488 name: The event to wait for.
489 timeout: QEMUMonitorProtocol.pull_event timeout parameter.
490 match: Optional match criteria. See event_match for details.
491 """
492 return self.events_wait([(name, match)], timeout)
493
494 def events_wait(self, events, timeout=60.0):
495 """
John Snow8dfac2e2020-05-28 18:21:29 -0400496 events_wait waits for and returns a named event
497 from QMP with a timeout.
John Snowabf0bf92019-06-27 17:28:14 -0400498
499 events: a sequence of (name, match_criteria) tuples.
500 The match criteria are optional and may be None.
501 See event_match for details.
502 timeout: QEMUMonitorProtocol.pull_event timeout parameter.
503 """
504 def _match(event):
505 for name, match in events:
John Snow306dfcd2019-06-27 17:28:15 -0400506 if event['event'] == name and self.event_match(event, match):
John Snowabf0bf92019-06-27 17:28:14 -0400507 return True
508 return False
509
510 # Search cached events
511 for event in self._events:
512 if _match(event):
513 self._events.remove(event)
514 return event
515
516 # Poll for new events
517 while True:
518 event = self._qmp.pull_event(wait=timeout)
519 if _match(event):
520 return event
521 self._events.append(event)
522
523 return None
524
525 def get_log(self):
526 """
527 After self.shutdown or failed qemu execution, this returns the output
528 of the qemu process.
529 """
530 return self._iolog
531
532 def add_args(self, *args):
533 """
534 Adds to the list of extra arguments to be given to the QEMU binary
535 """
536 self._args.extend(args)
537
538 def set_machine(self, machine_type):
539 """
540 Sets the machine type
541
542 If set, the machine type will be added to the base arguments
543 of the resulting QEMU command line.
544 """
545 self._machine = machine_type
546
Philippe Mathieu-Daudé746f2442020-01-21 00:51:56 +0100547 def set_console(self, device_type=None, console_index=0):
John Snowabf0bf92019-06-27 17:28:14 -0400548 """
549 Sets the device type for a console device
550
551 If set, the console device and a backing character device will
552 be added to the base arguments of the resulting QEMU command
553 line.
554
555 This is a convenience method that will either use the provided
556 device type, or default to a "-serial chardev:console" command
557 line argument.
558
559 The actual setting of command line arguments will be be done at
560 machine launch time, as it depends on the temporary directory
561 to be created.
562
563 @param device_type: the device type, such as "isa-serial". If
564 None is given (the default value) a "-serial
565 chardev:console" command line argument will
566 be used instead, resorting to the machine's
567 default device type.
Philippe Mathieu-Daudé746f2442020-01-21 00:51:56 +0100568 @param console_index: the index of the console device to use.
569 If not zero, the command line will create
570 'index - 1' consoles and connect them to
571 the 'null' backing character device.
John Snowabf0bf92019-06-27 17:28:14 -0400572 """
573 self._console_set = True
574 self._console_device_type = device_type
Philippe Mathieu-Daudé746f2442020-01-21 00:51:56 +0100575 self._console_index = console_index
John Snowabf0bf92019-06-27 17:28:14 -0400576
577 @property
578 def console_socket(self):
579 """
580 Returns a socket connected to the console
581 """
582 if self._console_socket is None:
583 self._console_socket = socket.socket(socket.AF_UNIX,
584 socket.SOCK_STREAM)
585 self._console_socket.connect(self._console_address)
586 return self._console_socket