Dell SC: Error attaching after LV-AFO
Fixed miscellaneous errors dealing with the state of Dell's Live Volumes after a LV AFO event. Was looking for the original volume and sc server object after the primary was down. We now look for the new primary and attach that. Ensure export was failing to properly kick off a swap back to the original primary if it was available. Fixed an error with deleting replications. Added a workaround to finding a ScLiveVolume object. Previous method could return stale information. Change-Id: I58889adbcf279d4b0107cbc51e1dace888a3fb15 Closes-Bug: #1617401
This commit is contained in:
parent
4d209fd966
commit
45bc8abc47
|
@ -262,7 +262,8 @@ class DellSCSanFCDriverTestCase(test.TestCase):
|
||||||
sclivevol = {'instanceId': '101.101',
|
sclivevol = {'instanceId': '101.101',
|
||||||
'secondaryVolume': {'instanceId': '102.101',
|
'secondaryVolume': {'instanceId': '102.101',
|
||||||
'instanceName': fake.VOLUME_ID},
|
'instanceName': fake.VOLUME_ID},
|
||||||
'secondaryScSerialNumber': 102}
|
'secondaryScSerialNumber': 102,
|
||||||
|
'secondaryRole': 'Secondary'}
|
||||||
mock_is_live_volume.return_value = True
|
mock_is_live_volume.return_value = True
|
||||||
mock_find_wwns.return_value = (
|
mock_find_wwns.return_value = (
|
||||||
1, [u'5000D31000FCBE3D', u'5000D31000FCBE35'],
|
1, [u'5000D31000FCBE3D', u'5000D31000FCBE35'],
|
||||||
|
@ -272,7 +273,7 @@ class DellSCSanFCDriverTestCase(test.TestCase):
|
||||||
1, [u'5000D31000FCBE3E', u'5000D31000FCBE36'],
|
1, [u'5000D31000FCBE3E', u'5000D31000FCBE36'],
|
||||||
{u'21000024FF30441E': [u'5000D31000FCBE36'],
|
{u'21000024FF30441E': [u'5000D31000FCBE36'],
|
||||||
u'21000024FF30441F': [u'5000D31000FCBE3E']})
|
u'21000024FF30441F': [u'5000D31000FCBE3E']})
|
||||||
mock_get_live_volume.return_value = (sclivevol, False)
|
mock_get_live_volume.return_value = sclivevol
|
||||||
res = self.driver.initialize_connection(volume, connector)
|
res = self.driver.initialize_connection(volume, connector)
|
||||||
expected = {'data':
|
expected = {'data':
|
||||||
{'discard': True,
|
{'discard': True,
|
||||||
|
@ -292,6 +293,74 @@ class DellSCSanFCDriverTestCase(test.TestCase):
|
||||||
mock_find_volume.assert_called_once_with(fake.VOLUME_ID, None, True)
|
mock_find_volume.assert_called_once_with(fake.VOLUME_ID, None, True)
|
||||||
mock_get_volume.assert_called_once_with(self.VOLUME[u'instanceId'])
|
mock_get_volume.assert_called_once_with(self.VOLUME[u'instanceId'])
|
||||||
|
|
||||||
|
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
|
||||||
|
'find_server',
|
||||||
|
return_value=SCSERVER)
|
||||||
|
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
|
||||||
|
'find_volume')
|
||||||
|
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
|
||||||
|
'get_volume')
|
||||||
|
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
|
||||||
|
'map_volume',
|
||||||
|
return_value=MAPPING)
|
||||||
|
@mock.patch.object(dell_storagecenter_fc.DellStorageCenterFCDriver,
|
||||||
|
'_is_live_vol')
|
||||||
|
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
|
||||||
|
'find_wwns')
|
||||||
|
@mock.patch.object(dell_storagecenter_fc.DellStorageCenterFCDriver,
|
||||||
|
'initialize_secondary')
|
||||||
|
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
|
||||||
|
'get_live_volume')
|
||||||
|
def test_initialize_connection_live_vol_afo(self,
|
||||||
|
mock_get_live_volume,
|
||||||
|
mock_initialize_secondary,
|
||||||
|
mock_find_wwns,
|
||||||
|
mock_is_live_volume,
|
||||||
|
mock_map_volume,
|
||||||
|
mock_get_volume,
|
||||||
|
mock_find_volume,
|
||||||
|
mock_find_server,
|
||||||
|
mock_close_connection,
|
||||||
|
mock_open_connection,
|
||||||
|
mock_init):
|
||||||
|
volume = {'id': fake.VOLUME_ID, 'provider_id': '101.101'}
|
||||||
|
scvol = {'instanceId': '102.101'}
|
||||||
|
mock_find_volume.return_value = scvol
|
||||||
|
mock_get_volume.return_value = scvol
|
||||||
|
connector = self.connector
|
||||||
|
sclivevol = {'instanceId': '101.10001',
|
||||||
|
'primaryVolume': {'instanceId': '102.101',
|
||||||
|
'instanceName': fake.VOLUME_ID},
|
||||||
|
'primaryScSerialNumber': 102,
|
||||||
|
'secondaryVolume': {'instanceId': '101.101',
|
||||||
|
'instanceName': fake.VOLUME_ID},
|
||||||
|
'secondaryScSerialNumber': 101,
|
||||||
|
'secondaryRole': 'Activated'}
|
||||||
|
|
||||||
|
mock_is_live_volume.return_value = True
|
||||||
|
mock_find_wwns.return_value = (
|
||||||
|
1, [u'5000D31000FCBE3D', u'5000D31000FCBE35'],
|
||||||
|
{u'21000024FF30441C': [u'5000D31000FCBE35'],
|
||||||
|
u'21000024FF30441D': [u'5000D31000FCBE3D']})
|
||||||
|
mock_get_live_volume.return_value = sclivevol
|
||||||
|
res = self.driver.initialize_connection(volume, connector)
|
||||||
|
expected = {'data':
|
||||||
|
{'discard': True,
|
||||||
|
'initiator_target_map':
|
||||||
|
{u'21000024FF30441C': [u'5000D31000FCBE35'],
|
||||||
|
u'21000024FF30441D': [u'5000D31000FCBE3D']},
|
||||||
|
'target_discovered': True,
|
||||||
|
'target_lun': 1,
|
||||||
|
'target_wwn': [u'5000D31000FCBE3D', u'5000D31000FCBE35']},
|
||||||
|
'driver_volume_type': 'fibre_channel'}
|
||||||
|
|
||||||
|
self.assertEqual(expected, res, 'Unexpected return data')
|
||||||
|
# verify find_volume has been called and that is has been called twice
|
||||||
|
self.assertFalse(mock_initialize_secondary.called)
|
||||||
|
mock_find_volume.assert_called_once_with(
|
||||||
|
fake.VOLUME_ID, '101.101', True)
|
||||||
|
mock_get_volume.assert_called_once_with('102.101')
|
||||||
|
|
||||||
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
|
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
|
||||||
'find_server',
|
'find_server',
|
||||||
return_value=SCSERVER)
|
return_value=SCSERVER)
|
||||||
|
@ -581,11 +650,7 @@ class DellSCSanFCDriverTestCase(test.TestCase):
|
||||||
volume = {'id': fake.VOLUME_ID}
|
volume = {'id': fake.VOLUME_ID}
|
||||||
connector = self.connector
|
connector = self.connector
|
||||||
mock_terminate_secondary.return_value = (None, [], {})
|
mock_terminate_secondary.return_value = (None, [], {})
|
||||||
sclivevol = {'instanceId': '101.101',
|
mock_is_live_vol.return_value = True
|
||||||
'secondaryVolume': {'instanceId': '102.101',
|
|
||||||
'instanceName': fake.VOLUME_ID},
|
|
||||||
'secondaryScSerialNumber': 102}
|
|
||||||
mock_is_live_vol.return_value = sclivevol
|
|
||||||
res = self.driver.terminate_connection(volume, connector)
|
res = self.driver.terminate_connection(volume, connector)
|
||||||
mock_unmap_volume.assert_called_once_with(self.VOLUME, self.SCSERVER)
|
mock_unmap_volume.assert_called_once_with(self.VOLUME, self.SCSERVER)
|
||||||
expected = {'driver_volume_type': 'fibre_channel',
|
expected = {'driver_volume_type': 'fibre_channel',
|
||||||
|
|
|
@ -518,9 +518,9 @@ class DellSCSanISCSIDriverTestCase(test.TestCase):
|
||||||
sclivevol = {'instanceId': '101.101',
|
sclivevol = {'instanceId': '101.101',
|
||||||
'secondaryVolume': {'instanceId': '102.101',
|
'secondaryVolume': {'instanceId': '102.101',
|
||||||
'instanceName': fake.VOLUME_ID},
|
'instanceName': fake.VOLUME_ID},
|
||||||
'secondaryScSerialNumber': 102}
|
'secondaryScSerialNumber': 102,
|
||||||
mock_api.get_live_volume = mock.MagicMock(return_value=(sclivevol,
|
'secondaryRole': 'Secondary'}
|
||||||
False))
|
mock_api.get_live_volume = mock.MagicMock(return_value=sclivevol)
|
||||||
# No replication driver data.
|
# No replication driver data.
|
||||||
ret = self.driver._delete_live_volume(mock_api, vol)
|
ret = self.driver._delete_live_volume(mock_api, vol)
|
||||||
self.assertFalse(ret)
|
self.assertFalse(ret)
|
||||||
|
@ -538,7 +538,7 @@ class DellSCSanISCSIDriverTestCase(test.TestCase):
|
||||||
ret = self.driver._delete_live_volume(mock_api, vol)
|
ret = self.driver._delete_live_volume(mock_api, vol)
|
||||||
self.assertFalse(ret)
|
self.assertFalse(ret)
|
||||||
# No live volume found.
|
# No live volume found.
|
||||||
mock_api.get_live_volume.return_value = (None, False)
|
mock_api.get_live_volume.return_value = None
|
||||||
ret = self.driver._delete_live_volume(mock_api, vol)
|
ret = self.driver._delete_live_volume(mock_api, vol)
|
||||||
self.assertFalse(ret)
|
self.assertFalse(ret)
|
||||||
|
|
||||||
|
@ -786,7 +786,8 @@ class DellSCSanISCSIDriverTestCase(test.TestCase):
|
||||||
data = self.driver.initialize_connection(volume, connector)
|
data = self.driver.initialize_connection(volume, connector)
|
||||||
self.assertEqual('iscsi', data['driver_volume_type'])
|
self.assertEqual('iscsi', data['driver_volume_type'])
|
||||||
# verify find_volume has been called and that is has been called twice
|
# verify find_volume has been called and that is has been called twice
|
||||||
mock_find_volume.assert_called_once_with(fake.VOLUME_ID, provider_id)
|
mock_find_volume.assert_called_once_with(
|
||||||
|
fake.VOLUME_ID, provider_id, False)
|
||||||
mock_get_volume.assert_called_once_with(provider_id)
|
mock_get_volume.assert_called_once_with(provider_id)
|
||||||
expected = {'data': self.ISCSI_PROPERTIES,
|
expected = {'data': self.ISCSI_PROPERTIES,
|
||||||
'driver_volume_type': 'iscsi'}
|
'driver_volume_type': 'iscsi'}
|
||||||
|
@ -990,11 +991,7 @@ class DellSCSanISCSIDriverTestCase(test.TestCase):
|
||||||
mock_init):
|
mock_init):
|
||||||
volume = {'id': fake.VOLUME_ID}
|
volume = {'id': fake.VOLUME_ID}
|
||||||
connector = self.connector
|
connector = self.connector
|
||||||
sclivevol = {'instanceId': '101.101',
|
mock_is_live_vol.return_value = True
|
||||||
'secondaryVolume': {'instanceId': '102.101',
|
|
||||||
'instanceName': fake.VOLUME_ID},
|
|
||||||
'secondaryScSerialNumber': 102}
|
|
||||||
mock_is_live_vol.return_value = sclivevol
|
|
||||||
lvol_properties = {'access_mode': 'rw',
|
lvol_properties = {'access_mode': 'rw',
|
||||||
'target_discovered': False,
|
'target_discovered': False,
|
||||||
'target_iqn':
|
'target_iqn':
|
||||||
|
@ -1018,6 +1015,75 @@ class DellSCSanISCSIDriverTestCase(test.TestCase):
|
||||||
'driver_volume_type': 'iscsi'}
|
'driver_volume_type': 'iscsi'}
|
||||||
self.assertEqual(expected, ret)
|
self.assertEqual(expected, ret)
|
||||||
|
|
||||||
|
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
|
||||||
|
'find_server',
|
||||||
|
return_value=None)
|
||||||
|
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
|
||||||
|
'create_server',
|
||||||
|
return_value=SCSERVER)
|
||||||
|
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
|
||||||
|
'find_volume')
|
||||||
|
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
|
||||||
|
'get_volume')
|
||||||
|
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
|
||||||
|
'map_volume',
|
||||||
|
return_value=MAPPINGS[0])
|
||||||
|
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
|
||||||
|
'find_iscsi_properties')
|
||||||
|
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
|
||||||
|
'get_live_volume')
|
||||||
|
@mock.patch.object(dell_storagecenter_iscsi.DellStorageCenterISCSIDriver,
|
||||||
|
'_is_live_vol')
|
||||||
|
@mock.patch.object(dell_storagecenter_iscsi.DellStorageCenterISCSIDriver,
|
||||||
|
'initialize_secondary')
|
||||||
|
def test_initialize_connection_live_volume_afo(self,
|
||||||
|
mock_initialize_secondary,
|
||||||
|
mock_is_live_vol,
|
||||||
|
mock_get_live_vol,
|
||||||
|
mock_find_iscsi_props,
|
||||||
|
mock_map_volume,
|
||||||
|
mock_get_volume,
|
||||||
|
mock_find_volume,
|
||||||
|
mock_create_server,
|
||||||
|
mock_find_server,
|
||||||
|
mock_close_connection,
|
||||||
|
mock_open_connection,
|
||||||
|
mock_init):
|
||||||
|
volume = {'id': fake.VOLUME_ID, 'provider_id': '101.101'}
|
||||||
|
scvol = {'instanceId': '102.101'}
|
||||||
|
mock_find_volume.return_value = scvol
|
||||||
|
mock_get_volume.return_value = scvol
|
||||||
|
connector = self.connector
|
||||||
|
sclivevol = {'instanceId': '101.10001',
|
||||||
|
'primaryVolume': {'instanceId': '101.101',
|
||||||
|
'instanceName': fake.VOLUME_ID},
|
||||||
|
'primaryScSerialNumber': 101,
|
||||||
|
'secondaryVolume': {'instanceId': '102.101',
|
||||||
|
'instanceName': fake.VOLUME_ID},
|
||||||
|
'secondaryScSerialNumber': 102,
|
||||||
|
'secondaryRole': 'Activated'}
|
||||||
|
mock_is_live_vol.return_value = True
|
||||||
|
mock_get_live_vol.return_value = sclivevol
|
||||||
|
props = {
|
||||||
|
'access_mode': 'rw',
|
||||||
|
'target_discovered': False,
|
||||||
|
'target_iqn': u'iqn:1',
|
||||||
|
'target_iqns': [u'iqn:1',
|
||||||
|
u'iqn:2'],
|
||||||
|
'target_lun': 1,
|
||||||
|
'target_luns': [1, 1],
|
||||||
|
'target_portal': u'192.168.1.21:3260',
|
||||||
|
'target_portals': [u'192.168.1.21:3260',
|
||||||
|
u'192.168.1.22:3260']
|
||||||
|
}
|
||||||
|
mock_find_iscsi_props.return_value = props
|
||||||
|
ret = self.driver.initialize_connection(volume, connector)
|
||||||
|
expected = {'data': props,
|
||||||
|
'driver_volume_type': 'iscsi'}
|
||||||
|
expected['data']['discard'] = True
|
||||||
|
self.assertEqual(expected, ret)
|
||||||
|
self.assertFalse(mock_initialize_secondary.called)
|
||||||
|
|
||||||
@mock.patch.object(dell_storagecenter_iscsi.DellStorageCenterISCSIDriver,
|
@mock.patch.object(dell_storagecenter_iscsi.DellStorageCenterISCSIDriver,
|
||||||
'_get_replication_specs',
|
'_get_replication_specs',
|
||||||
return_value={'enabled': True, 'live': True})
|
return_value={'enabled': True, 'live': True})
|
||||||
|
@ -1230,9 +1296,10 @@ class DellSCSanISCSIDriverTestCase(test.TestCase):
|
||||||
sclivevol = {'instanceId': '101.101',
|
sclivevol = {'instanceId': '101.101',
|
||||||
'secondaryVolume': {'instanceId': '102.101',
|
'secondaryVolume': {'instanceId': '102.101',
|
||||||
'instanceName': fake.VOLUME_ID},
|
'instanceName': fake.VOLUME_ID},
|
||||||
'secondaryScSerialNumber': 102}
|
'secondaryScSerialNumber': 102,
|
||||||
|
'secondaryRole': 'Secondary'}
|
||||||
mock_is_live_vol.return_value = True
|
mock_is_live_vol.return_value = True
|
||||||
mock_get_live_vol.return_value = (sclivevol, False)
|
mock_get_live_vol.return_value = sclivevol
|
||||||
connector = self.connector
|
connector = self.connector
|
||||||
res = self.driver.terminate_connection(volume, connector)
|
res = self.driver.terminate_connection(volume, connector)
|
||||||
mock_unmap_volume.assert_called_once_with(self.VOLUME, self.SCSERVER)
|
mock_unmap_volume.assert_called_once_with(self.VOLUME, self.SCSERVER)
|
||||||
|
@ -2661,36 +2728,36 @@ class DellSCSanISCSIDriverTestCase(test.TestCase):
|
||||||
'instanceName': fake.VOLUME2_ID},
|
'instanceName': fake.VOLUME2_ID},
|
||||||
'secondaryVolume': {'instanceId': '102.101',
|
'secondaryVolume': {'instanceId': '102.101',
|
||||||
'instanceName': fake.VOLUME_ID},
|
'instanceName': fake.VOLUME_ID},
|
||||||
'secondaryScSerialNumber': 102}
|
'secondaryScSerialNumber': 102,
|
||||||
|
'secondaryRole': 'Secondary'}
|
||||||
postfail = {'instanceId': '101.100',
|
postfail = {'instanceId': '101.100',
|
||||||
'primaryVolume': {'instanceId': '102.101',
|
'primaryVolume': {'instanceId': '102.101',
|
||||||
'instanceName': fake.VOLUME_ID},
|
'instanceName': fake.VOLUME_ID},
|
||||||
'secondaryVolume': {'instanceId': '101.101',
|
'secondaryVolume': {'instanceId': '101.101',
|
||||||
'instanceName': fake.VOLUME2_ID},
|
'instanceName': fake.VOLUME2_ID},
|
||||||
'secondaryScSerialNumber': 102}
|
'secondaryScSerialNumber': 102,
|
||||||
|
'secondaryRole': 'Secondary'}
|
||||||
mock_api.get_live_volume = mock.MagicMock()
|
mock_api.get_live_volume = mock.MagicMock()
|
||||||
mock_api.get_live_volume.side_effect = [(sclivevol, False),
|
mock_api.get_live_volume.side_effect = [sclivevol, postfail,
|
||||||
(postfail, True),
|
sclivevol, sclivevol]
|
||||||
(sclivevol, False),
|
|
||||||
(sclivevol, False)
|
|
||||||
]
|
|
||||||
# Good run.
|
# Good run.
|
||||||
|
mock_api.is_swapped = mock.MagicMock(return_value=False)
|
||||||
mock_api.swap_roles_live_volume = mock.MagicMock(return_value=True)
|
mock_api.swap_roles_live_volume = mock.MagicMock(return_value=True)
|
||||||
model_update = {'provider_id': '102.101',
|
model_update = {'provider_id': '102.101',
|
||||||
'replication_status': 'failed-over'}
|
'replication_status': 'failed-over'}
|
||||||
ret = self.driver._failover_live_volume(mock_api, fake.VOLUME_ID,
|
ret = self.driver._failover_live_volume(mock_api, fake.VOLUME_ID,
|
||||||
'101.100')
|
'101.101')
|
||||||
self.assertEqual(model_update, ret)
|
self.assertEqual(model_update, ret)
|
||||||
# Swap fail
|
# Swap fail
|
||||||
mock_api.swap_roles_live_volume.return_value = False
|
mock_api.swap_roles_live_volume.return_value = False
|
||||||
model_update = {'status': 'error'}
|
model_update = {'status': 'error'}
|
||||||
ret = self.driver._failover_live_volume(mock_api, fake.VOLUME_ID,
|
ret = self.driver._failover_live_volume(mock_api, fake.VOLUME_ID,
|
||||||
'101.100')
|
'101.101')
|
||||||
self.assertEqual(model_update, ret)
|
self.assertEqual(model_update, ret)
|
||||||
# Can't find live volume.
|
# Can't find live volume.
|
||||||
mock_api.get_live_volume.return_value = (None, False)
|
mock_api.get_live_volume.return_value = None
|
||||||
ret = self.driver._failover_live_volume(mock_api, fake.VOLUME_ID,
|
ret = self.driver._failover_live_volume(mock_api, fake.VOLUME_ID,
|
||||||
'101.100')
|
'101.101')
|
||||||
self.assertEqual(model_update, ret)
|
self.assertEqual(model_update, ret)
|
||||||
|
|
||||||
def test__failover_replication(self,
|
def test__failover_replication(self,
|
||||||
|
@ -3206,16 +3273,16 @@ class DellSCSanISCSIDriverTestCase(test.TestCase):
|
||||||
{'id': fake.VOLUME2_ID,
|
{'id': fake.VOLUME2_ID,
|
||||||
'replication_driver_data': '12345',
|
'replication_driver_data': '12345',
|
||||||
'provider_id': '12345.2'}]
|
'provider_id': '12345.2'}]
|
||||||
mock_get_live_volume.side_effect = [(
|
mock_get_live_volume.side_effect = [
|
||||||
{'instanceId': '11111.101',
|
{'instanceId': '11111.101',
|
||||||
'secondaryVolume': {'instanceId': '11111.1001',
|
'secondaryVolume': {'instanceId': '11111.1001',
|
||||||
'instanceName': fake.VOLUME_ID},
|
'instanceName': fake.VOLUME_ID},
|
||||||
'secondaryScSerialNumber': 11111}, True), (
|
'secondaryScSerialNumber': 11111},
|
||||||
{'instanceId': '11111.102',
|
{'instanceId': '11111.102',
|
||||||
'secondaryVolume': {'instanceId': '11111.1002',
|
'secondaryVolume': {'instanceId': '11111.1002',
|
||||||
'instanceName': fake.VOLUME2_ID},
|
'instanceName': fake.VOLUME2_ID},
|
||||||
'secondaryScSerialNumber': 11111}, True
|
'secondaryScSerialNumber': 11111}
|
||||||
)]
|
]
|
||||||
mock_get_replication_specs.return_value = {'enabled': True,
|
mock_get_replication_specs.return_value = {'enabled': True,
|
||||||
'live': True}
|
'live': True}
|
||||||
mock_swap_roles_live_volume.side_effect = [True, True]
|
mock_swap_roles_live_volume.side_effect = [True, True]
|
||||||
|
|
|
@ -21,6 +21,7 @@ import uuid
|
||||||
from cinder import context
|
from cinder import context
|
||||||
from cinder import exception
|
from cinder import exception
|
||||||
from cinder import test
|
from cinder import test
|
||||||
|
from cinder.tests.unit import fake_constants as fake
|
||||||
from cinder.volume.drivers.dell import dell_storagecenter_api
|
from cinder.volume.drivers.dell import dell_storagecenter_api
|
||||||
|
|
||||||
|
|
||||||
|
@ -5977,7 +5978,7 @@ class DellSCSanAPITestCase(test.TestCase):
|
||||||
expected = 'StorageCenter/ScReplication/%s' % (
|
expected = 'StorageCenter/ScReplication/%s' % (
|
||||||
self.SCREPL[0]['instanceId'])
|
self.SCREPL[0]['instanceId'])
|
||||||
expected_payload = {'DeleteDestinationVolume': True,
|
expected_payload = {'DeleteDestinationVolume': True,
|
||||||
'RecycleDestinationVolume': False,
|
'RecycleDestinationVolume': True,
|
||||||
'DeleteRestorePoint': True}
|
'DeleteRestorePoint': True}
|
||||||
ret = self.scapi.delete_replication(self.VOLUME, destssn)
|
ret = self.scapi.delete_replication(self.VOLUME, destssn)
|
||||||
mock_delete.assert_any_call(expected, payload=expected_payload,
|
mock_delete.assert_any_call(expected, payload=expected_payload,
|
||||||
|
@ -6014,7 +6015,7 @@ class DellSCSanAPITestCase(test.TestCase):
|
||||||
expected = 'StorageCenter/ScReplication/%s' % (
|
expected = 'StorageCenter/ScReplication/%s' % (
|
||||||
self.SCREPL[0]['instanceId'])
|
self.SCREPL[0]['instanceId'])
|
||||||
expected_payload = {'DeleteDestinationVolume': True,
|
expected_payload = {'DeleteDestinationVolume': True,
|
||||||
'RecycleDestinationVolume': False,
|
'RecycleDestinationVolume': True,
|
||||||
'DeleteRestorePoint': True}
|
'DeleteRestorePoint': True}
|
||||||
ret = self.scapi.delete_replication(self.VOLUME, destssn)
|
ret = self.scapi.delete_replication(self.VOLUME, destssn)
|
||||||
mock_delete.assert_any_call(expected, payload=expected_payload,
|
mock_delete.assert_any_call(expected, payload=expected_payload,
|
||||||
|
@ -6399,37 +6400,60 @@ class DellSCSanAPITestCase(test.TestCase):
|
||||||
scvol,
|
scvol,
|
||||||
'a,b')
|
'a,b')
|
||||||
|
|
||||||
@mock.patch.object(dell_storagecenter_api.HttpClient,
|
|
||||||
'get')
|
|
||||||
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
|
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
|
||||||
'_get_json')
|
'_sc_live_volumes')
|
||||||
|
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
|
||||||
|
'_get_live_volumes')
|
||||||
def test_get_live_volume(self,
|
def test_get_live_volume(self,
|
||||||
mock_get_json,
|
mock_get_live_volumes,
|
||||||
mock_get,
|
mock_sc_live_volumes,
|
||||||
mock_close_connection,
|
mock_close_connection,
|
||||||
mock_open_connection,
|
mock_open_connection,
|
||||||
mock_init):
|
mock_init):
|
||||||
# Basic check
|
# Basic check
|
||||||
retlv, retswapped = self.scapi.get_live_volume(None)
|
retlv = self.scapi.get_live_volume(None)
|
||||||
self.assertIsNone(retlv)
|
self.assertIsNone(retlv)
|
||||||
self.assertFalse(retswapped)
|
|
||||||
lv1 = {'primaryVolume': {'instanceId': '12345.1'},
|
lv1 = {'primaryVolume': {'instanceId': '12345.1'},
|
||||||
'secondaryVolume': {'instanceId': '67890.1'}}
|
'secondaryVolume': {'instanceId': '67890.1'}}
|
||||||
lv2 = {'primaryVolume': {'instanceId': '12345.2'}}
|
lv2 = {'primaryVolume': {'instanceId': '12345.2'}}
|
||||||
mock_get_json.return_value = [lv1, lv2]
|
mock_sc_live_volumes.return_value = [lv1, lv2]
|
||||||
mock_get.return_value = self.RESPONSE_200
|
|
||||||
# Good Run
|
# Good Run
|
||||||
retlv, retswapped = self.scapi.get_live_volume('12345.2')
|
retlv = self.scapi.get_live_volume('12345.2')
|
||||||
self.assertEqual(lv2, retlv)
|
self.assertEqual(lv2, retlv)
|
||||||
self.assertFalse(retswapped)
|
mock_sc_live_volumes.assert_called_once_with('12345')
|
||||||
|
self.assertFalse(mock_get_live_volumes.called)
|
||||||
|
|
||||||
@mock.patch.object(dell_storagecenter_api.HttpClient,
|
|
||||||
'get')
|
|
||||||
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
|
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
|
||||||
'_get_json')
|
'_sc_live_volumes')
|
||||||
|
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
|
||||||
|
'_get_live_volumes')
|
||||||
|
def test_get_live_volume_on_secondary(self,
|
||||||
|
mock_get_live_volumes,
|
||||||
|
mock_sc_live_volumes,
|
||||||
|
mock_close_connection,
|
||||||
|
mock_open_connection,
|
||||||
|
mock_init):
|
||||||
|
# Basic check
|
||||||
|
retlv = self.scapi.get_live_volume(None)
|
||||||
|
self.assertIsNone(retlv)
|
||||||
|
lv1 = {'primaryVolume': {'instanceId': '12345.1'},
|
||||||
|
'secondaryVolume': {'instanceId': '67890.1'}}
|
||||||
|
lv2 = {'primaryVolume': {'instanceId': '12345.2'}}
|
||||||
|
mock_sc_live_volumes.return_value = []
|
||||||
|
mock_get_live_volumes.return_value = [lv1, lv2]
|
||||||
|
# Good Run
|
||||||
|
retlv = self.scapi.get_live_volume('12345.2')
|
||||||
|
self.assertEqual(lv2, retlv)
|
||||||
|
mock_sc_live_volumes.assert_called_once_with('12345')
|
||||||
|
mock_get_live_volumes.assert_called_once_with()
|
||||||
|
|
||||||
|
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
|
||||||
|
'_sc_live_volumes')
|
||||||
|
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
|
||||||
|
'_get_live_volumes')
|
||||||
def test_get_live_volume_not_found(self,
|
def test_get_live_volume_not_found(self,
|
||||||
mock_get_json,
|
mock_get_live_volumes,
|
||||||
mock_get,
|
mock_sc_live_volumes,
|
||||||
mock_close_connection,
|
mock_close_connection,
|
||||||
mock_open_connection,
|
mock_open_connection,
|
||||||
mock_init):
|
mock_init):
|
||||||
|
@ -6437,19 +6461,20 @@ class DellSCSanAPITestCase(test.TestCase):
|
||||||
'secondaryVolume': {'instanceId': '67890.1'}}
|
'secondaryVolume': {'instanceId': '67890.1'}}
|
||||||
lv2 = {'primaryVolume': {'instanceId': '12345.2'},
|
lv2 = {'primaryVolume': {'instanceId': '12345.2'},
|
||||||
'secondaryVolume': {'instanceId': '67890.2'}}
|
'secondaryVolume': {'instanceId': '67890.2'}}
|
||||||
mock_get_json.return_value = [lv1, lv2]
|
mock_get_live_volumes.return_value = [lv1, lv2]
|
||||||
mock_get.return_value = self.RESPONSE_200
|
mock_sc_live_volumes.return_value = []
|
||||||
retlv, retswapped = self.scapi.get_live_volume('12345.3')
|
retlv = self.scapi.get_live_volume('12345.3')
|
||||||
self.assertIsNone(retlv)
|
self.assertIsNone(retlv)
|
||||||
self.assertFalse(retswapped)
|
mock_sc_live_volumes.assert_called_once_with('12345')
|
||||||
|
mock_get_live_volumes.assert_called_once_with()
|
||||||
|
|
||||||
@mock.patch.object(dell_storagecenter_api.HttpClient,
|
|
||||||
'get')
|
|
||||||
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
|
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
|
||||||
'_get_json')
|
'_sc_live_volumes')
|
||||||
|
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
|
||||||
|
'_get_live_volumes')
|
||||||
def test_get_live_volume_swapped(self,
|
def test_get_live_volume_swapped(self,
|
||||||
mock_get_json,
|
mock_get_live_volumes,
|
||||||
mock_get,
|
mock_sc_live_volumes,
|
||||||
mock_close_connection,
|
mock_close_connection,
|
||||||
mock_open_connection,
|
mock_open_connection,
|
||||||
mock_init):
|
mock_init):
|
||||||
|
@ -6457,23 +6482,50 @@ class DellSCSanAPITestCase(test.TestCase):
|
||||||
'secondaryVolume': {'instanceId': '67890.1'}}
|
'secondaryVolume': {'instanceId': '67890.1'}}
|
||||||
lv2 = {'primaryVolume': {'instanceId': '67890.2'},
|
lv2 = {'primaryVolume': {'instanceId': '67890.2'},
|
||||||
'secondaryVolume': {'instanceId': '12345.2'}}
|
'secondaryVolume': {'instanceId': '12345.2'}}
|
||||||
mock_get_json.return_value = [lv1, lv2]
|
mock_get_live_volumes.return_value = [lv1, lv2]
|
||||||
mock_get.return_value = self.RESPONSE_200
|
mock_sc_live_volumes.return_value = []
|
||||||
retlv, retswapped = self.scapi.get_live_volume('12345.2')
|
retlv = self.scapi.get_live_volume('12345.2')
|
||||||
self.assertEqual(lv2, retlv)
|
self.assertEqual(lv2, retlv)
|
||||||
self.assertTrue(retswapped)
|
mock_sc_live_volumes.assert_called_once_with('12345')
|
||||||
|
mock_get_live_volumes.assert_called_once_with()
|
||||||
|
|
||||||
@mock.patch.object(dell_storagecenter_api.HttpClient,
|
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
|
||||||
'get')
|
'_sc_live_volumes')
|
||||||
|
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
|
||||||
|
'_get_live_volumes')
|
||||||
def test_get_live_volume_error(self,
|
def test_get_live_volume_error(self,
|
||||||
mock_get,
|
mock_get_live_volumes,
|
||||||
|
mock_sc_live_volumes,
|
||||||
mock_close_connection,
|
mock_close_connection,
|
||||||
mock_open_connection,
|
mock_open_connection,
|
||||||
mock_init):
|
mock_init):
|
||||||
mock_get.return_value = self.RESPONSE_400
|
mock_get_live_volumes.return_value = []
|
||||||
retlv, retswapped = self.scapi.get_live_volume('12345.2')
|
mock_sc_live_volumes.return_value = []
|
||||||
|
retlv = self.scapi.get_live_volume('12345.2')
|
||||||
self.assertIsNone(retlv)
|
self.assertIsNone(retlv)
|
||||||
self.assertFalse(retswapped)
|
|
||||||
|
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
|
||||||
|
'_sc_live_volumes')
|
||||||
|
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
|
||||||
|
'_get_live_volumes')
|
||||||
|
def test_get_live_volume_by_name(self,
|
||||||
|
mock_get_live_volumes,
|
||||||
|
mock_sc_live_volumes,
|
||||||
|
mock_close_connection,
|
||||||
|
mock_open_connection,
|
||||||
|
mock_init):
|
||||||
|
lv1 = {'primaryVolume': {'instanceId': '12345.1'},
|
||||||
|
'secondaryVolume': {'instanceId': '67890.1'},
|
||||||
|
'instanceName': 'Live volume of ' + fake.VOLUME2_ID}
|
||||||
|
lv2 = {'primaryVolume': {'instanceId': '67890.2'},
|
||||||
|
'secondaryVolume': {'instanceId': '12345.2'},
|
||||||
|
'instanceName': 'Live volume of ' + fake.VOLUME_ID}
|
||||||
|
mock_get_live_volumes.return_value = [lv1, lv2]
|
||||||
|
mock_sc_live_volumes.return_value = []
|
||||||
|
retlv = self.scapi.get_live_volume('12345.2', fake.VOLUME_ID)
|
||||||
|
self.assertEqual(lv2, retlv)
|
||||||
|
mock_sc_live_volumes.assert_called_once_with('12345')
|
||||||
|
mock_get_live_volumes.assert_called_once_with()
|
||||||
|
|
||||||
@mock.patch.object(dell_storagecenter_api.HttpClient,
|
@mock.patch.object(dell_storagecenter_api.HttpClient,
|
||||||
'post')
|
'post')
|
||||||
|
|
|
@ -1091,20 +1091,26 @@ class StorageCenterApi(object):
|
||||||
def _autofailback(self, lv):
|
def _autofailback(self, lv):
|
||||||
# if we have a working replication state.
|
# if we have a working replication state.
|
||||||
ret = False
|
ret = False
|
||||||
if (lv['ReplicationState'] == 'Up' and
|
LOG.debug('Attempting autofailback of %s', lv)
|
||||||
lv['failoverState'] == 'AutoFailedOver'):
|
if (lv and lv['status'] == 'Up' and lv['replicationState'] == 'Up' and
|
||||||
|
lv['failoverState'] == 'Protected' and lv['secondaryStatus'] == 'Up'
|
||||||
|
and lv['primarySwapRoleState'] == 'NotSwapping'):
|
||||||
ret = self.swap_roles_live_volume(lv)
|
ret = self.swap_roles_live_volume(lv)
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
def _find_volume_primary(self, provider_id):
|
def _find_volume_primary(self, provider_id, name):
|
||||||
# if there is no live volume then we return our provider_id.
|
# if there is no live volume then we return our provider_id.
|
||||||
primary_id = provider_id
|
primary_id = provider_id
|
||||||
lv, swapped = self.get_live_volume(provider_id)
|
lv = self.get_live_volume(provider_id, name)
|
||||||
# if we swapped see if we can autofailback. Unless the admin
|
LOG.info(_LI('Volume %(provider)s at primary %(primary)s.'),
|
||||||
# failed us over, that is.
|
{'provider': provider_id, 'primary': primary_id})
|
||||||
if swapped and not self.failed_over:
|
# If we have a live volume and are swapped and are not failed over
|
||||||
|
# at least give failback a shot.
|
||||||
|
if lv and self.is_swapped(provider_id, lv) and not self.failed_over:
|
||||||
if self._autofailback(lv):
|
if self._autofailback(lv):
|
||||||
ls, swapped = self.get_live_volume(provider_id)
|
lv = self.get_live_volume(provider_id)
|
||||||
|
LOG.info(_LI('After failback %s'), lv)
|
||||||
|
|
||||||
if lv:
|
if lv:
|
||||||
primary_id = lv['primaryVolume']['instanceId']
|
primary_id = lv['primaryVolume']['instanceId']
|
||||||
return primary_id
|
return primary_id
|
||||||
|
@ -1127,7 +1133,7 @@ class StorageCenterApi(object):
|
||||||
scvolume = None
|
scvolume = None
|
||||||
if islivevol:
|
if islivevol:
|
||||||
# Just get the primary from the sc live vol.
|
# Just get the primary from the sc live vol.
|
||||||
primary_id = self._find_volume_primary(provider_id)
|
primary_id = self._find_volume_primary(provider_id, name)
|
||||||
scvolume = self.get_volume(primary_id)
|
scvolume = self.get_volume(primary_id)
|
||||||
elif self._use_provider_id(provider_id):
|
elif self._use_provider_id(provider_id):
|
||||||
# just get our volume
|
# just get our volume
|
||||||
|
@ -2733,7 +2739,7 @@ class StorageCenterApi(object):
|
||||||
if replication:
|
if replication:
|
||||||
payload = {}
|
payload = {}
|
||||||
payload['DeleteDestinationVolume'] = deletedestvolume
|
payload['DeleteDestinationVolume'] = deletedestvolume
|
||||||
payload['RecycleDestinationVolume'] = False
|
payload['RecycleDestinationVolume'] = deletedestvolume
|
||||||
payload['DeleteRestorePoint'] = True
|
payload['DeleteRestorePoint'] = True
|
||||||
r = self.client.delete('StorageCenter/ScReplication/%s' %
|
r = self.client.delete('StorageCenter/ScReplication/%s' %
|
||||||
self._get_id(replication), payload=payload,
|
self._get_id(replication), payload=payload,
|
||||||
|
@ -3053,24 +3059,73 @@ class StorageCenterApi(object):
|
||||||
progress)
|
progress)
|
||||||
return None, None
|
return None, None
|
||||||
|
|
||||||
def get_live_volume(self, primaryid):
|
def is_swapped(self, provider_id, sclivevolume):
|
||||||
|
if (sclivevolume.get('primaryVolume') and
|
||||||
|
sclivevolume['primaryVolume']['instanceId'] != provider_id):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def is_failed_over(self, provider_id, sclivevolume):
|
||||||
|
# either the secondary is active or the secondary is now our primary.
|
||||||
|
if (sclivevolume.get('secondaryRole') == 'Activated' or
|
||||||
|
self.is_swapped(provider_id, sclivevolume)):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _sc_live_volumes(self, ssn):
|
||||||
|
if ssn:
|
||||||
|
r = self.client.get('StorageCenter/StorageCenter/%s/LiveVolumeList'
|
||||||
|
% ssn)
|
||||||
|
if self._check_result(r):
|
||||||
|
return self._get_json(r)
|
||||||
|
return []
|
||||||
|
|
||||||
|
def _get_live_volumes(self):
|
||||||
|
# Work around for a FW bug. Instead of grabbing the entire list at
|
||||||
|
# once we have to Trundle through each SC's list.
|
||||||
|
lvs = []
|
||||||
|
pf = self._get_payload_filter()
|
||||||
|
pf.append('connected', True)
|
||||||
|
r = self.client.post('StorageCenter/StorageCenter/GetList',
|
||||||
|
pf.payload)
|
||||||
|
if self._check_result(r):
|
||||||
|
# Should return [] if nothing there.
|
||||||
|
# Just in case do the or.
|
||||||
|
scs = self._get_json(r) or []
|
||||||
|
for sc in scs:
|
||||||
|
lvs += self._sc_live_volumes(self._get_id(sc))
|
||||||
|
return lvs
|
||||||
|
|
||||||
|
def get_live_volume(self, primaryid, name=None):
|
||||||
"""Get's the live ScLiveVolume object for the vol with primaryid.
|
"""Get's the live ScLiveVolume object for the vol with primaryid.
|
||||||
|
|
||||||
:param primaryid: InstanceId of the primary volume.
|
:param primaryid: InstanceId of the primary volume.
|
||||||
:return: ScLiveVolume object or None, swapped True/False.
|
:parma name: Volume name associated with this live volume.
|
||||||
|
:return: ScLiveVolume object or None
|
||||||
"""
|
"""
|
||||||
|
sclivevol = None
|
||||||
if primaryid:
|
if primaryid:
|
||||||
r = self.client.get('StorageCenter/ScLiveVolume')
|
# Try from our primary SSN. This would be the authoritay on the
|
||||||
if self._check_result(r):
|
# Live Volume in question.
|
||||||
lvs = self._get_json(r)
|
lvs = self._sc_live_volumes(primaryid.split('.')[0])
|
||||||
|
# No, grab them all and see if we are on the secondary.
|
||||||
|
if not lvs:
|
||||||
|
lvs = self._get_live_volumes()
|
||||||
|
if lvs:
|
||||||
|
# Look for our primaryid.
|
||||||
for lv in lvs:
|
for lv in lvs:
|
||||||
if (lv.get('primaryVolume') and
|
if ((lv.get('primaryVolume') and
|
||||||
lv['primaryVolume']['instanceId'] == primaryid):
|
lv['primaryVolume']['instanceId'] == primaryid) or
|
||||||
return lv, False
|
(lv.get('secondaryVolume') and
|
||||||
if (lv.get('secondaryVolume') and
|
lv['secondaryVolume']['instanceId'] == primaryid)):
|
||||||
lv['secondaryVolume']['instanceId'] == primaryid):
|
sclivevol = lv
|
||||||
return lv, True
|
break
|
||||||
return None, False
|
# Sometimes the lv object returns without a secondary
|
||||||
|
# volume. Make sure we find this by name if we have to.
|
||||||
|
if (name and sclivevol is None and
|
||||||
|
lv['instanceName'].endswith(name)):
|
||||||
|
sclivevol = lv
|
||||||
|
return sclivevol
|
||||||
|
|
||||||
def _get_hbas(self, serverid):
|
def _get_hbas(self, serverid):
|
||||||
# Helper to get the hba's of a given server.
|
# Helper to get the hba's of a given server.
|
||||||
|
@ -3170,6 +3225,7 @@ class StorageCenterApi(object):
|
||||||
payload['ConvertToReplication'] = False
|
payload['ConvertToReplication'] = False
|
||||||
payload['DeleteSecondaryVolume'] = deletesecondaryvolume
|
payload['DeleteSecondaryVolume'] = deletesecondaryvolume
|
||||||
payload['RecycleSecondaryVolume'] = deletesecondaryvolume
|
payload['RecycleSecondaryVolume'] = deletesecondaryvolume
|
||||||
|
payload['DeleteRestorePoint'] = deletesecondaryvolume
|
||||||
r = self.client.delete('StorageCenter/ScLiveVolume/%s' %
|
r = self.client.delete('StorageCenter/ScLiveVolume/%s' %
|
||||||
self._get_id(sclivevolume), payload, True)
|
self._get_id(sclivevolume), payload, True)
|
||||||
if self._check_result(r):
|
if self._check_result(r):
|
||||||
|
|
|
@ -362,8 +362,7 @@ class DellCommonDriver(driver.ConsistencyGroupVD, driver.ManageableVD,
|
||||||
ssnstrings = self._split_driver_data(replication_driver_data)
|
ssnstrings = self._split_driver_data(replication_driver_data)
|
||||||
if ssnstrings:
|
if ssnstrings:
|
||||||
ssn = int(ssnstrings[0])
|
ssn = int(ssnstrings[0])
|
||||||
sclivevolume, swapped = api.get_live_volume(
|
sclivevolume = api.get_live_volume(volume.get('provider_id'))
|
||||||
volume.get('provider_id'))
|
|
||||||
# Have we found the live volume?
|
# Have we found the live volume?
|
||||||
if (sclivevolume and
|
if (sclivevolume and
|
||||||
sclivevolume.get('secondaryScSerialNumber') == ssn and
|
sclivevolume.get('secondaryScSerialNumber') == ssn and
|
||||||
|
@ -682,12 +681,7 @@ class DellCommonDriver(driver.ConsistencyGroupVD, driver.ManageableVD,
|
||||||
def _update_volume_stats(self):
|
def _update_volume_stats(self):
|
||||||
"""Retrieve stats info from volume group."""
|
"""Retrieve stats info from volume group."""
|
||||||
with self._client.open_connection() as api:
|
with self._client.open_connection() as api:
|
||||||
storageusage = api.get_storage_usage()
|
# Static stats.
|
||||||
if not storageusage:
|
|
||||||
msg = _('Unable to retrieve volume stats.')
|
|
||||||
raise exception.VolumeBackendAPIException(message=msg)
|
|
||||||
|
|
||||||
# all of this is basically static for now
|
|
||||||
data = {}
|
data = {}
|
||||||
data['volume_backend_name'] = self.backend_name
|
data['volume_backend_name'] = self.backend_name
|
||||||
data['vendor_name'] = 'Dell'
|
data['vendor_name'] = 'Dell'
|
||||||
|
@ -696,12 +690,6 @@ class DellCommonDriver(driver.ConsistencyGroupVD, driver.ManageableVD,
|
||||||
data['reserved_percentage'] = 0
|
data['reserved_percentage'] = 0
|
||||||
data['consistencygroup_support'] = True
|
data['consistencygroup_support'] = True
|
||||||
data['thin_provisioning_support'] = True
|
data['thin_provisioning_support'] = True
|
||||||
totalcapacity = storageusage.get('availableSpace')
|
|
||||||
totalcapacitygb = self._bytes_to_gb(totalcapacity)
|
|
||||||
data['total_capacity_gb'] = totalcapacitygb
|
|
||||||
freespace = storageusage.get('freeSpace')
|
|
||||||
freespacegb = self._bytes_to_gb(freespace)
|
|
||||||
data['free_capacity_gb'] = freespacegb
|
|
||||||
data['QoS_support'] = False
|
data['QoS_support'] = False
|
||||||
data['replication_enabled'] = self.replication_enabled
|
data['replication_enabled'] = self.replication_enabled
|
||||||
if self.replication_enabled:
|
if self.replication_enabled:
|
||||||
|
@ -715,6 +703,22 @@ class DellCommonDriver(driver.ConsistencyGroupVD, driver.ManageableVD,
|
||||||
replication_targets.append(target_device_id)
|
replication_targets.append(target_device_id)
|
||||||
data['replication_targets'] = replication_targets
|
data['replication_targets'] = replication_targets
|
||||||
|
|
||||||
|
# Get our capacity.
|
||||||
|
storageusage = api.get_storage_usage()
|
||||||
|
if storageusage:
|
||||||
|
# Get actual stats.
|
||||||
|
totalcapacity = storageusage.get('availableSpace')
|
||||||
|
totalcapacitygb = self._bytes_to_gb(totalcapacity)
|
||||||
|
data['total_capacity_gb'] = totalcapacitygb
|
||||||
|
freespace = storageusage.get('freeSpace')
|
||||||
|
freespacegb = self._bytes_to_gb(freespace)
|
||||||
|
data['free_capacity_gb'] = freespacegb
|
||||||
|
else:
|
||||||
|
# Soldier on. Just return 0 for this iteration.
|
||||||
|
LOG.error(_LE('Unable to retrieve volume stats.'))
|
||||||
|
data['total_capacity_gb'] = 0
|
||||||
|
data['free_capacity_gb'] = 0
|
||||||
|
|
||||||
self._stats = data
|
self._stats = data
|
||||||
LOG.debug('Total cap %(total)s Free cap %(free)s',
|
LOG.debug('Total cap %(total)s Free cap %(free)s',
|
||||||
{'total': data['total_capacity_gb'],
|
{'total': data['total_capacity_gb'],
|
||||||
|
@ -1390,7 +1394,10 @@ class DellCommonDriver(driver.ConsistencyGroupVD, driver.ManageableVD,
|
||||||
:return: model_update dict
|
:return: model_update dict
|
||||||
"""
|
"""
|
||||||
model_update = {}
|
model_update = {}
|
||||||
sclivevolume, swapped = api.get_live_volume(provider_id)
|
# We do not search by name. Only failback if we have a complete
|
||||||
|
# LV object.
|
||||||
|
sclivevolume = api.get_live_volume(provider_id)
|
||||||
|
# TODO(tswanson): Check swapped state first.
|
||||||
if sclivevolume and api.swap_roles_live_volume(sclivevolume):
|
if sclivevolume and api.swap_roles_live_volume(sclivevolume):
|
||||||
LOG.info(_LI('Success swapping sclivevolume roles %s'), id)
|
LOG.info(_LI('Success swapping sclivevolume roles %s'), id)
|
||||||
model_update = {
|
model_update = {
|
||||||
|
@ -1489,8 +1496,10 @@ class DellCommonDriver(driver.ConsistencyGroupVD, driver.ManageableVD,
|
||||||
|
|
||||||
def _failover_live_volume(self, api, id, provider_id):
|
def _failover_live_volume(self, api, id, provider_id):
|
||||||
model_update = {}
|
model_update = {}
|
||||||
sclivevolume, swapped = api.get_live_volume(provider_id)
|
# Search for volume by id if we have to.
|
||||||
|
sclivevolume = api.get_live_volume(provider_id, id)
|
||||||
if sclivevolume:
|
if sclivevolume:
|
||||||
|
swapped = api.is_swapped(provider_id, sclivevolume)
|
||||||
# If we aren't swapped try it. If fail error out.
|
# If we aren't swapped try it. If fail error out.
|
||||||
if not swapped and not api.swap_roles_live_volume(sclivevolume):
|
if not swapped and not api.swap_roles_live_volume(sclivevolume):
|
||||||
LOG.info(_LI('Failure swapping roles %s'), id)
|
LOG.info(_LI('Failure swapping roles %s'), id)
|
||||||
|
@ -1498,7 +1507,7 @@ class DellCommonDriver(driver.ConsistencyGroupVD, driver.ManageableVD,
|
||||||
return model_update
|
return model_update
|
||||||
|
|
||||||
LOG.info(_LI('Success swapping sclivevolume roles %s'), id)
|
LOG.info(_LI('Success swapping sclivevolume roles %s'), id)
|
||||||
sclivevolume, swapped = api.get_live_volume(provider_id)
|
sclivevolume = api.get_live_volume(provider_id)
|
||||||
model_update = {
|
model_update = {
|
||||||
'replication_status':
|
'replication_status':
|
||||||
fields.ReplicationStatus.FAILED_OVER,
|
fields.ReplicationStatus.FAILED_OVER,
|
||||||
|
|
|
@ -90,52 +90,59 @@ class DellStorageCenterFCDriver(dell_storagecenter_common.DellCommonDriver,
|
||||||
with self._client.open_connection() as api:
|
with self._client.open_connection() as api:
|
||||||
try:
|
try:
|
||||||
wwpns = connector.get('wwpns')
|
wwpns = connector.get('wwpns')
|
||||||
# Find our server.
|
# Find the volume on the storage center. Note that if this
|
||||||
scserver = self._find_server(api, wwpns)
|
# is live volume and we are swapped this will be the back
|
||||||
|
# half of the live volume.
|
||||||
# No? Create it.
|
|
||||||
if scserver is None:
|
|
||||||
scserver = api.create_server(
|
|
||||||
wwpns, self.configuration.dell_server_os)
|
|
||||||
# Find the volume on the storage center.
|
|
||||||
scvolume = api.find_volume(volume_name, provider_id, islivevol)
|
scvolume = api.find_volume(volume_name, provider_id, islivevol)
|
||||||
if scserver is not None and scvolume is not None:
|
if scvolume:
|
||||||
mapping = api.map_volume(scvolume, scserver)
|
# Get the SSN it is on.
|
||||||
if mapping is not None:
|
ssn = scvolume['instanceId'].split('.')[0]
|
||||||
# Since we just mapped our volume we had best update
|
# Find our server.
|
||||||
# our sc volume object.
|
scserver = self._find_server(api, wwpns, ssn)
|
||||||
scvolume = api.get_volume(scvolume['instanceId'])
|
|
||||||
lun, targets, init_targ_map = api.find_wwns(scvolume,
|
|
||||||
scserver)
|
|
||||||
|
|
||||||
# Do we have extra live volume work?
|
# No? Create it.
|
||||||
if islivevol:
|
if scserver is None:
|
||||||
# Get our volume and our swap state.
|
scserver = api.create_server(
|
||||||
sclivevolume, swapped = api.get_live_volume(
|
wwpns, self.configuration.dell_server_os, ssn)
|
||||||
provider_id)
|
# We have a volume and a server. Map them.
|
||||||
# Do not map to a failed over volume.
|
if scserver is not None:
|
||||||
if sclivevolume and not swapped:
|
mapping = api.map_volume(scvolume, scserver)
|
||||||
# Now map our secondary.
|
if mapping is not None:
|
||||||
lvlun, lvtargets, lvinit_targ_map = (
|
# Since we just mapped our volume we had
|
||||||
self.initialize_secondary(api,
|
# best update our sc volume object.
|
||||||
sclivevolume,
|
scvolume = api.get_volume(scvolume['instanceId'])
|
||||||
wwpns))
|
lun, targets, init_targ_map = api.find_wwns(
|
||||||
# Unmapped. Add info to our list.
|
scvolume, scserver)
|
||||||
targets += lvtargets
|
|
||||||
init_targ_map.update(lvinit_targ_map)
|
|
||||||
|
|
||||||
# Roll up our return data.
|
# Do we have extra live volume work?
|
||||||
if lun is not None and len(targets) > 0:
|
if islivevol:
|
||||||
data = {'driver_volume_type': 'fibre_channel',
|
# Get our live volume.
|
||||||
'data': {'target_lun': lun,
|
sclivevolume = api.get_live_volume(provider_id)
|
||||||
'target_discovered': True,
|
# Do not map to a failed over volume.
|
||||||
'target_wwn': targets,
|
if (sclivevolume and not
|
||||||
'initiator_target_map':
|
api.is_failed_over(provider_id,
|
||||||
init_targ_map,
|
sclivevolume)):
|
||||||
'discard': True}}
|
# Now map our secondary.
|
||||||
LOG.debug('Return FC data: %s', data)
|
lvlun, lvtargets, lvinit_targ_map = (
|
||||||
return data
|
self.initialize_secondary(api,
|
||||||
LOG.error(_LE('Lun mapping returned null!'))
|
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:
|
except Exception:
|
||||||
with excutils.save_and_reraise_exception():
|
with excutils.save_and_reraise_exception():
|
||||||
|
@ -191,46 +198,53 @@ class DellStorageCenterFCDriver(dell_storagecenter_common.DellCommonDriver,
|
||||||
with self._client.open_connection() as api:
|
with self._client.open_connection() as api:
|
||||||
try:
|
try:
|
||||||
wwpns = connector.get('wwpns')
|
wwpns = connector.get('wwpns')
|
||||||
scserver = self._find_server(api, wwpns)
|
|
||||||
|
|
||||||
# Find the volume on the storage center.
|
# Find the volume on the storage center.
|
||||||
scvolume = api.find_volume(volume_name, provider_id, islivevol)
|
scvolume = api.find_volume(volume_name, provider_id, islivevol)
|
||||||
# Get our target map so we can return it to free up a zone.
|
if scvolume:
|
||||||
lun, targets, init_targ_map = api.find_wwns(scvolume, scserver)
|
# Get the SSN it is on.
|
||||||
|
ssn = scvolume['instanceId'].split('.')[0]
|
||||||
|
|
||||||
# Do we have extra live volume work?
|
scserver = self._find_server(api, wwpns, ssn)
|
||||||
if islivevol:
|
|
||||||
# Get our volume and our swap state.
|
|
||||||
sclivevolume, swapped = api.get_live_volume(
|
|
||||||
provider_id)
|
|
||||||
# Do not map to a failed over volume.
|
|
||||||
if sclivevolume and not swapped:
|
|
||||||
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.
|
# Get our target map so we can return it to free up a zone.
|
||||||
if (scserver is not None and
|
lun, targets, init_targ_map = api.find_wwns(scvolume,
|
||||||
scvolume is not None and
|
scserver)
|
||||||
api.unmap_volume(scvolume, scserver) is True):
|
|
||||||
LOG.debug('Connection terminated')
|
|
||||||
else:
|
|
||||||
raise exception.VolumeBackendAPIException(
|
|
||||||
_('Terminate connection failed'))
|
|
||||||
|
|
||||||
# basic return info...
|
# Do we have extra live volume work?
|
||||||
info = {'driver_volume_type': 'fibre_channel',
|
if islivevol:
|
||||||
'data': {}}
|
# 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 not then we return the target map so that
|
# If we have a server and a volume lets unmap them.
|
||||||
# the zone can be freed up.
|
if (scserver is not None and
|
||||||
if api.get_volume_count(scserver) == 0:
|
scvolume is not None and
|
||||||
info['data'] = {'target_wwn': targets,
|
api.unmap_volume(scvolume, scserver) is True):
|
||||||
'initiator_target_map': init_targ_map}
|
LOG.debug('Connection terminated')
|
||||||
return info
|
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:
|
except Exception:
|
||||||
with excutils.save_and_reraise_exception():
|
with excutils.save_and_reraise_exception():
|
||||||
|
|
|
@ -96,59 +96,68 @@ class DellStorageCenterISCSIDriver(dell_storagecenter_common.DellCommonDriver,
|
||||||
|
|
||||||
with self._client.open_connection() as api:
|
with self._client.open_connection() as api:
|
||||||
try:
|
try:
|
||||||
# Find our server.
|
# Find the volume on the storage center. Note that if this
|
||||||
scserver = api.find_server(initiator_name)
|
# is live volume and we are swapped this will be the back
|
||||||
# No? Create it.
|
# half of the live volume.
|
||||||
if scserver is None:
|
scvolume = api.find_volume(volume_name, provider_id, islivevol)
|
||||||
scserver = api.create_server(
|
if scvolume:
|
||||||
[initiator_name], self.configuration.dell_server_os)
|
# Get the SSN it is on.
|
||||||
# Find the volume on the storage center.
|
ssn = scvolume['instanceId'].split('.')[0]
|
||||||
scvolume = api.find_volume(volume_name, provider_id)
|
# 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 we have a server and a volume lets bring them
|
||||||
if scserver is not None and scvolume is not None:
|
# together.
|
||||||
mapping = api.map_volume(scvolume, scserver)
|
if scserver is not None:
|
||||||
if mapping is not None:
|
mapping = api.map_volume(scvolume, scserver)
|
||||||
# Since we just mapped our volume we had best update
|
if mapping is not None:
|
||||||
# our sc volume object.
|
# Since we just mapped our volume we had best
|
||||||
scvolume = api.get_volume(provider_id)
|
# update our sc volume object.
|
||||||
# Our return.
|
scvolume = api.get_volume(scvolume['instanceId'])
|
||||||
iscsiprops = {}
|
# Our return.
|
||||||
|
iscsiprops = {}
|
||||||
|
|
||||||
# Three cases that should all be satisfied with the
|
# Three cases that should all be satisfied with the
|
||||||
# same return of Target_Portal and Target_Portals.
|
# same return of Target_Portal and Target_Portals.
|
||||||
# 1. Nova is calling us so we need to return the
|
# 1. Nova is calling us so we need to return the
|
||||||
# Target_Portal stuff. It should ignore the
|
# Target_Portal stuff. It should ignore the
|
||||||
# Target_Portals stuff.
|
# Target_Portals stuff.
|
||||||
# 2. OS brick is calling us in multipath mode so we
|
# 2. OS brick is calling us in multipath mode so we
|
||||||
# want to return Target_Portals. It will ignore
|
# want to return Target_Portals. It will ignore
|
||||||
# the Target_Portal stuff.
|
# the Target_Portal stuff.
|
||||||
# 3. OS brick is calling us in single path mode so
|
# 3. OS brick is calling us in single path mode so
|
||||||
# we want to return Target_Portal and
|
# we want to return Target_Portal and
|
||||||
# Target_Portals as alternates.
|
# Target_Portals as alternates.
|
||||||
iscsiprops = api.find_iscsi_properties(scvolume)
|
iscsiprops = api.find_iscsi_properties(scvolume)
|
||||||
|
|
||||||
# If this is a live volume we need to map up our
|
# If this is a live volume we need to map up our
|
||||||
# secondary volume.
|
# secondary volume. Note that if we have failed
|
||||||
if islivevol:
|
# over we do not wish to do this.
|
||||||
sclivevolume, swapped = api.get_live_volume(
|
if islivevol:
|
||||||
provider_id)
|
sclivevolume = api.get_live_volume(provider_id)
|
||||||
# Only map if we are not swapped.
|
# Only map if we are not failed over.
|
||||||
if sclivevolume and not swapped:
|
if (sclivevolume and not
|
||||||
secondaryprops = self.initialize_secondary(
|
api.is_failed_over(provider_id,
|
||||||
api, sclivevolume, initiator_name)
|
sclivevolume)):
|
||||||
# Combine with iscsiprops
|
secondaryprops = self.initialize_secondary(
|
||||||
iscsiprops['target_iqns'] += (
|
api, sclivevolume, initiator_name)
|
||||||
secondaryprops['target_iqns'])
|
# Combine with iscsiprops
|
||||||
iscsiprops['target_portals'] += (
|
iscsiprops['target_iqns'] += (
|
||||||
secondaryprops['target_portals'])
|
secondaryprops['target_iqns'])
|
||||||
iscsiprops['target_luns'] += (
|
iscsiprops['target_portals'] += (
|
||||||
secondaryprops['target_luns'])
|
secondaryprops['target_portals'])
|
||||||
|
iscsiprops['target_luns'] += (
|
||||||
|
secondaryprops['target_luns'])
|
||||||
|
|
||||||
# Return our iscsi properties.
|
# Return our iscsi properties.
|
||||||
iscsiprops['discard'] = True
|
iscsiprops['discard'] = True
|
||||||
return {'driver_volume_type': 'iscsi',
|
return {'driver_volume_type': 'iscsi',
|
||||||
'data': iscsiprops}
|
'data': iscsiprops}
|
||||||
# Re-raise any backend exception.
|
# Re-raise any backend exception.
|
||||||
except exception.VolumeBackendAPIException:
|
except exception.VolumeBackendAPIException:
|
||||||
with excutils.save_and_reraise_exception():
|
with excutils.save_and_reraise_exception():
|
||||||
|
@ -214,23 +223,31 @@ class DellStorageCenterISCSIDriver(dell_storagecenter_common.DellCommonDriver,
|
||||||
'initiator': initiator_name})
|
'initiator': initiator_name})
|
||||||
with self._client.open_connection() as api:
|
with self._client.open_connection() as api:
|
||||||
try:
|
try:
|
||||||
scserver = api.find_server(initiator_name)
|
# Find the volume on the storage center. Note that if this
|
||||||
# Find the volume on the storage center.
|
# is live volume and we are swapped this will be the back
|
||||||
scvolume = api.find_volume(volume_name, provider_id)
|
# 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)
|
||||||
|
|
||||||
# Unmap our secondary if it isn't swapped.
|
# Unmap our secondary if not failed over..
|
||||||
if islivevol:
|
if islivevol:
|
||||||
sclivevolume, swapped = api.get_live_volume(provider_id)
|
sclivevolume = api.get_live_volume(provider_id)
|
||||||
if sclivevolume and not swapped:
|
if (sclivevolume and not
|
||||||
self.terminate_secondary(api, sclivevolume,
|
api.is_failed_over(provider_id,
|
||||||
initiator_name)
|
sclivevolume)):
|
||||||
|
self.terminate_secondary(api, sclivevolume,
|
||||||
|
initiator_name)
|
||||||
|
|
||||||
# If we have a server and a volume lets pull them apart.
|
# If we have a server and a volume lets pull them apart.
|
||||||
if (scserver is not None and
|
if (scserver is not None and
|
||||||
scvolume is not None and
|
scvolume is not None and
|
||||||
api.unmap_volume(scvolume, scserver) is True):
|
api.unmap_volume(scvolume, scserver) is True):
|
||||||
LOG.debug('Connection terminated')
|
LOG.debug('Connection terminated')
|
||||||
return
|
return
|
||||||
except Exception:
|
except Exception:
|
||||||
with excutils.save_and_reraise_exception():
|
with excutils.save_and_reraise_exception():
|
||||||
LOG.error(_LE('Failed to terminate connection '
|
LOG.error(_LE('Failed to terminate connection '
|
||||||
|
|
Loading…
Reference in New Issue