From 72ce36b415da2fa33658c5ee7a3ee17d2d3974a1 Mon Sep 17 00:00:00 2001 From: Idan Hefetz Date: Wed, 20 Feb 2019 07:18:41 +0000 Subject: [PATCH] Support templates with parameters in v3. Change-Id: I5ddd4fb27694bfb4efe33753c85f4e3c58edb89a Story: 2004056 Task: 29445 Depends-On: https://review.openstack.org/#/c/638096/ --- .../template_functions/function_resolver.py | 72 ++++------ .../template_functions/v2/__init__.py | 21 +-- .../template_functions/v2/functions.py | 39 +++--- vitrage/evaluator/template_schemas.py | 5 +- .../evaluator/template_validation/__init__.py | 2 +- vitrage/evaluator/template_validation/base.py | 2 +- .../content/template_content_validator.py | 3 +- .../content/template_content_validator_v3.py | 14 +- .../content/v1/get_param_validator.py | 22 +-- .../content/v2/get_param_validator.py | 22 +-- .../template_syntax_validator_v3.py | 10 ++ vitrage/tests/base.py | 6 + .../functional/api_handler/test_templates.py | 126 ++++++------------ .../api_handler/test_templates_v2.py | 67 ++++++++++ .../api_handler/test_templates_v3.py | 60 +++++++++ ..._def.yaml => v2_with_extra_param_def.yaml} | 0 ...ef.yaml => v2_with_missing_param_def.yaml} | 0 .../{with_params.yaml => v2_with_params.yaml} | 0 ...out_params.yaml => v2_without_params.yaml} | 0 .../parameters/v3_with_extra_param_def.yaml | 30 +++++ .../parameters/v3_with_missing_param_def.yaml | 24 ++++ .../templates/parameters/v3_with_params.yaml | 28 ++++ .../content/v1/test_parameters_validator.py | 6 +- .../content/v2/test_parameters_validator.py | 87 +++++++----- 24 files changed, 430 insertions(+), 216 deletions(-) create mode 100644 vitrage/tests/functional/api_handler/test_templates_v2.py create mode 100644 vitrage/tests/functional/api_handler/test_templates_v3.py rename vitrage/tests/resources/templates/parameters/{with_extra_param_def.yaml => v2_with_extra_param_def.yaml} (100%) rename vitrage/tests/resources/templates/parameters/{with_missing_param_def.yaml => v2_with_missing_param_def.yaml} (100%) rename vitrage/tests/resources/templates/parameters/{with_params.yaml => v2_with_params.yaml} (100%) rename vitrage/tests/resources/templates/parameters/{without_params.yaml => v2_without_params.yaml} (100%) create mode 100644 vitrage/tests/resources/templates/parameters/v3_with_extra_param_def.yaml create mode 100644 vitrage/tests/resources/templates/parameters/v3_with_missing_param_def.yaml create mode 100644 vitrage/tests/resources/templates/parameters/v3_with_params.yaml diff --git a/vitrage/evaluator/template_functions/function_resolver.py b/vitrage/evaluator/template_functions/function_resolver.py index aecede2aa..8bda43ea9 100644 --- a/vitrage/evaluator/template_functions/function_resolver.py +++ b/vitrage/evaluator/template_functions/function_resolver.py @@ -17,65 +17,49 @@ from oslo_log import log import re import six -from vitrage.evaluator.template_validation.content.base import \ - get_content_correct_result -from vitrage.evaluator.template_validation.content.base import \ - get_content_fault_result -from vitrage.evaluator.template_validation.status_messages import status_msgs +from vitrage.evaluator.template_validation.base import ValidationError LOG = log.getLogger(__name__) FuncInfo = namedtuple('FuncInfo', ['name', 'func', 'error_code']) -class FunctionResolver(object): - @classmethod - def resolve_function(cls, func_info, template, **kwargs): - return cls._traverse_function(func_info, template, True, **kwargs) +def resolve_function(func_info, template, **kwargs): + return _traverse_function(func_info, template, resolve=True, **kwargs) - @classmethod - def validate_function(cls, func_info, template, **kwargs): - return cls._traverse_function(func_info, template, False, **kwargs) - @classmethod - def _traverse_function(cls, func_info, template, resolve, **kwargs): - return cls._recursive_resolve_function( - func_info, template, template, resolve, **kwargs) +def validate_function(func_info, template, **kwargs): + return _traverse_function(func_info, template, resolve=False, **kwargs) - @classmethod - def _recursive_resolve_function(cls, func_info, template, template_block, - resolve, **kwargs): - result = get_content_correct_result() - for key, value in template_block.items(): - if result.is_valid_config: - if isinstance(value, six.string_types) and \ - _is_wanted_function(value, func_info.name): +def _traverse_function(func_info, template, resolve, **kwargs): + return _recursive_resolve_function( + func_info, template, template, resolve, **kwargs) - func = func_info.func - if not func: - status = func_info.error_code - LOG.error('%s status code: %s' % - (status_msgs[status], status)) - return get_content_fault_result(status) - result, resolved_value = func(value, template, **kwargs) - if result.is_valid_config and resolve: - template_block[key] = resolved_value - LOG.debug('Replaced %s with %s', value, - resolved_value) +def _recursive_resolve_function(func_info, template, template_block, + resolve, **kwargs): - elif isinstance(value, dict): - result = cls._recursive_resolve_function( - func_info, template, value, resolve, **kwargs) + for key, value in template_block.items(): + if isinstance(value, six.string_types) and \ + _is_wanted_function(value, func_info.name): - elif isinstance(value, list): - for item in value: - if result.is_valid_config: - result = cls._recursive_resolve_function( - func_info, template, item, resolve, **kwargs) + if not func_info.func: + raise ValidationError(func_info.error_code, value) - return result + resolved_value = func_info.func(value, template, **kwargs) + if resolve: + template_block[key] = resolved_value + LOG.debug('Replaced %s with %s', value, resolved_value) + + elif isinstance(value, dict): + _recursive_resolve_function( + func_info, template, value, resolve, **kwargs) + + elif isinstance(value, list): + for item in value: + _recursive_resolve_function( + func_info, template, item, resolve, **kwargs) def is_function(str): diff --git a/vitrage/evaluator/template_functions/v2/__init__.py b/vitrage/evaluator/template_functions/v2/__init__.py index 4f5af62d5..9eb7db7ca 100644 --- a/vitrage/evaluator/template_functions/v2/__init__.py +++ b/vitrage/evaluator/template_functions/v2/__init__.py @@ -13,11 +13,10 @@ # under the License. from oslo_log import log -from vitrage.evaluator.template_functions.function_resolver import \ - FuncInfo -from vitrage.evaluator.template_functions.function_resolver import \ - FunctionResolver +from vitrage.evaluator.template_functions import function_resolver from vitrage.evaluator.template_functions import GET_PARAM +from vitrage.evaluator.template_validation.base import get_custom_fault_result +from vitrage.evaluator.template_validation.base import ValidationError from vitrage.evaluator.template_validation.content.base import \ get_content_correct_result from vitrage.evaluator.template_validation.content.base import \ @@ -37,7 +36,13 @@ def resolve_parameters(template_def, params=None): get_param = template_schema.functions.get(GET_PARAM) - return FunctionResolver().resolve_function( - func_info=FuncInfo(name=GET_PARAM, func=get_param, error_code=160), - template=template_def, - actual_params=params) + try: + function_resolver.resolve_function( + func_info=function_resolver.FuncInfo( + name=GET_PARAM, func=get_param, error_code=0), + template=template_def, + actual_params=params) + except ValidationError as e: + return get_custom_fault_result(e.code, e.details) + + return get_content_correct_result() diff --git a/vitrage/evaluator/template_functions/v2/functions.py b/vitrage/evaluator/template_functions/v2/functions.py index 870f14ea6..75bf40a5c 100644 --- a/vitrage/evaluator/template_functions/v2/functions.py +++ b/vitrage/evaluator/template_functions/v2/functions.py @@ -15,11 +15,7 @@ from oslo_log import log from vitrage.evaluator.template_fields import TemplateFields from vitrage.evaluator.template_functions import GET_PARAM -from vitrage.evaluator.template_validation.content.base import \ - get_content_correct_result -from vitrage.evaluator.template_validation.content.base import \ - get_content_fault_result -from vitrage.evaluator.template_validation.status_messages import status_msgs +from vitrage.evaluator.template_validation.base import ValidationError LOG = log.getLogger(__name__) @@ -112,42 +108,39 @@ def get_param(param_name, template, **kwargs): :param kwargs: Additional arguments. The expected argument is actual_params, a dict with key=value pairs of parameter values. - :return: A tuple of (Result, param value) - The parameter value is taken from the actual_params, if given, or from the - default value that is defined in the template parameters section. - If none exists, a fault result is returned. + :return: The parameter value is taken from the actual_params, if given, or + from the default value that is defined in the template parameters section. + If none exists, or param_name does not contains a valid function call + a ValidationError is raised. + :raises: ValidationError """ param_defs = template.get(TemplateFields.PARAMETERS) actual_params = kwargs.get('actual_params') if not param_defs: - LOG.error('%s status code: %s' % (status_msgs[161], 161)) - return get_content_fault_result(161), None + raise ValidationError(161) if param_name.startswith(GET_PARAM): if not param_name.startswith(GET_PARAM + '(') or \ not param_name.endswith(')') or \ len(param_name) < len(GET_PARAM) + 3: - LOG.error('%s status code: %s' % (status_msgs[162], 162)) - return get_content_fault_result(162), None + raise ValidationError(162, param_name) - param_name = extract_param_name(param_name) - if not param_name: - LOG.error('%s status code: %s' % (status_msgs[162], 162)) - return get_content_fault_result(162), None + extracted_param_name = extract_param_name(param_name) + if not extracted_param_name: + raise ValidationError(162, param_name) # Make sure the parameter is defined in the parameters section found_param_def = None for param_key, param_value in param_defs.items(): - if param_name == param_key: + if extracted_param_name == param_key: found_param_def = param_key, param_value if not found_param_def: - LOG.error('%s status code: %s' % (status_msgs[161], 161)) - return get_content_fault_result(161), None + raise ValidationError(161, extracted_param_name) # Check if an actual value was assigned to this parameter - param_value = get_actual_value(param_name, actual_params) + param_value = get_actual_value(extracted_param_name, actual_params) if not param_value: found_param_value = found_param_def[1] default = found_param_value.get(TemplateFields.DEFAULT) \ @@ -155,9 +148,9 @@ def get_param(param_name, template, **kwargs): if default: param_value = default else: - return get_content_fault_result(163), None + raise ValidationError(163, extracted_param_name) - return get_content_correct_result(), param_value + return param_value def extract_param_name(param): diff --git a/vitrage/evaluator/template_schemas.py b/vitrage/evaluator/template_schemas.py index d3e2fbee0..bb28af8ce 100644 --- a/vitrage/evaluator/template_schemas.py +++ b/vitrage/evaluator/template_schemas.py @@ -127,7 +127,10 @@ class TemplateSchema3(object): ActionType.SET_STATE: V3ActionLoader(), } - self.functions = {GET_ATTR: get_attr} + self.functions = { + GET_ATTR: get_attr, + GET_PARAM: get_param + } def version(self): return '3' diff --git a/vitrage/evaluator/template_validation/__init__.py b/vitrage/evaluator/template_validation/__init__.py index dd33a3a45..3b541494f 100644 --- a/vitrage/evaluator/template_validation/__init__.py +++ b/vitrage/evaluator/template_validation/__init__.py @@ -40,7 +40,7 @@ def validate_template(template, def_templates, params=None): try: template_schema.validators[SYNTAX].validate(template) - template_schema.validators[CONTENT].validate(template) + template_schema.validators[CONTENT].validate(template, params) except base.ValidationError as e: return base.get_custom_fault_result(e.code, e.details) except VoluptuousError as e: diff --git a/vitrage/evaluator/template_validation/base.py b/vitrage/evaluator/template_validation/base.py index 9c2e1d04e..8999412ff 100644 --- a/vitrage/evaluator/template_validation/base.py +++ b/vitrage/evaluator/template_validation/base.py @@ -43,7 +43,7 @@ def get_fault_result(description, code, msg=None): def get_custom_fault_result(code, msg): return Result('Template validation', False, code, - status_msgs[code] + ' ' + msg) + status_msgs[code] + ' - ' + msg) def get_status_code(voluptuous_error): diff --git a/vitrage/evaluator/template_validation/content/template_content_validator.py b/vitrage/evaluator/template_validation/content/template_content_validator.py index 9ade7cabd..1d80ec082 100644 --- a/vitrage/evaluator/template_validation/content/template_content_validator.py +++ b/vitrage/evaluator/template_validation/content/template_content_validator.py @@ -104,6 +104,5 @@ def content_validation(template, def_templates=None, params=None): def parameters_validation(template_schema, template, actual_params): - params_validator = \ - template_schema.validators.get(GET_PARAM) if template_schema else None + params_validator = template_schema.validators[GET_PARAM] return params_validator.validate(template, actual_params) diff --git a/vitrage/evaluator/template_validation/content/template_content_validator_v3.py b/vitrage/evaluator/template_validation/content/template_content_validator_v3.py index bc66aaa0c..6b4292f76 100644 --- a/vitrage/evaluator/template_validation/content/template_content_validator_v3.py +++ b/vitrage/evaluator/template_validation/content/template_content_validator_v3.py @@ -21,6 +21,9 @@ from vitrage.evaluator.base import get_template_schema from vitrage.evaluator import condition as dnf from vitrage.evaluator.template_fields import TemplateFields +from vitrage.evaluator.template_functions import function_resolver +from vitrage.evaluator.template_functions import GET_PARAM +from vitrage.evaluator.template_functions.v2.functions import get_param from vitrage.evaluator.template_loading.template_loader_v3 import \ TemplateLoader as V3TemplateLoader from vitrage.evaluator.template_validation.base import ValidationError @@ -33,9 +36,10 @@ RELATION = 'relationship' class ContentValidator(object): @staticmethod - def validate(template): + def validate(template, actual_params): _validate_entities_regex(template) _validate_conditions(template) + _validate_parameters(template, actual_params) # As part of validation, when it is finished, # we try to load the template, as some validations can only be @@ -69,6 +73,14 @@ def _validate_conditions(template): _validate_not_condition(condition) +def _validate_parameters(template, actual_params): + function_resolver.validate_function( + func_info=function_resolver.FuncInfo( + name=GET_PARAM, func=get_param, error_code=160), + template=template, + actual_params=actual_params) + + def _validate_condition_entity_ids(template, condition): curr_str = ' ' + condition + ' ' diff --git a/vitrage/evaluator/template_validation/content/v1/get_param_validator.py b/vitrage/evaluator/template_validation/content/v1/get_param_validator.py index 2354efbad..92f53330d 100644 --- a/vitrage/evaluator/template_validation/content/v1/get_param_validator.py +++ b/vitrage/evaluator/template_validation/content/v1/get_param_validator.py @@ -12,11 +12,12 @@ # License for the specific language governing permissions and limitations # under the License. -from vitrage.evaluator.template_functions.function_resolver import \ - FuncInfo -from vitrage.evaluator.template_functions.function_resolver import \ - FunctionResolver +from vitrage.evaluator.template_functions import function_resolver from vitrage.evaluator.template_functions import GET_PARAM +from vitrage.evaluator.template_validation.base import get_custom_fault_result +from vitrage.evaluator.template_validation.base import ValidationError +from vitrage.evaluator.template_validation.content.base import \ + get_content_correct_result class GetParamValidator(object): @@ -25,7 +26,12 @@ class GetParamValidator(object): def validate(cls, template, actual_params): # if there is a get_param in the template, an error message will be # returned since func is None - return FunctionResolver().validate_function( - func_info=FuncInfo(name=GET_PARAM, func=None, error_code=160), - template=template, - actual_params=actual_params) + try: + function_resolver.validate_function( + func_info=function_resolver.FuncInfo( + name=GET_PARAM, func=None, error_code=160), + template=template, + actual_params=actual_params) + except ValidationError as e: + return get_custom_fault_result(e.code, e.details) + return get_content_correct_result() diff --git a/vitrage/evaluator/template_validation/content/v2/get_param_validator.py b/vitrage/evaluator/template_validation/content/v2/get_param_validator.py index b88600241..bc1991d31 100644 --- a/vitrage/evaluator/template_validation/content/v2/get_param_validator.py +++ b/vitrage/evaluator/template_validation/content/v2/get_param_validator.py @@ -12,19 +12,25 @@ # License for the specific language governing permissions and limitations # under the License. -from vitrage.evaluator.template_functions.function_resolver import \ - FuncInfo -from vitrage.evaluator.template_functions.function_resolver import \ - FunctionResolver +from vitrage.evaluator.template_functions import function_resolver from vitrage.evaluator.template_functions import GET_PARAM from vitrage.evaluator.template_functions.v2.functions import get_param +from vitrage.evaluator.template_validation.base import get_custom_fault_result +from vitrage.evaluator.template_validation.base import ValidationError +from vitrage.evaluator.template_validation.content.base import \ + get_content_correct_result class GetParamValidator(object): @classmethod def validate(cls, template, actual_params): - return FunctionResolver().validate_function( - func_info=FuncInfo(name=GET_PARAM, func=get_param, error_code=160), - template=template, - actual_params=actual_params) + try: + function_resolver.validate_function( + function_resolver.FuncInfo( + name=GET_PARAM, func=get_param, error_code=0), + template, + actual_params=actual_params) + except ValidationError as e: + return get_custom_fault_result(e.code, e.details) + return get_content_correct_result() diff --git a/vitrage/evaluator/template_validation/template_syntax_validator_v3.py b/vitrage/evaluator/template_validation/template_syntax_validator_v3.py index bec5cc65c..4b2fe0913 100644 --- a/vitrage/evaluator/template_validation/template_syntax_validator_v3.py +++ b/vitrage/evaluator/template_validation/template_syntax_validator_v3.py @@ -44,6 +44,7 @@ class SyntaxValidator(object): Required(TF.ENTITIES, msg=10000): _entities_schema(), Required(TF.METADATA, msg=62): _metadata_schema(), Required(TF.SCENARIOS, msg=80): _scenarios_schema(template), + Optional(TF.PARAMETERS): _parameters_schema(), })(template) @@ -79,6 +80,15 @@ def _scenarios_schema(template): })]) +def _parameters_schema(): + return Schema({ + any_str: Any(any_str, Schema({ + Optional(TF.DESCRIPTION): any_str, + Optional(TF.DEFAULT): any_str, + })), + }) + + def _raise_alarm_schema(template): return Schema({ Optional(ActionType.RAISE_ALARM): Schema({ diff --git a/vitrage/tests/base.py b/vitrage/tests/base.py index 11e374db4..8b22bf340 100644 --- a/vitrage/tests/base.py +++ b/vitrage/tests/base.py @@ -19,6 +19,8 @@ from oslo_utils import timeutils # noinspection PyPackageRequirements from oslotest import base import sys + +from testtools import matchers from testtools.matchers import HasLength @@ -91,6 +93,10 @@ class BaseTest(base.BaseTestCase): dict(g2_edges.get(e_source_id)), "Edges of each graph are not equal") + def assert_starts_with(self, expected_prefix, observed_str, msg=None): + self.assertThat(observed_str, + matchers.StartsWith(expected_prefix), msg) + @staticmethod def path_get(project_file=None): root = os.path.abspath(os.path.join(os.path.dirname(__file__), diff --git a/vitrage/tests/functional/api_handler/test_templates.py b/vitrage/tests/functional/api_handler/test_templates.py index 433834c3a..021597570 100644 --- a/vitrage/tests/functional/api_handler/test_templates.py +++ b/vitrage/tests/functional/api_handler/test_templates.py @@ -23,10 +23,7 @@ from vitrage.tests.unit.entity_graph.base import TestEntityGraphUnitBase class TestTemplates(TestEntityGraphUnitBase, TestConfiguration): - TEMPLATE_WITH_PARAMS = 'with_params.yaml' - TEMPLATE_WITH_EXTRA_PARAM_DEF = 'with_extra_param_def.yaml' - TEMPLATE_WITH_MISSING_PARAM_DEF = 'with_missing_param_def.yaml' - TEMPLATE_WITHOUT_PARAMS = 'without_params.yaml' + VALIDATION_FAILED = 'validation failed' VALIDATION_OK = 'validation OK' @@ -46,13 +43,14 @@ class TestTemplates(TestEntityGraphUnitBase, TestConfiguration): super(TestTemplates, self).tearDown() self._delete_templates() - def test_validate_template_with_no_params(self): - # Setup + def _load_template_content(self, template_filename): template_path = '%s/templates/parameters/%s' % ( utils.get_resources_dir(), - self.TEMPLATE_WITH_PARAMS) - files_content = [(template_path, self._load_yaml_file(template_path))] + template_filename) + return [(template_path, self._load_yaml_file(template_path))] + def _validate_template_with_no_params(self, template_filename): + files_content = self._load_template_content(template_filename) # Action results = self.apis.validate_template( ctx=None, templates=files_content, template_type=None, params=None) @@ -62,14 +60,11 @@ class TestTemplates(TestEntityGraphUnitBase, TestConfiguration): self.VALIDATION_FAILED, 163, 'Failed to resolve parameter', results) - def test_validate_template_with_missing_param(self): + def _validate_template_with_missing_param(self, template_filename): # Setup apis = TemplateApis(notifier=self.MockNotifier(), db=self._db) - template_path = '%s/templates/parameters/%s' % ( - utils.get_resources_dir(), - self.TEMPLATE_WITH_PARAMS) - files_content = [(template_path, self._load_yaml_file(template_path))] + files_content = self._load_template_content(template_filename) params = {'template_name': 'template_with_params_1', 'alarm_name': 'My alarm', 'new_state': 'SUBOPTIMAL'} @@ -82,14 +77,10 @@ class TestTemplates(TestEntityGraphUnitBase, TestConfiguration): self.VALIDATION_FAILED, 163, 'Failed to resolve parameter', results) - def test_validate_template_with_actual_params(self): + def _validate_template_with_actual_params(self, template_filename): # Setup apis = TemplateApis(notifier=self.MockNotifier(), db=self._db) - - template_path = '%s/templates/parameters/%s' % ( - utils.get_resources_dir(), - self.TEMPLATE_WITH_PARAMS) - files_content = [(template_path, self._load_yaml_file(template_path))] + files_content = self._load_template_content(template_filename) params = {'template_name': 'template_with_params_2', 'alarm_type': 'zabbix', 'alarm_name': 'My alarm', 'new_state': 'SUBOPTIMAL'} @@ -102,14 +93,10 @@ class TestTemplates(TestEntityGraphUnitBase, TestConfiguration): self._assert_validate_template_result( self.VALIDATION_OK, 0, 'Template validation is OK', results) - def test_validate_template_with_missing_param_def(self): + def _validate_template_with_missing_param_def(self, template_filename): # Setup apis = TemplateApis(notifier=self.MockNotifier(), db=self._db) - - template_path = '%s/templates/parameters/%s' % ( - utils.get_resources_dir(), - self.TEMPLATE_WITH_MISSING_PARAM_DEF) - files_content = [(template_path, self._load_yaml_file(template_path))] + files_content = self._load_template_content(template_filename) params = {'alarm_type': 'zabbix', 'alarm_name': 'My alarm', 'new_state': 'SUBOPTIMAL'} @@ -122,14 +109,10 @@ class TestTemplates(TestEntityGraphUnitBase, TestConfiguration): self.VALIDATION_FAILED, 161, 'get_param called for a parameter ' 'that is not defined in the \'parameters\' block', results) - def test_validate_template_without_params(self): + def _validate_template_without_params(self, template_filename): # Setup apis = TemplateApis(notifier=self.MockNotifier(), db=self._db) - - template_path = '%s/templates/parameters/%s' % ( - utils.get_resources_dir(), - self.TEMPLATE_WITHOUT_PARAMS) - files_content = [(template_path, self._load_yaml_file(template_path))] + files_content = self._load_template_content(template_filename) # Action results = apis.validate_template(ctx=None, templates=files_content, @@ -139,14 +122,10 @@ class TestTemplates(TestEntityGraphUnitBase, TestConfiguration): self._assert_validate_template_result( self.VALIDATION_OK, 0, 'Template validation is OK', results) - def test_validate_template_with_extra_actual_param(self): + def _validate_template_with_extra_actual_param(self, template_filename): # Setup apis = TemplateApis(notifier=self.MockNotifier(), db=self._db) - - template_path = '%s/templates/parameters/%s' % ( - utils.get_resources_dir(), - self.TEMPLATE_WITH_PARAMS) - files_content = [(template_path, self._load_yaml_file(template_path))] + files_content = self._load_template_content(template_filename) params = {'template_name': 'template_with_params_2', 'alarm_type': 'zabbix', 'alarm_name': 'My alarm', 'new_state': 'SUBOPTIMAL', @@ -160,14 +139,10 @@ class TestTemplates(TestEntityGraphUnitBase, TestConfiguration): self._assert_validate_template_result( self.VALIDATION_OK, 0, 'Template validation is OK', results) - def test_validate_template_with_extra_param_def(self): + def _validate_template_with_extra_param_def(self, template_filename): # Setup apis = TemplateApis(notifier=self.MockNotifier(), db=self._db) - - template_path = '%s/templates/parameters/%s' % ( - utils.get_resources_dir(), - self.TEMPLATE_WITH_EXTRA_PARAM_DEF) - files_content = [(template_path, self._load_yaml_file(template_path))] + files_content = self._load_template_content(template_filename) params = {'template_name': 'template_with_params_2', 'alarm_type': 'zabbix', 'alarm_name': 'My alarm', 'new_state': 'SUBOPTIMAL'} @@ -180,12 +155,9 @@ class TestTemplates(TestEntityGraphUnitBase, TestConfiguration): self._assert_validate_template_result( self.VALIDATION_OK, 0, 'Template validation is OK', results) - def test_add_template_with_no_params(self): + def _add_template_with_no_params(self, template_filename): # Setup - template_path = '%s/templates/parameters/%s' % ( - utils.get_resources_dir(), - self.TEMPLATE_WITH_PARAMS) - files_content = [(template_path, self._load_yaml_file(template_path))] + files_content = self._load_template_content(template_filename) # Action. added_templates = \ @@ -196,15 +168,12 @@ class TestTemplates(TestEntityGraphUnitBase, TestConfiguration): # Test assertions self.assertThat(added_templates, matchers.HasLength(1)) self.assertEqual('ERROR', added_templates[0]['status']) - self.assertEqual('Failed to resolve parameter', - added_templates[0]['status details']) + self.assert_starts_with('Failed to resolve parameter', + added_templates[0]['status details']) - def test_add_template_with_missing_param(self): + def _add_template_with_missing_param(self, template_filename): # Setup - template_path = '%s/templates/parameters/%s' % ( - utils.get_resources_dir(), - self.TEMPLATE_WITH_PARAMS) - files_content = [(template_path, self._load_yaml_file(template_path))] + files_content = self._load_template_content(template_filename) params = {'template_name': 'template_with_params_3', 'alarm_name': 'My alarm', 'new_state': 'SUBOPTIMAL'} @@ -217,15 +186,12 @@ class TestTemplates(TestEntityGraphUnitBase, TestConfiguration): # Test assertions self.assertThat(added_templates, matchers.HasLength(1)) self.assertEqual('ERROR', added_templates[0]['status']) - self.assertEqual('Failed to resolve parameter', - added_templates[0]['status details']) + self.assert_starts_with('Failed to resolve parameter', + added_templates[0]['status details']) - def test_add_template_with_actual_params(self): + def _add_template_with_actual_params(self, template_filename): # Setup - template_path = '%s/templates/parameters/%s' % ( - utils.get_resources_dir(), - self.TEMPLATE_WITH_PARAMS) - files_content = [(template_path, self._load_yaml_file(template_path))] + files_content = self._load_template_content(template_filename) params = {'template_name': 'template_with_params_4', 'alarm_type': 'zabbix', 'alarm_name': 'My alarm', 'new_state': 'SUBOPTIMAL'} @@ -240,12 +206,9 @@ class TestTemplates(TestEntityGraphUnitBase, TestConfiguration): self.assertThat(added_templates, matchers.HasLength(1)) self.assertEqual('LOADING', added_templates[0]['status']) - def test_add_template_with_missing_param_def(self): + def _add_template_with_missing_param_def(self, template_filename): # Setup - template_path = '%s/templates/parameters/%s' % ( - utils.get_resources_dir(), - self.TEMPLATE_WITH_MISSING_PARAM_DEF) - files_content = [(template_path, self._load_yaml_file(template_path))] + files_content = self._load_template_content(template_filename) params = {'alarm_type': 'zabbix', 'alarm_name': 'My alarm', 'new_state': 'SUBOPTIMAL'} @@ -257,16 +220,13 @@ class TestTemplates(TestEntityGraphUnitBase, TestConfiguration): # Test assertions self.assertEqual('ERROR', added_templates[0]['status']) - self.assertEqual('get_param called for a parameter that is not ' - 'defined in the \'parameters\' block', - added_templates[0]['status details']) + self.assert_starts_with('get_param called for a parameter that is not ' + 'defined in the \'parameters\' block', + added_templates[0]['status details']) - def test_add_template_without_params(self): + def _add_template_without_params(self, template_filename): # Setup - template_path = '%s/templates/parameters/%s' % ( - utils.get_resources_dir(), - self.TEMPLATE_WITHOUT_PARAMS) - files_content = [(template_path, self._load_yaml_file(template_path))] + files_content = self._load_template_content(template_filename) # Action added_templates = \ @@ -278,12 +238,9 @@ class TestTemplates(TestEntityGraphUnitBase, TestConfiguration): self.assertThat(added_templates, matchers.HasLength(1)) self.assertEqual('LOADING', added_templates[0]['status']) - def test_add_template_with_extra_actual_param(self): + def _add_template_with_extra_actual_param(self, template_filename): # Setup - template_path = '%s/templates/parameters/%s' % ( - utils.get_resources_dir(), - self.TEMPLATE_WITH_PARAMS) - files_content = [(template_path, self._load_yaml_file(template_path))] + files_content = self._load_template_content(template_filename) params = {'template_name': 'template_with_extra_actual_param', 'alarm_type': 'zabbix', 'alarm_name': 'My alarm', 'new_state': 'SUBOPTIMAL', @@ -299,12 +256,9 @@ class TestTemplates(TestEntityGraphUnitBase, TestConfiguration): self.assertThat(added_templates, matchers.HasLength(1)) self.assertEqual('LOADING', added_templates[0]['status']) - def test_add_template_with_extra_param_def(self): + def _add_template_with_extra_param_def(self, template_filename): # Setup - template_path = '%s/templates/parameters/%s' % ( - utils.get_resources_dir(), - self.TEMPLATE_WITH_EXTRA_PARAM_DEF) - files_content = [(template_path, self._load_yaml_file(template_path))] + files_content = self._load_template_content(template_filename) params = {'template_name': 'template_with_extra_param_def', 'alarm_type': 'zabbix', 'alarm_name': 'My alarm', 'new_state': 'SUBOPTIMAL'} @@ -329,7 +283,7 @@ class TestTemplates(TestEntityGraphUnitBase, TestConfiguration): self.assertThat(results, matchers.HasLength(1)) self.assertEqual(expected_status, results[0]['status']) self.assertEqual(expected_status_code, results[0]['status code']) - self.assertEqual(expected_message, results[0]['message']) + self.assert_starts_with(expected_message, results[0]['message']) def _delete_templates(self): if self.added_template: diff --git a/vitrage/tests/functional/api_handler/test_templates_v2.py b/vitrage/tests/functional/api_handler/test_templates_v2.py new file mode 100644 index 000000000..3f91a2ae4 --- /dev/null +++ b/vitrage/tests/functional/api_handler/test_templates_v2.py @@ -0,0 +1,67 @@ +# Copyright 2019 - Nokia +# +# 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. +from vitrage.tests.functional.api_handler.test_templates import TestTemplates + +TEMPLATE_WITH_PARAMS = 'v2_with_params.yaml' +TEMPLATE_WITH_EXTRA_PARAM_DEF = 'v2_with_extra_param_def.yaml' +TEMPLATE_WITH_MISSING_PARAM_DEF = 'v2_with_missing_param_def.yaml' +TEMPLATE_WITHOUT_PARAMS = 'v2_without_params.yaml' + + +class TestTemplatesV2(TestTemplates): + + def test_validate_template_with_no_params(self): + self._validate_template_with_no_params(TEMPLATE_WITH_PARAMS) + + def test_validate_template_with_missing_param(self): + self._validate_template_with_missing_param(TEMPLATE_WITH_PARAMS) + + def test_validate_template_with_actual_params(self): + self._validate_template_with_actual_params(TEMPLATE_WITH_PARAMS) + + def test_validate_template_with_missing_param_def(self): + self._validate_template_with_missing_param_def( + TEMPLATE_WITH_MISSING_PARAM_DEF) + + def test_validate_template_without_params(self): + self._validate_template_without_params(TEMPLATE_WITHOUT_PARAMS) + + def test_validate_template_with_extra_actual_param(self): + self._validate_template_with_extra_actual_param(TEMPLATE_WITH_PARAMS) + + def test_validate_template_with_extra_param_def(self): + self._validate_template_with_extra_param_def( + TEMPLATE_WITH_EXTRA_PARAM_DEF) + + def test_add_template_with_no_params(self): + self._add_template_with_no_params(TEMPLATE_WITH_PARAMS) + + def test_add_template_with_missing_param(self): + self._add_template_with_missing_param(TEMPLATE_WITH_PARAMS) + + def test_add_template_with_actual_params(self): + self._add_template_with_actual_params(TEMPLATE_WITH_PARAMS) + + def test_add_template_with_missing_param_def(self): + self._add_template_with_missing_param_def( + TEMPLATE_WITH_MISSING_PARAM_DEF) + + def test_add_template_without_params(self): + self._add_template_without_params(TEMPLATE_WITHOUT_PARAMS) + + def test_add_template_with_extra_actual_param(self): + self._add_template_with_extra_actual_param(TEMPLATE_WITH_PARAMS) + + def test_add_template_with_extra_param_def(self): + self._add_template_with_extra_param_def(TEMPLATE_WITH_EXTRA_PARAM_DEF) diff --git a/vitrage/tests/functional/api_handler/test_templates_v3.py b/vitrage/tests/functional/api_handler/test_templates_v3.py new file mode 100644 index 000000000..6f064f2aa --- /dev/null +++ b/vitrage/tests/functional/api_handler/test_templates_v3.py @@ -0,0 +1,60 @@ +# Copyright 2019 - Nokia +# +# 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. +from vitrage.tests.functional.api_handler.test_templates import TestTemplates + +TEMPLATE_WITH_PARAMS = 'v3_with_params.yaml' +TEMPLATE_WITH_EXTRA_PARAM_DEF = 'v3_with_extra_param_def.yaml' +TEMPLATE_WITH_MISSING_PARAM_DEF = 'v3_with_missing_param_def.yaml' + + +class TestTemplatesV3(TestTemplates): + + def test_validate_template_with_no_params(self): + self._validate_template_with_no_params(TEMPLATE_WITH_PARAMS) + + def test_validate_template_with_missing_param(self): + self._validate_template_with_missing_param(TEMPLATE_WITH_PARAMS) + + def test_validate_template_with_actual_params(self): + self._validate_template_with_actual_params(TEMPLATE_WITH_PARAMS) + + def test_validate_template_with_missing_param_def(self): + self._validate_template_with_missing_param_def( + TEMPLATE_WITH_MISSING_PARAM_DEF) + + def test_validate_template_with_extra_actual_param(self): + self._validate_template_with_extra_actual_param(TEMPLATE_WITH_PARAMS) + + def test_validate_template_with_extra_param_def(self): + self._validate_template_with_extra_param_def( + TEMPLATE_WITH_EXTRA_PARAM_DEF) + + def test_add_template_with_no_params(self): + self._add_template_with_no_params(TEMPLATE_WITH_PARAMS) + + def test_add_template_with_missing_param(self): + self._add_template_with_missing_param(TEMPLATE_WITH_PARAMS) + + def test_add_template_with_actual_params(self): + self._add_template_with_actual_params(TEMPLATE_WITH_PARAMS) + + def test_add_template_with_missing_param_def(self): + self._add_template_with_missing_param_def( + TEMPLATE_WITH_MISSING_PARAM_DEF) + + def test_add_template_with_extra_actual_param(self): + self._add_template_with_extra_actual_param(TEMPLATE_WITH_PARAMS) + + def test_add_template_with_extra_param_def(self): + self._add_template_with_extra_param_def(TEMPLATE_WITH_EXTRA_PARAM_DEF) diff --git a/vitrage/tests/resources/templates/parameters/with_extra_param_def.yaml b/vitrage/tests/resources/templates/parameters/v2_with_extra_param_def.yaml similarity index 100% rename from vitrage/tests/resources/templates/parameters/with_extra_param_def.yaml rename to vitrage/tests/resources/templates/parameters/v2_with_extra_param_def.yaml diff --git a/vitrage/tests/resources/templates/parameters/with_missing_param_def.yaml b/vitrage/tests/resources/templates/parameters/v2_with_missing_param_def.yaml similarity index 100% rename from vitrage/tests/resources/templates/parameters/with_missing_param_def.yaml rename to vitrage/tests/resources/templates/parameters/v2_with_missing_param_def.yaml diff --git a/vitrage/tests/resources/templates/parameters/with_params.yaml b/vitrage/tests/resources/templates/parameters/v2_with_params.yaml similarity index 100% rename from vitrage/tests/resources/templates/parameters/with_params.yaml rename to vitrage/tests/resources/templates/parameters/v2_with_params.yaml diff --git a/vitrage/tests/resources/templates/parameters/without_params.yaml b/vitrage/tests/resources/templates/parameters/v2_without_params.yaml similarity index 100% rename from vitrage/tests/resources/templates/parameters/without_params.yaml rename to vitrage/tests/resources/templates/parameters/v2_without_params.yaml diff --git a/vitrage/tests/resources/templates/parameters/v3_with_extra_param_def.yaml b/vitrage/tests/resources/templates/parameters/v3_with_extra_param_def.yaml new file mode 100644 index 000000000..983199787 --- /dev/null +++ b/vitrage/tests/resources/templates/parameters/v3_with_extra_param_def.yaml @@ -0,0 +1,30 @@ +metadata: + version: 3 + type: standard + name: get_param(template_name) + description: template with extra parameter def (extra_param is unused) +parameters: + template_name: + description: the name of the template + default: template_with_params + alarm_type: + description: the type of the alarm + alarm_name: + new_state: + default: ERROR + extra_param: + description: a parameter definition that is unused in the template +entities: + alarm: + category: ALARM + type: get_param(alarm_type) + name: get_param(alarm_name) + host: + category: RESOURCE + type: nova.host +scenarios: + - condition: alarm [on] host + actions: + - set_state: + state: get_param(new_state) + target: host diff --git a/vitrage/tests/resources/templates/parameters/v3_with_missing_param_def.yaml b/vitrage/tests/resources/templates/parameters/v3_with_missing_param_def.yaml new file mode 100644 index 000000000..c5ea09ae8 --- /dev/null +++ b/vitrage/tests/resources/templates/parameters/v3_with_missing_param_def.yaml @@ -0,0 +1,24 @@ +metadata: + version: 3 + type: standard + name: template_with_missing_param_def + description: INVALID template with missing parameter def for alarm_name +parameters: + alarm_type: + description: the type of the alarm + new_state: + default: ERROR +entities: + alarm: + category: ALARM + type: get_param(alarm_type) + name: get_param(alarm_name) + host: + category: RESOURCE + type: nova.host +scenarios: + - condition: alarm [on] host + actions: + - set_state: + state: get_param(new_state) + target: host diff --git a/vitrage/tests/resources/templates/parameters/v3_with_params.yaml b/vitrage/tests/resources/templates/parameters/v3_with_params.yaml new file mode 100644 index 000000000..0474436e7 --- /dev/null +++ b/vitrage/tests/resources/templates/parameters/v3_with_params.yaml @@ -0,0 +1,28 @@ +metadata: + version: 3 + type: standard + name: get_param(template_name) + description: template with parameters +parameters: + template_name: + description: the name of the template + default: template_with_params + alarm_type: + description: the type of the alarm + alarm_name: + new_state: + default: ERROR +entities: + alarm: + category: ALARM + type: get_param(alarm_type) + name: get_param(alarm_name) + host: + category: RESOURCE + type: nova.host +scenarios: + - condition: alarm [on] host + actions: + - set_state: + state: get_param(new_state) + target: host diff --git a/vitrage/tests/unit/evaluator/template_validation/content/v1/test_parameters_validator.py b/vitrage/tests/unit/evaluator/template_validation/content/v1/test_parameters_validator.py index 00a0ad326..976c69270 100644 --- a/vitrage/tests/unit/evaluator/template_validation/content/v1/test_parameters_validator.py +++ b/vitrage/tests/unit/evaluator/template_validation/content/v1/test_parameters_validator.py @@ -26,11 +26,13 @@ class ParametersValidatorTest(ValidatorTest): """ def test_validate_no_parameters(self): - result = GetParamValidator.validate(template={}, actual_params=None) + result = GetParamValidator.validate( + template={'alarm_name': "Don't add a comment"}, actual_params=None) self._assert_correct_result(result) def test_validate_empty_parameters(self): - result = GetParamValidator.validate(template={}, actual_params={}) + result = GetParamValidator.validate( + template={'alarm_name': '+2 for everybody'}, actual_params={}) self._assert_correct_result(result) def test_validate_with_parameter(self): diff --git a/vitrage/tests/unit/evaluator/template_validation/content/v2/test_parameters_validator.py b/vitrage/tests/unit/evaluator/template_validation/content/v2/test_parameters_validator.py index 891cb36dc..cf961958d 100644 --- a/vitrage/tests/unit/evaluator/template_validation/content/v2/test_parameters_validator.py +++ b/vitrage/tests/unit/evaluator/template_validation/content/v2/test_parameters_validator.py @@ -14,6 +14,7 @@ from vitrage.evaluator.template_fields import TemplateFields from vitrage.evaluator.template_functions.v2.functions import get_param +from vitrage.evaluator.template_validation.base import ValidationError from vitrage.evaluator.template_validation.content.v2.get_param_validator \ import GetParamValidator from vitrage.tests.unit.evaluator.template_validation.content.base import \ @@ -70,13 +71,15 @@ class ParametersValidatorTest(ValidatorTest): def test_validate_get_param_with_no_parameters(self): template = {'alarm_name': 'get_param(param1)'} - result, _ = get_param('get_param(param1)', template) - self._assert_fault_result(result, 161) + self.assert_get_param_result('get_param(param1)', + template, + expected_error_code=161) def test_validate_get_param_with_empty_parameters(self): template = {} - result, _ = get_param('get_param(param1)', template) - self._assert_fault_result(result, 161) + self.assert_get_param_result('get_param(param1)', + template, + expected_error_code=161) def test_validate_get_param_with_undefined_parameter(self): template = { @@ -90,8 +93,9 @@ class ParametersValidatorTest(ValidatorTest): }, } } - result, _ = get_param('get_param(undefined)', template) - self._assert_fault_result(result, 161) + self.assert_get_param_result('get_param(undefined)', + template, + expected_error_code=161) def test_validate_get_param_with_valid_parameter(self): template = { @@ -105,8 +109,9 @@ class ParametersValidatorTest(ValidatorTest): }, } } - result, result_value = get_param('get_param(param1)', template) - self._assert_correct_result(result) + self.assert_get_param_result('get_param(param1)', + template, + expected_error_code=0) def test_validate_get_param_with_malformed_parameter(self): template = { @@ -121,23 +126,23 @@ class ParametersValidatorTest(ValidatorTest): } } - result, _ = get_param('get_param(param1', template) - self._assert_fault_result(result, 162) + self.assert_get_param_result( + 'get_param(param1', template, expected_error_code=162) - result, _ = get_param('get_paramparam1)', template) - self._assert_fault_result(result, 162) + self.assert_get_param_result( + 'get_paramparam1)', template, expected_error_code=162) - result, _ = get_param('get_paramparam1', template) - self._assert_fault_result(result, 162) + self.assert_get_param_result( + 'get_paramparam1', template, expected_error_code=162) - result, _ = get_param('get_param', template) - self._assert_fault_result(result, 162) + self.assert_get_param_result( + 'get_param', template, expected_error_code=162) - result, _ = get_param('get_param()', template) - self._assert_fault_result(result, 162) + self.assert_get_param_result( + 'get_param()', template, expected_error_code=162) - result, _ = get_param('get_param)param1(', template) - self._assert_fault_result(result, 162) + self.assert_get_param_result( + 'get_param)param1(', template, expected_error_code=162) def test_validate_get_param_with_actual_parameter(self): template = { @@ -155,10 +160,10 @@ class ParametersValidatorTest(ValidatorTest): 'param1': 'value1', 'param2': 'value2' } - result, result_value = get_param('get_param(param2)', template, - actual_params=actual_params) - self._assert_correct_result(result) - self.assertEqual('value2', result_value) + self.assert_get_param_result('get_param(param2)', + template, + actual_params, + expected_result='value2') def test_validate_get_param_with_missing_actual_parameter(self): template = { @@ -175,9 +180,9 @@ class ParametersValidatorTest(ValidatorTest): actual_params = { 'param1': 'value1', } - result, _ = get_param('get_param(param2)', template, - actual_params=actual_params) - self._assert_fault_result(result, 163) + self.assert_get_param_result('get_param(param2)', + template, actual_params, + expected_error_code=163) def test_validate_get_param_with_default_actual_parameter(self): template = { @@ -194,7 +199,27 @@ class ParametersValidatorTest(ValidatorTest): actual_params = { 'param2': 'value2', } - result, result_value = get_param('get_param(param1)', template, - actual_params=actual_params) - self._assert_correct_result(result) - self.assertEqual('this is my default', result_value) + self.assert_get_param_result('get_param(param1)', + template, + actual_params, + expected_result='this is my default') + + def assert_get_param_result(self, + func_str, + template, + actual_params=None, + expected_result=None, + expected_error_code=None): + error = None + try: + result = get_param(func_str, template, actual_params=actual_params) + except ValidationError as e: + error = e + + if expected_error_code: + self.assertIsNotNone(error) + self.assertEqual(error.code, expected_error_code) + else: + self.assertIsNone(error) + if expected_result: + self.assertEqual(expected_result, result)