]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
Dell sc driver iscsi multipath enhancement
authorTom Swanson <tom_swanson@dell.com>
Tue, 17 Feb 2015 20:00:04 +0000 (14:00 -0600)
committerTom Swanson <tom_swanson@dell.com>
Wed, 18 Feb 2015 19:22:00 +0000 (13:22 -0600)
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
cinder/tests/test_dellsc.py
cinder/tests/test_dellscapi.py
cinder/volume/drivers/dell/dell_storagecenter_api.py
cinder/volume/drivers/dell/dell_storagecenter_iscsi.py

index 7a653eeb1ee13c0e01c77d500729703ae908543c..362e7e018f47b15b86a4a8f5c4f2f7772072965f 100644 (file)
@@ -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)
index 82e0462ec8646fc670d4a44ee9af6305286e5653..e7d1ec89fe377c72c5ec93eaacdad05b44399dea 100644 (file)
@@ -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)
index 2c7ec72b94863c9ccb1ad68716b68f0fca48febe..ae9dd851b80bc42cec3d539bd55289352956348b 100644 (file)
@@ -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)
 
index 6c4e918e34b3ad28acb5f5e14de8cc4c21eb7873..069c2157074eb05f35021addcef3e63c67a0c22e 100644 (file)
@@ -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)
index 00d198655b58f11ff501f3e65f9b166fda834633..3561ffaf0d2c05f129d336eaaf3a8e1cfec8b7ef 100644 (file)
@@ -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 '