Merge "Huawei: Refactor driver for the second time"

This commit is contained in:
Jenkins 2016-01-22 22:53:19 +00:00 committed by Gerrit Code Review
commit 65dbadedde
9 changed files with 1529 additions and 1781 deletions

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,4 @@
# Copyright (c) 2015 Huawei Technologies Co., Ltd.
# Copyright (c) 2016 Huawei Technologies Co., Ltd.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may

View File

@ -1,4 +1,4 @@
# Copyright (c) 2015 Huawei Technologies Co., Ltd.
# Copyright (c) 2016 Huawei Technologies Co., Ltd.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
@ -23,14 +23,14 @@ LOG = logging.getLogger(__name__)
class FCZoneHelper(object):
"""FC zone helper for Huawei driver."""
def __init__(self, fcsan_lookup_service, restclient):
def __init__(self, fcsan_lookup_service, client):
self.fcsan_lookup_service = fcsan_lookup_service
self.restclient = restclient
self.client = client
def _get_fc_port_contr_map(self):
port_list = []
port_contr_map = {}
data = self.restclient.get_fc_ports_on_array()
data = self.client.get_fc_ports_on_array()
for item in data:
if item['RUNNINGSTATUS'] == constants.FC_PORT_CONNECTED:
port_list.append(item['WWN'])

View File

