Dell SC: Use Live Volume for replication

Adding support for Dell SC live volume in replication. If
an extra spec is set the driver will attempt to create live
volumes instead of normal replications.

Added to the test coverage.

Minor refactor to failback.

DocImpact
Change-Id: Id0056ec9b9a42005b1f7cb2d8cd5aee208d8015c
This commit is contained in:
Tom Swanson 2016-06-07 14:27:58 -05:00
parent aaf820f429
commit fecbf75edc
8 changed files with 1963 additions and 450 deletions

View File

@ -176,7 +176,7 @@ class DellSCSanFCDriverTestCase(test.TestCase):
'find_server',
return_value=None)
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'create_server_multiple_hbas',
'create_server',
return_value=SCSERVER)
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'find_volume',
@ -225,6 +225,69 @@ class DellSCSanFCDriverTestCase(test.TestCase):
mock_find_volume.assert_called_once_with(fake.VOLUME_ID, None)
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',
return_value=VOLUME)
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'get_volume',
return_value=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')
def test_initialize_connection_live_vol(self,
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}
connector = self.connector
sclivevol = {'instanceId': '101.101',
'secondaryVolume': {'instanceId': '102.101',
'instanceName': fake.VOLUME_ID},
'secondaryScSerialNumber': 102}
mock_is_live_volume.return_value = sclivevol
mock_find_wwns.return_value = (
1, [u'5000D31000FCBE3D', u'5000D31000FCBE35'],
{u'21000024FF30441C': [u'5000D31000FCBE35'],
u'21000024FF30441D': [u'5000D31000FCBE3D']})
mock_initialize_secondary.return_value = (
1, [u'5000D31000FCBE3E', u'5000D31000FCBE36'],
{u'21000024FF30441E': [u'5000D31000FCBE36'],
u'21000024FF30441F': [u'5000D31000FCBE3E']})
res = self.driver.initialize_connection(volume, connector)
expected = {'data':
{'discard': True,
'initiator_target_map':
{u'21000024FF30441C': [u'5000D31000FCBE35'],
u'21000024FF30441D': [u'5000D31000FCBE3D'],
u'21000024FF30441E': [u'5000D31000FCBE36'],
u'21000024FF30441F': [u'5000D31000FCBE3E']},
'target_discovered': True,
'target_lun': 1,
'target_wwn': [u'5000D31000FCBE3D', u'5000D31000FCBE35',
u'5000D31000FCBE3E', u'5000D31000FCBE36']},
'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
mock_find_volume.assert_called_once_with(fake.VOLUME_ID, None)
mock_get_volume.assert_called_once_with(self.VOLUME[u'instanceId'])
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'find_server',
return_value=SCSERVER)
@ -260,7 +323,7 @@ class DellSCSanFCDriverTestCase(test.TestCase):
'find_server',
return_value=None)
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'create_server_multiple_hbas',
'create_server',
return_value=None)
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'find_volume',
@ -342,6 +405,101 @@ class DellSCSanFCDriverTestCase(test.TestCase):
volume,
connector)
def test_initialize_secondary(self,
mock_close_connection,
mock_open_connection,
mock_init):
sclivevol = {'instanceId': '101.101',
'secondaryVolume': {'instanceId': '102.101',
'instanceName': fake.VOLUME_ID},
'secondaryScSerialNumber': 102}
mock_api = mock.MagicMock()
mock_api.find_server = mock.MagicMock(return_value=self.SCSERVER)
mock_api.map_secondary_volume = mock.MagicMock(
return_value=self.VOLUME)
find_wwns_ret = (1, [u'5000D31000FCBE3D', u'5000D31000FCBE35'],
{u'21000024FF30441C': [u'5000D31000FCBE35'],
u'21000024FF30441D': [u'5000D31000FCBE3D']})
mock_api.find_wwns = mock.MagicMock(return_value=find_wwns_ret)
mock_api.get_volume = mock.MagicMock(return_value=self.VOLUME)
ret = self.driver.initialize_secondary(mock_api, sclivevol,
['wwn1', 'wwn2'])
self.assertEqual(find_wwns_ret, ret)
def test_initialize_secondary_create_server(self,
mock_close_connection,
mock_open_connection,
mock_init):
sclivevol = {'instanceId': '101.101',
'secondaryVolume': {'instanceId': '102.101',
'instanceName': fake.VOLUME_ID},
'secondaryScSerialNumber': 102}
mock_api = mock.MagicMock()
mock_api.find_server = mock.MagicMock(return_value=None)
mock_api.create_server = mock.MagicMock(return_value=self.SCSERVER)
mock_api.map_secondary_volume = mock.MagicMock(
return_value=self.VOLUME)
find_wwns_ret = (1, [u'5000D31000FCBE3D', u'5000D31000FCBE35'],
{u'21000024FF30441C': [u'5000D31000FCBE35'],
u'21000024FF30441D': [u'5000D31000FCBE3D']})
mock_api.find_wwns = mock.MagicMock(return_value=find_wwns_ret)
mock_api.get_volume = mock.MagicMock(return_value=self.VOLUME)
ret = self.driver.initialize_secondary(mock_api, sclivevol,
['wwn1', 'wwn2'])
self.assertEqual(find_wwns_ret, ret)
def test_initialize_secondary_no_server(self,
mock_close_connection,
mock_open_connection,
mock_init):
sclivevol = {'instanceId': '101.101',
'secondaryVolume': {'instanceId': '102.101',
'instanceName': fake.VOLUME_ID},
'secondaryScSerialNumber': 102}
mock_api = mock.MagicMock()
mock_api.find_server = mock.MagicMock(return_value=None)
mock_api.create_server = mock.MagicMock(return_value=None)
ret = self.driver.initialize_secondary(mock_api, sclivevol,
['wwn1', 'wwn2'])
expected = (None, [], {})
self.assertEqual(expected, ret)
def test_initialize_secondary_map_fail(self,
mock_close_connection,
mock_open_connection,
mock_init):
sclivevol = {'instanceId': '101.101',
'secondaryVolume': {'instanceId': '102.101',
'instanceName': fake.VOLUME_ID},
'secondaryScSerialNumber': 102}
mock_api = mock.MagicMock()
mock_api.find_server = mock.MagicMock(return_value=self.SCSERVER)
mock_api.map_secondary_volume = mock.MagicMock(return_value=None)
ret = self.driver.initialize_secondary(mock_api, sclivevol,
['wwn1', 'wwn2'])
expected = (None, [], {})
self.assertEqual(expected, ret)
def test_initialize_secondary_vol_not_found(self,
mock_close_connection,
mock_open_connection,
mock_init):
sclivevol = {'instanceId': '101.101',
'secondaryVolume': {'instanceId': '102.101',
'instanceName': fake.VOLUME_ID},
'secondaryScSerialNumber': 102}
mock_api = mock.MagicMock()
mock_api.find_server = mock.MagicMock(return_value=self.SCSERVER)
mock_api.map_secondary_volume = mock.MagicMock(
return_value=self.VOLUME)
mock_api.get_volume = mock.MagicMock(return_value=None)
ret = self.driver.initialize_secondary(mock_api, sclivevol,
['wwn1', 'wwn2'])
expected = (None, [], {})
self.assertEqual(expected, ret)
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'find_server',
return_value=SCSERVER)
@ -380,6 +538,56 @@ class DellSCSanFCDriverTestCase(test.TestCase):
'data': {}}
self.assertEqual(expected, res, 'Unexpected return data')
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'find_server',
return_value=SCSERVER)
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'find_volume',
return_value=VOLUME)
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'unmap_volume',
return_value=True)
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'find_wwns',
return_value=(1,
[u'5000D31000FCBE3D',
u'5000D31000FCBE35'],
{u'21000024FF30441C':
[u'5000D31000FCBE35'],
u'21000024FF30441D':
[u'5000D31000FCBE3D']}))
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'get_volume_count',
return_value=1)
@mock.patch.object(dell_storagecenter_fc.DellStorageCenterFCDriver,
'_is_live_vol')
@mock.patch.object(dell_storagecenter_fc.DellStorageCenterFCDriver,
'terminate_secondary')
def test_terminate_connection_live_vol(self,
mock_terminate_secondary,
mock_is_live_vol,
mock_get_volume_count,
mock_find_wwns,
mock_unmap_volume,
mock_find_volume,
mock_find_server,
mock_close_connection,
mock_open_connection,
mock_init):
volume = {'id': fake.VOLUME_ID}
connector = self.connector
mock_terminate_secondary.return_value = (None, [], {})
sclivevol = {'instanceId': '101.101',
'secondaryVolume': {'instanceId': '102.101',
'instanceName': fake.VOLUME_ID},
'secondaryScSerialNumber': 102}
mock_is_live_vol.return_value = sclivevol
res = self.driver.terminate_connection(volume, connector)
mock_unmap_volume.assert_called_once_with(self.VOLUME, self.SCSERVER)
expected = {'driver_volume_type': 'fibre_channel',
'data': {}}
self.assertEqual(expected, res, 'Unexpected return data')
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'find_server',
return_value=None)
@ -482,10 +690,6 @@ class DellSCSanFCDriverTestCase(test.TestCase):
mock_init):
volume = {'id': fake.VOLUME_ID}
connector = self.connector
# self.assertRaises(exception.VolumeBackendAPIException,
# self.driver.terminate_connection,
# volume,
# connector)
res = self.driver.terminate_connection(volume, connector)
expected = {'driver_volume_type': 'fibre_channel',
'data': {}}
@ -572,6 +776,24 @@ class DellSCSanFCDriverTestCase(test.TestCase):
'driver_volume_type': 'fibre_channel'}
self.assertEqual(expected, res, 'Unexpected return data')
def test_terminate_secondary(self,
mock_close_connection,
mock_open_connection,
mock_init):
mock_api = mock.MagicMock()
mock_api.find_server = mock.MagicMock(return_value=self.SCSERVER)
mock_api.get_volume = mock.MagicMock(return_value=self.VOLUME)
mock_api.find_wwns = mock.MagicMock(return_value=(None, [], {}))
mock_api.unmap_volume = mock.MagicMock(return_value=True)
sclivevol = {'instanceId': '101.101',
'secondaryVolume': {'instanceId': '102.101',
'instanceName': fake.VOLUME_ID},
'secondaryScSerialNumber': 102}
ret = self.driver.terminate_secondary(mock_api, sclivevol,
['wwn1', 'wwn2'])
expected = (None, [], {})
self.assertEqual(expected, ret)
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'get_storage_usage',
return_value={'availableSpace': 100, 'freeSpace': 50})

View File

