blob: ce095082cdf50b16bc35185e36167620ac3aeda4 [file] [log] [blame]
smolaon1fb381d2016-03-09 11:10:58 +01001# -*- coding: utf-8 -*-
2'''
3Work with virtual machines managed by libvirt
4
5:depends: libvirt Python module
6'''
7# Special Thanks to Michael Dehann, many of the concepts, and a few structures
8# of his in the virt func module have been used
9
10# Import python libs
11from __future__ import absolute_import
Dennis Dmitrievf5dba8c2017-10-10 19:05:20 +030012import copy
smolaon1fb381d2016-03-09 11:10:58 +010013import os
14import re
15import sys
16import shutil
17import subprocess
18import string # pylint: disable=deprecated-module
19import logging
20
21# Import third party libs
22import yaml
23import jinja2
24import jinja2.exceptions
25import salt.ext.six as six
26from salt.ext.six.moves import StringIO as _StringIO # pylint: disable=import-error
27from xml.dom import minidom
28try:
29 import libvirt # pylint: disable=import-error
30 HAS_ALL_IMPORTS = True
31except ImportError:
32 HAS_ALL_IMPORTS = False
33
34# Import salt libs
35import salt.utils
36import salt.utils.files
37import salt.utils.templates
38import salt.utils.validate.net
39from salt.exceptions import CommandExecutionError, SaltInvocationError
40
41log = logging.getLogger(__name__)
42
43# Set up template environment
44JINJA = jinja2.Environment(
45 loader=jinja2.FileSystemLoader(
46 os.path.join(salt.utils.templates.TEMPLATE_DIRNAME, 'virt')
47 )
48)
49
50VIRT_STATE_NAME_MAP = {0: 'running',
51 1: 'running',
52 2: 'running',
53 3: 'paused',
54 4: 'shutdown',
55 5: 'shutdown',
56 6: 'crashed'}
57
58VIRT_DEFAULT_HYPER = 'kvm'
59
60
61def __virtual__():
62 if not HAS_ALL_IMPORTS:
63 return False
Ales Komarekf8188332016-03-09 11:32:08 +010064 return 'virtng'
smolaon1fb381d2016-03-09 11:10:58 +010065
66
67def __get_conn():
68 '''
69 Detects what type of dom this node is and attempts to connect to the
70 correct hypervisor via libvirt.
71 '''
72 # This has only been tested on kvm and xen, it needs to be expanded to
73 # support all vm layers supported by libvirt
74
75 def __esxi_uri():
76 '''
77 Connect to an ESXi host with a configuration like so:
78
79 .. code-block:: yaml
80
81 libvirt:
82 hypervisor: esxi
83 connection: esx01
84
85 The connection setting can either be an explicit libvirt URI,
86 or a libvirt URI alias as in this example. No, it cannot be
87 just a hostname.
88
89
90 Example libvirt `/etc/libvirt/libvirt.conf`:
91
92 .. code-block::
93
94 uri_aliases = [
95 "esx01=esx://10.1.1.101/?no_verify=1&auto_answer=1",
96 "esx02=esx://10.1.1.102/?no_verify=1&auto_answer=1",
97 ]
98
99 Reference:
100
101 - http://libvirt.org/drvesx.html#uriformat
102 - http://libvirt.org/uri.html#URI_config
103 '''
104 connection = __salt__['config.get']('libvirt:connection', 'esx')
105 return connection
106
107 def __esxi_auth():
108 '''
109 We rely on that the credentials is provided to libvirt through
110 its built in mechanisms.
111
112 Example libvirt `/etc/libvirt/auth.conf`:
113
114 .. code-block::
115
116 [credentials-myvirt]
117 username=user
118 password=secret
119
120 [auth-esx-10.1.1.101]
121 credentials=myvirt
122
123 [auth-esx-10.1.1.102]
124 credentials=myvirt
125
126 Reference:
127
128 - http://libvirt.org/auth.html#Auth_client_config
129 '''
130 return [[libvirt.VIR_CRED_EXTERNAL], lambda: 0, None]
131
132 if 'virt.connect' in __opts__:
133 conn_str = __opts__['virt.connect']
134 else:
135 conn_str = 'qemu:///system'
136
137 conn_func = {
138 'esxi': [libvirt.openAuth, [__esxi_uri(),
139 __esxi_auth(),
140 0]],
141 'qemu': [libvirt.open, [conn_str]],
142 }
143
144 hypervisor = __salt__['config.get']('libvirt:hypervisor', 'qemu')
145
146 try:
147 conn = conn_func[hypervisor][0](*conn_func[hypervisor][1])
148 except Exception:
149 raise CommandExecutionError(
150 'Sorry, {0} failed to open a connection to the hypervisor '
151 'software at {1}'.format(
152 __grains__['fqdn'],
153 conn_func[hypervisor][1][0]
154 )
155 )
156 return conn
157
158
159def _get_dom(vm_):
160 '''
161 Return a domain object for the named vm
162 '''
163 conn = __get_conn()
164 if vm_ not in list_vms():
165 raise CommandExecutionError('The specified vm is not present')
166 return conn.lookupByName(vm_)
167
168
169def _libvirt_creds():
170 '''
171 Returns the user and group that the disk images should be owned by
172 '''
173 g_cmd = 'grep ^\\s*group /etc/libvirt/qemu.conf'
174 u_cmd = 'grep ^\\s*user /etc/libvirt/qemu.conf'
175 try:
176 group = subprocess.Popen(g_cmd,
177 shell=True,
178 stdout=subprocess.PIPE).communicate()[0].split('"')[1]
179 except IndexError:
180 group = 'root'
181 try:
182 user = subprocess.Popen(u_cmd,
183 shell=True,
184 stdout=subprocess.PIPE).communicate()[0].split('"')[1]
185 except IndexError:
186 user = 'root'
187 return {'user': user, 'group': group}
188
189
190def _get_migrate_command():
191 '''
192 Returns the command shared by the different migration types
193 '''
194 if __salt__['config.option']('virt.tunnel'):
195 return ('virsh migrate --p2p --tunnelled --live --persistent '
196 '--undefinesource ')
197 return 'virsh migrate --live --persistent --undefinesource '
198
199
200def _get_target(target, ssh):
201 proto = 'qemu'
202 if ssh:
203 proto += '+ssh'
204 return ' {0}://{1}/{2}'.format(proto, target, 'system')
205
206
207def _gen_xml(name,
208 cpu,
209 mem,
210 diskp,
211 nicp,
212 hypervisor,
213 **kwargs):
214 '''
215 Generate the XML string to define a libvirt vm
216 '''
217 hypervisor = 'vmware' if hypervisor == 'esxi' else hypervisor
218 mem = mem * 1024 # MB
219 context = {
220 'hypervisor': hypervisor,
221 'name': name,
222 'cpu': str(cpu),
223 'mem': str(mem),
224 }
225 if hypervisor in ['qemu', 'kvm']:
226 context['controller_model'] = False
227 elif hypervisor in ['esxi', 'vmware']:
228 # TODO: make bus and model parameterized, this works for 64-bit Linux
229 context['controller_model'] = 'lsilogic'
230
231 if 'boot_dev' in kwargs:
232 context['boot_dev'] = []
233 for dev in kwargs['boot_dev'].split():
234 context['boot_dev'].append(dev)
235 else:
236 context['boot_dev'] = ['hd']
237
238 if 'serial_type' in kwargs:
239 context['serial_type'] = kwargs['serial_type']
240 if 'serial_type' in context and context['serial_type'] == 'tcp':
241 if 'telnet_port' in kwargs:
242 context['telnet_port'] = kwargs['telnet_port']
243 else:
244 context['telnet_port'] = 23023 # FIXME: use random unused port
245 if 'serial_type' in context:
246 if 'console' in kwargs:
247 context['console'] = kwargs['console']
248 else:
249 context['console'] = True
250
251 context['disks'] = {}
252 for i, disk in enumerate(diskp):
253 for disk_name, args in disk.items():
254 context['disks'][disk_name] = {}
255 fn_ = '{0}.{1}'.format(disk_name, args['format'])
256 context['disks'][disk_name]['file_name'] = fn_
257 context['disks'][disk_name]['source_file'] = os.path.join(args['pool'],
258 name,
259 fn_)
260 if hypervisor in ['qemu', 'kvm']:
261 context['disks'][disk_name]['target_dev'] = 'vd{0}'.format(string.ascii_lowercase[i])
262 context['disks'][disk_name]['address'] = False
263 context['disks'][disk_name]['driver'] = True
264 elif hypervisor in ['esxi', 'vmware']:
265 context['disks'][disk_name]['target_dev'] = 'sd{0}'.format(string.ascii_lowercase[i])
266 context['disks'][disk_name]['address'] = True
267 context['disks'][disk_name]['driver'] = False
268 context['disks'][disk_name]['disk_bus'] = args['model']
269 context['disks'][disk_name]['type'] = args['format']
270 context['disks'][disk_name]['index'] = str(i)
271
272 context['nics'] = nicp
273
274 fn_ = 'libvirt_domain.jinja'
275 try:
276 template = JINJA.get_template(fn_)
277 except jinja2.exceptions.TemplateNotFound:
278 log.error('Could not load template {0}'.format(fn_))
279 return ''
280
281 return template.render(**context)
282
283
284def _gen_vol_xml(vmname,
285 diskname,
286 size,
287 hypervisor,
288 **kwargs):
289 '''
290 Generate the XML string to define a libvirt storage volume
291 '''
292 size = int(size) * 1024 # MB
293 disk_info = _get_image_info(hypervisor, vmname, **kwargs)
294 context = {
295 'name': vmname,
296 'filename': '{0}.{1}'.format(diskname, disk_info['disktype']),
297 'volname': diskname,
298 'disktype': disk_info['disktype'],
299 'size': str(size),
300 'pool': disk_info['pool'],
301 }
302 fn_ = 'libvirt_volume.jinja'
303 try:
304 template = JINJA.get_template(fn_)
305 except jinja2.exceptions.TemplateNotFound:
306 log.error('Could not load template {0}'.format(fn_))
307 return ''
308 return template.render(**context)
309
310
311def _qemu_image_info(path):
312 '''
313 Detect information for the image at path
314 '''
315 ret = {}
Oleh Hryhorov7c5dfd32018-01-19 15:43:44 +0200316 out = __salt__['cmd.shell']('qemu-img info {0}'.format(path))
smolaon1fb381d2016-03-09 11:10:58 +0100317
318 match_map = {'size': r'virtual size: \w+ \((\d+) byte[s]?\)',
319 'format': r'file format: (\w+)'}
320
321 for info, search in match_map.items():
322 try:
323 ret[info] = re.search(search, out).group(1)
324 except AttributeError:
325 continue
326 return ret
327
328
329# TODO: this function is deprecated, should be replaced with
330# _qemu_image_info()
331def _image_type(vda):
332 '''
333 Detect what driver needs to be used for the given image
334 '''
Oleh Hryhorov7c5dfd32018-01-19 15:43:44 +0200335 out = __salt__['cmd.shell']('qemu-img info {0}'.format(vda))
smolaon1fb381d2016-03-09 11:10:58 +0100336 if 'file format: qcow2' in out:
337 return 'qcow2'
338 else:
339 return 'raw'
340
341
342# TODO: this function is deprecated, should be merged and replaced
343# with _disk_profile()
344def _get_image_info(hypervisor, name, **kwargs):
345 '''
346 Determine disk image info, such as filename, image format and
347 storage pool, based on which hypervisor is used
348 '''
349 ret = {}
350 if hypervisor in ['esxi', 'vmware']:
351 ret['disktype'] = 'vmdk'
352 ret['filename'] = '{0}{1}'.format(name, '.vmdk')
353 ret['pool'] = '[{0}] '.format(kwargs.get('pool', '0'))
354 elif hypervisor in ['kvm', 'qemu']:
355 ret['disktype'] = 'qcow2'
356 ret['filename'] = '{0}{1}'.format(name, '.qcow2')
Mateusz Los4c7cd2d2018-01-09 11:46:07 +0100357 if 'img_dest' in kwargs:
358 ret['pool'] = kwargs['img_dest']
359 else:
360 ret['pool'] = __salt__['config.option']('virt.images')
smolaon1fb381d2016-03-09 11:10:58 +0100361 return ret
362
363
364def _disk_profile(profile, hypervisor, **kwargs):
365 '''
366 Gather the disk profile from the config or apply the default based
367 on the active hypervisor
368
369 This is the ``default`` profile for KVM/QEMU, which can be
370 overridden in the configuration:
371
372 .. code-block:: yaml
373
374 virt:
375 disk:
376 default:
377 - system:
378 size: 8192
379 format: qcow2
380 model: virtio
381
Dennis Dmitrieva57463c2017-03-24 17:05:22 +0200382 Example profile for KVM/QEMU with two disks, first is created
383 from specified image, the second is empty:
384
385 .. code-block:: yaml
386
387 virt:
388 disk:
389 two_disks:
390 - system:
391 size: 8192
392 format: qcow2
393 model: virtio
394 image: http://path/to/image.qcow2
395 - lvm:
396 size: 32768
397 format: qcow2
398 model: virtio
399
smolaon1fb381d2016-03-09 11:10:58 +0100400 The ``format`` and ``model`` parameters are optional, and will
401 default to whatever is best suitable for the active hypervisor.
402 '''
403 default = [
404 {'system':
405 {'size': '8192'}
406 }
407 ]
408 if hypervisor in ['esxi', 'vmware']:
409 overlay = {'format': 'vmdk',
410 'model': 'scsi',
411 'pool': '[{0}] '.format(kwargs.get('pool', '0'))
412 }
413 elif hypervisor in ['qemu', 'kvm']:
Mateusz Los4c7cd2d2018-01-09 11:46:07 +0100414 if 'img_dest' in kwargs:
415 pool = kwargs['img_dest']
416 else:
417 pool = __salt__['config.option']('virt.images')
418 overlay = {'format': 'qcow2', 'model': 'virtio', 'pool': pool}
smolaon1fb381d2016-03-09 11:10:58 +0100419 else:
420 overlay = {}
421
Dennis Dmitrievf5dba8c2017-10-10 19:05:20 +0300422 disklist = copy.deepcopy(__salt__['config.get']('virt:disk', {}).get(profile, default))
smolaon1fb381d2016-03-09 11:10:58 +0100423 for key, val in overlay.items():
424 for i, disks in enumerate(disklist):
425 for disk in disks:
426 if key not in disks[disk]:
427 disklist[i][disk][key] = val
428 return disklist
429
430
431def _nic_profile(profile_name, hypervisor, **kwargs):
432
smolaon1fb381d2016-03-09 11:10:58 +0100433 def append_dict_profile_to_interface_list(profile_dict):
434 for interface_name, attributes in profile_dict.items():
435 attributes['name'] = interface_name
436 interfaces.append(attributes)
437
smolaon1fb381d2016-03-09 11:10:58 +0100438 def _normalize_net_types(attributes):
439 '''
440 Guess which style of definition:
441
442 bridge: br0
443
444 or
445
446 network: net0
447
448 or
449
450 type: network
451 source: net0
452 '''
453 for type_ in ['bridge', 'network']:
454 if type_ in attributes:
455 attributes['type'] = type_
456 # we want to discard the original key
457 attributes['source'] = attributes.pop(type_)
458
459 attributes['type'] = attributes.get('type', None)
460 attributes['source'] = attributes.get('source', None)
Dzmitry Stremkouskib8acf1f2018-06-28 12:56:23 +0200461 attributes['virtualport'] = attributes.get('virtualport', None)
smolaon1fb381d2016-03-09 11:10:58 +0100462
463 def _apply_default_overlay(attributes):
464 for key, value in overlays[hypervisor].items():
465 if key not in attributes or not attributes[key]:
466 attributes[key] = value
467
468 def _assign_mac(attributes):
469 dmac = '{0}_mac'.format(attributes['name'])
470 if dmac in kwargs:
471 dmac = kwargs[dmac]
472 if salt.utils.validate.net.mac(dmac):
473 attributes['mac'] = dmac
474 else:
475 msg = 'Malformed MAC address: {0}'.format(dmac)
476 raise CommandExecutionError(msg)
477 else:
478 attributes['mac'] = salt.utils.gen_mac()
479
Dzmitry Stremkouskib8acf1f2018-06-28 12:56:23 +0200480
481 default = [{'eth0': {}}]
482 vmware_overlay = {'type': 'bridge', 'source': 'DEFAULT', 'model': 'e1000'}
483 kvm_overlay = {'type': 'bridge', 'source': 'br0', 'model': 'virtio'}
484 overlays = {
485 'kvm': kvm_overlay,
486 'qemu': kvm_overlay,
487 'esxi': vmware_overlay,
488 'vmware': vmware_overlay,
489 }
490
491 # support old location
492 config_data = __salt__['config.option']('virt.nic', {}).get(
493 profile_name, None
494 )
495
496 if config_data is None:
497 config_data = __salt__['config.get']('virt:nic', {}).get(
498 profile_name, default
499 )
500
501 interfaces = []
502
503 if isinstance(config_data, dict):
504 append_dict_profile_to_interface_list(config_data)
505
506 elif isinstance(config_data, list):
507 for interface in config_data:
508 if isinstance(interface, dict):
509 if len(interface) == 1:
510 append_dict_profile_to_interface_list(interface)
511 else:
512 interfaces.append(interface)
513
smolaon1fb381d2016-03-09 11:10:58 +0100514 for interface in interfaces:
515 _normalize_net_types(interface)
516 _assign_mac(interface)
517 if hypervisor in overlays:
518 _apply_default_overlay(interface)
519
520 return interfaces
521
522
523def init(name,
524 cpu,
525 mem,
526 image=None,
527 nic='default',
528 hypervisor=VIRT_DEFAULT_HYPER,
529 start=True, # pylint: disable=redefined-outer-name
530 disk='default',
531 saltenv='base',
azvyagintseva4e802d2018-05-04 20:16:02 +0300532 rng=None,
smolaon1fb381d2016-03-09 11:10:58 +0100533 **kwargs):
534 '''
535 Initialize a new vm
536
537 CLI Example:
538
539 .. code-block:: bash
540
541 salt 'hypervisor' virt.init vm_name 4 512 salt://path/to/image.raw
542 salt 'hypervisor' virt.init vm_name 4 512 nic=profile disk=profile
543 '''
Dennis Dmitrieva57463c2017-03-24 17:05:22 +0200544
azvyagintseva4e802d2018-05-04 20:16:02 +0300545 rng = rng or {'backend':'/dev/urandom'}
smolaon1fb381d2016-03-09 11:10:58 +0100546 hypervisor = __salt__['config.get']('libvirt:hypervisor', hypervisor)
547
548 nicp = _nic_profile(nic, hypervisor, **kwargs)
549
Dennis Dmitrieva57463c2017-03-24 17:05:22 +0200550 diskp = _disk_profile(disk, hypervisor, **kwargs)
551
552 if image:
553 # Backward compatibility: if 'image' is specified in the VMs arguments
554 # instead of a disk arguments. In this case, 'image' will be assigned
555 # to the first disk for the VM.
smolaon1fb381d2016-03-09 11:10:58 +0100556 disk_name = next(diskp[0].iterkeys())
Dennis Dmitrieva57463c2017-03-24 17:05:22 +0200557 if not diskp[0][disk_name].get('image', None):
558 diskp[0][disk_name]['image'] = image
smolaon1fb381d2016-03-09 11:10:58 +0100559
Dennis Dmitrieva57463c2017-03-24 17:05:22 +0200560 # Create multiple disks, empty or from specified images.
561 for disk in diskp:
562 log.debug("Creating disk for VM [ {0} ]: {1}".format(name, disk))
smolaon1fb381d2016-03-09 11:10:58 +0100563
Dennis Dmitrieva57463c2017-03-24 17:05:22 +0200564 for disk_name, args in disk.items():
smolaon1fb381d2016-03-09 11:10:58 +0100565
Dennis Dmitrieva57463c2017-03-24 17:05:22 +0200566 if hypervisor in ['esxi', 'vmware']:
567 if 'image' in args:
568 # TODO: we should be copying the image file onto the ESX host
569 raise SaltInvocationError('virt.init does not support image '
570 'template template in conjunction '
571 'with esxi hypervisor')
572 else:
573 # assume libvirt manages disks for us
smolaon1fb381d2016-03-09 11:10:58 +0100574 xml = _gen_vol_xml(name,
575 disk_name,
576 args['size'],
Mateusz Los4c7cd2d2018-01-09 11:46:07 +0100577 hypervisor,
578 **kwargs)
smolaon1fb381d2016-03-09 11:10:58 +0100579 define_vol_xml_str(xml)
580
Dennis Dmitrieva57463c2017-03-24 17:05:22 +0200581 elif hypervisor in ['qemu', 'kvm']:
582
583 disk_type = args['format']
584 disk_file_name = '{0}.{1}'.format(disk_name, disk_type)
585 # disk size TCP cloud
586 disk_size = args['size']
587
Mateusz Los4c7cd2d2018-01-09 11:46:07 +0100588 if 'img_dest' in kwargs:
589 img_dir = kwargs['img_dest']
590 else:
591 img_dir = __salt__['config.option']('virt.images')
Dennis Dmitrieva57463c2017-03-24 17:05:22 +0200592 img_dest = os.path.join(
593 img_dir,
594 name,
595 disk_file_name
596 )
597 img_dir = os.path.dirname(img_dest)
598 if not os.path.isdir(img_dir):
599 os.makedirs(img_dir)
600
601 if 'image' in args:
602 # Create disk from specified image
603 sfn = __salt__['cp.cache_file'](args['image'], saltenv)
604 try:
605 salt.utils.files.copyfile(sfn, img_dest)
606 mask = os.umask(0)
607 os.umask(mask)
608 # Apply umask and remove exec bit
609
610 # Resizing image TCP cloud
611 cmd = 'qemu-img resize ' + img_dest + ' ' + str(disk_size) + 'M'
612 subprocess.call(cmd, shell=True)
613
614 mode = (0o0777 ^ mask) & 0o0666
615 os.chmod(img_dest, mode)
616
617 except (IOError, OSError) as e:
618 raise CommandExecutionError('problem while copying image. {0} - {1}'.format(args['image'], e))
619
620 if kwargs.get('seed'):
621 install = kwargs.get('install', True)
622 seed_cmd = kwargs.get('seed_cmd', 'seedng.apply')
623
624 __salt__[seed_cmd](img_dest,
625 id_=name,
626 config=kwargs.get('config'),
627 install=install)
628 else:
629 # Create empty disk
630 try:
631 mask = os.umask(0)
632 os.umask(mask)
633 # Apply umask and remove exec bit
634
635 # Create empty image
636 cmd = 'qemu-img create -f ' + disk_type + ' ' + img_dest + ' ' + str(disk_size) + 'M'
637 subprocess.call(cmd, shell=True)
638
639 mode = (0o0777 ^ mask) & 0o0666
640 os.chmod(img_dest, mode)
641
642 except (IOError, OSError) as e:
643 raise CommandExecutionError('problem while creating volume {0} - {1}'.format(img_dest, e))
644
645 else:
646 # Unknown hypervisor
647 raise SaltInvocationError('Unsupported hypervisor when handling disk image: {0}'
648 .format(hypervisor))
649
smolaon1fb381d2016-03-09 11:10:58 +0100650 xml = _gen_xml(name, cpu, mem, diskp, nicp, hypervisor, **kwargs)
Dzmitry Stremkouski7ee23402018-04-10 00:43:48 +0200651
652 # TODO: Remove this code and refactor module, when salt-common would have updated libvirt_domain.jinja template
Dzmitry Stremkouskib8acf1f2018-06-28 12:56:23 +0200653 for _nic in nicp:
654 if _nic['virtualport']:
655 xml_doc = minidom.parseString(xml)
656 interfaces = xml_doc.getElementsByTagName("domain")[0].getElementsByTagName("devices")[0].getElementsByTagName("interface")
657 for interface in interfaces:
658 if interface.getElementsByTagName('mac')[0].getAttribute('address').lower() == _nic['mac'].lower():
659 vport_xml = xml_doc.createElement("virtualport")
660 vport_xml.setAttribute("type", _nic['virtualport']['type'])
661 interface.appendChild(vport_xml)
662 xml = xml_doc.toxml()
663
664 # TODO: Remove this code and refactor module, when salt-common would have updated libvirt_domain.jinja template
Dzmitry Stremkouski7ee23402018-04-10 00:43:48 +0200665 if rng:
666 rng_model = rng.get('model', 'random')
667 rng_backend = rng.get('backend', '/dev/urandom')
668 xml_doc = minidom.parseString(xml)
669 rng_xml = xml_doc.createElement("rng")
670 rng_xml.setAttribute("model", "virtio")
671 backend = xml_doc.createElement("backend")
672 backend.setAttribute("model", rng_model)
673 backend.appendChild(xml_doc.createTextNode(rng_backend))
674 rng_xml.appendChild(backend)
675 if 'rate' in rng:
676 rng_rate_period = rng['rate'].get('period', '2000')
677 rng_rate_bytes = rng['rate'].get('bytes', '1234')
678 rate = xml_doc.createElement("rate")
679 rate.setAttribute("period", rng_rate_period)
680 rate.setAttribute("bytes", rng_rate_bytes)
681 rng_xml.appendChild(rate)
682 xml_doc.getElementsByTagName("domain")[0].getElementsByTagName("devices")[0].appendChild(rng_xml)
683 xml = xml_doc.toxml()
684
smolaon1fb381d2016-03-09 11:10:58 +0100685 define_xml_str(xml)
686
smolaon1fb381d2016-03-09 11:10:58 +0100687 if start:
688 create(name)
689
690 return True
691
692
693def list_vms():
694 '''
695 Return a list of virtual machine names on the minion
696
697 CLI Example:
698
699 .. code-block:: bash
700
Ales Komarekf8188332016-03-09 11:32:08 +0100701 salt '*' virtng.list_vms
smolaon1fb381d2016-03-09 11:10:58 +0100702 '''
703 vms = []
704 vms.extend(list_active_vms())
705 vms.extend(list_inactive_vms())
706 return vms
707
708
709def list_active_vms():
710 '''
711 Return a list of names for active virtual machine on the minion
712
713 CLI Example:
714
715 .. code-block:: bash
716
Ales Komarekf8188332016-03-09 11:32:08 +0100717 salt '*' virtng.list_active_vms
smolaon1fb381d2016-03-09 11:10:58 +0100718 '''
719 conn = __get_conn()
720 vms = []
721 for id_ in conn.listDomainsID():
722 vms.append(conn.lookupByID(id_).name())
723 return vms
724
725
726def list_inactive_vms():
727 '''
728 Return a list of names for inactive virtual machine on the minion
729
730 CLI Example:
731
732 .. code-block:: bash
733
Ales Komarekf8188332016-03-09 11:32:08 +0100734 salt '*' virtng.list_inactive_vms
smolaon1fb381d2016-03-09 11:10:58 +0100735 '''
736 conn = __get_conn()
737 vms = []
738 for id_ in conn.listDefinedDomains():
739 vms.append(id_)
740 return vms
741
742
743def vm_info(vm_=None):
744 '''
745 Return detailed information about the vms on this hyper in a
746 list of dicts:
747
748 .. code-block:: python
749
750 [
751 'your-vm': {
752 'cpu': <int>,
753 'maxMem': <int>,
754 'mem': <int>,
755 'state': '<state>',
756 'cputime' <int>
757 },
758 ...
759 ]
760
761 If you pass a VM name in as an argument then it will return info
762 for just the named VM, otherwise it will return all VMs.
763
764 CLI Example:
765
766 .. code-block:: bash
767
Ales Komarekf8188332016-03-09 11:32:08 +0100768 salt '*' virtng.vm_info
smolaon1fb381d2016-03-09 11:10:58 +0100769 '''
770 def _info(vm_):
771 dom = _get_dom(vm_)
772 raw = dom.info()
773 return {'cpu': raw[3],
774 'cputime': int(raw[4]),
775 'disks': get_disks(vm_),
776 'graphics': get_graphics(vm_),
777 'nics': get_nics(vm_),
778 'maxMem': int(raw[1]),
779 'mem': int(raw[2]),
780 'state': VIRT_STATE_NAME_MAP.get(raw[0], 'unknown')}
781 info = {}
782 if vm_:
783 info[vm_] = _info(vm_)
784 else:
785 for vm_ in list_vms():
786 info[vm_] = _info(vm_)
787 return info
788
789
790def vm_state(vm_=None):
791 '''
792 Return list of all the vms and their state.
793
794 If you pass a VM name in as an argument then it will return info
795 for just the named VM, otherwise it will return all VMs.
796
797 CLI Example:
798
799 .. code-block:: bash
800
Ales Komarekf8188332016-03-09 11:32:08 +0100801 salt '*' virtng.vm_state <vm name>
smolaon1fb381d2016-03-09 11:10:58 +0100802 '''
803 def _info(vm_):
804 state = ''
805 dom = _get_dom(vm_)
806 raw = dom.info()
807 state = VIRT_STATE_NAME_MAP.get(raw[0], 'unknown')
808 return state
809 info = {}
810 if vm_:
811 info[vm_] = _info(vm_)
812 else:
813 for vm_ in list_vms():
814 info[vm_] = _info(vm_)
815 return info
816
817
818def node_info():
819 '''
820 Return a dict with information about this node
821
822 CLI Example:
823
824 .. code-block:: bash
825
Ales Komarekf8188332016-03-09 11:32:08 +0100826 salt '*' virtng.node_info
smolaon1fb381d2016-03-09 11:10:58 +0100827 '''
828 conn = __get_conn()
829 raw = conn.getInfo()
830 info = {'cpucores': raw[6],
831 'cpumhz': raw[3],
832 'cpumodel': str(raw[0]),
833 'cpus': raw[2],
834 'cputhreads': raw[7],
835 'numanodes': raw[4],
836 'phymemory': raw[1],
837 'sockets': raw[5]}
838 return info
839
840
841def get_nics(vm_):
842 '''
843 Return info about the network interfaces of a named vm
844
845 CLI Example:
846
847 .. code-block:: bash
848
Ales Komarekf8188332016-03-09 11:32:08 +0100849 salt '*' virtng.get_nics <vm name>
smolaon1fb381d2016-03-09 11:10:58 +0100850 '''
851 nics = {}
852 doc = minidom.parse(_StringIO(get_xml(vm_)))
853 for node in doc.getElementsByTagName('devices'):
854 i_nodes = node.getElementsByTagName('interface')
855 for i_node in i_nodes:
856 nic = {}
857 nic['type'] = i_node.getAttribute('type')
858 for v_node in i_node.getElementsByTagName('*'):
859 if v_node.tagName == 'mac':
860 nic['mac'] = v_node.getAttribute('address')
861 if v_node.tagName == 'model':
862 nic['model'] = v_node.getAttribute('type')
863 if v_node.tagName == 'target':
864 nic['target'] = v_node.getAttribute('dev')
865 # driver, source, and match can all have optional attributes
866 if re.match('(driver|source|address)', v_node.tagName):
867 temp = {}
868 for key, value in v_node.attributes.items():
869 temp[key] = value
870 nic[str(v_node.tagName)] = temp
871 # virtualport needs to be handled separately, to pick up the
872 # type attribute of the virtualport itself
873 if v_node.tagName == 'virtualport':
874 temp = {}
875 temp['type'] = v_node.getAttribute('type')
876 for key, value in v_node.attributes.items():
877 temp[key] = value
878 nic['virtualport'] = temp
879 if 'mac' not in nic:
880 continue
881 nics[nic['mac']] = nic
882 return nics
883
884
885def get_macs(vm_):
886 '''
887 Return a list off MAC addresses from the named vm
888
889 CLI Example:
890
891 .. code-block:: bash
892
Ales Komarekf8188332016-03-09 11:32:08 +0100893 salt '*' virtng.get_macs <vm name>
smolaon1fb381d2016-03-09 11:10:58 +0100894 '''
895 macs = []
896 doc = minidom.parse(_StringIO(get_xml(vm_)))
897 for node in doc.getElementsByTagName('devices'):
898 i_nodes = node.getElementsByTagName('interface')
899 for i_node in i_nodes:
900 for v_node in i_node.getElementsByTagName('mac'):
901 macs.append(v_node.getAttribute('address'))
902 return macs
903
904
905def get_graphics(vm_):
906 '''
907 Returns the information on vnc for a given vm
908
909 CLI Example:
910
911 .. code-block:: bash
912
Ales Komarekf8188332016-03-09 11:32:08 +0100913 salt '*' virtng.get_graphics <vm name>
smolaon1fb381d2016-03-09 11:10:58 +0100914 '''
915 out = {'autoport': 'None',
916 'keymap': 'None',
917 'listen': 'None',
918 'port': 'None',
919 'type': 'vnc'}
920 xml = get_xml(vm_)
921 ssock = _StringIO(xml)
922 doc = minidom.parse(ssock)
923 for node in doc.getElementsByTagName('domain'):
924 g_nodes = node.getElementsByTagName('graphics')
925 for g_node in g_nodes:
926 for key, value in g_node.attributes.items():
927 out[key] = value
928 return out
929
930
931def get_disks(vm_):
932 '''
933 Return the disks of a named vm
934
935 CLI Example:
936
937 .. code-block:: bash
938
Ales Komarekf8188332016-03-09 11:32:08 +0100939 salt '*' virtng.get_disks <vm name>
smolaon1fb381d2016-03-09 11:10:58 +0100940 '''
941 disks = {}
942 doc = minidom.parse(_StringIO(get_xml(vm_)))
943 for elem in doc.getElementsByTagName('disk'):
944 sources = elem.getElementsByTagName('source')
945 targets = elem.getElementsByTagName('target')
946 if len(sources) > 0:
947 source = sources[0]
948 else:
949 continue
950 if len(targets) > 0:
951 target = targets[0]
952 else:
953 continue
954 if target.hasAttribute('dev'):
955 qemu_target = ''
956 if source.hasAttribute('file'):
957 qemu_target = source.getAttribute('file')
958 elif source.hasAttribute('dev'):
959 qemu_target = source.getAttribute('dev')
960 elif source.hasAttribute('protocol') and \
961 source.hasAttribute('name'): # For rbd network
962 qemu_target = '{0}:{1}'.format(
963 source.getAttribute('protocol'),
964 source.getAttribute('name'))
965 if qemu_target:
966 disks[target.getAttribute('dev')] = {
967 'file': qemu_target}
968 for dev in disks:
969 try:
970 hypervisor = __salt__['config.get']('libvirt:hypervisor', 'kvm')
971 if hypervisor not in ['qemu', 'kvm']:
972 break
973
974 output = []
975 qemu_output = subprocess.Popen(['qemu-img', 'info',
976 disks[dev]['file']],
977 shell=False,
978 stdout=subprocess.PIPE).communicate()[0]
979 snapshots = False
980 columns = None
981 lines = qemu_output.strip().split('\n')
982 for line in lines:
983 if line.startswith('Snapshot list:'):
984 snapshots = True
985 continue
986
987 # If this is a copy-on-write image, then the backing file
988 # represents the base image
989 #
990 # backing file: base.qcow2 (actual path: /var/shared/base.qcow2)
991 elif line.startswith('backing file'):
992 matches = re.match(r'.*\(actual path: (.*?)\)', line)
993 if matches:
994 output.append('backing file: {0}'.format(matches.group(1)))
995 continue
996
997 elif snapshots:
998 if line.startswith('ID'): # Do not parse table headers
999 line = line.replace('VM SIZE', 'VMSIZE')
1000 line = line.replace('VM CLOCK', 'TIME VMCLOCK')
1001 columns = re.split(r'\s+', line)
1002 columns = [c.lower() for c in columns]
1003 output.append('snapshots:')
1004 continue
1005 fields = re.split(r'\s+', line)
1006 for i, field in enumerate(fields):
1007 sep = ' '
1008 if i == 0:
1009 sep = '-'
1010 output.append(
1011 '{0} {1}: "{2}"'.format(
1012 sep, columns[i], field
1013 )
1014 )
1015 continue
1016 output.append(line)
1017 output = '\n'.join(output)
1018 disks[dev].update(yaml.safe_load(output))
1019 except TypeError:
1020 disks[dev].update(yaml.safe_load('image: Does not exist'))
1021 return disks
1022
1023
1024def setmem(vm_, memory, config=False):
1025 '''
1026 Changes the amount of memory allocated to VM. The VM must be shutdown
1027 for this to work.
1028
1029 memory is to be specified in MB
1030 If config is True then we ask libvirt to modify the config as well
1031
1032 CLI Example:
1033
1034 .. code-block:: bash
1035
Ales Komarekf8188332016-03-09 11:32:08 +01001036 salt '*' virtng.setmem myvm 768
smolaon1fb381d2016-03-09 11:10:58 +01001037 '''
1038 if vm_state(vm_) != 'shutdown':
1039 return False
1040
1041 dom = _get_dom(vm_)
1042
1043 # libvirt has a funny bitwise system for the flags in that the flag
1044 # to affect the "current" setting is 0, which means that to set the
1045 # current setting we have to call it a second time with just 0 set
1046 flags = libvirt.VIR_DOMAIN_MEM_MAXIMUM
1047 if config:
1048 flags = flags | libvirt.VIR_DOMAIN_AFFECT_CONFIG
1049
1050 ret1 = dom.setMemoryFlags(memory * 1024, flags)
1051 ret2 = dom.setMemoryFlags(memory * 1024, libvirt.VIR_DOMAIN_AFFECT_CURRENT)
1052
1053 # return True if both calls succeeded
1054 return ret1 == ret2 == 0
1055
1056
1057def setvcpus(vm_, vcpus, config=False):
1058 '''
1059 Changes the amount of vcpus allocated to VM. The VM must be shutdown
1060 for this to work.
1061
1062 vcpus is an int representing the number to be assigned
1063 If config is True then we ask libvirt to modify the config as well
1064
1065 CLI Example:
1066
1067 .. code-block:: bash
1068
Ales Komarekf8188332016-03-09 11:32:08 +01001069 salt '*' virtng.setvcpus myvm 2
smolaon1fb381d2016-03-09 11:10:58 +01001070 '''
1071 if vm_state(vm_) != 'shutdown':
1072 return False
1073
1074 dom = _get_dom(vm_)
1075
1076 # see notes in setmem
1077 flags = libvirt.VIR_DOMAIN_VCPU_MAXIMUM
1078 if config:
1079 flags = flags | libvirt.VIR_DOMAIN_AFFECT_CONFIG
1080
1081 ret1 = dom.setVcpusFlags(vcpus, flags)
1082 ret2 = dom.setVcpusFlags(vcpus, libvirt.VIR_DOMAIN_AFFECT_CURRENT)
1083
1084 return ret1 == ret2 == 0
1085
1086
1087def freemem():
1088 '''
1089 Return an int representing the amount of memory that has not been given
1090 to virtual machines on this node
1091
1092 CLI Example:
1093
1094 .. code-block:: bash
1095
Ales Komarekf8188332016-03-09 11:32:08 +01001096 salt '*' virtng.freemem
smolaon1fb381d2016-03-09 11:10:58 +01001097 '''
1098 conn = __get_conn()
1099 mem = conn.getInfo()[1]
1100 # Take off just enough to sustain the hypervisor
1101 mem -= 256
1102 for vm_ in list_vms():
1103 dom = _get_dom(vm_)
1104 if dom.ID() > 0:
1105 mem -= dom.info()[2] / 1024
1106 return mem
1107
1108
1109def freecpu():
1110 '''
1111 Return an int representing the number of unallocated cpus on this
1112 hypervisor
1113
1114 CLI Example:
1115
1116 .. code-block:: bash
1117
Ales Komarekf8188332016-03-09 11:32:08 +01001118 salt '*' virtng.freecpu
smolaon1fb381d2016-03-09 11:10:58 +01001119 '''
1120 conn = __get_conn()
1121 cpus = conn.getInfo()[2]
1122 for vm_ in list_vms():
1123 dom = _get_dom(vm_)
1124 if dom.ID() > 0:
1125 cpus -= dom.info()[3]
1126 return cpus
1127
1128
1129def full_info():
1130 '''
1131 Return the node_info, vm_info and freemem
1132
1133 CLI Example:
1134
1135 .. code-block:: bash
1136
Ales Komarekf8188332016-03-09 11:32:08 +01001137 salt '*' virtng.full_info
smolaon1fb381d2016-03-09 11:10:58 +01001138 '''
1139 return {'freecpu': freecpu(),
1140 'freemem': freemem(),
1141 'node_info': node_info(),
1142 'vm_info': vm_info()}
1143
1144
1145def get_xml(vm_):
1146 '''
1147 Returns the XML for a given vm
1148
1149 CLI Example:
1150
1151 .. code-block:: bash
1152
Ales Komarekf8188332016-03-09 11:32:08 +01001153 salt '*' virtng.get_xml <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001154 '''
1155 dom = _get_dom(vm_)
1156 return dom.XMLDesc(0)
1157
1158
1159def get_profiles(hypervisor=None):
1160 '''
1161 Return the virt profiles for hypervisor.
1162
1163 Currently there are profiles for:
1164
1165 - nic
1166 - disk
1167
1168 CLI Example:
1169
1170 .. code-block:: bash
1171
Ales Komarekf8188332016-03-09 11:32:08 +01001172 salt '*' virtng.get_profiles
1173 salt '*' virtng.get_profiles hypervisor=esxi
smolaon1fb381d2016-03-09 11:10:58 +01001174 '''
1175 ret = {}
1176 if hypervisor:
1177 hypervisor = hypervisor
1178 else:
1179 hypervisor = __salt__['config.get']('libvirt:hypervisor', VIRT_DEFAULT_HYPER)
1180 virtconf = __salt__['config.get']('virt', {})
1181 for typ in ['disk', 'nic']:
1182 _func = getattr(sys.modules[__name__], '_{0}_profile'.format(typ))
1183 ret[typ] = {'default': _func('default', hypervisor)}
1184 if typ in virtconf:
1185 ret.setdefault(typ, {})
1186 for prf in virtconf[typ]:
1187 ret[typ][prf] = _func(prf, hypervisor)
1188 return ret
1189
1190
1191def shutdown(vm_):
1192 '''
1193 Send a soft shutdown signal to the named vm
1194
1195 CLI Example:
1196
1197 .. code-block:: bash
1198
Ales Komarekf8188332016-03-09 11:32:08 +01001199 salt '*' virtng.shutdown <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001200 '''
1201 dom = _get_dom(vm_)
1202 return dom.shutdown() == 0
1203
1204
1205def pause(vm_):
1206 '''
1207 Pause the named vm
1208
1209 CLI Example:
1210
1211 .. code-block:: bash
1212
Ales Komarekf8188332016-03-09 11:32:08 +01001213 salt '*' virtng.pause <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001214 '''
1215 dom = _get_dom(vm_)
1216 return dom.suspend() == 0
1217
1218
1219def resume(vm_):
1220 '''
1221 Resume the named vm
1222
1223 CLI Example:
1224
1225 .. code-block:: bash
1226
Ales Komarekf8188332016-03-09 11:32:08 +01001227 salt '*' virtng.resume <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001228 '''
1229 dom = _get_dom(vm_)
1230 return dom.resume() == 0
1231
1232
1233def create(vm_):
1234 '''
1235 Start a defined domain
1236
1237 CLI Example:
1238
1239 .. code-block:: bash
1240
Ales Komarekf8188332016-03-09 11:32:08 +01001241 salt '*' virtng.create <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001242 '''
1243 dom = _get_dom(vm_)
1244 return dom.create() == 0
1245
1246
1247def start(vm_):
1248 '''
1249 Alias for the obscurely named 'create' function
1250
1251 CLI Example:
1252
1253 .. code-block:: bash
1254
Ales Komarekf8188332016-03-09 11:32:08 +01001255 salt '*' virtng.start <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001256 '''
1257 return create(vm_)
1258
1259
1260def stop(vm_):
1261 '''
1262 Alias for the obscurely named 'destroy' function
1263
1264 CLI Example:
1265
1266 .. code-block:: bash
1267
Ales Komarekf8188332016-03-09 11:32:08 +01001268 salt '*' virtng.stop <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001269 '''
1270 return destroy(vm_)
1271
1272
1273def reboot(vm_):
1274 '''
1275 Reboot a domain via ACPI request
1276
1277 CLI Example:
1278
1279 .. code-block:: bash
1280
Ales Komarekf8188332016-03-09 11:32:08 +01001281 salt '*' virtng.reboot <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001282 '''
1283 dom = _get_dom(vm_)
1284
1285 # reboot has a few modes of operation, passing 0 in means the
1286 # hypervisor will pick the best method for rebooting
1287 return dom.reboot(0) == 0
1288
1289
1290def reset(vm_):
1291 '''
1292 Reset a VM by emulating the reset button on a physical machine
1293
1294 CLI Example:
1295
1296 .. code-block:: bash
1297
Ales Komarekf8188332016-03-09 11:32:08 +01001298 salt '*' virtng.reset <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001299 '''
1300 dom = _get_dom(vm_)
1301
1302 # reset takes a flag, like reboot, but it is not yet used
1303 # so we just pass in 0
1304 # see: http://libvirt.org/html/libvirt-libvirt.html#virDomainReset
1305 return dom.reset(0) == 0
1306
1307
1308def ctrl_alt_del(vm_):
1309 '''
1310 Sends CTRL+ALT+DEL to a VM
1311
1312 CLI Example:
1313
1314 .. code-block:: bash
1315
Ales Komarekf8188332016-03-09 11:32:08 +01001316 salt '*' virtng.ctrl_alt_del <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001317 '''
1318 dom = _get_dom(vm_)
1319 return dom.sendKey(0, 0, [29, 56, 111], 3, 0) == 0
1320
1321
1322def create_xml_str(xml):
1323 '''
1324 Start a domain based on the XML passed to the function
1325
1326 CLI Example:
1327
1328 .. code-block:: bash
1329
Ales Komarekf8188332016-03-09 11:32:08 +01001330 salt '*' virtng.create_xml_str <XML in string format>
smolaon1fb381d2016-03-09 11:10:58 +01001331 '''
1332 conn = __get_conn()
1333 return conn.createXML(xml, 0) is not None
1334
1335
1336def create_xml_path(path):
1337 '''
1338 Start a domain based on the XML-file path passed to the function
1339
1340 CLI Example:
1341
1342 .. code-block:: bash
1343
Ales Komarekf8188332016-03-09 11:32:08 +01001344 salt '*' virtng.create_xml_path <path to XML file on the node>
smolaon1fb381d2016-03-09 11:10:58 +01001345 '''
1346 if not os.path.isfile(path):
1347 return False
1348 return create_xml_str(salt.utils.fopen(path, 'r').read())
1349
1350
1351def define_xml_str(xml):
1352 '''
1353 Define a domain based on the XML passed to the function
1354
1355 CLI Example:
1356
1357 .. code-block:: bash
1358
Ales Komarekf8188332016-03-09 11:32:08 +01001359 salt '*' virtng.define_xml_str <XML in string format>
smolaon1fb381d2016-03-09 11:10:58 +01001360 '''
1361 conn = __get_conn()
1362 return conn.defineXML(xml) is not None
1363
1364
1365def define_xml_path(path):
1366 '''
1367 Define a domain based on the XML-file path passed to the function
1368
1369 CLI Example:
1370
1371 .. code-block:: bash
1372
Ales Komarekf8188332016-03-09 11:32:08 +01001373 salt '*' virtng.define_xml_path <path to XML file on the node>
smolaon1fb381d2016-03-09 11:10:58 +01001374
1375 '''
1376 if not os.path.isfile(path):
1377 return False
1378 return define_xml_str(salt.utils.fopen(path, 'r').read())
1379
1380
1381def define_vol_xml_str(xml):
1382 '''
1383 Define a volume based on the XML passed to the function
1384
1385 CLI Example:
1386
1387 .. code-block:: bash
1388
Ales Komarekf8188332016-03-09 11:32:08 +01001389 salt '*' virtng.define_vol_xml_str <XML in string format>
smolaon1fb381d2016-03-09 11:10:58 +01001390 '''
1391 poolname = __salt__['config.get']('libvirt:storagepool', 'default')
1392 conn = __get_conn()
1393 pool = conn.storagePoolLookupByName(str(poolname))
1394 return pool.createXML(xml, 0) is not None
1395
1396
1397def define_vol_xml_path(path):
1398 '''
1399 Define a volume based on the XML-file path passed to the function
1400
1401 CLI Example:
1402
1403 .. code-block:: bash
1404
Ales Komarekf8188332016-03-09 11:32:08 +01001405 salt '*' virtng.define_vol_xml_path <path to XML file on the node>
smolaon1fb381d2016-03-09 11:10:58 +01001406
1407 '''
1408 if not os.path.isfile(path):
1409 return False
1410 return define_vol_xml_str(salt.utils.fopen(path, 'r').read())
1411
1412
1413def migrate_non_shared(vm_, target, ssh=False):
1414 '''
1415 Attempt to execute non-shared storage "all" migration
1416
1417 CLI Example:
1418
1419 .. code-block:: bash
1420
Ales Komarekf8188332016-03-09 11:32:08 +01001421 salt '*' virtng.migrate_non_shared <vm name> <target hypervisor>
smolaon1fb381d2016-03-09 11:10:58 +01001422 '''
1423 cmd = _get_migrate_command() + ' --copy-storage-all ' + vm_\
1424 + _get_target(target, ssh)
1425
1426 return subprocess.Popen(cmd,
1427 shell=True,
1428 stdout=subprocess.PIPE).communicate()[0]
1429
1430
1431def migrate_non_shared_inc(vm_, target, ssh=False):
1432 '''
1433 Attempt to execute non-shared storage "all" migration
1434
1435 CLI Example:
1436
1437 .. code-block:: bash
1438
Ales Komarekf8188332016-03-09 11:32:08 +01001439 salt '*' virtng.migrate_non_shared_inc <vm name> <target hypervisor>
smolaon1fb381d2016-03-09 11:10:58 +01001440 '''
1441 cmd = _get_migrate_command() + ' --copy-storage-inc ' + vm_\
1442 + _get_target(target, ssh)
1443
1444 return subprocess.Popen(cmd,
1445 shell=True,
1446 stdout=subprocess.PIPE).communicate()[0]
1447
1448
1449def migrate(vm_, target, ssh=False):
1450 '''
1451 Shared storage migration
1452
1453 CLI Example:
1454
1455 .. code-block:: bash
1456
Ales Komarekf8188332016-03-09 11:32:08 +01001457 salt '*' virtng.migrate <vm name> <target hypervisor>
smolaon1fb381d2016-03-09 11:10:58 +01001458 '''
1459 cmd = _get_migrate_command() + ' ' + vm_\
1460 + _get_target(target, ssh)
1461
1462 return subprocess.Popen(cmd,
1463 shell=True,
1464 stdout=subprocess.PIPE).communicate()[0]
1465
1466
1467def seed_non_shared_migrate(disks, force=False):
1468 '''
1469 Non shared migration requires that the disks be present on the migration
1470 destination, pass the disks information via this function, to the
1471 migration destination before executing the migration.
1472
1473 CLI Example:
1474
1475 .. code-block:: bash
1476
Ales Komarekf8188332016-03-09 11:32:08 +01001477 salt '*' virtng.seed_non_shared_migrate <disks>
smolaon1fb381d2016-03-09 11:10:58 +01001478 '''
1479 for _, data in disks.items():
1480 fn_ = data['file']
1481 form = data['file format']
1482 size = data['virtual size'].split()[1][1:]
1483 if os.path.isfile(fn_) and not force:
1484 # the target exists, check to see if it is compatible
1485 pre = yaml.safe_load(subprocess.Popen('qemu-img info arch',
1486 shell=True,
1487 stdout=subprocess.PIPE).communicate()[0])
1488 if pre['file format'] != data['file format']\
1489 and pre['virtual size'] != data['virtual size']:
1490 return False
1491 if not os.path.isdir(os.path.dirname(fn_)):
1492 os.makedirs(os.path.dirname(fn_))
1493 if os.path.isfile(fn_):
1494 os.remove(fn_)
1495 cmd = 'qemu-img create -f ' + form + ' ' + fn_ + ' ' + size
1496 subprocess.call(cmd, shell=True)
1497 creds = _libvirt_creds()
1498 cmd = 'chown ' + creds['user'] + ':' + creds['group'] + ' ' + fn_
1499 subprocess.call(cmd, shell=True)
1500 return True
1501
1502
1503def set_autostart(vm_, state='on'):
1504 '''
1505 Set the autostart flag on a VM so that the VM will start with the host
1506 system on reboot.
1507
1508 CLI Example:
1509
1510 .. code-block:: bash
1511
1512 salt "*" virt.set_autostart <vm name> <on | off>
1513 '''
1514
1515 dom = _get_dom(vm_)
1516
1517 if state == 'on':
1518 return dom.setAutostart(1) == 0
1519
1520 elif state == 'off':
1521 return dom.setAutostart(0) == 0
1522
1523 else:
1524 # return False if state is set to something other then on or off
1525 return False
1526
1527
1528def destroy(vm_):
1529 '''
1530 Hard power down the virtual machine, this is equivalent to pulling the
1531 power
1532
1533 CLI Example:
1534
1535 .. code-block:: bash
1536
Ales Komarekf8188332016-03-09 11:32:08 +01001537 salt '*' virtng.destroy <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001538 '''
1539 dom = _get_dom(vm_)
1540 return dom.destroy() == 0
1541
1542
1543def undefine(vm_):
1544 '''
1545 Remove a defined vm, this does not purge the virtual machine image, and
1546 this only works if the vm is powered down
1547
1548 CLI Example:
1549
1550 .. code-block:: bash
1551
Ales Komarekf8188332016-03-09 11:32:08 +01001552 salt '*' virtng.undefine <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001553 '''
1554 dom = _get_dom(vm_)
1555 return dom.undefine() == 0
1556
1557
1558def purge(vm_, dirs=False):
1559 '''
1560 Recursively destroy and delete a virtual machine, pass True for dir's to
1561 also delete the directories containing the virtual machine disk images -
1562 USE WITH EXTREME CAUTION!
1563
1564 CLI Example:
1565
1566 .. code-block:: bash
1567
Ales Komarekf8188332016-03-09 11:32:08 +01001568 salt '*' virtng.purge <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001569 '''
1570 disks = get_disks(vm_)
1571 try:
1572 if not destroy(vm_):
1573 return False
1574 except libvirt.libvirtError:
1575 # This is thrown if the machine is already shut down
1576 pass
1577 directories = set()
1578 for disk in disks:
1579 os.remove(disks[disk]['file'])
1580 directories.add(os.path.dirname(disks[disk]['file']))
1581 if dirs:
1582 for dir_ in directories:
1583 shutil.rmtree(dir_)
1584 undefine(vm_)
1585 return True
1586
1587
1588def virt_type():
1589 '''
1590 Returns the virtual machine type as a string
1591
1592 CLI Example:
1593
1594 .. code-block:: bash
1595
Ales Komarekf8188332016-03-09 11:32:08 +01001596 salt '*' virtng.virt_type
smolaon1fb381d2016-03-09 11:10:58 +01001597 '''
1598 return __grains__['virtual']
1599
1600
1601def is_kvm_hyper():
1602 '''
1603 Returns a bool whether or not this node is a KVM hypervisor
1604
1605 CLI Example:
1606
1607 .. code-block:: bash
1608
Ales Komarekf8188332016-03-09 11:32:08 +01001609 salt '*' virtng.is_kvm_hyper
smolaon1fb381d2016-03-09 11:10:58 +01001610 '''
1611 try:
1612 if 'kvm_' not in salt.utils.fopen('/proc/modules').read():
1613 return False
1614 except IOError:
1615 # No /proc/modules? Are we on Windows? Or Solaris?
1616 return False
Oleh Hryhorov7c5dfd32018-01-19 15:43:44 +02001617 return 'libvirtd' in __salt__['cmd.shell'](__grains__['ps'])
smolaon1fb381d2016-03-09 11:10:58 +01001618
1619
1620def is_xen_hyper():
1621 '''
1622 Returns a bool whether or not this node is a XEN hypervisor
1623
1624 CLI Example:
1625
1626 .. code-block:: bash
1627
Ales Komarekf8188332016-03-09 11:32:08 +01001628 salt '*' virtng.is_xen_hyper
smolaon1fb381d2016-03-09 11:10:58 +01001629 '''
1630 try:
1631 if __grains__['virtual_subtype'] != 'Xen Dom0':
1632 return False
1633 except KeyError:
1634 # virtual_subtype isn't set everywhere.
1635 return False
1636 try:
1637 if 'xen_' not in salt.utils.fopen('/proc/modules').read():
1638 return False
1639 except IOError:
1640 # No /proc/modules? Are we on Windows? Or Solaris?
1641 return False
Oleh Hryhorov7c5dfd32018-01-19 15:43:44 +02001642 return 'libvirtd' in __salt__['cmd.shell'](__grains__['ps'])
smolaon1fb381d2016-03-09 11:10:58 +01001643
1644
1645def is_hyper():
1646 '''
1647 Returns a bool whether or not this node is a hypervisor of any kind
1648
1649 CLI Example:
1650
1651 .. code-block:: bash
1652
Ales Komarekf8188332016-03-09 11:32:08 +01001653 salt '*' virtng.is_hyper
smolaon1fb381d2016-03-09 11:10:58 +01001654 '''
1655 try:
1656 import libvirt # pylint: disable=import-error
1657 except ImportError:
1658 # not a usable hypervisor without libvirt module
1659 return False
1660 return is_xen_hyper() or is_kvm_hyper()
1661
1662
1663def vm_cputime(vm_=None):
1664 '''
1665 Return cputime used by the vms on this hyper in a
1666 list of dicts:
1667
1668 .. code-block:: python
1669
1670 [
1671 'your-vm': {
1672 'cputime' <int>
1673 'cputime_percent' <int>
1674 },
1675 ...
1676 ]
1677
1678 If you pass a VM name in as an argument then it will return info
1679 for just the named VM, otherwise it will return all VMs.
1680
1681 CLI Example:
1682
1683 .. code-block:: bash
1684
Ales Komarekf8188332016-03-09 11:32:08 +01001685 salt '*' virtng.vm_cputime
smolaon1fb381d2016-03-09 11:10:58 +01001686 '''
1687 host_cpus = __get_conn().getInfo()[2]
1688
1689 def _info(vm_):
1690 dom = _get_dom(vm_)
1691 raw = dom.info()
1692 vcpus = int(raw[3])
1693 cputime = int(raw[4])
1694 cputime_percent = 0
1695 if cputime:
1696 # Divide by vcpus to always return a number between 0 and 100
1697 cputime_percent = (1.0e-7 * cputime / host_cpus) / vcpus
1698 return {
1699 'cputime': int(raw[4]),
1700 'cputime_percent': int('{0:.0f}'.format(cputime_percent))
1701 }
1702 info = {}
1703 if vm_:
1704 info[vm_] = _info(vm_)
1705 else:
1706 for vm_ in list_vms():
1707 info[vm_] = _info(vm_)
1708 return info
1709
1710
1711def vm_netstats(vm_=None):
1712 '''
1713 Return combined network counters used by the vms on this hyper in a
1714 list of dicts:
1715
1716 .. code-block:: python
1717
1718 [
1719 'your-vm': {
1720 'rx_bytes' : 0,
1721 'rx_packets' : 0,
1722 'rx_errs' : 0,
1723 'rx_drop' : 0,
1724 'tx_bytes' : 0,
1725 'tx_packets' : 0,
1726 'tx_errs' : 0,
1727 'tx_drop' : 0
1728 },
1729 ...
1730 ]
1731
1732 If you pass a VM name in as an argument then it will return info
1733 for just the named VM, otherwise it will return all VMs.
1734
1735 CLI Example:
1736
1737 .. code-block:: bash
1738
Ales Komarekf8188332016-03-09 11:32:08 +01001739 salt '*' virtng.vm_netstats
smolaon1fb381d2016-03-09 11:10:58 +01001740 '''
1741 def _info(vm_):
1742 dom = _get_dom(vm_)
1743 nics = get_nics(vm_)
1744 ret = {
1745 'rx_bytes': 0,
1746 'rx_packets': 0,
1747 'rx_errs': 0,
1748 'rx_drop': 0,
1749 'tx_bytes': 0,
1750 'tx_packets': 0,
1751 'tx_errs': 0,
1752 'tx_drop': 0
1753 }
1754 for attrs in six.itervalues(nics):
1755 if 'target' in attrs:
1756 dev = attrs['target']
1757 stats = dom.interfaceStats(dev)
1758 ret['rx_bytes'] += stats[0]
1759 ret['rx_packets'] += stats[1]
1760 ret['rx_errs'] += stats[2]
1761 ret['rx_drop'] += stats[3]
1762 ret['tx_bytes'] += stats[4]
1763 ret['tx_packets'] += stats[5]
1764 ret['tx_errs'] += stats[6]
1765 ret['tx_drop'] += stats[7]
1766
1767 return ret
1768 info = {}
1769 if vm_:
1770 info[vm_] = _info(vm_)
1771 else:
1772 for vm_ in list_vms():
1773 info[vm_] = _info(vm_)
1774 return info
1775
1776
1777def vm_diskstats(vm_=None):
1778 '''
1779 Return disk usage counters used by the vms on this hyper in a
1780 list of dicts:
1781
1782 .. code-block:: python
1783
1784 [
1785 'your-vm': {
1786 'rd_req' : 0,
1787 'rd_bytes' : 0,
1788 'wr_req' : 0,
1789 'wr_bytes' : 0,
1790 'errs' : 0
1791 },
1792 ...
1793 ]
1794
1795 If you pass a VM name in as an argument then it will return info
1796 for just the named VM, otherwise it will return all VMs.
1797
1798 CLI Example:
1799
1800 .. code-block:: bash
1801
Ales Komarekf8188332016-03-09 11:32:08 +01001802 salt '*' virtng.vm_blockstats
smolaon1fb381d2016-03-09 11:10:58 +01001803 '''
1804 def get_disk_devs(vm_):
1805 doc = minidom.parse(_StringIO(get_xml(vm_)))
1806 disks = []
1807 for elem in doc.getElementsByTagName('disk'):
1808 targets = elem.getElementsByTagName('target')
1809 target = targets[0]
1810 disks.append(target.getAttribute('dev'))
1811 return disks
1812
1813 def _info(vm_):
1814 dom = _get_dom(vm_)
1815 # Do not use get_disks, since it uses qemu-img and is very slow
1816 # and unsuitable for any sort of real time statistics
1817 disks = get_disk_devs(vm_)
1818 ret = {'rd_req': 0,
1819 'rd_bytes': 0,
1820 'wr_req': 0,
1821 'wr_bytes': 0,
1822 'errs': 0
1823 }
1824 for disk in disks:
1825 stats = dom.blockStats(disk)
1826 ret['rd_req'] += stats[0]
1827 ret['rd_bytes'] += stats[1]
1828 ret['wr_req'] += stats[2]
1829 ret['wr_bytes'] += stats[3]
1830 ret['errs'] += stats[4]
1831
1832 return ret
1833 info = {}
1834 if vm_:
1835 info[vm_] = _info(vm_)
1836 else:
1837 # Can not run function blockStats on inactive VMs
1838 for vm_ in list_active_vms():
1839 info[vm_] = _info(vm_)
1840 return info