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