From 35b7e1f5a72a74c7965fd78260014b2f7347e9d0 Mon Sep 17 00:00:00 2001 From: Peter Razumovsky Date: Tue, 7 Feb 2017 14:48:30 +0400 Subject: [PATCH] Add validation for nodes configs section Add check for nodes configs section - it should be a dict. Change-Id: Ie0114881aca4f62cfd50ff5358808765aa2924f8 --- fuel_ccp/deploy.py | 5 ++--- fuel_ccp/tests/base.py | 11 ++++++++++ fuel_ccp/tests/test_config.py | 11 ---------- fuel_ccp/tests/test_deploy.py | 32 ++++++++++++++++++++++++++++- fuel_ccp/validation/deploy.py | 38 +++++++++++++++++++++++++++++++++++ 5 files changed, 82 insertions(+), 15 deletions(-) diff --git a/fuel_ccp/deploy.py b/fuel_ccp/deploy.py index 9dc6ab6e..01f0d62b 100644 --- a/fuel_ccp/deploy.py +++ b/fuel_ccp/deploy.py @@ -390,10 +390,9 @@ def _create_exports_configmap(exports_map): def _make_topology(nodes, roles, replicas): failed = False - # TODO(sreshetniak): move it to validation - if not nodes: - LOG.error("Nodes section is not specified in configs") + if not deploy_validation.validate_nodes_section(nodes, CONF.configs): failed = True + # TODO(sreshetniak): move it to validation if not roles: LOG.error("Roles section is not specified in configs") failed = True diff --git a/fuel_ccp/tests/base.py b/fuel_ccp/tests/base.py index 0e795e8c..dabb541e 100644 --- a/fuel_ccp/tests/base.py +++ b/fuel_ccp/tests/base.py @@ -16,7 +16,9 @@ # under the License. from oslotest import base +import six +from fuel_ccp.config import _yaml from fuel_ccp.tests import conf_fixture @@ -26,3 +28,12 @@ class TestCase(base.BaseTestCase): def setUp(self): super(TestCase, self).setUp() self.conf = self.useFixture(conf_fixture.Config()).conf + + def nested_dict_to_attrdict(self, d): + if isinstance(d, dict): + return _yaml.AttrDict({k: self.nested_dict_to_attrdict(v) + for k, v in six.iteritems(d)}) + elif isinstance(d, list): + return list(map(self.nested_dict_to_attrdict, d)) + else: + return d diff --git a/fuel_ccp/tests/test_config.py b/fuel_ccp/tests/test_config.py index 93cb53c5..dfdf23a4 100644 --- a/fuel_ccp/tests/test_config.py +++ b/fuel_ccp/tests/test_config.py @@ -1,22 +1,11 @@ import io import jsonschema -import six from fuel_ccp import config from fuel_ccp.config import _yaml from fuel_ccp.tests import base -def nested_dict_to_attrdict(d): - if isinstance(d, dict): - return _yaml.AttrDict({k: nested_dict_to_attrdict(v) - for k, v in six.iteritems(d)}) - elif isinstance(d, list): - return list(map(nested_dict_to_attrdict, d)) - else: - return d - - class TestConfigSchema(base.TestCase): def test_validate_config_schema(self): schema = config.get_config_schema() diff --git a/fuel_ccp/tests/test_deploy.py b/fuel_ccp/tests/test_deploy.py index 7bbb59ed..5e33c4f2 100644 --- a/fuel_ccp/tests/test_deploy.py +++ b/fuel_ccp/tests/test_deploy.py @@ -8,6 +8,7 @@ import yaml from fuel_ccp.config import _yaml from fuel_ccp import deploy from fuel_ccp.tests import base +from fuel_ccp.validation import deploy as deploy_validation class TestDeploy(base.TestCase): @@ -396,7 +397,7 @@ class TestDeployMakeTopology(base.TestCase): ] }) - def test_make_empty_topology(self): + def test_make_topology_failed(self): self.assertRaises(RuntimeError, deploy._make_topology, _yaml.AttrDict(), _yaml.AttrDict(), _yaml.AttrDict()) @@ -407,6 +408,35 @@ class TestDeployMakeTopology(base.TestCase): deploy._make_topology, _yaml.AttrDict({"spam": "eggs"}), _yaml.AttrDict(), _yaml.AttrDict()) + self.assertRaises(RuntimeError, + deploy._make_topology, + self.nested_dict_to_attrdict( + {"node1": {"configs": "because-cows"}}), + _yaml.AttrDict({"spam": "eggs"}), None) + + def test_nodes_configs_has_new_var(self): + nodes = { + 'node1': { + 'configs': { + 'heat': { + 'stack_params': { + 'converge_resources': 'True', + } + } + } + } + } + configs = { + 'heat': { + 'stack_params': { + 'debug': True + } + } + } + nodes = self.nested_dict_to_attrdict(nodes) + configs = self.nested_dict_to_attrdict(configs) + self.assertFalse(deploy_validation.validate_nodes_section(nodes, + configs)) def test_make_topology_without_replicas(self): nodes = _yaml.AttrDict({ diff --git a/fuel_ccp/validation/deploy.py b/fuel_ccp/validation/deploy.py index bef169db..a2dc41bc 100644 --- a/fuel_ccp/validation/deploy.py +++ b/fuel_ccp/validation/deploy.py @@ -1,6 +1,11 @@ +import logging + from fuel_ccp.common import utils +from fuel_ccp.config import _yaml from fuel_ccp import dependencies +LOG = logging.getLogger(__name__) + def validate_requested_components(components, components_map): """Validate requested components. @@ -17,3 +22,36 @@ def validate_requested_components(components, components_map): raise RuntimeError('Following components are also required for ' 'successful deployment: ' '%s' % ' '.join(not_provided_components)) + + +def validate_nodes_section(nodes, configs): + valid = True + if not nodes: + LOG.error("Nodes section is not specified in configs") + valid = False + else: + for name in nodes: + if 'configs' in nodes[name]: + if not isinstance(nodes[name]['configs'], _yaml.AttrDict): + LOG.error("Nodes configs should be a dict, found " + "%s" % type(nodes[name]['configs'])) + valid = False + break + else: + valid = validate_nodes_config(nodes[name]['configs'], + configs) + if not valid: + break + return valid + + +def validate_nodes_config(node_config, global_config): + for k in node_config: + if k not in global_config: + LOG.error('Nodes configs cannot contain new variables, just ' + 'override existent') + return False + elif (isinstance(node_config[k], (dict, _yaml.AttrDict)) and + isinstance(global_config[k], (dict, _yaml.AttrDict))): + return validate_nodes_config(node_config[k], global_config[k]) + return True