3 # Copyright (C) 2015-2016 Red Hat Inc.
4 # Copyright (C) 2012 IBM Corp.
7 # Fam Zheng <famz@redhat.com>
9 # This work is licensed under the terms of the GNU GPL, version 2. See
10 # the COPYING file in the top-level directory.
26 LOG
= logging
.getLogger(__name__
)
28 # Mapping host architecture to any additional architectures it can
29 # support which often includes its 32 bit cousin.
35 def kvm_available(target_arch
=None):
36 host_arch
= os
.uname()[4]
37 if target_arch
and target_arch
!= host_arch
:
38 if target_arch
!= ADDITIONAL_ARCHES
.get(host_arch
):
40 return os
.access("/dev/kvm", os
.R_OK | os
.W_OK
)
43 #: Maps machine types to the preferred console device types
45 r
'^clipper$': 'isa-serial',
46 r
'^malta': 'isa-serial',
47 r
'^(pc.*|q35.*|isapc)$': 'isa-serial',
48 r
'^(40p|powernv|prep)$': 'isa-serial',
49 r
'^pseries.*': 'spapr-vty',
50 r
'^s390-ccw-virtio.*': 'sclpconsole',
54 class QEMUMachineError(Exception):
56 Exception called when an error in QEMUMachine happens.
60 class QEMUMachineAddDeviceError(QEMUMachineError
):
62 Exception raised when a request to add a device can not be fulfilled
64 The failures are caused by limitations, lack of information or conflicting
65 requests on the QEMUMachine methods. This exception does not represent
66 failures reported by the QEMU binary itself.
69 class MonitorResponseError(qmp
.qmp
.QMPError
):
71 Represents erroneous QMP monitor reply
73 def __init__(self
, reply
):
75 desc
= reply
["error"]["desc"]
78 super(MonitorResponseError
, self
).__init
__(desc
)
82 class QEMUMachine(object):
86 Use this object as a context manager to ensure the QEMU process terminates::
88 with VM(binary) as vm:
90 # vm is guaranteed to be shut down here
93 def __init__(self
, binary
, args
=None, wrapper
=None, name
=None,
94 test_dir
="/var/tmp", monitor_address
=None,
95 socket_scm_helper
=None):
97 Initialize a QEMUMachine
99 @param binary: path to the qemu binary
100 @param args: list of extra arguments
101 @param wrapper: list of arguments used as prefix to qemu binary
102 @param name: prefix for socket and log file names (default: qemu-PID)
103 @param test_dir: where to create socket and log file
104 @param monitor_address: address for QMP monitor
105 @param socket_scm_helper: helper program, required for send_fd_scm()
106 @note: Qemu process is not started until launch() is used.
113 name
= "qemu-%d" % os
.getpid()
115 self
._monitor
_address
= monitor_address
116 self
._vm
_monitor
= None
117 self
._qemu
_log
_path
= None
118 self
._qemu
_log
_file
= None
120 self
._binary
= binary
121 self
._args
= list(args
) # Force copy args in case we modify them
122 self
._wrapper
= wrapper
125 self
._socket
_scm
_helper
= socket_scm_helper
127 self
._qemu
_full
_args
= None
128 self
._test
_dir
= test_dir
129 self
._temp
_dir
= None
130 self
._launched
= False
132 self
._console
_device
_type
= None
133 self
._console
_address
= None
134 self
._console
_socket
= None
136 # just in case logging wasn't configured by the main script:
137 logging
.basicConfig()
142 def __exit__(self
, exc_type
, exc_val
, exc_tb
):
146 # This can be used to add an unused monitor instance.
147 def add_monitor_null(self
):
148 self
._args
.append('-monitor')
149 self
._args
.append('null')
151 def add_fd(self
, fd
, fdset
, opaque
, opts
=''):
153 Pass a file descriptor to the VM
155 options
= ['fd=%d' % fd
,
157 'opaque=%s' % opaque
]
161 # This did not exist before 3.4, but since then it is
162 # mandatory for our purpose
163 if hasattr(os
, 'set_inheritable'):
164 os
.set_inheritable(fd
, True)
166 self
._args
.append('-add-fd')
167 self
._args
.append(','.join(options
))
170 # Exactly one of fd and file_path must be given.
171 # (If it is file_path, the helper will open that file and pass its
173 def send_fd_scm(self
, fd
=None, file_path
=None):
174 # In iotest.py, the qmp should always use unix socket.
175 assert self
._qmp
.is_scm_available()
176 if self
._socket
_scm
_helper
is None:
177 raise QEMUMachineError("No path to socket_scm_helper set")
178 if not os
.path
.exists(self
._socket
_scm
_helper
):
179 raise QEMUMachineError("%s does not exist" %
180 self
._socket
_scm
_helper
)
182 # This did not exist before 3.4, but since then it is
183 # mandatory for our purpose
184 if hasattr(os
, 'set_inheritable'):
185 os
.set_inheritable(self
._qmp
.get_sock_fd(), True)
187 os
.set_inheritable(fd
, True)
189 fd_param
= ["%s" % self
._socket
_scm
_helper
,
190 "%d" % self
._qmp
.get_sock_fd()]
192 if file_path
is not None:
194 fd_param
.append(file_path
)
196 assert fd
is not None
197 fd_param
.append(str(fd
))
199 devnull
= open(os
.path
.devnull
, 'rb')
200 proc
= subprocess
.Popen(fd_param
, stdin
=devnull
, stdout
=subprocess
.PIPE
,
201 stderr
=subprocess
.STDOUT
, close_fds
=False)
202 output
= proc
.communicate()[0]
206 return proc
.returncode
209 def _remove_if_exists(path
):
211 Remove file object at path if it exists
215 except OSError as exception
:
216 if exception
.errno
== errno
.ENOENT
:
220 def is_running(self
):
221 return self
._popen
is not None and self
._popen
.poll() is None
224 if self
._popen
is None:
226 return self
._popen
.poll()
229 if not self
.is_running():
231 return self
._popen
.pid
233 def _load_io_log(self
):
234 if self
._qemu
_log
_path
is not None:
235 with
open(self
._qemu
_log
_path
, "r") as iolog
:
236 self
._iolog
= iolog
.read()
238 def _base_args(self
):
239 if isinstance(self
._monitor
_address
, tuple):
240 moncdev
= "socket,id=mon,host=%s,port=%s" % (
241 self
._monitor
_address
[0],
242 self
._monitor
_address
[1])
244 moncdev
= 'socket,id=mon,path=%s' % self
._vm
_monitor
245 args
= ['-chardev', moncdev
,
246 '-mon', 'chardev=mon,mode=control',
247 '-display', 'none', '-vga', 'none']
248 if self
._machine
is not None:
249 args
.extend(['-machine', self
._machine
])
250 if self
._console
_device
_type
is not None:
251 self
._console
_address
= os
.path
.join(self
._temp
_dir
,
252 self
._name
+ "-console.sock")
253 chardev
= ('socket,id=console,path=%s,server,nowait' %
254 self
._console
_address
)
255 device
= '%s,chardev=console' % self
._console
_device
_type
256 args
.extend(['-chardev', chardev
, '-device', device
])
259 def _pre_launch(self
):
260 self
._temp
_dir
= tempfile
.mkdtemp(dir=self
._test
_dir
)
261 if self
._monitor
_address
is not None:
262 self
._vm
_monitor
= self
._monitor
_address
264 self
._vm
_monitor
= os
.path
.join(self
._temp
_dir
,
265 self
._name
+ "-monitor.sock")
266 self
._qemu
_log
_path
= os
.path
.join(self
._temp
_dir
, self
._name
+ ".log")
267 self
._qemu
_log
_file
= open(self
._qemu
_log
_path
, 'wb')
269 self
._qmp
= qmp
.qmp
.QEMUMonitorProtocol(self
._vm
_monitor
,
272 def _post_launch(self
):
275 def _post_shutdown(self
):
276 if self
._qemu
_log
_file
is not None:
277 self
._qemu
_log
_file
.close()
278 self
._qemu
_log
_file
= None
280 self
._qemu
_log
_path
= None
282 if self
._console
_socket
is not None:
283 self
._console
_socket
.close()
284 self
._console
_socket
= None
286 if self
._temp
_dir
is not None:
287 shutil
.rmtree(self
._temp
_dir
)
288 self
._temp
_dir
= None
292 Launch the VM and make sure we cleanup and expose the
293 command line/output in case of exception
297 raise QEMUMachineError('VM already launched')
300 self
._qemu
_full
_args
= None
303 self
._launched
= True
307 LOG
.debug('Error launching VM')
308 if self
._qemu
_full
_args
:
309 LOG
.debug('Command: %r', ' '.join(self
._qemu
_full
_args
))
311 LOG
.debug('Output: %r', self
._iolog
)
316 Launch the VM and establish a QMP connection
318 devnull
= open(os
.path
.devnull
, 'rb')
320 self
._qemu
_full
_args
= (self
._wrapper
+ [self
._binary
] +
321 self
._base
_args
() + self
._args
)
322 self
._popen
= subprocess
.Popen(self
._qemu
_full
_args
,
324 stdout
=self
._qemu
_log
_file
,
325 stderr
=subprocess
.STDOUT
,
332 Wait for the VM to power off
337 self
._post
_shutdown
()
341 Terminate the VM and clean up
343 if self
.is_running():
345 self
._qmp
.cmd('quit')
352 self
._post
_shutdown
()
354 exitcode
= self
.exitcode()
355 if exitcode
is not None and exitcode
< 0:
356 msg
= 'qemu received signal %i: %s'
357 if self
._qemu
_full
_args
:
358 command
= ' '.join(self
._qemu
_full
_args
)
361 LOG
.warn(msg
, -exitcode
, command
)
363 self
._launched
= False
365 def qmp(self
, cmd
, conv_keys
=True, **args
):
367 Invoke a QMP command and return the response dict
370 for key
, value
in args
.items():
372 qmp_args
[key
.replace('_', '-')] = value
374 qmp_args
[key
] = value
376 return self
._qmp
.cmd(cmd
, args
=qmp_args
)
378 def command(self
, cmd
, conv_keys
=True, **args
):
380 Invoke a QMP command.
381 On success return the response dict.
382 On failure raise an exception.
384 reply
= self
.qmp(cmd
, conv_keys
, **args
)
386 raise qmp
.qmp
.QMPError("Monitor is closed")
388 raise MonitorResponseError(reply
)
389 return reply
["return"]
391 def get_qmp_event(self
, wait
=False):
393 Poll for one queued QMP events and return it
395 if len(self
._events
) > 0:
396 return self
._events
.pop(0)
397 return self
._qmp
.pull_event(wait
=wait
)
399 def get_qmp_events(self
, wait
=False):
401 Poll for queued QMP events and return a list of dicts
403 events
= self
._qmp
.get_events(wait
=wait
)
404 events
.extend(self
._events
)
406 self
._qmp
.clear_events()
409 def event_wait(self
, name
, timeout
=60.0, match
=None):
411 Wait for specified timeout on named event in QMP; optionally filter
414 The 'match' is checked to be a recursive subset of the 'event'; skips
415 branch processing on match's value None
416 {"foo": {"bar": 1}} matches {"foo": None}
417 {"foo": {"bar": 1}} does not matches {"foo": {"baz": None}}
419 def event_match(event
, match
=None):
425 if isinstance(event
[key
], dict):
426 if not event_match(event
[key
], match
[key
]):
428 elif event
[key
] != match
[key
]:
435 # Search cached events
436 for event
in self
._events
:
437 if (event
['event'] == name
) and event_match(event
, match
):
438 self
._events
.remove(event
)
441 # Poll for new events
443 event
= self
._qmp
.pull_event(wait
=timeout
)
444 if (event
['event'] == name
) and event_match(event
, match
):
446 self
._events
.append(event
)
452 After self.shutdown or failed qemu execution, this returns the output
457 def add_args(self
, *args
):
459 Adds to the list of extra arguments to be given to the QEMU binary
461 self
._args
.extend(args
)
463 def set_machine(self
, machine_type
):
465 Sets the machine type
467 If set, the machine type will be added to the base arguments
468 of the resulting QEMU command line.
470 self
._machine
= machine_type
472 def set_console(self
, device_type
=None):
474 Sets the device type for a console device
476 If set, the console device and a backing character device will
477 be added to the base arguments of the resulting QEMU command
480 This is a convenience method that will either use the provided
481 device type, of if not given, it will used the device type set
482 on CONSOLE_DEV_TYPES.
484 The actual setting of command line arguments will be be done at
485 machine launch time, as it depends on the temporary directory
488 @param device_type: the device type, such as "isa-serial"
489 @raises: QEMUMachineAddDeviceError if the device type is not given
490 and can not be determined.
492 if device_type
is None:
493 if self
._machine
is None:
494 raise QEMUMachineAddDeviceError("Can not add a console device:"
495 " QEMU instance without a "
496 "defined machine type")
497 for regex
, device
in CONSOLE_DEV_TYPES
.items():
498 if re
.match(regex
, self
._machine
):
501 if device_type
is None:
502 raise QEMUMachineAddDeviceError("Can not add a console device:"
503 " no matching console device "
505 self
._console
_device
_type
= device_type
508 def console_socket(self
):
510 Returns a socket connected to the console
512 if self
._console
_socket
is None:
513 self
._console
_socket
= socket
.socket(socket
.AF_UNIX
,
515 self
._console
_socket
.connect(self
._console
_address
)
516 return self
._console
_socket