cinder/cinder/volume/drivers/huawei/huawei_utils.py

500 lines
16 KiB
Python

# Copyright (c) 2016 Huawei Technologies Co., Ltd.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import json
import math
from oslo_log import log as logging
from oslo_utils.secretutils import md5
from oslo_utils import strutils
import six
from cinder import context
from cinder import exception
from cinder.i18n import _
from cinder import objects
from cinder.objects import fields
from cinder import utils
from cinder.volume.drivers.huawei import constants
from cinder.volume import qos_specs
from cinder.volume import volume_types
LOG = logging.getLogger(__name__)
def encode_name(name):
encoded_name = md5(name.encode('utf-8'),
usedforsecurity=False).hexdigest()
prefix = name.split('-')[0] + '-'
postfix = encoded_name[:constants.MAX_NAME_LENGTH - len(prefix)]
return prefix + postfix
def old_encode_name(name):
pre_name = name.split("-")[0]
vol_encoded = six.text_type(hash(name))
if vol_encoded.startswith('-'):
newuuid = pre_name + vol_encoded
else:
newuuid = pre_name + '-' + vol_encoded
return newuuid
def encode_host_name(name):
if name and len(name) > constants.MAX_NAME_LENGTH:
encoded_name = md5(name.encode('utf-8'),
usedforsecurity=False).hexdigest()
return encoded_name[:constants.MAX_NAME_LENGTH]
return name
def old_encode_host_name(name):
if name and len(name) > constants.MAX_NAME_LENGTH:
name = six.text_type(hash(name))
return name
def wait_for_condition(func, interval, timeout):
"""Wait for ``func`` to return True.
This retries running func until it either returns True or raises an
exception.
:param func: The function to call.
:param interval: The interval to wait in seconds between calls.
:param timeout: The maximum time in seconds to wait.
"""
if interval == 0:
interval = 1
if timeout == 0:
timeout = 1
@utils.retry(exception.VolumeDriverException,
interval=interval,
backoff_rate=1,
retries=(math.ceil(timeout / interval)))
def _retry_call():
result = func()
if not result:
raise exception.VolumeDriverException(
_('Timed out waiting for condition.'))
_retry_call()
def _get_volume_type(volume):
if volume.volume_type:
return volume.volume_type
if volume.volume_type_id:
return volume_types.get_volume_type(None, volume.volume_type_id)
def get_volume_params(volume):
volume_type = _get_volume_type(volume)
return get_volume_type_params(volume_type)
def get_volume_type_params(volume_type):
specs = {}
if isinstance(volume_type, dict) and volume_type.get('extra_specs'):
specs = volume_type['extra_specs']
elif isinstance(volume_type, objects.VolumeType
) and volume_type.extra_specs:
specs = volume_type.extra_specs
vol_params = get_volume_params_from_specs(specs)
vol_params['qos'] = None
if isinstance(volume_type, dict) and volume_type.get('qos_specs_id'):
vol_params['qos'] = _get_qos_specs(volume_type['qos_specs_id'])
elif isinstance(volume_type, objects.VolumeType
) and volume_type.qos_specs_id:
vol_params['qos'] = _get_qos_specs(volume_type.qos_specs_id)
LOG.info('volume opts %s.', vol_params)
return vol_params
def get_volume_params_from_specs(specs):
opts = _get_opts_from_specs(specs)
_verify_smartcache_opts(opts)
_verify_smartpartition_opts(opts)
_verify_smartthin_opts(opts)
return opts
def _get_opts_from_specs(specs):
"""Get the well defined extra specs."""
opts = {}
def _get_bool_param(k, v):
words = v.split()
if len(words) == 2 and words[0] == '<is>':
return strutils.bool_from_string(words[1], strict=True)
msg = _("%(k)s spec must be specified as %(k)s='<is> True' "
"or '<is> False'.") % {'k': k}
LOG.error(msg)
raise exception.InvalidInput(reason=msg)
def _get_replication_type_param(k, v):
words = v.split()
if len(words) == 2 and words[0] == '<in>':
REPLICA_SYNC_TYPES = {'sync': constants.REPLICA_SYNC_MODEL,
'async': constants.REPLICA_ASYNC_MODEL}
sync_type = words[1].lower()
if sync_type in REPLICA_SYNC_TYPES:
return REPLICA_SYNC_TYPES[sync_type]
msg = _("replication_type spec must be specified as "
"replication_type='<in> sync' or '<in> async'.")
LOG.error(msg)
raise exception.InvalidInput(reason=msg)
def _get_string_param(k, v):
if not v:
msg = _("%s spec must be specified as a string.") % k
LOG.error(msg)
raise exception.InvalidInput(reason=msg)
return v
opts_capability = {
'capabilities:smarttier': (_get_bool_param, False),
'capabilities:smartcache': (_get_bool_param, False),
'capabilities:smartpartition': (_get_bool_param, False),
'capabilities:thin_provisioning_support': (_get_bool_param, False),
'capabilities:thick_provisioning_support': (_get_bool_param, False),
'capabilities:hypermetro': (_get_bool_param, False),
'capabilities:replication_enabled': (_get_bool_param, False),
'replication_type': (_get_replication_type_param,
constants.REPLICA_ASYNC_MODEL),
'smarttier:policy': (_get_string_param, None),
'smartcache:cachename': (_get_string_param, None),
'smartpartition:partitionname': (_get_string_param, None),
'huawei_controller:controllername': (_get_string_param, None),
'capabilities:dedup': (_get_bool_param, None),
'capabilities:compression': (_get_bool_param, None),
}
def _get_opt_key(spec_key):
key_split = spec_key.split(':')
if len(key_split) == 1:
return key_split[0]
else:
return key_split[1]
for spec_key in opts_capability:
opt_key = _get_opt_key(spec_key)
opts[opt_key] = opts_capability[spec_key][1]
for key, value in six.iteritems(specs):
if key not in opts_capability:
continue
func = opts_capability[key][0]
opt_key = _get_opt_key(key)
opts[opt_key] = func(key, value)
return opts
def _get_qos_specs(qos_specs_id):
ctxt = context.get_admin_context()
specs = qos_specs.get_qos_specs(ctxt, qos_specs_id)
if specs is None:
return {}
if specs.get('consumer') == 'front-end':
return {}
kvs = specs.get('specs', {})
LOG.info('The QoS specs is: %s.', kvs)
qos = {'IOTYPE': kvs.pop('IOType', None)}
if qos['IOTYPE'] not in constants.QOS_IOTYPES:
msg = _('IOType must be in %(types)s.'
) % {'types': constants.QOS_IOTYPES}
LOG.error(msg)
raise exception.InvalidInput(reason=msg)
for k, v in kvs.items():
if k not in constants.QOS_SPEC_KEYS:
msg = _('QoS key %s is not valid.') % k
LOG.error(msg)
raise exception.InvalidInput(reason=msg)
if int(v) <= 0:
msg = _('QoS value for %s must > 0.') % k
LOG.error(msg)
raise exception.InvalidInput(reason=msg)
qos[k.upper()] = v
if len(qos) < 2:
msg = _('QoS policy must specify both IOType and one another '
'qos spec, got policy: %s.') % qos
LOG.error(msg)
raise exception.InvalidInput(reason=msg)
qos_keys = set(qos.keys())
if (qos_keys & set(constants.UPPER_LIMIT_KEYS) and
qos_keys & set(constants.LOWER_LIMIT_KEYS)):
msg = _('QoS policy upper limit and lower limit '
'conflict, QoS policy: %s.') % qos
LOG.error(msg)
raise exception.InvalidInput(reason=msg)
return qos
def _verify_smartthin_opts(opts):
if (opts['thin_provisioning_support'] and
opts['thick_provisioning_support']):
msg = _('Cannot set thin and thick at the same time.')
LOG.error(msg)
raise exception.InvalidInput(reason=msg)
elif opts['thin_provisioning_support']:
opts['LUNType'] = constants.THIN_LUNTYPE
elif opts['thick_provisioning_support']:
opts['LUNType'] = constants.THICK_LUNTYPE
def _verify_smartcache_opts(opts):
if opts['smartcache'] and not opts['cachename']:
msg = _('Cache name is not specified, please set '
'smartcache:cachename in extra specs.')
LOG.error(msg)
raise exception.InvalidInput(reason=msg)
def _verify_smartpartition_opts(opts):
if opts['smartpartition'] and not opts['partitionname']:
msg = _('Partition name is not specified, please set '
'smartpartition:partitionname in extra specs.')
LOG.error(msg)
raise exception.InvalidInput(reason=msg)
def wait_lun_online(client, lun_id, wait_interval=None, wait_timeout=None):
def _lun_online():
result = client.get_lun_info_by_id(lun_id)
if result['HEALTHSTATUS'] != constants.STATUS_HEALTH:
err_msg = _('LUN %s is abnormal.') % lun_id
LOG.error(err_msg)
raise exception.VolumeBackendAPIException(data=err_msg)
if result['RUNNINGSTATUS'] == constants.LUN_INITIALIZING:
return False
return True
if not wait_interval:
wait_interval = constants.DEFAULT_WAIT_INTERVAL
if not wait_timeout:
wait_timeout = wait_interval * 10
wait_for_condition(_lun_online, wait_interval, wait_timeout)
def is_not_exist_exc(exc):
msg = getattr(exc, 'msg', '')
return 'not exist' in msg
def to_string(**kwargs):
return json.dumps(kwargs) if kwargs else ''
def to_dict(text):
return json.loads(text) if text else {}
def get_volume_private_data(volume):
if not volume.provider_location:
return {}
try:
info = json.loads(volume.provider_location)
except Exception:
LOG.exception("Decode volume provider_location error")
return {}
if isinstance(info, dict):
return info
# To keep compatible with old driver version
return {'huawei_lun_id': six.text_type(info),
'huawei_lun_wwn': volume.admin_metadata.get('huawei_lun_wwn'),
'huawei_sn': volume.metadata.get('huawei_sn'),
'hypermetro_id': volume.metadata.get('hypermetro_id'),
'remote_lun_id': volume.metadata.get('remote_lun_id')
}
def get_volume_metadata(volume):
if isinstance(volume, objects.Volume):
return volume.metadata
if volume.get('volume_metadata'):
return {item['key']: item['value'] for item in
volume['volume_metadata']}
return {}
def get_replication_data(volume):
if not volume.replication_driver_data:
return {}
return json.loads(volume.replication_driver_data)
def get_snapshot_private_data(snapshot):
if not snapshot.provider_location:
return {}
info = json.loads(snapshot.provider_location)
if isinstance(info, dict):
return info
# To keep compatible with old driver version
return {'huawei_snapshot_id': six.text_type(info),
'huawei_snapshot_wwn': snapshot.metadata.get(
'huawei_snapshot_wwn'),
}
def get_external_lun_info(client, external_ref):
lun_info = None
if 'source-id' in external_ref:
lun = client.get_lun_info_by_id(external_ref['source-id'])
lun_info = client.get_lun_info_by_name(lun['NAME'])
elif 'source-name' in external_ref:
lun_info = client.get_lun_info_by_name(external_ref['source-name'])
return lun_info
def get_external_snapshot_info(client, external_ref):
snapshot_info = None
if 'source-id' in external_ref:
snapshot_info = client.get_snapshot_info_by_id(
external_ref['source-id'])
elif 'source-name' in external_ref:
snapshot_info = client.get_snapshot_info_by_name(
external_ref['source-name'])
return snapshot_info
def get_lun_info(client, volume):
metadata = get_volume_private_data(volume)
volume_name = encode_name(volume.id)
lun_info = client.get_lun_info_by_name(volume_name)
# If new encoded way not found, try the old encoded way.
if not lun_info:
volume_name = old_encode_name(volume.id)
lun_info = client.get_lun_info_by_name(volume_name)
if not lun_info and metadata.get('huawei_lun_id'):
lun_info = client.get_lun_info_by_id(metadata['huawei_lun_id'])
if lun_info and ('huawei_lun_wwn' in metadata and
lun_info.get('WWN') != metadata['huawei_lun_wwn']):
return None
return lun_info
def get_snapshot_info(client, snapshot):
name = encode_name(snapshot.id)
snapshot_info = client.get_snapshot_info_by_name(name)
# If new encoded way not found, try the old encoded way.
if not snapshot_info:
name = old_encode_name(snapshot.id)
snapshot_info = client.get_snapshot_info_by_name(name)
return snapshot_info
def get_host_id(client, host_name):
encoded_name = encode_host_name(host_name)
host_id = client.get_host_id_by_name(encoded_name)
if encoded_name == host_name:
return host_id
if not host_id:
encoded_name = old_encode_host_name(host_name)
host_id = client.get_host_id_by_name(encoded_name)
return host_id
def get_hypermetro_group(client, group_id):
encoded_name = encode_name(group_id)
group = client.get_metrogroup_by_name(encoded_name)
if not group:
encoded_name = old_encode_name(group_id)
group = client.get_metrogroup_by_name(encoded_name)
return group
def get_replication_group(client, group_id):
encoded_name = encode_name(group_id)
group = client.get_replication_group_by_name(encoded_name)
if not group:
encoded_name = old_encode_name(group_id)
group = client.get_replication_group_by_name(encoded_name)
return group
def get_volume_model_update(volume, **kwargs):
private_data = get_volume_private_data(volume)
if kwargs.get('hypermetro_id'):
private_data['hypermetro_id'] = kwargs.get('hypermetro_id')
elif 'hypermetro_id' in private_data:
private_data.pop('hypermetro_id')
if 'huawei_lun_id' in kwargs:
private_data['huawei_lun_id'] = kwargs['huawei_lun_id']
if 'huawei_lun_wwn' in kwargs:
private_data['huawei_lun_wwn'] = kwargs['huawei_lun_wwn']
if 'huawei_sn' in kwargs:
private_data['huawei_sn'] = kwargs['huawei_sn']
model_update = {'provider_location': to_string(**private_data)}
if kwargs.get('replication_id'):
model_update['replication_driver_data'] = to_string(
pair_id=kwargs.get('replication_id'))
model_update['replication_status'] = fields.ReplicationStatus.ENABLED
else:
model_update['replication_driver_data'] = None
model_update['replication_status'] = fields.ReplicationStatus.DISABLED
return model_update
def get_group_type_params(group):
opts = []
for volume_type in group.volume_types:
opt = get_volume_type_params(volume_type)
opts.append(opt)
return opts