mirror of
				https://github.com/asterisk/asterisk.git
				synced 2025-10-31 02:37:10 +00:00 
			
		
		
		
	Currently, each module that uses libcurl duplicates the standard Asterisk curl user agent. This adds a global macro for the Asterisk user agent used for curl requests to eliminate this duplication. ASTERISK-29861 #close Change-Id: I9fc37935980384b4daf96ae54fa3c9adb962ed2d
		
			
				
	
	
		
			828 lines
		
	
	
		
			26 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			828 lines
		
	
	
		
			26 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*
 | |
|  * Asterisk -- An open source telephony toolkit.
 | |
|  *
 | |
|  * Copyright (C) 2019 Sangoma, Inc.
 | |
|  *
 | |
|  * Matt Jordan <mjordan@digium.com>
 | |
|  *
 | |
|  * See http://www.asterisk.org for more information about
 | |
|  * the Asterisk project. Please do not directly contact
 | |
|  * any of the maintainers of this project for assistance;
 | |
|  * the project provides a web site, mailing lists and IRC
 | |
|  * channels for your use.
 | |
|  *
 | |
|  * This program is free software, distributed under the terms of
 | |
|  * the GNU General Public License Version 2. See the LICENSE file
 | |
|  * at the top of the source tree.
 | |
|  */
 | |
| 
 | |
| /*** MODULEINFO
 | |
| 	<depend>TEST_FRAMEWORK</depend>
 | |
| 	<depend>res_prometheus</depend>
 | |
| 	<depend>curl</depend>
 | |
| 	<support_level>extended</support_level>
 | |
|  ***/
 | |
| 
 | |
| #include "asterisk.h"
 | |
| 
 | |
| #include <curl/curl.h>
 | |
| 
 | |
| #include "asterisk/test.h"
 | |
| #include "asterisk/module.h"
 | |
| #include "asterisk/config.h"
 | |
| #include "asterisk/res_prometheus.h"
 | |
| 
 | |
| #define CATEGORY "/res/prometheus/"
 | |
| 
 | |
| static char server_uri[512];
 | |
| 
 | |
| struct prometheus_general_config *module_config;
 | |
| 
 | |
| static void curl_free_wrapper(void *ptr)
 | |
