add custom virt module
diff --git a/_modules/virt_ng.py b/_modules/virt_ng.py
new file mode 100644
index 0000000..4c55e22
--- /dev/null
+++ b/_modules/virt_ng.py
@@ -0,0 +1,1784 @@
+# -*- coding: utf-8 -*-
+'''
+Work with virtual machines managed by libvirt
+
+:depends: libvirt Python module
+'''
+# Special Thanks to Michael Dehann, many of the concepts, and a few structures
+# of his in the virt func module have been used
+
+# Import python libs
+from __future__ import absolute_import
+import os
+import re
+import sys
+import shutil
+import subprocess
+import string # pylint: disable=deprecated-module
+import logging
+
+# Import third party libs
+import yaml
+import jinja2
+import jinja2.exceptions
+import salt.ext.six as six
+from salt.ext.six.moves import StringIO as _StringIO # pylint: disable=import-error
+from xml.dom import minidom
+try:
+ import libvirt # pylint: disable=import-error
+ HAS_ALL_IMPORTS = True
+except ImportError:
+ HAS_ALL_IMPORTS = False
+
+# Import salt libs
+import salt.utils
+import salt.utils.files
+import salt.utils.templates
+import salt.utils.validate.net
+from salt.exceptions import CommandExecutionError, SaltInvocationError
+
+log = logging.getLogger(__name__)
+
+# Set up template environment
+JINJA = jinja2.Environment(
+ loader=jinja2.FileSystemLoader(
+ os.path.join(salt.utils.templates.TEMPLATE_DIRNAME, 'virt')
+ )
+)
+
+VIRT_STATE_NAME_MAP = {0: 'running',
+ 1: 'running',
+ 2: 'running',
+ 3: 'paused',
+ 4: 'shutdown',
+ 5: 'shutdown',
+ 6: 'crashed'}
+
+VIRT_DEFAULT_HYPER = 'kvm'
+
+
+def __virtual__():
+ if not HAS_ALL_IMPORTS:
+ return False
+ return 'virt'
+
+
+def __get_conn():
+ '''
+ Detects what type of dom this node is and attempts to connect to the
+ correct hypervisor via libvirt.
+ '''
+ # This has only been tested on kvm and xen, it needs to be expanded to
+ # support all vm layers supported by libvirt
+
+ def __esxi_uri():
+ '''
+ Connect to an ESXi host with a configuration like so:
+
+ .. code-block:: yaml
+
+ libvirt:
+ hypervisor: esxi
+ connection: esx01
+
+ The connection setting can either be an explicit libvirt URI,
+ or a libvirt URI alias as in this example. No, it cannot be
+ just a hostname.
+
+
+ Example libvirt `/etc/libvirt/libvirt.conf`:
+
+ .. code-block::
+
+ uri_aliases = [
+ "esx01=esx://10.1.1.101/?no_verify=1&auto_answer=1",
+ "esx02=esx://10.1.1.102/?no_verify=1&auto_answer=1",
+ ]
+
+ Reference:
+
+ - http://libvirt.org/drvesx.html#uriformat
+ - http://libvirt.org/uri.html#URI_config
+ '''
+ connection = __salt__['config.get']('libvirt:connection', 'esx')
+ return connection
+
+ def __esxi_auth():
+ '''
+ We rely on that the credentials is provided to libvirt through
+ its built in mechanisms.
+
+ Example libvirt `/etc/libvirt/auth.conf`:
+
+ .. code-block::
+
+ [credentials-myvirt]
+ username=user
+ password=secret
+
+ [auth-esx-10.1.1.101]
+ credentials=myvirt
+
+ [auth-esx-10.1.1.102]
+ credentials=myvirt
+
+ Reference:
+
+ - http://libvirt.org/auth.html#Auth_client_config
+ '''
+ return [[libvirt.VIR_CRED_EXTERNAL], lambda: 0, None]
+
+ if 'virt.connect' in __opts__:
+ conn_str = __opts__['virt.connect']
+ else:
+ conn_str = 'qemu:///system'
+
+ conn_func = {
+ 'esxi': [libvirt.openAuth, [__esxi_uri(),
+ __esxi_auth(),
+ 0]],
+ 'qemu': [libvirt.open, [conn_str]],
+ }
+
+ hypervisor = __salt__['config.get']('libvirt:hypervisor', 'qemu')
+
+ try:
+ conn = conn_func[hypervisor][0](*conn_func[hypervisor][1])
+ except Exception:
+ raise CommandExecutionError(
+ 'Sorry, {0} failed to open a connection to the hypervisor '
+ 'software at {1}'.format(
+ __grains__['fqdn'],
+ conn_func[hypervisor][1][0]
+ )
+ )
+ return conn
+
+
+def _get_dom(vm_):
+ '''
+ Return a domain object for the named vm
+ '''
+ conn = __get_conn()
+ if vm_ not in list_vms():
+ raise CommandExecutionError('The specified vm is not present')
+ return conn.lookupByName(vm_)
+
+
+def _libvirt_creds():
+ '''
+ Returns the user and group that the disk images should be owned by
+ '''
+ g_cmd = 'grep ^\\s*group /etc/libvirt/qemu.conf'
+ u_cmd = 'grep ^\\s*user /etc/libvirt/qemu.conf'
+ try:
+ group = subprocess.Popen(g_cmd,
+ shell=True,
+ stdout=subprocess.PIPE).communicate()[0].split('"')[1]
+ except IndexError:
+ group = 'root'
+ try:
+ user = subprocess.Popen(u_cmd,
+ shell=True,
+ stdout=subprocess.PIPE).communicate()[0].split('"')[1]
+ except IndexError:
+ user = 'root'
+ return {'user': user, 'group': group}
+
+
+def _get_migrate_command():
+ '''
+ Returns the command shared by the different migration types
+ '''
+ if __salt__['config.option']('virt.tunnel'):
+ return ('virsh migrate --p2p --tunnelled --live --persistent '
+ '--undefinesource ')
+ return 'virsh migrate --live --persistent --undefinesource '
+
+
+def _get_target(target, ssh):
+ proto = 'qemu'
+ if ssh:
+ proto += '+ssh'
+ return ' {0}://{1}/{2}'.format(proto, target, 'system')
+
+
+def _gen_xml(name,
+ cpu,
+ mem,
+ diskp,
+ nicp,
+ hypervisor,
+ **kwargs):
+ '''
+ Generate the XML string to define a libvirt vm
+ '''
+ hypervisor = 'vmware' if hypervisor == 'esxi' else hypervisor
+ mem = mem * 1024 # MB
+ context = {
+ 'hypervisor': hypervisor,
+ 'name': name,
+ 'cpu': str(cpu),
+ 'mem': str(mem),
+ }
+ if hypervisor in ['qemu', 'kvm']:
+ context['controller_model'] = False
+ elif hypervisor in ['esxi', 'vmware']:
+ # TODO: make bus and model parameterized, this works for 64-bit Linux
+ context['controller_model'] = 'lsilogic'
+
+ if 'boot_dev' in kwargs:
+ context['boot_dev'] = []
+ for dev in kwargs['boot_dev'].split():
+ context['boot_dev'].append(dev)
+ else:
+ context['boot_dev'] = ['hd']
+
+ if 'serial_type' in kwargs:
+ context['serial_type'] = kwargs['serial_type']
+ if 'serial_type' in context and context['serial_type'] == 'tcp':
+ if 'telnet_port' in kwargs:
+ context['telnet_port'] = kwargs['telnet_port']
+ else:
+ context['telnet_port'] = 23023 # FIXME: use random unused port
+ if 'serial_type' in context:
+ if 'console' in kwargs:
+ context['console'] = kwargs['console']
+ else:
+ context['console'] = True
+
+ context['disks'] = {}
+ for i, disk in enumerate(diskp):
+ for disk_name, args in disk.items():
+ context['disks'][disk_name] = {}
+ fn_ = '{0}.{1}'.format(disk_name, args['format'])
+ context['disks'][disk_name]['file_name'] = fn_
+ context['disks'][disk_name]['source_file'] = os.path.join(args['pool'],
+ name,
+ fn_)
+ if hypervisor in ['qemu', 'kvm']:
+ context['disks'][disk_name]['target_dev'] = 'vd{0}'.format(string.ascii_lowercase[i])
+ context['disks'][disk_name]['address'] = False
+ context['disks'][disk_name]['driver'] = True
+ elif hypervisor in ['esxi', 'vmware']:
+ context['disks'][disk_name]['target_dev'] = 'sd{0}'.format(string.ascii_lowercase[i])
+ context['disks'][disk_name]['address'] = True
+ context['disks'][disk_name]['driver'] = False
+ context['disks'][disk_name]['disk_bus'] = args['model']
+ context['disks'][disk_name]['type'] = args['format']
+ context['disks'][disk_name]['index'] = str(i)
+
+ context['nics'] = nicp
+
+ fn_ = 'libvirt_domain.jinja'
+ try:
+ template = JINJA.get_template(fn_)
+ except jinja2.exceptions.TemplateNotFound:
+ log.error('Could not load template {0}'.format(fn_))
+ return ''
+
+ return template.render(**context)
+
+
+def _gen_vol_xml(vmname,
+ diskname,
+ size,
+ hypervisor,
+ **kwargs):
+ '''
+ Generate the XML string to define a libvirt storage volume
+ '''
+ size = int(size) * 1024 # MB
+ disk_info = _get_image_info(hypervisor, vmname, **kwargs)
+ context = {
+ 'name': vmname,
+ 'filename': '{0}.{1}'.format(diskname, disk_info['disktype']),
+ 'volname': diskname,
+ 'disktype': disk_info['disktype'],
+ 'size': str(size),
+ 'pool': disk_info['pool'],
+ }
+ fn_ = 'libvirt_volume.jinja'
+ try:
+ template = JINJA.get_template(fn_)
+ except jinja2.exceptions.TemplateNotFound:
+ log.error('Could not load template {0}'.format(fn_))
+ return ''
+ return template.render(**context)
+
+
+def _qemu_image_info(path):
+ '''
+ Detect information for the image at path
+ '''
+ ret = {}
+ out = __salt__['cmd.run']('qemu-img info {0}'.format(path))
+
+ match_map = {'size': r'virtual size: \w+ \((\d+) byte[s]?\)',
+ 'format': r'file format: (\w+)'}
+
+ for info, search in match_map.items():
+ try:
+ ret[info] = re.search(search, out).group(1)
+ except AttributeError:
+ continue
+ return ret
+
+
+# TODO: this function is deprecated, should be replaced with
+# _qemu_image_info()
+def _image_type(vda):
+ '''
+ Detect what driver needs to be used for the given image
+ '''
+ out = __salt__['cmd.run']('qemu-img info {0}'.format(vda))
+ if 'file format: qcow2' in out:
+ return 'qcow2'
+ else:
+ return 'raw'
+
+
+# TODO: this function is deprecated, should be merged and replaced
+# with _disk_profile()
+def _get_image_info(hypervisor, name, **kwargs):
+ '''
+ Determine disk image info, such as filename, image format and
+ storage pool, based on which hypervisor is used
+ '''
+ ret = {}
+ if hypervisor in ['esxi', 'vmware']:
+ ret['disktype'] = 'vmdk'
+ ret['filename'] = '{0}{1}'.format(name, '.vmdk')
+ ret['pool'] = '[{0}] '.format(kwargs.get('pool', '0'))
+ elif hypervisor in ['kvm', 'qemu']:
+ ret['disktype'] = 'qcow2'
+ ret['filename'] = '{0}{1}'.format(name, '.qcow2')
+ ret['pool'] = __salt__['config.option']('virt.images')
+ return ret
+
+
+def _disk_profile(profile, hypervisor, **kwargs):
+ '''
+ Gather the disk profile from the config or apply the default based
+ on the active hypervisor
+
+ This is the ``default`` profile for KVM/QEMU, which can be
+ overridden in the configuration:
+
+ .. code-block:: yaml
+
+ virt:
+ disk:
+ default:
+ - system:
+ size: 8192
+ format: qcow2
+ model: virtio
+
+ The ``format`` and ``model`` parameters are optional, and will
+ default to whatever is best suitable for the active hypervisor.
+ '''
+ default = [
+ {'system':
+ {'size': '8192'}
+ }
+ ]
+ if hypervisor in ['esxi', 'vmware']:
+ overlay = {'format': 'vmdk',
+ 'model': 'scsi',
+ 'pool': '[{0}] '.format(kwargs.get('pool', '0'))
+ }
+ elif hypervisor in ['qemu', 'kvm']:
+ overlay = {'format': 'qcow2',
+ 'model': 'virtio',
+ 'pool': __salt__['config.option']('virt.images')
+ }
+ else:
+ overlay = {}
+
+ disklist = __salt__['config.get']('virt:disk', {}).get(profile, default)
+ for key, val in overlay.items():
+ for i, disks in enumerate(disklist):
+ for disk in disks:
+ if key not in disks[disk]:
+ disklist[i][disk][key] = val
+ return disklist
+
+
+def _nic_profile(profile_name, hypervisor, **kwargs):
+
+ default = [{'eth0': {}}]
+ vmware_overlay = {'type': 'bridge', 'source': 'DEFAULT', 'model': 'e1000'}
+ kvm_overlay = {'type': 'bridge', 'source': 'br0', 'model': 'virtio'}
+ overlays = {
+ 'kvm': kvm_overlay,
+ 'qemu': kvm_overlay,
+ 'esxi': vmware_overlay,
+ 'vmware': vmware_overlay,
+ }
+
+ # support old location
+ config_data = __salt__['config.option']('virt.nic', {}).get(
+ profile_name, None
+ )
+
+ if config_data is None:
+ config_data = __salt__['config.get']('virt:nic', {}).get(
+ profile_name, default
+ )
+
+ interfaces = []
+
+ def append_dict_profile_to_interface_list(profile_dict):
+ for interface_name, attributes in profile_dict.items():
+ attributes['name'] = interface_name
+ interfaces.append(attributes)
+
+ # old style dicts (top-level dicts)
+ #
+ # virt:
+ # nic:
+ # eth0:
+ # bridge: br0
+ # eth1:
+ # network: test_net
+ if isinstance(config_data, dict):
+ append_dict_profile_to_interface_list(config_data)
+
+ # new style lists (may contain dicts)
+ #
+ # virt:
+ # nic:
+ # - eth0:
+ # bridge: br0
+ # - eth1:
+ # network: test_net
+ #
+ # virt:
+ # nic:
+ # - name: eth0
+ # bridge: br0
+ # - name: eth1
+ # network: test_net
+ elif isinstance(config_data, list):
+ for interface in config_data:
+ if isinstance(interface, dict):
+ if len(interface) == 1:
+ append_dict_profile_to_interface_list(interface)
+ else:
+ interfaces.append(interface)
+
+ def _normalize_net_types(attributes):
+ '''
+ Guess which style of definition:
+
+ bridge: br0
+
+ or
+
+ network: net0
+
+ or
+
+ type: network
+ source: net0
+ '''
+ for type_ in ['bridge', 'network']:
+ if type_ in attributes:
+ attributes['type'] = type_
+ # we want to discard the original key
+ attributes['source'] = attributes.pop(type_)
+
+ attributes['type'] = attributes.get('type', None)
+ attributes['source'] = attributes.get('source', None)
+
+ def _apply_default_overlay(attributes):
+ for key, value in overlays[hypervisor].items():
+ if key not in attributes or not attributes[key]:
+ attributes[key] = value
+
+ def _assign_mac(attributes):
+ dmac = '{0}_mac'.format(attributes['name'])
+ if dmac in kwargs:
+ dmac = kwargs[dmac]
+ if salt.utils.validate.net.mac(dmac):
+ attributes['mac'] = dmac
+ else:
+ msg = 'Malformed MAC address: {0}'.format(dmac)
+ raise CommandExecutionError(msg)
+ else:
+ attributes['mac'] = salt.utils.gen_mac()
+
+ for interface in interfaces:
+ _normalize_net_types(interface)
+ _assign_mac(interface)
+ if hypervisor in overlays:
+ _apply_default_overlay(interface)
+
+ return interfaces
+
+
+def init(name,
+ cpu,
+ mem,
+ image=None,
+ nic='default',
+ hypervisor=VIRT_DEFAULT_HYPER,
+ start=True, # pylint: disable=redefined-outer-name
+ disk='default',
+ saltenv='base',
+ **kwargs):
+ '''
+ Initialize a new vm
+
+ CLI Example:
+
+ .. code-block:: bash
+
+ salt 'hypervisor' virt.init vm_name 4 512 salt://path/to/image.raw
+ salt 'hypervisor' virt.init vm_name 4 512 nic=profile disk=profile
+ '''
+ hypervisor = __salt__['config.get']('libvirt:hypervisor', hypervisor)
+
+ nicp = _nic_profile(nic, hypervisor, **kwargs)
+
+ diskp = None
+ seedable = False
+ if image: # with disk template image
+ # if image was used, assume only one disk, i.e. the
+ # 'default' disk profile
+ # TODO: make it possible to use disk profiles and use the
+ # template image as the system disk
+ #diskp = _disk_profile('default', hypervisor, **kwargs)
+ #new diskp TCP cloud
+ diskp = _disk_profile(disk, hypervisor, **kwargs)
+ # When using a disk profile extract the sole dict key of the first
+ # array element as the filename for disk
+ disk_name = next(diskp[0].iterkeys())
+ disk_type = diskp[0][disk_name]['format']
+ disk_file_name = '{0}.{1}'.format(disk_name, disk_type)
+ # disk size TCP cloud
+ disk_size = diskp[0][disk_name]['size']
+
+
+ if hypervisor in ['esxi', 'vmware']:
+ # TODO: we should be copying the image file onto the ESX host
+ raise SaltInvocationError('virt.init does not support image '
+ 'template template in conjunction '
+ 'with esxi hypervisor')
+ elif hypervisor in ['qemu', 'kvm']:
+ img_dir = __salt__['config.option']('virt.images')
+ img_dest = os.path.join(
+ img_dir,
+ name,
+ disk_file_name
+ )
+ img_dir = os.path.dirname(img_dest)
+ sfn = __salt__['cp.cache_file'](image, saltenv)
+ if not os.path.isdir(img_dir):
+ os.makedirs(img_dir)
+ try:
+ salt.utils.files.copyfile(sfn, img_dest)
+ mask = os.umask(0)
+ os.umask(mask)
+ # Apply umask and remove exec bit
+
+ # Resizing image TCP cloud
+ cmd = 'qemu-img resize ' + img_dest + ' ' + str(disk_size) + 'M'
+ subprocess.call(cmd, shell=True)
+
+ mode = (0o0777 ^ mask) & 0o0666
+ os.chmod(img_dest, mode)
+
+ except (IOError, OSError) as e:
+ raise CommandExecutionError('problem copying image. {0} - {1}'.format(image, e))
+
+ seedable = True
+ else:
+ log.error('unsupported hypervisor when handling disk image')
+
+ else:
+ # no disk template image specified, create disks based on disk profile
+ diskp = _disk_profile(disk, hypervisor, **kwargs)
+ if hypervisor in ['qemu', 'kvm']:
+ # TODO: we should be creating disks in the local filesystem with
+ # qemu-img
+ raise SaltInvocationError('virt.init does not support disk '
+ 'profiles in conjunction with '
+ 'qemu/kvm at this time, use image '
+ 'template instead')
+ else:
+ # assume libvirt manages disks for us
+ for disk in diskp:
+ for disk_name, args in disk.items():
+ xml = _gen_vol_xml(name,
+ disk_name,
+ args['size'],
+ hypervisor)
+ define_vol_xml_str(xml)
+
+ xml = _gen_xml(name, cpu, mem, diskp, nicp, hypervisor, **kwargs)
+ define_xml_str(xml)
+
+ if kwargs.get('seed') and seedable:
+ install = kwargs.get('install', True)
+ seed_cmd = kwargs.get('seed_cmd', 'seed.apply')
+
+ __salt__[seed_cmd](img_dest,
+ id_=name,
+ config=kwargs.get('config'),
+ install=install)
+ if start:
+ create(name)
+
+ return True
+
+
+def list_vms():
+ '''
+ Return a list of virtual machine names on the minion
+
+ CLI Example:
+
+ .. code-block:: bash
+
+ salt '*' virt.list_vms
+ '''
+ vms = []
+ vms.extend(list_active_vms())
+ vms.extend(list_inactive_vms())
+ return vms
+
+
+def list_active_vms():
+ '''
+ Return a list of names for active virtual machine on the minion
+
+ CLI Example:
+
+ .. code-block:: bash
+
+ salt '*' virt.list_active_vms
+ '''
+ conn = __get_conn()
+ vms = []
+ for id_ in conn.listDomainsID():
+ vms.append(conn.lookupByID(id_).name())
+ return vms
+
+
+def list_inactive_vms():
+ '''
+ Return a list of names for inactive virtual machine on the minion
+
+ CLI Example:
+
+ .. code-block:: bash
+
+ salt '*' virt.list_inactive_vms
+ '''
+ conn = __get_conn()
+ vms = []
+ for id_ in conn.listDefinedDomains():
+ vms.append(id_)
+ return vms
+
+
+def vm_info(vm_=None):
+ '''
+ Return detailed information about the vms on this hyper in a
+ list of dicts:
+
+ .. code-block:: python
+
+ [
+ 'your-vm': {
+ 'cpu': <int>,
+ 'maxMem': <int>,
+ 'mem': <int>,
+ 'state': '<state>',
+ 'cputime' <int>
+ },
+ ...
+ ]
+
+ If you pass a VM name in as an argument then it will return info
+ for just the named VM, otherwise it will return all VMs.
+
+ CLI Example:
+
+ .. code-block:: bash
+
+ salt '*' virt.vm_info
+ '''
+ def _info(vm_):
+ dom = _get_dom(vm_)
+ raw = dom.info()
+ return {'cpu': raw[3],
+ 'cputime': int(raw[4]),
+ 'disks': get_disks(vm_),
+ 'graphics': get_graphics(vm_),
+ 'nics': get_nics(vm_),
+ 'maxMem': int(raw[1]),
+ 'mem': int(raw[2]),
+ 'state': VIRT_STATE_NAME_MAP.get(raw[0], 'unknown')}
+ info = {}
+ if vm_:
+ info[vm_] = _info(vm_)
+ else:
+ for vm_ in list_vms():
+ info[vm_] = _info(vm_)
+ return info
+
+
+def vm_state(vm_=None):
+ '''
+ Return list of all the vms and their state.
+
+ If you pass a VM name in as an argument then it will return info
+ for just the named VM, otherwise it will return all VMs.
+
+ CLI Example:
+
+ .. code-block:: bash
+
+ salt '*' virt.vm_state <vm name>
+ '''
+ def _info(vm_):
+ state = ''
+ dom = _get_dom(vm_)
+ raw = dom.info()
+ state = VIRT_STATE_NAME_MAP.get(raw[0], 'unknown')
+ return state
+ info = {}
+ if vm_:
+ info[vm_] = _info(vm_)
+ else:
+ for vm_ in list_vms():
+ info[vm_] = _info(vm_)
+ return info
+
+
+def node_info():
+ '''
+ Return a dict with information about this node
+
+ CLI Example:
+
+ .. code-block:: bash
+
+ salt '*' virt.node_info
+ '''
+ conn = __get_conn()
+ raw = conn.getInfo()
+ info = {'cpucores': raw[6],
+ 'cpumhz': raw[3],
+ 'cpumodel': str(raw[0]),
+ 'cpus': raw[2],
+ 'cputhreads': raw[7],
+ 'numanodes': raw[4],
+ 'phymemory': raw[1],
+ 'sockets': raw[5]}
+ return info
+
+
+def get_nics(vm_):
+ '''
+ Return info about the network interfaces of a named vm
+
+ CLI Example:
+
+ .. code-block:: bash
+
+ salt '*' virt.get_nics <vm name>
+ '''
+ nics = {}
+ doc = minidom.parse(_StringIO(get_xml(vm_)))
+ for node in doc.getElementsByTagName('devices'):
+ i_nodes = node.getElementsByTagName('interface')
+ for i_node in i_nodes:
+ nic = {}
+ nic['type'] = i_node.getAttribute('type')
+ for v_node in i_node.getElementsByTagName('*'):
+ if v_node.tagName == 'mac':
+ nic['mac'] = v_node.getAttribute('address')
+ if v_node.tagName == 'model':
+ nic['model'] = v_node.getAttribute('type')
+ if v_node.tagName == 'target':
+ nic['target'] = v_node.getAttribute('dev')
+ # driver, source, and match can all have optional attributes
+ if re.match('(driver|source|address)', v_node.tagName):
+ temp = {}
+ for key, value in v_node.attributes.items():
+ temp[key] = value
+ nic[str(v_node.tagName)] = temp
+ # virtualport needs to be handled separately, to pick up the
+ # type attribute of the virtualport itself
+ if v_node.tagName == 'virtualport':
+ temp = {}
+ temp['type'] = v_node.getAttribute('type')
+ for key, value in v_node.attributes.items():
+ temp[key] = value
+ nic['virtualport'] = temp
+ if 'mac' not in nic:
+ continue
+ nics[nic['mac']] = nic
+ return nics
+
+
+def get_macs(vm_):
+ '''
+ Return a list off MAC addresses from the named vm
+
+ CLI Example:
+
+ .. code-block:: bash
+
+ salt '*' virt.get_macs <vm name>
+ '''
+ macs = []
+ doc = minidom.parse(_StringIO(get_xml(vm_)))
+ for node in doc.getElementsByTagName('devices'):
+ i_nodes = node.getElementsByTagName('interface')
+ for i_node in i_nodes:
+ for v_node in i_node.getElementsByTagName('mac'):
+ macs.append(v_node.getAttribute('address'))
+ return macs
+
+
+def get_graphics(vm_):
+ '''
+ Returns the information on vnc for a given vm
+
+ CLI Example:
+
+ .. code-block:: bash
+
+ salt '*' virt.get_graphics <vm name>
+ '''
+ out = {'autoport': 'None',
+ 'keymap': 'None',
+ 'listen': 'None',
+ 'port': 'None',
+ 'type': 'vnc'}
+ xml = get_xml(vm_)
+ ssock = _StringIO(xml)
+ doc = minidom.parse(ssock)
+ for node in doc.getElementsByTagName('domain'):
+ g_nodes = node.getElementsByTagName('graphics')
+ for g_node in g_nodes:
+ for key, value in g_node.attributes.items():
+ out[key] = value
+ return out
+
+
+def get_disks(vm_):
+ '''
+ Return the disks of a named vm
+
+ CLI Example:
+
+ .. code-block:: bash
+
+ salt '*' virt.get_disks <vm name>
+ '''
+ disks = {}
+ doc = minidom.parse(_StringIO(get_xml(vm_)))
+ for elem in doc.getElementsByTagName('disk'):
+ sources = elem.getElementsByTagName('source')
+ targets = elem.getElementsByTagName('target')
+ if len(sources) > 0:
+ source = sources[0]
+ else:
+ continue
+ if len(targets) > 0:
+ target = targets[0]
+ else:
+ continue
+ if target.hasAttribute('dev'):
+ qemu_target = ''
+ if source.hasAttribute('file'):
+ qemu_target = source.getAttribute('file')
+ elif source.hasAttribute('dev'):
+ qemu_target = source.getAttribute('dev')
+ elif source.hasAttribute('protocol') and \
+ source.hasAttribute('name'): # For rbd network
+ qemu_target = '{0}:{1}'.format(
+ source.getAttribute('protocol'),
+ source.getAttribute('name'))
+ if qemu_target:
+ disks[target.getAttribute('dev')] = {
+ 'file': qemu_target}
+ for dev in disks:
+ try:
+ hypervisor = __salt__['config.get']('libvirt:hypervisor', 'kvm')
+ if hypervisor not in ['qemu', 'kvm']:
+ break
+
+ output = []
+ qemu_output = subprocess.Popen(['qemu-img', 'info',
+ disks[dev]['file']],
+ shell=False,
+ stdout=subprocess.PIPE).communicate()[0]
+ snapshots = False
+ columns = None
+ lines = qemu_output.strip().split('\n')
+ for line in lines:
+ if line.startswith('Snapshot list:'):
+ snapshots = True
+ continue
+
+ # If this is a copy-on-write image, then the backing file
+ # represents the base image
+ #
+ # backing file: base.qcow2 (actual path: /var/shared/base.qcow2)
+ elif line.startswith('backing file'):
+ matches = re.match(r'.*\(actual path: (.*?)\)', line)
+ if matches:
+ output.append('backing file: {0}'.format(matches.group(1)))
+ continue
+
+ elif snapshots:
+ if line.startswith('ID'): # Do not parse table headers
+ line = line.replace('VM SIZE', 'VMSIZE')
+ line = line.replace('VM CLOCK', 'TIME VMCLOCK')
+ columns = re.split(r'\s+', line)
+ columns = [c.lower() for c in columns]
+ output.append('snapshots:')
+ continue
+ fields = re.split(r'\s+', line)
+ for i, field in enumerate(fields):
+ sep = ' '
+ if i == 0:
+ sep = '-'
+ output.append(
+ '{0} {1}: "{2}"'.format(
+ sep, columns[i], field
+ )
+ )
+ continue
+ output.append(line)
+ output = '\n'.join(output)
+ disks[dev].update(yaml.safe_load(output))
+ except TypeError:
+ disks[dev].update(yaml.safe_load('image: Does not exist'))
+ return disks
+
+
+def setmem(vm_, memory, config=False):
+ '''
+ Changes the amount of memory allocated to VM. The VM must be shutdown
+ for this to work.
+
+ memory is to be specified in MB
+ If config is True then we ask libvirt to modify the config as well
+
+ CLI Example:
+
+ .. code-block:: bash
+
+ salt '*' virt.setmem myvm 768
+ '''
+ if vm_state(vm_) != 'shutdown':
+ return False
+
+ dom = _get_dom(vm_)
+
+ # libvirt has a funny bitwise system for the flags in that the flag
+ # to affect the "current" setting is 0, which means that to set the
+ # current setting we have to call it a second time with just 0 set
+ flags = libvirt.VIR_DOMAIN_MEM_MAXIMUM
+ if config:
+ flags = flags | libvirt.VIR_DOMAIN_AFFECT_CONFIG
+
+ ret1 = dom.setMemoryFlags(memory * 1024, flags)
+ ret2 = dom.setMemoryFlags(memory * 1024, libvirt.VIR_DOMAIN_AFFECT_CURRENT)
+
+ # return True if both calls succeeded
+ return ret1 == ret2 == 0
+
+
+def setvcpus(vm_, vcpus, config=False):
+ '''
+ Changes the amount of vcpus allocated to VM. The VM must be shutdown
+ for this to work.
+
+ vcpus is an int representing the number to be assigned
+ If config is True then we ask libvirt to modify the config as well
+
+ CLI Example:
+
+ .. code-block:: bash
+
+ salt '*' virt.setvcpus myvm 2
+ '''
+ if vm_state(vm_) != 'shutdown':
+ return False
+
+ dom = _get_dom(vm_)
+
+ # see notes in setmem
+ flags = libvirt.VIR_DOMAIN_VCPU_MAXIMUM
+ if config:
+ flags = flags | libvirt.VIR_DOMAIN_AFFECT_CONFIG
+
+ ret1 = dom.setVcpusFlags(vcpus, flags)
+ ret2 = dom.setVcpusFlags(vcpus, libvirt.VIR_DOMAIN_AFFECT_CURRENT)
+
+ return ret1 == ret2 == 0
+
+
+def freemem():
+ '''
+ Return an int representing the amount of memory that has not been given
+ to virtual machines on this node
+
+ CLI Example:
+
+ .. code-block:: bash
+
+ salt '*' virt.freemem
+ '''
+ conn = __get_conn()
+ mem = conn.getInfo()[1]
+ # Take off just enough to sustain the hypervisor
+ mem -= 256
+ for vm_ in list_vms():
+ dom = _get_dom(vm_)
+ if dom.ID() > 0:
+ mem -= dom.info()[2] / 1024
+ return mem
+
+
+def freecpu():
+ '''
+ Return an int representing the number of unallocated cpus on this
+ hypervisor
+
+ CLI Example:
+
+ .. code-block:: bash
+
+ salt '*' virt.freecpu
+ '''
+ conn = __get_conn()
+ cpus = conn.getInfo()[2]
+ for vm_ in list_vms():
+ dom = _get_dom(vm_)
+ if dom.ID() > 0:
+ cpus -= dom.info()[3]
+ return cpus
+
+
+def full_info():
+ '''
+ Return the node_info, vm_info and freemem
+
+ CLI Example:
+
+ .. code-block:: bash
+
+ salt '*' virt.full_info
+ '''
+ return {'freecpu': freecpu(),
+ 'freemem': freemem(),
+ 'node_info': node_info(),
+ 'vm_info': vm_info()}
+
+
+def get_xml(vm_):
+ '''
+ Returns the XML for a given vm
+
+ CLI Example:
+
+ .. code-block:: bash
+
+ salt '*' virt.get_xml <vm name>
+ '''
+ dom = _get_dom(vm_)
+ return dom.XMLDesc(0)
+
+
+def get_profiles(hypervisor=None):
+ '''
+ Return the virt profiles for hypervisor.
+
+ Currently there are profiles for:
+
+ - nic
+ - disk
+
+ CLI Example:
+
+ .. code-block:: bash
+
+ salt '*' virt.get_profiles
+ salt '*' virt.get_profiles hypervisor=esxi
+ '''
+ ret = {}
+ if hypervisor:
+ hypervisor = hypervisor
+ else:
+ hypervisor = __salt__['config.get']('libvirt:hypervisor', VIRT_DEFAULT_HYPER)
+ virtconf = __salt__['config.get']('virt', {})
+ for typ in ['disk', 'nic']:
+ _func = getattr(sys.modules[__name__], '_{0}_profile'.format(typ))
+ ret[typ] = {'default': _func('default', hypervisor)}
+ if typ in virtconf:
+ ret.setdefault(typ, {})
+ for prf in virtconf[typ]:
+ ret[typ][prf] = _func(prf, hypervisor)
+ return ret
+
+
+def shutdown(vm_):
+ '''
+ Send a soft shutdown signal to the named vm
+
+ CLI Example:
+
+ .. code-block:: bash
+
+ salt '*' virt.shutdown <vm name>
+ '''
+ dom = _get_dom(vm_)
+ return dom.shutdown() == 0
+
+
+def pause(vm_):
+ '''
+ Pause the named vm
+
+ CLI Example:
+
+ .. code-block:: bash
+
+ salt '*' virt.pause <vm name>
+ '''
+ dom = _get_dom(vm_)
+ return dom.suspend() == 0
+
+
+def resume(vm_):
+ '''
+ Resume the named vm
+
+ CLI Example:
+
+ .. code-block:: bash
+
+ salt '*' virt.resume <vm name>
+ '''
+ dom = _get_dom(vm_)
+ return dom.resume() == 0
+
+
+def create(vm_):
+ '''
+ Start a defined domain
+
+ CLI Example:
+
+ .. code-block:: bash
+
+ salt '*' virt.create <vm name>
+ '''
+ dom = _get_dom(vm_)
+ return dom.create() == 0
+
+
+def start(vm_):
+ '''
+ Alias for the obscurely named 'create' function
+
+ CLI Example:
+
+ .. code-block:: bash
+
+ salt '*' virt.start <vm name>
+ '''
+ return create(vm_)
+
+
+def stop(vm_):
+ '''
+ Alias for the obscurely named 'destroy' function
+
+ CLI Example:
+
+ .. code-block:: bash
+
+ salt '*' virt.stop <vm name>
+ '''
+ return destroy(vm_)
+
+
+def reboot(vm_):
+ '''
+ Reboot a domain via ACPI request
+
+ CLI Example:
+
+ .. code-block:: bash
+
+ salt '*' virt.reboot <vm name>
+ '''
+ dom = _get_dom(vm_)
+
+ # reboot has a few modes of operation, passing 0 in means the
+ # hypervisor will pick the best method for rebooting
+ return dom.reboot(0) == 0
+
+
+def reset(vm_):
+ '''
+ Reset a VM by emulating the reset button on a physical machine
+
+ CLI Example:
+
+ .. code-block:: bash
+
+ salt '*' virt.reset <vm name>
+ '''
+ dom = _get_dom(vm_)
+
+ # reset takes a flag, like reboot, but it is not yet used
+ # so we just pass in 0
+ # see: http://libvirt.org/html/libvirt-libvirt.html#virDomainReset
+ return dom.reset(0) == 0
+
+
+def ctrl_alt_del(vm_):
+ '''
+ Sends CTRL+ALT+DEL to a VM
+
+ CLI Example:
+
+ .. code-block:: bash
+
+ salt '*' virt.ctrl_alt_del <vm name>
+ '''
+ dom = _get_dom(vm_)
+ return dom.sendKey(0, 0, [29, 56, 111], 3, 0) == 0
+
+
+def create_xml_str(xml):
+ '''
+ Start a domain based on the XML passed to the function
+
+ CLI Example:
+
+ .. code-block:: bash
+
+ salt '*' virt.create_xml_str <XML in string format>
+ '''
+ conn = __get_conn()
+ return conn.createXML(xml, 0) is not None
+
+
+def create_xml_path(path):
+ '''
+ Start a domain based on the XML-file path passed to the function
+
+ CLI Example:
+
+ .. code-block:: bash
+
+ salt '*' virt.create_xml_path <path to XML file on the node>
+ '''
+ if not os.path.isfile(path):
+ return False
+ return create_xml_str(salt.utils.fopen(path, 'r').read())
+
+
+def define_xml_str(xml):
+ '''
+ Define a domain based on the XML passed to the function
+
+ CLI Example:
+
+ .. code-block:: bash
+
+ salt '*' virt.define_xml_str <XML in string format>
+ '''
+ conn = __get_conn()
+ return conn.defineXML(xml) is not None
+
+
+def define_xml_path(path):
+ '''
+ Define a domain based on the XML-file path passed to the function
+
+ CLI Example:
+
+ .. code-block:: bash
+
+ salt '*' virt.define_xml_path <path to XML file on the node>
+
+ '''
+ if not os.path.isfile(path):
+ return False
+ return define_xml_str(salt.utils.fopen(path, 'r').read())
+
+
+def define_vol_xml_str(xml):
+ '''
+ Define a volume based on the XML passed to the function
+
+ CLI Example:
+
+ .. code-block:: bash
+
+ salt '*' virt.define_vol_xml_str <XML in string format>
+ '''
+ poolname = __salt__['config.get']('libvirt:storagepool', 'default')
+ conn = __get_conn()
+ pool = conn.storagePoolLookupByName(str(poolname))
+ return pool.createXML(xml, 0) is not None
+
+
+def define_vol_xml_path(path):
+ '''
+ Define a volume based on the XML-file path passed to the function
+
+ CLI Example:
+
+ .. code-block:: bash
+
+ salt '*' virt.define_vol_xml_path <path to XML file on the node>
+
+ '''
+ if not os.path.isfile(path):
+ return False
+ return define_vol_xml_str(salt.utils.fopen(path, 'r').read())
+
+
+def migrate_non_shared(vm_, target, ssh=False):
+ '''
+ Attempt to execute non-shared storage "all" migration
+
+ CLI Example:
+
+ .. code-block:: bash
+
+ salt '*' virt.migrate_non_shared <vm name> <target hypervisor>
+ '''
+ cmd = _get_migrate_command() + ' --copy-storage-all ' + vm_\
+ + _get_target(target, ssh)
+
+ return subprocess.Popen(cmd,
+ shell=True,
+ stdout=subprocess.PIPE).communicate()[0]
+
+
+def migrate_non_shared_inc(vm_, target, ssh=False):
+ '''
+ Attempt to execute non-shared storage "all" migration
+
+ CLI Example:
+
+ .. code-block:: bash
+
+ salt '*' virt.migrate_non_shared_inc <vm name> <target hypervisor>
+ '''
+ cmd = _get_migrate_command() + ' --copy-storage-inc ' + vm_\
+ + _get_target(target, ssh)
+
+ return subprocess.Popen(cmd,
+ shell=True,
+ stdout=subprocess.PIPE).communicate()[0]
+
+
+def migrate(vm_, target, ssh=False):
+ '''
+ Shared storage migration
+
+ CLI Example:
+
+ .. code-block:: bash
+
+ salt '*' virt.migrate <vm name> <target hypervisor>
+ '''
+ cmd = _get_migrate_command() + ' ' + vm_\
+ + _get_target(target, ssh)
+
+ return subprocess.Popen(cmd,
+ shell=True,
+ stdout=subprocess.PIPE).communicate()[0]
+
+
+def seed_non_shared_migrate(disks, force=False):
+ '''
+ Non shared migration requires that the disks be present on the migration
+ destination, pass the disks information via this function, to the
+ migration destination before executing the migration.
+
+ CLI Example:
+
+ .. code-block:: bash
+
+ salt '*' virt.seed_non_shared_migrate <disks>
+ '''
+ for _, data in disks.items():
+ fn_ = data['file']
+ form = data['file format']
+ size = data['virtual size'].split()[1][1:]
+ if os.path.isfile(fn_) and not force:
+ # the target exists, check to see if it is compatible
+ pre = yaml.safe_load(subprocess.Popen('qemu-img info arch',
+ shell=True,
+ stdout=subprocess.PIPE).communicate()[0])
+ if pre['file format'] != data['file format']\
+ and pre['virtual size'] != data['virtual size']:
+ return False
+ if not os.path.isdir(os.path.dirname(fn_)):
+ os.makedirs(os.path.dirname(fn_))
+ if os.path.isfile(fn_):
+ os.remove(fn_)
+ cmd = 'qemu-img create -f ' + form + ' ' + fn_ + ' ' + size
+ subprocess.call(cmd, shell=True)
+ creds = _libvirt_creds()
+ cmd = 'chown ' + creds['user'] + ':' + creds['group'] + ' ' + fn_
+ subprocess.call(cmd, shell=True)
+ return True
+
+
+def set_autostart(vm_, state='on'):
+ '''
+ Set the autostart flag on a VM so that the VM will start with the host
+ system on reboot.
+
+ CLI Example:
+
+ .. code-block:: bash
+
+ salt "*" virt.set_autostart <vm name> <on | off>
+ '''
+
+ dom = _get_dom(vm_)
+
+ if state == 'on':
+ return dom.setAutostart(1) == 0
+
+ elif state == 'off':
+ return dom.setAutostart(0) == 0
+
+ else:
+ # return False if state is set to something other then on or off
+ return False
+
+
+def destroy(vm_):
+ '''
+ Hard power down the virtual machine, this is equivalent to pulling the
+ power
+
+ CLI Example:
+
+ .. code-block:: bash
+
+ salt '*' virt.destroy <vm name>
+ '''
+ dom = _get_dom(vm_)
+ return dom.destroy() == 0
+
+
+def undefine(vm_):
+ '''
+ Remove a defined vm, this does not purge the virtual machine image, and
+ this only works if the vm is powered down
+
+ CLI Example:
+
+ .. code-block:: bash
+
+ salt '*' virt.undefine <vm name>
+ '''
+ dom = _get_dom(vm_)
+ return dom.undefine() == 0
+
+
+def purge(vm_, dirs=False):
+ '''
+ Recursively destroy and delete a virtual machine, pass True for dir's to
+ also delete the directories containing the virtual machine disk images -
+ USE WITH EXTREME CAUTION!
+
+ CLI Example:
+
+ .. code-block:: bash
+
+ salt '*' virt.purge <vm name>
+ '''
+ disks = get_disks(vm_)
+ try:
+ if not destroy(vm_):
+ return False
+ except libvirt.libvirtError:
+ # This is thrown if the machine is already shut down
+ pass
+ directories = set()
+ for disk in disks:
+ os.remove(disks[disk]['file'])
+ directories.add(os.path.dirname(disks[disk]['file']))
+ if dirs:
+ for dir_ in directories:
+ shutil.rmtree(dir_)
+ undefine(vm_)
+ return True
+
+
+def virt_type():
+ '''
+ Returns the virtual machine type as a string
+
+ CLI Example:
+
+ .. code-block:: bash
+
+ salt '*' virt.virt_type
+ '''
+ return __grains__['virtual']
+
+
+def is_kvm_hyper():
+ '''
+ Returns a bool whether or not this node is a KVM hypervisor
+
+ CLI Example:
+
+ .. code-block:: bash
+
+ salt '*' virt.is_kvm_hyper
+ '''
+ try:
+ if 'kvm_' not in salt.utils.fopen('/proc/modules').read():
+ return False
+ except IOError:
+ # No /proc/modules? Are we on Windows? Or Solaris?
+ return False
+ return 'libvirtd' in __salt__['cmd.run'](__grains__['ps'])
+
+
+def is_xen_hyper():
+ '''
+ Returns a bool whether or not this node is a XEN hypervisor
+
+ CLI Example:
+
+ .. code-block:: bash
+
+ salt '*' virt.is_xen_hyper
+ '''
+ try:
+ if __grains__['virtual_subtype'] != 'Xen Dom0':
+ return False
+ except KeyError:
+ # virtual_subtype isn't set everywhere.
+ return False
+ try:
+ if 'xen_' not in salt.utils.fopen('/proc/modules').read():
+ return False
+ except IOError:
+ # No /proc/modules? Are we on Windows? Or Solaris?
+ return False
+ return 'libvirtd' in __salt__['cmd.run'](__grains__['ps'])
+
+
+def is_hyper():
+ '''
+ Returns a bool whether or not this node is a hypervisor of any kind
+
+ CLI Example:
+
+ .. code-block:: bash
+
+ salt '*' virt.is_hyper
+ '''
+ try:
+ import libvirt # pylint: disable=import-error
+ except ImportError:
+ # not a usable hypervisor without libvirt module
+ return False
+ return is_xen_hyper() or is_kvm_hyper()
+
+
+def vm_cputime(vm_=None):
+ '''
+ Return cputime used by the vms on this hyper in a
+ list of dicts:
+
+ .. code-block:: python
+
+ [
+ 'your-vm': {
+ 'cputime' <int>
+ 'cputime_percent' <int>
+ },
+ ...
+ ]
+
+ If you pass a VM name in as an argument then it will return info
+ for just the named VM, otherwise it will return all VMs.
+
+ CLI Example:
+
+ .. code-block:: bash
+
+ salt '*' virt.vm_cputime
+ '''
+ host_cpus = __get_conn().getInfo()[2]
+
+ def _info(vm_):
+ dom = _get_dom(vm_)
+ raw = dom.info()
+ vcpus = int(raw[3])
+ cputime = int(raw[4])
+ cputime_percent = 0
+ if cputime:
+ # Divide by vcpus to always return a number between 0 and 100
+ cputime_percent = (1.0e-7 * cputime / host_cpus) / vcpus
+ return {
+ 'cputime': int(raw[4]),
+ 'cputime_percent': int('{0:.0f}'.format(cputime_percent))
+ }
+ info = {}
+ if vm_:
+ info[vm_] = _info(vm_)
+ else:
+ for vm_ in list_vms():
+ info[vm_] = _info(vm_)
+ return info
+
+
+def vm_netstats(vm_=None):
+ '''
+ Return combined network counters used by the vms on this hyper in a
+ list of dicts:
+
+ .. code-block:: python
+
+ [
+ 'your-vm': {
+ 'rx_bytes' : 0,
+ 'rx_packets' : 0,
+ 'rx_errs' : 0,
+ 'rx_drop' : 0,
+ 'tx_bytes' : 0,
+ 'tx_packets' : 0,
+ 'tx_errs' : 0,
+ 'tx_drop' : 0
+ },
+ ...
+ ]
+
+ If you pass a VM name in as an argument then it will return info
+ for just the named VM, otherwise it will return all VMs.
+
+ CLI Example:
+
+ .. code-block:: bash
+
+ salt '*' virt.vm_netstats
+ '''
+ def _info(vm_):
+ dom = _get_dom(vm_)
+ nics = get_nics(vm_)
+ ret = {
+ 'rx_bytes': 0,
+ 'rx_packets': 0,
+ 'rx_errs': 0,
+ 'rx_drop': 0,
+ 'tx_bytes': 0,
+ 'tx_packets': 0,
+ 'tx_errs': 0,
+ 'tx_drop': 0
+ }
+ for attrs in six.itervalues(nics):
+ if 'target' in attrs:
+ dev = attrs['target']
+ stats = dom.interfaceStats(dev)
+ ret['rx_bytes'] += stats[0]
+ ret['rx_packets'] += stats[1]
+ ret['rx_errs'] += stats[2]
+ ret['rx_drop'] += stats[3]
+ ret['tx_bytes'] += stats[4]
+ ret['tx_packets'] += stats[5]
+ ret['tx_errs'] += stats[6]
+ ret['tx_drop'] += stats[7]
+
+ return ret
+ info = {}
+ if vm_:
+ info[vm_] = _info(vm_)
+ else:
+ for vm_ in list_vms():
+ info[vm_] = _info(vm_)
+ return info
+
+
+def vm_diskstats(vm_=None):
+ '''
+ Return disk usage counters used by the vms on this hyper in a
+ list of dicts:
+
+ .. code-block:: python
+
+ [
+ 'your-vm': {
+ 'rd_req' : 0,
+ 'rd_bytes' : 0,
+ 'wr_req' : 0,
+ 'wr_bytes' : 0,
+ 'errs' : 0
+ },
+ ...
+ ]
+
+ If you pass a VM name in as an argument then it will return info
+ for just the named VM, otherwise it will return all VMs.
+
+ CLI Example:
+
+ .. code-block:: bash
+
+ salt '*' virt.vm_blockstats
+ '''
+ def get_disk_devs(vm_):
+ doc = minidom.parse(_StringIO(get_xml(vm_)))
+ disks = []
+ for elem in doc.getElementsByTagName('disk'):
+ targets = elem.getElementsByTagName('target')
+ target = targets[0]
+ disks.append(target.getAttribute('dev'))
+ return disks
+
+ def _info(vm_):
+ dom = _get_dom(vm_)
+ # Do not use get_disks, since it uses qemu-img and is very slow
+ # and unsuitable for any sort of real time statistics
+ disks = get_disk_devs(vm_)
+ ret = {'rd_req': 0,
+ 'rd_bytes': 0,
+ 'wr_req': 0,
+ 'wr_bytes': 0,
+ 'errs': 0
+ }
+ for disk in disks:
+ stats = dom.blockStats(disk)
+ ret['rd_req'] += stats[0]
+ ret['rd_bytes'] += stats[1]
+ ret['wr_req'] += stats[2]
+ ret['wr_bytes'] += stats[3]
+ ret['errs'] += stats[4]
+
+ return ret
+ info = {}
+ if vm_:
+ info[vm_] = _info(vm_)
+ else:
+ # Can not run function blockStats on inactive VMs
+ for vm_ in list_active_vms():
+ info[vm_] = _info(vm_)
+ return info