Support per-version template loading + change execute_mistral structure

This change contains refactoring of the template validation and loading,
in order to allow different validators and loaders for execute_mistral
action in versions 1 and 2.

* template_versions.py contains all supported versions, including their
  validators and loaders (and in the future - functions like get_attr())
* template_version_factory.py allows getting the right template version
  instance
* validators and loaders are retrieved only from the template-version
  instance, and are no longer created directly
* All content validators and loaders were moved under v1 and v2 packages

In addition, the structure of execute_mistral has changed, so now all
the input parameters of the workflow must reside under an 'input' section.

Change-Id: I8eebc79b0885eab9c013fafdc17ae8378c7bcedf
This commit is contained in:
Ifat Afek 2017-12-20 14:04:33 +00:00
parent c4f7989bf4
commit 4576523641
49 changed files with 911 additions and 189 deletions

View File

@ -117,6 +117,9 @@ The following describes all the possible status code and their messages:
| 135 | condition must contain a common entity for all 'or' | content |
| | clauses | |
+------------------+---------------------------------------------------------+-------------------------------+
| 136 | Input parameters for Mistral workflow in execute_mistral| content (version 2) |
| | action must be placed under an 'input' block | |
+------------------+---------------------------------------------------------+-------------------------------+
| 140 | At least one template must be included | syntax |
+------------------+---------------------------------------------------------+-------------------------------+
| 141 | Name field is unspecified for include | syntax |

View File

@ -11,6 +11,12 @@ This page describes the format of the Vitrage templates, with some examples and
open questions on extending this format. Additionally, a short guide on adding
templates is presented.
*Note:* This document refers to Vitrage templates version 2. The documentation
of version 1 can be found here_
.. _here: https://docs.openstack.org/vitrage/pike/
Template Structure
==================
The template is written in YAML language, with the following structure:
@ -463,9 +469,10 @@ its parameters.
action:
action_type: execute_mistral
properties:
workflow: demo_workflow # mandatory. The name of the workflow to be executed
farewell: Goodbye and Good Luck! # optional. A list of properties to be passed to the workflow
employee: John Smith # optional. A list of properties to be passed to the workflow
workflow: demo_workflow # mandatory. The name of the workflow to be executed
input: # optional. A list of properties to be passed to the workflow
farewell: Goodbye and Good Luck!
employee: John Smith
Future support & Open Issues

View File

@ -0,0 +1,6 @@
---
features:
- Refactored the ``execute-mistral`` action. All input parameters should
appear under an ``input`` section. The change takes effect in template
version 2. ``execute-mistral`` actions from version 1 are automatically
converted to the new format.

View File

@ -14,6 +14,8 @@
from oslo_config import cfg
from vitrage.evaluator.template_schemas import init_template_schemas
# Register options for the service
OPTS = [
@ -38,3 +40,5 @@ OPTS = [
'determined, else a default worker count of 1 is returned.'
),
]
init_template_schemas()

View File

@ -18,6 +18,7 @@ from vitrage.evaluator.actions.recipes.base import ActionStepWrapper
MISTRAL = 'mistral'
INPUT = 'input'
WORKFLOW = 'workflow'

View File

@ -20,6 +20,7 @@ from oslo_log import log
from vitrage.common.constants import EdgeProperties as EProps
from vitrage.common.constants import VertexProperties as VProps
from vitrage.common.utils import recursive_keypairs
from vitrage.datasources.listener_service import defaultdict
from vitrage.entity_graph.mappings.datasource_info_mapper \
import DatasourceInfoMapper
@ -238,11 +239,19 @@ class ScenarioEvaluator(EvaluatorBase):
@staticmethod
def _generate_action_id(action_spec):
"""Generate a unique action id for the action
BEWARE: The implementation of this function MUST NOT BE CHANGED!!
The created hash is used for storing the active actions in the
database. If changed, existing active actions can no longer be
retrieved.
"""
targets = [(k, v.vertex_id) for k, v in action_spec.targets.items()]
return hash(
(action_spec.type,
tuple(sorted(targets)),
tuple(sorted(action_spec.properties.items())))
tuple(sorted(recursive_keypairs(action_spec.properties))))
)
def _analyze_and_filter_actions(self, actions):

View File

