Dell EMC SC: On None connector terminate_connection unmaps all

On terminate_connection if the connector is None the driver now detatches
the volume from all mapped servers. It leaves connections to remote Storage
Center's so things like replication and live volume continue.

Change-Id: I205006a3ebab742883018d564e766031024eeb3d
This commit is contained in:
Tom Swanson 2018-01-04 14:08:37 -06:00
parent f1f1ff45b9
commit 00e54d6049
6 changed files with 426 additions and 14 deletions

View File

@ -573,6 +573,90 @@ class DellSCSanFCDriverTestCase(test.TestCase):
expected = (None, [], {})
self.assertEqual(expected, ret)
@mock.patch.object(storagecenter_api.SCApi,
'find_volume')
@mock.patch.object(storagecenter_api.SCApi,
'unmap_all')
@mock.patch.object(storagecenter_fc.SCFCDriver,
'_is_live_vol')
def test_force_detach(self, mock_is_live_vol, mock_unmap_all,
mock_find_volume, mock_close_connection,
mock_open_connection, mock_init):
mock_is_live_vol.return_value = False
scvol = {'instandId': '12345.1'}
mock_find_volume.return_value = scvol
mock_unmap_all.return_value = True
volume = {'id': fake.VOLUME_ID}
res = self.driver.force_detach(volume)
mock_unmap_all.assert_called_once_with(scvol)
expected = {'driver_volume_type': 'fibre_channel',
'data': {}}
self.assertEqual(expected, res)
mock_unmap_all.assert_called_once_with(scvol)
@mock.patch.object(storagecenter_api.SCApi,
'find_volume')
@mock.patch.object(storagecenter_api.SCApi,
'unmap_all')
@mock.patch.object(storagecenter_fc.SCFCDriver,
'_is_live_vol')
def test_force_detach_fail(self, mock_is_live_vol, mock_unmap_all,
mock_find_volume, mock_close_connection,
mock_open_connection, mock_init):
mock_is_live_vol.return_value = False
scvol = {'instandId': '12345.1'}
mock_find_volume.return_value = scvol
mock_unmap_all.return_value = False
volume = {'id': fake.VOLUME_ID}
self.assertRaises(exception.VolumeBackendAPIException,
self.driver.force_detach, volume)
mock_unmap_all.assert_called_once_with(scvol)
@mock.patch.object(storagecenter_api.SCApi,
'find_volume')
@mock.patch.object(storagecenter_api.SCApi,
'unmap_all')
@mock.patch.object(storagecenter_fc.SCFCDriver,
'_is_live_vol')
@mock.patch.object(storagecenter_fc.SCFCDriver,
'terminate_secondary')
@mock.patch.object(storagecenter_api.SCApi,
'get_live_volume')
def test_force_detach_lv(self, mock_get_live_volume,
mock_terminate_secondary, mock_is_live_vol,
mock_unmap_all, mock_find_volume,
mock_close_connection, mock_open_connection,
mock_init):
mock_is_live_vol.return_value = True
scvol = {'instandId': '12345.1'}
mock_find_volume.return_value = scvol
sclivevol = {'instandId': '12345.1.0'}
mock_get_live_volume.return_value = sclivevol
mock_terminate_secondary.return_value = True
volume = {'id': fake.VOLUME_ID}
mock_unmap_all.return_value = True
res = self.driver.force_detach(volume)
mock_unmap_all.assert_called_once_with(scvol)
expected = {'driver_volume_type': 'fibre_channel', 'data': {}}
self.assertEqual(expected, res)
self.assertEqual(1, mock_terminate_secondary.call_count)
mock_unmap_all.assert_called_once_with(scvol)
@mock.patch.object(storagecenter_api.SCApi,
'find_volume')
@mock.patch.object(storagecenter_fc.SCFCDriver,
'_is_live_vol')
def test_force_detach_vol_not_found(self,
mock_is_live_vol, mock_find_volume,
mock_close_connection,
mock_open_connection, mock_init):
mock_is_live_vol.return_value = False
mock_find_volume.return_value = None
volume = {'id': fake.VOLUME_ID}
res = self.driver.force_detach(volume)
expected = {'driver_volume_type': 'fibre_channel', 'data': {}}
self.assertEqual(expected, res)
@mock.patch.object(storagecenter_api.SCApi,
'find_server',
return_value=SCSERVER)
@ -611,6 +695,16 @@ class DellSCSanFCDriverTestCase(test.TestCase):
'data': {}}
self.assertEqual(expected, res, 'Unexpected return data')
@mock.patch.object(storagecenter_fc.SCFCDriver,
'force_detach')
def test_terminate_connection_none_connector(self, mock_force_detach,
mock_close_connection,
mock_open_connection,
mock_init):
volume = {'id': fake.VOLUME_ID}
self.driver.terminate_connection(volume, None)
mock_force_detach.assert_called_once_with(volume)
@mock.patch.object(storagecenter_api.SCApi,
'find_server',
return_value=SCSERVER)

