Line data Source code
1 : /* Copyright (c) 2020-2021, The Tor Project, Inc. */
2 : /* See LICENSE for licensing information */
3 :
4 : /**
5 : * \file test_metrics.c
6 : * \brief Test lib/metrics and feature/metrics functionalities
7 : */
8 :
9 : #define CONFIG_PRIVATE
10 : #define CONNECTION_PRIVATE
11 : #define MAINLOOP_PRIVATE
12 : #define METRICS_STORE_ENTRY_PRIVATE
13 :
14 : #include "test/test.h"
15 : #include "test/test_helpers.h"
16 : #include "test/log_test_helpers.h"
17 :
18 : #include "app/config/config.h"
19 :
20 : #include "core/mainloop/connection.h"
21 : #include "core/mainloop/mainloop.h"
22 : #include "core/or/connection_st.h"
23 : #include "core/or/policies.h"
24 : #include "core/or/port_cfg_st.h"
25 :
26 : #include "feature/metrics/metrics.h"
27 :
28 : #include "lib/encoding/confline.h"
29 : #include "lib/metrics/metrics_store.h"
30 :
31 : #define TEST_METRICS_ENTRY_NAME "entryA"
32 : #define TEST_METRICS_ENTRY_HELP "Description of entryA"
33 : #define TEST_METRICS_ENTRY_LABEL_1 "label=farfadet"
34 : #define TEST_METRICS_ENTRY_LABEL_2 "label=ponki"
35 :
36 : static void
37 2 : set_metrics_port(or_options_t *options)
38 : {
39 2 : const char *port = "MetricsPort 9035"; /* Default to 127.0.0.1 */
40 2 : const char *policy = "MetricsPortPolicy accept 1.2.3.4";
41 :
42 2 : config_get_lines(port, &options->MetricsPort_lines, 0);
43 2 : config_get_lines(policy, &options->MetricsPortPolicy, 0);
44 :
45 : /* Parse and validate policy. */
46 2 : policies_parse_from_options(options);
47 2 : }
48 :
49 : static void
50 1 : test_config(void *arg)
51 : {
52 1 : char *err_msg = NULL;
53 1 : tor_addr_t addr;
54 1 : smartlist_t *ports = smartlist_new();
55 1 : or_options_t *options = get_options_mutable();
56 :
57 1 : (void) arg;
58 :
59 1 : set_metrics_port(options);
60 :
61 1 : int ret = metrics_parse_ports(options, ports, &err_msg);
62 1 : tt_int_op(ret, OP_EQ, 0);
63 1 : tt_int_op(smartlist_len(ports), OP_EQ, 1);
64 :
65 : /* Validate the configured port. */
66 1 : const port_cfg_t *cfg = smartlist_get(ports, 0);
67 1 : tt_assert(tor_addr_eq_ipv4h(&cfg->addr, 0x7f000001));
68 1 : tt_int_op(cfg->port, OP_EQ, 9035);
69 1 : tt_int_op(cfg->type, OP_EQ, CONN_TYPE_METRICS_LISTENER);
70 :
71 : /* Address of the policy should be permitted. */
72 1 : tor_addr_from_ipv4h(&addr, 0x01020304); /* 1.2.3.4 */
73 1 : ret = metrics_policy_permits_address(&addr);
74 1 : tt_int_op(ret, OP_EQ, true);
75 :
76 : /* Anything else, should not. */
77 1 : tor_addr_from_ipv4h(&addr, 0x01020305); /* 1.2.3.5 */
78 1 : ret = metrics_policy_permits_address(&addr);
79 1 : tt_int_op(ret, OP_EQ, false);
80 :
81 1 : done:
82 2 : SMARTLIST_FOREACH(ports, port_cfg_t *, c, port_cfg_free(c));
83 1 : smartlist_free(ports);
84 1 : or_options_free(options);
85 1 : tor_free(err_msg);
86 1 : }
87 :
88 : static char _c_buf[256];
89 : #define CONTAINS(conn, msg) \
90 : do { \
91 : tt_int_op(buf_datalen(conn->outbuf), OP_EQ, (strlen(msg))); \
92 : memset(_c_buf, 0, sizeof(_c_buf)); \
93 : buf_get_bytes(conn->outbuf, _c_buf, (strlen(msg))); \
94 : tt_str_op(_c_buf, OP_EQ, (msg)); \
95 : tt_int_op(buf_datalen(conn->outbuf), OP_EQ, 0); \
96 : } while (0)
97 :
98 : #define WRITE(conn, msg) \
99 : buf_add(conn->inbuf, (msg), (strlen(msg)));
100 :
101 : /* Free the previous conn object if any and allocate a new connection. In
102 : * order to be allowed, set its address to 1.2.3.4 as per the policy. */
103 : #define NEW_ALLOWED_CONN() \
104 : do { \
105 : close_closeable_connections(); \
106 : conn = connection_new(CONN_TYPE_METRICS, AF_INET); \
107 : tor_addr_from_ipv4h(&conn->addr, 0x01020304); \
108 : } while (0)
109 :
110 : static void
111 1 : test_connection(void *arg)
112 : {
113 1 : int ret;
114 1 : connection_t *conn = NULL;
115 1 : or_options_t *options = get_options_mutable();
116 :
117 1 : (void) arg;
118 :
119 : /* Notice that in this test, we will allocate a new connection at every test
120 : * case. This is because the metrics_connection_process_inbuf() marks for
121 : * close the connection in case of an error and thus we can't call again an
122 : * inbuf process function on a marked for close connection. */
123 :
124 1 : tor_init_connection_lists();
125 :
126 : /* Setup policy. */
127 1 : set_metrics_port(options);
128 :
129 : /* Set 1.2.3.5 IP, we should get rejected. */
130 1 : NEW_ALLOWED_CONN();
131 1 : tor_addr_from_ipv4h(&conn->addr, 0x01020305);
132 1 : ret = metrics_connection_process_inbuf(conn);
133 1 : tt_int_op(ret, OP_EQ, -1);
134 :
135 : /* No HTTP request yet. */
136 1 : NEW_ALLOWED_CONN();
137 1 : ret = metrics_connection_process_inbuf(conn);
138 1 : tt_int_op(ret, OP_EQ, 0);
139 1 : connection_free_minimal(conn);
140 :
141 : /* Bad request. */
142 1 : NEW_ALLOWED_CONN();
143 1 : WRITE(conn, "HTTP 4.7\r\n\r\n");
144 1 : ret = metrics_connection_process_inbuf(conn);
145 1 : tt_int_op(ret, OP_EQ, -1);
146 1 : CONTAINS(conn, "HTTP/1.0 400 Bad Request\r\n\r\n");
147 :
148 : /* Path not found. */
149 1 : NEW_ALLOWED_CONN();
150 1 : WRITE(conn, "GET /badpath HTTP/1.0\r\n\r\n");
151 1 : ret = metrics_connection_process_inbuf(conn);
152 1 : tt_int_op(ret, OP_EQ, -1);
153 1 : CONTAINS(conn, "HTTP/1.0 404 Not Found\r\n\r\n");
154 :
155 : /* Method not allowed. */
156 1 : NEW_ALLOWED_CONN();
157 1 : WRITE(conn, "POST /something HTTP/1.0\r\n\r\n");
158 1 : ret = metrics_connection_process_inbuf(conn);
159 1 : tt_int_op(ret, OP_EQ, -1);
160 1 : CONTAINS(conn, "HTTP/1.0 405 Method Not Allowed\r\n\r\n");
161 :
162 : /* Ask for metrics. The content should be above 0. We don't test the
163 : * validity of the returned content but it is certainly not an error. */
164 1 : NEW_ALLOWED_CONN();
165 1 : WRITE(conn, "GET /metrics HTTP/1.0\r\n\r\n");
166 1 : ret = metrics_connection_process_inbuf(conn);
167 1 : tt_int_op(ret, OP_EQ, 0);
168 1 : tt_int_op(buf_datalen(conn->outbuf), OP_GT, 0);
169 :
170 1 : done:
171 1 : or_options_free(options);
172 1 : connection_free_minimal(conn);
173 1 : }
174 :
175 : static void
176 1 : test_prometheus(void *arg)
177 : {
178 1 : metrics_store_t *store = NULL;
179 1 : metrics_store_entry_t *entry = NULL;
180 1 : buf_t *buf = buf_new();
181 1 : char *output = NULL;
182 :
183 1 : (void) arg;
184 :
185 : /* Fresh new store. No entries. */
186 1 : store = metrics_store_new();
187 1 : tt_assert(store);
188 :
189 : /* Add entry and validate its content. */
190 1 : entry = metrics_store_add(store, METRICS_TYPE_COUNTER,
191 : TEST_METRICS_ENTRY_NAME,
192 : TEST_METRICS_ENTRY_HELP);
193 1 : tt_assert(entry);
194 1 : metrics_store_entry_add_label(entry, TEST_METRICS_ENTRY_LABEL_1);
195 :
196 1 : static const char *expected =
197 : "# HELP " TEST_METRICS_ENTRY_NAME " " TEST_METRICS_ENTRY_HELP "\n"
198 : "# TYPE " TEST_METRICS_ENTRY_NAME " counter\n"
199 : TEST_METRICS_ENTRY_NAME "{" TEST_METRICS_ENTRY_LABEL_1 "} 0\n";
200 :
201 1 : metrics_store_get_output(METRICS_FORMAT_PROMETHEUS, store, buf);
202 1 : output = buf_extract(buf, NULL);
203 1 : tt_str_op(expected, OP_EQ, output);
204 :
205 1 : done:
206 1 : buf_free(buf);
207 1 : tor_free(output);
208 1 : metrics_store_free(store);
209 1 : }
210 :
211 : static void
212 1 : test_store(void *arg)
213 : {
214 1 : metrics_store_t *store = NULL;
215 1 : metrics_store_entry_t *entry = NULL;
216 :
217 1 : (void) arg;
218 :
219 : /* Fresh new store. No entries. */
220 1 : store = metrics_store_new();
221 1 : tt_assert(store);
222 1 : tt_assert(!metrics_store_get_all(store, TEST_METRICS_ENTRY_NAME));
223 :
224 : /* Add entry and validate its content. */
225 1 : entry = metrics_store_add(store, METRICS_TYPE_COUNTER,
226 : TEST_METRICS_ENTRY_NAME,
227 : TEST_METRICS_ENTRY_HELP);
228 1 : tt_assert(entry);
229 1 : tt_int_op(entry->type, OP_EQ, METRICS_TYPE_COUNTER);
230 1 : tt_str_op(entry->name, OP_EQ, TEST_METRICS_ENTRY_NAME);
231 1 : tt_str_op(entry->help, OP_EQ, TEST_METRICS_ENTRY_HELP);
232 1 : tt_uint_op(entry->u.counter.value, OP_EQ, 0);
233 :
234 : /* Access the entry. */
235 1 : tt_assert(metrics_store_get_all(store, TEST_METRICS_ENTRY_NAME));
236 :
237 : /* Add a label to the entry to make it unique. */
238 1 : metrics_store_entry_add_label(entry, TEST_METRICS_ENTRY_LABEL_1);
239 1 : tt_int_op(metrics_store_entry_has_label(entry, TEST_METRICS_ENTRY_LABEL_1),
240 : OP_EQ, true);
241 :
242 : /* Update entry's value. */
243 1 : metrics_store_entry_update(entry, 42);
244 1 : tt_int_op(metrics_store_entry_get_value(entry), OP_EQ, 42);
245 1 : metrics_store_entry_update(entry, 42);
246 1 : tt_int_op(metrics_store_entry_get_value(entry), OP_EQ, 84);
247 1 : metrics_store_entry_reset(entry);
248 1 : tt_int_op(metrics_store_entry_get_value(entry), OP_EQ, 0);
249 :
250 : /* Add a new entry of same name but different label. */
251 : /* Add entry and validate its content. */
252 1 : entry = metrics_store_add(store, METRICS_TYPE_COUNTER,
253 : TEST_METRICS_ENTRY_NAME,
254 : TEST_METRICS_ENTRY_HELP);
255 1 : tt_assert(entry);
256 1 : metrics_store_entry_add_label(entry, TEST_METRICS_ENTRY_LABEL_2);
257 :
258 : /* Make sure _both_ entries are there. */
259 1 : const smartlist_t *entries =
260 1 : metrics_store_get_all(store, TEST_METRICS_ENTRY_NAME);
261 1 : tt_assert(entries);
262 1 : tt_int_op(smartlist_len(entries), OP_EQ, 2);
263 :
264 1 : done:
265 1 : metrics_store_free(store);
266 1 : }
267 :
268 : struct testcase_t metrics_tests[] = {
269 :
270 : { "config", test_config, TT_FORK, NULL, NULL },
271 : { "connection", test_connection, TT_FORK, NULL, NULL },
272 : { "prometheus", test_prometheus, TT_FORK, NULL, NULL },
273 : { "store", test_store, TT_FORK, NULL, NULL },
274 :
275 : END_OF_TESTCASES
276 : };
277 :
|