#!/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 : 16-08-2018 // Created for Project : FLAME """ # Python standard libs from os.path import join # PIP installed libs from yaml import load # CLMC-service imports from clmcservice import ROOT_DIR CLMC_ALERTS_TOSCA_DEFINITIONS_REL_PATH = ["static", "flame_clmc_alerts_definitions.yaml"] CLMC_ALERTS_TOSCA_DEFINITIONS_ABS_PATH = join(ROOT_DIR, *CLMC_ALERTS_TOSCA_DEFINITIONS_REL_PATH) CLMC_ALERTS_TOSCA_DEFINITIONS_FILE = CLMC_ALERTS_TOSCA_DEFINITIONS_REL_PATH[-1] def adjust_tosca_definitions_import(alert_spec): """ A utility function to adjust any imports of flame_clmc_alerts_definitions.yaml to point to the correct location of the tosca definitions file. :param alert_spec: the TOSCA alert specification content (yaml dict) """ global CLMC_ALERTS_TOSCA_DEFINITIONS_ABS_PATH try: import_index = alert_spec["imports"].index(CLMC_ALERTS_TOSCA_DEFINITIONS_FILE) alert_spec["imports"][import_index] = CLMC_ALERTS_TOSCA_DEFINITIONS_ABS_PATH except Exception: pass # nothing to replace if the import is not specified (either imports are missed, or no reference to the clmc tosca definitions file) def get_resource_spec_topic_ids(resource_spec_reference): """ Tries to extract all event identifiers from a TOSCA resource specification :param resource_spec_reference: the resource specification file reference from the POST HTTP request :return: sfc ID, sfc instance ID and the list of topic IDs """ resource_spec = load(resource_spec_reference.file) topic_ids = [] sfc, sfc_i = resource_spec["metadata"]["sfc"], resource_spec["metadata"]["sfci"] policies = resource_spec["topology_template"]["policies"] for policy in policies: policy = list(policy.items())[0] policy_id, policy_object = policy[0], policy[1] if policy_object["type"] == "eu.ict-flame.policies.StateChange": triggers = policy_object["triggers"] for trigger in triggers.values(): event = trigger["condition"]["constraint"] source, event_id = event.split("::") if source.lower() == "clmc": # only take those event IDs that have clmc set as their source topic_ids.append("{0}\n{1}".format(policy_id, event_id)) return sfc, sfc_i, topic_ids def get_alert_spec_topic_ids(alerts_spec_tpl): """ Tries to extract all event identifiers from a TOSCA alerts specification :param alerts_spec_tpl: the alerts specification TOSCA template object :return: the list of topic IDs """ topic_ids = [] for policy in alerts_spec_tpl.policies: policy_id = policy.name for trigger in policy.triggers: trigger_id = trigger.name topic_id = "{0}\n{1}".format(policy_id, trigger_id) topic_ids.append(topic_id) return topic_ids def fill_http_post_handler_vars(handler_id, handler_url): """ Creates a dictionary object ready to be posted to kapacitor to create an alert handler. :param handler_id: handler identifier :param handler_url: url to post alerts to :return: a dictionary object ready to be posted to kapacitor to create an alert handler. """ return { "id": handler_id, "kind": "post", "options": { "url": handler_url } } class TICKScriptTemplateFiller: """ A utility class used for TICK script templates filtering. """ # a class variable used to hold the comparison operator used to build the where clause in TICK script templates, # these differ if the where clause is built as a string opposed to when it is build as a lambda _TEMPLATE_COMPARISON_OPERATOR = {"threshold": "=", "relative": "=", "deadman": "=="} @staticmethod def get_comparison_operator(template_type): """ Get the correct comparison operator depending on the template type, if template type not recognized, return "==" :param template_type: one of the template types, that are created within kapacitor :return: the comparison operator that should be used in the template to build the where clause """ return TICKScriptTemplateFiller._TEMPLATE_COMPARISON_OPERATOR.get(template_type, "==") @staticmethod def fill_template_vars(template_type, **kwargs): """ A utility function acting as an entry poiny to the fill_<template_type>_template_vars() functions defined below. :param template_type: the template type - e.g. :param kwargs: keyword arguments to forward to the actual function that will be used :return: the result of the actual function that will be used. """ fill_function_name = "_fill_{0}_template_vars".format(template_type) fill_function = getattr(TICKScriptTemplateFiller, fill_function_name) # python functions are first-class objects ! return fill_function(**kwargs) @staticmethod def _fill_threshold_template_vars(db=None, measurement=None, field=None, influx_function=None, critical_value=None, comparison_operator=None, alert_period=None, topic_id=None, where_clause=None, **kwargs): """ Creates a dictionary object ready to be posted to kapacitor to create a "threshold" task from template. :param db: db name :param measurement: measurement name :param field: field name :param influx_function: influx function to use for querying :param critical_value: critical value to compare with :param comparison_operator: type of comparison :param alert_period: alert period to query influx :param topic_id: topic identifier :param where_clause: (OPTIONAL) argument for filtering the influx query by tag values :return: a dictionary object ready to be posted to kapacitor to create a "threshold" task from template. """ comparison_lambda = '"real_value" {0} {1}'.format(comparison_operator, critical_value) # build up lambda string, e.g. "real_value" >= 10 template_vars = { "db": { "type": "string", "value": db }, "measurement": { "type": "string", "value": measurement }, "field": { "type": "string", "value": field }, "influxFunction": { "type": "string", "value": influx_function }, "comparisonLambda": { "type": "lambda", "value": comparison_lambda }, "alertPeriod": { "type": "duration", "value": alert_period }, "topicID": { "type": "string", "value": topic_id } } if where_clause is not None: template_vars["whereClause"] = { "type": "string", "value": where_clause } return template_vars @staticmethod def _fill_relative_template_vars(db=None, measurement=None, field=None, influx_function=None, critical_value=None, comparison_operator=None, alert_period=None, topic_id=None, where_clause=None, **kwargs): """ Creates a dictionary object ready to be posted to kapacitor to create a "relative" task from template. :param db: db name :param measurement: measurement name :param field: field name :param influx_function: influx function to use for querying :param critical_value: critical value to compare with :param comparison_operator: type of comparison :param alert_period: alert period to use for relative comparison :param topic_id: topic identifier :param where_clause: (OPTIONAL) argument for filtering the influx query by tag values :return: a dictionary object ready to be posted to kapacitor to create a "relative" task from template. """ comparison_lambda = '"diff" {0} {1}'.format(comparison_operator, critical_value) template_vars = { "db": { "type": "string", "value": db }, "measurement": { "type": "string", "value": measurement }, "field": { "type": "string", "value": field }, "influxFunction": { "type": "string", "value": influx_function }, "comparisonLambda": { "type": "lambda", "value": comparison_lambda }, "alertPeriod": { "type": "duration", "value": alert_period }, "topicID": { "type": "string", "value": topic_id } } if where_clause is not None: template_vars["whereClause"] = { "type": "string", "value": where_clause } return template_vars @staticmethod def _fill_deadman_template_vars(db=None, measurement=None, critical_value=None, alert_period=None, topic_id=None, where_clause=None, **kwargs): """ Creates a dictionary object ready to be posted to kapacitor to create a "deadman" task from template. :param db: db name :param measurement: measurement name :param critical_value: critical value to compare with :param alert_period: alert period to use for relative comparison :param topic_id: topic identifier :param where_clause: (OPTIONAL) argument for filtering the influx query by tag values :return: a dictionary object ready to be posted to kapacitor to create a "deadman" task from template. """ template_vars = { "db": { "type": "string", "value": db }, "measurement": { "type": "string", "value": measurement }, "alertPeriod": { "type": "duration", "value": alert_period }, "throughputThreshold": { "type": "float", "value": critical_value }, "topicID": { "type": "string", "value": topic_id } } if where_clause is not None: template_vars["whereClause"] = { "type": "lambda", "value": where_clause } return template_vars