diff --git a/tripleo_validations/tests/library/test_network_environment.py b/tripleo_validations/tests/library/test_network_environment.py index 862103fc7..bbd05839c 100644 --- a/tripleo_validations/tests/library/test_network_environment.py +++ b/tripleo_validations/tests/library/test_network_environment.py @@ -49,99 +49,6 @@ class TestNicConfigs(base.TestCase): self.assertEqual("The 'config' property of 'foo' must be" " a dictionary.", errors[0]) - def test_get_network_config(self): - # Test config lookup using current format (t-h-t >= Ocata) - resources = { - 'properties': { - 'config': { - 'str_replace': { - 'params': { - '$network_config': { - 'network_config': [ - 'current' - ] - } - } - } - } - } - } - self.assertEqual( - validation.get_network_config(resources, 'foo')[0], - 'current') - - def test_get_network_config_returns_none_if_not_found(self): - # get_network_config should return None if - # any of the keys cannot be found in the resources tree: - # `properties`, `config`, `network_config` - no_properties = { - 'bar': { - 'config': { - 'str_replace': { - 'params': { - '$network_config': { - 'network_config': [ - 'current' - ] - } - } - } - } - } - } - no_config = { - 'properties': { - 'bar': { - 'str_replace': { - 'params': { - '$network_config': { - 'network_config': [ - 'current' - ] - } - } - } - } - } - } - no_network_config = { - 'properties': { - 'config': { - 'str_replace': { - 'params': { - '$network_config': { - 'bar': { - 'some': 'val' - } - } - } - } - } - } - } - self.assertEqual( - validation.get_network_config(no_properties, 'foo'), None) - self.assertEqual(validation.get_network_config(no_config, 'foo'), None) - self.assertEqual( - validation.get_network_config(no_network_config, 'foo'), None) - - def test_get_network_config_old_format(self): - # Test config lookup using format used in t-h-t <= Newton - resources = { - 'properties': { - 'config': { - 'os_net_config': { - 'network_config': [ - 'old' - ] - } - } - } - } - self.assertEqual( - validation.get_network_config(resources, 'foo')[0], - 'old') - def nic_data(self, bridges): return { 'resources': { diff --git a/tripleo_validations/tests/test_utils.py b/tripleo_validations/tests/test_utils.py new file mode 100644 index 000000000..928d0a700 --- /dev/null +++ b/tripleo_validations/tests/test_utils.py @@ -0,0 +1,120 @@ +# -*- coding: utf-8 -*- + +# 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. + +import collections + +from tripleo_validations.tests import base +from tripleo_validations import utils + +PATH = [ + ('properties', collections.Mapping, 'dictionary'), + ('config', collections.Mapping, 'dictionary'), + ('network_config', collections.Iterable, 'list'), +] + + +class TestGetNested(base.TestCase): + + def test_get_nested(self): + # Test config lookup using current format (t-h-t >= Ocata) + resources = { + 'properties': { + 'config': { + 'str_replace': { + 'params': { + '$network_config': { + 'network_config': [ + 'current' + ] + } + } + } + } + } + } + self.assertEqual( + utils.get_nested(resources, 'foo', PATH[:])[0], + 'current') + + def test_get_nested_returns_none_if_not_found(self): + # get_nested should return None if + # any of the keys cannot be found in the resources tree: + # `properties`, `config`, `network_config` + no_properties = { + 'bar': { + 'config': { + 'str_replace': { + 'params': { + '$network_config': { + 'network_config': [ + 'current' + ] + } + } + } + } + } + } + no_config = { + 'properties': { + 'bar': { + 'str_replace': { + 'params': { + '$network_config': { + 'network_config': [ + 'current' + ] + } + } + } + } + } + } + no_network_config = { + 'properties': { + 'config': { + 'str_replace': { + 'params': { + '$network_config': { + 'bar': { + 'some': 'val' + } + } + } + } + } + } + } + self.assertEqual( + utils.get_nested(no_properties, 'foo', PATH[:]), None) + self.assertEqual(utils.get_nested(no_config, 'foo', PATH[:]), None) + self.assertEqual( + utils.get_nested(no_network_config, 'foo', PATH[:]), None) + + def test_get_nested_old_format(self): + # Test config lookup using format used in t-h-t <= Newton + resources = { + 'properties': { + 'config': { + 'os_net_config': { + 'network_config': [ + 'old' + ] + } + } + } + } + self.assertEqual( + utils.get_nested(resources, 'foo', PATH[:])[0], + 'old') diff --git a/tripleo_validations/utils.py b/tripleo_validations/utils.py index aab2b0033..c04d6e693 100644 --- a/tripleo_validations/utils.py +++ b/tripleo_validations/utils.py @@ -17,6 +17,8 @@ from __future__ import print_function +import collections + from keystoneauth1.identity import generic as ks_id from keystoneauth1 import session from six import string_types @@ -52,3 +54,33 @@ def filtered(obj): """Only return properties of obj whose value can be properly serialized.""" return {k: v for k, v in obj.__dict__.items() if isinstance(v, (string_types, int, list, dict, type(None)))} + + +def get_nested(data, name, path): + # Finds and returns a property from a nested dictionary by + # following a path of a defined set of property names and types. + + def deep_find_key(key_data, data, name): + key, instance_type, instance_name = key_data + if key in data.keys(): + if not isinstance(data[key], instance_type): + raise ValueError("The '{}' property of '{}' must be a {}." + "".format(key, name, instance_name)) + return data[key] + for item in data.values(): + if isinstance(item, collections.Mapping): + return deep_find_key(key_data, item, name) + return None + + if not isinstance(data, collections.Mapping): + raise ValueError( + "'{}' is not a valid resource.".format(name)) + + current_value = data + while len(path) > 0: + key_data = path.pop(0) + current_value = deep_find_key(key_data, current_value, name) + if current_value is None: + break + + return current_value diff --git a/validations/library/network_environment.py b/validations/library/network_environment.py index 1d1bc75e6..3abe6beab 100644 --- a/validations/library/network_environment.py +++ b/validations/library/network_environment.py @@ -26,6 +26,9 @@ import six from ansible.module_utils.basic import AnsibleModule from os_net_config import validator +from tripleo_validations.utils import get_nested + + DOCUMENTATION = ''' --- module: network_environment @@ -135,42 +138,6 @@ def validate_network_environment(network_data, nic_configs): return errors -def get_network_config(resource, resource_name): - # Finds and returns `properties > config > network_config` inside - # a resources dictionary, with optional nesting levels in between. - - def deep_find_key(key_data, resource, resource_name): - key, instance_type, instance_name = key_data - if key in resource.keys(): - if not isinstance(resource[key], instance_type): - raise ValueError("The '{}' property of '{}' must be a {}." - "".format(key, resource_name, instance_name)) - return resource[key] - for item in resource.values(): - if isinstance(item, collections.Mapping): - return deep_find_key(key_data, item, resource_name) - return None - - keys = [ - ('properties', collections.Mapping, 'dictionary'), - ('config', collections.Mapping, 'dictionary'), - ('network_config', collections.Iterable, 'list'), - ] - current_value = resource - - if not isinstance(resource, collections.Mapping): - raise ValueError( - "'{}' is not a valid resource.".format(resource_name)) - - while len(keys) > 0: - key_data = keys.pop(0) - current_value = deep_find_key(key_data, current_value, resource_name) - if current_value is None: - break - - return current_value - - def check_nic_configs(path, nic_data): errors = [] @@ -185,7 +152,12 @@ def check_nic_configs(path, nic_data): "a dictionary."] for name, resource in six.iteritems(resources): try: - bridges = get_network_config(resource, name) + nested_path = [ + ('properties', collections.Mapping, 'dictionary'), + ('config', collections.Mapping, 'dictionary'), + ('network_config', collections.Iterable, 'list'), + ] + bridges = get_nested(resource, name, nested_path) except ValueError as e: errors.append('{}'.format(e)) continue diff --git a/validations/library/switch_vlans.py b/validations/library/switch_vlans.py index 9018c5dcd..6ff3a1f57 100644 --- a/validations/library/switch_vlans.py +++ b/validations/library/switch_vlans.py @@ -15,12 +15,13 @@ # under the License. import collections +import os.path import yaml import six from ansible.module_utils.basic import AnsibleModule # noqa -import validations.library.network_environment as net_utils +from tripleo_validations import utils DOCUMENTATION = ''' --- @@ -62,6 +63,32 @@ EXAMPLES = ''' ''' +def open_network_environment_files(netenv_path, template_files): + errors = [] + + try: + network_data = yaml.safe_load(template_files[netenv_path]) + except Exception as e: + return ({}, {}, ["Can't open network environment file '{}': {}" + .format(netenv_path, e)]) + nic_configs = [] + resource_registry = network_data.get('resource_registry', {}) + for nic_name, relative_path in six.iteritems(resource_registry): + if nic_name.endswith("Net::SoftwareConfig"): + nic_config_path = os.path.normpath( + os.path.join(os.path.dirname(netenv_path), relative_path)) + try: + nic_configs.append(( + nic_name, nic_config_path, + yaml.safe_load(template_files[nic_config_path]))) + except Exception as e: + errors.append( + "Can't open the resource '{}' reference file '{}': {}" + .format(nic_name, nic_config_path, e)) + + return (network_data, nic_configs, errors) + + def validate_switch_vlans(netenv_path, template_files, introspection_data): """Check if VLAN exists in introspection data for node @@ -73,7 +100,7 @@ def validate_switch_vlans(netenv_path, template_files, introspection_data): """ network_data, nic_configs, errors =\ - net_utils.open_network_environment_files(netenv_path, template_files) + open_network_environment_files(netenv_path, template_files) warnings = [] vlans_in_templates = False @@ -95,7 +122,12 @@ def validate_switch_vlans(netenv_path, template_files, introspection_data): "and it must be a dictionary."] for name, resource in six.iteritems(resources): try: - nw_config = net_utils.get_network_config(resource, name) + nested_path = [ + ('properties', collections.Mapping, 'dictionary'), + ('config', collections.Mapping, 'dictionary'), + ('network_config', collections.Iterable, 'list'), + ] + nw_config = utils.get_nested(resource, name, nested_path) except ValueError as e: errors.append('{}'.format(e)) continue