
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>
337 lines
15 KiB
Python
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
|