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