@ -193,6 +193,7 @@ class DellSCSanISCSIDriverTestCase(test.TestCase):
IQN = 'iqn.2002-03.com.compellent:5000D31000000001'
ISCSI_PROPERTIES = {'access_mode': 'rw',
'discard': True,
'target_discovered': False,
'target_iqn':
u'iqn.2002-03.com.compellent:5000d31000fcbe43',
@ -274,6 +275,54 @@ class DellSCSanISCSIDriverTestCase(test.TestCase):
self.fake_iqn)
}
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'find_sc')
def test_check_for_setup_error(self,
mock_find_sc,
mock_close_connection,
mock_open_connection,
mock_init):
# Fail, Fail due to repl partner not found, success.
mock_find_sc.side_effect = [exception.VolumeBackendAPIException(''),
10000,
12345,
exception.VolumeBackendAPIException(''),
10000,
12345,
67890]
# Find SC throws
self.assertRaises(exception.VolumeBackendAPIException,
self.driver.check_for_setup_error)
# Replication enabled but one backend is down.
self.driver.replication_enabled = True
self.driver.backends = [{'target_device_id': '12345',
'managed_backend_name': 'host@dell1',
'qosnode': 'cinderqos'},
{'target_device_id': '67890',
'managed_backend_name': 'host@dell2',
'qosnode': 'otherqos'}]
self.assertRaises(exception.InvalidHost,
self.driver.check_for_setup_error)
# Good run. Should run without exceptions.
self.driver.check_for_setup_error()
# failed over run
mock_find_sc.side_effect = None
mock_find_sc.reset_mock()
mock_find_sc.return_value = 10000
self.driver.failed_over = True
self.driver.check_for_setup_error()
# find sc should be called exactly once
mock_find_sc.assert_called_once_with()
# No repl run
mock_find_sc.reset_mock()
mock_find_sc.return_value = 10000
self.driver.failed_over = False
self.driver.replication_enabled = False
self.driver.backends = None
self.driver.check_for_setup_error()
mock_find_sc.assert_called_once_with()
@mock.patch.object(dell_storagecenter_iscsi.DellStorageCenterISCSIDriver,
'_get_volume_extra_specs')
def test__create_replications(self,
@ -358,6 +407,71 @@ class DellSCSanISCSIDriverTestCase(test.TestCase):
self.assertEqual({}, res)
self.driver.backends = backends
@mock.patch.object(dell_storagecenter_iscsi.DellStorageCenterISCSIDriver,
'_get_volume_extra_specs')
def test__create_replications_live_volume(self,
mock_get_volume_extra_specs,
mock_close_connection,
mock_open_connection,
mock_init):
backends = self.driver.backends
model_update = {'replication_status': 'enabled',
'replication_driver_data': '12345'}
vol = {'id': fake.VOLUME_ID, 'replication_driver_data': ''}
scvol = {'name': fake.VOLUME_ID}
mock_api = mock.MagicMock()
mock_api.create_live_volume = mock.MagicMock(
return_value={'instanceId': '1'})
# Live volume with two backends defined.
self.driver.backends = [{'target_device_id': '12345',
'managed_backend_name': 'host@dell1',
'qosnode': 'cinderqos',
'remoteqos': 'remoteqos'},
{'target_device_id': '67890',
'managed_backend_name': 'host@dell2',
'qosnode': 'otherqos',
'remoteqos': 'remoteqos'}]
mock_get_volume_extra_specs.return_value = {
'replication:activereplay': '<is> True',
'replication_enabled': '<is> True',
'replication:livevolume': '<is> True'}
self.assertRaises(exception.ReplicationError,
self.driver._create_replications,
mock_api,
vol,
scvol)
# Live volume
self.driver.backends = [{'target_device_id': '12345',
'managed_backend_name': 'host@dell1',
'qosnode': 'cinderqos',
'diskfolder': 'ssd',
'remoteqos': 'remoteqos'}]
res = self.driver._create_replications(mock_api, vol, scvol)
mock_api.create_live_volume.assert_called_once_with(
scvol, '12345', True, False, 'cinderqos', 'remoteqos')
self.assertEqual(model_update, res)
# Active replay False
mock_get_volume_extra_specs.return_value = {
'replication_enabled': '<is> True',
'replication:livevolume': '<is> True'}
res = self.driver._create_replications(mock_api, vol, scvol)
mock_api.create_live_volume.assert_called_with(
scvol, '12345', False, False, 'cinderqos', 'remoteqos')
self.assertEqual(model_update, res)
# Sync
mock_get_volume_extra_specs.return_value = {
'replication_enabled': '<is> True',
'replication:livevolume': '<is> True',
'replication_type': '<in> sync'}
res = self.driver._create_replications(mock_api, vol, scvol)
mock_api.create_live_volume.assert_called_with(
scvol, '12345', False, True, 'cinderqos', 'remoteqos')
self.assertEqual(model_update, res)
self.driver.backends = backends
@mock.patch.object(dell_storagecenter_iscsi.DellStorageCenterISCSIDriver,
'_get_volume_extra_specs')
def test__delete_replications(self,
@ -388,6 +502,44 @@ class DellSCSanISCSIDriverTestCase(test.TestCase):
mock_api.delete_replication.assert_any_call(scvol, 67890)
self.driver.backends = backends
@mock.patch.object(dell_storagecenter_iscsi.DellStorageCenterISCSIDriver,
'_get_volume_extra_specs')
def test__delete_live_volume(self,
mock_get_volume_extra_specs,
mock_close_connection,
mock_open_connection,
mock_init):
backends = self.driver.backends
vol = {'id': fake.VOLUME_ID}
mock_api = mock.MagicMock()
sclivevol = {'instanceId': '101.101',
'secondaryVolume': {'instanceId': '102.101',
'instanceName': fake.VOLUME_ID},
'secondaryScSerialNumber': 102}
mock_api.get_live_volume = mock.MagicMock(return_value=sclivevol)
# No replication driver data.
ret = self.driver._delete_live_volume(mock_api, vol)
self.assertFalse(ret)
# Bogus rdd
vol = {'id': fake.VOLUME_ID, 'replication_driver_data': ''}
ret = self.driver._delete_live_volume(mock_api, vol)
self.assertFalse(ret)
# Valid delete.
mock_api.delete_live_volume = mock.MagicMock(return_value=True)
vol = {'id': fake.VOLUME_ID, 'replication_driver_data': '102'}
ret = self.driver._delete_live_volume(mock_api, vol)
self.assertTrue(ret)
# Wrong ssn.
vol = {'id': fake.VOLUME_ID, 'replication_driver_data': '103'}
ret = self.driver._delete_live_volume(mock_api, vol)
self.assertFalse(ret)
# No live volume found.
mock_api.get_live_volume.return_value = None
ret = self.driver._delete_live_volume(mock_api, vol)
self.assertFalse(ret)
self.driver.backends = backends
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'create_volume',
return_value=VOLUME)
@ -530,7 +682,12 @@ class DellSCSanISCSIDriverTestCase(test.TestCase):
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'delete_volume',
return_value=True)
@mock.patch.object(dell_storagecenter_iscsi.DellStorageCenterISCSIDriver,
'_get_replication_specs',
return_value={'enabled': True,
'live': False})
def test_delete_volume(self,
mock_get_replication_specs,
mock_delete_volume,
mock_delete_replications,
mock_close_connection,
@ -547,12 +704,38 @@ class DellSCSanISCSIDriverTestCase(test.TestCase):
self.assertTrue(mock_delete_replications.called)
self.assertEqual(2, mock_delete_replications.call_count)
@mock.patch.object(dell_storagecenter_iscsi.DellStorageCenterISCSIDriver,
'_delete_live_volume')
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'delete_volume',
return_value=True)
@mock.patch.object(dell_storagecenter_iscsi.DellStorageCenterISCSIDriver,
'_get_replication_specs',
return_value={'enabled': True,
'live': True})
def test_delete_volume_live_volume(self,
mock_get_replication_specs,
mock_delete_volume,
mock_delete_live_volume,
mock_close_connection,
mock_open_connection,
mock_init):
volume = {'id': fake.VOLUME_ID, 'provider_id': '1.1'}
self.driver.delete_volume(volume)
mock_delete_volume.assert_called_with(fake.VOLUME_ID, '1.1')
self.assertTrue(mock_delete_live_volume.called)
@mock.patch.object(dell_storagecenter_iscsi.DellStorageCenterISCSIDriver,
'_delete_replications')
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'delete_volume',
return_value=False)
@mock.patch.object(dell_storagecenter_iscsi.DellStorageCenterISCSIDriver,
'_get_replication_specs',
return_value={'enabled': True,
'live': False})
def test_delete_volume_failure(self,
mock_get_replication_specs,
mock_delete_volume,
mock_delete_replications,
mock_close_connection,
@ -644,7 +827,7 @@ class DellSCSanISCSIDriverTestCase(test.TestCase):
# verify find_volume has been called and that is has been called twice
mock_find_volume.called_once_with(fake.VOLUME_ID, provider_id)
mock_get_volume.called_once_with(provider_id)
props = self.ISCSI_PROPERTIES
props = self.ISCSI_PROPERTIES.copy()
expected = {'data': props,
'driver_volume_type': 'iscsi'}
self.assertEqual(expected, data, 'Unexpected return value')
@ -767,6 +950,236 @@ class DellSCSanISCSIDriverTestCase(test.TestCase):
volume,
connector)
@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',
return_value=VOLUME)
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'get_volume',
return_value=VOLUME)
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'map_volume',
return_value=MAPPINGS[0])
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'find_iscsi_properties',
return_value=ISCSI_PROPERTIES)
@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(self,
mock_initialize_secondary,
mock_is_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}
connector = self.connector
sclivevol = {'instanceId': '101.101',
'secondaryVolume': {'instanceId': '102.101',
'instanceName': fake.VOLUME_ID},
'secondaryScSerialNumber': 102}
mock_is_live_vol.return_value = sclivevol
lvol_properties = {'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_initialize_secondary.return_value = lvol_properties
props = self.ISCSI_PROPERTIES.copy()
props['target_iqns'] += lvol_properties['target_iqns']
props['target_luns'] += lvol_properties['target_luns']
props['target_portals'] += lvol_properties['target_portals']
ret = self.driver.initialize_connection(volume, connector)
expected = {'data': props,
'driver_volume_type': 'iscsi'}
self.assertEqual(expected, ret)
@mock.patch.object(dell_storagecenter_iscsi.DellStorageCenterISCSIDriver,
'_get_replication_specs',
return_value={'enabled': True, 'live': True})
def test_is_live_vol(self,
mock_get_replication_specs,
mock_close_connection,
mock_open_connection,
mock_init):
volume = {'id': fake.VOLUME_ID,
'provider_id': '101.1'}
sclivevol = {'instanceId': '101.101'}
mock_api = mock.MagicMock()
mock_api.get_live_volume = mock.MagicMock(return_value=sclivevol)
ret = self.driver._is_live_vol(mock_api, volume)
self.assertEqual(sclivevol, ret)
@mock.patch.object(dell_storagecenter_iscsi.DellStorageCenterISCSIDriver,
'_get_replication_specs',
return_value={'enabled': True, 'live': False})
def test_is_live_vol_repl_not_live(self,
mock_get_replication_specs,
mock_close_connection,
mock_open_connection,
mock_init):
volume = {'id': fake.VOLUME_ID}
mock_api = mock.MagicMock()
ret = self.driver._is_live_vol(mock_api, volume)
self.assertIsNone(ret)
@mock.patch.object(dell_storagecenter_iscsi.DellStorageCenterISCSIDriver,
'_get_replication_specs',
return_value={'enabled': False, 'live': False})
def test_is_live_vol_no_repl(self,
mock_get_replication_specs,
mock_close_connection,
mock_open_connection,
mock_init):
volume = {'id': fake.VOLUME_ID}
mock_api = mock.MagicMock()
ret = self.driver._is_live_vol(mock_api, volume)
self.assertIsNone(ret)
def test_initialize_secondary(self,
mock_close_connection,
mock_open_connection,
mock_init):
sclivevol = {'instanceId': '101.101',
'secondaryVolume': {'instanceId': '102.101',
'instanceName': fake.VOLUME_ID},
'secondaryScSerialNumber': 102}
mock_api = mock.MagicMock()
mock_api.find_server = mock.MagicMock(return_value=self.SCSERVER)
mock_api.map_secondary_volume = mock.MagicMock(
return_value=self.VOLUME)
mock_api.find_iscsi_properties = mock.MagicMock(
return_value=self.ISCSI_PROPERTIES)
mock_api.get_volume = mock.MagicMock(return_value=self.VOLUME)
ret = self.driver.initialize_secondary(mock_api, sclivevol, 'iqn')
self.assertEqual(self.ISCSI_PROPERTIES, ret)
def test_initialize_secondary_create_server(self,
mock_close_connection,
mock_open_connection,
mock_init):
sclivevol = {'instanceId': '101.101',
'secondaryVolume': {'instanceId': '102.101',
'instanceName': fake.VOLUME_ID},
'secondaryScSerialNumber': 102}
mock_api = mock.MagicMock()
mock_api.find_server = mock.MagicMock(return_value=None)
mock_api.create_server = mock.MagicMock(return_value=self.SCSERVER)
mock_api.map_secondary_volume = mock.MagicMock(
return_value=self.VOLUME)
mock_api.find_iscsi_properties = mock.MagicMock(
return_value=self.ISCSI_PROPERTIES)
mock_api.get_volume = mock.MagicMock(return_value=self.VOLUME)
ret = self.driver.initialize_secondary(mock_api, sclivevol, 'iqn')
self.assertEqual(self.ISCSI_PROPERTIES, ret)
def test_initialize_secondary_no_server(self,
mock_close_connection,
mock_open_connection,
mock_init):
sclivevol = {'instanceId': '101.101',
'secondaryVolume': {'instanceId': '102.101',
'instanceName': fake.VOLUME_ID},
'secondaryScSerialNumber': 102}
mock_api = mock.MagicMock()
mock_api.find_server = mock.MagicMock(return_value=None)
mock_api.create_server = mock.MagicMock(return_value=None)
expected = {'target_discovered': False,
'target_iqn': None,
'target_iqns': [],
'target_portal': None,
'target_portals': [],
'target_lun': None,
'target_luns': [],
}
ret = self.driver.initialize_secondary(mock_api, sclivevol, 'iqn')
self.assertEqual(expected, ret)
def test_initialize_secondary_map_fail(self,
mock_close_connection,
mock_open_connection,
mock_init):
sclivevol = {'instanceId': '101.101',
'secondaryVolume': {'instanceId': '102.101',
'instanceName': fake.VOLUME_ID},
'secondaryScSerialNumber': 102}
mock_api = mock.MagicMock()
mock_api.find_server = mock.MagicMock(return_value=self.SCSERVER)
mock_api.map_secondary_volume = mock.MagicMock(return_value=None)
expected = {'target_discovered': False,
'target_iqn': None,
'target_iqns': [],
'target_portal': None,
'target_portals': [],
'target_lun': None,
'target_luns': [],
}
ret = self.driver.initialize_secondary(mock_api, sclivevol, 'iqn')
self.assertEqual(expected, ret)
def test_initialize_secondary_vol_not_found(self,
mock_close_connection,
mock_open_connection,
mock_init):
sclivevol = {'instanceId': '101.101',
'secondaryVolume': {'instanceId': '102.101',
'instanceName': fake.VOLUME_ID},
'secondaryScSerialNumber': 102}
mock_api = mock.MagicMock()
mock_api.find_server = mock.MagicMock(return_value=self.SCSERVER)
mock_api.map_secondary_volume = mock.MagicMock(
return_value=self.VOLUME)
mock_api.get_volume = mock.MagicMock(return_value=None)
expected = {'target_discovered': False,
'target_iqn': None,
'target_iqns': [],
'target_portal': None,
'target_portals': [],
'target_lun': None,
'target_luns': [],
}
ret = self.driver.initialize_secondary(mock_api, sclivevol, 'iqn')
self.assertEqual(expected, ret)
def test_terminate_secondary(self,
mock_close_connection,
mock_open_connection,
mock_init):
sclivevol = {'instanceId': '101.101',
'secondaryVolume': {'instanceId': '102.101',
'instanceName': fake.VOLUME_ID},
'secondaryScSerialNumber': 102}
mock_api = mock.MagicMock()
mock_api.find_server = mock.MagicMock(return_value=self.SCSERVER)
mock_api.get_volume = mock.MagicMock(return_value=self.VOLUME)
mock_api.unmap_volume = mock.MagicMock()
self.driver.terminate_secondary(mock_api, sclivevol, 'iqn')
mock_api.find_server.assert_called_once_with('iqn', 102)
mock_api.get_volume.assert_called_once_with('102.101')
mock_api.unmap_volume.assert_called_once_with(self.VOLUME,
self.SCSERVER)
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'find_server',
return_value=SCSERVER)
@ -789,6 +1202,40 @@ class DellSCSanISCSIDriverTestCase(test.TestCase):
mock_unmap_volume.assert_called_once_with(self.VOLUME, self.SCSERVER)
self.assertIsNone(res, 'None expected')
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'find_server',
return_value=SCSERVER)
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'find_volume',
return_value=VOLUME)
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'unmap_volume',
return_value=True)
@mock.patch.object(dell_storagecenter_iscsi.DellStorageCenterISCSIDriver,
'_is_live_vol')
@mock.patch.object(dell_storagecenter_iscsi.DellStorageCenterISCSIDriver,
'terminate_secondary')
def test_terminate_connection_live_volume(self,
mock_terminate_secondary,
mock_is_live_vol,
mock_unmap_volume,
mock_find_volume,
mock_find_server,
mock_close_connection,
mock_open_connection,
mock_init):
volume = {'id': fake.VOLUME_ID}
sclivevol = {'instanceId': '101.101',
'secondaryVolume': {'instanceId': '102.101',
'instanceName': fake.VOLUME_ID},
'secondaryScSerialNumber': 102}
mock_is_live_vol.return_value = sclivevol
connector = self.connector
res = self.driver.terminate_connection(volume, connector)
mock_unmap_volume.assert_called_once_with(self.VOLUME, self.SCSERVER)
self.assertIsNone(res, 'None expected')
self.assertTrue(mock_terminate_secondary.called)
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'find_server',
return_value=None)
@ -2201,8 +2648,57 @@ class DellSCSanISCSIDriverTestCase(test.TestCase):
self.assertIsNone(destssn)
self.driver.backends = backends
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'break_replication')
def test__failover_live_volume(self,
mock_close_connection,
mock_open_connection,
mock_init):
mock_api = mock.MagicMock()
sclivevol = {'instanceId': '101.101',
'secondaryVolume': {'instanceId': '102.101',
'instanceName': fake.VOLUME_ID},
'secondaryScSerialNumber': 102}
mock_api.get_live_volume = mock.MagicMock(return_value=sclivevol)
# Good run.
mock_api.swap_roles_live_volume = mock.MagicMock(return_value=True)
model_update = {'provider_id': '102.101',
'replication_status': 'failed-over'}
ret = self.driver._failover_live_volume(mock_api, fake.VOLUME_ID,
'101.100')
self.assertEqual(model_update, ret)
# Swap fail
mock_api.swap_roles_live_volume.return_value = False
model_update = {'status': 'error'}
ret = self.driver._failover_live_volume(mock_api, fake.VOLUME_ID,
'101.100')
self.assertEqual(model_update, ret)
# Can't find live volume.
mock_api.get_live_volume.return_value = None
ret = self.driver._failover_live_volume(mock_api, fake.VOLUME_ID,
'101.100')
self.assertEqual(model_update, ret)
def test__failover_replication(self,
mock_close_connection,
mock_open_connection,
mock_init):
rvol = {'instanceId': '102.101'}
mock_api = mock.MagicMock()
mock_api.break_replication = mock.MagicMock(return_value=rvol)
# Good run.
model_update = {'replication_status': 'failed-over',
'provider_id': '102.101'}
ret = self.driver._failover_replication(mock_api, fake.VOLUME_ID,
'101.100', 102)
self.assertEqual(model_update, ret)
# break fail
mock_api.break_replication.return_value = None
model_update = {'status': 'error'}
ret = self.driver._failover_replication(mock_api, fake.VOLUME_ID,
'101.100', 102)
self.assertEqual(model_update, ret)
@mock.patch.object(dell_storagecenter_iscsi.DellStorageCenterISCSIDriver,
'_failover_replication')
@mock.patch.object(dell_storagecenter_iscsi.DellStorageCenterISCSIDriver,
'_parse_secondary')
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
@ -2211,15 +2707,20 @@ class DellSCSanISCSIDriverTestCase(test.TestCase):
'remove_mappings')
@mock.patch.object(dell_storagecenter_iscsi.DellStorageCenterISCSIDriver,
'failback_volumes')
@mock.patch.object(dell_storagecenter_iscsi.DellStorageCenterISCSIDriver,
'_get_replication_specs')
def test_failover_host(self,
mock_get_replication_specs,
mock_failback_volumes,
mock_remove_mappings,
mock_find_volume,
mock_parse_secondary,
mock_break_replication,
mock_failover_replication,
mock_close_connection,
mock_open_connection,
mock_init):
mock_get_replication_specs.return_value = {'enabled': False,
'live': False}
self.driver.replication_enabled = False
self.driver.failed_over = False
volumes = [{'id': fake.VOLUME_ID,
@ -2236,12 +2737,133 @@ class DellSCSanISCSIDriverTestCase(test.TestCase):
'12345')
# Good run
self.driver.replication_enabled = True
mock_get_replication_specs.return_value = {'enabled': True,
'live': False}
mock_parse_secondary.return_value = 12345
expected_destssn = 12345
mock_break_replication.side_effect = [{'instanceId': '2.1'}, # test1
{'instanceId': '2.2'},
{'instanceId': '2.1'}, # test2
{'instanceId': '2.1'}] # test3
mock_failover_replication.side_effect = [
{'provider_id': '2.1', 'replication_status': 'failed-over'}, # 1
{'provider_id': '2.2', 'replication_status': 'failed-over'},
{'provider_id': '2.1', 'replication_status': 'failed-over'}, # 2
{'provider_id': '2.1', 'replication_status': 'failed-over'}] # 3
expected_volume_update = [{'volume_id': fake.VOLUME_ID, 'updates':
{'replication_status': 'failed-over',
'provider_id': '2.1'}},
{'volume_id': fake.VOLUME2_ID, 'updates':
{'replication_status': 'failed-over',
'provider_id': '2.2'}}]
destssn, volume_update = self.driver.failover_host(
{}, volumes, '12345')
self.assertEqual(expected_destssn, destssn)
self.assertEqual(expected_volume_update, volume_update)
# Good run. Not all volumes replicated.
volumes = [{'id': fake.VOLUME_ID, 'replication_driver_data': '12345'},
{'id': fake.VOLUME2_ID, 'replication_driver_data': ''}]
expected_volume_update = [{'volume_id': fake.VOLUME_ID, 'updates':
{'replication_status': 'failed-over',
'provider_id': '2.1'}},
{'volume_id': fake.VOLUME2_ID, 'updates':
{'status': 'error'}}]
self.driver.failed_over = False
self.driver.active_backend_id = None
destssn, volume_update = self.driver.failover_host(
{}, volumes, '12345')
self.assertEqual(expected_destssn, destssn)
self.assertEqual(expected_volume_update, volume_update)
# Good run. Not all volumes replicated. No replication_driver_data.
volumes = [{'id': fake.VOLUME_ID, 'replication_driver_data': '12345'},
{'id': fake.VOLUME2_ID}]
expected_volume_update = [{'volume_id': fake.VOLUME_ID, 'updates':
{'replication_status': 'failed-over',
'provider_id': '2.1'}},
{'volume_id': fake.VOLUME2_ID, 'updates':
{'status': 'error'}}]
self.driver.failed_over = False
self.driver.active_backend_id = None
destssn, volume_update = self.driver.failover_host(
{}, volumes, '12345')
self.assertEqual(expected_destssn, destssn)
self.assertEqual(expected_volume_update, volume_update)
# Good run. No volumes replicated. No replication_driver_data.
volumes = [{'id': fake.VOLUME_ID},
{'id': fake.VOLUME2_ID}]
expected_volume_update = [{'volume_id': fake.VOLUME_ID, 'updates':
{'status': 'error'}},
{'volume_id': fake.VOLUME2_ID, 'updates':
{'status': 'error'}}]
self.driver.failed_over = False
self.driver.active_backend_id = None
destssn, volume_update = self.driver.failover_host(
{}, volumes, '12345')
self.assertEqual(expected_destssn, destssn)
self.assertEqual(expected_volume_update, volume_update)
# Secondary not found.
mock_parse_secondary.return_value = None
self.driver.failed_over = False
self.driver.active_backend_id = None
self.assertRaises(exception.InvalidInput,
self.driver.failover_host,
{},
volumes,
'54321')
# Already failed over.
self.driver.failed_over = True
self.driver.failover_host({}, volumes, 'default')
mock_failback_volumes.assert_called_once_with(volumes)
# Already failed over.
self.assertRaises(exception.VolumeBackendAPIException,
self.driver.failover_host, {}, volumes, '67890')
self.driver.replication_enabled = False
@mock.patch.object(dell_storagecenter_iscsi.DellStorageCenterISCSIDriver,
'_failover_live_volume')
@mock.patch.object(dell_storagecenter_iscsi.DellStorageCenterISCSIDriver,
'_parse_secondary')
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'find_volume')
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'remove_mappings')
@mock.patch.object(dell_storagecenter_iscsi.DellStorageCenterISCSIDriver,
'failback_volumes')
@mock.patch.object(dell_storagecenter_iscsi.DellStorageCenterISCSIDriver,
'_get_replication_specs')
def test_failover_host_live_volume(self,
mock_get_replication_specs,
mock_failback_volumes,
mock_remove_mappings,
mock_find_volume,
mock_parse_secondary,
mock_failover_live_volume,
mock_close_connection,
mock_open_connection,
mock_init):
mock_get_replication_specs.return_value = {'enabled': False,
'live': False}
self.driver.replication_enabled = False
self.driver.failed_over = False
volumes = [{'id': fake.VOLUME_ID,
'replication_driver_data': '12345',
'provider_id': '1.1'},
{'id': fake.VOLUME2_ID,
'replication_driver_data': '12345',
'provider_id': '1.2'}]
# No run. Not doing repl. Should raise.
self.assertRaises(exception.VolumeBackendAPIException,
self.driver.failover_host,
{},
volumes,
'12345')
# Good run
self.driver.replication_enabled = True
mock_get_replication_specs.return_value = {'enabled': True,
'live': True}
mock_parse_secondary.return_value = 12345
expected_destssn = 12345
mock_failover_live_volume.side_effect = [
{'provider_id': '2.1', 'replication_status': 'failed-over'}, # 1
{'provider_id': '2.2', 'replication_status': 'failed-over'},
{'provider_id': '2.1', 'replication_status': 'failed-over'}, # 2
{'provider_id': '2.1', 'replication_status': 'failed-over'}] # 3
expected_volume_update = [{'volume_id': fake.VOLUME_ID, 'updates':
{'replication_status': 'failed-over',
'provider_id': '2.1'}},
@ -2531,6 +3153,77 @@ class DellSCSanISCSIDriverTestCase(test.TestCase):
self.driver.failed_over = False
self.driver.backends = backends
@mock.patch.object(dell_storagecenter_iscsi.DellStorageCenterISCSIDriver,
'_get_qos',
return_value='cinderqos')
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'find_volume')
@mock.patch.object(dell_storagecenter_iscsi.DellStorageCenterISCSIDriver,
'_update_backend')
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'get_live_volume')
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'swap_roles_live_volume')
@mock.patch.object(dell_storagecenter_iscsi.DellStorageCenterISCSIDriver,
'_get_replication_specs')
def test_failback_volumes_live_vol(self,
mock_get_replication_specs,
mock_swap_roles_live_volume,
mock_get_live_volume,
mock_update_backend,
mock_find_volume,
mock_get_qos,
mock_close_connection,
mock_open_connection,
mock_init):
self.driver.replication_enabled = True
self.driver.failed_over = True
self.driver.active_backend_id = 12345
self.driver.primaryssn = 11111
backends = self.driver.backends
self.driver.backends = [{'target_device_id': '12345',
'qosnode': 'cinderqos',
'remoteqos': 'remoteqos'}]
volumes = [{'id': fake.VOLUME_ID,
'replication_driver_data': '12345',
'provider_id': '12345.1'},
{'id': fake.VOLUME2_ID,
'replication_driver_data': '12345',
'provider_id': '12345.2'}]
mock_get_live_volume.side_effect = [
{'instanceId': '11111.101',
'secondaryVolume': {'instanceId': '11111.1001',
'instanceName': fake.VOLUME_ID},
'secondaryScSerialNumber': 11111},
{'instanceId': '11111.102',
'secondaryVolume': {'instanceId': '11111.1002',
'instanceName': fake.VOLUME2_ID},
'secondaryScSerialNumber': 11111}
]
mock_get_replication_specs.return_value = {'enabled': True,
'live': True}
mock_swap_roles_live_volume.side_effect = [True, True]
mock_find_volume.side_effect = [{'instanceId': '12345.1'},
{'instanceId': '12345.2'}]
# we don't care about the return. We just want to make sure that
# _wait_for_replication is called with the proper replitems.
ret = self.driver.failback_volumes(volumes)
expected = [{'updates': {'provider_id': '11111.1001',
'replication_status': 'enabled',
'status': 'available'},
'volume_id': fake.VOLUME_ID},
{'updates': {'provider_id': '11111.1002',
'replication_status': 'enabled',
'status': 'available'},
'volume_id': fake.VOLUME2_ID}]
self.assertEqual(expected, ret)
self.driver.replication_enabled = False
self.driver.failed_over = False
self.driver.backends = backends
@mock.patch.object(dell_storagecenter_iscsi.DellStorageCenterISCSIDriver,
'_get_qos',
return_value='cinderqos')

