From 837ba79d876a685b5870c7b3c0ffb2b37407ac80 Mon Sep 17 00:00:00 2001 From: Bob-OpenStack <295988511@qq.com> Date: Sun, 23 Nov 2014 18:11:22 -0800 Subject: [PATCH] Update volume driver for Huawei storage system This driver is similar to the previous one, but contains the following differences: 1. Remove smart tier support because that our product does not support smart tier any more. 18000 does not support smart tier, so this has no problem with 18000 series. 2. Update qos support implementation. 3. Add synchronization because that our tests show that when we create volumes or snapshots in batch in a shell script with cli command, our array may occur error. Certification test result for Huawei storage drivers: https://bugs.launchpad.net/cinder/+bug/1399038 Implements: blueprint huawei-storage-drivers Change-Id: I69c32ef3225c23881be522eac451b8855805e35d --- cinder/tests/test_huawei_18000.py | 935 +++++++++++++ .../test_huawei_drivers_compatibility.py | 64 + cinder/tests/test_huawei_hvs.py | 862 ------------ cinder/volume/drivers/huawei/__init__.py | 32 +- .../huawei/{huawei_hvs.py => huawei_18000.py} | 101 +- cinder/volume/drivers/huawei/rest_common.py | 1199 +++++++++++------ cinder/volume/manager.py | 10 + 7 files changed, 1880 insertions(+), 1323 deletions(-) create mode 100644 cinder/tests/test_huawei_18000.py create mode 100644 cinder/tests/test_huawei_drivers_compatibility.py delete mode 100644 cinder/tests/test_huawei_hvs.py rename cinder/volume/drivers/huawei/{huawei_hvs.py => huawei_18000.py} (57%) diff --git a/cinder/tests/test_huawei_18000.py b/cinder/tests/test_huawei_18000.py new file mode 100644 index 000000000..626a4f2f7 --- /dev/null +++ b/cinder/tests/test_huawei_18000.py @@ -0,0 +1,935 @@ +# 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.minidom import Document + +import mock + +from cinder import exception +from cinder.openstack.common import log as logging +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 + +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, + '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.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": + 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"): + data = '{"error":{"code":0}}' + + 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": + if 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 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 = 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) + + 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 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 = 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) + + 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/test_huawei_drivers_compatibility.py b/cinder/tests/test_huawei_drivers_compatibility.py new file mode 100644 index 000000000..e082a15f6 --- /dev/null +++ b/cinder/tests/test_huawei_drivers_compatibility.py @@ -0,0 +1,64 @@ +# Copyright 2012 OpenStack Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + + +from oslo.config import cfg +from oslo.utils import importutils + +from cinder import context +from cinder import test + + +CONF = cfg.CONF +HUAWEI_ISCSI_MODULE = ("cinder.volume.drivers.huawei.huawei_18000." + "Huawei18000ISCSIDriver") +HUAWEI_FC_MODULE = ("cinder.volume.drivers.huawei.huawei_18000." + "Huawei18000FCDriver") + + +class VolumeDriverCompatibility(test.TestCase): + """Test backwards compatibility for volume drivers.""" + + def fake_update_cluster_status(self): + return + + def setUp(self): + super(VolumeDriverCompatibility, self).setUp() + self.manager = importutils.import_object(CONF.volume_manager) + self.context = context.get_admin_context() + + def _load_driver(self, driver): + self.manager.__init__(volume_driver=driver) + + def _driver_module_name(self): + return "%s.%s" % (self.manager.driver.__class__.__module__, + self.manager.driver.__class__.__name__) + + def test_huawei_driver_iscsi_old(self): + self._load_driver( + 'cinder.volume.drivers.huawei.huawei_hvs.HuaweiHVSISCSIDriver') + self.assertEqual(self._driver_module_name(), HUAWEI_ISCSI_MODULE) + + def test_huawei_driver_iscsi_new(self): + self._load_driver(HUAWEI_ISCSI_MODULE) + self.assertEqual(self._driver_module_name(), HUAWEI_ISCSI_MODULE) + + def test_huawei_driver_fc_old(self): + self._load_driver( + 'cinder.volume.drivers.huawei.huawei_hvs.HuaweiHVSFCDriver') + self.assertEqual(self._driver_module_name(), HUAWEI_FC_MODULE) + + def test_huawei_driver_fc_new(self): + self._load_driver(HUAWEI_FC_MODULE) + self.assertEqual(self._driver_module_name(), HUAWEI_FC_MODULE) diff --git a/cinder/tests/test_huawei_hvs.py b/cinder/tests/test_huawei_hvs.py deleted file mode 100644 index 1c6f51621..000000000 --- a/cinder/tests/test_huawei_hvs.py +++ /dev/null @@ -1,862 +0,0 @@ - -# Copyright (c) 2013 Huawei Technologies Co., Ltd. -# Copyright (c) 2013 OpenStack Foundation -# 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. -""" -Unit Tests for Huawei HVS volume drivers. -""" - -import json -import os -import shutil -import tempfile -import time -from xml.dom.minidom import Document - -import mox - -from cinder import exception -from cinder import test -from cinder.volume import configuration as conf -from cinder.volume.drivers.huawei import huawei_hvs -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} - -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} - -FakeConnector = {'initiator': 'iqn.1993-08.debian:01:ec2bff7ac3a3', - 'wwpns': ['10000090fa0d6754'], - 'wwnns': ['10000090fa0d6755'], - 'host': 'fakehost', - 'ip': '10.10.0.1'} - -volume_size = 3 - - -def Fake_sleep(time): - pass - - -class FakeHVSCommon(rest_common.HVSCommon): - - def __init__(self, configuration): - rest_common.HVSCommon.__init__(self, configuration) - self.test_normal = True - self.other_flag = True - self.deviceid = None - self.lun_id = None - self.snapshot_id = None - self.luncopy_id = None - self.termin_flag = False - - def _parse_volume_type(self, volume): - self._get_lun_conf_params() - 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 _change_file_mode(self, filepath): - # NOTE(flaper87): Changing file permissions is - # not needed since we're using a tempfile created - # within this test. - pass - - def call(self, url=False, data=None, method=None): # noqa - - 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": - data = """{"error":{"code":0}, - "data":{"username":"admin", - "deviceid":"210235G7J20000000000" - }}""" - if url == "sessions": - data = """{"error":{"code":0}, - "data":{"ID":11}}""" - - if url == "storagepool": - data = """{"error":{"code":0}, - "data":[{"ID":"0", - "NAME":"OpenStack_Pool", - "USERFREECAPACITY":"985661440", - "USERTOTALCAPACITY":"985661440" - }]}""" - - if url == "lun": - if method is None: - data = """{"error":{"code":0}, - "data":{"ID":"1", - "NAME":"5mFHcBv4RkCcD+JyrWc0SA"}}""" - self.lun_id = "0" - - if method == 'GET': - data = """{"error":{"code":0}, - "data":[{"ID":"1", - "NAME":"IexzQZJWSXuX2e9I7c8GNQ"}]}""" - - if url == "lungroup": - if method is None: - data = """{"error":{"code":0}, - "data":{"NAME":"5mFHcBv4RkCcD+JyrWc0SA", - "DESCRIPTION":"5mFHcBv4RkCcD", - "ID":"11", - "TYPE":256}}""" - - if method == "GET": - data = """{"error":{"code":0}, - "data":[{"NAME":"IexzQZJWSXuX2e9I7c8GNQ", - "DESCRIPTION":"5mFHcBv4RkCcD", - "ID":"11", - "TYPE":256}]}""" - - if method == "DELETE": - data = """{"error":{"code":0}, - "data":[{"NAME":"IexzQZJWSXuX2e9I7c8GNQ", - "DESCRIPTION":"5mFHcBv4RkCcD+JyrWc0SA", - "ID":"11", - "TYPE":256}]}""" - - if url == "lungroup/associate": - data = """{"error":{"code":0}, - "data":{"NAME":"5mFHcBv4RkCcD+JyrWc0SA", - "DESCRIPTION":"5mFHcBv4RkCcD+JyrWc0SA", - "ID":"11", - "TYPE":256}}""" - - if url == "snapshot": - if method is None: - data = """{"error":{"code":0}, - "data":{"ID":11}}""" - self.snapshot_id = "3" - - if method == "GET": - data = """{"error":{"code":0}, - "data":[{"ID":11,"NAME":"SDFAJSDFLKJ"}, - {"ID":12,"NAME":"SDFAJSDFLKJ"}]}""" - - if url == "snapshot/activate": - data = """{"error":{"code":0}}""" - - if url == ("lungroup/associate?ID=11" - "&ASSOCIATEOBJTYPE=11&ASSOCIATEOBJID=1"): - data = """{"error":{"code":0}}""" - - if url == "LUNGroup/11": - data = """{"error":{"code":0}}""" - - if url == 'lun/1': - data = """{"error":{"code":0}}""" - self.lun_id = None - - if url == 'snapshot': - if method == "GET": - data = """{"error":{"code":0}, - "data":[{"PARENTTYPE":11, - "NAME":"IexzQZJWSXuX2e9I7c8GNQ", - "WWN":"60022a11000a2a3907ce96cb00000b", - "ID":"11", - "CONSUMEDCAPACITY":"0"}]}""" - - if url == "snapshot/stop": - data = """{"error":{"code":0}}""" - - if url == "snapshot/11": - data = """{"error":{"code":0}}""" - self.snapshot_id = None - - 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"}}""" - self.luncopy_id = "7" - - if url == "LUNCOPY/start": - data = """{"error":{"code":0}}""" - - 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 == "LUNCOPY/0": - data = '{"error":{"code":0}}' - - if url == "eth_port": - data = """{"error":{"code":0}, - "data":[{"PARENTTYPE":209, - "MACADDRESS":"00:22:a1:0a:79:57", - "ETHNEGOTIATE":"-1","ERRORPACKETS":"0", - "IPV4ADDR":"100.115.10.68", - "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"}]}""" - - if url == "iscsidevicename": - data = """{"error":{"code":0}, -"data":[{"CMO_ISCSI_DEVICE_NAME": -"iqn.2006-08.com.huawei:oceanstor:21000022a10a2a39:iscsinametest"}]}""" - - if url == "hostgroup": - 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}]}""" - - if url == "host": - 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}]} """ - - if url == "host/associate": - if method is None: - data = """{"error":{"code":0}}""" - if method == "GET": - data = """{"error":{"code":0}}""" - - if url == "iscsi_initiator/iqn.1993-08.debian:01:ec2bff7ac3a3": - data = """{"error":{"code":0}, - "data":{"ID":"iqn.1993-08.win:01:ec2bff7ac3a3", - "NAME":"iqn.1993-08.win:01:ec2bff7ac3a3", - "ISFREE":"True"}}""" - - if url == "iscsi_initiator/": - data = """{"error":{"code":0}}""" - - if url == "iscsi_initiator": - data = """{"error":{"code":0}}""" - - if url == "mappingview": - self.termin_flag = True - 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 self.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}]}""" - - if url == "MAPPINGVIEW/CREATE_ASSOCIATE": - data = """{"error":{"code":0}}""" - - if url == ("lun/associate?TYPE=11&" - "ASSOCIATEOBJTYPE=21&ASSOCIATEOBJID=0"): - data = """{"error":{"code":0}}""" - - if url == "fc_initiator?ISFREE=true&range=[0-1000]": - 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}]}""" - - if url == "host_link?INITIATOR_TYPE=223&INITIATOR_PORT_WWN="\ - "10000090fa0d6754": - - 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"}]}""" - - if url == ("mappingview/associate?TYPE=245&" - "ASSOCIATEOBJTYPE=14&ASSOCIATEOBJID=0"): - - data = """{"error":{"code":0}, - "data":[{"ID":11,"NAME":"test"}]}""" - - if url == ("mappingview/associate?TYPE=245&" - "ASSOCIATEOBJTYPE=256&ASSOCIATEOBJID=11"): - - data = """{"error":{"code":0}, - "data":[{"ID":11,"NAME":"test"}]}""" - - if url == "fc_initiator/10000090fa0d6754": - data = """{"error":{"code":0}}""" - - if url == "mappingview/REMOVE_ASSOCIATE": - data = """{"error":{"code":0}}""" - self.termin_flag = True - - if url == "mappingview/1": - data = """{"error":{"code":0}}""" - - if url == "ioclass": - data = """{"error":{"code":0}, - "data":[{"NAME":"OpenStack_Qos_High", - "ID":"0", - "LUNLIST":"[]", - "TYPE":230}]}""" - - if url == "ioclass/0": - data = """{"error":{"code":0}}""" - - if url == "lun/expand": - data = """{"error":{"code":0}}""" - self.lun_id = '0' - - else: - data = """{"error":{"code":31755596}}""" - - res_json = json.loads(data) - return res_json - - -class FakeHVSiSCSIStorage(huawei_hvs.HuaweiHVSISCSIDriver): - - def __init__(self, configuration): - super(FakeHVSiSCSIStorage, self).__init__(configuration) - self.configuration = configuration - - def do_setup(self, context): - self.common = FakeHVSCommon(configuration=self.configuration) - - -class FakeHVSFCStorage(huawei_hvs.HuaweiHVSFCDriver): - - def __init__(self, configuration): - super(FakeHVSFCStorage, self).__init__(configuration) - self.configuration = configuration - - def do_setup(self, context): - self.common = FakeHVSCommon(configuration=self.configuration) - - -class HVSRESTiSCSIDriverTestCase(test.TestCase): - def setUp(self): - super(HVSRESTiSCSIDriverTestCase, self).setUp() - self.tmp_dir = tempfile.mkdtemp() - self.addCleanup(shutil.rmtree, self.tmp_dir) - self.fake_conf_file = self.tmp_dir + '/cinder_huawei_conf.xml' - self.addCleanup(os.remove, self.fake_conf_file) - - self.create_fake_conf_file() - self.configuration = mox.MockObject(conf.Configuration) - self.configuration.cinder_huawei_conf_file = self.fake_conf_file - self.configuration.append_config_values(mox.IgnoreArg()) - - self.stubs.Set(time, 'sleep', Fake_sleep) - #self.stubs.Set(greenthread, 'sleep', Fake_sleep) - - self.driver = FakeHVSiSCSIStorage(configuration=self.configuration) - self.driver.do_setup({}) - self.driver.common.test_normal = True - - def test_log_in_success(self): - deviceid = self.driver.common.login() - self.assertIsNotNone(deviceid) - - def test_log_out_success(self): - self.driver.common.login() - self.driver.common.login_out() - - def test_create_volume_success(self): - self.driver.common.login() - self.driver.create_volume(test_volume) - self.assertEqual(self.driver.common.lun_id, "0") - - def test_extend_volume_success(self): - self.driver.common.login() - self.driver.extend_volume(test_volume, volume_size) - self.assertEqual(self.driver.common.lun_id, "0") - - def test_create_snapshot_success(self): - self.driver.common.login() - self.driver.create_snapshot(test_volume) - self.assertEqual(self.driver.common.snapshot_id, "3") - - def test_delete_volume_success(self): - self.driver.common.login() - self.driver.delete_volume(test_volume) - self.assertIsNone(self.driver.common.lun_id) - - def test_delete_snapshot_success(self): - self.driver.common.login() - self.driver.delete_snapshot(test_snap) - self.assertIsNone(self.driver.common.snapshot_id) - - def test_colone_volume_success(self): - self.driver.common.login() - self.driver.create_cloned_volume(test_volume, test_volume) - self.assertEqual(self.driver.common.luncopy_id, "7") - - def test_create_volume_from_snapshot_success(self): - self.driver.common.login() - self.driver.create_volume_from_snapshot(test_volume, test_volume) - self.assertEqual(self.driver.common.luncopy_id, "7") - - def test_initialize_connection_success(self): - self.driver.common.login() - conn = self.driver.initialize_connection(test_volume, FakeConnector) - self.assertEqual(conn['data']['target_lun'], 1) - - def test_terminate_connection_success(self): - self.driver.common.login() - self.driver.terminate_connection(test_volume, FakeConnector) - self.assertEqual(self.driver.common.termin_flag, True) - - def test_initialize_connection_no_view_success(self): - self.driver.common.login() - self.driver.common.other_flag = False - conn = self.driver.initialize_connection(test_volume, FakeConnector) - self.assertEqual(conn['data']['target_lun'], 1) - - def test_terminate_connectio_no_view_success(self): - self.driver.common.login() - self.driver.common.other_flag = False - self.driver.terminate_connection(test_volume, FakeConnector) - self.assertEqual(self.driver.common.termin_flag, True) - - def test_get_volume_stats(self): - self.driver.common.login() - status = self.driver.get_volume_stats() - self.assertIsNotNone(status['free_capacity_gb']) - - def test_create_snapshot_fail(self): - self.driver.common.login() - self.driver.common.test_normal = False - self.assertRaises(exception.CinderException, - self.driver.create_snapshot, test_volume) - - def test_create_volume_fail(self): - self.driver.common.login() - self.driver.common.test_normal = False - self.assertRaises(exception.CinderException, - self.driver.create_volume, test_volume) - - def test_delete_volume_fail(self): - self.driver.common.login() - self.driver.common.test_normal = False - self.assertRaises(exception.CinderException, - self.driver.delete_volume, test_volume) - - def test_delete_snapshot_fail(self): - self.driver.common.login() - self.driver.common.test_normal = False - self.assertRaises(exception.CinderException, - self.driver.delete_snapshot, test_volume) - - def test_initialize_connection_fail(self): - self.driver.common.login() - self.driver.common.test_normal = False - self.assertRaises(exception.CinderException, - self.driver.initialize_connection, - test_volume, FakeConnector) - - def create_fake_conf_file(self): - doc = Document() - - config = doc.createElement('config') - doc.appendChild(config) - - storage = doc.createElement('Storage') - config.appendChild(storage) - - product = doc.createElement('Product') - product_text = doc.createTextNode('HVS') - product.appendChild(product_text) - storage.appendChild(product) - - protocol = doc.createElement('Protocol') - protocol_text = doc.createTextNode('iSCSI') - protocol.appendChild(protocol_text) - storage.appendChild(protocol) - - 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('HVSURL') - 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) - - luntype = doc.createElement('LUNType') - luntype_text = doc.createTextNode('Thick') - luntype.appendChild(luntype_text) - lun.appendChild(luntype) - - writetype = doc.createElement('WriteType') - writetype_text = doc.createTextNode('1') - writetype.appendChild(writetype_text) - lun.appendChild(writetype) - - prefetchType = doc.createElement('Prefetch') - prefetchType.setAttribute('Type', '2') - prefetchType.setAttribute('Value', '20') - lun.appendChild(prefetchType) - - 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', '100.115.10.68') - iscsi.appendChild(initiator) - - newefile = open(self.fake_conf_file, 'w') - newefile.write(doc.toprettyxml(indent='')) - newefile.close() - - -class HVSRESTFCDriverTestCase(test.TestCase): - def setUp(self): - super(HVSRESTFCDriverTestCase, self).setUp() - self.tmp_dir = tempfile.mkdtemp() - self.addCleanup(shutil.rmtree, self.tmp_dir) - self.fake_conf_file = self.tmp_dir + '/cinder_huawei_conf.xml' - self.addCleanup(os.remove, self.fake_conf_file) - - self.create_fake_conf_file() - self.configuration = mox.MockObject(conf.Configuration) - self.configuration.cinder_huawei_conf_file = self.fake_conf_file - self.configuration.append_config_values(mox.IgnoreArg()) - - self.stubs.Set(time, 'sleep', Fake_sleep) - - self.driver = FakeHVSFCStorage(configuration=self.configuration) - self.driver.do_setup({}) - self.driver.common.test_normal = True - - def test_log_in_Success(self): - deviceid = self.driver.common.login() - self.assertIsNotNone(deviceid) - - def test_create_volume_success(self): - self.driver.common.login() - self.driver.create_volume(test_volume) - self.assertEqual(self.driver.common.lun_id, "0") - - def test_extend_volume_success(self): - self.driver.common.login() - self.driver.extend_volume(test_volume, volume_size) - self.assertEqual(self.driver.common.lun_id, "0") - - def test_create_snapshot_success(self): - self.driver.common.login() - self.driver.create_snapshot(test_volume) - self.assertEqual(self.driver.common.snapshot_id, "3") - - def test_delete_volume_success(self): - self.driver.common.login() - self.driver.delete_volume(test_volume) - self.assertIsNone(self.driver.common.lun_id) - - def test_delete_snapshot_success(self): - self.driver.common.login() - self.driver.delete_snapshot(test_snap) - self.assertIsNone(self.driver.common.snapshot_id) - - def test_colone_volume_success(self): - self.driver.common.login() - self.driver.create_cloned_volume(test_volume, test_volume) - self.assertEqual(self.driver.common.luncopy_id, "7") - - def test_create_volume_from_snapshot_success(self): - self.driver.common.login() - self.driver.create_volume_from_snapshot(test_volume, test_volume) - self.assertEqual(self.driver.common.luncopy_id, "7") - - def test_initialize_connection_success(self): - self.driver.common.login() - conn = self.driver.initialize_connection(test_volume, FakeConnector) - self.assertEqual(conn['data']['target_lun'], 1) - - def test_terminate_connection_success(self): - self.driver.common.login() - self.driver.terminate_connection(test_volume, FakeConnector) - self.assertEqual(self.driver.common.termin_flag, True) - - def test_initialize_connection_no_view_success(self): - self.driver.common.login() - self.driver.common.other_flag = False - conn = self.driver.initialize_connection(test_volume, FakeConnector) - self.assertEqual(conn['data']['target_lun'], 1) - - def test_terminate_connection_no_viewn_success(self): - self.driver.common.login() - self.driver.common.other_flag = False - self.driver.terminate_connection(test_volume, FakeConnector) - self.assertEqual(self.driver.common.termin_flag, True) - - def test_get_volume_stats(self): - self.driver.common.login() - status = self.driver.get_volume_stats() - self.assertIsNotNone(status['free_capacity_gb']) - - def test_create_snapshot_fail(self): - self.driver.common.login() - self.driver.common.test_normal = False - self.assertRaises(exception.CinderException, - self.driver.create_snapshot, test_volume) - - def test_create_volume_fail(self): - self.driver.common.login() - self.driver.common.test_normal = False - self.assertRaises(exception.CinderException, - self.driver.create_volume, test_volume) - - def test_delete_volume_fail(self): - self.driver.common.login() - self.driver.common.test_normal = False - self.assertRaises(exception.CinderException, - self.driver.delete_volume, test_volume) - - def test_delete_snapshot_fail(self): - self.driver.common.login() - self.driver.common.test_normal = False - self.assertRaises(exception.CinderException, - self.driver.delete_snapshot, test_volume) - - def create_fake_conf_file(self): - doc = Document() - - config = doc.createElement('config') - doc.appendChild(config) - - storage = doc.createElement('Storage') - config.appendChild(storage) - - product = doc.createElement('Product') - product_text = doc.createTextNode('HVS') - product.appendChild(product_text) - storage.appendChild(product) - - protocol = doc.createElement('Protocol') - protocol_text = doc.createTextNode('FC') - protocol.appendChild(protocol_text) - storage.appendChild(protocol) - - 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('HVSURL') - 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) - - luntype = doc.createElement('LUNType') - luntype_text = doc.createTextNode('Thick') - luntype.appendChild(luntype_text) - lun.appendChild(luntype) - - writetype = doc.createElement('WriteType') - writetype_text = doc.createTextNode('1') - writetype.appendChild(writetype_text) - lun.appendChild(writetype) - - prefetchType = doc.createElement('Prefetch') - prefetchType.setAttribute('Type', '2') - prefetchType.setAttribute('Value', '20') - lun.appendChild(prefetchType) - - newfile = open(self.fake_conf_file, 'w') - newfile.write(doc.toprettyxml(indent='')) - newfile.close() diff --git a/cinder/volume/drivers/huawei/__init__.py b/cinder/volume/drivers/huawei/__init__.py index 3fca8eed4..554a3f6ad 100644 --- a/cinder/volume/drivers/huawei/__init__.py +++ b/cinder/volume/drivers/huawei/__init__.py @@ -1,5 +1,4 @@ -# Copyright (c) 2013 Huawei Technologies Co., Ltd. -# Copyright (c) 2012 OpenStack Foundation +# Copyright (c) 2013 - 2014 Huawei Technologies Co., Ltd. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may @@ -20,15 +19,17 @@ The product type and the protocol should be specified in config file before. """ from oslo.config import cfg +import six from cinder import exception -from cinder.i18n import _ +from cinder.i18n import _, _LI, _LW from cinder.openstack.common import log as logging +from cinder.volume.drivers.huawei import huawei_18000 from cinder.volume.drivers.huawei import huawei_dorado -from cinder.volume.drivers.huawei import huawei_hvs from cinder.volume.drivers.huawei import huawei_t from cinder.volume.drivers.huawei import huawei_utils + LOG = logging.getLogger(__name__) huawei_opt = [ @@ -39,6 +40,7 @@ huawei_opt = [ CONF = cfg.CONF CONF.register_opts(huawei_opt) +MAPPING = {'HVS': '18000'} class HuaweiVolumeDriver(object): @@ -47,7 +49,7 @@ class HuaweiVolumeDriver(object): def __init__(self, *args, **kwargs): super(HuaweiVolumeDriver, self).__init__() self._product = {'T': huawei_t, 'Dorado': huawei_dorado, - 'HVS': huawei_hvs} + '18000': huawei_18000, 'HVS': huawei_18000} self._protocol = {'iSCSI': 'ISCSIDriver', 'FC': 'FCDriver'} self.driver = self._instantiate_driver(*args, **kwargs) @@ -63,10 +65,16 @@ class HuaweiVolumeDriver(object): conf_file = self.configuration.cinder_huawei_conf_file (product, protocol) = self._get_conf_info(conf_file) - LOG.debug('_instantiate_driver: Loading %(protocol)s driver for ' - 'Huawei OceanStor %(product)s series storage arrays.' - % {'protocol': protocol, - 'product': product}) + 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.warn(_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] @@ -84,11 +92,11 @@ class HuaweiVolumeDriver(object): return (product, protocol) else: msg = (_('"Product" or "Protocol" is illegal. "Product" should ' - 'be set to either T, Dorado or HVS. "Protocol" should ' + 'be set to either T, Dorado or 18000. "Protocol" should ' 'be set to either iSCSI or FC. Product: %(product)s ' 'Protocol: %(protocol)s') - % {'product': product, - 'protocol': protocol}) + % {'product': six.text_type(product), + 'protocol': six.text_type(protocol)}) raise exception.InvalidInput(reason=msg) def __setattr__(self, name, value): diff --git a/cinder/volume/drivers/huawei/huawei_hvs.py b/cinder/volume/drivers/huawei/huawei_18000.py similarity index 57% rename from cinder/volume/drivers/huawei/huawei_hvs.py rename to cinder/volume/drivers/huawei/huawei_18000.py index 098346561..f936cde29 100644 --- a/cinder/volume/drivers/huawei/huawei_hvs.py +++ b/cinder/volume/drivers/huawei/huawei_18000.py @@ -1,5 +1,4 @@ -# Copyright (c) 2013 Huawei Technologies Co., Ltd. -# Copyright (c) 2013 OpenStack Foundation +# Copyright (c) 2013 - 2014 Huawei Technologies Co., Ltd. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may @@ -14,62 +13,75 @@ # License for the specific language governing permissions and limitations # under the License. """ -Volume Drivers for Huawei OceanStor HVS storage arrays. +Volume Drivers for Huawei OceanStor 18000 storage arrays. """ from cinder.volume import driver -from cinder.volume.drivers.huawei.rest_common import HVSCommon +from cinder.volume.drivers.huawei.rest_common import RestCommon from cinder.zonemanager import utils as fczm_utils -class HuaweiHVSISCSIDriver(driver.ISCSIDriver): - """ISCSI driver for Huawei OceanStor HVS storage arrays.""" +class Huawei18000ISCSIDriver(driver.ISCSIDriver): + """ISCSI driver for Huawei OceanStor 18000 storage arrays. - VERSION = '1.0.0' + 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(HuaweiHVSISCSIDriver, self).__init__(*args, **kwargs) + super(Huawei18000ISCSIDriver, self).__init__(*args, **kwargs) def do_setup(self, context): """Instantiate common class and log in storage system.""" - self.common = HVSCommon(configuration=self.configuration) + self.common = RestCommon(configuration=self.configuration) + return self.common.login() def check_for_setup_error(self): - """Check configuration file.""" - self.common._check_conf_file() - self.common.login() + """Check configuration file.""" + return self.common._check_conf_file() def create_volume(self, volume): """Create a volume.""" - self.common.create_volume(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.""" - self.common.create_volume_from_snapshot(volume, 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.""" - self.common.create_cloned_volume(volume, src_vref) + 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.""" - self.common.extend_volume(volume, new_size) + return self.common.extend_volume(volume, new_size) def delete_volume(self, volume): """Delete a volume.""" - self.common.delete_volume(volume) + return self.common.delete_volume(volume) def create_snapshot(self, snapshot): """Create a snapshot.""" - self.common.create_snapshot(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.""" - self.common.delete_snapshot(snapshot) + return self.common.delete_snapshot(snapshot) def get_volume_stats(self, refresh=False): """Get volume stats.""" - data = self.common.update_volume_stats(refresh) + 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' @@ -82,7 +94,7 @@ class HuaweiHVSISCSIDriver(driver.ISCSIDriver): def terminate_connection(self, volume, connector, **kwargs): """Terminate the map.""" - self.common.terminate_connection(volume, connector, **kwargs) + self.common.terminate_connection_iscsi(volume, connector) def create_export(self, context, volume): """Export the volume.""" @@ -97,54 +109,67 @@ class HuaweiHVSISCSIDriver(driver.ISCSIDriver): pass -class HuaweiHVSFCDriver(driver.FibreChannelDriver): - """FC driver for Huawei OceanStor HVS storage arrays.""" +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.0.0' + VERSION = "1.1.0" def __init__(self, *args, **kwargs): - super(HuaweiHVSFCDriver, self).__init__(*args, **kwargs) + super(Huawei18000FCDriver, self).__init__(*args, **kwargs) def do_setup(self, context): """Instantiate common class and log in storage system.""" - self.common = HVSCommon(configuration=self.configuration) - self.common.login() + self.common = RestCommon(configuration=self.configuration) + return self.common.login() def check_for_setup_error(self): - """Check configuration file.""" - self.common._check_conf_file() + """Check configuration file.""" + return self.common._check_conf_file() def create_volume(self, volume): """Create a volume.""" - self.common.create_volume(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.""" - self.common.create_volume_from_snapshot(volume, 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.""" - self.common.create_cloned_volume(volume, src_vref) + 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.""" - self.common.extend_volume(volume, new_size) + return self.common.extend_volume(volume, new_size) def delete_volume(self, volume): """Delete a volume.""" - self.common.delete_volume(volume) + return self.common.delete_volume(volume) def create_snapshot(self, snapshot): """Create a snapshot.""" - self.common.create_snapshot(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.""" - self.common.delete_snapshot(snapshot) + return self.common.delete_snapshot(snapshot) def get_volume_stats(self, refresh=False): """Get volume stats.""" - data = self.common.update_volume_stats(refresh) + 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' @@ -159,7 +184,7 @@ class HuaweiHVSFCDriver(driver.FibreChannelDriver): @fczm_utils.RemoveFCZone def terminate_connection(self, volume, connector, **kwargs): """Terminate the map.""" - self.common.terminate_connection(volume, connector, **kwargs) + return self.common.terminate_connection_fc(volume, connector) def create_export(self, context, volume): """Export the volume.""" diff --git a/cinder/volume/drivers/huawei/rest_common.py b/cinder/volume/drivers/huawei/rest_common.py index bb8cbfb7f..7069268a3 100644 --- a/cinder/volume/drivers/huawei/rest_common.py +++ b/cinder/volume/drivers/huawei/rest_common.py @@ -1,5 +1,4 @@ -# Copyright (c) 2013 Huawei Technologies Co., Ltd. -# Copyright (c) 2013 OpenStack Foundation +# Copyright (c) 2013 - 2014 Huawei Technologies Co., Ltd. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may @@ -13,7 +12,7 @@ # 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 HVS storage drivers.""" +"""Common class for Huawei 18000 storage drivers.""" import base64 import cookielib @@ -25,153 +24,122 @@ from xml.etree import ElementTree as ET from oslo.utils import excutils from oslo.utils import units +import six from cinder import context from cinder import exception -from cinder.i18n import _, _LE, _LW +from cinder.i18n import _, _LE, _LI, _LW from cinder.openstack.common import log as logging from cinder import utils -from cinder.volume.drivers.huawei import huawei_utils +from cinder.volume import qos_specs from cinder.volume import volume_types - LOG = logging.getLogger(__name__) -QOS_KEY = ["Qos-high", "Qos-normal", "Qos-low"] -TIER_KEY = ["Tier-high", "Tier-normal", "Tier-low"] +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(): + """Common class for Huawei OceanStor 18000 storage system.""" -class HVSCommon(): - """Common class for Huawei OceanStor HVS storage system.""" + SLEEP_FOR_LUN_READY = 2 + SLEEP_FOR_LUNCOPY = 15 def __init__(self, configuration): self.configuration = configuration self.cookie = cookielib.CookieJar() self.url = None - self.xml_conf = self.configuration.cinder_huawei_conf_file + self.productversion = None + self.headers = {"Connection": "keep-alive", + "Content-Type": "application/json"} def call(self, url=False, data=None, method=None): - """Send requests to HVS server. - + """Send requests to 18000 server. Send HTTPS call, get response in JSON. Convert response into Python Object and return it. """ - LOG.debug('HVS Request URL: %(url)s' % {'url': url}) - LOG.debug('HVS Request Data: %(data)s' % {'data': data}) - - headers = {"Connection": "keep-alive", - "Content-Type": "application/json"} opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(self.cookie)) urllib2.install_opener(opener) try: urllib2.socket.setdefaulttimeout(720) - req = urllib2.Request(url, data, headers) + req = urllib2.Request(url, data, self.headers) if method: req.get_method = lambda: method res = urllib2.urlopen(req).read().decode("utf-8") - LOG.debug('HVS Response Data: %(res)s' % {'res': res}) + + 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'), {'url': url, + 'method': method, + 'data': data}) + except Exception as err: - err_msg = _('Bad response from server: %s') % err - LOG.error(err_msg) + LOG.error(_LE('\nBad response from server: %s.') % err) raise err try: res_json = json.loads(res) except Exception as err: - LOG.error(_LE('JSON transfer error')) + err_msg = (_LE('JSON transfer error: %s.') % err) + LOG.error(err_msg) raise err return res_json def login(self): - """Log in HVS array. - - If login failed, the driver will sleep 30's to avoid frequent - connection to the server. - """ + """Log in 18000 array.""" login_info = self._get_login_info() - url = login_info['HVSURL'] + "xx/sessions" + 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): - time.sleep(30) - msg = _("Login error, reason is %s") % result + msg = (_("Login error, reason is: %s.") % result) LOG.error(msg) raise exception.CinderException(msg) deviceid = result['data']['deviceid'] - self.url = login_info['HVSURL'] + deviceid + self.url = login_info['RestURL'] + deviceid + self.headers['iBaseToken'] = result['data']['iBaseToken'] return deviceid - def _init_tier_parameters(self, parameters, lunparam): - """Init the LUN parameters through the volume type "performance".""" - if "tier" in parameters: - smart_tier = parameters['tier'] - if smart_tier == 'Tier_high': - lunparam['INITIALDISTRIBUTEPOLICY'] = "1" - elif smart_tier == 'Tier_normal': - lunparam['INITIALDISTRIBUTEPOLICY'] = "2" - elif smart_tier == 'Tier_low': - lunparam['INITIALDISTRIBUTEPOLICY'] = "3" - else: - lunparam['INITIALDISTRIBUTEPOLICY'] = "2" - def _init_lun_parameters(self, name, parameters): """Init basic LUN parameters.""" lunparam = {"TYPE": "11", "NAME": name, "PARENTTYPE": "216", "PARENTID": parameters['pool_id'], - "DESCRIPTION": "", + "DESCRIPTION": parameters['volume_description'], "ALLOCTYPE": parameters['LUNType'], "CAPACITY": parameters['volume_size'], "WRITEPOLICY": parameters['WriteType'], "MIRRORPOLICY": parameters['MirrorSwitch'], "PREFETCHPOLICY": parameters['PrefetchType'], - "PREFETCHVALUE": parameters['PrefetchValue'], - "DATATRANSFERPOLICY": "1", - "INITIALDISTRIBUTEPOLICY": "0"} + "PREFETCHVALUE": parameters['PrefetchValue']} return lunparam - def _init_qos_parameters(self, parameters, lun_param): - """Init the LUN parameters through the volume type "Qos-xxx".""" - policy_id = None - policy_info = None - if "qos" in parameters: - policy_info = self._find_qos_policy_info(parameters['qos']) - if policy_info: - policy_id = policy_info['ID'] - - lun_param['IOClASSID'] = policy_info['ID'] - qos_level = parameters['qos_level'] - if qos_level == 'Qos-high': - lun_param['IOPRIORITY'] = "3" - elif qos_level == 'Qos-normal': - lun_param['IOPRIORITY'] = "2" - elif qos_level == 'Qos-low': - lun_param['IOPRIORITY'] = "1" - else: - lun_param['IOPRIORITY'] = "2" - - return (policy_info, policy_id) - 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} + 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 + err_msg = (_('%s "data" was not in result.') % msg) LOG.error(err_msg) raise exception.CinderException(err_msg) @@ -184,40 +152,66 @@ class HVSCommon(): self._assert_rest_result(result, msg) self._assert_data_in_result(result, msg) - return result['data']['ID'] + 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']) - config_params = self._parse_volume_type(volume) - - # Prepare lun parameters, including qos parameter and tier parameter. - lun_param = self._init_lun_parameters(volume_name, config_params) - self._init_tier_parameters(config_params, lun_param) - policy_info, policy_id = self._init_qos_parameters(config_params, - lun_param) - - # Create LUN in array - lunid = self._create_volume(lun_param) - - # Enable qos, need to add lun into qos policy - if "qos" in config_params: - lun_list = policy_info['LUNLIST'] - lun_list.append(lunid) - if policy_id: - self._update_qos_policy_lunlist(lun_list, policy_id) - else: - LOG.warn(_LW("Can't find the Qos policy in array")) + 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) - # Create lun group and add LUN into to lun group - lungroup_id = self._create_lungroup(volume_name) - self._associate_lun_to_lungroup(lungroup_id, lunid) + self._delete_lun(lunid) - return lunid + return lun_info - def _get_volume_size(self, poolinfo, volume): + def _get_volume_size(self, volume): """Calculate the volume size. - We should divide the given volume size by 512 for the HVS system + We should divide the given volume size by 512 for the 18000 system calculates volume size with sectors, which is 512 bytes. """ @@ -227,45 +221,63 @@ class HVSCommon(): return volume_size + @utils.synchronized('huawei', external=True) def delete_volume(self, volume): """Delete a volume. - Three steps: first, remove associate from lun group. - Second, remove associate from qos policy. Third, remove the lun. + 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 = self._get_volume_by_name(name) - lungroup_id = self._find_lungroup(name) - - if lun_id and lungroup_id: - self._delete_lun_from_qos_policy(volume, lun_id) - self._delete_associated_lun_from_lungroup(lungroup_id, lun_id) - self._delete_lungroup(lungroup_id) - self._delete_lun(lun_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.warn(_LW("Can't find lun or lun group in array")) - - def _delete_lun_from_qos_policy(self, volume, lun_id): - """Remove lun from qos policy.""" - parameters = self._parse_volume_type(volume) - - if "qos" in parameters: - qos = parameters['qos'] - policy_info = self._find_qos_policy_info(qos) - if policy_info: - lun_list = policy_info['LUNLIST'] - for item in lun_list: - if lun_id == item: - lun_list.remove(item) - self._update_qos_policy_lunlist(lun_list, policy_info['ID']) + 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') + 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 err + return root def _encode_name(self, name): uuid_str = name.replace("-", "") @@ -275,16 +287,16 @@ class HVSCommon(): return newuuid def _find_pool_info(self): - root = huawei_utils.parse_xml_file(self.xml_conf) + root = self._read_xml() pool_name = root.findtext('LUN/StoragePool') if not pool_name: - err_msg = _("Invalid resource pool: %s") % 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') + self._assert_rest_result(result, 'Query resource pool error.') poolinfo = {} if "data" in result: @@ -296,14 +308,14 @@ class HVSCommon(): break if not poolinfo: - msg = (_('Get pool info error, pool name is:%s') % pool_name) + 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" + url = self.url + "/lun?range=[0-65535]" result = self.call(url, None, "GET") self._assert_rest_result(result, 'Get volume by name error!') @@ -323,18 +335,27 @@ class HVSCommon(): 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.debug('create_snapshot:snapshot name:%(snapshot)s, ' - 'volume name:%(volume)s.' - % {'snapshot': snapshot_name, - 'volume': volume_name}) + 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) @@ -342,41 +363,65 @@ class HVSCommon(): self._assert_rest_result(result, msg) self._assert_data_in_result(result, msg) - return result['data']['ID'] + return result['data'] + @utils.synchronized('huawei', external=True) def create_snapshot(self, snapshot): - snapshot_id = self._create_snapshot(snapshot) + snapshot_info = self._create_snapshot(snapshot) + snapshot_id = snapshot_info['ID'] self._active_snapshot(snapshot_id) - def _stop_snapshot(self, snapshot): - snapshot_name = self._encode_name(snapshot['id']) - volume_name = self._encode_name(snapshot['volume_id']) + return snapshot_info - LOG.debug('_stop_snapshot:snapshot name:%(snapshot)s, ' - 'volume name:%(volume)s.' - % {'snapshot': snapshot_name, - 'volume': volume_name}) + 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 - snapshotid = self._get_snapshotid_by_name(snapshot_name) - stopdata = json.dumps({"ID": snapshotid}) + 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.') - return snapshotid - 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): - snapshotid = self._stop_snapshot(snapshot) - self._delete_snapshot(snapshotid) + 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" + 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.') @@ -387,14 +432,17 @@ class HVSCommon(): 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) try: self._start_luncopy(luncopy_id) self._wait_for_luncopy(luncopy_id) + except Exception: with excutils.save_and_reraise_exception(): self._delete_luncopy(luncopy_id) @@ -402,6 +450,17 @@ class HVSCommon(): self._delete_luncopy(luncopy_id) + def _is_vol_ready(self, lunid): + url = self.url + "/lun/" + lunid + result = self.call(url, None, "GET") + self._assert_rest_result(result, 'Get volume by id error!') + + if "data" in result: + if (result['data']['HEALTHSTATUS'] == "1") and\ + (result['data']['RUNNINGSTATUS'] == "27"): + return True + return False + def create_volume_from_snapshot(self, volume, snapshot): """Create a volume from a snapshot. @@ -410,28 +469,78 @@ class HVSCommon(): """ snapshot_name = self._encode_name(snapshot['id']) - src_lun_id = self._get_snapshotid_by_name(snapshot_name) - tgt_lun_id = self.create_volume(volume) + + 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']) - self._copy_volume(volume, luncopy_name, src_lun_id, tgt_lun_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}) + + timeout = 10 + time_pass = 0 + while True: + if self._is_vol_ready(tgt_lun_id): + break + LOG.info(_LI('Waiting newly created lun to be ready.')) + time.sleep(self.SLEEP_FOR_LUN_READY) + time_pass = time_pass + self.SLEEP_FOR_LUN_READY + if time_pass >= timeout: + msg = _("Waited %s seconds. Timeout when waiting the newly" + " created lun to be ready.") + raise exception.VolumeBackendAPIException(msg % time_pass) + + 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.""" - volume_name = self._encode_name(src_vref['id']) - src_lun_id = self._get_volume_by_name(volume_name) - tgt_lun_id = self.create_volume(volume) - luncopy_name = self._encode_name(volume['id']) - self._copy_volume(volume, luncopy_name, src_lun_id, tgt_lun_id) + # 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", + data = json.dumps({"TYPE": 219, "NAME": luncopyname, "DESCRIPTION": luncopyname, - "COPYSPEED": "2", + "COPYSPEED": 2, "LUNCOPYTYPE": "1", "SOURCELUN": ("INVALID;%s;INVALID;INVALID;INVALID" % srclunid), @@ -445,41 +554,55 @@ class HVSCommon(): return result['data']['ID'] - def _add_host_into_hostgroup(self, host_name, host_ip): + def _add_host_into_hostgroup(self, host_id): """Associate host to hostgroup. - If host group doesn't exist, create one. + 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}) - hostgroup_id = self._find_hostgroup(host_name) if hostgroup_id is None: - hostgroup_id = self._create_hostgroup(host_name) + hostgroup_id = self._create_hostgroup(host_group_name) - hostid = self._find_host(host_name) - if hostid is None: - os_type = huawei_utils.get_conf_host_os_type(host_ip, - self.xml_conf) - hostid = self._add_host(host_name, os_type) - self._associate_host_to_hostgroup(hostgroup_id, hostid) + isAssociate = self._is_host_associate_to_hostgroup(hostgroup_id, + host_id) + if isAssociate is False: + self._associate_host_to_hostgroup(hostgroup_id, host_id) - return hostid, hostgroup_id + return hostgroup_id def _mapping_hostgroup_and_lungroup(self, volume_name, hostgroup_id, host_id): """Add hostgroup and lungroup to view.""" - lungroup_id = self._find_lungroup(volume_name) + 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(volume_name) + view_id = self._find_mapping_view(mapping_view_name) - LOG.debug('_mapping_hostgroup_and_lungroup: lun_group: %(lun_group)s' - 'view_id: %(view_id)s' - % {'lun_group': lungroup_id, - 'view_id': view_id}) + LOG.info(_LI( + '_mapping_hostgroup_and_lungroup, lun_group: %(lun_group)s, ' + 'view_id: %(view_id)s, lun_id: %(lun_id)s.') + % {'lun_group': six.text_type(lungroup_id), + 'view_id': six.text_type(view_id), + 'lun_id': six.text_type(lun_id)}) try: + # Create lungroup and add LUN into to lungroup. + if lungroup_id is None: + lungroup_id = self._create_lungroup(lungroup_name) + self._associate_lun_to_lungroup(lungroup_id, lun_id) + if view_id is None: - view_id = self._add_mapping_view(volume_name, host_id) + 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: @@ -490,9 +613,11 @@ class HVSCommon(): except Exception: with excutils.save_and_reraise_exception(): - self._delete_hostgoup_mapping_view(view_id, hostgroup_id) - self._delete_lungroup_mapping_view(view_id, lungroup_id) - self._delete_mapping_view(view_id) + err_msg = (_LE( + 'Error occured 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 @@ -500,58 +625,87 @@ class HVSCommon(): 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.debug('initiator name:%(initiator_name)s, ' - 'volume name:%(volume)s.' - % {'initiator_name': initiator_name, - 'volume': volume_name}) + 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) - #create host_group if not exist - hostid, hostgroup_id = self._add_host_into_hostgroup(connector['host'], - connector['ip']) + # 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 + # 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.debug("host lun id is %s" % hostlunid) + + 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_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.debug('initiator name:%(initiator_name)s, ' - 'volume name:%(volume)s.' - % {'initiator_name': wwns, - 'volume': volume_name}) + 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, hostgroup_id = self._add_host_into_hostgroup(connector['host'], - connector['ip']) + # 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.debug("the free wwns %s" % 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) @@ -566,16 +720,22 @@ class HVSCommon(): if tgtwwpns: tgt_port_wwns.append(tgtwwpns) + init_targ_map = {} + for initiator in wwns: + init_targ_map[initiator] = tgt_port_wwns + # Return FC properties. - properties = {} - properties['target_discovered'] = False - properties['target_wwn'] = tgt_port_wwns - properties['target_lun'] = int(host_lun_id) - properties['volume_id'] = volume['id'] - LOG.debug("the fc server properties is:%s" % 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}} - return {'driver_volume_type': 'fibre_channel', - 'data': properties} + LOG.info(_LI("initialize_connection_fc, return data is: %s.") + % info) + + return info def _get_iscsi_tgt_port(self): url = self.url + "/iscsidevicename" @@ -589,9 +749,9 @@ class HVSCommon(): def _find_hostgroup(self, groupname): """Get the given hostgroup id.""" - url = self.url + "/hostgroup" + url = self.url + "/hostgroup?range=[0-8191]" result = self.call(url, None, "GET") - self._assert_rest_result(result, 'Get host group information error.') + self._assert_rest_result(result, 'Get hostgroup information error.') host_group_id = None if "data" in result: @@ -603,9 +763,9 @@ class HVSCommon(): def _find_lungroup(self, lungroupname): """Get the given hostgroup id.""" - url = self.url + "/lungroup" + url = self.url + "/lungroup?range=[0-8191]" result = self.call(url, None, "GET") - self._assert_rest_result(result, 'Get lun group information error.') + self._assert_rest_result(result, 'Get lungroup information error.') lun_group_id = None if 'data' in result: @@ -620,7 +780,7 @@ class HVSCommon(): data = json.dumps({"TYPE": "14", "NAME": hostgroupname}) result = self.call(url, data) - msg = 'Create host group error.' + msg = 'Create hostgroup error.' self._assert_rest_result(result, msg) self._assert_data_in_result(result, msg) @@ -629,10 +789,12 @@ class HVSCommon(): 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 lun group error.' + msg = 'Create lungroup error.' self._assert_rest_result(result, msg) self._assert_data_in_result(result, msg) @@ -641,14 +803,14 @@ class HVSCommon(): def _delete_lungroup(self, lungroupid): url = self.url + "/LUNGroup/" + lungroupid result = self.call(url, None, "DELETE") - self._assert_rest_result(result, 'Delete lun group error.') + 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 lun group associated error.') + self._assert_rest_result(result, 'Check lungroup associated error.') if "data" in result: for item in result['data']: @@ -661,7 +823,7 @@ class HVSCommon(): "ASSOCIATEOBJTYPE=14&ASSOCIATEOBJID=%s" % hostgroupid) url = self.url + url_subfix result = self.call(url, None, "GET") - self._assert_rest_result(result, 'Check host group associated error.') + self._assert_rest_result(result, 'Check hostgroup associated error.') if "data" in result: for item in result['data']: @@ -670,7 +832,7 @@ class HVSCommon(): return False def _find_host_lun_id(self, hostid, lunid): - time.sleep(2) + url = self.url + ("/lun/associate?TYPE=11&ASSOCIATEOBJTYPE=21" "&ASSOCIATEOBJID=%s" % (hostid)) result = self.call(url, None, "GET") @@ -686,17 +848,17 @@ class HVSCommon(): host_lun_id = hostassoinfo['HostLUNID'] break except Exception as err: - msg = _("JSON transfer data error. %s") % err + msg = (_LE("JSON transfer data error. %s") % err) LOG.error(msg) raise err return host_lun_id def _find_host(self, hostname): """Get the given host ID.""" - url = self.url + "/host" + 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 host group error.') + self._assert_rest_result(result, 'Find host in hostgroup error.') host_id = None if "data" in result: @@ -706,12 +868,12 @@ class HVSCommon(): break return host_id - def _add_host(self, hostname, type): + def _add_host(self, hostname): """Add a new host.""" url = self.url + "/host" data = json.dumps({"TYPE": "21", "NAME": hostname, - "OPERATIONSYSTEM": type}) + "OPERATIONSYSTEM": "0"}) result = self.call(url, data) self._assert_rest_result(result, 'Add new host error.') @@ -720,26 +882,43 @@ class HVSCommon(): else: return None - def _associate_host_to_hostgroup(self, hostgroupid, hostid): - url = self.url + "/host/associate" - data = json.dumps({"ID": hostgroupid, + def _is_host_associate_to_hostgroup(self, hostgroup_id, host_id): + """Check whether the host is associate 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 _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": hostid}) + "ASSOCIATEOBJID": host_id}) result = self.call(url, data) - self._assert_rest_result(result, 'Associate host to host group error.') + self._assert_rest_result(result, 'Associate host to hostgroup error.') def _associate_lun_to_lungroup(self, lungroupid, lunid): - """Associate lun to lun group.""" + """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 lun group error.') + self._assert_rest_result(result, 'Associate lun to lungroup error.') - def _delete_associated_lun_from_lungroup(self, lungroupid, lunid): - """Remove lun from lun group.""" + def _remove_lun_from_lungroup(self, lungroupid, lunid): + """Remove lun from lungroup.""" url = self.url + ("/lungroup/associate?ID=%s" "&ASSOCIATEOBJTYPE=11&ASSOCIATEOBJID=%s" @@ -747,10 +926,10 @@ class HVSCommon(): result = self.call(url, None, 'DELETE') self._assert_rest_result(result, - 'Delete associated lun from lun group error') + 'Delete associated lun from lungroup error.') def _initiator_is_added_to_array(self, ininame): - """Check whether the initiator is already added in array.""" + """Check whether the initiator is already added on the array.""" url = self.url + "/iscsi_initiator" data = json.dumps({"TYPE": "222", "ID": ininame}) result = self.call(url, data, "GET") @@ -782,7 +961,7 @@ class HVSCommon(): url = self.url + "/iscsi_initiator/" data = json.dumps({"TYPE": "222", "ID": ininame, - "USECHAP": "False"}) + "USECHAP": "false"}) result = self.call(url, data) self._assert_rest_result(result, 'Add initiator to array error.') @@ -791,7 +970,7 @@ class HVSCommon(): url = self.url + "/iscsi_initiator/" + ininame data = json.dumps({"TYPE": "222", "ID": ininame, - "USECHAP": "False", + "USECHAP": "false", "PARENTTYPE": "21", "PARENTID": hostid}) result = self.call(url, data, "PUT") @@ -799,22 +978,22 @@ class HVSCommon(): def _find_mapping_view(self, name): """Find mapping view.""" - url = self.url + "/mappingview" + 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) - self._assert_data_in_result(result, msg) - viewid = None - for item in result['data']: - if name == item['NAME']: - viewid = item['ID'] - break + 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, host_id): + def _add_mapping_view(self, name): url = self.url + "/mappingview" data = json.dumps({"NAME": name, "TYPE": "245"}) result = self.call(url, data) @@ -838,53 +1017,102 @@ class HVSCommon(): "TYPE": "245", "ID": viewID}) result = self.call(url, data, "PUT") - self._assert_rest_result(result, 'Associate lun group to view error.') + self._assert_rest_result(result, 'Associate lungroup to view error.') def _delete_lungroup_mapping_view(self, view_id, lungroup_id): - """remove lun group associate from the mapping view.""" + """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 lun group from view error.') + self._assert_rest_result(result, 'Delete lungroup from view error.') def _delete_hostgoup_mapping_view(self, view_id, hostgroup_id): - """remove host group associate from the mapping view.""" + """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 host group from view error.') + self._assert_rest_result(result, 'Delete hostgroup from view error.') def _delete_mapping_view(self, view_id): - """remove mapping view from the storage.""" + """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 terminate_connection(self, volume, connector, **kwargs): + 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']) - host_name = connector['host'] + 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) - LOG.debug('terminate_connection:volume name: %(volume)s, ' - 'initiator name: %(ini)s.' - % {'volume': volume_name, - 'ini': initiator_name}) + 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 - view_id = self._find_mapping_view(volume_name) - hostgroup_id = self._find_hostgroup(host_name) - lungroup_id = self._find_lungroup(volume_name) + else: + LOG.warning(_LW("Can't find lun on the array.")) - if view_id is not None: - self._delete_hostgoup_mapping_view(view_id, hostgroup_id) - self._delete_lungroup_mapping_view(view_id, lungroup_id) - self._delete_mapping_view(view_id) + 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.""" @@ -915,7 +1143,7 @@ class HVSCommon(): def _get_lun_conf_params(self): """Get parameters from config file for creating lun.""" - # Default lun set information + # Default lun set information. lunsetinfo = {'LUNType': 'Thick', 'StripUnitSize': '64', 'WriteType': '1', @@ -924,7 +1152,7 @@ class HVSCommon(): 'PrefetchValue': '0', 'PrefetchTimes': '0'} - root = huawei_utils.parse_xml_file(self.xml_conf) + root = self._read_xml() luntype = root.findtext('LUN/LUNType') if luntype: if luntype.strip() in ['Thick', 'Thin']: @@ -935,9 +1163,10 @@ class HVSCommon(): 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}) + 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) @@ -958,18 +1187,22 @@ class HVSCommon(): lunsetinfo['PrefetchType'] = fetchtype.strip() typevalue = prefetch.attrib['Value'].strip() if lunsetinfo['PrefetchType'] == '1': - lunsetinfo['PrefetchValue'] = typevalue + 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 in 1,2,3,4. fetchtype is:%(fetchtype)s') - % {'fetchtype': fetchtype}) + err_msg = (_( + 'PrefetchType config is wrong. PrefetchType' + ' must be in 1,2,3,4. fetchtype is: %(fetchtype)s.') + % {'fetchtype': fetchtype}) LOG.error(err_msg) raise exception.CinderException(err_msg) else: - LOG.debug('Use default prefetch fetchtype. ' - 'Prefetch fetchtype:Intelligent.') + LOG.info(_LI( + 'Use default prefetch fetchtype. ' + 'Prefetch fetchtype:Intelligent.')) return lunsetinfo @@ -980,12 +1213,14 @@ class HVSCommon(): if luncopy_info['status'] == '40': break elif luncopy_info['state'] != '1': - err_msg = (_('_wait_for_luncopy:LUNcopy status is not normal.' - 'LUNcopy name: %(luncopyname)s') - % {'luncopyname': luncopyid}) + err_msg = (_( + '_wait_for_luncopy: LUNcopy status is not normal.' + 'LUNcopy name: %(luncopyname)s.') + % {'luncopyname': luncopyid}) LOG.error(err_msg) raise exception.VolumeBackendAPIException(data=err_msg) - time.sleep(10) + LOG.info(_LI('Waiting for luncopy to be complete.')) + time.sleep(self.SLEEP_FOR_LUNCOPY) def _get_luncopy_info(self, luncopyid): """Get LUNcopy information.""" @@ -1021,14 +1256,15 @@ class HVSCommon(): msg = 'Get connected free FC wwn error.' self._assert_rest_result(result, msg) - self._assert_data_in_result(result, msg) wwns = [] - for item in result['data']: - wwns.append(item['ID']) + if 'data' in result: + for item in result['data']: + wwns.append(item['ID']) + return wwns - def _add_fc_port_to_host(self, hostid, wwn, multipathtype=0): + 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", @@ -1053,25 +1289,18 @@ class HVSCommon(): iscsi_port_info = item['LOCATION'] break - if not iscsi_port_info: - msg = (_('_get_iscsi_port_info: Failed to get iscsi port info ' - 'through config IP %(ip)s, please check config file.') - % {'ip': ip}) - LOG.error(msg) - raise exception.InvalidInput(reason=msg) - return iscsi_port_info def _get_iscsi_conf(self): """Get iSCSI info from config file.""" iscsiinfo = {} - root = huawei_utils.parse_xml_file(self.xml_conf) - iscsiinfo['DefaultTargetIP'] = \ - root.findtext('iSCSI/DefaultTargetIP').strip() + root = self._read_xml() + TargetIP = root.findtext('iSCSI/DefaultTargetIP').strip() + iscsiinfo['DefaultTargetIP'] = TargetIP initiator_list = [] tmp_dic = {} for dic in root.findall('iSCSI/Initiator'): - # Strip values of dic + # Strip values of dic. for k, v in dic.items(): tmp_dic[k] = v.strip() initiator_list.append(tmp_dic) @@ -1081,26 +1310,32 @@ class HVSCommon(): def _get_tgt_iqn(self, iscsiip): """Get target iSCSI iqn.""" - LOG.debug('_get_tgt_iqn: iSCSI IP is %s.' % iscsiip) + 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] - 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.debug('_get_tgt_iqn: iSCSI target iqn is %s' % iqn) - return iqn + 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 + @@ -1109,62 +1344,17 @@ class HVSCommon(): msg = 'Get FC target wwpn error.' self._assert_rest_result(result, msg) - self._assert_data_in_result(result, msg) fc_wwpns = None - for item in result['data']: - if wwn == item['INITIATOR_PORT_WWN']: - fc_wwpns = item['TARGET_PORT_WWN'] - break + 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 _parse_volume_type(self, volume): - type_id = volume['volume_type_id'] - params = self._get_lun_conf_params() - LOG.debug('_parse_volume_type: type id: %(type_id)s ' - 'config parameter is: %(params)s' - % {'type_id': type_id, - 'params': params}) - - poolinfo = self._find_pool_info() - volume_size = self._get_volume_size(poolinfo, volume) - params['volume_size'] = volume_size - params['pool_id'] = poolinfo['ID'] - - if type_id is not None: - ctxt = context.get_admin_context() - volume_type = volume_types.get_volume_type(ctxt, type_id) - specs = volume_type.get('extra_specs') - for key, value in specs.iteritems(): - key_split = key.split(':') - if len(key_split) > 1: - if key_split[0] == 'drivers': - key = key_split[1] - else: - continue - else: - key = key_split[0] - - if key in QOS_KEY: - params["qos"] = value.strip() - params["qos_level"] = key - elif key in TIER_KEY: - params["tier"] = value.strip() - elif key in params.keys(): - params[key] = value.strip() - else: - conf = self.configuration.cinder_huawei_conf_file - LOG.warn(_LW('_parse_volume_type: Unacceptable parameter ' - '%(key)s. Please check this key in ' - 'extra_specs and make it consistent with the ' - 'configuration file ' - '%(conf)s.') % {'key': key, 'conf': conf}) - - LOG.debug("The config parameters are: %s" % params) - return params - - def update_volume_stats(self, refresh=False): + def update_volume_stats(self): capacity = self._get_capacity() data = {} data['vendor_name'] = 'Huawei' @@ -1179,17 +1369,19 @@ class HVSCommon(): url = self.url + "/ioclass" result = self.call(url, None, "GET") - msg = 'Get qos policy error.' + msg = 'Get QoS policy error.' self._assert_rest_result(result, msg) - self._assert_data_in_result(result, msg) qos_info = {} - 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 - break + 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): @@ -1198,7 +1390,7 @@ class HVSCommon(): "ID": policy_id, "LUNLIST": lunlist}) result = self.call(url, data, "PUT") - self._assert_rest_result(result, 'Up date qos policy error.') + self._assert_rest_result(result, 'Update QoS policy error.') def _get_login_info(self): """Get login IP, username and password from config file.""" @@ -1206,7 +1398,7 @@ class HVSCommon(): filename = self.configuration.cinder_huawei_conf_file tree = ET.parse(filename) root = tree.getroot() - logininfo['HVSURL'] = root.findtext('Storage/HVSURL').strip() + logininfo['RestURL'] = root.findtext('Storage/RestURL').strip() need_encode = False for key in ['UserName', 'UserPassword']: @@ -1224,43 +1416,34 @@ class HVSCommon(): try: tree.write(filename, 'UTF-8') except Exception as err: - LOG.warn(_LW('%s') % err) + LOG.warning(_LW('Unable to access config file. %s') % err) return logininfo def _change_file_mode(self, filepath): - utils.execute('chmod', '777', filepath, run_as_root=True) + 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 = huawei_utils.parse_xml_file(self.xml_conf) - check_list = ['Storage/HVSURL', 'Storage/UserName', - 'Storage/UserPassword'] - for item in check_list: - if not huawei_utils.is_xml_item_exist(root, item): - err_msg = (_('_check_conf_file: Config file invalid. ' - '%s must be set.') % item) - LOG.error(err_msg) - raise exception.InvalidInput(reason=err_msg) - - # make sure storage pool is set - if not huawei_utils.is_xml_item_exist(root, 'LUN/StoragePool'): - err_msg = _('_check_conf_file: Config file invalid. ' - 'StoragePool must be 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) - # make sure host os type valid - if huawei_utils.is_xml_item_exist(root, 'Host', 'OSType'): - os_list = huawei_utils.os_type.keys() - if not huawei_utils.is_xml_item_valid(root, 'Host', os_list, - 'OSType'): - err_msg = (_('_check_conf_file: Config file invalid. ' - 'Host OSType invalid.\n' - 'The valid values are: %(os_list)s') - % {'os_list': os_list}) - 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.""" @@ -1277,26 +1460,220 @@ class HVSCommon(): 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}) + 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) - target_iqn = self._get_tgt_iqn(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) + 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): - name = self._encode_name(volume['id']) - lun_id = self._get_volume_by_name(name) - if lun_id: - url = self.url + "/lun/expand" - capacity = int(new_size) * units.Gi / 512 - data = json.dumps({"TYPE": "11", - "ID": lun_id, - "CAPACITY": capacity}) - result = self.call(url, data, "PUT") - self._assert_rest_result(result, 'Extend lun error.') + """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.iteritems(): + 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: - LOG.warn(_LW('Can not find lun in array')) + 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 + baseData = {"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 = dict(baseData.items() + qos.items()) + url = self.url + "/ioclass/" + data = json.dumps(mergedata) + + 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.iteritems(): + 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 ab2e68c7c..a6a6f9a9e 100644 --- a/cinder/volume/manager.py +++ b/cinder/volume/manager.py @@ -97,6 +97,12 @@ volume_manager_opts = [ 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', } + def locked_volume_operation(f): """Lock decorator for volume operations. @@ -163,6 +169,10 @@ class VolumeManager(manager.SchedulerDependentManager): # Get from configuration, which will get the default # if its not using the multi backend volume_driver = self.configuration.volume_driver + if volume_driver in MAPPING: + LOG.warning(_LW("Driver path %s is deprecated, update your " + "configuration to the new path."), volume_driver) + volume_driver = MAPPING[volume_driver] vol_db_empty = self._set_voldb_empty_at_startup_indicator( context.get_admin_context()) -- 2.45.2