Merge "vector: defaults and indexes"
[asterisk/asterisk.git] / tests / test_dns_query_set.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2015, Digium, Inc.
5  *
6  * Joshua Colp <jcolp@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         <support_level>core</support_level>
22  ***/
23
24 #include "asterisk.h"
25
26 #include <arpa/nameser.h>
27 #include <arpa/inet.h>
28
29 #include "asterisk/test.h"
30 #include "asterisk/module.h"
31 #include "asterisk/vector.h"
32 #include "asterisk/dns_core.h"
33 #include "asterisk/dns_resolver.h"
34 #include "asterisk/dns_query_set.h"
35 #include "asterisk/dns_internal.h"
36
37 struct query_set_data {
38         /*! Boolean indicator if query set has completed */
39         int query_set_complete;
40         /*! Number of times resolve() method has been called */
41         int resolves;
42         /*! Number of times resolve() method is allowed to be called */
43         int resolves_allowed;
44         /*! Number of times cancel() method has been called */
45         int cancel;
46         /*! Number of times cancel() method is allowed to be called */
47         int cancel_allowed;
48         ast_mutex_t lock;
49         ast_cond_t cond;
50 };
51
52 static void query_set_data_destructor(void *obj)
53 {
54         struct query_set_data *qsdata = obj;
55
56         ast_mutex_destroy(&qsdata->lock);
57         ast_cond_destroy(&qsdata->cond);
58 }
59
60 static struct query_set_data *query_set_data_alloc(void)
61 {
62         struct query_set_data *qsdata;
63
64         qsdata = ao2_alloc(sizeof(*qsdata), query_set_data_destructor);
65         if (!qsdata) {
66                 return NULL;
67         }
68
69         ast_mutex_init(&qsdata->lock);
70         ast_cond_init(&qsdata->cond, NULL);
71
72         return qsdata;
73 }
74
75 #define DNS_ANSWER "Yes sirree"
76 #define DNS_ANSWER_SIZE strlen(DNS_ANSWER)
77
78 /*!
79  * \brief Thread that performs asynchronous resolution.
80  *
81  * This thread uses the query's user data to determine how to
82  * perform the resolution. If the allowed number of resolutions
83  * has not been reached then this will succeed, otherwise the
84  * query is expected to have been canceled.
85  *
86  * \param dns_query The ast_dns_query that is being performed
87  * \return NULL
88  */
89 static void *resolution_thread(void *dns_query)
90 {
91         struct ast_dns_query *query = dns_query;
92         struct ast_dns_query_set *query_set = ast_dns_query_get_data(query);
93         struct query_set_data *qsdata = query_set->user_data;
94
95         ast_assert(qsdata != NULL);
96
97         ast_dns_resolver_set_result(query, 0, 0, NOERROR, "asterisk.org", DNS_ANSWER, DNS_ANSWER_SIZE);
98         ast_dns_resolver_completed(query);
99
100         ao2_ref(query, -1);
101         return NULL;
102 }
103
104 /*!
105  * \brief Resolver's resolve() method
106  *
107  * \param query The query that is to be resolved
108  * \retval 0 Successfully created thread to perform the resolution
109  * \retval non-zero Failed to create resolution thread
110  */
111 static int query_set_resolve(struct ast_dns_query *query)
112 {
113         struct ast_dns_query_set *query_set = ast_dns_query_get_data(query);
114         struct query_set_data *qsdata = query_set->user_data;
115         pthread_t resolver_thread;
116
117         /* Only the queries which will not be canceled actually start a thread */
118         if (qsdata->resolves++ < qsdata->cancel_allowed) {
119                 return 0;
120         }
121
122         return ast_pthread_create_detached(&resolver_thread, NULL, resolution_thread, ao2_bump(query));
123 }
124
125 /*!
126  * \brief Resolver's cancel() method
127  *
128  * \param query The query to cancel
129  * \return 0
130  */
131 static int query_set_cancel(struct ast_dns_query *query)
132 {
133         struct ast_dns_query_set *query_set = ast_dns_query_get_data(query);
134         struct query_set_data *qsdata;
135         int res = -1;
136
137         if (!query_set) {
138                 return -1;
139         }
140         qsdata = query_set->user_data;
141
142         if (qsdata->cancel++ < qsdata->cancel_allowed) {
143                 res = 0;
144         }
145
146         return res;
147 }
148
149 static struct ast_dns_resolver query_set_resolver = {
150         .name = "query_set",
151         .priority = 0,
152         .resolve = query_set_resolve,
153         .cancel = query_set_cancel,
154 };
155
156 /*!
157  * \brief Callback which is invoked upon query set completion
158  *
159  * \param query_set The query set
160  */
161 static void query_set_callback(const struct ast_dns_query_set *query_set)
162 {
163         struct query_set_data *qsdata = ast_dns_query_set_get_data(query_set);
164
165         ast_mutex_lock(&qsdata->lock);
166         qsdata->query_set_complete = 1;
167         ast_cond_signal(&qsdata->cond);
168         ast_mutex_unlock(&qsdata->lock);
169 }
170
171 /*!
172  * \brief Framework for running a query set DNS test
173  *
174  * This function serves as a common way of testing various numbers of queries in a
175  * query set and optional canceling of them.
176  *
177  * \param test The test being run
178  * \param resolve The number of queries that should be allowed to complete resolution
179  * \param cancel The number of queries that should be allowed to be canceled
180  */
181 static enum ast_test_result_state query_set_test(struct ast_test *test, int resolve, int cancel)
182 {
183         int total = resolve + cancel;
184         RAII_VAR(struct ast_dns_query_set *, query_set, NULL, ao2_cleanup);
185         RAII_VAR(struct query_set_data *, qsdata, NULL, ao2_cleanup);
186         enum ast_test_result_state res = AST_TEST_PASS;
187         int idx;
188         struct timespec timeout;
189
190         if (ast_dns_resolver_register(&query_set_resolver)) {
191                 ast_test_status_update(test, "Failed to register query set DNS resolver\n");
192                 return AST_TEST_FAIL;
193         }
194
195         qsdata = query_set_data_alloc();
196         if (!qsdata) {
197                 ast_test_status_update(test, "Failed to allocate data necessary for query set test\n");
198                 res = AST_TEST_FAIL;
199                 goto cleanup;
200         }
201
202         query_set = ast_dns_query_set_create();
203         if (!query_set) {
204                 ast_test_status_update(test, "Failed to create DNS query set\n");
205                 res = AST_TEST_FAIL;
206                 goto cleanup;
207         }
208
209         qsdata->resolves_allowed = resolve;
210         qsdata->cancel_allowed = cancel;
211
212         for (idx = 0; idx < total; ++idx) {
213                 if (ast_dns_query_set_add(query_set, "asterisk.org", T_A, C_IN)) {
214                         ast_test_status_update(test, "Failed to add query to DNS query set\n");
215                         res = AST_TEST_FAIL;
216                         goto cleanup;
217                 }
218         }
219
220         if (ast_dns_query_set_num_queries(query_set) != total) {
221                 ast_test_status_update(test, "DNS query set does not contain the correct number of queries\n");
222                 res = AST_TEST_FAIL;
223                 goto cleanup;
224         }
225
226         ast_dns_query_set_resolve_async(query_set, query_set_callback, qsdata);
227
228         if (cancel && (cancel == total)) {
229                 if (ast_dns_query_set_resolve_cancel(query_set)) {
230                         ast_test_status_update(test, "Failed to cancel DNS query set when it should be cancellable\n");
231                         res = AST_TEST_FAIL;
232                 }
233
234                 if (qsdata->query_set_complete) {
235                         ast_test_status_update(test, "Query set callback was invoked despite all queries being cancelled\n");
236                         res = AST_TEST_FAIL;
237                 }
238
239                 goto cleanup;
240         } else if (cancel) {
241                 if (!ast_dns_query_set_resolve_cancel(query_set)) {
242                         ast_test_status_update(test, "Successfully cancelled DNS query set when it should not be possible\n");
243                         res = AST_TEST_FAIL;
244                         goto cleanup;
245                 }
246         }
247
248         timeout = ast_tsnow();
249         timeout.tv_sec += 10;
250
251         ast_mutex_lock(&qsdata->lock);
252         while (!qsdata->query_set_complete) {
253                 if (ast_cond_timedwait(&qsdata->cond, &qsdata->lock, &timeout) == ETIMEDOUT) {
254                         break;
255                 }
256         }
257         ast_mutex_unlock(&qsdata->lock);
258
259         if (!qsdata->query_set_complete) {
260                 ast_test_status_update(test, "Query set did not complete when it should have\n");
261                 res = AST_TEST_FAIL;
262                 goto cleanup;
263         }
264
265         for (idx = 0; idx < ast_dns_query_set_num_queries(query_set); ++idx) {
266                 const struct ast_dns_query *query = ast_dns_query_set_get(query_set, idx);
267
268                 if (strcmp(ast_dns_query_get_name(query), "asterisk.org")) {
269                         ast_test_status_update(test, "Query did not have expected name\n");
270                         res = AST_TEST_FAIL;
271                 }
272                 if (ast_dns_query_get_rr_type(query) != T_A) {
273                         ast_test_status_update(test, "Query did not have expected type\n");
274                         res = AST_TEST_FAIL;
275                 }
276                 if (ast_dns_query_get_rr_class(query) != C_IN) {
277                         ast_test_status_update(test, "Query did not have expected class\n");
278                         res = AST_TEST_FAIL;
279                 }
280         }
281
282 cleanup:
283         ast_dns_resolver_unregister(&query_set_resolver);
284         return res;
285 }
286
287 AST_TEST_DEFINE(query_set)
288 {
289         switch (cmd) {
290         case TEST_INIT:
291                 info->name = "query_set";
292                 info->category = "/main/dns/query_set/";
293                 info->summary = "Test nominal asynchronous DNS query set";
294                 info->description =
295                         "This tests nominal query set in the following ways:\n"
296                         "\t* Multiple queries are added to a query set\n"
297                         "\t* The mock resolver is configured to respond to all queries\n"
298                         "\t* Asynchronous resolution of the query set is started\n"
299                         "\t* The mock resolver responds to all queries\n"
300                         "\t* We ensure that the query set callback is invoked upon completion";
301                 return AST_TEST_NOT_RUN;
302         case TEST_EXECUTE:
303                 break;
304         }
305
306         return query_set_test(test, 4, 0);
307 }
308
309 AST_TEST_DEFINE(query_set_empty)
310 {
311         switch (cmd) {
312         case TEST_INIT:
313                 info->name = "query_set_empty";
314                 info->category = "/main/dns/query_set/";
315                 info->summary = "Test nominal asynchronous empty DNS query set";
316                 info->description =
317                         "This tests nominal query set in the following ways:\n"
318                         "\t* No queries are added to a query set\n"
319                         "\t* Asynchronous resolution of the query set is started\n"
320                         "\t* We ensure that the query set callback is invoked upon completion";
321                 return AST_TEST_NOT_RUN;
322         case TEST_EXECUTE:
323                 break;
324         }
325
326         return query_set_test(test, 0, 0);
327 }
328
329 AST_TEST_DEFINE(query_set_nominal_cancel)
330 {
331         switch (cmd) {
332         case TEST_INIT:
333                 info->name = "query_set_nominal_cancel";
334                 info->category = "/main/dns/query_set/";
335                 info->summary = "Test nominal asynchronous DNS query set cancellation";
336                 info->description =
337                         "This tests nominal query set cancellation in the following ways:\n"
338                         "\t* Multiple queries are added to a query set\n"
339                         "\t* The mock resolver is configured to NOT respond to any queries\n"
340                         "\t* Asynchronous resolution of the query set is started\n"
341                         "\t* The query set is canceled and is confirmed to return with success";
342                 return AST_TEST_NOT_RUN;
343         case TEST_EXECUTE:
344                 break;
345         }
346
347         return query_set_test(test, 0, 4);
348 }
349
350 AST_TEST_DEFINE(query_set_off_nominal_cancel)
351 {
352         switch (cmd) {
353         case TEST_INIT:
354                 info->name = "query_set_off_nominal_cancel";
355                 info->category = "/main/dns/query_set/";
356                 info->summary = "Test off-nominal asynchronous DNS query set cancellation";
357                 info->description =
358                         "This tests nominal query set cancellation in the following ways:\n"
359                         "\t* Multiple queries are added to a query set\n"
360                         "\t* The mock resolver is configured to respond to half the queries\n"
361                         "\t* Asynchronous resolution of the query set is started\n"
362                         "\t* The query set is canceled and is confirmed to return failure\n"
363                         "\t* The query set callback is confirmed to run, since it could not be fully canceled";
364                 return AST_TEST_NOT_RUN;
365         case TEST_EXECUTE:
366                 break;
367         }
368
369         return query_set_test(test, 2, 2);
370 }
371
372 static int unload_module(void)
373 {
374         AST_TEST_UNREGISTER(query_set);
375         AST_TEST_UNREGISTER(query_set_empty);
376         AST_TEST_UNREGISTER(query_set_nominal_cancel);
377         AST_TEST_UNREGISTER(query_set_off_nominal_cancel);
378
379         return 0;
380 }
381
382 static int load_module(void)
383 {
384         AST_TEST_REGISTER(query_set);
385         AST_TEST_REGISTER(query_set_empty);
386         AST_TEST_REGISTER(query_set_nominal_cancel);
387         AST_TEST_REGISTER(query_set_off_nominal_cancel);
388
389         return AST_MODULE_LOAD_SUCCESS;
390 }
391
392 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "DNS query set tests");