Merge "Introduce the execute-mistral action. This patch includes validating the action, and converting it to a general-purpose ExecuteExternal step."
This commit is contained in:
commit
32de6d3b8c
@ -101,3 +101,6 @@ The following describes all the possible status code and their messages:
|
||||
| 132 | add_causal_relationship action requires action_target to| content |
|
||||
| | be ALARM | |
|
||||
+------------------+---------------------------------------------------------+-------------------------------+
|
||||
| 133 | execute_mistral action must contain workflow field in | content |
|
||||
| | properties block | |
|
||||
+------------------+---------------------------------------------------------+-------------------------------+
|
||||
|
@ -25,12 +25,14 @@ from vitrage.evaluator.actions.evaluator_event_transformer \
|
||||
import VITRAGE_DATASOURCE
|
||||
from vitrage.evaluator.actions.recipes.action_steps import ADD_EDGE
|
||||
from vitrage.evaluator.actions.recipes.action_steps import ADD_VERTEX
|
||||
from vitrage.evaluator.actions.recipes.action_steps import EXECUTE_EXTERNAL
|
||||
from vitrage.evaluator.actions.recipes.action_steps import REMOVE_EDGE
|
||||
from vitrage.evaluator.actions.recipes.action_steps import REMOVE_VERTEX
|
||||
from vitrage.evaluator.actions.recipes.action_steps import UPDATE_VERTEX
|
||||
from vitrage.evaluator.actions.recipes.add_causal_relationship import \
|
||||
AddCausalRelationship
|
||||
from vitrage.evaluator.actions.recipes.base import EVALUATOR_EVENT_TYPE
|
||||
from vitrage.evaluator.actions.recipes.execute_mistral import ExecuteMistral
|
||||
from vitrage.evaluator.actions.recipes.mark_down import MarkDown
|
||||
from vitrage.evaluator.actions.recipes.raise_alarm import RaiseAlarm
|
||||
from vitrage.evaluator.actions.recipes.set_state import SetState
|
||||
@ -44,11 +46,12 @@ class ActionExecutor(object):
|
||||
self.action_recipes = ActionExecutor._register_action_recipes()
|
||||
|
||||
self.action_step_defs = {
|
||||
ADD_VERTEX: self.add_vertex,
|
||||
REMOVE_VERTEX: self.remove_vertex,
|
||||
UPDATE_VERTEX: self.update_vertex,
|
||||
ADD_EDGE: self.add_edge,
|
||||
REMOVE_EDGE: self.remove_edge,
|
||||
ADD_VERTEX: self._add_vertex,
|
||||
REMOVE_VERTEX: self._remove_vertex,
|
||||
UPDATE_VERTEX: self._update_vertex,
|
||||
ADD_EDGE: self._add_edge,
|
||||
REMOVE_EDGE: self._remove_edge,
|
||||
EXECUTE_EXTERNAL: self._execute_external,
|
||||
}
|
||||
|
||||
def execute(self, action_spec, action_mode):
|
||||
@ -62,7 +65,7 @@ class ActionExecutor(object):
|
||||
for step in steps:
|
||||
self.action_step_defs[step.type](step.params)
|
||||
|
||||
def add_vertex(self, params):
|
||||
def _add_vertex(self, params):
|
||||
|
||||
event = copy.deepcopy(params)
|
||||
ActionExecutor._add_default_properties(event)
|
||||
@ -70,7 +73,7 @@ class ActionExecutor(object):
|
||||
|
||||
self.event_queue.put(event)
|
||||
|
||||
def update_vertex(self, params):
|
||||
def _update_vertex(self, params):
|
||||
|
||||
event = copy.deepcopy(params)
|
||||
ActionExecutor._add_default_properties(event)
|
||||
@ -78,14 +81,14 @@ class ActionExecutor(object):
|
||||
|
||||
self.event_queue.put(event)
|
||||
|
||||
def remove_vertex(self, params):
|
||||
def _remove_vertex(self, params):
|
||||
event = copy.deepcopy(params)
|
||||
ActionExecutor._add_default_properties(event)
|
||||
event[EVALUATOR_EVENT_TYPE] = REMOVE_VERTEX
|
||||
|
||||
self.event_queue.put(event)
|
||||
|
||||
def add_edge(self, params):
|
||||
def _add_edge(self, params):
|
||||
|
||||
event = copy.deepcopy(params)
|
||||
ActionExecutor._add_default_properties(event)
|
||||
@ -93,7 +96,7 @@ class ActionExecutor(object):
|
||||
|
||||
self.event_queue.put(event)
|
||||
|
||||
def remove_edge(self, params):
|
||||
def _remove_edge(self, params):
|
||||
|
||||
event = copy.deepcopy(params)
|
||||
ActionExecutor._add_default_properties(event)
|
||||
@ -101,6 +104,12 @@ class ActionExecutor(object):
|
||||
|
||||
self.event_queue.put(event)
|
||||
|
||||
def _execute_external(self, params):
|
||||
|
||||
# TODO(ifat_afek): send to a dedicated queue
|
||||
# external_engine = params[EXECUTION_ENGINE]
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def _add_default_properties(event):
|
||||
|
||||
@ -129,4 +138,7 @@ class ActionExecutor(object):
|
||||
recipes[ActionType.MARK_DOWN] = importutils.import_object(
|
||||
"%s.%s" % (MarkDown.__module__, MarkDown.__name__))
|
||||
|
||||
recipes[ActionType.EXECUTE_MISTRAL] = importutils.import_object(
|
||||
"%s.%s" % (ExecuteMistral.__module__, ExecuteMistral.__name__))
|
||||
|
||||
return recipes
|
||||
|
@ -19,11 +19,13 @@ class ActionType(object):
|
||||
RAISE_ALARM = 'raise_alarm'
|
||||
ADD_CAUSAL_RELATIONSHIP = 'add_causal_relationship'
|
||||
MARK_DOWN = 'mark_down'
|
||||
EXECUTE_MISTRAL = 'execute_mistral'
|
||||
|
||||
action_types = [ActionType.SET_STATE,
|
||||
ActionType.RAISE_ALARM,
|
||||
ActionType.ADD_CAUSAL_RELATIONSHIP,
|
||||
ActionType.MARK_DOWN]
|
||||
ActionType.MARK_DOWN,
|
||||
ActionType.EXECUTE_MISTRAL]
|
||||
|
||||
|
||||
class ActionMode(object):
|
||||
|
@ -17,3 +17,6 @@ REMOVE_VERTEX = 'remove_vertex'
|
||||
UPDATE_VERTEX = 'update_vertex'
|
||||
ADD_EDGE = 'add_edge'
|
||||
REMOVE_EDGE = 'remove_edge'
|
||||
EXECUTE_EXTERNAL = 'execute_external'
|
||||
|
||||
EXECUTION_ENGINE = 'execution_engine'
|
||||
|
59
vitrage/evaluator/actions/recipes/execute_mistral.py
Normal file
59
vitrage/evaluator/actions/recipes/execute_mistral.py
Normal file
@ -0,0 +1,59 @@
|
||||
# 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.actions.recipes.action_steps import EXECUTE_EXTERNAL
|
||||
from vitrage.evaluator.actions.recipes.action_steps import EXECUTION_ENGINE
|
||||
from vitrage.evaluator.actions.recipes import base
|
||||
from vitrage.evaluator.actions.recipes.base import ActionStepWrapper
|
||||
|
||||
|
||||
MISTRAL = 'mistral'
|
||||
WORKFLOW = 'workflow'
|
||||
|
||||
|
||||
class ExecuteMistral(base.Recipe):
|
||||
"""Execute a Mistral workflow
|
||||
|
||||
The 'get_do_recipe' and 'get_undo_recipe' receive action_spec as input.
|
||||
The action_spec contains the following fields: type and properties.
|
||||
|
||||
example input:
|
||||
|
||||
action_spec = ActionSpecs('type'= {'execute_mistral'},
|
||||
'properties' = {workflow : wf_1,
|
||||
host: host_2,
|
||||
host_status: down}
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def get_do_recipe(action_spec):
|
||||
|
||||
execute_external_step = ExecuteMistral._get_execute_external_step(
|
||||
action_spec.properties
|
||||
)
|
||||
|
||||
return [execute_external_step]
|
||||
|
||||
@staticmethod
|
||||
def get_undo_recipe(action_spec):
|
||||
# No undo for execute an external action
|
||||
return []
|
||||
|
||||
@staticmethod
|
||||
def _get_execute_external_step(properties):
|
||||
|
||||
properties[EXECUTION_ENGINE] = MISTRAL
|
||||
execute_external_step = ActionStepWrapper(EXECUTE_EXTERNAL,
|
||||
properties)
|
||||
|
||||
return execute_external_step
|
@ -382,7 +382,8 @@ class ActionTracker(object):
|
||||
ActionType.SET_STATE: pt.SetStateTools(all_scores),
|
||||
ActionType.RAISE_ALARM: pt.RaiseAlarmTools(alarms_score),
|
||||
ActionType.ADD_CAUSAL_RELATIONSHIP: pt.BaselineTools,
|
||||
ActionType.MARK_DOWN: pt.BaselineTools
|
||||
ActionType.MARK_DOWN: pt.BaselineTools,
|
||||
ActionType.EXECUTE_MISTRAL: pt.BaselineTools
|
||||
}
|
||||
|
||||
def get_key(self, action_specs):
|
||||
|
@ -274,7 +274,7 @@ class TemplateData(object):
|
||||
|
||||
action_dict = action_def[TFields.ACTION]
|
||||
action_type = action_dict[TFields.ACTION_TYPE]
|
||||
targets = action_dict[TFields.ACTION_TARGET]
|
||||
targets = action_dict.get(TFields.ACTION_TARGET, {})
|
||||
properties = action_dict.get(TFields.PROPERTIES, {})
|
||||
|
||||
actions.append(ActionSpecs(action_type, targets, properties))
|
||||
|
@ -0,0 +1,41 @@
|
||||
# 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 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)
|
||||
|
||||
return get_content_correct_result()
|
@ -62,8 +62,8 @@ status_msgs = {
|
||||
'{actions}'.format(actions=action_types),
|
||||
121: 'At least one action must be defined.',
|
||||
122: 'Action field is required.',
|
||||
123: 'Relationship definition must contain action_type field.',
|
||||
124: 'Relationship definition must contain action_target field.',
|
||||
123: 'Action definition must contain action_type field.',
|
||||
124: 'Action definition must contain action_target field.',
|
||||
125: 'raise_alarm action must contain alarm_name field in properties '
|
||||
'block.',
|
||||
126: 'raise_alarm action must contain severity field in properties block.',
|
||||
@ -74,6 +74,8 @@ status_msgs = {
|
||||
'in target_action block.',
|
||||
131: 'mark_down action must contain \'target\' field in'
|
||||
' \'target_action\' block.',
|
||||
132: 'add_causal_relationship action requires action_target to be ALARM'
|
||||
132: 'add_causal_relationship action requires action_target to be ALARM',
|
||||
133: 'execute_mistral action must contain workflow field in properties '
|
||||
'block'
|
||||
|
||||
}
|
||||
|
@ -17,6 +17,10 @@ from oslo_utils import timeutils
|
||||
# noinspection PyPackageRequirements
|
||||
from oslotest import base
|
||||
import sys
|
||||
from testtools.matchers import HasLength
|
||||
|
||||
|
||||
IsEmpty = lambda: HasLength(0)
|
||||
|
||||
|
||||
class BaseTest(base.BaseTestCase):
|
||||
|
70
vitrage/tests/unit/evaluator/recipes/test_execute_mistral.py
Normal file
70
vitrage/tests/unit/evaluator/recipes/test_execute_mistral.py
Normal file
@ -0,0 +1,70 @@
|
||||
# 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 testtools.matchers import HasLength
|
||||
from vitrage.evaluator.actions.base import ActionType
|
||||
from vitrage.evaluator.actions.recipes.action_steps import EXECUTE_EXTERNAL
|
||||
from vitrage.evaluator.actions.recipes.action_steps import EXECUTION_ENGINE
|
||||
from vitrage.evaluator.actions.recipes.execute_mistral import ExecuteMistral
|
||||
from vitrage.evaluator.actions.recipes.execute_mistral import MISTRAL
|
||||
from vitrage.evaluator.actions.recipes.execute_mistral import WORKFLOW
|
||||
from vitrage.evaluator.template_data import ActionSpecs
|
||||
from vitrage.tests.base import BaseTest
|
||||
from vitrage.tests.base import IsEmpty
|
||||
|
||||
|
||||
class RaiseAlarmRecipeTest(BaseTest):
|
||||
|
||||
# noinspection PyPep8Naming
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
|
||||
cls.props = {EXECUTION_ENGINE: MISTRAL,
|
||||
WORKFLOW: 'wf_4',
|
||||
'host': 'host5',
|
||||
'state': 'ok'}
|
||||
cls.action_spec = ActionSpecs(ActionType.EXECUTE_MISTRAL,
|
||||
{},
|
||||
cls.props)
|
||||
|
||||
def test_get_do_recipe(self):
|
||||
|
||||
# Test Action
|
||||
action_steps = ExecuteMistral.get_do_recipe(self.action_spec)
|
||||
|
||||
# Test Assertions
|
||||
|
||||
# expecting for one step: [execute_external]
|
||||
self.assertThat(action_steps, HasLength(1))
|
||||
|
||||
self.assertEqual(EXECUTE_EXTERNAL, action_steps[0].type)
|
||||
execute_external_step_params = action_steps[0].params
|
||||
self.assertIsNotNone(execute_external_step_params)
|
||||
self.assertLessEqual(2, len(execute_external_step_params))
|
||||
|
||||
execution_engine = execute_external_step_params[EXECUTION_ENGINE]
|
||||
self.assertEqual(self.props[EXECUTION_ENGINE], execution_engine)
|
||||
|
||||
workflow = execute_external_step_params[WORKFLOW]
|
||||
self.assertEqual(self.props[WORKFLOW], workflow)
|
||||
|
||||
def test_get_undo_recipe(self):
|
||||
|
||||
# Test Action
|
||||
action_steps = ExecuteMistral.get_undo_recipe(self.action_spec)
|
||||
|
||||
# Test Assertions
|
||||
|
||||
# expecting for zero steps (no undo for this action)
|
||||
self.assertThat(action_steps, IsEmpty())
|
@ -0,0 +1,99 @@
|
||||
# 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.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):
|
||||
|
||||
def test_validate_execute_mistral_action(self):
|
||||
|
||||
self._validate_action(
|
||||
self._create_execute_mistral_action('wf_1', 'host_2', 'down'),
|
||||
ExecuteMistralValidator.validate
|
||||
)
|
||||
|
||||
def test_validate_execute_mistral_action_without_workflow(self):
|
||||
|
||||
# Test setup
|
||||
idx = DEFINITIONS_INDEX_MOCK.copy()
|
||||
action = self._create_execute_mistral_action('wf_1', 'host_2', 'down')
|
||||
action[TemplateFields.PROPERTIES].pop(WORKFLOW)
|
||||
|
||||
# Test action
|
||||
result = ExecuteMistralValidator.validate(action, idx)
|
||||
|
||||
# Test assertions
|
||||
self._assert_fault_result(result, 133)
|
||||
|
||||
def test_validate_execute_mistral_action_with_empty_workflow(self):
|
||||
|
||||
# Test setup
|
||||
idx = DEFINITIONS_INDEX_MOCK.copy()
|
||||
action = self._create_execute_mistral_action('', 'host_2', 'down')
|
||||
|
||||
# Test action
|
||||
result = ExecuteMistralValidator.validate(action, idx)
|
||||
|
||||
# Test assertions
|
||||
self._assert_fault_result(result, 133)
|
||||
|
||||
def test_validate_execute_mistral_action_with_none_workflow(self):
|
||||
|
||||
# Test setup
|
||||
idx = DEFINITIONS_INDEX_MOCK.copy()
|
||||
action = self._create_execute_mistral_action(None, 'host_2', 'down')
|
||||
|
||||
# Test action
|
||||
result = ExecuteMistralValidator.validate(action, idx)
|
||||
|
||||
# Test assertions
|
||||
self._assert_fault_result(result, 133)
|
||||
|
||||
def test_validate_execute_mistral_action_without_additional_params(self):
|
||||
|
||||
# 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')
|
||||
|
||||
# Test action
|
||||
result = ExecuteMistralValidator.validate(action, idx)
|
||||
|
||||
# Test assertions
|
||||
self._assert_correct_result(result)
|
||||
|
||||
@staticmethod
|
||||
def _create_execute_mistral_action(workflow, host, host_state):
|
||||
|
||||
properties = {
|
||||
WORKFLOW: workflow,
|
||||
'host': host,
|
||||
'host_state': host_state
|
||||
}
|
||||
action = {
|
||||
TemplateFields.ACTION_TYPE: ActionType.EXECUTE_MISTRAL,
|
||||
TemplateFields.PROPERTIES: properties
|
||||
}
|
||||
|
||||
return action
|
Loading…
x
Reference in New Issue
Block a user