]> git.proxmox.com Git - mirror_qemu.git/blame - python/qemu/machine.py
python/machine: use subprocess.DEVNULL instead of open(os.path.devnull)
[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
aad3f3bb 21from itertools import chain
abf0bf99
JS
22import logging
23import os
abf0bf99 24import shutil
de6e08b5 25import signal
f12a282f 26import socket
932ca4bb 27import subprocess
abf0bf99 28import tempfile
1dda0404 29from types import TracebackType
aaa81ec6
JS
30from typing import (
31 Any,
f12a282f 32 BinaryIO,
aaa81ec6
JS
33 Dict,
34 List,
35 Optional,
aad3f3bb
JS
36 Sequence,
37 Tuple,
aaa81ec6
JS
38 Type,
39)
932ca4bb
JS
40
41from . import console_socket, qmp
f12a282f 42from .qmp import QMPMessage, QMPReturnValue, SocketAddrT
abf0bf99 43
abf0bf99
JS
44
45LOG = logging.getLogger(__name__)
46
8dfac2ed 47
abf0bf99
JS
48class QEMUMachineError(Exception):
49 """
50 Exception called when an error in QEMUMachine happens.
51 """
52
53
54class QEMUMachineAddDeviceError(QEMUMachineError):
55 """
56 Exception raised when a request to add a device can not be fulfilled
57
58 The failures are caused by limitations, lack of information or conflicting
59 requests on the QEMUMachine methods. This exception does not represent
60 failures reported by the QEMU binary itself.
61 """
62
63
193bf1c0
JS
64class AbnormalShutdown(QEMUMachineError):
65 """
66 Exception raised when a graceful shutdown was requested, but not performed.
67 """
68
69
9b8ccd6d 70class QEMUMachine:
abf0bf99 71 """
f12a282f 72 A QEMU VM.
abf0bf99 73
8dfac2ed
JS
74 Use this object as a context manager to ensure
75 the QEMU process terminates::
abf0bf99
JS
76
77 with VM(binary) as vm:
78 ...
79 # vm is guaranteed to be shut down here
80 """
81
aad3f3bb
JS
82 def __init__(self,
83 binary: str,
84 args: Sequence[str] = (),
85 wrapper: Sequence[str] = (),
86 name: Optional[str] = None,
2ca6e26c 87 base_temp_dir: str = "/var/tmp",
c4e6023f 88 monitor_address: Optional[SocketAddrT] = None,
f12a282f
JS
89 socket_scm_helper: Optional[str] = None,
90 sock_dir: Optional[str] = None,
91 drain_console: bool = False,
92 console_log: Optional[str] = None):
abf0bf99
JS
93 '''
94 Initialize a QEMUMachine
95
96 @param binary: path to the qemu binary
97 @param args: list of extra arguments
98 @param wrapper: list of arguments used as prefix to qemu binary
99 @param name: prefix for socket and log file names (default: qemu-PID)
2ca6e26c 100 @param base_temp_dir: default location where temporary files are created
abf0bf99
JS
101 @param monitor_address: address for QMP monitor
102 @param socket_scm_helper: helper program, required for send_fd_scm()
2ca6e26c 103 @param sock_dir: where to create socket (defaults to base_temp_dir)
0fc8f660 104 @param drain_console: (optional) True to drain console socket to buffer
c5e61a6d 105 @param console_log: (optional) path to console log file
abf0bf99
JS
106 @note: Qemu process is not started until launch() is used.
107 '''
c5e61a6d
JS
108 # Direct user configuration
109
110 self._binary = binary
c5e61a6d 111 self._args = list(args)
c5e61a6d
JS
112 self._wrapper = wrapper
113
114 self._name = name or "qemu-%d" % os.getpid()
2ca6e26c
CR
115 self._base_temp_dir = base_temp_dir
116 self._sock_dir = sock_dir or self._base_temp_dir
c5e61a6d
JS
117 self._socket_scm_helper = socket_scm_helper
118
c4e6023f
JS
119 if monitor_address is not None:
120 self._monitor_address = monitor_address
121 self._remove_monitor_sockfile = False
122 else:
123 self._monitor_address = os.path.join(
c5e61a6d 124 self._sock_dir, f"{self._name}-monitor.sock"
c4e6023f
JS
125 )
126 self._remove_monitor_sockfile = True
c5e61a6d
JS
127
128 self._console_log_path = console_log
129 if self._console_log_path:
130 # In order to log the console, buffering needs to be enabled.
131 self._drain_console = True
132 else:
133 self._drain_console = drain_console
134
135 # Runstate
f12a282f
JS
136 self._qemu_log_path: Optional[str] = None
137 self._qemu_log_file: Optional[BinaryIO] = None
9223fda4 138 self._popen: Optional['subprocess.Popen[bytes]'] = None
f12a282f
JS
139 self._events: List[QMPMessage] = []
140 self._iolog: Optional[str] = None
74b56bb5 141 self._qmp_set = True # Enable QMP monitor by default.
be1183e5 142 self._qmp_connection: Optional[qmp.QEMUMonitorProtocol] = None
aad3f3bb 143 self._qemu_full_args: Tuple[str, ...] = ()
f12a282f 144 self._temp_dir: Optional[str] = None
abf0bf99 145 self._launched = False
f12a282f 146 self._machine: Optional[str] = None
746f244d 147 self._console_index = 0
abf0bf99 148 self._console_set = False
f12a282f 149 self._console_device_type: Optional[str] = None
652809df
JS
150 self._console_address = os.path.join(
151 self._sock_dir, f"{self._name}-console.sock"
152 )
f12a282f
JS
153 self._console_socket: Optional[socket.socket] = None
154 self._remove_files: List[str] = []
de6e08b5 155 self._user_killed = False
abf0bf99 156
f12a282f 157 def __enter__(self) -> 'QEMUMachine':
abf0bf99
JS
158 return self
159
1dda0404
JS
160 def __exit__(self,
161 exc_type: Optional[Type[BaseException]],
162 exc_val: Optional[BaseException],
163 exc_tb: Optional[TracebackType]) -> None:
abf0bf99 164 self.shutdown()
abf0bf99 165
f12a282f 166 def add_monitor_null(self) -> None:
306dfcd6
JS
167 """
168 This can be used to add an unused monitor instance.
169 """
abf0bf99
JS
170 self._args.append('-monitor')
171 self._args.append('null')
172
f12a282f
JS
173 def add_fd(self, fd: int, fdset: int,
174 opaque: str, opts: str = '') -> 'QEMUMachine':
abf0bf99
JS
175 """
176 Pass a file descriptor to the VM
177 """
178 options = ['fd=%d' % fd,
179 'set=%d' % fdset,
180 'opaque=%s' % opaque]
181 if opts:
182 options.append(opts)
183
184 # This did not exist before 3.4, but since then it is
185 # mandatory for our purpose
186 if hasattr(os, 'set_inheritable'):
187 os.set_inheritable(fd, True)
188
189 self._args.append('-add-fd')
190 self._args.append(','.join(options))
191 return self
192
f12a282f
JS
193 def send_fd_scm(self, fd: Optional[int] = None,
194 file_path: Optional[str] = None) -> int:
306dfcd6
JS
195 """
196 Send an fd or file_path to socket_scm_helper.
197
198 Exactly one of fd and file_path must be given.
199 If it is file_path, the helper will open that file and pass its own fd.
200 """
abf0bf99
JS
201 # In iotest.py, the qmp should always use unix socket.
202 assert self._qmp.is_scm_available()
203 if self._socket_scm_helper is None:
204 raise QEMUMachineError("No path to socket_scm_helper set")
205 if not os.path.exists(self._socket_scm_helper):
206 raise QEMUMachineError("%s does not exist" %
207 self._socket_scm_helper)
208
209 # This did not exist before 3.4, but since then it is
210 # mandatory for our purpose
211 if hasattr(os, 'set_inheritable'):
212 os.set_inheritable(self._qmp.get_sock_fd(), True)
213 if fd is not None:
214 os.set_inheritable(fd, True)
215
216 fd_param = ["%s" % self._socket_scm_helper,
217 "%d" % self._qmp.get_sock_fd()]
218
219 if file_path is not None:
220 assert fd is None
221 fd_param.append(file_path)
222 else:
223 assert fd is not None
224 fd_param.append(str(fd))
225
8dfac2ed 226 proc = subprocess.Popen(
07b71233 227 fd_param, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE,
8dfac2ed
JS
228 stderr=subprocess.STDOUT, close_fds=False
229 )
abf0bf99
JS
230 output = proc.communicate()[0]
231 if output:
232 LOG.debug(output)
233
234 return proc.returncode
235
236 @staticmethod
f12a282f 237 def _remove_if_exists(path: str) -> None:
abf0bf99
JS
238 """
239 Remove file object at path if it exists
240 """
241 try:
242 os.remove(path)
243 except OSError as exception:
244 if exception.errno == errno.ENOENT:
245 return
246 raise
247
f12a282f 248 def is_running(self) -> bool:
306dfcd6 249 """Returns true if the VM is running."""
abf0bf99
JS
250 return self._popen is not None and self._popen.poll() is None
251
9223fda4
JS
252 @property
253 def _subp(self) -> 'subprocess.Popen[bytes]':
254 if self._popen is None:
255 raise QEMUMachineError('Subprocess pipe not present')
256 return self._popen
257
f12a282f 258 def exitcode(self) -> Optional[int]:
306dfcd6 259 """Returns the exit code if possible, or None."""
abf0bf99
JS
260 if self._popen is None:
261 return None
262 return self._popen.poll()
263
f12a282f 264 def get_pid(self) -> Optional[int]:
306dfcd6 265 """Returns the PID of the running process, or None."""
abf0bf99
JS
266 if not self.is_running():
267 return None
9223fda4 268 return self._subp.pid
abf0bf99 269
f12a282f 270 def _load_io_log(self) -> None:
abf0bf99
JS
271 if self._qemu_log_path is not None:
272 with open(self._qemu_log_path, "r") as iolog:
273 self._iolog = iolog.read()
274
652809df
JS
275 @property
276 def _base_args(self) -> List[str]:
74b56bb5 277 args = ['-display', 'none', '-vga', 'none']
c4e6023f 278
74b56bb5
WSM
279 if self._qmp_set:
280 if isinstance(self._monitor_address, tuple):
c4e6023f
JS
281 moncdev = "socket,id=mon,host={},port={}".format(
282 *self._monitor_address
283 )
74b56bb5 284 else:
c4e6023f 285 moncdev = f"socket,id=mon,path={self._monitor_address}"
74b56bb5
WSM
286 args.extend(['-chardev', moncdev, '-mon',
287 'chardev=mon,mode=control'])
c4e6023f 288
abf0bf99
JS
289 if self._machine is not None:
290 args.extend(['-machine', self._machine])
9b8ccd6d 291 for _ in range(self._console_index):
746f244d 292 args.extend(['-serial', 'null'])
abf0bf99 293 if self._console_set:
991c180d 294 chardev = ('socket,id=console,path=%s,server=on,wait=off' %
abf0bf99
JS
295 self._console_address)
296 args.extend(['-chardev', chardev])
297 if self._console_device_type is None:
298 args.extend(['-serial', 'chardev:console'])
299 else:
300 device = '%s,chardev=console' % self._console_device_type
301 args.extend(['-device', device])
302 return args
303
f12a282f 304 def _pre_launch(self) -> None:
2ca6e26c 305 self._qemu_log_path = os.path.join(self.temp_dir, self._name + ".log")
abf0bf99
JS
306 self._qemu_log_file = open(self._qemu_log_path, 'wb')
307
652809df
JS
308 if self._console_set:
309 self._remove_files.append(self._console_address)
310
74b56bb5 311 if self._qmp_set:
c4e6023f
JS
312 if self._remove_monitor_sockfile:
313 assert isinstance(self._monitor_address, str)
314 self._remove_files.append(self._monitor_address)
be1183e5 315 self._qmp_connection = qmp.QEMUMonitorProtocol(
c4e6023f
JS
316 self._monitor_address,
317 server=True,
318 nickname=self._name
319 )
abf0bf99 320
f12a282f 321 def _post_launch(self) -> None:
be1183e5 322 if self._qmp_connection:
74b56bb5 323 self._qmp.accept()
abf0bf99 324
f12a282f 325 def _post_shutdown(self) -> None:
a3842cb0
JS
326 """
327 Called to cleanup the VM instance after the process has exited.
328 May also be called after a failed launch.
329 """
330 # Comprehensive reset for the failed launch case:
331 self._early_cleanup()
332
be1183e5 333 if self._qmp_connection:
671940e6 334 self._qmp.close()
be1183e5 335 self._qmp_connection = None
671940e6 336
abf0bf99
JS
337 if self._qemu_log_file is not None:
338 self._qemu_log_file.close()
339 self._qemu_log_file = None
340
3c1e16c6
CR
341 self._load_io_log()
342
abf0bf99
JS
343 self._qemu_log_path = None
344
abf0bf99
JS
345 if self._temp_dir is not None:
346 shutil.rmtree(self._temp_dir)
347 self._temp_dir = None
348
32558ce7
HR
349 while len(self._remove_files) > 0:
350 self._remove_if_exists(self._remove_files.pop())
351
14661d93 352 exitcode = self.exitcode()
de6e08b5
JS
353 if (exitcode is not None and exitcode < 0
354 and not (self._user_killed and exitcode == -signal.SIGKILL)):
14661d93
JS
355 msg = 'qemu received signal %i; command: "%s"'
356 if self._qemu_full_args:
357 command = ' '.join(self._qemu_full_args)
358 else:
359 command = ''
360 LOG.warning(msg, -int(exitcode), command)
361
de6e08b5 362 self._user_killed = False
14661d93
JS
363 self._launched = False
364
f12a282f 365 def launch(self) -> None:
abf0bf99
JS
366 """
367 Launch the VM and make sure we cleanup and expose the
368 command line/output in case of exception
369 """
370
371 if self._launched:
372 raise QEMUMachineError('VM already launched')
373
374 self._iolog = None
aad3f3bb 375 self._qemu_full_args = ()
abf0bf99
JS
376 try:
377 self._launch()
378 self._launched = True
379 except:
a3842cb0 380 self._post_shutdown()
abf0bf99
JS
381
382 LOG.debug('Error launching VM')
383 if self._qemu_full_args:
384 LOG.debug('Command: %r', ' '.join(self._qemu_full_args))
385 if self._iolog:
386 LOG.debug('Output: %r', self._iolog)
387 raise
388
f12a282f 389 def _launch(self) -> None:
abf0bf99
JS
390 """
391 Launch the VM and establish a QMP connection
392 """
abf0bf99 393 self._pre_launch()
aad3f3bb
JS
394 self._qemu_full_args = tuple(
395 chain(self._wrapper,
396 [self._binary],
397 self._base_args,
398 self._args)
399 )
abf0bf99
JS
400 LOG.debug('VM launch command: %r', ' '.join(self._qemu_full_args))
401 self._popen = subprocess.Popen(self._qemu_full_args,
07b71233 402 stdin=subprocess.DEVNULL,
abf0bf99
JS
403 stdout=self._qemu_log_file,
404 stderr=subprocess.STDOUT,
405 shell=False,
406 close_fds=False)
407 self._post_launch()
408
e2c97f16
JS
409 def _early_cleanup(self) -> None:
410 """
411 Perform any cleanup that needs to happen before the VM exits.
a3842cb0 412
193bf1c0 413 May be invoked by both soft and hard shutdown in failover scenarios.
a3842cb0 414 Called additionally by _post_shutdown for comprehensive cleanup.
e2c97f16
JS
415 """
416 # If we keep the console socket open, we may deadlock waiting
417 # for QEMU to exit, while QEMU is waiting for the socket to
418 # become writeable.
419 if self._console_socket is not None:
420 self._console_socket.close()
421 self._console_socket = None
422
193bf1c0
JS
423 def _hard_shutdown(self) -> None:
424 """
425 Perform early cleanup, kill the VM, and wait for it to terminate.
426
427 :raise subprocess.Timeout: When timeout is exceeds 60 seconds
428 waiting for the QEMU process to terminate.
429 """
430 self._early_cleanup()
9223fda4
JS
431 self._subp.kill()
432 self._subp.wait(timeout=60)
193bf1c0 433
8226a4b8
JS
434 def _soft_shutdown(self, timeout: Optional[int],
435 has_quit: bool = False) -> None:
193bf1c0
JS
436 """
437 Perform early cleanup, attempt to gracefully shut down the VM, and wait
438 for it to terminate.
439
8226a4b8
JS
440 :param timeout: Timeout in seconds for graceful shutdown.
441 A value of None is an infinite wait.
193bf1c0 442 :param has_quit: When True, don't attempt to issue 'quit' QMP command
193bf1c0
JS
443
444 :raise ConnectionReset: On QMP communication errors
445 :raise subprocess.TimeoutExpired: When timeout is exceeded waiting for
446 the QEMU process to terminate.
447 """
448 self._early_cleanup()
449
be1183e5 450 if self._qmp_connection:
193bf1c0
JS
451 if not has_quit:
452 # Might raise ConnectionReset
453 self._qmp.cmd('quit')
454
455 # May raise subprocess.TimeoutExpired
9223fda4 456 self._subp.wait(timeout=timeout)
193bf1c0 457
8226a4b8
JS
458 def _do_shutdown(self, timeout: Optional[int],
459 has_quit: bool = False) -> None:
193bf1c0
JS
460 """
461 Attempt to shutdown the VM gracefully; fallback to a hard shutdown.
462
8226a4b8
JS
463 :param timeout: Timeout in seconds for graceful shutdown.
464 A value of None is an infinite wait.
193bf1c0 465 :param has_quit: When True, don't attempt to issue 'quit' QMP command
193bf1c0
JS
466
467 :raise AbnormalShutdown: When the VM could not be shut down gracefully.
468 The inner exception will likely be ConnectionReset or
469 subprocess.TimeoutExpired. In rare cases, non-graceful termination
470 may result in its own exceptions, likely subprocess.TimeoutExpired.
471 """
472 try:
8226a4b8 473 self._soft_shutdown(timeout, has_quit)
193bf1c0
JS
474 except Exception as exc:
475 self._hard_shutdown()
476 raise AbnormalShutdown("Could not perform graceful shutdown") \
477 from exc
478
c9b3045b
JS
479 def shutdown(self, has_quit: bool = False,
480 hard: bool = False,
8226a4b8 481 timeout: Optional[int] = 30) -> None:
abf0bf99 482 """
193bf1c0
JS
483 Terminate the VM (gracefully if possible) and perform cleanup.
484 Cleanup will always be performed.
485
486 If the VM has not yet been launched, or shutdown(), wait(), or kill()
487 have already been called, this method does nothing.
488
489 :param has_quit: When true, do not attempt to issue 'quit' QMP command.
490 :param hard: When true, do not attempt graceful shutdown, and
491 suppress the SIGKILL warning log message.
492 :param timeout: Optional timeout in seconds for graceful shutdown.
8226a4b8 493 Default 30 seconds, A `None` value is an infinite wait.
abf0bf99 494 """
a3842cb0
JS
495 if not self._launched:
496 return
497
193bf1c0 498 try:
e0e925a6 499 if hard:
de6e08b5 500 self._user_killed = True
193bf1c0
JS
501 self._hard_shutdown()
502 else:
8226a4b8 503 self._do_shutdown(timeout, has_quit)
193bf1c0
JS
504 finally:
505 self._post_shutdown()
abf0bf99 506
f12a282f 507 def kill(self) -> None:
193bf1c0
JS
508 """
509 Terminate the VM forcefully, wait for it to exit, and perform cleanup.
510 """
e0e925a6
VSO
511 self.shutdown(hard=True)
512
8226a4b8 513 def wait(self, timeout: Optional[int] = 30) -> None:
89528059
JS
514 """
515 Wait for the VM to power off and perform post-shutdown cleanup.
516
8226a4b8
JS
517 :param timeout: Optional timeout in seconds. Default 30 seconds.
518 A value of `None` is an infinite wait.
89528059
JS
519 """
520 self.shutdown(has_quit=True, timeout=timeout)
521
f12a282f 522 def set_qmp_monitor(self, enabled: bool = True) -> None:
74b56bb5
WSM
523 """
524 Set the QMP monitor.
525
526 @param enabled: if False, qmp monitor options will be removed from
527 the base arguments of the resulting QEMU command
528 line. Default is True.
529 @note: call this function before launch().
530 """
be1183e5
JS
531 self._qmp_set = enabled
532
533 @property
534 def _qmp(self) -> qmp.QEMUMonitorProtocol:
535 if self._qmp_connection is None:
536 raise QEMUMachineError("Attempt to access QMP with no connection")
537 return self._qmp_connection
74b56bb5 538
aaa81ec6
JS
539 @classmethod
540 def _qmp_args(cls, _conv_keys: bool = True, **args: Any) -> Dict[str, Any]:
abf0bf99
JS
541 qmp_args = dict()
542 for key, value in args.items():
aaa81ec6 543 if _conv_keys:
abf0bf99
JS
544 qmp_args[key.replace('_', '-')] = value
545 else:
546 qmp_args[key] = value
aaa81ec6 547 return qmp_args
abf0bf99 548
aaa81ec6
JS
549 def qmp(self, cmd: str,
550 conv_keys: bool = True,
551 **args: Any) -> QMPMessage:
552 """
553 Invoke a QMP command and return the response dict
554 """
555 qmp_args = self._qmp_args(conv_keys, **args)
abf0bf99
JS
556 return self._qmp.cmd(cmd, args=qmp_args)
557
f12a282f
JS
558 def command(self, cmd: str,
559 conv_keys: bool = True,
560 **args: Any) -> QMPReturnValue:
abf0bf99
JS
561 """
562 Invoke a QMP command.
563 On success return the response dict.
564 On failure raise an exception.
565 """
aaa81ec6
JS
566 qmp_args = self._qmp_args(conv_keys, **args)
567 return self._qmp.command(cmd, **qmp_args)
abf0bf99 568
f12a282f 569 def get_qmp_event(self, wait: bool = False) -> Optional[QMPMessage]:
abf0bf99
JS
570 """
571 Poll for one queued QMP events and return it
572 """
306dfcd6 573 if self._events:
abf0bf99
JS
574 return self._events.pop(0)
575 return self._qmp.pull_event(wait=wait)
576
f12a282f 577 def get_qmp_events(self, wait: bool = False) -> List[QMPMessage]:
abf0bf99
JS
578 """
579 Poll for queued QMP events and return a list of dicts
580 """
581 events = self._qmp.get_events(wait=wait)
582 events.extend(self._events)
583 del self._events[:]
584 self._qmp.clear_events()
585 return events
586
587 @staticmethod
f12a282f 588 def event_match(event: Any, match: Optional[Any]) -> bool:
abf0bf99
JS
589 """
590 Check if an event matches optional match criteria.
591
592 The match criteria takes the form of a matching subdict. The event is
593 checked to be a superset of the subdict, recursively, with matching
594 values whenever the subdict values are not None.
595
596 This has a limitation that you cannot explicitly check for None values.
597
598 Examples, with the subdict queries on the left:
599 - None matches any object.
600 - {"foo": None} matches {"foo": {"bar": 1}}
601 - {"foo": None} matches {"foo": 5}
602 - {"foo": {"abc": None}} does not match {"foo": {"bar": 1}}
603 - {"foo": {"rab": 2}} matches {"foo": {"bar": 1, "rab": 2}}
604 """
605 if match is None:
606 return True
607
608 try:
609 for key in match:
610 if key in event:
611 if not QEMUMachine.event_match(event[key], match[key]):
612 return False
613 else:
614 return False
615 return True
616 except TypeError:
617 # either match or event wasn't iterable (not a dict)
f12a282f 618 return bool(match == event)
abf0bf99 619
f12a282f
JS
620 def event_wait(self, name: str,
621 timeout: float = 60.0,
622 match: Optional[QMPMessage] = None) -> Optional[QMPMessage]:
abf0bf99
JS
623 """
624 event_wait waits for and returns a named event from QMP with a timeout.
625
626 name: The event to wait for.
627 timeout: QEMUMonitorProtocol.pull_event timeout parameter.
628 match: Optional match criteria. See event_match for details.
629 """
630 return self.events_wait([(name, match)], timeout)
631
f12a282f
JS
632 def events_wait(self,
633 events: Sequence[Tuple[str, Any]],
634 timeout: float = 60.0) -> Optional[QMPMessage]:
abf0bf99 635 """
1847a4a8
JS
636 events_wait waits for and returns a single named event from QMP.
637 In the case of multiple qualifying events, this function returns the
638 first one.
abf0bf99 639
1847a4a8
JS
640 :param events: A sequence of (name, match_criteria) tuples.
641 The match criteria are optional and may be None.
642 See event_match for details.
643 :param timeout: Optional timeout, in seconds.
644 See QEMUMonitorProtocol.pull_event.
645
646 :raise QMPTimeoutError: If timeout was non-zero and no matching events
647 were found.
648 :return: A QMP event matching the filter criteria.
649 If timeout was 0 and no event matched, None.
abf0bf99 650 """
f12a282f 651 def _match(event: QMPMessage) -> bool:
abf0bf99 652 for name, match in events:
306dfcd6 653 if event['event'] == name and self.event_match(event, match):
abf0bf99
JS
654 return True
655 return False
656
1847a4a8
JS
657 event: Optional[QMPMessage]
658
abf0bf99
JS
659 # Search cached events
660 for event in self._events:
661 if _match(event):
662 self._events.remove(event)
663 return event
664
665 # Poll for new events
666 while True:
667 event = self._qmp.pull_event(wait=timeout)
1847a4a8
JS
668 if event is None:
669 # NB: None is only returned when timeout is false-ish.
670 # Timeouts raise QMPTimeoutError instead!
671 break
abf0bf99
JS
672 if _match(event):
673 return event
674 self._events.append(event)
675
676 return None
677
f12a282f 678 def get_log(self) -> Optional[str]:
abf0bf99
JS
679 """
680 After self.shutdown or failed qemu execution, this returns the output
681 of the qemu process.
682 """
683 return self._iolog
684
f12a282f 685 def add_args(self, *args: str) -> None:
abf0bf99
JS
686 """
687 Adds to the list of extra arguments to be given to the QEMU binary
688 """
689 self._args.extend(args)
690
f12a282f 691 def set_machine(self, machine_type: str) -> None:
abf0bf99
JS
692 """
693 Sets the machine type
694
695 If set, the machine type will be added to the base arguments
696 of the resulting QEMU command line.
697 """
698 self._machine = machine_type
699
f12a282f
JS
700 def set_console(self,
701 device_type: Optional[str] = None,
702 console_index: int = 0) -> None:
abf0bf99
JS
703 """
704 Sets the device type for a console device
705
706 If set, the console device and a backing character device will
707 be added to the base arguments of the resulting QEMU command
708 line.
709
710 This is a convenience method that will either use the provided
711 device type, or default to a "-serial chardev:console" command
712 line argument.
713
714 The actual setting of command line arguments will be be done at
715 machine launch time, as it depends on the temporary directory
716 to be created.
717
718 @param device_type: the device type, such as "isa-serial". If
719 None is given (the default value) a "-serial
720 chardev:console" command line argument will
721 be used instead, resorting to the machine's
722 default device type.
746f244d
PMD
723 @param console_index: the index of the console device to use.
724 If not zero, the command line will create
725 'index - 1' consoles and connect them to
726 the 'null' backing character device.
abf0bf99
JS
727 """
728 self._console_set = True
729 self._console_device_type = device_type
746f244d 730 self._console_index = console_index
abf0bf99
JS
731
732 @property
f12a282f 733 def console_socket(self) -> socket.socket:
abf0bf99
JS
734 """
735 Returns a socket connected to the console
736 """
737 if self._console_socket is None:
80ded8e9
RF
738 self._console_socket = console_socket.ConsoleSocket(
739 self._console_address,
740 file=self._console_log_path,
741 drain=self._drain_console)
abf0bf99 742 return self._console_socket
2ca6e26c
CR
743
744 @property
745 def temp_dir(self) -> str:
746 """
747 Returns a temporary directory to be used for this machine
748 """
749 if self._temp_dir is None:
750 self._temp_dir = tempfile.mkdtemp(prefix="qemu-machine-",
751 dir=self._base_temp_dir)
752 return self._temp_dir