--- /dev/null
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright (c) 2013 Huawei Technologies Co., Ltd.
+# Copyright (c) 2013 OpenStack LLC.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+"""
+Unit Tests for Huawei HVS volume drivers.
+"""
+
+import json
+import mox
+import os
+import shutil
+import tempfile
+import time
+
+from xml.dom.minidom import Document
+
+from cinder import exception
+from cinder import test
+from cinder import utils
+from cinder.volume import configuration as conf
+from cinder.volume.drivers.huawei import huawei_hvs
+from cinder.volume.drivers.huawei import rest_common
+
+
+test_volume = {'name': 'volume-21ec7341-9256-497b-97d9-ef48edcf0635',
+ 'size': 2,
+ 'volume_name': 'vol1',
+ 'id': '21ec7341-9256-497b-97d9-ef48edcf0635',
+ 'volume_id': '21ec7341-9256-497b-97d9-ef48edcf0635',
+ 'provider_auth': None,
+ 'project_id': 'project',
+ 'display_name': 'vol1',
+ 'display_description': 'test volume',
+ 'volume_type_id': None}
+
+test_snap = {'name': 'volume-21ec7341-9256-497b-97d9-ef48edcf0635',
+ 'size': 1,
+ 'volume_name': 'vol1',
+ 'id': '21ec7341-9256-497b-97d9-ef48edcf0635',
+ 'volume_id': '21ec7341-9256-497b-97d9-ef48edcf0635',
+ 'provider_auth': None,
+ 'project_id': 'project',
+ 'display_name': 'vol1',
+ 'display_description': 'test volume',
+ 'volume_type_id': None}
+
+FakeConnector = {'initiator': 'iqn.1993-08.debian:01:ec2bff7ac3a3',
+ 'wwpns': ['10000090fa0d6754'],
+ 'wwnns': ['10000090fa0d6755'],
+ 'host': 'fakehost'}
+
+
+def Fake_sleep(time):
+ pass
+
+
+class FakeHVSCommon(rest_common.HVSCommon):
+
+ def __init__(self, configuration):
+ rest_common.HVSCommon.__init__(self, configuration)
+ self.test_normal = True
+ self.other_flag = True
+ self.deviceid = None
+ self.lun_id = None
+ self.snapshot_id = None
+ self.luncopy_id = None
+ self.termin_flag = False
+
+ def _parse_volume_type(self, volume):
+ self._get_lun_conf_params()
+ poolinfo = self._find_pool_info()
+ volume_size = self._get_volume_size(poolinfo, volume)
+
+ params = {'LUNType': 0,
+ 'WriteType': '1',
+ 'PrefetchType': '3',
+ 'qos_level': 'Qos-high',
+ 'StripUnitSize': '64',
+ 'PrefetchValue': '0',
+ 'PrefetchTimes': '0',
+ 'qos': 'OpenStack_Qos_High',
+ 'MirrorSwitch': '1',
+ 'tier': 'Tier_high'}
+
+ params['volume_size'] = volume_size
+ params['pool_id'] = poolinfo['ID']
+ return params
+
+ def _change_file_mode(self, filepath):
+ utils.execute('chmod', '777', filepath)
+
+ def call(self, url=False, data=None, method=None):
+
+ url = url.replace('http://100.115.10.69:8082/deviceManager/rest', '')
+ url = url.replace('/210235G7J20000000000/', '')
+ data = None
+
+ if self.test_normal:
+ if url == "/xx/sessions":
+ data = """{"error":{"code":0},
+ "data":{"username":"admin",
+ "deviceid":"210235G7J20000000000"
+ }}"""
+ if url == "sessions":
+ data = """{"error":{"code":0},
+ "data":{"ID":11}}"""
+
+ if url == "storagepool":
+ data = """{"error":{"code":0},
+ "data":[{"ID":"0",
+ "NAME":"OpenStack_Pool",
+ "USERFREECAPACITY":"985661440",
+ "USERTOTALCAPACITY":"985661440"
+ }]}"""
+
+ if url == "lun":
+ if method is None:
+ data = """{"error":{"code":0},
+ "data":{"ID":"1",
+ "NAME":"5mFHcBv4RkCcD+JyrWc0SA"}}"""
+ self.lun_id = "0"
+
+ if method == 'GET':
+ data = """{"error":{"code":0},
+ "data":[{"ID":"1",
+ "NAME":"IexzQZJWSXuX2e9I7c8GNQ"}]}"""
+
+ if url == "lungroup":
+ if method is None:
+ data = """{"error":{"code":0},
+ "data":{"NAME":"5mFHcBv4RkCcD+JyrWc0SA",
+ "DESCRIPTION":"5mFHcBv4RkCcD",
+ "ID":"11",
+ "TYPE":256}}"""
+
+ if method == "GET":
+ data = """{"error":{"code":0},
+ "data":[{"NAME":"IexzQZJWSXuX2e9I7c8GNQ",
+ "DESCRIPTION":"5mFHcBv4RkCcD",
+ "ID":"11",
+ "TYPE":256}]}"""
+
+ if method == "DELETE":
+ data = """{"error":{"code":0},
+ "data":[{"NAME":"IexzQZJWSXuX2e9I7c8GNQ",
+ "DESCRIPTION":"5mFHcBv4RkCcD+JyrWc0SA",
+ "ID":"11",
+ "TYPE":256}]}"""
+
+ if url == "lungroup/associate":
+ data = """{"error":{"code":0},
+ "data":{"NAME":"5mFHcBv4RkCcD+JyrWc0SA",
+ "DESCRIPTION":"5mFHcBv4RkCcD+JyrWc0SA",
+ "ID":"11",
+ "TYPE":256}}"""
+
+ if url == "snapshot":
+ if method is None:
+ data = """{"error":{"code":0},
+ "data":{"ID":11}}"""
+ self.snapshot_id = "3"
+
+ if method == "GET":
+ data = """{"error":{"code":0},
+ "data":[{"ID":11,"NAME":"SDFAJSDFLKJ"},
+ {"ID":12,"NAME":"SDFAJSDFLKJ"}]}"""
+
+ if url == "snapshot/activate":
+ data = """{"error":{"code":0}}"""
+
+ if url == ("lungroup/associate?ID=11"
+ "&ASSOCIATEOBJTYPE=11&ASSOCIATEOBJID=1"):
+ data = """{"error":{"code":0}}"""
+
+ if url == "LUNGroup/11":
+ data = """{"error":{"code":0}}"""
+
+ if url == 'lun/1':
+ data = """{"error":{"code":0}}"""
+ self.lun_id = None
+
+ if url == 'snapshot':
+ if method == "GET":
+ data = """{"error":{"code":0},
+ "data":[{"PARENTTYPE":11,
+ "NAME":"IexzQZJWSXuX2e9I7c8GNQ",
+ "WWN":"60022a11000a2a3907ce96cb00000b",
+ "ID":"11",
+ "CONSUMEDCAPACITY":"0"}]}"""
+
+ if url == "snapshot/stop":
+ data = """{"error":{"code":0}}"""
+
+ if url == "snapshot/11":
+ data = """{"error":{"code":0}}"""
+ self.snapshot_id = None
+
+ if url == "luncopy":
+ data = """{"error":{"code":0},
+ "data":{"COPYSTOPTIME":"-1",
+ "HEALTHSTATUS":"1",
+ "NAME":"w1PSNvu6RumcZMmSh4/l+Q==",
+ "RUNNINGSTATUS":"36",
+ "DESCRIPTION":"w1PSNvu6RumcZMmSh4/l+Q==",
+ "ID":"0","LUNCOPYTYPE":"1",
+ "COPYPROGRESS":"0","COPYSPEED":"2",
+ "TYPE":219,"COPYSTARTTIME":"-1"}}"""
+ self.luncopy_id = "7"
+
+ if url == "LUNCOPY/start":
+ data = """{"error":{"code":0}}"""
+
+ if url == "LUNCOPY?range=[0-100000]":
+ data = """{"error":{"code":0},
+ "data":[{"COPYSTOPTIME":"1372209335",
+ "HEALTHSTATUS":"1",
+ "NAME":"w1PSNvu6RumcZMmSh4/l+Q==",
+ "RUNNINGSTATUS":"40",
+ "DESCRIPTION":"w1PSNvu6RumcZMmSh4/l+Q==",
+ "ID":"0","LUNCOPYTYPE":"1",
+ "COPYPROGRESS":"100",
+ "COPYSPEED":"2",
+ "TYPE":219,
+ "COPYSTARTTIME":"1372209329"}]}"""
+
+ if url == "LUNCOPY/0":
+ data = '{"error":{"code":0}}'
+
+ if url == "eth_port":
+ data = """{"error":{"code":0},
+ "data":[{"PARENTTYPE":209,
+ "MACADDRESS":"00:22:a1:0a:79:57",
+ "ETHNEGOTIATE":"-1","ERRORPACKETS":"0",
+ "IPV4ADDR":"100.115.10.68",
+ "IPV6GATEWAY":"","IPV6MASK":"0",
+ "OVERFLOWEDPACKETS":"0","ISCSINAME":"P0",
+ "HEALTHSTATUS":"1","ETHDUPLEX":"2",
+ "ID":"16909568","LOSTPACKETS":"0",
+ "TYPE":213,"NAME":"P0","INIORTGT":"4",
+ "RUNNINGSTATUS":"10","IPV4GATEWAY":"",
+ "BONDNAME":"","STARTTIME":"1371684218",
+ "SPEED":"1000","ISCSITCPPORT":"0",
+ "IPV4MASK":"255.255.0.0","IPV6ADDR":"",
+ "LOGICTYPE":"0","LOCATION":"ENG0.B5.P0",
+ "MTU":"1500","PARENTID":"1.5"}]}"""
+
+ if url == "iscsidevicename":
+ data = """{"error":{"code":0},
+"data":[{"CMO_ISCSI_DEVICE_NAME":
+"iqn.2006-08.com.huawei:oceanstor:21000022a10a2a39:iscsinametest"}]}"""
+
+ if url == "hostgroup":
+ if method is None:
+ data = """{"error":{"code":0},
+ "data":{"NAME":"ubuntuc",
+ "DESCRIPTION":"",
+ "ID":"0",
+ "TYPE":14}}"""
+
+ if method == "GET":
+ data = """{"error":{"code":0},
+ "data":[{"NAME":"ubuntuc",
+ "DESCRIPTION":"",
+ "ID":"0",
+ "TYPE":14}]}"""
+
+ if url == "host":
+ if method is None:
+ data = """{"error":{"code":0},
+ "data":{"PARENTTYPE":245,
+ "NAME":"Default Host",
+ "DESCRIPTION":"",
+ "RUNNINGSTATUS":"1",
+ "IP":"","PARENTNAME":"0",
+ "OPERATIONSYSTEM":"1","LOCATION":"",
+ "HEALTHSTATUS":"1","MODEL":"",
+ "ID":"0","PARENTID":"0",
+ "NETWORKNAME":"","TYPE":21}} """
+
+ if method == "GET":
+ data = """{"error":{"code":0},
+ "data":[{"PARENTTYPE":245,
+ "NAME":"ubuntuc",
+ "DESCRIPTION":"",
+ "RUNNINGSTATUS":"1",
+ "IP":"","PARENTNAME":"",
+ "OPERATIONSYSTEM":"0",
+ "LOCATION":"",
+ "HEALTHSTATUS":"1",
+ "MODEL":"",
+ "ID":"1","PARENTID":"",
+ "NETWORKNAME":"","TYPE":21},
+ {"PARENTTYPE":245,
+ "NAME":"ubuntu",
+ "DESCRIPTION":"",
+ "RUNNINGSTATUS":"1",
+ "IP":"","PARENTNAME":"",
+ "OPERATIONSYSTEM":"0",
+ "LOCATION":"",
+ "HEALTHSTATUS":"1",
+ "MODEL":"","ID":"2",
+ "PARENTID":"",
+ "NETWORKNAME":"","TYPE":21}]} """
+
+ if url == "host/associate":
+ if method is None:
+ data = """{"error":{"code":0}}"""
+ if method == "GET":
+ data = """{"error":{"code":0}}"""
+
+ if url == "iscsi_initiator/iqn.1993-08.debian:01:ec2bff7ac3a3":
+ data = """{"error":{"code":0},
+ "data":{"ID":"iqn.1993-08.win:01:ec2bff7ac3a3",
+ "NAME":"iqn.1993-08.win:01:ec2bff7ac3a3",
+ "ISFREE":"True"}}"""
+
+ if url == "iscsi_initiator/":
+ data = """{"error":{"code":0}}"""
+
+ if url == "mappingview":
+ self.termin_flag = True
+ if method is None:
+ data = """{"error":{"code":0},
+ "data":{"WORKMODE":"255",
+ "HEALTHSTATUS":"1",
+ "NAME":"mOWtSXnaQKi3hpB3tdFRIQ",
+ "RUNNINGSTATUS":"27","DESCRIPTION":"",
+ "ENABLEINBANDCOMMAND":"true",
+ "ID":"1","INBANDLUNWWN":"",
+ "TYPE":245}}"""
+
+ if method == "GET":
+ if self.other_flag:
+ data = """{"error":{"code":0},
+ "data":[{"WORKMODE":"255",
+ "HEALTHSTATUS":"1",
+ "NAME":"mOWtSXnaQKi3hpB3tdFRIQ",
+ "RUNNINGSTATUS":"27",
+ "DESCRIPTION":"",
+ "ENABLEINBANDCOMMAND":
+ "true","ID":"1",
+ "INBANDLUNWWN":"",
+ "TYPE":245},
+ {"WORKMODE":"255",
+ "HEALTHSTATUS":"1",
+ "NAME":"YheUoRwbSX2BxN767nvLSw",
+ "RUNNINGSTATUS":"27",
+ "DESCRIPTION":"",
+ "ENABLEINBANDCOMMAND":"true",
+ "ID":"2",
+ "INBANDLUNWWN":"",
+ "TYPE":245}]}"""
+ else:
+ data = """{"error":{"code":0},
+ "data":[{"WORKMODE":"255",
+ "HEALTHSTATUS":"1",
+ "NAME":"IexzQZJWSXuX2e9I7c8GNQ",
+ "RUNNINGSTATUS":"27",
+ "DESCRIPTION":"",
+ "ENABLEINBANDCOMMAND":"true",
+ "ID":"1",
+ "INBANDLUNWWN":"",
+ "TYPE":245},
+ {"WORKMODE":"255",
+ "HEALTHSTATUS":"1",
+ "NAME":"YheUoRwbSX2BxN767nvLSw",
+ "RUNNINGSTATUS":"27",
+ "DESCRIPTION":"",
+ "ENABLEINBANDCOMMAND":"true",
+ "ID":"2",
+ "INBANDLUNWWN":"",
+ "TYPE":245}]}"""
+
+ if url == "MAPPINGVIEW/CREATE_ASSOCIATE":
+ data = """{"error":{"code":0}}"""
+
+ if url == ("lun/associate?TYPE=11&"
+ "ASSOCIATEOBJTYPE=21&ASSOCIATEOBJID=0"):
+ data = """{"error":{"code":0}}"""
+
+ if url == "fc_initiator?ISFREE=true&range=[0-1000]":
+ data = """{"error":{"code":0},
+ "data":[{"HEALTHSTATUS":"1",
+ "NAME":"",
+ "MULTIPATHTYPE":"1",
+ "ISFREE":"true",
+ "RUNNINGSTATUS":"27",
+ "ID":"10000090fa0d6754",
+ "OPERATIONSYSTEM":"255",
+ "TYPE":223},
+ {"HEALTHSTATUS":"1",
+ "NAME":"",
+ "MULTIPATHTYPE":"1",
+ "ISFREE":"true",
+ "RUNNINGSTATUS":"27",
+ "ID":"10000090fa0d6755",
+ "OPERATIONSYSTEM":"255",
+ "TYPE":223}]}"""
+
+ if url == "host_link?INITIATOR_TYPE=223&INITIATOR_PORT_WWN="\
+ "10000090fa0d6754":
+
+ data = """{"error":{"code":0},
+ "data":[{"PARENTTYPE":21,
+ "TARGET_ID":"0000000000000000",
+ "INITIATOR_NODE_WWN":"20000090fa0d6754",
+ "INITIATOR_TYPE":"223",
+ "RUNNINGSTATUS":"27",
+ "PARENTNAME":"ubuntuc",
+ "INITIATOR_ID":"10000090fa0d6754",
+ "TARGET_PORT_WWN":"24000022a10a2a39",
+ "HEALTHSTATUS":"1",
+ "INITIATOR_PORT_WWN":"10000090fa0d6754",
+ "ID":"010000090fa0d675-0000000000110400",
+ "TARGET_NODE_WWN":"21000022a10a2a39",
+ "PARENTID":"1","CTRL_ID":"0",
+ "TYPE":255,"TARGET_TYPE":"212"}]}"""
+
+ if url == ("mappingview/associate?TYPE=245&"
+ "ASSOCIATEOBJTYPE=14&ASSOCIATEOBJID=0"):
+
+ data = """{"error":{"code":0},
+ "data":[{"ID":11,"NAME":"test"}]}"""
+
+ if url == ("mappingview/associate?TYPE=245&"
+ "ASSOCIATEOBJTYPE=256&ASSOCIATEOBJID=11"):
+
+ data = """{"error":{"code":0},
+ "data":[{"ID":11,"NAME":"test"}]}"""
+
+ if url == "fc_initiator/10000090fa0d6754":
+ data = """{"error":{"code":0}}"""
+
+ if url == "mappingview/REMOVE_ASSOCIATE":
+ data = """{"error":{"code":0}}"""
+ self.termin_flag = True
+
+ if url == "mappingview/1":
+ data = """{"error":{"code":0}}"""
+
+ if url == "ioclass":
+ data = """{"error":{"code":0},
+ "data":[{"NAME":"OpenStack_Qos_High",
+ "ID":"0",
+ "LUNLIST":"[]",
+ "TYPE":230}]}"""
+
+ if url == "ioclass/0":
+ data = """{"error":{"code":0}}"""
+
+ else:
+ data = """{"error":{"code":31755596}}"""
+
+ res_json = json.loads(data)
+ return res_json
+
+
+class FakeHVSiSCSIStorage(huawei_hvs.HuaweiHVSISCSIDriver):
+
+ def __init__(self, configuration):
+ super(FakeHVSiSCSIStorage, self).__init__(configuration)
+ self.configuration = configuration
+
+ def do_setup(self, context):
+ self.common = FakeHVSCommon(configuration=self.configuration)
+
+
+class FakeHVSFCStorage(huawei_hvs.HuaweiHVSFCDriver):
+
+ def __init__(self, configuration):
+ super(FakeHVSFCStorage, self).__init__(configuration)
+ self.configuration = configuration
+
+ def do_setup(self, context):
+ self.common = FakeHVSCommon(configuration=self.configuration)
+
+
+class HVSRESTiSCSIDriverTestCase(test.TestCase):
+ def setUp(self):
+ super(HVSRESTiSCSIDriverTestCase, self).setUp()
+ self.tmp_dir = tempfile.mkdtemp()
+ self.fake_conf_file = self.tmp_dir + '/cinder_huawei_conf.xml'
+ self.create_fake_conf_file()
+ self.configuration = mox.MockObject(conf.Configuration)
+ self.configuration.cinder_huawei_conf_file = self.fake_conf_file
+ self.configuration.append_config_values(mox.IgnoreArg())
+
+ self.stubs.Set(time, 'sleep', Fake_sleep)
+
+ self.driver = FakeHVSiSCSIStorage(configuration=self.configuration)
+ self.driver.do_setup({})
+ self.driver.common.test_normal = True
+
+ def tearDown(self):
+ if os.path.exists(self.fake_conf_file):
+ os.remove(self.fake_conf_file)
+ shutil.rmtree(self.tmp_dir)
+ super(HVSRESTiSCSIDriverTestCase, self).tearDown()
+
+ def test_log_in_success(self):
+ deviceid = self.driver.common.login()
+ self.assertNotEqual(deviceid, None)
+
+ def test_log_out_success(self):
+ self.driver.common.login()
+ self.driver.common.login_out()
+
+ def test_create_volume_success(self):
+ self.driver.common.login()
+ self.driver.create_volume(test_volume)
+ self.assertEqual(self.driver.common.lun_id, "0")
+
+ def test_create_snapshot_success(self):
+ self.driver.common.login()
+ self.driver.create_snapshot(test_volume)
+ self.assertEqual(self.driver.common.snapshot_id, "3")
+
+ def test_delete_volume_success(self):
+ self.driver.common.login()
+ self.driver.delete_volume(test_volume)
+ self.assertEqual(self.driver.common.lun_id, None)
+
+ def test_delete_snapshot_success(self):
+ self.driver.common.login()
+ self.driver.delete_snapshot(test_snap)
+ self.assertEqual(self.driver.common.snapshot_id, None)
+
+ def test_colone_volume_success(self):
+ self.driver.common.login()
+ self.driver.create_cloned_volume(test_volume, test_volume)
+ self.assertEqual(self.driver.common.luncopy_id, "7")
+
+ def test_create_volume_from_snapshot_success(self):
+ self.driver.common.login()
+ self.driver.create_volume_from_snapshot(test_volume, test_volume)
+ self.assertEqual(self.driver.common.luncopy_id, "7")
+
+ def test_initialize_connection_success(self):
+ self.driver.common.login()
+ conn = self.driver.initialize_connection(test_volume, FakeConnector)
+ self.assertEqual(conn['data']['target_lun'], 1)
+
+ def test_terminate_connection_success(self):
+ self.driver.common.login()
+ self.driver.terminate_connection(test_volume, FakeConnector)
+ self.assertEqual(self.driver.common.termin_flag, True)
+
+ def test_initialize_connection_no_view_success(self):
+ self.driver.common.login()
+ self.driver.common.other_flag = False
+ conn = self.driver.initialize_connection(test_volume, FakeConnector)
+ self.assertEqual(conn['data']['target_lun'], 1)
+
+ def test_terminate_connectio_no_view_success(self):
+ self.driver.common.login()
+ self.driver.common.other_flag = False
+ self.driver.terminate_connection(test_volume, FakeConnector)
+ self.assertEqual(self.driver.common.termin_flag, True)
+
+ def test_get_volume_stats(self):
+ self.driver.common.login()
+ status = self.driver.get_volume_stats()
+ self.assertNotEqual(status['free_capacity_gb'], None)
+
+ def test_create_snapshot_fail(self):
+ self.driver.common.login()
+ self.driver.common.test_normal = False
+ self.assertRaises(exception.CinderException,
+ self.driver.create_snapshot, test_volume)
+
+ def test_create_volume_fail(self):
+ self.driver.common.login()
+ self.driver.common.test_normal = False
+ self.assertRaises(exception.CinderException,
+ self.driver.create_volume, test_volume)
+
+ def test_delete_volume_fail(self):
+ self.driver.common.login()
+ self.driver.common.test_normal = False
+ self.assertRaises(exception.CinderException,
+ self.driver.delete_volume, test_volume)
+
+ def test_delete_snapshot_fail(self):
+ self.driver.common.login()
+ self.driver.common.test_normal = False
+ self.assertRaises(exception.CinderException,
+ self.driver.delete_snapshot, test_volume)
+
+ def test_initialize_connection_fail(self):
+ self.driver.common.login()
+ self.driver.common.test_normal = False
+ self.assertRaises(exception.CinderException,
+ self.driver.initialize_connection,
+ test_volume, FakeConnector)
+
+ def create_fake_conf_file(self):
+ doc = Document()
+
+ config = doc.createElement('config')
+ doc.appendChild(config)
+
+ storage = doc.createElement('Storage')
+ config.appendChild(storage)
+
+ product = doc.createElement('Product')
+ product_text = doc.createTextNode('HVS')
+ product.appendChild(product_text)
+ storage.appendChild(product)
+
+ protocal = doc.createElement('Protocol')
+ protocal_text = doc.createTextNode('iSCSI')
+ protocal.appendChild(protocal_text)
+ storage.appendChild(protocal)
+
+ username = doc.createElement('UserName')
+ username_text = doc.createTextNode('admin')
+ username.appendChild(username_text)
+ storage.appendChild(username)
+ userpassword = doc.createElement('UserPassword')
+ userpassword_text = doc.createTextNode('Admin@storage')
+ userpassword.appendChild(userpassword_text)
+ storage.appendChild(userpassword)
+ url = doc.createElement('HVSURL')
+ url_text = doc.createTextNode('http://100.115.10.69:8082/'
+ 'deviceManager/rest/')
+ url.appendChild(url_text)
+ storage.appendChild(url)
+ lun = doc.createElement('LUN')
+ config.appendChild(lun)
+ storagepool = doc.createElement('StoragePool')
+ pool_text = doc.createTextNode('OpenStack_Pool')
+ storagepool.appendChild(pool_text)
+ lun.appendChild(storagepool)
+
+ luntype = doc.createElement('LUNType')
+ luntype_text = doc.createTextNode('Thick')
+ luntype.appendChild(luntype_text)
+ lun.appendChild(luntype)
+
+ writetype = doc.createElement('WriteType')
+ writetype_text = doc.createTextNode('1')
+ writetype.appendChild(writetype_text)
+ lun.appendChild(writetype)
+
+ prefetchType = doc.createElement('Prefetch')
+ prefetchType.setAttribute('Type', '2')
+ prefetchType.setAttribute('Value', '20')
+ lun.appendChild(prefetchType)
+
+ iscsi = doc.createElement('iSCSI')
+ config.appendChild(iscsi)
+ defaulttargetip = doc.createElement('DefaultTargetIP')
+ defaulttargetip_text = doc.createTextNode('100.115.10.68')
+ defaulttargetip.appendChild(defaulttargetip_text)
+ iscsi.appendChild(defaulttargetip)
+
+ initiator = doc.createElement('Initiator')
+ initiator.setAttribute('Name', 'iqn.1993-08.debian:01:ec2bff7ac3a3')
+ initiator.setAttribute('TargetIP', '100.115.10.68')
+ iscsi.appendChild(initiator)
+
+ newefile = open(self.fake_conf_file, 'w')
+ newefile.write(doc.toprettyxml(indent=''))
+ newefile.close()
+
+
+class HVSRESTFCDriverTestCase(test.TestCase):
+ def setUp(self):
+ super(HVSRESTFCDriverTestCase, self).setUp()
+ self.tmp_dir = tempfile.mkdtemp()
+ self.fake_conf_file = self.tmp_dir + '/cinder_huawei_conf.xml'
+ self.create_fake_conf_file()
+ self.configuration = mox.MockObject(conf.Configuration)
+ self.configuration.cinder_huawei_conf_file = self.fake_conf_file
+ self.configuration.append_config_values(mox.IgnoreArg())
+
+ self.stubs.Set(time, 'sleep', Fake_sleep)
+
+ self.driver = FakeHVSFCStorage(configuration=self.configuration)
+ self.driver.do_setup({})
+ self.driver.common.test_normal = True
+
+ def tearDown(self):
+ if os.path.exists(self.fake_conf_file):
+ os.remove(self.fake_conf_file)
+ shutil.rmtree(self.tmp_dir)
+ super(HVSRESTFCDriverTestCase, self).tearDown()
+
+ def test_log_in_Success(self):
+ deviceid = self.driver.common.login()
+ self.assertNotEqual(deviceid, None)
+
+ def test_create_volume_success(self):
+ self.driver.common.login()
+ self.driver.create_volume(test_volume)
+ self.assertEqual(self.driver.common.lun_id, "0")
+
+ def test_create_snapshot_success(self):
+ self.driver.common.login()
+ self.driver.create_snapshot(test_volume)
+ self.assertEqual(self.driver.common.snapshot_id, "3")
+
+ def test_delete_volume_success(self):
+ self.driver.common.login()
+ self.driver.delete_volume(test_volume)
+ self.assertEqual(self.driver.common.lun_id, None)
+
+ def test_delete_snapshot_success(self):
+ self.driver.common.login()
+ self.driver.delete_snapshot(test_snap)
+ self.assertEqual(self.driver.common.snapshot_id, None)
+
+ def test_colone_volume_success(self):
+ self.driver.common.login()
+ self.driver.create_cloned_volume(test_volume, test_volume)
+ self.assertEqual(self.driver.common.luncopy_id, "7")
+
+ def test_create_volume_from_snapshot_success(self):
+ self.driver.common.login()
+ self.driver.create_volume_from_snapshot(test_volume, test_volume)
+ self.assertEqual(self.driver.common.luncopy_id, "7")
+
+ def test_initialize_connection_success(self):
+ self.driver.common.login()
+ conn = self.driver.initialize_connection(test_volume, FakeConnector)
+ self.assertEqual(conn['data']['target_lun'], 1)
+
+ def test_terminate_connection_success(self):
+ self.driver.common.login()
+ self.driver.terminate_connection(test_volume, FakeConnector)
+ self.assertEqual(self.driver.common.termin_flag, True)
+
+ def test_initialize_connection_no_view_success(self):
+ self.driver.common.login()
+ self.driver.common.other_flag = False
+ conn = self.driver.initialize_connection(test_volume, FakeConnector)
+ self.assertEqual(conn['data']['target_lun'], 1)
+
+ def test_terminate_connection_no_viewn_success(self):
+ self.driver.common.login()
+ self.driver.common.other_flag = False
+ self.driver.terminate_connection(test_volume, FakeConnector)
+ self.assertEqual(self.driver.common.termin_flag, True)
+
+ def test_get_volume_stats(self):
+ self.driver.common.login()
+ status = self.driver.get_volume_stats()
+ self.assertNotEqual(status['free_capacity_gb'], None)
+
+ def test_create_snapshot_fail(self):
+ self.driver.common.login()
+ self.driver.common.test_normal = False
+ self.assertRaises(exception.CinderException,
+ self.driver.create_snapshot, test_volume)
+
+ def test_create_volume_fail(self):
+ self.driver.common.login()
+ self.driver.common.test_normal = False
+ self.assertRaises(exception.CinderException,
+ self.driver.create_volume, test_volume)
+
+ def test_delete_volume_fail(self):
+ self.driver.common.login()
+ self.driver.common.test_normal = False
+ self.assertRaises(exception.CinderException,
+ self.driver.delete_volume, test_volume)
+
+ def test_delete_snapshot_fail(self):
+ self.driver.common.login()
+ self.driver.common.test_normal = False
+ self.assertRaises(exception.CinderException,
+ self.driver.delete_snapshot, test_volume)
+
+ def create_fake_conf_file(self):
+ doc = Document()
+
+ config = doc.createElement('config')
+ doc.appendChild(config)
+
+ storage = doc.createElement('Storage')
+ config.appendChild(storage)
+
+ product = doc.createElement('Product')
+ product_text = doc.createTextNode('HVS')
+ product.appendChild(product_text)
+ storage.appendChild(product)
+
+ protocal = doc.createElement('Protocol')
+ protocal_text = doc.createTextNode('FC')
+ protocal.appendChild(protocal_text)
+ storage.appendChild(protocal)
+
+ username = doc.createElement('UserName')
+ username_text = doc.createTextNode('admin')
+ username.appendChild(username_text)
+ storage.appendChild(username)
+
+ userpassword = doc.createElement('UserPassword')
+ userpassword_text = doc.createTextNode('Admin@storage')
+ userpassword.appendChild(userpassword_text)
+ storage.appendChild(userpassword)
+ url = doc.createElement('HVSURL')
+ url_text = doc.createTextNode('http://100.115.10.69:8082/'
+ 'deviceManager/rest/')
+ url.appendChild(url_text)
+ storage.appendChild(url)
+
+ lun = doc.createElement('LUN')
+ config.appendChild(lun)
+ storagepool = doc.createElement('StoragePool')
+ pool_text = doc.createTextNode('OpenStack_Pool')
+ storagepool.appendChild(pool_text)
+ lun.appendChild(storagepool)
+
+ luntype = doc.createElement('LUNType')
+ luntype_text = doc.createTextNode('Thick')
+ luntype.appendChild(luntype_text)
+ lun.appendChild(luntype)
+
+ writetype = doc.createElement('WriteType')
+ writetype_text = doc.createTextNode('1')
+ writetype.appendChild(writetype_text)
+ lun.appendChild(writetype)
+
+ prefetchType = doc.createElement('Prefetch')
+ prefetchType.setAttribute('Type', '2')
+ prefetchType.setAttribute('Value', '20')
+ lun.appendChild(prefetchType)
+
+ newfile = open(self.fake_conf_file, 'w')
+ newfile.write(doc.toprettyxml(indent=''))
+ newfile.close()
--- /dev/null
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright (c) 2013 Huawei Technologies Co., Ltd.
+# Copyright (c) 2013 OpenStack LLC.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+"""Common class for Huawei HVS storage drivers."""
+
+import base64
+import cookielib
+import json
+import time
+import urllib2
+import uuid
+
+from xml.etree import ElementTree as ET
+
+from cinder import context
+from cinder import exception
+from cinder.openstack.common import excutils
+from cinder.openstack.common import log as logging
+from cinder import units
+from cinder import utils
+from cinder.volume import volume_types
+
+
+LOG = logging.getLogger(__name__)
+
+QOS_KEY = ["Qos-high", "Qos-normal", "Qos-low"]
+TIER_KEY = ["Tier-high", "Tier-normal", "Tier-low"]
+
+
+class HVSCommon():
+ """Common class for Huawei OceanStor HVS storage system."""
+
+ def __init__(self, configuration):
+ self.configuration = configuration
+ self.cookie = cookielib.CookieJar()
+ self.url = None
+
+ def call(self, url=False, data=None, method=None):
+ """Send requests to HVS server.
+
+ Send HTTPS call, get response in JSON.
+ Convert response into Python Object and return it.
+ """
+
+ LOG.debug(_('HVS Request URL: %(url)s') % {'url': url})
+ LOG.debug(_('HVS Request Data: %(data)s') % {'data': data})
+
+ headers = {"Connection": "keep-alive",
+ "Content-Type": "application/json"}
+ opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(self.cookie))
+ urllib2.install_opener(opener)
+
+ try:
+ urllib2.socket.setdefaulttimeout(720)
+ req = urllib2.Request(url, data, headers)
+ if method:
+ req.get_method = lambda: method
+ res = urllib2.urlopen(req).read().decode("utf-8")
+ LOG.debug(_('HVS Response Data: %(res)s') % {'res': res})
+ except Exception as err:
+ err_msg = _('Bad reponse from server: %s') % err
+ LOG.error(err_msg)
+ raise err
+
+ try:
+ res_json = json.loads(res)
+ except Exception:
+ raise exception.CinderException(_('JSON transfer Error'))
+
+ return res_json
+
+ def login(self):
+ """Log in HVS array.
+
+ If login failed, the driver will sleep 30's to avoid frequent
+ connection to the server.
+ """
+
+ login_info = self._get_login_info()
+ url = login_info['HVSURL'] + "xx/sessions"
+ data = json.dumps({"username": login_info['UserName'],
+ "password": login_info['UserPassword'],
+ "scope": "0"})
+ result = self.call(url, data)
+ if (result['error']['code'] != 0) or ("data" not in result):
+ time.sleep(30)
+ msg = _("Login error, reason is %s") % result
+ raise exception.CinderException(msg)
+
+ deviceid = result['data']['deviceid']
+ self.url = login_info['HVSURL'] + deviceid
+ return deviceid
+
+ def _init_tier_parameters(self, parameters, lunparam):
+ """Init the LUN parameters through the volume type "performance"."""
+ if "tier" in parameters:
+ smart_tier = parameters['tier']
+ if smart_tier == 'Tier_high':
+ lunparam['INITIALDISTRIBUTEPOLICY'] = "1"
+ elif smart_tier == 'Tier_normal':
+ lunparam['INITIALDISTRIBUTEPOLICY'] = "2"
+ elif smart_tier == 'Tier_low':
+ lunparam['INITIALDISTRIBUTEPOLICY'] = "3"
+ else:
+ lunparam['INITIALDISTRIBUTEPOLICY'] = "2"
+
+ def _init_lun_parameters(self, name, parameters):
+ """Init basic LUN parameters """
+ lunparam = {"TYPE": "11",
+ "NAME": name,
+ "PARENTTYPE": "216",
+ "PARENTID": parameters['pool_id'],
+ "DESCRIPTION": "",
+ "ALLOCTYPE": parameters['LUNType'],
+ "CAPACITY": parameters['volume_size'],
+ "WRITEPOLICY": parameters['WriteType'],
+ "MIRRORPOLICY": parameters['MirrorSwitch'],
+ "PREFETCHPOLICY": parameters['PrefetchType'],
+ "PREFETCHVALUE": parameters['PrefetchValue'],
+ "DATATRANSFERPOLICY": "1",
+ "INITIALDISTRIBUTEPOLICY": "0"}
+
+ return lunparam
+
+ def _init_qos_parameters(self, parameters, lun_param):
+ """Init the LUN parameters through the volume type "Qos-xxx"."""
+ policy_id = None
+ policy_info = None
+ if "qos" in parameters:
+ policy_info = self._find_qos_policy_info(parameters['qos'])
+ if policy_info:
+ policy_id = policy_info['ID']
+
+ lun_param['IOClASSID'] = policy_info['ID']
+ qos_level = parameters['qos_level']
+ if qos_level == 'Qos-high':
+ lun_param['IOPRIORITY'] = "3"
+ elif qos_level == 'Qos-normal':
+ lun_param['IOPRIORITY'] = "2"
+ elif qos_level == 'Qos-low':
+ lun_param['IOPRIORITY'] = "1"
+ else:
+ lun_param['IOPRIORITY'] = "2"
+
+ return (policy_info, policy_id)
+
+ def _assert_rest_result(self, result, err_str):
+ error_code = result['error']['code']
+ if error_code != 0:
+ msg = _('%(err)s\nresult: %(res)s') % {'err': err_str,
+ 'res': result}
+ LOG.error(msg)
+ raise exception.CinderException(msg)
+
+ def _create_volume(self, lun_param):
+ url = self.url + "/lun"
+ data = json.dumps(lun_param)
+ result = self.call(url, data)
+ self._assert_rest_result(result, 'create volume error')
+
+ if "data" in result:
+ return result['data']['ID']
+ else:
+ msg = _('create volume error: %(err)s') % {'err': result}
+ raise exception.CinderException(msg)
+
+ def create_volume(self, volume):
+ volume_name = self._encode_name(volume['id'])
+ config_params = self._parse_volume_type(volume)
+
+ # Prepare lun parameters, including qos parameter and tier parameter.
+ lun_param = self._init_lun_parameters(volume_name, config_params)
+ self._init_tier_parameters(config_params, lun_param)
+ policy_info, policy_id = self._init_qos_parameters(config_params,
+ lun_param)
+
+ # Create LUN in array
+ lunid = self._create_volume(lun_param)
+
+ # Enable qos, need to add lun into qos policy
+ if "qos" in config_params:
+ lun_list = policy_info['LUNLIST']
+ lun_list.append(lunid)
+ if policy_id:
+ self._update_qos_policy_lunlist(lun_list, policy_id)
+ else:
+ LOG.warn(_("Can't find the Qos policy in array"))
+
+ # Create lun group and add LUN into to lun group
+ lungroup_id = self._create_lungroup(volume_name)
+ self._associate_lun_to_lungroup(lungroup_id, lunid)
+
+ return lunid
+
+ def _get_volume_size(self, poolinfo, volume):
+ """Calculate the volume size.
+
+ We should devide the given volume size by 512 for the HVS system
+ caculates volume size with sectors, which is 512 bytes.
+ """
+
+ volume_size = units.GiB / 512 # 1G
+ if int(volume['size']) != 0:
+ volume_size = int(volume['size']) * units.GiB / 512
+
+ return volume_size
+
+ def delete_volume(self, volume):
+ """Delete a volume.
+
+ Three steps: first, remove associate from lun group.
+ Second, remove associate from qos policy. Third, remove the lun.
+ """
+
+ name = self._encode_name(volume['id'])
+ lun_id = self._get_volume_by_name(name)
+ lungroup_id = self._find_lungroup(name)
+
+ if lun_id and lungroup_id:
+ self._delete_lun_from_qos_policy(volume, lun_id)
+ self._delete_associated_lun_from_lungroup(lungroup_id, lun_id)
+ self._delete_lungroup(lungroup_id)
+ self._delete_lun(lun_id)
+ else:
+ LOG.warn(_("Can't find lun or lun goup in array"))
+
+ def _delete_lun_from_qos_policy(self, volume, lun_id):
+ """Remove lun from qos policy."""
+ parameters = self._parse_volume_type(volume)
+
+ if "qos" in parameters:
+ qos = parameters['qos']
+ policy_info = self._find_qos_policy_info(qos)
+ if policy_info:
+ lun_list = policy_info['LUNLIST']
+ for item in lun_list:
+ if lun_id == item:
+ lun_list.remove(item)
+ self._update_qos_policy_lunlist(lun_list, policy_info['ID'])
+
+ def _delete_lun(self, lun_id):
+ url = self.url + "/lun/" + lun_id
+ data = json.dumps({"TYPE": "11",
+ "ID": lun_id})
+ result = self.call(url, data, "DELETE")
+ self._assert_rest_result(result, 'delete lun error')
+
+ def _read_xml(self):
+ """Open xml file and parse the content."""
+ filename = self.configuration.cinder_huawei_conf_file
+ try:
+ tree = ET.parse(filename)
+ root = tree.getroot()
+ except Exception as err:
+ LOG.error(_('_read_xml:%s') % err)
+ raise exception.VolumeBackendAPIException(data=err)
+ return root
+
+ def _encode_name(self, name):
+ uuid_str = name.replace("-", "")
+ vol_uuid = uuid.UUID('urn:uuid:%s' % uuid_str)
+ vol_encoded = base64.urlsafe_b64encode(vol_uuid.bytes)
+ newuuid = vol_encoded.replace("=", "")
+ return newuuid
+
+ def _find_pool_info(self):
+ root = self._read_xml()
+ pool_name = root.findtext('LUN/StoragePool')
+ if not pool_name:
+ err_msg = _("Invalid resource pool: %s") % pool_name
+ raise exception.InvalidInput(err_msg)
+
+ url = self.url + "/storagepool"
+ result = self.call(url, None)
+ self._assert_rest_result(result, 'Query resource pool error')
+
+ poolinfo = {}
+ if "data" in result:
+ for item in result['data']:
+ if pool_name.strip() == item['NAME']:
+ poolinfo['ID'] = item['ID']
+ poolinfo['CAPACITY'] = item['USERFREECAPACITY']
+ poolinfo['TOTALCAPACITY'] = item['USERTOTALCAPACITY']
+ break
+
+ if not poolinfo:
+ msg = (_('Get pool info error, pool name is:%s') % pool_name)
+ raise exception.CinderException(msg)
+
+ return poolinfo
+
+ def _get_volume_by_name(self, name):
+ url = self.url + "/lun"
+ result = self.call(url, None, "GET")
+ self._assert_rest_result(result, 'Get volume by name error!')
+
+ volume_id = None
+ if "data" in result:
+ for item in result['data']:
+ if name == item['NAME']:
+ volume_id = item['ID']
+ break
+ return volume_id
+
+ def _active_snapshot(self, snapshot_id):
+ activeurl = self.url + "/snapshot/activate"
+ data = json.dumps({"SNAPSHOTLIST": [snapshot_id]})
+ result = self.call(activeurl, data)
+ self._assert_rest_result(result, 'Active snapshot error.')
+
+ def _create_snapshot(self, snapshot):
+ snapshot_name = self._encode_name(snapshot['id'])
+ volume_name = self._encode_name(snapshot['volume_id'])
+
+ LOG.debug(_('create_snapshot:snapshot name:%(snapshot)s, '
+ 'volume name:%(volume)s.')
+ % {'snapshot': snapshot_name,
+ 'volume': volume_name})
+
+ lun_id = self._get_volume_by_name(volume_name)
+ url = self.url + "/snapshot"
+ data = json.dumps({"TYPE": "27",
+ "NAME": snapshot_name,
+ "PARENTTYPE": "11",
+ "PARENTID": lun_id})
+ result = self.call(url, data)
+ self._assert_rest_result(result, 'Create snapshot error.')
+
+ if 'data' not in result:
+ raise exception.CinderException(_('Create snapshot error.'))
+
+ return result['data']['ID']
+
+ def create_snapshot(self, snapshot):
+ snapshot_id = self._create_snapshot(snapshot)
+ self._active_snapshot(snapshot_id)
+
+ def _stop_snapshot(self, snapshot):
+ snapshot_name = self._encode_name(snapshot['id'])
+ volume_name = self._encode_name(snapshot['volume_id'])
+
+ LOG.debug(_('_stop_snapshot:snapshot name:%(snapshot)s, '
+ 'volume name:%(volume)s.')
+ % {'snapshot': snapshot_name,
+ 'volume': volume_name})
+
+ snapshotid = self._get_snapshotid_by_name(snapshot_name)
+ stopdata = json.dumps({"ID": snapshotid})
+ url = self.url + "/snapshot/stop"
+ result = self.call(url, stopdata, "PUT")
+ self._assert_rest_result(result, 'Stop snapshot error.')
+
+ return snapshotid
+
+ def _delete_snapshot(self, snapshotid):
+ url = self.url + "/snapshot/%s" % snapshotid
+ data = json.dumps({"TYPE": "27", "ID": snapshotid})
+ result = self.call(url, data, "DELETE")
+ self._assert_rest_result(result, 'Delete snapshot error.')
+
+ def delete_snapshot(self, snapshot):
+ snapshotid = self._stop_snapshot(snapshot)
+ self._delete_snapshot(snapshotid)
+
+ def _get_snapshotid_by_name(self, name):
+ url = self.url + "/snapshot"
+ data = json.dumps({"TYPE": "27"})
+ result = self.call(url, data, "GET")
+ self._assert_rest_result(result, 'Get snapshot id error.')
+
+ snapshot_id = None
+ if "data" in result:
+ for item in result['data']:
+ if name == item['NAME']:
+ snapshot_id = item['ID']
+ break
+ return snapshot_id
+
+ def _copy_volume(self, volume, copy_name, src_lun, tgt_lun):
+ luncopy_id = self._create_luncopy(copy_name,
+ src_lun, tgt_lun)
+ try:
+ self._start_luncopy(luncopy_id)
+ self._wait_for_luncopy(luncopy_id)
+ except Exception:
+ with excutils.save_and_reraise_exception():
+ self._delete_luncopy(luncopy_id)
+ self.delete_volume(volume)
+
+ self._delete_luncopy(luncopy_id)
+
+ def create_volume_from_snapshot(self, volume, snapshot):
+ """Create a volume from a snapshot.
+
+ We use LUNcopy to copy a new volume from snapshot.
+ The time needed increases as volume size does.
+ """
+
+ snapshot_name = self._encode_name(snapshot['id'])
+ src_lun_id = self._get_snapshotid_by_name(snapshot_name)
+ tgt_lun_id = self.create_volume(volume)
+ luncopy_name = self._encode_name(volume['id'])
+
+ self._copy_volume(volume, luncopy_name, src_lun_id, tgt_lun_id)
+
+ def create_cloned_volume(self, volume, src_vref):
+ """Clone a new volume from an existing volume."""
+ volume_name = self._encode_name(src_vref['id'])
+ src_lun_id = self._get_volume_by_name(volume_name)
+ tgt_lun_id = self.create_volume(volume)
+ luncopy_name = self._encode_name(volume['id'])
+
+ self._copy_volume(volume, luncopy_name, src_lun_id, tgt_lun_id)
+
+ def _create_luncopy(self, luncopyname, srclunid, tgtlunid):
+ """Create a luncopy."""
+ url = self.url + "/luncopy"
+ data = json.dumps({"TYPE": "219",
+ "NAME": luncopyname,
+ "DESCRIPTION": luncopyname,
+ "COPYSPEED": "2",
+ "LUNCOPYTYPE": "1",
+ "SOURCELUN": ("INVALID;%s;INVALID;INVALID;INVALID"
+ % srclunid),
+ "TARGETLUN": ("INVALID;%s;INVALID;INVALID;INVALID"
+ % tgtlunid)})
+ result = self.call(url, data)
+ self._assert_rest_result(result, 'Create lun copy error.')
+
+ if "data" not in result:
+ raise exception.CinderException(_('Create luncopy error.'))
+
+ return result['data']['ID']
+
+ def _add_host_into_hostgroup(self, host_name):
+ """Associate host to hostgroup.
+
+ If host group doesn't exist, create one.
+
+ """
+
+ hostgroup_id = self._find_hostgroup(host_name)
+ if hostgroup_id is None:
+ hostgroup_id = self._create_hostgroup(host_name)
+
+ hostid = self._find_host(host_name)
+ if hostid is None:
+ hostid = self._add_host(host_name)
+ self._associate_host_to_hostgroup(hostgroup_id, hostid)
+
+ return hostid, hostgroup_id
+
+ def _mapping_hostgroup_and_lungroup(self, volume_name,
+ hostgroup_id, host_id):
+ """Add hostgroup and lungroup to view."""
+ lungroup_id = self._find_lungroup(volume_name)
+ lun_id = self._get_volume_by_name(volume_name)
+ view_id = self._find_mapping_view(volume_name)
+
+ LOG.debug(_('_mapping_hostgroup_and_lungroup: lun_group: %(lun_group)s'
+ 'view_id: %(view_id)s')
+ % {'lun_group': str(lungroup_id),
+ 'view_id': str(view_id)})
+
+ try:
+ if view_id is None:
+ view_id = self._add_mapping_view(volume_name, host_id)
+ self._associate_hostgroup_to_view(view_id, hostgroup_id)
+ self._associate_lungroup_to_view(view_id, lungroup_id)
+ else:
+ if not self._hostgroup_associated(view_id, hostgroup_id):
+ self._associate_hostgroup_to_view(view_id, hostgroup_id)
+ if not self._lungroup_associated(view_id, lungroup_id):
+ self._associate_lungroup_to_view(view_id, lungroup_id)
+
+ except Exception:
+ with excutils.save_and_reraise_exception():
+ self._delete_hostgoup_mapping_view(view_id, hostgroup_id)
+ self._delete_lungroup_mapping_view(view_id, lungroup_id)
+ self._delete_mapping_view(view_id)
+
+ return lun_id
+
+ def _ensure_initiator_added(self, initiator_name, hostid):
+ added = self._initiator_is_added_to_array(initiator_name)
+ if not added:
+ self._add_initiator_to_array(initiator_name)
+ else:
+ if self._is_initiator_associated_to_host(initiator_name) is False:
+ self._associate_initiator_to_host(initiator_name, hostid)
+
+ def initialize_connection_iscsi(self, volume, connector):
+ """Map a volume to a host and return target iSCSI information."""
+ initiator_name = connector['initiator']
+ host_name = connector['host']
+ volume_name = self._encode_name(volume['id'])
+
+ LOG.debug(_('initiator name:%(initiator_name)s, '
+ 'volume name:%(volume)s.')
+ % {'initiator_name': initiator_name,
+ 'volume': volume_name})
+ (iscsi_iqn, target_ip) = self._get_iscsi_params(connector)
+
+ #create host_goup if not exist
+ hostid, hostgroup_id = self._add_host_into_hostgroup(host_name)
+ self._ensure_initiator_added(initiator_name, hostid)
+
+ # Mapping lungooup and hostgoup to view
+ lun_id = self._mapping_hostgroup_and_lungroup(volume_name,
+ hostgroup_id, hostid)
+ hostlunid = self._find_host_lun_id(hostid, lun_id)
+ LOG.debug(_("host lun id is %s") % hostlunid)
+
+ # Return iSCSI properties.
+ properties = {}
+ properties['target_discovered'] = False
+ properties['target_portal'] = ('%s:%s' % (target_ip, '3260'))
+ properties['target_iqn'] = iscsi_iqn
+ properties['target_lun'] = int(hostlunid)
+ properties['volume_id'] = volume['id']
+
+ return {'driver_volume_type': 'iscsi', 'data': properties}
+
+ def initialize_connection_fc(self, volume, connector):
+ wwns = connector['wwpns']
+ host_name = connector['host']
+ volume_name = self._encode_name(volume['id'])
+
+ LOG.debug(_('initiator name:%(initiator_name)s, '
+ 'volume name:%(volume)s.')
+ % {'initiator_name': wwns,
+ 'volume': volume_name})
+
+ # Create host goup if not exist
+ hostid, hostgroup_id = self._add_host_into_hostgroup(host_name)
+
+ free_wwns = self._get_connected_free_wwns()
+ LOG.debug(_("the free wwns %s") % free_wwns)
+ for wwn in wwns:
+ if wwn in free_wwns:
+ self._add_fc_port_to_host(hostid, wwn)
+
+ lun_id = self._mapping_hostgroup_and_lungroup(volume_name,
+ hostgroup_id, hostid)
+ host_lun_id = self._find_host_lun_id(hostid, lun_id)
+
+ tgt_port_wwns = []
+ for wwn in wwns:
+ tgtwwpns = self._get_fc_target_wwpns(wwn)
+ if tgtwwpns:
+ tgt_port_wwns.append(tgtwwpns)
+
+ # Return FC properties.
+ properties = {}
+ properties['target_discovered'] = False
+ properties['target_wwn'] = tgt_port_wwns
+ properties['target_lun'] = int(host_lun_id)
+ properties['volume_id'] = volume['id']
+ LOG.debug(_("the fc server properties is:%s") % properties)
+
+ return {'driver_volume_type': 'fibre_channel',
+ 'data': properties}
+
+ def _get_iscsi_tgt_port(self):
+ url = self.url + "/iscsidevicename"
+ result = self.call(url, None)
+ msg = 'Get iSCSI target port error.'
+ self._assert_rest_result(result, msg)
+
+ if "data" not in result:
+ raise exception.CinderException(_('%s') % msg)
+
+ return result['data'][0]['CMO_ISCSI_DEVICE_NAME']
+
+ def _find_hostgroup(self, groupname):
+ """Get the given hostgroup id."""
+ url = self.url + "/hostgroup"
+ result = self.call(url, None, "GET")
+ self._assert_rest_result(result, 'Get host group information error.')
+
+ host_group_id = None
+ if "data" in result:
+ for item in result['data']:
+ if groupname == item['NAME']:
+ host_group_id = item['ID']
+ break
+ return host_group_id
+
+ def _find_lungroup(self, lungroupname):
+ """Get the given hostgroup id."""
+ url = self.url + "/lungroup"
+ result = self.call(url, None, "GET")
+ self._assert_rest_result(result, 'Get lun group information error.')
+
+ lun_group_id = None
+ if 'data' in result:
+ for item in result['data']:
+ if lungroupname == item['NAME']:
+ lun_group_id = item['ID']
+ break
+ return lun_group_id
+
+ def _create_hostgroup(self, hostgroupname):
+ url = self.url + "/hostgroup"
+ data = json.dumps({"TYPE": "14", "NAME": hostgroupname})
+ result = self.call(url, data)
+ msg = 'Create host group error.'
+ self._assert_rest_result(result, msg)
+
+ if "data" not in result:
+ raise exception.CinderException(_('%s') % msg)
+
+ return result['data']['ID']
+
+ def _create_lungroup(self, lungroupname):
+ url = self.url + "/lungroup"
+ data = json.dumps({"DESCRIPTION": lungroupname,
+ "NAME": lungroupname})
+ result = self.call(url, data)
+ msg = 'Create lun group error.'
+ self._assert_rest_result(result, msg)
+
+ if "data" not in result:
+ raise exception.CinderException(_('%s') % msg)
+
+ return result['data']['ID']
+
+ def _delete_lungroup(self, lungroupid):
+ url = self.url + "/LUNGroup/" + lungroupid
+ result = self.call(url, None, "DELETE")
+ self._assert_rest_result(result, 'Delete lun group error.')
+
+ def _lungroup_associated(self, viewid, lungroupid):
+ url_subfix = ("/mappingview/associate?TYPE=245&"
+ "ASSOCIATEOBJTYPE=256&ASSOCIATEOBJID=%s" % lungroupid)
+ url = self.url + url_subfix
+ result = self.call(url, None, "GET")
+ self._assert_rest_result(result, 'Check lun group associated error.')
+
+ if "data" in result:
+ for item in result['data']:
+ if viewid == item['ID']:
+ return True
+ return False
+
+ def _hostgroup_associated(self, viewid, hostgroupid):
+ url_subfix = ("/mappingview/associate?TYPE=245&"
+ "ASSOCIATEOBJTYPE=14&ASSOCIATEOBJID=%s" % hostgroupid)
+ url = self.url + url_subfix
+ result = self.call(url, None, "GET")
+ self._assert_rest_result(result, 'Check host group associated error.')
+
+ if "data" in result:
+ for item in result['data']:
+ if viewid == item['ID']:
+ return True
+ return False
+
+ def _find_host_lun_id(self, hostid, lunid):
+ time.sleep(2)
+ url = self.url + ("/lun/associate?TYPE=11&ASSOCIATEOBJTYPE=21"
+ "&ASSOCIATEOBJID=%s" % (hostid))
+ result = self.call(url, None, "GET")
+ self._assert_rest_result(result, 'Find host lun id error.')
+
+ host_lun_id = 1
+ if "data" in result:
+ for item in result['data']:
+ if lunid == item['ID']:
+ associate_data = result['data'][0]['ASSOCIATEMETADATA']
+ try:
+ hostassoinfo = json.loads(associate_data)
+ host_lun_id = hostassoinfo['HostLUNID']
+ break
+ except Exception:
+ msg = _("_find_host_lun_id transfer data error! ")
+ raise exception.CinderException(msg)
+ return host_lun_id
+
+ def _find_host(self, hostname):
+ """Get the given host ID."""
+ url = self.url + "/host"
+ data = json.dumps({"TYPE": "21"})
+ result = self.call(url, data, "GET")
+ self._assert_rest_result(result, 'Find host in host group error.')
+
+ host_id = None
+ if "data" in result:
+ for item in result['data']:
+ if hostname == item['NAME']:
+ host_id = item['ID']
+ break
+ return host_id
+
+ def _add_host(self, hostname):
+ """Add a new host."""
+ url = self.url + "/host"
+ data = json.dumps({"TYPE": "21",
+ "NAME": hostname,
+ "OPERATIONSYSTEM": "0"})
+ result = self.call(url, data)
+ self._assert_rest_result(result, 'Add new host error.')
+
+ if "data" in result:
+ return result['data']['ID']
+ else:
+ return None
+
+ def _associate_host_to_hostgroup(self, hostgroupid, hostid):
+ url = self.url + "/host/associate"
+ data = json.dumps({"ID": hostgroupid,
+ "ASSOCIATEOBJTYPE": "21",
+ "ASSOCIATEOBJID": hostid})
+
+ result = self.call(url, data)
+ self._assert_rest_result(result, 'Associate host to host group error.')
+
+ def _associate_lun_to_lungroup(self, lungroupid, lunid):
+ """Associate lun to lun group."""
+ url = self.url + "/lungroup/associate"
+ data = json.dumps({"ID": lungroupid,
+ "ASSOCIATEOBJTYPE": "11",
+ "ASSOCIATEOBJID": lunid})
+ result = self.call(url, data)
+ self._assert_rest_result(result, 'Associate lun to lun group error.')
+
+ def _delete_associated_lun_from_lungroup(self, lungroupid, lunid):
+ """Remove lun from lun group."""
+
+ url = self.url + ("/lungroup/associate?ID=%s"
+ "&ASSOCIATEOBJTYPE=11&ASSOCIATEOBJID=%s"
+ % (lungroupid, lunid))
+
+ result = self.call(url, None, 'DELETE')
+ self._assert_rest_result(result,
+ 'Delete associated lun from lun group error')
+
+ def _initiator_is_added_to_array(self, ininame):
+ """Check whether the initiator is already added in array."""
+ url = self.url + "/iscsi_initiator/" + ininame
+ data = json.dumps({"TYPE": "222", "ID": ininame})
+ result = self.call(url, data, "GET")
+ self._assert_rest_result(result,
+ 'Check initiator added to array error.')
+
+ if "data" in result and result['data']['ID']:
+ return True
+ else:
+ return False
+
+ def _is_initiator_associated_to_host(self, ininame):
+ """Check whether the initiator is associated to the host."""
+ url = self.url + "/iscsi_initiator/" + ininame
+ data = json.dumps({"TYPE": "222", "ID": ininame})
+ result = self.call(url, data, "GET")
+ self._assert_rest_result(result,
+ 'Check initiator associated to host error.')
+
+ if "data" in result and result['data']['ISFREE'] == "true":
+ return True
+ else:
+ return False
+
+ def _add_initiator_to_array(self, ininame):
+ """Add a new initiator to storage device."""
+ url = self.url + "/iscsi_initiator/"
+ data = json.dumps({"TYPE": "222",
+ "ID": ininame,
+ "USECHAP": "False"})
+ result = self.call(url, data)
+ self._assert_rest_result(result, 'Add initiator to array error.')
+
+ def _associate_initiator_to_host(self, ininame, hostid):
+ """Associate initiator with the host."""
+ url = self.url + "/iscsi_initiator/" + ininame
+ data = json.dumps({"TYPE": "222",
+ "ID": ininame,
+ "USECHAP": "False",
+ "PARENTTYPE": "21",
+ "PARENTID": hostid})
+ result = self.call(url, data, "PUT")
+ self._assert_rest_result(result, 'Associate initiator to host error.')
+
+ def _find_mapping_view(self, name):
+ """Find mapping view."""
+ url = self.url + "/mappingview"
+ data = json.dumps({"TYPE": "245"})
+ result = self.call(url, data, "GET")
+ msg = 'Find map view error.'
+ self._assert_rest_result(result, msg)
+
+ if "data" not in result:
+ raise exception.CinderException(_('%s') % msg)
+
+ viewid = None
+ for item in result['data']:
+ if name == item['NAME']:
+ viewid = item['ID']
+ break
+ return viewid
+
+ def _add_mapping_view(self, name, host_id):
+ url = self.url + "/mappingview"
+ data = json.dumps({"NAME": name, "TYPE": "245"})
+ result = self.call(url, data)
+ self._assert_rest_result(result, 'Add map view error.')
+
+ return result['data']['ID']
+
+ def _associate_hostgroup_to_view(self, viewID, hostGroupID):
+ url = self.url + "/MAPPINGVIEW/CREATE_ASSOCIATE"
+ data = json.dumps({"ASSOCIATEOBJTYPE": "14",
+ "ASSOCIATEOBJID": hostGroupID,
+ "TYPE": "245",
+ "ID": viewID})
+ result = self.call(url, data, "PUT")
+ self._assert_rest_result(result, 'Associate host to view error.')
+
+ def _associate_lungroup_to_view(self, viewID, lunGroupID):
+ url = self.url + "/MAPPINGVIEW/CREATE_ASSOCIATE"
+ data = json.dumps({"ASSOCIATEOBJTYPE": "256",
+ "ASSOCIATEOBJID": lunGroupID,
+ "TYPE": "245",
+ "ID": viewID})
+ result = self.call(url, data, "PUT")
+ self._assert_rest_result(result, 'Associate lun group to view error.')
+
+ def _delete_lungroup_mapping_view(self, view_id, lungroup_id):
+ """remove lun group associate from the mapping view."""
+ url = self.url + "/mappingview/REMOVE_ASSOCIATE"
+ data = json.dumps({"ASSOCIATEOBJTYPE": "256",
+ "ASSOCIATEOBJID": lungroup_id,
+ "TYPE": "245",
+ "ID": view_id})
+ result = self.call(url, data, "PUT")
+ self._assert_rest_result(result, 'Delete lun group from view error.')
+
+ def _delete_hostgoup_mapping_view(self, view_id, hostgroup_id):
+ """remove host group associate from the mapping view."""
+ url = self.url + "/mappingview/REMOVE_ASSOCIATE"
+ data = json.dumps({"ASSOCIATEOBJTYPE": "14",
+ "ASSOCIATEOBJID": hostgroup_id,
+ "TYPE": "245",
+ "ID": view_id})
+ result = self.call(url, data, "PUT")
+ self._assert_rest_result(result, 'Delete host group from view error.')
+
+ def _delete_mapping_view(self, view_id):
+ """remove mapping view from the storage."""
+ url = self.url + "/mappingview/" + view_id
+ result = self.call(url, None, "DELETE")
+ self._assert_rest_result(result, 'Delete map view error.')
+
+ def terminate_connection(self, volume, connector, **kwargs):
+ """Delete map between a volume and a host."""
+ initiator_name = connector['initiator']
+ volume_name = self._encode_name(volume['id'])
+ host_name = connector['host']
+
+ LOG.debug(_('terminate_connection:volume name: %(volume)s, '
+ 'initiator name: %(ini)s.')
+ % {'volume': volume_name,
+ 'ini': initiator_name})
+
+ view_id = self._find_mapping_view(volume_name)
+ hostgroup_id = self._find_hostgroup(host_name)
+ lungroup_id = self._find_lungroup(volume_name)
+
+ if view_id is not None:
+ self._delete_hostgoup_mapping_view(view_id, hostgroup_id)
+ self._delete_lungroup_mapping_view(view_id, lungroup_id)
+ self._delete_mapping_view(view_id)
+
+ def login_out(self):
+ """logout the session."""
+ url = self.url + "/sessions"
+ result = self.call(url, None, "DELETE")
+ self._assert_rest_result(result, 'Log out of session error.')
+
+ def _start_luncopy(self, luncopyid):
+ """Starte a LUNcopy."""
+ url = self.url + "/LUNCOPY/start"
+ data = json.dumps({"TYPE": "219", "ID": luncopyid})
+ result = self.call(url, data, "PUT")
+ self._assert_rest_result(result, 'Start lun copy error.')
+
+ def _get_capacity(self):
+ """Get free capacity and total capacity of the pools."""
+ poolinfo = self._find_pool_info()
+ pool_capacity = {'total_capacity': 0.0,
+ 'CAPACITY': 0.0}
+
+ if poolinfo:
+ total = int(poolinfo['TOTALCAPACITY']) / 1024.0 / 1024.0 / 2
+ free = int(poolinfo['CAPACITY']) / 1024.0 / 1024.0 / 2
+ pool_capacity['total_capacity'] = total
+ pool_capacity['free_capacity'] = free
+
+ return pool_capacity
+
+ def _get_lun_conf_params(self):
+ """Get parameters from config file for creating lun."""
+ # Default lun set information
+ lunsetinfo = {'LUNType': 'Thick',
+ 'StripUnitSize': '64',
+ 'WriteType': '1',
+ 'MirrorSwitch': '1',
+ 'PrefetchType': '3',
+ 'PrefetchValue': '0',
+ 'PrefetchTimes': '0'}
+
+ root = self._read_xml()
+ luntype = root.findtext('LUN/LUNType')
+ if luntype:
+ if luntype.strip() in ['Thick', 'Thin']:
+ lunsetinfo['LUNType'] = luntype.strip()
+ if luntype.strip() == 'Thick':
+ lunsetinfo['LUNType'] = 0
+ if luntype.strip() == 'Thin':
+ lunsetinfo['LUNType'] = 1
+
+ elif luntype is not '' and luntype is not None:
+ err_msg = (_('Config file is wrong. LUNType must be "Thin"'
+ ' or "Thick". LUNType:%(fetchtype)s')
+ % {'fetchtype': luntype})
+ raise exception.VolumeBackendAPIException(data=err_msg)
+
+ stripunitsize = root.findtext('LUN/StripUnitSize')
+ if stripunitsize is not None:
+ lunsetinfo['StripUnitSize'] = stripunitsize.strip()
+ writetype = root.findtext('LUN/WriteType')
+ if writetype is not None:
+ lunsetinfo['WriteType'] = writetype.strip()
+ mirrorswitch = root.findtext('LUN/MirrorSwitch')
+ if mirrorswitch is not None:
+ lunsetinfo['MirrorSwitch'] = mirrorswitch.strip()
+
+ prefetch = root.find('LUN/Prefetch')
+ fetchtype = prefetch.attrib['Type']
+ if prefetch is not None and prefetch.attrib['Type']:
+ if fetchtype in ['0', '1', '2', '3']:
+ lunsetinfo['PrefetchType'] = fetchtype.strip()
+ typevalue = prefetch.attrib['Value'].strip()
+ if lunsetinfo['PrefetchType'] == '1':
+ lunsetinfo['PrefetchValue'] = typevalue
+ elif lunsetinfo['PrefetchType'] == '2':
+ lunsetinfo['PrefetchValue'] = typevalue
+ else:
+ err_msg = (_('PrefetchType config is wrong. PrefetchType'
+ ' must in 1,2,3,4. fetchtype is:%(fetchtype)s')
+ % {'fetchtype': fetchtype})
+ raise exception.CinderException(err_msg)
+ else:
+ LOG.debug(_('Use default prefetch fetchtype. '
+ 'Prefetch fetchtype:Intelligent.'))
+
+ return lunsetinfo
+
+ def _wait_for_luncopy(self, luncopyid):
+ """Wait for LUNcopy to complete."""
+ while True:
+ luncopy_info = self._get_luncopy_info(luncopyid)
+ if luncopy_info['status'] == '40':
+ break
+ elif luncopy_info['state'] != '1':
+ err_msg = (_('_wait_for_luncopy:LUNcopy status is not normal.'
+ 'LUNcopy name: %(luncopyname)s')
+ % {'luncopyname': luncopyid})
+ raise exception.VolumeBackendAPIException(data=err_msg)
+ time.sleep(10)
+
+ def _get_luncopy_info(self, luncopyid):
+ """Get LUNcopy information."""
+ url = self.url + "/LUNCOPY?range=[0-100000]"
+ data = json.dumps({"TYPE": "219", })
+ result = self.call(url, data, "GET")
+ self._assert_rest_result(result, 'Get lun copy information error.')
+
+ luncopyinfo = {}
+ if "data" in result:
+ for item in result['data']:
+ if luncopyid == item['ID']:
+ luncopyinfo['name'] = item['NAME']
+ luncopyinfo['id'] = item['ID']
+ luncopyinfo['state'] = item['HEALTHSTATUS']
+ luncopyinfo['status'] = item['RUNNINGSTATUS']
+ break
+ return luncopyinfo
+
+ def _delete_luncopy(self, luncopyid):
+ """Delete a LUNcopy."""
+ url = self.url + "/LUNCOPY/%s" % luncopyid
+ result = self.call(url, None, "DELETE")
+ self._assert_rest_result(result, 'Delete lun copy error.')
+
+ def _get_connected_free_wwns(self):
+ """Get free connected FC port WWNs.
+
+ If no new ports connected, return an empty list.
+ """
+ url = self.url + "/fc_initiator?ISFREE=true&range=[0-1000]"
+ result = self.call(url, None, "GET")
+ msg = 'Get connected free FC wwn error.'
+ self._assert_rest_result(result, msg)
+
+ if "data" not in result:
+ raise exception.CinderException(_('%s') % msg)
+
+ wwns = []
+ for item in result['data']:
+ wwns.append(item['ID'])
+ return wwns
+
+ def _add_fc_port_to_host(self, hostid, wwn, multipathtype=0):
+ """Add a FC port to the host."""
+ url = self.url + "/fc_initiator/" + wwn
+ data = json.dumps({"TYPE": "223",
+ "ID": wwn,
+ "PARENTTYPE": 21,
+ "PARENTID": hostid})
+ result = self.call(url, data, "PUT")
+ self._assert_rest_result(result, 'Add FC port to host error.')
+
+ def _get_iscsi_port_info(self, ip):
+ """Get iscsi port info in order to build the iscsi target iqn."""
+ url = self.url + "/eth_port"
+ result = self.call(url, None, "GET")
+ msg = 'Get iSCSI port information error.'
+ self._assert_rest_result(result, msg)
+
+ if "data" not in result:
+ raise exception.CinderException(_('%s') % msg)
+
+ iscsi_port_info = None
+ for item in result['data']:
+ if ip == item['IPV4ADDR']:
+ iscsi_port_info = item['LOCATION']
+ break
+
+ return iscsi_port_info
+
+ def _get_iscsi_conf(self):
+ """Get iSCSI info from config file."""
+ iscsiinfo = {}
+ root = self._read_xml()
+ iscsiinfo['DefaultTargetIP'] = \
+ root.findtext('iSCSI/DefaultTargetIP').strip()
+ initiator_list = []
+ tmp_dic = {}
+ for dic in root.findall('iSCSI/Initiator'):
+ # Strip values of dic
+ for k, v in dic.items():
+ tmp_dic[k] = v.strip()
+ initiator_list.append(tmp_dic)
+ iscsiinfo['Initiator'] = initiator_list
+
+ return iscsiinfo
+
+ def _get_tgt_iqn(self, iscsiip):
+ """Get target iSCSI iqn."""
+ LOG.debug(_('_get_tgt_iqn: iSCSI IP is %s.') % iscsiip)
+ ip_info = self._get_iscsi_port_info(iscsiip)
+ iqn_prefix = self._get_iscsi_tgt_port()
+ LOG.debug(_('request ip info is %s.') % ip_info)
+ split_list = ip_info.split(".")
+ newstr = split_list[1] + split_list[2]
+ LOG.debug(_('new str info is %s.') % newstr)
+
+ if ip_info:
+ if newstr[0] == 'A':
+ ctr = "0"
+ elif newstr[0] == 'B':
+ ctr = "1"
+ interface = '0' + newstr[1]
+ port = '0' + newstr[3]
+ iqn_suffix = ctr + '02' + interface + port
+ for i in range(0, len(iqn_suffix)):
+ if iqn_suffix[i] != '0':
+ iqn_suffix = iqn_suffix[i:]
+ break
+ iqn = iqn_prefix + ':' + iqn_suffix + ':' + iscsiip
+ LOG.debug(_('_get_tgt_iqn: iSCSI target iqn is %s') % iqn)
+ return iqn
+ else:
+ return None
+
+ def _get_fc_target_wwpns(self, wwn):
+ url = (self.url +
+ "/host_link?INITIATOR_TYPE=223&INITIATOR_PORT_WWN=" + wwn)
+ result = self.call(url, None, "GET")
+ msg = 'Get FC target wwpn error.'
+ self._assert_rest_result(result, msg)
+
+ if "data" not in result:
+ raise exception.CinderException(_('%s') % msg)
+
+ fc_wwpns = None
+ for item in result['data']:
+ if wwn == item['INITIATOR_PORT_WWN']:
+ fc_wwpns = item['TARGET_PORT_WWN']
+ break
+
+ return fc_wwpns
+
+ def _parse_volume_type(self, volume):
+ type_id = volume['volume_type_id']
+ params = self._get_lun_conf_params()
+ LOG.debug(_('_parse_volume_type: type id: %(type_id)s '
+ 'config parameter is: %(params)s')
+ % {'type_id': type_id,
+ 'params': params})
+
+ poolinfo = self._find_pool_info()
+ volume_size = self._get_volume_size(poolinfo, volume)
+ params['volume_size'] = volume_size
+ params['pool_id'] = poolinfo['ID']
+
+ if type_id is not None:
+ ctxt = context.get_admin_context()
+ volume_type = volume_types.get_volume_type(ctxt, type_id)
+ specs = volume_type.get('extra_specs')
+ for key, value in specs.iteritems():
+ key_split = key.split(':')
+ if len(key_split) > 1:
+ if key_split[0] == 'drivers':
+ key = key_split[1]
+ else:
+ continue
+ else:
+ key = key_split[0]
+
+ if key in QOS_KEY:
+ params["qos"] = value.strip()
+ params["qos_level"] = key
+ elif key in TIER_KEY:
+ params["tier"] = value.strip()
+ elif key in params.keys():
+ params[key] = value.strip()
+ else:
+ conf = self.configuration.cinder_huawei_conf_file
+ LOG.warn(_('_parse_volume_type: Unacceptable paramater '
+ '%(key)s. Please check this key in extra_specs '
+ 'and make it consistent with the configuration '
+ 'file %(conf)s.') % {'key': key, 'conf': conf})
+
+ LOG.debug(_("The config parameters are: %s") % params)
+ return params
+
+ def update_volume_stats(self, refresh=False):
+ capacity = self._get_capacity()
+ data = {}
+ data['vendor_name'] = 'Huawei'
+ data['total_capacity_gb'] = capacity['total_capacity']
+ data['free_capacity_gb'] = capacity['free_capacity']
+ data['reserved_percentage'] = 0
+ data['QoS_support'] = True
+ data['Tier_support'] = True
+ return data
+
+ def _find_qos_policy_info(self, policy_name):
+ url = self.url + "/ioclass"
+ result = self.call(url, None, "GET")
+ msg = 'Get qos policy error.'
+ self._assert_rest_result(result, msg)
+
+ if "data" not in result:
+ raise exception.CinderException(_('%s') % msg)
+
+ qos_info = {}
+ for item in result['data']:
+ if policy_name == item['NAME']:
+ qos_info['ID'] = item['ID']
+ lun_list = json.loads(item['LUNLIST'])
+ qos_info['LUNLIST'] = lun_list
+ break
+ return qos_info
+
+ def _update_qos_policy_lunlist(self, lunlist, policy_id):
+ url = self.url + "/ioclass/" + policy_id
+ data = json.dumps({"TYPE": "230",
+ "ID": policy_id,
+ "LUNLIST": lunlist})
+ result = self.call(url, data, "PUT")
+ self._assert_rest_result(result, 'Up date qos policy error.')
+
+ def _get_login_info(self):
+ """Get login IP, username and password from config file."""
+ logininfo = {}
+ filename = self.configuration.cinder_huawei_conf_file
+ tree = ET.parse(filename)
+ root = tree.getroot()
+ logininfo['HVSURL'] = root.findtext('Storage/HVSURL').strip()
+
+ need_encode = False
+ for key in ['UserName', 'UserPassword']:
+ node = root.find('Storage/%s' % key)
+ node_text = node.text
+ # Prefix !$$$ means encoded already.
+ if node_text.find('!$$$') > -1:
+ logininfo[key] = base64.b64decode(node_text[4:])
+ else:
+ logininfo[key] = node_text
+ node.text = '!$$$' + base64.b64encode(node_text)
+ need_encode = True
+ if need_encode:
+ self._change_file_mode(filename)
+ try:
+ tree.write(filename, 'UTF-8')
+ except Exception as err:
+ LOG.warn(_('%s') % err)
+
+ return logininfo
+
+ def _change_file_mode(self, filepath):
+ utils.execute('chmod', '777', filepath, run_as_root=True)
+
+ def _check_conf_file(self):
+ """Check the config file, make sure the essential items are set."""
+ root = self._read_xml()
+ hvsurl = root.findtext('Storage/HVSURL')
+ username = root.findtext('Storage/UserName')
+ pwd = root.findtext('Storage/UserPassword')
+ pool_node = root.findall('LUN/StoragePool')
+
+ if (not hvsurl) or (not username) or (not pwd):
+ err_msg = (_('_check_conf_file: Config file invalid. HVSURL,'
+ ' UserName and UserPassword must be set.'))
+ LOG.error(err_msg)
+ raise exception.InvalidInput(reason=err_msg)
+
+ if not pool_node:
+ err_msg = (_('_check_conf_file: Config file invalid. '
+ 'StoragePool must be set.'))
+ raise exception.InvalidInput(reason=err_msg)
+
+ def _get_iscsi_params(self, connector):
+ """Get target iSCSI params, including iqn, IP."""
+ initiator = connector['initiator']
+ iscsi_conf = self._get_iscsi_conf()
+ target_ip = None
+ for ini in iscsi_conf['Initiator']:
+ if ini['Name'] == initiator:
+ target_ip = ini['TargetIP']
+ break
+ # If didn't specify target IP for some initiator, use default IP.
+ if not target_ip:
+ if iscsi_conf['DefaultTargetIP']:
+ target_ip = iscsi_conf['DefaultTargetIP']
+
+ else:
+ msg = (_('_get_iscsi_params: Failed to get target IP '
+ 'for initiator %(ini)s, please check config file.')
+ % {'ini': initiator})
+ LOG.error(msg)
+ raise exception.InvalidInput(reason=msg)
+
+ target_iqn = self._get_tgt_iqn(target_ip)
+
+ return (target_iqn, target_ip)