Merge "Support per-version template loading + change execute_mistral structure"

This commit is contained in:
Zuul 2018-01-01 11:59:49 +00:00 committed by Gerrit Code Review
commit 4ff1908d1b
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 | | 135 | condition must contain a common entity for all 'or' | content |
| | clauses | | | | 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 | | 140 | At least one template must be included | syntax |
+------------------+---------------------------------------------------------+-------------------------------+ +------------------+---------------------------------------------------------+-------------------------------+
| 141 | Name field is unspecified for include | 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 open questions on extending this format. Additionally, a short guide on adding
templates is presented. 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 Template Structure
================== ==================
The template is written in YAML language, with the following structure: The template is written in YAML language, with the following structure:
@ -463,9 +469,10 @@ its parameters.
action: action:
action_type: execute_mistral action_type: execute_mistral
properties: properties:
workflow: demo_workflow # mandatory. The name of the workflow to be executed 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 input: # 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 farewell: Goodbye and Good Luck!
employee: John Smith
Future support & Open Issues 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 oslo_config import cfg
from vitrage.evaluator.template_schemas import init_template_schemas
# Register options for the service # Register options for the service
OPTS = [ OPTS = [
@ -38,3 +40,5 @@ OPTS = [
'determined, else a default worker count of 1 is returned.' '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' MISTRAL = 'mistral'
INPUT = 'input'
WORKFLOW = 'workflow' 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 EdgeProperties as EProps
from vitrage.common.constants import VertexProperties as VProps from vitrage.common.constants import VertexProperties as VProps
from vitrage.common.utils import recursive_keypairs
from vitrage.datasources.listener_service import defaultdict from vitrage.datasources.listener_service import defaultdict
from vitrage.entity_graph.mappings.datasource_info_mapper \ from vitrage.entity_graph.mappings.datasource_info_mapper \
import DatasourceInfoMapper import DatasourceInfoMapper
@ -238,11 +239,19 @@ class ScenarioEvaluator(EvaluatorBase):
@staticmethod @staticmethod
def _generate_action_id(action_spec): 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()] targets = [(k, v.vertex_id) for k, v in action_spec.targets.items()]
return hash( return hash(
(action_spec.type, (action_spec.type,
tuple(sorted(targets)), tuple(sorted(targets)),
tuple(sorted(action_spec.properties.items()))) tuple(sorted(recursive_keypairs(action_spec.properties))))
) )
def _analyze_and_filter_actions(self, actions): 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_fields import TemplateFields
from vitrage.evaluator.template_loading.scenario_loader import ScenarioLoader from vitrage.evaluator.template_loading.scenario_loader import ScenarioLoader
from vitrage.evaluator.template_loading.template_loader import TemplateLoader from vitrage.evaluator.template_loading.template_loader import TemplateLoader
from vitrage.evaluator.template_validation.content.definitions_validator \ from vitrage.evaluator.template_validation.content.base import \
import DefinitionsValidator as DefValidator get_template_schema
from vitrage.evaluator.template_validation.content.template_content_validator \ from vitrage.evaluator.template_validation.content.template_content_validator \
import content_validation import content_validation
from vitrage.evaluator.template_validation.template_syntax_validator import \ from vitrage.evaluator.template_validation.template_syntax_validator import \
@ -135,23 +135,30 @@ class ScenarioRepository(object):
self._add_scenario(equivalent_scenario) self._add_scenario(equivalent_scenario)
def add_def_template(self, def_template): def add_def_template(self, def_template):
result, template_schema = get_template_schema(def_template)
result = def_template_syntax_validation(def_template) if result.is_valid_config:
if not result.is_valid_config: result = def_template_syntax_validation(def_template)
LOG.info('Unable to load definition template, syntax err: %s'
% result.comment)
else:
result = DefValidator.def_template_content_validation(def_template)
if not result.is_valid_config: 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) % result.comment)
current_time = datetime_utils.utcnow() if result.is_valid_config:
include_uuid = uuidutils.generate_uuid() def_validator = \
self._def_templates[str(include_uuid)] = Template(include_uuid, template_schema.validator(TemplateFields.DEFINITIONS)
def_template, result = \
current_time, def_validator.def_template_content_validation(def_template)
result)
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): def _expand_equivalence(self, scenario):
equivalent_scenarios = [scenario] equivalent_scenarios = [scenario]

