# -*- coding: utf-8 -*-

'''
XenServer Cloud Driver
======================

The XenServer driver is designed to work with a Citrix XenServer.

Requires XenServer SDK
(can be downloaded from https://www.citrix.com/downloads/xenserver/product-software/ )

Place a copy of the XenAPI.py in the Python site-packages folder.

:depends: XenAPI

Example provider configuration:

    .. code-block:: yaml

        # /etc/salt/cloud.providers.d/myxen.conf
        myxen:
          driver: xen
          url: http://10.0.0.120
          user: root
          password: p@ssw0rd

Example profile configuration:

    .. code-block:: yaml

        # /etc/salt/cloud.profiles.d/myxen.conf
        suse:
          provider: myxen
          user: root
          password: p@ssw0rd
          image: opensuseleap42_2-template
          storage_repo: 'Local storage'
          resource_pool: default_pool
          clone: True
          minion:
            master: 10.0.0.18
        sles:
          provider: myxen
          user: root
          clone: False
          image: sles12sp2-template
          deploy: False
        w2k12:
          provider: myxen
          image: w2k12svr-template
          clone: True
          userdata_file: /srv/salt/win/files/windows-firewall.ps1
          win_installer: /srv/salt/win/files/Salt-Minion-2016.11.3-AMD64-Setup.exe
          win_username: Administrator
          win_password: p@ssw0rd
          use_winrm: False
          ipv4_cidr: 10.0.0.215/24
          ipv4_gw: 10.0.0.1

'''

# Import python libs
from __future__ import absolute_import, print_function, unicode_literals
from datetime import datetime
import logging
import time

# Import salt libs
import salt.config as config
from salt.ext import six

# Import Salt-Cloud Libs
import salt.utils.cloud
from salt.exceptions import (
    SaltCloudSystemExit,
    SaltCloudException
)

# Get logging started
log = logging.getLogger(__name__)

try:
    import XenAPI

    HAS_XEN_API = True
except ImportError:
    HAS_XEN_API = False

__virtualname__ = 'xen'
cache = None


def __virtual__():
    '''
    Only load if Xen configuration and XEN SDK is found.
    '''
    if get_configured_provider() is False:
        return False
    if _get_dependencies() is False:
        return False

    global cache  # pylint: disable=global-statement,invalid-name
    cache = salt.cache.Cache(__opts__)

    return __virtualname__


def _get_dependencies():
    '''
    Warn if dependencies aren't met.

    Checks for the XenAPI.py module
    '''
    return config.check_driver_dependencies(
        __virtualname__,
        {'XenAPI': HAS_XEN_API}
    )


def get_configured_provider():
    '''
    Return the first configured instance.
    '''
    return config.is_provider_configured(
        __opts__,
        __active_provider_name__ or __virtualname__,
        ('url',)
    )


def _get_session():
    '''
    Get a connection to the XenServer host
    '''
    api_version = '1.0'
    originator = 'salt_cloud_{}_driver'.format(__virtualname__)
    url = config.get_cloud_config_value(
        'url',
        get_configured_provider(),
        __opts__,
        search_global=False
    )
    user = config.get_cloud_config_value(
        'user',
        get_configured_provider(),
        __opts__,
        search_global=False
    )
    password = config.get_cloud_config_value(
        'password',
        get_configured_provider(),
        __opts__,
        search_global=False
    )
    ignore_ssl = config.get_cloud_config_value(
        'ignore_ssl',
        get_configured_provider(),
        __opts__,
        default=False,
        search_global=False
    )
    try:
        session = XenAPI.Session(url, ignore_ssl=ignore_ssl)
        log.debug(
            'url: %s user: %s password: %s, originator: %s',
            url, user, 'XXX-pw-redacted-XXX', originator
        )
        session.xenapi.login_with_password(
            user, password, api_version, originator)
    except XenAPI.Failure as ex:
        pool_master_addr = six.text_type(ex.__dict__['details'][1])
        slash_parts = url.split('/')
        new_url = '/'.join(slash_parts[:2]) + '/' + pool_master_addr
        session = XenAPI.Session(new_url)
        log.debug(
            'session is -> url: %s user: %s password: %s, originator:%s',
            new_url, user, 'XXX-pw-redacted-XXX', originator
        )
        session.xenapi.login_with_password(
            user, password, api_version, originator)
    return session