@ -25,8 +25,8 @@ from vitrage.evaluator.equivalence_repository import EquivalenceRepository
from vitrage.evaluator.template_fields import TemplateFields
from vitrage.evaluator.template_loading.scenario_loader import ScenarioLoader
from vitrage.evaluator.template_loading.template_loader import TemplateLoader
from vitrage.evaluator.template_validation.content.definitions_validator \
import DefinitionsValidator as DefValidator
from vitrage.evaluator.template_validation.content.base import \
get_template_schema
from vitrage.evaluator.template_validation.content.template_content_validator \
import content_validation
from vitrage.evaluator.template_validation.template_syntax_validator import \
@ -135,23 +135,30 @@ class ScenarioRepository(object):
self._add_scenario(equivalent_scenario)
def add_def_template(self, def_template):
result, template_schema = get_template_schema(def_template)
result = def_template_syntax_validation(def_template)
if not result.is_valid_config:
LOG.info('Unable to load definition template, syntax err: %s'
% result.comment)
else:
result = DefValidator.def_template_content_validation(def_template)
if result.is_valid_config:
result = def_template_syntax_validation(def_template)
if not result.is_valid_config:
LOG.info('Unable to load definition template, content err: %s'
LOG.info('Unable to load definition template, syntax err: %s'
% result.comment)
current_time = datetime_utils.utcnow()
include_uuid = uuidutils.generate_uuid()
self._def_templates[str(include_uuid)] = Template(include_uuid,
def_template,
current_time,
result)
if result.is_valid_config:
def_validator = \
template_schema.validator(TemplateFields.DEFINITIONS)
result = \
def_validator.def_template_content_validation(def_template)
if result.is_valid_config:
current_time = datetime_utils.utcnow()
include_uuid = uuidutils.generate_uuid()
self._def_templates[str(include_uuid)] = Template(include_uuid,
def_template,
current_time,
result)
else:
LOG.info('Unable to load definition template, content err: %s'
% result.comment)
def _expand_equivalence(self, scenario):
equivalent_scenarios = [scenario]

View File

@ -15,10 +15,6 @@
from vitrage.common.constants import TemplateTopologyFields
DEFAULT_VERSION = '1'
SUPPORTED_VERSIONS = ['1']
class TemplateFields(TemplateTopologyFields):
SCENARIOS = 'scenarios'

View File

@ -12,24 +12,29 @@
# License for the specific language governing permissions and limitations
# under the License.
from oslo_log import log
from vitrage.common.exception import VitrageError
from vitrage.evaluator.condition import get_condition_common_targets
from vitrage.evaluator.condition import parse_condition
from vitrage.evaluator.condition import SymbolResolver
from vitrage.evaluator.template_data import ActionSpecs
from vitrage.evaluator.template_data import EdgeDescription
from vitrage.evaluator.template_data import ENTITY
from vitrage.evaluator.template_data import RELATIONSHIP
from vitrage.evaluator.template_data import Scenario
from vitrage.evaluator.template_fields import TemplateFields as TFields
from vitrage.evaluator.template_loading.subgraph_builder import SubGraphBuilder
from vitrage.evaluator.template_loading.subgraph_builder import \
SubGraphBuilder
from vitrage.graph import Vertex
LOG = log.getLogger(__name__)
class ScenarioLoader(object):
def __init__(self, name, entities, relationships):
def __init__(self, template_schema, name, entities, relationships):
self.name = name
self._template_schema = template_schema
self._template_entities = entities
self._template_relationships = relationships
@ -93,14 +98,14 @@ class ScenarioLoader(object):
for counter, action_def in enumerate(actions_def):
action_id = '%s-action%s' % (scenario_id, str(counter))
action_dict = action_def[TFields.ACTION]
action_type = action_dict[TFields.ACTION_TYPE]
targets = action_dict.get(TFields.ACTION_TARGET,
self.valid_target)
properties = action_dict.get(TFields.PROPERTIES, {})
action_type = action_def[TFields.ACTION][TFields.ACTION_TYPE]
action_loader = self._template_schema.loader(action_type)
actions.append(
ActionSpecs(action_id, action_type, targets, properties))
if action_loader:
actions.append(action_loader.load(action_id, self.valid_target,
action_def))
else:
LOG.warning('Failed to load action of type %s', action_type)
return actions

View File

@ -12,17 +12,23 @@
# License for the specific language governing permissions and limitations
# under the License.
from oslo_log import log
from vitrage.common.constants import VertexProperties as VProps
from vitrage.evaluator.template_data import EdgeDescription
from vitrage.evaluator.template_data import TemplateData
from vitrage.evaluator.template_fields import TemplateFields as TFields
from vitrage.evaluator.template_loading.props_converter import PropsConverter
from vitrage.evaluator.template_loading.scenario_loader import ScenarioLoader
from vitrage.evaluator.template_schema_factory import TemplateSchemaFactory
from vitrage.graph import Edge
from vitrage.graph import Vertex
from vitrage.utils import evaluator as evaluator_utils
LOG = log.getLogger(__name__)
class TemplateLoader(object):
PROPS_CONVERSION = {
@ -43,6 +49,12 @@ class TemplateLoader(object):
self.relationships = {}
def load(self, template_def, def_templates=None):
template_schema = self._get_template_schema(template_def)
if not template_schema:
LOG.error('Failed to load template - unsupported version')
return
name = template_def[TFields.METADATA][TFields.NAME]
if def_templates is None:
@ -70,8 +82,9 @@ class TemplateLoader(object):
def_templates,
self.relationships)
scenarios = ScenarioLoader(name, self.entities, self.relationships)\
.build_scenarios(template_def[TFields.SCENARIOS])
scenarios = ScenarioLoader(template_schema, name, self.entities,
self.relationships).\
build_scenarios(template_def[TFields.SCENARIOS])
return TemplateData(name, self.entities, self.relationships, scenarios)
@ -162,3 +175,13 @@ class TemplateLoader(object):
ignore_ids = [TFields.TEMPLATE_ID, TFields.SOURCE, TFields.TARGET]
return \
{key: var_dict[key] for key in var_dict if key not in ignore_ids}
@staticmethod
def _get_template_schema(template):
metadata = template.get(TFields.METADATA)
if metadata:
version = metadata.get(TFields.VERSION)
return TemplateSchemaFactory().template_schema(version)
else:
return None

View File

@ -0,0 +1,15 @@
# Copyright 2017 - 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.
__author__ = 'stack'

View File

@ -0,0 +1,37 @@
# Copyright 2016 - 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.
import abc
from vitrage.evaluator.template_data import ActionSpecs
from vitrage.evaluator.template_fields import TemplateFields as TFields
class BaseActionLoader(object):
def load(self, action_id, valid_target, action_def):
action_dict = action_def[TFields.ACTION]
action_type = action_dict[TFields.ACTION_TYPE]
targets = action_dict.get(TFields.ACTION_TARGET, valid_target)
return ActionSpecs(action_id, action_type, targets,
self._get_properties(action_dict))
@abc.abstractmethod
def _get_properties(self, action_dict):
pass
class ActionLoader(BaseActionLoader):
def _get_properties(self, action_dict):
return action_dict.get(TFields.PROPERTIES, {})

View File

@ -0,0 +1,29 @@
# Copyright 2017 - 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 copy import deepcopy
from vitrage.evaluator.actions.recipes.execute_mistral import INPUT
from vitrage.evaluator.actions.recipes.execute_mistral import WORKFLOW
from vitrage.evaluator.template_fields import TemplateFields as TFields
from vitrage.evaluator.template_loading.v1.action_loader import \
BaseActionLoader
class ExecuteMistralLoader(BaseActionLoader):
def _get_properties(self, action_dict):
"""Place all properties under an 'input' block"""
properties = action_dict.get(TFields.PROPERTIES, {})
input_properties = deepcopy(properties)
input_properties.pop(WORKFLOW)
return {WORKFLOW: properties[WORKFLOW], INPUT: input_properties}

View File

@ -0,0 +1,42 @@
# Copyright 2017 - 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 oslo_log import log
LOG = log.getLogger(__name__)
class TemplateSchemaFactory(object):
DEFAULT_VERSION = '1'
_schemas = dict()
@classmethod
def supported_versions(cls):
return cls._schemas.keys()
@classmethod
def is_version_supported(cls, version):
return version in cls._schemas
@classmethod
def template_schema(cls, version):
if not version:
version = cls.DEFAULT_VERSION
template_schema = cls._schemas.get(version)
return template_schema
@classmethod
def register_template_schema(cls, version, template_schema):
cls._schemas[version] = template_schema
LOG.debug('Registered template schema for version %s', version)

View File

@ -0,0 +1,86 @@
# Copyright 2017 - 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 oslo_log import log
from vitrage.evaluator.actions.base import ActionType
from vitrage.evaluator.template_fields import TemplateFields
from vitrage.evaluator.template_loading.v1.action_loader import ActionLoader
from vitrage.evaluator.template_loading.v1.execute_mistral_loader import \
ExecuteMistralLoader
from vitrage.evaluator.template_validation.content.v1.\
add_causal_relationship_validator import AddCausalRelationshipValidator
from vitrage.evaluator.template_validation.content.v1.definitions_validator \
import DefinitionsValidator
from vitrage.evaluator.template_validation.content.v1.\
execute_mistral_validator import ExecuteMistralValidator as \
V1ExecuteMistralValidator
from vitrage.evaluator.template_validation.content.v1.mark_down_validator \
import MarkDownValidator
from vitrage.evaluator.template_validation.content.v1.raise_alarm_validator \
import RaiseAlarmValidator
from vitrage.evaluator.template_validation.content.v1.scenario_validator \
import ScenarioValidator
from vitrage.evaluator.template_validation.content.v1.set_state_validator \
import SetStateValidator
from vitrage.evaluator.template_validation.content.v2.\
execute_mistral_validator import ExecuteMistralValidator as \
V2ExecuteMistralValidator
from vitrage.evaluator.template_schema_factory import TemplateSchemaFactory
LOG = log.getLogger(__name__)
class TemplateSchema1(object):
def __init__(self):
self._validators = {
TemplateFields.DEFINITIONS: DefinitionsValidator,
TemplateFields.SCENARIOS: ScenarioValidator,
ActionType.ADD_CAUSAL_RELATIONSHIP: AddCausalRelationshipValidator,
ActionType.EXECUTE_MISTRAL: V1ExecuteMistralValidator,
ActionType.MARK_DOWN: MarkDownValidator,
ActionType.RAISE_ALARM: RaiseAlarmValidator,
ActionType.SET_STATE: SetStateValidator,
}
self._loaders = {
ActionType.ADD_CAUSAL_RELATIONSHIP: ActionLoader(),
ActionType.EXECUTE_MISTRAL: ExecuteMistralLoader(),
ActionType.MARK_DOWN: ActionLoader(),
ActionType.RAISE_ALARM: ActionLoader(),
ActionType.SET_STATE: ActionLoader(),
}
def validator(self, validator_type):
LOG.debug('Get validator. validator_type: %s. validators: %s',
validator_type, self._validators)
return self._validators.get(validator_type)
def loader(self, loader_type):
LOG.debug('Get loader. loader_type: %s. loaders: %s',
loader_type, self._loaders)
return self._loaders.get(loader_type)
class TemplateSchema2(TemplateSchema1):
def __init__(self):
super(TemplateSchema2, self).__init__()
self._validators[ActionType.EXECUTE_MISTRAL] = \
V2ExecuteMistralValidator()
self._loaders[ActionType.EXECUTE_MISTRAL] = ActionLoader()
def init_template_schemas():
TemplateSchemaFactory.register_template_schema('1', TemplateSchema1())
TemplateSchemaFactory.register_template_schema('2', TemplateSchema2())

View File

@ -16,6 +16,8 @@ import abc
import six
from oslo_log import log
from vitrage.evaluator.template_fields import TemplateFields
from vitrage.evaluator.template_schema_factory import TemplateSchemaFactory
from vitrage.evaluator.template_validation.base import get_correct_result
from vitrage.evaluator.template_validation.base import get_fault_result
from vitrage.evaluator.template_validation.status_messages import status_msgs
@ -42,6 +44,23 @@ def validate_template_id(definitions_index, id_to_check):
return get_correct_result(RESULT_DESCRIPTION)
def get_template_schema(template):
metadata = template.get(TemplateFields.METADATA)
if metadata is None:
LOG.error('%s status code: %s' % (status_msgs[62], 62))
return get_content_fault_result(62), None
version = metadata.get(TemplateFields.VERSION)
template_schema = TemplateSchemaFactory().template_schema(version)
if template_schema:
return get_content_correct_result(), template_schema
else:
LOG.error('%s status code: %s' % (status_msgs[63], 63))
return get_content_fault_result(63), None
@six.add_metaclass(abc.ABCMeta)
class ActionValidator(object):

View File

@ -14,18 +14,9 @@
from oslo_log import log
from vitrage.evaluator.template_fields import DEFAULT_VERSION
from vitrage.evaluator.template_fields import SUPPORTED_VERSIONS
from vitrage.evaluator.template_fields import TemplateFields
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.content.definitions_validator \
import DefinitionsValidator as DefValidator
from vitrage.evaluator.template_validation.content.scenario_validator import \
ScenarioValidator
from vitrage.evaluator.template_validation.status_messages import status_msgs
get_template_schema
LOG = log.getLogger(__name__)
@ -35,65 +26,57 @@ def content_validation(template, def_templates=None):
if def_templates is None:
def_templates = {}
result = _validate_version(template)
entities_index = {}
template_definitions = {}
result, template_schema = get_template_schema(template)
def_validator = template_schema.validator(TemplateFields.DEFINITIONS) \
if result.is_valid_config and template_schema else None
if result.is_valid_config and not def_validator:
result.is_valid_config = False # Not supposed to happen
if result.is_valid_config and TemplateFields.DEFINITIONS in template:
template_definitions = template[TemplateFields.DEFINITIONS]
if TemplateFields.ENTITIES in template_definitions:
entities = template_definitions[TemplateFields.ENTITIES]
result = DefValidator.validate_entities_definition(entities,
entities_index)
result = def_validator.validate_entities_definition(entities,
entities_index)
# If there are duplicate definitions in several includes under the same
# name, will regard the first one
if result.is_valid_config and TemplateFields.INCLUDES in template:
template_includes = template[TemplateFields.INCLUDES]
result = \
DefValidator.validate_definitions_with_includes(template_includes,
def_templates,
entities_index)
def_validator.validate_definitions_with_includes(template_includes,
def_templates,
entities_index)
relationship_index = {}
if result.is_valid_config and \
TemplateFields.RELATIONSHIPS in template_definitions:
relationships = template_definitions[TemplateFields.RELATIONSHIPS]
result = \
DefValidator.validate_relationships_definitions(relationships,
relationship_index,
entities_index)
result = def_validator.validate_relationships_definitions(
relationships, relationship_index, entities_index)
if result.is_valid_config and TemplateFields.INCLUDES in template:
template_includes = template[TemplateFields.INCLUDES]
result = DefValidator.validate_relationships_definitions_with_includes(
template_includes,
def_templates,
entities_index,
relationship_index)
result = \
def_validator.validate_relationships_definitions_with_includes(
template_includes,
def_templates,
entities_index,
relationship_index)
if result.is_valid_config:
scenario_validator = template_schema.validator(
TemplateFields.SCENARIOS)
scenarios = template[TemplateFields.SCENARIOS]
definitions_index = entities_index.copy()
definitions_index.update(relationship_index)
result = ScenarioValidator(definitions_index).validate(scenarios)
result = scenario_validator.validate(template_schema,
definitions_index, scenarios)
return result
def _validate_version(template):
metadata = template.get(TemplateFields.METADATA)
if metadata is None:
LOG.error('%s status code: %s' % (status_msgs[62], 62))
return get_content_fault_result(62)
version = metadata.get(TemplateFields.VERSION, DEFAULT_VERSION)
if version in SUPPORTED_VERSIONS:
return get_content_correct_result()
else:
LOG.error('%s status code: %s' % (status_msgs[63], 63))
return get_content_fault_result(63)

View File

@ -0,0 +1,15 @@
# Copyright 2017 - 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.
__author__ = 'stack'

View File

@ -143,6 +143,7 @@ class DefinitionsValidator(object):
return get_content_correct_result()
# noinspection PyBroadException
@classmethod
def _validate_entity_definition(cls, entity_dict, entities_index):
template_id = entity_dict[TemplateFields.TEMPLATE_ID]

View File

@ -19,29 +19,18 @@ from oslo_log import log
from six.moves import reduce
from vitrage.common.constants import EdgeProperties as EProps
from vitrage.evaluator.actions.base import ActionType
from vitrage.evaluator.condition import convert_to_dnf_format
from vitrage.evaluator.condition import get_condition_common_targets
from vitrage.evaluator.condition import is_condition_include_positive_clause
from vitrage.evaluator.condition import parse_condition
from vitrage.evaluator.condition import SymbolResolver
from vitrage.evaluator.template_fields import TemplateFields
from vitrage.evaluator.template_validation.content. \
add_causal_relationship_validator import AddCausalRelationshipValidator
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.content.base import \
validate_template_id
from vitrage.evaluator.template_validation.content.execute_mistral_validator \
import ExecuteMistralValidator
from vitrage.evaluator.template_validation.content.mark_down_validator \
import MarkDownValidator
from vitrage.evaluator.template_validation.content.raise_alarm_validator \
import RaiseAlarmValidator
from vitrage.evaluator.template_validation.content.set_state_validator \
import SetStateValidator
from vitrage.evaluator.template_validation.status_messages import status_msgs
LOG = log.getLogger(__name__)
@ -62,28 +51,29 @@ class ScenarioValidator(object):
def get_entity_id(self, entity):
return entity[TemplateFields.TEMPLATE_ID]
def __init__(self, definitions_index):
self.definitions_index = definitions_index
def validate(self, scenarios):
@classmethod
def validate(cls, template_schema, def_index, scenarios):
for scenario in scenarios:
scenario_values = scenario[TemplateFields.SCENARIO]
condition = scenario_values[TemplateFields.CONDITION]
result = self._validate_scenario_condition(condition)
result = cls._validate_scenario_condition(def_index, condition)
if not result.is_valid_config:
return result
actions = scenario_values[TemplateFields.ACTIONS]
result = self._validate_scenario_actions(actions)
result = cls._validate_scenario_actions(template_schema,
def_index, actions)
if not result.is_valid_config:
return result
return get_content_correct_result()
def _validate_scenario_condition(self, condition):
# noinspection PyBroadException
@classmethod
def _validate_scenario_condition(cls, def_index, condition):
try:
dnf_result = convert_to_dnf_format(condition)
except Exception:
@ -91,7 +81,8 @@ class ScenarioValidator(object):
return get_content_fault_result(85)
# not condition validation
not_condition_result = self._validate_not_condition(dnf_result)
not_condition_result = cls._validate_not_condition(def_index,
dnf_result)
if not not_condition_result.is_valid_config:
return not_condition_result
@ -107,28 +98,29 @@ class ScenarioValidator(object):
continue
result = \
validate_template_id(self.definitions_index, condition_var)
validate_template_id(def_index, condition_var)
if not result.is_valid_config:
return result
# condition structure validation
condition_structure_result = \
self._validate_condition_structure(parse_condition(condition))
condition_structure_result = cls._validate_condition_structure(
def_index, parse_condition(condition))
if not condition_structure_result.is_valid_config:
return condition_structure_result
return get_content_correct_result()
def _validate_condition_structure(self, condition_dnf):
@classmethod
def _validate_condition_structure(cls, def_index, condition_dnf):
result = \
self._validate_condition_includes_positive_clause(condition_dnf)
cls._validate_condition_includes_positive_clause(condition_dnf)
if not result.is_valid_config:
return result
common_targets = \
get_condition_common_targets(condition_dnf,
self.definitions_index,
self.TemplateSymbolResolver())
def_index,
cls.TemplateSymbolResolver())
return get_content_correct_result() if common_targets \
else get_content_fault_result(135)
@ -139,65 +131,63 @@ class ScenarioValidator(object):
is_condition_include_positive_clause(condition) \
else get_content_fault_result(134)
def _validate_not_condition(self, dnf_result):
@classmethod
def _validate_not_condition(cls, def_index, dnf_result):
"""Not operator validation
Not operator can appear only on edges.
:param dnf_result:
:param definitions_index:
:param def_index:
:return:
"""
if isinstance(dnf_result, Not):
for arg in dnf_result.args:
if isinstance(arg, Symbol):
definition = self.definitions_index.get(str(arg), None)
definition = def_index.get(str(arg), None)
if not (definition and
definition.get(EProps.RELATIONSHIP_TYPE)):
msg = status_msgs[86] + ' template id: %s' % arg
LOG.error('%s status code: %s' % (msg, 86))
return get_content_fault_result(86, msg)
else:
res = self._validate_not_condition(arg)
res = cls._validate_not_condition(def_index, arg)
if not res.is_valid_config:
return res
return get_content_correct_result()
for arg in dnf_result.args:
if not isinstance(arg, Symbol):
res = self._validate_not_condition(arg)
res = cls._validate_not_condition(def_index, arg)
if not res.is_valid_config:
return res
return get_content_correct_result()
def _validate_scenario_actions(self, actions):
@classmethod
def _validate_scenario_actions(cls,
template_schema,
def_index,
actions):
for action in actions:
result = \
self._validate_scenario_action(action[TemplateFields.ACTION])
cls._validate_scenario_action(template_schema,
def_index,
action[TemplateFields.ACTION])
if not result.is_valid_config:
return result
return get_content_correct_result()
def _validate_scenario_action(self, action):
@staticmethod
def _validate_scenario_action(template_schema, def_index, action):
action_type = action[TemplateFields.ACTION_TYPE]
action_validator = template_schema.validator(action_type)
action_validators = {
ActionType.RAISE_ALARM: RaiseAlarmValidator(),
ActionType.SET_STATE: SetStateValidator(),
ActionType.ADD_CAUSAL_RELATIONSHIP:
AddCausalRelationshipValidator(),
ActionType.MARK_DOWN: MarkDownValidator(),
ActionType.EXECUTE_MISTRAL: ExecuteMistralValidator(),
}
if action_type not in action_validators:
if not action_validator:
LOG.error('%s status code: %s' % (status_msgs[120], 120))
return get_content_fault_result(120)
return action_validators[action_type].validate(action,
self.definitions_index)
return action_validator.validate(action, def_index)

