summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJakub Hrozek <jhrozek@redhat.com>2013-04-09 17:40:40 +0200
committerJakub Hrozek <jhrozek@redhat.com>2013-05-03 20:25:46 +0200
commit5a4239490c7fb7d732180a9d40f27f0247c56631 (patch)
tree5d118934b4a922a1c549f1cc96b31c09ee60be64
parent04868f1573f4b26ef34610b6d7069172f93bd8ab (diff)
downloadsssd2-5a4239490c7fb7d732180a9d40f27f0247c56631.tar.gz
sssd2-5a4239490c7fb7d732180a9d40f27f0247c56631.tar.xz
sssd2-5a4239490c7fb7d732180a9d40f27f0247c56631.zip
dyndns: new option dyndns_refresh_interval
This new options adds the possibility of updating the DNS entries periodically regardless if they have changed or not. This feature will be useful mainly in AD environments where the Windows clients periodically update their DNS records.
-rw-r--r--Makefile.am3
-rw-r--r--src/config/SSSDConfig/__init__.py.in1
-rwxr-xr-xsrc/config/SSSDConfigTest.py2
-rw-r--r--src/config/etc/sssd.api.conf1
-rw-r--r--src/man/sssd-ipa.5.xml16
-rw-r--r--src/providers/dp_dyndns.c53
-rw-r--r--src/providers/dp_dyndns.h17
-rw-r--r--src/providers/ipa/ipa_common.c4
-rw-r--r--src/providers/ipa/ipa_dyndns.c107
-rw-r--r--src/providers/ipa/ipa_dyndns.h3
-rw-r--r--src/providers/ipa/ipa_opts.h1
-rw-r--r--src/tests/cmocka/test_dyndns.c85
12 files changed, 286 insertions, 7 deletions
diff --git a/Makefile.am b/Makefile.am
index 1bbfb01c..5514fc02 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1283,10 +1283,13 @@ test_io_CFLAGS = \
test_io_LDADD = \
$(CMOCKA_LIBS)
+dyndns_tests_DEPENDENCIES = \
+ $(ldblib_LTLIBRARIES)
dyndns_tests_SOURCES = \
$(TEST_MOCK_OBJ) \
$(SSSD_RESOLV_OBJ) \
src/tests/common_tev.c \
+ src/tests/common_dom.c \
src/tests/cmocka/test_dyndns.c \
src/providers/data_provider_opts.c
dyndns_tests_CFLAGS = \
diff --git a/src/config/SSSDConfig/__init__.py.in b/src/config/SSSDConfig/__init__.py.in
index 9f11d31c..003153d0 100644
--- a/src/config/SSSDConfig/__init__.py.in
+++ b/src/config/SSSDConfig/__init__.py.in
@@ -128,6 +128,7 @@ option_strings = {
'dyndns_update' : _("Whether to automatically update the client's DNS entry"),
'dyndns_ttl' : _("The TTL to apply to the client's DNS entry after updating it"),
'dyndns_iface' : _("The interface whose IP should be used for dynamic DNS updates"),
+ 'dyndns_refresh_interval' : _("How often to periodically update the client's DNS entry"),
# [provider/ipa]
'ipa_domain' : _('IPA domain'),
diff --git a/src/config/SSSDConfigTest.py b/src/config/SSSDConfigTest.py
index 2b4df874..18328d06 100755
--- a/src/config/SSSDConfigTest.py
+++ b/src/config/SSSDConfigTest.py
@@ -510,6 +510,7 @@ class SSSDConfigTestSSSDDomain(unittest.TestCase):
'dyndns_update',
'dyndns_ttl',
'dyndns_iface',
+ 'dyndns_refresh_interval',
'override_gid',
'case_sensitive',
'override_homedir',
@@ -856,6 +857,7 @@ class SSSDConfigTestSSSDDomain(unittest.TestCase):
'dyndns_update',
'dyndns_ttl',
'dyndns_iface',
+ 'dyndns_refresh_interval',
'override_gid',
'case_sensitive',
'override_homedir',
diff --git a/src/config/etc/sssd.api.conf b/src/config/etc/sssd.api.conf
index 02e2a0a6..b09cbd18 100644
--- a/src/config/etc/sssd.api.conf
+++ b/src/config/etc/sssd.api.conf
@@ -125,6 +125,7 @@ entry_cache_sudo_timeout = int, None, false
dyndns_update = bool, None, false
dyndns_ttl = int, None, false
dyndns_iface = str, None, false
+dyndns_refresh_interval = int, None, false
# Special providers
[provider/permit]
diff --git a/src/man/sssd-ipa.5.xml b/src/man/sssd-ipa.5.xml
index bedb1107..426e9a5b 100644
--- a/src/man/sssd-ipa.5.xml
+++ b/src/man/sssd-ipa.5.xml
@@ -204,6 +204,22 @@
</varlistentry>
<varlistentry>
+ <term>dyndns_refresh_interval (integer)</term>
+ <listitem>
+ <para>
+ How often should the back end perform periodic DNS update in
+ addition to the automatic update performed when the back end
+ goes online.
+ This option is optional and applicable only when dyndns_update
+ is true.
+ </para>
+ <para>
+ Default: 0 (disabled)
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term>ipa_hbac_search_base (string)</term>
<listitem>
<para>
diff --git a/src/providers/dp_dyndns.c b/src/providers/dp_dyndns.c
index 81228d5c..337817fc 100644
--- a/src/providers/dp_dyndns.c
+++ b/src/providers/dp_dyndns.c
@@ -879,6 +879,45 @@ be_nsupdate_recv(struct tevent_req *req, int *child_status)
return EOK;
}
+static void be_nsupdate_timer(struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval current_time,
+ void *pvt)
+{
+ struct be_nsupdate_ctx *ctx = talloc_get_type(pvt, struct be_nsupdate_ctx);
+
+ talloc_zfree(ctx->refresh_timer);
+ ctx->timer_callback(ctx->timer_pvt);
+
+ /* timer_callback is responsible for calling be_nsupdate_timer_schedule
+ * again */
+}
+
+void be_nsupdate_timer_schedule(struct tevent_context *ev,
+ struct be_nsupdate_ctx *ctx)
+{
+ int refresh;
+ struct timeval tv;
+
+ if (ctx->refresh_timer) {
+ DEBUG(SSSDBG_FUNC_DATA, ("Timer already scheduled\n"));
+ return;
+ }
+
+ refresh = dp_opt_get_int(ctx->opts, DP_OPT_DYNDNS_REFRESH_INTERVAL);
+ if (refresh == 0) return;
+ DEBUG(SSSDBG_FUNC_DATA, ("Scheduling timer in %d seconds\n", refresh));
+
+ tv = tevent_timeval_current_ofs(refresh, 0);
+ ctx->refresh_timer = tevent_add_timer(ev, ctx, tv,
+ be_nsupdate_timer, ctx);
+
+ if (!ctx->refresh_timer) {
+ DEBUG(SSSDBG_MINOR_FAILURE,
+ ("Failed to add dyndns refresh timer event\n"));
+ }
+}
+
errno_t
be_nsupdate_check(void)
{
@@ -906,6 +945,7 @@ be_nsupdate_check(void)
static struct dp_option default_dyndns_opts[] = {
{ "dyndns_update", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE },
+ { "dyndns_refresh_interval", DP_OPT_NUMBER, NULL_NUMBER, NULL_NUMBER },
{ "dyndns_iface", DP_OPT_STRING, NULL_STRING, NULL_STRING },
{ "dyndns_ttl", DP_OPT_NUMBER, { .number = 1200 }, NULL_NUMBER },
@@ -914,13 +954,16 @@ static struct dp_option default_dyndns_opts[] = {
errno_t
be_nsupdate_init(TALLOC_CTX *mem_ctx, struct be_ctx *be_ctx,
- struct dp_option *defopts, struct be_nsupdate_ctx **_ctx)
+ struct dp_option *defopts,
+ nsupdate_timer_fn_t timer_callback,
+ void *timer_pvt,
+ struct be_nsupdate_ctx **_ctx)
{
errno_t ret;
struct dp_option *src_opts;
struct be_nsupdate_ctx *ctx;
- ctx = talloc(mem_ctx, struct be_nsupdate_ctx);
+ ctx = talloc_zero(mem_ctx, struct be_nsupdate_ctx);
if (ctx == NULL) return ENOMEM;
src_opts = defopts ? defopts : default_dyndns_opts;
@@ -932,6 +975,10 @@ be_nsupdate_init(TALLOC_CTX *mem_ctx, struct be_ctx *be_ctx,
return ret;
}
+ ctx->timer_callback = timer_callback;
+ ctx->timer_pvt = timer_pvt;
+ be_nsupdate_timer_schedule(be_ctx->ev, ctx);
+
*_ctx = ctx;
- return EOK;
+ return ERR_OK;
}
diff --git a/src/providers/dp_dyndns.h b/src/providers/dp_dyndns.h
index 95403a3a..e49ab8f0 100644
--- a/src/providers/dp_dyndns.h
+++ b/src/providers/dp_dyndns.h
@@ -29,12 +29,21 @@
/* dynamic dns helpers */
struct sss_iface_addr;
+typedef void (*nsupdate_timer_fn_t)(void *pvt);
+
struct be_nsupdate_ctx {
struct dp_option *opts;
+
+ time_t last_refresh;
+ bool timer_in_progress;
+ struct tevent_timer *refresh_timer;
+ nsupdate_timer_fn_t timer_callback;
+ void *timer_pvt;
};
enum dp_dyndns_opts {
DP_OPT_DYNDNS_UPDATE,
+ DP_OPT_DYNDNS_REFRESH_INTERVAL,
DP_OPT_DYNDNS_IFACE,
DP_OPT_DYNDNS_TTL,
@@ -48,7 +57,13 @@ errno_t be_nsupdate_check(void);
errno_t
be_nsupdate_init(TALLOC_CTX *mem_ctx, struct be_ctx *be_ctx,
- struct dp_option *defopts, struct be_nsupdate_ctx **ctx);
+ struct dp_option *defopts,
+ nsupdate_timer_fn_t timer_callback,
+ void *timer_pvt,
+ struct be_nsupdate_ctx **_ctx);
+
+void be_nsupdate_timer_schedule(struct tevent_context *ev,
+ struct be_nsupdate_ctx *ctx);
errno_t
sss_iface_addr_list_get(TALLOC_CTX *mem_ctx, const char *ifname,
diff --git a/src/providers/ipa/ipa_common.c b/src/providers/ipa/ipa_common.c
index 51750b2a..baece274 100644
--- a/src/providers/ipa/ipa_common.c
+++ b/src/providers/ipa/ipa_common.c
@@ -28,6 +28,7 @@
#include "db/sysdb_selinux.h"
#include "providers/ipa/ipa_common.h"
+#include "providers/ipa/ipa_dyndns.h"
#include "providers/ldap/sdap_async_private.h"
#include "providers/dp_dyndns.h"
#include "util/sss_krb5.h"
@@ -1018,7 +1019,8 @@ errno_t ipa_get_dyndns_options(struct be_ctx *be_ctx,
bool update;
int ttl;
- ret = be_nsupdate_init(ctx, be_ctx, ipa_dyndns_opts, &ctx->dyndns_ctx);
+ ret = be_nsupdate_init(ctx, be_ctx, ipa_dyndns_opts, ipa_dyndns_timer,
+ ctx, &ctx->dyndns_ctx);
if (ret != EOK) {
DEBUG(SSSDBG_OP_FAILURE,
("Cannot initialize IPA dyndns opts [%d]: %s\n",
diff --git a/src/providers/ipa/ipa_dyndns.c b/src/providers/ipa/ipa_dyndns.c
index 8023b533..52b8051b 100644
--- a/src/providers/ipa/ipa_dyndns.c
+++ b/src/providers/ipa/ipa_dyndns.c
@@ -55,6 +55,98 @@ errno_t ipa_dyndns_init(struct be_ctx *be_ctx,
return EOK;
}
+struct ipa_dyndns_timer_ctx {
+ struct sdap_id_op *sdap_op;
+ struct tevent_context *ev;
+
+ struct ipa_options *ctx;
+};
+
+static void ipa_dyndns_timer_connected(struct tevent_req *req);
+
+void ipa_dyndns_timer(void *pvt)
+{
+ struct ipa_options *ctx = talloc_get_type(pvt, struct ipa_options);
+ struct sdap_id_ctx *sdap_ctx = ctx->id_ctx->sdap_id_ctx;
+ struct tevent_req *req;
+ struct ipa_dyndns_timer_ctx *timer_ctx;
+ errno_t ret;
+
+ timer_ctx = talloc_zero(ctx, struct ipa_dyndns_timer_ctx);
+ if (timer_ctx == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, ("Out of memory\n"));
+ /* Not much we can do */
+ return;
+ }
+ timer_ctx->ev = sdap_ctx->be->ev;
+ timer_ctx->ctx = ctx;
+
+ /* In order to prevent the connection triggering an
+ * online callback which would in turn trigger a concurrent DNS
+ * update
+ */
+ ctx->dyndns_ctx->timer_in_progress = true;
+
+ /* Make sure to have a valid LDAP connection */
+ timer_ctx->sdap_op = sdap_id_op_create(timer_ctx, sdap_ctx->conn_cache);
+ if (timer_ctx->sdap_op == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, ("sdap_id_op_create failed\n"));
+ goto fail;
+ }
+
+ req = sdap_id_op_connect_send(timer_ctx->sdap_op, timer_ctx, &ret);
+ if (req == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, ("sdap_id_op_connect_send failed: [%d](%s)\n",
+ ret, sss_strerror(ret)));
+ goto fail;
+ }
+ tevent_req_set_callback(req, ipa_dyndns_timer_connected, timer_ctx);
+ return;
+
+fail:
+ ctx->dyndns_ctx->timer_in_progress = false;
+ be_nsupdate_timer_schedule(timer_ctx->ev, ctx->dyndns_ctx);
+ talloc_free(timer_ctx);
+}
+
+static void ipa_dyndns_timer_connected(struct tevent_req *req)
+{
+ errno_t ret;
+ int dp_error;
+ struct ipa_dyndns_timer_ctx *timer_ctx = tevent_req_callback_data(req,
+ struct ipa_dyndns_timer_ctx);
+ struct tevent_context *ev;
+ struct ipa_options *ctx;
+
+ ctx = timer_ctx->ctx;
+ ev = timer_ctx->ev;
+ ctx->dyndns_ctx->timer_in_progress = false;
+
+ ret = sdap_id_op_connect_recv(req, &dp_error);
+ talloc_zfree(req);
+ talloc_free(timer_ctx);
+ if (ret != EOK) {
+ if (dp_error == DP_ERR_OFFLINE) {
+ DEBUG(SSSDBG_MINOR_FAILURE, ("No IPA server is available, "
+ "dynamic DNS update is skipped in offline mode.\n"));
+ /* Another timer will be scheduled when provider goes online
+ * and ipa_dyndns_update() is called */
+ } else {
+ DEBUG(SSSDBG_OP_FAILURE,
+ ("Failed to connect to LDAP server: [%d](%s)\n",
+ ret, sss_strerror(ret)));
+
+ /* Just schedule another dyndns retry */
+ be_nsupdate_timer_schedule(ev, ctx->dyndns_ctx);
+ }
+ return;
+ }
+
+ /* all OK just call ipa_dyndns_update and schedule another refresh */
+ be_nsupdate_timer_schedule(ev, ctx->dyndns_ctx);
+ return ipa_dyndns_update(ctx);
+}
+
static struct tevent_req *ipa_dyndns_update_send(struct ipa_options *ctx);
static errno_t ipa_dyndns_update_recv(struct tevent_req *req);
@@ -63,6 +155,11 @@ static void ipa_dyndns_nsupdate_done(struct tevent_req *subreq);
void ipa_dyndns_update(void *pvt)
{
struct ipa_options *ctx = talloc_get_type(pvt, struct ipa_options);
+ struct sdap_id_ctx *sdap_ctx = ctx->id_ctx->sdap_id_ctx;
+
+ /* Schedule timer after provider went offline */
+ be_nsupdate_timer_schedule(sdap_ctx->be->ev, ctx->dyndns_ctx);
+
struct tevent_req *req = ipa_dyndns_update_send(ctx);
if (req == NULL) {
DEBUG(SSSDBG_CRIT_FAILURE, ("Could not update DNS\n"));
@@ -109,6 +206,16 @@ ipa_dyndns_update_send(struct ipa_options *ctx)
}
state->ipa_ctx = ctx;
+ if (ctx->dyndns_ctx->last_refresh + 60 > time(NULL) ||
+ ctx->dyndns_ctx->timer_in_progress) {
+ DEBUG(SSSDBG_FUNC_DATA, ("Last periodic update ran recently or timer"
+ "in progress, not scheduling another update\n"));
+ tevent_req_done(req);
+ tevent_req_post(req, sdap_ctx->be->ev);
+ return req;
+ }
+ state->ipa_ctx->dyndns_ctx->last_refresh = time(NULL);
+
dns_zone = dp_opt_get_string(ctx->basic, IPA_DOMAIN);
if (!dns_zone) {
ret = EIO;
diff --git a/src/providers/ipa/ipa_dyndns.h b/src/providers/ipa/ipa_dyndns.h
index d86c6634..ced3f097 100644
--- a/src/providers/ipa/ipa_dyndns.h
+++ b/src/providers/ipa/ipa_dyndns.h
@@ -25,6 +25,9 @@
#ifndef IPA_DYNDNS_H_
#define IPA_DYNDNS_H_
+void ipa_dyndns_update(void *pvt);
+void ipa_dyndns_timer(void *pvt);
+
errno_t ipa_dyndns_init(struct be_ctx *be_ctx,
struct ipa_options *ctx);
diff --git a/src/providers/ipa/ipa_opts.h b/src/providers/ipa/ipa_opts.h
index 392fcd86..57911082 100644
--- a/src/providers/ipa/ipa_opts.h
+++ b/src/providers/ipa/ipa_opts.h
@@ -53,6 +53,7 @@ struct dp_option ipa_basic_opts[] = {
struct dp_option ipa_dyndns_opts[] = {
{ "dyndns_update", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE },
+ { "dyndns_refresh_interval", DP_OPT_NUMBER, NULL_NUMBER, NULL_NUMBER },
{ "dyndns_iface", DP_OPT_STRING, NULL_STRING, NULL_STRING },
{ "dyndns_ttl", DP_OPT_NUMBER, { .number = 1200 }, NULL_NUMBER },
DP_OPTION_TERMINATOR
diff --git a/src/tests/cmocka/test_dyndns.c b/src/tests/cmocka/test_dyndns.c
index 93d3373a..0657bf1b 100644
--- a/src/tests/cmocka/test_dyndns.c
+++ b/src/tests/cmocka/test_dyndns.c
@@ -35,6 +35,12 @@
#include "tests/cmocka/common_mock.h"
#include "src/providers/dp_dyndns.h"
+#define TESTS_PATH "tests_dyndns"
+#define TEST_CONF_DB "test_dyndns_conf.ldb"
+#define TEST_SYSDB_FILE "cache_dyndns_test.ldb"
+#define TEST_DOM_NAME "dyndns_test"
+#define TEST_ID_PROVIDER "ldap"
+
enum mock_nsupdate_states {
MOCK_NSUPDATE_OK,
MOCK_NSUPDATE_ERR,
@@ -44,6 +50,9 @@ enum mock_nsupdate_states {
struct dyndns_test_ctx {
struct sss_test_ctx *tctx;
+ struct be_ctx *be_ctx;
+ struct be_nsupdate_ctx *update_ctx;
+
enum mock_nsupdate_states state;
int child_status;
int child_retval;
@@ -275,15 +284,73 @@ void dyndns_test_timeout(void **state)
talloc_free(tmp_ctx);
}
+void dyndns_test_timer(void *pvt)
+{
+ struct dyndns_test_ctx *ctx = talloc_get_type(pvt, struct dyndns_test_ctx);
+ static int ncalls = 0;
+
+ ncalls++;
+ if (ncalls == 1) {
+ be_nsupdate_timer_schedule(ctx->tctx->ev, ctx->update_ctx);
+ } else if (ncalls == 2) {
+ ctx->tctx->done = true;
+ }
+ ctx->tctx->error = ERR_OK;
+}
+
+void dyndns_test_interval(void **state)
+{
+ errno_t ret;
+ TALLOC_CTX *tmp_ctx;
+
+ tmp_ctx = talloc_new(global_talloc_context);
+ assert_non_null(tmp_ctx);
+ check_leaks_push(tmp_ctx);
+
+ ret = be_nsupdate_init(tmp_ctx, dyndns_test_ctx->be_ctx, NULL,
+ dyndns_test_timer, dyndns_test_ctx,
+ &dyndns_test_ctx->update_ctx);
+ assert_int_equal(ret, EOK);
+
+ /* Wait until the timer hits */
+ 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_OK);
+
+ talloc_free(dyndns_test_ctx->update_ctx);
+ assert_true(check_leaks_pop(tmp_ctx) == true);
+ talloc_free(tmp_ctx);
+}
+
/* Testsuite setup and teardown */
void dyndns_test_setup(void **state)
{
+ struct sss_test_conf_param params[] = {
+ { "dyndns_update", "true" },
+ { "dyndns_refresh_interval", "2" },
+ { NULL, NULL }, /* Sentinel */
+ };
+
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);
+ dyndns_test_ctx->tctx = create_dom_test_ctx(dyndns_test_ctx, TESTS_PATH,
+ TEST_CONF_DB, TEST_SYSDB_FILE,
+ TEST_DOM_NAME, TEST_ID_PROVIDER,
+ params);
assert_non_null(dyndns_test_ctx->tctx);
+
+ dyndns_test_ctx->be_ctx = talloc_zero(dyndns_test_ctx, struct be_ctx);
+ assert_non_null(dyndns_test_ctx->be_ctx);
+
+ dyndns_test_ctx->be_ctx->cdb = dyndns_test_ctx->tctx->confdb;
+ dyndns_test_ctx->be_ctx->ev = dyndns_test_ctx->tctx->ev;
+ dyndns_test_ctx->be_ctx->conf_path = talloc_asprintf(dyndns_test_ctx,
+ CONFDB_DOMAIN_PATH_TMPL,
+ TEST_DOM_NAME);
+ assert_non_null(dyndns_test_ctx->be_ctx->conf_path);
}
void dyndns_test_teardown(void **state)
@@ -295,11 +362,14 @@ void dyndns_test_teardown(void **state)
int main(int argc, const char *argv[])
{
int rv;
+ int no_cleanup = 0;
poptContext pc;
int opt;
struct poptOption long_options[] = {
POPT_AUTOHELP
SSSD_DEBUG_OPTS
+ {"no-cleanup", 'n', POPT_ARG_NONE, &no_cleanup, 0,
+ _("Do not delete the test database after a test run"), NULL },
POPT_TABLEEND
};
@@ -314,6 +384,8 @@ int main(int argc, const char *argv[])
dyndns_test_setup, dyndns_test_teardown),
unit_test_setup_teardown(dyndns_test_timeout,
dyndns_test_setup, dyndns_test_teardown),
+ unit_test_setup_teardown(dyndns_test_interval,
+ dyndns_test_setup, dyndns_test_teardown),
};
/* Set debug level to invalid value so we can deside if -d 0 was used. */
@@ -332,8 +404,17 @@ int main(int argc, const char *argv[])
poptFreeContext(pc);
DEBUG_INIT(debug_level);
+
+ /* Even though normally the tests should clean up after themselves
+ * they might not after a failed run. Remove the old db to be sure */
tests_set_cwd();
- rv = run_tests(tests);
+ test_dom_suite_cleanup(TESTS_PATH, TEST_CONF_DB, TEST_SYSDB_FILE);
+ test_dom_suite_setup(TESTS_PATH);
+ tests_set_cwd();
+ rv = run_tests(tests);
+ if (rv == 0 && !no_cleanup) {
+ test_dom_suite_cleanup(TESTS_PATH, TEST_CONF_DB, TEST_SYSDB_FILE);
+ }
return rv;
}