View File

@ -1916,7 +1916,7 @@ class DellSCSanAPITestCase(test.TestCase):
False)
mock_find_folder.assert_called_once_with(
'StorageCenter/ScVolumeFolder/GetList',
self.configuration.dell_sc_volume_folder)
self.configuration.dell_sc_volume_folder, -1)
self.assertIsNone(res, 'Expected None')
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
@ -1931,7 +1931,7 @@ class DellSCSanAPITestCase(test.TestCase):
False)
mock_find_folder.assert_called_once_with(
'StorageCenter/ScVolumeFolder/GetList',
self.configuration.dell_sc_volume_folder)
self.configuration.dell_sc_volume_folder, -1)
self.assertEqual(self.FLDR, res, 'Unexpected Folder')
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
@ -2003,7 +2003,7 @@ class DellSCSanAPITestCase(test.TestCase):
True)
mock_find_folder.assert_called_once_with(
'StorageCenter/ScVolumeFolder/GetList',
self.configuration.dell_sc_volume_folder)
self.configuration.dell_sc_volume_folder, -1)
self.assertTrue(mock_create_folder_path.called)
self.assertEqual(self.FLDR, res, 'Unexpected Folder')
@ -2546,7 +2546,7 @@ class DellSCSanAPITestCase(test.TestCase):
res = self.scapi._find_server_folder(False)
mock_find_folder.assert_called_once_with(
'StorageCenter/ScServerFolder/GetList',
self.configuration.dell_sc_server_folder)
self.configuration.dell_sc_server_folder, 12345)
self.assertEqual(self.SVR_FLDR, res, 'Unexpected server folder')
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
@ -2566,7 +2566,7 @@ class DellSCSanAPITestCase(test.TestCase):
res = self.scapi._find_server_folder(True)
mock_find_folder.assert_called_once_with(
'StorageCenter/ScServerFolder/GetList',
self.configuration.dell_sc_server_folder)
self.configuration.dell_sc_server_folder, 12345)
self.assertTrue(mock_create_folder_path.called)
self.assertEqual(self.SVR_FLDR, res, 'Unexpected server folder')
@ -2583,7 +2583,7 @@ class DellSCSanAPITestCase(test.TestCase):
False)
mock_find_folder.assert_called_once_with(
'StorageCenter/ScServerFolder/GetList',
self.configuration.dell_sc_volume_folder)
self.configuration.dell_sc_volume_folder, 12345)
self.assertIsNone(res, 'Expected None')
@mock.patch.object(dell_storagecenter_api.HttpClient,
@ -2674,20 +2674,23 @@ class DellSCSanAPITestCase(test.TestCase):
res = self.scapi._find_serveros('Red Hat Linux 6.x')
self.assertIsNone(res, 'None expected')
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'_find_server_folder',
return_value=SVR_FLDR)
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'_add_hba',
return_value=FC_HBA)
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'create_server',
'_create_server',
return_value=SCSERVER)
def test_create_server_multiple_hbas(self,
mock_create_server,
mock_add_hba,
mock_find_server_folder,
mock_close_connection,
mock_open_connection,
mock_init):
res = self.scapi.create_server_multiple_hbas(
self.WWNS)
res = self.scapi.create_server(self.WWNS)
self.assertTrue(mock_create_server.called)
self.assertTrue(mock_add_hba.called)
self.assertEqual(self.SCSERVER, res, 'Unexpected ScServer')
@ -3417,93 +3420,6 @@ class DellSCSanAPITestCase(test.TestCase):
'target_portals': [u'192.168.0.21:3260']}
self.assertEqual(expected, res, 'Wrong Target Info')
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'_find_active_controller',
return_value='64702.64702')
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'_find_controller_port',
return_value=ISCSI_CTRLR_PORT)
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'_find_domains',
return_value=ISCSI_FLT_DOMAINS)
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'_find_mappings',
return_value=MAPPINGS)
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'_is_virtualport_mode',
return_value=True)
def test_find_iscsi_properties_by_address(self,
mock_is_virtualport_mode,
mock_find_mappings,
mock_find_domains,
mock_find_ctrl_port,
mock_find_active_controller,
mock_close_connection,
mock_open_connection,
mock_init):
# Test case to find iSCSI mappings by IP Address & port
res = self.scapi.find_iscsi_properties(
self.VOLUME, '192.168.0.21', 3260)
self.assertTrue(mock_is_virtualport_mode.called)
self.assertTrue(mock_find_mappings.called)
self.assertTrue(mock_find_domains.called)
self.assertTrue(mock_find_ctrl_port.called)
self.assertTrue(mock_find_active_controller.called)
expected = {'target_discovered': False,
'target_iqn':
u'iqn.2002-03.com.compellent:5000d31000fcbe43',
'target_iqns':
[u'iqn.2002-03.com.compellent:5000d31000fcbe43'],
'target_lun': 1,
'target_luns': [1],
'target_portal': u'192.168.0.21:3260',
'target_portals': [u'192.168.0.21:3260']}
self.assertEqual(expected, res, 'Wrong Target Info')
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'_find_active_controller',
return_value='64702.64702')
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'_find_controller_port',
return_value=ISCSI_CTRLR_PORT)
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'_find_domains',
return_value=ISCSI_FLT_DOMAINS)
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'_find_mappings',
return_value=MAPPINGS)
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'_is_virtualport_mode',
return_value=True)
def test_find_iscsi_properties_by_address_not_found(
self,
mock_is_virtualport_mode,
mock_find_mappings,
mock_find_domains,
mock_find_ctrl_port,
mock_find_active_ctrl,
mock_close_connection,
mock_open_connection,
mock_init):
# Test case to find iSCSI mappings by IP Address & port are not found
res = self.scapi.find_iscsi_properties(
self.VOLUME, '192.168.1.21', 3260)
self.assertTrue(mock_is_virtualport_mode.called)
self.assertTrue(mock_find_mappings.called)
self.assertTrue(mock_find_domains.called)
self.assertTrue(mock_find_ctrl_port.called)
self.assertTrue(mock_find_active_ctrl.called)
expected = {'target_discovered': False,
'target_iqn':
u'iqn.2002-03.com.compellent:5000d31000fcbe43',
'target_iqns':
[u'iqn.2002-03.com.compellent:5000d31000fcbe43'],
'target_lun': 1,
'target_luns': [1],
'target_portal': u'192.168.0.21:3260',
'target_portals': [u'192.168.0.21:3260']}
self.assertEqual(expected, res, 'Wrong Target Info')
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'_find_mappings',
return_value=[])
@ -3747,94 +3663,6 @@ class DellSCSanAPITestCase(test.TestCase):
self.assertTrue(mock_find_controller_port_iscsi_config.called)
self.assertTrue(mock_find_active_controller.called)
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'_find_active_controller',
return_value='64702.64702')
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'_find_controller_port',
return_value=ISCSI_CTRLR_PORT)
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'_find_mappings',
return_value=MAPPINGS)
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'_is_virtualport_mode',
return_value=False)
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'_find_controller_port_iscsi_config',
return_value=ISCSI_CONFIG)
def test_find_iscsi_properties_by_address_legacy(
self,
mock_find_controller_port_iscsi_config,
mock_is_virtualport_mode,
mock_find_mappings,
mock_find_ctrl_port,
mock_find_active_controller,
mock_close_connection,
mock_open_connection,
mock_init):
# Test case to find iSCSI mappings by IP Address & port
res = self.scapi.find_iscsi_properties(
self.VOLUME, '192.168.0.21', 3260)
self.assertTrue(mock_is_virtualport_mode.called)
self.assertTrue(mock_find_mappings.called)
self.assertTrue(mock_find_ctrl_port.called)
self.assertTrue(mock_find_active_controller.called)
self.assertTrue(mock_find_controller_port_iscsi_config.called)
expected = {'target_discovered': False,
'target_iqn':
u'iqn.2002-03.com.compellent:5000d31000fcbe43',
'target_iqns':
[u'iqn.2002-03.com.compellent:5000d31000fcbe43'],
'target_lun': 1,
'target_luns': [1],
'target_portal': u'192.168.0.21:3260',
'target_portals': [u'192.168.0.21:3260']}
self.assertEqual(expected, res, 'Wrong Target Info')
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'_find_active_controller',
return_value='64702.64702')
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'_find_controller_port',
return_value=ISCSI_CTRLR_PORT)
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'_find_mappings',
return_value=MAPPINGS)
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'_is_virtualport_mode',
return_value=False)
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'_find_controller_port_iscsi_config',
return_value=ISCSI_CONFIG)
def test_find_iscsi_properties_by_address_not_found_legacy(
self,
mock_find_controller_port_iscsi_config,
mock_is_virtualport_mode,
mock_find_mappings,
mock_find_ctrl_port,
mock_find_active_ctrl,
mock_close_connection,
mock_open_connection,
mock_init):
# Test case to find iSCSI mappings by IP Address & port are not found
res = self.scapi.find_iscsi_properties(
self.VOLUME, '192.168.1.21', 3260)
self.assertTrue(mock_is_virtualport_mode.called)
self.assertTrue(mock_find_mappings.called)
self.assertTrue(mock_find_ctrl_port.called)
self.assertTrue(mock_find_active_ctrl.called)
self.assertTrue(mock_find_controller_port_iscsi_config.called)
expected = {'target_discovered': False,
'target_iqn':
u'iqn.2002-03.com.compellent:5000d31000fcbe43',
'target_iqns':
[u'iqn.2002-03.com.compellent:5000d31000fcbe43'],
'target_lun': 1,
'target_luns': [1],
'target_portal': u'192.168.0.21:3260',
'target_portals': [u'192.168.0.21:3260']}
self.assertEqual(expected, res, 'Wrong Target Info')
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'_find_active_controller',
return_value='64702.64702')
@ -5965,6 +5793,133 @@ class DellSCSanAPITestCase(test.TestCase):
self.scapi._find_qos,
'Cinder QoS')
@mock.patch.object(dell_storagecenter_api.HttpClient,
'put',
return_value=RESPONSE_400)
@mock.patch.object(dell_storagecenter_api.HttpClient,
'get',
return_value=RESPONSE_200)
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'_get_json',
return_value=SCREPL)
def test_update_replicate_active_replay_fail(self,
mock_get_json,
mock_get,
mock_put,
mock_close_connection,
mock_open_connection,
mock_init):
ret = self.scapi.update_replicate_active_replay({'instanceId': '1'},
True)
self.assertFalse(ret)
@mock.patch.object(dell_storagecenter_api.HttpClient,
'get',
return_value=RESPONSE_200)
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'_get_json',
return_value=SCREPL)
def test_update_replicate_active_replay_nothing_to_do(
self, mock_get_json, mock_get, mock_close_connection,
mock_open_connection, mock_init):
ret = self.scapi.update_replicate_active_replay({'instanceId': '1'},
False)
self.assertTrue(ret)
@mock.patch.object(dell_storagecenter_api.HttpClient,
'get',
return_value=RESPONSE_200)
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'_get_json',
return_value=[])
def test_update_replicate_active_replay_not_found(self,
mock_get_json,
mock_get,
mock_close_connection,
mock_open_connection,
mock_init):
ret = self.scapi.update_replicate_active_replay({'instanceId': '1'},
True)
self.assertTrue(ret)
@mock.patch.object(dell_storagecenter_api.HttpClient,
'get',
return_value=RESPONSE_400)
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'_get_json',
return_value=[])
def test_update_replicate_active_replay_not_found2(self,
mock_get_json,
mock_get,
mock_close_connection,
mock_open_connection,
mock_init):
ret = self.scapi.update_replicate_active_replay({'instanceId': '1'},
True)
self.assertTrue(ret)
@mock.patch.object(dell_storagecenter_api.HttpClient,
'post',
return_value=RESPONSE_200)
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'_get_json',
return_value=[{'instanceId': '12345.1'}])
def test_get_disk_folder(self,
mock_get_json,
mock_post,
mock_close_connection,
mock_open_connection,
mock_init):
ret = self.scapi._get_disk_folder(12345, 'name')
expected_payload = {'filter': {'filterType': 'AND', 'filters': [
{'filterType': 'Equals', 'attributeName': 'scSerialNumber',
'attributeValue': 12345},
{'filterType': 'Equals', 'attributeName': 'name',
'attributeValue': 'name'}]}}
mock_post.assert_called_once_with('StorageCenter/ScDiskFolder/GetList',
expected_payload)
self.assertEqual({'instanceId': '12345.1'}, ret)
@mock.patch.object(dell_storagecenter_api.HttpClient,
'post',
return_value=RESPONSE_400)
def test_get_disk_folder_fail(self,
mock_post,
mock_close_connection,
mock_open_connection,
mock_init):
ret = self.scapi._get_disk_folder(12345, 'name')
expected_payload = {'filter': {'filterType': 'AND', 'filters': [
{'filterType': 'Equals', 'attributeName': 'scSerialNumber',
'attributeValue': 12345},
{'filterType': 'Equals', 'attributeName': 'name',
'attributeValue': 'name'}]}}
mock_post.assert_called_once_with('StorageCenter/ScDiskFolder/GetList',
expected_payload)
self.assertIsNone(ret)
@mock.patch.object(dell_storagecenter_api.HttpClient,
'post',
return_value=RESPONSE_200)
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'_get_json')
def test_get_disk_folder_fail_bad_json(self,
mock_get_json,
mock_post,
mock_close_connection,
mock_open_connection,
mock_init):
mock_get_json.side_effect = (exception.VolumeBackendAPIException(''))
ret = self.scapi._get_disk_folder(12345, 'name')
expected_payload = {'filter': {'filterType': 'AND', 'filters': [
{'filterType': 'Equals', 'attributeName': 'scSerialNumber',
'attributeValue': 12345},
{'filterType': 'Equals', 'attributeName': 'name',
'attributeValue': 'name'}]}}
mock_post.assert_called_once_with('StorageCenter/ScDiskFolder/GetList',
expected_payload)
self.assertIsNone(ret)
@mock.patch.object(dell_storagecenter_api.HttpClient,
'get',
return_value=RESPONSE_200)
@ -6446,6 +6401,250 @@ class DellSCSanAPITestCase(test.TestCase):
scvol,
'a,b')
@mock.patch.object(dell_storagecenter_api.HttpClient,
'get')
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'_get_json')
def test_get_live_volume(self,
mock_get_json,
mock_get,
mock_close_connection,
mock_open_connection,
mock_init):
# Basic check
ret = self.scapi.get_live_volume(None)
self.assertIsNone(ret)
lv1 = {'primaryVolume': {'instanceId': '12345.1'}}
lv2 = {'primaryVolume': {'instanceId': '12345.2'}}
mock_get_json.return_value = [lv1, lv2]
mock_get.return_value = self.RESPONSE_200
# Good Run
ret = self.scapi.get_live_volume('12345.2')
self.assertEqual(lv2, ret)
@mock.patch.object(dell_storagecenter_api.HttpClient,
'get')
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'_get_json')
def test_get_live_volume_not_found(self,
mock_get_json,
mock_get,
mock_close_connection,
mock_open_connection,
mock_init):
lv1 = {'primaryVolume': {'instanceId': '12345.1'}}
lv2 = {'primaryVolume': {'instanceId': '12345.2'}}
mock_get_json.return_value = [lv1, lv2]
mock_get.return_value = self.RESPONSE_200
ret = self.scapi.get_live_volume('12345.3')
self.assertIsNone(ret)
@mock.patch.object(dell_storagecenter_api.HttpClient,
'get')
def test_get_live_volume_error(self,
mock_get,
mock_close_connection,
mock_open_connection,
mock_init):
mock_get.return_value = self.RESPONSE_400
ret = self.scapi.get_live_volume('12345.2')
self.assertIsNone(ret)
@mock.patch.object(dell_storagecenter_api.HttpClient,
'post')
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'_get_json')
def test_map_secondary_volume(self,
mock_get_json,
mock_post,
mock_close_connection,
mock_open_connection,
mock_init):
sclivevol = {'instanceId': '101.101',
'secondaryVolume': {'instanceId': '102.101'},
'secondaryScSerialNumber': 102}
scdestsrv = {'instanceId': '102.1000'}
mock_post.return_value = self.RESPONSE_200
mock_get_json.return_value = {'instanceId': '102.101.1'}
ret = self.scapi.map_secondary_volume(sclivevol, scdestsrv)
expected_payload = {'Server': '102.1000',
'Advanced': {'MapToDownServerHbas': True}}
mock_post.assert_called_once_with(
'StorageCenter/ScLiveVolume/101.101/MapSecondaryVolume',
expected_payload, True
)
self.assertEqual({'instanceId': '102.101.1'}, ret)
@mock.patch.object(dell_storagecenter_api.HttpClient,
'post')
def test_map_secondary_volume_err(self,
mock_post,
mock_close_connection,
mock_open_connection,
mock_init):
sclivevol = {'instanceId': '101.101',
'secondaryVolume': {'instanceId': '102.101'},
'secondaryScSerialNumber': 102}
scdestsrv = {'instanceId': '102.1000'}
mock_post.return_value = self.RESPONSE_400
ret = self.scapi.map_secondary_volume(sclivevol, scdestsrv)
expected_payload = {'Server': '102.1000',
'Advanced': {'MapToDownServerHbas': True}}
mock_post.assert_called_once_with(
'StorageCenter/ScLiveVolume/101.101/MapSecondaryVolume',
expected_payload, True
)
self.assertIsNone(ret)
@mock.patch.object(dell_storagecenter_api.HttpClient,
'post')
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'_get_json')
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'_find_qos')
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'find_sc')
def test_create_live_volume(self,
mock_find_sc,
mock_find_qos,
mock_get_json,
mock_post,
mock_close_connection,
mock_open_connection,
mock_init):
scvol = {'instanceId': '101.1',
'name': 'name'}
sclivevol = {'instanceId': '101.101',
'secondaryVolume': {'instanceId': '102.101'},
'secondaryScSerialNumber': 102}
remotessn = '102'
active = True
sync = False
primaryqos = 'fast'
secondaryqos = 'slow'
mock_find_sc.return_value = 102
mock_find_qos.side_effect = [{'instanceId': '101.1001'},
{'instanceId': '102.1001'}]
mock_post.return_value = self.RESPONSE_200
mock_get_json.return_value = sclivevol
ret = self.scapi.create_live_volume(scvol, remotessn, active, sync,
primaryqos, secondaryqos)
mock_find_sc.assert_called_once_with(102)
mock_find_qos.assert_any_call(primaryqos)
mock_find_qos.assert_any_call(secondaryqos, 102)
self.assertEqual(sclivevol, ret)
@mock.patch.object(dell_storagecenter_api.HttpClient,
'post')
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'_find_qos')
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'find_sc')
def test_create_live_volume_error(self,
mock_find_sc,
mock_find_qos,
mock_post,
mock_close_connection,
mock_open_connection,
mock_init):
scvol = {'instanceId': '101.1',
'name': 'name'}
remotessn = '102'
active = True
sync = False
primaryqos = 'fast'
secondaryqos = 'slow'
mock_find_sc.return_value = 102
mock_find_qos.side_effect = [{'instanceId': '101.1001'},
{'instanceId': '102.1001'}]
mock_post.return_value = self.RESPONSE_400
ret = self.scapi.create_live_volume(scvol, remotessn, active, sync,
primaryqos, secondaryqos)
mock_find_sc.assert_called_once_with(102)
mock_find_qos.assert_any_call(primaryqos)
mock_find_qos.assert_any_call(secondaryqos, 102)
self.assertIsNone(ret)
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'_find_qos')
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'find_sc')
def test_create_live_volume_no_dest(self,
mock_find_sc,
mock_find_qos,
mock_close_connection,
mock_open_connection,
mock_init):
scvol = {'instanceId': '101.1',
'name': 'name'}
remotessn = '102'
active = True
sync = False
primaryqos = 'fast'
secondaryqos = 'slow'
mock_find_sc.return_value = 102
mock_find_qos.return_value = {}
ret = self.scapi.create_live_volume(scvol, remotessn, active, sync,
primaryqos, secondaryqos)
mock_find_sc.assert_called_once_with(102)
mock_find_qos.assert_any_call(primaryqos)
mock_find_qos.assert_any_call(secondaryqos, 102)
self.assertIsNone(ret)
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'_find_qos')
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'find_sc')
def test_create_live_volume_no_qos(self,
mock_find_sc,
mock_find_qos,
mock_close_connection,
mock_open_connection,
mock_init):
scvol = {'instanceId': '101.1',
'name': 'name'}
remotessn = '102'
active = True
sync = False
primaryqos = 'fast'
secondaryqos = 'slow'
mock_find_sc.return_value = 102
mock_find_qos.return_value = None
ret = self.scapi.create_live_volume(scvol, remotessn, active, sync,
primaryqos, secondaryqos)
mock_find_sc.assert_called_once_with(102)
mock_find_qos.assert_any_call(primaryqos)
mock_find_qos.assert_any_call(secondaryqos, 102)
self.assertIsNone(ret)
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'_find_qos')
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'find_sc')
def test_create_live_volume_no_secondary_qos(self,
mock_find_sc,
mock_find_qos,
mock_close_connection,
mock_open_connection,
mock_init):
scvol = {'instanceId': '101.1',
'name': 'name'}
remotessn = '102'
active = True
sync = False
primaryqos = 'fast'
secondaryqos = 'slow'
mock_find_sc.return_value = 102
mock_find_qos.side_effect = [{'instanceId': '101.1001'},
None]
ret = self.scapi.create_live_volume(scvol, remotessn, active, sync,
primaryqos, secondaryqos)
mock_find_sc.assert_called_once_with(102)
mock_find_qos.assert_any_call(primaryqos)
mock_find_qos.assert_any_call(secondaryqos, 102)
self.assertIsNone(ret)
@mock.patch.object(dell_storagecenter_api.HttpClient,
'put')
def test_manage_replay(self,
@ -6707,6 +6906,22 @@ class DellSCSanAPITestCase(test.TestCase):
self.assertIsNone(retbool)
self.assertIsNone(retnum)
@mock.patch.object(dell_storagecenter_api.HttpClient,
'delete')
def test_delete_live_volume(self,
mock_delete,
mock_close_connection,
mock_open_connection,
mock_init):
mock_delete.return_value = self.RESPONSE_200
ret = self.scapi.delete_live_volume({'instanceId': '12345.101'},
True)
self.assertTrue(ret)
mock_delete.return_value = self.RESPONSE_400
ret = self.scapi.delete_live_volume({'instanceId': '12345.101'},
True)
self.assertFalse(ret)
class DellSCSanAPIConnectionTestCase(test.TestCase):
@ -7009,41 +7224,18 @@ class DellHttpClientTestCase(test.TestCase):
self.httpclient._wait_for_async_complete,
self.ASYNCTASK)
@mock.patch.object(dell_storagecenter_api.HttpClient,
'_rest_ret',
return_value=RESPONSE_200)
@mock.patch.object(requests.Session,
'get',
return_value=RESPONSE_200)
def test_get(self,
mock_get,
mock_rest_ret):
ret = self.httpclient.get('url', False)
mock_get):
ret = self.httpclient.get('url')
self.assertEqual(self.RESPONSE_200, ret)
mock_rest_ret.assert_called_once_with(self.RESPONSE_200, False)
expected_headers = self.httpclient.header.copy()
mock_get.assert_called_once_with('https://localhost:3033/api/rest/url',
headers=expected_headers,
verify=False)
@mock.patch.object(dell_storagecenter_api.HttpClient,
'_rest_ret',
return_value=RESPONSE_200)
@mock.patch.object(requests.Session,
'get',
return_value=RESPONSE_200)
def test_get_async(self,
mock_get,
mock_rest_ret):
ret = self.httpclient.get('url', True)
self.assertEqual(self.RESPONSE_200, ret)
mock_rest_ret.assert_called_once_with(self.RESPONSE_200, True)
expected_headers = self.httpclient.header.copy()
expected_headers['async'] = True
mock_get.assert_called_once_with('https://localhost:3033/api/rest/url',
headers=expected_headers,
verify=False)
class DellStorageCenterApiHelperTestCase(test.TestCase):

