diff --git a/Knot.files b/Knot.files
index e18b95993afb21e9a16ea3c6652103e5f5e37348..613f302748008d6923e4cbad06aebf36d947eba3 100644
--- a/Knot.files
+++ b/Knot.files
@@ -1,5 +1,3 @@
-Doxyfile
-KNOWN_ISSUES
 Makefile.am
 README
 configure.ac
@@ -73,6 +71,11 @@ src/common/mem.c
 src/common/mem.h
 src/common/mempool.c
 src/common/mempool.h
+src/common/namedb/namedb.h
+src/common/namedb/namedb_lmdb.c
+src/common/namedb/namedb_lmdb.h
+src/common/namedb/namedb_trie.c
+src/common/namedb/namedb_trie.h
 src/dnstap/Makefile.am
 src/dnstap/convert.c
 src/dnstap/convert.h
@@ -176,8 +179,6 @@ src/knot/worker/queue.c
 src/knot/worker/queue.h
 src/knot/zone/contents.c
 src/knot/zone/contents.h
-src/knot/zone/events.c
-src/knot/zone/events.h
 src/knot/zone/node.c
 src/knot/zone/node.h
 src/knot/zone/semantic-check.c
@@ -198,6 +199,14 @@ src/knot/zone/zonedb.c
 src/knot/zone/zonedb.h
 src/knot/zone/zonefile.c
 src/knot/zone/zonefile.h
+src/knot/zone/events/events.c
+src/knot/zone/events/events.h
+src/knot/zone/events/handlers.h
+src/knot/zone/events/handlers.c
+src/knot/zone/events/replan.c
+src/knot/zone/events/replan.h
+src/knot/zone/timers.c
+src/knot/zone/timers.h
 src/libknot/binary.c
 src/libknot/binary.h
 src/libknot/common.h
@@ -322,6 +331,7 @@ tests/fdset.c
 tests/hattrie.c
 tests/hhash.c
 tests/journal.c
+tests/namedb.c
 tests/node.c
 tests/pkt.c
 tests/process_answer.c
@@ -343,3 +353,4 @@ tests/zone_events.c
 tests/zone_update.c
 tests/zonedb.c
 tests/ztree.c
+tests/zone_timers.c
diff --git a/configure.ac b/configure.ac
index 95aff9fe068a52e7b9c5c8855080439d4c1d1b89..1c463f8cac996d33d4eb83026b1e58cdbf781c8b 100644
--- a/configure.ac
+++ b/configure.ac
@@ -312,6 +312,10 @@ dt_DNSTAP([
 ])
 AM_CONDITIONAL([HAVE_DNSTAP], test "$opt_dnstap" != "no")
 
+dnl Check for LMDB
+KNOT_CHECK_HEADER([lmdb], [LMDB], [auto], [lmdb.h], [], [-llmdb])
+AS_IF([test "$enable_lmdb" = yes], [AC_DEFINE([HAVE_LMDB], [1], [Define to 1 to enable LMDB support])])
+
 AC_SEARCH_LIBS([pow], [m])
 AC_SEARCH_LIBS([pthread_create], [pthread], [], [AC_MSG_ERROR([pthreads not found])])
 AC_SEARCH_LIBS([dlopen], [dl])
@@ -407,6 +411,7 @@ AC_MSG_RESULT([
     Systemd integration:   ${enable_systemd}
     Dnstap support:        ${opt_dnstap}
     Code coverage:         ${enable_code_coverage}
+    LMDB support:          ${enable_lmdb}
 
   Continue with 'make' command
 ])
diff --git a/doc/running.rst b/doc/running.rst
index 496834ed95496e1155272213b85ee02b4a603241..0a80081e5c001fc1ffbd22698ed6c21d1b655e64 100644
--- a/doc/running.rst
+++ b/doc/running.rst
@@ -45,14 +45,13 @@ If you want to control the daemon directly, use ``SIGINT`` to quit the process o
      -f, --force            Force operation - override some checks.
      -v, --verbose          Verbose mode - additional runtime information.
      -V, --version          Print knot server version.
-     -i, --interactive      Interactive mode (do not daemonize).
      -h, --help             Print help and usage.
 
     Actions:
      stop                   Stop server.
      reload                 Reload configuration and changed zones.
      refresh <zone>         Refresh slave zone (all if not specified).
-     flush                  Flush journal and update zone files.
+     flush <zone>           Flush journal and update zone files. (all if not specified)
      status                 Check if server is running.
      zonestatus             Show status of configured zones.
      checkconf              Check current server configuration.
diff --git a/m4/knot-check-header.m4 b/m4/knot-check-header.m4
new file mode 100644
index 0000000000000000000000000000000000000000..884b986043c6a265130b06619f19c1151edb92fc
--- /dev/null
+++ b/m4/knot-check-header.m4
@@ -0,0 +1,49 @@
+# KNOT_CHECK_HEADER([prefix], [name], [default], [header], [cflags], [libs])
+# -----------------------------------------------------------------------------
+# Check presence of a library by checking for a header file.
+#
+# - adds --enable-prefix configure flag
+#
+# - if $enable_prefix is yes or auto, checks for the header file
+#
+# - emits an error if $enable_prefix is yes and the header is not present
+#
+# - check can be overridden by setting prefix_CFLAGS and prefix_LIBS
+#   environment variables
+#
+# Output variables: $enable_foo (yes or no), $foo_CFLAGS, and $foo_LIBS
+#
+AC_DEFUN([KNOT_CHECK_HEADER],
+[
+  AC_ARG_ENABLE([$1], AC_HELP_STRING([--enable-$1], [Support for $2 [default $3]]),
+    [enable_][$1][=$enableval], [enable_][$1][=][$3])
+
+  AC_ARG_VAR([$1][_CFLAGS], [C compiler flags for $2, overriding defaults])
+  AC_ARG_VAR([$1][_LIBS], [linker flags for $2, overriding defaults])
+
+  AS_CASE([$enable_][$1],
+    [no], [
+      [$1][_CFLAGS]=
+      [$1][_LIBS]=
+    ],
+    [auto|yes], [
+      AS_IF([test -n "$][$1][_LIBS"], [
+        dnl: skip header check if environment variable is set
+        [enable_][$1][=yes]
+      ],[
+        dnl: check for header
+        AC_CHECK_HEADER([$4], [
+          [enable_][$1]=yes
+          [$1][_CFLAGS]=[$5]
+          [$1][_LIBS]=[$6]
+        ], [
+          AS_IF([test "$enable_][$1][" = auto],
+            [[enable_][$1]=no],
+            [AC_MSG_ERROR([Header file "$4" for $2 not found])]
+          )
+        ])
+      ])
+    ],
+    [AC_MSG_ERROR([Invalid value of --enable-$1])]
+  )
+])
diff --git a/man/knotc.8.in b/man/knotc.8.in
index badf20ad9f6b368c6e6d7386500861d16c98cd05..543b281d1032b41b6140ebba85b7ee9ac81d8571 100644
--- a/man/knotc.8.in
+++ b/man/knotc.8.in
@@ -32,9 +32,6 @@ Verbose mode \- additional runtime information.
 \fB\-V\fR, \fB\-\-version\fR
 Print version of the server.
 .TP
-\fB\-i\fR, \fB\-\-interactive\fR
-Interactive mode (do not daemonize).
-.TP
 \fB\-h\fR, \fB\-\-help\fR
 Print help and usage.
 .SS "Actions:"
@@ -42,11 +39,11 @@ Print help and usage.
 \fBstop\fR
 Stop server (no\-op if not running).
 .TP
-\fBreload\fR
-Reload configuration and changed zones.
+\fBreload\fR [\fIzone\fR]...
+Reload configuration and changed zones (all if not specified).
 .TP
-\fBflush\fR
-Flush journal and update zone files.
+\fBflush\fR [\fIzone\fR]...
+Flush journal and update zone files (all if not specified).
 .TP
 \fBstatus\fR
 Check if server is running.
diff --git a/src/Makefile.am b/src/Makefile.am
index 81bbbe915d1d164a36a6f8fcb4829b88a0177893..32e2a9c85a5845b6cab98779d5c9ab8c314988ad 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -112,6 +112,11 @@ libknotcs_la_SOURCES =				\
 	common/mem.h				\
 	common/mempool.c			\
 	common/mempool.h			\
+	common/namedb/namedb.h			\
+	common/namedb/namedb_lmdb.h		\
+	common/namedb/namedb_lmdb.c		\
+	common/namedb/namedb_trie.h		\
+	common/namedb/namedb_trie.c		\
 	common/log.c				\
 	common/log.h
 
@@ -296,12 +301,18 @@ libknotd_la_SOURCES =				\
 	knot/worker/queue.h			\
 	knot/zone/contents.c			\
 	knot/zone/contents.h			\
-	knot/zone/events.c			\
-	knot/zone/events.h			\
+	knot/zone/events/events.c		\
+	knot/zone/events/events.h		\
+	knot/zone/events/handlers.c		\
+	knot/zone/events/handlers.h		\
+	knot/zone/events/replan.c		\
+	knot/zone/events/replan.h		\
 	knot/zone/node.c			\
 	knot/zone/node.h			\
 	knot/zone/semantic-check.c		\
 	knot/zone/semantic-check.h		\
+	knot/zone/timers.c			\
+	knot/zone/timers.h			\
 	knot/zone/zone-diff.c			\
 	knot/zone/zone-diff.h			\
 	knot/zone/zone-dump.c			\
@@ -322,17 +333,20 @@ libknotd_la_SOURCES =				\
 # libraries
 libknot_la_LIBADD  = libknotcs.la zscanner/libzscanner.la
 libknotd_la_LIBADD = libknots.la libknotcs.la libknot.la
+libknotus_la_LIBADD = libknots.la libknotcs.la libknot.la
+libknotd_la_CPPFLAGS = $(AM_CPPFLAGS) $(lmdb_CFLAGS)
+libknotd_la_LDFLAGS = $(AM_LDFLAGS) $(lmdb_LIBS)
 libknotus_la_CPPFLAGS = $(AM_CPPFLAGS) $(libidn_CFLAGS)
 libknotus_la_LDFLAGS = $(AM_LDFLAGS) $(libidn_LIBS)
-libknotcs_la_CPPFLAGS = $(AM_CPPFLAGS) $(systemd_CFLAGS)
-libknotcs_la_LDFLAGS = $(AM_LDFLAGS) $(systemd_LIBS)
+libknotcs_la_CPPFLAGS = $(AM_CPPFLAGS) $(systemd_CFLAGS) $(lmdb_CFLAGS)
+libknotcs_la_LDFLAGS = $(AM_LDFLAGS) $(systemd_LIBS) $(lmdb_LIBS)
 
 # sbin programs
-knotd_LDADD = libknot.la libknotd.la $(systemd_LIBS)
+knotd_LDADD = libknot.la libknotd.la $(systemd_LIBS) $(lmdb_LIBS)
 knotc_LDADD = libknot.la libknotd.la
 
 # bin programs
-BIN_LIBS = libknotus.la libknotcs.la libknots.la libknot.la
+BIN_LIBS = libknotus.la libknots.la
 kdig_LDADD       = $(BIN_LIBS) $(libidn_LIBS)
 khost_LDADD      = $(BIN_LIBS) $(libidn_LIBS)
 knsupdate_LDADD  = $(BIN_LIBS) zscanner/libzscanner.la
