Skip to content
Snippets Groups Projects
test_alerts.py 7.73 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 :          22-08-2018
##      Created for Project :   FLAME
"""

from time import sleep, strptime
from requests import post, get, delete
from os import listdir
from os.path import join, dirname
from json import load
from schema import Schema, And, Or, Optional, SchemaError
from clmctest.alerts.alert_handler_server import LOG_TEST_FOLDER_PATH


NGINX_PORT = 80


def is_valid_timestamp(str_timestamp):
    try:
        strptime(str_timestamp, "%Y-%m-%dT%H:%M:%SZ")
        return True
    except ValueError:
        return False


def is_valid_details_string(details):
    try:
        details_dict = {key.strip(): value.strip() for key,value in [item.split("=") for item in details.split(",")]}
        return len(details_dict) == 4 and "db" in details_dict and "sfc" in details_dict and "sfci" in details_dict and "policy" in details_dict
    except Exception:
        return False


JSON_BODY_SCHEMA = Schema({
    "message": "TRUE",
    "id": str,
    "level": "CRITICAL",
    "duration": int,
    "previousLevel": str,
    "details": And(str, is_valid_details_string),
    "time": And(str, is_valid_timestamp),
    "data": {
        "series": [
            {
                "name": str,
                Optional("tags"): {
                    str: str
                },
                "columns": [
                    str
                ],
                "values": [
                    [
                        Or(str, int)
                    ]
                ]
            }
        ]
    }
})


class TestAlerts(object):

    def test_alert_triggers(self, rspec_config, set_up_tear_down_fixture):
        """
        Test is implemented using the following steps:
            * Send clmc service a TOSCA alert spec. file
            * Wait 15 seconds for Kapacitor to configure and start executing the defined tasks
            * Send some test requests to nginx to increase the load
            * Wait 20 seconds for alerts to be triggered
            * Check that 4 log files have been created - one for each alert defined in the alert spec.

        :param rspec_config: fixture from conftest.py
        """

        global NGINX_PORT, JSON_BODY_SCHEMA

        clmc_service_host, nginx_host = None, None
        for host in rspec_config:
            if host["name"] == "clmc-service":
                clmc_service_host = host["ip_address"]
            elif host["name"] == "nginx":
                nginx_host = host["ip_address"]

            if clmc_service_host is not None and nginx_host is not None:
                break

        print("Sending alerts specification to clmc service...")
        alerts_spec = join(dirname(__file__), "alerts_test_config.yaml")
        resources_spec = join(dirname(__file__), "resources_test_config.yaml")

        with open(alerts_spec, 'rb') as alerts:
            with open(resources_spec, 'rb') as resources:
                files = {'alert-spec': alerts, 'resource-spec': resources}
                response = post("http://{0}/clmc-service/alerts".format(clmc_service_host), files=files)

        assert response.status_code == 200
        clmc_service_response = response.json()
        assert "triggers_specification_errors" not in clmc_service_response, "Unexpected error was returned for triggers specification"
        assert "triggers_action_errors" not in clmc_service_response, "Unexpected error was returned for handlers specification"

        print("Alert spec sent successfully")

        print("Wait 10 seconds for Kapacitor stream/batch tasks to start working...")
        sleep(10)

        print("Sending test requests to nginx...")
        for i in range(40):
            response = get("http://{0}:{1}/".format(nginx_host, NGINX_PORT))
            assert response.status_code == 200
        print("Wait 15 seconds for Kapacitor to trigger alerts...")
        sleep(15)
        alert_logs = listdir(LOG_TEST_FOLDER_PATH)
        assert len(alert_logs) == 4, "4 log files must have been created - one for each alert defined in the specification."

        for alert_log in alert_logs:
            alert_log_path = join(LOG_TEST_FOLDER_PATH, alert_log)

            with open(alert_log_path) as fh:
                alert_json = load(fh)

            try:
                JSON_BODY_SCHEMA.validate(alert_json)
                valid = True
            except SchemaError:
                valid = False

            assert valid, "Alert log content is invalid - {0}".format(alert_log_path)

        with open(alerts_spec, 'rb') as alerts:
            files = {'alert-spec': alerts}
            response = delete("http://{0}/clmc-service/alerts".format(clmc_service_host), files=files)

        assert response.status_code == 200, "Incorrect status code returned after deleting the alert specification"

        json_response = response.json()
        # sort by trigger to ensure comparison order is correct
        assert sorted(json_response["deleted_alerts"], key=lambda x: x['trigger']) == [{"policy": "scale_nginx_policy", "trigger": "high_requests"}, {"policy": "scale_nginx_policy", "trigger": "increase_in_active_requests"},
                                                                                       {"policy": "scale_nginx_policy", "trigger": "increase_in_running_processes"}, {"policy": "deadman_policy", "trigger": "no_measurements"}], \
            "Incorrect list of deleted alerts"
        # sort by handler and trigger to ensure comparison order is correct
        assert sorted(json_response["deleted_handlers"], key=lambda x: (x['handler'], x['trigger'])) == [{"policy": "scale_nginx_policy", "trigger": "increase_in_active_requests", "handler": "flame_sfemc"},
                                                                                                         {"policy": "scale_nginx_policy", "trigger": "increase_in_running_processes", "handler": "flame_sfemc"},
                                                                                                         {"policy": "deadman_policy", "trigger": "no_measurements", "handler": "flame_sfemc"},
                                                                                                         {"policy": "scale_nginx_policy", "trigger": "high_requests", "handler": "http://172.40.231.200:9999/"},
                                                                                                         {"policy": "scale_nginx_policy", "trigger": "increase_in_active_requests", "handler": "http://172.40.231.200:9999/"},
                                                                                                         {"policy": "scale_nginx_policy", "trigger": "increase_in_running_processes", "handler": "http://172.40.231.200:9999/"},
                                                                                                         {"policy": "deadman_policy", "trigger": "no_measurements", "handler": "http://172.40.231.200:9999/"}], \
            "Incorrect list of deleted handlers"