View File

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

View File

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

View File

@ -12,17 +12,23 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
from oslo_log import log
from vitrage.common.constants import VertexProperties as VProps from vitrage.common.constants import VertexProperties as VProps
from vitrage.evaluator.template_data import EdgeDescription from vitrage.evaluator.template_data import EdgeDescription
from vitrage.evaluator.template_data import TemplateData from vitrage.evaluator.template_data import TemplateData
from vitrage.evaluator.template_fields import TemplateFields as TFields from vitrage.evaluator.template_fields import TemplateFields as TFields
from vitrage.evaluator.template_loading.props_converter import PropsConverter from vitrage.evaluator.template_loading.props_converter import PropsConverter
from vitrage.evaluator.template_loading.scenario_loader import ScenarioLoader 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 Edge
from vitrage.graph import Vertex from vitrage.graph import Vertex
from vitrage.utils import evaluator as evaluator_utils from vitrage.utils import evaluator as evaluator_utils
LOG = log.getLogger(__name__)
class TemplateLoader(object): class TemplateLoader(object):
PROPS_CONVERSION = { PROPS_CONVERSION = {
@ -43,6 +49,12 @@ class TemplateLoader(object):
self.relationships = {} self.relationships = {}
def load(self, template_def, def_templates=None): 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] name = template_def[TFields.METADATA][TFields.NAME]
if def_templates is None: if def_templates is None:
@ -70,8 +82,9 @@ class TemplateLoader(object):
def_templates, def_templates,
self.relationships) self.relationships)
scenarios = ScenarioLoader(name, self.entities, self.relationships)\ scenarios = ScenarioLoader(template_schema, name, self.entities,
.build_scenarios(template_def[TFields.SCENARIOS]) self.relationships).\
build_scenarios(template_def[TFields.SCENARIOS])
return TemplateData(name, self.entities, self.relationships, 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] ignore_ids = [TFields.TEMPLATE_ID, TFields.SOURCE, TFields.TARGET]
return \ return \
{key: var_dict[key] for key in var_dict if key not in ignore_ids} {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 import six
from oslo_log import log 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_correct_result
from vitrage.evaluator.template_validation.base import get_fault_result from vitrage.evaluator.template_validation.base import get_fault_result
from vitrage.evaluator.template_validation.status_messages import status_msgs 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) 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) @six.add_metaclass(abc.ABCMeta)
class ActionValidator(object): class ActionValidator(object):

View File

