Merge remote-tracking branch 'remotes/jnsnow/tags/bitmaps-pull-request' into staging
[qemu.git] / scripts / qemu.py
blobbcd24aad82e4c3e5323e9597fa00af8b28d12162
1 # QEMU library
3 # Copyright (C) 2015-2016 Red Hat Inc.
4 # Copyright (C) 2012 IBM Corp.
6 # Authors:
7 # Fam Zheng <famz@redhat.com>
9 # This work is licensed under the terms of the GNU GPL, version 2. See
10 # the COPYING file in the top-level directory.
12 # Based on qmp.py.
15 import errno
16 import logging
17 import os
18 import subprocess
19 import qmp.qmp
20 import re
21 import shutil
22 import socket
23 import tempfile
26 LOG = logging.getLogger(__name__)
29 def kvm_available(target_arch=None):
30 if target_arch and target_arch != os.uname()[4]:
31 return False
32 return os.access("/dev/kvm", os.R_OK | os.W_OK)
35 #: Maps machine types to the preferred console device types
36 CONSOLE_DEV_TYPES = {
37 r'^clipper$': 'isa-serial',
38 r'^malta': 'isa-serial',
39 r'^(pc.*|q35.*|isapc)$': 'isa-serial',
40 r'^(40p|powernv|prep)$': 'isa-serial',
41 r'^pseries.*': 'spapr-vty',
42 r'^s390-ccw-virtio.*': 'sclpconsole',
46 class QEMUMachineError(Exception):
47 """
48 Exception called when an error in QEMUMachine happens.
49 """
52 class QEMUMachineAddDeviceError(QEMUMachineError):
53 """
54 Exception raised when a request to add a device can not be fulfilled
56 The failures are caused by limitations, lack of information or conflicting
57 requests on the QEMUMachine methods. This exception does not represent
58 failures reported by the QEMU binary itself.
59 """
61 class MonitorResponseError(qmp.qmp.QMPError):
62 '''
63 Represents erroneous QMP monitor reply
64 '''
65 def __init__(self, reply):
66 try:
67 desc = reply["error"]["desc"]
68 except KeyError:
69 desc = reply
70 super(MonitorResponseError, self).__init__(desc)
71 self.reply = reply
74 class QEMUMachine(object):
75 '''A QEMU VM
77 Use this object as a context manager to ensure the QEMU process terminates::
79 with VM(binary) as vm:
80 ...
81 # vm is guaranteed to be shut down here
82 '''
84 def __init__(self, binary, args=None, wrapper=None, name=None,
85 test_dir="/var/tmp", monitor_address=None,
86 socket_scm_helper=None):
87 '''
88 Initialize a QEMUMachine
90 @param binary: path to the qemu binary
91 @param args: list of extra arguments
92 @param wrapper: list of arguments used as prefix to qemu binary
93 @param name: prefix for socket and log file names (default: qemu-PID)
94 @param test_dir: where to create socket and log file
95 @param monitor_address: address for QMP monitor
96 @param socket_scm_helper: helper program, required for send_fd_scm()"
97 @note: Qemu process is not started until launch() is used.
98 '''
99 if args is None:
100 args = []
101 if wrapper is None:
102 wrapper = []
103 if name is None:
104 name = "qemu-%d" % os.getpid()
105 self._name = name
106 self._monitor_address = monitor_address
107 self._vm_monitor = None
108 self._qemu_log_path = None
109 self._qemu_log_file = None
110 self._popen = None
111 self._binary = binary
112 self._args = list(args) # Force copy args in case we modify them
113 self._wrapper = wrapper
114 self._events = []
115 self._iolog = None
116 self._socket_scm_helper = socket_scm_helper
117 self._qmp = None
118 self._qemu_full_args = None
119 self._test_dir = test_dir
120 self._temp_dir = None
121 self._launched = False
122 self._machine = None
123 self._console_device_type = None
124 self._console_address = None
125 self._console_socket = None
127 # just in case logging wasn't configured by the main script:
128 logging.basicConfig()
130 def __enter__(self):
131 return self
133 def __exit__(self, exc_type, exc_val, exc_tb):
134 self.shutdown()
135 return False
137 # This can be used to add an unused monitor instance.
138 def add_monitor_telnet(self, ip, port):
139 args = 'tcp:%s:%d,server,nowait,telnet' % (ip, port)
140 self._args.append('-monitor')
141 self._args.append(args)
143 def add_fd(self, fd, fdset, opaque, opts=''):
144 '''Pass a file descriptor to the VM'''
145 options = ['fd=%d' % fd,
146 'set=%d' % fdset,
147 'opaque=%s' % opaque]
148 if opts:
149 options.append(opts)
151 self._args.append('-add-fd')
152 self._args.append(','.join(options))
153 return self
155 def send_fd_scm(self, fd_file_path):
156 # In iotest.py, the qmp should always use unix socket.
157 assert self._qmp.is_scm_available()
158 if self._socket_scm_helper is None:
159 raise QEMUMachineError("No path to socket_scm_helper set")
160 if not os.path.exists(self._socket_scm_helper):
161 raise QEMUMachineError("%s does not exist" %
162 self._socket_scm_helper)
163 fd_param = ["%s" % self._socket_scm_helper,
164 "%d" % self._qmp.get_sock_fd(),
165 "%s" % fd_file_path]
166 devnull = open(os.path.devnull, 'rb')
167 proc = subprocess.Popen(fd_param, stdin=devnull, stdout=subprocess.PIPE,
168 stderr=subprocess.STDOUT)
169 output = proc.communicate()[0]
170 if output:
171 LOG.debug(output)
173 return proc.returncode
175 @staticmethod
176 def _remove_if_exists(path):
177 '''Remove file object at path if it exists'''
178 try:
179 os.remove(path)
180 except OSError as exception:
181 if exception.errno == errno.ENOENT:
182 return
183 raise
185 def is_running(self):
186 return self._popen is not None and self._popen.poll() is None
188 def exitcode(self):
189 if self._popen is None:
190 return None
191 return self._popen.poll()
193 def get_pid(self):
194 if not self.is_running():
195 return None
196 return self._popen.pid
198 def _load_io_log(self):
199 if self._qemu_log_path is not None:
200 with open(self._qemu_log_path, "r") as iolog:
201 self._iolog = iolog.read()
203 def _base_args(self):
204 if isinstance(self._monitor_address, tuple):
205 moncdev = "socket,id=mon,host=%s,port=%s" % (
206 self._monitor_address[0],
207 self._monitor_address[1])
208 else:
209 moncdev = 'socket,id=mon,path=%s' % self._vm_monitor
210 args = ['-chardev', moncdev,
211 '-mon', 'chardev=mon,mode=control',
212 '-display', 'none', '-vga', 'none']
213 if self._machine is not None:
214 args.extend(['-machine', self._machine])
215 if self._console_device_type is not None:
216 self._console_address = os.path.join(self._temp_dir,
217 self._name + "-console.sock")
218 chardev = ('socket,id=console,path=%s,server,nowait' %
219 self._console_address)
220 device = '%s,chardev=console' % self._console_device_type
221 args.extend(['-chardev', chardev, '-device', device])
222 return args
224 def _pre_launch(self):
225 self._temp_dir = tempfile.mkdtemp(dir=self._test_dir)
226 if self._monitor_address is not None:
227 self._vm_monitor = self._monitor_address
228 else:
229 self._vm_monitor = os.path.join(self._temp_dir,
230 self._name + "-monitor.sock")
231 self._qemu_log_path = os.path.join(self._temp_dir, self._name + ".log")
232 self._qemu_log_file = open(self._qemu_log_path, 'wb')
234 self._qmp = qmp.qmp.QEMUMonitorProtocol(self._vm_monitor,
235 server=True)
237 def _post_launch(self):
238 self._qmp.accept()
240 def _post_shutdown(self):
241 if self._qemu_log_file is not None:
242 self._qemu_log_file.close()
243 self._qemu_log_file = None
245 self._qemu_log_path = None
247 if self._console_socket is not None:
248 self._console_socket.close()
249 self._console_socket = None
251 if self._temp_dir is not None:
252 shutil.rmtree(self._temp_dir)
253 self._temp_dir = None
255 def launch(self):
257 Launch the VM and make sure we cleanup and expose the
258 command line/output in case of exception
261 if self._launched:
262 raise QEMUMachineError('VM already launched')
264 self._iolog = None
265 self._qemu_full_args = None
266 try:
267 self._launch()
268 self._launched = True
269 except:
270 self.shutdown()
272 LOG.debug('Error launching VM')
273 if self._qemu_full_args:
274 LOG.debug('Command: %r', ' '.join(self._qemu_full_args))
275 if self._iolog:
276 LOG.debug('Output: %r', self._iolog)
277 raise
279 def _launch(self):
280 '''Launch the VM and establish a QMP connection'''
281 devnull = open(os.path.devnull, 'rb')
282 self._pre_launch()
283 self._qemu_full_args = (self._wrapper + [self._binary] +
284 self._base_args() + self._args)
285 self._popen = subprocess.Popen(self._qemu_full_args,
286 stdin=devnull,
287 stdout=self._qemu_log_file,
288 stderr=subprocess.STDOUT,
289 shell=False)
290 self._post_launch()
292 def wait(self):
293 '''Wait for the VM to power off'''
294 self._popen.wait()
295 self._qmp.close()
296 self._load_io_log()
297 self._post_shutdown()
299 def shutdown(self):
300 '''Terminate the VM and clean up'''
301 if self.is_running():
302 try:
303 self._qmp.cmd('quit')
304 self._qmp.close()
305 except:
306 self._popen.kill()
307 self._popen.wait()
309 self._load_io_log()
310 self._post_shutdown()
312 exitcode = self.exitcode()
313 if exitcode is not None and exitcode < 0:
314 msg = 'qemu received signal %i: %s'
315 if self._qemu_full_args:
316 command = ' '.join(self._qemu_full_args)
317 else:
318 command = ''
319 LOG.warn(msg, exitcode, command)
321 self._launched = False
323 def qmp(self, cmd, conv_keys=True, **args):
324 '''Invoke a QMP command and return the response dict'''
325 qmp_args = dict()
326 for key, value in args.items():
327 if conv_keys:
328 qmp_args[key.replace('_', '-')] = value
329 else:
330 qmp_args[key] = value
332 return self._qmp.cmd(cmd, args=qmp_args)
334 def command(self, cmd, conv_keys=True, **args):
336 Invoke a QMP command.
337 On success return the response dict.
338 On failure raise an exception.
340 reply = self.qmp(cmd, conv_keys, **args)
341 if reply is None:
342 raise qmp.qmp.QMPError("Monitor is closed")
343 if "error" in reply:
344 raise MonitorResponseError(reply)
345 return reply["return"]
347 def get_qmp_event(self, wait=False):
348 '''Poll for one queued QMP events and return it'''
349 if len(self._events) > 0:
350 return self._events.pop(0)
351 return self._qmp.pull_event(wait=wait)
353 def get_qmp_events(self, wait=False):
354 '''Poll for queued QMP events and return a list of dicts'''
355 events = self._qmp.get_events(wait=wait)
356 events.extend(self._events)
357 del self._events[:]
358 self._qmp.clear_events()
359 return events
361 def event_wait(self, name, timeout=60.0, match=None):
363 Wait for specified timeout on named event in QMP; optionally filter
364 results by match.
366 The 'match' is checked to be a recursive subset of the 'event'; skips
367 branch processing on match's value None
368 {"foo": {"bar": 1}} matches {"foo": None}
369 {"foo": {"bar": 1}} does not matches {"foo": {"baz": None}}
371 def event_match(event, match=None):
372 if match is None:
373 return True
375 for key in match:
376 if key in event:
377 if isinstance(event[key], dict):
378 if not event_match(event[key], match[key]):
379 return False
380 elif event[key] != match[key]:
381 return False
382 else:
383 return False
385 return True
387 # Search cached events
388 for event in self._events:
389 if (event['event'] == name) and event_match(event, match):
390 self._events.remove(event)
391 return event
393 # Poll for new events
394 while True:
395 event = self._qmp.pull_event(wait=timeout)
396 if (event['event'] == name) and event_match(event, match):
397 return event
398 self._events.append(event)
400 return None
402 def get_log(self):
404 After self.shutdown or failed qemu execution, this returns the output
405 of the qemu process.
407 return self._iolog
409 def add_args(self, *args):
411 Adds to the list of extra arguments to be given to the QEMU binary
413 self._args.extend(args)
415 def set_machine(self, machine_type):
417 Sets the machine type
419 If set, the machine type will be added to the base arguments
420 of the resulting QEMU command line.
422 self._machine = machine_type
424 def set_console(self, device_type=None):
426 Sets the device type for a console device
428 If set, the console device and a backing character device will
429 be added to the base arguments of the resulting QEMU command
430 line.
432 This is a convenience method that will either use the provided
433 device type, of if not given, it will used the device type set
434 on CONSOLE_DEV_TYPES.
436 The actual setting of command line arguments will be be done at
437 machine launch time, as it depends on the temporary directory
438 to be created.
440 @param device_type: the device type, such as "isa-serial"
441 @raises: QEMUMachineAddDeviceError if the device type is not given
442 and can not be determined.
444 if device_type is None:
445 if self._machine is None:
446 raise QEMUMachineAddDeviceError("Can not add a console device:"
447 " QEMU instance without a "
448 "defined machine type")
449 for regex, device in CONSOLE_DEV_TYPES.items():
450 if re.match(regex, self._machine):
451 device_type = device
452 break
453 if device_type is None:
454 raise QEMUMachineAddDeviceError("Can not add a console device:"
455 " no matching console device "
456 "type definition")
457 self._console_device_type = device_type
459 @property
460 def console_socket(self):
462 Returns a socket connected to the console
464 if self._console_socket is None:
465 self._console_socket = socket.socket(socket.AF_UNIX,
466 socket.SOCK_STREAM)
467 self._console_socket.connect(self._console_address)
468 return self._console_socket