New Saltstack Salt formula
Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

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