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_telnet(self
, ip
, port
):
148 args
= 'tcp:%s:%d,server,nowait,telnet' % (ip
, port
)
149 self
._args
.append('-monitor')
150 self
._args
.append(args
)
152 def add_fd(self
, fd
, fdset
, opaque
, opts
=''):
154 Pass a file descriptor to the VM
156 options
= ['fd=%d' % fd
,
158 'opaque=%s' % opaque
]
162 # This did not exist before 3.4, but since then it is
163 # mandatory for our purpose
164 if hasattr(os
, 'set_inheritable'):
165 os
.set_inheritable(fd
, True)
167 self
._args
.append('-add-fd')
168 self
._args
.append(','.join(options
))
171 # Exactly one of fd and file_path must be given.
172 # (If it is file_path, the helper will open that file and pass its
174 def send_fd_scm(self
, fd
=None, file_path
=None):
175 # In iotest.py, the qmp should always use unix socket.
176 assert self
._qmp
.is_scm_available()
177 if self
._socket
_scm
_helper
is None:
178 raise QEMUMachineError("No path to socket_scm_helper set")
179 if not os
.path
.exists(self
._socket
_scm
_helper
):
180 raise QEMUMachineError("%s does not exist" %
181 self
._socket
_scm
_helper
)
183 # This did not exist before 3.4, but since then it is
184 # mandatory for our purpose
185 if hasattr(os
, 'set_inheritable'):
186 os
.set_inheritable(self
._qmp
.get_sock_fd(), True)
188 os
.set_inheritable(fd
, True)
190 fd_param
= ["%s" % self
._socket
_scm
_helper
,
191 "%d" % self
._qmp
.get_sock_fd()]
193 if file_path
is not None:
195 fd_param
.append(file_path
)
197 assert fd
is not None
198 fd_param
.append(str(fd
))
200 devnull
= open(os
.path
.devnull
, 'rb')
201 proc
= subprocess
.Popen(fd_param
, stdin
=devnull
, stdout
=subprocess
.PIPE
,
202 stderr
=subprocess
.STDOUT
, close_fds
=False)
203 output
= proc
.communicate()[0]
207 return proc
.returncode
210 def _remove_if_exists(path
):
212 Remove file object at path if it exists
216 except OSError as exception
:
217 if exception
.errno
== errno
.ENOENT
:
221 def is_running(self
):
222 return self
._popen
is not None and self
._popen
.poll() is None
225 if self
._popen
is None:
227 return self
._popen
.poll()
230 if not self
.is_running():
232 return self
._popen
.pid
234 def _load_io_log(self
):
235 if self
._qemu
_log
_path
is not None:
236 with
open(self
._qemu
_log
_path
, "r") as iolog
:
237 self
._iolog
= iolog
.read()
239 def _base_args(self
):
240 if isinstance(self
._monitor
_address
, tuple):
241 moncdev
= "socket,id=mon,host=%s,port=%s" % (
242 self
._monitor
_address
[0],
243 self
._monitor
_address
[1])
245 moncdev
= 'socket,id=mon,path=%s' % self
._vm
_monitor
246 args
= ['-chardev', moncdev
,
247 '-mon', 'chardev=mon,mode=control',
248 '-display', 'none', '-vga', 'none']
249 if self
._machine
is not None:
250 args
.extend(['-machine', self
._machine
])
251 if self
._console
_device
_type
is not None:
252 self
._console
_address
= os
.path
.join(self
._temp
_dir
,
253 self
._name
+ "-console.sock")
254 chardev
= ('socket,id=console,path=%s,server,nowait' %
255 self
._console
_address
)
256 device
= '%s,chardev=console' % self
._console
_device
_type
257 args
.extend(['-chardev', chardev
, '-device', device
])
260 def _pre_launch(self
):
261 self
._temp
_dir
= tempfile
.mkdtemp(dir=self
._test
_dir
)
262 if self
._monitor
_address
is not None:
263 self
._vm
_monitor
= self
._monitor
_address
265 self
._vm
_monitor
= os
.path
.join(self
._temp
_dir
,
266 self
._name
+ "-monitor.sock")
267 self
._qemu
_log
_path
= os
.path
.join(self
._temp
_dir
, self
._name
+ ".log")
268 self
._qemu
_log
_file
= open(self
._qemu
_log
_path
, 'wb')
270 self
._qmp
= qmp
.qmp
.QEMUMonitorProtocol(self
._vm
_monitor
,
273 def _post_launch(self
):
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
281 self
._qemu
_log
_path
= None
283 if self
._console
_socket
is not None:
284 self
._console
_socket
.close()
285 self
._console
_socket
= None
287 if self
._temp
_dir
is not None:
288 shutil
.rmtree(self
._temp
_dir
)
289 self
._temp
_dir
= None
293 Launch the VM and make sure we cleanup and expose the
294 command line/output in case of exception
298 raise QEMUMachineError('VM already launched')
301 self
._qemu
_full
_args
= None
304 self
._launched
= True
308 LOG
.debug('Error launching VM')
309 if self
._qemu
_full
_args
:
310 LOG
.debug('Command: %r', ' '.join(self
._qemu
_full
_args
))
312 LOG
.debug('Output: %r', self
._iolog
)
317 Launch the VM and establish a QMP connection
319 devnull
= open(os
.path
.devnull
, 'rb')
321 self
._qemu
_full
_args
= (self
._wrapper
+ [self
._binary
] +
322 self
._base
_args
() + self
._args
)
323 self
._popen
= subprocess
.Popen(self
._qemu
_full
_args
,
325 stdout
=self
._qemu
_log
_file
,
326 stderr
=subprocess
.STDOUT
,
333 Wait for the VM to power off
338 self
._post
_shutdown
()
342 Terminate the VM and clean up
344 if self
.is_running():
346 self
._qmp
.cmd('quit')
353 self
._post
_shutdown
()
355 exitcode
= self
.exitcode()
356 if exitcode
is not None and exitcode
< 0:
357 msg
= 'qemu received signal %i: %s'
358 if self
._qemu
_full
_args
:
359 command
= ' '.join(self
._qemu
_full
_args
)
362 LOG
.warn(msg
, -exitcode
, command
)
364 self
._launched
= False
366 def qmp(self
, cmd
, conv_keys
=True, **args
):
368 Invoke a QMP command and return the response dict
371 for key
, value
in args
.items():
373 qmp_args
[key
.replace('_', '-')] = value
375 qmp_args
[key
] = value
377 return self
._qmp
.cmd(cmd
, args
=qmp_args
)
379 def command(self
, cmd
, conv_keys
=True, **args
):
381 Invoke a QMP command.
382 On success return the response dict.
383 On failure raise an exception.
385 reply
= self
.qmp(cmd
, conv_keys
, **args
)
387 raise qmp
.qmp
.QMPError("Monitor is closed")
389 raise MonitorResponseError(reply
)
390 return reply
["return"]
392 def get_qmp_event(self
, wait
=False):
394 Poll for one queued QMP events and return it
396 if len(self
._events
) > 0:
397 return self
._events
.pop(0)
398 return self
._qmp
.pull_event(wait
=wait
)
400 def get_qmp_events(self
, wait
=False):
402 Poll for queued QMP events and return a list of dicts
404 events
= self
._qmp
.get_events(wait
=wait
)
405 events
.extend(self
._events
)
407 self
._qmp
.clear_events()
410 def event_wait(self
, name
, timeout
=60.0, match
=None):
412 Wait for specified timeout on named event in QMP; optionally filter
415 The 'match' is checked to be a recursive subset of the 'event'; skips
416 branch processing on match's value None
417 {"foo": {"bar": 1}} matches {"foo": None}
418 {"foo": {"bar": 1}} does not matches {"foo": {"baz": None}}
420 def event_match(event
, match
=None):
426 if isinstance(event
[key
], dict):
427 if not event_match(event
[key
], match
[key
]):
429 elif event
[key
] != match
[key
]:
436 # Search cached events
437 for event
in self
._events
:
438 if (event
['event'] == name
) and event_match(event
, match
):
439 self
._events
.remove(event
)
442 # Poll for new events
444 event
= self
._qmp
.pull_event(wait
=timeout
)
445 if (event
['event'] == name
) and event_match(event
, match
):
447 self
._events
.append(event
)
453 After self.shutdown or failed qemu execution, this returns the output
458 def add_args(self
, *args
):
460 Adds to the list of extra arguments to be given to the QEMU binary
462 self
._args
.extend(args
)
464 def set_machine(self
, machine_type
):
466 Sets the machine type
468 If set, the machine type will be added to the base arguments
469 of the resulting QEMU command line.
471 self
._machine
= machine_type
473 def set_console(self
, device_type
=None):
475 Sets the device type for a console device
477 If set, the console device and a backing character device will
478 be added to the base arguments of the resulting QEMU command
481 This is a convenience method that will either use the provided
482 device type, of if not given, it will used the device type set
483 on CONSOLE_DEV_TYPES.
485 The actual setting of command line arguments will be be done at
486 machine launch time, as it depends on the temporary directory
489 @param device_type: the device type, such as "isa-serial"
490 @raises: QEMUMachineAddDeviceError if the device type is not given
491 and can not be determined.
493 if device_type
is None:
494 if self
._machine
is None:
495 raise QEMUMachineAddDeviceError("Can not add a console device:"
496 " QEMU instance without a "
497 "defined machine type")
498 for regex
, device
in CONSOLE_DEV_TYPES
.items():
499 if re
.match(regex
, self
._machine
):
502 if device_type
is None:
503 raise QEMUMachineAddDeviceError("Can not add a console device:"
504 " no matching console device "
506 self
._console
_device
_type
= device_type
509 def console_socket(self
):
511 Returns a socket connected to the console
513 if self
._console
_socket
is None:
514 self
._console
_socket
= socket
.socket(socket
.AF_UNIX
,
516 self
._console
_socket
.connect(self
._console
_address
)
517 return self
._console
_socket