text: #    Copyright (c) 2015-2017 Dell Inc, or its subsidiaries.
  #
  #    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 _
  from cinder import interface
  from cinder.volume import driver
  from cinder.volume.drivers.dell_emc.sc import storagecenter_common
  
  LOG = logging.getLogger(__name__)
  
  
  @interface.volumedriver
  class SCISCSIDriver(storagecenter_common.SCCommonDriver,
                      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_emc.sc.\
          dell_storagecenter_iscsi.SCISCSIDriver
  
      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.
          3.3.0 - Support for a secondary DSM.
          3.4.0 - Support for excluding a domain.
          3.5.0 - Support for AFO.
          3.6.0 - Server type support.
          3.7.0 - Support for Data Reduction, Group QOS and Volume QOS.
          4.0.0 - Driver moved to dell_emc.
  
      """
  
      VERSION = '4.0.0'
      CI_WIKI_NAME = "Dell_Storage_CI"
  
      def __init__(self, *args, **kwargs):
          super(SCISCSIDriver, 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('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 the volume on the storage center. Note that if this
                  # is live volume and we are swapped this will be the back
                  # half of the live volume.
                  scvolume = api.find_volume(volume_name, provider_id, islivevol)
                  if scvolume:
                      # Get the SSN it is on.
                      ssn = scvolume['instanceId'].split('.')[0]
                      # Find our server.
                      scserver = api.find_server(initiator_name, ssn)
                      # No? Create it.
                      if scserver is None:
                          scserver = api.create_server(
                              [initiator_name],
                              self.configuration.dell_server_os, ssn)
  
                      # if we have a server and a volume lets bring them
                      # together.
                      if scserver 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(scvolume['instanceId'])
                              # 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. Note that if we have failed
                              # over we do not wish to do this.
                              if islivevol:
                                  sclivevolume = api.get_live_volume(provider_id)
                                  # Only map if we are not failed over.
                                  if (sclivevolume and not
                                      api.is_failed_over(provider_id,
                                                         sclivevolume)):
                                      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('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], self.configuration.dell_server_os,
                  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('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 quick info.
          volume_name = volume.get('id')
          provider_id = volume.get('provider_id')
          initiator_name = None if not connector else connector.get('initiator')
          LOG.debug('Terminate connection: %(vol)s:%(initiator)s',
                    {'vol': volume_name,
                     'initiator': initiator_name})
  
          with self._client.open_connection() as api:
              try:
                  # Find the volume on the storage center. Note that if this
                  # is live volume and we are swapped this will be the back
                  # half of the live volume.
                  islivevol = self._is_live_vol(volume)
                  scvolume = api.find_volume(volume_name, provider_id, islivevol)
                  if scvolume:
                      # Get the SSN it is on.
                      ssn = scvolume['instanceId'].split('.')[0]
  
                      # Unmap our secondary if not failed over..
                      if islivevol:
                          sclivevolume = api.get_live_volume(provider_id)
                          if (sclivevolume and not
                              api.is_failed_over(provider_id,
                                                 sclivevolume)):
                              self.terminate_secondary(api, sclivevolume,
                                                       initiator_name)
  
                      # Find our server.
                      scserver = (None if not initiator_name else
                                  api.find_server(initiator_name, ssn))
  
                      # If we have a server and a volume lets pull them apart.
                      if ((scserver and
                           api.unmap_volume(scvolume, scserver) is True) or
                         (not scserver and api.unmap_all(scvolume))):
                          LOG.debug('Connection terminated')
                          return
              except Exception:
                  with excutils.save_and_reraise_exception():
                      LOG.error('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):
          secondaryvol = api.get_volume(
              sclivevolume['secondaryVolume']['instanceId'])
          if secondaryvol:
              if initiatorname:
                  # Find our server.
                  secondary = api.find_server(
                      initiatorname, sclivevolume['secondaryScSerialNumber'])
                  return api.unmap_volume(secondaryvol, secondary)
              else:
                  return api.unmap_all(secondaryvol)
          else:
              LOG.debug('terminate_secondary: secondary volume not found.')
  