@ -14,18 +14,9 @@
from oslo_log import log 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_fields import TemplateFields
from vitrage.evaluator.template_validation.content.base import \ from vitrage.evaluator.template_validation.content.base import \
get_content_correct_result get_template_schema
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
LOG = log.getLogger(__name__) LOG = log.getLogger(__name__)
@ -35,65 +26,57 @@ def content_validation(template, def_templates=None):
if def_templates is None: if def_templates is None:
def_templates = {} def_templates = {}
result = _validate_version(template)
entities_index = {} entities_index = {}
template_definitions = {} 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: if result.is_valid_config and TemplateFields.DEFINITIONS in template:
template_definitions = template[TemplateFields.DEFINITIONS] template_definitions = template[TemplateFields.DEFINITIONS]
if TemplateFields.ENTITIES in template_definitions: if TemplateFields.ENTITIES in template_definitions:
entities = template_definitions[TemplateFields.ENTITIES] entities = template_definitions[TemplateFields.ENTITIES]
result = DefValidator.validate_entities_definition(entities, result = def_validator.validate_entities_definition(entities,
entities_index) entities_index)
# If there are duplicate definitions in several includes under the same # If there are duplicate definitions in several includes under the same
# name, will regard the first one # name, will regard the first one
if result.is_valid_config and TemplateFields.INCLUDES in template: if result.is_valid_config and TemplateFields.INCLUDES in template:
template_includes = template[TemplateFields.INCLUDES] template_includes = template[TemplateFields.INCLUDES]
result = \ result = \
DefValidator.validate_definitions_with_includes(template_includes, def_validator.validate_definitions_with_includes(template_includes,
def_templates, def_templates,
entities_index) entities_index)
relationship_index = {} relationship_index = {}
if result.is_valid_config and \ if result.is_valid_config and \
TemplateFields.RELATIONSHIPS in template_definitions: TemplateFields.RELATIONSHIPS in template_definitions:
relationships = template_definitions[TemplateFields.RELATIONSHIPS] relationships = template_definitions[TemplateFields.RELATIONSHIPS]
result = \ result = def_validator.validate_relationships_definitions(
DefValidator.validate_relationships_definitions(relationships, relationships, relationship_index, entities_index)
relationship_index,
entities_index)
if result.is_valid_config and TemplateFields.INCLUDES in template: if result.is_valid_config and TemplateFields.INCLUDES in template:
template_includes = template[TemplateFields.INCLUDES] template_includes = template[TemplateFields.INCLUDES]
result = DefValidator.validate_relationships_definitions_with_includes( result = \
template_includes, def_validator.validate_relationships_definitions_with_includes(
def_templates, template_includes,
entities_index, def_templates,
relationship_index) entities_index,
relationship_index)
if result.is_valid_config: if result.is_valid_config:
scenario_validator = template_schema.validator(
TemplateFields.SCENARIOS)
scenarios = template[TemplateFields.SCENARIOS] scenarios = template[TemplateFields.SCENARIOS]
definitions_index = entities_index.copy() definitions_index = entities_index.copy()
definitions_index.update(relationship_index) definitions_index.update(relationship_index)
result = ScenarioValidator(definitions_index).validate(scenarios) result = scenario_validator.validate(template_schema,
definitions_index, scenarios)
return result 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() return get_content_correct_result()
# noinspection PyBroadException
@classmethod @classmethod
def _validate_entity_definition(cls, entity_dict, entities_index): def _validate_entity_definition(cls, entity_dict, entities_index):
template_id = entity_dict[TemplateFields.TEMPLATE_ID] template_id = entity_dict[TemplateFields.TEMPLATE_ID]

View File

