# Copyright (c) 2010 Citrix Systems, Inc.
# Copyright 2010 OpenStack Foundation
#
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
#    not use this file except in compliance with the License. You may obtain
#    a copy of the License at
#
#         http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing, software
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
#    License for the specific language governing permissions and limitations
#    under the License.

"""
A driver for XenServer or Xen Cloud Platform.

**Variable Naming Scheme**

- suffix "_ref" for opaque references
- suffix "_uuid" for UUIDs
- suffix "_rec" for record objects
"""

import math

from oslo_config import cfg
from oslo_log import log as logging
from oslo_serialization import jsonutils
from oslo_utils import units
from oslo_utils import versionutils
import six
import six.moves.urllib.parse as urlparse

from nova.i18n import _, _LE, _LW
from nova import objects
from nova.virt import driver
from nova.virt.xenapi.client import session
from nova.virt.xenapi import host
from nova.virt.xenapi import pool
from nova.virt.xenapi import vm_utils
from nova.virt.xenapi import vmops
from nova.virt.xenapi import volumeops

LOG = logging.getLogger(__name__)

xenapi_opts = [
    cfg.StrOpt('connection_url',
               help='URL for connection to XenServer/Xen Cloud Platform. '
                    'A special value of unix://local can be used to connect '
                    'to the local unix socket.  '
                    'Required if compute_driver=xenapi.XenAPIDriver'),
    cfg.StrOpt('connection_username',
               default='root',
               help='Username for connection to XenServer/Xen Cloud Platform. '
                    'Used only if compute_driver=xenapi.XenAPIDriver'),
    cfg.StrOpt('connection_password',
               help='Password for connection to XenServer/Xen Cloud Platform. '
                    'Used only if compute_driver=xenapi.XenAPIDriver',
               secret=True),
    cfg.FloatOpt('vhd_coalesce_poll_interval',
                 default=5.0,
                 help='The interval used for polling of coalescing vhds. '
                      'Used only if compute_driver=xenapi.XenAPIDriver'),
    cfg.BoolOpt('check_host',
                default=True,
                help='Ensure compute service is running on host XenAPI '
                     'connects to.'),
    cfg.IntOpt('vhd_coalesce_max_attempts',
               default=20,
               help='Max number of times to poll for VHD to coalesce. '
                    'Used only if compute_driver=xenapi.XenAPIDriver'),
    cfg.StrOpt('sr_base_path',
               default='/var/run/sr-mount',
               help='Base path to the storage repository'),
    cfg.StrOpt('target_host',
               help='The iSCSI Target Host'),
    cfg.StrOpt('target_port',
               default='3260',
               help='The iSCSI Target Port, default is port 3260'),
    cfg.StrOpt('iqn_prefix',
               default='iqn.2010-10.org.openstack',
               help='IQN Prefix'),
    # NOTE(sirp): This is a work-around for a bug in Ubuntu Maverick,
    # when we pull support for it, we should remove this
    cfg.BoolOpt('remap_vbd_dev',
                default=False,
                help='Used to enable the remapping of VBD dev '
                     '(Works around an issue in Ubuntu Maverick)'),
    cfg.StrOpt('remap_vbd_dev_prefix',
               default='sd',
               help='Specify prefix to remap VBD dev to '
                    '(ex. /dev/xvdb -> /dev/sdb)'),
    ]

CONF = cfg.CONF
CONF.register_opts(xenapi_opts, 'xenserver')
CONF.import_opt('host', 'nova.netconf')

OVERHEAD_BASE = 3
OVERHEAD_PER_MB = 0.00781
OVERHEAD_PER_VCPU = 1.5


