qemu.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509
  1. # QEMU library
  2. #
  3. # Copyright (C) 2015-2016 Red Hat Inc.
  4. # Copyright (C) 2012 IBM Corp.
  5. #
  6. # Authors:
  7. # Fam Zheng <famz@redhat.com>
  8. #
  9. # This work is licensed under the terms of the GNU GPL, version 2. See
  10. # the COPYING file in the top-level directory.
  11. #
  12. # Based on qmp.py.
  13. #
  14. import errno
  15. import logging
  16. import os
  17. import subprocess
  18. import qmp.qmp
  19. import re
  20. import shutil
  21. import socket
  22. import tempfile
  23. LOG = logging.getLogger(__name__)
  24. def kvm_available(target_arch=None):
  25. if target_arch and target_arch != os.uname()[4]:
  26. return False
  27. return os.access("/dev/kvm", os.R_OK | os.W_OK)
  28. #: Maps machine types to the preferred console device types
  29. CONSOLE_DEV_TYPES = {
  30. r'^clipper$': 'isa-serial',
  31. r'^malta': 'isa-serial',
  32. r'^(pc.*|q35.*|isapc)$': 'isa-serial',
  33. r'^(40p|powernv|prep)$': 'isa-serial',
  34. r'^pseries.*': 'spapr-vty',
  35. r'^s390-ccw-virtio.*': 'sclpconsole',
  36. }
  37. class QEMUMachineError(Exception):
  38. """
  39. Exception called when an error in QEMUMachine happens.
  40. """
  41. class QEMUMachineAddDeviceError(QEMUMachineError):
  42. """
  43. Exception raised when a request to add a device can not be fulfilled
  44. The failures are caused by limitations, lack of information or conflicting
  45. requests on the QEMUMachine methods. This exception does not represent
  46. failures reported by the QEMU binary itself.
  47. """
  48. class MonitorResponseError(qmp.qmp.QMPError):
  49. """
  50. Represents erroneous QMP monitor reply
  51. """
  52. def __init__(self, reply):
  53. try:
  54. desc = reply["error"]["desc"]
  55. except KeyError:
  56. desc = reply
  57. super(MonitorResponseError, self).__init__(desc)
  58. self.reply = reply
  59. class QEMUMachine(object):
  60. """
  61. A QEMU VM
  62. Use this object as a context manager to ensure the QEMU process terminates::
  63. with VM(binary) as vm:
  64. ...
  65. # vm is guaranteed to be shut down here
  66. """
  67. def __init__(self, binary, args=None, wrapper=None, name=None,
  68. test_dir="/var/tmp", monitor_address=None,
  69. socket_scm_helper=None):
  70. '''
  71. Initialize a QEMUMachine
  72. @param binary: path to the qemu binary
  73. @param args: list of extra arguments
  74. @param wrapper: list of arguments used as prefix to qemu binary
  75. @param name: prefix for socket and log file names (default: qemu-PID)
  76. @param test_dir: where to create socket and log file
  77. @param monitor_address: address for QMP monitor
  78. @param socket_scm_helper: helper program, required for send_fd_scm()
  79. @note: Qemu process is not started until launch() is used.
  80. '''
  81. if args is None:
  82. args = []
  83. if wrapper is None:
  84. wrapper = []
  85. if name is None:
  86. name = "qemu-%d" % os.getpid()
  87. self._name = name
  88. self._monitor_address = monitor_address
  89. self._vm_monitor = None
  90. self._qemu_log_path = None
  91. self._qemu_log_file = None
  92. self._popen = None
  93. self._binary = binary
  94. self._args = list(args) # Force copy args in case we modify them
  95. self._wrapper = wrapper
  96. self._events = []
  97. self._iolog = None
  98. self._socket_scm_helper = socket_scm_helper
  99. self._qmp = None
  100. self._qemu_full_args = None
  101. self._test_dir = test_dir
  102. self._temp_dir = None
  103. self._launched = False
  104. self._machine = None
  105. self._console_device_type = None
  106. self._console_address = None
  107. self._console_socket = None
  108. # just in case logging wasn't configured by the main script:
  109. logging.basicConfig()
  110. def __enter__(self):
  111. return self
  112. def __exit__(self, exc_type, exc_val, exc_tb):
  113. self.shutdown()
  114. return False
  115. # This can be used to add an unused monitor instance.
  116. def add_monitor_telnet(self, ip, port):
  117. args = 'tcp:%s:%d,server,nowait,telnet' % (ip, port)
  118. self._args.append('-monitor')
  119. self._args.append(args)
  120. def add_fd(self, fd, fdset, opaque, opts=''):
  121. """
  122. Pass a file descriptor to the VM
  123. """
  124. options = ['fd=%d' % fd,
  125. 'set=%d' % fdset,
  126. 'opaque=%s' % opaque]
  127. if opts:
  128. options.append(opts)
  129. # This did not exist before 3.4, but since then it is
  130. # mandatory for our purpose
  131. if hasattr(os, 'set_inheritable'):
  132. os.set_inheritable(fd, True)
  133. self._args.append('-add-fd')
  134. self._args.append(','.join(options))
  135. return self
  136. # Exactly one of fd and file_path must be given.
  137. # (If it is file_path, the helper will open that file and pass its
  138. # own fd)
  139. def send_fd_scm(self, fd=None, file_path=None):
  140. # In iotest.py, the qmp should always use unix socket.
  141. assert self._qmp.is_scm_available()
  142. if self._socket_scm_helper is None:
  143. raise QEMUMachineError("No path to socket_scm_helper set")
  144. if not os.path.exists(self._socket_scm_helper):
  145. raise QEMUMachineError("%s does not exist" %
  146. self._socket_scm_helper)
  147. # This did not exist before 3.4, but since then it is
  148. # mandatory for our purpose
  149. if hasattr(os, 'set_inheritable'):
  150. os.set_inheritable(self._qmp.get_sock_fd(), True)
  151. if fd is not None:
  152. os.set_inheritable(fd, True)
  153. fd_param = ["%s" % self._socket_scm_helper,
  154. "%d" % self._qmp.get_sock_fd()]
  155. if file_path is not None:
  156. assert fd is None
  157. fd_param.append(file_path)
  158. else:
  159. assert fd is not None
  160. fd_param.append(str(fd))
  161. devnull = open(os.path.devnull, 'rb')
  162. proc = subprocess.Popen(fd_param, stdin=devnull, stdout=subprocess.PIPE,
  163. stderr=subprocess.STDOUT, close_fds=False)
  164. output = proc.communicate()[0]
  165. if output:
  166. LOG.debug(output)
  167. return proc.returncode
  168. @staticmethod
  169. def _remove_if_exists(path):
  170. """
  171. Remove file object at path if it exists
  172. """
  173. try:
  174. os.remove(path)
  175. except OSError as exception:
  176. if exception.errno == errno.ENOENT:
  177. return
  178. raise
  179. def is_running(self):
  180. return self._popen is not None and self._popen.poll() is None
  181. def exitcode(self):
  182. if self._popen is None:
  183. return None
  184. return self._popen.poll()
  185. def get_pid(self):
  186. if not self.is_running():
  187. return None
  188. return self._popen.pid
  189. def _load_io_log(self):
  190. if self._qemu_log_path is not None:
  191. with open(self._qemu_log_path, "r") as iolog:
  192. self._iolog = iolog.read()
  193. def _base_args(self):
  194. if isinstance(self._monitor_address, tuple):
  195. moncdev = "socket,id=mon,host=%s,port=%s" % (
  196. self._monitor_address[0],
  197. self._monitor_address[1])
  198. else:
  199. moncdev = 'socket,id=mon,path=%s' % self._vm_monitor
  200. args = ['-chardev', moncdev,
  201. '-mon', 'chardev=mon,mode=control',
  202. '-display', 'none', '-vga', 'none']
  203. if self._machine is not None:
  204. args.extend(['-machine', self._machine])
  205. if self._console_device_type is not None:
  206. self._console_address = os.path.join(self._temp_dir,
  207. self._name + "-console.sock")
  208. chardev = ('socket,id=console,path=%s,server,nowait' %
  209. self._console_address)
  210. device = '%s,chardev=console' % self._console_device_type
  211. args.extend(['-chardev', chardev, '-device', device])
  212. return args
  213. def _pre_launch(self):
  214. self._temp_dir = tempfile.mkdtemp(dir=self._test_dir)
  215. if self._monitor_address is not None:
  216. self._vm_monitor = self._monitor_address
  217. else:
  218. self._vm_monitor = os.path.join(self._temp_dir,
  219. self._name + "-monitor.sock")
  220. self._qemu_log_path = os.path.join(self._temp_dir, self._name + ".log")
  221. self._qemu_log_file = open(self._qemu_log_path, 'wb')
  222. self._qmp = qmp.qmp.QEMUMonitorProtocol(self._vm_monitor,
  223. server=True)
  224. def _post_launch(self):
  225. self._qmp.accept()
  226. def _post_shutdown(self):
  227. if self._qemu_log_file is not None:
  228. self._qemu_log_file.close()
  229. self._qemu_log_file = None
  230. self._qemu_log_path = None
  231. if self._console_socket is not None:
  232. self._console_socket.close()
  233. self._console_socket = None
  234. if self._temp_dir is not None:
  235. shutil.rmtree(self._temp_dir)
  236. self._temp_dir = None
  237. def launch(self):
  238. """
  239. Launch the VM and make sure we cleanup and expose the
  240. command line/output in case of exception
  241. """
  242. if self._launched:
  243. raise QEMUMachineError('VM already launched')
  244. self._iolog = None
  245. self._qemu_full_args = None
  246. try:
  247. self._launch()
  248. self._launched = True
  249. except:
  250. self.shutdown()
  251. LOG.debug('Error launching VM')
  252. if self._qemu_full_args:
  253. LOG.debug('Command: %r', ' '.join(self._qemu_full_args))
  254. if self._iolog:
  255. LOG.debug('Output: %r', self._iolog)
  256. raise
  257. def _launch(self):
  258. """
  259. Launch the VM and establish a QMP connection
  260. """
  261. devnull = open(os.path.devnull, 'rb')
  262. self._pre_launch()
  263. self._qemu_full_args = (self._wrapper + [self._binary] +
  264. self._base_args() + self._args)
  265. self._popen = subprocess.Popen(self._qemu_full_args,
  266. stdin=devnull,
  267. stdout=self._qemu_log_file,
  268. stderr=subprocess.STDOUT,
  269. shell=False,
  270. close_fds=False)
  271. self._post_launch()
  272. def wait(self):
  273. """
  274. Wait for the VM to power off
  275. """
  276. self._popen.wait()
  277. self._qmp.close()
  278. self._load_io_log()
  279. self._post_shutdown()
  280. def shutdown(self):
  281. """
  282. Terminate the VM and clean up
  283. """
  284. if self.is_running():
  285. try:
  286. self._qmp.cmd('quit')
  287. self._qmp.close()
  288. except:
  289. self._popen.kill()
  290. self._popen.wait()
  291. self._load_io_log()
  292. self._post_shutdown()
  293. exitcode = self.exitcode()
  294. if exitcode is not None and exitcode < 0:
  295. msg = 'qemu received signal %i: %s'
  296. if self._qemu_full_args:
  297. command = ' '.join(self._qemu_full_args)
  298. else:
  299. command = ''
  300. LOG.warn(msg, -exitcode, command)
  301. self._launched = False
  302. def qmp(self, cmd, conv_keys=True, **args):
  303. """
  304. Invoke a QMP command and return the response dict
  305. """
  306. qmp_args = dict()
  307. for key, value in args.items():
  308. if conv_keys:
  309. qmp_args[key.replace('_', '-')] = value
  310. else:
  311. qmp_args[key] = value
  312. return self._qmp.cmd(cmd, args=qmp_args)
  313. def command(self, cmd, conv_keys=True, **args):
  314. """
  315. Invoke a QMP command.
  316. On success return the response dict.
  317. On failure raise an exception.
  318. """
  319. reply = self.qmp(cmd, conv_keys, **args)
  320. if reply is None:
  321. raise qmp.qmp.QMPError("Monitor is closed")
  322. if "error" in reply:
  323. raise MonitorResponseError(reply)
  324. return reply["return"]
  325. def get_qmp_event(self, wait=False):
  326. """
  327. Poll for one queued QMP events and return it
  328. """
  329. if len(self._events) > 0:
  330. return self._events.pop(0)
  331. return self._qmp.pull_event(wait=wait)
  332. def get_qmp_events(self, wait=False):
  333. """
  334. Poll for queued QMP events and return a list of dicts
  335. """
  336. events = self._qmp.get_events(wait=wait)
  337. events.extend(self._events)
  338. del self._events[:]
  339. self._qmp.clear_events()
  340. return events
  341. def event_wait(self, name, timeout=60.0, match=None):
  342. """
  343. Wait for specified timeout on named event in QMP; optionally filter
  344. results by match.
  345. The 'match' is checked to be a recursive subset of the 'event'; skips
  346. branch processing on match's value None
  347. {"foo": {"bar": 1}} matches {"foo": None}
  348. {"foo": {"bar": 1}} does not matches {"foo": {"baz": None}}
  349. """
  350. def event_match(event, match=None):
  351. if match is None:
  352. return True
  353. for key in match:
  354. if key in event:
  355. if isinstance(event[key], dict):
  356. if not event_match(event[key], match[key]):
  357. return False
  358. elif event[key] != match[key]:
  359. return False
  360. else:
  361. return False
  362. return True
  363. # Search cached events
  364. for event in self._events:
  365. if (event['event'] == name) and event_match(event, match):
  366. self._events.remove(event)
  367. return event
  368. # Poll for new events
  369. while True:
  370. event = self._qmp.pull_event(wait=timeout)
  371. if (event['event'] == name) and event_match(event, match):
  372. return event
  373. self._events.append(event)
  374. return None
  375. def get_log(self):
  376. """
  377. After self.shutdown or failed qemu execution, this returns the output
  378. of the qemu process.
  379. """
  380. return self._iolog
  381. def add_args(self, *args):
  382. """
  383. Adds to the list of extra arguments to be given to the QEMU binary
  384. """
  385. self._args.extend(args)
  386. def set_machine(self, machine_type):
  387. """
  388. Sets the machine type
  389. If set, the machine type will be added to the base arguments
  390. of the resulting QEMU command line.
  391. """
  392. self._machine = machine_type
  393. def set_console(self, device_type=None):
  394. """
  395. Sets the device type for a console device
  396. If set, the console device and a backing character device will
  397. be added to the base arguments of the resulting QEMU command
  398. line.
  399. This is a convenience method that will either use the provided
  400. device type, of if not given, it will used the device type set
  401. on CONSOLE_DEV_TYPES.
  402. The actual setting of command line arguments will be be done at
  403. machine launch time, as it depends on the temporary directory
  404. to be created.
  405. @param device_type: the device type, such as "isa-serial"
  406. @raises: QEMUMachineAddDeviceError if the device type is not given
  407. and can not be determined.
  408. """
  409. if device_type is None:
  410. if self._machine is None:
  411. raise QEMUMachineAddDeviceError("Can not add a console device:"
  412. " QEMU instance without a "
  413. "defined machine type")
  414. for regex, device in CONSOLE_DEV_TYPES.items():
  415. if re.match(regex, self._machine):
  416. device_type = device
  417. break
  418. if device_type is None:
  419. raise QEMUMachineAddDeviceError("Can not add a console device:"
  420. " no matching console device "
  421. "type definition")
  422. self._console_device_type = device_type
  423. @property
  424. def console_socket(self):
  425. """
  426. Returns a socket connected to the console
  427. """
  428. if self._console_socket is None:
  429. self._console_socket = socket.socket(socket.AF_UNIX,
  430. socket.SOCK_STREAM)
  431. self._console_socket.connect(self._console_address)
  432. return self._console_socket