PageRenderTime 56ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/salt/modules/vboxmanage.py

https://gitlab.com/ricardo.hernandez/salt
Python | 508 lines | 465 code | 12 blank | 31 comment | 11 complexity | a53019b33bc7554791b3215af1de4266 MD5 | raw file
  1. # -*- coding: utf-8 -*-
  2. '''
  3. Support for VirtualBox using the VBoxManage command
  4. .. versionadded:: 2016.3.0
  5. If the ``vboxdrv`` kernel module is not loaded, this module can automatically
  6. load it by configuring ``autoload_vboxdrv`` in ``/etc/salt/minion``:
  7. .. code-block: yaml
  8. autoload_vboxdrv: True
  9. The default for this setting is ``False``.
  10. :depends: virtualbox
  11. '''
  12. from __future__ import absolute_import
  13. import re
  14. import os.path
  15. import logging
  16. # pylint: disable=import-error,no-name-in-module
  17. import salt.utils
  18. from salt.ext.six import string_types
  19. from salt.exceptions import CommandExecutionError
  20. # pylint: enable=import-error,no-name-in-module
  21. LOG = logging.getLogger(__name__)
  22. UUID_RE = re.compile('[^{0}]'.format('a-zA-Z0-9._-'))
  23. NAME_RE = re.compile('[^{0}]'.format('a-zA-Z0-9._-'))
  24. def __virtual__():
  25. '''
  26. Only load the module if VBoxManage is installed
  27. '''
  28. if vboxcmd():
  29. if __opts__.get('autoload_vboxdrv', False) is True:
  30. if not __salt__['kmod.is_loaded']('vboxdrv'):
  31. __salt__['kmod.load']('vboxdrv')
  32. return True
  33. return (False, 'The vboxmanaged execution module failed to load: VBoxManage is not installed.')
  34. def vboxcmd():
  35. '''
  36. Return the location of the VBoxManage command
  37. '''
  38. return salt.utils.which('VBoxManage')
  39. def list_ostypes():
  40. '''
  41. List the available OS Types
  42. '''
  43. return list_items('ostypes', True, 'ID')
  44. def list_nodes_min():
  45. '''
  46. Return a list of registered VMs, with minimal information
  47. '''
  48. ret = {}
  49. cmd = '{0} list vms'.format(vboxcmd())
  50. for line in salt.modules.cmdmod.run(cmd).splitlines():
  51. if not line.strip():
  52. continue
  53. comps = line.split()
  54. name = comps[0].replace('"', '')
  55. ret[name] = True
  56. return ret
  57. def list_nodes_full():
  58. '''
  59. Return a list of registered VMs, with detailed information
  60. '''
  61. return list_items('vms', True, 'Name')
  62. def list_nodes():
  63. '''
  64. Return a list of registered VMs
  65. '''
  66. ret = {}
  67. nodes = list_nodes_full()
  68. for node in nodes:
  69. ret[node] = {
  70. 'id': nodes[node]['UUID'],
  71. 'image': nodes[node]['Guest OS'],
  72. 'name': nodes[node]['Name'],
  73. 'state': None,
  74. 'private_ips': [],
  75. 'public_ips': [],
  76. }
  77. ret[node]['size'] = '{0} RAM, {1} CPU'.format(
  78. nodes[node]['Memory size'],
  79. nodes[node]['Number of CPUs'],
  80. )
  81. return ret
  82. def start(name):
  83. '''
  84. Start a VM
  85. '''
  86. ret = {}
  87. cmd = '{0} startvm {1}'.format(vboxcmd(), name)
  88. ret = salt.modules.cmdmod.run(cmd).splitlines()
  89. return ret
  90. def stop(name):
  91. '''
  92. Stop a VM
  93. '''
  94. cmd = '{0} controlvm {1} poweroff'.format(vboxcmd(), name)
  95. ret = salt.modules.cmdmod.run(cmd).splitlines()
  96. return ret
  97. def register(filename):
  98. '''
  99. Register a VM
  100. '''
  101. if not os.path.isfile(filename):
  102. raise CommandExecutionError(
  103. 'The specified filename ({0}) does not exist.'.format(filename)
  104. )
  105. cmd = '{0} registervm {1}'.format(vboxcmd(), filename)
  106. ret = salt.modules.cmdmod.run_all(cmd)
  107. if ret['retcode'] == 0:
  108. return True
  109. return ret['stderr']
  110. def unregister(name, delete=False):
  111. '''
  112. Unregister a VM
  113. '''
  114. nodes = list_nodes_min()
  115. if name not in nodes:
  116. raise CommandExecutionError(
  117. 'The specified VM ({0}) is not registered.'.format(name)
  118. )
  119. cmd = '{0} unregistervm {1}'.format(vboxcmd(), name)
  120. if delete is True:
  121. cmd += ' --delete'
  122. ret = salt.modules.cmdmod.run_all(cmd)
  123. if ret['retcode'] == 0:
  124. return True
  125. return ret['stderr']
  126. def destroy(name):
  127. '''
  128. Unregister and destroy a VM
  129. '''
  130. return unregister(name, True)
  131. def create(name,
  132. groups=None,
  133. ostype=None,
  134. register=True,
  135. basefolder=None,
  136. new_uuid=None,
  137. **kwargs):
  138. '''
  139. Create a new VM
  140. CLI Example:
  141. .. code-block:: bash
  142. salt 'hypervisor' vboxmanage.create <name>
  143. '''
  144. nodes = list_nodes_min()
  145. if name in nodes:
  146. raise CommandExecutionError(
  147. 'The specified VM ({0}) is already registered.'.format(name)
  148. )
  149. params = ''
  150. if name:
  151. if NAME_RE.search(name):
  152. raise CommandExecutionError('New VM name contains invalid characters')
  153. params += ' --name {0}'.format(name)
  154. if groups:
  155. if isinstance(groups, string_types):
  156. groups = [groups]
  157. if isinstance(groups, list):
  158. params += ' --groups {0}'.format(','.join(groups))
  159. else:
  160. raise CommandExecutionError(
  161. 'groups must be either a string or a list of strings'
  162. )
  163. ostypes = list_ostypes()
  164. if ostype not in ostypes:
  165. raise CommandExecutionError(
  166. 'The specified OS type ({0}) is not available.'.format(name)
  167. )
  168. else:
  169. params += ' --ostype ' + ostype
  170. if register is True:
  171. params += ' --register'
  172. if basefolder:
  173. if not os.path.exists(basefolder):
  174. raise CommandExecutionError('basefolder {0} was not found'.format(basefolder))
  175. params += ' --basefolder {0}'.format(basefolder)
  176. if new_uuid:
  177. if NAME_RE.search(new_uuid):
  178. raise CommandExecutionError('New UUID contains invalid characters')
  179. params += ' --uuid {0}'.format(new_uuid)
  180. cmd = '{0} create {1}'.format(vboxcmd(), params)
  181. ret = salt.modules.cmdmod.run_all(cmd)
  182. if ret['retcode'] == 0:
  183. return True
  184. return ret['stderr']
  185. def clonevm(name=None,
  186. uuid=None,
  187. new_name=None,
  188. snapshot_uuid=None,
  189. snapshot_name=None,
  190. mode='machine',
  191. options=None,
  192. basefolder=None,
  193. new_uuid=None,
  194. register=False,
  195. groups=None,
  196. **kwargs):
  197. '''
  198. Clone a new VM from an existing VM
  199. CLI Example:
  200. .. code-block:: bash
  201. salt 'hypervisor' vboxmanage.clonevm <name> <new_name>
  202. '''
  203. if (name and uuid) or (not name and not uuid):
  204. raise CommandExecutionError(
  205. 'Either a name or a uuid must be specified, but not both.'
  206. )
  207. params = ''
  208. nodes_names = list_nodes_min()
  209. nodes_uuids = list_items('vms', True, 'UUID').keys()
  210. if name:
  211. if name not in nodes_names:
  212. raise CommandExecutionError(
  213. 'The specified VM ({0}) is not registered.'.format(name)
  214. )
  215. params += ' ' + name
  216. elif uuid:
  217. if uuid not in nodes_uuids:
  218. raise CommandExecutionError(
  219. 'The specified VM ({0}) is not registered.'.format(name)
  220. )
  221. params += ' ' + uuid
  222. if snapshot_name and snapshot_uuid:
  223. raise CommandExecutionError(
  224. 'Either a snapshot_name or a snapshot_uuid may be specified, but not both'
  225. )
  226. if snapshot_name:
  227. if NAME_RE.search(snapshot_name):
  228. raise CommandExecutionError('Snapshot name contains invalid characters')
  229. params += ' --snapshot {0}'.format(snapshot_name)
  230. elif snapshot_uuid:
  231. if UUID_RE.search(snapshot_uuid):
  232. raise CommandExecutionError('Snapshot name contains invalid characters')
  233. params += ' --snapshot {0}'.format(snapshot_uuid)
  234. valid_modes = ('machine', 'machineandchildren', 'all')
  235. if mode and mode not in valid_modes:
  236. raise CommandExecutionError(
  237. 'Mode must be one of: {0} (default "machine")'.format(', '.join(valid_modes))
  238. )
  239. else:
  240. params += ' --mode ' + mode
  241. valid_options = ('link', 'keepallmacs', 'keepnatmacs', 'keepdisknames')
  242. if options and options not in valid_options:
  243. raise CommandExecutionError(
  244. 'If specified, options must be one of: {0}'.format(', '.join(valid_options))
  245. )
  246. else:
  247. params += ' --options ' + options
  248. if new_name:
  249. if NAME_RE.search(new_name):
  250. raise CommandExecutionError('New name contains invalid characters')
  251. params += ' --name {0}'.format(new_name)
  252. if groups:
  253. if isinstance(groups, string_types):
  254. groups = [groups]
  255. if isinstance(groups, list):
  256. params += ' --groups {0}'.format(','.join(groups))
  257. else:
  258. raise CommandExecutionError(
  259. 'groups must be either a string or a list of strings'
  260. )
  261. if basefolder:
  262. if not os.path.exists(basefolder):
  263. raise CommandExecutionError('basefolder {0} was not found'.format(basefolder))
  264. params += ' --basefolder {0}'.format(basefolder)
  265. if new_uuid:
  266. if NAME_RE.search(new_uuid):
  267. raise CommandExecutionError('New UUID contains invalid characters')
  268. params += ' --uuid {0}'.format(new_uuid)
  269. if register is True:
  270. params += ' --register'
  271. cmd = '{0} clonevm {1}'.format(vboxcmd(), name)
  272. ret = salt.modules.cmdmod.run_all(cmd)
  273. if ret['retcode'] == 0:
  274. return True
  275. return ret['stderr']
  276. def clonemedium(medium,
  277. uuid_in=None,
  278. file_in=None,
  279. uuid_out=None,
  280. file_out=None,
  281. mformat=None,
  282. variant=None,
  283. existing=False,
  284. **kwargs):
  285. '''
  286. Clone a new VM from an existing VM
  287. CLI Example:
  288. .. code-block:: bash
  289. salt 'hypervisor' vboxmanage.clonemedium <name> <new_name>
  290. '''
  291. params = ''
  292. valid_mediums = ('disk', 'dvd', 'floppy')
  293. if medium in valid_mediums:
  294. params += medium
  295. else:
  296. raise CommandExecutionError(
  297. 'Medium must be one of: {0}.'.format(', '.join(valid_mediums))
  298. )
  299. if (uuid_in and file_in) or (not uuid_in and not file_in):
  300. raise CommandExecutionError(
  301. 'Either uuid_in or file_in must be used, but not both.'
  302. )
  303. if uuid_in:
  304. if medium == 'disk':
  305. item = 'hdds'
  306. elif medium == 'dvd':
  307. item = 'dvds'
  308. elif medium == 'floppy':
  309. item = 'floppies'
  310. items = list_items(item)
  311. if uuid_in not in items:
  312. raise CommandExecutionError('UUID {0} was not found'.format(uuid_in))
  313. params += ' ' + uuid_in
  314. elif file_in:
  315. if not os.path.exists(file_in):
  316. raise CommandExecutionError('File {0} was not found'.format(file_in))
  317. params += ' ' + file_in
  318. if (uuid_out and file_out) or (not uuid_out and not file_out):
  319. raise CommandExecutionError(
  320. 'Either uuid_out or file_out must be used, but not both.'
  321. )
  322. if uuid_out:
  323. params += ' ' + uuid_out
  324. elif file_out:
  325. try:
  326. salt.utils.fopen(file_out, 'w').close()
  327. os.unlink(file_out)
  328. params += ' ' + file_out
  329. except OSError:
  330. raise CommandExecutionError('{0} is not a valid filename'.format(file_out))
  331. if mformat:
  332. valid_mformat = ('VDI', 'VMDK', 'VHD', 'RAW')
  333. if mformat not in valid_mformat:
  334. raise CommandExecutionError(
  335. 'If specified, mformat must be one of: {0}'.format(', '.join(valid_mformat))
  336. )
  337. else:
  338. params += ' --format ' + mformat
  339. valid_variant = ('Standard', 'Fixed', 'Split2G', 'Stream', 'ESX')
  340. if variant and variant not in valid_variant:
  341. if not os.path.exists(file_in):
  342. raise CommandExecutionError(
  343. 'If specified, variant must be one of: {0}'.format(', '.join(valid_variant))
  344. )
  345. else:
  346. params += ' --variant ' + variant
  347. if existing:
  348. params += ' --existing'
  349. cmd = '{0} clonemedium {1}'.format(vboxcmd(), params)
  350. ret = salt.modules.cmdmod.run_all(cmd)
  351. if ret['retcode'] == 0:
  352. return True
  353. return ret['stderr']
  354. def list_items(item, details=False, group_by='UUID'):
  355. '''
  356. Return a list of a specific type of item. The following items are available:
  357. vms
  358. runningvms
  359. ostypes
  360. hostdvds
  361. hostfloppies
  362. intnets
  363. bridgedifs
  364. hostonlyifs
  365. natnets
  366. dhcpservers
  367. hostinfo
  368. hostcpuids
  369. hddbackends
  370. hdds
  371. dvds
  372. floppies
  373. usbhost
  374. usbfilters
  375. systemproperties
  376. extpacks
  377. groups
  378. webcams
  379. screenshotformats
  380. CLI Example:
  381. .. code-block:: bash
  382. salt 'hypervisor' vboxmanage.items <item>
  383. salt 'hypervisor' vboxmanage.items <item> details=True
  384. salt 'hypervisor' vboxmanage.items <item> details=True group_by=Name
  385. Some items do not display well, or at all, unless ``details`` is set to
  386. ``True``. By default, items are grouped by the ``UUID`` field, but not all
  387. items contain that field. In those cases, another field must be specified.
  388. '''
  389. types = (
  390. 'vms', 'runningvms', 'ostypes', 'hostdvds', 'hostfloppies', 'intnets',
  391. 'bridgedifs', 'hostonlyifs', 'natnets', 'dhcpservers', 'hostinfo',
  392. 'hostcpuids', 'hddbackends', 'hdds', 'dvds', 'floppies', 'usbhost',
  393. 'usbfilters', 'systemproperties', 'extpacks', 'groups', 'webcams',
  394. 'screenshotformats'
  395. )
  396. if item not in types:
  397. raise CommandExecutionError(
  398. 'Item must be one of: {0}.'.format(', '.join(types))
  399. )
  400. flag = ''
  401. if details is True:
  402. flag = ' -l'
  403. ret = {}
  404. tmp_id = None
  405. tmp_dict = {}
  406. cmd = '{0} list{1} {2}'.format(vboxcmd(), flag, item)
  407. for line in salt.modules.cmdmod.run(cmd).splitlines():
  408. if not line.strip():
  409. continue
  410. comps = line.split(':')
  411. if len(comps) < 1:
  412. continue
  413. if tmp_id is not None:
  414. ret[tmp_id] = tmp_dict
  415. line_val = ':'.join(comps[1:]).strip()
  416. if comps[0] == group_by:
  417. tmp_id = line_val
  418. tmp_dict = {}
  419. tmp_dict[comps[0]] = line_val
  420. return ret