blob: ea78c19e2f93d985f37f68d51a56fb38d2a80f58 [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
Andrei Danin996e2092018-09-10 21:58:23 -070012import collections
Dennis Dmitrievf5dba8c2017-10-10 19:05:20 +030013import copy
smolaon1fb381d2016-03-09 11:10:58 +010014import os
15import re
16import sys
17import shutil
18import subprocess
19import string # pylint: disable=deprecated-module
20import logging
21
22# Import third party libs
23import yaml
24import jinja2
25import jinja2.exceptions
26import salt.ext.six as six
27from salt.ext.six.moves import StringIO as _StringIO # pylint: disable=import-error
Andrei Danin996e2092018-09-10 21:58:23 -070028from salt.utils.odict import OrderedDict
smolaon1fb381d2016-03-09 11:10:58 +010029from xml.dom import minidom
Andrei Danin996e2092018-09-10 21:58:23 -070030from xml.etree import ElementTree
smolaon1fb381d2016-03-09 11:10:58 +010031try:
32 import libvirt # pylint: disable=import-error
33 HAS_ALL_IMPORTS = True
34except ImportError:
35 HAS_ALL_IMPORTS = False
36
37# Import salt libs
38import salt.utils
39import salt.utils.files
40import salt.utils.templates
41import salt.utils.validate.net
42from salt.exceptions import CommandExecutionError, SaltInvocationError
43
44log = logging.getLogger(__name__)
45
46# Set up template environment
47JINJA = jinja2.Environment(
48 loader=jinja2.FileSystemLoader(
49 os.path.join(salt.utils.templates.TEMPLATE_DIRNAME, 'virt')
50 )
51)
52
53VIRT_STATE_NAME_MAP = {0: 'running',
54 1: 'running',
55 2: 'running',
56 3: 'paused',
57 4: 'shutdown',
58 5: 'shutdown',
59 6: 'crashed'}
60
61VIRT_DEFAULT_HYPER = 'kvm'
62
63
64def __virtual__():
65 if not HAS_ALL_IMPORTS:
66 return False
Ales Komarekf8188332016-03-09 11:32:08 +010067 return 'virtng'
smolaon1fb381d2016-03-09 11:10:58 +010068
69
70def __get_conn():
71 '''
72 Detects what type of dom this node is and attempts to connect to the
73 correct hypervisor via libvirt.
74 '''
75 # This has only been tested on kvm and xen, it needs to be expanded to
76 # support all vm layers supported by libvirt
77
78 def __esxi_uri():
79 '''
80 Connect to an ESXi host with a configuration like so:
81
82 .. code-block:: yaml
83
84 libvirt:
85 hypervisor: esxi
86 connection: esx01
87
88 The connection setting can either be an explicit libvirt URI,
89 or a libvirt URI alias as in this example. No, it cannot be
90 just a hostname.
91
92
93 Example libvirt `/etc/libvirt/libvirt.conf`:
94
95 .. code-block::
96
97 uri_aliases = [
98 "esx01=esx://10.1.1.101/?no_verify=1&auto_answer=1",
99 "esx02=esx://10.1.1.102/?no_verify=1&auto_answer=1",
100 ]
101
102 Reference:
103
104 - http://libvirt.org/drvesx.html#uriformat
105 - http://libvirt.org/uri.html#URI_config
106 '''
107 connection = __salt__['config.get']('libvirt:connection', 'esx')
108 return connection
109
110 def __esxi_auth():
111 '''
112 We rely on that the credentials is provided to libvirt through
113 its built in mechanisms.
114
115 Example libvirt `/etc/libvirt/auth.conf`:
116
117 .. code-block::
118
119 [credentials-myvirt]
120 username=user
121 password=secret
122
123 [auth-esx-10.1.1.101]
124 credentials=myvirt
125
126 [auth-esx-10.1.1.102]
127 credentials=myvirt
128
129 Reference:
130
131 - http://libvirt.org/auth.html#Auth_client_config
132 '''
133 return [[libvirt.VIR_CRED_EXTERNAL], lambda: 0, None]
134
135 if 'virt.connect' in __opts__:
136 conn_str = __opts__['virt.connect']
137 else:
138 conn_str = 'qemu:///system'
139
140 conn_func = {
141 'esxi': [libvirt.openAuth, [__esxi_uri(),
142 __esxi_auth(),
143 0]],
144 'qemu': [libvirt.open, [conn_str]],
145 }
146
147 hypervisor = __salt__['config.get']('libvirt:hypervisor', 'qemu')
148
149 try:
150 conn = conn_func[hypervisor][0](*conn_func[hypervisor][1])
151 except Exception:
152 raise CommandExecutionError(
153 'Sorry, {0} failed to open a connection to the hypervisor '
154 'software at {1}'.format(
155 __grains__['fqdn'],
156 conn_func[hypervisor][1][0]
157 )
158 )
159 return conn
160
161
162def _get_dom(vm_):
163 '''
164 Return a domain object for the named vm
165 '''
166 conn = __get_conn()
167 if vm_ not in list_vms():
168 raise CommandExecutionError('The specified vm is not present')
169 return conn.lookupByName(vm_)
170
171
172def _libvirt_creds():
173 '''
174 Returns the user and group that the disk images should be owned by
175 '''
176 g_cmd = 'grep ^\\s*group /etc/libvirt/qemu.conf'
177 u_cmd = 'grep ^\\s*user /etc/libvirt/qemu.conf'
178 try:
179 group = subprocess.Popen(g_cmd,
180 shell=True,
181 stdout=subprocess.PIPE).communicate()[0].split('"')[1]
182 except IndexError:
183 group = 'root'
184 try:
185 user = subprocess.Popen(u_cmd,
186 shell=True,
187 stdout=subprocess.PIPE).communicate()[0].split('"')[1]
188 except IndexError:
189 user = 'root'
190 return {'user': user, 'group': group}
191
192
193def _get_migrate_command():
194 '''
195 Returns the command shared by the different migration types
196 '''
197 if __salt__['config.option']('virt.tunnel'):
198 return ('virsh migrate --p2p --tunnelled --live --persistent '
199 '--undefinesource ')
200 return 'virsh migrate --live --persistent --undefinesource '
201
202
203def _get_target(target, ssh):
204 proto = 'qemu'
205 if ssh:
206 proto += '+ssh'
207 return ' {0}://{1}/{2}'.format(proto, target, 'system')
208
209
210def _gen_xml(name,
211 cpu,
212 mem,
213 diskp,
214 nicp,
215 hypervisor,
216 **kwargs):
217 '''
218 Generate the XML string to define a libvirt vm
219 '''
220 hypervisor = 'vmware' if hypervisor == 'esxi' else hypervisor
221 mem = mem * 1024 # MB
222 context = {
223 'hypervisor': hypervisor,
224 'name': name,
225 'cpu': str(cpu),
226 'mem': str(mem),
227 }
228 if hypervisor in ['qemu', 'kvm']:
229 context['controller_model'] = False
230 elif hypervisor in ['esxi', 'vmware']:
231 # TODO: make bus and model parameterized, this works for 64-bit Linux
232 context['controller_model'] = 'lsilogic'
233
234 if 'boot_dev' in kwargs:
235 context['boot_dev'] = []
236 for dev in kwargs['boot_dev'].split():
237 context['boot_dev'].append(dev)
238 else:
239 context['boot_dev'] = ['hd']
240
Martin Horak0d183cb2018-09-14 16:11:08 +0200241 if 'enable_vnc' in kwargs:
242 context['enable_vnc'] = kwargs['enable_vnc']
243 log.info('VNC enabled: {0}.'.format(kwargs['enable_vnc']))
smolaon1fb381d2016-03-09 11:10:58 +0100244 if 'serial_type' in kwargs:
245 context['serial_type'] = kwargs['serial_type']
246 if 'serial_type' in context and context['serial_type'] == 'tcp':
247 if 'telnet_port' in kwargs:
248 context['telnet_port'] = kwargs['telnet_port']
249 else:
250 context['telnet_port'] = 23023 # FIXME: use random unused port
251 if 'serial_type' in context:
252 if 'console' in kwargs:
253 context['console'] = kwargs['console']
254 else:
255 context['console'] = True
256
257 context['disks'] = {}
Andrei Danin996e2092018-09-10 21:58:23 -0700258 context['cdrom'] = []
smolaon1fb381d2016-03-09 11:10:58 +0100259 for i, disk in enumerate(diskp):
260 for disk_name, args in disk.items():
Andrei Danin996e2092018-09-10 21:58:23 -0700261 if args.get('device', 'disk') == 'cdrom':
262 context['cdrom'].append(args)
263 continue
smolaon1fb381d2016-03-09 11:10:58 +0100264 context['disks'][disk_name] = {}
265 fn_ = '{0}.{1}'.format(disk_name, args['format'])
266 context['disks'][disk_name]['file_name'] = fn_
267 context['disks'][disk_name]['source_file'] = os.path.join(args['pool'],
268 name,
269 fn_)
270 if hypervisor in ['qemu', 'kvm']:
271 context['disks'][disk_name]['target_dev'] = 'vd{0}'.format(string.ascii_lowercase[i])
272 context['disks'][disk_name]['address'] = False
273 context['disks'][disk_name]['driver'] = True
274 elif hypervisor in ['esxi', 'vmware']:
275 context['disks'][disk_name]['target_dev'] = 'sd{0}'.format(string.ascii_lowercase[i])
276 context['disks'][disk_name]['address'] = True
277 context['disks'][disk_name]['driver'] = False
278 context['disks'][disk_name]['disk_bus'] = args['model']
279 context['disks'][disk_name]['type'] = args['format']
280 context['disks'][disk_name]['index'] = str(i)
281
282 context['nics'] = nicp
283
284 fn_ = 'libvirt_domain.jinja'
285 try:
286 template = JINJA.get_template(fn_)
287 except jinja2.exceptions.TemplateNotFound:
288 log.error('Could not load template {0}'.format(fn_))
289 return ''
290
Andrei Danin996e2092018-09-10 21:58:23 -0700291 xml = template.render(**context)
smolaon1fb381d2016-03-09 11:10:58 +0100292
Andrei Danin996e2092018-09-10 21:58:23 -0700293 # Add cdrom devices separately because a current template doesn't support them.
294 if context['cdrom']:
295 xml_doc = ElementTree.fromstring(xml)
296 xml_devs = xml_doc.find('.//devices')
297 cdrom_xml_tmpl = """<disk type='file' device='cdrom'>
298 <driver name='{driver_name}' type='{driver_type}'/>
299 <source file='{filename}'/>
300 <target dev='{dev}' bus='{bus}'/>
301 <readonly/>
302 </disk>"""
303 for disk in context['cdrom']:
304 cdrom_elem = ElementTree.fromstring(cdrom_xml_tmpl.format(**disk))
305 xml_devs.append(cdrom_elem)
306 xml = ElementTree.tostring(xml_doc)
307 return xml
smolaon1fb381d2016-03-09 11:10:58 +0100308
309def _gen_vol_xml(vmname,
310 diskname,
311 size,
312 hypervisor,
313 **kwargs):
314 '''
315 Generate the XML string to define a libvirt storage volume
316 '''
317 size = int(size) * 1024 # MB
318 disk_info = _get_image_info(hypervisor, vmname, **kwargs)
319 context = {
320 'name': vmname,
321 'filename': '{0}.{1}'.format(diskname, disk_info['disktype']),
322 'volname': diskname,
323 'disktype': disk_info['disktype'],
324 'size': str(size),
325 'pool': disk_info['pool'],
326 }
327 fn_ = 'libvirt_volume.jinja'
328 try:
329 template = JINJA.get_template(fn_)
330 except jinja2.exceptions.TemplateNotFound:
331 log.error('Could not load template {0}'.format(fn_))
332 return ''
333 return template.render(**context)
334
335
336def _qemu_image_info(path):
337 '''
338 Detect information for the image at path
339 '''
340 ret = {}
Oleh Hryhorov7c5dfd32018-01-19 15:43:44 +0200341 out = __salt__['cmd.shell']('qemu-img info {0}'.format(path))
smolaon1fb381d2016-03-09 11:10:58 +0100342
343 match_map = {'size': r'virtual size: \w+ \((\d+) byte[s]?\)',
344 'format': r'file format: (\w+)'}
345
346 for info, search in match_map.items():
347 try:
348 ret[info] = re.search(search, out).group(1)
349 except AttributeError:
350 continue
351 return ret
352
353
354# TODO: this function is deprecated, should be replaced with
355# _qemu_image_info()
356def _image_type(vda):
357 '''
358 Detect what driver needs to be used for the given image
359 '''
Oleh Hryhorov7c5dfd32018-01-19 15:43:44 +0200360 out = __salt__['cmd.shell']('qemu-img info {0}'.format(vda))
smolaon1fb381d2016-03-09 11:10:58 +0100361 if 'file format: qcow2' in out:
362 return 'qcow2'
363 else:
364 return 'raw'
365
366
367# TODO: this function is deprecated, should be merged and replaced
368# with _disk_profile()
369def _get_image_info(hypervisor, name, **kwargs):
370 '''
371 Determine disk image info, such as filename, image format and
372 storage pool, based on which hypervisor is used
373 '''
374 ret = {}
375 if hypervisor in ['esxi', 'vmware']:
376 ret['disktype'] = 'vmdk'
377 ret['filename'] = '{0}{1}'.format(name, '.vmdk')
378 ret['pool'] = '[{0}] '.format(kwargs.get('pool', '0'))
379 elif hypervisor in ['kvm', 'qemu']:
380 ret['disktype'] = 'qcow2'
381 ret['filename'] = '{0}{1}'.format(name, '.qcow2')
Mateusz Los4c7cd2d2018-01-09 11:46:07 +0100382 if 'img_dest' in kwargs:
383 ret['pool'] = kwargs['img_dest']
384 else:
385 ret['pool'] = __salt__['config.option']('virt.images')
smolaon1fb381d2016-03-09 11:10:58 +0100386 return ret
387
388
389def _disk_profile(profile, hypervisor, **kwargs):
390 '''
391 Gather the disk profile from the config or apply the default based
392 on the active hypervisor
393
394 This is the ``default`` profile for KVM/QEMU, which can be
395 overridden in the configuration:
396
397 .. code-block:: yaml
398
399 virt:
400 disk:
401 default:
402 - system:
403 size: 8192
404 format: qcow2
405 model: virtio
406
Dennis Dmitrieva57463c2017-03-24 17:05:22 +0200407 Example profile for KVM/QEMU with two disks, first is created
408 from specified image, the second is empty:
409
410 .. code-block:: yaml
411
412 virt:
413 disk:
414 two_disks:
415 - system:
416 size: 8192
417 format: qcow2
418 model: virtio
419 image: http://path/to/image.qcow2
420 - lvm:
421 size: 32768
422 format: qcow2
423 model: virtio
424
smolaon1fb381d2016-03-09 11:10:58 +0100425 The ``format`` and ``model`` parameters are optional, and will
426 default to whatever is best suitable for the active hypervisor.
427 '''
428 default = [
429 {'system':
430 {'size': '8192'}
431 }
432 ]
433 if hypervisor in ['esxi', 'vmware']:
434 overlay = {'format': 'vmdk',
435 'model': 'scsi',
436 'pool': '[{0}] '.format(kwargs.get('pool', '0'))
437 }
438 elif hypervisor in ['qemu', 'kvm']:
Mateusz Los4c7cd2d2018-01-09 11:46:07 +0100439 if 'img_dest' in kwargs:
440 pool = kwargs['img_dest']
441 else:
442 pool = __salt__['config.option']('virt.images')
443 overlay = {'format': 'qcow2', 'model': 'virtio', 'pool': pool}
smolaon1fb381d2016-03-09 11:10:58 +0100444 else:
445 overlay = {}
446
Dennis Dmitrievf5dba8c2017-10-10 19:05:20 +0300447 disklist = copy.deepcopy(__salt__['config.get']('virt:disk', {}).get(profile, default))
smolaon1fb381d2016-03-09 11:10:58 +0100448 for key, val in overlay.items():
449 for i, disks in enumerate(disklist):
450 for disk in disks:
451 if key not in disks[disk]:
452 disklist[i][disk][key] = val
453 return disklist
454
455
456def _nic_profile(profile_name, hypervisor, **kwargs):
457
smolaon1fb381d2016-03-09 11:10:58 +0100458 def append_dict_profile_to_interface_list(profile_dict):
459 for interface_name, attributes in profile_dict.items():
460 attributes['name'] = interface_name
461 interfaces.append(attributes)
462
smolaon1fb381d2016-03-09 11:10:58 +0100463 def _normalize_net_types(attributes):
464 '''
465 Guess which style of definition:
466
467 bridge: br0
468
469 or
470
471 network: net0
472
473 or
474
475 type: network
476 source: net0
477 '''
478 for type_ in ['bridge', 'network']:
479 if type_ in attributes:
480 attributes['type'] = type_
481 # we want to discard the original key
482 attributes['source'] = attributes.pop(type_)
483
484 attributes['type'] = attributes.get('type', None)
485 attributes['source'] = attributes.get('source', None)
Dzmitry Stremkouskib8acf1f2018-06-28 12:56:23 +0200486 attributes['virtualport'] = attributes.get('virtualport', None)
Roman Lubianyi8a7640e2021-04-29 17:51:47 +0300487 attributes['driver'] = attributes.get('driver', None)
smolaon1fb381d2016-03-09 11:10:58 +0100488
489 def _apply_default_overlay(attributes):
490 for key, value in overlays[hypervisor].items():
491 if key not in attributes or not attributes[key]:
492 attributes[key] = value
493
494 def _assign_mac(attributes):
495 dmac = '{0}_mac'.format(attributes['name'])
496 if dmac in kwargs:
497 dmac = kwargs[dmac]
498 if salt.utils.validate.net.mac(dmac):
499 attributes['mac'] = dmac
500 else:
501 msg = 'Malformed MAC address: {0}'.format(dmac)
502 raise CommandExecutionError(msg)
503 else:
504 attributes['mac'] = salt.utils.gen_mac()
505
Dzmitry Stremkouskib8acf1f2018-06-28 12:56:23 +0200506
507 default = [{'eth0': {}}]
508 vmware_overlay = {'type': 'bridge', 'source': 'DEFAULT', 'model': 'e1000'}
509 kvm_overlay = {'type': 'bridge', 'source': 'br0', 'model': 'virtio'}
510 overlays = {
511 'kvm': kvm_overlay,
512 'qemu': kvm_overlay,
513 'esxi': vmware_overlay,
514 'vmware': vmware_overlay,
515 }
516
517 # support old location
518 config_data = __salt__['config.option']('virt.nic', {}).get(
519 profile_name, None
520 )
521
522 if config_data is None:
523 config_data = __salt__['config.get']('virt:nic', {}).get(
524 profile_name, default
525 )
526
527 interfaces = []
528
529 if isinstance(config_data, dict):
530 append_dict_profile_to_interface_list(config_data)
531
532 elif isinstance(config_data, list):
533 for interface in config_data:
534 if isinstance(interface, dict):
535 if len(interface) == 1:
536 append_dict_profile_to_interface_list(interface)
537 else:
538 interfaces.append(interface)
539
smolaon1fb381d2016-03-09 11:10:58 +0100540 for interface in interfaces:
541 _normalize_net_types(interface)
542 _assign_mac(interface)
543 if hypervisor in overlays:
544 _apply_default_overlay(interface)
545
546 return interfaces
547
548
549def init(name,
550 cpu,
551 mem,
552 image=None,
553 nic='default',
554 hypervisor=VIRT_DEFAULT_HYPER,
555 start=True, # pylint: disable=redefined-outer-name
Dzmitry Stremkouski4783b6c2019-04-17 16:16:23 +0200556 dry_run=False,
smolaon1fb381d2016-03-09 11:10:58 +0100557 disk='default',
558 saltenv='base',
azvyagintseva4e802d2018-05-04 20:16:02 +0300559 rng=None,
Alexandru Avadanii00f187a2018-06-24 20:36:44 +0200560 loader=None,
561 machine=None,
562 cpu_mode=None,
Pavel Cizinskyf03c4e82018-09-10 14:56:11 +0200563 cpuset=None,
smolaon1fb381d2016-03-09 11:10:58 +0100564 **kwargs):
565 '''
566 Initialize a new vm
567
568 CLI Example:
569
570 .. code-block:: bash
571
572 salt 'hypervisor' virt.init vm_name 4 512 salt://path/to/image.raw
573 salt 'hypervisor' virt.init vm_name 4 512 nic=profile disk=profile
574 '''
Dennis Dmitrieva57463c2017-03-24 17:05:22 +0200575
azvyagintseva4e802d2018-05-04 20:16:02 +0300576 rng = rng or {'backend':'/dev/urandom'}
smolaon1fb381d2016-03-09 11:10:58 +0100577 hypervisor = __salt__['config.get']('libvirt:hypervisor', hypervisor)
Andrei Danin996e2092018-09-10 21:58:23 -0700578 if kwargs.get('seed') not in (False, True, None, 'qemu-nbd', 'cloud-init'):
579 log.warning(
580 "The seeding method '{0}' is not supported".format(kwargs.get('seed'))
581 )
smolaon1fb381d2016-03-09 11:10:58 +0100582
583 nicp = _nic_profile(nic, hypervisor, **kwargs)
584
Dennis Dmitrieva57463c2017-03-24 17:05:22 +0200585 diskp = _disk_profile(disk, hypervisor, **kwargs)
586
587 if image:
588 # Backward compatibility: if 'image' is specified in the VMs arguments
589 # instead of a disk arguments. In this case, 'image' will be assigned
590 # to the first disk for the VM.
smolaon1fb381d2016-03-09 11:10:58 +0100591 disk_name = next(diskp[0].iterkeys())
Dennis Dmitrieva57463c2017-03-24 17:05:22 +0200592 if not diskp[0][disk_name].get('image', None):
593 diskp[0][disk_name]['image'] = image
smolaon1fb381d2016-03-09 11:10:58 +0100594
Ivan Berezovskiy3dff8d92020-03-24 19:55:55 +0400595 mask = kwargs.get('file_mask', os.umask(0))
596 os.umask(mask)
597
Dennis Dmitrieva57463c2017-03-24 17:05:22 +0200598 # Create multiple disks, empty or from specified images.
599 for disk in diskp:
600 log.debug("Creating disk for VM [ {0} ]: {1}".format(name, disk))
smolaon1fb381d2016-03-09 11:10:58 +0100601
Dennis Dmitrieva57463c2017-03-24 17:05:22 +0200602 for disk_name, args in disk.items():
smolaon1fb381d2016-03-09 11:10:58 +0100603
Dennis Dmitrieva57463c2017-03-24 17:05:22 +0200604 if hypervisor in ['esxi', 'vmware']:
605 if 'image' in args:
606 # TODO: we should be copying the image file onto the ESX host
607 raise SaltInvocationError('virt.init does not support image '
608 'template template in conjunction '
609 'with esxi hypervisor')
610 else:
611 # assume libvirt manages disks for us
smolaon1fb381d2016-03-09 11:10:58 +0100612 xml = _gen_vol_xml(name,
613 disk_name,
614 args['size'],
Mateusz Los4c7cd2d2018-01-09 11:46:07 +0100615 hypervisor,
616 **kwargs)
smolaon1fb381d2016-03-09 11:10:58 +0100617 define_vol_xml_str(xml)
618
Dennis Dmitrieva57463c2017-03-24 17:05:22 +0200619 elif hypervisor in ['qemu', 'kvm']:
620
621 disk_type = args['format']
622 disk_file_name = '{0}.{1}'.format(disk_name, disk_type)
623 # disk size TCP cloud
624 disk_size = args['size']
625
Mateusz Los4c7cd2d2018-01-09 11:46:07 +0100626 if 'img_dest' in kwargs:
627 img_dir = kwargs['img_dest']
628 else:
629 img_dir = __salt__['config.option']('virt.images')
Dennis Dmitrieva57463c2017-03-24 17:05:22 +0200630 img_dest = os.path.join(
631 img_dir,
632 name,
633 disk_file_name
634 )
635 img_dir = os.path.dirname(img_dest)
636 if not os.path.isdir(img_dir):
637 os.makedirs(img_dir)
Ivan Berezovskiy3dff8d92020-03-24 19:55:55 +0400638 dir_mode = (0o0777 ^ mask) & 0o0777
639 os.chmod(img_dir, dir_mode)
Dennis Dmitrieva57463c2017-03-24 17:05:22 +0200640
641 if 'image' in args:
Dzmitry Stremkouski4783b6c2019-04-17 16:16:23 +0200642 if dry_run:
643 continue
Dennis Dmitrieva57463c2017-03-24 17:05:22 +0200644 # Create disk from specified image
645 sfn = __salt__['cp.cache_file'](args['image'], saltenv)
646 try:
647 salt.utils.files.copyfile(sfn, img_dest)
Dennis Dmitrieva57463c2017-03-24 17:05:22 +0200648 # Resizing image TCP cloud
649 cmd = 'qemu-img resize ' + img_dest + ' ' + str(disk_size) + 'M'
650 subprocess.call(cmd, shell=True)
651
Ivan Berezovskiy3dff8d92020-03-24 19:55:55 +0400652 # Apply umask and remove exec bit
Dennis Dmitrieva57463c2017-03-24 17:05:22 +0200653 mode = (0o0777 ^ mask) & 0o0666
654 os.chmod(img_dest, mode)
655
656 except (IOError, OSError) as e:
657 raise CommandExecutionError('problem while copying image. {0} - {1}'.format(args['image'], e))
658
Andrei Danin996e2092018-09-10 21:58:23 -0700659 if kwargs.get('seed') in (True, 'qemu-nbd'):
660 install = kwargs.get('install', True)
661 seed_cmd = kwargs.get('seed_cmd', 'seedng.apply')
Dennis Dmitrieva57463c2017-03-24 17:05:22 +0200662
Andrei Danin996e2092018-09-10 21:58:23 -0700663 __salt__[seed_cmd](img_dest,
664 id_=name,
665 config=kwargs.get('config'),
666 install=install)
Dennis Dmitrieva57463c2017-03-24 17:05:22 +0200667 else:
668 # Create empty disk
669 try:
Dennis Dmitrieva57463c2017-03-24 17:05:22 +0200670 # Create empty image
671 cmd = 'qemu-img create -f ' + disk_type + ' ' + img_dest + ' ' + str(disk_size) + 'M'
672 subprocess.call(cmd, shell=True)
673
Ivan Berezovskiy3dff8d92020-03-24 19:55:55 +0400674 # Apply umask and remove exec bit
Dennis Dmitrieva57463c2017-03-24 17:05:22 +0200675 mode = (0o0777 ^ mask) & 0o0666
676 os.chmod(img_dest, mode)
677
678 except (IOError, OSError) as e:
679 raise CommandExecutionError('problem while creating volume {0} - {1}'.format(img_dest, e))
680
681 else:
682 # Unknown hypervisor
683 raise SaltInvocationError('Unsupported hypervisor when handling disk image: {0}'
684 .format(hypervisor))
685
Andrei Danin996e2092018-09-10 21:58:23 -0700686 cloud_init = kwargs.get('cloud_init', {})
687
688 # Seed Salt Minion config via Cloud-init if required.
689 if kwargs.get('seed') == 'cloud-init':
690 # Recursive dict update.
691 def rec_update(d, u):
692 for k, v in u.iteritems():
693 if isinstance(v, collections.Mapping):
694 d[k] = rec_update(d.get(k, {}), v)
695 else:
696 d[k] = v
697 return d
698
699 cloud_init_seed = {
700 "user_data": {
701 "salt_minion": {
702 "conf": {
703 "master": __salt__['config.option']('master'),
704 "id": name
705 }
706 }
707 }
708 }
709 cloud_init = rec_update(cloud_init_seed, cloud_init)
710
711 # Create a cloud-init config drive if defined.
712 if cloud_init:
713 if hypervisor not in ['qemu', 'kvm']:
714 raise SaltInvocationError('Unsupported hypervisor when '
715 'handling Cloud-Init disk '
716 'image: {0}'.format(hypervisor))
717 cfg_drive = os.path.join(img_dir, 'config-2.iso')
718 vm_hostname, vm_domainname = name.split('.', 1)
719
720 def OrderedDict_to_dict(instance):
721 if isinstance(instance, basestring):
722 return instance
723 elif isinstance(instance, collections.Sequence):
724 return map(OrderedDict_to_dict, instance)
725 elif isinstance(instance, collections.Mapping):
726 if isinstance(instance, OrderedDict):
727 instance = dict(instance)
728 for k, v in instance.iteritems():
729 instance[k] = OrderedDict_to_dict(v)
730 return instance
731 else:
732 return instance
733
734 # Yaml.dump dumps OrderedDict in the way to be incompatible with
735 # Cloud-init, hence all OrderedDicts have to be converted to dict first.
736 user_data = OrderedDict_to_dict(cloud_init.get('user_data', None))
737
738 __salt__["cfgdrive.generate"](
739 dst=cfg_drive,
740 hostname=vm_hostname,
741 domainname=vm_domainname,
742 user_data=user_data,
743 network_data=cloud_init.get('network_data', None),
744 )
745 diskp.append({
746 'config_2': {
747 'device': 'cdrom',
748 'driver_name': 'qemu',
749 'driver_type': 'raw',
750 'dev': 'hdc',
751 'bus': 'ide',
752 'filename': cfg_drive
753 }
754 })
755
smolaon1fb381d2016-03-09 11:10:58 +0100756 xml = _gen_xml(name, cpu, mem, diskp, nicp, hypervisor, **kwargs)
Dzmitry Stremkouski7ee23402018-04-10 00:43:48 +0200757
758 # TODO: Remove this code and refactor module, when salt-common would have updated libvirt_domain.jinja template
Andrei Danin996e2092018-09-10 21:58:23 -0700759 xml_doc = minidom.parseString(xml)
Pavel Cizinskyf03c4e82018-09-10 14:56:11 +0200760 if cpuset:
Pavel Cizinskyf03c4e82018-09-10 14:56:11 +0200761 xml_doc.getElementsByTagName("vcpu")[0].setAttribute('cpuset', cpuset)
Pavel Cizinskyf03c4e82018-09-10 14:56:11 +0200762
763 # TODO: Remove this code and refactor module, when salt-common would have updated libvirt_domain.jinja template
Alexandru Avadanii00f187a2018-06-24 20:36:44 +0200764 if cpu_mode:
Alexandru Avadanii00f187a2018-06-24 20:36:44 +0200765 cpu_xml = xml_doc.createElement("cpu")
766 cpu_xml.setAttribute('mode', cpu_mode)
767 xml_doc.getElementsByTagName("domain")[0].appendChild(cpu_xml)
Alexandru Avadanii00f187a2018-06-24 20:36:44 +0200768
769 # TODO: Remove this code and refactor module, when salt-common would have updated libvirt_domain.jinja template
770 if machine:
Alexandru Avadanii00f187a2018-06-24 20:36:44 +0200771 os_xml = xml_doc.getElementsByTagName("domain")[0].getElementsByTagName("os")[0]
772 os_xml.getElementsByTagName("type")[0].setAttribute('machine', machine)
Alexandru Avadanii00f187a2018-06-24 20:36:44 +0200773
774 # TODO: Remove this code and refactor module, when salt-common would have updated libvirt_domain.jinja template
775 if loader and 'path' not in loader:
776 log.info('`path` is a required property of `loader`, and cannot be found. Skipping loader configuration')
777 loader = None
778 elif loader:
Alexandru Avadanii00f187a2018-06-24 20:36:44 +0200779 loader_xml = xml_doc.createElement("loader")
780 for key, val in loader.items():
781 if key == 'path':
782 continue
783 loader_xml.setAttribute(key, val)
784 loader_path_xml = xml_doc.createTextNode(loader['path'])
785 loader_xml.appendChild(loader_path_xml)
786 xml_doc.getElementsByTagName("domain")[0].getElementsByTagName("os")[0].appendChild(loader_xml)
Alexandru Avadanii00f187a2018-06-24 20:36:44 +0200787
788 # TODO: Remove this code and refactor module, when salt-common would have updated libvirt_domain.jinja template
Dzmitry Stremkouskib8acf1f2018-06-28 12:56:23 +0200789 for _nic in nicp:
Roman Lubianyi8a7640e2021-04-29 17:51:47 +0300790 if _nic['virtualport'] or _nic['driver']:
Dzmitry Stremkouskib8acf1f2018-06-28 12:56:23 +0200791 interfaces = xml_doc.getElementsByTagName("domain")[0].getElementsByTagName("devices")[0].getElementsByTagName("interface")
792 for interface in interfaces:
793 if interface.getElementsByTagName('mac')[0].getAttribute('address').lower() == _nic['mac'].lower():
Roman Lubianyi8a7640e2021-04-29 17:51:47 +0300794 if _nic['virtualport']:
795 vport_xml = xml_doc.createElement("virtualport")
796 vport_xml.setAttribute("type", _nic['virtualport']['type'])
797 interface.appendChild(vport_xml)
798 if _nic['driver']:
799 driver_xml = xml_doc.createElement("driver")
800 driver_xml.setAttribute("name", _nic['driver']['name'])
801 if _nic['driver'].get('queues'):
802 driver_xml.setAttribute("queues", _nic['driver']['queues'])
803 else:
804 driver_xml.setAttribute("queues", str(cpu))
805 if _nic['driver'].get('rx_queue_size'):
806 driver_xml.setAttribute("rx_queue_size", _nic['driver']['rx_queue_size'])
807 if _nic['driver'].get('tx_queue_size'):
808 driver_xml.setAttribute("tx_queue_size", _nic['driver']['tx_queue_size'])
809 interface.appendChild(driver_xml)
Dzmitry Stremkouskib8acf1f2018-06-28 12:56:23 +0200810
811 # TODO: Remove this code and refactor module, when salt-common would have updated libvirt_domain.jinja template
Dzmitry Stremkouski7ee23402018-04-10 00:43:48 +0200812 if rng:
813 rng_model = rng.get('model', 'random')
814 rng_backend = rng.get('backend', '/dev/urandom')
Dzmitry Stremkouski7ee23402018-04-10 00:43:48 +0200815 rng_xml = xml_doc.createElement("rng")
816 rng_xml.setAttribute("model", "virtio")
817 backend = xml_doc.createElement("backend")
818 backend.setAttribute("model", rng_model)
819 backend.appendChild(xml_doc.createTextNode(rng_backend))
820 rng_xml.appendChild(backend)
821 if 'rate' in rng:
822 rng_rate_period = rng['rate'].get('period', '2000')
823 rng_rate_bytes = rng['rate'].get('bytes', '1234')
824 rate = xml_doc.createElement("rate")
825 rate.setAttribute("period", rng_rate_period)
826 rate.setAttribute("bytes", rng_rate_bytes)
827 rng_xml.appendChild(rate)
828 xml_doc.getElementsByTagName("domain")[0].getElementsByTagName("devices")[0].appendChild(rng_xml)
Dzmitry Stremkouski7ee23402018-04-10 00:43:48 +0200829
Andrei Danin996e2092018-09-10 21:58:23 -0700830 xml = xml_doc.toxml()
smolaon1fb381d2016-03-09 11:10:58 +0100831 define_xml_str(xml)
832
Dzmitry Stremkouski4783b6c2019-04-17 16:16:23 +0200833 if start and not dry_run:
smolaon1fb381d2016-03-09 11:10:58 +0100834 create(name)
835
836 return True
837
838
839def list_vms():
840 '''
841 Return a list of virtual machine names on the minion
842
843 CLI Example:
844
845 .. code-block:: bash
846
Ales Komarekf8188332016-03-09 11:32:08 +0100847 salt '*' virtng.list_vms
smolaon1fb381d2016-03-09 11:10:58 +0100848 '''
849 vms = []
850 vms.extend(list_active_vms())
851 vms.extend(list_inactive_vms())
852 return vms
853
854
855def list_active_vms():
856 '''
857 Return a list of names for active virtual machine on the minion
858
859 CLI Example:
860
861 .. code-block:: bash
862
Ales Komarekf8188332016-03-09 11:32:08 +0100863 salt '*' virtng.list_active_vms
smolaon1fb381d2016-03-09 11:10:58 +0100864 '''
865 conn = __get_conn()
866 vms = []
867 for id_ in conn.listDomainsID():
868 vms.append(conn.lookupByID(id_).name())
869 return vms
870
871
872def list_inactive_vms():
873 '''
874 Return a list of names for inactive virtual machine on the minion
875
876 CLI Example:
877
878 .. code-block:: bash
879
Ales Komarekf8188332016-03-09 11:32:08 +0100880 salt '*' virtng.list_inactive_vms
smolaon1fb381d2016-03-09 11:10:58 +0100881 '''
882 conn = __get_conn()
883 vms = []
884 for id_ in conn.listDefinedDomains():
885 vms.append(id_)
886 return vms
887
888
889def vm_info(vm_=None):
890 '''
891 Return detailed information about the vms on this hyper in a
892 list of dicts:
893
894 .. code-block:: python
895
896 [
897 'your-vm': {
898 'cpu': <int>,
899 'maxMem': <int>,
900 'mem': <int>,
901 'state': '<state>',
902 'cputime' <int>
903 },
904 ...
905 ]
906
907 If you pass a VM name in as an argument then it will return info
908 for just the named VM, otherwise it will return all VMs.
909
910 CLI Example:
911
912 .. code-block:: bash
913
Ales Komarekf8188332016-03-09 11:32:08 +0100914 salt '*' virtng.vm_info
smolaon1fb381d2016-03-09 11:10:58 +0100915 '''
916 def _info(vm_):
917 dom = _get_dom(vm_)
918 raw = dom.info()
919 return {'cpu': raw[3],
920 'cputime': int(raw[4]),
921 'disks': get_disks(vm_),
922 'graphics': get_graphics(vm_),
923 'nics': get_nics(vm_),
924 'maxMem': int(raw[1]),
925 'mem': int(raw[2]),
926 'state': VIRT_STATE_NAME_MAP.get(raw[0], 'unknown')}
927 info = {}
928 if vm_:
929 info[vm_] = _info(vm_)
930 else:
931 for vm_ in list_vms():
932 info[vm_] = _info(vm_)
933 return info
934
935
936def vm_state(vm_=None):
937 '''
938 Return list of all the vms and their state.
939
940 If you pass a VM name in as an argument then it will return info
941 for just the named VM, otherwise it will return all VMs.
942
943 CLI Example:
944
945 .. code-block:: bash
946
Ales Komarekf8188332016-03-09 11:32:08 +0100947 salt '*' virtng.vm_state <vm name>
smolaon1fb381d2016-03-09 11:10:58 +0100948 '''
949 def _info(vm_):
950 state = ''
951 dom = _get_dom(vm_)
952 raw = dom.info()
953 state = VIRT_STATE_NAME_MAP.get(raw[0], 'unknown')
954 return state
955 info = {}
956 if vm_:
957 info[vm_] = _info(vm_)
958 else:
959 for vm_ in list_vms():
960 info[vm_] = _info(vm_)
961 return info
962
963
964def node_info():
965 '''
966 Return a dict with information about this node
967
968 CLI Example:
969
970 .. code-block:: bash
971
Ales Komarekf8188332016-03-09 11:32:08 +0100972 salt '*' virtng.node_info
smolaon1fb381d2016-03-09 11:10:58 +0100973 '''
974 conn = __get_conn()
975 raw = conn.getInfo()
976 info = {'cpucores': raw[6],
977 'cpumhz': raw[3],
978 'cpumodel': str(raw[0]),
979 'cpus': raw[2],
980 'cputhreads': raw[7],
981 'numanodes': raw[4],
982 'phymemory': raw[1],
983 'sockets': raw[5]}
984 return info
985
986
987def get_nics(vm_):
988 '''
989 Return info about the network interfaces of a named vm
990
991 CLI Example:
992
993 .. code-block:: bash
994
Ales Komarekf8188332016-03-09 11:32:08 +0100995 salt '*' virtng.get_nics <vm name>
smolaon1fb381d2016-03-09 11:10:58 +0100996 '''
997 nics = {}
998 doc = minidom.parse(_StringIO(get_xml(vm_)))
999 for node in doc.getElementsByTagName('devices'):
1000 i_nodes = node.getElementsByTagName('interface')
1001 for i_node in i_nodes:
1002 nic = {}
1003 nic['type'] = i_node.getAttribute('type')
1004 for v_node in i_node.getElementsByTagName('*'):
1005 if v_node.tagName == 'mac':
1006 nic['mac'] = v_node.getAttribute('address')
1007 if v_node.tagName == 'model':
1008 nic['model'] = v_node.getAttribute('type')
1009 if v_node.tagName == 'target':
1010 nic['target'] = v_node.getAttribute('dev')
1011 # driver, source, and match can all have optional attributes
1012 if re.match('(driver|source|address)', v_node.tagName):
1013 temp = {}
1014 for key, value in v_node.attributes.items():
1015 temp[key] = value
1016 nic[str(v_node.tagName)] = temp
1017 # virtualport needs to be handled separately, to pick up the
1018 # type attribute of the virtualport itself
1019 if v_node.tagName == 'virtualport':
1020 temp = {}
1021 temp['type'] = v_node.getAttribute('type')
1022 for key, value in v_node.attributes.items():
1023 temp[key] = value
1024 nic['virtualport'] = temp
1025 if 'mac' not in nic:
1026 continue
1027 nics[nic['mac']] = nic
1028 return nics
1029
1030
1031def get_macs(vm_):
1032 '''
1033 Return a list off MAC addresses from the named vm
1034
1035 CLI Example:
1036
1037 .. code-block:: bash
1038
Ales Komarekf8188332016-03-09 11:32:08 +01001039 salt '*' virtng.get_macs <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001040 '''
1041 macs = []
1042 doc = minidom.parse(_StringIO(get_xml(vm_)))
1043 for node in doc.getElementsByTagName('devices'):
1044 i_nodes = node.getElementsByTagName('interface')
1045 for i_node in i_nodes:
1046 for v_node in i_node.getElementsByTagName('mac'):
1047 macs.append(v_node.getAttribute('address'))
1048 return macs
1049
1050
1051def get_graphics(vm_):
1052 '''
1053 Returns the information on vnc for a given vm
1054
1055 CLI Example:
1056
1057 .. code-block:: bash
1058
Ales Komarekf8188332016-03-09 11:32:08 +01001059 salt '*' virtng.get_graphics <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001060 '''
1061 out = {'autoport': 'None',
1062 'keymap': 'None',
1063 'listen': 'None',
1064 'port': 'None',
1065 'type': 'vnc'}
1066 xml = get_xml(vm_)
1067 ssock = _StringIO(xml)
1068 doc = minidom.parse(ssock)
1069 for node in doc.getElementsByTagName('domain'):
1070 g_nodes = node.getElementsByTagName('graphics')
1071 for g_node in g_nodes:
1072 for key, value in g_node.attributes.items():
1073 out[key] = value
1074 return out
1075
1076
1077def get_disks(vm_):
1078 '''
1079 Return the disks of a named vm
1080
1081 CLI Example:
1082
1083 .. code-block:: bash
1084
Ales Komarekf8188332016-03-09 11:32:08 +01001085 salt '*' virtng.get_disks <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001086 '''
1087 disks = {}
1088 doc = minidom.parse(_StringIO(get_xml(vm_)))
1089 for elem in doc.getElementsByTagName('disk'):
1090 sources = elem.getElementsByTagName('source')
1091 targets = elem.getElementsByTagName('target')
1092 if len(sources) > 0:
1093 source = sources[0]
1094 else:
1095 continue
1096 if len(targets) > 0:
1097 target = targets[0]
1098 else:
1099 continue
1100 if target.hasAttribute('dev'):
1101 qemu_target = ''
1102 if source.hasAttribute('file'):
1103 qemu_target = source.getAttribute('file')
1104 elif source.hasAttribute('dev'):
1105 qemu_target = source.getAttribute('dev')
1106 elif source.hasAttribute('protocol') and \
1107 source.hasAttribute('name'): # For rbd network
1108 qemu_target = '{0}:{1}'.format(
1109 source.getAttribute('protocol'),
1110 source.getAttribute('name'))
1111 if qemu_target:
1112 disks[target.getAttribute('dev')] = {
1113 'file': qemu_target}
1114 for dev in disks:
1115 try:
1116 hypervisor = __salt__['config.get']('libvirt:hypervisor', 'kvm')
1117 if hypervisor not in ['qemu', 'kvm']:
1118 break
1119
1120 output = []
1121 qemu_output = subprocess.Popen(['qemu-img', 'info',
1122 disks[dev]['file']],
1123 shell=False,
1124 stdout=subprocess.PIPE).communicate()[0]
1125 snapshots = False
1126 columns = None
1127 lines = qemu_output.strip().split('\n')
1128 for line in lines:
1129 if line.startswith('Snapshot list:'):
1130 snapshots = True
1131 continue
1132
1133 # If this is a copy-on-write image, then the backing file
1134 # represents the base image
1135 #
1136 # backing file: base.qcow2 (actual path: /var/shared/base.qcow2)
1137 elif line.startswith('backing file'):
1138 matches = re.match(r'.*\(actual path: (.*?)\)', line)
1139 if matches:
1140 output.append('backing file: {0}'.format(matches.group(1)))
1141 continue
1142
1143 elif snapshots:
1144 if line.startswith('ID'): # Do not parse table headers
1145 line = line.replace('VM SIZE', 'VMSIZE')
1146 line = line.replace('VM CLOCK', 'TIME VMCLOCK')
1147 columns = re.split(r'\s+', line)
1148 columns = [c.lower() for c in columns]
1149 output.append('snapshots:')
1150 continue
1151 fields = re.split(r'\s+', line)
1152 for i, field in enumerate(fields):
1153 sep = ' '
1154 if i == 0:
1155 sep = '-'
1156 output.append(
1157 '{0} {1}: "{2}"'.format(
1158 sep, columns[i], field
1159 )
1160 )
1161 continue
1162 output.append(line)
1163 output = '\n'.join(output)
1164 disks[dev].update(yaml.safe_load(output))
1165 except TypeError:
1166 disks[dev].update(yaml.safe_load('image: Does not exist'))
1167 return disks
1168
1169
1170def setmem(vm_, memory, config=False):
1171 '''
1172 Changes the amount of memory allocated to VM. The VM must be shutdown
1173 for this to work.
1174
1175 memory is to be specified in MB
1176 If config is True then we ask libvirt to modify the config as well
1177
1178 CLI Example:
1179
1180 .. code-block:: bash
1181
Ales Komarekf8188332016-03-09 11:32:08 +01001182 salt '*' virtng.setmem myvm 768
smolaon1fb381d2016-03-09 11:10:58 +01001183 '''
1184 if vm_state(vm_) != 'shutdown':
1185 return False
1186
1187 dom = _get_dom(vm_)
1188
1189 # libvirt has a funny bitwise system for the flags in that the flag
1190 # to affect the "current" setting is 0, which means that to set the
1191 # current setting we have to call it a second time with just 0 set
1192 flags = libvirt.VIR_DOMAIN_MEM_MAXIMUM
1193 if config:
1194 flags = flags | libvirt.VIR_DOMAIN_AFFECT_CONFIG
1195
1196 ret1 = dom.setMemoryFlags(memory * 1024, flags)
1197 ret2 = dom.setMemoryFlags(memory * 1024, libvirt.VIR_DOMAIN_AFFECT_CURRENT)
1198
1199 # return True if both calls succeeded
1200 return ret1 == ret2 == 0
1201
1202
1203def setvcpus(vm_, vcpus, config=False):
1204 '''
1205 Changes the amount of vcpus allocated to VM. The VM must be shutdown
1206 for this to work.
1207
1208 vcpus is an int representing the number to be assigned
1209 If config is True then we ask libvirt to modify the config as well
1210
1211 CLI Example:
1212
1213 .. code-block:: bash
1214
Ales Komarekf8188332016-03-09 11:32:08 +01001215 salt '*' virtng.setvcpus myvm 2
smolaon1fb381d2016-03-09 11:10:58 +01001216 '''
1217 if vm_state(vm_) != 'shutdown':
1218 return False
1219
1220 dom = _get_dom(vm_)
1221
1222 # see notes in setmem
1223 flags = libvirt.VIR_DOMAIN_VCPU_MAXIMUM
1224 if config:
1225 flags = flags | libvirt.VIR_DOMAIN_AFFECT_CONFIG
1226
1227 ret1 = dom.setVcpusFlags(vcpus, flags)
1228 ret2 = dom.setVcpusFlags(vcpus, libvirt.VIR_DOMAIN_AFFECT_CURRENT)
1229
1230 return ret1 == ret2 == 0
1231
1232
1233def freemem():
1234 '''
1235 Return an int representing the amount of memory that has not been given
1236 to virtual machines on this node
1237
1238 CLI Example:
1239
1240 .. code-block:: bash
1241
Ales Komarekf8188332016-03-09 11:32:08 +01001242 salt '*' virtng.freemem
smolaon1fb381d2016-03-09 11:10:58 +01001243 '''
1244 conn = __get_conn()
1245 mem = conn.getInfo()[1]
1246 # Take off just enough to sustain the hypervisor
1247 mem -= 256
1248 for vm_ in list_vms():
1249 dom = _get_dom(vm_)
1250 if dom.ID() > 0:
1251 mem -= dom.info()[2] / 1024
1252 return mem
1253
1254
1255def freecpu():
1256 '''
1257 Return an int representing the number of unallocated cpus on this
1258 hypervisor
1259
1260 CLI Example:
1261
1262 .. code-block:: bash
1263
Ales Komarekf8188332016-03-09 11:32:08 +01001264 salt '*' virtng.freecpu
smolaon1fb381d2016-03-09 11:10:58 +01001265 '''
1266 conn = __get_conn()
1267 cpus = conn.getInfo()[2]
1268 for vm_ in list_vms():
1269 dom = _get_dom(vm_)
1270 if dom.ID() > 0:
1271 cpus -= dom.info()[3]
1272 return cpus
1273
1274
1275def full_info():
1276 '''
1277 Return the node_info, vm_info and freemem
1278
1279 CLI Example:
1280
1281 .. code-block:: bash
1282
Ales Komarekf8188332016-03-09 11:32:08 +01001283 salt '*' virtng.full_info
smolaon1fb381d2016-03-09 11:10:58 +01001284 '''
1285 return {'freecpu': freecpu(),
1286 'freemem': freemem(),
1287 'node_info': node_info(),
1288 'vm_info': vm_info()}
1289
1290
1291def get_xml(vm_):
1292 '''
1293 Returns the XML for a given vm
1294
1295 CLI Example:
1296
1297 .. code-block:: bash
1298
Ales Komarekf8188332016-03-09 11:32:08 +01001299 salt '*' virtng.get_xml <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001300 '''
1301 dom = _get_dom(vm_)
1302 return dom.XMLDesc(0)
1303
1304
1305def get_profiles(hypervisor=None):
1306 '''
1307 Return the virt profiles for hypervisor.
1308
1309 Currently there are profiles for:
1310
1311 - nic
1312 - disk
1313
1314 CLI Example:
1315
1316 .. code-block:: bash
1317
Ales Komarekf8188332016-03-09 11:32:08 +01001318 salt '*' virtng.get_profiles
1319 salt '*' virtng.get_profiles hypervisor=esxi
smolaon1fb381d2016-03-09 11:10:58 +01001320 '''
1321 ret = {}
1322 if hypervisor:
1323 hypervisor = hypervisor
1324 else:
1325 hypervisor = __salt__['config.get']('libvirt:hypervisor', VIRT_DEFAULT_HYPER)
1326 virtconf = __salt__['config.get']('virt', {})
1327 for typ in ['disk', 'nic']:
1328 _func = getattr(sys.modules[__name__], '_{0}_profile'.format(typ))
1329 ret[typ] = {'default': _func('default', hypervisor)}
1330 if typ in virtconf:
1331 ret.setdefault(typ, {})
1332 for prf in virtconf[typ]:
1333 ret[typ][prf] = _func(prf, hypervisor)
1334 return ret
1335
1336
1337def shutdown(vm_):
1338 '''
1339 Send a soft shutdown signal to the named vm
1340
1341 CLI Example:
1342
1343 .. code-block:: bash
1344
Ales Komarekf8188332016-03-09 11:32:08 +01001345 salt '*' virtng.shutdown <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001346 '''
1347 dom = _get_dom(vm_)
1348 return dom.shutdown() == 0
1349
1350
1351def pause(vm_):
1352 '''
1353 Pause the named vm
1354
1355 CLI Example:
1356
1357 .. code-block:: bash
1358
Ales Komarekf8188332016-03-09 11:32:08 +01001359 salt '*' virtng.pause <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001360 '''
1361 dom = _get_dom(vm_)
1362 return dom.suspend() == 0
1363
1364
1365def resume(vm_):
1366 '''
1367 Resume the named vm
1368
1369 CLI Example:
1370
1371 .. code-block:: bash
1372
Ales Komarekf8188332016-03-09 11:32:08 +01001373 salt '*' virtng.resume <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001374 '''
1375 dom = _get_dom(vm_)
1376 return dom.resume() == 0
1377
1378
1379def create(vm_):
1380 '''
1381 Start a defined domain
1382
1383 CLI Example:
1384
1385 .. code-block:: bash
1386
Ales Komarekf8188332016-03-09 11:32:08 +01001387 salt '*' virtng.create <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001388 '''
1389 dom = _get_dom(vm_)
1390 return dom.create() == 0
1391
1392
1393def start(vm_):
1394 '''
1395 Alias for the obscurely named 'create' function
1396
1397 CLI Example:
1398
1399 .. code-block:: bash
1400
Ales Komarekf8188332016-03-09 11:32:08 +01001401 salt '*' virtng.start <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001402 '''
1403 return create(vm_)
1404
1405
1406def stop(vm_):
1407 '''
1408 Alias for the obscurely named 'destroy' function
1409
1410 CLI Example:
1411
1412 .. code-block:: bash
1413
Ales Komarekf8188332016-03-09 11:32:08 +01001414 salt '*' virtng.stop <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001415 '''
1416 return destroy(vm_)
1417
1418
1419def reboot(vm_):
1420 '''
1421 Reboot a domain via ACPI request
1422
1423 CLI Example:
1424
1425 .. code-block:: bash
1426
Ales Komarekf8188332016-03-09 11:32:08 +01001427 salt '*' virtng.reboot <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001428 '''
1429 dom = _get_dom(vm_)
1430
1431 # reboot has a few modes of operation, passing 0 in means the
1432 # hypervisor will pick the best method for rebooting
1433 return dom.reboot(0) == 0
1434
1435
1436def reset(vm_):
1437 '''
1438 Reset a VM by emulating the reset button on a physical machine
1439
1440 CLI Example:
1441
1442 .. code-block:: bash
1443
Ales Komarekf8188332016-03-09 11:32:08 +01001444 salt '*' virtng.reset <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001445 '''
1446 dom = _get_dom(vm_)
1447
1448 # reset takes a flag, like reboot, but it is not yet used
1449 # so we just pass in 0
1450 # see: http://libvirt.org/html/libvirt-libvirt.html#virDomainReset
1451 return dom.reset(0) == 0
1452
1453
1454def ctrl_alt_del(vm_):
1455 '''
1456 Sends CTRL+ALT+DEL to a VM
1457
1458 CLI Example:
1459
1460 .. code-block:: bash
1461
Ales Komarekf8188332016-03-09 11:32:08 +01001462 salt '*' virtng.ctrl_alt_del <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001463 '''
1464 dom = _get_dom(vm_)
1465 return dom.sendKey(0, 0, [29, 56, 111], 3, 0) == 0
1466
1467
1468def create_xml_str(xml):
1469 '''
1470 Start a domain based on the XML passed to the function
1471
1472 CLI Example:
1473
1474 .. code-block:: bash
1475
Ales Komarekf8188332016-03-09 11:32:08 +01001476 salt '*' virtng.create_xml_str <XML in string format>
smolaon1fb381d2016-03-09 11:10:58 +01001477 '''
1478 conn = __get_conn()
1479 return conn.createXML(xml, 0) is not None
1480
1481
1482def create_xml_path(path):
1483 '''
1484 Start a domain based on the XML-file path passed to the function
1485
1486 CLI Example:
1487
1488 .. code-block:: bash
1489
Ales Komarekf8188332016-03-09 11:32:08 +01001490 salt '*' virtng.create_xml_path <path to XML file on the node>
smolaon1fb381d2016-03-09 11:10:58 +01001491 '''
1492 if not os.path.isfile(path):
1493 return False
1494 return create_xml_str(salt.utils.fopen(path, 'r').read())
1495
1496
1497def define_xml_str(xml):
1498 '''
1499 Define a domain based on the XML passed to the function
1500
1501 CLI Example:
1502
1503 .. code-block:: bash
1504
Ales Komarekf8188332016-03-09 11:32:08 +01001505 salt '*' virtng.define_xml_str <XML in string format>
smolaon1fb381d2016-03-09 11:10:58 +01001506 '''
1507 conn = __get_conn()
1508 return conn.defineXML(xml) is not None
1509
1510
1511def define_xml_path(path):
1512 '''
1513 Define a domain based on the XML-file path passed to the function
1514
1515 CLI Example:
1516
1517 .. code-block:: bash
1518
Ales Komarekf8188332016-03-09 11:32:08 +01001519 salt '*' virtng.define_xml_path <path to XML file on the node>
smolaon1fb381d2016-03-09 11:10:58 +01001520
1521 '''
1522 if not os.path.isfile(path):
1523 return False
1524 return define_xml_str(salt.utils.fopen(path, 'r').read())
1525
1526
1527def define_vol_xml_str(xml):
1528 '''
1529 Define a volume based on the XML passed to the function
1530
1531 CLI Example:
1532
1533 .. code-block:: bash
1534
Ales Komarekf8188332016-03-09 11:32:08 +01001535 salt '*' virtng.define_vol_xml_str <XML in string format>
smolaon1fb381d2016-03-09 11:10:58 +01001536 '''
1537 poolname = __salt__['config.get']('libvirt:storagepool', 'default')
1538 conn = __get_conn()
1539 pool = conn.storagePoolLookupByName(str(poolname))
1540 return pool.createXML(xml, 0) is not None
1541
1542
1543def define_vol_xml_path(path):
1544 '''
1545 Define a volume based on the XML-file path passed to the function
1546
1547 CLI Example:
1548
1549 .. code-block:: bash
1550
Ales Komarekf8188332016-03-09 11:32:08 +01001551 salt '*' virtng.define_vol_xml_path <path to XML file on the node>
smolaon1fb381d2016-03-09 11:10:58 +01001552
1553 '''
1554 if not os.path.isfile(path):
1555 return False
1556 return define_vol_xml_str(salt.utils.fopen(path, 'r').read())
1557
1558
1559def migrate_non_shared(vm_, target, ssh=False):
1560 '''
1561 Attempt to execute non-shared storage "all" migration
1562
1563 CLI Example:
1564
1565 .. code-block:: bash
1566
Ales Komarekf8188332016-03-09 11:32:08 +01001567 salt '*' virtng.migrate_non_shared <vm name> <target hypervisor>
smolaon1fb381d2016-03-09 11:10:58 +01001568 '''
1569 cmd = _get_migrate_command() + ' --copy-storage-all ' + vm_\
1570 + _get_target(target, ssh)
1571
1572 return subprocess.Popen(cmd,
1573 shell=True,
1574 stdout=subprocess.PIPE).communicate()[0]
1575
1576
1577def migrate_non_shared_inc(vm_, target, ssh=False):
1578 '''
1579 Attempt to execute non-shared storage "all" migration
1580
1581 CLI Example:
1582
1583 .. code-block:: bash
1584
Ales Komarekf8188332016-03-09 11:32:08 +01001585 salt '*' virtng.migrate_non_shared_inc <vm name> <target hypervisor>
smolaon1fb381d2016-03-09 11:10:58 +01001586 '''
1587 cmd = _get_migrate_command() + ' --copy-storage-inc ' + vm_\
1588 + _get_target(target, ssh)
1589
1590 return subprocess.Popen(cmd,
1591 shell=True,
1592 stdout=subprocess.PIPE).communicate()[0]
1593
1594
1595def migrate(vm_, target, ssh=False):
1596 '''
1597 Shared storage migration
1598
1599 CLI Example:
1600
1601 .. code-block:: bash
1602
Ales Komarekf8188332016-03-09 11:32:08 +01001603 salt '*' virtng.migrate <vm name> <target hypervisor>
smolaon1fb381d2016-03-09 11:10:58 +01001604 '''
1605 cmd = _get_migrate_command() + ' ' + vm_\
1606 + _get_target(target, ssh)
1607
1608 return subprocess.Popen(cmd,
1609 shell=True,
1610 stdout=subprocess.PIPE).communicate()[0]
1611
1612
1613def seed_non_shared_migrate(disks, force=False):
1614 '''
1615 Non shared migration requires that the disks be present on the migration
1616 destination, pass the disks information via this function, to the
1617 migration destination before executing the migration.
1618
1619 CLI Example:
1620
1621 .. code-block:: bash
1622
Ales Komarekf8188332016-03-09 11:32:08 +01001623 salt '*' virtng.seed_non_shared_migrate <disks>
smolaon1fb381d2016-03-09 11:10:58 +01001624 '''
1625 for _, data in disks.items():
1626 fn_ = data['file']
1627 form = data['file format']
1628 size = data['virtual size'].split()[1][1:]
1629 if os.path.isfile(fn_) and not force:
1630 # the target exists, check to see if it is compatible
1631 pre = yaml.safe_load(subprocess.Popen('qemu-img info arch',
1632 shell=True,
1633 stdout=subprocess.PIPE).communicate()[0])
1634 if pre['file format'] != data['file format']\
1635 and pre['virtual size'] != data['virtual size']:
1636 return False
1637 if not os.path.isdir(os.path.dirname(fn_)):
1638 os.makedirs(os.path.dirname(fn_))
1639 if os.path.isfile(fn_):
1640 os.remove(fn_)
1641 cmd = 'qemu-img create -f ' + form + ' ' + fn_ + ' ' + size
1642 subprocess.call(cmd, shell=True)
1643 creds = _libvirt_creds()
1644 cmd = 'chown ' + creds['user'] + ':' + creds['group'] + ' ' + fn_
1645 subprocess.call(cmd, shell=True)
1646 return True
1647
1648
1649def set_autostart(vm_, state='on'):
1650 '''
1651 Set the autostart flag on a VM so that the VM will start with the host
1652 system on reboot.
1653
1654 CLI Example:
1655
1656 .. code-block:: bash
1657
1658 salt "*" virt.set_autostart <vm name> <on | off>
1659 '''
1660
1661 dom = _get_dom(vm_)
1662
1663 if state == 'on':
1664 return dom.setAutostart(1) == 0
1665
1666 elif state == 'off':
1667 return dom.setAutostart(0) == 0
1668
1669 else:
1670 # return False if state is set to something other then on or off
1671 return False
1672
1673
1674def destroy(vm_):
1675 '''
1676 Hard power down the virtual machine, this is equivalent to pulling the
1677 power
1678
1679 CLI Example:
1680
1681 .. code-block:: bash
1682
Ales Komarekf8188332016-03-09 11:32:08 +01001683 salt '*' virtng.destroy <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001684 '''
1685 dom = _get_dom(vm_)
1686 return dom.destroy() == 0
1687
1688
1689def undefine(vm_):
1690 '''
1691 Remove a defined vm, this does not purge the virtual machine image, and
1692 this only works if the vm is powered down
1693
1694 CLI Example:
1695
1696 .. code-block:: bash
1697
Ales Komarekf8188332016-03-09 11:32:08 +01001698 salt '*' virtng.undefine <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001699 '''
1700 dom = _get_dom(vm_)
Alexandru Avadanii00f187a2018-06-24 20:36:44 +02001701 if getattr(libvirt, 'VIR_DOMAIN_UNDEFINE_NVRAM', False):
1702 # This one is only in 1.2.8+
1703 return dom.undefineFlags(libvirt.VIR_DOMAIN_UNDEFINE_NVRAM) == 0
1704 else:
1705 return dom.undefine() == 0
smolaon1fb381d2016-03-09 11:10:58 +01001706
1707
1708def purge(vm_, dirs=False):
1709 '''
1710 Recursively destroy and delete a virtual machine, pass True for dir's to
1711 also delete the directories containing the virtual machine disk images -
1712 USE WITH EXTREME CAUTION!
1713
1714 CLI Example:
1715
1716 .. code-block:: bash
1717
Ales Komarekf8188332016-03-09 11:32:08 +01001718 salt '*' virtng.purge <vm name>
smolaon1fb381d2016-03-09 11:10:58 +01001719 '''
1720 disks = get_disks(vm_)
1721 try:
1722 if not destroy(vm_):
1723 return False
1724 except libvirt.libvirtError:
1725 # This is thrown if the machine is already shut down
1726 pass
1727 directories = set()
1728 for disk in disks:
1729 os.remove(disks[disk]['file'])
1730 directories.add(os.path.dirname(disks[disk]['file']))
1731 if dirs:
1732 for dir_ in directories:
1733 shutil.rmtree(dir_)
1734 undefine(vm_)
1735 return True
1736
1737
1738def virt_type():
1739 '''
1740 Returns the virtual machine type as a string
1741
1742 CLI Example:
1743
1744 .. code-block:: bash
1745
Ales Komarekf8188332016-03-09 11:32:08 +01001746 salt '*' virtng.virt_type
smolaon1fb381d2016-03-09 11:10:58 +01001747 '''
1748 return __grains__['virtual']
1749
1750
1751def is_kvm_hyper():
1752 '''
1753 Returns a bool whether or not this node is a KVM hypervisor
1754
1755 CLI Example:
1756
1757 .. code-block:: bash
1758
Ales Komarekf8188332016-03-09 11:32:08 +01001759 salt '*' virtng.is_kvm_hyper
smolaon1fb381d2016-03-09 11:10:58 +01001760 '''
1761 try:
1762 if 'kvm_' not in salt.utils.fopen('/proc/modules').read():
1763 return False
1764 except IOError:
1765 # No /proc/modules? Are we on Windows? Or Solaris?
1766 return False
Oleh Hryhorov7c5dfd32018-01-19 15:43:44 +02001767 return 'libvirtd' in __salt__['cmd.shell'](__grains__['ps'])
smolaon1fb381d2016-03-09 11:10:58 +01001768
1769
1770def is_xen_hyper():
1771 '''
1772 Returns a bool whether or not this node is a XEN hypervisor
1773
1774 CLI Example:
1775
1776 .. code-block:: bash
1777
Ales Komarekf8188332016-03-09 11:32:08 +01001778 salt '*' virtng.is_xen_hyper
smolaon1fb381d2016-03-09 11:10:58 +01001779 '''
1780 try:
1781 if __grains__['virtual_subtype'] != 'Xen Dom0':
1782 return False
1783 except KeyError:
1784 # virtual_subtype isn't set everywhere.
1785 return False
1786 try:
1787 if 'xen_' not in salt.utils.fopen('/proc/modules').read():
1788 return False
1789 except IOError:
1790 # No /proc/modules? Are we on Windows? Or Solaris?
1791 return False
Oleh Hryhorov7c5dfd32018-01-19 15:43:44 +02001792 return 'libvirtd' in __salt__['cmd.shell'](__grains__['ps'])
smolaon1fb381d2016-03-09 11:10:58 +01001793
1794
1795def is_hyper():
1796 '''
1797 Returns a bool whether or not this node is a hypervisor of any kind
1798
1799 CLI Example:
1800
1801 .. code-block:: bash
1802
Ales Komarekf8188332016-03-09 11:32:08 +01001803 salt '*' virtng.is_hyper
smolaon1fb381d2016-03-09 11:10:58 +01001804 '''
1805 try:
1806 import libvirt # pylint: disable=import-error
1807 except ImportError:
1808 # not a usable hypervisor without libvirt module
1809 return False
1810 return is_xen_hyper() or is_kvm_hyper()
1811
1812
1813def vm_cputime(vm_=None):
1814 '''
1815 Return cputime used by the vms on this hyper in a
1816 list of dicts:
1817
1818 .. code-block:: python
1819
1820 [
1821 'your-vm': {
1822 'cputime' <int>
1823 'cputime_percent' <int>
1824 },
1825 ...
1826 ]
1827
1828 If you pass a VM name in as an argument then it will return info
1829 for just the named VM, otherwise it will return all VMs.
1830
1831 CLI Example:
1832
1833 .. code-block:: bash
1834
Ales Komarekf8188332016-03-09 11:32:08 +01001835 salt '*' virtng.vm_cputime
smolaon1fb381d2016-03-09 11:10:58 +01001836 '''
1837 host_cpus = __get_conn().getInfo()[2]
1838
1839 def _info(vm_):
1840 dom = _get_dom(vm_)
1841 raw = dom.info()
1842 vcpus = int(raw[3])
1843 cputime = int(raw[4])
1844 cputime_percent = 0
1845 if cputime:
1846 # Divide by vcpus to always return a number between 0 and 100
1847 cputime_percent = (1.0e-7 * cputime / host_cpus) / vcpus
1848 return {
1849 'cputime': int(raw[4]),
1850 'cputime_percent': int('{0:.0f}'.format(cputime_percent))
1851 }
1852 info = {}
1853 if vm_:
1854 info[vm_] = _info(vm_)
1855 else:
1856 for vm_ in list_vms():
1857 info[vm_] = _info(vm_)
1858 return info
1859
1860
1861def vm_netstats(vm_=None):
1862 '''
1863 Return combined network counters used by the vms on this hyper in a
1864 list of dicts:
1865
1866 .. code-block:: python
1867
1868 [
1869 'your-vm': {
1870 'rx_bytes' : 0,
1871 'rx_packets' : 0,
1872 'rx_errs' : 0,
1873 'rx_drop' : 0,
1874 'tx_bytes' : 0,
1875 'tx_packets' : 0,
1876 'tx_errs' : 0,
1877 'tx_drop' : 0
1878 },
1879 ...
1880 ]
1881
1882 If you pass a VM name in as an argument then it will return info
1883 for just the named VM, otherwise it will return all VMs.
1884
1885 CLI Example:
1886
1887 .. code-block:: bash
1888
Ales Komarekf8188332016-03-09 11:32:08 +01001889 salt '*' virtng.vm_netstats
smolaon1fb381d2016-03-09 11:10:58 +01001890 '''
1891 def _info(vm_):
1892 dom = _get_dom(vm_)
1893 nics = get_nics(vm_)
1894 ret = {
1895 'rx_bytes': 0,
1896 'rx_packets': 0,
1897 'rx_errs': 0,
1898 'rx_drop': 0,
1899 'tx_bytes': 0,
1900 'tx_packets': 0,
1901 'tx_errs': 0,
1902 'tx_drop': 0
1903 }
1904 for attrs in six.itervalues(nics):
1905 if 'target' in attrs:
1906 dev = attrs['target']
1907 stats = dom.interfaceStats(dev)
1908 ret['rx_bytes'] += stats[0]
1909 ret['rx_packets'] += stats[1]
1910 ret['rx_errs'] += stats[2]
1911 ret['rx_drop'] += stats[3]
1912 ret['tx_bytes'] += stats[4]
1913 ret['tx_packets'] += stats[5]
1914 ret['tx_errs'] += stats[6]
1915 ret['tx_drop'] += stats[7]
1916
1917 return ret
1918 info = {}
1919 if vm_:
1920 info[vm_] = _info(vm_)
1921 else:
1922 for vm_ in list_vms():
1923 info[vm_] = _info(vm_)
1924 return info
1925
1926
1927def vm_diskstats(vm_=None):
1928 '''
1929 Return disk usage counters used by the vms on this hyper in a
1930 list of dicts:
1931
1932 .. code-block:: python
1933
1934 [
1935 'your-vm': {
1936 'rd_req' : 0,
1937 'rd_bytes' : 0,
1938 'wr_req' : 0,
1939 'wr_bytes' : 0,
1940 'errs' : 0
1941 },
1942 ...
1943 ]
1944
1945 If you pass a VM name in as an argument then it will return info
1946 for just the named VM, otherwise it will return all VMs.
1947
1948 CLI Example:
1949
1950 .. code-block:: bash
1951
Ales Komarekf8188332016-03-09 11:32:08 +01001952 salt '*' virtng.vm_blockstats
smolaon1fb381d2016-03-09 11:10:58 +01001953 '''
1954 def get_disk_devs(vm_):
1955 doc = minidom.parse(_StringIO(get_xml(vm_)))
1956 disks = []
1957 for elem in doc.getElementsByTagName('disk'):
1958 targets = elem.getElementsByTagName('target')
1959 target = targets[0]
1960 disks.append(target.getAttribute('dev'))
1961 return disks
1962
1963 def _info(vm_):
1964 dom = _get_dom(vm_)
1965 # Do not use get_disks, since it uses qemu-img and is very slow
1966 # and unsuitable for any sort of real time statistics
1967 disks = get_disk_devs(vm_)
1968 ret = {'rd_req': 0,
1969 'rd_bytes': 0,
1970 'wr_req': 0,
1971 'wr_bytes': 0,
1972 'errs': 0
1973 }
1974 for disk in disks:
1975 stats = dom.blockStats(disk)
1976 ret['rd_req'] += stats[0]
1977 ret['rd_bytes'] += stats[1]
1978 ret['wr_req'] += stats[2]
1979 ret['wr_bytes'] += stats[3]
1980 ret['errs'] += stats[4]
1981
1982 return ret
1983 info = {}
1984 if vm_:
1985 info[vm_] = _info(vm_)
1986 else:
1987 # Can not run function blockStats on inactive VMs
1988 for vm_ in list_active_vms():
1989 info[vm_] = _info(vm_)
1990 return info