blob: 2c6bc9db707702fa9a6bbcdb081fc36f0d2cf69d [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
Martin Horak0d183cb2018-09-14 16:11:08 +0200239 if 'enable_vnc' in kwargs:
240 context['enable_vnc'] = kwargs['enable_vnc']
241 log.info('VNC enabled: {0}.'.format(kwargs['enable_vnc']))
smolaon1fb381d2016-03-09 11:10:58 +0100242 if 'serial_type' in kwargs:
243 context['serial_type'] = kwargs['serial_type']
244 if 'serial_type' in context and context['serial_type'] == 'tcp':
245 if 'telnet_port' in kwargs:
246 context['telnet_port'] = kwargs['telnet_port']
247 else:
248 context['telnet_port'] = 23023 # FIXME: use random unused port
249 if 'serial_type' in context:
250 if 'console' in kwargs:
251 context['console'] = kwargs['console']
252 else:
253 context['console'] = True
254
255 context['disks'] = {}
256 for i, disk in enumerate(diskp):
257 for disk_name, args in disk.items():
258 context['disks'][disk_name] = {}
259 fn_ = '{0}.{1}'.format(disk_name, args['format'])
260 context['disks'][disk_name]['file_name'] = fn_
261 context['disks'][disk_name]['source_file'] = os.path.join(args['pool'],
262 name,
263 fn_)
264 if hypervisor in ['qemu', 'kvm']:
265 context['disks'][disk_name]['target_dev'] = 'vd{0}'.format(string.ascii_lowercase[i])
266 context['disks'][disk_name]['address'] = False
267 context['disks'][disk_name]['driver'] = True
268 elif hypervisor in ['esxi', 'vmware']:
269 context['disks'][disk_name]['target_dev'] = 'sd{0}'.format(string.ascii_lowercase[i])
270 context['disks'][disk_name]['address'] = True
271 context['disks'][disk_name]['driver'] = False
272 context['disks'][disk_name]['disk_bus'] = args['model']
273 context['disks'][disk_name]['type'] = args['format']
274 context['disks'][disk_name]['index'] = str(i)
275
276 context['nics'] = nicp
277
278 fn_ = 'libvirt_domain.jinja'
279 try:
280 template = JINJA.get_template(fn_)
281 except jinja2.exceptions.TemplateNotFound:
282 log.error('Could not load template {0}'.format(fn_))
283 return ''
284
285 return template.render(**context)
286
287
288def _gen_vol_xml(vmname,
289 diskname,
290 size,
291 hypervisor,
292 **kwargs):
293 '''
294 Generate the XML string to define a libvirt storage volume
295 '''
296 size = int(size) * 1024 # MB
297 disk_info = _get_image_info(hypervisor, vmname, **kwargs)
298 context = {
299 'name': vmname,
300 'filename': '{0}.{1}'.format(diskname, disk_info['disktype']),
301 'volname': diskname,
302 'disktype': disk_info['disktype'],
303 'size': str(size),
304 'pool': disk_info['pool'],
305 }
306 fn_ = 'libvirt_volume.jinja'
307 try:
308 template = JINJA.get_template(fn_)
309 except jinja2.exceptions.TemplateNotFound:
310 log.error('Could not load template {0}'.format(fn_))
311 return ''
312 return template.render(**context)
313
314
315def _qemu_image_info(path):
316 '''
317 Detect information for the image at path
318 '''
319 ret = {}
Oleh Hryhorov7c5dfd32018-01-19 15:43:44 +0200320 out = __salt__['cmd.shell']('qemu-img info {0}'.format(path))
smolaon1fb381d2016-03-09 11:10:58 +0100321
322 match_map = {'size': r'virtual size: \w+ \((\d+) byte[s]?\)',
323 'format': r'file format: (\w+)'}
324
325 for info, search in match_map.items():
326 try:
327 ret[info] = re.search(search, out).group(1)
328 except AttributeError:
329 continue
330 return ret
331
332
333# TODO: this function is deprecated, should be replaced with
334# _qemu_image_info()
335def _image_type(vda):
336 '''
337 Detect what driver needs to be used for the given image
338 '''
Oleh Hryhorov7c5dfd32018-01-19 15:43:44 +0200339 out = __salt__['cmd.shell']('qemu-img info {0}'.format(vda))
smolaon1fb381d2016-03-09 11:10:58 +0100340 if 'file format: qcow2' in out:
341 return 'qcow2'
342 else:
343 return 'raw'
344
345
346# TODO: this function is deprecated, should be merged and replaced
347# with _disk_profile()
348def _get_image_info(hypervisor, name, **kwargs):
349 '''
350 Determine disk image info, such as filename, image format and
351 storage pool, based on which hypervisor is used
352 '''
353 ret = {}
354 if hypervisor in ['esxi', 'vmware']:
355 ret['disktype'] = 'vmdk'
356 ret['filename'] = '{0}{1}'.format(name, '.vmdk')
357 ret['pool'] = '[{0}] '.format(kwargs.get('pool', '0'))
358 elif hypervisor in ['kvm', 'qemu']:
359 ret['disktype'] = 'qcow2'
360 ret['filename'] = '{0}{1}'.format(name, '.qcow2')
Mateusz Los4c7cd2d2018-01-09 11:46:07 +0100361 if 'img_dest' in kwargs:
362 ret['pool'] = kwargs['img_dest']
363 else:
364 ret['pool'] = __salt__['config.option']('virt.images')
smolaon1fb381d2016-03-09 11:10:58 +0100365 return ret
366
367
368def _disk_profile(profile, hypervisor, **kwargs):
369 '''
370 Gather the disk profile from the config or apply the default based
371 on the active hypervisor
372
373 This is the ``default`` profile for KVM/QEMU, which can be
374 overridden in the configuration:
375
376 .. code-block:: yaml
377
378 virt:
379 disk:
380 default:
381 - system:
382 size: 8192
383 format: qcow2
384 model: virtio
385
Dennis Dmitrieva57463c2017-03-24 17:05:22 +0200386 Example profile for KVM/QEMU with two disks, first is created
387 from specified image, the second is empty:
388
389 .. code-block:: yaml
390
391 virt:
392 disk:
393 two_disks:
394 - system:
395 size: 8192
396 format: qcow2
397 model: virtio
398 image: http://path/to/image.qcow2
399 - lvm:
400 size: 32768
401 format: qcow2
402 model: virtio
403
smolaon1fb381d2016-03-09 11:10:58 +0100404 The ``format`` and ``model`` parameters are optional, and will
405 default to whatever is best suitable for the active hypervisor.
406 '''
407 default = [
408 {'system':
409 {'size': '8192'}
410 }
411 ]
412 if hypervisor in ['esxi', 'vmware']:
413 overlay = {'format': 'vmdk',
414 'model': 'scsi',
415 'pool': '[{0}] '.format(kwargs.get('pool', '0'))
416 }
417 elif hypervisor in ['qemu', 'kvm']:
Mateusz Los4c7cd2d2018-01-09 11:46:07 +0100418 if 'img_dest' in kwargs:
419 pool = kwargs['img_dest']
420 else:
421 pool = __salt__['config.option']('virt.images')
422 overlay = {'format': 'qcow2', 'model': 'virtio', 'pool': pool}
smolaon1fb381d2016-03-09 11:10:58 +0100423 else:
424 overlay = {}
425
Dennis Dmitrievf5dba8c2017-10-10 19:05:20 +0300426 disklist = copy.deepcopy(__salt__['config.get']('virt:disk', {}).get(profile, default))
smolaon1fb381d2016-03-09 11:10:58 +0100427 for key, val in overlay.items():
428 for i, disks in enumerate(disklist):
429 for disk in disks:
430 if key not in disks[disk]:
431 disklist[i][disk][key] = val
432 return disklist
433
434
435def _nic_profile(profile_name, hypervisor, **kwargs):
436
smolaon1fb381d2016-03-09 11:10:58 +0100437 def append_dict_profile_to_interface_list(profile_dict):
438 for interface_name, attributes in profile_dict.items():
439 attributes['name'] = interface_name
440 interfaces.append(attributes)
441
smolaon1fb381d2016-03-09 11:10:58 +0100442 def _normalize_net_types(attributes):
443 '''
444 Guess which style of definition:
445
446 bridge: br0
447
448 or
449
450 network: net0
451
452 or
453
454 type: network
455 source: net0
456 '''
457 for type_ in ['bridge', 'network']:
458 if type_ in attributes:
459 attributes['type'] = type_
460 # we want to discard the original key
461 attributes['source'] = attributes.pop(type_)
462
463 attributes['type'] = attributes.get('type', None)
464 attributes['source'] = attributes.get('source', None)
Dzmitry Stremkouskib8acf1f2018-06-28 12:56:23 +0200465 attributes['virtualport'] = attributes.get('virtualport', None)
smolaon1fb381d2016-03-09 11:10:58 +0100466
467 def _apply_default_overlay(attributes):
468 for key, value in overlays[hypervisor].items():
469 if key not in attributes or not attributes[key]:
470 attributes[key] = value
471
472 def _assign_mac(attributes):
473 dmac = '{0}_mac'.format(attributes['name'])
474 if dmac in kwargs:
475 dmac = kwargs[dmac]
476 if salt.utils.validate.net.mac(dmac):
477 attributes['mac'] = dmac
478 else:
479 msg = 'Malformed MAC address: {0}'.format(dmac)
480 raise CommandExecutionError(msg)
481 else:
482 attributes['mac'] = salt.utils.gen_mac()
483
Dzmitry Stremkouskib8acf1f2018-06-28 12:56:23 +0200484
485 default = [{'eth0': {}}]
486 vmware_overlay = {'type': 'bridge', 'source': 'DEFAULT', 'model': 'e1000'}
487 kvm_overlay = {'type': 'bridge', 'source': 'br0', 'model': 'virtio'}
488 overlays = {
489 'kvm': kvm_overlay,
490 'qemu': kvm_overlay,
491 'esxi': vmware_overlay,
492 'vmware': vmware_overlay,
493 }
494
495 # support old location
496 config_data = __salt__['config.option']('virt.nic', {}).get(
497 profile_name, None
498 )
499
500 if config_data is None:
501 config_data = __salt__['config.get']('virt:nic', {}).get(
502 profile_name, default
503 )
504
505 interfaces = []
506
507 if isinstance(config_data, dict):
508 append_dict_profile_to_interface_list(config_data)
509
510 elif isinstance(config_data, list):
511 for interface in config_data:
512 if isinstance(interface, dict):
513 if len(interface) == 1:
514 append_dict_profile_to_interface_list(interface)
515 else:
516 interfaces.append(interface)
517
smolaon1fb381d2016-03-09 11:10:58 +0100518 for interface in interfaces:
519 _normalize_net_types(interface)
520 _assign_mac(interface)
521 if hypervisor in overlays:
522 _apply_default_overlay(interface)
523
524 return interfaces
525
526
527def init(name,
528 cpu,
529 mem,
530 image=None,
531 nic='default',
532 hypervisor=VIRT_DEFAULT_HYPER,
533 start=True, # pylint: disable=redefined-outer-name
534 disk='default',
535 saltenv='base',
azvyagintseva4e802d2018-05-04 20:16:02 +0300536 rng=None,
Alexandru Avadanii00f187a2018-06-24 20:36:44 +0200537 loader=None,
538 machine=None,
539 cpu_mode=None,
Pavel Cizinskyf03c4e82018-09-10 14:56:11 +0200540 cpuset=None,
smolaon1fb381d2016-03-09 11:10:58 +0100541 **kwargs):
542 '''
543 Initialize a new vm
544
545 CLI Example:
546
547 .. code-block:: bash
548
549 salt 'hypervisor' virt.init vm_name 4 512 salt://path/to/image.raw
550 salt 'hypervisor' virt.init vm_name 4 512 nic=profile disk=profile
551 '''
Dennis Dmitrieva57463c2017-03-24 17:05:22 +0200552
azvyagintseva4e802d2018-05-04 20:16:02 +0300553 rng = rng or {'backend':'/dev/urandom'}
smolaon1fb381d2016-03-09 11:10:58 +0100554 hypervisor = __salt__['config.get']('libvirt:hypervisor', hypervisor)
555
556 nicp = _nic_profile(nic, hypervisor, **kwargs)
557
Dennis Dmitrieva57463c2017-03-24 17:05:22 +0200558 diskp = _disk_profile(disk, hypervisor, **kwargs)
559
560 if image:
561 # Backward compatibility: if 'image' is specified in the VMs arguments
562 # instead of a disk arguments. In this case, 'image' will be assigned
563 # to the first disk for the VM.
smolaon1fb381d2016-03-09 11:10:58 +0100564 disk_name = next(diskp[0].iterkeys())
Dennis Dmitrieva57463c2017-03-24 17:05:22 +0200565 if not diskp[0][disk_name].get('image', None):
566 diskp[0][disk_name]['image'] = image
smolaon1fb381d2016-03-09 11:10:58 +0100567
Dennis Dmitrieva57463c2017-03-24 17:05:22 +0200568 # Create multiple disks, empty or from specified images.
Dzmitry Stremkouski97927ee2018-08-23 23:20:38 +0200569 cloud_init = None
570 cfg_drive = None
571
Dennis Dmitrieva57463c2017-03-24 17:05:22 +0200572 for disk in diskp:
573 log.debug("Creating disk for VM [ {0} ]: {1}".format(name, disk))
smolaon1fb381d2016-03-09 11:10:58 +0100574
Dennis Dmitrieva57463c2017-03-24 17:05:22 +0200575 for disk_name, args in disk.items():
smolaon1fb381d2016-03-09 11:10:58 +0100576
Dennis Dmitrieva57463c2017-03-24 17:05:22 +0200577 if hypervisor in ['esxi', 'vmware']:
578 if 'image' in args:
579 # TODO: we should be copying the image file onto the ESX host
580 raise SaltInvocationError('virt.init does not support image '
581 'template template in conjunction '
582 'with esxi hypervisor')
583 else:
584 # assume libvirt manages disks for us
smolaon1fb381d2016-03-09 11:10:58 +0100585 xml = _gen_vol_xml(name,
586 disk_name,
587 args['size'],
Mateusz Los4c7cd2d2018-01-09 11:46:07 +0100588 hypervisor,
589 **kwargs)
smolaon1fb381d2016-03-09 11:10:58 +0100590 define_vol_xml_str(xml)
591
Dennis Dmitrieva57463c2017-03-24 17:05:22 +0200592 elif hypervisor in ['qemu', 'kvm']:
593
594 disk_type = args['format']
595 disk_file_name = '{0}.{1}'.format(disk_name, disk_type)
596 # disk size TCP cloud
597 disk_size = args['size']
598
Mateusz Los4c7cd2d2018-01-09 11:46:07 +0100599 if 'img_dest' in kwargs:
600 img_dir = kwargs['img_dest']
601 else:
602 img_dir = __salt__['config.option']('virt.images')
Dennis Dmitrieva57463c2017-03-24 17:05:22 +0200603 img_dest = os.path.join(
604 img_dir,
605 name,
606 disk_file_name
607 )
608 img_dir = os.path.dirname(img_dest)
609 if not os.path.isdir(img_dir):
610 os.makedirs(img_dir)
611
612 if 'image' in args:
613 # Create disk from specified image
614 sfn = __salt__['cp.cache_file'](args['image'], saltenv)
615 try:
616 salt.utils.files.copyfile(sfn, img_dest)
617 mask = os.umask(0)
618 os.umask(mask)
619 # Apply umask and remove exec bit
620
621 # Resizing image TCP cloud
622 cmd = 'qemu-img resize ' + img_dest + ' ' + str(disk_size) + 'M'
623 subprocess.call(cmd, shell=True)
624
625 mode = (0o0777 ^ mask) & 0o0666
626 os.chmod(img_dest, mode)
627
628 except (IOError, OSError) as e:
629 raise CommandExecutionError('problem while copying image. {0} - {1}'.format(args['image'], e))
630
631 if kwargs.get('seed'):
Dzmitry Stremkouski97927ee2018-08-23 23:20:38 +0200632 seed_cmd = kwargs.get('seed_cmd', 'seedng.apply')
633 cloud_init = kwargs.get('cloud_init', None)
634 master = __salt__['config.option']('master')
635 cfg_drive = os.path.join(img_dir,'config-2.iso')
Dennis Dmitrieva57463c2017-03-24 17:05:22 +0200636
Dzmitry Stremkouski97927ee2018-08-23 23:20:38 +0200637 if cloud_init:
638 _tmp = name.split('.')
639
640 try:
641 user_data = json.dumps(cloud_init["user_data"])
642 except:
643 user_data = None
644
645 try:
646 network_data = json.dumps(cloud_init["network_data"])
647 except:
648 network_data = None
649
650 __salt__["cfgdrive.generate"](
651 dst = cfg_drive,
652 hostname = _tmp.pop(0),
653 domainname = '.'.join(_tmp),
654 user_data = user_data,
655 network_data = network_data,
656 saltconfig = { "salt_minion": { "conf": { "master": master, "id": name } } }
657 )
658 else:
659 __salt__[seed_cmd](
660 path = img_dest,
661 id_ = name,
662 config = kwargs.get('config'),
663 install = kwargs.get('install', True)
664 )
Dennis Dmitrieva57463c2017-03-24 17:05:22 +0200665 else:
666 # Create empty disk
667 try:
668 mask = os.umask(0)
669 os.umask(mask)
670 # Apply umask and remove exec bit
671
672 # Create empty image
673 cmd = 'qemu-img create -f ' + disk_type + ' ' + img_dest + ' ' + str(disk_size) + 'M'
674 subprocess.call(cmd, shell=True)
675
676 mode = (0o0777 ^ mask) & 0o0666
677 os.chmod(img_dest, mode)
678
679 except (IOError, OSError) as e:
680 raise CommandExecutionError('problem while creating volume {0} - {1}'.format(img_dest, e))
681
682 else:
683 # Unknown hypervisor
684 raise SaltInvocationError('Unsupported hypervisor when handling disk image: {0}'
685 .format(hypervisor))
686
smolaon1fb381d2016-03-09 11:10:58 +0100687 xml = _gen_xml(name, cpu, mem, diskp, nicp, hypervisor, **kwargs)
Dzmitry Stremkouski7ee23402018-04-10 00:43:48 +0200688
Dzmitry Stremkouski97927ee2018-08-23 23:20:38 +0200689 if cloud_init and cfg_drive:
690 xml_doc = minidom.parseString(xml)
691 iso_xml = xml_doc.createElement("disk")
692 iso_xml.setAttribute("type", "file")
693 iso_xml.setAttribute("device", "cdrom")
694 iso_xml.appendChild(xml_doc.createElement("readonly"))
695 driver = xml_doc.createElement("driver")
696 driver.setAttribute("name", "qemu")
697 driver.setAttribute("type", "raw")
698 target = xml_doc.createElement("target")
699 target.setAttribute("dev", "hdc")
700 target.setAttribute("bus", "ide")
701 source = xml_doc.createElement("source")
702 source.setAttribute("file", cfg_drive)
703 iso_xml.appendChild(driver)
704 iso_xml.appendChild(target)
705 iso_xml.appendChild(source)
706 xml_doc.getElementsByTagName("domain")[0].getElementsByTagName("devices")[0].appendChild(iso_xml)
707 xml = xml_doc.toxml()
708
Dzmitry Stremkouski7ee23402018-04-10 00:43:48 +0200709 # TODO: Remove this code and refactor module, when salt-common would have updated libvirt_domain.jinja template
Pavel Cizinskyf03c4e82018-09-10 14:56:11 +0200710 if cpuset:
711 xml_doc = minidom.parseString(xml)
712 xml_doc.getElementsByTagName("vcpu")[0].setAttribute('cpuset', cpuset)
713 xml = xml_doc.toxml()
714
715 # TODO: Remove this code and refactor module, when salt-common would have updated libvirt_domain.jinja template
Alexandru Avadanii00f187a2018-06-24 20:36:44 +0200716 if cpu_mode:
717 xml_doc = minidom.parseString(xml)
718 cpu_xml = xml_doc.createElement("cpu")
719 cpu_xml.setAttribute('mode', cpu_mode)
720 xml_doc.getElementsByTagName("domain")[0].appendChild(cpu_xml)
721 xml = xml_doc.toxml()
722
723 # TODO: Remove this code and refactor module, when salt-common would have updated libvirt_domain.jinja template
724 if machine:
725 xml_doc = minidom.parseString(xml)
726 os_xml = xml_doc.getElementsByTagName("domain")[0].getElementsByTagName("os")[0]
727 os_xml.getElementsByTagName("type")[0].setAttribute('machine', machine)
728 xml = xml_doc.toxml()
729
730 # TODO: Remove this code and refactor module, when salt-common would have updated libvirt_domain.jinja template
731 if loader and 'path' not in loader:
732 log.info('`path` is a required property of `loader`, and cannot be found. Skipping loader configuration')
733 loader = None
734 elif loader:
735 xml_doc = minidom.parseString(xml)
736 loader_xml = xml_doc.createElement("loader")
737 for key, val in loader.items():
738 if key == 'path':
739 continue
740 loader_xml.setAttribute(key, val)
741 loader_path_xml = xml_doc.createTextNode(loader['path'])
742 loader_xml.appendChild(loader_path_xml)
743 xml_doc.getElementsByTagName("domain")[0].getElementsByTagName("os")[0].appendChild(loader_xml)
744 xml = xml_doc.toxml()
745
746 # TODO: Remove this code and refactor module, when salt-common would have updated libvirt_domain.jinja template
Dzmitry Stremkouskib8acf1f2018-06-28 12:56:23 +0200747 for _nic in nicp:
748 if _nic['virtualport']:
749 xml_doc = minidom.parseString(xml)
750 interfaces = xml_doc.getElementsByTagName("domain")[0].getElementsByTagName("devices")[0].getElementsByTagName("interface")
751 for interface in interfaces:
752 if interface.getElementsByTagName('mac')[0].getAttribute('address').lower() == _nic['mac'].lower():
753 vport_xml = xml_doc.createElement("virtualport")
754 vport_xml.setAttribute("type", _nic['virtualport']['type'])
755 interface.appendChild(vport_xml)
756 xml = xml_doc.toxml()
757
758 # TODO: Remove this code and refactor module, when salt-common would have updated libvirt_domain.jinja template
Dzmitry Stremkouski7ee23402018-04-10 00:43:48 +0200759 if rng:
760 rng_model = rng.get('model', 'random')
761 rng_backend = rng.get('backend', '/dev/urandom')
762 xml_doc = minidom.parseString(xml)
763 rng_xml = xml_doc.createElement("rng")
764 rng_xml.setAttribute("model", "virtio")
765 backend = xml_doc.createElement("backend")
766 backend.setAttribute("model", rng_model)
767 backend.appendChild(xml_doc.createTextNode(rng_backend))
768 rng_xml.appendChild(backend)
769 if 'rate' in rng:
770 rng_rate_period = rng['rate'].get('period', '2000')
771 rng_rate_bytes = rng['rate'].get('bytes', '1234')
772 rate = xml_doc.createElement("rate")
773 rate.setAttribute("period", rng_rate_period)
774 rate.setAttribute("bytes", rng_rate_bytes)
775 rng_xml.appendChild(rate)
776 xml_doc.getElementsByTagName("domain")[0].getElementsByTagName("devices")[0].appendChild(rng_xml)
777 xml = xml_doc.toxml()
778
smolaon1fb381d2016-03-09 11:10:58 +0100779 define_xml_str(xml)
780
smolaon1fb381d2016-03-09 11:10:58 +0100781 if start:
782 create(name)
783
784 return True
785
786
787def list_vms():
788 '''
789 Return a list of virtual machine names on the minion
790
791 CLI Example:
792
793 .. code-block:: bash
794
Ales Komarekf8188332016-03-09 11:32:08 +0100795 salt '*' virtng.list_vms
smolaon1fb381d2016-03-09 11:10:58 +0100796 '''
797 vms = []
798 vms.extend(list_active_vms())
799 vms.extend(list_inactive_vms())
800 return vms
801
802
803def list_active_vms():
804 '''
805 Return a list of names for active virtual machine on the minion
806
807 CLI Example:
808
809 .. code-block:: bash
810
Ales Komarekf8188332016-03-09 11:32:08 +0100811 salt '*' virtng.list_active_vms
smolaon1fb381d2016-03-09 11:10:58 +0100812 '''
813 conn = __get_conn()
814 vms = []
815 for id_ in conn.listDomainsID():
816 vms.append(conn.lookupByID(id_).name())
817 return vms
818
819
820def list_inactive_vms():
821 '''
822 Return a list of names for inactive virtual machine on the minion
823
824 CLI Example:
825
826 .. code-block:: bash
827
Ales Komarekf8188332016-03-09 11:32:08 +0100828 salt '*' virtng.list_inactive_vms
smolaon1fb381d2016-03-09 11:10:58 +0100829 '''
830 conn = __get_conn()
831 vms = []
832 for id_ in conn.listDefinedDomains():
833 vms.append(id_)
834 return vms
835
836
837def vm_info(vm_=None):
838 '''
839 Return detailed information about the vms on this hyper in a
840 list of dicts:
841
842 .. code-block:: python
843
844 [
845 'your-vm': {
846 'cpu': <int>,
847 'maxMem': <int>,
848 'mem': <int>,
849 'state': '<state>',
850 'cputime' <int>
851 },
852 ...
853 ]
854
855 If you pass a VM name in as an argument then it will return info
856 for just the named VM, otherwise it will return all VMs.
857
858 CLI Example:
859
860 .. code-block:: bash
861
Ales Komarekf8188332016-03-09 11:32:08 +0100862 salt '*' virtng.vm_info
smolaon1fb381d2016-03-09 11:10:58 +0100863 '''
864 def _info(vm_):
865 dom = _get_dom(vm_)
866 raw = dom.info()
867 return {'cpu': raw[3],
868 'cputime': int(raw[4]),
869 'disks': get_disks(vm_),
870 'graphics': get_graphics(vm_),
871 'nics': get_nics(vm_),
872 'maxMem': int(raw[1]),
873 'mem': int(raw[2]),
874 'state': VIRT_STATE_NAME_MAP.get(raw[0], 'unknown')}
875 info = {}
876 if vm_:
877 info[vm_] = _info(vm_)
878 else:
879 for vm_ in list_vms():
880 info[vm_] = _info(vm_)
881 return info
882
883
884def vm_state(vm_=None):
885 '''
886 Return list of all the vms and their state.
887
888 If you pass a VM name in as an argument then it will return info
889 for just the named VM, otherwise it will return all VMs.
890
891 CLI Example:
892
893 .. code-block:: bash
894
Ales Komarekf8188332016-03-09 11:32:08 +0100895 salt '*' virtng.vm_state <vm name>
smolaon1fb381d2016-03-09 11:10:58 +0100896 '''
897 def _info(vm_):
898 state = ''
899 dom = _get_dom(vm_)
900 raw = dom.info()
901 state = VIRT_STATE_NAME_MAP.get(raw[0], 'unknown')
902 return state
903 info = {}
904 if vm_:
905 info[vm_] = _info(vm_)
906 else:
907 for vm_ in list_vms():
908 info[vm_] = _info(vm_)
909 return info
910
911
912def node_info():
913 '''
914 Return a dict with information about this node
915
916 CLI Example:
917
918 .. code-block:: bash
919
Ales Komarekf8188332016-03-09 11:32:08 +0100920 salt '*' virtng.node_info
smolaon1fb381d2016-03-09 11:10:58 +0100921 '''
922 conn = __get_conn()
923 raw = conn.getInfo()
924 info = {'cpucores': raw[6],
925 'cpumhz': raw[3],
926 'cpumodel': str(raw[0]),
927 'cpus': raw[2],
928 'cputhreads': raw[7],
929 'numanodes': raw[4],
930 'phymemory': raw[1],
931 'sockets': raw[5]}
932 return info
933
934
935def get_nics(vm_):
936 '''
937 Return info about the network interfaces of a named vm
938
939 CLI Example:
940
941 .. code-block:: bash
942
Ales Komarekf8188332016-03-09 11:32:08 +0100943 salt '*' virtng.get_nics <vm name>
smolaon1fb381d2016-03-09 11:10:58 +0100944 '''
945 nics = {}
946 doc = minidom.parse(_StringIO(get_xml(vm_)))
947 for node in doc.getElementsByTagName('devices'):
948 i_nodes = node.getElementsByTagName('interface')
949 for i_node in i_nodes:
950 nic = {}
951 nic['type'] = i_node.getAttribute('type')
952 for v_node in i_node.getElementsByTagName('*'):
953 if v_node.tagName == 'mac':
954 nic['mac'] = v_node.getAttribute('address')
955 if v_node.tagName == 'model':
956 nic['model'] = v_node.getAttribute('type')
957 if v_node.tagName == 'target':
958 nic['target'] = v_node.getAttribute('dev')
959 # driver, source, and match can all have optional attributes
960 if re.match('(driver|source|address)', v_node.tagName):
961 temp = {}
962 for key, value in v_node.attributes.items():
963 temp[key] = value
964 nic[str(v_node.tagName)] = temp
965 # virtualport needs to be handled separately, to pick up the
966 # type attribute of the virtualport itself
967 if v_node.tagName == 'virtualport':
968 temp = {}
969 temp['type'] = v_node.getAttribute('type')
970 for key, value in v_node.attributes.items():
971 temp[key] = value
972 nic['virtualport'] = temp
973 if 'mac' not in nic:
974 continue
975 nics[nic['mac']] = nic
976 return nics
977
978
979def get_macs(vm_):
980 '''
981 Return a list off MAC addresses from the named vm
982
983 CLI Example:
984
985 .. code-block:: bash
986
Ales Komarekf8188332016-03-09 11:32:08 +0100987 salt '*' virtng.get_macs <vm name>
smolaon1fb381d2016-03-09 11:10:58 +0100988 '''
989 macs = []
990 doc = minidom.parse(_StringIO(get_xml(vm_)))
991 for node in doc.getElementsByTagName('devices'):
992 i_nodes = node.getElementsByTagName('interface')
993 for i_node in i_nodes:
994 for v_node in i_node.getElementsByTagName('mac'):
995 macs.append(v_node.getAttribute('address'))
996 return macs
997
998
999def get_graphics(vm_):
1000 '''
1001 Returns the information on vnc for a given vm
1002
1003 CLI Example:
1004
1005 .. code-block:: bash
1006
Ales Komarekf8188332016-03-09 11:32:08 +01001007 salt '*' virtng.get_graphics <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001008 '''
1009 out = {'autoport': 'None',
1010 'keymap': 'None',
1011 'listen': 'None',
1012 'port': 'None',
1013 'type': 'vnc'}
1014 xml = get_xml(vm_)
1015 ssock = _StringIO(xml)
1016 doc = minidom.parse(ssock)
1017 for node in doc.getElementsByTagName('domain'):
1018 g_nodes = node.getElementsByTagName('graphics')
1019 for g_node in g_nodes:
1020 for key, value in g_node.attributes.items():
1021 out[key] = value
1022 return out
1023
1024
1025def get_disks(vm_):
1026 '''
1027 Return the disks of a named vm
1028
1029 CLI Example:
1030
1031 .. code-block:: bash
1032
Ales Komarekf8188332016-03-09 11:32:08 +01001033 salt '*' virtng.get_disks <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001034 '''
1035 disks = {}
1036 doc = minidom.parse(_StringIO(get_xml(vm_)))
1037 for elem in doc.getElementsByTagName('disk'):
1038 sources = elem.getElementsByTagName('source')
1039 targets = elem.getElementsByTagName('target')
1040 if len(sources) > 0:
1041 source = sources[0]
1042 else:
1043 continue
1044 if len(targets) > 0:
1045 target = targets[0]
1046 else:
1047 continue
1048 if target.hasAttribute('dev'):
1049 qemu_target = ''
1050 if source.hasAttribute('file'):
1051 qemu_target = source.getAttribute('file')
1052 elif source.hasAttribute('dev'):
1053 qemu_target = source.getAttribute('dev')
1054 elif source.hasAttribute('protocol') and \
1055 source.hasAttribute('name'): # For rbd network
1056 qemu_target = '{0}:{1}'.format(
1057 source.getAttribute('protocol'),
1058 source.getAttribute('name'))
1059 if qemu_target:
1060 disks[target.getAttribute('dev')] = {
1061 'file': qemu_target}
1062 for dev in disks:
1063 try:
1064 hypervisor = __salt__['config.get']('libvirt:hypervisor', 'kvm')
1065 if hypervisor not in ['qemu', 'kvm']:
1066 break
1067
1068 output = []
1069 qemu_output = subprocess.Popen(['qemu-img', 'info',
1070 disks[dev]['file']],
1071 shell=False,
1072 stdout=subprocess.PIPE).communicate()[0]
1073 snapshots = False
1074 columns = None
1075 lines = qemu_output.strip().split('\n')
1076 for line in lines:
1077 if line.startswith('Snapshot list:'):
1078 snapshots = True
1079 continue
1080
1081 # If this is a copy-on-write image, then the backing file
1082 # represents the base image
1083 #
1084 # backing file: base.qcow2 (actual path: /var/shared/base.qcow2)
1085 elif line.startswith('backing file'):
1086 matches = re.match(r'.*\(actual path: (.*?)\)', line)
1087 if matches:
1088 output.append('backing file: {0}'.format(matches.group(1)))
1089 continue
1090
1091 elif snapshots:
1092 if line.startswith('ID'): # Do not parse table headers
1093 line = line.replace('VM SIZE', 'VMSIZE')
1094 line = line.replace('VM CLOCK', 'TIME VMCLOCK')
1095 columns = re.split(r'\s+', line)
1096 columns = [c.lower() for c in columns]
1097 output.append('snapshots:')
1098 continue
1099 fields = re.split(r'\s+', line)
1100 for i, field in enumerate(fields):
1101 sep = ' '
1102 if i == 0:
1103 sep = '-'
1104 output.append(
1105 '{0} {1}: "{2}"'.format(
1106 sep, columns[i], field
1107 )
1108 )
1109 continue
1110 output.append(line)
1111 output = '\n'.join(output)
1112 disks[dev].update(yaml.safe_load(output))
1113 except TypeError:
1114 disks[dev].update(yaml.safe_load('image: Does not exist'))
1115 return disks
1116
1117
1118def setmem(vm_, memory, config=False):
1119 '''
1120 Changes the amount of memory allocated to VM. The VM must be shutdown
1121 for this to work.
1122
1123 memory is to be specified in MB
1124 If config is True then we ask libvirt to modify the config as well
1125
1126 CLI Example:
1127
1128 .. code-block:: bash
1129
Ales Komarekf8188332016-03-09 11:32:08 +01001130 salt '*' virtng.setmem myvm 768
smolaon1fb381d2016-03-09 11:10:58 +01001131 '''
1132 if vm_state(vm_) != 'shutdown':
1133 return False
1134
1135 dom = _get_dom(vm_)
1136
1137 # libvirt has a funny bitwise system for the flags in that the flag
1138 # to affect the "current" setting is 0, which means that to set the
1139 # current setting we have to call it a second time with just 0 set
1140 flags = libvirt.VIR_DOMAIN_MEM_MAXIMUM
1141 if config:
1142 flags = flags | libvirt.VIR_DOMAIN_AFFECT_CONFIG
1143
1144 ret1 = dom.setMemoryFlags(memory * 1024, flags)
1145 ret2 = dom.setMemoryFlags(memory * 1024, libvirt.VIR_DOMAIN_AFFECT_CURRENT)
1146
1147 # return True if both calls succeeded
1148 return ret1 == ret2 == 0
1149
1150
1151def setvcpus(vm_, vcpus, config=False):
1152 '''
1153 Changes the amount of vcpus allocated to VM. The VM must be shutdown
1154 for this to work.
1155
1156 vcpus is an int representing the number to be assigned
1157 If config is True then we ask libvirt to modify the config as well
1158
1159 CLI Example:
1160
1161 .. code-block:: bash
1162
Ales Komarekf8188332016-03-09 11:32:08 +01001163 salt '*' virtng.setvcpus myvm 2
smolaon1fb381d2016-03-09 11:10:58 +01001164 '''
1165 if vm_state(vm_) != 'shutdown':
1166 return False
1167
1168 dom = _get_dom(vm_)
1169
1170 # see notes in setmem
1171 flags = libvirt.VIR_DOMAIN_VCPU_MAXIMUM
1172 if config:
1173 flags = flags | libvirt.VIR_DOMAIN_AFFECT_CONFIG
1174
1175 ret1 = dom.setVcpusFlags(vcpus, flags)
1176 ret2 = dom.setVcpusFlags(vcpus, libvirt.VIR_DOMAIN_AFFECT_CURRENT)
1177
1178 return ret1 == ret2 == 0
1179
1180
1181def freemem():
1182 '''
1183 Return an int representing the amount of memory that has not been given
1184 to virtual machines on this node
1185
1186 CLI Example:
1187
1188 .. code-block:: bash
1189
Ales Komarekf8188332016-03-09 11:32:08 +01001190 salt '*' virtng.freemem
smolaon1fb381d2016-03-09 11:10:58 +01001191 '''
1192 conn = __get_conn()
1193 mem = conn.getInfo()[1]
1194 # Take off just enough to sustain the hypervisor
1195 mem -= 256
1196 for vm_ in list_vms():
1197 dom = _get_dom(vm_)
1198 if dom.ID() > 0:
1199 mem -= dom.info()[2] / 1024
1200 return mem
1201
1202
1203def freecpu():
1204 '''
1205 Return an int representing the number of unallocated cpus on this
1206 hypervisor
1207
1208 CLI Example:
1209
1210 .. code-block:: bash
1211
Ales Komarekf8188332016-03-09 11:32:08 +01001212 salt '*' virtng.freecpu
smolaon1fb381d2016-03-09 11:10:58 +01001213 '''
1214 conn = __get_conn()
1215 cpus = conn.getInfo()[2]
1216 for vm_ in list_vms():
1217 dom = _get_dom(vm_)
1218 if dom.ID() > 0:
1219 cpus -= dom.info()[3]
1220 return cpus
1221
1222
1223def full_info():
1224 '''
1225 Return the node_info, vm_info and freemem
1226
1227 CLI Example:
1228
1229 .. code-block:: bash
1230
Ales Komarekf8188332016-03-09 11:32:08 +01001231 salt '*' virtng.full_info
smolaon1fb381d2016-03-09 11:10:58 +01001232 '''
1233 return {'freecpu': freecpu(),
1234 'freemem': freemem(),
1235 'node_info': node_info(),
1236 'vm_info': vm_info()}
1237
1238
1239def get_xml(vm_):
1240 '''
1241 Returns the XML for a given vm
1242
1243 CLI Example:
1244
1245 .. code-block:: bash
1246
Ales Komarekf8188332016-03-09 11:32:08 +01001247 salt '*' virtng.get_xml <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001248 '''
1249 dom = _get_dom(vm_)
1250 return dom.XMLDesc(0)
1251
1252
1253def get_profiles(hypervisor=None):
1254 '''
1255 Return the virt profiles for hypervisor.
1256
1257 Currently there are profiles for:
1258
1259 - nic
1260 - disk
1261
1262 CLI Example:
1263
1264 .. code-block:: bash
1265
Ales Komarekf8188332016-03-09 11:32:08 +01001266 salt '*' virtng.get_profiles
1267 salt '*' virtng.get_profiles hypervisor=esxi
smolaon1fb381d2016-03-09 11:10:58 +01001268 '''
1269 ret = {}
1270 if hypervisor:
1271 hypervisor = hypervisor
1272 else:
1273 hypervisor = __salt__['config.get']('libvirt:hypervisor', VIRT_DEFAULT_HYPER)
1274 virtconf = __salt__['config.get']('virt', {})
1275 for typ in ['disk', 'nic']:
1276 _func = getattr(sys.modules[__name__], '_{0}_profile'.format(typ))
1277 ret[typ] = {'default': _func('default', hypervisor)}
1278 if typ in virtconf:
1279 ret.setdefault(typ, {})
1280 for prf in virtconf[typ]:
1281 ret[typ][prf] = _func(prf, hypervisor)
1282 return ret
1283
1284
1285def shutdown(vm_):
1286 '''
1287 Send a soft shutdown signal to the named vm
1288
1289 CLI Example:
1290
1291 .. code-block:: bash
1292
Ales Komarekf8188332016-03-09 11:32:08 +01001293 salt '*' virtng.shutdown <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001294 '''
1295 dom = _get_dom(vm_)
1296 return dom.shutdown() == 0
1297
1298
1299def pause(vm_):
1300 '''
1301 Pause the named vm
1302
1303 CLI Example:
1304
1305 .. code-block:: bash
1306
Ales Komarekf8188332016-03-09 11:32:08 +01001307 salt '*' virtng.pause <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001308 '''
1309 dom = _get_dom(vm_)
1310 return dom.suspend() == 0
1311
1312
1313def resume(vm_):
1314 '''
1315 Resume the named vm
1316
1317 CLI Example:
1318
1319 .. code-block:: bash
1320
Ales Komarekf8188332016-03-09 11:32:08 +01001321 salt '*' virtng.resume <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001322 '''
1323 dom = _get_dom(vm_)
1324 return dom.resume() == 0
1325
1326
1327def create(vm_):
1328 '''
1329 Start a defined domain
1330
1331 CLI Example:
1332
1333 .. code-block:: bash
1334
Ales Komarekf8188332016-03-09 11:32:08 +01001335 salt '*' virtng.create <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001336 '''
1337 dom = _get_dom(vm_)
1338 return dom.create() == 0
1339
1340
1341def start(vm_):
1342 '''
1343 Alias for the obscurely named 'create' function
1344
1345 CLI Example:
1346
1347 .. code-block:: bash
1348
Ales Komarekf8188332016-03-09 11:32:08 +01001349 salt '*' virtng.start <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001350 '''
1351 return create(vm_)
1352
1353
1354def stop(vm_):
1355 '''
1356 Alias for the obscurely named 'destroy' function
1357
1358 CLI Example:
1359
1360 .. code-block:: bash
1361
Ales Komarekf8188332016-03-09 11:32:08 +01001362 salt '*' virtng.stop <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001363 '''
1364 return destroy(vm_)
1365
1366
1367def reboot(vm_):
1368 '''
1369 Reboot a domain via ACPI request
1370
1371 CLI Example:
1372
1373 .. code-block:: bash
1374
Ales Komarekf8188332016-03-09 11:32:08 +01001375 salt '*' virtng.reboot <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001376 '''
1377 dom = _get_dom(vm_)
1378
1379 # reboot has a few modes of operation, passing 0 in means the
1380 # hypervisor will pick the best method for rebooting
1381 return dom.reboot(0) == 0
1382
1383
1384def reset(vm_):
1385 '''
1386 Reset a VM by emulating the reset button on a physical machine
1387
1388 CLI Example:
1389
1390 .. code-block:: bash
1391
Ales Komarekf8188332016-03-09 11:32:08 +01001392 salt '*' virtng.reset <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001393 '''
1394 dom = _get_dom(vm_)
1395
1396 # reset takes a flag, like reboot, but it is not yet used
1397 # so we just pass in 0
1398 # see: http://libvirt.org/html/libvirt-libvirt.html#virDomainReset
1399 return dom.reset(0) == 0
1400
1401
1402def ctrl_alt_del(vm_):
1403 '''
1404 Sends CTRL+ALT+DEL to a VM
1405
1406 CLI Example:
1407
1408 .. code-block:: bash
1409
Ales Komarekf8188332016-03-09 11:32:08 +01001410 salt '*' virtng.ctrl_alt_del <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001411 '''
1412 dom = _get_dom(vm_)
1413 return dom.sendKey(0, 0, [29, 56, 111], 3, 0) == 0
1414
1415
1416def create_xml_str(xml):
1417 '''
1418 Start a domain based on the XML passed to the function
1419
1420 CLI Example:
1421
1422 .. code-block:: bash
1423
Ales Komarekf8188332016-03-09 11:32:08 +01001424 salt '*' virtng.create_xml_str <XML in string format>
smolaon1fb381d2016-03-09 11:10:58 +01001425 '''
1426 conn = __get_conn()
1427 return conn.createXML(xml, 0) is not None
1428
1429
1430def create_xml_path(path):
1431 '''
1432 Start a domain based on the XML-file path passed to the function
1433
1434 CLI Example:
1435
1436 .. code-block:: bash
1437
Ales Komarekf8188332016-03-09 11:32:08 +01001438 salt '*' virtng.create_xml_path <path to XML file on the node>
smolaon1fb381d2016-03-09 11:10:58 +01001439 '''
1440 if not os.path.isfile(path):
1441 return False
1442 return create_xml_str(salt.utils.fopen(path, 'r').read())
1443
1444
1445def define_xml_str(xml):
1446 '''
1447 Define a domain based on the XML passed to the function
1448
1449 CLI Example:
1450
1451 .. code-block:: bash
1452
Ales Komarekf8188332016-03-09 11:32:08 +01001453 salt '*' virtng.define_xml_str <XML in string format>
smolaon1fb381d2016-03-09 11:10:58 +01001454 '''
1455 conn = __get_conn()
1456 return conn.defineXML(xml) is not None
1457
1458
1459def define_xml_path(path):
1460 '''
1461 Define a domain based on the XML-file path passed to the function
1462
1463 CLI Example:
1464
1465 .. code-block:: bash
1466
Ales Komarekf8188332016-03-09 11:32:08 +01001467 salt '*' virtng.define_xml_path <path to XML file on the node>
smolaon1fb381d2016-03-09 11:10:58 +01001468
1469 '''
1470 if not os.path.isfile(path):
1471 return False
1472 return define_xml_str(salt.utils.fopen(path, 'r').read())
1473
1474
1475def define_vol_xml_str(xml):
1476 '''
1477 Define a volume based on the XML passed to the function
1478
1479 CLI Example:
1480
1481 .. code-block:: bash
1482
Ales Komarekf8188332016-03-09 11:32:08 +01001483 salt '*' virtng.define_vol_xml_str <XML in string format>
smolaon1fb381d2016-03-09 11:10:58 +01001484 '''
1485 poolname = __salt__['config.get']('libvirt:storagepool', 'default')
1486 conn = __get_conn()
1487 pool = conn.storagePoolLookupByName(str(poolname))
1488 return pool.createXML(xml, 0) is not None
1489
1490
1491def define_vol_xml_path(path):
1492 '''
1493 Define a volume based on the XML-file path passed to the function
1494
1495 CLI Example:
1496
1497 .. code-block:: bash
1498
Ales Komarekf8188332016-03-09 11:32:08 +01001499 salt '*' virtng.define_vol_xml_path <path to XML file on the node>
smolaon1fb381d2016-03-09 11:10:58 +01001500
1501 '''
1502 if not os.path.isfile(path):
1503 return False
1504 return define_vol_xml_str(salt.utils.fopen(path, 'r').read())
1505
1506
1507def migrate_non_shared(vm_, target, ssh=False):
1508 '''
1509 Attempt to execute non-shared storage "all" migration
1510
1511 CLI Example:
1512
1513 .. code-block:: bash
1514
Ales Komarekf8188332016-03-09 11:32:08 +01001515 salt '*' virtng.migrate_non_shared <vm name> <target hypervisor>
smolaon1fb381d2016-03-09 11:10:58 +01001516 '''
1517 cmd = _get_migrate_command() + ' --copy-storage-all ' + vm_\
1518 + _get_target(target, ssh)
1519
1520 return subprocess.Popen(cmd,
1521 shell=True,
1522 stdout=subprocess.PIPE).communicate()[0]
1523
1524
1525def migrate_non_shared_inc(vm_, target, ssh=False):
1526 '''
1527 Attempt to execute non-shared storage "all" migration
1528
1529 CLI Example:
1530
1531 .. code-block:: bash
1532
Ales Komarekf8188332016-03-09 11:32:08 +01001533 salt '*' virtng.migrate_non_shared_inc <vm name> <target hypervisor>
smolaon1fb381d2016-03-09 11:10:58 +01001534 '''
1535 cmd = _get_migrate_command() + ' --copy-storage-inc ' + vm_\
1536 + _get_target(target, ssh)
1537
1538 return subprocess.Popen(cmd,
1539 shell=True,
1540 stdout=subprocess.PIPE).communicate()[0]
1541
1542
1543def migrate(vm_, target, ssh=False):
1544 '''
1545 Shared storage migration
1546
1547 CLI Example:
1548
1549 .. code-block:: bash
1550
Ales Komarekf8188332016-03-09 11:32:08 +01001551 salt '*' virtng.migrate <vm name> <target hypervisor>
smolaon1fb381d2016-03-09 11:10:58 +01001552 '''
1553 cmd = _get_migrate_command() + ' ' + vm_\
1554 + _get_target(target, ssh)
1555
1556 return subprocess.Popen(cmd,
1557 shell=True,
1558 stdout=subprocess.PIPE).communicate()[0]
1559
1560
1561def seed_non_shared_migrate(disks, force=False):
1562 '''
1563 Non shared migration requires that the disks be present on the migration
1564 destination, pass the disks information via this function, to the
1565 migration destination before executing the migration.
1566
1567 CLI Example:
1568
1569 .. code-block:: bash
1570
Ales Komarekf8188332016-03-09 11:32:08 +01001571 salt '*' virtng.seed_non_shared_migrate <disks>
smolaon1fb381d2016-03-09 11:10:58 +01001572 '''
1573 for _, data in disks.items():
1574 fn_ = data['file']
1575 form = data['file format']
1576 size = data['virtual size'].split()[1][1:]
1577 if os.path.isfile(fn_) and not force:
1578 # the target exists, check to see if it is compatible
1579 pre = yaml.safe_load(subprocess.Popen('qemu-img info arch',
1580 shell=True,
1581 stdout=subprocess.PIPE).communicate()[0])
1582 if pre['file format'] != data['file format']\
1583 and pre['virtual size'] != data['virtual size']:
1584 return False
1585 if not os.path.isdir(os.path.dirname(fn_)):
1586 os.makedirs(os.path.dirname(fn_))
1587 if os.path.isfile(fn_):
1588 os.remove(fn_)
1589 cmd = 'qemu-img create -f ' + form + ' ' + fn_ + ' ' + size
1590 subprocess.call(cmd, shell=True)
1591 creds = _libvirt_creds()
1592 cmd = 'chown ' + creds['user'] + ':' + creds['group'] + ' ' + fn_
1593 subprocess.call(cmd, shell=True)
1594 return True
1595
1596
1597def set_autostart(vm_, state='on'):
1598 '''
1599 Set the autostart flag on a VM so that the VM will start with the host
1600 system on reboot.
1601
1602 CLI Example:
1603
1604 .. code-block:: bash
1605
1606 salt "*" virt.set_autostart <vm name> <on | off>
1607 '''
1608
1609 dom = _get_dom(vm_)
1610
1611 if state == 'on':
1612 return dom.setAutostart(1) == 0
1613
1614 elif state == 'off':
1615 return dom.setAutostart(0) == 0
1616
1617 else:
1618 # return False if state is set to something other then on or off
1619 return False
1620
1621
1622def destroy(vm_):
1623 '''
1624 Hard power down the virtual machine, this is equivalent to pulling the
1625 power
1626
1627 CLI Example:
1628
1629 .. code-block:: bash
1630
Ales Komarekf8188332016-03-09 11:32:08 +01001631 salt '*' virtng.destroy <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001632 '''
1633 dom = _get_dom(vm_)
1634 return dom.destroy() == 0
1635
1636
1637def undefine(vm_):
1638 '''
1639 Remove a defined vm, this does not purge the virtual machine image, and
1640 this only works if the vm is powered down
1641
1642 CLI Example:
1643
1644 .. code-block:: bash
1645
Ales Komarekf8188332016-03-09 11:32:08 +01001646 salt '*' virtng.undefine <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001647 '''
1648 dom = _get_dom(vm_)
Alexandru Avadanii00f187a2018-06-24 20:36:44 +02001649 if getattr(libvirt, 'VIR_DOMAIN_UNDEFINE_NVRAM', False):
1650 # This one is only in 1.2.8+
1651 return dom.undefineFlags(libvirt.VIR_DOMAIN_UNDEFINE_NVRAM) == 0
1652 else:
1653 return dom.undefine() == 0
smolaon1fb381d2016-03-09 11:10:58 +01001654
1655
1656def purge(vm_, dirs=False):
1657 '''
1658 Recursively destroy and delete a virtual machine, pass True for dir's to
1659 also delete the directories containing the virtual machine disk images -
1660 USE WITH EXTREME CAUTION!
1661
1662 CLI Example:
1663
1664 .. code-block:: bash
1665
Ales Komarekf8188332016-03-09 11:32:08 +01001666 salt '*' virtng.purge <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001667 '''
1668 disks = get_disks(vm_)
1669 try:
1670 if not destroy(vm_):
1671 return False
1672 except libvirt.libvirtError:
1673 # This is thrown if the machine is already shut down
1674 pass
1675 directories = set()
1676 for disk in disks:
1677 os.remove(disks[disk]['file'])
1678 directories.add(os.path.dirname(disks[disk]['file']))
1679 if dirs:
1680 for dir_ in directories:
1681 shutil.rmtree(dir_)
1682 undefine(vm_)
1683 return True
1684
1685
1686def virt_type():
1687 '''
1688 Returns the virtual machine type as a string
1689
1690 CLI Example:
1691
1692 .. code-block:: bash
1693
Ales Komarekf8188332016-03-09 11:32:08 +01001694 salt '*' virtng.virt_type
smolaon1fb381d2016-03-09 11:10:58 +01001695 '''
1696 return __grains__['virtual']
1697
1698
1699def is_kvm_hyper():
1700 '''
1701 Returns a bool whether or not this node is a KVM hypervisor
1702
1703 CLI Example:
1704
1705 .. code-block:: bash
1706
Ales Komarekf8188332016-03-09 11:32:08 +01001707 salt '*' virtng.is_kvm_hyper
smolaon1fb381d2016-03-09 11:10:58 +01001708 '''
1709 try:
1710 if 'kvm_' not in salt.utils.fopen('/proc/modules').read():
1711 return False
1712 except IOError:
1713 # No /proc/modules? Are we on Windows? Or Solaris?
1714 return False
Oleh Hryhorov7c5dfd32018-01-19 15:43:44 +02001715 return 'libvirtd' in __salt__['cmd.shell'](__grains__['ps'])
smolaon1fb381d2016-03-09 11:10:58 +01001716
1717
1718def is_xen_hyper():
1719 '''
1720 Returns a bool whether or not this node is a XEN hypervisor
1721
1722 CLI Example:
1723
1724 .. code-block:: bash
1725
Ales Komarekf8188332016-03-09 11:32:08 +01001726 salt '*' virtng.is_xen_hyper
smolaon1fb381d2016-03-09 11:10:58 +01001727 '''
1728 try:
1729 if __grains__['virtual_subtype'] != 'Xen Dom0':
1730 return False
1731 except KeyError:
1732 # virtual_subtype isn't set everywhere.
1733 return False
1734 try:
1735 if 'xen_' not in salt.utils.fopen('/proc/modules').read():
1736 return False
1737 except IOError:
1738 # No /proc/modules? Are we on Windows? Or Solaris?
1739 return False
Oleh Hryhorov7c5dfd32018-01-19 15:43:44 +02001740 return 'libvirtd' in __salt__['cmd.shell'](__grains__['ps'])
smolaon1fb381d2016-03-09 11:10:58 +01001741
1742
1743def is_hyper():
1744 '''
1745 Returns a bool whether or not this node is a hypervisor of any kind
1746
1747 CLI Example:
1748
1749 .. code-block:: bash
1750
Ales Komarekf8188332016-03-09 11:32:08 +01001751 salt '*' virtng.is_hyper
smolaon1fb381d2016-03-09 11:10:58 +01001752 '''
1753 try:
1754 import libvirt # pylint: disable=import-error
1755 except ImportError:
1756 # not a usable hypervisor without libvirt module
1757 return False
1758 return is_xen_hyper() or is_kvm_hyper()
1759
1760
1761def vm_cputime(vm_=None):
1762 '''
1763 Return cputime used by the vms on this hyper in a
1764 list of dicts:
1765
1766 .. code-block:: python
1767
1768 [
1769 'your-vm': {
1770 'cputime' <int>
1771 'cputime_percent' <int>
1772 },
1773 ...
1774 ]
1775
1776 If you pass a VM name in as an argument then it will return info
1777 for just the named VM, otherwise it will return all VMs.
1778
1779 CLI Example:
1780
1781 .. code-block:: bash
1782
Ales Komarekf8188332016-03-09 11:32:08 +01001783 salt '*' virtng.vm_cputime
smolaon1fb381d2016-03-09 11:10:58 +01001784 '''
1785 host_cpus = __get_conn().getInfo()[2]
1786
1787 def _info(vm_):
1788 dom = _get_dom(vm_)
1789 raw = dom.info()
1790 vcpus = int(raw[3])
1791 cputime = int(raw[4])
1792 cputime_percent = 0
1793 if cputime:
1794 # Divide by vcpus to always return a number between 0 and 100
1795 cputime_percent = (1.0e-7 * cputime / host_cpus) / vcpus
1796 return {
1797 'cputime': int(raw[4]),
1798 'cputime_percent': int('{0:.0f}'.format(cputime_percent))
1799 }
1800 info = {}
1801 if vm_:
1802 info[vm_] = _info(vm_)
1803 else:
1804 for vm_ in list_vms():
1805 info[vm_] = _info(vm_)
1806 return info
1807
1808
1809def vm_netstats(vm_=None):
1810 '''
1811 Return combined network counters used by the vms on this hyper in a
1812 list of dicts:
1813
1814 .. code-block:: python
1815
1816 [
1817 'your-vm': {
1818 'rx_bytes' : 0,
1819 'rx_packets' : 0,
1820 'rx_errs' : 0,
1821 'rx_drop' : 0,
1822 'tx_bytes' : 0,
1823 'tx_packets' : 0,
1824 'tx_errs' : 0,
1825 'tx_drop' : 0
1826 },
1827 ...
1828 ]
1829
1830 If you pass a VM name in as an argument then it will return info
1831 for just the named VM, otherwise it will return all VMs.
1832
1833 CLI Example:
1834
1835 .. code-block:: bash
1836
Ales Komarekf8188332016-03-09 11:32:08 +01001837 salt '*' virtng.vm_netstats
smolaon1fb381d2016-03-09 11:10:58 +01001838 '''
1839 def _info(vm_):
1840 dom = _get_dom(vm_)
1841 nics = get_nics(vm_)
1842 ret = {
1843 'rx_bytes': 0,
1844 'rx_packets': 0,
1845 'rx_errs': 0,
1846 'rx_drop': 0,
1847 'tx_bytes': 0,
1848 'tx_packets': 0,
1849 'tx_errs': 0,
1850 'tx_drop': 0
1851 }
1852 for attrs in six.itervalues(nics):
1853 if 'target' in attrs:
1854 dev = attrs['target']
1855 stats = dom.interfaceStats(dev)
1856 ret['rx_bytes'] += stats[0]
1857 ret['rx_packets'] += stats[1]
1858 ret['rx_errs'] += stats[2]
1859 ret['rx_drop'] += stats[3]
1860 ret['tx_bytes'] += stats[4]
1861 ret['tx_packets'] += stats[5]
1862 ret['tx_errs'] += stats[6]
1863 ret['tx_drop'] += stats[7]
1864
1865 return ret
1866 info = {}
1867 if vm_:
1868 info[vm_] = _info(vm_)
1869 else:
1870 for vm_ in list_vms():
1871 info[vm_] = _info(vm_)
1872 return info
1873
1874
1875def vm_diskstats(vm_=None):
1876 '''
1877 Return disk usage counters used by the vms on this hyper in a
1878 list of dicts:
1879
1880 .. code-block:: python
1881
1882 [
1883 'your-vm': {
1884 'rd_req' : 0,
1885 'rd_bytes' : 0,
1886 'wr_req' : 0,
1887 'wr_bytes' : 0,
1888 'errs' : 0
1889 },
1890 ...
1891 ]
1892
1893 If you pass a VM name in as an argument then it will return info
1894 for just the named VM, otherwise it will return all VMs.
1895
1896 CLI Example:
1897
1898 .. code-block:: bash
1899
Ales Komarekf8188332016-03-09 11:32:08 +01001900 salt '*' virtng.vm_blockstats
smolaon1fb381d2016-03-09 11:10:58 +01001901 '''
1902 def get_disk_devs(vm_):
1903 doc = minidom.parse(_StringIO(get_xml(vm_)))
1904 disks = []
1905 for elem in doc.getElementsByTagName('disk'):
1906 targets = elem.getElementsByTagName('target')
1907 target = targets[0]
1908 disks.append(target.getAttribute('dev'))
1909 return disks
1910
1911 def _info(vm_):
1912 dom = _get_dom(vm_)
1913 # Do not use get_disks, since it uses qemu-img and is very slow
1914 # and unsuitable for any sort of real time statistics
1915 disks = get_disk_devs(vm_)
1916 ret = {'rd_req': 0,
1917 'rd_bytes': 0,
1918 'wr_req': 0,
1919 'wr_bytes': 0,
1920 'errs': 0
1921 }
1922 for disk in disks:
1923 stats = dom.blockStats(disk)
1924 ret['rd_req'] += stats[0]
1925 ret['rd_bytes'] += stats[1]
1926 ret['wr_req'] += stats[2]
1927 ret['wr_bytes'] += stats[3]
1928 ret['errs'] += stats[4]
1929
1930 return ret
1931 info = {}
1932 if vm_:
1933 info[vm_] = _info(vm_)
1934 else:
1935 # Can not run function blockStats on inactive VMs
1936 for vm_ in list_active_vms():
1937 info[vm_] = _info(vm_)
1938 return info