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