def list_nodes():
    '''
    List virtual machines

      .. code-block:: bash

          salt-cloud -Q

    '''
    session = _get_session()
    vms = session.xenapi.VM.get_all_records()
    ret = {}
    for vm in vms:
        record = session.xenapi.VM.get_record(vm)
        if not record['is_a_template'] and not record['is_control_domain']:
            try:
                base_template_name = record['other_config']['base_template_name']
            except Exception:  # pylint: disable=broad-except
                base_template_name = None
                log.debug(
                    'VM %s, doesnt have base_template_name attribute',
                    record['name_label']
                )
            ret[record['name_label']] = {'id': record['uuid'],
                                         'image': base_template_name,
                                         'name': record['name_label'],
                                         'size': record['memory_dynamic_max'],
                                         'state': record['power_state'],
                                         'private_ips': get_vm_ip(record['name_label'], session),
                                         'public_ips': None}
    return ret


def get_vm_ip(name=None, session=None, call=None):
    '''
    Get the IP address of the VM

    .. code-block:: bash

        salt-cloud -a get_vm_ip xenvm01

    .. note:: Requires xen guest tools to be installed in VM

    '''
    if call == 'function':
        raise SaltCloudException(
            'This function must be called with -a or --action.'
        )
    if session is None:
        log.debug('New session being created')
        session = _get_session()
    vm = _get_vm(name, session=session)
    ret = None
    # -- try to get ip from vif
    vifs = session.xenapi.VM.get_VIFs(vm)
    if vifs is not None:
        for vif in vifs:
            if session.xenapi.VIF.get_ipv4_addresses(vif):
                cidr = session.xenapi.VIF.get_ipv4_addresses(vif).pop()
                ret, subnet = cidr.split('/')
                log.debug(
                    'VM vif returned for instance: %s ip: %s', name, ret)
                return ret
    # -- try to get ip from get tools metrics
    vgm = session.xenapi.VM.get_guest_metrics(vm)
    try:
        net = session.xenapi.VM_guest_metrics.get_networks(vgm)
        if "0/ip" in net.keys():
            log.debug(
                'VM guest metrics returned for instance: %s 0/ip: %s',
                name, net["0/ip"]
            )
            ret = net["0/ip"]
    # except Exception as ex:  # pylint: disable=broad-except
    except XenAPI.Failure:
        log.info('Could not get vm metrics at this time')
    return ret


def set_vm_ip(name=None,
              ipv4_cidr=None,
              ipv4_gw=None,
              session=None,
              call=None):
    '''
    Set the IP address on a virtual interface (vif)

    '''
    mode = 'static'
    # TODO: Need to add support for IPv6
    if call == 'function':
        raise SaltCloudException(
            'The function must be called with -a or --action.')

    log.debug(
        'Setting name: %s ipv4_cidr: %s ipv4_gw: %s mode: %s',
        name, ipv4_cidr, ipv4_gw, mode
    )
    if session is None:
        log.debug('New session being created')
        session = _get_session()
    vm = _get_vm(name, session)
    # -- try to get ip from vif
    # TODO: for now will take first interface
    #       addition consideration needed for
    #       multiple interface(vif) VMs
    vifs = session.xenapi.VM.get_VIFs(vm)
    if vifs is not None:
        log.debug('There are %s vifs.', len(vifs))
        for vif in vifs:
            record = session.xenapi.VIF.get_record(vif)
            log.debug(record)
            try:
                session.xenapi.VIF.configure_ipv4(
                    vif, mode, ipv4_cidr, ipv4_gw)
            except XenAPI.Failure:
                log.info('Static IP assignment could not be performed.')

    return True