View File

@ -1325,6 +1325,87 @@ class DellSCSanISCSIDriverTestCase(test.TestCase):
mock_api.unmap_volume.assert_called_once_with(self.VOLUME,
self.SCSERVER)
@mock.patch.object(storagecenter_api.SCApi,
'find_volume')
@mock.patch.object(storagecenter_api.SCApi,
'unmap_all')
@mock.patch.object(storagecenter_iscsi.SCISCSIDriver,
'_is_live_vol')
def test_force_detach(self, mock_is_live_vol, mock_unmap_all,
mock_find_volume, mock_close_connection,
mock_open_connection, mock_init):
mock_is_live_vol.return_value = False
scvol = {'instandId': '12345.1'}
mock_find_volume.return_value = scvol
mock_unmap_all.return_value = True
volume = {'id': fake.VOLUME_ID}
res = self.driver.force_detach(volume)
mock_unmap_all.assert_called_once_with(scvol)
self.assertTrue(res)
mock_unmap_all.assert_called_once_with(scvol)
@mock.patch.object(storagecenter_api.SCApi,
'find_volume')
@mock.patch.object(storagecenter_api.SCApi,
'unmap_all')
@mock.patch.object(storagecenter_iscsi.SCISCSIDriver,
'_is_live_vol')
def test_force_detach_fail(self, mock_is_live_vol, mock_unmap_all,
mock_find_volume, mock_close_connection,
mock_open_connection, mock_init):
mock_is_live_vol.return_value = False
scvol = {'instandId': '12345.1'}
mock_find_volume.return_value = scvol
mock_unmap_all.return_value = False
volume = {'id': fake.VOLUME_ID}
res = self.driver.force_detach(volume)
mock_unmap_all.assert_called_once_with(scvol)
self.assertFalse(res)
mock_unmap_all.assert_called_once_with(scvol)
@mock.patch.object(storagecenter_api.SCApi,
'find_volume')
@mock.patch.object(storagecenter_api.SCApi,
'unmap_all')
@mock.patch.object(storagecenter_iscsi.SCISCSIDriver,
'_is_live_vol')
@mock.patch.object(storagecenter_iscsi.SCISCSIDriver,
'terminate_secondary')
@mock.patch.object(storagecenter_api.SCApi,
'get_live_volume')
def test_force_detach_lv(self, mock_get_live_volume,
mock_terminate_secondary, mock_is_live_vol,
mock_unmap_all, mock_find_volume,
mock_close_connection, mock_open_connection,
mock_init):
mock_is_live_vol.return_value = True
scvol = {'instandId': '12345.1'}
mock_find_volume.return_value = scvol
sclivevol = {'instandId': '12345.1.0'}
mock_get_live_volume.return_value = sclivevol
mock_terminate_secondary.return_value = True
volume = {'id': fake.VOLUME_ID}
mock_unmap_all.return_value = True
res = self.driver.force_detach(volume)
mock_unmap_all.assert_called_once_with(scvol)
self.assertTrue(res)
self.assertEqual(1, mock_terminate_secondary.call_count)
mock_unmap_all.assert_called_once_with(scvol)
@mock.patch.object(storagecenter_api.SCApi,
'find_volume')
@mock.patch.object(storagecenter_iscsi.SCISCSIDriver,
'_is_live_vol')
def test_force_detach_vol_not_found(self,
mock_is_live_vol, mock_find_volume,
mock_close_connection,
mock_open_connection, mock_init):
mock_is_live_vol.return_value = False
mock_find_volume.return_value = None
volume = {'id': fake.VOLUME_ID}
res = self.driver.force_detach(volume)
self.assertFalse(res)
@mock.patch.object(storagecenter_api.SCApi,
'find_server',
return_value=SCSERVER)
@ -1347,6 +1428,16 @@ class DellSCSanISCSIDriverTestCase(test.TestCase):
mock_unmap_volume.assert_called_once_with(self.VOLUME, self.SCSERVER)
self.assertIsNone(res, 'None expected')
@mock.patch.object(storagecenter_iscsi.SCISCSIDriver,
'force_detach')
def test_terminate_connection_no_connector(self, mock_force_detach,
mock_close_connection,
mock_open_connection,
mock_init):
volume = {'id': fake.VOLUME_ID}
self.driver.terminate_connection(volume, None)
mock_force_detach.assert_called_once_with(volume)
@mock.patch.object(storagecenter_api.SCApi,
'find_server',
return_value=SCSERVER)

View File

