New Saltstack Salt formula
Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

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