#    Copyright 2015 Dell 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.

"""Volume driver for Dell Storage Center."""

from oslo_log import log as logging
from oslo_utils import excutils

from cinder import exception
from cinder.i18n import _, _LE, _LI, _LW
from cinder import interface
from cinder.volume import driver
from cinder.volume.drivers.dell import dell_storagecenter_common
LOG = logging.getLogger(__name__)


@interface.volumedriver
class DellStorageCenterISCSIDriver(dell_storagecenter_common.DellCommonDriver,
                                   driver.ISCSIDriver):

    """Implements commands for Dell Storage Center ISCSI management.

    To enable the driver add the following line to the cinder configuration:
        volume_driver=cinder.volume.drivers.dell.DellStorageCenterISCSIDriver

    Version history:

    .. code-block:: none

        1.0.0 - Initial driver
        1.1.0 - Added extra spec support for Storage Profile selection
        1.2.0 - Added consistency group support.
        2.0.0 - Switched to inheriting functional objects rather than volume
                driver.
        2.1.0 - Added support for ManageableVD.
        2.2.0 - Driver retype support for switching volume's Storage Profile.
                Added API 2.2 support.
        2.3.0 - Added Legacy Port Mode Support
        2.3.1 - Updated error handling.
        2.4.0 - Added Replication V2 support.
        2.4.1 - Updated Replication support to V2.1.
        2.5.0 - ManageableSnapshotsVD implemented.
        3.0.0 - ProviderID utilized.
        3.1.0 - Failback Supported.
        3.2.0 - Live Volume support.

    """

    VERSION = '3.2.0'
    CI_WIKI_NAME = "Dell_Storage_CI"

    def __init__(self, *args, **kwargs):
        super(DellStorageCenterISCSIDriver, self).__init__(*args, **kwargs)
        self.backend_name = (
            self.configuration.safe_get('volume_backend_name') or 'Dell-iSCSI')

    def initialize_connection(self, volume, connector):
        # Initialize_connection will find or create a server identified by the
        # connector on the Dell backend.  It will then map the volume to it
        # and return the properties as follows..
        # {'driver_volume_type': 'iscsi',
        #  data = {'target_discovered': False,
        #          'target_iqn': preferred iqn,
        #           'target_iqns': all iqns,
        #           'target_portal': preferred portal,
        #           'target_portals': all portals,
        #           'target_lun': preferred lun,
        #           'target_luns': all luns,
        #         }

        # We use id to name the volume name as it is a
        # known unique name.
        volume_name = volume.get('id')
        provider_id = volume.get('provider_id')
        islivevol = self._is_live_vol(volume)
        initiator_name = connector.get('initiator')
        multipath = connector.get('multipath', False)
        LOG.info(_LI('initialize_ connection: %(vol)s:%(pid)s:'
                     '%(intr)s. Multipath is %(mp)r'),
                 {'vol': volume_name,
                  'pid': provider_id,
                  'intr': initiator_name,
                  'mp': multipath})

        with self._client.open_connection() as api:
            try:
                # Find our server.
                scserver = api.find_server(initiator_name)
                # No? Create it.
                if scserver is None:
                    scserver = api.create_server([initiator_name])
                # Find the volume on the storage center.
                scvolume = api.find_volume(volume_name, provider_id)

                # if we have a server and a volume lets bring them together.
                if scserver is not None and scvolume is not None:
                    mapping = api.map_volume(scvolume, scserver)
                    if mapping is not None:
                        # Since we just mapped our volume we had best update
                        # our sc volume object.
                        scvolume = api.get_volume(provider_id)
                        # Our return.
                        iscsiprops = {}

                        # Three cases that should all be satisfied with the
                        # same return of Target_Portal and Target_Portals.
                        # 1. Nova is calling us so we need to return the
                        #    Target_Portal stuff.  It should ignore the
                        #    Target_Portals stuff.
                        # 2. OS brick is calling us in multipath mode so we
                        #    want to return Target_Portals.  It will ignore
                        #    the Target_Portal stuff.
                        # 3. OS brick is calling us in single path mode so
                        #    we want to return Target_Portal and
                        #    Target_Portals as alternates.
                        iscsiprops = api.find_iscsi_properties(scvolume)

                        # If this is a live volume we need to map up our
                        # secondary volume.
                        if islivevol:
                            sclivevolume, swapped = api.get_live_volume(
                                provider_id)
                            # Only map if we are not swapped.
                            if sclivevolume and not swapped:
                                secondaryprops = self.initialize_secondary(
                                    api, sclivevolume, initiator_name)
                                # Combine with iscsiprops
                                iscsiprops['target_iqns'] += (
                                    secondaryprops['target_iqns'])
                                iscsiprops['target_portals'] += (
                                    secondaryprops['target_portals'])
                                iscsiprops['target_luns'] += (
                                    secondaryprops['target_luns'])

                        # Return our iscsi properties.
                        iscsiprops['discard'] = True
                        return {'driver_volume_type': 'iscsi',
                                'data': iscsiprops}
            # Re-raise any backend exception.
            except exception.VolumeBackendAPIException:
                with excutils.save_and_reraise_exception():
                    LOG.error(_LE('Failed to initialize connection'))
            # If there is a data structure issue then detail the exception
            # and bail with a Backend Exception.
            except Exception as error:
                LOG.error(error)
                raise exception.VolumeBackendAPIException(error)

        # We get here because our mapping is none or we have no valid iqn to
        # return so blow up.
        raise exception.VolumeBackendAPIException(
            _('Unable to map volume'))

    def initialize_secondary(self, api, sclivevolume, initiatorname):
        """Initialize the secondary connection of a live volume pair.

        :param api: Dell SC api.
        :param sclivevolume: Dell SC live volume object.
        :param initiatorname: Cinder iscsi initiator from the connector.
        :return: ISCSI properties.
        """

        # Find our server.
        secondary = api.find_server(initiatorname,
                                    sclivevolume['secondaryScSerialNumber'])
        # No? Create it.
        if secondary is None:
            secondary = api.create_server(
                [initiatorname], sclivevolume['secondaryScSerialNumber'])
        if secondary:
            if api.map_secondary_volume(sclivevolume, secondary):
                # Get our volume and get our properties.
                secondaryvol = api.get_volume(
                    sclivevolume['secondaryVolume']['instanceId'])
                if secondaryvol:
                    return api.find_iscsi_properties(secondaryvol)
        # Dummy return on failure.
        data = {'target_discovered': False,
                'target_iqn': None,
                'target_iqns': [],
                'target_portal': None,
                'target_portals': [],
                'target_lun': None,
                'target_luns': [],
                }
        LOG.warning(_LW('Unable to map live volume secondary volume'
                        ' %(vol)s to secondary server intiator: %(init)r'),
                    {'vol': sclivevolume['secondaryVolume']['instanceName'],
                     'init': initiatorname})
        return data

    def terminate_connection(self, volume, connector, force=False, **kwargs):
        # Grab some initial info.
        initiator_name = connector.get('initiator')
        volume_name = volume.get('id')
        provider_id = volume.get('provider_id')
        islivevol = self._is_live_vol(volume)
        LOG.debug('Terminate connection: %(vol)s:%(initiator)s',
                  {'vol': volume_name,
                   'initiator': initiator_name})
        with self._client.open_connection() as api:
            try:
                scserver = api.find_server(initiator_name)
                # Find the volume on the storage center.
                scvolume = api.find_volume(volume_name, provider_id)

                # Unmap our secondary if it isn't swapped.
                if islivevol:
                    sclivevolume, swapped = api.get_live_volume(provider_id)
                    if sclivevolume and not swapped:
                        self.terminate_secondary(api, sclivevolume,
                                                 initiator_name)

                # If we have a server and a volume lets pull them apart.
                if (scserver is not None and
                        scvolume is not None and
                        api.unmap_volume(scvolume, scserver) is True):
                    LOG.debug('Connection terminated')
                    return
            except Exception:
                with excutils.save_and_reraise_exception():
                    LOG.error(_LE('Failed to terminate connection '
                                  '%(initiator)s %(vol)s'),
                              {'initiator': initiator_name,
                               'vol': volume_name})
        raise exception.VolumeBackendAPIException(
            _('Terminate connection failed'))

    def terminate_secondary(self, api, sclivevolume, initiatorname):
        # Find our server.
        secondary = api.find_server(initiatorname,
                                    sclivevolume['secondaryScSerialNumber'])
        secondaryvol = api.get_volume(
            sclivevolume['secondaryVolume']['instanceId'])
        return api.unmap_volume(secondaryvol, secondary)
