blob: 183d8f3d3894b201c8c512e32a833fb6a2d4634a [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)
273 self._qmp = qmp.QEMUMonitorProtocol(self._vm_monitor, server=True)
John Snowabf0bf92019-06-27 17:28:14 -0400274
275 def _post_launch(self):
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500276 if self._qmp:
277 self._qmp.accept()
John Snowabf0bf92019-06-27 17:28:14 -0400278
279 def _post_shutdown(self):
280 if self._qemu_log_file is not None:
281 self._qemu_log_file.close()
282 self._qemu_log_file = None
283
284 self._qemu_log_path = None
285
John Snowabf0bf92019-06-27 17:28:14 -0400286 if self._temp_dir is not None:
287 shutil.rmtree(self._temp_dir)
288 self._temp_dir = None
289
Max Reitz32558ce2019-10-17 15:31:34 +0200290 while len(self._remove_files) > 0:
291 self._remove_if_exists(self._remove_files.pop())
292
John Snowabf0bf92019-06-27 17:28:14 -0400293 def launch(self):
294 """
295 Launch the VM and make sure we cleanup and expose the
296 command line/output in case of exception
297 """
298
299 if self._launched:
300 raise QEMUMachineError('VM already launched')
301
302 self._iolog = None
303 self._qemu_full_args = None
304 try:
305 self._launch()
306 self._launched = True
307 except:
308 self.shutdown()
309
310 LOG.debug('Error launching VM')
311 if self._qemu_full_args:
312 LOG.debug('Command: %r', ' '.join(self._qemu_full_args))
313 if self._iolog:
314 LOG.debug('Output: %r', self._iolog)
315 raise
316
317 def _launch(self):
318 """
319 Launch the VM and establish a QMP connection
320 """
321 devnull = open(os.path.devnull, 'rb')
322 self._pre_launch()
323 self._qemu_full_args = (self._wrapper + [self._binary] +
324 self._base_args() + self._args)
325 LOG.debug('VM launch command: %r', ' '.join(self._qemu_full_args))
326 self._popen = subprocess.Popen(self._qemu_full_args,
327 stdin=devnull,
328 stdout=self._qemu_log_file,
329 stderr=subprocess.STDOUT,
330 shell=False,
331 close_fds=False)
332 self._post_launch()
333
334 def wait(self):
335 """
336 Wait for the VM to power off
337 """
338 self._popen.wait()
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500339 if self._qmp:
340 self._qmp.close()
John Snowabf0bf92019-06-27 17:28:14 -0400341 self._load_io_log()
342 self._post_shutdown()
343
Max Reitz46871332019-07-19 11:26:17 +0200344 def shutdown(self, has_quit=False):
John Snowabf0bf92019-06-27 17:28:14 -0400345 """
346 Terminate the VM and clean up
347 """
Cleber Rosa08580962019-10-28 19:04:04 -0400348 # If we keep the console socket open, we may deadlock waiting
349 # for QEMU to exit, while QEMU is waiting for the socket to
350 # become writeable.
351 if self._console_socket is not None:
352 self._console_socket.close()
353 self._console_socket = None
354
John Snowabf0bf92019-06-27 17:28:14 -0400355 if self.is_running():
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500356 if self._qmp:
357 try:
358 if not has_quit:
359 self._qmp.cmd('quit')
360 self._qmp.close()
361 except:
362 self._popen.kill()
John Snowabf0bf92019-06-27 17:28:14 -0400363 self._popen.wait()
364
365 self._load_io_log()
366 self._post_shutdown()
367
368 exitcode = self.exitcode()
369 if exitcode is not None and exitcode < 0:
370 msg = 'qemu received signal %i: %s'
371 if self._qemu_full_args:
372 command = ' '.join(self._qemu_full_args)
373 else:
374 command = ''
John Snow306dfcd2019-06-27 17:28:15 -0400375 LOG.warning(msg, -exitcode, command)
John Snowabf0bf92019-06-27 17:28:14 -0400376
377 self._launched = False
378
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500379 def set_qmp_monitor(self, enabled=True):
380 """
381 Set the QMP monitor.
382
383 @param enabled: if False, qmp monitor options will be removed from
384 the base arguments of the resulting QEMU command
385 line. Default is True.
386 @note: call this function before launch().
387 """
388 if enabled:
389 self._qmp_set = True
390 else:
391 self._qmp_set = False
392 self._qmp = None
393
John Snowabf0bf92019-06-27 17:28:14 -0400394 def qmp(self, cmd, conv_keys=True, **args):
395 """
396 Invoke a QMP command and return the response dict
397 """
398 qmp_args = dict()
399 for key, value in args.items():
400 if conv_keys:
401 qmp_args[key.replace('_', '-')] = value
402 else:
403 qmp_args[key] = value
404
405 return self._qmp.cmd(cmd, args=qmp_args)
406
407 def command(self, cmd, conv_keys=True, **args):
408 """
409 Invoke a QMP command.
410 On success return the response dict.
411 On failure raise an exception.
412 """
413 reply = self.qmp(cmd, conv_keys, **args)
414 if reply is None:
415 raise qmp.QMPError("Monitor is closed")
416 if "error" in reply:
417 raise MonitorResponseError(reply)
418 return reply["return"]
419
420 def get_qmp_event(self, wait=False):
421 """
422 Poll for one queued QMP events and return it
423 """
John Snow306dfcd2019-06-27 17:28:15 -0400424 if self._events:
John Snowabf0bf92019-06-27 17:28:14 -0400425 return self._events.pop(0)
426 return self._qmp.pull_event(wait=wait)
427
428 def get_qmp_events(self, wait=False):
429 """
430 Poll for queued QMP events and return a list of dicts
431 """
432 events = self._qmp.get_events(wait=wait)
433 events.extend(self._events)
434 del self._events[:]
435 self._qmp.clear_events()
436 return events
437
438 @staticmethod
439 def event_match(event, match=None):
440 """
441 Check if an event matches optional match criteria.
442
443 The match criteria takes the form of a matching subdict. The event is
444 checked to be a superset of the subdict, recursively, with matching
445 values whenever the subdict values are not None.
446
447 This has a limitation that you cannot explicitly check for None values.
448
449 Examples, with the subdict queries on the left:
450 - None matches any object.
451 - {"foo": None} matches {"foo": {"bar": 1}}
452 - {"foo": None} matches {"foo": 5}
453 - {"foo": {"abc": None}} does not match {"foo": {"bar": 1}}
454 - {"foo": {"rab": 2}} matches {"foo": {"bar": 1, "rab": 2}}
455 """
456 if match is None:
457 return True
458
459 try:
460 for key in match:
461 if key in event:
462 if not QEMUMachine.event_match(event[key], match[key]):
463 return False
464 else:
465 return False
466 return True
467 except TypeError:
468 # either match or event wasn't iterable (not a dict)
469 return match == event
470
471 def event_wait(self, name, timeout=60.0, match=None):
472 """
473 event_wait waits for and returns a named event from QMP with a timeout.
474
475 name: The event to wait for.
476 timeout: QEMUMonitorProtocol.pull_event timeout parameter.
477 match: Optional match criteria. See event_match for details.
478 """
479 return self.events_wait([(name, match)], timeout)
480
481 def events_wait(self, events, timeout=60.0):
482 """
483 events_wait waits for and returns a named event from QMP with a timeout.
484
485 events: a sequence of (name, match_criteria) tuples.
486 The match criteria are optional and may be None.
487 See event_match for details.
488 timeout: QEMUMonitorProtocol.pull_event timeout parameter.
489 """
490 def _match(event):
491 for name, match in events:
John Snow306dfcd2019-06-27 17:28:15 -0400492 if event['event'] == name and self.event_match(event, match):
John Snowabf0bf92019-06-27 17:28:14 -0400493 return True
494 return False
495
496 # Search cached events
497 for event in self._events:
498 if _match(event):
499 self._events.remove(event)
500 return event
501
502 # Poll for new events
503 while True:
504 event = self._qmp.pull_event(wait=timeout)
505 if _match(event):
506 return event
507 self._events.append(event)
508
509 return None
510
511 def get_log(self):
512 """
513 After self.shutdown or failed qemu execution, this returns the output
514 of the qemu process.
515 """
516 return self._iolog
517
518 def add_args(self, *args):
519 """
520 Adds to the list of extra arguments to be given to the QEMU binary
521 """
522 self._args.extend(args)
523
524 def set_machine(self, machine_type):
525 """
526 Sets the machine type
527
528 If set, the machine type will be added to the base arguments
529 of the resulting QEMU command line.
530 """
531 self._machine = machine_type
532
Philippe Mathieu-Daudé746f2442020-01-21 00:51:56 +0100533 def set_console(self, device_type=None, console_index=0):
John Snowabf0bf92019-06-27 17:28:14 -0400534 """
535 Sets the device type for a console device
536
537 If set, the console device and a backing character device will
538 be added to the base arguments of the resulting QEMU command
539 line.
540
541 This is a convenience method that will either use the provided
542 device type, or default to a "-serial chardev:console" command
543 line argument.
544
545 The actual setting of command line arguments will be be done at
546 machine launch time, as it depends on the temporary directory
547 to be created.
548
549 @param device_type: the device type, such as "isa-serial". If
550 None is given (the default value) a "-serial
551 chardev:console" command line argument will
552 be used instead, resorting to the machine's
553 default device type.
Philippe Mathieu-Daudé746f2442020-01-21 00:51:56 +0100554 @param console_index: the index of the console device to use.
555 If not zero, the command line will create
556 'index - 1' consoles and connect them to
557 the 'null' backing character device.
John Snowabf0bf92019-06-27 17:28:14 -0400558 """
559 self._console_set = True
560 self._console_device_type = device_type
Philippe Mathieu-Daudé746f2442020-01-21 00:51:56 +0100561 self._console_index = console_index
John Snowabf0bf92019-06-27 17:28:14 -0400562
563 @property
564 def console_socket(self):
565 """
566 Returns a socket connected to the console
567 """
568 if self._console_socket is None:
569 self._console_socket = socket.socket(socket.AF_UNIX,
570 socket.SOCK_STREAM)
571 self._console_socket.connect(self._console_address)
572 return self._console_socket