Merge "support adding and deleting templates without the need to restart vitrage."
This commit is contained in:
commit
da7bc6e0c8
@ -24,22 +24,25 @@ else
|
|||||||
TESTS="topology"
|
TESTS="topology"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [ "$DEVSTACK_GATE_USE_PYTHON3" == "True" ]; then
|
||||||
|
export PYTHON=python3
|
||||||
|
fi
|
||||||
|
|
||||||
sudo cp -rf $DEVSTACK_PATH/vitrage/vitrage_tempest_tests/tests/resources/static_physical/static_physical_configuration.yaml /etc/vitrage/
|
sudo cp -rf $DEVSTACK_PATH/vitrage/vitrage_tempest_tests/tests/resources/static_physical/static_physical_configuration.yaml /etc/vitrage/
|
||||||
sudo cp -rf $DEVSTACK_PATH/vitrage/vitrage_tempest_tests/tests/resources/heat/heat_template.yaml /etc/vitrage/
|
sudo cp -rf $DEVSTACK_PATH/vitrage/vitrage_tempest_tests/tests/resources/heat/heat_template.yaml /etc/vitrage/
|
||||||
sudo cp -rf $DEVSTACK_PATH/vitrage/vitrage_tempest_tests/tests/resources/heat/heat_nested_template.yaml /etc/vitrage/
|
sudo cp -rf $DEVSTACK_PATH/vitrage/vitrage_tempest_tests/tests/resources/heat/heat_nested_template.yaml /etc/vitrage/
|
||||||
sudo cp -rf $DEVSTACK_PATH/vitrage/vitrage_tempest_tests/tests/resources/heat/server.yaml /etc/vitrage/
|
sudo cp -rf $DEVSTACK_PATH/vitrage/vitrage_tempest_tests/tests/resources/heat/server.yaml /etc/vitrage/
|
||||||
sudo cp -rf $DEVSTACK_PATH/vitrage/vitrage_tempest_tests/tests/resources/templates/api/* /etc/vitrage/templates/
|
|
||||||
sudo cp $DEVSTACK_PATH/tempest/etc/logging.conf.sample $DEVSTACK_PATH/tempest/etc/logging.conf
|
sudo cp $DEVSTACK_PATH/tempest/etc/logging.conf.sample $DEVSTACK_PATH/tempest/etc/logging.conf
|
||||||
|
|
||||||
# copied the templates need to restart
|
${PYTHON:-python} $DEVSTACK_PATH/vitrage/vitrage_tempest_tests/add_legacy_dir_templates.py
|
||||||
|
|
||||||
|
# restart due to configuration files changes
|
||||||
sudo systemctl restart devstack@vitrage-graph.service
|
sudo systemctl restart devstack@vitrage-graph.service
|
||||||
|
|
||||||
# wait for 30 seconds
|
# wait for 30 seconds
|
||||||
sleep 30
|
sleep 30
|
||||||
|
|
||||||
if [ "$DEVSTACK_GATE_USE_PYTHON3" == "True" ]; then
|
|
||||||
export PYTHON=python3
|
|
||||||
fi
|
|
||||||
|
|
||||||
cd $DEVSTACK_PATH/tempest/; sudo -E testr init
|
cd $DEVSTACK_PATH/tempest/; sudo -E testr init
|
||||||
|
|
||||||
|
@ -21,7 +21,9 @@ from pecan.core import abort
|
|||||||
|
|
||||||
from vitrage.api.controllers.rest import RootRestController
|
from vitrage.api.controllers.rest import RootRestController
|
||||||
from vitrage.api.policy import enforce
|
from vitrage.api.policy import enforce
|
||||||
|
from vitrage.common.constants import TemplateStatus as TStatus
|
||||||
|
from vitrage.common.exception import VitrageError
|
||||||
|
from vitrage.evaluator.template_db import template_repository as template_repo
|
||||||
|
|
||||||
LOG = log.getLogger(__name__)
|
LOG = log.getLogger(__name__)
|
||||||
|
|
||||||
@ -65,6 +67,38 @@ class TemplateController(RootRestController):
|
|||||||
to_unicode)
|
to_unicode)
|
||||||
abort(404, to_unicode)
|
abort(404, to_unicode)
|
||||||
|
|
||||||
|
@pecan.expose('json')
|
||||||
|
def delete(self, **kwargs):
|
||||||
|
uuid = kwargs['uuid']
|
||||||
|
LOG.info("delete template. uuid: %s", str(uuid))
|
||||||
|
|
||||||
|
enforce("template delete",
|
||||||
|
pecan.request.headers,
|
||||||
|
pecan.request.enforcer,
|
||||||
|
{})
|
||||||
|
try:
|
||||||
|
return self._delete(uuid)
|
||||||
|
except Exception as e:
|
||||||
|
LOG.exception('failed to delete template %s', e)
|
||||||
|
abort(404, str(e))
|
||||||
|
|
||||||
|
@pecan.expose('json')
|
||||||
|
def put(self, **kwargs):
|
||||||
|
template_path = kwargs['path']
|
||||||
|
LOG.info("add template: %s", template_path)
|
||||||
|
|
||||||
|
enforce("template add",
|
||||||
|
pecan.request.headers,
|
||||||
|
pecan.request.enforcer,
|
||||||
|
{})
|
||||||
|
template_type = kwargs['template_type']
|
||||||
|
|
||||||
|
try:
|
||||||
|
return self._add(template_path, template_type)
|
||||||
|
except Exception as e:
|
||||||
|
LOG.exception('failed to add template %s', e)
|
||||||
|
abort(404, str(e))
|
||||||
|
|
||||||
@pecan.expose('json')
|
@pecan.expose('json')
|
||||||
def post(self, **kwargs):
|
def post(self, **kwargs):
|
||||||
|
|
||||||
@ -84,30 +118,25 @@ class TemplateController(RootRestController):
|
|||||||
LOG.exception('failed to validate template(s) %s', to_unicode)
|
LOG.exception('failed to validate template(s) %s', to_unicode)
|
||||||
abort(404, to_unicode)
|
abort(404, to_unicode)
|
||||||
|
|
||||||
@staticmethod
|
@classmethod
|
||||||
def _get_templates():
|
def _get_templates(cls):
|
||||||
templates_json = pecan.request.client.call(pecan.request.context,
|
|
||||||
'get_templates')
|
|
||||||
LOG.info(templates_json)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
template_list = json.loads(templates_json)['templates_details']
|
templates = pecan.request.storage.templates.query()
|
||||||
return template_list
|
templates = [t for t in templates if t.status != TStatus.DELETED]
|
||||||
|
templates.sort(key=lambda template: template.created_at)
|
||||||
|
return [cls._db_template_to_dict(t) for t in templates]
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
to_unicode = encodeutils.exception_to_unicode(e)
|
to_unicode = encodeutils.exception_to_unicode(e)
|
||||||
LOG.exception('failed to get template list %s ', to_unicode)
|
LOG.exception('failed to get template list %s ', to_unicode)
|
||||||
abort(404, to_unicode)
|
abort(404, to_unicode)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _show_template(template_uuid):
|
def _show_template(uuid):
|
||||||
|
|
||||||
template_json = pecan.request.client.call(pecan.request.context,
|
|
||||||
'show_template',
|
|
||||||
template_uuid=template_uuid)
|
|
||||||
LOG.info(template_json)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return json.loads(template_json)
|
templates = pecan.request.storage.templates.query(uuid=uuid)
|
||||||
|
if not templates:
|
||||||
|
raise VitrageError("Template %s not found", uuid)
|
||||||
|
return templates[0].file_content
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
to_unicode = encodeutils.exception_to_unicode(e)
|
to_unicode = encodeutils.exception_to_unicode(e)
|
||||||
LOG.exception('failed to show template with uuid: %s ', to_unicode)
|
LOG.exception('failed to show template with uuid: %s ', to_unicode)
|
||||||
@ -125,3 +154,44 @@ class TemplateController(RootRestController):
|
|||||||
to_unicode = encodeutils.exception_to_unicode(e)
|
to_unicode = encodeutils.exception_to_unicode(e)
|
||||||
LOG.exception('failed to open template file(s) %s ', to_unicode)
|
LOG.exception('failed to open template file(s) %s ', to_unicode)
|
||||||
abort(404, to_unicode)
|
abort(404, to_unicode)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _add(cls, path, template_type):
|
||||||
|
try:
|
||||||
|
templates = template_repo.add_template_to_db(
|
||||||
|
pecan.request.storage, path, template_type)
|
||||||
|
pecan.request.client.call(pecan.request.context, 'add_template')
|
||||||
|
return [cls._db_template_to_dict(t) for t in templates]
|
||||||
|
except Exception as e:
|
||||||
|
LOG.exception('failed to add template file %s ', e)
|
||||||
|
abort(404, str(e))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _db_template_to_dict(cls, template):
|
||||||
|
return {
|
||||||
|
"uuid": template.uuid,
|
||||||
|
"name": template.name,
|
||||||
|
"status": template.status,
|
||||||
|
"date": template.created_at,
|
||||||
|
"status details": template.status_details,
|
||||||
|
"type": template.template_type,
|
||||||
|
}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _delete(uuid):
|
||||||
|
try:
|
||||||
|
storage = pecan.request.storage
|
||||||
|
templates = storage.templates.query(uuid=uuid)
|
||||||
|
if not templates:
|
||||||
|
raise VitrageError('template does not exists')
|
||||||
|
elif templates[0].status == TStatus.DELETED:
|
||||||
|
raise VitrageError('template is deleted')
|
||||||
|
elif templates[0].status == TStatus.ERROR:
|
||||||
|
storage.templates.update(uuid, "status", TStatus.DELETED)
|
||||||
|
elif templates[0].status == TStatus.ACTIVE:
|
||||||
|
storage.templates.update(uuid, "status", TStatus.DELETING)
|
||||||
|
pecan.request.client.call(pecan.request.context,
|
||||||
|
'delete_template')
|
||||||
|
except Exception as e:
|
||||||
|
LOG.exception('failed to delete template file %s ', e)
|
||||||
|
abort(404, str(e))
|
||||||
|
@ -16,7 +16,6 @@ import json
|
|||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
from osprofiler import profiler
|
from osprofiler import profiler
|
||||||
|
|
||||||
from vitrage.evaluator.template_fields import TemplateFields
|
|
||||||
from vitrage.evaluator.template_validation.content.template_content_validator \
|
from vitrage.evaluator.template_validation.content.template_content_validator \
|
||||||
import content_validation
|
import content_validation
|
||||||
from vitrage.evaluator.template_validation.status_messages import status_msgs
|
from vitrage.evaluator.template_validation.status_messages import status_msgs
|
||||||
@ -34,41 +33,8 @@ class TemplateApis(object):
|
|||||||
FAILED_MSG = 'validation failed'
|
FAILED_MSG = 'validation failed'
|
||||||
OK_MSG = 'validation OK'
|
OK_MSG = 'validation OK'
|
||||||
|
|
||||||
def __init__(self, templates, def_templates=None):
|
def __init__(self, notifier=None):
|
||||||
|
self.notifier = notifier
|
||||||
if def_templates is None:
|
|
||||||
def_templates = {}
|
|
||||||
|
|
||||||
self.def_templates = def_templates
|
|
||||||
self.templates = templates
|
|
||||||
|
|
||||||
def get_templates(self, ctx):
|
|
||||||
LOG.debug("TemplateApis get_templates")
|
|
||||||
|
|
||||||
templates_details = []
|
|
||||||
for uuid, template in self.templates.items():
|
|
||||||
|
|
||||||
template_metadata = template.data[TemplateFields.METADATA]
|
|
||||||
|
|
||||||
templates_details.append({
|
|
||||||
'uuid': str(template.uuid),
|
|
||||||
'name': template_metadata[TemplateFields.NAME],
|
|
||||||
'status': self._get_template_status(template.result),
|
|
||||||
'status details': template.result.comment,
|
|
||||||
'date': template.date.strftime('%Y-%m-%dT%H:%M:%SZ')
|
|
||||||
})
|
|
||||||
return json.dumps({'templates_details': templates_details})
|
|
||||||
|
|
||||||
def show_template(self, ctx, template_uuid):
|
|
||||||
|
|
||||||
LOG.debug("Show template with uuid: %s", str(template_uuid))
|
|
||||||
|
|
||||||
template = self.templates[template_uuid]
|
|
||||||
|
|
||||||
if template:
|
|
||||||
return json.dumps(template.data)
|
|
||||||
else:
|
|
||||||
return json.dumps({'ERROR': 'Incorrect uuid'})
|
|
||||||
|
|
||||||
def validate_template(self, ctx, templates):
|
def validate_template(self, ctx, templates):
|
||||||
LOG.debug("TemplateApis validate_template templates:"
|
LOG.debug("TemplateApis validate_template templates:"
|
||||||
@ -111,6 +77,23 @@ class TemplateApis(object):
|
|||||||
|
|
||||||
return json.dumps({'results': results})
|
return json.dumps({'results': results})
|
||||||
|
|
||||||
|
def add_template(self, ctx):
|
||||||
|
"""Signal the evaluator
|
||||||
|
|
||||||
|
A new template has been added to the database with a status of
|
||||||
|
LOADING that needs to be handled.
|
||||||
|
"""
|
||||||
|
LOG.info("Add Template Running")
|
||||||
|
self.notifier.notify("add template", {'template_action': 'add'})
|
||||||
|
|
||||||
|
def delete_template(self, ctx):
|
||||||
|
"""Signal the evaluator
|
||||||
|
|
||||||
|
A template status has been changed to DELETING.
|
||||||
|
"""
|
||||||
|
LOG.info("Delete Template Running")
|
||||||
|
self.notifier.notify("delete template", {'template_action': 'delete'})
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _add_result(template_path, status, description, message, status_code,
|
def _add_result(template_path, status, description, message, status_code,
|
||||||
results):
|
results):
|
||||||
|
@ -16,6 +16,9 @@ from oslo_log import log
|
|||||||
import oslo_messaging
|
import oslo_messaging
|
||||||
from oslo_service import service as os_service
|
from oslo_service import service as os_service
|
||||||
|
|
||||||
|
from vitrage.entity_graph import EVALUATOR_TOPIC
|
||||||
|
from vitrage.messaging import VitrageNotifier
|
||||||
|
|
||||||
from vitrage.api_handler.apis.alarm import AlarmApis
|
from vitrage.api_handler.apis.alarm import AlarmApis
|
||||||
from vitrage.api_handler.apis.event import EventApis
|
from vitrage.api_handler.apis.event import EventApis
|
||||||
from vitrage.api_handler.apis.rca import RcaApis
|
from vitrage.api_handler.apis.rca import RcaApis
|
||||||
@ -30,11 +33,12 @@ LOG = log.getLogger(__name__)
|
|||||||
|
|
||||||
class VitrageApiHandlerService(os_service.Service):
|
class VitrageApiHandlerService(os_service.Service):
|
||||||
|
|
||||||
def __init__(self, conf, e_graph, scenario_repo):
|
def __init__(self, conf, e_graph):
|
||||||
super(VitrageApiHandlerService, self).__init__()
|
super(VitrageApiHandlerService, self).__init__()
|
||||||
self.conf = conf
|
self.conf = conf
|
||||||
self.entity_graph = e_graph
|
self.entity_graph = e_graph
|
||||||
self.scenario_repo = scenario_repo
|
self.notifier = VitrageNotifier(self.conf, "vitrage.api",
|
||||||
|
EVALUATOR_TOPIC)
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
LOG.info("Vitrage Api Handler Service - Starting...")
|
LOG.info("Vitrage Api Handler Service - Starting...")
|
||||||
@ -49,9 +53,7 @@ class VitrageApiHandlerService(os_service.Service):
|
|||||||
endpoints = [TopologyApis(self.entity_graph, self.conf),
|
endpoints = [TopologyApis(self.entity_graph, self.conf),
|
||||||
AlarmApis(self.entity_graph, self.conf),
|
AlarmApis(self.entity_graph, self.conf),
|
||||||
RcaApis(self.entity_graph, self.conf),
|
RcaApis(self.entity_graph, self.conf),
|
||||||
TemplateApis(
|
TemplateApis(self.notifier),
|
||||||
self.scenario_repo.templates,
|
|
||||||
self.scenario_repo.def_templates),
|
|
||||||
EventApis(self.conf),
|
EventApis(self.conf),
|
||||||
ResourceApis(self.entity_graph, self.conf)]
|
ResourceApis(self.entity_graph, self.conf)]
|
||||||
|
|
||||||
|
@ -21,11 +21,12 @@ from vitrage.api_handler.service import VitrageApiHandlerService
|
|||||||
from vitrage.cli import VITRAGE_TITLE
|
from vitrage.cli import VITRAGE_TITLE
|
||||||
from vitrage import entity_graph
|
from vitrage import entity_graph
|
||||||
from vitrage.entity_graph.consistency.service import VitrageConsistencyService
|
from vitrage.entity_graph.consistency.service import VitrageConsistencyService
|
||||||
from vitrage.entity_graph.service import VitrageGraphService
|
|
||||||
from vitrage.evaluator.scenario_repository import ScenarioRepository
|
|
||||||
from vitrage import service
|
from vitrage import service
|
||||||
from vitrage import storage
|
from vitrage import storage
|
||||||
|
|
||||||
|
from vitrage.entity_graph.service import VitrageGraphService
|
||||||
|
from vitrage.evaluator.evaluator_service import EvaluatorManager
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
"""Starts all the Entity graph services
|
"""Starts all the Entity graph services
|
||||||
@ -38,28 +39,28 @@ def main():
|
|||||||
print(VITRAGE_TITLE)
|
print(VITRAGE_TITLE)
|
||||||
conf = service.prepare_service()
|
conf = service.prepare_service()
|
||||||
e_graph = entity_graph.get_graph_driver(conf)('Entity Graph')
|
e_graph = entity_graph.get_graph_driver(conf)('Entity Graph')
|
||||||
|
evaluator = EvaluatorManager(conf, e_graph)
|
||||||
launcher = os_service.ServiceLauncher(conf)
|
launcher = os_service.ServiceLauncher(conf)
|
||||||
full_scenario_repo = ScenarioRepository(conf)
|
db_connection = storage.get_connection_from_config(conf)
|
||||||
clear_db(conf)
|
clear_active_actions_table(db_connection)
|
||||||
|
|
||||||
launcher.launch_service(VitrageGraphService(conf, e_graph))
|
launcher.launch_service(VitrageGraphService(
|
||||||
|
conf, e_graph, evaluator, db_connection))
|
||||||
|
|
||||||
launcher.launch_service(VitrageApiHandlerService(
|
launcher.launch_service(VitrageApiHandlerService(conf, e_graph))
|
||||||
conf, e_graph, full_scenario_repo))
|
|
||||||
|
|
||||||
launcher.launch_service(VitrageConsistencyService(conf, e_graph))
|
launcher.launch_service(VitrageConsistencyService(conf, e_graph))
|
||||||
|
|
||||||
launcher.wait()
|
launcher.wait()
|
||||||
|
|
||||||
|
|
||||||
def clear_db(conf):
|
def clear_active_actions_table(db_connection):
|
||||||
"""Delete all data from vitrage tables
|
"""Delete all data from active_actions table
|
||||||
|
|
||||||
The following deletes the entire vitrage database
|
The following deletes the entire vitrage database
|
||||||
It should be removed once graph is persistent
|
It should be removed once graph is persistent
|
||||||
"""
|
"""
|
||||||
db_connection = storage.get_connection_from_config(conf)
|
db_connection.active_actions.delete()
|
||||||
db_connection.clear()
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
sys.exit(main())
|
sys.exit(main())
|
||||||
|
@ -161,3 +161,11 @@ class TemplateTypes(object):
|
|||||||
STANDARD = 'standard'
|
STANDARD = 'standard'
|
||||||
DEFINITION = 'definition'
|
DEFINITION = 'definition'
|
||||||
EQUIVALENCE = 'equivalence'
|
EQUIVALENCE = 'equivalence'
|
||||||
|
|
||||||
|
|
||||||
|
class TemplateStatus(object):
|
||||||
|
ACTIVE = 'ACTIVE'
|
||||||
|
ERROR = 'ERROR'
|
||||||
|
DELETING = 'DELETING'
|
||||||
|
DELETED = 'DELETED'
|
||||||
|
LOADING = 'LOADING'
|
||||||
|
@ -17,6 +17,28 @@ from vitrage.common.policies import base
|
|||||||
TEMPLATE = 'template %s'
|
TEMPLATE = 'template %s'
|
||||||
|
|
||||||
rules = [
|
rules = [
|
||||||
|
policy.DocumentedRuleDefault(
|
||||||
|
name=TEMPLATE % 'delete',
|
||||||
|
check_str=base.UNPROTECTED,
|
||||||
|
description='Delete a template',
|
||||||
|
operations=[
|
||||||
|
{
|
||||||
|
'path': '/template',
|
||||||
|
'method': 'DELETE'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
),
|
||||||
|
policy.DocumentedRuleDefault(
|
||||||
|
name=TEMPLATE % 'add',
|
||||||
|
check_str=base.UNPROTECTED,
|
||||||
|
description='Add a template',
|
||||||
|
operations=[
|
||||||
|
{
|
||||||
|
'path': '/template',
|
||||||
|
'method': 'PUT'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
),
|
||||||
policy.DocumentedRuleDefault(
|
policy.DocumentedRuleDefault(
|
||||||
name=TEMPLATE % 'validate',
|
name=TEMPLATE % 'validate',
|
||||||
check_str=base.UNPROTECTED,
|
check_str=base.UNPROTECTED,
|
||||||
|
@ -21,7 +21,7 @@ from oslo_service import service as os_service
|
|||||||
from vitrage.entity_graph import EVALUATOR_TOPIC
|
from vitrage.entity_graph import EVALUATOR_TOPIC
|
||||||
from vitrage.entity_graph.processor.processor import Processor
|
from vitrage.entity_graph.processor.processor import Processor
|
||||||
from vitrage.entity_graph.vitrage_init import VitrageInit
|
from vitrage.entity_graph.vitrage_init import VitrageInit
|
||||||
from vitrage.evaluator.evaluator_service import EvaluatorManager
|
from vitrage.evaluator.template_loader_service import TemplateLoaderManager
|
||||||
from vitrage import messaging
|
from vitrage import messaging
|
||||||
from vitrage.persistency.graph_persistor import GraphPersistor
|
from vitrage.persistency.graph_persistor import GraphPersistor
|
||||||
|
|
||||||
@ -32,12 +32,16 @@ class VitrageGraphService(os_service.Service):
|
|||||||
|
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
conf,
|
conf,
|
||||||
graph):
|
graph,
|
||||||
|
evaluator,
|
||||||
|
db):
|
||||||
super(VitrageGraphService, self).__init__()
|
super(VitrageGraphService, self).__init__()
|
||||||
self.conf = conf
|
self.conf = conf
|
||||||
self.graph = graph
|
self.graph = graph
|
||||||
self.evaluator = EvaluatorManager(conf, graph)
|
self.evaluator = evaluator
|
||||||
self.init = VitrageInit(conf, graph, self.evaluator)
|
self.templates_loader = TemplateLoaderManager(conf, graph, db)
|
||||||
|
self.init = VitrageInit(conf, graph, self.evaluator,
|
||||||
|
self.templates_loader)
|
||||||
self.graph_persistor = GraphPersistor(conf) if \
|
self.graph_persistor = GraphPersistor(conf) if \
|
||||||
self.conf.persistency.enable_persistency else None
|
self.conf.persistency.enable_persistency else None
|
||||||
self.processor = Processor(self.conf, self.init, graph,
|
self.processor = Processor(self.conf, self.init, graph,
|
||||||
@ -49,10 +53,17 @@ class VitrageGraphService(os_service.Service):
|
|||||||
evaluator_topic = EVALUATOR_TOPIC
|
evaluator_topic = EVALUATOR_TOPIC
|
||||||
return TwoPriorityListener(
|
return TwoPriorityListener(
|
||||||
self.conf,
|
self.conf,
|
||||||
self.processor.process_event,
|
self.process_event,
|
||||||
collector_topic,
|
collector_topic,
|
||||||
evaluator_topic)
|
evaluator_topic)
|
||||||
|
|
||||||
|
def process_event(self, event):
|
||||||
|
if event.get('template_action'):
|
||||||
|
self.templates_loader.handle_template_event(event)
|
||||||
|
self.evaluator.reload_evaluators_templates()
|
||||||
|
else:
|
||||||
|
self.processor.process_event(event)
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
LOG.info("Vitrage Graph Service - Starting...")
|
LOG.info("Vitrage Graph Service - Starting...")
|
||||||
super(VitrageGraphService, self).start()
|
super(VitrageGraphService, self).start()
|
||||||
|
@ -24,10 +24,11 @@ class VitrageInit(object):
|
|||||||
RECEIVED_ALL_END_MESSAGES = 'received_all_end_messages'
|
RECEIVED_ALL_END_MESSAGES = 'received_all_end_messages'
|
||||||
FINISHED = 'finished'
|
FINISHED = 'finished'
|
||||||
|
|
||||||
def __init__(self, conf, graph=None, evaluator=None):
|
def __init__(self, conf, graph=None, evaluator=None, template_loader=None):
|
||||||
self.conf = conf
|
self.conf = conf
|
||||||
self.graph = graph
|
self.graph = graph
|
||||||
self.evaluator = evaluator
|
self.evaluator = evaluator
|
||||||
|
self.template_loader = template_loader
|
||||||
self.status = self.STARTED
|
self.status = self.STARTED
|
||||||
self.end_messages = {}
|
self.end_messages = {}
|
||||||
|
|
||||||
@ -44,6 +45,8 @@ class VitrageInit(object):
|
|||||||
on_end_messages_func()
|
on_end_messages_func()
|
||||||
|
|
||||||
self.evaluator.start()
|
self.evaluator.start()
|
||||||
|
if self.template_loader:
|
||||||
|
self.template_loader.start()
|
||||||
|
|
||||||
# TODO(idan_hefetz) As vitrage is not yet persistent, there aren't
|
# TODO(idan_hefetz) As vitrage is not yet persistent, there aren't
|
||||||
# TODO(idan_hefetz) any deduced alarms to be removed during init
|
# TODO(idan_hefetz) any deduced alarms to be removed during init
|
||||||
|
@ -16,7 +16,6 @@ from oslo_config import cfg
|
|||||||
|
|
||||||
from vitrage.evaluator.template_schemas import init_template_schemas
|
from vitrage.evaluator.template_schemas import init_template_schemas
|
||||||
|
|
||||||
|
|
||||||
# Register options for the service
|
# Register options for the service
|
||||||
OPTS = [
|
OPTS = [
|
||||||
cfg.StrOpt('templates_dir',
|
cfg.StrOpt('templates_dir',
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
import re
|
import re
|
||||||
|
|
||||||
Template = namedtuple('Template', ['uuid', 'data', 'date', 'result'])
|
Template = namedtuple('Template', ['uuid', 'data', 'date'])
|
||||||
|
|
||||||
|
|
||||||
def is_function(str):
|
def is_function(str):
|
||||||
|
@ -11,20 +11,26 @@
|
|||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
from vitrage.common.constants import TemplateStatus
|
||||||
|
from vitrage.common.constants import TemplateTypes
|
||||||
from vitrage.common.exception import VitrageError
|
from vitrage.common.exception import VitrageError
|
||||||
from vitrage.evaluator.template_loading.equivalence_loader import \
|
from vitrage.evaluator.template_loading.equivalence_loader import \
|
||||||
EquivalenceLoader
|
EquivalenceLoader
|
||||||
from vitrage.utils import file as file_utils
|
|
||||||
|
from oslo_log import log as logging
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class EquivalenceRepository(object):
|
class EquivalenceRepository(object):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.entity_equivalences = {}
|
self.entity_equivalences = {}
|
||||||
|
|
||||||
def load_files(self, directory):
|
def load(self, db):
|
||||||
equivalence_defs = file_utils.load_yaml_files(directory)
|
equivalence_defs = db.templates.query(
|
||||||
|
template_type=TemplateTypes.EQUIVALENCE,
|
||||||
|
status=TemplateStatus.ACTIVE)
|
||||||
|
equivalence_defs = [e.file_content for e in equivalence_defs]
|
||||||
for equivalence_def in equivalence_defs:
|
for equivalence_def in equivalence_defs:
|
||||||
equivalences = EquivalenceLoader(equivalence_def).equivalences
|
equivalences = EquivalenceLoader(equivalence_def).equivalences
|
||||||
for equivalence in equivalences:
|
for equivalence in equivalences:
|
||||||
|
@ -99,4 +99,8 @@ class EvaluatorWorker(base.GraphCloneWorkerBase):
|
|||||||
self._reload_templates()
|
self._reload_templates()
|
||||||
|
|
||||||
def _reload_templates(self):
|
def _reload_templates(self):
|
||||||
raise NotImplementedError()
|
scenario_repo = ScenarioRepository(self._conf, self._worker_index,
|
||||||
|
self._workers_num)
|
||||||
|
self._evaluator.scenario_repo = scenario_repo
|
||||||
|
LOG.info("reloading evaluator scenarios")
|
||||||
|
self._evaluator.scenario_repo.log_enabled_scenarios()
|
||||||
|
@ -81,14 +81,18 @@ class ScenarioEvaluator(object):
|
|||||||
def scenario_repo(self, scenario_repo):
|
def scenario_repo(self, scenario_repo):
|
||||||
self._scenario_repo = scenario_repo
|
self._scenario_repo = scenario_repo
|
||||||
|
|
||||||
def run_evaluator(self):
|
def run_evaluator(self, action_mode=ActionMode.DO):
|
||||||
self.enabled = True
|
self.enabled = True
|
||||||
vertices = self._entity_graph.get_vertices()
|
vertices = self._entity_graph.get_vertices()
|
||||||
start_time = time.time()
|
start_time = time.time()
|
||||||
for vertex in vertices:
|
for vertex in vertices:
|
||||||
self.process_event(None, vertex, True)
|
if action_mode == ActionMode.DO:
|
||||||
LOG.info('Run Evaluator on %s items - took %s', str(len(vertices)),
|
self.process_event(None, vertex, True)
|
||||||
str(time.time() - start_time))
|
elif action_mode == ActionMode.UNDO:
|
||||||
|
self.process_event(vertex, None, True)
|
||||||
|
LOG.info(
|
||||||
|
'Run %s Evaluator on %s items - took %s',
|
||||||
|
action_mode, str(len(vertices)), str(time.time() - start_time))
|
||||||
|
|
||||||
def process_event(self, before, current, is_vertex, *args, **kwargs):
|
def process_event(self, before, current, is_vertex, *args, **kwargs):
|
||||||
"""Notification of a change in the entity graph.
|
"""Notification of a change in the entity graph.
|
||||||
|
@ -17,26 +17,18 @@ from collections import namedtuple
|
|||||||
import itertools
|
import itertools
|
||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
|
|
||||||
from oslo_utils import uuidutils
|
from vitrage.common.constants import TemplateStatus
|
||||||
|
from vitrage.common.constants import TemplateTypes as TType
|
||||||
from vitrage.common.utils import get_portion
|
from vitrage.common.utils import get_portion
|
||||||
from vitrage.evaluator.base import Template
|
from vitrage.evaluator.base import Template
|
||||||
from vitrage.evaluator.equivalence_repository import EquivalenceRepository
|
from vitrage.evaluator.equivalence_repository import EquivalenceRepository
|
||||||
from vitrage.evaluator.template_fields import TemplateFields
|
from vitrage.evaluator.template_fields import TemplateFields
|
||||||
from vitrage.evaluator.template_loading.scenario_loader import ScenarioLoader
|
from vitrage.evaluator.template_loading.scenario_loader import ScenarioLoader
|
||||||
from vitrage.evaluator.template_loading.template_loader import TemplateLoader
|
from vitrage.evaluator.template_loading.template_loader import TemplateLoader
|
||||||
from vitrage.evaluator.template_validation.content.base import \
|
|
||||||
get_template_schema
|
|
||||||
from vitrage.evaluator.template_validation.content.template_content_validator \
|
|
||||||
import content_validation
|
|
||||||
from vitrage.evaluator.template_validation.template_syntax_validator import \
|
|
||||||
def_template_syntax_validation
|
|
||||||
from vitrage.evaluator.template_validation.template_syntax_validator import \
|
from vitrage.evaluator.template_validation.template_syntax_validator import \
|
||||||
EXCEPTION
|
EXCEPTION
|
||||||
from vitrage.evaluator.template_validation.template_syntax_validator import \
|
|
||||||
syntax_validation
|
|
||||||
from vitrage.graph.filter import check_filter as check_subset
|
from vitrage.graph.filter import check_filter as check_subset
|
||||||
from vitrage.utils import datetime as datetime_utils
|
from vitrage import storage
|
||||||
from vitrage.utils import file as file_utils
|
from vitrage.utils import file as file_utils
|
||||||
|
|
||||||
LOG = log.getLogger(__name__)
|
LOG = log.getLogger(__name__)
|
||||||
@ -56,12 +48,12 @@ class ScenarioRepository(object):
|
|||||||
self._templates = {}
|
self._templates = {}
|
||||||
self._def_templates = {}
|
self._def_templates = {}
|
||||||
self._all_scenarios = []
|
self._all_scenarios = []
|
||||||
self.entity_equivalences = EquivalenceRepository().load_files(
|
self._db = storage.get_connection_from_config(conf)
|
||||||
conf.evaluator.equivalences_dir)
|
self.entity_equivalences = EquivalenceRepository().load(self._db)
|
||||||
self.relationship_scenarios = defaultdict(list)
|
self.relationship_scenarios = defaultdict(list)
|
||||||
self.entity_scenarios = defaultdict(list)
|
self.entity_scenarios = defaultdict(list)
|
||||||
self._load_def_template_files(conf)
|
self._load_def_templates_from_db()
|
||||||
self._load_templates_files(conf)
|
self._load_templates_from_db()
|
||||||
self._enable_worker_scenarios(worker_index, workers_num)
|
self._enable_worker_scenarios(worker_index, workers_num)
|
||||||
self.actions = self._create_actions_collection()
|
self.actions = self._create_actions_collection()
|
||||||
|
|
||||||
@ -108,57 +100,21 @@ class ScenarioRepository(object):
|
|||||||
|
|
||||||
return scenarios
|
return scenarios
|
||||||
|
|
||||||
def add_template(self, template_def):
|
def _add_template(self, template):
|
||||||
|
self.templates[template.uuid] = Template(template.uuid,
|
||||||
|
template.file_content,
|
||||||
|
template.created_at)
|
||||||
|
template_data = TemplateLoader().load(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)
|
||||||
|
|
||||||
result = syntax_validation(template_def)
|
def _add_def_template(self, def_template):
|
||||||
|
self.def_templates[def_template.uuid] = Template(
|
||||||
if not result.is_valid_config:
|
def_template.uuid,
|
||||||
LOG.info('Unable to load template, syntax err: %s'
|
def_template.file_content,
|
||||||
% result.comment)
|
def_template.created_at)
|
||||||
else:
|
|
||||||
result = content_validation(template_def, self._def_templates)
|
|
||||||
if not result.is_valid_config:
|
|
||||||
LOG.info('Unable to load template, content err: %s'
|
|
||||||
% result.comment)
|
|
||||||
|
|
||||||
template_uuid = uuidutils.generate_uuid()
|
|
||||||
current_time = datetime_utils.utcnow()
|
|
||||||
self.templates[str(template_uuid)] = Template(template_uuid,
|
|
||||||
template_def,
|
|
||||||
current_time,
|
|
||||||
result)
|
|
||||||
if result.is_valid_config:
|
|
||||||
template_data = \
|
|
||||||
TemplateLoader().load(template_def, self._def_templates)
|
|
||||||
for scenario in template_data.scenarios:
|
|
||||||
for equivalent_scenario in self._expand_equivalence(scenario):
|
|
||||||
self._add_scenario(equivalent_scenario)
|
|
||||||
|
|
||||||
def add_def_template(self, def_template):
|
|
||||||
result, template_schema = get_template_schema(def_template)
|
|
||||||
|
|
||||||
if result.is_valid_config:
|
|
||||||
result = def_template_syntax_validation(def_template)
|
|
||||||
if not result.is_valid_config:
|
|
||||||
LOG.info('Unable to load definition template, syntax err: %s'
|
|
||||||
% result.comment)
|
|
||||||
|
|
||||||
if result.is_valid_config:
|
|
||||||
def_validator = \
|
|
||||||
template_schema.validators.get(TemplateFields.DEFINITIONS)
|
|
||||||
result = \
|
|
||||||
def_validator.def_template_content_validation(def_template)
|
|
||||||
|
|
||||||
if result.is_valid_config:
|
|
||||||
current_time = datetime_utils.utcnow()
|
|
||||||
include_uuid = uuidutils.generate_uuid()
|
|
||||||
self._def_templates[str(include_uuid)] = Template(include_uuid,
|
|
||||||
def_template,
|
|
||||||
current_time,
|
|
||||||
result)
|
|
||||||
else:
|
|
||||||
LOG.info('Unable to load definition template, content err: %s'
|
|
||||||
% result.comment)
|
|
||||||
|
|
||||||
def _expand_equivalence(self, scenario):
|
def _expand_equivalence(self, scenario):
|
||||||
equivalent_scenarios = [scenario]
|
equivalent_scenarios = [scenario]
|
||||||
@ -191,29 +147,21 @@ class ScenarioRepository(object):
|
|||||||
self._add_relationship_scenario(scenario, relationship)
|
self._add_relationship_scenario(scenario, relationship)
|
||||||
self._all_scenarios.append(scenario)
|
self._all_scenarios.append(scenario)
|
||||||
|
|
||||||
def _load_def_template_files(self, conf):
|
def _load_def_templates_from_db(self):
|
||||||
|
def_templates = self._db.templates.query(
|
||||||
|
template_type=TType.DEFINITION,
|
||||||
|
status=TemplateStatus.ACTIVE)
|
||||||
|
for def_template in def_templates:
|
||||||
|
self._add_def_template(def_template)
|
||||||
|
|
||||||
if DEF_TEMPLATES_DIR_OPT in conf.evaluator:
|
def _load_templates_from_db(self):
|
||||||
|
items = self._db.templates.query(template_type=TType.STANDARD)
|
||||||
def_templates_dir = conf.evaluator.def_templates_dir
|
# TODO(ikinory): statuses may cause loading templates to be running
|
||||||
def_templates = file_utils.load_yaml_files(def_templates_dir)
|
templates = [x for x in items if x.status in [TemplateStatus.ACTIVE,
|
||||||
|
TemplateStatus.LOADING,
|
||||||
for def_template in def_templates:
|
TemplateStatus.DELETING]]
|
||||||
self.add_def_template(def_template)
|
for t in templates:
|
||||||
|
self._add_template(t)
|
||||||
def _load_templates_files(self, conf):
|
|
||||||
|
|
||||||
templates_dir = conf.evaluator.templates_dir
|
|
||||||
|
|
||||||
files = \
|
|
||||||
file_utils.list_files(templates_dir, '.yaml', with_pathname=True)
|
|
||||||
|
|
||||||
template_defs = []
|
|
||||||
for f in files:
|
|
||||||
template_defs.append(self._load_template_file(f))
|
|
||||||
|
|
||||||
for template_def in template_defs:
|
|
||||||
self.add_template(template_def)
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _load_template_file(file_name):
|
def _load_template_file(file_name):
|
||||||
|
15
vitrage/evaluator/template_db/__init__.py
Normal file
15
vitrage/evaluator/template_db/__init__.py
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
# Copyright 2018 - Nokia
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
__author__ = 'stack'
|
126
vitrage/evaluator/template_db/template_repository.py
Normal file
126
vitrage/evaluator/template_db/template_repository.py
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
# Copyright 2018 - Nokia
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
import os
|
||||||
|
|
||||||
|
from oslo_log import log
|
||||||
|
from oslo_utils import uuidutils
|
||||||
|
|
||||||
|
from vitrage.common.constants import TemplateStatus
|
||||||
|
from vitrage.common.constants import TemplateTypes as TType
|
||||||
|
from vitrage.common.exception import VitrageError
|
||||||
|
from vitrage.evaluator.base import Template
|
||||||
|
from vitrage.evaluator.template_fields import TemplateFields
|
||||||
|
from vitrage.evaluator import template_validation
|
||||||
|
from vitrage.evaluator.template_validation import base
|
||||||
|
from vitrage.evaluator.template_validation.template_syntax_validator import \
|
||||||
|
EXCEPTION
|
||||||
|
from vitrage.storage.sqlalchemy import models
|
||||||
|
from vitrage.utils import file
|
||||||
|
|
||||||
|
LOG = log.getLogger(__name__)
|
||||||
|
|
||||||
|
METADATA = 'metadata'
|
||||||
|
NAME = 'name'
|
||||||
|
|
||||||
|
|
||||||
|
def add_template_to_db(db, path, template_type):
|
||||||
|
"""Add templates to db
|
||||||
|
|
||||||
|
Loads template files, for every template, check it is valid and does
|
||||||
|
not exist, if so adds it to the database.
|
||||||
|
|
||||||
|
:param db:
|
||||||
|
:param path: path to a file or directory
|
||||||
|
:param template_type: standard/definition/equivalence
|
||||||
|
:return: all the templates that were added
|
||||||
|
:rtype: list of models.Template
|
||||||
|
"""
|
||||||
|
|
||||||
|
added_rows = list()
|
||||||
|
files = _list_files(path)
|
||||||
|
for f in files:
|
||||||
|
template = load_template_file(f)
|
||||||
|
template_name = template[METADATA][NAME]
|
||||||
|
|
||||||
|
templates = db.templates.query(name=template_name)
|
||||||
|
if [t for t in templates if t.status != TemplateStatus.DELETED]:
|
||||||
|
LOG.warning("Duplicate templates found %s."
|
||||||
|
" new template will not be added", template_name)
|
||||||
|
else:
|
||||||
|
validation_result = _validate_template(db, template, template_type)
|
||||||
|
db_row = _to_db_row(validation_result, template, template_type)
|
||||||
|
db.templates.create(db_row)
|
||||||
|
added_rows.append(db_row)
|
||||||
|
return added_rows
|
||||||
|
|
||||||
|
|
||||||
|
def _list_files(path):
|
||||||
|
if os.path.isdir(path):
|
||||||
|
LOG.info("Adding all templates from %s", path)
|
||||||
|
return file.list_files(path, '.yaml', with_pathname=True)
|
||||||
|
elif os.path.isfile(path):
|
||||||
|
LOG.info("Adding template %s", path)
|
||||||
|
return [path] # only one file
|
||||||
|
else:
|
||||||
|
raise VitrageError("No such file or directory %s" % path)
|
||||||
|
|
||||||
|
|
||||||
|
def _validate_template(db, template, template_type):
|
||||||
|
if template_type == TType.DEFINITION:
|
||||||
|
result = template_validation.validate_definition_template(template)
|
||||||
|
elif template_type == TType.STANDARD:
|
||||||
|
result = template_validation.validate_template(template,
|
||||||
|
_load_def_templates(db))
|
||||||
|
elif template_type == TType.EQUIVALENCE:
|
||||||
|
result = base.Result("", True, "", "No Validation")
|
||||||
|
else:
|
||||||
|
raise VitrageError("Unknown template type %s", template_type)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def load_template_file(file_name):
|
||||||
|
try:
|
||||||
|
return file.load_yaml_file(file_name, with_exception=True)
|
||||||
|
except Exception as e:
|
||||||
|
return {TemplateFields.METADATA: {TemplateFields.NAME: file_name},
|
||||||
|
EXCEPTION: str(e)}
|
||||||
|
|
||||||
|
|
||||||
|
def _to_db_row(result, template, template_type):
|
||||||
|
uuid = uuidutils.generate_uuid()
|
||||||
|
status = TemplateStatus.LOADING if result.is_valid_config else \
|
||||||
|
TemplateStatus.ERROR
|
||||||
|
status_details = result.comment
|
||||||
|
db_row = models.Template(
|
||||||
|
name=template[METADATA][NAME],
|
||||||
|
uuid=uuid,
|
||||||
|
status=status,
|
||||||
|
status_details=status_details,
|
||||||
|
file_content=template,
|
||||||
|
template_type=template_type,
|
||||||
|
)
|
||||||
|
return db_row
|
||||||
|
|
||||||
|
|
||||||
|
def _load_def_templates(db):
|
||||||
|
def_templates = {}
|
||||||
|
items = db.templates.query(template_type=TType.DEFINITION)
|
||||||
|
def_templates_db = [x for x in items if x.status in [
|
||||||
|
TemplateStatus.ACTIVE,
|
||||||
|
TemplateStatus.LOADING]]
|
||||||
|
for df in def_templates_db:
|
||||||
|
def_templates[df.uuid] = Template(df.uuid,
|
||||||
|
df.file_content,
|
||||||
|
df.created_at)
|
||||||
|
return def_templates
|
120
vitrage/evaluator/template_loader_service.py
Normal file
120
vitrage/evaluator/template_loader_service.py
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
import multiprocessing
|
||||||
|
|
||||||
|
from oslo_log import log
|
||||||
|
|
||||||
|
from vitrage.common.constants import TemplateStatus as TStatus
|
||||||
|
from vitrage.common.constants import TemplateTypes as TType
|
||||||
|
from vitrage.common.exception import VitrageError
|
||||||
|
from vitrage.entity_graph import EVALUATOR_TOPIC
|
||||||
|
from vitrage.entity_graph.graph_clone import base
|
||||||
|
from vitrage.evaluator.actions.base import ActionMode
|
||||||
|
from vitrage.evaluator.scenario_evaluator import ScenarioEvaluator
|
||||||
|
from vitrage.evaluator.scenario_repository import ScenarioRepository
|
||||||
|
from vitrage.messaging import VitrageNotifier
|
||||||
|
|
||||||
|
LOG = log.getLogger(__name__)
|
||||||
|
|
||||||
|
TEMPLATE_ACTION = 'template_action'
|
||||||
|
|
||||||
|
|
||||||
|
class TemplateLoaderManager(base.GraphCloneManagerBase):
|
||||||
|
|
||||||
|
def __init__(self, conf, entity_graph, db):
|
||||||
|
super(TemplateLoaderManager, self).__init__(conf, entity_graph, 1)
|
||||||
|
self._db = db
|
||||||
|
|
||||||
|
def _run_worker(self, worker_index, workers_num):
|
||||||
|
tasks_queue = multiprocessing.JoinableQueue()
|
||||||
|
w = TemplateLoaderWorker(
|
||||||
|
self._conf,
|
||||||
|
tasks_queue,
|
||||||
|
self._entity_graph)
|
||||||
|
self._p_launcher.launch_service(w)
|
||||||
|
return tasks_queue
|
||||||
|
|
||||||
|
def handle_template_event(self, event):
|
||||||
|
template_action = event.get('template_action')
|
||||||
|
|
||||||
|
if template_action == 'add':
|
||||||
|
templates = self._db.templates.query(status=TStatus.LOADING)
|
||||||
|
new_status = TStatus.ACTIVE
|
||||||
|
action_mode = ActionMode.DO
|
||||||
|
elif template_action == 'delete':
|
||||||
|
templates = self._db.templates.query(status=TStatus.DELETING)
|
||||||
|
new_status = TStatus.DELETED
|
||||||
|
action_mode = ActionMode.UNDO
|
||||||
|
else:
|
||||||
|
raise VitrageError('Invalid template_action %s' % template_action)
|
||||||
|
|
||||||
|
self._template_worker_task(
|
||||||
|
[t.name for t in templates if t.template_type == TType.STANDARD],
|
||||||
|
action_mode)
|
||||||
|
|
||||||
|
for t in templates:
|
||||||
|
self._db.templates.update(t.uuid, 'status', new_status)
|
||||||
|
|
||||||
|
def _template_worker_task(self, template_names, action_mode):
|
||||||
|
self._notify_and_wait((TEMPLATE_ACTION, template_names, action_mode))
|
||||||
|
|
||||||
|
|
||||||
|
class TemplateLoaderWorker(base.GraphCloneWorkerBase):
|
||||||
|
def __init__(self,
|
||||||
|
conf,
|
||||||
|
task_queue,
|
||||||
|
e_graph):
|
||||||
|
super(TemplateLoaderWorker, self).__init__(conf, task_queue, e_graph)
|
||||||
|
self._evaluator = None
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
super(TemplateLoaderWorker, self).start()
|
||||||
|
actions_callback = VitrageNotifier(
|
||||||
|
conf=self._conf,
|
||||||
|
publisher_id='vitrage_evaluator',
|
||||||
|
topic=EVALUATOR_TOPIC).notify
|
||||||
|
self._evaluator = ScenarioEvaluator(
|
||||||
|
self._conf,
|
||||||
|
self._entity_graph,
|
||||||
|
None,
|
||||||
|
actions_callback,
|
||||||
|
enabled=False)
|
||||||
|
|
||||||
|
def do_task(self, task):
|
||||||
|
super(TemplateLoaderWorker, self).do_task(task)
|
||||||
|
action = task[0]
|
||||||
|
if action == TEMPLATE_ACTION:
|
||||||
|
(action, template_names, action_mode) = task
|
||||||
|
self._template_action(template_names, action_mode)
|
||||||
|
|
||||||
|
def _template_action(self, template_names, action_mode):
|
||||||
|
self._enable_evaluator_templates(template_names)
|
||||||
|
self._evaluator.run_evaluator(action_mode)
|
||||||
|
self._disable_evaluator()
|
||||||
|
|
||||||
|
def _enable_evaluator_templates(self, template_names):
|
||||||
|
scenario_repo = ScenarioRepository(self._conf)
|
||||||
|
for s in scenario_repo._all_scenarios:
|
||||||
|
s.enabled = False
|
||||||
|
for template_name in template_names:
|
||||||
|
if s.id.startswith(template_name):
|
||||||
|
s.enabled = True
|
||||||
|
self._evaluator.scenario_repo = scenario_repo
|
||||||
|
self._evaluator.scenario_repo.log_enabled_scenarios()
|
||||||
|
self._evaluator.enabled = True
|
||||||
|
|
||||||
|
def _disable_evaluator(self):
|
||||||
|
self._entity_graph.notifier._subscriptions = [] # Quick n dirty
|
||||||
|
self._evaluator.enabled = False
|
@ -11,4 +11,48 @@
|
|||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
from oslo_log import log
|
||||||
|
|
||||||
|
from vitrage.evaluator.template_fields import TemplateFields
|
||||||
|
from vitrage.evaluator.template_validation.content.base import \
|
||||||
|
get_template_schema
|
||||||
|
from vitrage.evaluator.template_validation.content.template_content_validator \
|
||||||
|
import content_validation
|
||||||
|
from vitrage.evaluator.template_validation.template_syntax_validator import \
|
||||||
|
def_template_syntax_validation
|
||||||
|
from vitrage.evaluator.template_validation.template_syntax_validator import \
|
||||||
|
syntax_validation
|
||||||
|
|
||||||
__author__ = 'stack'
|
__author__ = 'stack'
|
||||||
|
|
||||||
|
LOG = log.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def validate_template(template, def_templates):
|
||||||
|
result = syntax_validation(template)
|
||||||
|
if not result.is_valid_config:
|
||||||
|
LOG.error('Unable to load template, syntax err: %s' % result.comment)
|
||||||
|
return result
|
||||||
|
result = content_validation(template, def_templates)
|
||||||
|
if not result.is_valid_config:
|
||||||
|
LOG.error('Unable to load template, content err: %s' % result.comment)
|
||||||
|
return result
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def validate_definition_template(template_def):
|
||||||
|
result, template_schema = get_template_schema(template_def)
|
||||||
|
if not result.is_valid_config:
|
||||||
|
return result
|
||||||
|
|
||||||
|
result = def_template_syntax_validation(template_def)
|
||||||
|
if not result.is_valid_config:
|
||||||
|
LOG.error('Unable to load template, syntax err: %s' % result.comment)
|
||||||
|
return result
|
||||||
|
|
||||||
|
validator = template_schema.validators.get(TemplateFields.DEFINITIONS)
|
||||||
|
result = validator.def_template_content_validation(template_def)
|
||||||
|
if not result.is_valid_config:
|
||||||
|
LOG.error('Unable to load template, content err: %s' % result.comment)
|
||||||
|
return result
|
||||||
|
return result
|
||||||
|
@ -121,7 +121,7 @@ class TemplatesConnection(object):
|
|||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def query(self, name=None, file_content=None,
|
def query(self, name=None, file_content=None,
|
||||||
uuid=None, status=None, status_details=None, is_deleted=None,
|
uuid=None, status=None, status_details=None,
|
||||||
template_type=None):
|
template_type=None):
|
||||||
"""Yields a lists of templates that match filters.
|
"""Yields a lists of templates that match filters.
|
||||||
|
|
||||||
|
@ -116,7 +116,7 @@ class TemplatesConnection(base.TemplatesConnection, BaseTableConn):
|
|||||||
session.query(Template).filter_by(uuid=uuid).update({var: value})
|
session.query(Template).filter_by(uuid=uuid).update({var: value})
|
||||||
|
|
||||||
def query(self, name=None, file_content=None,
|
def query(self, name=None, file_content=None,
|
||||||
uuid=None, status=None, status_details=None, is_deleted=None,
|
uuid=None, status=None, status_details=None,
|
||||||
template_type=None):
|
template_type=None):
|
||||||
query = self.query_filter(
|
query = self.query_filter(
|
||||||
models.Template,
|
models.Template,
|
||||||
@ -125,7 +125,6 @@ class TemplatesConnection(base.TemplatesConnection, BaseTableConn):
|
|||||||
uuid=uuid,
|
uuid=uuid,
|
||||||
status=status,
|
status=status,
|
||||||
status_details=status_details,
|
status_details=status_details,
|
||||||
is_deleted=is_deleted,
|
|
||||||
template_type=template_type,
|
template_type=template_type,
|
||||||
)
|
)
|
||||||
return query.all()
|
return query.all()
|
||||||
|
@ -16,7 +16,7 @@ import json
|
|||||||
from oslo_db.sqlalchemy import models
|
from oslo_db.sqlalchemy import models
|
||||||
|
|
||||||
from sqlalchemy import Column, DateTime, INTEGER, String, \
|
from sqlalchemy import Column, DateTime, INTEGER, String, \
|
||||||
SmallInteger, BigInteger, Index, Boolean
|
SmallInteger, BigInteger, Index
|
||||||
from sqlalchemy.ext.declarative import declarative_base
|
from sqlalchemy.ext.declarative import declarative_base
|
||||||
|
|
||||||
import sqlalchemy.types as types
|
import sqlalchemy.types as types
|
||||||
@ -146,13 +146,12 @@ class Template(Base, models.TimestampMixin):
|
|||||||
status_details = Column(String(128))
|
status_details = Column(String(128))
|
||||||
name = Column(String(128), nullable=False)
|
name = Column(String(128), nullable=False)
|
||||||
file_content = Column(JSONEncodedDict, nullable=False)
|
file_content = Column(JSONEncodedDict, nullable=False)
|
||||||
is_deleted = Column(Boolean, nullable=False, default=False)
|
|
||||||
template_type = Column("type", String(64), default='standard')
|
template_type = Column("type", String(64), default='standard')
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "<Template(id='%s', name='%s', created_at='%s'," \
|
return "<Template(id='%s', name='%s', created_at='%s'," \
|
||||||
" updated_at='%s', status='%s'," \
|
" updated_at='%s', status='%s'," \
|
||||||
"status_details='%s', file_content='%s', is_deleted='%s'," \
|
"status_details='%s', file_content='%s', " \
|
||||||
" template_type='%s' )>" % \
|
" template_type='%s' )>" % \
|
||||||
(self.uuid,
|
(self.uuid,
|
||||||
self.name,
|
self.name,
|
||||||
@ -161,5 +160,4 @@ class Template(Base, models.TimestampMixin):
|
|||||||
self.status,
|
self.status,
|
||||||
self.status_details,
|
self.status_details,
|
||||||
self.file_content,
|
self.file_content,
|
||||||
self.is_deleted,
|
|
||||||
self.template_type,)
|
self.template_type,)
|
||||||
|
@ -55,7 +55,7 @@ class FunctionalTest(base.BaseTest):
|
|||||||
self.CONF.set_override('auth_mode', self.auth, group='api')
|
self.CONF.set_override('auth_mode', self.auth, group='api')
|
||||||
|
|
||||||
self.CONF.set_override('connection',
|
self.CONF.set_override('connection',
|
||||||
'sqlite:///:memory:',
|
'sqlite:///:test.db:',
|
||||||
group='database')
|
group='database')
|
||||||
|
|
||||||
self.app = webtest.TestApp(app.load_app(self.CONF))
|
self.app = webtest.TestApp(app.load_app(self.CONF))
|
||||||
|
@ -19,6 +19,7 @@ from datetime import datetime
|
|||||||
# noinspection PyPackageRequirements
|
# noinspection PyPackageRequirements
|
||||||
from mock import mock
|
from mock import mock
|
||||||
|
|
||||||
|
from vitrage.storage.sqlalchemy import models
|
||||||
from vitrage.tests.functional.api.v1 import FunctionalTest
|
from vitrage.tests.functional.api.v1 import FunctionalTest
|
||||||
|
|
||||||
|
|
||||||
@ -113,19 +114,20 @@ class NoAuthTest(FunctionalTest):
|
|||||||
def test_noauth_mode_list_templates(self):
|
def test_noauth_mode_list_templates(self):
|
||||||
|
|
||||||
with mock.patch('pecan.request') as request:
|
with mock.patch('pecan.request') as request:
|
||||||
request.client.call.return_value = '{"templates_details": []}'
|
request.storage.templates.query.return_value = []
|
||||||
data = self.get_json('/template/')
|
data = self.get_json('/template/')
|
||||||
|
|
||||||
self.assertEqual(1, request.client.call.call_count)
|
self.assertEqual(1, request.storage.templates.query.call_count)
|
||||||
self.assertEqual([], data)
|
self.assertEqual([], data)
|
||||||
|
|
||||||
def test_noauth_mode_show_template(self):
|
def test_noauth_mode_show_template(self):
|
||||||
|
|
||||||
with mock.patch('pecan.request') as request:
|
with mock.patch('pecan.request') as request:
|
||||||
request.client.call.return_value = '{}'
|
request.storage.templates.query.return_value = \
|
||||||
|
[models.Template(file_content={})]
|
||||||
data = self.get_json('/template/1234')
|
data = self.get_json('/template/1234')
|
||||||
|
|
||||||
self.assertEqual(1, request.client.call.call_count)
|
self.assertEqual(1, request.storage.templates.query.call_count)
|
||||||
self.assertEqual({}, data)
|
self.assertEqual({}, data)
|
||||||
|
|
||||||
def test_noauth_mode_validate_template(self):
|
def test_noauth_mode_validate_template(self):
|
||||||
|
@ -13,11 +13,12 @@
|
|||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from six.moves import queue
|
|
||||||
import time
|
import time
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
|
from six.moves import queue
|
||||||
|
|
||||||
|
|
||||||
from vitrage.common.constants import EdgeLabel
|
from vitrage.common.constants import EdgeLabel
|
||||||
from vitrage.common.constants import EntityCategory
|
from vitrage.common.constants import EntityCategory
|
||||||
|
@ -12,9 +12,8 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
from six.moves import queue
|
|
||||||
|
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
|
from six.moves import queue
|
||||||
|
|
||||||
from vitrage.common.constants import DatasourceAction
|
from vitrage.common.constants import DatasourceAction
|
||||||
from vitrage.common.constants import DatasourceProperties as DSProp
|
from vitrage.common.constants import DatasourceProperties as DSProp
|
||||||
|
@ -12,9 +12,11 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
|
|
||||||
|
from vitrage.tests.functional.test_configuration import TestConfiguration
|
||||||
|
|
||||||
LOG = log.getLogger(__name__)
|
LOG = log.getLogger(__name__)
|
||||||
|
|
||||||
from oslo_db.options import database_opts
|
|
||||||
from six.moves import queue
|
from six.moves import queue
|
||||||
|
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
@ -42,8 +44,6 @@ from vitrage.evaluator.actions.evaluator_event_transformer \
|
|||||||
from vitrage.evaluator.scenario_evaluator import ScenarioEvaluator
|
from vitrage.evaluator.scenario_evaluator import ScenarioEvaluator
|
||||||
from vitrage.evaluator.scenario_repository import ScenarioRepository
|
from vitrage.evaluator.scenario_repository import ScenarioRepository
|
||||||
from vitrage.graph import create_edge
|
from vitrage.graph import create_edge
|
||||||
from vitrage import storage
|
|
||||||
from vitrage.storage.sqlalchemy import models
|
|
||||||
from vitrage.tests.functional.base import \
|
from vitrage.tests.functional.base import \
|
||||||
TestFunctionalBase
|
TestFunctionalBase
|
||||||
import vitrage.tests.mocks.mock_driver as mock_driver
|
import vitrage.tests.mocks.mock_driver as mock_driver
|
||||||
@ -57,17 +57,13 @@ _NAGIOS_TEST_INFO = {NagiosProperties.RESOURCE_NAME: _TARGET_HOST,
|
|||||||
DSProps.DATASOURCE_ACTION: DatasourceAction.SNAPSHOT}
|
DSProps.DATASOURCE_ACTION: DatasourceAction.SNAPSHOT}
|
||||||
|
|
||||||
|
|
||||||
class TestScenarioEvaluator(TestFunctionalBase):
|
class TestScenarioEvaluator(TestFunctionalBase, TestConfiguration):
|
||||||
|
|
||||||
EVALUATOR_OPTS = [
|
EVALUATOR_OPTS = [
|
||||||
cfg.StrOpt('templates_dir',
|
cfg.StrOpt('templates_dir',
|
||||||
default=utils.get_resources_dir() +
|
default=utils.get_resources_dir() +
|
||||||
'/templates/evaluator',
|
'/templates/evaluator',
|
||||||
),
|
),
|
||||||
cfg.StrOpt('equivalences_dir',
|
|
||||||
default=utils.get_resources_dir() +
|
|
||||||
'/equivalences',
|
|
||||||
),
|
|
||||||
cfg.StrOpt('notifier_topic',
|
cfg.StrOpt('notifier_topic',
|
||||||
default='vitrage.evaluator',
|
default='vitrage.evaluator',
|
||||||
),
|
),
|
||||||
@ -81,13 +77,8 @@ class TestScenarioEvaluator(TestFunctionalBase):
|
|||||||
cls.conf.register_opts(cls.PROCESSOR_OPTS, group='entity_graph')
|
cls.conf.register_opts(cls.PROCESSOR_OPTS, group='entity_graph')
|
||||||
cls.conf.register_opts(cls.EVALUATOR_OPTS, group='evaluator')
|
cls.conf.register_opts(cls.EVALUATOR_OPTS, group='evaluator')
|
||||||
cls.conf.register_opts(cls.DATASOURCES_OPTS, group='datasources')
|
cls.conf.register_opts(cls.DATASOURCES_OPTS, group='datasources')
|
||||||
cls.conf.register_opts(database_opts, group='database')
|
cls.add_db(cls.conf)
|
||||||
cls.conf.set_override('connection', 'sqlite:///test.db',
|
cls.add_templates(cls.conf.evaluator.templates_dir)
|
||||||
group='database')
|
|
||||||
cls._db = storage.get_connection_from_config(cls.conf)
|
|
||||||
engine = cls._db._engine_facade.get_engine()
|
|
||||||
models.Base.metadata.create_all(engine)
|
|
||||||
|
|
||||||
TestScenarioEvaluator.load_datasources(cls.conf)
|
TestScenarioEvaluator.load_datasources(cls.conf)
|
||||||
cls.scenario_repository = ScenarioRepository(cls.conf)
|
cls.scenario_repository = ScenarioRepository(cls.conf)
|
||||||
|
|
||||||
|
@ -13,10 +13,13 @@
|
|||||||
# under the License.
|
# under the License.
|
||||||
from oslo_db.options import database_opts
|
from oslo_db.options import database_opts
|
||||||
|
|
||||||
|
from vitrage.common.constants import TemplateStatus
|
||||||
|
from vitrage.common.constants import TemplateTypes as TType
|
||||||
|
from vitrage.evaluator.template_db.template_repository import \
|
||||||
|
add_template_to_db
|
||||||
from vitrage import storage
|
from vitrage import storage
|
||||||
from vitrage.storage.sqlalchemy import models
|
from vitrage.storage.sqlalchemy import models
|
||||||
|
|
||||||
|
|
||||||
TEMPLATE_DIR = '/etc/vitrage/templates'
|
TEMPLATE_DIR = '/etc/vitrage/templates'
|
||||||
|
|
||||||
|
|
||||||
@ -32,3 +35,15 @@ class TestConfiguration(object):
|
|||||||
models.Base.metadata.drop_all(engine)
|
models.Base.metadata.drop_all(engine)
|
||||||
models.Base.metadata.create_all(engine)
|
models.Base.metadata.create_all(engine)
|
||||||
return cls._db
|
return cls._db
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def add_templates(cls, templates_dir, templates_type=TType.STANDARD):
|
||||||
|
templates = add_template_to_db(cls._db, templates_dir, templates_type)
|
||||||
|
for t in templates:
|
||||||
|
if t.status == TemplateStatus.LOADING:
|
||||||
|
cls._db.templates.update(t.uuid, 'status',
|
||||||
|
TemplateStatus.ACTIVE)
|
||||||
|
if t.status == TemplateStatus.DELETING:
|
||||||
|
cls._db.templates.update(t.uuid, 'status',
|
||||||
|
TemplateStatus.DELETED)
|
||||||
|
return templates
|
||||||
|
@ -14,22 +14,30 @@
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
from oslo_config import cfg
|
||||||
|
|
||||||
|
from vitrage.common.constants import TemplateTypes
|
||||||
from vitrage.common.exception import VitrageError
|
from vitrage.common.exception import VitrageError
|
||||||
from vitrage.evaluator.equivalence_repository import EquivalenceRepository
|
from vitrage.evaluator.equivalence_repository import EquivalenceRepository
|
||||||
from vitrage.tests import base
|
from vitrage.tests import base
|
||||||
|
from vitrage.tests.functional.test_configuration import TestConfiguration
|
||||||
from vitrage.tests.mocks import utils
|
from vitrage.tests.mocks import utils
|
||||||
|
|
||||||
|
|
||||||
class TestEquivalenceRepository(base.BaseTest):
|
class TestEquivalenceRepository(base.BaseTest, TestConfiguration):
|
||||||
|
|
||||||
# noinspection PyPep8Naming
|
# noinspection PyPep8Naming
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(TestEquivalenceRepository, self).setUp()
|
super(TestEquivalenceRepository, self).setUp()
|
||||||
|
conf = cfg.ConfigOpts()
|
||||||
|
self.add_db(conf)
|
||||||
self.equivalence_repository = EquivalenceRepository()
|
self.equivalence_repository = EquivalenceRepository()
|
||||||
|
|
||||||
def test_duplicate_entities_in_equivalence(self):
|
def test_duplicate_entities_in_equivalence(self):
|
||||||
base_dir = utils.get_resources_dir() + '/templates/equivalences_dup'
|
base_dir = utils.get_resources_dir() + '/templates/equivalences_dup'
|
||||||
for directory in os.listdir(base_dir):
|
for directory in os.listdir(base_dir):
|
||||||
self.assertRaises(VitrageError,
|
self.add_templates(os.path.join(base_dir, directory),
|
||||||
self.equivalence_repository.load_files,
|
TemplateTypes.EQUIVALENCE)
|
||||||
os.path.join(base_dir, directory))
|
self.assertRaises(
|
||||||
|
VitrageError,
|
||||||
|
self.equivalence_repository.load, self._db)
|
||||||
|
@ -12,22 +12,22 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
import os
|
|
||||||
|
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
|
|
||||||
from vitrage.common.constants import EntityCategory
|
from vitrage.common.constants import EntityCategory
|
||||||
|
from vitrage.common.constants import TemplateTypes as TType
|
||||||
from vitrage.common.constants import VertexProperties as VProps
|
from vitrage.common.constants import VertexProperties as VProps
|
||||||
from vitrage.evaluator.scenario_repository import ScenarioRepository
|
from vitrage.evaluator.scenario_repository import ScenarioRepository
|
||||||
from vitrage.evaluator.template_validation.template_syntax_validator import \
|
from vitrage.evaluator.template_validation.template_syntax_validator import \
|
||||||
syntax_validation
|
syntax_validation
|
||||||
from vitrage.graph import Vertex
|
from vitrage.graph import Vertex
|
||||||
from vitrage.tests import base
|
from vitrage.tests import base
|
||||||
|
from vitrage.tests.functional.test_configuration import TestConfiguration
|
||||||
from vitrage.tests.mocks import utils
|
from vitrage.tests.mocks import utils
|
||||||
from vitrage.utils import file as file_utils
|
from vitrage.utils import file as file_utils
|
||||||
|
|
||||||
|
|
||||||
class ScenarioRepositoryTest(base.BaseTest):
|
class ScenarioRepositoryTest(base.BaseTest, TestConfiguration):
|
||||||
BASE_DIR = utils.get_resources_dir() + '/templates/general'
|
BASE_DIR = utils.get_resources_dir() + '/templates/general'
|
||||||
OPTS = [
|
OPTS = [
|
||||||
cfg.StrOpt('templates_dir',
|
cfg.StrOpt('templates_dir',
|
||||||
@ -44,7 +44,8 @@ class ScenarioRepositoryTest(base.BaseTest):
|
|||||||
super(ScenarioRepositoryTest, cls).setUpClass()
|
super(ScenarioRepositoryTest, cls).setUpClass()
|
||||||
cls.conf = cfg.ConfigOpts()
|
cls.conf = cfg.ConfigOpts()
|
||||||
cls.conf.register_opts(cls.OPTS, group='evaluator')
|
cls.conf.register_opts(cls.OPTS, group='evaluator')
|
||||||
|
cls.add_db(cls.conf)
|
||||||
|
cls.add_templates(cls.conf.evaluator.templates_dir)
|
||||||
templates_dir_path = cls.conf.evaluator.templates_dir
|
templates_dir_path = cls.conf.evaluator.templates_dir
|
||||||
cls.template_defs = file_utils.load_yaml_files(templates_dir_path)
|
cls.template_defs = file_utils.load_yaml_files(templates_dir_path)
|
||||||
|
|
||||||
@ -57,8 +58,10 @@ class ScenarioRepositoryTest(base.BaseTest):
|
|||||||
|
|
||||||
# Test assertions
|
# Test assertions
|
||||||
self.assertIsNotNone(scenario_repository)
|
self.assertIsNotNone(scenario_repository)
|
||||||
path, dirs, files = next(os.walk(self.conf.evaluator.templates_dir))
|
self.assertEqual(
|
||||||
self.assertEqual(len(files), len(scenario_repository.templates))
|
2,
|
||||||
|
len(scenario_repository.templates),
|
||||||
|
'scenario_repository.templates should contain all valid templates')
|
||||||
|
|
||||||
def test_init_scenario_repository(self):
|
def test_init_scenario_repository(self):
|
||||||
|
|
||||||
@ -74,7 +77,10 @@ class ScenarioRepositoryTest(base.BaseTest):
|
|||||||
|
|
||||||
scenario_templates = self.scenario_repository.templates
|
scenario_templates = self.scenario_repository.templates
|
||||||
# there is one bad template
|
# there is one bad template
|
||||||
self.assertEqual(valid_template_counter, len(scenario_templates) - 1)
|
self.assertEqual(
|
||||||
|
valid_template_counter,
|
||||||
|
len(scenario_templates),
|
||||||
|
'scenario_repository.templates should contain all valid templates')
|
||||||
|
|
||||||
entity_equivalences = self.scenario_repository.entity_equivalences
|
entity_equivalences = self.scenario_repository.entity_equivalences
|
||||||
for entity_props, equivalence in entity_equivalences.items():
|
for entity_props, equivalence in entity_equivalences.items():
|
||||||
@ -100,20 +106,20 @@ class ScenarioRepositoryTest(base.BaseTest):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class RegExTemplateTest(base.BaseTest):
|
class RegExTemplateTest(base.BaseTest, TestConfiguration):
|
||||||
|
|
||||||
BASE_DIR = utils.get_resources_dir() + '/templates/regex'
|
BASE_DIR = utils.get_resources_dir() + '/templates/regex'
|
||||||
OPTS = [
|
OPTS = [
|
||||||
cfg.StrOpt('templates_dir',
|
cfg.StrOpt('templates_dir',
|
||||||
default=BASE_DIR),
|
default=BASE_DIR)]
|
||||||
cfg.StrOpt('equivalences_dir',
|
|
||||||
default=BASE_DIR + '/equivalences')]
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
super(RegExTemplateTest, cls).setUpClass()
|
super(RegExTemplateTest, cls).setUpClass()
|
||||||
cls.conf = cfg.ConfigOpts()
|
cls.conf = cfg.ConfigOpts()
|
||||||
cls.conf.register_opts(cls.OPTS, group='evaluator')
|
cls.conf.register_opts(cls.OPTS, group='evaluator')
|
||||||
|
cls.add_db(cls.conf)
|
||||||
|
cls.add_templates(cls.conf.evaluator.templates_dir)
|
||||||
cls.scenario_repository = ScenarioRepository(cls.conf)
|
cls.scenario_repository = ScenarioRepository(cls.conf)
|
||||||
|
|
||||||
def test_basic_regex(self):
|
def test_basic_regex(self):
|
||||||
@ -169,7 +175,7 @@ class RegExTemplateTest(base.BaseTest):
|
|||||||
self.assertEqual(0, len(relevant_scenarios))
|
self.assertEqual(0, len(relevant_scenarios))
|
||||||
|
|
||||||
|
|
||||||
class EquivalentScenarioTest(base.BaseTest):
|
class EquivalentScenarioTest(base.BaseTest, TestConfiguration):
|
||||||
BASE_DIR = utils.get_resources_dir() + '/templates/equivalent_scenarios/'
|
BASE_DIR = utils.get_resources_dir() + '/templates/equivalent_scenarios/'
|
||||||
OPTS = [
|
OPTS = [
|
||||||
cfg.StrOpt('templates_dir',
|
cfg.StrOpt('templates_dir',
|
||||||
@ -188,10 +194,12 @@ class EquivalentScenarioTest(base.BaseTest):
|
|||||||
super(EquivalentScenarioTest, cls).setUpClass()
|
super(EquivalentScenarioTest, cls).setUpClass()
|
||||||
cls.conf = cfg.ConfigOpts()
|
cls.conf = cfg.ConfigOpts()
|
||||||
cls.conf.register_opts(cls.OPTS, group='evaluator')
|
cls.conf.register_opts(cls.OPTS, group='evaluator')
|
||||||
|
cls.add_db(cls.conf)
|
||||||
templates_dir_path = cls.conf.evaluator.templates_dir
|
cls.add_templates(cls.conf.evaluator.templates_dir)
|
||||||
cls.template_defs = file_utils.load_yaml_files(templates_dir_path)
|
cls.add_templates(cls.conf.evaluator.equivalences_dir,
|
||||||
|
TType.EQUIVALENCE)
|
||||||
|
cls.add_templates(cls.conf.evaluator.def_templates_dir,
|
||||||
|
TType.DEFINITION)
|
||||||
cls.scenario_repository = ScenarioRepository(cls.conf)
|
cls.scenario_repository = ScenarioRepository(cls.conf)
|
||||||
|
|
||||||
def test_expansion(self):
|
def test_expansion(self):
|
||||||
|
43
vitrage_tempest_tests/add_legacy_dir_templates.py
Normal file
43
vitrage_tempest_tests/add_legacy_dir_templates.py
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
# 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.
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from vitrage.common.constants import TemplateStatus
|
||||||
|
from vitrage.common.constants import TemplateTypes as TType
|
||||||
|
from vitrage.evaluator.template_db.template_repository import \
|
||||||
|
add_template_to_db
|
||||||
|
from vitrage import service
|
||||||
|
from vitrage import storage
|
||||||
|
from vitrage_tempest_tests.tests.common import general_utils
|
||||||
|
|
||||||
|
files = ['corrupted_template.yaml', 'e2e_test_basic_actions.yaml',
|
||||||
|
'e2e_test_overlapping_actions.yaml', 'v1_execute_mistral.yaml',
|
||||||
|
'v2_execute_mistral.yaml', 'host_aodh_alarm_for_rca.yaml',
|
||||||
|
'nagios_alarm_for_alarms.yaml'
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
resources_path = general_utils.tempest_resources_dir() + '/templates/api/'
|
||||||
|
conf = service.prepare_service()
|
||||||
|
db = storage.get_connection_from_config(conf)
|
||||||
|
for f in files:
|
||||||
|
full_path = resources_path + f
|
||||||
|
template = add_template_to_db(db, full_path, TType.STANDARD)
|
||||||
|
if template[0]['name'] != 'corrupted_template':
|
||||||
|
db.templates.update(template[0]['uuid'],
|
||||||
|
'status', TemplateStatus.ACTIVE)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
@ -15,10 +15,13 @@ import json
|
|||||||
|
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
|
|
||||||
|
from vitrage.common.exception import VitrageError
|
||||||
from vitrage_tempest_tests.tests.base import BaseVitrageTempest
|
from vitrage_tempest_tests.tests.base import BaseVitrageTempest
|
||||||
from vitrage_tempest_tests.tests.common import general_utils as g_utils
|
from vitrage_tempest_tests.tests.common import general_utils as g_utils
|
||||||
|
from vitrage_tempest_tests.tests.common import vitrage_utils
|
||||||
from vitrage_tempest_tests.tests import utils
|
from vitrage_tempest_tests.tests import utils
|
||||||
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@ -162,3 +165,11 @@ class BaseTemplateTest(BaseVitrageTempest):
|
|||||||
relationships, len(template_show['definitions']['relationships']))
|
relationships, len(template_show['definitions']['relationships']))
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
scenarios, len(template_show['scenarios']))
|
scenarios, len(template_show['scenarios']))
|
||||||
|
|
||||||
|
def _rollback_to_default(self, templates):
|
||||||
|
try:
|
||||||
|
for t in templates:
|
||||||
|
db_row = vitrage_utils.get_first_template(name=t)
|
||||||
|
vitrage_utils.delete_template(db_row['uuid'])
|
||||||
|
except Exception as e:
|
||||||
|
raise VitrageError('Rollback to default failed %s', e)
|
||||||
|
@ -12,15 +12,30 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
from oslo_log import log as logging
|
|
||||||
|
|
||||||
from vitrage_tempest_tests.tests.api.templates.base import BaseTemplateTest
|
|
||||||
import vitrage_tempest_tests.tests.utils as utils
|
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
|
from oslo_log import log as logging
|
||||||
|
|
||||||
|
from vitrage.common.constants import TemplateStatus
|
||||||
|
from vitrage.common.constants import TemplateTypes as TTypes
|
||||||
|
from vitrage.evaluator.template_db.template_repository import \
|
||||||
|
load_template_file
|
||||||
|
from vitrage_tempest_tests.tests.api.templates.base import BaseTemplateTest
|
||||||
|
from vitrage_tempest_tests.tests.common import general_utils as g_utils
|
||||||
|
from vitrage_tempest_tests.tests.common.tempest_clients import TempestClients
|
||||||
|
from vitrage_tempest_tests.tests.common import vitrage_utils
|
||||||
|
import vitrage_tempest_tests.tests.utils as utils
|
||||||
|
from vitrageclient.exceptions import ClientException
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
STANDARD_TEMPLATE = 'host_high_memory_usage_scenarios.yaml'
|
||||||
|
EQUIVALENCE_TEMPLATE = 'basic_equivalence_templates.yaml'
|
||||||
|
DEFINITION_TEMPLATE = 'basic_def_template.yaml'
|
||||||
|
STANDARD_ERROR = 'corrupted_template.yaml'
|
||||||
|
|
||||||
|
FAKE_UUID = 'ade68276-0fe9-42cd-9ec2-e7f20470a771'
|
||||||
|
|
||||||
|
|
||||||
class TestValidate(BaseTemplateTest):
|
class TestValidate(BaseTemplateTest):
|
||||||
"""Template test class for Vitrage API tests."""
|
"""Template test class for Vitrage API tests."""
|
||||||
@ -132,3 +147,162 @@ class TestValidate(BaseTemplateTest):
|
|||||||
self._compare_template_show(
|
self._compare_template_show(
|
||||||
api_template_show, cli_template_show)
|
api_template_show, cli_template_show)
|
||||||
self._validate_template_structure(item, api_template_show)
|
self._validate_template_structure(item, api_template_show)
|
||||||
|
|
||||||
|
|
||||||
|
class TemplatesDBTest(BaseTemplateTest):
|
||||||
|
"""Template DB test class for vitrage API tests"""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
super(TemplatesDBTest, cls).setUpClass()
|
||||||
|
cls.client = TempestClients.vitrage()
|
||||||
|
|
||||||
|
def test_template_add(self):
|
||||||
|
"""template add test
|
||||||
|
|
||||||
|
test standard , definition and equivalence templates
|
||||||
|
"""
|
||||||
|
templates_names = list()
|
||||||
|
try:
|
||||||
|
# TODO(ikinory): add folder of templates
|
||||||
|
# Add standard ,equivalence and definition templates
|
||||||
|
templates_names = self._add_templates()
|
||||||
|
vitrage_utils.add_template(STANDARD_TEMPLATE,
|
||||||
|
template_type=TTypes.STANDARD)
|
||||||
|
# assert standard template
|
||||||
|
db_row = vitrage_utils.get_first_template(
|
||||||
|
name='host_high_memory_usage_scenarios', type=TTypes.STANDARD)
|
||||||
|
self.assertEqual(db_row['name'],
|
||||||
|
'host_high_memory_usage_scenarios',
|
||||||
|
'standard template not found in list')
|
||||||
|
|
||||||
|
# assert equivalence template
|
||||||
|
db_row = vitrage_utils.get_first_template(
|
||||||
|
name='entity equivalence example',
|
||||||
|
type=TTypes.EQUIVALENCE)
|
||||||
|
self.assertEqual(db_row['name'],
|
||||||
|
'entity equivalence example',
|
||||||
|
'equivalence template not found in list')
|
||||||
|
|
||||||
|
# assert definition template
|
||||||
|
db_row = vitrage_utils.get_first_template(
|
||||||
|
name='basic_def_template',
|
||||||
|
type=TTypes.DEFINITION,
|
||||||
|
status=TemplateStatus.ACTIVE)
|
||||||
|
|
||||||
|
self.assertEqual(db_row['name'],
|
||||||
|
'basic_def_template',
|
||||||
|
'definition template not found in list')
|
||||||
|
|
||||||
|
# assert corrupted template - validate failed
|
||||||
|
db_row = vitrage_utils.get_first_template(
|
||||||
|
name='corrupted_template',
|
||||||
|
type=TTypes.STANDARD,
|
||||||
|
status=TemplateStatus.ERROR)
|
||||||
|
self.assertIsNotNone(
|
||||||
|
db_row,
|
||||||
|
'corrupted template template presented in list')
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self._handle_exception(e)
|
||||||
|
raise
|
||||||
|
finally:
|
||||||
|
self._rollback_to_default(templates_names)
|
||||||
|
|
||||||
|
def test_template_delete(self):
|
||||||
|
try:
|
||||||
|
|
||||||
|
# add standard template
|
||||||
|
vitrage_utils.add_template(STANDARD_TEMPLATE,
|
||||||
|
template_type=TTypes.STANDARD)
|
||||||
|
db_row = vitrage_utils.get_first_template(
|
||||||
|
name='host_high_memory_usage_scenarios',
|
||||||
|
type=TTypes.STANDARD,
|
||||||
|
status=TemplateStatus.ACTIVE)
|
||||||
|
self.assertIsNotNone(db_row,
|
||||||
|
'Template should appear in templates list')
|
||||||
|
|
||||||
|
# delete template
|
||||||
|
uuid = db_row['uuid']
|
||||||
|
vitrage_utils.delete_template(uuid)
|
||||||
|
db_row = vitrage_utils.get_first_template(
|
||||||
|
name='host_high_memory_usage_scenarios', type=TTypes.STANDARD)
|
||||||
|
self.assertIsNone(db_row, 'Template should not appear in list')
|
||||||
|
|
||||||
|
# delete the same template again - should raise VitrageError
|
||||||
|
self.assertRaises(ClientException,
|
||||||
|
vitrage_utils.delete_template, uuid)
|
||||||
|
|
||||||
|
# delete non-existing template - should raise VitrageError
|
||||||
|
self.assertRaises(ClientException,
|
||||||
|
vitrage_utils.delete_template, FAKE_UUID)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self._handle_exception(e)
|
||||||
|
raise
|
||||||
|
|
||||||
|
def test_compare_cli_to_api(self):
|
||||||
|
"""Compare between api template list
|
||||||
|
|
||||||
|
to cli template list
|
||||||
|
compares each template in list
|
||||||
|
"""
|
||||||
|
templates_names = list()
|
||||||
|
try:
|
||||||
|
# Add standard ,equivalence and definition templates
|
||||||
|
templates_names = self._add_templates()
|
||||||
|
cli_templates_list = utils.run_vitrage_command(
|
||||||
|
"vitrage template list", self.conf)
|
||||||
|
api_templates_list = self.client.template.list()
|
||||||
|
|
||||||
|
self.assertNotEqual(len(api_templates_list), 0,
|
||||||
|
'The template list taken from api is empty')
|
||||||
|
self.assertIsNotNone(cli_templates_list,
|
||||||
|
'The template list taken from cli is empty')
|
||||||
|
self._validate_templates_list_length(api_templates_list,
|
||||||
|
cli_templates_list)
|
||||||
|
self._validate_passed_templates_length(api_templates_list,
|
||||||
|
cli_templates_list)
|
||||||
|
self._compare_each_template_in_list(api_templates_list,
|
||||||
|
cli_templates_list)
|
||||||
|
except Exception as e:
|
||||||
|
self._handle_exception(e)
|
||||||
|
raise
|
||||||
|
finally:
|
||||||
|
self._rollback_to_default(templates_names)
|
||||||
|
|
||||||
|
def test_template_show(self):
|
||||||
|
"""Compare template content from file to DB"""
|
||||||
|
try:
|
||||||
|
# add standard template
|
||||||
|
template_path = \
|
||||||
|
g_utils.tempest_resources_dir() + '/templates/api/'\
|
||||||
|
+ STANDARD_TEMPLATE
|
||||||
|
vitrage_utils.add_template(STANDARD_TEMPLATE,
|
||||||
|
template_type=TTypes.STANDARD)
|
||||||
|
db_row = vitrage_utils.get_first_template(
|
||||||
|
name='host_high_memory_usage_scenarios',
|
||||||
|
type=TTypes.STANDARD,
|
||||||
|
status=TemplateStatus.ACTIVE)
|
||||||
|
payload_from_db = self.client.template.show(db_row['uuid'])
|
||||||
|
payload_from_file = load_template_file(template_path)
|
||||||
|
self.assertEqual(payload_from_file, payload_from_db,
|
||||||
|
"Template content doesn't match")
|
||||||
|
vitrage_utils.delete_template(db_row['uuid'])
|
||||||
|
except Exception as e:
|
||||||
|
self._handle_exception(e)
|
||||||
|
raise
|
||||||
|
|
||||||
|
def _add_templates(self):
|
||||||
|
vitrage_utils.add_template(STANDARD_TEMPLATE,
|
||||||
|
template_type=TTypes.STANDARD)
|
||||||
|
vitrage_utils.add_template(EQUIVALENCE_TEMPLATE,
|
||||||
|
template_type=TTypes.EQUIVALENCE)
|
||||||
|
vitrage_utils.add_template(DEFINITION_TEMPLATE,
|
||||||
|
template_type=TTypes.DEFINITION)
|
||||||
|
vitrage_utils.add_template(STANDARD_ERROR,
|
||||||
|
template_type=TTypes.STANDARD)
|
||||||
|
return ['host_high_memory_usage_scenarios',
|
||||||
|
'entity equivalence example',
|
||||||
|
'basic_def_template',
|
||||||
|
'corrupted_template']
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
from os import path
|
||||||
import six
|
import six
|
||||||
|
|
||||||
|
|
||||||
@ -37,3 +38,7 @@ def is_subset(subset, full):
|
|||||||
|
|
||||||
def _remove_none_values(**kwargs):
|
def _remove_none_values(**kwargs):
|
||||||
return {k: v for k, v in kwargs.items() if v is not None}
|
return {k: v for k, v in kwargs.items() if v is not None}
|
||||||
|
|
||||||
|
|
||||||
|
def tempest_resources_dir():
|
||||||
|
return path.join(path.dirname(path.dirname(__file__)), 'resources')
|
||||||
|
@ -12,11 +12,17 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from oslo_log import log as logging
|
||||||
|
|
||||||
|
from vitrage.common.constants import TemplateStatus
|
||||||
|
from vitrage.common.constants import TemplateTypes
|
||||||
from vitrage.datasources import NOVA_HOST_DATASOURCE
|
from vitrage.datasources import NOVA_HOST_DATASOURCE
|
||||||
from vitrage.datasources import NOVA_INSTANCE_DATASOURCE
|
from vitrage.datasources import NOVA_INSTANCE_DATASOURCE
|
||||||
from vitrage_tempest_tests.tests.common import general_utils as g_utils
|
from vitrage_tempest_tests.tests.common import general_utils as g_utils
|
||||||
from vitrage_tempest_tests.tests.common.tempest_clients import TempestClients
|
from vitrage_tempest_tests.tests.common.tempest_clients import TempestClients
|
||||||
|
from vitrage_tempest_tests.tests.utils import wait_for_status
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
DOWN = 'down'
|
DOWN = 'down'
|
||||||
UP = 'up'
|
UP = 'up'
|
||||||
@ -38,8 +44,13 @@ def generate_fake_host_alarm(hostname, event_type, enabled=True):
|
|||||||
|
|
||||||
|
|
||||||
def get_first_host(**kwargs):
|
def get_first_host(**kwargs):
|
||||||
hosts = TempestClients.vitrage().resource.list(
|
try:
|
||||||
NOVA_HOST_DATASOURCE, all_tenants=True)
|
hosts = TempestClients.vitrage().resource.list(
|
||||||
|
NOVA_HOST_DATASOURCE, all_tenants=True)
|
||||||
|
except Exception as e:
|
||||||
|
LOG.exception("get_first_host failed with %s", e)
|
||||||
|
hosts = TempestClients.vitrage().resource.list(
|
||||||
|
NOVA_HOST_DATASOURCE, all_tenants=True)
|
||||||
return g_utils.first_match(hosts, **kwargs)
|
return g_utils.first_match(hosts, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
@ -47,3 +58,30 @@ def get_first_instance(**kwargs):
|
|||||||
instances = TempestClients.vitrage().resource.list(
|
instances = TempestClients.vitrage().resource.list(
|
||||||
NOVA_INSTANCE_DATASOURCE, all_tenants=True)
|
NOVA_INSTANCE_DATASOURCE, all_tenants=True)
|
||||||
return g_utils.first_match(instances, **kwargs)
|
return g_utils.first_match(instances, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def add_template(filename='',
|
||||||
|
folder='templates/api',
|
||||||
|
template_type=TemplateTypes.STANDARD):
|
||||||
|
full_path = g_utils.tempest_resources_dir() + '/' + folder + '/' + filename
|
||||||
|
t = TempestClients.vitrage().template.add(full_path, template_type)
|
||||||
|
if t and t[0]:
|
||||||
|
wait_for_status(
|
||||||
|
10,
|
||||||
|
get_first_template,
|
||||||
|
uuid=t[0]['uuid'], status=TemplateStatus.ACTIVE)
|
||||||
|
return t[0]
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def get_first_template(**kwargs):
|
||||||
|
templates = TempestClients.vitrage().template.list()
|
||||||
|
return g_utils.first_match(templates, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def delete_template(uuid):
|
||||||
|
TempestClients.vitrage().template.delete(uuid)
|
||||||
|
wait_for_status(
|
||||||
|
10,
|
||||||
|
lambda _id: True if not get_first_template(uuid=_id) else False,
|
||||||
|
_id=uuid)
|
||||||
|
189
vitrage_tempest_tests/tests/e2e/test_basic_template_actions.py
Normal file
189
vitrage_tempest_tests/tests/e2e/test_basic_template_actions.py
Normal file
@ -0,0 +1,189 @@
|
|||||||
|
# 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 as logging
|
||||||
|
import time
|
||||||
|
|
||||||
|
from vitrage.common.constants import EntityCategory
|
||||||
|
from vitrage.common.constants import VertexProperties as VProps
|
||||||
|
from vitrage.evaluator.actions.evaluator_event_transformer import \
|
||||||
|
VITRAGE_DATASOURCE
|
||||||
|
from vitrage_tempest_tests.tests.common import vitrage_utils as v_util
|
||||||
|
from vitrage_tempest_tests.tests.e2e.test_actions_base import TestActionsBase
|
||||||
|
from vitrage_tempest_tests.tests import utils
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
TRIGGER_ALARM_1 = 'e2e.test_template_actions.trigger.alarm1'
|
||||||
|
DEDUCED = 'e2e.test_template_actions.deduced.alarm'
|
||||||
|
|
||||||
|
TRIGGER_ALARM_2 = 'e2e.test_template_actions.trigger.alarm2'
|
||||||
|
DEDUCED_2 = 'e2e.test_template_actions.deduced.alarm2'
|
||||||
|
|
||||||
|
TEST_TEMPLATE = 'e2e_test_template_actions.yaml'
|
||||||
|
TEST_TEMPLATE_2 = 'e2e_test_template_actions_2.yaml'
|
||||||
|
|
||||||
|
INFILE_NAME = 'e2e_test_template_actions'
|
||||||
|
INFILE_NAME_2 = 'e2e_test_template_actions_2'
|
||||||
|
|
||||||
|
|
||||||
|
FOLDER_PATH = 'templates/api/e2e_test_template'
|
||||||
|
|
||||||
|
DEDUCED_PROPS = {
|
||||||
|
VProps.NAME: DEDUCED,
|
||||||
|
VProps.VITRAGE_CATEGORY: EntityCategory.ALARM,
|
||||||
|
VProps.VITRAGE_TYPE: VITRAGE_DATASOURCE,
|
||||||
|
}
|
||||||
|
|
||||||
|
DEDUCED_PROPS_2 = {
|
||||||
|
VProps.NAME: DEDUCED_2,
|
||||||
|
VProps.VITRAGE_CATEGORY: EntityCategory.ALARM,
|
||||||
|
VProps.VITRAGE_TYPE: VITRAGE_DATASOURCE,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class TestTemplateActions(TestActionsBase):
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwds):
|
||||||
|
super(TestTemplateActions, self).__init__(*args, **kwds)
|
||||||
|
self.added_template = None
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestTemplateActions, self).setUp()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
super(TestTemplateActions, self).tearDown()
|
||||||
|
time.sleep(10)
|
||||||
|
self._trigger_undo_action(TRIGGER_ALARM_1)
|
||||||
|
if self.added_template is not None:
|
||||||
|
v_util.delete_template(self.added_template['uuid'])
|
||||||
|
self.added_template = None
|
||||||
|
|
||||||
|
@utils.tempest_logger
|
||||||
|
def test_evaluator_reload_with_new_template(self):
|
||||||
|
"""Test reload new template e2e
|
||||||
|
|
||||||
|
1. raise trigger alarm (template is not loaded yet)
|
||||||
|
2. add the relevant template
|
||||||
|
3. check action is executed
|
||||||
|
This checks that the evaluators are reloaded and run on all existing
|
||||||
|
vertices.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
host_id = self.orig_host.get(VProps.VITRAGE_ID)
|
||||||
|
self._trigger_do_action(TRIGGER_ALARM_1)
|
||||||
|
self.added_template = v_util.add_template(TEST_TEMPLATE,
|
||||||
|
folder=FOLDER_PATH)
|
||||||
|
self._check_deduced(1, DEDUCED_PROPS, host_id)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self._handle_exception(e)
|
||||||
|
raise
|
||||||
|
|
||||||
|
@utils.tempest_logger
|
||||||
|
def test_evaluator_reload_with_existing_template(self):
|
||||||
|
"""Test reload new template e2e
|
||||||
|
|
||||||
|
1.add the relevant template
|
||||||
|
2.raise trigger alarm.
|
||||||
|
3. check action is executed
|
||||||
|
This checks that new workers execute new template
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
host_id = self.orig_host.get(VProps.VITRAGE_ID)
|
||||||
|
self.added_template = v_util.add_template(TEST_TEMPLATE,
|
||||||
|
folder=FOLDER_PATH)
|
||||||
|
self._trigger_do_action(TRIGGER_ALARM_1)
|
||||||
|
self._check_deduced(1, DEDUCED_PROPS, host_id)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self._handle_exception(e)
|
||||||
|
raise
|
||||||
|
|
||||||
|
@utils.tempest_logger
|
||||||
|
def test_evaluator_reload_with_new_template_v2(self):
|
||||||
|
"""Test reload new template e2e v2
|
||||||
|
|
||||||
|
1. raise trigger alarm
|
||||||
|
2. add the relevant template
|
||||||
|
3. delete the template.
|
||||||
|
4. check action - should be not active.
|
||||||
|
This checks that the evaluators are reloaded and run on all existing
|
||||||
|
vertices.
|
||||||
|
Checks temporary worker that was added to delete template.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
host_id = self.orig_host.get(VProps.VITRAGE_ID)
|
||||||
|
|
||||||
|
self._trigger_do_action(TRIGGER_ALARM_1)
|
||||||
|
self.added_template = v_util.add_template(TEST_TEMPLATE,
|
||||||
|
folder=FOLDER_PATH)
|
||||||
|
self._check_deduced(1, DEDUCED_PROPS, host_id)
|
||||||
|
v_util.delete_template(self.added_template['uuid'])
|
||||||
|
self.added_template = None
|
||||||
|
self._check_deduced(0, DEDUCED_PROPS, host_id)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self._handle_exception(e)
|
||||||
|
raise
|
||||||
|
|
||||||
|
@utils.tempest_logger
|
||||||
|
def test_evaluator_reload_with_existing_template_v2(self):
|
||||||
|
"""Test reload new template e2e v2
|
||||||
|
|
||||||
|
1.add the relevant template
|
||||||
|
2.delete the template
|
||||||
|
2.raise trigger alarm.
|
||||||
|
3. check no deduced alarm
|
||||||
|
This checks that template deleted properly and no action executed.
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
host_id = self.orig_host.get(VProps.VITRAGE_ID)
|
||||||
|
self.added_template = v_util.add_template(TEST_TEMPLATE,
|
||||||
|
folder=FOLDER_PATH)
|
||||||
|
v_util.delete_template(self.added_template['uuid'])
|
||||||
|
self.added_template = None
|
||||||
|
self._trigger_do_action(TRIGGER_ALARM_1)
|
||||||
|
self._check_deduced(0, DEDUCED_PROPS, host_id)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self._handle_exception(e)
|
||||||
|
raise
|
||||||
|
|
||||||
|
@utils.tempest_logger
|
||||||
|
def test_evaluator_reload_with_multiple_new_template(self):
|
||||||
|
"""Test reload new template e2e
|
||||||
|
|
||||||
|
1. raise trigger alarm (template is not loaded yet)
|
||||||
|
2. add 2 new templates.
|
||||||
|
3. check both actions are executed
|
||||||
|
This checks that the evaluators are reloaded for both templates
|
||||||
|
and run on all existing vertices.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
host_id = self.orig_host.get(VProps.VITRAGE_ID)
|
||||||
|
self._trigger_do_action(TRIGGER_ALARM_1)
|
||||||
|
self._trigger_do_action(TRIGGER_ALARM_2)
|
||||||
|
v_util.add_template(folder=FOLDER_PATH)
|
||||||
|
self.added_template = v_util.get_first_template(name=INFILE_NAME)
|
||||||
|
second_template = v_util.get_first_template(name=INFILE_NAME_2)
|
||||||
|
self._check_deduced(1, DEDUCED_PROPS, host_id)
|
||||||
|
self._check_deduced(1, DEDUCED_PROPS_2, host_id)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self._handle_exception(e)
|
||||||
|
raise
|
||||||
|
finally:
|
||||||
|
if second_template:
|
||||||
|
v_util.delete_template(second_template['uuid'])
|
@ -0,0 +1,22 @@
|
|||||||
|
metadata:
|
||||||
|
#a basic def_template file
|
||||||
|
name: basic_def_template
|
||||||
|
description: basic def_template for general tests
|
||||||
|
definitions:
|
||||||
|
entities:
|
||||||
|
- entity:
|
||||||
|
category: ALARM
|
||||||
|
type: nagios
|
||||||
|
name: host_problem
|
||||||
|
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
|
||||||
|
|
@ -0,0 +1,21 @@
|
|||||||
|
metadata:
|
||||||
|
name: entity equivalence example
|
||||||
|
equivalences:
|
||||||
|
- equivalence:
|
||||||
|
- entity:
|
||||||
|
category: ALARM
|
||||||
|
type: nagios
|
||||||
|
name: cpu_high
|
||||||
|
- entity:
|
||||||
|
category: ALARM
|
||||||
|
type: vitrage
|
||||||
|
name: cpu_high
|
||||||
|
- equivalence:
|
||||||
|
- entity:
|
||||||
|
category: ALARM
|
||||||
|
type: nagios
|
||||||
|
name: mem_low
|
||||||
|
- entity:
|
||||||
|
category: ALARM
|
||||||
|
type: vitrage
|
||||||
|
name: mem_low
|
@ -0,0 +1,66 @@
|
|||||||
|
metadata:
|
||||||
|
name: e2e_test_template_actions
|
||||||
|
description: this template includes vitrage basic actions
|
||||||
|
definitions:
|
||||||
|
entities:
|
||||||
|
- entity:
|
||||||
|
category: ALARM
|
||||||
|
name: e2e.test_template_actions.trigger.alarm1
|
||||||
|
template_id: trigger_alarm_1
|
||||||
|
- entity:
|
||||||
|
category: ALARM
|
||||||
|
type: vitrage
|
||||||
|
name: e2e.test_template_actions.deduced.alarm
|
||||||
|
template_id: deduced_alarm
|
||||||
|
- entity:
|
||||||
|
category: RESOURCE
|
||||||
|
type: nova.host
|
||||||
|
template_id: host
|
||||||
|
- entity:
|
||||||
|
category: RESOURCE
|
||||||
|
type: nova.instance
|
||||||
|
template_id: instance
|
||||||
|
relationships:
|
||||||
|
- relationship:
|
||||||
|
source: trigger_alarm_1
|
||||||
|
relationship_type: on
|
||||||
|
target: host
|
||||||
|
template_id : trigger_alarm_1_on_host
|
||||||
|
- relationship:
|
||||||
|
source: deduced_alarm
|
||||||
|
relationship_type: on
|
||||||
|
target: host
|
||||||
|
template_id : deduced_alarm_on_host
|
||||||
|
- relationship:
|
||||||
|
source: host
|
||||||
|
target: instance
|
||||||
|
relationship_type: contains
|
||||||
|
template_id: host_contains_instance
|
||||||
|
scenarios:
|
||||||
|
- scenario:
|
||||||
|
condition: trigger_alarm_1_on_host
|
||||||
|
actions:
|
||||||
|
- action:
|
||||||
|
action_type: set_state
|
||||||
|
action_target:
|
||||||
|
target: host
|
||||||
|
properties:
|
||||||
|
state: ERROR
|
||||||
|
- scenario:
|
||||||
|
condition: trigger_alarm_1_on_host
|
||||||
|
actions:
|
||||||
|
- action:
|
||||||
|
action_type: raise_alarm
|
||||||
|
action_target:
|
||||||
|
target: host
|
||||||
|
properties:
|
||||||
|
alarm_name: e2e.test_template_actions.deduced.alarm
|
||||||
|
severity: WARNING
|
||||||
|
- scenario:
|
||||||
|
condition: trigger_alarm_1_on_host and deduced_alarm_on_host
|
||||||
|
actions:
|
||||||
|
- action:
|
||||||
|
action_type: add_causal_relationship
|
||||||
|
action_target:
|
||||||
|
source: trigger_alarm_1
|
||||||
|
target: deduced_alarm
|
@ -0,0 +1,66 @@
|
|||||||
|
metadata:
|
||||||
|
name: e2e_test_template_actions_2
|
||||||
|
description: this template includes vitrage basic actions
|
||||||
|
definitions:
|
||||||
|
entities:
|
||||||
|
- entity:
|
||||||
|
category: ALARM
|
||||||
|
name: e2e.test_template_actions.trigger.alarm2
|
||||||
|
template_id: trigger_alarm_2
|
||||||
|
- entity:
|
||||||
|
category: ALARM
|
||||||
|
type: vitrage
|
||||||
|
name: e2e.test_template_actions.deduced.alarm2
|
||||||
|
template_id: deduced_alarm_2
|
||||||
|
- entity:
|
||||||
|
category: RESOURCE
|
||||||
|
type: nova.host
|
||||||
|
template_id: host
|
||||||
|
- entity:
|
||||||
|
category: RESOURCE
|
||||||
|
type: nova.instance
|
||||||
|
template_id: instance
|
||||||
|
relationships:
|
||||||
|
- relationship:
|
||||||
|
source: trigger_alarm_2
|
||||||
|
relationship_type: on
|
||||||
|
target: host
|
||||||
|
template_id : trigger_alarm_2_on_host
|
||||||
|
- relationship:
|
||||||
|
source: deduced_alarm_2
|
||||||
|
relationship_type: on
|
||||||
|
target: host
|
||||||
|
template_id : deduced_alarm_2_on_host
|
||||||
|
- relationship:
|
||||||
|
source: host
|
||||||
|
target: instance
|
||||||
|
relationship_type: contains
|
||||||
|
template_id: host_contains_instance
|
||||||
|
scenarios:
|
||||||
|
- scenario:
|
||||||
|
condition: trigger_alarm_2_on_host
|
||||||
|
actions:
|
||||||
|
- action:
|
||||||
|
action_type: set_state
|
||||||
|
action_target:
|
||||||
|
target: host
|
||||||
|
properties:
|
||||||
|
state: ERROR
|
||||||
|
- scenario:
|
||||||
|
condition: trigger_alarm_2_on_host
|
||||||
|
actions:
|
||||||
|
- action:
|
||||||
|
action_type: raise_alarm
|
||||||
|
action_target:
|
||||||
|
target: host
|
||||||
|
properties:
|
||||||
|
alarm_name: e2e.test_template_actions.deduced.alarm2
|
||||||
|
severity: WARNING
|
||||||
|
- scenario:
|
||||||
|
condition: trigger_alarm_2_on_host and deduced_alarm_2_on_host
|
||||||
|
actions:
|
||||||
|
- action:
|
||||||
|
action_type: add_causal_relationship
|
||||||
|
action_target:
|
||||||
|
source: trigger_alarm_2
|
||||||
|
target: deduced_alarm_2
|
@ -0,0 +1,12 @@
|
|||||||
|
metadata:
|
||||||
|
name: zabbix_host_equivalence
|
||||||
|
equivalences:
|
||||||
|
- equivalence:
|
||||||
|
- entity:
|
||||||
|
category: ALARM
|
||||||
|
type: nagios
|
||||||
|
name: host_problem
|
||||||
|
- entity:
|
||||||
|
category: ALARM
|
||||||
|
type: zabbix
|
||||||
|
name: host_problem
|
@ -0,0 +1,73 @@
|
|||||||
|
metadata:
|
||||||
|
name: host_high_memory_usage_scenarios
|
||||||
|
description: scenarios triggered by high memory usage on physical host
|
||||||
|
definitions:
|
||||||
|
entities:
|
||||||
|
- entity:
|
||||||
|
category: ALARM
|
||||||
|
type: zabbix
|
||||||
|
rawtext: Lack of available memory on server {HOST.NAME}
|
||||||
|
template_id: lack_of_available_memory_alarm
|
||||||
|
- entity:
|
||||||
|
category: ALARM
|
||||||
|
type: vitrage
|
||||||
|
name: Instance memory performance degraded
|
||||||
|
template_id: instance_memory_performance_degraded_alarm
|
||||||
|
- entity:
|
||||||
|
category: RESOURCE
|
||||||
|
type: nova.instance
|
||||||
|
template_id: instance
|
||||||
|
- entity:
|
||||||
|
category: RESOURCE
|
||||||
|
type: nova.host
|
||||||
|
template_id: host
|
||||||
|
relationships:
|
||||||
|
- relationship:
|
||||||
|
source: lack_of_available_memory_alarm
|
||||||
|
relationship_type: on
|
||||||
|
target: host
|
||||||
|
template_id : lack_of_available_memory_alarm_on_host
|
||||||
|
- relationship:
|
||||||
|
source: host
|
||||||
|
relationship_type: contains
|
||||||
|
target: instance
|
||||||
|
template_id : host_contains_instance
|
||||||
|
- relationship:
|
||||||
|
source: instance_memory_performance_degraded_alarm
|
||||||
|
relationship_type: on
|
||||||
|
target: instance
|
||||||
|
template_id : memory_performance_degraded_alarm_on_instance
|
||||||
|
scenarios:
|
||||||
|
- scenario:
|
||||||
|
condition: lack_of_available_memory_alarm_on_host and host_contains_instance
|
||||||
|
actions:
|
||||||
|
- action:
|
||||||
|
action_type: raise_alarm
|
||||||
|
action_target:
|
||||||
|
target: instance
|
||||||
|
properties:
|
||||||
|
alarm_name: Instance memory performance degraded
|
||||||
|
severity: warning
|
||||||
|
- scenario:
|
||||||
|
condition: lack_of_available_memory_alarm_on_host and host_contains_instance and memory_performance_degraded_alarm_on_instance
|
||||||
|
actions:
|
||||||
|
- action:
|
||||||
|
action_type: add_causal_relationship
|
||||||
|
action_target:
|
||||||
|
source: lack_of_available_memory_alarm
|
||||||
|
target: instance_memory_performance_degraded_alarm
|
||||||
|
- action:
|
||||||
|
action_type: set_state
|
||||||
|
action_target:
|
||||||
|
target: instance
|
||||||
|
properties:
|
||||||
|
state: SUBOPTIMAL
|
||||||
|
- scenario:
|
||||||
|
condition: lack_of_available_memory_alarm_on_host
|
||||||
|
actions:
|
||||||
|
- action:
|
||||||
|
action_type: set_state
|
||||||
|
action_target:
|
||||||
|
target: host
|
||||||
|
properties:
|
||||||
|
state: SUBOPTIMAL
|
@ -167,5 +167,5 @@ def wait_for_status(max_waiting, func, **kwargs):
|
|||||||
return True
|
return True
|
||||||
count += 1
|
count += 1
|
||||||
time.sleep(2)
|
time.sleep(2)
|
||||||
LOG.info("wait_for_status - False")
|
LOG.error("wait_for_status - False")
|
||||||
return False
|
return False
|
||||||
|
Loading…
Reference in New Issue
Block a user