blob: b0b2f12ce6c988d72ca2f85578df54dd9440ad7f [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
Cleber Rosa22dea9d2018-05-30 14:41:55 -040029#: Maps machine types to the preferred console device types
30CONSOLE_DEV_TYPES = {
31 r'^clipper$': 'isa-serial',
32 r'^malta': 'isa-serial',
33 r'^(pc.*|q35.*|isapc)$': 'isa-serial',
34 r'^(40p|powernv|prep)$': 'isa-serial',
35 r'^pseries.*': 'spapr-vty',
36 r'^s390-ccw-virtio.*': 'sclpconsole',
37 }
38
39
Amador Pahim4738b0a2017-09-01 13:28:18 +020040class QEMUMachineError(Exception):
41 """
42 Exception called when an error in QEMUMachine happens.
43 """
44
45
Cleber Rosa22dea9d2018-05-30 14:41:55 -040046class QEMUMachineAddDeviceError(QEMUMachineError):
47 """
48 Exception raised when a request to add a device can not be fulfilled
49
50 The failures are caused by limitations, lack of information or conflicting
51 requests on the QEMUMachine methods. This exception does not represent
52 failures reported by the QEMU binary itself.
53 """
54
Lukáš Doktora004e242017-08-18 16:26:08 +020055class MonitorResponseError(qmp.qmp.QMPError):
56 '''
57 Represents erroneous QMP monitor reply
58 '''
59 def __init__(self, reply):
60 try:
61 desc = reply["error"]["desc"]
62 except KeyError:
63 desc = reply
64 super(MonitorResponseError, self).__init__(desc)
65 self.reply = reply
66
67
Daniel P. Berrange66613972016-07-20 14:23:10 +010068class QEMUMachine(object):
Stefan Hajnoczid792bc32017-08-24 08:22:00 +010069 '''A QEMU VM
70
71 Use this object as a context manager to ensure the QEMU process terminates::
72
73 with VM(binary) as vm:
74 ...
75 # vm is guaranteed to be shut down here
76 '''
Daniel P. Berrange66613972016-07-20 14:23:10 +010077
Lukáš Doktor2782fc52017-08-18 16:26:05 +020078 def __init__(self, binary, args=None, wrapper=None, name=None,
Lukáš Doktor2d853c72017-08-18 16:26:04 +020079 test_dir="/var/tmp", monitor_address=None,
Eduardo Habkost1a6d3752017-10-05 14:20:13 -030080 socket_scm_helper=None):
Lukáš Doktor2d853c72017-08-18 16:26:04 +020081 '''
82 Initialize a QEMUMachine
83
84 @param binary: path to the qemu binary
85 @param args: list of extra arguments
86 @param wrapper: list of arguments used as prefix to qemu binary
87 @param name: prefix for socket and log file names (default: qemu-PID)
88 @param test_dir: where to create socket and log file
89 @param monitor_address: address for QMP monitor
Cleber Rosaa5a98622018-10-04 12:18:52 -040090 @param socket_scm_helper: helper program, required for send_fd_scm()
Lukáš Doktor2d853c72017-08-18 16:26:04 +020091 @note: Qemu process is not started until launch() is used.
92 '''
Lukáš Doktor2782fc52017-08-18 16:26:05 +020093 if args is None:
94 args = []
95 if wrapper is None:
96 wrapper = []
Daniel P. Berrange66613972016-07-20 14:23:10 +010097 if name is None:
98 name = "qemu-%d" % os.getpid()
Amador Pahimaf99fa92018-01-22 21:50:28 +010099 self._name = name
Daniel P. Berrange66613972016-07-20 14:23:10 +0100100 self._monitor_address = monitor_address
Amador Pahimaf99fa92018-01-22 21:50:28 +0100101 self._vm_monitor = None
102 self._qemu_log_path = None
103 self._qemu_log_file = None
Daniel P. Berrange66613972016-07-20 14:23:10 +0100104 self._popen = None
105 self._binary = binary
Lukáš Doktor2d853c72017-08-18 16:26:04 +0200106 self._args = list(args) # Force copy args in case we modify them
Daniel P. Berrange66613972016-07-20 14:23:10 +0100107 self._wrapper = wrapper
108 self._events = []
109 self._iolog = None
Daniel P. Berrange4c44b4a2016-07-26 17:16:07 +0100110 self._socket_scm_helper = socket_scm_helper
Lukáš Doktor2d853c72017-08-18 16:26:04 +0200111 self._qmp = None
Amador Pahimdab91d92017-09-01 13:28:20 +0200112 self._qemu_full_args = None
Amador Pahimaf99fa92018-01-22 21:50:28 +0100113 self._test_dir = test_dir
114 self._temp_dir = None
Amador Pahim156dc7b2018-01-22 21:50:33 +0100115 self._launched = False
Cleber Rosa22dea9d2018-05-30 14:41:55 -0400116 self._machine = None
117 self._console_device_type = None
118 self._console_address = None
119 self._console_socket = None
Daniel P. Berrange66613972016-07-20 14:23:10 +0100120
Eduardo Habkost58103142017-09-21 13:22:34 -0300121 # just in case logging wasn't configured by the main script:
Eduardo Habkost1a6d3752017-10-05 14:20:13 -0300122 logging.basicConfig()
Eduardo Habkost58103142017-09-21 13:22:34 -0300123
Stefan Hajnoczid792bc32017-08-24 08:22:00 +0100124 def __enter__(self):
125 return self
126
127 def __exit__(self, exc_type, exc_val, exc_tb):
128 self.shutdown()
129 return False
130
Daniel P. Berrange66613972016-07-20 14:23:10 +0100131 # This can be used to add an unused monitor instance.
132 def add_monitor_telnet(self, ip, port):
133 args = 'tcp:%s:%d,server,nowait,telnet' % (ip, port)
134 self._args.append('-monitor')
135 self._args.append(args)
136
137 def add_fd(self, fd, fdset, opaque, opts=''):
138 '''Pass a file descriptor to the VM'''
139 options = ['fd=%d' % fd,
140 'set=%d' % fdset,
141 'opaque=%s' % opaque]
142 if opts:
143 options.append(opts)
144
145 self._args.append('-add-fd')
146 self._args.append(','.join(options))
147 return self
148
149 def send_fd_scm(self, fd_file_path):
150 # In iotest.py, the qmp should always use unix socket.
151 assert self._qmp.is_scm_available()
Daniel P. Berrange4c44b4a2016-07-26 17:16:07 +0100152 if self._socket_scm_helper is None:
Amador Pahim4738b0a2017-09-01 13:28:18 +0200153 raise QEMUMachineError("No path to socket_scm_helper set")
Lukáš Doktor2d853c72017-08-18 16:26:04 +0200154 if not os.path.exists(self._socket_scm_helper):
Amador Pahim4738b0a2017-09-01 13:28:18 +0200155 raise QEMUMachineError("%s does not exist" %
156 self._socket_scm_helper)
Daniel P. Berrange4c44b4a2016-07-26 17:16:07 +0100157 fd_param = ["%s" % self._socket_scm_helper,
Daniel P. Berrange66613972016-07-20 14:23:10 +0100158 "%d" % self._qmp.get_sock_fd(),
159 "%s" % fd_file_path]
Amador Pahim63e0ba52017-09-01 13:28:19 +0200160 devnull = open(os.path.devnull, 'rb')
Amador Pahim4738b0a2017-09-01 13:28:18 +0200161 proc = subprocess.Popen(fd_param, stdin=devnull, stdout=subprocess.PIPE,
162 stderr=subprocess.STDOUT)
163 output = proc.communicate()[0]
164 if output:
165 LOG.debug(output)
166
167 return proc.returncode
Daniel P. Berrange66613972016-07-20 14:23:10 +0100168
169 @staticmethod
170 def _remove_if_exists(path):
171 '''Remove file object at path if it exists'''
172 try:
173 os.remove(path)
174 except OSError as exception:
175 if exception.errno == errno.ENOENT:
176 return
177 raise
178
Eduardo Habkost37bbcd52017-05-26 15:11:58 -0300179 def is_running(self):
Amador Pahim17589ca2018-01-22 21:50:31 +0100180 return self._popen is not None and self._popen.poll() is None
Eduardo Habkost37bbcd52017-05-26 15:11:58 -0300181
Eduardo Habkostb2b8d982017-05-26 15:11:59 -0300182 def exitcode(self):
183 if self._popen is None:
184 return None
Amador Pahim17589ca2018-01-22 21:50:31 +0100185 return self._popen.poll()
Eduardo Habkostb2b8d982017-05-26 15:11:59 -0300186
Daniel P. Berrange66613972016-07-20 14:23:10 +0100187 def get_pid(self):
Eduardo Habkost37bbcd52017-05-26 15:11:58 -0300188 if not self.is_running():
Daniel P. Berrange66613972016-07-20 14:23:10 +0100189 return None
190 return self._popen.pid
191
192 def _load_io_log(self):
Amador Pahim04a963b2018-01-22 21:50:30 +0100193 if self._qemu_log_path is not None:
194 with open(self._qemu_log_path, "r") as iolog:
195 self._iolog = iolog.read()
Daniel P. Berrange66613972016-07-20 14:23:10 +0100196
197 def _base_args(self):
198 if isinstance(self._monitor_address, tuple):
199 moncdev = "socket,id=mon,host=%s,port=%s" % (
200 self._monitor_address[0],
201 self._monitor_address[1])
202 else:
Amador Pahimaf99fa92018-01-22 21:50:28 +0100203 moncdev = 'socket,id=mon,path=%s' % self._vm_monitor
Cleber Rosa22dea9d2018-05-30 14:41:55 -0400204 args = ['-chardev', moncdev,
Daniel P. Berrange66613972016-07-20 14:23:10 +0100205 '-mon', 'chardev=mon,mode=control',
206 '-display', 'none', '-vga', 'none']
Cleber Rosa22dea9d2018-05-30 14:41:55 -0400207 if self._machine is not None:
208 args.extend(['-machine', self._machine])
209 if self._console_device_type is not None:
210 self._console_address = os.path.join(self._temp_dir,
211 self._name + "-console.sock")
212 chardev = ('socket,id=console,path=%s,server,nowait' %
213 self._console_address)
214 device = '%s,chardev=console' % self._console_device_type
215 args.extend(['-chardev', chardev, '-device', device])
216 return args
Daniel P. Berrange66613972016-07-20 14:23:10 +0100217
218 def _pre_launch(self):
Amador Pahimaf99fa92018-01-22 21:50:28 +0100219 self._temp_dir = tempfile.mkdtemp(dir=self._test_dir)
220 if self._monitor_address is not None:
221 self._vm_monitor = self._monitor_address
222 else:
223 self._vm_monitor = os.path.join(self._temp_dir,
224 self._name + "-monitor.sock")
225 self._qemu_log_path = os.path.join(self._temp_dir, self._name + ".log")
226 self._qemu_log_file = open(self._qemu_log_path, 'wb')
227
228 self._qmp = qmp.qmp.QEMUMonitorProtocol(self._vm_monitor,
Eduardo Habkost09177652017-10-05 14:20:12 -0300229 server=True)
Daniel P. Berrange66613972016-07-20 14:23:10 +0100230
231 def _post_launch(self):
232 self._qmp.accept()
233
234 def _post_shutdown(self):
Amador Pahimaf99fa92018-01-22 21:50:28 +0100235 if self._qemu_log_file is not None:
236 self._qemu_log_file.close()
237 self._qemu_log_file = None
238
239 self._qemu_log_path = None
240
Cleber Rosa22dea9d2018-05-30 14:41:55 -0400241 if self._console_socket is not None:
242 self._console_socket.close()
243 self._console_socket = None
244
Amador Pahimaf99fa92018-01-22 21:50:28 +0100245 if self._temp_dir is not None:
246 shutil.rmtree(self._temp_dir)
247 self._temp_dir = None
Daniel P. Berrange66613972016-07-20 14:23:10 +0100248
249 def launch(self):
Amador Pahimd301bcc2018-01-22 21:50:29 +0100250 """
251 Launch the VM and make sure we cleanup and expose the
252 command line/output in case of exception
253 """
Amador Pahim156dc7b2018-01-22 21:50:33 +0100254
255 if self._launched:
256 raise QEMUMachineError('VM already launched')
257
Amador Pahimb92a0012017-09-01 13:28:21 +0200258 self._iolog = None
Amador Pahimdab91d92017-09-01 13:28:20 +0200259 self._qemu_full_args = None
Daniel P. Berrange66613972016-07-20 14:23:10 +0100260 try:
Amador Pahimd301bcc2018-01-22 21:50:29 +0100261 self._launch()
Amador Pahim156dc7b2018-01-22 21:50:33 +0100262 self._launched = True
Daniel P. Berrange66613972016-07-20 14:23:10 +0100263 except:
Amador Pahimc58b5352018-01-22 21:50:32 +0100264 self.shutdown()
Amador Pahimb92a0012017-09-01 13:28:21 +0200265
266 LOG.debug('Error launching VM')
267 if self._qemu_full_args:
268 LOG.debug('Command: %r', ' '.join(self._qemu_full_args))
269 if self._iolog:
270 LOG.debug('Output: %r', self._iolog)
Daniel P. Berrange66613972016-07-20 14:23:10 +0100271 raise
272
Amador Pahimd301bcc2018-01-22 21:50:29 +0100273 def _launch(self):
274 '''Launch the VM and establish a QMP connection'''
275 devnull = open(os.path.devnull, 'rb')
276 self._pre_launch()
277 self._qemu_full_args = (self._wrapper + [self._binary] +
278 self._base_args() + self._args)
279 self._popen = subprocess.Popen(self._qemu_full_args,
280 stdin=devnull,
281 stdout=self._qemu_log_file,
282 stderr=subprocess.STDOUT,
283 shell=False)
284 self._post_launch()
285
Fam Zheng22491a22017-09-05 10:11:51 +0800286 def wait(self):
287 '''Wait for the VM to power off'''
288 self._popen.wait()
289 self._qmp.close()
290 self._load_io_log()
291 self._post_shutdown()
292
Daniel P. Berrange66613972016-07-20 14:23:10 +0100293 def shutdown(self):
294 '''Terminate the VM and clean up'''
Eduardo Habkost37bbcd52017-05-26 15:11:58 -0300295 if self.is_running():
Daniel P. Berrange66613972016-07-20 14:23:10 +0100296 try:
297 self._qmp.cmd('quit')
298 self._qmp.close()
299 except:
300 self._popen.kill()
Amador Pahimdab91d92017-09-01 13:28:20 +0200301 self._popen.wait()
Daniel P. Berrange66613972016-07-20 14:23:10 +0100302
Amador Pahim04a963b2018-01-22 21:50:30 +0100303 self._load_io_log()
304 self._post_shutdown()
Daniel P. Berrange66613972016-07-20 14:23:10 +0100305
Amador Pahimdab91d92017-09-01 13:28:20 +0200306 exitcode = self.exitcode()
307 if exitcode is not None and exitcode < 0:
308 msg = 'qemu received signal %i: %s'
309 if self._qemu_full_args:
310 command = ' '.join(self._qemu_full_args)
311 else:
312 command = ''
313 LOG.warn(msg, exitcode, command)
314
Amador Pahim156dc7b2018-01-22 21:50:33 +0100315 self._launched = False
316
Daniel P. Berrange66613972016-07-20 14:23:10 +0100317 def qmp(self, cmd, conv_keys=True, **args):
Lukáš Doktor2d853c72017-08-18 16:26:04 +0200318 '''Invoke a QMP command and return the response dict'''
Daniel P. Berrange66613972016-07-20 14:23:10 +0100319 qmp_args = dict()
Eduardo Habkostfb2e1cc2018-03-12 15:55:01 -0300320 for key, value in args.items():
Daniel P. Berrange66613972016-07-20 14:23:10 +0100321 if conv_keys:
Lukáš Doktor41f714b2017-08-18 16:26:07 +0200322 qmp_args[key.replace('_', '-')] = value
Daniel P. Berrange66613972016-07-20 14:23:10 +0100323 else:
Lukáš Doktor7f33ca72017-08-18 16:26:06 +0200324 qmp_args[key] = value
Daniel P. Berrange66613972016-07-20 14:23:10 +0100325
326 return self._qmp.cmd(cmd, args=qmp_args)
327
328 def command(self, cmd, conv_keys=True, **args):
Lukáš Doktor2d853c72017-08-18 16:26:04 +0200329 '''
330 Invoke a QMP command.
331 On success return the response dict.
332 On failure raise an exception.
333 '''
Daniel P. Berrange66613972016-07-20 14:23:10 +0100334 reply = self.qmp(cmd, conv_keys, **args)
335 if reply is None:
Lukáš Doktora004e242017-08-18 16:26:08 +0200336 raise qmp.qmp.QMPError("Monitor is closed")
Daniel P. Berrange66613972016-07-20 14:23:10 +0100337 if "error" in reply:
Lukáš Doktora004e242017-08-18 16:26:08 +0200338 raise MonitorResponseError(reply)
Daniel P. Berrange66613972016-07-20 14:23:10 +0100339 return reply["return"]
340
341 def get_qmp_event(self, wait=False):
342 '''Poll for one queued QMP events and return it'''
343 if len(self._events) > 0:
344 return self._events.pop(0)
345 return self._qmp.pull_event(wait=wait)
346
347 def get_qmp_events(self, wait=False):
348 '''Poll for queued QMP events and return a list of dicts'''
349 events = self._qmp.get_events(wait=wait)
350 events.extend(self._events)
351 del self._events[:]
352 self._qmp.clear_events()
353 return events
354
355 def event_wait(self, name, timeout=60.0, match=None):
Lukáš Doktor2d853c72017-08-18 16:26:04 +0200356 '''
357 Wait for specified timeout on named event in QMP; optionally filter
358 results by match.
359
360 The 'match' is checked to be a recursive subset of the 'event'; skips
361 branch processing on match's value None
362 {"foo": {"bar": 1}} matches {"foo": None}
363 {"foo": {"bar": 1}} does not matches {"foo": {"baz": None}}
364 '''
Daniel P. Berrange4c44b4a2016-07-26 17:16:07 +0100365 def event_match(event, match=None):
366 if match is None:
367 return True
368
369 for key in match:
370 if key in event:
371 if isinstance(event[key], dict):
372 if not event_match(event[key], match[key]):
373 return False
374 elif event[key] != match[key]:
375 return False
376 else:
377 return False
378
379 return True
380
Daniel P. Berrange66613972016-07-20 14:23:10 +0100381 # Search cached events
382 for event in self._events:
383 if (event['event'] == name) and event_match(event, match):
384 self._events.remove(event)
385 return event
386
387 # Poll for new events
388 while True:
389 event = self._qmp.pull_event(wait=timeout)
390 if (event['event'] == name) and event_match(event, match):
391 return event
392 self._events.append(event)
393
394 return None
395
396 def get_log(self):
Lukáš Doktor2d853c72017-08-18 16:26:04 +0200397 '''
398 After self.shutdown or failed qemu execution, this returns the output
399 of the qemu process.
400 '''
Daniel P. Berrange66613972016-07-20 14:23:10 +0100401 return self._iolog
Cleber Rosa572a8242018-05-30 14:41:53 -0400402
403 def add_args(self, *args):
404 '''
405 Adds to the list of extra arguments to be given to the QEMU binary
406 '''
407 self._args.extend(args)
Cleber Rosa22dea9d2018-05-30 14:41:55 -0400408
409 def set_machine(self, machine_type):
410 '''
411 Sets the machine type
412
413 If set, the machine type will be added to the base arguments
414 of the resulting QEMU command line.
415 '''
416 self._machine = machine_type
417
418 def set_console(self, device_type=None):
419 '''
420 Sets the device type for a console device
421
422 If set, the console device and a backing character device will
423 be added to the base arguments of the resulting QEMU command
424 line.
425
426 This is a convenience method that will either use the provided
427 device type, of if not given, it will used the device type set
428 on CONSOLE_DEV_TYPES.
429
430 The actual setting of command line arguments will be be done at
431 machine launch time, as it depends on the temporary directory
432 to be created.
433
434 @param device_type: the device type, such as "isa-serial"
435 @raises: QEMUMachineAddDeviceError if the device type is not given
436 and can not be determined.
437 '''
438 if device_type is None:
439 if self._machine is None:
440 raise QEMUMachineAddDeviceError("Can not add a console device:"
441 " QEMU instance without a "
442 "defined machine type")
443 for regex, device in CONSOLE_DEV_TYPES.items():
444 if re.match(regex, self._machine):
445 device_type = device
446 break
447 if device_type is None:
448 raise QEMUMachineAddDeviceError("Can not add a console device:"
449 " no matching console device "
450 "type definition")
451 self._console_device_type = device_type
452
453 @property
454 def console_socket(self):
455 """
456 Returns a socket connected to the console
457 """
458 if self._console_socket is None:
459 self._console_socket = socket.socket(socket.AF_UNIX,
460 socket.SOCK_STREAM)
461 self._console_socket.connect(self._console_address)
462 return self._console_socket