blob: ca1f2114e6c58e3a51797c5db0029f8f52469202 [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
Robert Foley0fc8f662020-07-01 14:56:24 +010029from qemu.console_socket import ConsoleSocket
John Snowabf0bf92019-06-27 17:28:14 -040030
31from . import qmp
32
33LOG = logging.getLogger(__name__)
34
John Snow8dfac2e2020-05-28 18:21:29 -040035
John Snowabf0bf92019-06-27 17:28:14 -040036class QEMUMachineError(Exception):
37 """
38 Exception called when an error in QEMUMachine happens.
39 """
40
41
42class QEMUMachineAddDeviceError(QEMUMachineError):
43 """
44 Exception raised when a request to add a device can not be fulfilled
45
46 The failures are caused by limitations, lack of information or conflicting
47 requests on the QEMUMachine methods. This exception does not represent
48 failures reported by the QEMU binary itself.
49 """
50
51
52class MonitorResponseError(qmp.QMPError):
53 """
54 Represents erroneous QMP monitor reply
55 """
56 def __init__(self, reply):
57 try:
58 desc = reply["error"]["desc"]
59 except KeyError:
60 desc = reply
John Snow3797dbc2020-05-14 01:53:42 -040061 super().__init__(desc)
John Snowabf0bf92019-06-27 17:28:14 -040062 self.reply = reply
63
64
John Snow9b8ccd62020-05-28 18:21:28 -040065class QEMUMachine:
John Snowabf0bf92019-06-27 17:28:14 -040066 """
67 A QEMU VM
68
John Snow8dfac2e2020-05-28 18:21:29 -040069 Use this object as a context manager to ensure
70 the QEMU process terminates::
John Snowabf0bf92019-06-27 17:28:14 -040071
72 with VM(binary) as vm:
73 ...
74 # vm is guaranteed to be shut down here
75 """
76
77 def __init__(self, binary, args=None, wrapper=None, name=None,
78 test_dir="/var/tmp", monitor_address=None,
Robert Foley0fc8f662020-07-01 14:56:24 +010079 socket_scm_helper=None, sock_dir=None,
80 drain_console=False, console_log=None):
John Snowabf0bf92019-06-27 17:28:14 -040081 '''
82 Initialize a QEMUMachine
83
84 @param binary: path to the qemu binary
85 @param args: list of extra arguments
86 @param wrapper: list of arguments used as prefix to qemu binary
87 @param name: prefix for socket and log file names (default: qemu-PID)
88 @param test_dir: where to create socket and log file
89 @param monitor_address: address for QMP monitor
90 @param socket_scm_helper: helper program, required for send_fd_scm()
Robert Foley0fc8f662020-07-01 14:56:24 +010091 @param sock_dir: where to create socket (overrides test_dir for sock)
92 @param console_log: (optional) path to console log file
93 @param drain_console: (optional) True to drain console socket to buffer
John Snowabf0bf92019-06-27 17:28:14 -040094 @note: Qemu process is not started until launch() is used.
95 '''
96 if args is None:
97 args = []
98 if wrapper is None:
99 wrapper = []
100 if name is None:
101 name = "qemu-%d" % os.getpid()
Max Reitz32558ce2019-10-17 15:31:34 +0200102 if sock_dir is None:
103 sock_dir = test_dir
John Snowabf0bf92019-06-27 17:28:14 -0400104 self._name = name
105 self._monitor_address = monitor_address
106 self._vm_monitor = None
107 self._qemu_log_path = None
108 self._qemu_log_file = None
109 self._popen = None
110 self._binary = binary
111 self._args = list(args) # Force copy args in case we modify them
112 self._wrapper = wrapper
113 self._events = []
114 self._iolog = None
115 self._socket_scm_helper = socket_scm_helper
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500116 self._qmp_set = True # Enable QMP monitor by default.
John Snowabf0bf92019-06-27 17:28:14 -0400117 self._qmp = None
118 self._qemu_full_args = None
119 self._test_dir = test_dir
120 self._temp_dir = None
Max Reitz32558ce2019-10-17 15:31:34 +0200121 self._sock_dir = sock_dir
John Snowabf0bf92019-06-27 17:28:14 -0400122 self._launched = False
123 self._machine = None
Philippe Mathieu-Daudé746f2442020-01-21 00:51:56 +0100124 self._console_index = 0
John Snowabf0bf92019-06-27 17:28:14 -0400125 self._console_set = False
126 self._console_device_type = None
127 self._console_address = None
128 self._console_socket = None
Max Reitz32558ce2019-10-17 15:31:34 +0200129 self._remove_files = []
Robert Foley0fc8f662020-07-01 14:56:24 +0100130 self._console_log_path = console_log
131 if self._console_log_path:
132 # In order to log the console, buffering needs to be enabled.
133 self._drain_console = True
134 else:
135 self._drain_console = drain_console
John Snowabf0bf92019-06-27 17:28:14 -0400136
John Snowabf0bf92019-06-27 17:28:14 -0400137 def __enter__(self):
138 return self
139
John Snow1dda0402020-05-14 01:53:44 -0400140 def __exit__(self,
141 exc_type: Optional[Type[BaseException]],
142 exc_val: Optional[BaseException],
143 exc_tb: Optional[TracebackType]) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400144 self.shutdown()
John Snowabf0bf92019-06-27 17:28:14 -0400145
John Snowabf0bf92019-06-27 17:28:14 -0400146 def add_monitor_null(self):
John Snow306dfcd2019-06-27 17:28:15 -0400147 """
148 This can be used to add an unused monitor instance.
149 """
John Snowabf0bf92019-06-27 17:28:14 -0400150 self._args.append('-monitor')
151 self._args.append('null')
152
153 def add_fd(self, fd, fdset, opaque, opts=''):
154 """
155 Pass a file descriptor to the VM
156 """
157 options = ['fd=%d' % fd,
158 'set=%d' % fdset,
159 'opaque=%s' % opaque]
160 if opts:
161 options.append(opts)
162
163 # This did not exist before 3.4, but since then it is
164 # mandatory for our purpose
165 if hasattr(os, 'set_inheritable'):
166 os.set_inheritable(fd, True)
167
168 self._args.append('-add-fd')
169 self._args.append(','.join(options))
170 return self
171
John Snowabf0bf92019-06-27 17:28:14 -0400172 def send_fd_scm(self, fd=None, file_path=None):
John Snow306dfcd2019-06-27 17:28:15 -0400173 """
174 Send an fd or file_path to socket_scm_helper.
175
176 Exactly one of fd and file_path must be given.
177 If it is file_path, the helper will open that file and pass its own fd.
178 """
John Snowabf0bf92019-06-27 17:28:14 -0400179 # In iotest.py, the qmp should always use unix socket.
180 assert self._qmp.is_scm_available()
181 if self._socket_scm_helper is None:
182 raise QEMUMachineError("No path to socket_scm_helper set")
183 if not os.path.exists(self._socket_scm_helper):
184 raise QEMUMachineError("%s does not exist" %
185 self._socket_scm_helper)
186
187 # This did not exist before 3.4, but since then it is
188 # mandatory for our purpose
189 if hasattr(os, 'set_inheritable'):
190 os.set_inheritable(self._qmp.get_sock_fd(), True)
191 if fd is not None:
192 os.set_inheritable(fd, True)
193
194 fd_param = ["%s" % self._socket_scm_helper,
195 "%d" % self._qmp.get_sock_fd()]
196
197 if file_path is not None:
198 assert fd is None
199 fd_param.append(file_path)
200 else:
201 assert fd is not None
202 fd_param.append(str(fd))
203
204 devnull = open(os.path.devnull, 'rb')
John Snow8dfac2e2020-05-28 18:21:29 -0400205 proc = subprocess.Popen(
206 fd_param, stdin=devnull, stdout=subprocess.PIPE,
207 stderr=subprocess.STDOUT, close_fds=False
208 )
John Snowabf0bf92019-06-27 17:28:14 -0400209 output = proc.communicate()[0]
210 if output:
211 LOG.debug(output)
212
213 return proc.returncode
214
215 @staticmethod
216 def _remove_if_exists(path):
217 """
218 Remove file object at path if it exists
219 """
220 try:
221 os.remove(path)
222 except OSError as exception:
223 if exception.errno == errno.ENOENT:
224 return
225 raise
226
227 def is_running(self):
John Snow306dfcd2019-06-27 17:28:15 -0400228 """Returns true if the VM is running."""
John Snowabf0bf92019-06-27 17:28:14 -0400229 return self._popen is not None and self._popen.poll() is None
230
231 def exitcode(self):
John Snow306dfcd2019-06-27 17:28:15 -0400232 """Returns the exit code if possible, or None."""
John Snowabf0bf92019-06-27 17:28:14 -0400233 if self._popen is None:
234 return None
235 return self._popen.poll()
236
237 def get_pid(self):
John Snow306dfcd2019-06-27 17:28:15 -0400238 """Returns the PID of the running process, or None."""
John Snowabf0bf92019-06-27 17:28:14 -0400239 if not self.is_running():
240 return None
241 return self._popen.pid
242
243 def _load_io_log(self):
244 if self._qemu_log_path is not None:
245 with open(self._qemu_log_path, "r") as iolog:
246 self._iolog = iolog.read()
247
248 def _base_args(self):
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500249 args = ['-display', 'none', '-vga', 'none']
250 if self._qmp_set:
251 if isinstance(self._monitor_address, tuple):
252 moncdev = "socket,id=mon,host=%s,port=%s" % (
253 self._monitor_address[0],
254 self._monitor_address[1])
255 else:
256 moncdev = 'socket,id=mon,path=%s' % self._vm_monitor
257 args.extend(['-chardev', moncdev, '-mon',
258 'chardev=mon,mode=control'])
John Snowabf0bf92019-06-27 17:28:14 -0400259 if self._machine is not None:
260 args.extend(['-machine', self._machine])
John Snow9b8ccd62020-05-28 18:21:28 -0400261 for _ in range(self._console_index):
Philippe Mathieu-Daudé746f2442020-01-21 00:51:56 +0100262 args.extend(['-serial', 'null'])
John Snowabf0bf92019-06-27 17:28:14 -0400263 if self._console_set:
Max Reitz32558ce2019-10-17 15:31:34 +0200264 self._console_address = os.path.join(self._sock_dir,
John Snowabf0bf92019-06-27 17:28:14 -0400265 self._name + "-console.sock")
Max Reitz32558ce2019-10-17 15:31:34 +0200266 self._remove_files.append(self._console_address)
John Snowabf0bf92019-06-27 17:28:14 -0400267 chardev = ('socket,id=console,path=%s,server,nowait' %
268 self._console_address)
269 args.extend(['-chardev', chardev])
270 if self._console_device_type is None:
271 args.extend(['-serial', 'chardev:console'])
272 else:
273 device = '%s,chardev=console' % self._console_device_type
274 args.extend(['-device', device])
275 return args
276
277 def _pre_launch(self):
278 self._temp_dir = tempfile.mkdtemp(dir=self._test_dir)
John Snowabf0bf92019-06-27 17:28:14 -0400279 self._qemu_log_path = os.path.join(self._temp_dir, self._name + ".log")
280 self._qemu_log_file = open(self._qemu_log_path, 'wb')
281
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500282 if self._qmp_set:
283 if self._monitor_address is not None:
284 self._vm_monitor = self._monitor_address
285 else:
286 self._vm_monitor = os.path.join(self._sock_dir,
287 self._name + "-monitor.sock")
288 self._remove_files.append(self._vm_monitor)
Oksana Vohchana566054a2020-03-16 12:32:03 +0200289 self._qmp = qmp.QEMUMonitorProtocol(self._vm_monitor, server=True,
290 nickname=self._name)
John Snowabf0bf92019-06-27 17:28:14 -0400291
292 def _post_launch(self):
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500293 if self._qmp:
294 self._qmp.accept()
John Snowabf0bf92019-06-27 17:28:14 -0400295
296 def _post_shutdown(self):
John Snow14661d92020-07-10 01:06:38 -0400297 self._load_io_log()
298
John Snowabf0bf92019-06-27 17:28:14 -0400299 if self._qemu_log_file is not None:
300 self._qemu_log_file.close()
301 self._qemu_log_file = None
302
303 self._qemu_log_path = None
304
John Snowabf0bf92019-06-27 17:28:14 -0400305 if self._temp_dir is not None:
306 shutil.rmtree(self._temp_dir)
307 self._temp_dir = None
308
Max Reitz32558ce2019-10-17 15:31:34 +0200309 while len(self._remove_files) > 0:
310 self._remove_if_exists(self._remove_files.pop())
311
John Snow14661d92020-07-10 01:06:38 -0400312 exitcode = self.exitcode()
313 if exitcode is not None and exitcode < 0:
314 msg = 'qemu received signal %i; command: "%s"'
315 if self._qemu_full_args:
316 command = ' '.join(self._qemu_full_args)
317 else:
318 command = ''
319 LOG.warning(msg, -int(exitcode), command)
320
321 self._launched = False
322
John Snowabf0bf92019-06-27 17:28:14 -0400323 def launch(self):
324 """
325 Launch the VM and make sure we cleanup and expose the
326 command line/output in case of exception
327 """
328
329 if self._launched:
330 raise QEMUMachineError('VM already launched')
331
332 self._iolog = None
333 self._qemu_full_args = None
334 try:
335 self._launch()
336 self._launched = True
337 except:
338 self.shutdown()
339
340 LOG.debug('Error launching VM')
341 if self._qemu_full_args:
342 LOG.debug('Command: %r', ' '.join(self._qemu_full_args))
343 if self._iolog:
344 LOG.debug('Output: %r', self._iolog)
345 raise
346
347 def _launch(self):
348 """
349 Launch the VM and establish a QMP connection
350 """
351 devnull = open(os.path.devnull, 'rb')
352 self._pre_launch()
353 self._qemu_full_args = (self._wrapper + [self._binary] +
354 self._base_args() + self._args)
355 LOG.debug('VM launch command: %r', ' '.join(self._qemu_full_args))
356 self._popen = subprocess.Popen(self._qemu_full_args,
357 stdin=devnull,
358 stdout=self._qemu_log_file,
359 stderr=subprocess.STDOUT,
360 shell=False,
361 close_fds=False)
362 self._post_launch()
363
364 def wait(self):
365 """
366 Wait for the VM to power off
367 """
368 self._popen.wait()
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500369 if self._qmp:
370 self._qmp.close()
John Snowabf0bf92019-06-27 17:28:14 -0400371 self._post_shutdown()
372
Vladimir Sementsov-Ogievskiye0e925a2020-02-17 18:02:42 +0300373 def shutdown(self, has_quit=False, hard=False):
John Snowabf0bf92019-06-27 17:28:14 -0400374 """
375 Terminate the VM and clean up
376 """
Cleber Rosa08580962019-10-28 19:04:04 -0400377 # If we keep the console socket open, we may deadlock waiting
378 # for QEMU to exit, while QEMU is waiting for the socket to
379 # become writeable.
380 if self._console_socket is not None:
381 self._console_socket.close()
382 self._console_socket = None
383
John Snowabf0bf92019-06-27 17:28:14 -0400384 if self.is_running():
Vladimir Sementsov-Ogievskiye0e925a2020-02-17 18:02:42 +0300385 if hard:
386 self._popen.kill()
387 elif self._qmp:
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500388 try:
389 if not has_quit:
390 self._qmp.cmd('quit')
391 self._qmp.close()
Kevin Wolfcd87f5e2020-03-13 09:36:16 +0100392 self._popen.wait(timeout=3)
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500393 except:
394 self._popen.kill()
John Snowabf0bf92019-06-27 17:28:14 -0400395 self._popen.wait()
396
John Snowabf0bf92019-06-27 17:28:14 -0400397 self._post_shutdown()
398
Vladimir Sementsov-Ogievskiye0e925a2020-02-17 18:02:42 +0300399 def kill(self):
400 self.shutdown(hard=True)
401
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500402 def set_qmp_monitor(self, enabled=True):
403 """
404 Set the QMP monitor.
405
406 @param enabled: if False, qmp monitor options will be removed from
407 the base arguments of the resulting QEMU command
408 line. Default is True.
409 @note: call this function before launch().
410 """
411 if enabled:
412 self._qmp_set = True
413 else:
414 self._qmp_set = False
415 self._qmp = None
416
John Snowabf0bf92019-06-27 17:28:14 -0400417 def qmp(self, cmd, conv_keys=True, **args):
418 """
419 Invoke a QMP command and return the response dict
420 """
421 qmp_args = dict()
422 for key, value in args.items():
423 if conv_keys:
424 qmp_args[key.replace('_', '-')] = value
425 else:
426 qmp_args[key] = value
427
428 return self._qmp.cmd(cmd, args=qmp_args)
429
430 def command(self, cmd, conv_keys=True, **args):
431 """
432 Invoke a QMP command.
433 On success return the response dict.
434 On failure raise an exception.
435 """
436 reply = self.qmp(cmd, conv_keys, **args)
437 if reply is None:
438 raise qmp.QMPError("Monitor is closed")
439 if "error" in reply:
440 raise MonitorResponseError(reply)
441 return reply["return"]
442
443 def get_qmp_event(self, wait=False):
444 """
445 Poll for one queued QMP events and return it
446 """
John Snow306dfcd2019-06-27 17:28:15 -0400447 if self._events:
John Snowabf0bf92019-06-27 17:28:14 -0400448 return self._events.pop(0)
449 return self._qmp.pull_event(wait=wait)
450
451 def get_qmp_events(self, wait=False):
452 """
453 Poll for queued QMP events and return a list of dicts
454 """
455 events = self._qmp.get_events(wait=wait)
456 events.extend(self._events)
457 del self._events[:]
458 self._qmp.clear_events()
459 return events
460
461 @staticmethod
462 def event_match(event, match=None):
463 """
464 Check if an event matches optional match criteria.
465
466 The match criteria takes the form of a matching subdict. The event is
467 checked to be a superset of the subdict, recursively, with matching
468 values whenever the subdict values are not None.
469
470 This has a limitation that you cannot explicitly check for None values.
471
472 Examples, with the subdict queries on the left:
473 - None matches any object.
474 - {"foo": None} matches {"foo": {"bar": 1}}
475 - {"foo": None} matches {"foo": 5}
476 - {"foo": {"abc": None}} does not match {"foo": {"bar": 1}}
477 - {"foo": {"rab": 2}} matches {"foo": {"bar": 1, "rab": 2}}
478 """
479 if match is None:
480 return True
481
482 try:
483 for key in match:
484 if key in event:
485 if not QEMUMachine.event_match(event[key], match[key]):
486 return False
487 else:
488 return False
489 return True
490 except TypeError:
491 # either match or event wasn't iterable (not a dict)
492 return match == event
493
494 def event_wait(self, name, timeout=60.0, match=None):
495 """
496 event_wait waits for and returns a named event from QMP with a timeout.
497
498 name: The event to wait for.
499 timeout: QEMUMonitorProtocol.pull_event timeout parameter.
500 match: Optional match criteria. See event_match for details.
501 """
502 return self.events_wait([(name, match)], timeout)
503
504 def events_wait(self, events, timeout=60.0):
505 """
John Snow8dfac2e2020-05-28 18:21:29 -0400506 events_wait waits for and returns a named event
507 from QMP with a timeout.
John Snowabf0bf92019-06-27 17:28:14 -0400508
509 events: a sequence of (name, match_criteria) tuples.
510 The match criteria are optional and may be None.
511 See event_match for details.
512 timeout: QEMUMonitorProtocol.pull_event timeout parameter.
513 """
514 def _match(event):
515 for name, match in events:
John Snow306dfcd2019-06-27 17:28:15 -0400516 if event['event'] == name and self.event_match(event, match):
John Snowabf0bf92019-06-27 17:28:14 -0400517 return True
518 return False
519
520 # Search cached events
521 for event in self._events:
522 if _match(event):
523 self._events.remove(event)
524 return event
525
526 # Poll for new events
527 while True:
528 event = self._qmp.pull_event(wait=timeout)
529 if _match(event):
530 return event
531 self._events.append(event)
532
533 return None
534
535 def get_log(self):
536 """
537 After self.shutdown or failed qemu execution, this returns the output
538 of the qemu process.
539 """
540 return self._iolog
541
542 def add_args(self, *args):
543 """
544 Adds to the list of extra arguments to be given to the QEMU binary
545 """
546 self._args.extend(args)
547
548 def set_machine(self, machine_type):
549 """
550 Sets the machine type
551
552 If set, the machine type will be added to the base arguments
553 of the resulting QEMU command line.
554 """
555 self._machine = machine_type
556
Philippe Mathieu-Daudé746f2442020-01-21 00:51:56 +0100557 def set_console(self, device_type=None, console_index=0):
John Snowabf0bf92019-06-27 17:28:14 -0400558 """
559 Sets the device type for a console device
560
561 If set, the console device and a backing character device will
562 be added to the base arguments of the resulting QEMU command
563 line.
564
565 This is a convenience method that will either use the provided
566 device type, or default to a "-serial chardev:console" command
567 line argument.
568
569 The actual setting of command line arguments will be be done at
570 machine launch time, as it depends on the temporary directory
571 to be created.
572
573 @param device_type: the device type, such as "isa-serial". If
574 None is given (the default value) a "-serial
575 chardev:console" command line argument will
576 be used instead, resorting to the machine's
577 default device type.
Philippe Mathieu-Daudé746f2442020-01-21 00:51:56 +0100578 @param console_index: the index of the console device to use.
579 If not zero, the command line will create
580 'index - 1' consoles and connect them to
581 the 'null' backing character device.
John Snowabf0bf92019-06-27 17:28:14 -0400582 """
583 self._console_set = True
584 self._console_device_type = device_type
Philippe Mathieu-Daudé746f2442020-01-21 00:51:56 +0100585 self._console_index = console_index
John Snowabf0bf92019-06-27 17:28:14 -0400586
587 @property
588 def console_socket(self):
589 """
590 Returns a socket connected to the console
591 """
592 if self._console_socket is None:
Robert Foley0fc8f662020-07-01 14:56:24 +0100593 if self._drain_console:
594 self._console_socket = ConsoleSocket(self._console_address,
595 file=self._console_log_path)
596 else:
597 self._console_socket = socket.socket(socket.AF_UNIX,
598 socket.SOCK_STREAM)
599 self._console_socket.connect(self._console_address)
John Snowabf0bf92019-06-27 17:28:14 -0400600 return self._console_socket