Asterisk - The Open Source Telephony Project  21.4.1
test_res_prometheus.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2019 Sangoma, Inc.
5  *
6  * Matt Jordan <mjordan@digium.com>
7  *
8  * See http://www.asterisk.org for more information about
9  * the Asterisk project. Please do not directly contact
10  * any of the maintainers of this project for assistance;
11  * the project provides a web site, mailing lists and IRC
12  * channels for your use.
13  *
14  * This program is free software, distributed under the terms of
15  * the GNU General Public License Version 2. See the LICENSE file
16  * at the top of the source tree.
17  */
18 
19 /*** MODULEINFO
20  <depend>TEST_FRAMEWORK</depend>
21  <depend>res_prometheus</depend>
22  <depend>curl</depend>
23  <support_level>extended</support_level>
24  ***/
25 
26 #include "asterisk.h"
27 
28 #include <curl/curl.h>
29 
30 #include "asterisk/test.h"
31 #include "asterisk/module.h"
32 #include "asterisk/bridge.h"
33 #include "asterisk/bridge_basic.h"
34 #include "asterisk/config.h"
36 #include "../res/prometheus/prometheus_internal.h"
37 
38 #define CATEGORY "/res/prometheus/"
39 
40 static char server_uri[512];
41 
43 
44 static void curl_free_wrapper(void *ptr)
45 {
46  if (!ptr) {
47  return;
48  }
49 
50  curl_easy_cleanup(ptr);
51 }
52 
53 static void prometheus_metric_free_wrapper(void *ptr)
54 {
57  }
58 }
59 
60 static struct prometheus_general_config *config_alloc(void)
61 {
62  struct prometheus_general_config *config;
63 
65  if (!config) {
66  return NULL;
67  }
68 
69  /* Set what we need on the config for most tests */
70  ast_string_field_set(config, uri, "test_metrics");
71  config->enabled = 1;
72  config->core_metrics_enabled = 0;
73 
74  return config;
75 }
76 
77 static CURL *get_curl_instance(void)
78 {
79  CURL *curl;
80 
81  curl = curl_easy_init();
82  if (!curl) {
83  return NULL;
84  }
85 
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);
91 
92  return curl;
93 }
94 
95 static size_t curl_write_string_callback(void *contents, size_t size, size_t nmemb, void *userdata)
96 {
97  struct ast_str **buffer = userdata;
98  size_t realsize = size * nmemb;
99  char *rawdata;
100 
101  rawdata = ast_malloc(realsize + 1);
102  if (!rawdata) {
103  return 0;
104  }
105  memcpy(rawdata, contents, realsize);
106  rawdata[realsize] = 0;
107  ast_str_append(buffer, 0, "%s", rawdata);
108  ast_free(rawdata);
109 
110  return realsize;
111 }
112 
113 static void metric_values_get_counter_value_cb(struct prometheus_metric *metric)
114 {
115  strcpy(metric->value, "2");
116 }
117 
118 AST_TEST_DEFINE(metric_values)
119 {
120  RAII_VAR(CURL *, curl, NULL, curl_free_wrapper);
121  RAII_VAR(struct ast_str *, buffer, NULL, ast_free);
122  int res;
125  "test_counter_one",
126  "A test counter",
127  NULL);
130  "test_counter_two",
131  "A test counter",
132  metric_values_get_counter_value_cb);
133  enum ast_test_result_state result = AST_TEST_PASS;
134 
135  switch (cmd) {
136  case TEST_INIT:
137  info->name = __func__;
138  info->category = CATEGORY;
139  info->summary = "Test value generation/respecting in metrics";
140  info->description =
141  "Metrics have two ways to provide values when the HTTP callback\n"
142  "is invoked:\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;
148  case TEST_EXECUTE:
149  break;
150  }
151 
152  buffer = ast_str_create(128);
153  if (!buffer) {
154  return AST_TEST_FAIL;
155  }
156 
157  curl = get_curl_instance();
158  if (!curl) {
159  return AST_TEST_FAIL;
160  }
161 
162  ast_test_validate_cleanup(test, prometheus_metric_register(&test_counter_one) == 0, result, metric_values_cleanup);
163  ast_test_validate_cleanup(test, prometheus_metric_register(&test_counter_two) == 0, result, metric_values_cleanup);
164  strcpy(test_counter_one.value, "1");
165 
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;
174  }
175 
176  ast_test_status_update(test, " -> Retrieved: %s\n", ast_str_buffer(buffer));
177  ast_test_validate_cleanup(test, strstr(ast_str_buffer(buffer),
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);
184 
185 metric_values_cleanup:
186  prometheus_metric_unregister(&test_counter_one);
187  prometheus_metric_unregister(&test_counter_two);
188 
189  return result;
190 }
191 
192 static void prometheus_metric_callback(struct ast_str **output)
193 {
196  "test_counter",
197  "A test counter",
198  NULL);
199 
200  prometheus_metric_to_string(&test_counter, output);
201 }
202 
203 AST_TEST_DEFINE(metric_callback_register)
204 {
205  RAII_VAR(CURL *, curl, NULL, curl_free_wrapper);
206  RAII_VAR(struct ast_str *, buffer, NULL, ast_free);
207  int res;
208  struct prometheus_callback callback = {
209  .name = "test_callback",
210  .callback_fn = &prometheus_metric_callback,
211  };
212 
213  switch (cmd) {
214  case TEST_INIT:
215  info->name = __func__;
216  info->category = CATEGORY;
217  info->summary = "Test registration of callbacks";
218  info->description =
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;
225  case TEST_EXECUTE:
226  break;
227  }
228 
229  buffer = ast_str_create(128);
230  if (!buffer) {
231  return AST_TEST_FAIL;
232  }
233 
234  ast_test_validate(test, prometheus_callback_register(&callback) == 0);
235 
236  curl = get_curl_instance();
237  if (!curl) {
238  return AST_TEST_NOT_RUN;
239  }
240 
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;
248  }
249 
250  ast_test_status_update(test, " -> Retrieved: %s\n", ast_str_buffer(buffer));
251  ast_test_validate(test, strstr(ast_str_buffer(buffer),
252  "# HELP test_counter A test counter\n"
253  "# TYPE test_counter counter\n"
254  "test_counter 0\n") != NULL);
255 
257 
258  return AST_TEST_PASS;
259 }
260 
261 AST_TEST_DEFINE(metric_register)
262 {
265  "test_counter",
266  "A test counter",
267  NULL);
268  RAII_VAR(struct prometheus_metric *, test_gauge, NULL, prometheus_metric_free_wrapper);
269  RAII_VAR(struct prometheus_metric *, test_gauge_child_one, NULL, prometheus_metric_free_wrapper);
270  RAII_VAR(struct prometheus_metric *, test_gauge_child_two, NULL, prometheus_metric_free_wrapper);
271  RAII_VAR(struct prometheus_metric *, bad_metric, NULL, prometheus_metric_free_wrapper);
272  enum ast_test_result_state result;
273 
274  switch (cmd) {
275  case TEST_INIT:
276  info->name = __func__;
277  info->category = CATEGORY;
278  info->summary = "Test registration of metrics";
279  info->description =
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;
287  case TEST_EXECUTE:
288  break;
289  }
290 
291  ast_test_status_update(test, "Testing nominal registration\n");
292  ast_test_status_update(test, "-> Static metric\n");
293  ast_test_validate_cleanup(test, prometheus_metric_register(&test_counter) == 0, result, metric_register_cleanup);
294  ast_test_status_update(test, "-> Malloc'd metric\n");
295  test_gauge = prometheus_gauge_create("test_gauge", "A test gauge");
296  ast_test_validate(test, test_gauge != NULL);
297  ast_test_validate_cleanup(test, prometheus_metric_register(test_gauge) == 0, result, metric_register_cleanup);
298  ast_test_validate_cleanup(test, prometheus_metric_registered_count() == 2, result, metric_register_cleanup);
299 
300  ast_test_status_update(test, "Testing nominal registration of child metrics\n");
301  test_gauge_child_one = prometheus_gauge_create("test_gauge", "A test gauge");
302  ast_test_validate_cleanup(test, test_gauge_child_one != NULL, result, metric_register_cleanup);
303  PROMETHEUS_METRIC_SET_LABEL(test_gauge_child_one, 0, "key_one", "value_one");
304  PROMETHEUS_METRIC_SET_LABEL(test_gauge_child_one, 1, "key_two", "value_one");
305  test_gauge_child_two = prometheus_gauge_create("test_gauge", "A test gauge");
306  ast_test_validate_cleanup(test, test_gauge_child_two != NULL, result, metric_register_cleanup);
307  PROMETHEUS_METRIC_SET_LABEL(test_gauge_child_two, 0, "key_one", "value_two");
308  PROMETHEUS_METRIC_SET_LABEL(test_gauge_child_two, 1, "key_two", "value_two");
309  ast_test_validate_cleanup(test, prometheus_metric_register(test_gauge_child_one) == 0, result, metric_register_cleanup);
310  ast_test_validate_cleanup(test, prometheus_metric_register(test_gauge_child_two) == 0, result, metric_register_cleanup);
311  ast_test_validate_cleanup(test, prometheus_metric_registered_count() == 2, 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);
314 
315  ast_test_status_update(test, "Testing name collisions\n");
316  bad_metric = prometheus_counter_create("test_counter", "A test counter");
317  ast_test_validate_cleanup(test, bad_metric != NULL, result, metric_register_cleanup);
318  ast_test_validate_cleanup(test, prometheus_metric_register(bad_metric) != 0, result, metric_register_cleanup);
319  prometheus_metric_free(bad_metric);
320  bad_metric = NULL;
321 
322  ast_test_status_update(test, "Testing label collisions\n");
323  bad_metric = prometheus_gauge_create("test_gauge", "A test gauge");
324  ast_test_validate_cleanup(test, bad_metric != NULL, result, metric_register_cleanup);
325  PROMETHEUS_METRIC_SET_LABEL(bad_metric, 0, "key_one", "value_one");
326  PROMETHEUS_METRIC_SET_LABEL(bad_metric, 1, "key_two", "value_one");
327  ast_test_validate_cleanup(test, prometheus_metric_register(bad_metric) != 0, result, metric_register_cleanup);
328  prometheus_metric_free(bad_metric);
329  bad_metric = NULL;
330 
331  ast_test_status_update(test, "Testing removal of metrics\n");
332  prometheus_metric_unregister(test_gauge_child_two);
333  test_gauge_child_two = NULL;
334 
335  ast_test_validate_cleanup(test, prometheus_metric_registered_count() == 2, result, metric_register_cleanup);
336  prometheus_metric_unregister(test_gauge);
337  test_gauge = NULL;
338 
339  ast_test_validate_cleanup(test, prometheus_metric_registered_count() == 2, result, metric_register_cleanup);
340  prometheus_metric_unregister(test_gauge_child_one);
341  test_gauge_child_one = NULL;
342 
343  ast_test_validate_cleanup(test, prometheus_metric_registered_count() == 1, result, metric_register_cleanup);
344  prometheus_metric_unregister(&test_counter);
345 
346  ast_test_validate_cleanup(test, prometheus_metric_registered_count() == 0, result, metric_register_cleanup);
347 
348  return AST_TEST_PASS;
349 
350 metric_register_cleanup:
351  prometheus_metric_unregister(&test_counter);
352  return result;
353 }
354 
355 AST_TEST_DEFINE(counter_to_string)
356 {
359  "test_counter",
360  "A test counter",
361  NULL);
362  struct prometheus_metric test_counter_child_one = PROMETHEUS_METRIC_STATIC_INITIALIZATION(
364  "test_counter",
365  "A test counter",
366  NULL);
367  struct prometheus_metric test_counter_child_two = PROMETHEUS_METRIC_STATIC_INITIALIZATION(
369  "test_counter",
370  "A test counter",
371  NULL);
372  RAII_VAR(struct ast_str *, buffer, NULL, ast_free);
373 
374  switch (cmd) {
375  case TEST_INIT:
376  info->name = __func__;
377  info->category = CATEGORY;
378  info->summary = "Test formatting of counters";
379  info->description =
380  "This test covers the formatting of printed counters";
381  return AST_TEST_NOT_RUN;
382  case TEST_EXECUTE:
383  break;
384  }
385 
386  buffer = ast_str_create(128);
387  if (!buffer) {
388  return AST_TEST_FAIL;
389  }
390 
391  PROMETHEUS_METRIC_SET_LABEL(&test_counter_child_one, 0, "key_one", "value_one");
392  PROMETHEUS_METRIC_SET_LABEL(&test_counter_child_one, 1, "key_two", "value_one");
393  PROMETHEUS_METRIC_SET_LABEL(&test_counter_child_two, 0, "key_one", "value_two");
394  PROMETHEUS_METRIC_SET_LABEL(&test_counter_child_two, 1, "key_two", "value_two");
395  AST_LIST_INSERT_TAIL(&test_counter.children, &test_counter_child_one, entry);
396  AST_LIST_INSERT_TAIL(&test_counter.children, &test_counter_child_two, entry);
397  prometheus_metric_to_string(&test_counter, &buffer);
398  ast_test_validate(test, strcmp(ast_str_buffer(buffer),
399  "# HELP test_counter A test counter\n"
400  "# TYPE test_counter counter\n"
401  "test_counter 0\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);
404 
405  return AST_TEST_PASS;
406 }
407 
408 AST_TEST_DEFINE(counter_create)
409 {
410  RAII_VAR(struct prometheus_metric *, metric, NULL, prometheus_metric_free_wrapper);
411 
412  switch (cmd) {
413  case TEST_INIT:
414  info->name = __func__;
415  info->category = CATEGORY;
416  info->summary = "Test creation (and destruction) of malloc'd counters";
417  info->description =
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;
421  case TEST_EXECUTE:
422  break;
423  }
424 
425  metric = prometheus_counter_create("test_counter", "A test counter");
426  ast_test_validate(test, metric != NULL);
427  ast_test_validate(test, metric->type == PROMETHEUS_METRIC_COUNTER);
428  ast_test_validate(test, metric->allocation_strategy = PROMETHEUS_METRIC_MALLOCD);
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);
434 
435  return AST_TEST_PASS;
436 }
437 
438 AST_TEST_DEFINE(gauge_to_string)
439 {
442  "test_gauge",
443  "A test gauge",
444  NULL);
445  struct prometheus_metric test_gauge_child_one = PROMETHEUS_METRIC_STATIC_INITIALIZATION(
447  "test_gauge",
448  "A test gauge",
449  NULL);
450  struct prometheus_metric test_gauge_child_two = PROMETHEUS_METRIC_STATIC_INITIALIZATION(
452  "test_gauge",
453  "A test gauge",
454  NULL);
455  RAII_VAR(struct ast_str *, buffer, NULL, ast_free);
456 
457  switch (cmd) {
458  case TEST_INIT:
459  info->name = __func__;
460  info->category = CATEGORY;
461  info->summary = "Test formatting of gauges";
462  info->description =
463  "This test covers the formatting of printed gauges";
464  return AST_TEST_NOT_RUN;
465  case TEST_EXECUTE:
466  break;
467  }
468 
469  buffer = ast_str_create(128);
470  if (!buffer) {
471  return AST_TEST_FAIL;
472  }
473 
474  PROMETHEUS_METRIC_SET_LABEL(&test_gauge_child_one, 0, "key_one", "value_one");
475  PROMETHEUS_METRIC_SET_LABEL(&test_gauge_child_one, 1, "key_two", "value_one");
476  PROMETHEUS_METRIC_SET_LABEL(&test_gauge_child_two, 0, "key_one", "value_two");
477  PROMETHEUS_METRIC_SET_LABEL(&test_gauge_child_two, 1, "key_two", "value_two");
478  AST_LIST_INSERT_TAIL(&test_gauge.children, &test_gauge_child_one, entry);
479  AST_LIST_INSERT_TAIL(&test_gauge.children, &test_gauge_child_two, entry);
480  prometheus_metric_to_string(&test_gauge, &buffer);
481  ast_test_validate(test, strcmp(ast_str_buffer(buffer),
482  "# HELP test_gauge A test gauge\n"
483  "# TYPE test_gauge gauge\n"
484  "test_gauge 0\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);
487 
488  return AST_TEST_PASS;
489 }
490 
491 AST_TEST_DEFINE(gauge_create)
492 {
493  RAII_VAR(struct prometheus_metric *, metric, NULL, prometheus_metric_free_wrapper);
494 
495  switch (cmd) {
496  case TEST_INIT:
497  info->name = __func__;
498  info->category = CATEGORY;
499  info->summary = "Test creation (and destruction) of malloc'd gauges";
500  info->description =
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;
504  case TEST_EXECUTE:
505  break;
506  }
507 
508  metric = prometheus_gauge_create("test_gauge", "A test gauge");
509  ast_test_validate(test, metric != NULL);
510  ast_test_validate(test, metric->type == PROMETHEUS_METRIC_GAUGE);
511  ast_test_validate(test, metric->allocation_strategy = PROMETHEUS_METRIC_MALLOCD);
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);
517 
518  return AST_TEST_PASS;
519 }
520 
521 AST_TEST_DEFINE(config_general_basic_auth)
522 {
523  RAII_VAR(CURL *, curl, NULL, curl_free_wrapper);
524  struct prometheus_general_config *config;
525  int res;
526  long response_code;
527 
528  switch (cmd) {
529  case TEST_INIT:
530  info->name = __func__;
531  info->category = CATEGORY;
532  info->summary = "Test basic auth handling";
533  info->description =
534  "This test covers authentication of requests";
535  return AST_TEST_NOT_RUN;
536  case TEST_EXECUTE:
537  break;
538  }
539 
540  config = config_alloc();
541  if (!config) {
542  return AST_TEST_NOT_RUN;
543  }
544  ast_string_field_set(config, auth_username, "foo");
545  ast_string_field_set(config, auth_password, "bar");
546  /* Prometheus module owns the ref after this call */
548  ao2_ref(config, -1);
549 
550  curl = get_curl_instance();
551  if (!curl) {
552  return AST_TEST_NOT_RUN;
553  }
554 
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;
561  }
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);
565 
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;
574  }
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);
578 
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;
586  }
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);
590 
591  return AST_TEST_PASS;
592 }
593 
594 AST_TEST_DEFINE(config_general_enabled)
595 {
596  RAII_VAR(CURL *, curl, NULL, curl_free_wrapper);
597  struct prometheus_general_config *config;
598  int res;
599  long response_code;
600 
601  switch (cmd) {
602  case TEST_INIT:
603  info->name = __func__;
604  info->category = CATEGORY;
605  info->summary = "Test handling of enable/disable";
606  info->description =
607  "When disabled, the module should return a 503.\n"
608  "This test verifies that it actually occurs.";
609  return AST_TEST_NOT_RUN;
610  case TEST_EXECUTE:
611  break;
612  }
613 
614  config = config_alloc();
615  if (!config) {
616  return AST_TEST_NOT_RUN;
617  }
618  config->enabled = 0;
619  /* Prometheus module owns the ref after this call */
621  ao2_ref(config, -1);
622 
623  curl = get_curl_instance();
624  if (!curl) {
625  return AST_TEST_NOT_RUN;
626  }
627 
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;
633  }
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);
637 
638  return AST_TEST_PASS;
639 }
640 
641 AST_TEST_DEFINE(config_general_core_metrics)
642 {
643  RAII_VAR(CURL *, curl, NULL, curl_free_wrapper);
644  RAII_VAR(struct ast_str *, buffer, NULL, ast_free);
645  struct prometheus_general_config *config;
646  int res;
647 
648  switch (cmd) {
649  case TEST_INIT:
650  info->name = __func__;
651  info->category = CATEGORY;
652  info->summary = "Test producing core metrics";
653  info->description =
654  "This test covers the core metrics that are produced\n"
655  "by the basic Prometheus module.";
656  return AST_TEST_NOT_RUN;
657  case TEST_EXECUTE:
658  break;
659  }
660 
661  buffer = ast_str_create(128);
662  if (!buffer) {
663  return AST_TEST_NOT_RUN;
664  }
665 
666  config = config_alloc();
667  if (!config) {
668  return AST_TEST_NOT_RUN;
669  }
670  config->core_metrics_enabled = 1;
671  /* Prometheus module owns the ref after this call */
673  ao2_ref(config, -1);
674 
675  curl = get_curl_instance();
676  if (!curl) {
677  return AST_TEST_NOT_RUN;
678  }
679 
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;
687  }
688  ast_test_status_update(test, " -> Retrieved: %s\n", ast_str_buffer(buffer));
689 
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);
692 
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);
695 
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);
698 
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);
701 
702  return AST_TEST_PASS;
703 }
704 
705 static void safe_bridge_destroy(struct ast_bridge *bridge)
706 {
707  if (!bridge) {
708  return;
709  }
710  ast_bridge_destroy(bridge, 0);
711 }
712 
713 static int match_count(const char *str, const char *needle)
714 {
715  int count = 0;
716 
717  while ((str = strstr(str, needle))) {
718  str += strlen(needle);
719  count++;
720  }
721 
722  return count;
723 }
724 
725 AST_TEST_DEFINE(bridge_to_string)
726 {
727  RAII_VAR(struct ast_bridge *, bridge1, NULL, safe_bridge_destroy);
728  RAII_VAR(struct ast_bridge *, bridge2, NULL, safe_bridge_destroy);
729  RAII_VAR(struct ast_bridge *, bridge3, NULL, safe_bridge_destroy);
730  struct ast_str *response;
731 
732  switch (cmd) {
733  case TEST_INIT:
734  info->name = __func__;
735  info->category = CATEGORY;
736  info->summary = "Test producing bridge metrics";
737  info->description =
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;
741  case TEST_EXECUTE:
742  break;
743  }
744 
745  bridge1 = ast_bridge_basic_new();
746  ast_test_validate(test, bridge1 != NULL);
747 
750  "test_res_prometheus", "test_bridge_invisible", NULL);
751 
752  bridge3 = ast_bridge_basic_new();
753  ast_test_validate(test, bridge3 != NULL);
754 
755  response = prometheus_scrape_to_string();
756  if (!response) {
757  return AST_TEST_FAIL;
758  }
759 
760  ast_test_status_update(test, " -> Retrieved: %s\n", ast_str_buffer(response));
761  ast_test_validate(test, strstr(ast_str_buffer(response), "(null)") == NULL);
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);
764  ast_free(response);
765  return AST_TEST_PASS;
766 }
767 
768 static int process_config(int reload)
769 {
770  struct ast_config *config;
771  struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
772  const char *bindaddr;
773  const char *bindport;
774  const char *prefix;
775  const char *enabled;
776 
777  config = ast_config_load("http.conf", config_flags);
778  if (!config || config == CONFIG_STATUS_FILEINVALID) {
779  ast_log(AST_LOG_NOTICE, "HTTP config file is invalid; declining load");
780  return -1;
781  } else if (config == CONFIG_STATUS_FILEUNCHANGED) {
782  return 0;
783  }
784 
785  enabled = ast_config_option(config, "general", "enabled");
786  if (!enabled || ast_false(enabled)) {
787  ast_config_destroy(config);
788  ast_log(AST_LOG_NOTICE, "HTTP server is disabled; declining load");
789  return -1;
790  }
791 
792  /* Construct our Server URI */
793  bindaddr = ast_config_option(config, "general", "bindaddr");
794  if (!bindaddr) {
795  ast_config_destroy(config);
796  ast_log(AST_LOG_NOTICE, "HTTP config file fails to specify 'bindaddr'; declining load");
797  return -1;
798  }
799 
800  bindport = ast_config_option(config, "general", "bindport");
801  if (!bindport) {
802  bindport = "8088";
803  }
804 
805  prefix = ast_config_option(config, "general", "prefix");
806 
807  snprintf(server_uri, sizeof(server_uri), "http://%s:%s%s/test_metrics", bindaddr, bindport, S_OR(prefix, ""));
808 
809  ast_config_destroy(config);
810 
811  return 0;
812 }
813 
814 static int test_init_cb(struct ast_test_info *info, struct ast_test *test)
815 {
816  struct prometheus_general_config *new_module_config;
817 
818  new_module_config = config_alloc();
819  if (!new_module_config) {
820  return -1;
821  }
822 
823  module_config = prometheus_general_config_get();
824  prometheus_general_config_set(new_module_config);
825 
826  /* Allow the module to own the ref */
827  ao2_ref(new_module_config, -1);
828 
829  return 0;
830 }
831 
832 static int test_cleanup_cb(struct ast_test_info *info, struct ast_test *test)
833 {
834  prometheus_general_config_set(module_config);
835  ao2_cleanup(module_config);
836 
837  return 0;
838 }
839 
840 static int reload_module(void)
841 {
842  return process_config(1);
843 }
844 
845 static int unload_module(void)
846 {
847  AST_TEST_UNREGISTER(metric_values);
848  AST_TEST_UNREGISTER(metric_callback_register);
849  AST_TEST_UNREGISTER(metric_register);
850 
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);
855 
856  AST_TEST_UNREGISTER(config_general_enabled);
857  AST_TEST_UNREGISTER(config_general_basic_auth);
858  AST_TEST_UNREGISTER(config_general_core_metrics);
859 
860  AST_TEST_UNREGISTER(bridge_to_string);
861 
862  return 0;
863 }
864 
865 static int load_module(void)
866 {
867  if (process_config(0)) {
869  }
870 
871  AST_TEST_REGISTER(metric_values);
872  AST_TEST_REGISTER(metric_callback_register);
873  AST_TEST_REGISTER(metric_register);
874 
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);
879 
880  AST_TEST_REGISTER(config_general_enabled);
881  AST_TEST_REGISTER(config_general_basic_auth);
882  AST_TEST_REGISTER(config_general_core_metrics);
883 
884  AST_TEST_REGISTER(bridge_to_string);
885 
886  ast_test_register_init(CATEGORY, &test_init_cb);
887  ast_test_register_cleanup(CATEGORY, &test_cleanup_cb);
888 
890 }
891 
892 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Prometheus Core Unit Tests",
893  .load = load_module,
894  .reload = reload_module,
895  .unload = unload_module,
896  .requires = "res_prometheus",
897 );
Contains all the initialization information required to store a new test definition.
Definition: test.h:235
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.
Definition: strings.h:761
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)
Test Framework API.
int ast_bridge_destroy(struct ast_bridge *bridge, int cause)
Destroy a bridge.
Definition: bridge.c:944
int ast_str_append(struct ast_str **buf, ssize_t max_len, const char *fmt,...)
Append to a thread local dynamic string.
Definition: strings.h:1139
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 CATEGORY
#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.
Definition: astobj2.h:459
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()
Definition: astmm.h:191
The configuration settings for this module.
Definition: cdr.c:264
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.
Definition: bridge.c:934
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.
Definition: bridge.h:349
Asterisk Prometheus Metrics.
#define AST_LIST_INSERT_TAIL(head, elm, field)
Appends a list entry to the tail of a list.
Definition: linkedlists.h:731
Support for dynamic strings.
Definition: strings.h:623
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.
Definition: module.h:78
void prometheus_callback_unregister(struct prometheus_callback *callback)
Remove a registered callback.
Basic bridge subclass API.
Structure used to handle boolean flags.
Definition: utils.h:199
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...
Definition: strings.h:80
int attribute_pure ast_false(const char *val)
Make sure something is false. Determine if a string containing a boolean value is "false"...
Definition: utils.c:2216
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.
Definition: main/config.c:773
#define AST_TEST_DEFINE(hdr)
Definition: test.h:126
A metric whose value can bounce around like a jackrabbit.
Definition: search.h:40
The metric was allocated on the heap.
void ast_config_destroy(struct ast_config *cfg)
Destroys a config.
Definition: extconf.c:1289
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.
Definition: module.h:46
Bridging API.
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.
Definition: utils.h:941
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.
Definition: strings.h:659
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.
Definition: stringfields.h:521