]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
Enhance FC zone support for Huawei driver
authorLiu Xinguo <295988511@qq.com>
Tue, 14 Jul 2015 14:36:57 +0000 (22:36 +0800)
committerSean Dague <sean@dague.net>
Mon, 31 Aug 2015 18:13:24 +0000 (18:13 +0000)
Currently, when using FC switch, there are manual operations
necessary to enable the Huawei driver to work. This change
makes it so those manual operations are no longer required
any more. Huawei driver can choose the appropriate
FC ports and manage FC zone automatically.

(Pulled from gate, cinder can no longer pass unit tests)

Implements: blueprint huawei-driver-fc-zone-enhancement
Change-Id: Iabdbf294074f68f0f3a01860cc43a486af91d3ba

cinder/tests/unit/test_huawei_drivers.py
cinder/volume/drivers/huawei/constants.py
cinder/volume/drivers/huawei/fc_zone_helper.py [new file with mode: 0644]
cinder/volume/drivers/huawei/huawei_driver.py
cinder/volume/drivers/huawei/huawei_utils.py
cinder/volume/drivers/huawei/rest_client.py

index 5c1496c99fe8c20ac6a8ab05b9537ac025044b2e..523377519a2ebb4d65eec96a439ede191031aab6 100644 (file)
@@ -27,6 +27,7 @@ from cinder import exception
 from cinder import test
 from cinder.volume import configuration as conf
 from cinder.volume.drivers.huawei import constants
+from cinder.volume.drivers.huawei import fc_zone_helper
 from cinder.volume.drivers.huawei import huawei_driver
 from cinder.volume.drivers.huawei import huawei_utils
 from cinder.volume.drivers.huawei import rest_client
@@ -48,6 +49,16 @@ test_volume = {'name': 'volume-21ec7341-9256-497b-97d9-ef48edcf0635',
                'provider_location': '11',
                }
 