def list_nodes_full(session=None):
    '''
    List full virtual machines

      .. code-block:: bash

          salt-cloud -F

    '''
    if session is None:
        session = _get_session()

    ret = {}
    vms = session.xenapi.VM.get_all()
    for vm in vms:
        record = session.xenapi.VM.get_record(vm)
        if not record['is_a_template'] and not record['is_control_domain']:
            # deal with cases where the VM doesn't have 'base_template_name' attribute
            try:
                base_template_name = record['other_config']['base_template_name']
            except Exception:  # pylint: disable=broad-except
                base_template_name = None
                log.debug(
                    'VM %s, doesnt have base_template_name attribute',
                    record['name_label']
                )
            vm_cfg = session.xenapi.VM.get_record(vm)
            vm_cfg['id'] = record['uuid']
            vm_cfg['name'] = record['name_label']
            vm_cfg['image'] = base_template_name
            vm_cfg['size'] = None
            vm_cfg['state'] = record['power_state']
            vm_cfg['private_ips'] = get_vm_ip(record['name_label'], session)
            vm_cfg['public_ips'] = None
            if 'snapshot_time' in vm_cfg.keys():
                del vm_cfg['snapshot_time']
            ret[record['name_label']] = vm_cfg

    provider = __active_provider_name__ or 'xen'
    if ':' in provider:
        comps = provider.split(':')
        provider = comps[0]
    log.debug('ret: %s', ret)
    log.debug('provider: %s', provider)
    log.debug('__opts__: %s', __opts__)
    __utils__['cloud.cache_node_list'](ret, provider, __opts__)
    return ret


def list_nodes_select(call=None):
    '''
    Perform a select query on Xen VM instances

    .. code-block:: bash

        salt-cloud -S

    '''
    return salt.utils.cloud.list_nodes_select(
        list_nodes_full(),
        __opts__['query.selection'],
        call,
    )


def vdi_list(call=None, kwargs=None):
    '''
    Return available Xen VDI images

    If this function is called with the ``-f`` or ``--function`` then
    it can return a list with minimal deatil using the ``terse=True`` keyword
    argument.

    .. code-block:: bash

        salt-cloud -f vdi_list myxen terse=True

    '''
    if call == 'action':
        raise SaltCloudException(
            'This function must be called with -f or --function.')
    log.debug('kwargs is %s', kwargs)
    if kwargs is not None:
        if 'terse' in kwargs:
            if kwargs['terse'] == 'True':
                terse = True
            else:
                terse = False
        else:
            terse = False
    else:
        kwargs = {}
        terse = False
    session = _get_session()
    vdis = session.xenapi.VDI.get_all()
    ret = {}
    for vdi in vdis:
        data = session.xenapi.VDI.get_record(vdi)
        log.debug(type(terse))
        if terse is True:
            ret[data.get('name_label')] = {
                'uuid': data.get('uuid'),
                'OpqueRef': vdi}
        else:
            data.update({'OpaqueRef': vdi})
            ret[data.get('name_label')] = data
    return ret


def avail_locations(session=None, call=None):
    '''
    Return available Xen locations (not implemented)

    .. code-block:: bash

        salt-cloud --list-locations myxen

    '''
    # TODO: need to figure out a good meaning of locations in Xen
    if call == 'action':
        raise SaltCloudException(
            'The avail_locations function must be called with -f or --function.'
        )
    return pool_list()


def avail_sizes(session=None, call=None):
    '''
    Return a list of Xen template definitions

    .. code-block:: bash

        salt-cloud --list-sizes myxen

    '''
    if call == 'action':
        raise SaltCloudException(
            'The avail_sizes function must be called with -f or --function.')
    return {'STATUS':
            'Sizes are build into templates. Consider running --list-images to see sizes'}


def template_list(call=None):
    '''
    Return available Xen template information.

    This returns the details of
    each template to show number cores, memory sizes, etc..

    .. code-block:: bash

       salt-cloud -f template_list myxen

    '''
    templates = {}
    session = _get_session()
    vms = session.xenapi.VM.get_all()
    for vm in vms:
        record = session.xenapi.VM.get_record(vm)
        if record['is_a_template']:
            templates[record['name_label']] = record
    return templates


def show_instance(name, session=None, call=None):
    '''
    Show information about a specific VM or template

        .. code-block:: bash

            salt-cloud -a show_instance xenvm01

    .. note:: memory is memory_dynamic_max

    '''
    if call == 'function':
        raise SaltCloudException(
            'The show_instnce function must be called with -a or --action.'
        )
    log.debug('show_instance-> name: %s session: %s', name, session)
    if session is None:
        session = _get_session()
    vm = _get_vm(name, session=session)
    record = session.xenapi.VM.get_record(vm)
    if not record['is_a_template'] and not record['is_control_domain']:
        try:
            base_template_name = record['other_config']['base_template_name']
        except Exception:  # pylint: disable=broad-except
            base_template_name = None
            log.debug(
                'VM %s, doesnt have base_template_name attribute',
                record['name_label']
            )
        ret = {'id': record['uuid'],
               'image': base_template_name,
               'name': record['name_label'],
               'size': record['memory_dynamic_max'],
               'state': record['power_state'],
               'private_ips': get_vm_ip(name, session),
               'public_ips': None}

        __utils__['cloud.cache_node'](
            ret,
            __active_provider_name__,
            __opts__
        )
    return ret


