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