From 9cb46bc62f22e0104f1b41a423b014c281ef5fc2 Mon Sep 17 00:00:00 2001 From: Jakub Hrozek Date: Tue, 26 Mar 2013 16:49:26 +0100 Subject: Refactor dynamic DNS updates Provides two new layers instead of the previous IPA specific layer: 1) dp_dyndns.c -- a very generic dyndns layer on the DP level. Its purpose it to make it possible for any back end to use dynamic DNS updates. 2) sdap_dyndns.c -- a wrapper around dp_dyndns.c that utilizes some LDAP-specific features like autodetecting the address from the LDAP connection. Also converts the dyndns code to new specific error codes. --- src/tests/cmocka/test_dyndns.c | 339 +++++++++++++++++++++++++++++++++++++++++ src/tests/common.h | 2 + src/tests/common_dom.c | 9 +- src/tests/common_tev.c | 25 +++ 4 files changed, 367 insertions(+), 8 deletions(-) create mode 100644 src/tests/cmocka/test_dyndns.c (limited to 'src/tests') diff --git a/src/tests/cmocka/test_dyndns.c b/src/tests/cmocka/test_dyndns.c new file mode 100644 index 000000000..fd9240438 --- /dev/null +++ b/src/tests/cmocka/test_dyndns.c @@ -0,0 +1,339 @@ +/* + Authors: + Jakub Hrozek + + Copyright (C) 2013 Red Hat + + SSSD tests: Dynamic DNS tests + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* In order to access opaque types */ +#include "providers/dp_dyndns.c" + +#include "tests/cmocka/common_mock.h" +#include "src/providers/dp_dyndns.h" + +enum mock_nsupdate_states { + MOCK_NSUPDATE_OK, + MOCK_NSUPDATE_ERR, + MOCK_NSUPDATE_TIMEOUT, +}; + +struct dyndns_test_ctx { + struct sss_test_ctx *tctx; + + enum mock_nsupdate_states state; + int child_status; + int child_retval; +}; + +static struct dyndns_test_ctx *dyndns_test_ctx; + +void __wrap_execv(const char *path, char *const argv[]) +{ + int err; + + switch (dyndns_test_ctx->state) { + case MOCK_NSUPDATE_OK: + DEBUG(SSSDBG_FUNC_DATA, ("nsupdate success test case\n")); + err = 0; + break; + case MOCK_NSUPDATE_ERR: + DEBUG(SSSDBG_FUNC_DATA, ("nsupdate error test case\n")); + err = 1; + break; + case MOCK_NSUPDATE_TIMEOUT: + DEBUG(SSSDBG_FUNC_DATA, ("nsupdate timeout test case\n")); + err = 2; + sleep(3); + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE, ("unknown test case\n")); + err = 255; + break; + } + + DEBUG(SSSDBG_TRACE_LIBS, ("Child exiting with status %d\n", err)); + _exit(err); +} + +int __wrap_getifaddrs(struct ifaddrs **_ifap) +{ + struct ifaddrs *ifap; + struct ifaddrs *ifap_prev = NULL; + struct ifaddrs *ifap_head = NULL; + char *name; + char *straddr; + + while ((name = sss_mock_ptr_type(char *)) != NULL) { + straddr = sss_mock_ptr_type(char *); + if (straddr == NULL) { + errno = EINVAL; + goto fail; + } + + ifap = talloc_zero(global_talloc_context, struct ifaddrs); + if (ifap == NULL) { + errno = ENOMEM; /* getifaddrs sets errno, too */ + goto fail; + } + + if (ifap_prev) { + ifap_prev->ifa_next = ifap; + } else { + ifap_head = ifap; + } + ifap_prev = ifap; + + ifap->ifa_name = talloc_strdup(ifap, name); + if (ifap == NULL) { + errno = ENOMEM; + goto fail; + } + + ifap->ifa_addr = (struct sockaddr *) talloc(ifap, struct sockaddr_in); + if (ifap->ifa_addr == NULL) { + errno = ENOMEM; + goto fail; + } + ((struct sockaddr_in *) ifap->ifa_addr)->sin_family = AF_INET; + + /* convert straddr into ifa_addr */ + if (inet_pton(AF_INET, straddr, + &(((struct sockaddr_in *) ifap->ifa_addr)->sin_addr)) != 1) { + goto fail; + } + } + + *_ifap = ifap_head; + return 0; + +fail: + talloc_free(ifap); + return -1; +} + +void __wrap_freeifaddrs(struct ifaddrs *ifap) +{ + talloc_free(ifap); +} + +static void dyndns_test_done(struct tevent_req *req) +{ + struct dyndns_test_ctx *ctx = + tevent_req_callback_data(req, struct dyndns_test_ctx); + + ctx->child_retval = -1; + ctx->tctx->error = be_nsupdate_recv(req, &ctx->child_status); + talloc_zfree(req); + + ctx->tctx->done = true; +} + +void will_return_getifaddrs(const char *ifname, const char *straddr) +{ + will_return(__wrap_getifaddrs, ifname); + if (ifname) { + will_return(__wrap_getifaddrs, straddr); + } +} + +void dyndns_test_get_ifaddr(void **state) +{ + errno_t ret; + struct sss_iface_addr *addrlist; + char straddr[128]; + + check_leaks_push(dyndns_test_ctx); + will_return_getifaddrs("eth0", "192.168.0.1"); + will_return_getifaddrs("eth1", "192.168.0.2"); + will_return_getifaddrs(NULL, NULL); /* sentinel */ + ret = sss_iface_addr_list_get(dyndns_test_ctx, "eth0", &addrlist); + assert_int_equal(ret, EOK); + + /* There must be only one address with the correct value */ + assert_non_null(addrlist); + assert_non_null(addrlist->addr); + assert_null(addrlist->next); + assert_null(addrlist->prev); + + assert_non_null(inet_ntop(AF_INET, + &((struct sockaddr_in *) addrlist->addr)->sin_addr, + straddr, INET6_ADDRSTRLEN)); + assert_string_equal(straddr, "192.168.0.1"); + + talloc_free(addrlist); + assert_true(check_leaks_pop(dyndns_test_ctx) == true); +} + +void dyndns_test_ok(void **state) +{ + struct tevent_req *req; + errno_t ret; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_new(global_talloc_context); + assert_non_null(tmp_ctx); + check_leaks_push(tmp_ctx); + + dyndns_test_ctx->state = MOCK_NSUPDATE_OK; + + req = be_nsupdate_send(tmp_ctx, dyndns_test_ctx->tctx->ev, + discard_const("test message")); + assert_non_null(req); + tevent_req_set_callback(req, dyndns_test_done, dyndns_test_ctx); + + /* Wait until the test finishes with EOK */ + ret = test_ev_loop(dyndns_test_ctx->tctx); + DEBUG(SSSDBG_TRACE_LIBS, + ("Child request returned [%d]: %s\n", ret, strerror(ret))); + assert_int_equal(ret, EOK); + + assert_true(WIFEXITED(dyndns_test_ctx->child_status)); + assert_int_equal(WEXITSTATUS(dyndns_test_ctx->child_status), 0); + + assert_true(check_leaks_pop(tmp_ctx) == true); + talloc_free(tmp_ctx); +} + +void dyndns_test_error(void **state) +{ + struct tevent_req *req; + errno_t ret; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_new(global_talloc_context); + assert_non_null(tmp_ctx); + check_leaks_push(tmp_ctx); + + dyndns_test_ctx->state = MOCK_NSUPDATE_ERR; + + req = be_nsupdate_send(tmp_ctx, dyndns_test_ctx->tctx->ev, + discard_const("test message")); + assert_non_null(req); + tevent_req_set_callback(req, dyndns_test_done, dyndns_test_ctx); + + /* Wait until the test finishes with EIO (child error) */ + ret = test_ev_loop(dyndns_test_ctx->tctx); + DEBUG(SSSDBG_TRACE_LIBS, + ("Child request returned [%d]: %s\n", ret, strerror(ret))); + assert_int_equal(ret, ERR_DYNDNS_FAILED); + + assert_true(WIFEXITED(dyndns_test_ctx->child_status)); + assert_int_equal(WEXITSTATUS(dyndns_test_ctx->child_status), 1); + + assert_true(check_leaks_pop(tmp_ctx) == true); + talloc_free(tmp_ctx); +} + +void dyndns_test_timeout(void **state) +{ + struct tevent_req *req; + errno_t ret; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_new(global_talloc_context); + assert_non_null(tmp_ctx); + check_leaks_push(tmp_ctx); + + dyndns_test_ctx->state = MOCK_NSUPDATE_TIMEOUT; + + req = be_nsupdate_send(tmp_ctx, dyndns_test_ctx->tctx->ev, + discard_const("test message")); + assert_non_null(req); + tevent_req_set_callback(req, dyndns_test_done, dyndns_test_ctx); + + /* Wait until the test finishes with EIO (child error) */ + ret = test_ev_loop(dyndns_test_ctx->tctx); + DEBUG(SSSDBG_TRACE_LIBS, + ("Child request returned [%d]: %s\n", ret, strerror(ret))); + assert_int_equal(ret, ERR_DYNDNS_TIMEOUT); + + assert_true(check_leaks_pop(tmp_ctx) == true); + talloc_free(tmp_ctx); +} + +/* Testsuite setup and teardown */ +void dyndns_test_setup(void **state) +{ + assert_true(leak_check_setup()); + dyndns_test_ctx = talloc_zero(global_talloc_context, struct dyndns_test_ctx); + assert_non_null(dyndns_test_ctx); + + dyndns_test_ctx->tctx = create_ev_test_ctx(dyndns_test_ctx); + assert_non_null(dyndns_test_ctx->tctx); +} + +void dyndns_test_teardown(void **state) +{ + talloc_free(dyndns_test_ctx); + assert_true(leak_check_teardown()); +} + +int main(int argc, const char *argv[]) +{ + int rv; + poptContext pc; + int opt; + struct poptOption long_options[] = { + POPT_AUTOHELP + SSSD_DEBUG_OPTS + POPT_TABLEEND + }; + + const UnitTest tests[] = { + /* Utility functions unit test */ + unit_test(dyndns_test_get_ifaddr), + + /* Dynamic DNS update unit tests*/ + unit_test_setup_teardown(dyndns_test_ok, + dyndns_test_setup, dyndns_test_teardown), + unit_test_setup_teardown(dyndns_test_error, + dyndns_test_setup, dyndns_test_teardown), + unit_test_setup_teardown(dyndns_test_timeout, + dyndns_test_setup, dyndns_test_teardown), + }; + + /* Set debug level to invalid value so we can deside if -d 0 was used. */ + debug_level = SSSDBG_INVALID; + + pc = poptGetContext(argv[0], argc, argv, long_options, 0); + while((opt = poptGetNextOpt(pc)) != -1) { + switch(opt) { + default: + fprintf(stderr, "\nInvalid option %s: %s\n\n", + poptBadOption(pc, 0), poptStrerror(opt)); + poptPrintUsage(pc, stderr, 0); + return 1; + } + } + poptFreeContext(pc); + + DEBUG_INIT(debug_level); + tests_set_cwd(); + rv = run_tests(tests); + + return rv; +} diff --git a/src/tests/common.h b/src/tests/common.h index e7fc812c7..931e603c1 100644 --- a/src/tests/common.h +++ b/src/tests/common.h @@ -72,6 +72,8 @@ struct sss_test_conf_param { const char *value; }; +struct sss_test_ctx *create_ev_test_ctx(TALLOC_CTX *mem_ctx); + struct sss_test_ctx * create_dom_test_ctx(TALLOC_CTX *mem_ctx, const char *tests_path, diff --git a/src/tests/common_dom.c b/src/tests/common_dom.c index db3297872..00e7f5ae2 100644 --- a/src/tests/common_dom.c +++ b/src/tests/common_dom.c @@ -42,19 +42,12 @@ create_dom_test_ctx(TALLOC_CTX *mem_ctx, errno_t ret; char *dompath; - test_ctx = talloc_zero(mem_ctx, struct sss_test_ctx); + test_ctx = create_ev_test_ctx(mem_ctx); if (test_ctx == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, ("talloc_zero failed\n")); goto fail; } - /* Create an event context */ - test_ctx->ev = tevent_context_init(test_ctx); - if (test_ctx->ev == NULL) { - DEBUG(SSSDBG_CRIT_FAILURE, ("tevent_context_init failed\n")); - goto fail; - } - conf_db = talloc_asprintf(test_ctx, "%s/%s", tests_path, confdb_path); if (conf_db == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, ("talloc_asprintf failed\n")); diff --git a/src/tests/common_tev.c b/src/tests/common_tev.c index e9db8d053..81b97d331 100644 --- a/src/tests/common_tev.c +++ b/src/tests/common_tev.c @@ -26,6 +26,31 @@ #include "tests/common.h" +struct sss_test_ctx * +create_ev_test_ctx(TALLOC_CTX *mem_ctx) +{ + struct sss_test_ctx *test_ctx; + + test_ctx = talloc_zero(mem_ctx, struct sss_test_ctx); + if (test_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, ("talloc_zero failed\n")); + goto fail; + } + + /* Create an event context */ + test_ctx->ev = tevent_context_init(test_ctx); + if (test_ctx->ev == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, ("tevent_context_init failed\n")); + goto fail; + } + + return test_ctx; + +fail: + talloc_free(test_ctx); + return NULL; +} + struct tevent_req * test_request_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, errno_t err) { -- cgit