PageRenderTime 56ms CodeModel.GetById 26ms RepoModel.GetById 1ms app.codeStats 0ms

/cloudcontrol/node/hypervisor/domains/__init__.py

https://bitbucket.org/sp4ke/cc-node
Python | 412 lines | 298 code | 61 blank | 53 comment | 73 complexity | 767094ddb09f3d94de0c0522a0071c4a MD5 | raw file
  1. # This file is part of CloudControl.
  2. #
  3. # CloudControl is free software: you can redistribute it and/or modify
  4. # it under the terms of the GNU Lesser General Public License as published by
  5. # the Free Software Foundation, either version 3 of the License, or
  6. # (at your option) any later version.
  7. #
  8. # CloudControl is distributed in the hope that it will be useful,
  9. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. # GNU Lesser General Public License for more details.
  12. #
  13. # You should have received a copy of the GNU Lesser General Public License
  14. # along with CloudControl. If not, see <http://www.gnu.org/licenses/>.
  15. import errno
  16. import logging
  17. import socket
  18. import weakref
  19. from StringIO import StringIO
  20. from xml.etree import cElementTree as et
  21. from collections import namedtuple
  22. import pyev
  23. import libvirt
  24. from cloudcontrol.common.client.tags import Tag, tag_inspector
  25. from cloudcontrol.node.hypervisor import lib as _libvirt
  26. from cloudcontrol.node.hypervisor.lib import DOMAIN_STATES as STATE
  27. from cloudcontrol.node.hypervisor.domains import vm_tags
  28. from cloudcontrol.node.utils import SocketBuffer
  29. from cloudcontrol.node.exc import ConsoleAlreadyOpened, ConsoleError
  30. logger = logging.getLogger(__name__)
  31. NetworkInterface = namedtuple('NetworkInterface', ('source', 'mac', 'model'))
  32. class VirtualMachine(object):
  33. """Represent a VM instance."""
  34. #: buffer size for console connection handling
  35. BUFFER_LEN = 1024
  36. def __init__(self, dom, hypervisor):
  37. """
  38. :param dom: libvirt domain instance
  39. :param hypervisor: hypervisor where the VM is
  40. """
  41. self.hypervisor = weakref.proxy(hypervisor)
  42. #: UUID string of domain
  43. self.uuid = dom.UUIDString()
  44. self.name = dom.name()
  45. #: state of VM: started, stoped, paused
  46. self._state = STATE[dom.info()[0]]
  47. #: tags for this VM
  48. # FIXME use a tag db instance
  49. self.tags = dict((t.name, t) for t in tag_inspector(vm_tags, self))
  50. #: Driver cache behavior for each VM storage, see
  51. #: http://libvirt.org/formatdomain.html#elementsDisks
  52. self.cache_behaviour = dict()
  53. # define dynamic tags
  54. i = 0
  55. for v in self.iter_disks():
  56. for t in (
  57. Tag('disk%s_size' % i, v.capacity, 10),
  58. Tag('disk%s_path' % i, v.path, 10),
  59. Tag('disk%s_pool' % i, v.storage, 10), # FIXME: change
  60. Tag('disk%s_vol' % i, v.name, 10),
  61. Tag('disk%s_cache' %i,
  62. lambda: self.cache_behaviour.get(v.path), 10)
  63. ):
  64. self.tags[t.name] = t
  65. i += 1
  66. i = 0
  67. for nic in self.iter_nics():
  68. for t in (
  69. Tag('nic%s_mac' % i, nic.mac, 10),
  70. Tag('nic%s_source' % i, nic.source, 10),
  71. Tag('nic%s_model' % i, nic.model, 10),
  72. ):
  73. self.tags[t.name] = t
  74. i += 1
  75. #: keep record of CPU stats (libev timestamp, cpu time)
  76. self.cpu_stats = (hypervisor.handler.main.evloop.now(), dom.info()[4])
  77. # attributes related to console handling
  78. self.stream = None
  79. self.sock = None # socketpair endpoint
  80. self.read_watcher = None # ev watcher for sock
  81. self.write_watcher = None # ev watcher for sock
  82. self.from_tunnel = None # buffer
  83. self.from_stream = None # buffer
  84. self.stream_handling = 0 # libvirt stream event mask
  85. @property
  86. def state(self):
  87. return self._state
  88. @state.setter
  89. def state(self, value):
  90. self._state = value
  91. self.tags['status'].update_value()
  92. self.tags['vncport'].update_value()
  93. @property
  94. def lv_dom(self):
  95. """Libvirt domain instance."""
  96. return self.hypervisor.vir_con.lookupByUUIDString(self.uuid)
  97. def start(self, pause=False):
  98. flags = 0
  99. if pause:
  100. flags |= libvirt.VIR_DOMAIN_START_PAUSED
  101. self.lv_dom.createWithFlags(flags)
  102. def stop(self):
  103. self.lv_dom.shutdown()
  104. def suspend(self):
  105. self.lv_dom.suspend()
  106. def resume(self):
  107. self.lv_dom.resume()
  108. def destroy(self):
  109. self.lv_dom.destroy()
  110. def undefine(self):
  111. self.lv_dom.undefine()
  112. @property
  113. def disks(self):
  114. return list(self.iter_disks())
  115. def iter_disks(self):
  116. for d in et.ElementTree().parse(
  117. StringIO(self.lv_dom.XMLDesc(0))
  118. ).findall('devices/disk'):
  119. if d.get('device') != 'disk':
  120. continue
  121. type_ = d.get('type')
  122. if type_ not in ('file', 'block'):
  123. continue
  124. path = d.find('source').get(dict(file='file', block='dev')[type_])
  125. # update cache behaviour
  126. driver = d.find('driver')
  127. if driver is None:
  128. driver = {}
  129. self.cache_behaviour[path] = driver.get('cache', 'default')
  130. volume = self.hypervisor.storage.get_volume(path)
  131. if volume is None:
  132. continue
  133. yield volume
  134. @property
  135. def nics(self):
  136. return list(self.iter_nics())
  137. def iter_nics(self):
  138. for nic in et.ElementTree().parse(
  139. StringIO(self.lv_dom.XMLDesc(0))
  140. ).findall('devices/interface'):
  141. if nic.get('type') == 'bridge':
  142. try:
  143. mac = nic.find('mac').get('address')
  144. except AttributeError:
  145. mac = None
  146. try:
  147. model = nic.find('model').get('type')
  148. except AttributeError:
  149. model = None
  150. try:
  151. source = nic.find('source').get('bridge')
  152. except AttributeError:
  153. source = None
  154. yield NetworkInterface(
  155. mac=mac,
  156. source=source,
  157. model=model,
  158. )
  159. def open_console(self):
  160. if self.stream is not None:
  161. raise ConsoleAlreadyOpened('Console for this VM is already'
  162. ' opened')
  163. if str(
  164. self.hypervisor.handler.tag_db['__main__']['libvirtver'].value,
  165. ).startswith('8'):
  166. raise ConsoleError(
  167. 'Cannot open console, not compatible with this version of libvirt')
  168. logger.info('Opening console stream on VM %s', self.name)
  169. try:
  170. self.stream = self.hypervisor.vir_con.newStream(
  171. libvirt.VIR_STREAM_NONBLOCK)
  172. except libvirt.libvirtError:
  173. logger.error('Cannot create new stream for console %s', self.name)
  174. self.close_console()
  175. raise
  176. self.stream_handling = libvirt.VIR_STREAM_EVENT_READABLE | (
  177. libvirt.VIR_STREAM_EVENT_ERROR | libvirt.VIR_STREAM_EVENT_HANGUP)
  178. try:
  179. self.lv_dom.openConsole(None, self.stream, 0)
  180. self.stream.eventAddCallback(self.stream_handling,
  181. self.virt_console_stream_cb,
  182. None)
  183. except libvirt.libvirtError:
  184. logger.error('Cannot open console on domain %s', self.name)
  185. self.close_console()
  186. raise
  187. try:
  188. self.sock, tunnel_endpoint = socket.socketpair()
  189. except socket.error:
  190. logger.error('Cannot create socket pair for console on domain %s',
  191. self.name)
  192. self.close_console()
  193. raise
  194. try:
  195. self.sock.setblocking(0)
  196. except socket.error:
  197. logger.error('Cannot set socket to non blocking for console on'
  198. ' domain %s', self.name)
  199. self.close_console()
  200. raise
  201. self.read_watcher = self.hypervisor.handler.main.evloop.io(
  202. self.sock, pyev.EV_READ, self.read_from_tun_cb)
  203. self.read_watcher.start()
  204. self.write_watcher = self.hypervisor.handler.main.evloop.io(
  205. self.sock, pyev.EV_WRITE, self.write_to_tun_cb)
  206. # self.write_watcher.start()
  207. self.from_tunnel = SocketBuffer(4096)
  208. self.from_stream = SocketBuffer(4096)
  209. return tunnel_endpoint
  210. def virt_console_stream_cb(self, stream, events, opaque):
  211. """Handles read/write from/to libvirt stream."""
  212. if events & libvirt.VIR_EVENT_HANDLE_ERROR or (
  213. events & libvirt.VIR_EVENT_HANDLE_HANGUP):
  214. # error/hangup
  215. # logger.debug('Received error on stream')
  216. self.close_console()
  217. return
  218. if events & libvirt.VIR_EVENT_HANDLE_WRITABLE:
  219. # logger.debug('Write to stream')
  220. # logger.debug('Event %s', self.stream_handling)
  221. while True:
  222. try:
  223. to_send = self.from_tunnel.popleft()
  224. except IndexError:
  225. # update libvirt event mask
  226. self.stream_handling ^= libvirt.VIR_STREAM_EVENT_WRITABLE
  227. self.stream.eventUpdateCallback(self.stream_handling)
  228. break
  229. send_buffer = to_send
  230. total_sent = 0
  231. while True:
  232. try:
  233. written = self.stream.send(send_buffer)
  234. except:
  235. # libvirt send error
  236. logger.exception('Error while writing to stream')
  237. self.close_console()
  238. return
  239. if written == -2: # equivalent to EAGAIN
  240. self.from_tunnel.appendleft(to_send[total_sent:])
  241. break
  242. elif written == len(send_buffer):
  243. break
  244. total_sent += written
  245. send_buffer = buffer(to_send, total_sent)
  246. if not self.from_tunnel.is_full():
  247. self.read_watcher.start()
  248. if events & libvirt.VIR_EVENT_HANDLE_READABLE:
  249. # logger.debug('Read from stream')
  250. # logger.debug('Event %s', self.stream_handling)
  251. while True:
  252. try:
  253. incoming = self.stream.recv(self.BUFFER_LEN)
  254. except:
  255. pass
  256. if incoming == -2:
  257. # equivalent to EAGAIN
  258. break
  259. elif not incoming:
  260. # EOF
  261. self.close_console()
  262. return
  263. self.from_stream.append(incoming)
  264. if self.from_stream.is_full():
  265. # update libvirt event mask
  266. self.stream_handling ^= libvirt.VIR_STREAM_EVENT_READABLE
  267. self.stream.eventUpdateCallback(self.stream_handling)
  268. break
  269. if not self.from_stream.is_empty():
  270. self.write_watcher.start()
  271. def read_from_tun_cb(self, watcher, revents):
  272. """Read data from tunnel and save into buffer."""
  273. # logger.debug('Read from tunnel')
  274. # logger.debug('Event %s', self.stream_handling)
  275. while True:
  276. try:
  277. incoming = self.sock.recv(self.BUFFER_LEN)
  278. except socket.error as exc:
  279. if exc.errno in (errno.EAGAIN, errno.EWOULDBLOCK):
  280. break
  281. logger.exception('Error reading on socket for vm console')
  282. self.close_console()
  283. return
  284. if not incoming:
  285. # EOF (we could wait before closing console stream)
  286. self.close_console()
  287. return
  288. self.from_tunnel.append(incoming)
  289. if self.from_tunnel.is_full():
  290. self.read_watcher.stop()
  291. break
  292. if not self.from_tunnel.is_empty():
  293. # update libvirt event callback
  294. self.stream_handling |= libvirt.VIR_STREAM_EVENT_WRITABLE
  295. self.stream.eventUpdateCallback(self.stream_handling)
  296. def write_to_tun_cb(self, watcher, revents):
  297. """Write data from buffer to tunnel."""
  298. # logger.debug('Write to tunnel')
  299. # logger.debug('Event %s', self.stream_handling)
  300. while True:
  301. try:
  302. to_send = self.from_stream.popleft()
  303. except IndexError:
  304. self.write_watcher.stop()
  305. break
  306. send_buffer = to_send
  307. total_sent = 0
  308. while True:
  309. try:
  310. written = self.sock.send(send_buffer)
  311. except socket.error as exc:
  312. if exc.errno in (errno.EAGAIN, errno.EWOULDBLOCK):
  313. self.from_stream.appenleft(to_send[total_sent:])
  314. break
  315. logger.exception('Error writing on socket for vm console')
  316. self.close_console()
  317. return
  318. if written == len(send_buffer):
  319. break
  320. total_sent += written
  321. send_buffer = buffer(to_send, total_sent)
  322. if not self.from_stream.is_full():
  323. # update libvirt event callback
  324. self.stream_handling |= libvirt.VIR_STREAM_EVENT_READABLE
  325. self.stream.eventUpdateCallback(self.stream_handling)
  326. def close_console(self):
  327. logger.info('Closing console stream on VM %s', self.name)
  328. if self.stream is not None:
  329. try:
  330. self.stream.eventRemoveCallback()
  331. except Exception:
  332. logger.error('Error while removing callback on stream')
  333. try:
  334. self.stream.finish()
  335. except Exception:
  336. logger.error('Cannot finnish console stream')
  337. self.stream = None
  338. if self.sock is not None:
  339. try:
  340. self.sock.close()
  341. except socket.error:
  342. logger.error('Cannot close socket')
  343. self.sock = None
  344. if self.read_watcher is not None:
  345. self.read_watcher.stop()
  346. self.read_watcher = None
  347. if self.write_watcher is not None:
  348. self.write_watcher.stop()
  349. self.write_watcher = None
  350. self.from_tunnel = None
  351. self.from_stream = None