cinder/cinder/volume/drivers/huawei/huawei_utils.py
Takashi Kajinami e4341b7cea Use builtin hashlib.md5
Cinder already removed support for Python 3.8, thus it no longer has
to use the wrapper function from oslo.utils but can use the md5 method
from hashlib directly.

The md5 function from oslo.utils is being deprecated by [1].

[1] https://review.opendev.org/c/openstack/oslo.utils/+/930879

Change-Id: Ief1f4951b7b3b4fb8e7ba599cd7138f62b39d46f
2024-09-30 20:31:14 +09:00

506 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 hashlib
import json
import math
from oslo_log import log as logging
from oslo_utils import strutils
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 = hashlib.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 = str(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 = hashlib.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 = str(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 specs.items():
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': str(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': str(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
def is_support_clone_pair(client):
array_info = client.get_array_info()
version_info = array_info['PRODUCTVERSION']
if version_info >= constants.SUPPORT_CLONE_PAIR_VERSION:
return True