]> git.proxmox.com Git - mirror_qemu.git/blame - python/qemu/machine.py
iotests/297: add --namespace-packages to mypy arguments
[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)
859aeb67 100 @param base_temp_dir: default location where temp 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
14b41797
JS
226 proc = subprocess.run(
227 fd_param,
228 stdin=subprocess.DEVNULL,
229 stdout=subprocess.PIPE,
230 stderr=subprocess.STDOUT,
231 check=False,
232 close_fds=False,
8dfac2ed 233 )
14b41797
JS
234 if proc.stdout:
235 LOG.debug(proc.stdout)
abf0bf99
JS
236
237 return proc.returncode
238
239 @staticmethod
f12a282f 240 def _remove_if_exists(path: str) -> None:
abf0bf99
JS
241 """
242 Remove file object at path if it exists
243 """
244 try:
245 os.remove(path)
246 except OSError as exception:
247 if exception.errno == errno.ENOENT:
248 return
249 raise
250
f12a282f 251 def is_running(self) -> bool:
306dfcd6 252 """Returns true if the VM is running."""
abf0bf99
JS
253 return self._popen is not None and self._popen.poll() is None
254
9223fda4
JS
255 @property
256 def _subp(self) -> 'subprocess.Popen[bytes]':
257 if self._popen is None:
258 raise QEMUMachineError('Subprocess pipe not present')
259 return self._popen
260
f12a282f 261 def exitcode(self) -> Optional[int]:
306dfcd6 262 """Returns the exit code if possible, or None."""
abf0bf99
JS
263 if self._popen is None:
264 return None
265 return self._popen.poll()
266
f12a282f 267 def get_pid(self) -> Optional[int]:
306dfcd6 268 """Returns the PID of the running process, or None."""
abf0bf99
JS
269 if not self.is_running():
270 return None
9223fda4 271 return self._subp.pid
abf0bf99 272
f12a282f 273 def _load_io_log(self) -> None:
abf0bf99
JS
274 if self._qemu_log_path is not None:
275 with open(self._qemu_log_path, "r") as iolog:
276 self._iolog = iolog.read()
277
652809df
JS
278 @property
279 def _base_args(self) -> List[str]:
74b56bb5 280 args = ['-display', 'none', '-vga', 'none']
c4e6023f 281
74b56bb5
WSM
282 if self._qmp_set:
283 if isinstance(self._monitor_address, tuple):
c4e6023f
JS
284 moncdev = "socket,id=mon,host={},port={}".format(
285 *self._monitor_address
286 )
74b56bb5 287 else:
c4e6023f 288 moncdev = f"socket,id=mon,path={self._monitor_address}"
74b56bb5
WSM
289 args.extend(['-chardev', moncdev, '-mon',
290 'chardev=mon,mode=control'])
c4e6023f 291
abf0bf99
JS
292 if self._machine is not None:
293 args.extend(['-machine', self._machine])
9b8ccd6d 294 for _ in range(self._console_index):
746f244d 295 args.extend(['-serial', 'null'])
abf0bf99 296 if self._console_set:
991c180d 297 chardev = ('socket,id=console,path=%s,server=on,wait=off' %
abf0bf99
JS
298 self._console_address)
299 args.extend(['-chardev', chardev])
300 if self._console_device_type is None:
301 args.extend(['-serial', 'chardev:console'])
302 else:
303 device = '%s,chardev=console' % self._console_device_type
304 args.extend(['-device', device])
305 return args
306
f12a282f 307 def _pre_launch(self) -> None:
2ca6e26c 308 self._qemu_log_path = os.path.join(self.temp_dir, self._name + ".log")
abf0bf99 309
652809df
JS
310 if self._console_set:
311 self._remove_files.append(self._console_address)
312
74b56bb5 313 if self._qmp_set:
c4e6023f
JS
314 if self._remove_monitor_sockfile:
315 assert isinstance(self._monitor_address, str)
316 self._remove_files.append(self._monitor_address)
be1183e5 317 self._qmp_connection = qmp.QEMUMonitorProtocol(
c4e6023f
JS
318 self._monitor_address,
319 server=True,
320 nickname=self._name
321 )
abf0bf99 322
63c33f3c
JS
323 # NOTE: Make sure any opened resources are *definitely* freed in
324 # _post_shutdown()!
325 # pylint: disable=consider-using-with
326 self._qemu_log_file = open(self._qemu_log_path, 'wb')
327
f12a282f 328 def _post_launch(self) -> None:
be1183e5 329 if self._qmp_connection:
74b56bb5 330 self._qmp.accept()
abf0bf99 331
f12a282f 332 def _post_shutdown(self) -> None:
a3842cb0
JS
333 """
334 Called to cleanup the VM instance after the process has exited.
335 May also be called after a failed launch.
336 """
337 # Comprehensive reset for the failed launch case:
338 self._early_cleanup()
339
be1183e5 340 if self._qmp_connection:
671940e6 341 self._qmp.close()
be1183e5 342 self._qmp_connection = None
671940e6 343
abf0bf99
JS
344 if self._qemu_log_file is not None:
345 self._qemu_log_file.close()
346 self._qemu_log_file = None
347
3c1e16c6
CR
348 self._load_io_log()
349
abf0bf99
JS
350 self._qemu_log_path = None
351
abf0bf99
JS
352 if self._temp_dir is not None:
353 shutil.rmtree(self._temp_dir)
354 self._temp_dir = None
355
32558ce7
HR
356 while len(self._remove_files) > 0:
357 self._remove_if_exists(self._remove_files.pop())
358
14661d93 359 exitcode = self.exitcode()
de6e08b5
JS
360 if (exitcode is not None and exitcode < 0
361 and not (self._user_killed and exitcode == -signal.SIGKILL)):
14661d93
JS
362 msg = 'qemu received signal %i; command: "%s"'
363 if self._qemu_full_args:
364 command = ' '.join(self._qemu_full_args)
365 else:
366 command = ''
367 LOG.warning(msg, -int(exitcode), command)
368
de6e08b5 369 self._user_killed = False
14661d93
JS
370 self._launched = False
371
f12a282f 372 def launch(self) -> None:
abf0bf99
JS
373 """
374 Launch the VM and make sure we cleanup and expose the
375 command line/output in case of exception
376 """
377
378 if self._launched:
379 raise QEMUMachineError('VM already launched')
380
381 self._iolog = None
aad3f3bb 382 self._qemu_full_args = ()
abf0bf99
JS
383 try:
384 self._launch()
385 self._launched = True
386 except:
a3842cb0 387 self._post_shutdown()
abf0bf99
JS
388
389 LOG.debug('Error launching VM')
390 if self._qemu_full_args:
391 LOG.debug('Command: %r', ' '.join(self._qemu_full_args))
392 if self._iolog:
393 LOG.debug('Output: %r', self._iolog)
394 raise
395
f12a282f 396 def _launch(self) -> None:
abf0bf99
JS
397 """
398 Launch the VM and establish a QMP connection
399 """
abf0bf99 400 self._pre_launch()
aad3f3bb
JS
401 self._qemu_full_args = tuple(
402 chain(self._wrapper,
403 [self._binary],
404 self._base_args,
405 self._args)
406 )
abf0bf99 407 LOG.debug('VM launch command: %r', ' '.join(self._qemu_full_args))
a0eae17a
JS
408
409 # Cleaning up of this subprocess is guaranteed by _do_shutdown.
410 # pylint: disable=consider-using-with
abf0bf99 411 self._popen = subprocess.Popen(self._qemu_full_args,
07b71233 412 stdin=subprocess.DEVNULL,
abf0bf99
JS
413 stdout=self._qemu_log_file,
414 stderr=subprocess.STDOUT,
415 shell=False,
416 close_fds=False)
417 self._post_launch()
418
e2c97f16
JS
419 def _early_cleanup(self) -> None:
420 """
421 Perform any cleanup that needs to happen before the VM exits.
a3842cb0 422
193bf1c0 423 May be invoked by both soft and hard shutdown in failover scenarios.
a3842cb0 424 Called additionally by _post_shutdown for comprehensive cleanup.
e2c97f16
JS
425 """
426 # If we keep the console socket open, we may deadlock waiting
427 # for QEMU to exit, while QEMU is waiting for the socket to
428 # become writeable.
429 if self._console_socket is not None:
430 self._console_socket.close()
431 self._console_socket = None
432
193bf1c0
JS
433 def _hard_shutdown(self) -> None:
434 """
435 Perform early cleanup, kill the VM, and wait for it to terminate.
436
437 :raise subprocess.Timeout: When timeout is exceeds 60 seconds
438 waiting for the QEMU process to terminate.
439 """
440 self._early_cleanup()
9223fda4
JS
441 self._subp.kill()
442 self._subp.wait(timeout=60)
193bf1c0 443
8226a4b8
JS
444 def _soft_shutdown(self, timeout: Optional[int],
445 has_quit: bool = False) -> None:
193bf1c0
JS
446 """
447 Perform early cleanup, attempt to gracefully shut down the VM, and wait
448 for it to terminate.
449
8226a4b8
JS
450 :param timeout: Timeout in seconds for graceful shutdown.
451 A value of None is an infinite wait.
193bf1c0 452 :param has_quit: When True, don't attempt to issue 'quit' QMP command
193bf1c0
JS
453
454 :raise ConnectionReset: On QMP communication errors
455 :raise subprocess.TimeoutExpired: When timeout is exceeded waiting for
456 the QEMU process to terminate.
457 """
458 self._early_cleanup()
459
be1183e5 460 if self._qmp_connection:
193bf1c0
JS
461 if not has_quit:
462 # Might raise ConnectionReset
463 self._qmp.cmd('quit')
464
465 # May raise subprocess.TimeoutExpired
9223fda4 466 self._subp.wait(timeout=timeout)
193bf1c0 467
8226a4b8
JS
468 def _do_shutdown(self, timeout: Optional[int],
469 has_quit: bool = False) -> None:
193bf1c0
JS
470 """
471 Attempt to shutdown the VM gracefully; fallback to a hard shutdown.
472
8226a4b8
JS
473 :param timeout: Timeout in seconds for graceful shutdown.
474 A value of None is an infinite wait.
193bf1c0 475 :param has_quit: When True, don't attempt to issue 'quit' QMP command
193bf1c0
JS
476
477 :raise AbnormalShutdown: When the VM could not be shut down gracefully.
478 The inner exception will likely be ConnectionReset or
479 subprocess.TimeoutExpired. In rare cases, non-graceful termination
480 may result in its own exceptions, likely subprocess.TimeoutExpired.
481 """
482 try:
8226a4b8 483 self._soft_shutdown(timeout, has_quit)
193bf1c0
JS
484 except Exception as exc:
485 self._hard_shutdown()
486 raise AbnormalShutdown("Could not perform graceful shutdown") \
487 from exc
488
c9b3045b
JS
489 def shutdown(self, has_quit: bool = False,
490 hard: bool = False,
8226a4b8 491 timeout: Optional[int] = 30) -> None:
abf0bf99 492 """
193bf1c0
JS
493 Terminate the VM (gracefully if possible) and perform cleanup.
494 Cleanup will always be performed.
495
496 If the VM has not yet been launched, or shutdown(), wait(), or kill()
497 have already been called, this method does nothing.
498
499 :param has_quit: When true, do not attempt to issue 'quit' QMP command.
500 :param hard: When true, do not attempt graceful shutdown, and
501 suppress the SIGKILL warning log message.
502 :param timeout: Optional timeout in seconds for graceful shutdown.
8226a4b8 503 Default 30 seconds, A `None` value is an infinite wait.
abf0bf99 504 """
a3842cb0
JS
505 if not self._launched:
506 return
507
193bf1c0 508 try:
e0e925a6 509 if hard:
de6e08b5 510 self._user_killed = True
193bf1c0
JS
511 self._hard_shutdown()
512 else:
8226a4b8 513 self._do_shutdown(timeout, has_quit)
193bf1c0
JS
514 finally:
515 self._post_shutdown()
abf0bf99 516
f12a282f 517 def kill(self) -> None:
193bf1c0
JS
518 """
519 Terminate the VM forcefully, wait for it to exit, and perform cleanup.
520 """
e0e925a6
VSO
521 self.shutdown(hard=True)
522
8226a4b8 523 def wait(self, timeout: Optional[int] = 30) -> None:
89528059
JS
524 """
525 Wait for the VM to power off and perform post-shutdown cleanup.
526
8226a4b8
JS
527 :param timeout: Optional timeout in seconds. Default 30 seconds.
528 A value of `None` is an infinite wait.
89528059
JS
529 """
530 self.shutdown(has_quit=True, timeout=timeout)
531
f12a282f 532 def set_qmp_monitor(self, enabled: bool = True) -> None:
74b56bb5
WSM
533 """
534 Set the QMP monitor.
535
536 @param enabled: if False, qmp monitor options will be removed from
537 the base arguments of the resulting QEMU command
538 line. Default is True.
539 @note: call this function before launch().
540 """
be1183e5
JS
541 self._qmp_set = enabled
542
543 @property
544 def _qmp(self) -> qmp.QEMUMonitorProtocol:
545 if self._qmp_connection is None:
546 raise QEMUMachineError("Attempt to access QMP with no connection")
547 return self._qmp_connection
74b56bb5 548
aaa81ec6
JS
549 @classmethod
550 def _qmp_args(cls, _conv_keys: bool = True, **args: Any) -> Dict[str, Any]:
abf0bf99
JS
551 qmp_args = dict()
552 for key, value in args.items():
aaa81ec6 553 if _conv_keys:
abf0bf99
JS
554 qmp_args[key.replace('_', '-')] = value
555 else:
556 qmp_args[key] = value
aaa81ec6 557 return qmp_args
abf0bf99 558
aaa81ec6
JS
559 def qmp(self, cmd: str,
560 conv_keys: bool = True,
561 **args: Any) -> QMPMessage:
562 """
563 Invoke a QMP command and return the response dict
564 """
565 qmp_args = self._qmp_args(conv_keys, **args)
abf0bf99
JS
566 return self._qmp.cmd(cmd, args=qmp_args)
567
f12a282f
JS
568 def command(self, cmd: str,
569 conv_keys: bool = True,
570 **args: Any) -> QMPReturnValue:
abf0bf99
JS
571 """
572 Invoke a QMP command.
573 On success return the response dict.
574 On failure raise an exception.
575 """
aaa81ec6
JS
576 qmp_args = self._qmp_args(conv_keys, **args)
577 return self._qmp.command(cmd, **qmp_args)
abf0bf99 578
f12a282f 579 def get_qmp_event(self, wait: bool = False) -> Optional[QMPMessage]:
abf0bf99
JS
580 """
581 Poll for one queued QMP events and return it
582 """
306dfcd6 583 if self._events:
abf0bf99
JS
584 return self._events.pop(0)
585 return self._qmp.pull_event(wait=wait)
586
f12a282f 587 def get_qmp_events(self, wait: bool = False) -> List[QMPMessage]:
abf0bf99
JS
588 """
589 Poll for queued QMP events and return a list of dicts
590 """
591 events = self._qmp.get_events(wait=wait)
592 events.extend(self._events)
593 del self._events[:]
594 self._qmp.clear_events()
595 return events
596
597 @staticmethod
f12a282f 598 def event_match(event: Any, match: Optional[Any]) -> bool:
abf0bf99
JS
599 """
600 Check if an event matches optional match criteria.
601
602 The match criteria takes the form of a matching subdict. The event is
603 checked to be a superset of the subdict, recursively, with matching
604 values whenever the subdict values are not None.
605
606 This has a limitation that you cannot explicitly check for None values.
607
608 Examples, with the subdict queries on the left:
609 - None matches any object.
610 - {"foo": None} matches {"foo": {"bar": 1}}
611 - {"foo": None} matches {"foo": 5}
612 - {"foo": {"abc": None}} does not match {"foo": {"bar": 1}}
613 - {"foo": {"rab": 2}} matches {"foo": {"bar": 1, "rab": 2}}
614 """
615 if match is None:
616 return True
617
618 try:
619 for key in match:
620 if key in event:
621 if not QEMUMachine.event_match(event[key], match[key]):
622 return False
623 else:
624 return False
625 return True
626 except TypeError:
627 # either match or event wasn't iterable (not a dict)
f12a282f 628 return bool(match == event)
abf0bf99 629
f12a282f
JS
630 def event_wait(self, name: str,
631 timeout: float = 60.0,
632 match: Optional[QMPMessage] = None) -> Optional[QMPMessage]:
abf0bf99
JS
633 """
634 event_wait waits for and returns a named event from QMP with a timeout.
635
636 name: The event to wait for.
637 timeout: QEMUMonitorProtocol.pull_event timeout parameter.
638 match: Optional match criteria. See event_match for details.
639 """
640 return self.events_wait([(name, match)], timeout)
641
f12a282f
JS
642 def events_wait(self,
643 events: Sequence[Tuple[str, Any]],
644 timeout: float = 60.0) -> Optional[QMPMessage]:
abf0bf99 645 """
1847a4a8
JS
646 events_wait waits for and returns a single named event from QMP.
647 In the case of multiple qualifying events, this function returns the
648 first one.
abf0bf99 649
1847a4a8
JS
650 :param events: A sequence of (name, match_criteria) tuples.
651 The match criteria are optional and may be None.
652 See event_match for details.
653 :param timeout: Optional timeout, in seconds.
654 See QEMUMonitorProtocol.pull_event.
655
656 :raise QMPTimeoutError: If timeout was non-zero and no matching events
657 were found.
658 :return: A QMP event matching the filter criteria.
659 If timeout was 0 and no event matched, None.
abf0bf99 660 """
f12a282f 661 def _match(event: QMPMessage) -> bool:
abf0bf99 662 for name, match in events:
306dfcd6 663 if event['event'] == name and self.event_match(event, match):
abf0bf99
JS
664 return True
665 return False
666
1847a4a8
JS
667 event: Optional[QMPMessage]
668
abf0bf99
JS
669 # Search cached events
670 for event in self._events:
671 if _match(event):
672 self._events.remove(event)
673 return event
674
675 # Poll for new events
676 while True:
677 event = self._qmp.pull_event(wait=timeout)
1847a4a8
JS
678 if event is None:
679 # NB: None is only returned when timeout is false-ish.
680 # Timeouts raise QMPTimeoutError instead!
681 break
abf0bf99
JS
682 if _match(event):
683 return event
684 self._events.append(event)
685
686 return None
687
f12a282f 688 def get_log(self) -> Optional[str]:
abf0bf99
JS
689 """
690 After self.shutdown or failed qemu execution, this returns the output
691 of the qemu process.
692 """
693 return self._iolog
694
f12a282f 695 def add_args(self, *args: str) -> None:
abf0bf99
JS
696 """
697 Adds to the list of extra arguments to be given to the QEMU binary
698 """
699 self._args.extend(args)
700
f12a282f 701 def set_machine(self, machine_type: str) -> None:
abf0bf99
JS
702 """
703 Sets the machine type
704
705 If set, the machine type will be added to the base arguments
706 of the resulting QEMU command line.
707 """
708 self._machine = machine_type
709
f12a282f
JS
710 def set_console(self,
711 device_type: Optional[str] = None,
712 console_index: int = 0) -> None:
abf0bf99
JS
713 """
714 Sets the device type for a console device
715
716 If set, the console device and a backing character device will
717 be added to the base arguments of the resulting QEMU command
718 line.
719
720 This is a convenience method that will either use the provided
721 device type, or default to a "-serial chardev:console" command
722 line argument.
723
724 The actual setting of command line arguments will be be done at
725 machine launch time, as it depends on the temporary directory
726 to be created.
727
728 @param device_type: the device type, such as "isa-serial". If
729 None is given (the default value) a "-serial
730 chardev:console" command line argument will
731 be used instead, resorting to the machine's
732 default device type.
746f244d
PMD
733 @param console_index: the index of the console device to use.
734 If not zero, the command line will create
735 'index - 1' consoles and connect them to
736 the 'null' backing character device.
abf0bf99
JS
737 """
738 self._console_set = True
739 self._console_device_type = device_type
746f244d 740 self._console_index = console_index
abf0bf99
JS
741
742 @property
f12a282f 743 def console_socket(self) -> socket.socket:
abf0bf99
JS
744 """
745 Returns a socket connected to the console
746 """
747 if self._console_socket is None:
80ded8e9
RF
748 self._console_socket = console_socket.ConsoleSocket(
749 self._console_address,
750 file=self._console_log_path,
751 drain=self._drain_console)
abf0bf99 752 return self._console_socket
2ca6e26c
CR
753
754 @property
755 def temp_dir(self) -> str:
756 """
757 Returns a temporary directory to be used for this machine
758 """
759 if self._temp_dir is None:
760 self._temp_dir = tempfile.mkdtemp(prefix="qemu-machine-",
761 dir=self._base_temp_dir)
762 return self._temp_dir