+fake_smartx_value = {'smarttier': 'true',
+                     'smartcache': 'true',
+                     'smartpartition': 'true',
+                     'thin_provisioning_support': 'true',
+                     'thick_provisioning_support': False,
+                     'policy': '2',
+                     'cachename': 'cache-test',
+                     'partitionname': 'partition-test',
+                     }
+
 error_volume = {'name': 'volume-21ec7341-9256-497b-97d9-ef48edcf0637',
                 'size': 2,
                 'volume_name': 'vol2',
@@ -73,6 +84,7 @@ test_snap = {'name': 'volume-21ec7341-9256-497b-97d9-ef48edcf0635',
              'display_description': 'test volume',
              'volume_type_id': None,
              'provider_location': '11',
+             'volume': {"volume_id": '21ec7341-9256-497b-97d9-ef48edcf0635'}
              }
 
 test_host = {'host': 'ubuntu001@backend001#OpenStack_Pool',
@@ -91,8 +103,8 @@ test_host = {'host': 'ubuntu001@backend001#OpenStack_Pool',
                               'hypermetro': True,
                               'reserved_percentage': 0,
                               'vendor_name': None,
-                              'thick_provisioning': True,
-                              'thin_provisioning': True,
+                              'thick_provisioning_support': False,
+                              'thin_provisioning_support': True,
                               'storage_protocol': 'FC',
                               }
              }
@@ -107,8 +119,8 @@ test_new_type = {
         'smarttier': '<is> true',
         'smartcache': '<is> true',
         'smartpartition': '<is> true',
-        'thin_provisioning': '<is> true',
-        'thick_provisioning': '<is> False',
+        'thin_provisioning_support': '<is> true',
+        'thick_provisioning_support': '<is> False',
         'policy': '2',
         'smartcache:cachename': 'cache-test',
         'smartpartition:partitionname': 'partition-test',
@@ -135,6 +147,38 @@ smarttier_opts = {'smarttier': 'true',
                   'writecachepolicy': None,
                   }
 
+fake_fabric_mapping = {
+    'swd1': {
+        'target_port_wwn_list': ['2000643e8c4c5f66'],
+        'initiator_port_wwn_list': ['10000090fa0d6754']
+    }
+}
+
+FAKE_CREATE_VOLUME_RESPONSE = {"ID": "1",
+                               "NAME": "5mFHcBv4RkCcD+JyrWc0SA"}
+
+CHANGE_OPTS = {'policy': ('1', '2'),
+               'partitionid': (['1', 'partition001'], ['2', 'partition002']),
+               'cacheid': (['1', 'cache001'], ['2', 'cache002']),
+               'qos': (['11', {'MAXIOPS': '100', 'IOType': '1'}],
+                       {'MAXIOPS': '100', 'IOType': '2',
+                        'MIN': 1, 'LATENCY': 1}),
+               'host': ('ubuntu@huawei#OpenStack_Pool',
+                        'ubuntu@huawei#OpenStack_Pool'),
+               'LUNType': ('0', '1'),
+               }
+
+# A fake response of create a host
+FAKE_CREATE_HOST_RESPONSE = """
+{
+    "error": {
+        "code": 0
+    },
+    "data":{"NAME": "ubuntuc001",
+            "ID": "1"}
+}
+"""
+
 # A fake response of success response storage
 FAKE_COMMON_SUCCESS_RESPONSE = """
 {
@@ -206,12 +250,13 @@ FAKE_LUN_DELETE_SUCCESS_RESPONSE = """
     },
     "data": {
         "ID": "11",
-        "IOCLASSID": "",
+        "IOCLASSID": "11",
         "NAME": "5mFHcBv4RkCcD+JyrWc0SA",
         "RUNNINGSTATUS": "2",
         "HEALTHSTATUS": "1",
         "RUNNINGSTATUS": "27",
-        "ALLOCTYPE": "0",
+        "LUNLIST": "",
+        "ALLOCTYPE": "1",
         "CAPACITY": "2097152",
         "WRITEPOLICY": "1",
         "MIRRORPOLICY": "0",
@@ -295,7 +340,7 @@ FAKE_QUERY_LUN_GROUP_ASSOCIAT_RESPONSE = """
 FAKE_LUN_COUNT_RESPONSE = """
 {
     "data":{
-        "COUNT":"7"
+        "COUNT":"0"
     },
     "error":{
         "code":0,
@@ -316,6 +361,10 @@ FAKE_SNAPSHOT_LIST_INFO_RESPONSE = """
     {
         "ID": 12,
         "NAME": "SDFAJSDFLKJ"
+    },
+    {
+        "ID": 13,
+        "NAME": "s1Ew5v36To-hR2txJitX5Q"
     }]
 }
 """
@@ -516,11 +565,17 @@ FAKE_GET_ALL_HOST_GROUP_INFO_RESPONSE = """
         "code": 0
     },
     "data": [{
-        "NAME": "OpenStack_HostGroup_1",
+        "NAME":"ubuntuc",
         "DESCRIPTION":"",
         "ID":"0",
         "TYPE":14
-    }]
+    },
+    {"NAME":"OpenStack_HostGroup_1",
+     "DESCRIPTION":"",
+     "ID":"0",
+     "TYPE":14
+    }
+    ]
 }
 """
 
@@ -591,7 +646,7 @@ FAKE_GET_MAPPING_VIEW_INFO_RESPONSE = """
     "data":[{
         "WORKMODE":"255",
         "HEALTHSTATUS":"1",
-        "NAME":"IexzQZJWSXuX2e9I7c8GNQ",
+        "NAME":"OpenStack_Mapping_View_1",
         "RUNNINGSTATUS":"27",
         "DESCRIPTION":"",
         "ENABLEINBANDCOMMAND":"true",
@@ -607,13 +662,33 @@ FAKE_GET_MAPPING_VIEW_INFO_RESPONSE = """
         "DESCRIPTION":"",
         "ENABLEINBANDCOMMAND":"true",
         "ID":"2",
-        "INBANDLUNWWN":"",
-        "TYPE":245
+        "INBANDLUNWWN": "",
+        "TYPE": 245
     }]
 }
 """
 
 FAKE_GET_MAPPING_VIEW_RESPONSE = """
+{
+    "error":{
+        "code":0
+    },
+    "data":[{
+        "WORKMODE":"255",
+        "HEALTHSTATUS":"1",
+        "NAME":"mOWtSXnaQKi3hpB3tdFRIQ",
+        "RUNNINGSTATUS":"27",
+        "DESCRIPTION":"",
+        "ENABLEINBANDCOMMAND":"true",
+        "ID":"11",
+        "INBANDLUNWWN":"",
+        "TYPE": 245,
+        "AVAILABLEHOSTLUNIDLIST": ""
+    }]
+}
+"""
+
+FAKE_GET_SPEC_MAPPING_VIEW_RESPONSE = """
 {
     "error":{
         "code":0
@@ -774,10 +849,38 @@ FAKE_ERROR_LUN_INFO_RESPONSE = """
     "data":{
         "ID":"11",
         "IOCLASSID":"11",
-        "NAME":"5mFHcBv4RkCcD+JyrWc0SA"
+        "NAME":"5mFHcBv4RkCcD+JyrWc0SA",
+        "ALLOCTYPE": "0",
+        "DATATRANSFERPOLICY": "0",
+        "SMARTCACHEPARTITIONID": "0",
+        "CACHEPARTITIONID": "0"
     }
 }
 """
+FAKE_GET_FC_INI_RESPONSE = """
+{
+    "error":{
+        "code":0
+    },
+    "data":[{
+        "ID":"10000090fa0d6754",
+        "ISFREE":"true"
+    }]
+}
+"""
+
+FAKE_GET_FC_PORT_RESPONSE = """
+{
+    "error":{
+        "code":0
+    },
+    "data":[{
+        "RUNNINGSTATUS":"10",
+        "WWN":"2000643e8c4c5f66",
+        "PARENTID":"0A.1"
+    }]
+}
+"""
 
 FAKE_SYSTEM_VERSION_RESPONSE = """
 {
@@ -810,6 +913,18 @@ FAKE_GET_LUN_MIGRATION_RESPONSE = """
 }
 """
 
+FAKE_GET_FC_INI_RESPONSE = """
+{
+    "error":{
+        "code":0
+    },
+    "data":[{
+        "ID":"10000090fa0d6754",
+        "ISFREE":"true"
+    }]
+}
+"""
+
 FAKE_QOS_INFO_RESPONSE = """
 {
     "error":{
@@ -821,6 +936,19 @@ FAKE_QOS_INFO_RESPONSE = """
 }
 """
 
+FAKE_GET_FC_PORT_RESPONSE = """
+{
+    "error":{
+        "code":0
+    },
+    "data":[{
+        "RUNNINGSTATUS":"10",
+        "WWN":"2000643e8c4c5f66",
+        "PARENTID":"0A.1"
+    }]
+}
+"""
+
 # mock login info map
 MAP_COMMAND_TO_FAKE_RESPONSE = {}
 MAP_COMMAND_TO_FAKE_RESPONSE['/xx/sessions'] = (
@@ -857,6 +985,12 @@ MAP_COMMAND_TO_FAKE_RESPONSE['lun/11/DELETE'] = (
 MAP_COMMAND_TO_FAKE_RESPONSE['lun/1/DELETE'] = (
     FAKE_COMMON_SUCCESS_RESPONSE)
 
+MAP_COMMAND_TO_FAKE_RESPONSE['lun/1/PUT'] = (
+    FAKE_COMMON_SUCCESS_RESPONSE)
+
+MAP_COMMAND_TO_FAKE_RESPONSE['lun/11/PUT'] = (
+    FAKE_COMMON_SUCCESS_RESPONSE)
+
 MAP_COMMAND_TO_FAKE_RESPONSE['lun?range=[0-65535]/GET'] = (
     FAKE_QUERY_ALL_LUN_RESPONSE)
 
@@ -876,6 +1010,11 @@ MAP_COMMAND_TO_FAKE_RESPONSE['lun/associate?TYPE=11&ASSOCIATEOBJTYPE=21'
                              '&ASSOCIATEOBJID=1/GET'] = (
     FAKE_COMMON_SUCCESS_RESPONSE)
 
+MAP_COMMAND_TO_FAKE_RESPONSE['lun/associate/cachepartition?ID=1'
+                             '&ASSOCIATEOBJTYPE=11&ASSOCIATEOBJID=11'
+                             '/DELETE'] = (
+    FAKE_COMMON_SUCCESS_RESPONSE)
+
 MAP_COMMAND_TO_FAKE_RESPONSE['lungroup?range=[0-8191]/GET'] = (
     FAKE_QUERY_LUN_GROUP_INFO_RESPONSE)
 
@@ -885,6 +1024,9 @@ MAP_COMMAND_TO_FAKE_RESPONSE['lungroup'] = (
 MAP_COMMAND_TO_FAKE_RESPONSE['lungroup/associate'] = (
     FAKE_QUERY_LUN_GROUP_ASSOCIAT_RESPONSE)
 
+MAP_COMMAND_TO_FAKE_RESPONSE['LUNGroup/11/DELETE'] = (
+    FAKE_COMMON_SUCCESS_RESPONSE)
+
 MAP_COMMAND_TO_FAKE_RESPONSE['lungroup/associate?ID=11&ASSOCIATEOBJTYPE=11'
                              '&ASSOCIATEOBJID=1/DELETE'] = (
     FAKE_COMMON_SUCCESS_RESPONSE)
@@ -895,7 +1037,7 @@ MAP_COMMAND_TO_FAKE_RESPONSE['lungroup/associate?TYPE=256&ASSOCIATEOBJTYPE=11'
 
 MAP_COMMAND_TO_FAKE_RESPONSE['lungroup/associate?TYPE=256&ASSOCIATEOBJTYPE=11'
                              '&ASSOCIATEOBJID=1/GET'] = (
-    FAKE_COMMON_SUCCESS_RESPONSE)
+    FAKE_LUN_ASSOCIATE_RESPONSE)
 
 MAP_COMMAND_TO_FAKE_RESPONSE['lungroup/associate?ID=11&ASSOCIATEOBJTYPE=11'
                              '&ASSOCIATEOBJID=11/DELETE'] = (
@@ -951,8 +1093,8 @@ MAP_COMMAND_TO_FAKE_RESPONSE['iscsi_tgt_port/GET'] = (
 MAP_COMMAND_TO_FAKE_RESPONSE['eth_port/GET'] = (
     FAKE_GET_ETH_INFO_RESPONSE)
 
-MAP_COMMAND_TO_FAKE_RESPONSE['eth_port/associate?TYPE=213&ASSOCIATEOBJTYPE=257'
-                             '&ASSOCIATEOBJID=11/GET'] = (
+MAP_COMMAND_TO_FAKE_RESPONSE['eth_port/associate?TYPE=213&ASSOCIATEOBJTYPE'
+                             '=257&ASSOCIATEOBJID=11/GET'] = (
     FAKE_GET_ETH_ASSOCIATE_RESPONSE)
 
 MAP_COMMAND_TO_FAKE_RESPONSE['iscsidevicename'] = (
@@ -962,13 +1104,17 @@ MAP_COMMAND_TO_FAKE_RESPONSE['iscsi_initiator?range=[0-256]/GET'] = (
     FAKE_COMMON_SUCCESS_RESPONSE)
 
 MAP_COMMAND_TO_FAKE_RESPONSE['iscsi_initiator/'] = (
-    FAKE_ISCSI_INITIATOR_RESPONSE)
+    FAKE_COMMON_SUCCESS_RESPONSE)
 
 MAP_COMMAND_TO_FAKE_RESPONSE['iscsi_initiator/POST'] = (
     FAKE_ISCSI_INITIATOR_RESPONSE)
 
 MAP_COMMAND_TO_FAKE_RESPONSE['iscsi_initiator/PUT'] = (
     FAKE_ISCSI_INITIATOR_RESPONSE)
+
+MAP_COMMAND_TO_FAKE_RESPONSE['iscsi_initiator/remove_iscsi_from_host/PUT'] = (
+    FAKE_COMMON_SUCCESS_RESPONSE)
+
 MAP_COMMAND_TO_FAKE_RESPONSE['iscsi_initiator/'
                              'iqn.1993-08.debian:01:ec2bff7ac3a3/PUT'] = (
     FAKE_ISCSI_INITIATOR_RESPONSE)
@@ -976,16 +1122,34 @@ MAP_COMMAND_TO_FAKE_RESPONSE['iscsi_initiator/'
 MAP_COMMAND_TO_FAKE_RESPONSE['host?range=[0-65535]/GET'] = (
     FAKE_GET_ALL_HOST_INFO_RESPONSE)
 
+MAP_COMMAND_TO_FAKE_RESPONSE['host/1/DELETE'] = (
+    FAKE_COMMON_SUCCESS_RESPONSE)
+
+MAP_COMMAND_TO_FAKE_RESPONSE['host'] = (
+    FAKE_CREATE_HOST_RESPONSE)
+
 MAP_COMMAND_TO_FAKE_RESPONSE['hostgroup?range=[0-8191]/GET'] = (
     FAKE_GET_ALL_HOST_GROUP_INFO_RESPONSE)
 
 MAP_COMMAND_TO_FAKE_RESPONSE['hostgroup'] = (
     FAKE_GET_HOST_GROUP_INFO_RESPONSE)
 
-MAP_COMMAND_TO_FAKE_RESPONSE['host/associate?TYPE=21&ASSOCIATEOBJTYPE=14'
+MAP_COMMAND_TO_FAKE_RESPONSE['host/associate?TYPE=14&ID=0'
+                             '&ASSOCIATEOBJTYPE=21&ASSOCIATEOBJID=1'
+                             '/DELETE'] = (
+    FAKE_COMMON_SUCCESS_RESPONSE)
+
+MAP_COMMAND_TO_FAKE_RESPONSE['host/associate?TYPE=14&ID=0'
                              '&ASSOCIATEOBJID=0/GET'] = (
     FAKE_COMMON_SUCCESS_RESPONSE)
 
+MAP_COMMAND_TO_FAKE_RESPONSE['host/associate?TYPE=21&'
+                             'ASSOCIATEOBJTYPE=14&ASSOCIATEOBJID=0/GET'] = (
+    FAKE_COMMON_SUCCESS_RESPONSE)
+
+MAP_COMMAND_TO_FAKE_RESPONSE['hostgroup/0/DELETE'] = (
+    FAKE_COMMON_SUCCESS_RESPONSE)
+
 MAP_COMMAND_TO_FAKE_RESPONSE['hostgroup/associate'] = (
     FAKE_COMMON_SUCCESS_RESPONSE)
 
@@ -1009,11 +1173,41 @@ MAP_COMMAND_TO_FAKE_RESPONSE['mappingview?range=[0-8191]/GET'] = (
 MAP_COMMAND_TO_FAKE_RESPONSE['mappingview'] = (
     FAKE_GET_MAPPING_VIEW_RESPONSE)
 
+MAP_COMMAND_TO_FAKE_RESPONSE['MAPPINGVIEW/1/GET'] = (
+    FAKE_GET_SPEC_MAPPING_VIEW_RESPONSE)
+
+MAP_COMMAND_TO_FAKE_RESPONSE['mappingview/1/DELETE'] = (
+    FAKE_COMMON_SUCCESS_RESPONSE)
+
+MAP_COMMAND_TO_FAKE_RESPONSE['mappingview/associate/lungroup?TYPE=256&'
+                             'ASSOCIATEOBJTYPE=245&ASSOCIATEOBJID=1/GET'] = (
+    FAKE_GET_MAPPING_VIEW_RESPONSE)
+
+MAP_COMMAND_TO_FAKE_RESPONSE['mappingview/associate?TYPE=245&'
+                             'ASSOCIATEOBJTYPE=14&ASSOCIATEOBJID=0/GET'] = (
+    FAKE_GET_MAPPING_VIEW_RESPONSE)
+
+MAP_COMMAND_TO_FAKE_RESPONSE['mappingview/associate?TYPE=245&'
+                             'ASSOCIATEOBJTYPE=256&ASSOCIATEOBJID=11/GET'] = (
+    FAKE_GET_MAPPING_VIEW_RESPONSE)
+
+MAP_COMMAND_TO_FAKE_RESPONSE['mappingview/associate?TYPE=245&'
+                             'ASSOCIATEOBJTYPE=257&ASSOCIATEOBJID=11/GET'] = (
+    FAKE_GET_MAPPING_VIEW_RESPONSE)
+
+MAP_COMMAND_TO_FAKE_RESPONSE['portgroup/associate?ASSOCIATEOBJTYPE=245&'
+                             'ASSOCIATEOBJID=1&range=[0-8191]/GET'] = (
+    FAKE_GET_MAPPING_VIEW_RESPONSE)
+
 MAP_COMMAND_TO_FAKE_RESPONSE['MAPPINGVIEW/CREATE_ASSOCIATE/PUT'] = (
     FAKE_COMMON_SUCCESS_RESPONSE)
 
 # mock FC info map
-MAP_COMMAND_TO_FAKE_RESPONSE['fc_initiator?ISFREE=true&range=[0-8191]/GET'] = (
+MAP_COMMAND_TO_FAKE_RESPONSE['fc_initiator?ISFREE=true&'
+                             'range=[0-8191]/GET'] = (
+    FAKE_FC_INFO_RESPONSE)
+
+MAP_COMMAND_TO_FAKE_RESPONSE['fc_initiator/10000090fa0d6754/GET'] = (
     FAKE_FC_INFO_RESPONSE)
 
 MAP_COMMAND_TO_FAKE_RESPONSE['fc_initiator/10000090fa0d6754/PUT'] = (
@@ -1030,8 +1224,17 @@ MAP_COMMAND_TO_FAKE_RESPONSE['portgroup?range=[0-8191]&TYPE=257/GET'] = (
 MAP_COMMAND_TO_FAKE_RESPONSE['system/'] = (
     FAKE_SYSTEM_VERSION_RESPONSE)
 
-MAP_COMMAND_TO_FAKE_RESPONSE['lun/associate/cachepartition/POST'] = (
-    FAKE_SYSTEM_VERSION_RESPONSE)
+MAP_COMMAND_TO_FAKE_RESPONSE['fc_initiator?range=[0-256]/GET'] = (
+    FAKE_GET_FC_INI_RESPONSE)
+
+MAP_COMMAND_TO_FAKE_RESPONSE['fc_port/GET'] = (
+    FAKE_GET_FC_PORT_RESPONSE)
+
+MAP_COMMAND_TO_FAKE_RESPONSE['fc_initiator/GET'] = (
+    FAKE_GET_FC_PORT_RESPONSE)
+
+MAP_COMMAND_TO_FAKE_RESPONSE['fc_initiator?range=[0-100]/GET'] = (
+    FAKE_GET_FC_PORT_RESPONSE)
 
 
 def Fake_sleep(time):
@@ -1044,7 +1247,7 @@ class Fake18000Client(rest_client.RestClient):
         rest_client.RestClient.__init__(self, configuration)
         self.delete_flag = False
         self.terminateFlag = False
-        self.deviceid = None
+        self.device_id = None
         self.test_fail = False
         self.checkFlag = False
         self.remove_chap_flag = False
@@ -1131,6 +1334,7 @@ class Fake18000FCStorage(huawei_driver.Huawei18000FCDriver):
     def __init__(self, configuration):
         self.configuration = configuration
         self.xml_file_path = self.configuration.cinder_huawei_conf_file
+        self.fcsan_lookup_service = None
 
     def do_setup(self):
         self.restclient = Fake18000Client(configuration=self.configuration)
@@ -1162,8 +1366,8 @@ class Huawei18000ISCSIDriverTestCase(test.TestCase):
         self.portgroup_id = 11
 
     def test_login_success(self):
-        deviceid = self.driver.restclient.login()
-        self.assertEqual('210235G7J20000000000', deviceid)
+        device_id = self.driver.restclient.login()
+        self.assertEqual('210235G7J20000000000', device_id)
 
     def test_create_volume_success(self):
         self.driver.restclient.login()
@@ -1177,7 +1381,7 @@ class Huawei18000ISCSIDriverTestCase(test.TestCase):
 
     def test_create_snapshot_success(self):
         self.driver.restclient.login()
-        lun_info = self.driver.create_snapshot(test_volume)
+        lun_info = self.driver.create_snapshot(test_snap)
         self.assertEqual(11, lun_info['provider_location'])
 
     def test_delete_snapshot_success(self):
@@ -1222,14 +1426,11 @@ class Huawei18000ISCSIDriverTestCase(test.TestCase):
         self.driver.restclient.login()
         self.driver.restclient.test_fail = True
         self.assertRaises(exception.VolumeBackendAPIException,
-                          self.driver.create_snapshot, test_volume)
+                          self.driver.create_snapshot, test_snap)
 
     def test_create_volume_fail(self):
         self.driver.restclient.login()
         self.driver.restclient.test_fail = True
-        self.assertRaises(exception.VolumeBackendAPIException,
-                          self.driver.create_volume, test_volume)
-
         self.assertRaises(exception.VolumeBackendAPIException,
                           self.driver.create_volume, error_volume)
 
@@ -1391,16 +1592,17 @@ class Huawei18000ISCSIDriverTestCase(test.TestCase):
         lun_info = self.driver.create_volume(test_volume)
         self.assertEqual('1', lun_info['provider_location'])
 
+    @mock.patch.object(rest_client.RestClient, 'add_lun_to_partition')
     @mock.patch.object(huawei_utils, 'get_volume_params',
                        return_value={'smarttier': 'true',
                                      'smartcache': 'true',
                                      'smartpartition': 'true',
                                      'thin_provisioning_support': 'true',
-                                     'thick_provisioning_support': False,
+                                     'thick_provisioning_support': 'false',
                                      'policy': '2',
                                      'cachename': 'cache-test',
                                      'partitionname': 'partition-test'})
-    def test_creat_smartx(self, mock_volume_types):
+    def test_creat_smartx(self, mock_volume_types, mock_add_lun_to_partition):
         self.driver.restclient.login()
         lun_info = self.driver.create_volume(test_volume)
         self.assertEqual('1', lun_info['provider_location'])
@@ -1492,6 +1694,12 @@ class Huawei18000ISCSIDriverTestCase(test.TestCase):
         fakefile.close()
 
 
+class FCSanLookupService(object):
+    def get_device_mapping_from_network(self, initiator_list,
+                                        target_list):
+        return fake_fabric_mapping
+
+
 class Huawei18000FCDriverTestCase(test.TestCase):
 
     def setUp(self):
@@ -1510,8 +1718,8 @@ class Huawei18000FCDriverTestCase(test.TestCase):
         self.driver.do_setup()
 
     def test_login_success(self):
-        deviceid = self.driver.restclient.login()
-        self.assertEqual('210235G7J20000000000', deviceid)
+        device_id = self.driver.restclient.login()
+        self.assertEqual('210235G7J20000000000', device_id)
 
     def test_create_volume_success(self):
         self.driver.restclient.login()
@@ -1525,7 +1733,7 @@ class Huawei18000FCDriverTestCase(test.TestCase):
 
     def test_create_snapshot_success(self):
         self.driver.restclient.login()
-        lun_info = self.driver.create_snapshot(test_volume)
+        lun_info = self.driver.create_snapshot(test_snap)
         self.assertEqual(11, lun_info['provider_location'])
 
     def test_delete_snapshot_success(self):
@@ -1570,7 +1778,7 @@ class Huawei18000FCDriverTestCase(test.TestCase):
         self.driver.restclient.login()
         self.driver.restclient.test_fail = True
         self.assertRaises(exception.VolumeBackendAPIException,
-                          self.driver.create_snapshot, test_volume)
+                          self.driver.create_snapshot, test_snap)
 
     def test_create_volume_fail(self):
         self.driver.restclient.login()
@@ -1646,7 +1854,8 @@ class Huawei18000FCDriverTestCase(test.TestCase):
                                                      self.configuration)
         self.assertEqual('0', host_os)
 
-    def test_migrate_volume_success(self):
+    @mock.patch.object(rest_client.RestClient, 'add_lun_to_partition')
+    def test_migrate_volume_success(self, mock_add_lun_to_partition):
         self.driver.restclient.login()
 
         # Migrate volume without new type.
@@ -1668,8 +1877,8 @@ class Huawei18000FCDriverTestCase(test.TestCase):
                     {'smarttier': '<is> true',
                      'smartcache': '<is> true',
                      'smartpartition': '<is> true',
-                     'thin_provisioning': '<is> true',
-                     'thick_provisioning': '<is> False',
+                     'thin_provisioning_support': '<is> true',
+                     'thick_provisioning_support': '<is> False',
                      'policy': '2',
                      'smartcache:cachename': 'cache-test',
                      'smartpartition:partitionname': 'partition-test'}}
@@ -1693,8 +1902,8 @@ class Huawei18000FCDriverTestCase(test.TestCase):
         new_type = {'extra_specs':
                     {'smarttier': '<is> true',
                      'smartcache': '<is> true',
-                     'thin_provisioning': '<is> true',
-                     'thick_provisioning': '<is> False',
+                     'thin_provisioning_support': '<is> true',
+                     'thick_provisioning_support': '<is> False',
                      'policy': '2',
                      'smartcache:cachename': 'cache-test',
                      'partitionname': 'partition-test'}}
@@ -1786,7 +1995,8 @@ class Huawei18000FCDriverTestCase(test.TestCase):
         self.assertEqual({'_name_id': '21ec7341-9256-497b-97d9-ef48edcf0637'},
                          model_update)
 
-    def test_retype_volume_success(self):
+    @mock.patch.object(rest_client.RestClient, 'add_lun_to_partition')
+    def test_retype_volume_success(self, mock_add_lun_to_partition):
         self.driver.restclient.login()
         retype = self.driver.retype(None, test_volume,
                                     test_new_type, None, test_host)
@@ -1815,6 +2025,38 @@ class Huawei18000FCDriverTestCase(test.TestCase):
                                     test_new_type, None, test_host)
         self.assertFalse(retype)
 
+    def test_build_ini_targ_map(self):
+        self.driver.restclient.login()
+        fake_lookup_service = FCSanLookupService()
+        fake_lookup_service.get_device_mapping_from_network = mock.Mock(
+            return_value=fake_fabric_mapping)
+
+        zone_helper = fc_zone_helper.FCZoneHelper(
+            fake_lookup_service, self.driver.restclient)
+        (tgt_port_wwns,
+         init_targ_map) = (zone_helper.build_ini_targ_map(
+             ['10000090fa0d6754']))
+        target_port_wwns = ['2000643e8c4c5f66']
+        ini_target_map = {'10000090fa0d6754': ['2000643e8c4c5f66']}
+        self.assertEqual(target_port_wwns, tgt_port_wwns)
+        self.assertEqual(ini_target_map, init_targ_map)
+
+    def test_filter_port_by_contr(self):
+        self.driver.restclient.login()
+        # Six ports in one fabric.
+        ports_in_fabric = ['1', '2', '3', '4', '5', '6']
+        # Ports 1,3,4,7 belonged to controller A
+        # Ports 2,5,8 belonged to controller B
+        # ports 6 belonged to controller C
+        total_port_contr_map = {'1': 'A', '3': 'A', '4': 'A', '7': 'A',
+                                '2': 'B', '5': 'B', '8': 'B',
+                                '6': 'C'}
+        zone_helper = fc_zone_helper.FCZoneHelper(None, None)
+        filtered_ports = zone_helper._filter_port_by_contr(
+            ports_in_fabric, total_port_contr_map)
+        expected_filtered_ports = ['1', '3', '2', '5', '6']
+        self.assertEqual(expected_filtered_ports, filtered_ports)
+
     def create_fake_conf_file(self):
         """Create a fake Config file
 
index 5bf2a74c41681a8c35df1962d0e573ee3ad2adce..4ea9a8c9f9dcf0b3d77940e5f3777e7196445a4e 100644 (file)
@@ -27,6 +27,8 @@ LUNGROUP_PREFIX = 'OpenStack_LunGroup_'
 MAPPING_VIEW_PREFIX = 'OpenStack_Mapping_View_'
 QOS_NAME_PREFIX = 'OpenStack_'
 ARRAY_VERSION = 'V300R003C00'
+FC_PORT_CONNECTED = '10'
+FC_INIT_ONLINE = '27'
 CAPACITY_UNIT = 1024.0 / 1024.0 / 2
 DEFAULT_WAIT_TIMEOUT = 3600 * 24 * 30
 DEFAULT_WAIT_INTERVAL = 5
@@ -52,6 +54,7 @@ OS_TYPE = {'Linux': '0',
            'Mac OS X': '6',
            'VMware ESX': '7'}
 
+CONTROLLER_LIST = ['A', 'B', 'C', 'D']
 HUAWEI_VALID_KEYS = ['maxIOPS', 'minIOPS', 'minBandWidth',
                      'maxBandWidth', 'latency', 'IOType']
 QOS_KEYS = ['MAXIOPS', 'MINIOPS', 'MINBANDWidth',
diff --git a/cinder/volume/drivers/huawei/fc_zone_helper.py b/cinder/volume/drivers/huawei/fc_zone_helper.py
new file mode 100644 (file)
index 0000000..64af987
--- /dev/null
@@ -0,0 +1,69 @@
+# Copyright (c) 2015 Huawei Technologies Co., Ltd.
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+from oslo_log import log as logging
+
+from cinder.volume.drivers.huawei import constants
+
+LOG = logging.getLogger(__name__)
+
+
+class FCZoneHelper(object):
+    """FC zone helper for Huawei driver."""
+
+    def __init__(self, fcsan_lookup_service, restclient):
+        self.fcsan_lookup_service = fcsan_lookup_service
+        self.restclient = restclient
+
+    def _get_fc_port_contr_map(self):
+        port_list = []
+        port_contr_map = {}
+        data = self.restclient.get_fc_ports_on_array()
+        for item in data:
+            if item['RUNNINGSTATUS'] == constants.FC_PORT_CONNECTED:
+                port_list.append(item['WWN'])
+                location = item['PARENTID'].split('.')
+                port_contr_map[item['WWN']] = location[0][1]
+        return port_list, port_contr_map
+
+    def _filter_port_by_contr(self, ports_in_fabric, port_contr_map):
+        filtered_ports = []
+        for contr in constants.CONTROLLER_LIST:
+            found_port_per_contr = 0
+            for port in ports_in_fabric:
+                if port in port_contr_map and port_contr_map[port] == contr:
+                    filtered_ports.append(port)
+                    found_port_per_contr = found_port_per_contr + 1
+                    # We select two ports per every controller.
+                    if found_port_per_contr == 2:
+                        break
+        return filtered_ports
+
+    def build_ini_targ_map(self, wwns):
+        tgt_wwns = []
+        init_targ_map = {}
+        port_lists, port_contr_map = self._get_fc_port_contr_map()
+        ini_tgt_map = (self.fcsan_lookup_service.
+                       get_device_mapping_from_network(wwns, port_lists))
+
+        for fabric in ini_tgt_map:
+            ports_in_fabric = ini_tgt_map[fabric]['target_port_wwn_list']
+            contr_filtered_ports = self._filter_port_by_contr(ports_in_fabric,
+                                                              port_contr_map)
+            tgt_wwns.extend(contr_filtered_ports)
+            for ini in ini_tgt_map[fabric]['initiator_port_wwn_list']:
+                init_targ_map[ini] = contr_filtered_ports
+
+        return (list(set(tgt_wwns)), init_targ_map)
index 0b63dcd31a524b7d1d9c04fe812b21ba37218f90..b49db23c088865d1a27ba98e97e2a6626bbe55d2 100644 (file)
@@ -26,6 +26,7 @@ from cinder.i18n import _, _LE, _LI, _LW
 from cinder import utils
 from cinder.volume import driver
 from cinder.volume.drivers.huawei import constants
+from cinder.volume.drivers.huawei import fc_zone_helper
 from cinder.volume.drivers.huawei import huawei_utils
 from cinder.volume.drivers.huawei import rest_client
 from cinder.volume.drivers.huawei import smartx
@@ -117,8 +118,7 @@ class HuaweiBaseDriver(driver.VolumeDriver):
             smartcache = smartx.SmartCache(self.restclient)
             smartcache.add(opts, lun_id)
         except Exception as err:
-            if lun_id:
-                self._delete_lun_with_check(lun_id)
+            self._delete_lun_with_check(lun_id)
             raise exception.InvalidInput(
                 reason=_('Create volume error. Because %s.') % err)
 
@@ -163,14 +163,14 @@ class HuaweiBaseDriver(driver.VolumeDriver):
                                                 lun_list,
                                                 qos_id)
 
-    def _delete_lun_with_check(self, dst_id):
-        LOG.info(_LI("Try to delete lun %s if it exists."), dst_id)
-        if self.restclient.check_lun_exist(dst_id):
-            qos_id = self.restclient.get_qosid_by_lunid(dst_id)
-            if qos_id:
-                qos = smartx.SmartQos(self.restclient)
-                qos.delete_qos(qos_id)
-            self.restclient.delete_lun(dst_id)
+    def _delete_lun_with_check(self, lun_id):
+        if lun_id:
+            if self.restclient.check_lun_exist(lun_id):
+                qos_id = self.restclient.get_qosid_by_lunid(lun_id)
+                if qos_id:
+                    self.remove_qos_lun(lun_id, qos_id)
+
+                self.restclient.delete_lun(lun_id)
 
     def _is_lun_migration_complete(self, src_id, dst_id):
         result = self.restclient.get_lun_migration_task()
@@ -252,7 +252,7 @@ class HuaweiBaseDriver(driver.VolumeDriver):
             return 'in-use'
 
     def update_migrated_volume(self, ctxt, volume, new_volume,
-                               original_volume_status):
+                               original_volume_status=None):
         original_name = huawei_utils.encode_name(volume['id'])
         current_name = huawei_utils.encode_name(new_volume['id'])
 
@@ -534,219 +534,6 @@ class HuaweiBaseDriver(driver.VolumeDriver):
 
         return True
 
-    @utils.synchronized('huawei', external=True)
-    def initialize_connection_fc(self, volume, connector):
-        wwns = connector['wwpns']
-        volume_name = huawei_utils.encode_name(volume['id'])
-
-        LOG.info(_LI(
-            'initialize_connection_fc, initiator: %(wwpns)s,'
-            ' volume name: %(volume)s.'),
-            {'wwpns': wwns,
-             'volume': volume_name},)
-
-        host_name_before_hash = None
-        host_name = connector['host']
-        if host_name and (len(host_name) > constants.MAX_HOSTNAME_LENGTH):
-            host_name_before_hash = host_name
-            host_name = six.text_type(hash(host_name))
-
-        # Create hostgroup if not exist.
-        host_id = self.restclient.add_host_with_check(host_name,
-                                                      host_name_before_hash)
-
-        # Add host into hostgroup.
-        hostgroup_id = self.restclient.add_host_into_hostgroup(host_id)
-
-        free_wwns = self.restclient.get_connected_free_wwns()
-        LOG.info(_LI("initialize_connection_fc, the array has free wwns: %s."),
-                 free_wwns)
-        for wwn in wwns:
-            if wwn in free_wwns:
-                self.restclient.add_fc_port_to_host(host_id, wwn)
-
-        lun_id = self.restclient.mapping_hostgroup_and_lungroup(volume_name,
-                                                                hostgroup_id,
-                                                                host_id)
-        host_lun_id = self.restclient.find_host_lun_id(host_id, lun_id)
-
-        tgt_port_wwns = []
-        for wwn in wwns:
-            tgtwwpns = self.restclient.get_fc_target_wwpns(wwn)
-            if tgtwwpns:
-                tgt_port_wwns.append(tgtwwpns)
-
-        init_targ_map = {}
-        for initiator in wwns:
-            init_targ_map[initiator] = tgt_port_wwns
-
-        # Return FC properties.
-        info = {'driver_volume_type': 'fibre_channel',
-                'data': {'target_lun': int(host_lun_id),
-                         'target_discovered': True,
-                         'target_wwn': tgt_port_wwns,
-                         'volume_id': volume['id'],
-                         'initiator_target_map': init_targ_map}, }
-
-        LOG.info(_LI("initialize_connection_fc, return data is: %s."),
-                 info)
-
-        return info
-
-    @utils.synchronized('huawei', external=True)
-    def initialize_connection_iscsi(self, volume, connector):
-        """Map a volume to a host and return target iSCSI information."""
-        LOG.info(_LI('Enter initialize_connection_iscsi.'))
-        initiator_name = connector['initiator']
-        volume_name = huawei_utils.encode_name(volume['id'])
-
-        LOG.info(_LI(
-            'initiator name: %(initiator_name)s, '
-            'volume name: %(volume)s.'),
-            {'initiator_name': initiator_name,
-             'volume': volume_name})
-
-        (iscsi_iqns,
-         target_ips,
-         portgroup_id) = self.restclient.get_iscsi_params(self.xml_file_path,
-                                                          connector)
-        LOG.info(_LI('initialize_connection_iscsi, iscsi_iqn: %(iscsi_iqn)s, '
-                     'target_ip: %(target_ip)s, '
-                     'portgroup_id: %(portgroup_id)s.'),
-                 {'iscsi_iqn': iscsi_iqns,
-                  'target_ip': target_ips,
-                  'portgroup_id': portgroup_id},)
-
-        # Create hostgroup if not exist.
-        host_name = connector['host']
-        host_name_before_hash = None
-        if host_name and (len(host_name) > constants.MAX_HOSTNAME_LENGTH):
-            host_name_before_hash = host_name
-            host_name = six.text_type(hash(host_name))
-        host_id = self.restclient.add_host_with_check(host_name,
-                                                      host_name_before_hash)
-
-        # Add initiator to the host.
-        self.restclient.ensure_initiator_added(self.xml_file_path,
-                                               initiator_name,
-                                               host_id)
-        hostgroup_id = self.restclient.add_host_into_hostgroup(host_id)
-
-        # Mapping lungroup and hostgroup to view.
-        lun_id = self.restclient.mapping_hostgroup_and_lungroup(volume_name,
-                                                                hostgroup_id,
-                                                                host_id,
-                                                                portgroup_id)
-
-        hostlun_id = self.restclient.find_host_lun_id(host_id, lun_id)
-
-        LOG.info(_LI("initialize_connection_iscsi, host lun id is: %s."),
-                 hostlun_id)
-
-        iscsi_conf = huawei_utils.get_iscsi_conf(self.xml_file_path)
-        chapinfo = self.restclient.find_chap_info(iscsi_conf,
-                                                  initiator_name)
-        # Return iSCSI properties.
-        properties = {}
-        properties['target_discovered'] = False
-        properties['volume_id'] = volume['id']
-        multipath = connector.get('multipath', False)
-        hostlun_id = int(hostlun_id)
-        if not multipath:
-            properties['target_portal'] = ('%s:3260' % target_ips[0])
-            properties['target_iqn'] = iscsi_iqns[0]
-            properties['target_lun'] = hostlun_id
-        else:
-            properties['target_iqns'] = [iqn for iqn in iscsi_iqns]
-            properties['target_portals'] = [
-                '%s:3260' % ip for ip in target_ips]
-            properties['target_luns'] = [hostlun_id] * len(target_ips)
-
-        # If use CHAP, return CHAP info.
-        if chapinfo:
-            chap_username, chap_password = chapinfo.split(';')
-            properties['auth_method'] = 'CHAP'
-            properties['auth_username'] = chap_username
-            properties['auth_password'] = chap_password
-
-        LOG.info(_LI("initialize_connection_iscsi success. Return data: %s."),
-                 properties)
-        return {'driver_volume_type': 'iscsi', 'data': properties}
-
-    @utils.synchronized('huawei', external=True)
-    def terminate_connection_iscsi(self, volume, connector):
-        """Delete map between a volume and a host."""
-        initiator_name = connector['initiator']
-        volume_name = huawei_utils.encode_name(volume['id'])
-        lun_id = volume.get('provider_location', None)
-        host_name = connector['host']
-
-        LOG.info(_LI(
-            'terminate_connection_iscsi: volume name: %(volume)s, '
-            'initiator name: %(ini)s, '
-            'lun_id: %(lunid)s.'),
-            {'volume': volume_name,
-             'ini': initiator_name,
-             'lunid': lun_id},)
-
-        iscsi_conf = huawei_utils.get_iscsi_conf(self.xml_file_path)
-        portgroup = None
-        portgroup_id = None
-        left_lunnum = -1
-        for ini in iscsi_conf['Initiator']:
-            if ini['Name'] == initiator_name:
-                for key in ini:
-                    if key == 'TargetPortGroup':
-                        portgroup = ini['TargetPortGroup']
-                        break
-        # Remove lun from lungroup.
-        if lun_id:
-            if self.restclient.check_lun_exist(lun_id):
-                # Get lungroup id by lun id.
-                lungroup_id = self.restclient.get_lungroupid_by_lunid(lun_id)
-                if lungroup_id:
-                    self.restclient.remove_lun_from_lungroup(lungroup_id,
-                                                             lun_id)
-            else:
-                LOG.warning(_LW("Can't find lun on the array."))
-        # Remove portgroup from mapping view if no lun left in lungroup.
-        if portgroup:
-            portgroup_id = self.restclient.find_tgt_port_group(portgroup)
-        host_id = self.restclient.find_host(host_name)
-        if host_id:
-            mapping_view_name = constants.MAPPING_VIEW_PREFIX + host_id
-            view_id = self.restclient.find_mapping_view(mapping_view_name)
-            if view_id:
-                lungroup_id = self.restclient.find_lungroup_from_map(view_id)
-        if lungroup_id:
-            left_lunnum = self.restclient.get_lunnum_from_lungroup(lungroup_id)
-
-        if portgroup_id and view_id and (int(left_lunnum) <= 0):
-            if self.restclient.is_portgroup_associated_to_view(view_id,
-                                                               portgroup_id):
-                self.restclient.delete_portgroup_mapping_view(view_id,
-                                                              portgroup_id)
-        if view_id and (int(left_lunnum) <= 0):
-            self.restclient.remove_chap(initiator_name)
-
-            if self.restclient.lungroup_associated(view_id, lungroup_id):
-                self.restclient.delete_lungroup_mapping_view(view_id,
-                                                             lungroup_id)
-            self.restclient.delete_lungroup(lungroup_id)
-            if self.restclient.is_initiator_associated_to_host(initiator_name):
-                self.restclient.remove_iscsi_from_host(initiator_name)
-            hostgroup_name = constants.HOSTGROUP_PREFIX + host_id
-            hostgroup_id = self.restclient.find_hostgroup(hostgroup_name)
-            if hostgroup_id:
-                if self.restclient.hostgroup_associated(view_id, hostgroup_id):
-                    self.restclient.delete_hostgoup_mapping_view(view_id,
-                                                                 hostgroup_id)
-                self.restclient.remove_host_from_hostgroup(hostgroup_id,
-                                                           host_id)
-                self.restclient.delete_hostgroup(hostgroup_id)
-            self.restclient.remove_host(host_id)
-            self.restclient.delete_mapping_view(view_id)
-
     def retype(self, ctxt, volume, new_type, diff, host):
         """Convert the volume to be of the new type."""
         LOG.debug("Enter retype: id=%(id)s, new_type=%(new_type)s, "
@@ -961,58 +748,6 @@ class HuaweiBaseDriver(driver.VolumeDriver):
                     qos[key.upper()] = value
         return qos
 
-    def terminate_connection_fc(self, volume, connector):
-        """Delete map between a volume and a host."""
-        wwns = connector['wwpns']
-        volume_name = huawei_utils.encode_name(volume['id'])
-        lun_id = volume.get('provider_location', None)
-        host_name = connector['host']
-        left_lunnum = -1
-
-        LOG.info(_LI('terminate_connection_fc: volume name: %(volume)s, '
-                     'wwpns: %(wwns)s, '
-                     'lun_id: %(lunid)s.'),
-                 {'volume': volume_name,
-                  'wwns': wwns,
-                  'lunid': lun_id},)
-        if lun_id:
-            if self.restclient.check_lun_exist(lun_id):
-                # Get lungroup id by lun id.
-                lungroup_id = self.restclient.get_lungroupid_by_lunid(lun_id)
-                if not lungroup_id:
-                    LOG.info(_LI("Can't find lun in lungroup."))
-                else:
-                    self.restclient.remove_lun_from_lungroup(lungroup_id,
-                                                             lun_id)
-            else:
-                LOG.warning(_LW("Can't find lun on the array."))
-        tgt_port_wwns = []
-        for wwn in wwns:
-            tgtwwpns = self.restclient.get_fc_target_wwpns(wwn)
-            if tgtwwpns:
-                tgt_port_wwns.append(tgtwwpns)
-
-        init_targ_map = {}
-        for initiator in wwns:
-            init_targ_map[initiator] = tgt_port_wwns
-        host_id = self.restclient.find_host(host_name)
-        if host_id:
-            mapping_view_name = constants.MAPPING_VIEW_PREFIX + host_id
-            view_id = self.restclient.find_mapping_view(mapping_view_name)
-            if view_id:
-                lungroup_id = self.restclient.find_lungroup_from_map(view_id)
-        if lungroup_id:
-            left_lunnum = self.restclient.get_lunnum_from_lungroup(lungroup_id)
-        if int(left_lunnum) > 0:
-            info = {'driver_volume_type': 'fibre_channel',
-                    'data': {}}
-        else:
-            info = {'driver_volume_type': 'fibre_channel',
-                    'data': {'target_wwn': tgt_port_wwns,
-                             'initiator_target_map': init_targ_map}, }
-
-        return info
-
     def create_export(self, context, volume, connector):
         """Export a volume."""
         pass
@@ -1099,15 +834,171 @@ class Huawei18000ISCSIDriver(HuaweiBaseDriver, driver.ISCSIDriver):
         data['vendor_name'] = 'Huawei'
         return data
 
+    @utils.synchronized('huawei', external=True)
     def initialize_connection(self, volume, connector):
-        return HuaweiBaseDriver.initialize_connection_iscsi(self,
-                                                            volume,
-                                                            connector)
+        """Map a volume to a host and return target iSCSI information."""
+        LOG.info(_LI('Enter initialize_connection.'))
+        initiator_name = connector['initiator']
+        volume_name = huawei_utils.encode_name(volume['id'])
+
+        LOG.info(_LI(
+            'initiator name: %(initiator_name)s, '
+            'volume name: %(volume)s.'),
+            {'initiator_name': initiator_name,
+             'volume': volume_name})
+
+        (iscsi_iqns,
+         target_ips,
+         portgroup_id) = self.restclient.get_iscsi_params(self.xml_file_path,
+                                                          connector)
+        LOG.info(_LI('initialize_connection, iscsi_iqn: %(iscsi_iqn)s, '
+                     'target_ip: %(target_ip)s, '
+                     'portgroup_id: %(portgroup_id)s.'),
+                 {'iscsi_iqn': iscsi_iqns,
+                  'target_ip': target_ips,
+                  'portgroup_id': portgroup_id},)
+
+        # Create hostgroup if not exist.
+        host_name = connector['host']
+        host_name_before_hash = None
+        if host_name and (len(host_name) > constants.MAX_HOSTNAME_LENGTH):
+            host_name_before_hash = host_name
+            host_name = six.text_type(hash(host_name))
+        host_id = self.restclient.add_host_with_check(host_name,
+                                                      host_name_before_hash)
+
+        # Add initiator to the host.
+        self.restclient.ensure_initiator_added(self.xml_file_path,
+                                               initiator_name,
+                                               host_id)
+        hostgroup_id = self.restclient.add_host_into_hostgroup(host_id)
+
+        # Mapping lungroup and hostgroup to view.
+        lun_id = self.restclient.mapping_hostgroup_and_lungroup(volume_name,
+                                                                hostgroup_id,
+                                                                host_id,
+                                                                portgroup_id)
+
+        hostlun_id = self.restclient.find_host_lun_id(host_id, lun_id)
+
+        LOG.info(_LI("initialize_connection, host lun id is: %s."),
+                 hostlun_id)
+
+        iscsi_conf = huawei_utils.get_iscsi_conf(self.xml_file_path)
+        chapinfo = self.restclient.find_chap_info(iscsi_conf,
+                                                  initiator_name)
+        # Return iSCSI properties.
+        properties = {}
+        properties['target_discovered'] = False
+        properties['volume_id'] = volume['id']
+        multipath = connector.get('multipath', False)
+        hostlun_id = int(hostlun_id)
+        if not multipath:
+            properties['target_portal'] = ('%s:3260' % target_ips[0])
+            properties['target_iqn'] = iscsi_iqns[0]
+            properties['target_lun'] = hostlun_id
+        else:
+            properties['target_iqns'] = [iqn for iqn in iscsi_iqns]
+            properties['target_portals'] = [
+                '%s:3260' % ip for ip in target_ips]
+            properties['target_luns'] = [hostlun_id] * len(target_ips)
+
+        # If use CHAP, return CHAP info.
+        if chapinfo:
+            chap_username, chap_password = chapinfo.split(';')
+            properties['auth_method'] = 'CHAP'
+            properties['auth_username'] = chap_username
+            properties['auth_password'] = chap_password
+
+        LOG.info(_LI("initialize_connection success. Return data: %s."),
+                 properties)
+        return {'driver_volume_type': 'iscsi', 'data': properties}
 
+    @utils.synchronized('huawei', external=True)
     def terminate_connection(self, volume, connector, **kwargs):
-        return HuaweiBaseDriver.terminate_connection_iscsi(self,
-                                                           volume,
-                                                           connector)
+        """Delete map between a volume and a host."""
+        initiator_name = connector['initiator']
+        volume_name = huawei_utils.encode_name(volume['id'])
+        lun_id = volume.get('provider_location', None)
+        host_name = connector['host']
+        lungroup_id = None
+
+        LOG.info(_LI(
+            'terminate_connection: volume name: %(volume)s, '
+            'initiator name: %(ini)s, '
+            'lun_id: %(lunid)s.'),
+            {'volume': volume_name,
+             'ini': initiator_name,
+             'lunid': lun_id},)
+
+        iscsi_conf = huawei_utils.get_iscsi_conf(self.xml_file_path)
+        portgroup = None
+        portgroup_id = None
+        view_id = None
+        left_lunnum = -1
+        for ini in iscsi_conf['Initiator']:
+            if ini['Name'] == initiator_name:
+                for key in ini:
+                    if key == 'TargetPortGroup':
+                        portgroup = ini['TargetPortGroup']
+                        break
+
+        if portgroup:
+            portgroup_id = self.restclient.find_tgt_port_group(portgroup)
+        if host_name and (len(host_name) > constants.MAX_HOSTNAME_LENGTH):
+            host_name = six.text_type(hash(host_name))
+        host_id = self.restclient.find_host(host_name)
+        if host_id:
+            mapping_view_name = constants.MAPPING_VIEW_PREFIX + host_id
+            view_id = self.restclient.find_mapping_view(mapping_view_name)
+            if view_id:
+                lungroup_id = self.restclient.find_lungroup_from_map(view_id)
+
+        # Remove lun from lungroup.
+        if lun_id and self.restclient.check_lun_exist(lun_id):
+            if lungroup_id:
+                lungroup_ids = self.restclient.get_lungroupids_by_lunid(lun_id)
+                if lungroup_id in lungroup_ids:
+                    self.restclient.remove_lun_from_lungroup(lungroup_id,
+                                                             lun_id)
+                else:
+                    LOG.warning(_LW("Lun is not in lungroup. "
+                                    "Lun id: %(lun_id)s. "
+                                    "lungroup id: %(lungroup_id)s."),
+                                {"lun_id": lun_id,
+                                 "lungroup_id": lungroup_id})
+        else:
+            LOG.warning(_LW("Can't find lun on the array."))
+
+        # Remove portgroup from mapping view if no lun left in lungroup.
+        if lungroup_id:
+            left_lunnum = self.restclient.get_lunnum_from_lungroup(lungroup_id)
+
+        if portgroup_id and view_id and (int(left_lunnum) <= 0):
+            if self.restclient.is_portgroup_associated_to_view(view_id,
+                                                               portgroup_id):
+                self.restclient.delete_portgroup_mapping_view(view_id,
+                                                              portgroup_id)
+        if view_id and (int(left_lunnum) <= 0):
+            self.restclient.remove_chap(initiator_name)
+
+            if self.restclient.lungroup_associated(view_id, lungroup_id):
+                self.restclient.delete_lungroup_mapping_view(view_id,
+                                                             lungroup_id)
+            self.restclient.delete_lungroup(lungroup_id)
+            if self.restclient.is_initiator_associated_to_host(initiator_name):
+                self.restclient.remove_iscsi_from_host(initiator_name)
+            hostgroup_name = constants.HOSTGROUP_PREFIX + host_id
+            hostgroup_id = self.restclient.find_hostgroup(hostgroup_name)
+            if hostgroup_id:
+                if self.restclient.hostgroup_associated(view_id, hostgroup_id):
+                    self.restclient.delete_hostgoup_mapping_view(view_id,
+                                                                 hostgroup_id)
+                self.restclient.remove_host_from_hostgroup(hostgroup_id,
+                                                           host_id)
+                self.restclient.delete_hostgroup(hostgroup_id)
+            self.restclient.remove_host(host_id)
+            self.restclient.delete_mapping_view(view_id)
 
 
 class Huawei18000FCDriver(HuaweiBaseDriver, driver.FibreChannelDriver):
@@ -1121,12 +1012,14 @@ class Huawei18000FCDriver(HuaweiBaseDriver, driver.FibreChannelDriver):
                 SmartX support
                 Volume migration support
                 Volume retype support
+                FC zone enhancement
     """
 
     VERSION = "1.1.1"
 
     def __init__(self, *args, **kwargs):
         super(Huawei18000FCDriver, self).__init__(*args, **kwargs)
+        self.fcsan_lookup_service = None
 
     def get_volume_stats(self, refresh=False):
         """Get volume status."""
@@ -1138,14 +1031,177 @@ class Huawei18000FCDriver(HuaweiBaseDriver, driver.FibreChannelDriver):
         data['vendor_name'] = 'Huawei'
         return data
 
+    @utils.synchronized('huawei', external=True)
     @fczm_utils.AddFCZone
     def initialize_connection(self, volume, connector):
-        return HuaweiBaseDriver.initialize_connection_fc(self,
-                                                         volume,
-                                                         connector)
+        wwns = connector['wwpns']
+        volume_name = huawei_utils.encode_name(volume['id'])
+        LOG.info(_LI(
+            'initialize_connection, initiator: %(wwpns)s,'
+            ' volume name: %(volume)s.'),
+            {'wwpns': wwns,
+             'volume': volume_name},)
+
+        host_name_before_hash = None
+        host_name = connector['host']
+        if host_name and (len(host_name) > constants.MAX_HOSTNAME_LENGTH):
+            host_name_before_hash = host_name
+            host_name = six.text_type(hash(host_name))
+
+        if not self.fcsan_lookup_service:
+            self.fcsan_lookup_service = fczm_utils.create_lookup_service()
+
+        if self.fcsan_lookup_service:
+            # Use FC switch.
+            host_id = self.restclient.add_host_with_check(
+                host_name, host_name_before_hash)
+            zone_helper = fc_zone_helper.FCZoneHelper(
+                self.fcsan_lookup_service, self.restclient)
+            (tgt_port_wwns, init_targ_map) = (
+                zone_helper.build_ini_targ_map(wwns))
+            for ini in init_targ_map:
+                self.restclient.ensure_fc_initiator_added(ini, host_id)
+        else:
+            # Not use FC switch.
+            host_id = self.restclient.add_host_with_check(
+                host_name, host_name_before_hash)
+            online_wwns_in_host = (
+                self.restclient.get_host_online_fc_initiators(host_id))
+            online_free_wwns = self.restclient.get_online_free_wwns()
+            for wwn in wwns:
+                if (wwn not in online_wwns_in_host
+                        and wwn not in online_free_wwns):
+                    wwns_in_host = (
+                        self.restclient.get_host_fc_initiators(host_id))
+                    iqns_in_host = (
+                        self.restclient.get_host_iscsi_initiators(host_id))
+                    if not wwns_in_host and not iqns_in_host:
+                        self.restclient.remove_host(host_id)
+
+                    msg = (_('Can not add FC initiator to host.'))
+                    LOG.error(msg)
+                    raise exception.VolumeBackendAPIException(data=msg)
+
+            for wwn in wwns:
+                if wwn in online_free_wwns:
+                    self.restclient.add_fc_port_to_host(host_id, wwn)
+
+            (tgt_port_wwns, init_targ_map) = (
+                self.restclient.get_init_targ_map(wwns))
+
+        # Add host into hostgroup.
+        hostgroup_id = self.restclient.add_host_into_hostgroup(host_id)
+        lun_id = self.restclient.mapping_hostgroup_and_lungroup(volume_name,
+                                                                hostgroup_id,
+                                                                host_id)
+        host_lun_id = self.restclient.find_host_lun_id(host_id, lun_id)
+
+        # Return FC properties.
+        info = {'driver_volume_type': 'fibre_channel',
+                'data': {'target_lun': int(host_lun_id),
+                         'target_discovered': True,
+                         'target_wwn': tgt_port_wwns,
+                         'volume_id': volume['id'],
+                         'initiator_target_map': init_targ_map}, }
+
+        LOG.info(_LI("initialize_connection, return data is: %s."),
+                 info)
+
+        return info
 
+    @utils.synchronized('huawei', external=True)
     @fczm_utils.RemoveFCZone
     def terminate_connection(self, volume, connector, **kwargs):
-        return HuaweiBaseDriver.terminate_connection_fc(self,
-                                                        volume,
-                                                        connector)
+        """Delete map between a volume and a host."""
+        wwns = connector['wwpns']
+        volume_name = huawei_utils.encode_name(volume['id'])
+        lun_id = volume.get('provider_location', None)
+        host_name = connector['host']
+        left_lunnum = -1
+        lungroup_id = None
+        view_id = None
+        LOG.info(_LI('terminate_connection: volume name: %(volume)s, '
+                     'wwpns: %(wwns)s, '
+                     'lun_id: %(lunid)s.'),
+                 {'volume': volume_name,
+                  'wwns': wwns,
+                  'lunid': lun_id},)
+
+        if host_name and len(host_name) > constants.MAX_HOSTNAME_LENGTH:
+            host_name = six.text_type(hash(host_name))
+        host_id = self.restclient.find_host(host_name)
+        if host_id:
+            mapping_view_name = constants.MAPPING_VIEW_PREFIX + host_id
+            view_id = self.restclient.find_mapping_view(mapping_view_name)
+            if view_id:
+                lungroup_id = self.restclient.find_lungroup_from_map(view_id)
+
+        if lun_id and self.restclient.check_lun_exist(lun_id):
+            if lungroup_id:
+                lungroup_ids = self.restclient.get_lungroupids_by_lunid(lun_id)
+                if lungroup_id in lungroup_ids:
+                    self.restclient.remove_lun_from_lungroup(lungroup_id,
+                                                             lun_id)
+                else:
+                    LOG.warning(_LW("Lun is not in lungroup. "
+                                    "Lun id: %(lun_id)s. "
+                                    "Lungroup id: %(lungroup_id)s."),
+                                {"lun_id": lun_id,
+                                 "lungroup_id": lungroup_id})
+        else:
+            LOG.warning(_LW("Can't find lun on the array."))
+        if lungroup_id:
+            left_lunnum = self.restclient.get_lunnum_from_lungroup(lungroup_id)
+        if int(left_lunnum) > 0:
+            info = {'driver_volume_type': 'fibre_channel',
+                    'data': {}}
+        else:
+            if not self.fcsan_lookup_service:
+                self.fcsan_lookup_service = fczm_utils.create_lookup_service()
+
+            if self.fcsan_lookup_service:
+                zone_helper = fc_zone_helper.FCZoneHelper(
+                    self.fcsan_lookup_service, self.restclient)
+
+                (tgt_port_wwns, init_targ_map) = (
+                    zone_helper.build_ini_targ_map(wwns))
+            else:
+                (tgt_port_wwns, init_targ_map) = (
+                    self.restclient.get_init_targ_map(wwns))
+
+            for wwn in wwns:
+                if self.restclient.is_fc_initiator_associated_to_host(wwn):
+                    self.restclient.remove_fc_from_host(wwn)
+            if lungroup_id:
+                if view_id and self.restclient.lungroup_associated(
+                        view_id, lungroup_id):
+                    self.restclient.delete_lungroup_mapping_view(view_id,
+                                                                 lungroup_id)
+                self.restclient.delete_lungroup(lungroup_id)
+
+            if host_id:
+                hostgroup_name = constants.HOSTGROUP_PREFIX + host_id
+                hostgroup_id = self.restclient.find_hostgroup(hostgroup_name)
+                if hostgroup_id:
+                    if view_id and self.restclient.hostgroup_associated(
+                            view_id, hostgroup_id):
+                        self.restclient.delete_hostgoup_mapping_view(
+                            view_id, hostgroup_id)
+                    self.restclient.remove_host_from_hostgroup(
+                        hostgroup_id, host_id)
+                    self.restclient.delete_hostgroup(hostgroup_id)
+
+                if not self.restclient.check_fc_initiators_exist_in_host(
+                        host_id):
+                    self.restclient.remove_host(host_id)
+
+            if view_id:
+                self.restclient.delete_mapping_view(view_id)
+
+            info = {'driver_volume_type': 'fibre_channel',
+                    'data': {'target_wwn': tgt_port_wwns,
+                             'initiator_target_map': init_targ_map}}
+        LOG.info(_LI("terminate_connection, return data is: %s."),
+                 info)
+
+        return info
index f601c1d3a14e4bbf893fd4387ebf6c4cb14ebcdb..79b270cbf1596db51eeb473bfd5f5e607308934f 100644 (file)
@@ -108,8 +108,9 @@ def _get_opts_from_specs(opts_capability, opts_value, specs):
         if key:
             key = key.lower()
 
-        if ((not scope or scope == 'capabilities') and
-           key in opts_capability):
+        if ((not scope or scope == 'capabilities')
+                and key in opts_capability):
+
             words = value.split()
 
             if not (words and len(words) == 2 and words[0] == '<is>'):
@@ -117,8 +118,6 @@ def _get_opts_from_specs(opts_capability, opts_value, specs):
                               "capabilities:%s='<is> True' or "
                               "'<is> true'."), key)
             else:
-                # Get the second value(true/True) of the Extra specs
-                # value(<is> true/<is> True)
                 opts[key] = words[1].lower()
 
         if (scope in opts_capability) and (key in opts_value):
@@ -298,8 +297,8 @@ def get_lun_conf_params(xml_file_path):
         lunsetinfo['MirrorSwitch'] = mirrorswitch.strip()
 
     prefetch = root.find('LUN/Prefetch')
-    fetchtype = prefetch.attrib['Type']
     if prefetch is not None and prefetch.attrib['Type']:
+        fetchtype = prefetch.attrib['Type']
         if fetchtype in ['0', '1', '2', '3']:
             lunsetinfo['PrefetchType'] = fetchtype.strip()
             typevalue = prefetch.attrib['Value'].strip()
@@ -435,17 +434,17 @@ def wait_for_condition(xml_file_path, func, interval, timeout=None):
 
 def get_login_info(xml_file_path):
     """Get login IP, user name and password from config file."""
-    logininfo = {}
+    login_info = {}
     root = parse_xml_file(xml_file_path)
 
-    logininfo['RestURL'] = root.findtext('Storage/RestURL').strip()
+    login_info['RestURL'] = root.findtext('Storage/RestURL').strip()
 
     for key in ['UserName', 'UserPassword']:
         node = root.find('Storage/%s' % key)
         node_text = node.text
-        logininfo[key] = node_text
+        login_info[key] = node_text
 
-    return logininfo
+    return login_info
 
 
 def _change_file_mode(filepath):
index 812a4d57972b19b367b424efda6ec97f74d7125e..d77683996539ae79851a72563b00ffad18074982 100644 (file)
@@ -117,8 +117,7 @@ class RestClient(object):
         raise exception.VolumeBackendAPIException(data=msg)
 
     def _assert_rest_result(self, result, err_str):
-        error_code = result['error']['code']
-        if error_code != 0:
+        if result['error']['code'] != 0:
             msg = (_('%(err)s\nresult: %(res)s.') % {'err': err_str,
                                                      'res': result})
             LOG.error(msg)
@@ -153,9 +152,9 @@ class RestClient(object):
         return True
 
     def delete_lun(self, lun_id):
-        lun_group_id = self.get_lungroupid_by_lunid(lun_id)
-        if lun_group_id:
-            self.remove_lun_from_lungroup(lun_group_id, lun_id)
+        lun_group_ids = self.get_lungroupids_by_lunid(lun_id)
+        if lun_group_ids and len(lun_group_ids) == 1:
+            self.remove_lun_from_lungroup(lun_group_ids[0], lun_id)
 
         url = self.url + "/lun/" + lun_id
         data = json.dumps({"TYPE": "11",
@@ -297,7 +296,7 @@ class RestClient(object):
         If hostgroup doesn't exist, create one.
         """
         hostgroup_name = constants.HOSTGROUP_PREFIX + host_id
-        hostgroup_id = self._create_hostgroup_with_check(hostgroup_name)
+        hostgroup_id = self.create_hostgroup_with_check(hostgroup_name)
         is_associated = self._is_host_associate_to_hostgroup(hostgroup_id,
                                                              host_id)
         if not is_associated:
@@ -334,9 +333,10 @@ class RestClient(object):
         result = self.call(url, None, "GET")
         self._assert_rest_result(result, _('Check portgroup associate error.'))
 
-        if self._get_id_from_result(result, view_id, 'ID'):
-            return True
-
+        if 'data' in result:
+            for item in result['data']:
+                if view_id == item['ID']:
+                    return True
         return False
 
     def mapping_hostgroup_and_lungroup(self, volume_name, hostgroup_id,
@@ -426,12 +426,12 @@ class RestClient(object):
 
         return self._get_id_from_result(result, lungroup_name, 'NAME')
 
-    def _create_hostgroup_with_check(self, hostgroup_name):
+    def create_hostgroup_with_check(self, hostgroup_name):
         """Check if host exists on the array, or create it."""
         hostgroup_id = self.find_hostgroup(hostgroup_name)
         if hostgroup_id:
             LOG.info(_LI(
-                '_create_hostgroup_with_check. '
+                'create_hostgroup_with_check. '
                 'hostgroup name: %(name)s, '
                 'hostgroup id: %(id)s'),
                 {'name': hostgroup_name,
@@ -455,7 +455,7 @@ class RestClient(object):
                 raise exception.VolumeBackendAPIException(data=err_msg)
 
         LOG.info(_LI(
-            '_create_hostgroup_with_check. '
+            'create_hostgroup_with_check. '
             'Create hostgroup success. '
             'hostgroup name: %(name)s, '
             'hostgroup id: %(id)s'),
@@ -500,8 +500,10 @@ class RestClient(object):
         result = self.call(url, None, "GET")
         self._assert_rest_result(result, _('Check lungroup associate error.'))
 
-        if self._get_id_from_result(result, view_id, 'ID'):
-            return True
+        if 'data' in result:
+            for item in result['data']:
+                if view_id == item['ID']:
+                    return True
         return False
 
     def hostgroup_associated(self, view_id, hostgroup_id):
@@ -511,8 +513,10 @@ class RestClient(object):
         result = self.call(url, None, "GET")
         self._assert_rest_result(result, _('Check hostgroup associate error.'))
 
-        if self._get_id_from_result(result, view_id, 'ID'):
-            return True
+        if 'data' in result:
+            for item in result['data']:
+                if view_id == item['ID']:
+                    return True
         return False
 
     def find_host_lun_id(self, host_id, lun_id):
@@ -535,14 +539,14 @@ class RestClient(object):
                         raise
         return host_lun_id
 
-    def find_host(self, hostname):
+    def find_host(self, host_name):
         """Get the given host ID."""
         url = self.url + "/host?range=[0-65535]"
         data = json.dumps({"TYPE": "21"})
         result = self.call(url, data, "GET")
         self._assert_rest_result(result, _('Find host in hostgroup error.'))
 
-        return self._get_id_from_result(result, hostname, 'NAME')
+        return self._get_id_from_result(result, host_name, 'NAME')
 
     def add_host_with_check(self, host_name, host_name_before_hash):
         host_id = self.find_host(host_name)
@@ -602,8 +606,11 @@ class RestClient(object):
         result = self.call(url, None, "GET")
         self._assert_rest_result(result, _('Check hostgroup associate error.'))
 
-        if self._get_id_from_result(result, host_id, 'ID'):
-            return True
+        if 'data' in result:
+            for item in result['data']:
+                if host_id == item['ID']:
+                    return True
+
         return False
 
     def _is_lun_associated_to_lungroup(self, lungroup_id, lun_id):
@@ -911,13 +918,13 @@ class RestClient(object):
 
     def _get_capacity(self, pool_name, result):
         """Get free capacity and total capacity of the pool."""
-        poolinfo = self.find_pool_info(pool_name, result)
+        pool_info = self.find_pool_info(pool_name, result)
         pool_capacity = {'total_capacity': 0.0,
                          'free_capacity': 0.0}
 
-        if poolinfo:
-            total = int(poolinfo['TOTALCAPACITY']) / constants.CAPACITY_UNIT
-            free = int(poolinfo['CAPACITY']) / constants.CAPACITY_UNIT
+        if pool_info:
+            total = float(pool_info['TOTALCAPACITY']) / constants.CAPACITY_UNIT
+            free = float(pool_info['CAPACITY']) / constants.CAPACITY_UNIT
             pool_capacity['total_capacity'] = total
             pool_capacity['free_capacity'] = free
 
@@ -947,8 +954,23 @@ class RestClient(object):
         result = self.call(url, None, "DELETE")
         self._assert_rest_result(result, _('Delete LUNcopy error.'))
 
-    def get_connected_free_wwns(self):
-        """Get free connected FC port WWNs.
+    def get_init_targ_map(self, wwns):
+        init_targ_map = {}
+        tgt_port_wwns = []
+        for wwn in wwns:
+            tgtwwpns = self.get_fc_target_wwpns(wwn)
+            if not tgtwwpns:
+                continue
+
+            init_targ_map[wwn] = tgtwwpns
+            for tgtwwpn in tgtwwpns:
+                if tgtwwpn not in tgt_port_wwns:
+                    tgt_port_wwns.append(tgtwwpn)
+
+        return (tgt_port_wwns, init_targ_map)
+
+    def get_online_free_wwns(self):
+        """Get online free WWNs.
 
         If no new ports connected, return an empty list.
         """
@@ -1032,12 +1054,11 @@ class RestClient(object):
         msg = _('Get FC target wwpn error.')
         self._assert_rest_result(result, msg)
 
-        fc_wwpns = None
-        if 'data' in result:
+        fc_wwpns = []
+        if "data" in result:
             for item in result['data']:
                 if wwn == item['INITIATOR_PORT_WWN']:
-                    fc_wwpns = item['TARGET_PORT_WWN']
-                    break
+                    fc_wwpns.append(item['TARGET_PORT_WWN'])
 
         return fc_wwpns
 
@@ -1309,21 +1330,21 @@ class RestClient(object):
 
         return result['data']['IOCLASSID']
 
-    def get_lungroupid_by_lunid(self, lun_id):
-        """Get lungroup id by lun id."""
+    def get_lungroupids_by_lunid(self, lun_id):
+        """Get lungroup ids by lun id."""
+
         url = self.url + ("/lungroup/associate?TYPE=256"
                           "&ASSOCIATEOBJTYPE=11&ASSOCIATEOBJID=%s" % lun_id)
 
         result = self.call(url, None, "GET")
         self._assert_rest_result(result, _('Get lungroup id by lun id error.'))
 
-        lun_group_id = None
-        # Lun only in one lungroup.
+        lungroup_ids = []
         if 'data' in result:
             for item in result['data']:
-                lun_group_id = item['ID']
+                lungroup_ids.append(item['ID'])
 
-        return lun_group_id
+        return lungroup_ids
 
     def get_lun_info(self, lun_id):
         url = self.url + "/lun/" + lun_id
@@ -1382,8 +1403,6 @@ class RestClient(object):
 
         if 'data' in result:
             for item in result['data']:
-                LOG.debug('get_partition_id_by_name item %(item)s.',
-                          {'item': item})
                 if name == item['NAME']:
                     return item['ID']
 
@@ -1527,6 +1546,49 @@ class RestClient(object):
         result = self.call(url, data, "PUT")
         self._assert_rest_result(result, _('Remove iscsi from host error.'))
 
+    def get_host_online_fc_initiators(self, host_id):
+        url = self.url + "/fc_initiator"
+        data = json.dumps({'PARENTTYPE': 21,
+                           'PARENTID': host_id})
+        result = self.call(url, data, "GET")
+
+        initiators = []
+        if 'data' in result:
+            for item in result['data']:
+                if (('PARENTID' in item) and (item['PARENTID'] == host_id)
+                   and (item['RUNNINGSTATUS'] == constants.FC_INIT_ONLINE)):
+                    initiators.append(item['ID'])
+
+        return initiators
+
+    def get_host_fc_initiators(self, host_id):
+        url = self.url + "/fc_initiator"
+        data = json.dumps({'PARENTTYPE': 21,
+                           'PARENTID': host_id})
+        result = self.call(url, data, "GET")
+
+        initiators = []
+        if 'data' in result:
+            for item in result['data']:
+                if (('PARENTID' in item) and (item['PARENTID'] == host_id)):
+                    initiators.append(item['ID'])
+
+        return initiators
+
+    def get_host_iscsi_initiators(self, host_id):
+        url = self.url + "/iscsi_initiator"
+        data = json.dumps({'PARENTTYPE': 21,
+                           'PARENTID': host_id})
+        result = self.call(url, data, "GET")
+
+        initiators = []
+        if 'data' in result:
+            for item in result['data']:
+                if (('PARENTID' in item) and (item['PARENTID'] == host_id)):
+                    initiators.append(item['ID'])
+
+        return initiators
+
     def rename_lun(self, lun_id, new_name):
         url = self.url + "/lun/" + lun_id
         data = json.dumps({"NAME": new_name})
@@ -1534,3 +1596,79 @@ class RestClient(object):
         msg = _('Rename lun on array error.')
         self._assert_rest_result(result, msg)
         self._assert_data_in_result(result, msg)
+
+    def is_fc_initiator_associated_to_host(self, ininame):
+        """Check whether the initiator is associated to the host."""
+        url = self.url + '/fc_initiator?range=[0-256]'
+        result = self.call(url, None, "GET")
+        self._assert_rest_result(result,
+                                 'Check initiator associated to host error.')
+
+        if "data" in result:
+            for item in result['data']:
+                if item['ID'] == ininame and item['ISFREE'] != "true":
+                    return True
+        return False
+
+    def remove_fc_from_host(self, initiator):
+        url = self.url + '/fc_initiator/remove_fc_from_host'
+        data = json.dumps({"TYPE": '223',
+                           "ID": initiator})
+        result = self.call(url, data, "PUT")
+        self._assert_rest_result(result, _('Remove fc from host error.'))
+
+    def check_fc_initiators_exist_in_host(self, host_id):
+        url = self.url + '/fc_initiator?range=[0-256]'
+        data = json.dumps({"PARENTID": host_id})
+        result = self.call(url, data, "GET")
+        self._assert_rest_result(result, _('Get host initiators info failed.'))
+        if 'data' in result:
+            return True
+
+        return False
+
+    def _fc_initiator_is_added_to_array(self, ininame):
+        """Check whether the fc initiator is already added on the array."""
+        url = self.url + '/fc_initiator/' + ininame
+        data = json.dumps({"TYPE": '223',
+                           "ID": ininame})
+        result = self.call(url, data, "GET")
+        error_code = result['error']['code']
+        if error_code != 0:
+            return False
+
+        return True
+
+    def _add_fc_initiator_to_array(self, ininame):
+        """Add a fc initiator to storage device."""
+        url = self.url + '/fc_initiator/'
+        data = json.dumps({"TYPE": '223',
+                           "ID": ininame})
+        result = self.call(url, data)
+        self._assert_rest_result(result, _('Add fc initiator to array error.'))
+
+    def ensure_fc_initiator_added(self, initiator_name, host_id):
+        added = self._fc_initiator_is_added_to_array(initiator_name)
+        if not added:
+            self._add_fc_initiator_to_array(initiator_name)
+        # Just add, no need to check whether have been added.
+        self.add_fc_port_to_host(host_id, initiator_name)
+
+    def get_fc_ports_on_array(self):
+        url = self.url + '/fc_port'
+        result = self.call(url, None, "GET")
+        msg = _('Get FC ports from array error.')
+        self._assert_rest_result(result, msg)
+
+        return result['data']
+
+    def get_fc_ports_from_contr(self, contr):
+        port_list_from_contr = []
+        location = []
+        data = self.get_fc_ports_on_array()
+        for item in data:
+            location = item['PARENTID'].split('.')
+            if (location[0][1] == contr) and (item['RUNNINGSTATUS'] ==
+                                              constants.FC_PORT_CONNECTED):
+                port_list_from_contr.append(item['WWN'])
+        return port_list_from_contr