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__
)
29 def kvm_available(target_arch
=None):
30 if target_arch
and target_arch
!= os
.uname()[4]:
32 return os
.access("/dev/kvm", os
.R_OK | os
.W_OK
)
35 #: Maps machine types to the preferred console device types
37 r
'^clipper$': 'isa-serial',
38 r
'^malta': 'isa-serial',
39 r
'^(pc.*|q35.*|isapc)$': 'isa-serial',
40 r
'^(40p|powernv|prep)$': 'isa-serial',
41 r
'^pseries.*': 'spapr-vty',
42 r
'^s390-ccw-virtio.*': 'sclpconsole',
46 class QEMUMachineError(Exception):
48 Exception called when an error in QEMUMachine happens.
52 class QEMUMachineAddDeviceError(QEMUMachineError
):
54 Exception raised when a request to add a device can not be fulfilled
56 The failures are caused by limitations, lack of information or conflicting
57 requests on the QEMUMachine methods. This exception does not represent
58 failures reported by the QEMU binary itself.
61 class MonitorResponseError(qmp
.qmp
.QMPError
):
63 Represents erroneous QMP monitor reply
65 def __init__(self
, reply
):
67 desc
= reply
["error"]["desc"]
70 super(MonitorResponseError
, self
).__init
__(desc
)
74 class QEMUMachine(object):
78 Use this object as a context manager to ensure the QEMU process terminates::
80 with VM(binary) as vm:
82 # vm is guaranteed to be shut down here
85 def __init__(self
, binary
, args
=None, wrapper
=None, name
=None,
86 test_dir
="/var/tmp", monitor_address
=None,
87 socket_scm_helper
=None):
89 Initialize a QEMUMachine
91 @param binary: path to the qemu binary
92 @param args: list of extra arguments
93 @param wrapper: list of arguments used as prefix to qemu binary
94 @param name: prefix for socket and log file names (default: qemu-PID)
95 @param test_dir: where to create socket and log file
96 @param monitor_address: address for QMP monitor
97 @param socket_scm_helper: helper program, required for send_fd_scm()
98 @note: Qemu process is not started until launch() is used.
105 name
= "qemu-%d" % os
.getpid()
107 self
._monitor
_address
= monitor_address
108 self
._vm
_monitor
= None
109 self
._qemu
_log
_path
= None
110 self
._qemu
_log
_file
= None
112 self
._binary
= binary
113 self
._args
= list(args
) # Force copy args in case we modify them
114 self
._wrapper
= wrapper
117 self
._socket
_scm
_helper
= socket_scm_helper
119 self
._qemu
_full
_args
= None
120 self
._test
_dir
= test_dir
121 self
._temp
_dir
= None
122 self
._launched
= False
124 self
._console
_device
_type
= None
125 self
._console
_address
= None
126 self
._console
_socket
= None
128 # just in case logging wasn't configured by the main script:
129 logging
.basicConfig()
134 def __exit__(self
, exc_type
, exc_val
, exc_tb
):
138 # This can be used to add an unused monitor instance.
139 def add_monitor_telnet(self
, ip
, port
):
140 args
= 'tcp:%s:%d,server,nowait,telnet' % (ip
, port
)
141 self
._args
.append('-monitor')
142 self
._args
.append(args
)
144 def add_fd(self
, fd
, fdset
, opaque
, opts
=''):
146 Pass a file descriptor to the VM
148 options
= ['fd=%d' % fd
,
150 'opaque=%s' % opaque
]
154 # This did not exist before 3.4, but since then it is
155 # mandatory for our purpose
156 if hasattr(os
, 'set_inheritable'):
157 os
.set_inheritable(fd
, True)
159 self
._args
.append('-add-fd')
160 self
._args
.append(','.join(options
))
163 # Exactly one of fd and file_path must be given.
164 # (If it is file_path, the helper will open that file and pass its
166 def send_fd_scm(self
, fd
=None, file_path
=None):
167 # In iotest.py, the qmp should always use unix socket.
168 assert self
._qmp
.is_scm_available()
169 if self
._socket
_scm
_helper
is None:
170 raise QEMUMachineError("No path to socket_scm_helper set")
171 if not os
.path
.exists(self
._socket
_scm
_helper
):
172 raise QEMUMachineError("%s does not exist" %
173 self
._socket
_scm
_helper
)
175 # This did not exist before 3.4, but since then it is
176 # mandatory for our purpose
177 if hasattr(os
, 'set_inheritable'):
178 os
.set_inheritable(self
._qmp
.get_sock_fd(), True)
180 os
.set_inheritable(fd
, True)
182 fd_param
= ["%s" % self
._socket
_scm
_helper
,
183 "%d" % self
._qmp
.get_sock_fd()]
185 if file_path
is not None:
187 fd_param
.append(file_path
)
189 assert fd
is not None
190 fd_param
.append(str(fd
))
192 devnull
= open(os
.path
.devnull
, 'rb')
193 proc
= subprocess
.Popen(fd_param
, stdin
=devnull
, stdout
=subprocess
.PIPE
,
194 stderr
=subprocess
.STDOUT
, close_fds
=False)
195 output
= proc
.communicate()[0]
199 return proc
.returncode
202 def _remove_if_exists(path
):
204 Remove file object at path if it exists
208 except OSError as exception
:
209 if exception
.errno
== errno
.ENOENT
:
213 def is_running(self
):
214 return self
._popen
is not None and self
._popen
.poll() is None
217 if self
._popen
is None:
219 return self
._popen
.poll()
222 if not self
.is_running():
224 return self
._popen
.pid
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()
231 def _base_args(self
):
232 if isinstance(self
._monitor
_address
, tuple):
233 moncdev
= "socket,id=mon,host=%s,port=%s" % (
234 self
._monitor
_address
[0],
235 self
._monitor
_address
[1])
237 moncdev
= 'socket,id=mon,path=%s' % self
._vm
_monitor
238 args
= ['-chardev', moncdev
,
239 '-mon', 'chardev=mon,mode=control',
240 '-display', 'none', '-vga', 'none']
241 if self
._machine
is not None:
242 args
.extend(['-machine', self
._machine
])
243 if self
._console
_device
_type
is not None:
244 self
._console
_address
= os
.path
.join(self
._temp
_dir
,
245 self
._name
+ "-console.sock")
246 chardev
= ('socket,id=console,path=%s,server,nowait' %
247 self
._console
_address
)
248 device
= '%s,chardev=console' % self
._console
_device
_type
249 args
.extend(['-chardev', chardev
, '-device', device
])
252 def _pre_launch(self
):
253 self
._temp
_dir
= tempfile
.mkdtemp(dir=self
._test
_dir
)
254 if self
._monitor
_address
is not None:
255 self
._vm
_monitor
= self
._monitor
_address
257 self
._vm
_monitor
= os
.path
.join(self
._temp
_dir
,
258 self
._name
+ "-monitor.sock")
259 self
._qemu
_log
_path
= os
.path
.join(self
._temp
_dir
, self
._name
+ ".log")
260 self
._qemu
_log
_file
= open(self
._qemu
_log
_path
, 'wb')
262 self
._qmp
= qmp
.qmp
.QEMUMonitorProtocol(self
._vm
_monitor
,
265 def _post_launch(self
):
268 def _post_shutdown(self
):
269 if self
._qemu
_log
_file
is not None:
270 self
._qemu
_log
_file
.close()
271 self
._qemu
_log
_file
= None
273 self
._qemu
_log
_path
= None
275 if self
._console
_socket
is not None:
276 self
._console
_socket
.close()
277 self
._console
_socket
= None
279 if self
._temp
_dir
is not None:
280 shutil
.rmtree(self
._temp
_dir
)
281 self
._temp
_dir
= None
285 Launch the VM and make sure we cleanup and expose the
286 command line/output in case of exception
290 raise QEMUMachineError('VM already launched')
293 self
._qemu
_full
_args
= None
296 self
._launched
= True
300 LOG
.debug('Error launching VM')
301 if self
._qemu
_full
_args
:
302 LOG
.debug('Command: %r', ' '.join(self
._qemu
_full
_args
))
304 LOG
.debug('Output: %r', self
._iolog
)
309 Launch the VM and establish a QMP connection
311 devnull
= open(os
.path
.devnull
, 'rb')
313 self
._qemu
_full
_args
= (self
._wrapper
+ [self
._binary
] +
314 self
._base
_args
() + self
._args
)
315 self
._popen
= subprocess
.Popen(self
._qemu
_full
_args
,
317 stdout
=self
._qemu
_log
_file
,
318 stderr
=subprocess
.STDOUT
,
325 Wait for the VM to power off
330 self
._post
_shutdown
()
334 Terminate the VM and clean up
336 if self
.is_running():
338 self
._qmp
.cmd('quit')
345 self
._post
_shutdown
()
347 exitcode
= self
.exitcode()
348 if exitcode
is not None and exitcode
< 0:
349 msg
= 'qemu received signal %i: %s'
350 if self
._qemu
_full
_args
:
351 command
= ' '.join(self
._qemu
_full
_args
)
354 LOG
.warn(msg
, exitcode
, command
)
356 self
._launched
= False
358 def qmp(self
, cmd
, conv_keys
=True, **args
):
360 Invoke a QMP command and return the response dict
363 for key
, value
in args
.items():
365 qmp_args
[key
.replace('_', '-')] = value
367 qmp_args
[key
] = value
369 return self
._qmp
.cmd(cmd
, args
=qmp_args
)
371 def command(self
, cmd
, conv_keys
=True, **args
):
373 Invoke a QMP command.
374 On success return the response dict.
375 On failure raise an exception.
377 reply
= self
.qmp(cmd
, conv_keys
, **args
)
379 raise qmp
.qmp
.QMPError("Monitor is closed")
381 raise MonitorResponseError(reply
)
382 return reply
["return"]
384 def get_qmp_event(self
, wait
=False):
386 Poll for one queued QMP events and return it
388 if len(self
._events
) > 0:
389 return self
._events
.pop(0)
390 return self
._qmp
.pull_event(wait
=wait
)
392 def get_qmp_events(self
, wait
=False):
394 Poll for queued QMP events and return a list of dicts
396 events
= self
._qmp
.get_events(wait
=wait
)
397 events
.extend(self
._events
)
399 self
._qmp
.clear_events()
402 def event_wait(self
, name
, timeout
=60.0, match
=None):
404 Wait for specified timeout on named event in QMP; optionally filter
407 The 'match' is checked to be a recursive subset of the 'event'; skips
408 branch processing on match's value None
409 {"foo": {"bar": 1}} matches {"foo": None}
410 {"foo": {"bar": 1}} does not matches {"foo": {"baz": None}}
412 def event_match(event
, match
=None):
418 if isinstance(event
[key
], dict):
419 if not event_match(event
[key
], match
[key
]):
421 elif event
[key
] != match
[key
]:
428 # Search cached events
429 for event
in self
._events
:
430 if (event
['event'] == name
) and event_match(event
, match
):
431 self
._events
.remove(event
)
434 # Poll for new events
436 event
= self
._qmp
.pull_event(wait
=timeout
)
437 if (event
['event'] == name
) and event_match(event
, match
):
439 self
._events
.append(event
)
445 After self.shutdown or failed qemu execution, this returns the output
450 def add_args(self
, *args
):
452 Adds to the list of extra arguments to be given to the QEMU binary
454 self
._args
.extend(args
)
456 def set_machine(self
, machine_type
):
458 Sets the machine type
460 If set, the machine type will be added to the base arguments
461 of the resulting QEMU command line.
463 self
._machine
= machine_type
465 def set_console(self
, device_type
=None):
467 Sets the device type for a console device
469 If set, the console device and a backing character device will
470 be added to the base arguments of the resulting QEMU command
473 This is a convenience method that will either use the provided
474 device type, of if not given, it will used the device type set
475 on CONSOLE_DEV_TYPES.
477 The actual setting of command line arguments will be be done at
478 machine launch time, as it depends on the temporary directory
481 @param device_type: the device type, such as "isa-serial"
482 @raises: QEMUMachineAddDeviceError if the device type is not given
483 and can not be determined.
485 if device_type
is None:
486 if self
._machine
is None:
487 raise QEMUMachineAddDeviceError("Can not add a console device:"
488 " QEMU instance without a "
489 "defined machine type")
490 for regex
, device
in CONSOLE_DEV_TYPES
.items():
491 if re
.match(regex
, self
._machine
):
494 if device_type
is None:
495 raise QEMUMachineAddDeviceError("Can not add a console device:"
496 " no matching console device "
498 self
._console
_device
_type
= device_type
501 def console_socket(self
):
503 Returns a socket connected to the console
505 if self
._console
_socket
is None:
506 self
._console
_socket
= socket
.socket(socket
.AF_UNIX
,
508 self
._console
_socket
.connect(self
._console
_address
)
509 return self
._console
_socket