blob: b9a98e2c862767a3e639fbb359cf9fa9b7b2d221 [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()
Kevin Wolfcd87f5e2020-03-13 09:36:16 +0100362 self._popen.wait(timeout=3)
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500363 except:
364 self._popen.kill()
John Snowabf0bf92019-06-27 17:28:14 -0400365 self._popen.wait()
366
367 self._load_io_log()
368 self._post_shutdown()
369
370 exitcode = self.exitcode()
371 if exitcode is not None and exitcode < 0:
372 msg = 'qemu received signal %i: %s'
373 if self._qemu_full_args:
374 command = ' '.join(self._qemu_full_args)
375 else:
376 command = ''
John Snow306dfcd2019-06-27 17:28:15 -0400377 LOG.warning(msg, -exitcode, command)
John Snowabf0bf92019-06-27 17:28:14 -0400378
379 self._launched = False
380
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500381 def set_qmp_monitor(self, enabled=True):
382 """
383 Set the QMP monitor.
384
385 @param enabled: if False, qmp monitor options will be removed from
386 the base arguments of the resulting QEMU command
387 line. Default is True.
388 @note: call this function before launch().
389 """
390 if enabled:
391 self._qmp_set = True
392 else:
393 self._qmp_set = False
394 self._qmp = None
395
John Snowabf0bf92019-06-27 17:28:14 -0400396 def qmp(self, cmd, conv_keys=True, **args):
397 """
398 Invoke a QMP command and return the response dict
399 """
400 qmp_args = dict()
401 for key, value in args.items():
402 if conv_keys:
403 qmp_args[key.replace('_', '-')] = value
404 else:
405 qmp_args[key] = value
406
407 return self._qmp.cmd(cmd, args=qmp_args)
408
409 def command(self, cmd, conv_keys=True, **args):
410 """
411 Invoke a QMP command.
412 On success return the response dict.
413 On failure raise an exception.
414 """
415 reply = self.qmp(cmd, conv_keys, **args)
416 if reply is None:
417 raise qmp.QMPError("Monitor is closed")
418 if "error" in reply:
419 raise MonitorResponseError(reply)
420 return reply["return"]
421
422 def get_qmp_event(self, wait=False):
423 """
424 Poll for one queued QMP events and return it
425 """
John Snow306dfcd2019-06-27 17:28:15 -0400426 if self._events:
John Snowabf0bf92019-06-27 17:28:14 -0400427 return self._events.pop(0)
428 return self._qmp.pull_event(wait=wait)
429
430 def get_qmp_events(self, wait=False):
431 """
432 Poll for queued QMP events and return a list of dicts
433 """
434 events = self._qmp.get_events(wait=wait)
435 events.extend(self._events)
436 del self._events[:]
437 self._qmp.clear_events()
438 return events
439
440 @staticmethod
441 def event_match(event, match=None):
442 """
443 Check if an event matches optional match criteria.
444
445 The match criteria takes the form of a matching subdict. The event is
446 checked to be a superset of the subdict, recursively, with matching
447 values whenever the subdict values are not None.
448
449 This has a limitation that you cannot explicitly check for None values.
450
451 Examples, with the subdict queries on the left:
452 - None matches any object.
453 - {"foo": None} matches {"foo": {"bar": 1}}
454 - {"foo": None} matches {"foo": 5}
455 - {"foo": {"abc": None}} does not match {"foo": {"bar": 1}}
456 - {"foo": {"rab": 2}} matches {"foo": {"bar": 1, "rab": 2}}
457 """
458 if match is None:
459 return True
460
461 try:
462 for key in match:
463 if key in event:
464 if not QEMUMachine.event_match(event[key], match[key]):
465 return False
466 else:
467 return False
468 return True
469 except TypeError:
470 # either match or event wasn't iterable (not a dict)
471 return match == event
472
473 def event_wait(self, name, timeout=60.0, match=None):
474 """
475 event_wait waits for and returns a named event from QMP with a timeout.
476
477 name: The event to wait for.
478 timeout: QEMUMonitorProtocol.pull_event timeout parameter.
479 match: Optional match criteria. See event_match for details.
480 """
481 return self.events_wait([(name, match)], timeout)
482
483 def events_wait(self, events, timeout=60.0):
484 """
485 events_wait waits for and returns a named event from QMP with a timeout.
486
487 events: a sequence of (name, match_criteria) tuples.
488 The match criteria are optional and may be None.
489 See event_match for details.
490 timeout: QEMUMonitorProtocol.pull_event timeout parameter.
491 """
492 def _match(event):
493 for name, match in events:
John Snow306dfcd2019-06-27 17:28:15 -0400494 if event['event'] == name and self.event_match(event, match):
John Snowabf0bf92019-06-27 17:28:14 -0400495 return True
496 return False
497
498 # Search cached events
499 for event in self._events:
500 if _match(event):
501 self._events.remove(event)
502 return event
503
504 # Poll for new events
505 while True:
506 event = self._qmp.pull_event(wait=timeout)
507 if _match(event):
508 return event
509 self._events.append(event)
510
511 return None
512
513 def get_log(self):
514 """
515 After self.shutdown or failed qemu execution, this returns the output
516 of the qemu process.
517 """
518 return self._iolog
519
520 def add_args(self, *args):
521 """
522 Adds to the list of extra arguments to be given to the QEMU binary
523 """
524 self._args.extend(args)
525
526 def set_machine(self, machine_type):
527 """
528 Sets the machine type
529
530 If set, the machine type will be added to the base arguments
531 of the resulting QEMU command line.
532 """
533 self._machine = machine_type
534
Philippe Mathieu-Daudé746f2442020-01-21 00:51:56 +0100535 def set_console(self, device_type=None, console_index=0):
John Snowabf0bf92019-06-27 17:28:14 -0400536 """
537 Sets the device type for a console device
538
539 If set, the console device and a backing character device will
540 be added to the base arguments of the resulting QEMU command
541 line.
542
543 This is a convenience method that will either use the provided
544 device type, or default to a "-serial chardev:console" command
545 line argument.
546
547 The actual setting of command line arguments will be be done at
548 machine launch time, as it depends on the temporary directory
549 to be created.
550
551 @param device_type: the device type, such as "isa-serial". If
552 None is given (the default value) a "-serial
553 chardev:console" command line argument will
554 be used instead, resorting to the machine's
555 default device type.
Philippe Mathieu-Daudé746f2442020-01-21 00:51:56 +0100556 @param console_index: the index of the console device to use.
557 If not zero, the command line will create
558 'index - 1' consoles and connect them to
559 the 'null' backing character device.
John Snowabf0bf92019-06-27 17:28:14 -0400560 """
561 self._console_set = True
562 self._console_device_type = device_type
Philippe Mathieu-Daudé746f2442020-01-21 00:51:56 +0100563 self._console_index = console_index
John Snowabf0bf92019-06-27 17:28:14 -0400564
565 @property
566 def console_socket(self):
567 """
568 Returns a socket connected to the console
569 """
570 if self._console_socket is None:
571 self._console_socket = socket.socket(socket.AF_UNIX,
572 socket.SOCK_STREAM)
573 self._console_socket.connect(self._console_address)
574 return self._console_socket