blob: b9ef85d91c582e09b2765c65ac1c4e4d5e4d1358 [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,
smolaon1fb381d2016-03-09 11:10:58 +0100534 **kwargs):
535 '''
536 Initialize a new vm
537
538 CLI Example:
539
540 .. code-block:: bash
541
542 salt 'hypervisor' virt.init vm_name 4 512 salt://path/to/image.raw
543 salt 'hypervisor' virt.init vm_name 4 512 nic=profile disk=profile
544 '''
Dennis Dmitrieva57463c2017-03-24 17:05:22 +0200545
azvyagintseva4e802d2018-05-04 20:16:02 +0300546 rng = rng or {'backend':'/dev/urandom'}
smolaon1fb381d2016-03-09 11:10:58 +0100547 hypervisor = __salt__['config.get']('libvirt:hypervisor', hypervisor)
548
549 nicp = _nic_profile(nic, hypervisor, **kwargs)
550
Dennis Dmitrieva57463c2017-03-24 17:05:22 +0200551 diskp = _disk_profile(disk, hypervisor, **kwargs)
552
553 if image:
554 # Backward compatibility: if 'image' is specified in the VMs arguments
555 # instead of a disk arguments. In this case, 'image' will be assigned
556 # to the first disk for the VM.
smolaon1fb381d2016-03-09 11:10:58 +0100557 disk_name = next(diskp[0].iterkeys())
Dennis Dmitrieva57463c2017-03-24 17:05:22 +0200558 if not diskp[0][disk_name].get('image', None):
559 diskp[0][disk_name]['image'] = image
smolaon1fb381d2016-03-09 11:10:58 +0100560
Dennis Dmitrieva57463c2017-03-24 17:05:22 +0200561 # Create multiple disks, empty or from specified images.
Dzmitry Stremkouski97927ee2018-08-23 23:20:38 +0200562 cloud_init = None
563 cfg_drive = None
564
Dennis Dmitrieva57463c2017-03-24 17:05:22 +0200565 for disk in diskp:
566 log.debug("Creating disk for VM [ {0} ]: {1}".format(name, disk))
smolaon1fb381d2016-03-09 11:10:58 +0100567
Dennis Dmitrieva57463c2017-03-24 17:05:22 +0200568 for disk_name, args in disk.items():
smolaon1fb381d2016-03-09 11:10:58 +0100569
Dennis Dmitrieva57463c2017-03-24 17:05:22 +0200570 if hypervisor in ['esxi', 'vmware']:
571 if 'image' in args:
572 # TODO: we should be copying the image file onto the ESX host
573 raise SaltInvocationError('virt.init does not support image '
574 'template template in conjunction '
575 'with esxi hypervisor')
576 else:
577 # assume libvirt manages disks for us
smolaon1fb381d2016-03-09 11:10:58 +0100578 xml = _gen_vol_xml(name,
579 disk_name,
580 args['size'],
Mateusz Los4c7cd2d2018-01-09 11:46:07 +0100581 hypervisor,
582 **kwargs)
smolaon1fb381d2016-03-09 11:10:58 +0100583 define_vol_xml_str(xml)
584
Dennis Dmitrieva57463c2017-03-24 17:05:22 +0200585 elif hypervisor in ['qemu', 'kvm']:
586
587 disk_type = args['format']
588 disk_file_name = '{0}.{1}'.format(disk_name, disk_type)
589 # disk size TCP cloud
590 disk_size = args['size']
591
Mateusz Los4c7cd2d2018-01-09 11:46:07 +0100592 if 'img_dest' in kwargs:
593 img_dir = kwargs['img_dest']
594 else:
595 img_dir = __salt__['config.option']('virt.images')
Dennis Dmitrieva57463c2017-03-24 17:05:22 +0200596 img_dest = os.path.join(
597 img_dir,
598 name,
599 disk_file_name
600 )
601 img_dir = os.path.dirname(img_dest)
602 if not os.path.isdir(img_dir):
603 os.makedirs(img_dir)
604
605 if 'image' in args:
606 # Create disk from specified image
607 sfn = __salt__['cp.cache_file'](args['image'], saltenv)
608 try:
609 salt.utils.files.copyfile(sfn, img_dest)
610 mask = os.umask(0)
611 os.umask(mask)
612 # Apply umask and remove exec bit
613
614 # Resizing image TCP cloud
615 cmd = 'qemu-img resize ' + img_dest + ' ' + str(disk_size) + 'M'
616 subprocess.call(cmd, shell=True)
617
618 mode = (0o0777 ^ mask) & 0o0666
619 os.chmod(img_dest, mode)
620
621 except (IOError, OSError) as e:
622 raise CommandExecutionError('problem while copying image. {0} - {1}'.format(args['image'], e))
623
624 if kwargs.get('seed'):
Dzmitry Stremkouski97927ee2018-08-23 23:20:38 +0200625 seed_cmd = kwargs.get('seed_cmd', 'seedng.apply')
626 cloud_init = kwargs.get('cloud_init', None)
627 master = __salt__['config.option']('master')
628 cfg_drive = os.path.join(img_dir,'config-2.iso')
Dennis Dmitrieva57463c2017-03-24 17:05:22 +0200629
Dzmitry Stremkouski97927ee2018-08-23 23:20:38 +0200630 if cloud_init:
631 _tmp = name.split('.')
632
633 try:
634 user_data = json.dumps(cloud_init["user_data"])
635 except:
636 user_data = None
637
638 try:
639 network_data = json.dumps(cloud_init["network_data"])
640 except:
641 network_data = None
642
643 __salt__["cfgdrive.generate"](
644 dst = cfg_drive,
645 hostname = _tmp.pop(0),
646 domainname = '.'.join(_tmp),
647 user_data = user_data,
648 network_data = network_data,
649 saltconfig = { "salt_minion": { "conf": { "master": master, "id": name } } }
650 )
651 else:
652 __salt__[seed_cmd](
653 path = img_dest,
654 id_ = name,
655 config = kwargs.get('config'),
656 install = kwargs.get('install', True)
657 )
Dennis Dmitrieva57463c2017-03-24 17:05:22 +0200658 else:
659 # Create empty disk
660 try:
661 mask = os.umask(0)
662 os.umask(mask)
663 # Apply umask and remove exec bit
664
665 # Create empty image
666 cmd = 'qemu-img create -f ' + disk_type + ' ' + img_dest + ' ' + str(disk_size) + 'M'
667 subprocess.call(cmd, shell=True)
668
669 mode = (0o0777 ^ mask) & 0o0666
670 os.chmod(img_dest, mode)
671
672 except (IOError, OSError) as e:
673 raise CommandExecutionError('problem while creating volume {0} - {1}'.format(img_dest, e))
674
675 else:
676 # Unknown hypervisor
677 raise SaltInvocationError('Unsupported hypervisor when handling disk image: {0}'
678 .format(hypervisor))
679
smolaon1fb381d2016-03-09 11:10:58 +0100680 xml = _gen_xml(name, cpu, mem, diskp, nicp, hypervisor, **kwargs)
Dzmitry Stremkouski7ee23402018-04-10 00:43:48 +0200681
Dzmitry Stremkouski97927ee2018-08-23 23:20:38 +0200682 if cloud_init and cfg_drive:
683 xml_doc = minidom.parseString(xml)
684 iso_xml = xml_doc.createElement("disk")
685 iso_xml.setAttribute("type", "file")
686 iso_xml.setAttribute("device", "cdrom")
687 iso_xml.appendChild(xml_doc.createElement("readonly"))
688 driver = xml_doc.createElement("driver")
689 driver.setAttribute("name", "qemu")
690 driver.setAttribute("type", "raw")
691 target = xml_doc.createElement("target")
692 target.setAttribute("dev", "hdc")
693 target.setAttribute("bus", "ide")
694 source = xml_doc.createElement("source")
695 source.setAttribute("file", cfg_drive)
696 iso_xml.appendChild(driver)
697 iso_xml.appendChild(target)
698 iso_xml.appendChild(source)
699 xml_doc.getElementsByTagName("domain")[0].getElementsByTagName("devices")[0].appendChild(iso_xml)
700 xml = xml_doc.toxml()
701
Dzmitry Stremkouski7ee23402018-04-10 00:43:48 +0200702 # TODO: Remove this code and refactor module, when salt-common would have updated libvirt_domain.jinja template
Dzmitry Stremkouskib8acf1f2018-06-28 12:56:23 +0200703 for _nic in nicp:
704 if _nic['virtualport']:
705 xml_doc = minidom.parseString(xml)
706 interfaces = xml_doc.getElementsByTagName("domain")[0].getElementsByTagName("devices")[0].getElementsByTagName("interface")
707 for interface in interfaces:
708 if interface.getElementsByTagName('mac')[0].getAttribute('address').lower() == _nic['mac'].lower():
709 vport_xml = xml_doc.createElement("virtualport")
710 vport_xml.setAttribute("type", _nic['virtualport']['type'])
711 interface.appendChild(vport_xml)
712 xml = xml_doc.toxml()
713
714 # TODO: Remove this code and refactor module, when salt-common would have updated libvirt_domain.jinja template
Dzmitry Stremkouski7ee23402018-04-10 00:43:48 +0200715 if rng:
716 rng_model = rng.get('model', 'random')
717 rng_backend = rng.get('backend', '/dev/urandom')
718 xml_doc = minidom.parseString(xml)
719 rng_xml = xml_doc.createElement("rng")
720 rng_xml.setAttribute("model", "virtio")
721 backend = xml_doc.createElement("backend")
722 backend.setAttribute("model", rng_model)
723 backend.appendChild(xml_doc.createTextNode(rng_backend))
724 rng_xml.appendChild(backend)
725 if 'rate' in rng:
726 rng_rate_period = rng['rate'].get('period', '2000')
727 rng_rate_bytes = rng['rate'].get('bytes', '1234')
728 rate = xml_doc.createElement("rate")
729 rate.setAttribute("period", rng_rate_period)
730 rate.setAttribute("bytes", rng_rate_bytes)
731 rng_xml.appendChild(rate)
732 xml_doc.getElementsByTagName("domain")[0].getElementsByTagName("devices")[0].appendChild(rng_xml)
733 xml = xml_doc.toxml()
734
smolaon1fb381d2016-03-09 11:10:58 +0100735 define_xml_str(xml)
736
smolaon1fb381d2016-03-09 11:10:58 +0100737 if start:
738 create(name)
739
740 return True
741
742
743def list_vms():
744 '''
745 Return a list of virtual machine names on the minion
746
747 CLI Example:
748
749 .. code-block:: bash
750
Ales Komarekf8188332016-03-09 11:32:08 +0100751 salt '*' virtng.list_vms
smolaon1fb381d2016-03-09 11:10:58 +0100752 '''
753 vms = []
754 vms.extend(list_active_vms())
755 vms.extend(list_inactive_vms())
756 return vms
757
758
759def list_active_vms():
760 '''
761 Return a list of names for active virtual machine on the minion
762
763 CLI Example:
764
765 .. code-block:: bash
766
Ales Komarekf8188332016-03-09 11:32:08 +0100767 salt '*' virtng.list_active_vms
smolaon1fb381d2016-03-09 11:10:58 +0100768 '''
769 conn = __get_conn()
770 vms = []
771 for id_ in conn.listDomainsID():
772 vms.append(conn.lookupByID(id_).name())
773 return vms
774
775
776def list_inactive_vms():
777 '''
778 Return a list of names for inactive virtual machine on the minion
779
780 CLI Example:
781
782 .. code-block:: bash
783
Ales Komarekf8188332016-03-09 11:32:08 +0100784 salt '*' virtng.list_inactive_vms
smolaon1fb381d2016-03-09 11:10:58 +0100785 '''
786 conn = __get_conn()
787 vms = []
788 for id_ in conn.listDefinedDomains():
789 vms.append(id_)
790 return vms
791
792
793def vm_info(vm_=None):
794 '''
795 Return detailed information about the vms on this hyper in a
796 list of dicts:
797
798 .. code-block:: python
799
800 [
801 'your-vm': {
802 'cpu': <int>,
803 'maxMem': <int>,
804 'mem': <int>,
805 'state': '<state>',
806 'cputime' <int>
807 },
808 ...
809 ]
810
811 If you pass a VM name in as an argument then it will return info
812 for just the named VM, otherwise it will return all VMs.
813
814 CLI Example:
815
816 .. code-block:: bash
817
Ales Komarekf8188332016-03-09 11:32:08 +0100818 salt '*' virtng.vm_info
smolaon1fb381d2016-03-09 11:10:58 +0100819 '''
820 def _info(vm_):
821 dom = _get_dom(vm_)
822 raw = dom.info()
823 return {'cpu': raw[3],
824 'cputime': int(raw[4]),
825 'disks': get_disks(vm_),
826 'graphics': get_graphics(vm_),
827 'nics': get_nics(vm_),
828 'maxMem': int(raw[1]),
829 'mem': int(raw[2]),
830 'state': VIRT_STATE_NAME_MAP.get(raw[0], 'unknown')}
831 info = {}
832 if vm_:
833 info[vm_] = _info(vm_)
834 else:
835 for vm_ in list_vms():
836 info[vm_] = _info(vm_)
837 return info
838
839
840def vm_state(vm_=None):
841 '''
842 Return list of all the vms and their state.
843
844 If you pass a VM name in as an argument then it will return info
845 for just the named VM, otherwise it will return all VMs.
846
847 CLI Example:
848
849 .. code-block:: bash
850
Ales Komarekf8188332016-03-09 11:32:08 +0100851 salt '*' virtng.vm_state <vm name>
smolaon1fb381d2016-03-09 11:10:58 +0100852 '''
853 def _info(vm_):
854 state = ''
855 dom = _get_dom(vm_)
856 raw = dom.info()
857 state = VIRT_STATE_NAME_MAP.get(raw[0], 'unknown')
858 return state
859 info = {}
860 if vm_:
861 info[vm_] = _info(vm_)
862 else:
863 for vm_ in list_vms():
864 info[vm_] = _info(vm_)
865 return info
866
867
868def node_info():
869 '''
870 Return a dict with information about this node
871
872 CLI Example:
873
874 .. code-block:: bash
875
Ales Komarekf8188332016-03-09 11:32:08 +0100876 salt '*' virtng.node_info
smolaon1fb381d2016-03-09 11:10:58 +0100877 '''
878 conn = __get_conn()
879 raw = conn.getInfo()
880 info = {'cpucores': raw[6],
881 'cpumhz': raw[3],
882 'cpumodel': str(raw[0]),
883 'cpus': raw[2],
884 'cputhreads': raw[7],
885 'numanodes': raw[4],
886 'phymemory': raw[1],
887 'sockets': raw[5]}
888 return info
889
890
891def get_nics(vm_):
892 '''
893 Return info about the network interfaces of a named vm
894
895 CLI Example:
896
897 .. code-block:: bash
898
Ales Komarekf8188332016-03-09 11:32:08 +0100899 salt '*' virtng.get_nics <vm name>
smolaon1fb381d2016-03-09 11:10:58 +0100900 '''
901 nics = {}
902 doc = minidom.parse(_StringIO(get_xml(vm_)))
903 for node in doc.getElementsByTagName('devices'):
904 i_nodes = node.getElementsByTagName('interface')
905 for i_node in i_nodes:
906 nic = {}
907 nic['type'] = i_node.getAttribute('type')
908 for v_node in i_node.getElementsByTagName('*'):
909 if v_node.tagName == 'mac':
910 nic['mac'] = v_node.getAttribute('address')
911 if v_node.tagName == 'model':
912 nic['model'] = v_node.getAttribute('type')
913 if v_node.tagName == 'target':
914 nic['target'] = v_node.getAttribute('dev')
915 # driver, source, and match can all have optional attributes
916 if re.match('(driver|source|address)', v_node.tagName):
917 temp = {}
918 for key, value in v_node.attributes.items():
919 temp[key] = value
920 nic[str(v_node.tagName)] = temp
921 # virtualport needs to be handled separately, to pick up the
922 # type attribute of the virtualport itself
923 if v_node.tagName == 'virtualport':
924 temp = {}
925 temp['type'] = v_node.getAttribute('type')
926 for key, value in v_node.attributes.items():
927 temp[key] = value
928 nic['virtualport'] = temp
929 if 'mac' not in nic:
930 continue
931 nics[nic['mac']] = nic
932 return nics
933
934
935def get_macs(vm_):
936 '''
937 Return a list off MAC addresses from the named vm
938
939 CLI Example:
940
941 .. code-block:: bash
942
Ales Komarekf8188332016-03-09 11:32:08 +0100943 salt '*' virtng.get_macs <vm name>
smolaon1fb381d2016-03-09 11:10:58 +0100944 '''
945 macs = []
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 for v_node in i_node.getElementsByTagName('mac'):
951 macs.append(v_node.getAttribute('address'))
952 return macs
953
954
955def get_graphics(vm_):
956 '''
957 Returns the information on vnc for a given vm
958
959 CLI Example:
960
961 .. code-block:: bash
962
Ales Komarekf8188332016-03-09 11:32:08 +0100963 salt '*' virtng.get_graphics <vm name>
smolaon1fb381d2016-03-09 11:10:58 +0100964 '''
965 out = {'autoport': 'None',
966 'keymap': 'None',
967 'listen': 'None',
968 'port': 'None',
969 'type': 'vnc'}
970 xml = get_xml(vm_)
971 ssock = _StringIO(xml)
972 doc = minidom.parse(ssock)
973 for node in doc.getElementsByTagName('domain'):
974 g_nodes = node.getElementsByTagName('graphics')
975 for g_node in g_nodes:
976 for key, value in g_node.attributes.items():
977 out[key] = value
978 return out
979
980
981def get_disks(vm_):
982 '''
983 Return the disks of a named vm
984
985 CLI Example:
986
987 .. code-block:: bash
988
Ales Komarekf8188332016-03-09 11:32:08 +0100989 salt '*' virtng.get_disks <vm name>
smolaon1fb381d2016-03-09 11:10:58 +0100990 '''
991 disks = {}
992 doc = minidom.parse(_StringIO(get_xml(vm_)))
993 for elem in doc.getElementsByTagName('disk'):
994 sources = elem.getElementsByTagName('source')
995 targets = elem.getElementsByTagName('target')
996 if len(sources) > 0:
997 source = sources[0]
998 else:
999 continue
1000 if len(targets) > 0:
1001 target = targets[0]
1002 else:
1003 continue
1004 if target.hasAttribute('dev'):
1005 qemu_target = ''
1006 if source.hasAttribute('file'):
1007 qemu_target = source.getAttribute('file')
1008 elif source.hasAttribute('dev'):
1009 qemu_target = source.getAttribute('dev')
1010 elif source.hasAttribute('protocol') and \
1011 source.hasAttribute('name'): # For rbd network
1012 qemu_target = '{0}:{1}'.format(
1013 source.getAttribute('protocol'),
1014 source.getAttribute('name'))
1015 if qemu_target:
1016 disks[target.getAttribute('dev')] = {
1017 'file': qemu_target}
1018 for dev in disks:
1019 try:
1020 hypervisor = __salt__['config.get']('libvirt:hypervisor', 'kvm')
1021 if hypervisor not in ['qemu', 'kvm']:
1022 break
1023
1024 output = []
1025 qemu_output = subprocess.Popen(['qemu-img', 'info',
1026 disks[dev]['file']],
1027 shell=False,
1028 stdout=subprocess.PIPE).communicate()[0]
1029 snapshots = False
1030 columns = None
1031 lines = qemu_output.strip().split('\n')
1032 for line in lines:
1033 if line.startswith('Snapshot list:'):
1034 snapshots = True
1035 continue
1036
1037 # If this is a copy-on-write image, then the backing file
1038 # represents the base image
1039 #
1040 # backing file: base.qcow2 (actual path: /var/shared/base.qcow2)
1041 elif line.startswith('backing file'):
1042 matches = re.match(r'.*\(actual path: (.*?)\)', line)
1043 if matches:
1044 output.append('backing file: {0}'.format(matches.group(1)))
1045 continue
1046
1047 elif snapshots:
1048 if line.startswith('ID'): # Do not parse table headers
1049 line = line.replace('VM SIZE', 'VMSIZE')
1050 line = line.replace('VM CLOCK', 'TIME VMCLOCK')
1051 columns = re.split(r'\s+', line)
1052 columns = [c.lower() for c in columns]
1053 output.append('snapshots:')
1054 continue
1055 fields = re.split(r'\s+', line)
1056 for i, field in enumerate(fields):
1057 sep = ' '
1058 if i == 0:
1059 sep = '-'
1060 output.append(
1061 '{0} {1}: "{2}"'.format(
1062 sep, columns[i], field
1063 )
1064 )
1065 continue
1066 output.append(line)
1067 output = '\n'.join(output)
1068 disks[dev].update(yaml.safe_load(output))
1069 except TypeError:
1070 disks[dev].update(yaml.safe_load('image: Does not exist'))
1071 return disks
1072
1073
1074def setmem(vm_, memory, config=False):
1075 '''
1076 Changes the amount of memory allocated to VM. The VM must be shutdown
1077 for this to work.
1078
1079 memory is to be specified in MB
1080 If config is True then we ask libvirt to modify the config as well
1081
1082 CLI Example:
1083
1084 .. code-block:: bash
1085
Ales Komarekf8188332016-03-09 11:32:08 +01001086 salt '*' virtng.setmem myvm 768
smolaon1fb381d2016-03-09 11:10:58 +01001087 '''
1088 if vm_state(vm_) != 'shutdown':
1089 return False
1090
1091 dom = _get_dom(vm_)
1092
1093 # libvirt has a funny bitwise system for the flags in that the flag
1094 # to affect the "current" setting is 0, which means that to set the
1095 # current setting we have to call it a second time with just 0 set
1096 flags = libvirt.VIR_DOMAIN_MEM_MAXIMUM
1097 if config:
1098 flags = flags | libvirt.VIR_DOMAIN_AFFECT_CONFIG
1099
1100 ret1 = dom.setMemoryFlags(memory * 1024, flags)
1101 ret2 = dom.setMemoryFlags(memory * 1024, libvirt.VIR_DOMAIN_AFFECT_CURRENT)
1102
1103 # return True if both calls succeeded
1104 return ret1 == ret2 == 0
1105
1106
1107def setvcpus(vm_, vcpus, config=False):
1108 '''
1109 Changes the amount of vcpus allocated to VM. The VM must be shutdown
1110 for this to work.
1111
1112 vcpus is an int representing the number to be assigned
1113 If config is True then we ask libvirt to modify the config as well
1114
1115 CLI Example:
1116
1117 .. code-block:: bash
1118
Ales Komarekf8188332016-03-09 11:32:08 +01001119 salt '*' virtng.setvcpus myvm 2
smolaon1fb381d2016-03-09 11:10:58 +01001120 '''
1121 if vm_state(vm_) != 'shutdown':
1122 return False
1123
1124 dom = _get_dom(vm_)
1125
1126 # see notes in setmem
1127 flags = libvirt.VIR_DOMAIN_VCPU_MAXIMUM
1128 if config:
1129 flags = flags | libvirt.VIR_DOMAIN_AFFECT_CONFIG
1130
1131 ret1 = dom.setVcpusFlags(vcpus, flags)
1132 ret2 = dom.setVcpusFlags(vcpus, libvirt.VIR_DOMAIN_AFFECT_CURRENT)
1133
1134 return ret1 == ret2 == 0
1135
1136
1137def freemem():
1138 '''
1139 Return an int representing the amount of memory that has not been given
1140 to virtual machines on this node
1141
1142 CLI Example:
1143
1144 .. code-block:: bash
1145
Ales Komarekf8188332016-03-09 11:32:08 +01001146 salt '*' virtng.freemem
smolaon1fb381d2016-03-09 11:10:58 +01001147 '''
1148 conn = __get_conn()
1149 mem = conn.getInfo()[1]
1150 # Take off just enough to sustain the hypervisor
1151 mem -= 256
1152 for vm_ in list_vms():
1153 dom = _get_dom(vm_)
1154 if dom.ID() > 0:
1155 mem -= dom.info()[2] / 1024
1156 return mem
1157
1158
1159def freecpu():
1160 '''
1161 Return an int representing the number of unallocated cpus on this
1162 hypervisor
1163
1164 CLI Example:
1165
1166 .. code-block:: bash
1167
Ales Komarekf8188332016-03-09 11:32:08 +01001168 salt '*' virtng.freecpu
smolaon1fb381d2016-03-09 11:10:58 +01001169 '''
1170 conn = __get_conn()
1171 cpus = conn.getInfo()[2]
1172 for vm_ in list_vms():
1173 dom = _get_dom(vm_)
1174 if dom.ID() > 0:
1175 cpus -= dom.info()[3]
1176 return cpus
1177
1178
1179def full_info():
1180 '''
1181 Return the node_info, vm_info and freemem
1182
1183 CLI Example:
1184
1185 .. code-block:: bash
1186
Ales Komarekf8188332016-03-09 11:32:08 +01001187 salt '*' virtng.full_info
smolaon1fb381d2016-03-09 11:10:58 +01001188 '''
1189 return {'freecpu': freecpu(),
1190 'freemem': freemem(),
1191 'node_info': node_info(),
1192 'vm_info': vm_info()}
1193
1194
1195def get_xml(vm_):
1196 '''
1197 Returns the XML for a given vm
1198
1199 CLI Example:
1200
1201 .. code-block:: bash
1202
Ales Komarekf8188332016-03-09 11:32:08 +01001203 salt '*' virtng.get_xml <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001204 '''
1205 dom = _get_dom(vm_)
1206 return dom.XMLDesc(0)
1207
1208
1209def get_profiles(hypervisor=None):
1210 '''
1211 Return the virt profiles for hypervisor.
1212
1213 Currently there are profiles for:
1214
1215 - nic
1216 - disk
1217
1218 CLI Example:
1219
1220 .. code-block:: bash
1221
Ales Komarekf8188332016-03-09 11:32:08 +01001222 salt '*' virtng.get_profiles
1223 salt '*' virtng.get_profiles hypervisor=esxi
smolaon1fb381d2016-03-09 11:10:58 +01001224 '''
1225 ret = {}
1226 if hypervisor:
1227 hypervisor = hypervisor
1228 else:
1229 hypervisor = __salt__['config.get']('libvirt:hypervisor', VIRT_DEFAULT_HYPER)
1230 virtconf = __salt__['config.get']('virt', {})
1231 for typ in ['disk', 'nic']:
1232 _func = getattr(sys.modules[__name__], '_{0}_profile'.format(typ))
1233 ret[typ] = {'default': _func('default', hypervisor)}
1234 if typ in virtconf:
1235 ret.setdefault(typ, {})
1236 for prf in virtconf[typ]:
1237 ret[typ][prf] = _func(prf, hypervisor)
1238 return ret
1239
1240
1241def shutdown(vm_):
1242 '''
1243 Send a soft shutdown signal to the named vm
1244
1245 CLI Example:
1246
1247 .. code-block:: bash
1248
Ales Komarekf8188332016-03-09 11:32:08 +01001249 salt '*' virtng.shutdown <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001250 '''
1251 dom = _get_dom(vm_)
1252 return dom.shutdown() == 0
1253
1254
1255def pause(vm_):
1256 '''
1257 Pause the named vm
1258
1259 CLI Example:
1260
1261 .. code-block:: bash
1262
Ales Komarekf8188332016-03-09 11:32:08 +01001263 salt '*' virtng.pause <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001264 '''
1265 dom = _get_dom(vm_)
1266 return dom.suspend() == 0
1267
1268
1269def resume(vm_):
1270 '''
1271 Resume the named vm
1272
1273 CLI Example:
1274
1275 .. code-block:: bash
1276
Ales Komarekf8188332016-03-09 11:32:08 +01001277 salt '*' virtng.resume <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001278 '''
1279 dom = _get_dom(vm_)
1280 return dom.resume() == 0
1281
1282
1283def create(vm_):
1284 '''
1285 Start a defined domain
1286
1287 CLI Example:
1288
1289 .. code-block:: bash
1290
Ales Komarekf8188332016-03-09 11:32:08 +01001291 salt '*' virtng.create <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001292 '''
1293 dom = _get_dom(vm_)
1294 return dom.create() == 0
1295
1296
1297def start(vm_):
1298 '''
1299 Alias for the obscurely named 'create' function
1300
1301 CLI Example:
1302
1303 .. code-block:: bash
1304
Ales Komarekf8188332016-03-09 11:32:08 +01001305 salt '*' virtng.start <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001306 '''
1307 return create(vm_)
1308
1309
1310def stop(vm_):
1311 '''
1312 Alias for the obscurely named 'destroy' function
1313
1314 CLI Example:
1315
1316 .. code-block:: bash
1317
Ales Komarekf8188332016-03-09 11:32:08 +01001318 salt '*' virtng.stop <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001319 '''
1320 return destroy(vm_)
1321
1322
1323def reboot(vm_):
1324 '''
1325 Reboot a domain via ACPI request
1326
1327 CLI Example:
1328
1329 .. code-block:: bash
1330
Ales Komarekf8188332016-03-09 11:32:08 +01001331 salt '*' virtng.reboot <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001332 '''
1333 dom = _get_dom(vm_)
1334
1335 # reboot has a few modes of operation, passing 0 in means the
1336 # hypervisor will pick the best method for rebooting
1337 return dom.reboot(0) == 0
1338
1339
1340def reset(vm_):
1341 '''
1342 Reset a VM by emulating the reset button on a physical machine
1343
1344 CLI Example:
1345
1346 .. code-block:: bash
1347
Ales Komarekf8188332016-03-09 11:32:08 +01001348 salt '*' virtng.reset <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001349 '''
1350 dom = _get_dom(vm_)
1351
1352 # reset takes a flag, like reboot, but it is not yet used
1353 # so we just pass in 0
1354 # see: http://libvirt.org/html/libvirt-libvirt.html#virDomainReset
1355 return dom.reset(0) == 0
1356
1357
1358def ctrl_alt_del(vm_):
1359 '''
1360 Sends CTRL+ALT+DEL to a VM
1361
1362 CLI Example:
1363
1364 .. code-block:: bash
1365
Ales Komarekf8188332016-03-09 11:32:08 +01001366 salt '*' virtng.ctrl_alt_del <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001367 '''
1368 dom = _get_dom(vm_)
1369 return dom.sendKey(0, 0, [29, 56, 111], 3, 0) == 0
1370
1371
1372def create_xml_str(xml):
1373 '''
1374 Start a domain based on the XML passed to the function
1375
1376 CLI Example:
1377
1378 .. code-block:: bash
1379
Ales Komarekf8188332016-03-09 11:32:08 +01001380 salt '*' virtng.create_xml_str <XML in string format>
smolaon1fb381d2016-03-09 11:10:58 +01001381 '''
1382 conn = __get_conn()
1383 return conn.createXML(xml, 0) is not None
1384
1385
1386def create_xml_path(path):
1387 '''
1388 Start a domain based on the XML-file path passed to the function
1389
1390 CLI Example:
1391
1392 .. code-block:: bash
1393
Ales Komarekf8188332016-03-09 11:32:08 +01001394 salt '*' virtng.create_xml_path <path to XML file on the node>
smolaon1fb381d2016-03-09 11:10:58 +01001395 '''
1396 if not os.path.isfile(path):
1397 return False
1398 return create_xml_str(salt.utils.fopen(path, 'r').read())
1399
1400
1401def define_xml_str(xml):
1402 '''
1403 Define a domain based on the XML passed to the function
1404
1405 CLI Example:
1406
1407 .. code-block:: bash
1408
Ales Komarekf8188332016-03-09 11:32:08 +01001409 salt '*' virtng.define_xml_str <XML in string format>
smolaon1fb381d2016-03-09 11:10:58 +01001410 '''
1411 conn = __get_conn()
1412 return conn.defineXML(xml) is not None
1413
1414
1415def define_xml_path(path):
1416 '''
1417 Define a domain based on the XML-file path passed to the function
1418
1419 CLI Example:
1420
1421 .. code-block:: bash
1422
Ales Komarekf8188332016-03-09 11:32:08 +01001423 salt '*' virtng.define_xml_path <path to XML file on the node>
smolaon1fb381d2016-03-09 11:10:58 +01001424
1425 '''
1426 if not os.path.isfile(path):
1427 return False
1428 return define_xml_str(salt.utils.fopen(path, 'r').read())
1429
1430
1431def define_vol_xml_str(xml):
1432 '''
1433 Define a volume based on the XML passed to the function
1434
1435 CLI Example:
1436
1437 .. code-block:: bash
1438
Ales Komarekf8188332016-03-09 11:32:08 +01001439 salt '*' virtng.define_vol_xml_str <XML in string format>
smolaon1fb381d2016-03-09 11:10:58 +01001440 '''
1441 poolname = __salt__['config.get']('libvirt:storagepool', 'default')
1442 conn = __get_conn()
1443 pool = conn.storagePoolLookupByName(str(poolname))
1444 return pool.createXML(xml, 0) is not None
1445
1446
1447def define_vol_xml_path(path):
1448 '''
1449 Define a volume based on the XML-file path passed to the function
1450
1451 CLI Example:
1452
1453 .. code-block:: bash
1454
Ales Komarekf8188332016-03-09 11:32:08 +01001455 salt '*' virtng.define_vol_xml_path <path to XML file on the node>
smolaon1fb381d2016-03-09 11:10:58 +01001456
1457 '''
1458 if not os.path.isfile(path):
1459 return False
1460 return define_vol_xml_str(salt.utils.fopen(path, 'r').read())
1461
1462
1463def migrate_non_shared(vm_, target, ssh=False):
1464 '''
1465 Attempt to execute non-shared storage "all" migration
1466
1467 CLI Example:
1468
1469 .. code-block:: bash
1470
Ales Komarekf8188332016-03-09 11:32:08 +01001471 salt '*' virtng.migrate_non_shared <vm name> <target hypervisor>
smolaon1fb381d2016-03-09 11:10:58 +01001472 '''
1473 cmd = _get_migrate_command() + ' --copy-storage-all ' + vm_\
1474 + _get_target(target, ssh)
1475
1476 return subprocess.Popen(cmd,
1477 shell=True,
1478 stdout=subprocess.PIPE).communicate()[0]
1479
1480
1481def migrate_non_shared_inc(vm_, target, ssh=False):
1482 '''
1483 Attempt to execute non-shared storage "all" migration
1484
1485 CLI Example:
1486
1487 .. code-block:: bash
1488
Ales Komarekf8188332016-03-09 11:32:08 +01001489 salt '*' virtng.migrate_non_shared_inc <vm name> <target hypervisor>
smolaon1fb381d2016-03-09 11:10:58 +01001490 '''
1491 cmd = _get_migrate_command() + ' --copy-storage-inc ' + vm_\
1492 + _get_target(target, ssh)
1493
1494 return subprocess.Popen(cmd,
1495 shell=True,
1496 stdout=subprocess.PIPE).communicate()[0]
1497
1498
1499def migrate(vm_, target, ssh=False):
1500 '''
1501 Shared storage migration
1502
1503 CLI Example:
1504
1505 .. code-block:: bash
1506
Ales Komarekf8188332016-03-09 11:32:08 +01001507 salt '*' virtng.migrate <vm name> <target hypervisor>
smolaon1fb381d2016-03-09 11:10:58 +01001508 '''
1509 cmd = _get_migrate_command() + ' ' + vm_\
1510 + _get_target(target, ssh)
1511
1512 return subprocess.Popen(cmd,
1513 shell=True,
1514 stdout=subprocess.PIPE).communicate()[0]
1515
1516
1517def seed_non_shared_migrate(disks, force=False):
1518 '''
1519 Non shared migration requires that the disks be present on the migration
1520 destination, pass the disks information via this function, to the
1521 migration destination before executing the migration.
1522
1523 CLI Example:
1524
1525 .. code-block:: bash
1526
Ales Komarekf8188332016-03-09 11:32:08 +01001527 salt '*' virtng.seed_non_shared_migrate <disks>
smolaon1fb381d2016-03-09 11:10:58 +01001528 '''
1529 for _, data in disks.items():
1530 fn_ = data['file']
1531 form = data['file format']
1532 size = data['virtual size'].split()[1][1:]
1533 if os.path.isfile(fn_) and not force:
1534 # the target exists, check to see if it is compatible
1535 pre = yaml.safe_load(subprocess.Popen('qemu-img info arch',
1536 shell=True,
1537 stdout=subprocess.PIPE).communicate()[0])
1538 if pre['file format'] != data['file format']\
1539 and pre['virtual size'] != data['virtual size']:
1540 return False
1541 if not os.path.isdir(os.path.dirname(fn_)):
1542 os.makedirs(os.path.dirname(fn_))
1543 if os.path.isfile(fn_):
1544 os.remove(fn_)
1545 cmd = 'qemu-img create -f ' + form + ' ' + fn_ + ' ' + size
1546 subprocess.call(cmd, shell=True)
1547 creds = _libvirt_creds()
1548 cmd = 'chown ' + creds['user'] + ':' + creds['group'] + ' ' + fn_
1549 subprocess.call(cmd, shell=True)
1550 return True
1551
1552
1553def set_autostart(vm_, state='on'):
1554 '''
1555 Set the autostart flag on a VM so that the VM will start with the host
1556 system on reboot.
1557
1558 CLI Example:
1559
1560 .. code-block:: bash
1561
1562 salt "*" virt.set_autostart <vm name> <on | off>
1563 '''
1564
1565 dom = _get_dom(vm_)
1566
1567 if state == 'on':
1568 return dom.setAutostart(1) == 0
1569
1570 elif state == 'off':
1571 return dom.setAutostart(0) == 0
1572
1573 else:
1574 # return False if state is set to something other then on or off
1575 return False
1576
1577
1578def destroy(vm_):
1579 '''
1580 Hard power down the virtual machine, this is equivalent to pulling the
1581 power
1582
1583 CLI Example:
1584
1585 .. code-block:: bash
1586
Ales Komarekf8188332016-03-09 11:32:08 +01001587 salt '*' virtng.destroy <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001588 '''
1589 dom = _get_dom(vm_)
1590 return dom.destroy() == 0
1591
1592
1593def undefine(vm_):
1594 '''
1595 Remove a defined vm, this does not purge the virtual machine image, and
1596 this only works if the vm is powered down
1597
1598 CLI Example:
1599
1600 .. code-block:: bash
1601
Ales Komarekf8188332016-03-09 11:32:08 +01001602 salt '*' virtng.undefine <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001603 '''
1604 dom = _get_dom(vm_)
1605 return dom.undefine() == 0
1606
1607
1608def purge(vm_, dirs=False):
1609 '''
1610 Recursively destroy and delete a virtual machine, pass True for dir's to
1611 also delete the directories containing the virtual machine disk images -
1612 USE WITH EXTREME CAUTION!
1613
1614 CLI Example:
1615
1616 .. code-block:: bash
1617
Ales Komarekf8188332016-03-09 11:32:08 +01001618 salt '*' virtng.purge <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001619 '''
1620 disks = get_disks(vm_)
1621 try:
1622 if not destroy(vm_):
1623 return False
1624 except libvirt.libvirtError:
1625 # This is thrown if the machine is already shut down
1626 pass
1627 directories = set()
1628 for disk in disks:
1629 os.remove(disks[disk]['file'])
1630 directories.add(os.path.dirname(disks[disk]['file']))
1631 if dirs:
1632 for dir_ in directories:
1633 shutil.rmtree(dir_)
1634 undefine(vm_)
1635 return True
1636
1637
1638def virt_type():
1639 '''
1640 Returns the virtual machine type as a string
1641
1642 CLI Example:
1643
1644 .. code-block:: bash
1645
Ales Komarekf8188332016-03-09 11:32:08 +01001646 salt '*' virtng.virt_type
smolaon1fb381d2016-03-09 11:10:58 +01001647 '''
1648 return __grains__['virtual']
1649
1650
1651def is_kvm_hyper():
1652 '''
1653 Returns a bool whether or not this node is a KVM hypervisor
1654
1655 CLI Example:
1656
1657 .. code-block:: bash
1658
Ales Komarekf8188332016-03-09 11:32:08 +01001659 salt '*' virtng.is_kvm_hyper
smolaon1fb381d2016-03-09 11:10:58 +01001660 '''
1661 try:
1662 if 'kvm_' not in salt.utils.fopen('/proc/modules').read():
1663 return False
1664 except IOError:
1665 # No /proc/modules? Are we on Windows? Or Solaris?
1666 return False
Oleh Hryhorov7c5dfd32018-01-19 15:43:44 +02001667 return 'libvirtd' in __salt__['cmd.shell'](__grains__['ps'])
smolaon1fb381d2016-03-09 11:10:58 +01001668
1669
1670def is_xen_hyper():
1671 '''
1672 Returns a bool whether or not this node is a XEN hypervisor
1673
1674 CLI Example:
1675
1676 .. code-block:: bash
1677
Ales Komarekf8188332016-03-09 11:32:08 +01001678 salt '*' virtng.is_xen_hyper
smolaon1fb381d2016-03-09 11:10:58 +01001679 '''
1680 try:
1681 if __grains__['virtual_subtype'] != 'Xen Dom0':
1682 return False
1683 except KeyError:
1684 # virtual_subtype isn't set everywhere.
1685 return False
1686 try:
1687 if 'xen_' not in salt.utils.fopen('/proc/modules').read():
1688 return False
1689 except IOError:
1690 # No /proc/modules? Are we on Windows? Or Solaris?
1691 return False
Oleh Hryhorov7c5dfd32018-01-19 15:43:44 +02001692 return 'libvirtd' in __salt__['cmd.shell'](__grains__['ps'])
smolaon1fb381d2016-03-09 11:10:58 +01001693
1694
1695def is_hyper():
1696 '''
1697 Returns a bool whether or not this node is a hypervisor of any kind
1698
1699 CLI Example:
1700
1701 .. code-block:: bash
1702
Ales Komarekf8188332016-03-09 11:32:08 +01001703 salt '*' virtng.is_hyper
smolaon1fb381d2016-03-09 11:10:58 +01001704 '''
1705 try:
1706 import libvirt # pylint: disable=import-error
1707 except ImportError:
1708 # not a usable hypervisor without libvirt module
1709 return False
1710 return is_xen_hyper() or is_kvm_hyper()
1711
1712
1713def vm_cputime(vm_=None):
1714 '''
1715 Return cputime used by the vms on this hyper in a
1716 list of dicts:
1717
1718 .. code-block:: python
1719
1720 [
1721 'your-vm': {
1722 'cputime' <int>
1723 'cputime_percent' <int>
1724 },
1725 ...
1726 ]
1727
1728 If you pass a VM name in as an argument then it will return info
1729 for just the named VM, otherwise it will return all VMs.
1730
1731 CLI Example:
1732
1733 .. code-block:: bash
1734
Ales Komarekf8188332016-03-09 11:32:08 +01001735 salt '*' virtng.vm_cputime
smolaon1fb381d2016-03-09 11:10:58 +01001736 '''
1737 host_cpus = __get_conn().getInfo()[2]
1738
1739 def _info(vm_):
1740 dom = _get_dom(vm_)
1741 raw = dom.info()
1742 vcpus = int(raw[3])
1743 cputime = int(raw[4])
1744 cputime_percent = 0
1745 if cputime:
1746 # Divide by vcpus to always return a number between 0 and 100
1747 cputime_percent = (1.0e-7 * cputime / host_cpus) / vcpus
1748 return {
1749 'cputime': int(raw[4]),
1750 'cputime_percent': int('{0:.0f}'.format(cputime_percent))
1751 }
1752 info = {}
1753 if vm_:
1754 info[vm_] = _info(vm_)
1755 else:
1756 for vm_ in list_vms():
1757 info[vm_] = _info(vm_)
1758 return info
1759
1760
1761def vm_netstats(vm_=None):
1762 '''
1763 Return combined network counters used by the vms on this hyper in a
1764 list of dicts:
1765
1766 .. code-block:: python
1767
1768 [
1769 'your-vm': {
1770 'rx_bytes' : 0,
1771 'rx_packets' : 0,
1772 'rx_errs' : 0,
1773 'rx_drop' : 0,
1774 'tx_bytes' : 0,
1775 'tx_packets' : 0,
1776 'tx_errs' : 0,
1777 'tx_drop' : 0
1778 },
1779 ...
1780 ]
1781
1782 If you pass a VM name in as an argument then it will return info
1783 for just the named VM, otherwise it will return all VMs.
1784
1785 CLI Example:
1786
1787 .. code-block:: bash
1788
Ales Komarekf8188332016-03-09 11:32:08 +01001789 salt '*' virtng.vm_netstats
smolaon1fb381d2016-03-09 11:10:58 +01001790 '''
1791 def _info(vm_):
1792 dom = _get_dom(vm_)
1793 nics = get_nics(vm_)
1794 ret = {
1795 'rx_bytes': 0,
1796 'rx_packets': 0,
1797 'rx_errs': 0,
1798 'rx_drop': 0,
1799 'tx_bytes': 0,
1800 'tx_packets': 0,
1801 'tx_errs': 0,
1802 'tx_drop': 0
1803 }
1804 for attrs in six.itervalues(nics):
1805 if 'target' in attrs:
1806 dev = attrs['target']
1807 stats = dom.interfaceStats(dev)
1808 ret['rx_bytes'] += stats[0]
1809 ret['rx_packets'] += stats[1]
1810 ret['rx_errs'] += stats[2]
1811 ret['rx_drop'] += stats[3]
1812 ret['tx_bytes'] += stats[4]
1813 ret['tx_packets'] += stats[5]
1814 ret['tx_errs'] += stats[6]
1815 ret['tx_drop'] += stats[7]
1816
1817 return ret
1818 info = {}
1819 if vm_:
1820 info[vm_] = _info(vm_)
1821 else:
1822 for vm_ in list_vms():
1823 info[vm_] = _info(vm_)
1824 return info
1825
1826
1827def vm_diskstats(vm_=None):
1828 '''
1829 Return disk usage counters used by the vms on this hyper in a
1830 list of dicts:
1831
1832 .. code-block:: python
1833
1834 [
1835 'your-vm': {
1836 'rd_req' : 0,
1837 'rd_bytes' : 0,
1838 'wr_req' : 0,
1839 'wr_bytes' : 0,
1840 'errs' : 0
1841 },
1842 ...
1843 ]
1844
1845 If you pass a VM name in as an argument then it will return info
1846 for just the named VM, otherwise it will return all VMs.
1847
1848 CLI Example:
1849
1850 .. code-block:: bash
1851
Ales Komarekf8188332016-03-09 11:32:08 +01001852 salt '*' virtng.vm_blockstats
smolaon1fb381d2016-03-09 11:10:58 +01001853 '''
1854 def get_disk_devs(vm_):
1855 doc = minidom.parse(_StringIO(get_xml(vm_)))
1856 disks = []
1857 for elem in doc.getElementsByTagName('disk'):
1858 targets = elem.getElementsByTagName('target')
1859 target = targets[0]
1860 disks.append(target.getAttribute('dev'))
1861 return disks
1862
1863 def _info(vm_):
1864 dom = _get_dom(vm_)
1865 # Do not use get_disks, since it uses qemu-img and is very slow
1866 # and unsuitable for any sort of real time statistics
1867 disks = get_disk_devs(vm_)
1868 ret = {'rd_req': 0,
1869 'rd_bytes': 0,
1870 'wr_req': 0,
1871 'wr_bytes': 0,
1872 'errs': 0
1873 }
1874 for disk in disks:
1875 stats = dom.blockStats(disk)
1876 ret['rd_req'] += stats[0]
1877 ret['rd_bytes'] += stats[1]
1878 ret['wr_req'] += stats[2]
1879 ret['wr_bytes'] += stats[3]
1880 ret['errs'] += stats[4]
1881
1882 return ret
1883 info = {}
1884 if vm_:
1885 info[vm_] = _info(vm_)
1886 else:
1887 # Can not run function blockStats on inactive VMs
1888 for vm_ in list_active_vms():
1889 info[vm_] = _info(vm_)
1890 return info