Add document about implementation details of template loading
- add strict tests in template data verification to help understand - add traceback in scenario evaluator design document - rename example templates with a more meaningful name Change-Id: Ie5a9c34fcd6fdac3bf9d4552a0c92fb569c075fc
This commit is contained in:
parent
6c24d20a31
commit
86691304c8
@ -95,7 +95,10 @@ Templates should all be located in the *<vitrage folder>/templates* folder.
|
|||||||
When Vitrage is started up, all the templates are loaded into a *Scenario*
|
When Vitrage is started up, all the templates are loaded into a *Scenario*
|
||||||
*Repository*. During this loading, template verification is done to
|
*Repository*. During this loading, template verification is done to
|
||||||
ensure the correct format is used, references are valid, and more. Errors in
|
ensure the correct format is used, references are valid, and more. Errors in
|
||||||
each template should be written to the log. Invalid templates are skipped.
|
each template should be written to the log. Invalid templates are skipped. See
|
||||||
|
details_.
|
||||||
|
|
||||||
|
.. _details: templates-loading.html
|
||||||
|
|
||||||
The Scenario Repository supports querying for scenarios based on a vertex or
|
The Scenario Repository supports querying for scenarios based on a vertex or
|
||||||
edge in the Entity Graph. Given such a graph element, the Scenario Repository
|
edge in the Entity Graph. Given such a graph element, the Scenario Repository
|
||||||
|
195
doc/source/templates-loading.rst
Normal file
195
doc/source/templates-loading.rst
Normal file
@ -0,0 +1,195 @@
|
|||||||
|
========================
|
||||||
|
Vitrage Template Loading
|
||||||
|
========================
|
||||||
|
|
||||||
|
Overview
|
||||||
|
========
|
||||||
|
|
||||||
|
Vitrage templates are defined in yaml with specific format_. During startup,
|
||||||
|
templates are loaded into ``TemplateData``. After that , scenarios in loaded
|
||||||
|
templates will be added into scenario repository.
|
||||||
|
|
||||||
|
This document explains the implementation details of template data to help
|
||||||
|
developer understand how scenario_evaluator_ works.
|
||||||
|
|
||||||
|
.. _format: vitrage-template-format.html
|
||||||
|
.. _scenario_evaluator: scenario-evaluator.html
|
||||||
|
|
||||||
|
Example
|
||||||
|
=======
|
||||||
|
|
||||||
|
Let's take a basic template as example
|
||||||
|
|
||||||
|
.. code-block:: yaml
|
||||||
|
|
||||||
|
metadata:
|
||||||
|
name: basic_template
|
||||||
|
description: basic template for general tests
|
||||||
|
definitions:
|
||||||
|
entities:
|
||||||
|
- entity:
|
||||||
|
category: ALARM
|
||||||
|
type: nagios
|
||||||
|
name: HOST_HIGH_CPU_LOAD
|
||||||
|
template_id: alarm
|
||||||
|
- entity:
|
||||||
|
category: RESOURCE
|
||||||
|
type: nova.host
|
||||||
|
template_id: resource
|
||||||
|
relationships:
|
||||||
|
- relationship:
|
||||||
|
source: alarm
|
||||||
|
target: resource
|
||||||
|
relationship_type: on
|
||||||
|
template_id : alarm_on_host
|
||||||
|
scenarios:
|
||||||
|
- scenario:
|
||||||
|
condition: alarm_on_host
|
||||||
|
actions:
|
||||||
|
- action:
|
||||||
|
action_type: set_state
|
||||||
|
properties:
|
||||||
|
state: SUBOPTIMAL
|
||||||
|
action_target:
|
||||||
|
target: resource
|
||||||
|
|
||||||
|
``TemplateData`` will build ``entites``, ``relationships`` and most importantly
|
||||||
|
``scenarios`` out from the definition.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
expected_entities = {
|
||||||
|
'alarm': Vertex(vertex_id='alarm',
|
||||||
|
properties={'category': 'ALARM',
|
||||||
|
'type': 'nagios',
|
||||||
|
'name': 'HOST_HIGH_CPU_LOAD',
|
||||||
|
'is_deleted': False,
|
||||||
|
'is_placeholder': False
|
||||||
|
}),
|
||||||
|
'resource': Vertex(vertex_id='resource',
|
||||||
|
properties={'category': 'RESOURCE',
|
||||||
|
'type': 'nova.host',
|
||||||
|
'is_deleted': False,
|
||||||
|
'is_placeholder': False
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
expected_relationships = {
|
||||||
|
'alarm_on_host': EdgeDescription(
|
||||||
|
edge=Edge(
|
||||||
|
source_id='alarm',
|
||||||
|
target_id='resource',
|
||||||
|
label='on',
|
||||||
|
properties={
|
||||||
|
'relationship_type': 'on',
|
||||||
|
'is_deleted': False,
|
||||||
|
'negative_condition': False}),
|
||||||
|
source=expected_entities['alarm'],
|
||||||
|
target=expected_entities['resource']
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected_scenario = Scenario(
|
||||||
|
id='basic_template-scenario0',
|
||||||
|
condition=[
|
||||||
|
[ConditionVar(
|
||||||
|
variable=expected_relationships['alarm_on_host'],
|
||||||
|
type='relationship',
|
||||||
|
positive=True)]
|
||||||
|
],
|
||||||
|
actions=[
|
||||||
|
ActionSpecs(
|
||||||
|
type='set_state',
|
||||||
|
targets={
|
||||||
|
'target': 'resource'},
|
||||||
|
properties={
|
||||||
|
'state': 'SUBOPTIMAL'})],
|
||||||
|
subgraphs=[object] # [<vitrage.graph.driver.networkx_graph.NXGraph object>]
|
||||||
|
)
|
||||||
|
|
||||||
|
Entities and relationships
|
||||||
|
==========================
|
||||||
|
|
||||||
|
Entities and relationships are loaded into dicts keyed by ``template_id`` so
|
||||||
|
that the references in scenarios can be resolved quickly.
|
||||||
|
|
||||||
|
Note that entities and relationships dicts are **NOT** added to scenario
|
||||||
|
repository. This implies the scope of ``template_id`` is restricted to one
|
||||||
|
template file. It is **NOT** global.
|
||||||
|
|
||||||
|
It is considered invalid to have duplicated ``template_id`` in one template, but
|
||||||
|
it is possible that two or more entities have exactly the same properties except
|
||||||
|
``template_id``. There is an example in
|
||||||
|
``vitrage/tests/templates/evaluator/high_availability.yaml``:
|
||||||
|
|
||||||
|
.. code:: yaml
|
||||||
|
|
||||||
|
- entity:
|
||||||
|
category: RESOURCE
|
||||||
|
type: nova.instance
|
||||||
|
template_id: instance1
|
||||||
|
- entity:
|
||||||
|
category: RESOURCE
|
||||||
|
type: nova.instance
|
||||||
|
template_id: instance2
|
||||||
|
|
||||||
|
It is used to model scenario contains two or more entities of same type, such
|
||||||
|
as high availability condition.
|
||||||
|
|
||||||
|
Scenarios
|
||||||
|
=========
|
||||||
|
|
||||||
|
``Scenario`` are defined as a ``namedtuple``
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
Scenario = namedtuple('Scenario', ['id', 'condition', 'actions', 'subgraphs'])
|
||||||
|
|
||||||
|
id
|
||||||
|
--
|
||||||
|
|
||||||
|
Formatted from template name and scenario index
|
||||||
|
|
||||||
|
condition
|
||||||
|
---------
|
||||||
|
|
||||||
|
Condition strings in template are expressions composed of template id and
|
||||||
|
operators. As explained in embedded comment:
|
||||||
|
|
||||||
|
The condition string will be converted here into DNF (Disjunctive
|
||||||
|
Normal Form), e.g., (X and Y) or (X and Z) or (X and V and not W)...
|
||||||
|
where X, Y, Z, V, W are either entities or relationships
|
||||||
|
more details: https://en.wikipedia.org/wiki/Disjunctive_normal_form
|
||||||
|
|
||||||
|
The condition variable lists is then extracted from the DNF object. It
|
||||||
|
is a list of lists. Each inner list represents an AND expression
|
||||||
|
compound condition variables. The outer list presents the OR expression
|
||||||
|
|
||||||
|
[[and_var1, and_var2, ...], or_list_2, ...]
|
||||||
|
|
||||||
|
:param condition_str: the string as it written in the template itself
|
||||||
|
:return: condition_vars_lists
|
||||||
|
|
||||||
|
Each condition variable refers either an entity or relationship by ``variable``.
|
||||||
|
The same variable in different conditions share an identical object. It is not
|
||||||
|
duplicated.
|
||||||
|
|
||||||
|
actions
|
||||||
|
-------
|
||||||
|
|
||||||
|
``actions`` is a list of ``ActionSpecs``.
|
||||||
|
|
||||||
|
Note that the values of action ``targets`` are **string**. It is different from
|
||||||
|
how relationships and entities are referred in ``condition``, which are object
|
||||||
|
references.
|
||||||
|
|
||||||
|
The targets values are linked to ``vertex_id`` of entity condition variables or
|
||||||
|
``source_id`` and ``target_id`` in a relationship condition variable. It will
|
||||||
|
be resolved to real targets from matched sub-graph in entity graph.
|
||||||
|
|
||||||
|
subgraphs
|
||||||
|
---------
|
||||||
|
|
||||||
|
Sub graphs are compiled from conditions for pattern matching in the entity
|
||||||
|
graph. Each sub-list in condition variables list is compiled into one sub
|
||||||
|
graph. The actions will be triggered if any of the subgraph is matched.
|
@ -203,17 +203,19 @@ class TemplateData(object):
|
|||||||
def _parse_condition(self, condition_str):
|
def _parse_condition(self, condition_str):
|
||||||
"""Parse condition string into an object
|
"""Parse condition string into an object
|
||||||
|
|
||||||
The condition object will be converted here into DNF (Disjunctive
|
The condition string will be converted here into DNF (Disjunctive
|
||||||
Normal Form), e.g., (X and Y) or (X and Z) or (X and V and not W)...
|
Normal Form), e.g., (X and Y) or (X and Z) or (X and V and not W)...
|
||||||
where X, Y, Z, V, W are either entities or relationships
|
where X, Y, Z, V, W are either entities or relationships
|
||||||
more details: https://en.wikipedia.org/wiki/Disjunctive_normal_form
|
more details: https://en.wikipedia.org/wiki/Disjunctive_normal_form
|
||||||
|
|
||||||
The condition object itself is a list of tuples. each tuple represents
|
The condition variable lists is then extracted from the DNF object. It
|
||||||
an AND expression compound ConditionElements. The list presents the
|
is a list of lists. Each inner list represents an AND expression
|
||||||
OR expression e.g. [(condition_element1, condition_element2)]
|
compound condition variables. The outer list presents the OR expression
|
||||||
|
|
||||||
|
[[and_var1, and_var2, ...], or_list_2, ...]
|
||||||
|
|
||||||
:param condition_str: the string as it written in the template itself
|
:param condition_str: the string as it written in the template itself
|
||||||
:return: Condition object
|
:return: condition_vars_lists
|
||||||
"""
|
"""
|
||||||
|
|
||||||
condition_dnf = self.convert_to_dnf_format(condition_str)
|
condition_dnf = self.convert_to_dnf_format(condition_str)
|
||||||
|
@ -13,10 +13,13 @@
|
|||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
from vitrage.common.constants import EdgeLabel
|
from vitrage.common.constants import EdgeLabel
|
||||||
|
from vitrage.evaluator.template_data import ActionSpecs
|
||||||
from vitrage.evaluator.template_data import ConditionVar
|
from vitrage.evaluator.template_data import ConditionVar
|
||||||
from vitrage.evaluator.template_data import EdgeDescription
|
from vitrage.evaluator.template_data import EdgeDescription
|
||||||
|
from vitrage.evaluator.template_data import Scenario
|
||||||
from vitrage.evaluator.template_data import TemplateData
|
from vitrage.evaluator.template_data import TemplateData
|
||||||
from vitrage.evaluator.template_fields import TemplateFields as TFields
|
from vitrage.evaluator.template_fields import TemplateFields as TFields
|
||||||
|
from vitrage.graph import Edge
|
||||||
from vitrage.graph import Vertex
|
from vitrage.graph import Vertex
|
||||||
from vitrage.tests import base
|
from vitrage.tests import base
|
||||||
from vitrage.tests.mocks import utils
|
from vitrage.tests.mocks import utils
|
||||||
@ -48,6 +51,70 @@ class BasicTemplateTest(base.BaseTest):
|
|||||||
self._validate_relationships(relationships, relate_def, entities)
|
self._validate_relationships(relationships, relate_def, entities)
|
||||||
self._validate_scenarios(scenarios, entities)
|
self._validate_scenarios(scenarios, entities)
|
||||||
|
|
||||||
|
expected_entities = {
|
||||||
|
'alarm': Vertex(vertex_id='alarm',
|
||||||
|
properties={'category': 'ALARM',
|
||||||
|
'type': 'nagios',
|
||||||
|
'name': 'HOST_HIGH_CPU_LOAD',
|
||||||
|
'is_deleted': False,
|
||||||
|
'is_placeholder': False
|
||||||
|
}),
|
||||||
|
'resource': Vertex(vertex_id='resource',
|
||||||
|
properties={'category': 'RESOURCE',
|
||||||
|
'type': 'nova.host',
|
||||||
|
'is_deleted': False,
|
||||||
|
'is_placeholder': False
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
expected_relationships = {
|
||||||
|
'alarm_on_host': EdgeDescription(
|
||||||
|
edge=Edge(source_id='alarm',
|
||||||
|
target_id='resource',
|
||||||
|
label='on',
|
||||||
|
properties={'relationship_type': 'on',
|
||||||
|
'is_deleted': False,
|
||||||
|
'negative_condition': False}),
|
||||||
|
source=expected_entities['alarm'],
|
||||||
|
target=expected_entities['resource']
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected_scenario = Scenario(
|
||||||
|
id='basic_template-scenario0',
|
||||||
|
condition=[
|
||||||
|
[ConditionVar(variable=expected_relationships['alarm_on_host'],
|
||||||
|
type='relationship',
|
||||||
|
positive=True)]],
|
||||||
|
actions=[
|
||||||
|
ActionSpecs(
|
||||||
|
type='set_state',
|
||||||
|
targets={'target': 'resource'},
|
||||||
|
properties={'state': 'SUBOPTIMAL'})],
|
||||||
|
subgraphs=template_data.scenarios[0].subgraphs
|
||||||
|
)
|
||||||
|
|
||||||
|
self._validate_strict_equal(template_data,
|
||||||
|
expected_entities,
|
||||||
|
expected_relationships,
|
||||||
|
expected_scenario)
|
||||||
|
|
||||||
|
def _validate_strict_equal(self,
|
||||||
|
template_data,
|
||||||
|
expected_entities,
|
||||||
|
expected_relationships,
|
||||||
|
expected_scenario
|
||||||
|
):
|
||||||
|
self.assert_dict_equal(expected_entities, template_data.entities,
|
||||||
|
'entities not equal')
|
||||||
|
|
||||||
|
self.assert_dict_equal(expected_relationships,
|
||||||
|
template_data.relationships,
|
||||||
|
'relationship not equal')
|
||||||
|
|
||||||
|
self.assertEqual(expected_scenario, template_data.scenarios[0],
|
||||||
|
'scenario not equal')
|
||||||
|
|
||||||
def _validate_entities(self, entities, entities_def):
|
def _validate_entities(self, entities, entities_def):
|
||||||
|
|
||||||
self.assertIsNotNone(entities)
|
self.assertIsNotNone(entities)
|
||||||
|
Loading…
Reference in New Issue
Block a user