View File

@ -200,11 +200,12 @@ class HttpClient(object):
@utils.retry(exceptions=(requests.ConnectionError,
exception.DellDriverRetryableException))
def get(self, url, async=False):
def get(self, url):
LOG.debug('get: %(url)s', {'url': url})
rest_response = self._rest_ret(self.session.get(
self.__formatUrl(url), headers=self._get_header(async),
verify=self.verify), async)
rest_response = self.session.get(self.__formatUrl(url),
headers=self.header,
verify=self.verify)
if rest_response and rest_response.status_code == 400 and (
'Unhandled Exception' in rest_response.text):
raise exception.DellDriverRetryableException()
@ -381,7 +382,8 @@ class StorageCenterApi(object):
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.1.0 - Failback supported.
3.2.0 - Live Volume support.
3.3.0 - Support for a secondary DSM.
"""
@ -664,8 +666,7 @@ class StorageCenterApi(object):
"""
# We might be looking for another ssn. If not then
# look for our default.
if ssn == -1:
ssn = self.ssn
ssn = self._vet_ssn(ssn)
r = self.client.get('StorageCenter/StorageCenter')
result = self._get_result(r, 'scSerialNumber', ssn)
@ -680,7 +681,7 @@ class StorageCenterApi(object):
# Folder functions
def _create_folder(self, url, parent, folder):
def _create_folder(self, url, parent, folder, ssn=-1):
"""Creates folder under parent.
This can create both to server and volume folders. The REST url
@ -693,10 +694,12 @@ class StorageCenterApi(object):
:param folder: The folder name to be created. This is one level deep.
:returns: The REST folder object.
"""
ssn = self._vet_ssn(ssn)
scfolder = None
payload = {}
payload['Name'] = folder
payload['StorageCenter'] = self.ssn
payload['StorageCenter'] = ssn
if parent != '':
payload['Parent'] = parent
payload['Notes'] = self.notes
@ -706,7 +709,7 @@ class StorageCenterApi(object):
scfolder = self._first_result(r)
return scfolder
def _create_folder_path(self, url, foldername):
def _create_folder_path(self, url, foldername, ssn=-1):
"""Creates a folder path from a fully qualified name.
The REST url sent in defines the folder type being created on the Dell
@ -718,6 +721,8 @@ class StorageCenterApi(object):
:param foldername: The full folder name with path.
:returns: The REST folder object.
"""
ssn = self._vet_ssn(ssn)
path = self._path_to_array(foldername)
folderpath = ''
instanceId = ''
@ -729,12 +734,12 @@ class StorageCenterApi(object):
# If the last was found see if this part of the path exists too
if found:
listurl = url + '/GetList'
scfolder = self._find_folder(listurl, folderpath)
scfolder = self._find_folder(listurl, folderpath, ssn)
if scfolder is None:
found = False
# We didn't find it so create it
if found is False:
scfolder = self._create_folder(url, instanceId, folder)
scfolder = self._create_folder(url, instanceId, folder, ssn)
# If we haven't found a folder or created it then leave
if scfolder is None:
LOG.error(_LE('Unable to create folder path %s'), folderpath)
@ -744,7 +749,7 @@ class StorageCenterApi(object):
folderpath = folderpath + '/'
return scfolder
def _find_folder(self, url, foldername):
def _find_folder(self, url, foldername, ssn=-1):
"""Find a folder on the SC using the specified url.
Most of the time the folder will already have been created so
@ -761,8 +766,10 @@ class StorageCenterApi(object):
:param foldername: Full path to the folder we are looking for.
:returns: Dell folder object.
"""
ssn = self._vet_ssn(ssn)
pf = self._get_payload_filter()
pf.append('scSerialNumber', self.ssn)
pf.append('scSerialNumber', ssn)
basename = os.path.basename(foldername)
pf.append('Name', basename)
# If we have any kind of path we throw it into the filters.
@ -777,7 +784,7 @@ class StorageCenterApi(object):
folder = self._get_result(r, 'folderPath', folderpath)
return folder
def _find_volume_folder(self, create=False):
def _find_volume_folder(self, create=False, ssn=-1):
"""Looks for the volume folder where backend volumes will be created.
Volume folder is specified in the cindef.conf. See __init.
@ -786,11 +793,11 @@ class StorageCenterApi(object):
:returns: Folder object.
"""
folder = self._find_folder('StorageCenter/ScVolumeFolder/GetList',
self.vfname)
self.vfname, ssn)
# Doesn't exist? make it
if folder is None and create is True:
folder = self._create_folder_path('StorageCenter/ScVolumeFolder',
self.vfname)
self.vfname, ssn)
return folder
def _init_volume(self, scvolume):
@ -1051,8 +1058,7 @@ class StorageCenterApi(object):
:param ssn: SSN to search on.
:return: Returns the scvolume list or None.
"""
if ssn == -1:
ssn = self.ssn
ssn = self._vet_ssn(ssn)
result = None
# We need a name or a device ID to find a volume.
if name or deviceid:
@ -1194,7 +1200,7 @@ class StorageCenterApi(object):
'provider_id: %s'), provider_id)
return True
def _find_server_folder(self, create=False):
def _find_server_folder(self, create=False, ssn=-1):
"""Looks for the server folder on the Dell Storage Center.
This is the folder where a server objects for mapping volumes will be
@ -1203,11 +1209,13 @@ class StorageCenterApi(object):
:param create: If True will create the folder if not found.
:return: Folder object.
"""
ssn = self._vet_ssn(ssn)
folder = self._find_folder('StorageCenter/ScServerFolder/GetList',
self.sfname)
self.sfname, ssn)
if folder is None and create is True:
folder = self._create_folder_path('StorageCenter/ScServerFolder',
self.sfname)
self.sfname, ssn)
return folder
def _add_hba(self, scserver, wwnoriscsiname):
@ -1235,7 +1243,7 @@ class StorageCenterApi(object):
return False
return True
def _find_serveros(self, osname='Red Hat Linux 6.x'):
def _find_serveros(self, osname='Red Hat Linux 6.x', ssn=-1):
"""Returns the serveros instance id of the specified osname.
Required to create a Dell server object.
@ -1246,8 +1254,9 @@ class StorageCenterApi(object):
:param osname: The name of the OS to look for.
:returns: InstanceId of the ScServerOperatingSystem object.
"""
ssn = self._vet_ssn(ssn)
pf = self._get_payload_filter()
pf.append('scSerialNumber', self.ssn)
pf.append('scSerialNumber', ssn)
r = self.client.post('StorageCenter/ScServerOperatingSystem/GetList',
pf.payload)
if self._check_result(r):
@ -1262,55 +1271,47 @@ class StorageCenterApi(object):
return None
def create_server_multiple_hbas(self, wwns):
def create_server(self, wwnlist, ssn=-1):
"""Creates a server with multiple WWNS associated with it.
Same as create_server except it can take a list of HBAs.
:param wwns: A list of FC WWNs or iSCSI IQNs associated with this
server.
:param wwnlist: A list of FC WWNs or iSCSI IQNs associated with this
server.
:returns: Dell server object.
"""
scserver = None
# Our instance names
for wwn in wwns:
if scserver is None:
# Use the fist wwn to create the server.
scserver = self.create_server(wwn)
else:
# Add the wwn to our server
self._add_hba(scserver, wwn)
# Find our folder or make it
folder = self._find_server_folder(True, ssn)
# Create our server.
scserver = self._create_server('Server_' + wwnlist[0], folder, ssn)
if not scserver:
return None
# Add our HBAs.
if scserver:
for wwn in wwnlist:
if not self._add_hba(scserver, wwn):
# We failed so log it. Delete our server and return None.
LOG.error(_LE('Error adding HBA %s to server'), wwn)
self._delete_server(scserver)
return None
return scserver
def create_server(self, wwnoriscsiname):
"""Creates a Dell server object on the the Storage Center.
def _create_server(self, servername, folder, ssn):
ssn = self._vet_ssn(ssn)
Adds the first HBA identified by wwnoriscsiname to it.
:param wwnoriscsiname: FC WWN or iSCSI IQN associated with
this Dell server object.
:returns: Dell server object.
"""
LOG.info(_LI('Creating server %s'), wwnoriscsiname)
scserver = None
LOG.info(_LI('Creating server %s'), servername)
payload = {}
payload['Name'] = 'Server_' + wwnoriscsiname
payload['StorageCenter'] = self.ssn
payload['Name'] = servername
payload['StorageCenter'] = ssn
payload['Notes'] = self.notes
# We pick Red Hat Linux 6.x because it supports multipath and
# will attach luns to paths as they are found.
scserveros = self._find_serveros('Red Hat Linux 6.x')
scserveros = self._find_serveros('Red Hat Linux 6.x', ssn)
if scserveros is not None:
payload['OperatingSystem'] = scserveros
# Find our folder or make it
folder = self._find_server_folder(True)
# At this point it doesn't matter if the folder was created or not.
# We just attempt to create the server. Let it be in the root if
# the folder creation fails.
# At this point it doesn't matter if we have a folder or not.
# Let it be in the root if the folder creation fails.
if folder is not None:
payload['ServerFolder'] = self._get_id(folder)
@ -1320,19 +1321,24 @@ class StorageCenterApi(object):
# Server was created
scserver = self._first_result(r)
LOG.info(_LI('SC server created %s'), scserver)
return scserver
LOG.error(_LE('Unable to create SC server %s'), servername)
return None
# Add hba to our server
if scserver is not None:
if not self._add_hba(scserver, wwnoriscsiname):
LOG.error(_LE('Error adding HBA to server'))
# Can't have a server without an HBA
self._delete_server(scserver)
scserver = None
def _vet_ssn(self, ssn):
"""Returns the default if a ssn was not set.
# Success or failure is determined by the caller
return scserver
Added to support live volume as we aren't always on the primary ssn
anymore
def find_server(self, instance_name):
:param ssn: ssn to check.
:return: Current ssn or the ssn sent down.
"""
if ssn == -1:
return self.ssn
return ssn
def find_server(self, instance_name, ssn=-1):
"""Hunts for a server on the Dell backend by instance_name.
The instance_name is the same as the server's HBA. This is the IQN or
@ -1342,17 +1348,20 @@ class StorageCenterApi(object):
:param instance_name: instance_name is a FC WWN or iSCSI IQN from
the connector. In cinder a server is identified
by its HBA.
:param ssn: Storage center to search.
:returns: Dell server object or None.
"""
ssn = self._vet_ssn(ssn)
scserver = None
# We search for our server by first finding our HBA
hba = self._find_serverhba(instance_name)
hba = self._find_serverhba(instance_name, ssn)
# Once created hbas stay in the system. So it isn't enough
# that we found one it actually has to be attached to a
# server.
if hba is not None and hba.get('server') is not None:
pf = self._get_payload_filter()
pf.append('scSerialNumber', self.ssn)
pf.append('scSerialNumber', ssn)
pf.append('instanceId', self._get_id(hba['server']))
r = self.client.post('StorageCenter/ScServer/GetList', pf.payload)
if self._check_result(r):
@ -1362,7 +1371,7 @@ class StorageCenterApi(object):
LOG.debug('Server (%s) not found.', instance_name)
return scserver
def _find_serverhba(self, instance_name):
def _find_serverhba(self, instance_name, ssn):
"""Hunts for a server HBA on the Dell backend by instance_name.
Instance_name is the same as the IQN or WWN specified in the
@ -1370,12 +1379,13 @@ class StorageCenterApi(object):
:param instance_name: Instance_name is a FC WWN or iSCSI IQN from
the connector.
:param ssn: Storage center to search.
:returns: Dell server HBA object.
"""
scserverhba = None
# We search for our server by first finding our HBA
pf = self._get_payload_filter()
pf.append('scSerialNumber', self.ssn)
pf.append('scSerialNumber', ssn)
pf.append('instanceName', instance_name)
r = self.client.post('StorageCenter/ScServerHba/GetList', pf.payload)
if self._check_result(r):
@ -1449,6 +1459,7 @@ class StorageCenterApi(object):
LOG.info(_LI('Volume mappings for %(name)s: %(mappings)s'),
{'name': scvolume.get('name'),
'mappings': mappings})
return mappings
def _find_mapping_profiles(self, scvolume):
@ -1599,23 +1610,19 @@ class StorageCenterApi(object):
'Error finding configuration: %s'), cportid)
return controllerport
def find_iscsi_properties(self, scvolume, ip=None, port=None):
def find_iscsi_properties(self, scvolume):
"""Finds target information for a given Dell scvolume object mapping.
The data coming back is both the preferred path and all the paths.
:param scvolume: The dell sc volume object.
:param ip: The preferred target portal ip.
:param port: The preferred target portal port.
:returns: iSCSI property dictionary.
:raises: VolumeBackendAPIException
"""
LOG.debug('find_iscsi_properties: scvolume: %s', scvolume)
# Our mutable process object.
pdata = {'active': -1,
'up': -1,
'ip': ip,
'port': port}
'up': -1}
# Our output lists.
portals = []
luns = []
@ -1640,22 +1647,15 @@ class StorageCenterApi(object):
iqns.append(iqn)
luns.append(lun)
# We've all the information. We need to find
# the best single portal to return. So check
# this one if it is on the right IP, port and
# if the access and status are correct.
if ((pdata['ip'] is None or pdata['ip'] == address) and
(pdata['port'] is None or pdata['port'] == port)):
# We need to point to the best link.
# So state active and status up is preferred
# but we don't actually need the state to be
# up at this point.
if pdata['up'] == -1:
if active:
pdata['active'] = len(iqns) - 1
if status == 'Up':
pdata['up'] = pdata['active']
# We need to point to the best link.
# So state active and status up is preferred
# but we don't actually need the state to be
# up at this point.
if pdata['up'] == -1:
if active:
pdata['active'] = len(iqns) - 1
if status == 'Up':
pdata['up'] = pdata['active']
# Start by getting our mappings.
mappings = self._find_mappings(scvolume)
@ -1672,6 +1672,11 @@ class StorageCenterApi(object):
isvpmode = self._is_virtualport_mode()
# Trundle through our mappings.
for mapping in mappings:
# Don't return remote sc links.
msrv = mapping.get('server')
if msrv and msrv.get('objectType') == 'ScRemoteStorageCenter':
continue
# The lun, ro mode and status are in the mapping.
LOG.debug('find_iscsi_properties: mapping: %s', mapping)
lun = mapping.get('lun')
@ -2605,8 +2610,7 @@ class StorageCenterApi(object):
:param ssn: SSN to search on.
:return: scqos node object.
"""
if ssn == -1:
ssn = self.ssn
ssn = self._vet_ssn(ssn)
pf = self._get_payload_filter()
pf.append('scSerialNumber', ssn)
pf.append('name', qosnode)
@ -3002,3 +3006,128 @@ class StorageCenterApi(object):
' progress information returned: %s'),
progress)
return None, None
def get_live_volume(self, primaryid):
"""Get's the live ScLiveVolume object for the vol with primaryid.
:param primaryid: InstanceId of the primary volume.
:return: ScLiveVolume object or None.
"""
if primaryid:
r = self.client.get('StorageCenter/ScLiveVolume')
if self._check_result(r):
lvs = self._get_json(r)
for lv in lvs:
if lv['primaryVolume']['instanceId'] == primaryid:
return lv
return None
def _get_hbas(self, serverid):
# Helper to get the hba's of a given server.
r = self.client.get('StorageCenter/ScServer/%s/HbaList' % serverid)
if self._check_result(r):
return self._get_json(r)
return None
def map_secondary_volume(self, sclivevol, scdestsrv):
"""Map's the secondary volume or a LiveVolume to destsrv.
:param sclivevol: ScLiveVolume object.
:param scdestsrv: ScServer object for the destination.
:return: ScMappingProfile object or None on failure.
"""
payload = {}
payload['Server'] = self._get_id(scdestsrv)
payload['Advanced'] = {'MapToDownServerHbas': True}
r = self.client.post('StorageCenter/ScLiveVolume/%s/MapSecondaryVolume'
% self._get_id(sclivevol), payload, True)
if self._check_result(r):
return self._get_json(r)
return None
def create_live_volume(self, scvolume, remotessn, active=False, sync=False,
primaryqos='CinderQOS', secondaryqos='CinderQOS'):
"""This create's a live volume instead of a replication.
Servers are not created at this point so we cannot map up a remote
server immediately.
:param scvolume: Source SC Volume
:param remotessn: Destination SSN.
:param active: Replicate the active replay boolean.
:param sync: Sync replication boolean.
:param primaryqos: QOS node name for the primary side.
:param secondaryqos: QOS node name for the remote side.
:return: ScLiveVolume object or None on failure.
"""
destssn = self.find_sc(int(remotessn))
pscqos = self._find_qos(primaryqos)
sscqos = self._find_qos(secondaryqos, destssn)
if not destssn:
LOG.error(_LE('create_live_volume: Unable to find remote %s'),
remotessn)
elif not pscqos:
LOG.error(_LE('create_live_volume: Unable to find or create '
'qos node %s'), primaryqos)
elif not sscqos:
LOG.error(_LE('create_live_volume: Unable to find or create remote'
' qos node %(qos)s on %(ssn)s'),
{'qos': secondaryqos, 'ssn': destssn})
else:
payload = {}
payload['PrimaryVolume'] = self._get_id(scvolume)
payload['PrimaryQosNode'] = self._get_id(pscqos)
payload['SecondaryQosNode'] = self._get_id(sscqos)
payload['SecondaryStorageCenter'] = destssn
payload['StorageCenter'] = self.ssn
# payload['Dedup'] = False
payload['FailoverAutomaticallyEnabled'] = False
payload['RestoreAutomaticallyEnabled'] = False
payload['SwapRolesAutomaticallyEnabled'] = False
payload['ReplicateActiveReplay'] = active
payload['Type'] = 'Synchronous' if sync else 'Asynchronous'
secondaryvolumeattributes = {}
secondaryvolumeattributes['CreateSourceVolumeFolderPath'] = True
secondaryvolumeattributes['Notes'] = self.notes
secondaryvolumeattributes['Name'] = self._repl_name(
scvolume['name'])
payload[
'SecondaryVolumeAttributes'] = secondaryvolumeattributes
r = self.client.post('StorageCenter/ScLiveVolume', payload, True)
if self._check_result(r):
LOG.info(_LI('create_live_volume: Live Volume created from'
'%(svol)s to %(ssn)s'),
{'svol': self._get_id(scvolume), 'ssn': remotessn})
return self._get_json(r)
LOG.error(_LE('create_live_volume: Failed to create Live Volume from'
'%(svol)s to %(ssn)s'),
{'svol': self._get_id(scvolume), 'ssn': remotessn})
return None
def delete_live_volume(self, sclivevolume, deletesecondaryvolume):
"""Deletes the live volume.
:param sclivevolume: ScLiveVolume object to be whacked.
:return: Boolean on success/fail.
"""
payload = {}
payload['DeleteSecondaryVolume'] = deletesecondaryvolume
payload['RecycleSecondaryVolume'] = False
r = self.client.delete('StorageCenter/ScLiveVolume/%s' %
self._get_id(sclivevolume), payload, True)
if self._check_result(r):
return True
return False
def swap_roles_live_volume(self, sclivevolume):
"""Swap live volume roles.
:param sclivevolume: Dell SC live volume object.
:return: True/False on success/failure.
"""
r = self.client.post('StorageCenter/ScLiveVolume/%s/SwapRoles' %
self._get_id(sclivevolume), {}, True)
if self._check_result(r):
return True
return False

