From c8dc22af4bbf51893d9f87080b9c6bbb7572493f Mon Sep 17 00:00:00 2001 From: kongwei Date: Wed, 4 May 2016 15:44:35 +0800 Subject: [PATCH] Add ZTE Block Storage Driver It will support the minimum set of features required in Newton: * Volume Create/Delete * Volume Attach/Detach * Snapshot Create/Delete * Create Volume from Snapshot * Copy Image to Volume * Copy Volume to Image * Clone Volume * Extend Volume DocImpact Implements: bp zte-cinder-driver Change-Id: I1629ef022a5227a7c488909152f78f33719bc573 --- cinder/opts.py | 2 + cinder/tests/unit/test_zte_ks.py | 427 ++++++++ cinder/volume/drivers/zte/__init__.py | 0 cinder/volume/drivers/zte/zte_ks.py | 982 ++++++++++++++++++ cinder/volume/drivers/zte/zte_pub.py | 59 ++ .../zte_cinder_driver-76ba6d034e1b6f65.yaml | 4 + 6 files changed, 1474 insertions(+) create mode 100644 cinder/tests/unit/test_zte_ks.py create mode 100644 cinder/volume/drivers/zte/__init__.py create mode 100644 cinder/volume/drivers/zte/zte_ks.py create mode 100644 cinder/volume/drivers/zte/zte_pub.py create mode 100755 releasenotes/notes/zte_cinder_driver-76ba6d034e1b6f65.yaml diff --git a/cinder/opts.py b/cinder/opts.py index 64c6ca5f2db..666ff4c1996 100644 --- a/cinder/opts.py +++ b/cinder/opts.py @@ -164,6 +164,7 @@ from cinder.volume.drivers.zfssa import zfssaiscsi as \ cinder_volume_drivers_zfssa_zfssaiscsi from cinder.volume.drivers.zfssa import zfssanfs as \ cinder_volume_drivers_zfssa_zfssanfs +from cinder.volume.drivers.zte import zte_ks as cinder_volume_drivers_zte_zteks from cinder.volume import manager as cinder_volume_manager from cinder.wsgi import eventlet_server as cinder_wsgi_eventletserver from cinder.zonemanager.drivers.brocade import brcd_fabric_opts as \ @@ -221,6 +222,7 @@ def list_opts(): cinder_volume_drivers_fujitsu_eternusdxcommon. FJ_ETERNUS_DX_OPT_opts, cinder_volume_drivers_ibm_gpfs.gpfs_opts, + cinder_volume_drivers_zte_zteks.zte_opts, cinder_volume_drivers_violin_v7000common.violin_opts, cinder_volume_drivers_nexenta_options.NEXENTA_CONNECTION_OPTS, cinder_volume_drivers_nexenta_options.NEXENTA_ISCSI_OPTS, diff --git a/cinder/tests/unit/test_zte_ks.py b/cinder/tests/unit/test_zte_ks.py new file mode 100644 index 00000000000..7f44911f0eb --- /dev/null +++ b/cinder/tests/unit/test_zte_ks.py @@ -0,0 +1,427 @@ +# Copyright 2016 ZTE Corporation. All rights reserved +# 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. +# + +""" +Self test for ZTE Storage Driver platform. +""" +from oslo_config import cfg + +from cinder import exception +from cinder import test +from cinder.volume import configuration as conf +from cinder.volume.drivers.zte import zte_ks +from cinder.volume.drivers.zte import zte_pub + + +session_id = 'kfomqdnoetjcjlva' +volume_paras = {'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', + 'source_volid': None, + 'volume_metadata': [], + 'display_description': 'test volume', + 'volume_type_id': None} + +volume_clone = {'name': 'volume-ee317512-f6a6-4284-a94e-5f4ac8783169', + 'size': 4, + 'volume_name': 'vol1', + 'id': 'ee317512-f6a6-4284-a94e-5f4ac8783169', + 'volume_id': 'ee317512-f6a6-4284-a94e-5f4ac8783169', + 'provider_auth': None, + 'project_id': 'project', + 'display_name': 'clone_vol1', + 'source_volid': None, + 'volume_metadata': [], + 'display_description': 'test clone volume', + 'volume_type_id': None} +snapvolume_paras = {'name': 'volume-21ec7341-9256-497b-97d9-ef48edcf0635', + 'size': 2, + 'volume_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', + 'source_volid': None, + 'volume_metadata': [], + 'display_description': 'test volume', + 'volume_type_id': None} +connector = {'ip': '10.0.0.0', + 'initiator': 'iqn.1993-08.org.debian:01:222'} +fcconnector = {'ip': '10.0.0.0', + 'wwpns': [1, 2, 3, 4, 5, 6, 7, 8]} +fake_opt = [ + cfg.StrOpt('fake_opt', default='fake', help='fake opts') +] +VolFlowLimitAttr_paras = {'sqwWriteFlowLimit': 0, + 'cVolName': 'OpenCos_5072124445952515861', + 'sqwTotalFlowLimit': 500, + 'sqwWriteIoCount': 0, + 'sqwTotalIoCount': 0, + 'sqwReadFlowLimit': 0, + 'sqwReadIoCount': 0} + +volume_name = 'OpenCos_8359669312515962256' +return_success = {'returncode': zte_pub.ZTE_SUCCESS, 'data': {}} +return_error = {'returncode': zte_pub.ZTE_ERR_LUNDEV_NOT_EXIST, 'data': {}} +return_port_error = ( + {'returncode': zte_pub.ZTE_ERR_PORT_EXIST_INOTHER, 'data': {}}) +return_host_error = ( + {'returncode': zte_pub.ZTE_ERR_HOST_EXIST_INOTHER, 'data': {}}) + +MAP_TO_RESPONSE = {} +signin_info = {'sessionID': session_id} +MAP_TO_RESPONSE['plat.session.signin'] = {'returncode': zte_pub.ZTE_SUCCESS, + 'data': signin_info} +MAP_TO_RESPONSE['plat.session.heartbeat'] = return_success +pool_info = {'sdwState': 1, 'qwTotalCapacity': 1024560, + 'qwFreeCapacity': 102456} +MAP_TO_RESPONSE['GetPoolInfo'] = {'returncode': zte_pub.ZTE_SUCCESS, + 'data': pool_info} +MAP_TO_RESPONSE['CreateVolOnPool'] = return_success +MAP_TO_RESPONSE['DelCvol'] = return_success +MAP_TO_RESPONSE['GetCvolNamesOnVol'] = { + 'returncode': zte_pub.ZTE_SUCCESS, + 'data': {'sdwCvolNum': 2, 'scCvolNames': [{'scCvolName': 'clone1'}, + {'scCvolName': 'clone2'}]}} +MAP_TO_RESPONSE['CreateSvol'] = return_success +MAP_TO_RESPONSE['DelSvol'] = return_success +MAP_TO_RESPONSE['ExpandVolOnPool'] = return_success +MAP_TO_RESPONSE['CreateCvol'] = return_success +cVolName_from_vol_name = "OpenCos_9fbc232bf71ee2fa8bd" +grp_info = {'sdwHostNum': 1, + 'tHostInfo': [{'ucHostName': 'host1'}], + 'sdwLunNum': 5, + 'cMapGrpName': 'group_cjf', + 'tLunInfo': [{'sdwLunState': 0, 'sdwBlockSize': 0, + 'sdwAccessAttr': 0, 'sdwLunId': 0, + 'cVolName': 'vol1'}, + {'sdwLunState': 0, 'sdwBlockSize': 0, + 'sdwAccessAttr': 0, 'sdwLunId': 1, + 'cVolName': volume_name}, + {'sdwLunState': 0, 'sdwBlockSize': 0, + 'sdwAccessAttr': 0, 'sdwLunId': 2, + 'cVolName': 'vol3'}, + {'sdwLunState': 0, 'sdwBlockSize': 0, + 'sdwAccessAttr': 0, 'sdwLunId': 3, + 'cVolName': cVolName_from_vol_name}, + {'sdwLunState': 0, 'sdwBlockSize': 0, + 'sdwAccessAttr': 0, 'sdwLunId': 5, + 'cVolName': 'vol4'}]} +MAP_TO_RESPONSE['GetMapGrpInfo'] = ( + {'returncode': zte_pub.ZTE_SUCCESS, 'data': grp_info}) +MAP_TO_RESPONSE['DelMapGrp'] = return_success +simple_grp_info = {'sdwMapGrpNum': 0, + 'tMapGrpSimpleInfo': [{'sdwHostNum': 0, + 'sdwLunNum': 0, + 'cMapGrpName': session_id}, + {'sdwHostNum': 1, + 'sdwLunNum': 1, + 'cMapGrpName': ''}]} +MAP_TO_RESPONSE['GetGrpSimpleInfoList'] = {'returncode': zte_pub.ZTE_SUCCESS, + 'data': simple_grp_info} +luninfo = {'sdwLunId': 3} +MAP_TO_RESPONSE['AddVolToGrp'] = {'returncode': zte_pub.ZTE_SUCCESS, + 'data': luninfo} +sys_info = {'cVendor': 'ZTE', 'cVersionName': 'V1.0', + 'storage_protocol': 'iSCSI'} +MAP_TO_RESPONSE['GetSysInfo'] = {'returncode': zte_pub.ZTE_SUCCESS, + 'data': sys_info} +cfg_info = {'sdwDeviceNum': 4, + 'tSystemNetCfg': [ + {'udwCtrlId': 0, 'udwRoleType': 0, + 'udwPortType': 1, 'udwDeviceId': 123, + 'cIpAddr': '198.51.100.20'}, + {'udwCtrlId': 0, 'udwRoleType': 0, + 'udwPortType': 1, 'udwDeviceId': 123, + 'cIpAddr': '198.51.100.21'}, + {'udwCtrlId': 0, 'udwRoleType': 0, + 'udwPortType': 1, 'udwDeviceId': 123, + 'cIpAddr': '198.51.100.22'}, + {'udwCtrlId': 0, 'udwRoleType': 0, + 'udwPortType': 1, 'udwDeviceId': 123, + 'cIpAddr': '198.51.100.23'}]} +MAP_TO_RESPONSE['GetSystemNetCfg'] = {'returncode': zte_pub.ZTE_SUCCESS, + 'data': cfg_info} +iscsi_target = { + 'tIscsiTargetInfo': [ + {'udwCtrlId': 0, + 'cTgtName': 'iqn.2099-01.cn.com.zte:usp.spr11-00:00:22:15'}, + {'udwCtrlId': 0, + 'cTgtName': 'iqn.2099-01.cn.com.zte:usp.spr11-00:00:22:25'}], + 'udwCtrlCount': 2} +MAP_TO_RESPONSE['GetIscsiTargetName'] = {'returncode': zte_pub.ZTE_SUCCESS, + 'data': iscsi_target} +MAP_TO_RESPONSE['CreateMapGrp'] = return_success +grp_info_forsearch = {'cVolName': 'vol1', + 'sdwMapGrpNum': 1, + 'cMapGrpNames': ['grp1'], + 'sdwLunLocalId': [1]} +MAP_TO_RESPONSE['GetGrpNamesOfVol'] = ( + {'returncode': zte_pub.ZTE_SUCCESS, 'data': grp_info_forsearch}) +MAP_TO_RESPONSE['CreateHost'] = return_success +MAP_TO_RESPONSE['AddPortToHost'] = return_success +MAP_TO_RESPONSE['AddHostToGrp'] = return_success +MAP_TO_RESPONSE['DelVolFromGrp'] = return_success +MAP_TO_RESPONSE['DelHostFromGrp'] = return_success +host_info = {'sdwPortNum': 2, + 'tPort': [{'cPortName': 'port1'}, + {'cPortName': 'port2'}]} +MAP_TO_RESPONSE['GetHost'] = {'returncode': zte_pub.ZTE_SUCCESS, + 'data': host_info} +MAP_TO_RESPONSE['DelPortFromHost'] = return_success +MAP_TO_RESPONSE['DelHost'] = return_success + + +class FakeZteISCSIDriver(zte_ks.ZteISCSIDriver): + def __init__(self, configuration): + self.configuration = configuration + super(FakeZteISCSIDriver, self).__init__( + configuration=self.configuration) + self.result = zte_pub.ZTE_SUCCESS + self.test_flag = True + self.portexist_flag = False + self.portexistother_flag = False + self.hostexist_flag = False + self.hostexistother_flag = False + + def _call(self, sessionid='', method='', params=None): + return_data = return_success + + if method in MAP_TO_RESPONSE.keys(): + return_data = MAP_TO_RESPONSE[method] + + if not self.test_flag: + return_data = return_error + if self.portexistother_flag: + return_data = return_port_error + if self.hostexistother_flag: + return_data = return_host_error + return return_data + + def _check_conf_file(self): + pass + + def _get_iscsi_info(self): + iscsi_info = {'DefaultTargetIPs': ["198.51.100.20"]} + + return iscsi_info + + +class ZteBaseDriverTestCase(object): + def test_create_volume_success(self): + self.driver.test_flag = True + self.driver.create_volume(volume_paras) + self.assertEqual(zte_pub.ZTE_SUCCESS, self.driver.result) + + def test_create_volume_fail(self): + self.driver.test_flag = False + self.assertRaises(exception.CinderException, + self.driver.create_volume, volume_paras) + + def test_delete_volume_success(self): + self.driver.test_flag = True + self.driver.delete_volume(volume_paras) + self.assertEqual(zte_pub.ZTE_SUCCESS, self.driver.result) + + def test_delete_volume_fail(self): + self.driver.test_flag = False + self.assertRaises(exception.CinderException, + self.driver.delete_volume, volume_paras) + + def test_delete_cloned_volume_success(self): + self.driver.test_flag = True + vol = {'name': 'volume-21ec7341-9256-497b-97d9-ef48edcf0635', + 'source_volid': '68a52c1e-ecbe-4f6c-954c-9f551347ff3f'} + self.driver.delete_volume(vol) + self.assertEqual(zte_pub.ZTE_SUCCESS, self.driver.result) + + def test_delete_cloned_volume_fail(self): + self.driver.test_flag = False + vol = {'name': 'volume-21ec7341-9256-497b-97d9-ef48edcf0635', + 'source_volid': '68a52c1e-ecbe-4f6c-954c-9f551347ff3f'} + self.assertRaises(exception.CinderException, + self.driver.delete_volume, vol) + + def test_create_snapshot_success(self): + self.driver.test_flag = True + snap_vol = {'name': 'snapshot-2b9b982a-8b56-46e3-9d4f-6392e8a72e6e', + 'volume_name': + 'volume-21ec7341-9256-497b-97d9-ef48edcf0635', + 'volume_size': 2} + self.driver.create_snapshot(snap_vol) + self.assertEqual(zte_pub.ZTE_SUCCESS, self.driver.result) + + def test_create_snapshot_fail(self): + self.driver.test_flag = False + snap_vol = {'name': 'snapshot-2b9b982a-8b56-46e3-9d4f-6392e8a72e6e', + 'volume_name': + 'volume-21ec7341-9256-497b-97d9-ef48edcf0635', + 'volume_size': 2} + self.assertRaises(exception.CinderException, + self.driver.create_snapshot, snap_vol) + + def test_delete_snapshot_success(self): + self.driver.test_flag = True + snap_vol = {'name': 'snapshot-2b9b982a-8b56-46e3-9d4f-6392e8a72e6e'} + self.driver.delete_snapshot(snap_vol) + self.assertEqual(zte_pub.ZTE_SUCCESS, self.driver.result) + + def test_delete_snapshot_fail(self): + self.driver.test_flag = False + snap_vol = {'name': 'snapshot-2b9b982a-8b56-46e3-9d4f-6392e8a72e6e'} + self.assertRaises(exception.CinderException, + self.driver.delete_snapshot, snap_vol) + + def test_extend_volume_success(self): + self.driver.test_flag = True + self.driver.extend_volume(volume_paras, 4) + self.assertEqual(zte_pub.ZTE_SUCCESS, self.driver.result) + + def test_extend_volume_fail(self): + self.driver.test_flag = False + self.assertRaises(exception.CinderException, + self.driver.extend_volume, volume_paras, 4) + + def test_create_cloned_volume_success(self): + self.driver.test_flag = True + self.driver.create_cloned_volume(volume_clone, volume_paras) + self.assertEqual(zte_pub.ZTE_SUCCESS, self.driver.result) + + def test_create_cloned_volume_fail(self): + self.driver.test_flag = False + self.assertRaises(exception.CinderException, + self.driver.create_cloned_volume, + volume_clone, volume_paras) + + def test_create_volume_from_snapshot_success(self): + self.driver.test_flag = True + self.driver.create_volume_from_snapshot(volume_clone, snapvolume_paras) + self.assertEqual(zte_pub.ZTE_SUCCESS, self.driver.result) + + def test_create_volume_from_snapshot_fail(self): + self.driver.test_flag = False + self.assertRaises(exception.CinderException, + self.driver.create_cloned_volume, + volume_clone, volume_paras) + + +class ZteISCSIDriverTestCase(ZteBaseDriverTestCase, test.TestCase): + """Test ZTE iSCSI volume driver.""" + + def __init__(self, *args, **kwargs): + super(ZteISCSIDriverTestCase, self).__init__(*args, **kwargs) + + def setUp(self): + super(ZteISCSIDriverTestCase, self).setUp() + configuration = conf.Configuration(None) + self.configuration = configuration + + self.configuration.zteControllerIP0 = '192.0.2.2' + self.configuration.zteLocalIP = '192.0.2.8' + self.configuration.zteUserName = 'root' + self.configuration.zteUserPassword = 'root' + self.configuration.zteChunkSize = 64 + self.configuration.zteAheadReadSize = 8 + self.configuration.zteCachePolicy = 65535 + self.configuration.zteSSDCacheSwitch = 0 + self.configuration.zteStoragePool = 'pool1,pool2,pool3' + self.configuration.ztePoolVolAllocPolicy = 0 + self.configuration.ztePoolVolMovePolicy = 0 + self.configuration.ztePoolVolIsThin = 0 + self.configuration.ztePoolVolInitAllocedCapacity = 0 + self.configuration.ztePoolVolAlarmThreshold = 0 + self.configuration.ztePoolVolAlarmStopAllocFlag = 0 + + self.driver = FakeZteISCSIDriver(configuration=self.configuration) + self.driver.do_setup({}) + + def tearDown(self): + super(ZteISCSIDriverTestCase, self).tearDown() + + def test_get_volume_stats(self): + stats = self.driver.get_volume_stats(True) + self.assertEqual("ZTE", stats["vendor_name"]) + self.assertEqual("iSCSI", stats["storage_protocol"]) + self.assertEqual("V1.0", stats["driver_version"]) + self.assertLess(0, stats["total_capacity_gb"]) + + def test_initialize_connection_success(self): + self.driver.test_flag = True + data = self.driver.initialize_connection(volume_paras, connector) + properties = data['data'] + self.assertEqual("iscsi", data["driver_volume_type"]) + self.assertEqual('iqn.2099-01.cn.com.zte:usp.spr11-00:00:22:15', + properties["target_iqn"]) + self.assertEqual(3, properties["target_lun"]) + self.assertEqual('198.51.100.20:3260', properties["target_portal"]) + + def test_initialize_connection_portexist(self): + self.driver.portexist_flag = True + data = self.driver.initialize_connection(volume_paras, connector) + properties = data['data'] + self.assertEqual("iscsi", data["driver_volume_type"]) + self.assertEqual('iqn.2099-01.cn.com.zte:usp.spr11-00:00:22:15', + properties["target_iqn"]) + self.assertEqual(3, properties["target_lun"]) + self.assertEqual('198.51.100.20:3260', properties["target_portal"]) + + def test_initialize_connection_hostexist(self): + self.driver.hostexist_flag = True + data = self.driver.initialize_connection(volume_paras, connector) + + properties = data['data'] + self.assertEqual("iscsi", data["driver_volume_type"]) + self.assertEqual('iqn.2099-01.cn.com.zte:usp.spr11-00:00:22:15', + properties["target_iqn"]) + self.assertEqual(3, properties["target_lun"]) + self.assertEqual('198.51.100.20:3260', properties["target_portal"]) + + def test_initialize_connection_portexistother(self): + self.driver.portexistother_flag = True + self.assertRaises(exception.CinderException, + self.driver.initialize_connection, + volume_paras, connector) + + def test_initialize_connection_hostexistother(self): + self.driver.hostexistother_flag = True + self.assertRaises(exception.CinderException, + self.driver.initialize_connection, + volume_paras, connector) + + def test_initialize_connection_fail(self): + self.driver.test_flag = False + self.assertRaises(exception.CinderException, + self.driver.initialize_connection, + volume_paras, connector) + + def test_terminate_connection_success(self): + self.driver.test_flag = True + vol = {'name': 'volume-ee317512-f6a6-4284-a94e-5f4ac8783169'} + self.driver.terminate_connection(vol, connector) + self.assertEqual(zte_pub.ZTE_SUCCESS, self.driver.result) + + def test_terminate_connection_fail(self): + self.driver.test_flag = False + vol = {'name': 'volume-ee317512-f6a6-4284-a94e-5f4ac8783169'} + self.assertRaises(exception.CinderException, + self.driver.terminate_connection, vol, connector) diff --git a/cinder/volume/drivers/zte/__init__.py b/cinder/volume/drivers/zte/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/cinder/volume/drivers/zte/zte_ks.py b/cinder/volume/drivers/zte/zte_ks.py new file mode 100644 index 00000000000..2c667d15e85 --- /dev/null +++ b/cinder/volume/drivers/zte/zte_ks.py @@ -0,0 +1,982 @@ +# Copyright 2016 ZTE Corporation. All rights reserved +# 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 driver for ZTE storage systems. +""" + +import hashlib +import json + +from oslo_config import cfg +from oslo_log import log as logging +from oslo_utils import units +import six +from six.moves import urllib + +from cinder import exception +from cinder.i18n import _, _LE, _LI +from cinder import interface +from cinder import utils +from cinder.volume import driver +from cinder.volume.drivers.zte import zte_pub + + +LOG = logging.getLogger(__name__) + +zte_opts = [ + cfg.IPOpt('zteControllerIP0', default=None, + help='Main controller IP.'), + cfg.IPOpt('zteControllerIP1', default=None, + help='Slave controller IP.'), + cfg.IPOpt('zteLocalIP', default=None, help='Local IP.'), + cfg.StrOpt('zteUserName', default=None, help='User name.'), + cfg.StrOpt('zteUserPassword', default=None, secret=True, + help='User password.'), + cfg.IntOpt('zteChunkSize', default=4, + help='Virtual block size of pool. ' + 'Unit : KB. ' + 'Valid value : 4, 8, 16, 32, 64, 128, 256, 512. '), + cfg.IntOpt('zteAheadReadSize', default=8, help='Cache readahead size.'), + cfg.IntOpt('zteCachePolicy', default=1, + help='Cache policy. ' + '0, Write Back; 1, Write Through.'), + cfg.IntOpt('zteSSDCacheSwitch', default=1, + help='SSD cache switch. ' + '0, OFF; 1, ON.'), + cfg.ListOpt('zteStoragePool', default=[], help='Pool name list.'), + cfg.IntOpt('ztePoolVoAllocatedPolicy', default=0, + help='Pool volume allocated policy. ' + '0, Auto; ' + '1, High Performance Tier First; ' + '2, Performance Tier First; ' + '3, Capacity Tier First.'), + cfg.IntOpt('ztePoolVolMovePolicy', default=0, + help='Pool volume move policy.' + '0, Auto; ' + '1, Highest Available; ' + '2, Lowest Available; ' + '3, No Relocation.'), + cfg.IntOpt('ztePoolVolIsThin', default=False, + help='Whether it is a thin volume.'), + cfg.IntOpt('ztePoolVolInitAllocatedCapacity', default=0, + help='Pool volume init allocated Capacity.' + 'Unit : KB. '), + cfg.IntOpt('ztePoolVolAlarmThreshold', default=0, + help='Pool volume alarm threshold. [0, 100]'), + cfg.IntOpt('ztePoolVolAlarmStopAllocatedFlag', default=0, + help='Pool volume alarm stop allocated flag.') +] + +CONF = cfg.CONF +CONF.register_opts(zte_opts) + + +@interface.volumedriver +class ZTEVolumeDriver(driver.VolumeDriver): + def __init__(self, *args, **kwargs): + super(ZTEVolumeDriver, self).__init__(*args, **kwargs) + self.configuration.append_config_values(zte_opts) + self.url = '' + self.login_info = {} + self.session_id = '' + + def _get_md5(self, src_string): + md5obj = hashlib.md5() + md5obj.update(src_string.encode('UTF-8')) + md5_string = md5obj.hexdigest() + md5_string = md5_string[0:19] + return md5_string + + def _call_method(self, method='', params=None): + sid = self._get_sessionid() + return self._call(sid, method, params) + + def _call(self, sessin_id='', method='', params=None): + try: + params = params or {} + data = ("sessionID=" + sessin_id + "&method=" + + method + "¶ms=" + json.dumps(params)) + LOG.debug('Req Data: method %(method)s data %(data)s.', + {'method': method, 'data': data}) + headers = {"Connection": "keep-alive", + "Content-Type": "application/x-www-form-urlencoded"} + req = urllib.request.Request(self.url, data, headers) + req.get_method = lambda: 'POST' + response = urllib.request.urlopen(req, + timeout= + zte_pub.ZTE_DEFAULT_TIMEOUT + ).read() + LOG.debug('Response Data: method %(method)s res %(res)s.', + {'method': method, 'res': response}) + except Exception: + LOG.exception(_LE('Bad response from server.')) + msg = (_('_call failed.')) + raise exception.VolumeBackendAPIException(data=msg) + res_json = json.loads(response) + return res_json + + def _get_server(self): + controller_ip = (self.login_info['ControllerIP0'] + or self.login_info['ControllerIP1'] or '') + self.url = 'https://' + controller_ip + '/phpclient/client.php' + LOG.debug('Set ZTE server is %s.', self.url) + + def _change_server(self): + if (self.login_info['ControllerIP0'] and + self.login_info['ControllerIP1']): + controller_ip = (self.login_info['ControllerIP1'] + if self.login_info['ControllerIP0'] in self.url + else self.login_info['ControllerIP0']) + self.url = 'https://' + controller_ip + '/phpclient/client.php' + + def _user_login(self): + loginfo = {'UserName': self.login_info['UserName'], + 'UserPassword': self.login_info['UserPassword'], + 'LocalIP': self.login_info['LocalIP'], + 'LoginType': zte_pub.ZTE_WEB_LOGIN_TYPE} + + result = self._call('""', 'plat.session.signin', loginfo) + + if result['returncode'] in [zte_pub.ZTE_SUCCESS, + zte_pub.ZTE_SESSION_EXIST]: + self.session_id = result['data']['sessionID'] + return self.session_id + else: + err_msg = ( + _('Failed to login. Return code: %(ret)s.') % { + 'ret': result['returncode']}) + raise exception.VolumeBackendAPIException( + data=err_msg) + + def do_setup(self, context): + """Any initialization the volume driver does while starting.""" + self.login_info = { + 'ControllerIP0': self.configuration.zteControllerIP0, + 'ControllerIP1': self.configuration.zteControllerIP1, + 'LocalIP': self.configuration.zteLocalIP, + 'UserName': self.configuration.zteUserName, + 'UserPassword': self.configuration.zteUserPassword} + self._get_server() + try: + self.session_id = self._user_login() + except exception.VolumeBackendAPIException: + self._change_server() + self.session_id = self._user_login() + + def check_for_setup_error(self): + + zteControllerIP0 = self.configuration.zteControllerIP0 + if zteControllerIP0 is None: + msg = (_("Controller IP is missing for ZTE driver.")) + raise exception.VolumeBackendAPIException(data=msg) + + zteUserName = self.configuration.zteUserName + if zteUserName is None: + msg = (_("User Name is missing for ZTE driver.")) + raise exception.VolumeBackendAPIException(data=msg) + + zteUserPassword = self.configuration.zteUserPassword + if zteUserPassword is None: + msg = (_("User Password is missing for ZTE driver.")) + raise exception.VolumeBackendAPIException(data=msg) + + def _get_sessionid(self): + try: + sid = self.session_id + ret = self._call(sid, 'plat.session.heartbeat') + if ret['returncode'] == zte_pub.ZTE_SUCCESS: + return sid + else: + LOG.info(_LI('heartbeat failed. Return code:' + ' %(ret)s.'), + {'ret': ret['returncode']}) + except Exception: + LOG.exception(_LE('_get_sessionid error.')) + + self._change_server() + return self._user_login() + + def _get_pool_list(self): + pool_info_list = [] + + for pool_name in self.configuration.zteStoragePool: + if pool_name: + ret = self._call_method( + 'GetPoolInfo', { + 'scPoolName': pool_name}) + pool_info = {'name': pool_name} + if ((ret['returncode'] == zte_pub.ZTE_SUCCESS) and + (ret['data']['sdwState'] == zte_pub.ZTE_STATUS_OK)): + total_capacity = ret['data']['qwTotalCapacity'] + free_capacitity = ret['data']['qwFreeCapacity'] + pool_info['total'] = ( + float(total_capacity) / units.Ki) + pool_info['free'] = ( + float(free_capacitity) / units.Ki) + pool_info_list.append(pool_info) + if not pool_info_list: + err_msg = (_('No pool available.')) + raise exception.VolumeBackendAPIException(data=err_msg) + return pool_info_list + + def _find_pool_to_create_volume(self): + pool_list = self._get_pool_list() + pool = max(pool_list, key=lambda arg: arg['free']) + return pool['name'] + + def _create_volume_in_pool(self, volume_name, volume_size, pool_name): + + vol = { + 'scPoolName': pool_name, + 'scVolName': volume_name, + 'sdwStripeDepth': self.configuration.zteChunkSize, + 'qwCapacity': float(volume_size), + 'sdwCtrlPrefer': 0xFFFF, + 'sdwCachePolicy': self.configuration.zteCachePolicy, + 'sdwAheadReadSize': self.configuration.zteAheadReadSize, + 'sdwAllocPolicy': self.configuration.ztePoolVoAllocatedPolicy, + 'sdwMovePolicy': self.configuration.ztePoolVolMovePolicy, + 'udwIsThinVol': self.configuration.ztePoolVolIsThin, + 'uqwInitAllocedCapacity': + self.configuration.ztePoolVolInitAllocatedCapacity, + 'sdwAlarmThreshold': + self.configuration.ztePoolVolAlarmThreshold, + 'sdwAlarmStopAllocFlag': + self.configuration.ztePoolVolAlarmStopAllocatedFlag, + 'dwSSDCacheSwitch': self.configuration.zteSSDCacheSwitch} + + ret = self._call_method('CreateVolOnPool', vol) + if ret['returncode'] not in [zte_pub.ZTE_ERR_OBJECT_EXIST, + zte_pub.ZTE_SUCCESS]: + err_msg = ( + _('Create volume failed. Volume name: %(name)s. ' + 'Return code: %(ret)s.') % + {'name': volume_name, + 'ret': ret['returncode']}) + raise exception.VolumeBackendAPIException( + data=err_msg) + + def _create_volume(self, volume_name, volume_size): + pool_name = self._find_pool_to_create_volume() + if pool_name: + self._create_volume_in_pool(volume_name, volume_size, pool_name) + else: + msg = _('No pool available.') + raise exception.VolumeDriverException(message=msg) + + def create_volume(self, volume): + """Create a new volume.""" + volume_name = self._translate_volume_name(volume['name']) + + volume_size = float(volume['size'] * units.Mi) + self._create_volume(volume_name, volume_size) + + def _delete_clone_volume(self, cloned_name): + cloned_name += zte_pub.ZTE_CLONE_SUFFIX + cvol_name = {'scCvolName': cloned_name} + ret = self._call_method('DelCvol', cvol_name) + + if ret['returncode'] not in [zte_pub.ZTE_ERR_CLONE_OR_SNAP_NOT_EXIST, + zte_pub.ZTE_ERR_VAS_OBJECT_NOT_EXIST, + zte_pub.ZTE_SUCCESS]: + err_msg = (_('Delete volume failed. Clone name: %(name)s. ' + 'Return code: %(ret)s.') % + {'name': cloned_name, + 'ret': ret['returncode']}) + raise exception.VolumeBackendAPIException(data=err_msg) + + def _remove_volume_from_group(self, volume): + ret = self._call_method('GetGrpNamesOfVol', {'cVolName': volume}) + if ret['returncode'] == zte_pub.ZTE_SUCCESS: + group_num = int(ret['data']['sdwMapGrpNum']) + for index in range(0, group_num): + group_name = ret['data']['cMapGrpNames'][index] + lun_ID = ret['data']['sdwLunLocalId'][index] + self._map_delete_lun(lun_ID, group_name) + + def _delete_volume(self, volume_name): + vol_name = {'cVolName': volume_name} + ret = self._call_method('DelVol', vol_name) + if ret['returncode'] not in [zte_pub.ZTE_ERR_VOLUME_NOT_EXIST, + zte_pub.ZTE_ERR_LUNDEV_NOT_EXIST, + zte_pub.ZTE_SUCCESS]: + err_msg = (_('Delete volume failed. Volume name: %(name)s.' + 'Return code: %(ret)s.') % + {'name': volume_name, + 'ret': ret['returncode']}) + raise exception.VolumeBackendAPIException(data=err_msg) + + def delete_volume(self, volume): + """Delete a volume.""" + volume_name = self._translate_volume_name(volume['name']) + LOG.debug('delete_volume: volume name: %s.', volume_name) + + self._delete_clone_relation_by_volname(volume_name, False) + self._remove_volume_from_group(volume_name) + self._delete_volume(volume_name) + + def _delete_cvol(self, cloned_name, issnapshot): + cvol_name = {'scCvolName': cloned_name} + ret = self._call_method('SyncForceDelCvol', cvol_name) + if ret['returncode'] not in [zte_pub.ZTE_ERR_CLONE_OR_SNAP_NOT_EXIST, + zte_pub.ZTE_ERR_VAS_OBJECT_NOT_EXIST, + zte_pub.ZTE_SUCCESS]: + err_msg = (_('_delete_cvol: Failed to delete clone vol. ' + 'cloned name: %(name)s with Return code: ' + '%(ret)s.') % + {'name': cloned_name, 'ret': ret['returncode']}) + if ret['returncode'] == zte_pub.ZTE_VOLUME_TASK_NOT_FINISHED: + if issnapshot: + raise exception.SnapshotIsBusy(snapshot_name=cloned_name) + else: + raise exception.VolumeIsBusy(volume_name=cloned_name) + else: + raise exception.VolumeBackendAPIException(data=err_msg) + + def _delete_clone_relation_by_volname(self, volname, issnapshot): + svol_name = {'scVolName': volname} + LOG.debug('GetCvolNamesOnVol: volume name: %s.', volname) + + ret = self._call_method('GetCvolNamesOnVol', svol_name) + data_info = ret['data'] + if ret['returncode'] == zte_pub.ZTE_SUCCESS: + sccvolnames = data_info['scCvolNames'] + for i in range(0, ret['data']['sdwCvolNum']): + cloned_name = sccvolnames[i]['scCvolName'] + self._delete_cvol(cloned_name, issnapshot) + + cloned_name = volname + zte_pub.ZTE_CLONE_SUFFIX + self._delete_cvol(cloned_name, False) + + def _create_snapshot( + self, + snapshot_name, + src_vol, + src_vol_size, + snapshot_mode): + svol_paras = { + 'scVolName': src_vol, + 'scSnapName': snapshot_name, + 'sdwSnapType': 1, + 'swRepoSpaceAlarm': 60, + 'swRepoOverflowPolicy': 0, + 'sqwRepoCapacity': float(src_vol_size * units.Mi), + 'ucIsAgent': 0, + 'ucSnapMode': snapshot_mode, + 'is_private': 0, + 'ucIsAuto': 0} + ret = self._call_method('CreateSvol', svol_paras) + if ret['returncode'] != zte_pub.ZTE_SUCCESS: + err_msg = (_('Failed to create snap.snap name: %(snapname)s,' + 'srvol name :%(srv)s with Return code: %(ret)s. ') % + {'snapname': snapshot_name, + 'srv': src_vol, + 'ret': ret['returncode']}) + raise exception.VolumeBackendAPIException(data=err_msg) + + def create_snapshot(self, snapshot): + """create a snapshot from volume""" + snapshot_name = self._translate_volume_name(snapshot['name']) + volume_name = self._translate_volume_name(snapshot['volume_name']) + volume_size = snapshot['volume_size'] + self._create_snapshot(snapshot_name, + volume_name, + volume_size, + zte_pub.ZTE_SNAPSHOT_MODE_RW) + + def _delete_snapshot(self, snapshot_name): + svol_name = {'scSnapName': snapshot_name} + + ret = self._call_method('DelSvol', svol_name) + if ret['returncode'] not in [zte_pub.ZTE_ERR_CLONE_OR_SNAP_NOT_EXIST, + zte_pub.ZTE_ERR_VAS_OBJECT_NOT_EXIST, + zte_pub.ZTE_SUCCESS]: + err_msg = (_('_delete_snapshot:Failed to delete snap.' + 'snap name: %(snapname)s with Return code: ' + '%(ret)s.') % + {'snapname': snapshot_name, + 'ret': ret['returncode']}) + if ret['returncode'] == zte_pub.ZTE_ERR_SNAP_EXIST_CLONE: + raise exception.SnapshotIsBusy(snapshot_name=snapshot_name) + else: + raise exception.VolumeBackendAPIException(data=err_msg) + + def delete_snapshot(self, snapshot): + """delete a snapshot volume""" + snapshot_name = self._translate_volume_name(snapshot['name']) + self._delete_clone_relation_by_volname(snapshot_name, True) + self._delete_snapshot(snapshot_name) + + def _extend_volume(self, volume_name, inc_size): + ext_vol_paras = {'scVolName': volume_name, + 'qwExpandCapacity': + float(inc_size * units.Ki)} + + ret = self._call_method('ExpandVolOnPool', ext_vol_paras) + if ret['returncode'] != zte_pub.ZTE_SUCCESS: + err_msg = (_('_extend_volume:Failed to extend vol.vol name:' + '%(name)s with Return code: %(ret)s.') % + {'name': volume_name, 'ret': ret['returncode']}) + raise exception.VolumeBackendAPIException(data=err_msg) + + def extend_volume(self, volume, new_size): + """extend volume size""" + size_increase = (int(new_size)) - volume['size'] + volume_name = self._translate_volume_name(volume['name']) + self._extend_volume(volume_name, size_increase) + + def _cloned_volume(self, cloned_name, src_name, vol_size, vol_type): + self._create_volume(cloned_name, vol_size) + + cvol_paras = { + 'scCvolName': cloned_name + zte_pub.ZTE_CLONE_SUFFIX, + 'scBvolName': src_name, + 'scTargetName': cloned_name, + 'sdwInitSync': 1, + 'sdwProtectRestore': 0, + 'sdwPri': 0, + 'sdwPolicy': 0, + 'sdwBvolType': vol_type} + + ret = self._call_method('CreateCvol', cvol_paras) + if ret['returncode'] != zte_pub.ZTE_SUCCESS: + self._delete_volume(cloned_name) + err_msg = (_('_cloned_volume: Failed to clone vol. ' + 'vol name: %(name)s with Return code: %(ret)s. ') % + {'name': src_name, + 'ret': ret['returncode']}) + raise exception.VolumeBackendAPIException(data=err_msg) + + def create_cloned_volume(self, volume, src_vref): + """clone a volume""" + bvol_name = self._translate_volume_name(src_vref['name']) + cvol_name = self._translate_volume_name(volume['name']) + if volume['size'] < src_vref['size']: + err_msg = (_('Cloned volume size invalid. ' + 'Clone size: %(cloned_size)s. ' + 'Src volume size: %(volume_size)s.') % + {'cloned_size': volume['size'], + 'volume_size': src_vref['size']}) + raise exception.VolumeDriverException(message=err_msg) + else: + volume_size = float( + volume['size'] * units.Mi) + + try: + self._cloned_volume( + cvol_name, + bvol_name, + volume_size, + zte_pub.ZTE_VOLUME) + except Exception: + self._delete_clone_relation_by_volname(bvol_name, False) + self._cloned_volume( + cvol_name, + bvol_name, + volume_size, + zte_pub.ZTE_VOLUME) + + def create_volume_from_snapshot(self, volume, snapshot): + """Create volume from snapshot """ + bvol_name = self._translate_volume_name(snapshot['name']) + cvol_name = self._translate_volume_name(volume['name']) + if volume['size'] < snapshot['volume_size']: + err_msg = ( + _('Cloned volume size invalid. ' + 'Clone size: %(cloned_size)s. ' + 'Src volume size: %(volume_size)s.') % + {'cloned_size': volume['size'], + 'volume_size': snapshot['volume_size']}) + raise exception.VolumeDriverException(message=err_msg) + else: + volume_size = float( + volume['size'] * units.Mi) + + try: + self._cloned_volume( + cvol_name, + bvol_name, + volume_size, + zte_pub.ZTE_SNAPSHOT) + except Exception: + self._delete_clone_relation_by_volname(bvol_name, False) + self._cloned_volume( + cvol_name, + bvol_name, + volume_size, + zte_pub.ZTE_SNAPSHOT) + + def create_export(self, context, volume, connector): + """Exports the volume """ + pass + + def ensure_export(self, context, volume): + """Driver entry point to get the export info for a existing volume.""" + pass + + def remove_export(self, context, volume_id): + """Driver entry point to remove an export for a volume.""" + pass + + def get_volume_stats(self, refresh=False): + """Get volume status.""" + if refresh: + self._update_volume_status() + + return self._stats + + def _translate_host_name(self, host_name): + new_name = 'host_' + six.text_type(self._get_md5(host_name)) + new_name = new_name.replace('-', 'R') + LOG.debug('_translate_host_name: Name in cinder: %(old)s, ' + 'new name in storage system: %(new)s.', + {'old': host_name, 'new': new_name}) + + return new_name + + def _translate_volume_name(self, vol_name): + new_name = zte_pub.ZTE_VOL_NAME_PREFIX_NEW + six.text_type( + self._get_md5(vol_name)) + new_name = new_name.replace('-', 'R') + + LOG.debug('_translate_volume_name: Name in cinder: %(old)s, ' + 'new name in storage system: %(new)s.', + {'old': vol_name, 'new': new_name}) + + return new_name + + def _get_lunid_from_vol(self, volume_name, map_group_name): + map_grp_info = {'cMapGrpName': map_group_name} + ret = self._call_method('GetMapGrpInfo', map_grp_info) + if ret['returncode'] == zte_pub.ZTE_SUCCESS: + lun_num = int(ret['data']['sdwLunNum']) + lun_info = ret['data']['tLunInfo'] + for count in range(0, lun_num): + if volume_name == lun_info[count]['cVolName']: + return lun_info[count]['sdwLunId'] + return None + elif ret['returncode'] == zte_pub.ZTE_ERR_GROUP_NOT_EXIST: + return None + else: + err_msg = (_('_get_lunid_from_vol:Get lunid from vol fail. ' + 'Group name:%(name)s vol:%(vol)s ' + 'with Return code: %(ret)s.') % + {'name': map_group_name, + 'vol': volume_name, + 'ret': ret['returncode']}) + raise exception.VolumeDriverException(message=err_msg) + + def _get_group_lunnum(self, map_group_name): + map_grp_info = {'cMapGrpName': map_group_name} + ret = self._call_method('GetMapGrpInfo', map_grp_info) + if ret['returncode'] == zte_pub.ZTE_SUCCESS: + lun_num = ret['data']['sdwLunNum'] + return int(lun_num) + elif ret['returncode'] == zte_pub.ZTE_ERR_GROUP_NOT_EXIST: + return -1 + else: + err_msg = (_('_get_group_lunnum:Get group info fail. ' + 'Group name:%(name)s with Return code: %(ret)s.') % + {'name': map_group_name, 'ret': ret['returncode']}) + raise exception.VolumeDriverException(message=err_msg) + + def _delete_group(self, map_group_name): + # before delete the group, we must delete the hosts in group + self._map_delete_host(map_group_name) + + map_grp_info = {'cMapGrpName': map_group_name} + ret = self._call_method('DelMapGrp', map_grp_info) + if ret['returncode'] not in [zte_pub.ZTE_SUCCESS, + zte_pub.ZTE_ERR_GROUP_NOT_EXIST]: + err_msg = (_('_delete_group:Del group fail. ' + 'Group name:%(name)s with Return code: %(ret)s.') % + {'name': map_group_name, 'ret': ret['returncode']}) + raise exception.VolumeDriverException(message=err_msg) + + def _map_add_lun(self, volume_name, map_group_name): + add_vol_to_grp = { + 'cMapGrpName': map_group_name, + 'sdwLunId': 0, + 'cVolName': volume_name} + ret = self._call_method('AddVolToGrp', add_vol_to_grp) + + if ret['returncode'] in [zte_pub.ZTE_SUCCESS, + zte_pub.ZTE_VOLUME_IN_GROUP, + zte_pub.ZTE_ERR_VOL_EXISTS]: + return self._get_lunid_from_vol(volume_name, map_group_name) + + err_msg = ( + _( + '_map_add_lun:fail to add vol to grp. group name:%(name)s' + ' lunid:%(lun)s ' + 'vol:%(vol)s with Return code: %(ret)s') % + {'name': map_group_name, + 'lun': 0, + 'vol': volume_name, + 'ret': ret['returncode']}) + raise exception.VolumeDriverException(message=err_msg) + + def _update_volume_group_info(self): + pool_list = self._get_pool_list() + pool_info = {'total': 0, 'free': 0} + + for item in pool_list: + pool_info['total'] += item['total'] + pool_info['free'] += item['free'] + return pool_info + + def _get_sysinfo(self): + ret = self._call_method('GetSysInfo') + if ret['returncode'] != zte_pub.ZTE_SUCCESS: + err_msg = (_('_get_sysinfo:get sys info failed. Return code: ' + '%(ret)s.'), + {'ret': ret['returncode']}) + raise exception.VolumeDriverException(message=err_msg) + + return ret['data'] + + def _update_volume_status(self): + LOG.debug("Updating volume status") + + sys_info = self._get_sysinfo() + backend_name = self.configuration.safe_get('volume_backend_name') + pool_info = self._update_volume_group_info() + data = { + "volume_backend_name": backend_name or 'ZteISCSIDriver', + 'vendor_name': sys_info['cVendor'], + 'driver_version': sys_info['cVersionName'], + 'storage_protocol': 'iSCSI', + 'multiattach': True, + 'total_capacity_gb': pool_info['total'], + 'free_capacity_gb': pool_info['free'], + 'reserved_percentage': 0, + 'QoS_support': False} + + self._stats = data + + def _create_group(self, initiator_name, map_group_name): + pass + + def _map_delete_host(self, map_group_name): + pass + + def _map_delete_lun(self, lunid, initiator_name): + pass + + +@interface.volumedriver +class ZteISCSIDriver(ZTEVolumeDriver, driver.ISCSIDriver): + """Zte iSCSI volume driver.""" + + def __init__(self, *args, **kwargs): + super(ZteISCSIDriver, self).__init__(*args, **kwargs) + + def _map_lun(self, initiator_name, volume_name, map_group_name): + self._create_group(initiator_name, map_group_name) + return self._map_add_lun(volume_name, map_group_name) + + def _get_net_cfg_ips(self): + ret = self._call_method('GetSystemNetCfg') + if ret['returncode'] != zte_pub.ZTE_SUCCESS: + err_msg = (_('get_Net_Cfg failed. Return code: %(ret)s.') % + {'ret': ret['returncode']}) + raise exception.VolumeBackendAPIException(data=err_msg) + + targetips = [] + net_cfg_info = ret['data']['tSystemNetCfg'] + for item in range(ret['data']['sdwDeviceNum']): + if (net_cfg_info[item]['udwRoleType'] == 0 and + net_cfg_info[item]['cIpAddr']): + targetips.append(net_cfg_info[item]['cIpAddr']) + return targetips + + @utils.synchronized('zte_locked_initialize_connection') + def initialize_connection(self, volume, connector): + """Map a volume to a host and return target iSCSI information.""" + initiator_name = connector['initiator'] + volume_name = self._translate_volume_name(volume['name']) + + LOG.debug('initialize_connection: Volume name: %(volume)s. ' + 'Initiator name: %(ini)s.', + {'volume': volume_name, + 'ini': initiator_name}) + + iscsi_conf = self._get_iscsi_info() + target_ips = iscsi_conf['DefaultTargetIPs'] + + target_portals = {} + target_iqns = [] + for ip in target_ips: + iqn = self._get_tgt_iqn(ip) + if iqn: + if iqn not in target_iqns: + target_iqns.append(iqn) + target_portals[iqn] = ['%s:%s' % (ip, '3260')] + else: + target_portals[iqn].append('%s:%s' % (ip, '3260')) + if not target_iqns: + msg = (_('Failed to get target ip or iqn ' + 'for initiator %(ini)s, please check config file.') % + {'ini': initiator_name}) + raise exception.VolumeDriverException(message=msg) + + map_group_name = self._translate_grp_name(initiator_name) + lunid = self._map_lun(initiator_name, volume_name, map_group_name) + + # Return iSCSI properties. + properties = { + 'target_discovered': False, + 'target_portal': target_portals[ + target_iqns[0]][0], + 'target_iqn': target_iqns[0], + 'target_lun': lunid, + 'volume_id': volume['id']} + + if target_iqns and target_portals: + properties['target_portals'] = target_portals + properties['target_iqns'] = target_iqns + + return {'driver_volume_type': 'iscsi', 'data': properties} + + @utils.synchronized('zte_locked_initialize_connection') + def terminate_connection(self, volume, connector, **kwargs): + """Delete map between a volume and a host.""" + initiator_name = connector['initiator'] + volume_name = self._translate_volume_name(volume['name']) + LOG.debug('volume name: %(volume)s, initiator name: %(ini)s.', + {'volume': volume_name, + 'ini': initiator_name}) + + map_group_name = self._translate_grp_name(initiator_name) + lunid = self._get_lunid_from_vol(volume_name, map_group_name) + self._map_delete_lun(lunid, initiator_name) + + def _get_iscsi_info(self): + iscsi_info = {} + try: + iscsi_info['DefaultTargetIPs'] = self._get_net_cfg_ips() + if not iscsi_info['DefaultTargetIPs']: + err_msg = _('Can not get target ip address. ') + raise exception.VolumeBackendAPIException(data=err_msg) + initiator_list = [] + iscsi_info['Initiator'] = initiator_list + + except Exception: + LOG.exception(_LE('_get_iscsi_info error.')) + raise + + return iscsi_info + + def _translate_grp_name(self, grp_name): + new_name = zte_pub.ZTE_HOST_GROUP_NAME_PREFIX + six.text_type( + self._get_md5(grp_name)) + new_name = new_name.replace('-', 'R') + + LOG.debug('_translate_grp_name:Name in cinder: %(old)s, ' + 'new name in storage system: %(new)s.', + {'old': grp_name, + 'new': new_name}) + + return new_name + + def _get_target_ip_ctrl(self, target_ip): + LOG.debug('_get_target_ip_ctrl:target IP is %s.', target_ip) + ret = self._call_method('GetSystemNetCfg') + if ret['returncode'] != zte_pub.ZTE_SUCCESS: + err_msg = (_('_get_target_ip_ctrl:get iscsi port list fail. ' + 'with Return code: %(ret)s.') % + {'ret': ret['returncode']}) + raise exception.VolumeBackendAPIException(data=err_msg) + count = ret['data']['sdwDeviceNum'] + for index in range(0, count): + systemnetcfg = ret['data']['tSystemNetCfg'][index] + if target_ip == systemnetcfg['cIpAddr']: + return systemnetcfg['udwCtrlId'] + return None + + def _get_tgt_iqn(self, iscsiip): + # as the given iscsiip,we need to find it's ctrl number + ip_ctrl = self._get_target_ip_ctrl(iscsiip) + + if ip_ctrl is None: + LOG.exception(_LE('_get_tgt_iqn:get iscsi ip ctrl fail, ' + 'IP is %s.'), iscsiip) + return None + + # get the ctrl iqn + ret = self._call_method('GetIscsiTargetName') + if ret['returncode'] != zte_pub.ZTE_SUCCESS: + return None + + target_info = ret['data']['tIscsiTargetInfo'] + ctrl_count = ret['data']['udwCtrlCount'] + for index in range(0, ctrl_count): + if ip_ctrl == target_info[index]['udwCtrlId']: + return target_info[index]['cTgtName'] + return None + + def _create_group(self, initiator_name, map_group_name): + + map_grp_info = {'cMapGrpName': map_group_name} + ret = self._call_method('CreateMapGrp', map_grp_info) + + if ((ret['returncode'] == zte_pub.ZTE_SUCCESS) or + (ret['returncode'] == zte_pub.ZTE_ERR_GROUP_EXIST)): + host_name = self._translate_host_name(initiator_name) + host_info = {'cHostAlias': host_name, 'ucOs': 1, 'ucType': 1, + 'cPortName': initiator_name, + 'sdwMultiPathMode': 1, 'cMulChapPass': ''} + + # create host + ret = self._call_method('CreateHost', host_info) + if ret['returncode'] not in [zte_pub.ZTE_SUCCESS, + zte_pub.ZTE_ERR_HOSTNAME_EXIST, + zte_pub.ZTE_ERR_PORT_EXIST, + zte_pub.ZTE_ERR_PORT_EXIST_OLD]: + err_msg = ( + _('create host failed. Host name:%(name)s ' + 'with Return code: %(ret)s.') % + {'name': host_name, 'ret': ret['returncode']}) + raise exception.VolumeBackendAPIException(data=err_msg) + + # If port deleted by user, add it. + port_info = { + 'cHostAlias': host_name, + 'ucType': 1, + 'cPortName': initiator_name, + 'sdwMultiPathMode': 1, + 'cMulChapPass': ''} + ret = self._call_method('AddPortToHost', port_info) + if ret['returncode'] not in [zte_pub.ZTE_SUCCESS, + zte_pub.ZTE_ERR_PORT_EXIST, + zte_pub.ZTE_ERR_PORT_EXIST_OLD]: + err_msg = (_('_create_group:add port failed. Port name: ' + '%(name)s with Return code: %(ret)s.') % + {'name': initiator_name, + 'ret': ret['returncode']}) + raise exception.VolumeBackendAPIException(data=err_msg) + + host_in_grp = { + 'ucInitName': host_name, + 'cMapGrpName': map_group_name} + ret = self._call_method('AddHostToGrp', host_in_grp) + if ret['returncode'] not in [zte_pub.ZTE_SUCCESS, + zte_pub.ZTE_ERR_HOST_EXIST, + zte_pub.ZTE_ERR_HOST_EXIST_OLD]: + self._delete_group(map_group_name) + err_msg = (_('_create_group:add host to group failed. ' + 'group name:%(name)s init name :%(init)s ' + 'with Return code: %(ret)s.') % + {'name': map_group_name, + 'init': host_name, + 'ret': ret['returncode']}) + raise exception.VolumeBackendAPIException(data=err_msg) + else: + err_msg = (_('create group failed. Group name:%(name)s ' + 'with Return code: %(ret)s.') % + {'name': map_group_name, + 'ret': ret['returncode']}) + raise exception.VolumeBackendAPIException(data=err_msg) + + def _map_delete_lun(self, lunid, initiator_name): + map_group_name = self._translate_grp_name(initiator_name) + + # lun not exist, no need to delete + if (lunid != zte_pub.ZTE_LUNID_NULL + and lunid is not None): + del_vol_from_grp = { + 'cMapGrpName': map_group_name, + 'sdwLunId': lunid} + ret = self._call_method('DelVolFromGrp', del_vol_from_grp) + if ret['returncode'] != zte_pub.ZTE_SUCCESS: + err_msg = (_('_map_lun:delete lunid from group failed. ' + 'group name:%(name)s lunid : %(lun)s ' + 'with Return code: %(ret)s.') % + {'name': map_group_name, 'lun': lunid, + 'ret': ret['returncode']}) + raise exception.VolumeBackendAPIException(data=err_msg) + + # if no lun in group,then we will delete this group + lun_num = self._get_group_lunnum(map_group_name) + if lun_num == 0: + self._delete_group(map_group_name) + + def _map_delete_host(self, map_group_name): + + map_grp_info = {'cMapGrpName': map_group_name} + ret = self._call_method('GetMapGrpInfo', map_grp_info) + if ret['returncode'] != zte_pub.ZTE_SUCCESS: + err_msg = (_('_map_delete_host:get map group info failed. ' + 'group name:%(name)s with Return code: %(ret)s.') % + {'name': map_group_name, 'ret': ret['returncode']}) + raise exception.VolumeBackendAPIException(data=err_msg) + + sdwhostnum = ret['data']['sdwHostNum'] + + if sdwhostnum > 0: + thostinfo = ret['data']['tHostInfo'] + for hostindex in range(0, int(sdwhostnum)): + initiator_name = thostinfo[hostindex]['ucHostName'] + host_in_grp = { + 'ucInitName': initiator_name, + 'cMapGrpName': map_group_name} + ret = self._call_method('DelHostFromGrp', host_in_grp) + if ret['returncode'] == zte_pub.ZTE_ERR_GROUP_NOT_EXIST: + continue + if ret['returncode'] not in [zte_pub.ZTE_SUCCESS, + zte_pub.ZTE_ERR_HOST_NOT_EXIST]: + msg = _('delete host from group failed. ') + raise exception.VolumeDriverException(message=msg) + + ret = self._call_method( + 'GetHost', {"cHostAlias": initiator_name}) + if ret['returncode'] != zte_pub.ZTE_SUCCESS: + err_msg = (_('_map_delete_host:get host info failed. ' + 'host name:%(name)s with Return code: ' + '%(ret)s.') % + {'name': initiator_name, + 'ret': ret['returncode']}) + raise exception.VolumeBackendAPIException(data=err_msg) + + return_data = ret['data'] + portnum = return_data['sdwPortNum'] + for portindex in range(0, int(portnum)): + port_host_info = {} + port_info = return_data['tPort'] + port_name = port_info[portindex]['cPortName'] + port_host_info['cPortName'] = port_name + port_host_info['cHostAlias'] = initiator_name + + ret = self._call_method('DelPortFromHost', port_host_info) + if ret['returncode'] != zte_pub.ZTE_SUCCESS: + err_msg = (_('delete port from host failed. ' + 'host name:%(name)s, port name:%(port)s ' + 'with Return code: %(ret)s.') % + {'name': initiator_name, + 'port': port_name, + 'ret': ret['returncode']}) + raise exception.VolumeBackendAPIException(data=err_msg) + + ret = self._call_method( + 'DelHost', {"cHostAlias": initiator_name}) + if (ret['returncode'] not + in [zte_pub.ZTE_SUCCESS, + zte_pub.ZTE_ERR_HOSTNAME_NOT_EXIST]): + err_msg = (_('_map_delete_host: delete host failed. ' + 'host name:%(name)s with Return code: ' + '%(ret)s') % + {'name': initiator_name, + 'ret': ret['returncode']}) + raise exception.VolumeBackendAPIException(data=err_msg) diff --git a/cinder/volume/drivers/zte/zte_pub.py b/cinder/volume/drivers/zte/zte_pub.py new file mode 100644 index 00000000000..dc9747dfe64 --- /dev/null +++ b/cinder/volume/drivers/zte/zte_pub.py @@ -0,0 +1,59 @@ +# Copyright 2016 ZTE Corporation. All rights reserved +# 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. +""" +Constants used in ZTE Volume driver. +""" + +ZTE_HOST_GROUP_NAME_PREFIX = 'HostGroup_' +ZTE_VOL_AND_SNAP_NAME_PREFIX = 'OpenStack_' +ZTE_VOL_NAME_PREFIX_NEW = 'OpenCos_' +ZTE_CLONE_SUFFIX = 'c' +ZTE_GROUP_MAX_LUN = 255 +ZTE_SUCCESS = 0 +ZTE_VOLUME_IN_GROUP = 16917029 +ZTE_WEB_LOGIN_TYPE = 5 +ZTE_ERR_GROUP_NOT_EXIST = 16916997 +ZTE_ERR_GROUP_EXIST = 16916994 +ZTE_ERR_HOSTNAME_EXIST = 16918785 +ZTE_ERR_HOSTNAME_NOT_EXIST = 16918786 +ZTE_ERR_PORT_EXIST_OLD = 16918787 +ZTE_ERR_PORT_EXIST = 16918798 +ZTE_ERR_PORT_EXIST_INOTHER = 16918799 +ZTE_ERR_HOST_NOT_EXIST = 16917004 +ZTE_ERR_HOST_EXIST_OLD = 16917002 +ZTE_ERR_HOST_EXIST = 16917015 +ZTE_ERR_HOST_EXIST_INOTHER = 16917016 +ZTE_ERR_VOLUME_NOT_EXIST = 17108999 +ZTE_ERR_OBJECT_EXIST = 16917159 +ZTE_ERR_CLONE_OR_SNAP_NOT_EXIST = 16917040 +ZTE_ERR_VOL_EXISTS = 16917000 +ZTE_ERR_LUNDEV_NOT_EXIST = 16973826 +ZTE_ERR_VAS_OBJECT_NOT_EXIST = 17436681 +ZTE_VOLUME_TASK_NOT_FINISHED = 17436959 +ZTE_ERR_SNAP_EXIST_CLONE = 16917163 +ZTE_SESSION_EXIST = 1495 +ZTE_STATUS_OK = 1 + +ZTE_VOLUME = 0 +ZTE_SNAPSHOT = 1 + +ZTE_LUNID_NULL = -1 + +ZTE_VOLUME_ALLOCATION_RATIO = 20 +ZTE_VOLUME_SNAPSHOT_PERCENT = 50 +ZTE_DEFAULT_TIMEOUT = 720 + +ZTE_SNAPSHOT_MODE_READ_ONLY = 0 +ZTE_SNAPSHOT_MODE_RW = 1 diff --git a/releasenotes/notes/zte_cinder_driver-76ba6d034e1b6f65.yaml b/releasenotes/notes/zte_cinder_driver-76ba6d034e1b6f65.yaml new file mode 100755 index 00000000000..4bdd5e8040a --- /dev/null +++ b/releasenotes/notes/zte_cinder_driver-76ba6d034e1b6f65.yaml @@ -0,0 +1,4 @@ +--- +features: + - Added backend driver for ZTE iSCSI storage. +