| {
 | |
| 	if (!ptr) {
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	curl_easy_cleanup(ptr);
 | |
| }
 | |
| 
 | |
| static void prometheus_metric_free_wrapper(void *ptr)
 | |
| {
 | |
| 	if (prometheus_metric_unregister(ptr)) {
 | |
| 		prometheus_metric_free(ptr);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static struct prometheus_general_config *config_alloc(void)
 | |
| {
 | |
| 	struct prometheus_general_config *config;
 | |
| 
 | |
| 	config = prometheus_general_config_alloc();
 | |
| 	if (!config) {
 | |
| 		return NULL;
 | |
| 	}
 | |
| 
 | |
| 	/* Set what we need on the config for most tests */
 | |
| 	ast_string_field_set(config, uri, "test_metrics");
 | |
| 	config->enabled = 1;
 | |
| 	config->core_metrics_enabled = 0;
 | |
| 
 | |
| 	return config;
 | |
| }
 | |
| 
 | |
| static CURL *get_curl_instance(void)
 | |
| {
 | |
| 	CURL *curl;
 | |
| 
 | |
| 	curl = curl_easy_init();
 | |
| 	if (!curl) {
 | |
| 		return NULL;
 | |
| 	}
 | |
| 
 | |
| 	curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);
 | |
| 	curl_easy_setopt(curl, CURLOPT_TIMEOUT, 180);
 | |
| 	curl_easy_setopt(curl, CURLOPT_USERAGENT, AST_CURL_USER_AGENT);
 | |
| 	curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
 | |
| 	curl_easy_setopt(curl, CURLOPT_URL, server_uri);
 | |
| 
 | |
| 	return curl;
 | |
| }
 | |
| 
 | |
| static size_t curl_write_string_callback(void *contents, size_t size, size_t nmemb, void *userdata)
 | |
| {
 | |
| 	struct ast_str **buffer = userdata;
 | |
| 	size_t realsize = size * nmemb;
 | |
| 	char *rawdata;
 | |
| 
 | |
| 	rawdata = ast_malloc(realsize + 1);
 | |
| 	if (!rawdata) {
 | |
| 		return 0;
 | |
| 	}
 | |
| 	memcpy(rawdata, contents, realsize);
 | |
| 	rawdata[realsize] = 0;
 | |
| 	ast_str_append(buffer, 0, "%s", rawdata);
 | |
| 	ast_free(rawdata);
 | |
| 
 | |
| 	return realsize;
 | |
| }
 | |
| 
 | |
| static void metric_values_get_counter_value_cb(struct prometheus_metric *metric)
 | |
| {
 | |
| 	strcpy(metric->value, "2");
 | |
| }
 | |
| 
 | |
| AST_TEST_DEFINE(metric_values)
 | |
| {
 | |
| 	RAII_VAR(CURL *, curl, NULL, curl_free_wrapper);
 | |
| 	RAII_VAR(struct ast_str *, buffer, NULL, ast_free);
 | |
| 	int res;
 | |
| 	struct prometheus_metric test_counter_one = PROMETHEUS_METRIC_STATIC_INITIALIZATION(
 | |
| 		PROMETHEUS_METRIC_COUNTER,
 | |
| 		"test_counter_one",
 | |
| 		"A test counter",
 | |
| 		NULL);
 | |
| 	struct prometheus_metric test_counter_two = PROMETHEUS_METRIC_STATIC_INITIALIZATION(
 | |
| 		PROMETHEUS_METRIC_COUNTER,
 | |
| 		"test_counter_two",
 | |
| 		"A test counter",
 | |
| 		metric_values_get_counter_value_cb);
 | |
| 	enum ast_test_result_state result = AST_TEST_PASS;
 | |
| 
 | |
| 	switch (cmd) {
 | |
| 	case TEST_INIT:
 | |
| 		info->name = __func__;
 | |
| 		info->category = CATEGORY;
 | |
| 		info->summary = "Test value generation/respecting in metrics";
 | |
| 		info->description =
 | |
| 			"Metrics have two ways to provide values when the HTTP callback\n"
 | |
| 			"is invoked:\n"
 | |
| 			"1. By using the direct value that resides in the metric\n"
 | |
| 			"2. By providing a callback function to specify the value\n"
 | |
| 			"This test verifies that both function appropriately when the\n"
 | |
| 			"HTTP callback is called.";
 | |
| 		return AST_TEST_NOT_RUN;
 | |
| 	case TEST_EXECUTE:
 | |
| 		break;
 | |
| 	}
 | |
| 
 | |
| 	buffer = ast_str_create(128);
 | |
| 	if (!buffer) {
 | |
| 		return AST_TEST_FAIL;
 | |
| 	}
 | |
| 
 | |
| 	curl = get_curl_instance();
 | |
| 	if (!curl) {
 | |
| 		return AST_TEST_FAIL;
 | |
| 	}
 | |
| 
 | |
| 	ast_test_validate_cleanup(test, prometheus_metric_register(&test_counter_one) == 0, result, metric_values_cleanup);
 | |
| 	ast_test_validate_cleanup(test, prometheus_metric_register(&test_counter_two) == 0, result, metric_values_cleanup);
 | |
| 	strcpy(test_counter_one.value, "1");
 | |
| 
 | |
| 	ast_test_status_update(test, " -> CURLing request...\n");
 | |
| 	curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_write_string_callback);
 | |
| 	curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buffer);
 | |
| 	res = curl_easy_perform(curl);
 | |
| 	if (res != CURLE_OK) {
 | |
| 		ast_test_status_update(test, "Failed to execute CURL: %d\n", res);
 | |
| 		result = AST_TEST_FAIL;
 | |
| 		goto metric_values_cleanup;
 | |
| 	}
 | |
| 
 | |
| 	ast_test_status_update(test, " -> Retrieved: %s\n", ast_str_buffer(buffer));
 | |
| 	ast_test_validate_cleanup(test, strstr(ast_str_buffer(buffer),
 | |
| 		"# HELP test_counter_one A test counter\n"
 | |
| 		"# TYPE test_counter_one counter\n"
 | |
| 		"test_counter_one 1\n"
 | |
| 		"# HELP test_counter_two A test counter\n"
 | |
| 		"# TYPE test_counter_two counter\n"
 | |
| 		"test_counter_two 2\n") != NULL, result, metric_values_cleanup);
 | |
| 
 | |
| metric_values_cleanup:
 | |
| 	prometheus_metric_unregister(&test_counter_one);
 | |
| 	prometheus_metric_unregister(&test_counter_two);
 | |
| 
 | |
| 	return result;
 | |
| }
 | |
| 
 | |
| static void prometheus_metric_callback(struct ast_str **output)
 | |
| {
 | |
| 	struct prometheus_metric test_counter = PROMETHEUS_METRIC_STATIC_INITIALIZATION(
 | |
| 		PROMETHEUS_METRIC_COUNTER,
 | |
| 		"test_counter",
 | |
| 		"A test counter",
 | |
| 		NULL);
 | |
| 
 | |
| 	prometheus_metric_to_string(&test_counter, output);
 | |
| }
 | |
| 
 | |
| AST_TEST_DEFINE(metric_callback_register)
 | |
| {
 | |
| 	RAII_VAR(CURL *, curl, NULL, curl_free_wrapper);
 | |
| 	RAII_VAR(struct ast_str *, buffer, NULL, ast_free);
 | |
| 	int res;
 | |
| 	struct prometheus_callback callback = {
 | |
| 		.name = "test_callback",
 | |
| 		.callback_fn = &prometheus_metric_callback,
 | |
| 	};
 | |
| 
 | |
| 	switch (cmd) {
 | |
| 	case TEST_INIT:
 | |
| 		info->name = __func__;
 | |
| 		info->category = CATEGORY;
 | |
| 		info->summary = "Test registration of callbacks";
 | |
| 		info->description =
 | |
| 			"This test covers callback registration. It registers\n"
 | |
| 			"a callback that is invoked when an HTTP request is made,\n"
 | |
| 			"and it verifies that during said callback the output to\n"
 | |
| 			"the response string is correctly appended to. It also verifies\n"
 | |
| 			"that unregistered callbacks are not invoked.";
 | |
| 		return AST_TEST_NOT_RUN;
 | |
| 	case TEST_EXECUTE:
 | |
| 		break;
 | |
| 	}
 | |
| 
 | |
| 	buffer = ast_str_create(128);
 | |
| 	if (!buffer) {
 | |
| 		return AST_TEST_FAIL;
 | |
| 	}
 | |
| 
 | |
| 	ast_test_validate(test, prometheus_callback_register(&callback) == 0);
 | |
| 
 | |
| 	curl = get_curl_instance();
 | |
| 	if (!curl) {
 | |
| 		return AST_TEST_NOT_RUN;
 | |
| 	}
 | |
| 
 | |
| 	ast_test_status_update(test, " -> CURLing request...\n");
 | |
| 	curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_write_string_callback);
 | |
