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