blob: 768611f1de0ae98e5a35bbfc0521dac1591e9437 [file] [log] [blame]
Daniel P. Berrange66613972016-07-20 14:23:10 +01001# 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
Amador Pahim4738b0a2017-09-01 13:28:18 +020016import logging
Daniel P. Berrange66613972016-07-20 14:23:10 +010017import os
Daniel P. Berrange66613972016-07-20 14:23:10 +010018import subprocess
19import qmp.qmp
Cleber Rosa22dea9d2018-05-30 14:41:55 -040020import re
Amador Pahimaf99fa92018-01-22 21:50:28 +010021import shutil
Cleber Rosa22dea9d2018-05-30 14:41:55 -040022import socket
Amador Pahimaf99fa92018-01-22 21:50:28 +010023import tempfile
Daniel P. Berrange66613972016-07-20 14:23:10 +010024
25
Amador Pahim4738b0a2017-09-01 13:28:18 +020026LOG = logging.getLogger(__name__)
27
28
Philippe Mathieu-Daudéb59b82e2018-10-13 02:40:26 +020029def kvm_available(target_arch=None):
Philippe Mathieu-Daudé67a52f32018-10-13 02:40:31 +020030 if target_arch and target_arch != os.uname()[4]:
31 return False
Philippe Mathieu-Daudéb59b82e2018-10-13 02:40:26 +020032 return os.access("/dev/kvm", os.R_OK | os.W_OK)
33
34
Cleber Rosa22dea9d2018-05-30 14:41:55 -040035#: Maps machine types to the preferred console device types
36CONSOLE_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',
43 }
44
45
Amador Pahim4738b0a2017-09-01 13:28:18 +020046class QEMUMachineError(Exception):
47 """
48 Exception called when an error in QEMUMachine happens.
49 """
50
51
Cleber Rosa22dea9d2018-05-30 14:41:55 -040052class QEMUMachineAddDeviceError(QEMUMachineError):
53 """
54 Exception raised when a request to add a device can not be fulfilled
55
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 """
60
Lukáš Doktora004e242017-08-18 16:26:08 +020061class 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
72
73
Daniel P. Berrange66613972016-07-20 14:23:10 +010074class QEMUMachine(object):
Stefan Hajnoczid792bc32017-08-24 08:22:00 +010075 '''A QEMU VM
76
77 Use this object as a context manager to ensure the QEMU process terminates::
78
79 with VM(binary) as vm:
80 ...
81 # vm is guaranteed to be shut down here
82 '''
Daniel P. Berrange66613972016-07-20 14:23:10 +010083
Lukáš Doktor2782fc52017-08-18 16:26:05 +020084 def __init__(self, binary, args=None, wrapper=None, name=None,
Lukáš Doktor2d853c72017-08-18 16:26:04 +020085 test_dir="/var/tmp", monitor_address=None,
Eduardo Habkost1a6d3752017-10-05 14:20:13 -030086 socket_scm_helper=None):
Lukáš Doktor2d853c72017-08-18 16:26:04 +020087 '''
88 Initialize a QEMUMachine
89
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
Cleber Rosaa5a98622018-10-04 12:18:52 -040096 @param socket_scm_helper: helper program, required for send_fd_scm()
Lukáš Doktor2d853c72017-08-18 16:26:04 +020097 @note: Qemu process is not started until launch() is used.
98 '''
Lukáš Doktor2782fc52017-08-18 16:26:05 +020099 if args is None:
100 args = []
101 if wrapper is None:
102 wrapper = []
Daniel P. Berrange66613972016-07-20 14:23:10 +0100103 if name is None:
104 name = "qemu-%d" % os.getpid()
Amador Pahimaf99fa92018-01-22 21:50:28 +0100105 self._name = name
Daniel P. Berrange66613972016-07-20 14:23:10 +0100106 self._monitor_address = monitor_address
Amador Pahimaf99fa92018-01-22 21:50:28 +0100107 self._vm_monitor = None
108 self._qemu_log_path = None
109 self._qemu_log_file = None
Daniel P. Berrange66613972016-07-20 14:23:10 +0100110 self._popen = None
111 self._binary = binary
Lukáš Doktor2d853c72017-08-18 16:26:04 +0200112 self._args = list(args) # Force copy args in case we modify them
Daniel P. Berrange66613972016-07-20 14:23:10 +0100113 self._wrapper = wrapper
114 self._events = []
115 self._iolog = None
Daniel P. Berrange4c44b4a2016-07-26 17:16:07 +0100116 self._socket_scm_helper = socket_scm_helper
Lukáš Doktor2d853c72017-08-18 16:26:04 +0200117 self._qmp = None
Amador Pahimdab91d92017-09-01 13:28:20 +0200118 self._qemu_full_args = None
Amador Pahimaf99fa92018-01-22 21:50:28 +0100119 self._test_dir = test_dir
120 self._temp_dir = None
Amador Pahim156dc7b2018-01-22 21:50:33 +0100121 self._launched = False
Cleber Rosa22dea9d2018-05-30 14:41:55 -0400122 self._machine = None
123 self._console_device_type = None
124 self._console_address = None
125 self._console_socket = None
Daniel P. Berrange66613972016-07-20 14:23:10 +0100126
Eduardo Habkost58103142017-09-21 13:22:34 -0300127 # just in case logging wasn't configured by the main script:
Eduardo Habkost1a6d3752017-10-05 14:20:13 -0300128 logging.basicConfig()
Eduardo Habkost58103142017-09-21 13:22:34 -0300129
Stefan Hajnoczid792bc32017-08-24 08:22:00 +0100130 def __enter__(self):
131 return self
132
133 def __exit__(self, exc_type, exc_val, exc_tb):
134 self.shutdown()
135 return False
136
Daniel P. Berrange66613972016-07-20 14:23:10 +0100137 # 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)
142
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)
150
Max Reitzbf43b292018-10-22 14:53:04 +0100151 # This did not exist before 3.4, but since then it is
152 # mandatory for our purpose
153 if hasattr(os, 'set_inheritable'):
154 os.set_inheritable(fd, True)
155
Daniel P. Berrange66613972016-07-20 14:23:10 +0100156 self._args.append('-add-fd')
157 self._args.append(','.join(options))
158 return self
159
Max Reitzbf43b292018-10-22 14:53:04 +0100160 # Exactly one of fd and file_path must be given.
161 # (If it is file_path, the helper will open that file and pass its
162 # own fd)
163 def send_fd_scm(self, fd=None, file_path=None):
Daniel P. Berrange66613972016-07-20 14:23:10 +0100164 # In iotest.py, the qmp should always use unix socket.
165 assert self._qmp.is_scm_available()
Daniel P. Berrange4c44b4a2016-07-26 17:16:07 +0100166 if self._socket_scm_helper is None:
Amador Pahim4738b0a2017-09-01 13:28:18 +0200167 raise QEMUMachineError("No path to socket_scm_helper set")
Lukáš Doktor2d853c72017-08-18 16:26:04 +0200168 if not os.path.exists(self._socket_scm_helper):
Amador Pahim4738b0a2017-09-01 13:28:18 +0200169 raise QEMUMachineError("%s does not exist" %
170 self._socket_scm_helper)
Max Reitzbf43b292018-10-22 14:53:04 +0100171
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
Daniel P. Berrange4c44b4a2016-07-26 17:16:07 +0100179 fd_param = ["%s" % self._socket_scm_helper,
Max Reitzbf43b292018-10-22 14:53:04 +0100180 "%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
Amador Pahim63e0ba52017-09-01 13:28:19 +0200189 devnull = open(os.path.devnull, 'rb')
Amador Pahim4738b0a2017-09-01 13:28:18 +0200190 proc = subprocess.Popen(fd_param, stdin=devnull, stdout=subprocess.PIPE,
Max Reitzbf43b292018-10-22 14:53:04 +0100191 stderr=subprocess.STDOUT, close_fds=False)
Amador Pahim4738b0a2017-09-01 13:28:18 +0200192 output = proc.communicate()[0]
193 if output:
194 LOG.debug(output)
195
196 return proc.returncode
Daniel P. Berrange66613972016-07-20 14:23:10 +0100197
198 @staticmethod
199 def _remove_if_exists(path):
200 '''Remove file object at path if it exists'''
201 try:
202 os.remove(path)
203 except OSError as exception:
204 if exception.errno == errno.ENOENT:
205 return
206 raise
207
Eduardo Habkost37bbcd52017-05-26 15:11:58 -0300208 def is_running(self):
Amador Pahim17589ca2018-01-22 21:50:31 +0100209 return self._popen is not None and self._popen.poll() is None
Eduardo Habkost37bbcd52017-05-26 15:11:58 -0300210
Eduardo Habkostb2b8d982017-05-26 15:11:59 -0300211 def exitcode(self):
212 if self._popen is None:
213 return None
Amador Pahim17589ca2018-01-22 21:50:31 +0100214 return self._popen.poll()
Eduardo Habkostb2b8d982017-05-26 15:11:59 -0300215
Daniel P. Berrange66613972016-07-20 14:23:10 +0100216 def get_pid(self):
Eduardo Habkost37bbcd52017-05-26 15:11:58 -0300217 if not self.is_running():
Daniel P. Berrange66613972016-07-20 14:23:10 +0100218 return None
219 return self._popen.pid
220
221 def _load_io_log(self):
Amador Pahim04a963b2018-01-22 21:50:30 +0100222 if self._qemu_log_path is not None:
223 with open(self._qemu_log_path, "r") as iolog:
224 self._iolog = iolog.read()
Daniel P. Berrange66613972016-07-20 14:23:10 +0100225
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:
Amador Pahimaf99fa92018-01-22 21:50:28 +0100232 moncdev = 'socket,id=mon,path=%s' % self._vm_monitor
Cleber Rosa22dea9d2018-05-30 14:41:55 -0400233 args = ['-chardev', moncdev,
Daniel P. Berrange66613972016-07-20 14:23:10 +0100234 '-mon', 'chardev=mon,mode=control',
235 '-display', 'none', '-vga', 'none']
Cleber Rosa22dea9d2018-05-30 14:41:55 -0400236 if self._machine is not None:
237 args.extend(['-machine', self._machine])
238 if self._console_device_type is not None:
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 device = '%s,chardev=console' % self._console_device_type
244 args.extend(['-chardev', chardev, '-device', device])
245 return args
Daniel P. Berrange66613972016-07-20 14:23:10 +0100246
247 def _pre_launch(self):
Amador Pahimaf99fa92018-01-22 21:50:28 +0100248 self._temp_dir = tempfile.mkdtemp(dir=self._test_dir)
249 if self._monitor_address is not None:
250 self._vm_monitor = self._monitor_address
251 else:
252 self._vm_monitor = os.path.join(self._temp_dir,
253 self._name + "-monitor.sock")
254 self._qemu_log_path = os.path.join(self._temp_dir, self._name + ".log")
255 self._qemu_log_file = open(self._qemu_log_path, 'wb')
256
257 self._qmp = qmp.qmp.QEMUMonitorProtocol(self._vm_monitor,
Eduardo Habkost09177652017-10-05 14:20:12 -0300258 server=True)
Daniel P. Berrange66613972016-07-20 14:23:10 +0100259
260 def _post_launch(self):
261 self._qmp.accept()
262
263 def _post_shutdown(self):
Amador Pahimaf99fa92018-01-22 21:50:28 +0100264 if self._qemu_log_file is not None:
265 self._qemu_log_file.close()
266 self._qemu_log_file = None
267
268 self._qemu_log_path = None
269
Cleber Rosa22dea9d2018-05-30 14:41:55 -0400270 if self._console_socket is not None:
271 self._console_socket.close()
272 self._console_socket = None
273
Amador Pahimaf99fa92018-01-22 21:50:28 +0100274 if self._temp_dir is not None:
275 shutil.rmtree(self._temp_dir)
276 self._temp_dir = None
Daniel P. Berrange66613972016-07-20 14:23:10 +0100277
278 def launch(self):
Amador Pahimd301bcc2018-01-22 21:50:29 +0100279 """
280 Launch the VM and make sure we cleanup and expose the
281 command line/output in case of exception
282 """
Amador Pahim156dc7b2018-01-22 21:50:33 +0100283
284 if self._launched:
285 raise QEMUMachineError('VM already launched')
286
Amador Pahimb92a0012017-09-01 13:28:21 +0200287 self._iolog = None
Amador Pahimdab91d92017-09-01 13:28:20 +0200288 self._qemu_full_args = None
Daniel P. Berrange66613972016-07-20 14:23:10 +0100289 try:
Amador Pahimd301bcc2018-01-22 21:50:29 +0100290 self._launch()
Amador Pahim156dc7b2018-01-22 21:50:33 +0100291 self._launched = True
Daniel P. Berrange66613972016-07-20 14:23:10 +0100292 except:
Amador Pahimc58b5352018-01-22 21:50:32 +0100293 self.shutdown()
Amador Pahimb92a0012017-09-01 13:28:21 +0200294
295 LOG.debug('Error launching VM')
296 if self._qemu_full_args:
297 LOG.debug('Command: %r', ' '.join(self._qemu_full_args))
298 if self._iolog:
299 LOG.debug('Output: %r', self._iolog)
Daniel P. Berrange66613972016-07-20 14:23:10 +0100300 raise
301
Amador Pahimd301bcc2018-01-22 21:50:29 +0100302 def _launch(self):
303 '''Launch the VM and establish a QMP connection'''
304 devnull = open(os.path.devnull, 'rb')
305 self._pre_launch()
306 self._qemu_full_args = (self._wrapper + [self._binary] +
307 self._base_args() + self._args)
308 self._popen = subprocess.Popen(self._qemu_full_args,
309 stdin=devnull,
310 stdout=self._qemu_log_file,
311 stderr=subprocess.STDOUT,
Max Reitzbf43b292018-10-22 14:53:04 +0100312 shell=False,
313 close_fds=False)
Amador Pahimd301bcc2018-01-22 21:50:29 +0100314 self._post_launch()
315
Fam Zheng22491a22017-09-05 10:11:51 +0800316 def wait(self):
317 '''Wait for the VM to power off'''
318 self._popen.wait()
319 self._qmp.close()
320 self._load_io_log()
321 self._post_shutdown()
322
Daniel P. Berrange66613972016-07-20 14:23:10 +0100323 def shutdown(self):
324 '''Terminate the VM and clean up'''
Eduardo Habkost37bbcd52017-05-26 15:11:58 -0300325 if self.is_running():
Daniel P. Berrange66613972016-07-20 14:23:10 +0100326 try:
327 self._qmp.cmd('quit')
328 self._qmp.close()
329 except:
330 self._popen.kill()
Amador Pahimdab91d92017-09-01 13:28:20 +0200331 self._popen.wait()
Daniel P. Berrange66613972016-07-20 14:23:10 +0100332
Amador Pahim04a963b2018-01-22 21:50:30 +0100333 self._load_io_log()
334 self._post_shutdown()
Daniel P. Berrange66613972016-07-20 14:23:10 +0100335
Amador Pahimdab91d92017-09-01 13:28:20 +0200336 exitcode = self.exitcode()
337 if exitcode is not None and exitcode < 0:
338 msg = 'qemu received signal %i: %s'
339 if self._qemu_full_args:
340 command = ' '.join(self._qemu_full_args)
341 else:
342 command = ''
343 LOG.warn(msg, exitcode, command)
344
Amador Pahim156dc7b2018-01-22 21:50:33 +0100345 self._launched = False
346
Daniel P. Berrange66613972016-07-20 14:23:10 +0100347 def qmp(self, cmd, conv_keys=True, **args):
Lukáš Doktor2d853c72017-08-18 16:26:04 +0200348 '''Invoke a QMP command and return the response dict'''
Daniel P. Berrange66613972016-07-20 14:23:10 +0100349 qmp_args = dict()
Eduardo Habkostfb2e1cc2018-03-12 15:55:01 -0300350 for key, value in args.items():
Daniel P. Berrange66613972016-07-20 14:23:10 +0100351 if conv_keys:
Lukáš Doktor41f714b2017-08-18 16:26:07 +0200352 qmp_args[key.replace('_', '-')] = value
Daniel P. Berrange66613972016-07-20 14:23:10 +0100353 else:
Lukáš Doktor7f33ca72017-08-18 16:26:06 +0200354 qmp_args[key] = value
Daniel P. Berrange66613972016-07-20 14:23:10 +0100355
356 return self._qmp.cmd(cmd, args=qmp_args)
357
358 def command(self, cmd, conv_keys=True, **args):
Lukáš Doktor2d853c72017-08-18 16:26:04 +0200359 '''
360 Invoke a QMP command.
361 On success return the response dict.
362 On failure raise an exception.
363 '''
Daniel P. Berrange66613972016-07-20 14:23:10 +0100364 reply = self.qmp(cmd, conv_keys, **args)
365 if reply is None:
Lukáš Doktora004e242017-08-18 16:26:08 +0200366 raise qmp.qmp.QMPError("Monitor is closed")
Daniel P. Berrange66613972016-07-20 14:23:10 +0100367 if "error" in reply:
Lukáš Doktora004e242017-08-18 16:26:08 +0200368 raise MonitorResponseError(reply)
Daniel P. Berrange66613972016-07-20 14:23:10 +0100369 return reply["return"]
370
371 def get_qmp_event(self, wait=False):
372 '''Poll for one queued QMP events and return it'''
373 if len(self._events) > 0:
374 return self._events.pop(0)
375 return self._qmp.pull_event(wait=wait)
376
377 def get_qmp_events(self, wait=False):
378 '''Poll for queued QMP events and return a list of dicts'''
379 events = self._qmp.get_events(wait=wait)
380 events.extend(self._events)
381 del self._events[:]
382 self._qmp.clear_events()
383 return events
384
385 def event_wait(self, name, timeout=60.0, match=None):
Lukáš Doktor2d853c72017-08-18 16:26:04 +0200386 '''
387 Wait for specified timeout on named event in QMP; optionally filter
388 results by match.
389
390 The 'match' is checked to be a recursive subset of the 'event'; skips
391 branch processing on match's value None
392 {"foo": {"bar": 1}} matches {"foo": None}
393 {"foo": {"bar": 1}} does not matches {"foo": {"baz": None}}
394 '''
Daniel P. Berrange4c44b4a2016-07-26 17:16:07 +0100395 def event_match(event, match=None):
396 if match is None:
397 return True
398
399 for key in match:
400 if key in event:
401 if isinstance(event[key], dict):
402 if not event_match(event[key], match[key]):
403 return False
404 elif event[key] != match[key]:
405 return False
406 else:
407 return False
408
409 return True
410
Daniel P. Berrange66613972016-07-20 14:23:10 +0100411 # Search cached events
412 for event in self._events:
413 if (event['event'] == name) and event_match(event, match):
414 self._events.remove(event)
415 return event
416
417 # Poll for new events
418 while True:
419 event = self._qmp.pull_event(wait=timeout)
420 if (event['event'] == name) and event_match(event, match):
421 return event
422 self._events.append(event)
423
424 return None
425
426 def get_log(self):
Lukáš Doktor2d853c72017-08-18 16:26:04 +0200427 '''
428 After self.shutdown or failed qemu execution, this returns the output
429 of the qemu process.
430 '''
Daniel P. Berrange66613972016-07-20 14:23:10 +0100431 return self._iolog
Cleber Rosa572a8242018-05-30 14:41:53 -0400432
433 def add_args(self, *args):
434 '''
435 Adds to the list of extra arguments to be given to the QEMU binary
436 '''
437 self._args.extend(args)
Cleber Rosa22dea9d2018-05-30 14:41:55 -0400438
439 def set_machine(self, machine_type):
440 '''
441 Sets the machine type
442
443 If set, the machine type will be added to the base arguments
444 of the resulting QEMU command line.
445 '''
446 self._machine = machine_type
447
448 def set_console(self, device_type=None):
449 '''
450 Sets the device type for a console device
451
452 If set, the console device and a backing character device will
453 be added to the base arguments of the resulting QEMU command
454 line.
455
456 This is a convenience method that will either use the provided
457 device type, of if not given, it will used the device type set
458 on CONSOLE_DEV_TYPES.
459
460 The actual setting of command line arguments will be be done at
461 machine launch time, as it depends on the temporary directory
462 to be created.
463
464 @param device_type: the device type, such as "isa-serial"
465 @raises: QEMUMachineAddDeviceError if the device type is not given
466 and can not be determined.
467 '''
468 if device_type is None:
469 if self._machine is None:
470 raise QEMUMachineAddDeviceError("Can not add a console device:"
471 " QEMU instance without a "
472 "defined machine type")
473 for regex, device in CONSOLE_DEV_TYPES.items():
474 if re.match(regex, self._machine):
475 device_type = device
476 break
477 if device_type is None:
478 raise QEMUMachineAddDeviceError("Can not add a console device:"
479 " no matching console device "
480 "type definition")
481 self._console_device_type = device_type
482
483 @property
484 def console_socket(self):
485 """
486 Returns a socket connected to the console
487 """
488 if self._console_socket is None:
489 self._console_socket = socket.socket(socket.AF_UNIX,
490 socket.SOCK_STREAM)
491 self._console_socket.connect(self._console_address)
492 return self._console_socket