Skip to content
Snippets Groups Projects
utilities.py 11.5 KiB
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 :          16-08-2018
//      Created for Project :   FLAME
"""


# Python standard libs
from os.path import join

# 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