Christian Berendt 5061ab9586 debug level logs should not be translated
According to the OpenStack translation policy available at
https://wiki.openstack.org/wiki/LoggingStandards debug messages
should not be translated. Like mentioned in several changes in
Nova by garyk this is to help prioritize log translation.

This patch adds a new hacking check - N319 - that ensures all
debug log messages don't have translations.

Change-Id: Id9c2715f25c8f2ea52235aba4bd1583655391584
Implements: blueprint debug-translation-removal
Closes-Bug: #1318713
2014-06-18 09:03:02 -06:00

700 lines
26 KiB
Python

# Copyright (c) 2014 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.
#
"""
iSCSI Cinder Volume driver for Hitachi Unified Storage (HUS-HNAS) platform.
"""
from oslo.config import cfg
from xml.etree import ElementTree as ETree
from cinder import exception
from cinder.openstack.common import excutils
from cinder.openstack.common import log as logging
from cinder import units
from cinder import utils
from cinder.volume import driver
from cinder.volume.drivers.hds.hnas_backend import HnasBackend
HDS_HNAS_ISCSI_VERSION = '1.0.0'
LOG = logging.getLogger(__name__)
iSCSI_OPTS = [
cfg.StrOpt('hds_hnas_iscsi_config_file',
default='/opt/hds/hnas/cinder_iscsi_conf.xml',
help='configuration file for HDS iSCSI cinder plugin')]
CONF = cfg.CONF
CONF.register_opts(iSCSI_OPTS)
HNAS_DEFAULT_CONFIG = {'hnas_cmd': 'ssc', 'chap_enabled': 'True'}
def factory_bend(type):
return HnasBackend()
def _loc_info(loc):
"""Parse info from location string."""
LOG.info("Parse_loc: %s" % loc)
info = {}
tup = loc.split(',')
if len(tup) < 5:
info['id_lu'] = tup[0].split('.')
return info
info['id_lu'] = tup[2].split('.')
info['tgt'] = tup
return info
def _xml_read(root, element, check=None):
"""Read an xml element."""
try:
val = root.findtext(element)
LOG.info(_("%(element)s: %(val)s")
% {'element': element,
'val': val})
if val:
return val.strip()
if check:
raise exception.ParameterNotFound(param=element)
return None
except ETree.ParseError:
if check:
with excutils.save_and_reraise_exception():
LOG.error(_("XML exception reading parameter: %s") % element)
else:
LOG.info(_("XML exception reading parameter: %s") % element)
return None
def _read_config(xml_config_file):
"""Read hds driver specific xml config file."""
try:
root = ETree.parse(xml_config_file).getroot()
except Exception:
raise exception.NotFound(message='config file not found: '
+ xml_config_file)
# mandatory parameters
config = {}
arg_prereqs = ['mgmt_ip0', 'username', 'password']
for req in arg_prereqs:
config[req] = _xml_read(root, req, 'check')
# optional parameters
for opt in ['hnas_cmd', 'chap_enabled']:
config[opt] = _xml_read(root, opt) or\
HNAS_DEFAULT_CONFIG[opt]
config['hdp'] = {}
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 ['volume_type', 'hdp', 'iscsi_ip']:
service[arg] = _xml_read(root, svc + '/' + arg, 'check')
config['services'][service['volume_type']] = service
config['hdp'][service['hdp']] = service['hdp']
# at least one service required!
if config['services'].keys() is None:
raise exception.ParameterNotFound(param="No service found")
return config
class HDSISCSIDriver(driver.ISCSIDriver):
"""HDS HNAS volume driver."""
def __init__(self, *args, **kwargs):
"""Initialize, read different config parameters."""
super(HDSISCSIDriver, self).__init__(*args, **kwargs)
self.driver_stats = {}
self.context = {}
self.configuration.append_config_values(iSCSI_OPTS)
self.config = _read_config(
self.configuration.hds_hnas_iscsi_config_file)
self.type = 'HNAS'
self.platform = self.type.lower()
LOG.info(_("Backend type: %s") % self.type)
self.bend = factory_bend(self.type)
def _array_info_get(self):
"""Get array parameters."""
out = self.bend.get_version(self.config['hnas_cmd'],
HDS_HNAS_ISCSI_VERSION,
self.config['mgmt_ip0'],
self.config['username'],
self.config['password'])
inf = out.split()
return inf[1], 'hnas_' + inf[1], inf[6]
def _get_iscsi_info(self):
"""Validate array iscsi parameters."""
out = self.bend.get_iscsi_info(self.config['hnas_cmd'],
self.config['mgmt_ip0'],
self.config['username'],
self.config['password'])
lines = out.split('\n')
# dict based on iSCSI portal ip addresses
conf = {}
for line in lines:
# only record up links
if 'CTL' in line and 'Up' in line:
inf = line.split()
(ctl, port, ip, ipp) = (inf[1], inf[3], inf[5], inf[7])
conf[ip] = {}
conf[ip]['ctl'] = ctl
conf[ip]['port'] = port
conf[ip]['iscsi_port'] = ipp
msg = ('portal: %(ip)s:%(ipp)s, CTL: %(ctl)s, port: %(port)s')
LOG.debug(msg
% {'ip': ip,
'ipp': ipp,
'ctl': ctl,
'port': port})
return conf
def _get_service(self, volume):
"""Get the available service parameters for a given volume using
its type.
:param volume: dictionary volume reference
"""
label = None
if volume['volume_type']:
label = volume['volume_type']['name']
label = label or 'default'
if label not in self.config['services'].keys():
# default works if no match is found
label = 'default'
LOG.info(_("Using default: instead of %s") % label)
LOG.info(_("Available services: %s")
% self.config['services'].keys())
if label in self.config['services'].keys():
svc = self.config['services'][label]
# HNAS - one time lookup
# see if the client supports CHAP authentication and if
# iscsi_secret has already been set, retrieve the secret if
# available, otherwise generate and store
if self.config['chap_enabled'] == 'True':
# it may not exist, create and set secret
if 'iscsi_secret' not in svc:
LOG.info(_("Retrieving secret for service: %s")
% label)
out = self.bend.get_targetsecret(self.config['hnas_cmd'],
self.config['mgmt_ip0'],
self.config['username'],
self.config['password'],
'cinder-' + label,
svc['hdp'])
svc['iscsi_secret'] = out
if svc['iscsi_secret'] == "":
svc['iscsi_secret'] = utils.generate_password()[0:15]
self.bend.set_targetsecret(self.config['hnas_cmd'],
self.config['mgmt_ip0'],
self.config['username'],
self.config['password'],
svc['iscsi_target'],
svc['hdp'],
svc['iscsi_secret'])
LOG.info("Set tgt CHAP secret for service: %s"
% (label))
else:
# We set blank password when the client does not
# support CHAP. Later on, if the client tries to create a new
# target that does not exists in the backend, we check for this
# value and use a temporary dummy password.
if 'iscsi_secret' not in svc:
# Warns in the first time
LOG.info("CHAP authentication disabled")
svc['iscsi_secret'] = ""
if 'iscsi_target' not in svc:
LOG.info(_("Retrieving target for service: %s") % label)
out = self.bend.get_targetiqn(self.config['hnas_cmd'],
self.config['mgmt_ip0'],
self.config['username'],
self.config['password'],
'cinder-' + label,
svc['hdp'],
svc['iscsi_secret'])
svc['iscsi_target'] = out
self.config['services'][label] = svc
service = (svc['iscsi_ip'], svc['iscsi_port'], svc['ctl'],
svc['port'], svc['hdp'], svc['iscsi_target'],
svc['iscsi_secret'])
else:
LOG.info(_("Available services: %s")
% self.config['services'].keys())
LOG.error(_("No configuration found for service: %s")
% label)
raise exception.ParameterNotFound(param=label)
return service
def _get_stats(self):
"""Get HDP stats from HNAS."""
total_cap = 0
total_used = 0
out = self.bend.get_hdp_info(self.config['hnas_cmd'],
self.config['mgmt_ip0'],
self.config['username'],
self.config['password'])
for line in out.split('\n'):
if 'HDP' in line:
(hdp, size, _ign, used) = line.split()[1:5] # in MB
LOG.debug("stats: looking for: %s", hdp)
if int(hdp) >= units.KiB: # HNAS fsid
hdp = line.split()[11]
if hdp in self.config['hdp'].keys():
total_cap += int(size)
total_used += int(used)
LOG.info("stats: total: %d used: %d" % (total_cap, total_used))
hnas_stat = {}
hnas_stat['total_capacity_gb'] = int(total_cap / units.KiB) # in GB
hnas_stat['free_capacity_gb'] = \
int((total_cap - total_used) / units.KiB)
be_name = self.configuration.safe_get('volume_backend_name')
hnas_stat["volume_backend_name"] = be_name or 'HDSISCSIDriver'
hnas_stat["vendor_name"] = 'HDS'
hnas_stat["driver_version"] = HDS_HNAS_ISCSI_VERSION
hnas_stat["storage_protocol"] = 'iSCSI'
hnas_stat['QoS_support'] = False
hnas_stat['reserved_percentage'] = 0
LOG.info(_("stats: stats: %s") % hnas_stat)
return hnas_stat
def _get_hdp_list(self):
"""Get HDPs from HNAS."""
out = self.bend.get_hdp_info(self.config['hnas_cmd'],
self.config['mgmt_ip0'],
self.config['username'],
self.config['password'])
hdp_list = []
for line in out.split('\n'):
if 'HDP' in line:
inf = line.split()
if int(inf[1]) >= units.KiB:
# HDP fsids start at units.KiB (1024)
hdp_list.append(inf[11])
else:
# HDP pools are 2-digits max
hdp_list.extend(inf[1:2])
# returns a list of HDP IDs
LOG.info(_("HDP list: %s") % hdp_list)
return hdp_list
def _check_hdp_list(self):
"""Verify HDPs in HNAS array.
Verify that all HDPs specified in the configuration files actually
exists on the storage.
"""
hdpl = self._get_hdp_list()
lst = self.config['hdp'].keys()
for hdp in lst:
if hdp not in hdpl:
LOG.error(_("HDP not found: %s") % hdp)
err = "HDP not found: " + hdp
raise exception.ParameterNotFound(param=err)
# status, verify corresponding status is Normal
def _id_to_vol(self, volume_id):
"""Given the volume id, retrieve the volume object from database.
:param volume_id: volume id string
"""
vol = self.db.volume_get(self.context, volume_id)
return vol
def _update_vol_location(self, volume_id, loc):
"""Update the provider location.
:param volume_id: volume id string
:param loc: string provider location value
"""
update = {'provider_location': loc}
self.db.volume_update(self.context, volume_id, update)
def check_for_setup_error(self):
"""Returns an error if prerequisites aren't met."""
pass
def do_setup(self, context):
"""Setup and verify HDS HNAS storage connection."""
self.context = context
(self.arid, self.hnas_name, self.lumax) = self._array_info_get()
self._check_hdp_list()
iscsi_info = self._get_iscsi_info()
LOG.info(_("do_setup: %s") % iscsi_info)
for svc in self.config['services'].keys():
svc_ip = self.config['services'][svc]['iscsi_ip']
if svc_ip in iscsi_info.keys():
LOG.info(_("iSCSI portal found for service: %s") % svc_ip)
self.config['services'][svc]['port'] = \
iscsi_info[svc_ip]['port']
self.config['services'][svc]['ctl'] = iscsi_info[svc_ip]['ctl']
self.config['services'][svc]['iscsi_port'] = \
iscsi_info[svc_ip]['iscsi_port']
else: # config iscsi address not found on device!
LOG.error(_("iSCSI portal not found for service: %s") % svc_ip)
raise exception.ParameterNotFound(param=svc_ip)
def ensure_export(self, context, volume):
pass
def create_export(self, context, volume):
"""Create an export. Moved to initialize_connection.
:param context:
:param volume: volume reference
"""
name = volume['name']
LOG.debug("create_export %(name)s" % {'name': name})
pass
def remove_export(self, context, volume):
"""Disconnect a volume from an attached instance.
:param context: context
:param volume: dictionary volume referencej
"""
provider = volume['provider_location']
name = volume['name']
LOG.debug("remove_export provider %(provider)s on %(name)s"
% {'provider': provider,
'name': name})
pass
def create_volume(self, volume):
"""Create a LU on HNAS.
:param volume: ditctionary volume reference
"""
service = self._get_service(volume)
(_ip, _ipp, _ctl, _port, hdp, target, secret) = service
out = self.bend.create_lu(self.config['hnas_cmd'],
self.config['mgmt_ip0'],
self.config['username'],
self.config['password'],
hdp,
'%s' % (int(volume['size']) * units.KiB),
volume['name'])
LOG.info(_("create_volume: create_lu returns %s") % out)
lun = self.arid + '.' + out.split()[1]
sz = int(out.split()[5])
# Example: 92210013.volume-44d7e29b-2aa4-4606-8bc4-9601528149fd
LOG.info(_("LUN %(lun)s of size %(sz)s MB is created.")
% {'lun': lun, 'sz': sz})
return {'provider_location': lun}
def create_cloned_volume(self, dst, src):
"""Create a clone of a volume.
:param dst: ditctionary destination volume reference
:param src: ditctionary source volume reference
"""
if src['size'] != dst['size']:
msg = 'clone volume size mismatch'
raise exception.VolumeBackendAPIException(data=msg)
service = self._get_service(dst)
(_ip, _ipp, _ctl, _port, hdp, target, secret) = service
size = int(src['size']) * units.KiB
source_vol = self._id_to_vol(src['id'])
(arid, slun) = _loc_info(source_vol['provider_location'])['id_lu']
out = self.bend.create_dup(self.config['hnas_cmd'],
self.config['mgmt_ip0'],
self.config['username'],
self.config['password'],
slun, hdp, '%s' % size,
dst['name'])
lun = self.arid + '.' + out.split()[1]
size = int(out.split()[5])
LOG.debug("LUN %(lun)s of size %(size)s MB is cloned."
% {'lun': lun,
'size': size})
return {'provider_location': lun}
def extend_volume(self, volume, new_size):
"""Extend an existing volume.
:param volume: dictionary volume reference
:param new_size: int size in GB to extend
"""
service = self._get_service(volume)
(_ip, _ipp, _ctl, _port, hdp, target, secret) = service
(arid, lun) = _loc_info(volume['provider_location'])['id_lu']
self.bend.extend_vol(self.config['hnas_cmd'],
self.config['mgmt_ip0'],
self.config['username'],
self.config['password'],
hdp, lun,
'%s' % (new_size * units.KiB),
volume['name'])
LOG.info(_("LUN %(lun)s extended to %(size)s GB.")
% {'lun': lun, 'size': new_size})
def delete_volume(self, volume):
"""Delete an LU on HNAS.
:param volume: dictionary volume reference
"""
prov_loc = volume['provider_location']
if prov_loc is None:
LOG.error("delete_vol: provider location empty.")
return
info = _loc_info(prov_loc)
(arid, lun) = info['id_lu']
if 'tgt' in info.keys(): # connected?
LOG.info("delete lun loc %s" % info['tgt'])
# loc = id.lun
(_portal, iqn, loc, ctl, port, hlun) = info['tgt']
self.bend.del_iscsi_conn(self.config['hnas_cmd'],
self.config['mgmt_ip0'],
self.config['username'],
self.config['password'],
ctl, iqn, hlun)
name = self.hnas_name
LOG.debug("delete lun %(lun)s on %(name)s"
% {'lun': lun,
'name': name})
service = self._get_service(volume)
(_ip, _ipp, _ctl, _port, hdp, target, secret) = service
self.bend.delete_lu(self.config['hnas_cmd'],
self.config['mgmt_ip0'],
self.config['username'],
self.config['password'],
hdp, lun)
def initialize_connection(self, volume, connector):
"""Map the created volume to connector['initiator'].
:param volume: dictionary volume reference
:param connector: dictionary connector reference
"""
LOG.info("initialize volume %s connector %s" % (volume, connector))
# connector[ip, host, wwnns, unititator, wwp/
service = self._get_service(volume)
(ip, ipp, ctl, port, _hdp, target, secret) = service
info = _loc_info(volume['provider_location'])
if 'tgt' in info.keys(): # spurious repeat connection
# print info.keys()
LOG.debug("initiate_conn: tgt already set %s" % info['tgt'])
(arid, lun) = info['id_lu']
loc = arid + '.' + lun
# sps, use target if provided
iqn = target
out = self.bend.add_iscsi_conn(self.config['hnas_cmd'],
self.config['mgmt_ip0'],
self.config['username'],
self.config['password'],
lun, _hdp, port, iqn,
connector['initiator'])
hnas_portal = ip + ':' + ipp
# sps need hlun, fulliqn
hlun = out.split()[1]
fulliqn = out.split()[13]
tgt = hnas_portal + ',' + iqn + ',' + loc + ',' + ctl + ','
tgt += port + ',' + hlun
LOG.info("initiate: connection %s" % tgt)
properties = {}
properties['provider_location'] = tgt
self._update_vol_location(volume['id'], tgt)
properties['target_discovered'] = False
properties['target_portal'] = hnas_portal
properties['target_iqn'] = fulliqn
properties['target_lun'] = hlun
properties['volume_id'] = volume['id']
properties['auth_username'] = connector['initiator']
if self.config['chap_enabled'] == 'True':
properties['auth_method'] = 'CHAP'
properties['auth_password'] = secret
return {'driver_volume_type': 'iscsi', 'data': properties}
def terminate_connection(self, volume, connector, **kwargs):
"""Terminate a connection to a volume.
:param volume: dictionary volume reference
:param connector: dictionary connector reference
"""
info = _loc_info(volume['provider_location'])
if 'tgt' not in info.keys(): # spurious disconnection
LOG.warn("terminate_conn: provider location empty.")
return
(arid, lun) = info['id_lu']
(_portal, iqn, loc, ctl, port, hlun) = info['tgt']
LOG.info("terminate: connection %s" % volume['provider_location'])
self.bend.del_iscsi_conn(self.config['hnas_cmd'],
self.config['mgmt_ip0'],
self.config['username'],
self.config['password'],
ctl, iqn, hlun)
self._update_vol_location(volume['id'], loc)
return {'provider_location': loc}
def create_volume_from_snapshot(self, volume, snapshot):
"""Create a volume from a snapshot.
:param volume: dictionary volume reference
:param snapshot: dictionary snapshot reference
"""
size = int(snapshot['volume_size']) * units.KiB
(arid, slun) = _loc_info(snapshot['provider_location'])['id_lu']
service = self._get_service(volume)
(_ip, _ipp, _ctl, _port, hdp, target, secret) = service
out = self.bend.create_dup(self.config['hnas_cmd'],
self.config['mgmt_ip0'],
self.config['username'],
self.config['password'],
slun, hdp, '%s' % (size),
volume['name'])
lun = self.arid + '.' + out.split()[1]
sz = int(out.split()[5])
LOG.debug("LUN %(lun)s of size %(sz)s MB is created from snapshot."
% {'lun': lun, 'sz': sz})
return {'provider_location': lun}
def create_snapshot(self, snapshot):
"""Create a snapshot.
:param snapshot: dictionary snapshot reference
"""
source_vol = self._id_to_vol(snapshot['volume_id'])
service = self._get_service(source_vol)
(_ip, _ipp, _ctl, _port, hdp, target, secret) = service
size = int(snapshot['volume_size']) * units.KiB
(arid, slun) = _loc_info(source_vol['provider_location'])['id_lu']
out = self.bend.create_dup(self.config['hnas_cmd'],
self.config['mgmt_ip0'],
self.config['username'],
self.config['password'],
slun, hdp,
'%s' % (size),
snapshot['name'])
lun = self.arid + '.' + out.split()[1]
size = int(out.split()[5])
LOG.debug("LUN %(lun)s of size %(size)s MB is created."
% {'lun': lun, 'size': size})
return {'provider_location': lun}
def delete_snapshot(self, snapshot):
"""Delete a snapshot.
:param snapshot: dictionary snapshot reference
"""
loc = snapshot['provider_location']
# to take care of spurious input
if loc is None:
# which could cause exception.
return
(arid, lun) = loc.split('.')
source_vol = self._id_to_vol(snapshot['volume_id'])
service = self._get_service(source_vol)
(_ip, _ipp, _ctl, _port, hdp, target, secret) = service
myid = self.arid
if arid != myid:
LOG.error(_('Array mismatch %(myid)s vs %(arid)s')
% {'myid': myid,
'arid': arid})
msg = 'Array id mismatch in delete snapshot'
raise exception.VolumeBackendAPIException(data=msg)
self.bend.delete_lu(self.config['hnas_cmd'],
self.config['mgmt_ip0'],
self.config['username'],
self.config['password'],
hdp, lun)
LOG.debug("LUN %s is deleted.", lun)
return
def get_volume_stats(self, refresh=False):
"""Get volume stats. If 'refresh', run update the stats first."""
if refresh:
self.driver_stats = self._get_stats()
return self.driver_stats