def _determine_resource_pool(session, vm_):
    '''
    Called by create() used to determine resource pool
    '''
    resource_pool = ''
    if 'resource_pool' in vm_.keys():
        resource_pool = _get_pool(vm_['resource_pool'], session)
    else:
        pool = session.xenapi.pool.get_all()
        if not pool:
            resource_pool = None
        else:
            first_pool = session.xenapi.pool.get_all()[0]
            resource_pool = first_pool
    pool_record = session.xenapi.pool.get_record(resource_pool)
    log.debug('resource pool: %s', pool_record['name_label'])
    return resource_pool


def _determine_storage_repo(session, resource_pool, vm_):
    '''
    Called by create() used to determine storage repo for create
    '''
    storage_repo = ''
    if 'storage_repo' in vm_.keys():
        storage_repo = _get_sr(vm_['storage_repo'], session)
    else:
        storage_repo = None
        if resource_pool:
            default_sr = session.xenapi.pool.get_default_SR(resource_pool)
            sr_record = session.xenapi.SR.get_record(default_sr)
            log.debug('storage repository: %s', sr_record['name_label'])
            storage_repo = default_sr
        else:
            storage_repo = None
    log.debug('storage repository: %s', storage_repo)
    return storage_repo


def create(vm_):
    '''
    Create a VM in Xen

    The configuration for this function is read from the profile settings.

    .. code-block:: bash

        salt-cloud -p some_profile xenvm01

    '''
    name = vm_['name']
    record = {}
    ret = {}

    # fire creating event
    __utils__['cloud.fire_event'](
        'event',
        'starting create',
        'salt/cloud/{0}/creating'.format(name),
        args={
            'name': name,
            'profile': vm_['profile'],
            'provider': vm_['driver'],
        },
        sock_dir=__opts__['sock_dir'],
        transport=__opts__['transport']
    )
    log.debug('Adding %s to cloud cache.', name)
    __utils__['cloud.cachedir_index_add'](
        vm_['name'], vm_['profile'], 'xen', vm_['driver']
    )

    # connect to xen
    session = _get_session()

    # determine resource pool
    resource_pool = _determine_resource_pool(session, vm_)

    # determine storage repo
    storage_repo = _determine_storage_repo(session, resource_pool, vm_)

    # build VM
    image = vm_.get('image')
    clone = vm_.get('clone')
    if clone is None:
        clone = True
    log.debug('Clone: %s ', clone)

    # fire event to read new vm properties (requesting)
    __utils__['cloud.fire_event'](
        'event',
        'requesting instance',
        'salt/cloud/{0}/requesting'.format(name),
        sock_dir=__opts__['sock_dir'],
        transport=__opts__['transport']
    )

    # create by cloning template
    if clone:
        _clone_vm(image, name, session)
    else:
        _copy_vm(image, name, session, storage_repo)

    # provision template to vm
    _provision_vm(name, session)
    vm = _get_vm(name, session)

    # start vm
    start(name, None, session)

    # get new VM
    vm = _get_vm(name, session)

    # wait for vm to report IP via guest tools
    _wait_for_ip(name, session)

    # set static IP if configured
    _set_static_ip(name, session, vm_)

    # if not deploying salt then exit
    deploy = vm_.get('deploy', True)
    log.debug('delopy is set to %s', deploy)
    if deploy:
        record = session.xenapi.VM.get_record(vm)
        if record is not None:
            _deploy_salt_minion(name, session, vm_)
    else:
        log.debug(
            'The Salt minion will not be installed, deploy: %s',
            vm_['deploy']
        )
    record = session.xenapi.VM.get_record(vm)
    ret = show_instance(name)
    ret.update({'extra': record})

    __utils__['cloud.fire_event'](
        'event',
        'created instance',
        'salt/cloud/{0}/created'.format(name),
        args={
            'name': name,
            'profile': vm_['profile'],
            'provider': vm_['driver'],
        },
        sock_dir=__opts__['sock_dir'],
        transport=__opts__['transport']
    )
    return ret