| 	curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buffer);
 | |
| 	res = curl_easy_perform(curl);
 | |
| 	if (res != CURLE_OK) {
 | |
| 		ast_test_status_update(test, "Failed to execute CURL: %d\n", res);
 | |
| 		return AST_TEST_FAIL;
 | |
| 	}
 | |
| 
 | |
| 	ast_test_status_update(test, " -> Retrieved: %s\n", ast_str_buffer(buffer));
 | |
| 	ast_test_validate(test, strstr(ast_str_buffer(buffer),
 | |
| 		"# HELP test_counter A test counter\n"
 | |
| 		"# TYPE test_counter counter\n"
 | |
| 		"test_counter 0\n") != NULL);
 | |
| 
 | |
| 	prometheus_callback_unregister(&callback);
 | |
| 
 | |
| 	return AST_TEST_PASS;
 | |
| }
 | |
| 
 | |
| AST_TEST_DEFINE(metric_register)
 | |
| {
 | |
| 	struct prometheus_metric test_counter = PROMETHEUS_METRIC_STATIC_INITIALIZATION(
 | |
| 		PROMETHEUS_METRIC_COUNTER,
 | |
| 		"test_counter",
 | |
| 		"A test counter",
 | |
| 		NULL);
 | |
| 	RAII_VAR(struct prometheus_metric *, test_gauge, NULL, prometheus_metric_free_wrapper);
 | |
| 	RAII_VAR(struct prometheus_metric *, test_gauge_child_one, NULL, prometheus_metric_free_wrapper);
 | |
| 	RAII_VAR(struct prometheus_metric *, test_gauge_child_two, NULL, prometheus_metric_free_wrapper);
 | |
| 	RAII_VAR(struct prometheus_metric *, bad_metric, NULL, prometheus_metric_free_wrapper);
 | |
| 	enum ast_test_result_state result;
 | |
| 
 | |
| 	switch (cmd) {
 | |
| 	case TEST_INIT:
 | |
| 		info->name = __func__;
 | |
| 		info->category = CATEGORY;
 | |
| 		info->summary = "Test registration of metrics";
 | |
| 		info->description =
 | |
| 			"This test covers the following registration scenarios:\n"
 | |
| 			"- Nominal registration of simple metrics\n"
 | |
| 			"- Registration of metrics with different allocation strategies\n"
 | |
| 			"- Nested metrics with label families\n"
 | |
| 			"- Off nominal registration with simple name collisions\n"
 | |
| 			"- Off nominal registration with label collisions";
 | |
| 		return AST_TEST_NOT_RUN;
 | |
| 	case TEST_EXECUTE:
 | |
| 		break;
 | |
| 	}
 | |
| 
 | |
| 	ast_test_status_update(test, "Testing nominal registration\n");
 | |
| 	ast_test_status_update(test, "-> Static metric\n");
 | |
| 	ast_test_validate_cleanup(test, prometheus_metric_register(&test_counter) == 0, result, metric_register_cleanup);
 | |
| 	ast_test_status_update(test, "-> Malloc'd metric\n");
 | |
| 	test_gauge = prometheus_gauge_create("test_gauge", "A test gauge");
 | |
| 	ast_test_validate(test, test_gauge != NULL);
 | |
| 	ast_test_validate_cleanup(test, prometheus_metric_register(test_gauge) == 0, result, metric_register_cleanup);
 | |
| 	ast_test_validate_cleanup(test, prometheus_metric_registered_count() == 2, result, metric_register_cleanup);
 | |
| 
 | |
| 	ast_test_status_update(test, "Testing nominal registration of child metrics\n");
 | |
| 	test_gauge_child_one = prometheus_gauge_create("test_gauge", "A test gauge");
 | |
| 	ast_test_validate_cleanup(test, test_gauge_child_one != NULL, result, metric_register_cleanup);
 | |
| 	PROMETHEUS_METRIC_SET_LABEL(test_gauge_child_one, 0, "key_one", "value_one");
 | |
| 	PROMETHEUS_METRIC_SET_LABEL(test_gauge_child_one, 1, "key_two", "value_one");
 | |
| 	test_gauge_child_two = prometheus_gauge_create("test_gauge", "A test gauge");
 | |
| 	ast_test_validate_cleanup(test, test_gauge_child_two != NULL, result, metric_register_cleanup);
 | |
| 	PROMETHEUS_METRIC_SET_LABEL(test_gauge_child_two, 0, "key_one", "value_two");
 | |
| 	PROMETHEUS_METRIC_SET_LABEL(test_gauge_child_two, 1, "key_two", "value_two");
 | |
| 	ast_test_validate_cleanup(test, prometheus_metric_register(test_gauge_child_one) == 0, result, metric_register_cleanup);
 | |
| 	ast_test_validate_cleanup(test, prometheus_metric_register(test_gauge_child_two) == 0, result, metric_register_cleanup);
 | |
| 	ast_test_validate_cleanup(test, prometheus_metric_registered_count() == 2, result, metric_register_cleanup);
 | |
| 	ast_test_validate_cleanup(test, test_gauge->children.first == test_gauge_child_one, result, metric_register_cleanup);
 | |
| 	ast_test_validate_cleanup(test, test_gauge->children.last == test_gauge_child_two, result, metric_register_cleanup);
 | |
| 
 | |
| 	ast_test_status_update(test, "Testing name collisions\n");
 | |
| 	bad_metric = prometheus_counter_create("test_counter", "A test counter");
 | |
| 	ast_test_validate_cleanup(test, bad_metric != NULL, result, metric_register_cleanup);
 | |
| 	ast_test_validate_cleanup(test, prometheus_metric_register(bad_metric) != 0, result, metric_register_cleanup);
 | |
| 	prometheus_metric_free(bad_metric);
 | |
| 	bad_metric = NULL;
 | |
| 
 | |
| 	ast_test_status_update(test, "Testing label collisions\n");
 | |
| 	bad_metric = prometheus_gauge_create("test_gauge", "A test gauge");
 | |
| 	ast_test_validate_cleanup(test, bad_metric != NULL, result, metric_register_cleanup);
 | |
| 	PROMETHEUS_METRIC_SET_LABEL(bad_metric, 0, "key_one", "value_one");
 | |
| 	PROMETHEUS_METRIC_SET_LABEL(bad_metric, 1, "key_two", "value_one");
 | |
| 	ast_test_validate_cleanup(test, prometheus_metric_register(bad_metric) != 0, result, metric_register_cleanup);
 | |
| 	prometheus_metric_free(bad_metric);
 | |
| 	bad_metric = NULL;
 | |
| 
 | |
| 	ast_test_status_update(test, "Testing removal of metrics\n");
 | |
| 	prometheus_metric_unregister(test_gauge_child_two);
 | |
| 	test_gauge_child_two = NULL;
 | |
| 
 | |
| 	ast_test_validate_cleanup(test, prometheus_metric_registered_count() == 2, result, metric_register_cleanup);
 | |
| 	prometheus_metric_unregister(test_gauge);
 | |
| 	test_gauge = NULL;
 | |
| 
 | |
| 	ast_test_validate_cleanup(test, prometheus_metric_registered_count() == 2, result, metric_register_cleanup);
 | |
| 	prometheus_metric_unregister(test_gauge_child_one);
 | |
| 	test_gauge_child_one = NULL;
 | |
| 
 | |
| 	ast_test_validate_cleanup(test, prometheus_metric_registered_count() == 1, result, metric_register_cleanup);
 | |
| 	prometheus_metric_unregister(&test_counter);
 | |
| 
 | |
| 	ast_test_validate_cleanup(test, prometheus_metric_registered_count() == 0, result, metric_register_cleanup);
 | |
| 
 | |
| 	return AST_TEST_PASS;
 | |
| 
 | |
| metric_register_cleanup:
 | |
| 	prometheus_metric_unregister(&test_counter);
 | |
| 	return result;
 | |
| }
 | |
