|
- # -*- 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 copy
- 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 'virtng'
-
-
- 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')
- if 'img_dest' in kwargs:
- ret['pool'] = kwargs['img_dest']
- else:
- 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
-
- Example profile for KVM/QEMU with two disks, first is created
- from specified image, the second is empty:
-
- .. code-block:: yaml
-
- virt:
- disk:
- two_disks:
- - system:
- size: 8192
- format: qcow2
- model: virtio
- image: http://path/to/image.qcow2
- - lvm:
- size: 32768
- 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']:
- if 'img_dest' in kwargs:
- pool = kwargs['img_dest']
- else:
- pool = __salt__['config.option']('virt.images')
- overlay = {'format': 'qcow2', 'model': 'virtio', 'pool': pool}
- else:
- overlay = {}
-
- disklist = copy.deepcopy(__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 = _disk_profile(disk, hypervisor, **kwargs)
-
- if image:
- # Backward compatibility: if 'image' is specified in the VMs arguments
- # instead of a disk arguments. In this case, 'image' will be assigned
- # to the first disk for the VM.
- disk_name = next(diskp[0].iterkeys())
- if not diskp[0][disk_name].get('image', None):
- diskp[0][disk_name]['image'] = image
-
- # Create multiple disks, empty or from specified images.
- for disk in diskp:
- log.debug("Creating disk for VM [ {0} ]: {1}".format(name, disk))
-
- for disk_name, args in disk.items():
-
- if hypervisor in ['esxi', 'vmware']:
- if 'image' in args:
- # 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')
- else:
- # assume libvirt manages disks for us
- xml = _gen_vol_xml(name,
- disk_name,
- args['size'],
- hypervisor,
- **kwargs)
- define_vol_xml_str(xml)
-
- elif hypervisor in ['qemu', 'kvm']:
-
- disk_type = args['format']
- disk_file_name = '{0}.{1}'.format(disk_name, disk_type)
- # disk size TCP cloud
- disk_size = args['size']
-
- if 'img_dest' in kwargs:
- img_dir = kwargs['img_dest']
- else:
- 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)
- if not os.path.isdir(img_dir):
- os.makedirs(img_dir)
-
- if 'image' in args:
- # Create disk from specified image
- sfn = __salt__['cp.cache_file'](args['image'], saltenv)
- 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 while copying image. {0} - {1}'.format(args['image'], e))
-
- if kwargs.get('seed'):
- install = kwargs.get('install', True)
- seed_cmd = kwargs.get('seed_cmd', 'seedng.apply')
-
- __salt__[seed_cmd](img_dest,
- id_=name,
- config=kwargs.get('config'),
- install=install)
- else:
- # Create empty disk
- try:
- mask = os.umask(0)
- os.umask(mask)
- # Apply umask and remove exec bit
-
- # Create empty image
- cmd = 'qemu-img create -f ' + disk_type + ' ' + 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 while creating volume {0} - {1}'.format(img_dest, e))
-
- else:
- # Unknown hypervisor
- raise SaltInvocationError('Unsupported hypervisor when handling disk image: {0}'
- .format(hypervisor))
-
- xml = _gen_xml(name, cpu, mem, diskp, nicp, hypervisor, **kwargs)
- define_xml_str(xml)
-
- 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 '*' virtng.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 '*' virtng.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 '*' virtng.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 '*' virtng.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 '*' virtng.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 '*' virtng.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 '*' virtng.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 '*' virtng.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 '*' virtng.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 '*' virtng.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 '*' virtng.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 '*' virtng.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 '*' virtng.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 '*' virtng.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 '*' virtng.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 '*' virtng.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 '*' virtng.get_profiles
- salt '*' virtng.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 '*' virtng.shutdown <vm name>
- '''
- dom = _get_dom(vm_)
- return dom.shutdown() == 0
-
-
- def pause(vm_):
- '''
- Pause the named vm
-
- CLI Example:
-
- .. code-block:: bash
-
- salt '*' virtng.pause <vm name>
- '''
- dom = _get_dom(vm_)
- return dom.suspend() == 0
-
-
- def resume(vm_):
- '''
- Resume the named vm
-
- CLI Example:
-
- .. code-block:: bash
-
- salt '*' virtng.resume <vm name>
- '''
- dom = _get_dom(vm_)
- return dom.resume() == 0
-
-
- def create(vm_):
- '''
- Start a defined domain
-
- CLI Example:
-
- .. code-block:: bash
-
- salt '*' virtng.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 '*' virtng.start <vm name>
- '''
- return create(vm_)
-
-
- def stop(vm_):
- '''
- Alias for the obscurely named 'destroy' function
-
- CLI Example:
-
- .. code-block:: bash
-
- salt '*' virtng.stop <vm name>
- '''
- return destroy(vm_)
-
-
- def reboot(vm_):
- '''
- Reboot a domain via ACPI request
-
- CLI Example:
-
- .. code-block:: bash
-
- salt '*' virtng.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 '*' virtng.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 '*' virtng.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 '*' virtng.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 '*' virtng.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 '*' virtng.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 '*' virtng.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 '*' virtng.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 '*' virtng.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 '*' virtng.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 '*' virtng.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 '*' virtng.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 '*' virtng.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 '*' virtng.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 '*' virtng.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 '*' virtng.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 '*' virtng.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 '*' virtng.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 '*' virtng.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 '*' virtng.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 '*' virtng.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 '*' virtng.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 '*' virtng.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
|