deb-cinder/cinder/volume/drivers/hitachi/hnas_utils.py
Alyson Rosa dd8bb0014d Improve Hitachi HNAS volume drivers log messages
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
2016-08-16 08:19:43 -03:00

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