blob: f53abfa4920dd38ff34e633ecdf0274e5be222e0 [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
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
122 # just in case logging wasn't configured by the main script:
123 logging.basicConfig()
124
125 def __enter__(self):
126 return self
127
128 def __exit__(self, exc_type, exc_val, exc_tb):
129 self.shutdown()
130 return False
131
John Snowabf0bf92019-06-27 17:28:14 -0400132 def add_monitor_null(self):
John Snow306dfcd2019-06-27 17:28:15 -0400133 """
134 This can be used to add an unused monitor instance.
135 """
John Snowabf0bf92019-06-27 17:28:14 -0400136 self._args.append('-monitor')
137 self._args.append('null')
138
139 def add_fd(self, fd, fdset, opaque, opts=''):
140 """
141 Pass a file descriptor to the VM
142 """
143 options = ['fd=%d' % fd,
144 'set=%d' % fdset,
145 'opaque=%s' % opaque]
146 if opts:
147 options.append(opts)
148
149 # This did not exist before 3.4, but since then it is
150 # mandatory for our purpose
151 if hasattr(os, 'set_inheritable'):
152 os.set_inheritable(fd, True)
153
154 self._args.append('-add-fd')
155 self._args.append(','.join(options))
156 return self
157
John Snowabf0bf92019-06-27 17:28:14 -0400158 def send_fd_scm(self, fd=None, file_path=None):
John Snow306dfcd2019-06-27 17:28:15 -0400159 """
160 Send an fd or file_path to socket_scm_helper.
161
162 Exactly one of fd and file_path must be given.
163 If it is file_path, the helper will open that file and pass its own fd.
164 """
John Snowabf0bf92019-06-27 17:28:14 -0400165 # In iotest.py, the qmp should always use unix socket.
166 assert self._qmp.is_scm_available()
167 if self._socket_scm_helper is None:
168 raise QEMUMachineError("No path to socket_scm_helper set")
169 if not os.path.exists(self._socket_scm_helper):
170 raise QEMUMachineError("%s does not exist" %
171 self._socket_scm_helper)
172
173 # This did not exist before 3.4, but since then it is
174 # mandatory for our purpose
175 if hasattr(os, 'set_inheritable'):
176 os.set_inheritable(self._qmp.get_sock_fd(), True)
177 if fd is not None:
178 os.set_inheritable(fd, True)
179
180 fd_param = ["%s" % self._socket_scm_helper,
181 "%d" % self._qmp.get_sock_fd()]
182
183 if file_path is not None:
184 assert fd is None
185 fd_param.append(file_path)
186 else:
187 assert fd is not None
188 fd_param.append(str(fd))
189
190 devnull = open(os.path.devnull, 'rb')
191 proc = subprocess.Popen(fd_param, stdin=devnull, stdout=subprocess.PIPE,
192 stderr=subprocess.STDOUT, close_fds=False)
193 output = proc.communicate()[0]
194 if output:
195 LOG.debug(output)
196
197 return proc.returncode
198
199 @staticmethod
200 def _remove_if_exists(path):
201 """
202 Remove file object at path if it exists
203 """
204 try:
205 os.remove(path)
206 except OSError as exception:
207 if exception.errno == errno.ENOENT:
208 return
209 raise
210
211 def is_running(self):
John Snow306dfcd2019-06-27 17:28:15 -0400212 """Returns true if the VM is running."""
John Snowabf0bf92019-06-27 17:28:14 -0400213 return self._popen is not None and self._popen.poll() is None
214
215 def exitcode(self):
John Snow306dfcd2019-06-27 17:28:15 -0400216 """Returns the exit code if possible, or None."""
John Snowabf0bf92019-06-27 17:28:14 -0400217 if self._popen is None:
218 return None
219 return self._popen.poll()
220
221 def get_pid(self):
John Snow306dfcd2019-06-27 17:28:15 -0400222 """Returns the PID of the running process, or None."""
John Snowabf0bf92019-06-27 17:28:14 -0400223 if not self.is_running():
224 return None
225 return self._popen.pid
226
227 def _load_io_log(self):
228 if self._qemu_log_path is not None:
229 with open(self._qemu_log_path, "r") as iolog:
230 self._iolog = iolog.read()
231
232 def _base_args(self):
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500233 args = ['-display', 'none', '-vga', 'none']
234 if self._qmp_set:
235 if isinstance(self._monitor_address, tuple):
236 moncdev = "socket,id=mon,host=%s,port=%s" % (
237 self._monitor_address[0],
238 self._monitor_address[1])
239 else:
240 moncdev = 'socket,id=mon,path=%s' % self._vm_monitor
241 args.extend(['-chardev', moncdev, '-mon',
242 'chardev=mon,mode=control'])
John Snowabf0bf92019-06-27 17:28:14 -0400243 if self._machine is not None:
244 args.extend(['-machine', self._machine])
Philippe Mathieu-Daudé746f2442020-01-21 00:51:56 +0100245 for i in range(self._console_index):
246 args.extend(['-serial', 'null'])
John Snowabf0bf92019-06-27 17:28:14 -0400247 if self._console_set:
Max Reitz32558ce2019-10-17 15:31:34 +0200248 self._console_address = os.path.join(self._sock_dir,
John Snowabf0bf92019-06-27 17:28:14 -0400249 self._name + "-console.sock")
Max Reitz32558ce2019-10-17 15:31:34 +0200250 self._remove_files.append(self._console_address)
John Snowabf0bf92019-06-27 17:28:14 -0400251 chardev = ('socket,id=console,path=%s,server,nowait' %
252 self._console_address)
253 args.extend(['-chardev', chardev])
254 if self._console_device_type is None:
255 args.extend(['-serial', 'chardev:console'])
256 else:
257 device = '%s,chardev=console' % self._console_device_type
258 args.extend(['-device', device])
259 return args
260
261 def _pre_launch(self):
262 self._temp_dir = tempfile.mkdtemp(dir=self._test_dir)
John Snowabf0bf92019-06-27 17:28:14 -0400263 self._qemu_log_path = os.path.join(self._temp_dir, self._name + ".log")
264 self._qemu_log_file = open(self._qemu_log_path, 'wb')
265
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500266 if self._qmp_set:
267 if self._monitor_address is not None:
268 self._vm_monitor = self._monitor_address
269 else:
270 self._vm_monitor = os.path.join(self._sock_dir,
271 self._name + "-monitor.sock")
272 self._remove_files.append(self._vm_monitor)
Oksana Vohchana566054a2020-03-16 12:32:03 +0200273 self._qmp = qmp.QEMUMonitorProtocol(self._vm_monitor, server=True,
274 nickname=self._name)
John Snowabf0bf92019-06-27 17:28:14 -0400275
276 def _post_launch(self):
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500277 if self._qmp:
278 self._qmp.accept()
John Snowabf0bf92019-06-27 17:28:14 -0400279
280 def _post_shutdown(self):
281 if self._qemu_log_file is not None:
282 self._qemu_log_file.close()
283 self._qemu_log_file = None
284
285 self._qemu_log_path = None
286
John Snowabf0bf92019-06-27 17:28:14 -0400287 if self._temp_dir is not None:
288 shutil.rmtree(self._temp_dir)
289 self._temp_dir = None
290
Max Reitz32558ce2019-10-17 15:31:34 +0200291 while len(self._remove_files) > 0:
292 self._remove_if_exists(self._remove_files.pop())
293
John Snowabf0bf92019-06-27 17:28:14 -0400294 def launch(self):
295 """
296 Launch the VM and make sure we cleanup and expose the
297 command line/output in case of exception
298 """
299
300 if self._launched:
301 raise QEMUMachineError('VM already launched')
302
303 self._iolog = None
304 self._qemu_full_args = None
305 try:
306 self._launch()
307 self._launched = True
308 except:
309 self.shutdown()
310
311 LOG.debug('Error launching VM')
312 if self._qemu_full_args:
313 LOG.debug('Command: %r', ' '.join(self._qemu_full_args))
314 if self._iolog:
315 LOG.debug('Output: %r', self._iolog)
316 raise
317
318 def _launch(self):
319 """
320 Launch the VM and establish a QMP connection
321 """
322 devnull = open(os.path.devnull, 'rb')
323 self._pre_launch()
324 self._qemu_full_args = (self._wrapper + [self._binary] +
325 self._base_args() + self._args)
326 LOG.debug('VM launch command: %r', ' '.join(self._qemu_full_args))
327 self._popen = subprocess.Popen(self._qemu_full_args,
328 stdin=devnull,
329 stdout=self._qemu_log_file,
330 stderr=subprocess.STDOUT,
331 shell=False,
332 close_fds=False)
333 self._post_launch()
334
335 def wait(self):
336 """
337 Wait for the VM to power off
338 """
339 self._popen.wait()
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500340 if self._qmp:
341 self._qmp.close()
John Snowabf0bf92019-06-27 17:28:14 -0400342 self._load_io_log()
343 self._post_shutdown()
344
Max Reitz46871332019-07-19 11:26:17 +0200345 def shutdown(self, has_quit=False):
John Snowabf0bf92019-06-27 17:28:14 -0400346 """
347 Terminate the VM and clean up
348 """
Cleber Rosa08580962019-10-28 19:04:04 -0400349 # If we keep the console socket open, we may deadlock waiting
350 # for QEMU to exit, while QEMU is waiting for the socket to
351 # become writeable.
352 if self._console_socket is not None:
353 self._console_socket.close()
354 self._console_socket = None
355
John Snowabf0bf92019-06-27 17:28:14 -0400356 if self.is_running():
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500357 if self._qmp:
358 try:
359 if not has_quit:
360 self._qmp.cmd('quit')
361 self._qmp.close()
362 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()
370 if exitcode is not None and exitcode < 0:
371 msg = 'qemu received signal %i: %s'
372 if self._qemu_full_args:
373 command = ' '.join(self._qemu_full_args)
374 else:
375 command = ''
John Snow306dfcd2019-06-27 17:28:15 -0400376 LOG.warning(msg, -exitcode, command)
John Snowabf0bf92019-06-27 17:28:14 -0400377
378 self._launched = False
379
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500380 def set_qmp_monitor(self, enabled=True):
381 """
382 Set the QMP monitor.
383
384 @param enabled: if False, qmp monitor options will be removed from
385 the base arguments of the resulting QEMU command
386 line. Default is True.
387 @note: call this function before launch().
388 """
389 if enabled:
390 self._qmp_set = True
391 else:
392 self._qmp_set = False
393 self._qmp = None
394
John Snowabf0bf92019-06-27 17:28:14 -0400395 def qmp(self, cmd, conv_keys=True, **args):
396 """
397 Invoke a QMP command and return the response dict
398 """
399 qmp_args = dict()
400 for key, value in args.items():
401 if conv_keys:
402 qmp_args[key.replace('_', '-')] = value
403 else:
404 qmp_args[key] = value
405
406 return self._qmp.cmd(cmd, args=qmp_args)
407
408 def command(self, cmd, conv_keys=True, **args):
409 """
410 Invoke a QMP command.
411 On success return the response dict.
412 On failure raise an exception.
413 """
414 reply = self.qmp(cmd, conv_keys, **args)
415 if reply is None:
416 raise qmp.QMPError("Monitor is closed")
417 if "error" in reply:
418 raise MonitorResponseError(reply)
419 return reply["return"]
420
421 def get_qmp_event(self, wait=False):
422 """
423 Poll for one queued QMP events and return it
424 """
John Snow306dfcd2019-06-27 17:28:15 -0400425 if self._events:
John Snowabf0bf92019-06-27 17:28:14 -0400426 return self._events.pop(0)
427 return self._qmp.pull_event(wait=wait)
428
429 def get_qmp_events(self, wait=False):
430 """
431 Poll for queued QMP events and return a list of dicts
432 """
433 events = self._qmp.get_events(wait=wait)
434 events.extend(self._events)
435 del self._events[:]
436 self._qmp.clear_events()
437 return events
438
439 @staticmethod
440 def event_match(event, match=None):
441 """
442 Check if an event matches optional match criteria.
443
444 The match criteria takes the form of a matching subdict. The event is
445 checked to be a superset of the subdict, recursively, with matching
446 values whenever the subdict values are not None.
447
448 This has a limitation that you cannot explicitly check for None values.
449
450 Examples, with the subdict queries on the left:
451 - None matches any object.
452 - {"foo": None} matches {"foo": {"bar": 1}}
453 - {"foo": None} matches {"foo": 5}
454 - {"foo": {"abc": None}} does not match {"foo": {"bar": 1}}
455 - {"foo": {"rab": 2}} matches {"foo": {"bar": 1, "rab": 2}}
456 """
457 if match is None:
458 return True
459
460 try:
461 for key in match:
462 if key in event:
463 if not QEMUMachine.event_match(event[key], match[key]):
464 return False
465 else:
466 return False
467 return True
468 except TypeError:
469 # either match or event wasn't iterable (not a dict)
470 return match == event
471
472 def event_wait(self, name, timeout=60.0, match=None):
473 """
474 event_wait waits for and returns a named event from QMP with a timeout.
475
476 name: The event to wait for.
477 timeout: QEMUMonitorProtocol.pull_event timeout parameter.
478 match: Optional match criteria. See event_match for details.
479 """
480 return self.events_wait([(name, match)], timeout)
481
482 def events_wait(self, events, timeout=60.0):
483 """
484 events_wait waits for and returns a named event from QMP with a timeout.
485
486 events: a sequence of (name, match_criteria) tuples.
487 The match criteria are optional and may be None.
488 See event_match for details.
489 timeout: QEMUMonitorProtocol.pull_event timeout parameter.
490 """
491 def _match(event):
492 for name, match in events:
John Snow306dfcd2019-06-27 17:28:15 -0400493 if event['event'] == name and self.event_match(event, match):
John Snowabf0bf92019-06-27 17:28:14 -0400494 return True
495 return False
496
497 # Search cached events
498 for event in self._events:
499 if _match(event):
500 self._events.remove(event)
501 return event
502
503 # Poll for new events
504 while True:
505 event = self._qmp.pull_event(wait=timeout)
506 if _match(event):
507 return event
508 self._events.append(event)
509
510 return None
511
512 def get_log(self):
513 """
514 After self.shutdown or failed qemu execution, this returns the output
515 of the qemu process.
516 """
517 return self._iolog
518
519 def add_args(self, *args):
520 """
521 Adds to the list of extra arguments to be given to the QEMU binary
522 """
523 self._args.extend(args)
524
525 def set_machine(self, machine_type):
526 """
527 Sets the machine type
528
529 If set, the machine type will be added to the base arguments
530 of the resulting QEMU command line.
531 """
532 self._machine = machine_type
533
Philippe Mathieu-Daudé746f2442020-01-21 00:51:56 +0100534 def set_console(self, device_type=None, console_index=0):
John Snowabf0bf92019-06-27 17:28:14 -0400535 """
536 Sets the device type for a console device
537
538 If set, the console device and a backing character device will
539 be added to the base arguments of the resulting QEMU command
540 line.
541
542 This is a convenience method that will either use the provided
543 device type, or default to a "-serial chardev:console" command
544 line argument.
545
546 The actual setting of command line arguments will be be done at
547 machine launch time, as it depends on the temporary directory
548 to be created.
549
550 @param device_type: the device type, such as "isa-serial". If
551 None is given (the default value) a "-serial
552 chardev:console" command line argument will
553 be used instead, resorting to the machine's
554 default device type.
Philippe Mathieu-Daudé746f2442020-01-21 00:51:56 +0100555 @param console_index: the index of the console device to use.
556 If not zero, the command line will create
557 'index - 1' consoles and connect them to
558 the 'null' backing character device.
John Snowabf0bf92019-06-27 17:28:14 -0400559 """
560 self._console_set = True
561 self._console_device_type = device_type
Philippe Mathieu-Daudé746f2442020-01-21 00:51:56 +0100562 self._console_index = console_index
John Snowabf0bf92019-06-27 17:28:14 -0400563
564 @property
565 def console_socket(self):
566 """
567 Returns a socket connected to the console
568 """
569 if self._console_socket is None:
570 self._console_socket = socket.socket(socket.AF_UNIX,
571 socket.SOCK_STREAM)
572 self._console_socket.connect(self._console_address)
573 return self._console_socket