New version of salt-formula from Saltstack
Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

virtng.py 49KB

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