@ -4231,6 +4231,125 @@ class DellSCSanAPITestCase(test.TestCase):
self.assertFalse(mock_delete.called)
self.assertTrue(res)
@mock.patch.object(storagecenter_api.HttpClient,
'delete')
@mock.patch.object(storagecenter_api.HttpClient,
'get')
@mock.patch.object(storagecenter_api.SCApi,
'_find_mapping_profiles')
@mock.patch.object(storagecenter_api.SCApi,
'_get_json')
def test_unmap_all(self, mock_get_json, mock_find_mapping_profiles,
mock_get, mock_delete, mock_close_connection,
mock_open_connection, mock_init):
mock_delete.return_value = self.RESPONSE_200
mock_get.return_value = self.RESPONSE_200
mock_find_mapping_profiles.return_value = [
{'instanceId': '12345.0.1',
'server': {'instanceId': '12345.100', 'instanceName': 'Srv1'}},
{'instanceId': '12345.0.2',
'server': {'instanceId': '12345.101', 'instanceName': 'Srv2'}},
{'instanceId': '12345.0.3',
'server': {'instanceId': '12345.102', 'instanceName': 'Srv3'}},
]
# server, result pairs
mock_get_json.side_effect = [
{'instanceId': '12345.100', 'instanceName': 'Srv1',
'type': 'Physical'},
{'result': True},
{'instanceId': '12345.101', 'instanceName': 'Srv2',
'type': 'Physical'},
{'result': True},
{'instanceId': '12345.102', 'instanceName': 'Srv3',
'type': 'Physical'},
{'result': True}
]
vol = {'instanceId': '12345.0', 'name': 'vol1'}
res = self.scapi.unmap_all(vol)
# Success and 3 delete calls
self.assertTrue(res)
self.assertEqual(3, mock_delete.call_count)
@mock.patch.object(storagecenter_api.HttpClient,
'delete')
@mock.patch.object(storagecenter_api.HttpClient,
'get')
@mock.patch.object(storagecenter_api.SCApi,
'_find_mapping_profiles')
@mock.patch.object(storagecenter_api.SCApi,
'_get_json')
def test_unmap_all_with_remote(self, mock_get_json,
mock_find_mapping_profiles, mock_get,
mock_delete, mock_close_connection,
mock_open_connection, mock_init):
mock_delete.return_value = self.RESPONSE_200
mock_get.return_value = self.RESPONSE_200
mock_find_mapping_profiles.return_value = [
{'instanceId': '12345.0.1',
'server': {'instanceId': '12345.100', 'instanceName': 'Srv1'}},
{'instanceId': '12345.0.2',
'server': {'instanceId': '12345.101', 'instanceName': 'Srv2'}},
{'instanceId': '12345.0.3',
'server': {'instanceId': '12345.102', 'instanceName': 'Srv3'}},
]
# server, result pairs
mock_get_json.side_effect = [
{'instanceId': '12345.100', 'instanceName': 'Srv1',
'type': 'Physical'},
{'result': True},
{'instanceId': '12345.101', 'instanceName': 'Srv2',
'type': 'RemoteStorageCenter'},
{'instanceId': '12345.102', 'instanceName': 'Srv3',
'type': 'Physical'},
{'result': True}
]
vol = {'instanceId': '12345.0', 'name': 'vol1'}
res = self.scapi.unmap_all(vol)
# Should succeed but call delete only twice
self.assertTrue(res)
self.assertEqual(2, mock_delete.call_count)
@mock.patch.object(storagecenter_api.HttpClient,
'delete')
@mock.patch.object(storagecenter_api.HttpClient,
'get')
@mock.patch.object(storagecenter_api.SCApi,
'_find_mapping_profiles')
@mock.patch.object(storagecenter_api.SCApi,
'_get_json')
def test_unmap_all_fail(self, mock_get_json, mock_find_mapping_profiles,
mock_get, mock_delete, mock_close_connection,
mock_open_connection, mock_init):
mock_delete.return_value = self.RESPONSE_400
mock_get.return_value = self.RESPONSE_200
mock_find_mapping_profiles.return_value = [
{'instanceId': '12345.0.1',
'server': {'instanceId': '12345.100', 'instanceName': 'Srv1'}},
{'instanceId': '12345.0.2',
'server': {'instanceId': '12345.101', 'instanceName': 'Srv2'}},
{'instanceId': '12345.0.3',
'server': {'instanceId': '12345.102', 'instanceName': 'Srv3'}},
]
# server, result pairs
mock_get_json.side_effect = [
{'instanceId': '12345.100', 'instanceName': 'Srv1',
'type': 'Physical'}
]
vol = {'instanceId': '12345.0', 'name': 'vol1'}
res = self.scapi.unmap_all(vol)
self.assertFalse(res)
@mock.patch.object(storagecenter_api.SCApi,
'_find_mapping_profiles')
def test_unmap_all_no_profiles(self, mock_find_mapping_profiles,
mock_close_connection, mock_open_connection,
mock_init):
mock_find_mapping_profiles.return_value = []
vol = {'instanceId': '12345.0', 'name': 'vol1'}
res = self.scapi.unmap_all(vol)
# Should exit with success.
self.assertTrue(res)
@mock.patch.object(storagecenter_api.SCApi,
'_get_json',
return_value=[{'a': 1}, {'a': 2}])

