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:
parent
aaf820f429
commit
fecbf75edc
@ -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})
|
||||
|
@ -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')
|
||||
|
@ -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):
|
||||
|
||||
|
@ -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
|
||||
|
@ -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(
|
||||
|
@ -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, [], {}
|
||||
|
@ -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)
|
||||
|
@ -0,0 +1,4 @@
|
||||
---
|
||||
features:
|
||||
- Added support for the use of live volume in place of
|
||||
standard replication in the Dell SC driver.
|
Loading…
Reference in New Issue
Block a user