| 
 | |
| AST_TEST_DEFINE(counter_to_string)
 | |
| {
 | |
| 	struct prometheus_metric test_counter = PROMETHEUS_METRIC_STATIC_INITIALIZATION(
 | |
| 		PROMETHEUS_METRIC_COUNTER,
 | |
| 		"test_counter",
 | |
| 		"A test counter",
 | |
| 		NULL);
 | |
| 	struct prometheus_metric test_counter_child_one = PROMETHEUS_METRIC_STATIC_INITIALIZATION(
 | |
| 		PROMETHEUS_METRIC_COUNTER,
 | |
| 		"test_counter",
 | |
| 		"A test counter",
 | |
| 		NULL);
 | |
| 	struct prometheus_metric test_counter_child_two = PROMETHEUS_METRIC_STATIC_INITIALIZATION(
 | |
| 		PROMETHEUS_METRIC_COUNTER,
 | |
| 		"test_counter",
 | |
| 		"A test counter",
 | |
| 		NULL);
 | |
| 	RAII_VAR(struct ast_str *, buffer, NULL, ast_free);
 | |
| 
 | |
| 	switch (cmd) {
 | |
| 	case TEST_INIT:
 | |
| 		info->name = __func__;
 | |
| 		info->category = CATEGORY;
 | |
| 		info->summary = "Test formatting of counters";
 | |
| 		info->description =
 | |
| 			"This test covers the formatting of printed counters";
 | |
| 		return AST_TEST_NOT_RUN;
 | |
| 	case TEST_EXECUTE:
 | |
| 		break;
 | |
| 	}
 | |
| 
 | |
| 	buffer = ast_str_create(128);
 | |
| 	if (!buffer) {
 | |
| 		return AST_TEST_FAIL;
 | |
| 	}
 | |
| 
 | |
| 	PROMETHEUS_METRIC_SET_LABEL(&test_counter_child_one, 0, "key_one", "value_one");
 | |
| 	PROMETHEUS_METRIC_SET_LABEL(&test_counter_child_one, 1, "key_two", "value_one");
 | |
| 	PROMETHEUS_METRIC_SET_LABEL(&test_counter_child_two, 0, "key_one", "value_two");
 | |
| 	PROMETHEUS_METRIC_SET_LABEL(&test_counter_child_two, 1, "key_two", "value_two");
 | |
| 	AST_LIST_INSERT_TAIL(&test_counter.children, &test_counter_child_one, entry);
 | |
| 	AST_LIST_INSERT_TAIL(&test_counter.children, &test_counter_child_two, entry);
 | |
| 	prometheus_metric_to_string(&test_counter, &buffer);
 | |
| 	ast_test_validate(test, strcmp(ast_str_buffer(buffer),
 | |
| 		"# HELP test_counter A test counter\n"
 | |
| 		"# TYPE test_counter counter\n"
 | |
| 		"test_counter 0\n"
 | |
| 		"test_counter{key_one=\"value_one\",key_two=\"value_one\"} 0\n"
 | |
| 		"test_counter{key_one=\"value_two\",key_two=\"value_two\"} 0\n") == 0);
 | |
| 
 | |
| 	return AST_TEST_PASS;
 | |
| }
 | |