@ -19,29 +19,18 @@ from oslo_log import log
from six.moves import reduce from six.moves import reduce
from vitrage.common.constants import EdgeProperties as EProps 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 convert_to_dnf_format
from vitrage.evaluator.condition import get_condition_common_targets from vitrage.evaluator.condition import get_condition_common_targets
from vitrage.evaluator.condition import is_condition_include_positive_clause from vitrage.evaluator.condition import is_condition_include_positive_clause
from vitrage.evaluator.condition import parse_condition from vitrage.evaluator.condition import parse_condition
from vitrage.evaluator.condition import SymbolResolver from vitrage.evaluator.condition import SymbolResolver
from vitrage.evaluator.template_fields import TemplateFields 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 \ from vitrage.evaluator.template_validation.content.base import \
get_content_correct_result get_content_correct_result
from vitrage.evaluator.template_validation.content.base import \ from vitrage.evaluator.template_validation.content.base import \
get_content_fault_result get_content_fault_result
from vitrage.evaluator.template_validation.content.base import \ from vitrage.evaluator.template_validation.content.base import \
validate_template_id 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 from vitrage.evaluator.template_validation.status_messages import status_msgs
LOG = log.getLogger(__name__) LOG = log.getLogger(__name__)
@ -62,28 +51,29 @@ class ScenarioValidator(object):
def get_entity_id(self, entity): def get_entity_id(self, entity):
return entity[TemplateFields.TEMPLATE_ID] return entity[TemplateFields.TEMPLATE_ID]
def __init__(self, definitions_index): @classmethod
self.definitions_index = definitions_index def validate(cls, template_schema, def_index, scenarios):
def validate(self, scenarios):
for scenario in scenarios: for scenario in scenarios:
scenario_values = scenario[TemplateFields.SCENARIO] scenario_values = scenario[TemplateFields.SCENARIO]
condition = scenario_values[TemplateFields.CONDITION] 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: if not result.is_valid_config:
return result return result
actions = scenario_values[TemplateFields.ACTIONS] 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: if not result.is_valid_config:
return result return result
return get_content_correct_result() return get_content_correct_result()
def _validate_scenario_condition(self, condition): # noinspection PyBroadException
@classmethod
def _validate_scenario_condition(cls, def_index, condition):
try: try:
dnf_result = convert_to_dnf_format(condition) dnf_result = convert_to_dnf_format(condition)
except Exception: except Exception:
@ -91,7 +81,8 @@ class ScenarioValidator(object):
return get_content_fault_result(85) return get_content_fault_result(85)
# not condition validation # 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: if not not_condition_result.is_valid_config:
return not_condition_result return not_condition_result
@ -107,28 +98,29 @@ class ScenarioValidator(object):
continue continue
result = \ result = \
validate_template_id(self.definitions_index, condition_var) validate_template_id(def_index, condition_var)
if not result.is_valid_config: if not result.is_valid_config:
return result return result
# condition structure validation # condition structure validation
condition_structure_result = \ condition_structure_result = cls._validate_condition_structure(
self._validate_condition_structure(parse_condition(condition)) def_index, parse_condition(condition))
if not condition_structure_result.is_valid_config: if not condition_structure_result.is_valid_config:
return condition_structure_result return condition_structure_result
return get_content_correct_result() return get_content_correct_result()
def _validate_condition_structure(self, condition_dnf): @classmethod
def _validate_condition_structure(cls, def_index, condition_dnf):
result = \ result = \
self._validate_condition_includes_positive_clause(condition_dnf) cls._validate_condition_includes_positive_clause(condition_dnf)
if not result.is_valid_config: if not result.is_valid_config:
return result return result
common_targets = \ common_targets = \
get_condition_common_targets(condition_dnf, get_condition_common_targets(condition_dnf,
self.definitions_index, def_index,
self.TemplateSymbolResolver()) cls.TemplateSymbolResolver())
return get_content_correct_result() if common_targets \ return get_content_correct_result() if common_targets \
else get_content_fault_result(135) else get_content_fault_result(135)
@ -139,65 +131,63 @@ class ScenarioValidator(object):
is_condition_include_positive_clause(condition) \ is_condition_include_positive_clause(condition) \
else get_content_fault_result(134) 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 validation
Not operator can appear only on edges. Not operator can appear only on edges.
:param dnf_result: :param dnf_result:
:param definitions_index: :param def_index:
:return: :return:
""" """
if isinstance(dnf_result, Not): if isinstance(dnf_result, Not):
for arg in dnf_result.args: for arg in dnf_result.args:
if isinstance(arg, Symbol): if isinstance(arg, Symbol):
definition = self.definitions_index.get(str(arg), None) definition = def_index.get(str(arg), None)
if not (definition and if not (definition and
definition.get(EProps.RELATIONSHIP_TYPE)): definition.get(EProps.RELATIONSHIP_TYPE)):
msg = status_msgs[86] + ' template id: %s' % arg msg = status_msgs[86] + ' template id: %s' % arg
LOG.error('%s status code: %s' % (msg, 86)) LOG.error('%s status code: %s' % (msg, 86))
return get_content_fault_result(86, msg) return get_content_fault_result(86, msg)
else: else:
res = self._validate_not_condition(arg) res = cls._validate_not_condition(def_index, arg)
if not res.is_valid_config: if not res.is_valid_config:
return res return res
return get_content_correct_result() return get_content_correct_result()
for arg in dnf_result.args: for arg in dnf_result.args:
if not isinstance(arg, Symbol): 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: if not res.is_valid_config:
return res return res
return get_content_correct_result() 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: for action in actions:
result = \ 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: if not result.is_valid_config:
return result return result
return get_content_correct_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_type = action[TemplateFields.ACTION_TYPE]
action_validator = template_schema.validator(action_type)
action_validators = { if not action_validator:
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:
LOG.error('%s status code: %s' % (status_msgs[120], 120)) LOG.error('%s status code: %s' % (status_msgs[120], 120))
return get_content_fault_result(120) return get_content_fault_result(120)
return action_validators[action_type].validate(action, return action_validator.validate(action, def_index)
self.definitions_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 EdgeLabel
from vitrage.common.constants import EntityCategory from vitrage.common.constants import EntityCategory
from vitrage.evaluator.actions.base import action_types 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 = { status_msgs = {
@ -43,7 +44,7 @@ status_msgs = {
60: 'metadata section must contain id field.', 60: 'metadata section must contain id field.',
62: 'metadata is a mandatory section.', 62: 'metadata is a mandatory section.',
63: 'Unsupported version. Version must be one of: {versions}' 63: 'Unsupported version. Version must be one of: {versions}'
.format(versions=SUPPORTED_VERSIONS), .format(versions=TemplateSchemaFactory.supported_versions()),
# scenarios section status messages 80-99 # scenarios section status messages 80-99
80: 'scenarios is a mandatory section.', 80: 'scenarios is a mandatory section.',
@ -84,6 +85,8 @@ status_msgs = {
'block', 'block',
134: 'condition can not contain only \'not\' clauses', 134: 'condition can not contain only \'not\' clauses',
135: 'condition must contain a common entity for all \'or\' 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 # def_templates status messages 140-159
140: 'At least one template must be included', 140: 'At least one template must be included',

View File

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

View File

@ -1,25 +1,26 @@
metadata: metadata:
name: execute_mistral version: 1
name: v1_execute_mistral
description: execute mistral description: execute mistral
definitions: definitions:
entities: entities:
- entity: - entity:
category: ALARM category: ALARM
name: compute.host.down name: notifiers.mistral.trigger.alarm.1
template_id: host_down_alarm template_id: alarm
- entity: - entity:
category: RESOURCE category: RESOURCE
type: nova.host type: nova.host
template_id: host template_id: host
relationships: relationships:
- relationship: - relationship:
source: host_down_alarm source: alarm
relationship_type: on relationship_type: on
target: host target: host
template_id : host_down_alarm_on_host template_id : alarm_on_host
scenarios: scenarios:
- scenario: - scenario:
condition: host_down_alarm_on_host condition: alarm_on_host
actions: actions:
- action: - action:
action_type: execute_mistral 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 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import abc
from vitrage.evaluator.actions.base import ActionType from vitrage.evaluator.actions.base import ActionType
from vitrage.evaluator.actions.recipes.execute_mistral import WORKFLOW from vitrage.evaluator.actions.recipes.execute_mistral import WORKFLOW
from vitrage.evaluator.template_fields import TemplateFields 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 \ from vitrage.tests.unit.evaluator.template_validation.content.base import \
ActionValidatorTest ActionValidatorTest
from vitrage.tests.unit.evaluator.template_validation.content.base import \ from vitrage.tests.unit.evaluator.template_validation.content.base import \
DEFINITIONS_INDEX_MOCK 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._validate_action(
self._create_execute_mistral_action('wf_1', 'host_2', 'down'), 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 # Test setup
idx = DEFINITIONS_INDEX_MOCK.copy() idx = DEFINITIONS_INDEX_MOCK.copy()
@ -40,51 +42,63 @@ class ExecuteMistralValidatorTest(ActionValidatorTest):
action[TemplateFields.PROPERTIES].pop(WORKFLOW) action[TemplateFields.PROPERTIES].pop(WORKFLOW)
# Test action # Test action
result = ExecuteMistralValidator.validate(action, idx) result = validator.validate(action, idx)
# Test assertions # Test assertions
self._assert_fault_result(result, 133) 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 # Test setup
idx = DEFINITIONS_INDEX_MOCK.copy() idx = DEFINITIONS_INDEX_MOCK.copy()
action = self._create_execute_mistral_action('', 'host_2', 'down') action = self._create_execute_mistral_action('', 'host_2', 'down')
# Test action # Test action
result = ExecuteMistralValidator.validate(action, idx) result = validator.validate(action, idx)
# Test assertions # Test assertions
self._assert_fault_result(result, 133) 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 # Test setup
idx = DEFINITIONS_INDEX_MOCK.copy() idx = DEFINITIONS_INDEX_MOCK.copy()
action = self._create_execute_mistral_action(None, 'host_2', 'down') action = self._create_execute_mistral_action(None, 'host_2', 'down')
# Test action # Test action
result = ExecuteMistralValidator.validate(action, idx) result = validator.validate(action, idx)
# Test assertions # Test assertions
self._assert_fault_result(result, 133) 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 # Test setup - having only the 'workflow' param is a legal config
idx = DEFINITIONS_INDEX_MOCK.copy() idx = DEFINITIONS_INDEX_MOCK.copy()
action = self._create_execute_mistral_action('wf_1', 'host_2', 'down') action = self._create_no_input_mistral_action('wf_1')
action[TemplateFields.PROPERTIES].pop('host')
action[TemplateFields.PROPERTIES].pop('host_state')
# Test action # Test action
result = ExecuteMistralValidator.validate(action, idx) result = validator.validate(action, idx)
# Test assertions # Test assertions
self._assert_correct_result(result) self._assert_correct_result(result)
@staticmethod @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 = { properties = {
WORKFLOW: workflow, WORKFLOW: workflow,
@ -97,3 +111,21 @@ class ExecuteMistralValidatorTest(ActionValidatorTest):
} }
return action 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.actions.base import ActionType
from vitrage.evaluator.template_fields import TemplateFields 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 add_causal_relationship_validator import AddCausalRelationshipValidator
from vitrage.tests.unit.evaluator.template_validation.content.base import \ from vitrage.tests.unit.evaluator.template_validation.content.base import \
ActionValidatorTest 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.actions.base import ActionType
from vitrage.evaluator.template_fields import TemplateFields 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 MarkDownValidator
from vitrage.tests.unit.evaluator.template_validation.content.base import \ from vitrage.tests.unit.evaluator.template_validation.content.base import \
ActionValidatorTest ActionValidatorTest

View File

@ -14,7 +14,7 @@
from vitrage.evaluator.actions.base import ActionType from vitrage.evaluator.actions.base import ActionType
from vitrage.evaluator.template_fields import TemplateFields 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 import RaiseAlarmValidator
from vitrage.tests.unit.evaluator.template_validation.content.base import \ from vitrage.tests.unit.evaluator.template_validation.content.base import \
ActionValidatorTest ActionValidatorTest

View File

@ -16,7 +16,7 @@ from vitrage.entity_graph.mappings.operational_resource_state import \
OperationalResourceState OperationalResourceState
from vitrage.evaluator.actions.base import ActionType from vitrage.evaluator.actions.base import ActionType
from vitrage.evaluator.template_fields import TemplateFields 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 import SetStateValidator
from vitrage.tests.unit.evaluator.template_validation.content.base import \ from vitrage.tests.unit.evaluator.template_validation.content.base import \
ActionValidatorTest 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.condition import SymbolResolver
from vitrage.evaluator.template_data import EdgeDescription from vitrage.evaluator.template_data import EdgeDescription
from vitrage.evaluator.template_loading.template_loader import TemplateLoader 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 import get_condition_common_targets
from vitrage.tests import base from vitrage.tests import base
from vitrage.tests.mocks import utils 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 = 'basic.yaml'
BASIC_TEMPLATE_WITH_INCLUDE = 'basic_with_include.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() +\ DEF_TEMPLATE_TESTS_DIR = utils.get_resources_dir() +\
'/templates/def_template_tests' '/templates/def_template_tests'
@ -230,6 +232,39 @@ class BasicTemplateTest(base.BaseTest):
expected_relationships, expected_relationships,
expected_scenario) 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, def _validate_strict_equal(self,
template_data, template_data,
expected_entities, expected_entities,

View File

@ -14,14 +14,13 @@
from datetime import datetime from datetime import datetime
from oslo_log import log as logging 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__) LOG = logging.getLogger(__name__)
DOWN = 'down'
UP = 'up'
class BaseTestEvents(BaseVitrageTempest): class BaseTestEvents(TestActionsBase):
"""Test class for Vitrage event API""" """Test class for Vitrage event API"""
# noinspection PyPep8Naming # 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 EventProperties as EventProps
from vitrage.common.constants import VertexProperties as VProps 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 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 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_HOST_DATASOURCE
from vitrage.datasources import NOVA_INSTANCE_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 import general_utils as g_utils
from vitrage_tempest_tests.tests.common.tempest_clients import TempestClients from vitrage_tempest_tests.tests.common.tempest_clients import TempestClients
DOWN = 'down'
UP = 'up'
def generate_fake_host_alarm(hostname, event_type, enabled=True): def generate_fake_host_alarm(hostname, event_type, enabled=True):
details = { details = {

View File

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