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