diff --git a/cinder/tests/unit/volume/drivers/dell/test_dellfc.py b/cinder/tests/unit/volume/drivers/dell/test_dellfc.py index 4faa37efcd7..a91d8b900a4 100644 --- a/cinder/tests/unit/volume/drivers/dell/test_dellfc.py +++ b/cinder/tests/unit/volume/drivers/dell/test_dellfc.py @@ -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}) diff --git a/cinder/tests/unit/volume/drivers/dell/test_dellsc.py b/cinder/tests/unit/volume/drivers/dell/test_dellsc.py index 94c481de156..c620f524766 100644 --- a/cinder/tests/unit/volume/drivers/dell/test_dellsc.py +++ b/cinder/tests/unit/volume/drivers/dell/test_dellsc.py @@ -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': ' True', + 'replication_enabled': ' True', + 'replication:livevolume': ' 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': ' True', + 'replication:livevolume': ' 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': ' True', + 'replication:livevolume': ' True', + 'replication_type': ' 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') diff --git a/cinder/tests/unit/volume/drivers/dell/test_dellscapi.py b/cinder/tests/unit/volume/drivers/dell/test_dellscapi.py index 791291990e6..7c0132e2ed2 100644 --- a/cinder/tests/unit/volume/drivers/dell/test_dellscapi.py +++ b/cinder/tests/unit/volume/drivers/dell/test_dellscapi.py @@ -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): diff --git a/cinder/volume/drivers/dell/dell_storagecenter_api.py b/cinder/volume/drivers/dell/dell_storagecenter_api.py index a6f5a3ae8d3..63087d3669d 100644 --- a/cinder/volume/drivers/dell/dell_storagecenter_api.py +++ b/cinder/volume/drivers/dell/dell_storagecenter_api.py @@ -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 diff --git a/cinder/volume/drivers/dell/dell_storagecenter_common.py b/cinder/volume/drivers/dell/dell_storagecenter_common.py index 0997a5cdb65..9d26d58e42d 100644 --- a/cinder/volume/drivers/dell/dell_storagecenter_common.py +++ b/cinder/volume/drivers/dell/dell_storagecenter_common.py @@ -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') == ' True' - sync = specs.get('replication_type') == ' sync' - return do_repl, sync + if (not self.failed_over and + specs.get('replication_enabled') == ' True'): + rinfo['enabled'] = True + if specs.get('replication_type') == ' sync': + rinfo['sync'] = True + if specs.get('replication:livevolume') == ' True': + rinfo['live'] = True + if specs.get('replication:activereplay') == ' 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') == ' 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( diff --git a/cinder/volume/drivers/dell/dell_storagecenter_fc.py b/cinder/volume/drivers/dell/dell_storagecenter_fc.py index 9fe94392c42..d1bc0c9bed3 100644 --- a/cinder/volume/drivers/dell/dell_storagecenter_fc.py +++ b/cinder/volume/drivers/dell/dell_storagecenter_fc.py @@ -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, [], {} diff --git a/cinder/volume/drivers/dell/dell_storagecenter_iscsi.py b/cinder/volume/drivers/dell/dell_storagecenter_iscsi.py index 846b353d2e7..f675c741d1d 100644 --- a/cinder/volume/drivers/dell/dell_storagecenter_iscsi.py +++ b/cinder/volume/drivers/dell/dell_storagecenter_iscsi.py @@ -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) diff --git a/releasenotes/notes/Dell-SC-live-volume-41bacddee199ce83.yaml b/releasenotes/notes/Dell-SC-live-volume-41bacddee199ce83.yaml new file mode 100644 index 00000000000..21b56a6c526 --- /dev/null +++ b/releasenotes/notes/Dell-SC-live-volume-41bacddee199ce83.yaml @@ -0,0 +1,4 @@ +--- +features: + - Added support for the use of live volume in place of + standard replication in the Dell SC driver. \ No newline at end of file