New Saltstack Salt formula
Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.

1936 rindas
54KB

  1. # -*- coding: utf-8 -*-
  2. '''
  3. Work with virtual machines managed by libvirt
  4. :depends: libvirt Python module
  5. '''
  6. # Special Thanks to Michael Dehann, many of the concepts, and a few structures
  7. # of his in the virt func module have been used
  8. # Import python libs
  9. from __future__ import absolute_import
  10. import copy
  11. import os
  12. import re
  13. import sys
  14. import shutil
  15. import subprocess
  16. import string # pylint: disable=deprecated-module
  17. import logging
  18. # Import third party libs
  19. import yaml
  20. import json
  21. import jinja2
  22. import jinja2.exceptions
  23. import salt.ext.six as six
  24. from salt.ext.six.moves import StringIO as _StringIO # pylint: disable=import-error
  25. from xml.dom import minidom
  26. try:
  27. import libvirt # pylint: disable=import-error
  28. HAS_ALL_IMPORTS = True
  29. except ImportError:
  30. HAS_ALL_IMPORTS = False
  31. # Import salt libs
  32. import salt.utils
  33. import salt.utils.files
  34. import salt.utils.templates
  35. import salt.utils.validate.net
  36. from salt.exceptions import CommandExecutionError, SaltInvocationError
  37. log = logging.getLogger(__name__)
  38. # Set up template environment
  39. JINJA = jinja2.Environment(
  40. loader=jinja2.FileSystemLoader(
  41. os.path.join(salt.utils.templates.TEMPLATE_DIRNAME, 'virt')
  42. )
  43. )
  44. VIRT_STATE_NAME_MAP = {0: 'running',
  45. 1: 'running',
  46. 2: 'running',
  47. 3: 'paused',
  48. 4: 'shutdown',
  49. 5: 'shutdown',
  50. 6: 'crashed'}
  51. VIRT_DEFAULT_HYPER = 'kvm'
  52. def __virtual__():
  53. if not HAS_ALL_IMPORTS:
  54. return False
  55. return 'virtng'
  56. def __get_conn():
  57. '''
  58. Detects what type of dom this node is and attempts to connect to the
  59. correct hypervisor via libvirt.
  60. '''
  61. # This has only been tested on kvm and xen, it needs to be expanded to
  62. # support all vm layers supported by libvirt
  63. def __esxi_uri():
  64. '''
  65. Connect to an ESXi host with a configuration like so:
  66. .. code-block:: yaml
  67. libvirt:
  68. hypervisor: esxi
  69. connection: esx01
  70. The connection setting can either be an explicit libvirt URI,
  71. or a libvirt URI alias as in this example. No, it cannot be
  72. just a hostname.
  73. Example libvirt `/etc/libvirt/libvirt.conf`:
  74. .. code-block::
  75. uri_aliases = [
  76. "esx01=esx://10.1.1.101/?no_verify=1&auto_answer=1",
  77. "esx02=esx://10.1.1.102/?no_verify=1&auto_answer=1",
  78. ]
  79. Reference:
  80. - http://libvirt.org/drvesx.html#uriformat
  81. - http://libvirt.org/uri.html#URI_config
  82. '''
  83. connection = __salt__['config.get']('libvirt:connection', 'esx')
  84. return connection
  85. def __esxi_auth():
  86. '''
  87. We rely on that the credentials is provided to libvirt through
  88. its built in mechanisms.
  89. Example libvirt `/etc/libvirt/auth.conf`:
  90. .. code-block::
  91. [credentials-myvirt]
  92. username=user
  93. password=secret
  94. [auth-esx-10.1.1.101]
  95. credentials=myvirt
  96. [auth-esx-10.1.1.102]
  97. credentials=myvirt
  98. Reference:
  99. - http://libvirt.org/auth.html#Auth_client_config
  100. '''
  101. return [[libvirt.VIR_CRED_EXTERNAL], lambda: 0, None]
  102. if 'virt.connect' in __opts__:
  103. conn_str = __opts__['virt.connect']
  104. else:
  105. conn_str = 'qemu:///system'
  106. conn_func = {
  107. 'esxi': [libvirt.openAuth, [__esxi_uri(),
  108. __esxi_auth(),
  109. 0]],
  110. 'qemu': [libvirt.open, [conn_str]],
  111. }
  112. hypervisor = __salt__['config.get']('libvirt:hypervisor', 'qemu')
  113. try:
  114. conn = conn_func[hypervisor][0](*conn_func[hypervisor][1])
  115. except Exception:
  116. raise CommandExecutionError(
  117. 'Sorry, {0} failed to open a connection to the hypervisor '
  118. 'software at {1}'.format(
  119. __grains__['fqdn'],
  120. conn_func[hypervisor][1][0]
  121. )
  122. )
  123. return conn
  124. def _get_dom(vm_):
  125. '''
  126. Return a domain object for the named vm
  127. '''
  128. conn = __get_conn()
  129. if vm_ not in list_vms():
  130. raise CommandExecutionError('The specified vm is not present')
  131. return conn.lookupByName(vm_)
  132. def _libvirt_creds():
  133. '''
  134. Returns the user and group that the disk images should be owned by
  135. '''
  136. g_cmd = 'grep ^\\s*group /etc/libvirt/qemu.conf'
  137. u_cmd = 'grep ^\\s*user /etc/libvirt/qemu.conf'
  138. try:
  139. group = subprocess.Popen(g_cmd,
  140. shell=True,
  141. stdout=subprocess.PIPE).communicate()[0].split('"')[1]
  142. except IndexError:
  143. group = 'root'
  144. try:
  145. user = subprocess.Popen(u_cmd,
  146. shell=True,
  147. stdout=subprocess.PIPE).communicate()[0].split('"')[1]
  148. except IndexError:
  149. user = 'root'
  150. return {'user': user, 'group': group}
  151. def _get_migrate_command():
  152. '''
  153. Returns the command shared by the different migration types
  154. '''
  155. if __salt__['config.option']('virt.tunnel'):
  156. return ('virsh migrate --p2p --tunnelled --live --persistent '
  157. '--undefinesource ')
  158. return 'virsh migrate --live --persistent --undefinesource '
  159. def _get_target(target, ssh):
  160. proto = 'qemu'
  161. if ssh:
  162. proto += '+ssh'
  163. return ' {0}://{1}/{2}'.format(proto, target, 'system')
  164. def _gen_xml(name,
  165. cpu,
  166. mem,
  167. diskp,
  168. nicp,
  169. hypervisor,
  170. **kwargs):
  171. '''
  172. Generate the XML string to define a libvirt vm
  173. '''
  174. hypervisor = 'vmware' if hypervisor == 'esxi' else hypervisor
  175. mem = mem * 1024 # MB
  176. context = {
  177. 'hypervisor': hypervisor,
  178. 'name': name,
  179. 'cpu': str(cpu),
  180. 'mem': str(mem),
  181. }
  182. if hypervisor in ['qemu', 'kvm']:
  183. context['controller_model'] = False
  184. elif hypervisor in ['esxi', 'vmware']:
  185. # TODO: make bus and model parameterized, this works for 64-bit Linux
  186. context['controller_model'] = 'lsilogic'
  187. if 'boot_dev' in kwargs:
  188. context['boot_dev'] = []
  189. for dev in kwargs['boot_dev'].split():
  190. context['boot_dev'].append(dev)
  191. else:
  192. context['boot_dev'] = ['hd']
  193. if 'serial_type' in kwargs:
  194. context['serial_type'] = kwargs['serial_type']
  195. if 'serial_type' in context and context['serial_type'] == 'tcp':
  196. if 'telnet_port' in kwargs:
  197. context['telnet_port'] = kwargs['telnet_port']
  198. else:
  199. context['telnet_port'] = 23023 # FIXME: use random unused port
  200. if 'serial_type' in context:
  201. if 'console' in kwargs:
  202. context['console'] = kwargs['console']
  203. else:
  204. context['console'] = True
  205. context['disks'] = {}
  206. for i, disk in enumerate(diskp):
  207. for disk_name, args in disk.items():
  208. context['disks'][disk_name] = {}
  209. fn_ = '{0}.{1}'.format(disk_name, args['format'])
  210. context['disks'][disk_name]['file_name'] = fn_
  211. context['disks'][disk_name]['source_file'] = os.path.join(args['pool'],
  212. name,
  213. fn_)
  214. if hypervisor in ['qemu', 'kvm']:
  215. context['disks'][disk_name]['target_dev'] = 'vd{0}'.format(string.ascii_lowercase[i])
  216. context['disks'][disk_name]['address'] = False
  217. context['disks'][disk_name]['driver'] = True
  218. elif hypervisor in ['esxi', 'vmware']:
  219. context['disks'][disk_name]['target_dev'] = 'sd{0}'.format(string.ascii_lowercase[i])
  220. context['disks'][disk_name]['address'] = True
  221. context['disks'][disk_name]['driver'] = False
  222. context['disks'][disk_name]['disk_bus'] = args['model']
  223. context['disks'][disk_name]['type'] = args['format']
  224. context['disks'][disk_name]['index'] = str(i)
  225. context['nics'] = nicp
  226. fn_ = 'libvirt_domain.jinja'
  227. try:
  228. template = JINJA.get_template(fn_)
  229. except jinja2.exceptions.TemplateNotFound:
  230. log.error('Could not load template {0}'.format(fn_))
  231. return ''
  232. return template.render(**context)
  233. def _gen_vol_xml(vmname,
  234. diskname,
  235. size,
  236. hypervisor,
  237. **kwargs):
  238. '''
  239. Generate the XML string to define a libvirt storage volume
  240. '''
  241. size = int(size) * 1024 # MB
  242. disk_info = _get_image_info(hypervisor, vmname, **kwargs)
  243. context = {
  244. 'name': vmname,
  245. 'filename': '{0}.{1}'.format(diskname, disk_info['disktype']),
  246. 'volname': diskname,
  247. 'disktype': disk_info['disktype'],
  248. 'size': str(size),
  249. 'pool': disk_info['pool'],
  250. }
  251. fn_ = 'libvirt_volume.jinja'
  252. try:
  253. template = JINJA.get_template(fn_)
  254. except jinja2.exceptions.TemplateNotFound:
  255. log.error('Could not load template {0}'.format(fn_))
  256. return ''
  257. return template.render(**context)
  258. def _qemu_image_info(path):
  259. '''
  260. Detect information for the image at path
  261. '''
  262. ret = {}
  263. out = __salt__['cmd.shell']('qemu-img info {0}'.format(path))
  264. match_map = {'size': r'virtual size: \w+ \((\d+) byte[s]?\)',
  265. 'format': r'file format: (\w+)'}
  266. for info, search in match_map.items():
  267. try:
  268. ret[info] = re.search(search, out).group(1)
  269. except AttributeError:
  270. continue
  271. return ret
  272. # TODO: this function is deprecated, should be replaced with
  273. # _qemu_image_info()
  274. def _image_type(vda):
  275. '''
  276. Detect what driver needs to be used for the given image
  277. '''
  278. out = __salt__['cmd.shell']('qemu-img info {0}'.format(vda))
  279. if 'file format: qcow2' in out:
  280. return 'qcow2'
  281. else:
  282. return 'raw'
  283. # TODO: this function is deprecated, should be merged and replaced
  284. # with _disk_profile()
  285. def _get_image_info(hypervisor, name, **kwargs):
  286. '''
  287. Determine disk image info, such as filename, image format and
  288. storage pool, based on which hypervisor is used
  289. '''
  290. ret = {}
  291. if hypervisor in ['esxi', 'vmware']:
  292. ret['disktype'] = 'vmdk'
  293. ret['filename'] = '{0}{1}'.format(name, '.vmdk')
  294. ret['pool'] = '[{0}] '.format(kwargs.get('pool', '0'))
  295. elif hypervisor in ['kvm', 'qemu']:
  296. ret['disktype'] = 'qcow2'
  297. ret['filename'] = '{0}{1}'.format(name, '.qcow2')
  298. if 'img_dest' in kwargs:
  299. ret['pool'] = kwargs['img_dest']
  300. else:
  301. ret['pool'] = __salt__['config.option']('virt.images')
  302. return ret
  303. def _disk_profile(profile, hypervisor, **kwargs):
  304. '''
  305. Gather the disk profile from the config or apply the default based
  306. on the active hypervisor
  307. This is the ``default`` profile for KVM/QEMU, which can be
  308. overridden in the configuration:
  309. .. code-block:: yaml
  310. virt:
  311. disk:
  312. default:
  313. - system:
  314. size: 8192
  315. format: qcow2
  316. model: virtio
  317. Example profile for KVM/QEMU with two disks, first is created
  318. from specified image, the second is empty:
  319. .. code-block:: yaml
  320. virt:
  321. disk:
  322. two_disks:
  323. - system:
  324. size: 8192
  325. format: qcow2
  326. model: virtio
  327. image: http://path/to/image.qcow2
  328. - lvm:
  329. size: 32768
  330. format: qcow2
  331. model: virtio
  332. The ``format`` and ``model`` parameters are optional, and will
  333. default to whatever is best suitable for the active hypervisor.
  334. '''
  335. default = [
  336. {'system':
  337. {'size': '8192'}
  338. }
  339. ]
  340. if hypervisor in ['esxi', 'vmware']:
  341. overlay = {'format': 'vmdk',
  342. 'model': 'scsi',
  343. 'pool': '[{0}] '.format(kwargs.get('pool', '0'))
  344. }
  345. elif hypervisor in ['qemu', 'kvm']:
  346. if 'img_dest' in kwargs:
  347. pool = kwargs['img_dest']
  348. else:
  349. pool = __salt__['config.option']('virt.images')
  350. overlay = {'format': 'qcow2', 'model': 'virtio', 'pool': pool}
  351. else:
  352. overlay = {}
  353. disklist = copy.deepcopy(__salt__['config.get']('virt:disk', {}).get(profile, default))
  354. for key, val in overlay.items():
  355. for i, disks in enumerate(disklist):
  356. for disk in disks:
  357. if key not in disks[disk]:
  358. disklist[i][disk][key] = val
  359. return disklist
  360. def _nic_profile(profile_name, hypervisor, **kwargs):
  361. def append_dict_profile_to_interface_list(profile_dict):
  362. for interface_name, attributes in profile_dict.items():
  363. attributes['name'] = interface_name
  364. interfaces.append(attributes)
  365. def _normalize_net_types(attributes):
  366. '''
  367. Guess which style of definition:
  368. bridge: br0
  369. or
  370. network: net0
  371. or
  372. type: network
  373. source: net0
  374. '''
  375. for type_ in ['bridge', 'network']:
  376. if type_ in attributes:
  377. attributes['type'] = type_
  378. # we want to discard the original key
  379. attributes['source'] = attributes.pop(type_)
  380. attributes['type'] = attributes.get('type', None)
  381. attributes['source'] = attributes.get('source', None)
  382. attributes['virtualport'] = attributes.get('virtualport', None)
  383. def _apply_default_overlay(attributes):
  384. for key, value in overlays[hypervisor].items():
  385. if key not in attributes or not attributes[key]:
  386. attributes[key] = value
  387. def _assign_mac(attributes):
  388. dmac = '{0}_mac'.format(attributes['name'])
  389. if dmac in kwargs:
  390. dmac = kwargs[dmac]
  391. if salt.utils.validate.net.mac(dmac):
  392. attributes['mac'] = dmac
  393. else:
  394. msg = 'Malformed MAC address: {0}'.format(dmac)
  395. raise CommandExecutionError(msg)
  396. else:
  397. attributes['mac'] = salt.utils.gen_mac()
  398. default = [{'eth0': {}}]
  399. vmware_overlay = {'type': 'bridge', 'source': 'DEFAULT', 'model': 'e1000'}
  400. kvm_overlay = {'type': 'bridge', 'source': 'br0', 'model': 'virtio'}
  401. overlays = {
  402. 'kvm': kvm_overlay,
  403. 'qemu': kvm_overlay,
  404. 'esxi': vmware_overlay,
  405. 'vmware': vmware_overlay,
  406. }
  407. # support old location
  408. config_data = __salt__['config.option']('virt.nic', {}).get(
  409. profile_name, None
  410. )
  411. if config_data is None:
  412. config_data = __salt__['config.get']('virt:nic', {}).get(
  413. profile_name, default
  414. )
  415. interfaces = []
  416. if isinstance(config_data, dict):
  417. append_dict_profile_to_interface_list(config_data)
  418. elif isinstance(config_data, list):
  419. for interface in config_data:
  420. if isinstance(interface, dict):
  421. if len(interface) == 1:
  422. append_dict_profile_to_interface_list(interface)
  423. else:
  424. interfaces.append(interface)
  425. for interface in interfaces:
  426. _normalize_net_types(interface)
  427. _assign_mac(interface)
  428. if hypervisor in overlays:
  429. _apply_default_overlay(interface)
  430. return interfaces
  431. def init(name,
  432. cpu,
  433. mem,
  434. image=None,
  435. nic='default',
  436. hypervisor=VIRT_DEFAULT_HYPER,
  437. start=True, # pylint: disable=redefined-outer-name
  438. disk='default',
  439. saltenv='base',
  440. rng=None,
  441. loader=None,
  442. machine=None,
  443. cpu_mode=None,
  444. cpuset=None,
  445. **kwargs):
  446. '''
  447. Initialize a new vm
  448. CLI Example:
  449. .. code-block:: bash
  450. salt 'hypervisor' virt.init vm_name 4 512 salt://path/to/image.raw
  451. salt 'hypervisor' virt.init vm_name 4 512 nic=profile disk=profile
  452. '''
  453. rng = rng or {'backend':'/dev/urandom'}
  454. hypervisor = __salt__['config.get']('libvirt:hypervisor', hypervisor)
  455. nicp = _nic_profile(nic, hypervisor, **kwargs)
  456. diskp = _disk_profile(disk, hypervisor, **kwargs)
  457. if image:
  458. # Backward compatibility: if 'image' is specified in the VMs arguments
  459. # instead of a disk arguments. In this case, 'image' will be assigned
  460. # to the first disk for the VM.
  461. disk_name = next(diskp[0].iterkeys())
  462. if not diskp[0][disk_name].get('image', None):
  463. diskp[0][disk_name]['image'] = image
  464. # Create multiple disks, empty or from specified images.
  465. cloud_init = None
  466. cfg_drive = None
  467. for disk in diskp:
  468. log.debug("Creating disk for VM [ {0} ]: {1}".format(name, disk))
  469. for disk_name, args in disk.items():
  470. if hypervisor in ['esxi', 'vmware']:
  471. if 'image' in args:
  472. # TODO: we should be copying the image file onto the ESX host
  473. raise SaltInvocationError('virt.init does not support image '
  474. 'template template in conjunction '
  475. 'with esxi hypervisor')
  476. else:
  477. # assume libvirt manages disks for us
  478. xml = _gen_vol_xml(name,
  479. disk_name,
  480. args['size'],
  481. hypervisor,
  482. **kwargs)
  483. define_vol_xml_str(xml)
  484. elif hypervisor in ['qemu', 'kvm']:
  485. disk_type = args['format']
  486. disk_file_name = '{0}.{1}'.format(disk_name, disk_type)
  487. # disk size TCP cloud
  488. disk_size = args['size']
  489. if 'img_dest' in kwargs:
  490. img_dir = kwargs['img_dest']
  491. else:
  492. img_dir = __salt__['config.option']('virt.images')
  493. img_dest = os.path.join(
  494. img_dir,
  495. name,
  496. disk_file_name
  497. )
  498. img_dir = os.path.dirname(img_dest)
  499. if not os.path.isdir(img_dir):
  500. os.makedirs(img_dir)
  501. if 'image' in args:
  502. # Create disk from specified image
  503. sfn = __salt__['cp.cache_file'](args['image'], saltenv)
  504. try:
  505. salt.utils.files.copyfile(sfn, img_dest)
  506. mask = os.umask(0)
  507. os.umask(mask)
  508. # Apply umask and remove exec bit
  509. # Resizing image TCP cloud
  510. cmd = 'qemu-img resize ' + img_dest + ' ' + str(disk_size) + 'M'
  511. subprocess.call(cmd, shell=True)
  512. mode = (0o0777 ^ mask) & 0o0666
  513. os.chmod(img_dest, mode)
  514. except (IOError, OSError) as e:
  515. raise CommandExecutionError('problem while copying image. {0} - {1}'.format(args['image'], e))
  516. if kwargs.get('seed'):
  517. seed_cmd = kwargs.get('seed_cmd', 'seedng.apply')
  518. cloud_init = kwargs.get('cloud_init', None)
  519. master = __salt__['config.option']('master')
  520. cfg_drive = os.path.join(img_dir,'config-2.iso')
  521. if cloud_init:
  522. _tmp = name.split('.')
  523. try:
  524. user_data = json.dumps(cloud_init["user_data"])
  525. except:
  526. user_data = None
  527. try:
  528. network_data = json.dumps(cloud_init["network_data"])
  529. except:
  530. network_data = None
  531. __salt__["cfgdrive.generate"](
  532. dst = cfg_drive,
  533. hostname = _tmp.pop(0),
  534. domainname = '.'.join(_tmp),
  535. user_data = user_data,
  536. network_data = network_data,
  537. saltconfig = { "salt_minion": { "conf": { "master": master, "id": name } } }
  538. )
  539. else:
  540. __salt__[seed_cmd](
  541. path = img_dest,
  542. id_ = name,
  543. config = kwargs.get('config'),
  544. install = kwargs.get('install', True)
  545. )
  546. else:
  547. # Create empty disk
  548. try:
  549. mask = os.umask(0)
  550. os.umask(mask)
  551. # Apply umask and remove exec bit
  552. # Create empty image
  553. cmd = 'qemu-img create -f ' + disk_type + ' ' + img_dest + ' ' + str(disk_size) + 'M'
  554. subprocess.call(cmd, shell=True)
  555. mode = (0o0777 ^ mask) & 0o0666
  556. os.chmod(img_dest, mode)
  557. except (IOError, OSError) as e:
  558. raise CommandExecutionError('problem while creating volume {0} - {1}'.format(img_dest, e))
  559. else:
  560. # Unknown hypervisor
  561. raise SaltInvocationError('Unsupported hypervisor when handling disk image: {0}'
  562. .format(hypervisor))
  563. xml = _gen_xml(name, cpu, mem, diskp, nicp, hypervisor, **kwargs)
  564. if cloud_init and cfg_drive:
  565. xml_doc = minidom.parseString(xml)
  566. iso_xml = xml_doc.createElement("disk")
  567. iso_xml.setAttribute("type", "file")
  568. iso_xml.setAttribute("device", "cdrom")
  569. iso_xml.appendChild(xml_doc.createElement("readonly"))
  570. driver = xml_doc.createElement("driver")
  571. driver.setAttribute("name", "qemu")
  572. driver.setAttribute("type", "raw")
  573. target = xml_doc.createElement("target")
  574. target.setAttribute("dev", "hdc")
  575. target.setAttribute("bus", "ide")
  576. source = xml_doc.createElement("source")
  577. source.setAttribute("file", cfg_drive)
  578. iso_xml.appendChild(driver)
  579. iso_xml.appendChild(target)
  580. iso_xml.appendChild(source)
  581. xml_doc.getElementsByTagName("domain")[0].getElementsByTagName("devices")[0].appendChild(iso_xml)
  582. xml = xml_doc.toxml()
  583. # TODO: Remove this code and refactor module, when salt-common would have updated libvirt_domain.jinja template
  584. if cpuset:
  585. xml_doc = minidom.parseString(xml)
  586. xml_doc.getElementsByTagName("vcpu")[0].setAttribute('cpuset', cpuset)
  587. xml = xml_doc.toxml()
  588. # TODO: Remove this code and refactor module, when salt-common would have updated libvirt_domain.jinja template
  589. if cpu_mode:
  590. xml_doc = minidom.parseString(xml)
  591. cpu_xml = xml_doc.createElement("cpu")
  592. cpu_xml.setAttribute('mode', cpu_mode)
  593. xml_doc.getElementsByTagName("domain")[0].appendChild(cpu_xml)
  594. xml = xml_doc.toxml()
  595. # TODO: Remove this code and refactor module, when salt-common would have updated libvirt_domain.jinja template
  596. if machine:
  597. xml_doc = minidom.parseString(xml)
  598. os_xml = xml_doc.getElementsByTagName("domain")[0].getElementsByTagName("os")[0]
  599. os_xml.getElementsByTagName("type")[0].setAttribute('machine', machine)
  600. xml = xml_doc.toxml()
  601. # TODO: Remove this code and refactor module, when salt-common would have updated libvirt_domain.jinja template
  602. if loader and 'path' not in loader:
  603. log.info('`path` is a required property of `loader`, and cannot be found. Skipping loader configuration')
  604. loader = None
  605. elif loader:
  606. xml_doc = minidom.parseString(xml)
  607. loader_xml = xml_doc.createElement("loader")
  608. for key, val in loader.items():
  609. if key == 'path':
  610. continue
  611. loader_xml.setAttribute(key, val)
  612. loader_path_xml = xml_doc.createTextNode(loader['path'])
  613. loader_xml.appendChild(loader_path_xml)
  614. xml_doc.getElementsByTagName("domain")[0].getElementsByTagName("os")[0].appendChild(loader_xml)
  615. xml = xml_doc.toxml()
  616. # TODO: Remove this code and refactor module, when salt-common would have updated libvirt_domain.jinja template
  617. for _nic in nicp:
  618. if _nic['virtualport']:
  619. xml_doc = minidom.parseString(xml)
  620. interfaces = xml_doc.getElementsByTagName("domain")[0].getElementsByTagName("devices")[0].getElementsByTagName("interface")
  621. for interface in interfaces:
  622. if interface.getElementsByTagName('mac')[0].getAttribute('address').lower() == _nic['mac'].lower():
  623. vport_xml = xml_doc.createElement("virtualport")
  624. vport_xml.setAttribute("type", _nic['virtualport']['type'])
  625. interface.appendChild(vport_xml)
  626. xml = xml_doc.toxml()
  627. # TODO: Remove this code and refactor module, when salt-common would have updated libvirt_domain.jinja template
  628. if rng:
  629. rng_model = rng.get('model', 'random')
  630. rng_backend = rng.get('backend', '/dev/urandom')
  631. xml_doc = minidom.parseString(xml)
  632. rng_xml = xml_doc.createElement("rng")
  633. rng_xml.setAttribute("model", "virtio")
  634. backend = xml_doc.createElement("backend")
  635. backend.setAttribute("model", rng_model)
  636. backend.appendChild(xml_doc.createTextNode(rng_backend))
  637. rng_xml.appendChild(backend)
  638. if 'rate' in rng:
  639. rng_rate_period = rng['rate'].get('period', '2000')
  640. rng_rate_bytes = rng['rate'].get('bytes', '1234')
  641. rate = xml_doc.createElement("rate")
  642. rate.setAttribute("period", rng_rate_period)
  643. rate.setAttribute("bytes", rng_rate_bytes)
  644. rng_xml.appendChild(rate)
  645. xml_doc.getElementsByTagName("domain")[0].getElementsByTagName("devices")[0].appendChild(rng_xml)
  646. xml = xml_doc.toxml()
  647. define_xml_str(xml)
  648. if start:
  649. create(name)
  650. return True
  651. def list_vms():
  652. '''
  653. Return a list of virtual machine names on the minion
  654. CLI Example:
  655. .. code-block:: bash
  656. salt '*' virtng.list_vms
  657. '''
  658. vms = []
  659. vms.extend(list_active_vms())
  660. vms.extend(list_inactive_vms())
  661. return vms
  662. def list_active_vms():
  663. '''
  664. Return a list of names for active virtual machine on the minion
  665. CLI Example:
  666. .. code-block:: bash
  667. salt '*' virtng.list_active_vms
  668. '''
  669. conn = __get_conn()
  670. vms = []
  671. for id_ in conn.listDomainsID():
  672. vms.append(conn.lookupByID(id_).name())
  673. return vms
  674. def list_inactive_vms():
  675. '''
  676. Return a list of names for inactive virtual machine on the minion
  677. CLI Example:
  678. .. code-block:: bash
  679. salt '*' virtng.list_inactive_vms
  680. '''
  681. conn = __get_conn()
  682. vms = []
  683. for id_ in conn.listDefinedDomains():
  684. vms.append(id_)
  685. return vms
  686. def vm_info(vm_=None):
  687. '''
  688. Return detailed information about the vms on this hyper in a
  689. list of dicts:
  690. .. code-block:: python
  691. [
  692. 'your-vm': {
  693. 'cpu': <int>,
  694. 'maxMem': <int>,
  695. 'mem': <int>,
  696. 'state': '<state>',
  697. 'cputime' <int>
  698. },
  699. ...
  700. ]
  701. If you pass a VM name in as an argument then it will return info
  702. for just the named VM, otherwise it will return all VMs.
  703. CLI Example:
  704. .. code-block:: bash
  705. salt '*' virtng.vm_info
  706. '''
  707. def _info(vm_):
  708. dom = _get_dom(vm_)
  709. raw = dom.info()
  710. return {'cpu': raw[3],
  711. 'cputime': int(raw[4]),
  712. 'disks': get_disks(vm_),
  713. 'graphics': get_graphics(vm_),
  714. 'nics': get_nics(vm_),
  715. 'maxMem': int(raw[1]),
  716. 'mem': int(raw[2]),
  717. 'state': VIRT_STATE_NAME_MAP.get(raw[0], 'unknown')}
  718. info = {}
  719. if vm_:
  720. info[vm_] = _info(vm_)
  721. else:
  722. for vm_ in list_vms():
  723. info[vm_] = _info(vm_)
  724. return info
  725. def vm_state(vm_=None):
  726. '''
  727. Return list of all the vms and their state.
  728. If you pass a VM name in as an argument then it will return info
  729. for just the named VM, otherwise it will return all VMs.
  730. CLI Example:
  731. .. code-block:: bash
  732. salt '*' virtng.vm_state <vm name>
  733. '''
  734. def _info(vm_):
  735. state = ''
  736. dom = _get_dom(vm_)
  737. raw = dom.info()
  738. state = VIRT_STATE_NAME_MAP.get(raw[0], 'unknown')
  739. return state
  740. info = {}
  741. if vm_:
  742. info[vm_] = _info(vm_)
  743. else:
  744. for vm_ in list_vms():
  745. info[vm_] = _info(vm_)
  746. return info
  747. def node_info():
  748. '''
  749. Return a dict with information about this node
  750. CLI Example:
  751. .. code-block:: bash
  752. salt '*' virtng.node_info
  753. '''
  754. conn = __get_conn()
  755. raw = conn.getInfo()
  756. info = {'cpucores': raw[6],
  757. 'cpumhz': raw[3],
  758. 'cpumodel': str(raw[0]),
  759. 'cpus': raw[2],
  760. 'cputhreads': raw[7],
  761. 'numanodes': raw[4],
  762. 'phymemory': raw[1],
  763. 'sockets': raw[5]}
  764. return info
  765. def get_nics(vm_):
  766. '''
  767. Return info about the network interfaces of a named vm
  768. CLI Example:
  769. .. code-block:: bash
  770. salt '*' virtng.get_nics <vm name>
  771. '''
  772. nics = {}
  773. doc = minidom.parse(_StringIO(get_xml(vm_)))
  774. for node in doc.getElementsByTagName('devices'):
  775. i_nodes = node.getElementsByTagName('interface')
  776. for i_node in i_nodes:
  777. nic = {}
  778. nic['type'] = i_node.getAttribute('type')
  779. for v_node in i_node.getElementsByTagName('*'):
  780. if v_node.tagName == 'mac':
  781. nic['mac'] = v_node.getAttribute('address')
  782. if v_node.tagName == 'model':
  783. nic['model'] = v_node.getAttribute('type')
  784. if v_node.tagName == 'target':
  785. nic['target'] = v_node.getAttribute('dev')
  786. # driver, source, and match can all have optional attributes
  787. if re.match('(driver|source|address)', v_node.tagName):
  788. temp = {}
  789. for key, value in v_node.attributes.items():
  790. temp[key] = value
  791. nic[str(v_node.tagName)] = temp
  792. # virtualport needs to be handled separately, to pick up the
  793. # type attribute of the virtualport itself
  794. if v_node.tagName == 'virtualport':
  795. temp = {}
  796. temp['type'] = v_node.getAttribute('type')
  797. for key, value in v_node.attributes.items():
  798. temp[key] = value
  799. nic['virtualport'] = temp
  800. if 'mac' not in nic:
  801. continue
  802. nics[nic['mac']] = nic
  803. return nics
  804. def get_macs(vm_):
  805. '''
  806. Return a list off MAC addresses from the named vm
  807. CLI Example:
  808. .. code-block:: bash
  809. salt '*' virtng.get_macs <vm name>
  810. '''
  811. macs = []
  812. doc = minidom.parse(_StringIO(get_xml(vm_)))
  813. for node in doc.getElementsByTagName('devices'):
  814. i_nodes = node.getElementsByTagName('interface')
  815. for i_node in i_nodes:
  816. for v_node in i_node.getElementsByTagName('mac'):
  817. macs.append(v_node.getAttribute('address'))
  818. return macs
  819. def get_graphics(vm_):
  820. '''
  821. Returns the information on vnc for a given vm
  822. CLI Example:
  823. .. code-block:: bash
  824. salt '*' virtng.get_graphics <vm name>
  825. '''
  826. out = {'autoport': 'None',
  827. 'keymap': 'None',
  828. 'listen': 'None',
  829. 'port': 'None',
  830. 'type': 'vnc'}
  831. xml = get_xml(vm_)
  832. ssock = _StringIO(xml)
  833. doc = minidom.parse(ssock)
  834. for node in doc.getElementsByTagName('domain'):
  835. g_nodes = node.getElementsByTagName('graphics')
  836. for g_node in g_nodes:
  837. for key, value in g_node.attributes.items():
  838. out[key] = value
  839. return out
  840. def get_disks(vm_):
  841. '''
  842. Return the disks of a named vm
  843. CLI Example:
  844. .. code-block:: bash
  845. salt '*' virtng.get_disks <vm name>
  846. '''
  847. disks = {}
  848. doc = minidom.parse(_StringIO(get_xml(vm_)))
  849. for elem in doc.getElementsByTagName('disk'):
  850. sources = elem.getElementsByTagName('source')
  851. targets = elem.getElementsByTagName('target')
  852. if len(sources) > 0:
  853. source = sources[0]
  854. else:
  855. continue
  856. if len(targets) > 0:
  857. target = targets[0]
  858. else:
  859. continue
  860. if target.hasAttribute('dev'):
  861. qemu_target = ''
  862. if source.hasAttribute('file'):
  863. qemu_target = source.getAttribute('file')
  864. elif source.hasAttribute('dev'):
  865. qemu_target = source.getAttribute('dev')
  866. elif source.hasAttribute('protocol') and \
  867. source.hasAttribute('name'): # For rbd network
  868. qemu_target = '{0}:{1}'.format(
  869. source.getAttribute('protocol'),
  870. source.getAttribute('name'))
  871. if qemu_target:
  872. disks[target.getAttribute('dev')] = {
  873. 'file': qemu_target}
  874. for dev in disks:
  875. try:
  876. hypervisor = __salt__['config.get']('libvirt:hypervisor', 'kvm')
  877. if hypervisor not in ['qemu', 'kvm']:
  878. break
  879. output = []
  880. qemu_output = subprocess.Popen(['qemu-img', 'info',
  881. disks[dev]['file']],
  882. shell=False,
  883. stdout=subprocess.PIPE).communicate()[0]
  884. snapshots = False
  885. columns = None
  886. lines = qemu_output.strip().split('\n')
  887. for line in lines:
  888. if line.startswith('Snapshot list:'):
  889. snapshots = True
  890. continue
  891. # If this is a copy-on-write image, then the backing file
  892. # represents the base image
  893. #
  894. # backing file: base.qcow2 (actual path: /var/shared/base.qcow2)
  895. elif line.startswith('backing file'):
  896. matches = re.match(r'.*\(actual path: (.*?)\)', line)
  897. if matches:
  898. output.append('backing file: {0}'.format(matches.group(1)))
  899. continue
  900. elif snapshots:
  901. if line.startswith('ID'): # Do not parse table headers
  902. line = line.replace('VM SIZE', 'VMSIZE')
  903. line = line.replace('VM CLOCK', 'TIME VMCLOCK')
  904. columns = re.split(r'\s+', line)
  905. columns = [c.lower() for c in columns]
  906. output.append('snapshots:')
  907. continue
  908. fields = re.split(r'\s+', line)
  909. for i, field in enumerate(fields):
  910. sep = ' '
  911. if i == 0:
  912. sep = '-'
  913. output.append(
  914. '{0} {1}: "{2}"'.format(
  915. sep, columns[i], field
  916. )
  917. )
  918. continue
  919. output.append(line)
  920. output = '\n'.join(output)
  921. disks[dev].update(yaml.safe_load(output))
  922. except TypeError:
  923. disks[dev].update(yaml.safe_load('image: Does not exist'))
  924. return disks
  925. def setmem(vm_, memory, config=False):
  926. '''
  927. Changes the amount of memory allocated to VM. The VM must be shutdown
  928. for this to work.
  929. memory is to be specified in MB
  930. If config is True then we ask libvirt to modify the config as well
  931. CLI Example:
  932. .. code-block:: bash
  933. salt '*' virtng.setmem myvm 768
  934. '''
  935. if vm_state(vm_) != 'shutdown':
  936. return False
  937. dom = _get_dom(vm_)
  938. # libvirt has a funny bitwise system for the flags in that the flag
  939. # to affect the "current" setting is 0, which means that to set the
  940. # current setting we have to call it a second time with just 0 set
  941. flags = libvirt.VIR_DOMAIN_MEM_MAXIMUM
  942. if config:
  943. flags = flags | libvirt.VIR_DOMAIN_AFFECT_CONFIG
  944. ret1 = dom.setMemoryFlags(memory * 1024, flags)
  945. ret2 = dom.setMemoryFlags(memory * 1024, libvirt.VIR_DOMAIN_AFFECT_CURRENT)
  946. # return True if both calls succeeded
  947. return ret1 == ret2 == 0
  948. def setvcpus(vm_, vcpus, config=False):
  949. '''
  950. Changes the amount of vcpus allocated to VM. The VM must be shutdown
  951. for this to work.
  952. vcpus is an int representing the number to be assigned
  953. If config is True then we ask libvirt to modify the config as well
  954. CLI Example:
  955. .. code-block:: bash
  956. salt '*' virtng.setvcpus myvm 2
  957. '''
  958. if vm_state(vm_) != 'shutdown':
  959. return False
  960. dom = _get_dom(vm_)
  961. # see notes in setmem
  962. flags = libvirt.VIR_DOMAIN_VCPU_MAXIMUM
  963. if config:
  964. flags = flags | libvirt.VIR_DOMAIN_AFFECT_CONFIG
  965. ret1 = dom.setVcpusFlags(vcpus, flags)
  966. ret2 = dom.setVcpusFlags(vcpus, libvirt.VIR_DOMAIN_AFFECT_CURRENT)
  967. return ret1 == ret2 == 0
  968. def freemem():
  969. '''
  970. Return an int representing the amount of memory that has not been given
  971. to virtual machines on this node
  972. CLI Example:
  973. .. code-block:: bash
  974. salt '*' virtng.freemem
  975. '''
  976. conn = __get_conn()
  977. mem = conn.getInfo()[1]
  978. # Take off just enough to sustain the hypervisor
  979. mem -= 256
  980. for vm_ in list_vms():
  981. dom = _get_dom(vm_)
  982. if dom.ID() > 0:
  983. mem -= dom.info()[2] / 1024
  984. return mem
  985. def freecpu():
  986. '''
  987. Return an int representing the number of unallocated cpus on this
  988. hypervisor
  989. CLI Example:
  990. .. code-block:: bash
  991. salt '*' virtng.freecpu
  992. '''
  993. conn = __get_conn()
  994. cpus = conn.getInfo()[2]
  995. for vm_ in list_vms():
  996. dom = _get_dom(vm_)
  997. if dom.ID() > 0:
  998. cpus -= dom.info()[3]
  999. return cpus
  1000. def full_info():
  1001. '''
  1002. Return the node_info, vm_info and freemem
  1003. CLI Example:
  1004. .. code-block:: bash
  1005. salt '*' virtng.full_info
  1006. '''
  1007. return {'freecpu': freecpu(),
  1008. 'freemem': freemem(),
  1009. 'node_info': node_info(),
  1010. 'vm_info': vm_info()}
  1011. def get_xml(vm_):
  1012. '''
  1013. Returns the XML for a given vm
  1014. CLI Example:
  1015. .. code-block:: bash
  1016. salt '*' virtng.get_xml <vm name>
  1017. '''
  1018. dom = _get_dom(vm_)
  1019. return dom.XMLDesc(0)
  1020. def get_profiles(hypervisor=None):
  1021. '''
  1022. Return the virt profiles for hypervisor.
  1023. Currently there are profiles for:
  1024. - nic
  1025. - disk
  1026. CLI Example:
  1027. .. code-block:: bash
  1028. salt '*' virtng.get_profiles
  1029. salt '*' virtng.get_profiles hypervisor=esxi
  1030. '''
  1031. ret = {}
  1032. if hypervisor:
  1033. hypervisor = hypervisor
  1034. else:
  1035. hypervisor = __salt__['config.get']('libvirt:hypervisor', VIRT_DEFAULT_HYPER)
  1036. virtconf = __salt__['config.get']('virt', {})
  1037. for typ in ['disk', 'nic']:
  1038. _func = getattr(sys.modules[__name__], '_{0}_profile'.format(typ))
  1039. ret[typ] = {'default': _func('default', hypervisor)}
  1040. if typ in virtconf:
  1041. ret.setdefault(typ, {})
  1042. for prf in virtconf[typ]:
  1043. ret[typ][prf] = _func(prf, hypervisor)
  1044. return ret
  1045. def shutdown(vm_):
  1046. '''
  1047. Send a soft shutdown signal to the named vm
  1048. CLI Example:
  1049. .. code-block:: bash
  1050. salt '*' virtng.shutdown <vm name>
  1051. '''
  1052. dom = _get_dom(vm_)
  1053. return dom.shutdown() == 0
  1054. def pause(vm_):
  1055. '''
  1056. Pause the named vm
  1057. CLI Example:
  1058. .. code-block:: bash
  1059. salt '*' virtng.pause <vm name>
  1060. '''
  1061. dom = _get_dom(vm_)
  1062. return dom.suspend() == 0
  1063. def resume(vm_):
  1064. '''
  1065. Resume the named vm
  1066. CLI Example:
  1067. .. code-block:: bash
  1068. salt '*' virtng.resume <vm name>
  1069. '''
  1070. dom = _get_dom(vm_)
  1071. return dom.resume() == 0
  1072. def create(vm_):
  1073. '''
  1074. Start a defined domain
  1075. CLI Example:
  1076. .. code-block:: bash
  1077. salt '*' virtng.create <vm name>
  1078. '''
  1079. dom = _get_dom(vm_)
  1080. return dom.create() == 0
  1081. def start(vm_):
  1082. '''
  1083. Alias for the obscurely named 'create' function
  1084. CLI Example:
  1085. .. code-block:: bash
  1086. salt '*' virtng.start <vm name>
  1087. '''
  1088. return create(vm_)
  1089. def stop(vm_):
  1090. '''
  1091. Alias for the obscurely named 'destroy' function
  1092. CLI Example:
  1093. .. code-block:: bash
  1094. salt '*' virtng.stop <vm name>
  1095. '''
  1096. return destroy(vm_)
  1097. def reboot(vm_):
  1098. '''
  1099. Reboot a domain via ACPI request
  1100. CLI Example:
  1101. .. code-block:: bash
  1102. salt '*' virtng.reboot <vm name>
  1103. '''
  1104. dom = _get_dom(vm_)
  1105. # reboot has a few modes of operation, passing 0 in means the
  1106. # hypervisor will pick the best method for rebooting
  1107. return dom.reboot(0) == 0
  1108. def reset(vm_):
  1109. '''
  1110. Reset a VM by emulating the reset button on a physical machine
  1111. CLI Example:
  1112. .. code-block:: bash
  1113. salt '*' virtng.reset <vm name>
  1114. '''
  1115. dom = _get_dom(vm_)
  1116. # reset takes a flag, like reboot, but it is not yet used
  1117. # so we just pass in 0
  1118. # see: http://libvirt.org/html/libvirt-libvirt.html#virDomainReset
  1119. return dom.reset(0) == 0
  1120. def ctrl_alt_del(vm_):
  1121. '''
  1122. Sends CTRL+ALT+DEL to a VM
  1123. CLI Example:
  1124. .. code-block:: bash
  1125. salt '*' virtng.ctrl_alt_del <vm name>
  1126. '''
  1127. dom = _get_dom(vm_)
  1128. return dom.sendKey(0, 0, [29, 56, 111], 3, 0) == 0
  1129. def create_xml_str(xml):
  1130. '''
  1131. Start a domain based on the XML passed to the function
  1132. CLI Example:
  1133. .. code-block:: bash
  1134. salt '*' virtng.create_xml_str <XML in string format>
  1135. '''
  1136. conn = __get_conn()
  1137. return conn.createXML(xml, 0) is not None
  1138. def create_xml_path(path):
  1139. '''
  1140. Start a domain based on the XML-file path passed to the function
  1141. CLI Example:
  1142. .. code-block:: bash
  1143. salt '*' virtng.create_xml_path <path to XML file on the node>
  1144. '''
  1145. if not os.path.isfile(path):
  1146. return False
  1147. return create_xml_str(salt.utils.fopen(path, 'r').read())
  1148. def define_xml_str(xml):
  1149. '''
  1150. Define a domain based on the XML passed to the function
  1151. CLI Example:
  1152. .. code-block:: bash
  1153. salt '*' virtng.define_xml_str <XML in string format>
  1154. '''
  1155. conn = __get_conn()
  1156. return conn.defineXML(xml) is not None
  1157. def define_xml_path(path):
  1158. '''
  1159. Define a domain based on the XML-file path passed to the function
  1160. CLI Example:
  1161. .. code-block:: bash
  1162. salt '*' virtng.define_xml_path <path to XML file on the node>
  1163. '''
  1164. if not os.path.isfile(path):
  1165. return False
  1166. return define_xml_str(salt.utils.fopen(path, 'r').read())
  1167. def define_vol_xml_str(xml):
  1168. '''
  1169. Define a volume based on the XML passed to the function
  1170. CLI Example:
  1171. .. code-block:: bash
  1172. salt '*' virtng.define_vol_xml_str <XML in string format>
  1173. '''
  1174. poolname = __salt__['config.get']('libvirt:storagepool', 'default')
  1175. conn = __get_conn()
  1176. pool = conn.storagePoolLookupByName(str(poolname))
  1177. return pool.createXML(xml, 0) is not None
  1178. def define_vol_xml_path(path):
  1179. '''
  1180. Define a volume based on the XML-file path passed to the function
  1181. CLI Example:
  1182. .. code-block:: bash
  1183. salt '*' virtng.define_vol_xml_path <path to XML file on the node>
  1184. '''
  1185. if not os.path.isfile(path):
  1186. return False
  1187. return define_vol_xml_str(salt.utils.fopen(path, 'r').read())
  1188. def migrate_non_shared(vm_, target, ssh=False):
  1189. '''
  1190. Attempt to execute non-shared storage "all" migration
  1191. CLI Example:
  1192. .. code-block:: bash
  1193. salt '*' virtng.migrate_non_shared <vm name> <target hypervisor>
  1194. '''
  1195. cmd = _get_migrate_command() + ' --copy-storage-all ' + vm_\
  1196. + _get_target(target, ssh)
  1197. return subprocess.Popen(cmd,
  1198. shell=True,
  1199. stdout=subprocess.PIPE).communicate()[0]
  1200. def migrate_non_shared_inc(vm_, target, ssh=False):
  1201. '''
  1202. Attempt to execute non-shared storage "all" migration
  1203. CLI Example:
  1204. .. code-block:: bash
  1205. salt '*' virtng.migrate_non_shared_inc <vm name> <target hypervisor>
  1206. '''
  1207. cmd = _get_migrate_command() + ' --copy-storage-inc ' + vm_\
  1208. + _get_target(target, ssh)
  1209. return subprocess.Popen(cmd,
  1210. shell=True,
  1211. stdout=subprocess.PIPE).communicate()[0]
  1212. def migrate(vm_, target, ssh=False):
  1213. '''
  1214. Shared storage migration
  1215. CLI Example:
  1216. .. code-block:: bash
  1217. salt '*' virtng.migrate <vm name> <target hypervisor>
  1218. '''
  1219. cmd = _get_migrate_command() + ' ' + vm_\
  1220. + _get_target(target, ssh)
  1221. return subprocess.Popen(cmd,
  1222. shell=True,
  1223. stdout=subprocess.PIPE).communicate()[0]
  1224. def seed_non_shared_migrate(disks, force=False):
  1225. '''
  1226. Non shared migration requires that the disks be present on the migration
  1227. destination, pass the disks information via this function, to the
  1228. migration destination before executing the migration.
  1229. CLI Example:
  1230. .. code-block:: bash
  1231. salt '*' virtng.seed_non_shared_migrate <disks>
  1232. '''
  1233. for _, data in disks.items():
  1234. fn_ = data['file']
  1235. form = data['file format']
  1236. size = data['virtual size'].split()[1][1:]
  1237. if os.path.isfile(fn_) and not force:
  1238. # the target exists, check to see if it is compatible
  1239. pre = yaml.safe_load(subprocess.Popen('qemu-img info arch',
  1240. shell=True,
  1241. stdout=subprocess.PIPE).communicate()[0])
  1242. if pre['file format'] != data['file format']\
  1243. and pre['virtual size'] != data['virtual size']:
  1244. return False
  1245. if not os.path.isdir(os.path.dirname(fn_)):
  1246. os.makedirs(os.path.dirname(fn_))
  1247. if os.path.isfile(fn_):
  1248. os.remove(fn_)
  1249. cmd = 'qemu-img create -f ' + form + ' ' + fn_ + ' ' + size
  1250. subprocess.call(cmd, shell=True)
  1251. creds = _libvirt_creds()
  1252. cmd = 'chown ' + creds['user'] + ':' + creds['group'] + ' ' + fn_
  1253. subprocess.call(cmd, shell=True)
  1254. return True
  1255. def set_autostart(vm_, state='on'):
  1256. '''
  1257. Set the autostart flag on a VM so that the VM will start with the host
  1258. system on reboot.
  1259. CLI Example:
  1260. .. code-block:: bash
  1261. salt "*" virt.set_autostart <vm name> <on | off>
  1262. '''
  1263. dom = _get_dom(vm_)
  1264. if state == 'on':
  1265. return dom.setAutostart(1) == 0
  1266. elif state == 'off':
  1267. return dom.setAutostart(0) == 0
  1268. else:
  1269. # return False if state is set to something other then on or off
  1270. return False
  1271. def destroy(vm_):
  1272. '''
  1273. Hard power down the virtual machine, this is equivalent to pulling the
  1274. power
  1275. CLI Example:
  1276. .. code-block:: bash
  1277. salt '*' virtng.destroy <vm name>
  1278. '''
  1279. dom = _get_dom(vm_)
  1280. return dom.destroy() == 0
  1281. def undefine(vm_):
  1282. '''
  1283. Remove a defined vm, this does not purge the virtual machine image, and
  1284. this only works if the vm is powered down
  1285. CLI Example:
  1286. .. code-block:: bash
  1287. salt '*' virtng.undefine <vm name>
  1288. '''
  1289. dom = _get_dom(vm_)
  1290. if getattr(libvirt, 'VIR_DOMAIN_UNDEFINE_NVRAM', False):
  1291. # This one is only in 1.2.8+
  1292. return dom.undefineFlags(libvirt.VIR_DOMAIN_UNDEFINE_NVRAM) == 0
  1293. else:
  1294. return dom.undefine() == 0
  1295. def purge(vm_, dirs=False):
  1296. '''
  1297. Recursively destroy and delete a virtual machine, pass True for dir's to
  1298. also delete the directories containing the virtual machine disk images -
  1299. USE WITH EXTREME CAUTION!
  1300. CLI Example:
  1301. .. code-block:: bash
  1302. salt '*' virtng.purge <vm name>
  1303. '''
  1304. disks = get_disks(vm_)
  1305. try:
  1306. if not destroy(vm_):
  1307. return False
  1308. except libvirt.libvirtError:
  1309. # This is thrown if the machine is already shut down
  1310. pass
  1311. directories = set()
  1312. for disk in disks:
  1313. os.remove(disks[disk]['file'])
  1314. directories.add(os.path.dirname(disks[disk]['file']))
  1315. if dirs:
  1316. for dir_ in directories:
  1317. shutil.rmtree(dir_)
  1318. undefine(vm_)
  1319. return True
  1320. def virt_type():
  1321. '''
  1322. Returns the virtual machine type as a string
  1323. CLI Example:
  1324. .. code-block:: bash
  1325. salt '*' virtng.virt_type
  1326. '''
  1327. return __grains__['virtual']
  1328. def is_kvm_hyper():
  1329. '''
  1330. Returns a bool whether or not this node is a KVM hypervisor
  1331. CLI Example:
  1332. .. code-block:: bash
  1333. salt '*' virtng.is_kvm_hyper
  1334. '''
  1335. try:
  1336. if 'kvm_' not in salt.utils.fopen('/proc/modules').read():
  1337. return False
  1338. except IOError:
  1339. # No /proc/modules? Are we on Windows? Or Solaris?
  1340. return False
  1341. return 'libvirtd' in __salt__['cmd.shell'](__grains__['ps'])
  1342. def is_xen_hyper():
  1343. '''
  1344. Returns a bool whether or not this node is a XEN hypervisor
  1345. CLI Example:
  1346. .. code-block:: bash
  1347. salt '*' virtng.is_xen_hyper
  1348. '''
  1349. try:
  1350. if __grains__['virtual_subtype'] != 'Xen Dom0':
  1351. return False
  1352. except KeyError:
  1353. # virtual_subtype isn't set everywhere.
  1354. return False
  1355. try:
  1356. if 'xen_' not in salt.utils.fopen('/proc/modules').read():
  1357. return False
  1358. except IOError:
  1359. # No /proc/modules? Are we on Windows? Or Solaris?
  1360. return False
  1361. return 'libvirtd' in __salt__['cmd.shell'](__grains__['ps'])
  1362. def is_hyper():
  1363. '''
  1364. Returns a bool whether or not this node is a hypervisor of any kind
  1365. CLI Example:
  1366. .. code-block:: bash
  1367. salt '*' virtng.is_hyper
  1368. '''
  1369. try:
  1370. import libvirt # pylint: disable=import-error
  1371. except ImportError:
  1372. # not a usable hypervisor without libvirt module
  1373. return False
  1374. return is_xen_hyper() or is_kvm_hyper()
  1375. def vm_cputime(vm_=None):
  1376. '''
  1377. Return cputime used by the vms on this hyper in a
  1378. list of dicts:
  1379. .. code-block:: python
  1380. [
  1381. 'your-vm': {
  1382. 'cputime' <int>
  1383. 'cputime_percent' <int>
  1384. },
  1385. ...
  1386. ]
  1387. If you pass a VM name in as an argument then it will return info
  1388. for just the named VM, otherwise it will return all VMs.
  1389. CLI Example:
  1390. .. code-block:: bash
  1391. salt '*' virtng.vm_cputime
  1392. '''
  1393. host_cpus = __get_conn().getInfo()[2]
  1394. def _info(vm_):
  1395. dom = _get_dom(vm_)
  1396. raw = dom.info()
  1397. vcpus = int(raw[3])
  1398. cputime = int(raw[4])
  1399. cputime_percent = 0
  1400. if cputime:
  1401. # Divide by vcpus to always return a number between 0 and 100
  1402. cputime_percent = (1.0e-7 * cputime / host_cpus) / vcpus
  1403. return {
  1404. 'cputime': int(raw[4]),
  1405. 'cputime_percent': int('{0:.0f}'.format(cputime_percent))
  1406. }
  1407. info = {}
  1408. if vm_:
  1409. info[vm_] = _info(vm_)
  1410. else:
  1411. for vm_ in list_vms():
  1412. info[vm_] = _info(vm_)
  1413. return info
  1414. def vm_netstats(vm_=None):
  1415. '''
  1416. Return combined network counters used by the vms on this hyper in a
  1417. list of dicts:
  1418. .. code-block:: python
  1419. [
  1420. 'your-vm': {
  1421. 'rx_bytes' : 0,
  1422. 'rx_packets' : 0,
  1423. 'rx_errs' : 0,
  1424. 'rx_drop' : 0,
  1425. 'tx_bytes' : 0,
  1426. 'tx_packets' : 0,
  1427. 'tx_errs' : 0,
  1428. 'tx_drop' : 0
  1429. },
  1430. ...
  1431. ]
  1432. If you pass a VM name in as an argument then it will return info
  1433. for just the named VM, otherwise it will return all VMs.
  1434. CLI Example:
  1435. .. code-block:: bash
  1436. salt '*' virtng.vm_netstats
  1437. '''
  1438. def _info(vm_):
  1439. dom = _get_dom(vm_)
  1440. nics = get_nics(vm_)
  1441. ret = {
  1442. 'rx_bytes': 0,
  1443. 'rx_packets': 0,
  1444. 'rx_errs': 0,
  1445. 'rx_drop': 0,
  1446. 'tx_bytes': 0,
  1447. 'tx_packets': 0,
  1448. 'tx_errs': 0,
  1449. 'tx_drop': 0
  1450. }
  1451. for attrs in six.itervalues(nics):
  1452. if 'target' in attrs:
  1453. dev = attrs['target']
  1454. stats = dom.interfaceStats(dev)
  1455. ret['rx_bytes'] += stats[0]
  1456. ret['rx_packets'] += stats[1]
  1457. ret['rx_errs'] += stats[2]
  1458. ret['rx_drop'] += stats[3]
  1459. ret['tx_bytes'] += stats[4]
  1460. ret['tx_packets'] += stats[5]
  1461. ret['tx_errs'] += stats[6]
  1462. ret['tx_drop'] += stats[7]
  1463. return ret
  1464. info = {}
  1465. if vm_:
  1466. info[vm_] = _info(vm_)
  1467. else:
  1468. for vm_ in list_vms():
  1469. info[vm_] = _info(vm_)
  1470. return info
  1471. def vm_diskstats(vm_=None):
  1472. '''
  1473. Return disk usage counters used by the vms on this hyper in a
  1474. list of dicts:
  1475. .. code-block:: python
  1476. [
  1477. 'your-vm': {
  1478. 'rd_req' : 0,
  1479. 'rd_bytes' : 0,
  1480. 'wr_req' : 0,
  1481. 'wr_bytes' : 0,
  1482. 'errs' : 0
  1483. },
  1484. ...
  1485. ]
  1486. If you pass a VM name in as an argument then it will return info
  1487. for just the named VM, otherwise it will return all VMs.
  1488. CLI Example:
  1489. .. code-block:: bash
  1490. salt '*' virtng.vm_blockstats
  1491. '''
  1492. def get_disk_devs(vm_):
  1493. doc = minidom.parse(_StringIO(get_xml(vm_)))
  1494. disks = []
  1495. for elem in doc.getElementsByTagName('disk'):
  1496. targets = elem.getElementsByTagName('target')
  1497. target = targets[0]
  1498. disks.append(target.getAttribute('dev'))
  1499. return disks
  1500. def _info(vm_):
  1501. dom = _get_dom(vm_)
  1502. # Do not use get_disks, since it uses qemu-img and is very slow
  1503. # and unsuitable for any sort of real time statistics
  1504. disks = get_disk_devs(vm_)
  1505. ret = {'rd_req': 0,
  1506. 'rd_bytes': 0,
  1507. 'wr_req': 0,
  1508. 'wr_bytes': 0,
  1509. 'errs': 0
  1510. }
  1511. for disk in disks:
  1512. stats = dom.blockStats(disk)
  1513. ret['rd_req'] += stats[0]
  1514. ret['rd_bytes'] += stats[1]
  1515. ret['wr_req'] += stats[2]
  1516. ret['wr_bytes'] += stats[3]
  1517. ret['errs'] += stats[4]
  1518. return ret
  1519. info = {}
  1520. if vm_:
  1521. info[vm_] = _info(vm_)
  1522. else:
  1523. # Can not run function blockStats on inactive VMs
  1524. for vm_ in list_active_vms():
  1525. info[vm_] = _info(vm_)
  1526. return info