Newer
Older
#!/usr/bin/python3
"""
// © University of Southampton IT Innovation Centre, 2018
//
// Copyright in this software belongs to University of Southampton
// IT Innovation Centre of Gamma House, Enterprise Road,
// Chilworth Science Park, Southampton, SO16 7NS, UK.
//
// This software may not be used, sold, licensed, transferred, copied
// or reproduced in whole or in part in any manner or form or in or
// on any media by any person other than in accordance with the terms
// of the Licence Agreement supplied with the software, or otherwise
// without the prior written consent of the copyright owners.
//
// This software is distributed WITHOUT ANY WARRANTY, without even the
// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
// PURPOSE, except where stated in the Licence Agreement supplied with
// the software.
//
// Created By : Nikolay Stanchev
// Created Date : 14-08-2018
// Created for Project : FLAME
"""
# Python standard libs
import logging
# PIP installed libs
from pyramid.httpexceptions import HTTPBadRequest
from pyramid.view import view_defaults, view_config
from yaml import load, YAMLError
from toscaparser.tosca_template import ToscaTemplate
from requests import post
# CLMC-service imports
from clmcservice.alertsapi.utilities import adjust_tosca_definitions_import, TICKScriptTemplateFiller, fill_http_post_handler_vars
from clmcservice.alertsapi.alerts_specification_schema import COMPARISON_OPERATORS, validate_clmc_alerts_specification
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
# initialise logger
log = logging.getLogger('service_logger')
@view_defaults(renderer='json')
class AlertsConfigurationAPI(object):
"""
A class-based view for configuring alerts within CLMC.
"""
def __init__(self, request):
"""
Initialises the instance of the view with the request argument.
:param request: client's call request
"""
self.request = request
@view_config(route_name='alerts_configuration', request_method='POST')
def post_alerts_specification(self):
"""
The view for receiving and configuring alerts based on the TOSCA alerts specification document.
:raises HTTPBadRequest: if the request doesn't contain a (YAML) file input referenced as alert-spec representing the TOSCA Alerts Specification
"""
if not hasattr(self.request.POST.get('alert-spec'), "file") or not hasattr(self.request.POST.get('alert-spec'), "filename"):
raise HTTPBadRequest("Request to this API endpoint must include a (YAML) file input referenced as 'alert-spec' representing the TOSCA Alerts Specification.")
input_filename = self.request.POST['alert-spec'].filename
input_file = self.request.POST['alert-spec'].file
if not input_filename.lower().endswith('.yaml'):
raise HTTPBadRequest("Request to this API endpoint must include a (YAML) file input referenced as 'alert-spec' representing the TOSCA Alerts Specification.")
try:
yaml_content = load(input_file)
adjust_tosca_definitions_import(yaml_content)
except YAMLError as err:
log.error("Couldn't parse user request file {0} to yaml format due to error: {1}".format(input_filename, err))
log.error("Invalid content is: {0}".format(input_file.read()))
raise HTTPBadRequest("Request alert specification file could not be parsed as valid YAML document.")
try:
tosca_tpl = ToscaTemplate(yaml_dict_tpl=yaml_content)
except Exception as e:
log.error(e)
raise HTTPBadRequest("Request alert specification file could not be parsed as a valid TOSCA document.")
valid_alert_spec = validate_clmc_alerts_specification(tosca_tpl.tpl)
if not valid_alert_spec:
raise HTTPBadRequest("Request alert specification file could not be validated as a CLMC TOSCA alerts specification document.")
sfc, sfc_instance = tosca_tpl.tpl["metadata"]["sfc"], tosca_tpl.tpl["metadata"]["sfci"]
db = sfc # ASSUMPTION: database per service function chain, named after the service function chain ID
for policy in tosca_tpl.policies:
for trigger in policy.triggers:
event_id = trigger.name
event_type = trigger.trigger_tpl["event_type"]
template_id = "{0}-template".format(event_type)
measurement, field = trigger.trigger_tpl["metric"].split(".")
condition = trigger.trigger_tpl["condition"]
critical_value = float(condition["threshold"])
alert_period = "{0}s".format(condition["granularity"])
influx_function = condition.get("aggregation_method", "mean") # if not specified, use "mean"
# check for tag filtering
where_clause = None
if "resource_type" in trigger.trigger_tpl["condition"]:
tags = condition["resource_type"]
where_clause = " AND ".join(map(lambda tag_name: '"{0}"==\'{1}\''.format(tag_name, tags[tag_name]), tags))
comparison_operator = COMPARISON_OPERATORS[condition.get("comparison_operator", "gte")] # if not specified, use "gte" (>=)
# generate topic and alert identifiers
topic_id = "{0}.{1}.{2}".format(sfc, sfc_instance, event_id) # scoped per service function chain instance (no two sfc instances report to the same topic)
alert_id = "{0}.{1}.{2}.{3}".format(sfc, sfc_instance, policy.name, event_id)
# built up the template vars dictionary depending on the event type (threshold, relative, etc.)
# all extracted properties from the trigger are passed, the TICKScriptTemplateFiller entry point then forwards those to the appropriate function
template_vars = TICKScriptTemplateFiller.fill_template_vars(event_type, db=db, measurement=measurement, field=field, influx_function=influx_function,
critical_value=critical_value, comparison_operator=comparison_operator, alert_period=alert_period,
topic_id=topic_id, where_clause=where_clause)
# create and activate alert task through the kapacitor HTTP API
kapacitor_api_tasks_url = "http://localhost:9092/kapacitor/v1/tasks"
kapacitor_http_request_body = {
"id": alert_id,
"template-id": template_id,
"dbrps": [{"db": db, "rp": "autogen"}],
"status": "enabled",
"vars": template_vars
}
# send the request and receive a response
response = post(kapacitor_api_tasks_url, json=kapacitor_http_request_body)
response_content = response.json()
# log the response
log.info(response_content, response.status_code)
# exttranc http handlers
http_handlers = trigger.trigger_tpl["action"]["implementation"]
# subscribe all http handlers to the created topic
kapacitor_api_handlers_url = "http://localhost:9092/kapacitor/v1/alerts/topics/{0}/handlers".format(topic_id)
for http_handler_url in http_handlers:
http_handler_host = urlparse(http_handler_url).hostname
handler_id = "{0}.{1}.{2}".format(policy.name, event_id, http_handler_host)
kapacitor_http_request_body = fill_http_post_handler_vars(handler_id, http_handler_url)
response = post(kapacitor_api_handlers_url, json=kapacitor_http_request_body)
response_content = response.json()
log.info(response_content, response.status_code)
return {"msg": "Alerts specification has been successfully validated and configured", "service_function_chain_id": sfc,
"service_function_chain_instance_id": sfc_instance}