From 6806ea491f76be8e6717a7ec4238ef672df9b85a Mon Sep 17 00:00:00 2001 From: Tom Swanson Date: Tue, 21 Jul 2015 14:05:14 -0500 Subject: [PATCH] Dell SC: Fix legacy bug, init_conn bug and REST API bug. If Dell iSCSI ports are in legacy mode rather than virtual port mode initialize_connection would fail to return connection information. If initialize_connection was called more than once for the same connection it would attempt to map the volumes again and fail. It should instead just return the connection information and not try to map the volumes again. The Dell REST API 2.2 and later changed the format of the payload filter. We need to support either format. Finally consistency group support is only supported in Dell REST API 2.1 or later. If the version is 2.0 we raise NotImplemented. Change-Id: Ie798c49a591abb510bd947fc89c26de18f0f3c87 Closes-Bug: #1476786 Closes-Bug: #1476794 Closes-Bug: #1476797 --- cinder/tests/unit/test_dellscapi.py | 603 +++++++++++++++--- .../drivers/dell/dell_storagecenter_api.py | 365 ++++++++--- 2 files changed, 794 insertions(+), 174 deletions(-) diff --git a/cinder/tests/unit/test_dellscapi.py b/cinder/tests/unit/test_dellscapi.py index 709e0b7f4..93eff06d5 100644 --- a/cinder/tests/unit/test_dellscapi.py +++ b/cinder/tests/unit/test_dellscapi.py @@ -474,29 +474,6 @@ class DellSCSanAPITestCase(test.TestCase): u'instanceName': u'Other Multipath', u'objectType': u'ScServerOperatingSystem'}}] - MAP_PROFILES = [{u'instanceId': u'64702.2941', - u'scName': u'Storage Center 64702', - u'scSerialNumber': 64702, - u'controller': {u'instanceId': u'64702.64703', - u'instanceName': u'SN 64703', - u'objectType': u'ScController'}, - u'lunUsed': [1], - u'server': {u'instanceId': u'64702.47', - u'instanceName': u'Server_21000024ff30441d', - u'objectType': u'ScPhysicalServer'}, - u'volume': - {u'instanceId': u'64702.6025', - u'instanceName': u'Server_21000024ff30441d Test Vol', - u'objectType': u'ScVolume'}, - u'connectivity': u'Up', - u'readOnly': False, - u'objectType': u'ScMappingProfile', - u'hostCache': False, - u'mappedVia': u'Server', - u'mapCount': 3, - u'instanceName': u'6025-47', - u'lunRequested': u'N/A'}] - MAP_PROFILE = {u'instanceId': u'64702.2941', u'scName': u'Storage Center 64702', u'scSerialNumber': 64702, @@ -520,6 +497,8 @@ class DellSCSanAPITestCase(test.TestCase): u'instanceName': u'6025-47', u'lunRequested': u'N/A'} + MAP_PROFILES = [MAP_PROFILE] + MAPPINGS = [{u'profile': {u'instanceId': u'64702.104', u'instanceName': u'92-30', u'objectType': u'ScMappingProfile'}, @@ -1473,6 +1452,57 @@ class DellSCSanAPITestCase(test.TestCase): u'userCreated': False, u'volumeCount': 0}] + ISCSI_CONFIG = { + u'initialReadyToTransfer': True, + u'scSerialNumber': 64065, + u'macAddress': u'00c0dd-1da173', + u'instanceId': u'64065.5764839588723573038.6', + u'vlanTagging': False, + u'mapCount': 8, + u'cardModel': u'Qle4062', + u'portNumber': 3260, + u'firstBurstSize': 256, + u'deviceName': u'PCIDEV09', + u'subnetMask': u'255.255.255.0', + u'speed': u'1 Gbps', + u'maximumVlanCount': 0, + u'gatewayIpAddress': u'192.168.0.1', + u'slot': 4, + u'sfpData': u'', + u'dataDigest': False, + u'chapEnabled': False, + u'firmwareVersion': u'03.00.01.77', + u'preferredControllerIndex': 64066, + u'defaultTimeToRetain': 20, + u'objectType': u'ScControllerPortIscsiConfiguration', + u'instanceName': u'5000d31000FCBE43', + u'scName': u'sc64065', + u'revision': u'0', + u'controllerPortIndex': 5764839588723573038, + u'maxBurstSize': 512, + u'targetCount': 20, + u'description': u'QLogic QLE4062 iSCSI Adapter Rev 0 Copper', + u'vlanSupported': True, + u'chapName': u'iqn.2002-03.com.compellent:5000d31000fcbe43', + u'windowSize': 128, + u'vlanId': 0, + u'defaultTimeToWait': 2, + u'headerDigest': False, + u'slotPort': 2, + u'immediateDataWrite': False, + u'storageCenterTargetCount': 20, + u'vlanCount': 0, + u'scsiCommandTimeout': 60, + u'slotType': u'PCI4', + u'ipAddress': u'192.168.0.21', + u'vlanUserPriority': 0, + u'bothCount': 0, + u'initiatorCount': 33, + u'keepAliveTimeout': 30, + u'homeControllerIndex': 64066, + u'chapSecret': u'', + u'maximumTransmissionUnit': 1500} + IQN = 'iqn.2002-03.com.compellent:5000D31000000001' WWN = u'21000024FF30441C' @@ -2839,6 +2869,37 @@ class DellSCSanAPITestCase(test.TestCase): self.assertTrue(mock_get_json.called) self.assertEqual([], res, 'Mapping count mismatch') + @mock.patch.object(dell_storagecenter_api.StorageCenterApi, + '_get_json', + return_value=MAP_PROFILES) + @mock.patch.object(dell_storagecenter_api.HttpClient, + 'get', + return_value=RESPONSE_200) + def test_find_mapping_profiles(self, + mock_get, + mock_get_json, + mock_close_connection, + mock_open_connection, + mock_init): + # Test case where ScVolume has no mappings + res = self.scapi._find_mapping_profiles(self.VOLUME) + self.assertTrue(mock_get.called) + self.assertTrue(mock_get_json.called) + self.assertEqual(self.MAP_PROFILES, res) + + @mock.patch.object(dell_storagecenter_api.HttpClient, + 'get', + return_value=RESPONSE_400) + def test_find_mapping_profiles_error(self, + mock_get, + mock_close_connection, + mock_open_connection, + mock_init): + # Test case where ScVolume has no mappings + res = self.scapi._find_mapping_profiles(self.VOLUME) + self.assertTrue(mock_get.called) + self.assertEqual([], res) + @mock.patch.object(dell_storagecenter_api.StorageCenterApi, '_first_result', return_value=CTRLR_PORT) @@ -3075,7 +3136,11 @@ class DellSCSanAPITestCase(test.TestCase): @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_mappings(self, + mock_is_virtualport_mode, mock_find_mappings, mock_find_domains, mock_find_ctrl_port, @@ -3084,6 +3149,7 @@ class DellSCSanAPITestCase(test.TestCase): mock_open_connection, mock_init): res = self.scapi.find_iscsi_properties(self.VOLUME) + 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) @@ -3112,7 +3178,11 @@ class DellSCSanAPITestCase(test.TestCase): @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, @@ -3123,6 +3193,7 @@ class DellSCSanAPITestCase(test.TestCase): # 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) @@ -3151,17 +3222,23 @@ class DellSCSanAPITestCase(test.TestCase): @mock.patch.object(dell_storagecenter_api.StorageCenterApi, '_find_mappings', return_value=MAPPINGS) - def test_find_iscsi_properties_by_address_not_found(self, - mock_find_mappings, - mock_find_domains, - mock_find_ctrl_port, - mock_find_active_ctrl, - mock_close_connection, - mock_open_connection, - mock_init): + @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) @@ -3204,7 +3281,11 @@ class DellSCSanAPITestCase(test.TestCase): @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_no_domain(self, + mock_is_virtualport_mode, mock_find_mappings, mock_find_domains, mock_find_ctrl_port, @@ -3216,6 +3297,7 @@ class DellSCSanAPITestCase(test.TestCase): self.assertRaises(exception.VolumeBackendAPIException, self.scapi.find_iscsi_properties, self.VOLUME) + 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) @@ -3227,15 +3309,15 @@ class DellSCSanAPITestCase(test.TestCase): @mock.patch.object(dell_storagecenter_api.StorageCenterApi, '_find_controller_port', return_value=None) - @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_no_ctrl_port(self, + mock_is_virtualport_mode, mock_find_mappings, - mock_find_domains, mock_find_ctrl_port, mock_find_active_controller, mock_close_connection, @@ -3245,8 +3327,8 @@ class DellSCSanAPITestCase(test.TestCase): self.assertRaises(exception.VolumeBackendAPIException, self.scapi.find_iscsi_properties, self.VOLUME) + 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) @@ -3262,7 +3344,11 @@ class DellSCSanAPITestCase(test.TestCase): @mock.patch.object(dell_storagecenter_api.StorageCenterApi, '_find_mappings', return_value=MAPPINGS_READ_ONLY) + @mock.patch.object(dell_storagecenter_api.StorageCenterApi, + '_is_virtualport_mode', + return_value=True) def test_find_iscsi_properties_ro(self, + mock_is_virtualport_mode, mock_find_mappings, mock_find_domains, mock_find_ctrl_port, @@ -3272,6 +3358,7 @@ class DellSCSanAPITestCase(test.TestCase): mock_init): # Test case where Read Only mappings are found res = self.scapi.find_iscsi_properties(self.VOLUME) + 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) @@ -3300,7 +3387,11 @@ class DellSCSanAPITestCase(test.TestCase): @mock.patch.object(dell_storagecenter_api.StorageCenterApi, '_find_mappings', return_value=MAPPINGS_MULTI_PORTAL) + @mock.patch.object(dell_storagecenter_api.StorageCenterApi, + '_is_virtualport_mode', + return_value=True) def test_find_iscsi_properties_multi_portals(self, + mock_is_virtualport_mode, mock_find_mappings, mock_find_domains, mock_find_ctrl_port, @@ -3314,6 +3405,7 @@ class DellSCSanAPITestCase(test.TestCase): self.assertTrue(mock_find_domains.called) self.assertTrue(mock_find_ctrl_port.called) self.assertTrue(mock_find_active_controller.called) + self.assertTrue(mock_is_virtualport_mode.called) expected = {'access_mode': 'rw', 'target_discovered': False, 'target_iqn': @@ -3332,13 +3424,275 @@ class DellSCSanAPITestCase(test.TestCase): u'192.168.0.25:3260']} self.assertEqual(expected, res, 'Wrong Target Info') + @mock.patch.object(dell_storagecenter_api.StorageCenterApi, + '_find_active_controller', + return_value='64702.5764839588723736131.91') + @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_mappings_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): + res = self.scapi.find_iscsi_properties(self.VOLUME) + self.assertTrue(mock_is_virtualport_mode.called) + self.assertTrue(mock_find_mappings.called) + self.assertTrue(mock_find_ctrl_port.called) + self.assertTrue(mock_find_controller_port_iscsi_config.called) + self.assertTrue(mock_find_active_controller.called) + expected = {'access_mode': 'rw', + '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.5764839588723736131.91') + @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=None) + def test_find_iscsi_properties_mappings_legacy_no_iscsi_config( + 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): + self.assertRaises(exception.VolumeBackendAPIException, + self.scapi.find_iscsi_properties, + self.VOLUME) + self.assertTrue(mock_is_virtualport_mode.called) + self.assertTrue(mock_find_mappings.called) + self.assertTrue(mock_find_ctrl_port.called) + 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 = {'access_mode': 'rw', + '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 = {'access_mode': 'rw', + '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_READ_ONLY) + @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_ro_legacy(self, + mock_find_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 where Read Only mappings are found + res = self.scapi.find_iscsi_properties(self.VOLUME) + 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_iscsi_config.called) + expected = {'access_mode': 'ro', + '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_MULTI_PORTAL) + @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_multi_portals_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 where there are multiple portals + res = self.scapi.find_iscsi_properties(self.VOLUME) + self.assertTrue(mock_find_mappings.called) + self.assertTrue(mock_find_ctrl_port.called) + self.assertTrue(mock_find_active_controller.called) + self.assertTrue(mock_is_virtualport_mode.called) + self.assertTrue(mock_find_controller_port_iscsi_config.called) + # Since we're feeding the same info back multiple times the information + # will be duped. + expected = {'access_mode': 'rw', + 'target_discovered': False, + 'target_iqn': + u'iqn.2002-03.com.compellent:5000d31000fcbe43', + 'target_iqns': + [u'iqn.2002-03.com.compellent:5000d31000fcbe43', + u'iqn.2002-03.com.compellent:5000d31000fcbe43'], + 'target_lun': 1, + 'target_luns': [1, 1], + 'target_portal': u'192.168.0.21:3260', + 'target_portals': [u'192.168.0.21:3260', + u'192.168.0.21:3260']} + self.assertEqual(expected, res, 'Wrong Target Info') + @mock.patch.object(dell_storagecenter_api.StorageCenterApi, '_first_result', return_value=MAP_PROFILE) @mock.patch.object(dell_storagecenter_api.HttpClient, 'post', return_value=RESPONSE_200) + @mock.patch.object(dell_storagecenter_api.StorageCenterApi, + '_find_mapping_profiles', + return_value=[]) def test_map_volume(self, + mock_find_mapping_profiles, mock_post, mock_first_result, mock_close_connection, @@ -3346,6 +3700,54 @@ class DellSCSanAPITestCase(test.TestCase): mock_init): res = self.scapi.map_volume(self.VOLUME, self.SCSERVER) + self.assertTrue(mock_find_mapping_profiles.called) + self.assertTrue(mock_post.called) + self.assertTrue(mock_first_result.called) + self.assertEqual(self.MAP_PROFILE, res, 'Incorrect ScMappingProfile') + + @mock.patch.object(dell_storagecenter_api.StorageCenterApi, + '_first_result', + return_value=MAP_PROFILE) + @mock.patch.object(dell_storagecenter_api.HttpClient, + 'post', + return_value=RESPONSE_200) + @mock.patch.object(dell_storagecenter_api.StorageCenterApi, + '_find_mapping_profiles', + return_value=MAP_PROFILES) + def test_map_volume_existing_mapping(self, + mock_find_mappings, + mock_post, + mock_first_result, + mock_close_connection, + mock_open_connection, + mock_init): + res = self.scapi.map_volume(self.VOLUME, + self.SCSERVER) + self.assertTrue(mock_find_mappings.called) + self.assertFalse(mock_post.called) + self.assertFalse(mock_first_result.called) + self.assertEqual(self.MAP_PROFILE, res, 'Incorrect ScMappingProfile') + + @mock.patch.object(dell_storagecenter_api.StorageCenterApi, + '_first_result', + return_value=MAP_PROFILE) + @mock.patch.object(dell_storagecenter_api.HttpClient, + 'post', + return_value=RESPONSE_200) + @mock.patch.object(dell_storagecenter_api.StorageCenterApi, + '_find_mapping_profiles', + return_value=[]) + def test_map_volume_existing_mapping_not_us(self, + mock_find_mappings, + mock_post, + mock_first_result, + mock_close_connection, + mock_open_connection, + mock_init): + server = {'instanceId': 64702.48} + res = self.scapi.map_volume(self.VOLUME, + server) + self.assertTrue(mock_find_mappings.called) self.assertTrue(mock_post.called) self.assertTrue(mock_first_result.called) self.assertEqual(self.MAP_PROFILE, res, 'Incorrect ScMappingProfile') @@ -3395,7 +3797,11 @@ class DellSCSanAPITestCase(test.TestCase): @mock.patch.object(dell_storagecenter_api.HttpClient, 'post', return_value=RESPONSE_204) + @mock.patch.object(dell_storagecenter_api.StorageCenterApi, + '_find_mapping_profiles', + return_value=[]) def test_map_volume_failure(self, + mock_find_mapping_profiles, mock_post, mock_close_connection, mock_open_connection, @@ -3403,6 +3809,7 @@ class DellSCSanAPITestCase(test.TestCase): # Test case where mapping volume to server fails res = self.scapi.map_volume(self.VOLUME, self.SCSERVER) + self.assertTrue(mock_find_mapping_profiles.called) self.assertTrue(mock_post.called) self.assertIsNone(res, 'None expected') @@ -3410,76 +3817,66 @@ class DellSCSanAPITestCase(test.TestCase): 'delete', return_value=RESPONSE_200) @mock.patch.object(dell_storagecenter_api.StorageCenterApi, - '_get_json', + '_find_mapping_profiles', return_value=MAP_PROFILES) - @mock.patch.object(dell_storagecenter_api.HttpClient, - 'get', - return_value=RESPONSE_200) def test_unmap_volume(self, - mock_get, - mock_get_json, + mock_find_mapping_profiles, mock_delete, mock_close_connection, mock_open_connection, mock_init): res = self.scapi.unmap_volume(self.VOLUME, self.SCSERVER) - self.assertTrue(mock_get.called) - self.assertTrue(mock_get_json.called) + self.assertTrue(mock_find_mapping_profiles.called) self.assertTrue(mock_delete.called) self.assertTrue(res) + @mock.patch.object(dell_storagecenter_api.StorageCenterApi, + '_find_mapping_profiles', + return_value=MAP_PROFILES) @mock.patch.object(dell_storagecenter_api.HttpClient, - 'get', + 'delete', return_value=RESPONSE_204) def test_unmap_volume_failure(self, - mock_get, + mock_delete, + mock_find_mapping_profiles, mock_close_connection, mock_open_connection, mock_init): res = self.scapi.unmap_volume(self.VOLUME, self.SCSERVER) - self.assertTrue(mock_get.called) + self.assertTrue(mock_find_mapping_profiles.called) + self.assertTrue(mock_delete.called) self.assertFalse(res) @mock.patch.object(dell_storagecenter_api.StorageCenterApi, - '_get_json', + '_find_mapping_profiles', return_value=[]) - @mock.patch.object(dell_storagecenter_api.HttpClient, - 'get', - return_value=RESPONSE_200) def test_unmap_volume_no_map_profile(self, - mock_get, - mock_get_json, + mock_find_mapping_profiles, mock_close_connection, mock_open_connection, mock_init): res = self.scapi.unmap_volume(self.VOLUME, self.SCSERVER) - self.assertTrue(mock_get.called) - self.assertTrue(mock_get_json.called) + self.assertTrue(mock_find_mapping_profiles.called) self.assertTrue(res) @mock.patch.object(dell_storagecenter_api.HttpClient, 'delete', return_value=RESPONSE_204) @mock.patch.object(dell_storagecenter_api.StorageCenterApi, - '_get_json', + '_find_mapping_profiles', return_value=MAP_PROFILES) - @mock.patch.object(dell_storagecenter_api.HttpClient, - 'get', - return_value=RESPONSE_200) def test_unmap_volume_del_fail(self, - mock_get, - mock_get_json, + mock_find_mapping_profiles, mock_delete, mock_close_connection, mock_open_connection, mock_init): res = self.scapi.unmap_volume(self.VOLUME, self.SCSERVER) - self.assertTrue(mock_get.called) - self.assertTrue(mock_get_json.called) + self.assertTrue(mock_find_mapping_profiles.called) self.assertTrue(mock_delete.called) self.assertFalse(res, False) @@ -3489,14 +3886,10 @@ class DellSCSanAPITestCase(test.TestCase): 'delete', return_value=RESPONSE_200) @mock.patch.object(dell_storagecenter_api.StorageCenterApi, - '_get_json', + '_find_mapping_profiles', return_value=MAP_PROFILES) - @mock.patch.object(dell_storagecenter_api.HttpClient, - 'get', - return_value=RESPONSE_200) def test_unmap_volume_no_vol_id(self, - mock_get, - mock_get_json, + mock_find_mapping_profiles, mock_delete, mock_get_id, mock_close_connection, @@ -3506,8 +3899,7 @@ class DellSCSanAPITestCase(test.TestCase): mock_get_id.side_effect = [None, '64702.47'] res = self.scapi.unmap_volume(self.VOLUME, self.SCSERVER) - self.assertFalse(mock_get.called) - self.assertFalse(mock_get_json.called) + self.assertFalse(mock_find_mapping_profiles.called) self.assertFalse(mock_delete.called) self.assertTrue(res) @@ -3517,14 +3909,10 @@ class DellSCSanAPITestCase(test.TestCase): 'delete', return_value=RESPONSE_200) @mock.patch.object(dell_storagecenter_api.StorageCenterApi, - '_get_json', + '_find_mapping_profiles', return_value=MAP_PROFILES) - @mock.patch.object(dell_storagecenter_api.HttpClient, - 'get', - return_value=RESPONSE_200) def test_unmap_volume_no_server_id(self, - mock_get, - mock_get_json, + mock_find_mapping_profiles, mock_delete, mock_get_id, mock_close_connection, @@ -3534,11 +3922,41 @@ class DellSCSanAPITestCase(test.TestCase): mock_get_id.side_effect = ['64702.3494', None] res = self.scapi.unmap_volume(self.VOLUME, self.SCSERVER) - self.assertFalse(mock_get.called) - self.assertFalse(mock_get_json.called) + self.assertFalse(mock_find_mapping_profiles.called) self.assertFalse(mock_delete.called) self.assertTrue(res) + @mock.patch.object(dell_storagecenter_api.StorageCenterApi, + '_get_json', + return_value=[{'a': 1}, {'a': 2}]) + @mock.patch.object(dell_storagecenter_api.HttpClient, + 'get', + return_value=RESPONSE_200) + def test_find_controller_port_iscsi_config(self, + mock_get, + mock_get_json, + mock_close_connection, + mock_open_connection, + mock_init): + # Not much to test here. Just make sure we call our stuff and + # that we return the first item returned to us. + res = self.scapi._find_controller_port_iscsi_config('guid') + self.assertTrue(mock_get.called) + self.assertTrue(mock_get_json.called) + self.assertEqual({'a': 1}, res) + + @mock.patch.object(dell_storagecenter_api.HttpClient, + 'get', + return_value=RESPONSE_400) + def test_find_controller_port_iscsi_config_err(self, + mock_get, + mock_close_connection, + mock_open_connection, + mock_init): + res = self.scapi._find_controller_port_iscsi_config('guid') + self.assertTrue(mock_get.called) + self.assertEqual(None, res) + @mock.patch.object(dell_storagecenter_api.StorageCenterApi, '_get_json', return_value=STRG_USAGE) @@ -5077,6 +5495,29 @@ class DellSCSanAPIConnectionTestCase(test.TestCase): response_nc.reason = u'duplicate' RESPONSE_204 = response_nc + APIDICT = {u'instanceId': u'0', + u'hostName': u'192.168.0.200', + u'userId': 434226, + u'connectionKey': u'', + u'minApiVersion': u'0.1', + u'webServicesPort': 3033, + u'locale': u'en_US', + u'objectType': u'ApiConnection', + u'secureString': u'', + u'applicationVersion': u'2.0.1', + u'source': u'REST', + u'commandLine': False, + u'application': u'Cinder REST Driver', + u'sessionKey': 1436460614863, + u'provider': u'EnterpriseManager', + u'instanceName': u'ApiConnection', + u'connected': True, + u'userName': u'Admin', + u'useHttps': False, + u'providerVersion': u'15.3.1.186', + u'apiVersion': u'2.2', + u'apiBuild': 199} + def setUp(self): super(DellSCSanAPIConnectionTestCase, self).setUp() @@ -5121,7 +5562,11 @@ class DellSCSanAPIConnectionTestCase(test.TestCase): @mock.patch.object(dell_storagecenter_api.HttpClient, 'post', return_value=RESPONSE_200) + @mock.patch.object(dell_storagecenter_api.StorageCenterApi, + '_get_json', + return_value=APIDICT) def test_open_connection(self, + mock_get_json, mock_post): self.scapi.open_connection() self.assertTrue(mock_post.called) diff --git a/cinder/volume/drivers/dell/dell_storagecenter_api.py b/cinder/volume/drivers/dell/dell_storagecenter_api.py index 95352f303..cd56cfb7d 100644 --- a/cinder/volume/drivers/dell/dell_storagecenter_api.py +++ b/cinder/volume/drivers/dell/dell_storagecenter_api.py @@ -33,16 +33,34 @@ class PayloadFilter(object): '''PayloadFilter Simple class for creating filters for interacting with the Dell - Storage API. - - Note that this defaults to "AND" filter types. This is a pretty limited - class. It only does the trivial filters required for this driver. + Storage API DropTop2 and later. ''' - def __init__(self): + def __init__(self, filtertype='AND'): self.payload = {} - self.payload['filterType'] = 'AND' - self.payload['filters'] = [] + self.payload['filter'] = {'filterType': filtertype, + 'filters': []} + + def append(self, name, val, filtertype='Equals'): + if val is not None: + apifilter = {} + apifilter['attributeName'] = name + apifilter['attributeValue'] = val + apifilter['filterType'] = filtertype + self.payload['filter']['filters'].append(apifilter) + + +class LegacyPayloadFilter(object): + + '''LegacyPayloadFilter + + Simple class for creating filters for interacting with the Dell + Storage API pre DropTop2. + ''' + + def __init__(self, filter_type='AND'): + self.payload = {'filterType': filter_type, + 'filters': []} def append(self, name, val, filtertype='Equals'): if val is not None: @@ -167,9 +185,18 @@ class StorageCenterApi(object): '''StorageCenterApi Handles calls to Dell Enterprise Manager (EM) via the REST API interface. - ''' - APIVERSION = '2.0.1' + Version history: + 1.0.0 - Initial driver + 1.1.0 - Added extra spec support for Storage Profile selection + 1.2.0 - Added consistency group support. + 2.0.0 - Switched to inheriting functional objects rather than volume + driver. + 2.1.0 - Added support for ManageableVD. + 2.2.0 - Added API 2.2 support. + 2.3.0 - Added Legacy Port Mode Support + ''' + APIVERSION = '2.3.0' def __init__(self, host, port, user, password, verify): '''This creates a connection to Dell Enterprise Manager. @@ -185,6 +212,8 @@ class StorageCenterApi(object): self.ssn = None self.vfname = 'openstack' self.sfname = 'openstack' + self.legacypayloadfilters = False + self.consisgroups = True self.client = HttpClient(host, port, user, @@ -286,6 +315,12 @@ class StorageCenterApi(object): blob) return None + def _get_payload_filter(self, filterType='AND'): + # 2.1 or earlier and we are talking LegacyPayloadFilters. + if self.legacypayloadfilters: + return LegacyPayloadFilter(filterType) + return PayloadFilter(filterType) + def open_connection(self): '''Authenticate against Dell Enterprise Manager. @@ -297,13 +332,41 @@ class StorageCenterApi(object): payload['ApplicationVersion'] = self.APIVERSION r = self.client.post('ApiConnection/Login', payload) - # TODO(Swanson): If we get a 400 back we should also print the text. - if r.status_code != 200: + + if r.status_code == 200: + # We should be logged in. Try to grab the api version out of the + # response. + try: + apidict = self._get_json(r) + version = apidict['apiVersion'] + splitver = version.split('.') + if splitver[0] == '2': + if splitver[1] == '0': + self.consisgroups = False + self.legacypayloadfilters = True + + elif splitver[1] == '1': + self.legacypayloadfilters = True + return + + except Exception: + # Good return but not the login response we were expecting. + # Log it and error out. + LOG.error(_LE('Unrecognized Login Response: %s'), r) + else: + # Call error. LOG.error(_LE('Login error: %(code)d %(reason)s'), {'code': r.status_code, 'reason': r.reason}) - raise exception.VolumeBackendAPIException( - _('Failed to connect to Enterprise Manager')) + + # Bad request. + # TODO(Swanson): Should add this to all returns. + if r.status_code == 400: + LOG.debug('Bad Request. Return text: %s', r.text) + + # If we fell to this point then raise an exception. + raise exception.VolumeBackendAPIException( + _('Failed to connect to Enterprise Manager')) def close_connection(self): '''Logout of Dell Enterprise Manager.''' @@ -427,7 +490,7 @@ class StorageCenterApi(object): :param foldername: Full path to the folder we are looking for. :returns: Dell folder object. ''' - pf = PayloadFilter() + pf = self._get_payload_filter() pf.append('scSerialNumber', self.ssn) basename = os.path.basename(foldername) pf.append('Name', basename) @@ -476,7 +539,7 @@ class StorageCenterApi(object): Don't wig out if this fails. :param scvolume: Dell Volume object. ''' - pf = PayloadFilter() + pf = self._get_payload_filter() pf.append('scSerialNumber', scvolume.get('scSerialNumber'), 'Equals') r = self.client.post('StorageCenter/ScServer/GetList', pf.payload) if r.status_code == 200: @@ -521,7 +584,7 @@ class StorageCenterApi(object): # and look through for the one we want. Never many profiles, so # this doesn't cause as much overhead as it might seem. storage_profile = storage_profile.replace(' ', '').lower() - pf = PayloadFilter() + pf = self._get_payload_filter() pf.append('scSerialNumber', self.ssn, 'Equals') r = self.client.post( 'StorageCenter/ScStorageProfile/GetList', pf.payload) @@ -617,7 +680,7 @@ class StorageCenterApi(object): result = None # We need a name or a device ID to find a volume. if name or deviceid: - pf = PayloadFilter() + pf = self._get_payload_filter() pf.append('scSerialNumber', self.ssn) if name is not None: pf.append('Name', name) @@ -773,7 +836,7 @@ class StorageCenterApi(object): :param osname: The name of the OS to look for. :returns: InstanceId of the ScServerOperatingSystem object. ''' - pf = PayloadFilter() + pf = self._get_payload_filter() pf.append('scSerialNumber', self.ssn) r = self.client.post('StorageCenter/ScServerOperatingSystem/GetList', pf.payload) @@ -888,7 +951,7 @@ class StorageCenterApi(object): # 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 = PayloadFilter() + pf = self._get_payload_filter() pf.append('scSerialNumber', self.ssn) pf.append('instanceId', self._get_id(hba['server'])) r = self.client.post('StorageCenter/ScServer/GetList', @@ -916,7 +979,7 @@ class StorageCenterApi(object): ''' scserverhba = None # We search for our server by first finding our HBA - pf = PayloadFilter() + pf = self._get_payload_filter() pf.append('scSerialNumber', self.ssn) pf.append('instanceName', instance_name) r = self.client.post('StorageCenter/ScServerHba/GetList', @@ -1006,6 +1069,28 @@ class StorageCenterApi(object): LOG.error(_LE('_find_mappings: volume is not active')) return mappings + def _find_mapping_profiles(self, scvolume): + '''Find the Dell volume object mapping profiles. + + :param scvolume: Dell volume object. + :returns: A list of Dell mapping profile objects. + ''' + mapping_profiles = [] + if scvolume.get('active', False): + r = self.client.get('StorageCenter/ScVolume/%s/MappingProfileList' + % self._get_id(scvolume)) + if r.status_code == 200: + mapping_profiles = self._get_json(r) + else: + LOG.debug('MappingProfileList error: %(code)d %(reason)s', + {'code': r.status_code, + 'reason': r.reason}) + LOG.error(_LE('Unable to find volume mapping profiles: %s'), + scvolume.get('name')) + else: + LOG.error(_LE('_find_mappings: volume is not active')) + return mapping_profiles + def _find_controller_port(self, cportid): '''Finds the SC controller port object for the specified cportid. @@ -1124,6 +1209,38 @@ class StorageCenterApi(object): iqn = controllerport.get('iscsiName') return iqn + def _is_virtualport_mode(self): + isvpmode = False + r = self.client.get('StorageCenter/ScConfiguration/%s' % self.ssn) + if r.status_code == 200: + scconfig = self._get_json(r) + if scconfig: + isvpmode = True if (scconfig['iscsiTransportMode'] == + 'VirtualPort') else False + return isvpmode + + def _find_controller_port_iscsi_config(self, cportid): + '''Finds the SC controller port object for the specified cportid. + + :param cportid: The instanceID of the Dell backend controller port. + :returns: The controller port object. + ''' + controllerport = None + r = self.client.get('StorageCenter/' + 'ScControllerPortIscsiConfiguration/%s' + % cportid) + if r.status_code == 200: + controllerport = self._first_result(r) + else: + LOG.debug('ScControllerPortIscsiConfiguration error: ' + '%(code)d %(reason)s', + {'code': r.status_code, + 'reason': r.reason}) + LOG.error(_LE('Unable to find controller ' + 'port iscsi configuration: %s'), + cportid) + return controllerport + def find_iscsi_properties(self, scvolume, ip=None, port=None): '''Finds target information for a given Dell scvolume object mapping. @@ -1137,58 +1254,108 @@ class StorageCenterApi(object): ''' LOG.debug('enter find_iscsi_properties') LOG.debug('scvolume: %s', scvolume) - active = -1 - up = -1 - access_mode = 'rw' + # Our mutable process object. + pdata = {'active': -1, + 'up': -1, + 'access_mode': 'rw', + 'ip': ip, + 'port': port} + # Our output lists. portals = [] luns = [] iqns = [] + + # Process just looks for the best port to return. + def process(lun, iqn, address, port, readonly, status, active): + '''Process this mapping information. + + :param lun: SCSI Lun. + :param iqn: iSCSI IQN address. + :param address: IP address. + :param port: IP Port number + :param readonly: Boolean indicating mapping is readonly. + :param status: String indicating mapping status. (Up is what we + are looking for.) + :param active: Boolean indicating whether this is on the active + controller or not. + :return: Nothing + ''' + portals.append(address + ':' + + six.text_type(port)) + 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: + pdata['access_mode'] = 'rw' if readonly is False else 'ro' + if active: + pdata['active'] = len(iqns) - 1 + if status == 'Up': + pdata['up'] = pdata['active'] + + # Start by getting our mappings. mappings = self._find_mappings(scvolume) + + # We should have mappings at the time of this call but do check. if len(mappings) > 0: # In multipath (per Liberty) we will return all paths. But # if multipath is not set (ip and port are None) then we need # to return a mapping from the controller on which the volume # is active. So find that controller. actvctrl = self._find_active_controller(scvolume) + # Two different methods are used to find our luns and portals + # depending on whether we are in virtual or legacy port mode. + isvpmode = self._is_virtualport_mode() + # Trundle through our mappings. for mapping in mappings: # The lun, ro mode and status are in the mapping. LOG.debug('mapping: %s', mapping) lun = mapping.get('lun') ro = mapping.get('readOnly', False) status = mapping.get('status') - # Dig a bit to get our domains,IQN and controller id. - domains = self._get_domains(mapping) + # Get our IQN from our mapping. iqn = self._get_iqn(mapping) - ctrlid = self._get_controller_id(mapping) - if domains and iqn is not None: - for dom in domains: - LOG.debug('domain: %s', dom) - ipaddress = dom.get('targetIpv4Address', - dom.get('wellKnownIpAddress')) - portnumber = dom.get('portNumber') - # We save our portal. - portals.append(ipaddress + ':' + - six.text_type(portnumber)) - iqns.append(iqn) - luns.append(lun) + # Check if our controller ID matches our active controller ID. + isactive = True if (self._get_controller_id(mapping) == + actvctrl) else False + # If we have an IQN and are in virtual port mode. + if isvpmode and iqn: + domains = self._get_domains(mapping) + if domains: + for dom in domains: + LOG.debug('domain: %s', dom) + ipaddress = dom.get('targetIpv4Address', + dom.get('wellKnownIpAddress')) + portnumber = dom.get('portNumber') + # We have all our information. Process this portal. + process(lun, iqn, ipaddress, portnumber, + ro, status, isactive) + # Else we are in legacy mode. + elif iqn: + # Need to get individual ports + cportid = self._get_id(mapping.get('controllerPort')) + # Legacy mode stuff is in the ISCSI configuration object. + cpconfig = self._find_controller_port_iscsi_config(cportid) + # This should really never fail. Things happen so if it + # does just keep moving. Return what we can. + if cpconfig: + ipaddress = cpconfig.get('ipAddress') + portnumber = cpconfig.get('portNumber') + # We have all our information. Process this portal. + process(lun, iqn, ipaddress, portnumber, + ro, status, isactive) - # 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 ((ip is None or ip == ipaddress) and - (port is None or port == portnumber)): - - # 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 up == -1: - access_mode = 'rw' if ro is False else 'ro' - if actvctrl == ctrlid: - active = len(iqns) - 1 - if status == 'Up': - up = active + # We've gone through all our mappings. # Make sure we found something to return. if len(luns) == 0: # Since we just mapped this and can't find that mapping the world @@ -1199,26 +1366,25 @@ class StorageCenterApi(object): # Make sure we point to the best portal we can. This means it is # on the active controller and, preferably, up. If it isn't return # what we have. - if up != -1: + if pdata['up'] != -1: # We found a connection that is already up. Return that. - active = up - elif active == -1: + pdata['active'] = pdata['up'] + elif pdata['active'] == -1: # This shouldn't be able to happen. Maybe a controller went # down in the middle of this so just return the first one and # hope the ports are up by the time the connection is attempted. LOG.debug('Volume is not yet active on any controller.') - active = 0 + pdata['active'] = 0 data = {'target_discovered': False, - 'target_iqn': iqns[active], + 'target_iqn': iqns[pdata['active']], 'target_iqns': iqns, - 'target_portal': portals[active], + 'target_portal': portals[pdata['active']], 'target_portals': portals, - 'target_lun': luns[active], + 'target_lun': luns[pdata['active']], 'target_luns': luns, - 'access_mode': access_mode + 'access_mode': pdata['access_mode'] } - LOG.debug('find_iscsi_properties return: %s', data) @@ -1232,12 +1398,18 @@ class StorageCenterApi(object): :param scvolume: Storage Center volume object. :param scserver: Storage Center server opbject. - :returns: scmapping or None + :returns: SC mapping profile or None ''' # Make sure we have what we think we have serverid = self._get_id(scserver) volumeid = self._get_id(scvolume) if serverid is not None and volumeid is not None: + # If we have a mapping to our server return it here. + mprofiles = self._find_mapping_profiles(scvolume) + for mprofile in mprofiles: + if self._get_id(mprofile.get('server')) == serverid: + return mprofile + # No? Then map it up. payload = {} payload['server'] = serverid advanced = {} @@ -1273,35 +1445,26 @@ class StorageCenterApi(object): serverid = self._get_id(scserver) volumeid = self._get_id(scvolume) if serverid is not None and volumeid is not None: - r = self.client.get('StorageCenter/ScVolume/%s/MappingProfileList' - % volumeid) - if r.status_code == 200: - profiles = self._get_json(r) - for profile in profiles: - prosrv = profile.get('server') - if prosrv is not None and self._get_id(prosrv) == serverid: - r = self.client.delete( - 'StorageCenter/ScMappingProfile/%s' - % self._get_id(profile)) - if (r.status_code != 200 or r.ok is False): - LOG.debug('ScMappingProfile error: ' - '%(code)d %(reason)s', - {'code': r.status_code, - 'reason': r.reason}) - LOG.error(_LE('Unable to unmap Volume %s'), - volumeid) - # 1 failed unmap is as good as 100. - # Fail it and leave - rtn = False - break - LOG.debug('Volume %(vol)s unmapped from %(srv)s', - {'vol': volumeid, - 'srv': serverid}) - else: - LOG.debug('MappingProfileList error: %(code)d %(reason)s', - {'code': r.status_code, - 'reason': r.reason}) - rtn = False + profiles = self._find_mapping_profiles(scvolume) + for profile in profiles: + prosrv = profile.get('server') + if prosrv is not None and self._get_id(prosrv) == serverid: + r = self.client.delete('StorageCenter/ScMappingProfile/%s' + % self._get_id(profile)) + if (r.status_code != 200 or r.ok is False): + LOG.debug('ScMappingProfile error: ' + '%(code)d %(reason)s', + {'code': r.status_code, + 'reason': r.reason}) + LOG.error(_LE('Unable to unmap Volume %s'), + volumeid) + # 1 failed unmap is as good as 100. + # Fail it and leave + rtn = False + break + LOG.debug('Volume %(vol)s unmapped from %(srv)s', + {'vol': volumeid, + 'srv': serverid}) return rtn def get_storage_usage(self): @@ -1569,7 +1732,8 @@ class StorageCenterApi(object): :return: Dell SC replay profile or None. :raises: VolumeBackendAPIException ''' - pf = PayloadFilter() + self.cg_except_on_no_support() + pf = self._get_payload_filter() pf.append('ScSerialNumber', self.ssn) pf.append('Name', name) r = self.client.post('StorageCenter/ScReplayProfile/GetList', @@ -1594,6 +1758,7 @@ class StorageCenterApi(object): the name on the Dell SC. :return: SC profile or None. ''' + self.cg_except_on_no_support() profile = self.find_replay_profile(name) if not profile: payload = {} @@ -1616,6 +1781,7 @@ class StorageCenterApi(object): :return: Nothing. :raises: VolumeBackendAPIException ''' + self.cg_except_on_no_support() r = self.client.delete('StorageCenter/ScReplayProfile/%s' % self._get_id(profile)) # 200 is a good return. Log and leave. @@ -1732,6 +1898,7 @@ class StorageCenterApi(object): removing the profile from this list of volumes.) :return: True/False on success/failure. ''' + self.cg_except_on_no_support() ret = True profileid = self._get_id(profile) if add_volumes: @@ -1751,6 +1918,7 @@ class StorageCenterApi(object): expiration. :returns: Dell SC replay object. ''' + self.cg_except_on_no_support() if profile: payload = {} payload['description'] = replayid @@ -1780,6 +1948,7 @@ class StorageCenterApi(object): GUID in the replay description. :returns: Dell replay object or None. ''' + self.cg_except_on_no_support() r = self.client.get('StorageCenter/ScReplayProfile/%s/ReplayList' % self._get_id(profile)) replays = self._get_json(r) @@ -1815,7 +1984,7 @@ class StorageCenterApi(object): replay description. :returns: Boolean for success or failure. ''' - + self.cg_except_on_no_support() LOG.debug('Expiring consistency group replay %s', replayid) replay = self.find_replay(profile, replayid) @@ -1831,6 +2000,12 @@ class StorageCenterApi(object): # We either couldn't find it or expired it. return True + def cg_except_on_no_support(self): + if not self.consisgroups: + msg = _('Dell API 2.1 or later required' + ' for Consistency Group support') + raise NotImplementedError(msg) + def _size_to_gb(self, spacestring): '''Splits a SC size string into GB and a remainder.