diff --git a/ostack_validator/config_model.py b/ostack_validator/config_model.py index b7580f0..8acc6d6 100644 --- a/ostack_validator/config_model.py +++ b/ostack_validator/config_model.py @@ -1,3 +1,5 @@ +import string + from ostack_validator.common import Mark class ConfigurationSection(object): @@ -6,29 +8,35 @@ class ConfigurationSection(object): self.config = config self.section = section - def get(self, *args, **kwargs): - return self.config.get(self.section, *args, **kwargs) + def _combine_names(self, section, param): + if section == 'DEFAULT': + return param - def set(self, *args, **kwargs): - self.config.set(self.section, *args, **kwargs) + return '%s.%s' % (section, param) - def set_default(self, *args, **kwargs): - self.config.set_default(self.section, *args, **kwargs) + def get(self, name, *args, **kwargs): + return self.config.get(self._combine_names(self.section, name), *args, **kwargs) - def contains(self, *args, **kwargs): - return self.config.contains(self.section, *args, **kwargs) + def set(self, name, *args, **kwargs): + self.config.set(self._combine_names(self.section, name), *args, **kwargs) - def is_default(self, *args, **kwargs): - return self.config.is_default(self.section, *args, **kwargs) + def set_default(self, name, *args, **kwargs): + self.config.set_default(self._combine_names(self.section, name), *args, **kwargs) + + def contains(self, name, *args, **kwargs): + return self.config.contains(self._combine_names(self.section, name), *args, **kwargs) + + def is_default(self, name, *args, **kwargs): + return self.config.is_default(self._combine_names(self.section, name), *args, **kwargs) def __getitem__(self, key): - return self.config.get(self.section, key) + return self.config.get(self._combine_names(self.section, key)) def __setitem__(self, key, value): - return self.config.set(self.section, key, value) + return self.config.set(self._combine_names(self.section, key), value) def __contains__(self, key): - return self.config.contains(self.section, key) + return self.config.contains(self._combine_names(self.section, key)) def keys(self): return self.config.keys(self.section) @@ -36,6 +44,18 @@ class ConfigurationSection(object): def items(self, *args, **kwargs): return self.config.items(self.section, *args, **kwargs) +class ConfigurationWrapper(object): + def __init__(self, config, state): + super(ConfigurationWrapper, self).__init__() + self.config = config + self.state = state + + def __getitem__(self, key): + if key in self.state: + return '' + + return self.config.get(key, _state=self.state) + class Configuration(object): def __init__(self): @@ -43,16 +63,39 @@ class Configuration(object): self._defaults = dict() self._normal = dict() - def get(self, section, name, default=None): + def _normalize_name(self, name): + if name.find('.') == -1: + section = 'DEFAULT' + else: + section, name = name.split('.', 1) + + return (section, name) + + def _combine_names(self, section, param): + if section == 'DEFAULT': + return param + + return '%s.%s' % (section, param) + + def get(self, name, default=None, _state=[]): + section, name = self._normalize_name(name) + if section in self._normal and name in self._normal[section]: - return self._normal[section][name] + value = self._normal[section][name] + elif section in self._defaults and name in self._defaults[section]: + value = self._defaults[section][name] + else: + value = default - if section in self._defaults and name in self._defaults[section]: - return self._defaults[section][name] + if not isinstance(value, str): + return value - return default + tmpl = string.Template(value) + return tmpl.safe_substitute(ConfigurationWrapper(self, _state + [name])) + + def contains(self, name, ignoreDefault=False): + section, name = self._normalize_name(name) - def contains(self, section, name, ignoreDefault=False): if section in self._normal and name in self._normal[section]: return True @@ -61,16 +104,22 @@ class Configuration(object): return False - def is_default(self, section, name): + def is_default(self, name): + section, name = self._normalize_name(name) + return not (section in self._normal and name in self._normal[section]) and (section in self._defaults and name in self._defaults[section]) - def set_default(self, section, name, value): + def set_default(self, name, value): + section, name = self._normalize_name(name) + if not section in self._defaults: self._defaults[section] = dict() self._defaults[section][name] = value - def set(self, section, name, value): + def set(self, name, value): + section, name = self._normalize_name(name) + if not section in self._normal: self._normal[section] = dict() @@ -80,18 +129,10 @@ class Configuration(object): return ConfigurationSection(self, section) def __getitem__(self, key): - if not isinstance(key, tuple) == 1: - return self.section(key) - elif isinstance(key, tuple) and len(key) == 2: - return self.get(key[0], key[1]) - else: - raise TypeError, "expectes 1 or 2 arguments, %d given" % len(key) + return self.get(key) def __setitem__(self, key, value): - if isinstance(key, tuple) and len(key) == 2: - self.set(key[0], key[1], value) - else: - raise TypeError, "key expected to be section + parameter names, got %s" % key + self.set(key, value) def __contains__(self, section): return (section in self._defaults) or (section in self._normal) @@ -119,7 +160,7 @@ class Configuration(object): def items(self, section=None): if section: - return [(name, self.get(section, name)) for name in self.keys(section)] + return [(name, self.get(self._combine_names(section, name))) for name in self.keys(section)] else: return [(name, ConfigurationSection(self, name)) for name in self.keys()] diff --git a/ostack_validator/discovery.py b/ostack_validator/discovery.py index f1dd674..a42e75c 100644 --- a/ostack_validator/discovery.py +++ b/ostack_validator/discovery.py @@ -204,11 +204,11 @@ class OpenstackDiscovery(object): keystone.version = self._find_python_package_version(client, 'keystone') keystone.config_file = self._collect_file(client, config_path) - token = keystone.config['DEFAULT']['admin_token'] - host = keystone.config['DEFAULT']['bind_host'] + token = keystone.config['admin_token'] + host = keystone.config['bind_host'] if host == '0.0.0.0': host = '127.0.0.1' - port = int(keystone.config['DEFAULT']['admin_port']) + port = int(keystone.config['admin_port']) keystone_env = { 'OS_SERVICE_TOKEN': token, @@ -238,7 +238,7 @@ class OpenstackDiscovery(object): nova_api.version = self._find_python_package_version(client, 'nova') nova_api.config_file = self._collect_file(client, config_path) - paste_config_path = path_relative_to(nova_api.config['DEFAULT']['api_paste_config'], os.path.dirname(config_path)) + paste_config_path = path_relative_to(nova_api.config['api_paste_config'], os.path.dirname(config_path)) nova_api.paste_config_file = self._collect_file(client, paste_config_path) return nova_api @@ -326,7 +326,7 @@ class OpenstackDiscovery(object): cinder_api.version = self._find_python_package_version(client, 'cinder') cinder_api.config_file = self._collect_file(client, config_path) - paste_config_path = path_relative_to(cinder_api.config['DEFAULT']['api_paste_config'], os.path.dirname(config_path)) + paste_config_path = path_relative_to(cinder_api.config['api_paste_config'], os.path.dirname(config_path)) cinder_api.paste_config_file = self._collect_file(client, paste_config_path) return cinder_api @@ -346,7 +346,7 @@ class OpenstackDiscovery(object): cinder_volume.version = self._find_python_package_version(client, 'cinder') cinder_volume.config_file = self._collect_file(client, config_path) - rootwrap_config_path = path_relative_to(cinder_volume.config['DEFAULT']['rootwrap_config'], os.path.dirname(config_path)) + rootwrap_config_path = path_relative_to(cinder_volume.config['rootwrap_config'], os.path.dirname(config_path)) cinder_volume.rootwrap_config = self._collect_file(client, rootwrap_config_path) return cinder_volume @@ -400,3 +400,4 @@ class OpenstackDiscovery(object): return None return None + diff --git a/ostack_validator/inspections/keystone_authtoken.py b/ostack_validator/inspections/keystone_authtoken.py index a44657e..18cdafc 100644 --- a/ostack_validator/inspections/keystone_authtoken.py +++ b/ostack_validator/inspections/keystone_authtoken.py @@ -18,12 +18,12 @@ class KeystoneAuthtokenSettingsInspection(Inspection): return keystone = keystones[0] - keystone_addresses = [keystone.config['DEFAULT']['bind_host']] + keystone_addresses = [keystone.config['bind_host']] if keystone_addresses == ['0.0.0.0']: keystone_addresses = keystone.host.network_addresses for nova in [c for c in components if c.name == 'nova-api']: - if nova.config['DEFAULT']['auth_strategy'] != 'keystone': + if nova.config['auth_strategy'] != 'keystone': continue (authtoken_section,_) = find( @@ -33,11 +33,11 @@ class KeystoneAuthtokenSettingsInspection(Inspection): if not authtoken_section: continue - authtoken_settings = nova.paste_config[authtoken_section] + authtoken_settings = nova.paste_config.section(authtoken_section) def get_value(name): - return authtoken_settings[name] or nova.config['keystone_authtoken', name] + return authtoken_settings[name] or nova.config['keystone_authtoken.%s' % name] auth_host = get_value('auth_host') auth_port = get_value('auth_port') @@ -56,7 +56,7 @@ class KeystoneAuthtokenSettingsInspection(Inspection): if not auth_port: nova.report_issue(Issue(Issue.ERROR, msg_prefix + ' miss "auth_port" setting in keystone authtoken config')) - elif auth_port != keystone.config['DEFAULT']['admin_port']: + elif auth_port != keystone.config['admin_port']: nova.report_issue(Issue(Issue.ERROR, msg_prefix + ' has incorrect "auth_port" setting in keystone authtoken config')) if not auth_protocol: diff --git a/ostack_validator/inspections/keystone_endpoints.py b/ostack_validator/inspections/keystone_endpoints.py index 6987b24..52abb80 100644 --- a/ostack_validator/inspections/keystone_endpoints.py +++ b/ostack_validator/inspections/keystone_endpoints.py @@ -31,7 +31,7 @@ class KeystoneEndpointsInspection(Inspection): for c in host.components: if c.name != 'nova-compute': continue - if c.config['DEFAULT', 'osapi_compute_listen'] in ['0.0.0.0', url.hostname] and c.config['DEFAULT', 'osapi_compute_listen_port'] == url.port: + if c.config['osapi_compute_listen'] in ['0.0.0.0', url.hostname] and c.config['osapi_compute_listen_port'] == url.port: nova_compute = c break diff --git a/ostack_validator/model.py b/ostack_validator/model.py index 870549a..3506175 100644 --- a/ostack_validator/model.py +++ b/ostack_validator/model.py @@ -139,7 +139,10 @@ class OpenstackComponent(Service): # Apply defaults if schema: for parameter in filter(lambda p: p.default, schema.parameters): - _config.set_default(parameter.section, parameter.name, parameter.default) + if parameter.section == 'DEFAULT': + _config.set_default(parameter.name, parameter.default) + else: + _config.set_default('%s.%s' % (parameter.section, parameter.name), parameter.default) # Parse config file @@ -182,6 +185,10 @@ class OpenstackComponent(Service): else: seen_parameters.add(parameter.name.text) + parameter_fullname = parameter.name.text + if section_name != 'DEFAULT': + parameter_fullname = section_name + '.' + parameter_fullname + if parameter_schema: type_validator = TypeValidatorRegistry.get_validator(parameter_schema.type) type_validation_result = type_validator.validate(parameter.value.text) @@ -193,14 +200,14 @@ class OpenstackComponent(Service): else: value = type_validation_result - _config.set(section_name, parameter.name.text, value) + _config.set(parameter_fullname, value) # if value == parameter_schema.default: # report_issue(MarkedIssue(Issue.INFO, 'Explicit value equals default: section "%s" parameter "%s"' % (section_name, parameter.name.text), parameter.start_mark)) if parameter_schema.deprecation_message: report_issue(MarkedIssue(Issue.WARNING, 'Deprecated parameter: section "%s" name "%s". %s' % (section_name, parameter.name.text, parameter_schema.deprecation_message), parameter.start_mark)) else: - _config.set(section_name, parameter.name.text, parameter.value.text) + _config.set(parameter_fullname, parameter.value.text) return _config diff --git a/ostack_validator/test_configuration.py b/ostack_validator/test_configuration.py index 9eecb11..005e724 100644 --- a/ostack_validator/test_configuration.py +++ b/ostack_validator/test_configuration.py @@ -5,173 +5,193 @@ from ostack_validator.config_model import Configuration class ConfigurationTests(unittest.TestCase): section = 'section1' param = 'param1' + fullparam = '%s.%s' % (section, param) value = 'foobar' default_value = 'bar123' def test_empty(self): c = Configuration() - self.assertIsNone(c.get('section1', 'param1')) + self.assertIsNone(c.get('section1.param1')) def test_storage(self): - c = Configuration() - c.set(self.section, self.param, self.value) + c.set(self.fullparam, self.value) - self.assertEqual(self.value, c.get(self.section, self.param)) + self.assertEqual(self.value, c.get(self.fullparam)) + + def test_parameter_names_containing_sections(self): + c = Configuration() + c.set(self.fullparam, self.value) + + self.assertEqual(self.value, c.get('%s.%s' % (self.section, self.param))) + + def test_parameter_with_default_section(self): + c = Configuration() + c.set(self.param, self.value) + + self.assertEqual(self.value, c.get(self.param)) def test_explicit_default_on_get(self): c = Configuration() override_value = '12345' - self.assertEqual(override_value, c.get(self.section, self.param, default=override_value)) + self.assertEqual(override_value, c.get(self.fullparam, default=override_value)) def test_default(self): c = Configuration() - c.set_default(self.section, self.param, self.default_value) + c.set_default(self.fullparam, self.default_value) - self.assertEqual(self.default_value, c.get(self.section, self.param)) + self.assertEqual(self.default_value, c.get(self.fullparam)) def test_normal_overrides_default(self): c = Configuration() - c.set(self.section, self.param, self.value) - c.set_default(self.section, self.param, self.default_value) + c.set(self.fullparam, self.value) + c.set_default(self.fullparam, self.default_value) - self.assertEqual(self.value, c.get(self.section, self.param)) + self.assertEqual(self.value, c.get(self.fullparam)) def test_contains(self): c = Configuration() - self.assertFalse(c.contains(self.section, self.param)) + self.assertFalse(c.contains(self.fullparam)) def test_contains_default(self): c = Configuration() - c.set_default(self.section, self.param, self.default_value) + c.set_default(self.fullparam, self.default_value) - self.assertTrue(c.contains(self.section, self.param)) - self.assertFalse(c.contains(self.section, self.param, ignoreDefault=True)) + self.assertTrue(c.contains(self.fullparam)) + self.assertFalse(c.contains(self.fullparam, ignoreDefault=True)) def test_contains_normal(self): c = Configuration() - c.set(self.section, self.param, self.value) + c.set(self.fullparam, self.value) - self.assertTrue(c.contains(self.section, self.param)) - self.assertTrue(c.contains(self.section, self.param, ignoreDefault=True)) + self.assertTrue(c.contains(self.fullparam)) + self.assertTrue(c.contains(self.fullparam, ignoreDefault=True)) def test_is_default_returns_false_if_param_missing(self): c = Configuration() - self.assertFalse(c.is_default(self.section, self.param)) + self.assertFalse(c.is_default(self.fullparam)) def test_is_default_returns_true_if_only_default_value_set(self): c = Configuration() - c.set_default(self.section, self.param, self.default_value) + c.set_default(self.fullparam, self.default_value) - self.assertTrue(c.is_default(self.section, self.param)) + self.assertTrue(c.is_default(self.fullparam)) def test_is_default_returns_false_if_normal_value_set(self): c = Configuration() - c.set(self.section, self.param, self.value) + c.set(self.fullparam, self.value) - self.assertFalse(c.is_default(self.section, self.param)) + self.assertFalse(c.is_default(self.fullparam)) def test_is_default_returns_false_if_both_values_set(self): c = Configuration() - c.set_default(self.section, self.param, self.default_value) - c.set(self.section, self.param, self.value) + c.set_default(self.fullparam, self.default_value) + c.set(self.fullparam, self.value) - self.assertFalse(c.is_default(self.section, self.param)) + self.assertFalse(c.is_default(self.fullparam)) def test_subsection_set(self): c = Configuration() - c[self.section].set(self.param, self.value) + c.section(self.section).set(self.param, self.value) - self.assertEqual(self.value, c.get(self.section, self.param)) + self.assertEqual(self.value, c.get(self.fullparam)) def test_keys(self): c = Configuration() - c.set_default('section1', 'param1', '123') - c.set('section2', 'param1', '456') + c.set_default('section1.param1', '123') + c.set('section2.param1', '456') self.assertEqual(['section1', 'section2'], sorted(c.keys())) def test_subsection_keys(self): c = Configuration() - c.set_default(self.section, 'param1', '123') - c.set(self.section, 'param2', '456') + c.set_default('%s.param1' % self.section, '123') + c.set('%s.param2' % self.section, '456') - self.assertEqual(['param1', 'param2'], sorted(c[self.section].keys())) + self.assertEqual(['param1', 'param2'], sorted(c.section(self.section).keys())) def test_subsection_items(self): c = Configuration() - c.set(self.section, 'param1', 'value1') - c.set_default(self.section, 'param2', 'value2') + c.set('%s.param1' % self.section, 'value1') + c.set_default('%s.param2' % self.section, 'value2') - self.assertEqual([('param1', 'value1'), ('param2', 'value2')], sorted(c[self.section].items())) + self.assertEqual([('param1', 'value1'), ('param2', 'value2')], sorted(c.section(self.section).items())) def test_subsection_get(self): c = Configuration() - c.set(self.section, self.param, self.value) + c.set(self.fullparam, self.value) cs = c.section(self.section) self.assertEqual(self.value, cs.get(self.param)) - def test_subsection_through_indexer(self): - c = Configuration() - - c.set(self.section, self.param, self.value) - - cs = c[self.section] - self.assertEqual(self.value, cs.get(self.param)) - def test_getitem(self): c = Configuration() - c.set(self.section, self.param, self.value) + c.set(self.fullparam, self.value) - self.assertEqual(self.value, c[self.section, self.param]) + self.assertEqual(self.value, c[self.fullparam]) def test_subsection_getitem(self): c = Configuration() - c.set(self.section, self.param, self.value) + c.set(self.fullparam, self.value) - cs = c[self.section] + cs = c.section(self.section) self.assertEqual(self.value, cs[self.param]) def test_setitem(self): c = Configuration() - c[self.section, self.param] = self.value + c[self.fullparam] = self.value - self.assertEqual(self.value, c.get(self.section, self.param)) + self.assertEqual(self.value, c.get(self.fullparam)) def test_subsection_setitem(self): c = Configuration() - cs = c[self.section] + cs = c.section(self.section) cs[self.param] = self.value - self.assertEqual(self.value, c.get(self.section, self.param)) + self.assertEqual(self.value, c.get(self.fullparam)) def test_contains(self): c = Configuration() self.assertFalse(self.section in c) - c.set(self.section, self.param, self.value) + c.set(self.fullparam, self.value) self.assertTrue(self.section in c) def test_subsection_contains(self): c = Configuration() - c.set('section1', 'param1', '123') - c.set_default('section2', 'param2', '234') + c.set('section1.param1', '123') + c.set_default('section2.param2', '234') - self.assertTrue('param1' in c['section1']) - self.assertTrue('param2' in c['section2']) - self.assertFalse('param1' in c['section2']) + self.assertTrue('param1' in c.section('section1')) + self.assertTrue('param2' in c.section('section2')) + self.assertFalse('param1' in c.section('section2')) def test_returns_section_object_even_if_section_doesnot_exist(self): c = Configuration() - self.assertIsNotNone(c['foo']) + self.assertIsNotNone(c.section('foo')) + + def test_template_substitution(self): + c = Configuration() + c.set('a', 'x') + c.set('b', '$a') + c.set('c', '$b') + + self.assertEqual('x', c.get('c')) + + def test_cycle_template_substitution_resolves_in_empty_string(self): + c = Configuration() + c.set('a', 'a$c') + c.set('b', 'b$a') + c.set('c', 'c$b') + + self.assertEqual('cba', c.get('c'))