28 #include <curl/curl.h>
36 #include "../res/prometheus/prometheus_internal.h"
38 #define CATEGORY "/res/prometheus/"
40 static char server_uri[512];
44 static void curl_free_wrapper(
void *ptr)
50 curl_easy_cleanup(ptr);
53 static void prometheus_metric_free_wrapper(
void *ptr)
77 static CURL *get_curl_instance(
void)
81 curl = curl_easy_init();
86 curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);
87 curl_easy_setopt(curl, CURLOPT_TIMEOUT, 180);
88 curl_easy_setopt(curl, CURLOPT_USERAGENT, AST_CURL_USER_AGENT);
89 curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
90 curl_easy_setopt(curl, CURLOPT_URL, server_uri);
95 static size_t curl_write_string_callback(
void *contents,
size_t size,
size_t nmemb,
void *userdata)
97 struct ast_str **buffer = userdata;
98 size_t realsize = size * nmemb;
105 memcpy(rawdata, contents, realsize);
106 rawdata[realsize] = 0;
115 strcpy(metric->
value,
"2");
120 RAII_VAR(CURL *, curl, NULL, curl_free_wrapper);
132 metric_values_get_counter_value_cb);
133 enum ast_test_result_state result = AST_TEST_PASS;
137 info->name = __func__;
139 info->summary =
"Test value generation/respecting in metrics";
141 "Metrics have two ways to provide values when the HTTP callback\n"
143 "1. By using the direct value that resides in the metric\n"
144 "2. By providing a callback function to specify the value\n"
145 "This test verifies that both function appropriately when the\n"
146 "HTTP callback is called.";
147 return AST_TEST_NOT_RUN;
154 return AST_TEST_FAIL;
157 curl = get_curl_instance();
159 return AST_TEST_FAIL;
164 strcpy(test_counter_one.
value,
"1");
166 ast_test_status_update(
test,
" -> CURLing request...\n");
167 curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_write_string_callback);
168 curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buffer);
169 res = curl_easy_perform(curl);
170 if (res != CURLE_OK) {
171 ast_test_status_update(
test,
"Failed to execute CURL: %d\n", res);
172 result = AST_TEST_FAIL;
173 goto metric_values_cleanup;
178 "# HELP test_counter_one A test counter\n"
179 "# TYPE test_counter_one counter\n"
180 "test_counter_one 1\n"
181 "# HELP test_counter_two A test counter\n"
182 "# TYPE test_counter_two counter\n"
183 "test_counter_two 2\n") != NULL, result, metric_values_cleanup);
185 metric_values_cleanup:
192 static void prometheus_metric_callback(
struct ast_str **output)
205 RAII_VAR(CURL *, curl, NULL, curl_free_wrapper);
209 .
name =
"test_callback",
210 .callback_fn = &prometheus_metric_callback,
215 info->name = __func__;
217 info->summary =
"Test registration of callbacks";
219 "This test covers callback registration. It registers\n"
220 "a callback that is invoked when an HTTP request is made,\n"
221 "and it verifies that during said callback the output to\n"
222 "the response string is correctly appended to. It also verifies\n"
223 "that unregistered callbacks are not invoked.";
224 return AST_TEST_NOT_RUN;
231 return AST_TEST_FAIL;
236 curl = get_curl_instance();
238 return AST_TEST_NOT_RUN;
241 ast_test_status_update(
test,
" -> CURLing request...\n");
242 curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_write_string_callback);
243 curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buffer);
244 res = curl_easy_perform(curl);
245 if (res != CURLE_OK) {
246 ast_test_status_update(
test,
"Failed to execute CURL: %d\n", res);
247 return AST_TEST_FAIL;
252 "# HELP test_counter A test counter\n"
253 "# TYPE test_counter counter\n"
254 "test_counter 0\n") != NULL);
258 return AST_TEST_PASS;
272 enum ast_test_result_state result;
276 info->name = __func__;
278 info->summary =
"Test registration of metrics";
280 "This test covers the following registration scenarios:\n"
281 "- Nominal registration of simple metrics\n"
282 "- Registration of metrics with different allocation strategies\n"
283 "- Nested metrics with label families\n"
284 "- Off nominal registration with simple name collisions\n"
285 "- Off nominal registration with label collisions";
286 return AST_TEST_NOT_RUN;
291 ast_test_status_update(
test,
"Testing nominal registration\n");
292 ast_test_status_update(
test,
"-> Static metric\n");
294 ast_test_status_update(
test,
"-> Malloc'd metric\n");
296 ast_test_validate(
test, test_gauge != NULL);
300 ast_test_status_update(
test,
"Testing nominal registration of child metrics\n");
302 ast_test_validate_cleanup(
test, test_gauge_child_one != NULL, result, metric_register_cleanup);
306 ast_test_validate_cleanup(
test, test_gauge_child_two != NULL, result, metric_register_cleanup);
312 ast_test_validate_cleanup(
test, test_gauge->children.first == test_gauge_child_one, result, metric_register_cleanup);
313 ast_test_validate_cleanup(
test, test_gauge->children.last == test_gauge_child_two, result, metric_register_cleanup);
315 ast_test_status_update(
test,
"Testing name collisions\n");
317 ast_test_validate_cleanup(
test, bad_metric != NULL, result, metric_register_cleanup);
322 ast_test_status_update(
test,
"Testing label collisions\n");
324 ast_test_validate_cleanup(
test, bad_metric != NULL, result, metric_register_cleanup);
331 ast_test_status_update(
test,
"Testing removal of metrics\n");
333 test_gauge_child_two = NULL;
341 test_gauge_child_one = NULL;
348 return AST_TEST_PASS;
350 metric_register_cleanup:
376 info->name = __func__;
378 info->summary =
"Test formatting of counters";
380 "This test covers the formatting of printed counters";
381 return AST_TEST_NOT_RUN;
388 return AST_TEST_FAIL;
399 "# HELP test_counter A test counter\n"
400 "# TYPE test_counter counter\n"
402 "test_counter{key_one=\"value_one\",key_two=\"value_one\"} 0\n"
403 "test_counter{key_one=\"value_two\",key_two=\"value_two\"} 0\n") == 0);
405 return AST_TEST_PASS;
414 info->name = __func__;
416 info->summary =
"Test creation (and destruction) of malloc'd counters";
418 "This test covers creating a counter metric and destroying\n"
419 "it. The metric should be malloc'd.";
420 return AST_TEST_NOT_RUN;
426 ast_test_validate(
test, metric != NULL);
429 ast_test_validate(
test, !strcmp(metric->help,
"A test counter"));
430 ast_test_validate(
test, !strcmp(metric->name,
"test_counter"));
431 ast_test_validate(
test, !strcmp(metric->value,
""));
432 ast_test_validate(
test, metric->children.first == NULL);
433 ast_test_validate(
test, metric->children.last == NULL);
435 return AST_TEST_PASS;
459 info->name = __func__;
461 info->summary =
"Test formatting of gauges";
463 "This test covers the formatting of printed gauges";
464 return AST_TEST_NOT_RUN;
471 return AST_TEST_FAIL;
482 "# HELP test_gauge A test gauge\n"
483 "# TYPE test_gauge gauge\n"
485 "test_gauge{key_one=\"value_one\",key_two=\"value_one\"} 0\n"
486 "test_gauge{key_one=\"value_two\",key_two=\"value_two\"} 0\n") == 0);
488 return AST_TEST_PASS;
497 info->name = __func__;
499 info->summary =
"Test creation (and destruction) of malloc'd gauges";
501 "This test covers creating a gauge metric and destroying\n"
502 "it. The metric should be malloc'd.";
503 return AST_TEST_NOT_RUN;
509 ast_test_validate(
test, metric != NULL);
512 ast_test_validate(
test, !strcmp(metric->help,
"A test gauge"));
513 ast_test_validate(
test, !strcmp(metric->name,
"test_gauge"));
514 ast_test_validate(
test, !strcmp(metric->value,
""));
515 ast_test_validate(
test, metric->children.first == NULL);
516 ast_test_validate(
test, metric->children.last == NULL);
518 return AST_TEST_PASS;
523 RAII_VAR(CURL *, curl, NULL, curl_free_wrapper);
530 info->name = __func__;
532 info->summary =
"Test basic auth handling";
534 "This test covers authentication of requests";
535 return AST_TEST_NOT_RUN;
540 config = config_alloc();
542 return AST_TEST_NOT_RUN;
550 curl = get_curl_instance();
552 return AST_TEST_NOT_RUN;
555 ast_test_status_update(
test,
"Testing without auth credentials\n");
556 ast_test_status_update(
test,
" -> CURLing request...\n");
557 res = curl_easy_perform(curl);
558 if (res != CURLE_OK) {
559 ast_test_status_update(
test,
"Failed to execute CURL: %d\n", res);
560 return AST_TEST_FAIL;
562 curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code);
563 ast_test_status_update(
test,
" -> CURL returned %ld\n", response_code);
564 ast_test_validate(
test, response_code == 401);
566 ast_test_status_update(
test,
"Testing with invalid auth credentials\n");
567 ast_test_status_update(
test,
" -> CURLing request...\n");
568 curl_easy_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
569 curl_easy_setopt(curl, CURLOPT_USERPWD,
"matt:jordan");
570 res = curl_easy_perform(curl);
571 if (res != CURLE_OK) {
572 ast_test_status_update(
test,
"Failed to execute CURL: %d\n", res);
573 return AST_TEST_FAIL;
575 curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code);
576 ast_test_status_update(
test,
" -> CURL returned %ld\n", response_code);
577 ast_test_validate(
test, response_code == 401);
579 ast_test_status_update(
test,
"Testing with valid auth credentials\n");
580 ast_test_status_update(
test,
" -> CURLing request...\n");
581 curl_easy_setopt(curl, CURLOPT_USERPWD,
"foo:bar");
582 res = curl_easy_perform(curl);
583 if (res != CURLE_OK) {
584 ast_test_status_update(
test,
"Failed to execute CURL: %d\n", res);
585 return AST_TEST_FAIL;
587 curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code);
588 ast_test_status_update(
test,
" -> CURL returned %ld\n", response_code);
589 ast_test_validate(
test, response_code == 200);
591 return AST_TEST_PASS;
596 RAII_VAR(CURL *, curl, NULL, curl_free_wrapper);
603 info->name = __func__;
605 info->summary =
"Test handling of enable/disable";
607 "When disabled, the module should return a 503.\n"
608 "This test verifies that it actually occurs.";
609 return AST_TEST_NOT_RUN;
614 config = config_alloc();
616 return AST_TEST_NOT_RUN;
623 curl = get_curl_instance();
625 return AST_TEST_NOT_RUN;
628 ast_test_status_update(
test,
" -> CURLing request...\n");
629 res = curl_easy_perform(curl);
630 if (res != CURLE_OK) {
631 ast_test_status_update(
test,
"Failed to execute CURL: %d\n", res);
632 return AST_TEST_FAIL;
634 curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code);
635 ast_test_status_update(
test,
" -> CURL returned %ld\n", response_code);
636 ast_test_validate(
test, response_code == 503);
638 return AST_TEST_PASS;
643 RAII_VAR(CURL *, curl, NULL, curl_free_wrapper);
650 info->name = __func__;
652 info->summary =
"Test producing core metrics";
654 "This test covers the core metrics that are produced\n"
655 "by the basic Prometheus module.";
656 return AST_TEST_NOT_RUN;
663 return AST_TEST_NOT_RUN;
666 config = config_alloc();
668 return AST_TEST_NOT_RUN;
675 curl = get_curl_instance();
677 return AST_TEST_NOT_RUN;
680 ast_test_status_update(
test,
" -> CURLing request...\n");
681 curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_write_string_callback);
682 curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buffer);
683 res = curl_easy_perform(curl);
684 if (res != CURLE_OK) {
685 ast_test_status_update(
test,
"Failed to execute CURL: %d\n", res);
686 return AST_TEST_FAIL;
690 ast_test_status_update(
test,
" -> Checking for core properties\n");
691 ast_test_validate(
test, strstr(
ast_str_buffer(buffer),
"asterisk_core_properties") != NULL);
693 ast_test_status_update(
test,
" -> Checking for uptime\n");
694 ast_test_validate(
test, strstr(
ast_str_buffer(buffer),
"asterisk_core_uptime_seconds") != NULL);
696 ast_test_status_update(
test,
" -> Checking for last reload\n");
697 ast_test_validate(
test, strstr(
ast_str_buffer(buffer),
"asterisk_core_last_reload_seconds") != NULL);
699 ast_test_status_update(
test,
" -> Checking for scrape time\n");
700 ast_test_validate(
test, strstr(
ast_str_buffer(buffer),
"asterisk_core_scrape_time_ms") != NULL);
702 return AST_TEST_PASS;
705 static void safe_bridge_destroy(
struct ast_bridge *bridge)
713 static int match_count(
const char *str,
const char *needle)
717 while ((str = strstr(str, needle))) {
718 str += strlen(needle);
734 info->name = __func__;
736 info->summary =
"Test producing bridge metrics";
738 "This test covers checking the metrics produced by the\n"
739 "bridge support of the basic Promtheus module.";
740 return AST_TEST_NOT_RUN;
746 ast_test_validate(
test, bridge1 != NULL);
750 "test_res_prometheus",
"test_bridge_invisible", NULL);
753 ast_test_validate(
test, bridge3 != NULL);
757 return AST_TEST_FAIL;
762 ast_test_validate(
test, strstr(
ast_str_buffer(response),
"asterisk_bridges_channels_count{") != NULL);
763 ast_test_validate(
test, match_count(
ast_str_buffer(response),
"# HELP asterisk_bridges_channels_count Number of channels in the bridge.") == 1);
765 return AST_TEST_PASS;
772 const char *bindaddr;
773 const char *bindport;
778 if (!config || config == CONFIG_STATUS_FILEINVALID) {
779 ast_log(AST_LOG_NOTICE,
"HTTP config file is invalid; declining load");
781 }
else if (config == CONFIG_STATUS_FILEUNCHANGED) {
788 ast_log(AST_LOG_NOTICE,
"HTTP server is disabled; declining load");
796 ast_log(AST_LOG_NOTICE,
"HTTP config file fails to specify 'bindaddr'; declining load");
807 snprintf(server_uri,
sizeof(server_uri),
"http://%s:%s%s/test_metrics", bindaddr, bindport,
S_OR(prefix,
""));
818 new_module_config = config_alloc();
819 if (!new_module_config) {
827 ao2_ref(new_module_config, -1);
832 static int test_cleanup_cb(
struct ast_test_info *info,
struct ast_test *test)
835 ao2_cleanup(module_config);
840 static int reload_module(
void)
845 static int unload_module(
void)
847 AST_TEST_UNREGISTER(metric_values);
848 AST_TEST_UNREGISTER(metric_callback_register);
849 AST_TEST_UNREGISTER(metric_register);
851 AST_TEST_UNREGISTER(counter_to_string);
852 AST_TEST_UNREGISTER(counter_create);
853 AST_TEST_UNREGISTER(gauge_to_string);
854 AST_TEST_UNREGISTER(gauge_create);
856 AST_TEST_UNREGISTER(config_general_enabled);
857 AST_TEST_UNREGISTER(config_general_basic_auth);
858 AST_TEST_UNREGISTER(config_general_core_metrics);
860 AST_TEST_UNREGISTER(bridge_to_string);
865 static int load_module(
void)
871 AST_TEST_REGISTER(metric_values);
872 AST_TEST_REGISTER(metric_callback_register);
873 AST_TEST_REGISTER(metric_register);
875 AST_TEST_REGISTER(counter_to_string);
876 AST_TEST_REGISTER(counter_create);
877 AST_TEST_REGISTER(gauge_to_string);
878 AST_TEST_REGISTER(gauge_create);
880 AST_TEST_REGISTER(config_general_enabled);
881 AST_TEST_REGISTER(config_general_basic_auth);
882 AST_TEST_REGISTER(config_general_core_metrics);
884 AST_TEST_REGISTER(bridge_to_string);
886 ast_test_register_init(
CATEGORY, &test_init_cb);
887 ast_test_register_cleanup(
CATEGORY, &test_cleanup_cb);
892 AST_MODULE_INFO(
ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT,
"Prometheus Core Unit Tests",
894 .reload = reload_module,
895 .unload = unload_module,
896 .requires =
"res_prometheus",
Contains all the initialization information required to store a new test definition.
unsigned int core_metrics_enabled
Whether or not core metrics are enabled.
Asterisk main include file. File version handling, generic pbx functions.
An actual, honest to god, metric.
int prometheus_metric_register(struct prometheus_metric *metric)
Prometheus general configuration.
int prometheus_callback_register(struct prometheus_callback *callback)
char * ast_str_buffer(const struct ast_str *buf)
Returns the string buffer within the ast_str buf.
A metric whose value always goes up.
static int process_config(int reload)
Load (or reload) configuration.
const char * name
The name of our callback (always useful for debugging)
int ast_bridge_destroy(struct ast_bridge *bridge, int cause)
Destroy a bridge.
int ast_str_append(struct ast_str **buf, ssize_t max_len, const char *fmt,...)
Append to a thread local dynamic string.
int prometheus_metric_unregister(struct prometheus_metric *metric)
Remove a registered metric.
void prometheus_metric_free(struct prometheus_metric *metric)
Destroy a metric and all its children.
#define PROMETHEUS_METRIC_STATIC_INITIALIZATION(mtype, n, h, cb)
Convenience macro for initializing a metric on the stack.
void prometheus_general_config_set(struct prometheus_general_config *config)
Set the configuration for the module.
void prometheus_metric_to_string(struct prometheus_metric *metric, struct ast_str **output)
Convert a metric (and its children) into Prometheus compatible text.
Configuration File Parser.
#define ast_config_load(filename, flags)
Load a config file.
#define PROMETHEUS_METRIC_SET_LABEL(metric, label, n, v)
Convenience macro for setting a label / value in a metric.
#define ao2_ref(o, delta)
Reference/unreference an object and return the old refcount.
struct prometheus_metric::@274 children
A list of children metrics.
const ast_string_field auth_password
Auth password for Basic Auth.
#define ast_malloc(len)
A wrapper for malloc()
The configuration settings for this module.
struct ast_bridge * ast_bridge_base_new(uint32_t capabilities, unsigned int flags, const char *creator, const char *name, const char *id)
Create a new base class bridge.
struct prometheus_metric * prometheus_gauge_create(const char *name, const char *help)
Create a malloc'd gauge metric.
Structure that contains information about a bridge.
Asterisk Prometheus Metrics.
#define AST_LIST_INSERT_TAIL(head, elm, field)
Appends a list entry to the tail of a list.
Support for dynamic strings.
char value[PROMETHEUS_MAX_VALUE_LENGTH]
The current value.
void * prometheus_general_config_alloc(void)
Allocate a new configuration object.
Module has failed to load, may be in an inconsistent state.
void prometheus_callback_unregister(struct prometheus_callback *callback)
Remove a registered callback.
Basic bridge subclass API.
Structure used to handle boolean flags.
const ast_string_field uri
The HTTP URI we register ourselves to.
struct ast_str * prometheus_scrape_to_string(void)
Get the raw output of what a scrape would produce.
struct ast_bridge * ast_bridge_basic_new(void)
Create a new basic class bridge.
struct prometheus_metric * prometheus_counter_create(const char *name, const char *help)
Create a malloc'd counter metric.
static int enabled
Whether or not we are storing history.
#define S_OR(a, b)
returns the equivalent of logic or for strings: first one if not empty, otherwise second one...
int attribute_pure ast_false(const char *val)
Make sure something is false. Determine if a string containing a boolean value is "false"...
int prometheus_metric_registered_count(void)
const char * ast_config_option(struct ast_config *cfg, const char *cat, const char *var)
Retrieve a configuration variable within the configuration set.
#define AST_TEST_DEFINE(hdr)
A metric whose value can bounce around like a jackrabbit.
The metric was allocated on the heap.
void ast_config_destroy(struct ast_config *cfg)
Destroys a config.
struct prometheus_general_config * prometheus_general_config_get(void)
Retrieve the current configuration of the module.
#define ASTERISK_GPL_KEY
The text the key() function should return.
Asterisk module definitions.
#define RAII_VAR(vartype, varname, initval, dtor)
Declare a variable that will call a destructor function when it goes out of scope.
Defines a callback that will be invoked when the HTTP route is called.
#define ast_str_create(init_len)
Create a malloc'ed dynamic length string.
const ast_string_field auth_username
Auth username for Basic Auth.
unsigned int enabled
Whether or not the module is enabled.
#define ast_string_field_set(x, field, data)
Set a field to a simple string value.