1a5de5d4bd
This patch adds a CI_WIKI_NAME to each driver object. The value is the exact name of the ThirdPartySystems wiki page. This allows us to create an automated tool to associated jobs to drivers and track their CI reporting status correctly. This patch also updates the generate_driver_list.py script to output the driver list as a python list of dicts that can be directly consumed. Change-Id: I0ec5f705e91f680a731648cf50738ea219565f70
984 lines
42 KiB
Python
984 lines
42 KiB
Python
# 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)
|