]> Git Repo - qemu.git/blob - scripts/qemu.py
Merge remote-tracking branch 'remotes/kraxel/tags/usb-20180618-pull-request' into...
[qemu.git] / scripts / qemu.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 qmp.qmp
20 import re
21 import shutil
22 import socket
23 import tempfile
24
25
26 LOG = logging.getLogger(__name__)
27
28
29 #: Maps machine types to the preferred console device types
30 CONSOLE_DEV_TYPES = {
31     r'^clipper$': 'isa-serial',
32     r'^malta': 'isa-serial',
33     r'^(pc.*|q35.*|isapc)$': 'isa-serial',
34     r'^(40p|powernv|prep)$': 'isa-serial',
35     r'^pseries.*': 'spapr-vty',
36     r'^s390-ccw-virtio.*': 'sclpconsole',
37     }
38
39
40 class QEMUMachineError(Exception):
41     """
42     Exception called when an error in QEMUMachine happens.
43     """
44
45
46 class QEMUMachineAddDeviceError(QEMUMachineError):
47     """
48     Exception raised when a request to add a device can not be fulfilled
49
50     The failures are caused by limitations, lack of information or conflicting
51     requests on the QEMUMachine methods.  This exception does not represent
52     failures reported by the QEMU binary itself.
53     """
54
55 class MonitorResponseError(qmp.qmp.QMPError):
56     '''
57     Represents erroneous QMP monitor reply
58     '''
59     def __init__(self, reply):
60         try:
61             desc = reply["error"]["desc"]
62         except KeyError:
63             desc = reply
64         super(MonitorResponseError, self).__init__(desc)
65         self.reply = reply
66
67
68 class QEMUMachine(object):
69     '''A QEMU VM
70
71     Use this object as a context manager to ensure the QEMU process terminates::
72
73         with VM(binary) as vm:
74             ...
75         # vm is guaranteed to be shut down here
76     '''
77
78     def __init__(self, binary, args=None, wrapper=None, name=None,
79                  test_dir="/var/tmp", monitor_address=None,
80                  socket_scm_helper=None):
81         '''
82         Initialize a QEMUMachine
83
84         @param binary: path to the qemu binary
85         @param args: list of extra arguments
86         @param wrapper: list of arguments used as prefix to qemu binary
87         @param name: prefix for socket and log file names (default: qemu-PID)
88         @param test_dir: where to create socket and log file
89         @param monitor_address: address for QMP monitor
90         @param socket_scm_helper: helper program, required for send_fd_scm()"
91         @note: Qemu process is not started until launch() is used.
92         '''
93         if args is None:
94             args = []
95         if wrapper is None:
96             wrapper = []
97         if name is None:
98             name = "qemu-%d" % os.getpid()
99         self._name = name
100         self._monitor_address = monitor_address
101         self._vm_monitor = None
102         self._qemu_log_path = None
103         self._qemu_log_file = None
104         self._popen = None
105         self._binary = binary
106         self._args = list(args)     # Force copy args in case we modify them
107         self._wrapper = wrapper
108         self._events = []
109         self._iolog = None
110         self._socket_scm_helper = socket_scm_helper
111         self._qmp = None
112         self._qemu_full_args = None
113         self._test_dir = test_dir
114         self._temp_dir = None
115         self._launched = False
116         self._machine = None
117         self._console_device_type = None
118         self._console_address = None
119         self._console_socket = None
120
121         # just in case logging wasn't configured by the main script:
122         logging.basicConfig()
123
124     def __enter__(self):
125         return self
126
127     def __exit__(self, exc_type, exc_val, exc_tb):
128         self.shutdown()
129         return False
130
131     # This can be used to add an unused monitor instance.
132     def add_monitor_telnet(self, ip, port):
133         args = 'tcp:%s:%d,server,nowait,telnet' % (ip, port)
134         self._args.append('-monitor')
135         self._args.append(args)
136
137     def add_fd(self, fd, fdset, opaque, opts=''):
138         '''Pass a file descriptor to the VM'''
139         options = ['fd=%d' % fd,
140                    'set=%d' % fdset,
141                    'opaque=%s' % opaque]
142         if opts:
143             options.append(opts)
144
145         self._args.append('-add-fd')
146         self._args.append(','.join(options))
147         return self
148
149     def send_fd_scm(self, fd_file_path):
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         fd_param = ["%s" % self._socket_scm_helper,
158                     "%d" % self._qmp.get_sock_fd(),
159                     "%s" % fd_file_path]
160         devnull = open(os.path.devnull, 'rb')
161         proc = subprocess.Popen(fd_param, stdin=devnull, stdout=subprocess.PIPE,
162                                 stderr=subprocess.STDOUT)
163         output = proc.communicate()[0]
164         if output:
165             LOG.debug(output)
166
167         return proc.returncode
168
169     @staticmethod
170     def _remove_if_exists(path):
171         '''Remove file object at path if it exists'''
172         try:
173             os.remove(path)
174         except OSError as exception:
175             if exception.errno == errno.ENOENT:
176                 return
177             raise
178
179     def is_running(self):
180         return self._popen is not None and self._popen.poll() is None
181
182     def exitcode(self):
183         if self._popen is None:
184             return None
185         return self._popen.poll()
186
187     def get_pid(self):
188         if not self.is_running():
189             return None
190         return self._popen.pid
191
192     def _load_io_log(self):
193         if self._qemu_log_path is not None:
194             with open(self._qemu_log_path, "r") as iolog:
195                 self._iolog = iolog.read()
196
197     def _base_args(self):
198         if isinstance(self._monitor_address, tuple):
199             moncdev = "socket,id=mon,host=%s,port=%s" % (
200                 self._monitor_address[0],
201                 self._monitor_address[1])
202         else:
203             moncdev = 'socket,id=mon,path=%s' % self._vm_monitor
204         args = ['-chardev', moncdev,
205                 '-mon', 'chardev=mon,mode=control',
206                 '-display', 'none', '-vga', 'none']
207         if self._machine is not None:
208             args.extend(['-machine', self._machine])
209         if self._console_device_type is not None:
210             self._console_address = os.path.join(self._temp_dir,
211                                                  self._name + "-console.sock")
212             chardev = ('socket,id=console,path=%s,server,nowait' %
213                        self._console_address)
214             device = '%s,chardev=console' % self._console_device_type
215             args.extend(['-chardev', chardev, '-device', device])
216         return args
217
218     def _pre_launch(self):
219         self._temp_dir = tempfile.mkdtemp(dir=self._test_dir)
220         if self._monitor_address is not None:
221             self._vm_monitor = self._monitor_address
222         else:
223             self._vm_monitor = os.path.join(self._temp_dir,
224                                             self._name + "-monitor.sock")
225         self._qemu_log_path = os.path.join(self._temp_dir, self._name + ".log")
226         self._qemu_log_file = open(self._qemu_log_path, 'wb')
227
228         self._qmp = qmp.qmp.QEMUMonitorProtocol(self._vm_monitor,
229                                                 server=True)
230
231     def _post_launch(self):
232         self._qmp.accept()
233
234     def _post_shutdown(self):
235         if self._qemu_log_file is not None:
236             self._qemu_log_file.close()
237             self._qemu_log_file = None
238
239         self._qemu_log_path = None
240
241         if self._console_socket is not None:
242             self._console_socket.close()
243             self._console_socket = None
244
245         if self._temp_dir is not None:
246             shutil.rmtree(self._temp_dir)
247             self._temp_dir = None
248
249     def launch(self):
250         """
251         Launch the VM and make sure we cleanup and expose the
252         command line/output in case of exception
253         """
254
255         if self._launched:
256             raise QEMUMachineError('VM already launched')
257
258         self._iolog = None
259         self._qemu_full_args = None
260         try:
261             self._launch()
262             self._launched = True
263         except:
264             self.shutdown()
265
266             LOG.debug('Error launching VM')
267             if self._qemu_full_args:
268                 LOG.debug('Command: %r', ' '.join(self._qemu_full_args))
269             if self._iolog:
270                 LOG.debug('Output: %r', self._iolog)
271             raise
272
273     def _launch(self):
274         '''Launch the VM and establish a QMP connection'''
275         devnull = open(os.path.devnull, 'rb')
276         self._pre_launch()
277         self._qemu_full_args = (self._wrapper + [self._binary] +
278                                 self._base_args() + self._args)
279         self._popen = subprocess.Popen(self._qemu_full_args,
280                                        stdin=devnull,
281                                        stdout=self._qemu_log_file,
282                                        stderr=subprocess.STDOUT,
283                                        shell=False)
284         self._post_launch()
285
286     def wait(self):
287         '''Wait for the VM to power off'''
288         self._popen.wait()
289         self._qmp.close()
290         self._load_io_log()
291         self._post_shutdown()
292
293     def shutdown(self):
294         '''Terminate the VM and clean up'''
295         if self.is_running():
296             try:
297                 self._qmp.cmd('quit')
298                 self._qmp.close()
299             except:
300                 self._popen.kill()
301             self._popen.wait()
302
303         self._load_io_log()
304         self._post_shutdown()
305
306         exitcode = self.exitcode()
307         if exitcode is not None and exitcode < 0:
308             msg = 'qemu received signal %i: %s'
309             if self._qemu_full_args:
310                 command = ' '.join(self._qemu_full_args)
311             else:
312                 command = ''
313             LOG.warn(msg, exitcode, command)
314
315         self._launched = False
316
317     def qmp(self, cmd, conv_keys=True, **args):
318         '''Invoke a QMP command and return the response dict'''
319         qmp_args = dict()
320         for key, value in args.items():
321             if conv_keys:
322                 qmp_args[key.replace('_', '-')] = value
323             else:
324                 qmp_args[key] = value
325
326         return self._qmp.cmd(cmd, args=qmp_args)
327
328     def command(self, cmd, conv_keys=True, **args):
329         '''
330         Invoke a QMP command.
331         On success return the response dict.
332         On failure raise an exception.
333         '''
334         reply = self.qmp(cmd, conv_keys, **args)
335         if reply is None:
336             raise qmp.qmp.QMPError("Monitor is closed")
337         if "error" in reply:
338             raise MonitorResponseError(reply)
339         return reply["return"]
340
341     def get_qmp_event(self, wait=False):
342         '''Poll for one queued QMP events and return it'''
343         if len(self._events) > 0:
344             return self._events.pop(0)
345         return self._qmp.pull_event(wait=wait)
346
347     def get_qmp_events(self, wait=False):
348         '''Poll for queued QMP events and return a list of dicts'''
349         events = self._qmp.get_events(wait=wait)
350         events.extend(self._events)
351         del self._events[:]
352         self._qmp.clear_events()
353         return events
354
355     def event_wait(self, name, timeout=60.0, match=None):
356         '''
357         Wait for specified timeout on named event in QMP; optionally filter
358         results by match.
359
360         The 'match' is checked to be a recursive subset of the 'event'; skips
361         branch processing on match's value None
362            {"foo": {"bar": 1}} matches {"foo": None}
363            {"foo": {"bar": 1}} does not matches {"foo": {"baz": None}}
364         '''
365         def event_match(event, match=None):
366             if match is None:
367                 return True
368
369             for key in match:
370                 if key in event:
371                     if isinstance(event[key], dict):
372                         if not event_match(event[key], match[key]):
373                             return False
374                     elif event[key] != match[key]:
375                         return False
376                 else:
377                     return False
378
379             return True
380
381         # Search cached events
382         for event in self._events:
383             if (event['event'] == name) and event_match(event, match):
384                 self._events.remove(event)
385                 return event
386
387         # Poll for new events
388         while True:
389             event = self._qmp.pull_event(wait=timeout)
390             if (event['event'] == name) and event_match(event, match):
391                 return event
392             self._events.append(event)
393
394         return None
395
396     def get_log(self):
397         '''
398         After self.shutdown or failed qemu execution, this returns the output
399         of the qemu process.
400         '''
401         return self._iolog
402
403     def add_args(self, *args):
404         '''
405         Adds to the list of extra arguments to be given to the QEMU binary
406         '''
407         self._args.extend(args)
408
409     def set_machine(self, machine_type):
410         '''
411         Sets the machine type
412
413         If set, the machine type will be added to the base arguments
414         of the resulting QEMU command line.
415         '''
416         self._machine = machine_type
417
418     def set_console(self, device_type=None):
419         '''
420         Sets the device type for a console device
421
422         If set, the console device and a backing character device will
423         be added to the base arguments of the resulting QEMU command
424         line.
425
426         This is a convenience method that will either use the provided
427         device type, of if not given, it will used the device type set
428         on CONSOLE_DEV_TYPES.
429
430         The actual setting of command line arguments will be be done at
431         machine launch time, as it depends on the temporary directory
432         to be created.
433
434         @param device_type: the device type, such as "isa-serial"
435         @raises: QEMUMachineAddDeviceError if the device type is not given
436                  and can not be determined.
437         '''
438         if device_type is None:
439             if self._machine is None:
440                 raise QEMUMachineAddDeviceError("Can not add a console device:"
441                                                 " QEMU instance without a "
442                                                 "defined machine type")
443             for regex, device in CONSOLE_DEV_TYPES.items():
444                 if re.match(regex, self._machine):
445                     device_type = device
446                     break
447             if device_type is None:
448                 raise QEMUMachineAddDeviceError("Can not add a console device:"
449                                                 " no matching console device "
450                                                 "type definition")
451         self._console_device_type = device_type
452
453     @property
454     def console_socket(self):
455         """
456         Returns a socket connected to the console
457         """
458         if self._console_socket is None:
459             self._console_socket = socket.socket(socket.AF_UNIX,
460                                                  socket.SOCK_STREAM)
461             self._console_socket.connect(self._console_address)
462         return self._console_socket
This page took 0.054434 seconds and 4 git commands to generate.