]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
Add volume driver for Huawei HVS storage system
authorzhangchao010 <zhangchao010@huawei.com>
Tue, 3 Sep 2013 11:17:26 +0000 (19:17 +0800)
committerzhangchao010 <zhangchao010@huawei.com>
Tue, 3 Sep 2013 11:19:38 +0000 (19:19 +0800)
Huawei OceanStor HVS-series enterprise storage system is an optimum
storage platform for next-generation data centers that feature
virtualization, hybrid cloud, simplified IT, and low carbon footprints.

This patch add an iSCSI driver and a FC driver for Huawei HVS storage
system, using REST. We define a common module for both iSCSI driver and
FC driver. The drivers support volume type, QoS.

Implements: blueprint huawei-hvs-volume-driver
Change-Id: Ibdfed7df6d347e00f498694898c88dfa641559eb

cinder/tests/test_huawei_hvs.py [new file with mode: 0644]
cinder/volume/drivers/huawei/__init__.py
cinder/volume/drivers/huawei/huawei_hvs.py [new file with mode: 0644]
cinder/volume/drivers/huawei/rest_common.py [new file with mode: 0644]

diff --git a/cinder/tests/test_huawei_hvs.py b/cinder/tests/test_huawei_hvs.py
new file mode 100644 (file)
index 0000000..7b1a45c
--- /dev/null
@@ -0,0 +1,846 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright (c) 2013 Huawei Technologies Co., Ltd.
+# Copyright (c) 2013 OpenStack LLC.
+# 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 mox
+import os
+import shutil
+import tempfile
+import time
+
+from xml.dom.minidom import Document
+
+from cinder import exception
+from cinder import test
+from cinder import utils
+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'}
+
+
+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):
+        utils.execute('chmod', '777', filepath)
+
+    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":
+                    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 == "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}}"""
+
+        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.fake_conf_file = self.tmp_dir + '/cinder_huawei_conf.xml'
+        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 = FakeHVSiSCSIStorage(configuration=self.configuration)
+        self.driver.do_setup({})
+        self.driver.common.test_normal = True
+
+    def tearDown(self):
+        if os.path.exists(self.fake_conf_file):
+            os.remove(self.fake_conf_file)
+        shutil.rmtree(self.tmp_dir)
+        super(HVSRESTiSCSIDriverTestCase, self).tearDown()
+
+    def test_log_in_success(self):
+        deviceid = self.driver.common.login()
+        self.assertNotEqual(deviceid, None)
+
+    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_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.assertEqual(self.driver.common.lun_id, None)
+
+    def test_delete_snapshot_success(self):
+        self.driver.common.login()
+        self.driver.delete_snapshot(test_snap)
+        self.assertEqual(self.driver.common.snapshot_id, None)
+
+    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.assertNotEqual(status['free_capacity_gb'], None)
+
+    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)
+
+        protocal = doc.createElement('Protocol')
+        protocal_text = doc.createTextNode('iSCSI')
+        protocal.appendChild(protocal_text)
+        storage.appendChild(protocal)
+
+        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.fake_conf_file = self.tmp_dir + '/cinder_huawei_conf.xml'
+        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 tearDown(self):
+        if os.path.exists(self.fake_conf_file):
+            os.remove(self.fake_conf_file)
+        shutil.rmtree(self.tmp_dir)
+        super(HVSRESTFCDriverTestCase, self).tearDown()
+
+    def test_log_in_Success(self):
+        deviceid = self.driver.common.login()
+        self.assertNotEqual(deviceid, None)
+
+    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_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.assertEqual(self.driver.common.lun_id, None)
+
+    def test_delete_snapshot_success(self):
+        self.driver.common.login()
+        self.driver.delete_snapshot(test_snap)
+        self.assertEqual(self.driver.common.snapshot_id, None)
+
+    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.assertNotEqual(status['free_capacity_gb'], None)
+
+    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)
+
+        protocal = doc.createElement('Protocol')
+        protocal_text = doc.createTextNode('FC')
+        protocal.appendChild(protocal_text)
+        storage.appendChild(protocal)
+
+        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()
index f1f5777bf8e9b9fd23cb14ff0f6a0caaedd67655..37948aa478ab96bfd5af3e0d784647f0d6d63110 100644 (file)
@@ -28,6 +28,7 @@ from cinder.openstack.common import log as logging
 from cinder.volume.configuration import Configuration
 from cinder.volume import driver
 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 ssh_common
 
@@ -47,7 +48,8 @@ class HuaweiVolumeDriver(object):
 
     def __init__(self, *args, **kwargs):
         super(HuaweiVolumeDriver, self).__init__()
-        self._product = {'T': huawei_t, 'Dorado': huawei_dorado}
+        self._product = {'T': huawei_t, 'Dorado': huawei_dorado,
+                         'HVS': huawei_hvs}
         self._protocol = {'iSCSI': 'ISCSIDriver', 'FC': 'FCDriver'}
 
         self.driver = self._instantiate_driver(*args, **kwargs)
@@ -84,8 +86,8 @@ class HuaweiVolumeDriver(object):
             return (product, protocol)
         else:
             msg = (_('"Product" or "Protocol" is illegal. "Product" should '
-                     'be set to either T or Dorado. "Protocol" should be set '
-                     'to either iSCSI or FC. Product: %(product)s '
+                     'be set to either T, Dorado or HVS. "Protocol" should '
+                     'be set to either iSCSI or FC. Product: %(product)s '
                      'Protocol: %(protocol)s')
                    % {'product': str(product),
                       'protocol': str(protocol)})
