commit f65c0820af6082f15541756eaaeb042716751098 Author: David Goulet dgoulet@torproject.org Date: Fri Oct 23 12:27:12 2020 -0400
test: Metrics tests for lib/ and feature/
Related to #40063
Signed-off-by: David Goulet dgoulet@torproject.org --- src/test/include.am | 1 + src/test/test.c | 1 + src/test/test.h | 1 + src/test/test_metrics.c | 254 ++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 257 insertions(+)
diff --git a/src/test/include.am b/src/test/include.am index cb3a498f74..5149416044 100644 --- a/src/test/include.am +++ b/src/test/include.am @@ -200,6 +200,7 @@ src_test_test_SOURCES += \ src/test/test_link_handshake.c \ src/test/test_logging.c \ src/test/test_mainloop.c \ + src/test/test_metrics.c \ src/test/test_microdesc.c \ src/test/test_namemap.c \ src/test/test_netinfo.c \ diff --git a/src/test/test.c b/src/test/test.c index 77aa6db975..9e0fc4433b 100644 --- a/src/test/test.c +++ b/src/test/test.c @@ -738,6 +738,7 @@ struct testgroup_t testgroups[] = { { "legacy_hs/", hs_tests }, { "link-handshake/", link_handshake_tests }, { "mainloop/", mainloop_tests }, + { "metrics/", metrics_tests }, { "netinfo/", netinfo_tests }, { "nodelist/", nodelist_tests }, { "oom/", oom_tests }, diff --git a/src/test/test.h b/src/test/test.h index bd3a4102f5..ba1d69fa11 100644 --- a/src/test/test.h +++ b/src/test/test.h @@ -150,6 +150,7 @@ extern struct testcase_t keypin_tests[]; extern struct testcase_t link_handshake_tests[]; extern struct testcase_t logging_tests[]; extern struct testcase_t mainloop_tests[]; +extern struct testcase_t metrics_tests[]; extern struct testcase_t microdesc_tests[]; extern struct testcase_t namemap_tests[]; extern struct testcase_t netinfo_tests[]; diff --git a/src/test/test_metrics.c b/src/test/test_metrics.c new file mode 100644 index 0000000000..26e84a5798 --- /dev/null +++ b/src/test/test_metrics.c @@ -0,0 +1,254 @@ +/* Copyright (c) 2020, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file test_metrics.c + * \brief Test lib/metrics and feature/metrics functionnalities + */ + +#define CONFIG_PRIVATE +#define CONNECTION_PRIVATE +#define METRICS_STORE_ENTRY_PRIVATE + +#include "test/test.h" +#include "test/test_helpers.h" +#include "test/log_test_helpers.h" + +#include "app/config/config.h" + +#include "core/mainloop/connection.h" +#include "core/or/connection_st.h" +#include "core/or/policies.h" +#include "core/or/port_cfg_st.h" + +#include "feature/metrics/metrics.h" + +#include "lib/encoding/confline.h" +#include "lib/metrics/metrics_store.h" + +#define TEST_METRICS_ENTRY_NAME "entryA" +#define TEST_METRICS_ENTRY_HELP "Description of entryA" +#define TEST_METRICS_ENTRY_LABEL_1 "label=farfadet" +#define TEST_METRICS_ENTRY_LABEL_2 "label=ponki" + +static void +set_metrics_port(or_options_t *options) +{ + const char *port = "MetricsPort 9035"; /* Default to 127.0.0.1 */ + const char *policy = "MetricsPortPolicy accept 1.2.3.4"; + + config_get_lines(port, &options->MetricsPort_lines, 0); + config_get_lines(policy, &options->MetricsPortPolicy, 0); + + /* Parse and validate policy. */ + policies_parse_from_options(options); +} + +static void +test_config(void *arg) +{ + char *err_msg = NULL; + tor_addr_t addr; + smartlist_t *ports = smartlist_new(); + or_options_t *options = get_options_mutable(); + + (void) arg; + + set_metrics_port(options); + + int ret = metrics_parse_ports(options, ports, &err_msg); + tt_int_op(ret, OP_EQ, 0); + tt_int_op(smartlist_len(ports), OP_EQ, 1); + + /* Validate the configured port. */ + const port_cfg_t *cfg = smartlist_get(ports, 0); + tt_assert(tor_addr_eq_ipv4h(&cfg->addr, 0x7f000001)); + tt_int_op(cfg->port, OP_EQ, 9035); + tt_int_op(cfg->type, OP_EQ, CONN_TYPE_METRICS_LISTENER); + + /* Address of the policy should be permitted. */ + tor_addr_from_ipv4h(&addr, 0x01020304); /* 1.2.3.4 */ + ret = metrics_policy_permits_address(&addr); + tt_int_op(ret, OP_EQ, true); + + /* Anything else, should not. */ + tor_addr_from_ipv4h(&addr, 0x01020305); /* 1.2.3.5 */ + ret = metrics_policy_permits_address(&addr); + tt_int_op(ret, OP_EQ, false); + + done: + SMARTLIST_FOREACH(ports, port_cfg_t *, c, port_cfg_free(c)); + smartlist_free(ports); + or_options_free(options); +} + +static char _c_buf[256]; +#define CONTAINS(conn, msg) \ + do { \ + tt_int_op(buf_datalen(conn->outbuf), OP_EQ, (strlen(msg))); \ + memset(_c_buf, 0, sizeof(_c_buf)); \ + buf_get_bytes(conn->outbuf, _c_buf, (strlen(msg))); \ + tt_str_op(_c_buf, OP_EQ, (msg)); \ + tt_int_op(buf_datalen(conn->outbuf), OP_EQ, 0); \ + } while (0) + +#define WRITE(conn, msg) \ + buf_add(conn->inbuf, (msg), (strlen(msg))); + +static void +test_connection(void *arg) +{ + int ret; + connection_t *conn = connection_new(CONN_TYPE_METRICS, AF_INET); + or_options_t *options = get_options_mutable(); + + (void) arg; + + /* Setup policy. */ + set_metrics_port(options); + + /* Set 1.2.3.5 IP, we should get rejected. */ + tor_addr_from_ipv4h(&conn->addr, 0x01020305); + ret = metrics_connection_process_inbuf(conn); + tt_int_op(ret, OP_EQ, -1); + + /* Set 1.2.3.4 so from now on we are allowed to process the inbuf. */ + tor_addr_from_ipv4h(&conn->addr, 0x01020304); + + /* No HTTP request yet. */ + ret = metrics_connection_process_inbuf(conn); + tt_int_op(ret, OP_EQ, -1); + + /* Bad request. */ + WRITE(conn, "HTTP 4.7\r\n\r\n"); + ret = metrics_connection_process_inbuf(conn); + tt_int_op(ret, OP_EQ, -1); + CONTAINS(conn, "HTTP/1.0 400 Bad Request\r\n\r\n"); + + /* Path not found. */ + WRITE(conn, "GET /badpath HTTP/1.0\r\n\r\n"); + ret = metrics_connection_process_inbuf(conn); + tt_int_op(ret, OP_EQ, -1); + CONTAINS(conn, "HTTP/1.0 404 Not Found\r\n\r\n"); + + /* Method not allowed. */ + WRITE(conn, "POST /something HTTP/1.0\r\n\r\n"); + ret = metrics_connection_process_inbuf(conn); + tt_int_op(ret, OP_EQ, -1); + CONTAINS(conn, "HTTP/1.0 405 Method Not Allowed\r\n\r\n"); + + /* Ask for metrics. The content should be above 0. We don't test the + * validity of the returned content but it is certainly not an error. */ + WRITE(conn, "GET /metrics HTTP/1.0\r\n\r\n"); + ret = metrics_connection_process_inbuf(conn); + tt_int_op(ret, OP_EQ, 0); + tt_int_op(buf_datalen(conn->outbuf), OP_GT, 0); + + done: + or_options_free(options); + connection_free_minimal(conn); +} + +static void +test_prometheus(void *arg) +{ + metrics_store_t *store = NULL; + metrics_store_entry_t *entry = NULL; + buf_t *buf = buf_new(); + char *output = NULL; + + (void) arg; + + /* Fresh new store. No entries. */ + store = metrics_store_new(); + tt_assert(store); + + /* Add entry and validate its content. */ + entry = metrics_store_add(store, METRICS_TYPE_COUNTER, + TEST_METRICS_ENTRY_NAME, + TEST_METRICS_ENTRY_HELP); + tt_assert(entry); + metrics_store_entry_add_label(entry, TEST_METRICS_ENTRY_LABEL_1); + + static const char *expected = + "# HELP " TEST_METRICS_ENTRY_NAME " " TEST_METRICS_ENTRY_HELP "\n" + "# TYPE " TEST_METRICS_ENTRY_NAME " counter\n" + TEST_METRICS_ENTRY_NAME "{" TEST_METRICS_ENTRY_LABEL_1 "} 0\n"; + + metrics_store_get_output(METRICS_FORMAT_PROMETHEUS, store, buf); + output = buf_extract(buf, NULL); + tt_str_op(expected, OP_EQ, output); + + done: + buf_free(buf); + tor_free(output); + metrics_store_free(store); +} + +static void +test_store(void *arg) +{ + metrics_store_t *store = NULL; + metrics_store_entry_t *entry = NULL; + + (void) arg; + + /* Fresh new store. No entries. */ + store = metrics_store_new(); + tt_assert(store); + tt_assert(!metrics_store_get_all(store, TEST_METRICS_ENTRY_NAME)); + + /* Add entry and validate its content. */ + entry = metrics_store_add(store, METRICS_TYPE_COUNTER, + TEST_METRICS_ENTRY_NAME, + TEST_METRICS_ENTRY_HELP); + tt_assert(entry); + tt_int_op(entry->type, OP_EQ, METRICS_TYPE_COUNTER); + tt_str_op(entry->name, OP_EQ, TEST_METRICS_ENTRY_NAME); + tt_str_op(entry->help, OP_EQ, TEST_METRICS_ENTRY_HELP); + tt_uint_op(entry->u.counter.value, OP_EQ, 0); + + /* Access the entry. */ + tt_assert(metrics_store_get_all(store, TEST_METRICS_ENTRY_NAME)); + + /* Add a label to the entry to make it unique. */ + metrics_store_entry_add_label(entry, TEST_METRICS_ENTRY_LABEL_1); + tt_int_op(metrics_store_entry_has_label(entry, TEST_METRICS_ENTRY_LABEL_1), + OP_EQ, true); + + /* Update entry's value. */ + metrics_store_entry_update(entry, 42); + tt_int_op(metrics_store_entry_get_value(entry), OP_EQ, 42); + metrics_store_entry_update(entry, 42); + tt_int_op(metrics_store_entry_get_value(entry), OP_EQ, 84); + metrics_store_entry_reset(entry); + tt_int_op(metrics_store_entry_get_value(entry), OP_EQ, 0); + + /* Add a new entry of same name but different label. */ + /* Add entry and validate its content. */ + entry = metrics_store_add(store, METRICS_TYPE_COUNTER, + TEST_METRICS_ENTRY_NAME, + TEST_METRICS_ENTRY_HELP); + tt_assert(entry); + metrics_store_entry_add_label(entry, TEST_METRICS_ENTRY_LABEL_2); + + /* Make sure _both_ entires are there. */ + const smartlist_t *entries = + metrics_store_get_all(store, TEST_METRICS_ENTRY_NAME); + tt_assert(entries); + tt_int_op(smartlist_len(entries), OP_EQ, 2); + + done: + metrics_store_free(store); +} + +struct testcase_t metrics_tests[] = { + + { "config", test_config, TT_FORK, NULL, NULL }, + { "connection", test_connection, TT_FORK, NULL, NULL }, + { "prometheus", test_prometheus, TT_FORK, NULL, NULL }, + { "store", test_store, TT_FORK, NULL, NULL }, + + END_OF_TESTCASES +}; +