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):
77 Use this object as a context manager to ensure the QEMU process terminates::
79 with VM(binary) as vm:
81 # vm is guaranteed to be shut down here
84 def __init__(self
, binary
, args
=None, wrapper
=None, name
=None,
85 test_dir
="/var/tmp", monitor_address
=None,
86 socket_scm_helper
=None):
88 Initialize a QEMUMachine
90 @param binary: path to the qemu binary
91 @param args: list of extra arguments
92 @param wrapper: list of arguments used as prefix to qemu binary
93 @param name: prefix for socket and log file names (default: qemu-PID)
94 @param test_dir: where to create socket and log file
95 @param monitor_address: address for QMP monitor
96 @param socket_scm_helper: helper program, required for send_fd_scm()"
97 @note: Qemu process is not started until launch() is used.
104 name
= "qemu-%d" % os
.getpid()
106 self
._monitor
_address
= monitor_address
107 self
._vm
_monitor
= None
108 self
._qemu
_log
_path
= None
109 self
._qemu
_log
_file
= None
111 self
._binary
= binary
112 self
._args
= list(args
) # Force copy args in case we modify them
113 self
._wrapper
= wrapper
116 self
._socket
_scm
_helper
= socket_scm_helper
118 self
._qemu
_full
_args
= None
119 self
._test
_dir
= test_dir
120 self
._temp
_dir
= None
121 self
._launched
= False
123 self
._console
_device
_type
= None
124 self
._console
_address
= None
125 self
._console
_socket
= None
127 # just in case logging wasn't configured by the main script:
128 logging
.basicConfig()
133 def __exit__(self
, exc_type
, exc_val
, exc_tb
):
137 # This can be used to add an unused monitor instance.
138 def add_monitor_telnet(self
, ip
, port
):
139 args
= 'tcp:%s:%d,server,nowait,telnet' % (ip
, port
)
140 self
._args
.append('-monitor')
141 self
._args
.append(args
)
143 def add_fd(self
, fd
, fdset
, opaque
, opts
=''):
144 '''Pass a file descriptor to the VM'''
145 options
= ['fd=%d' % fd
,
147 'opaque=%s' % opaque
]
151 self
._args
.append('-add-fd')
152 self
._args
.append(','.join(options
))
155 def send_fd_scm(self
, fd_file_path
):
156 # In iotest.py, the qmp should always use unix socket.
157 assert self
._qmp
.is_scm_available()
158 if self
._socket
_scm
_helper
is None:
159 raise QEMUMachineError("No path to socket_scm_helper set")
160 if not os
.path
.exists(self
._socket
_scm
_helper
):
161 raise QEMUMachineError("%s does not exist" %
162 self
._socket
_scm
_helper
)
163 fd_param
= ["%s" % self
._socket
_scm
_helper
,
164 "%d" % self
._qmp
.get_sock_fd(),
166 devnull
= open(os
.path
.devnull
, 'rb')
167 proc
= subprocess
.Popen(fd_param
, stdin
=devnull
, stdout
=subprocess
.PIPE
,
168 stderr
=subprocess
.STDOUT
)
169 output
= proc
.communicate()[0]
173 return proc
.returncode
176 def _remove_if_exists(path
):
177 '''Remove file object at path if it exists'''
180 except OSError as exception
:
181 if exception
.errno
== errno
.ENOENT
:
185 def is_running(self
):
186 return self
._popen
is not None and self
._popen
.poll() is None
189 if self
._popen
is None:
191 return self
._popen
.poll()
194 if not self
.is_running():
196 return self
._popen
.pid
198 def _load_io_log(self
):
199 if self
._qemu
_log
_path
is not None:
200 with
open(self
._qemu
_log
_path
, "r") as iolog
:
201 self
._iolog
= iolog
.read()
203 def _base_args(self
):
204 if isinstance(self
._monitor
_address
, tuple):
205 moncdev
= "socket,id=mon,host=%s,port=%s" % (
206 self
._monitor
_address
[0],
207 self
._monitor
_address
[1])
209 moncdev
= 'socket,id=mon,path=%s' % self
._vm
_monitor
210 args
= ['-chardev', moncdev
,
211 '-mon', 'chardev=mon,mode=control',
212 '-display', 'none', '-vga', 'none']
213 if self
._machine
is not None:
214 args
.extend(['-machine', self
._machine
])
215 if self
._console
_device
_type
is not None:
216 self
._console
_address
= os
.path
.join(self
._temp
_dir
,
217 self
._name
+ "-console.sock")
218 chardev
= ('socket,id=console,path=%s,server,nowait' %
219 self
._console
_address
)
220 device
= '%s,chardev=console' % self
._console
_device
_type
221 args
.extend(['-chardev', chardev
, '-device', device
])
224 def _pre_launch(self
):
225 self
._temp
_dir
= tempfile
.mkdtemp(dir=self
._test
_dir
)
226 if self
._monitor
_address
is not None:
227 self
._vm
_monitor
= self
._monitor
_address
229 self
._vm
_monitor
= os
.path
.join(self
._temp
_dir
,
230 self
._name
+ "-monitor.sock")
231 self
._qemu
_log
_path
= os
.path
.join(self
._temp
_dir
, self
._name
+ ".log")
232 self
._qemu
_log
_file
= open(self
._qemu
_log
_path
, 'wb')
234 self
._qmp
= qmp
.qmp
.QEMUMonitorProtocol(self
._vm
_monitor
,
237 def _post_launch(self
):
240 def _post_shutdown(self
):
241 if self
._qemu
_log
_file
is not None:
242 self
._qemu
_log
_file
.close()
243 self
._qemu
_log
_file
= None
245 self
._qemu
_log
_path
= None
247 if self
._console
_socket
is not None:
248 self
._console
_socket
.close()
249 self
._console
_socket
= None
251 if self
._temp
_dir
is not None:
252 shutil
.rmtree(self
._temp
_dir
)
253 self
._temp
_dir
= None
257 Launch the VM and make sure we cleanup and expose the
258 command line/output in case of exception
262 raise QEMUMachineError('VM already launched')
265 self
._qemu
_full
_args
= None
268 self
._launched
= True
272 LOG
.debug('Error launching VM')
273 if self
._qemu
_full
_args
:
274 LOG
.debug('Command: %r', ' '.join(self
._qemu
_full
_args
))
276 LOG
.debug('Output: %r', self
._iolog
)
280 '''Launch the VM and establish a QMP connection'''
281 devnull
= open(os
.path
.devnull
, 'rb')
283 self
._qemu
_full
_args
= (self
._wrapper
+ [self
._binary
] +
284 self
._base
_args
() + self
._args
)
285 self
._popen
= subprocess
.Popen(self
._qemu
_full
_args
,
287 stdout
=self
._qemu
_log
_file
,
288 stderr
=subprocess
.STDOUT
,
293 '''Wait for the VM to power off'''
297 self
._post
_shutdown
()
300 '''Terminate the VM and clean up'''
301 if self
.is_running():
303 self
._qmp
.cmd('quit')
310 self
._post
_shutdown
()
312 exitcode
= self
.exitcode()
313 if exitcode
is not None and exitcode
< 0:
314 msg
= 'qemu received signal %i: %s'
315 if self
._qemu
_full
_args
:
316 command
= ' '.join(self
._qemu
_full
_args
)
319 LOG
.warn(msg
, exitcode
, command
)
321 self
._launched
= False
323 def qmp(self
, cmd
, conv_keys
=True, **args
):
324 '''Invoke a QMP command and return the response dict'''
326 for key
, value
in args
.items():
328 qmp_args
[key
.replace('_', '-')] = value
330 qmp_args
[key
] = value
332 return self
._qmp
.cmd(cmd
, args
=qmp_args
)
334 def command(self
, cmd
, conv_keys
=True, **args
):
336 Invoke a QMP command.
337 On success return the response dict.
338 On failure raise an exception.
340 reply
= self
.qmp(cmd
, conv_keys
, **args
)
342 raise qmp
.qmp
.QMPError("Monitor is closed")
344 raise MonitorResponseError(reply
)
345 return reply
["return"]
347 def get_qmp_event(self
, wait
=False):
348 '''Poll for one queued QMP events and return it'''
349 if len(self
._events
) > 0:
350 return self
._events
.pop(0)
351 return self
._qmp
.pull_event(wait
=wait
)
353 def get_qmp_events(self
, wait
=False):
354 '''Poll for queued QMP events and return a list of dicts'''
355 events
= self
._qmp
.get_events(wait
=wait
)
356 events
.extend(self
._events
)
358 self
._qmp
.clear_events()
361 def event_wait(self
, name
, timeout
=60.0, match
=None):
363 Wait for specified timeout on named event in QMP; optionally filter
366 The 'match' is checked to be a recursive subset of the 'event'; skips
367 branch processing on match's value None
368 {"foo": {"bar": 1}} matches {"foo": None}
369 {"foo": {"bar": 1}} does not matches {"foo": {"baz": None}}
371 def event_match(event
, match
=None):
377 if isinstance(event
[key
], dict):
378 if not event_match(event
[key
], match
[key
]):
380 elif event
[key
] != match
[key
]:
387 # Search cached events
388 for event
in self
._events
:
389 if (event
['event'] == name
) and event_match(event
, match
):
390 self
._events
.remove(event
)
393 # Poll for new events
395 event
= self
._qmp
.pull_event(wait
=timeout
)
396 if (event
['event'] == name
) and event_match(event
, match
):
398 self
._events
.append(event
)
404 After self.shutdown or failed qemu execution, this returns the output
409 def add_args(self
, *args
):
411 Adds to the list of extra arguments to be given to the QEMU binary
413 self
._args
.extend(args
)
415 def set_machine(self
, machine_type
):
417 Sets the machine type
419 If set, the machine type will be added to the base arguments
420 of the resulting QEMU command line.
422 self
._machine
= machine_type
424 def set_console(self
, device_type
=None):
426 Sets the device type for a console device
428 If set, the console device and a backing character device will
429 be added to the base arguments of the resulting QEMU command
432 This is a convenience method that will either use the provided
433 device type, of if not given, it will used the device type set
434 on CONSOLE_DEV_TYPES.
436 The actual setting of command line arguments will be be done at
437 machine launch time, as it depends on the temporary directory
440 @param device_type: the device type, such as "isa-serial"
441 @raises: QEMUMachineAddDeviceError if the device type is not given
442 and can not be determined.
444 if device_type
is None:
445 if self
._machine
is None:
446 raise QEMUMachineAddDeviceError("Can not add a console device:"
447 " QEMU instance without a "
448 "defined machine type")
449 for regex
, device
in CONSOLE_DEV_TYPES
.items():
450 if re
.match(regex
, self
._machine
):
453 if device_type
is None:
454 raise QEMUMachineAddDeviceError("Can not add a console device:"
455 " no matching console device "
457 self
._console
_device
_type
= device_type
460 def console_socket(self
):
462 Returns a socket connected to the console
464 if self
._console
_socket
is None:
465 self
._console
_socket
= socket
.socket(socket
.AF_UNIX
,
467 self
._console
_socket
.connect(self
._console
_address
)
468 return self
._console
_socket