View File

@ -1993,10 +1993,51 @@ class SCApi(object):
return rtn
def unmap_all(self, scvolume):
volumeid = self._get_id(scvolume)
r = self.client.post('StorageCenter/ScVolume/%s/Unmap' % volumeid,
{}, True)
return self._check_result(r)
"""Unmaps a volume from all connections except SCs.
:param scvolume: The SC Volume object.
:return: Boolean
"""
rtn = True
profiles = self._find_mapping_profiles(scvolume)
for profile in profiles:
# get our server
scserver = None
r = self.client.get('StorageCenter/ScServer/%s' %
self._get_id(profile.get('server')))
if self._check_result(r):
scserver = self._get_json(r)
# We do not want to whack our replication or live volume
# connections. So anything other than a remote storage center
# is fair game.
if scserver and scserver['type'].upper() != 'REMOTESTORAGECENTER':
# we can whack the connection.
r = self.client.delete('StorageCenter/ScMappingProfile/%s'
% self._get_id(profile),
async_call=True)
if self._check_result(r):
# Check our result in the json.
result = self._get_json(r)
# EM 15.1 and 15.2 return a boolean directly.
# 15.3 on up return it in a dict under 'result'.
if result is True or (type(result) is dict and
result.get('result')):
LOG.info(
'Volume %(vol)s unmapped from %(srv)s',
{'vol': scvolume['name'],
'srv': scserver['instanceName']})
# yay, it is gone, carry on.
continue
LOG.error('Unable to unmap %(vol)s from %(srv)s',
{'vol': scvolume['name'],
'srv': scserver['instanceName']})
# 1 failed unmap is as good as 100.
# Fail it and leave
rtn = False
break
return rtn
def get_storage_usage(self):
"""Gets the storage usage object from the Dell backend.

View File

@ -75,14 +75,6 @@ class SCFCDriver(storagecenter_common.SCCommonDriver,
self.configuration.safe_get('volume_backend_name') or 'Dell-FC'
self.storage_protocol = 'FC'
def validate_connector(self, connector):
"""Fail if connector doesn't contain all the data needed by driver.
Do a check on the connector and ensure that it has wwnns, wwpns.
"""
self.validate_connector_has_setting(connector, 'wwpns')
self.validate_connector_has_setting(connector, 'wwnns')
@fczm_utils.add_fc_zone
def initialize_connection(self, volume, connector):
"""Initializes the connection and returns connection info.
@ -203,8 +195,47 @@ class SCFCDriver(storagecenter_common.SCCommonDriver,
'wwns': wwns})
return None, [], {}
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:
islivevol = self._is_live_vol(volume)
scvolume = api.find_volume(volume_name, provider_id, islivevol)
if scvolume:
rtn = api.unmap_all(scvolume)
# If this fails we blow up.
if not rtn:
raise exception.VolumeBackendAPIException(
_('Terminate connection failed'))
# If there is a livevol we just take a shot at
# disconnecting.
if islivevol:
sclivevolume = api.get_live_volume(provider_id)
if sclivevolume:
self.terminate_secondary(api, sclivevolume, None)
except Exception:
with excutils.save_and_reraise_exception():
LOG.error('Failed to terminates %(vol)s connections.',
{'vol': volume_name})
# We don't know the servers that were involved so we just return
# the most basic of data.
info = {'driver_volume_type': 'fibre_channel',
'data': {}}
return info
@fczm_utils.remove_fc_zone
def terminate_connection(self, volume, connector, force=False, **kwargs):
# Special case
if connector is None:
return self.force_detach(volume)
# Grab some quick info.
volume_name = volume.get('id')
provider_id = volume.get('provider_id')

View File

@ -219,7 +219,40 @@ class SCISCSIDriver(storagecenter_common.SCCommonDriver,
'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'))
def terminate_connection(self, volume, connector, force=False, **kwargs):
# Special case
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')
@ -268,6 +301,8 @@ class SCISCSIDriver(storagecenter_common.SCCommonDriver,
_('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:
@ -275,8 +310,9 @@ class SCISCSIDriver(storagecenter_common.SCCommonDriver,
# Find our server.
secondary = api.find_server(
initiatorname, sclivevolume['secondaryScSerialNumber'])
return api.unmap_volume(secondaryvol, secondary)
rtn = api.unmap_volume(secondaryvol, secondary)
else:
return api.unmap_all(secondaryvol)
rtn = api.unmap_all(secondaryvol)
else:
LOG.debug('terminate_secondary: secondary volume not found.')
return rtn