def _deploy_salt_minion(name, session, vm_):
    '''
    Deploy salt minion during create()
    '''
    # Get bootstrap values
    vm_['ssh_host'] = get_vm_ip(name, session)
    vm_['user'] = vm_.get('user', 'root')
    vm_['password'] = vm_.get('password', 'p@ssw0rd!')
    vm_['provider'] = vm_.get('provider', 'xen')
    log.debug('%s has IP of %s', name, vm_['ssh_host'])
    # Bootstrap Salt minion!
    if vm_['ssh_host'] is not None:
        log.info('Installing Salt minion on %s', name)
        boot_ret = __utils__['cloud.bootstrap'](vm_, __opts__)
        log.debug('boot return: %s', boot_ret)


def _set_static_ip(name, session, vm_):
    '''
    Set static IP during create() if defined
    '''
    ipv4_cidr = ''
    ipv4_gw = ''
    if 'ipv4_gw' in vm_.keys():
        log.debug('ipv4_gw is found in keys')
        ipv4_gw = vm_['ipv4_gw']
    if 'ipv4_cidr' in vm_.keys():
        log.debug('ipv4_cidr is found in keys')
        ipv4_cidr = vm_['ipv4_cidr']
        log.debug('attempting to set IP in instance')
        set_vm_ip(name, ipv4_cidr, ipv4_gw, session, None)


def _wait_for_ip(name, session):
    '''
    Wait for IP  to be available during create()
    '''
    start_time = datetime.now()
    status = None
    while status is None:
        status = get_vm_ip(name, session)
        if status is not None:
            # ignore APIPA address
            if status.startswith('169'):
                status = None
        check_time = datetime.now()
        delta = check_time - start_time
        log.debug(
            'Waited %s seconds for %s to report ip address...',
            delta.seconds, name
        )
        if delta.seconds > 180:
            log.warning('Timeout getting IP address')
            break
        time.sleep(5)


def _run_async_task(task=None, session=None):
    '''
    Run  XenAPI task in asynchronous mode to prevent timeouts
    '''
    if task is None or session is None:
        return None
    task_name = session.xenapi.task.get_name_label(task)
    log.debug('Running %s', task_name)
    while session.xenapi.task.get_status(task) == 'pending':
        progress = round(session.xenapi.task.get_progress(task), 2) * 100
        log.debug('Task progress %.2f%%', progress)
        time.sleep(1)
    log.debug('Cleaning up task %s', task_name)
    session.xenapi.task.destroy(task)


def _clone_vm(image=None, name=None, session=None):
    '''
    Create VM by cloning

    This is faster and should be used if source and target are
    in the same storage repository

    '''
    if session is None:
        session = _get_session()
    log.debug('Creating VM %s by cloning %s', name, image)
    source = _get_vm(image, session)
    task = session.xenapi.Async.VM.clone(source, name)
    _run_async_task(task, session)


def _copy_vm(template=None, name=None, session=None, sr=None):
    '''
    Create VM by copy

    This is slower and should be used if source and target are
    NOT in the same storage repository

    template = object reference
    name = string name of new VM
    session = object reference
    sr = object reference
    '''
    if session is None:
        session = _get_session()
    log.debug('Creating VM %s by copying %s', name, template)
    source = _get_vm(template, session)
    task = session.xenapi.Async.VM.copy(source, name, sr)
    _run_async_task(task, session)


def _provision_vm(name=None, session=None):
    '''
    Provision vm right after clone/copy
    '''
    if session is None:
        session = _get_session()
    log.info('Provisioning VM %s', name)
    vm = _get_vm(name, session)
    task = session.xenapi.Async.VM.provision(vm)
    _run_async_task(task, session)


def start(name, call=None, session=None):
    '''
    Start  a vm

    .. code-block:: bash

        salt-cloud -a start xenvm01

    '''
    if call == 'function':
        raise SaltCloudException(
            'The show_instnce function must be called with -a or --action.'
        )
    if session is None:
        session = _get_session()
    log.info('Starting VM %s', name)
    vm = _get_vm(name, session)
    task = session.xenapi.Async.VM.start(vm, False, True)
    _run_async_task(task, session)
    return show_instance(name)


