blob: 95a20a17f9627f33ab9a6bcf46f6759303754ecf [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
John Snow8dfac2e2020-05-28 18:21:29 -040032
John Snowabf0bf92019-06-27 17:28:14 -040033class QEMUMachineError(Exception):
34 """
35 Exception called when an error in QEMUMachine happens.
36 """
37
38
39class QEMUMachineAddDeviceError(QEMUMachineError):
40 """
41 Exception raised when a request to add a device can not be fulfilled
42
43 The failures are caused by limitations, lack of information or conflicting
44 requests on the QEMUMachine methods. This exception does not represent
45 failures reported by the QEMU binary itself.
46 """
47
48
49class MonitorResponseError(qmp.QMPError):
50 """
51 Represents erroneous QMP monitor reply
52 """
53 def __init__(self, reply):
54 try:
55 desc = reply["error"]["desc"]
56 except KeyError:
57 desc = reply
John Snow3797dbc2020-05-14 01:53:42 -040058 super().__init__(desc)
John Snowabf0bf92019-06-27 17:28:14 -040059 self.reply = reply
60
61
John Snow9b8ccd62020-05-28 18:21:28 -040062class QEMUMachine:
John Snowabf0bf92019-06-27 17:28:14 -040063 """
64 A QEMU VM
65
John Snow8dfac2e2020-05-28 18:21:29 -040066 Use this object as a context manager to ensure
67 the QEMU process terminates::
John Snowabf0bf92019-06-27 17:28:14 -040068
69 with VM(binary) as vm:
70 ...
71 # vm is guaranteed to be shut down here
72 """
73
74 def __init__(self, binary, args=None, wrapper=None, name=None,
75 test_dir="/var/tmp", monitor_address=None,
Max Reitz32558ce2019-10-17 15:31:34 +020076 socket_scm_helper=None, sock_dir=None):
John Snowabf0bf92019-06-27 17:28:14 -040077 '''
78 Initialize a QEMUMachine
79
80 @param binary: path to the qemu binary
81 @param args: list of extra arguments
82 @param wrapper: list of arguments used as prefix to qemu binary
83 @param name: prefix for socket and log file names (default: qemu-PID)
84 @param test_dir: where to create socket and log file
85 @param monitor_address: address for QMP monitor
86 @param socket_scm_helper: helper program, required for send_fd_scm()
87 @note: Qemu process is not started until launch() is used.
88 '''
89 if args is None:
90 args = []
91 if wrapper is None:
92 wrapper = []
93 if name is None:
94 name = "qemu-%d" % os.getpid()
Max Reitz32558ce2019-10-17 15:31:34 +020095 if sock_dir is None:
96 sock_dir = test_dir
John Snowabf0bf92019-06-27 17:28:14 -040097 self._name = name
98 self._monitor_address = monitor_address
99 self._vm_monitor = None
100 self._qemu_log_path = None
101 self._qemu_log_file = None
102 self._popen = None
103 self._binary = binary
104 self._args = list(args) # Force copy args in case we modify them
105 self._wrapper = wrapper
106 self._events = []
107 self._iolog = None
108 self._socket_scm_helper = socket_scm_helper
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500109 self._qmp_set = True # Enable QMP monitor by default.
John Snowabf0bf92019-06-27 17:28:14 -0400110 self._qmp = None
111 self._qemu_full_args = None
112 self._test_dir = test_dir
113 self._temp_dir = None
Max Reitz32558ce2019-10-17 15:31:34 +0200114 self._sock_dir = sock_dir
John Snowabf0bf92019-06-27 17:28:14 -0400115 self._launched = False
116 self._machine = None
Philippe Mathieu-Daudé746f2442020-01-21 00:51:56 +0100117 self._console_index = 0
John Snowabf0bf92019-06-27 17:28:14 -0400118 self._console_set = False
119 self._console_device_type = None
120 self._console_address = None
121 self._console_socket = None
Max Reitz32558ce2019-10-17 15:31:34 +0200122 self._remove_files = []
John Snowabf0bf92019-06-27 17:28:14 -0400123
John Snowabf0bf92019-06-27 17:28:14 -0400124 def __enter__(self):
125 return self
126
127 def __exit__(self, exc_type, exc_val, exc_tb):
128 self.shutdown()
129 return False
130
John Snowabf0bf92019-06-27 17:28:14 -0400131 def add_monitor_null(self):
John Snow306dfcd2019-06-27 17:28:15 -0400132 """
133 This can be used to add an unused monitor instance.
134 """
John Snowabf0bf92019-06-27 17:28:14 -0400135 self._args.append('-monitor')
136 self._args.append('null')
137
138 def add_fd(self, fd, fdset, opaque, opts=''):
139 """
140 Pass a file descriptor to the VM
141 """
142 options = ['fd=%d' % fd,
143 'set=%d' % fdset,
144 'opaque=%s' % opaque]
145 if opts:
146 options.append(opts)
147
148 # This did not exist before 3.4, but since then it is
149 # mandatory for our purpose
150 if hasattr(os, 'set_inheritable'):
151 os.set_inheritable(fd, True)
152
153 self._args.append('-add-fd')
154 self._args.append(','.join(options))
155 return self
156
John Snowabf0bf92019-06-27 17:28:14 -0400157 def send_fd_scm(self, fd=None, file_path=None):
John Snow306dfcd2019-06-27 17:28:15 -0400158 """
159 Send an fd or file_path to socket_scm_helper.
160
161 Exactly one of fd and file_path must be given.
162 If it is file_path, the helper will open that file and pass its own fd.
163 """
John Snowabf0bf92019-06-27 17:28:14 -0400164 # In iotest.py, the qmp should always use unix socket.
165 assert self._qmp.is_scm_available()
166 if self._socket_scm_helper is None:
167 raise QEMUMachineError("No path to socket_scm_helper set")
168 if not os.path.exists(self._socket_scm_helper):
169 raise QEMUMachineError("%s does not exist" %
170 self._socket_scm_helper)
171
172 # This did not exist before 3.4, but since then it is
173 # mandatory for our purpose
174 if hasattr(os, 'set_inheritable'):
175 os.set_inheritable(self._qmp.get_sock_fd(), True)
176 if fd is not None:
177 os.set_inheritable(fd, True)
178
179 fd_param = ["%s" % self._socket_scm_helper,
180 "%d" % self._qmp.get_sock_fd()]
181
182 if file_path is not None:
183 assert fd is None
184 fd_param.append(file_path)
185 else:
186 assert fd is not None
187 fd_param.append(str(fd))
188
189 devnull = open(os.path.devnull, 'rb')
John Snow8dfac2e2020-05-28 18:21:29 -0400190 proc = subprocess.Popen(
191 fd_param, stdin=devnull, stdout=subprocess.PIPE,
192 stderr=subprocess.STDOUT, close_fds=False
193 )
John Snowabf0bf92019-06-27 17:28:14 -0400194 output = proc.communicate()[0]
195 if output:
196 LOG.debug(output)
197
198 return proc.returncode
199
200 @staticmethod
201 def _remove_if_exists(path):
202 """
203 Remove file object at path if it exists
204 """
205 try:
206 os.remove(path)
207 except OSError as exception:
208 if exception.errno == errno.ENOENT:
209 return
210 raise
211
212 def is_running(self):
John Snow306dfcd2019-06-27 17:28:15 -0400213 """Returns true if the VM is running."""
John Snowabf0bf92019-06-27 17:28:14 -0400214 return self._popen is not None and self._popen.poll() is None
215
216 def exitcode(self):
John Snow306dfcd2019-06-27 17:28:15 -0400217 """Returns the exit code if possible, or None."""
John Snowabf0bf92019-06-27 17:28:14 -0400218 if self._popen is None:
219 return None
220 return self._popen.poll()
221
222 def get_pid(self):
John Snow306dfcd2019-06-27 17:28:15 -0400223 """Returns the PID of the running process, or None."""
John Snowabf0bf92019-06-27 17:28:14 -0400224 if not self.is_running():
225 return None
226 return self._popen.pid
227
228 def _load_io_log(self):
229 if self._qemu_log_path is not None:
230 with open(self._qemu_log_path, "r") as iolog:
231 self._iolog = iolog.read()
232
233 def _base_args(self):
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500234 args = ['-display', 'none', '-vga', 'none']
235 if self._qmp_set:
236 if isinstance(self._monitor_address, tuple):
237 moncdev = "socket,id=mon,host=%s,port=%s" % (
238 self._monitor_address[0],
239 self._monitor_address[1])
240 else:
241 moncdev = 'socket,id=mon,path=%s' % self._vm_monitor
242 args.extend(['-chardev', moncdev, '-mon',
243 'chardev=mon,mode=control'])
John Snowabf0bf92019-06-27 17:28:14 -0400244 if self._machine is not None:
245 args.extend(['-machine', self._machine])
John Snow9b8ccd62020-05-28 18:21:28 -0400246 for _ in range(self._console_index):
Philippe Mathieu-Daudé746f2442020-01-21 00:51:56 +0100247 args.extend(['-serial', 'null'])
John Snowabf0bf92019-06-27 17:28:14 -0400248 if self._console_set:
Max Reitz32558ce2019-10-17 15:31:34 +0200249 self._console_address = os.path.join(self._sock_dir,
John Snowabf0bf92019-06-27 17:28:14 -0400250 self._name + "-console.sock")
Max Reitz32558ce2019-10-17 15:31:34 +0200251 self._remove_files.append(self._console_address)
John Snowabf0bf92019-06-27 17:28:14 -0400252 chardev = ('socket,id=console,path=%s,server,nowait' %
253 self._console_address)
254 args.extend(['-chardev', chardev])
255 if self._console_device_type is None:
256 args.extend(['-serial', 'chardev:console'])
257 else:
258 device = '%s,chardev=console' % self._console_device_type
259 args.extend(['-device', device])
260 return args
261
262 def _pre_launch(self):
263 self._temp_dir = tempfile.mkdtemp(dir=self._test_dir)
John Snowabf0bf92019-06-27 17:28:14 -0400264 self._qemu_log_path = os.path.join(self._temp_dir, self._name + ".log")
265 self._qemu_log_file = open(self._qemu_log_path, 'wb')
266
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500267 if self._qmp_set:
268 if self._monitor_address is not None:
269 self._vm_monitor = self._monitor_address
270 else:
271 self._vm_monitor = os.path.join(self._sock_dir,
272 self._name + "-monitor.sock")
273 self._remove_files.append(self._vm_monitor)
Oksana Vohchana566054a2020-03-16 12:32:03 +0200274 self._qmp = qmp.QEMUMonitorProtocol(self._vm_monitor, server=True,
275 nickname=self._name)
John Snowabf0bf92019-06-27 17:28:14 -0400276
277 def _post_launch(self):
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500278 if self._qmp:
279 self._qmp.accept()
John Snowabf0bf92019-06-27 17:28:14 -0400280
281 def _post_shutdown(self):
282 if self._qemu_log_file is not None:
283 self._qemu_log_file.close()
284 self._qemu_log_file = None
285
286 self._qemu_log_path = None
287
John Snowabf0bf92019-06-27 17:28:14 -0400288 if self._temp_dir is not None:
289 shutil.rmtree(self._temp_dir)
290 self._temp_dir = None
291
Max Reitz32558ce2019-10-17 15:31:34 +0200292 while len(self._remove_files) > 0:
293 self._remove_if_exists(self._remove_files.pop())
294
John Snowabf0bf92019-06-27 17:28:14 -0400295 def launch(self):
296 """
297 Launch the VM and make sure we cleanup and expose the
298 command line/output in case of exception
299 """
300
301 if self._launched:
302 raise QEMUMachineError('VM already launched')
303
304 self._iolog = None
305 self._qemu_full_args = None
306 try:
307 self._launch()
308 self._launched = True
309 except:
310 self.shutdown()
311
312 LOG.debug('Error launching VM')
313 if self._qemu_full_args:
314 LOG.debug('Command: %r', ' '.join(self._qemu_full_args))
315 if self._iolog:
316 LOG.debug('Output: %r', self._iolog)
317 raise
318
319 def _launch(self):
320 """
321 Launch the VM and establish a QMP connection
322 """
323 devnull = open(os.path.devnull, 'rb')
324 self._pre_launch()
325 self._qemu_full_args = (self._wrapper + [self._binary] +
326 self._base_args() + self._args)
327 LOG.debug('VM launch command: %r', ' '.join(self._qemu_full_args))
328 self._popen = subprocess.Popen(self._qemu_full_args,
329 stdin=devnull,
330 stdout=self._qemu_log_file,
331 stderr=subprocess.STDOUT,
332 shell=False,
333 close_fds=False)
334 self._post_launch()
335
336 def wait(self):
337 """
338 Wait for the VM to power off
339 """
340 self._popen.wait()
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500341 if self._qmp:
342 self._qmp.close()
John Snowabf0bf92019-06-27 17:28:14 -0400343 self._load_io_log()
344 self._post_shutdown()
345
Vladimir Sementsov-Ogievskiye0e925a2020-02-17 18:02:42 +0300346 def shutdown(self, has_quit=False, hard=False):
John Snowabf0bf92019-06-27 17:28:14 -0400347 """
348 Terminate the VM and clean up
349 """
Cleber Rosa08580962019-10-28 19:04:04 -0400350 # If we keep the console socket open, we may deadlock waiting
351 # for QEMU to exit, while QEMU is waiting for the socket to
352 # become writeable.
353 if self._console_socket is not None:
354 self._console_socket.close()
355 self._console_socket = None
356
John Snowabf0bf92019-06-27 17:28:14 -0400357 if self.is_running():
Vladimir Sementsov-Ogievskiye0e925a2020-02-17 18:02:42 +0300358 if hard:
359 self._popen.kill()
360 elif self._qmp:
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500361 try:
362 if not has_quit:
363 self._qmp.cmd('quit')
364 self._qmp.close()
Kevin Wolfcd87f5e2020-03-13 09:36:16 +0100365 self._popen.wait(timeout=3)
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500366 except:
367 self._popen.kill()
John Snowabf0bf92019-06-27 17:28:14 -0400368 self._popen.wait()
369
370 self._load_io_log()
371 self._post_shutdown()
372
373 exitcode = self.exitcode()
Vladimir Sementsov-Ogievskiye0e925a2020-02-17 18:02:42 +0300374 if exitcode is not None and exitcode < 0 and \
375 not (exitcode == -9 and hard):
John Snowabf0bf92019-06-27 17:28:14 -0400376 msg = 'qemu received signal %i: %s'
377 if self._qemu_full_args:
378 command = ' '.join(self._qemu_full_args)
379 else:
380 command = ''
John Snow9b8ccd62020-05-28 18:21:28 -0400381 LOG.warning(msg, -int(exitcode), command)
John Snowabf0bf92019-06-27 17:28:14 -0400382
383 self._launched = False
384
Vladimir Sementsov-Ogievskiye0e925a2020-02-17 18:02:42 +0300385 def kill(self):
386 self.shutdown(hard=True)
387
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500388 def set_qmp_monitor(self, enabled=True):
389 """
390 Set the QMP monitor.
391
392 @param enabled: if False, qmp monitor options will be removed from
393 the base arguments of the resulting QEMU command
394 line. Default is True.
395 @note: call this function before launch().
396 """
397 if enabled:
398 self._qmp_set = True
399 else:
400 self._qmp_set = False
401 self._qmp = None
402
John Snowabf0bf92019-06-27 17:28:14 -0400403 def qmp(self, cmd, conv_keys=True, **args):
404 """
405 Invoke a QMP command and return the response dict
406 """
407 qmp_args = dict()
408 for key, value in args.items():
409 if conv_keys:
410 qmp_args[key.replace('_', '-')] = value
411 else:
412 qmp_args[key] = value
413
414 return self._qmp.cmd(cmd, args=qmp_args)
415
416 def command(self, cmd, conv_keys=True, **args):
417 """
418 Invoke a QMP command.
419 On success return the response dict.
420 On failure raise an exception.
421 """
422 reply = self.qmp(cmd, conv_keys, **args)
423 if reply is None:
424 raise qmp.QMPError("Monitor is closed")
425 if "error" in reply:
426 raise MonitorResponseError(reply)
427 return reply["return"]
428
429 def get_qmp_event(self, wait=False):
430 """
431 Poll for one queued QMP events and return it
432 """
John Snow306dfcd2019-06-27 17:28:15 -0400433 if self._events:
John Snowabf0bf92019-06-27 17:28:14 -0400434 return self._events.pop(0)
435 return self._qmp.pull_event(wait=wait)
436
437 def get_qmp_events(self, wait=False):
438 """
439 Poll for queued QMP events and return a list of dicts
440 """
441 events = self._qmp.get_events(wait=wait)
442 events.extend(self._events)
443 del self._events[:]
444 self._qmp.clear_events()
445 return events
446
447 @staticmethod
448 def event_match(event, match=None):
449 """
450 Check if an event matches optional match criteria.
451
452 The match criteria takes the form of a matching subdict. The event is
453 checked to be a superset of the subdict, recursively, with matching
454 values whenever the subdict values are not None.
455
456 This has a limitation that you cannot explicitly check for None values.
457
458 Examples, with the subdict queries on the left:
459 - None matches any object.
460 - {"foo": None} matches {"foo": {"bar": 1}}
461 - {"foo": None} matches {"foo": 5}
462 - {"foo": {"abc": None}} does not match {"foo": {"bar": 1}}
463 - {"foo": {"rab": 2}} matches {"foo": {"bar": 1, "rab": 2}}
464 """
465 if match is None:
466 return True
467
468 try:
469 for key in match:
470 if key in event:
471 if not QEMUMachine.event_match(event[key], match[key]):
472 return False
473 else:
474 return False
475 return True
476 except TypeError:
477 # either match or event wasn't iterable (not a dict)
478 return match == event
479
480 def event_wait(self, name, timeout=60.0, match=None):
481 """
482 event_wait waits for and returns a named event from QMP with a timeout.
483
484 name: The event to wait for.
485 timeout: QEMUMonitorProtocol.pull_event timeout parameter.
486 match: Optional match criteria. See event_match for details.
487 """
488 return self.events_wait([(name, match)], timeout)
489
490 def events_wait(self, events, timeout=60.0):
491 """
John Snow8dfac2e2020-05-28 18:21:29 -0400492 events_wait waits for and returns a named event
493 from QMP with a timeout.
John Snowabf0bf92019-06-27 17:28:14 -0400494
495 events: a sequence of (name, match_criteria) tuples.
496 The match criteria are optional and may be None.
497 See event_match for details.
498 timeout: QEMUMonitorProtocol.pull_event timeout parameter.
499 """
500 def _match(event):
501 for name, match in events:
John Snow306dfcd2019-06-27 17:28:15 -0400502 if event['event'] == name and self.event_match(event, match):
John Snowabf0bf92019-06-27 17:28:14 -0400503 return True
504 return False
505
506 # Search cached events
507 for event in self._events:
508 if _match(event):
509 self._events.remove(event)
510 return event
511
512 # Poll for new events
513 while True:
514 event = self._qmp.pull_event(wait=timeout)
515 if _match(event):
516 return event
517 self._events.append(event)
518
519 return None
520
521 def get_log(self):
522 """
523 After self.shutdown or failed qemu execution, this returns the output
524 of the qemu process.
525 """
526 return self._iolog
527
528 def add_args(self, *args):
529 """
530 Adds to the list of extra arguments to be given to the QEMU binary
531 """
532 self._args.extend(args)
533
534 def set_machine(self, machine_type):
535 """
536 Sets the machine type
537
538 If set, the machine type will be added to the base arguments
539 of the resulting QEMU command line.
540 """
541 self._machine = machine_type
542
Philippe Mathieu-Daudé746f2442020-01-21 00:51:56 +0100543 def set_console(self, device_type=None, console_index=0):
John Snowabf0bf92019-06-27 17:28:14 -0400544 """
545 Sets the device type for a console device
546
547 If set, the console device and a backing character device will
548 be added to the base arguments of the resulting QEMU command
549 line.
550
551 This is a convenience method that will either use the provided
552 device type, or default to a "-serial chardev:console" command
553 line argument.
554
555 The actual setting of command line arguments will be be done at
556 machine launch time, as it depends on the temporary directory
557 to be created.
558
559 @param device_type: the device type, such as "isa-serial". If
560 None is given (the default value) a "-serial
561 chardev:console" command line argument will
562 be used instead, resorting to the machine's
563 default device type.
Philippe Mathieu-Daudé746f2442020-01-21 00:51:56 +0100564 @param console_index: the index of the console device to use.
565 If not zero, the command line will create
566 'index - 1' consoles and connect them to
567 the 'null' backing character device.
John Snowabf0bf92019-06-27 17:28:14 -0400568 """
569 self._console_set = True
570 self._console_device_type = device_type
Philippe Mathieu-Daudé746f2442020-01-21 00:51:56 +0100571 self._console_index = console_index
John Snowabf0bf92019-06-27 17:28:14 -0400572
573 @property
574 def console_socket(self):
575 """
576 Returns a socket connected to the console
577 """
578 if self._console_socket is None:
579 self._console_socket = socket.socket(socket.AF_UNIX,
580 socket.SOCK_STREAM)
581 self._console_socket.connect(self._console_address)
582 return self._console_socket