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