# Copyright (c) 2011 Citrix Systems, Inc.
#
#    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.

from nova import exception
from nova import flags
from nova.openstack.common import log as logging
from nova import utils
from nova.virt.xenapi import connection as xenapi_conn
from nova.virt.xenapi import volumeops
import nova.volume.driver

LOG = logging.getLogger(__name__)
FLAGS = flags.FLAGS


class XenSMDriver(nova.volume.driver.VolumeDriver):

    def _convert_config_params(self, conf_str):
        params = dict([item.split("=") for item in conf_str.split()])
        return params

    def _get_introduce_sr_keys(self, params):
        if 'name_label' in params:
            del params['name_label']
        keys = params.keys()
        keys.append('sr_type')
        return keys

    def _create_storage_repo(self, context, backend_ref):
        """Either creates or introduces SR on host
        depending on whether it exists in xapi db."""
        params = self._convert_config_params(backend_ref['config_params'])
        if 'name_label' in params:
            label = params['name_label']
            del params['name_label']
        else:
            label = 'SR-' + str(backend_ref['id'])

        params['sr_type'] = backend_ref['sr_type']

        if backend_ref['sr_uuid'] is None:
            # run the sr create command
            try:
                LOG.debug(_('SR name = %s') % label)
                LOG.debug(_('Params: %s') % str(params))
                sr_uuid = self._volumeops.create_sr(label, params)
            # update sr_uuid and created in db
            except Exception as ex:
                LOG.debug(_("Failed to create sr %s...continuing") %
                          str(backend_ref['id']))
                raise exception.NovaException(_('Create failed'))

            LOG.debug(_('SR UUID of new SR is: %s') % sr_uuid)
            try:
                self.db.sm_backend_conf_update(context,
                                               backend_ref['id'],
                                               dict(sr_uuid=sr_uuid))
            except Exception as ex:
                LOG.exception(ex)
                raise exception.NovaException(_("Failed to update db"))

        else:
            # sr introduce, if not already done
            try:
                self._volumeops.introduce_sr(backend_ref['sr_uuid'], label,
                                              params)
            except Exception as ex:
                LOG.exception(ex)
                LOG.debug(_("Failed to introduce sr %s...continuing")
                          % str(backend_ref['id']))

    def _create_storage_repos(self, context):
        """Create/Introduce storage repositories at start."""
        backends = self.db.sm_backend_conf_get_all(context)
        for backend in backends:
            try:
                self._create_storage_repo(context, backend)
            except Exception as ex:
                LOG.exception(ex)
                raise exception.NovaException(_('Failed to reach backend %d')
                                      % backend['id'])

    def __init__(self, *args, **kwargs):
        """Connect to the hypervisor."""

        # This driver leverages Xen storage manager, and hence requires
        # hypervisor to be Xen
        if FLAGS.connection_type != 'xenapi':
            msg = _('XenSMDriver requires xenapi connection')
            raise exception.NovaException(msg)

        url = FLAGS.xenapi_connection_url
        username = FLAGS.xenapi_connection_username
        password = FLAGS.xenapi_connection_password
        try:
            session = xenapi_conn.XenAPISession(url, username, password)
            self._volumeops = volumeops.VolumeOps(session)
        except Exception as ex:
            LOG.exception(ex)
            raise exception.NovaException(_("Failed to initiate session"))

        super(XenSMDriver, self).__init__(execute=utils.execute,
                                          sync_exec=utils.execute,
                                          *args, **kwargs)

    def do_setup(self, ctxt):
        """Setup includes creating or introducing storage repos
           existing in the database and destroying deleted ones."""

        # TODO(renukaapte) purge storage repos
        self.ctxt = ctxt
        self._create_storage_repos(ctxt)

    def create_volume(self, volume):
        """Creates a logical volume. Can optionally return a Dictionary of
        changes to the volume object to be persisted."""

        # For now the scheduling logic will be to try to fit the volume in
        # the first available backend.
        # TODO(renukaapte) better scheduling once APIs are in place
        sm_vol_rec = None
        backends = self.db.sm_backend_conf_get_all(self.ctxt)
        for backend in backends:
            # Ensure that storage repo exists, if not create.
            # This needs to be done because if nova compute and
            # volume are both running on this host, then, as a
            # part of detach_volume, compute could potentially forget SR
            self._create_storage_repo(self.ctxt, backend)
            sm_vol_rec = self._volumeops.create_volume_for_sm(volume,
                                                  backend['sr_uuid'])
            if sm_vol_rec:
                LOG.debug(_('Volume will be created in backend - %d')
                          % backend['id'])
                break

        if sm_vol_rec:
            # Update db
            sm_vol_rec['id'] = volume['id']
            sm_vol_rec['backend_id'] = backend['id']
            try:
                self.db.sm_volume_create(self.ctxt, sm_vol_rec)
            except Exception as ex:
                LOG.exception(ex)
                msg = _("Failed to update volume in db")
                raise exception.NovaException(msg)

        else:
            raise exception.NovaException(_('Unable to create volume'))

    def delete_volume(self, volume):

        vol_rec = self.db.sm_volume_get(self.ctxt, volume['id'])
        if not vol_rec:
            raise exception.NotFound(_("Volume %s does not exist"),
                                     volume['id'])
        try:
            # If compute runs on this node, detach could have disconnected SR
            backend_ref = self.db.sm_backend_conf_get(self.ctxt,
                                                      vol_rec['backend_id'])
            self._create_storage_repo(self.ctxt, backend_ref)
            self._volumeops.delete_volume_for_sm(vol_rec['vdi_uuid'])
        except Exception as ex:
            LOG.exception(ex)
            raise exception.NovaException(_("Failed to delete vdi"))

        try:
            self.db.sm_volume_delete(self.ctxt, volume['id'])
        except Exception as ex:
            LOG.exception(ex)
            raise exception.NovaException(_("Failed to delete volume in db"))

    def local_path(self, volume):
        return str(volume['id'])

    def undiscover_volume(self, volume):
        """Undiscover volume on a remote host."""
        pass

    def discover_volume(self, context, volume):
        return str(volume['id'])

    def check_for_setup_error(self):
        pass

    def create_export(self, context, volume):
        """Exports the volume."""
        pass

    def remove_export(self, context, volume):
        """Removes an export for a logical volume."""
        pass

    def ensure_export(self, context, volume):
        """Safely, synchronously recreates an export for a logical volume."""
        pass

    def initialize_connection(self, volume, connector):
        try:
            xensm_properties = dict(self.db.sm_volume_get(self.ctxt,
                                                          volume['id']))
        except Exception as ex:
            LOG.exception(ex)
            raise exception.NovaException(_("Failed to find volume in db"))

        # Keep the volume id key consistent with what ISCSI driver calls it
        xensm_properties['volume_id'] = xensm_properties['id']
        del xensm_properties['id']

        try:
            backend_conf = self.db.sm_backend_conf_get(self.ctxt,
                                  xensm_properties['backend_id'])
        except Exception as ex:
            LOG.exception(ex)
            raise exception.NovaException(_("Failed to find backend in db"))

        params = self._convert_config_params(backend_conf['config_params'])

        xensm_properties['flavor_id'] = backend_conf['flavor_id']
        xensm_properties['sr_uuid'] = backend_conf['sr_uuid']
        xensm_properties['sr_type'] = backend_conf['sr_type']
        xensm_properties.update(params)
        _introduce_sr_keys = self._get_introduce_sr_keys(params)
        xensm_properties['introduce_sr_keys'] = _introduce_sr_keys
        return {
            'driver_volume_type': 'xensm',
            'data': xensm_properties
        }

    def terminate_connection(self, volume, connector):
        pass
