cinder/cinder/volume/drivers/dell_emc/sc/storagecenter_iscsi.py
rajinir 08d35b0991 Dell EMC SC: Handle the mappings of multiattached volume
For volumes attached to multiple instances, this handles
the deletion of mappings in the backend when the volume
is attached to multiple instances on the same host.

Change-Id: I33d691d1dfa835b70726bcfacf868faab52de16a
Closes-Bug: #1822229
Co-Authored-By: Sam Wan <sam_wan@emc.com>
2019-08-29 16:15:59 +00:00

337 lines
15 KiB
Python

# 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 import utils
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.\
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.
4.1.0 - Timeouts added to rest calls.
4.1.1 - excluded_domain_ips support.
"""
VERSION = '4.1.1'
CI_WIKI_NAME = "Dell_EMC_SC_Series_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,
scserver)
# 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,
secondary)
# 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 force_detach(self, volume):
"""Breaks all volume server connections including to the live volume.
:param volume: volume to be detached
:raises VolumeBackendAPIException: On failure to sever connections.
"""
with self._client.open_connection() as api:
volume_name = volume.get('id')
provider_id = volume.get('provider_id')
try:
rtn = False
islivevol = self._is_live_vol(volume)
scvolume = api.find_volume(volume_name, provider_id, islivevol)
if scvolume:
rtn = api.unmap_all(scvolume)
if rtn and islivevol:
sclivevolume = api.get_live_volume(provider_id)
if sclivevolume:
rtn = self.terminate_secondary(api, sclivevolume,
None)
return rtn
except Exception:
with excutils.save_and_reraise_exception():
LOG.error('Failed to terminates %(vol)s connections.',
{'vol': volume_name})
raise exception.VolumeBackendAPIException(
_('Terminate connection failed'))
@utils.synchronized('{self.driver_prefix}-{volume.id}')
def terminate_connection(self, volume, connector, force=False, **kwargs):
# Special case
# None `connector` indicates force detach, then detach all even if the
# volume is multi-attached.
if connector is None:
return self.force_detach(volume)
# Normal terminate connection, then.
# 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.info('Volume in terminate connection: %(vol)s',
{'vol': volume})
is_multiattached = (hasattr(volume, 'volume_attachment') and
self.is_multiattach_to_host(
volume.get('volume_attachment'),
connector['host']))
if is_multiattached:
LOG.info('Cannot terminate connection: '
'%(vol)s is multiattached.',
{'vol': volume_name})
return True
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):
# Only return False if we tried something and it failed.
rtn = True
secondaryvol = api.get_volume(
sclivevolume['secondaryVolume']['instanceId'])
if secondaryvol:
if initiatorname:
# Find our server.
secondary = api.find_server(
initiatorname, sclivevolume['secondaryScSerialNumber'])
rtn = api.unmap_volume(secondaryvol, secondary)
else:
rtn = api.unmap_all(secondaryvol)
else:
LOG.debug('terminate_secondary: secondary volume not found.')
return rtn