class XenAPIDriver(driver.ComputeDriver):
    """A connection to XenServer or Xen Cloud Platform."""

    def __init__(self, virtapi, read_only=False):
        super(XenAPIDriver, self).__init__(virtapi)

        url = CONF.xenserver.connection_url
        username = CONF.xenserver.connection_username
        password = CONF.xenserver.connection_password
        if not url or password is None:
            raise Exception(_('Must specify connection_url, '
                              'connection_username (optionally), and '
                              'connection_password to use '
                              'compute_driver=xenapi.XenAPIDriver'))

        self._session = session.XenAPISession(url, username, password)
        self._volumeops = volumeops.VolumeOps(self._session)
        self._host_state = None
        self._host = host.Host(self._session, self.virtapi)
        self._vmops = vmops.VMOps(self._session, self.virtapi)
        self._initiator = None
        self._hypervisor_hostname = None
        self._pool = pool.ResourcePool(self._session, self.virtapi)

    @property
    def host_state(self):
        if not self._host_state:
            self._host_state = host.HostState(self._session)
        return self._host_state

    def init_host(self, host):
        if CONF.xenserver.check_host:
            vm_utils.ensure_correct_host(self._session)

        try:
            vm_utils.cleanup_attached_vdis(self._session)
        except Exception:
            LOG.exception(_LE('Failure while cleaning up attached VDIs'))

    def instance_exists(self, instance):
        """Checks existence of an instance on the host.

        :param instance: The instance to lookup

        Returns True if supplied instance exists on the host, False otherwise.

        NOTE(belliott): This is an override of the base method for
        efficiency.
        """
        return self._vmops.instance_exists(instance.name)

    def estimate_instance_overhead(self, instance_info):
        """Get virtualization overhead required to build an instance of the
        given flavor.

        :param instance_info: Instance/flavor to calculate overhead for.
        :returns: Overhead memory in MB.
        """

        # XenServer memory overhead is proportional to the size of the
        # VM.  Larger flavor VMs become more efficient with respect to
        # overhead.

        # interpolated formula to predict overhead required per vm.
        # based on data from:
        # https://wiki.openstack.org/wiki/XenServer/Overhead
        # Some padding is done to each value to fit all available VM data
        memory_mb = instance_info['memory_mb']
        vcpus = instance_info.get('vcpus', 1)
        overhead = ((memory_mb * OVERHEAD_PER_MB) + (vcpus * OVERHEAD_PER_VCPU)
                        + OVERHEAD_BASE)
        overhead = math.ceil(overhead)
        return {'memory_mb': overhead}

    def list_instances(self):
        """List VM instances."""
        return self._vmops.list_instances()

    def list_instance_uuids(self):
        """Get the list of nova instance uuids for VMs found on the
        hypervisor.
        """
        return self._vmops.list_instance_uuids()

    def spawn(self, context, instance, image_meta, injected_files,
              admin_password, network_info=None, block_device_info=None):
        """Create VM instance."""
        self._vmops.spawn(context, instance, image_meta, injected_files,
                          admin_password, network_info, block_device_info)

    def confirm_migration(self, migration, instance, network_info):
        """Confirms a resize, destroying the source VM."""
        # TODO(Vek): Need to pass context in for access to auth_token
        self._vmops.confirm_migration(migration, instance, network_info)

    def finish_revert_migration(self, context, instance, network_info,
                                block_device_info=None, power_on=True):
        """Finish reverting a resize."""
        # NOTE(vish): Xen currently does not use network info.
        self._vmops.finish_revert_migration(context, instance,
                                            block_device_info,
                                            power_on)

    def finish_migration(self, context, migration, instance, disk_info,
                         network_info, image_meta, resize_instance,
                         block_device_info=None, power_on=True):
        """Completes a resize, turning on the migrated instance."""
        self._vmops.finish_migration(context, migration, instance, disk_info,
                                     network_info, image_meta, resize_instance,
                                     block_device_info, power_on)

    def snapshot(self, context, instance, image_id, update_task_state):
        """Create snapshot from a running VM instance."""
        self._vmops.snapshot(context, instance, image_id, update_task_state)

    def post_interrupted_snapshot_cleanup(self, context, instance):
        """Cleans up any resources left after a failed snapshot."""
        self._vmops.post_interrupted_snapshot_cleanup(context, instance)

    def reboot(self, context, instance, network_info, reboot_type,
               block_device_info=None, bad_volumes_callback=None):
        """Reboot VM instance."""
        self._vmops.reboot(instance, reboot_type,
                           bad_volumes_callback=bad_volumes_callback)

    def set_admin_password(self, instance, new_pass):
        """Set the root/admin password on the VM instance."""