From 8b2bfe46b5313ed7270cff02bf1761f94330f888 Mon Sep 17 00:00:00 2001 From: Tom Swanson Date: Tue, 17 Feb 2015 14:00:04 -0600 Subject: [PATCH] Dell sc driver iscsi multipath enhancement Added support for the connector's multipath boolean. If this is set the initialize_connection function returns arrays of all the IQNs, LUNs and portals associated with the mapping of the volume to the server. If this is not set the standard return of a lone IQN, LUN and portal is implemented. Minor change to find_volume. If a volume name or instance isn't set then it will not be sent as a payload filter to the Dell Storage Center array. Additional Unit Tests to support the changes. Change-Id: I2f7404c76c71f5bf6d06496ab5aa4617ba80615b Implements: blueprint dell-sc-driver-iscsi-multipath-enhancement --- cinder/tests/test_dellfc.py | 32 + cinder/tests/test_dellsc.py | 134 ++++- cinder/tests/test_dellscapi.py | 553 ++++++++++++++++-- .../drivers/dell/dell_storagecenter_api.py | 188 +++--- .../drivers/dell/dell_storagecenter_iscsi.py | 57 +- 5 files changed, 802 insertions(+), 162 deletions(-) diff --git a/cinder/tests/test_dellfc.py b/cinder/tests/test_dellfc.py index 7a653eeb1..362e7e018 100644 --- a/cinder/tests/test_dellfc.py +++ b/cinder/tests/test_dellfc.py @@ -310,6 +310,38 @@ class DellSCSanFCDriverTestCase(test.TestCase): volume, connector) + @mock.patch.object(dell_storagecenter_api.StorageCenterApi, + 'find_sc', + return_value=12345) + @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, + 'map_volume', + return_value=None) + @mock.patch.object(dell_storagecenter_api.StorageCenterApi, + 'find_wwns', + return_value=(None, [], {})) + def test_initialize_connection_map_vol_fail(self, + mock_find_wwns, + mock_map_volume, + mock_find_volume, + mock_find_server, + mock_find_sc, + mock_close_connection, + mock_open_connection, + mock_init): + # Test case where map_volume returns None (no mappings) + volume = {'id': self.volume_name} + connector = self.connector + self.assertRaises(exception.VolumeBackendAPIException, + self.driver.initialize_connection, + volume, + connector) + @mock.patch.object(dell_storagecenter_api.StorageCenterApi, 'find_sc', return_value=64702) diff --git a/cinder/tests/test_dellsc.py b/cinder/tests/test_dellsc.py index 82e0462ec..e7d1ec89f 100644 --- a/cinder/tests/test_dellsc.py +++ b/cinder/tests/test_dellsc.py @@ -176,6 +176,19 @@ class DellSCSanISCSIDriverTestCase(test.TestCase): IQN = 'iqn.2002-03.com.compellent:5000D31000000001' + ISCSI_PROPERTIES = {'access_mode': 'rw', + 'target_discovered': False, + 'target_iqns': + [u'iqn.2002-03.com.compellent:5000d31000fcbe43'], + 'target_luns': [1], + 'target_portals': [u'192.168.0.21:3260']} + + ISCSI_PROPERTIES_EMPTY = {'access_mode': 'rw', + 'target_discovered': False, + 'target_iqns': [], + 'target_luns': [], + 'target_portals': []} + def setUp(self): super(DellSCSanISCSIDriverTestCase, self).setUp() @@ -216,6 +229,11 @@ class DellSCSanISCSIDriverTestCase(test.TestCase): 'ip': '10.0.0.2', 'initiator': 'iqn.1993-08.org.debian:01:2227dab76162', 'host': 'fakehost'} + self.connector_multipath = { + 'ip': '10.0.0.2', + 'initiator': 'iqn.1993-08.org.debian:01:2227dab76162', + 'host': 'fakehost', + 'multipath': True} self.access_record_output = [ "ID Initiator Ipaddress AuthMethod UserName Apply-To", "--- --------------- ------------- ---------- ---------- --------", @@ -323,10 +341,10 @@ class DellSCSanISCSIDriverTestCase(test.TestCase): 'map_volume', return_value=MAPPINGS[0]) @mock.patch.object(dell_storagecenter_api.StorageCenterApi, - 'find_iqn', - return_value=IQN) + 'find_iscsi_properties', + return_value=ISCSI_PROPERTIES) def test_initialize_connection(self, - mock_find_iqn, + mock_find_iscsi_props, mock_map_volume, mock_find_volume, mock_create_server, @@ -342,6 +360,62 @@ class DellSCSanISCSIDriverTestCase(test.TestCase): # verify find_volume has been called and that is has been called twice mock_find_volume.assert_any_call(12345, self.volume_name) assert mock_find_volume.call_count == 2 + expected = {'data': + {'access_mode': 'rw', + 'target_discovered': False, + 'target_iqn': + u'iqn.2002-03.com.compellent:5000d31000fcbe43', + 'target_lun': 1, + 'target_portal': u'192.168.0.21:3260'}, + 'driver_volume_type': 'iscsi'} + self.assertEqual(expected, data, 'Unexpected return value') + + @mock.patch.object(dell_storagecenter_api.StorageCenterApi, + 'find_sc', + return_value=12345) + @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, + 'map_volume', + return_value=MAPPINGS[0]) + @mock.patch.object(dell_storagecenter_api.StorageCenterApi, + 'find_iscsi_properties', + return_value=ISCSI_PROPERTIES) + def test_initialize_connection_multi_path(self, + mock_find_iscsi_props, + mock_map_volume, + mock_find_volume, + mock_create_server, + mock_find_server, + mock_find_sc, + mock_close_connection, + mock_open_connection, + mock_init): + # Test case where connection is multipath + volume = {'id': self.volume_name} + connector = self.connector_multipath + + data = self.driver.initialize_connection(volume, connector) + self.assertEqual(data['driver_volume_type'], 'iscsi') + # verify find_volume has been called and that is has been called twice + mock_find_volume.assert_any_call(12345, self.volume_name) + assert mock_find_volume.call_count == 2 + expected = {'data': + {'access_mode': 'rw', + 'target_discovered': False, + 'target_iqns': + [u'iqn.2002-03.com.compellent:5000d31000fcbe43'], + 'target_luns': [1], + 'target_portals': [u'192.168.0.21:3260']}, + 'driver_volume_type': 'iscsi'} + self.assertEqual(expected, data, 'Unexpected return value') @mock.patch.object(dell_storagecenter_api.StorageCenterApi, 'find_sc', @@ -356,10 +430,10 @@ class DellSCSanISCSIDriverTestCase(test.TestCase): 'map_volume', return_value=MAPPINGS) @mock.patch.object(dell_storagecenter_api.StorageCenterApi, - 'find_iqn', - return_value=None) + 'find_iscsi_properties', + return_value=ISCSI_PROPERTIES_EMPTY) def test_initialize_connection_no_iqn(self, - mock_find_iqn, + mock_find_iscsi_properties, mock_map_volume, mock_find_volume, mock_find_server, @@ -390,10 +464,10 @@ class DellSCSanISCSIDriverTestCase(test.TestCase): 'map_volume', return_value=MAPPINGS) @mock.patch.object(dell_storagecenter_api.StorageCenterApi, - 'find_iqn', - return_value=None) + 'find_iscsi_properties', + return_value=ISCSI_PROPERTIES_EMPTY) def test_initialize_connection_no_server(self, - mock_find_iqn, + mock_find_iscsi_properties, mock_map_volume, mock_find_volume, mock_create_server, @@ -422,10 +496,10 @@ class DellSCSanISCSIDriverTestCase(test.TestCase): 'map_volume', return_value=MAPPINGS) @mock.patch.object(dell_storagecenter_api.StorageCenterApi, - 'find_iqn', - return_value=None) + 'find_iscsi_properties', + return_value=ISCSI_PROPERTIES_EMPTY) def test_initialize_connection_vol_not_found(self, - mock_find_iqn, + mock_find_iscsi_properties, mock_map_volume, mock_find_volume, mock_find_server, @@ -440,6 +514,42 @@ class DellSCSanISCSIDriverTestCase(test.TestCase): volume, connector) + @mock.patch.object(dell_storagecenter_api.StorageCenterApi, + 'find_sc', + return_value=12345) + @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, + 'map_volume', + return_value=None) + @mock.patch.object(dell_storagecenter_api.StorageCenterApi, + 'find_iscsi_properties', + return_value=ISCSI_PROPERTIES) + def test_initialize_connection_map_vol_fail(self, + mock_find_iscsi_props, + mock_map_volume, + mock_find_volume, + mock_create_server, + mock_find_server, + mock_find_sc, + mock_close_connection, + mock_open_connection, + mock_init): + # Test case where map_volume returns None (no mappings) + volume = {'id': self.volume_name} + connector = self.connector + self.assertRaises(exception.VolumeBackendAPIException, + self.driver.initialize_connection, + volume, + connector) + @mock.patch.object(dell_storagecenter_api.StorageCenterApi, 'find_sc', return_value=12345) diff --git a/cinder/tests/test_dellscapi.py b/cinder/tests/test_dellscapi.py index 2c7ec72b9..ae9dd851b 100644 --- a/cinder/tests/test_dellscapi.py +++ b/cinder/tests/test_dellscapi.py @@ -180,6 +180,7 @@ class DellSCSanAPITestCase(test.TestCase): u'objectType': u'ScServerOperatingSystem'} } + # ScServer where deletedAllowed=False (not allowed to be deleted) SCSERVER_NO_DEL = {u'scName': u'Storage Center 64702', u'volumeCount': 0, u'removeHbasAllowed': True, @@ -278,6 +279,41 @@ class DellSCSanAPITestCase(test.TestCase): u'instanceName': u'Other Singlepath', u'objectType': u'ScServerOperatingSystem'}}] + # ScServers list where status = Down + SCSERVERS_DOWN = \ + [{u'scName': u'Storage Center 64702', + u'volumeCount': 5, + u'removeHbasAllowed': True, + u'legacyFluidFs': False, + u'serverFolderIndex': 0, + u'alertOnConnectivity': True, + u'objectType': u'ScPhysicalServer', + u'instanceName': u'openstack4', + u'instanceId': u'64702.1', + u'serverFolderPath': u'', + u'portType': [u'Iscsi'], + u'type': u'Physical', + u'statusMessage': u'', + u'status': u'Down', + u'scSerialNumber': 64702, + u'serverFolder': {u'instanceId': u'64702.0', + u'instanceName': u'Servers', + u'objectType': u'ScServerFolder'}, + u'parentIndex': 0, + u'connectivity': u'Up', + u'hostCacheIndex': 0, + u'deleteAllowed': True, + u'pathCount': 0, + u'name': u'openstack4', + u'hbaPresent': True, + u'hbaCount': 1, + u'notes': u'', + u'mapped': True, + u'operatingSystem': + {u'instanceId': u'64702.3', + 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, @@ -363,6 +399,126 @@ class DellSCSanAPITestCase(test.TestCase): u'transport': u'Iscsi', u'objectType': u'ScMapping'}] + # Multiple mappings to test find_iscsi_properties with multiple portals + MAPPINGS_MULTI_PORTAL = \ + [{u'profile': {u'instanceId': u'64702.104', + u'instanceName': u'92-30', + u'objectType': u'ScMappingProfile'}, + u'status': u'Down', + u'statusMessage': u'', + u'instanceId': u'64702.969.64702', + u'scName': u'Storage Center 64702', + u'scSerialNumber': 64702, + u'controller': {u'instanceId': u'64702.64702', + u'instanceName': u'SN 64702', + u'objectType': u'ScController'}, + u'server': {u'instanceId': u'64702.30', + u'instanceName': + u'Server_iqn.1993-08.org.debian:01:3776df826e4f', + u'objectType': u'ScPhysicalServer'}, + u'volume': {u'instanceId': u'64702.92', + u'instanceName': + u'volume-74a21934-60ad-4cf2-b89b-1f0dda309ddf', + u'objectType': u'ScVolume'}, + u'readOnly': False, + u'lun': 1, + u'lunUsed': [1], + u'serverHba': {u'instanceId': u'64702.3454975614', + u'instanceName': + u'iqn.1993-08.org.debian:01:3776df826e4f', + u'objectType': u'ScServerHba'}, + u'path': {u'instanceId': u'64702.64702.64702.31.8', + u'instanceName': + u'iqn.1993-08.org.debian:' + '01:3776df826e4f-5000D31000FCBE43', + u'objectType': u'ScServerHbaPath'}, + u'controllerPort': {u'instanceId': + u'64702.5764839588723736131.91', + u'instanceName': u'5000D31000FCBE43', + u'objectType': u'ScControllerPort'}, + u'instanceName': u'64702-969', + u'transport': u'Iscsi', + u'objectType': u'ScMapping'}, + {u'profile': {u'instanceId': u'64702.104', + u'instanceName': u'92-30', + u'objectType': u'ScMappingProfile'}, + u'status': u'Down', + u'statusMessage': u'', + u'instanceId': u'64702.969.64702', + u'scName': u'Storage Center 64702', + u'scSerialNumber': 64702, + u'controller': {u'instanceId': u'64702.64702', + u'instanceName': u'SN 64702', + u'objectType': u'ScController'}, + u'server': {u'instanceId': u'64702.30', + u'instanceName': + u'Server_iqn.1993-08.org.debian:01:3776df826e4f', + u'objectType': u'ScPhysicalServer'}, + u'volume': {u'instanceId': u'64702.92', + u'instanceName': + u'volume-74a21934-60ad-4cf2-b89b-1f0dda309ddf', + u'objectType': u'ScVolume'}, + u'readOnly': False, + u'lun': 1, + u'lunUsed': [1], + u'serverHba': {u'instanceId': u'64702.3454975614', + u'instanceName': + u'iqn.1993-08.org.debian:01:3776df826e4f', + u'objectType': u'ScServerHba'}, + u'path': {u'instanceId': u'64702.64702.64702.31.8', + u'instanceName': + u'iqn.1993-08.org.debian:' + '01:3776df826e4f-5000D31000FCBE43', + u'objectType': u'ScServerHbaPath'}, + u'controllerPort': {u'instanceId': + u'64702.5764839588723736131.91', + u'instanceName': u'5000D31000FCBE43', + u'objectType': u'ScControllerPort'}, + u'instanceName': u'64702-969', + u'transport': u'Iscsi', + u'objectType': u'ScMapping'}] + + MAPPINGS_READ_ONLY = \ + [{u'profile': {u'instanceId': u'64702.104', + u'instanceName': u'92-30', + u'objectType': u'ScMappingProfile'}, + u'status': u'Down', + u'statusMessage': u'', + u'instanceId': u'64702.969.64702', + u'scName': u'Storage Center 64702', + u'scSerialNumber': 64702, + u'controller': {u'instanceId': u'64702.64702', + u'instanceName': u'SN 64702', + u'objectType': u'ScController'}, + u'server': {u'instanceId': u'64702.30', + u'instanceName': + u'Server_iqn.1993-08.org.debian:01:3776df826e4f', + u'objectType': u'ScPhysicalServer'}, + u'volume': {u'instanceId': u'64702.92', + u'instanceName': + u'volume-74a21934-60ad-4cf2-b89b-1f0dda309ddf', + u'objectType': u'ScVolume'}, + u'readOnly': True, + u'lun': 1, + u'lunUsed': [1], + u'serverHba': {u'instanceId': u'64702.3454975614', + u'instanceName': + u'iqn.1993-08.org.debian:01:3776df826e4f', + u'objectType': u'ScServerHba'}, + u'path': {u'instanceId': u'64702.64702.64702.31.8', + u'instanceName': + u'iqn.1993-08.org.debian:' + '01:3776df826e4f-5000D31000FCBE43', + u'objectType': u'ScServerHbaPath'}, + u'controllerPort': {u'instanceId': + u'64702.5764839588723736131.91', + u'instanceName': + u'5000D31000FCBE43', + u'objectType': u'ScControllerPort'}, + u'instanceName': u'64702-969', + u'transport': u'Iscsi', + u'objectType': u'ScMapping'}] + FC_MAPPINGS = [{u'profile': {u'instanceId': u'64702.2941', u'instanceName': u'6025-47', u'objectType': u'ScMappingProfile'}, @@ -725,6 +881,71 @@ class DellSCSanAPITestCase(test.TestCase): u'bidirectionalChapSecret': u'', u'keepAliveTimeout': u'SECONDS_30'}] + # For testing find_iscsi_properties where multiple portals are found + ISCSI_FLT_DOMAINS_MULTI_PORTALS = \ + [{u'headerDigestEnabled': False, + u'classOfServicePriority': 0, + u'wellKnownIpAddress': u'192.168.0.21', + u'scSerialNumber': 64702, + u'iscsiName': + u'iqn.2002-03.com.compellent:5000d31000fcbe42', + u'portNumber': 3260, + u'subnetMask': u'255.255.255.0', + u'gateway': u'192.168.0.1', + u'objectType': u'ScIscsiFaultDomain', + u'chapEnabled': False, + u'instanceId': u'64702.6.5.3', + u'childStatus': u'Up', + u'defaultTimeToRetain': u'SECONDS_20', + u'dataDigestEnabled': False, + u'instanceName': u'iSCSI 10G 2', + u'statusMessage': u'', + u'status': u'Up', + u'transportType': u'Iscsi', + u'vlanId': 0, + u'windowSize': u'131072.0 Bytes', + u'defaultTimeToWait': u'SECONDS_2', + u'scsiCommandTimeout': u'MINUTES_1', + u'deleteAllowed': False, + u'name': u'iSCSI 10G 2', + u'immediateDataWriteEnabled': False, + u'scName': u'Storage Center 64702', + u'notes': u'', + u'mtu': u'MTU_1500', + u'bidirectionalChapSecret': u'', + u'keepAliveTimeout': u'SECONDS_30'}, + {u'headerDigestEnabled': False, + u'classOfServicePriority': 0, + u'wellKnownIpAddress': u'192.168.0.25', + u'scSerialNumber': 64702, + u'iscsiName': + u'iqn.2002-03.com.compellent:5000d31000fcbe42', + u'portNumber': 3260, + u'subnetMask': u'255.255.255.0', + u'gateway': u'192.168.0.1', + u'objectType': u'ScIscsiFaultDomain', + u'chapEnabled': False, + u'instanceId': u'64702.6.5.3', + u'childStatus': u'Up', + u'defaultTimeToRetain': u'SECONDS_20', + u'dataDigestEnabled': False, + u'instanceName': u'iSCSI 10G 2', + u'statusMessage': u'', + u'status': u'Up', + u'transportType': u'Iscsi', + u'vlanId': 0, + u'windowSize': u'131072.0 Bytes', + u'defaultTimeToWait': u'SECONDS_2', + u'scsiCommandTimeout': u'MINUTES_1', + u'deleteAllowed': False, + u'name': u'iSCSI 10G 2', + u'immediateDataWriteEnabled': False, + u'scName': u'Storage Center 64702', + u'notes': u'', + u'mtu': u'MTU_1500', + u'bidirectionalChapSecret': u'', + u'keepAliveTimeout': u'SECONDS_30'}] + ISCSI_FLT_DOMAIN = {u'headerDigestEnabled': False, u'classOfServicePriority': 0, u'wellKnownIpAddress': u'192.168.0.21', @@ -895,7 +1116,7 @@ class DellSCSanAPITestCase(test.TestCase): def setUp(self): super(DellSCSanAPITestCase, self).setUp() - # configuration is a mock. A mock is pretty much a blank + # Configuration is a mock. A mock is pretty much a blank # slate. I believe mock's done in setup are not happy time # mocks. So we just do a few things like driver config here. self.configuration = mock.Mock() @@ -1118,7 +1339,7 @@ class DellSCSanAPITestCase(test.TestCase): mock_close_connection, mock_open_connection, mock_init): - # test case for folder path with multiple folders + # Test case for folder path with multiple folders res = self.scapi._find_folder( 'StorageCenter/ScVolumeFolder', 12345, u'testParentFolder/opnstktst') @@ -1209,6 +1430,31 @@ class DellSCSanAPITestCase(test.TestCase): self.scapi._init_volume(self.VOLUME) mock_post.assert_called() + @mock.patch.object(dell_storagecenter_api.StorageCenterApi, + 'unmap_volume', + return_value=True) + @mock.patch.object(dell_storagecenter_api.StorageCenterApi, + 'map_volume', + return_value=MAPPINGS) + @mock.patch.object(dell_storagecenter_api.StorageCenterApi, + '_get_json', + return_value=SCSERVERS_DOWN) + @mock.patch.object(dell_storagecenter_api.HttpClient, + 'post', + return_value=RESPONSE_200) + def test_init_volume_servers_down(self, + mock_post, + mock_get_json, + mock_map_volume, + mock_unmap_volume, + mock_close_connection, + mock_open_connection, + mock_init): + # Test case where ScServer Status = Down + self.scapi._init_volume(self.VOLUME) + mock_map_volume.assert_called() + mock_unmap_volume.assert_called() + @mock.patch.object(dell_storagecenter_api.StorageCenterApi, '_get_json', return_value=VOLUME) @@ -1338,16 +1584,38 @@ class DellSCSanAPITestCase(test.TestCase): @mock.patch.object(dell_storagecenter_api.HttpClient, 'post', return_value=RESPONSE_200) - def test_find_volume(self, - mock_post, - mock_first_result, - mock_close_connection, - mock_open_connection, - mock_init): - self.scapi.find_volume(12345, - self.volume_name) + def test_find_volume_by_name(self, + mock_post, + mock_first_result, + mock_close_connection, + mock_open_connection, + mock_init): + # Test case to find volume by name + res = self.scapi.find_volume(12345, + self.volume_name) mock_post.assert_called() mock_first_result.assert_called() + self.assertEqual(self.VOLUME, res, 'Unexpected volume') + + @mock.patch.object(dell_storagecenter_api.StorageCenterApi, + '_first_result', + return_value=VOLUME) + @mock.patch.object(dell_storagecenter_api.HttpClient, + 'post', + return_value=RESPONSE_200) + # Test case to find volume by InstancedId + def test_find_volume_by_instanceid(self, + mock_post, + mock_first_result, + mock_close_connection, + mock_open_connection, + mock_init): + res = self.scapi.find_volume(12345, + None, + '64702.3494') + mock_post.assert_called() + mock_first_result.assert_called() + self.assertEqual(self.VOLUME, res, 'Unexpected volume') def test_find_volume_no_name_or_instance(self, mock_close_connection, @@ -1871,6 +2139,36 @@ class DellSCSanAPITestCase(test.TestCase): self.IQN) self.assertIsNone(res, 'Expected None') + @mock.patch.object(dell_storagecenter_api.StorageCenterApi, + '_get_json', + return_value=ISCSI_FLT_DOMAINS) + @mock.patch.object(dell_storagecenter_api.HttpClient, + 'get', + return_value=RESPONSE_200) + def test_find_domains(self, + mock_get, + mock_get_json, + mock_close_connection, + mock_open_connection, + mock_init): + res = self.scapi._find_domains(u'64702.5764839588723736074.69') + mock_get .assert_called() + mock_get_json.assert_called() + self.assertEqual( + self.ISCSI_FLT_DOMAINS, res, 'Unexpected ScIscsiFaultDomain') + + @mock.patch.object(dell_storagecenter_api.HttpClient, + 'get', + return_value=RESPONSE_204) + def test_find_domains_error(self, + mock_get, + mock_close_connection, + mock_open_connection, + mock_init): + # Test case where get of ScControllerPort FaultDomainList fails + res = self.scapi._find_domains(u'64702.5764839588723736074.69') + self.assertIsNone(res, 'Expected None') + @mock.patch.object(dell_storagecenter_api.StorageCenterApi, '_get_json', return_value=ISCSI_FLT_DOMAINS) @@ -1915,7 +2213,7 @@ class DellSCSanAPITestCase(test.TestCase): mock_open_connection, mock_init): # Test case where domainip does not equal any WellKnownIpAddress - # of thefault domains + # of the fault domains res = self.scapi._find_domain(u'64702.5764839588723736074.69', u'192.168.0.22') self.assertIsNone(res, 'Expected None') @@ -2082,7 +2380,7 @@ class DellSCSanAPITestCase(test.TestCase): mock_close_connection, mock_open_connection, mock_init): - # Test case of where get of ScVolume MappingList fails + # Test case where get of ScVolume MappingList fails res = self.scapi._find_controller_port(self.VOLUME) mock_get.assert_called() self.assertIsNone(res, 'None expected') @@ -2130,7 +2428,7 @@ class DellSCSanAPITestCase(test.TestCase): mock_close_connection, mock_open_connection, mock_init): - # Test case where there are no ScMapping + # Test case where there are no ScMapping(s) lun, wwns, itmap = self.scapi.find_wwns(self.VOLUME, self.SCSERVER) mock_find_fc_initiators.assert_called() @@ -2169,64 +2467,221 @@ class DellSCSanAPITestCase(test.TestCase): '_find_controller_port', return_value=ISCSI_CTRLR_PORT) @mock.patch.object(dell_storagecenter_api.StorageCenterApi, - '_find_domain', - return_value=ISCSI_FLT_DOMAIN) + '_find_domains', + return_value=ISCSI_FLT_DOMAINS) @mock.patch.object(dell_storagecenter_api.StorageCenterApi, '_find_mappings', return_value=MAPPINGS) - def test_find_iqn_mappings(self, - mock_find_mappings, - mock_find_domain, - mock_find_ctrl_port, - mock_close_connection, - mock_open_connection, - mock_init): - res = self.scapi.find_iqn(self.VOLUME, - self.SCSERVER) + def test_find_iscsi_properties_mappings(self, + mock_find_mappings, + mock_find_domain, + mock_find_ctrl_port, + mock_close_connection, + mock_open_connection, + mock_init): + res = self.scapi.find_iscsi_properties(self.VOLUME) mock_find_mappings.assert_called() mock_find_domain.assert_called() mock_find_ctrl_port.assert_called() + expected = {'access_mode': 'rw', + 'target_discovered': False, + 'target_iqns': + [u'iqn.2002-03.com.compellent:5000d31000fcbe43'], + 'target_luns': [1], + 'target_portals': [u'192.168.0.21:3260']} + self.assertEqual(expected, res, 'Wrong Target Info') - expected_iqn = u'iqn.2002-03.com.compellent:5000d31000fcbe43' - self.assertEqual(expected_iqn, res, 'Wrong IQN') + @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) + def test_find_iscsi_properties_by_address(self, + mock_find_mappings, + mock_find_domain, + mock_find_ctrl_port, + 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) + mock_find_mappings.assert_called() + mock_find_domain.assert_called() + mock_find_ctrl_port.assert_called() + expected = {'access_mode': 'rw', + 'target_discovered': False, + 'target_iqns': + [u'iqn.2002-03.com.compellent:5000d31000fcbe43'], + 'target_luns': [1], + 'target_portals': [u'192.168.0.21:3260']} + self.assertEqual(expected, res, 'Wrong Target Info') + + @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) + def test_find_iscsi_properties_by_address_not_found(self, + mock_find_mappings, + mock_find_domain, + mock_find_ctrl_port, + 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) + mock_find_mappings.assert_called() + mock_find_domain.assert_called() + mock_find_ctrl_port.assert_called() + expected = {'access_mode': 'rw', + 'target_discovered': False, + 'target_iqns': [], + 'target_luns': [], + 'target_portals': []} + self.assertEqual(expected, res, 'Wrong Target Info') @mock.patch.object(dell_storagecenter_api.StorageCenterApi, '_find_mappings', return_value=[]) - def test_find_iqn_no_mapping(self, - mock_find_mappings, - mock_close_connection, - mock_open_connection, - mock_init): - # Test case where there are no ScMapping - res = self.scapi.find_iqn(self.VOLUME, - self.SCSERVER) + def test_find_iscsi_properties_no_mapping(self, + mock_find_mappings, + mock_close_connection, + mock_open_connection, + mock_init): + # Test case where there are no ScMapping(s) + res = self.scapi.find_iscsi_properties(self.VOLUME) mock_find_mappings.assert_called() - self.assertIsNone(res, 'No IQN expected') + expected = {'access_mode': 'rw', + 'target_discovered': False, + 'target_iqns': [], + 'target_luns': [], + 'target_portals': []} + self.assertEqual(expected, res, 'Expected empty Target Info') @mock.patch.object(dell_storagecenter_api.StorageCenterApi, '_find_controller_port', return_value=ISCSI_CTRLR_PORT) @mock.patch.object(dell_storagecenter_api.StorageCenterApi, - '_find_domain', + '_find_domains', return_value=None) @mock.patch.object(dell_storagecenter_api.StorageCenterApi, '_find_mappings', return_value=MAPPINGS) - def test_find_iqn_no_domain(self, - mock_find_mappings, - mock_find_domain, - mock_find_ctrl_port, - mock_close_connection, - mock_open_connection, - mock_init): - # Test case where there are no ScFaultDomain - res = self.scapi.find_iqn(self.VOLUME, - self.SCSERVER) + def test_find_iscsi_properties_no_domain(self, + mock_find_mappings, + mock_find_domain, + mock_find_ctrl_port, + mock_close_connection, + mock_open_connection, + mock_init): + # Test case where there are no ScFaultDomain(s) + res = self.scapi.find_iscsi_properties(self.VOLUME) + mock_find_mappings.assert_called() + mock_find_domain.assert_called() + mock_find_ctrl_port.assert_called() + expected = {'access_mode': 'rw', + 'target_discovered': False, + 'target_iqns': [], + 'target_luns': [], + 'target_portals': []} + self.assertEqual(expected, res, 'Expected empty Target Info') + + @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) + def test_find_iscsi_properties_no_ctrl_port(self, + mock_find_mappings, + mock_find_domain, + mock_find_ctrl_port, + mock_close_connection, + mock_open_connection, + mock_init): + # Test case where there are no ScFaultDomain(s) + res = self.scapi.find_iscsi_properties(self.VOLUME) + mock_find_mappings.assert_called() + mock_find_domain.assert_called() + mock_find_ctrl_port.assert_called() + expected = {'access_mode': 'rw', + 'target_discovered': False, + 'target_iqns': [], + 'target_luns': [], + 'target_portals': []} + self.assertEqual(expected, res, 'Expected empty Target Info') + + @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_READ_ONLY) + def test_find_iscsi_properties_ro(self, + mock_find_mappings, + mock_find_domain, + mock_find_ctrl_port, + mock_close_connection, + mock_open_connection, + mock_init): + # Test case where Read Only mappings are found + res = self.scapi.find_iscsi_properties(self.VOLUME) + mock_find_mappings.assert_called() + mock_find_domain.assert_called() + mock_find_ctrl_port.assert_called() + expected = {'access_mode': 'ro', + 'target_discovered': False, + 'target_iqns': + [u'iqn.2002-03.com.compellent:5000d31000fcbe43'], + 'target_luns': [1], + 'target_portals': [u'192.168.0.21:3260']} + self.assertEqual(expected, res, 'Wrong Target Info') + + @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_MULTI_PORTALS) + @mock.patch.object(dell_storagecenter_api.StorageCenterApi, + '_find_mappings', + return_value=MAPPINGS_MULTI_PORTAL) + def test_find_iscsi_properties_multi_portals(self, + mock_find_mappings, + mock_find_domain, + mock_find_ctrl_port, + mock_close_connection, + mock_open_connection, + mock_init): + # Test case where there are multiple portals + res = self.scapi.find_iscsi_properties(self.VOLUME) mock_find_mappings.assert_called() mock_find_domain.assert_called() mock_find_ctrl_port.assert_called() - self.assertIsNone(res, 'No IQN expected') + expected = {'access_mode': 'rw', + 'target_discovered': False, + 'target_iqns': + [u'iqn.2002-03.com.compellent:5000d31000fcbe43'], + 'target_luns': [1], + 'target_portals': + [u'192.168.0.21:3260', u'192.168.0.25:3260']} + self.assertEqual(expected, res, 'Wrong Target Info') @mock.patch.object(dell_storagecenter_api.StorageCenterApi, '_first_result', @@ -2833,7 +3288,7 @@ class DellSCSanAPIConnectionTestCase(test.TestCase): def setUp(self): super(DellSCSanAPIConnectionTestCase, self).setUp() - # configuration is a mock. A mock is pretty much a blank + # Configuration is a mock. A mock is pretty much a blank # slate. I believe mock's done in setup are not happy time # mocks. So we just do a few things like driver config here. self.configuration = mock.Mock() @@ -2870,8 +3325,6 @@ class DellSCSanAPIConnectionTestCase(test.TestCase): return_value=RESPONSE_204) def test_open_connection_failure(self, mock_post): - # self.scapi.open_connection() - # mock_post.assert_called() self.assertRaises(exception.VolumeBackendAPIException, self.scapi.open_connection) diff --git a/cinder/volume/drivers/dell/dell_storagecenter_api.py b/cinder/volume/drivers/dell/dell_storagecenter_api.py index 6c4e918e3..069c21570 100644 --- a/cinder/volume/drivers/dell/dell_storagecenter_api.py +++ b/cinder/volume/drivers/dell/dell_storagecenter_api.py @@ -17,6 +17,7 @@ import json import os.path import requests +import six from cinder import exception from cinder.i18n import _, _LE, _LI, _LW @@ -65,8 +66,6 @@ class HttpClient(object): self.header = {} self.header['Content-Type'] = 'application/json; charset=utf-8' self.header['x-dell-api-version'] = '1.5' - # we don't verify. If the end user has a real SSL cert rather than - # a self signed cert they could turn this on self.verify = False def __enter__(self): @@ -170,7 +169,7 @@ class StorageCenterApi(object): rsp = None content = self._get_json(blob) if content is not None: - # we can get a list or a dict or nothing + # We can get a list or a dict or nothing if isinstance(content, list): for r in content: if attribute is None or r.get(attribute) == value: @@ -246,7 +245,7 @@ class StorageCenterApi(object): return self._get_id(result) - # volume functions + # Volume functions def _create_folder(self, url, ssn, parent, folder): '''This is generic to server and volume folders. @@ -276,12 +275,12 @@ class StorageCenterApi(object): path = self._path_to_array(foldername) folderpath = '' instanceId = '' - # technically the first folder is the root so that is already created. + # Technically the first folder is the root so that is already created. found = True f = None for folder in path: folderpath = folderpath + folder - # if the last was found see if this part of the path exists too + # If the last was found see if this part of the path exists too if found: listurl = url + '/GetList' f = self._find_folder(listurl, @@ -289,18 +288,18 @@ class StorageCenterApi(object): folderpath) if f is None: found = False - # we didn't find it so create it + # We didn't find it so create it if found is False: f = self._create_folder(url, ssn, instanceId, folder) - # if we haven't found a folder or created it then leave + # If we haven't found a folder or created it then leave if f is None: LOG.error(_LE('Unable to create folder path %s'), folderpath) break - # next part of the path will need this + # Next part of the path will need this instanceId = self._get_id(f) folderpath = folderpath + '/' return f @@ -316,7 +315,7 @@ class StorageCenterApi(object): pf.append('scSerialNumber', ssn) basename = os.path.basename(foldername) pf.append('Name', basename) - # if we have any kind of path we add '/' to match the storage + # If we have any kind of path we add '/' to match the storage # center's convention and throw it into the filters. folderpath = os.path.dirname(foldername) if folderpath != '': @@ -373,7 +372,7 @@ class StorageCenterApi(object): the volume will be created in the root. ''' scvolume = None - # find our folder + # Find our folder LOG.debug('Create Volume %(name)s %(ssn)s %(folder)s', {'name': name, 'ssn': ssn, @@ -381,12 +380,12 @@ class StorageCenterApi(object): folder = self._find_volume_folder(ssn, volfolder) - # doesn't exist? make it + # Doesn't exist? make it if folder is None: folder = self._create_volume_folder_path(ssn, volfolder) - # if we actually have a place to put our volume create it + # If we actually have a place to put our volume create it if folder is None: LOG.error(_LE('Unable to create folder %s'), volfolder) @@ -421,14 +420,16 @@ class StorageCenterApi(object): {'sn': ssn, 'name': name, 'id': instanceid}) - if name is None and instanceid is None: - return None pf = PayloadFilter() - # we need at least a name and or an instance id. If we have - # that we can find a volume. pf.append('scSerialNumber', ssn) - pf.append('Name', name) - pf.append('instanceId', instanceid) + # We need at least a name and or an instance id. If we have + # that we can find a volume. + if instanceid is not None: + pf.append('instanceId', instanceid) + elif name is not None: + pf.append('Name', name) + else: + return None r = self.client.post('StorageCenter/ScVolume/GetList', pf.payload) if r.status_code != 200: @@ -491,7 +492,7 @@ class StorageCenterApi(object): return False return True - # we do not know that we are red hat linux 6.x but that works + # We do not know that we are red hat linux 6.x but that works # best for red hat and ubuntu. So, there. def _find_serveros(self, ssn, osname='Red Hat Linux 6.x'): '''Returns the serveros instance id of the specified osname. @@ -506,7 +507,7 @@ class StorageCenterApi(object): for srvos in oslist: name = srvos.get('name', 'nope') if name.lower() == osname.lower(): - # found it return the id + # Found it return the id return self._get_id(srvos) LOG.warning(_LW('ScServerOperatingSystem GetList return: %(c)d %(r)s'), @@ -518,9 +519,9 @@ class StorageCenterApi(object): '''Same as create_server except it can take a list of hbas. hbas can be wwns or iqns. ''' - # add hbas + # Add hbas scserver = None - # our instance names + # Our instance names for wwn in wwns: if scserver is None: # Use the fist wwn to create the server. @@ -529,7 +530,7 @@ class StorageCenterApi(object): wwn, True) else: - # add the wwn to our server + # Add the wwn to our server self._add_hba(scserver, wwn, True) @@ -544,21 +545,21 @@ class StorageCenterApi(object): payload['Name'] = 'Server_' + wwnoriscsiname payload['StorageCenter'] = ssn payload['Notes'] = self.notes - # we pick Red Hat Linux 6.x because it supports multipath and + # 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(ssn, 'Red Hat Linux 6.x') if scserveros is not None: payload['OperatingSystem'] = scserveros - # find our folder or make it + # Find our folder or make it folder = self._find_server_folder(ssn, foldername) if folder is None: folder = self._create_server_folder_path(ssn, foldername) - # 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 + # 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. if folder is not None: payload['ServerFolder'] = self._get_id(folder) @@ -572,10 +573,10 @@ class StorageCenterApi(object): 'c': r.status_code, 'r': r.reason}) else: - # server was created + # Server was created scserver = self._first_result(r) - # add hba to our server + # Add hba to our server if scserver is not None: if not self._add_hba(scserver, wwnoriscsiname, @@ -584,7 +585,7 @@ class StorageCenterApi(object): # Can't have a server without an HBA self._delete_server(scserver) scserver = None - # success or failure is determined by the caller + # Success or failure is determined by the caller return scserver def find_server(self, ssn, instance_name): @@ -594,9 +595,9 @@ class StorageCenterApi(object): If found, the server the HBA is attached to, if any, is returned. ''' scserver = None - # we search for our server by first finding our HBA + # We search for our server by first finding our HBA hba = self._find_serverhba(ssn, instance_name) - # once created hbas stay in the system. So it isn't enough + # 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: @@ -623,7 +624,7 @@ class StorageCenterApi(object): If found, the sc server HBA is returned. ''' scserverhba = None - # we search for our server by first finding our HBA + # We search for our server by first finding our HBA pf = PayloadFilter() pf.append('scSerialNumber', ssn) pf.append('instanceName', instance_name) @@ -637,20 +638,12 @@ class StorageCenterApi(object): scserverhba = self._first_result(r) return scserverhba - def _find_domain(self, cportid, domainip): - '''Returns the fault domain which a given controller port can - be seen by the server - ''' + def _find_domains(self, cportid): r = self.client.get('StorageCenter/ScControllerPort/%s/FaultDomainList' % cportid) if r.status_code == 200: domains = self._get_json(r) - # wiffle through the domains looking for our - # configured ip - for domain in domains: - # if this is us we return the port - if domain.get('wellKnownIpAddress') == domainip: - return domain + return domains else: LOG.debug('FaultDomainList error: %(c)d %(r)s', {'c': r.status_code, @@ -658,6 +651,21 @@ class StorageCenterApi(object): LOG.error(_LE('Error getting FaultDomainList')) return None + def _find_domain(self, cportid, domainip): + '''Returns the fault domain which a given controller port can + be seen by the server + ''' + domains = self._find_domains(cportid) + if domains: + # Wiffle through the domains looking for our + # configured ip. + for domain in domains: + # If this is us we return the port. + if domain.get('targetIpv4Address', + domain.get('wellKnownIpAddress')) == domainip: + return domain + return None + def _find_fc_initiators(self, scserver): '''_find_fc_initiators @@ -670,8 +678,8 @@ class StorageCenterApi(object): hbas = self._get_json(r) for hba in hbas: wwn = hba.get('instanceName') - if hba.get('portType') == 'FibreChannel' and\ - wwn is not None: + if (hba.get('portType') == 'FibreChannel' and + wwn is not None): initiators.append(wwn) else: LOG.debug('HbaList error: %(c)d %(r)s', @@ -730,21 +738,21 @@ class StorageCenterApi(object): def find_wwns(self, scvolume, scserver): '''returns the lun and wwns of the mapped volume''' - # our returnables + # Our returnables lun = None # our lun. We return the first lun. wwns = [] # list of targets itmap = {} # dict of initiators and the associated targets - # make sure we know our server's initiators. Only return + # Make sure we know our server's initiators. Only return # mappings that contain HBA for this server. initiators = self._find_fc_initiators(scserver) - # get our volume mappings + # Get our volume mappings mappings = self._find_mappings(scvolume) if len(mappings) > 0: - # we check each of our mappings. We want to return + # We check each of our mappings. We want to return # the mapping we have been configured to use. for mapping in mappings: - # find the controller port for this mapping + # Find the controller port for this mapping cport = mapping.get('controllerPort') controllerport = self._find_controller_port( self._get_id(cport)) @@ -774,36 +782,54 @@ class StorageCenterApi(object): # pretend we succeeded. return lun, wwns, itmap - def find_iqn(self, scvolume, ip): - '''find_iqn - - returns the iqn of the port pointed to by the openstack compute - node. This is to make sure that the compute node looks for the - volume on the active controller. - ''' - iqn = None - # get our volume mappings + def find_iscsi_properties(self, scvolume, ip=None, port=None): + luns = [] + iqns = [] + portals = [] + access_mode = 'rw' mappings = self._find_mappings(scvolume) if len(mappings) > 0: - # we check each of our mappings. We want to return - # the mapping we have been configured to use. for mapping in mappings: # find the controller port for this mapping cport = mapping.get('controllerPort') cportid = self._get_id(cport) - domain = self._find_domain(cportid, - ip) - if domain is not None: + domains = self._find_domains(cportid) + if domains: controllerport = self._find_controller_port(cportid) if controllerport is not None: - iqn = controllerport.get('iscsiName') - break - else: - LOG.error(_LE('Find_iqn: Volume appears unmapped')) - # TODO(tom_swanson): if we have nothing to return raise an exception - # here. We can't do anything with an unmapped volume. We shouldn't - # pretend we succeeded. - return iqn + appendproperties = False + for d in domains: + ipaddress = d.get('targetIpv4Address', + d.get('wellKnownIpAddress')) + portnumber = d.get('portNumber') + if ((ip is None or ip == ipaddress) and + (port is None or port == portnumber)): + portal = (ipaddress + ':' + + six.text_type(portnumber)) + # I'm not sure when we can have more than + # one portal for a domain but since it is an + # array being returned it is best to check. + if portals.count(portal) == 0: + appendproperties = True + portals.append(portal) + # We do not report lun and iqn info unless it is for + # the configured port OR the user has not enabled + # multipath. (In which case ip and port sent in + # will be None). + if appendproperties is True: + iqns.append(controllerport.get('iscsiName')) + luns.append(mapping.get('lun')) + if mapping['readOnly'] is True: + access_mode = 'ro' + + data = {'target_discovered': False, + 'target_iqns': iqns, + 'target_portals': portals, + 'target_luns': luns, + 'access_mode': access_mode + } + + return data def map_volume(self, scvolume, scserver): '''map_volume @@ -811,7 +837,7 @@ class StorageCenterApi(object): The check for server existence is elsewhere; does not create the server. ''' - # make sure we have what we think we have + # 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: @@ -824,13 +850,13 @@ class StorageCenterApi(object): % volumeid, payload) if r.status_code == 200: - # we just return our mapping + # We just return our mapping return self._first_result(r) # Should not be here. LOG.debug('MapToServer error: %(c)d %(r)s', {'c': r.status_code, 'r': r.reason}) - # error out + # Error out LOG.error(_LE('Unable to map %(vol)s to %(srv)s'), {'vol': scvolume['name'], 'srv': scserver['name']}) @@ -932,7 +958,7 @@ class StorageCenterApi(object): % self._get_id(scvolume)) try: content = self._get_json(r) - # this will be a list. If it isn't bail + # This will be a list. If it isn't bail if isinstance(content, list): for r in content: # The only place to save our information with the public @@ -941,9 +967,9 @@ class StorageCenterApi(object): # the max length and we compare that to the start of # the snapshot id. description = r.get('description') - if len(description) >= 30 and \ - replayid.startswith(description) is True and \ - r.get('markedForExpiration') is not True: + if (len(description) >= 30 and + replayid.startswith(description) is True and + r.get('markedForExpiration') is not True): replay = r break except Exception: @@ -990,7 +1016,7 @@ class StorageCenterApi(object): folder = self._find_volume_folder(ssn, volfolder) - # doesn't exist? make it + # Doesn't exist? make it if folder is None: folder = self._create_volume_folder_path(ssn, volfolder) diff --git a/cinder/volume/drivers/dell/dell_storagecenter_iscsi.py b/cinder/volume/drivers/dell/dell_storagecenter_iscsi.py index 00d198655..3561ffaf0 100644 --- a/cinder/volume/drivers/dell/dell_storagecenter_iscsi.py +++ b/cinder/volume/drivers/dell/dell_storagecenter_iscsi.py @@ -38,14 +38,16 @@ class DellStorageCenterISCSIDriver(san.SanISCSIDriver, def __init__(self, *args, **kwargs): super(DellStorageCenterISCSIDriver, self).__init__(*args, **kwargs) - self.backend_name =\ - self.configuration.safe_get('volume_backend_name') or 'Dell-iSCSI' + self.backend_name = ( + self.configuration.safe_get('volume_backend_name') + or 'Dell-iSCSI') def initialize_connection(self, volume, connector): # We use id to name the volume name as it is a # known unique name. volume_name = volume.get('id') initiator_name = connector.get('initiator') + multipath = connector.get('multipath', False) LOG.debug('initialize_ connection: %(n)s:%(i)s', {'n': volume_name, 'i': initiator_name}) @@ -75,27 +77,44 @@ class DellStorageCenterISCSIDriver(san.SanISCSIDriver, # our sc volume object. scvolume = api.find_volume(ssn, volume_name) - ip = self.configuration.iscsi_ip_address - port = self.configuration.iscsi_port - iqn = api.find_iqn(scvolume, - ip) - if iqn is None: - LOG.error(_LE('Volume mapped to invalid path.')) + + if multipath: + # Just return our properties with all the mappings + iscsiproperties = ( + api.find_iscsi_properties(scvolume, + None, + None)) + return {'driver_volume_type': 'iscsi', + 'data': iscsiproperties} else: + # Only return the iqn for the user specified port. + ip = self.configuration.iscsi_ip_address + port = self.configuration.iscsi_port + iscsiproperties = ( + api.find_iscsi_properties(scvolume, + ip, + port)) properties = {} properties['target_discovered'] = False - properties['target_lun'] = mapping['lunUsed'][0] - if mapping['readOnly'] is True: - properties['access_mode'] = 'ro' + portals = iscsiproperties['target_portals'] + # We'll key off of target_portals. If we have + # one listed we can assume that we found what + # we are looking for. Otherwise error. + if len(portals) > 0: + properties['target_portal'] = portals[0] + properties['target_iqn'] = ( + iscsiproperties['target_iqns'][0]) + properties['target_lun'] = ( + iscsiproperties['target_luns'][0]) + properties['access_mode'] = ( + iscsiproperties['access_mode']) + LOG.debug(properties) + return {'driver_volume_type': 'iscsi', + 'data': properties + } else: - properties['access_mode'] = 'rw' - properties['target_portal'] = ip + ':' + str(port) - properties['target_iqn'] = iqn - - LOG.debug(properties) - return {'driver_volume_type': 'iscsi', - 'data': properties - } + LOG.error( + _LE('Volume mapped to invalid path.')) except Exception: with excutils.save_and_reraise_exception(): LOG.error(_LE('Failed to initialize connection ' -- 2.45.2