3 # Copyright (C) 2015-2016 Red Hat Inc.
4 # Copyright (C) 2012 IBM Corp.
9 # This work is licensed under the terms of the GNU GPL, version 2. See
10 # the COPYING file in the top-level directory.
26 LOG = logging.getLogger(__name__)
28 class QEMUMachineError(Exception):
30 Exception called when an error in QEMUMachine happens.
34 class QEMUMachineAddDeviceError(QEMUMachineError):
36 Exception raised when a request to add a device can not be fulfilled
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.
44 class MonitorResponseError(qmp.QMPError):
46 Represents erroneous QMP monitor reply
48 def __init__(self, reply):
50 desc = reply["error"]["desc"]
53 super(MonitorResponseError, self).__init__(desc)
57 class QEMUMachine(object):
61 Use this object as a context manager to ensure the QEMU process terminates::
63 with VM(binary) as vm:
65 # vm is guaranteed to be shut down here
68 def __init__(self, binary, args=None, wrapper=None, name=None,
69 test_dir="/var/tmp", monitor_address=None,
70 socket_scm_helper=None):
72 Initialize a QEMUMachine
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.
88 name = "qemu-%d" % os.getpid()
90 self._monitor_address = monitor_address
91 self._vm_monitor = None
92 self._qemu_log_path = None
93 self._qemu_log_file = None
96 self._args = list(args) # Force copy args in case we modify them
97 self._wrapper = wrapper
100 self._socket_scm_helper = socket_scm_helper
102 self._qemu_full_args = None
103 self._test_dir = test_dir
104 self._temp_dir = None
105 self._launched = False
107 self._console_set = False
108 self._console_device_type = None
109 self._console_address = None
110 self._console_socket = None
112 # just in case logging wasn't configured by the main script:
113 logging.basicConfig()
118 def __exit__(self, exc_type, exc_val, exc_tb):
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')
127 def add_fd(self, fd, fdset, opaque, opts=''):
129 Pass a file descriptor to the VM
131 options = ['fd=%d' % fd,
133 'opaque=%s' % opaque]
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)
142 self._args.append('-add-fd')
143 self._args.append(','.join(options))
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
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)
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)
163 os.set_inheritable(fd, True)
165 fd_param = ["%s" % self._socket_scm_helper,
166 "%d" % self._qmp.get_sock_fd()]
168 if file_path is not None:
170 fd_param.append(file_path)
172 assert fd is not None
173 fd_param.append(str(fd))
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]
182 return proc.returncode
185 def _remove_if_exists(path):
187 Remove file object at path if it exists
191 except OSError as exception:
192 if exception.errno == errno.ENOENT:
196 def is_running(self):
197 return self._popen is not None and self._popen.poll() is None
200 if self._popen is None:
202 return self._popen.poll()
205 if not self.is_running():
207 return self._popen.pid
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()
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])
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'])
235 device = '%s,chardev=console' % self._console_device_type
236 args.extend(['-device', device])
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
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')
249 self._qmp = qmp.QEMUMonitorProtocol(self._vm_monitor,
252 def _post_launch(self):
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
260 self._qemu_log_path = None
262 if self._console_socket is not None:
263 self._console_socket.close()
264 self._console_socket = None
266 if self._temp_dir is not None:
267 shutil.rmtree(self._temp_dir)
268 self._temp_dir = None
272 Launch the VM and make sure we cleanup and expose the
273 command line/output in case of exception
277 raise QEMUMachineError('VM already launched')
280 self._qemu_full_args = None
283 self._launched = True
287 LOG.debug('Error launching VM')
288 if self._qemu_full_args:
289 LOG.debug('Command: %r', ' '.join(self._qemu_full_args))
291 LOG.debug('Output: %r', self._iolog)
296 Launch the VM and establish a QMP connection
298 devnull = open(os.path.devnull, 'rb')
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,
305 stdout=self._qemu_log_file,
306 stderr=subprocess.STDOUT,
313 Wait for the VM to power off
318 self._post_shutdown()
322 Terminate the VM and clean up
324 if self.is_running():
326 self._qmp.cmd('quit')
333 self._post_shutdown()
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)
342 LOG.warn(msg, -exitcode, command)
344 self._launched = False
346 def qmp(self, cmd, conv_keys=True, **args):
348 Invoke a QMP command and return the response dict
351 for key, value in args.items():
353 qmp_args[key.replace('_', '-')] = value
355 qmp_args[key] = value
357 return self._qmp.cmd(cmd, args=qmp_args)
359 def command(self, cmd, conv_keys=True, **args):
361 Invoke a QMP command.
362 On success return the response dict.
363 On failure raise an exception.
365 reply = self.qmp(cmd, conv_keys, **args)
367 raise qmp.QMPError("Monitor is closed")
369 raise MonitorResponseError(reply)
370 return reply["return"]
372 def get_qmp_event(self, wait=False):
374 Poll for one queued QMP events and return it
376 if len(self._events) > 0:
377 return self._events.pop(0)
378 return self._qmp.pull_event(wait=wait)
380 def get_qmp_events(self, wait=False):
382 Poll for queued QMP events and return a list of dicts
384 events = self._qmp.get_events(wait=wait)
385 events.extend(self._events)
387 self._qmp.clear_events()
391 def event_match(event, match=None):
393 Check if an event matches optional match criteria.
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.
399 This has a limitation that you cannot explicitly check for None values.
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}}
414 if not QEMUMachine.event_match(event[key], match[key]):
420 # either match or event wasn't iterable (not a dict)
421 return match == event
423 def event_wait(self, name, timeout=60.0, match=None):
425 event_wait waits for and returns a named event from QMP with a timeout.
427 name: The event to wait for.
428 timeout: QEMUMonitorProtocol.pull_event timeout parameter.
429 match: Optional match criteria. See event_match for details.
431 return self.events_wait([(name, match)], timeout)
433 def events_wait(self, events, timeout=60.0):
435 events_wait waits for and returns a named event from QMP with a timeout.
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.
443 for name, match in events:
444 if (event['event'] == name and
445 self.event_match(event, match)):
449 # Search cached events
450 for event in self._events:
452 self._events.remove(event)
455 # Poll for new events
457 event = self._qmp.pull_event(wait=timeout)
460 self._events.append(event)
466 After self.shutdown or failed qemu execution, this returns the output
471 def add_args(self, *args):
473 Adds to the list of extra arguments to be given to the QEMU binary
475 self._args.extend(args)
477 def set_machine(self, machine_type):
479 Sets the machine type
481 If set, the machine type will be added to the base arguments
482 of the resulting QEMU command line.
484 self._machine = machine_type
486 def set_console(self, device_type=None):
488 Sets the device type for a console device
490 If set, the console device and a backing character device will
491 be added to the base arguments of the resulting QEMU command
494 This is a convenience method that will either use the provided
495 device type, or default to a "-serial chardev:console" command
498 The actual setting of command line arguments will be be done at
499 machine launch time, as it depends on the temporary directory
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
508 self._console_set = True
509 self._console_device_type = device_type
512 def console_socket(self):
514 Returns a socket connected to the console
516 if self._console_socket is None:
517 self._console_socket = socket.socket(socket.AF_UNIX,
519 self._console_socket.connect(self._console_address)
520 return self._console_socket