diff --git a/cinder/opts.py b/cinder/opts.py index 521855df83a..aa2a0b4f670 100644 --- a/cinder/opts.py +++ b/cinder/opts.py @@ -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, diff --git a/cinder/tests/unit/volume/drivers/hitachi/test_hitachi_hnas_iscsi.py b/cinder/tests/unit/volume/drivers/hitachi/test_hitachi_hnas_iscsi.py index b50ab255e6a..b5d789806a7 100644 --- a/cinder/tests/unit/volume/drivers/hitachi/test_hitachi_hnas_iscsi.py +++ b/cinder/tests/unit/volume/drivers/hitachi/test_hitachi_hnas_iscsi.py @@ -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')) diff --git a/cinder/tests/unit/volume/drivers/hitachi/test_hitachi_hnas_nfs.py b/cinder/tests/unit/volume/drivers/hitachi/test_hitachi_hnas_nfs.py index d4e75d3aa13..ecdb9e5a81f 100644 --- a/cinder/tests/unit/volume/drivers/hitachi/test_hitachi_hnas_nfs.py +++ b/cinder/tests/unit/volume/drivers/hitachi/test_hitachi_hnas_nfs.py @@ -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) diff --git a/cinder/tests/unit/volume/drivers/hitachi/test_hitachi_hnas_utils.py b/cinder/tests/unit/volume/drivers/hitachi/test_hitachi_hnas_utils.py index e6ba81837db..34e00e4b7e1 100644 --- a/cinder/tests/unit/volume/drivers/hitachi/test_hitachi_hnas_utils.py +++ b/cinder/tests/unit/volume/drivers/hitachi/test_hitachi_hnas_utils.py @@ -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 = ''' @@ -122,13 +125,14 @@ XML_no_services_configured = ''' 172.24.44.15 supervisor supervisor + 10 False /home/ubuntu/.ssh/id_rsa ''' 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') diff --git a/cinder/volume/drivers/hitachi/hnas_backend.py b/cinder/volume/drivers/hitachi/hnas_backend.py index a339297be66..624572a4b43 100644 --- a/cinder/volume/drivers/hitachi/hnas_backend.py +++ b/cinder/volume/drivers/hitachi/hnas_backend.py @@ -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') diff --git a/cinder/volume/drivers/hitachi/hnas_iscsi.py b/cinder/volume/drivers/hitachi/hnas_iscsi.py index 041bb528095..536d73fcba9 100644 --- a/cinder/volume/drivers/hitachi/hnas_iscsi.py +++ b/cinder/volume/drivers/hitachi/hnas_iscsi.py @@ -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 diff --git a/cinder/volume/drivers/hitachi/hnas_nfs.py b/cinder/volume/drivers/hitachi/hnas_nfs.py index e0edb0210c7..1675217a523 100644 --- a/cinder/volume/drivers/hitachi/hnas_nfs.py +++ b/cinder/volume/drivers/hitachi/hnas_nfs.py @@ -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) diff --git a/cinder/volume/drivers/hitachi/hnas_utils.py b/cinder/volume/drivers/hitachi/hnas_utils.py index 0063b2e96d0..f4c782dbefe 100644 --- a/cinder/volume/drivers/hitachi/hnas_utils.py +++ b/cinder/volume/drivers/hitachi/hnas_utils.py @@ -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 diff --git a/releasenotes/notes/hnas_deprecate_xml-16840b5a8c25d15e.yaml b/releasenotes/notes/hnas_deprecate_xml-16840b5a8c25d15e.yaml new file mode 100644 index 00000000000..e64d53af9d5 --- /dev/null +++ b/releasenotes/notes/hnas_deprecate_xml-16840b5a8c25d15e.yaml @@ -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.