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