From: chenzongliang Date: Thu, 4 Jun 2015 02:43:07 +0000 (+0800) Subject: Refactor Huawei Volume driver X-Git-Url: https://review.fuel-infra.org/gitweb?a=commitdiff_plain;h=c9bb99b52f2805acd86d56d156eec62690b9ff48;p=openstack-build%2Fcinder-build.git Refactor Huawei Volume driver This patch attempts to refactor Huawei volume driver in liberty. We add a base driver to implement the basic functions. The sub-class will inherit from the base driver according to different storages. The following changes were made in this refactor: 1. Abstract a base class named HuaweiBaseDriver to make Huawei driver more universal. You can find it in the huawei_driver.py. 2. Put all static variables into the constants.py. 3. Rename rest_common.py to rest_client.py. rest_client.py stores the relevant methods implemented for Huawei driver. 4. Migrate some public methods from rest_client.py to huawei_utils.py, such as parse_xml_file(), _get_volume_type() and so on. 5. This refactor only involves structural adjustment and does not involve functional changes. Change-Id: I768889e2577a4d975397218eb31e89b42e08e04f Implements: blueprint refactor-huawei-volume-driver --- diff --git a/cinder/tests/unit/test_huawei_18000.py b/cinder/tests/unit/test_huawei_18000.py deleted file mode 100644 index 02b7a82fb..000000000 --- a/cinder/tests/unit/test_huawei_18000.py +++ /dev/null @@ -1,1015 +0,0 @@ -# Copyright (c) 2013 - 2014 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. -""" Tests for huawei 18000 storage.""" -import json -import os -import shutil -import tempfile -import time -from xml.dom import minidom - -import mock - -from cinder import exception -from cinder import test -from cinder.volume import configuration as conf -from cinder.volume.drivers.huawei import huawei_18000 -from cinder.volume.drivers.huawei import rest_common - - -test_volume = {'name': 'volume-21ec7341-9256-497b-97d9-ef48edcf0635', - 'size': 2, - 'volume_name': 'vol1', - 'id': '21ec7341-9256-497b-97d9-ef48edcf0635', - 'volume_id': '21ec7341-9256-497b-97d9-ef48edcf0635', - 'provider_auth': None, - 'project_id': 'project', - 'display_name': 'vol1', - 'display_description': 'test volume', - 'volume_type_id': None, - 'provider_location': '11'} - -test_snap = {'name': 'volume-21ec7341-9256-497b-97d9-ef48edcf0635', - 'size': 1, - 'volume_name': 'vol1', - 'id': '21ec7341-9256-497b-97d9-ef48edcf0635', - 'volume_id': '21ec7341-9256-497b-97d9-ef48edcf0635', - 'provider_auth': None, - 'project_id': 'project', - 'display_name': 'vol1', - 'display_description': 'test volume', - 'volume_type_id': None, - 'provider_location': '11'} - -FakeConnector = {'initiator': 'iqn.1993-08.debian:01:ec2bff7ac3a3', - 'wwpns': ['10000090fa0d6754'], - 'wwnns': ['10000090fa0d6755'], - 'host': 'ubuntuc'} - - -def find_data(method): - if method is None: - data = """{"error":{"code":0}, - "data":{"ID":"1", - "NAME":"5mFHcBv4RkCcD+JyrWc0SA"}}""" - if method == 'GET': - data = """{"error":{"code":0}, - "data":[{"ID":"1", - "NAME":"IexzQZJWSXuX2e9I7c8GNQ"}]}""" - return data - - -def find_data_lun(method): - if method == 'GET': - data = """{"error":{"code":0}, - "data":{"ID":"1", - "NAME":"IexzQZJWSXuX2e9I7c8GNQ", - "HEALTHSTATUS":"1", - "RUNNINGSTATUS":"27"}}""" - return data - - -def find_data_lungroup(method): - if method is None: - data = '{"error":{"code":0},\ - "data":{"NAME":"5mFHcBv4RkCcD+JyrWc0SA",\ - "DESCRIPTION":"5mFHcBv4RkCcD+JyrWc0SA",\ - "ID":"11",\ - "TYPE":256}}' - - if method == "GET": - data = """{"error":{"code":0}, - "data":[{ - "NAME":"OpenStack_LunGroup_1", - "DESCRIPTION":"5mFHcBv4RkCcD+JyrWc0SA", - "ID":"11", - "TYPE":256}]}""" - - if method == "DELETE": - data = """{"error":{"code":0}, - "data":[{ - "NAME":"IexzQZJWSXuX2e9I7c8GNQ", - "DESCRIPTION":"5mFHcBv4RkCcD+JyrWc0SA", - "ID":"11", - "TYPE":256}]}""" - return data - - -def find_data_hostgroup(method): - if method is None: - data = """{"error":{"code":0},"data":{ - "NAME":"ubuntuc", - "DESCRIPTION":"", - "ID":"0", - "TYPE":14}}""" - - if method == "GET": - data = """{"error":{"code":0},"data":[{ - "NAME":"ubuntuc", - "DESCRIPTION":"", - "ID":"0","TYPE":14}]}""" - return data - - -def Fake_sleep(time): - pass - - -def find_data_mappingview(method, other_flag): - if method is None: - data = """{"error":{"code":0},"data": - {"WORKMODE":"255","HEALTHSTATUS":"1", - "NAME":"mOWtSXnaQKi3hpB3tdFRIQ", - "RUNNINGSTATUS":"27","DESCRIPTION":"", - "ENABLEINBANDCOMMAND":"true", - "ID":"1","INBANDLUNWWN":"", - "TYPE":245}} - """ - - if method == "GET": - if other_flag: - data = """{"error":{"code":0},"data":[ - {"WORKMODE":"255","HEALTHSTATUS":"1", - "NAME":"mOWtSXnaQKi3hpB3tdFRIQ", - "RUNNINGSTATUS":"27","DESCRIPTION":"", - "ENABLEINBANDCOMMAND":"true","ID":"1", - "INBANDLUNWWN":"","TYPE":245}, - {"WORKMODE":"255","HEALTHSTATUS":"1", - "NAME":"YheUoRwbSX2BxN767nvLSw", - "RUNNINGSTATUS":"27","DESCRIPTION":"", - "ENABLEINBANDCOMMAND":"true", - "ID":"2","INBANDLUNWWN":"", - "TYPE":245}]} - """ - else: - data = """{"error":{"code":0},"data":[ - {"WORKMODE":"255","HEALTHSTATUS":"1", - "NAME":"IexzQZJWSXuX2e9I7c8GNQ", - "RUNNINGSTATUS":"27","DESCRIPTION":"", - "ENABLEINBANDCOMMAND":"true","ID":"1", - "INBANDLUNWWN":"","TYPE":245}, - {"WORKMODE":"255","HEALTHSTATUS":"1", - "NAME":"YheUoRwbSX2BxN767nvLSw", - "RUNNINGSTATUS":"27","DESCRIPTION":"", - "ENABLEINBANDCOMMAND":"true","ID":"2", - "INBANDLUNWWN":"","TYPE":245}]} - """ - return data - - -def find_data_snapshot(method): - if method is None: - data = '{"error":{"code":0},"data":{"ID":11,"NAME":"YheUoRwbSX2BxN7"}}' - if method == "GET": - data = """{"error":{"code":0},"data":[ - {"ID":11,"NAME":"SDFAJSDFLKJ"}, - {"ID":12,"NAME":"SDFAJSDFLKJ2"}]}""" - return data - - -def find_data_host(method): - if method is None: - data = """{"error":{"code":0}, - "data": - {"PARENTTYPE":245, - "NAME":"Default Host", - "DESCRIPTION":"", - "RUNNINGSTATUS":"1", - "IP":"", - "PARENTNAME":"0", - "OPERATIONSYSTEM":"1", - "LOCATION":"", - "HEALTHSTATUS":"1", - "MODEL":"", - "ID":"0", - "PARENTID":"0", - "NETWORKNAME":"", - "TYPE":21}} """ - - if method == "GET": - data = """{"error":{"code":0}, - "data":[ - {"PARENTTYPE":245,"NAME":"ubuntuc", - "DESCRIPTION":"","RUNNINGSTATUS":"1", - "IP":"","PARENTNAME":"", - "OPERATIONSYSTEM":"0","LOCATION":"", - "HEALTHSTATUS":"1","MODEL":"", - "ID":"1","PARENTID":"", - "NETWORKNAME":"","TYPE":21}, - {"PARENTTYPE":245,"NAME":"ubuntu", - "DESCRIPTION":"","RUNNINGSTATUS":"1", - "IP":"","PARENTNAME":"","OPERATIONSYSTEM":"0", - "LOCATION":"","HEALTHSTATUS":"1", - "MODEL":"","ID":"2","PARENTID":"", - "NETWORKNAME":"","TYPE":21}]} """ - return data - - -def find_data_host_associate(method): - if (method is None) or (method == "GET"): - data = '{"error":{"code":0}}' - return data - - -def data_session(url): - if url == "/xx/sessions": - data = """{"error":{"code":0}, - "data":{"username":"admin", - "iBaseToken":"2001031430", - "deviceid":"210235G7J20000000000"}}""" - if url == "sessions": - data = '{"error":{"code":0},"data":{"ID":11}}' - return data - - -def data_lun(url, method): - if url == "lun": - data = find_data(method) - if url == "lun/1": - data = find_data_lun(method) - if url == "lun?range=[0-65535]": - data = find_data(method) - if url == "lungroup?range=[0-8191]": - data = find_data_lungroup(method) - if url == "lungroup": - data = find_data_lungroup(method) - if url == "lungroup/associate": - data = """{"error":{"code":0}, - "data":{"NAME":"5mFHcBv4RkCcD+JyrWc0SA", - "DESCRIPTION":"5mFHcBv4RkCcD+JyrWc0SA", - "ID":"11", - "TYPE":256}}""" - return data - - -def data_host(url, method): - if url == "hostgroup": - data = find_data_hostgroup(method) - if url == "hostgroup?range=[0-8191]": - data = find_data_hostgroup(method) - if url == "host": - data = find_data_host(method) - if url == "host?range=[0-65534]": - data = find_data_host(method) - if url == "host/associate": - data = find_data_host_associate(method) - if url == "host/associate?TYPE=21&ASSOCIATEOBJTYPE=14&ASSOCIATEOBJID=0": - data = find_data_host_associate(method) - return data - - -def find_data_storagepool_snapshot(url, method): - if url == "storagepool": - data = """{"error":{"code":0}, - "data":[{"USERFREECAPACITY":"985661440", - "ID":"0", - "NAME":"OpenStack_Pool", - "USERTOTALCAPACITY":"985661440" - }]}""" - if url == "snapshot": - data = find_data_snapshot(method) - if url == "snapshot/activate": - data = """{"error":{"code":0},"data":[ - {"ID":11,"NAME":"SDFAJSDFLKJ"}, - {"ID":12,"NAME":"SDFAJSDFLKJ"}]}""" - - return data - - -def find_data_luncpy_range_eth_port(url): - if url == "luncopy": - data = """{"error":{"code":0}, - "data":{"COPYSTOPTIME":"-1", - "HEALTHSTATUS":"1", - "NAME":"w1PSNvu6RumcZMmSh4/l+Q==", - "RUNNINGSTATUS":"36", - "DESCRIPTION":"w1PSNvu6RumcZMmSh4/l+Q==", - "ID":"0","LUNCOPYTYPE":"1", - "COPYPROGRESS":"0","COPYSPEED":"2", - "TYPE":219,"COPYSTARTTIME":"-1"}}""" - - if url == "LUNCOPY?range=[0-100000]": - data = """{"error":{"code":0}, - "data":[{"COPYSTOPTIME":"1372209335", - "HEALTHSTATUS":"1", - "NAME":"w1PSNvu6RumcZMmSh4/l+Q==", - "RUNNINGSTATUS":"40", - "DESCRIPTION":"w1PSNvu6RumcZMmSh4/l+Q==", - "ID":"0","LUNCOPYTYPE":"1", - "COPYPROGRESS":"100", - "COPYSPEED":"2", - "TYPE":219, - "COPYSTARTTIME":"1372209329"}]}""" - - if url == "eth_port": - data = """{"error":{"code":0}, - "data":[{"PARENTTYPE":209, - "MACADDRESS":"00:22:a1:0a:79:57", - "ETHNEGOTIATE":"-1","ERRORPACKETS":"0", - "IPV4ADDR":"192.168.100.2", - "IPV6GATEWAY":"","IPV6MASK":"0", - "OVERFLOWEDPACKETS":"0","ISCSINAME":"P0", - "HEALTHSTATUS":"1","ETHDUPLEX":"2", - "ID":"16909568","LOSTPACKETS":"0", - "TYPE":213,"NAME":"P0","INIORTGT":"4", - "RUNNINGSTATUS":"10","IPV4GATEWAY":"", - "BONDNAME":"","STARTTIME":"1371684218", - "SPEED":"1000","ISCSITCPPORT":"0", - "IPV4MASK":"255.255.0.0","IPV6ADDR":"", - "LOGICTYPE":"0","LOCATION":"ENG0.B5.P0", - "MTU":"1500","PARENTID":"1.5"}]}""" - return data - - -class Fake18000Common(rest_common.RestCommon): - - def __init__(self, configuration): - rest_common.RestCommon.__init__(self, configuration) - self.test_normal = True - self.other_flag = True - self.associate_flag = True - self.connect_flag = False - self.delete_flag = False - self.terminateFlag = False - self.deviceid = None - - def _change_file_mode(self, filepath): - pass - - def _parse_volume_type(self, volume): - - poolinfo = self._find_pool_info() - volume_size = self._get_volume_size(poolinfo, volume) - - params = {'LUNType': 0, - 'WriteType': '1', - 'PrefetchType': '3', - 'qos_level': 'Qos-high', - 'StripUnitSize': '64', - 'PrefetchValue': '0', - 'PrefetchTimes': '0', - 'qos': 'OpenStack_Qos_High', - 'MirrorSwitch': '1', - 'tier': 'Tier_high'} - - params['volume_size'] = volume_size - params['pool_id'] = poolinfo['ID'] - return params - - def _get_snapshotid_by_name(self, snapshot_name): - return "11" - - def _get_qosid_by_lunid(self, lunid): - return "" - - def _check_snapshot_exist(self, snapshot_id): - return True - - def fc_initiator_data(self): - data = """{"error":{"code":0},"data":[ - {"HEALTHSTATUS":"1","NAME":"", - "MULTIPATHTYPE":"1","ISFREE":"true", - "RUNNINGSTATUS":"27","ID":"10000090fa0d6754", - "OPERATIONSYSTEM":"255","TYPE":223}, - {"HEALTHSTATUS":"1","NAME":"", - "MULTIPATHTYPE":"1","ISFREE":"true", - "RUNNINGSTATUS":"27","ID":"10000090fa0d6755", - "OPERATIONSYSTEM":"255","TYPE":223}]}""" - return data - - def host_link(self): - data = """{"error":{"code":0}, - "data":[{"PARENTTYPE":21, - "TARGET_ID":"0000000000000000", - "INITIATOR_NODE_WWN":"20000090fa0d6754", - "INITIATOR_TYPE":"223", - "RUNNINGSTATUS":"27", - "PARENTNAME":"ubuntuc", - "INITIATOR_ID":"10000090fa0d6754", - "TARGET_PORT_WWN":"24000022a10a2a39", - "HEALTHSTATUS":"1", - "INITIATOR_PORT_WWN":"10000090fa0d6754", - "ID":"010000090fa0d675-0000000000110400", - "TARGET_NODE_WWN":"21000022a10a2a39", - "PARENTID":"1","CTRL_ID":"0", - "TYPE":255,"TARGET_TYPE":"212"}]}""" - self.connect_flag = True - return data - - def call(self, url=False, data=None, method=None): - - url = url.replace('http://100.115.10.69:8082/deviceManager/rest', '') - url = url.replace('/210235G7J20000000000/', '') - data = None - - if self.test_normal: - if url == "/xx/sessions" or url == "sessions": - data = data_session(url) - - if url == "lun/count?TYPE=11&ASSOCIATEOBJTYPE=256&"\ - "ASSOCIATEOBJID=11": - data = """{"data":{"COUNT":7}, - "error":{"code":0,"description":"0"}}""" - - if url == "lungroup/associate?TYPE=256&ASSOCIATEOBJTYPE=11&"\ - "ASSOCIATEOBJID=11": - data = """{"error":{"code":0}, - "data":[{"ID":11}]}""" - - if url == "storagepool" or url == "snapshot" or url == "snaps"\ - "hot/activate": - data = find_data_storagepool_snapshot(url, method) - - if url == "lungroup" or url == "lungroup/associate"\ - or url == "lun" or url == "lun/1": - data = data_lun(url, method) - - if url == "lun?range=[0-65535]" or url == "lungroup?r"\ - "ange=[0-8191]": - data = data_lun(url, method) - - if url == "lungroup/associate?ID=11"\ - "&ASSOCIATEOBJTYPE=11&ASSOCIATEOBJID=11"\ - or url == "lungroup/associate?ID=12"\ - "&ASSOCIATEOBJTYPE=11&ASSOCIATEOBJID=12": - data = '{"error":{"code":0}}' - self.terminateFlag = True - - if url == "fc_initiator/10000090fa0d6754" or url == "lun/11"\ - or url == "LUNCOPY/0"\ - or url == "mappingview/REMOVE_ASSOCIATE": - data = '{"error":{"code":0}}' - self.delete_flag = True - - if url == "LUNCOPY/start" or url == "mappingview/1"\ - or url == "hostgroup/associate": - data = '{"error":{"code":0}}' - - if url == "MAPPINGVIEW/CREATE_ASSOCIATE" or url == "snapshot/11"\ - or url == "snapshot/stop" or url == "LUNGroup/11": - data = '{"error":{"code":0}}' - self.delete_flag = True - - if url == "luncopy" or url == "eth_port" or url == "LUNC"\ - "OPY?range=[0-100000]": - data = find_data_luncpy_range_eth_port(url) - - if url == "iscsidevicename": - data = """{"error":{"code":0}, - "data":[{"CMO_ISCSI_DEVICE_NAME": -"iqn.2006-08.com.huawei:oceanstor:21000022a10a2a39:iscsinametest"}]}""" - - if url == "hostgroup" or url == "host" or url == "host/associate": - data = data_host(url, method) - - if url == "host/associate?TYPE=21&ASSOCIATEOBJTYPE=14&AS"\ - "SOCIATEOBJID=0": - data = data_host(url, method) - - if url == "hostgroup?range=[0-8191]" or url == "host?ra"\ - "nge=[0-65534]": - data = data_host(url, method) - - if url == "iscsi_initiator/iqn.1993-08.debian:01:ec2bff7ac3a3": - data = """{"error":{"code":0},"data":{ - "ID":"iqn.1993-08.debian:01:ec2bff7ac3a3", - "NAME":"iqn.1993-08.debian:01:ec2bff7ac3a3", - "ISFREE":"True"}}""" - - if url == "iscsi_initiator" or url == "iscsi_initiator/"\ - or url == "iscsi_initiator?range=[0-65535]": - data = '{"error":{"code":0}}' - - if url == "mappingview" or url == "mappingview?range=[0-65535]": - data = find_data_mappingview(method, self.other_flag) - - if (url == ("lun/associate?ID=1&TYPE=11&" - "ASSOCIATEOBJTYPE=21&ASSOCIATEOBJID=0") - or url == ("lun/associate?TYPE=11&ASSOCIATEOBJTYPE=256" - "&ASSOCIATEOBJID=11") - or (url == ("lun/associate?TYPE=11&ASSOCIATEOBJTYPE=256" - "&ASSOCIATEOBJID=12") - and not self.associate_flag)): - data = '{"error":{"code":0},"data":[{"ID":"11"}]}' - if ((url == ("lun/associate?TYPE=11&ASSOCIATEOBJTYPE=256" - "&ASSOCIATEOBJID=12")) - and self.associate_flag): - data = '{"error":{"code":0},"data":[{"ID":"12"}]}' - - if url == "fc_initiator?ISFREE=true&range=[0-1000]": - data = self.fc_initiator_data() - - if url == "host_link?INITIATOR_TYPE=223&INITIATOR_PORT_WWN="\ - "10000090fa0d6754": - data = self.host_link() - - if url == "mappingview/associate?TYPE=245&"\ - "ASSOCIATEOBJTYPE=14&ASSOCIATEOBJID=0"\ - or url == "mappingview/associate?TYPE=245&"\ - "ASSOCIATEOBJTYPE=256&ASSOCIATEOBJID=11": - data = '{"error":{"code":0},"data":[{"ID":11,"NAME":"test"}]}' - - if url == "lun/associate?TYPE=11&"\ - "ASSOCIATEOBJTYPE=21&ASSOCIATEOBJID=1": - data = '{"error":{"code":0}}' - self.connect_flag = True - - if url == "iscsi_tgt_port": - data = '{"data":[{"ETHPORTID":"139267",\ - "ID":"iqn.oceanstor:21004846fb8ca15f::22003:111.111.101.244",\ - "TPGT":"8196","TYPE":249}],\ - "error":{"code":0,"description":"0"}}' - - else: - data = '{"error":{"code":31755596}}' - if (url == "lun/11") and (method == "GET"): - data = """{"error":{"code":0},"data":{"ID":"11", - "IOCLASSID":"11", - "NAME":"5mFHcBv4RkCcD+JyrWc0SA"}}""" - res_json = json.loads(data) - - return res_json - - -class Fake18000Storage(huawei_18000.Huawei18000ISCSIDriver): - """Fake Huawei Storage, Rewrite some methods of HuaweiISCSIDriver.""" - - def __init__(self, configuration): - super(Fake18000Storage, self).__init__(configuration) - self.configuration = configuration - - def do_setup(self): - self.common = Fake18000Common(configuration=self.configuration) - - -class Fake18000FCStorage(huawei_18000.Huawei18000FCDriver): - """Fake Huawei Storage, Rewrite some methods of HuaweiISCSIDriver.""" - def __init__(self, configuration): - super(Fake18000FCStorage, self).__init__(configuration) - self.configuration = configuration - - def do_setup(self): - self.common = Fake18000Common(configuration=self.configuration) - - -class Huawei18000ISCSIDriverTestCase(test.TestCase): - - def setUp(self): - super(Huawei18000ISCSIDriverTestCase, self).setUp() - self.tmp_dir = tempfile.mkdtemp() - self.fake_conf_file = self.tmp_dir + '/cinder_huawei_conf.xml' - self.addCleanup(shutil.rmtree, self.tmp_dir) - self.create_fake_conf_file() - self.addCleanup(os.remove, self.fake_conf_file) - self.configuration = mock.Mock(spec=conf.Configuration) - self.configuration.cinder_huawei_conf_file = self.fake_conf_file - self.stubs.Set(time, 'sleep', Fake_sleep) - driver = Fake18000Storage(configuration=self.configuration) - self.driver = driver - self.driver.do_setup() - self.driver.common.test_normal = True - - def testloginSuccess(self): - deviceid = self.driver.common.login() - self.assertEqual(deviceid, '210235G7J20000000000') - - def testcreatevolumesuccess(self): - self.driver.common.login() - lun_info = self.driver.create_volume(test_volume) - self.assertEqual(lun_info['provider_location'], '1') - self.assertEqual(lun_info['lun_info']['NAME'], - '5mFHcBv4RkCcD+JyrWc0SA') - - def testcreatesnapshotsuccess(self): - self.driver.common.login() - lun_info = self.driver.create_snapshot(test_volume) - self.assertEqual(lun_info['provider_location'], 11) - self.assertEqual(lun_info['lun_info']['NAME'], 'YheUoRwbSX2BxN7') - - def testdeletevolumesuccess(self): - self.driver.common.login() - self.driver.common.delete_flag = False - self.driver.delete_volume(test_volume) - self.assertTrue(self.driver.common.delete_flag) - - def testdeletesnapshotsuccess(self): - self.driver.common.login() - self.driver.common.delete_flag = False - self.driver.delete_snapshot(test_snap) - self.assertTrue(self.driver.common.delete_flag) - - def testcolonevolumesuccess(self): - self.driver.common.login() - lun_info = self.driver.create_cloned_volume(test_volume, - test_volume) - self.assertEqual(lun_info['provider_location'], '1') - self.assertEqual(lun_info['lun_info']['NAME'], - '5mFHcBv4RkCcD+JyrWc0SA') - - def testcreateolumefromsnapsuccess(self): - self.driver.common.login() - lun_info = self.driver.create_volume_from_snapshot(test_volume, - test_volume) - self.assertEqual(lun_info['provider_location'], '1') - self.assertEqual(lun_info['lun_info']['NAME'], - '5mFHcBv4RkCcD+JyrWc0SA') - - def testinitializeconnectionsuccess(self): - self.driver.common.login() - iscsi_properties = self.driver.initialize_connection(test_volume, - FakeConnector) - self.assertEqual(iscsi_properties['data']['target_lun'], 1) - - def testterminateconnectionsuccess(self): - self.driver.common.login() - self.driver.common.terminateFlag = False - self.driver.terminate_connection(test_volume, FakeConnector) - self.assertTrue(self.driver.common.terminateFlag) - - def testinitializeconnectionnoviewsuccess(self): - self.driver.common.login() - self.driver.common.other_flag = False - self.driver.common.connect_flag = False - self.driver.initialize_connection(test_volume, FakeConnector) - self.assertTrue(self.driver.common.connect_flag) - - def testterminateconnectionoviewnsuccess(self): - self.driver.common.login() - self.driver.common.terminateFlag = False - self.driver.terminate_connection(test_volume, FakeConnector) - self.assertTrue(self.driver.common.terminateFlag) - - def testgetvolumestatus(self): - self.driver.common.login() - data = self.driver.get_volume_stats() - self.assertEqual(data['driver_version'], '1.1.0') - - def testloginfail(self): - self.driver.common.test_normal = False - self.assertRaises(exception.CinderException, self.driver.common.login) - - def testcreatesnapshotfail(self): - self.driver.common.login() - self.driver.common.test_normal = False - self.assertRaises(exception.CinderException, - self.driver.create_snapshot, test_volume) - - def testcreatevolumefail(self): - self.driver.common.login() - self.driver.common.test_normal = False - self.assertRaises(exception.CinderException, - self.driver.create_volume, test_volume) - - def testdeletevolumefail(self): - self.driver.common.login() - self.driver.common.test_normal = False - self.assertRaises(exception.CinderException, - self.driver.delete_volume, test_volume) - - def testdeletesnapshotfail(self): - self.driver.common.login() - self.driver.common.test_normal = False - self.assertRaises(exception.CinderException, - self.driver.delete_snapshot, test_volume) - - def testinitializeconnectionfail(self): - self.driver.common.login() - self.driver.common.test_normal = False - self.assertRaises(exception.CinderException, - self.driver.initialize_connection, - test_volume, FakeConnector) - - def testgetdefaulttimeout(self): - result = self.driver.common._get_default_timeout() - self.assertEqual('43200', result) - - def testgetwaitinterval(self): - result = self.driver.common._get_wait_interval('LUNReadyWaitInterval') - self.assertEqual('2', result) - - def test_lun_is_associated_to_lungroup(self): - self.driver.common.login() - self.driver.common._associate_lun_to_lungroup('11', '11') - result = self.driver.common._is_lun_associated_to_lungroup('11', '11') - self.assertTrue(result) - - def test_lun_is_not_associated_to_lun_group(self): - self.driver.common.login() - self.driver.common._associate_lun_to_lungroup('12', '12') - self.driver.common.associate_flag = True - result = self.driver.common._is_lun_associated_to_lungroup('12', '12') - self.assertTrue(result) - self.driver.common._remove_lun_from_lungroup('12', '12') - self.driver.common.associate_flag = False - result = self.driver.common._is_lun_associated_to_lungroup('12', '12') - self.assertFalse(result) - - def create_fake_conf_file(self): - """Create a fake Config file - - Huawei storage customize a XML configuration file, the configuration - file is used to set the Huawei storage custom parameters, therefore, - in the UT test we need to simulate such a configuration file - """ - doc = minidom.Document() - - config = doc.createElement('config') - doc.appendChild(config) - - storage = doc.createElement('Storage') - config.appendChild(storage) - controllerip0 = doc.createElement('ControllerIP0') - controllerip0_text = doc.createTextNode('10.10.10.1') - controllerip0.appendChild(controllerip0_text) - storage.appendChild(controllerip0) - controllerip1 = doc.createElement('ControllerIP1') - controllerip1_text = doc.createTextNode('10.10.10.2') - controllerip1.appendChild(controllerip1_text) - storage.appendChild(controllerip1) - username = doc.createElement('UserName') - username_text = doc.createTextNode('admin') - username.appendChild(username_text) - storage.appendChild(username) - userpassword = doc.createElement('UserPassword') - userpassword_text = doc.createTextNode('Admin@storage') - userpassword.appendChild(userpassword_text) - storage.appendChild(userpassword) - url = doc.createElement('RestURL') - url_text = doc.createTextNode('http://100.115.10.69:8082/' - 'deviceManager/rest/') - url.appendChild(url_text) - storage.appendChild(url) - - lun = doc.createElement('LUN') - config.appendChild(lun) - storagepool = doc.createElement('StoragePool') - pool_text = doc.createTextNode('OpenStack_Pool') - storagepool.appendChild(pool_text) - lun.appendChild(storagepool) - - timeout = doc.createElement('Timeout') - timeout_text = doc.createTextNode('43200') - timeout.appendChild(timeout_text) - lun.appendChild(timeout) - - lun_ready_wait_interval = doc.createElement('LUNReadyWaitInterval') - lun_ready_wait_interval_text = doc.createTextNode('2') - lun_ready_wait_interval.appendChild(lun_ready_wait_interval_text) - lun.appendChild(lun_ready_wait_interval) - - prefetch = doc.createElement('Prefetch') - prefetch.setAttribute('Type', '0') - prefetch.setAttribute('Value', '0') - lun.appendChild(prefetch) - - iscsi = doc.createElement('iSCSI') - config.appendChild(iscsi) - defaulttargetip = doc.createElement('DefaultTargetIP') - defaulttargetip_text = doc.createTextNode('100.115.10.68') - defaulttargetip.appendChild(defaulttargetip_text) - iscsi.appendChild(defaulttargetip) - initiator = doc.createElement('Initiator') - initiator.setAttribute('Name', 'iqn.1993-08.debian:01:ec2bff7ac3a3') - initiator.setAttribute('TargetIP', '192.168.100.2') - iscsi.appendChild(initiator) - - fakefile = open(self.fake_conf_file, 'w') - fakefile.write(doc.toprettyxml(indent='')) - fakefile.close() - - -class Huawei18000FCDriverTestCase(test.TestCase): - - def setUp(self): - super(Huawei18000FCDriverTestCase, self).setUp() - self.tmp_dir = tempfile.mkdtemp() - self.fake_conf_file = self.tmp_dir + '/cinder_huawei_conf.xml' - self.addCleanup(shutil.rmtree, self.tmp_dir) - self.create_fake_conf_file() - self.addCleanup(os.remove, self.fake_conf_file) - self.configuration = mock.Mock(spec=conf.Configuration) - self.configuration.cinder_huawei_conf_file = self.fake_conf_file - self.stubs.Set(time, 'sleep', Fake_sleep) - driver = Fake18000FCStorage(configuration=self.configuration) - self.driver = driver - self.driver.do_setup() - self.driver.common.test_normal = True - - def testloginSuccess(self): - deviceid = self.driver.common.login() - self.assertEqual(deviceid, '210235G7J20000000000') - - def testcreatevolumesuccess(self): - self.driver.common.login() - lun_info = self.driver.create_volume(test_volume) - self.assertEqual(lun_info['provider_location'], '1') - self.assertEqual(lun_info['lun_info']['NAME'], - '5mFHcBv4RkCcD+JyrWc0SA') - - def testcreatesnapshotsuccess(self): - self.driver.common.login() - lun_info = self.driver.create_snapshot(test_volume) - self.assertEqual(lun_info['provider_location'], 11) - self.assertEqual(lun_info['lun_info']['NAME'], 'YheUoRwbSX2BxN7') - - def testdeletevolumesuccess(self): - self.driver.common.login() - self.driver.common.delete_flag = False - self.driver.delete_volume(test_volume) - self.assertTrue(self.driver.common.delete_flag) - - def testdeletesnapshotsuccess(self): - self.driver.common.login() - self.driver.common.delete_flag = False - self.driver.delete_snapshot(test_snap) - self.assertTrue(self.driver.common.delete_flag) - - def testcolonevolumesuccess(self): - self.driver.common.login() - lun_info = self.driver.create_cloned_volume(test_volume, - test_volume) - self.assertEqual(lun_info['provider_location'], '1') - self.assertEqual(lun_info['lun_info']['NAME'], - '5mFHcBv4RkCcD+JyrWc0SA') - - def testcreateolumefromsnapsuccess(self): - self.driver.common.login() - volumeid = self.driver.create_volume_from_snapshot(test_volume, - test_volume) - self.assertEqual(volumeid['provider_location'], '1') - - def testinitializeconnectionsuccess(self): - self.driver.common.login() - properties = self.driver.initialize_connection(test_volume, - FakeConnector) - self.assertEqual(properties['data']['target_lun'], 1) - - def testterminateconnectionsuccess(self): - self.driver.common.login() - self.driver.common.terminateFlag = False - self.driver.terminate_connection(test_volume, FakeConnector) - self.assertTrue(self.driver.common.terminateFlag) - - def testinitializeconnectionnoviewsuccess(self): - self.driver.common.login() - self.driver.common.other_flag = False - self.driver.common.connect_flag = False - self.driver.initialize_connection(test_volume, FakeConnector) - self.assertTrue(self.driver.common.connect_flag) - - def testterminateconnectionoviewnsuccess(self): - self.driver.common.login() - self.driver.common.terminateFlag = False - self.driver.terminate_connection(test_volume, FakeConnector) - self.assertTrue(self.driver.common.terminateFlag) - - def testgetvolumestatus(self): - self.driver.common.login() - data = self.driver.get_volume_stats() - self.assertEqual(data['driver_version'], '1.1.0') - - def testloginfail(self): - self.driver.common.test_normal = False - self.assertRaises(exception.CinderException, - self.driver.common.login) - - def testcreatesnapshotfail(self): - self.driver.common.login() - self.driver.common.test_normal = False - self.assertRaises(exception.CinderException, - self.driver.create_snapshot, test_volume) - - def testcreatevolumefail(self): - self.driver.common.login() - self.driver.common.test_normal = False - self.assertRaises(exception.CinderException, - self.driver.create_volume, test_volume) - - def testdeletevolumefail(self): - self.driver.common.login() - self.driver.common.test_normal = False - self.assertRaises(exception.CinderException, - self.driver.delete_volume, test_volume) - - def testdeletesnapshotfail(self): - self.driver.common.login() - self.driver.common.test_normal = False - self.assertRaises(exception.CinderException, - self.driver.delete_snapshot, test_volume) - - def testinitializeconnectionfail(self): - self.driver.common.login() - self.driver.common.test_normal = False - self.assertRaises(exception.CinderException, - self.driver.initialize_connection, - test_volume, FakeConnector) - - def testgetdefaulttimeout(self): - result = self.driver.common._get_default_timeout() - self.assertEqual('43200', result) - - def testgetwaitinterval(self): - result = self.driver.common._get_wait_interval('LUNReadyWaitInterval') - self.assertEqual('2', result) - - def test_lun_is_associated_to_lungroup(self): - self.driver.common.login() - self.driver.common._associate_lun_to_lungroup('11', '11') - result = self.driver.common._is_lun_associated_to_lungroup('11', '11') - self.assertTrue(result) - - def test_lun_is_not_associated_to_lun_group(self): - self.driver.common.login() - self.driver.common._associate_lun_to_lungroup('12', '12') - self.driver.common.associate_flag = True - result = self.driver.common._is_lun_associated_to_lungroup('12', '12') - self.assertTrue(result) - self.driver.common._remove_lun_from_lungroup('12', '12') - self.driver.common.associate_flag = False - result = self.driver.common._is_lun_associated_to_lungroup('12', '12') - self.assertFalse(result) - - def create_fake_conf_file(self): - """Create a fake Config file - - Huawei storage customize a XML configuration file, the configuration - file is used to set the Huawei storage custom parameters, therefore, - in the UT test we need to simulate such a configuration file - """ - doc = minidom.Document() - - config = doc.createElement('config') - doc.appendChild(config) - - storage = doc.createElement('Storage') - config.appendChild(storage) - controllerip0 = doc.createElement('ControllerIP0') - controllerip0_text = doc.createTextNode('10.10.10.1') - controllerip0.appendChild(controllerip0_text) - storage.appendChild(controllerip0) - controllerip1 = doc.createElement('ControllerIP1') - controllerip1_text = doc.createTextNode('10.10.10.2') - controllerip1.appendChild(controllerip1_text) - storage.appendChild(controllerip1) - username = doc.createElement('UserName') - username_text = doc.createTextNode('admin') - username.appendChild(username_text) - storage.appendChild(username) - userpassword = doc.createElement('UserPassword') - userpassword_text = doc.createTextNode('Admin@storage') - userpassword.appendChild(userpassword_text) - storage.appendChild(userpassword) - url = doc.createElement('RestURL') - url_text = doc.createTextNode('http://100.115.10.69:8082/' - 'deviceManager/rest/') - url.appendChild(url_text) - storage.appendChild(url) - - lun = doc.createElement('LUN') - config.appendChild(lun) - storagepool = doc.createElement('StoragePool') - pool_text = doc.createTextNode('OpenStack_Pool') - storagepool.appendChild(pool_text) - lun.appendChild(storagepool) - - timeout = doc.createElement('Timeout') - timeout_text = doc.createTextNode('43200') - timeout.appendChild(timeout_text) - lun.appendChild(timeout) - - lun_ready_wait_interval = doc.createElement('LUNReadyWaitInterval') - lun_ready_wait_interval_text = doc.createTextNode('2') - lun_ready_wait_interval.appendChild(lun_ready_wait_interval_text) - lun.appendChild(lun_ready_wait_interval) - - prefetch = doc.createElement('Prefetch') - prefetch.setAttribute('Type', '0') - prefetch.setAttribute('Value', '0') - lun.appendChild(prefetch) - - iscsi = doc.createElement('iSCSI') - config.appendChild(iscsi) - defaulttargetip = doc.createElement('DefaultTargetIP') - defaulttargetip_text = doc.createTextNode('100.115.10.68') - defaulttargetip.appendChild(defaulttargetip_text) - iscsi.appendChild(defaulttargetip) - initiator = doc.createElement('Initiator') - initiator.setAttribute('Name', 'iqn.1993-08.debian:01:ec2bff7ac3a3') - initiator.setAttribute('TargetIP', '192.168.100.2') - iscsi.appendChild(initiator) - - fakefile = open(self.fake_conf_file, 'w') - fakefile.write(doc.toprettyxml(indent='')) - fakefile.close() diff --git a/cinder/tests/unit/test_huawei_drivers.py b/cinder/tests/unit/test_huawei_drivers.py new file mode 100644 index 000000000..6c5e40a1a --- /dev/null +++ b/cinder/tests/unit/test_huawei_drivers.py @@ -0,0 +1,1363 @@ +# 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. +"""Tests for huawei 18000 storage.""" +import json +import mock +import os +import shutil +import tempfile +import time +from xml.dom import minidom + +from oslo_log import log as logging + +from cinder import exception +from cinder import test +from cinder.volume import configuration as conf +from cinder.volume.drivers.huawei import huawei_driver +from cinder.volume.drivers.huawei import huawei_utils +from cinder.volume.drivers.huawei import rest_client + +LOG = logging.getLogger(__name__) + +test_volume = {'name': 'volume-21ec7341-9256-497b-97d9-ef48edcf0635', + 'size': 2, + 'volume_name': 'vol1', + 'id': '21ec7341-9256-497b-97d9-ef48edcf0635', + 'volume_id': '21ec7341-9256-497b-97d9-ef48edcf0635', + 'provider_auth': None, + 'project_id': 'project', + 'display_name': 'vol1', + 'display_description': 'test volume', + 'volume_type_id': None, + 'host': 'ubuntu@huawei#OpenStack_Pool', + 'provider_location': '11', + } + +test_snap = {'name': 'volume-21ec7341-9256-497b-97d9-ef48edcf0635', + 'size': 1, + 'volume_name': 'vol1', + 'id': '21ec7341-9256-497b-97d9-ef48edcf0635', + 'volume_id': '21ec7341-9256-497b-97d9-ef48edcf0635', + 'provider_auth': None, + 'project_id': 'project', + 'display_name': 'vol1', + 'display_description': 'test volume', + 'volume_type_id': None, + 'provider_location': '11', + } + +FakeConnector = {'initiator': 'iqn.1993-08.debian:01:ec2bff7ac3a3', + 'wwpns': ['10000090fa0d6754'], + 'wwnns': ['10000090fa0d6755'], + 'host': 'ubuntuc', + } + +# A fake response of success response storage +FAKE_COMMON_SUCCESS_RESPONSE = """ +{ + "error": { + "code": 0 + } +} +""" + +# A fake response of login huawei storage +FAKE_GET_LOGIN_STORAGE_RESPONSE = """ +{ + "error": { + "code": 0 + }, + "data": { + "username": "admin", + "iBaseToken": "2001031430", + "deviceid": "210235G7J20000000000" + } +} +""" + +# A fake response of login out huawei storage +FAKE_LOGIN_OUT_STORAGE_RESPONSE = """ +{ + "error": { + "code": 0 + }, + "data": { + "ID": 11 + } +} +""" + +# A fake response of mock storage pool info +FAKE_STORAGE_POOL_RESPONSE = """ +{ + "error": { + "code": 0 + }, + "data": [{ + "USERFREECAPACITY": "985661440", + "ID": "0", + "NAME": "OpenStack_Pool", + "USERTOTALCAPACITY": "985661440" + }] +} +""" + +# A fake response of lun or lungroup response +FAKE_LUN_INFO_RESPONSE = """ +{ + "error": { + "code": 0 + }, + "data": { + "ID": "1", + "NAME": "5mFHcBv4RkCcD+JyrWc0SA" + } +} +""" + +FAKE_LUN_DELETE_SUCCESS_RESPONSE = """ +{ + "error": { + "code": 0 + }, + "data": { + "ID": "11", + "IOCLASSID": "11", + "NAME": "5mFHcBv4RkCcD+JyrWc0SA", + "RUNNINGSTATUS": "2", + "HEALTHSTATUS": "1", + "RUNNINGSTATUS": "27" + } +} +""" + +FAKE_QUERY_ALL_LUN_RESPONSE = """ +{ + "error": { + "code": 0 + }, + "data": [{ + "ID": "1", + "NAME": "IexzQZJWSXuX2e9I7c8GNQ" + }] +} +""" + +FAKE_LUN_ASSOCIATE_RESPONSE = """ +{ + "error":{ + "code":0 + }, + "data":[{ + "ID":"11" + }] +} +""" + +FAKE_QUERY_LUN_GROUP_INFO_RESPONSE = """ +{ + "error": { + "code":0 + }, + "data":[{ + "NAME":"OpenStack_LunGroup_1", + "DESCRIPTION":"5mFHcBv4RkCcD+JyrWc0SA", + "ID":"11", + "TYPE":256 + }] +} +""" + +FAKE_QUERY_LUN_GROUP_RESPONSE = """ +{ + "error": { + "code":0 + }, + "data":{ + "NAME":"5mFHcBv4RkCcD+JyrWc0SA", + "DESCRIPTION":"5mFHcBv4RkCcD+JyrWc0SA", + "ID":"11", + "TYPE":256 + } +} +""" + +FAKE_QUERY_LUN_GROUP_ASSOCIAT_RESPONSE = """ +{ + "error":{ + "code":0 + }, + "data":{ + "NAME":"5mFHcBv4RkCcD+JyrWc0SA", + "DESCRIPTION":"5mFHcBv4RkCcD+JyrWc0SA", + "ID":"11", + "TYPE":256 + } +} +""" + +FAKE_LUN_COUNT_RESPONSE = """ +{ + "data":{ + "COUNT":"7" + }, + "error":{ + "code":0, + "description":"0" + } +} +""" +# A fake response of snapshot list response +FAKE_SNAPSHOT_LIST_INFO_RESPONSE = """ +{ + "error": { + "code": 0 + }, + "data": [{ + "ID": 11, + "NAME": "wr_LMKAjS7O_VtsEIREGYw" + }, + { + "ID": 12, + "NAME": "SDFAJSDFLKJ" + }] +} +""" + +# A fake response of create snapshot response +FAKE_CREATE_SNAPSHOT_INFO_RESPONSE = """ +{ + "error": { + "code": 0 + }, + "data": { + "ID": 11, + "NAME": "YheUoRwbSX2BxN7" + } +} +""" + +# A fake response of get snapshot response +FAKE_GET_SNAPSHOT_INFO_RESPONSE = """ +{ + "error": { + "code": 0 + }, + "data": { + "ID": 11, + "NAME": "YheUoRwbSX2BxN7" + } +} +""" + +# A fake response of get iscsi response +FAKE_GET_ISCSI_INFO_RESPONSE = """ +{ + "data": [{ + "ETHPORTID": "139267", + "ID": "iqn.oceanstor:21004846fb8ca15f::22003:111.111.101.244", + "TPGT": "8196", + "TYPE": 249 + }], + "error": { + "code": 0, + "description": "0" + } +} +""" + +# A fake response of get eth info response +FAKE_GET_ETH_INFO_RESPONSE = """ +{ + "error": { + "code": 0 + }, + "data": [{ + "PARENTTYPE": 209, + "MACADDRESS": "00:22:a1:0a:79:57", + "ETHNEGOTIATE": "-1", + "ERRORPACKETS": "0", + "IPV4ADDR": "192.168.100.2", + "IPV6GATEWAY": "", + "IPV6MASK": "0", + "OVERFLOWEDPACKETS": "0", + "ISCSINAME": "P0", + "HEALTHSTATUS": "1", + "ETHDUPLEX": "2", + "ID": "16909568", + "LOSTPACKETS": "0", + "TYPE": 213, + "NAME": "P0", + "INIORTGT": "4", + "RUNNINGSTATUS": "10", + "IPV4GATEWAY": "", + "BONDNAME": "", + "STARTTIME": "1371684218", + "SPEED": "1000", + "ISCSITCPPORT": "0", + "IPV4MASK": "255.255.0.0", + "IPV6ADDR": "", + "LOGICTYPE": "0", + "LOCATION": "ENG0.B5.P0", + "MTU": "1500", + "PARENTID": "1.5" + }] +} +""" + +FAKE_GET_ETH_ASSOCIATE_RESPONSE = """ +{ + "error":{ + "code":0 + }, + "data":[{ + "IPV4ADDR":"192.168.100.10", + "HEALTHSTATUS":"1", + "RUNNINGSTATUS":"10" + }] +} +""" +# A fake response of get iscsi device info response +FAKE_GET_ISCSI_DEVICE_RESPONSE = """ +{ + "error": { + "code": 0 + }, + "data": [{ + "CMO_ISCSI_DEVICE_NAME":\ + "iqn.2006-08.com.huawei:oceanstor:21000022a10a2a39:iscsinametest" + }] +} +""" + +# A fake response of get iscsi device info response +FAKE_GET_ALL_HOST_INFO_RESPONSE = """ +{ + "error": { + "code": 0 + }, + "data": [{ + "PARENTTYPE": 245, + "NAME": "ubuntuc", + "DESCRIPTION": "", + "RUNNINGSTATUS": "1", + "IP": "", + "PARENTNAME": "", + "OPERATIONSYSTEM": "0", + "LOCATION": "", + "HEALTHSTATUS": "1", + "MODEL": "", + "ID": "1", + "PARENTID": "", + "NETWORKNAME": "", + "TYPE": 21 + }, + { + "PARENTTYPE": 245, + "NAME": "ubuntu", + "DESCRIPTION": "", + "RUNNINGSTATUS": "1", + "IP": "", + "PARENTNAME": "", + "OPERATIONSYSTEM": "0", + "LOCATION": "", + "HEALTHSTATUS": "1", + "MODEL": "", + "ID": "2", + "PARENTID": "", + "NETWORKNAME": "", + "TYPE": 21 + }] +} +""" + +# A fake response of get host or hostgroup info response +FAKE_GET_ALL_HOST_GROUP_INFO_RESPONSE = """ +{ + "error": { + "code": 0 + }, + "data": [{ + "NAME":"ubuntuc", + "DESCRIPTION":"", + "ID":"0", + "TYPE":14 + }] +} +""" + +FAKE_GET_HOST_GROUP_INFO_RESPONSE = """ +{ + "error": { + "code": 0 + }, + "data":{ + "NAME":"ubuntuc", + "DESCRIPTION":"", + "ID":"0", + "TYPE":14 + } +} +""" + +# A fake response of lun copy info response +FAKE_GET_LUN_COPY_INFO_RESPONSE = """ +{ + "error": { + "code": 0 + }, + "data": { + "COPYSTOPTIME": "-1", + "HEALTHSTATUS": "1", + "NAME": "w1PSNvu6RumcZMmSh4/l+Q==", + "RUNNINGSTATUS": "36", + "DESCRIPTION": "w1PSNvu6RumcZMmSh4/l+Q==", + "ID": "0", + "LUNCOPYTYPE": "1", + "COPYPROGRESS": "0", + "COPYSPEED": "2", + "TYPE": 219, + "COPYSTARTTIME": "-1" + } +} +""" + +# A fake response of lun copy list info response +FAKE_GET_LUN_COPY_LIST_INFO_RESPONSE = """ +{ + "error": { + "code": 0 + }, + "data": [{ + "COPYSTOPTIME": "1372209335", + "HEALTHSTATUS": "1", + "NAME": "w1PSNvu6RumcZMmSh4/l+Q==", + "RUNNINGSTATUS": "40", + "DESCRIPTION": "w1PSNvu6RumcZMmSh4/l+Q==", + "ID": "0", + "LUNCOPYTYPE": "1", + "COPYPROGRESS": "100", + "COPYSPEED": "2", + "TYPE": 219, + "COPYSTARTTIME": "1372209329" + }] +} +""" + +# A fake response of mappingview info response +FAKE_GET_MAPPING_VIEW_INFO_RESPONSE = """ +{ + "error":{ + "code":0 + }, + "data":[{ + "WORKMODE":"255", + "HEALTHSTATUS":"1", + "NAME":"IexzQZJWSXuX2e9I7c8GNQ", + "RUNNINGSTATUS":"27", + "DESCRIPTION":"", + "ENABLEINBANDCOMMAND":"true", + "ID":"1", + "INBANDLUNWWN":"", + "TYPE":245 + }, + { + "WORKMODE":"255", + "HEALTHSTATUS":"1", + "NAME":"YheUoRwbSX2BxN767nvLSw", + "RUNNINGSTATUS":"27", + "DESCRIPTION":"", + "ENABLEINBANDCOMMAND":"true", + "ID":"2", + "INBANDLUNWWN":"", + "TYPE":245 + }] +} +""" + +FAKE_GET_MAPPING_VIEW_RESPONSE = """ +{ + "error":{ + "code":0 + }, + "data":{ + "WORKMODE":"255", + "HEALTHSTATUS":"1", + "NAME":"mOWtSXnaQKi3hpB3tdFRIQ", + "RUNNINGSTATUS":"27", + "DESCRIPTION":"", + "ENABLEINBANDCOMMAND":"true", + "ID":"1", + "INBANDLUNWWN":"", + "TYPE":245 + } +} +""" + +FAKE_FC_INFO_RESPONSE = """ +{ + "error":{ + "code":0 + }, + "data":[{ + "HEALTHSTATUS":"1", + "NAME":"", + "MULTIPATHTYPE":"1", + "ISFREE":"true", + "RUNNINGSTATUS":"27", + "ID":"10000090fa0d6754", + "OPERATIONSYSTEM":"255", + "TYPE":223 + }, + { + "HEALTHSTATUS":"1", + "NAME":"", + "MULTIPATHTYPE":"1", + "ISFREE":"true", + "RUNNINGSTATUS":"27", + "ID":"10000090fa0d6755", + "OPERATIONSYSTEM":"255", + "TYPE":223 + }] +} +""" + +FAKE_ISCSI_INITIATOR_RESPONSE = """ +{ + "error":{ + "code":0 + }, + "data":[{ + "CHAPNAME":"mm-user", + "HEALTHSTATUS":"1", + "ID":"iqn.1993-08.org.debian:01:9073aba6c6f", + "ISFREE":"true", + "MULTIPATHTYPE":"1", + "NAME":"", + "OPERATIONSYSTEM":"255", + "RUNNINGSTATUS":"28", + "TYPE":222, + "USECHAP":"true" + }] +} +""" + +FAKE_HOST_LINK_RESPONSE = """ +{ + "error":{ + "code":0 + }, + "data":[{ + "PARENTTYPE":21, + "TARGET_ID":"0000000000000000", + "INITIATOR_NODE_WWN":"20000090fa0d6754", + "INITIATOR_TYPE":"223", + "RUNNINGSTATUS":"27", + "PARENTNAME":"ubuntuc", + "INITIATOR_ID":"10000090fa0d6754", + "TARGET_PORT_WWN":"24000022a10a2a39", + "HEALTHSTATUS":"1", + "INITIATOR_PORT_WWN":"10000090fa0d6754", + "ID":"010000090fa0d675-0000000000110400", + "TARGET_NODE_WWN":"21000022a10a2a39", + "PARENTID":"1", + "CTRL_ID":"0", + "TYPE":255, + "TARGET_TYPE":"212" + }] +} +""" + +FAKE_PORT_GROUP_RESPONSE = """ +{ + "error":{ + "code":0 + }, + "data":[{ + "ID":11, + "NAME":"test" + }] +} +""" + +FAKE_ERROR_INFO_RESPONSE = """ +{ + "error":{ + "code":31755596 + } +} +""" + +FAKE_ERROR_LUN_INFO_RESPONSE = """ +{ + "error":{ + "code":0 + }, + "data":{ + "ID":"11", + "IOCLASSID":"11", + "NAME":"5mFHcBv4RkCcD+JyrWc0SA" + } +} +""" +# mock login info map +MAP_COMMAND_TO_FAKE_RESPONSE = {} +MAP_COMMAND_TO_FAKE_RESPONSE['/xx/sessions'] = ( + FAKE_GET_LOGIN_STORAGE_RESPONSE) +MAP_COMMAND_TO_FAKE_RESPONSE['sessions'] = ( + FAKE_LOGIN_OUT_STORAGE_RESPONSE) + +# mock storage info map +MAP_COMMAND_TO_FAKE_RESPONSE['storagepool'] = ( + FAKE_STORAGE_POOL_RESPONSE) + +# mock lun info map +MAP_COMMAND_TO_FAKE_RESPONSE['lun'] = ( + FAKE_LUN_INFO_RESPONSE) + +MAP_COMMAND_TO_FAKE_RESPONSE['lun/11/GET'] = ( + FAKE_LUN_DELETE_SUCCESS_RESPONSE) + +MAP_COMMAND_TO_FAKE_RESPONSE['lun/1/GET'] = ( + FAKE_LUN_DELETE_SUCCESS_RESPONSE) + +MAP_COMMAND_TO_FAKE_RESPONSE['lun/11/DELETE'] = ( + FAKE_COMMON_SUCCESS_RESPONSE) + +MAP_COMMAND_TO_FAKE_RESPONSE['lun?range=[0-65535]/GET'] = ( + FAKE_QUERY_ALL_LUN_RESPONSE) + +MAP_COMMAND_TO_FAKE_RESPONSE['lun/associate?TYPE=11&ASSOCIATEOBJTYPE=256' + '&ASSOCIATEOBJID=11/GET'] = ( + FAKE_LUN_ASSOCIATE_RESPONSE) + +MAP_COMMAND_TO_FAKE_RESPONSE['lun/associate?TYPE=11&ASSOCIATEOBJTYPE=256' + '&ASSOCIATEOBJID=12/GET'] = ( + FAKE_LUN_ASSOCIATE_RESPONSE) + +MAP_COMMAND_TO_FAKE_RESPONSE['lun/associate?ID=1&TYPE=11&ASSOCIATEOBJTYPE=21' + '&ASSOCIATEOBJID=0/GET'] = ( + FAKE_LUN_ASSOCIATE_RESPONSE) + +MAP_COMMAND_TO_FAKE_RESPONSE['lun/associate?TYPE=11&ASSOCIATEOBJTYPE=21' + '&ASSOCIATEOBJID=1/GET'] = ( + FAKE_COMMON_SUCCESS_RESPONSE) + +MAP_COMMAND_TO_FAKE_RESPONSE['lungroup?range=[0-8191]/GET'] = ( + FAKE_QUERY_LUN_GROUP_INFO_RESPONSE) + +MAP_COMMAND_TO_FAKE_RESPONSE['lungroup'] = ( + FAKE_QUERY_LUN_GROUP_RESPONSE) + +MAP_COMMAND_TO_FAKE_RESPONSE['lungroup/associate'] = ( + FAKE_QUERY_LUN_GROUP_ASSOCIAT_RESPONSE) + +MAP_COMMAND_TO_FAKE_RESPONSE['lungroup/associate?ID=11&ASSOCIATEOBJTYPE=11' + '&ASSOCIATEOBJID=1/DELETE'] = ( + FAKE_COMMON_SUCCESS_RESPONSE) + +MAP_COMMAND_TO_FAKE_RESPONSE['lungroup/associate?TYPE=256&ASSOCIATEOBJTYPE=11' + '&ASSOCIATEOBJID=11/GET'] = ( + FAKE_LUN_ASSOCIATE_RESPONSE) + +MAP_COMMAND_TO_FAKE_RESPONSE['lungroup/associate?ID=11&ASSOCIATEOBJTYPE=11' + '&ASSOCIATEOBJID=11/DELETE'] = ( + FAKE_COMMON_SUCCESS_RESPONSE) + +MAP_COMMAND_TO_FAKE_RESPONSE['lun/count?TYPE=11&ASSOCIATEOBJTYPE=256' + '&ASSOCIATEOBJID=11/GET'] = ( + FAKE_LUN_COUNT_RESPONSE) + +MAP_COMMAND_TO_FAKE_RESPONSE['lun/expand/PUT'] = ( + FAKE_LUN_INFO_RESPONSE) + +MAP_COMMAND_TO_FAKE_RESPONSE['lungroup/associate?ID=12&ASSOCIATEOBJTYPE=11' + '&ASSOCIATEOBJID=12/DELETE'] = ( + FAKE_COMMON_SUCCESS_RESPONSE) +# mock snapshot info map +MAP_COMMAND_TO_FAKE_RESPONSE['snapshot'] = ( + FAKE_CREATE_SNAPSHOT_INFO_RESPONSE) + +# mock snapshot info map +MAP_COMMAND_TO_FAKE_RESPONSE['snapshot/11/GET'] = ( + FAKE_GET_SNAPSHOT_INFO_RESPONSE) + +MAP_COMMAND_TO_FAKE_RESPONSE['snapshot/activate'] = ( + FAKE_COMMON_SUCCESS_RESPONSE) + +MAP_COMMAND_TO_FAKE_RESPONSE['snapshot/stop/PUT'] = ( + FAKE_COMMON_SUCCESS_RESPONSE) + +MAP_COMMAND_TO_FAKE_RESPONSE['snapshot/11/DELETE'] = ( + FAKE_COMMON_SUCCESS_RESPONSE) + +MAP_COMMAND_TO_FAKE_RESPONSE['snapshot?range=[0-32767]/GET'] = ( + FAKE_SNAPSHOT_LIST_INFO_RESPONSE) + +# mock QoS info map +MAP_COMMAND_TO_FAKE_RESPONSE['ioclass/11/GET'] = ( + FAKE_LUN_DELETE_SUCCESS_RESPONSE) + +MAP_COMMAND_TO_FAKE_RESPONSE['ioclass/11/DELETE'] = ( + FAKE_COMMON_SUCCESS_RESPONSE) + +MAP_COMMAND_TO_FAKE_RESPONSE['ioclass/active/11/PUT'] = ( + FAKE_COMMON_SUCCESS_RESPONSE) + +# mock iscsi info map +MAP_COMMAND_TO_FAKE_RESPONSE['iscsi_tgt_port/GET'] = ( + FAKE_GET_ISCSI_INFO_RESPONSE) + +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'] = ( + FAKE_GET_ETH_ASSOCIATE_RESPONSE) + +MAP_COMMAND_TO_FAKE_RESPONSE['iscsidevicename'] = ( + FAKE_GET_ISCSI_DEVICE_RESPONSE) + +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) + +# mock host info map +MAP_COMMAND_TO_FAKE_RESPONSE['host?range=[0-65535]/GET'] = ( + FAKE_GET_ALL_HOST_INFO_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' + '&ASSOCIATEOBJID=0/GET'] = ( + FAKE_COMMON_SUCCESS_RESPONSE) + +MAP_COMMAND_TO_FAKE_RESPONSE['hostgroup/associate'] = ( + FAKE_COMMON_SUCCESS_RESPONSE) + +# mock copy info map +MAP_COMMAND_TO_FAKE_RESPONSE['luncopy'] = ( + FAKE_GET_LUN_COPY_INFO_RESPONSE) + +MAP_COMMAND_TO_FAKE_RESPONSE['LUNCOPY?range=[0-1023]/GET'] = ( + FAKE_GET_LUN_COPY_LIST_INFO_RESPONSE) + +MAP_COMMAND_TO_FAKE_RESPONSE['LUNCOPY/start/PUT'] = ( + FAKE_COMMON_SUCCESS_RESPONSE) + +MAP_COMMAND_TO_FAKE_RESPONSE['LUNCOPY/0/DELETE'] = ( + FAKE_COMMON_SUCCESS_RESPONSE) + +# mock mapping view info map +MAP_COMMAND_TO_FAKE_RESPONSE['mappingview?range=[0-8191]/GET'] = ( + FAKE_GET_MAPPING_VIEW_INFO_RESPONSE) + +MAP_COMMAND_TO_FAKE_RESPONSE['mappingview'] = ( + 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'] = ( + FAKE_FC_INFO_RESPONSE) + +MAP_COMMAND_TO_FAKE_RESPONSE['fc_initiator/10000090fa0d6754/PUT'] = ( + FAKE_COMMON_SUCCESS_RESPONSE) + +MAP_COMMAND_TO_FAKE_RESPONSE['host_link?INITIATOR_TYPE=223' + '&INITIATOR_PORT_WWN=10000090fa0d6754/GET'] = ( + FAKE_HOST_LINK_RESPONSE) + +MAP_COMMAND_TO_FAKE_RESPONSE['portgroup?range=[0-8191]&TYPE=257/GET'] = ( + FAKE_PORT_GROUP_RESPONSE) + + +def Fake_sleep(time): + pass + + +class Fake18000Client(rest_client.RestClient): + + def __init__(self, configuration): + rest_client.RestClient.__init__(self, configuration) + self.delete_flag = False + self.terminateFlag = False + self.deviceid = None + self.test_fail = False + self.checkFlag = False + + def _change_file_mode(self, filepath): + pass + + def _parse_volume_type(self, volume): + poolinfo = self._find_pool_info() + volume_size = self._get_volume_size(poolinfo, volume) + + params = {'LUNType': 0, + 'WriteType': '1', + 'PrefetchType': '3', + 'qos_level': 'Qos-high', + 'StripUnitSize': '64', + 'PrefetchValue': '0', + 'PrefetchTimes': '0', + 'qos': 'OpenStack_Qos_High', + 'MirrorSwitch': '1', + 'tier': 'Tier_high', + } + + params['volume_size'] = volume_size + params['pool_id'] = poolinfo['ID'] + return params + + def _get_snapshotid_by_name(self, snapshot_name): + return "11" + + def _check_snapshot_exist(self, snapshot_id): + return True + + def call(self, url=False, data=None, method=None): + url = url.replace('http://100.115.10.69:8082/deviceManager/rest', '') + command = url.replace('/210235G7J20000000000/', '') + data = None + + if method: + command = command + "/" + method + + for item in MAP_COMMAND_TO_FAKE_RESPONSE.keys(): + if command == item: + data = MAP_COMMAND_TO_FAKE_RESPONSE[item] + if self.test_fail: + data = FAKE_ERROR_INFO_RESPONSE + if command == 'lun/11/GET': + data = FAKE_ERROR_LUN_INFO_RESPONSE + + self.test_fail = False + + return json.loads(data) + + +class Fake18000ISCSIStorage(huawei_driver.Huawei18000ISCSIDriver): + """Fake Huawei Storage, Rewrite some methods of HuaweiISCSIDriver.""" + + def __init__(self, configuration): + self.configuration = configuration + self.xml_file_path = self.configuration.cinder_huawei_conf_file + + def do_setup(self): + self.restclient = Fake18000Client(configuration=self.configuration) + + +class Fake18000FCStorage(huawei_driver.Huawei18000FCDriver): + """Fake Huawei Storage, Rewrite some methods of HuaweiISCSIDriver.""" + + def __init__(self, configuration): + self.configuration = configuration + self.xml_file_path = self.configuration.cinder_huawei_conf_file + + def do_setup(self): + self.restclient = Fake18000Client(configuration=self.configuration) + + +class Huawei18000ISCSIDriverTestCase(test.TestCase): + + def setUp(self): + super(Huawei18000ISCSIDriverTestCase, self).setUp() + self.tmp_dir = tempfile.mkdtemp() + self.fake_conf_file = self.tmp_dir + '/cinder_huawei_conf.xml' + self.addCleanup(shutil.rmtree, self.tmp_dir) + self.create_fake_conf_file() + self.addCleanup(os.remove, self.fake_conf_file) + self.configuration = mock.Mock(spec=conf.Configuration) + self.configuration.cinder_huawei_conf_file = self.fake_conf_file + self.xml_file_path = self.configuration.cinder_huawei_conf_file + self.stubs.Set(time, 'sleep', Fake_sleep) + driver = Fake18000ISCSIStorage(configuration=self.configuration) + self.driver = driver + self.driver.do_setup() + self.portgroup = 'test' + self.target_ip = '192.168.100.10' + + def test_login_success(self): + deviceid = self.driver.restclient.login() + self.assertEqual('210235G7J20000000000', deviceid) + + def test_create_volume_success(self): + self.driver.restclient.login() + lun_info = self.driver.create_volume(test_volume) + self.assertEqual('1', lun_info['provider_location']) + + def test_delete_volume_success(self): + self.driver.restclient.login() + delete_flag = self.driver.delete_volume(test_volume) + self.assertTrue(delete_flag) + + def test_create_snapshot_success(self): + self.driver.restclient.login() + lun_info = self.driver.create_snapshot(test_volume) + self.assertEqual(11, lun_info['provider_location']) + + def test_delete_snapshot_success(self): + self.driver.restclient.login() + delete_flag = self.driver.delete_snapshot(test_snap) + self.assertTrue(delete_flag) + + def test_create_volume_from_snapsuccess(self): + self.driver.restclient.login() + lun_info = self.driver.create_volume_from_snapshot(test_volume, + test_volume) + self.assertEqual('1', lun_info['provider_location']) + + def test_initialize_connection_success(self): + self.driver.restclient.login() + iscsi_properties = self.driver.initialize_connection(test_volume, + FakeConnector) + self.assertEqual(1, iscsi_properties['data']['target_lun']) + + def test_terminate_connection_success(self): + self.driver.restclient.login() + self.driver.restclient.terminateFlag = True + self.driver.terminate_connection(test_volume, FakeConnector) + self.assertTrue(self.driver.restclient.terminateFlag) + + def test_get_volume_status(self): + self.driver.restclient.login() + data = self.driver.get_volume_stats() + self.assertEqual('1.1.1', data['driver_version']) + + def test_extend_volume(self): + self.driver.restclient.login() + lun_info = self.driver.extend_volume(test_volume, 3) + self.assertEqual('1', lun_info['provider_location']) + + def test_login_fail(self): + self.driver.restclient.test_fail = True + self.assertRaises(exception.VolumeBackendAPIException, + self.driver.restclient.login) + + def test_create_snapshot_fail(self): + self.driver.restclient.login() + self.driver.restclient.test_fail = True + self.assertRaises(exception.VolumeBackendAPIException, + self.driver.create_snapshot, test_volume) + + 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) + + def test_delete_volume_fail(self): + self.driver.restclient.login() + self.driver.restclient.test_fail = True + delete_flag = self.driver.delete_volume(test_volume) + self.assertTrue(delete_flag) + + def test_delete_snapshot_fail(self): + self.driver.restclient.login() + self.driver.restclient.test_fail = True + delete_flag = self.driver.delete_volume(test_snap) + self.assertTrue(delete_flag) + + def test_initialize_connection_fail(self): + self.driver.restclient.login() + self.driver.restclient.test_fail = True + iscsi_properties = self.driver.initialize_connection(test_volume, + FakeConnector) + self.assertEqual(1, iscsi_properties['data']['target_lun']) + + def test_get_default_timeout(self): + result = huawei_utils._get_default_timeout(self.xml_file_path) + self.assertEqual('43200', result) + + def test_get_wait_interval(self): + result = huawei_utils.get_wait_interval(self.xml_file_path, + 'LUNReadyWaitInterval') + self.assertEqual(2, result) + + def test_lun_is_associated_to_lungroup(self): + self.driver.restclient.login() + self.driver.restclient._associate_lun_to_lungroup('11', '11') + result = self.driver.restclient._is_lun_associated_to_lungroup('11', + '11') + self.assertTrue(result) + + def test_lun_is_not_associated_to_lun_group(self): + self.driver.restclient.login() + self.driver.restclient._associate_lun_to_lungroup('12', '12') + self.driver.restclient.remove_lun_from_lungroup('12', '12') + result = self.driver.restclient._is_lun_associated_to_lungroup('12', + '12') + self.assertFalse(result) + + def test_get_tgtip(self): + self.driver.restclient.login() + portg_id = self.driver.restclient.find_tgt_port_group(self.portgroup) + result = self.driver.restclient._get_tgt_ip_from_portgroup(portg_id) + self.assertEqual(result, self.target_ip) + + def test_get_lun_conf_params(self): + self.driver.restclient.login() + luninfo = huawei_utils.get_lun_conf_params(self.xml_file_path) + luninfo['pool_id'] = '0' + luninfo['volume_size'] = 2 + luninfo['volume_description'] = 'test volume' + luninfo = huawei_utils.init_lun_parameters('5mFHcBv4RkCcD+JyrWc0SA', + luninfo) + self.assertEqual('5mFHcBv4RkCcD+JyrWc0SA', luninfo['NAME']) + + def tset_get_iscsi_conf(self): + self.driver.restclient.login() + iscsiinfo = huawei_utils.get_iscsi_conf(self.xml_file_path) + self.assertEqual('iqn.1993-08.debian:01:ec2bff7ac3a3', + iscsiinfo['Initiator']) + + def test_check_conf_file(self): + self.driver.restclient.login() + self.driver.restclient.checkFlag = True + huawei_utils.check_conf_file(self.xml_file_path) + self.assertTrue(self.driver.restclient.checkFlag) + + def test_get_conf_host_os_type(self): + self.driver.restclient.login() + host_os = huawei_utils.get_conf_host_os_type('100.97.10.30', + self.configuration) + self.assertEqual('0', host_os) + + def create_fake_conf_file(self): + """Create a fake Config file. + + Huawei storage customize a XML configuration file, the configuration + file is used to set the Huawei storage custom parameters, therefore, + in the UT test we need to simulate such a configuration file. + """ + doc = minidom.Document() + + config = doc.createElement('config') + doc.appendChild(config) + + storage = doc.createElement('Storage') + config.appendChild(storage) + controllerip0 = doc.createElement('ControllerIP0') + controllerip0_text = doc.createTextNode('10.10.10.1') + controllerip0.appendChild(controllerip0_text) + storage.appendChild(controllerip0) + controllerip1 = doc.createElement('ControllerIP1') + controllerip1_text = doc.createTextNode('10.10.10.2') + controllerip1.appendChild(controllerip1_text) + storage.appendChild(controllerip1) + username = doc.createElement('UserName') + username_text = doc.createTextNode('admin') + username.appendChild(username_text) + storage.appendChild(username) + userpassword = doc.createElement('UserPassword') + userpassword_text = doc.createTextNode('Admin@storage') + userpassword.appendChild(userpassword_text) + storage.appendChild(userpassword) + url = doc.createElement('RestURL') + url_text = doc.createTextNode('http://100.115.10.69:8082/' + 'deviceManager/rest/') + url.appendChild(url_text) + storage.appendChild(url) + + storagepool = doc.createElement('StoragePool') + pool_text = doc.createTextNode('OpenStack_Pool') + storagepool.appendChild(pool_text) + storage.appendChild(storagepool) + + lun = doc.createElement('LUN') + config.appendChild(lun) + storagepool = doc.createElement('StoragePool') + pool_text = doc.createTextNode('OpenStack_Pool') + storagepool.appendChild(pool_text) + lun.appendChild(storagepool) + + timeout = doc.createElement('Timeout') + timeout_text = doc.createTextNode('43200') + timeout.appendChild(timeout_text) + lun.appendChild(timeout) + + lun_ready_wait_interval = doc.createElement('LUNReadyWaitInterval') + lun_ready_wait_interval_text = doc.createTextNode('2') + lun_ready_wait_interval.appendChild(lun_ready_wait_interval_text) + lun.appendChild(lun_ready_wait_interval) + + prefetch = doc.createElement('Prefetch') + prefetch.setAttribute('Type', '1') + prefetch.setAttribute('Value', '0') + lun.appendChild(prefetch) + + iscsi = doc.createElement('iSCSI') + config.appendChild(iscsi) + defaulttargetip = doc.createElement('DefaultTargetIP') + defaulttargetip_text = doc.createTextNode('100.115.10.68') + defaulttargetip.appendChild(defaulttargetip_text) + iscsi.appendChild(defaulttargetip) + initiator = doc.createElement('Initiator') + initiator.setAttribute('Name', 'iqn.1993-08.debian:01:ec2bff7ac3a3') + initiator.setAttribute('TargetIP', '192.168.100.2') + iscsi.appendChild(initiator) + + host = doc.createElement('Host') + host.setAttribute('HostIP', '100.97.10.30') + host.setAttribute('OSType', 'Linux') + config.appendChild(host) + + fakefile = open(self.fake_conf_file, 'w') + fakefile.write(doc.toprettyxml(indent='')) + fakefile.close() + + +class Huawei18000FCDriverTestCase(test.TestCase): + + def setUp(self): + super(Huawei18000FCDriverTestCase, self).setUp() + self.tmp_dir = tempfile.mkdtemp() + self.fake_conf_file = self.tmp_dir + '/cinder_huawei_conf.xml' + self.addCleanup(shutil.rmtree, self.tmp_dir) + self.create_fake_conf_file() + self.addCleanup(os.remove, self.fake_conf_file) + self.configuration = mock.Mock(spec=conf.Configuration) + self.configuration.cinder_huawei_conf_file = self.fake_conf_file + self.xml_file_path = self.configuration.cinder_huawei_conf_file + self.stubs.Set(time, 'sleep', Fake_sleep) + driver = Fake18000FCStorage(configuration=self.configuration) + self.driver = driver + self.driver.do_setup() + + def test_login_success(self): + deviceid = self.driver.restclient.login() + self.assertEqual('210235G7J20000000000', deviceid) + + def test_create_volume_success(self): + self.driver.restclient.login() + lun_info = self.driver.create_volume(test_volume) + self.assertEqual('1', lun_info['provider_location']) + + def test_delete_volume_success(self): + self.driver.restclient.login() + delete_flag = self.driver.delete_volume(test_volume) + self.assertTrue(delete_flag) + + def test_create_snapshot_success(self): + self.driver.restclient.login() + lun_info = self.driver.create_snapshot(test_volume) + self.assertEqual(11, lun_info['provider_location']) + + def test_delete_snapshot_success(self): + self.driver.restclient.login() + delete_flag = self.driver.delete_snapshot(test_snap) + self.assertTrue(delete_flag) + + def test_create_volume_from_snapsuccess(self): + self.driver.restclient.login() + lun_info = self.driver.create_volume_from_snapshot(test_volume, + test_volume) + self.assertEqual('1', lun_info['provider_location']) + + def test_initialize_connection_success(self): + self.driver.restclient.login() + iscsi_properties = self.driver.initialize_connection(test_volume, + FakeConnector) + self.assertEqual(1, iscsi_properties['data']['target_lun']) + + def test_terminate_connection_success(self): + self.driver.restclient.login() + self.driver.restclient.terminateFlag = True + self.driver.terminate_connection(test_volume, FakeConnector) + self.assertTrue(self.driver.restclient.terminateFlag) + + def test_get_volume_status(self): + self.driver.restclient.login() + data = self.driver.get_volume_stats() + self.assertEqual('1.1.1', data['driver_version']) + + def test_extend_volume(self): + self.driver.restclient.login() + lun_info = self.driver.extend_volume(test_volume, 3) + self.assertEqual('1', lun_info['provider_location']) + + def test_login_fail(self): + self.driver.restclient.test_fail = True + self.assertRaises(exception.VolumeBackendAPIException, + self.driver.restclient.login) + + def test_create_snapshot_fail(self): + self.driver.restclient.login() + self.driver.restclient.test_fail = True + self.assertRaises(exception.VolumeBackendAPIException, + self.driver.create_snapshot, test_volume) + + 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) + + def test_delete_volume_fail(self): + self.driver.restclient.login() + self.driver.restclient.test_fail = True + delete_flag = self.driver.delete_volume(test_volume) + self.assertTrue(delete_flag) + + def test_delete_snapshot_fail(self): + self.driver.restclient.login() + self.driver.restclient.test_fail = True + delete_flag = self.driver.delete_snapshot(test_snap) + self.assertTrue(delete_flag) + + def test_initialize_connection_fail(self): + self.driver.restclient.login() + self.driver.restclient.test_fail = True + self.assertRaises(exception.VolumeBackendAPIException, + self.driver.initialize_connection, + test_volume, FakeConnector) + + def test_get_default_timeout(self): + result = huawei_utils._get_default_timeout(self.xml_file_path) + self.assertEqual('43200', result) + + def test_get_wait_interval(self): + result = huawei_utils.get_wait_interval(self.xml_file_path, + 'LUNReadyWaitInterval') + self.assertEqual(2, result) + + def test_lun_is_associated_to_lungroup(self): + self.driver.restclient.login() + self.driver.restclient._associate_lun_to_lungroup('11', '11') + result = self.driver.restclient._is_lun_associated_to_lungroup('11', + '11') + self.assertTrue(result) + + def test_lun_is_not_associated_to_lun_group(self): + self.driver.restclient.login() + self.driver.restclient._associate_lun_to_lungroup('12', '12') + self.driver.restclient.remove_lun_from_lungroup('12', '12') + result = self.driver.restclient._is_lun_associated_to_lungroup('12', + '12') + self.assertFalse(result) + + def test_get_lun_conf_params(self): + self.driver.restclient.login() + luninfo = huawei_utils.get_lun_conf_params(self.xml_file_path) + luninfo['pool_id'] = '0' + luninfo['volume_size'] = 2 + luninfo['volume_description'] = 'test volume' + luninfo = huawei_utils.init_lun_parameters('5mFHcBv4RkCcD+JyrWc0SA', + luninfo) + self.assertEqual('5mFHcBv4RkCcD+JyrWc0SA', luninfo['NAME']) + + def test_check_conf_file(self): + self.driver.restclient.login() + self.driver.restclient.checkFlag = True + huawei_utils.check_conf_file(self.xml_file_path) + self.assertTrue(self.driver.restclient.checkFlag) + + def test_get_conf_host_os_type(self): + self.driver.restclient.login() + host_os = huawei_utils.get_conf_host_os_type('100.97.10.30', + self.configuration) + self.assertEqual('0', host_os) + + def create_fake_conf_file(self): + """Create a fake Config file + + Huawei storage customize a XML configuration file, + the configuration file is used to set the Huawei storage custom + parameters, therefore, in the UT test we need to simulate such a + configuration file + """ + doc = minidom.Document() + + config = doc.createElement('config') + doc.appendChild(config) + + storage = doc.createElement('Storage') + config.appendChild(storage) + controllerip0 = doc.createElement('ControllerIP0') + controllerip0_text = doc.createTextNode('10.10.10.1') + controllerip0.appendChild(controllerip0_text) + storage.appendChild(controllerip0) + controllerip1 = doc.createElement('ControllerIP1') + controllerip1_text = doc.createTextNode('10.10.10.2') + controllerip1.appendChild(controllerip1_text) + storage.appendChild(controllerip1) + username = doc.createElement('UserName') + username_text = doc.createTextNode('admin') + username.appendChild(username_text) + storage.appendChild(username) + userpassword = doc.createElement('UserPassword') + userpassword_text = doc.createTextNode('Admin@storage') + userpassword.appendChild(userpassword_text) + storage.appendChild(userpassword) + url = doc.createElement('RestURL') + url_text = doc.createTextNode('http://100.115.10.69:8082/' + 'deviceManager/rest/') + url.appendChild(url_text) + storage.appendChild(url) + + storagepool = doc.createElement('StoragePool') + pool_text = doc.createTextNode('OpenStack_Pool') + storagepool.appendChild(pool_text) + storage.appendChild(storagepool) + + lun = doc.createElement('LUN') + config.appendChild(lun) + storagepool = doc.createElement('StoragePool') + pool_text = doc.createTextNode('OpenStack_Pool') + storagepool.appendChild(pool_text) + lun.appendChild(storagepool) + + timeout = doc.createElement('Timeout') + timeout_text = doc.createTextNode('43200') + timeout.appendChild(timeout_text) + lun.appendChild(timeout) + + lun_ready_wait_interval = doc.createElement('LUNReadyWaitInterval') + lun_ready_wait_interval_text = doc.createTextNode('2') + lun_ready_wait_interval.appendChild(lun_ready_wait_interval_text) + lun.appendChild(lun_ready_wait_interval) + + iscsi = doc.createElement('iSCSI') + config.appendChild(iscsi) + defaulttargetip = doc.createElement('DefaultTargetIP') + defaulttargetip_text = doc.createTextNode('100.115.10.68') + defaulttargetip.appendChild(defaulttargetip_text) + iscsi.appendChild(defaulttargetip) + initiator = doc.createElement('Initiator') + initiator.setAttribute('Name', 'iqn.1993-08.debian:01:ec2bff7ac3a3') + initiator.setAttribute('TargetIP', '192.168.100.2') + iscsi.appendChild(initiator) + + prefetch = doc.createElement('Prefetch') + prefetch.setAttribute('Type', '1') + prefetch.setAttribute('Value', '0') + lun.appendChild(prefetch) + + host = doc.createElement('Host') + host.setAttribute('HostIP', '100.97.10.30') + host.setAttribute('OSType', 'Linux') + config.appendChild(host) + + fakefile = open(self.fake_conf_file, 'w') + fakefile.write(doc.toprettyxml(indent='')) + fakefile.close() diff --git a/cinder/tests/unit/test_huawei_drivers_compatibility.py b/cinder/tests/unit/test_huawei_drivers_compatibility.py index 98d6b308e..3577a3d8b 100644 --- a/cinder/tests/unit/test_huawei_drivers_compatibility.py +++ b/cinder/tests/unit/test_huawei_drivers_compatibility.py @@ -21,9 +21,9 @@ from cinder import test CONF = cfg.CONF -HUAWEI_ISCSI_MODULE = ("cinder.volume.drivers.huawei.huawei_18000." +HUAWEI_ISCSI_MODULE = ("cinder.volume.drivers.huawei.huawei_driver." "Huawei18000ISCSIDriver") -HUAWEI_FC_MODULE = ("cinder.volume.drivers.huawei.huawei_18000." +HUAWEI_FC_MODULE = ("cinder.volume.drivers.huawei.huawei_driver." "Huawei18000FCDriver") @@ -47,7 +47,7 @@ class VolumeDriverCompatibility(test.TestCase): def test_huawei_driver_iscsi_old(self): self._load_driver( - 'cinder.volume.drivers.huawei.huawei_hvs.HuaweiHVSISCSIDriver') + 'cinder.volume.drivers.huawei.huawei_18000.Huawei18000ISCSIDriver') self.assertEqual(self._driver_module_name(), HUAWEI_ISCSI_MODULE) def test_huawei_driver_iscsi_new(self): @@ -56,7 +56,7 @@ class VolumeDriverCompatibility(test.TestCase): def test_huawei_driver_fc_old(self): self._load_driver( - 'cinder.volume.drivers.huawei.huawei_hvs.HuaweiHVSFCDriver') + 'cinder.volume.drivers.huawei.huawei_18000.Huawei18000FCDriver') self.assertEqual(self._driver_module_name(), HUAWEI_FC_MODULE) def test_huawei_driver_fc_new(self): diff --git a/cinder/volume/drivers/huawei/__init__.py b/cinder/volume/drivers/huawei/__init__.py index b2e501d7a..e69de29bb 100644 --- a/cinder/volume/drivers/huawei/__init__.py +++ b/cinder/volume/drivers/huawei/__init__.py @@ -1,108 +0,0 @@ -# Copyright (c) 2013 - 2014 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. -""" -Provide a unified driver class for users. - -The product type and the protocol should be specified in config file before. -""" - -from oslo_config import cfg -from oslo_log import log as logging -import six - -from cinder import exception -from cinder.i18n import _, _LI, _LW -from cinder.volume.drivers.huawei import huawei_18000 -from cinder.volume.drivers.huawei import huawei_utils - - -LOG = logging.getLogger(__name__) - -huawei_opt = [ - cfg.StrOpt('cinder_huawei_conf_file', - default='/etc/cinder/cinder_huawei_conf.xml', - help='The configuration file for the Cinder Huawei ' - 'driver')] - -CONF = cfg.CONF -CONF.register_opts(huawei_opt) -MAPPING = {'HVS': '18000'} - - -class HuaweiVolumeDriver(object): - """Define an unified driver for Huawei drivers.""" - - def __init__(self, *args, **kwargs): - super(HuaweiVolumeDriver, self).__init__() - self._product = {'18000': huawei_18000, 'HVS': huawei_18000} - self._protocol = {'iSCSI': 'ISCSIDriver', 'FC': 'FCDriver'} - - self.driver = self._instantiate_driver(*args, **kwargs) - - def _instantiate_driver(self, *args, **kwargs): - """Instantiate the specified driver.""" - self.configuration = kwargs.get('configuration', None) - if not self.configuration: - msg = (_('_instantiate_driver: configuration not found.')) - raise exception.InvalidInput(reason=msg) - - self.configuration.append_config_values(huawei_opt) - conf_file = self.configuration.cinder_huawei_conf_file - (product, protocol) = self._get_conf_info(conf_file) - - LOG.info(_LI('_instantiate_driver: Loading %(protocol)s driver for ' - 'Huawei OceanStor %(product)s series storage arrays.'), - {'protocol': protocol, - 'product': product}) - # Map HVS to 18000 - if product in MAPPING: - LOG.warning(_LW("Product name %s is deprecated, update your " - "configuration to the new product name."), - product) - product = MAPPING[product] - - driver_module = self._product[product] - driver_class = 'Huawei' + product + self._protocol[protocol] - - driver_class = getattr(driver_module, driver_class) - return driver_class(*args, **kwargs) - - def _get_conf_info(self, filename): - """Get product type and connection protocol from config file.""" - root = huawei_utils.parse_xml_file(filename) - product = root.findtext('Storage/Product').strip() - protocol = root.findtext('Storage/Protocol').strip() - if (product in self._product.keys() and - protocol in self._protocol.keys()): - return (product, protocol) - else: - msg = (_('"Product" or "Protocol" is illegal. "Product" should be ' - 'set to 18000. "Protocol" should be set to either iSCSI ' - 'or FC. Product: %(product)s Protocol: %(protocol)s') - % {'product': six.text_type(product), - 'protocol': six.text_type(protocol)}) - raise exception.InvalidInput(reason=msg) - - def __setattr__(self, name, value): - """Set the attribute.""" - if getattr(self, 'driver', None): - self.driver.__setattr__(name, value) - return - object.__setattr__(self, name, value) - - def __getattr__(self, name): - """"Get the attribute.""" - drver = object.__getattribute__(self, 'driver') - return getattr(drver, name) diff --git a/cinder/volume/drivers/huawei/constants.py b/cinder/volume/drivers/huawei/constants.py new file mode 100644 index 000000000..20f754ac4 --- /dev/null +++ b/cinder/volume/drivers/huawei/constants.py @@ -0,0 +1,41 @@ +# 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. + +STATUS_HEALTH = '1' +STATUS_RUNNING = '10' + +HOSTGROUP_PREFIX = 'OpenStack_HostGroup_' +LUNGROUP_PREFIX = 'OpenStack_LunGroup_' +MAPPING_VIEW_PREFIX = 'OpenStack_Mapping_View_' +QOS_NAME_PREFIX = 'OpenStack_' + +DEFAULT_WAIT_TIMEOUT = 3600 * 24 * 30 +DEFAULT_WAIT_INTERVAL = 5 +ERROR_CONNECT_TO_SERVER = -403 +ERROR_UNAUTHORIZED_TO_SERVER = -401 + +MAX_HOSTNAME_LENTH = 31 + +OS_TYPE = {'Linux': '0', + 'Windows': '1', + 'Solaris': '2', + 'HP-UX': '3', + 'AIX': '4', + 'XenServer': '5', + 'Mac OS X': '6', + 'VMware ESX': '7'} + +HUAWEI_VALID_KEYS = ['maxIOPS', 'minIOPS', 'minBandWidth', + 'maxBandWidth', 'latency', 'IOType'] diff --git a/cinder/volume/drivers/huawei/huawei_18000.py b/cinder/volume/drivers/huawei/huawei_18000.py deleted file mode 100644 index c306fecb2..000000000 --- a/cinder/volume/drivers/huawei/huawei_18000.py +++ /dev/null @@ -1,199 +0,0 @@ -# Copyright (c) 2013 - 2014 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. -""" -Volume Drivers for Huawei OceanStor 18000 storage arrays. -""" - -from cinder.volume import driver -from cinder.volume.drivers.huawei import rest_common -from cinder.zonemanager import utils as fczm_utils - - -class Huawei18000ISCSIDriver(driver.ISCSIDriver): - """ISCSI driver for Huawei OceanStor 18000 storage arrays. - - Version history: - 1.0.0 - Initial driver - 1.1.0 - Provide Huawei OceanStor 18000 storage volume driver. - """ - - VERSION = "1.1.0" - - def __init__(self, *args, **kwargs): - super(Huawei18000ISCSIDriver, self).__init__(*args, **kwargs) - - def do_setup(self, context): - """Instantiate common class and log in storage system.""" - self.common = rest_common.RestCommon(configuration=self.configuration) - return self.common.login() - - def check_for_setup_error(self): - """Check configuration file.""" - return self.common._check_conf_file() - - def create_volume(self, volume): - """Create a volume.""" - lun_info = self.common.create_volume(volume) - return {'provider_location': lun_info['ID'], - 'lun_info': lun_info} - - def create_volume_from_snapshot(self, volume, snapshot): - """Create a volume from a snapshot.""" - lun_info = self.common.create_volume_from_snapshot(volume, snapshot) - return {'provider_location': lun_info['ID'], - 'lun_info': lun_info} - - def create_cloned_volume(self, volume, src_vref): - """Create a clone of the specified volume.""" - lun_info = self.common.create_cloned_volume(volume, src_vref) - return {'provider_location': lun_info['ID'], - 'lun_info': lun_info} - - def extend_volume(self, volume, new_size): - """Extend a volume.""" - return self.common.extend_volume(volume, new_size) - - def delete_volume(self, volume): - """Delete a volume.""" - return self.common.delete_volume(volume) - - def create_snapshot(self, snapshot): - """Create a snapshot.""" - lun_info = self.common.create_snapshot(snapshot) - return {'provider_location': lun_info['ID'], - 'lun_info': lun_info} - - def delete_snapshot(self, snapshot): - """Delete a snapshot.""" - return self.common.delete_snapshot(snapshot) - - def get_volume_stats(self, refresh=False): - """Get volume stats.""" - data = self.common.update_volume_stats() - backend_name = self.configuration.safe_get('volume_backend_name') - data['volume_backend_name'] = backend_name or self.__class__.__name__ - data['storage_protocol'] = 'iSCSI' - data['driver_version'] = self.VERSION - return data - - def initialize_connection(self, volume, connector): - """Map a volume to a host.""" - return self.common.initialize_connection_iscsi(volume, connector) - - def terminate_connection(self, volume, connector, **kwargs): - """Terminate the map.""" - self.common.terminate_connection_iscsi(volume, connector) - - def create_export(self, context, volume): - """Export the volume.""" - pass - - def ensure_export(self, context, volume): - """Synchronously recreate an export for a volume.""" - pass - - def remove_export(self, context, volume): - """Remove an export for a volume.""" - pass - - -class Huawei18000FCDriver(driver.FibreChannelDriver): - """FC driver for Huawei OceanStor 18000 storage arrays. - - Version history: - 1.0.0 - Initial driver - 1.1.0 - Provide Huawei OceanStor 18000 storage volume driver. - """ - - VERSION = "1.1.0" - - def __init__(self, *args, **kwargs): - super(Huawei18000FCDriver, self).__init__(*args, **kwargs) - - def do_setup(self, context): - """Instantiate common class and log in storage system.""" - self.common = rest_common.RestCommon(configuration=self.configuration) - return self.common.login() - - def check_for_setup_error(self): - """Check configuration file.""" - return self.common._check_conf_file() - - def create_volume(self, volume): - """Create a volume.""" - lun_info = self.common.create_volume(volume) - return {'provider_location': lun_info['ID'], - 'lun_info': lun_info} - - def create_volume_from_snapshot(self, volume, snapshot): - """Create a volume from a snapshot.""" - lun_info = self.common.create_volume_from_snapshot(volume, snapshot) - return {'provider_location': lun_info['ID'], - 'lun_info': lun_info} - - def create_cloned_volume(self, volume, src_vref): - """Create a clone of the specified volume.""" - lun_info = self.common.create_cloned_volume(volume, src_vref) - return {'provider_location': lun_info['ID'], - 'lun_info': lun_info} - - def extend_volume(self, volume, new_size): - """Extend a volume.""" - return self.common.extend_volume(volume, new_size) - - def delete_volume(self, volume): - """Delete a volume.""" - return self.common.delete_volume(volume) - - def create_snapshot(self, snapshot): - """Create a snapshot.""" - lun_info = self.common.create_snapshot(snapshot) - return {'provider_location': lun_info['ID'], - 'lun_info': lun_info} - - def delete_snapshot(self, snapshot): - """Delete a snapshot.""" - return self.common.delete_snapshot(snapshot) - - def get_volume_stats(self, refresh=False): - """Get volume stats.""" - data = self.common.update_volume_stats() - backend_name = self.configuration.safe_get('volume_backend_name') - data['volume_backend_name'] = backend_name or self.__class__.__name__ - data['storage_protocol'] = 'FC' - data['driver_version'] = self.VERSION - return data - - @fczm_utils.AddFCZone - def initialize_connection(self, volume, connector): - """Map a volume to a host.""" - return self.common.initialize_connection_fc(volume, connector) - - @fczm_utils.RemoveFCZone - def terminate_connection(self, volume, connector, **kwargs): - """Terminate the map.""" - return self.common.terminate_connection_fc(volume, connector) - - def create_export(self, context, volume): - """Export the volume.""" - pass - - def ensure_export(self, context, volume): - """Synchronously recreate an export for a volume.""" - pass - - def remove_export(self, context, volume): - """Remove an export for a volume.""" - pass diff --git a/cinder/volume/drivers/huawei/huawei_driver.py b/cinder/volume/drivers/huawei/huawei_driver.py new file mode 100644 index 000000000..879892729 --- /dev/null +++ b/cinder/volume/drivers/huawei/huawei_driver.py @@ -0,0 +1,633 @@ +# 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. + +import uuid + +from oslo_config import cfg +from oslo_log import log as logging +from oslo_utils import excutils +from oslo_utils import units + +from cinder import exception +from cinder.i18n import _, _LI, _LW +from cinder import utils +from cinder.volume import driver +from cinder.volume.drivers.huawei import constants +from cinder.volume.drivers.huawei import huawei_utils +from cinder.volume.drivers.huawei import rest_client +from cinder.zonemanager import utils as fczm_utils + +LOG = logging.getLogger(__name__) + +huawei_opt = [ + cfg.StrOpt('cinder_huawei_conf_file', + default='/etc/cinder/cinder_huawei_conf.xml', + help='The configuration file for the Cinder Huawei ' + 'driver.')] + +CONF = cfg.CONF +CONF.register_opts(huawei_opt) + + +class HuaweiBaseDriver(driver.VolumeDriver): + + def __init__(self, *args, **kwargs): + super(HuaweiBaseDriver, self).__init__(*args, **kwargs) + self.configuration = kwargs.get('configuration', None) + if not self.configuration: + msg = _('_instantiate_driver: configuration not found.') + raise exception.InvalidInput(reason=msg) + + self.configuration.append_config_values(huawei_opt) + self.xml_file_path = self.configuration.cinder_huawei_conf_file + + def do_setup(self, context): + """Instantiate common class and login storage system.""" + self.restclient = rest_client.RestClient(self.configuration) + return self.restclient.login() + + def check_for_setup_error(self): + """Check configuration file.""" + return huawei_utils.check_conf_file(self.xml_file_path) + + def get_volume_stats(self, refresh=False): + """Get volume status.""" + return self.restclient.update_volume_stats() + + @utils.synchronized('huawei', external=True) + def create_volume(self, volume): + """Create a volume.""" + pool_info = self.restclient.find_pool_info() + volume_name = huawei_utils.encode_name(volume['id']) + volume_description = volume['name'] + volume_size = huawei_utils.get_volume_size(volume) + + LOG.info(_LI( + 'Create volume: %(volume)s, size: %(size)s.'), + {'volume': volume_name, + 'size': volume_size}) + + params = huawei_utils.get_lun_conf_params(self.xml_file_path) + params['pool_id'] = pool_info['ID'] + params['volume_size'] = volume_size + params['volume_description'] = volume_description + + # Prepare LUN parameters. + lun_param = huawei_utils.init_lun_parameters(volume_name, params) + + # Create LUN on the array. + lun_info = self.restclient.create_volume(lun_param) + lun_id = lun_info['ID'] + + return {'provider_location': lun_info['ID'], + 'ID': lun_id, + 'lun_info': lun_info} + + @utils.synchronized('huawei', external=True) + def delete_volume(self, volume): + """Delete a volume. + + Three steps: + Firstly, remove associate from lungroup. + Secondly, remove associate from QoS policy. + Thirdly, remove the lun. + """ + name = huawei_utils.encode_name(volume['id']) + lun_id = volume.get('provider_location', None) + LOG.info(_LI('Delete volume: %(name)s, array lun id: %(lun_id)s.'), + {'name': name, 'lun_id': lun_id},) + if lun_id: + if self.restclient.check_lun_exist(lun_id): + self.restclient.delete_lun(lun_id) + else: + LOG.warning(_LW("Can't find %s on the array."), lun_id) + return False + + return True + + def create_volume_from_snapshot(self, volume, snapshot): + """Create a volume from a snapshot. + + We use LUNcopy to copy a new volume from snapshot. + The time needed increases as volume size does. + """ + snapshotname = huawei_utils.encode_name(snapshot['id']) + + snapshot_id = snapshot.get('provider_location', None) + if snapshot_id is None: + snapshot_id = self.restclient.get_snapshotid_by_name(snapshotname) + if snapshot_id is None: + err_msg = (_( + 'create_volume_from_snapshot: Snapshot %(name)s ' + 'does not exist.') + % {'name': snapshotname}) + LOG.error(err_msg) + raise exception.VolumeBackendAPIException(data=err_msg) + + lun_info = self.create_volume(volume) + + tgt_lun_id = lun_info['ID'] + luncopy_name = huawei_utils.encode_name(volume['id']) + + LOG.info(_LI( + 'create_volume_from_snapshot: src_lun_id: %(src_lun_id)s, ' + 'tgt_lun_id: %(tgt_lun_id)s, copy_name: %(copy_name)s.'), + {'src_lun_id': snapshot_id, + 'tgt_lun_id': tgt_lun_id, + 'copy_name': luncopy_name}) + + event_type = 'LUNReadyWaitInterval' + + wait_interval = huawei_utils.get_wait_interval(self.xml_file_path, + event_type) + + def _volume_ready(): + result = self.restclient.get_lun_info(tgt_lun_id) + + if result['HEALTHSTATUS'] == "1": + if result['RUNNINGSTATUS'] == "27": + return True + return False + + huawei_utils.wait_for_condition(self.xml_file_path, + _volume_ready, + wait_interval, + wait_interval * 10) + + self._copy_volume(volume, luncopy_name, + snapshot_id, tgt_lun_id) + + return {'provider_location': lun_info['ID'], + 'lun_info': lun_info} + + def create_cloned_volume(self, volume, src_vref): + """Clone a new volume from an existing volume.""" + # Form the snapshot structure. + snapshot = {'id': uuid.uuid4().__str__(), 'volume_id': src_vref['id']} + + # Create snapshot. + self.create_snapshot(snapshot) + + try: + # Create volume from snapshot. + lun_info = self.create_volume_from_snapshot(volume, snapshot) + finally: + try: + # Delete snapshot. + self.delete_snapshot(snapshot) + except exception.VolumeBackendAPIException: + LOG.warning(_LW( + 'Failure deleting the snapshot %(snapshot_id)s ' + 'of volume %(volume_id)s.'), + {'snapshot_id': snapshot['id'], + 'volume_id': src_vref['id']},) + + return {'provider_location': lun_info['ID'], + 'lun_info': lun_info} + + @utils.synchronized('huawei', external=True) + def extend_volume(self, volume, new_size): + """Extend a volume.""" + volume_size = huawei_utils.get_volume_size(volume) + new_volume_size = int(new_size) * units.Gi / 512 + volume_name = huawei_utils.encode_name(volume['id']) + + LOG.info(_LI( + 'Extend volume: %(volumename)s, oldsize:' + ' %(oldsize)s newsize: %(newsize)s.'), + {'volumename': volume_name, + 'oldsize': volume_size, + 'newsize': new_volume_size},) + + lun_id = self.restclient.get_volume_by_name(volume_name) + + if lun_id is None: + msg = (_( + "Can't find lun info on the array, lun name is: %(name)s.") + % {'name': volume_name}) + LOG.error(msg) + raise exception.VolumeBackendAPIException(data=msg) + + luninfo = self.restclient.extend_volume(lun_id, new_volume_size) + + return {'provider_location': luninfo['ID'], + 'lun_info': luninfo} + + @utils.synchronized('huawei', external=True) + def create_snapshot(self, snapshot): + snapshot_info = self.restclient.create_snapshot(snapshot) + snapshot_id = snapshot_info['ID'] + self.restclient.active_snapshot(snapshot_id) + + return {'provider_location': snapshot_info['ID'], + 'lun_info': snapshot_info} + + @utils.synchronized('huawei', external=True) + def delete_snapshot(self, snapshot): + snapshotname = huawei_utils.encode_name(snapshot['id']) + volume_name = huawei_utils.encode_name(snapshot['volume_id']) + + LOG.info(_LI( + 'stop_snapshot: snapshot name: %(snapshot)s, ' + 'volume name: %(volume)s.'), + {'snapshot': snapshotname, + 'volume': volume_name},) + + snapshot_id = snapshot.get('provider_location', None) + if snapshot_id is None: + snapshot_id = self.restclient.get_snapshotid_by_name(snapshotname) + + if snapshot_id is not None: + if self.restclient.check_snapshot_exist(snapshot_id): + self.restclient.stop_snapshot(snapshot_id) + self.restclient.delete_snapshot(snapshot_id) + else: + LOG.warning(_LW("Can't find snapshot on the array.")) + else: + LOG.warning(_LW("Can't find snapshot on the array.")) + return False + + 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_LENTH): + host_name_before_hash = host_name + host_name = str(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_iqn, + target_ip, + 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, ' + 'TargetPortGroup: %(portgroup_id)s.'), + {'iscsi_iqn': iscsi_iqn, + 'target_ip': target_ip, + '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_LENTH): + host_name_before_hash = host_name + host_name = str(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(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) + + # Return iSCSI properties. + properties = {} + properties['target_discovered'] = False + properties['target_portal'] = ('%s:%s' % (target_ip, '3260')) + properties['target_iqn'] = iscsi_iqn + properties['target_lun'] = int(hostlun_id) + properties['volume_id'] = volume['id'] + + 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): + 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 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 migrate_volume(self, context, volume, host): + return (False, None) + + def create_export(self, context, volume): + """Export a volume.""" + pass + + def ensure_export(self, context, volume): + """Synchronously recreate an export for a volume.""" + pass + + def remove_export(self, context, volume): + """Remove an export for a volume.""" + pass + + def _copy_volume(self, volume, copy_name, src_lun, tgt_lun): + luncopy_id = self.restclient.create_luncopy(copy_name, + src_lun, tgt_lun) + event_type = 'LUNcopyWaitInterval' + wait_interval = huawei_utils.get_wait_interval(self.xml_file_path, + event_type) + + try: + self.restclient.start_luncopy(luncopy_id) + + def _luncopy_complete(): + luncopy_info = self.restclient.get_luncopy_info(luncopy_id) + if luncopy_info['status'] == '40': + # luncopy_info['status'] means for the running status of + # the luncopy. If luncopy_info['status'] is equal to '40', + # this luncopy is completely ready. + return True + elif luncopy_info['state'] != '1': + # luncopy_info['state'] means for the healthy status of the + # luncopy. If luncopy_info['state'] is not equal to '1', + # this means that an error occurred during the LUNcopy + # operation and we should abort it. + err_msg = (_( + 'An error occurred during the LUNcopy operation. ' + 'LUNcopy name: %(luncopyname)s. ' + 'LUNcopy status: %(luncopystatus)s. ' + 'LUNcopy state: %(luncopystate)s.') + % {'luncopyname': luncopy_id, + 'luncopystatus': luncopy_info['status'], + 'luncopystate': luncopy_info['state']},) + LOG.error(err_msg) + raise exception.VolumeBackendAPIException(data=err_msg) + huawei_utils.wait_for_condition(self.xml_file_path, + _luncopy_complete, + wait_interval) + + except Exception: + with excutils.save_and_reraise_exception(): + self.restclient.delete_luncopy(luncopy_id) + self.delete_volume(volume) + + self.restclient.delete_luncopy(luncopy_id) + + +class Huawei18000ISCSIDriver(HuaweiBaseDriver, driver.ISCSIDriver): + """ISCSI driver for Huawei OceanStor 18000 storage arrays. + + Version history: + 1.0.0 - Initial driver + 1.1.0 - Provide Huawei OceanStor 18000 storage volume driver. + """ + + VERSION = "1.1.1" + + def __init__(self, *args, **kwargs): + super(Huawei18000ISCSIDriver, self).__init__(*args, **kwargs) + + def get_volume_stats(self, refresh=False): + """Get volume status.""" + data = HuaweiBaseDriver.get_volume_stats(self, refresh=False) + backend_name = self.configuration.safe_get('volume_backend_name') + data['volume_backend_name'] = backend_name or self.__class__.__name__ + data['storage_protocol'] = 'iSCSI' + data['driver_version'] = self.VERSION + data['vendor_name'] = 'Huawei' + return data + + def initialize_connection(self, volume, connector): + return HuaweiBaseDriver.initialize_connection_iscsi(self, + volume, + connector) + + def terminate_connection(self, volume, connector, **kwargs): + return HuaweiBaseDriver.terminate_connection_iscsi(self, + volume, + connector) + + +class Huawei18000FCDriver(HuaweiBaseDriver, driver.FibreChannelDriver): + """FC driver for Huawei OceanStor 18000 storage arrays. + + Version history: + 1.0.0 - Initial driver + 1.1.0 - Provide Huawei OceanStor 18000 storage volume driver. + """ + + VERSION = "1.1.1" + + def __init__(self, *args, **kwargs): + super(Huawei18000FCDriver, self).__init__(*args, **kwargs) + + def get_volume_stats(self, refresh=False): + """Get volume status.""" + data = HuaweiBaseDriver.get_volume_stats(self, refresh=False) + backend_name = self.configuration.safe_get('volume_backend_name') + data['volume_backend_name'] = backend_name or self.__class__.__name__ + data['storage_protocol'] = 'FC' + data['driver_version'] = self.VERSION + data['verdor_name'] = 'Huawei' + return data + + @fczm_utils.AddFCZone + def initialize_connection(self, volume, connector): + return HuaweiBaseDriver.initialize_connection_fc(self, + volume, + connector) + + @fczm_utils.RemoveFCZone + def terminate_connection(self, volume, connector, **kwargs): + return HuaweiBaseDriver.terminate_connection_fc(self, + volume, + connector) diff --git a/cinder/volume/drivers/huawei/huawei_utils.py b/cinder/volume/drivers/huawei/huawei_utils.py index 02b35d094..ae13da953 100644 --- a/cinder/volume/drivers/huawei/huawei_utils.py +++ b/cinder/volume/drivers/huawei/huawei_utils.py @@ -1,5 +1,4 @@ -# Copyright (c) 2013 Huawei Technologies Co., Ltd. -# Copyright (c) 2012 OpenStack LLC. +# Copyright (c) 2015 Huawei Technologies Co., Ltd. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may @@ -14,32 +13,133 @@ # License for the specific language governing permissions and limitations # under the License. +import base64 +import six +import time +import uuid from xml.etree import ElementTree as ET from oslo_log import log as logging +from oslo_service import loopingcall +from oslo_utils import units -from cinder.i18n import _LE +from cinder import context +from cinder import exception +from cinder import utils +from cinder.i18n import _, _LE, _LI +from cinder.volume.drivers.huawei import constants +from cinder.volume import qos_specs +from cinder.volume import volume_types LOG = logging.getLogger(__name__) -os_type = {'Linux': '0', - 'Windows': '1', - 'Solaris': '2', - 'HP-UX': '3', - 'AIX': '4', - 'XenServer': '5', - 'Mac OS X': '6', - 'VMware ESX': '7'} +opts_capability = { + 'smarttier': False, + 'smartcache': False, + 'smartpartition': False, + 'thin_provisioning': False, + 'thick_provisioning': False, +} -def parse_xml_file(filepath): + +opts_value = { + 'policy': None, + 'partitionname': None, + 'cachename': None, +} + + +opts_associate = { + 'smarttier': 'policy', + 'smartcache': 'cachename', + 'smartpartition': 'partitionname', +} + + +def get_volume_params(volume): + opts = {} + ctxt = context.get_admin_context() + type_id = volume['volume_type_id'] + if type_id is not None: + volume_type = volume_types.get_volume_type(ctxt, type_id) + specs = dict(volume_type).get('extra_specs') + opts = _get_extra_spec_value(specs) + else: + opts.update(opts_capability) + opts.update(opts_value) + + return opts + + +def _get_extra_spec_value(specs): + """Return the parameters for creating the volume.""" + opts = {} + opts.update(opts_capability) + opts.update(opts_value) + + opts = _get_opts_from_specs(opts_capability, opts_value, specs) + LOG.debug('get_volume_params opts %(opts)s.', {'opts': opts}) + + return opts + + +def _get_opts_from_specs(opts_capability, opts_value, specs): + opts = {} + opts.update(opts_capability) + opts.update(opts_value) + + for key, value in specs.iteritems(): + + # Get the scope, if is using scope format. + scope = None + key_split = key.split(':') + if len(key_split) > 2 and key_split[0] != "capabilities": + continue + + if len(key_split) == 1: + key = key_split[0] + else: + scope = key_split[0] + key = key_split[1] + + if scope: + scope = scope.lower() + if key: + key = key.lower() + + if (scope in opts_capability) and (key in opts_value): + if (scope in opts_associate) and (opts_associate[scope] == key): + opts[key] = value + + return opts + + +def _get_smartx_specs_params(lunsetinfo, smartx_opts): + """Get parameters from config file for creating lun.""" + # Default lun set information. + if 'LUNType' in smartx_opts: + lunsetinfo['LUNType'] = smartx_opts['LUNType'] + lunsetinfo['policy'] = smartx_opts['policy'] + + return lunsetinfo + + +def get_lun_params(xml_file_path, smartx_opts): + lunsetinfo = {} + lunsetinfo = get_lun_conf_params(xml_file_path) + lunsetinfo = _get_smartx_specs_params(lunsetinfo, smartx_opts) + return lunsetinfo + + +def parse_xml_file(xml_file_path): """Get root of xml file.""" try: - tree = ET.parse(filepath) + tree = ET.parse(xml_file_path) root = tree.getroot() return root except IOError as err: - LOG.error(_LE('parse_xml_file: %s'), err) + LOG.error(_LE('parse_xml_file: %s.'), err) raise @@ -64,49 +164,7 @@ def get_xml_item(xml_root, item): return items_list -def is_xml_item_exist(xml_root, item, attrib_key=None): - """Check if the given item exits in xml config file. - - :param xml_root: The root of xml tree - :param item: The xml tag to check - :param attrib_key: The xml attrib to check - :return: True of False - """ - items_list = get_xml_item(xml_root, item) - if attrib_key: - for tmp_dict in items_list: - if tmp_dict['attrib'].get(attrib_key, None): - return True - else: - if items_list and items_list[0]['text']: - return True - return False - - -def is_xml_item_valid(xml_root, item, valid_list, attrib_key=None): - """Check if the given item is valid in xml config file. - - :param xml_root: The root of xml tree - :param item: The xml tag to check - :param valid_list: The valid item value - :param attrib_key: The xml attrib to check - :return: True of False - """ - items_list = get_xml_item(xml_root, item) - if attrib_key: - for tmp_dict in items_list: - value = tmp_dict['attrib'].get(attrib_key, None) - if value not in valid_list: - return False - else: - value = items_list[0]['text'] - if value not in valid_list: - return False - - return True - - -def get_conf_host_os_type(host_ip, config): +def get_conf_host_os_type(host_ip, conf): """Get host OS type from xml config file. :param host_ip: The IP of Nova host @@ -114,7 +172,8 @@ def get_conf_host_os_type(host_ip, config): :return: host OS type """ os_conf = {} - root = parse_xml_file(config) + xml_file_path = conf.cinder_huawei_conf_file + root = parse_xml_file(xml_file_path) hosts_list = get_xml_item(root, 'Host') for host in hosts_list: os = host['attrib']['OSType'].strip() @@ -123,11 +182,302 @@ def get_conf_host_os_type(host_ip, config): host_os = None for k, v in os_conf.items(): if host_ip in v: - host_os = os_type.get(k, None) + host_os = constants.OS_TYPE.get(k, None) if not host_os: - host_os = os_type['Linux'] # default os type + host_os = constants.OS_TYPE['Linux'] # Default OS type. LOG.debug('_get_host_os_type: Host %(ip)s OS type is %(os)s.', {'ip': host_ip, 'os': host_os}) return host_os + + +def get_qos_by_volume_type(volume_type): + qos = {} + qos_specs_id = volume_type.get('qos_specs_id') + + # We prefer the qos_specs association + # and override any existing extra-specs settings + # if present. + if qos_specs_id is not None: + kvs = qos_specs.get_qos_specs(context.get_admin_context(), + qos_specs_id)['specs'] + else: + return qos + + LOG.info(_LI('The QoS sepcs is: %s.'), kvs) + for key, value in kvs.iteritems(): + if key in constants.HUAWEI_VALID_KEYS: + if (key.upper() != 'IOTYPE') and (int(value) <= 0): + err_msg = (_('Qos config is wrong. %(key)s' + ' must be set greater than 0.') + % {'key': key}) + LOG.error(err_msg) + raise exception.VolumeBackendAPIException(data=err_msg) + elif (key.upper() == 'IOTYPE') and (value not in ['0', '1', '2']): + raise exception.InvalidInput( + reason=(_('Illegal value specified for IOTYPE: ' + 'set to either 0, 1, or 2.'))) + else: + qos[key.upper()] = value + + return qos + + +def get_volume_qos(volume): + qos = {} + ctxt = context.get_admin_context() + type_id = volume['volume_type_id'] + if type_id is not None: + volume_type = volume_types.get_volume_type(ctxt, type_id) + qos = get_qos_by_volume_type(volume_type) + + return qos + + +def _get_volume_type(type_id): + ctxt = context.get_admin_context() + return volume_types.get_volume_type(ctxt, type_id) + + +def get_lun_conf_params(xml_file_path): + """Get parameters from config file for creating lun.""" + lunsetinfo = { + 'LUNType': 'Thick', + 'StripUnitSize': '64', + 'WriteType': '1', + 'MirrorSwitch': '1', + 'PrefetchType': '3', + 'PrefetchValue': '0', + 'PrefetchTimes': '0', + 'policy': '0', + 'readcachepolicy': '2', + 'writecachepolicy': '5', + } + # Default lun set information. + root = parse_xml_file(xml_file_path) + luntype = root.findtext('LUN/LUNType') + if luntype: + if luntype.strip() in ['Thick', 'Thin']: + lunsetinfo['LUNType'] = luntype.strip() + if luntype.strip() == 'Thick': + lunsetinfo['LUNType'] = 0 + if luntype.strip() == 'Thin': + lunsetinfo['LUNType'] = 1 + + elif luntype is not '' and luntype is not None: + err_msg = (_( + 'Config file is wrong. LUNType must be "Thin"' + ' or "Thick". LUNType: %(fetchtype)s.') + % {'fetchtype': luntype}) + LOG.error(err_msg) + raise exception.VolumeBackendAPIException(data=err_msg) + + stripunitsize = root.findtext('LUN/StripUnitSize') + if stripunitsize is not None: + lunsetinfo['StripUnitSize'] = stripunitsize.strip() + writetype = root.findtext('LUN/WriteType') + if writetype is not None: + lunsetinfo['WriteType'] = writetype.strip() + mirrorswitch = root.findtext('LUN/MirrorSwitch') + if mirrorswitch is not None: + lunsetinfo['MirrorSwitch'] = mirrorswitch.strip() + + prefetch = root.find('LUN/Prefetch') + fetchtype = prefetch.attrib['Type'] + if prefetch is not None and prefetch.attrib['Type']: + if fetchtype in ['0', '1', '2', '3']: + lunsetinfo['PrefetchType'] = fetchtype.strip() + typevalue = prefetch.attrib['Value'].strip() + if lunsetinfo['PrefetchType'] == '1': + double_value = int(typevalue) * 2 + typevalue_double = six.text_type(double_value) + lunsetinfo['PrefetchValue'] = typevalue_double + elif lunsetinfo['PrefetchType'] == '2': + lunsetinfo['PrefetchValue'] = typevalue + else: + err_msg = (_( + 'PrefetchType config is wrong. PrefetchType' + ' must be in 0,1,2,3. PrefetchType is: %(fetchtype)s.') + % {'fetchtype': fetchtype}) + LOG.error(err_msg) + raise exception.VolumeBackendAPIException(data=err_msg) + else: + LOG.info(_LI( + 'Use default PrefetchType. ' + 'PrefetchType: Intelligent.')) + + return lunsetinfo + + +def encode_name(name): + uuid_str = name.replace("-", "") + vol_uuid = uuid.UUID('urn:uuid:%s' % uuid_str) + vol_encoded = base64.urlsafe_b64encode(vol_uuid.bytes) + vol_encoded = vol_encoded.decode("utf-8") # Make it compatible to py3. + newuuid = vol_encoded.replace("=", "") + return newuuid + + +def init_lun_parameters(name, parameters): + """Initialize basic LUN parameters.""" + lunparam = {"TYPE": "11", + "NAME": name, + "PARENTTYPE": "216", + "PARENTID": parameters['pool_id'], + "DESCRIPTION": parameters['volume_description'], + "ALLOCTYPE": parameters['LUNType'], + "CAPACITY": parameters['volume_size'], + "WRITEPOLICY": parameters['WriteType'], + "MIRRORPOLICY": parameters['MirrorSwitch'], + "PREFETCHPOLICY": parameters['PrefetchType'], + "PREFETCHVALUE": parameters['PrefetchValue'], + "DATATRANSFERPOLICY": parameters['policy'], + "READCACHEPOLICY": parameters['readcachepolicy'], + "WRITECACHEPOLICY": parameters['writecachepolicy'], + } + + return lunparam + + +def volume_in_use(volume): + """Check if the given volume is in use.""" + return (volume['volume_attachment'] and + len(volume['volume_attachment']) > 0) + + +def get_wait_interval(xml_file_path, event_type): + """Get wait interval from huawei conf file.""" + root = parse_xml_file(xml_file_path) + wait_interval = root.findtext('LUN/%s' % event_type) + if wait_interval is None: + wait_interval = constants.DEFAULT_WAIT_INTERVAL + LOG.info(_LI( + "Wait interval for %(event_type)s is not configured in huawei " + "conf file. Use default: %(default_wait_interval)d."), + {"event_type": event_type, + "default_wait_interval": wait_interval}) + + return int(wait_interval) + + +def _get_default_timeout(xml_file_path): + """Get timeout from huawei conf file.""" + root = parse_xml_file(xml_file_path) + timeout = root.findtext('LUN/Timeout') + if timeout is None: + timeout = constants.DEFAULT_WAIT_TIMEOUT + LOG.info(_LI( + "Timeout is not configured in huawei conf file. " + "Use default: %(default_timeout)d."), + {"default_timeout": timeout}) + + return timeout + + +def wait_for_condition(xml_file_path, func, interval, timeout=None): + start_time = time.time() + if timeout is None: + timeout = _get_default_timeout(xml_file_path) + + def _inner(): + try: + res = func() + except Exception as ex: + raise exception.VolumeBackendAPIException(ex) + if res: + raise loopingcall.LoopingCallDone() + + if int(time.time()) - start_time > timeout: + msg = (_('wait_for_condition: %s timed out.') + % func.__name__) + LOG.error(msg) + raise exception.VolumeBackendAPIException(data=msg) + + timer = loopingcall.FixedIntervalLoopingCall(_inner) + timer.start(interval=interval).wait() + + +def get_login_info(xml_file_path): + """Get login IP, user name and password from config file.""" + logininfo = {} + root = parse_xml_file(xml_file_path) + + logininfo['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 + + return logininfo + + +def _change_file_mode(filepath): + utils.execute('chmod', '640', filepath, run_as_root=True) + + +def get_iscsi_conf(xml_file_path): + """Get iSCSI info from config file.""" + iscsiinfo = {} + root = parse_xml_file(xml_file_path) + TargetIP = root.findtext('iSCSI/DefaultTargetIP').strip() + iscsiinfo['DefaultTargetIP'] = TargetIP + initiator_list = [] + + for dic in root.findall('iSCSI/Initiator'): + # Strip values of dict. + tmp_dic = {} + for k in dic.items(): + tmp_dic[k[0]] = k[1].strip() + + initiator_list.append(tmp_dic) + + iscsiinfo['Initiator'] = initiator_list + + return iscsiinfo + + +def check_qos_high_priority(qos): + """Check QoS priority.""" + for key, value in qos.iteritems(): + if (key.find('MIN') == 0) or (key.find('LATENCY') == 0): + return True + + return False + + +def check_conf_file(xml_file_path): + """Check the config file, make sure the essential items are set.""" + root = parse_xml_file(xml_file_path) + resturl = root.findtext('Storage/RestURL') + username = root.findtext('Storage/UserName') + pwd = root.findtext('Storage/UserPassword') + pool_node = root.findall('Storage/StoragePool') + + if (not resturl) or (not username) or (not pwd): + err_msg = (_( + 'check_conf_file: Config file invalid. RestURL,' + ' UserName and UserPassword must be set.')) + LOG.error(err_msg) + raise exception.InvalidInput(reason=err_msg) + + if not pool_node: + err_msg = (_( + 'check_conf_file: Config file invalid. ' + 'StoragePool must be set.')) + LOG.error(err_msg) + raise exception.InvalidInput(reason=err_msg) + + +def get_volume_size(volume): + """Calculate the volume size. + + We should divide the given volume size by 512 for the 18000 system + calculates volume size with sectors, which is 512 bytes. + """ + volume_size = units.Gi / 512 # 1G + if int(volume['size']) != 0: + volume_size = int(volume['size']) * units.Gi / 512 + + return volume_size diff --git a/cinder/volume/drivers/huawei/rest_client.py b/cinder/volume/drivers/huawei/rest_client.py new file mode 100644 index 000000000..0501a35c6 --- /dev/null +++ b/cinder/volume/drivers/huawei/rest_client.py @@ -0,0 +1,1219 @@ +# 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. + +import json +import time + +from oslo_log import log as logging +from oslo_utils import excutils +from six.moves import http_cookiejar +from six.moves import urllib + +from cinder import exception +from cinder.i18n import _, _LE, _LI, _LW +from cinder.volume.drivers.huawei import constants +from cinder.volume.drivers.huawei import huawei_utils + +LOG = logging.getLogger(__name__) + + +class RestClient(object): + """Common class for Huawei OceanStor 18000 storage system.""" + + def __init__(self, configuration): + self.configuration = configuration + self.xml_file_path = configuration.cinder_huawei_conf_file + self.cookie = http_cookiejar.CookieJar() + self.url = None + self.productversion = None + self.headers = {"Connection": "keep-alive", + "Content-Type": "application/json"} + + def call(self, url=False, data=None, method=None): + """Send requests to 18000 server. + + Send HTTPS call, get response in JSON. + Convert response into Python Object and return it. + """ + opener = urllib.build_opener(urllib.HTTPCookieProcessor(self.cookie)) + urllib.install_opener(opener) + res_json = None + + try: + urllib.socket.setdefaulttimeout(720) + req = urllib.Request(url, data, self.headers) + if method: + req.get_method = lambda: method + res = urllib.urlopen(req).read().decode("utf-8") + + if "xx/sessions" not in url: + LOG.info(_LI('\n\n\n\nRequest URL: %(url)s\n\n' + 'Call Method: %(method)s\n\n' + 'Request Data: %(data)s\n\n' + 'Response Data:%(res)s\n\n'), {'url': url, + 'method': method, + 'data': data, + 'res': res}) + + except Exception as err: + LOG.error(_LE('Bad response from server: %(url)s.' + ' Error: %(err)s'), {'url': url, 'err': err}) + json_msg = ('{"error":{"code": %s,"description": "Connect to ' + 'server error."}}'), constants.ERROR_CONNECT_TO_SERVER + res_json = json.loads(json_msg) + return res_json + + try: + res_json = json.loads(res) + except Exception as err: + LOG.error(_LE('JSON transfer error: %s.'), err) + raise + + return res_json + + def login(self): + """Login 18000 array.""" + login_info = huawei_utils.get_login_info(self.xml_file_path) + urlstr = login_info['RestURL'] + url_list = urlstr.split(";") + for item_url in url_list: + url = item_url + "xx/sessions" + data = json.dumps({"username": login_info['UserName'], + "password": login_info['UserPassword'], + "scope": "0"}) + result = self.call(url, data) + + if result['error']['code'] == constants.ERROR_CONNECT_TO_SERVER: + continue + + if (result['error']['code'] != 0) or ("data" not in result): + msg = (_("Login error, reason is: %s.") % result) + LOG.error(msg) + raise exception.VolumeBackendAPIException(data=msg) + + deviceid = result['data']['deviceid'] + self.url = item_url + deviceid + self.headers['iBaseToken'] = result['data']['iBaseToken'] + return deviceid + + msg = _("Login error: Can't connect to server.") + LOG.error(msg) + raise exception.VolumeBackendAPIException(data=msg) + + def _assert_rest_result(self, result, err_str): + error_code = result['error']['code'] + if error_code != 0: + msg = (_('%(err)s\nresult: %(res)s.') % {'err': err_str, + 'res': result}) + LOG.error(msg) + raise exception.VolumeBackendAPIException(data=msg) + + def _assert_data_in_result(self, result, msg): + if "data" not in result: + err_msg = (_('%s "data" was not in result.') % msg) + LOG.error(err_msg) + raise exception.VolumeBackendAPIException(data=err_msg) + + def create_volume(self, lun_param): + url = self.url + "/lun" + data = json.dumps(lun_param) + result = self.call(url, data) + + msg = 'Create volume error.' + self._assert_rest_result(result, msg) + self._assert_data_in_result(result, msg) + + return result['data'] + + def check_lun_exist(self, lun_id): + url = self.url + "/lun/" + lun_id + data = json.dumps({"TYPE": "11", + "ID": lun_id}) + result = self.call(url, data, "GET") + error_code = result['error']['code'] + if error_code != 0: + return False + + 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) + + url = self.url + "/lun/" + lun_id + data = json.dumps({"TYPE": "11", + "ID": lun_id}) + result = self.call(url, data, "DELETE") + self._assert_rest_result(result, 'Delete lun error.') + + def find_pool_info(self): + root = huawei_utils.parse_xml_file(self.xml_file_path) + pool_name = root.findtext('Storage/StoragePool') + if not pool_name: + err_msg = (_("Invalid resource pool: %s.") % pool_name) + LOG.error(err_msg) + raise exception.InvalidInput(err_msg) + + url = self.url + "/storagepool" + result = self.call(url, None) + self._assert_rest_result(result, 'Query resource pool error.') + + poolinfo = {} + if "data" in result: + for item in result['data']: + if pool_name.strip() == item['NAME']: + poolinfo['ID'] = item['ID'] + poolinfo['CAPACITY'] = item['USERFREECAPACITY'] + poolinfo['TOTALCAPACITY'] = item['USERTOTALCAPACITY'] + break + if not poolinfo: + msg = (_('Get pool info error, pool name is: %s.') % pool_name) + LOG.error(msg) + raise exception.VolumeBackendAPIException(data=msg) + return poolinfo + + def _get_id_from_result(self, result, name, key): + if "data" in result: + for item in result['data']: + if name == item[key]: + return item['ID'] + + def get_volume_by_name(self, name): + url = self.url + "/lun?range=[0-65535]" + result = self.call(url, None, "GET") + self._assert_rest_result(result, 'Get volume by name error.') + + return self._get_id_from_result(result, name, 'NAME') + + def active_snapshot(self, snapshot_id): + activeurl = self.url + "/snapshot/activate" + data = json.dumps({"SNAPSHOTLIST": [snapshot_id]}) + result = self.call(activeurl, data) + self._assert_rest_result(result, 'Active snapshot error.') + + def create_snapshot(self, snapshot): + snapshot_name = huawei_utils.encode_name(snapshot['id']) + snapshot_description = snapshot['id'] + volume_name = huawei_utils.encode_name(snapshot['volume_id']) + + LOG.info(_LI( + 'create_snapshot:snapshot name: %(snapshot)s, ' + 'volume name: %(volume)s.'), + {'snapshot': snapshot_name, + 'volume': volume_name}) + + lun_id = self.get_volume_by_name(volume_name) + if lun_id is None: + msg = (_("Can't find lun info on the array, " + "lun name is: %(name)s.") % {'name': volume_name}) + LOG.error(msg) + raise exception.VolumeBackendAPIException(data=msg) + + url = self.url + "/snapshot" + data = json.dumps({"TYPE": "27", + "NAME": snapshot_name, + "PARENTTYPE": "11", + "DESCRIPTION": snapshot_description, + "PARENTID": lun_id}) + result = self.call(url, data) + + msg = 'Create snapshot error.' + self._assert_rest_result(result, msg) + self._assert_data_in_result(result, msg) + + return result['data'] + + def check_snapshot_exist(self, snapshot_id): + url = self.url + "/snapshot/" + snapshot_id + data = json.dumps({"TYPE": "27", + "ID": snapshot_id}) + result = self.call(url, data, "GET") + error_code = result['error']['code'] + if error_code != 0: + return False + + return True + + def stop_snapshot(self, snapshot_id): + url = self.url + "/snapshot/stop" + stopdata = json.dumps({"ID": snapshot_id}) + result = self.call(url, stopdata, "PUT") + self._assert_rest_result(result, 'Stop snapshot error.') + + def delete_snapshot(self, snapshotid): + url = self.url + "/snapshot/%s" % snapshotid + data = json.dumps({"TYPE": "27", "ID": snapshotid}) + result = self.call(url, data, "DELETE") + self._assert_rest_result(result, 'Delete snapshot error.') + + def get_snapshotid_by_name(self, name): + url = self.url + "/snapshot?range=[0-32767]" + data = json.dumps({"TYPE": "27"}) + result = self.call(url, data, "GET") + self._assert_rest_result(result, 'Get snapshot id error.') + + return self._get_id_from_result(result, name, 'NAME') + + def create_luncopy(self, luncopyname, srclunid, tgtlunid): + """Create a luncopy.""" + url = self.url + "/luncopy" + data = json.dumps({"TYPE": 219, + "NAME": luncopyname, + "DESCRIPTION": luncopyname, + "COPYSPEED": 2, + "LUNCOPYTYPE": "1", + "SOURCELUN": ("INVALID;%s;INVALID;INVALID;INVALID" + % srclunid), + "TARGETLUN": ("INVALID;%s;INVALID;INVALID;INVALID" + % tgtlunid)}) + result = self.call(url, data) + + msg = 'Create luncopy error.' + self._assert_rest_result(result, msg) + self._assert_data_in_result(result, msg) + + return result['data']['ID'] + + def add_host_into_hostgroup(self, host_id): + """Associate host to hostgroup. + + If hostgroup doesn't exist, create one. + """ + host_group_name = constants.HOSTGROUP_PREFIX + host_id + hostgroup_id = self._create_hostgroup_with_check(host_group_name) + is_associated = self._is_host_associate_to_hostgroup(hostgroup_id, + host_id) + if not is_associated: + self._associate_host_to_hostgroup(hostgroup_id, host_id) + + return hostgroup_id + + def find_tgt_port_group(self, tgt_port_group): + """Find target portgroup id by target port group name.""" + url = self.url + "/portgroup?range=[0-8191]&TYPE=257" + result = self.call(url, None, "GET") + + msg = 'Find portgroup error.' + self._assert_rest_result(result, msg) + msg = 'Can not find the portgroup on the array.' + self._assert_data_in_result(result, msg) + + return self._get_id_from_result(result, tgt_port_group, 'NAME') + + def _associate_portgroup_to_view(self, view_id, portgroup_id): + url = self.url + "/MAPPINGVIEW/CREATE_ASSOCIATE" + data = json.dumps({"ASSOCIATEOBJTYPE": "257", + "ASSOCIATEOBJID": portgroup_id, + "TYPE": "245", + "ID": view_id}) + result = self.call(url, data, "PUT") + self._assert_rest_result(result, 'Associate portgroup to mapping ' + 'view error.') + + def _portgroup_associated(self, view_id, portgroup_id): + url_subfix = ("/mappingview/associate?TYPE=245&" + "ASSOCIATEOBJTYPE=257&ASSOCIATEOBJID=%s" % portgroup_id) + url = self.url + url_subfix + 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 + + return False + + def mapping_hostgroup_and_lungroup(self, volume_name, hostgroup_id, + host_id, tgtportgroup_id=None): + """Add hostgroup and lungroup to mapping view.""" + lungroup_name = constants.LUNGROUP_PREFIX + host_id + mapping_view_name = constants.MAPPING_VIEW_PREFIX + host_id + lungroup_id = self._find_lungroup(lungroup_name) + lun_id = self.get_volume_by_name(volume_name) + view_id = self.find_mapping_view(mapping_view_name) + + LOG.info(_LI( + 'mapping_hostgroup_and_lungroup, lun_group: %(lun_group)s, ' + 'view_id: %(view_id)s, lun_id: %(lun_id)s.'), + {'lun_group': lungroup_id, + 'view_id': view_id, + 'lun_id': lun_id}) + + try: + # Create lungroup and add LUN into to lungroup. + if lungroup_id is None: + lungroup_id = self._create_lungroup(lungroup_name) + is_associated = self._is_lun_associated_to_lungroup(lungroup_id, + lun_id) + if not is_associated: + self._associate_lun_to_lungroup(lungroup_id, lun_id) + + if view_id is None: + view_id = self._add_mapping_view(mapping_view_name) + self._associate_hostgroup_to_view(view_id, hostgroup_id) + self._associate_lungroup_to_view(view_id, lungroup_id) + if tgtportgroup_id: + self._associate_portgroup_to_view(view_id, tgtportgroup_id) + + else: + if not self.hostgroup_associated(view_id, hostgroup_id): + self._associate_hostgroup_to_view(view_id, hostgroup_id) + if not self.lungroup_associated(view_id, lungroup_id): + self._associate_lungroup_to_view(view_id, lungroup_id) + if tgtportgroup_id: + if not self._portgroup_associated(view_id, + tgtportgroup_id): + self._associate_portgroup_to_view(view_id, + tgtportgroup_id) + + except Exception: + with excutils.save_and_reraise_exception(): + err_msg = (_LE( + 'Error occurred when adding hostgroup and lungroup to ' + 'view. Remove lun from lungroup now.')) + LOG.error(err_msg) + self.remove_lun_from_lungroup(lungroup_id, lun_id) + + return lun_id + + def ensure_initiator_added(self, initiator_name, host_id): + added = self._initiator_is_added_to_array(initiator_name) + if not added: + self._add_initiator_to_array(initiator_name) + if not self.is_initiator_associated_to_host(initiator_name): + self._associate_initiator_to_host(initiator_name, host_id) + + def _get_iscsi_tgt_port(self): + url = self.url + "/iscsidevicename" + result = self.call(url, None) + + msg = 'Get iSCSI target port error.' + self._assert_rest_result(result, msg) + self._assert_data_in_result(result, msg) + + return result['data'][0]['CMO_ISCSI_DEVICE_NAME'] + + def find_hostgroup(self, groupname): + """Get the given hostgroup id.""" + url = self.url + "/hostgroup?range=[0-8191]" + result = self.call(url, None, "GET") + self._assert_rest_result(result, 'Get hostgroup information error.') + + return self._get_id_from_result(result, groupname, 'NAME') + + def _find_lungroup(self, lungroup_name): + """Get the given hostgroup id.""" + url = self.url + "/lungroup?range=[0-8191]" + result = self.call(url, None, "GET") + self._assert_rest_result(result, 'Get lungroup information error.') + + return self._get_id_from_result(result, lungroup_name, 'NAME') + + def _create_hostgroup_with_check(self, host_group_name): + """Check if host exists on the array, or create it.""" + hostgroup_id = self.find_hostgroup(host_group_name) + if hostgroup_id: + LOG.info(_LI( + '_create_hostgroup_with_check. ' + 'hostgroup name: %(name)s, ' + 'hostgroup id: %(id)s'), + {'name': host_group_name, + 'id': hostgroup_id}) + return hostgroup_id + + try: + hostgroup_id = self._create_hostgroup(host_group_name) + except Exception: + LOG.info(_LI( + 'Failed to create hostgroup: %(name)s. ' + 'Please check if it exists on the array.'), + {'name': host_group_name}) + hostgroup_id = self.find_hostgroup(host_group_name) + if hostgroup_id is None: + err_msg = (_( + 'Failed to create hostgroup: %(name)s. ' + 'Check if it exists on the array.'), + {'name': host_group_name}) + LOG.error(err_msg) + raise exception.VolumeBackendAPIException(data=err_msg) + + LOG.info(_LI( + '_create_hostgroup_with_check. ' + 'Create hostgroup success. ' + 'hostgroup name: %(name)s, ' + 'hostgroup id: %(id)s'), + {'name': host_group_name, + 'id': hostgroup_id}) + return hostgroup_id + + def _create_hostgroup(self, hostgroupname): + url = self.url + "/hostgroup" + data = json.dumps({"TYPE": "14", "NAME": hostgroupname}) + result = self.call(url, data) + + msg = 'Create hostgroup error.' + self._assert_rest_result(result, msg) + self._assert_data_in_result(result, msg) + + return result['data']['ID'] + + def _create_lungroup(self, lungroup_name): + url = self.url + "/lungroup" + data = json.dumps({"DESCRIPTION": lungroup_name, + "APPTYPE": '0', + "GROUPTYPE": '0', + "NAME": lungroup_name}) + result = self.call(url, data) + + msg = 'Create lungroup error.' + self._assert_rest_result(result, msg) + self._assert_data_in_result(result, msg) + + return result['data']['ID'] + + def delete_lungroup(self, lungroup_id): + url = self.url + "/LUNGroup/" + lungroup_id + result = self.call(url, None, "DELETE") + self._assert_rest_result(result, 'Delete lungroup error.') + + def lungroup_associated(self, view_id, lungroup_id): + url_subfix = ("/mappingview/associate?TYPE=245&" + "ASSOCIATEOBJTYPE=256&ASSOCIATEOBJID=%s" % lungroup_id) + url = self.url + url_subfix + 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 + return False + + def hostgroup_associated(self, view_id, hostgroup_id): + url_subfix = ("/mappingview/associate?TYPE=245&" + "ASSOCIATEOBJTYPE=14&ASSOCIATEOBJID=%s" % hostgroup_id) + url = self.url + url_subfix + 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 + return False + + def find_host_lun_id(self, host_id, lun_id): + url = self.url + ("/lun/associate?TYPE=11&ASSOCIATEOBJTYPE=21" + "&ASSOCIATEOBJID=%s" % (host_id)) + result = self.call(url, None, "GET") + self._assert_rest_result(result, 'Find host lun id error.') + + host_lun_id = 1 + if "data" in result: + for item in result['data']: + if lun_id == item['ID']: + associate_data = item['ASSOCIATEMETADATA'] + try: + hostassoinfo = json.loads(associate_data) + host_lun_id = hostassoinfo['HostLUNID'] + break + except Exception as err: + LOG.error(_LE("JSON transfer data error. %s."), err) + raise + return host_lun_id + + def find_host(self, hostname): + """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') + + def add_host_with_check(self, host_name, host_name_before_hash): + host_id = self.find_host(host_name) + if host_id: + LOG.info(_LI( + 'add_host_with_check. ' + 'host name: %(name)s, ' + 'host id: %(id)s'), + {'name': host_name, + 'id': host_id}) + return host_id + + try: + host_id = self._add_host(host_name, host_name_before_hash) + except Exception: + LOG.info(_LI( + 'Failed to create host: %(name)s. ' + 'Check if it exists on the array.'), + {'name': host_name}) + host_id = self.find_host(host_name) + if not host_id: + err_msg = (_( + 'Failed to create host: %(name)s. ' + 'Please check if it exists on the array.'), + {'name': host_name}) + LOG.error(err_msg) + raise exception.VolumeBackendAPIException(data=err_msg) + + LOG.info(_LI( + 'add_host_with_check. ' + 'create host success. ' + 'host name: %(name)s, ' + 'host id: %(id)s'), + {'name': host_name, + 'id': host_id}) + return host_id + + def _add_host(self, hostname, host_name_before_hash): + """Add a new host.""" + url = self.url + "/host" + data = json.dumps({"TYPE": "21", + "NAME": hostname, + "OPERATIONSYSTEM": "0", + "DESCRIPTION": host_name_before_hash}) + result = self.call(url, data) + self._assert_rest_result(result, 'Add new host error.') + + if "data" in result: + return result['data']['ID'] + else: + return + + def _is_host_associate_to_hostgroup(self, hostgroup_id, host_id): + """Check whether the host is associated to the hostgroup.""" + url_subfix = ("/host/associate?TYPE=21&" + "ASSOCIATEOBJTYPE=14&ASSOCIATEOBJID=%s" % hostgroup_id) + + url = self.url + url_subfix + 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 + return False + + def _is_lun_associated_to_lungroup(self, lungroup_id, lun_id): + """Check whether the lun is associated to the lungroup.""" + url_subfix = ("/lun/associate?TYPE=11&" + "ASSOCIATEOBJTYPE=256&ASSOCIATEOBJID=%s" % lungroup_id) + + url = self.url + url_subfix + result = self.call(url, None, "GET") + self._assert_rest_result(result, 'Check lungroup associate error.') + + if self._get_id_from_result(result, lun_id, 'ID'): + return True + + return False + + def _associate_host_to_hostgroup(self, hostgroup_id, host_id): + url = self.url + "/hostgroup/associate" + data = json.dumps({"TYPE": "14", + "ID": hostgroup_id, + "ASSOCIATEOBJTYPE": "21", + "ASSOCIATEOBJID": host_id}) + + result = self.call(url, data) + self._assert_rest_result(result, 'Associate host to hostgroup error.') + + def _associate_lun_to_lungroup(self, lungroup_id, lun_id): + """Associate lun to lungroup.""" + url = self.url + "/lungroup/associate" + data = json.dumps({"ID": lungroup_id, + "ASSOCIATEOBJTYPE": "11", + "ASSOCIATEOBJID": lun_id}) + result = self.call(url, data) + self._assert_rest_result(result, 'Associate lun to lungroup error.') + + def remove_lun_from_lungroup(self, lungroup_id, lun_id): + """Remove lun from lungroup.""" + url = self.url + ("/lungroup/associate?ID=%s" + "&ASSOCIATEOBJTYPE=11&ASSOCIATEOBJID=%s" + % (lungroup_id, lun_id)) + + result = self.call(url, None, 'DELETE') + self._assert_rest_result(result, + 'Delete associated lun from lungroup error.') + + def _initiator_is_added_to_array(self, ininame): + """Check whether the initiator is already added on the array.""" + url = self.url + "/iscsi_initiator?range=[0-256]" + result = self.call(url, None, "GET") + self._assert_rest_result(result, + 'Check initiator added to array error.') + + if "data" in result: + for item in result['data']: + if item["ID"] == ininame: + return True + return False + + def is_initiator_associated_to_host(self, ininame): + """Check whether the initiator is associated to the host.""" + url = self.url + "/iscsi_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 False + return True + + def _add_initiator_to_array(self, initiator_name): + """Add a new initiator to storage device.""" + url = self.url + "/iscsi_initiator/" + data = json.dumps({"TYPE": "222", + "ID": initiator_name, + "USECHAP": "false"}) + result = self.call(url, data) + self._assert_rest_result(result, 'Add initiator to array error.') + + def _associate_initiator_to_host(self, ininame, host_id): + """Associate initiator with the host.""" + url = self.url + "/iscsi_initiator/" + ininame + data = json.dumps({"TYPE": "222", + "ID": ininame, + "USECHAP": "false", + "PARENTTYPE": "21", + "PARENTID": host_id}) + result = self.call(url, data, "PUT") + self._assert_rest_result(result, 'Associate initiator to host error.') + + def find_mapping_view(self, name): + """Find mapping view.""" + url = self.url + "/mappingview?range=[0-8191]" + data = json.dumps({"TYPE": "245"}) + result = self.call(url, data, "GET") + + msg = 'Find mapping view error.' + self._assert_rest_result(result, msg) + + return self._get_id_from_result(result, name, 'NAME') + + def _add_mapping_view(self, name): + url = self.url + "/mappingview" + data = json.dumps({"NAME": name, "TYPE": "245"}) + result = self.call(url, data) + self._assert_rest_result(result, 'Add mapping view error.') + + return result['data']['ID'] + + def _associate_hostgroup_to_view(self, viewID, hostGroupID): + url = self.url + "/MAPPINGVIEW/CREATE_ASSOCIATE" + data = json.dumps({"ASSOCIATEOBJTYPE": "14", + "ASSOCIATEOBJID": hostGroupID, + "TYPE": "245", + "ID": viewID}) + result = self.call(url, data, "PUT") + self._assert_rest_result(result, 'Associate host to mapping view ' + 'error.') + + def _associate_lungroup_to_view(self, viewID, lunGroupID): + url = self.url + "/MAPPINGVIEW/CREATE_ASSOCIATE" + data = json.dumps({"ASSOCIATEOBJTYPE": "256", + "ASSOCIATEOBJID": lunGroupID, + "TYPE": "245", + "ID": viewID}) + result = self.call(url, data, "PUT") + self._assert_rest_result(result, 'Associate lungroup to mapping view ' + 'error.') + + def delete_lungroup_mapping_view(self, view_id, lungroup_id): + """Remove lungroup associate from the mapping view.""" + url = self.url + "/mappingview/REMOVE_ASSOCIATE" + data = json.dumps({"ASSOCIATEOBJTYPE": "256", + "ASSOCIATEOBJID": lungroup_id, + "TYPE": "245", + "ID": view_id}) + result = self.call(url, data, "PUT") + self._assert_rest_result(result, 'Delete lungroup from mapping view ' + 'error.') + + def delete_hostgoup_mapping_view(self, view_id, hostgroup_id): + """Remove hostgroup associate from the mapping view.""" + url = self.url + "/mappingview/REMOVE_ASSOCIATE" + data = json.dumps({"ASSOCIATEOBJTYPE": "14", + "ASSOCIATEOBJID": hostgroup_id, + "TYPE": "245", + "ID": view_id}) + result = self.call(url, data, "PUT") + self._assert_rest_result(result, 'Delete hostgroup from mapping view ' + 'error.') + + def delete_portgroup_mapping_view(self, view_id, portgroup_id): + """Remove portgroup associate from the mapping view.""" + url = self.url + "/mappingview/REMOVE_ASSOCIATE" + data = json.dumps({"ASSOCIATEOBJTYPE": "257", + "ASSOCIATEOBJID": portgroup_id, + "TYPE": "245", + "ID": view_id}) + result = self.call(url, data, "PUT") + self._assert_rest_result(result, 'Delete portgroup from mapping view ' + 'error.') + + def delete_mapping_view(self, view_id): + """Remove mapping view from the storage.""" + url = self.url + "/mappingview/" + view_id + result = self.call(url, None, "DELETE") + self._assert_rest_result(result, 'Delete mapping view error.') + + def get_lunnum_from_lungroup(self, lungroup_id): + """Check if there are still other luns associated to the lungroup.""" + url_subfix = ("/lun/count?TYPE=11&ASSOCIATEOBJTYPE=256&" + "ASSOCIATEOBJID=%s" % lungroup_id) + url = self.url + url_subfix + result = self.call(url, None, "GET") + self._assert_rest_result(result, 'Find lun number error.') + lunnum = -1 + if "data" in result: + lunnum = result['data']['COUNT'] + return lunnum + + def is_portgroup_associated_to_view(self, view_id, portgroup_id): + """Check whether the portgroup is associated to the mapping view.""" + url_subfix = ("/mappingview/associate/portgroup?TYPE=257&" + "ASSOCIATEOBJTYPE=245&ASSOCIATEOBJID=%s" % view_id) + url = self.url + url_subfix + result = self.call(url, None, "GET") + self._assert_rest_result(result, 'Find portgroup from mapping view ' + 'error.') + + if self._get_id_from_result(result, portgroup_id, 'ID'): + return True + return False + + def find_lungroup_from_map(self, view_id): + """Get lungroup from the given map""" + url_subfix = ("/mappingview/associate/lungroup?TYPE=256&" + "ASSOCIATEOBJTYPE=245&ASSOCIATEOBJID=%s" % view_id) + url = self.url + url_subfix + result = self.call(url, None, "GET") + self._assert_rest_result(result, 'Find lun group from mapping view ' + 'error.') + + return self._get_id_from_result(result, view_id, 'ID') + + def start_luncopy(self, luncopyid): + """Start a LUNcopy.""" + url = self.url + "/LUNCOPY/start" + data = json.dumps({"TYPE": "219", "ID": luncopyid}) + result = self.call(url, data, "PUT") + self._assert_rest_result(result, 'Start LUNcopy error.') + + def _get_capacity(self): + """Get free capacity and total capacity of the pool.""" + poolinfo = self.find_pool_info() + pool_capacity = {'total_capacity': 0.0, + 'free_capacity': 0.0} + + if poolinfo: + total = int(poolinfo['TOTALCAPACITY']) / 1024.0 / 1024.0 / 2 + free = int(poolinfo['CAPACITY']) / 1024.0 / 1024.0 / 2 + pool_capacity['total_capacity'] = total + pool_capacity['free_capacity'] = free + + return pool_capacity + + def get_luncopy_info(self, luncopyid): + """Get LUNcopy information.""" + url = self.url + "/LUNCOPY?range=[0-1023]" + data = json.dumps({"TYPE": "219", }) + result = self.call(url, data, "GET") + self._assert_rest_result(result, 'Get LUNcopy information error.') + + luncopyinfo = {} + if "data" in result: + for item in result['data']: + if luncopyid == item['ID']: + luncopyinfo['name'] = item['NAME'] + luncopyinfo['id'] = item['ID'] + luncopyinfo['state'] = item['HEALTHSTATUS'] + luncopyinfo['status'] = item['RUNNINGSTATUS'] + break + return luncopyinfo + + def delete_luncopy(self, luncopyid): + """Delete a LUNcopy.""" + url = self.url + "/LUNCOPY/%s" % luncopyid + 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. + + If no new ports connected, return an empty list. + """ + url = self.url + "/fc_initiator?ISFREE=true&range=[0-8191]" + result = self.call(url, None, "GET") + + msg = 'Get connected free FC wwn error.' + self._assert_rest_result(result, msg) + + wwns = [] + if 'data' in result: + for item in result['data']: + wwns.append(item['ID']) + + return wwns + + def add_fc_port_to_host(self, host_id, wwn): + """Add a FC port to the host.""" + url = self.url + "/fc_initiator/" + wwn + data = json.dumps({"TYPE": "223", + "ID": wwn, + "PARENTTYPE": 21, + "PARENTID": host_id}) + result = self.call(url, data, "PUT") + self._assert_rest_result(result, 'Add FC port to host error.') + + def _get_iscsi_port_info(self, ip): + """Get iscsi port info in order to build the iscsi target iqn.""" + url = self.url + "/eth_port" + result = self.call(url, None, "GET") + + msg = 'Get iSCSI port information error.' + self._assert_rest_result(result, msg) + self._assert_data_in_result(result, msg) + + iscsi_port_info = None + for item in result['data']: + if ip == item['IPV4ADDR']: + iscsi_port_info = item['LOCATION'] + break + + return iscsi_port_info + + def _get_tgt_iqn(self, iscsiip): + """Get target iSCSI iqn.""" + ip_info = self._get_iscsi_port_info(iscsiip) + iqn_prefix = self._get_iscsi_tgt_port() + if not ip_info: + err_msg = (_( + 'Get iSCSI port info error, please check the target IP ' + 'configured in huawei conf file.')) + LOG.error(err_msg) + raise exception.VolumeBackendAPIException(data=err_msg) + + LOG.debug('Request ip info is: %s.', ip_info) + split_list = ip_info.split(".") + newstr = split_list[1] + split_list[2] + LOG.info(_LI('New str info is: %s.'), newstr) + + if ip_info: + if newstr[0] == 'A': + ctr = "0" + elif newstr[0] == 'B': + ctr = "1" + interface = '0' + newstr[1] + port = '0' + newstr[3] + iqn_suffix = ctr + '02' + interface + port + for i in range(0, len(iqn_suffix)): + if iqn_suffix[i] != '0': + iqn_suffix = iqn_suffix[i:] + break + iqn = iqn_prefix + ':' + iqn_suffix + ':' + iscsiip + LOG.info(_LI('_get_tgt_iqn: iSCSI target iqn is: %s.'), iqn) + return iqn + else: + return + + def get_fc_target_wwpns(self, wwn): + url = (self.url + + "/host_link?INITIATOR_TYPE=223&INITIATOR_PORT_WWN=" + wwn) + result = self.call(url, None, "GET") + + msg = 'Get FC target wwpn error.' + self._assert_rest_result(result, msg) + + fc_wwpns = None + if "data" in result: + for item in result['data']: + if wwn == item['INITIATOR_PORT_WWN']: + fc_wwpns = item['TARGET_PORT_WWN'] + break + + return fc_wwpns + + def update_volume_stats(self): + root = huawei_utils.parse_xml_file(self.xml_file_path) + pool_name = root.findtext('Storage/StoragePool') + if not pool_name: + msg = (_( + 'Invalid resource pool name. ' + 'Please check the config file.')) + LOG.error(msg) + raise exception.InvalidInput(msg) + data = {} + capacity = self._get_capacity() + data["pools"] = [] + pool = {} + pool.update(dict( + pool_name=pool_name, + total_capacity_gb=capacity['total_capacity'], + free_capacity_gb=capacity['free_capacity'], + reserved_percentage=0, + QoS_support=True, + )) + data["pools"].append(pool) + return data + + def _find_qos_policy_info(self, policy_name): + url = self.url + "/ioclass" + result = self.call(url, None, "GET") + + msg = 'Get QoS policy error.' + self._assert_rest_result(result, msg) + + qos_info = {} + if "data" in result: + for item in result['data']: + if policy_name == item['NAME']: + qos_info['ID'] = item['ID'] + lun_list = json.loads(item['LUNLIST']) + qos_info['LUNLIST'] = lun_list + qos_info['RUNNINGSTATUS'] = item['RUNNINGSTATUS'] + break + + return qos_info + + def _update_qos_policy_lunlist(self, lunlist, policy_id): + url = self.url + "/ioclass/" + policy_id + data = json.dumps({"TYPE": "230", + "ID": policy_id, + "LUNLIST": lunlist}) + result = self.call(url, data, "PUT") + self._assert_rest_result(result, 'Update QoS policy error.') + + def _get_tgt_ip_from_portgroup(self, portgroup_id): + target_ip = None + url = self.url + ("/eth_port/associate?TYPE=213&ASSOCIATEOBJTYPE=257" + "&ASSOCIATEOBJID=%s" % portgroup_id) + result = self.call(url, None, "GET") + + msg = 'Get target IP error.' + self._assert_rest_result(result, msg) + self._assert_data_in_result(result, msg) + + if "data" in result: + for item in result['data']: + if (item['IPV4ADDR'] and item['HEALTHSTATUS'] == + constants.STATUS_HEALTH + and item['RUNNINGSTATUS'] == constants.STATUS_RUNNING): + target_ip = item['IPV4ADDR'] + LOG.info(_LI('_get_tgt_ip_from_portgroup: Get ip: %s.'), + target_ip) + break + return target_ip + + def get_iscsi_params(self, xml_file_path, connector): + """Get target iSCSI params, including iqn, IP.""" + initiator = connector['initiator'] + iscsi_conf = huawei_utils.get_iscsi_conf(xml_file_path) + target_ip = None + portgroup = None + portgroup_id = None + for ini in iscsi_conf['Initiator']: + if ini['Name'] == initiator: + for key in ini: + if key == 'TargetPortGroup': + portgroup = ini['TargetPortGroup'] + elif key == 'TargetIP': + target_ip = ini['TargetIP'] + + if portgroup: + portgroup_id = self.find_tgt_port_group(portgroup) + target_ip = self._get_tgt_ip_from_portgroup(portgroup_id) + + # If not specify target IP for some initiators, use default IP. + if not target_ip: + if iscsi_conf['DefaultTargetIP']: + target_ip = iscsi_conf['DefaultTargetIP'] + + else: + msg = (_( + 'get_iscsi_params: Failed to get target IP ' + 'for initiator %(ini)s, please check config file.') + % {'ini': initiator}) + LOG.error(msg) + raise exception.InvalidInput(reason=msg) + + LOG.info(_LI('Get the default ip: %s.'), target_ip) + # If didn't get target IP for rest, Automated assembly target ip. + target_iqn = self._get_tgt_iqn_from_rest(target_ip) + + if not target_iqn: + target_iqn = self._get_tgt_iqn(target_ip) + + return (target_iqn, target_ip, portgroup_id) + + def _get_tgt_iqn_from_rest(self, target_ip): + url = self.url + "/iscsi_tgt_port" + result = self.call(url, None, "GET") + + target_iqn = None + if result['error']['code'] != 0: + LOG.warning(_LW("Can't find target iqn from rest.")) + return target_iqn + + target_iqn = self._get_id_from_result(result, target_ip, 'ID') + + if not target_iqn: + LOG.warning(_LW("Can't find target iqn from rest.")) + return target_iqn + + split_list = target_iqn.split(",") + target_iqn_before = split_list[0] + + split_list_new = target_iqn_before.split("+") + target_iqn = split_list_new[1] + + return target_iqn + + def create_qos_policy(self, qos, lun_id): + # Get local time. + localtime = time.strftime('%Y%m%d%H%M%S', time.localtime(time.time())) + # Package QoS name. + qos_name = constants.QOS_NAME_PREFIX + lun_id + '_' + localtime + + mergedata = {"TYPE": "230", + "NAME": qos_name, + "LUNLIST": ["%s" % lun_id], + "CLASSTYPE": "1", + "SCHEDULEPOLICY": "2", + "SCHEDULESTARTTIME": "1410969600", + "STARTTIME": "08:00", + "DURATION": "86400", + "CYCLESET": "[1,2,3,4,5,6,0]", + } + mergedata.update(qos) + data = json.dumps(mergedata) + url = self.url + "/ioclass/" + + result = self.call(url, data) + self._assert_rest_result(result, 'Create QoS policy error.') + + return result['data']['ID'] + + def delete_qos_policy(self, qos_id): + """Delete a QoS policy.""" + url = self.url + "/ioclass/" + qos_id + data = json.dumps({"TYPE": "230", + "ID": qos_id}) + + result = self.call(url, data, 'DELETE') + self._assert_rest_result(result, 'Delete QoS policy error.') + + def active_deactive_qos(self, qos_id, enablestatus): + """Active or deactive QoS. + + enablestatus: true (active) + enbalestatus: false (deactive) + """ + url = self.url + "/ioclass/active/" + qos_id + data = json.dumps({"TYPE": 230, + "ID": qos_id, + "ENABLESTATUS": enablestatus}) + result = self.call(url, data, "PUT") + self._assert_rest_result(result, 'Active or deactive QoS error.') + + def get_qos_info(self, qos_id): + """Get QoS information.""" + url = self.url + "/ioclass/" + qos_id + data = json.dumps({"TYPE": "230", + "ID": qos_id}) + result = self.call(url, data, "GET") + self._assert_rest_result(result, 'Get QoS information error.') + + return result['data'] + + def change_lun_priority(self, lun_id): + """Change lun priority to high.""" + url = self.url + "/lun/" + lun_id + data = json.dumps({"TYPE": "11", + "ID": lun_id, + "IOPRIORITY": "3"}) + + result = self.call(url, data, "PUT") + self._assert_rest_result(result, 'Change lun priority error.') + + def get_qosid_by_lunid(self, lun_id): + """Get qosid by lun id.""" + url = self.url + "/lun/" + lun_id + data = json.dumps({"TYPE": "11", + "ID": lun_id}) + + result = self.call(url, data, "GET") + self._assert_rest_result(result, 'Get QoS id by lun id error.') + + return result['data']['IOCLASSID'] + + def get_lungroupid_by_lunid(self, lun_id): + """Get lungroup id 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.') + + return self._get_id_from_result(result, lun_id, 'ID') + + def get_lun_info(self, lun_id): + url = self.url + "/lun/" + lun_id + data = json.dumps({"TYPE": "11", + "ID": lun_id}) + result = self.call(url, data, "GET") + + msg = 'Get volume error.' + self._assert_rest_result(result, msg) + self._assert_data_in_result(result, msg) + + return result['data'] + + def extend_volume(self, lun_id, new_volume_size): + url = self.url + "/lun/expand" + data = json.dumps({"TYPE": 11, "ID": lun_id, + "CAPACITY": new_volume_size}) + result = self.call(url, data, 'PUT') + + msg = 'Extend volume error.' + self._assert_rest_result(result, msg) + self._assert_data_in_result(result, msg) + + return result['data'] + + def remove_host(self, host_id): + url = self.url + "/host/%s" % host_id + result = self.call(url, None, "DELETE") + self._assert_rest_result(result, 'Remove host from array error.') + + def delete_hostgroup(self, hostgroup_id): + url = self.url + "/hostgroup/%s" % hostgroup_id + result = self.call(url, None, "DELETE") + self._assert_rest_result(result, 'Delete hostgroup error.') + + def remove_host_from_hostgroup(self, hostgroup_id, host_id): + url_subfix001 = "/host/associate?TYPE=14&ID=%s" % hostgroup_id + url_subfix002 = "&ASSOCIATEOBJTYPE=21&ASSOCIATEOBJID=%s" % host_id + url = self.url + url_subfix001 + url_subfix002 + result = self.call(url, None, "DELETE") + self._assert_rest_result(result, 'Remove host from hostgroup error.') + + def remove_iscsi_from_host(self, initiator): + url = self.url + "/iscsi_initiator/remove_iscsi_from_host" + data = json.dumps({"TYPE": '222', + "ID": initiator}) + result = self.call(url, data, "PUT") + self._assert_rest_result(result, 'Remove iscsi from host error.') diff --git a/cinder/volume/drivers/huawei/rest_common.py b/cinder/volume/drivers/huawei/rest_common.py deleted file mode 100644 index 8ed1eec3e..000000000 --- a/cinder/volume/drivers/huawei/rest_common.py +++ /dev/null @@ -1,1750 +0,0 @@ -# Copyright (c) 2013 - 2014 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. -"""Common class for Huawei 18000 storage drivers.""" - -import base64 -import json -import socket -import time -import uuid -from xml.etree import ElementTree as ET - -from oslo_log import log as logging -from oslo_service import loopingcall -from oslo_utils import excutils -from oslo_utils import units -import six -from six.moves import http_cookiejar -from six.moves import urllib - -from cinder import context -from cinder import exception -from cinder.i18n import _, _LE, _LI, _LW -from cinder import utils -from cinder.volume import qos_specs -from cinder.volume import volume_types - -LOG = logging.getLogger(__name__) - -DEFAULT_WAIT_TIMEOUT = 3600 * 24 * 30 -DEFAULT_WAIT_INTERVAL = 5 - -HOSTGROUP_PREFIX = 'OpenStack_HostGroup_' -LUNGROUP_PREFIX = 'OpenStack_LunGroup_' -MAPPING_VIEW_PREFIX = 'OpenStack_Mapping_View_' -QOS_NAME_PREFIX = 'OpenStack_' -huawei_valid_keys = ['maxIOPS', 'minIOPS', 'minBandWidth', - 'maxBandWidth', 'latency', 'IOType'] - - -class RestCommon(object): - """Common class for Huawei OceanStor 18000 storage system.""" - - def __init__(self, configuration): - self.configuration = configuration - self.cookie = http_cookiejar.CookieJar() - self.url = None - self.productversion = None - self.headers = {"Connection": "keep-alive", - "Content-Type": "application/json"} - - def call(self, url=False, data=None, method=None): - """Send requests to 18000 server. - Send HTTPS call, get response in JSON. - Convert response into Python Object and return it. - """ - - handler = urllib.request.HTTPCookieProcessor(self.cookie) - opener = urllib.request.build_opener(handler) - urllib.request.install_opener(opener) - - try: - socket.setdefaulttimeout(720) - req = urllib.request.Request(url, data, self.headers) - if method: - req.get_method = lambda: method - res = urllib.request.urlopen(req).read().decode("utf-8") - - if "xx/sessions" not in url: - LOG.info(_LI('\n\n\n\nRequest URL: %(url)s\n\n' - 'Call Method: %(method)s\n\n' - 'Request Data: %(data)s\n\n' - 'Response Data:%(res)s\n\n'), {'url': url, - 'method': method, - 'data': data, - 'res': res}) - - except Exception as err: - LOG.error(_LE('\nBad response from server: %s.'), err) - raise - - try: - res_json = json.loads(res) - except Exception as err: - LOG.error(_LE('JSON transfer error: %s.'), err) - raise - - return res_json - - def login(self): - """Log in 18000 array.""" - - login_info = self._get_login_info() - url = login_info['RestURL'] + "xx/sessions" - data = json.dumps({"username": login_info['UserName'], - "password": login_info['UserPassword'], - "scope": "0"}) - result = self.call(url, data) - if (result['error']['code'] != 0) or ("data" not in result): - msg = (_("Login error, reason is: %s.") % result) - LOG.error(msg) - raise exception.CinderException(msg) - - deviceid = result['data']['deviceid'] - self.url = login_info['RestURL'] + deviceid - self.headers['iBaseToken'] = result['data']['iBaseToken'] - return deviceid - - def _init_lun_parameters(self, name, parameters): - """Init basic LUN parameters.""" - lunparam = {"TYPE": "11", - "NAME": name, - "PARENTTYPE": "216", - "PARENTID": parameters['pool_id'], - "DESCRIPTION": parameters['volume_description'], - "ALLOCTYPE": parameters['LUNType'], - "CAPACITY": parameters['volume_size'], - "WRITEPOLICY": parameters['WriteType'], - "MIRRORPOLICY": parameters['MirrorSwitch'], - "PREFETCHPOLICY": parameters['PrefetchType'], - "PREFETCHVALUE": parameters['PrefetchValue']} - - return lunparam - - def _assert_rest_result(self, result, err_str): - error_code = result['error']['code'] - if error_code != 0: - msg = (_('%(err)s\nresult: %(res)s.') % {'err': err_str, - 'res': result}) - LOG.error(msg) - raise exception.CinderException(msg) - - def _assert_data_in_result(self, result, msg): - if "data" not in result: - err_msg = (_('%s "data" was not in result.') % msg) - LOG.error(err_msg) - raise exception.CinderException(err_msg) - - def _create_volume(self, lun_param): - url = self.url + "/lun" - data = json.dumps(lun_param) - result = self.call(url, data) - - msg = 'Create volume error.' - self._assert_rest_result(result, msg) - self._assert_data_in_result(result, msg) - - return result['data'] - - @utils.synchronized('huawei', external=True) - def create_volume(self, volume): - - poolinfo = self._find_pool_info() - volume_name = self._encode_name(volume['id']) - volume_description = volume['name'] - volume_size = self._get_volume_size(volume) - - LOG.info(_LI('Create Volume: %(volume)s Size: %(size)s.'), - {'volume': volume_name, 'size': volume_size}) - - params = self._get_lun_conf_params() - params['pool_id'] = poolinfo['ID'] - params['volume_size'] = volume_size - params['volume_description'] = volume_description - - # Prepare lun parameters. - lun_param = self._init_lun_parameters(volume_name, params) - - # Create LUN on the array. - lun_info = self._create_volume(lun_param) - lunid = lun_info['ID'] - - type_id = volume.get('volume_type_id', None) - policy_id = None - - if type_id is not None: - volume_type = self._get_volume_type(type_id) - qos = self._get_qos_by_volume_type(volume_type) - - if qos is None: - msg = (_('Find QoS configuration error!')) - LOG.error(msg) - raise exception.CinderException(msg) - - try: - # Check QoS priority. if high, change lun priority to high. - if self._check_qos_high_priority(qos) is True: - self._change_lun_priority(lunid) - - # Create QoS policy and active. - policy_id = self._create_qos_policy(qos, lunid) - self._active_deactive_qos(policy_id, True) - except Exception: - with excutils.save_and_reraise_exception(): - if policy_id is not None: - self._delete_qos_policy(policy_id) - - self._delete_lun(lunid) - - return lun_info - - def _get_volume_size(self, volume): - """Calculate the volume size. - - We should divide the given volume size by 512 for the 18000 system - calculates volume size with sectors, which is 512 bytes. - """ - - volume_size = units.Gi / 512 # 1G - if int(volume['size']) != 0: - volume_size = int(volume['size']) * units.Gi / 512 - - return volume_size - - @utils.synchronized('huawei', external=True) - def delete_volume(self, volume): - """Delete a volume. - - Three steps: first, remove associate from lungroup. - Second, remove associate from QoS policy. Third, remove the lun. - """ - - name = self._encode_name(volume['id']) - lun_id = volume.get('provider_location', None) - LOG.info(_LI('Delete Volume: %(name)s array lun id: %(lun_id)s.'), - {'name': name, 'lun_id': lun_id}) - if lun_id: - if self._check_lun_exist(lun_id) is True: - # Get qos_id by lun_id. - qos_id = self._get_qosid_by_lunid(lun_id) - - if qos_id != "": - qos_info = self._get_qos_info(qos_id) - qos_status = qos_info['RUNNINGSTATUS'] - # 2: Active status. - if qos_status == '2': - self._active_deactive_qos(qos_id, False) - - self._delete_qos_policy(qos_id) - self._delete_lun(lun_id) - else: - LOG.warning(_LW("Can't find lun or lungroup on the array.")) - - def _check_lun_exist(self, lun_id): - url = self.url + "/lun/" + lun_id - data = json.dumps({"TYPE": "11", - "ID": lun_id}) - result = self.call(url, data, "GET") - error_code = result['error']['code'] - if error_code != 0: - return False - - return True - - def _delete_lun(self, lun_id): - url = self.url + "/lun/" + lun_id - data = json.dumps({"TYPE": "11", - "ID": lun_id}) - result = self.call(url, data, "DELETE") - self._assert_rest_result(result, 'Delete lun error.') - - def _read_xml(self): - """Open xml file and parse the content.""" - filename = self.configuration.cinder_huawei_conf_file - try: - tree = ET.parse(filename) - root = tree.getroot() - except Exception as err: - LOG.error(_LE('_read_xml: %s'), err) - raise - return root - - def _encode_name(self, name): - uuid_str = name.replace("-", "") - vol_uuid = uuid.UUID('urn:uuid:%s' % uuid_str) - vol_encoded = base64.urlsafe_b64encode(vol_uuid.bytes) - if six.PY3: - vol_encoded = vol_encoded.decode('ascii') - newuuid = vol_encoded.replace("=", "") - return newuuid - - def _find_pool_info(self): - root = self._read_xml() - pool_name = root.findtext('LUN/StoragePool') - if not pool_name: - err_msg = (_("Invalid resource pool: %s.") % pool_name) - LOG.error(err_msg) - raise exception.InvalidInput(err_msg) - - url = self.url + "/storagepool" - result = self.call(url, None) - self._assert_rest_result(result, 'Query resource pool error.') - - poolinfo = {} - if "data" in result: - for item in result['data']: - if pool_name.strip() == item['NAME']: - poolinfo['ID'] = item['ID'] - poolinfo['CAPACITY'] = item['USERFREECAPACITY'] - poolinfo['TOTALCAPACITY'] = item['USERTOTALCAPACITY'] - break - - if not poolinfo: - msg = (_('Get pool info error, pool name is: %s.') % pool_name) - LOG.error(msg) - raise exception.CinderException(msg) - - return poolinfo - - def _get_volume_by_name(self, name): - url = self.url + "/lun?range=[0-65535]" - result = self.call(url, None, "GET") - self._assert_rest_result(result, 'Get volume by name error!') - - volume_id = None - if "data" in result: - for item in result['data']: - if name == item['NAME']: - volume_id = item['ID'] - break - return volume_id - - def _active_snapshot(self, snapshot_id): - activeurl = self.url + "/snapshot/activate" - data = json.dumps({"SNAPSHOTLIST": [snapshot_id]}) - result = self.call(activeurl, data) - self._assert_rest_result(result, 'Active snapshot error.') - - def _create_snapshot(self, snapshot): - snapshot_name = self._encode_name(snapshot['id']) - snapshot_description = snapshot['id'] - volume_name = self._encode_name(snapshot['volume_id']) - - LOG.info(_LI('_create_snapshot:snapshot name: %(snapshot)s, ' - 'volume name: %(volume)s.'), - {'snapshot': snapshot_name, - 'volume': volume_name}) - - lun_id = self._get_volume_by_name(volume_name) - if lun_id is None: - msg = (_("Can't find lun info on the array, " - "lun name is: %(name)s") % {'name': volume_name}) - LOG.error(msg) - raise exception.CinderException(msg) - - url = self.url + "/snapshot" - data = json.dumps({"TYPE": "27", - "NAME": snapshot_name, - "PARENTTYPE": "11", - "DESCRIPTION": snapshot_description, - "PARENTID": lun_id}) - result = self.call(url, data) - - msg = 'Create snapshot error.' - self._assert_rest_result(result, msg) - self._assert_data_in_result(result, msg) - - return result['data'] - - @utils.synchronized('huawei', external=True) - def create_snapshot(self, snapshot): - snapshot_info = self._create_snapshot(snapshot) - snapshot_id = snapshot_info['ID'] - self._active_snapshot(snapshot_id) - - return snapshot_info - - def _check_snapshot_exist(self, snapshot_id): - url = self.url + "/snapshot/" + snapshot_id - data = json.dumps({"TYPE": "27", - "ID": snapshot_id}) - result = self.call(url, data, "GET") - error_code = result['error']['code'] - if error_code != 0: - return False - - return True - - def _stop_snapshot(self, snapshot_id): - url = self.url + "/snapshot/stop" - stopdata = json.dumps({"ID": snapshot_id}) - result = self.call(url, stopdata, "PUT") - self._assert_rest_result(result, 'Stop snapshot error.') - - def _delete_snapshot(self, snapshotid): - url = self.url + "/snapshot/%s" % snapshotid - data = json.dumps({"TYPE": "27", "ID": snapshotid}) - result = self.call(url, data, "DELETE") - self._assert_rest_result(result, 'Delete snapshot error.') - - @utils.synchronized('huawei', external=True) - def delete_snapshot(self, snapshot): - snapshot_name = self._encode_name(snapshot['id']) - volume_name = self._encode_name(snapshot['volume_id']) - - LOG.info(_LI('stop_snapshot:snapshot name: %(snapshot)s, ' - 'volume name: %(volume)s.'), - {'snapshot': snapshot_name, - 'volume': volume_name}) - - snapshot_id = snapshot.get('provider_location', None) - if snapshot_id is None: - snapshot_id = self._get_snapshotid_by_name(snapshot_name) - - if snapshot_id is not None: - if self._check_snapshot_exist(snapshot_id) is True: - self._stop_snapshot(snapshot_id) - self._delete_snapshot(snapshot_id) - else: - LOG.warning(_LW("Can't find snapshot on the array.")) - else: - LOG.warning(_LW("Can't find snapshot on the array.")) - - def _get_snapshotid_by_name(self, name): - url = self.url + "/snapshot?range=[0-65535]" - data = json.dumps({"TYPE": "27"}) - result = self.call(url, data, "GET") - self._assert_rest_result(result, 'Get snapshot id error.') - - snapshot_id = None - if "data" in result: - for item in result['data']: - if name == item['NAME']: - snapshot_id = item['ID'] - break - - return snapshot_id - - def _copy_volume(self, volume, copy_name, src_lun, tgt_lun): - - luncopy_id = self._create_luncopy(copy_name, - src_lun, tgt_lun) - event_type = 'LUNcopyWaitInterval' - wait_interval = self._get_wait_interval(event_type) - wait_interval = int(wait_interval) - try: - self._start_luncopy(luncopy_id) - - def _luncopy_complete(): - luncopy_info = self._get_luncopy_info(luncopy_id) - if luncopy_info['status'] == '40': - # luncopy_info['status'] means for the running status of - # the luncopy. If luncopy_info['status'] is equal to '40', - # this luncopy is completely ready. - return True - elif luncopy_info['state'] != '1': - # luncopy_info['state'] means for the healthy status of the - # luncopy. If luncopy_info['state'] is not equal to '1', - # this means that an error occurred during the LUNcopy - # operation and we should abort it. - err_msg = (_( - 'An error occurred during the LUNcopy operation. ' - 'LUNcopy name: %(luncopyname)s. ' - 'LUNcopy status: %(luncopystatus)s. ' - 'LUNcopy state: %(luncopystate)s.') - % {'luncopyname': luncopy_id, - 'luncopystatus': luncopy_info['status'], - 'luncopystate': luncopy_info['state']}) - LOG.error(err_msg) - raise exception.VolumeBackendAPIException(data=err_msg) - self._wait_for_condition(_luncopy_complete, wait_interval) - - except Exception: - with excutils.save_and_reraise_exception(): - self._delete_luncopy(luncopy_id) - self.delete_volume(volume) - - self._delete_luncopy(luncopy_id) - - def _get_wait_interval(self, event_type): - """Get wait interval from huawei conf file.""" - root = self._read_xml() - wait_interval = root.findtext('LUN/%s' % event_type) - if wait_interval: - return wait_interval - else: - LOG.info(_LI( - "Wait interval for %(event_type)s is not configured in huawei " - "conf file. Use default: %(default_wait_interval)d."), - {"event_type": event_type, - "default_wait_interval": DEFAULT_WAIT_INTERVAL}) - return DEFAULT_WAIT_INTERVAL - - def _get_default_timeout(self): - """Get timeout from huawei conf file.""" - root = self._read_xml() - timeout = root.findtext('LUN/Timeout') - if timeout is None: - timeout = DEFAULT_WAIT_TIMEOUT - LOG.info(_LI( - "Timeout is not configured in huawei conf file. " - "Use default: %(default_timeout)d."), - {"default_timeout": timeout}) - - return timeout - - def _wait_for_condition(self, func, interval, timeout=None): - start_time = time.time() - if timeout is None: - timeout = self._get_default_timeout() - - def _inner(): - try: - res = func() - except Exception as ex: - res = False - LOG.debug('_wait_for_condition: %(func_name)s ' - 'failed for %(exception)s.', - {'func_name': func.__name__, - 'exception': ex}) - if res: - raise loopingcall.LoopingCallDone() - - if int(time.time()) - start_time > timeout: - msg = (_('_wait_for_condition: %s timed out.') - % func.__name__) - LOG.error(msg) - raise exception.VolumeBackendAPIException(data=msg) - - timer = loopingcall.FixedIntervalLoopingCall(_inner) - timer.start(interval=interval).wait() - - def create_volume_from_snapshot(self, volume, snapshot): - """Create a volume from a snapshot. - - We use LUNcopy to copy a new volume from snapshot. - The time needed increases as volume size does. - """ - - snapshot_name = self._encode_name(snapshot['id']) - - snapshot_id = snapshot.get('provider_location', None) - if snapshot_id is None: - snapshot_id = self._get_snapshotid_by_name(snapshot_name) - if snapshot_id is None: - err_msg = (_( - 'create_volume_from_snapshot: Snapshot %(name)s ' - 'does not exist.') - % {'name': snapshot_name}) - LOG.error(err_msg) - raise exception.VolumeBackendAPIException(data=err_msg) - - lun_info = self.create_volume(volume) - tgt_lun_id = lun_info['ID'] - luncopy_name = self._encode_name(volume['id']) - - LOG.info(_LI('create_volume_from_snapshot: src_lun_id: ' - '%(src_lun_id)s, tgt_lun_id: %(tgt_lun_id)s, ' - 'copy_name: %(copy_name)s'), - {'src_lun_id': snapshot_id, - 'tgt_lun_id': tgt_lun_id, - 'copy_name': luncopy_name}) - - event_type = 'LUNReadyWaitInterval' - wait_interval = self._get_wait_interval(event_type) - - def _volume_ready(): - url = self.url + "/lun/" + tgt_lun_id - result = self.call(url, None, "GET") - self._assert_rest_result(result, 'Get volume by id failed!') - - if "data" in result: - if (result['data']['HEALTHSTATUS'] == "1" and - result['data']['RUNNINGSTATUS'] == "27"): - return True - return False - - self._wait_for_condition(_volume_ready, - wait_interval, - wait_interval * 3) - self._copy_volume(volume, luncopy_name, snapshot_id, tgt_lun_id) - - return lun_info - - def create_cloned_volume(self, volume, src_vref): - """Clone a new volume from an existing volume.""" - - # Form the snapshot structure. - snapshot = {'id': uuid.uuid4().__str__(), 'volume_id': src_vref['id']} - - # Create snapshot. - self.create_snapshot(snapshot) - - try: - # Create volume from snapshot. - lun_info = self.create_volume_from_snapshot(volume, snapshot) - finally: - try: - # Delete snapshot. - self.delete_snapshot(snapshot) - except exception.CinderException: - LOG.warning(_LW('Failure deleting the snapshot ' - '%(snapshot_id)s of volume %(volume_id)s.'), - {'snapshot_id': snapshot['id'], - 'volume_id': src_vref['id']}) - - return lun_info - - def _create_luncopy(self, luncopyname, srclunid, tgtlunid): - """Create a luncopy.""" - url = self.url + "/luncopy" - data = json.dumps({"TYPE": 219, - "NAME": luncopyname, - "DESCRIPTION": luncopyname, - "COPYSPEED": 2, - "LUNCOPYTYPE": "1", - "SOURCELUN": ("INVALID;%s;INVALID;INVALID;INVALID" - % srclunid), - "TARGETLUN": ("INVALID;%s;INVALID;INVALID;INVALID" - % tgtlunid)}) - result = self.call(url, data) - - msg = 'Create lun copy error.' - self._assert_rest_result(result, msg) - self._assert_data_in_result(result, msg) - - return result['data']['ID'] - - def _add_host_into_hostgroup(self, host_id): - """Associate host to hostgroup. - - If hostgroup doesn't exist, create one. - - """ - host_group_name = HOSTGROUP_PREFIX + host_id - hostgroup_id = self._find_hostgroup(host_group_name) - - LOG.info(_LI('_add_host_into_hostgroup, hostgroup name: %(name)s, ' - 'hostgroup id: %(id)s.'), - {'name': host_group_name, - 'id': hostgroup_id}) - - if hostgroup_id is None: - hostgroup_id = self._create_hostgroup(host_group_name) - - is_associated = self._is_host_associate_to_hostgroup(hostgroup_id, - host_id) - if is_associated is False: - self._associate_host_to_hostgroup(hostgroup_id, host_id) - - return hostgroup_id - - def _mapping_hostgroup_and_lungroup(self, volume_name, - hostgroup_id, host_id): - """Add hostgroup and lungroup to view.""" - lungroup_name = LUNGROUP_PREFIX + host_id - mapping_view_name = MAPPING_VIEW_PREFIX + host_id - lungroup_id = self._find_lungroup(lungroup_name) - lun_id = self._get_volume_by_name(volume_name) - view_id = self._find_mapping_view(mapping_view_name) - - LOG.info(_LI('_mapping_hostgroup_and_lungroup, lun_group: ' - '%(lun_group)s, view_id: %(view_id)s, lun_id: ' - '%(lun_id)s.'), {'lun_group': lungroup_id, - 'view_id': view_id, - 'lun_id': lun_id}) - - try: - # Create lungroup and add LUN into to lungroup. - if lungroup_id is None: - lungroup_id = self._create_lungroup(lungroup_name) - is_associated = self._is_lun_associated_to_lungroup(lungroup_id, - lun_id) - if not is_associated: - self._associate_lun_to_lungroup(lungroup_id, lun_id) - - if view_id is None: - view_id = self._add_mapping_view(mapping_view_name) - self._associate_hostgroup_to_view(view_id, hostgroup_id) - self._associate_lungroup_to_view(view_id, lungroup_id) - else: - if not self._hostgroup_associated(view_id, hostgroup_id): - self._associate_hostgroup_to_view(view_id, hostgroup_id) - if not self._lungroup_associated(view_id, lungroup_id): - self._associate_lungroup_to_view(view_id, lungroup_id) - - except Exception: - with excutils.save_and_reraise_exception(): - LOG.error(_LE('Error occurred when adding hostgroup and ' - 'lungroup to view. Remove lun from lungroup ' - 'now.')) - self._remove_lun_from_lungroup(lungroup_id, lun_id) - - return lun_id - - def _ensure_initiator_added(self, initiator_name, hostid): - added = self._initiator_is_added_to_array(initiator_name) - if not added: - self._add_initiator_to_array(initiator_name) - if self._is_initiator_associated_to_host(initiator_name) is False: - self._associate_initiator_to_host(initiator_name, hostid) - else: - if self._is_initiator_associated_to_host(initiator_name) is False: - self._associate_initiator_to_host(initiator_name, hostid) - - @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 = self._encode_name(volume['id']) - - LOG.info(_LI('initiator name: %(initiator_name)s, ' - 'volume name: %(volume)s.'), - {'initiator_name': initiator_name, - 'volume': volume_name}) - - (iscsi_iqn, target_ip) = self._get_iscsi_params(connector) - LOG.info(_LI('initialize_connection_iscsi,iscsi_iqn: %(iscsi_iqn)s, ' - 'target_ip: %(target_ip)s.'), - {'iscsi_iqn': iscsi_iqn, - 'target_ip': target_ip}) - - # Create host_group if not exist. - host_name = connector['host'] - hostid = self._find_host(host_name) - if hostid is None: - hostid = self._add_host(host_name) - - # Add initiator to the host. - self._ensure_initiator_added(initiator_name, hostid) - hostgroup_id = self._add_host_into_hostgroup(hostid) - - # Mapping lungroup and hostgroup to view. - lun_id = self._mapping_hostgroup_and_lungroup(volume_name, - hostgroup_id, hostid) - - hostlunid = self._find_host_lun_id(hostid, lun_id) - - LOG.info(_LI("initialize_connection_iscsi, host lun id is: %s."), - hostlunid) - - # Return iSCSI properties. - properties = {} - properties['target_discovered'] = False - properties['target_portal'] = ('%s:%s' % (target_ip, '3260')) - properties['target_iqn'] = iscsi_iqn - properties['target_lun'] = int(hostlunid) - properties['volume_id'] = volume['id'] - - LOG.info(_LI("initialize_connection_iscsi success. Return data: %s."), - properties) - return {'driver_volume_type': 'iscsi', 'data': properties} - - @utils.synchronized('huawei', external=True) - def initialize_connection_fc(self, volume, connector): - wwns = connector['wwpns'] - host_name = connector['host'] - volume_name = self._encode_name(volume['id']) - - LOG.info(_LI('initialize_connection_fc, initiator: %(initiator_name)s,' - ' volume name: %(volume)s.'), - {'initiator_name': wwns, - 'volume': volume_name}) - - # Create host_group if not exist. - hostid = self._find_host(host_name) - if hostid is None: - hostid = self._add_host(host_name) - - # Add host into hostgroup. - hostgroup_id = self._add_host_into_hostgroup(hostid) - - free_wwns = self._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._add_fc_port_to_host(hostid, wwn) - - lun_id = self._mapping_hostgroup_and_lungroup(volume_name, - hostgroup_id, hostid) - host_lun_id = self._find_host_lun_id(hostid, lun_id) - - tgt_port_wwns = [] - for wwn in wwns: - tgtwwpns = self._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 - - def _get_iscsi_tgt_port(self): - url = self.url + "/iscsidevicename" - result = self.call(url, None) - - msg = 'Get iSCSI target port error.' - self._assert_rest_result(result, msg) - self._assert_data_in_result(result, msg) - - return result['data'][0]['CMO_ISCSI_DEVICE_NAME'] - - def _find_hostgroup(self, groupname): - """Get the given hostgroup id.""" - url = self.url + "/hostgroup?range=[0-8191]" - result = self.call(url, None, "GET") - self._assert_rest_result(result, 'Get hostgroup information error.') - - host_group_id = None - if "data" in result: - for item in result['data']: - if groupname == item['NAME']: - host_group_id = item['ID'] - break - return host_group_id - - def _find_lungroup(self, lungroupname): - """Get the given hostgroup id.""" - url = self.url + "/lungroup?range=[0-8191]" - result = self.call(url, None, "GET") - self._assert_rest_result(result, 'Get lungroup information error.') - - lun_group_id = None - if 'data' in result: - for item in result['data']: - if lungroupname == item['NAME']: - lun_group_id = item['ID'] - break - return lun_group_id - - def _create_hostgroup(self, hostgroupname): - url = self.url + "/hostgroup" - data = json.dumps({"TYPE": "14", "NAME": hostgroupname}) - result = self.call(url, data) - - msg = 'Create hostgroup error.' - self._assert_rest_result(result, msg) - self._assert_data_in_result(result, msg) - - return result['data']['ID'] - - def _create_lungroup(self, lungroupname): - url = self.url + "/lungroup" - data = json.dumps({"DESCRIPTION": lungroupname, - "APPTYPE": '0', - "GROUPTYPE": '0', - "NAME": lungroupname}) - result = self.call(url, data) - - msg = 'Create lungroup error.' - self._assert_rest_result(result, msg) - self._assert_data_in_result(result, msg) - - return result['data']['ID'] - - def _delete_lungroup(self, lungroupid): - url = self.url + "/LUNGroup/" + lungroupid - result = self.call(url, None, "DELETE") - self._assert_rest_result(result, 'Delete lungroup error.') - - def _lungroup_associated(self, viewid, lungroupid): - url_subfix = ("/mappingview/associate?TYPE=245&" - "ASSOCIATEOBJTYPE=256&ASSOCIATEOBJID=%s" % lungroupid) - url = self.url + url_subfix - result = self.call(url, None, "GET") - self._assert_rest_result(result, 'Check lungroup associated error.') - - if "data" in result: - for item in result['data']: - if viewid == item['ID']: - return True - return False - - def _hostgroup_associated(self, viewid, hostgroupid): - url_subfix = ("/mappingview/associate?TYPE=245&" - "ASSOCIATEOBJTYPE=14&ASSOCIATEOBJID=%s" % hostgroupid) - url = self.url + url_subfix - result = self.call(url, None, "GET") - self._assert_rest_result(result, 'Check hostgroup associated error.') - - if "data" in result: - for item in result['data']: - if viewid == item['ID']: - return True - return False - - def _find_host_lun_id(self, hostid, lunid): - - url = self.url + ("/lun/associate?TYPE=11&ASSOCIATEOBJTYPE=21" - "&ASSOCIATEOBJID=%s" % (hostid)) - result = self.call(url, None, "GET") - self._assert_rest_result(result, 'Find host lun id error.') - - host_lun_id = 1 - if "data" in result: - for item in result['data']: - if lunid == item['ID']: - associate_data = result['data'][0]['ASSOCIATEMETADATA'] - try: - hostassoinfo = json.loads(associate_data) - host_lun_id = hostassoinfo['HostLUNID'] - break - except Exception as err: - LOG.error(_LE("JSON transfer data error. %s"), err) - raise - return host_lun_id - - def _find_host(self, hostname): - """Get the given host ID.""" - url = self.url + "/host?range=[0-65534]" - data = json.dumps({"TYPE": "21"}) - result = self.call(url, data, "GET") - self._assert_rest_result(result, 'Find host in hostgroup error.') - - host_id = None - if "data" in result: - for item in result['data']: - if hostname == item['NAME']: - host_id = item['ID'] - break - return host_id - - def _add_host(self, hostname): - """Add a new host.""" - url = self.url + "/host" - data = json.dumps({"TYPE": "21", - "NAME": hostname, - "OPERATIONSYSTEM": "0"}) - result = self.call(url, data) - self._assert_rest_result(result, 'Add new host error.') - - if "data" in result: - return result['data']['ID'] - else: - return None - - def _is_host_associate_to_hostgroup(self, hostgroup_id, host_id): - """Check whether the host is associated to the hostgroup.""" - url_subfix = ("/host/associate?TYPE=21&" - "ASSOCIATEOBJTYPE=14&ASSOCIATEOBJID=%s" % hostgroup_id) - - url = self.url + url_subfix - result = self.call(url, None, "GET") - self._assert_rest_result(result, 'Check hostgroup associated error.') - - 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): - """Check whether the lun is associated to the lungroup.""" - url_subfix = ("/lun/associate?TYPE=11&" - "ASSOCIATEOBJTYPE=256&ASSOCIATEOBJID=%s" % lungroup_id) - - url = self.url + url_subfix - result = self.call(url, None, "GET") - self._assert_rest_result(result, 'Check lungroup associate error.') - - if "data" in result: - for item in result['data']: - if lun_id == item['ID']: - return True - - return False - - def _associate_host_to_hostgroup(self, hostgroup_id, host_id): - url = self.url + "/hostgroup/associate" - data = json.dumps({"TYPE": "14", - "ID": hostgroup_id, - "ASSOCIATEOBJTYPE": "21", - "ASSOCIATEOBJID": host_id}) - - result = self.call(url, data) - self._assert_rest_result(result, 'Associate host to hostgroup error.') - - def _associate_lun_to_lungroup(self, lungroupid, lunid): - """Associate lun to lungroup.""" - url = self.url + "/lungroup/associate" - data = json.dumps({"ID": lungroupid, - "ASSOCIATEOBJTYPE": "11", - "ASSOCIATEOBJID": lunid}) - result = self.call(url, data) - self._assert_rest_result(result, 'Associate lun to lungroup error.') - - def _remove_lun_from_lungroup(self, lungroupid, lunid): - """Remove lun from lungroup.""" - - url = self.url + ("/lungroup/associate?ID=%s" - "&ASSOCIATEOBJTYPE=11&ASSOCIATEOBJID=%s" - % (lungroupid, lunid)) - - result = self.call(url, None, 'DELETE') - self._assert_rest_result(result, - 'Delete associated lun from lungroup error.') - - def _initiator_is_added_to_array(self, ininame): - """Check whether the initiator is already added on the array.""" - url = self.url + "/iscsi_initiator?range=[0-65535]" - result = self.call(url, None, "GET") - self._assert_rest_result(result, - 'Check initiator added to array error.') - - if "data" in result: - for item in result['data']: - if item["ID"] == ininame: - return True - return False - - def _is_initiator_associated_to_host(self, ininame): - """Check whether the initiator is associated to the host.""" - url = self.url + "/iscsi_initiator?range=[0-65535]" - 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 False - return True - - def _add_initiator_to_array(self, ininame): - """Add a new initiator to storage device.""" - url = self.url + "/iscsi_initiator/" - data = json.dumps({"TYPE": "222", - "ID": ininame, - "USECHAP": "false"}) - result = self.call(url, data) - self._assert_rest_result(result, 'Add initiator to array error.') - - def _associate_initiator_to_host(self, ininame, hostid): - """Associate initiator with the host.""" - url = self.url + "/iscsi_initiator/" + ininame - data = json.dumps({"TYPE": "222", - "ID": ininame, - "USECHAP": "false", - "PARENTTYPE": "21", - "PARENTID": hostid}) - result = self.call(url, data, "PUT") - self._assert_rest_result(result, 'Associate initiator to host error.') - - def _find_mapping_view(self, name): - """Find mapping view.""" - url = self.url + "/mappingview?range=[0-65535]" - data = json.dumps({"TYPE": "245"}) - result = self.call(url, data, "GET") - - msg = 'Find map view error.' - self._assert_rest_result(result, msg) - viewid = None - if "data" in result: - for item in result['data']: - if name == item['NAME']: - viewid = item['ID'] - break - - return viewid - - def _add_mapping_view(self, name): - url = self.url + "/mappingview" - data = json.dumps({"NAME": name, "TYPE": "245"}) - result = self.call(url, data) - self._assert_rest_result(result, 'Add map view error.') - - return result['data']['ID'] - - def _associate_hostgroup_to_view(self, viewID, hostGroupID): - url = self.url + "/MAPPINGVIEW/CREATE_ASSOCIATE" - data = json.dumps({"ASSOCIATEOBJTYPE": "14", - "ASSOCIATEOBJID": hostGroupID, - "TYPE": "245", - "ID": viewID}) - result = self.call(url, data, "PUT") - self._assert_rest_result(result, 'Associate host to view error.') - - def _associate_lungroup_to_view(self, viewID, lunGroupID): - url = self.url + "/MAPPINGVIEW/CREATE_ASSOCIATE" - data = json.dumps({"ASSOCIATEOBJTYPE": "256", - "ASSOCIATEOBJID": lunGroupID, - "TYPE": "245", - "ID": viewID}) - result = self.call(url, data, "PUT") - self._assert_rest_result(result, 'Associate lungroup to view error.') - - def _delete_lungroup_mapping_view(self, view_id, lungroup_id): - """Remove lungroup associate from the mapping view.""" - url = self.url + "/mappingview/REMOVE_ASSOCIATE" - data = json.dumps({"ASSOCIATEOBJTYPE": "256", - "ASSOCIATEOBJID": lungroup_id, - "TYPE": "245", - "ID": view_id}) - result = self.call(url, data, "PUT") - self._assert_rest_result(result, 'Delete lungroup from view error.') - - def _delete_hostgoup_mapping_view(self, view_id, hostgroup_id): - """Remove hostgroup associate from the mapping view.""" - url = self.url + "/mappingview/REMOVE_ASSOCIATE" - data = json.dumps({"ASSOCIATEOBJTYPE": "14", - "ASSOCIATEOBJID": hostgroup_id, - "TYPE": "245", - "ID": view_id}) - result = self.call(url, data, "PUT") - self._assert_rest_result(result, 'Delete hostgroup from view error.') - - def _delete_mapping_view(self, view_id): - """Remove mapping view from the storage.""" - url = self.url + "/mappingview/" + view_id - result = self.call(url, None, "DELETE") - self._assert_rest_result(result, 'Delete map view error.') - - def _get_lunnum_from_lungroup(self, lungroup_id): - """Check if there are still other luns associated to the lungroup.""" - url_subfix = ("/lun/count?TYPE=11&ASSOCIATEOBJTYPE=256&" - "ASSOCIATEOBJID=%s" % lungroup_id) - url = self.url + url_subfix - result = self.call(url, None, "GET") - self._assert_rest_result(result, 'Find lun number error.') - if "data" in result: - lunnum = result['data']['COUNT'] - return lunnum - return None - - @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 = self._encode_name(volume['id']) - lun_id = volume.get('provider_location', 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}) - - if lun_id: - if self._check_lun_exist(lun_id) is True: - # Get lungroupid by lun_id. - lungroup_id = self._get_lungroupid_by_lunid(lun_id) - - if lungroup_id is None: - LOG.info(_LI("Can't find lun in lungroup.")) - else: - self._remove_lun_from_lungroup(lungroup_id, lun_id) - LOG.info(_LI( - "Check if there are still other luns associated" - " to the lungroup.")) - left_lunnum = self._get_lunnum_from_lungroup(lungroup_id) - return left_lunnum - - else: - LOG.warning(_LW("Can't find lun on the array.")) - - def terminate_connection_fc(self, volume, connector): - """Delete map between a volume and a host.""" - wwns = connector['wwpns'] - left_lunnum = self.terminate_connection_iscsi(volume, connector) - - tgt_port_wwns = [] - for wwn in wwns: - tgtwwpns = self._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 - - if left_lunnum and 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 login_out(self): - """logout the session.""" - url = self.url + "/sessions" - result = self.call(url, None, "DELETE") - self._assert_rest_result(result, 'Log out of session error.') - - def _start_luncopy(self, luncopyid): - """Start a LUNcopy.""" - url = self.url + "/LUNCOPY/start" - data = json.dumps({"TYPE": "219", "ID": luncopyid}) - result = self.call(url, data, "PUT") - self._assert_rest_result(result, 'Start lun copy error.') - - def _get_capacity(self): - """Get free capacity and total capacity of the pools.""" - poolinfo = self._find_pool_info() - pool_capacity = {'total_capacity': 0.0, - 'CAPACITY': 0.0} - - if poolinfo: - total = int(poolinfo['TOTALCAPACITY']) / 1024.0 / 1024.0 / 2 - free = int(poolinfo['CAPACITY']) / 1024.0 / 1024.0 / 2 - pool_capacity['total_capacity'] = total - pool_capacity['free_capacity'] = free - - return pool_capacity - - def _get_lun_conf_params(self): - """Get parameters from config file for creating lun.""" - # Default lun set information. - lunsetinfo = {'LUNType': 'Thick', - 'StripUnitSize': '64', - 'WriteType': '1', - 'MirrorSwitch': '1', - 'PrefetchType': '3', - 'PrefetchValue': '0', - 'PrefetchTimes': '0'} - - root = self._read_xml() - luntype = root.findtext('LUN/LUNType') - if luntype: - if luntype.strip() in ['Thick', 'Thin']: - lunsetinfo['LUNType'] = luntype.strip() - if luntype.strip() == 'Thick': - lunsetinfo['LUNType'] = 0 - if luntype.strip() == 'Thin': - lunsetinfo['LUNType'] = 1 - - elif luntype is not '' and luntype is not None: - err_msg = (_( - 'Config file is wrong. LUNType must be "Thin"' - ' or "Thick". LUNType: %(fetchtype)s.') - % {'fetchtype': luntype}) - LOG.error(err_msg) - raise exception.VolumeBackendAPIException(data=err_msg) - - stripunitsize = root.findtext('LUN/StripUnitSize') - if stripunitsize is not None: - lunsetinfo['StripUnitSize'] = stripunitsize.strip() - writetype = root.findtext('LUN/WriteType') - if writetype is not None: - lunsetinfo['WriteType'] = writetype.strip() - mirrorswitch = root.findtext('LUN/MirrorSwitch') - if mirrorswitch is not None: - lunsetinfo['MirrorSwitch'] = mirrorswitch.strip() - - prefetch = root.find('LUN/Prefetch') - fetchtype = prefetch.attrib['Type'] - if prefetch is not None and prefetch.attrib['Type']: - if fetchtype in ['0', '1', '2', '3']: - lunsetinfo['PrefetchType'] = fetchtype.strip() - typevalue = prefetch.attrib['Value'].strip() - if lunsetinfo['PrefetchType'] == '1': - double_value = int(typevalue) * 2 - typevalue_double = six.text_type(double_value) - lunsetinfo['PrefetchValue'] = typevalue_double - elif lunsetinfo['PrefetchType'] == '2': - lunsetinfo['PrefetchValue'] = typevalue - else: - err_msg = (_( - 'PrefetchType config is wrong. PrefetchType' - ' must be in 0,1,2,3. PrefetchType is: %(fetchtype)s.') - % {'fetchtype': fetchtype}) - LOG.error(err_msg) - raise exception.CinderException(err_msg) - else: - LOG.info(_LI( - 'Use default PrefetchType. ' - 'PrefetchType: Intelligent.')) - - return lunsetinfo - - def _get_luncopy_info(self, luncopyid): - """Get LUNcopy information.""" - url = self.url + "/LUNCOPY?range=[0-100000]" - data = json.dumps({"TYPE": "219", }) - result = self.call(url, data, "GET") - self._assert_rest_result(result, 'Get lun copy information error.') - - luncopyinfo = {} - if "data" in result: - for item in result['data']: - if luncopyid == item['ID']: - luncopyinfo['name'] = item['NAME'] - luncopyinfo['id'] = item['ID'] - luncopyinfo['state'] = item['HEALTHSTATUS'] - luncopyinfo['status'] = item['RUNNINGSTATUS'] - break - return luncopyinfo - - def _delete_luncopy(self, luncopyid): - """Delete a LUNcopy.""" - url = self.url + "/LUNCOPY/%s" % luncopyid - result = self.call(url, None, "DELETE") - self._assert_rest_result(result, 'Delete lun copy error.') - - def _get_connected_free_wwns(self): - """Get free connected FC port WWNs. - - If no new ports connected, return an empty list. - """ - url = self.url + "/fc_initiator?ISFREE=true&range=[0-1000]" - result = self.call(url, None, "GET") - - msg = 'Get connected free FC wwn error.' - self._assert_rest_result(result, msg) - - wwns = [] - if 'data' in result: - for item in result['data']: - wwns.append(item['ID']) - - return wwns - - def _add_fc_port_to_host(self, hostid, wwn): - """Add a FC port to the host.""" - url = self.url + "/fc_initiator/" + wwn - data = json.dumps({"TYPE": "223", - "ID": wwn, - "PARENTTYPE": 21, - "PARENTID": hostid}) - result = self.call(url, data, "PUT") - self._assert_rest_result(result, 'Add FC port to host error.') - - def _get_iscsi_port_info(self, ip): - """Get iscsi port info in order to build the iscsi target iqn.""" - url = self.url + "/eth_port" - result = self.call(url, None, "GET") - - msg = 'Get iSCSI port information error.' - self._assert_rest_result(result, msg) - self._assert_data_in_result(result, msg) - - iscsi_port_info = None - for item in result['data']: - if ip == item['IPV4ADDR']: - iscsi_port_info = item['LOCATION'] - break - - return iscsi_port_info - - def _get_iscsi_conf(self): - """Get iSCSI info from config file.""" - iscsiinfo = {} - root = self._read_xml() - TargetIP = root.findtext('iSCSI/DefaultTargetIP').strip() - iscsiinfo['DefaultTargetIP'] = TargetIP - initiator_list = [] - - for dic in root.findall('iSCSI/Initiator'): - # Strip values of dic. - tmp_dic = {} - for k in dic.items(): - tmp_dic[k[0]] = k[1].strip() - - initiator_list.append(tmp_dic) - - iscsiinfo['Initiator'] = initiator_list - - return iscsiinfo - - def _get_tgt_iqn(self, iscsiip): - """Get target iSCSI iqn.""" - - ip_info = self._get_iscsi_port_info(iscsiip) - iqn_prefix = self._get_iscsi_tgt_port() - - LOG.info(_LI('Request ip info is: %s.'), ip_info) - split_list = ip_info.split(".") - newstr = split_list[1] + split_list[2] - LOG.info(_LI('New str info is: %s.'), newstr) - - if ip_info: - if newstr[0] == 'A': - ctr = "0" - elif newstr[0] == 'B': - ctr = "1" - interface = '0' + newstr[1] - port = '0' + newstr[3] - iqn_suffix = ctr + '02' + interface + port - for i in range(0, len(iqn_suffix)): - if iqn_suffix[i] != '0': - iqn_suffix = iqn_suffix[i:] - break - iqn = iqn_prefix + ':' + iqn_suffix + ':' + iscsiip - LOG.info(_LI('_get_tgt_iqn: iSCSI target iqn is: %s.'), iqn) - return iqn - else: - return None - - def _get_fc_target_wwpns(self, wwn): - url = (self.url + - "/host_link?INITIATOR_TYPE=223&INITIATOR_PORT_WWN=" + wwn) - result = self.call(url, None, "GET") - - msg = 'Get FC target wwpn error.' - self._assert_rest_result(result, msg) - - fc_wwpns = None - if "data" in result: - for item in result['data']: - if wwn == item['INITIATOR_PORT_WWN']: - fc_wwpns = item['TARGET_PORT_WWN'] - break - - return fc_wwpns - - def update_volume_stats(self): - capacity = self._get_capacity() - data = {} - data['vendor_name'] = 'Huawei' - data['total_capacity_gb'] = capacity['total_capacity'] - data['free_capacity_gb'] = capacity['free_capacity'] - data['reserved_percentage'] = 0 - data['QoS_support'] = True - data['Tier_support'] = True - return data - - def _find_qos_policy_info(self, policy_name): - url = self.url + "/ioclass" - result = self.call(url, None, "GET") - - msg = 'Get QoS policy error.' - self._assert_rest_result(result, msg) - - qos_info = {} - if "data" in result: - for item in result['data']: - if policy_name == item['NAME']: - qos_info['ID'] = item['ID'] - lun_list = json.loads(item['LUNLIST']) - qos_info['LUNLIST'] = lun_list - qos_info['RUNNINGSTATUS'] = item['RUNNINGSTATUS'] - break - - return qos_info - - def _update_qos_policy_lunlist(self, lunlist, policy_id): - url = self.url + "/ioclass/" + policy_id - data = json.dumps({"TYPE": "230", - "ID": policy_id, - "LUNLIST": lunlist}) - result = self.call(url, data, "PUT") - self._assert_rest_result(result, 'Update QoS policy error.') - - def _get_login_info(self): - """Get login IP, username and password from config file.""" - logininfo = {} - filename = self.configuration.cinder_huawei_conf_file - tree = ET.parse(filename) - root = tree.getroot() - logininfo['RestURL'] = root.findtext('Storage/RestURL').strip() - - need_encode = False - for key in ['UserName', 'UserPassword']: - node = root.find('Storage/%s' % key) - node_text = node.text - # Prefix !$$$ means encoded already. - if node_text.find('!$$$') > -1: - logininfo[key] = base64.b64decode(node_text[4:]) - else: - logininfo[key] = node_text - if six.PY3: - node_b64 = node_text.encode('utf-8') - node_b64 = base64.b64encode(node_b64) - node_b64 = node_b64.decode('ascii') - node.text = '!$$$' + node_b64 - else: - node.text = '!$$$' + base64.b64encode(node_text) - need_encode = True - if need_encode: - self._change_file_mode(filename) - try: - tree.write(filename, 'UTF-8') - except Exception as err: - LOG.warning(_LW('Unable to access config file. %s'), err) - - return logininfo - - def _change_file_mode(self, filepath): - utils.execute('chmod', '640', filepath, run_as_root=True) - - def _check_conf_file(self): - """Check the config file, make sure the essential items are set.""" - root = self._read_xml() - resturl = root.findtext('Storage/RestURL') - username = root.findtext('Storage/UserName') - pwd = root.findtext('Storage/UserPassword') - pool_node = root.findall('LUN/StoragePool') - - if (not resturl) or (not username) or (not pwd): - err_msg = (_( - '_check_conf_file: Config file invalid. RestURL,' - ' UserName and UserPassword must be set.')) - LOG.error(err_msg) - raise exception.InvalidInput(reason=err_msg) - - if not pool_node: - err_msg = (_( - '_check_conf_file: Config file invalid. ' - 'StoragePool must be set.')) - LOG.error(err_msg) - raise exception.InvalidInput(reason=err_msg) - - def _get_iscsi_params(self, connector): - """Get target iSCSI params, including iqn, IP.""" - initiator = connector['initiator'] - iscsi_conf = self._get_iscsi_conf() - target_ip = None - for ini in iscsi_conf['Initiator']: - if ini['Name'] == initiator: - target_ip = ini['TargetIP'] - break - # If didn't specify target IP for some initiator, use default IP. - if not target_ip: - if iscsi_conf['DefaultTargetIP']: - target_ip = iscsi_conf['DefaultTargetIP'] - - else: - msg = (_( - '_get_iscsi_params: Failed to get target IP ' - 'for initiator %(ini)s, please check config file.') - % {'ini': initiator}) - LOG.error(msg) - raise exception.InvalidInput(reason=msg) - - # If didn't get target IP for rest, Automated assembly target ip. - target_iqn = self._get_tgt_iqn_from_rest(target_ip) - - if not target_iqn: - target_iqn = self._get_tgt_iqn(target_ip) - - return (target_iqn, target_ip) - - def _get_tgt_iqn_from_rest(self, target_ip): - url = self.url + "/iscsi_tgt_port" - result = self.call(url, None, "GET") - - target_iqn = None - if result['error']['code'] != 0: - LOG.warning(_LW("Can't find target iqn from rest.")) - return target_iqn - - if 'data' in result: - for item in result['data']: - if target_ip in item['ID']: - target_iqn = item['ID'] - - if not target_iqn: - LOG.warning(_LW("Can't find target iqn from rest.")) - return target_iqn - - split_list = target_iqn.split(",") - target_iqn_before = split_list[0] - - split_list_new = target_iqn_before.split("+") - target_iqn = split_list_new[1] - - return target_iqn - - @utils.synchronized('huawei', external=True) - def extend_volume(self, volume, new_size): - """Extends a Huawei volume.""" - - LOG.info(_LI('Entering extend_volume.')) - volume_size = self._get_volume_size(volume) - new_volume_size = int(new_size) * units.Gi / 512 - volume_name = self._encode_name(volume['id']) - - LOG.info(_LI('Extend Volume: %(volumename)s, oldsize: %(oldsize)s ' - 'newsize: %(newsize)s.'), - {'volumename': volume_name, - 'oldsize': volume_size, - 'newsize': new_volume_size}) - - lun_id = self._get_volume_by_name(volume_name) - - if lun_id is None: - msg = (_( - "Can't find lun info on the array, lun name is: %(name)s.") - % {'name': volume_name}) - LOG.error(msg) - raise exception.CinderException(msg) - - url = self.url + "/lun/expand" - data = json.dumps({"TYPE": 11, "ID": lun_id, - "CAPACITY": new_volume_size}) - result = self.call(url, data, 'PUT') - - msg = 'Extend volume error.' - self._assert_rest_result(result, msg) - self._assert_data_in_result(result, msg) - - return result['data']['ID'] - - def _get_volume_type(self, type_id): - ctxt = context.get_admin_context() - return volume_types.get_volume_type(ctxt, type_id) - - def _get_qos_by_volume_type(self, volume_type): - qos = {} - qos_specs_id = volume_type.get('qos_specs_id') - specs = volume_type.get('extra_specs') - - # NOTE(kmartin): We prefer the qos_specs association - # and override any existing extra-specs settings - # if present. - if qos_specs_id is not None: - kvs = qos_specs.get_qos_specs(context.get_admin_context(), - qos_specs_id)['specs'] - else: - kvs = specs - - LOG.info(_LI('The QoS sepcs is: %s.'), kvs) - for key, value in kvs.items(): - if key in huawei_valid_keys: - qos[key.upper()] = value - - return qos - - def _get_qos_value(self, qos, key, default=None): - if key in qos: - return qos[key] - else: - return default - - def _create_qos_policy(self, qos, lun_id): - - # Get local time. - localtime = time.strftime('%Y%m%d%H%M%S', time.localtime(time.time())) - # Package QoS name. - qos_name = QOS_NAME_PREFIX + lun_id + '_' + localtime - - mergedata = {"TYPE": "230", - "NAME": qos_name, - "LUNLIST": ["%s" % lun_id], - "CLASSTYPE": "1", - "SCHEDULEPOLICY": "2", - "SCHEDULESTARTTIME": "1410969600", - "STARTTIME": "08:00", - "DURATION": "86400", - "CYCLESET": "[1,2,3,4,5,6,0]"} - mergedata.update(qos) - data = json.dumps(mergedata) - url = self.url + "/ioclass/" - - result = self.call(url, data) - self._assert_rest_result(result, 'Create QoS policy error.') - - return result['data']['ID'] - - def _delete_qos_policy(self, qos_id): - """Delete a QoS policy.""" - - url = self.url + "/ioclass/" + qos_id - data = json.dumps({"TYPE": "230", - "ID": qos_id}) - - result = self.call(url, data, 'DELETE') - self._assert_rest_result(result, 'Delete QoS policy error.') - - def _active_deactive_qos(self, qos_id, enablestatus): - """Active or deactive QoS. - - enablestatus: true (active) - enbalestatus: false (deactive) - """ - - url = self.url + "/ioclass/active/" + qos_id - data = json.dumps({"TYPE": 230, - "ID": qos_id, - "ENABLESTATUS": enablestatus}) - result = self.call(url, data, "PUT") - self._assert_rest_result(result, 'Active or Deactive QoS error.') - - def _get_qos_info(self, qos_id): - """Get QoS information.""" - - url = self.url + "/ioclass/" + qos_id - data = json.dumps({"TYPE": "230", - "ID": qos_id}) - result = self.call(url, data, "GET") - self._assert_rest_result(result, 'Get QoS information error.') - - return result['data'] - - def _check_qos_high_priority(self, qos): - """Check QoS priority.""" - - for key, value in qos.items(): - if (key.find('MIN') == 0) or (key.find('LATENCY') == 0): - return True - - return False - - def _change_lun_priority(self, lunid): - """Change lun priority to high.""" - - url = self.url + "/lun/" + lunid - data = json.dumps({"TYPE": "11", - "ID": lunid, - "IOPRIORITY": "3"}) - - result = self.call(url, data, "PUT") - self._assert_rest_result(result, 'Change lun priority error.') - - def _get_qosid_by_lunid(self, lunid): - """Get qosid by lunid.""" - - url = self.url + "/lun/" + lunid - data = json.dumps({"TYPE": "11", - "ID": lunid}) - - result = self.call(url, data, "GET") - self._assert_rest_result(result, 'Get qosid by lunid error.') - - return result['data']['IOCLASSID'] - - def _get_lungroupid_by_lunid(self, lunid): - """Get lungroupid by lunid.""" - - url = self.url + ("/lungroup/associate?TYPE=256" - "&ASSOCIATEOBJTYPE=11&ASSOCIATEOBJID=%s" % lunid) - - result = self.call(url, None, "GET") - self._assert_rest_result(result, 'Get lungroupid by lunid error.') - - lun_group_id = None - # Lun only in one lungroup. - if 'data' in result: - for item in result['data']: - lun_group_id = item['ID'] - - return lun_group_id diff --git a/cinder/volume/manager.py b/cinder/volume/manager.py index 8c323ab08..3ac4aa59f 100644 --- a/cinder/volume/manager.py +++ b/cinder/volume/manager.py @@ -109,10 +109,10 @@ CONF = cfg.CONF CONF.register_opts(volume_manager_opts) MAPPING = { - 'cinder.volume.drivers.huawei.huawei_hvs.HuaweiHVSISCSIDriver': - 'cinder.volume.drivers.huawei.huawei_18000.Huawei18000ISCSIDriver', - 'cinder.volume.drivers.huawei.huawei_hvs.HuaweiHVSFCDriver': - 'cinder.volume.drivers.huawei.huawei_18000.Huawei18000FCDriver', + 'cinder.volume.drivers.huawei.huawei_18000.Huawei18000ISCSIDriver': + 'cinder.volume.drivers.huawei.huawei_driver.Huawei18000ISCSIDriver', + 'cinder.volume.drivers.huawei.huawei_18000.Huawei18000FCDriver': + 'cinder.volume.drivers.huawei.huawei_driver.Huawei18000FCDriver', 'cinder.volume.drivers.fujitsu_eternus_dx_fc.FJDXFCDriver': 'cinder.volume.drivers.fujitsu.eternus_dx_fc.FJDXFCDriver', 'cinder.volume.drivers.fujitsu_eternus_dx_iscsi.FJDXISCSIDriver': diff --git a/tox.ini b/tox.ini index ce92a0bc5..063a4a601 100644 --- a/tox.ini +++ b/tox.ini @@ -66,7 +66,7 @@ commands = cinder.tests.unit.test_hitachi_hbsd_snm2_iscsi \ cinder.tests.unit.test_hp_xp_fc \ cinder.tests.unit.test_hplefthand \ - cinder.tests.unit.test_huawei_18000 \ + cinder.tests.unit.test_huawei_drivers \ cinder.tests.unit.test_huawei_drivers_compatibility \ cinder.tests.unit.test_ibm_xiv_ds8k \ cinder.tests.unit.test_infortrend_cli \