New version of salt-formula from Saltstack
Nie możesz wybrać więcej, niż 25 tematów Tematy muszą się zaczynać od litery lub cyfry, mogą zawierać myślniki ('-') i mogą mieć do 35 znaków.

virtng.py 54KB

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