| 
 | |
| AST_TEST_DEFINE(counter_create)
 | |
| {
 | |
| 	RAII_VAR(struct prometheus_metric *, metric, NULL, prometheus_metric_free_wrapper);
 | |
| 
 | |
| 	switch (cmd) {
 | |
| 	case TEST_INIT:
 | |
| 		info->name = __func__;
 | |
| 		info->category = CATEGORY;
 | |
| 		info->summary = "Test creation (and destruction) of malloc'd counters";
 | |
| 		info->description =
 | |
| 			"This test covers creating a counter metric and destroying\n"
 | |
| 			"it. The metric should be malloc'd.";
 | |
| 		return AST_TEST_NOT_RUN;
 | |
| 	case TEST_EXECUTE:
 | |
| 		break;
 | |
| 	}
 | |
| 
 | |
| 	metric = prometheus_counter_create("test_counter", "A test counter");
 | |
| 	ast_test_validate(test, metric != NULL);
 | |
| 	ast_test_validate(test, metric->type == PROMETHEUS_METRIC_COUNTER);
 | |
| 	ast_test_validate(test, metric->allocation_strategy = PROMETHEUS_METRIC_MALLOCD);
 | |
| 	ast_test_validate(test, !strcmp(metric->help, "A test counter"));
 | |
| 	ast_test_validate(test, !strcmp(metric->name, "test_counter"));
 | |
| 	ast_test_validate(test, !strcmp(metric->value, ""));
 | |
| 	ast_test_validate(test, metric->children.first == NULL);
 | |
| 	ast_test_validate(test, metric->children.last == NULL);
 | |
| 
 | |
| 	return AST_TEST_PASS;
 | |
| }
 | |
| 
 | |
| AST_TEST_DEFINE(gauge_to_string)
 | |
| {
 | |
| 	struct prometheus_metric test_gauge = PROMETHEUS_METRIC_STATIC_INITIALIZATION(
 | |
| 		PROMETHEUS_METRIC_GAUGE,
 | |
| 		"test_gauge",
 | |
| 		"A test gauge",
 | |
| 		NULL);
 | |
| 	struct prometheus_metric test_gauge_child_one = PROMETHEUS_METRIC_STATIC_INITIALIZATION(
 | |
| 		PROMETHEUS_METRIC_GAUGE,
 | |
| 		"test_gauge",
 | |
| 		"A test gauge",
 | |
| 		NULL);
 | |
| 	struct prometheus_metric test_gauge_child_two = PROMETHEUS_METRIC_STATIC_INITIALIZATION(
 | |
| 		PROMETHEUS_METRIC_GAUGE,
 | |
| 		"test_gauge",
 | |
| 		"A test gauge",
 | |
| 		NULL);
 | |
| 	RAII_VAR(struct ast_str *, buffer, NULL, ast_free);
 | |
| 
 | |
| 	switch (cmd) {
 | |
| 	case TEST_INIT:
 | |
| 		info->name = __func__;
 | |
| 		info->category = CATEGORY;
 | |
| 		info->summary = "Test formatting of gauges";
 | |
| 		info->description =
 | |
| 			"This test covers the formatting of printed gauges";
 | |
| 		return AST_TEST_NOT_RUN;
 | |
| 	case TEST_EXECUTE:
 | |
| 		break;
 | |
| 	}
 | |
| 
 | |
| 	buffer = ast_str_create(128);
 | |
| 	if (!buffer) {
 | |
| 		return AST_TEST_FAIL;
 | |
| 	}
 | |
| 
 | |
| 	PROMETHEUS_METRIC_SET_LABEL(&test_gauge_child_one, 0, "key_one", "value_one");
 | |
| 	PROMETHEUS_METRIC_SET_LABEL(&test_gauge_child_one, 1, "key_two", "value_one");
 | |
| 	PROMETHEUS_METRIC_SET_LABEL(&test_gauge_child_two, 0, "key_one", "value_two");
 | |
| 	PROMETHEUS_METRIC_SET_LABEL(&test_gauge_child_two, 1, "key_two", "value_two");
 | |
| 	AST_LIST_INSERT_TAIL(&test_gauge.children, &test_gauge_child_one, entry);
 | |
| 	AST_LIST_INSERT_TAIL(&test_gauge.children, &test_gauge_child_two, entry);
 | |
| 	prometheus_metric_to_string(&test_gauge, &buffer);
 | |
| 	ast_test_validate(test, strcmp(ast_str_buffer(buffer),
 | |
| 		"# HELP test_gauge A test gauge\n"
 | |
| 		"# TYPE test_gauge gauge\n"
 | |
| 		"test_gauge 0\n"
 | |
| 		"test_gauge{key_one=\"value_one\",key_two=\"value_one\"} 0\n"
 | |
| 		"test_gauge{key_one=\"value_two\",key_two=\"value_two\"} 0\n") == 0);
 | |
| 
 | |
| 	return AST_TEST_PASS;
 | |
| }
 | |
