blob: b405d594ffa16e2f5462fc6baeba01332ad4d38f [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,
smolaon1fb381d2016-03-09 11:10:58 +0100537 **kwargs):
538 '''
539 Initialize a new vm
540
541 CLI Example:
542
543 .. code-block:: bash
544
545 salt 'hypervisor' virt.init vm_name 4 512 salt://path/to/image.raw
546 salt 'hypervisor' virt.init vm_name 4 512 nic=profile disk=profile
547 '''
Dennis Dmitrieva57463c2017-03-24 17:05:22 +0200548
azvyagintseva4e802d2018-05-04 20:16:02 +0300549 rng = rng or {'backend':'/dev/urandom'}
smolaon1fb381d2016-03-09 11:10:58 +0100550 hypervisor = __salt__['config.get']('libvirt:hypervisor', hypervisor)
551
552 nicp = _nic_profile(nic, hypervisor, **kwargs)
553
Dennis Dmitrieva57463c2017-03-24 17:05:22 +0200554 diskp = _disk_profile(disk, hypervisor, **kwargs)
555
556 if image:
557 # Backward compatibility: if 'image' is specified in the VMs arguments
558 # instead of a disk arguments. In this case, 'image' will be assigned
559 # to the first disk for the VM.
smolaon1fb381d2016-03-09 11:10:58 +0100560 disk_name = next(diskp[0].iterkeys())
Dennis Dmitrieva57463c2017-03-24 17:05:22 +0200561 if not diskp[0][disk_name].get('image', None):
562 diskp[0][disk_name]['image'] = image
smolaon1fb381d2016-03-09 11:10:58 +0100563
Dennis Dmitrieva57463c2017-03-24 17:05:22 +0200564 # Create multiple disks, empty or from specified images.
Dzmitry Stremkouski97927ee2018-08-23 23:20:38 +0200565 cloud_init = None
566 cfg_drive = None
567
Dennis Dmitrieva57463c2017-03-24 17:05:22 +0200568 for disk in diskp:
569 log.debug("Creating disk for VM [ {0} ]: {1}".format(name, disk))
smolaon1fb381d2016-03-09 11:10:58 +0100570
Dennis Dmitrieva57463c2017-03-24 17:05:22 +0200571 for disk_name, args in disk.items():
smolaon1fb381d2016-03-09 11:10:58 +0100572
Dennis Dmitrieva57463c2017-03-24 17:05:22 +0200573 if hypervisor in ['esxi', 'vmware']:
574 if 'image' in args:
575 # TODO: we should be copying the image file onto the ESX host
576 raise SaltInvocationError('virt.init does not support image '
577 'template template in conjunction '
578 'with esxi hypervisor')
579 else:
580 # assume libvirt manages disks for us
smolaon1fb381d2016-03-09 11:10:58 +0100581 xml = _gen_vol_xml(name,
582 disk_name,
583 args['size'],
Mateusz Los4c7cd2d2018-01-09 11:46:07 +0100584 hypervisor,
585 **kwargs)
smolaon1fb381d2016-03-09 11:10:58 +0100586 define_vol_xml_str(xml)
587
Dennis Dmitrieva57463c2017-03-24 17:05:22 +0200588 elif hypervisor in ['qemu', 'kvm']:
589
590 disk_type = args['format']
591 disk_file_name = '{0}.{1}'.format(disk_name, disk_type)
592 # disk size TCP cloud
593 disk_size = args['size']
594
Mateusz Los4c7cd2d2018-01-09 11:46:07 +0100595 if 'img_dest' in kwargs:
596 img_dir = kwargs['img_dest']
597 else:
598 img_dir = __salt__['config.option']('virt.images')
Dennis Dmitrieva57463c2017-03-24 17:05:22 +0200599 img_dest = os.path.join(
600 img_dir,
601 name,
602 disk_file_name
603 )
604 img_dir = os.path.dirname(img_dest)
605 if not os.path.isdir(img_dir):
606 os.makedirs(img_dir)
607
608 if 'image' in args:
609 # Create disk from specified image
610 sfn = __salt__['cp.cache_file'](args['image'], saltenv)
611 try:
612 salt.utils.files.copyfile(sfn, img_dest)
613 mask = os.umask(0)
614 os.umask(mask)
615 # Apply umask and remove exec bit
616
617 # Resizing image TCP cloud
618 cmd = 'qemu-img resize ' + img_dest + ' ' + str(disk_size) + 'M'
619 subprocess.call(cmd, shell=True)
620
621 mode = (0o0777 ^ mask) & 0o0666
622 os.chmod(img_dest, mode)
623
624 except (IOError, OSError) as e:
625 raise CommandExecutionError('problem while copying image. {0} - {1}'.format(args['image'], e))
626
627 if kwargs.get('seed'):
Dzmitry Stremkouski97927ee2018-08-23 23:20:38 +0200628 seed_cmd = kwargs.get('seed_cmd', 'seedng.apply')
629 cloud_init = kwargs.get('cloud_init', None)
630 master = __salt__['config.option']('master')
631 cfg_drive = os.path.join(img_dir,'config-2.iso')
Dennis Dmitrieva57463c2017-03-24 17:05:22 +0200632
Dzmitry Stremkouski97927ee2018-08-23 23:20:38 +0200633 if cloud_init:
634 _tmp = name.split('.')
635
636 try:
637 user_data = json.dumps(cloud_init["user_data"])
638 except:
639 user_data = None
640
641 try:
642 network_data = json.dumps(cloud_init["network_data"])
643 except:
644 network_data = None
645
646 __salt__["cfgdrive.generate"](
647 dst = cfg_drive,
648 hostname = _tmp.pop(0),
649 domainname = '.'.join(_tmp),
650 user_data = user_data,
651 network_data = network_data,
652 saltconfig = { "salt_minion": { "conf": { "master": master, "id": name } } }
653 )
654 else:
655 __salt__[seed_cmd](
656 path = img_dest,
657 id_ = name,
658 config = kwargs.get('config'),
659 install = kwargs.get('install', True)
660 )
Dennis Dmitrieva57463c2017-03-24 17:05:22 +0200661 else:
662 # Create empty disk
663 try:
664 mask = os.umask(0)
665 os.umask(mask)
666 # Apply umask and remove exec bit
667
668 # Create empty image
669 cmd = 'qemu-img create -f ' + disk_type + ' ' + img_dest + ' ' + str(disk_size) + 'M'
670 subprocess.call(cmd, shell=True)
671
672 mode = (0o0777 ^ mask) & 0o0666
673 os.chmod(img_dest, mode)
674
675 except (IOError, OSError) as e:
676 raise CommandExecutionError('problem while creating volume {0} - {1}'.format(img_dest, e))
677
678 else:
679 # Unknown hypervisor
680 raise SaltInvocationError('Unsupported hypervisor when handling disk image: {0}'
681 .format(hypervisor))
682
smolaon1fb381d2016-03-09 11:10:58 +0100683 xml = _gen_xml(name, cpu, mem, diskp, nicp, hypervisor, **kwargs)
Dzmitry Stremkouski7ee23402018-04-10 00:43:48 +0200684
Dzmitry Stremkouski97927ee2018-08-23 23:20:38 +0200685 if cloud_init and cfg_drive:
686 xml_doc = minidom.parseString(xml)
687 iso_xml = xml_doc.createElement("disk")
688 iso_xml.setAttribute("type", "file")
689 iso_xml.setAttribute("device", "cdrom")
690 iso_xml.appendChild(xml_doc.createElement("readonly"))
691 driver = xml_doc.createElement("driver")
692 driver.setAttribute("name", "qemu")
693 driver.setAttribute("type", "raw")
694 target = xml_doc.createElement("target")
695 target.setAttribute("dev", "hdc")
696 target.setAttribute("bus", "ide")
697 source = xml_doc.createElement("source")
698 source.setAttribute("file", cfg_drive)
699 iso_xml.appendChild(driver)
700 iso_xml.appendChild(target)
701 iso_xml.appendChild(source)
702 xml_doc.getElementsByTagName("domain")[0].getElementsByTagName("devices")[0].appendChild(iso_xml)
703 xml = xml_doc.toxml()
704
Dzmitry Stremkouski7ee23402018-04-10 00:43:48 +0200705 # TODO: Remove this code and refactor module, when salt-common would have updated libvirt_domain.jinja template
Alexandru Avadanii00f187a2018-06-24 20:36:44 +0200706 if cpu_mode:
707 xml_doc = minidom.parseString(xml)
708 cpu_xml = xml_doc.createElement("cpu")
709 cpu_xml.setAttribute('mode', cpu_mode)
710 xml_doc.getElementsByTagName("domain")[0].appendChild(cpu_xml)
711 xml = xml_doc.toxml()
712
713 # TODO: Remove this code and refactor module, when salt-common would have updated libvirt_domain.jinja template
714 if machine:
715 xml_doc = minidom.parseString(xml)
716 os_xml = xml_doc.getElementsByTagName("domain")[0].getElementsByTagName("os")[0]
717 os_xml.getElementsByTagName("type")[0].setAttribute('machine', machine)
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 loader and 'path' not in loader:
722 log.info('`path` is a required property of `loader`, and cannot be found. Skipping loader configuration')
723 loader = None
724 elif loader:
725 xml_doc = minidom.parseString(xml)
726 loader_xml = xml_doc.createElement("loader")
727 for key, val in loader.items():
728 if key == 'path':
729 continue
730 loader_xml.setAttribute(key, val)
731 loader_path_xml = xml_doc.createTextNode(loader['path'])
732 loader_xml.appendChild(loader_path_xml)
733 xml_doc.getElementsByTagName("domain")[0].getElementsByTagName("os")[0].appendChild(loader_xml)
734 xml = xml_doc.toxml()
735
736 # TODO: Remove this code and refactor module, when salt-common would have updated libvirt_domain.jinja template
Dzmitry Stremkouskib8acf1f2018-06-28 12:56:23 +0200737 for _nic in nicp:
738 if _nic['virtualport']:
739 xml_doc = minidom.parseString(xml)
740 interfaces = xml_doc.getElementsByTagName("domain")[0].getElementsByTagName("devices")[0].getElementsByTagName("interface")
741 for interface in interfaces:
742 if interface.getElementsByTagName('mac')[0].getAttribute('address').lower() == _nic['mac'].lower():
743 vport_xml = xml_doc.createElement("virtualport")
744 vport_xml.setAttribute("type", _nic['virtualport']['type'])
745 interface.appendChild(vport_xml)
746 xml = xml_doc.toxml()
747
748 # TODO: Remove this code and refactor module, when salt-common would have updated libvirt_domain.jinja template
Dzmitry Stremkouski7ee23402018-04-10 00:43:48 +0200749 if rng:
750 rng_model = rng.get('model', 'random')
751 rng_backend = rng.get('backend', '/dev/urandom')
752 xml_doc = minidom.parseString(xml)
753 rng_xml = xml_doc.createElement("rng")
754 rng_xml.setAttribute("model", "virtio")
755 backend = xml_doc.createElement("backend")
756 backend.setAttribute("model", rng_model)
757 backend.appendChild(xml_doc.createTextNode(rng_backend))
758 rng_xml.appendChild(backend)
759 if 'rate' in rng:
760 rng_rate_period = rng['rate'].get('period', '2000')
761 rng_rate_bytes = rng['rate'].get('bytes', '1234')
762 rate = xml_doc.createElement("rate")
763 rate.setAttribute("period", rng_rate_period)
764 rate.setAttribute("bytes", rng_rate_bytes)
765 rng_xml.appendChild(rate)
766 xml_doc.getElementsByTagName("domain")[0].getElementsByTagName("devices")[0].appendChild(rng_xml)
767 xml = xml_doc.toxml()
768
smolaon1fb381d2016-03-09 11:10:58 +0100769 define_xml_str(xml)
770
smolaon1fb381d2016-03-09 11:10:58 +0100771 if start:
772 create(name)
773
774 return True
775
776
777def list_vms():
778 '''
779 Return a list of virtual machine names on the minion
780
781 CLI Example:
782
783 .. code-block:: bash
784
Ales Komarekf8188332016-03-09 11:32:08 +0100785 salt '*' virtng.list_vms
smolaon1fb381d2016-03-09 11:10:58 +0100786 '''
787 vms = []
788 vms.extend(list_active_vms())
789 vms.extend(list_inactive_vms())
790 return vms
791
792
793def list_active_vms():
794 '''
795 Return a list of names for active virtual machine on the minion
796
797 CLI Example:
798
799 .. code-block:: bash
800
Ales Komarekf8188332016-03-09 11:32:08 +0100801 salt '*' virtng.list_active_vms
smolaon1fb381d2016-03-09 11:10:58 +0100802 '''
803 conn = __get_conn()
804 vms = []
805 for id_ in conn.listDomainsID():
806 vms.append(conn.lookupByID(id_).name())
807 return vms
808
809
810def list_inactive_vms():
811 '''
812 Return a list of names for inactive virtual machine on the minion
813
814 CLI Example:
815
816 .. code-block:: bash
817
Ales Komarekf8188332016-03-09 11:32:08 +0100818 salt '*' virtng.list_inactive_vms
smolaon1fb381d2016-03-09 11:10:58 +0100819 '''
820 conn = __get_conn()
821 vms = []
822 for id_ in conn.listDefinedDomains():
823 vms.append(id_)
824 return vms
825
826
827def vm_info(vm_=None):
828 '''
829 Return detailed information about the vms on this hyper in a
830 list of dicts:
831
832 .. code-block:: python
833
834 [
835 'your-vm': {
836 'cpu': <int>,
837 'maxMem': <int>,
838 'mem': <int>,
839 'state': '<state>',
840 'cputime' <int>
841 },
842 ...
843 ]
844
845 If you pass a VM name in as an argument then it will return info
846 for just the named VM, otherwise it will return all VMs.
847
848 CLI Example:
849
850 .. code-block:: bash
851
Ales Komarekf8188332016-03-09 11:32:08 +0100852 salt '*' virtng.vm_info
smolaon1fb381d2016-03-09 11:10:58 +0100853 '''
854 def _info(vm_):
855 dom = _get_dom(vm_)
856 raw = dom.info()
857 return {'cpu': raw[3],
858 'cputime': int(raw[4]),
859 'disks': get_disks(vm_),
860 'graphics': get_graphics(vm_),
861 'nics': get_nics(vm_),
862 'maxMem': int(raw[1]),
863 'mem': int(raw[2]),
864 'state': VIRT_STATE_NAME_MAP.get(raw[0], 'unknown')}
865 info = {}
866 if vm_:
867 info[vm_] = _info(vm_)
868 else:
869 for vm_ in list_vms():
870 info[vm_] = _info(vm_)
871 return info
872
873
874def vm_state(vm_=None):
875 '''
876 Return list of all the vms and their state.
877
878 If you pass a VM name in as an argument then it will return info
879 for just the named VM, otherwise it will return all VMs.
880
881 CLI Example:
882
883 .. code-block:: bash
884
Ales Komarekf8188332016-03-09 11:32:08 +0100885 salt '*' virtng.vm_state <vm name>
smolaon1fb381d2016-03-09 11:10:58 +0100886 '''
887 def _info(vm_):
888 state = ''
889 dom = _get_dom(vm_)
890 raw = dom.info()
891 state = VIRT_STATE_NAME_MAP.get(raw[0], 'unknown')
892 return state
893 info = {}
894 if vm_:
895 info[vm_] = _info(vm_)
896 else:
897 for vm_ in list_vms():
898 info[vm_] = _info(vm_)
899 return info
900
901
902def node_info():
903 '''
904 Return a dict with information about this node
905
906 CLI Example:
907
908 .. code-block:: bash
909
Ales Komarekf8188332016-03-09 11:32:08 +0100910 salt '*' virtng.node_info
smolaon1fb381d2016-03-09 11:10:58 +0100911 '''
912 conn = __get_conn()
913 raw = conn.getInfo()
914 info = {'cpucores': raw[6],
915 'cpumhz': raw[3],
916 'cpumodel': str(raw[0]),
917 'cpus': raw[2],
918 'cputhreads': raw[7],
919 'numanodes': raw[4],
920 'phymemory': raw[1],
921 'sockets': raw[5]}
922 return info
923
924
925def get_nics(vm_):
926 '''
927 Return info about the network interfaces of a named vm
928
929 CLI Example:
930
931 .. code-block:: bash
932
Ales Komarekf8188332016-03-09 11:32:08 +0100933 salt '*' virtng.get_nics <vm name>
smolaon1fb381d2016-03-09 11:10:58 +0100934 '''
935 nics = {}
936 doc = minidom.parse(_StringIO(get_xml(vm_)))
937 for node in doc.getElementsByTagName('devices'):
938 i_nodes = node.getElementsByTagName('interface')
939 for i_node in i_nodes:
940 nic = {}
941 nic['type'] = i_node.getAttribute('type')
942 for v_node in i_node.getElementsByTagName('*'):
943 if v_node.tagName == 'mac':
944 nic['mac'] = v_node.getAttribute('address')
945 if v_node.tagName == 'model':
946 nic['model'] = v_node.getAttribute('type')
947 if v_node.tagName == 'target':
948 nic['target'] = v_node.getAttribute('dev')
949 # driver, source, and match can all have optional attributes
950 if re.match('(driver|source|address)', v_node.tagName):
951 temp = {}
952 for key, value in v_node.attributes.items():
953 temp[key] = value
954 nic[str(v_node.tagName)] = temp
955 # virtualport needs to be handled separately, to pick up the
956 # type attribute of the virtualport itself
957 if v_node.tagName == 'virtualport':
958 temp = {}
959 temp['type'] = v_node.getAttribute('type')
960 for key, value in v_node.attributes.items():
961 temp[key] = value
962 nic['virtualport'] = temp
963 if 'mac' not in nic:
964 continue
965 nics[nic['mac']] = nic
966 return nics
967
968
969def get_macs(vm_):
970 '''
971 Return a list off MAC addresses from the named vm
972
973 CLI Example:
974
975 .. code-block:: bash
976
Ales Komarekf8188332016-03-09 11:32:08 +0100977 salt '*' virtng.get_macs <vm name>
smolaon1fb381d2016-03-09 11:10:58 +0100978 '''
979 macs = []
980 doc = minidom.parse(_StringIO(get_xml(vm_)))
981 for node in doc.getElementsByTagName('devices'):
982 i_nodes = node.getElementsByTagName('interface')
983 for i_node in i_nodes:
984 for v_node in i_node.getElementsByTagName('mac'):
985 macs.append(v_node.getAttribute('address'))
986 return macs
987
988
989def get_graphics(vm_):
990 '''
991 Returns the information on vnc for a given vm
992
993 CLI Example:
994
995 .. code-block:: bash
996
Ales Komarekf8188332016-03-09 11:32:08 +0100997 salt '*' virtng.get_graphics <vm name>
smolaon1fb381d2016-03-09 11:10:58 +0100998 '''
999 out = {'autoport': 'None',
1000 'keymap': 'None',
1001 'listen': 'None',
1002 'port': 'None',
1003 'type': 'vnc'}
1004 xml = get_xml(vm_)
1005 ssock = _StringIO(xml)
1006 doc = minidom.parse(ssock)
1007 for node in doc.getElementsByTagName('domain'):
1008 g_nodes = node.getElementsByTagName('graphics')
1009 for g_node in g_nodes:
1010 for key, value in g_node.attributes.items():
1011 out[key] = value
1012 return out
1013
1014
1015def get_disks(vm_):
1016 '''
1017 Return the disks of a named vm
1018
1019 CLI Example:
1020
1021 .. code-block:: bash
1022
Ales Komarekf8188332016-03-09 11:32:08 +01001023 salt '*' virtng.get_disks <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001024 '''
1025 disks = {}
1026 doc = minidom.parse(_StringIO(get_xml(vm_)))
1027 for elem in doc.getElementsByTagName('disk'):
1028 sources = elem.getElementsByTagName('source')
1029 targets = elem.getElementsByTagName('target')
1030 if len(sources) > 0:
1031 source = sources[0]
1032 else:
1033 continue
1034 if len(targets) > 0:
1035 target = targets[0]
1036 else:
1037 continue
1038 if target.hasAttribute('dev'):
1039 qemu_target = ''
1040 if source.hasAttribute('file'):
1041 qemu_target = source.getAttribute('file')
1042 elif source.hasAttribute('dev'):
1043 qemu_target = source.getAttribute('dev')
1044 elif source.hasAttribute('protocol') and \
1045 source.hasAttribute('name'): # For rbd network
1046 qemu_target = '{0}:{1}'.format(
1047 source.getAttribute('protocol'),
1048 source.getAttribute('name'))
1049 if qemu_target:
1050 disks[target.getAttribute('dev')] = {
1051 'file': qemu_target}
1052 for dev in disks:
1053 try:
1054 hypervisor = __salt__['config.get']('libvirt:hypervisor', 'kvm')
1055 if hypervisor not in ['qemu', 'kvm']:
1056 break
1057
1058 output = []
1059 qemu_output = subprocess.Popen(['qemu-img', 'info',
1060 disks[dev]['file']],
1061 shell=False,
1062 stdout=subprocess.PIPE).communicate()[0]
1063 snapshots = False
1064 columns = None
1065 lines = qemu_output.strip().split('\n')
1066 for line in lines:
1067 if line.startswith('Snapshot list:'):
1068 snapshots = True
1069 continue
1070
1071 # If this is a copy-on-write image, then the backing file
1072 # represents the base image
1073 #
1074 # backing file: base.qcow2 (actual path: /var/shared/base.qcow2)
1075 elif line.startswith('backing file'):
1076 matches = re.match(r'.*\(actual path: (.*?)\)', line)
1077 if matches:
1078 output.append('backing file: {0}'.format(matches.group(1)))
1079 continue
1080
1081 elif snapshots:
1082 if line.startswith('ID'): # Do not parse table headers
1083 line = line.replace('VM SIZE', 'VMSIZE')
1084 line = line.replace('VM CLOCK', 'TIME VMCLOCK')
1085 columns = re.split(r'\s+', line)
1086 columns = [c.lower() for c in columns]
1087 output.append('snapshots:')
1088 continue
1089 fields = re.split(r'\s+', line)
1090 for i, field in enumerate(fields):
1091 sep = ' '
1092 if i == 0:
1093 sep = '-'
1094 output.append(
1095 '{0} {1}: "{2}"'.format(
1096 sep, columns[i], field
1097 )
1098 )
1099 continue
1100 output.append(line)
1101 output = '\n'.join(output)
1102 disks[dev].update(yaml.safe_load(output))
1103 except TypeError:
1104 disks[dev].update(yaml.safe_load('image: Does not exist'))
1105 return disks
1106
1107
1108def setmem(vm_, memory, config=False):
1109 '''
1110 Changes the amount of memory allocated to VM. The VM must be shutdown
1111 for this to work.
1112
1113 memory is to be specified in MB
1114 If config is True then we ask libvirt to modify the config as well
1115
1116 CLI Example:
1117
1118 .. code-block:: bash
1119
Ales Komarekf8188332016-03-09 11:32:08 +01001120 salt '*' virtng.setmem myvm 768
smolaon1fb381d2016-03-09 11:10:58 +01001121 '''
1122 if vm_state(vm_) != 'shutdown':
1123 return False
1124
1125 dom = _get_dom(vm_)
1126
1127 # libvirt has a funny bitwise system for the flags in that the flag
1128 # to affect the "current" setting is 0, which means that to set the
1129 # current setting we have to call it a second time with just 0 set
1130 flags = libvirt.VIR_DOMAIN_MEM_MAXIMUM
1131 if config:
1132 flags = flags | libvirt.VIR_DOMAIN_AFFECT_CONFIG
1133
1134 ret1 = dom.setMemoryFlags(memory * 1024, flags)
1135 ret2 = dom.setMemoryFlags(memory * 1024, libvirt.VIR_DOMAIN_AFFECT_CURRENT)
1136
1137 # return True if both calls succeeded
1138 return ret1 == ret2 == 0
1139
1140
1141def setvcpus(vm_, vcpus, config=False):
1142 '''
1143 Changes the amount of vcpus allocated to VM. The VM must be shutdown
1144 for this to work.
1145
1146 vcpus is an int representing the number to be assigned
1147 If config is True then we ask libvirt to modify the config as well
1148
1149 CLI Example:
1150
1151 .. code-block:: bash
1152
Ales Komarekf8188332016-03-09 11:32:08 +01001153 salt '*' virtng.setvcpus myvm 2
smolaon1fb381d2016-03-09 11:10:58 +01001154 '''
1155 if vm_state(vm_) != 'shutdown':
1156 return False
1157
1158 dom = _get_dom(vm_)
1159
1160 # see notes in setmem
1161 flags = libvirt.VIR_DOMAIN_VCPU_MAXIMUM
1162 if config:
1163 flags = flags | libvirt.VIR_DOMAIN_AFFECT_CONFIG
1164
1165 ret1 = dom.setVcpusFlags(vcpus, flags)
1166 ret2 = dom.setVcpusFlags(vcpus, libvirt.VIR_DOMAIN_AFFECT_CURRENT)
1167
1168 return ret1 == ret2 == 0
1169
1170
1171def freemem():
1172 '''
1173 Return an int representing the amount of memory that has not been given
1174 to virtual machines on this node
1175
1176 CLI Example:
1177
1178 .. code-block:: bash
1179
Ales Komarekf8188332016-03-09 11:32:08 +01001180 salt '*' virtng.freemem
smolaon1fb381d2016-03-09 11:10:58 +01001181 '''
1182 conn = __get_conn()
1183 mem = conn.getInfo()[1]
1184 # Take off just enough to sustain the hypervisor
1185 mem -= 256
1186 for vm_ in list_vms():
1187 dom = _get_dom(vm_)
1188 if dom.ID() > 0:
1189 mem -= dom.info()[2] / 1024
1190 return mem
1191
1192
1193def freecpu():
1194 '''
1195 Return an int representing the number of unallocated cpus on this
1196 hypervisor
1197
1198 CLI Example:
1199
1200 .. code-block:: bash
1201
Ales Komarekf8188332016-03-09 11:32:08 +01001202 salt '*' virtng.freecpu
smolaon1fb381d2016-03-09 11:10:58 +01001203 '''
1204 conn = __get_conn()
1205 cpus = conn.getInfo()[2]
1206 for vm_ in list_vms():
1207 dom = _get_dom(vm_)
1208 if dom.ID() > 0:
1209 cpus -= dom.info()[3]
1210 return cpus
1211
1212
1213def full_info():
1214 '''
1215 Return the node_info, vm_info and freemem
1216
1217 CLI Example:
1218
1219 .. code-block:: bash
1220
Ales Komarekf8188332016-03-09 11:32:08 +01001221 salt '*' virtng.full_info
smolaon1fb381d2016-03-09 11:10:58 +01001222 '''
1223 return {'freecpu': freecpu(),
1224 'freemem': freemem(),
1225 'node_info': node_info(),
1226 'vm_info': vm_info()}
1227
1228
1229def get_xml(vm_):
1230 '''
1231 Returns the XML for a given vm
1232
1233 CLI Example:
1234
1235 .. code-block:: bash
1236
Ales Komarekf8188332016-03-09 11:32:08 +01001237 salt '*' virtng.get_xml <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001238 '''
1239 dom = _get_dom(vm_)
1240 return dom.XMLDesc(0)
1241
1242
1243def get_profiles(hypervisor=None):
1244 '''
1245 Return the virt profiles for hypervisor.
1246
1247 Currently there are profiles for:
1248
1249 - nic
1250 - disk
1251
1252 CLI Example:
1253
1254 .. code-block:: bash
1255
Ales Komarekf8188332016-03-09 11:32:08 +01001256 salt '*' virtng.get_profiles
1257 salt '*' virtng.get_profiles hypervisor=esxi
smolaon1fb381d2016-03-09 11:10:58 +01001258 '''
1259 ret = {}
1260 if hypervisor:
1261 hypervisor = hypervisor
1262 else:
1263 hypervisor = __salt__['config.get']('libvirt:hypervisor', VIRT_DEFAULT_HYPER)
1264 virtconf = __salt__['config.get']('virt', {})
1265 for typ in ['disk', 'nic']:
1266 _func = getattr(sys.modules[__name__], '_{0}_profile'.format(typ))
1267 ret[typ] = {'default': _func('default', hypervisor)}
1268 if typ in virtconf:
1269 ret.setdefault(typ, {})
1270 for prf in virtconf[typ]:
1271 ret[typ][prf] = _func(prf, hypervisor)
1272 return ret
1273
1274
1275def shutdown(vm_):
1276 '''
1277 Send a soft shutdown signal to the named vm
1278
1279 CLI Example:
1280
1281 .. code-block:: bash
1282
Ales Komarekf8188332016-03-09 11:32:08 +01001283 salt '*' virtng.shutdown <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001284 '''
1285 dom = _get_dom(vm_)
1286 return dom.shutdown() == 0
1287
1288
1289def pause(vm_):
1290 '''
1291 Pause the named vm
1292
1293 CLI Example:
1294
1295 .. code-block:: bash
1296
Ales Komarekf8188332016-03-09 11:32:08 +01001297 salt '*' virtng.pause <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001298 '''
1299 dom = _get_dom(vm_)
1300 return dom.suspend() == 0
1301
1302
1303def resume(vm_):
1304 '''
1305 Resume the named vm
1306
1307 CLI Example:
1308
1309 .. code-block:: bash
1310
Ales Komarekf8188332016-03-09 11:32:08 +01001311 salt '*' virtng.resume <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001312 '''
1313 dom = _get_dom(vm_)
1314 return dom.resume() == 0
1315
1316
1317def create(vm_):
1318 '''
1319 Start a defined domain
1320
1321 CLI Example:
1322
1323 .. code-block:: bash
1324
Ales Komarekf8188332016-03-09 11:32:08 +01001325 salt '*' virtng.create <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001326 '''
1327 dom = _get_dom(vm_)
1328 return dom.create() == 0
1329
1330
1331def start(vm_):
1332 '''
1333 Alias for the obscurely named 'create' function
1334
1335 CLI Example:
1336
1337 .. code-block:: bash
1338
Ales Komarekf8188332016-03-09 11:32:08 +01001339 salt '*' virtng.start <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001340 '''
1341 return create(vm_)
1342
1343
1344def stop(vm_):
1345 '''
1346 Alias for the obscurely named 'destroy' function
1347
1348 CLI Example:
1349
1350 .. code-block:: bash
1351
Ales Komarekf8188332016-03-09 11:32:08 +01001352 salt '*' virtng.stop <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001353 '''
1354 return destroy(vm_)
1355
1356
1357def reboot(vm_):
1358 '''
1359 Reboot a domain via ACPI request
1360
1361 CLI Example:
1362
1363 .. code-block:: bash
1364
Ales Komarekf8188332016-03-09 11:32:08 +01001365 salt '*' virtng.reboot <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001366 '''
1367 dom = _get_dom(vm_)
1368
1369 # reboot has a few modes of operation, passing 0 in means the
1370 # hypervisor will pick the best method for rebooting
1371 return dom.reboot(0) == 0
1372
1373
1374def reset(vm_):
1375 '''
1376 Reset a VM by emulating the reset button on a physical machine
1377
1378 CLI Example:
1379
1380 .. code-block:: bash
1381
Ales Komarekf8188332016-03-09 11:32:08 +01001382 salt '*' virtng.reset <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001383 '''
1384 dom = _get_dom(vm_)
1385
1386 # reset takes a flag, like reboot, but it is not yet used
1387 # so we just pass in 0
1388 # see: http://libvirt.org/html/libvirt-libvirt.html#virDomainReset
1389 return dom.reset(0) == 0
1390
1391
1392def ctrl_alt_del(vm_):
1393 '''
1394 Sends CTRL+ALT+DEL to a VM
1395
1396 CLI Example:
1397
1398 .. code-block:: bash
1399
Ales Komarekf8188332016-03-09 11:32:08 +01001400 salt '*' virtng.ctrl_alt_del <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001401 '''
1402 dom = _get_dom(vm_)
1403 return dom.sendKey(0, 0, [29, 56, 111], 3, 0) == 0
1404
1405
1406def create_xml_str(xml):
1407 '''
1408 Start a domain based on the XML passed to the function
1409
1410 CLI Example:
1411
1412 .. code-block:: bash
1413
Ales Komarekf8188332016-03-09 11:32:08 +01001414 salt '*' virtng.create_xml_str <XML in string format>
smolaon1fb381d2016-03-09 11:10:58 +01001415 '''
1416 conn = __get_conn()
1417 return conn.createXML(xml, 0) is not None
1418
1419
1420def create_xml_path(path):
1421 '''
1422 Start a domain based on the XML-file path passed to the function
1423
1424 CLI Example:
1425
1426 .. code-block:: bash
1427
Ales Komarekf8188332016-03-09 11:32:08 +01001428 salt '*' virtng.create_xml_path <path to XML file on the node>
smolaon1fb381d2016-03-09 11:10:58 +01001429 '''
1430 if not os.path.isfile(path):
1431 return False
1432 return create_xml_str(salt.utils.fopen(path, 'r').read())
1433
1434
1435def define_xml_str(xml):
1436 '''
1437 Define a domain based on the XML passed to the function
1438
1439 CLI Example:
1440
1441 .. code-block:: bash
1442
Ales Komarekf8188332016-03-09 11:32:08 +01001443 salt '*' virtng.define_xml_str <XML in string format>
smolaon1fb381d2016-03-09 11:10:58 +01001444 '''
1445 conn = __get_conn()
1446 return conn.defineXML(xml) is not None
1447
1448
1449def define_xml_path(path):
1450 '''
1451 Define a domain based on the XML-file path passed to the function
1452
1453 CLI Example:
1454
1455 .. code-block:: bash
1456
Ales Komarekf8188332016-03-09 11:32:08 +01001457 salt '*' virtng.define_xml_path <path to XML file on the node>
smolaon1fb381d2016-03-09 11:10:58 +01001458
1459 '''
1460 if not os.path.isfile(path):
1461 return False
1462 return define_xml_str(salt.utils.fopen(path, 'r').read())
1463
1464
1465def define_vol_xml_str(xml):
1466 '''
1467 Define a volume based on the XML passed to the function
1468
1469 CLI Example:
1470
1471 .. code-block:: bash
1472
Ales Komarekf8188332016-03-09 11:32:08 +01001473 salt '*' virtng.define_vol_xml_str <XML in string format>
smolaon1fb381d2016-03-09 11:10:58 +01001474 '''
1475 poolname = __salt__['config.get']('libvirt:storagepool', 'default')
1476 conn = __get_conn()
1477 pool = conn.storagePoolLookupByName(str(poolname))
1478 return pool.createXML(xml, 0) is not None
1479
1480
1481def define_vol_xml_path(path):
1482 '''
1483 Define a volume based on the XML-file path passed to the function
1484
1485 CLI Example:
1486
1487 .. code-block:: bash
1488
Ales Komarekf8188332016-03-09 11:32:08 +01001489 salt '*' virtng.define_vol_xml_path <path to XML file on the node>
smolaon1fb381d2016-03-09 11:10:58 +01001490
1491 '''
1492 if not os.path.isfile(path):
1493 return False
1494 return define_vol_xml_str(salt.utils.fopen(path, 'r').read())
1495
1496
1497def migrate_non_shared(vm_, target, ssh=False):
1498 '''
1499 Attempt to execute non-shared storage "all" migration
1500
1501 CLI Example:
1502
1503 .. code-block:: bash
1504
Ales Komarekf8188332016-03-09 11:32:08 +01001505 salt '*' virtng.migrate_non_shared <vm name> <target hypervisor>
smolaon1fb381d2016-03-09 11:10:58 +01001506 '''
1507 cmd = _get_migrate_command() + ' --copy-storage-all ' + vm_\
1508 + _get_target(target, ssh)
1509
1510 return subprocess.Popen(cmd,
1511 shell=True,
1512 stdout=subprocess.PIPE).communicate()[0]
1513
1514
1515def migrate_non_shared_inc(vm_, target, ssh=False):
1516 '''
1517 Attempt to execute non-shared storage "all" migration
1518
1519 CLI Example:
1520
1521 .. code-block:: bash
1522
Ales Komarekf8188332016-03-09 11:32:08 +01001523 salt '*' virtng.migrate_non_shared_inc <vm name> <target hypervisor>
smolaon1fb381d2016-03-09 11:10:58 +01001524 '''
1525 cmd = _get_migrate_command() + ' --copy-storage-inc ' + vm_\
1526 + _get_target(target, ssh)
1527
1528 return subprocess.Popen(cmd,
1529 shell=True,
1530 stdout=subprocess.PIPE).communicate()[0]
1531
1532
1533def migrate(vm_, target, ssh=False):
1534 '''
1535 Shared storage migration
1536
1537 CLI Example:
1538
1539 .. code-block:: bash
1540
Ales Komarekf8188332016-03-09 11:32:08 +01001541 salt '*' virtng.migrate <vm name> <target hypervisor>
smolaon1fb381d2016-03-09 11:10:58 +01001542 '''
1543 cmd = _get_migrate_command() + ' ' + vm_\
1544 + _get_target(target, ssh)
1545
1546 return subprocess.Popen(cmd,
1547 shell=True,
1548 stdout=subprocess.PIPE).communicate()[0]
1549
1550
1551def seed_non_shared_migrate(disks, force=False):
1552 '''
1553 Non shared migration requires that the disks be present on the migration
1554 destination, pass the disks information via this function, to the
1555 migration destination before executing the migration.
1556
1557 CLI Example:
1558
1559 .. code-block:: bash
1560
Ales Komarekf8188332016-03-09 11:32:08 +01001561 salt '*' virtng.seed_non_shared_migrate <disks>
smolaon1fb381d2016-03-09 11:10:58 +01001562 '''
1563 for _, data in disks.items():
1564 fn_ = data['file']
1565 form = data['file format']
1566 size = data['virtual size'].split()[1][1:]
1567 if os.path.isfile(fn_) and not force:
1568 # the target exists, check to see if it is compatible
1569 pre = yaml.safe_load(subprocess.Popen('qemu-img info arch',
1570 shell=True,
1571 stdout=subprocess.PIPE).communicate()[0])
1572 if pre['file format'] != data['file format']\
1573 and pre['virtual size'] != data['virtual size']:
1574 return False
1575 if not os.path.isdir(os.path.dirname(fn_)):
1576 os.makedirs(os.path.dirname(fn_))
1577 if os.path.isfile(fn_):
1578 os.remove(fn_)
1579 cmd = 'qemu-img create -f ' + form + ' ' + fn_ + ' ' + size
1580 subprocess.call(cmd, shell=True)
1581 creds = _libvirt_creds()
1582 cmd = 'chown ' + creds['user'] + ':' + creds['group'] + ' ' + fn_
1583 subprocess.call(cmd, shell=True)
1584 return True
1585
1586
1587def set_autostart(vm_, state='on'):
1588 '''
1589 Set the autostart flag on a VM so that the VM will start with the host
1590 system on reboot.
1591
1592 CLI Example:
1593
1594 .. code-block:: bash
1595
1596 salt "*" virt.set_autostart <vm name> <on | off>
1597 '''
1598
1599 dom = _get_dom(vm_)
1600
1601 if state == 'on':
1602 return dom.setAutostart(1) == 0
1603
1604 elif state == 'off':
1605 return dom.setAutostart(0) == 0
1606
1607 else:
1608 # return False if state is set to something other then on or off
1609 return False
1610
1611
1612def destroy(vm_):
1613 '''
1614 Hard power down the virtual machine, this is equivalent to pulling the
1615 power
1616
1617 CLI Example:
1618
1619 .. code-block:: bash
1620
Ales Komarekf8188332016-03-09 11:32:08 +01001621 salt '*' virtng.destroy <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001622 '''
1623 dom = _get_dom(vm_)
1624 return dom.destroy() == 0
1625
1626
1627def undefine(vm_):
1628 '''
1629 Remove a defined vm, this does not purge the virtual machine image, and
1630 this only works if the vm is powered down
1631
1632 CLI Example:
1633
1634 .. code-block:: bash
1635
Ales Komarekf8188332016-03-09 11:32:08 +01001636 salt '*' virtng.undefine <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001637 '''
1638 dom = _get_dom(vm_)
Alexandru Avadanii00f187a2018-06-24 20:36:44 +02001639 if getattr(libvirt, 'VIR_DOMAIN_UNDEFINE_NVRAM', False):
1640 # This one is only in 1.2.8+
1641 return dom.undefineFlags(libvirt.VIR_DOMAIN_UNDEFINE_NVRAM) == 0
1642 else:
1643 return dom.undefine() == 0
smolaon1fb381d2016-03-09 11:10:58 +01001644
1645
1646def purge(vm_, dirs=False):
1647 '''
1648 Recursively destroy and delete a virtual machine, pass True for dir's to
1649 also delete the directories containing the virtual machine disk images -
1650 USE WITH EXTREME CAUTION!
1651
1652 CLI Example:
1653
1654 .. code-block:: bash
1655
Ales Komarekf8188332016-03-09 11:32:08 +01001656 salt '*' virtng.purge <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001657 '''
1658 disks = get_disks(vm_)
1659 try:
1660 if not destroy(vm_):
1661 return False
1662 except libvirt.libvirtError:
1663 # This is thrown if the machine is already shut down
1664 pass
1665 directories = set()
1666 for disk in disks:
1667 os.remove(disks[disk]['file'])
1668 directories.add(os.path.dirname(disks[disk]['file']))
1669 if dirs:
1670 for dir_ in directories:
1671 shutil.rmtree(dir_)
1672 undefine(vm_)
1673 return True
1674
1675
1676def virt_type():
1677 '''
1678 Returns the virtual machine type as a string
1679
1680 CLI Example:
1681
1682 .. code-block:: bash
1683
Ales Komarekf8188332016-03-09 11:32:08 +01001684 salt '*' virtng.virt_type
smolaon1fb381d2016-03-09 11:10:58 +01001685 '''
1686 return __grains__['virtual']
1687
1688
1689def is_kvm_hyper():
1690 '''
1691 Returns a bool whether or not this node is a KVM hypervisor
1692
1693 CLI Example:
1694
1695 .. code-block:: bash
1696
Ales Komarekf8188332016-03-09 11:32:08 +01001697 salt '*' virtng.is_kvm_hyper
smolaon1fb381d2016-03-09 11:10:58 +01001698 '''
1699 try:
1700 if 'kvm_' not in salt.utils.fopen('/proc/modules').read():
1701 return False
1702 except IOError:
1703 # No /proc/modules? Are we on Windows? Or Solaris?
1704 return False
Oleh Hryhorov7c5dfd32018-01-19 15:43:44 +02001705 return 'libvirtd' in __salt__['cmd.shell'](__grains__['ps'])
smolaon1fb381d2016-03-09 11:10:58 +01001706
1707
1708def is_xen_hyper():
1709 '''
1710 Returns a bool whether or not this node is a XEN hypervisor
1711
1712 CLI Example:
1713
1714 .. code-block:: bash
1715
Ales Komarekf8188332016-03-09 11:32:08 +01001716 salt '*' virtng.is_xen_hyper
smolaon1fb381d2016-03-09 11:10:58 +01001717 '''
1718 try:
1719 if __grains__['virtual_subtype'] != 'Xen Dom0':
1720 return False
1721 except KeyError:
1722 # virtual_subtype isn't set everywhere.
1723 return False
1724 try:
1725 if 'xen_' not in salt.utils.fopen('/proc/modules').read():
1726 return False
1727 except IOError:
1728 # No /proc/modules? Are we on Windows? Or Solaris?
1729 return False
Oleh Hryhorov7c5dfd32018-01-19 15:43:44 +02001730 return 'libvirtd' in __salt__['cmd.shell'](__grains__['ps'])
smolaon1fb381d2016-03-09 11:10:58 +01001731
1732
1733def is_hyper():
1734 '''
1735 Returns a bool whether or not this node is a hypervisor of any kind
1736
1737 CLI Example:
1738
1739 .. code-block:: bash
1740
Ales Komarekf8188332016-03-09 11:32:08 +01001741 salt '*' virtng.is_hyper
smolaon1fb381d2016-03-09 11:10:58 +01001742 '''
1743 try:
1744 import libvirt # pylint: disable=import-error
1745 except ImportError:
1746 # not a usable hypervisor without libvirt module
1747 return False
1748 return is_xen_hyper() or is_kvm_hyper()
1749
1750
1751def vm_cputime(vm_=None):
1752 '''
1753 Return cputime used by the vms on this hyper in a
1754 list of dicts:
1755
1756 .. code-block:: python
1757
1758 [
1759 'your-vm': {
1760 'cputime' <int>
1761 'cputime_percent' <int>
1762 },
1763 ...
1764 ]
1765
1766 If you pass a VM name in as an argument then it will return info
1767 for just the named VM, otherwise it will return all VMs.
1768
1769 CLI Example:
1770
1771 .. code-block:: bash
1772
Ales Komarekf8188332016-03-09 11:32:08 +01001773 salt '*' virtng.vm_cputime
smolaon1fb381d2016-03-09 11:10:58 +01001774 '''
1775 host_cpus = __get_conn().getInfo()[2]
1776
1777 def _info(vm_):
1778 dom = _get_dom(vm_)
1779 raw = dom.info()
1780 vcpus = int(raw[3])
1781 cputime = int(raw[4])
1782 cputime_percent = 0
1783 if cputime:
1784 # Divide by vcpus to always return a number between 0 and 100
1785 cputime_percent = (1.0e-7 * cputime / host_cpus) / vcpus
1786 return {
1787 'cputime': int(raw[4]),
1788 'cputime_percent': int('{0:.0f}'.format(cputime_percent))
1789 }
1790 info = {}
1791 if vm_:
1792 info[vm_] = _info(vm_)
1793 else:
1794 for vm_ in list_vms():
1795 info[vm_] = _info(vm_)
1796 return info
1797
1798
1799def vm_netstats(vm_=None):
1800 '''
1801 Return combined network counters used by the vms on this hyper in a
1802 list of dicts:
1803
1804 .. code-block:: python
1805
1806 [
1807 'your-vm': {
1808 'rx_bytes' : 0,
1809 'rx_packets' : 0,
1810 'rx_errs' : 0,
1811 'rx_drop' : 0,
1812 'tx_bytes' : 0,
1813 'tx_packets' : 0,
1814 'tx_errs' : 0,
1815 'tx_drop' : 0
1816 },
1817 ...
1818 ]
1819
1820 If you pass a VM name in as an argument then it will return info
1821 for just the named VM, otherwise it will return all VMs.
1822
1823 CLI Example:
1824
1825 .. code-block:: bash
1826
Ales Komarekf8188332016-03-09 11:32:08 +01001827 salt '*' virtng.vm_netstats
smolaon1fb381d2016-03-09 11:10:58 +01001828 '''
1829 def _info(vm_):
1830 dom = _get_dom(vm_)
1831 nics = get_nics(vm_)
1832 ret = {
1833 'rx_bytes': 0,
1834 'rx_packets': 0,
1835 'rx_errs': 0,
1836 'rx_drop': 0,
1837 'tx_bytes': 0,
1838 'tx_packets': 0,
1839 'tx_errs': 0,
1840 'tx_drop': 0
1841 }
1842 for attrs in six.itervalues(nics):
1843 if 'target' in attrs:
1844 dev = attrs['target']
1845 stats = dom.interfaceStats(dev)
1846 ret['rx_bytes'] += stats[0]
1847 ret['rx_packets'] += stats[1]
1848 ret['rx_errs'] += stats[2]
1849 ret['rx_drop'] += stats[3]
1850 ret['tx_bytes'] += stats[4]
1851 ret['tx_packets'] += stats[5]
1852 ret['tx_errs'] += stats[6]
1853 ret['tx_drop'] += stats[7]
1854
1855 return ret
1856 info = {}
1857 if vm_:
1858 info[vm_] = _info(vm_)
1859 else:
1860 for vm_ in list_vms():
1861 info[vm_] = _info(vm_)
1862 return info
1863
1864
1865def vm_diskstats(vm_=None):
1866 '''
1867 Return disk usage counters used by the vms on this hyper in a
1868 list of dicts:
1869
1870 .. code-block:: python
1871
1872 [
1873 'your-vm': {
1874 'rd_req' : 0,
1875 'rd_bytes' : 0,
1876 'wr_req' : 0,
1877 'wr_bytes' : 0,
1878 'errs' : 0
1879 },
1880 ...
1881 ]
1882
1883 If you pass a VM name in as an argument then it will return info
1884 for just the named VM, otherwise it will return all VMs.
1885
1886 CLI Example:
1887
1888 .. code-block:: bash
1889
Ales Komarekf8188332016-03-09 11:32:08 +01001890 salt '*' virtng.vm_blockstats
smolaon1fb381d2016-03-09 11:10:58 +01001891 '''
1892 def get_disk_devs(vm_):
1893 doc = minidom.parse(_StringIO(get_xml(vm_)))
1894 disks = []
1895 for elem in doc.getElementsByTagName('disk'):
1896 targets = elem.getElementsByTagName('target')
1897 target = targets[0]
1898 disks.append(target.getAttribute('dev'))
1899 return disks
1900
1901 def _info(vm_):
1902 dom = _get_dom(vm_)
1903 # Do not use get_disks, since it uses qemu-img and is very slow
1904 # and unsuitable for any sort of real time statistics
1905 disks = get_disk_devs(vm_)
1906 ret = {'rd_req': 0,
1907 'rd_bytes': 0,
1908 'wr_req': 0,
1909 'wr_bytes': 0,
1910 'errs': 0
1911 }
1912 for disk in disks:
1913 stats = dom.blockStats(disk)
1914 ret['rd_req'] += stats[0]
1915 ret['rd_bytes'] += stats[1]
1916 ret['wr_req'] += stats[2]
1917 ret['wr_bytes'] += stats[3]
1918 ret['errs'] += stats[4]
1919
1920 return ret
1921 info = {}
1922 if vm_:
1923 info[vm_] = _info(vm_)
1924 else:
1925 # Can not run function blockStats on inactive VMs
1926 for vm_ in list_active_vms():
1927 info[vm_] = _info(vm_)
1928 return info