Merge "Support template functions"
This commit is contained in:
commit
c350c92482
@ -128,11 +128,17 @@ as high availability condition.
|
|||||||
Scenarios
|
Scenarios
|
||||||
=========
|
=========
|
||||||
|
|
||||||
``Scenario`` are defined as a ``namedtuple``
|
``Scenario`` class holds the following properties:
|
||||||
|
|
||||||
.. code-block:: python
|
* id
|
||||||
|
* version
|
||||||
|
* condition
|
||||||
|
* actions
|
||||||
|
* subgraphs
|
||||||
|
* entities
|
||||||
|
* relationships
|
||||||
|
* enabled
|
||||||
|
|
||||||
Scenario = namedtuple('Scenario', ['id', 'condition', 'actions', 'subgraphs'])
|
|
||||||
|
|
||||||
id
|
id
|
||||||
--
|
--
|
||||||
|
@ -406,6 +406,40 @@ regular expression:
|
|||||||
rawtext.regex: Interface ([_a-zA-Z0-9'-]+) down on {HOST.NAME}
|
rawtext.regex: Interface ([_a-zA-Z0-9'-]+) down on {HOST.NAME}
|
||||||
template_id: zabbix_alarm
|
template_id: zabbix_alarm
|
||||||
|
|
||||||
|
Using functions in an action definition
|
||||||
|
---------------------------------------
|
||||||
|
Some properties of an action can be defined using functions. On version 2, one
|
||||||
|
function is supported: get_attr, and it is supported only for execute_mistral
|
||||||
|
action.
|
||||||
|
|
||||||
|
*Note:* Functions are supported from version 2 and on.
|
||||||
|
|
||||||
|
get_attr
|
||||||
|
^^^^^^^^
|
||||||
|
This function retrieves the value of an attribute of an entity that is defined
|
||||||
|
in the template.
|
||||||
|
|
||||||
|
Usage
|
||||||
|
~~~~~
|
||||||
|
|
||||||
|
get_attr(template_id, attr_name)
|
||||||
|
|
||||||
|
Example
|
||||||
|
~~~~~~~
|
||||||
|
::
|
||||||
|
|
||||||
|
scenario:
|
||||||
|
condition: alarm_on_host_1
|
||||||
|
actions:
|
||||||
|
action:
|
||||||
|
action_type: execute_mistral
|
||||||
|
properties:
|
||||||
|
workflow: demo_workflow
|
||||||
|
input:
|
||||||
|
host_name: get_attr(host_1,name)
|
||||||
|
retries: 5
|
||||||
|
|
||||||
|
|
||||||
Supported Actions
|
Supported Actions
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
|
@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- Support functions in Vitrage templates version 2. The first supported
|
||||||
|
function is ``get_attr`` which allows retrieving attributes from the
|
||||||
|
matched entity in the graph. As the first stage this function is supported
|
||||||
|
only for ``execute_mistral`` action.
|
@ -12,5 +12,15 @@
|
|||||||
# 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 collections import namedtuple
|
from collections import namedtuple
|
||||||
|
import re
|
||||||
|
|
||||||
Template = namedtuple('Template', ['uuid', 'data', 'date', 'result'])
|
Template = namedtuple('Template', ['uuid', 'data', 'date', 'result'])
|
||||||
|
|
||||||
|
|
||||||
|
def is_function(str):
|
||||||
|
"""Check if the string represents a function
|
||||||
|
|
||||||
|
A function has the format: func_name(params)
|
||||||
|
Search for a regex with open and close parenthesis
|
||||||
|
"""
|
||||||
|
return re.match('.*\(.*\)', str)
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
import copy
|
import copy
|
||||||
|
import re
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
@ -28,8 +29,10 @@ from vitrage.evaluator.actions.action_executor import ActionExecutor
|
|||||||
from vitrage.evaluator.actions.base import ActionMode
|
from vitrage.evaluator.actions.base import ActionMode
|
||||||
from vitrage.evaluator.actions.base import ActionType
|
from vitrage.evaluator.actions.base import ActionType
|
||||||
import vitrage.evaluator.actions.priority_tools as pt
|
import vitrage.evaluator.actions.priority_tools as pt
|
||||||
|
from vitrage.evaluator.base import is_function
|
||||||
from vitrage.evaluator.template_data import ActionSpecs
|
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_schema_factory import TemplateSchemaFactory
|
||||||
from vitrage.graph.algo_driver.algorithm import Mapping
|
from vitrage.graph.algo_driver.algorithm import Mapping
|
||||||
from vitrage.graph.algo_driver.sub_graph_matching import \
|
from vitrage.graph.algo_driver.sub_graph_matching import \
|
||||||
NEG_CONDITION
|
NEG_CONDITION
|
||||||
@ -184,7 +187,8 @@ class ScenarioEvaluator(object):
|
|||||||
scenario_element,
|
scenario_element,
|
||||||
action.targets[TARGET])
|
action.targets[TARGET])
|
||||||
|
|
||||||
actions.extend(self._get_actions_from_matches(matches,
|
actions.extend(self._get_actions_from_matches(scenario.version,
|
||||||
|
matches,
|
||||||
mode,
|
mode,
|
||||||
action))
|
action))
|
||||||
|
|
||||||
@ -207,6 +211,7 @@ class ScenarioEvaluator(object):
|
|||||||
scenario_element)
|
scenario_element)
|
||||||
|
|
||||||
def _get_actions_from_matches(self,
|
def _get_actions_from_matches(self,
|
||||||
|
scenario_version,
|
||||||
combined_matches,
|
combined_matches,
|
||||||
mode,
|
mode,
|
||||||
action_spec):
|
action_spec):
|
||||||
@ -217,15 +222,67 @@ class ScenarioEvaluator(object):
|
|||||||
new_mode = ActionMode.UNDO \
|
new_mode = ActionMode.UNDO \
|
||||||
if mode == ActionMode.DO else ActionMode.DO
|
if mode == ActionMode.DO else ActionMode.DO
|
||||||
|
|
||||||
|
template_schema = \
|
||||||
|
TemplateSchemaFactory().template_schema(scenario_version)
|
||||||
|
|
||||||
for match in matches:
|
for match in matches:
|
||||||
match_action_spec = self._get_action_spec(action_spec, match)
|
match_action_spec = self._get_action_spec(action_spec, match)
|
||||||
items_ids = [match[1].vertex_id for match in match.items()]
|
items_ids = \
|
||||||
|
[match_item[1].vertex_id for match_item in match.items()]
|
||||||
match_hash = hash(tuple(sorted(items_ids)))
|
match_hash = hash(tuple(sorted(items_ids)))
|
||||||
|
self._evaluate_property_functions(template_schema, match,
|
||||||
|
match_action_spec.properties)
|
||||||
|
|
||||||
actions.append(ActionInfo(match_action_spec, new_mode,
|
actions.append(ActionInfo(match_action_spec, new_mode,
|
||||||
match_action_spec.id, match_hash))
|
match_action_spec.id, match_hash))
|
||||||
|
|
||||||
return actions
|
return actions
|
||||||
|
|
||||||
|
def _evaluate_property_functions(self, template_schema, match,
|
||||||
|
action_props):
|
||||||
|
"""Evaluate the action properties, in case they contain functions
|
||||||
|
|
||||||
|
In template version 2 we introduced functions, and specifically the
|
||||||
|
get_attr function. This method evaluate its value and updates the
|
||||||
|
action properties, before the action is being executed.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
- action:
|
||||||
|
action_type: execute_mistral
|
||||||
|
properties:
|
||||||
|
workflow: evacuate_vm
|
||||||
|
input:
|
||||||
|
vm_name: get_attr(instance1,name)
|
||||||
|
force: false
|
||||||
|
|
||||||
|
In this example, the method will iterate over 'properties', and then
|
||||||
|
recursively over 'input', and for 'vm_name' it will replace the
|
||||||
|
call for get_attr with the actual name of the VM. The input for the
|
||||||
|
Mistral workflow will then be:
|
||||||
|
vm_name: vm_1
|
||||||
|
force: false
|
||||||
|
|
||||||
|
"""
|
||||||
|
for key, value in action_props.items():
|
||||||
|
if isinstance(value, dict):
|
||||||
|
# Recursive call for a dictionary
|
||||||
|
self._evaluate_property_functions(template_schema,
|
||||||
|
match, value)
|
||||||
|
|
||||||
|
elif value is not None and is_function(value):
|
||||||
|
# The value is a function
|
||||||
|
func_and_args = re.split('[(),]', value)
|
||||||
|
func_name = func_and_args.pop(0)
|
||||||
|
args = [arg.strip() for arg in func_and_args if len(arg) > 0]
|
||||||
|
|
||||||
|
# Get the function, execute it and update the property value
|
||||||
|
func = template_schema.functions.get(func_name)
|
||||||
|
action_props[key] = func(match, *args)
|
||||||
|
|
||||||
|
LOG.debug('Changed property %s value from %s to %s', key,
|
||||||
|
value, action_props[key])
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _get_action_spec(action_spec, match):
|
def _get_action_spec(action_spec, match):
|
||||||
targets = action_spec.targets
|
targets = action_spec.targets
|
||||||
|
@ -145,7 +145,7 @@ class ScenarioRepository(object):
|
|||||||
|
|
||||||
if result.is_valid_config:
|
if result.is_valid_config:
|
||||||
def_validator = \
|
def_validator = \
|
||||||
template_schema.validator(TemplateFields.DEFINITIONS)
|
template_schema.validators.get(TemplateFields.DEFINITIONS)
|
||||||
result = \
|
result = \
|
||||||
def_validator.def_template_content_validation(def_template)
|
def_validator.def_template_content_validation(def_template)
|
||||||
|
|
||||||
|
@ -24,9 +24,10 @@ RELATIONSHIP = 'relationship'
|
|||||||
|
|
||||||
|
|
||||||
class Scenario(object):
|
class Scenario(object):
|
||||||
def __init__(self, id, condition, actions, subgraphs, entities,
|
def __init__(self, id, version, condition, actions, subgraphs, entities,
|
||||||
relationships, enabled=False):
|
relationships, enabled=False):
|
||||||
self.id = id
|
self.id = id
|
||||||
|
self.version = version
|
||||||
self.condition = condition
|
self.condition = condition
|
||||||
self.actions = actions
|
self.actions = actions
|
||||||
self.subgraphs = subgraphs
|
self.subgraphs = subgraphs
|
||||||
@ -46,8 +47,9 @@ class Scenario(object):
|
|||||||
# noinspection PyAttributeOutsideInit
|
# noinspection PyAttributeOutsideInit
|
||||||
class TemplateData(object):
|
class TemplateData(object):
|
||||||
|
|
||||||
def __init__(self, name, entities, relationships, scenarios):
|
def __init__(self, name, version, entities, relationships, scenarios):
|
||||||
self.name = name
|
self.name = name
|
||||||
|
self.version = version
|
||||||
self.entities = entities
|
self.entities = entities
|
||||||
self.relationships = relationships
|
self.relationships = relationships
|
||||||
self.scenarios = scenarios
|
self.scenarios = scenarios
|
||||||
@ -60,6 +62,14 @@ class TemplateData(object):
|
|||||||
def name(self, template_name):
|
def name(self, template_name):
|
||||||
self._name = template_name
|
self._name = template_name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def version(self):
|
||||||
|
return self._version
|
||||||
|
|
||||||
|
@version.setter
|
||||||
|
def version(self, version):
|
||||||
|
self._version = version
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def entities(self):
|
def entities(self):
|
||||||
return self._entities
|
return self._entities
|
||||||
|
15
vitrage/evaluator/template_functions/__init__.py
Normal file
15
vitrage/evaluator/template_functions/__init__.py
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
# Copyright 2018 - 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'
|
15
vitrage/evaluator/template_functions/v2/__init__.py
Normal file
15
vitrage/evaluator/template_functions/v2/__init__.py
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
# Copyright 2018 - 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'
|
78
vitrage/evaluator/template_functions/v2/functions.py
Normal file
78
vitrage/evaluator/template_functions/v2/functions.py
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
# Copyright 2018 - 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__)
|
||||||
|
|
||||||
|
# Function names
|
||||||
|
GET_ATTR = 'get_attr'
|
||||||
|
|
||||||
|
|
||||||
|
def get_attr(match, *args):
|
||||||
|
"""Get the runtime value of an attribute of a template entity
|
||||||
|
|
||||||
|
Usage: get_attr(template_id, attr_name)
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
scenario:
|
||||||
|
condition: alarm_on_host_1
|
||||||
|
actions:
|
||||||
|
action:
|
||||||
|
action_type: execute_mistral
|
||||||
|
properties:
|
||||||
|
workflow: demo_workflow
|
||||||
|
input:
|
||||||
|
host_name: get_attr(host_1,name)
|
||||||
|
retries: 5
|
||||||
|
|
||||||
|
get_attr(host_1, name) will return the name of the host that was matched
|
||||||
|
by the evaluator to host_1
|
||||||
|
|
||||||
|
:param match: The evaluator's match structure. A dictionary of
|
||||||
|
{template_id, Vertex}
|
||||||
|
:param args: The arguments of the function. For get_attr, the expected
|
||||||
|
arguments are:
|
||||||
|
- template_id: The internal template id of the entity
|
||||||
|
- attr_name: The name of the wanted attribute
|
||||||
|
:return: The wanted attribute if found, or None
|
||||||
|
"""
|
||||||
|
|
||||||
|
if len(args) != 2:
|
||||||
|
LOG.warning('Called function get_attr with wrong number of '
|
||||||
|
'arguments: %s. Usage: get_attr(vertex, attr_name)',
|
||||||
|
args)
|
||||||
|
return
|
||||||
|
|
||||||
|
template_id = args[0]
|
||||||
|
attr_name = args[1]
|
||||||
|
vertex = match.get(template_id)
|
||||||
|
|
||||||
|
if not vertex:
|
||||||
|
LOG.warning('Called function get_attr with unknown template_id %s',
|
||||||
|
args[0])
|
||||||
|
return
|
||||||
|
|
||||||
|
entity_props = vertex.properties
|
||||||
|
attr = entity_props.get(attr_name) if entity_props else None
|
||||||
|
|
||||||
|
if attr is None:
|
||||||
|
LOG.warning('Attribute %s not found for vertex %s',
|
||||||
|
attr_name, str(vertex))
|
||||||
|
|
||||||
|
LOG.debug('Function get_attr called with template_id %s and attr_name %s.'
|
||||||
|
'Matched vertex properties: %s. Returned attribute value: %s',
|
||||||
|
template_id, attr_name, str(entity_props), attr)
|
||||||
|
|
||||||
|
return attr
|
@ -57,7 +57,8 @@ class ScenarioLoader(object):
|
|||||||
self._extract_var_and_update_index)
|
self._extract_var_and_update_index)
|
||||||
|
|
||||||
scenarios.append(
|
scenarios.append(
|
||||||
Scenario(scenario_id, condition, actions, subgraphs,
|
Scenario(scenario_id, self._template_schema.version(),
|
||||||
|
condition, actions, subgraphs,
|
||||||
self.entities, self.relationships))
|
self.entities, self.relationships))
|
||||||
|
|
||||||
return scenarios
|
return scenarios
|
||||||
@ -87,6 +88,7 @@ class ScenarioLoader(object):
|
|||||||
scenario.condition, extract_var)
|
scenario.condition, extract_var)
|
||||||
|
|
||||||
return Scenario(id=scenario.id + '_equivalence',
|
return Scenario(id=scenario.id + '_equivalence',
|
||||||
|
version=scenario.version,
|
||||||
condition=scenario.condition,
|
condition=scenario.condition,
|
||||||
actions=scenario.actions,
|
actions=scenario.actions,
|
||||||
subgraphs=subgraphs,
|
subgraphs=subgraphs,
|
||||||
@ -99,7 +101,7 @@ 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_type = action_def[TFields.ACTION][TFields.ACTION_TYPE]
|
action_type = action_def[TFields.ACTION][TFields.ACTION_TYPE]
|
||||||
action_loader = self._template_schema.loader(action_type)
|
action_loader = self._template_schema.loaders.get(action_type)
|
||||||
|
|
||||||
if action_loader:
|
if action_loader:
|
||||||
actions.append(action_loader.load(action_id, self.valid_target,
|
actions.append(action_loader.load(action_id, self.valid_target,
|
||||||
|
@ -86,7 +86,8 @@ class TemplateLoader(object):
|
|||||||
self.relationships).\
|
self.relationships).\
|
||||||
build_scenarios(template_def[TFields.SCENARIOS])
|
build_scenarios(template_def[TFields.SCENARIOS])
|
||||||
|
|
||||||
return TemplateData(name, self.entities, self.relationships, scenarios)
|
return TemplateData(name, template_schema.version(), self.entities,
|
||||||
|
self.relationships, scenarios)
|
||||||
|
|
||||||
def _build_entities(self, entities_defs):
|
def _build_entities(self, entities_defs):
|
||||||
entities = {}
|
entities = {}
|
||||||
|
@ -15,6 +15,8 @@ from oslo_log import log
|
|||||||
|
|
||||||
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_functions.v2.functions import get_attr
|
||||||
|
from vitrage.evaluator.template_functions.v2.functions import GET_ATTR
|
||||||
from vitrage.evaluator.template_loading.v1.action_loader import ActionLoader
|
from vitrage.evaluator.template_loading.v1.action_loader import ActionLoader
|
||||||
from vitrage.evaluator.template_loading.v1.execute_mistral_loader import \
|
from vitrage.evaluator.template_loading.v1.execute_mistral_loader import \
|
||||||
ExecuteMistralLoader
|
ExecuteMistralLoader
|
||||||
@ -43,7 +45,7 @@ LOG = log.getLogger(__name__)
|
|||||||
|
|
||||||
class TemplateSchema1(object):
|
class TemplateSchema1(object):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._validators = {
|
self.validators = {
|
||||||
TemplateFields.DEFINITIONS: DefinitionsValidator,
|
TemplateFields.DEFINITIONS: DefinitionsValidator,
|
||||||
TemplateFields.SCENARIOS: ScenarioValidator,
|
TemplateFields.SCENARIOS: ScenarioValidator,
|
||||||
ActionType.ADD_CAUSAL_RELATIONSHIP: AddCausalRelationshipValidator,
|
ActionType.ADD_CAUSAL_RELATIONSHIP: AddCausalRelationshipValidator,
|
||||||
@ -53,7 +55,7 @@ class TemplateSchema1(object):
|
|||||||
ActionType.SET_STATE: SetStateValidator,
|
ActionType.SET_STATE: SetStateValidator,
|
||||||
}
|
}
|
||||||
|
|
||||||
self._loaders = {
|
self.loaders = {
|
||||||
ActionType.ADD_CAUSAL_RELATIONSHIP: ActionLoader(),
|
ActionType.ADD_CAUSAL_RELATIONSHIP: ActionLoader(),
|
||||||
ActionType.EXECUTE_MISTRAL: ExecuteMistralLoader(),
|
ActionType.EXECUTE_MISTRAL: ExecuteMistralLoader(),
|
||||||
ActionType.MARK_DOWN: ActionLoader(),
|
ActionType.MARK_DOWN: ActionLoader(),
|
||||||
@ -61,24 +63,23 @@ class TemplateSchema1(object):
|
|||||||
ActionType.SET_STATE: ActionLoader(),
|
ActionType.SET_STATE: ActionLoader(),
|
||||||
}
|
}
|
||||||
|
|
||||||
def validator(self, validator_type):
|
self.functions = {}
|
||||||
LOG.debug('Get validator. validator_type: %s. validators: %s',
|
|
||||||
validator_type, self._validators)
|
|
||||||
return self._validators.get(validator_type)
|
|
||||||
|
|
||||||
def loader(self, loader_type):
|
def version(self):
|
||||||
LOG.debug('Get loader. loader_type: %s. loaders: %s',
|
return '1'
|
||||||
loader_type, self._loaders)
|
|
||||||
return self._loaders.get(loader_type)
|
|
||||||
|
|
||||||
|
|
||||||
class TemplateSchema2(TemplateSchema1):
|
class TemplateSchema2(TemplateSchema1):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(TemplateSchema2, self).__init__()
|
super(TemplateSchema2, self).__init__()
|
||||||
self._validators[ActionType.EXECUTE_MISTRAL] = \
|
self.validators[ActionType.EXECUTE_MISTRAL] = \
|
||||||
V2ExecuteMistralValidator()
|
V2ExecuteMistralValidator()
|
||||||
self._loaders[ActionType.EXECUTE_MISTRAL] = ActionLoader()
|
self.loaders[ActionType.EXECUTE_MISTRAL] = ActionLoader()
|
||||||
|
self.functions[GET_ATTR] = get_attr
|
||||||
|
|
||||||
|
def version(self):
|
||||||
|
return '2'
|
||||||
|
|
||||||
|
|
||||||
def init_template_schemas():
|
def init_template_schemas():
|
||||||
|
@ -22,6 +22,10 @@ def get_correct_result(description):
|
|||||||
return Result(description, True, 0, status_msgs[0])
|
return Result(description, True, 0, status_msgs[0])
|
||||||
|
|
||||||
|
|
||||||
|
def get_warning_result(description, code):
|
||||||
|
return Result(description, True, code, status_msgs[code])
|
||||||
|
|
||||||
|
|
||||||
def get_fault_result(description, code, msg=None):
|
def get_fault_result(description, code, msg=None):
|
||||||
if msg:
|
if msg:
|
||||||
return Result(description, False, code, msg)
|
return Result(description, False, code, msg)
|
||||||
|
@ -20,6 +20,7 @@ from vitrage.evaluator.template_fields import TemplateFields
|
|||||||
from vitrage.evaluator.template_schema_factory import TemplateSchemaFactory
|
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.base import get_warning_result
|
||||||
from vitrage.evaluator.template_validation.status_messages import status_msgs
|
from vitrage.evaluator.template_validation.status_messages import status_msgs
|
||||||
|
|
||||||
|
|
||||||
@ -35,6 +36,10 @@ def get_content_fault_result(code, msg=None):
|
|||||||
return get_fault_result(RESULT_DESCRIPTION, code, msg)
|
return get_fault_result(RESULT_DESCRIPTION, code, msg)
|
||||||
|
|
||||||
|
|
||||||
|
def get_content_warning_result(code):
|
||||||
|
return get_warning_result(RESULT_DESCRIPTION, code)
|
||||||
|
|
||||||
|
|
||||||
def validate_template_id(definitions_index, id_to_check):
|
def validate_template_id(definitions_index, id_to_check):
|
||||||
if id_to_check not in definitions_index:
|
if id_to_check not in definitions_index:
|
||||||
msg = status_msgs[3] + ' template id: %s' % id_to_check
|
msg = status_msgs[3] + ' template id: %s' % id_to_check
|
||||||
|
@ -30,7 +30,8 @@ def content_validation(template, def_templates=None):
|
|||||||
template_definitions = {}
|
template_definitions = {}
|
||||||
|
|
||||||
result, template_schema = get_template_schema(template)
|
result, template_schema = get_template_schema(template)
|
||||||
def_validator = template_schema.validator(TemplateFields.DEFINITIONS) \
|
def_validator = \
|
||||||
|
template_schema.validators.get(TemplateFields.DEFINITIONS) \
|
||||||
if result.is_valid_config and template_schema else None
|
if result.is_valid_config and template_schema else None
|
||||||
|
|
||||||
if result.is_valid_config and not def_validator:
|
if result.is_valid_config and not def_validator:
|
||||||
@ -71,7 +72,7 @@ def content_validation(template, def_templates=None):
|
|||||||
relationship_index)
|
relationship_index)
|
||||||
|
|
||||||
if result.is_valid_config:
|
if result.is_valid_config:
|
||||||
scenario_validator = template_schema.validator(
|
scenario_validator = template_schema.validators.get(
|
||||||
TemplateFields.SCENARIOS)
|
TemplateFields.SCENARIOS)
|
||||||
scenarios = template[TemplateFields.SCENARIOS]
|
scenarios = template[TemplateFields.SCENARIOS]
|
||||||
definitions_index = entities_index.copy()
|
definitions_index = entities_index.copy()
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
|
|
||||||
from vitrage.evaluator.actions.recipes.execute_mistral import WORKFLOW
|
from vitrage.evaluator.actions.recipes.execute_mistral import WORKFLOW
|
||||||
|
from vitrage.evaluator.base import is_function
|
||||||
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 \
|
||||||
ActionValidator
|
ActionValidator
|
||||||
@ -38,4 +39,9 @@ class ExecuteMistralValidator(ActionValidator):
|
|||||||
LOG.error('%s status code: %s' % (status_msgs[133], 133))
|
LOG.error('%s status code: %s' % (status_msgs[133], 133))
|
||||||
return get_content_fault_result(133)
|
return get_content_fault_result(133)
|
||||||
|
|
||||||
|
for key, value in properties.items():
|
||||||
|
if not isinstance(value, dict) and is_function(value):
|
||||||
|
LOG.error('%s status code: %s' % (status_msgs[137], 137))
|
||||||
|
return get_content_fault_result(137)
|
||||||
|
|
||||||
return get_content_correct_result()
|
return get_content_correct_result()
|
||||||
|
@ -184,7 +184,7 @@ class ScenarioValidator(object):
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def _validate_scenario_action(template_schema, def_index, action):
|
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_validator = template_schema.validators.get(action_type)
|
||||||
|
|
||||||
if not action_validator:
|
if not action_validator:
|
||||||
LOG.error('%s status code: %s' % (status_msgs[120], 120))
|
LOG.error('%s status code: %s' % (status_msgs[120], 120))
|
||||||
|
@ -13,9 +13,11 @@
|
|||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
|
import re
|
||||||
|
|
||||||
from vitrage.evaluator.actions.recipes.execute_mistral import INPUT
|
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.evaluator.base import is_function
|
||||||
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 \
|
||||||
ActionValidator
|
ActionValidator
|
||||||
@ -23,6 +25,8 @@ 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 \
|
||||||
|
get_content_warning_result
|
||||||
from vitrage.evaluator.template_validation.status_messages import status_msgs
|
from vitrage.evaluator.template_validation.status_messages import status_msgs
|
||||||
|
|
||||||
|
|
||||||
@ -44,4 +48,11 @@ class ExecuteMistralValidator(ActionValidator):
|
|||||||
LOG.error('%s status code: %s' % (status_msgs[136], 136))
|
LOG.error('%s status code: %s' % (status_msgs[136], 136))
|
||||||
return get_content_fault_result(136)
|
return get_content_fault_result(136)
|
||||||
|
|
||||||
|
inputs = properties[INPUT] if INPUT in properties else {}
|
||||||
|
|
||||||
|
for key, value in inputs.items():
|
||||||
|
if re.findall('[(),]', value) and not is_function(value):
|
||||||
|
LOG.error('%s status code: %s' % (status_msgs[138], 138))
|
||||||
|
return get_content_warning_result(138)
|
||||||
|
|
||||||
return get_content_correct_result()
|
return get_content_correct_result()
|
||||||
|
@ -84,6 +84,9 @@ status_msgs = {
|
|||||||
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 '
|
136: 'Input parameters for the Mistral workflow in execute_mistral action '
|
||||||
'must be placed under an \'input\' block ',
|
'must be placed under an \'input\' block ',
|
||||||
|
137: 'Functions are supported only from version 2',
|
||||||
|
138: 'Warning: only open or close parenthesis exists. Did you try to use '
|
||||||
|
'a function?',
|
||||||
|
|
||||||
# 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',
|
||||||
|
@ -0,0 +1,30 @@
|
|||||||
|
metadata:
|
||||||
|
version: 2
|
||||||
|
name: v2_with_func
|
||||||
|
description: template with a function
|
||||||
|
definitions:
|
||||||
|
entities:
|
||||||
|
- entity:
|
||||||
|
category: ALARM
|
||||||
|
name: notifiers.mistral.trigger.alarm.for.function
|
||||||
|
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: get_attr(alarm,name)
|
15
vitrage/tests/unit/evaluator/template_functions/__init__.py
Normal file
15
vitrage/tests/unit/evaluator/template_functions/__init__.py
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
# Copyright 2018 - 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,59 @@
|
|||||||
|
# Copyright 2018 - 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_functions.v2.functions import get_attr
|
||||||
|
from vitrage.graph.driver import Vertex
|
||||||
|
from vitrage.tests import base
|
||||||
|
|
||||||
|
|
||||||
|
class TemplateFunctionsTest(base.BaseTest):
|
||||||
|
|
||||||
|
def test_get_attr_with_existing_attr(self):
|
||||||
|
entity_id = 'id1234'
|
||||||
|
match = self._create_match('instance', properties={'id': entity_id})
|
||||||
|
|
||||||
|
attr = get_attr(match, 'instance', 'id')
|
||||||
|
self.assertIsNotNone(attr)
|
||||||
|
self.assertEqual(entity_id, attr)
|
||||||
|
|
||||||
|
def test_get_attr_with_non_existing_attr(self):
|
||||||
|
match = self._create_match('instance', properties={'id': 'id1'})
|
||||||
|
attr = get_attr(match, 'instance', 'non_existing_attr')
|
||||||
|
self.assertIsNone(attr)
|
||||||
|
|
||||||
|
def test_get_attr_with_two_attrs(self):
|
||||||
|
properties = {'attr1': 'first_attr', 'attr2': 'second_attr'}
|
||||||
|
match = self._create_match('instance', properties)
|
||||||
|
|
||||||
|
attr = get_attr(match, 'instance', 'attr1')
|
||||||
|
self.assertIsNotNone(attr)
|
||||||
|
self.assertEqual('first_attr', attr)
|
||||||
|
|
||||||
|
attr = get_attr(match, 'instance', 'attr2')
|
||||||
|
self.assertIsNotNone(attr)
|
||||||
|
self.assertEqual('second_attr', attr)
|
||||||
|
|
||||||
|
attr = get_attr(match, 'instance', 'attr3')
|
||||||
|
self.assertIsNone(attr)
|
||||||
|
|
||||||
|
def test_get_attr_with_non_existing_entity(self):
|
||||||
|
match = self._create_match('instance', properties={'attr1': 'attr1'})
|
||||||
|
attr = get_attr(match, 'non_existing_entity', 'attr1')
|
||||||
|
self.assertIsNone(attr)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _create_match(template_id, properties):
|
||||||
|
entity = Vertex(vertex_id='f89fe840-b595-4010-8a09-a444c7642865',
|
||||||
|
properties=properties)
|
||||||
|
return {template_id: entity}
|
@ -47,6 +47,12 @@ class ValidatorTest(base.BaseTest):
|
|||||||
self.assertTrue(result.comment.startswith(status_msgs[status_code]))
|
self.assertTrue(result.comment.startswith(status_msgs[status_code]))
|
||||||
self.assertEqual(result.status_code, status_code)
|
self.assertEqual(result.status_code, status_code)
|
||||||
|
|
||||||
|
def _assert_warning_result(self, result, status_code):
|
||||||
|
|
||||||
|
self.assertTrue(result.is_valid_config)
|
||||||
|
self.assertTrue(result.comment.startswith(status_msgs[status_code]))
|
||||||
|
self.assertEqual(result.status_code, status_code)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _hide_useless_logging_messages():
|
def _hide_useless_logging_messages():
|
||||||
|
|
||||||
|
@ -98,13 +98,16 @@ class BaseExecuteMistralValidatorTest(ActionValidatorTest):
|
|||||||
return action
|
return action
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _create_v1_execute_mistral_action(workflow, host, host_state):
|
def _create_v1_execute_mistral_action(workflow, host, host_state,
|
||||||
|
**kwargs):
|
||||||
|
|
||||||
properties = {
|
properties = {
|
||||||
WORKFLOW: workflow,
|
WORKFLOW: workflow,
|
||||||
'host': host,
|
'host': host,
|
||||||
'host_state': host_state
|
'host_state': host_state
|
||||||
}
|
}
|
||||||
|
properties.update(kwargs)
|
||||||
|
|
||||||
action = {
|
action = {
|
||||||
TemplateFields.ACTION_TYPE: ActionType.EXECUTE_MISTRAL,
|
TemplateFields.ACTION_TYPE: ActionType.EXECUTE_MISTRAL,
|
||||||
TemplateFields.PROPERTIES: properties
|
TemplateFields.PROPERTIES: properties
|
||||||
@ -113,16 +116,19 @@ class BaseExecuteMistralValidatorTest(ActionValidatorTest):
|
|||||||
return action
|
return action
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _create_v2_execute_mistral_action(workflow, host, host_state):
|
def _create_v2_execute_mistral_action(workflow, host, host_state,
|
||||||
|
**kwargs):
|
||||||
|
|
||||||
input_props = {
|
input_props = {
|
||||||
'host': host,
|
'host': host,
|
||||||
'host_state': host_state
|
'host_state': host_state
|
||||||
}
|
}
|
||||||
|
input_props.update(kwargs)
|
||||||
properties = {
|
properties = {
|
||||||
WORKFLOW: workflow,
|
WORKFLOW: workflow,
|
||||||
'input': input_props
|
'input': input_props
|
||||||
}
|
}
|
||||||
|
|
||||||
action = {
|
action = {
|
||||||
TemplateFields.ACTION_TYPE: ActionType.EXECUTE_MISTRAL,
|
TemplateFields.ACTION_TYPE: ActionType.EXECUTE_MISTRAL,
|
||||||
TemplateFields.PROPERTIES: properties
|
TemplateFields.PROPERTIES: properties
|
||||||
|
@ -289,7 +289,7 @@ class TemplateContentValidatorTest(ValidatorTest):
|
|||||||
def test_validate_template_with_version_1(self):
|
def test_validate_template_with_version_1(self):
|
||||||
invalid_version_path = \
|
invalid_version_path = \
|
||||||
VERSION_TEMPLATE_DIR % (utils.get_resources_dir(),
|
VERSION_TEMPLATE_DIR % (utils.get_resources_dir(),
|
||||||
"version1.yaml")
|
"v1/version1.yaml")
|
||||||
template = file_utils.load_yaml_file(invalid_version_path)
|
template = file_utils.load_yaml_file(invalid_version_path)
|
||||||
self._execute_and_assert_with_correct_result(template)
|
self._execute_and_assert_with_correct_result(template)
|
||||||
|
|
||||||
|
@ -60,6 +60,34 @@ class ExecuteMistralValidatorTest(BaseExecuteMistralValidatorTest):
|
|||||||
# Test assertions
|
# Test assertions
|
||||||
self._assert_correct_result(result)
|
self._assert_correct_result(result)
|
||||||
|
|
||||||
|
def test_validate_execute_mistral_action_with_input_dict(self):
|
||||||
|
"""A version1 execute_mistral action can have an 'input' dictionary"""
|
||||||
|
|
||||||
|
# Test setup
|
||||||
|
idx = DEFINITIONS_INDEX_MOCK.copy()
|
||||||
|
action = self._create_execute_mistral_action('wf_1', 'host_2', 'down')
|
||||||
|
input_dict = {'a': '1'}
|
||||||
|
action[TemplateFields.PROPERTIES]['input'] = input_dict
|
||||||
|
|
||||||
|
# Test action
|
||||||
|
result = self.validator.validate(action, idx)
|
||||||
|
|
||||||
|
# Test assertions
|
||||||
|
self._assert_correct_result(result)
|
||||||
|
|
||||||
|
def test_validate_execute_mistral_action_with_func(self):
|
||||||
|
# Test setup
|
||||||
|
idx = DEFINITIONS_INDEX_MOCK.copy()
|
||||||
|
action = \
|
||||||
|
self._create_v1_execute_mistral_action(
|
||||||
|
'wf_1', 'host_2', 'down', func1='get_attr(alarm, name)')
|
||||||
|
|
||||||
|
# Test action
|
||||||
|
result = self.validator.validate(action, idx)
|
||||||
|
|
||||||
|
# Test assertions
|
||||||
|
self._assert_fault_result(result, 137)
|
||||||
|
|
||||||
def _create_execute_mistral_action(self, workflow, host, host_state):
|
def _create_execute_mistral_action(self, workflow, host, host_state):
|
||||||
return self.\
|
return self.\
|
||||||
_create_v1_execute_mistral_action(workflow, host, host_state)
|
_create_v1_execute_mistral_action(workflow, host, host_state)
|
||||||
|
@ -63,6 +63,53 @@ class ExecuteMistralValidatorTest(BaseExecuteMistralValidatorTest):
|
|||||||
self._validate_execute_mistral_action_without_additional_props(
|
self._validate_execute_mistral_action_without_additional_props(
|
||||||
self.validator)
|
self.validator)
|
||||||
|
|
||||||
|
def test_v2_validate_execute_mistral_action_with_func(self):
|
||||||
|
self._validate_action(
|
||||||
|
self._create_v2_execute_mistral_action(
|
||||||
|
'wf_1', 'host_2', 'down', func1='get_attr(alarm,name)'),
|
||||||
|
self.validator.validate
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_v2_validate_execute_mistral_action_with_func_2(self):
|
||||||
|
self._validate_action(
|
||||||
|
self._create_v2_execute_mistral_action(
|
||||||
|
'wf_1', 'host_2', 'down', func1='get_attr(alarm, name)'),
|
||||||
|
self.validator.validate
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_v2_validate_execute_mistral_action_with_func_3(self):
|
||||||
|
self._validate_action(
|
||||||
|
self._create_v2_execute_mistral_action(
|
||||||
|
'wf_1', 'host_2', 'down', func1='get_attr ( alarm , name ) '),
|
||||||
|
self.validator.validate
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_v2_validate_execute_mistral_action_with_func_typo_1(self):
|
||||||
|
# Test setup
|
||||||
|
idx = DEFINITIONS_INDEX_MOCK.copy()
|
||||||
|
action = \
|
||||||
|
self._create_v2_execute_mistral_action(
|
||||||
|
'wf_1', 'host_2', 'down', func1='get_attr(alarm, name')
|
||||||
|
|
||||||
|
# Test action
|
||||||
|
result = self.validator.validate(action, idx)
|
||||||
|
|
||||||
|
# Test assertions
|
||||||
|
self._assert_warning_result(result, 138)
|
||||||
|
|
||||||
|
def test_v2_validate_execute_mistral_action_with_func_typo_2(self):
|
||||||
|
# Test setup
|
||||||
|
idx = DEFINITIONS_INDEX_MOCK.copy()
|
||||||
|
action = \
|
||||||
|
self._create_v2_execute_mistral_action(
|
||||||
|
'wf_1', 'host_2', 'down', func1='get_attr, name)')
|
||||||
|
|
||||||
|
# Test action
|
||||||
|
result = self.validator.validate(action, idx)
|
||||||
|
|
||||||
|
# Test assertions
|
||||||
|
self._assert_warning_result(result, 138)
|
||||||
|
|
||||||
def _create_execute_mistral_action(self, workflow, host, host_state):
|
def _create_execute_mistral_action(self, workflow, host, host_state):
|
||||||
return self.\
|
return self.\
|
||||||
_create_v2_execute_mistral_action(workflow, host, host_state)
|
_create_v2_execute_mistral_action(workflow, host, host_state)
|
||||||
|
@ -228,7 +228,7 @@ class TemplateSyntaxValidatorTest(base.BaseTest):
|
|||||||
self._test_execution_with_correct_result(template)
|
self._test_execution_with_correct_result(template)
|
||||||
|
|
||||||
def test_template_with_valid_version(self):
|
def test_template_with_valid_version(self):
|
||||||
template_path = self.version_dir_path + 'version1.yaml'
|
template_path = self.version_dir_path + 'v1/version1.yaml'
|
||||||
template = file_utils.load_yaml_file(template_path)
|
template = file_utils.load_yaml_file(template_path)
|
||||||
self._test_execution_with_correct_result(template)
|
self._test_execution_with_correct_result(template)
|
||||||
|
|
||||||
|
@ -38,8 +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'
|
V1_MISTRAL_TEMPLATE = 'v1/v1_execute_mistral.yaml'
|
||||||
V2_MISTRAL_TEMPLATE = 'v2_execute_mistral.yaml'
|
V2_MISTRAL_TEMPLATE = 'v2/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'
|
||||||
|
|
||||||
@ -135,6 +135,7 @@ class BasicTemplateTest(base.BaseTest):
|
|||||||
|
|
||||||
expected_scenario = Scenario(
|
expected_scenario = Scenario(
|
||||||
id='basic_template_with_include-scenario0',
|
id='basic_template_with_include-scenario0',
|
||||||
|
version=1,
|
||||||
condition=[
|
condition=[
|
||||||
[ConditionVar(symbol_name='alarm_on_host',
|
[ConditionVar(symbol_name='alarm_on_host',
|
||||||
positive=True)]],
|
positive=True)]],
|
||||||
@ -209,6 +210,7 @@ class BasicTemplateTest(base.BaseTest):
|
|||||||
|
|
||||||
expected_scenario = Scenario(
|
expected_scenario = Scenario(
|
||||||
id='basic_template-scenario0',
|
id='basic_template-scenario0',
|
||||||
|
version=1,
|
||||||
condition=[
|
condition=[
|
||||||
[ConditionVar(symbol_name='alarm_on_host',
|
[ConditionVar(symbol_name='alarm_on_host',
|
||||||
positive=True)]],
|
positive=True)]],
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
# 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 json
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
from testtools.matchers import HasLength
|
from testtools.matchers import HasLength
|
||||||
|
|
||||||
@ -45,6 +46,7 @@ class TestMistralNotifier(BaseTestEvents):
|
|||||||
|
|
||||||
TRIGGER_ALARM_1 = "notifiers.mistral.trigger.alarm.1"
|
TRIGGER_ALARM_1 = "notifiers.mistral.trigger.alarm.1"
|
||||||
TRIGGER_ALARM_2 = "notifiers.mistral.trigger.alarm.2"
|
TRIGGER_ALARM_2 = "notifiers.mistral.trigger.alarm.2"
|
||||||
|
TRIGGER_ALARM_FOR_FUNCTION = "notifiers.mistral.trigger.alarm.for.function"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
@ -59,6 +61,36 @@ class TestMistralNotifier(BaseTestEvents):
|
|||||||
def test_execute_mistral_v2(self):
|
def test_execute_mistral_v2(self):
|
||||||
self._do_test_execute_mistral(self.TRIGGER_ALARM_2)
|
self._do_test_execute_mistral(self.TRIGGER_ALARM_2)
|
||||||
|
|
||||||
|
@utils.tempest_logger
|
||||||
|
def test_execute_mistral_with_function(self):
|
||||||
|
# Execute the basic test
|
||||||
|
self._do_test_execute_mistral(self.TRIGGER_ALARM_FOR_FUNCTION)
|
||||||
|
|
||||||
|
# Make sure that the workflow execution was done with the correct input
|
||||||
|
# (can be checked even if the Vitrage alarm is already down)
|
||||||
|
executions = self.mistral_client.executions.list()
|
||||||
|
|
||||||
|
last_execution = executions[0]
|
||||||
|
for execution in executions:
|
||||||
|
if execution.updated_at > last_execution.updated_at:
|
||||||
|
last_execution = execution
|
||||||
|
|
||||||
|
execution_input_str = last_execution.input
|
||||||
|
self.assertIsNotNone(execution_input_str,
|
||||||
|
'The last execution had no input')
|
||||||
|
self.assertIn('farewell', execution_input_str,
|
||||||
|
'No \'farewell\' key in the last execution input')
|
||||||
|
|
||||||
|
execution_input = json.loads(execution_input_str)
|
||||||
|
|
||||||
|
farewell_value = execution_input['farewell']
|
||||||
|
self.assertIsNotNone(farewell_value, '\'farewell\' input parameter is '
|
||||||
|
'None in last workflow execution')
|
||||||
|
|
||||||
|
self.assertEqual(self.TRIGGER_ALARM_FOR_FUNCTION, farewell_value,
|
||||||
|
'\'farewell\' input parameter does not match the'
|
||||||
|
'alarm name')
|
||||||
|
|
||||||
def _do_test_execute_mistral(self, trigger_alarm):
|
def _do_test_execute_mistral(self, trigger_alarm):
|
||||||
workflows = self.mistral_client.workflows.list()
|
workflows = self.mistral_client.workflows.list()
|
||||||
self.assertIsNotNone(workflows, 'Failed to get the list of workflows')
|
self.assertIsNotNone(workflows, 'Failed to get the list of workflows')
|
||||||
|
@ -7,20 +7,29 @@ definitions:
|
|||||||
- entity:
|
- entity:
|
||||||
category: ALARM
|
category: ALARM
|
||||||
name: notifiers.mistral.trigger.alarm.2
|
name: notifiers.mistral.trigger.alarm.2
|
||||||
template_id: alarm
|
template_id: alarm_2
|
||||||
|
- entity:
|
||||||
|
category: ALARM
|
||||||
|
name: notifiers.mistral.trigger.alarm.for.function
|
||||||
|
template_id: alarm_for_func
|
||||||
- entity:
|
- entity:
|
||||||
category: RESOURCE
|
category: RESOURCE
|
||||||
type: nova.host
|
type: nova.host
|
||||||
template_id: host
|
template_id: host
|
||||||
relationships:
|
relationships:
|
||||||
- relationship:
|
- relationship:
|
||||||
source: alarm
|
source: alarm_2
|
||||||
relationship_type: on
|
relationship_type: on
|
||||||
target: host
|
target: host
|
||||||
template_id : alarm_on_host
|
template_id : alarm_2_on_host
|
||||||
|
- relationship:
|
||||||
|
source: alarm_for_func
|
||||||
|
relationship_type: on
|
||||||
|
target: host
|
||||||
|
template_id : alarm_for_func_on_host
|
||||||
scenarios:
|
scenarios:
|
||||||
- scenario:
|
- scenario:
|
||||||
condition: alarm_on_host
|
condition: alarm_2_on_host
|
||||||
actions:
|
actions:
|
||||||
- action:
|
- action:
|
||||||
action_type: execute_mistral
|
action_type: execute_mistral
|
||||||
@ -28,3 +37,12 @@ scenarios:
|
|||||||
workflow: wf_for_tempest_test_1234
|
workflow: wf_for_tempest_test_1234
|
||||||
input:
|
input:
|
||||||
farewell: Hello and Goodbye
|
farewell: Hello and Goodbye
|
||||||
|
- scenario:
|
||||||
|
condition: alarm_for_func_on_host
|
||||||
|
actions:
|
||||||
|
- action:
|
||||||
|
action_type: execute_mistral
|
||||||
|
properties:
|
||||||
|
workflow: wf_for_tempest_test_1234
|
||||||
|
input:
|
||||||
|
farewell: get_attr(alarm_for_func,name)
|
||||||
|
Loading…
Reference in New Issue
Block a user