From: Liu Xinguo <295988511@qq.com> Date: Tue, 14 Jul 2015 14:36:57 +0000 (+0800) Subject: Enhance FC zone support for Huawei driver X-Git-Url: https://review.fuel-infra.org/gitweb?a=commitdiff_plain;h=b7ffc2b70c6d2af064e00160dfd7abe2e5376513;p=openstack-build%2Fcinder-build.git Enhance FC zone support for Huawei driver 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 --- diff --git a/cinder/tests/unit/test_huawei_drivers.py b/cinder/tests/unit/test_huawei_drivers.py index 5c1496c99..523377519 100644 --- a/cinder/tests/unit/test_huawei_drivers.py +++ b/cinder/tests/unit/test_huawei_drivers.py @@ -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': ' true', 'smartcache': ' true', 'smartpartition': ' true', - 'thin_provisioning': ' true', - 'thick_provisioning': ' False', + 'thin_provisioning_support': ' true', + 'thick_provisioning_support': ' 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': ' true', 'smartcache': ' true', 'smartpartition': ' true', - 'thin_provisioning': ' true', - 'thick_provisioning': ' False', + 'thin_provisioning_support': ' true', + 'thick_provisioning_support': ' False', 'policy': '2', 'smartcache:cachename': 'cache-test', 'smartpartition:partitionname': 'partition-test'}} @@ -1693,8 +1902,8 @@ class Huawei18000FCDriverTestCase(test.TestCase): new_type = {'extra_specs': {'smarttier': ' true', 'smartcache': ' true', - 'thin_provisioning': ' true', - 'thick_provisioning': ' False', + 'thin_provisioning_support': ' true', + 'thick_provisioning_support': ' 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 diff --git a/cinder/volume/drivers/huawei/constants.py b/cinder/volume/drivers/huawei/constants.py index 5bf2a74c4..4ea9a8c9f 100644 --- a/cinder/volume/drivers/huawei/constants.py +++ b/cinder/volume/drivers/huawei/constants.py @@ -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 index 000000000..64af9875e --- /dev/null +++ b/cinder/volume/drivers/huawei/fc_zone_helper.py @@ -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) diff --git a/cinder/volume/drivers/huawei/huawei_driver.py b/cinder/volume/drivers/huawei/huawei_driver.py index 0b63dcd31..b49db23c0 100644 --- a/cinder/volume/drivers/huawei/huawei_driver.py +++ b/cinder/volume/drivers/huawei/huawei_driver.py @@ -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 diff --git a/cinder/volume/drivers/huawei/huawei_utils.py b/cinder/volume/drivers/huawei/huawei_utils.py index f601c1d3a..79b270cbf 100644 --- a/cinder/volume/drivers/huawei/huawei_utils.py +++ b/cinder/volume/drivers/huawei/huawei_utils.py @@ -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] == ''): @@ -117,8 +118,6 @@ def _get_opts_from_specs(opts_capability, opts_value, specs): "capabilities:%s=' True' or " "' true'."), key) else: - # Get the second value(true/True) of the Extra specs - # value( true/ 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): diff --git a/cinder/volume/drivers/huawei/rest_client.py b/cinder/volume/drivers/huawei/rest_client.py index 812a4d579..d77683996 100644 --- a/cinder/volume/drivers/huawei/rest_client.py +++ b/cinder/volume/drivers/huawei/rest_client.py @@ -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