View File

@ -0,0 +1,15 @@
# Copyright 2017 - 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.
__author__ = 'stack'

View File

@ -0,0 +1,47 @@
# Copyright 2017 - 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 oslo_log import log
from vitrage.evaluator.actions.recipes.execute_mistral import INPUT
from vitrage.evaluator.actions.recipes.execute_mistral import WORKFLOW
from vitrage.evaluator.template_fields import TemplateFields
from vitrage.evaluator.template_validation.content.base import \
ActionValidator
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
LOG = log.getLogger(__name__)
class ExecuteMistralValidator(ActionValidator):
@staticmethod
def validate(action, definitions_index):
properties = action[TemplateFields.PROPERTIES]
if WORKFLOW not in properties or not properties[WORKFLOW]:
LOG.error('%s status code: %s' % (status_msgs[133], 133))
return get_content_fault_result(133)
for prop in properties:
if prop not in {WORKFLOW, INPUT}:
LOG.error('%s status code: %s' % (status_msgs[136], 136))
return get_content_fault_result(136)
return get_content_correct_result()

View File

@ -14,7 +14,8 @@
from vitrage.common.constants import EdgeLabel
from vitrage.common.constants import EntityCategory
from vitrage.evaluator.actions.base import action_types
from vitrage.evaluator.template_fields import SUPPORTED_VERSIONS
from vitrage.evaluator.template_schema_factory import TemplateSchemaFactory
status_msgs = {
@ -43,7 +44,7 @@ status_msgs = {
60: 'metadata section must contain id field.',
62: 'metadata is a mandatory section.',
63: 'Unsupported version. Version must be one of: {versions}'
.format(versions=SUPPORTED_VERSIONS),
.format(versions=TemplateSchemaFactory.supported_versions()),
# scenarios section status messages 80-99
80: 'scenarios is a mandatory section.',
@ -84,6 +85,8 @@ status_msgs = {
'block',
134: 'condition can not contain only \'not\' clauses',
135: 'condition must contain a common entity for all \'or\' clauses',
136: 'Input parameters for the Mistral workflow in execute_mistral action '
'must be placed under an \'input\' block ',
# def_templates status messages 140-159
140: 'At least one template must be included',

View File

@ -14,6 +14,7 @@
from oslo_log import log as logging
from vitrage.common.constants import NotifierEventTypes
from vitrage.evaluator.actions.recipes.execute_mistral import INPUT
from vitrage.evaluator.actions.recipes.execute_mistral import WORKFLOW
from vitrage.notifier.plugins.base import NotifierBase
from vitrage import os_clients
@ -63,11 +64,11 @@ class MistralNotifier(NotifierBase):
try:
workflow = data[WORKFLOW]
del data[WORKFLOW]
workflow_input = data.get(INPUT, {})
response = self.client.executions.create(
workflow_identifier=workflow,
workflow_input=data,
workflow_input=workflow_input,
wf_params={})
if response:

View File

@ -1,25 +1,26 @@
metadata:
name: execute_mistral
version: 1
name: v1_execute_mistral
description: execute mistral
definitions:
entities:
- entity:
category: ALARM
name: compute.host.down
template_id: host_down_alarm
name: notifiers.mistral.trigger.alarm.1
template_id: alarm
- entity:
category: RESOURCE
type: nova.host
template_id: host
relationships:
- relationship:
source: host_down_alarm
source: alarm
relationship_type: on
target: host
template_id : host_down_alarm_on_host
template_id : alarm_on_host
scenarios:
- scenario:
condition: host_down_alarm_on_host
condition: alarm_on_host
actions:
- action:
action_type: execute_mistral

View File

@ -0,0 +1,30 @@
metadata:
version: 2
name: v2_execute_mistral
description: execute mistral
definitions:
entities:
- entity:
category: ALARM
name: notifiers.mistral.trigger.alarm.2
template_id: alarm
- entity:
category: RESOURCE
type: nova.host
template_id: host
relationships:
- relationship:
source: alarm
relationship_type: on
target: host
template_id : alarm_on_host
scenarios:
- scenario:
condition: alarm_on_host
actions:
- action:
action_type: execute_mistral
properties:
workflow: wf_for_tempest_test_1234
input:
farewell: Hello and Goodbye

View File

@ -11,28 +11,30 @@
# 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 abc
from vitrage.evaluator.actions.base import ActionType
from vitrage.evaluator.actions.recipes.execute_mistral import WORKFLOW
from vitrage.evaluator.template_fields import TemplateFields
from vitrage.evaluator.template_validation.content.execute_mistral_validator \
import ExecuteMistralValidator
from vitrage.tests.unit.evaluator.template_validation.content.base import \
ActionValidatorTest
from vitrage.tests.unit.evaluator.template_validation.content.base import \
DEFINITIONS_INDEX_MOCK
class ExecuteMistralValidatorTest(ActionValidatorTest):
class BaseExecuteMistralValidatorTest(ActionValidatorTest):
def test_validate_execute_mistral_action(self):
@abc.abstractmethod
def _create_execute_mistral_action(self, workflow, host, host_state):
pass
def _validate_execute_mistral_action(self, validator):
self._validate_action(
self._create_execute_mistral_action('wf_1', 'host_2', 'down'),
ExecuteMistralValidator.validate
validator.validate
)
def test_validate_execute_mistral_action_without_workflow(self):
def _validate_execute_mistral_action_without_workflow(self, validator):
# Test setup
idx = DEFINITIONS_INDEX_MOCK.copy()
@ -40,51 +42,63 @@ class ExecuteMistralValidatorTest(ActionValidatorTest):
action[TemplateFields.PROPERTIES].pop(WORKFLOW)
# Test action
result = ExecuteMistralValidator.validate(action, idx)
result = validator.validate(action, idx)
# Test assertions
self._assert_fault_result(result, 133)
def test_validate_execute_mistral_action_with_empty_workflow(self):
def _validate_execute_mistral_action_with_empty_workflow(self, validator):
# Test setup
idx = DEFINITIONS_INDEX_MOCK.copy()
action = self._create_execute_mistral_action('', 'host_2', 'down')
# Test action
result = ExecuteMistralValidator.validate(action, idx)
result = validator.validate(action, idx)
# Test assertions
self._assert_fault_result(result, 133)
def test_validate_execute_mistral_action_with_none_workflow(self):
def _validate_execute_mistral_action_with_none_workflow(self, validator):
# Test setup
idx = DEFINITIONS_INDEX_MOCK.copy()
action = self._create_execute_mistral_action(None, 'host_2', 'down')
# Test action
result = ExecuteMistralValidator.validate(action, idx)
result = validator.validate(action, idx)
# Test assertions
self._assert_fault_result(result, 133)
def test_validate_execute_mistral_action_without_additional_params(self):
def _validate_execute_mistral_action_without_additional_props(self,
validator):
# Test setup - having only the 'workflow' param is a legal config
idx = DEFINITIONS_INDEX_MOCK.copy()
action = self._create_execute_mistral_action('wf_1', 'host_2', 'down')
action[TemplateFields.PROPERTIES].pop('host')
action[TemplateFields.PROPERTIES].pop('host_state')
action = self._create_no_input_mistral_action('wf_1')
# Test action
result = ExecuteMistralValidator.validate(action, idx)
result = validator.validate(action, idx)
# Test assertions
self._assert_correct_result(result)
@staticmethod
def _create_execute_mistral_action(workflow, host, host_state):
def _create_no_input_mistral_action(workflow):
properties = {
WORKFLOW: workflow,
}
action = {
TemplateFields.ACTION_TYPE: ActionType.EXECUTE_MISTRAL,
TemplateFields.PROPERTIES: properties
}
return action
@staticmethod
def _create_v1_execute_mistral_action(workflow, host, host_state):
properties = {
WORKFLOW: workflow,
@ -97,3 +111,21 @@ class ExecuteMistralValidatorTest(ActionValidatorTest):
}
return action
@staticmethod
def _create_v2_execute_mistral_action(workflow, host, host_state):
input_props = {
'host': host,
'host_state': host_state
}
properties = {
WORKFLOW: workflow,
'input': input_props
}
action = {
TemplateFields.ACTION_TYPE: ActionType.EXECUTE_MISTRAL,
TemplateFields.PROPERTIES: properties
}
return action

View File

@ -0,0 +1,15 @@
# Copyright 2017 - 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.
__author__ = 'stack'

View File

@ -14,7 +14,7 @@
from vitrage.evaluator.actions.base import ActionType
from vitrage.evaluator.template_fields import TemplateFields
from vitrage.evaluator.template_validation.content. \
from vitrage.evaluator.template_validation.content.v1.\
add_causal_relationship_validator import AddCausalRelationshipValidator
from vitrage.tests.unit.evaluator.template_validation.content.base import \
ActionValidatorTest

View File

@ -0,0 +1,65 @@
# Copyright 2017 - 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.evaluator.template_fields import TemplateFields
from vitrage.evaluator.template_validation.content.v1.\
execute_mistral_validator import ExecuteMistralValidator as \
V1ExecuteMistralValidator
from vitrage.tests.unit.evaluator.template_validation.content.\
base_test_execute_mistral_validator import BaseExecuteMistralValidatorTest
from vitrage.tests.unit.evaluator.template_validation.content.base import \
DEFINITIONS_INDEX_MOCK
class ExecuteMistralValidatorTest(BaseExecuteMistralValidatorTest):
@classmethod
def setUpClass(cls):
super(ExecuteMistralValidatorTest, cls).setUpClass()
cls.validator = V1ExecuteMistralValidator()
def test_v1_validate_execute_mistral_action(self):
self._validate_execute_mistral_action(self.validator)
def test_validate_execute_mistral_action_without_workflow(self):
self._validate_execute_mistral_action_without_workflow(self.validator)
def test_validate_execute_mistral_action_with_empty_workflow(self):
self._validate_execute_mistral_action_with_empty_workflow(
self.validator)
def test_validate_execute_mistral_action_with_none_workflow(self):
self._validate_execute_mistral_action_with_none_workflow(
self.validator)
def test_validate_execute_mistral_action_without_additional_props(self):
self._validate_execute_mistral_action_without_additional_props(
self.validator)
def test_validate_execute_mistral_action_with_input_prop(self):
"""A version1 execute_mistral action can have an 'input' property"""
# Test setup
idx = DEFINITIONS_INDEX_MOCK.copy()
action = self._create_execute_mistral_action('wf_1', 'host_2', 'down')
action[TemplateFields.PROPERTIES]['input'] = 'kuku'
# Test action
result = self.validator.validate(action, idx)
# Test assertions
self._assert_correct_result(result)
def _create_execute_mistral_action(self, workflow, host, host_state):
return self.\
_create_v1_execute_mistral_action(workflow, host, host_state)

View File

@ -14,7 +14,7 @@
from vitrage.evaluator.actions.base import ActionType
from vitrage.evaluator.template_fields import TemplateFields
from vitrage.evaluator.template_validation.content.mark_down_validator import \
from vitrage.evaluator.template_validation.content.v1.mark_down_validator import \
MarkDownValidator
from vitrage.tests.unit.evaluator.template_validation.content.base import \
ActionValidatorTest

View File

@ -14,7 +14,7 @@
from vitrage.evaluator.actions.base import ActionType
from vitrage.evaluator.template_fields import TemplateFields
from vitrage.evaluator.template_validation.content.raise_alarm_validator \
from vitrage.evaluator.template_validation.content.v1.raise_alarm_validator \
import RaiseAlarmValidator
from vitrage.tests.unit.evaluator.template_validation.content.base import \
ActionValidatorTest

View File

@ -16,7 +16,7 @@ from vitrage.entity_graph.mappings.operational_resource_state import \
OperationalResourceState
from vitrage.evaluator.actions.base import ActionType
from vitrage.evaluator.template_fields import TemplateFields
from vitrage.evaluator.template_validation.content.set_state_validator \
from vitrage.evaluator.template_validation.content.v1.set_state_validator \
import SetStateValidator
from vitrage.tests.unit.evaluator.template_validation.content.base import \
ActionValidatorTest

View File

@ -0,0 +1,15 @@
# Copyright 2017 - 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.
__author__ = 'stack'

View File

@ -0,0 +1,68 @@
# Copyright 2017 - 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.evaluator.template_validation.content.v2.\
execute_mistral_validator import ExecuteMistralValidator as \
V2ExecuteMistralValidator
from vitrage.tests.unit.evaluator.template_validation.content.\
base_test_execute_mistral_validator import BaseExecuteMistralValidatorTest
from vitrage.tests.unit.evaluator.template_validation.content.base import \
DEFINITIONS_INDEX_MOCK
class ExecuteMistralValidatorTest(BaseExecuteMistralValidatorTest):
@classmethod
def setUpClass(cls):
super(ExecuteMistralValidatorTest, cls).setUpClass()
cls.validator = V2ExecuteMistralValidator()
def test_v2_validate_execute_mistral_action(self):
self._validate_execute_mistral_action(self.validator)
def test_v2_validate_old_execute_mistral_action(self):
"""Test version2 validator on version1 template.
An execute_mistral action from version 1 should fail in the validation
of version 2.
"""
# Test setup
idx = DEFINITIONS_INDEX_MOCK.copy()
v1_action = \
self._create_v1_execute_mistral_action('wf_1', 'host_2', 'down')
# Test action
result = self.validator.validate(v1_action, idx)
# Test assertions
self._assert_fault_result(result, 136)
def test_validate_execute_mistral_action_without_workflow(self):
self._validate_execute_mistral_action_without_workflow(self.validator)
def test_validate_execute_mistral_action_with_empty_workflow(self):
self._validate_execute_mistral_action_with_empty_workflow(
self.validator)
def test_validate_execute_mistral_action_with_none_workflow(self):
self._validate_execute_mistral_action_with_none_workflow(
self.validator)
def test_validate_execute_mistral_action_without_additional_props(self):
self._validate_execute_mistral_action_without_additional_props(
self.validator)
def _create_execute_mistral_action(self, workflow, host, host_state):
return self.\
_create_v2_execute_mistral_action(workflow, host, host_state)

View File

@ -15,7 +15,7 @@
from vitrage.evaluator.condition import SymbolResolver
from vitrage.evaluator.template_data import EdgeDescription
from vitrage.evaluator.template_loading.template_loader import TemplateLoader
from vitrage.evaluator.template_validation.content.scenario_validator \
from vitrage.evaluator.template_validation.content.v1.scenario_validator \
import get_condition_common_targets
from vitrage.tests import base
from vitrage.tests.mocks import utils

View File

@ -0,0 +1,48 @@
# Copyright 2016 - 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.evaluator.actions.base import ActionType
from vitrage.evaluator.scenario_evaluator import ScenarioEvaluator
from vitrage.evaluator.template_data import ActionSpecs
from vitrage.tests import base
class TestScenarioEvaluator(base.BaseTest):
def test_verify_execute_mistral_v2_action_hash(self):
execute_mistral_action_spec_1 = \
ActionSpecs(id='mistmistmist1',
type=ActionType.EXECUTE_MISTRAL,
targets={},
properties={'workflow': 'wf4',
'input': {
'prop1': 'ppp',
'prop2': 'qqq',
'prop3': 'rrr',
}})
execute_mistral_action_spec_2 = \
ActionSpecs(id='mistmistmist2',
type=ActionType.EXECUTE_MISTRAL,
targets={},
properties={'workflow': 'wf4',
'input': {
'prop2': 'qqq',
'prop3': 'rrr',
'prop1': 'ppp',
}})
self.assertEqual(ScenarioEvaluator.
_generate_action_id(execute_mistral_action_spec_1),
ScenarioEvaluator.
_generate_action_id(execute_mistral_action_spec_2))

View File

@ -38,6 +38,8 @@ class BasicTemplateTest(base.BaseTest):
BASIC_TEMPLATE = 'basic.yaml'
BASIC_TEMPLATE_WITH_INCLUDE = 'basic_with_include.yaml'
V1_MISTRAL_TEMPLATE = 'v1_execute_mistral.yaml'
V2_MISTRAL_TEMPLATE = 'v2_execute_mistral.yaml'
DEF_TEMPLATE_TESTS_DIR = utils.get_resources_dir() +\
'/templates/def_template_tests'
@ -230,6 +232,39 @@ class BasicTemplateTest(base.BaseTest):
expected_relationships,
expected_scenario)
def test_convert_v1_template(self):
# Load v1 and v2 templates, and get their actions
v1_action = self._get_template_single_action(self.V1_MISTRAL_TEMPLATE)
v2_action = self._get_template_single_action(self.V2_MISTRAL_TEMPLATE)
# Validate that the action definition is identical (since v1 template
# should be converted to v2 format)
self._assert_equal_actions(v1_action, v2_action)
def _get_template_single_action(self, template_file):
template_path = '%s/templates/version/%s' % (utils.get_resources_dir(),
template_file)
template_definition = file_utils.load_yaml_file(template_path, True)
template_data = TemplateLoader().load(template_definition)
scenarios = template_data.scenarios
self.assertIsNotNone(scenarios, 'Template should include a scenario')
self.assertEqual(1, len(scenarios),
'Template should include a single scenario')
actions = scenarios[0].actions
self.assertIsNotNone(actions, 'Scenario should include an action')
self.assertEqual(1, len(actions),
'Scenario should include a single action')
return actions[0]
def _assert_equal_actions(self, action1, action2):
"""Compare all action fields except from the id"""
self.assertEqual(action1.type, action2.type,
'Action types should be equal')
self.assert_dict_equal(action1.targets, action2.targets,
'Action targets should be equal')
self.assert_dict_equal(action1.properties, action2.properties,
'Action targets should be equal')
def _validate_strict_equal(self,
template_data,
expected_entities,

View File

@ -14,14 +14,13 @@
from datetime import datetime
from oslo_log import log as logging
from vitrage_tempest_tests.tests.base import BaseVitrageTempest
from vitrage_tempest_tests.tests.e2e.test_basic_actions import TestActionsBase
LOG = logging.getLogger(__name__)
DOWN = 'down'
UP = 'up'
class BaseTestEvents(BaseVitrageTempest):
class BaseTestEvents(TestActionsBase):
"""Test class for Vitrage event API"""
# noinspection PyPep8Naming

View File

@ -21,7 +21,7 @@ from vitrage.common.constants import EntityCategory
from vitrage.common.constants import EventProperties as EventProps
from vitrage.common.constants import VertexProperties as VProps
from vitrage_tempest_tests.tests.api.event.base import BaseTestEvents
from vitrage_tempest_tests.tests.api.event.base import DOWN
from vitrage_tempest_tests.tests.common.vitrage_utils import DOWN
from vitrage_tempest_tests.tests.utils import wait_for_answer

View File

@ -15,11 +15,12 @@ from datetime import datetime
from vitrage.datasources import NOVA_HOST_DATASOURCE
from vitrage.datasources import NOVA_INSTANCE_DATASOURCE
from vitrage_tempest_tests.tests.api.event.base import DOWN
from vitrage_tempest_tests.tests.api.event.base import UP
from vitrage_tempest_tests.tests.common import general_utils as g_utils
from vitrage_tempest_tests.tests.common.tempest_clients import TempestClients
DOWN = 'down'
UP = 'up'
def generate_fake_host_alarm(hostname, event_type, enabled=True):
details = {

View File

@ -17,10 +17,7 @@ from testtools.matchers import HasLength
from vitrage import os_clients
from vitrage_tempest_tests.tests.api.event.base import BaseTestEvents
from vitrage_tempest_tests.tests.api.event.base import DOWN
from vitrage_tempest_tests.tests.api.event.base import UP
from vitrage_tempest_tests.tests.common.tempest_clients import TempestClients
from vitrage_tempest_tests.tests.common import vitrage_utils
from vitrage_tempest_tests.tests import utils
from vitrage_tempest_tests.tests.utils import wait_for_status
@ -46,25 +43,34 @@ wf_for_tempest_test_1234:
class TestMistralNotifier(BaseTestEvents):
TRIGGER_ALARM_1 = "notifiers.mistral.trigger.alarm.1"
TRIGGER_ALARM_2 = "notifiers.mistral.trigger.alarm.2"
@classmethod
def setUpClass(cls):
super(TestMistralNotifier, cls).setUpClass()
cls.mistral_client = os_clients.mistral_client(cls.conf)
@utils.tempest_logger
def test_execute_mistral(self):
hostname = vitrage_utils.get_first_host()['name']
def test_execute_mistral_v1(self):
self._do_test_execute_mistral(self.TRIGGER_ALARM_1)
@utils.tempest_logger
def test_execute_mistral_v2(self):
self._do_test_execute_mistral(self.TRIGGER_ALARM_2)
def _do_test_execute_mistral(self, trigger_alarm):
workflows = self.mistral_client.workflows.list()
self.assertIsNotNone(workflows)
self.assertIsNotNone(workflows, 'Failed to get the list of workflows')
num_workflows = len(workflows)
executions = self.mistral_client.executions.list()
self.assertIsNotNone(executions)
self.assertIsNotNone(executions,
'Failed to get the list of workflow executions')
num_executions = len(executions)
alarms = utils.wait_for_answer(2, 0.5, self._check_alarms)
self.assertIsNotNone(alarms)
self.assertIsNotNone(alarms, 'Failed to get the list of alarms')
num_alarms = len(alarms)
try:
@ -73,54 +79,58 @@ class TestMistralNotifier(BaseTestEvents):
# Validate the workflow creation
workflows = self.mistral_client.workflows.list()
self.assertIsNotNone(workflows)
self.assertThat(workflows, HasLength(num_workflows + 1))
self.assertIsNotNone(workflows,
'Failed to get the list of workflows')
self.assertThat(workflows, HasLength(num_workflows + 1),
'Mistral workflow was not created')
# Send a Doctor event that should generate an alarm. According to
# execute_mistral.yaml template, the alarm should cause execution
# of the workflow
details = self._create_doctor_event_details(hostname, DOWN)
self._post_event(details)
# Trigger an alarm. According to v1_execute_mistral.yaml template,
# the alarm should cause execution of the workflow
self._trigger_do_action(trigger_alarm)
# Wait for the alarm to be raised
self.assertTrue(wait_for_status(
10,
self._check_num_vitrage_alarms,
num_alarms=num_alarms + 1))
num_alarms=num_alarms + 1),
'Trigger alarm was not raised')
# Wait for the Mistral workflow execution
self.assertTrue(wait_for_status(
20,
self._check_mistral_workflow_execution,
num_executions=num_executions + 1))
num_executions=num_executions + 1),
'Mistral workflow was not executed')
except Exception as e:
self._handle_exception(e)
raise
finally:
self._rollback_to_default(WF_NAME, num_workflows,
hostname, num_alarms)
trigger_alarm, num_alarms)
pass
def _rollback_to_default(self, workflow_name, num_workflows,
hostname, num_alarms):
trigger_alarm, num_alarms):
# Delete the workflow
self.mistral_client.workflows.delete(workflow_name)
workflows = self.mistral_client.workflows.list()
self.assertIsNotNone(workflows)
self.assertThat(workflows, HasLength(num_workflows))
self.assertIsNotNone(workflows, 'Failed to get the list of workflows')
self.assertThat(workflows, HasLength(num_workflows),
'Failed to remove the test workflow')
# Clear the host down event and wait for the alarm to be deleted
details = self._create_doctor_event_details(hostname, UP)
self._post_event(details)
# Clear the trigger alarm and wait it to be deleted
self._trigger_undo_action(trigger_alarm)
self.assertTrue(wait_for_status(
10,
self._check_num_vitrage_alarms,
num_alarms=num_alarms))
num_alarms=num_alarms),
'Vitrage trigger alarm was not deleted')
def _check_num_vitrage_alarms(self, num_alarms):
@staticmethod
def _check_num_vitrage_alarms(num_alarms):
vitrage_alarms = TempestClients.vitrage().alarm.list(vitrage_id='all',
all_tenants=True)
if len(vitrage_alarms) == num_alarms:

View File

@ -0,0 +1,29 @@
metadata:
version: 1
name: v1_execute_mistral
description: execute mistral
definitions:
entities:
- entity:
category: ALARM
name: notifiers.mistral.trigger.alarm.1
template_id: alarm
- entity:
category: RESOURCE
type: nova.host
template_id: host
relationships:
- relationship:
source: alarm
relationship_type: on
target: host
template_id : alarm_on_host
scenarios:
- scenario:
condition: alarm_on_host
actions:
- action:
action_type: execute_mistral
properties:
workflow: wf_for_tempest_test_1234
farewell: Hello and Goodbye

View File

@ -0,0 +1,30 @@
metadata:
version: 2
name: v2_execute_mistral
description: execute mistral
definitions:
entities:
- entity:
category: ALARM
name: notifiers.mistral.trigger.alarm.2
template_id: alarm
- entity:
category: RESOURCE
type: nova.host
template_id: host
relationships:
- relationship:
source: alarm
relationship_type: on
target: host
template_id : alarm_on_host
scenarios:
- scenario:
condition: alarm_on_host
actions:
- action:
action_type: execute_mistral
properties:
workflow: wf_for_tempest_test_1234
input:
farewell: Hello and Goodbye