blob: e664f68a3f11cc5cad24f7a976cf38b1ead38b92 [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',
Dzmitry Stremkouski7ee23402018-04-10 00:43:48 +0200553 rng={},
smolaon1fb381d2016-03-09 11:10:58 +0100554 **kwargs):
555 '''
556 Initialize a new vm
557
558 CLI Example:
559
560 .. code-block:: bash
561
562 salt 'hypervisor' virt.init vm_name 4 512 salt://path/to/image.raw
563 salt 'hypervisor' virt.init vm_name 4 512 nic=profile disk=profile
564 '''
Dennis Dmitrieva57463c2017-03-24 17:05:22 +0200565
smolaon1fb381d2016-03-09 11:10:58 +0100566 hypervisor = __salt__['config.get']('libvirt:hypervisor', hypervisor)
567
568 nicp = _nic_profile(nic, hypervisor, **kwargs)
569
Dennis Dmitrieva57463c2017-03-24 17:05:22 +0200570 diskp = _disk_profile(disk, hypervisor, **kwargs)
571
572 if image:
573 # Backward compatibility: if 'image' is specified in the VMs arguments
574 # instead of a disk arguments. In this case, 'image' will be assigned
575 # to the first disk for the VM.
smolaon1fb381d2016-03-09 11:10:58 +0100576 disk_name = next(diskp[0].iterkeys())
Dennis Dmitrieva57463c2017-03-24 17:05:22 +0200577 if not diskp[0][disk_name].get('image', None):
578 diskp[0][disk_name]['image'] = image
smolaon1fb381d2016-03-09 11:10:58 +0100579
Dennis Dmitrieva57463c2017-03-24 17:05:22 +0200580 # Create multiple disks, empty or from specified images.
581 for disk in diskp:
582 log.debug("Creating disk for VM [ {0} ]: {1}".format(name, disk))
smolaon1fb381d2016-03-09 11:10:58 +0100583
Dennis Dmitrieva57463c2017-03-24 17:05:22 +0200584 for disk_name, args in disk.items():
smolaon1fb381d2016-03-09 11:10:58 +0100585
Dennis Dmitrieva57463c2017-03-24 17:05:22 +0200586 if hypervisor in ['esxi', 'vmware']:
587 if 'image' in args:
588 # TODO: we should be copying the image file onto the ESX host
589 raise SaltInvocationError('virt.init does not support image '
590 'template template in conjunction '
591 'with esxi hypervisor')
592 else:
593 # assume libvirt manages disks for us
smolaon1fb381d2016-03-09 11:10:58 +0100594 xml = _gen_vol_xml(name,
595 disk_name,
596 args['size'],
Mateusz Los4c7cd2d2018-01-09 11:46:07 +0100597 hypervisor,
598 **kwargs)
smolaon1fb381d2016-03-09 11:10:58 +0100599 define_vol_xml_str(xml)
600
Dennis Dmitrieva57463c2017-03-24 17:05:22 +0200601 elif hypervisor in ['qemu', 'kvm']:
602
603 disk_type = args['format']
604 disk_file_name = '{0}.{1}'.format(disk_name, disk_type)
605 # disk size TCP cloud
606 disk_size = args['size']
607
Mateusz Los4c7cd2d2018-01-09 11:46:07 +0100608 if 'img_dest' in kwargs:
609 img_dir = kwargs['img_dest']
610 else:
611 img_dir = __salt__['config.option']('virt.images')
Dennis Dmitrieva57463c2017-03-24 17:05:22 +0200612 img_dest = os.path.join(
613 img_dir,
614 name,
615 disk_file_name
616 )
617 img_dir = os.path.dirname(img_dest)
618 if not os.path.isdir(img_dir):
619 os.makedirs(img_dir)
620
621 if 'image' in args:
622 # Create disk from specified image
623 sfn = __salt__['cp.cache_file'](args['image'], saltenv)
624 try:
625 salt.utils.files.copyfile(sfn, img_dest)
626 mask = os.umask(0)
627 os.umask(mask)
628 # Apply umask and remove exec bit
629
630 # Resizing image TCP cloud
631 cmd = 'qemu-img resize ' + img_dest + ' ' + str(disk_size) + 'M'
632 subprocess.call(cmd, shell=True)
633
634 mode = (0o0777 ^ mask) & 0o0666
635 os.chmod(img_dest, mode)
636
637 except (IOError, OSError) as e:
638 raise CommandExecutionError('problem while copying image. {0} - {1}'.format(args['image'], e))
639
640 if kwargs.get('seed'):
641 install = kwargs.get('install', True)
642 seed_cmd = kwargs.get('seed_cmd', 'seedng.apply')
643
644 __salt__[seed_cmd](img_dest,
645 id_=name,
646 config=kwargs.get('config'),
647 install=install)
648 else:
649 # Create empty disk
650 try:
651 mask = os.umask(0)
652 os.umask(mask)
653 # Apply umask and remove exec bit
654
655 # Create empty image
656 cmd = 'qemu-img create -f ' + disk_type + ' ' + img_dest + ' ' + str(disk_size) + 'M'
657 subprocess.call(cmd, shell=True)
658
659 mode = (0o0777 ^ mask) & 0o0666
660 os.chmod(img_dest, mode)
661
662 except (IOError, OSError) as e:
663 raise CommandExecutionError('problem while creating volume {0} - {1}'.format(img_dest, e))
664
665 else:
666 # Unknown hypervisor
667 raise SaltInvocationError('Unsupported hypervisor when handling disk image: {0}'
668 .format(hypervisor))
669
smolaon1fb381d2016-03-09 11:10:58 +0100670 xml = _gen_xml(name, cpu, mem, diskp, nicp, hypervisor, **kwargs)
Dzmitry Stremkouski7ee23402018-04-10 00:43:48 +0200671
672 # TODO: Remove this code and refactor module, when salt-common would have updated libvirt_domain.jinja template
673 if rng:
674 rng_model = rng.get('model', 'random')
675 rng_backend = rng.get('backend', '/dev/urandom')
676 xml_doc = minidom.parseString(xml)
677 rng_xml = xml_doc.createElement("rng")
678 rng_xml.setAttribute("model", "virtio")
679 backend = xml_doc.createElement("backend")
680 backend.setAttribute("model", rng_model)
681 backend.appendChild(xml_doc.createTextNode(rng_backend))
682 rng_xml.appendChild(backend)
683 if 'rate' in rng:
684 rng_rate_period = rng['rate'].get('period', '2000')
685 rng_rate_bytes = rng['rate'].get('bytes', '1234')
686 rate = xml_doc.createElement("rate")
687 rate.setAttribute("period", rng_rate_period)
688 rate.setAttribute("bytes", rng_rate_bytes)
689 rng_xml.appendChild(rate)
690 xml_doc.getElementsByTagName("domain")[0].getElementsByTagName("devices")[0].appendChild(rng_xml)
691 xml = xml_doc.toxml()
692
smolaon1fb381d2016-03-09 11:10:58 +0100693 define_xml_str(xml)
694
smolaon1fb381d2016-03-09 11:10:58 +0100695 if start:
696 create(name)
697
698 return True
699
700
701def list_vms():
702 '''
703 Return a list of virtual machine names on the minion
704
705 CLI Example:
706
707 .. code-block:: bash
708
Ales Komarekf8188332016-03-09 11:32:08 +0100709 salt '*' virtng.list_vms
smolaon1fb381d2016-03-09 11:10:58 +0100710 '''
711 vms = []
712 vms.extend(list_active_vms())
713 vms.extend(list_inactive_vms())
714 return vms
715
716
717def list_active_vms():
718 '''
719 Return a list of names for active virtual machine on the minion
720
721 CLI Example:
722
723 .. code-block:: bash
724
Ales Komarekf8188332016-03-09 11:32:08 +0100725 salt '*' virtng.list_active_vms
smolaon1fb381d2016-03-09 11:10:58 +0100726 '''
727 conn = __get_conn()
728 vms = []
729 for id_ in conn.listDomainsID():
730 vms.append(conn.lookupByID(id_).name())
731 return vms
732
733
734def list_inactive_vms():
735 '''
736 Return a list of names for inactive virtual machine on the minion
737
738 CLI Example:
739
740 .. code-block:: bash
741
Ales Komarekf8188332016-03-09 11:32:08 +0100742 salt '*' virtng.list_inactive_vms
smolaon1fb381d2016-03-09 11:10:58 +0100743 '''
744 conn = __get_conn()
745 vms = []
746 for id_ in conn.listDefinedDomains():
747 vms.append(id_)
748 return vms
749
750
751def vm_info(vm_=None):
752 '''
753 Return detailed information about the vms on this hyper in a
754 list of dicts:
755
756 .. code-block:: python
757
758 [
759 'your-vm': {
760 'cpu': <int>,
761 'maxMem': <int>,
762 'mem': <int>,
763 'state': '<state>',
764 'cputime' <int>
765 },
766 ...
767 ]
768
769 If you pass a VM name in as an argument then it will return info
770 for just the named VM, otherwise it will return all VMs.
771
772 CLI Example:
773
774 .. code-block:: bash
775
Ales Komarekf8188332016-03-09 11:32:08 +0100776 salt '*' virtng.vm_info
smolaon1fb381d2016-03-09 11:10:58 +0100777 '''
778 def _info(vm_):
779 dom = _get_dom(vm_)
780 raw = dom.info()
781 return {'cpu': raw[3],
782 'cputime': int(raw[4]),
783 'disks': get_disks(vm_),
784 'graphics': get_graphics(vm_),
785 'nics': get_nics(vm_),
786 'maxMem': int(raw[1]),
787 'mem': int(raw[2]),
788 'state': VIRT_STATE_NAME_MAP.get(raw[0], 'unknown')}
789 info = {}
790 if vm_:
791 info[vm_] = _info(vm_)
792 else:
793 for vm_ in list_vms():
794 info[vm_] = _info(vm_)
795 return info
796
797
798def vm_state(vm_=None):
799 '''
800 Return list of all the vms and their state.
801
802 If you pass a VM name in as an argument then it will return info
803 for just the named VM, otherwise it will return all VMs.
804
805 CLI Example:
806
807 .. code-block:: bash
808
Ales Komarekf8188332016-03-09 11:32:08 +0100809 salt '*' virtng.vm_state <vm name>
smolaon1fb381d2016-03-09 11:10:58 +0100810 '''
811 def _info(vm_):
812 state = ''
813 dom = _get_dom(vm_)
814 raw = dom.info()
815 state = VIRT_STATE_NAME_MAP.get(raw[0], 'unknown')
816 return state
817 info = {}
818 if vm_:
819 info[vm_] = _info(vm_)
820 else:
821 for vm_ in list_vms():
822 info[vm_] = _info(vm_)
823 return info
824
825
826def node_info():
827 '''
828 Return a dict with information about this node
829
830 CLI Example:
831
832 .. code-block:: bash
833
Ales Komarekf8188332016-03-09 11:32:08 +0100834 salt '*' virtng.node_info
smolaon1fb381d2016-03-09 11:10:58 +0100835 '''
836 conn = __get_conn()
837 raw = conn.getInfo()
838 info = {'cpucores': raw[6],
839 'cpumhz': raw[3],
840 'cpumodel': str(raw[0]),
841 'cpus': raw[2],
842 'cputhreads': raw[7],
843 'numanodes': raw[4],
844 'phymemory': raw[1],
845 'sockets': raw[5]}
846 return info
847
848
849def get_nics(vm_):
850 '''
851 Return info about the network interfaces of a named vm
852
853 CLI Example:
854
855 .. code-block:: bash
856
Ales Komarekf8188332016-03-09 11:32:08 +0100857 salt '*' virtng.get_nics <vm name>
smolaon1fb381d2016-03-09 11:10:58 +0100858 '''
859 nics = {}
860 doc = minidom.parse(_StringIO(get_xml(vm_)))
861 for node in doc.getElementsByTagName('devices'):
862 i_nodes = node.getElementsByTagName('interface')
863 for i_node in i_nodes:
864 nic = {}
865 nic['type'] = i_node.getAttribute('type')
866 for v_node in i_node.getElementsByTagName('*'):
867 if v_node.tagName == 'mac':
868 nic['mac'] = v_node.getAttribute('address')
869 if v_node.tagName == 'model':
870 nic['model'] = v_node.getAttribute('type')
871 if v_node.tagName == 'target':
872 nic['target'] = v_node.getAttribute('dev')
873 # driver, source, and match can all have optional attributes
874 if re.match('(driver|source|address)', v_node.tagName):
875 temp = {}
876 for key, value in v_node.attributes.items():
877 temp[key] = value
878 nic[str(v_node.tagName)] = temp
879 # virtualport needs to be handled separately, to pick up the
880 # type attribute of the virtualport itself
881 if v_node.tagName == 'virtualport':
882 temp = {}
883 temp['type'] = v_node.getAttribute('type')
884 for key, value in v_node.attributes.items():
885 temp[key] = value
886 nic['virtualport'] = temp
887 if 'mac' not in nic:
888 continue
889 nics[nic['mac']] = nic
890 return nics
891
892
893def get_macs(vm_):
894 '''
895 Return a list off MAC addresses from the named vm
896
897 CLI Example:
898
899 .. code-block:: bash
900
Ales Komarekf8188332016-03-09 11:32:08 +0100901 salt '*' virtng.get_macs <vm name>
smolaon1fb381d2016-03-09 11:10:58 +0100902 '''
903 macs = []
904 doc = minidom.parse(_StringIO(get_xml(vm_)))
905 for node in doc.getElementsByTagName('devices'):
906 i_nodes = node.getElementsByTagName('interface')
907 for i_node in i_nodes:
908 for v_node in i_node.getElementsByTagName('mac'):
909 macs.append(v_node.getAttribute('address'))
910 return macs
911
912
913def get_graphics(vm_):
914 '''
915 Returns the information on vnc for a given vm
916
917 CLI Example:
918
919 .. code-block:: bash
920
Ales Komarekf8188332016-03-09 11:32:08 +0100921 salt '*' virtng.get_graphics <vm name>
smolaon1fb381d2016-03-09 11:10:58 +0100922 '''
923 out = {'autoport': 'None',
924 'keymap': 'None',
925 'listen': 'None',
926 'port': 'None',
927 'type': 'vnc'}
928 xml = get_xml(vm_)
929 ssock = _StringIO(xml)
930 doc = minidom.parse(ssock)
931 for node in doc.getElementsByTagName('domain'):
932 g_nodes = node.getElementsByTagName('graphics')
933 for g_node in g_nodes:
934 for key, value in g_node.attributes.items():
935 out[key] = value
936 return out
937
938
939def get_disks(vm_):
940 '''
941 Return the disks of a named vm
942
943 CLI Example:
944
945 .. code-block:: bash
946
Ales Komarekf8188332016-03-09 11:32:08 +0100947 salt '*' virtng.get_disks <vm name>
smolaon1fb381d2016-03-09 11:10:58 +0100948 '''
949 disks = {}
950 doc = minidom.parse(_StringIO(get_xml(vm_)))
951 for elem in doc.getElementsByTagName('disk'):
952 sources = elem.getElementsByTagName('source')
953 targets = elem.getElementsByTagName('target')
954 if len(sources) > 0:
955 source = sources[0]
956 else:
957 continue
958 if len(targets) > 0:
959 target = targets[0]
960 else:
961 continue
962 if target.hasAttribute('dev'):
963 qemu_target = ''
964 if source.hasAttribute('file'):
965 qemu_target = source.getAttribute('file')
966 elif source.hasAttribute('dev'):
967 qemu_target = source.getAttribute('dev')
968 elif source.hasAttribute('protocol') and \
969 source.hasAttribute('name'): # For rbd network
970 qemu_target = '{0}:{1}'.format(
971 source.getAttribute('protocol'),
972 source.getAttribute('name'))
973 if qemu_target:
974 disks[target.getAttribute('dev')] = {
975 'file': qemu_target}
976 for dev in disks:
977 try:
978 hypervisor = __salt__['config.get']('libvirt:hypervisor', 'kvm')
979 if hypervisor not in ['qemu', 'kvm']:
980 break
981
982 output = []
983 qemu_output = subprocess.Popen(['qemu-img', 'info',
984 disks[dev]['file']],
985 shell=False,
986 stdout=subprocess.PIPE).communicate()[0]
987 snapshots = False
988 columns = None
989 lines = qemu_output.strip().split('\n')
990 for line in lines:
991 if line.startswith('Snapshot list:'):
992 snapshots = True
993 continue
994
995 # If this is a copy-on-write image, then the backing file
996 # represents the base image
997 #
998 # backing file: base.qcow2 (actual path: /var/shared/base.qcow2)
999 elif line.startswith('backing file'):
1000 matches = re.match(r'.*\(actual path: (.*?)\)', line)
1001 if matches:
1002 output.append('backing file: {0}'.format(matches.group(1)))
1003 continue
1004
1005 elif snapshots:
1006 if line.startswith('ID'): # Do not parse table headers
1007 line = line.replace('VM SIZE', 'VMSIZE')
1008 line = line.replace('VM CLOCK', 'TIME VMCLOCK')
1009 columns = re.split(r'\s+', line)
1010 columns = [c.lower() for c in columns]
1011 output.append('snapshots:')
1012 continue
1013 fields = re.split(r'\s+', line)
1014 for i, field in enumerate(fields):
1015 sep = ' '
1016 if i == 0:
1017 sep = '-'
1018 output.append(
1019 '{0} {1}: "{2}"'.format(
1020 sep, columns[i], field
1021 )
1022 )
1023 continue
1024 output.append(line)
1025 output = '\n'.join(output)
1026 disks[dev].update(yaml.safe_load(output))
1027 except TypeError:
1028 disks[dev].update(yaml.safe_load('image: Does not exist'))
1029 return disks
1030
1031
1032def setmem(vm_, memory, config=False):
1033 '''
1034 Changes the amount of memory allocated to VM. The VM must be shutdown
1035 for this to work.
1036
1037 memory is to be specified in MB
1038 If config is True then we ask libvirt to modify the config as well
1039
1040 CLI Example:
1041
1042 .. code-block:: bash
1043
Ales Komarekf8188332016-03-09 11:32:08 +01001044 salt '*' virtng.setmem myvm 768
smolaon1fb381d2016-03-09 11:10:58 +01001045 '''
1046 if vm_state(vm_) != 'shutdown':
1047 return False
1048
1049 dom = _get_dom(vm_)
1050
1051 # libvirt has a funny bitwise system for the flags in that the flag
1052 # to affect the "current" setting is 0, which means that to set the
1053 # current setting we have to call it a second time with just 0 set
1054 flags = libvirt.VIR_DOMAIN_MEM_MAXIMUM
1055 if config:
1056 flags = flags | libvirt.VIR_DOMAIN_AFFECT_CONFIG
1057
1058 ret1 = dom.setMemoryFlags(memory * 1024, flags)
1059 ret2 = dom.setMemoryFlags(memory * 1024, libvirt.VIR_DOMAIN_AFFECT_CURRENT)
1060
1061 # return True if both calls succeeded
1062 return ret1 == ret2 == 0
1063
1064
1065def setvcpus(vm_, vcpus, config=False):
1066 '''
1067 Changes the amount of vcpus allocated to VM. The VM must be shutdown
1068 for this to work.
1069
1070 vcpus is an int representing the number to be assigned
1071 If config is True then we ask libvirt to modify the config as well
1072
1073 CLI Example:
1074
1075 .. code-block:: bash
1076
Ales Komarekf8188332016-03-09 11:32:08 +01001077 salt '*' virtng.setvcpus myvm 2
smolaon1fb381d2016-03-09 11:10:58 +01001078 '''
1079 if vm_state(vm_) != 'shutdown':
1080 return False
1081
1082 dom = _get_dom(vm_)
1083
1084 # see notes in setmem
1085 flags = libvirt.VIR_DOMAIN_VCPU_MAXIMUM
1086 if config:
1087 flags = flags | libvirt.VIR_DOMAIN_AFFECT_CONFIG
1088
1089 ret1 = dom.setVcpusFlags(vcpus, flags)
1090 ret2 = dom.setVcpusFlags(vcpus, libvirt.VIR_DOMAIN_AFFECT_CURRENT)
1091
1092 return ret1 == ret2 == 0
1093
1094
1095def freemem():
1096 '''
1097 Return an int representing the amount of memory that has not been given
1098 to virtual machines on this node
1099
1100 CLI Example:
1101
1102 .. code-block:: bash
1103
Ales Komarekf8188332016-03-09 11:32:08 +01001104 salt '*' virtng.freemem
smolaon1fb381d2016-03-09 11:10:58 +01001105 '''
1106 conn = __get_conn()
1107 mem = conn.getInfo()[1]
1108 # Take off just enough to sustain the hypervisor
1109 mem -= 256
1110 for vm_ in list_vms():
1111 dom = _get_dom(vm_)
1112 if dom.ID() > 0:
1113 mem -= dom.info()[2] / 1024
1114 return mem
1115
1116
1117def freecpu():
1118 '''
1119 Return an int representing the number of unallocated cpus on this
1120 hypervisor
1121
1122 CLI Example:
1123
1124 .. code-block:: bash
1125
Ales Komarekf8188332016-03-09 11:32:08 +01001126 salt '*' virtng.freecpu
smolaon1fb381d2016-03-09 11:10:58 +01001127 '''
1128 conn = __get_conn()
1129 cpus = conn.getInfo()[2]
1130 for vm_ in list_vms():
1131 dom = _get_dom(vm_)
1132 if dom.ID() > 0:
1133 cpus -= dom.info()[3]
1134 return cpus
1135
1136
1137def full_info():
1138 '''
1139 Return the node_info, vm_info and freemem
1140
1141 CLI Example:
1142
1143 .. code-block:: bash
1144
Ales Komarekf8188332016-03-09 11:32:08 +01001145 salt '*' virtng.full_info
smolaon1fb381d2016-03-09 11:10:58 +01001146 '''
1147 return {'freecpu': freecpu(),
1148 'freemem': freemem(),
1149 'node_info': node_info(),
1150 'vm_info': vm_info()}
1151
1152
1153def get_xml(vm_):
1154 '''
1155 Returns the XML for a given vm
1156
1157 CLI Example:
1158
1159 .. code-block:: bash
1160
Ales Komarekf8188332016-03-09 11:32:08 +01001161 salt '*' virtng.get_xml <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001162 '''
1163 dom = _get_dom(vm_)
1164 return dom.XMLDesc(0)
1165
1166
1167def get_profiles(hypervisor=None):
1168 '''
1169 Return the virt profiles for hypervisor.
1170
1171 Currently there are profiles for:
1172
1173 - nic
1174 - disk
1175
1176 CLI Example:
1177
1178 .. code-block:: bash
1179
Ales Komarekf8188332016-03-09 11:32:08 +01001180 salt '*' virtng.get_profiles
1181 salt '*' virtng.get_profiles hypervisor=esxi
smolaon1fb381d2016-03-09 11:10:58 +01001182 '''
1183 ret = {}
1184 if hypervisor:
1185 hypervisor = hypervisor
1186 else:
1187 hypervisor = __salt__['config.get']('libvirt:hypervisor', VIRT_DEFAULT_HYPER)
1188 virtconf = __salt__['config.get']('virt', {})
1189 for typ in ['disk', 'nic']:
1190 _func = getattr(sys.modules[__name__], '_{0}_profile'.format(typ))
1191 ret[typ] = {'default': _func('default', hypervisor)}
1192 if typ in virtconf:
1193 ret.setdefault(typ, {})
1194 for prf in virtconf[typ]:
1195 ret[typ][prf] = _func(prf, hypervisor)
1196 return ret
1197
1198
1199def shutdown(vm_):
1200 '''
1201 Send a soft shutdown signal to the named vm
1202
1203 CLI Example:
1204
1205 .. code-block:: bash
1206
Ales Komarekf8188332016-03-09 11:32:08 +01001207 salt '*' virtng.shutdown <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001208 '''
1209 dom = _get_dom(vm_)
1210 return dom.shutdown() == 0
1211
1212
1213def pause(vm_):
1214 '''
1215 Pause the named vm
1216
1217 CLI Example:
1218
1219 .. code-block:: bash
1220
Ales Komarekf8188332016-03-09 11:32:08 +01001221 salt '*' virtng.pause <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001222 '''
1223 dom = _get_dom(vm_)
1224 return dom.suspend() == 0
1225
1226
1227def resume(vm_):
1228 '''
1229 Resume the named vm
1230
1231 CLI Example:
1232
1233 .. code-block:: bash
1234
Ales Komarekf8188332016-03-09 11:32:08 +01001235 salt '*' virtng.resume <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001236 '''
1237 dom = _get_dom(vm_)
1238 return dom.resume() == 0
1239
1240
1241def create(vm_):
1242 '''
1243 Start a defined domain
1244
1245 CLI Example:
1246
1247 .. code-block:: bash
1248
Ales Komarekf8188332016-03-09 11:32:08 +01001249 salt '*' virtng.create <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001250 '''
1251 dom = _get_dom(vm_)
1252 return dom.create() == 0
1253
1254
1255def start(vm_):
1256 '''
1257 Alias for the obscurely named 'create' function
1258
1259 CLI Example:
1260
1261 .. code-block:: bash
1262
Ales Komarekf8188332016-03-09 11:32:08 +01001263 salt '*' virtng.start <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001264 '''
1265 return create(vm_)
1266
1267
1268def stop(vm_):
1269 '''
1270 Alias for the obscurely named 'destroy' function
1271
1272 CLI Example:
1273
1274 .. code-block:: bash
1275
Ales Komarekf8188332016-03-09 11:32:08 +01001276 salt '*' virtng.stop <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001277 '''
1278 return destroy(vm_)
1279
1280
1281def reboot(vm_):
1282 '''
1283 Reboot a domain via ACPI request
1284
1285 CLI Example:
1286
1287 .. code-block:: bash
1288
Ales Komarekf8188332016-03-09 11:32:08 +01001289 salt '*' virtng.reboot <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001290 '''
1291 dom = _get_dom(vm_)
1292
1293 # reboot has a few modes of operation, passing 0 in means the
1294 # hypervisor will pick the best method for rebooting
1295 return dom.reboot(0) == 0
1296
1297
1298def reset(vm_):
1299 '''
1300 Reset a VM by emulating the reset button on a physical machine
1301
1302 CLI Example:
1303
1304 .. code-block:: bash
1305
Ales Komarekf8188332016-03-09 11:32:08 +01001306 salt '*' virtng.reset <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001307 '''
1308 dom = _get_dom(vm_)
1309
1310 # reset takes a flag, like reboot, but it is not yet used
1311 # so we just pass in 0
1312 # see: http://libvirt.org/html/libvirt-libvirt.html#virDomainReset
1313 return dom.reset(0) == 0
1314
1315
1316def ctrl_alt_del(vm_):
1317 '''
1318 Sends CTRL+ALT+DEL to a VM
1319
1320 CLI Example:
1321
1322 .. code-block:: bash
1323
Ales Komarekf8188332016-03-09 11:32:08 +01001324 salt '*' virtng.ctrl_alt_del <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001325 '''
1326 dom = _get_dom(vm_)
1327 return dom.sendKey(0, 0, [29, 56, 111], 3, 0) == 0
1328
1329
1330def create_xml_str(xml):
1331 '''
1332 Start a domain based on the XML passed to the function
1333
1334 CLI Example:
1335
1336 .. code-block:: bash
1337
Ales Komarekf8188332016-03-09 11:32:08 +01001338 salt '*' virtng.create_xml_str <XML in string format>
smolaon1fb381d2016-03-09 11:10:58 +01001339 '''
1340 conn = __get_conn()
1341 return conn.createXML(xml, 0) is not None
1342
1343
1344def create_xml_path(path):
1345 '''
1346 Start a domain based on the XML-file path passed to the function
1347
1348 CLI Example:
1349
1350 .. code-block:: bash
1351
Ales Komarekf8188332016-03-09 11:32:08 +01001352 salt '*' virtng.create_xml_path <path to XML file on the node>
smolaon1fb381d2016-03-09 11:10:58 +01001353 '''
1354 if not os.path.isfile(path):
1355 return False
1356 return create_xml_str(salt.utils.fopen(path, 'r').read())
1357
1358
1359def define_xml_str(xml):
1360 '''
1361 Define a domain based on the XML passed to the function
1362
1363 CLI Example:
1364
1365 .. code-block:: bash
1366
Ales Komarekf8188332016-03-09 11:32:08 +01001367 salt '*' virtng.define_xml_str <XML in string format>
smolaon1fb381d2016-03-09 11:10:58 +01001368 '''
1369 conn = __get_conn()
1370 return conn.defineXML(xml) is not None
1371
1372
1373def define_xml_path(path):
1374 '''
1375 Define a domain 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_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_xml_str(salt.utils.fopen(path, 'r').read())
1387
1388
1389def define_vol_xml_str(xml):
1390 '''
1391 Define a volume based on the XML passed to the function
1392
1393 CLI Example:
1394
1395 .. code-block:: bash
1396
Ales Komarekf8188332016-03-09 11:32:08 +01001397 salt '*' virtng.define_vol_xml_str <XML in string format>
smolaon1fb381d2016-03-09 11:10:58 +01001398 '''
1399 poolname = __salt__['config.get']('libvirt:storagepool', 'default')
1400 conn = __get_conn()
1401 pool = conn.storagePoolLookupByName(str(poolname))
1402 return pool.createXML(xml, 0) is not None
1403
1404
1405def define_vol_xml_path(path):
1406 '''
1407 Define a volume based on the XML-file path passed to the function
1408
1409 CLI Example:
1410
1411 .. code-block:: bash
1412
Ales Komarekf8188332016-03-09 11:32:08 +01001413 salt '*' virtng.define_vol_xml_path <path to XML file on the node>
smolaon1fb381d2016-03-09 11:10:58 +01001414
1415 '''
1416 if not os.path.isfile(path):
1417 return False
1418 return define_vol_xml_str(salt.utils.fopen(path, 'r').read())
1419
1420
1421def migrate_non_shared(vm_, target, ssh=False):
1422 '''
1423 Attempt to execute non-shared storage "all" migration
1424
1425 CLI Example:
1426
1427 .. code-block:: bash
1428
Ales Komarekf8188332016-03-09 11:32:08 +01001429 salt '*' virtng.migrate_non_shared <vm name> <target hypervisor>
smolaon1fb381d2016-03-09 11:10:58 +01001430 '''
1431 cmd = _get_migrate_command() + ' --copy-storage-all ' + vm_\
1432 + _get_target(target, ssh)
1433
1434 return subprocess.Popen(cmd,
1435 shell=True,
1436 stdout=subprocess.PIPE).communicate()[0]
1437
1438
1439def migrate_non_shared_inc(vm_, target, ssh=False):
1440 '''
1441 Attempt to execute non-shared storage "all" migration
1442
1443 CLI Example:
1444
1445 .. code-block:: bash
1446
Ales Komarekf8188332016-03-09 11:32:08 +01001447 salt '*' virtng.migrate_non_shared_inc <vm name> <target hypervisor>
smolaon1fb381d2016-03-09 11:10:58 +01001448 '''
1449 cmd = _get_migrate_command() + ' --copy-storage-inc ' + vm_\
1450 + _get_target(target, ssh)
1451
1452 return subprocess.Popen(cmd,
1453 shell=True,
1454 stdout=subprocess.PIPE).communicate()[0]
1455
1456
1457def migrate(vm_, target, ssh=False):
1458 '''
1459 Shared storage migration
1460
1461 CLI Example:
1462
1463 .. code-block:: bash
1464
Ales Komarekf8188332016-03-09 11:32:08 +01001465 salt '*' virtng.migrate <vm name> <target hypervisor>
smolaon1fb381d2016-03-09 11:10:58 +01001466 '''
1467 cmd = _get_migrate_command() + ' ' + vm_\
1468 + _get_target(target, ssh)
1469
1470 return subprocess.Popen(cmd,
1471 shell=True,
1472 stdout=subprocess.PIPE).communicate()[0]
1473
1474
1475def seed_non_shared_migrate(disks, force=False):
1476 '''
1477 Non shared migration requires that the disks be present on the migration
1478 destination, pass the disks information via this function, to the
1479 migration destination before executing the migration.
1480
1481 CLI Example:
1482
1483 .. code-block:: bash
1484
Ales Komarekf8188332016-03-09 11:32:08 +01001485 salt '*' virtng.seed_non_shared_migrate <disks>
smolaon1fb381d2016-03-09 11:10:58 +01001486 '''
1487 for _, data in disks.items():
1488 fn_ = data['file']
1489 form = data['file format']
1490 size = data['virtual size'].split()[1][1:]
1491 if os.path.isfile(fn_) and not force:
1492 # the target exists, check to see if it is compatible
1493 pre = yaml.safe_load(subprocess.Popen('qemu-img info arch',
1494 shell=True,
1495 stdout=subprocess.PIPE).communicate()[0])
1496 if pre['file format'] != data['file format']\
1497 and pre['virtual size'] != data['virtual size']:
1498 return False
1499 if not os.path.isdir(os.path.dirname(fn_)):
1500 os.makedirs(os.path.dirname(fn_))
1501 if os.path.isfile(fn_):
1502 os.remove(fn_)
1503 cmd = 'qemu-img create -f ' + form + ' ' + fn_ + ' ' + size
1504 subprocess.call(cmd, shell=True)
1505 creds = _libvirt_creds()
1506 cmd = 'chown ' + creds['user'] + ':' + creds['group'] + ' ' + fn_
1507 subprocess.call(cmd, shell=True)
1508 return True
1509
1510
1511def set_autostart(vm_, state='on'):
1512 '''
1513 Set the autostart flag on a VM so that the VM will start with the host
1514 system on reboot.
1515
1516 CLI Example:
1517
1518 .. code-block:: bash
1519
1520 salt "*" virt.set_autostart <vm name> <on | off>
1521 '''
1522
1523 dom = _get_dom(vm_)
1524
1525 if state == 'on':
1526 return dom.setAutostart(1) == 0
1527
1528 elif state == 'off':
1529 return dom.setAutostart(0) == 0
1530
1531 else:
1532 # return False if state is set to something other then on or off
1533 return False
1534
1535
1536def destroy(vm_):
1537 '''
1538 Hard power down the virtual machine, this is equivalent to pulling the
1539 power
1540
1541 CLI Example:
1542
1543 .. code-block:: bash
1544
Ales Komarekf8188332016-03-09 11:32:08 +01001545 salt '*' virtng.destroy <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001546 '''
1547 dom = _get_dom(vm_)
1548 return dom.destroy() == 0
1549
1550
1551def undefine(vm_):
1552 '''
1553 Remove a defined vm, this does not purge the virtual machine image, and
1554 this only works if the vm is powered down
1555
1556 CLI Example:
1557
1558 .. code-block:: bash
1559
Ales Komarekf8188332016-03-09 11:32:08 +01001560 salt '*' virtng.undefine <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001561 '''
1562 dom = _get_dom(vm_)
1563 return dom.undefine() == 0
1564
1565
1566def purge(vm_, dirs=False):
1567 '''
1568 Recursively destroy and delete a virtual machine, pass True for dir's to
1569 also delete the directories containing the virtual machine disk images -
1570 USE WITH EXTREME CAUTION!
1571
1572 CLI Example:
1573
1574 .. code-block:: bash
1575
Ales Komarekf8188332016-03-09 11:32:08 +01001576 salt '*' virtng.purge <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001577 '''
1578 disks = get_disks(vm_)
1579 try:
1580 if not destroy(vm_):
1581 return False
1582 except libvirt.libvirtError:
1583 # This is thrown if the machine is already shut down
1584 pass
1585 directories = set()
1586 for disk in disks:
1587 os.remove(disks[disk]['file'])
1588 directories.add(os.path.dirname(disks[disk]['file']))
1589 if dirs:
1590 for dir_ in directories:
1591 shutil.rmtree(dir_)
1592 undefine(vm_)
1593 return True
1594
1595
1596def virt_type():
1597 '''
1598 Returns the virtual machine type as a string
1599
1600 CLI Example:
1601
1602 .. code-block:: bash
1603
Ales Komarekf8188332016-03-09 11:32:08 +01001604 salt '*' virtng.virt_type
smolaon1fb381d2016-03-09 11:10:58 +01001605 '''
1606 return __grains__['virtual']
1607
1608
1609def is_kvm_hyper():
1610 '''
1611 Returns a bool whether or not this node is a KVM hypervisor
1612
1613 CLI Example:
1614
1615 .. code-block:: bash
1616
Ales Komarekf8188332016-03-09 11:32:08 +01001617 salt '*' virtng.is_kvm_hyper
smolaon1fb381d2016-03-09 11:10:58 +01001618 '''
1619 try:
1620 if 'kvm_' not in salt.utils.fopen('/proc/modules').read():
1621 return False
1622 except IOError:
1623 # No /proc/modules? Are we on Windows? Or Solaris?
1624 return False
Oleh Hryhorov7c5dfd32018-01-19 15:43:44 +02001625 return 'libvirtd' in __salt__['cmd.shell'](__grains__['ps'])
smolaon1fb381d2016-03-09 11:10:58 +01001626
1627
1628def is_xen_hyper():
1629 '''
1630 Returns a bool whether or not this node is a XEN hypervisor
1631
1632 CLI Example:
1633
1634 .. code-block:: bash
1635
Ales Komarekf8188332016-03-09 11:32:08 +01001636 salt '*' virtng.is_xen_hyper
smolaon1fb381d2016-03-09 11:10:58 +01001637 '''
1638 try:
1639 if __grains__['virtual_subtype'] != 'Xen Dom0':
1640 return False
1641 except KeyError:
1642 # virtual_subtype isn't set everywhere.
1643 return False
1644 try:
1645 if 'xen_' not in salt.utils.fopen('/proc/modules').read():
1646 return False
1647 except IOError:
1648 # No /proc/modules? Are we on Windows? Or Solaris?
1649 return False
Oleh Hryhorov7c5dfd32018-01-19 15:43:44 +02001650 return 'libvirtd' in __salt__['cmd.shell'](__grains__['ps'])
smolaon1fb381d2016-03-09 11:10:58 +01001651
1652
1653def is_hyper():
1654 '''
1655 Returns a bool whether or not this node is a hypervisor of any kind
1656
1657 CLI Example:
1658
1659 .. code-block:: bash
1660
Ales Komarekf8188332016-03-09 11:32:08 +01001661 salt '*' virtng.is_hyper
smolaon1fb381d2016-03-09 11:10:58 +01001662 '''
1663 try:
1664 import libvirt # pylint: disable=import-error
1665 except ImportError:
1666 # not a usable hypervisor without libvirt module
1667 return False
1668 return is_xen_hyper() or is_kvm_hyper()
1669
1670
1671def vm_cputime(vm_=None):
1672 '''
1673 Return cputime used by the vms on this hyper in a
1674 list of dicts:
1675
1676 .. code-block:: python
1677
1678 [
1679 'your-vm': {
1680 'cputime' <int>
1681 'cputime_percent' <int>
1682 },
1683 ...
1684 ]
1685
1686 If you pass a VM name in as an argument then it will return info
1687 for just the named VM, otherwise it will return all VMs.
1688
1689 CLI Example:
1690
1691 .. code-block:: bash
1692
Ales Komarekf8188332016-03-09 11:32:08 +01001693 salt '*' virtng.vm_cputime
smolaon1fb381d2016-03-09 11:10:58 +01001694 '''
1695 host_cpus = __get_conn().getInfo()[2]
1696
1697 def _info(vm_):
1698 dom = _get_dom(vm_)
1699 raw = dom.info()
1700 vcpus = int(raw[3])
1701 cputime = int(raw[4])
1702 cputime_percent = 0
1703 if cputime:
1704 # Divide by vcpus to always return a number between 0 and 100
1705 cputime_percent = (1.0e-7 * cputime / host_cpus) / vcpus
1706 return {
1707 'cputime': int(raw[4]),
1708 'cputime_percent': int('{0:.0f}'.format(cputime_percent))
1709 }
1710 info = {}
1711 if vm_:
1712 info[vm_] = _info(vm_)
1713 else:
1714 for vm_ in list_vms():
1715 info[vm_] = _info(vm_)
1716 return info
1717
1718
1719def vm_netstats(vm_=None):
1720 '''
1721 Return combined network counters used by the vms on this hyper in a
1722 list of dicts:
1723
1724 .. code-block:: python
1725
1726 [
1727 'your-vm': {
1728 'rx_bytes' : 0,
1729 'rx_packets' : 0,
1730 'rx_errs' : 0,
1731 'rx_drop' : 0,
1732 'tx_bytes' : 0,
1733 'tx_packets' : 0,
1734 'tx_errs' : 0,
1735 'tx_drop' : 0
1736 },
1737 ...
1738 ]
1739
1740 If you pass a VM name in as an argument then it will return info
1741 for just the named VM, otherwise it will return all VMs.
1742
1743 CLI Example:
1744
1745 .. code-block:: bash
1746
Ales Komarekf8188332016-03-09 11:32:08 +01001747 salt '*' virtng.vm_netstats
smolaon1fb381d2016-03-09 11:10:58 +01001748 '''
1749 def _info(vm_):
1750 dom = _get_dom(vm_)
1751 nics = get_nics(vm_)
1752 ret = {
1753 'rx_bytes': 0,
1754 'rx_packets': 0,
1755 'rx_errs': 0,
1756 'rx_drop': 0,
1757 'tx_bytes': 0,
1758 'tx_packets': 0,
1759 'tx_errs': 0,
1760 'tx_drop': 0
1761 }
1762 for attrs in six.itervalues(nics):
1763 if 'target' in attrs:
1764 dev = attrs['target']
1765 stats = dom.interfaceStats(dev)
1766 ret['rx_bytes'] += stats[0]
1767 ret['rx_packets'] += stats[1]
1768 ret['rx_errs'] += stats[2]
1769 ret['rx_drop'] += stats[3]
1770 ret['tx_bytes'] += stats[4]
1771 ret['tx_packets'] += stats[5]
1772 ret['tx_errs'] += stats[6]
1773 ret['tx_drop'] += stats[7]
1774
1775 return ret
1776 info = {}
1777 if vm_:
1778 info[vm_] = _info(vm_)
1779 else:
1780 for vm_ in list_vms():
1781 info[vm_] = _info(vm_)
1782 return info
1783
1784
1785def vm_diskstats(vm_=None):
1786 '''
1787 Return disk usage counters used by the vms on this hyper in a
1788 list of dicts:
1789
1790 .. code-block:: python
1791
1792 [
1793 'your-vm': {
1794 'rd_req' : 0,
1795 'rd_bytes' : 0,
1796 'wr_req' : 0,
1797 'wr_bytes' : 0,
1798 'errs' : 0
1799 },
1800 ...
1801 ]
1802
1803 If you pass a VM name in as an argument then it will return info
1804 for just the named VM, otherwise it will return all VMs.
1805
1806 CLI Example:
1807
1808 .. code-block:: bash
1809
Ales Komarekf8188332016-03-09 11:32:08 +01001810 salt '*' virtng.vm_blockstats
smolaon1fb381d2016-03-09 11:10:58 +01001811 '''
1812 def get_disk_devs(vm_):
1813 doc = minidom.parse(_StringIO(get_xml(vm_)))
1814 disks = []
1815 for elem in doc.getElementsByTagName('disk'):
1816 targets = elem.getElementsByTagName('target')
1817 target = targets[0]
1818 disks.append(target.getAttribute('dev'))
1819 return disks
1820
1821 def _info(vm_):
1822 dom = _get_dom(vm_)
1823 # Do not use get_disks, since it uses qemu-img and is very slow
1824 # and unsuitable for any sort of real time statistics
1825 disks = get_disk_devs(vm_)
1826 ret = {'rd_req': 0,
1827 'rd_bytes': 0,
1828 'wr_req': 0,
1829 'wr_bytes': 0,
1830 'errs': 0
1831 }
1832 for disk in disks:
1833 stats = dom.blockStats(disk)
1834 ret['rd_req'] += stats[0]
1835 ret['rd_bytes'] += stats[1]
1836 ret['wr_req'] += stats[2]
1837 ret['wr_bytes'] += stats[3]
1838 ret['errs'] += stats[4]
1839
1840 return ret
1841 info = {}
1842 if vm_:
1843 info[vm_] = _info(vm_)
1844 else:
1845 # Can not run function blockStats on inactive VMs
1846 for vm_ in list_active_vms():
1847 info[vm_] = _info(vm_)
1848 return info