From f2cf03287b48c2529aa0d8af1e2ab3ae8fb0b8d7 Mon Sep 17 00:00:00 2001 From: Nikolay Stanchev <ns17@it-innovation.soton.ac.uk> Date: Tue, 21 Aug 2018 14:57:31 +0100 Subject: [PATCH] Implements tests for alerts API --- src/service/clmcservice/alertsapi/tests.py | 121 +++++++++++++++++++-- src/service/clmcservice/alertsapi/views.py | 7 +- 2 files changed, 112 insertions(+), 16 deletions(-) diff --git a/src/service/clmcservice/alertsapi/tests.py b/src/service/clmcservice/alertsapi/tests.py index 11984dc..9707a70 100644 --- a/src/service/clmcservice/alertsapi/tests.py +++ b/src/service/clmcservice/alertsapi/tests.py @@ -26,12 +26,13 @@ # Python standard libs from os import listdir from os.path import isfile, join - +from urllib.parse import urlparse # PIP installed libs import pytest from yaml import load from pyramid import testing +from requests import get, delete from toscaparser.tosca_template import ToscaTemplate # CLMC-service imports @@ -129,27 +130,67 @@ class TestAlertsConfigurationAPI(object): """ Tests the POST API endpoint of the alerts configuration API responsible for receiving alerts specifications. - Unit test consists of: - * Traverse all valid TOSCA Alerts Specifications in the src/service/clmcservice/resources/tosca/test-data/clmc-validator/valid + Test steps are: + * Traverse all valid TOSCA Alerts Specifications in the + src/service/clmcservice/resources/tosca/test-data/clmc-validator/valid and src/service/clmcservice/resources/tosca/test-data/tosca-parser/valid * Sending a valid TOSCA Alert Specification to the view responsible for configuring Kapacitor * Check that Kapacitor alerts, topics and handlers are created with the correct identifier and arguments :param app_config: fixture for setUp/tearDown of the web service registry """ - test_data_path = join(ROOT_DIR, *["resources", "tosca", "test-data", "clmc-validator", "valid"]) + for test_folder in ("clmc-validator", "tosca-parser"): + test_data_path = join(ROOT_DIR, *["resources", "tosca", "test-data", test_folder, "valid"]) + + for test_file_path in listdir(test_data_path): + alert_spec_abs_path = join(test_data_path, test_file_path) + + if not isfile(alert_spec_abs_path): + continue # skip directories + + if not test_file_path.lower().endswith('.yaml'): + continue # non-yaml files are not intended for being tested + + print("Testing file {0} in folder {1}".format(test_file_path, test_folder)) + + request = testing.DummyRequest() + + with open(alert_spec_abs_path) as alert_spec: + sfc, sfc_instance, alert_ids, topic_handlers = extract_alert_spec_data(alert_spec) + alert_spec.seek(0) + request.POST['alert-spec'] = FieldStorageMock(test_file_path, alert_spec) # a simple mock class is used to mimic the FieldStorage class + clmc_service_response = AlertsConfigurationAPI(request).post_alerts_specification() + + assert (sfc, sfc_instance) == (clmc_service_response["service_function_chain_id"], clmc_service_response["service_function_chain_instance_id"]), \ + "Incorrect extraction of metadata for file {0}". format(test_file_path) - for test_file_path in listdir(test_data_path): + # traverse through all alert IDs and check that they are created within Kapacitor + for alert_id in alert_ids: + kapacitor_response = get("http://localhost:9092/kapacitor/v1/tasks/{0}".format(alert_id)) + assert kapacitor_response.status_code == 200, "Alert with ID {0} was not created - test file {1}.".format(alert_id, test_file_path) + kapacitor_response_json = kapacitor_response.json() + assert "link" in kapacitor_response_json, "Incorrect response from kapacitor for alert with ID {0} - test file {1}".format(alert_id, test_file_path) + assert kapacitor_response_json["status"] == "enabled", "Alert with ID {0} was created but is disabled - test file {1}".format(alert_id, test_file_path) - request = testing.DummyRequest() - alert_spec_abs_path = join(test_data_path, test_file_path) - print(alert_spec_abs_path) - with open(alert_spec_abs_path) as alert_spec: - request.POST['alert-spec'] = FieldStorageMock(test_file_path, alert_spec) # a simple mock class is used to mimic the FieldStorage class + # check that all topic IDs were registered within Kapacitor + topic_ids = list(topic_handlers.keys()) + kapacitor_response = get("http://localhost:9092/kapacitor/v1/alerts/topics") + assert kapacitor_response.status_code == 200, "Kapacitor couldn't return the list of created topics - test file {0}".format(test_file_path) + kapacitor_response_json = kapacitor_response.json() + kapacitor_defined_topics = [topic["id"] for topic in kapacitor_response_json["topics"]] + assert set(topic_ids).issubset(kapacitor_defined_topics), "Not all topic IDs were created within kapacitor - test file {0}".format(test_file_path) - print(AlertsConfigurationAPI(request).post_alerts_specification()) + # check that all handler IDs were created and each of them is subscribed to the correct topic ID + for topic_id in topic_handlers: + for handler_id, handler_url in topic_handlers[topic_id]: + kapacitor_response = get("http://localhost:9092/kapacitor/v1/alerts/topics/{0}/handlers/{1}".format(topic_id, handler_id)) + assert kapacitor_response.status_code == 200, "Handler with ID {0} for topic with ID {1} doesn't exist - test file {2}".format(handler_id, topic_id, test_file_path) + kapacitor_response_json = kapacitor_response.json() + assert kapacitor_response_json["id"] == handler_id, "Incorrect ID of handler {0} in the Kapacitor response - test file {1}".format(handler_id, test_file_path) + assert kapacitor_response_json["kind"] == "post", "Incorrect kind of handler {0} in the Kapacitor response - test file {1}".format(handler_id, test_file_path) + assert kapacitor_response_json["options"]["url"], "Incorrect url of handler {0} in the Kapacitor response - test file {1}".format(handler_id, test_file_path) - break + clear_kapacitor_alerts(alert_ids, topic_handlers) class FieldStorageMock(object): @@ -164,3 +205,59 @@ class FieldStorageMock(object): self.filename = filename self.file = file + + +def extract_alert_spec_data(alert_spec): + """ + A utility function to extract the expected alert, handler and topic identifiers from a given alert specification. + + :param alert_spec: the alert specification file (file object) + :return: a tuple containing sfc_id and sfc_instance_id along with a list and a dictionary of generated IDs (alert IDs (list), topic IDs linked to handler IDs (dict)) + """ + + yaml_alert_spec = load(alert_spec) + adjust_tosca_definitions_import(yaml_alert_spec) + tosca_tpl = ToscaTemplate(yaml_dict_tpl=yaml_alert_spec) + sfc, sfc_instance = tosca_tpl.tpl["metadata"]["sfc"], tosca_tpl.tpl["metadata"]["sfci"] + + alert_ids = [] # saves all alert IDs in a list + topic_handlers = {} # saves all topics in a dictionary, each topic is linked to a list of handler pairs (a handler pair consists of handler id and handler url) + + for policy in tosca_tpl.policies: + policy_id = policy.name + for trigger in policy.triggers: + trigger_id = trigger.name + + topic_id = "{0}.{1}.{2}".format(sfc, sfc_instance, trigger_id) + topic_handlers[topic_id] = [] + + alert_id = "{0}.{1}.{2}.{3}".format(sfc, sfc_instance, policy_id, trigger_id) + alert_ids.append(alert_id) + + for handler_url in trigger.trigger_tpl["action"]["implementation"]: + handler_host = urlparse(handler_url).hostname + handler_id = "{0}.{1}.{2}".format(policy_id, trigger_id, handler_host) + topic_handlers[topic_id].append((handler_id, handler_url)) + + return sfc, sfc_instance, alert_ids, topic_handlers + + +def clear_kapacitor_alerts(alert_ids, topic_handlers): + """ + A utility function to clean up Kapacitor from the configured alerts, topics and handlers. + + :param alert_ids: the list of alert IDs to delete + :param topic_handlers: the dictionary of topic and handlers to delete + """ + + for alert_id in alert_ids: + kapacitor_response = delete("http://localhost:9092/kapacitor/v1/tasks/{0}".format(alert_id)) # delete alert + assert kapacitor_response.status_code == 204 + + for topic_id in topic_handlers: + for handler_id, handler_url in topic_handlers[topic_id]: + kapacitor_response = delete("http://localhost:9092/kapacitor/v1/alerts/topics/{0}/handlers/{1}".format(topic_id, handler_id)) # delete handler + assert kapacitor_response.status_code == 204 + + kapacitor_response = delete("http://localhost:9092/kapacitor/v1/alerts/topics/{0}".format(topic_id)) # delete topic + assert kapacitor_response.status_code == 204 diff --git a/src/service/clmcservice/alertsapi/views.py b/src/service/clmcservice/alertsapi/views.py index 70cbf7f..5f9e24e 100644 --- a/src/service/clmcservice/alertsapi/views.py +++ b/src/service/clmcservice/alertsapi/views.py @@ -24,7 +24,6 @@ # Python standard libs import logging -from json import dumps from urllib.parse import urlparse # PIP installed libs @@ -136,7 +135,7 @@ class AlertsConfigurationAPI(object): } # send the request and receive a response - response = post(kapacitor_api_tasks_url, data=dumps(kapacitor_http_request_body)) + 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) @@ -147,10 +146,10 @@ class AlertsConfigurationAPI(object): # 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).netloc + 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, data=dumps(kapacitor_http_request_body)) + response = post(kapacitor_api_handlers_url, json=kapacitor_http_request_body) response_content = response.json() log.info(response_content, response.status_code) -- GitLab