| 
 | |
| AST_TEST_DEFINE(gauge_create)
 | |
| {
 | |
| 	RAII_VAR(struct prometheus_metric *, metric, NULL, prometheus_metric_free_wrapper);
 | |
| 
 | |
| 	switch (cmd) {
 | |
| 	case TEST_INIT:
 | |
| 		info->name = __func__;
 | |
| 		info->category = CATEGORY;
 | |
| 		info->summary = "Test creation (and destruction) of malloc'd gauges";
 | |
| 		info->description =
 | |
| 			"This test covers creating a gauge metric and destroying\n"
 | |
| 			"it. The metric should be malloc'd.";
 | |
| 		return AST_TEST_NOT_RUN;
 | |
| 	case TEST_EXECUTE:
 | |
| 		break;
 | |
| 	}
 | |
| 
 | |
| 	metric = prometheus_gauge_create("test_gauge", "A test gauge");
 | |
| 	ast_test_validate(test, metric != NULL);
 | |
| 	ast_test_validate(test, metric->type == PROMETHEUS_METRIC_GAUGE);
 | |
| 	ast_test_validate(test, metric->allocation_strategy = PROMETHEUS_METRIC_MALLOCD);
 | |
| 	ast_test_validate(test, !strcmp(metric->help, "A test gauge"));
 | |
| 	ast_test_validate(test, !strcmp(metric->name, "test_gauge"));
 | |
| 	ast_test_validate(test, !strcmp(metric->value, ""));
 | |
| 	ast_test_validate(test, metric->children.first == NULL);
 | |
| 	ast_test_validate(test, metric->children.last == NULL);
 | |
| 
 | |
| 	return AST_TEST_PASS;
 | |
| }
 | |
| 
 | |
| AST_TEST_DEFINE(config_general_basic_auth)
 | |
| {
 | |
| 	RAII_VAR(CURL *, curl, NULL, curl_free_wrapper);
 | |
| 	struct prometheus_general_config *config;
 | |
| 	int res;
 | |
| 	long response_code;
 | |
| 
 | |
| 	switch (cmd) {
 | |
| 	case TEST_INIT:
 | |
| 		info->name = __func__;
 | |
| 		info->category = CATEGORY;
 | |
| 		info->summary = "Test basic auth handling";
 | |
| 		info->description =
 | |
| 			"This test covers authentication of requests";
 | |
| 		return AST_TEST_NOT_RUN;
 | |
| 	case TEST_EXECUTE:
 | |
| 		break;
 | |
| 	}
 | |
| 
 | |
| 	config = config_alloc();
 | |
| 	if (!config) {
 | |
| 		return AST_TEST_NOT_RUN;
 | |
| 	}
 | |
| 	ast_string_field_set(config, auth_username, "foo");
 | |
| 	ast_string_field_set(config, auth_password, "bar");
 | |
| 	/* Prometheus module owns the ref after this call */
 | |
| 	prometheus_general_config_set(config);
 | |
| 	ao2_ref(config, -1);
 | |
| 
 | |
| 	curl = get_curl_instance();
 | |
| 	if (!curl) {
 | |
| 		return AST_TEST_NOT_RUN;
 | |
| 	}
 | |
| 
 | |
| 	ast_test_status_update(test, "Testing without auth credentials\n");
 | |
| 	ast_test_status_update(test, " -> CURLing request...\n");
 | |
| 	res = curl_easy_perform(curl);
 | |
| 	if (res != CURLE_OK) {
 | |
| 		ast_test_status_update(test, "Failed to execute CURL: %d\n", res);
 | |
| 		return AST_TEST_FAIL;
 | |
| 	}
 | |
| 	curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code);
 | |
| 	ast_test_status_update(test, " -> CURL returned %ld\n", response_code);
 | |
| 	ast_test_validate(test, response_code == 401);
 | |
| 
 | |
| 	ast_test_status_update(test, "Testing with invalid auth credentials\n");
 | |
| 	ast_test_status_update(test, " -> CURLing request...\n");
 | |
| 	curl_easy_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
 | |
| 	curl_easy_setopt(curl, CURLOPT_USERPWD, "matt:jordan");
 | |
| 	res = curl_easy_perform(curl);
 | |
| 	if (res != CURLE_OK) {
 | |
| 		ast_test_status_update(test, "Failed to execute CURL: %d\n", res);
 | |
| 		return AST_TEST_FAIL;
 | |
| 	}
 | |
| 	curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code);
 | |
| 	ast_test_status_update(test, " -> CURL returned %ld\n", response_code);
 | |
| 	ast_test_validate(test, response_code == 401);
 | |
| 
 | |
| 	ast_test_status_update(test, "Testing with valid auth credentials\n");
 | |
| 	ast_test_status_update(test, " -> CURLing request...\n");
 | |
| 	curl_easy_setopt(curl, CURLOPT_USERPWD, "foo:bar");
 | |
| 	res = curl_easy_perform(curl);
 | |
| 	if (res != CURLE_OK) {
 | |
| 		ast_test_status_update(test, "Failed to execute CURL: %d\n", res);
 | |
| 		return AST_TEST_FAIL;
 | |
| 	}
 | |
| 	curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code);
 | |
| 	ast_test_status_update(test, " -> CURL returned %ld\n", response_code);
 | |
