HNAS: Deprecating XML config file
Currently HNAS drivers use an external XML configuration file. This patch will deprecate this file and get all the configurations from cinder.conf. Co-Authored-By: Alyson Rosa <alyson.rodrigues.rosa@gmail.com> DocImpact Depends-On: I1b5d5155306c39c528af4037a5f54cf3d4dc1ffa Change-Id: I00f4ae5ccd99f8f5da12e687ef0dc9cdb1717b3b
This commit is contained in:
parent
387e543475
commit
3f292f024e
@ -109,6 +109,8 @@ from cinder.volume.drivers.hitachi import hnas_iscsi as \
|
||||
cinder_volume_drivers_hitachi_hnasiscsi
|
||||
from cinder.volume.drivers.hitachi import hnas_nfs as \
|
||||
cinder_volume_drivers_hitachi_hnasnfs
|
||||
from cinder.volume.drivers.hitachi import hnas_utils as \
|
||||
cinder_volume_drivers_hitachi_hnasutils
|
||||
from cinder.volume.drivers.hpe import hpe_3par_common as \
|
||||
cinder_volume_drivers_hpe_hpe3parcommon
|
||||
from cinder.volume.drivers.hpe import hpe_lefthand_iscsi as \
|
||||
@ -208,6 +210,7 @@ def list_opts():
|
||||
itertools.chain(
|
||||
cinder_backup_driver.service_opts,
|
||||
[cinder_cmd_volume.cluster_opt],
|
||||
cinder_volume_drivers_hitachi_hnasutils.drivers_common_opts,
|
||||
cinder_api_common.api_common_opts,
|
||||
cinder_backup_drivers_ceph.service_opts,
|
||||
cinder_volume_drivers_smbfs.volume_opts,
|
||||
|
@ -71,7 +71,7 @@ class HNASiSCSIDriverTest(test.TestCase):
|
||||
self.context, **_VOLUME)
|
||||
self.volume_clone = fake_volume.fake_volume_obj(
|
||||
self.context, **_VOLUME2)
|
||||
self.snapshot = self.snapshot = self.instantiate_snapshot(_SNAPSHOT)
|
||||
self.snapshot = self.instantiate_snapshot(_SNAPSHOT)
|
||||
|
||||
self.volume_type = fake_volume.fake_volume_type_obj(
|
||||
None,
|
||||
@ -115,7 +115,7 @@ class HNASiSCSIDriverTest(test.TestCase):
|
||||
},
|
||||
'cluster_admin_ip0': None,
|
||||
'ssh_private_key': None,
|
||||
'chap_enabled': 'True',
|
||||
'chap_enabled': True,
|
||||
'mgmt_ip0': '172.17.44.15',
|
||||
'ssh_enabled': None
|
||||
}
|
||||
@ -123,7 +123,7 @@ class HNASiSCSIDriverTest(test.TestCase):
|
||||
self.configuration = mock.Mock(spec=conf.Configuration)
|
||||
self.configuration.hds_hnas_iscsi_config_file = 'fake.xml'
|
||||
|
||||
self.mock_object(hnas_utils, 'read_config',
|
||||
self.mock_object(hnas_utils, 'read_cinder_conf',
|
||||
mock.Mock(return_value=self.parsed_xml))
|
||||
|
||||
self.driver = iscsi.HNASISCSIDriver(configuration=self.configuration)
|
||||
@ -189,7 +189,7 @@ class HNASiSCSIDriverTest(test.TestCase):
|
||||
'auth': 'Enabled'}}
|
||||
iqn = 'iqn.2014-12.10.10.10.10:evstest1.cinder-default'
|
||||
|
||||
self.driver.config['chap_enabled'] = 'False'
|
||||
self.driver.config['chap_enabled'] = False
|
||||
|
||||
self.mock_object(HNASSSHBackend, 'get_evs',
|
||||
mock.Mock(return_value='1'))
|
||||
|
@ -119,7 +119,7 @@ class HNASNFSDriverTest(test.TestCase):
|
||||
self.configuration = mock.Mock(spec=conf.Configuration)
|
||||
self.configuration.hds_hnas_nfs_config_file = 'fake.xml'
|
||||
|
||||
self.mock_object(hnas_utils, 'read_config',
|
||||
self.mock_object(hnas_utils, 'read_cinder_conf',
|
||||
mock.Mock(return_value=self.parsed_xml))
|
||||
|
||||
self.configuration = mock.Mock(spec=conf.Configuration)
|
||||
|
@ -14,6 +14,8 @@
|
||||
# under the License.
|
||||
#
|
||||
|
||||
import copy
|
||||
import ddt
|
||||
import mock
|
||||
import os
|
||||
|
||||
@ -25,10 +27,11 @@ from cinder import exception
|
||||
from cinder import test
|
||||
from cinder.tests.unit import fake_constants
|
||||
from cinder.tests.unit import fake_volume
|
||||
from cinder.volume import configuration as conf
|
||||
from cinder.volume.drivers.hitachi import hnas_iscsi
|
||||
from cinder.volume.drivers.hitachi import hnas_utils
|
||||
from cinder.volume import volume_types
|
||||
|
||||
|
||||
_VOLUME = {'name': 'cinder-volume',
|
||||
'id': fake_constants.VOLUME_ID,
|
||||
'size': 128,
|
||||
@ -37,17 +40,18 @@ _VOLUME = {'name': 'cinder-volume',
|
||||
'provider_location': 'hnas'}
|
||||
|
||||
service_parameters = ['volume_type', 'hdp']
|
||||
optional_parameters = ['hnas_cmd', 'cluster_admin_ip0', 'iscsi_ip']
|
||||
optional_parameters = ['ssc_cmd', 'cluster_admin_ip0', 'iscsi_ip']
|
||||
|
||||
config_from_cinder_conf = {
|
||||
'username': 'supervisor',
|
||||
'fs': {'silver': 'silver',
|
||||
'easy-stack': 'easy-stack'},
|
||||
'ssh_port': '22',
|
||||
'chap_enabled': None,
|
||||
'fs': {'easy-stack': 'easy-stack',
|
||||
'silver': 'silver'},
|
||||
'ssh_port': 22,
|
||||
'chap_enabled': True,
|
||||
'cluster_admin_ip0': None,
|
||||
'ssh_private_key': None,
|
||||
'mgmt_ip0': '172.24.44.15',
|
||||
'ssc_cmd': 'ssc',
|
||||
'services': {
|
||||
'default': {
|
||||
'label': u'svc_0',
|
||||
@ -57,8 +61,7 @@ config_from_cinder_conf = {
|
||||
'label': u'svc_1',
|
||||
'volume_type': 'FS-CinderDev1',
|
||||
'hdp': 'silver'}},
|
||||
'password': 'supervisor',
|
||||
'hnas_cmd': 'ssc'}
|
||||
'password': 'supervisor'}
|
||||
|
||||
valid_XML_str = '''
|
||||
<config>
|
||||
@ -122,13 +125,14 @@ XML_no_services_configured = '''
|
||||
<mgmt_ip0>172.24.44.15</mgmt_ip0>
|
||||
<username>supervisor</username>
|
||||
<password>supervisor</password>
|
||||
<ssh_port>10</ssh_port>
|
||||
<ssh_enabled>False</ssh_enabled>
|
||||
<ssh_private_key>/home/ubuntu/.ssh/id_rsa</ssh_private_key>
|
||||
</config>
|
||||
'''
|
||||
|
||||
parsed_xml = {'username': 'supervisor', 'password': 'supervisor',
|
||||
'hnas_cmd': 'ssc', 'iscsi_ip': None, 'ssh_port': '22',
|
||||
'ssc_cmd': 'ssc', 'iscsi_ip': None, 'ssh_port': 22,
|
||||
'fs': {'easy-stack': 'easy-stack',
|
||||
'FS-CinderDev1': 'FS-CinderDev1'},
|
||||
'cluster_admin_ip0': None,
|
||||
@ -149,6 +153,7 @@ invalid_XML_etree_no_service = ETree.XML(XML_no_services_configured)
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class HNASUtilsTest(test.TestCase):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
@ -156,13 +161,26 @@ class HNASUtilsTest(test.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(HNASUtilsTest, self).setUp()
|
||||
|
||||
self.fake_conf = conf.Configuration(hnas_utils.CONF)
|
||||
self.fake_conf.append_config_values(hnas_iscsi.iSCSI_OPTS)
|
||||
|
||||
self.override_config('hnas_username', 'supervisor')
|
||||
self.override_config('hnas_password', 'supervisor')
|
||||
self.override_config('hnas_mgmt_ip0', '172.24.44.15')
|
||||
self.override_config('hnas_svc0_volume_type', 'default')
|
||||
self.override_config('hnas_svc0_hdp', 'easy-stack')
|
||||
self.override_config('hnas_svc0_iscsi_ip', '172.24.49.21')
|
||||
self.override_config('hnas_svc1_volume_type', 'FS-CinderDev1')
|
||||
self.override_config('hnas_svc1_hdp', 'silver')
|
||||
self.override_config('hnas_svc1_iscsi_ip', '172.24.49.32')
|
||||
|
||||
self.context = context.get_admin_context()
|
||||
self.volume = fake_volume.fake_volume_obj(self.context, **_VOLUME)
|
||||
self.volume_type = (fake_volume.fake_volume_type_obj(None, **{
|
||||
'id': fake_constants.VOLUME_TYPE_ID, 'name': 'silver'}))
|
||||
|
||||
def test_read_config(self):
|
||||
|
||||
def test_read_xml_config(self):
|
||||
self.mock_object(os, 'access', mock.Mock(return_value=True))
|
||||
self.mock_object(ETree, 'parse',
|
||||
mock.Mock(return_value=ETree.ElementTree))
|
||||
@ -170,29 +188,29 @@ class HNASUtilsTest(test.TestCase):
|
||||
mock.Mock(return_value=valid_XML_etree))
|
||||
|
||||
xml_path = 'xml_file_found'
|
||||
out = hnas_utils.read_config(xml_path,
|
||||
service_parameters,
|
||||
optional_parameters)
|
||||
out = hnas_utils.read_xml_config(xml_path,
|
||||
service_parameters,
|
||||
optional_parameters)
|
||||
|
||||
self.assertEqual(parsed_xml, out)
|
||||
|
||||
def test_read_config_parser_error(self):
|
||||
def test_read_xml_config_parser_error(self):
|
||||
xml_file = 'hnas_nfs.xml'
|
||||
self.mock_object(os, 'access', mock.Mock(return_value=True))
|
||||
self.mock_object(ETree, 'parse',
|
||||
mock.Mock(side_effect=ETree.ParseError))
|
||||
|
||||
self.assertRaises(exception.ConfigNotFound, hnas_utils.read_config,
|
||||
self.assertRaises(exception.ConfigNotFound, hnas_utils.read_xml_config,
|
||||
xml_file, service_parameters, optional_parameters)
|
||||
|
||||
def test_read_config_not_found(self):
|
||||
def test_read_xml_config_not_found(self):
|
||||
self.mock_object(os, 'access', mock.Mock(return_value=False))
|
||||
|
||||
xml_path = 'xml_file_not_found'
|
||||
self.assertRaises(exception.NotFound, hnas_utils.read_config,
|
||||
self.assertRaises(exception.NotFound, hnas_utils.read_xml_config,
|
||||
xml_path, service_parameters, optional_parameters)
|
||||
|
||||
def test_read_config_without_services_configured(self):
|
||||
def test_read_xml_config_without_services_configured(self):
|
||||
xml_file = 'hnas_nfs.xml'
|
||||
|
||||
self.mock_object(os, 'access', mock.Mock(return_value=True))
|
||||
@ -201,10 +219,11 @@ class HNASUtilsTest(test.TestCase):
|
||||
self.mock_object(ETree.ElementTree, 'getroot',
|
||||
mock.Mock(return_value=invalid_XML_etree_no_service))
|
||||
|
||||
self.assertRaises(exception.ParameterNotFound, hnas_utils.read_config,
|
||||
xml_file, service_parameters, optional_parameters)
|
||||
self.assertRaises(exception.ParameterNotFound,
|
||||
hnas_utils.read_xml_config, xml_file,
|
||||
service_parameters, optional_parameters)
|
||||
|
||||
def test_read_config_empty_authentication_parameter(self):
|
||||
def test_read_xml_config_empty_authentication_parameter(self):
|
||||
xml_file = 'hnas_nfs.xml'
|
||||
|
||||
self.mock_object(os, 'access', mock.Mock(return_value=True))
|
||||
@ -214,10 +233,11 @@ class HNASUtilsTest(test.TestCase):
|
||||
mock.Mock(return_value=
|
||||
invalid_XML_etree_empty_parameter))
|
||||
|
||||
self.assertRaises(exception.ParameterNotFound, hnas_utils.read_config,
|
||||
xml_file, service_parameters, optional_parameters)
|
||||
self.assertRaises(exception.ParameterNotFound,
|
||||
hnas_utils.read_xml_config, xml_file,
|
||||
service_parameters, optional_parameters)
|
||||
|
||||
def test_read_config_mandatory_parameters_missing(self):
|
||||
def test_read_xml_config_mandatory_parameters_missing(self):
|
||||
xml_file = 'hnas_nfs.xml'
|
||||
|
||||
self.mock_object(os, 'access', mock.Mock(return_value=True))
|
||||
@ -227,10 +247,11 @@ class HNASUtilsTest(test.TestCase):
|
||||
mock.Mock(return_value=
|
||||
invalid_XML_etree_no_mandatory_params))
|
||||
|
||||
self.assertRaises(exception.ParameterNotFound, hnas_utils.read_config,
|
||||
xml_file, service_parameters, optional_parameters)
|
||||
self.assertRaises(exception.ParameterNotFound,
|
||||
hnas_utils.read_xml_config, xml_file,
|
||||
service_parameters, optional_parameters)
|
||||
|
||||
def test_read_config_XML_without_authentication_parameter(self):
|
||||
def test_read_config_xml_without_authentication_parameter(self):
|
||||
xml_file = 'hnas_nfs.xml'
|
||||
|
||||
self.mock_object(os, 'access', mock.Mock(return_value=True))
|
||||
@ -240,7 +261,7 @@ class HNASUtilsTest(test.TestCase):
|
||||
mock.Mock(return_value=
|
||||
invalid_XML_etree_no_authentication))
|
||||
|
||||
self.assertRaises(exception.ConfigNotFound, hnas_utils.read_config,
|
||||
self.assertRaises(exception.ConfigNotFound, hnas_utils.read_xml_config,
|
||||
xml_file, service_parameters, optional_parameters)
|
||||
|
||||
def test_get_pool_with_vol_type(self):
|
||||
@ -254,6 +275,56 @@ class HNASUtilsTest(test.TestCase):
|
||||
|
||||
self.assertEqual('silver', out)
|
||||
|
||||
def test_get_pool_with_vol_type_id_none(self):
|
||||
self.volume.volume_type_id = None
|
||||
self.volume.volume_type = self.volume_type
|
||||
|
||||
out = hnas_utils.get_pool(parsed_xml, self.volume)
|
||||
|
||||
self.assertEqual('default', out)
|
||||
|
||||
def test_get_pool_with_missing_service_label(self):
|
||||
self.mock_object(volume_types, 'get_volume_type_extra_specs',
|
||||
mock.Mock(return_value={'service_label': 'gold'}))
|
||||
|
||||
self.volume.volume_type_id = fake_constants.VOLUME_TYPE_ID
|
||||
self.volume.volume_type = self.volume_type
|
||||
|
||||
out = hnas_utils.get_pool(parsed_xml, self.volume)
|
||||
|
||||
self.assertEqual('default', out)
|
||||
|
||||
def test_get_pool_without_vol_type(self):
|
||||
out = hnas_utils.get_pool(parsed_xml, self.volume)
|
||||
self.assertEqual('default', out)
|
||||
|
||||
def test_read_cinder_conf_nfs(self):
|
||||
out = hnas_utils.read_cinder_conf(self.fake_conf, 'nfs')
|
||||
|
||||
self.assertEqual(config_from_cinder_conf, out)
|
||||
|
||||
def test_read_cinder_conf_iscsi(self):
|
||||
local_config = copy.deepcopy(config_from_cinder_conf)
|
||||
|
||||
local_config['services']['FS-CinderDev1']['iscsi_ip'] = '172.24.49.32'
|
||||
local_config['services']['default']['iscsi_ip'] = '172.24.49.21'
|
||||
|
||||
out = hnas_utils.read_cinder_conf(self.fake_conf, 'iscsi')
|
||||
|
||||
self.assertEqual(local_config, out)
|
||||
|
||||
def test_read_cinder_conf_break(self):
|
||||
self.override_config('hnas_username', None)
|
||||
self.override_config('hnas_password', None)
|
||||
self.override_config('hnas_mgmt_ip0', None)
|
||||
out = hnas_utils.read_cinder_conf(self.fake_conf, 'nfs')
|
||||
self.assertIsNone(out)
|
||||
|
||||
@ddt.data('hnas_username', 'hnas_password',
|
||||
'hnas_mgmt_ip0', 'hnas_svc0_iscsi_ip', 'hnas_svc0_volume_type',
|
||||
'hnas_svc0_hdp', )
|
||||
def test_init_invalid_conf_parameters(self, attr_name):
|
||||
self.override_config(attr_name, None)
|
||||
|
||||
self.assertRaises(exception.InvalidParameterValue,
|
||||
hnas_utils.read_cinder_conf, self.fake_conf, 'iscsi')
|
||||
|
@ -36,7 +36,7 @@ class HNASSSHBackend(object):
|
||||
def __init__(self, backend_opts):
|
||||
|
||||
self.mgmt_ip0 = backend_opts.get('mgmt_ip0')
|
||||
self.hnas_cmd = backend_opts.get('hnas_cmd', 'ssc')
|
||||
self.hnas_cmd = backend_opts.get('ssc_cmd', 'ssc')
|
||||
self.cluster_admin_ip0 = backend_opts.get('cluster_admin_ip0')
|
||||
self.ssh_port = backend_opts.get('ssh_port', '22')
|
||||
self.ssh_username = backend_opts.get('username')
|
||||
|
@ -41,14 +41,31 @@ 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 HNAS iSCSI cinder plugin')]
|
||||
help='Legacy configuration file for HNAS iSCSI Cinder '
|
||||
'plugin. This is not needed if you fill all '
|
||||
'configuration on cinder.conf',
|
||||
deprecated_for_removal=True),
|
||||
cfg.BoolOpt('hnas_chap_enabled',
|
||||
default=True,
|
||||
help='Whether the chap authentication is enabled in the '
|
||||
'iSCSI target or not.'),
|
||||
cfg.IPOpt('hnas_svc0_iscsi_ip',
|
||||
help='Service 0 iSCSI IP'),
|
||||
cfg.IPOpt('hnas_svc1_iscsi_ip',
|
||||
help='Service 1 iSCSI IP'),
|
||||
cfg.IPOpt('hnas_svc2_iscsi_ip',
|
||||
help='Service 2 iSCSI IP'),
|
||||
cfg.IPOpt('hnas_svc3_iscsi_ip',
|
||||
help='Service 3 iSCSI IP')
|
||||
]
|
||||
|
||||
|
||||
CONF = cfg.CONF
|
||||
CONF.register_opts(iSCSI_OPTS)
|
||||
|
||||
HNAS_DEFAULT_CONFIG = {'hnas_cmd': 'ssc',
|
||||
'chap_enabled': 'True',
|
||||
'ssh_port': '22'}
|
||||
HNAS_DEFAULT_CONFIG = {'ssc_cmd': 'ssc',
|
||||
'chap_enabled': True,
|
||||
'ssh_port': 22}
|
||||
MAX_HNAS_ISCSI_TARGETS = 32
|
||||
MAX_HNAS_LUS_PER_TARGET = 32
|
||||
|
||||
@ -75,7 +92,8 @@ class HNASISCSIDriver(driver.ISCSIDriver):
|
||||
Removed the option to use local SSC (ssh_enabled=False)
|
||||
Updated to use versioned objects
|
||||
Changed the class name to HNASISCSIDriver
|
||||
"""
|
||||
Deprecated XML config file
|
||||
"""
|
||||
|
||||
# ThirdPartySystems wiki page
|
||||
CI_WIKI_NAME = "Hitachi_HNAS_CI"
|
||||
@ -83,18 +101,29 @@ class HNASISCSIDriver(driver.ISCSIDriver):
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""Initializes and reads different config parameters."""
|
||||
self.configuration = kwargs.get('configuration', None)
|
||||
|
||||
self.context = {}
|
||||
self.config = {}
|
||||
|
||||
service_parameters = ['volume_type', 'hdp', 'iscsi_ip']
|
||||
optional_parameters = ['hnas_cmd', 'cluster_admin_ip0',
|
||||
optional_parameters = ['ssc_cmd', 'cluster_admin_ip0',
|
||||
'chap_enabled']
|
||||
|
||||
if self.configuration:
|
||||
self.configuration.append_config_values(
|
||||
hnas_utils.drivers_common_opts)
|
||||
self.configuration.append_config_values(iSCSI_OPTS)
|
||||
self.config = hnas_utils.read_config(
|
||||
self.configuration.hds_hnas_iscsi_config_file,
|
||||
service_parameters,
|
||||
optional_parameters)
|
||||
|
||||
# Trying to get HNAS configuration from cinder.conf
|
||||
self.config = hnas_utils.read_cinder_conf(
|
||||
self.configuration, 'iscsi')
|
||||
|
||||
# If HNAS configuration are not set on cinder.conf, tries to use
|
||||
# the deprecated XML configuration file
|
||||
if not self.config:
|
||||
self.config = hnas_utils.read_xml_config(
|
||||
self.configuration.hds_hnas_iscsi_config_file,
|
||||
service_parameters,
|
||||
optional_parameters)
|
||||
|
||||
super(HNASISCSIDriver, self).__init__(*args, **kwargs)
|
||||
self.backend = hnas_backend.HNASSSHBackend(self.config)
|
||||
@ -185,7 +214,7 @@ class HNASISCSIDriver(driver.ISCSIDriver):
|
||||
# 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':
|
||||
if self.config['chap_enabled']:
|
||||
# CHAP support is enabled. Tries to get the target secret.
|
||||
if 'iscsi_secret' not in tgt_info.keys():
|
||||
LOG.info(_LI("Retrieving secret for service: %(tgt)s."),
|
||||
@ -217,7 +246,7 @@ class HNASISCSIDriver(driver.ISCSIDriver):
|
||||
self.backend.create_target(tgt_alias, fs_label,
|
||||
tgt_info['iscsi_secret'])
|
||||
elif (tgt['tgt']['secret'] == "" and
|
||||
self.config['chap_enabled'] == 'True'):
|
||||
self.config['chap_enabled']):
|
||||
# The target exists, has no secret and chap is enabled
|
||||
self.backend.set_target_secret(tgt_alias, fs_label,
|
||||
tgt_info['iscsi_secret'])
|
||||
@ -482,7 +511,7 @@ class HNASISCSIDriver(driver.ISCSIDriver):
|
||||
properties['volume_id'] = volume.id
|
||||
properties['auth_username'] = connector['initiator']
|
||||
|
||||
if self.config['chap_enabled'] == 'True':
|
||||
if self.config['chap_enabled']:
|
||||
properties['auth_method'] = 'CHAP'
|
||||
properties['auth_password'] = secret
|
||||
|
||||
|
@ -45,12 +45,16 @@ LOG = logging.getLogger(__name__)
|
||||
NFS_OPTS = [
|
||||
cfg.StrOpt('hds_hnas_nfs_config_file',
|
||||
default='/opt/hds/hnas/cinder_nfs_conf.xml',
|
||||
help='Configuration file for HNAS NFS cinder plugin'), ]
|
||||
help='Legacy configuration file for HNAS NFS Cinder plugin. '
|
||||
'This is not needed if you fill all configuration on '
|
||||
'cinder.conf',
|
||||
deprecated_for_removal=True)
|
||||
]
|
||||
|
||||
CONF = cfg.CONF
|
||||
CONF.register_opts(NFS_OPTS)
|
||||
|
||||
HNAS_DEFAULT_CONFIG = {'hnas_cmd': 'ssc', 'ssh_port': '22'}
|
||||
HNAS_DEFAULT_CONFIG = {'ssc_cmd': 'ssc', 'ssh_port': '22'}
|
||||
|
||||
|
||||
@interface.volumedriver
|
||||
@ -74,6 +78,7 @@ class HNASNFSDriver(nfs.NfsDriver):
|
||||
Removed the option to use local SSC (ssh_enabled=False)
|
||||
Updated to use versioned objects
|
||||
Changed the class name to HNASNFSDriver
|
||||
Deprecated XML config file
|
||||
"""
|
||||
# ThirdPartySystems wiki page
|
||||
CI_WIKI_NAME = "Hitachi_HNAS_CI"
|
||||
@ -84,14 +89,25 @@ class HNASNFSDriver(nfs.NfsDriver):
|
||||
self.configuration = kwargs.get('configuration', None)
|
||||
|
||||
service_parameters = ['volume_type', 'hdp']
|
||||
optional_parameters = ['hnas_cmd', 'cluster_admin_ip0']
|
||||
optional_parameters = ['ssc_cmd', 'cluster_admin_ip0']
|
||||
|
||||
if self.configuration:
|
||||
self.configuration.append_config_values(
|
||||
hnas_utils.drivers_common_opts)
|
||||
self.configuration.append_config_values(NFS_OPTS)
|
||||
self.config = hnas_utils.read_config(
|
||||
self.configuration.hds_hnas_nfs_config_file,
|
||||
service_parameters,
|
||||
optional_parameters)
|
||||
self.config = {}
|
||||
|
||||
# Trying to get HNAS configuration from cinder.conf
|
||||
self.config = hnas_utils.read_cinder_conf(
|
||||
self.configuration, 'nfs')
|
||||
|
||||
# If HNAS configuration are not set on cinder.conf, tries to use
|
||||
# the deprecated XML configuration file
|
||||
if not self.config:
|
||||
self.config = hnas_utils.read_xml_config(
|
||||
self.configuration.hds_hnas_nfs_config_file,
|
||||
service_parameters,
|
||||
optional_parameters)
|
||||
|
||||
super(HNASNFSDriver, self).__init__(*args, **kwargs)
|
||||
self.backend = hnas_backend.HNASSSHBackend(self.config)
|
||||
|
@ -20,21 +20,120 @@ 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 _
|
||||
|
||||
from cinder.i18n import _, _LW
|
||||
from cinder.volume import volume_types
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
HNAS_DEFAULT_CONFIG = {'hnas_cmd': 'ssc',
|
||||
'chap_enabled': 'True',
|
||||
'ssh_port': '22'}
|
||||
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 array.'),
|
||||
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."))
|
||||
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."))
|
||||
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."))
|
||||
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})
|
||||
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})
|
||||
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})
|
||||
raise exception.InvalidParameterValue(err=msg)
|
||||
|
||||
|
||||
def _xml_read(root, element, check=None):
|
||||
"""Read an xml element.
|
||||
@ -68,19 +167,26 @@ def _xml_read(root, element, check=None):
|
||||
return val.strip()
|
||||
|
||||
|
||||
def read_config(xml_config_file, svc_params, optional_params):
|
||||
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
|
||||
['hnas_cmd', 'ssh_enabled', 'cluster_admin_ip0', 'chap_enabled']
|
||||
['ssc_cmd', 'cluster_admin_ip0', 'chap_enabled']
|
||||
"""
|
||||
|
||||
if not os.access(xml_config_file, os.R_OK):
|
||||
msg = (_("Can't open config file: %s") % xml_config_file)
|
||||
raise exception.NotFound(message=msg)
|
||||
msg = (_("Can't find HNAS configurations on cinder.conf neither "
|
||||
"on the path %(xml)s.") % {'xml': xml_config_file})
|
||||
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()
|
||||
@ -107,8 +213,9 @@ def read_config(xml_config_file, svc_params, optional_params):
|
||||
msg = (_("Missing authentication option (passw or private key file)."))
|
||||
raise exception.ConfigNotFound(message=msg)
|
||||
|
||||
config['ssh_port'] = _xml_read(root, 'ssh_port')
|
||||
if config['ssh_port'] is None:
|
||||
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'] = {}
|
||||
@ -141,6 +248,7 @@ def get_pool(config, volume):
|
||||
:param volume: dictionary volume reference
|
||||
:returns: the pool related to the volume
|
||||
"""
|
||||
|
||||
if volume.volume_type:
|
||||
metadata = {}
|
||||
type_id = volume.volume_type_id
|
||||
@ -150,3 +258,71 @@ def get_pool(config, volume):
|
||||
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
|
||||
|
@ -0,0 +1,7 @@
|
||||
---
|
||||
upgrade:
|
||||
- HNAS drivers will now read configuration from cinder.conf.
|
||||
deprecations:
|
||||
- The XML configuration file used by the HNAS drivers is now
|
||||
deprecated and will no longer be used in the future. Please
|
||||
use cinder.conf for all driver configuration.
|
Loading…
x
Reference in New Issue
Block a user