diff --git a/src/common/log.c b/src/common/log.c
index a5c8b1c8554cd81c65831618649f2fccbea19fd0..7eb73ccff6e96aa8fc6a38a148762d90d456438a 100644
--- a/src/common/log.c
+++ b/src/common/log.c
@@ -403,7 +403,7 @@ int log_msg_zone(int priority, const knot_dname_t *zone, const char *fmt, ...)
 {
 	va_list args;
 	va_start(args, fmt);
-	char *zone_str = knot_dname_to_str(zone);
+	char *zone_str = knot_dname_to_str_alloc(zone);
 	int result = log_msg_text(priority,
 				  zone_str ? zone_str : LOG_NULL_ZONE_STRING,
 				  fmt, args);
diff --git a/src/common/namedb/namedb.h b/src/common/namedb/namedb.h
new file mode 100644
index 0000000000000000000000000000000000000000..10d2cdd53540d6798b82244ae166f28188af3a1b
--- /dev/null
+++ b/src/common/namedb/namedb.h
@@ -0,0 +1,68 @@
+/*  Copyright (C) 2014 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+    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 <http://www.gnu.org/licenses/>.
+*/
+
+#pragma once
+
+#include "libknot/dname.h"
+
+enum {
+	KNOT_NAMEDB_RDONLY = 1 << 0,
+	KNOT_NAMEDB_SORTED = 1 << 1
+};
+
+typedef void knot_namedb_t;
+typedef void knot_iter_t;
+
+typedef struct knot_val {
+	void *data;
+	size_t len;
+} knot_val_t;
+
+typedef struct knot_txn {
+	knot_namedb_t *db;
+	void *txn;
+} knot_txn_t;
+
+struct namedb_api {
+
+	const char *name;
+
+	/* Context operations */
+
+	knot_namedb_t *(*init)(const char *handle, mm_ctx_t *mm);
+	void (*deinit)(knot_namedb_t *db);
+
+	/* Transactions */
+
+	int (*txn_begin)(knot_namedb_t *db, knot_txn_t *txn, unsigned flags);
+	int (*txn_commit)(knot_txn_t *txn);
+	void (*txn_abort)(knot_txn_t *txn);
+
+	/* Data access */
+
+	int (*count)(knot_txn_t *txn);
+	int (*find)(knot_txn_t *txn, knot_val_t *key, knot_val_t *val, unsigned flags);
+	int (*insert)(knot_txn_t *txn, knot_val_t *key, knot_val_t *val, unsigned flags);
+	int (*del)(knot_txn_t *txn,knot_val_t *key);
+
+	/* Iteration */
+
+	knot_iter_t *(*iter_begin)(knot_txn_t *txn, unsigned flags);
+	knot_iter_t *(*iter_next)(knot_iter_t *iter);
+	int (*iter_key)(knot_iter_t *iter, knot_val_t *key);
+	int (*iter_val)(knot_iter_t *iter, knot_val_t *val);
+	void (*iter_finish)(knot_iter_t *iter);
+};
diff --git a/src/common/namedb/namedb_lmdb.c b/src/common/namedb/namedb_lmdb.c
new file mode 100644
index 0000000000000000000000000000000000000000..5e0b4ae4d34d68154ef25ceda7e6729a9c0c52e7
--- /dev/null
+++ b/src/common/namedb/namedb_lmdb.c
@@ -0,0 +1,315 @@
+/*  Copyright (C) 2014 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+    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 <http://www.gnu.org/licenses/>.
+*/
+
+#ifdef HAVE_LMDB
+
+#include <lmdb.h>
+
+#include "common/namedb/namedb_lmdb.h"
+#include "libknot/errcode.h"
+
+struct lmdb_env
+{
+	MDB_dbi dbi;
+	MDB_env *env;
+	mm_ctx_t *pool;
+};
+
+static int dbase_open(struct lmdb_env *env, const char *handle)
+{
+	int ret = mdb_env_create(&env->env);
+	if (ret != 0) {
+		return ret;
+	}
+
+	ret = mdb_env_open(env->env, handle, 0, 0644);
+	if (ret != 0) {
+		mdb_env_close(env->env);
+		return ret;
+	}
+
+	MDB_txn *txn = NULL;
+	ret = mdb_txn_begin(env->env, NULL, 0, &txn);
+	if (ret != 0) {
+		mdb_env_close(env->env);
+		return ret;
+	}
+
+	ret = mdb_open(txn, NULL, 0, &env->dbi);
+	if (ret != 0) {
+		mdb_txn_abort(txn);
+		mdb_env_close(env->env);
+		return ret;
+	}
+
+	ret = mdb_txn_commit(txn);
+	if (ret != 0) {
+		mdb_env_close(env->env);
+		return ret;
+	}
+
+	return 0;
+}
+
+static void dbase_close(struct lmdb_env *env)
+{
+	mdb_close(env->env, env->dbi);
+	mdb_env_close(env->env);
+}
+
+static knot_namedb_t *init(const char *handle, mm_ctx_t *mm)
+{
+	struct lmdb_env *env = mm_alloc(mm, sizeof(struct lmdb_env));
+	if (env == NULL) {
+		return NULL;
+	}
+	memset(env, 0, sizeof(struct lmdb_env));
+
+	int ret = dbase_open(env, handle);
+	if (ret != 0) {
+		mm_free(mm, env);
+		return NULL;
+	}
+
+	env->pool = mm;
+	return env;
+}
+
+static void deinit(knot_namedb_t *db)
+{
+	if (db) {
+		struct lmdb_env *env = db;
+
+		dbase_close(env);
+		mm_free(env->pool, env);
+	}
+}
+
+static int txn_begin(knot_namedb_t *db, knot_txn_t *txn, unsigned flags)
+{
+	txn->db = db;
+	txn->txn = NULL;
+
+	unsigned txn_flags = 0;
+	if (flags & KNOT_NAMEDB_RDONLY) {
+		txn_flags |= MDB_RDONLY;
+	}
+
+	struct lmdb_env *env = db;
+	int ret = mdb_txn_begin(env->env, NULL, txn_flags, (MDB_txn **)&txn->txn);
+	if (ret != 0) {
+		return KNOT_ERROR;
+	}
+
+	return KNOT_EOK;
+}
+
+static int txn_commit(knot_txn_t *txn)
+{
+	int ret = mdb_txn_commit((MDB_txn *)txn->txn);
+	if (ret != 0) {
+		return KNOT_ERROR;
+	}
+
+	return KNOT_EOK;
+}
+
+static void txn_abort(knot_txn_t *txn)
+{
+	mdb_txn_abort((MDB_txn *)txn->txn);
+}
+
+static int count(knot_txn_t *txn)
+{
+	struct lmdb_env *env = txn->db;
+
+	MDB_stat stat;
+	int ret = mdb_stat(txn->txn, env->dbi, &stat);
+	if (ret != 0) {
+		return KNOT_ERROR;
+	}
+
+	return stat.ms_entries;
+}
+
+static int find(knot_txn_t *txn, knot_val_t *key, knot_val_t *val, unsigned flags)
+{
+	struct lmdb_env *env = txn->db;
+	MDB_val db_key = { key->len, key->data };
+	MDB_val data = { 0, NULL };
+
+
+	int ret = mdb_get(txn->txn, env->dbi, &db_key, &data);
+	if (ret != 0) {
+		if (ret == MDB_NOTFOUND) {
+			return KNOT_ENOENT;
+		} else {
+			return KNOT_ERROR;
+		}
+	}
+
+	val->data = data.mv_data;
+	val->len  = data.mv_size;
+	return KNOT_EOK;
+}
+
+static int insert(knot_txn_t *txn, knot_val_t *key, knot_val_t *val, unsigned flags)
+{
+	struct lmdb_env *env = txn->db;
+
+	MDB_cursor *cursor = NULL;
+	int ret = mdb_cursor_open(txn->txn, env->dbi, &cursor);
+	if (ret != 0) {
+		return KNOT_ERROR;
+	}
+
+	MDB_val db_key = { key->len, key->data };
+	MDB_val data = { val->len, val->data };
+
+	ret = mdb_cursor_get(cursor, &db_key, NULL, MDB_SET);
+	if (ret != 0) {
+		mdb_cursor_close(cursor);
+		if (ret == MDB_NOTFOUND) {
+			// Insert new item
+			ret = mdb_put(txn->txn, env->dbi, &db_key, &data, 0);
+			if (ret != 0) {
+				return KNOT_ERROR;
+			}
+
+			return KNOT_EOK;
+		} else {
+			return ret;
+		}
+	}
+
+	ret = mdb_cursor_put(cursor, &db_key, &data, MDB_CURRENT);
+	mdb_cursor_close(cursor);
+	if (ret != 0) {
+		return KNOT_ERROR;
+	}
+
+	return KNOT_EOK;
+}
+
+static int del(knot_txn_t *txn, knot_val_t *key)
+{
+	struct lmdb_env *env = txn->db;
+	MDB_val db_key = { key->len, key->data };
+	MDB_val data = { 0, NULL };
+
+	int ret = mdb_del(txn->txn, env->dbi, &db_key, &data);
+	if (ret != 0) {
+		return KNOT_ERROR;
+	}
+
+	return KNOT_EOK;
+}
+
+static knot_iter_t *iter_begin(knot_txn_t *txn, unsigned flags)
+{
+	struct lmdb_env *env = txn->db;
+	MDB_cursor *cursor = NULL;
+
+	int ret = mdb_cursor_open(txn->txn, env->dbi, &cursor);
+	if (ret != 0) {
+		return NULL;
+	}
+
+	ret = mdb_cursor_get(cursor, NULL, NULL, MDB_FIRST);
+	if (ret != 0) {
+		mdb_cursor_close(cursor);
+		return NULL;
+	}
+
+	return cursor;
+}
+
+static knot_iter_t *iter_next(knot_iter_t *iter)
+{
+	MDB_cursor *cursor = iter;
+
+	int ret = mdb_cursor_get(cursor, NULL, NULL, MDB_NEXT);
+	if (ret != 0) {
+		mdb_cursor_close(cursor);
+		return NULL;
+	}
+
+	return cursor;
+}
+
+static int iter_key(knot_iter_t *iter, knot_val_t *key)
+{
+	MDB_cursor *cursor = iter;
+
+	MDB_val mdb_key, mdb_val;
+	int ret = mdb_cursor_get(cursor, &mdb_key, &mdb_val, MDB_GET_CURRENT);
+	if (ret != 0) {
+		return KNOT_ERROR;
+	}
+
+	key->data = mdb_key.mv_data;
+	key->len  = mdb_key.mv_size;
+	return KNOT_EOK;
+}
+
+static int iter_val(knot_iter_t *iter, knot_val_t *val)
+{
+	MDB_cursor *cursor = iter;
+
+	MDB_val mdb_key, mdb_val;
+	int ret = mdb_cursor_get(cursor, &mdb_key, &mdb_val, MDB_GET_CURRENT);
+	if (ret != 0) {
+		return KNOT_ERROR;
+	}
+
+	val->data = mdb_val.mv_data;
+	val->len  = mdb_val.mv_size;
+	return KNOT_EOK;
+}
+
+static void iter_finish(knot_iter_t *iter)
+{
+	if (iter == NULL) {
+		return;
+	}
+
+	MDB_cursor *cursor = iter;
+	mdb_cursor_close(cursor);
+}
+
+struct namedb_api *namedb_lmdb_api(void)
+{
+	static struct namedb_api api = {
+		"lmdb",
+		init, deinit,
+		txn_begin, txn_commit, txn_abort,
+		count, find, insert, del,
+		iter_begin, iter_next, iter_key, iter_val, iter_finish
+	};
+
+	return &api;
+}
+
+#else
+
+#include <stdlib.h>
+
+struct namedb_api *namedb_lmdb_api(void)
+{
+	return NULL;
+}
+
+#endif
diff --git a/src/common/namedb/namedb_lmdb.h b/src/common/namedb/namedb_lmdb.h
new file mode 100644
index 0000000000000000000000000000000000000000..0dcf9d64f1d8a708926d09b74435b1c96b6d8350
--- /dev/null
+++ b/src/common/namedb/namedb_lmdb.h
@@ -0,0 +1,21 @@
+/*  Copyright (C) 2014 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+    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 <http://www.gnu.org/licenses/>.
+*/
+
+#pragma once
+
+#include "common/namedb/namedb.h"
+
+struct namedb_api *namedb_lmdb_api(void);
diff --git a/src/common/namedb/namedb_trie.c b/src/common/namedb/namedb_trie.c
new file mode 100644
index 0000000000000000000000000000000000000000..0ca29f652ccea480c562f075cebed552840af161
--- /dev/null
+++ b/src/common/namedb/namedb_trie.c
@@ -0,0 +1,140 @@
+/*  Copyright (C) 2014 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+    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 <http://www.gnu.org/licenses/>.
+*/
+
+#include "common/namedb/namedb_trie.h"
+#include "common-knot/hattrie/hat-trie.h"
+#include "libknot/errcode.h"
+
+static knot_namedb_t *init(const char *handle, mm_ctx_t *mm)
+{
+	return hattrie_create_n(TRIE_BUCKET_SIZE, mm);
+}
+
+static void deinit(knot_namedb_t *db)
+{
+	hattrie_free((hattrie_t *)db);
+}
+
+static int txn_begin(knot_namedb_t *db, knot_txn_t *txn, unsigned flags)
+{
+	txn->txn = (void *)(size_t)flags;
+	txn->db  = db;
+	return KNOT_EOK; /* N/A */
+}
+
+static int txn_commit(knot_txn_t *txn)
+{
+	/* Rebuild order index only for WR transactions. */
+	if ((size_t)txn->txn & KNOT_NAMEDB_RDONLY) {
+		return KNOT_EOK;
+	}
+
+	hattrie_build_index((hattrie_t *)txn->db);
+	return KNOT_EOK;
+}
+
+static void txn_abort(knot_txn_t *txn)
+{
+}
+
+static int count(knot_txn_t *txn)
+{
+	return hattrie_weight((hattrie_t *)txn->db);
+}
+
+static int find(knot_txn_t *txn, knot_val_t *key, knot_val_t *val, unsigned flags)
+{
+	value_t *ret = hattrie_tryget((hattrie_t *)txn->db, key->data, key->len);
+	if (ret == NULL) {
+		return KNOT_ENOENT;
+	}
+
+	val->data = *ret;
+	val->len  = sizeof(value_t); /* Trie doesn't support storing length. */
+	return KNOT_EOK;
+}
+
+static int insert(knot_txn_t *txn, knot_val_t *key, knot_val_t *val, unsigned flags)
+{
+	value_t *ret = hattrie_get((hattrie_t *)txn->db, key->data, key->len);
+	if (ret == NULL) {
+		return KNOT_ENOMEM;
+	}
+
+	*ret = val->data;
+	return KNOT_EOK;
+}
+
+static int del(knot_txn_t *txn, knot_val_t *key)
+{
+	return hattrie_del((hattrie_t *)txn->db, key->data, key->len);
+}
+
+static knot_iter_t *iter_begin(knot_txn_t *txn, unsigned flags)
+{
+	return hattrie_iter_begin((hattrie_t *)txn->db, (flags & KNOT_NAMEDB_SORTED));
+}
+
+static knot_iter_t *iter_next(knot_iter_t *iter)
+{
+	hattrie_iter_next((hattrie_iter_t *)iter);
+	if (hattrie_iter_finished((hattrie_iter_t *)iter)) {
+		hattrie_iter_free((hattrie_iter_t *)iter);
+		return NULL;
+	}
+
+	return iter;
+}
+
+static int iter_key(knot_iter_t *iter, knot_val_t *val)
+{
+	val->data = (void *)hattrie_iter_key((hattrie_iter_t *)iter, &val->len);
+	if (val->data == NULL) {
+		return KNOT_ENOENT;
+	}
+
+	return KNOT_EOK;
+}
+
+static int iter_val(knot_iter_t *iter, knot_val_t *val)
+{
+	value_t *ret = hattrie_iter_val((hattrie_iter_t *)iter);
+	if (ret == NULL) {
+		return KNOT_ENOENT;
+	}
+
+	val->data = *ret;
+	val->len  = sizeof(value_t);
+	return KNOT_EOK;
+}
+
+static void iter_finish(knot_iter_t *iter)
+{
+	hattrie_iter_free((hattrie_iter_t *)iter);
+}
+
+struct namedb_api *namedb_trie_api(void)
+{
+	static struct namedb_api api = {
+		"hattrie",
+		init, deinit,
+		txn_begin, txn_commit, txn_abort,
+		count, find, insert, del,
+		iter_begin, iter_next, iter_key, iter_val, iter_finish
+	};
+
+	return &api;
+}
diff --git a/src/common/namedb/namedb_trie.h b/src/common/namedb/namedb_trie.h
new file mode 100644
index 0000000000000000000000000000000000000000..048f299d2d01afb4fa7176da6a2bf6ae62316544
--- /dev/null
+++ b/src/common/namedb/namedb_trie.h
@@ -0,0 +1,21 @@
+/*  Copyright (C) 2014 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+    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 <http://www.gnu.org/licenses/>.
+*/
+
+#pragma once
+
+#include "common/namedb/namedb.h"
+
+struct namedb_api *namedb_trie_api(void);
diff --git a/src/knot/conf/cf-parse.y b/src/knot/conf/cf-parse.y
index 9010d4fc52d023a52bedba4dc324043b1bd76595..0db4e115d1d034b9f36d13cfbe951057f70b0151 100644
--- a/src/knot/conf/cf-parse.y
+++ b/src/knot/conf/cf-parse.y
@@ -307,7 +307,7 @@ static void query_module_create(void *scanner, const char *name, const char *par
 static int conf_key_exists(void *scanner, char *item)
 {
 	/* Find existing node in keys. */
-	knot_dname_t *sample = knot_dname_from_str(item);
+	knot_dname_t *sample = knot_dname_from_str_alloc(item);
 	knot_dname_to_lower(sample);
 	conf_key_t* r = 0;
 	WALK_LIST (r, new_config->keys) {
@@ -328,7 +328,7 @@ static int conf_key_add(void *scanner, knot_tsig_key_t **key, char *item)
 	*key = 0;
 
 	/* Find in keys */
-	knot_dname_t *sample = knot_dname_from_str(item);
+	knot_dname_t *sample = knot_dname_from_str_alloc(item);
 	knot_dname_to_lower(sample);
 
 	conf_key_t* r = 0;
@@ -376,7 +376,7 @@ static void conf_zone_start(void *scanner, char *name) {
 	/* Check domain name. */
 	knot_dname_t *dn = NULL;
 	if (this_zone->name != NULL) {
-		dn = knot_dname_from_str(this_zone->name);
+		dn = knot_dname_from_str_alloc(this_zone->name);
 	}
 	if (dn == NULL) {
 		free(this_zone->name);
@@ -679,7 +679,7 @@ keys:
      }
 
      if (fqdn != NULL && !conf_key_exists(scanner, fqdn)) {
-         knot_dname_t *dname = knot_dname_from_str(fqdn);
+         knot_dname_t *dname = knot_dname_from_str_alloc(fqdn);
 	 if (!dname) {
              cf_error(scanner, "key name '%s' not in valid domain name format",
                       fqdn);
diff --git a/src/knot/conf/conf.c b/src/knot/conf/conf.c
index f77fb678aa9b5cf12e2747b06102f9d459d36495..8c7fd7fceb6d27208c8bfa45d584822d34fae208 100644
--- a/src/knot/conf/conf.c
+++ b/src/knot/conf/conf.c
@@ -33,6 +33,7 @@
 #include "knot/knot.h"
 #include "knot/ctl/remote.h"
 #include "knot/nameserver/internet.h"
+#include "knot/zone/timers.h"
 
 /*
  * Defaults.
@@ -724,6 +725,10 @@ void conf_truncate(conf_t *conf, int unload_hooks)
 
 	/* Free remote control iface. */
 	conf_free_iface(conf->ctl.iface);
+	
+	/* Close timers db. */
+	close_timers_db(conf->timers_db);
+	conf->timers_db = NULL;
 }
 
 void conf_free(conf_t *conf)
@@ -776,6 +781,12 @@ int conf_open(const char* path)
 		return ret;
 	}
 
+	/* Open zone timers db. */
+	nconf->timers_db = open_timers_db(nconf->storage);
+	if (nconf->timers_db == NULL) {
+		log_warning("cannot open timers db\n");
+	}
+
 	/* Replace current config. */
 	conf_t **current_config = &s_config;
 	conf_t *oldconf = rcu_xchg_pointer(current_config, nconf);
@@ -795,10 +806,11 @@ int conf_open(const char* path)
 
 		/* Update hooks. */
 		conf_update_hooks(nconf);
-
+		
 		/* Free old config. */
 		conf_free(oldconf);
 	}
+	
 
 	return KNOT_EOK;
 }
diff --git a/src/knot/conf/conf.h b/src/knot/conf/conf.h
index 97afd9595804e1e39bbd9a43a7420c95a06fac24..e30c033aa10a6c9c8fce99a78216f18227ed69f7 100644
--- a/src/knot/conf/conf.h
+++ b/src/knot/conf/conf.h
@@ -38,6 +38,7 @@
 #include "libknot/dnssec/key.h"
 #include "libknot/dnssec/policy.h"
 #include "common-knot/lists.h"
+#include "common/namedb/namedb.h"
 #include "common/log.h"
 #include "knot/updates/acl.h"
 #include "common-knot/sockaddr.h"
@@ -268,6 +269,11 @@ typedef struct conf_t {
 	int serial_policy;   /*!< Serial policy when updating zone. */
 	struct query_plan *query_plan;
 	list_t query_modules;
+	
+	/*
+	 * Zone timers db
+	 */
+	knot_namedb_t *timers_db;
 
 	/*
 	 * Remote control interface.
diff --git a/src/knot/ctl/knotc_main.c b/src/knot/ctl/knotc_main.c
index d882106d7ddfc1d0c55650f56f26ed2af7d7e23c..122a31e0f0ccd421b40d9f4cb59c46e5ef7e9191 100644
--- a/src/knot/ctl/knotc_main.c
+++ b/src/knot/ctl/knotc_main.c
@@ -42,8 +42,7 @@ enum knotc_flag_t {
 	F_NULL =         0 << 0,
 	F_FORCE =        1 << 0,
 	F_VERBOSE =      1 << 1,
-	F_INTERACTIVE =  1 << 2,
-	F_NOCONF =       1 << 3,
+	F_NOCONF =       1 << 2
 };
 
 /*! \brief Check if flag is present. */
@@ -79,9 +78,9 @@ static int cmd_signzone(int argc, char *argv[], unsigned flags);
 /*! \brief Table of remote commands. */
 knot_cmd_t knot_cmd_tbl[] = {
 	{&cmd_stop,       0, "stop",       "",       "\t\tStop server."},
-	{&cmd_reload,     0, "reload",     "",       "\tReload configuration and changed zones."},
+	{&cmd_reload,     0, "reload",     "<zone>", "\tReload configuration and changed zones."},
 	{&cmd_refresh,    0, "refresh",    "<zone>", "\tRefresh slave zone (all if not specified). Flag '-f' forces retransfer."},
-	{&cmd_flush,      0, "flush",      "",       "\t\tFlush journal and update zone files."},
+	{&cmd_flush,      0, "flush",      "<zone>", "\t\tFlush journal and update zone file (all if not specified)."},
 	{&cmd_status,     0, "status",     "",       "\tCheck if server is running."},
 	{&cmd_zonestatus, 0, "zonestatus", "",       "\tShow status of configured zones."},
 	{&cmd_checkconf,  1, "checkconf",  "",       "\tCheck current server configuration."},
@@ -104,7 +103,6 @@ void help(void)
 	       " -f, --force            \tForce operation - override some checks.\n"
 	       " -v, --verbose          \tVerbose mode - additional runtime information.\n"
 	       " -V, --version          \tPrint %s server version.\n"
-	       " -i, --interactive      \tInteractive mode (do not daemonize).\n"
 	       " -h, --help             \tPrint help and usage.\n",
 	       RUN_DIR "/knot.sock", PACKAGE_NAME);
 	printf("\nActions:\n");
@@ -434,7 +432,6 @@ int main(int argc, char **argv)
 		{"force", no_argument, 0, 'f'},
 		{"config", required_argument, 0, 'c'},
 		{"verbose", no_argument, 0, 'v'},
-		{"interactive", no_argument, 0, 'i'},
 		{"version", no_argument, 0, 'V'},
 		{"help", no_argument, 0, 'h'},
 		{"server", required_argument, 0, 's' },
@@ -444,7 +441,7 @@ int main(int argc, char **argv)
 		{0, 0, 0, 0}
 	};
 
-	while ((c = getopt_long(argc, argv, "s:p:y:k:fc:viVh", opts, &li)) != -1) {
+	while ((c = getopt_long(argc, argv, "s:p:y:k:fc:vVh", opts, &li)) != -1) {
 		switch (c) {
 		case 's':
 			r_addr = optarg;
@@ -472,9 +469,6 @@ int main(int argc, char **argv)
 		case 'v':
 			flags |= F_VERBOSE;
 			break;
-		case 'i':
-			flags |= F_INTERACTIVE;
-			break;
 		case 'c':
 			config_fn = optarg;
 			break;
@@ -662,7 +656,7 @@ static int cmd_checkconf(int argc, char *argv[], unsigned flags)
 static bool fetch_zone(int argc, char *argv[], conf_zone_t *zone)
 {
 	/* Convert zone name to dname */
-	knot_dname_t *zone_name = knot_dname_from_str(zone->name);
+	knot_dname_t *zone_name = knot_dname_from_str_alloc(zone->name);
 	if (zone_name == NULL) {
 		return false;
 	}
@@ -673,7 +667,7 @@ static bool fetch_zone(int argc, char *argv[], conf_zone_t *zone)
 	int i = 0;
 	while (!found && i < argc) {
 		/* Convert the argument to dname */
-		knot_dname_t *arg_name = knot_dname_from_str(argv[i]);
+		knot_dname_t *arg_name = knot_dname_from_str_alloc(argv[i]);
 
 		if (arg_name != NULL) {
 			(void)knot_dname_to_lower(arg_name);
diff --git a/src/knot/ctl/remote.c b/src/knot/ctl/remote.c
index db5cddd1a5b585524734ec98ea7dbb8164185d7f..067f39bfb8ee68faaf9f40f5eb571848bc1f9a7c 100644
--- a/src/knot/ctl/remote.c
+++ b/src/knot/ctl/remote.c
@@ -32,6 +32,7 @@
 #include "libknot/rrtype/soa.h"
 #include "libknot/dnssec/random.h"
 #include "libknot/packet/wire.h"
+#include "knot/zone/timers.h"
 #include "knot/dnssec/zone-sign.h"
 #include "knot/dnssec/zone-nsec.h"
 
@@ -607,7 +608,7 @@ static void log_command(const char *cmd, const remote_cmdargs_t* args)
 		uint16_t rr_count = rr->rrs.rr_count;
 		for (uint16_t j = 0; j < rr_count; j++) {
 			const knot_dname_t *dn = knot_ns_name(&rr->rrs, j);
-			char *name = knot_dname_to_str(dn);
+			char *name = knot_dname_to_str_alloc(dn);
 
 			int ret = snprintf(params, rest, " %s", name);
 			free(name);
@@ -637,7 +638,7 @@ int remote_answer(int sock, server_t *s, knot_pkt_t *pkt)
 		return KNOT_EMALF;
 	}
 
-	knot_dname_t *realm = knot_dname_from_str(KNOT_CTL_REALM);
+	knot_dname_t *realm = knot_dname_from_str_alloc(KNOT_CTL_REALM);
 	if (!knot_dname_is_sub(qname, realm) != 0) {
 		dbg_server("remote: qname != *%s\n", KNOT_CTL_REALM_EXT);
 		knot_dname_free(&realm, NULL);
@@ -899,7 +900,7 @@ knot_pkt_t* remote_query(const char *query, const knot_tsig_key_t *key)
 
 	/* Question section. */
 	char *qname = strcdup(query, KNOT_CTL_REALM_EXT);
-	knot_dname_t *dname = knot_dname_from_str(qname);
+	knot_dname_t *dname = knot_dname_from_str_alloc(qname);
 	free(qname);
 	if (!dname) {
 		knot_pkt_free(&pkt);
@@ -944,7 +945,7 @@ int remote_build_rr(knot_rrset_t *rr, const char *k, uint16_t t)
 	}
 
 	/* Assert K is FQDN. */
-	knot_dname_t *key = knot_dname_from_str(k);
+	knot_dname_t *key = knot_dname_from_str_alloc(k);
 	if (key == NULL) {
 		return KNOT_ENOMEM;
 	}
@@ -993,7 +994,7 @@ int remote_create_ns(knot_rrset_t *rr, const char *d)
 	}
 
 	/* Create dname. */
-	knot_dname_t *dn = knot_dname_from_str(d);
+	knot_dname_t *dn = knot_dname_from_str_alloc(d);
 	if (!dn) {
 		return KNOT_ERROR;
 	}
diff --git a/src/knot/dnssec/nsec3-chain.c b/src/knot/dnssec/nsec3-chain.c
index c486a1508c32a89901c7e175baaed21241c6ca77..685ee2998a7150e3e9446674b46793a6414ce264 100644
--- a/src/knot/dnssec/nsec3-chain.c
+++ b/src/knot/dnssec/nsec3-chain.c
@@ -287,7 +287,7 @@ static zone_node_t *create_nsec3_node(knot_dname_t *owner,
 	if (!new_node) {
 		return NULL;
 	}
-	
+
 	node_set_parent(new_node, apex_node);
 
 	knot_rrset_t nsec3_rrset;
@@ -385,7 +385,7 @@ static int connect_nsec3_nodes(zone_node_t *a, zone_node_t *b,
 	assert(raw_length == knot_nsec3_hash_length(algorithm));
 
 	knot_dname_to_lower(b->owner);
-	uint8_t *b32_hash = (uint8_t *)knot_dname_to_str(b->owner);
+	uint8_t *b32_hash = (uint8_t *)knot_dname_to_str_alloc(b->owner);
 	size_t b32_length = knot_nsec3_hash_b32_length(algorithm);
 	if (!b32_hash) {
 		return KNOT_ENOMEM;
diff --git a/src/knot/main.c b/src/knot/main.c
index 720d0ed4796ab53bf9122dd60abebfa28f03eb7f..2dc5e3f55613ac3a6993f0c30abe159b9f87a927 100644
--- a/src/knot/main.c
+++ b/src/knot/main.c
@@ -36,6 +36,7 @@
 #include "knot/ctl/process.h"
 #include "knot/ctl/remote.h"
 #include "knot/conf/conf.h"
+#include "knot/zone/timers.h"
 #include "knot/server/tcp-handler.h"
 
 /* Signal flags. */
diff --git a/src/knot/modules/synth_record.c b/src/knot/modules/synth_record.c
index 779b3517a11d9d0af2fa122749791612423f14e4..80be22c2a3fa16383e78d5ffb117587964d5551e 100644
--- a/src/knot/modules/synth_record.c
+++ b/src/knot/modules/synth_record.c
@@ -185,7 +185,7 @@ static knot_dname_t *synth_ptrname(const char *addr_str, synth_template_t *tpl)
 	memcpy(ptrname + written, tpl->zone, zone_len);
 
 	/* Convert to domain name. */
-	return knot_dname_from_str(ptrname);
+	return knot_dname_from_str_alloc(ptrname);
 }
 
 static int reverse_rr(char *addr_str, synth_template_t *tpl, knot_pkt_t *pkt, knot_rrset_t *rr)
@@ -361,7 +361,7 @@ int synth_record_load(struct query_plan *plan, struct query_module *self)
 	/* Parse zone if generating reverse record. */
 	if (tpl->type == SYNTH_REVERSE) {
 		tpl->zone = strtok_r(NULL, " ", &saveptr);
-		knot_dname_t *check_name = knot_dname_from_str(tpl->zone);
+		knot_dname_t *check_name = knot_dname_from_str_alloc(tpl->zone);
 		if (check_name == NULL) {
 			MODULE_ERR("invalid zone '%s'", tpl->zone);
 			return KNOT_EMALF;
diff --git a/src/knot/nameserver/chaos.c b/src/knot/nameserver/chaos.c
index 41822136355eede084f8557e4ed8e47cd2763114..086ca8e7aff258df824dfdfd1f3091f0c1ce0d5e 100644
--- a/src/knot/nameserver/chaos.c
+++ b/src/knot/nameserver/chaos.c
@@ -26,7 +26,7 @@
  */
 static const char *get_txt_response_string(const knot_dname_t *qname)
 {
-	char *qname_str = knot_dname_to_str(qname);
+	char *qname_str = knot_dname_to_str_alloc(qname);
 	const char *response = NULL;
 
 	/* id.server and hostname.bind should have similar meaning */
diff --git a/src/knot/nameserver/internet.c b/src/knot/nameserver/internet.c
index d49ad312915300265b2c708c2dd102396bab2dd4..17c13d8e1f589c806ba94603a7306835d1eec81c 100644
--- a/src/knot/nameserver/internet.c
+++ b/src/knot/nameserver/internet.c
@@ -416,8 +416,8 @@ static int follow_cname(knot_pkt_t *pkt, uint16_t rrtype, struct query_data *qda
 	qdata->name = knot_cname_name(&cname_rr.rrs);
 
 #ifdef KNOT_NS_DEBUG
-	char *cname_str = knot_dname_to_str(cname_node->owner);
-	char *target_str = knot_dname_to_str(qdata->name);
+	char *cname_str = knot_dname_to_str_alloc(cname_node->owner);
+	char *target_str = knot_dname_to_str_alloc(qdata->name);
 	dbg_ns("%s: FOLLOW '%s' -> '%s'\n", __func__, cname_str, target_str);
 	free(cname_str);
 	free(target_str);
diff --git a/src/knot/nameserver/notify.c b/src/knot/nameserver/notify.c
index 9726acea2a94743acc8149fbacb4af2bbc3253ec..502b80de35d4ca03563d38962a8bc0dd21398498 100644
--- a/src/knot/nameserver/notify.c
+++ b/src/knot/nameserver/notify.c
@@ -24,6 +24,7 @@
 #include "libknot/rrset.h"
 #include "libknot/consts.h"
 #include "knot/zone/zonedb.h"
+#include "knot/zone/timers.h"
 #include "libknot/common.h"
 #include "libknot/packet/wire.h"
 #include "knot/updates/acl.h"
@@ -99,6 +100,10 @@ int notify_process_query(knot_pkt_t *pkt, struct query_data *qdata)
 	/* Incoming NOTIFY expires REFRESH timer and renews EXPIRE timer. */
 	zone_t *zone = (zone_t *)qdata->zone;
 	zone_events_schedule(zone, ZONE_EVENT_REFRESH, ZONE_EVENT_NOW);
+	int ret = write_zone_timers(conf()->timers_db, zone);
+	if (ret != KNOT_EOK) {
+		return NS_PROC_FAIL;
+	}
 
 	return NS_PROC_DONE;
 }
diff --git a/src/knot/nameserver/nsec_proofs.c b/src/knot/nameserver/nsec_proofs.c
index abf8546a364f40e913c530d8121760b4908eb533..1cb76edbb3bb51b4ce799d2919251d355b997cbf 100644
--- a/src/knot/nameserver/nsec_proofs.c
+++ b/src/knot/nameserver/nsec_proofs.c
@@ -107,7 +107,7 @@ static int ns_put_covering_nsec3(const zone_contents_t *zone,
 	}
 
 dbg_ns_exec_verb(
-	char *name = knot_dname_to_str(prev->owner);
+	char *name = knot_dname_to_str_alloc(prev->owner);
 	dbg_ns_verb("Covering NSEC3 node: %s\n", name);
 	free(name);
 );
@@ -155,7 +155,7 @@ static int ns_put_nsec3_closest_encloser_proof(
 
 	if (zone_contents_nsec3params(zone) == NULL) {
 dbg_ns_exec_verb(
-		char *name = knot_dname_to_str(zone->apex->owner);
+		char *name = knot_dname_to_str_alloc(zone->apex->owner);
 		dbg_ns_verb("No NSEC3PARAM found in zone %s.\n", name);
 		free(name);
 );
@@ -163,7 +163,7 @@ dbg_ns_exec_verb(
 	}
 
 dbg_ns_exec_detail(
-	char *name = knot_dname_to_str((*closest_encloser)->owner);
+	char *name = knot_dname_to_str_alloc((*closest_encloser)->owner);
 	dbg_ns_detail("Closest encloser: %s\n", name);
 	free(name);
 );
@@ -186,14 +186,14 @@ dbg_ns_exec_detail(
 	assert(nsec3_node != NULL);
 
 dbg_ns_exec_verb(
-	char *name = knot_dname_to_str(nsec3_node->owner);
+	char *name = knot_dname_to_str_alloc(nsec3_node->owner);
 	dbg_ns_verb("NSEC3 node: %s\n", name);
 	free(name);
-	name = knot_dname_to_str((*closest_encloser)->owner);
+	name = knot_dname_to_str_alloc((*closest_encloser)->owner);
 	dbg_ns_verb("Closest provable encloser: %s\n", name);
 	free(name);
 	if (next_closer != NULL) {
-		name = knot_dname_to_str(next_closer);
+		name = knot_dname_to_str_alloc(next_closer);
 		dbg_ns_verb("Next closer name: %s\n", name);
 		free(name);
 	} else {
@@ -218,7 +218,7 @@ dbg_ns_exec_verb(
 			return KNOT_ERROR; /*servfail */
 		}
 dbg_ns_exec_verb(
-		char *name = knot_dname_to_str(new_next_closer);
+		char *name = knot_dname_to_str_alloc(new_next_closer);
 		dbg_ns_verb("Next closer name: %s\n", name);
 		free(name);
 );
@@ -244,7 +244,7 @@ static knot_dname_t *ns_wildcard_child_name(const knot_dname_t *name)
 {
 	assert(name != NULL);
 
-	knot_dname_t *wildcard = knot_dname_from_str("*");
+	knot_dname_t *wildcard = knot_dname_from_str_alloc("*");
 	if (wildcard == NULL) {
 		return NULL;
 	}
@@ -254,7 +254,7 @@ static knot_dname_t *ns_wildcard_child_name(const knot_dname_t *name)
 		return NULL;
 
 dbg_ns_exec_verb(
-	char *name = knot_dname_to_str(wildcard);
+	char *name = knot_dname_to_str_alloc(wildcard);
 	dbg_ns_verb("Wildcard: %s\n", name);
 	free(name);
 );
@@ -386,7 +386,7 @@ static int ns_put_nsec3_wildcard(const zone_contents_t *zone,
 		return KNOT_ERROR; /* servfail */
 	}
 dbg_ns_exec_verb(
-	char *name = knot_dname_to_str(next_closer);
+	char *name = knot_dname_to_str_alloc(next_closer);
 	dbg_ns_verb("Next closer name: %s\n", name);
 	free(name);
 );
@@ -480,7 +480,7 @@ static int ns_put_nsec_nxdomain(const knot_dname_t *qname,
 	}
 
 dbg_ns_exec_verb(
-	char *name = knot_dname_to_str(previous->owner);
+	char *name = knot_dname_to_str_alloc(previous->owner);
 	dbg_ns_verb("Previous node: %s\n", name);
 	free(name);
 );
@@ -517,7 +517,7 @@ dbg_ns_exec_verb(
 
 	while (knot_dname_cmp(prev_new->owner, wildcard) > 0) {
 dbg_ns_exec_verb(
-		char *name = knot_dname_to_str(prev_new->owner);
+		char *name = knot_dname_to_str_alloc(prev_new->owner);
 		dbg_ns_verb("Previous node: %s\n", name);
 		free(name);
 );
@@ -527,7 +527,7 @@ dbg_ns_exec_verb(
 	assert(knot_dname_cmp(prev_new->owner, wildcard) < 0);
 
 dbg_ns_exec_verb(
-	char *name = knot_dname_to_str(prev_new->owner);
+	char *name = knot_dname_to_str_alloc(prev_new->owner);
 	dbg_ns_verb("Previous node: %s\n", name);
 	free(name);
 );
diff --git a/src/knot/nameserver/update.c b/src/knot/nameserver/update.c
index b8212a8bf8ad792fadc583d84304ce2f1663389d..44f8acec7ea5aad74c8b71177b3c974f5ef99fde 100644
--- a/src/knot/nameserver/update.c
+++ b/src/knot/nameserver/update.c
@@ -26,7 +26,7 @@
 #include "libknot/descriptor.h"
 #include "libknot/tsig-op.h"
 #include "knot/zone/zone.h"
-#include "knot/zone/events.h"
+#include "knot/zone/events/events.h"
 #include "knot/server/tcp-handler.h"
 #include "knot/server/udp-handler.h"
 #include "knot/nameserver/requestor.h"
diff --git a/src/knot/server/server.h b/src/knot/server/server.h
index f151e55fbda0e9f7e4670213977b8cb73b3ed323..342666fc491840d836d7a32b78a944c7be819dba 100644
--- a/src/knot/server/server.h
+++ b/src/knot/server/server.h
@@ -21,13 +21,7 @@
  * \brief Core server functions.
  *
  * Contains the main high-level server structure (server_t) and interface
- * to functions taking care of proper initialization of the server and clean-up
- * when terminated.
- *
- * As of now, the server supports only one zone file and only in a special
- * format.
- *
- * \see zone-parser.h
+ * to functions taking care of initialization of the server and clean-up.
  *
  * \addtogroup server
  * @{
@@ -59,11 +53,6 @@ typedef struct iohandler {
 	unsigned           *thread_id; /*!< Thread identifier. */
 } iohandler_t;
 
-/*! \brief Round-robin mechanism of switching.
-  */
-#define get_next_rr(current, count) \
-	(((current) + 1) % (count))
-
 /*! \brief Server state flags.
  */
 typedef enum {
diff --git a/src/knot/server/udp-handler.c b/src/knot/server/udp-handler.c
index a7a18251a64797d494e3d3372b24c11682718a10..9f1da742f29f047f316b2ba62a8f28b545e2043b 100644
--- a/src/knot/server/udp-handler.c
+++ b/src/knot/server/udp-handler.c
@@ -489,10 +489,8 @@ int udp_master(dthread_t *thread)
 {
 	unsigned cpu = dt_online_cpus();
 	if (cpu > 1) {
-		unsigned cpu_mask[2];
-		cpu_mask[0] = dt_get_id(thread) % cpu;
-		cpu_mask[1] = (cpu_mask[0] + 2) % cpu;
-		dt_setaffinity(thread, cpu_mask, 2);
+		unsigned cpu_mask = (dt_get_id(thread) % cpu);
+		dt_setaffinity(thread, &cpu_mask, 1);
 	}
 
 	/* Drop all capabilities on all workers. */
diff --git a/src/knot/updates/ddns.c b/src/knot/updates/ddns.c
index 9a4158ade629db75a427d4be0928f39e36b77930..494d00bbf6c61be155d381c6928b1e7f7f2f9fe7 100644
--- a/src/knot/updates/ddns.c
+++ b/src/knot/updates/ddns.c
@@ -436,7 +436,7 @@ static bool singleton_replaced(changeset_t *changeset,
 	if (!should_replace(rr)) {
 		return false;
 	}
-	
+
 	zone_node_t *n = zone_contents_find_node_for_rr(changeset->add, rr);
 	if (n == NULL) {
 		return false;
@@ -446,12 +446,12 @@ static bool singleton_replaced(changeset_t *changeset,
 	if (rrs == NULL) {
 		return false;
 	}
-	
+
 	// Replace singleton RR.
 	knot_rdataset_clear(rrs, NULL);
 	node_remove_rdataset(n, rr->type);
 	node_add_rrset(n, rr, NULL);
-	
+
 	return true;
 }
 
@@ -544,7 +544,7 @@ static int process_add_nsec3param(const zone_node_t *node,
 {
 	if (node == NULL || !node_rrtype_exists(node, KNOT_RRTYPE_SOA)) {
 		// Ignore non-apex additions
-		char *owner = knot_dname_to_str(rr->owner);
+		char *owner = knot_dname_to_str_alloc(rr->owner);
 		log_warning("DDNS, refusing to add NSEC3PARAM to non-apex "
 		            "node '%s'", owner);
 		free(owner);
@@ -555,7 +555,7 @@ static int process_add_nsec3param(const zone_node_t *node,
 		return add_rr_to_chgset(rr, changeset, NULL);
 	}
 
-	char *owner = knot_dname_to_str(rr->owner);
+	char *owner = knot_dname_to_str_alloc(rr->owner);
 	log_warning("DDNS, refusing to add second NSEC3PARAM to node '%s'", owner);
 	free(owner);
 
@@ -918,7 +918,7 @@ int ddns_process_update(const zone_t *zone, const knot_pkt_t *query,
 		}
 		return KNOT_EINVAL;
 	}
-	
+
 	changeset_t *changeset = update->change;
 
 	if (changeset->soa_from == NULL) {
diff --git a/src/knot/zone/contents.c b/src/knot/zone/contents.c
index 81d92fc1f69ff825ea253e59506170b604b502d4..6d30d8072e1a73bb8fcf872a48dc58c6ae5ab797 100644
--- a/src/knot/zone/contents.c
+++ b/src/knot/zone/contents.c
@@ -744,7 +744,7 @@ int zone_contents_remove_node(zone_contents_t *contents, const knot_dname_t *own
 	}
 
 dbg_zone_exec_verb(
-	char *name = knot_dname_to_str(owner);
+	char *name = knot_dname_to_str_alloc(owner);
 	dbg_zone_verb("Removing zone node: %s\n", name);
 	free(name);
 );
@@ -801,8 +801,8 @@ int zone_contents_find_dname(const zone_contents_t *zone,
 	}
 
 dbg_zone_exec_verb(
-	char *name_str = knot_dname_to_str(name);
-	char *zone_str = knot_dname_to_str(zone->apex->owner);
+	char *name_str = knot_dname_to_str_alloc(name);
+	char *zone_str = knot_dname_to_str_alloc(zone->apex->owner);
 	dbg_zone_verb("Searching for name %s in zone %s...\n",
 		      name_str, zone_str);
 	free(name_str);
@@ -818,10 +818,10 @@ dbg_zone_exec_verb(
 	*previous = prev;
 
 dbg_zone_exec_detail(
-	char *name_str = (*node) ? knot_dname_to_str((*node)->owner)
+	char *name_str = (*node) ? knot_dname_to_str_alloc((*node)->owner)
 				 : "(nil)";
 	char *name_str2 = (*previous != NULL)
-			  ? knot_dname_to_str((*previous)->owner)
+			  ? knot_dname_to_str_alloc((*previous)->owner)
 			  : "(nil)";
 dbg_zone_detail("Search function returned %d, node %s (%p) and prev: %s (%p)\n",
 			exact_match, name_str, *node, name_str2, *previous);
@@ -933,7 +933,7 @@ int zone_contents_find_nsec3_for_name(const zone_contents_t *zone,
 	}
 
 dbg_zone_exec_verb(
-	char *n = knot_dname_to_str(nsec3_name);
+	char *n = knot_dname_to_str_alloc(nsec3_name);
 	dbg_zone_verb("NSEC3 node name: %s.\n", n);
 	free(n);
 );
@@ -949,7 +949,7 @@ dbg_zone_exec_verb(
 
 dbg_zone_exec_detail(
 	if (found) {
-		char *n = knot_dname_to_str(found->owner);
+		char *n = knot_dname_to_str_alloc(found->owner);
 		dbg_zone_detail("Found NSEC3 node: %s.\n", n);
 		free(n);
 	} else {
@@ -958,7 +958,7 @@ dbg_zone_exec_detail(
 
 	if (prev) {
 		assert(prev->owner);
-		char *n = knot_dname_to_str(prev->owner);
+		char *n = knot_dname_to_str_alloc(prev->owner);
 		dbg_zone_detail("Found previous NSEC3 node: %s.\n", n);
 		free(n);
 	} else {
diff --git a/src/knot/zone/events/events.c b/src/knot/zone/events/events.c
new file mode 100644
index 0000000000000000000000000000000000000000..0a6821002a72ed66a9991c39b4db7979b4c72423
--- /dev/null
+++ b/src/knot/zone/events/events.c
@@ -0,0 +1,411 @@
+/*  Copyright (C) 2014 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+    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 <http://www.gnu.org/licenses/>.
+*/
+
+#include <assert.h>
+#include <time.h>
+
+#include "common-knot/evsched.h"
+#include "knot/server/server.h"
+#include "knot/worker/pool.h"
+#include "knot/zone/zone.h"
+#include "knot/zone/events/events.h"
+#include "knot/zone/events/handlers.h"
+#include "knot/zone/events/replan.h"
+
+/* ------------------------- internal timers -------------------------------- */
+
+#define ZONE_EVENT_IMMEDIATE 1 /* Fast-track to worker queue. */
+
+/* -- internal API ---------------------------------------------------------- */
+
+typedef int (*zone_event_cb)(zone_t *zone);
+
+typedef struct event_info_t {
+	zone_event_type_t type;
+	const zone_event_cb callback;
+	const char *name;
+} event_info_t;
+
+static const event_info_t EVENT_INFO[] = {
+        { ZONE_EVENT_RELOAD,  event_reload,  "reload" },
+        { ZONE_EVENT_REFRESH, event_refresh, "refresh" },
+        { ZONE_EVENT_XFER,    event_xfer,    "transfer" },
+        { ZONE_EVENT_UPDATE,  event_update,  "update" },
+        { ZONE_EVENT_EXPIRE,  event_expire,  "expiration" },
+        { ZONE_EVENT_FLUSH,   event_flush,   "journal flush" },
+        { ZONE_EVENT_NOTIFY,  event_notify,  "notify" },
+        { ZONE_EVENT_DNSSEC,  event_dnssec,  "DNSSEC resign" },
+        { 0 }
+};
+
+static const event_info_t *get_event_info(zone_event_type_t type)
+{
+	const event_info_t *info;
+	for (info = EVENT_INFO; info->callback != NULL; info++) {
+		if (info->type == type) {
+			return info;
+		}
+	}
+
+	assert(0);
+	return NULL;
+}
+
+static bool valid_event(zone_event_type_t type)
+{
+	return (type > ZONE_EVENT_INVALID && type < ZONE_EVENT_COUNT);
+}
+
+/*! \brief Return remaining time to planned event (seconds). */
+static time_t time_until(time_t planned)
+{
+	time_t now = time(NULL);
+	return now < planned ? (planned - now) : 0;
+}
+
+/*!
+ * \brief Find next scheduled zone event.
+ *
+ * \param events  Zone events.
+ *
+ * \return Zone event type, or ZONE_EVENT_INVALID if no event is scheduled.
+ */
+static zone_event_type_t get_next_event(zone_events_t *events)
+{
+	if (!events) {
+		return ZONE_EVENT_INVALID;
+	}
+
+	zone_event_type_t next_type = ZONE_EVENT_INVALID;
+	time_t next = 0;
+
+	for (int i = 0; i < ZONE_EVENT_COUNT; i++) {
+		time_t current = events->time[i];
+		if (current == 0) {
+			continue;
+		}
+
+		if (next == 0 || current < next) {
+			next = current;
+			next_type = i;
+		}
+	}
+
+	return next_type;
+}
+
+/*!
+ * \brief Set time of a given event type.
+ */
+static void event_set_time(zone_events_t *events, zone_event_type_t type, time_t time)
+{
+	assert(events);
+	assert(valid_event(type));
+
+	events->time[type] = time;
+}
+
+/*!
+ * \brief Get time of a given event type.
+ */
+static time_t event_get_time(zone_events_t *events, zone_event_type_t type)
+{
+	assert(events);
+	assert(valid_event(type));
+
+	return events->time[type];
+}
+
+/*!
+ * \brief Cancel scheduled item, schedule first enqueued item.
+ *
+ * The events mutex must be locked when calling this function.
+ */
+static void reschedule(zone_events_t *events)
+{
+	assert(events);
+	assert(pthread_mutex_trylock(&events->mx) == EBUSY);
+
+	if (!events->event || events->running || events->frozen) {
+		return;
+	}
+
+	zone_event_type_t type = get_next_event(events);
+	if (!valid_event(type)) {
+		return;
+	}
+
+	time_t diff = time_until(event_get_time(events, type));
+
+	evsched_schedule(events->event, diff * 1000);
+}
+
+/* -- callbacks control ----------------------------------------------------- */
+
+/*!
+ * \brief Zone event wrapper, expected to be called from a worker thread.
+ *
+ * 1. Takes the next planned event.
+ * 2. Resets the event's scheduled time.
+ * 3. Perform the event's callback.
+ * 4. Schedule next event planned event.
+ */
+static void event_wrap(task_t *task)
+{
+	assert(task);
+	assert(task->ctx);
+
+	zone_t *zone = task->ctx;
+	zone_events_t *events = &zone->events;
+
+	pthread_mutex_lock(&events->mx);
+	zone_event_type_t type = get_next_event(events);
+	if (!valid_event(type)) {
+		events->running = false;
+		pthread_mutex_unlock(&events->mx);
+		return;
+	}
+	event_set_time(events, type, 0);
+	pthread_mutex_unlock(&events->mx);
+
+	const event_info_t *info = get_event_info(type);
+	int result = info->callback(zone);
+	if (result != KNOT_EOK) {
+		log_zone_error(zone->name, "zone %s failed (%s)", info->name,
+		               knot_strerror(result));
+	}
+
+	pthread_mutex_lock(&events->mx);
+	events->running = false;
+	reschedule(events);
+	pthread_mutex_unlock(&events->mx);
+}
+
+/*!
+ * \brief Called by scheduler thread if the event occurs.
+ */
+static int event_dispatch(event_t *event)
+{
+	assert(event);
+	assert(event->data);
+
+	zone_events_t *events = event->data;
+
+	pthread_mutex_lock(&events->mx);
+	if (!events->running && !events->frozen) {
+		events->running = true;
+		worker_pool_assign(events->pool, &events->task);
+	}
+	pthread_mutex_unlock(&events->mx);
+
+	return KNOT_EOK;
+}
+
+/* -- public API ------------------------------------------------------------ */
+
+int zone_events_init(zone_t *zone)
+{
+	if (!zone) {
+		return KNOT_EINVAL;
+	}
+
+	zone_events_t *events = &zone->events;
+
+	memset(&zone->events, 0, sizeof(zone->events));
+	pthread_mutex_init(&events->mx, NULL);
+	events->task.ctx = zone;
+	events->task.run = event_wrap;
+
+	return KNOT_EOK;
+}
+
+int zone_events_setup(zone_t *zone, worker_pool_t *workers, evsched_t *scheduler)
+{
+	if (!zone || !workers || !scheduler) {
+		return KNOT_EINVAL;
+	}
+
+	event_t *event;
+	event = evsched_event_create(scheduler, event_dispatch, &zone->events);
+	if (!event) {
+		return KNOT_ENOMEM;
+	}
+
+	zone->events.event = event;
+	zone->events.pool = workers;
+
+	return KNOT_EOK;
+}
+
+void zone_events_deinit(zone_t *zone)
+{
+	if (!zone) {
+		return;
+	}
+
+	evsched_cancel(zone->events.event);
+	evsched_event_free(zone->events.event);
+
+	pthread_mutex_destroy(&zone->events.mx);
+
+	memset(&zone->events, 0, sizeof(zone->events));
+}
+
+void zone_events_schedule_at(zone_t *zone, zone_event_type_t type, time_t time)
+{
+	if (!zone || !valid_event(type)) {
+		return;
+	}
+
+	zone_events_t *events = &zone->events;
+
+	pthread_mutex_lock(&events->mx);
+	event_set_time(events, type, time);
+	reschedule(events);
+	pthread_mutex_unlock(&events->mx);
+}
+
+void zone_events_enqueue(zone_t *zone, zone_event_type_t type)
+{
+	if (!zone || !valid_event(type)) {
+		return;
+	}
+
+	zone_events_t *events = &zone->events;
+
+	pthread_mutex_lock(&events->mx);
+
+	/* Bypass scheduler if no event is running. */
+	if (!events->running && !events->frozen) {
+		events->running = true;
+		event_set_time(events, type, ZONE_EVENT_IMMEDIATE);
+		worker_pool_assign(events->pool, &events->task);
+		pthread_mutex_unlock(&events->mx);
+		return;
+	}
+
+	pthread_mutex_unlock(&events->mx);
+
+	/* Execute as soon as possible. */
+	zone_events_schedule(zone, type, ZONE_EVENT_NOW);
+}
+
+void zone_events_schedule(zone_t *zone, zone_event_type_t type, unsigned dt)
+{
+	time_t abstime = time(NULL) + dt;
+	return zone_events_schedule_at(zone, type, abstime);
+}
+
+void zone_events_cancel(zone_t *zone, zone_event_type_t type)
+{
+	zone_events_schedule_at(zone, type, 0);
+}
+
+void zone_events_freeze(zone_t *zone)
+{
+	if (!zone) {
+		return;
+	}
+
+	zone_events_t *events = &zone->events;
+
+	/* Prevent new events being enqueued. */
+	pthread_mutex_lock(&events->mx);
+	events->frozen = true;
+	pthread_mutex_unlock(&events->mx);
+
+	/* Cancel current event. */
+	evsched_cancel(events->event);
+}
+
+void zone_events_start(zone_t *zone)
+{
+	if (!zone) {
+		return;
+	}
+
+	pthread_mutex_lock(&zone->events.mx);
+	reschedule(&zone->events);
+	pthread_mutex_unlock(&zone->events.mx);
+}
+
+time_t zone_events_get_time(const struct zone_t *zone, zone_event_type_t type)
+{
+	if (zone == NULL) {
+		return KNOT_EINVAL;
+	}
+
+	time_t event_time = KNOT_ENOENT;
+	zone_events_t *events = (zone_events_t *)&zone->events;
+
+	pthread_mutex_lock(&events->mx);
+
+	/* Get next valid event. */
+	if (valid_event(type)) {
+		event_time = event_get_time(events, type);
+	}
+
+	pthread_mutex_unlock(&events->mx);
+
+	return event_time;
+}
+
+const char *zone_events_get_name(zone_event_type_t type)
+{
+	/* Get information about the event and time. */
+	const event_info_t *info = get_event_info(type);
+	if (info == NULL) {
+		return NULL;
+	}
+
+	return info->name;
+}
+
+time_t zone_events_get_next(const struct zone_t *zone, zone_event_type_t *type)
+{
+	if (zone == NULL || type == NULL) {
+		return KNOT_EINVAL;
+	}
+
+	time_t next_time = KNOT_ENOENT;
+	zone_events_t *events = (zone_events_t *)&zone->events;
+
+	pthread_mutex_lock(&events->mx);
+
+	/* Get time of next valid event. */
+	*type = get_next_event(events);
+	if (valid_event(*type)) {
+		next_time = event_get_time(events, *type);
+	} else {
+		*type = ZONE_EVENT_INVALID;
+	}
+
+	pthread_mutex_unlock(&events->mx);
+
+	return next_time;
+}
+
+void zone_events_update(zone_t *zone, const zone_t *old_zone)
+{
+	replan_events(zone, old_zone);
+}
+
+void zone_events_replan_ddns(struct zone_t *zone, const struct zone_t *old_zone)
+{
+	if (old_zone) {
+		replan_update(zone, (zone_t *)old_zone);
+	}
+}
diff --git a/src/knot/zone/events.h b/src/knot/zone/events/events.h
similarity index 100%
rename from src/knot/zone/events.h
rename to src/knot/zone/events/events.h
diff --git a/src/knot/zone/events.c b/src/knot/zone/events/handlers.c
similarity index 54%
rename from src/knot/zone/events.c
rename to src/knot/zone/events/handlers.c
index 0252eeb77ae27fc9c9f936bc31c6ba0ae14abc88..61d45d178d5bcb4a263a30060bf6fe3d2223d206 100644
--- a/src/knot/zone/events.c
+++ b/src/knot/zone/events/handlers.c
@@ -14,26 +14,20 @@
     along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
-#include <assert.h>
-#include <time.h>
-
-#include "common-knot/evsched.h"
+#include "libknot/rrtype/soa.h"
+#include "libknot/dnssec/random.h"
 #include "common-knot/trim.h"
-#include "common/mem.h"
 #include "common/mempool.h"
-#include "knot/server/server.h"
 #include "knot/server/udp-handler.h"
 #include "knot/server/tcp-handler.h"
 #include "knot/updates/changesets.h"
 #include "knot/dnssec/zone-events.h"
-#include "knot/worker/pool.h"
-#include "knot/zone/events.h"
-#include "knot/zone/zone.h"
+#include "knot/zone/timers.h"
 #include "knot/zone/zone-load.h"
 #include "knot/zone/zonefile.h"
+#include "knot/zone/events/events.h"
+#include "knot/zone/events/handlers.h"
 #include "knot/updates/apply.h"
-#include "libknot/rrtype/soa.h"
-#include "libknot/dnssec/random.h"
 #include "knot/nameserver/internet.h"
 #include "knot/nameserver/update.h"
 #include "knot/nameserver/notify.h"
@@ -41,26 +35,9 @@
 #include "knot/nameserver/tsig_ctx.h"
 #include "knot/nameserver/process_answer.h"
 
-/* ------------------------- internal timers -------------------------------- */
-
-#define ZONE_EVENT_IMMEDIATE 1 /* Fast-track to worker queue. */
-
-/* ------------------------- bootstrap timer logic -------------------------- */
-
 #define BOOTSTRAP_RETRY (30) /*!< Interval between AXFR bootstrap retries. */
 #define BOOTSTRAP_MAXTIME (24*60*60) /*!< Maximum AXFR retry cap of 24 hours. */
 
-/*! \brief Progressive bootstrap retry timer. */
-static uint32_t bootstrap_next(uint32_t timer)
-{
-	timer *= 2;
-	timer += knot_random_uint32_t() % BOOTSTRAP_RETRY;
-	if (timer > BOOTSTRAP_MAXTIME) {
-		timer = BOOTSTRAP_MAXTIME;
-	}
-	return timer;
-}
-
 /* ------------------------- zone query requesting -------------------------- */
 
 /*! \brief Zone event logging. */
@@ -229,9 +206,7 @@ static uint32_t soa_graceful_expire(const knot_rdataset_t *soa)
 	return knot_soa_expire(soa) + 2 * conf()->max_conn_idle;
 }
 
-typedef int (*zone_event_cb)(zone_t *zone);
-
-static int event_reload(zone_t *zone)
+int event_reload(zone_t *zone)
 {
 	assert(zone);
 
@@ -300,14 +275,17 @@ static int event_reload(zone_t *zone)
 	uint32_t current_serial = zone_contents_serial(zone->contents);
 	log_zone_info(zone->name, "loaded, serial %u -> %u",
 	              old_serial, current_serial);
-	return KNOT_EOK;
+
+	return write_zone_timers(conf()->timers_db, zone);
 
 fail:
 	zone_contents_deep_free(&contents);
 	return result;
 }
 
-static int event_refresh(zone_t *zone)
+/* -- zone events implementation API ---------------------------------------- */
+
+int event_refresh(zone_t *zone)
 {
 	assert(zone);
 
@@ -343,10 +321,10 @@ static int event_refresh(zone_t *zone)
 		zone_events_cancel(zone, ZONE_EVENT_EXPIRE);
 	}
 
-	return KNOT_EOK;
+	return write_zone_timers(conf()->timers_db, zone);
 }
 
-static int event_xfer(zone_t *zone)
+int event_xfer(zone_t *zone)
 {
 	assert(zone);
 
@@ -391,7 +369,7 @@ static int event_xfer(zone_t *zone)
 	return KNOT_EOK;
 }
 
-static int event_update(zone_t *zone)
+int event_update(zone_t *zone)
 {
 	assert(zone);
 
@@ -416,7 +394,7 @@ static int event_update(zone_t *zone)
 	return KNOT_EOK;
 }
 
-static int event_expire(zone_t *zone)
+int event_expire(zone_t *zone)
 {
 	assert(zone);
 
@@ -436,7 +414,7 @@ static int event_expire(zone_t *zone)
 	return KNOT_EOK;
 }
 
-static int event_flush(zone_t *zone)
+int event_flush(zone_t *zone)
 {
 	assert(zone);
 
@@ -454,7 +432,7 @@ static int event_flush(zone_t *zone)
 	return zone_flush_journal(zone);
 }
 
-static int event_notify(zone_t *zone)
+int event_notify(zone_t *zone)
 {
 	assert(zone);
 
@@ -482,7 +460,7 @@ static int event_notify(zone_t *zone)
 	return KNOT_EOK;
 }
 
-static int event_dnssec(zone_t *zone)
+int event_dnssec(zone_t *zone)
 {
 	assert(zone);
 
@@ -553,497 +531,14 @@ done:
 
 #undef ZONE_QUERY_LOG
 
-/* -- Zone event replanning functions --------------------------------------- */
-
-/*!< \brief Replans event for new zone according to old zone. */
-static void replan_event(zone_t *zone, const zone_t *old_zone, zone_event_type_t e)
-{
-	const time_t event_time = zone_events_get_time(old_zone, e);
-	if (event_time > ZONE_EVENT_NOW) {
-		zone_events_schedule_at(zone, e, event_time);
-	}
-}
-
-/*!< \brief Replans events that are dependent on the SOA record. */
-static void replan_soa_events(zone_t *zone, const zone_t *old_zone)
-{
-	if (!zone_master(zone)) {
-		// Events only valid for slaves.
-		return;
-	}
-
-	if (zone_master(old_zone)) {
-		// Replan SOA events.
-		replan_event(zone, old_zone, ZONE_EVENT_REFRESH);
-		replan_event(zone, old_zone, ZONE_EVENT_EXPIRE);
-	} else {
-		// Plan SOA events anew.
-		if (!zone_contents_is_empty(zone->contents)) {
-			const knot_rdataset_t *soa = node_rdataset(zone->contents->apex,
-			                                           KNOT_RRTYPE_SOA);
-			assert(soa);
-			zone_events_schedule(zone, ZONE_EVENT_REFRESH, knot_soa_refresh(soa));
-		}
-	}
-}
-
-/*!< \brief Replans transfer event. */
-static void replan_xfer(zone_t *zone, const zone_t *old_zone)
-{
-	if (!zone_master(zone)) {
-		// Only valid for slaves.
-		return;
-	}
-
-	if (zone_master(old_zone)) {
-		// Replan the transfer from old zone.
-		replan_event(zone, old_zone, ZONE_EVENT_XFER);
-	} else if (zone_contents_is_empty(zone->contents)) {
-		// Plan transfer anew.
-		zone->bootstrap_retry = bootstrap_next(zone->bootstrap_retry);
-		zone_events_schedule(zone, ZONE_EVENT_XFER, zone->bootstrap_retry);
-	}
-}
-
-/*!< \brief Replans flush event. */
-static void replan_flush(zone_t *zone, const zone_t *old_zone)
-{
-	if (zone->conf->dbsync_timeout <= 0) {
-		// Immediate sync scheduled after events.
-		return;
-	}
-
-	const time_t flush_time = zone_events_get_time(old_zone, ZONE_EVENT_FLUSH);
-	if (flush_time <= ZONE_EVENT_NOW) {
-		// Not scheduled previously.
-		zone_events_schedule(zone, ZONE_EVENT_FLUSH, zone->conf->dbsync_timeout);
-		return;
-	}
-
-	// Pick time to schedule: either reuse or schedule sooner than old event.
-	const time_t schedule_at = MIN(time(NULL) + zone->conf->dbsync_timeout, flush_time);
-	zone_events_schedule_at(zone, ZONE_EVENT_FLUSH, schedule_at);
-}
-
-/*!< \brief Creates new DDNS q in the new zone - q contains references from the old zone. */
-static void duplicate_ddns_q(zone_t *zone, zone_t *old_zone)
-{
-	struct request_data *d, *nxt;
-	WALK_LIST_DELSAFE(d, nxt, old_zone->ddns_queue) {
-		add_tail(&zone->ddns_queue, (node_t *)d);
-	}
-	zone->ddns_queue_size = old_zone->ddns_queue_size;
-
-	// Reset the list, new zone will free the data.
-	init_list(&old_zone->ddns_queue);
-}
-
-/*!< Replans DDNS event. */
-static void replan_update(zone_t *zone, zone_t *old_zone)
-{
-	pthread_mutex_lock(&old_zone->ddns_lock);
-
-	const bool have_updates = old_zone->ddns_queue_size > 0;
-	if (have_updates) {
-		duplicate_ddns_q(zone, (zone_t *)old_zone);
-	}
-	
-	pthread_mutex_unlock(&old_zone->ddns_lock);
-	
-	if (have_updates) {
-		zone_events_schedule(zone, ZONE_EVENT_UPDATE, ZONE_EVENT_NOW);
-	}
-}
-
-/*!< Replans DNSSEC event. Not whole resign needed, \todo #247 */
-static void replan_dnssec(zone_t *zone)
-{
-	if (zone->conf->dnssec_enable) {
-		/* Keys could have changed, force resign. */
-		zone_events_schedule(zone, ZONE_EVENT_DNSSEC, ZONE_EVENT_NOW);
-	}
-}
-
-/* -- internal API --------------------------------------------------------- */
-
-static bool valid_event(zone_event_type_t type)
-{
-	return (type > ZONE_EVENT_INVALID && type < ZONE_EVENT_COUNT);
-}
-
-/*! \brief Return remaining time to planned event (seconds). */
-static time_t time_until(time_t planned)
-{
-	time_t now = time(NULL);
-	return now < planned ? (planned - now) : 0;
-}
-
-/*!
- * \brief Find next scheduled zone event.
- *
- * \param events  Zone events.
- *
- * \return Zone event type, or ZONE_EVENT_INVALID if no event is scheduled.
- */
-static zone_event_type_t get_next_event(zone_events_t *events)
-{
-	if (!events) {
-		return ZONE_EVENT_INVALID;
-	}
-
-	zone_event_type_t next_type = ZONE_EVENT_INVALID;
-	time_t next = 0;
-
-	for (int i = 0; i < ZONE_EVENT_COUNT; i++) {
-		time_t current = events->time[i];
-		if (current == 0) {
-			continue;
-		}
-
-		if (next == 0 || current < next) {
-			next = current;
-			next_type = i;
-		}
-	}
-
-	return next_type;
-}
-
-/*!
- * \brief Set time of a given event type.
- */
-static void event_set_time(zone_events_t *events, zone_event_type_t type, time_t time)
-{
-	assert(events);
-	assert(valid_event(type));
-
-	events->time[type] = time;
-}
-
-/*!
- * \brief Get time of a given event type.
- */
-static time_t event_get_time(zone_events_t *events, zone_event_type_t type)
-{
-	assert(events);
-	assert(valid_event(type));
-
-	return events->time[type];
-}
-
-/*!
- * \brief Cancel scheduled item, schedule first enqueued item.
- *
- * The events mutex must be locked when calling this function.
- */
-static void reschedule(zone_events_t *events)
-{
-	assert(events);
-	assert(pthread_mutex_trylock(&events->mx) == EBUSY);
-
-	if (!events->event || events->running || events->frozen) {
-		return;
-	}
-
-	zone_event_type_t type = get_next_event(events);
-	if (!valid_event(type)) {
-		return;
-	}
-
-	time_t diff = time_until(event_get_time(events, type));
-
-	evsched_schedule(events->event, diff * 1000);
-}
-
-/* -- callbacks control ---------------------------------------------------- */
-
-typedef struct event_info_t {
-	zone_event_type_t type;
-	const zone_event_cb callback;
-	const char *name;
-} event_info_t;
-
-static const event_info_t EVENT_INFO[] = {
-        { ZONE_EVENT_RELOAD,  event_reload,  "reload" },
-        { ZONE_EVENT_REFRESH, event_refresh, "refresh" },
-        { ZONE_EVENT_XFER,    event_xfer,    "transfer" },
-        { ZONE_EVENT_UPDATE,  event_update,  "update" },
-        { ZONE_EVENT_EXPIRE,  event_expire,  "expiration" },
-        { ZONE_EVENT_FLUSH,   event_flush,   "journal flush" },
-        { ZONE_EVENT_NOTIFY,  event_notify,  "notify" },
-        { ZONE_EVENT_DNSSEC,  event_dnssec,  "DNSSEC resign" },
-        { 0 }
-};
-
-static const event_info_t *get_event_info(zone_event_type_t type)
-{
-	const event_info_t *info;
-	for (info = EVENT_INFO; info->callback != NULL; info++) {
-		if (info->type == type) {
-			return info;
-		}
-	}
-
-	assert(0);
-	return NULL;
-}
-
-/*!
- * \brief Zone event wrapper, expected to be called from a worker thread.
- *
- * 1. Takes the next planned event.
- * 2. Resets the event's scheduled time.
- * 3. Perform the event's callback.
- * 4. Schedule next event planned event.
- */
-static void event_wrap(task_t *task)
-{
-	assert(task);
-	assert(task->ctx);
-
-	zone_t *zone = task->ctx;
-	zone_events_t *events = &zone->events;
-
-	pthread_mutex_lock(&events->mx);
-	zone_event_type_t type = get_next_event(events);
-	if (!valid_event(type)) {
-		events->running = false;
-		pthread_mutex_unlock(&events->mx);
-		return;
-	}
-	event_set_time(events, type, 0);
-	pthread_mutex_unlock(&events->mx);
-
-	const event_info_t *info = get_event_info(type);
-	int result = info->callback(zone);
-	if (result != KNOT_EOK) {
-		log_zone_error(zone->name, "zone %s failed (%s)", info->name,
-		               knot_strerror(result));
-	}
-
-	pthread_mutex_lock(&events->mx);
-	events->running = false;
-	reschedule(events);
-	pthread_mutex_unlock(&events->mx);
-}
-
-/*!
- * \brief Called by scheduler thread if the event occurs.
- */
-static int event_dispatch(event_t *event)
-{
-	assert(event);
-	assert(event->data);
-
-	zone_events_t *events = event->data;
-
-	pthread_mutex_lock(&events->mx);
-	if (!events->running && !events->frozen) {
-		events->running = true;
-		worker_pool_assign(events->pool, &events->task);
-	}
-	pthread_mutex_unlock(&events->mx);
-
-	return KNOT_EOK;
-}
-
-/* -- public API ----------------------------------------------------------- */
-
-int zone_events_init(zone_t *zone)
-{
-	if (!zone) {
-		return KNOT_EINVAL;
-	}
-
-	zone_events_t *events = &zone->events;
-
-	memset(&zone->events, 0, sizeof(zone->events));
-	pthread_mutex_init(&events->mx, NULL);
-	events->task.ctx = zone;
-	events->task.run = event_wrap;
-
-	return KNOT_EOK;
-}
-
-int zone_events_setup(zone_t *zone, worker_pool_t *workers, evsched_t *scheduler)
-{
-	if (!zone || !workers || !scheduler) {
-		return KNOT_EINVAL;
-	}
-
-	event_t *event;
-	event = evsched_event_create(scheduler, event_dispatch, &zone->events);
-	if (!event) {
-		return KNOT_ENOMEM;
-	}
-
-	zone->events.event = event;
-	zone->events.pool = workers;
-
-	return KNOT_EOK;
-}
-
-void zone_events_deinit(zone_t *zone)
-{
-	if (!zone) {
-		return;
-	}
-
-	evsched_cancel(zone->events.event);
-	evsched_event_free(zone->events.event);
-
-	pthread_mutex_destroy(&zone->events.mx);
-
-	memset(&zone->events, 0, sizeof(zone->events));
-}
-
-void zone_events_schedule_at(zone_t *zone, zone_event_type_t type, time_t time)
-{
-	if (!zone || !valid_event(type)) {
-		return;
-	}
-
-	zone_events_t *events = &zone->events;
-
-	pthread_mutex_lock(&events->mx);
-	event_set_time(events, type, time);
-	reschedule(events);
-	pthread_mutex_unlock(&events->mx);
-}
-
-void zone_events_enqueue(zone_t *zone, zone_event_type_t type)
-{
-	if (!zone || !valid_event(type)) {
-		return;
-	}
-
-	zone_events_t *events = &zone->events;
-
-	pthread_mutex_lock(&events->mx);
-
-	/* Bypass scheduler if no event is running. */
-	if (!events->running && !events->frozen) {
-		events->running = true;
-		event_set_time(events, type, ZONE_EVENT_IMMEDIATE);
-		worker_pool_assign(events->pool, &events->task);
-		pthread_mutex_unlock(&events->mx);
-		return;
-	}
-
-	pthread_mutex_unlock(&events->mx);
-
-	/* Execute as soon as possible. */
-	zone_events_schedule(zone, type, ZONE_EVENT_NOW);
-}
-
-void zone_events_schedule(zone_t *zone, zone_event_type_t type, unsigned dt)
-{
-	time_t abstime = time(NULL) + dt;
-	return zone_events_schedule_at(zone, type, abstime);
-}
-
-void zone_events_cancel(zone_t *zone, zone_event_type_t type)
-{
-	zone_events_schedule_at(zone, type, 0);
-}
-
-void zone_events_freeze(zone_t *zone)
-{
-	if (!zone) {
-		return;
-	}
-
-	zone_events_t *events = &zone->events;
-
-	/* Prevent new events being enqueued. */
-	pthread_mutex_lock(&events->mx);
-	events->frozen = true;
-	pthread_mutex_unlock(&events->mx);
-
-	/* Cancel current event. */
-	evsched_cancel(events->event);
-}
-
-void zone_events_start(zone_t *zone)
-{
-	if (!zone) {
-		return;
-	}
-
-	pthread_mutex_lock(&zone->events.mx);
-	reschedule(&zone->events);
-	pthread_mutex_unlock(&zone->events.mx);
-}
-
-time_t zone_events_get_time(const struct zone_t *zone, zone_event_type_t type)
-{
-	if (zone == NULL) {
-		return KNOT_EINVAL;
-	}
-
-	time_t event_time = KNOT_ENOENT;
-	zone_events_t *events = (zone_events_t *)&zone->events;
-
-	pthread_mutex_lock(&events->mx);
-
-	/* Get next valid event. */
-	if (valid_event(type)) {
-		event_time = event_get_time(events, type);
-	}
-
-	pthread_mutex_unlock(&events->mx);
-
-	return event_time;
-}
-
-const char *zone_events_get_name(zone_event_type_t type)
-{
-	/* Get information about the event and time. */
-	const event_info_t *info = get_event_info(type);
-	if (info == NULL) {
-		return NULL;
-	}
-
-	return info->name;
-}
-
-time_t zone_events_get_next(const struct zone_t *zone, zone_event_type_t *type)
+/*! \brief Progressive bootstrap retry timer. */
+uint32_t bootstrap_next(uint32_t timer)
 {
-	if (zone == NULL || type == NULL) {
-		return KNOT_EINVAL;
-	}
-
-	time_t next_time = KNOT_ENOENT;
-	zone_events_t *events = (zone_events_t *)&zone->events;
-
-	pthread_mutex_lock(&events->mx);
-
-	/* Get time of next valid event. */
-	*type = get_next_event(events);
-	if (valid_event(*type)) {
-		next_time = event_get_time(events, *type);
-	} else {
-		*type = ZONE_EVENT_INVALID;
+	timer *= 2;
+	timer += knot_random_uint32_t() % BOOTSTRAP_RETRY;
+	if (timer > BOOTSTRAP_MAXTIME) {
+		timer = BOOTSTRAP_MAXTIME;
 	}
-
-	pthread_mutex_unlock(&events->mx);
-
-	return next_time;
-}
-
-void zone_events_update(zone_t *zone, const zone_t *old_zone)
-{
-	replan_soa_events(zone, old_zone);
-	replan_xfer(zone, old_zone);
-	replan_flush(zone, old_zone);
-	replan_event(zone, old_zone, ZONE_EVENT_NOTIFY);
-	replan_update(zone, (zone_t *)old_zone);
-	replan_dnssec(zone);
+	return timer;
 }
 
-void zone_events_replan_ddns(struct zone_t *zone, const struct zone_t *old_zone)
-{
-	if (old_zone) {
-		replan_update(zone, (zone_t *)old_zone);
-	}
-}
diff --git a/src/knot/zone/events/handlers.h b/src/knot/zone/events/handlers.h
new file mode 100644
index 0000000000000000000000000000000000000000..7d9f5f5e6361ba97b4d746cbd735162a0367fbef
--- /dev/null
+++ b/src/knot/zone/events/handlers.h
@@ -0,0 +1,40 @@
+/*  Copyright (C) 2014 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+    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 <http://www.gnu.org/licenses/>.
+*/
+
+#pragma once
+
+#include "knot/zone/events/events.h"
+
+/*! \brief Reloads the potentially changed zone. */
+int event_reload(zone_t *zone);
+/*! \brief Sends a SOA query to master. */
+int event_refresh(zone_t *zone);
+/*! \brief Initiates transfer with master. */
+int event_xfer(zone_t *zone);
+/*! \brief Processes DDNS updates in the zone's DDNS queue. */
+int event_update(zone_t *zone);
+/*! \brief Empties in-memory zone contents. */
+int event_expire(zone_t *zone);
+/*! \brief Flushes zone contents into text file. */
+int event_flush(zone_t *zone);
+/*! \brief Sends notify to slaves. */
+int event_notify(zone_t *zone);
+/*! \brief (re)Signs the zone using its DNSSEC keys. */
+int event_dnssec(zone_t *zone);
+
+/*! \brief Progressive bootstrap retry timer. */
+uint32_t bootstrap_next(uint32_t timer);
+
diff --git a/src/knot/zone/events/replan.c b/src/knot/zone/events/replan.c
new file mode 100644
index 0000000000000000000000000000000000000000..1aecf49d907bb6ff7f95a2fa618ad23595f4cbd2
--- /dev/null
+++ b/src/knot/zone/events/replan.c
@@ -0,0 +1,137 @@
+/*  Copyright (C) 2014 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+    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 <http://www.gnu.org/licenses/>.
+*/
+
+#include "libknot/rrtype/soa.h"
+#include "knot/zone/events/replan.h"
+#include "knot/zone/events/handlers.h"
+#include "knot/zone/zone.h"
+
+/* -- Zone event replanning functions --------------------------------------- */
+
+/*!< \brief Replans event for new zone according to old zone. */
+static void replan_event(zone_t *zone, const zone_t *old_zone, zone_event_type_t e)
+{
+	const time_t event_time = zone_events_get_time(old_zone, e);
+	if (event_time > ZONE_EVENT_NOW) {
+		zone_events_schedule_at(zone, e, event_time);
+	}
+}
+
+/*!< \brief Replans events that are dependent on the SOA record. */
+static void replan_soa_events(zone_t *zone, const zone_t *old_zone)
+{
+	if (!zone_master(zone)) {
+		// Events only valid for slaves.
+		return;
+	}
+
+	if (zone_master(old_zone)) {
+		// Replan SOA events.
+		replan_event(zone, old_zone, ZONE_EVENT_REFRESH);
+		replan_event(zone, old_zone, ZONE_EVENT_EXPIRE);
+	} else {
+		// Plan SOA events anew.
+		if (!zone_contents_is_empty(zone->contents)) {
+			const knot_rdataset_t *soa = node_rdataset(zone->contents->apex,
+			                                           KNOT_RRTYPE_SOA);
+			assert(soa);
+			zone_events_schedule(zone, ZONE_EVENT_REFRESH, knot_soa_refresh(soa));
+		}
+	}
+}
+
+/*!< \brief Replans transfer event. */
+static void replan_xfer(zone_t *zone, const zone_t *old_zone)
+{
+	if (!zone_master(zone)) {
+		// Only valid for slaves.
+		return;
+	}
+
+	if (zone_master(old_zone)) {
+		// Replan the transfer from old zone.
+		replan_event(zone, old_zone, ZONE_EVENT_XFER);
+	} else if (zone_contents_is_empty(zone->contents)) {
+		// Plan transfer anew.
+		zone->bootstrap_retry = bootstrap_next(zone->bootstrap_retry);
+		zone_events_schedule(zone, ZONE_EVENT_XFER, zone->bootstrap_retry);
+	}
+}
+
+/*!< \brief Replans flush event. */
+static void replan_flush(zone_t *zone, const zone_t *old_zone)
+{
+	if (zone->conf->dbsync_timeout <= 0) {
+		// Immediate sync scheduled after events.
+		return;
+	}
+
+	const time_t flush_time = zone_events_get_time(old_zone, ZONE_EVENT_FLUSH);
+	if (flush_time <= ZONE_EVENT_NOW) {
+		// Not scheduled previously.
+		zone_events_schedule(zone, ZONE_EVENT_FLUSH, zone->conf->dbsync_timeout);
+		return;
+	}
+
+	// Pick time to schedule: either reuse or schedule sooner than old event.
+	const time_t schedule_at = MIN(time(NULL) + zone->conf->dbsync_timeout, flush_time);
+	zone_events_schedule_at(zone, ZONE_EVENT_FLUSH, schedule_at);
+}
+
+/*!< \brief Creates new DDNS q in the new zone - q contains references from the old zone. */
+static void duplicate_ddns_q(zone_t *zone, zone_t *old_zone)
+{
+	struct request_data *d, *nxt;
+	WALK_LIST_DELSAFE(d, nxt, old_zone->ddns_queue) {
+		add_tail(&zone->ddns_queue, (node_t *)d);
+	}
+	zone->ddns_queue_size = old_zone->ddns_queue_size;
+
+	// Reset the list, new zone will free the data.
+	init_list(&old_zone->ddns_queue);
+}
+
+/*!< Replans DNSSEC event. Not whole resign needed, \todo #247 */
+static void replan_dnssec(zone_t *zone)
+{
+	if (zone->conf->dnssec_enable) {
+		/* Keys could have changed, force resign. */
+		zone_events_schedule(zone, ZONE_EVENT_DNSSEC, ZONE_EVENT_NOW);
+	}
+}
+
+/*!< Replans DDNS event. */
+void replan_update(zone_t *zone, const zone_t *old_zone)
+{
+	const bool have_updates = old_zone->ddns_queue_size > 0;
+	if (have_updates) {
+		duplicate_ddns_q(zone, (zone_t *)old_zone);
+	}
+
+	if (have_updates) {
+		zone_events_schedule(zone, ZONE_EVENT_UPDATE, ZONE_EVENT_NOW);
+	}
+}
+
+void replan_events(zone_t *zone, const zone_t *old_zone)
+{
+	replan_soa_events(zone, old_zone);
+	replan_xfer(zone, old_zone);
+	replan_flush(zone, old_zone);
+	replan_event(zone, old_zone, ZONE_EVENT_NOTIFY);
+	replan_update(zone, (zone_t *)old_zone);
+	replan_dnssec(zone);
+}
diff --git a/src/knot/zone/events/replan.h b/src/knot/zone/events/replan.h
new file mode 100644
index 0000000000000000000000000000000000000000..b7174cedbd428cd937cb55529279ef9975c23097
--- /dev/null
+++ b/src/knot/zone/events/replan.h
@@ -0,0 +1,26 @@
+/*  Copyright (C) 2014 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+    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 <http://www.gnu.org/licenses/>.
+*/
+
+#pragma once
+
+#include "knot/zone/events/events.h"
+#include "knot/zone/zone.h"
+
+/*! \brief Replans zone's events using old zone. */
+void replan_events(zone_t *zone, const zone_t *old_zone);
+
+/*! \brief Replans zone's DDNS events using old zone's DDNS queue. */
+void replan_update(zone_t *zone, const zone_t *old_zone);
diff --git a/src/knot/zone/semantic-check.c b/src/knot/zone/semantic-check.c
index be0821ae1f3537ae6ba25f257305837d6cc59396..cc06004a502893fba42d7ee6615ac62c80f6802b 100644
--- a/src/knot/zone/semantic-check.c
+++ b/src/knot/zone/semantic-check.c
@@ -181,7 +181,7 @@ static void log_error_from_node(err_handler_t *handler,
 
 	handler->error_count++;
 
-	char *name = knot_dname_to_str(node->owner);
+	char *name = knot_dname_to_str_alloc(node->owner);
 	const char *errmsg = error_messages[-error];
 
 	log_zone_warning(zone_name, "semantic check, node '%s' (%s%s%s)",
diff --git a/src/knot/zone/timers.c b/src/knot/zone/timers.c
new file mode 100644
index 0000000000000000000000000000000000000000..9492eb12065f0748a9f39eb863ef45bd74144e42
--- /dev/null
+++ b/src/knot/zone/timers.c
@@ -0,0 +1,230 @@
+/*  Copyright (C) 2014 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+    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 <http://www.gnu.org/licenses/>.
+ */
+
+#include "libknot/dname.h"
+#include "common/namedb/namedb.h"
+#include "common/namedb/namedb_lmdb.h"
+#include "knot/zone/timers.h"
+#include "knot/zone/zonedb.h"
+
+/* ---- Knot-internal event code to db key lookup tables ------------------ - */
+
+#define PERSISTENT_EVENT_COUNT 3
+
+enum {
+	KEY_REFRESH = 1,
+	KEY_EXPIRE,
+	KEY_FLUSH
+};
+
+// Do not change these mappings if you want backwards compatibility.
+static const uint8_t event_id_to_key[ZONE_EVENT_COUNT] = {
+ [ZONE_EVENT_REFRESH] = KEY_REFRESH,
+ [ZONE_EVENT_EXPIRE] = KEY_EXPIRE,
+ [ZONE_EVENT_FLUSH] = KEY_FLUSH
+};
+
+static const int key_to_event_id[PERSISTENT_EVENT_COUNT + 1] = {
+ [KEY_REFRESH] = ZONE_EVENT_REFRESH,
+ [KEY_EXPIRE] = ZONE_EVENT_EXPIRE,
+ [KEY_FLUSH] = ZONE_EVENT_FLUSH
+};
+
+static bool known_event_key(uint8_t key)
+{
+	return key <= KEY_FLUSH;
+}
+
+#define EVENT_KEY_PAIR_SIZE (sizeof(uint8_t) + sizeof(int64_t))
+
+static bool event_persistent(size_t event)
+{
+	return event_id_to_key[event] != 0;
+}
+
+/*! \brief Stores timers for persistent events. */
+static int store_timers(knot_txn_t *txn, zone_t *zone)
+{
+	// Create key
+	knot_val_t key = { .len = knot_dname_size(zone->name), .data = zone->name };
+	
+	// Create value
+	uint8_t packed_timer[EVENT_KEY_PAIR_SIZE * PERSISTENT_EVENT_COUNT];
+	size_t offset = 0;
+	for (zone_event_type_t event = 0; event < ZONE_EVENT_COUNT; ++event) {
+		if (!event_persistent(event)) {
+			continue;
+		}
+
+		// Write event key and timer to buffer
+		packed_timer[offset] = event_id_to_key[event];
+		offset += 1;
+		knot_wire_write_u64(packed_timer + offset,
+		                    (int64_t)zone_events_get_time(zone, event));
+		offset += sizeof(uint64_t);
+	}
+	knot_val_t val = { .len = sizeof(packed_timer), .data = packed_timer };
+
+	// Store
+	return namedb_lmdb_api()->insert(txn, &key, &val, 0);
+}
+
+/*! \brief Reads timers for persistent events. */
+static int read_timers(knot_txn_t *txn, const zone_t *zone, time_t *timers)
+{
+	const struct namedb_api *db_api = namedb_lmdb_api();
+
+	knot_val_t key = { .len = knot_dname_size(zone->name), .data = zone->name };
+	knot_val_t val;
+	int ret = db_api->find(txn, &key, &val, 0);
+	if (ret != KNOT_EOK) {
+		if (ret == KNOT_ENOENT) {
+			// New zone, no entry in db.
+			memset(timers, 0, ZONE_EVENT_COUNT * sizeof(time_t));
+			return KNOT_EOK;
+		}
+		return ret;
+	}
+
+	// Set unknown/unset event timers to 0.
+	memset(timers, 0, ZONE_EVENT_COUNT * sizeof(time_t));
+
+	const size_t stored_event_count = val.len / EVENT_KEY_PAIR_SIZE;
+	size_t offset = 0;
+	for (size_t i = 0; i < stored_event_count; ++i) {
+		const uint8_t db_key = ((uint8_t *)val.data)[offset];
+		offset += 1;
+		if (known_event_key(db_key)) {
+			const zone_event_type_t event = key_to_event_id[db_key];
+			timers[event] = (time_t)knot_wire_read_u64(val.data + offset);
+		}
+		offset += sizeof(uint64_t);
+	}
+
+	return KNOT_EOK;
+}
+
+/* -------- API ------------------------------------------------------------- */
+
+knot_namedb_t *open_timers_db(const char *storage)
+{
+#ifndef HAVE_LMDB
+	// No-op if we don't have lmdb, all other operations will no-op as well then
+	return NULL;
+#else
+	return namedb_lmdb_api()->init(storage, NULL);
+#endif
+}
+
+void close_timers_db(knot_namedb_t *timer_db)
+{
+	if (timer_db) {
+		namedb_lmdb_api()->deinit(timer_db);
+	}
+}
+
+int read_zone_timers(knot_namedb_t *timer_db, const zone_t *zone, time_t *timers)
+{
+	if (timer_db == NULL) {
+		memset(timers, 0, ZONE_EVENT_COUNT * sizeof(time_t));
+		return KNOT_EOK;
+	}
+
+	const struct namedb_api *db_api = namedb_lmdb_api();
+
+	knot_txn_t txn;
+	int ret = db_api->txn_begin(timer_db, &txn, KNOT_NAMEDB_RDONLY);
+	if (ret != KNOT_EOK) {
+		return ret;
+	}
+
+	ret = read_timers(&txn, zone, timers);
+	db_api->txn_abort(&txn);
+	if (ret != KNOT_EOK) {
+		return ret;
+	}
+
+	return KNOT_EOK;
+}
+
+int write_zone_timers(knot_namedb_t *timer_db, zone_t *zone)
+{
+	if (timer_db == NULL) {
+		return KNOT_EOK;
+	}
+
+	const struct namedb_api *db_api = namedb_lmdb_api();
+
+	knot_txn_t txn;
+	int ret = db_api->txn_begin(timer_db, &txn, 0);
+	if (ret != KNOT_EOK) {
+		return ret;
+	}
+
+	ret = store_timers(&txn, zone);
+	if (ret != KNOT_EOK) {
+		db_api->txn_abort(&txn);
+		return ret;
+	}
+
+	return db_api->txn_commit(&txn);
+}
+
+int sweep_timer_db(knot_namedb_t *timer_db, knot_zonedb_t *zone_db)
+{
+	if (timer_db == NULL) {
+		return KNOT_EOK;
+	}
+	const struct namedb_api *db_api = namedb_lmdb_api();
+
+	knot_txn_t txn;
+	int ret = db_api->txn_begin(timer_db, &txn, KNOT_NAMEDB_SORTED);
+	if (ret != KNOT_EOK) {
+		return ret;
+	}
+
+	if (db_api->count(&txn) == 0) {
+		db_api->txn_abort(&txn);
+		return KNOT_EOK;
+	}
+
+	knot_iter_t *it = db_api->iter_begin(&txn, 0);
+	if (it == NULL) {
+		db_api->txn_abort(&txn);
+		return KNOT_ERROR;
+	}
+
+	while (it) {
+		knot_val_t key;
+		ret = db_api->iter_key(it, &key);
+		if (ret != KNOT_EOK) {
+			db_api->txn_abort(&txn);
+			return ret;
+		}
+		const knot_dname_t *dbkey = (const knot_dname_t *)key.data;
+		if (!knot_zonedb_find(zone_db, dbkey)) {
+			// Delete obsolete timers
+			db_api->del(&txn, &key);
+		}
+
+		it = db_api->iter_next(it);
+	}
+	db_api->iter_finish(it);
+
+	return db_api->txn_commit(&txn);
+}
+
+
diff --git a/src/knot/zone/timers.h b/src/knot/zone/timers.h
new file mode 100644
index 0000000000000000000000000000000000000000..caa2772ac7082302a14091036ecdf73d17598ffa
--- /dev/null
+++ b/src/knot/zone/timers.h
@@ -0,0 +1,72 @@
+/*  Copyright (C) 2014 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+    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 <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "common/namedb/namedb.h"
+#include "knot/zone/zone.h"
+#include "knot/zone/zonedb.h"
+
+/*!
+ * \brief Opens zone timers db. No-op without LMDB support.
+ *
+ * \param storage  Path to storage directory.
+ *
+ * \return Created database.
+ */
+knot_namedb_t *open_timers_db(const char *storage);
+
+/*!
+ * \brief Closes zone timers db.
+ *
+ * \param timer_db  Timer database.
+ */
+void close_timers_db(knot_namedb_t *timer_db);
+
+/*!
+ * \brief Reads zone timers from timers db.
+ *        Currently these events are read (and stored):
+ *          ZONE_EVENT_REFRESH
+ *          ZONE_EVENT_EXPIRE
+ *          ZONE_EVENT_FLUSH
+ *
+ * \param timer_db  Timer database.
+ * \param zone      Zone to read timers for.
+ * \param timers    Output array with timers (size must be ZONE_EVENT_COUNT).
+ *
+ * \return KNOT_E*
+ */
+int read_zone_timers(knot_namedb_t *timer_db, const zone_t *zone, time_t *timers);
+
+/*!
+ * \brief Writes zone timers to timers db.
+ *
+ * \param timer_db  Timer database.
+ * \param zone      Zone to store timers for.
+ *
+ * \return KNOT_E*
+ */
+int write_zone_timers(knot_namedb_t *timer_db, zone_t *zone);
+
+/*!
+ * \brief Removes stale zones info from timers db.
+ *
+ * \param timer_db  Timer database.
+ * \param zone_db   Current zone database.
+ * \return KNOT_EOK or an error
+ */
+int sweep_timer_db(knot_namedb_t *timer_db, knot_zonedb_t *zone_db);
+
diff --git a/src/knot/zone/zone-tree.c b/src/knot/zone/zone-tree.c
index ef601ec07996eaa232737f339bc6d9ce90efef71..370a45d1e89395e7ae827182a4967ee6b070ac91 100644
--- a/src/knot/zone/zone-tree.c
+++ b/src/knot/zone/zone-tree.c
@@ -170,9 +170,9 @@ int zone_tree_get_less_or_equal(zone_tree_t *tree,
 	}
 
 dbg_zone_exec_detail(
-		char *name = knot_dname_to_str(owner);
+		char *name = knot_dname_to_str_alloc(owner);
 		char *name_f = (*found != NULL)
-			? knot_dname_to_str((*found)->owner)
+			? knot_dname_to_str_alloc((*found)->owner)
 			: "none";
 
 		dbg_zone_detail("Searched for owner %s in zone tree.\n",
@@ -214,7 +214,7 @@ zone_node_t *zone_tree_get_next(zone_tree_t *tree,
 		fval = hattrie_iter_val(it);
 		hattrie_iter_free(it);
 	}
-	
+
 	n = (zone_node_t *)*fval;
 	/* Next node must be non-empty and auth. */
 	if (n->rrset_count == 0 || n->flags & NODE_FLAGS_NONAUTH) {
diff --git a/src/knot/zone/zone.c b/src/knot/zone/zone.c
index 56000527db5e00e25ff0805a20ff4ae322c665b9..e3557ebbba4eef651c8da7f2c4544d8c59e05b6e 100644
--- a/src/knot/zone/zone.c
+++ b/src/knot/zone/zone.c
@@ -60,7 +60,7 @@ zone_t* zone_new(conf_zone_t *conf)
 	}
 	memset(zone, 0, sizeof(zone_t));
 
-	zone->name = knot_dname_from_str(conf->name);
+	zone->name = knot_dname_from_str_alloc(conf->name);
 	knot_dname_to_lower(zone->name);
 	if (zone->name == NULL) {
 		free(zone);
diff --git a/src/knot/zone/zone.h b/src/knot/zone/zone.h
index 50d0a9ccdfbb40d0f049d1a24e0bf235bb815ce3..069f60c8f92f7eb753d5dee3c2128e89f1d0bd8f 100644
--- a/src/knot/zone/zone.h
+++ b/src/knot/zone/zone.h
@@ -33,7 +33,7 @@
 #include "knot/conf/conf.h"
 #include "knot/server/journal.h"
 #include "knot/updates/acl.h"
-#include "knot/zone/events.h"
+#include "knot/zone/events/events.h"
 #include "knot/zone/contents.h"
 #include "libknot/dname.h"
 
diff --git a/src/knot/zone/zonedb-load.c b/src/knot/zone/zonedb-load.c
index 6bb1f6da7eed8a15cc38505d388c9eace6b604cc..97f938edf0d81c074b8c15d80ef10ef07acf3164 100644
--- a/src/knot/zone/zonedb-load.c
+++ b/src/knot/zone/zonedb-load.c
@@ -24,6 +24,7 @@
 #include "knot/zone/zone.h"
 #include "knot/zone/zonefile.h"
 #include "knot/zone/zonedb.h"
+#include "knot/zone/timers.h"
 #include "knot/server/server.h"
 #include "libknot/dname.h"
 
@@ -112,66 +113,153 @@ static void log_zone_load_info(const zone_t *zone, const char *zone_name,
 	log_zone_info(zone->name, "zone %s, serial %u", action, serial);
 }
 
-/*!
- * \brief Load or reload the zone.
- *
- * \param conf      Zone configuration.
- * \param server    Server.
- * \param old_zone  Already loaded zone (can be NULL).
- *
- * \return Error code, KNOT_EOK if successful.
- */
-static zone_t *create_zone(conf_zone_t *conf, server_t *server, zone_t *old_zone)
+static zone_t *create_zone_from(conf_zone_t *zone_conf, server_t *server)
 {
-	assert(conf);
-	assert(server);
-
-	zone_t *zone = zone_new(conf);
+	zone_t *zone = zone_new(zone_conf);
 	if (!zone) {
 		return NULL;
 	}
 
-	if (old_zone) {
-		zone->contents = old_zone->contents;
-	}
-
-	zone_status_t zstatus = zone_file_status(old_zone, conf);
-
 	int result = zone_events_setup(zone, server->workers, &server->sched);
 	if (result != KNOT_EOK) {
 		zone->conf = NULL;
 		zone_free(&zone);
 		return NULL;
 	}
+	
+	return zone;
+}
 
+static zone_t *create_zone_reload(const conf_t *conf, conf_zone_t *zone_conf, server_t *server,
+                                  zone_t *old_zone)
+{
+	zone_t *zone = create_zone_from(zone_conf, server);
+	if (!zone) {
+		return NULL;
+	}
+	zone->contents = old_zone->contents;
+	
+	const zone_status_t zstatus = zone_file_status(old_zone, zone_conf);
+	
 	switch (zstatus) {
-	case ZONE_STATUS_FOUND_NEW:
 	case ZONE_STATUS_FOUND_UPDATED:
 		/* Enqueueing makes the first zone load waitable. */
 		zone_events_enqueue(zone, ZONE_EVENT_RELOAD);
 		/* Replan DDNS processing if there are pending updates. */
 		zone_events_replan_ddns(zone, old_zone);
 		break;
-	case ZONE_STATUS_BOOSTRAP:
-		zone_events_schedule(zone, ZONE_EVENT_REFRESH, ZONE_EVENT_NOW);
-		break;
-	case ZONE_STATUS_NOT_FOUND:
-		break;
 	case ZONE_STATUS_FOUND_CURRENT:
-		assert(old_zone);
 		zone->zonefile_mtime = old_zone->zonefile_mtime;
 		zone->zonefile_serial = old_zone->zonefile_serial;
+		/* Reuse events from old zone. */
 		zone_events_update(zone, old_zone);
+		/* Write updated timers. */
+		write_zone_timers(conf->timers_db, zone);
 		break;
 	default:
 		assert(0);
 	}
+	
+	return zone;
+}
 
-	log_zone_load_info(zone, conf->name, zstatus);
+static bool slave_event(zone_event_type_t event)
+{
+	return event == ZONE_EVENT_EXPIRE || event == ZONE_EVENT_REFRESH;
+}
 
+static void reuse_uvents(zone_t *zone, const time_t *timers)
+{
+	for (zone_event_type_t event = 0; event < ZONE_EVENT_COUNT; ++event) {
+		if (timers[event] == 0) {
+			// Timer unset.
+			continue;
+		}
+		if (slave_event(event) && !zone_master(zone)) {
+			// Slave-only event.
+			continue;
+		}
+		
+		zone_events_schedule_at(zone, event, timers[event]);
+	}
+}
+
+static bool zone_expired(const time_t *timers)
+{
+	const time_t now = time(NULL);
+	return now <= timers[ZONE_EVENT_EXPIRE];
+}
+
+static zone_t *create_zone_new(conf_zone_t *zone_conf, server_t *server)
+{
+	zone_t *zone = create_zone_from(zone_conf, server);
+	if (!zone) {
+		return NULL;
+	}
+	
+	time_t timers[ZONE_EVENT_COUNT];
+	memset(timers, 0, sizeof(timers));
+	
+	// Get persistent timers
+	int ret = read_zone_timers(conf()->timers_db, zone, timers);
+	if (ret != KNOT_EOK) {
+		log_zone_error(zone->name, "cannot read zone timers (%s)",
+		               knot_strerror(ret));
+		zone->conf = NULL;
+		zone_free(&zone);
+		return NULL;
+	}
+	
+	reuse_uvents(zone, timers);
+	
+	const zone_status_t zstatus = zone_file_status(NULL, zone_conf);
+	
+	switch (zstatus) {
+	case ZONE_STATUS_FOUND_NEW:
+		if (!zone_expired(timers)) {
+			/* Enqueueing makes the first zone load waitable. */
+			zone_events_enqueue(zone, ZONE_EVENT_RELOAD);
+		}
+		break;
+	case ZONE_STATUS_BOOSTRAP:
+		if (timers[ZONE_EVENT_REFRESH] == 0) {
+			// Plan immediate refresh if not already planned.
+			zone_events_schedule(zone, ZONE_EVENT_REFRESH, ZONE_EVENT_NOW);
+		}
+		break;
+	case ZONE_STATUS_NOT_FOUND:
+		break;
+	default:
+		assert(0);
+	}
+
+	log_zone_load_info(zone, zone_conf->name, zstatus);
+	
 	return zone;
 }
 
+/*!
+ * \brief Load or reload the zone.
+ *
+ * \param zone_conf  Zone configuration.
+ * \param server     Server.
+ * \param old_zone   Already loaded zone (can be NULL).
+ *
+ * \return Error code, KNOT_EOK if successful.
+ */
+static zone_t *create_zone(const conf_t *conf, conf_zone_t *zone_conf, server_t *server,
+                           zone_t *old_zone)
+{
+	assert(zone_conf);
+	assert(server);
+
+	if (old_zone) {
+		return create_zone_reload(conf, zone_conf, server, old_zone);
+	} else {
+		return create_zone_new(zone_conf, server);
+	}
+}
+
 /*!
  * \brief Create new zone database.
  *
@@ -199,11 +287,11 @@ static knot_zonedb_t *create_zonedb(const conf_t *conf, server_t *server)
 
 		conf_zone_t *zone_config = (conf_zone_t *)*hattrie_iter_val(it);
 
-		knot_dname_t *apex = knot_dname_from_str(zone_config->name);
+		knot_dname_t *apex = knot_dname_from_str_alloc(zone_config->name);
 		zone_t *old_zone = knot_zonedb_find(db_old, apex);
 		knot_dname_free(&apex, NULL);
 
-		zone_t *zone = create_zone(zone_config, server, old_zone);
+		zone_t *zone = create_zone(conf, zone_config, server, old_zone);
 		if (!zone) {
 			log_zone_str_error(zone_config->name,
 					   "zone cannot be created");
@@ -282,6 +370,9 @@ int zonedb_reload(const conf_t *conf, struct server_t *server)
 
 	/* Wait for readers to finish reading old zone database. */
 	synchronize_rcu();
+	
+	/* Sweep the timer database. */
+	sweep_timer_db(conf->timers_db, db_new);
 
 	/*
 	 * Remove all zones present in the new DB from the old DB.
diff --git a/src/knot/zone/zonefile.c b/src/knot/zone/zonefile.c
index 70ee8e988f08e280c1c17c3a0c723ef887553bd6..2f1b24254c19023493ffb6fffd558ae61cdfc99b 100644
--- a/src/knot/zone/zonefile.c
+++ b/src/knot/zone/zonefile.c
@@ -63,7 +63,7 @@ static bool handle_err(zcreator_t *zc, const zone_node_t *node,
                        const knot_rrset_t *rr, int ret, bool master)
 {
 	const knot_dname_t *zname = zc->z->apex->owner;
-	char *rrname = rr ? knot_dname_to_str(rr->owner) : NULL;
+	char *rrname = rr ? knot_dname_to_str_alloc(rr->owner) : NULL;
 	if (ret == KNOT_EOUTOFZONE) {
 		WARNING(zname, "ignoring out-of-zone data, owner '%s'",
 		        rrname ? rrname : "unknown");
@@ -164,7 +164,7 @@ static void scanner_process(zs_scanner_t *scanner)
 	knot_rrset_init(&rr, owner, scanner->r_type, scanner->r_class);
 	int ret = add_rdata_to_rr(&rr, scanner);
 	if (ret != KNOT_EOK) {
-		char *rr_name = knot_dname_to_str(rr.owner);
+		char *rr_name = knot_dname_to_str_alloc(rr.owner);
 		const knot_dname_t *zname = zc->z->apex->owner;
 		ERROR(zname, "failed to add RDATA, file '%s', line %"PRIu64", owner '%s'",
 		      scanner->file.name, scanner->line_counter, rr_name);
@@ -184,7 +184,7 @@ static zone_contents_t *create_zone_from_name(const char *origin)
 	if (origin == NULL) {
 		return NULL;
 	}
-	knot_dname_t *owner = knot_dname_from_str(origin);
+	knot_dname_t *owner = knot_dname_from_str_alloc(origin);
 	if (owner == NULL) {
 		return NULL;
 	}
diff --git a/src/libknot/consts.h b/src/libknot/consts.h
index adaa18c256e5dc68e809770f3df73c5fe0b105cb..1bac3ac783493a98a874ede6d883336e2259fe96 100644
--- a/src/libknot/consts.h
+++ b/src/libknot/consts.h
@@ -34,8 +34,9 @@
 /*!
  * \brief Basic limits for domain names (RFC 1035).
  */
-#define KNOT_DNAME_MAXLEN 255     /*!< 1-byte maximum. */
-#define KNOT_DNAME_MAXLABELS 127  /*!< 1-char labels. */
+#define KNOT_DNAME_MAXLEN      255 /*!< 1-byte maximum. */
+#define KNOT_DNAME_MAXLABELS   127 /*!< 1-char labels. */
+#define KNOT_DNAME_MAXLABELLEN  63 /*!< 2^6 - 1 */
 
 /*!
  * \brief Address family numbers.
diff --git a/src/libknot/dname.c b/src/libknot/dname.c
index e5ad1b8494e5eb17b9880f55fcfe18ab9578b196..31b7a6b88313fbcd1d130547349711f1e79eef20 100644
--- a/src/libknot/dname.c
+++ b/src/libknot/dname.c
@@ -73,8 +73,8 @@ int knot_dname_wire_check(const uint8_t *name, const uint8_t *endp,
 				is_compressed = true;
 			}
 		} else {
-			/* Check label length (maximum 63 bytes allowed). */
-			if (*name > 63)
+			/* Check label length. */
+			if (*name > KNOT_DNAME_MAXLABELLEN)
 				return KNOT_EMALF;
 			/* Check if there's enough space. */
 			int lblen = *name + 1;
@@ -211,17 +211,24 @@ int knot_dname_unpack(uint8_t* dst, const knot_dname_t *src,
 
 /*----------------------------------------------------------------------------*/
 
-char *knot_dname_to_str(const knot_dname_t *name)
+char *knot_dname_to_str(char *dst, const knot_dname_t *name, size_t maxlen)
 {
-	if (name == NULL)
+	if (name == NULL) {
 		return NULL;
+	}
 
-	/*! \todo Supply packet. */
-	/*! \todo Write to static buffer? */
-	// Allocate space for dname string + 1 char termination.
 	int dname_size = knot_dname_size(name);
-	size_t alloc_size = dname_size + 1;
-	char *res = malloc(alloc_size);
+	if (dname_size <= 0) {
+		return NULL;
+	}
+
+	/* Check the size for len(dname) + 1 char termination. */
+	size_t alloc_size = (dst == NULL) ? dname_size + 1 : maxlen;
+	if (alloc_size < dname_size + 1) {
+		return NULL;
+	}
+
+	char *res = (dst == NULL) ? malloc(alloc_size) : dst;
 	if (res == NULL) {
 		return NULL;
 	}
@@ -232,11 +239,11 @@ char *knot_dname_to_str(const knot_dname_t *name)
 	for (uint i = 0; i < dname_size; i++) {
 		uint8_t c = name[i];
 
-		// Read next label size.
+		/* Read next label size. */
 		if (label_len == 0) {
 			label_len = c;
 
-			// Write label separation.
+			/* Write label separation. */
 			if (str_len > 0 || dname_size == 1) {
 				res[str_len++] = '.';
 			}
@@ -253,33 +260,47 @@ char *knot_dname_to_str(const knot_dname_t *name)
 			 * encoded in \ddd notation.
 			 */
 
-			// Increase output size for \x format.
-			alloc_size += 1;
-			char *extended = realloc(res, alloc_size);
-			if (extended == NULL) {
-				free(res);
-				return NULL;
+			if (dst != NULL) {
+				if (maxlen <= str_len + 2) {
+					return NULL;
+				}
+			} else {
+				/* Extend output buffer for \x format. */
+				alloc_size += 1;
+				char *extended = realloc(res, alloc_size);
+				if (extended == NULL) {
+					free(res);
+					return NULL;
+				}
+				res = extended;
 			}
-			res = extended;
 
-			// Write encoded character.
+			/* Write encoded character. */
 			res[str_len++] = '\\';
 			res[str_len++] = c;
 		} else {
-			// Increase output size for \DDD format.
-			alloc_size += 3;
-			char *extended = realloc(res, alloc_size);
-			if (extended == NULL) {
-				free(res);
-				return NULL;
+			if (dst != NULL) {
+				if (maxlen <= str_len + 4) {
+					return NULL;
+				}
+			} else {
+				/* Extend output buffer for \DDD format. */
+				alloc_size += 3;
+				char *extended = realloc(res, alloc_size);
+				if (extended == NULL) {
+					free(res);
+					return NULL;
+				}
+				res = extended;
 			}
-			res = extended;
 
-			// Write encoded character.
+			/* Write encoded character. */
 			int ret = snprintf(res + str_len, alloc_size - str_len,
 			                   "\\%03u", c);
 			if (ret <= 0 || ret >= alloc_size - str_len) {
-				free(res);
+				if (dst == NULL) {
+					free(res);
+				}
 				return NULL;
 			}
 
@@ -289,7 +310,7 @@ char *knot_dname_to_str(const knot_dname_t *name)
 		label_len--;
 	}
 
-	// String_termination.
+	/* String_termination. */
 	res[str_len] = 0;
 
 	return res;
@@ -297,60 +318,143 @@ char *knot_dname_to_str(const knot_dname_t *name)
 
 /*----------------------------------------------------------------------------*/
 
-knot_dname_t *knot_dname_from_str(const char *name)
+knot_dname_t *knot_dname_from_str(uint8_t *dst, const char *name, size_t maxlen)
 {
 	if (name == NULL) {
 		return NULL;
 	}
 
-	unsigned len = strlen(name);
-	if (len > KNOT_DNAME_MAXLEN) {
+	size_t name_len = strlen(name);
+	if (name_len == 0) {
 		return NULL;
 	}
 
-	/* Estimate wire size for special cases. */
-	unsigned wire_size = len + 1;
-	if (name[0] == '.' && len == 1) {
-		wire_size = 1; /* Root label. */
-		len = 0;      /* Do not parse input. */
-	} else if (name[len - 1] != '.') {
-		++wire_size; /* No FQDN, reserve last root label. */
+	/* Wire size estimation. */
+	size_t alloc_size = maxlen;
+	if (dst == NULL) {
+		/* Check for the root label. */
+		if (name[0] == '.') {
+			/* Just the root dname can begin with a dot. */
+			if (name_len > 1) {
+				return NULL;
+			}
+			name_len = 0; /* Skip the following parsing. */
+			alloc_size = 1;
+		} else if (name[name_len - 1] != '.') { /* Check for non-FQDN. */
+			alloc_size = 1 + name_len + 1;
+		} else {
+			alloc_size = 1 + name_len ; /* + 1 ~ first label length. */
+		}
 	}
 
-	/* Create wire. */
-	uint8_t *wire = malloc(wire_size * sizeof(uint8_t));
-	if (wire == NULL)
+	/* The minimal (root) dname takes 1 byte. */
+	if (alloc_size == 0) {
 		return NULL;
-	*wire = '\0';
+	}
+
+	/* Check the maximal wire size. */
+	if (alloc_size > KNOT_DNAME_MAXLEN) {
+		alloc_size = KNOT_DNAME_MAXLEN;
+	}
+
+	/* Prepare output buffer. */
+	uint8_t *wire = (dst == NULL) ? malloc(alloc_size) : dst;
+	if (wire == NULL) {
+		return NULL;
+	}
 
-	/* Parse labels. */
-	const uint8_t *ch = (const uint8_t *)name;
-	const uint8_t *np = ch + len;
 	uint8_t *label = wire;
-	uint8_t *w = wire + 1; /* Reserve 1 for label len */
-	while (ch != np) {
-		if (*ch == '.') {
-			/* Zero-length label inside a dname - invalid. */
-			if (*label == 0) {
-				free(wire);
-				return NULL;
+	uint8_t *wire_pos = wire + 1;
+	uint8_t *wire_end = wire + alloc_size;
+
+	/* Initialize the first label (root label). */
+	*label = 0;
+
+	const uint8_t *ch = (const uint8_t *)name;
+	const uint8_t *end = ch + name_len;
+
+	while (ch < end) {
+		/* Check the output buffer for enough space. */
+		if (wire_pos >= wire_end) {
+			goto dname_from_str_failed;
+		}
+
+		switch (*ch) {
+		case '.':
+			/* Check for invalid zero-length label. */
+			if (*label == 0 && name_len > 1) {
+				goto dname_from_str_failed;
 			}
-			label = w;
-			*label = '\0';
-		} else {
-			*w = *ch;
-			*label += 1;
+			label = wire_pos++;
+			*label = 0;
+			break;
+		case '\\':
+			ch++;
+
+			/* At least one more character is required OR
+			 * check for maximal label length.
+			 */
+			if (ch == end || ++(*label) > KNOT_DNAME_MAXLABELLEN) {
+				goto dname_from_str_failed;
+			}
+
+			/* Check for \DDD notation. */
+			if (isdigit(*ch) != 0) {
+				/* Check for next two digits. */
+				if (ch + 2 >= end ||
+				    isdigit(*(ch + 1)) == 0 ||
+				    isdigit(*(ch + 2)) == 0) {
+					goto dname_from_str_failed;
+				}
+
+				uint32_t num = (*(ch + 0) - '0') * 100 +
+				               (*(ch + 1) - '0') * 10 +
+				               (*(ch + 2) - '0') * 1;
+				if (num > UINT8_MAX) {
+					goto dname_from_str_failed;
+				}
+				*(wire_pos++) = num;
+				ch +=2;
+			} else {
+				*(wire_pos++) = *ch;
+			}
+			break;
+		default:
+			/* Check for maximal label length. */
+			if (++(*label) > KNOT_DNAME_MAXLABELLEN) {
+				goto dname_from_str_failed;
+			}
+			*(wire_pos++) = *ch;
 		}
-		++w;
-		++ch;
+		ch++;
 	}
 
 	/* Check for non-FQDN name. */
 	if (*label > 0) {
-		*w = '\0';
+		if (wire_pos >= wire_end) {
+			goto dname_from_str_failed;
+		}
+		*(wire_pos++) = 0;
+	}
+
+	/* Reduce output buffer if the size is overestimated. */
+	if (wire_pos < wire_end && dst == NULL) {
+		uint8_t *reduced = realloc(wire, wire_pos - wire);
+		if (reduced == NULL) {
+			goto dname_from_str_failed;
+		}
+		wire = reduced;
 	}
 
 	return wire;
+
+dname_from_str_failed:
+
+	if (dst == NULL) {
+		free(wire);
+	}
+
+	return NULL;
 }
 
 /*----------------------------------------------------------------------------*/
diff --git a/src/libknot/dname.h b/src/libknot/dname.h
index 58bfc174e03d347572c1a54cee3d480ed833a746..db7af67c6bd21a6d6191659f81a8aee5b793f543 100644
--- a/src/libknot/dname.h
+++ b/src/libknot/dname.h
@@ -118,31 +118,51 @@ int knot_dname_unpack(uint8_t *dst, const knot_dname_t *src,
                       size_t maxlen, const uint8_t *pkt);
 
 /*!
- * \brief Converts the given domain name to string representation.
+ * \brief Converts the given domain name to its string representation.
  *
- * \note Allocates new memory, remember to free it.
+ * \note Output buffer is allocated automatically if dst is NULL.
  *
- * \todo The function doesn't process escaped characters like \DDD or \X.
+ * \param dst    Output buffer.
+ * \param name   Domain name to be converted.
+ * \param maxlen Output buffer length.
  *
- * \param name Domain name to be converted.
- *
- * \return 0-terminated string representing the given domain name in
- *         presentation format.
+ * \return 0-terminated string if successful, NULL if error.
+ */
+char *knot_dname_to_str(char *dst, const knot_dname_t *name, size_t maxlen);
+
+/*!
+ * \brief This function is a shortcut for \ref knot_dname_to_str with
+ *        no output buffer parameters.
  */
-char *knot_dname_to_str(const knot_dname_t *name);
+static inline char *knot_dname_to_str_alloc(const knot_dname_t *name)
+{
+	return knot_dname_to_str(NULL, name, 0);
+}
 
 /*!
  * \brief Creates a dname structure from domain name given in presentation
  *        format.
  *
- * The resulting FQDN is stored in the wire format.
+ * \note The resulting FQDN is stored in the wire format.
+ * \note Output buffer is allocated automatically if dst is NULL.
  *
- * \param name Domain name in presentation format (labels separated by dots,
- *             '\0' terminated).
+ * \param dst    Output buffer.
+ * \param name   Domain name in presentation format (labels separated by dots,
+ *               '\0' terminated).
+ * \param maxlen Output buffer length.
  *
- * \return New name or NULL
+ * \return New dname if successful, NULL if error.
+ */
+knot_dname_t *knot_dname_from_str(uint8_t *dst, const char *name, size_t maxlen);
+
+/*!
+ * \brief This function is a shortcut for \ref knot_dname_from_str with
+ *        no output buffer parameters.
  */
-knot_dname_t *knot_dname_from_str(const char *name);
+static inline knot_dname_t *knot_dname_from_str_alloc(const char *name)
+{
+	return knot_dname_from_str(NULL, name, 0);
+}
 
 /*!
  * \brief Convert name to lowercase.
diff --git a/src/libknot/dnssec/key.c b/src/libknot/dnssec/key.c
index dc6fff2894e68b6ae1f16264ebcbc85f262c3b56..2b9cce3102257bec449d69ca3f725d334675bc50 100644
--- a/src/libknot/dnssec/key.c
+++ b/src/libknot/dnssec/key.c
@@ -528,7 +528,7 @@ int knot_tsig_create_key(const char *name, int algorithm,
 	}
 
 	knot_dname_t *dname;
-	dname = knot_dname_from_str(name);
+	dname = knot_dname_from_str_alloc(name);
 	if (!dname) {
 		return KNOT_ENOMEM;
 	}
diff --git a/src/libknot/errcode.c b/src/libknot/errcode.c
index 68fd8336bb8bcf86ece5675dea8a541df3a9eecb..a85f6a9d05fdc9fca4f01943abeb14851b24ba16 100644
--- a/src/libknot/errcode.c
+++ b/src/libknot/errcode.c
@@ -39,6 +39,7 @@ const error_table_t error_messages[] = {
 	{ KNOT_EISCONN,      "already connected" },
 	{ KNOT_EADDRINUSE,   "address already in use" },
 	{ KNOT_ENOENT,       "not exists" },
+	{ KNOT_EEXIST,       "already exists" },
 	{ KNOT_ERANGE,       "value is out of range" },
 
 	/* General errors. */
diff --git a/src/libknot/errcode.h b/src/libknot/errcode.h
index c91fa46d91edb30fc4c631d73694e22f05bf5248..88bec58fc42a2ead7846a24488117f5f516d1e11 100644
--- a/src/libknot/errcode.h
+++ b/src/libknot/errcode.h
@@ -55,6 +55,7 @@ enum knot_error {
 	KNOT_EISCONN = knot_errno_to_error(EISCONN),
 	KNOT_EADDRINUSE = knot_errno_to_error(EADDRINUSE),
 	KNOT_ENOENT = knot_errno_to_error(ENOENT),
+	KNOT_EEXIST = knot_errno_to_error(EEXIST),
 	KNOT_ERANGE = knot_errno_to_error(ERANGE),
 
 	/* General errors. */
diff --git a/src/libknot/rrset-dump.c b/src/libknot/rrset-dump.c
index 75f3590e2031d8aa0e6b6f12ca577fa29b4089b2..647ea6b95752cad3054b70abcdb23ce300e7ed0d 100644
--- a/src/libknot/rrset-dump.c
+++ b/src/libknot/rrset-dump.c
@@ -804,16 +804,23 @@ static void wire_dname_to_str(rrset_dump_params_t *p)
 	}
 
 	// Write dname string.
-	char *dname_str = knot_dname_to_str(p->in);
-	if (p->style->ascii_to_idn != NULL) {
+	if (p->style->ascii_to_idn == NULL) {
+		char *dname_str = knot_dname_to_str(p->out, p->in, p->out_max);
+		if (dname_str == NULL) {
+			return;
+		}
+		out_len = strlen(dname_str);
+	} else {
+		char *dname_str = knot_dname_to_str_alloc(p->in);
 		p->style->ascii_to_idn(&dname_str);
+
+		int ret = snprintf(p->out, p->out_max, "%s", dname_str);
+		free(dname_str);
+		if (ret < 0 || (size_t)ret >= p->out_max) {
+			return;
+		}
+		out_len = ret;
 	}
-	int ret = snprintf(p->out, p->out_max, "%s", dname_str);
-	free(dname_str);
-	if (ret < 0 || (size_t)ret >= p->out_max) {
-		return;
-	}
-	out_len = ret;
 
 	// Fill in output.
 	p->in += in_len;
@@ -1831,7 +1838,7 @@ int knot_rrset_txt_dump_header(const knot_rrset_t      *rrset,
 	int    ret;
 
 	// Dump rrset owner.
-	char *name = knot_dname_to_str(rrset->owner);
+	char *name = knot_dname_to_str_alloc(rrset->owner);
 	if (style->ascii_to_idn != NULL) {
 		style->ascii_to_idn(&name);
 	}
diff --git a/src/libknot/rrtype/tsig.c b/src/libknot/rrtype/tsig.c
index 0f8ea5f5f9bb9377adbd542e0ff29a7f0cfec215..ee2b99871f9a5e260025d75b9018adc5be0c4616 100644
--- a/src/libknot/rrtype/tsig.c
+++ b/src/libknot/rrtype/tsig.c
@@ -241,7 +241,7 @@ knot_tsig_algorithm_t tsig_rdata_alg(const knot_rrset_t *tsig)
 	}
 
 	/* Convert alg name to string. */
-	char *name = knot_dname_to_str(alg_name);
+	char *name = knot_dname_to_str_alloc(alg_name);
 	if (!name) {
 		dbg_tsig("TSIG: rdata: cannot convert alg name.\n");
 		return KNOT_TSIG_ALG_NULL;
@@ -336,7 +336,7 @@ int tsig_alg_from_name(const knot_dname_t *alg_name)
 		return 0;
 	}
 
-	char *name = knot_dname_to_str(alg_name);
+	char *name = knot_dname_to_str_alloc(alg_name);
 	if (!name) {
 		return 0;
 	}
diff --git a/src/libknot/tsig-op.c b/src/libknot/tsig-op.c
index a35e0842f604fc1078feb96c0a5a83abdaaab72e..b3a8a81e29fe36bc308794a85362508ba4d0b704 100644
--- a/src/libknot/tsig-op.c
+++ b/src/libknot/tsig-op.c
@@ -69,7 +69,7 @@ static int knot_tsig_check_key(const knot_rrset_t *tsig_rr,
 		return KNOT_EMALF;
 	}
 
-	char *name = knot_dname_to_str(tsig_name);
+	char *name = knot_dname_to_str_alloc(tsig_name);
 	if (!name) {
 		return KNOT_EMALF;
 	}
diff --git a/src/utils/common/exec.c b/src/utils/common/exec.c
index b146a79a8c55e2e43ced029cb2eb7ccd3df1c725..a5f5b2e86002f914b939ea9843b8c4831cb354ac 100644
--- a/src/utils/common/exec.c
+++ b/src/utils/common/exec.c
@@ -382,7 +382,7 @@ static void print_section_host(const knot_rrset_t *rrsets,
 		char                type[32] = "NULL";
 		char                *owner;
 
-		owner = knot_dname_to_str(rrset->owner);
+		owner = knot_dname_to_str_alloc(rrset->owner);
 		if (style->style.ascii_to_idn != NULL) {
 			style->style.ascii_to_idn(&owner);
 		}
@@ -438,7 +438,7 @@ static void print_error_host(const uint16_t    code,
 
 	knot_lookup_table_t *rcode;
 
-	owner = knot_dname_to_str(knot_pkt_qname(packet));
+	owner = knot_dname_to_str_alloc(knot_pkt_qname(packet));
 	if (style->style.ascii_to_idn != NULL) {
 		style->style.ascii_to_idn(&owner);
 	}
@@ -493,7 +493,7 @@ void print_header_xfr(const knot_pkt_t *packet, const style_t  *style)
 	}
 
 	if (style->show_header) {
-		char *owner = knot_dname_to_str(knot_pkt_qname(packet));
+		char *owner = knot_dname_to_str_alloc(knot_pkt_qname(packet));
 		if (style->style.ascii_to_idn != NULL) {
 			style->style.ascii_to_idn(&owner);
 		}
diff --git a/src/utils/common/params.c b/src/utils/common/params.c
index 3e308834cfef4f796da0f4bf6840ea418d73d6b2..0058e3a3aacf3389981050a75fba4e5695d67193 100644
--- a/src/utils/common/params.c
+++ b/src/utils/common/params.c
@@ -434,7 +434,7 @@ int params_parse_tsig(const char *value, knot_key_params_t *key_params)
 	}
 
 	/* Set key name and secret. */
-	key_params->name = knot_dname_from_str(k);
+	key_params->name = knot_dname_from_str_alloc(k);
 	knot_dname_to_lower(key_params->name);
 	int r = knot_binary_from_base64(s, &key_params->secret);
 	if (r != KNOT_EOK) {
diff --git a/src/utils/dig/dig_exec.c b/src/utils/dig/dig_exec.c
index 2dc8d16844213fb38a9a32a1f83e1c7101715635..e5f5f3b32bc69b5f594d2244f9f6108036b64567 100644
--- a/src/utils/dig/dig_exec.c
+++ b/src/utils/dig/dig_exec.c
@@ -315,7 +315,7 @@ static knot_pkt_t* create_query_packet(const query_t *query)
 	}
 
 	// Create QNAME from string.
-	knot_dname_t *qname = knot_dname_from_str(query->owner);
+	knot_dname_t *qname = knot_dname_from_str_alloc(query->owner);
 	if (qname == NULL) {
 		knot_pkt_free(&packet);
 		return NULL;
diff --git a/src/utils/nsec3hash/nsec3hash_main.c b/src/utils/nsec3hash/nsec3hash_main.c
index 8fa87a43c41fc5e71877de628f4622439913f174..5cfb8b938f09fa5df89bdb822774343d5cd446c6 100644
--- a/src/utils/nsec3hash/nsec3hash_main.c
+++ b/src/utils/nsec3hash/nsec3hash_main.c
@@ -150,10 +150,10 @@ int main(int argc, char *argv[])
 			fprintf(stderr, "Cannot transform IDN domain name.\n");
 			goto fail;
 		}
-		dname = knot_dname_from_str(ascii_name);
+		dname = knot_dname_from_str_alloc(ascii_name);
 		free(ascii_name);
 	} else {
-		dname = knot_dname_from_str(argv[4]);
+		dname = knot_dname_from_str_alloc(argv[4]);
 	}
 	if (dname == NULL) {
 		fprintf(stderr, "Cannot parse domain name.\n");
diff --git a/src/utils/nsupdate/nsupdate_exec.c b/src/utils/nsupdate/nsupdate_exec.c
index 8b8cf397ccc047570038799afc68f5a666bc3502..c212d6987a33a08b2341e6c55d023b0b06397b8d 100644
--- a/src/utils/nsupdate/nsupdate_exec.c
+++ b/src/utils/nsupdate/nsupdate_exec.c
@@ -145,7 +145,7 @@ enum {
 
 static bool dname_isvalid(const char *lp)
 {
-	knot_dname_t *dn = knot_dname_from_str(lp);
+	knot_dname_t *dn = knot_dname_from_str_alloc(lp);
 	if (dn == NULL) {
 		return false;
 	}
@@ -187,7 +187,7 @@ static int parse_partial_rr(zs_scanner_t *s, const char *lp, unsigned flags)
 		fqdn = false;
 	}
 
-	knot_dname_t *owner = knot_dname_from_str(owner_str);
+	knot_dname_t *owner = knot_dname_from_str_alloc(owner_str);
 	free(owner_str);
 	if (owner == NULL) {
 		return KNOT_EPARSEFAIL;
@@ -388,7 +388,7 @@ static int build_query(nsupdate_params_t *params)
 	/* Write question. */
 	knot_wire_set_id(query->wire, knot_random_uint16_t());
 	knot_wire_set_opcode(query->wire, KNOT_OPCODE_UPDATE);
-	knot_dname_t *qname = knot_dname_from_str(params->zone);
+	knot_dname_t *qname = knot_dname_from_str_alloc(params->zone);
 	int ret = knot_pkt_put_question(query, qname, params->class_num,
 	                                params->type_num);
 	knot_dname_free(&qname, NULL);
diff --git a/tests-extra/tests/events/soa/data/example.zone b/tests-extra/tests/events/soa/data/example.zone
index 2d90e7dba83477f0dc8281b112994288c36245db..9f024246e9b3b22754d75051df84c9906ef5d311 100644
--- a/tests-extra/tests/events/soa/data/example.zone
+++ b/tests-extra/tests/events/soa/data/example.zone
@@ -1,7 +1,7 @@
 $ORIGIN example.
 $TTL 3600
 
-@	SOA	dns1 hostmaster 1 1 1 1 600
+@	SOA	dns1 hostmaster 1 1 1 2 600
 	NS	dns1
 
 dns1	A	192.0.2.1
diff --git a/tests-extra/tests/events/soa/data/example.zone.1 b/tests-extra/tests/events/soa/data/example.zone.1
index 36a96d8db7677afd6c556b67d8e539a8696620fd..1657faf43b76f4418f96f671629916d913a4cc2d 100644
--- a/tests-extra/tests/events/soa/data/example.zone.1
+++ b/tests-extra/tests/events/soa/data/example.zone.1
@@ -1,7 +1,7 @@
 $ORIGIN example.
 $TTL 3600
 
-@	SOA	dns1 hostmaster 2 1 10 1 600
+@	SOA	dns1 hostmaster 2 1 10 2 600
 	NS	dns1
 
 dns1	A	192.0.2.1
diff --git a/tests-extra/tests/events/soa/test.py b/tests-extra/tests/events/soa/test.py
index d57565b2473fe558e86f35de53260007f2a2c5a0..37e976188c139c47c0bd84562aba8d8ccd965b95 100644
--- a/tests-extra/tests/events/soa/test.py
+++ b/tests-extra/tests/events/soa/test.py
@@ -4,6 +4,7 @@
 
 from dnstest.utils import *
 from dnstest.test import Test
+import random
 
 EXPIRE_SLEEP = 4
 
@@ -21,69 +22,92 @@ def test_expire(slave):
     resp = slave.dig("example.", "SOA")
     resp.check(rcode="SERVFAIL")
 
+def test_run(t, action):
+    master = t.server("bind")
+    master.disable_notify = True
+
+    slave = t.server("knot")
+    slave.disable_notify = True
+    slave.max_conn_idle = "1s"
+
+    # this zone has refresh = 1s, retry = 1s and expire = 1s + 2s for connection timeouts
+    zone = t.zone("example.", storage=".")
+
+    t.link(zone, master, slave)
+    t.start()
+
+    slave.zone_wait(zone)
+    action(t, slave) # action should keep the event intact
+    #test that zone does not expire when master is alive
+    detail_log("Refresh - master alive")
+    test_refresh(slave)
+    master.stop()
+    #test that zone does expire when master is down
+    action(t, slave) # action should keep the event intact
+    detail_log("Refresh - master down")
+    test_expire(slave)
+
+    #update master zone file with 10s retry in SOA
+    master.update_zonefile(zone, version=1)
+    master.start()
+
+    slave.reload() #get new zone file
+    slave.zone_wait(zone)
+    #stop the master and start it again
+    master.stop()
+    t.sleep(EXPIRE_SLEEP)
+    master.start()
+
+    #zone should expire, retry is pending now
+    detail_log("Retry - master dead then alive")
+    resp = slave.dig("example.", "SOA")
+    resp.check(rcode="SERVFAIL")
+
+    #switch server roles, slave becomes master - there should be no expire
+    master.stop()
+    slave.zones = {}
+    master.zones = {}
+    t.link(zone, slave)
+    t.generate_conf()
+    action(t, slave)
+
+    #test that the zone does not expire
+    slave.zone_wait(zone)
+    t.sleep(EXPIRE_SLEEP)
+    detail_log("Expire - roles switch")
+    slave.zone_wait(zone)
+
+    #switch again - zone should expire now
+    slave.zones = {}
+    t.link(zone, master, slave)
+    t.generate_conf()
+    action(t, slave)
+
+    detail_log("Expire - roles switch 2")
+    test_expire(slave)
+
+    t.stop()
+
+def reload_server(t, s):
+    s.reload()
+    t.sleep(1)
+
+def restart_server(t, s):
+    s.stop()
+    s.start()
+
+def reload_or_restart(t, s):
+    if random.choice([True, False]):
+        restart_server(t, s)
+    else:
+        reload_server(t, s)
+
 t = Test()
 
-master = t.server("bind")
-slave = t.server("knot")
-slave.disable_notify = True
-slave.max_conn_idle = "1s"
-
-# this zone has refresh = 1s, retry = 1s and expire = 1s + 2s for connection timeouts
-zone = t.zone("example.", storage=".")
-
-t.link(zone, master, slave)
-
-t.start()
-
-slave.zone_wait(zone)
-slave.reload() # reload should keep the event intact
-#test that zone does not expire when master is alive
-detail_log("Refresh - master alive")
-test_refresh(slave)
-master.stop()
-#test that zone does expire when master is down
-slave.reload() # reload should keep the event intact
-detail_log("Refresh - master alive")
-test_expire(slave)
-
-#update master zone file with 10s retry in SOA
-master.update_zonefile(zone, version=1)
-master.start()
-
-slave.reload() #get new zone file
-slave.zone_wait(zone)
-#stop the master and start it again
-master.stop()
-t.sleep(EXPIRE_SLEEP)
-master.start()
-
-#zone should expire, retry is pending now
-detail_log("Retry - master dead then alive")
-resp = slave.dig("example.", "SOA")
-resp.check(rcode="SERVFAIL")
-
-#switch server roles, slave becomes master - there should be no expire
-master.stop()
-slave.zones = {}
-master.zones = {}
-t.link(zone, slave)
-t.generate_conf()
-slave.reload()
-
-slave.zone_wait(zone)
-t.sleep(EXPIRE_SLEEP)
-detail_log("Expire - roles switch")
-slave.zone_wait(zone)
-
-#switch again - zone should expire now
-slave.zones = {}
-t.link(zone, master, slave)
-t.generate_conf()
-slave.reload()
-t.sleep(1)
-
-detail_log("Expire - roles switch 2")
-test_expire(slave)
-
-t.stop()
+random.seed()
+
+test_run(t, reload_server)
+test_run(t, restart_server)
+test_run(t, reload_or_restart)
+
 
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 74fff3bd17da996c70e0685e3309315c06bc7191..80beba65e8a70804d78b8931fb641b00256d83c4 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -45,7 +45,9 @@ check_PROGRAMS = \
 	worker_pool			\
 	worker_queue			\
 	zone_events			\
-	zone_update
+	zone_update			\
+	zone_timers			\
+	namedb
 
 check-compile-only: $(check_PROGRAMS)
 
diff --git a/tests/changeset.c b/tests/changeset.c
index e76da37344626cc1eeacce2abdc3609df9cda360..b9734dd0802f73c7c93d7493df59731c8ec5edb8 100644
--- a/tests/changeset.c
+++ b/tests/changeset.c
@@ -29,7 +29,7 @@ int main(int argc, char *argv[])
 	ok(changeset_empty(NULL), "changeset: NULL empty");
 
 	// Test creation.
-	knot_dname_t *d = knot_dname_from_str("test.");
+	knot_dname_t *d = knot_dname_from_str_alloc("test.");
 	assert(d);
 	changeset_t *ch = changeset_new(d);
 	knot_dname_free(&d, NULL);
@@ -41,7 +41,7 @@ int main(int argc, char *argv[])
 	ok(changeset_size(ch) == 0, "changeset: empty size");
 
 	// Test additions.
-	d = knot_dname_from_str("non.terminals.test.");
+	d = knot_dname_from_str_alloc("non.terminals.test.");
 	assert(d);
 	knot_rrset_t *apex_txt_rr = knot_rrset_new(d, KNOT_RRTYPE_TXT, KNOT_CLASS_IN, NULL);
 	assert(apex_txt_rr);
@@ -66,7 +66,7 @@ int main(int argc, char *argv[])
 
 	// Add another node.
 	knot_dname_free(&d, NULL);
-	d = knot_dname_from_str("here.come.more.non.terminals.test");
+	d = knot_dname_from_str_alloc("here.come.more.non.terminals.test");
 	assert(d);
 	knot_rrset_t *other_rr = knot_rrset_new(d, KNOT_RRTYPE_TXT, KNOT_CLASS_IN, NULL);
 	assert(other_rr);
@@ -114,14 +114,14 @@ int main(int argc, char *argv[])
 
 	// Create new changeset.
 	knot_dname_free(&d, NULL);
-	d = knot_dname_from_str("test.");
+	d = knot_dname_from_str_alloc("test.");
 	assert(d);
 	changeset_t *ch2 = changeset_new(d);
 	knot_dname_free(&d, NULL);
 	assert(ch2);
 	// Add something to add section.
 	knot_dname_free(&apex_txt_rr->owner, NULL);
-	apex_txt_rr->owner = knot_dname_from_str("something.test.");
+	apex_txt_rr->owner = knot_dname_from_str_alloc("something.test.");
 	assert(apex_txt_rr->owner);
 	ret = changeset_add_rrset(ch2, apex_txt_rr);
 	assert(ret == KNOT_EOK);
@@ -129,7 +129,7 @@ int main(int argc, char *argv[])
 	// Add something to remove section.
 	knot_dname_free(&apex_txt_rr->owner, NULL);
 	apex_txt_rr->owner =
-		knot_dname_from_str("and.now.for.something.completely.different.test.");
+		knot_dname_from_str_alloc("and.now.for.something.completely.different.test.");
 	assert(apex_txt_rr->owner);
 	ret = changeset_rem_rrset(ch2, apex_txt_rr);
 	assert(ret == KNOT_EOK);
diff --git a/tests/conf.c b/tests/conf.c
index 0b25aad27454f5c7145e4cce2892bbe6b46bb5fd..a70a756a677795cdae52cee252194a5826db556f 100644
--- a/tests/conf.c
+++ b/tests/conf.c
@@ -114,7 +114,7 @@ int main(int argc, char *argv[])
 
 	// Test 21: Load key dname
 	const char *sample_str = "key0.example.net";
-	knot_dname_t *sample = knot_dname_from_str(sample_str);
+	knot_dname_t *sample = knot_dname_from_str_alloc(sample_str);
 	if (list_size(&conf->keys) > 0) {
 		knot_tsig_key_t *k = &((conf_key_t *)HEAD(conf->keys))->k;
 		ok(knot_dname_cmp(sample, k->name) == 0,
diff --git a/tests/dname.c b/tests/dname.c
index 4c7158a9c0ddb0cc103231387f1bf435dd9fe457..1b3d6df9b59fc410c3c50b8e8bc098ab3b9101bd 100644
--- a/tests/dname.c
+++ b/tests/dname.c
@@ -14,6 +14,7 @@
     along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
+#include <stdlib.h>
 #include <tap/basic.h>
 
 #include "libknot/dname.h"
@@ -24,113 +25,472 @@ static int test_fw(size_t l, const char *w) {
 	return knot_dname_wire_check((const uint8_t *)w, np, NULL) > 0;
 }
 
+/* Test dname to/from string operations */
+static void test_str(const char *in_str, const char *in_bin, size_t bin_len) {
+	uint8_t      d1[KNOT_DNAME_MAXLEN] = "";
+	char         s1[4 * KNOT_DNAME_MAXLEN] = "";
+	knot_dname_t *d2 = NULL, *aux_d = NULL;
+	char         *s2 = NULL, *aux_s = NULL;
+	int          ret = 0;
+
+	/* dname_from_str */
+	aux_d = knot_dname_from_str(d1, in_str, sizeof(d1));
+	ok(aux_d != NULL, "dname_from_str: %s", in_str);
+	if (aux_d == NULL) {
+		skip_block(10, "dname_from_str: %s", in_str);
+		return;
+	}
+
+	/* dname_wire_check */
+	ret = knot_dname_wire_check(d1, d1 + sizeof(d1), NULL);
+	ok(ret == bin_len, "dname_wire_check: %s", in_str);
+
+	/* dname compare */
+	ok(memcmp(d1, in_bin, bin_len) == 0, "dname compare: %s", in_str);
+
+	/* dname_to_str */
+	aux_s = knot_dname_to_str(s1, d1, sizeof(s1));
+	ok(aux_s != NULL, "dname_to_str: %s", in_str);
+	if (aux_s == NULL) {
+		skip_block(7, "dname_to_str: %s", in_str);
+		return;
+	}
+
+	/* dname_from_str_alloc */
+	d2 = knot_dname_from_str_alloc(s1);
+	ok(d2 != NULL, "dname_from_str_alloc: %s", s1);
+	if (d2 == NULL) {
+		skip_block(6, "dname_from_str_alloc: %s", s1);
+		return;
+	}
+
+	/* dname_wire_check */
+	ret = knot_dname_wire_check(d2, d2 + bin_len, NULL);
+	ok(ret == bin_len, "dname_wire_check: %s", s1);
+
+	/* dname compare */
+	ok(d2 && memcmp(d2, in_bin, bin_len) == 0, "dname compare: %s", s1);
+
+	/* dname_to_str_alloc */
+	s2 = knot_dname_to_str_alloc(d2);
+	knot_dname_free(&d2, NULL);
+	ok(s2 != NULL, "dname_to_str_alloc: %s", s1);
+	if (s2 == NULL) {
+		skip_block(3, "dname_to_str_alloc: %s", s1);
+		return;
+	}
+
+	/* As the string representation is ambiguous, the following steps
+	 * are just for comparison in wire form.
+	 */
+	d2 = knot_dname_from_str_alloc(s2);
+	ok(d2 != NULL, "dname_from_str_alloc: %s", s2);
+	if (aux_d == NULL) {
+		skip_block(2, "dname_from_str_alloc: %s", s2);
+		free(s2);
+		return;
+	}
+
+	/* dname_wire_check */
+	ret = knot_dname_wire_check(d2, d2 + bin_len, NULL);
+	ok(ret == bin_len, "dname_wire_check: %s", s2);
+
+	/* dname compare */
+	ok(d2 && memcmp(d2, in_bin, bin_len) == 0, "dname compare: %s", s2);
+
+	knot_dname_free(&d2, NULL);
+	free(s2);
+}
+
 int main(int argc, char *argv[])
 {
-	plan(29);
- 
+	plan(285);
+
 	knot_dname_t *d = NULL, *d2 = NULL;
 	const char *w = NULL, *t = NULL;
 	unsigned len = 0;
 	size_t pos = 0;
+	char *s = NULL;
+
+	/* DNAME WIRE CHECKS */
 
-	/* 1. NULL wire */
+	/* NULL wire */
 	ok(!test_fw(0, NULL), "parsing NULL dname");
 
-	/* 2. empty label */
+	/* empty label */
 	ok(test_fw(1, ""), "parsing empty dname");
 
-	/* 3. incomplete dname */
-	ok(!test_fw(5, "\x08""dddd"), "parsing incomplete wire");
+	/* incomplete dname */
+	ok(!test_fw(5, "\x08" "dddd"), "parsing incomplete wire");
 
-	/* 4. non-fqdn */
-	ok(!test_fw(3, "\x02""ab"), "parsing non-fqdn name");
+	/* non-fqdn */
+	ok(!test_fw(3, "\x02" "ab"), "parsing non-fqdn name");
 
-	/* 5. label > 63b */
-	w = "\x40""dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd";
-	ok(!test_fw(65, w), "parsing label > 63b");
+	/* label length == 63 */
+	w = "\x3f" "123456789012345678901234567890123456789012345678901234567890123";
+	ok(test_fw(1 + 63 + 1, w), "parsing label length == 63");
 
-	/* 6. label count == 126 */
-	w = "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64";
-	ok(test_fw(253, w), "parsing label count == 127");
+	/* label length > 63 */
+	w = "\x40" "1234567890123456789012345678901234567890123456789012345678901234";
+	ok(!test_fw(1 + 64 + 1, w), "parsing label length > 63");
 
-	/* 7. label count == 127 */
-	w = "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64";
-	ok(test_fw(255, w), "parsing label count == 127");
+	/* label count == 127 (also maximal dname length) */
+	w = "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+	    "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+	    "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+	    "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+	    "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+	    "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+	    "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+	    "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+	    "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+	    "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+	    "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+	    "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+	    "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64";
+	ok(test_fw(127 * 2 + 1, w), "parsing label count == 127");
 
-	/* 8. label count > 127 */
-	w = "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64";
-	ok(!test_fw(257, w), "parsing label count > 127");
+	/* label count > 127 */
+	w = "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+	    "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+	    "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+	    "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+	    "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+	    "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+	    "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+	    "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+	    "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+	    "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+	    "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+	    "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+	    "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64";
+	ok(!test_fw(128 * 2 + 1, w), "parsing label count > 127");
 
-	/* 9. dname length > 255 */
-	w = "\xff""ddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd";
-	ok(!test_fw(257, w), "parsing dname len > 255");
+	/* dname length > 255 */
+	w = "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+	    "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+	    "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+	    "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+	    "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+	    "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+	    "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+	    "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+	    "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+	    "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+	    "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+	    "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+	    "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x02\x64\x64";
+	ok(!test_fw(126 * 2 + 3 + 1, w), "parsing dname len > 255");
 
-	/* 10. special case - invalid label */
-	w = "\x20\x68\x6d\x6e\x63\x62\x67\x61\x61\x61\x61\x65\x72\x6b\x30\x30\x30\x30\x64\x6c\x61\x61\x61\x61\x61\x61\x61\x61\x62\x65\x6a\x61\x6d\x20\x67\x6e\x69\x64\x68\x62\x61\x61\x61\x61\x65\x6c\x64\x30\x30\x30\x30\x64\x6c\x61\x61\x61\x61\x61\x61\x61\x61\x62\x65\x6a\x61\x6d\x20\x61\x63\x6f\x63\x64\x62\x61\x61\x61\x61\x65\x6b\x72\x30\x30\x30\x30\x64\x6c\x61\x61\x61\x61\x61\x61\x61\x61\x62\x65\x6a\x61\x6d\x20\x69\x62\x63\x6d\x6a\x6f\x61\x61\x61\x61\x65\x72\x6a\x30\x30\x30\x30\x64\x6c\x61\x61\x61\x61\x61\x61\x61\x61\x62\x65\x6a\x61\x6d\x20\x6f\x6c\x6e\x6c\x67\x68\x61\x61\x61\x61\x65\x73\x72\x30\x30\x30\x30\x64\x6c\x61\x61\x61\x61\x61\x61\x61\x61\x62\x65\x6a\x61\x6d\x20\x6a\x6b\x64\x66\x66\x67\x61\x61\x61\x61\x65\x6c\x68\x30\x30\x30\x30\x64\x6c\x61\x61\x61\x61\x61\x61\x61\x61\x62\x65\x6a\x61\x6d\x20\x67\x67\x6c\x70\x70\x61\x61\x61\x61\x61\x65\x73\x72\x30\x30\x30\x30\x64\x6c\x61\x61\x61\x61\x61\x61\x61\x61\x62\x65\x6a\x61\x6d\x20\x65\x6b\x6c\x67\x70\x66\x61\x61\x61\x61\x65\x6c\x68\x30\x30\x30\x30\x64\x6c\x61\x61\x61\x61\x61\x0\x21\x42\x63\x84\xa5\xc6\xe7\x8\xa\xd\x11\x73\x3\x6e\x69\x63\x2\x43\x5a";
-	ok(!test_fw(277, w), "parsing invalid label (spec. case 1)");
+	/* DNAME STRING CHECKS */
 
-	/* 11. parse from string (correct) .*/
-	len = 10;
-	w = "\x04""abcd""\x03""efg";
-	t = "abcd.efg";
-	d = knot_dname_from_str(t);
-	ok(d && knot_dname_size(d) == len && memcmp(d, w, len) == 0,
-	   "dname_fromstr: parsed correct non-FQDN name");
-	knot_dname_free(&d, NULL);
+	/* root dname */
+	test_str(".", "\x00", 1);
 
-	/* 12. parse FQDN from string (correct) .*/
-	t = "abcd.efg.";
-	d = knot_dname_from_str(t);
-	ok(d && knot_dname_size(d) == len && memcmp(d, w, len) == 0,
-	   "dname_fromstr: parsed correct FQDN name");
-	knot_dname_free(&d, NULL);
+	/* 1-char dname */
+	test_str("a.", "\x01""a", 2 + 1);
+
+	/* 1-char dname - non-fqdn */
+	test_str("a", "\x01""a", 2 + 1);
+
+	/* wildcard and asterisks */
+	test_str("*.*a.a*a.**.",
+	         "\x01" "*" "\x02" "*a" "\x03" "a*a" "\x02" "**",
+	         2 + 3 + 4 + 3 + 1);
+
+	/* special label */
+	test_str("\\000\\0320\\ \\\\\\\"\\.\\@\\*.",
+	         "\x09" "\x00\x20\x30\x20\x5c\x22.@*",
+	         10 + 1);
+
+	/* unescaped special characters */
+	test_str("_a.b-c./d.",
+	         "\x02" "_a" "\x03" "b-c" "\x02" "/d",
+	         3 + 4 + 3 + 1);
+
+	/* all possible characters */
+	test_str("\\000\\001\\002\\003\\004\\005\\006\\007\\008\\009\\010\\011\\012\\013\\014\\015\\016\\017\\018\\019",
+	         "\x14" "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13",
+	         22);
+	test_str("\\020\\021\\022\\023\\024\\025\\026\\027\\028\\029\\030\\031\\032\\033\\034\\035\\036\\037\\038\\039",
+	         "\x14" "\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27",
+	         22);
+	test_str("\\040\\041\\042\\043\\044\\045\\046\\047\\048\\049\\050\\051\\052\\053\\054\\055\\056\\057\\058\\059",
+	         "\x14" "\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b",
+	         22);
+	test_str("\\060\\061\\062\\063\\064\\065\\066\\067\\068\\069\\070\\071\\072\\073\\074\\075\\076\\077\\078\\079",
+	         "\x14" "\x3c\x3d\x3e\x3f\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f",
+	         22);
+	test_str("\\080\\081\\082\\083\\084\\085\\086\\087\\088\\089\\090\\091\\092\\093\\094\\095\\096\\097\\098\\099",
+	         "\x14" "\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60\x61\x62\x63",
+	         22);
+	test_str("\\100\\101\\102\\103\\104\\105\\106\\107\\108\\109\\110\\111\\112\\113\\114\\115\\116\\117\\118\\119",
+	         "\x14" "\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77",
+	         22);
+	test_str("\\120\\121\\122\\123\\124\\125\\126\\127\\128\\129\\130\\131\\132\\133\\134\\135\\136\\137\\138\\139",
+	         "\x14" "\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b",
+	         22);
+	test_str("\\140\\141\\142\\143\\144\\145\\146\\147\\148\\149\\150\\151\\152\\153\\154\\155\\156\\157\\158\\159",
+	         "\x14" "\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f",
+	         22);
+	test_str("\\160\\161\\162\\163\\164\\165\\166\\167\\168\\169\\170\\171\\172\\173\\174\\175\\176\\177\\178\\179",
+	         "\x14" "\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3",
+	         22);
+	test_str("\\180\\181\\182\\183\\184\\185\\186\\187\\188\\189\\190\\191\\192\\193\\194\\195\\196\\197\\198\\199",
+	         "\x14" "\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7",
+	         22);
+	test_str("\\200\\201\\202\\203\\204\\205\\206\\207\\208\\209\\210\\211\\212\\213\\214\\215\\216\\217\\218\\219",
+	         "\x14" "\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb",
+	         22);
+	test_str("\\220\\221\\222\\223\\224\\225\\226\\227\\228\\229\\230\\231\\232\\233\\234\\235\\236\\237\\238\\239",
+	         "\x14" "\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef",
+	         22);
+	test_str("\\240\\241\\242\\243\\244\\245\\246\\247\\248\\249\\250\\251\\252\\253\\254\\255",
+	         "\x10" "\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff",
+	         18);
+
+	/* maximal dname label length */
+	test_str("12345678901234567890123456789012345678901234567890123456789012\\063",
+		 "\x3f" "12345678901234567890123456789012345678901234567890123456789012?",
+		 65);
+
+	/* maximal dname length */
+	test_str("1234567890123456789012345678901234567890123456789."
+	         "1234567890123456789012345678901234567890123456789."
+	         "1234567890123456789012345678901234567890123456789."
+	         "1234567890123456789012345678901234567890123456789."
+	         "\\#234567890123456789012345678901234567890123456789012\\063",
+	         "\x31" "1234567890123456789012345678901234567890123456789"
+	         "\x31" "1234567890123456789012345678901234567890123456789"
+	         "\x31" "1234567890123456789012345678901234567890123456789"
+	         "\x31" "1234567890123456789012345678901234567890123456789"
+	         "\x35" "#234567890123456789012345678901234567890123456789012?",
+	         255);
+
+	/* NULL output, positive maxlen */
+	w = "\x02" "aa";
+	s = knot_dname_to_str(NULL, (const uint8_t *)w, 1);
+	ok(s != NULL, "dname_to_str: null dname");
+	if (s != NULL) {
+		ok(memcmp(s, "aa.", 4) == 0, "dname_to_str: null dname compare");
+		free(s);
+	} else {
+		skip("dname_to_str: null dname");
+	}
+
+	/* non-NULL output, zero maxlen */
+	char s_small[2];
+	s = knot_dname_to_str(s_small, (const uint8_t *)w, 0);
+	ok(s == NULL, "dname_to_str: non-NULL output, zero maxlen");
+
+	/* small buffer */
+	s = knot_dname_to_str(s_small, (const uint8_t *)w, 1);
+	ok(s == NULL, "dname_to_str: small buffer");
+
+	/* NULL dname */
+	s = knot_dname_to_str_alloc(NULL);
+	ok(s == NULL, "dname_to_str: null dname");
 
-	/* 13. parse name from string (incorrect) .*/
+	/* empty dname is considered as a root dname */
+	w = "";
+	s = knot_dname_to_str_alloc((const uint8_t *)w);
+	ok(s != NULL, "dname_to_str: empty dname");
+	if (s != NULL) {
+		ok(memcmp(s, ".", 1) == 0, "dname_to_str: empty dname is root dname");
+		free(s);
+	} else {
+		skip("dname_to_str: empty dname");
+	}
+
+	/* incomplete dname */
+	w = "\x08" "dddd";
+	s = knot_dname_to_str_alloc((const uint8_t *)w);
+	ok(s != NULL, "dname_to_str: incomplete dname");
+	free(s);
+
+	/* non-fqdn */
+	w = "\x02" "ab";
+	s = knot_dname_to_str_alloc((const uint8_t *)w);
+	ok(s != NULL, "dname_to_str: non-fqdn");
+	free(s);
+
+	/* label length > 63 */
+	w = "\x40" "1234567890123456789012345678901234567890123456789012345678901234";
+	s = knot_dname_to_str_alloc((const uint8_t *)w);
+	ok(s != NULL, "dname_to_str: label length > 63");
+	free(s);
+
+	/* label count > 127 */
+	w = "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+	    "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+	    "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+	    "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+	    "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+	    "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+	    "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+	    "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+	    "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+	    "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+	    "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+	    "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+	    "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64";
+	s = knot_dname_to_str_alloc((const uint8_t *)w);
+	ok(s != NULL, "dname_to_str: label count > 127");
+	free(s);
+
+	/* dname length > 255 */
+	w = "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+	    "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+	    "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+	    "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+	    "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+	    "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+	    "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+	    "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+	    "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+	    "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+	    "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+	    "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+	    "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x02\x64\x64";
+	s = knot_dname_to_str_alloc((const uint8_t *)w);
+	ok(s != NULL, "dname_to_str: dname length > 255");
+	free(s);
+
+	/* NULL output, positive maxlen */
+	s = "aa.";
+	d = knot_dname_from_str(NULL, s, 1);
+	ok(s != NULL, "dname_from_str: null name");
+	if (s != NULL) {
+		ok(memcmp(d, "\x02" "aa", 4) == 0, "dname_from_str: null name compare");
+		free(d);
+	} else {
+		skip("dname_from_str: null name");
+	}
+
+	/* non-NULL output, zero maxlen */
+	uint8_t d_small[2];
+	d = knot_dname_from_str(d_small, s, 0);
+	ok(d == NULL, "dname_from_str: non-NULL output, zero maxlen");
+
+	/* small buffer */
+	d = knot_dname_from_str(d_small, s, 1);
+	ok(d == NULL, "dname_from_str: small buffer");
+
+	/* NULL string */
+	d = knot_dname_from_str_alloc(NULL);
+	ok(d == NULL, "dname_from_str: null string");
+
+	/* empty string */
+	t = "";
+	d = knot_dname_from_str_alloc(t);
+	ok(d == NULL, "dname_from_str: empty string");
+
+	/* empty label */
 	t = "..";
-	d = knot_dname_from_str(t);
-	ok(d == NULL, "dname_fromstr: parsed incorrect name");
+	d = knot_dname_from_str_alloc(t);
+	ok(d == NULL, "dname_from_str: empty label");
+
+	/* leading dot */
+	t = ".a";
+	d = knot_dname_from_str_alloc(t);
+	ok(d == NULL, "dname_from_str: leading dot");
+
+	/* incomplete decimal notation I */
+	t = "\\1";
+	d = knot_dname_from_str_alloc(t);
+	ok(d == NULL, "dname_from_str: incomplete decimal I");
+
+	/* incomplete decimal notation II */
+	t = "\\12";
+	d = knot_dname_from_str_alloc(t);
+	ok(d == NULL, "dname_from_str: incomplete decimal II");
+
+	/* invalid decimal notation I */
+	t = "\\256";
+	d = knot_dname_from_str_alloc(t);
+	ok(d == NULL, "dname_from_str: invalid decimal I");
+
+	/* invalid decimal notation II */
+	t = "\\2x6";
+	d = knot_dname_from_str_alloc(t);
+	ok(d == NULL, "dname_from_str: invalid decimal II");
 
-	/* 14. equal name is subdomain */
+	/* invalid escape notation */
+	t = "\\2";
+	d = knot_dname_from_str_alloc(t);
+	ok(d == NULL, "dname_from_str: invalid escape");
+
+	/* label length > 63 I */
+	t = "1234567890123456789012345678901234567890123456789012345678901234";
+	d = knot_dname_from_str_alloc(t);
+	ok(d == NULL, "dname_from_str: label length > 63 I");
+
+	/* label length > 63 II */
+	t = "123456789012345678901234567890123456789012345678901234567890123\\?";
+	d = knot_dname_from_str_alloc(t);
+	ok(d == NULL, "dname_from_str: label length > 63 II");
+
+	/* label length > 63 III */
+	t = "123456789012345678901234567890123456789012345678901234567890123\\063";
+	d = knot_dname_from_str_alloc(t);
+	ok(d == NULL, "dname_from_str: label length > 63 III");
+
+	/* dname length > 255 */
+	t = "1234567890123456789012345678901234567890123456789."
+	    "1234567890123456789012345678901234567890123456789."
+	    "1234567890123456789012345678901234567890123456789."
+	    "1234567890123456789012345678901234567890123456789."
+	    "123456789012345678901234567890123456789012345678901234.",
+	d = knot_dname_from_str_alloc(t);
+	ok(d == NULL, "dname_from_str: dname length > 255");
+
+	/* DNAME SUBDOMAIN CHECKS */
+
+	/* equal name is subdomain */
 	t = "ab.cd.ef";
-	d2 = knot_dname_from_str(t);
+	d2 = knot_dname_from_str_alloc(t);
 	t = "ab.cd.ef";
-	d = knot_dname_from_str(t);
+	d = knot_dname_from_str_alloc(t);
 	ok(!knot_dname_is_sub(d, d2), "dname_subdomain: equal name");
 	knot_dname_free(&d, NULL);
 
-	/* 15. true subdomain */
+	/* true subdomain */
 	t = "0.ab.cd.ef";
-	d = knot_dname_from_str(t);
+	d = knot_dname_from_str_alloc(t);
 	ok(knot_dname_is_sub(d, d2), "dname_subdomain: true subdomain");
 	knot_dname_free(&d, NULL);
 
-	/* 16. not subdomain */
+	/* not subdomain */
 	t = "cd.ef";
-	d = knot_dname_from_str(t);
+	d = knot_dname_from_str_alloc(t);
 	ok(!knot_dname_is_sub(d, d2), "dname_subdomain: not subdomain");
 	knot_dname_free(&d, NULL);
 
-	/* 17. root subdomain */
+	/* root subdomain */
 	t = ".";
-	d = knot_dname_from_str(t);
+	d = knot_dname_from_str_alloc(t);
 	ok(knot_dname_is_sub(d2, d), "dname_subdomain: root subdomain");
 	knot_dname_free(&d, NULL);
 	knot_dname_free(&d2, NULL);
 
-	/* 18-19. dname cat (valid) */
+	/* DNAME CAT CHECKS */
+
+	/* dname cat (valid) */
 	w = "\x03""cat";
 	d = knot_dname_copy((const uint8_t *)w, NULL);
 	t = "*";
-	d2 = knot_dname_from_str(t);
+	d2 = knot_dname_from_str_alloc(t);
 	d2 = knot_dname_cat(d2, d);
 	t = "\x01""*""\x03""cat";
 	len = 2 + 4 + 1;
-	ok (d2 && len == knot_dname_size(d2), "dname_cat: valid concatenation size");
-	ok(memcmp(d2, t, len) == 0, "dname_cat: valid concatenation");
+	ok(d2 && len == knot_dname_size(d2), "dname_cat: valid concatenation size");
+	ok(d2 && memcmp(d2, t, len) == 0, "dname_cat: valid concatenation");
 	knot_dname_free(&d, NULL);
 	knot_dname_free(&d2, NULL);
 
-	/* 20-21. parse from wire (valid) */
+	/* DNAME PARSE CHECKS */
+
+	/* parse from wire (valid) */
 	t = "\x04""abcd""\x03""efg";
 	len = 10;
 	pos = 0;
@@ -139,7 +499,7 @@ int main(int argc, char *argv[])
 	ok(pos == len, "dname_parse: valid name (parsed length)");
 	knot_dname_free(&d, NULL);
 
-	/* 22-23. parse from wire (invalid) */
+	/* parse from wire (invalid) */
 	t = "\x08""dddd";
 	len = 5;
 	pos = 0;
@@ -147,30 +507,38 @@ int main(int argc, char *argv[])
 	ok(d == NULL, "dname_parse: bad name");
 	ok(pos == 0, "dname_parse: bad name (parsed length)");
 
-	/* name equality checks */
+	/* DNAME EQUALITY CHECKS */
+
 	t = "ab.cd.ef";
-	d = knot_dname_from_str(t);
+	d = knot_dname_from_str_alloc(t);
 	ok(knot_dname_is_equal(d, d), "dname_is_equal: equal names");
+
 	t = "ab.cd.fe";
-	d2 = knot_dname_from_str(t);
+	d2 = knot_dname_from_str_alloc(t);
 	ok(!knot_dname_is_equal(d, d2), "dname_is_equal: same label count");
 	knot_dname_free(&d2, NULL);
+
 	t = "ab.cd";
-	d2 = knot_dname_from_str(t);
+	d2 = knot_dname_from_str_alloc(t);
 	ok(!knot_dname_is_equal(d, d2), "dname_is_equal: len(d1) < len(d2)");
 	knot_dname_free(&d2, NULL);
+
 	t = "ab.cd.ef.gh";
-	d2 = knot_dname_from_str(t);
+	d2 = knot_dname_from_str_alloc(t);
 	ok(!knot_dname_is_equal(d, d2), "dname_is_equal: len(d1) > len(d2)");
 	knot_dname_free(&d2, NULL);
+
 	t = "ab.cd.efe";
-	d2 = knot_dname_from_str(t);
+	d2 = knot_dname_from_str_alloc(t);
 	ok(!knot_dname_is_equal(d, d2), "dname_is_equal: last label longer");
 	knot_dname_free(&d2, NULL);
+
 	t = "ab.cd.e";
-	d2 = knot_dname_from_str(t);
+	d2 = knot_dname_from_str_alloc(t);
 	ok(!knot_dname_is_equal(d, d2), "dname_is_equal: last label shorter");
 	knot_dname_free(&d2, NULL);
 
+	knot_dname_free(&d, NULL);
+
 	return 0;
 }
diff --git a/tests/dnssec_keys.c b/tests/dnssec_keys.c
index 8dbb91924cbea06be3a99694210546cd2ba70b1f..b2b048c65a2f7fb7b36772aec4c4c3501f6b3829 100644
--- a/tests/dnssec_keys.c
+++ b/tests/dnssec_keys.c
@@ -205,7 +205,7 @@ int main(int argc, char *argv[])
 		knot_key_params_t params = { 0 };
 		knot_tsig_key_t tsig_key = { 0 };
 		const char *owner = "shared.example.com.";
-		knot_dname_t *name = knot_dname_from_str(owner);
+		knot_dname_t *name = knot_dname_from_str_alloc(owner);
 
 		result = knot_tsig_key_from_params(&params, &tsig_key);
 		ok(result == KNOT_EINVAL,
diff --git a/tests/dnssec_nsec3.c b/tests/dnssec_nsec3.c
index 17dbb8e8777c853dad27f4c3db851bba1f0f5f6f..4e5dfe4b50b7c3cea4590d93bbbd5533b20eb2fd 100644
--- a/tests/dnssec_nsec3.c
+++ b/tests/dnssec_nsec3.c
@@ -54,7 +54,7 @@ int main(int argc, char *argv[])
 		'a', 'b', 'c', 'd'     // salt
 	};
 
-	knot_dname_t *owner = knot_dname_from_str("test.");
+	knot_dname_t *owner = knot_dname_from_str_alloc("test.");
 	rrset = knot_rrset_new(owner, KNOT_RRTYPE_NSEC3PARAM, KNOT_CLASS_IN, NULL);
 	knot_dname_free(&owner, NULL);
 
@@ -81,7 +81,7 @@ int main(int argc, char *argv[])
 	params.salt = (uint8_t *)strdup("happywithnsec3");
 
 	const char *dname_str = "knot-dns.cz.";
-	knot_dname_t *dname = knot_dname_from_str(dname_str);
+	knot_dname_t *dname = knot_dname_from_str_alloc(dname_str);
 
 	uint8_t expected[] = {
 		0x72, 0x40, 0x55, 0x83, 0x92, 0x93, 0x95, 0x28, 0xee, 0xa2,
diff --git a/tests/dnssec_sign.c b/tests/dnssec_sign.c
index 5941c0ffeceb20072536a4bb3328fb52836b8836..abe6e2f34f93e8fcbefd4a7270e562bb246cf939 100644
--- a/tests/dnssec_sign.c
+++ b/tests/dnssec_sign.c
@@ -93,7 +93,7 @@ int main(int argc, char *argv[])
 
 	// RSA
 
-	kp.name = knot_dname_from_str("example.com.");
+	kp.name = knot_dname_from_str_alloc("example.com.");
 	kp.algorithm = 5;
 	knot_binary_from_base64("pSxiFXG8wB1SSHdok+OdaAp6QdvqjpZ17ucNge21iYVfv+DZq52l21KdmmyEqoG9wG/87O7XG8XVLNyYPue8Mw==", &kp.modulus);
 	knot_binary_from_base64("AQAB", &kp.public_exponent);
@@ -109,7 +109,7 @@ int main(int argc, char *argv[])
 
 	// DSA
 
-	kp.name = knot_dname_from_str("example.com.");
+	kp.name = knot_dname_from_str_alloc("example.com.");
 	kp.algorithm = 6;
 	knot_binary_from_base64("u7tr4jc7CH0+r2muVEZyjYu7hpMrQ1dHGAMv7hr5dBFYzkutfdBmDSW4C+qxaXWo14gi+jJ8XqFqQ7rQn23DdQ==", &kp.prime);
 	knot_binary_from_base64("tgZ5X6pFoCOM2NzfiAYVG1434Mk=", &kp.subprime);
@@ -123,7 +123,7 @@ int main(int argc, char *argv[])
 	// ECDSA
 
 #ifdef KNOT_ENABLE_ECDSA
-	kp.name = knot_dname_from_str("example.com");
+	kp.name = knot_dname_from_str_alloc("example.com");
 	kp.algorithm = 13;
 	knot_binary_from_base64("1N/PvpB8jZcvv+zr3Q987RKK1cBxDKULzEc5F/nnpSg=", &kp.private_key);
 	knot_binary_from_base64("AAAAAH3t6EfkvHK5fQMGslhWcCfMF6Q3oNbol2f19DGAb8r49ZX7iQ12sFIyrs2CiwDxFR9Y7fF2zOZ005VV1LA3m1Q=", &kp.rdata);
@@ -135,7 +135,7 @@ int main(int argc, char *argv[])
 #endif
 
 #if KNOT_ENABLE_GOST
-	kp.name = knot_dname_from_str("example.com");
+	kp.name = knot_dname_from_str_alloc("example.com");
 	kp.algorithm = 12;
 	knot_binary_from_base64("MEUCAQAwHAYGKoUDAgITMBIGByqFAwICIwEGByqFAwICHgEEIgIgN2CMRL538HmFM9+GHYM54rEDYO+tLDV3q7AtK1nZ4iA=", &kp.private_key);
 	knot_binary_from_base64("eHh4eOJ4YHvlasoDRc4ZnvRzldoTUgwWSW0bPv7r9xJ074Dn8KzM4yU9fJgTwIT1TsaHmejYopDnVdjxZyrKNra8Keo=", &kp.rdata);
diff --git a/tests/dnssec_zone_nsec.c b/tests/dnssec_zone_nsec.c
index f3f578c20d4959a89c143c921f8461d23bc3826a..5b2a0750cbfe0d1501ff203c31f19d07972e77fb 100644
--- a/tests/dnssec_zone_nsec.c
+++ b/tests/dnssec_zone_nsec.c
@@ -23,9 +23,9 @@ int main(int argc, char *argv[])
 {
 	plan(1);
 
-	knot_dname_t *owner  = knot_dname_from_str("name.example.com");
-	knot_dname_t *apex   = knot_dname_from_str("example.com");
-	knot_dname_t *expect = knot_dname_from_str("sv9o5lv8kgv6lm1t9dkst43b3c0aagbj.example.com");
+	knot_dname_t *owner  = knot_dname_from_str_alloc("name.example.com");
+	knot_dname_t *apex   = knot_dname_from_str_alloc("example.com");
+	knot_dname_t *expect = knot_dname_from_str_alloc("sv9o5lv8kgv6lm1t9dkst43b3c0aagbj.example.com");
 
 	knot_nsec3_params_t params = {
 		.algorithm = 1, .flags = 0, .iterations = 10,
diff --git a/tests/namedb.c b/tests/namedb.c
new file mode 100644
index 0000000000000000000000000000000000000000..f008a5be924fa17f39ba92aac8eb6d3dcd2d20c2
--- /dev/null
+++ b/tests/namedb.c
@@ -0,0 +1,176 @@
+/*  Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+    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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <string.h>
+#include <time.h>
+#include <tap/basic.h>
+
+#include "libknot/common.h"
+#include "common/mempool.h"
+#include "common/mem.h"
+#include "common/namedb/namedb_lmdb.h"
+#include "common/namedb/namedb_trie.h"
+
+/* Constants. */
+#define KEY_MAXLEN 64
+#define KEY_SET(key, str) key.data = (str); key.len = strlen(str) + 1
+
+/*! \brief Generate random key. */
+static const char *alphabet = "abcdefghijklmn0123456789";
+static char *str_key_rand(size_t len, mm_ctx_t *pool)
+{
+	char *s = mm_alloc(pool, len);
+	memset(s, 0, len);
+	for (unsigned i = 0; i < len - 1; ++i) {
+		s[i] = alphabet[rand() % strlen(alphabet)];
+	}
+	return s;
+}
+
+/* UCW array sorting defines. */
+#define ASORT_PREFIX(X) str_key_##X
+#define ASORT_KEY_TYPE char*
+#define ASORT_LT(x, y) (strcmp((x), (y)) < 0)
+#include "common-knot/array-sort.h"
+
+static void namedb_test_set(unsigned nkeys, char **keys, char *dbid,
+                            struct namedb_api *api, mm_ctx_t *pool)
+{
+	if (api == NULL) {
+		skip("API not compiled in");
+		return;
+	}
+
+	/* Create database */
+	knot_namedb_t *db = api->init(dbid, pool);
+	ok(db != NULL, "%s: create", api->name);
+
+	/* Start WR transaction. */
+	knot_txn_t txn;
+	int ret = api->txn_begin(db, &txn, 0);
+	ok(ret == KNOT_EOK, "%s: txn_begin(WR)", api->name);
+
+	/* Insert keys */
+	knot_val_t key, val;
+	bool passed = true;
+	for (unsigned i = 0; i < nkeys; ++i) {
+		KEY_SET(key, keys[i]);
+		val.len = sizeof(void*);
+		val.data = &key.data;
+
+		ret = api->insert(&txn, &key, &val, 0);
+		if (ret != KNOT_EOK && ret != KNOT_EEXIST) {
+			passed = false;
+			break;
+		}
+	}
+	ok(passed, "%s: insert", api->name);
+
+	/* Commit WR transaction. */
+	ret = api->txn_commit(&txn);
+	ok(ret == KNOT_EOK, "%s: txn_commit(WR)", api->name);
+
+	/* Start RD transaction. */
+	ret = api->txn_begin(db, &txn, KNOT_NAMEDB_RDONLY);
+	ok(ret == KNOT_EOK, "%s: txn_begin(RD)", api->name);
+
+	/* Lookup all keys */
+	passed = true;
+	for (unsigned i = 0; i < nkeys; ++i) {
+		KEY_SET(key, keys[i]);
+
+		ret = api->find(&txn, &key, &val, 0);
+		if (ret != KNOT_EOK) {
+			passed = false;
+			break;
+		}
+
+		const char **stored_key = val.data;
+		if (strcmp(*stored_key, keys[i]) != 0) {
+			diag("%s: mismatch on element '%u'", api->name, i);
+			passed = false;
+			break;
+		}
+	}
+	ok(passed, "%s: lookup all keys", api->name);
+
+	/* Fetch dataset size. */
+	int db_size = api->count(&txn);
+	ok(db_size > 0 && db_size <= nkeys, "%s: count %d", api->name, db_size);
+
+	/* Unsorted iteration */
+	int iterated = 0;
+	knot_iter_t *it = api->iter_begin(&txn, 0);
+	while (it != NULL) {
+		++iterated;
+		it = api->iter_next(it);
+	}
+	api->iter_finish(it);
+	is_int(db_size, iterated, "%s: unsorted iteration", api->name);
+
+	/* Sorted iteration. */
+	char key_buf[KEY_MAXLEN] = {'\0'};
+	iterated = 0;
+	it = api->iter_begin(&txn, KNOT_NAMEDB_SORTED);
+	while (it != NULL) {
+		ret = api->iter_key(it, &key);
+		if (iterated > 0) { /* Only if previous exists. */
+			if (strcmp(key_buf, key.data) > 0) {
+				diag("%s: iter_sort '%s' <= '%s' FAIL\n",
+				     api->name, key_buf, (const char *)key.data);
+				break;
+			}
+		}
+		++iterated;
+		memcpy(key_buf, key.data, key.len);
+		it = api->iter_next(it);
+	}
+	is_int(db_size, iterated, "hattrie: sorted iteration");
+	api->iter_finish(it);
+
+	api->txn_abort(&txn);
+	api->deinit(db);
+}
+
+int main(int argc, char *argv[])
+{
+	plan_lazy();
+
+	mm_ctx_t pool;
+	mm_ctx_mempool(&pool, 4096);
+
+	/* Temporary DB identifier. */
+	char dbid_buf[] = "/tmp/namedb.XXXXXX";
+	char *dbid = mkdtemp(dbid_buf);
+
+	/* Random keys. */
+	unsigned nkeys = 10000;
+	char **keys = mm_alloc(&pool, sizeof(char*) * nkeys);
+	for (unsigned i = 0; i < nkeys; ++i) {
+		keys[i] = str_key_rand(KEY_MAXLEN, &pool);
+	}
+
+	/* Sort random keys. */
+	str_key_sort(keys, nkeys);
+
+	/* Execute test set for all backends. */
+	namedb_test_set(nkeys, keys, dbid, namedb_lmdb_api(), &pool);
+	namedb_test_set(nkeys, keys, dbid, namedb_trie_api(), &pool);
+
+	/* Cleanup */
+	mp_delete(pool.ctx);
+	return 0;
+}
diff --git a/tests/node.c b/tests/node.c
index 3d527721d8a6dabb34a1a4eefd0aa38575577ac8..9f75eab1b2edf6e9e233457d12eda23c4696144f 100644
--- a/tests/node.c
+++ b/tests/node.c
@@ -50,7 +50,7 @@ int main(int argc, char *argv[])
 {
 	plan(23);
 	
-	knot_dname_t *dummy_owner = knot_dname_from_str("test.");
+	knot_dname_t *dummy_owner = knot_dname_from_str_alloc("test.");
 	// Test new
 	zone_node_t *node = node_new(dummy_owner, NULL);
 	ok(node != NULL, "Node: new");
diff --git a/tests/pkt.c b/tests/pkt.c
index 3a0d9b36db3654bd1f9761cc74bbe266ab48746a..cb596a13faac13eef8233959035846f7b042e86e 100644
--- a/tests/pkt.c
+++ b/tests/pkt.c
@@ -76,7 +76,7 @@ int main(int argc, char *argv[])
 	knot_dname_t* dnames[NAMECOUNT] = {0};
 	knot_rrset_t* rrsets[NAMECOUNT] = {0};
 	for (unsigned i = 0; i < NAMECOUNT; ++i) {
-		dnames[i] = knot_dname_from_str(g_names[i]);
+		dnames[i] = knot_dname_from_str_alloc(g_names[i]);
 	}
 
 	uint8_t *edns_str = (uint8_t *)"ab";
diff --git a/tests/rrl.c b/tests/rrl.c
index 3b8899de1086f2831084956099cdb96ee1a7644c..dcb7e9635ede8ad1a8b48417059342c18bb361ab 100644
--- a/tests/rrl.c
+++ b/tests/rrl.c
@@ -102,7 +102,7 @@ int main(int argc, char *argv[])
 		return KNOT_ERROR; /* Fatal */
 	}
 
-	knot_dname_t *qname = knot_dname_from_str("beef.");
+	knot_dname_t *qname = knot_dname_from_str_alloc("beef.");
 	int ret = knot_pkt_put_question(query, qname, KNOT_CLASS_IN, KNOT_RRTYPE_A);
 	knot_dname_free(&qname, NULL);
 	if (ret != KNOT_EOK) {
diff --git a/tests/rrset.c b/tests/rrset.c
index 1849a19fe790aeffeffec0b4a133c9ca315bf9ea..8c2192214984ef3549d4317e6321aadaded0c05d 100644
--- a/tests/rrset.c
+++ b/tests/rrset.c
@@ -39,7 +39,7 @@ int main(int argc, char *argv[])
 	plan(19);
 
 	// Test new
-	knot_dname_t *dummy_owner = knot_dname_from_str("test.");
+	knot_dname_t *dummy_owner = knot_dname_from_str_alloc("test.");
 	assert(dummy_owner);
 
 	knot_rrset_t *rrset = knot_rrset_new(dummy_owner, KNOT_RRTYPE_TXT,
@@ -52,7 +52,7 @@ int main(int argc, char *argv[])
 
 	// Test init
 	knot_dname_free(&dummy_owner, NULL);
-	dummy_owner = knot_dname_from_str("test2.");
+	dummy_owner = knot_dname_from_str_alloc("test2.");
 	assert(dummy_owner);
 
 	knot_dname_free(&rrset->owner, NULL);
diff --git a/tests/zone_events.c b/tests/zone_events.c
index a011f887e559e0ecf7a631e3157ec179ab40a4ba..804cf08f5c3045d568d97aad3184ba0d8c467ebb 100644
--- a/tests/zone_events.c
+++ b/tests/zone_events.c
@@ -18,7 +18,7 @@
 
 #include "common-knot/evsched.h"
 #include "knot/worker/pool.h"
-#include "knot/zone/events.h"
+#include "knot/zone/events/events.h"
 #include "knot/zone/zone.h"
 
 static void test_scheduling(zone_t *zone)
diff --git a/tests/zone_timers.c b/tests/zone_timers.c
new file mode 100644
index 0000000000000000000000000000000000000000..3162806f1d11b5bf5155cf6f1f2e9108d34f2e92
--- /dev/null
+++ b/tests/zone_timers.c
@@ -0,0 +1,123 @@
+/*  Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+    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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include <time.h>
+#include <tap/basic.h>
+
+#include "libknot/common.h"
+#include "common/namedb/namedb.h"
+#include "common/namedb/namedb_lmdb.h"
+#include "knot/zone/timers.h"
+#include "knot/zone/zone.h"
+#include "knot/zone/events/events.h"
+
+#define SLIP (1024 * 1024)
+static const size_t REFRESH_SLIP = SLIP;
+static const size_t EXPIRE_SLIP = SLIP + 1;
+static const size_t FLUSH_SLIP = SLIP + 2;
+
+int main(int argc, char *argv[])
+{
+	plan_lazy();
+
+	if (namedb_lmdb_api() == NULL) {
+		skip("LMDB API not compiled");
+		return EXIT_SUCCESS;
+	}
+
+	// Temporary DB identifier.
+	char dbid_buf[] = "/tmp/timerdb.XXXXXX";
+	const char *dbid = mkdtemp(dbid_buf);
+
+	// Mockup zones.
+	conf_zone_t zone_conf = { .name = "test1." };
+	zone_t *zone_1 = zone_new(&zone_conf);
+	zone_conf.name = "test2.";
+	zone_t *zone_2 = zone_new(&zone_conf);
+	assert(zone_1 && zone_2);
+
+	// Mockup zonedb.
+	knot_zonedb_t *zone_db = knot_zonedb_new(2);
+	assert(zone_db);
+	int ret = knot_zonedb_insert(zone_db, zone_1);
+	assert(ret == KNOT_EOK);
+	ret = knot_zonedb_insert(zone_db, zone_2);
+	assert(ret == KNOT_EOK);
+
+	knot_namedb_t *db = open_timers_db(dbid);
+	ok(db != NULL, "zone timers: create");
+
+	// Set up events in the future.
+	const time_t now = time(NULL);
+	const time_t REFRESH_TIME = now + REFRESH_SLIP;
+	const time_t EXPIRE_TIME = now + EXPIRE_SLIP;
+	const time_t FLUSH_TIME = now + FLUSH_SLIP;
+
+	// Refresh, expire and flush are the permanent events for now.
+	zone_events_schedule_at(zone_1, ZONE_EVENT_REFRESH, REFRESH_TIME);
+	zone_events_schedule_at(zone_1, ZONE_EVENT_EXPIRE, EXPIRE_TIME);
+	zone_events_schedule_at(zone_1, ZONE_EVENT_FLUSH, FLUSH_TIME);
+
+	// Write the timers.
+	ret = write_zone_timers(db, zone_1);
+	ok(ret == KNOT_EOK, "zone timers: write");
+
+	// Read the timers.
+	time_t timers[ZONE_EVENT_COUNT];
+	ret = read_zone_timers(db, zone_1, timers);
+	ok(ret == KNOT_EOK &&
+	   timers[ZONE_EVENT_REFRESH] == REFRESH_TIME &&
+	   timers[ZONE_EVENT_EXPIRE] == EXPIRE_TIME &&
+	   timers[ZONE_EVENT_FLUSH] == FLUSH_TIME, "zone timers: read set");
+
+	// Sweep and read again - timers should stay the same.
+	int s_ret = sweep_timer_db(db, zone_db);
+	if (s_ret == KNOT_EOK) {
+		ret = read_zone_timers(db, zone_1, timers);
+	}
+	ok(s_ret == KNOT_EOK && ret == KNOT_EOK &&
+	   timers[ZONE_EVENT_REFRESH] == REFRESH_TIME &&
+	   timers[ZONE_EVENT_EXPIRE] == EXPIRE_TIME &&
+	   timers[ZONE_EVENT_FLUSH] == FLUSH_TIME, "zone timers: sweep no-op");
+
+	// Read timers for unset zone.
+	const time_t empty_timers[ZONE_EVENT_COUNT] = { '\0' };
+	ret = read_zone_timers(db, zone_2, timers);
+	ok(ret == KNOT_EOK &&
+	   memcmp(timers, empty_timers, sizeof(timers)) == 0, "zone timers: read unset");
+
+	// Remove first zone from db and sweep.
+	ret = knot_zonedb_del(zone_db, zone_1->name);
+	assert(ret == KNOT_EOK);
+
+	s_ret = sweep_timer_db(db, zone_db);
+	if (s_ret == KNOT_EOK) {
+		ret = read_zone_timers(db, zone_1, timers);
+	}
+	ok(s_ret == KNOT_EOK && ret == KNOT_EOK &&
+	   memcmp(timers, empty_timers, sizeof(timers)) == 0, "zone timers: sweep");
+
+	// Clean up.
+	zone_1->conf = NULL;
+	zone_2->conf = NULL;
+	zone_free(&zone_1);
+	zone_free(&zone_2);
+	close_timers_db(db);
+
+	return EXIT_SUCCESS;
+}
+
diff --git a/tests/zone_update.c b/tests/zone_update.c
index 5553b8e6463d25d758b6cfcda8186459a6e95b43..cf596543fea94a0f394e0cb158f40e9723965b39 100644
--- a/tests/zone_update.c
+++ b/tests/zone_update.c
@@ -59,7 +59,7 @@ int main(int argc, char *argv[])
 {
 	plan(5);
 
-	knot_dname_t *apex = knot_dname_from_str("test");
+	knot_dname_t *apex = knot_dname_from_str_alloc("test");
 	assert(apex);
 	zone_contents_t *zone = zone_contents_new(apex);
 	knot_dname_free(&apex, NULL);
diff --git a/tests/zonedb.c b/tests/zonedb.c
index 2670d733ea8ed48a0f532b299d154ea1322ae135..cc0ed69ab3d69bdfb045986564bbb27694a9fa4d 100644
--- a/tests/zonedb.c
+++ b/tests/zonedb.c
@@ -72,7 +72,7 @@ int main(int argc, char *argv[])
 	/* Lookup of exact names. */
 	nr_passed = 0;
 	for (unsigned i = 0; i < ZONE_COUNT; ++i) {
-		dname = knot_dname_from_str(zone_list[i]);
+		dname = knot_dname_from_str_alloc(zone_list[i]);
 		if (knot_zonedb_find(db, dname) == zones[i]) {
 			++nr_passed;
 		} else {
@@ -89,7 +89,7 @@ int main(int argc, char *argv[])
 		if (strcmp(zone_list[i], ".") != 0) {
 			strlcat(buf, zone_list[i], sizeof(buf));
 		}
-		dname = knot_dname_from_str(buf);
+		dname = knot_dname_from_str_alloc(buf);
 		if (knot_zonedb_find_suffix(db, dname) == zones[i]) {
 			++nr_passed;
 		} else {
@@ -102,7 +102,7 @@ int main(int argc, char *argv[])
 	/* Remove all zones. */
 	nr_passed = 0;
 	for (unsigned i = 0; i < ZONE_COUNT; ++i) {
-		dname = knot_dname_from_str(zone_list[i]);
+		dname = knot_dname_from_str_alloc(zone_list[i]);
 		if (knot_zonedb_del(db, dname) == KNOT_EOK) {
 			zone_free(&zones[i]);
 			++nr_passed;
diff --git a/tests/ztree.c b/tests/ztree.c
index 4d24d9cd5c2848d2f310240490923f26d9a807bc..0edbc7ecd5480534c6985c8cb8190e66638ee2a3 100644
--- a/tests/ztree.c
+++ b/tests/ztree.c
@@ -24,10 +24,10 @@ static zone_node_t NODE[NCOUNT];
 static knot_dname_t* ORDER[NCOUNT];
 static void ztree_init_data()
 {
-	NAME[0] = knot_dname_from_str(".");
-	NAME[1] = knot_dname_from_str("master.ac.");
-	NAME[2] = knot_dname_from_str("ac.");
-	NAME[3] = knot_dname_from_str("ns.");
+	NAME[0] = knot_dname_from_str_alloc(".");
+	NAME[1] = knot_dname_from_str_alloc("master.ac.");
+	NAME[2] = knot_dname_from_str_alloc("ac.");
+	NAME[3] = knot_dname_from_str_alloc("ns.");
 
 	knot_dname_t *order[NCOUNT] = {
 	        NAME[0], NAME[2], NAME[1], NAME[3]
@@ -55,8 +55,8 @@ static int ztree_iter_data(zone_node_t **node, void *data)
 	int result = KNOT_EOK;
 	if (owner != ORDER[*i]) {
 		result = KNOT_ERROR;
-		char *exp_s = knot_dname_to_str(ORDER[*i]);
-		char *owner_s = knot_dname_to_str(owner);
+		char *exp_s = knot_dname_to_str_alloc(ORDER[*i]);
+		char *owner_s = knot_dname_to_str_alloc(owner);
 		diag("ztree: at index: %u expected '%s' got '%s'\n", *i, exp_s, owner_s);
 		free(exp_s);
 		free(owner_s);
@@ -103,7 +103,7 @@ int main(int argc, char *argv[])
 	/* 4. ordered lookup */
 	node = NULL;
 	const zone_node_t *prev = NULL;
-	knot_dname_t *tmp_dn = knot_dname_from_str("z.ac.");
+	knot_dname_t *tmp_dn = knot_dname_from_str_alloc("z.ac.");
 	zone_tree_find_less_or_equal(t, tmp_dn, &node, &prev);
 	knot_dname_free(&tmp_dn, NULL);
 	ok(prev == NODE + 1, "ztree: ordered lookup");