dd8bb0014d
Adds better logging messages to the Hitachi HNAS drivers. Also fixes some old messages that were being logged as tuples. Co-Authored-By: Erlon Cruz <sombrafam@gmail.com> Also-by: Alyson Rosa <alyson.rodrigues.rosa@gmail.com> Change-Id: I08175b031a65ea6ae5cec3c73d5312175f29c890
340 lines
12 KiB
Python
340 lines
12 KiB
Python
# Copyright (c) 2016 Hitachi Data Systems, Inc.
|
|
# 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.
|
|
|
|
"""
|
|
Shared code for HNAS drivers
|
|
"""
|
|
|
|
import os
|
|
import re
|
|
|
|
from oslo_config import cfg
|
|
from oslo_log import log as logging
|
|
import six
|
|
from xml.etree import ElementTree as ETree
|
|
|
|
from cinder import exception
|
|
from cinder.i18n import _, _LW, _LE
|
|
from cinder.volume import volume_types
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
HNAS_DEFAULT_CONFIG = {'ssc_cmd': 'ssc',
|
|
'chap_enabled': True,
|
|
'ssh_port': 22}
|
|
|
|
MAX_HNAS_ISCSI_TARGETS = 32
|
|
|
|
drivers_common_opts = [
|
|
cfg.IPOpt('hnas_mgmt_ip0',
|
|
help='Management IP address of HNAS. This can '
|
|
'be any IP in the admin address on HNAS or '
|
|
'the SMU IP.'),
|
|
cfg.StrOpt('hnas_ssc_cmd',
|
|
default='ssc',
|
|
help='Command to communicate to HNAS.'),
|
|
cfg.StrOpt('hnas_username',
|
|
help='HNAS username.'),
|
|
cfg.StrOpt('hnas_password',
|
|
secret=True,
|
|
help='HNAS password.'),
|
|
cfg.PortOpt('hnas_ssh_port',
|
|
default=22,
|
|
help='Port to be used for SSH authentication.'),
|
|
cfg.StrOpt('hnas_ssh_private_key',
|
|
help='Path to the SSH private key used to '
|
|
'authenticate in HNAS SMU.'),
|
|
cfg.StrOpt('hnas_cluster_admin_ip0',
|
|
default=None,
|
|
help='The IP of the HNAS cluster admin. '
|
|
'Required only for HNAS multi-cluster setups.'),
|
|
cfg.StrOpt('hnas_svc0_volume_type',
|
|
help='Service 0 volume type'),
|
|
cfg.StrOpt('hnas_svc0_hdp',
|
|
help='Service 0 HDP'),
|
|
cfg.StrOpt('hnas_svc1_volume_type',
|
|
help='Service 1 volume type'),
|
|
cfg.StrOpt('hnas_svc1_hdp',
|
|
help='Service 1 HDP'),
|
|
cfg.StrOpt('hnas_svc2_volume_type',
|
|
help='Service 2 volume type'),
|
|
cfg.StrOpt('hnas_svc2_hdp',
|
|
help='Service 2 HDP'),
|
|
cfg.StrOpt('hnas_svc3_volume_type',
|
|
help='Service 3 volume type'),
|
|
cfg.StrOpt('hnas_svc3_hdp',
|
|
help='Service 3 HDP')
|
|
]
|
|
|
|
CONF = cfg.CONF
|
|
CONF.register_opts(drivers_common_opts)
|
|
|
|
|
|
def _check_conf_params(config, vol_type, dv_type, idx):
|
|
"""Validates if the configuration on cinder.conf is complete.
|
|
|
|
:param config: Dictionary with the driver configurations
|
|
:param vol_type: The volume type of the current pool
|
|
:param dv_type: The type of the driver (NFS or iSCSI)
|
|
:param idx: Index of the current pool
|
|
"""
|
|
|
|
# Validating the inputs on cinder.conf
|
|
if config['username'] is None:
|
|
msg = (_("The config parameter hnas_username "
|
|
"is not set in the cinder.conf."))
|
|
LOG.error(msg)
|
|
raise exception.InvalidParameterValue(err=msg)
|
|
|
|
if (config['password'] is None and
|
|
config['ssh_private_key'] is None):
|
|
msg = (_("Credentials configuration parameters "
|
|
"missing: you need to set hnas_password "
|
|
"or hnas_ssh_private_key "
|
|
"in the cinder.conf."))
|
|
LOG.error(msg)
|
|
raise exception.InvalidParameterValue(err=msg)
|
|
|
|
if config['mgmt_ip0'] is None:
|
|
msg = (_("The config parameter hnas_mgmt_ip0 "
|
|
"is not set in the cinder.conf."))
|
|
LOG.error(msg)
|
|
raise exception.InvalidParameterValue(err=msg)
|
|
|
|
if config['services'][vol_type]['hdp'] is None:
|
|
msg = (_("The config parameter hnas_svc%(idx)s_hdp is "
|
|
"not set in the cinder.conf. Note that you need to "
|
|
"have at least one pool configured.") %
|
|
{'idx': idx})
|
|
LOG.error(msg)
|
|
raise exception.InvalidParameterValue(err=msg)
|
|
|
|
if config['services'][vol_type]['volume_type'] is None:
|
|
msg = (_("The config parameter "
|
|
"hnas_svc%(idx)s_volume_type is not set "
|
|
"in the cinder.conf. Note that you need to "
|
|
"have at least one pool configured.") %
|
|
{'idx': idx})
|
|
LOG.error(msg)
|
|
raise exception.InvalidParameterValue(err=msg)
|
|
|
|
if (dv_type == 'iscsi' and
|
|
config['services'][vol_type]['iscsi_ip'] is None):
|
|
msg = (_("The config parameter "
|
|
"hnas_svc%(idx)s_iscsi_ip is not set "
|
|
"in the cinder.conf. Note that you need to "
|
|
"have at least one pool configured.") % {'idx': idx})
|
|
LOG.error(msg)
|
|
raise exception.InvalidParameterValue(err=msg)
|
|
|
|
|
|
def _xml_read(root, element, check=None):
|
|
"""Read an xml element.
|
|
|
|
:param root: XML object
|
|
:param element: string desired tag
|
|
:param check: string if present, throw exception if element missing
|
|
"""
|
|
|
|
val = root.findtext(element)
|
|
|
|
# mandatory parameter not found
|
|
if val is None and check:
|
|
LOG.error(_LE("Mandatory parameter not found: %(p)s"), {'p': element})
|
|
raise exception.ParameterNotFound(param=element)
|
|
|
|
# tag not found
|
|
if val is None:
|
|
return None
|
|
|
|
svc_tag_pattern = re.compile("svc_[0-3]$")
|
|
# tag found but empty parameter.
|
|
if not val.strip():
|
|
if svc_tag_pattern.search(element):
|
|
return ""
|
|
LOG.error(_LE("Parameter not found: %(param)s"), {'param': element})
|
|
raise exception.ParameterNotFound(param=element)
|
|
|
|
LOG.debug("%(element)s: %(val)s",
|
|
{'element': element,
|
|
'val': val if element != 'password' else '***'})
|
|
|
|
return val.strip()
|
|
|
|
|
|
def read_xml_config(xml_config_file, svc_params, optional_params):
|
|
"""Read Hitachi driver specific xml config file.
|
|
|
|
:param xml_config_file: string filename containing XML configuration
|
|
:param svc_params: parameters to configure the services
|
|
['volume_type', 'hdp', 'iscsi_ip']
|
|
:param optional_params: parameters to configure that are not mandatory
|
|
['ssc_cmd', 'cluster_admin_ip0', 'chap_enabled']
|
|
"""
|
|
|
|
if not os.access(xml_config_file, os.R_OK):
|
|
msg = (_("Can't find HNAS configurations on cinder.conf neither "
|
|
"on the path %(xml)s.") % {'xml': xml_config_file})
|
|
LOG.error(msg)
|
|
raise exception.ConfigNotFound(message=msg)
|
|
else:
|
|
LOG.warning(_LW("This XML configuration file %(xml)s is deprecated. "
|
|
"Please, move all the configurations to the "
|
|
"cinder.conf file. If you keep both configuration "
|
|
"files, the options set on cinder.conf will be "
|
|
"used."), {'xml': xml_config_file})
|
|
|
|
try:
|
|
root = ETree.parse(xml_config_file).getroot()
|
|
except ETree.ParseError:
|
|
msg = (_("Error parsing config file: %(xml_config_file)s") %
|
|
{'xml_config_file': xml_config_file})
|
|
LOG.error(msg)
|
|
raise exception.ConfigNotFound(message=msg)
|
|
|
|
# mandatory parameters for NFS and iSCSI
|
|
config = {}
|
|
arg_prereqs = ['mgmt_ip0', 'username']
|
|
for req in arg_prereqs:
|
|
config[req] = _xml_read(root, req, 'check')
|
|
|
|
# optional parameters for NFS and iSCSI
|
|
for req in optional_params:
|
|
config[req] = _xml_read(root, req)
|
|
if config[req] is None and HNAS_DEFAULT_CONFIG.get(req) is not None:
|
|
config[req] = HNAS_DEFAULT_CONFIG.get(req)
|
|
|
|
config['ssh_private_key'] = _xml_read(root, 'ssh_private_key')
|
|
config['password'] = _xml_read(root, 'password')
|
|
|
|
if config['ssh_private_key'] is None and config['password'] is None:
|
|
msg = _("Missing authentication option (passw or private key file).")
|
|
LOG.error(msg)
|
|
raise exception.ConfigNotFound(message=msg)
|
|
|
|
if _xml_read(root, 'ssh_port') is not None:
|
|
config['ssh_port'] = int(_xml_read(root, 'ssh_port'))
|
|
else:
|
|
config['ssh_port'] = HNAS_DEFAULT_CONFIG['ssh_port']
|
|
|
|
config['fs'] = {}
|
|
config['services'] = {}
|
|
|
|
# min one needed
|
|
for svc in ['svc_0', 'svc_1', 'svc_2', 'svc_3']:
|
|
if _xml_read(root, svc) is None:
|
|
continue
|
|
service = {'label': svc}
|
|
|
|
# none optional
|
|
for arg in svc_params:
|
|
service[arg] = _xml_read(root, svc + '/' + arg, 'check')
|
|
config['services'][service['volume_type']] = service
|
|
config['fs'][service['hdp']] = service['hdp']
|
|
|
|
# at least one service required!
|
|
if not config['services'].keys():
|
|
LOG.error(_LE("No service found in xml config file"))
|
|
raise exception.ParameterNotFound(param="svc_0")
|
|
|
|
return config
|
|
|
|
|
|
def get_pool(config, volume):
|
|
"""Get the pool of a volume.
|
|
|
|
:param config: dictionary containing the configuration parameters
|
|
:param volume: dictionary volume reference
|
|
:returns: the pool related to the volume
|
|
"""
|
|
|
|
if volume.volume_type:
|
|
metadata = {}
|
|
type_id = volume.volume_type_id
|
|
if type_id is not None:
|
|
metadata = volume_types.get_volume_type_extra_specs(type_id)
|
|
if metadata.get('service_label'):
|
|
if metadata['service_label'] in config['services'].keys():
|
|
return metadata['service_label']
|
|
return 'default'
|
|
|
|
|
|
def read_cinder_conf(config_opts, dv_type):
|
|
"""Reads cinder.conf
|
|
|
|
Gets the driver specific information set on cinder.conf configuration
|
|
file.
|
|
|
|
:param config_opts: Configuration object that contains the information
|
|
needed by HNAS driver
|
|
:param dv_type: The type of the driver (NFS or iSCSI)
|
|
:returns: Dictionary with the driver configuration
|
|
"""
|
|
|
|
config = {}
|
|
config['services'] = {}
|
|
config['fs'] = {}
|
|
mandatory_parameters = ['username', 'password', 'mgmt_ip0']
|
|
optional_parameters = ['ssc_cmd', 'chap_enabled',
|
|
'ssh_port', 'cluster_admin_ip0',
|
|
'ssh_private_key']
|
|
|
|
# Trying to get the mandatory parameters from cinder.conf
|
|
for opt in mandatory_parameters:
|
|
config[opt] = config_opts.safe_get('hnas_%(opt)s' % {'opt': opt})
|
|
|
|
# If there is at least one of the mandatory parameters in
|
|
# cinder.conf, we assume that we should use the configuration
|
|
# from this file.
|
|
# Otherwise, we use the configuration from the deprecated XML file.
|
|
for param in mandatory_parameters:
|
|
if config[param] is not None:
|
|
break
|
|
else:
|
|
return None
|
|
|
|
# Getting the optional parameters from cinder.conf
|
|
for opt in optional_parameters:
|
|
config[opt] = config_opts.safe_get('hnas_%(opt)s' % {'opt': opt})
|
|
|
|
# It's possible to have up to 4 pools configured.
|
|
for i in range(0, 4):
|
|
idx = six.text_type(i)
|
|
svc_vol_type = (config_opts.safe_get(
|
|
'hnas_svc%(idx)s_volume_type' % {'idx': idx}))
|
|
|
|
svc_hdp = (config_opts.safe_get(
|
|
'hnas_svc%(idx)s_hdp' % {'idx': idx}))
|
|
|
|
# It's mandatory to have at least 1 pool configured (svc_0)
|
|
if (idx == '0' or svc_vol_type is not None or
|
|
svc_hdp is not None):
|
|
config['services'][svc_vol_type] = {}
|
|
config['fs'][svc_hdp] = svc_hdp
|
|
config['services'][svc_vol_type]['hdp'] = svc_hdp
|
|
config['services'][svc_vol_type]['volume_type'] = svc_vol_type
|
|
|
|
if dv_type == 'iscsi':
|
|
svc_ip = (config_opts.safe_get(
|
|
'hnas_svc%(idx)s_iscsi_ip' % {'idx': idx}))
|
|
config['services'][svc_vol_type]['iscsi_ip'] = svc_ip
|
|
|
|
config['services'][svc_vol_type]['label'] = (
|
|
'svc_%(idx)s' % {'idx': idx})
|
|
# Checking to ensure that the pools configurations are complete
|
|
_check_conf_params(config, svc_vol_type, dv_type, idx)
|
|
|
|
return config
|