diff --git a/cinder/volume/drivers/huawei/huawei_hvs.py b/cinder/volume/drivers/huawei/huawei_hvs.py
new file mode 100644 (file)
index 0000000..8682aca
--- /dev/null
@@ -0,0 +1,165 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright (c) 2013 Huawei Technologies Co., Ltd.
+# Copyright (c) 2013 OpenStack LLC.
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+"""
+Volume Drivers for Huawei OceanStor HVS storage arrays.
+"""
+
+from cinder.volume import driver
+from cinder.volume.drivers.huawei.rest_common import HVSCommon
+
+
+class HuaweiHVSISCSIDriver(driver.ISCSIDriver):
+    """ISCSI driver for Huawei OceanStor HVS storage arrays."""
+
+    VERSION = '1.0.0'
+
+    def __init__(self, *args, **kwargs):
+        super(HuaweiHVSISCSIDriver, 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()
+
+    def check_for_setup_error(self):
+        """Check configuration  file."""
+        self.common._check_conf_file()
+
+    def create_volume(self, volume):
+        """Create a volume."""
+        self.common.create_volume(volume)
+
+    def create_volume_from_snapshot(self, volume, snapshot):
+        """Create a volume from a snapshot."""
+        self.common.create_volume_from_snapshot(volume, snapshot)
+
+    def create_cloned_volume(self, volume, src_vref):
+        """Create a clone of the specified volume."""
+        self.common.create_cloned_volume(volume, src_vref)
+
+    def delete_volume(self, volume):
+        """Delete a volume."""
+        self.common.delete_volume(volume)
+
+    def create_snapshot(self, snapshot):
+        """Create a snapshot."""
+        self.common.create_snapshot(snapshot)
+
+    def delete_snapshot(self, snapshot):
+        """Delete a snapshot."""
+        self.common.delete_snapshot(snapshot)
+
+    def get_volume_stats(self, refresh=False):
+        """Get volume stats."""
+        data = self.common.update_volume_stats(refresh)
+        backend_name = self.configuration.safe_get('volume_backend_name')
+        data['volume_backend_name'] = backend_name or self.__class__.__name__
+        data['storage_protocol'] = 'iSCSI'
+        data['driver_version'] = self.VERSION
+        return data
+
+    def initialize_connection(self, volume, connector):
+        """Map a volume to a host."""
+        return self.common.initialize_connection_iscsi(volume, connector)
+
+    def terminate_connection(self, volume, connector, **kwargs):
+        """Terminate the map."""
+        self.common.terminate_connection(volume, connector, **kwargs)
+
+    def create_export(self, context, volume):
+        """Export the volume."""
+        pass
+
+    def ensure_export(self, context, volume):
+        """Synchronously recreate an export for a volume."""
+        pass
+
+    def remove_export(self, context, volume):
+        """Remove an export for a volume."""
+        pass
+
+
+class HuaweiHVSFCDriver(driver.FibreChannelDriver):
+    """FC driver for Huawei OceanStor HVS storage arrays."""
+
+    VERSION = '1.0.0'
+
+    def __init__(self, *args, **kwargs):
+        super(HuaweiHVSFCDriver, 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()
+
+    def check_for_setup_error(self):
+        """Check configuration  file."""
+        self.common._check_conf_file()
+
+    def create_volume(self, volume):
+        """Create a volume."""
+        self.common.create_volume(volume)
+
+    def create_volume_from_snapshot(self, volume, snapshot):
+        """Create a volume from a snapshot."""
+        self.common.create_volume_from_snapshot(volume, snapshot)
+
+    def create_cloned_volume(self, volume, src_vref):
+        """Create a clone of the specified volume."""
+        self.common.create_cloned_volume(volume, src_vref)
+
+    def delete_volume(self, volume):
+        """Delete a volume."""
+        self.common.delete_volume(volume)
+
+    def create_snapshot(self, snapshot):
+        """Create a snapshot."""
+        self.common.create_snapshot(snapshot)
+
+    def delete_snapshot(self, snapshot):
+        """Delete a snapshot."""
+        self.common.delete_snapshot(snapshot)
+
+    def get_volume_stats(self, refresh=False):
+        """Get volume stats."""
+        data = self.common.update_volume_stats(refresh)
+        backend_name = self.configuration.safe_get('volume_backend_name')
+        data['volume_backend_name'] = backend_name or self.__class__.__name__
+        data['storage_protocol'] = 'FC'
+        data['driver_version'] = self.VERSION
+        return data
+
+    def initialize_connection(self, volume, connector):
+        """Map a volume to a host."""
+        return self.common.initialize_connection_fc(volume, connector)
+
+    def terminate_connection(self, volume, connector, **kwargs):
+        """Terminate the map."""
+        self.common.terminate_connection(volume, connector, **kwargs)
+
+    def create_export(self, context, volume):
+        """Export the volume."""
+        pass
+
+    def ensure_export(self, context, volume):
+        """Synchronously recreate an export for a volume."""
+        pass
+
+    def remove_export(self, context, volume):
+        """Remove an export for a volume."""
+        pass
diff --git a/cinder/volume/drivers/huawei/rest_common.py b/cinder/volume/drivers/huawei/rest_common.py
new file mode 100644 (file)
index 0000000..8d46d48
--- /dev/null
@@ -0,0 +1,1273 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright (c) 2013 Huawei Technologies Co., Ltd.
+# Copyright (c) 2013 OpenStack LLC.
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+"""Common class for Huawei HVS storage drivers."""
+
+import base64
+import cookielib
+import json
+import time
+import urllib2
+import uuid
+
+from xml.etree import ElementTree as ET
+
+from cinder import context
+from cinder import exception
+from cinder.openstack.common import excutils
+from cinder.openstack.common import log as logging
+from cinder import units
+from cinder import utils
+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"]
+
+
+class HVSCommon():
+    """Common class for Huawei OceanStor HVS storage system."""
+
+    def __init__(self, configuration):
+        self.configuration = configuration
+        self.cookie = cookielib.CookieJar()
+        self.url = None
+
+    def call(self, url=False, data=None, method=None):
+        """Send requests to HVS 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)
+            if method:
+                req.get_method = lambda: method
+            res = urllib2.urlopen(req).read().decode("utf-8")
+            LOG.debug(_('HVS Response Data: %(res)s') % {'res': res})
+        except Exception as err:
+            err_msg = _('Bad reponse from server: %s') % err
+            LOG.error(err_msg)
+            raise err
+
+        try:
+            res_json = json.loads(res)
+        except Exception:
+            raise exception.CinderException(_('JSON transfer Error'))
+
+        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.
+        """
+
+        login_info = self._get_login_info()
+        url = login_info['HVSURL'] + "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
+            raise exception.CinderException(msg)
+
+        deviceid = result['data']['deviceid']
+        self.url = login_info['HVSURL'] + deviceid
+        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": "",
+                    "ALLOCTYPE": parameters['LUNType'],
+                    "CAPACITY": parameters['volume_size'],
+                    "WRITEPOLICY": parameters['WriteType'],
+                    "MIRRORPOLICY": parameters['MirrorSwitch'],
+                    "PREFETCHPOLICY": parameters['PrefetchType'],
+                    "PREFETCHVALUE": parameters['PrefetchValue'],
+                    "DATATRANSFERPOLICY": "1",
+                    "INITIALDISTRIBUTEPOLICY": "0"}
+
+        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}
+            LOG.error(msg)
+            raise exception.CinderException(msg)
+
+    def _create_volume(self, lun_param):
+        url = self.url + "/lun"
+        data = json.dumps(lun_param)
+        result = self.call(url, data)
+        self._assert_rest_result(result, 'create volume error')
+
+        if "data" in result:
+            return result['data']['ID']
+        else:
+            msg = _('create volume error: %(err)s') % {'err': result}
+            raise exception.CinderException(msg)
+
+    def create_volume(self, volume):
+        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(_("Can't find the Qos policy in array"))
+
+        # 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)
+
+        return lunid
+
+    def _get_volume_size(self, poolinfo, volume):
+        """Calculate the volume size.
+
+        We should devide the given volume size by 512 for the HVS system
+        caculates volume size with sectors, which is 512 bytes.
+        """
+
+        volume_size = units.GiB / 512  # 1G
+        if int(volume['size']) != 0:
+            volume_size = int(volume['size']) * units.GiB / 512
+
+        return volume_size
+
+    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.
+        """
+
+        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)
+        else:
+            LOG.warn(_("Can't find lun or lun goup 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'])
+
+    def _delete_lun(self, lun_id):
+        url = self.url + "/lun/" + lun_id
+        data = json.dumps({"TYPE": "11",
+                           "ID": lun_id})
+        result = self.call(url, data, "DELETE")
+        self._assert_rest_result(result, 'delete lun error')
+
+    def _read_xml(self):
+        """Open xml file and parse the content."""
+        filename = self.configuration.cinder_huawei_conf_file
+        try:
+            tree = ET.parse(filename)
+            root = tree.getroot()
+        except Exception as err:
+            LOG.error(_('_read_xml:%s') % err)
+            raise exception.VolumeBackendAPIException(data=err)
+        return root
+
+    def _encode_name(self, name):
+        uuid_str = name.replace("-", "")
+        vol_uuid = uuid.UUID('urn:uuid:%s' % uuid_str)
+        vol_encoded = base64.urlsafe_b64encode(vol_uuid.bytes)
+        newuuid = vol_encoded.replace("=", "")
+        return newuuid
+
+    def _find_pool_info(self):
+        root = self._read_xml()
+        pool_name = root.findtext('LUN/StoragePool')
+        if not pool_name:
+            err_msg = _("Invalid resource pool: %s") % pool_name
+            raise exception.InvalidInput(err_msg)
+
+        url = self.url + "/storagepool"
+        result = self.call(url, None)
+        self._assert_rest_result(result, 'Query resource pool error')
+
+        poolinfo = {}
+        if "data" in result:
+            for item in result['data']:
+                if pool_name.strip() == item['NAME']:
+                    poolinfo['ID'] = item['ID']
+                    poolinfo['CAPACITY'] = item['USERFREECAPACITY']
+                    poolinfo['TOTALCAPACITY'] = item['USERTOTALCAPACITY']
+                    break
+
+        if not poolinfo:
+            msg = (_('Get pool info error, pool name is:%s') % pool_name)
+            raise exception.CinderException(msg)
+
+        return poolinfo
+
+    def _get_volume_by_name(self, name):
+        url = self.url + "/lun"
+        result = self.call(url, None, "GET")
+        self._assert_rest_result(result, 'Get volume by name error!')
+
+        volume_id = None
+        if "data" in result:
+            for item in result['data']:
+                if name == item['NAME']:
+                    volume_id = item['ID']
+                    break
+        return volume_id
+
+    def _active_snapshot(self, snapshot_id):
+        activeurl = self.url + "/snapshot/activate"
+        data = json.dumps({"SNAPSHOTLIST": [snapshot_id]})
+        result = self.call(activeurl, data)
+        self._assert_rest_result(result, 'Active snapshot error.')
+
+    def _create_snapshot(self, snapshot):
+        snapshot_name = self._encode_name(snapshot['id'])
+        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})
+
+        lun_id = self._get_volume_by_name(volume_name)
+        url = self.url + "/snapshot"
+        data = json.dumps({"TYPE": "27",
+                           "NAME": snapshot_name,
+                           "PARENTTYPE": "11",
+                           "PARENTID": lun_id})
+        result = self.call(url, data)
+        self._assert_rest_result(result, 'Create snapshot error.')
+
+        if 'data' not in result:
+            raise exception.CinderException(_('Create snapshot error.'))
+
+        return result['data']['ID']
+
+    def create_snapshot(self, snapshot):
+        snapshot_id = self._create_snapshot(snapshot)
+        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'])
+
+        LOG.debug(_('_stop_snapshot:snapshot name:%(snapshot)s, '
+                    'volume name:%(volume)s.')
+                  % {'snapshot': snapshot_name,
+                     'volume': volume_name})
+
+        snapshotid = self._get_snapshotid_by_name(snapshot_name)
+        stopdata = json.dumps({"ID": snapshotid})
+        url = self.url + "/snapshot/stop"
+        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.')
+
+    def delete_snapshot(self, snapshot):
+        snapshotid = self._stop_snapshot(snapshot)
+        self._delete_snapshot(snapshotid)
+
+    def _get_snapshotid_by_name(self, name):
+        url = self.url + "/snapshot"
+        data = json.dumps({"TYPE": "27"})
+        result = self.call(url, data, "GET")
+        self._assert_rest_result(result, 'Get snapshot id error.')
+
+        snapshot_id = None
+        if "data" in result:
+            for item in result['data']:
+                if name == item['NAME']:
+                    snapshot_id = item['ID']
+                    break
+        return snapshot_id
+
+    def _copy_volume(self, volume, copy_name, src_lun, tgt_lun):
+        luncopy_id = self._create_luncopy(copy_name,
+                                          src_lun, tgt_lun)
+        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)
+                self.delete_volume(volume)
+
+        self._delete_luncopy(luncopy_id)
+
+    def create_volume_from_snapshot(self, volume, snapshot):
+        """Create a volume from a snapshot.
+
+        We use LUNcopy to copy a new volume from snapshot.
+        The time needed increases as volume size does.
+        """
+
+        snapshot_name = self._encode_name(snapshot['id'])
+        src_lun_id = self._get_snapshotid_by_name(snapshot_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)
+
+    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)
+
+    def _create_luncopy(self, luncopyname, srclunid, tgtlunid):
+        """Create a luncopy."""
+        url = self.url + "/luncopy"
+        data = json.dumps({"TYPE": "219",
+                           "NAME": luncopyname,
+                           "DESCRIPTION": luncopyname,
+                           "COPYSPEED": "2",
+                           "LUNCOPYTYPE": "1",
+                           "SOURCELUN": ("INVALID;%s;INVALID;INVALID;INVALID"
+                                         % srclunid),
+                           "TARGETLUN": ("INVALID;%s;INVALID;INVALID;INVALID"
+                                         % tgtlunid)})
+        result = self.call(url, data)
+        self._assert_rest_result(result, 'Create lun copy error.')
+
+        if "data" not in result:
+            raise exception.CinderException(_('Create luncopy error.'))
+
+        return result['data']['ID']
+
+    def _add_host_into_hostgroup(self, host_name):
+        """Associate host to hostgroup.
+
+        If host group doesn't exist, create one.
+
+        """
+
+        hostgroup_id = self._find_hostgroup(host_name)
+        if hostgroup_id is None:
+            hostgroup_id = self._create_hostgroup(host_name)
+
+        hostid = self._find_host(host_name)
+        if hostid is None:
+            hostid = self._add_host(host_name)
+            self._associate_host_to_hostgroup(hostgroup_id, hostid)
+
+        return hostid, 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)
+        lun_id = self._get_volume_by_name(volume_name)
+        view_id = self._find_mapping_view(volume_name)
+
+        LOG.debug(_('_mapping_hostgroup_and_lungroup: lun_group: %(lun_group)s'
+                    'view_id: %(view_id)s')
+                  % {'lun_group': str(lungroup_id),
+                     'view_id': str(view_id)})
+
+        try:
+            if view_id is None:
+                view_id = self._add_mapping_view(volume_name, host_id)
+                self._associate_hostgroup_to_view(view_id, hostgroup_id)
+                self._associate_lungroup_to_view(view_id, lungroup_id)
+            else:
+                if not self._hostgroup_associated(view_id, hostgroup_id):
+                    self._associate_hostgroup_to_view(view_id, hostgroup_id)
+                if not self._lungroup_associated(view_id, lungroup_id):
+                    self._associate_lungroup_to_view(view_id, lungroup_id)
+
+        except Exception:
+            with excutils.save_and_reraise_exception():
+                self._delete_hostgoup_mapping_view(view_id, hostgroup_id)
+                self._delete_lungroup_mapping_view(view_id, lungroup_id)
+                self._delete_mapping_view(view_id)
+
+        return lun_id
+
+    def _ensure_initiator_added(self, initiator_name, hostid):
+        added = self._initiator_is_added_to_array(initiator_name)
+        if not added:
+            self._add_initiator_to_array(initiator_name)
+        else:
+            if self._is_initiator_associated_to_host(initiator_name) is False:
+                self._associate_initiator_to_host(initiator_name, hostid)
+
+    def initialize_connection_iscsi(self, volume, connector):
+        """Map a volume to a host and return target iSCSI information."""
+        initiator_name = connector['initiator']
+        host_name = connector['host']
+        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})
+        (iscsi_iqn, target_ip) = self._get_iscsi_params(connector)
+
+        #create host_goup if not exist
+        hostid, hostgroup_id = self._add_host_into_hostgroup(host_name)
+        self._ensure_initiator_added(initiator_name, hostid)
+
+        # Mapping lungooup and hostgoup 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)
+
+        # Return iSCSI properties.
+        properties = {}
+        properties['target_discovered'] = False
+        properties['target_portal'] = ('%s:%s' % (target_ip, '3260'))
+        properties['target_iqn'] = iscsi_iqn
+        properties['target_lun'] = int(hostlunid)
+        properties['volume_id'] = volume['id']
+
+        return {'driver_volume_type': 'iscsi', 'data': properties}
+
+    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})
+
+        # Create host goup if not exist
+        hostid, hostgroup_id = self._add_host_into_hostgroup(host_name)
+
+        free_wwns = self._get_connected_free_wwns()
+        LOG.debug(_("the free wwns %s") % free_wwns)
+        for wwn in wwns:
+            if wwn in free_wwns:
+                self._add_fc_port_to_host(hostid, wwn)
+
+        lun_id = self._mapping_hostgroup_and_lungroup(volume_name,
+                                                      hostgroup_id, hostid)
+        host_lun_id = self._find_host_lun_id(hostid, lun_id)
+
+        tgt_port_wwns = []
+        for wwn in wwns:
+            tgtwwpns = self._get_fc_target_wwpns(wwn)
+            if tgtwwpns:
+                tgt_port_wwns.append(tgtwwpns)
+
+        # 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)
+
+        return {'driver_volume_type': 'fibre_channel',
+                'data': properties}
+
+    def _get_iscsi_tgt_port(self):
+        url = self.url + "/iscsidevicename"
+        result = self.call(url, None)
+        msg = 'Get iSCSI target port error.'
+        self._assert_rest_result(result, msg)
+
+        if "data" not in result:
+            raise exception.CinderException(_('%s') % msg)
+
+        return result['data'][0]['CMO_ISCSI_DEVICE_NAME']
+
+    def _find_hostgroup(self, groupname):
+        """Get the given hostgroup id."""
+        url = self.url + "/hostgroup"
+        result = self.call(url, None, "GET")
+        self._assert_rest_result(result, 'Get host group information error.')
+
+        host_group_id = None
+        if "data" in result:
+            for item in result['data']:
+                if groupname == item['NAME']:
+                    host_group_id = item['ID']
+                    break
+        return host_group_id
+
+    def _find_lungroup(self, lungroupname):
+        """Get the given hostgroup id."""
+        url = self.url + "/lungroup"
+        result = self.call(url, None, "GET")
+        self._assert_rest_result(result, 'Get lun group information error.')
+
+        lun_group_id = None
+        if 'data' in result:
+            for item in result['data']:
+                if lungroupname == item['NAME']:
+                    lun_group_id = item['ID']
+                    break
+        return lun_group_id
+
+    def _create_hostgroup(self, hostgroupname):
+        url = self.url + "/hostgroup"
+        data = json.dumps({"TYPE": "14", "NAME": hostgroupname})
+        result = self.call(url, data)
+        msg = 'Create host group error.'
+        self._assert_rest_result(result, msg)
+
+        if "data" not in result:
+            raise exception.CinderException(_('%s') % msg)
+
+        return result['data']['ID']
+
+    def _create_lungroup(self, lungroupname):
+        url = self.url + "/lungroup"
+        data = json.dumps({"DESCRIPTION": lungroupname,
+                           "NAME": lungroupname})
+        result = self.call(url, data)
+        msg = 'Create lun group error.'
+        self._assert_rest_result(result, msg)
+
+        if "data" not in result:
+            raise exception.CinderException(_('%s') % msg)
+
+        return result['data']['ID']
+
+    def _delete_lungroup(self, lungroupid):
+        url = self.url + "/LUNGroup/" + lungroupid
+        result = self.call(url, None, "DELETE")
+        self._assert_rest_result(result, 'Delete lun group 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.')
+
+        if "data" in result:
+            for item in result['data']:
+                if viewid == item['ID']:
+                    return True
+        return False
+
+    def _hostgroup_associated(self, viewid, hostgroupid):
+        url_subfix = ("/mappingview/associate?TYPE=245&"
+                      "ASSOCIATEOBJTYPE=14&ASSOCIATEOBJID=%s" % hostgroupid)
+        url = self.url + url_subfix
+        result = self.call(url, None, "GET")
+        self._assert_rest_result(result, 'Check host group associated error.')
+
+        if "data" in result:
+            for item in result['data']:
+                if viewid == item['ID']:
+                    return True
+        return False
+
+    def _find_host_lun_id(self, hostid, lunid):
+        time.sleep(2)
+        url = self.url + ("/lun/associate?TYPE=11&ASSOCIATEOBJTYPE=21"
+                          "&ASSOCIATEOBJID=%s" % (hostid))
+        result = self.call(url, None, "GET")
+        self._assert_rest_result(result, 'Find host lun id error.')
+
+        host_lun_id = 1
+        if "data" in result:
+            for item in result['data']:
+                if lunid == item['ID']:
+                    associate_data = result['data'][0]['ASSOCIATEMETADATA']
+                    try:
+                        hostassoinfo = json.loads(associate_data)
+                        host_lun_id = hostassoinfo['HostLUNID']
+                        break
+                    except Exception:
+                        msg = _("_find_host_lun_id transfer data error! ")
+                        raise exception.CinderException(msg)
+        return host_lun_id
+
+    def _find_host(self, hostname):
+        """Get the given host ID."""
+        url = self.url + "/host"
+        data = json.dumps({"TYPE": "21"})
+        result = self.call(url, data, "GET")
+        self._assert_rest_result(result, 'Find host in host group error.')
+
+        host_id = None
+        if "data" in result:
+            for item in result['data']:
+                if hostname == item['NAME']:
+                    host_id = item['ID']
+                    break
+        return host_id
+
+    def _add_host(self, hostname):
+        """Add a new host."""
+        url = self.url + "/host"
+        data = json.dumps({"TYPE": "21",
+                           "NAME": hostname,
+                           "OPERATIONSYSTEM": "0"})
+        result = self.call(url, data)
+        self._assert_rest_result(result, 'Add new host error.')
+
+        if "data" in result:
+            return result['data']['ID']
+        else:
+            return None
+
+    def _associate_host_to_hostgroup(self, hostgroupid, hostid):
+        url = self.url + "/host/associate"
+        data = json.dumps({"ID": hostgroupid,
+                           "ASSOCIATEOBJTYPE": "21",
+                           "ASSOCIATEOBJID": hostid})
+
+        result = self.call(url, data)
+        self._assert_rest_result(result, 'Associate host to host group error.')
+
+    def _associate_lun_to_lungroup(self, lungroupid, lunid):
+        """Associate lun to lun group."""
+        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.')
+
+    def _delete_associated_lun_from_lungroup(self, lungroupid, lunid):
+        """Remove lun from lun group."""
+
+        url = self.url + ("/lungroup/associate?ID=%s"
+                          "&ASSOCIATEOBJTYPE=11&ASSOCIATEOBJID=%s"
+                          % (lungroupid, lunid))
+
+        result = self.call(url, None, 'DELETE')
+        self._assert_rest_result(result,
+                                 'Delete associated lun from lun group error')
+
+    def _initiator_is_added_to_array(self, ininame):
+        """Check whether the initiator is already added in array."""
+        url = self.url + "/iscsi_initiator/" + ininame
+        data = json.dumps({"TYPE": "222", "ID": ininame})
+        result = self.call(url, data, "GET")
+        self._assert_rest_result(result,
+                                 'Check initiator added to array error.')
+
+        if "data" in result and result['data']['ID']:
+            return True
+        else:
+            return False
+
+    def _is_initiator_associated_to_host(self, ininame):
+        """Check whether the initiator is associated to the host."""
+        url = self.url + "/iscsi_initiator/" + ininame
+        data = json.dumps({"TYPE": "222", "ID": ininame})
+        result = self.call(url, data, "GET")
+        self._assert_rest_result(result,
+                                 'Check initiator associated to host error.')
+
+        if "data" in result and result['data']['ISFREE'] == "true":
+            return True
+        else:
+            return False
+
+    def _add_initiator_to_array(self, ininame):
+        """Add a new initiator to storage device."""
+        url = self.url + "/iscsi_initiator/"
+        data = json.dumps({"TYPE": "222",
+                           "ID": ininame,
+                           "USECHAP": "False"})
+        result = self.call(url, data)
+        self._assert_rest_result(result, 'Add initiator to array error.')
+
+    def _associate_initiator_to_host(self, ininame, hostid):
+        """Associate initiator with the host."""
+        url = self.url + "/iscsi_initiator/" + ininame
+        data = json.dumps({"TYPE": "222",
+                           "ID": ininame,
+                           "USECHAP": "False",
+                           "PARENTTYPE": "21",
+                           "PARENTID": hostid})
+        result = self.call(url, data, "PUT")
+        self._assert_rest_result(result, 'Associate initiator to host error.')
+
+    def _find_mapping_view(self, name):
+        """Find mapping view."""
+        url = self.url + "/mappingview"
+        data = json.dumps({"TYPE": "245"})
+        result = self.call(url, data, "GET")
+        msg = 'Find map view error.'
+        self._assert_rest_result(result, msg)
+
+        if "data" not in result:
+            raise exception.CinderException(_('%s') % msg)
+
+        viewid = None
+        for item in result['data']:
+            if name == item['NAME']:
+                viewid = item['ID']
+                break
+        return viewid
+
+    def _add_mapping_view(self, name, host_id):
+        url = self.url + "/mappingview"
+        data = json.dumps({"NAME": name, "TYPE": "245"})
+        result = self.call(url, data)
+        self._assert_rest_result(result, 'Add map view error.')
+
+        return result['data']['ID']
+
+    def _associate_hostgroup_to_view(self, viewID, hostGroupID):
+        url = self.url + "/MAPPINGVIEW/CREATE_ASSOCIATE"
+        data = json.dumps({"ASSOCIATEOBJTYPE": "14",
+                           "ASSOCIATEOBJID": hostGroupID,
+                           "TYPE": "245",
+                           "ID": viewID})
+        result = self.call(url, data, "PUT")
+        self._assert_rest_result(result, 'Associate host to view error.')
+
+    def _associate_lungroup_to_view(self, viewID, lunGroupID):
+        url = self.url + "/MAPPINGVIEW/CREATE_ASSOCIATE"
+        data = json.dumps({"ASSOCIATEOBJTYPE": "256",
+                           "ASSOCIATEOBJID": lunGroupID,
+                           "TYPE": "245",
+                           "ID": viewID})
+        result = self.call(url, data, "PUT")
+        self._assert_rest_result(result, 'Associate lun group to view error.')
+
+    def _delete_lungroup_mapping_view(self, view_id, lungroup_id):
+        """remove lun group 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.')
+
+    def _delete_hostgoup_mapping_view(self, view_id, hostgroup_id):
+        """remove host group 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.')
+
+    def _delete_mapping_view(self, view_id):
+        """remove mapping view from the storage."""
+        url = self.url + "/mappingview/" + view_id
+        result = self.call(url, None, "DELETE")
+        self._assert_rest_result(result, 'Delete map view error.')
+
+    def terminate_connection(self, volume, connector, **kwargs):
+        """Delete map between a volume and a host."""
+        initiator_name = connector['initiator']
+        volume_name = self._encode_name(volume['id'])
+        host_name = connector['host']
+
+        LOG.debug(_('terminate_connection:volume name: %(volume)s, '
+                    'initiator name: %(ini)s.')
+                  % {'volume': volume_name,
+                     'ini': initiator_name})
+
+        view_id = self._find_mapping_view(volume_name)
+        hostgroup_id = self._find_hostgroup(host_name)
+        lungroup_id = self._find_lungroup(volume_name)
+
+        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 login_out(self):
+        """logout the session."""
+        url = self.url + "/sessions"
+        result = self.call(url, None, "DELETE")
+        self._assert_rest_result(result, 'Log out of session error.')
+
+    def _start_luncopy(self, luncopyid):
+        """Starte a LUNcopy."""
+        url = self.url + "/LUNCOPY/start"
+        data = json.dumps({"TYPE": "219", "ID": luncopyid})
+        result = self.call(url, data, "PUT")
+        self._assert_rest_result(result, 'Start lun copy error.')
+
+    def _get_capacity(self):
+        """Get free capacity and total capacity of the pools."""
+        poolinfo = self._find_pool_info()
+        pool_capacity = {'total_capacity': 0.0,
+                         'CAPACITY': 0.0}
+
+        if poolinfo:
+            total = int(poolinfo['TOTALCAPACITY']) / 1024.0 / 1024.0 / 2
+            free = int(poolinfo['CAPACITY']) / 1024.0 / 1024.0 / 2
+            pool_capacity['total_capacity'] = total
+            pool_capacity['free_capacity'] = free
+
+        return pool_capacity
+
+    def _get_lun_conf_params(self):
+        """Get parameters from config file for creating lun."""
+        # Default lun set information
+        lunsetinfo = {'LUNType': 'Thick',
+                      'StripUnitSize': '64',
+                      'WriteType': '1',
+                      'MirrorSwitch': '1',
+                      'PrefetchType': '3',
+                      'PrefetchValue': '0',
+                      'PrefetchTimes': '0'}
+
+        root = self._read_xml()
+        luntype = root.findtext('LUN/LUNType')
+        if luntype:
+            if luntype.strip() in ['Thick', 'Thin']:
+                lunsetinfo['LUNType'] = luntype.strip()
+                if luntype.strip() == 'Thick':
+                    lunsetinfo['LUNType'] = 0
+                if luntype.strip() == 'Thin':
+                    lunsetinfo['LUNType'] = 1
+
+            elif luntype is not '' and luntype is not None:
+                err_msg = (_('Config file is wrong. LUNType must be "Thin"'
+                             ' or "Thick". LUNType:%(fetchtype)s')
+                           % {'fetchtype': luntype})
+                raise exception.VolumeBackendAPIException(data=err_msg)
+
+        stripunitsize = root.findtext('LUN/StripUnitSize')
+        if stripunitsize is not None:
+            lunsetinfo['StripUnitSize'] = stripunitsize.strip()
+        writetype = root.findtext('LUN/WriteType')
+        if writetype is not None:
+            lunsetinfo['WriteType'] = writetype.strip()
+        mirrorswitch = root.findtext('LUN/MirrorSwitch')
+        if mirrorswitch is not None:
+            lunsetinfo['MirrorSwitch'] = mirrorswitch.strip()
+
+        prefetch = root.find('LUN/Prefetch')
+        fetchtype = prefetch.attrib['Type']
+        if prefetch is not None and prefetch.attrib['Type']:
+            if fetchtype in ['0', '1', '2', '3']:
+                lunsetinfo['PrefetchType'] = fetchtype.strip()
+                typevalue = prefetch.attrib['Value'].strip()
+                if lunsetinfo['PrefetchType'] == '1':
+                    lunsetinfo['PrefetchValue'] = typevalue
+                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})
+                raise exception.CinderException(err_msg)
+        else:
+            LOG.debug(_('Use default prefetch fetchtype. '
+                        'Prefetch fetchtype:Intelligent.'))
+
+        return lunsetinfo
+
+    def _wait_for_luncopy(self, luncopyid):
+        """Wait for LUNcopy to complete."""
+        while True:
+            luncopy_info = self._get_luncopy_info(luncopyid)
+            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})
+                raise exception.VolumeBackendAPIException(data=err_msg)
+            time.sleep(10)
+
+    def _get_luncopy_info(self, luncopyid):
+        """Get LUNcopy information."""
+        url = self.url + "/LUNCOPY?range=[0-100000]"
+        data = json.dumps({"TYPE": "219", })
+        result = self.call(url, data, "GET")
+        self._assert_rest_result(result, 'Get lun copy information error.')
+
+        luncopyinfo = {}
+        if "data" in result:
+            for item in result['data']:
+                if luncopyid == item['ID']:
+                    luncopyinfo['name'] = item['NAME']
+                    luncopyinfo['id'] = item['ID']
+                    luncopyinfo['state'] = item['HEALTHSTATUS']
+                    luncopyinfo['status'] = item['RUNNINGSTATUS']
+                    break
+        return luncopyinfo
+
+    def _delete_luncopy(self, luncopyid):
+        """Delete a LUNcopy."""
+        url = self.url + "/LUNCOPY/%s" % luncopyid
+        result = self.call(url, None, "DELETE")
+        self._assert_rest_result(result, 'Delete lun copy error.')
+
+    def _get_connected_free_wwns(self):
+        """Get free connected FC port WWNs.
+
+        If no new ports connected, return an empty list.
+        """
+        url = self.url + "/fc_initiator?ISFREE=true&range=[0-1000]"
+        result = self.call(url, None, "GET")
+        msg = 'Get connected free FC wwn error.'
+        self._assert_rest_result(result, msg)
+
+        if "data" not in result:
+            raise exception.CinderException(_('%s') % msg)
+
+        wwns = []
+        for item in result['data']:
+            wwns.append(item['ID'])
+        return wwns
+
+    def _add_fc_port_to_host(self, hostid, wwn, multipathtype=0):
+        """Add a FC port to the host."""
+        url = self.url + "/fc_initiator/" + wwn
+        data = json.dumps({"TYPE": "223",
+                           "ID": wwn,
+                           "PARENTTYPE": 21,
+                           "PARENTID": hostid})
+        result = self.call(url, data, "PUT")
+        self._assert_rest_result(result, 'Add FC port to host error.')
+
+    def _get_iscsi_port_info(self, ip):
+        """Get iscsi port info in order to build the iscsi target iqn."""
+        url = self.url + "/eth_port"
+        result = self.call(url, None, "GET")
+        msg = 'Get iSCSI port information error.'
+        self._assert_rest_result(result, msg)
+
+        if "data" not in result:
+            raise exception.CinderException(_('%s') % msg)
+
+        iscsi_port_info = None
+        for item in result['data']:
+            if ip == item['IPV4ADDR']:
+                iscsi_port_info = item['LOCATION']
+                break
+
+        return iscsi_port_info
+
+    def _get_iscsi_conf(self):
+        """Get iSCSI info from config file."""
+        iscsiinfo = {}
+        root = self._read_xml()
+        iscsiinfo['DefaultTargetIP'] = \
+            root.findtext('iSCSI/DefaultTargetIP').strip()
+        initiator_list = []
+        tmp_dic = {}
+        for dic in root.findall('iSCSI/Initiator'):
+            # Strip values of dic
+            for k, v in dic.items():
+                tmp_dic[k] = v.strip()
+            initiator_list.append(tmp_dic)
+        iscsiinfo['Initiator'] = initiator_list
+
+        return iscsiinfo
+
+    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.debug(_('request ip info is %s.') % ip_info)
+        split_list = ip_info.split(".")
+        newstr = split_list[1] + split_list[2]
+        LOG.debug(_('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.debug(_('_get_tgt_iqn: iSCSI target iqn is %s') % iqn)
+            return iqn
+        else:
+            return None
+
+    def _get_fc_target_wwpns(self, wwn):
+        url = (self.url +
+               "/host_link?INITIATOR_TYPE=223&INITIATOR_PORT_WWN=" + wwn)
+        result = self.call(url, None, "GET")
+        msg = 'Get FC target wwpn error.'
+        self._assert_rest_result(result, msg)
+
+        if "data" not in result:
+            raise exception.CinderException(_('%s') % msg)
+
+        fc_wwpns = None
+        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(_('_parse_volume_type: Unacceptable paramater '
+                               '%(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):
+        capacity = self._get_capacity()
+        data = {}
+        data['vendor_name'] = 'Huawei'
+        data['total_capacity_gb'] = capacity['total_capacity']
+        data['free_capacity_gb'] = capacity['free_capacity']
+        data['reserved_percentage'] = 0
+        data['QoS_support'] = True
+        data['Tier_support'] = True
+        return data
+
+    def _find_qos_policy_info(self, policy_name):
+        url = self.url + "/ioclass"
+        result = self.call(url, None, "GET")
+        msg = 'Get qos policy error.'
+        self._assert_rest_result(result, msg)
+
+        if "data" not in result:
+            raise exception.CinderException(_('%s') % 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
+        return qos_info
+
+    def _update_qos_policy_lunlist(self, lunlist, policy_id):
+        url = self.url + "/ioclass/" + policy_id
+        data = json.dumps({"TYPE": "230",
+                           "ID": policy_id,
+                           "LUNLIST": lunlist})
+        result = self.call(url, data, "PUT")
+        self._assert_rest_result(result, 'Up date qos policy error.')
+
+    def _get_login_info(self):
+        """Get login IP, username and password from config file."""
+        logininfo = {}
+        filename = self.configuration.cinder_huawei_conf_file
+        tree = ET.parse(filename)
+        root = tree.getroot()
+        logininfo['HVSURL'] = root.findtext('Storage/HVSURL').strip()
+
+        need_encode = False
+        for key in ['UserName', 'UserPassword']:
+            node = root.find('Storage/%s' % key)
+            node_text = node.text
+            # Prefix !$$$ means encoded already.
+            if node_text.find('!$$$') > -1:
+                logininfo[key] = base64.b64decode(node_text[4:])
+            else:
+                logininfo[key] = node_text
+                node.text = '!$$$' + base64.b64encode(node_text)
+                need_encode = True
+        if need_encode:
+            self._change_file_mode(filename)
+            try:
+                tree.write(filename, 'UTF-8')
+            except Exception as err:
+                LOG.warn(_('%s') % err)
+
+        return logininfo
+
+    def _change_file_mode(self, filepath):
+        utils.execute('chmod', '777', filepath, run_as_root=True)
+
+    def _check_conf_file(self):
+        """Check the config file, make sure the essential items are set."""
+        root = self._read_xml()
+        hvsurl = root.findtext('Storage/HVSURL')
+        username = root.findtext('Storage/UserName')
+        pwd = root.findtext('Storage/UserPassword')
+        pool_node = root.findall('LUN/StoragePool')
+
+        if (not hvsurl) or (not username) or (not pwd):
+            err_msg = (_('_check_conf_file: Config file invalid. HVSURL,'
+                         ' UserName and UserPassword must be set.'))
+            LOG.error(err_msg)
+            raise exception.InvalidInput(reason=err_msg)
+
+        if not pool_node:
+            err_msg = (_('_check_conf_file: Config file invalid. '
+                         'StoragePool must be set.'))
+            raise exception.InvalidInput(reason=err_msg)
+
+    def _get_iscsi_params(self, connector):
+        """Get target iSCSI params, including iqn, IP."""
+        initiator = connector['initiator']
+        iscsi_conf = self._get_iscsi_conf()
+        target_ip = None
+        for ini in iscsi_conf['Initiator']:
+            if ini['Name'] == initiator:
+                target_ip = ini['TargetIP']
+                break
+        # If didn't specify target IP for some initiator, use default IP.
+        if not target_ip:
+            if iscsi_conf['DefaultTargetIP']:
+                target_ip = iscsi_conf['DefaultTargetIP']
+
+            else:
+                msg = (_('_get_iscsi_params: Failed to get target IP '
+                         'for initiator %(ini)s, please check config file.')
+                       % {'ini': initiator})
+                LOG.error(msg)
+                raise exception.InvalidInput(reason=msg)
+
+        target_iqn = self._get_tgt_iqn(target_ip)
+
+        return (target_iqn, target_ip)