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