def pause(name, call=None, session=None):
    '''
    Pause a vm

    .. code-block:: bash

        salt-cloud -a pause xenvm01

    '''
    if call == 'function':
        raise SaltCloudException(
            'The show_instnce function must be called with -a or --action.'
        )
    if session is None:
        session = _get_session()
    log.info('Pausing VM %s', name)
    vm = _get_vm(name, session)
    task = session.xenapi.Async.VM.pause(vm)
    _run_async_task(task, session)
    return show_instance(name)


def unpause(name, call=None, session=None):
    '''
    UnPause a vm

    .. code-block:: bash

        salt-cloud -a unpause xenvm01

    '''
    if call == 'function':
        raise SaltCloudException(
            'The show_instnce function must be called with -a or --action.'
        )
    if session is None:
        session = _get_session()
    log.info('Unpausing VM %s', name)
    vm = _get_vm(name, session)
    task = session.xenapi.Async.VM.unpause(vm)
    _run_async_task(task, session)
    return show_instance(name)


def suspend(name, call=None, session=None):
    '''
    Suspend a vm to disk

    .. code-block:: bash

        salt-cloud -a suspend xenvm01

    '''
    if call == 'function':
        raise SaltCloudException(
            'The show_instnce function must be called with -a or --action.'
        )
    if session is None:
        session = _get_session()
    log.info('Suspending VM %s', name)
    vm = _get_vm(name, session)
    task = session.xenapi.Async.VM.suspend(vm)
    _run_async_task(task, session)
    return show_instance(name)


def resume(name, call=None, session=None):
    '''
    Resume a vm from disk

    .. code-block:: bash

        salt-cloud -a resume xenvm01

    '''
    if call == 'function':
        raise SaltCloudException(
            'The show_instnce function must be called with -a or --action.'
        )
    if session is None:
        session = _get_session()
    log.info('Resuming VM %s', name)
    vm = _get_vm(name, session)
    task = session.xenapi.Async.VM.resume(vm, False, True)
    _run_async_task(task, session)
    return show_instance(name)


def stop(name, call=None, session=None):
    '''
    Stop a vm

    .. code-block:: bash

        salt-cloud -a stop xenvm01


    '''
    if call == 'function':
        raise SaltCloudException(
            'The show_instnce function must be called with -a or --action.'
        )
    return shutdown(name, call, session)


def shutdown(name, call=None, session=None):
    '''
    Shutdown  a vm

    .. code-block:: bash

        salt-cloud -a shutdown xenvm01

    '''
    if call == 'function':
        raise SaltCloudException(
            'The show_instnce function must be called with -a or --action.'
        )
    if session is None:
        session = _get_session()
    log.info('Starting VM %s', name)
    vm = _get_vm(name, session)
    task = session.xenapi.Async.VM.shutdown(vm)
    _run_async_task(task, session)
    return show_instance(name)


def reboot(name, call=None, session=None):
    '''
    Reboot a vm

    .. code-block:: bash

        salt-cloud -a reboot xenvm01

    '''
    if call == 'function':
        raise SaltCloudException(
            'The show_instnce function must be called with -a or --action.'
        )
    if session is None:
        session = _get_session()
    log.info('Starting VM %s', name)
    vm = _get_vm(name, session)
    power_state = session.xenapi.VM.get_power_state(vm)
    if power_state == 'Running':
        task = session.xenapi.Async.VM.clean_reboot(vm)
        _run_async_task(task, session)
        return show_instance(name)
    else:
        return '{} is not running to be rebooted'.format(name)


def _get_vm(name=None, session=None):
    '''
    Get XEN vm instance object reference
    '''
    if session is None:
        session = _get_session()
    vms = session.xenapi.VM.get_by_name_label(name)
    if len(vms) == 1:
        return vms[0]
    return None


def _get_sr(name=None, session=None):
    '''
    Get XEN sr (storage repo) object reference
    '''
    if session is None:
        session = _get_session()
    srs = session.xenapi.SR.get_by_name_label(name)
    if len(srs) == 1:
        return srs[0]
    return None


def _get_pool(name=None, session=None):
    '''
    Get XEN resource pool object reference
    '''
    if session is None:
        session = _get_session()
    pools = session.xenapi.pool.get_all()
    for pool in pools:
        pool_record = session.xenapi.pool.get_record(pool)
        if name in pool_record.get('name_label'):
            return pool
    return None


