blob: 426d6110442a04eaa6eaee24d039a153f1f41e20 [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
Dzmitry Stremkouski97927ee2018-08-23 23:20:38 +020023import json
smolaon1fb381d2016-03-09 11:10:58 +010024import jinja2
25import jinja2.exceptions
26import salt.ext.six as six
27from salt.ext.six.moves import StringIO as _StringIO # pylint: disable=import-error
28from xml.dom import minidom
29try:
30 import libvirt # pylint: disable=import-error
31 HAS_ALL_IMPORTS = True
32except ImportError:
33 HAS_ALL_IMPORTS = False
34
35# Import salt libs
36import salt.utils
37import salt.utils.files
38import salt.utils.templates
39import salt.utils.validate.net
40from salt.exceptions import CommandExecutionError, SaltInvocationError
41
42log = logging.getLogger(__name__)
43
44# Set up template environment
45JINJA = jinja2.Environment(
46 loader=jinja2.FileSystemLoader(
47 os.path.join(salt.utils.templates.TEMPLATE_DIRNAME, 'virt')
48 )
49)
50
51VIRT_STATE_NAME_MAP = {0: 'running',
52 1: 'running',
53 2: 'running',
54 3: 'paused',
55 4: 'shutdown',
56 5: 'shutdown',
57 6: 'crashed'}
58
59VIRT_DEFAULT_HYPER = 'kvm'
60
61
62def __virtual__():
63 if not HAS_ALL_IMPORTS:
64 return False
Ales Komarekf8188332016-03-09 11:32:08 +010065 return 'virtng'
smolaon1fb381d2016-03-09 11:10:58 +010066
67
68def __get_conn():
69 '''
70 Detects what type of dom this node is and attempts to connect to the
71 correct hypervisor via libvirt.
72 '''
73 # This has only been tested on kvm and xen, it needs to be expanded to
74 # support all vm layers supported by libvirt
75
76 def __esxi_uri():
77 '''
78 Connect to an ESXi host with a configuration like so:
79
80 .. code-block:: yaml
81
82 libvirt:
83 hypervisor: esxi
84 connection: esx01
85
86 The connection setting can either be an explicit libvirt URI,
87 or a libvirt URI alias as in this example. No, it cannot be
88 just a hostname.
89
90
91 Example libvirt `/etc/libvirt/libvirt.conf`:
92
93 .. code-block::
94
95 uri_aliases = [
96 "esx01=esx://10.1.1.101/?no_verify=1&auto_answer=1",
97 "esx02=esx://10.1.1.102/?no_verify=1&auto_answer=1",
98 ]
99
100 Reference:
101
102 - http://libvirt.org/drvesx.html#uriformat
103 - http://libvirt.org/uri.html#URI_config
104 '''
105 connection = __salt__['config.get']('libvirt:connection', 'esx')
106 return connection
107
108 def __esxi_auth():
109 '''
110 We rely on that the credentials is provided to libvirt through
111 its built in mechanisms.
112
113 Example libvirt `/etc/libvirt/auth.conf`:
114
115 .. code-block::
116
117 [credentials-myvirt]
118 username=user
119 password=secret
120
121 [auth-esx-10.1.1.101]
122 credentials=myvirt
123
124 [auth-esx-10.1.1.102]
125 credentials=myvirt
126
127 Reference:
128
129 - http://libvirt.org/auth.html#Auth_client_config
130 '''
131 return [[libvirt.VIR_CRED_EXTERNAL], lambda: 0, None]
132
133 if 'virt.connect' in __opts__:
134 conn_str = __opts__['virt.connect']
135 else:
136 conn_str = 'qemu:///system'
137
138 conn_func = {
139 'esxi': [libvirt.openAuth, [__esxi_uri(),
140 __esxi_auth(),
141 0]],
142 'qemu': [libvirt.open, [conn_str]],
143 }
144
145 hypervisor = __salt__['config.get']('libvirt:hypervisor', 'qemu')
146
147 try:
148 conn = conn_func[hypervisor][0](*conn_func[hypervisor][1])
149 except Exception:
150 raise CommandExecutionError(
151 'Sorry, {0} failed to open a connection to the hypervisor '
152 'software at {1}'.format(
153 __grains__['fqdn'],
154 conn_func[hypervisor][1][0]
155 )
156 )
157 return conn
158
159
160def _get_dom(vm_):
161 '''
162 Return a domain object for the named vm
163 '''
164 conn = __get_conn()
165 if vm_ not in list_vms():
166 raise CommandExecutionError('The specified vm is not present')
167 return conn.lookupByName(vm_)
168
169
170def _libvirt_creds():
171 '''
172 Returns the user and group that the disk images should be owned by
173 '''
174 g_cmd = 'grep ^\\s*group /etc/libvirt/qemu.conf'
175 u_cmd = 'grep ^\\s*user /etc/libvirt/qemu.conf'
176 try:
177 group = subprocess.Popen(g_cmd,
178 shell=True,
179 stdout=subprocess.PIPE).communicate()[0].split('"')[1]
180 except IndexError:
181 group = 'root'
182 try:
183 user = subprocess.Popen(u_cmd,
184 shell=True,
185 stdout=subprocess.PIPE).communicate()[0].split('"')[1]
186 except IndexError:
187 user = 'root'
188 return {'user': user, 'group': group}
189
190
191def _get_migrate_command():
192 '''
193 Returns the command shared by the different migration types
194 '''
195 if __salt__['config.option']('virt.tunnel'):
196 return ('virsh migrate --p2p --tunnelled --live --persistent '
197 '--undefinesource ')
198 return 'virsh migrate --live --persistent --undefinesource '
199
200
201def _get_target(target, ssh):
202 proto = 'qemu'
203 if ssh:
204 proto += '+ssh'
205 return ' {0}://{1}/{2}'.format(proto, target, 'system')
206
207
208def _gen_xml(name,
209 cpu,
210 mem,
211 diskp,
212 nicp,
213 hypervisor,
214 **kwargs):
215 '''
216 Generate the XML string to define a libvirt vm
217 '''
218 hypervisor = 'vmware' if hypervisor == 'esxi' else hypervisor
219 mem = mem * 1024 # MB
220 context = {
221 'hypervisor': hypervisor,
222 'name': name,
223 'cpu': str(cpu),
224 'mem': str(mem),
225 }
226 if hypervisor in ['qemu', 'kvm']:
227 context['controller_model'] = False
228 elif hypervisor in ['esxi', 'vmware']:
229 # TODO: make bus and model parameterized, this works for 64-bit Linux
230 context['controller_model'] = 'lsilogic'
231
232 if 'boot_dev' in kwargs:
233 context['boot_dev'] = []
234 for dev in kwargs['boot_dev'].split():
235 context['boot_dev'].append(dev)
236 else:
237 context['boot_dev'] = ['hd']
238
239 if 'serial_type' in kwargs:
240 context['serial_type'] = kwargs['serial_type']
241 if 'serial_type' in context and context['serial_type'] == 'tcp':
242 if 'telnet_port' in kwargs:
243 context['telnet_port'] = kwargs['telnet_port']
244 else:
245 context['telnet_port'] = 23023 # FIXME: use random unused port
246 if 'serial_type' in context:
247 if 'console' in kwargs:
248 context['console'] = kwargs['console']
249 else:
250 context['console'] = True
251
252 context['disks'] = {}
253 for i, disk in enumerate(diskp):
254 for disk_name, args in disk.items():
255 context['disks'][disk_name] = {}
256 fn_ = '{0}.{1}'.format(disk_name, args['format'])
257 context['disks'][disk_name]['file_name'] = fn_
258 context['disks'][disk_name]['source_file'] = os.path.join(args['pool'],
259 name,
260 fn_)
261 if hypervisor in ['qemu', 'kvm']:
262 context['disks'][disk_name]['target_dev'] = 'vd{0}'.format(string.ascii_lowercase[i])
263 context['disks'][disk_name]['address'] = False
264 context['disks'][disk_name]['driver'] = True
265 elif hypervisor in ['esxi', 'vmware']:
266 context['disks'][disk_name]['target_dev'] = 'sd{0}'.format(string.ascii_lowercase[i])
267 context['disks'][disk_name]['address'] = True
268 context['disks'][disk_name]['driver'] = False
269 context['disks'][disk_name]['disk_bus'] = args['model']
270 context['disks'][disk_name]['type'] = args['format']
271 context['disks'][disk_name]['index'] = str(i)
272
273 context['nics'] = nicp
274
275 fn_ = 'libvirt_domain.jinja'
276 try:
277 template = JINJA.get_template(fn_)
278 except jinja2.exceptions.TemplateNotFound:
279 log.error('Could not load template {0}'.format(fn_))
280 return ''
281
282 return template.render(**context)
283
284
285def _gen_vol_xml(vmname,
286 diskname,
287 size,
288 hypervisor,
289 **kwargs):
290 '''
291 Generate the XML string to define a libvirt storage volume
292 '''
293 size = int(size) * 1024 # MB
294 disk_info = _get_image_info(hypervisor, vmname, **kwargs)
295 context = {
296 'name': vmname,
297 'filename': '{0}.{1}'.format(diskname, disk_info['disktype']),
298 'volname': diskname,
299 'disktype': disk_info['disktype'],
300 'size': str(size),
301 'pool': disk_info['pool'],
302 }
303 fn_ = 'libvirt_volume.jinja'
304 try:
305 template = JINJA.get_template(fn_)
306 except jinja2.exceptions.TemplateNotFound:
307 log.error('Could not load template {0}'.format(fn_))
308 return ''
309 return template.render(**context)
310
311
312def _qemu_image_info(path):
313 '''
314 Detect information for the image at path
315 '''
316 ret = {}
Oleh Hryhorov7c5dfd32018-01-19 15:43:44 +0200317 out = __salt__['cmd.shell']('qemu-img info {0}'.format(path))
smolaon1fb381d2016-03-09 11:10:58 +0100318
319 match_map = {'size': r'virtual size: \w+ \((\d+) byte[s]?\)',
320 'format': r'file format: (\w+)'}
321
322 for info, search in match_map.items():
323 try:
324 ret[info] = re.search(search, out).group(1)
325 except AttributeError:
326 continue
327 return ret
328
329
330# TODO: this function is deprecated, should be replaced with
331# _qemu_image_info()
332def _image_type(vda):
333 '''
334 Detect what driver needs to be used for the given image
335 '''
Oleh Hryhorov7c5dfd32018-01-19 15:43:44 +0200336 out = __salt__['cmd.shell']('qemu-img info {0}'.format(vda))
smolaon1fb381d2016-03-09 11:10:58 +0100337 if 'file format: qcow2' in out:
338 return 'qcow2'
339 else:
340 return 'raw'
341
342
343# TODO: this function is deprecated, should be merged and replaced
344# with _disk_profile()
345def _get_image_info(hypervisor, name, **kwargs):
346 '''
347 Determine disk image info, such as filename, image format and
348 storage pool, based on which hypervisor is used
349 '''
350 ret = {}
351 if hypervisor in ['esxi', 'vmware']:
352 ret['disktype'] = 'vmdk'
353 ret['filename'] = '{0}{1}'.format(name, '.vmdk')
354 ret['pool'] = '[{0}] '.format(kwargs.get('pool', '0'))
355 elif hypervisor in ['kvm', 'qemu']:
356 ret['disktype'] = 'qcow2'
357 ret['filename'] = '{0}{1}'.format(name, '.qcow2')
Mateusz Los4c7cd2d2018-01-09 11:46:07 +0100358 if 'img_dest' in kwargs:
359 ret['pool'] = kwargs['img_dest']
360 else:
361 ret['pool'] = __salt__['config.option']('virt.images')
smolaon1fb381d2016-03-09 11:10:58 +0100362 return ret
363
364
365def _disk_profile(profile, hypervisor, **kwargs):
366 '''
367 Gather the disk profile from the config or apply the default based
368 on the active hypervisor
369
370 This is the ``default`` profile for KVM/QEMU, which can be
371 overridden in the configuration:
372
373 .. code-block:: yaml
374
375 virt:
376 disk:
377 default:
378 - system:
379 size: 8192
380 format: qcow2
381 model: virtio
382
Dennis Dmitrieva57463c2017-03-24 17:05:22 +0200383 Example profile for KVM/QEMU with two disks, first is created
384 from specified image, the second is empty:
385
386 .. code-block:: yaml
387
388 virt:
389 disk:
390 two_disks:
391 - system:
392 size: 8192
393 format: qcow2
394 model: virtio
395 image: http://path/to/image.qcow2
396 - lvm:
397 size: 32768
398 format: qcow2
399 model: virtio
400
smolaon1fb381d2016-03-09 11:10:58 +0100401 The ``format`` and ``model`` parameters are optional, and will
402 default to whatever is best suitable for the active hypervisor.
403 '''
404 default = [
405 {'system':
406 {'size': '8192'}
407 }
408 ]
409 if hypervisor in ['esxi', 'vmware']:
410 overlay = {'format': 'vmdk',
411 'model': 'scsi',
412 'pool': '[{0}] '.format(kwargs.get('pool', '0'))
413 }
414 elif hypervisor in ['qemu', 'kvm']:
Mateusz Los4c7cd2d2018-01-09 11:46:07 +0100415 if 'img_dest' in kwargs:
416 pool = kwargs['img_dest']
417 else:
418 pool = __salt__['config.option']('virt.images')
419 overlay = {'format': 'qcow2', 'model': 'virtio', 'pool': pool}
smolaon1fb381d2016-03-09 11:10:58 +0100420 else:
421 overlay = {}
422
Dennis Dmitrievf5dba8c2017-10-10 19:05:20 +0300423 disklist = copy.deepcopy(__salt__['config.get']('virt:disk', {}).get(profile, default))
smolaon1fb381d2016-03-09 11:10:58 +0100424 for key, val in overlay.items():
425 for i, disks in enumerate(disklist):
426 for disk in disks:
427 if key not in disks[disk]:
428 disklist[i][disk][key] = val
429 return disklist
430
431
432def _nic_profile(profile_name, hypervisor, **kwargs):
433
smolaon1fb381d2016-03-09 11:10:58 +0100434 def append_dict_profile_to_interface_list(profile_dict):
435 for interface_name, attributes in profile_dict.items():
436 attributes['name'] = interface_name
437 interfaces.append(attributes)
438
smolaon1fb381d2016-03-09 11:10:58 +0100439 def _normalize_net_types(attributes):
440 '''
441 Guess which style of definition:
442
443 bridge: br0
444
445 or
446
447 network: net0
448
449 or
450
451 type: network
452 source: net0
453 '''
454 for type_ in ['bridge', 'network']:
455 if type_ in attributes:
456 attributes['type'] = type_
457 # we want to discard the original key
458 attributes['source'] = attributes.pop(type_)
459
460 attributes['type'] = attributes.get('type', None)
461 attributes['source'] = attributes.get('source', None)
Dzmitry Stremkouskib8acf1f2018-06-28 12:56:23 +0200462 attributes['virtualport'] = attributes.get('virtualport', None)
smolaon1fb381d2016-03-09 11:10:58 +0100463
464 def _apply_default_overlay(attributes):
465 for key, value in overlays[hypervisor].items():
466 if key not in attributes or not attributes[key]:
467 attributes[key] = value
468
469 def _assign_mac(attributes):
470 dmac = '{0}_mac'.format(attributes['name'])
471 if dmac in kwargs:
472 dmac = kwargs[dmac]
473 if salt.utils.validate.net.mac(dmac):
474 attributes['mac'] = dmac
475 else:
476 msg = 'Malformed MAC address: {0}'.format(dmac)
477 raise CommandExecutionError(msg)
478 else:
479 attributes['mac'] = salt.utils.gen_mac()
480
Dzmitry Stremkouskib8acf1f2018-06-28 12:56:23 +0200481
482 default = [{'eth0': {}}]
483 vmware_overlay = {'type': 'bridge', 'source': 'DEFAULT', 'model': 'e1000'}
484 kvm_overlay = {'type': 'bridge', 'source': 'br0', 'model': 'virtio'}
485 overlays = {
486 'kvm': kvm_overlay,
487 'qemu': kvm_overlay,
488 'esxi': vmware_overlay,
489 'vmware': vmware_overlay,
490 }
491
492 # support old location
493 config_data = __salt__['config.option']('virt.nic', {}).get(
494 profile_name, None
495 )
496
497 if config_data is None:
498 config_data = __salt__['config.get']('virt:nic', {}).get(
499 profile_name, default
500 )
501
502 interfaces = []
503
504 if isinstance(config_data, dict):
505 append_dict_profile_to_interface_list(config_data)
506
507 elif isinstance(config_data, list):
508 for interface in config_data:
509 if isinstance(interface, dict):
510 if len(interface) == 1:
511 append_dict_profile_to_interface_list(interface)
512 else:
513 interfaces.append(interface)
514
smolaon1fb381d2016-03-09 11:10:58 +0100515 for interface in interfaces:
516 _normalize_net_types(interface)
517 _assign_mac(interface)
518 if hypervisor in overlays:
519 _apply_default_overlay(interface)
520
521 return interfaces
522
523
524def init(name,
525 cpu,
526 mem,
527 image=None,
528 nic='default',
529 hypervisor=VIRT_DEFAULT_HYPER,
530 start=True, # pylint: disable=redefined-outer-name
531 disk='default',
532 saltenv='base',
azvyagintseva4e802d2018-05-04 20:16:02 +0300533 rng=None,
Alexandru Avadanii00f187a2018-06-24 20:36:44 +0200534 loader=None,
535 machine=None,
536 cpu_mode=None,
Pavel Cizinskyf03c4e82018-09-10 14:56:11 +0200537 cpuset=None,
smolaon1fb381d2016-03-09 11:10:58 +0100538 **kwargs):
539 '''
540 Initialize a new vm
541
542 CLI Example:
543
544 .. code-block:: bash
545
546 salt 'hypervisor' virt.init vm_name 4 512 salt://path/to/image.raw
547 salt 'hypervisor' virt.init vm_name 4 512 nic=profile disk=profile
548 '''
Dennis Dmitrieva57463c2017-03-24 17:05:22 +0200549
azvyagintseva4e802d2018-05-04 20:16:02 +0300550 rng = rng or {'backend':'/dev/urandom'}
smolaon1fb381d2016-03-09 11:10:58 +0100551 hypervisor = __salt__['config.get']('libvirt:hypervisor', hypervisor)
552
553 nicp = _nic_profile(nic, hypervisor, **kwargs)
554
Dennis Dmitrieva57463c2017-03-24 17:05:22 +0200555 diskp = _disk_profile(disk, hypervisor, **kwargs)
556
557 if image:
558 # Backward compatibility: if 'image' is specified in the VMs arguments
559 # instead of a disk arguments. In this case, 'image' will be assigned
560 # to the first disk for the VM.
smolaon1fb381d2016-03-09 11:10:58 +0100561 disk_name = next(diskp[0].iterkeys())
Dennis Dmitrieva57463c2017-03-24 17:05:22 +0200562 if not diskp[0][disk_name].get('image', None):
563 diskp[0][disk_name]['image'] = image
smolaon1fb381d2016-03-09 11:10:58 +0100564
Dennis Dmitrieva57463c2017-03-24 17:05:22 +0200565 # Create multiple disks, empty or from specified images.
Dzmitry Stremkouski97927ee2018-08-23 23:20:38 +0200566 cloud_init = None
567 cfg_drive = None
568
Dennis Dmitrieva57463c2017-03-24 17:05:22 +0200569 for disk in diskp:
570 log.debug("Creating disk for VM [ {0} ]: {1}".format(name, disk))
smolaon1fb381d2016-03-09 11:10:58 +0100571
Dennis Dmitrieva57463c2017-03-24 17:05:22 +0200572 for disk_name, args in disk.items():
smolaon1fb381d2016-03-09 11:10:58 +0100573
Dennis Dmitrieva57463c2017-03-24 17:05:22 +0200574 if hypervisor in ['esxi', 'vmware']:
575 if 'image' in args:
576 # TODO: we should be copying the image file onto the ESX host
577 raise SaltInvocationError('virt.init does not support image '
578 'template template in conjunction '
579 'with esxi hypervisor')
580 else:
581 # assume libvirt manages disks for us
smolaon1fb381d2016-03-09 11:10:58 +0100582 xml = _gen_vol_xml(name,
583 disk_name,
584 args['size'],
Mateusz Los4c7cd2d2018-01-09 11:46:07 +0100585 hypervisor,
586 **kwargs)
smolaon1fb381d2016-03-09 11:10:58 +0100587 define_vol_xml_str(xml)
588
Dennis Dmitrieva57463c2017-03-24 17:05:22 +0200589 elif hypervisor in ['qemu', 'kvm']:
590
591 disk_type = args['format']
592 disk_file_name = '{0}.{1}'.format(disk_name, disk_type)
593 # disk size TCP cloud
594 disk_size = args['size']
595
Mateusz Los4c7cd2d2018-01-09 11:46:07 +0100596 if 'img_dest' in kwargs:
597 img_dir = kwargs['img_dest']
598 else:
599 img_dir = __salt__['config.option']('virt.images')
Dennis Dmitrieva57463c2017-03-24 17:05:22 +0200600 img_dest = os.path.join(
601 img_dir,
602 name,
603 disk_file_name
604 )
605 img_dir = os.path.dirname(img_dest)
606 if not os.path.isdir(img_dir):
607 os.makedirs(img_dir)
608
609 if 'image' in args:
610 # Create disk from specified image
611 sfn = __salt__['cp.cache_file'](args['image'], saltenv)
612 try:
613 salt.utils.files.copyfile(sfn, img_dest)
614 mask = os.umask(0)
615 os.umask(mask)
616 # Apply umask and remove exec bit
617
618 # Resizing image TCP cloud
619 cmd = 'qemu-img resize ' + img_dest + ' ' + str(disk_size) + 'M'
620 subprocess.call(cmd, shell=True)
621
622 mode = (0o0777 ^ mask) & 0o0666
623 os.chmod(img_dest, mode)
624
625 except (IOError, OSError) as e:
626 raise CommandExecutionError('problem while copying image. {0} - {1}'.format(args['image'], e))
627
628 if kwargs.get('seed'):
Dzmitry Stremkouski97927ee2018-08-23 23:20:38 +0200629 seed_cmd = kwargs.get('seed_cmd', 'seedng.apply')
630 cloud_init = kwargs.get('cloud_init', None)
631 master = __salt__['config.option']('master')
632 cfg_drive = os.path.join(img_dir,'config-2.iso')
Dennis Dmitrieva57463c2017-03-24 17:05:22 +0200633
Dzmitry Stremkouski97927ee2018-08-23 23:20:38 +0200634 if cloud_init:
635 _tmp = name.split('.')
636
637 try:
638 user_data = json.dumps(cloud_init["user_data"])
639 except:
640 user_data = None
641
642 try:
643 network_data = json.dumps(cloud_init["network_data"])
644 except:
645 network_data = None
646
647 __salt__["cfgdrive.generate"](
648 dst = cfg_drive,
649 hostname = _tmp.pop(0),
650 domainname = '.'.join(_tmp),
651 user_data = user_data,
652 network_data = network_data,
653 saltconfig = { "salt_minion": { "conf": { "master": master, "id": name } } }
654 )
655 else:
656 __salt__[seed_cmd](
657 path = img_dest,
658 id_ = name,
659 config = kwargs.get('config'),
660 install = kwargs.get('install', True)
661 )
Dennis Dmitrieva57463c2017-03-24 17:05:22 +0200662 else:
663 # Create empty disk
664 try:
665 mask = os.umask(0)
666 os.umask(mask)
667 # Apply umask and remove exec bit
668
669 # Create empty image
670 cmd = 'qemu-img create -f ' + disk_type + ' ' + img_dest + ' ' + str(disk_size) + 'M'
671 subprocess.call(cmd, shell=True)
672
673 mode = (0o0777 ^ mask) & 0o0666
674 os.chmod(img_dest, mode)
675
676 except (IOError, OSError) as e:
677 raise CommandExecutionError('problem while creating volume {0} - {1}'.format(img_dest, e))
678
679 else:
680 # Unknown hypervisor
681 raise SaltInvocationError('Unsupported hypervisor when handling disk image: {0}'
682 .format(hypervisor))
683
smolaon1fb381d2016-03-09 11:10:58 +0100684 xml = _gen_xml(name, cpu, mem, diskp, nicp, hypervisor, **kwargs)
Dzmitry Stremkouski7ee23402018-04-10 00:43:48 +0200685
Dzmitry Stremkouski97927ee2018-08-23 23:20:38 +0200686 if cloud_init and cfg_drive:
687 xml_doc = minidom.parseString(xml)
688 iso_xml = xml_doc.createElement("disk")
689 iso_xml.setAttribute("type", "file")
690 iso_xml.setAttribute("device", "cdrom")
691 iso_xml.appendChild(xml_doc.createElement("readonly"))
692 driver = xml_doc.createElement("driver")
693 driver.setAttribute("name", "qemu")
694 driver.setAttribute("type", "raw")
695 target = xml_doc.createElement("target")
696 target.setAttribute("dev", "hdc")
697 target.setAttribute("bus", "ide")
698 source = xml_doc.createElement("source")
699 source.setAttribute("file", cfg_drive)
700 iso_xml.appendChild(driver)
701 iso_xml.appendChild(target)
702 iso_xml.appendChild(source)
703 xml_doc.getElementsByTagName("domain")[0].getElementsByTagName("devices")[0].appendChild(iso_xml)
704 xml = xml_doc.toxml()
705
Dzmitry Stremkouski7ee23402018-04-10 00:43:48 +0200706 # TODO: Remove this code and refactor module, when salt-common would have updated libvirt_domain.jinja template
Pavel Cizinskyf03c4e82018-09-10 14:56:11 +0200707 if cpuset:
708 xml_doc = minidom.parseString(xml)
709 xml_doc.getElementsByTagName("vcpu")[0].setAttribute('cpuset', cpuset)
710 xml = xml_doc.toxml()
711
712 # TODO: Remove this code and refactor module, when salt-common would have updated libvirt_domain.jinja template
Alexandru Avadanii00f187a2018-06-24 20:36:44 +0200713 if cpu_mode:
714 xml_doc = minidom.parseString(xml)
715 cpu_xml = xml_doc.createElement("cpu")
716 cpu_xml.setAttribute('mode', cpu_mode)
717 xml_doc.getElementsByTagName("domain")[0].appendChild(cpu_xml)
718 xml = xml_doc.toxml()
719
720 # TODO: Remove this code and refactor module, when salt-common would have updated libvirt_domain.jinja template
721 if machine:
722 xml_doc = minidom.parseString(xml)
723 os_xml = xml_doc.getElementsByTagName("domain")[0].getElementsByTagName("os")[0]
724 os_xml.getElementsByTagName("type")[0].setAttribute('machine', machine)
725 xml = xml_doc.toxml()
726
727 # TODO: Remove this code and refactor module, when salt-common would have updated libvirt_domain.jinja template
728 if loader and 'path' not in loader:
729 log.info('`path` is a required property of `loader`, and cannot be found. Skipping loader configuration')
730 loader = None
731 elif loader:
732 xml_doc = minidom.parseString(xml)
733 loader_xml = xml_doc.createElement("loader")
734 for key, val in loader.items():
735 if key == 'path':
736 continue
737 loader_xml.setAttribute(key, val)
738 loader_path_xml = xml_doc.createTextNode(loader['path'])
739 loader_xml.appendChild(loader_path_xml)
740 xml_doc.getElementsByTagName("domain")[0].getElementsByTagName("os")[0].appendChild(loader_xml)
741 xml = xml_doc.toxml()
742
743 # TODO: Remove this code and refactor module, when salt-common would have updated libvirt_domain.jinja template
Dzmitry Stremkouskib8acf1f2018-06-28 12:56:23 +0200744 for _nic in nicp:
745 if _nic['virtualport']:
746 xml_doc = minidom.parseString(xml)
747 interfaces = xml_doc.getElementsByTagName("domain")[0].getElementsByTagName("devices")[0].getElementsByTagName("interface")
748 for interface in interfaces:
749 if interface.getElementsByTagName('mac')[0].getAttribute('address').lower() == _nic['mac'].lower():
750 vport_xml = xml_doc.createElement("virtualport")
751 vport_xml.setAttribute("type", _nic['virtualport']['type'])
752 interface.appendChild(vport_xml)
753 xml = xml_doc.toxml()
754
755 # TODO: Remove this code and refactor module, when salt-common would have updated libvirt_domain.jinja template
Dzmitry Stremkouski7ee23402018-04-10 00:43:48 +0200756 if rng:
757 rng_model = rng.get('model', 'random')
758 rng_backend = rng.get('backend', '/dev/urandom')
759 xml_doc = minidom.parseString(xml)
760 rng_xml = xml_doc.createElement("rng")
761 rng_xml.setAttribute("model", "virtio")
762 backend = xml_doc.createElement("backend")
763 backend.setAttribute("model", rng_model)
764 backend.appendChild(xml_doc.createTextNode(rng_backend))
765 rng_xml.appendChild(backend)
766 if 'rate' in rng:
767 rng_rate_period = rng['rate'].get('period', '2000')
768 rng_rate_bytes = rng['rate'].get('bytes', '1234')
769 rate = xml_doc.createElement("rate")
770 rate.setAttribute("period", rng_rate_period)
771 rate.setAttribute("bytes", rng_rate_bytes)
772 rng_xml.appendChild(rate)
773 xml_doc.getElementsByTagName("domain")[0].getElementsByTagName("devices")[0].appendChild(rng_xml)
774 xml = xml_doc.toxml()
775
smolaon1fb381d2016-03-09 11:10:58 +0100776 define_xml_str(xml)
777
smolaon1fb381d2016-03-09 11:10:58 +0100778 if start:
779 create(name)
780
781 return True
782
783
784def list_vms():
785 '''
786 Return a list of virtual machine names on the minion
787
788 CLI Example:
789
790 .. code-block:: bash
791
Ales Komarekf8188332016-03-09 11:32:08 +0100792 salt '*' virtng.list_vms
smolaon1fb381d2016-03-09 11:10:58 +0100793 '''
794 vms = []
795 vms.extend(list_active_vms())
796 vms.extend(list_inactive_vms())
797 return vms
798
799
800def list_active_vms():
801 '''
802 Return a list of names for active virtual machine on the minion
803
804 CLI Example:
805
806 .. code-block:: bash
807
Ales Komarekf8188332016-03-09 11:32:08 +0100808 salt '*' virtng.list_active_vms
smolaon1fb381d2016-03-09 11:10:58 +0100809 '''
810 conn = __get_conn()
811 vms = []
812 for id_ in conn.listDomainsID():
813 vms.append(conn.lookupByID(id_).name())
814 return vms
815
816
817def list_inactive_vms():
818 '''
819 Return a list of names for inactive virtual machine on the minion
820
821 CLI Example:
822
823 .. code-block:: bash
824
Ales Komarekf8188332016-03-09 11:32:08 +0100825 salt '*' virtng.list_inactive_vms
smolaon1fb381d2016-03-09 11:10:58 +0100826 '''
827 conn = __get_conn()
828 vms = []
829 for id_ in conn.listDefinedDomains():
830 vms.append(id_)
831 return vms
832
833
834def vm_info(vm_=None):
835 '''
836 Return detailed information about the vms on this hyper in a
837 list of dicts:
838
839 .. code-block:: python
840
841 [
842 'your-vm': {
843 'cpu': <int>,
844 'maxMem': <int>,
845 'mem': <int>,
846 'state': '<state>',
847 'cputime' <int>
848 },
849 ...
850 ]
851
852 If you pass a VM name in as an argument then it will return info
853 for just the named VM, otherwise it will return all VMs.
854
855 CLI Example:
856
857 .. code-block:: bash
858
Ales Komarekf8188332016-03-09 11:32:08 +0100859 salt '*' virtng.vm_info
smolaon1fb381d2016-03-09 11:10:58 +0100860 '''
861 def _info(vm_):
862 dom = _get_dom(vm_)
863 raw = dom.info()
864 return {'cpu': raw[3],
865 'cputime': int(raw[4]),
866 'disks': get_disks(vm_),
867 'graphics': get_graphics(vm_),
868 'nics': get_nics(vm_),
869 'maxMem': int(raw[1]),
870 'mem': int(raw[2]),
871 'state': VIRT_STATE_NAME_MAP.get(raw[0], 'unknown')}
872 info = {}
873 if vm_:
874 info[vm_] = _info(vm_)
875 else:
876 for vm_ in list_vms():
877 info[vm_] = _info(vm_)
878 return info
879
880
881def vm_state(vm_=None):
882 '''
883 Return list of all the vms and their state.
884
885 If you pass a VM name in as an argument then it will return info
886 for just the named VM, otherwise it will return all VMs.
887
888 CLI Example:
889
890 .. code-block:: bash
891
Ales Komarekf8188332016-03-09 11:32:08 +0100892 salt '*' virtng.vm_state <vm name>
smolaon1fb381d2016-03-09 11:10:58 +0100893 '''
894 def _info(vm_):
895 state = ''
896 dom = _get_dom(vm_)
897 raw = dom.info()
898 state = VIRT_STATE_NAME_MAP.get(raw[0], 'unknown')
899 return state
900 info = {}
901 if vm_:
902 info[vm_] = _info(vm_)
903 else:
904 for vm_ in list_vms():
905 info[vm_] = _info(vm_)
906 return info
907
908
909def node_info():
910 '''
911 Return a dict with information about this node
912
913 CLI Example:
914
915 .. code-block:: bash
916
Ales Komarekf8188332016-03-09 11:32:08 +0100917 salt '*' virtng.node_info
smolaon1fb381d2016-03-09 11:10:58 +0100918 '''
919 conn = __get_conn()
920 raw = conn.getInfo()
921 info = {'cpucores': raw[6],
922 'cpumhz': raw[3],
923 'cpumodel': str(raw[0]),
924 'cpus': raw[2],
925 'cputhreads': raw[7],
926 'numanodes': raw[4],
927 'phymemory': raw[1],
928 'sockets': raw[5]}
929 return info
930
931
932def get_nics(vm_):
933 '''
934 Return info about the network interfaces of a named vm
935
936 CLI Example:
937
938 .. code-block:: bash
939
Ales Komarekf8188332016-03-09 11:32:08 +0100940 salt '*' virtng.get_nics <vm name>
smolaon1fb381d2016-03-09 11:10:58 +0100941 '''
942 nics = {}
943 doc = minidom.parse(_StringIO(get_xml(vm_)))
944 for node in doc.getElementsByTagName('devices'):
945 i_nodes = node.getElementsByTagName('interface')
946 for i_node in i_nodes:
947 nic = {}
948 nic['type'] = i_node.getAttribute('type')
949 for v_node in i_node.getElementsByTagName('*'):
950 if v_node.tagName == 'mac':
951 nic['mac'] = v_node.getAttribute('address')
952 if v_node.tagName == 'model':
953 nic['model'] = v_node.getAttribute('type')
954 if v_node.tagName == 'target':
955 nic['target'] = v_node.getAttribute('dev')
956 # driver, source, and match can all have optional attributes
957 if re.match('(driver|source|address)', v_node.tagName):
958 temp = {}
959 for key, value in v_node.attributes.items():
960 temp[key] = value
961 nic[str(v_node.tagName)] = temp
962 # virtualport needs to be handled separately, to pick up the
963 # type attribute of the virtualport itself
964 if v_node.tagName == 'virtualport':
965 temp = {}
966 temp['type'] = v_node.getAttribute('type')
967 for key, value in v_node.attributes.items():
968 temp[key] = value
969 nic['virtualport'] = temp
970 if 'mac' not in nic:
971 continue
972 nics[nic['mac']] = nic
973 return nics
974
975
976def get_macs(vm_):
977 '''
978 Return a list off MAC addresses from the named vm
979
980 CLI Example:
981
982 .. code-block:: bash
983
Ales Komarekf8188332016-03-09 11:32:08 +0100984 salt '*' virtng.get_macs <vm name>
smolaon1fb381d2016-03-09 11:10:58 +0100985 '''
986 macs = []
987 doc = minidom.parse(_StringIO(get_xml(vm_)))
988 for node in doc.getElementsByTagName('devices'):
989 i_nodes = node.getElementsByTagName('interface')
990 for i_node in i_nodes:
991 for v_node in i_node.getElementsByTagName('mac'):
992 macs.append(v_node.getAttribute('address'))
993 return macs
994
995
996def get_graphics(vm_):
997 '''
998 Returns the information on vnc for a given vm
999
1000 CLI Example:
1001
1002 .. code-block:: bash
1003
Ales Komarekf8188332016-03-09 11:32:08 +01001004 salt '*' virtng.get_graphics <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001005 '''
1006 out = {'autoport': 'None',
1007 'keymap': 'None',
1008 'listen': 'None',
1009 'port': 'None',
1010 'type': 'vnc'}
1011 xml = get_xml(vm_)
1012 ssock = _StringIO(xml)
1013 doc = minidom.parse(ssock)
1014 for node in doc.getElementsByTagName('domain'):
1015 g_nodes = node.getElementsByTagName('graphics')
1016 for g_node in g_nodes:
1017 for key, value in g_node.attributes.items():
1018 out[key] = value
1019 return out
1020
1021
1022def get_disks(vm_):
1023 '''
1024 Return the disks of a named vm
1025
1026 CLI Example:
1027
1028 .. code-block:: bash
1029
Ales Komarekf8188332016-03-09 11:32:08 +01001030 salt '*' virtng.get_disks <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001031 '''
1032 disks = {}
1033 doc = minidom.parse(_StringIO(get_xml(vm_)))
1034 for elem in doc.getElementsByTagName('disk'):
1035 sources = elem.getElementsByTagName('source')
1036 targets = elem.getElementsByTagName('target')
1037 if len(sources) > 0:
1038 source = sources[0]
1039 else:
1040 continue
1041 if len(targets) > 0:
1042 target = targets[0]
1043 else:
1044 continue
1045 if target.hasAttribute('dev'):
1046 qemu_target = ''
1047 if source.hasAttribute('file'):
1048 qemu_target = source.getAttribute('file')
1049 elif source.hasAttribute('dev'):
1050 qemu_target = source.getAttribute('dev')
1051 elif source.hasAttribute('protocol') and \
1052 source.hasAttribute('name'): # For rbd network
1053 qemu_target = '{0}:{1}'.format(
1054 source.getAttribute('protocol'),
1055 source.getAttribute('name'))
1056 if qemu_target:
1057 disks[target.getAttribute('dev')] = {
1058 'file': qemu_target}
1059 for dev in disks:
1060 try:
1061 hypervisor = __salt__['config.get']('libvirt:hypervisor', 'kvm')
1062 if hypervisor not in ['qemu', 'kvm']:
1063 break
1064
1065 output = []
1066 qemu_output = subprocess.Popen(['qemu-img', 'info',
1067 disks[dev]['file']],
1068 shell=False,
1069 stdout=subprocess.PIPE).communicate()[0]
1070 snapshots = False
1071 columns = None
1072 lines = qemu_output.strip().split('\n')
1073 for line in lines:
1074 if line.startswith('Snapshot list:'):
1075 snapshots = True
1076 continue
1077
1078 # If this is a copy-on-write image, then the backing file
1079 # represents the base image
1080 #
1081 # backing file: base.qcow2 (actual path: /var/shared/base.qcow2)
1082 elif line.startswith('backing file'):
1083 matches = re.match(r'.*\(actual path: (.*?)\)', line)
1084 if matches:
1085 output.append('backing file: {0}'.format(matches.group(1)))
1086 continue
1087
1088 elif snapshots:
1089 if line.startswith('ID'): # Do not parse table headers
1090 line = line.replace('VM SIZE', 'VMSIZE')
1091 line = line.replace('VM CLOCK', 'TIME VMCLOCK')
1092 columns = re.split(r'\s+', line)
1093 columns = [c.lower() for c in columns]
1094 output.append('snapshots:')
1095 continue
1096 fields = re.split(r'\s+', line)
1097 for i, field in enumerate(fields):
1098 sep = ' '
1099 if i == 0:
1100 sep = '-'
1101 output.append(
1102 '{0} {1}: "{2}"'.format(
1103 sep, columns[i], field
1104 )
1105 )
1106 continue
1107 output.append(line)
1108 output = '\n'.join(output)
1109 disks[dev].update(yaml.safe_load(output))
1110 except TypeError:
1111 disks[dev].update(yaml.safe_load('image: Does not exist'))
1112 return disks
1113
1114
1115def setmem(vm_, memory, config=False):
1116 '''
1117 Changes the amount of memory allocated to VM. The VM must be shutdown
1118 for this to work.
1119
1120 memory is to be specified in MB
1121 If config is True then we ask libvirt to modify the config as well
1122
1123 CLI Example:
1124
1125 .. code-block:: bash
1126
Ales Komarekf8188332016-03-09 11:32:08 +01001127 salt '*' virtng.setmem myvm 768
smolaon1fb381d2016-03-09 11:10:58 +01001128 '''
1129 if vm_state(vm_) != 'shutdown':
1130 return False
1131
1132 dom = _get_dom(vm_)
1133
1134 # libvirt has a funny bitwise system for the flags in that the flag
1135 # to affect the "current" setting is 0, which means that to set the
1136 # current setting we have to call it a second time with just 0 set
1137 flags = libvirt.VIR_DOMAIN_MEM_MAXIMUM
1138 if config:
1139 flags = flags | libvirt.VIR_DOMAIN_AFFECT_CONFIG
1140
1141 ret1 = dom.setMemoryFlags(memory * 1024, flags)
1142 ret2 = dom.setMemoryFlags(memory * 1024, libvirt.VIR_DOMAIN_AFFECT_CURRENT)
1143
1144 # return True if both calls succeeded
1145 return ret1 == ret2 == 0
1146
1147
1148def setvcpus(vm_, vcpus, config=False):
1149 '''
1150 Changes the amount of vcpus allocated to VM. The VM must be shutdown
1151 for this to work.
1152
1153 vcpus is an int representing the number to be assigned
1154 If config is True then we ask libvirt to modify the config as well
1155
1156 CLI Example:
1157
1158 .. code-block:: bash
1159
Ales Komarekf8188332016-03-09 11:32:08 +01001160 salt '*' virtng.setvcpus myvm 2
smolaon1fb381d2016-03-09 11:10:58 +01001161 '''
1162 if vm_state(vm_) != 'shutdown':
1163 return False
1164
1165 dom = _get_dom(vm_)
1166
1167 # see notes in setmem
1168 flags = libvirt.VIR_DOMAIN_VCPU_MAXIMUM
1169 if config:
1170 flags = flags | libvirt.VIR_DOMAIN_AFFECT_CONFIG
1171
1172 ret1 = dom.setVcpusFlags(vcpus, flags)
1173 ret2 = dom.setVcpusFlags(vcpus, libvirt.VIR_DOMAIN_AFFECT_CURRENT)
1174
1175 return ret1 == ret2 == 0
1176
1177
1178def freemem():
1179 '''
1180 Return an int representing the amount of memory that has not been given
1181 to virtual machines on this node
1182
1183 CLI Example:
1184
1185 .. code-block:: bash
1186
Ales Komarekf8188332016-03-09 11:32:08 +01001187 salt '*' virtng.freemem
smolaon1fb381d2016-03-09 11:10:58 +01001188 '''
1189 conn = __get_conn()
1190 mem = conn.getInfo()[1]
1191 # Take off just enough to sustain the hypervisor
1192 mem -= 256
1193 for vm_ in list_vms():
1194 dom = _get_dom(vm_)
1195 if dom.ID() > 0:
1196 mem -= dom.info()[2] / 1024
1197 return mem
1198
1199
1200def freecpu():
1201 '''
1202 Return an int representing the number of unallocated cpus on this
1203 hypervisor
1204
1205 CLI Example:
1206
1207 .. code-block:: bash
1208
Ales Komarekf8188332016-03-09 11:32:08 +01001209 salt '*' virtng.freecpu
smolaon1fb381d2016-03-09 11:10:58 +01001210 '''
1211 conn = __get_conn()
1212 cpus = conn.getInfo()[2]
1213 for vm_ in list_vms():
1214 dom = _get_dom(vm_)
1215 if dom.ID() > 0:
1216 cpus -= dom.info()[3]
1217 return cpus
1218
1219
1220def full_info():
1221 '''
1222 Return the node_info, vm_info and freemem
1223
1224 CLI Example:
1225
1226 .. code-block:: bash
1227
Ales Komarekf8188332016-03-09 11:32:08 +01001228 salt '*' virtng.full_info
smolaon1fb381d2016-03-09 11:10:58 +01001229 '''
1230 return {'freecpu': freecpu(),
1231 'freemem': freemem(),
1232 'node_info': node_info(),
1233 'vm_info': vm_info()}
1234
1235
1236def get_xml(vm_):
1237 '''
1238 Returns the XML for a given vm
1239
1240 CLI Example:
1241
1242 .. code-block:: bash
1243
Ales Komarekf8188332016-03-09 11:32:08 +01001244 salt '*' virtng.get_xml <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001245 '''
1246 dom = _get_dom(vm_)
1247 return dom.XMLDesc(0)
1248
1249
1250def get_profiles(hypervisor=None):
1251 '''
1252 Return the virt profiles for hypervisor.
1253
1254 Currently there are profiles for:
1255
1256 - nic
1257 - disk
1258
1259 CLI Example:
1260
1261 .. code-block:: bash
1262
Ales Komarekf8188332016-03-09 11:32:08 +01001263 salt '*' virtng.get_profiles
1264 salt '*' virtng.get_profiles hypervisor=esxi
smolaon1fb381d2016-03-09 11:10:58 +01001265 '''
1266 ret = {}
1267 if hypervisor:
1268 hypervisor = hypervisor
1269 else:
1270 hypervisor = __salt__['config.get']('libvirt:hypervisor', VIRT_DEFAULT_HYPER)
1271 virtconf = __salt__['config.get']('virt', {})
1272 for typ in ['disk', 'nic']:
1273 _func = getattr(sys.modules[__name__], '_{0}_profile'.format(typ))
1274 ret[typ] = {'default': _func('default', hypervisor)}
1275 if typ in virtconf:
1276 ret.setdefault(typ, {})
1277 for prf in virtconf[typ]:
1278 ret[typ][prf] = _func(prf, hypervisor)
1279 return ret
1280
1281
1282def shutdown(vm_):
1283 '''
1284 Send a soft shutdown signal to the named vm
1285
1286 CLI Example:
1287
1288 .. code-block:: bash
1289
Ales Komarekf8188332016-03-09 11:32:08 +01001290 salt '*' virtng.shutdown <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001291 '''
1292 dom = _get_dom(vm_)
1293 return dom.shutdown() == 0
1294
1295
1296def pause(vm_):
1297 '''
1298 Pause the named vm
1299
1300 CLI Example:
1301
1302 .. code-block:: bash
1303
Ales Komarekf8188332016-03-09 11:32:08 +01001304 salt '*' virtng.pause <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001305 '''
1306 dom = _get_dom(vm_)
1307 return dom.suspend() == 0
1308
1309
1310def resume(vm_):
1311 '''
1312 Resume the named vm
1313
1314 CLI Example:
1315
1316 .. code-block:: bash
1317
Ales Komarekf8188332016-03-09 11:32:08 +01001318 salt '*' virtng.resume <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001319 '''
1320 dom = _get_dom(vm_)
1321 return dom.resume() == 0
1322
1323
1324def create(vm_):
1325 '''
1326 Start a defined domain
1327
1328 CLI Example:
1329
1330 .. code-block:: bash
1331
Ales Komarekf8188332016-03-09 11:32:08 +01001332 salt '*' virtng.create <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001333 '''
1334 dom = _get_dom(vm_)
1335 return dom.create() == 0
1336
1337
1338def start(vm_):
1339 '''
1340 Alias for the obscurely named 'create' function
1341
1342 CLI Example:
1343
1344 .. code-block:: bash
1345
Ales Komarekf8188332016-03-09 11:32:08 +01001346 salt '*' virtng.start <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001347 '''
1348 return create(vm_)
1349
1350
1351def stop(vm_):
1352 '''
1353 Alias for the obscurely named 'destroy' function
1354
1355 CLI Example:
1356
1357 .. code-block:: bash
1358
Ales Komarekf8188332016-03-09 11:32:08 +01001359 salt '*' virtng.stop <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001360 '''
1361 return destroy(vm_)
1362
1363
1364def reboot(vm_):
1365 '''
1366 Reboot a domain via ACPI request
1367
1368 CLI Example:
1369
1370 .. code-block:: bash
1371
Ales Komarekf8188332016-03-09 11:32:08 +01001372 salt '*' virtng.reboot <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001373 '''
1374 dom = _get_dom(vm_)
1375
1376 # reboot has a few modes of operation, passing 0 in means the
1377 # hypervisor will pick the best method for rebooting
1378 return dom.reboot(0) == 0
1379
1380
1381def reset(vm_):
1382 '''
1383 Reset a VM by emulating the reset button on a physical machine
1384
1385 CLI Example:
1386
1387 .. code-block:: bash
1388
Ales Komarekf8188332016-03-09 11:32:08 +01001389 salt '*' virtng.reset <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001390 '''
1391 dom = _get_dom(vm_)
1392
1393 # reset takes a flag, like reboot, but it is not yet used
1394 # so we just pass in 0
1395 # see: http://libvirt.org/html/libvirt-libvirt.html#virDomainReset
1396 return dom.reset(0) == 0
1397
1398
1399def ctrl_alt_del(vm_):
1400 '''
1401 Sends CTRL+ALT+DEL to a VM
1402
1403 CLI Example:
1404
1405 .. code-block:: bash
1406
Ales Komarekf8188332016-03-09 11:32:08 +01001407 salt '*' virtng.ctrl_alt_del <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001408 '''
1409 dom = _get_dom(vm_)
1410 return dom.sendKey(0, 0, [29, 56, 111], 3, 0) == 0
1411
1412
1413def create_xml_str(xml):
1414 '''
1415 Start a domain based on the XML passed to the function
1416
1417 CLI Example:
1418
1419 .. code-block:: bash
1420
Ales Komarekf8188332016-03-09 11:32:08 +01001421 salt '*' virtng.create_xml_str <XML in string format>
smolaon1fb381d2016-03-09 11:10:58 +01001422 '''
1423 conn = __get_conn()
1424 return conn.createXML(xml, 0) is not None
1425
1426
1427def create_xml_path(path):
1428 '''
1429 Start a domain based on the XML-file path passed to the function
1430
1431 CLI Example:
1432
1433 .. code-block:: bash
1434
Ales Komarekf8188332016-03-09 11:32:08 +01001435 salt '*' virtng.create_xml_path <path to XML file on the node>
smolaon1fb381d2016-03-09 11:10:58 +01001436 '''
1437 if not os.path.isfile(path):
1438 return False
1439 return create_xml_str(salt.utils.fopen(path, 'r').read())
1440
1441
1442def define_xml_str(xml):
1443 '''
1444 Define a domain based on the XML passed to the function
1445
1446 CLI Example:
1447
1448 .. code-block:: bash
1449
Ales Komarekf8188332016-03-09 11:32:08 +01001450 salt '*' virtng.define_xml_str <XML in string format>
smolaon1fb381d2016-03-09 11:10:58 +01001451 '''
1452 conn = __get_conn()
1453 return conn.defineXML(xml) is not None
1454
1455
1456def define_xml_path(path):
1457 '''
1458 Define a domain based on the XML-file path passed to the function
1459
1460 CLI Example:
1461
1462 .. code-block:: bash
1463
Ales Komarekf8188332016-03-09 11:32:08 +01001464 salt '*' virtng.define_xml_path <path to XML file on the node>
smolaon1fb381d2016-03-09 11:10:58 +01001465
1466 '''
1467 if not os.path.isfile(path):
1468 return False
1469 return define_xml_str(salt.utils.fopen(path, 'r').read())
1470
1471
1472def define_vol_xml_str(xml):
1473 '''
1474 Define a volume based on the XML passed to the function
1475
1476 CLI Example:
1477
1478 .. code-block:: bash
1479
Ales Komarekf8188332016-03-09 11:32:08 +01001480 salt '*' virtng.define_vol_xml_str <XML in string format>
smolaon1fb381d2016-03-09 11:10:58 +01001481 '''
1482 poolname = __salt__['config.get']('libvirt:storagepool', 'default')
1483 conn = __get_conn()
1484 pool = conn.storagePoolLookupByName(str(poolname))
1485 return pool.createXML(xml, 0) is not None
1486
1487
1488def define_vol_xml_path(path):
1489 '''
1490 Define a volume based on the XML-file path passed to the function
1491
1492 CLI Example:
1493
1494 .. code-block:: bash
1495
Ales Komarekf8188332016-03-09 11:32:08 +01001496 salt '*' virtng.define_vol_xml_path <path to XML file on the node>
smolaon1fb381d2016-03-09 11:10:58 +01001497
1498 '''
1499 if not os.path.isfile(path):
1500 return False
1501 return define_vol_xml_str(salt.utils.fopen(path, 'r').read())
1502
1503
1504def migrate_non_shared(vm_, target, ssh=False):
1505 '''
1506 Attempt to execute non-shared storage "all" migration
1507
1508 CLI Example:
1509
1510 .. code-block:: bash
1511
Ales Komarekf8188332016-03-09 11:32:08 +01001512 salt '*' virtng.migrate_non_shared <vm name> <target hypervisor>
smolaon1fb381d2016-03-09 11:10:58 +01001513 '''
1514 cmd = _get_migrate_command() + ' --copy-storage-all ' + vm_\
1515 + _get_target(target, ssh)
1516
1517 return subprocess.Popen(cmd,
1518 shell=True,
1519 stdout=subprocess.PIPE).communicate()[0]
1520
1521
1522def migrate_non_shared_inc(vm_, target, ssh=False):
1523 '''
1524 Attempt to execute non-shared storage "all" migration
1525
1526 CLI Example:
1527
1528 .. code-block:: bash
1529
Ales Komarekf8188332016-03-09 11:32:08 +01001530 salt '*' virtng.migrate_non_shared_inc <vm name> <target hypervisor>
smolaon1fb381d2016-03-09 11:10:58 +01001531 '''
1532 cmd = _get_migrate_command() + ' --copy-storage-inc ' + vm_\
1533 + _get_target(target, ssh)
1534
1535 return subprocess.Popen(cmd,
1536 shell=True,
1537 stdout=subprocess.PIPE).communicate()[0]
1538
1539
1540def migrate(vm_, target, ssh=False):
1541 '''
1542 Shared storage migration
1543
1544 CLI Example:
1545
1546 .. code-block:: bash
1547
Ales Komarekf8188332016-03-09 11:32:08 +01001548 salt '*' virtng.migrate <vm name> <target hypervisor>
smolaon1fb381d2016-03-09 11:10:58 +01001549 '''
1550 cmd = _get_migrate_command() + ' ' + vm_\
1551 + _get_target(target, ssh)
1552
1553 return subprocess.Popen(cmd,
1554 shell=True,
1555 stdout=subprocess.PIPE).communicate()[0]
1556
1557
1558def seed_non_shared_migrate(disks, force=False):
1559 '''
1560 Non shared migration requires that the disks be present on the migration
1561 destination, pass the disks information via this function, to the
1562 migration destination before executing the migration.
1563
1564 CLI Example:
1565
1566 .. code-block:: bash
1567
Ales Komarekf8188332016-03-09 11:32:08 +01001568 salt '*' virtng.seed_non_shared_migrate <disks>
smolaon1fb381d2016-03-09 11:10:58 +01001569 '''
1570 for _, data in disks.items():
1571 fn_ = data['file']
1572 form = data['file format']
1573 size = data['virtual size'].split()[1][1:]
1574 if os.path.isfile(fn_) and not force:
1575 # the target exists, check to see if it is compatible
1576 pre = yaml.safe_load(subprocess.Popen('qemu-img info arch',
1577 shell=True,
1578 stdout=subprocess.PIPE).communicate()[0])
1579 if pre['file format'] != data['file format']\
1580 and pre['virtual size'] != data['virtual size']:
1581 return False
1582 if not os.path.isdir(os.path.dirname(fn_)):
1583 os.makedirs(os.path.dirname(fn_))
1584 if os.path.isfile(fn_):
1585 os.remove(fn_)
1586 cmd = 'qemu-img create -f ' + form + ' ' + fn_ + ' ' + size
1587 subprocess.call(cmd, shell=True)
1588 creds = _libvirt_creds()
1589 cmd = 'chown ' + creds['user'] + ':' + creds['group'] + ' ' + fn_
1590 subprocess.call(cmd, shell=True)
1591 return True
1592
1593
1594def set_autostart(vm_, state='on'):
1595 '''
1596 Set the autostart flag on a VM so that the VM will start with the host
1597 system on reboot.
1598
1599 CLI Example:
1600
1601 .. code-block:: bash
1602
1603 salt "*" virt.set_autostart <vm name> <on | off>
1604 '''
1605
1606 dom = _get_dom(vm_)
1607
1608 if state == 'on':
1609 return dom.setAutostart(1) == 0
1610
1611 elif state == 'off':
1612 return dom.setAutostart(0) == 0
1613
1614 else:
1615 # return False if state is set to something other then on or off
1616 return False
1617
1618
1619def destroy(vm_):
1620 '''
1621 Hard power down the virtual machine, this is equivalent to pulling the
1622 power
1623
1624 CLI Example:
1625
1626 .. code-block:: bash
1627
Ales Komarekf8188332016-03-09 11:32:08 +01001628 salt '*' virtng.destroy <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001629 '''
1630 dom = _get_dom(vm_)
1631 return dom.destroy() == 0
1632
1633
1634def undefine(vm_):
1635 '''
1636 Remove a defined vm, this does not purge the virtual machine image, and
1637 this only works if the vm is powered down
1638
1639 CLI Example:
1640
1641 .. code-block:: bash
1642
Ales Komarekf8188332016-03-09 11:32:08 +01001643 salt '*' virtng.undefine <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001644 '''
1645 dom = _get_dom(vm_)
Alexandru Avadanii00f187a2018-06-24 20:36:44 +02001646 if getattr(libvirt, 'VIR_DOMAIN_UNDEFINE_NVRAM', False):
1647 # This one is only in 1.2.8+
1648 return dom.undefineFlags(libvirt.VIR_DOMAIN_UNDEFINE_NVRAM) == 0
1649 else:
1650 return dom.undefine() == 0
smolaon1fb381d2016-03-09 11:10:58 +01001651
1652
1653def purge(vm_, dirs=False):
1654 '''
1655 Recursively destroy and delete a virtual machine, pass True for dir's to
1656 also delete the directories containing the virtual machine disk images -
1657 USE WITH EXTREME CAUTION!
1658
1659 CLI Example:
1660
1661 .. code-block:: bash
1662
Ales Komarekf8188332016-03-09 11:32:08 +01001663 salt '*' virtng.purge <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001664 '''
1665 disks = get_disks(vm_)
1666 try:
1667 if not destroy(vm_):
1668 return False
1669 except libvirt.libvirtError:
1670 # This is thrown if the machine is already shut down
1671 pass
1672 directories = set()
1673 for disk in disks:
1674 os.remove(disks[disk]['file'])
1675 directories.add(os.path.dirname(disks[disk]['file']))
1676 if dirs:
1677 for dir_ in directories:
1678 shutil.rmtree(dir_)
1679 undefine(vm_)
1680 return True
1681
1682
1683def virt_type():
1684 '''
1685 Returns the virtual machine type as a string
1686
1687 CLI Example:
1688
1689 .. code-block:: bash
1690
Ales Komarekf8188332016-03-09 11:32:08 +01001691 salt '*' virtng.virt_type
smolaon1fb381d2016-03-09 11:10:58 +01001692 '''
1693 return __grains__['virtual']
1694
1695
1696def is_kvm_hyper():
1697 '''
1698 Returns a bool whether or not this node is a KVM hypervisor
1699
1700 CLI Example:
1701
1702 .. code-block:: bash
1703
Ales Komarekf8188332016-03-09 11:32:08 +01001704 salt '*' virtng.is_kvm_hyper
smolaon1fb381d2016-03-09 11:10:58 +01001705 '''
1706 try:
1707 if 'kvm_' not in salt.utils.fopen('/proc/modules').read():
1708 return False
1709 except IOError:
1710 # No /proc/modules? Are we on Windows? Or Solaris?
1711 return False
Oleh Hryhorov7c5dfd32018-01-19 15:43:44 +02001712 return 'libvirtd' in __salt__['cmd.shell'](__grains__['ps'])
smolaon1fb381d2016-03-09 11:10:58 +01001713
1714
1715def is_xen_hyper():
1716 '''
1717 Returns a bool whether or not this node is a XEN hypervisor
1718
1719 CLI Example:
1720
1721 .. code-block:: bash
1722
Ales Komarekf8188332016-03-09 11:32:08 +01001723 salt '*' virtng.is_xen_hyper
smolaon1fb381d2016-03-09 11:10:58 +01001724 '''
1725 try:
1726 if __grains__['virtual_subtype'] != 'Xen Dom0':
1727 return False
1728 except KeyError:
1729 # virtual_subtype isn't set everywhere.
1730 return False
1731 try:
1732 if 'xen_' not in salt.utils.fopen('/proc/modules').read():
1733 return False
1734 except IOError:
1735 # No /proc/modules? Are we on Windows? Or Solaris?
1736 return False
Oleh Hryhorov7c5dfd32018-01-19 15:43:44 +02001737 return 'libvirtd' in __salt__['cmd.shell'](__grains__['ps'])
smolaon1fb381d2016-03-09 11:10:58 +01001738
1739
1740def is_hyper():
1741 '''
1742 Returns a bool whether or not this node is a hypervisor of any kind
1743
1744 CLI Example:
1745
1746 .. code-block:: bash
1747
Ales Komarekf8188332016-03-09 11:32:08 +01001748 salt '*' virtng.is_hyper
smolaon1fb381d2016-03-09 11:10:58 +01001749 '''
1750 try:
1751 import libvirt # pylint: disable=import-error
1752 except ImportError:
1753 # not a usable hypervisor without libvirt module
1754 return False
1755 return is_xen_hyper() or is_kvm_hyper()
1756
1757
1758def vm_cputime(vm_=None):
1759 '''
1760 Return cputime used by the vms on this hyper in a
1761 list of dicts:
1762
1763 .. code-block:: python
1764
1765 [
1766 'your-vm': {
1767 'cputime' <int>
1768 'cputime_percent' <int>
1769 },
1770 ...
1771 ]
1772
1773 If you pass a VM name in as an argument then it will return info
1774 for just the named VM, otherwise it will return all VMs.
1775
1776 CLI Example:
1777
1778 .. code-block:: bash
1779
Ales Komarekf8188332016-03-09 11:32:08 +01001780 salt '*' virtng.vm_cputime
smolaon1fb381d2016-03-09 11:10:58 +01001781 '''
1782 host_cpus = __get_conn().getInfo()[2]
1783
1784 def _info(vm_):
1785 dom = _get_dom(vm_)
1786 raw = dom.info()
1787 vcpus = int(raw[3])
1788 cputime = int(raw[4])
1789 cputime_percent = 0
1790 if cputime:
1791 # Divide by vcpus to always return a number between 0 and 100
1792 cputime_percent = (1.0e-7 * cputime / host_cpus) / vcpus
1793 return {
1794 'cputime': int(raw[4]),
1795 'cputime_percent': int('{0:.0f}'.format(cputime_percent))
1796 }
1797 info = {}
1798 if vm_:
1799 info[vm_] = _info(vm_)
1800 else:
1801 for vm_ in list_vms():
1802 info[vm_] = _info(vm_)
1803 return info
1804
1805
1806def vm_netstats(vm_=None):
1807 '''
1808 Return combined network counters used by the vms on this hyper in a
1809 list of dicts:
1810
1811 .. code-block:: python
1812
1813 [
1814 'your-vm': {
1815 'rx_bytes' : 0,
1816 'rx_packets' : 0,
1817 'rx_errs' : 0,
1818 'rx_drop' : 0,
1819 'tx_bytes' : 0,
1820 'tx_packets' : 0,
1821 'tx_errs' : 0,
1822 'tx_drop' : 0
1823 },
1824 ...
1825 ]
1826
1827 If you pass a VM name in as an argument then it will return info
1828 for just the named VM, otherwise it will return all VMs.
1829
1830 CLI Example:
1831
1832 .. code-block:: bash
1833
Ales Komarekf8188332016-03-09 11:32:08 +01001834 salt '*' virtng.vm_netstats
smolaon1fb381d2016-03-09 11:10:58 +01001835 '''
1836 def _info(vm_):
1837 dom = _get_dom(vm_)
1838 nics = get_nics(vm_)
1839 ret = {
1840 'rx_bytes': 0,
1841 'rx_packets': 0,
1842 'rx_errs': 0,
1843 'rx_drop': 0,
1844 'tx_bytes': 0,
1845 'tx_packets': 0,
1846 'tx_errs': 0,
1847 'tx_drop': 0
1848 }
1849 for attrs in six.itervalues(nics):
1850 if 'target' in attrs:
1851 dev = attrs['target']
1852 stats = dom.interfaceStats(dev)
1853 ret['rx_bytes'] += stats[0]
1854 ret['rx_packets'] += stats[1]
1855 ret['rx_errs'] += stats[2]
1856 ret['rx_drop'] += stats[3]
1857 ret['tx_bytes'] += stats[4]
1858 ret['tx_packets'] += stats[5]
1859 ret['tx_errs'] += stats[6]
1860 ret['tx_drop'] += stats[7]
1861
1862 return ret
1863 info = {}
1864 if vm_:
1865 info[vm_] = _info(vm_)
1866 else:
1867 for vm_ in list_vms():
1868 info[vm_] = _info(vm_)
1869 return info
1870
1871
1872def vm_diskstats(vm_=None):
1873 '''
1874 Return disk usage counters used by the vms on this hyper in a
1875 list of dicts:
1876
1877 .. code-block:: python
1878
1879 [
1880 'your-vm': {
1881 'rd_req' : 0,
1882 'rd_bytes' : 0,
1883 'wr_req' : 0,
1884 'wr_bytes' : 0,
1885 'errs' : 0
1886 },
1887 ...
1888 ]
1889
1890 If you pass a VM name in as an argument then it will return info
1891 for just the named VM, otherwise it will return all VMs.
1892
1893 CLI Example:
1894
1895 .. code-block:: bash
1896
Ales Komarekf8188332016-03-09 11:32:08 +01001897 salt '*' virtng.vm_blockstats
smolaon1fb381d2016-03-09 11:10:58 +01001898 '''
1899 def get_disk_devs(vm_):
1900 doc = minidom.parse(_StringIO(get_xml(vm_)))
1901 disks = []
1902 for elem in doc.getElementsByTagName('disk'):
1903 targets = elem.getElementsByTagName('target')
1904 target = targets[0]
1905 disks.append(target.getAttribute('dev'))
1906 return disks
1907
1908 def _info(vm_):
1909 dom = _get_dom(vm_)
1910 # Do not use get_disks, since it uses qemu-img and is very slow
1911 # and unsuitable for any sort of real time statistics
1912 disks = get_disk_devs(vm_)
1913 ret = {'rd_req': 0,
1914 'rd_bytes': 0,
1915 'wr_req': 0,
1916 'wr_bytes': 0,
1917 'errs': 0
1918 }
1919 for disk in disks:
1920 stats = dom.blockStats(disk)
1921 ret['rd_req'] += stats[0]
1922 ret['rd_bytes'] += stats[1]
1923 ret['wr_req'] += stats[2]
1924 ret['wr_bytes'] += stats[3]
1925 ret['errs'] += stats[4]
1926
1927 return ret
1928 info = {}
1929 if vm_:
1930 info[vm_] = _info(vm_)
1931 else:
1932 # Can not run function blockStats on inactive VMs
1933 for vm_ in list_active_vms():
1934 info[vm_] = _info(vm_)
1935 return info