blob: a8a311b0355d56dfea2641f791b54162d428c1ab [file] [log] [blame]
John Snowabf0bf92019-06-27 17:28:14 -04001# QEMU library
2#
3# Copyright (C) 2015-2016 Red Hat Inc.
4# Copyright (C) 2012 IBM Corp.
5#
6# Authors:
7# Fam Zheng <famz@redhat.com>
8#
9# This work is licensed under the terms of the GNU GPL, version 2. See
10# the COPYING file in the top-level directory.
11#
12# Based on qmp.py.
13#
14
15import errno
16import logging
17import os
18import subprocess
19import re
20import shutil
21import socket
22import tempfile
23
24from . import qmp
25
26LOG = logging.getLogger(__name__)
27
28class QEMUMachineError(Exception):
29 """
30 Exception called when an error in QEMUMachine happens.
31 """
32
33
34class QEMUMachineAddDeviceError(QEMUMachineError):
35 """
36 Exception raised when a request to add a device can not be fulfilled
37
38 The failures are caused by limitations, lack of information or conflicting
39 requests on the QEMUMachine methods. This exception does not represent
40 failures reported by the QEMU binary itself.
41 """
42
43
44class MonitorResponseError(qmp.QMPError):
45 """
46 Represents erroneous QMP monitor reply
47 """
48 def __init__(self, reply):
49 try:
50 desc = reply["error"]["desc"]
51 except KeyError:
52 desc = reply
53 super(MonitorResponseError, self).__init__(desc)
54 self.reply = reply
55
56
57class QEMUMachine(object):
58 """
59 A QEMU VM
60
61 Use this object as a context manager to ensure the QEMU process terminates::
62
63 with VM(binary) as vm:
64 ...
65 # vm is guaranteed to be shut down here
66 """
67
68 def __init__(self, binary, args=None, wrapper=None, name=None,
69 test_dir="/var/tmp", monitor_address=None,
70 socket_scm_helper=None):
71 '''
72 Initialize a QEMUMachine
73
74 @param binary: path to the qemu binary
75 @param args: list of extra arguments
76 @param wrapper: list of arguments used as prefix to qemu binary
77 @param name: prefix for socket and log file names (default: qemu-PID)
78 @param test_dir: where to create socket and log file
79 @param monitor_address: address for QMP monitor
80 @param socket_scm_helper: helper program, required for send_fd_scm()
81 @note: Qemu process is not started until launch() is used.
82 '''
83 if args is None:
84 args = []
85 if wrapper is None:
86 wrapper = []
87 if name is None:
88 name = "qemu-%d" % os.getpid()
89 self._name = name
90 self._monitor_address = monitor_address
91 self._vm_monitor = None
92 self._qemu_log_path = None
93 self._qemu_log_file = None
94 self._popen = None
95 self._binary = binary
96 self._args = list(args) # Force copy args in case we modify them
97 self._wrapper = wrapper
98 self._events = []
99 self._iolog = None
100 self._socket_scm_helper = socket_scm_helper
101 self._qmp = None
102 self._qemu_full_args = None
103 self._test_dir = test_dir
104 self._temp_dir = None
105 self._launched = False
106 self._machine = None
107 self._console_set = False
108 self._console_device_type = None
109 self._console_address = None
110 self._console_socket = None
111
112 # just in case logging wasn't configured by the main script:
113 logging.basicConfig()
114
115 def __enter__(self):
116 return self
117
118 def __exit__(self, exc_type, exc_val, exc_tb):
119 self.shutdown()
120 return False
121
122 # This can be used to add an unused monitor instance.
123 def add_monitor_null(self):
124 self._args.append('-monitor')
125 self._args.append('null')
126
127 def add_fd(self, fd, fdset, opaque, opts=''):
128 """
129 Pass a file descriptor to the VM
130 """
131 options = ['fd=%d' % fd,
132 'set=%d' % fdset,
133 'opaque=%s' % opaque]
134 if opts:
135 options.append(opts)
136
137 # This did not exist before 3.4, but since then it is
138 # mandatory for our purpose
139 if hasattr(os, 'set_inheritable'):
140 os.set_inheritable(fd, True)
141
142 self._args.append('-add-fd')
143 self._args.append(','.join(options))
144 return self
145
146 # Exactly one of fd and file_path must be given.
147 # (If it is file_path, the helper will open that file and pass its
148 # own fd)
149 def send_fd_scm(self, fd=None, file_path=None):
150 # In iotest.py, the qmp should always use unix socket.
151 assert self._qmp.is_scm_available()
152 if self._socket_scm_helper is None:
153 raise QEMUMachineError("No path to socket_scm_helper set")
154 if not os.path.exists(self._socket_scm_helper):
155 raise QEMUMachineError("%s does not exist" %
156 self._socket_scm_helper)
157
158 # This did not exist before 3.4, but since then it is
159 # mandatory for our purpose
160 if hasattr(os, 'set_inheritable'):
161 os.set_inheritable(self._qmp.get_sock_fd(), True)
162 if fd is not None:
163 os.set_inheritable(fd, True)
164
165 fd_param = ["%s" % self._socket_scm_helper,
166 "%d" % self._qmp.get_sock_fd()]
167
168 if file_path is not None:
169 assert fd is None
170 fd_param.append(file_path)
171 else:
172 assert fd is not None
173 fd_param.append(str(fd))
174
175 devnull = open(os.path.devnull, 'rb')
176 proc = subprocess.Popen(fd_param, stdin=devnull, stdout=subprocess.PIPE,
177 stderr=subprocess.STDOUT, close_fds=False)
178 output = proc.communicate()[0]
179 if output:
180 LOG.debug(output)
181
182 return proc.returncode
183
184 @staticmethod
185 def _remove_if_exists(path):
186 """
187 Remove file object at path if it exists
188 """
189 try:
190 os.remove(path)
191 except OSError as exception:
192 if exception.errno == errno.ENOENT:
193 return
194 raise
195
196 def is_running(self):
197 return self._popen is not None and self._popen.poll() is None
198
199 def exitcode(self):
200 if self._popen is None:
201 return None
202 return self._popen.poll()
203
204 def get_pid(self):
205 if not self.is_running():
206 return None
207 return self._popen.pid
208
209 def _load_io_log(self):
210 if self._qemu_log_path is not None:
211 with open(self._qemu_log_path, "r") as iolog:
212 self._iolog = iolog.read()
213
214 def _base_args(self):
215 if isinstance(self._monitor_address, tuple):
216 moncdev = "socket,id=mon,host=%s,port=%s" % (
217 self._monitor_address[0],
218 self._monitor_address[1])
219 else:
220 moncdev = 'socket,id=mon,path=%s' % self._vm_monitor
221 args = ['-chardev', moncdev,
222 '-mon', 'chardev=mon,mode=control',
223 '-display', 'none', '-vga', 'none']
224 if self._machine is not None:
225 args.extend(['-machine', self._machine])
226 if self._console_set:
227 self._console_address = os.path.join(self._temp_dir,
228 self._name + "-console.sock")
229 chardev = ('socket,id=console,path=%s,server,nowait' %
230 self._console_address)
231 args.extend(['-chardev', chardev])
232 if self._console_device_type is None:
233 args.extend(['-serial', 'chardev:console'])
234 else:
235 device = '%s,chardev=console' % self._console_device_type
236 args.extend(['-device', device])
237 return args
238
239 def _pre_launch(self):
240 self._temp_dir = tempfile.mkdtemp(dir=self._test_dir)
241 if self._monitor_address is not None:
242 self._vm_monitor = self._monitor_address
243 else:
244 self._vm_monitor = os.path.join(self._temp_dir,
245 self._name + "-monitor.sock")
246 self._qemu_log_path = os.path.join(self._temp_dir, self._name + ".log")
247 self._qemu_log_file = open(self._qemu_log_path, 'wb')
248
249 self._qmp = qmp.QEMUMonitorProtocol(self._vm_monitor,
250 server=True)
251
252 def _post_launch(self):
253 self._qmp.accept()
254
255 def _post_shutdown(self):
256 if self._qemu_log_file is not None:
257 self._qemu_log_file.close()
258 self._qemu_log_file = None
259
260 self._qemu_log_path = None
261
262 if self._console_socket is not None:
263 self._console_socket.close()
264 self._console_socket = None
265
266 if self._temp_dir is not None:
267 shutil.rmtree(self._temp_dir)
268 self._temp_dir = None
269
270 def launch(self):
271 """
272 Launch the VM and make sure we cleanup and expose the
273 command line/output in case of exception
274 """
275
276 if self._launched:
277 raise QEMUMachineError('VM already launched')
278
279 self._iolog = None
280 self._qemu_full_args = None
281 try:
282 self._launch()
283 self._launched = True
284 except:
285 self.shutdown()
286
287 LOG.debug('Error launching VM')
288 if self._qemu_full_args:
289 LOG.debug('Command: %r', ' '.join(self._qemu_full_args))
290 if self._iolog:
291 LOG.debug('Output: %r', self._iolog)
292 raise
293
294 def _launch(self):
295 """
296 Launch the VM and establish a QMP connection
297 """
298 devnull = open(os.path.devnull, 'rb')
299 self._pre_launch()
300 self._qemu_full_args = (self._wrapper + [self._binary] +
301 self._base_args() + self._args)
302 LOG.debug('VM launch command: %r', ' '.join(self._qemu_full_args))
303 self._popen = subprocess.Popen(self._qemu_full_args,
304 stdin=devnull,
305 stdout=self._qemu_log_file,
306 stderr=subprocess.STDOUT,
307 shell=False,
308 close_fds=False)
309 self._post_launch()
310
311 def wait(self):
312 """
313 Wait for the VM to power off
314 """
315 self._popen.wait()
316 self._qmp.close()
317 self._load_io_log()
318 self._post_shutdown()
319
320 def shutdown(self):
321 """
322 Terminate the VM and clean up
323 """
324 if self.is_running():
325 try:
326 self._qmp.cmd('quit')
327 self._qmp.close()
328 except:
329 self._popen.kill()
330 self._popen.wait()
331
332 self._load_io_log()
333 self._post_shutdown()
334
335 exitcode = self.exitcode()
336 if exitcode is not None and exitcode < 0:
337 msg = 'qemu received signal %i: %s'
338 if self._qemu_full_args:
339 command = ' '.join(self._qemu_full_args)
340 else:
341 command = ''
342 LOG.warn(msg, -exitcode, command)
343
344 self._launched = False
345
346 def qmp(self, cmd, conv_keys=True, **args):
347 """
348 Invoke a QMP command and return the response dict
349 """
350 qmp_args = dict()
351 for key, value in args.items():
352 if conv_keys:
353 qmp_args[key.replace('_', '-')] = value
354 else:
355 qmp_args[key] = value
356
357 return self._qmp.cmd(cmd, args=qmp_args)
358
359 def command(self, cmd, conv_keys=True, **args):
360 """
361 Invoke a QMP command.
362 On success return the response dict.
363 On failure raise an exception.
364 """
365 reply = self.qmp(cmd, conv_keys, **args)
366 if reply is None:
367 raise qmp.QMPError("Monitor is closed")
368 if "error" in reply:
369 raise MonitorResponseError(reply)
370 return reply["return"]
371
372 def get_qmp_event(self, wait=False):
373 """
374 Poll for one queued QMP events and return it
375 """
376 if len(self._events) > 0:
377 return self._events.pop(0)
378 return self._qmp.pull_event(wait=wait)
379
380 def get_qmp_events(self, wait=False):
381 """
382 Poll for queued QMP events and return a list of dicts
383 """
384 events = self._qmp.get_events(wait=wait)
385 events.extend(self._events)
386 del self._events[:]
387 self._qmp.clear_events()
388 return events
389
390 @staticmethod
391 def event_match(event, match=None):
392 """
393 Check if an event matches optional match criteria.
394
395 The match criteria takes the form of a matching subdict. The event is
396 checked to be a superset of the subdict, recursively, with matching
397 values whenever the subdict values are not None.
398
399 This has a limitation that you cannot explicitly check for None values.
400
401 Examples, with the subdict queries on the left:
402 - None matches any object.
403 - {"foo": None} matches {"foo": {"bar": 1}}
404 - {"foo": None} matches {"foo": 5}
405 - {"foo": {"abc": None}} does not match {"foo": {"bar": 1}}
406 - {"foo": {"rab": 2}} matches {"foo": {"bar": 1, "rab": 2}}
407 """
408 if match is None:
409 return True
410
411 try:
412 for key in match:
413 if key in event:
414 if not QEMUMachine.event_match(event[key], match[key]):
415 return False
416 else:
417 return False
418 return True
419 except TypeError:
420 # either match or event wasn't iterable (not a dict)
421 return match == event
422
423 def event_wait(self, name, timeout=60.0, match=None):
424 """
425 event_wait waits for and returns a named event from QMP with a timeout.
426
427 name: The event to wait for.
428 timeout: QEMUMonitorProtocol.pull_event timeout parameter.
429 match: Optional match criteria. See event_match for details.
430 """
431 return self.events_wait([(name, match)], timeout)
432
433 def events_wait(self, events, timeout=60.0):
434 """
435 events_wait waits for and returns a named event from QMP with a timeout.
436
437 events: a sequence of (name, match_criteria) tuples.
438 The match criteria are optional and may be None.
439 See event_match for details.
440 timeout: QEMUMonitorProtocol.pull_event timeout parameter.
441 """
442 def _match(event):
443 for name, match in events:
444 if (event['event'] == name and
445 self.event_match(event, match)):
446 return True
447 return False
448
449 # Search cached events
450 for event in self._events:
451 if _match(event):
452 self._events.remove(event)
453 return event
454
455 # Poll for new events
456 while True:
457 event = self._qmp.pull_event(wait=timeout)
458 if _match(event):
459 return event
460 self._events.append(event)
461
462 return None
463
464 def get_log(self):
465 """
466 After self.shutdown or failed qemu execution, this returns the output
467 of the qemu process.
468 """
469 return self._iolog
470
471 def add_args(self, *args):
472 """
473 Adds to the list of extra arguments to be given to the QEMU binary
474 """
475 self._args.extend(args)
476
477 def set_machine(self, machine_type):
478 """
479 Sets the machine type
480
481 If set, the machine type will be added to the base arguments
482 of the resulting QEMU command line.
483 """
484 self._machine = machine_type
485
486 def set_console(self, device_type=None):
487 """
488 Sets the device type for a console device
489
490 If set, the console device and a backing character device will
491 be added to the base arguments of the resulting QEMU command
492 line.
493
494 This is a convenience method that will either use the provided
495 device type, or default to a "-serial chardev:console" command
496 line argument.
497
498 The actual setting of command line arguments will be be done at
499 machine launch time, as it depends on the temporary directory
500 to be created.
501
502 @param device_type: the device type, such as "isa-serial". If
503 None is given (the default value) a "-serial
504 chardev:console" command line argument will
505 be used instead, resorting to the machine's
506 default device type.
507 """
508 self._console_set = True
509 self._console_device_type = device_type
510
511 @property
512 def console_socket(self):
513 """
514 Returns a socket connected to the console
515 """
516 if self._console_socket is None:
517 self._console_socket = socket.socket(socket.AF_UNIX,
518 socket.SOCK_STREAM)
519 self._console_socket.connect(self._console_address)
520 return self._console_socket