def destroy(name=None, call=None):
    '''
    Destroy Xen VM or template instance

    .. code-block:: bash

        salt-cloud -d xenvm01

    '''
    if call == 'function':
        raise SaltCloudSystemExit(
            'The destroy action must be called with -d, --destroy, '
            '-a or --action.'
        )
    ret = {}
    __utils__['cloud.fire_event'](
        'event',
        'destroying instance',
        'salt/cloud/{0}/destroying'.format(name),
        args={'name': name},
        sock_dir=__opts__['sock_dir'],
        transport=__opts__['transport']
    )
    session = _get_session()
    vm = _get_vm(name)
    if vm:
        # get vm
        record = session.xenapi.VM.get_record(vm)
        log.debug('power_state: %s', record['power_state'])
        # shut down
        if record['power_state'] != 'Halted':
            task = session.xenapi.Async.VM.hard_shutdown(vm)
            _run_async_task(task, session)

        # destroy disk (vdi) by reading vdb on vm
        ret['vbd'] = destroy_vm_vdis(name, session)
        # destroy vm
        task = session.xenapi.Async.VM.destroy(vm)
        _run_async_task(task, session)
        ret['destroyed'] = True
        __utils__['cloud.fire_event'](
            'event',
            'destroyed instance',
            'salt/cloud/{0}/destroyed'.format(name),
            args={'name': name},
            sock_dir=__opts__['sock_dir'],
            transport=__opts__['transport']
        )
        if __opts__.get('update_cachedir', False) is True:
            __utils__['cloud.delete_minion_cachedir'](
                name,
                __active_provider_name__.split(':')[0],
                __opts__
            )
        __utils__['cloud.cachedir_index_del'](name)
        return ret


def sr_list(call=None):
    '''
    Geta list of storage repositories

    .. code-block:: bash

        salt-cloud -f sr_list myxen

    '''
    if call != 'function':
        raise SaltCloudSystemExit(
            'This function must be called with -f, --function argument.'
        )
    ret = {}
    session = _get_session()
    srs = session.xenapi.SR.get_all()
    for sr in srs:
        sr_record = session.xenapi.SR.get_record(sr)
        ret[sr_record['name_label']] = sr_record
    return ret


def host_list(call=None):
    '''
    Get a list of Xen Servers

    .. code-block:: bash

        salt-cloud -f host_list myxen
    '''
    if call == 'action':
        raise SaltCloudSystemExit(
            'This function must be called with -f, --function argument.'
        )
    ret = {}
    session = _get_session()
    hosts = session.xenapi.host.get_all()
    for host in hosts:
        host_record = session.xenapi.host.get_record(host)
        ret[host_record['name_label']] = host_record
    return ret


def pool_list(call=None):
    '''
    Get a list of Resource Pools

    .. code-block:: bash

        salt-cloud -f pool_list myxen

    '''
    if call == 'action':
        raise SaltCloudSystemExit(
            'This function must be called with -f, --function argument.'
        )
    ret = {}
    session = _get_session()
    pools = session.xenapi.pool.get_all()
    for pool in pools:
        pool_record = session.xenapi.pool.get_record(pool)
        ret[pool_record['name_label']] = pool_record
    return ret


def pif_list(call=None):
    '''
    Get a list of Resource Pools

    .. code-block:: bash

        salt-cloud -f pool_list myxen
    '''
    if call != 'function':
        raise SaltCloudSystemExit(
            'This function must be called with -f, --function argument.'
        )
    ret = {}
    session = _get_session()
    pifs = session.xenapi.PIF.get_all()
    for pif in pifs:
        record = session.xenapi.PIF.get_record(pif)
        ret[record['uuid']] = record
    return ret


def vif_list(name, call=None, kwargs=None):
    '''
    Get a list of virtual network interfaces  on a VM

    **requires**: the name of the vm with the vbd definition

    .. code-block:: bash

        salt-cloud -a vif_list xenvm01

    '''
    if call == 'function':
        raise SaltCloudSystemExit(
            'This function must be called with -a, --action argument.'
        )
    if name is None:
        return 'A name kwarg is rquired'
    ret = {}
    data = {}
    session = _get_session()
    vm = _get_vm(name)
    vifs = session.xenapi.VM.get_VIFs(vm)
    if vifs is not None:
        x = 0
        for vif in vifs:
            vif_record = session.xenapi.VIF.get_record(vif)
            data['vif-{}'.format(x)] = vif_record
            x += 1
    ret[name] = data
    return ret


