# 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)