Merge "Support per-version template loading + change execute_mistral structure"
This commit is contained in:
commit
4ff1908d1b
@ -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 |
|
||||
|
@ -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
|
||||
|
@ -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.
|
@ -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()
|
||||
|
@ -18,6 +18,7 @@ from vitrage.evaluator.actions.recipes.base import ActionStepWrapper
|
||||
|
||||
|
||||
MISTRAL = 'mistral'
|
||||
INPUT = 'input'
|
||||
WORKFLOW = 'workflow'
|
||||
|
||||
|
||||
|
@ -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):
|
||||
|
@ -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]
|
||||
|
@ -15,10 +15,6 @@
|
||||
from vitrage.common.constants import TemplateTopologyFields
|
||||
|
||||
|
||||
DEFAULT_VERSION = '1'
|
||||
SUPPORTED_VERSIONS = ['1']
|
||||
|
||||
|
||||
class TemplateFields(TemplateTopologyFields):
|
||||
|
||||
SCENARIOS = 'scenarios'
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
15
vitrage/evaluator/template_loading/v1/__init__.py
Normal file
15
vitrage/evaluator/template_loading/v1/__init__.py
Normal 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'
|
37
vitrage/evaluator/template_loading/v1/action_loader.py
Normal file
37
vitrage/evaluator/template_loading/v1/action_loader.py
Normal 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, {})
|
@ -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}
|
42
vitrage/evaluator/template_schema_factory.py
Normal file
42
vitrage/evaluator/template_schema_factory.py
Normal 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)
|
86
vitrage/evaluator/template_schemas.py
Normal file
86
vitrage/evaluator/template_schemas.py
Normal 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())
|
@ -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):
|
||||
|
||||
|
@ -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)
|
||||
|
15
vitrage/evaluator/template_validation/content/v1/__init__.py
Normal file
15
vitrage/evaluator/template_validation/content/v1/__init__.py
Normal 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'
|
@ -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]
|
@ -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)
|
15
vitrage/evaluator/template_validation/content/v2/__init__.py
Normal file
15
vitrage/evaluator/template_validation/content/v2/__init__.py
Normal 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'
|
@ -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()
|
@ -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',
|
||||
|
@ -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:
|
||||
|
@ -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
|
@ -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
|
@ -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
|
@ -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'
|
@ -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
|
@ -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)
|
@ -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
|
@ -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
|
@ -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
|
@ -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'
|
@ -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)
|
@ -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
|
||||
|
48
vitrage/tests/unit/evaluator/test_scenario_evaluator.py
Normal file
48
vitrage/tests/unit/evaluator/test_scenario_evaluator.py
Normal 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))
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
@ -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 = {
|
||||
|
@ -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:
|
||||
|
@ -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
|
@ -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
|
Loading…
Reference in New Issue
Block a user