Template version 3 - short format
Added version 3 syntax and content validation and a new loader, with unitests. Story: 2004871 Task: 29130 Depends-On: https://review.openstack.org/#/c/633410/ Change-Id: Ia6935b561b4123e99e35f46d6a2a07267edd92ea
This commit is contained in:
parent
10a7d7eb0d
commit
a859882b47
@ -14,7 +14,13 @@
|
||||
from collections import namedtuple
|
||||
import re
|
||||
|
||||
from vitrage.evaluator.template_fields import TemplateFields
|
||||
from vitrage.evaluator.template_schema_factory import TemplateSchemaFactory
|
||||
|
||||
Template = namedtuple('Template', ['uuid', 'data', 'date'])
|
||||
TEMPLATE_LOADER = 'template_loader'
|
||||
SYNTAX = 'syntax'
|
||||
CONTENT = 'content'
|
||||
|
||||
|
||||
def is_function(str):
|
||||
@ -24,3 +30,12 @@ def is_function(str):
|
||||
Search for a regex with open and close parenthesis
|
||||
"""
|
||||
return re.match('.*\(.*\)', str)
|
||||
|
||||
|
||||
def get_template_schema(template):
|
||||
metadata = template.get(TemplateFields.METADATA)
|
||||
if metadata is None:
|
||||
return None
|
||||
version = metadata.get(TemplateFields.VERSION,
|
||||
TemplateSchemaFactory.DEFAULT_VERSION)
|
||||
return TemplateSchemaFactory().template_schema(version)
|
||||
|
@ -17,7 +17,7 @@ from collections import namedtuple
|
||||
from sympy.logic.boolalg import And
|
||||
from sympy.logic.boolalg import Not
|
||||
from sympy.logic.boolalg import Or
|
||||
from sympy.logic.boolalg import to_dnf as sympy_to_dfn
|
||||
from sympy.logic.boolalg import to_dnf as sympy_to_dnf
|
||||
from sympy import Symbol
|
||||
|
||||
|
||||
@ -149,13 +149,16 @@ def parse_condition(condition_str):
|
||||
|
||||
|
||||
def convert_to_dnf_format(condition_str):
|
||||
|
||||
condition_str = condition_str.replace(' and ', '&')
|
||||
condition_str = condition_str.replace(' AND ', '&')
|
||||
condition_str = condition_str.replace(' or ', '|')
|
||||
condition_str = condition_str.replace(' OR ', '|')
|
||||
condition_str = condition_str.replace(' not ', '~')
|
||||
condition_str = condition_str.replace(' NOT ', '~')
|
||||
condition_str = condition_str.replace('not ', '~')
|
||||
condition_str = condition_str.replace('NOT ', '~')
|
||||
|
||||
return sympy_to_dfn(condition_str)
|
||||
return sympy_to_dnf(condition_str)
|
||||
|
||||
|
||||
def extract_or_condition(or_condition):
|
||||
|
@ -20,11 +20,12 @@ from oslo_log import log
|
||||
from vitrage.common.constants import TemplateStatus
|
||||
from vitrage.common.constants import TemplateTypes as TType
|
||||
from vitrage.common.utils import get_portion
|
||||
from vitrage.evaluator.base import get_template_schema
|
||||
from vitrage.evaluator.base import Template
|
||||
from vitrage.evaluator.base import TEMPLATE_LOADER
|
||||
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.template_syntax_validator import \
|
||||
EXCEPTION
|
||||
from vitrage.graph.filter import check_filter as check_subset
|
||||
@ -104,8 +105,11 @@ class ScenarioRepository(object):
|
||||
self.templates[template.uuid] = Template(template.uuid,
|
||||
template.file_content,
|
||||
template.created_at)
|
||||
template_data = TemplateLoader().load(template.file_content,
|
||||
self._def_templates)
|
||||
schema = get_template_schema(template.file_content)
|
||||
template_data = schema.loaders[TEMPLATE_LOADER].load(
|
||||
schema,
|
||||
template.file_content,
|
||||
self._def_templates)
|
||||
for scenario in template_data.scenarios:
|
||||
for equivalent_scenario in self._expand_equivalence(scenario):
|
||||
self._add_scenario(equivalent_scenario)
|
||||
|
@ -47,8 +47,13 @@ class Scenario(object):
|
||||
# noinspection PyAttributeOutsideInit
|
||||
class TemplateData(object):
|
||||
|
||||
def __init__(self, name, template_type, version, entities,
|
||||
relationships, scenarios):
|
||||
def __init__(self,
|
||||
name=None,
|
||||
template_type=None,
|
||||
version=None,
|
||||
entities=None,
|
||||
relationships=None,
|
||||
scenarios=None):
|
||||
self.name = name
|
||||
self.template_type = template_type
|
||||
self.version = version
|
||||
|
@ -25,6 +25,7 @@ class TemplateFields(TemplateTopologyFields):
|
||||
ACTION_TARGET = 'action_target'
|
||||
ACTION_TYPE = 'action_type'
|
||||
CATEGORY = 'category'
|
||||
CAUSING_ALARM = 'causing_alarm'
|
||||
CONDITION = 'condition'
|
||||
INCLUDES = 'includes'
|
||||
SEVERITY = 'severity'
|
||||
|
@ -48,8 +48,10 @@ class ScenarioLoader(object):
|
||||
scenario_id = "%s-scenario%s" % (self.name, str(counter))
|
||||
scenario_dict = scenario_def[TFields.SCENARIO]
|
||||
condition = parse_condition(scenario_dict[TFields.CONDITION])
|
||||
self.valid_target = \
|
||||
self._calculate_missing_action_target(condition)
|
||||
self.valid_target = calculate_action_target(
|
||||
condition,
|
||||
self._template_entities,
|
||||
self._template_relationships)
|
||||
actions = self._build_actions(scenario_dict[TFields.ACTIONS],
|
||||
scenario_id)
|
||||
subgraphs = SubGraphBuilder.from_condition(
|
||||
@ -70,9 +72,9 @@ class ScenarioLoader(object):
|
||||
vertex_id=entities[template_id].vertex_id,
|
||||
properties={k: v for k, v in entity_props})
|
||||
relationships = {
|
||||
rel_id: cls._build_equivalent_relationship(rel,
|
||||
template_id,
|
||||
entity_props)
|
||||
rel_id: _build_equivalent_relationship(rel,
|
||||
template_id,
|
||||
entity_props)
|
||||
for rel_id, rel in scenario.relationships.items()}
|
||||
|
||||
def extract_var(symbol_name):
|
||||
@ -125,48 +127,51 @@ class ScenarioLoader(object):
|
||||
self.entities[symbol_name] = entity
|
||||
return entity, ENTITY
|
||||
|
||||
def _calculate_missing_action_target(self, condition):
|
||||
"""Return a vertex that can be used as an action target.
|
||||
|
||||
External actions like execute_mistral do not have an explicit
|
||||
action target. This parameter is a must for the sub-graph matching
|
||||
algorithm. If it is missing, we would like to select an arbitrary
|
||||
target from the condition.
|
||||
def calculate_action_target(condition, entities, relationships):
|
||||
"""Return a vertex that can be used as an action target.
|
||||
|
||||
"""
|
||||
definition_index = self._template_entities.copy()
|
||||
definition_index.update(self._template_relationships)
|
||||
targets = \
|
||||
get_condition_common_targets(condition,
|
||||
definition_index,
|
||||
self.TemplateDataSymbolResolver())
|
||||
return {TFields.TARGET: targets.pop()} if targets else None
|
||||
External actions like execute_mistral do not have an explicit
|
||||
action target. This parameter is a must for the sub-graph matching
|
||||
algorithm. If it is missing, we would like to select an arbitrary
|
||||
target from the condition.
|
||||
If the target result is empty the condition is not valid.
|
||||
|
||||
class TemplateDataSymbolResolver(SymbolResolver):
|
||||
def is_relationship(self, symbol):
|
||||
return isinstance(symbol, EdgeDescription)
|
||||
"""
|
||||
definition_index = entities.copy()
|
||||
definition_index.update(relationships)
|
||||
targets = get_condition_common_targets(
|
||||
condition,
|
||||
definition_index,
|
||||
TemplateDataSymbolResolver())
|
||||
return {TFields.TARGET: targets.pop()} if targets else None
|
||||
|
||||
def get_relationship_source_id(self, relationship):
|
||||
return relationship.source.vertex_id
|
||||
|
||||
def get_relationship_target_id(self, relationship):
|
||||
return relationship.target.vertex_id
|
||||
class TemplateDataSymbolResolver(SymbolResolver):
|
||||
def is_relationship(self, symbol):
|
||||
return isinstance(symbol, EdgeDescription)
|
||||
|
||||
def get_entity_id(self, entity):
|
||||
return entity.vertex_id
|
||||
def get_relationship_source_id(self, relationship):
|
||||
return relationship.source.vertex_id
|
||||
|
||||
@staticmethod
|
||||
def _build_equivalent_relationship(relationship,
|
||||
template_id,
|
||||
entity_props):
|
||||
source = relationship.source
|
||||
target = relationship.target
|
||||
if relationship.edge.source_id == template_id:
|
||||
source = Vertex(vertex_id=source.vertex_id,
|
||||
properties={k: v for k, v in entity_props})
|
||||
elif relationship.edge.target_id == template_id:
|
||||
target = Vertex(vertex_id=target.vertex_id,
|
||||
properties={k: v for k, v in entity_props})
|
||||
return EdgeDescription(source=source,
|
||||
target=target,
|
||||
edge=relationship.edge)
|
||||
def get_relationship_target_id(self, relationship):
|
||||
return relationship.target.vertex_id
|
||||
|
||||
def get_entity_id(self, entity):
|
||||
return entity.vertex_id
|
||||
|
||||
|
||||
def _build_equivalent_relationship(relationship,
|
||||
template_id,
|
||||
entity_props):
|
||||
source = relationship.source
|
||||
target = relationship.target
|
||||
if relationship.edge.source_id == template_id:
|
||||
source = Vertex(vertex_id=source.vertex_id,
|
||||
properties={k: v for k, v in entity_props})
|
||||
elif relationship.edge.target_id == template_id:
|
||||
target = Vertex(vertex_id=target.vertex_id,
|
||||
properties={k: v for k, v in entity_props})
|
||||
return EdgeDescription(source=source,
|
||||
target=target,
|
||||
edge=relationship.edge)
|
||||
|
@ -14,13 +14,11 @@
|
||||
|
||||
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
|
||||
@ -31,29 +29,11 @@ LOG = log.getLogger(__name__)
|
||||
|
||||
class TemplateLoader(object):
|
||||
|
||||
PROPS_CONVERSION = {
|
||||
'category': VProps.VITRAGE_CATEGORY,
|
||||
'type': VProps.VITRAGE_TYPE,
|
||||
'resource_id': VProps.VITRAGE_RESOURCE_ID,
|
||||
'sample_timestamp': VProps.VITRAGE_SAMPLE_TIMESTAMP,
|
||||
'is_deleted': VProps.VITRAGE_IS_DELETED,
|
||||
'is_placeholder': VProps.VITRAGE_IS_PLACEHOLDER,
|
||||
'aggregated_state': VProps.VITRAGE_AGGREGATED_STATE,
|
||||
'operational_state': VProps.VITRAGE_OPERATIONAL_STATE,
|
||||
'aggregated_severity': VProps.VITRAGE_AGGREGATED_SEVERITY,
|
||||
'operational_severity': VProps.VITRAGE_OPERATIONAL_SEVERITY
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self.entities = {}
|
||||
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
|
||||
def load(self, template_schema, template_def, def_templates=None):
|
||||
|
||||
name = template_def[TFields.METADATA][TFields.NAME]
|
||||
|
||||
@ -179,13 +159,3 @@ 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
|
||||
|
158
vitrage/evaluator/template_loading/template_loader_v3.py
Normal file
158
vitrage/evaluator/template_loading/template_loader_v3.py
Normal file
@ -0,0 +1,158 @@
|
||||
# Copyright 2019 - 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 copy
|
||||
|
||||
from oslo_log import log
|
||||
import re
|
||||
|
||||
from vitrage.evaluator.condition import parse_condition
|
||||
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_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 \
|
||||
calculate_action_target
|
||||
from vitrage.evaluator.template_loading.subgraph_builder import SubGraphBuilder
|
||||
from vitrage.evaluator.template_validation.base import ValidationError
|
||||
from vitrage.graph import Edge
|
||||
from vitrage.graph import Vertex
|
||||
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
class TemplateLoader(object):
|
||||
|
||||
def load(self, template_schema, template_def, def_templates=None):
|
||||
template = copy.deepcopy(template_def)
|
||||
entities = _build_entities(template)
|
||||
relationships = _build_condition_relationships(template, entities)
|
||||
return TemplateData(scenarios=_build_scenarios(
|
||||
template, entities, relationships, template_schema))
|
||||
|
||||
|
||||
def _build_entities(template):
|
||||
entities = dict()
|
||||
for template_id, entity in template[TFields.ENTITIES].items():
|
||||
properties = PropsConverter.convert_props_with_dictionary(entity)
|
||||
entities[template_id] = Vertex(template_id, properties)
|
||||
return entities
|
||||
|
||||
|
||||
def _build_condition_relationships(template, entities):
|
||||
relationships = dict()
|
||||
for scenario in template[TFields.SCENARIOS]:
|
||||
condition = scenario.get(TFields.CONDITION)
|
||||
extracted_relationships, processed_condition = \
|
||||
_process_condition(condition, entities)
|
||||
relationships.update(extracted_relationships)
|
||||
scenario[TFields.CONDITION] = processed_condition
|
||||
|
||||
return relationships
|
||||
|
||||
|
||||
def _process_condition(condition, entities):
|
||||
"""Process the condition, while extracting the condition relationships
|
||||
|
||||
Example:
|
||||
condition: 'host_alarm [ on ] host AND host [contains] instance'
|
||||
|
||||
regex matches:
|
||||
match group 1: 'host_alarm'
|
||||
match group 2: 'on'
|
||||
match group 3: 'host'
|
||||
..
|
||||
|
||||
Example returns:
|
||||
processed_condition: 'host_alarm__on__host AND host__contains__instance'
|
||||
extracted_relationships: {
|
||||
host_alarm__on__host: EdgeDescription(...),
|
||||
host__contains__instance: EdgeDescription(...),
|
||||
}
|
||||
"""
|
||||
regex = r"(\w+)\s*\[\s*(\w+)\s*\]\s*(\w+)"
|
||||
extracted_relationships = dict()
|
||||
|
||||
def relation_str(matchobj):
|
||||
source = matchobj.group(1)
|
||||
label = matchobj.group(2)
|
||||
target = matchobj.group(3)
|
||||
relation_id = '%s__%s__%s' % (source, label, target)
|
||||
extracted_relationships[relation_id] = EdgeDescription(
|
||||
Edge(source, target, label, dict()),
|
||||
entities[source],
|
||||
entities[target])
|
||||
return relation_id
|
||||
|
||||
processed_condition = re.sub(regex, relation_str, condition)
|
||||
return extracted_relationships, processed_condition
|
||||
|
||||
|
||||
def _build_scenarios(template, entities, relationships, schema):
|
||||
name = template[TFields.METADATA][TFields.NAME]
|
||||
|
||||
scenarios = []
|
||||
for index, scenario in enumerate(template[TFields.SCENARIOS]):
|
||||
s_id = "%s-scenario%d" % (name, index)
|
||||
condition = parse_condition(scenario.get(TFields.CONDITION))
|
||||
default_target = calculate_action_target(condition,
|
||||
entities,
|
||||
relationships)
|
||||
|
||||
if not default_target:
|
||||
raise ValidationError(135, 'scenario %d' % index,
|
||||
scenario.get(TFields.CONDITION))
|
||||
|
||||
tmp_scenario = Scenario(
|
||||
id=s_id,
|
||||
version=schema.version(),
|
||||
condition=None,
|
||||
actions=_build_actions(schema, scenario, s_id, default_target),
|
||||
subgraphs=_build_subgraphs(condition, entities, relationships),
|
||||
entities=entities,
|
||||
relationships=relationships)
|
||||
scenarios.append(tmp_scenario)
|
||||
return scenarios
|
||||
|
||||
|
||||
def _build_actions(template_schema, scenario, scenario_id, default_target):
|
||||
actions = []
|
||||
actions_def = scenario[TFields.ACTIONS]
|
||||
for counter, action_def in enumerate(actions_def):
|
||||
action_id = '%s-action%d' % (scenario_id, counter)
|
||||
action_type, action_props = action_def.popitem()
|
||||
action = template_schema.loaders.get(action_type).load(
|
||||
action_id,
|
||||
default_target,
|
||||
action_props,
|
||||
action_type
|
||||
)
|
||||
actions.append(action)
|
||||
return actions
|
||||
|
||||
|
||||
def _build_subgraphs(condition, entities, relationships):
|
||||
|
||||
def _extract_var(symbol_name):
|
||||
if symbol_name in relationships:
|
||||
edge_descriptor = relationships[symbol_name]
|
||||
return edge_descriptor, RELATIONSHIP
|
||||
elif symbol_name in entities:
|
||||
vertex = entities[symbol_name]
|
||||
return vertex, ENTITY
|
||||
|
||||
return SubGraphBuilder.from_condition(condition, _extract_var)
|
@ -20,10 +20,10 @@ from vitrage.evaluator.template_fields import TemplateFields as TFields
|
||||
|
||||
class BaseActionLoader(object):
|
||||
|
||||
def load(self, action_id, valid_target, action_def):
|
||||
def load(self, action_id, default_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)
|
||||
targets = action_dict.get(TFields.ACTION_TARGET, default_target)
|
||||
return ActionSpecs(action_id, action_type, targets,
|
||||
self._get_properties(action_dict))
|
||||
|
||||
|
0
vitrage/evaluator/template_loading/v3/__init__.py
Normal file
0
vitrage/evaluator/template_loading/v3/__init__.py
Normal file
35
vitrage/evaluator/template_loading/v3/action_loader.py
Normal file
35
vitrage/evaluator/template_loading/v3/action_loader.py
Normal file
@ -0,0 +1,35 @@
|
||||
# Copyright 2019 - 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_data import ActionSpecs
|
||||
from vitrage.evaluator.template_fields import TemplateFields as TField
|
||||
|
||||
|
||||
class ActionLoader(object):
|
||||
|
||||
def load(self, action_id, default_target, action_dict, action_type):
|
||||
"""V3 template action to ActionSpecs transformation
|
||||
|
||||
:param action_id: Unique action identifier
|
||||
:param default_target: Is taken from the condition,
|
||||
it is used when the action doesn't define a target
|
||||
:param action_dict: Action section taken from the template.
|
||||
:param action_type: example: set_state/raise_alarm/etc..
|
||||
:rtype: ActionSpecs
|
||||
"""
|
||||
target = action_dict.pop(TField.TARGET, default_target[TField.TARGET])
|
||||
targets = {TField.TARGET: target}
|
||||
if action_dict.get(TField.SOURCE):
|
||||
targets[TField.SOURCE] = action_dict.pop(TField.SOURCE)
|
||||
return ActionSpecs(action_id, action_type, targets, action_dict)
|
@ -14,13 +14,21 @@
|
||||
from oslo_log import log
|
||||
|
||||
from vitrage.evaluator.actions.base import ActionType
|
||||
from vitrage.evaluator import base
|
||||
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.template_loader import TemplateLoader
|
||||
from vitrage.evaluator.template_loading.template_loader_v3 import\
|
||||
TemplateLoader as V3TemplateLoader
|
||||
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_loading.v3.action_loader import ActionLoader \
|
||||
as V3ActionLoader
|
||||
from vitrage.evaluator.template_schema_factory import TemplateSchemaFactory
|
||||
from vitrage.evaluator.template_validation.content.\
|
||||
template_content_validator_v3 import ContentValidator as V3ContentValidator
|
||||
from vitrage.evaluator.template_validation.content.v1.\
|
||||
add_causal_relationship_validator import AddCausalRelationshipValidator
|
||||
from vitrage.evaluator.template_validation.content.v1.definitions_validator \
|
||||
@ -43,6 +51,8 @@ from vitrage.evaluator.template_validation.content.v2.\
|
||||
V2ExecuteMistralValidator
|
||||
from vitrage.evaluator.template_validation.content.v2.metadata_validator \
|
||||
import MetadataValidator as V2MetadataValidator
|
||||
from vitrage.evaluator.template_validation.template_syntax_validator_v3 import\
|
||||
SyntaxValidator as V3SyntaxValidator
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
@ -61,6 +71,7 @@ class TemplateSchema1(object):
|
||||
}
|
||||
|
||||
self.loaders = {
|
||||
base.TEMPLATE_LOADER: TemplateLoader(),
|
||||
ActionType.ADD_CAUSAL_RELATIONSHIP: ActionLoader(),
|
||||
ActionType.EXECUTE_MISTRAL: ExecuteMistralLoader(),
|
||||
ActionType.MARK_DOWN: ActionLoader(),
|
||||
@ -88,6 +99,30 @@ class TemplateSchema2(TemplateSchema1):
|
||||
return '2'
|
||||
|
||||
|
||||
class TemplateSchema3(object):
|
||||
|
||||
def __init__(self):
|
||||
self.validators = {
|
||||
TemplateFields.METADATA: V2MetadataValidator,
|
||||
base.SYNTAX: V3SyntaxValidator,
|
||||
base.CONTENT: V3ContentValidator,
|
||||
}
|
||||
self.loaders = {
|
||||
base.TEMPLATE_LOADER: V3TemplateLoader(),
|
||||
ActionType.ADD_CAUSAL_RELATIONSHIP: V3ActionLoader(),
|
||||
ActionType.EXECUTE_MISTRAL: V3ActionLoader(),
|
||||
ActionType.MARK_DOWN: V3ActionLoader(),
|
||||
ActionType.RAISE_ALARM: V3ActionLoader(),
|
||||
ActionType.SET_STATE: V3ActionLoader(),
|
||||
}
|
||||
|
||||
self.functions = {GET_ATTR: get_attr}
|
||||
|
||||
def version(self):
|
||||
return '3'
|
||||
|
||||
|
||||
def init_template_schemas():
|
||||
TemplateSchemaFactory.register_template_schema('1', TemplateSchema1())
|
||||
TemplateSchemaFactory.register_template_schema('2', TemplateSchema2())
|
||||
TemplateSchemaFactory.register_template_schema('3', TemplateSchema3())
|
||||
|
@ -12,8 +12,12 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
from oslo_log import log
|
||||
from vitrage.evaluator.base import CONTENT
|
||||
from vitrage.evaluator.base import SYNTAX
|
||||
from voluptuous import Error as VoluptuousError
|
||||
|
||||
from vitrage.evaluator.template_fields import TemplateFields
|
||||
from vitrage.evaluator.template_validation import base
|
||||
from vitrage.evaluator.template_validation.content.base import \
|
||||
get_template_schema
|
||||
from vitrage.evaluator.template_validation.content.template_content_validator \
|
||||
@ -28,6 +32,23 @@ LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
def validate_template(template, def_templates):
|
||||
result, template_schema = get_template_schema(template)
|
||||
if not result.is_valid_config:
|
||||
return result
|
||||
if template_schema.version() < '3':
|
||||
return _validate_template_v1_v2(template, def_templates)
|
||||
|
||||
try:
|
||||
template_schema.validators[SYNTAX].validate(template)
|
||||
template_schema.validators[CONTENT].validate(template)
|
||||
except base.ValidationError as e:
|
||||
return base.get_custom_fault_result(e.code, e.details)
|
||||
except VoluptuousError as e:
|
||||
return base.get_custom_fault_result(base.get_status_code(e), str(e))
|
||||
return base.get_correct_result()
|
||||
|
||||
|
||||
def _validate_template_v1_v2(template, def_templates):
|
||||
result = syntax_validation(template)
|
||||
if not result.is_valid_config:
|
||||
LOG.error('Unable to load template, syntax error: %s' % result.comment)
|
||||
|
@ -13,12 +13,21 @@
|
||||
# under the License.
|
||||
from collections import namedtuple
|
||||
from vitrage.evaluator.template_validation.status_messages import status_msgs
|
||||
RESULT_DESCRIPTION = 'Template syntax validation'
|
||||
EXCEPTION = 'exception'
|
||||
|
||||
Result = namedtuple('Result', ['description', 'is_valid_config', 'status_code',
|
||||
'comment'])
|
||||
|
||||
|
||||
def get_correct_result(description):
|
||||
class ValidationError(Exception):
|
||||
def __init__(self, code, *args):
|
||||
self.code = code
|
||||
self.details = ''
|
||||
self.details = ','.join(str(arg) for arg in args)
|
||||
|
||||
|
||||
def get_correct_result(description=RESULT_DESCRIPTION):
|
||||
return Result(description, True, 0, status_msgs[0])
|
||||
|
||||
|
||||
@ -30,3 +39,15 @@ def get_fault_result(description, code, msg=None):
|
||||
if msg:
|
||||
return Result(description, False, code, msg)
|
||||
return Result(description, False, code, status_msgs[code])
|
||||
|
||||
|
||||
def get_custom_fault_result(code, msg):
|
||||
return Result('Template validation', False, code,
|
||||
status_msgs[code] + ' ' + msg)
|
||||
|
||||
|
||||
def get_status_code(voluptuous_error):
|
||||
prefix = str(voluptuous_error).split(' ')[0].strip()
|
||||
if prefix.isdigit():
|
||||
return int(prefix)
|
||||
return 4
|
||||
|
@ -0,0 +1,135 @@
|
||||
# Copyright 2019 - 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 re
|
||||
|
||||
from oslo_log import log
|
||||
from sympy import Not
|
||||
from sympy import Symbol
|
||||
|
||||
from vitrage.evaluator.base import get_template_schema
|
||||
from vitrage.evaluator import condition as dnf
|
||||
|
||||
from vitrage.evaluator.template_fields import TemplateFields
|
||||
from vitrage.evaluator.template_loading.template_loader_v3 import \
|
||||
TemplateLoader as V3TemplateLoader
|
||||
from vitrage.evaluator.template_validation.base import ValidationError
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
RELATION = 'relationship'
|
||||
|
||||
|
||||
class ContentValidator(object):
|
||||
|
||||
@staticmethod
|
||||
def validate(template):
|
||||
_validate_entities_regex(template)
|
||||
_validate_conditions(template)
|
||||
|
||||
# As part of validation, when it is finished,
|
||||
# we try to load the template, as some validations can only be
|
||||
# executed at loading phase
|
||||
schema = get_template_schema(template)
|
||||
V3TemplateLoader().load(schema, template)
|
||||
|
||||
|
||||
def _validate_entities_regex(template):
|
||||
for entity in template[TemplateFields.ENTITIES].values():
|
||||
for key, value in entity.items():
|
||||
if key.lower().endswith(TemplateFields.REGEX):
|
||||
try:
|
||||
re.compile(value)
|
||||
except Exception:
|
||||
raise ValidationError(47, key, value)
|
||||
|
||||
|
||||
def _validate_conditions(template):
|
||||
"""Validate conditions
|
||||
|
||||
'alarm_1 [on] host AND host [contains] instance AND alarm_2 [on] instance
|
||||
AND host'
|
||||
In this condition , replace all occurrences of 'source [label] target'
|
||||
so to create :
|
||||
'relationship AND relationship AND relationship AND host'
|
||||
"""
|
||||
for scenario in template[TemplateFields.SCENARIOS]:
|
||||
condition = scenario[TemplateFields.CONDITION]
|
||||
_validate_condition_entity_ids(template, condition)
|
||||
_validate_not_condition(condition)
|
||||
|
||||
|
||||
def _validate_condition_entity_ids(template, condition):
|
||||
curr_str = ' ' + condition + ' '
|
||||
|
||||
# Remove all [label] occurrences
|
||||
edge_labels_re = r'\s+\[\s*\w+\s*\]\s+'
|
||||
curr_str = re.sub(edge_labels_re, ' ', curr_str)
|
||||
if '[' in curr_str or ']' in curr_str:
|
||||
raise ValidationError(85, condition)
|
||||
|
||||
# Remove all operator occurrences
|
||||
operators_re = r'\b(AND|OR|NOT|and|or|not)\b'
|
||||
curr_str = re.sub(operators_re, '', curr_str)
|
||||
|
||||
# Remove all entity occurrences
|
||||
for entity_id in template[TemplateFields.ENTITIES].keys():
|
||||
entity_id_regex = r'\b' + entity_id + r'\b'
|
||||
curr_str = re.sub(entity_id_regex, ' ', curr_str)
|
||||
|
||||
# Remove all parentheses occurrences
|
||||
curr_str = curr_str.replace('(', '')
|
||||
curr_str = curr_str.replace(')', '')
|
||||
|
||||
# Remaining string should be empty
|
||||
if curr_str.strip():
|
||||
raise ValidationError(10200, condition, curr_str)
|
||||
|
||||
|
||||
def _validate_not_condition(condition):
|
||||
"""Not operator validation
|
||||
|
||||
1. Not operator can appear only on relationships.
|
||||
2. There must be at least one positive term
|
||||
"""
|
||||
regex = r"(\w+)\s*\[\s*(\w+)\s*\]\s*(\w+)"
|
||||
preprocessed_condition = re.sub(regex, RELATION, condition)
|
||||
|
||||
try:
|
||||
dnf_condition = dnf.convert_to_dnf_format(preprocessed_condition)
|
||||
_validate_not_condition_relationships_recursive(dnf_condition)
|
||||
dnf_condition = dnf.parse_condition(preprocessed_condition)
|
||||
_validate_positive_term_in_condition(dnf_condition)
|
||||
except ValidationError as e:
|
||||
raise ValidationError(e.code, e.details, condition)
|
||||
except Exception as e:
|
||||
raise ValidationError(85, condition, e)
|
||||
|
||||
|
||||
def _validate_not_condition_relationships_recursive(dnf_result):
|
||||
if isinstance(dnf_result, Not):
|
||||
for arg in dnf_result.args:
|
||||
if isinstance(arg, Symbol) and not str(arg).startswith(RELATION):
|
||||
raise ValidationError(86, arg)
|
||||
else:
|
||||
_validate_not_condition_relationships_recursive(arg)
|
||||
return
|
||||
|
||||
for arg in dnf_result.args:
|
||||
if not isinstance(arg, Symbol):
|
||||
_validate_not_condition_relationships_recursive(arg)
|
||||
|
||||
|
||||
def _validate_positive_term_in_condition(dnf_condition):
|
||||
if not dnf.is_condition_include_positive_clause(dnf_condition):
|
||||
raise ValidationError(134)
|
@ -54,8 +54,8 @@ status_msgs = {
|
||||
80: 'scenarios is a mandatory section.',
|
||||
81: 'At least one scenario must be defined.',
|
||||
82: 'scenario field is required.',
|
||||
83: 'Entity definition must contain condition field.',
|
||||
84: 'Entity definition must contain actions field.',
|
||||
83: 'Scenario definition must contain condition field.',
|
||||
84: 'Scenario definition must contain actions field.',
|
||||
85: 'Failed to convert condition.',
|
||||
86: 'Not operator can be used only on relationships.',
|
||||
|
||||
@ -90,7 +90,7 @@ status_msgs = {
|
||||
136: 'Input parameters for the Mistral workflow in execute_mistral action '
|
||||
'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 '
|
||||
138: 'Only open or close parenthesis exists. Did you try to use '
|
||||
'a function?',
|
||||
|
||||
# def_templates status messages 140-159
|
||||
@ -99,4 +99,14 @@ status_msgs = {
|
||||
142: 'Trying to include a template that does not exist',
|
||||
143: 'A template definition file cannot contain \'includes\' or '
|
||||
'\'scenarios\' blocks',
|
||||
|
||||
10100: 'Action must contain a \'target\' property',
|
||||
10101: 'Action \'target\' must match an entity id',
|
||||
10102: 'Action must contain a \'source\' property',
|
||||
10103: 'Action \'source\' must match an entity id',
|
||||
10104: 'raise_alarm action must contain an \'alarm_name\' property',
|
||||
10105: 'execute_mistral action must contain \'workflow\' property',
|
||||
10107: 'The property \'causing_alarm\' in raise_alarm action must match an'
|
||||
' entity id',
|
||||
10200: 'Condition contains an unknown entity id',
|
||||
}
|
||||
|
@ -28,6 +28,7 @@ from vitrage.evaluator.actions.base import action_types
|
||||
from vitrage.evaluator.template_fields import TemplateFields
|
||||
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_status_code
|
||||
from vitrage.evaluator.template_validation.status_messages import status_msgs
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
@ -145,7 +146,7 @@ def _validate_name_schema(schema, name):
|
||||
schema(name)
|
||||
except Error as e:
|
||||
|
||||
status_code = _get_status_code(e)
|
||||
status_code = get_status_code(e)
|
||||
if status_code:
|
||||
msg = status_msgs[status_code]
|
||||
else:
|
||||
@ -319,28 +320,14 @@ def _validate_dict_schema(schema, value):
|
||||
try:
|
||||
schema(value)
|
||||
except Error as e:
|
||||
|
||||
status_code = _get_status_code(e)
|
||||
if status_code:
|
||||
msg = status_msgs[status_code]
|
||||
else:
|
||||
# General syntax error
|
||||
status_code = 4
|
||||
msg = status_msgs[4] + str(e)
|
||||
|
||||
status_code = get_status_code(e)
|
||||
msg = status_msgs[status_code] + str(e)
|
||||
LOG.error('%s status code: %s' % (msg, status_code))
|
||||
return get_fault_result(RESULT_DESCRIPTION, status_code, msg)
|
||||
|
||||
return get_correct_result(RESULT_DESCRIPTION)
|
||||
|
||||
|
||||
def _get_status_code(e):
|
||||
prefix = str(e).split(' ')[0].strip()
|
||||
if prefix.isdigit():
|
||||
return int(prefix)
|
||||
return None
|
||||
|
||||
|
||||
def _validate_template_id_value(msg=None):
|
||||
def f(v):
|
||||
if re.match("_*[a-zA-Z]+\\w*", str(v)):
|
||||
|
@ -0,0 +1,137 @@
|
||||
# Copyright 2019 - 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 re
|
||||
import six
|
||||
|
||||
from oslo_log import log
|
||||
from voluptuous import Any
|
||||
from voluptuous import In
|
||||
from voluptuous import Invalid
|
||||
from voluptuous import Optional
|
||||
from voluptuous import Required
|
||||
from voluptuous import Schema
|
||||
|
||||
from vitrage.common.constants import TemplateTypes
|
||||
from vitrage.evaluator.actions.base import ActionType
|
||||
from vitrage.evaluator.actions.recipes.execute_mistral import INPUT
|
||||
from vitrage.evaluator.actions.recipes.execute_mistral import WORKFLOW
|
||||
from vitrage.evaluator.base import is_function
|
||||
from vitrage.evaluator.template_fields import TemplateFields as TF
|
||||
from vitrage.evaluator.template_schema_factory import TemplateSchemaFactory
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
any_str = Any(str, six.text_type)
|
||||
|
||||
|
||||
class SyntaxValidator(object):
|
||||
|
||||
@staticmethod
|
||||
def validate(template):
|
||||
Schema({
|
||||
Required(TF.ENTITIES, msg=10000): _entities_schema(),
|
||||
Required(TF.METADATA, msg=62): _metadata_schema(),
|
||||
Required(TF.SCENARIOS, msg=80): _scenarios_schema(template),
|
||||
})(template)
|
||||
|
||||
|
||||
def _entities_schema():
|
||||
return Schema({
|
||||
any_str: Schema({
|
||||
any_str: any_str,
|
||||
})})
|
||||
|
||||
|
||||
def _metadata_schema():
|
||||
return Schema({
|
||||
Required(TF.VERSION, msg=63): In(
|
||||
TemplateSchemaFactory.supported_versions()),
|
||||
Required(TF.NAME, msg=60): any_str,
|
||||
TF.DESCRIPTION: any_str,
|
||||
Required(TF.TYPE, msg=64): In(TemplateTypes.types(), msg=64),
|
||||
})
|
||||
|
||||
|
||||
def _scenarios_schema(template):
|
||||
|
||||
return Schema([
|
||||
Schema({
|
||||
Required(TF.ACTIONS, msg=84): Schema([Any(
|
||||
_raise_alarm_schema(template),
|
||||
_set_state_schema(template),
|
||||
_add_causal_relationship_schema(template),
|
||||
_mark_down_schema(template),
|
||||
_execute_mistral_schema(),
|
||||
)]),
|
||||
Required(TF.CONDITION, msg=83): any_str,
|
||||
})])
|
||||
|
||||
|
||||
def _raise_alarm_schema(template):
|
||||
return Schema({
|
||||
Optional(ActionType.RAISE_ALARM): Schema({
|
||||
Required(TF.SEVERITY, msg=126): any_str,
|
||||
Required(TF.TARGET, msg=10100):
|
||||
In(template.get(TF.ENTITIES, {}).keys(), msg=10101),
|
||||
Required(TF.ALARM_NAME, msg=10104): any_str,
|
||||
Optional(TF.CAUSING_ALARM):
|
||||
In(template.get(TF.ENTITIES, {}).keys(), msg=10107),
|
||||
})})
|
||||
|
||||
|
||||
def _set_state_schema(template):
|
||||
return Schema({
|
||||
Optional(ActionType.SET_STATE): Schema({
|
||||
Required(TF.STATE, msg=128): any_str,
|
||||
Required(TF.TARGET, msg=10100):
|
||||
In(template.get(TF.ENTITIES, {}).keys(), msg=10101),
|
||||
})})
|
||||
|
||||
|
||||
def _add_causal_relationship_schema(template):
|
||||
return Schema({
|
||||
Optional(ActionType.ADD_CAUSAL_RELATIONSHIP): Schema({
|
||||
Required(TF.SOURCE, msg=10102):
|
||||
In(template.get(TF.ENTITIES, {}).keys(), msg=10103),
|
||||
Required(TF.TARGET, msg=10100):
|
||||
In(template.get(TF.ENTITIES, {}).keys(), msg=10101),
|
||||
})})
|
||||
|
||||
|
||||
def _mark_down_schema(template):
|
||||
return Schema({
|
||||
Optional(ActionType.MARK_DOWN): Schema({
|
||||
Required(TF.TARGET, msg=10100):
|
||||
In(template.get(TF.ENTITIES, {}).keys(), msg=10101),
|
||||
})})
|
||||
|
||||
|
||||
def _execute_mistral_schema():
|
||||
|
||||
return Schema({
|
||||
Optional(ActionType.EXECUTE_MISTRAL): Schema({
|
||||
Required(WORKFLOW, msg=10105): any_str,
|
||||
Optional(INPUT): Schema({
|
||||
any_str: IsProperFunction()}
|
||||
)})})
|
||||
|
||||
|
||||
class IsProperFunction(object):
|
||||
"""If Value contains parentheses, check it is a proper function call"""
|
||||
|
||||
def __call__(self, v):
|
||||
if re.findall('[(),]', v) and not is_function(v):
|
||||
raise Invalid(138)
|
||||
return v
|
@ -12,7 +12,6 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from vitrage.evaluator.template_fields import TemplateFields as Fields
|
||||
from vitrage.graph.utils import check_property_with_regex
|
||||
|
||||
|
||||
@ -34,8 +33,9 @@ def check_filter(data, attr_filter, *args):
|
||||
if not isinstance(content, list):
|
||||
content = [content]
|
||||
if data.get(key) not in content:
|
||||
if key.lower().endswith(Fields.REGEX):
|
||||
new_key = key[:-len(Fields.REGEX)]
|
||||
# import of .regex constant removed, as it was circular
|
||||
if key.lower().endswith('.regex'):
|
||||
new_key = key[:-len('.regex')]
|
||||
if not check_property_with_regex(new_key, content[0],
|
||||
data):
|
||||
return False
|
||||
|
@ -17,6 +17,7 @@ import copy
|
||||
|
||||
from vitrage.common.constants import EdgeProperties
|
||||
from vitrage.common.constants import VertexProperties as VProps
|
||||
from vitrage.datasources import NOVA_HOST_DATASOURCE
|
||||
from vitrage.graph import Direction
|
||||
from vitrage.graph.driver.networkx_graph import NXGraph
|
||||
from vitrage.graph import Edge
|
||||
@ -131,6 +132,10 @@ class GraphGenerator(object):
|
||||
for i in range(n):
|
||||
v = self._file_to_vertex(neighbor_props_file, i)
|
||||
v[VProps.NAME] = v[VProps.NAME] + "-" + source_v[VProps.NAME]
|
||||
|
||||
if v.get(VProps.VITRAGE_TYPE) == NOVA_HOST_DATASOURCE:
|
||||
v[VProps.ID] = v.get(VProps.NAME)
|
||||
|
||||
created_vertices.append(v)
|
||||
g.add_vertex(v)
|
||||
if direction == Direction.OUT:
|
||||
|
@ -0,0 +1,63 @@
|
||||
metadata:
|
||||
version: 3
|
||||
name: valid actions
|
||||
description: zabbix alarm for network interface and ssh affects host instances
|
||||
type: standard
|
||||
entities:
|
||||
host_network_alarm:
|
||||
type: zabbix
|
||||
rawtext: host network interface is down
|
||||
host_ssh_alarm:
|
||||
type: zabbix
|
||||
rawtext: host ssh is down
|
||||
instance:
|
||||
type: nova.instance
|
||||
host:
|
||||
type: nova.host
|
||||
foo:
|
||||
name.regex: kuku
|
||||
scenarios:
|
||||
- condition: host_ssh_alarm [ on ] host
|
||||
actions:
|
||||
- set_state:
|
||||
state: ERROR
|
||||
target: host
|
||||
- raise_alarm:
|
||||
target: host
|
||||
alarm_name: ddd
|
||||
severity: WARNING
|
||||
- mark_down:
|
||||
target: host
|
||||
- execute_mistral:
|
||||
workflow: wf_1234
|
||||
input:
|
||||
farewell: get_attr(host, name) bla bla
|
||||
- condition: host_network_alarm [ on ] host AND host_ssh_alarm [ on ] host
|
||||
actions:
|
||||
- add_causal_relationship:
|
||||
source: host_network_alarm
|
||||
target: host_ssh_alarm
|
||||
- condition: host_ssh_alarm [ on ] host AND host [ contains ] instance
|
||||
actions:
|
||||
- raise_alarm:
|
||||
target: instance
|
||||
alarm_name: instance is down
|
||||
severity: WARNING
|
||||
causing_alarm: host_ssh_alarm
|
||||
- set_state:
|
||||
state: SUBOPTIMAL
|
||||
target: instance
|
||||
- condition: host AND NOT host_ssh_alarm [ on ] host or host [ contains ] instance
|
||||
actions:
|
||||
- mark_down:
|
||||
target: host
|
||||
- condition: host
|
||||
actions:
|
||||
- mark_down:
|
||||
target: host
|
||||
- condition: host AND NOT (host_ssh_alarm [ on ] host or host [ contains ] instance)
|
||||
actions:
|
||||
- mark_down:
|
||||
target: host
|
||||
|
||||
|
@ -0,0 +1,58 @@
|
||||
metadata:
|
||||
version: 3
|
||||
name: aaa
|
||||
description: aaa
|
||||
type: standard
|
||||
entities:
|
||||
instance:
|
||||
type: nova.instance
|
||||
host:
|
||||
type: nova.host
|
||||
host_ssh_alarm:
|
||||
name: ssh alarm
|
||||
type: zabbix
|
||||
scenarios:
|
||||
- condition: host
|
||||
actions:
|
||||
- mark_down:
|
||||
target: host
|
||||
- condition: host [ contains] instance AND host [contains] instance
|
||||
actions:
|
||||
- mark_down:
|
||||
target: host
|
||||
- condition: host AND NOT host [ contains ] instance
|
||||
actions:
|
||||
- mark_down:
|
||||
target: host
|
||||
- condition: host and not host [ contains ] instance or host
|
||||
actions:
|
||||
- mark_down:
|
||||
target: host
|
||||
- condition: host OR host or host and instance AND instance
|
||||
actions:
|
||||
- mark_down:
|
||||
target: host
|
||||
- condition: (host and NOT host [ contains ] instance) or host [ contains ] instance
|
||||
actions:
|
||||
- mark_down:
|
||||
target: host
|
||||
- condition: (host [contains] instance)
|
||||
actions:
|
||||
- mark_down:
|
||||
target: host
|
||||
- condition: (host [contains] instance AND host [contains] instance)
|
||||
actions:
|
||||
- mark_down:
|
||||
target: host
|
||||
- condition: ( host [contains] instance OR host [contains] instance )
|
||||
actions:
|
||||
- mark_down:
|
||||
target: host
|
||||
- condition: ( host OR host [contains] instance ) AND (instance OR host [contains] instance)
|
||||
actions:
|
||||
- mark_down:
|
||||
target: host
|
||||
- condition: host AND NOT host [contains] instance
|
||||
actions:
|
||||
- mark_down:
|
||||
target: host
|
@ -0,0 +1,21 @@
|
||||
# Copyright 2019 - 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.base import get_template_schema
|
||||
from vitrage.evaluator.base import TEMPLATE_LOADER
|
||||
|
||||
|
||||
def get_template_data(template, def_templates=None):
|
||||
schema = get_template_schema(template)
|
||||
template_loader = schema.loaders[TEMPLATE_LOADER]
|
||||
return template_loader.load(schema, template, def_templates)
|
@ -0,0 +1,450 @@
|
||||
# Copyright 2019 - 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_config import cfg
|
||||
from vitrage.evaluator.template_data import ActionSpecs
|
||||
from vitrage.evaluator.template_data import EdgeDescription
|
||||
from vitrage.evaluator.template_data import Scenario
|
||||
from vitrage.graph.driver.networkx_graph import NXGraph
|
||||
from vitrage.graph import Edge
|
||||
from vitrage.graph import Vertex
|
||||
from vitrage.tests.base import BaseTest
|
||||
from vitrage.tests.functional.test_configuration import TestConfiguration
|
||||
from vitrage.tests.mocks.utils import get_resources_dir
|
||||
from vitrage.tests.unit.evaluator import get_template_data
|
||||
from vitrage.utils import file as file_utils
|
||||
|
||||
|
||||
class TemplateLoaderV3Test(BaseTest, TestConfiguration):
|
||||
|
||||
expected_entities = {
|
||||
'host_ssh_alarm': Vertex('host_ssh_alarm', {
|
||||
'rawtext': 'host ssh is down', 'vitrage_type': 'zabbix'}),
|
||||
'host': Vertex('host', {'vitrage_type': 'nova.host'}),
|
||||
'foo': Vertex('foo', {'name.regex': 'kuku'}),
|
||||
'host_network_alarm': Vertex('host_network_alarm', {
|
||||
'rawtext': 'host network interface is down',
|
||||
'vitrage_type': 'zabbix',
|
||||
}),
|
||||
'instance': Vertex('instance', {'vitrage_type': 'nova.instance'}),
|
||||
}
|
||||
expected_relationships = {
|
||||
'host_ssh_alarm__on__host': EdgeDescription(
|
||||
Edge('host_ssh_alarm', 'host', 'on', {}),
|
||||
Vertex('host_ssh_alarm', {
|
||||
'rawtext': 'host ssh is down', 'vitrage_type': 'zabbix'
|
||||
}),
|
||||
Vertex('host', {'vitrage_type': 'nova.host'})),
|
||||
'host__contains__instance': EdgeDescription(
|
||||
Edge('host', 'instance', 'contains', {}),
|
||||
Vertex('host', {'vitrage_type': 'nova.host'}),
|
||||
Vertex('instance', {'vitrage_type': 'nova.instance'})),
|
||||
'host_network_alarm__on__host': EdgeDescription(
|
||||
Edge('host_network_alarm', 'host', 'on', {}),
|
||||
Vertex('host_network_alarm', {
|
||||
'rawtext': 'host network interface is down',
|
||||
'vitrage_type': 'zabbix'
|
||||
}),
|
||||
Vertex('host', {'vitrage_type': 'nova.host'})),
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TemplateLoaderV3Test, cls).setUpClass()
|
||||
cls.conf = cfg.ConfigOpts()
|
||||
cls.add_db(cls.conf)
|
||||
|
||||
def _load_scenarios(self, file=None, content=None):
|
||||
if file and not content:
|
||||
content = self._get_yaml(file)
|
||||
return get_template_data(content).scenarios
|
||||
|
||||
def _assert_scenario_equal(self, expected, observed):
|
||||
|
||||
# Basic
|
||||
self.assertEqual(expected.id, observed.id)
|
||||
self.assertEqual(expected.version, observed.version)
|
||||
self.assertEqual(expected.condition, observed.condition) # is None
|
||||
|
||||
# Actions
|
||||
self.assertEqual(len(expected.actions), len(observed.actions),
|
||||
'actions count')
|
||||
for j in range(len(expected.actions)):
|
||||
expected_action = expected.actions[j]
|
||||
observed_action = observed.actions[j]
|
||||
self.assertEqual(expected_action.id, observed_action.id)
|
||||
self.assertEqual(expected_action.type, observed_action.type)
|
||||
self.assertEqual(expected_action.properties,
|
||||
observed_action.properties)
|
||||
if expected_action.type == 'execute_mistral':
|
||||
continue
|
||||
self.assertEqual(expected_action.targets, observed_action.targets)
|
||||
|
||||
# Subgraphs
|
||||
self.assertEqual(len(expected.subgraphs), len(observed.subgraphs),
|
||||
'subgraphs count')
|
||||
for j in range(len(expected.subgraphs)):
|
||||
expected_subgraph = expected.subgraphs[j]
|
||||
observed_subgraph = observed.subgraphs[j]
|
||||
self.assert_graph_equal(expected_subgraph, observed_subgraph)
|
||||
|
||||
# Entities
|
||||
self.assert_dict_equal(expected.entities, observed.entities,
|
||||
'entities comparison')
|
||||
self.assert_dict_equal(expected.relationships, observed.relationships,
|
||||
'relationships comparison')
|
||||
|
||||
@staticmethod
|
||||
def _get_yaml(filename):
|
||||
path = '%s/templates/v3_templates/%s' % (get_resources_dir(), filename)
|
||||
return file_utils.load_yaml_file(path)
|
||||
|
||||
def test_scenarios(self):
|
||||
observed_scenarios = self._load_scenarios('valid_actions.yaml')
|
||||
self.assertEqual(6, len(observed_scenarios), 'scenarios count')
|
||||
|
||||
def test_scenario_0(self):
|
||||
observed_scenarios = self._load_scenarios('valid_actions.yaml')
|
||||
expected_scenario = Scenario(
|
||||
'valid actions-scenario0',
|
||||
'3',
|
||||
None,
|
||||
[
|
||||
ActionSpecs(
|
||||
'valid actions-scenario0-action0',
|
||||
'set_state',
|
||||
{'target': 'host'},
|
||||
{'state': 'ERROR'}),
|
||||
ActionSpecs(
|
||||
'valid actions-scenario0-action1',
|
||||
'raise_alarm',
|
||||
{'target': 'host'},
|
||||
{'severity': 'WARNING', 'alarm_name': 'ddd'}),
|
||||
ActionSpecs(
|
||||
'valid actions-scenario0-action2',
|
||||
'mark_down',
|
||||
{'target': 'host'},
|
||||
{}),
|
||||
ActionSpecs(
|
||||
'valid actions-scenario0-action3',
|
||||
'execute_mistral',
|
||||
{'target': 'host'},
|
||||
{'input': {'farewell': 'get_attr(host, name) bla bla'},
|
||||
'workflow': 'wf_1234'}),
|
||||
],
|
||||
[
|
||||
NXGraph(
|
||||
vertices=[
|
||||
Vertex('host_ssh_alarm',
|
||||
{
|
||||
'rawtext': 'host ssh is down',
|
||||
'vitrage_is_placeholder': False,
|
||||
'vitrage_type': 'zabbix',
|
||||
'vitrage_is_deleted': False,
|
||||
}),
|
||||
Vertex('host',
|
||||
{
|
||||
'vitrage_is_placeholder': False,
|
||||
'vitrage_type': 'nova.host',
|
||||
'vitrage_is_deleted': False,
|
||||
})
|
||||
],
|
||||
edges=[
|
||||
Edge('host_ssh_alarm', 'host', 'on',
|
||||
{
|
||||
'vitrage_is_deleted': False,
|
||||
'negative_condition': False
|
||||
})
|
||||
])
|
||||
],
|
||||
TemplateLoaderV3Test.expected_entities,
|
||||
TemplateLoaderV3Test.expected_relationships)
|
||||
self._assert_scenario_equal(
|
||||
expected_scenario,
|
||||
observed_scenarios[0])
|
||||
|
||||
def test_scenario_1(self):
|
||||
observed_scenarios = self._load_scenarios('valid_actions.yaml')
|
||||
expected_scenario = Scenario(
|
||||
'valid actions-scenario1',
|
||||
'3',
|
||||
None,
|
||||
[
|
||||
ActionSpecs(
|
||||
'valid actions-scenario1-action0',
|
||||
'add_causal_relationship',
|
||||
{
|
||||
'target': 'host_ssh_alarm',
|
||||
'source': 'host_network_alarm',
|
||||
},
|
||||
{}),
|
||||
],
|
||||
[
|
||||
NXGraph(
|
||||
vertices=[
|
||||
Vertex('host_ssh_alarm',
|
||||
{
|
||||
'rawtext': 'host ssh is down',
|
||||
'vitrage_is_placeholder': False,
|
||||
'vitrage_type': 'zabbix',
|
||||
'vitrage_is_deleted': False,
|
||||
}),
|
||||
Vertex('host_network_alarm',
|
||||
{
|
||||
'rawtext': 'host network interface is down',
|
||||
'vitrage_is_placeholder': False,
|
||||
'vitrage_type': 'zabbix',
|
||||
'vitrage_is_deleted': False,
|
||||
}),
|
||||
Vertex('host',
|
||||
{
|
||||
'vitrage_is_placeholder': False,
|
||||
'vitrage_type': 'nova.host',
|
||||
'vitrage_is_deleted': False,
|
||||
})
|
||||
],
|
||||
edges=[
|
||||
Edge('host_ssh_alarm', 'host', 'on',
|
||||
{
|
||||
'vitrage_is_deleted': False,
|
||||
'negative_condition': False
|
||||
}),
|
||||
Edge('host_network_alarm', 'host', 'on',
|
||||
{
|
||||
'vitrage_is_deleted': False,
|
||||
'negative_condition': False
|
||||
})
|
||||
])
|
||||
],
|
||||
TemplateLoaderV3Test.expected_entities,
|
||||
TemplateLoaderV3Test.expected_relationships)
|
||||
self._assert_scenario_equal(
|
||||
expected_scenario,
|
||||
observed_scenarios[1])
|
||||
|
||||
def test_scenario_2(self):
|
||||
observed_scenarios = self._load_scenarios('valid_actions.yaml')
|
||||
expected_scenario = Scenario(
|
||||
'valid actions-scenario2',
|
||||
'3',
|
||||
None,
|
||||
[
|
||||
ActionSpecs(
|
||||
'valid actions-scenario2-action0',
|
||||
'raise_alarm',
|
||||
{'target': 'instance'},
|
||||
{
|
||||
'severity': 'WARNING',
|
||||
'alarm_name': 'instance is down',
|
||||
'causing_alarm': 'host_ssh_alarm',
|
||||
}),
|
||||
ActionSpecs(
|
||||
'valid actions-scenario2-action1',
|
||||
'set_state',
|
||||
{'target': 'instance'},
|
||||
{'state': 'SUBOPTIMAL'}),
|
||||
],
|
||||
[
|
||||
NXGraph(
|
||||
vertices=[
|
||||
Vertex('host_ssh_alarm',
|
||||
{
|
||||
'rawtext': 'host ssh is down',
|
||||
'vitrage_is_placeholder': False,
|
||||
'vitrage_type': 'zabbix',
|
||||
'vitrage_is_deleted': False,
|
||||
}),
|
||||
Vertex('instance',
|
||||
{
|
||||
'vitrage_is_placeholder': False,
|
||||
'vitrage_type': 'nova.instance',
|
||||
'vitrage_is_deleted': False,
|
||||
}),
|
||||
Vertex('host',
|
||||
{
|
||||
'vitrage_is_placeholder': False,
|
||||
'vitrage_type': 'nova.host',
|
||||
'vitrage_is_deleted': False,
|
||||
}),
|
||||
],
|
||||
edges=[
|
||||
Edge('host_ssh_alarm', 'host', 'on',
|
||||
{
|
||||
'vitrage_is_deleted': False,
|
||||
'negative_condition': False
|
||||
}),
|
||||
Edge('host', 'instance', 'contains',
|
||||
{
|
||||
'vitrage_is_deleted': False,
|
||||
'negative_condition': False
|
||||
})
|
||||
])
|
||||
],
|
||||
TemplateLoaderV3Test.expected_entities,
|
||||
TemplateLoaderV3Test.expected_relationships)
|
||||
self._assert_scenario_equal(
|
||||
expected_scenario,
|
||||
observed_scenarios[2])
|
||||
|
||||
def test_scenario_3(self):
|
||||
observed_scenarios = self._load_scenarios('valid_actions.yaml')
|
||||
expected_scenario = Scenario(
|
||||
'valid actions-scenario3',
|
||||
'3',
|
||||
None,
|
||||
[
|
||||
ActionSpecs(
|
||||
'valid actions-scenario3-action0',
|
||||
'mark_down',
|
||||
{'target': 'host'},
|
||||
{}),
|
||||
],
|
||||
[
|
||||
NXGraph(
|
||||
vertices=[
|
||||
Vertex('instance',
|
||||
{
|
||||
'vitrage_is_placeholder': False,
|
||||
'vitrage_type': 'nova.instance',
|
||||
'vitrage_is_deleted': False,
|
||||
}),
|
||||
Vertex('host',
|
||||
{
|
||||
'vitrage_is_placeholder': False,
|
||||
'vitrage_type': 'nova.host',
|
||||
'vitrage_is_deleted': False,
|
||||
}),
|
||||
],
|
||||
edges=[
|
||||
Edge('host', 'instance', 'contains',
|
||||
{
|
||||
'vitrage_is_deleted': False,
|
||||
'negative_condition': False
|
||||
})
|
||||
]),
|
||||
NXGraph(
|
||||
vertices=[
|
||||
Vertex('host_ssh_alarm',
|
||||
{
|
||||
'rawtext': 'host ssh is down',
|
||||
'vitrage_is_placeholder': False,
|
||||
'vitrage_type': 'zabbix',
|
||||
'vitrage_is_deleted': False,
|
||||
}),
|
||||
Vertex('host',
|
||||
{
|
||||
'vitrage_is_placeholder': False,
|
||||
'vitrage_type': 'nova.host',
|
||||
'vitrage_is_deleted': False,
|
||||
}),
|
||||
],
|
||||
edges=[
|
||||
Edge('host_ssh_alarm', 'host', 'on',
|
||||
{
|
||||
'vitrage_is_deleted': True,
|
||||
'negative_condition': True,
|
||||
}),
|
||||
]),
|
||||
],
|
||||
TemplateLoaderV3Test.expected_entities,
|
||||
TemplateLoaderV3Test.expected_relationships)
|
||||
self._assert_scenario_equal(
|
||||
expected_scenario,
|
||||
observed_scenarios[3])
|
||||
|
||||
def test_scenario_4(self):
|
||||
observed_scenarios = self._load_scenarios('valid_actions.yaml')
|
||||
expected_scenario = Scenario(
|
||||
'valid actions-scenario4',
|
||||
'3',
|
||||
None,
|
||||
[
|
||||
ActionSpecs(
|
||||
'valid actions-scenario4-action0',
|
||||
'mark_down',
|
||||
{'target': 'host'},
|
||||
{}),
|
||||
],
|
||||
[
|
||||
NXGraph(
|
||||
vertices=[
|
||||
Vertex('host',
|
||||
{
|
||||
'vitrage_is_placeholder': False,
|
||||
'vitrage_type': 'nova.host',
|
||||
'vitrage_is_deleted': False,
|
||||
}),
|
||||
]),
|
||||
],
|
||||
TemplateLoaderV3Test.expected_entities,
|
||||
TemplateLoaderV3Test.expected_relationships)
|
||||
self._assert_scenario_equal(
|
||||
expected_scenario,
|
||||
observed_scenarios[4])
|
||||
|
||||
def test_scenario_5(self):
|
||||
observed_scenarios = self._load_scenarios('valid_actions.yaml')
|
||||
expected_scenario = Scenario(
|
||||
'valid actions-scenario5',
|
||||
'3',
|
||||
None,
|
||||
[
|
||||
ActionSpecs(
|
||||
'valid actions-scenario5-action0',
|
||||
'mark_down',
|
||||
{'target': 'host'},
|
||||
{}),
|
||||
],
|
||||
[
|
||||
NXGraph(
|
||||
vertices=[
|
||||
Vertex('host_ssh_alarm',
|
||||
{
|
||||
'rawtext': 'host ssh is down',
|
||||
'vitrage_is_placeholder': False,
|
||||
'vitrage_type': 'zabbix',
|
||||
'vitrage_is_deleted': False,
|
||||
}),
|
||||
Vertex('instance',
|
||||
{
|
||||
'vitrage_is_placeholder': False,
|
||||
'vitrage_type': 'nova.instance',
|
||||
'vitrage_is_deleted': False,
|
||||
}),
|
||||
Vertex('host',
|
||||
{
|
||||
'vitrage_is_placeholder': False,
|
||||
'vitrage_type': 'nova.host',
|
||||
'vitrage_is_deleted': False,
|
||||
}),
|
||||
],
|
||||
edges=[
|
||||
Edge('host_ssh_alarm', 'host', 'on',
|
||||
{
|
||||
'vitrage_is_deleted': True,
|
||||
'negative_condition': True
|
||||
}),
|
||||
Edge('host', 'instance', 'contains',
|
||||
{
|
||||
'vitrage_is_deleted': True,
|
||||
'negative_condition': True
|
||||
})
|
||||
]
|
||||
),
|
||||
],
|
||||
TemplateLoaderV3Test.expected_entities,
|
||||
TemplateLoaderV3Test.expected_relationships)
|
||||
self._assert_scenario_equal(
|
||||
expected_scenario,
|
||||
observed_scenarios[5])
|
@ -27,9 +27,9 @@
|
||||
# under the License.
|
||||
|
||||
from vitrage.common.constants import TemplateTypes
|
||||
from vitrage.evaluator.template_loading.template_loader import TemplateLoader
|
||||
from vitrage.tests import base
|
||||
from vitrage.tests.mocks import utils
|
||||
from vitrage.tests.unit.evaluator import get_template_data
|
||||
from vitrage.utils import file as file_utils
|
||||
|
||||
|
||||
@ -42,7 +42,7 @@ class TemplateLoaderTest(base.BaseTest):
|
||||
(utils.get_resources_dir(), self.STANDARD_TEMPLATE)
|
||||
template_definition = file_utils.load_yaml_file(template_path, True)
|
||||
|
||||
template_data = TemplateLoader().load(template_definition)
|
||||
template_data = get_template_data(template_definition)
|
||||
self.assertIsNotNone(template_data)
|
||||
|
||||
template_type = template_data.template_type
|
||||
|
@ -0,0 +1,346 @@
|
||||
# Copyright 2019 - 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 json
|
||||
from oslo_config import cfg
|
||||
|
||||
from vitrage.api_handler.apis.template import TemplateApis
|
||||
from vitrage.evaluator.actions.recipes import execute_mistral
|
||||
from vitrage.evaluator.template_fields import TemplateFields as TF
|
||||
from vitrage.tests import base
|
||||
from vitrage.tests.functional.test_configuration import TestConfiguration
|
||||
from vitrage.tests.mocks.utils import get_resources_dir
|
||||
from vitrage.utils import file as file_utils
|
||||
|
||||
|
||||
class TemplateValidatorV3Test(base.BaseTest, TestConfiguration):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TemplateValidatorV3Test, cls).setUpClass()
|
||||
cls.conf = cfg.ConfigOpts()
|
||||
cls.add_db(cls.conf)
|
||||
cls.template_apis = TemplateApis(db=cls._db)
|
||||
|
||||
def _test_validation(self, file=None, content=None, expected_code=0):
|
||||
if file and not content:
|
||||
content = self._get_yaml(file)
|
||||
self._call_validate_api('/tmp/tmp', content, expected_code)
|
||||
|
||||
@staticmethod
|
||||
def _get_yaml(filename):
|
||||
path = '%s/templates/v3_templates/%s' % (get_resources_dir(), filename)
|
||||
return file_utils.load_yaml_file(path)
|
||||
|
||||
def _call_validate_api(self, path, content, expected_error_code):
|
||||
templates = [[
|
||||
path,
|
||||
content,
|
||||
]]
|
||||
results = self.template_apis.validate_template(None, templates, None)
|
||||
result = json.loads(results)['results'][0]
|
||||
self.assertEqual(expected_error_code, result['status code'],
|
||||
message='GOT ' + result['message'])
|
||||
|
||||
def test_actions(self):
|
||||
template = self._get_yaml('valid_actions.yaml')
|
||||
self._test_validation(content=template, expected_code=0)
|
||||
|
||||
del template[TF.SCENARIOS][0][TF.CONDITION]
|
||||
self._test_validation(content=template, expected_code=83)
|
||||
|
||||
del template[TF.SCENARIOS][0]
|
||||
del template[TF.SCENARIOS][0][TF.ACTIONS]
|
||||
self._test_validation(content=template, expected_code=84)
|
||||
|
||||
def test_set_state(self):
|
||||
template = self._get_yaml('valid_actions.yaml')
|
||||
|
||||
valid_action = {
|
||||
'set_state': {
|
||||
TF.TARGET: 'host',
|
||||
TF.STATE: 'BAD',
|
||||
}
|
||||
}
|
||||
template[TF.SCENARIOS][0][TF.ACTIONS].append(valid_action)
|
||||
self._test_validation(content=template, expected_code=0)
|
||||
|
||||
valid_action['set_state'] = {
|
||||
TF.TARGET: 'host_incorrect_key',
|
||||
TF.STATE: 'BAD',
|
||||
}
|
||||
self._test_validation(content=template, expected_code=10101)
|
||||
|
||||
valid_action['set_state'] = {
|
||||
TF.STATE: 'BAD',
|
||||
}
|
||||
self._test_validation(content=template, expected_code=10100)
|
||||
|
||||
valid_action['set_state'] = {
|
||||
TF.TARGET: 'host',
|
||||
TF.STATE: 'BAD',
|
||||
'kuku': 'kuku',
|
||||
}
|
||||
self._test_validation(content=template, expected_code=4)
|
||||
|
||||
valid_action['set_state'] = {
|
||||
TF.TARGET: 'host',
|
||||
}
|
||||
self._test_validation(content=template, expected_code=128)
|
||||
|
||||
def test_mark_down(self):
|
||||
template = self._get_yaml('valid_actions.yaml')
|
||||
|
||||
valid_action = {
|
||||
'mark_down': {
|
||||
TF.TARGET: 'host',
|
||||
}
|
||||
}
|
||||
template[TF.SCENARIOS][0][TF.ACTIONS].append(valid_action)
|
||||
self._test_validation(content=template, expected_code=0)
|
||||
|
||||
valid_action['mark_down'] = {
|
||||
TF.TARGET: 'host_incorrect_key',
|
||||
}
|
||||
self._test_validation(content=template, expected_code=10101)
|
||||
|
||||
valid_action['mark_down'] = {}
|
||||
self._test_validation(content=template, expected_code=10100)
|
||||
|
||||
valid_action['mark_down'] = {
|
||||
TF.TARGET: 'host',
|
||||
'kuku': 'kuku',
|
||||
}
|
||||
self._test_validation(content=template, expected_code=4)
|
||||
|
||||
def test_raise_alarm(self):
|
||||
self._test_validation(file='valid_actions.yaml', expected_code=0)
|
||||
template = self._get_yaml('valid_actions.yaml')
|
||||
|
||||
valid_action = {
|
||||
'raise_alarm': {
|
||||
TF.TARGET: 'host',
|
||||
TF.ALARM_NAME: 'BAD',
|
||||
TF.SEVERITY: 'BAD',
|
||||
}
|
||||
}
|
||||
template[TF.SCENARIOS][0][TF.ACTIONS].append(valid_action)
|
||||
self._test_validation(content=template, expected_code=0)
|
||||
|
||||
valid_action['raise_alarm'] = {
|
||||
TF.TARGET: 'host_incorrect_key',
|
||||
TF.ALARM_NAME: 'BAD',
|
||||
TF.SEVERITY: 'BAD',
|
||||
}
|
||||
self._test_validation(content=template, expected_code=10101)
|
||||
|
||||
valid_action['raise_alarm'] = {
|
||||
TF.TARGET: 'host',
|
||||
TF.ALARM_NAME: 'BAD',
|
||||
TF.SEVERITY: 'BAD',
|
||||
'kuku': 'kuku',
|
||||
}
|
||||
self._test_validation(content=template, expected_code=4)
|
||||
|
||||
valid_action['raise_alarm'] = {
|
||||
TF.ALARM_NAME: 'BAD',
|
||||
TF.SEVERITY: 'BAD',
|
||||
}
|
||||
self._test_validation(content=template, expected_code=10100)
|
||||
|
||||
valid_action['raise_alarm'] = {
|
||||
TF.TARGET: 'host',
|
||||
TF.SEVERITY: 'BAD',
|
||||
}
|
||||
self._test_validation(content=template, expected_code=10104)
|
||||
|
||||
valid_action['raise_alarm'] = {
|
||||
TF.TARGET: 'host',
|
||||
TF.ALARM_NAME: 'BAD',
|
||||
}
|
||||
self._test_validation(content=template, expected_code=126)
|
||||
|
||||
valid_action['raise_alarm'] = {
|
||||
TF.TARGET: 'host',
|
||||
TF.ALARM_NAME: 'BAD',
|
||||
TF.SEVERITY: 'BAD',
|
||||
TF.CAUSING_ALARM: 'host_ssh_alarm'
|
||||
}
|
||||
self._test_validation(content=template, expected_code=0)
|
||||
|
||||
valid_action['raise_alarm'] = {
|
||||
TF.TARGET: 'host',
|
||||
TF.ALARM_NAME: 'BAD',
|
||||
TF.SEVERITY: 'BAD',
|
||||
TF.CAUSING_ALARM: 'host_ssh_alarm_incorrect_key'
|
||||
}
|
||||
self._test_validation(content=template, expected_code=10107)
|
||||
|
||||
def test_add_causal_relationship(self):
|
||||
self._test_validation(file='valid_actions.yaml', expected_code=0)
|
||||
template = self._get_yaml('valid_actions.yaml')
|
||||
|
||||
valid_action = {
|
||||
'add_causal_relationship': {
|
||||
TF.TARGET: 'host_ssh_alarm',
|
||||
TF.SOURCE: 'host_network_alarm',
|
||||
}
|
||||
}
|
||||
template[TF.SCENARIOS][0][TF.ACTIONS].append(valid_action)
|
||||
self._test_validation(content=template, expected_code=0)
|
||||
|
||||
valid_action['add_causal_relationship'] = {
|
||||
TF.TARGET: 'host_ssh_alarm_incorrect_key',
|
||||
TF.SOURCE: 'host_network_alarm',
|
||||
}
|
||||
self._test_validation(content=template, expected_code=10101)
|
||||
|
||||
valid_action['add_causal_relationship'] = {
|
||||
TF.TARGET: 'host_ssh_alarm',
|
||||
TF.SOURCE: 'host_network_alarm_incorrect_key',
|
||||
}
|
||||
self._test_validation(content=template, expected_code=10103)
|
||||
|
||||
valid_action['add_causal_relationship'] = {
|
||||
TF.SOURCE: 'host_network_alarm',
|
||||
}
|
||||
self._test_validation(content=template, expected_code=10100)
|
||||
|
||||
valid_action['add_causal_relationship'] = {
|
||||
TF.TARGET: 'host_ssh_alarm',
|
||||
}
|
||||
self._test_validation(content=template, expected_code=10102)
|
||||
|
||||
def test_execute_mistral(self):
|
||||
template = self._get_yaml('valid_actions.yaml')
|
||||
|
||||
valid_action = {
|
||||
'execute_mistral': {
|
||||
execute_mistral.WORKFLOW: 'kuku',
|
||||
execute_mistral.INPUT: {},
|
||||
}
|
||||
}
|
||||
|
||||
template[TF.SCENARIOS][0][TF.ACTIONS].append(valid_action)
|
||||
self._test_validation(content=template, expected_code=0)
|
||||
|
||||
valid_action['execute_mistral'] = {
|
||||
execute_mistral.WORKFLOW: {},
|
||||
execute_mistral.INPUT: {},
|
||||
}
|
||||
self._test_validation(content=template, expected_code=4)
|
||||
|
||||
valid_action['execute_mistral'] = {execute_mistral.INPUT: {}}
|
||||
self._test_validation(content=template, expected_code=10105)
|
||||
|
||||
valid_action['execute_mistral'] = {execute_mistral.WORKFLOW: 'kuku'}
|
||||
self._test_validation(content=template, expected_code=0)
|
||||
|
||||
valid_action['execute_mistral'] = {
|
||||
execute_mistral.WORKFLOW: 'kuku',
|
||||
execute_mistral.INPUT: {'kuku': 'get_attr('}
|
||||
}
|
||||
self._test_validation(content=template, expected_code=138)
|
||||
|
||||
valid_action['execute_mistral'] = {
|
||||
execute_mistral.WORKFLOW: 'kuku',
|
||||
execute_mistral.INPUT: {'kuku': 'get_attr(host, name)'},
|
||||
}
|
||||
self._test_validation(content=template, expected_code=0)
|
||||
|
||||
def test_conditions(self):
|
||||
self._test_validation(file='valid_conditions.yaml', expected_code=0)
|
||||
template = self._get_yaml('valid_conditions.yaml')
|
||||
|
||||
template[TF.SCENARIOS][0][TF.CONDITION] = \
|
||||
'host AND NOT host host [contains] instance'
|
||||
self._test_validation(content=template, expected_code=85)
|
||||
|
||||
template[TF.SCENARIOS][0][TF.CONDITION] = \
|
||||
'host AND instance host [contains] instance'
|
||||
self._test_validation(content=template, expected_code=85)
|
||||
|
||||
template[TF.SCENARIOS][0][TF.CONDITION] = \
|
||||
'host AND NOT host [host contains] instance'
|
||||
self._test_validation(content=template, expected_code=85)
|
||||
|
||||
template[TF.SCENARIOS][0][TF.CONDITION] = \
|
||||
'host AND instance host [contains] instance'
|
||||
self._test_validation(content=template, expected_code=85)
|
||||
|
||||
template[TF.SCENARIOS][0][TF.CONDITION] = \
|
||||
'NOT host [contains] instance'
|
||||
self._test_validation(content=template, expected_code=134)
|
||||
|
||||
template[TF.SCENARIOS][0][TF.CONDITION] = \
|
||||
'NOT host [contains] instance AND NOT host [contains] instance'
|
||||
self._test_validation(content=template, expected_code=134)
|
||||
|
||||
template[TF.SCENARIOS][0][TF.CONDITION] = \
|
||||
'NOT (host [ contains ] instance or host [ contains ] instance)'
|
||||
self._test_validation(content=template, expected_code=134)
|
||||
|
||||
template[TF.SCENARIOS][0][TF.CONDITION] = \
|
||||
'NOT host_incorrect_key'
|
||||
self._test_validation(content=template, expected_code=10200)
|
||||
|
||||
template[TF.SCENARIOS][0][TF.CONDITION] = \
|
||||
'host_incorrect_key'
|
||||
self._test_validation(content=template, expected_code=10200)
|
||||
|
||||
template[TF.SCENARIOS][0][TF.CONDITION] = \
|
||||
'host [contains] instance_incorrect_key'
|
||||
self._test_validation(content=template, expected_code=10200)
|
||||
|
||||
template[TF.SCENARIOS][0][TF.CONDITION] = \
|
||||
'host_incorrect_key [contains] instance'
|
||||
self._test_validation(content=template, expected_code=10200)
|
||||
|
||||
template[TF.SCENARIOS][0][TF.CONDITION] = \
|
||||
'NOT host'
|
||||
self._test_validation(content=template, expected_code=86)
|
||||
|
||||
template[TF.SCENARIOS][0][TF.CONDITION] = \
|
||||
'(host_incorrect_key)'
|
||||
self._test_validation(content=template, expected_code=10200)
|
||||
|
||||
template[TF.SCENARIOS][0][TF.CONDITION] = \
|
||||
'( host OR host [contains] instance ) AND' \
|
||||
' (instance OR host [contains] instance' # missing parentheses
|
||||
self._test_validation(content=template, expected_code=85)
|
||||
|
||||
template[TF.SCENARIOS][0][TF.CONDITION] = 'host OR instance'
|
||||
self._test_validation(content=template, expected_code=135)
|
||||
|
||||
template[TF.SCENARIOS][0][TF.CONDITION] = \
|
||||
'host OR NOT host [contains] instance'
|
||||
self._test_validation(content=template, expected_code=135)
|
||||
|
||||
def test_regex(self):
|
||||
template = self._get_yaml('valid_actions.yaml')
|
||||
template[TF.ENTITIES]['entity_regex'] = {'name.regex': 'bad.regex('}
|
||||
self._test_validation(content=template, expected_code=47)
|
||||
|
||||
def test_basic(self):
|
||||
template = self._get_yaml('valid_conditions.yaml')
|
||||
del template[TF.SCENARIOS]
|
||||
self._test_validation(content=template, expected_code=80)
|
||||
|
||||
template = self._get_yaml('valid_conditions.yaml')
|
||||
del template[TF.ENTITIES]
|
||||
self._test_validation(content=template, expected_code=10101)
|
||||
|
||||
template = self._get_yaml('valid_conditions.yaml')
|
||||
template['kuku'] = {}
|
||||
self._test_validation(content=template, expected_code=4)
|
@ -14,11 +14,11 @@
|
||||
|
||||
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.v1.scenario_validator \
|
||||
import get_condition_common_targets
|
||||
from vitrage.tests import base
|
||||
from vitrage.tests.mocks import utils
|
||||
from vitrage.tests.unit.evaluator import get_template_data
|
||||
from vitrage.utils import file as file_utils
|
||||
|
||||
|
||||
@ -93,7 +93,7 @@ class ConditionTest(base.BaseTest):
|
||||
template_name)
|
||||
template_definition = file_utils.load_yaml_file(template_path, True)
|
||||
|
||||
template_data = TemplateLoader().load(template_definition)
|
||||
template_data = get_template_data(template_definition)
|
||||
definitions_index = template_data.entities.copy()
|
||||
definitions_index.update(template_data.relationships)
|
||||
|
||||
|
@ -27,11 +27,12 @@ from vitrage.evaluator.template_data import ActionSpecs
|
||||
from vitrage.evaluator.template_data import EdgeDescription
|
||||
from vitrage.evaluator.template_data import Scenario
|
||||
from vitrage.evaluator.template_fields import TemplateFields as TFields
|
||||
from vitrage.evaluator.template_loading.template_loader import TemplateLoader
|
||||
from vitrage.evaluator.template_loading.props_converter import PropsConverter
|
||||
from vitrage.graph import Edge
|
||||
from vitrage.graph import Vertex
|
||||
from vitrage.tests import base
|
||||
from vitrage.tests.mocks import utils
|
||||
from vitrage.tests.unit.evaluator import get_template_data
|
||||
from vitrage.utils import file as file_utils
|
||||
|
||||
|
||||
@ -56,8 +57,8 @@ class TemplateLoaderTest(base.BaseTest):
|
||||
def_templates_path)
|
||||
def_templates_dict = utils.get_def_templates_dict_from_list(
|
||||
def_demplates_list)
|
||||
template_data = \
|
||||
TemplateLoader().load(template_definition, def_templates_dict)
|
||||
template_data = get_template_data(template_definition,
|
||||
def_templates_dict)
|
||||
entities = template_data.entities
|
||||
relationships = template_data.relationships
|
||||
scenarios = template_data.scenarios
|
||||
@ -74,8 +75,8 @@ class TemplateLoaderTest(base.BaseTest):
|
||||
# Assertions
|
||||
for definition in definitions[TFields.ENTITIES]:
|
||||
for key, value in definition['entity'].items():
|
||||
new_key = TemplateLoader.PROPS_CONVERSION[key] if key in \
|
||||
TemplateLoader.PROPS_CONVERSION else key
|
||||
new_key = PropsConverter.PROPS_CONVERSION[key] \
|
||||
if key in PropsConverter.PROPS_CONVERSION else key
|
||||
del definition['entity'][key]
|
||||
definition['entity'][new_key] = value
|
||||
self._validate_entities(entities, definitions[TFields.ENTITIES])
|
||||
@ -164,7 +165,7 @@ class TemplateLoaderTest(base.BaseTest):
|
||||
self.BASIC_TEMPLATE)
|
||||
template_definition = file_utils.load_yaml_file(template_path, True)
|
||||
|
||||
template_data = TemplateLoader().load(template_definition)
|
||||
template_data = get_template_data(template_definition)
|
||||
entities = template_data.entities
|
||||
relationships = template_data.relationships
|
||||
scenarios = template_data.scenarios
|
||||
@ -173,8 +174,8 @@ class TemplateLoaderTest(base.BaseTest):
|
||||
# Assertions
|
||||
for definition in definitions[TFields.ENTITIES]:
|
||||
for key, value in definition['entity'].items():
|
||||
new_key = TemplateLoader.PROPS_CONVERSION[key] if key in \
|
||||
TemplateLoader.PROPS_CONVERSION else key
|
||||
new_key = PropsConverter.PROPS_CONVERSION[key] \
|
||||
if key in PropsConverter.PROPS_CONVERSION else key
|
||||
del definition['entity'][key]
|
||||
definition['entity'][new_key] = value
|
||||
self._validate_entities(entities, definitions[TFields.ENTITIES])
|
||||
@ -248,7 +249,7 @@ class TemplateLoaderTest(base.BaseTest):
|
||||
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)
|
||||
template_data = get_template_data(template_definition)
|
||||
scenarios = template_data.scenarios
|
||||
self.assertIsNotNone(scenarios, 'Template should include a scenario')
|
||||
self.assertThat(scenarios, matchers.HasLength(1),
|
||||
|
Loading…
x
Reference in New Issue
Block a user