275 lines
12 KiB
Python
275 lines
12 KiB
Python
# 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, _LW
|
|
from cinder import interface
|
|
from cinder.volume import driver
|
|
from cinder.volume.drivers.dell import dell_storagecenter_common
|
|
from cinder.zonemanager import utils as fczm_utils
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
|
|
@interface.volumedriver
|
|
class DellStorageCenterFCDriver(dell_storagecenter_common.DellCommonDriver,
|
|
driver.FibreChannelDriver):
|
|
|
|
"""Implements commands for Dell Storage Center FC management.
|
|
|
|
To enable the driver add the following line to the cinder configuration:
|
|
volume_driver=cinder.volume.drivers.dell.dell_storagecenter_fc.\
|
|
DellStorageCenterFCDriver
|
|
|
|
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
|
|
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(DellStorageCenterFCDriver, self).__init__(*args, **kwargs)
|
|
self.backend_name =\
|
|
self.configuration.safe_get('volume_backend_name') or 'Dell-FC'
|
|
self.storage_protocol = 'FC'
|
|
|
|
@fczm_utils.AddFCZone
|
|
def initialize_connection(self, volume, connector):
|
|
"""Initializes the connection and returns connection info.
|
|
|
|
Assign any created volume to a compute node/host so that it can be
|
|
used from that host.
|
|
|
|
The driver returns a driver_volume_type of 'fibre_channel'.
|
|
The target_wwn can be a single entry or a list of wwns that
|
|
correspond to the list of remote wwn(s) that will export the volume.
|
|
"""
|
|
|
|
# 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)
|
|
LOG.debug('Initialize connection: %s', volume_name)
|
|
with self._client.open_connection() as api:
|
|
try:
|
|
wwpns = connector.get('wwpns')
|
|
# 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 = self._find_server(api, wwpns, ssn)
|
|
|
|
# No? Create it.
|
|
if scserver is None:
|
|
scserver = api.create_server(
|
|
wwpns, self.configuration.dell_server_os, ssn)
|
|
# We have a volume and a server. Map them.
|
|
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'])
|
|
lun, targets, init_targ_map = api.find_wwns(
|
|
scvolume, scserver)
|
|
|
|
# Do we have extra live volume work?
|
|
if islivevol:
|
|
# Get our live volume.
|
|
sclivevolume = api.get_live_volume(provider_id)
|
|
# Do not map to a failed over volume.
|
|
if (sclivevolume and not
|
|
api.is_failed_over(provider_id,
|
|
sclivevolume)):
|
|
# Now map our secondary.
|
|
lvlun, lvtargets, lvinit_targ_map = (
|
|
self.initialize_secondary(api,
|
|
sclivevolume,
|
|
wwpns))
|
|
# Unmapped. Add info to our list.
|
|
targets += lvtargets
|
|
init_targ_map.update(lvinit_targ_map)
|
|
|
|
# Roll up our return data.
|
|
if lun is not None and len(targets) > 0:
|
|
data = {'driver_volume_type': 'fibre_channel',
|
|
'data': {'target_lun': lun,
|
|
'target_discovered': True,
|
|
'target_wwn': targets,
|
|
'initiator_target_map':
|
|
init_targ_map,
|
|
'discard': True}}
|
|
LOG.debug('Return FC data: %s', data)
|
|
return data
|
|
LOG.error(_LE('Lun mapping returned null!'))
|
|
|
|
except Exception:
|
|
with excutils.save_and_reraise_exception():
|
|
LOG.error(_LE('Failed to initialize connection.'))
|
|
|
|
# We get here because our mapping is none so blow up.
|
|
raise exception.VolumeBackendAPIException(_('Unable to map volume.'))
|
|
|
|
def _find_server(self, api, wwns, ssn=-1):
|
|
for wwn in wwns:
|
|
scserver = api.find_server(wwn, ssn)
|
|
if scserver is not None:
|
|
return scserver
|
|
return None
|
|
|
|
def initialize_secondary(self, api, sclivevolume, wwns):
|
|
"""Initialize the secondary connection of a live volume pair.
|
|
|
|
:param api: Dell SC api object.
|
|
:param sclivevolume: Dell SC live volume object.
|
|
:param wwns: Cinder list of wwns from the connector.
|
|
:return: lun, targets and initiator target map.
|
|
"""
|
|
# Find our server.
|
|
secondary = self._find_server(
|
|
api, wwns, sclivevolume['secondaryScSerialNumber'])
|
|
|
|
# No? Create it.
|
|
if secondary is None:
|
|
secondary = api.create_server(
|
|
wwns, self.configuration.dell_server_os,
|
|
sclivevolume['secondaryScSerialNumber'])
|
|
if secondary:
|
|
if api.map_secondary_volume(sclivevolume, secondary):
|
|
# Get mappings.
|
|
secondaryvol = api.get_volume(
|
|
sclivevolume['secondaryVolume']['instanceId'])
|
|
if secondaryvol:
|
|
return api.find_wwns(secondaryvol, secondary)
|
|
LOG.warning(_LW('Unable to map live volume secondary volume'
|
|
' %(vol)s to secondary server wwns: %(wwns)r'),
|
|
{'vol': sclivevolume['secondaryVolume']['instanceName'],
|
|
'wwns': wwns})
|
|
return None, [], {}
|
|
|
|
@fczm_utils.RemoveFCZone
|
|
def terminate_connection(self, volume, connector, force=False, **kwargs):
|
|
# Get our volume name
|
|
volume_name = volume.get('id')
|
|
provider_id = volume.get('provider_id')
|
|
islivevol = self._is_live_vol(volume)
|
|
LOG.debug('Terminate connection: %s', volume_name)
|
|
with self._client.open_connection() as api:
|
|
try:
|
|
wwpns = connector.get('wwpns')
|
|
# Find the volume on the storage center.
|
|
scvolume = api.find_volume(volume_name, provider_id, islivevol)
|
|
if scvolume:
|
|
# Get the SSN it is on.
|
|
ssn = scvolume['instanceId'].split('.')[0]
|
|
|
|
scserver = self._find_server(api, wwpns, ssn)
|
|
|
|
# Get our target map so we can return it to free up a zone.
|
|
lun, targets, init_targ_map = api.find_wwns(scvolume,
|
|
scserver)
|
|
|
|
# Do we have extra live volume work?
|
|
if islivevol:
|
|
# Get our live volume.
|
|
sclivevolume = api.get_live_volume(provider_id)
|
|
# Do not map to a failed over volume.
|
|
if (sclivevolume and not
|
|
api.is_failed_over(provider_id,
|
|
sclivevolume)):
|
|
lvlun, lvtargets, lvinit_targ_map = (
|
|
self.terminate_secondary(
|
|
api, sclivevolume, wwpns))
|
|
# Add to our return.
|
|
if lvlun:
|
|
targets += lvtargets
|
|
init_targ_map.update(lvinit_targ_map)
|
|
|
|
# If we have a server and a volume lets unmap them.
|
|
if (scserver is not None and
|
|
scvolume is not None and
|
|
api.unmap_volume(scvolume, scserver) is True):
|
|
LOG.debug('Connection terminated')
|
|
else:
|
|
raise exception.VolumeBackendAPIException(
|
|
_('Terminate connection failed'))
|
|
|
|
# basic return info...
|
|
info = {'driver_volume_type': 'fibre_channel',
|
|
'data': {}}
|
|
|
|
# if not then we return the target map so that
|
|
# the zone can be freed up.
|
|
if api.get_volume_count(scserver) == 0:
|
|
info['data'] = {'target_wwn': targets,
|
|
'initiator_target_map': init_targ_map}
|
|
return info
|
|
|
|
except Exception:
|
|
with excutils.save_and_reraise_exception():
|
|
LOG.error(_LE('Failed to terminate connection'))
|
|
raise exception.VolumeBackendAPIException(
|
|
_('Terminate connection unable to connect to backend.'))
|
|
|
|
def terminate_secondary(self, api, sclivevolume, wwns):
|
|
# Find our server.
|
|
secondary = self._find_server(
|
|
api, wwns, sclivevolume['secondaryScSerialNumber'])
|
|
secondaryvol = api.get_volume(
|
|
sclivevolume['secondaryVolume']['instanceId'])
|
|
if secondary and secondaryvol:
|
|
# Get our map.
|
|
lun, targets, init_targ_map = api.find_wwns(secondaryvol,
|
|
secondary)
|
|
# If we have a server and a volume lets unmap them.
|
|
ret = api.unmap_volume(secondaryvol, secondary)
|
|
LOG.debug('terminate_secondary: secondary volume %(name)s unmap '
|
|
'to secondary server %(server)s result: %(result)r',
|
|
{'name': secondaryvol['name'],
|
|
'server': secondary['name'],
|
|
'result': ret})
|
|
# return info for
|
|
return lun, targets, init_targ_map
|
|
return None, [], {}
|