@ -0,0 +1,254 @@
# 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.
"""
Set Huawei private configuration into Configuration object.
For conveniently get private configuration. We parse Huawei config file
and set every property into Configuration object as an attribute.
"""
import base64
import six
from xml.etree import ElementTree as ET
from oslo_log import log as logging
from cinder import exception
from cinder.i18n import _
from cinder import utils
from cinder.volume.drivers.huawei import constants
LOG = logging.getLogger(__name__)
class HuaweiConf(object):
def __init__(self, conf):
self.conf = conf
def _encode_authentication(self):
need_encode = False
tree = ET.parse(self.conf.cinder_huawei_conf_file)
xml_root = tree.getroot()
name_node = xml_root.find('Storage/UserName')
pwd_node = xml_root.find('Storage/UserPassword')
if (name_node is not None
and not name_node.text.startswith('!$$$')):
name_node.text = '!$$$' + base64.b64encode(name_node.text)
need_encode = True
if (pwd_node is not None
and not pwd_node.text.startswith('!$$$')):
pwd_node.text = '!$$$' + base64.b64encode(pwd_node.text)
need_encode = True
if need_encode:
utils.execute('chmod',
'600',
self.conf.cinder_huawei_conf_file,
run_as_root=True)
tree.write(self.conf.cinder_huawei_conf_file, 'UTF-8')
def update_config_value(self):
self._encode_authentication()
set_attr_funcs = (self._san_address,
self._san_user,
self._san_password,
self._san_product,
self._san_protocol,
self._lun_type,
self._lun_ready_wait_interval,
self._lun_copy_wait_interval,
self._lun_timeout,
self._lun_write_type,
self._lun_mirror_switch,
self._lun_prefetch,
self._lun_policy,
self._lun_read_cache_policy,
self._lun_write_cache_policy,
self._storage_pools,
self._iscsi_default_target_ip,
self._iscsi_info,)
tree = ET.parse(self.conf.cinder_huawei_conf_file)
xml_root = tree.getroot()
for f in set_attr_funcs:
f(xml_root)
def _san_address(self, xml_root):
text = xml_root.findtext('Storage/RestURL')
if not text:
msg = _("RestURL is not configured.")
LOG.error(msg)
raise exception.InvalidInput(reason=msg)
addrs = text.split(';')
addrs = list(set([x.strip() for x in addrs if x.strip()]))
setattr(self.conf, 'san_address', addrs)
def _san_user(self, xml_root):
text = xml_root.findtext('Storage/UserName')
if not text:
msg = _("UserName is not configured.")
LOG.error(msg)
raise exception.InvalidInput(reason=msg)
user = base64.b64decode(text[4:])
setattr(self.conf, 'san_user', user)
def _san_password(self, xml_root):
text = xml_root.findtext('Storage/UserPassword')
if not text:
msg = _("UserPassword is not configured.")
LOG.error(msg)
raise exception.InvalidInput(reason=msg)
pwd = base64.b64decode(text[4:])
setattr(self.conf, 'san_password', pwd)
def _san_product(self, xml_root):
text = xml_root.findtext('Storage/Product')
if not text:
msg = _("SAN product is not configured.")
LOG.error(msg)
raise exception.InvalidInput(reason=msg)
product = text.strip()
setattr(self.conf, 'san_product', product)
def _san_protocol(self, xml_root):
text = xml_root.findtext('Storage/Protocol')
if not text:
msg = _("SAN protocol is not configured.")
LOG.error(msg)
raise exception.InvalidInput(reason=msg)
protocol = text.strip()
setattr(self.conf, 'san_protocol', protocol)
def _lun_type(self, xml_root):
lun_type = constants.THICK_LUNTYPE
text = xml_root.findtext('LUN/LUNType')
if text:
lun_type = text.strip()
if lun_type == 'Thick':
lun_type = constants.THICK_LUNTYPE
elif lun_type == 'Thin':
lun_type = constants.THIN_LUNTYPE
else:
msg = (_("Invalid lun type %s is configured.") % lun_type)
LOG.exception(msg)
raise exception.InvalidInput(reason=msg)
setattr(self.conf, 'lun_type', lun_type)
def _lun_ready_wait_interval(self, xml_root):
text = xml_root.findtext('LUN/LUNReadyWaitInterval')
interval = text.strip() if text else constants.DEFAULT_WAIT_INTERVAL
setattr(self.conf, 'lun_ready_wait_interval', int(interval))
def _lun_copy_wait_interval(self, xml_root):
text = xml_root.findtext('LUN/LUNcopyWaitInterval')
interval = text.strip() if text else constants.DEFAULT_WAIT_INTERVAL
setattr(self.conf, 'lun_copy_wait_interval', int(interval))
def _lun_timeout(self, xml_root):
text = xml_root.findtext('LUN/Timeout')
interval = text.strip() if text else constants.DEFAULT_WAIT_TIMEOUT
setattr(self.conf, 'lun_timeout', int(interval))
def _lun_write_type(self, xml_root):
text = xml_root.findtext('LUN/WriteType')
write_type = text.strip() if text else '1'
setattr(self.conf, 'lun_write_type', write_type)
def _lun_mirror_switch(self, xml_root):
text = xml_root.findtext('LUN/MirrorSwitch')
mirror_switch = text.strip() if text else '1'
setattr(self.conf, 'lun_mirror_switch', mirror_switch)
def _lun_prefetch(self, xml_root):
prefetch_type = '3'
prefetch_value = '0'
node = xml_root.find('LUN/Prefetch')
if (node is not None
and node.attrib['Type']
and node.attrib['Value']):
prefetch_type = node.attrib['Type'].strip()
if prefetch_type not in ['0', '1', '2', '3']:
msg = (_(
"Invalid prefetch type '%s' is configured. "
"PrefetchType must be in 0,1,2,3.") % prefetch_type)
LOG.error(msg)
raise exception.InvalidInput(reason=msg)
prefetch_value = node.attrib['Value'].strip()
factor = {'1': 2, '2': 2}
factor = int(factor.get('prefetch_type', '1'))
prefetch_value = int(prefetch_value) * factor
prefetch_value = six.text_type(prefetch_value)
setattr(self.conf, 'lun_prefetch_type', prefetch_type)
setattr(self.conf, 'lun_prefetch_value', prefetch_value)
def _lun_policy(self, xml_root):
setattr(self.conf, 'lun_policy', '0')
def _lun_read_cache_policy(self, xml_root):
setattr(self.conf, 'lun_read_cache_policy', '2')
def _lun_write_cache_policy(self, xml_root):
setattr(self.conf, 'lun_write_cache_policy', '5')
def _storage_pools(self, xml_root):
nodes = xml_root.findall('LUN/StoragePool')
if not nodes:
msg = _('Storage pool is not configured.')
LOG.error(msg)
raise exception.InvalidInput(reason=msg)
texts = [x.text for x in nodes]
merged_text = ';'.join(texts)
pools = set(x.strip() for x in merged_text.split(';') if x.strip())
if not pools:
msg = _('Invalid storage pool is configured.')
LOG.error(msg)
raise exception.InvalidInput(msg)
setattr(self.conf, 'storage_pools', list(pools))
def _iscsi_default_target_ip(self, xml_root):
text = xml_root.findtext('iSCSI/DefaultTargetIP')
target_ip = text.split() if text else []
setattr(self.conf, 'iscsi_default_target_ip', target_ip)
def _iscsi_info(self, xml_root):
nodes = xml_root.findall('iSCSI/Initiator')
if nodes is None:
setattr(self.conf, 'iscsi_info', [])
return
iscsi_info = []
for node in nodes:
props = {}
for item in node.items():
props[item[0].strip()] = item[1].strip()
iscsi_info.append(props)
setattr(self.conf, 'iscsi_info', iscsi_info)

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,4 @@
# Copyright (c) 2015 Huawei Technologies Co., Ltd.
# Copyright (c) 2016 Huawei Technologies Co., Ltd.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
@ -18,334 +18,19 @@ import json
import six
import time
import uuid
from xml.etree import ElementTree as ET
from oslo_log import log as logging
from oslo_service import loopingcall
from oslo_utils import units
from cinder import context
from cinder import exception
from cinder import utils
from cinder.i18n import _, _LE, _LI
from cinder.i18n import _
from cinder import objects
from cinder.volume.drivers.huawei import constants
from cinder.volume import qos_specs
from cinder.volume import volume_types
LOG = logging.getLogger(__name__)
opts_capability = {
'smarttier': False,
'smartcache': False,
'smartpartition': False,
'thin_provisioning_support': False,
'thick_provisioning_support': False,
'hypermetro': False,
}
opts_value = {
'policy': None,
'partitionname': None,
'cachename': None,
}
opts_associate = {
'smarttier': 'policy',
'smartcache': 'cachename',
'smartpartition': 'partitionname',
}
def get_volume_params(volume):
opts = {}
ctxt = context.get_admin_context()
type_id = volume['volume_type_id']
if type_id is not None:
volume_type = volume_types.get_volume_type(ctxt, type_id)
specs = dict(volume_type).get('extra_specs')
opts = _get_extra_spec_value(specs)
else:
opts.update(opts_capability)
opts.update(opts_value)
return opts
def _get_extra_spec_value(specs):
"""Return the parameters for creating the volume."""
opts = {}
opts.update(opts_capability)
opts.update(opts_value)
opts = _get_opts_from_specs(opts_capability, opts_value, specs)
LOG.debug('get_volume_params opts %(opts)s.', {'opts': opts})
return opts
def _get_opts_from_specs(opts_capability, opts_value, specs):
opts = {}
opts.update(opts_capability)
opts.update(opts_value)
for key, value in specs.items():
# Get the scope, if is using scope format.
scope = None
key_split = key.split(':')
if len(key_split) > 2 and key_split[0] != "capabilities":
continue
if len(key_split) == 1:
key = key_split[0]
else:
scope = key_split[0]
key = key_split[1]
if scope:
scope = scope.lower()
if key:
key = key.lower()
if ((not scope or scope == 'capabilities')
and key in opts_capability):
words = value.split()
if not (words and len(words) == 2 and words[0] == '<is>'):
LOG.error(_LE("Extra specs must be specified as "
"capabilities:%s='<is> True' or "
"'<is> true'."), key)
else:
opts[key] = words[1].lower()
if (scope in opts_capability) and (key in opts_value):
if (scope in opts_associate) and (opts_associate[scope] == key):
opts[key] = value
return opts
def _get_smartx_specs_params(lunsetinfo, smartx_opts):
"""Get parameters from config file for creating lun."""
# Default lun set information.
if 'LUNType' in smartx_opts:
lunsetinfo['LUNType'] = smartx_opts['LUNType']
lunsetinfo['policy'] = smartx_opts['policy']
return lunsetinfo
def get_lun_params(xml_file_path, smartx_opts):
lunsetinfo = get_lun_conf_params(xml_file_path)
lunsetinfo = _get_smartx_specs_params(lunsetinfo, smartx_opts)
return lunsetinfo
def parse_xml_file(xml_file_path):
"""Get root of xml file."""
try:
tree = ET.parse(xml_file_path)
root = tree.getroot()
return root
except IOError as err:
LOG.error(_LE('parse_xml_file: %s.'), err)
raise
def get_xml_item(xml_root, item):
"""Get the given item details.
:param xml_root: The root of xml tree
:param item: The tag need to get
:return: A dict contains all the config info of the given item.
"""
items_list = []
items = xml_root.findall(item)
for item in items:
tmp_dict = {'text': None, 'attrib': {}}
if item.text:
tmp_dict['text'] = item.text.strip()
for key, val in item.attrib.items():
if val:
item.attrib[key] = val.strip()
tmp_dict['attrib'] = item.attrib
items_list.append(tmp_dict)
return items_list
def get_conf_host_os_type(host_ip, conf):
"""Get host OS type from xml config file.
:param host_ip: The IP of Nova host
:param config: xml config file
:return: host OS type
"""
os_conf = {}
xml_file_path = conf.cinder_huawei_conf_file
root = parse_xml_file(xml_file_path)
hosts_list = get_xml_item(root, 'Host')
for host in hosts_list:
os = host['attrib']['OSType'].strip()
ips = [ip.strip() for ip in host['attrib']['HostIP'].split(',')]
os_conf[os] = ips
host_os = None
for k, v in os_conf.items():
if host_ip in v:
host_os = constants.OS_TYPE.get(k, None)
if not host_os:
host_os = constants.OS_TYPE['Linux'] # Default OS type.
LOG.debug('_get_host_os_type: Host %(ip)s OS type is %(os)s.',
{'ip': host_ip, 'os': host_os})
return host_os
def get_qos_by_volume_type(volume_type):
qos = {}
qos_specs_id = volume_type.get('qos_specs_id')
# We prefer the qos_specs association
# and override any existing extra-specs settings
# if present.
if qos_specs_id is not None:
kvs = qos_specs.get_qos_specs(context.get_admin_context(),
qos_specs_id)['specs']
else:
return qos
LOG.info(_LI('The QoS sepcs is: %s.'), kvs)
for key, value in kvs.items():
if key in constants.HUAWEI_VALID_KEYS:
if (key.upper() != 'IOTYPE') and (int(value) <= 0):
err_msg = (_('Qos config is wrong. %(key)s'
' must be set greater than 0.')
% {'key': key})
LOG.error(err_msg)
raise exception.VolumeBackendAPIException(data=err_msg)
elif (key.upper() == 'IOTYPE') and (value not in ['0', '1', '2']):
raise exception.InvalidInput(
reason=(_('Illegal value specified for IOTYPE: '
'set to either 0, 1, or 2.')))
else:
qos[key.upper()] = value
return qos
def get_volume_qos(volume):
qos = {}
ctxt = context.get_admin_context()
type_id = volume['volume_type_id']
if type_id is not None:
volume_type = volume_types.get_volume_type(ctxt, type_id)
qos = get_qos_by_volume_type(volume_type)
return qos
def _get_volume_type(type_id):
ctxt = context.get_admin_context()
return volume_types.get_volume_type(ctxt, type_id)
def get_lun_conf_params(xml_file_path):
"""Get parameters from config file for creating lun."""
lunsetinfo = {
'LUNType': 0,
'StripUnitSize': '64',
'WriteType': '1',
'MirrorSwitch': '1',
'PrefetchType': '3',
'PrefetchValue': '0',
'PrefetchTimes': '0',
'policy': '0',
'readcachepolicy': '2',
'writecachepolicy': '5',
}
# Default lun set information.
root = parse_xml_file(xml_file_path)
luntype = root.findtext('LUN/LUNType')
if luntype:
if luntype.strip() in ['Thick', 'Thin']:
lunsetinfo['LUNType'] = luntype.strip()
if luntype.strip() == 'Thick':
lunsetinfo['LUNType'] = 0
elif luntype.strip() == 'Thin':
lunsetinfo['LUNType'] = 1
else:
err_msg = (_(
"LUNType config is wrong. LUNType must be 'Thin'"
" or 'Thick'. LUNType: %(fetchtype)s.")
% {'fetchtype': luntype})
LOG.error(err_msg)
raise exception.VolumeBackendAPIException(data=err_msg)
else:
lunsetinfo['LUNType'] = 0
stripunitsize = root.findtext('LUN/StripUnitSize')
if stripunitsize is not None:
lunsetinfo['StripUnitSize'] = stripunitsize.strip()
writetype = root.findtext('LUN/WriteType')
if writetype is not None:
lunsetinfo['WriteType'] = writetype.strip()
mirrorswitch = root.findtext('LUN/MirrorSwitch')
if mirrorswitch is not None:
lunsetinfo['MirrorSwitch'] = mirrorswitch.strip()
prefetch = root.find('LUN/Prefetch')
if prefetch is not None and prefetch.attrib['Type']:
fetchtype = prefetch.attrib['Type']
if fetchtype in ['0', '1', '2', '3']:
lunsetinfo['PrefetchType'] = fetchtype.strip()
typevalue = prefetch.attrib['Value'].strip()
if lunsetinfo['PrefetchType'] == '1':
double_value = int(typevalue) * 2
typevalue_double = six.text_type(double_value)
lunsetinfo['PrefetchValue'] = typevalue_double
elif lunsetinfo['PrefetchType'] == '2':
lunsetinfo['PrefetchValue'] = typevalue
else:
err_msg = (_(
'PrefetchType config is wrong. PrefetchType'
' must be in 0,1,2,3. PrefetchType is: %(fetchtype)s.')
% {'fetchtype': fetchtype})
LOG.error(err_msg)
raise exception.VolumeBackendAPIException(data=err_msg)
else:
LOG.info(_LI(
'Use default PrefetchType. '
'PrefetchType: Intelligent.'))
return lunsetinfo
def find_luntype_in_xml(xml_file_path):
root = parse_xml_file(xml_file_path)
luntype = root.findtext('LUN/LUNType')
if luntype:
if luntype.strip() in ['Thick', 'Thin']:
if luntype.strip() == 'Thick':
luntype = constants.THICK_LUNTYPE
elif luntype.strip() == 'Thin':
luntype = constants.THIN_LUNTYPE
else:
err_msg = (_(
"LUNType config is wrong. LUNType must be 'Thin'"
" or 'Thick'. LUNType: %(fetchtype)s.")
% {'fetchtype': luntype})
LOG.error(err_msg)
raise exception.VolumeBackendAPIException(data=err_msg)
else:
luntype = constants.THICK_LUNTYPE
return luntype
def encode_name(name):
uuid_str = name.replace("-", "")
vol_uuid = uuid.UUID('urn:uuid:%s' % uuid_str)
@ -355,72 +40,21 @@ def encode_name(name):
return newuuid
def init_lun_parameters(name, parameters):
"""Initialize basic LUN parameters."""
lunparam = {"TYPE": "11",
"NAME": name,
"PARENTTYPE": "216",
"PARENTID": parameters['pool_id'],
"DESCRIPTION": parameters['volume_description'],
"ALLOCTYPE": parameters['LUNType'],
"CAPACITY": parameters['volume_size'],
"WRITEPOLICY": parameters['WriteType'],
"MIRRORPOLICY": parameters['MirrorSwitch'],
"PREFETCHPOLICY": parameters['PrefetchType'],
"PREFETCHVALUE": parameters['PrefetchValue'],
"DATATRANSFERPOLICY": parameters['policy'],
"READCACHEPOLICY": parameters['readcachepolicy'],
"WRITECACHEPOLICY": parameters['writecachepolicy'],
}
return lunparam
def encode_host_name(name):
if name and (len(name) > constants.MAX_HOSTNAME_LENGTH):
name = six.text_type(hash(name))
return name
def volume_in_use(volume):
"""Check if the given volume is in use."""
return (volume['volume_attachment'] and
len(volume['volume_attachment']) > 0)
def get_wait_interval(xml_file_path, event_type):
"""Get wait interval from huawei conf file."""
root = parse_xml_file(xml_file_path)
wait_interval = root.findtext('LUN/%s' % event_type)
if wait_interval is None:
wait_interval = constants.DEFAULT_WAIT_INTERVAL
LOG.info(_LI(
"Wait interval for %(event_type)s is not configured in huawei "
"conf file. Use default: %(default_wait_interval)d."),
{"event_type": event_type,
"default_wait_interval": wait_interval})
return int(wait_interval)
def get_default_timeout(xml_file_path):
"""Get timeout from huawei conf file."""
root = parse_xml_file(xml_file_path)
timeout = root.findtext('LUN/Timeout')
if timeout is None:
timeout = constants.DEFAULT_WAIT_TIMEOUT
LOG.info(_LI(
"Timeout is not configured in huawei conf file. "
"Use default: %(default_timeout)d."),
{"default_timeout": timeout})
return timeout
def wait_for_condition(xml_file_path, func, interval, timeout=None):
def wait_for_condition(func, interval, timeout):
start_time = time.time()
if timeout is None:
timeout = get_default_timeout(xml_file_path)
def _inner():
try:
res = func()
except Exception as ex:
raise exception.VolumeBackendAPIException(data=ex)
if res:
raise loopingcall.LoopingCallDone()
@ -434,78 +68,6 @@ def wait_for_condition(xml_file_path, func, interval, timeout=None):
timer.start(interval=interval).wait()
def get_login_info(xml_file_path):
"""Get login IP, user name and password from config file."""
login_info = {}
root = parse_xml_file(xml_file_path)
login_info['RestURL'] = root.findtext('Storage/RestURL').strip()
for key in ['UserName', 'UserPassword']:
node = root.find('Storage/%s' % key)
node_text = node.text
login_info[key] = node_text
return login_info
def _change_file_mode(filepath):
utils.execute('chmod', '640', filepath, run_as_root=True)
def get_iscsi_conf(xml_file_path):
"""Get iSCSI info from config file."""
iscsiinfo = {}
root = parse_xml_file(xml_file_path)
target_ip = root.findtext('iSCSI/DefaultTargetIP').strip()
iscsiinfo['DefaultTargetIP'] = target_ip
initiator_list = []
for dic in root.findall('iSCSI/Initiator'):
# Strip values of dict.
tmp_dic = {}
for k in dic.items():
tmp_dic[k[0]] = k[1].strip()
initiator_list.append(tmp_dic)
iscsiinfo['Initiator'] = initiator_list
return iscsiinfo
def check_qos_high_priority(qos):
"""Check QoS priority."""
for key, value in qos.items():
if (key.find('MIN') == 0) or (key.find('LATENCY') == 0):
return True
return False
def check_conf_file(xml_file_path):
"""Check the config file, make sure the essential items are set."""
root = parse_xml_file(xml_file_path)
resturl = root.findtext('Storage/RestURL')
username = root.findtext('Storage/UserName')
pwd = root.findtext('Storage/UserPassword')
pool_node = root.findall('LUN/StoragePool')
if (not resturl) or (not username) or (not pwd):
err_msg = (_(
'check_conf_file: Config file invalid. RestURL,'
' UserName and UserPassword must be set.'))
LOG.error(err_msg)
raise exception.InvalidInput(reason=err_msg)
if not pool_node:
err_msg = (_(
'check_conf_file: Config file invalid. '
'StoragePool must be set.'))
LOG.error(err_msg)
raise exception.InvalidInput(reason=err_msg)
def get_volume_size(volume):
"""Calculate the volume size.
@ -519,30 +81,6 @@ def get_volume_size(volume):
return volume_size
def get_protocol(xml_file_path):
"""Get protocol from huawei conf file."""
root = parse_xml_file(xml_file_path)
protocol = root.findtext('Storage/Protocol')
if not protocol:
err_msg = (_('Get protocol from huawei conf file error.'))
LOG.error(err_msg)
raise exception.InvalidInput(reason=err_msg)
return protocol
def get_pools(xml_file_path):
"""Get pools from huawei conf file."""
root = parse_xml_file(xml_file_path)
pool_names = root.findtext('LUN/StoragePool')
if not pool_names:
msg = _('Invalid resource pool name. '
'Please check the config file.')
LOG.error(msg)
raise exception.InvalidInput(msg)
return pool_names
def get_remote_device_info(valid_hypermetro_devices):
remote_device_info = {}
try:
@ -562,8 +100,22 @@ def get_remote_device_info(valid_hypermetro_devices):
def get_volume_metadata(volume):
if type(volume) is objects.Volume:
return volume.metadata
if 'volume_metadata' in volume:
metadata = volume.get('volume_metadata')
return {item['key']: item['value'] for item in metadata}
return {}
def get_snapshot_metadata_value(snapshot):
if type(snapshot) is objects.Snapshot:
return snapshot.metadata
if 'snapshot_metadata' in snapshot:
metadata = snapshot.get('snapshot_metadata')
return {item['key']: item['value'] for item in metadata}
return {}

View File

@ -1,4 +1,4 @@
# Copyright (c) 2015 Huawei Technologies Co., Ltd.
# Copyright (c) 2016 Huawei Technologies Co., Ltd.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
@ -14,67 +14,59 @@
# under the License.
#
import six
from oslo_log import log as logging
from cinder import exception
from cinder.i18n import _, _LI, _LW
from cinder.volume.drivers.huawei import constants
from cinder.volume.drivers.huawei import huawei_utils
from cinder.volume.drivers.huawei import rest_client
LOG = logging.getLogger(__name__)
class HuaweiHyperMetro(object):
def __init__(self, client, rmt_client, configuration):
def __init__(self, client, rmt_client, configuration, db):
self.db = db
self.client = client
self.rmt_client = rmt_client
self.configuration = configuration
self.xml_file_path = self.configuration.cinder_huawei_conf_file
def create_hypermetro(self, local_lun_id, lun_param):
def create_hypermetro(self, local_lun_id, lun_params):
"""Create hypermetro."""
metro_devices = self.configuration.hypermetro_devices
device_info = huawei_utils.get_remote_device_info(metro_devices)
self.rmt_client = rest_client.RestClient(self.configuration)
self.rmt_client.login_with_ip(device_info)
try:
# Get the remote pool info.
config_pool = device_info['StoragePool']
remote_pool = self.rmt_client.find_all_pools()
pool = self.rmt_client.find_pool_info(config_pool,
remote_pool)
# Create remote lun
lun_param['PARENTID'] = pool['ID']
remotelun_info = self.rmt_client.create_volume(lun_param)
config_pool = self.configuration.metro_storage_pools
remote_pool = self.rmt_client.get_all_pools()
pool = self.rmt_client.get_pool_info(config_pool, remote_pool)
# Create remote lun.
lun_params['pool_id'] = pool['ID']
remotelun_info = self.rmt_client.create_lun(lun_params)
remote_lun_id = remotelun_info['ID']
# Get hypermetro domain
# Get hypermetro domain.
try:
domain_name = device_info['domain_name']
domain_name = self.configuration.metro_domain_name
domain_id = self.rmt_client.get_hyper_domain_id(domain_name)
self._wait_volume_ready(remote_lun_id)
hypermetro = self._create_hypermetro_pair(domain_id,
local_lun_id,
remote_lun_id)
return hypermetro['ID'], remote_lun_id
except Exception as err:
LOG.info(_LI("Hypermetro id: %(metro_id)s. "
"Remote lun id: %(remote_lun_id)s."),
{'metro_id': hypermetro['ID'],
'remote_lun_id': remote_lun_id})
return {'hypermetro_id': hypermetro['ID'],
'remote_lun_id': remote_lun_id}
except exception.VolumeBackendAPIException as err:
self.rmt_client.delete_lun(remote_lun_id)
msg = _('Create hypermetro error. %s.') % err
raise exception.VolumeBackendAPIException(data=msg)
except exception.VolumeBackendAPIException:
raise
except Exception as err:
msg = _("Create remote LUN error. %s.") % err
LOG.exception(msg)
raise exception.VolumeBackendAPIException(data=msg)
finally:
self.rmt_client.logout()
def delete_hypermetro(self, volume):
"""Delete hypermetro."""
@ -96,21 +88,8 @@ class HuaweiHyperMetro(object):
self.client.delete_hypermetro(metro_id)
# Delete remote lun.
if remote_lun_id:
metro_devices = self.configuration.hypermetro_devices
device_info = huawei_utils.get_remote_device_info(metro_devices)
self.rmt_client = rest_client.RestClient(self.configuration)
self.rmt_client.login_with_ip(device_info)
try:
if self.rmt_client.check_lun_exist(remote_lun_id):
self.rmt_client.delete_lun(remote_lun_id)
except Exception as err:
msg = _("Delete remote lun err. %s.") % err
LOG.exception(msg)
raise exception.VolumeBackendAPIException(data=msg)
finally:
self.rmt_client.logout()
if remote_lun_id and self.rmt_client.check_lun_exist(remote_lun_id):
self.rmt_client.delete_lun(remote_lun_id)
def _create_hypermetro_pair(self, domain_id, lun_id, remote_lun_id):
"""Create a HyperMetroPair."""
@ -126,78 +105,64 @@ class HuaweiHyperMetro(object):
def connect_volume_fc(self, volume, connector):
"""Create map between a volume and a host for FC."""
self.xml_file_path = self.configuration.cinder_huawei_conf_file
metro_devices = self.configuration.hypermetro_devices
device_info = huawei_utils.get_remote_device_info(metro_devices)
self.rmt_client = rest_client.RestClient(self.configuration)
self.rmt_client.login_with_ip(device_info)
wwns = connector['wwpns']
volume_name = huawei_utils.encode_name(volume['id'])
try:
wwns = connector['wwpns']
volume_name = huawei_utils.encode_name(volume['id'])
LOG.info(_LI(
'initialize_connection_fc, initiator: %(wwpns)s,'
' volume name: %(volume)s.'),
{'wwpns': wwns,
'volume': volume_name})
LOG.info(_LI(
'initialize_connection_fc, initiator: %(wwpns)s,'
' volume name: %(volume)s.'),
{'wwpns': wwns,
'volume': volume_name})
metadata = huawei_utils.get_volume_metadata(volume)
lun_id = metadata['remote_lun_id']
metadata = huawei_utils.get_volume_metadata(volume)
lun_id = metadata['remote_lun_id']
if lun_id is None:
lun_id = self.rmt_client.get_lun_id_by_name(volume_name)
if lun_id is None:
msg = _("Can't get volume id. Volume name: %s.") % volume_name
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
if lun_id is None:
lun_id = self.rmt_client.get_volume_by_name(volume_name)
if lun_id is None:
msg = _("Can't get volume id. Volume name: %s.") % volume_name
original_host_name = connector['host']
host_name = huawei_utils.encode_host_name(original_host_name)
host_id = self.client.add_host_with_check(host_name,
original_host_name)
# Create hostgroup if not exist.
host_id = self.rmt_client.add_host_with_check(
host_name, original_host_name)
online_wwns_in_host = (
self.rmt_client.get_host_online_fc_initiators(host_id))
online_free_wwns = self.rmt_client.get_online_free_wwns()
for wwn in wwns:
if (wwn not in online_wwns_in_host
and wwn not in online_free_wwns):
wwns_in_host = (
self.rmt_client.get_host_fc_initiators(host_id))
iqns_in_host = (
self.rmt_client.get_host_iscsi_initiators(host_id))
if not (wwns_in_host or iqns_in_host):
self.rmt_client.remove_host(host_id)
msg = _('Can not add FC port to host.')
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
host_name_before_hash = None
host_name = connector['host']
if host_name and (len(host_name) > constants.MAX_HOSTNAME_LENGTH):
host_name_before_hash = host_name
host_name = six.text_type(hash(host_name))
for wwn in wwns:
if wwn in online_free_wwns:
self.rmt_client.add_fc_port_to_host(host_id, wwn)
# Create hostgroup if not exist.
host_id = self.rmt_client.add_host_with_check(
host_name, host_name_before_hash)
(tgt_port_wwns, init_targ_map) = (
self.rmt_client.get_init_targ_map(wwns))
online_wwns_in_host = (
self.rmt_client.get_host_online_fc_initiators(host_id))
online_free_wwns = self.rmt_client.get_online_free_wwns()
for wwn in wwns:
if (wwn not in online_wwns_in_host
and wwn not in online_free_wwns):
wwns_in_host = (
self.rmt_client.get_host_fc_initiators(host_id))
iqns_in_host = (
self.rmt_client.get_host_iscsi_initiators(host_id))
if not wwns_in_host and not iqns_in_host:
self.rmt_client.remove_host(host_id)
msg = _('Can not add FC port to host.')
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
for wwn in wwns:
if wwn in online_free_wwns:
self.rmt_client.add_fc_port_to_host(host_id, wwn)
(tgt_port_wwns, init_targ_map) = (
self.rmt_client.get_init_targ_map(wwns))
# Add host into hostgroup.
hostgroup_id = self.rmt_client.add_host_into_hostgroup(host_id)
map_info = self.rmt_client.do_mapping(lun_id,
hostgroup_id,
host_id)
host_lun_id = self.rmt_client.find_host_lun_id(host_id, lun_id)
except exception.VolumeBackendAPIException:
raise
except Exception as err:
msg = _("Connect volume fc: connect volume error. %s.") % err
LOG.exception(msg)
raise exception.VolumeBackendAPIException(data=msg)
# Add host into hostgroup.
hostgroup_id = self.rmt_client.add_host_to_hostgroup(host_id)
map_info = self.rmt_client.do_mapping(lun_id,
hostgroup_id,
host_id)
host_lun_id = self.rmt_client.get_host_lun_id(host_id, lun_id)
# Return FC properties.
fc_info = {'driver_volume_type': 'fibre_channel',
@ -216,76 +181,61 @@ class HuaweiHyperMetro(object):
def disconnect_volume_fc(self, volume, connector):
"""Delete map between a volume and a host for FC."""
# Login remote storage device.
self.xml_file_path = self.configuration.cinder_huawei_conf_file
metro_devices = self.configuration.hypermetro_devices
device_info = huawei_utils.get_remote_device_info(metro_devices)
self.rmt_client = rest_client.RestClient(self.configuration)
self.rmt_client.login_with_ip(device_info)
try:
wwns = connector['wwpns']
volume_name = huawei_utils.encode_name(volume['id'])
metadata = huawei_utils.get_volume_metadata(volume)
lun_id = metadata['remote_lun_id']
host_name = connector['host']
left_lunnum = -1
lungroup_id = None
view_id = None
wwns = connector['wwpns']
volume_name = huawei_utils.encode_name(volume['id'])
metadata = huawei_utils.get_volume_metadata(volume)
lun_id = metadata['remote_lun_id']
host_name = connector['host']
left_lunnum = -1
lungroup_id = None
view_id = None
LOG.info(_LI('terminate_connection_fc: volume name: %(volume)s, '
'wwpns: %(wwns)s, '
'lun_id: %(lunid)s.'),
{'volume': volume_name,
'wwns': wwns,
'lunid': lun_id},)
LOG.info(_LI('terminate_connection_fc: volume name: %(volume)s, '
'wwpns: %(wwns)s, '
'lun_id: %(lunid)s.'),
{'volume': volume_name,
'wwns': wwns,
'lunid': lun_id},)
if host_name and (len(host_name) > constants.MAX_HOSTNAME_LENGTH):
host_name = six.text_type(hash(host_name))
host_name = huawei_utils.encode_host_name(host_name)
hostid = self.rmt_client.get_host_id_by_name(host_name)
if hostid:
mapping_view_name = constants.MAPPING_VIEW_PREFIX + hostid
view_id = self.rmt_client.find_mapping_view(
mapping_view_name)
if view_id:
lungroup_id = self.rmt_client.find_lungroup_from_map(
view_id)
hostid = self.rmt_client.find_host(host_name)
if hostid:
mapping_view_name = constants.MAPPING_VIEW_PREFIX + hostid
view_id = self.rmt_client.find_mapping_view(
mapping_view_name)
if view_id:
lungroup_id = self.rmt_client.find_lungroup_from_map(
view_id)
if lun_id and self.rmt_client.check_lun_exist(lun_id):
if lungroup_id:
lungroup_ids = self.rmt_client.get_lungroupids_by_lunid(
lun_id)
if lungroup_id in lungroup_ids:
self.rmt_client.remove_lun_from_lungroup(
lungroup_id, lun_id)
else:
LOG.warning(_LW("Lun is not in lungroup. "
"Lun id: %(lun_id)s, "
"lungroup id: %(lungroup_id)s"),
{"lun_id": lun_id,
"lungroup_id": lungroup_id})
(tgt_port_wwns, init_targ_map) = (
self.rmt_client.get_init_targ_map(wwns))
hostid = self.rmt_client.find_host(host_name)
if hostid:
mapping_view_name = constants.MAPPING_VIEW_PREFIX + hostid
view_id = self.rmt_client.find_mapping_view(
mapping_view_name)
if view_id:
lungroup_id = self.rmt_client.find_lungroup_from_map(
view_id)
if lun_id and self.rmt_client.check_lun_exist(lun_id):
if lungroup_id:
left_lunnum = self.rmt_client.get_lunnum_from_lungroup(
lungroup_id)
lungroup_ids = self.rmt_client.get_lungroupids_by_lunid(
lun_id)
if lungroup_id in lungroup_ids:
self.rmt_client.remove_lun_from_lungroup(
lungroup_id, lun_id)
else:
LOG.warning(_LW("Lun is not in lungroup. "
"Lun id: %(lun_id)s, "
"lungroup id: %(lungroup_id)s"),
{"lun_id": lun_id,
"lungroup_id": lungroup_id})
except Exception as err:
msg = _("Remote detatch volume error. %s.") % err
LOG.exception(msg)
raise exception.VolumeBackendAPIException(data=msg)
finally:
self.rmt_client.logout()
(tgt_port_wwns, init_targ_map) = (
self.rmt_client.get_init_targ_map(wwns))
hostid = self.rmt_client.get_host_id_by_name(host_name)
if hostid:
mapping_view_name = constants.MAPPING_VIEW_PREFIX + hostid
view_id = self.rmt_client.find_mapping_view(
mapping_view_name)
if view_id:
lungroup_id = self.rmt_client.find_lungroup_from_map(
view_id)
if lungroup_id:
left_lunnum = self.rmt_client.get_lunnum_from_lungroup(
lungroup_id)
if int(left_lunnum) > 0:
info = {'driver_volume_type': 'fibre_channel',
@ -298,9 +248,7 @@ class HuaweiHyperMetro(object):
return info
def _wait_volume_ready(self, lun_id):
event_type = 'LUNReadyWaitInterval'
wait_interval = huawei_utils.get_wait_interval(self.xml_file_path,
event_type)
wait_interval = self.configuration.lun_ready_wait_interval
def _volume_ready():
result = self.rmt_client.get_lun_info(lun_id)
@ -309,8 +257,7 @@ class HuaweiHyperMetro(object):
return True
return False
huawei_utils.wait_for_condition(self.xml_file_path,
_volume_ready,
huawei_utils.wait_for_condition(_volume_ready,
wait_interval,
wait_interval * 10)

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,4 @@
# Copyright (c) 2015 Huawei Technologies Co., Ltd.
# Copyright (c) 2016 Huawei Technologies Co., Ltd.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
@ -16,10 +16,11 @@
from oslo_log import log as logging
from oslo_utils import excutils
from cinder import context
from cinder import exception
from cinder.i18n import _
from cinder.i18n import _, _LI
from cinder.volume.drivers.huawei import constants
from cinder.volume.drivers.huawei import huawei_utils
from cinder.volume import qos_specs
LOG = logging.getLogger(__name__)
@ -28,11 +29,52 @@ class SmartQos(object):
def __init__(self, client):
self.client = client
def create_qos(self, qos, lun_id):
@staticmethod
def get_qos_by_volume_type(volume_type):
# We prefer the qos_specs association
# and override any existing extra-specs settings
# if present.
if not volume_type:
return {}
qos_specs_id = volume_type.get('qos_specs_id')
if not qos_specs_id:
return {}
qos = {}
ctxt = context.get_admin_context()
kvs = qos_specs.get_qos_specs(ctxt, qos_specs_id)['specs']
LOG.info(_LI('The QoS sepcs is: %s.'), kvs)
for k, v in kvs.items():
if k not in constants.HUAWEI_VALID_KEYS:
continue
if k.upper() != 'IOTYPE' and int(v) <= 0:
msg = _('QoS config is wrong. %s must > 0.') % k
LOG.error(msg)
raise exception.InvalidInput(reason=msg)
elif k.upper() == 'IOTYPE' and v not in ['0', '1', '2']:
msg = _('Illegal value specified for IOTYPE: 0, 1, or 2.')
LOG.error(msg)
raise exception.InvalidInput(reason=msg)
else:
qos[k.upper()] = v
return qos
def _is_high_priority(self, qos):
"""Check QoS priority."""
for key, value in qos.items():
if (key.find('MIN') == 0) or (key.find('LATENCY') == 0):
return True
return False
def add(self, qos, lun_id):
policy_id = None
try:
# Check QoS priority.
if huawei_utils.check_qos_high_priority(qos):
if self._is_high_priority(qos):
self.client.change_lun_priority(lun_id)
# Create QoS policy and activate it.
version = self.client.find_array_version()
@ -51,13 +93,17 @@ class SmartQos(object):
if policy_id is not None:
self.client.delete_qos_policy(policy_id)
def delete_qos(self, qos_id):
def remove(self, qos_id, lun_id):
qos_info = self.client.get_qos_info(qos_id)
qos_status = qos_info['RUNNINGSTATUS']
# 2: Active status.
if qos_status == constants.STATUS_QOS_ACTIVE:
self.client.activate_deactivate_qos(qos_id, False)
self.client.delete_qos_policy(qos_id)
lun_list = self.client.get_lun_list_in_qos(qos_id, qos_info)
if len(lun_list) <= 1:
qos_status = qos_info['RUNNINGSTATUS']
# 2: Active status.
if qos_status == constants.STATUS_QOS_ACTIVE:
self.client.activate_deactivate_qos(qos_id, False)
self.client.delete_qos_policy(qos_id)
else:
self.client.remove_lun_from_qos(lun_id, lun_list, qos_id)
class SmartPartition(object):
@ -132,9 +178,9 @@ class SmartX(object):
reason=(_('Illegal value specified for thin: '
'Can not set thin and thick at the same time.')))
else:
opts['LUNType'] = 1
opts['LUNType'] = constants.THIN_LUNTYPE
if opts['thick_provisioning_support'] == 'true':
opts['LUNType'] = 0
opts['LUNType'] = constants.THICK_LUNTYPE
return opts