| 	ast_test_validate(test, response_code == 200);
 | |
| 
 | |
| 	return AST_TEST_PASS;
 | |
| }
 | |
| 
 | |
| AST_TEST_DEFINE(config_general_enabled)
 | |
| {
 | |
| 	RAII_VAR(CURL *, curl, NULL, curl_free_wrapper);
 | |
| 	struct prometheus_general_config *config;
 | |
| 	int res;
 | |
| 	long response_code;
 | |
| 
 | |
| 	switch (cmd) {
 | |
| 	case TEST_INIT:
 | |
| 		info->name = __func__;
 | |
| 		info->category = CATEGORY;
 | |
| 		info->summary = "Test handling of enable/disable";
 | |
| 		info->description =
 | |
| 			"When disabled, the module should return a 503.\n"
 | |
| 			"This test verifies that it actually occurs.";
 | |
| 		return AST_TEST_NOT_RUN;
 | |
| 	case TEST_EXECUTE:
 | |
| 		break;
 | |
| 	}
 | |
| 
 | |
| 	config = config_alloc();
 | |
| 	if (!config) {
 | |
| 		return AST_TEST_NOT_RUN;
 | |
| 	}
 | |
| 	config->enabled = 0;
 | |
| 	/* Prometheus module owns the ref after this call */
 | |
| 	prometheus_general_config_set(config);
 | |
| 	ao2_ref(config, -1);
 | |
| 
 | |
| 	curl = get_curl_instance();
 | |
| 	if (!curl) {
 | |
| 		return AST_TEST_NOT_RUN;
 | |
| 	}
 | |
| 
 | |
| 	ast_test_status_update(test, " -> CURLing request...\n");
 | |
| 	res = curl_easy_perform(curl);
 | |
| 	if (res != CURLE_OK) {
 | |
| 		ast_test_status_update(test, "Failed to execute CURL: %d\n", res);
 | |
| 		return AST_TEST_FAIL;
 | |
| 	}
 | |
| 	curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code);
 | |
| 	ast_test_status_update(test, " -> CURL returned %ld\n", response_code);
 | |
| 	ast_test_validate(test, response_code == 503);
 | |
| 
 | |
| 	return AST_TEST_PASS;
 | |
| }
 | |
| 
 | |
| AST_TEST_DEFINE(config_general_core_metrics)
 | |
| {
 | |
| 	RAII_VAR(CURL *, curl, NULL, curl_free_wrapper);
 | |
| 	RAII_VAR(struct ast_str *, buffer, NULL, ast_free);
 | |
| 	struct prometheus_general_config *config;
 | |
| 	int res;
 | |
| 
 | |
| 	switch (cmd) {
 | |
| 	case TEST_INIT:
 | |
| 		info->name = __func__;
 | |
| 		info->category = CATEGORY;
 | |
| 		info->summary = "Test producing core metrics";
 | |
| 		info->description =
 | |
| 			"This test covers the core metrics that are produced\n"
 | |
| 			"by the basic Prometheus module.";
 | |
| 		return AST_TEST_NOT_RUN;
 | |
| 	case TEST_EXECUTE:
 | |
| 		break;
 | |
| 	}
 | |
| 
 | |
| 	buffer = ast_str_create(128);
 | |
| 	if (!buffer) {
 | |
| 		return AST_TEST_NOT_RUN;
 | |
| 	}
 | |
| 
 | |
| 	config = config_alloc();
 | |
| 	if (!config) {
 | |
| 		return AST_TEST_NOT_RUN;
 | |
| 	}
 | |
| 	config->core_metrics_enabled = 1;
 | |
| 	/* Prometheus module owns the ref after this call */
 | |
| 	prometheus_general_config_set(config);
 | |
| 	ao2_ref(config, -1);
 | |
| 
 | |
| 	curl = get_curl_instance();
 | |
| 	if (!curl) {
 | |
| 		return AST_TEST_NOT_RUN;
 | |
| 	}
 | |
| 
 | |
| 	ast_test_status_update(test, " -> CURLing request...\n");
 | |
| 	curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_write_string_callback);
 | |
| 	curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buffer);
 | |
| 	res = curl_easy_perform(curl);
 | |
| 	if (res != CURLE_OK) {
 | |
| 		ast_test_status_update(test, "Failed to execute CURL: %d\n", res);
 | |
| 		return AST_TEST_FAIL;
 | |
| 	}
 | |
| 	ast_test_status_update(test, " -> Retrieved: %s\n", ast_str_buffer(buffer));
 | |
| 
 | |
| 	ast_test_status_update(test, " -> Checking for core properties\n");
 | |
| 	ast_test_validate(test, strstr(ast_str_buffer(buffer), "asterisk_core_properties") != NULL);
 | |
| 
 | |
| 	ast_test_status_update(test, " -> Checking for uptime\n");
 | |
| 	ast_test_validate(test, strstr(ast_str_buffer(buffer), "asterisk_core_uptime_seconds") != NULL);
 | |
| 
 | |
| 	ast_test_status_update(test, " -> Checking for last reload\n");
 | |
| 	ast_test_validate(test, strstr(ast_str_buffer(buffer), "asterisk_core_last_reload_seconds") != NULL);
 | |
| 
 | |
| 	ast_test_status_update(test, " -> Checking for scrape time\n");
 | |
| 	ast_test_validate(test, strstr(ast_str_buffer(buffer), "asterisk_core_scrape_time_ms") != NULL);
 | |