def vbd_list(name=None, call=None):
    '''
    Get a list of VBDs on a VM

    **requires**: the name of the vm with the vbd definition

    .. code-block:: bash

        salt-cloud -a vbd_list xenvm01

    '''
    if call == 'function':
        raise SaltCloudSystemExit(
            'This function must be called with -a, --action argument.'
        )
    if name is None:
        return 'A name kwarg is rquired'
    ret = {}
    data = {}
    session = _get_session()
    vms = session.xenapi.VM.get_by_name_label(name)
    if len(vms) == 1:
        vm = vms[0]
        vbds = session.xenapi.VM.get_VBDs(vm)
        if vbds is not None:
            x = 0
            for vbd in vbds:
                vbd_record = session.xenapi.VBD.get_record(vbd)
                data['vbd-{}'.format(x)] = vbd_record
                x += 1
    ret = data
    return ret


def avail_images(call=None):
    '''
    Get a list of images from Xen

    If called with the `--list-images` then it returns
    images with all details.

    .. code-block:: bash

        salt-cloud --list-images myxen

    '''
    if call == 'action':
        raise SaltCloudSystemExit(
            'This function must be called with -f, --function argument.'
        )
    return template_list()


def destroy_vm_vdis(name=None, session=None, call=None):
    '''
    Get virtual block devices on VM

    .. code-block:: bash

        salt-cloud -a destroy_vm_vdis  xenvm01

    '''
    if session is None:
        session = _get_session()
    ret = {}
    # get vm object
    vms = session.xenapi.VM.get_by_name_label(name)
    if len(vms) == 1:
        # read virtual block device (vdb)
        vbds = session.xenapi.VM.get_VBDs(vms[0])
        if vbds is not None:
            x = 0
            for vbd in vbds:
                vbd_record = session.xenapi.VBD.get_record(vbd)
                if vbd_record['VDI'] != 'OpaqueRef:NULL':
                    # read vdi on vdb
                    vdi_record = session.xenapi.VDI.get_record(
                        vbd_record['VDI'])
                    if 'iso' not in vdi_record['name_label']:
                        session.xenapi.VDI.destroy(vbd_record['VDI'])
                        ret['vdi-{}'.format(x)] = vdi_record['name_label']
                x += 1
    return ret


def destroy_template(name=None, call=None, kwargs=None):
    '''
    Destroy Xen VM or template instance

        .. code-block:: bash

            salt-cloud -f destroy_template myxen name=testvm2

    '''
    if call == 'action':
        raise SaltCloudSystemExit(
            'The destroy_template function must be called with  -f.'
        )
    if kwargs is None:
        kwargs = {}
    name = kwargs.get('name', None)
    session = _get_session()
    vms = session.xenapi.VM.get_all_records()
    ret = {}
    found = False
    for vm in vms:
        record = session.xenapi.VM.get_record(vm)
        if record['is_a_template']:
            if record['name_label'] == name:
                found = True
                # log.debug(record['name_label'])
                session.xenapi.VM.destroy(vm)
                ret[name] = {'status': 'destroyed'}
    if not found:
        ret[name] = {'status': 'not found'}
    return ret


def get_pv_args(name, session=None, call=None):
    '''
    Get PV arguments for a VM

    .. code-block:: bash

        salt-cloud -a get_pv_args xenvm01

    '''
    if call == 'function':
        raise SaltCloudException(
            'This function must be called with -a or --action.'
        )
    if session is None:
        log.debug('New session being created')
        session = _get_session()
    vm = _get_vm(name, session=session)
    pv_args = session.xenapi.VM.get_PV_args(vm)
    if pv_args:
        return pv_args
    return None


def set_pv_args(name, kwargs=None, session=None, call=None):
    '''
    Set PV arguments for a VM

    .. code-block:: bash

        salt-cloud -a set_pv_args xenvm01 pv_args="utf-8 graphical"

    '''
    if call == 'function':
        raise SaltCloudException(
            'This function must be called with -a or --action.'
        )
    if session is None:
        log.debug('New session being created')
        session = _get_session()
    vm = _get_vm(name, session=session)
    try:
        log.debug('Setting PV Args: %s', kwargs['pv_args'])
        session.xenapi.VM.set_PV_args(vm, str(kwargs['pv_args']))
    except KeyError:
        log.error('No pv_args parameter found.')
        return False
    except XenAPI.Failure:
        log.info('Setting PV Args failed.')
        return False
    return True