View File

@ -157,24 +157,55 @@ class DellCommonDriver(driver.ConsistencyGroupVD, driver.ManageableVD,
if profile:
api.update_cg_volumes(profile, [volume])
def _do_repl(self, api, volume):
def _get_replication_specs(self, volume):
"""Checks if we can do replication.
Need the extra spec set and we have to be talking to EM.
:param api: Dell REST API object.
:param volume: Cinder Volume object.
:return: Boolean (True if replication enabled), Boolean (True if
replication type is sync.
:return: rinfo dict.
"""
do_repl = False
sync = False
rinfo = {'enabled': False, 'sync': False,
'live': False, 'active': False}
# Repl does not work with direct connect.
if not self.failed_over and not self.is_direct_connect:
if not self.is_direct_connect:
specs = self._get_volume_extra_specs(volume)
do_repl = specs.get('replication_enabled') == '<is> True'
sync = specs.get('replication_type') == '<in> sync'
return do_repl, sync
if (not self.failed_over and
specs.get('replication_enabled') == '<is> True'):
rinfo['enabled'] = True
if specs.get('replication_type') == '<in> sync':
rinfo['sync'] = True
if specs.get('replication:livevolume') == '<is> True':
rinfo['live'] = True
if specs.get('replication:activereplay') == '<is> True':
rinfo['active'] = True
# Some quick checks.
if rinfo['enabled']:
replication_target_count = len(self.backends)
msg = None
if replication_target_count == 0:
msg = _(
'Replication setup failure: replication has been '
'enabled but no replication target has been specified '
'for this backend.')
if rinfo['live'] and replication_target_count != 1:
msg = _('Replication setup failure: replication:livevolume'
' has been enabled but more than one replication '
'target has been specified for this backend.')
if msg:
LOG.debug(msg)
raise exception.ReplicationError(message=msg)
# Got this far. Life is good. Return our data.
return rinfo
def _is_live_vol(self, api, volume):
sclivevolume = None
rspecs = self._get_replication_specs(volume)
if rspecs['enabled'] and rspecs['live']:
# Find our volume and server.
sclivevolume = api.get_live_volume(volume['provider_id'])
return sclivevolume
def _create_replications(self, api, volume, scvolume):
"""Creates any appropriate replications for a given volume.
@ -188,23 +219,32 @@ class DellCommonDriver(driver.ConsistencyGroupVD, driver.ManageableVD,
# for now we assume we have an array named backends.
replication_driver_data = None
# Replicate if we are supposed to.
do_repl, sync = self._do_repl(api, volume)
if do_repl:
rspecs = self._get_replication_specs(volume)
if rspecs['enabled']:
for backend in self.backends:
# Check if we are to replicate the active replay or not.
specs = self._get_volume_extra_specs(volume)
replact = specs.get('replication:activereplay') == '<is> True'
if not api.create_replication(scvolume,
backend['target_device_id'],
backend.get('qosnode',
'cinderqos'),
sync,
backend.get('diskfolder', None),
replact):
targetdeviceid = backend['target_device_id']
primaryqos = backend.get('qosnode', 'cinderqos')
secondaryqos = backend.get('remoteqos', 'cinderqos')
diskfolder = backend.get('diskfolder', None)
obj = None
if rspecs['live']:
# We are rolling with a live volume.
obj = api.create_live_volume(scvolume, targetdeviceid,
rspecs['active'],
rspecs['sync'],
primaryqos, secondaryqos)
else:
# Else a regular replication.
obj = api.create_replication(scvolume, targetdeviceid,
primaryqos, rspecs['sync'],
diskfolder, rspecs['active'])
# This is either a ScReplication object or a ScLiveVolume
# object. So long as it isn't None we are fine.
if not obj:
# Create replication will have printed a better error.
msg = _('Replication %(name)s to %(ssn)s failed.') % {
'name': volume['id'],
'ssn': backend['target_device_id']}
'ssn': targetdeviceid}
raise exception.VolumeBackendAPIException(data=msg)
if not replication_driver_data:
replication_driver_data = backend['target_device_id']
@ -295,6 +335,40 @@ class DellCommonDriver(driver.ConsistencyGroupVD, driver.ManageableVD,
ssnstrings.append(ssnstring)
return ssnstrings
def _delete_live_volume(self, api, volume):
"""Delete live volume associated with volume.
:param api:Dell REST API object.
:param volume: Cinder Volume object
:return: True if we actually deleted something. False for everything
else.
"""
# Live Volume was added after provider_id support. So just assume it is
# there.
replication_driver_data = volume.get('replication_driver_data')
# Do we have any replication driver data?
if replication_driver_data:
# Valid replication data?
ssnstrings = self._split_driver_data(replication_driver_data)
if ssnstrings:
ssn = int(ssnstrings[0])
sclivevolume = api.get_live_volume(volume.get('provider_id'))
# Have we found the live volume?
if (sclivevolume and
sclivevolume.get('secondaryScSerialNumber') == ssn and
api.delete_live_volume(sclivevolume, True)):
LOG.info(_LI('%(vname)s\'s replication live volume has '
'been deleted from storage Center %(sc)s,'),
{'vname': volume.get('id'),
'sc': ssn})
return True
# If we are here either we do not have a live volume, we do not have
# one on our configured SC or we were not able to delete it.
# Either way, warn and leave.
LOG.warning(_LW('Unable to delete %s live volume.'),
volume.get('id'))
return False
def _delete_replications(self, api, volume):
"""Delete replications associated with a given volume.
@ -304,26 +378,24 @@ class DellCommonDriver(driver.ConsistencyGroupVD, driver.ManageableVD,
:param api: Dell REST API object.
:param volume: Cinder Volume object
:return:
:return: None
"""
do_repl, sync = self._do_repl(api, volume)
if do_repl:
replication_driver_data = volume.get('replication_driver_data')
if replication_driver_data:
ssnstrings = self._split_driver_data(replication_driver_data)
volume_name = volume.get('id')
provider_id = volume.get('provider_id')
scvol = api.find_volume(volume_name, provider_id)
# This is just a string of ssns separated by commas.
# Trundle through these and delete them all.
for ssnstring in ssnstrings:
ssn = int(ssnstring)
if not api.delete_replication(scvol, ssn):
LOG.warning(_LW('Unable to delete replication of '
'Volume %(vname)s to Storage Center '
'%(sc)s.'),
{'vname': volume_name,
'sc': ssnstring})
replication_driver_data = volume.get('replication_driver_data')
if replication_driver_data:
ssnstrings = self._split_driver_data(replication_driver_data)
volume_name = volume.get('id')
provider_id = volume.get('provider_id')
scvol = api.find_volume(volume_name, provider_id)
# This is just a string of ssns separated by commas.
# Trundle through these and delete them all.
for ssnstring in ssnstrings:
ssn = int(ssnstring)
# Are we a replication or a live volume?
if not api.delete_replication(scvol, ssn):
LOG.warning(_LW('Unable to delete replication of Volume '
'%(vname)s to Storage Center %(sc)s.'),
{'vname': volume_name,
'sc': ssnstring})
# If none of that worked or there was nothing to do doesn't matter.
# Just move on.
@ -335,7 +407,12 @@ class DellCommonDriver(driver.ConsistencyGroupVD, driver.ManageableVD,
LOG.debug('Deleting volume %s', volume_name)
with self._client.open_connection() as api:
try:
self._delete_replications(api, volume)
rspecs = self._get_replication_specs(volume)
if rspecs['enabled']:
if rspecs['live']:
self._delete_live_volume(api, volume)
else:
self._delete_replications(api, volume)
deleted = api.delete_volume(volume_name, provider_id)
except Exception:
with excutils.save_and_reraise_exception():
@ -1241,6 +1318,78 @@ class DellCommonDriver(driver.ConsistencyGroupVD, driver.ManageableVD,
'updates': model_update})
return volume_updates
def _failback_replication(self, api, volume, qosnode):
"""Sets up the replication failback.
:param api: Dell SC API.
:param volume: Cinder Volume
:param qosnode: Dell QOS node object.
:return: replitem dict.
"""
LOG.info(_LI('failback_volumes: replicated volume'))
# Get our current volume.
cvol = api.find_volume(volume['id'], volume['provider_id'])
# Original volume on the primary.
ovol = api.find_repl_volume(volume['id'], api.primaryssn,
None, True, False)
# Delete our current mappings.
api.remove_mappings(cvol)
# If there is a replication to delete do so.
api.delete_replication(ovol, api.ssn, False)
# Replicate to a common replay.
screpl = api.replicate_to_common(cvol, ovol, 'tempqos')
# We made it this far. Update our status.
screplid = None
status = ''
if screpl:
screplid = screpl['instanceId']
nvolid = screpl['destinationVolume']['instanceId']
status = 'inprogress'
else:
LOG.error(_LE('Unable to restore %s'), volume['id'])
screplid = None
nvolid = None
status = 'error'
# Save some information for the next step.
# nvol is the new volume created by replicate_to_common.
# We also grab our extra specs here.
replitem = {
'volume': volume,
'specs': self._parse_extraspecs(volume),
'qosnode': qosnode,
'screpl': screplid,
'cvol': cvol['instanceId'],
'ovol': ovol['instanceId'],
'nvol': nvolid,
'rdd': six.text_type(api.ssn),
'status': status}
return replitem
def _failback_live_volume(self, api, id, provider_id):
"""failback the live volume to its original
:param api: Dell SC API
:param id: Volume ID
:param provider_id: Dell Instance ID
:return: model_update dict
"""
model_update = {}
sclivevolume = api.get_live_volume(provider_id)
if sclivevolume and api.swap_roles_live_volume(sclivevolume):
LOG.info(_LI('Success swapping sclivevolume roles %s'), id)
model_update = {
'status': 'available',
'replication_status': 'enabled',
'provider_id':
sclivevolume['secondaryVolume']['instanceId']}
else:
LOG.info(_LI('Failure swapping roles %s'), id)
model_update = {'status': 'error'}
return model_update
def failback_volumes(self, volumes):
"""This is a generic volume failback.
@ -1258,54 +1407,32 @@ class DellCommonDriver(driver.ConsistencyGroupVD, driver.ManageableVD,
volume_updates = []
replitems = []
screplid = None
status = ''
# Trundle through the volumes. Update non replicated to alive again
# and reverse the replications for the remaining volumes.
for volume in volumes:
LOG.info(_LI('failback_volumes: starting volume: %s'), volume)
model_update = {}
if volume.get('replication_driver_data'):
LOG.info(_LI('failback_volumes: replicated volume'))
# Get our current volume.
cvol = api.find_volume(volume['id'], volume['provider_id'])
# Original volume on the primary.
ovol = api.find_repl_volume(volume['id'], api.primaryssn,
None, True, False)
# Delete our current mappings.
api.remove_mappings(cvol)
# If there is a replication to delete do so.
api.delete_replication(ovol, api.ssn, False)
# Replicate to a common replay.
screpl = api.replicate_to_common(cvol, ovol, 'tempqos')
# We made it this far. Update our status.
if screpl:
screplid = screpl['instanceId']
nvolid = screpl['destinationVolume']['instanceId']
status = 'inprogress'
rspecs = self._get_replication_specs(volume)
if rspecs['live']:
model_update = self._failback_live_volume(
api, volume['id'], volume['provider_id'])
else:
LOG.error(_LE('Unable to restore %s'), volume['id'])
screplid = None
nvolid = None
status = 'error'
replitem = self._failback_replication(api, volume,
qosnode)
# Save some information for the next step.
# nvol is the new volume created by replicate_to_common.
# We also grab our extra specs here.
replitems.append(
{'volume': volume,
'specs': self._parse_extraspecs(volume),
'qosnode': qosnode,
'screpl': screplid,
'cvol': cvol['instanceId'],
'ovol': ovol['instanceId'],
'nvol': nvolid,
'rdd': six.text_type(api.ssn),
'status': status})
# Save some information for the next step.
# nvol is the new volume created by
# replicate_to_common. We also grab our
# extra specs here.
replitems.append(replitem)
else:
# Not replicated. Just set it to available.
model_update = {'status': 'available'}
# Either we are failed over or our status is now error.
# Save our update
if model_update:
volume_updates.append({'volume_id': volume['id'],
'updates': model_update})
@ -1324,6 +1451,33 @@ class DellCommonDriver(driver.ConsistencyGroupVD, driver.ManageableVD,
self._update_backend(None)
return volume_updates
def _failover_replication(self, api, id, provider_id, destssn):
rvol = api.break_replication(id, provider_id, destssn)
model_update = {}
if rvol:
LOG.info(_LI('Success failing over volume %s'), id)
model_update = {'replication_status': 'failed-over',
'provider_id': rvol['instanceId']}
else:
LOG.info(_LI('Failed failing over volume %s'), id)
model_update = {'status': 'error'}
return model_update
def _failover_live_volume(self, api, id, provider_id):
model_update = {}
sclivevolume = api.get_live_volume(provider_id)
if sclivevolume and api.swap_roles_live_volume(sclivevolume):
LOG.info(_LI('Success swapping sclivevolume roles %s'), id)
model_update = {'replication_status': 'failed-over',
'provider_id':
sclivevolume['secondaryVolume']['instanceId']}
else:
LOG.info(_LI('Failure swapping roles %s'), id)
model_update = {'status': 'error'}
return model_update
def failover_host(self, context, volumes, secondary_id=None):
"""Failover to secondary.
@ -1341,7 +1495,6 @@ class DellCommonDriver(driver.ConsistencyGroupVD, driver.ManageableVD,
'replication_status': 'failed-over',
'replication_extended_status': 'whatever',...}},]
"""
LOG.debug('failover-host')
LOG.debug(self.failed_over)
LOG.debug(self.active_backend_id)
@ -1366,21 +1519,15 @@ class DellCommonDriver(driver.ConsistencyGroupVD, driver.ManageableVD,
for volume in volumes:
model_update = {}
if volume.get('replication_driver_data'):
rvol = api.break_replication(
volume['id'], volume.get('provider_id'),
destssn)
if rvol:
LOG.info(_LI('Success failing over volume %s'),
volume['id'])
rspecs = self._get_replication_specs(volume)
if rspecs['live']:
model_update = self._failover_live_volume(
api, volume['id'],
volume.get('provider_id'))
else:
LOG.info(_LI('Failed failing over volume %s'),
volume['id'])
# We should note that we are now failed over
# and that we have a new instanceId.
model_update = {
'replication_status': 'failed-over',
'provider_id': rvol['instanceId']}
model_update = self._failover_replication(
api, volume['id'],
volume.get('provider_id'), destssn)
else:
# Not a replicated volume. Try to unmap it.
scvolume = api.find_volume(

View File

@ -18,7 +18,7 @@ from oslo_log import log as logging
from oslo_utils import excutils
from cinder import exception
from cinder.i18n import _, _LE
from cinder.i18n import _, _LE, _LW
from cinder import interface
from cinder.volume import driver
from cinder.volume.drivers.dell import dell_storagecenter_common
@ -53,11 +53,12 @@ class DellStorageCenterFCDriver(dell_storagecenter_common.DellCommonDriver,
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.1.0 - Failback supported.
3.2.0 - Live Volume support.
"""
VERSION = '3.1.0'
VERSION = '3.2.0'
def __init__(self, *args, **kwargs):
super(DellStorageCenterFCDriver, self).__init__(*args, **kwargs)
@ -84,17 +85,13 @@ class DellStorageCenterFCDriver(dell_storagecenter_common.DellCommonDriver,
LOG.debug('Initialize connection: %s', volume_name)
with self._client.open_connection() as api:
try:
# Find our server.
scserver = None
wwpns = connector.get('wwpns')
for wwn in wwpns:
scserver = api.find_server(wwn)
if scserver is not None:
break
# Find our server.
scserver = self._find_server(api, wwpns)
# No? Create it.
if scserver is None:
scserver = api.create_server_multiple_hbas(wwpns)
scserver = api.create_server(wwpns)
# Find the volume on the storage center.
scvolume = api.find_volume(volume_name, provider_id)
if scserver is not None and scvolume is not None:
@ -105,6 +102,16 @@ class DellStorageCenterFCDriver(dell_storagecenter_common.DellCommonDriver,
scvolume = api.get_volume(scvolume['instanceId'])
lun, targets, init_targ_map = api.find_wwns(scvolume,
scserver)
sclivevolume = self._is_live_vol(api, volume)
if 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)
if lun is not None and len(targets) > 0:
data = {'driver_volume_type': 'fibre_channel',
'data': {'target_lun': lun,
@ -124,6 +131,42 @@ class DellStorageCenterFCDriver(dell_storagecenter_common.DellCommonDriver,
# 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, 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
@ -132,17 +175,23 @@ class DellStorageCenterFCDriver(dell_storagecenter_common.DellCommonDriver,
LOG.debug('Terminate connection: %s', volume_name)
with self._client.open_connection() as api:
try:
scserver = None
wwpns = connector.get('wwpns')
for wwn in wwpns:
scserver = api.find_server(wwn)
if scserver is not None:
break
scserver = self._find_server(api, wwpns)
# Find the volume on the storage center.
scvolume = api.find_volume(volume_name, provider_id)
# Get our target map so we can return it to free up a zone.
lun, targets, init_targ_map = api.find_wwns(scvolume, scserver)
# Unmap from our secondary first.
sclivevolume = self._is_live_vol(api, volume)
if 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
@ -168,3 +217,24 @@ class DellStorageCenterFCDriver(dell_storagecenter_common.DellCommonDriver,
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, [], {}

View File

@ -18,7 +18,7 @@ from oslo_log import log as logging
from oslo_utils import excutils
from cinder import exception
from cinder.i18n import _, _LE, _LI
from cinder.i18n import _, _LE, _LI, _LW
from cinder import interface
from cinder.volume import driver
from cinder.volume.drivers.dell import dell_storagecenter_common
@ -53,10 +53,11 @@ class DellStorageCenterISCSIDriver(dell_storagecenter_common.DellCommonDriver,
2.5.0 - ManageableSnapshotsVD implemented.
3.0.0 - ProviderID utilized.
3.1.0 - Failback Supported.
3.2.0 - Live Volume support.
"""
VERSION = '3.1.0'
VERSION = '3.2.0'
def __init__(self, *args, **kwargs):
super(DellStorageCenterISCSIDriver, self).__init__(*args, **kwargs)
@ -83,39 +84,32 @@ class DellStorageCenterISCSIDriver(dell_storagecenter_common.DellCommonDriver,
provider_id = volume.get('provider_id')
initiator_name = connector.get('initiator')
multipath = connector.get('multipath', False)
LOG.info(_LI('initialize_ connection: %(vol)s:%(initiator)s'),
LOG.info(_LI('initialize_ connection: %(vol)s:%(pid)s:'
'%(intr)s. Multipath is %(mp)r'),
{'vol': volume_name,
'initiator': initiator_name})
'pid': provider_id,
'intr': initiator_name,
'mp': multipath})
with self._client.open_connection() as api:
try:
# Find our server.
server = api.find_server(initiator_name)
scserver = api.find_server(initiator_name)
# No? Create it.
if server is None:
server = api.create_server(initiator_name)
if scserver is None:
scserver = api.create_server([initiator_name])
# Find the volume on the storage center.
scvolume = api.find_volume(volume_name, provider_id)
# if we have a server and a volume lets bring them together.
if server is not None and scvolume is not None:
mapping = api.map_volume(scvolume,
server)
if scserver is not None and scvolume 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(provider_id)
# Our return.
iscsiprops = {}
ip = None
port = None
if not multipath:
# We want to make sure we point to the specified
# ip address for our target_portal return. This
# isn't an issue with multipath since it should
# try all the alternate portal.
ip = self.configuration.iscsi_ip_address
port = self.configuration.iscsi_port
# Three cases that should all be satisfied with the
# same return of Target_Portal and Target_Portals.
@ -128,10 +122,22 @@ class DellStorageCenterISCSIDriver(dell_storagecenter_common.DellCommonDriver,
# 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,
ip,
port))
iscsiprops = api.find_iscsi_properties(scvolume)
# If this is a live volume we need to map up our
# secondary volume.
sclivevolume = self._is_live_vol(api, volume)
if 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'])
# TODO(tswanson): Get non multipath info from primary.
# Return our iscsi properties.
iscsiprops['discard'] = True
return {'driver_volume_type': 'iscsi',
@ -151,6 +157,44 @@ class DellStorageCenterISCSIDriver(dell_storagecenter_common.DellCommonDriver,
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], 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)
# 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(_LW('Unable to map live volume secondary volume'
' %(vol)s to secondary server intiator: %(init)r'),
{'vol': sclivevolume['secondaryVolume']['instanceName'],
'init': initiatorname})
return data
def terminate_connection(self, volume, connector, force=False, **kwargs):
# Grab some initial info.
initiator_name = connector.get('initiator')
@ -165,6 +209,10 @@ class DellStorageCenterISCSIDriver(dell_storagecenter_common.DellCommonDriver,
# Find the volume on the storage center.
scvolume = api.find_volume(volume_name, provider_id)
sclivevolume = self._is_live_vol(api, volume)
if sclivevolume:
self.terminate_secondary(api, sclivevolume, initiator_name)
# If we have a server and a volume lets pull them apart.
if (scserver is not None and
scvolume is not None and
@ -179,3 +227,11 @@ class DellStorageCenterISCSIDriver(dell_storagecenter_common.DellCommonDriver,
'vol': volume_name})
raise exception.VolumeBackendAPIException(
_('Terminate connection failed'))
def terminate_secondary(self, api, sclivevolume, initiatorname):
# Find our server.
secondary = api.find_server(initiatorname,
sclivevolume['secondaryScSerialNumber'])
secondaryvol = api.get_volume(
sclivevolume['secondaryVolume']['instanceId'])
return api.unmap_volume(secondaryvol, secondary)

View File

@ -0,0 +1,4 @@
---
features:
- Added support for the use of live volume in place of
standard replication in the Dell SC driver.