]> Git Repo - qemu.git/blob - python/qemu/machine/machine.py
41be025ac7be7e32aec5531096cacb70a92f0fb1
[qemu.git] / python / qemu / machine / 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 <[email protected]>
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 from itertools import chain
22 import locale
23 import logging
24 import os
25 import shutil
26 import signal
27 import socket
28 import subprocess
29 import tempfile
30 from types import TracebackType
31 from typing import (
32     Any,
33     BinaryIO,
34     Dict,
35     List,
36     Optional,
37     Sequence,
38     Tuple,
39     Type,
40     TypeVar,
41 )
42
43 from qemu.aqmp import SocketAddrT
44 from qemu.aqmp.legacy import (
45     QEMUMonitorProtocol,
46     QMPMessage,
47     QMPReturnValue,
48 )
49
50 from . import console_socket
51
52
53 LOG = logging.getLogger(__name__)
54
55
56 class QEMUMachineError(Exception):
57     """
58     Exception called when an error in QEMUMachine happens.
59     """
60
61
62 class QEMUMachineAddDeviceError(QEMUMachineError):
63     """
64     Exception raised when a request to add a device can not be fulfilled
65
66     The failures are caused by limitations, lack of information or conflicting
67     requests on the QEMUMachine methods.  This exception does not represent
68     failures reported by the QEMU binary itself.
69     """
70
71
72 class VMLaunchFailure(QEMUMachineError):
73     """
74     Exception raised when a VM launch was attempted, but failed.
75     """
76     def __init__(self, exitcode: Optional[int],
77                  command: str, output: Optional[str]):
78         super().__init__(exitcode, command, output)
79         self.exitcode = exitcode
80         self.command = command
81         self.output = output
82
83     def __str__(self) -> str:
84         ret = ''
85         if self.__cause__ is not None:
86             name = type(self.__cause__).__name__
87             reason = str(self.__cause__)
88             if reason:
89                 ret += f"{name}: {reason}"
90             else:
91                 ret += f"{name}"
92         ret += '\n'
93
94         if self.exitcode is not None:
95             ret += f"\tExit code: {self.exitcode}\n"
96         ret += f"\tCommand: {self.command}\n"
97         ret += f"\tOutput: {self.output}\n"
98         return ret
99
100
101 class AbnormalShutdown(QEMUMachineError):
102     """
103     Exception raised when a graceful shutdown was requested, but not performed.
104     """
105
106
107 _T = TypeVar('_T', bound='QEMUMachine')
108
109
110 class QEMUMachine:
111     """
112     A QEMU VM.
113
114     Use this object as a context manager to ensure
115     the QEMU process terminates::
116
117         with VM(binary) as vm:
118             ...
119         # vm is guaranteed to be shut down here
120     """
121     # pylint: disable=too-many-instance-attributes, too-many-public-methods
122
123     def __init__(self,
124                  binary: str,
125                  args: Sequence[str] = (),
126                  wrapper: Sequence[str] = (),
127                  name: Optional[str] = None,
128                  base_temp_dir: str = "/var/tmp",
129                  monitor_address: Optional[SocketAddrT] = None,
130                  sock_dir: Optional[str] = None,
131                  drain_console: bool = False,
132                  console_log: Optional[str] = None,
133                  log_dir: Optional[str] = None,
134                  qmp_timer: Optional[float] = None):
135         '''
136         Initialize a QEMUMachine
137
138         @param binary: path to the qemu binary
139         @param args: list of extra arguments
140         @param wrapper: list of arguments used as prefix to qemu binary
141         @param name: prefix for socket and log file names (default: qemu-PID)
142         @param base_temp_dir: default location where temp files are created
143         @param monitor_address: address for QMP monitor
144         @param sock_dir: where to create socket (defaults to base_temp_dir)
145         @param drain_console: (optional) True to drain console socket to buffer
146         @param console_log: (optional) path to console log file
147         @param log_dir: where to create and keep log files
148         @param qmp_timer: (optional) default QMP socket timeout
149         @note: Qemu process is not started until launch() is used.
150         '''
151         # pylint: disable=too-many-arguments
152
153         # Direct user configuration
154
155         self._binary = binary
156         self._args = list(args)
157         self._wrapper = wrapper
158         self._qmp_timer = qmp_timer
159
160         self._name = name or f"qemu-{os.getpid()}-{id(self):02x}"
161         self._temp_dir: Optional[str] = None
162         self._base_temp_dir = base_temp_dir
163         self._sock_dir = sock_dir
164         self._log_dir = log_dir
165
166         if monitor_address is not None:
167             self._monitor_address = monitor_address
168         else:
169             self._monitor_address = os.path.join(
170                 self.sock_dir, f"{self._name}-monitor.sock"
171             )
172
173         self._console_log_path = console_log
174         if self._console_log_path:
175             # In order to log the console, buffering needs to be enabled.
176             self._drain_console = True
177         else:
178             self._drain_console = drain_console
179
180         # Runstate
181         self._qemu_log_path: Optional[str] = None
182         self._qemu_log_file: Optional[BinaryIO] = None
183         self._popen: Optional['subprocess.Popen[bytes]'] = None
184         self._events: List[QMPMessage] = []
185         self._iolog: Optional[str] = None
186         self._qmp_set = True   # Enable QMP monitor by default.
187         self._qmp_connection: Optional[QEMUMonitorProtocol] = None
188         self._qemu_full_args: Tuple[str, ...] = ()
189         self._launched = False
190         self._machine: Optional[str] = None
191         self._console_index = 0
192         self._console_set = False
193         self._console_device_type: Optional[str] = None
194         self._console_address = os.path.join(
195             self.sock_dir, f"{self._name}-console.sock"
196         )
197         self._console_socket: Optional[socket.socket] = None
198         self._remove_files: List[str] = []
199         self._user_killed = False
200         self._quit_issued = False
201
202     def __enter__(self: _T) -> _T:
203         return self
204
205     def __exit__(self,
206                  exc_type: Optional[Type[BaseException]],
207                  exc_val: Optional[BaseException],
208                  exc_tb: Optional[TracebackType]) -> None:
209         self.shutdown()
210
211     def add_monitor_null(self) -> None:
212         """
213         This can be used to add an unused monitor instance.
214         """
215         self._args.append('-monitor')
216         self._args.append('null')
217
218     def add_fd(self: _T, fd: int, fdset: int,
219                opaque: str, opts: str = '') -> _T:
220         """
221         Pass a file descriptor to the VM
222         """
223         options = ['fd=%d' % fd,
224                    'set=%d' % fdset,
225                    'opaque=%s' % opaque]
226         if opts:
227             options.append(opts)
228
229         # This did not exist before 3.4, but since then it is
230         # mandatory for our purpose
231         if hasattr(os, 'set_inheritable'):
232             os.set_inheritable(fd, True)
233
234         self._args.append('-add-fd')
235         self._args.append(','.join(options))
236         return self
237
238     def send_fd_scm(self, fd: Optional[int] = None,
239                     file_path: Optional[str] = None) -> int:
240         """
241         Send an fd or file_path to the remote via SCM_RIGHTS.
242
243         Exactly one of fd and file_path must be given.  If it is
244         file_path, the file will be opened read-only and the new file
245         descriptor will be sent to the remote.
246         """
247         if file_path is not None:
248             assert fd is None
249             with open(file_path, "rb") as passfile:
250                 fd = passfile.fileno()
251                 self._qmp.send_fd_scm(fd)
252         else:
253             assert fd is not None
254             self._qmp.send_fd_scm(fd)
255
256         return 0
257
258     @staticmethod
259     def _remove_if_exists(path: str) -> None:
260         """
261         Remove file object at path if it exists
262         """
263         try:
264             os.remove(path)
265         except OSError as exception:
266             if exception.errno == errno.ENOENT:
267                 return
268             raise
269
270     def is_running(self) -> bool:
271         """Returns true if the VM is running."""
272         return self._popen is not None and self._popen.poll() is None
273
274     @property
275     def _subp(self) -> 'subprocess.Popen[bytes]':
276         if self._popen is None:
277             raise QEMUMachineError('Subprocess pipe not present')
278         return self._popen
279
280     def exitcode(self) -> Optional[int]:
281         """Returns the exit code if possible, or None."""
282         if self._popen is None:
283             return None
284         return self._popen.poll()
285
286     def get_pid(self) -> Optional[int]:
287         """Returns the PID of the running process, or None."""
288         if not self.is_running():
289             return None
290         return self._subp.pid
291
292     def _load_io_log(self) -> None:
293         # Assume that the output encoding of QEMU's terminal output is
294         # defined by our locale. If indeterminate, allow open() to fall
295         # back to the platform default.
296         _, encoding = locale.getlocale()
297         if self._qemu_log_path is not None:
298             with open(self._qemu_log_path, "r", encoding=encoding) as iolog:
299                 self._iolog = iolog.read()
300
301     @property
302     def _base_args(self) -> List[str]:
303         args = ['-display', 'none', '-vga', 'none']
304
305         if self._qmp_set:
306             if isinstance(self._monitor_address, tuple):
307                 moncdev = "socket,id=mon,host={},port={}".format(
308                     *self._monitor_address
309                 )
310             else:
311                 moncdev = f"socket,id=mon,path={self._monitor_address}"
312             args.extend(['-chardev', moncdev, '-mon',
313                          'chardev=mon,mode=control'])
314
315         if self._machine is not None:
316             args.extend(['-machine', self._machine])
317         for _ in range(self._console_index):
318             args.extend(['-serial', 'null'])
319         if self._console_set:
320             chardev = ('socket,id=console,path=%s,server=on,wait=off' %
321                        self._console_address)
322             args.extend(['-chardev', chardev])
323             if self._console_device_type is None:
324                 args.extend(['-serial', 'chardev:console'])
325             else:
326                 device = '%s,chardev=console' % self._console_device_type
327                 args.extend(['-device', device])
328         return args
329
330     @property
331     def args(self) -> List[str]:
332         """Returns the list of arguments given to the QEMU binary."""
333         return self._args
334
335     def _pre_launch(self) -> None:
336         if self._console_set:
337             self._remove_files.append(self._console_address)
338
339         if self._qmp_set:
340             if isinstance(self._monitor_address, str):
341                 self._remove_files.append(self._monitor_address)
342             self._qmp_connection = QEMUMonitorProtocol(
343                 self._monitor_address,
344                 server=True,
345                 nickname=self._name
346             )
347
348         # NOTE: Make sure any opened resources are *definitely* freed in
349         # _post_shutdown()!
350         # pylint: disable=consider-using-with
351         self._qemu_log_path = os.path.join(self.log_dir, self._name + ".log")
352         self._qemu_log_file = open(self._qemu_log_path, 'wb')
353
354         self._iolog = None
355         self._qemu_full_args = tuple(chain(
356             self._wrapper,
357             [self._binary],
358             self._base_args,
359             self._args
360         ))
361
362     def _post_launch(self) -> None:
363         if self._qmp_connection:
364             self._qmp.accept(self._qmp_timer)
365
366     def _close_qemu_log_file(self) -> None:
367         if self._qemu_log_file is not None:
368             self._qemu_log_file.close()
369             self._qemu_log_file = None
370
371     def _post_shutdown(self) -> None:
372         """
373         Called to cleanup the VM instance after the process has exited.
374         May also be called after a failed launch.
375         """
376         try:
377             self._close_qmp_connection()
378         except Exception as err:  # pylint: disable=broad-except
379             LOG.warning(
380                 "Exception closing QMP connection: %s",
381                 str(err) if str(err) else type(err).__name__
382             )
383         finally:
384             assert self._qmp_connection is None
385
386         self._close_qemu_log_file()
387
388         self._load_io_log()
389
390         self._qemu_log_path = None
391
392         if self._temp_dir is not None:
393             shutil.rmtree(self._temp_dir)
394             self._temp_dir = None
395
396         while len(self._remove_files) > 0:
397             self._remove_if_exists(self._remove_files.pop())
398
399         exitcode = self.exitcode()
400         if (exitcode is not None and exitcode < 0
401                 and not (self._user_killed and exitcode == -signal.SIGKILL)):
402             msg = 'qemu received signal %i; command: "%s"'
403             if self._qemu_full_args:
404                 command = ' '.join(self._qemu_full_args)
405             else:
406                 command = ''
407             LOG.warning(msg, -int(exitcode), command)
408
409         self._quit_issued = False
410         self._user_killed = False
411         self._launched = False
412
413     def launch(self) -> None:
414         """
415         Launch the VM and make sure we cleanup and expose the
416         command line/output in case of exception
417         """
418
419         if self._launched:
420             raise QEMUMachineError('VM already launched')
421
422         try:
423             self._launch()
424         except BaseException as exc:
425             # We may have launched the process but it may
426             # have exited before we could connect via QMP.
427             # Assume the VM didn't launch or is exiting.
428             # If we don't wait for the process, exitcode() may still be
429             # 'None' by the time control is ceded back to the caller.
430             if self._launched:
431                 self.wait()
432             else:
433                 self._post_shutdown()
434
435             if isinstance(exc, Exception):
436                 raise VMLaunchFailure(
437                     exitcode=self.exitcode(),
438                     command=' '.join(self._qemu_full_args),
439                     output=self._iolog
440                 ) from exc
441
442             # Don't wrap 'BaseException'; doing so would downgrade
443             # that exception. However, we still want to clean up.
444             raise
445
446     def _launch(self) -> None:
447         """
448         Launch the VM and establish a QMP connection
449         """
450         self._pre_launch()
451         LOG.debug('VM launch command: %r', ' '.join(self._qemu_full_args))
452
453         # Cleaning up of this subprocess is guaranteed by _do_shutdown.
454         # pylint: disable=consider-using-with
455         self._popen = subprocess.Popen(self._qemu_full_args,
456                                        stdin=subprocess.DEVNULL,
457                                        stdout=self._qemu_log_file,
458                                        stderr=subprocess.STDOUT,
459                                        shell=False,
460                                        close_fds=False)
461         self._launched = True
462         self._post_launch()
463
464     def _close_qmp_connection(self) -> None:
465         """
466         Close the underlying QMP connection, if any.
467
468         Dutifully report errors that occurred while closing, but assume
469         that any error encountered indicates an abnormal termination
470         process and not a failure to close.
471         """
472         if self._qmp_connection is None:
473             return
474
475         try:
476             self._qmp.close()
477         except EOFError:
478             # EOF can occur as an Exception here when using the Async
479             # QMP backend. It indicates that the server closed the
480             # stream. If we successfully issued 'quit' at any point,
481             # then this was expected. If the remote went away without
482             # our permission, it's worth reporting that as an abnormal
483             # shutdown case.
484             if not (self._user_killed or self._quit_issued):
485                 raise
486         finally:
487             self._qmp_connection = None
488
489     def _early_cleanup(self) -> None:
490         """
491         Perform any cleanup that needs to happen before the VM exits.
492
493         This method may be called twice upon shutdown, once each by soft
494         and hard shutdown in failover scenarios.
495         """
496         # If we keep the console socket open, we may deadlock waiting
497         # for QEMU to exit, while QEMU is waiting for the socket to
498         # become writeable.
499         if self._console_socket is not None:
500             self._console_socket.close()
501             self._console_socket = None
502
503     def _hard_shutdown(self) -> None:
504         """
505         Perform early cleanup, kill the VM, and wait for it to terminate.
506
507         :raise subprocess.Timeout: When timeout is exceeds 60 seconds
508             waiting for the QEMU process to terminate.
509         """
510         self._early_cleanup()
511         self._subp.kill()
512         self._subp.wait(timeout=60)
513
514     def _soft_shutdown(self, timeout: Optional[int]) -> None:
515         """
516         Perform early cleanup, attempt to gracefully shut down the VM, and wait
517         for it to terminate.
518
519         :param timeout: Timeout in seconds for graceful shutdown.
520                         A value of None is an infinite wait.
521
522         :raise ConnectionReset: On QMP communication errors
523         :raise subprocess.TimeoutExpired: When timeout is exceeded waiting for
524             the QEMU process to terminate.
525         """
526         self._early_cleanup()
527
528         if self._qmp_connection:
529             try:
530                 if not self._quit_issued:
531                     # May raise ExecInterruptedError or StateError if the
532                     # connection dies or has *already* died.
533                     self.qmp('quit')
534             finally:
535                 # Regardless, we want to quiesce the connection.
536                 self._close_qmp_connection()
537
538         # May raise subprocess.TimeoutExpired
539         self._subp.wait(timeout=timeout)
540
541     def _do_shutdown(self, timeout: Optional[int]) -> None:
542         """
543         Attempt to shutdown the VM gracefully; fallback to a hard shutdown.
544
545         :param timeout: Timeout in seconds for graceful shutdown.
546                         A value of None is an infinite wait.
547
548         :raise AbnormalShutdown: When the VM could not be shut down gracefully.
549             The inner exception will likely be ConnectionReset or
550             subprocess.TimeoutExpired. In rare cases, non-graceful termination
551             may result in its own exceptions, likely subprocess.TimeoutExpired.
552         """
553         try:
554             self._soft_shutdown(timeout)
555         except Exception as exc:
556             self._hard_shutdown()
557             raise AbnormalShutdown("Could not perform graceful shutdown") \
558                 from exc
559
560     def shutdown(self,
561                  hard: bool = False,
562                  timeout: Optional[int] = 30) -> None:
563         """
564         Terminate the VM (gracefully if possible) and perform cleanup.
565         Cleanup will always be performed.
566
567         If the VM has not yet been launched, or shutdown(), wait(), or kill()
568         have already been called, this method does nothing.
569
570         :param hard: When true, do not attempt graceful shutdown, and
571                      suppress the SIGKILL warning log message.
572         :param timeout: Optional timeout in seconds for graceful shutdown.
573                         Default 30 seconds, A `None` value is an infinite wait.
574         """
575         if not self._launched:
576             return
577
578         try:
579             if hard:
580                 self._user_killed = True
581                 self._hard_shutdown()
582             else:
583                 self._do_shutdown(timeout)
584         finally:
585             self._post_shutdown()
586
587     def kill(self) -> None:
588         """
589         Terminate the VM forcefully, wait for it to exit, and perform cleanup.
590         """
591         self.shutdown(hard=True)
592
593     def wait(self, timeout: Optional[int] = 30) -> None:
594         """
595         Wait for the VM to power off and perform post-shutdown cleanup.
596
597         :param timeout: Optional timeout in seconds. Default 30 seconds.
598                         A value of `None` is an infinite wait.
599         """
600         self._quit_issued = True
601         self.shutdown(timeout=timeout)
602
603     def set_qmp_monitor(self, enabled: bool = True) -> None:
604         """
605         Set the QMP monitor.
606
607         @param enabled: if False, qmp monitor options will be removed from
608                         the base arguments of the resulting QEMU command
609                         line. Default is True.
610
611         .. note:: Call this function before launch().
612         """
613         self._qmp_set = enabled
614
615     @property
616     def _qmp(self) -> QEMUMonitorProtocol:
617         if self._qmp_connection is None:
618             raise QEMUMachineError("Attempt to access QMP with no connection")
619         return self._qmp_connection
620
621     @classmethod
622     def _qmp_args(cls, conv_keys: bool,
623                   args: Dict[str, Any]) -> Dict[str, object]:
624         if conv_keys:
625             return {k.replace('_', '-'): v for k, v in args.items()}
626
627         return args
628
629     def qmp(self, cmd: str,
630             args_dict: Optional[Dict[str, object]] = None,
631             conv_keys: Optional[bool] = None,
632             **args: Any) -> QMPMessage:
633         """
634         Invoke a QMP command and return the response dict
635         """
636         if args_dict is not None:
637             assert not args
638             assert conv_keys is None
639             args = args_dict
640             conv_keys = False
641
642         if conv_keys is None:
643             conv_keys = True
644
645         qmp_args = self._qmp_args(conv_keys, args)
646         ret = self._qmp.cmd(cmd, args=qmp_args)
647         if cmd == 'quit' and 'error' not in ret and 'return' in ret:
648             self._quit_issued = True
649         return ret
650
651     def command(self, cmd: str,
652                 conv_keys: bool = True,
653                 **args: Any) -> QMPReturnValue:
654         """
655         Invoke a QMP command.
656         On success return the response dict.
657         On failure raise an exception.
658         """
659         qmp_args = self._qmp_args(conv_keys, args)
660         ret = self._qmp.command(cmd, **qmp_args)
661         if cmd == 'quit':
662             self._quit_issued = True
663         return ret
664
665     def get_qmp_event(self, wait: bool = False) -> Optional[QMPMessage]:
666         """
667         Poll for one queued QMP events and return it
668         """
669         if self._events:
670             return self._events.pop(0)
671         return self._qmp.pull_event(wait=wait)
672
673     def get_qmp_events(self, wait: bool = False) -> List[QMPMessage]:
674         """
675         Poll for queued QMP events and return a list of dicts
676         """
677         events = self._qmp.get_events(wait=wait)
678         events.extend(self._events)
679         del self._events[:]
680         return events
681
682     @staticmethod
683     def event_match(event: Any, match: Optional[Any]) -> bool:
684         """
685         Check if an event matches optional match criteria.
686
687         The match criteria takes the form of a matching subdict. The event is
688         checked to be a superset of the subdict, recursively, with matching
689         values whenever the subdict values are not None.
690
691         This has a limitation that you cannot explicitly check for None values.
692
693         Examples, with the subdict queries on the left:
694          - None matches any object.
695          - {"foo": None} matches {"foo": {"bar": 1}}
696          - {"foo": None} matches {"foo": 5}
697          - {"foo": {"abc": None}} does not match {"foo": {"bar": 1}}
698          - {"foo": {"rab": 2}} matches {"foo": {"bar": 1, "rab": 2}}
699         """
700         if match is None:
701             return True
702
703         try:
704             for key in match:
705                 if key in event:
706                     if not QEMUMachine.event_match(event[key], match[key]):
707                         return False
708                 else:
709                     return False
710             return True
711         except TypeError:
712             # either match or event wasn't iterable (not a dict)
713             return bool(match == event)
714
715     def event_wait(self, name: str,
716                    timeout: float = 60.0,
717                    match: Optional[QMPMessage] = None) -> Optional[QMPMessage]:
718         """
719         event_wait waits for and returns a named event from QMP with a timeout.
720
721         name: The event to wait for.
722         timeout: QEMUMonitorProtocol.pull_event timeout parameter.
723         match: Optional match criteria. See event_match for details.
724         """
725         return self.events_wait([(name, match)], timeout)
726
727     def events_wait(self,
728                     events: Sequence[Tuple[str, Any]],
729                     timeout: float = 60.0) -> Optional[QMPMessage]:
730         """
731         events_wait waits for and returns a single named event from QMP.
732         In the case of multiple qualifying events, this function returns the
733         first one.
734
735         :param events: A sequence of (name, match_criteria) tuples.
736                        The match criteria are optional and may be None.
737                        See event_match for details.
738         :param timeout: Optional timeout, in seconds.
739                         See QEMUMonitorProtocol.pull_event.
740
741         :raise asyncio.TimeoutError:
742             If timeout was non-zero and no matching events were found.
743
744         :return: A QMP event matching the filter criteria.
745                  If timeout was 0 and no event matched, None.
746         """
747         def _match(event: QMPMessage) -> bool:
748             for name, match in events:
749                 if event['event'] == name and self.event_match(event, match):
750                     return True
751             return False
752
753         event: Optional[QMPMessage]
754
755         # Search cached events
756         for event in self._events:
757             if _match(event):
758                 self._events.remove(event)
759                 return event
760
761         # Poll for new events
762         while True:
763             event = self._qmp.pull_event(wait=timeout)
764             if event is None:
765                 # NB: None is only returned when timeout is false-ish.
766                 # Timeouts raise asyncio.TimeoutError instead!
767                 break
768             if _match(event):
769                 return event
770             self._events.append(event)
771
772         return None
773
774     def get_log(self) -> Optional[str]:
775         """
776         After self.shutdown or failed qemu execution, this returns the output
777         of the qemu process.
778         """
779         return self._iolog
780
781     def add_args(self, *args: str) -> None:
782         """
783         Adds to the list of extra arguments to be given to the QEMU binary
784         """
785         self._args.extend(args)
786
787     def set_machine(self, machine_type: str) -> None:
788         """
789         Sets the machine type
790
791         If set, the machine type will be added to the base arguments
792         of the resulting QEMU command line.
793         """
794         self._machine = machine_type
795
796     def set_console(self,
797                     device_type: Optional[str] = None,
798                     console_index: int = 0) -> None:
799         """
800         Sets the device type for a console device
801
802         If set, the console device and a backing character device will
803         be added to the base arguments of the resulting QEMU command
804         line.
805
806         This is a convenience method that will either use the provided
807         device type, or default to a "-serial chardev:console" command
808         line argument.
809
810         The actual setting of command line arguments will be be done at
811         machine launch time, as it depends on the temporary directory
812         to be created.
813
814         @param device_type: the device type, such as "isa-serial".  If
815                             None is given (the default value) a "-serial
816                             chardev:console" command line argument will
817                             be used instead, resorting to the machine's
818                             default device type.
819         @param console_index: the index of the console device to use.
820                               If not zero, the command line will create
821                               'index - 1' consoles and connect them to
822                               the 'null' backing character device.
823         """
824         self._console_set = True
825         self._console_device_type = device_type
826         self._console_index = console_index
827
828     @property
829     def console_socket(self) -> socket.socket:
830         """
831         Returns a socket connected to the console
832         """
833         if self._console_socket is None:
834             self._console_socket = console_socket.ConsoleSocket(
835                 self._console_address,
836                 file=self._console_log_path,
837                 drain=self._drain_console)
838         return self._console_socket
839
840     @property
841     def temp_dir(self) -> str:
842         """
843         Returns a temporary directory to be used for this machine
844         """
845         if self._temp_dir is None:
846             self._temp_dir = tempfile.mkdtemp(prefix="qemu-machine-",
847                                               dir=self._base_temp_dir)
848         return self._temp_dir
849
850     @property
851     def sock_dir(self) -> str:
852         """
853         Returns the directory used for sockfiles by this machine.
854         """
855         if self._sock_dir:
856             return self._sock_dir
857         return self.temp_dir
858
859     @property
860     def log_dir(self) -> str:
861         """
862         Returns a directory to be used for writing logs
863         """
864         if self._log_dir is None:
865             return self.temp_dir
866         return self._log_dir
This page took 0.066415 seconds and 2 git commands to generate.