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