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 #: Maps machine types to the preferred console device types
31 r
'^clipper$': 'isa-serial',
32 r
'^malta': 'isa-serial',
33 r
'^(pc.*|q35.*|isapc)$': 'isa-serial',
34 r
'^(40p|powernv|prep)$': 'isa-serial',
35 r
'^pseries.*': 'spapr-vty',
36 r
'^s390-ccw-virtio.*': 'sclpconsole',
40 class QEMUMachineError(Exception):
42 Exception called when an error in QEMUMachine happens.
46 class QEMUMachineAddDeviceError(QEMUMachineError
):
48 Exception raised when a request to add a device can not be fulfilled
50 The failures are caused by limitations, lack of information or conflicting
51 requests on the QEMUMachine methods. This exception does not represent
52 failures reported by the QEMU binary itself.
55 class MonitorResponseError(qmp
.qmp
.QMPError
):
57 Represents erroneous QMP monitor reply
59 def __init__(self
, reply
):
61 desc
= reply
["error"]["desc"]
64 super(MonitorResponseError
, self
).__init
__(desc
)
68 class QEMUMachine(object):
71 Use this object as a context manager to ensure the QEMU process terminates::
73 with VM(binary) as vm:
75 # vm is guaranteed to be shut down here
78 def __init__(self
, binary
, args
=None, wrapper
=None, name
=None,
79 test_dir
="/var/tmp", monitor_address
=None,
80 socket_scm_helper
=None):
82 Initialize a QEMUMachine
84 @param binary: path to the qemu binary
85 @param args: list of extra arguments
86 @param wrapper: list of arguments used as prefix to qemu binary
87 @param name: prefix for socket and log file names (default: qemu-PID)
88 @param test_dir: where to create socket and log file
89 @param monitor_address: address for QMP monitor
90 @param socket_scm_helper: helper program, required for send_fd_scm()"
91 @note: Qemu process is not started until launch() is used.
98 name
= "qemu-%d" % os
.getpid()
100 self
._monitor
_address
= monitor_address
101 self
._vm
_monitor
= None
102 self
._qemu
_log
_path
= None
103 self
._qemu
_log
_file
= None
105 self
._binary
= binary
106 self
._args
= list(args
) # Force copy args in case we modify them
107 self
._wrapper
= wrapper
110 self
._socket
_scm
_helper
= socket_scm_helper
112 self
._qemu
_full
_args
= None
113 self
._test
_dir
= test_dir
114 self
._temp
_dir
= None
115 self
._launched
= False
117 self
._console
_device
_type
= None
118 self
._console
_address
= None
119 self
._console
_socket
= None
121 # just in case logging wasn't configured by the main script:
122 logging
.basicConfig()
127 def __exit__(self
, exc_type
, exc_val
, exc_tb
):
131 # This can be used to add an unused monitor instance.
132 def add_monitor_telnet(self
, ip
, port
):
133 args
= 'tcp:%s:%d,server,nowait,telnet' % (ip
, port
)
134 self
._args
.append('-monitor')
135 self
._args
.append(args
)
137 def add_fd(self
, fd
, fdset
, opaque
, opts
=''):
138 '''Pass a file descriptor to the VM'''
139 options
= ['fd=%d' % fd
,
141 'opaque=%s' % opaque
]
145 self
._args
.append('-add-fd')
146 self
._args
.append(','.join(options
))
149 def send_fd_scm(self
, fd_file_path
):
150 # In iotest.py, the qmp should always use unix socket.
151 assert self
._qmp
.is_scm_available()
152 if self
._socket
_scm
_helper
is None:
153 raise QEMUMachineError("No path to socket_scm_helper set")
154 if not os
.path
.exists(self
._socket
_scm
_helper
):
155 raise QEMUMachineError("%s does not exist" %
156 self
._socket
_scm
_helper
)
157 fd_param
= ["%s" % self
._socket
_scm
_helper
,
158 "%d" % self
._qmp
.get_sock_fd(),
160 devnull
= open(os
.path
.devnull
, 'rb')
161 proc
= subprocess
.Popen(fd_param
, stdin
=devnull
, stdout
=subprocess
.PIPE
,
162 stderr
=subprocess
.STDOUT
)
163 output
= proc
.communicate()[0]
167 return proc
.returncode
170 def _remove_if_exists(path
):
171 '''Remove file object at path if it exists'''
174 except OSError as exception
:
175 if exception
.errno
== errno
.ENOENT
:
179 def is_running(self
):
180 return self
._popen
is not None and self
._popen
.poll() is None
183 if self
._popen
is None:
185 return self
._popen
.poll()
188 if not self
.is_running():
190 return self
._popen
.pid
192 def _load_io_log(self
):
193 if self
._qemu
_log
_path
is not None:
194 with
open(self
._qemu
_log
_path
, "r") as iolog
:
195 self
._iolog
= iolog
.read()
197 def _base_args(self
):
198 if isinstance(self
._monitor
_address
, tuple):
199 moncdev
= "socket,id=mon,host=%s,port=%s" % (
200 self
._monitor
_address
[0],
201 self
._monitor
_address
[1])
203 moncdev
= 'socket,id=mon,path=%s' % self
._vm
_monitor
204 args
= ['-chardev', moncdev
,
205 '-mon', 'chardev=mon,mode=control',
206 '-display', 'none', '-vga', 'none']
207 if self
._machine
is not None:
208 args
.extend(['-machine', self
._machine
])
209 if self
._console
_device
_type
is not None:
210 self
._console
_address
= os
.path
.join(self
._temp
_dir
,
211 self
._name
+ "-console.sock")
212 chardev
= ('socket,id=console,path=%s,server,nowait' %
213 self
._console
_address
)
214 device
= '%s,chardev=console' % self
._console
_device
_type
215 args
.extend(['-chardev', chardev
, '-device', device
])
218 def _pre_launch(self
):
219 self
._temp
_dir
= tempfile
.mkdtemp(dir=self
._test
_dir
)
220 if self
._monitor
_address
is not None:
221 self
._vm
_monitor
= self
._monitor
_address
223 self
._vm
_monitor
= os
.path
.join(self
._temp
_dir
,
224 self
._name
+ "-monitor.sock")
225 self
._qemu
_log
_path
= os
.path
.join(self
._temp
_dir
, self
._name
+ ".log")
226 self
._qemu
_log
_file
= open(self
._qemu
_log
_path
, 'wb')
228 self
._qmp
= qmp
.qmp
.QEMUMonitorProtocol(self
._vm
_monitor
,
231 def _post_launch(self
):
234 def _post_shutdown(self
):
235 if self
._qemu
_log
_file
is not None:
236 self
._qemu
_log
_file
.close()
237 self
._qemu
_log
_file
= None
239 self
._qemu
_log
_path
= None
241 if self
._console
_socket
is not None:
242 self
._console
_socket
.close()
243 self
._console
_socket
= None
245 if self
._temp
_dir
is not None:
246 shutil
.rmtree(self
._temp
_dir
)
247 self
._temp
_dir
= None
251 Launch the VM and make sure we cleanup and expose the
252 command line/output in case of exception
256 raise QEMUMachineError('VM already launched')
259 self
._qemu
_full
_args
= None
262 self
._launched
= True
266 LOG
.debug('Error launching VM')
267 if self
._qemu
_full
_args
:
268 LOG
.debug('Command: %r', ' '.join(self
._qemu
_full
_args
))
270 LOG
.debug('Output: %r', self
._iolog
)
274 '''Launch the VM and establish a QMP connection'''
275 devnull
= open(os
.path
.devnull
, 'rb')
277 self
._qemu
_full
_args
= (self
._wrapper
+ [self
._binary
] +
278 self
._base
_args
() + self
._args
)
279 self
._popen
= subprocess
.Popen(self
._qemu
_full
_args
,
281 stdout
=self
._qemu
_log
_file
,
282 stderr
=subprocess
.STDOUT
,
287 '''Wait for the VM to power off'''
291 self
._post
_shutdown
()
294 '''Terminate the VM and clean up'''
295 if self
.is_running():
297 self
._qmp
.cmd('quit')
304 self
._post
_shutdown
()
306 exitcode
= self
.exitcode()
307 if exitcode
is not None and exitcode
< 0:
308 msg
= 'qemu received signal %i: %s'
309 if self
._qemu
_full
_args
:
310 command
= ' '.join(self
._qemu
_full
_args
)
313 LOG
.warn(msg
, exitcode
, command
)
315 self
._launched
= False
317 def qmp(self
, cmd
, conv_keys
=True, **args
):
318 '''Invoke a QMP command and return the response dict'''
320 for key
, value
in args
.items():
322 qmp_args
[key
.replace('_', '-')] = value
324 qmp_args
[key
] = value
326 return self
._qmp
.cmd(cmd
, args
=qmp_args
)
328 def command(self
, cmd
, conv_keys
=True, **args
):
330 Invoke a QMP command.
331 On success return the response dict.
332 On failure raise an exception.
334 reply
= self
.qmp(cmd
, conv_keys
, **args
)
336 raise qmp
.qmp
.QMPError("Monitor is closed")
338 raise MonitorResponseError(reply
)
339 return reply
["return"]
341 def get_qmp_event(self
, wait
=False):
342 '''Poll for one queued QMP events and return it'''
343 if len(self
._events
) > 0:
344 return self
._events
.pop(0)
345 return self
._qmp
.pull_event(wait
=wait
)
347 def get_qmp_events(self
, wait
=False):
348 '''Poll for queued QMP events and return a list of dicts'''
349 events
= self
._qmp
.get_events(wait
=wait
)
350 events
.extend(self
._events
)
352 self
._qmp
.clear_events()
355 def event_wait(self
, name
, timeout
=60.0, match
=None):
357 Wait for specified timeout on named event in QMP; optionally filter
360 The 'match' is checked to be a recursive subset of the 'event'; skips
361 branch processing on match's value None
362 {"foo": {"bar": 1}} matches {"foo": None}
363 {"foo": {"bar": 1}} does not matches {"foo": {"baz": None}}
365 def event_match(event
, match
=None):
371 if isinstance(event
[key
], dict):
372 if not event_match(event
[key
], match
[key
]):
374 elif event
[key
] != match
[key
]:
381 # Search cached events
382 for event
in self
._events
:
383 if (event
['event'] == name
) and event_match(event
, match
):
384 self
._events
.remove(event
)
387 # Poll for new events
389 event
= self
._qmp
.pull_event(wait
=timeout
)
390 if (event
['event'] == name
) and event_match(event
, match
):
392 self
._events
.append(event
)
398 After self.shutdown or failed qemu execution, this returns the output
403 def add_args(self
, *args
):
405 Adds to the list of extra arguments to be given to the QEMU binary
407 self
._args
.extend(args
)
409 def set_machine(self
, machine_type
):
411 Sets the machine type
413 If set, the machine type will be added to the base arguments
414 of the resulting QEMU command line.
416 self
._machine
= machine_type
418 def set_console(self
, device_type
=None):
420 Sets the device type for a console device
422 If set, the console device and a backing character device will
423 be added to the base arguments of the resulting QEMU command
426 This is a convenience method that will either use the provided
427 device type, of if not given, it will used the device type set
428 on CONSOLE_DEV_TYPES.
430 The actual setting of command line arguments will be be done at
431 machine launch time, as it depends on the temporary directory
434 @param device_type: the device type, such as "isa-serial"
435 @raises: QEMUMachineAddDeviceError if the device type is not given
436 and can not be determined.
438 if device_type
is None:
439 if self
._machine
is None:
440 raise QEMUMachineAddDeviceError("Can not add a console device:"
441 " QEMU instance without a "
442 "defined machine type")
443 for regex
, device
in CONSOLE_DEV_TYPES
.items():
444 if re
.match(regex
, self
._machine
):
447 if device_type
is None:
448 raise QEMUMachineAddDeviceError("Can not add a console device:"
449 " no matching console device "
451 self
._console
_device
_type
= device_type
454 def console_socket(self
):
456 Returns a socket connected to the console
458 if self
._console
_socket
is None:
459 self
._console
_socket
= socket
.socket(socket
.AF_UNIX
,
461 self
._console
_socket
.connect(self
._console
_address
)
462 return self
._console
_socket