blob: 49445e675b8a7b08729bd39bd7d7d5f6570fae84 [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,
74 socket_scm_helper=None):
75 '''
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()
93 self._name = name
94 self._monitor_address = monitor_address
95 self._vm_monitor = None
96 self._qemu_log_path = None
97 self._qemu_log_file = None
98 self._popen = None
99 self._binary = binary
100 self._args = list(args) # Force copy args in case we modify them
101 self._wrapper = wrapper
102 self._events = []
103 self._iolog = None
104 self._socket_scm_helper = socket_scm_helper
105 self._qmp = None
106 self._qemu_full_args = None
107 self._test_dir = test_dir
108 self._temp_dir = None
109 self._launched = False
110 self._machine = None
111 self._console_set = False
112 self._console_device_type = None
113 self._console_address = None
114 self._console_socket = None
115
116 # just in case logging wasn't configured by the main script:
117 logging.basicConfig()
118
119 def __enter__(self):
120 return self
121
122 def __exit__(self, exc_type, exc_val, exc_tb):
123 self.shutdown()
124 return False
125
John Snowabf0bf92019-06-27 17:28:14 -0400126 def add_monitor_null(self):
John Snow306dfcd2019-06-27 17:28:15 -0400127 """
128 This can be used to add an unused monitor instance.
129 """
John Snowabf0bf92019-06-27 17:28:14 -0400130 self._args.append('-monitor')
131 self._args.append('null')
132
133 def add_fd(self, fd, fdset, opaque, opts=''):
134 """
135 Pass a file descriptor to the VM
136 """
137 options = ['fd=%d' % fd,
138 'set=%d' % fdset,
139 'opaque=%s' % opaque]
140 if opts:
141 options.append(opts)
142
143 # This did not exist before 3.4, but since then it is
144 # mandatory for our purpose
145 if hasattr(os, 'set_inheritable'):
146 os.set_inheritable(fd, True)
147
148 self._args.append('-add-fd')
149 self._args.append(','.join(options))
150 return self
151
John Snowabf0bf92019-06-27 17:28:14 -0400152 def send_fd_scm(self, fd=None, file_path=None):
John Snow306dfcd2019-06-27 17:28:15 -0400153 """
154 Send an fd or file_path to socket_scm_helper.
155
156 Exactly one of fd and file_path must be given.
157 If it is file_path, the helper will open that file and pass its own fd.
158 """
John Snowabf0bf92019-06-27 17:28:14 -0400159 # In iotest.py, the qmp should always use unix socket.
160 assert self._qmp.is_scm_available()
161 if self._socket_scm_helper is None:
162 raise QEMUMachineError("No path to socket_scm_helper set")
163 if not os.path.exists(self._socket_scm_helper):
164 raise QEMUMachineError("%s does not exist" %
165 self._socket_scm_helper)
166
167 # This did not exist before 3.4, but since then it is
168 # mandatory for our purpose
169 if hasattr(os, 'set_inheritable'):
170 os.set_inheritable(self._qmp.get_sock_fd(), True)
171 if fd is not None:
172 os.set_inheritable(fd, True)
173
174 fd_param = ["%s" % self._socket_scm_helper,
175 "%d" % self._qmp.get_sock_fd()]
176
177 if file_path is not None:
178 assert fd is None
179 fd_param.append(file_path)
180 else:
181 assert fd is not None
182 fd_param.append(str(fd))
183
184 devnull = open(os.path.devnull, 'rb')
185 proc = subprocess.Popen(fd_param, stdin=devnull, stdout=subprocess.PIPE,
186 stderr=subprocess.STDOUT, close_fds=False)
187 output = proc.communicate()[0]
188 if output:
189 LOG.debug(output)
190
191 return proc.returncode
192
193 @staticmethod
194 def _remove_if_exists(path):
195 """
196 Remove file object at path if it exists
197 """
198 try:
199 os.remove(path)
200 except OSError as exception:
201 if exception.errno == errno.ENOENT:
202 return
203 raise
204
205 def is_running(self):
John Snow306dfcd2019-06-27 17:28:15 -0400206 """Returns true if the VM is running."""
John Snowabf0bf92019-06-27 17:28:14 -0400207 return self._popen is not None and self._popen.poll() is None
208
209 def exitcode(self):
John Snow306dfcd2019-06-27 17:28:15 -0400210 """Returns the exit code if possible, or None."""
John Snowabf0bf92019-06-27 17:28:14 -0400211 if self._popen is None:
212 return None
213 return self._popen.poll()
214
215 def get_pid(self):
John Snow306dfcd2019-06-27 17:28:15 -0400216 """Returns the PID of the running process, or None."""
John Snowabf0bf92019-06-27 17:28:14 -0400217 if not self.is_running():
218 return None
219 return self._popen.pid
220
221 def _load_io_log(self):
222 if self._qemu_log_path is not None:
223 with open(self._qemu_log_path, "r") as iolog:
224 self._iolog = iolog.read()
225
226 def _base_args(self):
227 if isinstance(self._monitor_address, tuple):
228 moncdev = "socket,id=mon,host=%s,port=%s" % (
229 self._monitor_address[0],
230 self._monitor_address[1])
231 else:
232 moncdev = 'socket,id=mon,path=%s' % self._vm_monitor
233 args = ['-chardev', moncdev,
234 '-mon', 'chardev=mon,mode=control',
235 '-display', 'none', '-vga', 'none']
236 if self._machine is not None:
237 args.extend(['-machine', self._machine])
238 if self._console_set:
239 self._console_address = os.path.join(self._temp_dir,
240 self._name + "-console.sock")
241 chardev = ('socket,id=console,path=%s,server,nowait' %
242 self._console_address)
243 args.extend(['-chardev', chardev])
244 if self._console_device_type is None:
245 args.extend(['-serial', 'chardev:console'])
246 else:
247 device = '%s,chardev=console' % self._console_device_type
248 args.extend(['-device', device])
249 return args
250
251 def _pre_launch(self):
252 self._temp_dir = tempfile.mkdtemp(dir=self._test_dir)
253 if self._monitor_address is not None:
254 self._vm_monitor = self._monitor_address
255 else:
256 self._vm_monitor = os.path.join(self._temp_dir,
257 self._name + "-monitor.sock")
258 self._qemu_log_path = os.path.join(self._temp_dir, self._name + ".log")
259 self._qemu_log_file = open(self._qemu_log_path, 'wb')
260
261 self._qmp = qmp.QEMUMonitorProtocol(self._vm_monitor,
262 server=True)
263
264 def _post_launch(self):
265 self._qmp.accept()
266
267 def _post_shutdown(self):
268 if self._qemu_log_file is not None:
269 self._qemu_log_file.close()
270 self._qemu_log_file = None
271
272 self._qemu_log_path = None
273
274 if self._console_socket is not None:
275 self._console_socket.close()
276 self._console_socket = None
277
278 if self._temp_dir is not None:
279 shutil.rmtree(self._temp_dir)
280 self._temp_dir = None
281
282 def launch(self):
283 """
284 Launch the VM and make sure we cleanup and expose the
285 command line/output in case of exception
286 """
287
288 if self._launched:
289 raise QEMUMachineError('VM already launched')
290
291 self._iolog = None
292 self._qemu_full_args = None
293 try:
294 self._launch()
295 self._launched = True
296 except:
297 self.shutdown()
298
299 LOG.debug('Error launching VM')
300 if self._qemu_full_args:
301 LOG.debug('Command: %r', ' '.join(self._qemu_full_args))
302 if self._iolog:
303 LOG.debug('Output: %r', self._iolog)
304 raise
305
306 def _launch(self):
307 """
308 Launch the VM and establish a QMP connection
309 """
310 devnull = open(os.path.devnull, 'rb')
311 self._pre_launch()
312 self._qemu_full_args = (self._wrapper + [self._binary] +
313 self._base_args() + self._args)
314 LOG.debug('VM launch command: %r', ' '.join(self._qemu_full_args))
315 self._popen = subprocess.Popen(self._qemu_full_args,
316 stdin=devnull,
317 stdout=self._qemu_log_file,
318 stderr=subprocess.STDOUT,
319 shell=False,
320 close_fds=False)
321 self._post_launch()
322
323 def wait(self):
324 """
325 Wait for the VM to power off
326 """
327 self._popen.wait()
328 self._qmp.close()
329 self._load_io_log()
330 self._post_shutdown()
331
332 def shutdown(self):
333 """
334 Terminate the VM and clean up
335 """
336 if self.is_running():
337 try:
338 self._qmp.cmd('quit')
339 self._qmp.close()
340 except:
341 self._popen.kill()
342 self._popen.wait()
343
344 self._load_io_log()
345 self._post_shutdown()
346
347 exitcode = self.exitcode()
348 if exitcode is not None and exitcode < 0:
349 msg = 'qemu received signal %i: %s'
350 if self._qemu_full_args:
351 command = ' '.join(self._qemu_full_args)
352 else:
353 command = ''
John Snow306dfcd2019-06-27 17:28:15 -0400354 LOG.warning(msg, -exitcode, command)
John Snowabf0bf92019-06-27 17:28:14 -0400355
356 self._launched = False
357
358 def qmp(self, cmd, conv_keys=True, **args):
359 """
360 Invoke a QMP command and return the response dict
361 """
362 qmp_args = dict()
363 for key, value in args.items():
364 if conv_keys:
365 qmp_args[key.replace('_', '-')] = value
366 else:
367 qmp_args[key] = value
368
369 return self._qmp.cmd(cmd, args=qmp_args)
370
371 def command(self, cmd, conv_keys=True, **args):
372 """
373 Invoke a QMP command.
374 On success return the response dict.
375 On failure raise an exception.
376 """
377 reply = self.qmp(cmd, conv_keys, **args)
378 if reply is None:
379 raise qmp.QMPError("Monitor is closed")
380 if "error" in reply:
381 raise MonitorResponseError(reply)
382 return reply["return"]
383
384 def get_qmp_event(self, wait=False):
385 """
386 Poll for one queued QMP events and return it
387 """
John Snow306dfcd2019-06-27 17:28:15 -0400388 if self._events:
John Snowabf0bf92019-06-27 17:28:14 -0400389 return self._events.pop(0)
390 return self._qmp.pull_event(wait=wait)
391
392 def get_qmp_events(self, wait=False):
393 """
394 Poll for queued QMP events and return a list of dicts
395 """
396 events = self._qmp.get_events(wait=wait)
397 events.extend(self._events)
398 del self._events[:]
399 self._qmp.clear_events()
400 return events
401
402 @staticmethod
403 def event_match(event, match=None):
404 """
405 Check if an event matches optional match criteria.
406
407 The match criteria takes the form of a matching subdict. The event is
408 checked to be a superset of the subdict, recursively, with matching
409 values whenever the subdict values are not None.
410
411 This has a limitation that you cannot explicitly check for None values.
412
413 Examples, with the subdict queries on the left:
414 - None matches any object.
415 - {"foo": None} matches {"foo": {"bar": 1}}
416 - {"foo": None} matches {"foo": 5}
417 - {"foo": {"abc": None}} does not match {"foo": {"bar": 1}}
418 - {"foo": {"rab": 2}} matches {"foo": {"bar": 1, "rab": 2}}
419 """
420 if match is None:
421 return True
422
423 try:
424 for key in match:
425 if key in event:
426 if not QEMUMachine.event_match(event[key], match[key]):
427 return False
428 else:
429 return False
430 return True
431 except TypeError:
432 # either match or event wasn't iterable (not a dict)
433 return match == event
434
435 def event_wait(self, name, timeout=60.0, match=None):
436 """
437 event_wait waits for and returns a named event from QMP with a timeout.
438
439 name: The event to wait for.
440 timeout: QEMUMonitorProtocol.pull_event timeout parameter.
441 match: Optional match criteria. See event_match for details.
442 """
443 return self.events_wait([(name, match)], timeout)
444
445 def events_wait(self, events, timeout=60.0):
446 """
447 events_wait waits for and returns a named event from QMP with a timeout.
448
449 events: a sequence of (name, match_criteria) tuples.
450 The match criteria are optional and may be None.
451 See event_match for details.
452 timeout: QEMUMonitorProtocol.pull_event timeout parameter.
453 """
454 def _match(event):
455 for name, match in events:
John Snow306dfcd2019-06-27 17:28:15 -0400456 if event['event'] == name and self.event_match(event, match):
John Snowabf0bf92019-06-27 17:28:14 -0400457 return True
458 return False
459
460 # Search cached events
461 for event in self._events:
462 if _match(event):
463 self._events.remove(event)
464 return event
465
466 # Poll for new events
467 while True:
468 event = self._qmp.pull_event(wait=timeout)
469 if _match(event):
470 return event
471 self._events.append(event)
472
473 return None
474
475 def get_log(self):
476 """
477 After self.shutdown or failed qemu execution, this returns the output
478 of the qemu process.
479 """
480 return self._iolog
481
482 def add_args(self, *args):
483 """
484 Adds to the list of extra arguments to be given to the QEMU binary
485 """
486 self._args.extend(args)
487
488 def set_machine(self, machine_type):
489 """
490 Sets the machine type
491
492 If set, the machine type will be added to the base arguments
493 of the resulting QEMU command line.
494 """
495 self._machine = machine_type
496
497 def set_console(self, device_type=None):
498 """
499 Sets the device type for a console device
500
501 If set, the console device and a backing character device will
502 be added to the base arguments of the resulting QEMU command
503 line.
504
505 This is a convenience method that will either use the provided
506 device type, or default to a "-serial chardev:console" command
507 line argument.
508
509 The actual setting of command line arguments will be be done at
510 machine launch time, as it depends on the temporary directory
511 to be created.
512
513 @param device_type: the device type, such as "isa-serial". If
514 None is given (the default value) a "-serial
515 chardev:console" command line argument will
516 be used instead, resorting to the machine's
517 default device type.
518 """
519 self._console_set = True
520 self._console_device_type = device_type
521
522 @property
523 def console_socket(self):
524 """
525 Returns a socket connected to the console
526 """
527 if self._console_socket is None:
528 self._console_socket = socket.socket(socket.AF_UNIX,
529 socket.SOCK_STREAM)
530 self._console_socket.connect(self._console_address)
531 return self._console_socket