blob: 304a6bbf9f1b7b57e9fc2b5f1108b63cce1e5eed [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 = {}
Oleh Hryhorov7c5dfd32018-01-19 15:43:44 +0200316 out = __salt__['cmd.shell']('qemu-img info {0}'.format(path))
smolaon1fb381d2016-03-09 11:10:58 +0100317
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 '''
Oleh Hryhorov7c5dfd32018-01-19 15:43:44 +0200335 out = __salt__['cmd.shell']('qemu-img info {0}'.format(vda))
smolaon1fb381d2016-03-09 11:10:58 +0100336 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')
Mateusz Los4c7cd2d2018-01-09 11:46:07 +0100357 if 'img_dest' in kwargs:
358 ret['pool'] = kwargs['img_dest']
359 else:
360 ret['pool'] = __salt__['config.option']('virt.images')
smolaon1fb381d2016-03-09 11:10:58 +0100361 return ret
362
363
364def _disk_profile(profile, hypervisor, **kwargs):
365 '''
366 Gather the disk profile from the config or apply the default based
367 on the active hypervisor
368
369 This is the ``default`` profile for KVM/QEMU, which can be
370 overridden in the configuration:
371
372 .. code-block:: yaml
373
374 virt:
375 disk:
376 default:
377 - system:
378 size: 8192
379 format: qcow2
380 model: virtio
381
Dennis Dmitrieva57463c2017-03-24 17:05:22 +0200382 Example profile for KVM/QEMU with two disks, first is created
383 from specified image, the second is empty:
384
385 .. code-block:: yaml
386
387 virt:
388 disk:
389 two_disks:
390 - system:
391 size: 8192
392 format: qcow2
393 model: virtio
394 image: http://path/to/image.qcow2
395 - lvm:
396 size: 32768
397 format: qcow2
398 model: virtio
399
smolaon1fb381d2016-03-09 11:10:58 +0100400 The ``format`` and ``model`` parameters are optional, and will
401 default to whatever is best suitable for the active hypervisor.
402 '''
403 default = [
404 {'system':
405 {'size': '8192'}
406 }
407 ]
408 if hypervisor in ['esxi', 'vmware']:
409 overlay = {'format': 'vmdk',
410 'model': 'scsi',
411 'pool': '[{0}] '.format(kwargs.get('pool', '0'))
412 }
413 elif hypervisor in ['qemu', 'kvm']:
Mateusz Los4c7cd2d2018-01-09 11:46:07 +0100414 if 'img_dest' in kwargs:
415 pool = kwargs['img_dest']
416 else:
417 pool = __salt__['config.option']('virt.images')
418 overlay = {'format': 'qcow2', 'model': 'virtio', 'pool': pool}
smolaon1fb381d2016-03-09 11:10:58 +0100419 else:
420 overlay = {}
421
Dennis Dmitrievf5dba8c2017-10-10 19:05:20 +0300422 disklist = copy.deepcopy(__salt__['config.get']('virt:disk', {}).get(profile, default))
smolaon1fb381d2016-03-09 11:10:58 +0100423 for key, val in overlay.items():
424 for i, disks in enumerate(disklist):
425 for disk in disks:
426 if key not in disks[disk]:
427 disklist[i][disk][key] = val
428 return disklist
429
430
431def _nic_profile(profile_name, hypervisor, **kwargs):
432
433 default = [{'eth0': {}}]
434 vmware_overlay = {'type': 'bridge', 'source': 'DEFAULT', 'model': 'e1000'}
435 kvm_overlay = {'type': 'bridge', 'source': 'br0', 'model': 'virtio'}
436 overlays = {
437 'kvm': kvm_overlay,
438 'qemu': kvm_overlay,
439 'esxi': vmware_overlay,
440 'vmware': vmware_overlay,
441 }
442
443 # support old location
444 config_data = __salt__['config.option']('virt.nic', {}).get(
445 profile_name, None
446 )
447
448 if config_data is None:
449 config_data = __salt__['config.get']('virt:nic', {}).get(
450 profile_name, default
451 )
452
453 interfaces = []
454
455 def append_dict_profile_to_interface_list(profile_dict):
456 for interface_name, attributes in profile_dict.items():
457 attributes['name'] = interface_name
458 interfaces.append(attributes)
459
460 # old style dicts (top-level dicts)
461 #
462 # virt:
463 # nic:
464 # eth0:
465 # bridge: br0
466 # eth1:
467 # network: test_net
468 if isinstance(config_data, dict):
469 append_dict_profile_to_interface_list(config_data)
470
471 # new style lists (may contain dicts)
472 #
473 # virt:
474 # nic:
475 # - eth0:
476 # bridge: br0
477 # - eth1:
478 # network: test_net
479 #
480 # virt:
481 # nic:
482 # - name: eth0
483 # bridge: br0
484 # - name: eth1
485 # network: test_net
486 elif isinstance(config_data, list):
487 for interface in config_data:
488 if isinstance(interface, dict):
489 if len(interface) == 1:
490 append_dict_profile_to_interface_list(interface)
491 else:
492 interfaces.append(interface)
493
494 def _normalize_net_types(attributes):
495 '''
496 Guess which style of definition:
497
498 bridge: br0
499
500 or
501
502 network: net0
503
504 or
505
506 type: network
507 source: net0
508 '''
509 for type_ in ['bridge', 'network']:
510 if type_ in attributes:
511 attributes['type'] = type_
512 # we want to discard the original key
513 attributes['source'] = attributes.pop(type_)
514
515 attributes['type'] = attributes.get('type', None)
516 attributes['source'] = attributes.get('source', None)
517
518 def _apply_default_overlay(attributes):
519 for key, value in overlays[hypervisor].items():
520 if key not in attributes or not attributes[key]:
521 attributes[key] = value
522
523 def _assign_mac(attributes):
524 dmac = '{0}_mac'.format(attributes['name'])
525 if dmac in kwargs:
526 dmac = kwargs[dmac]
527 if salt.utils.validate.net.mac(dmac):
528 attributes['mac'] = dmac
529 else:
530 msg = 'Malformed MAC address: {0}'.format(dmac)
531 raise CommandExecutionError(msg)
532 else:
533 attributes['mac'] = salt.utils.gen_mac()
534
535 for interface in interfaces:
536 _normalize_net_types(interface)
537 _assign_mac(interface)
538 if hypervisor in overlays:
539 _apply_default_overlay(interface)
540
541 return interfaces
542
543
544def init(name,
545 cpu,
546 mem,
547 image=None,
548 nic='default',
549 hypervisor=VIRT_DEFAULT_HYPER,
550 start=True, # pylint: disable=redefined-outer-name
551 disk='default',
552 saltenv='base',
553 **kwargs):
554 '''
555 Initialize a new vm
556
557 CLI Example:
558
559 .. code-block:: bash
560
561 salt 'hypervisor' virt.init vm_name 4 512 salt://path/to/image.raw
562 salt 'hypervisor' virt.init vm_name 4 512 nic=profile disk=profile
563 '''
Dennis Dmitrieva57463c2017-03-24 17:05:22 +0200564
smolaon1fb381d2016-03-09 11:10:58 +0100565 hypervisor = __salt__['config.get']('libvirt:hypervisor', hypervisor)
566
567 nicp = _nic_profile(nic, hypervisor, **kwargs)
568
Dennis Dmitrieva57463c2017-03-24 17:05:22 +0200569 diskp = _disk_profile(disk, hypervisor, **kwargs)
570
571 if image:
572 # Backward compatibility: if 'image' is specified in the VMs arguments
573 # instead of a disk arguments. In this case, 'image' will be assigned
574 # to the first disk for the VM.
smolaon1fb381d2016-03-09 11:10:58 +0100575 disk_name = next(diskp[0].iterkeys())
Dennis Dmitrieva57463c2017-03-24 17:05:22 +0200576 if not diskp[0][disk_name].get('image', None):
577 diskp[0][disk_name]['image'] = image
smolaon1fb381d2016-03-09 11:10:58 +0100578
Dennis Dmitrieva57463c2017-03-24 17:05:22 +0200579 # Create multiple disks, empty or from specified images.
580 for disk in diskp:
581 log.debug("Creating disk for VM [ {0} ]: {1}".format(name, disk))
smolaon1fb381d2016-03-09 11:10:58 +0100582
Dennis Dmitrieva57463c2017-03-24 17:05:22 +0200583 for disk_name, args in disk.items():
smolaon1fb381d2016-03-09 11:10:58 +0100584
Dennis Dmitrieva57463c2017-03-24 17:05:22 +0200585 if hypervisor in ['esxi', 'vmware']:
586 if 'image' in args:
587 # TODO: we should be copying the image file onto the ESX host
588 raise SaltInvocationError('virt.init does not support image '
589 'template template in conjunction '
590 'with esxi hypervisor')
591 else:
592 # assume libvirt manages disks for us
smolaon1fb381d2016-03-09 11:10:58 +0100593 xml = _gen_vol_xml(name,
594 disk_name,
595 args['size'],
Mateusz Los4c7cd2d2018-01-09 11:46:07 +0100596 hypervisor,
597 **kwargs)
smolaon1fb381d2016-03-09 11:10:58 +0100598 define_vol_xml_str(xml)
599
Dennis Dmitrieva57463c2017-03-24 17:05:22 +0200600 elif hypervisor in ['qemu', 'kvm']:
601
602 disk_type = args['format']
603 disk_file_name = '{0}.{1}'.format(disk_name, disk_type)
604 # disk size TCP cloud
605 disk_size = args['size']
606
Mateusz Los4c7cd2d2018-01-09 11:46:07 +0100607 if 'img_dest' in kwargs:
608 img_dir = kwargs['img_dest']
609 else:
610 img_dir = __salt__['config.option']('virt.images')
Dennis Dmitrieva57463c2017-03-24 17:05:22 +0200611 img_dest = os.path.join(
612 img_dir,
613 name,
614 disk_file_name
615 )
616 img_dir = os.path.dirname(img_dest)
617 if not os.path.isdir(img_dir):
618 os.makedirs(img_dir)
619
620 if 'image' in args:
621 # Create disk from specified image
622 sfn = __salt__['cp.cache_file'](args['image'], saltenv)
623 try:
624 salt.utils.files.copyfile(sfn, img_dest)
625 mask = os.umask(0)
626 os.umask(mask)
627 # Apply umask and remove exec bit
628
629 # Resizing image TCP cloud
630 cmd = 'qemu-img resize ' + img_dest + ' ' + str(disk_size) + 'M'
631 subprocess.call(cmd, shell=True)
632
633 mode = (0o0777 ^ mask) & 0o0666
634 os.chmod(img_dest, mode)
635
636 except (IOError, OSError) as e:
637 raise CommandExecutionError('problem while copying image. {0} - {1}'.format(args['image'], e))
638
639 if kwargs.get('seed'):
640 install = kwargs.get('install', True)
641 seed_cmd = kwargs.get('seed_cmd', 'seedng.apply')
642
643 __salt__[seed_cmd](img_dest,
644 id_=name,
645 config=kwargs.get('config'),
646 install=install)
647 else:
648 # Create empty disk
649 try:
650 mask = os.umask(0)
651 os.umask(mask)
652 # Apply umask and remove exec bit
653
654 # Create empty image
655 cmd = 'qemu-img create -f ' + disk_type + ' ' + img_dest + ' ' + str(disk_size) + 'M'
656 subprocess.call(cmd, shell=True)
657
658 mode = (0o0777 ^ mask) & 0o0666
659 os.chmod(img_dest, mode)
660
661 except (IOError, OSError) as e:
662 raise CommandExecutionError('problem while creating volume {0} - {1}'.format(img_dest, e))
663
664 else:
665 # Unknown hypervisor
666 raise SaltInvocationError('Unsupported hypervisor when handling disk image: {0}'
667 .format(hypervisor))
668
smolaon1fb381d2016-03-09 11:10:58 +0100669 xml = _gen_xml(name, cpu, mem, diskp, nicp, hypervisor, **kwargs)
670 define_xml_str(xml)
671
smolaon1fb381d2016-03-09 11:10:58 +0100672 if start:
673 create(name)
674
675 return True
676
677
678def list_vms():
679 '''
680 Return a list of virtual machine names on the minion
681
682 CLI Example:
683
684 .. code-block:: bash
685
Ales Komarekf8188332016-03-09 11:32:08 +0100686 salt '*' virtng.list_vms
smolaon1fb381d2016-03-09 11:10:58 +0100687 '''
688 vms = []
689 vms.extend(list_active_vms())
690 vms.extend(list_inactive_vms())
691 return vms
692
693
694def list_active_vms():
695 '''
696 Return a list of names for active virtual machine on the minion
697
698 CLI Example:
699
700 .. code-block:: bash
701
Ales Komarekf8188332016-03-09 11:32:08 +0100702 salt '*' virtng.list_active_vms
smolaon1fb381d2016-03-09 11:10:58 +0100703 '''
704 conn = __get_conn()
705 vms = []
706 for id_ in conn.listDomainsID():
707 vms.append(conn.lookupByID(id_).name())
708 return vms
709
710
711def list_inactive_vms():
712 '''
713 Return a list of names for inactive virtual machine on the minion
714
715 CLI Example:
716
717 .. code-block:: bash
718
Ales Komarekf8188332016-03-09 11:32:08 +0100719 salt '*' virtng.list_inactive_vms
smolaon1fb381d2016-03-09 11:10:58 +0100720 '''
721 conn = __get_conn()
722 vms = []
723 for id_ in conn.listDefinedDomains():
724 vms.append(id_)
725 return vms
726
727
728def vm_info(vm_=None):
729 '''
730 Return detailed information about the vms on this hyper in a
731 list of dicts:
732
733 .. code-block:: python
734
735 [
736 'your-vm': {
737 'cpu': <int>,
738 'maxMem': <int>,
739 'mem': <int>,
740 'state': '<state>',
741 'cputime' <int>
742 },
743 ...
744 ]
745
746 If you pass a VM name in as an argument then it will return info
747 for just the named VM, otherwise it will return all VMs.
748
749 CLI Example:
750
751 .. code-block:: bash
752
Ales Komarekf8188332016-03-09 11:32:08 +0100753 salt '*' virtng.vm_info
smolaon1fb381d2016-03-09 11:10:58 +0100754 '''
755 def _info(vm_):
756 dom = _get_dom(vm_)
757 raw = dom.info()
758 return {'cpu': raw[3],
759 'cputime': int(raw[4]),
760 'disks': get_disks(vm_),
761 'graphics': get_graphics(vm_),
762 'nics': get_nics(vm_),
763 'maxMem': int(raw[1]),
764 'mem': int(raw[2]),
765 'state': VIRT_STATE_NAME_MAP.get(raw[0], 'unknown')}
766 info = {}
767 if vm_:
768 info[vm_] = _info(vm_)
769 else:
770 for vm_ in list_vms():
771 info[vm_] = _info(vm_)
772 return info
773
774
775def vm_state(vm_=None):
776 '''
777 Return list of all the vms and their state.
778
779 If you pass a VM name in as an argument then it will return info
780 for just the named VM, otherwise it will return all VMs.
781
782 CLI Example:
783
784 .. code-block:: bash
785
Ales Komarekf8188332016-03-09 11:32:08 +0100786 salt '*' virtng.vm_state <vm name>
smolaon1fb381d2016-03-09 11:10:58 +0100787 '''
788 def _info(vm_):
789 state = ''
790 dom = _get_dom(vm_)
791 raw = dom.info()
792 state = VIRT_STATE_NAME_MAP.get(raw[0], 'unknown')
793 return state
794 info = {}
795 if vm_:
796 info[vm_] = _info(vm_)
797 else:
798 for vm_ in list_vms():
799 info[vm_] = _info(vm_)
800 return info
801
802
803def node_info():
804 '''
805 Return a dict with information about this node
806
807 CLI Example:
808
809 .. code-block:: bash
810
Ales Komarekf8188332016-03-09 11:32:08 +0100811 salt '*' virtng.node_info
smolaon1fb381d2016-03-09 11:10:58 +0100812 '''
813 conn = __get_conn()
814 raw = conn.getInfo()
815 info = {'cpucores': raw[6],
816 'cpumhz': raw[3],
817 'cpumodel': str(raw[0]),
818 'cpus': raw[2],
819 'cputhreads': raw[7],
820 'numanodes': raw[4],
821 'phymemory': raw[1],
822 'sockets': raw[5]}
823 return info
824
825
826def get_nics(vm_):
827 '''
828 Return info about the network interfaces of a named vm
829
830 CLI Example:
831
832 .. code-block:: bash
833
Ales Komarekf8188332016-03-09 11:32:08 +0100834 salt '*' virtng.get_nics <vm name>
smolaon1fb381d2016-03-09 11:10:58 +0100835 '''
836 nics = {}
837 doc = minidom.parse(_StringIO(get_xml(vm_)))
838 for node in doc.getElementsByTagName('devices'):
839 i_nodes = node.getElementsByTagName('interface')
840 for i_node in i_nodes:
841 nic = {}
842 nic['type'] = i_node.getAttribute('type')
843 for v_node in i_node.getElementsByTagName('*'):
844 if v_node.tagName == 'mac':
845 nic['mac'] = v_node.getAttribute('address')
846 if v_node.tagName == 'model':
847 nic['model'] = v_node.getAttribute('type')
848 if v_node.tagName == 'target':
849 nic['target'] = v_node.getAttribute('dev')
850 # driver, source, and match can all have optional attributes
851 if re.match('(driver|source|address)', v_node.tagName):
852 temp = {}
853 for key, value in v_node.attributes.items():
854 temp[key] = value
855 nic[str(v_node.tagName)] = temp
856 # virtualport needs to be handled separately, to pick up the
857 # type attribute of the virtualport itself
858 if v_node.tagName == 'virtualport':
859 temp = {}
860 temp['type'] = v_node.getAttribute('type')
861 for key, value in v_node.attributes.items():
862 temp[key] = value
863 nic['virtualport'] = temp
864 if 'mac' not in nic:
865 continue
866 nics[nic['mac']] = nic
867 return nics
868
869
870def get_macs(vm_):
871 '''
872 Return a list off MAC addresses from the named vm
873
874 CLI Example:
875
876 .. code-block:: bash
877
Ales Komarekf8188332016-03-09 11:32:08 +0100878 salt '*' virtng.get_macs <vm name>
smolaon1fb381d2016-03-09 11:10:58 +0100879 '''
880 macs = []
881 doc = minidom.parse(_StringIO(get_xml(vm_)))
882 for node in doc.getElementsByTagName('devices'):
883 i_nodes = node.getElementsByTagName('interface')
884 for i_node in i_nodes:
885 for v_node in i_node.getElementsByTagName('mac'):
886 macs.append(v_node.getAttribute('address'))
887 return macs
888
889
890def get_graphics(vm_):
891 '''
892 Returns the information on vnc for a given vm
893
894 CLI Example:
895
896 .. code-block:: bash
897
Ales Komarekf8188332016-03-09 11:32:08 +0100898 salt '*' virtng.get_graphics <vm name>
smolaon1fb381d2016-03-09 11:10:58 +0100899 '''
900 out = {'autoport': 'None',
901 'keymap': 'None',
902 'listen': 'None',
903 'port': 'None',
904 'type': 'vnc'}
905 xml = get_xml(vm_)
906 ssock = _StringIO(xml)
907 doc = minidom.parse(ssock)
908 for node in doc.getElementsByTagName('domain'):
909 g_nodes = node.getElementsByTagName('graphics')
910 for g_node in g_nodes:
911 for key, value in g_node.attributes.items():
912 out[key] = value
913 return out
914
915
916def get_disks(vm_):
917 '''
918 Return the disks of a named vm
919
920 CLI Example:
921
922 .. code-block:: bash
923
Ales Komarekf8188332016-03-09 11:32:08 +0100924 salt '*' virtng.get_disks <vm name>
smolaon1fb381d2016-03-09 11:10:58 +0100925 '''
926 disks = {}
927 doc = minidom.parse(_StringIO(get_xml(vm_)))
928 for elem in doc.getElementsByTagName('disk'):
929 sources = elem.getElementsByTagName('source')
930 targets = elem.getElementsByTagName('target')
931 if len(sources) > 0:
932 source = sources[0]
933 else:
934 continue
935 if len(targets) > 0:
936 target = targets[0]
937 else:
938 continue
939 if target.hasAttribute('dev'):
940 qemu_target = ''
941 if source.hasAttribute('file'):
942 qemu_target = source.getAttribute('file')
943 elif source.hasAttribute('dev'):
944 qemu_target = source.getAttribute('dev')
945 elif source.hasAttribute('protocol') and \
946 source.hasAttribute('name'): # For rbd network
947 qemu_target = '{0}:{1}'.format(
948 source.getAttribute('protocol'),
949 source.getAttribute('name'))
950 if qemu_target:
951 disks[target.getAttribute('dev')] = {
952 'file': qemu_target}
953 for dev in disks:
954 try:
955 hypervisor = __salt__['config.get']('libvirt:hypervisor', 'kvm')
956 if hypervisor not in ['qemu', 'kvm']:
957 break
958
959 output = []
960 qemu_output = subprocess.Popen(['qemu-img', 'info',
961 disks[dev]['file']],
962 shell=False,
963 stdout=subprocess.PIPE).communicate()[0]
964 snapshots = False
965 columns = None
966 lines = qemu_output.strip().split('\n')
967 for line in lines:
968 if line.startswith('Snapshot list:'):
969 snapshots = True
970 continue
971
972 # If this is a copy-on-write image, then the backing file
973 # represents the base image
974 #
975 # backing file: base.qcow2 (actual path: /var/shared/base.qcow2)
976 elif line.startswith('backing file'):
977 matches = re.match(r'.*\(actual path: (.*?)\)', line)
978 if matches:
979 output.append('backing file: {0}'.format(matches.group(1)))
980 continue
981
982 elif snapshots:
983 if line.startswith('ID'): # Do not parse table headers
984 line = line.replace('VM SIZE', 'VMSIZE')
985 line = line.replace('VM CLOCK', 'TIME VMCLOCK')
986 columns = re.split(r'\s+', line)
987 columns = [c.lower() for c in columns]
988 output.append('snapshots:')
989 continue
990 fields = re.split(r'\s+', line)
991 for i, field in enumerate(fields):
992 sep = ' '
993 if i == 0:
994 sep = '-'
995 output.append(
996 '{0} {1}: "{2}"'.format(
997 sep, columns[i], field
998 )
999 )
1000 continue
1001 output.append(line)
1002 output = '\n'.join(output)
1003 disks[dev].update(yaml.safe_load(output))
1004 except TypeError:
1005 disks[dev].update(yaml.safe_load('image: Does not exist'))
1006 return disks
1007
1008
1009def setmem(vm_, memory, config=False):
1010 '''
1011 Changes the amount of memory allocated to VM. The VM must be shutdown
1012 for this to work.
1013
1014 memory is to be specified in MB
1015 If config is True then we ask libvirt to modify the config as well
1016
1017 CLI Example:
1018
1019 .. code-block:: bash
1020
Ales Komarekf8188332016-03-09 11:32:08 +01001021 salt '*' virtng.setmem myvm 768
smolaon1fb381d2016-03-09 11:10:58 +01001022 '''
1023 if vm_state(vm_) != 'shutdown':
1024 return False
1025
1026 dom = _get_dom(vm_)
1027
1028 # libvirt has a funny bitwise system for the flags in that the flag
1029 # to affect the "current" setting is 0, which means that to set the
1030 # current setting we have to call it a second time with just 0 set
1031 flags = libvirt.VIR_DOMAIN_MEM_MAXIMUM
1032 if config:
1033 flags = flags | libvirt.VIR_DOMAIN_AFFECT_CONFIG
1034
1035 ret1 = dom.setMemoryFlags(memory * 1024, flags)
1036 ret2 = dom.setMemoryFlags(memory * 1024, libvirt.VIR_DOMAIN_AFFECT_CURRENT)
1037
1038 # return True if both calls succeeded
1039 return ret1 == ret2 == 0
1040
1041
1042def setvcpus(vm_, vcpus, config=False):
1043 '''
1044 Changes the amount of vcpus allocated to VM. The VM must be shutdown
1045 for this to work.
1046
1047 vcpus is an int representing the number to be assigned
1048 If config is True then we ask libvirt to modify the config as well
1049
1050 CLI Example:
1051
1052 .. code-block:: bash
1053
Ales Komarekf8188332016-03-09 11:32:08 +01001054 salt '*' virtng.setvcpus myvm 2
smolaon1fb381d2016-03-09 11:10:58 +01001055 '''
1056 if vm_state(vm_) != 'shutdown':
1057 return False
1058
1059 dom = _get_dom(vm_)
1060
1061 # see notes in setmem
1062 flags = libvirt.VIR_DOMAIN_VCPU_MAXIMUM
1063 if config:
1064 flags = flags | libvirt.VIR_DOMAIN_AFFECT_CONFIG
1065
1066 ret1 = dom.setVcpusFlags(vcpus, flags)
1067 ret2 = dom.setVcpusFlags(vcpus, libvirt.VIR_DOMAIN_AFFECT_CURRENT)
1068
1069 return ret1 == ret2 == 0
1070
1071
1072def freemem():
1073 '''
1074 Return an int representing the amount of memory that has not been given
1075 to virtual machines on this node
1076
1077 CLI Example:
1078
1079 .. code-block:: bash
1080
Ales Komarekf8188332016-03-09 11:32:08 +01001081 salt '*' virtng.freemem
smolaon1fb381d2016-03-09 11:10:58 +01001082 '''
1083 conn = __get_conn()
1084 mem = conn.getInfo()[1]
1085 # Take off just enough to sustain the hypervisor
1086 mem -= 256
1087 for vm_ in list_vms():
1088 dom = _get_dom(vm_)
1089 if dom.ID() > 0:
1090 mem -= dom.info()[2] / 1024
1091 return mem
1092
1093
1094def freecpu():
1095 '''
1096 Return an int representing the number of unallocated cpus on this
1097 hypervisor
1098
1099 CLI Example:
1100
1101 .. code-block:: bash
1102
Ales Komarekf8188332016-03-09 11:32:08 +01001103 salt '*' virtng.freecpu
smolaon1fb381d2016-03-09 11:10:58 +01001104 '''
1105 conn = __get_conn()
1106 cpus = conn.getInfo()[2]
1107 for vm_ in list_vms():
1108 dom = _get_dom(vm_)
1109 if dom.ID() > 0:
1110 cpus -= dom.info()[3]
1111 return cpus
1112
1113
1114def full_info():
1115 '''
1116 Return the node_info, vm_info and freemem
1117
1118 CLI Example:
1119
1120 .. code-block:: bash
1121
Ales Komarekf8188332016-03-09 11:32:08 +01001122 salt '*' virtng.full_info
smolaon1fb381d2016-03-09 11:10:58 +01001123 '''
1124 return {'freecpu': freecpu(),
1125 'freemem': freemem(),
1126 'node_info': node_info(),
1127 'vm_info': vm_info()}
1128
1129
1130def get_xml(vm_):
1131 '''
1132 Returns the XML for a given vm
1133
1134 CLI Example:
1135
1136 .. code-block:: bash
1137
Ales Komarekf8188332016-03-09 11:32:08 +01001138 salt '*' virtng.get_xml <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001139 '''
1140 dom = _get_dom(vm_)
1141 return dom.XMLDesc(0)
1142
1143
1144def get_profiles(hypervisor=None):
1145 '''
1146 Return the virt profiles for hypervisor.
1147
1148 Currently there are profiles for:
1149
1150 - nic
1151 - disk
1152
1153 CLI Example:
1154
1155 .. code-block:: bash
1156
Ales Komarekf8188332016-03-09 11:32:08 +01001157 salt '*' virtng.get_profiles
1158 salt '*' virtng.get_profiles hypervisor=esxi
smolaon1fb381d2016-03-09 11:10:58 +01001159 '''
1160 ret = {}
1161 if hypervisor:
1162 hypervisor = hypervisor
1163 else:
1164 hypervisor = __salt__['config.get']('libvirt:hypervisor', VIRT_DEFAULT_HYPER)
1165 virtconf = __salt__['config.get']('virt', {})
1166 for typ in ['disk', 'nic']:
1167 _func = getattr(sys.modules[__name__], '_{0}_profile'.format(typ))
1168 ret[typ] = {'default': _func('default', hypervisor)}
1169 if typ in virtconf:
1170 ret.setdefault(typ, {})
1171 for prf in virtconf[typ]:
1172 ret[typ][prf] = _func(prf, hypervisor)
1173 return ret
1174
1175
1176def shutdown(vm_):
1177 '''
1178 Send a soft shutdown signal to the named vm
1179
1180 CLI Example:
1181
1182 .. code-block:: bash
1183
Ales Komarekf8188332016-03-09 11:32:08 +01001184 salt '*' virtng.shutdown <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001185 '''
1186 dom = _get_dom(vm_)
1187 return dom.shutdown() == 0
1188
1189
1190def pause(vm_):
1191 '''
1192 Pause the named vm
1193
1194 CLI Example:
1195
1196 .. code-block:: bash
1197
Ales Komarekf8188332016-03-09 11:32:08 +01001198 salt '*' virtng.pause <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001199 '''
1200 dom = _get_dom(vm_)
1201 return dom.suspend() == 0
1202
1203
1204def resume(vm_):
1205 '''
1206 Resume the named vm
1207
1208 CLI Example:
1209
1210 .. code-block:: bash
1211
Ales Komarekf8188332016-03-09 11:32:08 +01001212 salt '*' virtng.resume <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001213 '''
1214 dom = _get_dom(vm_)
1215 return dom.resume() == 0
1216
1217
1218def create(vm_):
1219 '''
1220 Start a defined domain
1221
1222 CLI Example:
1223
1224 .. code-block:: bash
1225
Ales Komarekf8188332016-03-09 11:32:08 +01001226 salt '*' virtng.create <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001227 '''
1228 dom = _get_dom(vm_)
1229 return dom.create() == 0
1230
1231
1232def start(vm_):
1233 '''
1234 Alias for the obscurely named 'create' function
1235
1236 CLI Example:
1237
1238 .. code-block:: bash
1239
Ales Komarekf8188332016-03-09 11:32:08 +01001240 salt '*' virtng.start <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001241 '''
1242 return create(vm_)
1243
1244
1245def stop(vm_):
1246 '''
1247 Alias for the obscurely named 'destroy' function
1248
1249 CLI Example:
1250
1251 .. code-block:: bash
1252
Ales Komarekf8188332016-03-09 11:32:08 +01001253 salt '*' virtng.stop <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001254 '''
1255 return destroy(vm_)
1256
1257
1258def reboot(vm_):
1259 '''
1260 Reboot a domain via ACPI request
1261
1262 CLI Example:
1263
1264 .. code-block:: bash
1265
Ales Komarekf8188332016-03-09 11:32:08 +01001266 salt '*' virtng.reboot <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001267 '''
1268 dom = _get_dom(vm_)
1269
1270 # reboot has a few modes of operation, passing 0 in means the
1271 # hypervisor will pick the best method for rebooting
1272 return dom.reboot(0) == 0
1273
1274
1275def reset(vm_):
1276 '''
1277 Reset a VM by emulating the reset button on a physical machine
1278
1279 CLI Example:
1280
1281 .. code-block:: bash
1282
Ales Komarekf8188332016-03-09 11:32:08 +01001283 salt '*' virtng.reset <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001284 '''
1285 dom = _get_dom(vm_)
1286
1287 # reset takes a flag, like reboot, but it is not yet used
1288 # so we just pass in 0
1289 # see: http://libvirt.org/html/libvirt-libvirt.html#virDomainReset
1290 return dom.reset(0) == 0
1291
1292
1293def ctrl_alt_del(vm_):
1294 '''
1295 Sends CTRL+ALT+DEL to a VM
1296
1297 CLI Example:
1298
1299 .. code-block:: bash
1300
Ales Komarekf8188332016-03-09 11:32:08 +01001301 salt '*' virtng.ctrl_alt_del <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001302 '''
1303 dom = _get_dom(vm_)
1304 return dom.sendKey(0, 0, [29, 56, 111], 3, 0) == 0
1305
1306
1307def create_xml_str(xml):
1308 '''
1309 Start a domain based on the XML passed to the function
1310
1311 CLI Example:
1312
1313 .. code-block:: bash
1314
Ales Komarekf8188332016-03-09 11:32:08 +01001315 salt '*' virtng.create_xml_str <XML in string format>
smolaon1fb381d2016-03-09 11:10:58 +01001316 '''
1317 conn = __get_conn()
1318 return conn.createXML(xml, 0) is not None
1319
1320
1321def create_xml_path(path):
1322 '''
1323 Start a domain based on the XML-file path passed to the function
1324
1325 CLI Example:
1326
1327 .. code-block:: bash
1328
Ales Komarekf8188332016-03-09 11:32:08 +01001329 salt '*' virtng.create_xml_path <path to XML file on the node>
smolaon1fb381d2016-03-09 11:10:58 +01001330 '''
1331 if not os.path.isfile(path):
1332 return False
1333 return create_xml_str(salt.utils.fopen(path, 'r').read())
1334
1335
1336def define_xml_str(xml):
1337 '''
1338 Define a domain based on the XML passed to the function
1339
1340 CLI Example:
1341
1342 .. code-block:: bash
1343
Ales Komarekf8188332016-03-09 11:32:08 +01001344 salt '*' virtng.define_xml_str <XML in string format>
smolaon1fb381d2016-03-09 11:10:58 +01001345 '''
1346 conn = __get_conn()
1347 return conn.defineXML(xml) is not None
1348
1349
1350def define_xml_path(path):
1351 '''
1352 Define a domain based on the XML-file path passed to the function
1353
1354 CLI Example:
1355
1356 .. code-block:: bash
1357
Ales Komarekf8188332016-03-09 11:32:08 +01001358 salt '*' virtng.define_xml_path <path to XML file on the node>
smolaon1fb381d2016-03-09 11:10:58 +01001359
1360 '''
1361 if not os.path.isfile(path):
1362 return False
1363 return define_xml_str(salt.utils.fopen(path, 'r').read())
1364
1365
1366def define_vol_xml_str(xml):
1367 '''
1368 Define a volume based on the XML passed to the function
1369
1370 CLI Example:
1371
1372 .. code-block:: bash
1373
Ales Komarekf8188332016-03-09 11:32:08 +01001374 salt '*' virtng.define_vol_xml_str <XML in string format>
smolaon1fb381d2016-03-09 11:10:58 +01001375 '''
1376 poolname = __salt__['config.get']('libvirt:storagepool', 'default')
1377 conn = __get_conn()
1378 pool = conn.storagePoolLookupByName(str(poolname))
1379 return pool.createXML(xml, 0) is not None
1380
1381
1382def define_vol_xml_path(path):
1383 '''
1384 Define a volume based on the XML-file path passed to the function
1385
1386 CLI Example:
1387
1388 .. code-block:: bash
1389
Ales Komarekf8188332016-03-09 11:32:08 +01001390 salt '*' virtng.define_vol_xml_path <path to XML file on the node>
smolaon1fb381d2016-03-09 11:10:58 +01001391
1392 '''
1393 if not os.path.isfile(path):
1394 return False
1395 return define_vol_xml_str(salt.utils.fopen(path, 'r').read())
1396
1397
1398def migrate_non_shared(vm_, target, ssh=False):
1399 '''
1400 Attempt to execute non-shared storage "all" migration
1401
1402 CLI Example:
1403
1404 .. code-block:: bash
1405
Ales Komarekf8188332016-03-09 11:32:08 +01001406 salt '*' virtng.migrate_non_shared <vm name> <target hypervisor>
smolaon1fb381d2016-03-09 11:10:58 +01001407 '''
1408 cmd = _get_migrate_command() + ' --copy-storage-all ' + vm_\
1409 + _get_target(target, ssh)
1410
1411 return subprocess.Popen(cmd,
1412 shell=True,
1413 stdout=subprocess.PIPE).communicate()[0]
1414
1415
1416def migrate_non_shared_inc(vm_, target, ssh=False):
1417 '''
1418 Attempt to execute non-shared storage "all" migration
1419
1420 CLI Example:
1421
1422 .. code-block:: bash
1423
Ales Komarekf8188332016-03-09 11:32:08 +01001424 salt '*' virtng.migrate_non_shared_inc <vm name> <target hypervisor>
smolaon1fb381d2016-03-09 11:10:58 +01001425 '''
1426 cmd = _get_migrate_command() + ' --copy-storage-inc ' + vm_\
1427 + _get_target(target, ssh)
1428
1429 return subprocess.Popen(cmd,
1430 shell=True,
1431 stdout=subprocess.PIPE).communicate()[0]
1432
1433
1434def migrate(vm_, target, ssh=False):
1435 '''
1436 Shared storage migration
1437
1438 CLI Example:
1439
1440 .. code-block:: bash
1441
Ales Komarekf8188332016-03-09 11:32:08 +01001442 salt '*' virtng.migrate <vm name> <target hypervisor>
smolaon1fb381d2016-03-09 11:10:58 +01001443 '''
1444 cmd = _get_migrate_command() + ' ' + vm_\
1445 + _get_target(target, ssh)
1446
1447 return subprocess.Popen(cmd,
1448 shell=True,
1449 stdout=subprocess.PIPE).communicate()[0]
1450
1451
1452def seed_non_shared_migrate(disks, force=False):
1453 '''
1454 Non shared migration requires that the disks be present on the migration
1455 destination, pass the disks information via this function, to the
1456 migration destination before executing the migration.
1457
1458 CLI Example:
1459
1460 .. code-block:: bash
1461
Ales Komarekf8188332016-03-09 11:32:08 +01001462 salt '*' virtng.seed_non_shared_migrate <disks>
smolaon1fb381d2016-03-09 11:10:58 +01001463 '''
1464 for _, data in disks.items():
1465 fn_ = data['file']
1466 form = data['file format']
1467 size = data['virtual size'].split()[1][1:]
1468 if os.path.isfile(fn_) and not force:
1469 # the target exists, check to see if it is compatible
1470 pre = yaml.safe_load(subprocess.Popen('qemu-img info arch',
1471 shell=True,
1472 stdout=subprocess.PIPE).communicate()[0])
1473 if pre['file format'] != data['file format']\
1474 and pre['virtual size'] != data['virtual size']:
1475 return False
1476 if not os.path.isdir(os.path.dirname(fn_)):
1477 os.makedirs(os.path.dirname(fn_))
1478 if os.path.isfile(fn_):
1479 os.remove(fn_)
1480 cmd = 'qemu-img create -f ' + form + ' ' + fn_ + ' ' + size
1481 subprocess.call(cmd, shell=True)
1482 creds = _libvirt_creds()
1483 cmd = 'chown ' + creds['user'] + ':' + creds['group'] + ' ' + fn_
1484 subprocess.call(cmd, shell=True)
1485 return True
1486
1487
1488def set_autostart(vm_, state='on'):
1489 '''
1490 Set the autostart flag on a VM so that the VM will start with the host
1491 system on reboot.
1492
1493 CLI Example:
1494
1495 .. code-block:: bash
1496
1497 salt "*" virt.set_autostart <vm name> <on | off>
1498 '''
1499
1500 dom = _get_dom(vm_)
1501
1502 if state == 'on':
1503 return dom.setAutostart(1) == 0
1504
1505 elif state == 'off':
1506 return dom.setAutostart(0) == 0
1507
1508 else:
1509 # return False if state is set to something other then on or off
1510 return False
1511
1512
1513def destroy(vm_):
1514 '''
1515 Hard power down the virtual machine, this is equivalent to pulling the
1516 power
1517
1518 CLI Example:
1519
1520 .. code-block:: bash
1521
Ales Komarekf8188332016-03-09 11:32:08 +01001522 salt '*' virtng.destroy <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001523 '''
1524 dom = _get_dom(vm_)
1525 return dom.destroy() == 0
1526
1527
1528def undefine(vm_):
1529 '''
1530 Remove a defined vm, this does not purge the virtual machine image, and
1531 this only works if the vm is powered down
1532
1533 CLI Example:
1534
1535 .. code-block:: bash
1536
Ales Komarekf8188332016-03-09 11:32:08 +01001537 salt '*' virtng.undefine <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001538 '''
1539 dom = _get_dom(vm_)
1540 return dom.undefine() == 0
1541
1542
1543def purge(vm_, dirs=False):
1544 '''
1545 Recursively destroy and delete a virtual machine, pass True for dir's to
1546 also delete the directories containing the virtual machine disk images -
1547 USE WITH EXTREME CAUTION!
1548
1549 CLI Example:
1550
1551 .. code-block:: bash
1552
Ales Komarekf8188332016-03-09 11:32:08 +01001553 salt '*' virtng.purge <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001554 '''
1555 disks = get_disks(vm_)
1556 try:
1557 if not destroy(vm_):
1558 return False
1559 except libvirt.libvirtError:
1560 # This is thrown if the machine is already shut down
1561 pass
1562 directories = set()
1563 for disk in disks:
1564 os.remove(disks[disk]['file'])
1565 directories.add(os.path.dirname(disks[disk]['file']))
1566 if dirs:
1567 for dir_ in directories:
1568 shutil.rmtree(dir_)
1569 undefine(vm_)
1570 return True
1571
1572
1573def virt_type():
1574 '''
1575 Returns the virtual machine type as a string
1576
1577 CLI Example:
1578
1579 .. code-block:: bash
1580
Ales Komarekf8188332016-03-09 11:32:08 +01001581 salt '*' virtng.virt_type
smolaon1fb381d2016-03-09 11:10:58 +01001582 '''
1583 return __grains__['virtual']
1584
1585
1586def is_kvm_hyper():
1587 '''
1588 Returns a bool whether or not this node is a KVM hypervisor
1589
1590 CLI Example:
1591
1592 .. code-block:: bash
1593
Ales Komarekf8188332016-03-09 11:32:08 +01001594 salt '*' virtng.is_kvm_hyper
smolaon1fb381d2016-03-09 11:10:58 +01001595 '''
1596 try:
1597 if 'kvm_' not in salt.utils.fopen('/proc/modules').read():
1598 return False
1599 except IOError:
1600 # No /proc/modules? Are we on Windows? Or Solaris?
1601 return False
Oleh Hryhorov7c5dfd32018-01-19 15:43:44 +02001602 return 'libvirtd' in __salt__['cmd.shell'](__grains__['ps'])
smolaon1fb381d2016-03-09 11:10:58 +01001603
1604
1605def is_xen_hyper():
1606 '''
1607 Returns a bool whether or not this node is a XEN hypervisor
1608
1609 CLI Example:
1610
1611 .. code-block:: bash
1612
Ales Komarekf8188332016-03-09 11:32:08 +01001613 salt '*' virtng.is_xen_hyper
smolaon1fb381d2016-03-09 11:10:58 +01001614 '''
1615 try:
1616 if __grains__['virtual_subtype'] != 'Xen Dom0':
1617 return False
1618 except KeyError:
1619 # virtual_subtype isn't set everywhere.
1620 return False
1621 try:
1622 if 'xen_' not in salt.utils.fopen('/proc/modules').read():
1623 return False
1624 except IOError:
1625 # No /proc/modules? Are we on Windows? Or Solaris?
1626 return False
Oleh Hryhorov7c5dfd32018-01-19 15:43:44 +02001627 return 'libvirtd' in __salt__['cmd.shell'](__grains__['ps'])
smolaon1fb381d2016-03-09 11:10:58 +01001628
1629
1630def is_hyper():
1631 '''
1632 Returns a bool whether or not this node is a hypervisor of any kind
1633
1634 CLI Example:
1635
1636 .. code-block:: bash
1637
Ales Komarekf8188332016-03-09 11:32:08 +01001638 salt '*' virtng.is_hyper
smolaon1fb381d2016-03-09 11:10:58 +01001639 '''
1640 try:
1641 import libvirt # pylint: disable=import-error
1642 except ImportError:
1643 # not a usable hypervisor without libvirt module
1644 return False
1645 return is_xen_hyper() or is_kvm_hyper()
1646
1647
1648def vm_cputime(vm_=None):
1649 '''
1650 Return cputime used by the vms on this hyper in a
1651 list of dicts:
1652
1653 .. code-block:: python
1654
1655 [
1656 'your-vm': {
1657 'cputime' <int>
1658 'cputime_percent' <int>
1659 },
1660 ...
1661 ]
1662
1663 If you pass a VM name in as an argument then it will return info
1664 for just the named VM, otherwise it will return all VMs.
1665
1666 CLI Example:
1667
1668 .. code-block:: bash
1669
Ales Komarekf8188332016-03-09 11:32:08 +01001670 salt '*' virtng.vm_cputime
smolaon1fb381d2016-03-09 11:10:58 +01001671 '''
1672 host_cpus = __get_conn().getInfo()[2]
1673
1674 def _info(vm_):
1675 dom = _get_dom(vm_)
1676 raw = dom.info()
1677 vcpus = int(raw[3])
1678 cputime = int(raw[4])
1679 cputime_percent = 0
1680 if cputime:
1681 # Divide by vcpus to always return a number between 0 and 100
1682 cputime_percent = (1.0e-7 * cputime / host_cpus) / vcpus
1683 return {
1684 'cputime': int(raw[4]),
1685 'cputime_percent': int('{0:.0f}'.format(cputime_percent))
1686 }
1687 info = {}
1688 if vm_:
1689 info[vm_] = _info(vm_)
1690 else:
1691 for vm_ in list_vms():
1692 info[vm_] = _info(vm_)
1693 return info
1694
1695
1696def vm_netstats(vm_=None):
1697 '''
1698 Return combined network counters used by the vms on this hyper in a
1699 list of dicts:
1700
1701 .. code-block:: python
1702
1703 [
1704 'your-vm': {
1705 'rx_bytes' : 0,
1706 'rx_packets' : 0,
1707 'rx_errs' : 0,
1708 'rx_drop' : 0,
1709 'tx_bytes' : 0,
1710 'tx_packets' : 0,
1711 'tx_errs' : 0,
1712 'tx_drop' : 0
1713 },
1714 ...
1715 ]
1716
1717 If you pass a VM name in as an argument then it will return info
1718 for just the named VM, otherwise it will return all VMs.
1719
1720 CLI Example:
1721
1722 .. code-block:: bash
1723
Ales Komarekf8188332016-03-09 11:32:08 +01001724 salt '*' virtng.vm_netstats
smolaon1fb381d2016-03-09 11:10:58 +01001725 '''
1726 def _info(vm_):
1727 dom = _get_dom(vm_)
1728 nics = get_nics(vm_)
1729 ret = {
1730 'rx_bytes': 0,
1731 'rx_packets': 0,
1732 'rx_errs': 0,
1733 'rx_drop': 0,
1734 'tx_bytes': 0,
1735 'tx_packets': 0,
1736 'tx_errs': 0,
1737 'tx_drop': 0
1738 }
1739 for attrs in six.itervalues(nics):
1740 if 'target' in attrs:
1741 dev = attrs['target']
1742 stats = dom.interfaceStats(dev)
1743 ret['rx_bytes'] += stats[0]
1744 ret['rx_packets'] += stats[1]
1745 ret['rx_errs'] += stats[2]
1746 ret['rx_drop'] += stats[3]
1747 ret['tx_bytes'] += stats[4]
1748 ret['tx_packets'] += stats[5]
1749 ret['tx_errs'] += stats[6]
1750 ret['tx_drop'] += stats[7]
1751
1752 return ret
1753 info = {}
1754 if vm_:
1755 info[vm_] = _info(vm_)
1756 else:
1757 for vm_ in list_vms():
1758 info[vm_] = _info(vm_)
1759 return info
1760
1761
1762def vm_diskstats(vm_=None):
1763 '''
1764 Return disk usage counters used by the vms on this hyper in a
1765 list of dicts:
1766
1767 .. code-block:: python
1768
1769 [
1770 'your-vm': {
1771 'rd_req' : 0,
1772 'rd_bytes' : 0,
1773 'wr_req' : 0,
1774 'wr_bytes' : 0,
1775 'errs' : 0
1776 },
1777 ...
1778 ]
1779
1780 If you pass a VM name in as an argument then it will return info
1781 for just the named VM, otherwise it will return all VMs.
1782
1783 CLI Example:
1784
1785 .. code-block:: bash
1786
Ales Komarekf8188332016-03-09 11:32:08 +01001787 salt '*' virtng.vm_blockstats
smolaon1fb381d2016-03-09 11:10:58 +01001788 '''
1789 def get_disk_devs(vm_):
1790 doc = minidom.parse(_StringIO(get_xml(vm_)))
1791 disks = []
1792 for elem in doc.getElementsByTagName('disk'):
1793 targets = elem.getElementsByTagName('target')
1794 target = targets[0]
1795 disks.append(target.getAttribute('dev'))
1796 return disks
1797
1798 def _info(vm_):
1799 dom = _get_dom(vm_)
1800 # Do not use get_disks, since it uses qemu-img and is very slow
1801 # and unsuitable for any sort of real time statistics
1802 disks = get_disk_devs(vm_)
1803 ret = {'rd_req': 0,
1804 'rd_bytes': 0,
1805 'wr_req': 0,
1806 'wr_bytes': 0,
1807 'errs': 0
1808 }
1809 for disk in disks:
1810 stats = dom.blockStats(disk)
1811 ret['rd_req'] += stats[0]
1812 ret['rd_bytes'] += stats[1]
1813 ret['wr_req'] += stats[2]
1814 ret['wr_bytes'] += stats[3]
1815 ret['errs'] += stats[4]
1816
1817 return ret
1818 info = {}
1819 if vm_:
1820 info[vm_] = _info(vm_)
1821 else:
1822 # Can not run function blockStats on inactive VMs
1823 for vm_ in list_active_vms():
1824 info[vm_] = _info(vm_)
1825 return info