| 
 | |
| 	return AST_TEST_PASS;
 | |
| }
 | |
| 
 | |
| static int process_config(int reload)
 | |
| {
 | |
| 	struct ast_config *config;
 | |
| 	struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
 | |
| 	const char *bindaddr;
 | |
| 	const char *bindport;
 | |
| 	const char *prefix;
 | |
| 	const char *enabled;
 | |
| 
 | |
| 	config = ast_config_load("http.conf", config_flags);
 | |
| 	if (!config || config == CONFIG_STATUS_FILEINVALID) {
 | |
| 		ast_log(AST_LOG_NOTICE, "HTTP config file is invalid; declining load");
 | |
| 		return -1;
 | |
| 	} else if (config == CONFIG_STATUS_FILEUNCHANGED) {
 | |
| 		return 0;
 | |
| 	}
 | |
| 
 | |
| 	enabled = ast_config_option(config, "general", "enabled");
 | |
| 	if (!enabled || ast_false(enabled)) {
 | |
| 		ast_config_destroy(config);
 | |
| 		ast_log(AST_LOG_NOTICE, "HTTP server is disabled; declining load");
 | |
| 		return -1;
 | |
| 	}
 | |
| 
 | |
| 	/* Construct our Server URI */
 | |
| 	bindaddr = ast_config_option(config, "general", "bindaddr");
 | |
| 	if (!bindaddr) {
 | |
| 		ast_config_destroy(config);
 | |
| 		ast_log(AST_LOG_NOTICE, "HTTP config file fails to specify 'bindaddr'; declining load");
 | |
| 		return -1;
 | |
| 	}
 | |
| 
 | |
| 	bindport = ast_config_option(config, "general", "bindport");
 | |
| 	if (!bindport) {
 | |
| 		bindport = "8088";
 | |
| 	}
 | |
| 
 | |
| 	prefix = ast_config_option(config, "general", "prefix");
 | |
| 
 | |
| 	snprintf(server_uri, sizeof(server_uri), "http://%s:%s%s/test_metrics", bindaddr, bindport, S_OR(prefix, ""));
 | |
| 
 | |
| 	ast_config_destroy(config);
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int test_init_cb(struct ast_test_info *info, struct ast_test *test)
 | |
| {
 | |
| 	struct prometheus_general_config *new_module_config;
 | |
| 
 | |
| 	new_module_config = config_alloc();
 | |
| 	if (!new_module_config) {
 | |
| 		return -1;
 | |
| 	}
 | |
| 
 | |
| 	module_config = prometheus_general_config_get();
 | |
| 	prometheus_general_config_set(new_module_config);
 | |
| 
 | |
| 	/* Allow the module to own the ref */
 | |
| 	ao2_ref(new_module_config, -1);
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int test_cleanup_cb(struct ast_test_info *info, struct ast_test *test)
 | |
| {
 | |
| 	prometheus_general_config_set(module_config);
 | |
| 	ao2_cleanup(module_config);
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int reload_module(void)
 | |
| {
 | |
| 	return process_config(1);
 | |
| }
 | |
| 
 | |
| static int unload_module(void)
 | |
| {
 | |
| 	AST_TEST_UNREGISTER(metric_values);
 | |
| 	AST_TEST_UNREGISTER(metric_callback_register);
 | |
| 	AST_TEST_UNREGISTER(metric_register);
 | |
| 
 | |
| 	AST_TEST_UNREGISTER(counter_to_string);
 | |
| 	AST_TEST_UNREGISTER(counter_create);
 | |
| 	AST_TEST_UNREGISTER(gauge_to_string);
 | |
| 	AST_TEST_UNREGISTER(gauge_create);
 | |
| 
 | |
| 	AST_TEST_UNREGISTER(config_general_enabled);
 | |
| 	AST_TEST_UNREGISTER(config_general_basic_auth);
 | |
| 	AST_TEST_UNREGISTER(config_general_core_metrics);
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int load_module(void)
 | |
| {
 | |
| 	if (process_config(0)) {
 | |
| 		return AST_MODULE_LOAD_DECLINE;
 | |
| 	}
 | |
| 
 | |
| 	AST_TEST_REGISTER(metric_values);
 | |
| 	AST_TEST_REGISTER(metric_callback_register);
 | |
| 	AST_TEST_REGISTER(metric_register);
 | |
| 
 | |
| 	AST_TEST_REGISTER(counter_to_string);
 | |
| 	AST_TEST_REGISTER(counter_create);
 | |
| 	AST_TEST_REGISTER(gauge_to_string);
 | |
| 	AST_TEST_REGISTER(gauge_create);
 | |
| 
 | |
| 	AST_TEST_REGISTER(config_general_enabled);
 | |
| 	AST_TEST_REGISTER(config_general_basic_auth);
 | |
| 	AST_TEST_REGISTER(config_general_core_metrics);
 | |
| 
 | |
| 	ast_test_register_init(CATEGORY, &test_init_cb);
 | |
| 	ast_test_register_cleanup(CATEGORY, &test_cleanup_cb);
 | |
| 
 | |
| 	return AST_MODULE_LOAD_SUCCESS;
 | |
| }
 | |
| 
 | |
| AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Prometheus Core Unit Tests",
 | |
| 	.load = load_module,
 | |
| 	.reload = reload_module,
 | |
| 	.unload = unload_module,
 | |
| 	.requires = "res_prometheus",
 | |
| );
 |