#!/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 to clmc service a POST request with TOSCA alert spec. and resource spec. files * Wait 10 seconds for Kapacitor to configure and start executing the defined tasks * Send some test requests to nginx to increase the load * Wait 15 seconds for alerts to be triggered * Check that 4 log files have been created - one for each alert defined in the alert spec. * Send to clmc service a DELETE request with TOSCA alert spec. file * Check that the returned lists of deleted handlers and alerts are correct :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 sleep(0.25) 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"