summaryrefslogtreecommitdiffstats
path: root/src/bin/d2/tests
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/bin/d2/tests/Makefile.am133
-rw-r--r--src/bin/d2/tests/Makefile.in1427
-rw-r--r--src/bin/d2/tests/callout_library.cc72
-rw-r--r--src/bin/d2/tests/configured_library.cc63
-rw-r--r--src/bin/d2/tests/d2_cfg_mgr_unittests.cc1080
-rw-r--r--src/bin/d2/tests/d2_command_unittest.cc1391
-rw-r--r--src/bin/d2/tests/d2_controller_unittests.cc303
-rw-r--r--src/bin/d2/tests/d2_process_tests.sh.in332
-rw-r--r--src/bin/d2/tests/d2_process_unittests.cc693
-rw-r--r--src/bin/d2/tests/d2_queue_mgr_unittests.cc457
-rw-r--r--src/bin/d2/tests/d2_simple_parser_unittest.cc1194
-rw-r--r--src/bin/d2/tests/d2_unittests.cc28
-rw-r--r--src/bin/d2/tests/d2_update_mgr_unittests.cc989
-rw-r--r--src/bin/d2/tests/get_config_unittest.cc293
-rw-r--r--src/bin/d2/tests/nc_add_unittests.cc1713
-rw-r--r--src/bin/d2/tests/nc_remove_unittests.cc1796
-rw-r--r--src/bin/d2/tests/parser_unittest.cc871
-rw-r--r--src/bin/d2/tests/parser_unittest.h36
-rw-r--r--src/bin/d2/tests/simple_add_unittests.cc1155
-rw-r--r--src/bin/d2/tests/simple_remove_unittests.cc1238
-rw-r--r--src/bin/d2/tests/test_callout_libraries.h.in24
-rw-r--r--src/bin/d2/tests/test_configured_libraries.h.in26
-rw-r--r--src/bin/d2/tests/test_data_files_config.h.in10
-rw-r--r--src/bin/d2/tests/testdata/d2_cfg_tests.json1577
-rw-r--r--src/bin/d2/tests/testdata/get_config.json106
25 files changed, 17007 insertions, 0 deletions
diff --git a/src/bin/d2/tests/Makefile.am b/src/bin/d2/tests/Makefile.am
new file mode 100644
index 0000000..0da992b
--- /dev/null
+++ b/src/bin/d2/tests/Makefile.am
@@ -0,0 +1,133 @@
+SUBDIRS = .
+
+# Add to the tarball:
+EXTRA_DIST =
+EXTRA_DIST += testdata/d2_cfg_tests.json
+EXTRA_DIST += testdata/get_config.json
+
+TESTS_ENVIRONMENT = \
+ $(LIBTOOL) --mode=execute $(VALGRIND_COMMAND)
+
+# Shell tests
+SHTESTS = d2_process_tests.sh
+
+# Run shell tests on "make check".
+check_SCRIPTS = $(SHTESTS)
+TESTS = $(SHTESTS)
+
+# As with every file generated by ./configure, clean them up when running
+# "make distclean", but not on "make clean".
+DISTCLEANFILES = $(SHTESTS)
+DISTCLEANFILES += d2_process_tests.sh
+DISTCLEANFILES += test_data_files_config.h
+DISTCLEANFILES += test_callout_libraries.h
+DISTCLEANFILES += test_configured_libraries.h
+
+# Don't install shell tests.
+noinst_SCRIPTS = $(SHTESTS)
+
+if HAVE_GTEST
+
+# C++ tests
+PROGRAM_TESTS = d2_unittests
+
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CPPFLAGS += -I$(top_srcdir)/src/bin -I$(top_builddir)/src/bin
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CPPFLAGS += -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/bin/d2/tests\"
+AM_CPPFLAGS += -DINSTALL_PROG=\"$(abs_top_srcdir)/install-sh\"
+AM_CPPFLAGS += -DCFG_EXAMPLES=\"$(abs_top_srcdir)/doc/examples/ddns\"
+AM_CPPFLAGS += -DSYNTAX_FILE=\"$(abs_srcdir)/../d2_parser.yy\"
+
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+
+if USE_STATIC_LINK
+AM_LDFLAGS = -static
+endif
+
+d2_unittests_SOURCES = d2_unittests.cc
+d2_unittests_SOURCES += d2_process_unittests.cc
+d2_unittests_SOURCES += d2_cfg_mgr_unittests.cc
+d2_unittests_SOURCES += d2_queue_mgr_unittests.cc
+d2_unittests_SOURCES += d2_update_mgr_unittests.cc
+d2_unittests_SOURCES += nc_add_unittests.cc
+d2_unittests_SOURCES += nc_remove_unittests.cc
+d2_unittests_SOURCES += d2_controller_unittests.cc
+d2_unittests_SOURCES += d2_simple_parser_unittest.cc
+d2_unittests_SOURCES += parser_unittest.cc parser_unittest.h
+d2_unittests_SOURCES += get_config_unittest.cc
+d2_unittests_SOURCES += d2_command_unittest.cc
+d2_unittests_SOURCES += simple_add_unittests.cc
+d2_unittests_SOURCES += simple_remove_unittests.cc
+
+d2_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+d2_unittests_LDFLAGS = $(AM_LDFLAGS) $(CRYPTO_LDFLAGS)
+if HAVE_MYSQL
+d2_unittests_LDFLAGS += $(MYSQL_LIBS)
+endif
+if HAVE_PGSQL
+d2_unittests_LDFLAGS += $(PGSQL_LIBS)
+endif
+d2_unittests_LDFLAGS += $(GTEST_LDFLAGS)
+
+d2_unittests_LDADD = $(top_builddir)/src/bin/d2/libd2.la
+d2_unittests_LDADD += $(top_builddir)/src/lib/d2srv/testutils/libd2srvtest.la
+d2_unittests_LDADD += $(top_builddir)/src/lib/d2srv/libkea-d2srv.la
+d2_unittests_LDADD += $(top_builddir)/src/lib/process/testutils/libprocesstest.la
+d2_unittests_LDADD += $(top_builddir)/src/lib/cfgrpt/libcfgrpt.la
+d2_unittests_LDADD += $(top_builddir)/src/lib/process/libkea-process.la
+d2_unittests_LDADD += $(top_builddir)/src/lib/dhcp_ddns/libkea-dhcp_ddns.la
+d2_unittests_LDADD += $(top_builddir)/src/lib/testutils/libkea-testutils.la
+d2_unittests_LDADD += $(top_builddir)/src/lib/asiodns/libkea-asiodns.la
+d2_unittests_LDADD += $(top_builddir)/src/lib/stats/libkea-stats.la
+d2_unittests_LDADD += $(top_builddir)/src/lib/config/libkea-cfgclient.la
+d2_unittests_LDADD += $(top_builddir)/src/lib/http/libkea-http.la
+d2_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la
+d2_unittests_LDADD += $(top_builddir)/src/lib/database/libkea-database.la
+d2_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
+d2_unittests_LDADD += $(top_builddir)/src/lib/cc/libkea-cc.la
+d2_unittests_LDADD += $(top_builddir)/src/lib/dns/libkea-dns++.la
+d2_unittests_LDADD += $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la
+d2_unittests_LDADD += $(top_builddir)/src/lib/hooks/libkea-hooks.la
+d2_unittests_LDADD += $(top_builddir)/src/lib/log/libkea-log.la
+d2_unittests_LDADD += $(top_builddir)/src/lib/util/libkea-util.la
+d2_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+d2_unittests_LDADD += $(LOG4CPLUS_LIBS) $(CRYPTO_LIBS)
+d2_unittests_LDADD += $(BOOST_LIBS) $(GTEST_LDADD)
+
+# The basic callout library - contains standard callouts
+libcallout_la_SOURCES = callout_library.cc
+libcallout_la_CXXFLAGS = $(AM_CXXFLAGS)
+libcallout_la_CPPFLAGS = $(AM_CPPFLAGS)
+libcallout_la_LIBADD = $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+libcallout_la_LIBADD += $(top_builddir)/src/lib/hooks/libkea-hooks.la
+libcallout_la_LIBADD += $(top_builddir)/src/lib/log/libkea-log.la
+libcallout_la_LDFLAGS = -avoid-version -export-dynamic -module -rpath /nowhere
+
+# The d2_srv_configured callout library
+libconfigured_la_SOURCES = configured_library.cc
+libconfigured_la_CXXFLAGS = $(AM_CXXFLAGS)
+libconfigured_la_CPPFLAGS = $(AM_CPPFLAGS)
+libconfigured_la_LIBADD = $(top_builddir)/src/lib/hooks/libkea-hooks.la
+libconfigured_la_LIBADD += $(top_builddir)/src/lib/cc/libkea-cc.la
+libconfigured_la_LIBADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
+libconfigured_la_LIBADD += $(top_builddir)/src/lib/log/libkea-log.la
+libconfigured_la_LIBADD += $(top_builddir)/src/lib/util/libkea-util.la
+libconfigured_la_LIBADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+libconfigured_la_LIBADD += $(LOG4CPLUS_LIBS) $(BOOST_LIBS)
+libconfigured_la_LDFLAGS = -avoid-version -export-dynamic -module -rpath /nowhere
+
+noinst_LTLIBRARIES = libcallout.la libconfigured.la
+
+nodist_d2_unittests_SOURCES =
+nodist_d2_unittests_SOURCES += test_data_files_config.h
+nodist_d2_unittests_SOURCES += test_callout_libraries.h
+nodist_d2_unittests_SOURCES += test_configured_libraries.h
+
+# Run C++ tests on "make check".
+TESTS += $(PROGRAM_TESTS)
+
+# Don't install C++ tests.
+noinst_PROGRAMS = $(PROGRAM_TESTS)
+
+endif
diff --git a/src/bin/d2/tests/Makefile.in b/src/bin/d2/tests/Makefile.in
new file mode 100644
index 0000000..74bc717
--- /dev/null
+++ b/src/bin/d2/tests/Makefile.in
@@ -0,0 +1,1427 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+TESTS = $(SHTESTS) $(am__EXEEXT_2)
+@HAVE_GTEST_TRUE@@HAVE_MYSQL_TRUE@am__append_1 = $(MYSQL_LIBS)
+@HAVE_GTEST_TRUE@@HAVE_PGSQL_TRUE@am__append_2 = $(PGSQL_LIBS)
+
+# Run C++ tests on "make check".
+@HAVE_GTEST_TRUE@am__append_3 = $(PROGRAM_TESTS)
+@HAVE_GTEST_TRUE@noinst_PROGRAMS = $(am__EXEEXT_1)
+subdir = src/bin/d2/tests
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.m4 \
+ $(top_srcdir)/m4macros/ax_crypto.m4 \
+ $(top_srcdir)/m4macros/ax_find_library.m4 \
+ $(top_srcdir)/m4macros/ax_gssapi.m4 \
+ $(top_srcdir)/m4macros/ax_gtest.m4 \
+ $(top_srcdir)/m4macros/ax_isc_rpath.m4 \
+ $(top_srcdir)/m4macros/ax_sysrepo.m4 \
+ $(top_srcdir)/m4macros/libtool.m4 \
+ $(top_srcdir)/m4macros/ltoptions.m4 \
+ $(top_srcdir)/m4macros/ltsugar.m4 \
+ $(top_srcdir)/m4macros/ltversion.m4 \
+ $(top_srcdir)/m4macros/lt~obsolete.m4 \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES = d2_process_tests.sh test_callout_libraries.h \
+ test_configured_libraries.h test_data_files_config.h
+CONFIG_CLEAN_VPATH_FILES =
+@HAVE_GTEST_TRUE@am__EXEEXT_1 = d2_unittests$(EXEEXT)
+PROGRAMS = $(noinst_PROGRAMS)
+LTLIBRARIES = $(noinst_LTLIBRARIES)
+@HAVE_GTEST_TRUE@libcallout_la_DEPENDENCIES = $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/hooks/libkea-hooks.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/log/libkea-log.la
+am__libcallout_la_SOURCES_DIST = callout_library.cc
+@HAVE_GTEST_TRUE@am_libcallout_la_OBJECTS = \
+@HAVE_GTEST_TRUE@ libcallout_la-callout_library.lo
+libcallout_la_OBJECTS = $(am_libcallout_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+libcallout_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
+ $(libcallout_la_CXXFLAGS) $(CXXFLAGS) $(libcallout_la_LDFLAGS) \
+ $(LDFLAGS) -o $@
+@HAVE_GTEST_TRUE@am_libcallout_la_rpath =
+am__DEPENDENCIES_1 =
+@HAVE_GTEST_TRUE@libconfigured_la_DEPENDENCIES = $(top_builddir)/src/lib/hooks/libkea-hooks.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cc/libkea-cc.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/log/libkea-log.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/util/libkea-util.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \
+@HAVE_GTEST_TRUE@ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1)
+am__libconfigured_la_SOURCES_DIST = configured_library.cc
+@HAVE_GTEST_TRUE@am_libconfigured_la_OBJECTS = \
+@HAVE_GTEST_TRUE@ libconfigured_la-configured_library.lo
+libconfigured_la_OBJECTS = $(am_libconfigured_la_OBJECTS)
+libconfigured_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
+ $(libconfigured_la_CXXFLAGS) $(CXXFLAGS) \
+ $(libconfigured_la_LDFLAGS) $(LDFLAGS) -o $@
+@HAVE_GTEST_TRUE@am_libconfigured_la_rpath =
+am__d2_unittests_SOURCES_DIST = d2_unittests.cc \
+ d2_process_unittests.cc d2_cfg_mgr_unittests.cc \
+ d2_queue_mgr_unittests.cc d2_update_mgr_unittests.cc \
+ nc_add_unittests.cc nc_remove_unittests.cc \
+ d2_controller_unittests.cc d2_simple_parser_unittest.cc \
+ parser_unittest.cc parser_unittest.h get_config_unittest.cc \
+ d2_command_unittest.cc simple_add_unittests.cc \
+ simple_remove_unittests.cc
+@HAVE_GTEST_TRUE@am_d2_unittests_OBJECTS = \
+@HAVE_GTEST_TRUE@ d2_unittests-d2_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ d2_unittests-d2_process_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ d2_unittests-d2_cfg_mgr_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ d2_unittests-d2_queue_mgr_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ d2_unittests-d2_update_mgr_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ d2_unittests-nc_add_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ d2_unittests-nc_remove_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ d2_unittests-d2_controller_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ d2_unittests-d2_simple_parser_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ d2_unittests-parser_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ d2_unittests-get_config_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ d2_unittests-d2_command_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ d2_unittests-simple_add_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ d2_unittests-simple_remove_unittests.$(OBJEXT)
+nodist_d2_unittests_OBJECTS =
+d2_unittests_OBJECTS = $(am_d2_unittests_OBJECTS) \
+ $(nodist_d2_unittests_OBJECTS)
+@HAVE_GTEST_TRUE@d2_unittests_DEPENDENCIES = \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/bin/d2/libd2.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/d2srv/testutils/libd2srvtest.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/d2srv/libkea-d2srv.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/process/testutils/libprocesstest.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cfgrpt/libcfgrpt.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/process/libkea-process.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/dhcp_ddns/libkea-dhcp_ddns.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/testutils/libkea-testutils.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/asiodns/libkea-asiodns.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/stats/libkea-stats.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/config/libkea-cfgclient.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/http/libkea-http.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/database/libkea-database.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cc/libkea-cc.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/dns/libkea-dns++.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/hooks/libkea-hooks.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/log/libkea-log.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/util/libkea-util.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \
+@HAVE_GTEST_TRUE@ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \
+@HAVE_GTEST_TRUE@ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1)
+d2_unittests_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(AM_CXXFLAGS) \
+ $(CXXFLAGS) $(d2_unittests_LDFLAGS) $(LDFLAGS) -o $@
+SCRIPTS = $(noinst_SCRIPTS)
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = \
+ ./$(DEPDIR)/d2_unittests-d2_cfg_mgr_unittests.Po \
+ ./$(DEPDIR)/d2_unittests-d2_command_unittest.Po \
+ ./$(DEPDIR)/d2_unittests-d2_controller_unittests.Po \
+ ./$(DEPDIR)/d2_unittests-d2_process_unittests.Po \
+ ./$(DEPDIR)/d2_unittests-d2_queue_mgr_unittests.Po \
+ ./$(DEPDIR)/d2_unittests-d2_simple_parser_unittest.Po \
+ ./$(DEPDIR)/d2_unittests-d2_unittests.Po \
+ ./$(DEPDIR)/d2_unittests-d2_update_mgr_unittests.Po \
+ ./$(DEPDIR)/d2_unittests-get_config_unittest.Po \
+ ./$(DEPDIR)/d2_unittests-nc_add_unittests.Po \
+ ./$(DEPDIR)/d2_unittests-nc_remove_unittests.Po \
+ ./$(DEPDIR)/d2_unittests-parser_unittest.Po \
+ ./$(DEPDIR)/d2_unittests-simple_add_unittests.Po \
+ ./$(DEPDIR)/d2_unittests-simple_remove_unittests.Po \
+ ./$(DEPDIR)/libcallout_la-callout_library.Plo \
+ ./$(DEPDIR)/libconfigured_la-configured_library.Plo
+am__mv = mv -f
+CXXCOMPILE = $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \
+ $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS)
+LTCXXCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CXXFLAGS) $(CXXFLAGS)
+AM_V_CXX = $(am__v_CXX_@AM_V@)
+am__v_CXX_ = $(am__v_CXX_@AM_DEFAULT_V@)
+am__v_CXX_0 = @echo " CXX " $@;
+am__v_CXX_1 =
+CXXLD = $(CXX)
+CXXLINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(AM_CXXFLAGS) \
+ $(CXXFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CXXLD = $(am__v_CXXLD_@AM_V@)
+am__v_CXXLD_ = $(am__v_CXXLD_@AM_DEFAULT_V@)
+am__v_CXXLD_0 = @echo " CXXLD " $@;
+am__v_CXXLD_1 =
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libcallout_la_SOURCES) $(libconfigured_la_SOURCES) \
+ $(d2_unittests_SOURCES) $(nodist_d2_unittests_SOURCES)
+DIST_SOURCES = $(am__libcallout_la_SOURCES_DIST) \
+ $(am__libconfigured_la_SOURCES_DIST) \
+ $(am__d2_unittests_SOURCES_DIST)
+RECURSIVE_TARGETS = all-recursive check-recursive cscopelist-recursive \
+ ctags-recursive dvi-recursive html-recursive info-recursive \
+ install-data-recursive install-dvi-recursive \
+ install-exec-recursive install-html-recursive \
+ install-info-recursive install-pdf-recursive \
+ install-ps-recursive install-recursive installcheck-recursive \
+ installdirs-recursive pdf-recursive ps-recursive \
+ tags-recursive uninstall-recursive
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \
+ distclean-recursive maintainer-clean-recursive
+am__recursive_targets = \
+ $(RECURSIVE_TARGETS) \
+ $(RECURSIVE_CLEAN_TARGETS) \
+ $(am__extra_recursive_targets)
+AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \
+ distdir distdir-am
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__tty_colors_dummy = \
+ mgn= red= grn= lgn= blu= brg= std=; \
+ am__color_tests=no
+am__tty_colors = { \
+ $(am__tty_colors_dummy); \
+ if test "X$(AM_COLOR_TESTS)" = Xno; then \
+ am__color_tests=no; \
+ elif test "X$(AM_COLOR_TESTS)" = Xalways; then \
+ am__color_tests=yes; \
+ elif test "X$$TERM" != Xdumb && { test -t 1; } 2>/dev/null; then \
+ am__color_tests=yes; \
+ fi; \
+ if test $$am__color_tests = yes; then \
+ red=''; \
+ grn=''; \
+ lgn=''; \
+ blu=''; \
+ mgn=''; \
+ brg=''; \
+ std=''; \
+ fi; \
+}
+@HAVE_GTEST_TRUE@am__EXEEXT_2 = $(am__EXEEXT_1)
+DIST_SUBDIRS = $(SUBDIRS)
+am__DIST_COMMON = $(srcdir)/Makefile.in \
+ $(srcdir)/d2_process_tests.sh.in \
+ $(srcdir)/test_callout_libraries.h.in \
+ $(srcdir)/test_configured_libraries.h.in \
+ $(srcdir)/test_data_files_config.h.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+am__relativize = \
+ dir0=`pwd`; \
+ sed_first='s,^\([^/]*\)/.*$$,\1,'; \
+ sed_rest='s,^[^/]*/*,,'; \
+ sed_last='s,^.*/\([^/]*\)$$,\1,'; \
+ sed_butlast='s,/*[^/]*$$,,'; \
+ while test -n "$$dir1"; do \
+ first=`echo "$$dir1" | sed -e "$$sed_first"`; \
+ if test "$$first" != "."; then \
+ if test "$$first" = ".."; then \
+ dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \
+ dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \
+ else \
+ first2=`echo "$$dir2" | sed -e "$$sed_first"`; \
+ if test "$$first2" = "$$first"; then \
+ dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \
+ else \
+ dir2="../$$dir2"; \
+ fi; \
+ dir0="$$dir0"/"$$first"; \
+ fi; \
+ fi; \
+ dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \
+ done; \
+ reldir="$$dir2"
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+ASCIIDOC = @ASCIIDOC@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BOOST_INCLUDES = @BOOST_INCLUDES@
+BOOST_LIBS = @BOOST_LIBS@
+BOTAN_TOOL = @BOTAN_TOOL@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CONTRIB_DIR = @CONTRIB_DIR@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CRYPTO_CFLAGS = @CRYPTO_CFLAGS@
+CRYPTO_INCLUDES = @CRYPTO_INCLUDES@
+CRYPTO_LDFLAGS = @CRYPTO_LDFLAGS@
+CRYPTO_LIBS = @CRYPTO_LIBS@
+CRYPTO_PACKAGE = @CRYPTO_PACKAGE@
+CRYPTO_RPATH = @CRYPTO_RPATH@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DISTCHECK_BOOST_CONFIGURE_FLAG = @DISTCHECK_BOOST_CONFIGURE_FLAG@
+DISTCHECK_CONTRIB_CONFIGURE_FLAG = @DISTCHECK_CONTRIB_CONFIGURE_FLAG@
+DISTCHECK_CRYPTO_CONFIGURE_FLAG = @DISTCHECK_CRYPTO_CONFIGURE_FLAG@
+DISTCHECK_GTEST_CONFIGURE_FLAG = @DISTCHECK_GTEST_CONFIGURE_FLAG@
+DISTCHECK_KEA_SHELL_CONFIGURE_FLAG = @DISTCHECK_KEA_SHELL_CONFIGURE_FLAG@
+DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG = @DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG@
+DISTCHECK_PERFDHCP_CONFIGURE_FLAG = @DISTCHECK_PERFDHCP_CONFIGURE_FLAG@
+DISTCHECK_PREMIUM_CONFIGURE_FLAG = @DISTCHECK_PREMIUM_CONFIGURE_FLAG@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GENHTML = @GENHTML@
+GREP = @GREP@
+GSSAPI_CFLAGS = @GSSAPI_CFLAGS@
+GSSAPI_LIBS = @GSSAPI_LIBS@
+GTEST_CONFIG = @GTEST_CONFIG@
+GTEST_INCLUDES = @GTEST_INCLUDES@
+GTEST_LDADD = @GTEST_LDADD@
+GTEST_LDFLAGS = @GTEST_LDFLAGS@
+GTEST_SOURCE = @GTEST_SOURCE@
+HAVE_SYSREPO = @HAVE_SYSREPO@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+KEA_CXXFLAGS = @KEA_CXXFLAGS@
+KEA_SRCID = @KEA_SRCID@
+KRB5_CONFIG = @KRB5_CONFIG@
+LCOV = @LCOV@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LEX = @LEX@
+LEXLIB = @LEXLIB@
+LEX_OUTPUT_ROOT = @LEX_OUTPUT_ROOT@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LOG4CPLUS_INCLUDES = @LOG4CPLUS_INCLUDES@
+LOG4CPLUS_LIBS = @LOG4CPLUS_LIBS@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MYSQL_CPPFLAGS = @MYSQL_CPPFLAGS@
+MYSQL_LIBS = @MYSQL_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PACKAGE_VERSION_TYPE = @PACKAGE_VERSION_TYPE@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PDFLATEX = @PDFLATEX@
+PERL = @PERL@
+PGSQL_CPPFLAGS = @PGSQL_CPPFLAGS@
+PGSQL_LIBS = @PGSQL_LIBS@
+PKGPYTHONDIR = @PKGPYTHONDIR@
+PKG_CONFIG = @PKG_CONFIG@
+PLANTUML = @PLANTUML@
+PREMIUM_DIR = @PREMIUM_DIR@
+PYTHON = @PYTHON@
+PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@
+PYTHON_PLATFORM = @PYTHON_PLATFORM@
+PYTHON_PREFIX = @PYTHON_PREFIX@
+PYTHON_VERSION = @PYTHON_VERSION@
+RANLIB = @RANLIB@
+SED = @SED@
+SEP = @SEP@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SPHINXBUILD = @SPHINXBUILD@
+SRPD_PLUGINS_PATH = @SRPD_PLUGINS_PATH@
+SR_REPO_PATH = @SR_REPO_PATH@
+STRIP = @STRIP@
+SYSREPOCPP_VERSION = @SYSREPOCPP_VERSION@
+SYSREPO_CPPFLAGS = @SYSREPO_CPPFLAGS@
+SYSREPO_INCLUDEDIR = @SYSREPO_INCLUDEDIR@
+SYSREPO_LIBS = @SYSREPO_LIBS@
+SYSREPO_VERSION = @SYSREPO_VERSION@
+USE_LCOV = @USE_LCOV@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+WARNING_GCC_44_STRICT_ALIASING_CFLAG = @WARNING_GCC_44_STRICT_ALIASING_CFLAG@
+YACC = @YACC@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+pkgpyexecdir = @pkgpyexecdir@
+pkgpythondir = @pkgpythondir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+pyexecdir = @pyexecdir@
+pythondir = @pythondir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+SUBDIRS = .
+
+# Add to the tarball:
+EXTRA_DIST = testdata/d2_cfg_tests.json testdata/get_config.json
+TESTS_ENVIRONMENT = \
+ $(LIBTOOL) --mode=execute $(VALGRIND_COMMAND)
+
+
+# Shell tests
+SHTESTS = d2_process_tests.sh
+
+# Run shell tests on "make check".
+check_SCRIPTS = $(SHTESTS)
+
+# As with every file generated by ./configure, clean them up when running
+# "make distclean", but not on "make clean".
+DISTCLEANFILES = $(SHTESTS) d2_process_tests.sh \
+ test_data_files_config.h test_callout_libraries.h \
+ test_configured_libraries.h
+
+# Don't install shell tests.
+noinst_SCRIPTS = $(SHTESTS)
+
+# C++ tests
+@HAVE_GTEST_TRUE@PROGRAM_TESTS = d2_unittests
+@HAVE_GTEST_TRUE@AM_CPPFLAGS = -I$(top_srcdir)/src/lib \
+@HAVE_GTEST_TRUE@ -I$(top_builddir)/src/lib \
+@HAVE_GTEST_TRUE@ -I$(top_srcdir)/src/bin \
+@HAVE_GTEST_TRUE@ -I$(top_builddir)/src/bin $(BOOST_INCLUDES) \
+@HAVE_GTEST_TRUE@ -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/bin/d2/tests\" \
+@HAVE_GTEST_TRUE@ -DINSTALL_PROG=\"$(abs_top_srcdir)/install-sh\" \
+@HAVE_GTEST_TRUE@ -DCFG_EXAMPLES=\"$(abs_top_srcdir)/doc/examples/ddns\" \
+@HAVE_GTEST_TRUE@ -DSYNTAX_FILE=\"$(abs_srcdir)/../d2_parser.yy\"
+@HAVE_GTEST_TRUE@AM_CXXFLAGS = $(KEA_CXXFLAGS)
+@HAVE_GTEST_TRUE@@USE_STATIC_LINK_TRUE@AM_LDFLAGS = -static
+@HAVE_GTEST_TRUE@d2_unittests_SOURCES = d2_unittests.cc \
+@HAVE_GTEST_TRUE@ d2_process_unittests.cc \
+@HAVE_GTEST_TRUE@ d2_cfg_mgr_unittests.cc \
+@HAVE_GTEST_TRUE@ d2_queue_mgr_unittests.cc \
+@HAVE_GTEST_TRUE@ d2_update_mgr_unittests.cc \
+@HAVE_GTEST_TRUE@ nc_add_unittests.cc nc_remove_unittests.cc \
+@HAVE_GTEST_TRUE@ d2_controller_unittests.cc \
+@HAVE_GTEST_TRUE@ d2_simple_parser_unittest.cc \
+@HAVE_GTEST_TRUE@ parser_unittest.cc parser_unittest.h \
+@HAVE_GTEST_TRUE@ get_config_unittest.cc d2_command_unittest.cc \
+@HAVE_GTEST_TRUE@ simple_add_unittests.cc \
+@HAVE_GTEST_TRUE@ simple_remove_unittests.cc
+@HAVE_GTEST_TRUE@d2_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+@HAVE_GTEST_TRUE@d2_unittests_LDFLAGS = $(AM_LDFLAGS) \
+@HAVE_GTEST_TRUE@ $(CRYPTO_LDFLAGS) $(am__append_1) \
+@HAVE_GTEST_TRUE@ $(am__append_2) $(GTEST_LDFLAGS)
+@HAVE_GTEST_TRUE@d2_unittests_LDADD = \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/bin/d2/libd2.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/d2srv/testutils/libd2srvtest.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/d2srv/libkea-d2srv.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/process/testutils/libprocesstest.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cfgrpt/libcfgrpt.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/process/libkea-process.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/dhcp_ddns/libkea-dhcp_ddns.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/testutils/libkea-testutils.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/asiodns/libkea-asiodns.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/stats/libkea-stats.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/config/libkea-cfgclient.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/http/libkea-http.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/database/libkea-database.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cc/libkea-cc.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/dns/libkea-dns++.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/hooks/libkea-hooks.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/log/libkea-log.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/util/libkea-util.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \
+@HAVE_GTEST_TRUE@ $(LOG4CPLUS_LIBS) $(CRYPTO_LIBS) \
+@HAVE_GTEST_TRUE@ $(BOOST_LIBS) $(GTEST_LDADD)
+
+# The basic callout library - contains standard callouts
+@HAVE_GTEST_TRUE@libcallout_la_SOURCES = callout_library.cc
+@HAVE_GTEST_TRUE@libcallout_la_CXXFLAGS = $(AM_CXXFLAGS)
+@HAVE_GTEST_TRUE@libcallout_la_CPPFLAGS = $(AM_CPPFLAGS)
+@HAVE_GTEST_TRUE@libcallout_la_LIBADD = $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/hooks/libkea-hooks.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/log/libkea-log.la
+@HAVE_GTEST_TRUE@libcallout_la_LDFLAGS = -avoid-version -export-dynamic -module -rpath /nowhere
+
+# The d2_srv_configured callout library
+@HAVE_GTEST_TRUE@libconfigured_la_SOURCES = configured_library.cc
+@HAVE_GTEST_TRUE@libconfigured_la_CXXFLAGS = $(AM_CXXFLAGS)
+@HAVE_GTEST_TRUE@libconfigured_la_CPPFLAGS = $(AM_CPPFLAGS)
+@HAVE_GTEST_TRUE@libconfigured_la_LIBADD = $(top_builddir)/src/lib/hooks/libkea-hooks.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cc/libkea-cc.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/log/libkea-log.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/util/libkea-util.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \
+@HAVE_GTEST_TRUE@ $(LOG4CPLUS_LIBS) $(BOOST_LIBS)
+@HAVE_GTEST_TRUE@libconfigured_la_LDFLAGS = -avoid-version -export-dynamic -module -rpath /nowhere
+@HAVE_GTEST_TRUE@noinst_LTLIBRARIES = libcallout.la libconfigured.la
+@HAVE_GTEST_TRUE@nodist_d2_unittests_SOURCES = \
+@HAVE_GTEST_TRUE@ test_data_files_config.h \
+@HAVE_GTEST_TRUE@ test_callout_libraries.h \
+@HAVE_GTEST_TRUE@ test_configured_libraries.h
+all: all-recursive
+
+.SUFFIXES:
+.SUFFIXES: .cc .lo .o .obj
+$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/bin/d2/tests/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/bin/d2/tests/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+d2_process_tests.sh: $(top_builddir)/config.status $(srcdir)/d2_process_tests.sh.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+test_callout_libraries.h: $(top_builddir)/config.status $(srcdir)/test_callout_libraries.h.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+test_configured_libraries.h: $(top_builddir)/config.status $(srcdir)/test_configured_libraries.h.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+test_data_files_config.h: $(top_builddir)/config.status $(srcdir)/test_data_files_config.h.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+
+clean-noinstPROGRAMS:
+ @list='$(noinst_PROGRAMS)'; test -n "$$list" || exit 0; \
+ echo " rm -f" $$list; \
+ rm -f $$list || exit $$?; \
+ test -n "$(EXEEXT)" || exit 0; \
+ list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \
+ echo " rm -f" $$list; \
+ rm -f $$list
+
+clean-noinstLTLIBRARIES:
+ -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES)
+ @list='$(noinst_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+libcallout.la: $(libcallout_la_OBJECTS) $(libcallout_la_DEPENDENCIES) $(EXTRA_libcallout_la_DEPENDENCIES)
+ $(AM_V_CXXLD)$(libcallout_la_LINK) $(am_libcallout_la_rpath) $(libcallout_la_OBJECTS) $(libcallout_la_LIBADD) $(LIBS)
+
+libconfigured.la: $(libconfigured_la_OBJECTS) $(libconfigured_la_DEPENDENCIES) $(EXTRA_libconfigured_la_DEPENDENCIES)
+ $(AM_V_CXXLD)$(libconfigured_la_LINK) $(am_libconfigured_la_rpath) $(libconfigured_la_OBJECTS) $(libconfigured_la_LIBADD) $(LIBS)
+
+d2_unittests$(EXEEXT): $(d2_unittests_OBJECTS) $(d2_unittests_DEPENDENCIES) $(EXTRA_d2_unittests_DEPENDENCIES)
+ @rm -f d2_unittests$(EXEEXT)
+ $(AM_V_CXXLD)$(d2_unittests_LINK) $(d2_unittests_OBJECTS) $(d2_unittests_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/d2_unittests-d2_cfg_mgr_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/d2_unittests-d2_command_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/d2_unittests-d2_controller_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/d2_unittests-d2_process_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/d2_unittests-d2_queue_mgr_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/d2_unittests-d2_simple_parser_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/d2_unittests-d2_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/d2_unittests-d2_update_mgr_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/d2_unittests-get_config_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/d2_unittests-nc_add_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/d2_unittests-nc_remove_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/d2_unittests-parser_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/d2_unittests-simple_add_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/d2_unittests-simple_remove_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libcallout_la-callout_library.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libconfigured_la-configured_library.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.cc.o:
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ $<
+
+.cc.obj:
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.cc.lo:
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LTCXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LTCXXCOMPILE) -c -o $@ $<
+
+libcallout_la-callout_library.lo: callout_library.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcallout_la_CPPFLAGS) $(CPPFLAGS) $(libcallout_la_CXXFLAGS) $(CXXFLAGS) -MT libcallout_la-callout_library.lo -MD -MP -MF $(DEPDIR)/libcallout_la-callout_library.Tpo -c -o libcallout_la-callout_library.lo `test -f 'callout_library.cc' || echo '$(srcdir)/'`callout_library.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libcallout_la-callout_library.Tpo $(DEPDIR)/libcallout_la-callout_library.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='callout_library.cc' object='libcallout_la-callout_library.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcallout_la_CPPFLAGS) $(CPPFLAGS) $(libcallout_la_CXXFLAGS) $(CXXFLAGS) -c -o libcallout_la-callout_library.lo `test -f 'callout_library.cc' || echo '$(srcdir)/'`callout_library.cc
+
+libconfigured_la-configured_library.lo: configured_library.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libconfigured_la_CPPFLAGS) $(CPPFLAGS) $(libconfigured_la_CXXFLAGS) $(CXXFLAGS) -MT libconfigured_la-configured_library.lo -MD -MP -MF $(DEPDIR)/libconfigured_la-configured_library.Tpo -c -o libconfigured_la-configured_library.lo `test -f 'configured_library.cc' || echo '$(srcdir)/'`configured_library.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libconfigured_la-configured_library.Tpo $(DEPDIR)/libconfigured_la-configured_library.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='configured_library.cc' object='libconfigured_la-configured_library.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libconfigured_la_CPPFLAGS) $(CPPFLAGS) $(libconfigured_la_CXXFLAGS) $(CXXFLAGS) -c -o libconfigured_la-configured_library.lo `test -f 'configured_library.cc' || echo '$(srcdir)/'`configured_library.cc
+
+d2_unittests-d2_unittests.o: d2_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT d2_unittests-d2_unittests.o -MD -MP -MF $(DEPDIR)/d2_unittests-d2_unittests.Tpo -c -o d2_unittests-d2_unittests.o `test -f 'd2_unittests.cc' || echo '$(srcdir)/'`d2_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/d2_unittests-d2_unittests.Tpo $(DEPDIR)/d2_unittests-d2_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='d2_unittests.cc' object='d2_unittests-d2_unittests.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o d2_unittests-d2_unittests.o `test -f 'd2_unittests.cc' || echo '$(srcdir)/'`d2_unittests.cc
+
+d2_unittests-d2_unittests.obj: d2_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT d2_unittests-d2_unittests.obj -MD -MP -MF $(DEPDIR)/d2_unittests-d2_unittests.Tpo -c -o d2_unittests-d2_unittests.obj `if test -f 'd2_unittests.cc'; then $(CYGPATH_W) 'd2_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/d2_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/d2_unittests-d2_unittests.Tpo $(DEPDIR)/d2_unittests-d2_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='d2_unittests.cc' object='d2_unittests-d2_unittests.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o d2_unittests-d2_unittests.obj `if test -f 'd2_unittests.cc'; then $(CYGPATH_W) 'd2_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/d2_unittests.cc'; fi`
+
+d2_unittests-d2_process_unittests.o: d2_process_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT d2_unittests-d2_process_unittests.o -MD -MP -MF $(DEPDIR)/d2_unittests-d2_process_unittests.Tpo -c -o d2_unittests-d2_process_unittests.o `test -f 'd2_process_unittests.cc' || echo '$(srcdir)/'`d2_process_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/d2_unittests-d2_process_unittests.Tpo $(DEPDIR)/d2_unittests-d2_process_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='d2_process_unittests.cc' object='d2_unittests-d2_process_unittests.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o d2_unittests-d2_process_unittests.o `test -f 'd2_process_unittests.cc' || echo '$(srcdir)/'`d2_process_unittests.cc
+
+d2_unittests-d2_process_unittests.obj: d2_process_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT d2_unittests-d2_process_unittests.obj -MD -MP -MF $(DEPDIR)/d2_unittests-d2_process_unittests.Tpo -c -o d2_unittests-d2_process_unittests.obj `if test -f 'd2_process_unittests.cc'; then $(CYGPATH_W) 'd2_process_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/d2_process_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/d2_unittests-d2_process_unittests.Tpo $(DEPDIR)/d2_unittests-d2_process_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='d2_process_unittests.cc' object='d2_unittests-d2_process_unittests.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o d2_unittests-d2_process_unittests.obj `if test -f 'd2_process_unittests.cc'; then $(CYGPATH_W) 'd2_process_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/d2_process_unittests.cc'; fi`
+
+d2_unittests-d2_cfg_mgr_unittests.o: d2_cfg_mgr_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT d2_unittests-d2_cfg_mgr_unittests.o -MD -MP -MF $(DEPDIR)/d2_unittests-d2_cfg_mgr_unittests.Tpo -c -o d2_unittests-d2_cfg_mgr_unittests.o `test -f 'd2_cfg_mgr_unittests.cc' || echo '$(srcdir)/'`d2_cfg_mgr_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/d2_unittests-d2_cfg_mgr_unittests.Tpo $(DEPDIR)/d2_unittests-d2_cfg_mgr_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='d2_cfg_mgr_unittests.cc' object='d2_unittests-d2_cfg_mgr_unittests.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o d2_unittests-d2_cfg_mgr_unittests.o `test -f 'd2_cfg_mgr_unittests.cc' || echo '$(srcdir)/'`d2_cfg_mgr_unittests.cc
+
+d2_unittests-d2_cfg_mgr_unittests.obj: d2_cfg_mgr_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT d2_unittests-d2_cfg_mgr_unittests.obj -MD -MP -MF $(DEPDIR)/d2_unittests-d2_cfg_mgr_unittests.Tpo -c -o d2_unittests-d2_cfg_mgr_unittests.obj `if test -f 'd2_cfg_mgr_unittests.cc'; then $(CYGPATH_W) 'd2_cfg_mgr_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/d2_cfg_mgr_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/d2_unittests-d2_cfg_mgr_unittests.Tpo $(DEPDIR)/d2_unittests-d2_cfg_mgr_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='d2_cfg_mgr_unittests.cc' object='d2_unittests-d2_cfg_mgr_unittests.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o d2_unittests-d2_cfg_mgr_unittests.obj `if test -f 'd2_cfg_mgr_unittests.cc'; then $(CYGPATH_W) 'd2_cfg_mgr_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/d2_cfg_mgr_unittests.cc'; fi`
+
+d2_unittests-d2_queue_mgr_unittests.o: d2_queue_mgr_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT d2_unittests-d2_queue_mgr_unittests.o -MD -MP -MF $(DEPDIR)/d2_unittests-d2_queue_mgr_unittests.Tpo -c -o d2_unittests-d2_queue_mgr_unittests.o `test -f 'd2_queue_mgr_unittests.cc' || echo '$(srcdir)/'`d2_queue_mgr_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/d2_unittests-d2_queue_mgr_unittests.Tpo $(DEPDIR)/d2_unittests-d2_queue_mgr_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='d2_queue_mgr_unittests.cc' object='d2_unittests-d2_queue_mgr_unittests.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o d2_unittests-d2_queue_mgr_unittests.o `test -f 'd2_queue_mgr_unittests.cc' || echo '$(srcdir)/'`d2_queue_mgr_unittests.cc
+
+d2_unittests-d2_queue_mgr_unittests.obj: d2_queue_mgr_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT d2_unittests-d2_queue_mgr_unittests.obj -MD -MP -MF $(DEPDIR)/d2_unittests-d2_queue_mgr_unittests.Tpo -c -o d2_unittests-d2_queue_mgr_unittests.obj `if test -f 'd2_queue_mgr_unittests.cc'; then $(CYGPATH_W) 'd2_queue_mgr_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/d2_queue_mgr_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/d2_unittests-d2_queue_mgr_unittests.Tpo $(DEPDIR)/d2_unittests-d2_queue_mgr_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='d2_queue_mgr_unittests.cc' object='d2_unittests-d2_queue_mgr_unittests.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o d2_unittests-d2_queue_mgr_unittests.obj `if test -f 'd2_queue_mgr_unittests.cc'; then $(CYGPATH_W) 'd2_queue_mgr_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/d2_queue_mgr_unittests.cc'; fi`
+
+d2_unittests-d2_update_mgr_unittests.o: d2_update_mgr_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT d2_unittests-d2_update_mgr_unittests.o -MD -MP -MF $(DEPDIR)/d2_unittests-d2_update_mgr_unittests.Tpo -c -o d2_unittests-d2_update_mgr_unittests.o `test -f 'd2_update_mgr_unittests.cc' || echo '$(srcdir)/'`d2_update_mgr_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/d2_unittests-d2_update_mgr_unittests.Tpo $(DEPDIR)/d2_unittests-d2_update_mgr_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='d2_update_mgr_unittests.cc' object='d2_unittests-d2_update_mgr_unittests.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o d2_unittests-d2_update_mgr_unittests.o `test -f 'd2_update_mgr_unittests.cc' || echo '$(srcdir)/'`d2_update_mgr_unittests.cc
+
+d2_unittests-d2_update_mgr_unittests.obj: d2_update_mgr_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT d2_unittests-d2_update_mgr_unittests.obj -MD -MP -MF $(DEPDIR)/d2_unittests-d2_update_mgr_unittests.Tpo -c -o d2_unittests-d2_update_mgr_unittests.obj `if test -f 'd2_update_mgr_unittests.cc'; then $(CYGPATH_W) 'd2_update_mgr_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/d2_update_mgr_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/d2_unittests-d2_update_mgr_unittests.Tpo $(DEPDIR)/d2_unittests-d2_update_mgr_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='d2_update_mgr_unittests.cc' object='d2_unittests-d2_update_mgr_unittests.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o d2_unittests-d2_update_mgr_unittests.obj `if test -f 'd2_update_mgr_unittests.cc'; then $(CYGPATH_W) 'd2_update_mgr_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/d2_update_mgr_unittests.cc'; fi`
+
+d2_unittests-nc_add_unittests.o: nc_add_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT d2_unittests-nc_add_unittests.o -MD -MP -MF $(DEPDIR)/d2_unittests-nc_add_unittests.Tpo -c -o d2_unittests-nc_add_unittests.o `test -f 'nc_add_unittests.cc' || echo '$(srcdir)/'`nc_add_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/d2_unittests-nc_add_unittests.Tpo $(DEPDIR)/d2_unittests-nc_add_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='nc_add_unittests.cc' object='d2_unittests-nc_add_unittests.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o d2_unittests-nc_add_unittests.o `test -f 'nc_add_unittests.cc' || echo '$(srcdir)/'`nc_add_unittests.cc
+
+d2_unittests-nc_add_unittests.obj: nc_add_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT d2_unittests-nc_add_unittests.obj -MD -MP -MF $(DEPDIR)/d2_unittests-nc_add_unittests.Tpo -c -o d2_unittests-nc_add_unittests.obj `if test -f 'nc_add_unittests.cc'; then $(CYGPATH_W) 'nc_add_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/nc_add_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/d2_unittests-nc_add_unittests.Tpo $(DEPDIR)/d2_unittests-nc_add_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='nc_add_unittests.cc' object='d2_unittests-nc_add_unittests.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o d2_unittests-nc_add_unittests.obj `if test -f 'nc_add_unittests.cc'; then $(CYGPATH_W) 'nc_add_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/nc_add_unittests.cc'; fi`
+
+d2_unittests-nc_remove_unittests.o: nc_remove_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT d2_unittests-nc_remove_unittests.o -MD -MP -MF $(DEPDIR)/d2_unittests-nc_remove_unittests.Tpo -c -o d2_unittests-nc_remove_unittests.o `test -f 'nc_remove_unittests.cc' || echo '$(srcdir)/'`nc_remove_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/d2_unittests-nc_remove_unittests.Tpo $(DEPDIR)/d2_unittests-nc_remove_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='nc_remove_unittests.cc' object='d2_unittests-nc_remove_unittests.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o d2_unittests-nc_remove_unittests.o `test -f 'nc_remove_unittests.cc' || echo '$(srcdir)/'`nc_remove_unittests.cc
+
+d2_unittests-nc_remove_unittests.obj: nc_remove_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT d2_unittests-nc_remove_unittests.obj -MD -MP -MF $(DEPDIR)/d2_unittests-nc_remove_unittests.Tpo -c -o d2_unittests-nc_remove_unittests.obj `if test -f 'nc_remove_unittests.cc'; then $(CYGPATH_W) 'nc_remove_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/nc_remove_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/d2_unittests-nc_remove_unittests.Tpo $(DEPDIR)/d2_unittests-nc_remove_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='nc_remove_unittests.cc' object='d2_unittests-nc_remove_unittests.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o d2_unittests-nc_remove_unittests.obj `if test -f 'nc_remove_unittests.cc'; then $(CYGPATH_W) 'nc_remove_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/nc_remove_unittests.cc'; fi`
+
+d2_unittests-d2_controller_unittests.o: d2_controller_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT d2_unittests-d2_controller_unittests.o -MD -MP -MF $(DEPDIR)/d2_unittests-d2_controller_unittests.Tpo -c -o d2_unittests-d2_controller_unittests.o `test -f 'd2_controller_unittests.cc' || echo '$(srcdir)/'`d2_controller_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/d2_unittests-d2_controller_unittests.Tpo $(DEPDIR)/d2_unittests-d2_controller_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='d2_controller_unittests.cc' object='d2_unittests-d2_controller_unittests.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o d2_unittests-d2_controller_unittests.o `test -f 'd2_controller_unittests.cc' || echo '$(srcdir)/'`d2_controller_unittests.cc
+
+d2_unittests-d2_controller_unittests.obj: d2_controller_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT d2_unittests-d2_controller_unittests.obj -MD -MP -MF $(DEPDIR)/d2_unittests-d2_controller_unittests.Tpo -c -o d2_unittests-d2_controller_unittests.obj `if test -f 'd2_controller_unittests.cc'; then $(CYGPATH_W) 'd2_controller_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/d2_controller_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/d2_unittests-d2_controller_unittests.Tpo $(DEPDIR)/d2_unittests-d2_controller_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='d2_controller_unittests.cc' object='d2_unittests-d2_controller_unittests.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o d2_unittests-d2_controller_unittests.obj `if test -f 'd2_controller_unittests.cc'; then $(CYGPATH_W) 'd2_controller_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/d2_controller_unittests.cc'; fi`
+
+d2_unittests-d2_simple_parser_unittest.o: d2_simple_parser_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT d2_unittests-d2_simple_parser_unittest.o -MD -MP -MF $(DEPDIR)/d2_unittests-d2_simple_parser_unittest.Tpo -c -o d2_unittests-d2_simple_parser_unittest.o `test -f 'd2_simple_parser_unittest.cc' || echo '$(srcdir)/'`d2_simple_parser_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/d2_unittests-d2_simple_parser_unittest.Tpo $(DEPDIR)/d2_unittests-d2_simple_parser_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='d2_simple_parser_unittest.cc' object='d2_unittests-d2_simple_parser_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o d2_unittests-d2_simple_parser_unittest.o `test -f 'd2_simple_parser_unittest.cc' || echo '$(srcdir)/'`d2_simple_parser_unittest.cc
+
+d2_unittests-d2_simple_parser_unittest.obj: d2_simple_parser_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT d2_unittests-d2_simple_parser_unittest.obj -MD -MP -MF $(DEPDIR)/d2_unittests-d2_simple_parser_unittest.Tpo -c -o d2_unittests-d2_simple_parser_unittest.obj `if test -f 'd2_simple_parser_unittest.cc'; then $(CYGPATH_W) 'd2_simple_parser_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/d2_simple_parser_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/d2_unittests-d2_simple_parser_unittest.Tpo $(DEPDIR)/d2_unittests-d2_simple_parser_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='d2_simple_parser_unittest.cc' object='d2_unittests-d2_simple_parser_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o d2_unittests-d2_simple_parser_unittest.obj `if test -f 'd2_simple_parser_unittest.cc'; then $(CYGPATH_W) 'd2_simple_parser_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/d2_simple_parser_unittest.cc'; fi`
+
+d2_unittests-parser_unittest.o: parser_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT d2_unittests-parser_unittest.o -MD -MP -MF $(DEPDIR)/d2_unittests-parser_unittest.Tpo -c -o d2_unittests-parser_unittest.o `test -f 'parser_unittest.cc' || echo '$(srcdir)/'`parser_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/d2_unittests-parser_unittest.Tpo $(DEPDIR)/d2_unittests-parser_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='parser_unittest.cc' object='d2_unittests-parser_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o d2_unittests-parser_unittest.o `test -f 'parser_unittest.cc' || echo '$(srcdir)/'`parser_unittest.cc
+
+d2_unittests-parser_unittest.obj: parser_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT d2_unittests-parser_unittest.obj -MD -MP -MF $(DEPDIR)/d2_unittests-parser_unittest.Tpo -c -o d2_unittests-parser_unittest.obj `if test -f 'parser_unittest.cc'; then $(CYGPATH_W) 'parser_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/parser_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/d2_unittests-parser_unittest.Tpo $(DEPDIR)/d2_unittests-parser_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='parser_unittest.cc' object='d2_unittests-parser_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o d2_unittests-parser_unittest.obj `if test -f 'parser_unittest.cc'; then $(CYGPATH_W) 'parser_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/parser_unittest.cc'; fi`
+
+d2_unittests-get_config_unittest.o: get_config_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT d2_unittests-get_config_unittest.o -MD -MP -MF $(DEPDIR)/d2_unittests-get_config_unittest.Tpo -c -o d2_unittests-get_config_unittest.o `test -f 'get_config_unittest.cc' || echo '$(srcdir)/'`get_config_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/d2_unittests-get_config_unittest.Tpo $(DEPDIR)/d2_unittests-get_config_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='get_config_unittest.cc' object='d2_unittests-get_config_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o d2_unittests-get_config_unittest.o `test -f 'get_config_unittest.cc' || echo '$(srcdir)/'`get_config_unittest.cc
+
+d2_unittests-get_config_unittest.obj: get_config_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT d2_unittests-get_config_unittest.obj -MD -MP -MF $(DEPDIR)/d2_unittests-get_config_unittest.Tpo -c -o d2_unittests-get_config_unittest.obj `if test -f 'get_config_unittest.cc'; then $(CYGPATH_W) 'get_config_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/get_config_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/d2_unittests-get_config_unittest.Tpo $(DEPDIR)/d2_unittests-get_config_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='get_config_unittest.cc' object='d2_unittests-get_config_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o d2_unittests-get_config_unittest.obj `if test -f 'get_config_unittest.cc'; then $(CYGPATH_W) 'get_config_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/get_config_unittest.cc'; fi`
+
+d2_unittests-d2_command_unittest.o: d2_command_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT d2_unittests-d2_command_unittest.o -MD -MP -MF $(DEPDIR)/d2_unittests-d2_command_unittest.Tpo -c -o d2_unittests-d2_command_unittest.o `test -f 'd2_command_unittest.cc' || echo '$(srcdir)/'`d2_command_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/d2_unittests-d2_command_unittest.Tpo $(DEPDIR)/d2_unittests-d2_command_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='d2_command_unittest.cc' object='d2_unittests-d2_command_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o d2_unittests-d2_command_unittest.o `test -f 'd2_command_unittest.cc' || echo '$(srcdir)/'`d2_command_unittest.cc
+
+d2_unittests-d2_command_unittest.obj: d2_command_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT d2_unittests-d2_command_unittest.obj -MD -MP -MF $(DEPDIR)/d2_unittests-d2_command_unittest.Tpo -c -o d2_unittests-d2_command_unittest.obj `if test -f 'd2_command_unittest.cc'; then $(CYGPATH_W) 'd2_command_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/d2_command_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/d2_unittests-d2_command_unittest.Tpo $(DEPDIR)/d2_unittests-d2_command_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='d2_command_unittest.cc' object='d2_unittests-d2_command_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o d2_unittests-d2_command_unittest.obj `if test -f 'd2_command_unittest.cc'; then $(CYGPATH_W) 'd2_command_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/d2_command_unittest.cc'; fi`
+
+d2_unittests-simple_add_unittests.o: simple_add_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT d2_unittests-simple_add_unittests.o -MD -MP -MF $(DEPDIR)/d2_unittests-simple_add_unittests.Tpo -c -o d2_unittests-simple_add_unittests.o `test -f 'simple_add_unittests.cc' || echo '$(srcdir)/'`simple_add_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/d2_unittests-simple_add_unittests.Tpo $(DEPDIR)/d2_unittests-simple_add_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='simple_add_unittests.cc' object='d2_unittests-simple_add_unittests.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o d2_unittests-simple_add_unittests.o `test -f 'simple_add_unittests.cc' || echo '$(srcdir)/'`simple_add_unittests.cc
+
+d2_unittests-simple_add_unittests.obj: simple_add_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT d2_unittests-simple_add_unittests.obj -MD -MP -MF $(DEPDIR)/d2_unittests-simple_add_unittests.Tpo -c -o d2_unittests-simple_add_unittests.obj `if test -f 'simple_add_unittests.cc'; then $(CYGPATH_W) 'simple_add_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/simple_add_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/d2_unittests-simple_add_unittests.Tpo $(DEPDIR)/d2_unittests-simple_add_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='simple_add_unittests.cc' object='d2_unittests-simple_add_unittests.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o d2_unittests-simple_add_unittests.obj `if test -f 'simple_add_unittests.cc'; then $(CYGPATH_W) 'simple_add_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/simple_add_unittests.cc'; fi`
+
+d2_unittests-simple_remove_unittests.o: simple_remove_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT d2_unittests-simple_remove_unittests.o -MD -MP -MF $(DEPDIR)/d2_unittests-simple_remove_unittests.Tpo -c -o d2_unittests-simple_remove_unittests.o `test -f 'simple_remove_unittests.cc' || echo '$(srcdir)/'`simple_remove_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/d2_unittests-simple_remove_unittests.Tpo $(DEPDIR)/d2_unittests-simple_remove_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='simple_remove_unittests.cc' object='d2_unittests-simple_remove_unittests.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o d2_unittests-simple_remove_unittests.o `test -f 'simple_remove_unittests.cc' || echo '$(srcdir)/'`simple_remove_unittests.cc
+
+d2_unittests-simple_remove_unittests.obj: simple_remove_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT d2_unittests-simple_remove_unittests.obj -MD -MP -MF $(DEPDIR)/d2_unittests-simple_remove_unittests.Tpo -c -o d2_unittests-simple_remove_unittests.obj `if test -f 'simple_remove_unittests.cc'; then $(CYGPATH_W) 'simple_remove_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/simple_remove_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/d2_unittests-simple_remove_unittests.Tpo $(DEPDIR)/d2_unittests-simple_remove_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='simple_remove_unittests.cc' object='d2_unittests-simple_remove_unittests.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o d2_unittests-simple_remove_unittests.obj `if test -f 'simple_remove_unittests.cc'; then $(CYGPATH_W) 'simple_remove_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/simple_remove_unittests.cc'; fi`
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+# This directory's subdirectories are mostly independent; you can cd
+# into them and run 'make' without going through this Makefile.
+# To change the values of 'make' variables: instead of editing Makefiles,
+# (1) if the variable is set in 'config.status', edit 'config.status'
+# (which will cause the Makefiles to be regenerated when you run 'make');
+# (2) otherwise, pass the desired values on the 'make' command line.
+$(am__recursive_targets):
+ @fail=; \
+ if $(am__make_keepgoing); then \
+ failcom='fail=yes'; \
+ else \
+ failcom='exit 1'; \
+ fi; \
+ dot_seen=no; \
+ target=`echo $@ | sed s/-recursive//`; \
+ case "$@" in \
+ distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \
+ *) list='$(SUBDIRS)' ;; \
+ esac; \
+ for subdir in $$list; do \
+ echo "Making $$target in $$subdir"; \
+ if test "$$subdir" = "."; then \
+ dot_seen=yes; \
+ local_target="$$target-am"; \
+ else \
+ local_target="$$target"; \
+ fi; \
+ ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \
+ || eval $$failcom; \
+ done; \
+ if test "$$dot_seen" = "no"; then \
+ $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \
+ fi; test -z "$$fail"
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-recursive
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \
+ include_option=--etags-include; \
+ empty_fix=.; \
+ else \
+ include_option=--include; \
+ empty_fix=; \
+ fi; \
+ list='$(SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ test ! -f $$subdir/TAGS || \
+ set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \
+ fi; \
+ done; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-recursive
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-recursive
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+check-TESTS: $(TESTS)
+ @failed=0; all=0; xfail=0; xpass=0; skip=0; \
+ srcdir=$(srcdir); export srcdir; \
+ list=' $(TESTS) '; \
+ $(am__tty_colors); \
+ if test -n "$$list"; then \
+ for tst in $$list; do \
+ if test -f ./$$tst; then dir=./; \
+ elif test -f $$tst; then dir=; \
+ else dir="$(srcdir)/"; fi; \
+ if $(TESTS_ENVIRONMENT) $${dir}$$tst $(AM_TESTS_FD_REDIRECT); then \
+ all=`expr $$all + 1`; \
+ case " $(XFAIL_TESTS) " in \
+ *[\ \ ]$$tst[\ \ ]*) \
+ xpass=`expr $$xpass + 1`; \
+ failed=`expr $$failed + 1`; \
+ col=$$red; res=XPASS; \
+ ;; \
+ *) \
+ col=$$grn; res=PASS; \
+ ;; \
+ esac; \
+ elif test $$? -ne 77; then \
+ all=`expr $$all + 1`; \
+ case " $(XFAIL_TESTS) " in \
+ *[\ \ ]$$tst[\ \ ]*) \
+ xfail=`expr $$xfail + 1`; \
+ col=$$lgn; res=XFAIL; \
+ ;; \
+ *) \
+ failed=`expr $$failed + 1`; \
+ col=$$red; res=FAIL; \
+ ;; \
+ esac; \
+ else \
+ skip=`expr $$skip + 1`; \
+ col=$$blu; res=SKIP; \
+ fi; \
+ echo "$${col}$$res$${std}: $$tst"; \
+ done; \
+ if test "$$all" -eq 1; then \
+ tests="test"; \
+ All=""; \
+ else \
+ tests="tests"; \
+ All="All "; \
+ fi; \
+ if test "$$failed" -eq 0; then \
+ if test "$$xfail" -eq 0; then \
+ banner="$$All$$all $$tests passed"; \
+ else \
+ if test "$$xfail" -eq 1; then failures=failure; else failures=failures; fi; \
+ banner="$$All$$all $$tests behaved as expected ($$xfail expected $$failures)"; \
+ fi; \
+ else \
+ if test "$$xpass" -eq 0; then \
+ banner="$$failed of $$all $$tests failed"; \
+ else \
+ if test "$$xpass" -eq 1; then passes=pass; else passes=passes; fi; \
+ banner="$$failed of $$all $$tests did not behave as expected ($$xpass unexpected $$passes)"; \
+ fi; \
+ fi; \
+ dashes="$$banner"; \
+ skipped=""; \
+ if test "$$skip" -ne 0; then \
+ if test "$$skip" -eq 1; then \
+ skipped="($$skip test was not run)"; \
+ else \
+ skipped="($$skip tests were not run)"; \
+ fi; \
+ test `echo "$$skipped" | wc -c` -le `echo "$$banner" | wc -c` || \
+ dashes="$$skipped"; \
+ fi; \
+ report=""; \
+ if test "$$failed" -ne 0 && test -n "$(PACKAGE_BUGREPORT)"; then \
+ report="Please report to $(PACKAGE_BUGREPORT)"; \
+ test `echo "$$report" | wc -c` -le `echo "$$banner" | wc -c` || \
+ dashes="$$report"; \
+ fi; \
+ dashes=`echo "$$dashes" | sed s/./=/g`; \
+ if test "$$failed" -eq 0; then \
+ col="$$grn"; \
+ else \
+ col="$$red"; \
+ fi; \
+ echo "$${col}$$dashes$${std}"; \
+ echo "$${col}$$banner$${std}"; \
+ test -z "$$skipped" || echo "$${col}$$skipped$${std}"; \
+ test -z "$$report" || echo "$${col}$$report$${std}"; \
+ echo "$${col}$$dashes$${std}"; \
+ test "$$failed" -eq 0; \
+ else :; fi
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+ @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ $(am__make_dryrun) \
+ || test -d "$(distdir)/$$subdir" \
+ || $(MKDIR_P) "$(distdir)/$$subdir" \
+ || exit 1; \
+ dir1=$$subdir; dir2="$(distdir)/$$subdir"; \
+ $(am__relativize); \
+ new_distdir=$$reldir; \
+ dir1=$$subdir; dir2="$(top_distdir)"; \
+ $(am__relativize); \
+ new_top_distdir=$$reldir; \
+ echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \
+ echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \
+ ($(am__cd) $$subdir && \
+ $(MAKE) $(AM_MAKEFLAGS) \
+ top_distdir="$$new_top_distdir" \
+ distdir="$$new_distdir" \
+ am__remove_distdir=: \
+ am__skip_length_check=: \
+ am__skip_mode_fix=: \
+ distdir) \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+ $(MAKE) $(AM_MAKEFLAGS) $(check_SCRIPTS)
+ $(MAKE) $(AM_MAKEFLAGS) check-TESTS
+check: check-recursive
+all-am: Makefile $(PROGRAMS) $(LTLIBRARIES) $(SCRIPTS)
+installdirs: installdirs-recursive
+installdirs-am:
+install: install-recursive
+install-exec: install-exec-recursive
+install-data: install-data-recursive
+uninstall: uninstall-recursive
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-recursive
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+ -test -z "$(DISTCLEANFILES)" || rm -f $(DISTCLEANFILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-recursive
+
+clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ clean-noinstPROGRAMS mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -f ./$(DEPDIR)/d2_unittests-d2_cfg_mgr_unittests.Po
+ -rm -f ./$(DEPDIR)/d2_unittests-d2_command_unittest.Po
+ -rm -f ./$(DEPDIR)/d2_unittests-d2_controller_unittests.Po
+ -rm -f ./$(DEPDIR)/d2_unittests-d2_process_unittests.Po
+ -rm -f ./$(DEPDIR)/d2_unittests-d2_queue_mgr_unittests.Po
+ -rm -f ./$(DEPDIR)/d2_unittests-d2_simple_parser_unittest.Po
+ -rm -f ./$(DEPDIR)/d2_unittests-d2_unittests.Po
+ -rm -f ./$(DEPDIR)/d2_unittests-d2_update_mgr_unittests.Po
+ -rm -f ./$(DEPDIR)/d2_unittests-get_config_unittest.Po
+ -rm -f ./$(DEPDIR)/d2_unittests-nc_add_unittests.Po
+ -rm -f ./$(DEPDIR)/d2_unittests-nc_remove_unittests.Po
+ -rm -f ./$(DEPDIR)/d2_unittests-parser_unittest.Po
+ -rm -f ./$(DEPDIR)/d2_unittests-simple_add_unittests.Po
+ -rm -f ./$(DEPDIR)/d2_unittests-simple_remove_unittests.Po
+ -rm -f ./$(DEPDIR)/libcallout_la-callout_library.Plo
+ -rm -f ./$(DEPDIR)/libconfigured_la-configured_library.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-recursive
+
+dvi-am:
+
+html: html-recursive
+
+html-am:
+
+info: info-recursive
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-recursive
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-recursive
+
+install-html-am:
+
+install-info: install-info-recursive
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-recursive
+
+install-pdf-am:
+
+install-ps: install-ps-recursive
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-recursive
+ -rm -f ./$(DEPDIR)/d2_unittests-d2_cfg_mgr_unittests.Po
+ -rm -f ./$(DEPDIR)/d2_unittests-d2_command_unittest.Po
+ -rm -f ./$(DEPDIR)/d2_unittests-d2_controller_unittests.Po
+ -rm -f ./$(DEPDIR)/d2_unittests-d2_process_unittests.Po
+ -rm -f ./$(DEPDIR)/d2_unittests-d2_queue_mgr_unittests.Po
+ -rm -f ./$(DEPDIR)/d2_unittests-d2_simple_parser_unittest.Po
+ -rm -f ./$(DEPDIR)/d2_unittests-d2_unittests.Po
+ -rm -f ./$(DEPDIR)/d2_unittests-d2_update_mgr_unittests.Po
+ -rm -f ./$(DEPDIR)/d2_unittests-get_config_unittest.Po
+ -rm -f ./$(DEPDIR)/d2_unittests-nc_add_unittests.Po
+ -rm -f ./$(DEPDIR)/d2_unittests-nc_remove_unittests.Po
+ -rm -f ./$(DEPDIR)/d2_unittests-parser_unittest.Po
+ -rm -f ./$(DEPDIR)/d2_unittests-simple_add_unittests.Po
+ -rm -f ./$(DEPDIR)/d2_unittests-simple_remove_unittests.Po
+ -rm -f ./$(DEPDIR)/libcallout_la-callout_library.Plo
+ -rm -f ./$(DEPDIR)/libconfigured_la-configured_library.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-recursive
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-recursive
+
+pdf-am:
+
+ps: ps-recursive
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: $(am__recursive_targets) check-am install-am install-strip
+
+.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am \
+ am--depfiles check check-TESTS check-am clean clean-generic \
+ clean-libtool clean-noinstLTLIBRARIES clean-noinstPROGRAMS \
+ cscopelist-am ctags ctags-am distclean distclean-compile \
+ distclean-generic distclean-libtool distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-man install-pdf \
+ install-pdf-am install-ps install-ps-am install-strip \
+ installcheck installcheck-am installdirs installdirs-am \
+ maintainer-clean maintainer-clean-generic mostlyclean \
+ mostlyclean-compile mostlyclean-generic mostlyclean-libtool \
+ pdf pdf-am ps ps-am tags tags-am uninstall uninstall-am
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/bin/d2/tests/callout_library.cc b/src/bin/d2/tests/callout_library.cc
new file mode 100644
index 0000000..8587eba
--- /dev/null
+++ b/src/bin/d2/tests/callout_library.cc
@@ -0,0 +1,72 @@
+// Copyright (C) 2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+/// @file
+/// @brief Basic callout library
+///
+/// This is source of a test library for Control Agent.
+///
+/// - Only the "version" framework function is supplied.
+///
+/// - hookpt_one callout is supplied.
+
+#include <config.h>
+#include <hooks/hooks.h>
+
+using namespace isc::hooks;
+using namespace std;
+
+namespace {
+
+extern "C" {
+
+// Callouts. All return their result through the "result" argument.
+
+int
+context_create(CalloutHandle& handle) {
+ handle.setContext("result", static_cast<int>(10));
+ handle.setArgument("result", static_cast<int>(10));
+ return (0);
+}
+
+// First callout adds the passed "integer" argument to the initialized context
+// value of 10. (Note that the value set by context_create is accessed through
+// context and not the argument, so checking that context is correctly passed
+// between callouts in the same library.)
+
+int
+hookpt_one(CalloutHandle& handle) {
+ int data;
+ handle.getArgument("integer", data);
+
+ int result;
+ handle.getArgument("result", result);
+
+ result += data;
+ handle.setArgument("result", result);
+
+ return (0);
+}
+
+// Framework functions.
+
+int
+version() {
+ return (KEA_HOOKS_VERSION);
+}
+
+// load() initializes the user library if the main image was statically linked.
+int
+load(isc::hooks::LibraryHandle&) {
+#ifdef USE_STATIC_LINK
+ hooksStaticLinkInit();
+#endif
+ return (0);
+}
+
+}
+}
+
diff --git a/src/bin/d2/tests/configured_library.cc b/src/bin/d2/tests/configured_library.cc
new file mode 100644
index 0000000..3aeb94d
--- /dev/null
+++ b/src/bin/d2/tests/configured_library.cc
@@ -0,0 +1,63 @@
+// Copyright (C) 2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+/// @brief d2_srv_configured callout testing library
+
+#include <config.h>
+#include <cc/data.h>
+#include <hooks/hooks.h>
+
+using namespace isc::data;
+using namespace isc::hooks;
+using namespace std;
+
+namespace {
+
+extern "C" {
+
+// d2_srv_configured callout.
+int
+d2_srv_configured(CalloutHandle& handle) {
+ // Get the parameters.
+ ConstElementPtr cfg;
+ string error;
+ handle.getArgument("json_config", cfg);
+ handle.getArgument("error", error);
+
+ if (cfg) {
+ ConstElementPtr uc = cfg->get("user-context");
+ if (uc) {
+ ConstElementPtr msg = uc->get("error");
+ if (msg && (msg->getType() == Element::string)) {
+ error += msg->stringValue();
+ }
+ }
+ }
+
+ if (!error.empty()) {
+ handle.setArgument("error", error);
+ handle.setStatus(CalloutHandle::NEXT_STEP_DROP);
+ }
+ return (0);
+}
+
+// Framework functions.
+int
+version() {
+ return (KEA_HOOKS_VERSION);
+}
+
+// load() initializes the user library if the main image was statically linked.
+int
+load(isc::hooks::LibraryHandle&) {
+#ifdef USE_STATIC_LINK
+ hooksStaticLinkInit();
+#endif
+ return (0);
+}
+
+}
+}
diff --git a/src/bin/d2/tests/d2_cfg_mgr_unittests.cc b/src/bin/d2/tests/d2_cfg_mgr_unittests.cc
new file mode 100644
index 0000000..5e27ff8
--- /dev/null
+++ b/src/bin/d2/tests/d2_cfg_mgr_unittests.cc
@@ -0,0 +1,1080 @@
+// Copyright (C) 2013-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <d2/parser_context.h>
+#include <d2/tests/parser_unittest.h>
+#include <d2/tests/test_callout_libraries.h>
+#include <d2srv/d2_cfg_mgr.h>
+#include <d2srv/d2_config.h>
+#include <d2srv/d2_simple_parser.h>
+#include <dhcpsrv/testutils/config_result_check.h>
+#include <process/testutils/d_test_stubs.h>
+#include <test_data_files_config.h>
+#include <util/encode/base64.h>
+
+#include <boost/foreach.hpp>
+#include <boost/scoped_ptr.hpp>
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::d2;
+using namespace isc::hooks;
+using namespace isc::process;
+
+namespace {
+
+/// @brief Function to create full path to test data file
+///
+/// The full path is dependent upon the value of D2_TEST_DATA_DIR which
+/// whose value is generated from test_data_files_config.h.in
+///
+/// @param name file name to which the path should be prepended
+std::string testDataFile(const std::string& name) {
+ return (std::string(D2_TEST_DATA_DIR) + "/" + name);
+}
+
+/// @brief Test fixture class for testing D2CfgMgr class.
+/// It maintains an member instance of D2CfgMgr and provides methods for
+/// converting JSON strings to configuration element sets, checking parse
+/// results, and accessing the configuration context.
+class D2CfgMgrTest : public ConfigParseTest {
+public:
+
+ /// @brief Constructor
+ D2CfgMgrTest():cfg_mgr_(new D2CfgMgr()), d2_params_() {
+ }
+
+ /// @brief Destructor
+ ~D2CfgMgrTest() {
+ }
+
+ /// @brief Configuration manager instance.
+ D2CfgMgrPtr cfg_mgr_;
+
+ /// @brief Build JSON configuration string for a D2Params element
+ ///
+ /// Constructs a JSON string for "params" element using replaceable
+ /// parameters.
+ ///
+ /// @param ip_address string to insert as ip_address value
+ /// @param port integer to insert as port value
+ /// @param dns_server_timeout integer to insert as dns_server_timeout value
+ /// @param ncr_protocol string to insert as ncr_protocol value
+ /// @param ncr_format string to insert as ncr_format value
+ ///
+ /// @return std::string containing the JSON configuration text
+ std::string makeParamsConfigString(const std::string& ip_address,
+ const int port,
+ const int dns_server_timeout,
+ const std::string& ncr_protocol,
+ const std::string& ncr_format) {
+ std::ostringstream config;
+ config <<
+ "{"
+ " \"ip-address\": \"" << ip_address << "\" , "
+ " \"port\": " << port << " , "
+ " \"dns-server-timeout\": " << dns_server_timeout << " , "
+ " \"ncr-protocol\": \"" << ncr_protocol << "\" , "
+ " \"ncr-format\": \"" << ncr_format << "\", "
+ " \"tsig-keys\": [], "
+ " \"forward-ddns\" : {}, "
+ " \"reverse-ddns\" : {} "
+ "}";
+
+ return (config.str());
+ }
+
+ /// @brief Enumeration to select between expected configuration outcomes
+ enum RunConfigMode {
+ NO_ERROR,
+ SYNTAX_ERROR,
+ LOGIC_ERROR
+ };
+
+ /// @brief Parses a configuration string and tests against a given outcome
+ ///
+ /// Convenience method which accepts JSON text and an expected pass or fail
+ /// outcome. It uses the D2ParserContext to parse the text under the
+ /// PARSE_SUB_DHCPDDNS context, then adds the D2 defaults to the resultant
+ /// element tree. Assuming that's successful the element tree is passed
+ /// to D2CfgMgr::parseConfig() method.
+ ///
+ /// @param config_str the JSON configuration text to parse
+ /// @param error_type indicates the type error expected, NONE, SYNTAX,
+ /// or LOGIC. SYNTAX errors are emitted by JSON parser, logic errors
+ /// are emitted by element parser(s).
+ /// @param exp_error exact text of the error message expected
+ /// defaults to SHOULD_PASS.
+ ///
+ /// @return AssertionSuccess if test passes, AssertionFailure otherwise
+ ::testing::AssertionResult runConfigOrFail(const std::string& json,
+ const RunConfigMode mode,
+ const std::string& exp_error) {
+
+ try {
+ // Invoke the JSON parser, casting the returned element tree
+ // into mutable form.
+ D2ParserContext parser_context;
+ data::ElementPtr elem =
+ boost::const_pointer_cast<Element>
+ (parser_context.parseString(json, D2ParserContext::
+ PARSER_SUB_DHCPDDNS));
+
+ // If parsing succeeded when we expected a syntax error, then fail.
+ if (mode == SYNTAX_ERROR) {
+ return ::testing::AssertionFailure()
+ << "Unexpected JSON parsing success"
+ << "\njson: [" << json << " ]";
+ }
+
+ // JSON parsed ok, so add the defaults to the element tree it produced.
+ D2SimpleParser::setAllDefaults(elem);
+ config_set_ = elem;
+ } catch (const std::exception& ex) {
+ // JSON Parsing failed
+ if (exp_error.empty()) {
+ // We did not expect an error, so fail.
+ return ::testing::AssertionFailure()
+ << "Unexpected syntax error:" << ex.what()
+ << "\njson: [" << json << " ]";
+ }
+
+ if (ex.what() != exp_error) {
+ // Expected an error not the one we got, so fail
+ return ::testing::AssertionFailure()
+ << "Wrong syntax error detected, expected: "
+ << exp_error << ", got: " << ex.what()
+ << "\njson: [" << json << " ]";
+ }
+
+ // We go the syntax error we expected, so return success
+ return ::testing::AssertionSuccess();
+ }
+
+ // The JSON parsed ok and we've added the defaults, pass the config
+ // into the Element parser and check for the expected outcome.
+ data::ConstElementPtr answer;
+ answer = cfg_mgr_->simpleParseConfig(config_set_, false);
+
+ // Extract the result and error text from the answer.
+ int rcode = 0;
+ isc::data::ConstElementPtr comment;
+ comment = isc::config::parseAnswer(rcode, answer);
+
+ if (rcode != 0) {
+ // Element Parsing failed.
+ if (exp_error.empty()) {
+ // We didn't expect it to, fail the test.
+ return ::testing::AssertionFailure()
+ << "Unexpected logic error: " << *comment
+ << "\njson: [" << json << " ]";
+ }
+
+ if (comment->stringValue() != exp_error) {
+ // We 't expect a different error, fail the test.
+ return ::testing::AssertionFailure()
+ << "Wrong logic error detected, expected: "
+ << exp_error << ", got: " << *comment
+ << "\njson: [" << json << " ]";
+ }
+ } else {
+ // Element parsing succeeded.
+ if (!exp_error.empty()) {
+ // It was supposed to fail, so fail the test.
+ return ::testing::AssertionFailure()
+ << "Unexpected logic success, expected error:"
+ << exp_error
+ << "\njson: [" << json << " ]";
+ }
+ }
+
+ // Verify that the D2 context can be retrieved and is not null.
+ D2CfgContextPtr context;
+ context = cfg_mgr_->getD2CfgContext();
+ if (!context) {
+ return ::testing::AssertionFailure() << "D2CfgContext is null";
+ }
+
+ // Verify that the global scalar container has been created.
+ d2_params_ = context->getD2Params();
+ if (!d2_params_) {
+ return ::testing::AssertionFailure() << "D2Params is null";
+ }
+
+ return ::testing::AssertionSuccess();
+ }
+
+ /// @brief Replaces %LIBRARY% with specified library name
+ ///
+ /// @param config input config text (should contain "%LIBRARY%" string)
+ /// @param lib_name %LIBRARY% will be replaced with that name
+ /// @return configuration text with library name replaced
+ std::string pathReplacer(const char* config, const char* lib_name) {
+ string txt(config);
+ txt.replace(txt.find("%LIBRARY%"), strlen("%LIBRARY%"), string(lib_name));
+ return (txt);
+ }
+
+ /// @brief Pointer the D2Params most recently parsed.
+ D2ParamsPtr d2_params_;
+};
+
+/// @brief Convenience macros for invoking runOrConfig()
+#define RUN_CONFIG_OK(a) (runConfigOrFail(a, NO_ERROR, ""))
+#define SYNTAX_ERROR(a,b) ASSERT_TRUE(runConfigOrFail(a,SYNTAX_ERROR,b))
+#define LOGIC_ERROR(a,b) ASSERT_TRUE(runConfigOrFail(a,LOGIC_ERROR,b))
+
+/// @brief Tests a basic valid configuration for D2Param.
+TEST_F(D2CfgMgrTest, validParamsEntry) {
+ // Verify that ip_address can be valid v4 address.
+ std::string config = makeParamsConfigString ("192.0.0.1", 777, 333,
+ "UDP", "JSON");
+ RUN_CONFIG_OK(config);
+
+ EXPECT_EQ(isc::asiolink::IOAddress("192.0.0.1"),
+ d2_params_->getIpAddress());
+ EXPECT_EQ(777, d2_params_->getPort());
+ EXPECT_EQ(333, d2_params_->getDnsServerTimeout());
+ EXPECT_EQ(dhcp_ddns::NCR_UDP, d2_params_->getNcrProtocol());
+ EXPECT_EQ(dhcp_ddns::FMT_JSON, d2_params_->getNcrFormat());
+
+ // Verify that ip_address can be valid v6 address.
+ config = makeParamsConfigString ("3001::5", 777, 333, "UDP", "JSON");
+ RUN_CONFIG_OK(config);
+
+ // Verify that the global scalars have the proper values.
+ EXPECT_EQ(isc::asiolink::IOAddress("3001::5"),
+ d2_params_->getIpAddress());
+
+ // Verify the configuration summary.
+ EXPECT_EQ("listening on 3001::5, port 777, using UDP",
+ d2_params_->getConfigSummary());
+}
+
+/// @brief Tests default values for D2Params.
+/// It verifies that D2Params is populated with default value for optional
+/// parameter if not supplied in the configuration.
+/// Currently they are all optional.
+TEST_F(D2CfgMgrTest, defaultValues) {
+
+ ElementPtr defaults = isc::d2::test::parseJSON("{ }");
+ ASSERT_NO_THROW(D2SimpleParser::setAllDefaults(defaults));
+
+ // Check that omitting ip_address gets you its default
+ std::string config =
+ "{"
+ " \"port\": 777 , "
+ " \"dns-server-timeout\": 333 , "
+ " \"ncr-protocol\": \"UDP\" , "
+ " \"ncr-format\": \"JSON\", "
+ " \"tsig-keys\": [], "
+ " \"forward-ddns\" : {}, "
+ " \"reverse-ddns\" : {} "
+ "}";
+
+ RUN_CONFIG_OK(config);
+ ConstElementPtr deflt;
+ ASSERT_NO_THROW(deflt = defaults->get("ip-address"));
+ ASSERT_TRUE(deflt);
+ EXPECT_EQ(deflt->stringValue(), d2_params_->getIpAddress().toText());
+
+ // Check that omitting port gets you its default
+ config =
+ "{"
+ " \"ip-address\": \"192.0.0.1\" , "
+ " \"dns-server-timeout\": 333 , "
+ " \"ncr-protocol\": \"UDP\" , "
+ " \"ncr-format\": \"JSON\", "
+ " \"tsig-keys\": [], "
+ " \"forward-ddns\" : {}, "
+ " \"reverse-ddns\" : {} "
+ "}";
+
+ RUN_CONFIG_OK(config);
+ ASSERT_NO_THROW(deflt = defaults->get("port"));
+ ASSERT_TRUE(deflt);
+ EXPECT_EQ(deflt->intValue(), d2_params_->getPort());
+
+ // Check that omitting timeout gets you its default
+ config =
+ "{"
+ " \"ip-address\": \"192.0.0.1\" , "
+ " \"port\": 777 , "
+ " \"ncr-protocol\": \"UDP\" , "
+ " \"ncr-format\": \"JSON\", "
+ " \"tsig-keys\": [], "
+ " \"forward-ddns\" : {}, "
+ " \"reverse-ddns\" : {} "
+ "}";
+
+ RUN_CONFIG_OK(config);
+ ASSERT_NO_THROW(deflt = defaults->get("dns-server-timeout"));
+ ASSERT_TRUE(deflt);
+ EXPECT_EQ(deflt->intValue(), d2_params_->getDnsServerTimeout());
+
+ // Check that omitting protocol gets you its default
+ config =
+ "{"
+ " \"ip-address\": \"192.0.0.1\" , "
+ " \"port\": 777 , "
+ " \"dns-server-timeout\": 333 , "
+ " \"ncr-format\": \"JSON\", "
+ " \"tsig-keys\": [], "
+ " \"forward-ddns\" : {}, "
+ " \"reverse-ddns\" : {} "
+ "}";
+
+ RUN_CONFIG_OK(config);
+ ASSERT_NO_THROW(deflt = defaults->get("ncr-protocol"));
+ ASSERT_TRUE(deflt);
+ EXPECT_EQ(dhcp_ddns::stringToNcrProtocol(deflt->stringValue()),
+ d2_params_->getNcrProtocol());
+
+ // Check that omitting format gets you its default
+ config =
+ "{"
+ " \"ip-address\": \"192.0.0.1\" , "
+ " \"port\": 777 , "
+ " \"dns-server-timeout\": 333 , "
+ " \"ncr-protocol\": \"UDP\", "
+ " \"tsig-keys\": [], "
+ " \"forward-ddns\" : {}, "
+ " \"reverse-ddns\" : {} "
+ "}";
+
+ RUN_CONFIG_OK(config);
+ ASSERT_NO_THROW(deflt = defaults->get("ncr-format"));
+ ASSERT_TRUE(deflt);
+ EXPECT_EQ(dhcp_ddns::stringToNcrFormat(deflt->stringValue()),
+ d2_params_->getNcrFormat());
+}
+
+/// @brief Tests the unsupported scalar parameters and objects are detected.
+TEST_F(D2CfgMgrTest, unsupportedTopLevelItems) {
+ // Check that an unsupported top level parameter fails.
+ std::string config =
+ "{"
+ " \"ip-address\": \"127.0.0.1\", "
+ " \"port\": 777 , "
+ " \"dns-server-timeout\": 333 , "
+ " \"ncr-protocol\": \"UDP\" , "
+ " \"ncr-format\": \"JSON\", "
+ " \"tsig-keys\": [], "
+ " \"forward-ddns\" : {}, "
+ " \"reverse-ddns\" : {}, "
+ " \"bogus-param\" : true "
+ "}";
+
+ SYNTAX_ERROR(config, "<string>:1.185-197: got unexpected "
+ "keyword \"bogus-param\" in DhcpDdns map.");
+
+ // Check that unsupported top level objects fails. For
+ // D2 these fail as they are not in the parse order.
+ config =
+ "{"
+ " \"ip-address\": \"127.0.0.1\", "
+ " \"port\": 777 , "
+ " \"dns-server-timeout\": 333 , "
+ " \"ncr-protocol\": \"UDP\" , "
+ " \"ncr-format\": \"JSON\", "
+ " \"tsig-keys\": [], "
+ " \"bogus-object-one\" : {}, "
+ " \"forward-ddns\" : {}, "
+ " \"reverse-ddns\" : {}, "
+ " \"bogus-object-two\" : {} "
+ "}";
+
+ SYNTAX_ERROR(config, "<string>:1.141-158: got unexpected"
+ " keyword \"bogus-object-one\" in DhcpDdns map.");
+}
+
+
+/// @brief Tests the enforcement of data validation when parsing D2Params.
+/// It verifies that:
+/// -# ip_address cannot be "0.0.0.0"
+/// -# ip_address cannot be "::"
+/// -# port cannot be 0
+/// -# dns_server_timeout cannot be 0
+/// -# ncr_protocol must be valid
+/// -# ncr_format must be valid
+TEST_F(D2CfgMgrTest, invalidEntry) {
+ // Cannot use IPv4 ANY address
+ std::string config = makeParamsConfigString ("0.0.0.0", 777, 333,
+ "UDP", "JSON");
+ LOGIC_ERROR(config, "IP address cannot be \"0.0.0.0\" (<string>:1:17)");
+
+ // Cannot use IPv6 ANY address
+ config = makeParamsConfigString ("::", 777, 333, "UDP", "JSON");
+ LOGIC_ERROR(config, "IP address cannot be \"::\" (<string>:1:17)");
+
+ // Cannot use port 0
+ config = makeParamsConfigString ("127.0.0.1", 0, 333, "UDP", "JSON");
+ SYNTAX_ERROR(config, "<string>:1.40: port must be greater than zero but less than 65536");
+
+ // Cannot use dns server timeout of 0
+ config = makeParamsConfigString ("127.0.0.1", 777, 0, "UDP", "JSON");
+ SYNTAX_ERROR(config, "<string>:1.69: dns-server-timeout"
+ " must be greater than zero");
+
+ // Invalid protocol
+ config = makeParamsConfigString ("127.0.0.1", 777, 333, "BOGUS", "JSON");
+ SYNTAX_ERROR(config, "<string>:1.92-98: syntax error,"
+ " unexpected constant string, expecting UDP or TCP");
+
+ // Unsupported protocol
+ config = makeParamsConfigString ("127.0.0.1", 777, 333, "TCP", "JSON");
+ LOGIC_ERROR(config, "ncr-protocol : TCP is not yet supported"
+ " (<string>:1:92)");
+
+ // Invalid format
+ config = makeParamsConfigString ("127.0.0.1", 777, 333, "UDP", "BOGUS");
+ SYNTAX_ERROR(config, "<string>:1.115-121: syntax error,"
+ " unexpected constant string, expecting JSON");
+}
+
+// Control socket tests in d2_process_unittests.cc
+
+// DdnsDomainList and TSIGKey tests moved to d2_simple_parser_unittest.cc
+
+/// @brief Tests construction of D2CfgMgr
+/// This test verifies that a D2CfgMgr constructs properly.
+TEST(D2CfgMgr, construction) {
+ boost::scoped_ptr<D2CfgMgr> cfg_mgr;
+
+ // Verify that configuration manager constructions without error.
+ ASSERT_NO_THROW(cfg_mgr.reset(new D2CfgMgr()));
+
+ // Verify that the context can be retrieved and is not null.
+ D2CfgContextPtr context;
+ ASSERT_NO_THROW(context = cfg_mgr->getD2CfgContext());
+ EXPECT_TRUE(context);
+
+ // Verify that the forward manager can be retrieved and is not null.
+ EXPECT_TRUE(context->getForwardMgr());
+
+ // Verify that the reverse manager can be retrieved and is not null.
+ EXPECT_TRUE(context->getReverseMgr());
+
+ // Verify that the manager can be destructed without error.
+ EXPECT_NO_THROW(cfg_mgr.reset());
+}
+
+/// @brief Tests the parsing of a complete, valid DHCP-DDNS configuration.
+/// This tests passes the configuration into an instance of D2CfgMgr just
+/// as it would be done by d2_process in response to a configuration update
+/// event.
+TEST_F(D2CfgMgrTest, fullConfig) {
+ // Create a configuration with all of application level parameters, plus
+ // both the forward and reverse ddns managers. Both managers have two
+ // domains with three servers per domain.
+ std::string config = "{ "
+ "\"ip-address\" : \"192.168.1.33\" , "
+ "\"port\" : 88 , "
+ "\"dns-server-timeout\": 333 , "
+ "\"ncr-protocol\": \"UDP\" , "
+ "\"ncr-format\": \"JSON\", "
+ "\"control-socket\" : {"
+ " \"socket-type\" : \"unix\" ,"
+ " \"socket-name\" : \"/tmp/d2-ctrl-channel\" "
+ "},"
+ "\"hooks-libraries\": ["
+ "{"
+ " \"library\": \"%LIBRARY%\" , "
+ " \"parameters\": "
+ " { \"param1\": \"foo\" } "
+ "}"
+ "],"
+ "\"tsig-keys\": ["
+ "{"
+ " \"name\": \"d2_key.example.com\" , "
+ " \"algorithm\": \"hmac-md5\" , "
+ " \"secret\": \"LSWXnfkKZjdPJI5QxlpnfQ==\" "
+ "},"
+ "{"
+ " \"name\": \"d2_key.billcat.net\" , "
+ " \"algorithm\": \"hmac-md5\" , "
+ " \"digest-bits\": 120 , "
+ " \"secret\": \"LSWXnfkKZjdPJI5QxlpnfQ==\" "
+ "}"
+ "],"
+ "\"forward-ddns\" : {"
+ "\"ddns-domains\": [ "
+ "{ \"name\": \"example.com\" , "
+ " \"key-name\": \"d2_key.example.com\" , "
+ " \"dns-servers\" : [ "
+ " { \"ip-address\": \"127.0.0.1\" } , "
+ " { \"ip-address\": \"127.0.0.2\" } , "
+ " { \"ip-address\": \"127.0.0.3\"} "
+ " ] } "
+ ", "
+ "{ \"name\": \"billcat.net\" , "
+ " \"key-name\": \"d2_key.billcat.net\" , "
+ " \"dns-servers\" : [ "
+ " { \"ip-address\": \"127.0.0.4\" } , "
+ " { \"ip-address\": \"127.0.0.5\" } , "
+ " { \"ip-address\": \"127.0.0.6\" } "
+ " ] } "
+ "] },"
+ "\"reverse-ddns\" : {"
+ "\"ddns-domains\": [ "
+ "{ \"name\": \" 0.168.192.in.addr.arpa.\" , "
+ " \"key-name\": \"d2_key.example.com\" , "
+ " \"dns-servers\" : [ "
+ " { \"ip-address\": \"127.0.1.1\" } , "
+ " { \"ip-address\": \"127.0.2.1\" } , "
+ " { \"ip-address\": \"127.0.3.1\" } "
+ " ] } "
+ ", "
+ "{ \"name\": \" 0.247.106.in.addr.arpa.\" , "
+ " \"key-name\": \"d2_key.billcat.net\" , "
+ " \"dns-servers\" : [ "
+ " { \"ip-address\": \"127.0.4.1\" }, "
+ " { \"ip-address\": \"127.0.5.1\" } , "
+ " { \"ip-address\": \"127.0.6.1\" } "
+ " ] } "
+ "] } }";
+
+ // Replace the library path.
+ std::string pr_config = pathReplacer(config.c_str(), CALLOUT_LIBRARY);
+ // Should parse without error.
+ RUN_CONFIG_OK(pr_config);
+
+ // Verify that the D2 context can be retrieved and is not null.
+ D2CfgContextPtr context;
+ ASSERT_NO_THROW(context = cfg_mgr_->getD2CfgContext());
+
+ // Verify that the global scalars have the proper values.
+ D2ParamsPtr& d2_params = context->getD2Params();
+ ASSERT_TRUE(d2_params);
+
+ EXPECT_EQ(isc::asiolink::IOAddress("192.168.1.33"),
+ d2_params->getIpAddress());
+ EXPECT_EQ(88, d2_params->getPort());
+ EXPECT_EQ(333, d2_params->getDnsServerTimeout());
+ EXPECT_EQ(dhcp_ddns::NCR_UDP, d2_params->getNcrProtocol());
+ EXPECT_EQ(dhcp_ddns::FMT_JSON, d2_params->getNcrFormat());
+
+ // Verify that the control socket can be retrieved.
+ ConstElementPtr ctrl_sock = context->getControlSocketInfo();
+ ASSERT_TRUE(ctrl_sock);
+ ASSERT_EQ(Element::map, ctrl_sock->getType());
+ EXPECT_EQ(2, ctrl_sock->size());
+ ASSERT_TRUE(ctrl_sock->get("socket-type"));
+ EXPECT_EQ("\"unix\"", ctrl_sock->get("socket-type")->str());
+ ASSERT_TRUE(ctrl_sock->get("socket-name"));
+ EXPECT_EQ("\"/tmp/d2-ctrl-channel\"", ctrl_sock->get("socket-name")->str());
+
+ // Verify that the hooks libraries can be retrieved.
+ const HookLibsCollection libs = context->getHooksConfig().get();
+ ASSERT_EQ(1, libs.size());
+ EXPECT_EQ(string(CALLOUT_LIBRARY), libs[0].first);
+ ASSERT_TRUE(libs[0].second);
+ EXPECT_EQ("{ \"param1\": \"foo\" }", libs[0].second->str());
+
+ // Verify that the forward manager can be retrieved.
+ DdnsDomainListMgrPtr mgr = context->getForwardMgr();
+ ASSERT_TRUE(mgr);
+ EXPECT_EQ("forward-ddns", mgr->getName());
+
+ // Verify that the forward manager has the correct number of domains.
+ DdnsDomainMapPtr domains = mgr->getDomains();
+ ASSERT_TRUE(domains);
+ int count = domains->size();
+ EXPECT_EQ(2, count);
+
+ // Verify that the server count in each of the forward manager domains.
+ // NOTE that since prior tests have validated server parsing, we are are
+ // assuming that the servers did in fact parse correctly if the correct
+ // number of them are there.
+ DdnsDomainMapPair domain_pair;
+ BOOST_FOREACH(domain_pair, (*domains)) {
+ DdnsDomainPtr domain = domain_pair.second;
+ DnsServerInfoStoragePtr servers = domain->getServers();
+ count = servers->size();
+ EXPECT_TRUE(servers);
+ EXPECT_EQ(3, count);
+ }
+
+ // Verify that the reverse manager can be retrieved.
+ mgr = context->getReverseMgr();
+ ASSERT_TRUE(mgr);
+ EXPECT_EQ("reverse-ddns", mgr->getName());
+
+ // Verify that the reverse manager has the correct number of domains.
+ domains = mgr->getDomains();
+ count = domains->size();
+ EXPECT_EQ(2, count);
+
+ // Verify that the server count in each of the reverse manager domains.
+ // NOTE that since prior tests have validated server parsing, we are are
+ // assuming that the servers did in fact parse correctly if the correct
+ // number of them are there.
+ BOOST_FOREACH(domain_pair, (*domains)) {
+ DdnsDomainPtr domain = domain_pair.second;
+ DnsServerInfoStoragePtr servers = domain->getServers();
+ count = servers->size();
+ EXPECT_TRUE(servers);
+ EXPECT_EQ(3, count);
+ }
+
+ // Test directional update flags.
+ EXPECT_TRUE(cfg_mgr_->forwardUpdatesEnabled());
+ EXPECT_TRUE(cfg_mgr_->reverseUpdatesEnabled());
+
+ // Verify that parsing the exact same configuration a second time
+ // does not cause a duplicate value errors.
+ answer_ = cfg_mgr_->simpleParseConfig(config_set_, false);
+ ASSERT_TRUE(checkAnswer(0));
+}
+
+/// @brief Tests the basics of the D2CfgMgr FQDN-domain matching
+/// This test uses a valid configuration to exercise the D2CfgMgr
+/// forward FQDN-to-domain matching.
+/// It verifies that:
+/// 1. Given an FQDN which exactly matches a domain's name, that domain is
+/// returned as match.
+/// 2. Given a FQDN for sub-domain in the list, returns the proper match.
+/// 3. Given a FQDN that matches no domain name, returns the wild card domain
+/// as a match.
+TEST_F(D2CfgMgrTest, forwardMatch) {
+ // Create configuration with one domain, one sub domain, and the wild
+ // card.
+ std::string config = "{ "
+ "\"ip-address\" : \"192.168.1.33\" , "
+ "\"port\" : 88 , "
+ "\"tsig-keys\": [] ,"
+ "\"forward-ddns\" : {"
+ "\"ddns-domains\": [ "
+ "{ \"name\": \"example.com\" , "
+ " \"dns-servers\" : [ "
+ " { \"ip-address\": \"127.0.0.1\" } "
+ " ] } "
+ ", "
+ "{ \"name\": \"one.example.com\" , "
+ " \"dns-servers\" : [ "
+ " { \"ip-address\": \"127.0.0.2\" } "
+ " ] } "
+ ", "
+ "{ \"name\": \"*\" , "
+ " \"dns-servers\" : [ "
+ " { \"ip-address\": \"127.0.0.3\" } "
+ " ] } "
+ "] }, "
+ "\"reverse-ddns\" : {} "
+ "}";
+
+ // Verify that we can parse the configuration.
+ RUN_CONFIG_OK(config);
+
+ // Verify that the D2 context can be retrieved and is not null.
+ D2CfgContextPtr context;
+ ASSERT_NO_THROW(context = cfg_mgr_->getD2CfgContext());
+
+ // Test directional update flags.
+ EXPECT_TRUE(cfg_mgr_->forwardUpdatesEnabled());
+ EXPECT_FALSE(cfg_mgr_->reverseUpdatesEnabled());
+
+ DdnsDomainPtr match;
+ // Verify that an exact match works.
+ EXPECT_TRUE(cfg_mgr_->matchForward("example.com", match));
+ EXPECT_EQ("example.com", match->getName());
+
+ // Verify that search is case insensitive.
+ EXPECT_TRUE(cfg_mgr_->matchForward("EXAMPLE.COM", match));
+ EXPECT_EQ("example.com", match->getName());
+
+ // Verify that an exact match works.
+ EXPECT_TRUE(cfg_mgr_->matchForward("one.example.com", match));
+ EXPECT_EQ("one.example.com", match->getName());
+
+ // Verify that a FQDN for sub-domain matches.
+ EXPECT_TRUE(cfg_mgr_->matchForward("blue.example.com", match));
+ EXPECT_EQ("example.com", match->getName());
+
+ // Verify that a FQDN for sub-domain matches.
+ EXPECT_TRUE(cfg_mgr_->matchForward("red.one.example.com", match));
+ EXPECT_EQ("one.example.com", match->getName());
+
+ // Verify that an FQDN with no match, returns the wild card domain.
+ EXPECT_TRUE(cfg_mgr_->matchForward("shouldbe.wildcard", match));
+ EXPECT_EQ("*", match->getName());
+
+ // Verify that an attempt to match an empty FQDN throws.
+ ASSERT_THROW(cfg_mgr_->matchForward("", match), D2CfgError);
+}
+
+/// @brief Tests domain matching when there is no wild card domain.
+/// This test verifies that matches are found only for FQDNs that match
+/// some or all of a domain name. FQDNs without matches should not return
+/// a match.
+TEST_F(D2CfgMgrTest, matchNoWildcard) {
+ // Create a configuration with one domain, one sub-domain, and NO wild card.
+ std::string config = "{ "
+ "\"ip-address\" : \"192.168.1.33\" , "
+ "\"port\" : 88 , "
+ "\"tsig-keys\": [] ,"
+ "\"forward-ddns\" : {"
+ "\"ddns-domains\": [ "
+ "{ \"name\": \"example.com\" , "
+ " \"dns-servers\" : [ "
+ " { \"ip-address\": \"127.0.0.1\" } "
+ " ] } "
+ ", "
+ "{ \"name\": \"one.example.com\" , "
+ " \"dns-servers\" : [ "
+ " { \"ip-address\": \"127.0.0.2\" } "
+ " ] } "
+ "] }, "
+ "\"reverse-ddns\" : {} "
+ " }";
+
+ // Verify that we can parse the configuration.
+ RUN_CONFIG_OK(config);
+
+ // Verify that the D2 context can be retrieved and is not null.
+ D2CfgContextPtr context;
+ ASSERT_NO_THROW(context = cfg_mgr_->getD2CfgContext());
+
+ DdnsDomainPtr match;
+ // Verify that full or partial matches, still match.
+ EXPECT_TRUE(cfg_mgr_->matchForward("example.com", match));
+ EXPECT_EQ("example.com", match->getName());
+
+ EXPECT_TRUE(cfg_mgr_->matchForward("blue.example.com", match));
+ EXPECT_EQ("example.com", match->getName());
+
+ EXPECT_TRUE(cfg_mgr_->matchForward("red.one.example.com", match));
+ EXPECT_EQ("one.example.com", match->getName());
+
+ // Verify that a FQDN with no match, fails to match.
+ EXPECT_FALSE(cfg_mgr_->matchForward("shouldbe.wildcard", match));
+}
+
+/// @brief Tests domain matching when there is ONLY a wild card domain.
+/// This test verifies that any FQDN matches the wild card.
+TEST_F(D2CfgMgrTest, matchAll) {
+ std::string config = "{ "
+ "\"ip-address\" : \"192.168.1.33\" , "
+ "\"port\" : 88 , "
+ "\"tsig-keys\": [] ,"
+ "\"forward-ddns\" : {"
+ "\"ddns-domains\": [ "
+ "{ \"name\": \"*\" , "
+ " \"dns-servers\" : [ "
+ " { \"ip-address\": \"127.0.0.1\" } "
+ " ] } "
+ "] }, "
+ "\"reverse-ddns\" : {} "
+ "}";
+
+ // Verify that we can parse the configuration.
+ RUN_CONFIG_OK(config);
+
+ // Verify that the D2 context can be retrieved and is not null.
+ D2CfgContextPtr context;
+ ASSERT_NO_THROW(context = cfg_mgr_->getD2CfgContext());
+
+ // Verify that wild card domain is returned for any FQDN.
+ DdnsDomainPtr match;
+ EXPECT_TRUE(cfg_mgr_->matchForward("example.com", match));
+ EXPECT_EQ("*", match->getName());
+ EXPECT_TRUE(cfg_mgr_->matchForward("shouldbe.wildcard", match));
+ EXPECT_EQ("*", match->getName());
+
+ // Verify that an attempt to match an empty FQDN still throws.
+ ASSERT_THROW(cfg_mgr_->matchReverse("", match), D2CfgError);
+
+}
+
+/// @brief Tests the basics of the D2CfgMgr reverse FQDN-domain matching
+/// This test uses a valid configuration to exercise the D2CfgMgr's
+/// reverse FQDN-to-domain matching.
+/// It verifies that:
+/// 1. Given an FQDN which exactly matches a domain's name, that domain is
+/// returned as match.
+/// 2. Given a FQDN for sub-domain in the list, returns the proper match.
+/// 3. Given a FQDN that matches no domain name, returns the wild card domain
+/// as a match.
+TEST_F(D2CfgMgrTest, matchReverse) {
+ std::string config = "{ "
+ "\"ip-address\" : \"192.168.1.33\" , "
+ "\"port\" : 88 , "
+ "\"tsig-keys\": [] ,"
+ "\"forward-ddns\" : {}, "
+ "\"reverse-ddns\" : {"
+ "\"ddns-domains\": [ "
+ "{ \"name\": \"5.100.168.192.in-addr.arpa.\" , "
+ " \"dns-servers\" : [ "
+ " { \"ip-address\": \"127.0.0.1\" } "
+ " ] }, "
+ "{ \"name\": \"100.200.192.in-addr.arpa.\" , "
+ " \"dns-servers\" : [ "
+ " { \"ip-address\": \"127.0.0.1\" } "
+ " ] }, "
+ "{ \"name\": \"170.192.in-addr.arpa.\" , "
+ " \"dns-servers\" : [ "
+ " { \"ip-address\": \"127.0.0.1\" } "
+ " ] }, "
+ // Note mixed case to test case insensitivity.
+ "{ \"name\": \"2.0.3.0.8.b.d.0.1.0.0.2.IP6.ARPA.\" , "
+ " \"dns-servers\" : [ "
+ " { \"ip-address\": \"127.0.0.1\" } "
+ " ] },"
+ "{ \"name\": \"*\" , "
+ " \"dns-servers\" : [ "
+ " { \"ip-address\": \"127.0.0.1\" } "
+ " ] } "
+ "] } }";
+
+ // Verify that we can parse the configuration.
+ RUN_CONFIG_OK(config);
+
+ // Verify that the D2 context can be retrieved and is not null.
+ D2CfgContextPtr context;
+ ASSERT_NO_THROW(context = cfg_mgr_->getD2CfgContext());
+
+ // Test directional update flags.
+ EXPECT_FALSE(cfg_mgr_->forwardUpdatesEnabled());
+ EXPECT_TRUE(cfg_mgr_->reverseUpdatesEnabled());
+
+ DdnsDomainPtr match;
+
+ // Verify an exact match.
+ EXPECT_TRUE(cfg_mgr_->matchReverse("192.168.100.5", match));
+ EXPECT_EQ("5.100.168.192.in-addr.arpa.", match->getName());
+
+ // Verify a sub-domain match.
+ EXPECT_TRUE(cfg_mgr_->matchReverse("192.200.100.27", match));
+ EXPECT_EQ("100.200.192.in-addr.arpa.", match->getName());
+
+ // Verify a sub-domain match.
+ EXPECT_TRUE(cfg_mgr_->matchReverse("192.170.50.30", match));
+ EXPECT_EQ("170.192.in-addr.arpa.", match->getName());
+
+ // Verify a wild card match.
+ EXPECT_TRUE(cfg_mgr_->matchReverse("1.1.1.1", match));
+ EXPECT_EQ("*", match->getName());
+
+ // Verify a IPv6 match.
+ EXPECT_TRUE(cfg_mgr_->matchReverse("2001:db8:302:99::",match));
+ EXPECT_EQ("2.0.3.0.8.b.d.0.1.0.0.2.IP6.ARPA.", match->getName());
+
+ // Verify a IPv6 wild card match.
+ EXPECT_TRUE(cfg_mgr_->matchReverse("2001:db8:99:302::",match));
+ EXPECT_EQ("*", match->getName());
+
+ // Verify that an attempt to match an invalid IP address throws.
+ ASSERT_THROW(cfg_mgr_->matchReverse("", match), D2CfgError);
+}
+
+/// @brief Tests D2 config parsing against a wide range of config permutations.
+///
+/// It tests for both syntax errors that the JSON parsing (D2ParserContext)
+/// should detect as well as post-JSON parsing logic errors generated by
+/// the Element parsers (i.e...SimpleParser/DhcpParser derivations)
+///
+/// It iterates over all of the test configurations described in given file.
+/// The file content is JSON specialized to this test. The format of the file
+/// is:
+///
+/// @code
+/// # The file must open with a list. It's name is arbitrary.
+///
+/// { "test_list" :
+/// [
+///
+/// # Test one starts here:
+/// {
+///
+/// # Each test has:
+/// # 1. description - optional text description
+/// # 2. syntax-error - error JSON parser should emit (omit if none)
+/// # 3. logic-error - error element parser(s) should emit (omit if none)
+/// # 4. data - configuration text to parse
+/// #
+/// "description" : "<text describing test>",
+/// "syntax-error" : "<exact text from JSON parser including position>" ,
+/// "logic-error" : "<exact text from element parser including position>" ,
+/// "data" :
+/// {
+/// # configuration elements here
+/// "bool_val" : false,
+/// "some_map" : {}
+/// # :
+/// }
+/// }
+///
+/// # Next test would start here
+/// ,
+/// {
+/// }
+///
+/// ]}
+///
+/// @endcode
+///
+/// (The file supports comments per Element::fromJSONFile())
+///
+TEST_F(D2CfgMgrTest, configPermutations) {
+ std::string test_file = testDataFile("d2_cfg_tests.json");
+ isc::data::ConstElementPtr tests;
+
+ // Read contents of the file and parse it as JSON. Note it must contain
+ // all valid JSON, we aren't testing JSON parsing.
+ try {
+ tests = isc::data::Element::fromJSONFile(test_file, true);
+ } catch (const std::exception& ex) {
+ FAIL() << "ERROR parsing file : " << test_file << " : " << ex.what();
+ }
+
+ // Read in each test For each test, read:
+ //
+ // 1. description - optional text description
+ // 2. syntax-error or logic-error or neither
+ // 3. data - configuration text to parse
+ // 4. convert data into JSON text
+ // 5. submit JSON for parsing
+ isc::data::ConstElementPtr test;
+ ASSERT_TRUE(tests->get("test-list"));
+ BOOST_FOREACH(test, tests->get("test-list")->listValue()) {
+ // Grab the description.
+ std::string description = "<no desc>";
+ isc::data::ConstElementPtr elem = test->get("description");
+ if (elem) {
+ elem->getValue(description);
+ }
+
+ // Grab the expected error message, if there is one.
+ std::string expected_error = "";
+ RunConfigMode mode = NO_ERROR;
+ elem = test->get("syntax-error");
+ if (elem) {
+ elem->getValue(expected_error);
+ mode = SYNTAX_ERROR;
+ } else {
+ elem = test->get("logic-error");
+ if (elem) {
+ elem->getValue(expected_error);
+ mode = LOGIC_ERROR;
+ }
+ }
+
+ // Grab the test's configuration data.
+ isc::data::ConstElementPtr data = test->get("data");
+ ASSERT_TRUE(data) << "No data for test: " << test->getPosition();
+
+ // Convert the test data back to JSON text, then submit it for parsing.
+ stringstream os;
+ data->toJSON(os);
+ EXPECT_TRUE(runConfigOrFail(os.str(), mode, expected_error))
+ << " failed for test: " << test->getPosition() << std::endl;
+ }
+}
+
+/// @brief Tests comments.
+TEST_F(D2CfgMgrTest, comments) {
+ std::string config = "{ "
+ "\"comment\": \"D2 config\" , "
+ "\"ip-address\" : \"192.168.1.33\" , "
+ "\"port\" : 88 , "
+ "\"control-socket\": {"
+ " \"comment\": \"Control channel\" , "
+ " \"socket-type\": \"unix\" ,"
+ " \"socket-name\": \"/tmp/d2-ctrl-channel\" "
+ "},"
+ "\"tsig-keys\": ["
+ "{"
+ " \"user-context\": { "
+ " \"comment\": \"Indirect comment\" } , "
+ " \"name\": \"d2_key.example.com\" , "
+ " \"algorithm\": \"hmac-md5\" , "
+ " \"secret\": \"LSWXnfkKZjdPJI5QxlpnfQ==\" "
+ "}"
+ "],"
+ "\"forward-ddns\" : {"
+ "\"ddns-domains\": [ "
+ "{ \"comment\": \"A DDNS domain\" , "
+ " \"name\": \"example.com\" , "
+ " \"key-name\": \"d2_key.example.com\" , "
+ " \"dns-servers\" : [ "
+ " { \"ip-address\": \"127.0.0.1\" , "
+ " \"user-context\": { \"version\": 1 } } "
+ " ] } "
+ "] } }";
+
+ // Should parse without error.
+ RUN_CONFIG_OK(config);
+
+ // Check the D2 context.
+ D2CfgContextPtr d2_context;
+ ASSERT_NO_THROW(d2_context = cfg_mgr_->getD2CfgContext());
+ ASSERT_TRUE(d2_context);
+
+ // Check global user context.
+ ConstElementPtr ctx = d2_context->getContext();
+ ASSERT_TRUE(ctx);
+ ASSERT_EQ(1, ctx->size());
+ ASSERT_TRUE(ctx->get("comment"));
+ EXPECT_EQ("\"D2 config\"", ctx->get("comment")->str());
+
+ // Check control socket.
+ ConstElementPtr ctrl_sock = d2_context->getControlSocketInfo();
+ ASSERT_TRUE(ctrl_sock);
+ ASSERT_TRUE(ctrl_sock->get("user-context"));
+ EXPECT_EQ("{ \"comment\": \"Control channel\" }",
+ ctrl_sock->get("user-context")->str());
+
+ // Check TSIG keys.
+ TSIGKeyInfoMapPtr keys = d2_context->getKeys();
+ ASSERT_TRUE(keys);
+ ASSERT_EQ(1, keys->size());
+
+ // Check the TSIG key.
+ TSIGKeyInfoMap::iterator gotkey = keys->find("d2_key.example.com");
+ ASSERT_TRUE(gotkey != keys->end());
+ TSIGKeyInfoPtr key = gotkey->second;
+ ASSERT_TRUE(key);
+
+ // Check the TSIG key user context.
+ ConstElementPtr key_ctx = key->getContext();
+ ASSERT_TRUE(key_ctx);
+ ASSERT_EQ(1, key_ctx->size());
+ ASSERT_TRUE(key_ctx->get("comment"));
+ EXPECT_EQ("\"Indirect comment\"", key_ctx->get("comment")->str());
+
+ // Check the forward manager.
+ DdnsDomainListMgrPtr mgr = d2_context->getForwardMgr();
+ ASSERT_TRUE(mgr);
+ EXPECT_EQ("forward-ddns", mgr->getName());
+ DdnsDomainMapPtr domains = mgr->getDomains();
+ ASSERT_TRUE(domains);
+ ASSERT_EQ(1, domains->size());
+
+ // Check the DDNS domain.
+ DdnsDomainMap::iterator gotdns = domains->find("example.com");
+ ASSERT_TRUE(gotdns != domains->end());
+ DdnsDomainPtr domain = gotdns->second;
+ ASSERT_TRUE(domain);
+
+ // Check the DNS server.
+ DnsServerInfoStoragePtr servers = domain->getServers();
+ ASSERT_TRUE(servers);
+ ASSERT_EQ(1, servers->size());
+ DnsServerInfoPtr server = (*servers)[0];
+ ASSERT_TRUE(server);
+
+ // Check the DNS server user context.
+ ConstElementPtr srv_ctx = server->getContext();
+ ASSERT_TRUE(srv_ctx);
+ ASSERT_EQ(1, srv_ctx->size());
+ ASSERT_TRUE(srv_ctx->get("version"));
+ EXPECT_EQ("1", srv_ctx->get("version")->str());
+}
+
+} // end of anonymous namespace
diff --git a/src/bin/d2/tests/d2_command_unittest.cc b/src/bin/d2/tests/d2_command_unittest.cc
new file mode 100644
index 0000000..8f956e7
--- /dev/null
+++ b/src/bin/d2/tests/d2_command_unittest.cc
@@ -0,0 +1,1391 @@
+// Copyright (C) 2018-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <asiolink/interval_timer.h>
+#include <asiolink/io_service.h>
+#include <cc/command_interpreter.h>
+#include <config/command_mgr.h>
+#include <config/timeouts.h>
+#include <testutils/io_utils.h>
+#include <testutils/unix_control_client.h>
+#include <d2/d2_controller.h>
+#include <d2/d2_process.h>
+#include <d2/parser_context.h>
+#include <gtest/gtest.h>
+#include <testutils/sandbox.h>
+#include <boost/pointer_cast.hpp>
+#include <fstream>
+#include <iostream>
+#include <sstream>
+#include <thread>
+#include <unistd.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::config;
+using namespace isc::d2;
+using namespace isc::data;
+using namespace isc::dhcp::test;
+using namespace isc::process;
+using namespace boost::asio;
+namespace ph = std::placeholders;
+
+namespace isc {
+namespace d2 {
+
+class NakedD2Controller;
+typedef boost::shared_ptr<NakedD2Controller> NakedD2ControllerPtr;
+
+class NakedD2Controller : public D2Controller {
+ // "Naked" D2 controller, exposes internal methods.
+public:
+ static DControllerBasePtr& instance() {
+ static DControllerBasePtr controller_ptr;
+ if (!controller_ptr) {
+ controller_ptr.reset(new NakedD2Controller());
+ }
+
+ return (controller_ptr);
+ }
+
+ virtual ~NakedD2Controller() { deregisterCommands(); }
+
+ using DControllerBase::getIOService;
+ using DControllerBase::initProcess;
+
+ D2ProcessPtr getProcess() {
+ return (boost::dynamic_pointer_cast<D2Process>(DControllerBase::getProcess()));
+ }
+
+private:
+ NakedD2Controller() { }
+};
+
+}; // namespace isc::d2
+}; // namespace isc
+
+namespace {
+
+/// @brief Simple RAII class which stops IO service upon destruction
+/// of the object.
+class IOServiceWork {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// @param io_service Pointer to the IO service to be stopped.
+ explicit IOServiceWork(const IOServicePtr& io_service)
+ : io_service_(io_service) {
+ }
+
+ /// @brief Destructor.
+ ///
+ /// Stops IO service.
+ ~IOServiceWork() {
+ io_service_->stop();
+ }
+
+private:
+
+ /// @brief Pointer to the IO service to be stopped upon destruction.
+ IOServicePtr io_service_;
+
+};
+
+/// @brief Fixture class intended for testing control channel in D2.
+class CtrlChannelD2Test : public ::testing::Test {
+public:
+ isc::test::Sandbox sandbox;
+
+ /// @brief Path to the UNIX socket being used to communicate with the server.
+ string socket_path_;
+
+ /// @brief Reference to the base controller object.
+ DControllerBasePtr& server_;
+
+ /// @brief Cast controller object.
+ NakedD2Controller* d2Controller() {
+ return (dynamic_cast<NakedD2Controller*>(server_.get()));
+ }
+
+ /// @brief Configuration file.
+ static const char* CFG_TEST_FILE;
+
+ /// @brief Default constructor.
+ ///
+ /// Sets socket path to its default value.
+ CtrlChannelD2Test()
+ : server_(NakedD2Controller::instance()) {
+ const char* env = getenv("KEA_SOCKET_TEST_DIR");
+ if (env) {
+ socket_path_ = string(env) + "/d2.sock";
+ } else {
+ socket_path_ = sandbox.join("d2.sock");
+ }
+ ::remove(socket_path_.c_str());
+ }
+
+ /// @brief Destructor.
+ ~CtrlChannelD2Test() {
+ // Deregister & co.
+ server_.reset();
+
+ // Remove files.
+ ::remove(CFG_TEST_FILE);
+ ::remove(socket_path_.c_str());
+
+ // Reset command manager.
+ CommandMgr::instance().deregisterAll();
+ CommandMgr::instance().setConnectionTimeout(TIMEOUT_DHCP_SERVER_RECEIVE_COMMAND);
+ }
+
+ /// @brief Returns pointer to the server's IO service.
+ ///
+ /// @return Pointer to the server's IO service or null pointer if the
+ /// hasn't been created server.
+ IOServicePtr getIOService() {
+ return (server_ ? d2Controller()->getIOService() : IOServicePtr());
+ }
+
+ /// @brief Runs parser in DHCPDDNS mode
+ ///
+ /// @param config input configuration
+ /// @param verbose display errors
+ /// @return element pointer representing the configuration
+ ElementPtr parseDHCPDDNS(const string& config, bool verbose = false) {
+ try {
+ D2ParserContext ctx;
+ return (ctx.parseString(config,
+ D2ParserContext::PARSER_SUB_DHCPDDNS));
+ } catch (const std::exception& ex) {
+ if (verbose) {
+ cout << "EXCEPTION: " << ex.what() << endl;
+ }
+ throw;
+ }
+ }
+
+ /// @brief Create a server with a command channel.
+ void createUnixChannelServer() {
+ ::remove(socket_path_.c_str());
+
+ // Just a simple config. The important part here is the socket
+ // location information.
+ string header =
+ "{"
+ " \"ip-address\": \"192.168.77.1\","
+ " \"port\": 777,"
+ " \"control-socket\": {"
+ " \"socket-type\": \"unix\","
+ " \"socket-name\": \"";
+
+ string footer =
+ "\""
+ " },"
+ " \"tsig-keys\": [],"
+ " \"forward-ddns\" : {},"
+ " \"reverse-ddns\" : {}"
+ "}";
+
+ // Fill in the socket-name value with socket_path_ to make
+ // the actual configuration text.
+ string config_txt = header + socket_path_ + footer;
+
+ ASSERT_TRUE(server_);
+
+ ConstElementPtr config;
+ ASSERT_NO_THROW(config = parseDHCPDDNS(config_txt, true));
+ ASSERT_NO_THROW(d2Controller()->initProcess());
+ D2ProcessPtr proc = d2Controller()->getProcess();
+ ASSERT_TRUE(proc);
+ ConstElementPtr answer = proc->configure(config, false);
+ ASSERT_TRUE(answer);
+ ASSERT_NO_THROW(d2Controller()->registerCommands());
+
+ int status = 0;
+ ConstElementPtr txt = parseAnswer(status, answer);
+ // This should succeed. If not, print the error message.
+ ASSERT_EQ(0, status) << txt->str();
+
+ // Now check that the socket was indeed open.
+ ASSERT_GT(CommandMgr::instance().getControlSocketFD(), -1);
+ }
+
+ /// @brief Conducts a command/response exchange via UnixCommandSocket.
+ ///
+ /// This method connects to the given server over the given socket path.
+ /// If successful, it then sends the given command and retrieves the
+ /// server's response. Note that it polls the server's I/O service
+ /// where needed to cause the server to process IO events on
+ /// the control channel sockets
+ ///
+ /// @param command the command text to execute in JSON form
+ /// @param response variable into which the received response should be
+ /// placed.
+ void sendUnixCommand(const string& command, string& response) {
+ response = "";
+ boost::scoped_ptr<UnixControlClient> client;
+ client.reset(new UnixControlClient());
+ ASSERT_TRUE(client);
+
+ // Connect to the server. This is expected to trigger server's acceptor
+ // handler when IOService::poll() is run.
+ ASSERT_TRUE(client->connectToServer(socket_path_));
+ ASSERT_NO_THROW(getIOService()->poll());
+
+ // Send the command. This will trigger server's handler which receives
+ // data over the unix domain socket. The server will start sending
+ // response to the client.
+ ASSERT_TRUE(client->sendCommand(command));
+ ASSERT_NO_THROW(getIOService()->poll());
+
+ // Read the response generated by the server. Note that getResponse
+ // only fails if there an IO error or no response data was present.
+ // It is not based on the response content.
+ ASSERT_TRUE(client->getResponse(response));
+
+ // Now disconnect and process the close event.
+ client->disconnectFromServer();
+
+ ASSERT_NO_THROW(getIOService()->poll());
+ }
+
+ /// @brief Checks response for list-commands.
+ ///
+ /// This method checks if the list-commands response is generally sane
+ /// and whether specified command is mentioned in the response.
+ ///
+ /// @param rsp response sent back by the server.
+ /// @param command command expected to be on the list.
+ void checkListCommands(const ConstElementPtr& rsp, const string command) {
+ ConstElementPtr params;
+ int status_code = -1;
+ EXPECT_NO_THROW(params = parseAnswer(status_code, rsp));
+ EXPECT_EQ(CONTROL_RESULT_SUCCESS, status_code);
+ ASSERT_TRUE(params);
+ ASSERT_EQ(Element::list, params->getType());
+
+ int cnt = 0;
+ for (size_t i = 0; i < params->size(); ++i) {
+ string tmp = params->get(i)->stringValue();
+ if (tmp == command) {
+ // Command found, but that's not enough.
+ // Need to continue working through the list to see
+ // if there are no duplicates.
+ cnt++;
+ }
+ }
+
+ // Exactly one command on the list is expected.
+ EXPECT_EQ(1, cnt) << "Command " << command << " not found";
+ }
+
+ /// @brief Check if the answer for config-write command is correct.
+ ///
+ /// @param response_txt response in text form.
+ /// (as read from the control socket)
+ /// @param exp_status expected status.
+ /// (0 success, 1 failure)
+ /// @param exp_txt for success cases this defines the expected filename,
+ /// for failure cases this defines the expected error message.
+ void checkConfigWrite(const string& response_txt, int exp_status,
+ const string& exp_txt = "") {
+
+ ConstElementPtr rsp;
+ EXPECT_NO_THROW(rsp = Element::fromJSON(response_txt));
+ ASSERT_TRUE(rsp);
+
+ int status;
+ ConstElementPtr params = parseAnswer(status, rsp);
+ EXPECT_EQ(exp_status, status);
+
+ if (exp_status == CONTROL_RESULT_SUCCESS) {
+ // Let's check couple things...
+
+ // The parameters must include filename.
+ ASSERT_TRUE(params);
+ ASSERT_TRUE(params->get("filename"));
+ ASSERT_EQ(Element::string, params->get("filename")->getType());
+ EXPECT_EQ(exp_txt, params->get("filename")->stringValue());
+
+ // The parameters must include size. And the size
+ // must indicate some content.
+ ASSERT_TRUE(params->get("size"));
+ ASSERT_EQ(Element::integer, params->get("size")->getType());
+ int64_t size = params->get("size")->intValue();
+ EXPECT_LE(1, size);
+
+ // Now check if the file is really there and suitable for
+ // opening.
+ ifstream f(exp_txt, ios::binary | ios::ate);
+ ASSERT_TRUE(f.good());
+
+ // Now check that it is the correct size as reported.
+ EXPECT_EQ(size, static_cast<int64_t>(f.tellg()));
+
+ // Finally, check that it's really a JSON.
+ ElementPtr from_file = Element::fromJSONFile(exp_txt);
+ ASSERT_TRUE(from_file);
+ } else if (exp_status == CONTROL_RESULT_ERROR) {
+
+ // Let's check if the reason for failure was given.
+ ConstElementPtr text = rsp->get("text");
+ ASSERT_TRUE(text);
+ ASSERT_EQ(Element::string, text->getType());
+ EXPECT_EQ(exp_txt, text->stringValue());
+ } else {
+ ADD_FAILURE() << "Invalid expected status: " << exp_status;
+ }
+ }
+
+ /// @brief Handler for long command.
+ ///
+ /// It checks whether the received command is equal to the one specified
+ /// as an argument.
+ ///
+ /// @param expected_command String representing an expected command.
+ /// @param command_name Command name received by the handler.
+ /// @param arguments Command arguments received by the handler.
+ ///
+ /// @returns Success answer.
+ static ConstElementPtr
+ longCommandHandler(const string& expected_command,
+ const string& command_name,
+ const ConstElementPtr& arguments) {
+ // The handler is called with a command name and the structure holding
+ // command arguments. We have to rebuild the command from those
+ // two arguments so as it can be compared against expected_command.
+ ElementPtr entire_command = Element::createMap();
+ entire_command->set("command", Element::create(command_name));
+ entire_command->set("arguments", (arguments));
+
+ // The rebuilt command will have a different order of parameters so
+ // let's parse expected_command back to JSON to guarantee that
+ // both structures are built using the same order.
+ EXPECT_EQ(Element::fromJSON(expected_command)->str(),
+ entire_command->str());
+ return (createAnswer(0, "long command received ok"));
+ }
+
+ /// @brief Command handler which generates long response.
+ ///
+ /// This handler generates a large response (over 400kB). It includes
+ /// a list of randomly generated strings to make sure that the test
+ /// can catch out of order delivery.
+ static ConstElementPtr
+ longResponseHandler(const string&, const ConstElementPtr&) {
+ ElementPtr arguments = Element::createList();
+ for (unsigned i = 0; i < 80000; ++i) {
+ std::ostringstream s;
+ s << std::setw(5) << i;
+ arguments->add(Element::create(s.str()));
+ }
+ return (createAnswer(0, arguments));
+ }
+};
+
+const char* CtrlChannelD2Test::CFG_TEST_FILE = "d2-test-config.json";
+
+// Test bad syntax rejected by the parser.
+TEST_F(CtrlChannelD2Test, parser) {
+ // no empty map.
+ string bad1 =
+ "{"
+ " \"ip-address\": \"192.168.77.1\","
+ " \"port\": 777,"
+ " \"control-socket\": { },"
+ " \"tsig-keys\": [],"
+ " \"forward-ddns\" : {},"
+ " \"reverse-ddns\" : {}"
+ "}";
+ ASSERT_THROW(parseDHCPDDNS(bad1), D2ParseError);
+
+ // unknown keyword.
+ string bad2 =
+ "{"
+ " \"ip-address\": \"192.168.77.1\","
+ " \"port\": 777,"
+ " \"control-socket\": {"
+ " \"socket-type\": \"unix\","
+ " \"socket-name\": \"/tmp/d2.sock\","
+ " \"bogus\": \"unknown...\""
+ " },"
+ " \"tsig-keys\": [],"
+ " \"forward-ddns\" : {},"
+ " \"reverse-ddns\" : {}"
+ "}";
+ ASSERT_THROW(parseDHCPDDNS(bad2), D2ParseError);
+}
+
+// Test bad syntax rejected by the process.
+TEST_F(CtrlChannelD2Test, configure) {
+ ASSERT_TRUE(server_);
+ ASSERT_NO_THROW(d2Controller()->initProcess());
+ D2ProcessPtr proc = d2Controller()->getProcess();
+ ASSERT_TRUE(proc);
+
+ // no type.
+ string bad1 =
+ "{"
+ " \"ip-address\": \"192.168.77.1\","
+ " \"port\": 777,"
+ " \"control-socket\": {"
+ " \"socket-name\": \"/tmp/d2.sock\""
+ " },"
+ " \"tsig-keys\": [],"
+ " \"forward-ddns\" : {},"
+ " \"reverse-ddns\" : {}"
+ "}";
+ ConstElementPtr config;
+ ASSERT_NO_THROW(config = parseDHCPDDNS(bad1, true));
+
+ ConstElementPtr answer = proc->configure(config, false);
+ ASSERT_TRUE(answer);
+
+ int status = 0;
+ ConstElementPtr txt = parseAnswer(status, answer);
+ EXPECT_EQ(1, status);
+ ASSERT_TRUE(txt);
+ ASSERT_EQ(Element::string, txt->getType());
+ EXPECT_EQ("Mandatory 'socket-type' parameter missing", txt->stringValue());
+ EXPECT_EQ(-1, CommandMgr::instance().getControlSocketFD());
+
+ // bad type.
+ string bad2 =
+ "{"
+ " \"ip-address\": \"192.168.77.1\","
+ " \"port\": 777,"
+ " \"control-socket\": {"
+ " \"socket-type\": \"bogus\","
+ " \"socket-name\": \"/tmp/d2.sock\""
+ " },"
+ " \"tsig-keys\": [],"
+ " \"forward-ddns\" : {},"
+ " \"reverse-ddns\" : {}"
+ "}";
+ ASSERT_NO_THROW(config = parseDHCPDDNS(bad2, true));
+
+ answer = proc->configure(config, false);
+ ASSERT_TRUE(answer);
+
+ status = 0;
+ txt = parseAnswer(status, answer);
+ EXPECT_EQ(1, status);
+ ASSERT_TRUE(txt);
+ ASSERT_EQ(Element::string, txt->getType());
+ EXPECT_EQ("Invalid 'socket-type' parameter value bogus",
+ txt->stringValue());
+ EXPECT_EQ(-1, CommandMgr::instance().getControlSocketFD());
+
+ // no name.
+ string bad3 =
+ "{"
+ " \"ip-address\": \"192.168.77.1\","
+ " \"port\": 777,"
+ " \"control-socket\": {"
+ " \"socket-type\": \"unix\""
+ " },"
+ " \"tsig-keys\": [],"
+ " \"forward-ddns\" : {},"
+ " \"reverse-ddns\" : {}"
+ "}";
+ ASSERT_NO_THROW(config = parseDHCPDDNS(bad3, true));
+
+ answer = proc->configure(config, false);
+ ASSERT_TRUE(answer);
+
+ status = 0;
+ txt = parseAnswer(status, answer);
+ EXPECT_EQ(1, status);
+ ASSERT_TRUE(txt);
+ ASSERT_EQ(Element::string, txt->getType());
+ EXPECT_EQ("Mandatory 'socket-name' parameter missing",
+ txt->stringValue());
+ EXPECT_EQ(-1, CommandMgr::instance().getControlSocketFD());
+}
+
+// This test checks which commands are registered by the D2 server.
+TEST_F(CtrlChannelD2Test, commandsRegistration) {
+
+ ConstElementPtr list_cmds = createCommand("list-commands");
+ ConstElementPtr answer;
+
+ // By default the list should be empty (except the standard list-commands
+ // supported by the CommandMgr itself).
+ EXPECT_NO_THROW(answer = CommandMgr::instance().processCommand(list_cmds));
+ ASSERT_TRUE(answer);
+ ASSERT_TRUE(answer->get("arguments"));
+ EXPECT_EQ("[ \"list-commands\" ]", answer->get("arguments")->str());
+
+ // Created server should register several additional commands.
+ EXPECT_NO_THROW(createUnixChannelServer());
+
+ EXPECT_NO_THROW(answer = CommandMgr::instance().processCommand(list_cmds));
+ ASSERT_TRUE(answer);
+ ASSERT_TRUE(answer->get("arguments"));
+ string command_list = answer->get("arguments")->str();
+
+ EXPECT_TRUE(command_list.find("\"list-commands\"") != string::npos);
+ EXPECT_TRUE(command_list.find("\"build-report\"") != string::npos);
+ EXPECT_TRUE(command_list.find("\"config-get\"") != string::npos);
+ EXPECT_TRUE(command_list.find("\"config-reload\"") != string::npos);
+ EXPECT_TRUE(command_list.find("\"config-set\"") != string::npos);
+ EXPECT_TRUE(command_list.find("\"config-test\"") != string::npos);
+ EXPECT_TRUE(command_list.find("\"config-write\"") != string::npos);
+ EXPECT_TRUE(command_list.find("\"shutdown\"") != string::npos);
+ EXPECT_TRUE(command_list.find("\"status-get\"") != string::npos);
+ EXPECT_TRUE(command_list.find("\"statistic-get\"") != string::npos);
+ EXPECT_TRUE(command_list.find("\"statistic-get-all\"") != string::npos);
+ EXPECT_TRUE(command_list.find("\"statistic-reset\"") != string::npos);
+ EXPECT_TRUE(command_list.find("\"statistic-reset-all\"") != string::npos);
+ EXPECT_TRUE(command_list.find("\"version-get\"") != string::npos);
+
+ // Ok, and now delete the server. It should deregister its commands.
+ server_.reset();
+
+ // The list should be (almost) empty again.
+ EXPECT_NO_THROW(answer = CommandMgr::instance().processCommand(list_cmds));
+ ASSERT_TRUE(answer);
+ ASSERT_TRUE(answer->get("arguments"));
+ EXPECT_EQ("[ \"list-commands\" ]", answer->get("arguments")->str());
+}
+
+// Tests that the server properly responds to invalid commands.
+TEST_F(CtrlChannelD2Test, invalid) {
+ EXPECT_NO_THROW(createUnixChannelServer());
+ string response;
+
+ sendUnixCommand("{ \"command\": \"bogus\" }", response);
+ EXPECT_EQ("{ \"result\": 2, \"text\": \"'bogus' command not supported.\" }",
+ response);
+
+ sendUnixCommand("utter nonsense", response);
+ EXPECT_EQ("{ \"result\": 1, \"text\": \"invalid first character u\" }",
+ response);
+}
+
+// Tests that the server properly responds to shutdown command.
+TEST_F(CtrlChannelD2Test, shutdown) {
+ EXPECT_NO_THROW(createUnixChannelServer());
+ string response;
+
+ sendUnixCommand("{ \"command\": \"shutdown\" }", response);
+ EXPECT_EQ("{ \"result\": 0, \"text\": \"Shutdown initiated, type is: normal\" }",
+ response);
+ EXPECT_EQ(EXIT_SUCCESS, server_->getExitValue());
+}
+
+// Tests that the server sets exit value supplied as argument
+// to shutdown command.
+TEST_F(CtrlChannelD2Test, shutdownExitValue) {
+ EXPECT_NO_THROW(createUnixChannelServer());
+ string response;
+
+ sendUnixCommand("{ \"command\": \"shutdown\", "
+ "\"arguments\": { \"exit-value\": 77 }}",
+ response);
+
+ EXPECT_EQ("{ \"result\": 0, \"text\": \"Shutdown initiated, type is: normal\" }",
+ response);
+
+ EXPECT_EQ(77, server_->getExitValue());
+}
+
+// This test verifies that the DHCP server handles version-get commands.
+TEST_F(CtrlChannelD2Test, getversion) {
+ EXPECT_NO_THROW(createUnixChannelServer());
+ string response;
+
+ // Send the version-get command.
+ sendUnixCommand("{ \"command\": \"version-get\" }", response);
+ EXPECT_TRUE(response.find("\"result\": 0") != string::npos);
+ EXPECT_TRUE(response.find("log4cplus") != string::npos);
+ EXPECT_FALSE(response.find("GTEST_VERSION") != string::npos);
+
+ // Send the build-report command.
+ sendUnixCommand("{ \"command\": \"build-report\" }", response);
+ EXPECT_TRUE(response.find("\"result\": 0") != string::npos);
+ EXPECT_TRUE(response.find("GTEST_VERSION") != string::npos);
+}
+
+// Tests that the server properly responds to list-commands command.
+TEST_F(CtrlChannelD2Test, listCommands) {
+ EXPECT_NO_THROW(createUnixChannelServer());
+ string response;
+
+ sendUnixCommand("{ \"command\": \"list-commands\" }", response);
+
+ ConstElementPtr rsp;
+ EXPECT_NO_THROW(rsp = Element::fromJSON(response));
+
+ // We expect the server to report at least the following commands:
+ checkListCommands(rsp, "build-report");
+ checkListCommands(rsp, "config-get");
+ checkListCommands(rsp, "config-reload");
+ checkListCommands(rsp, "config-set");
+ checkListCommands(rsp, "config-test");
+ checkListCommands(rsp, "config-write");
+ checkListCommands(rsp, "list-commands");
+ checkListCommands(rsp, "statistic-get");
+ checkListCommands(rsp, "statistic-get-all");
+ checkListCommands(rsp, "statistic-reset");
+ checkListCommands(rsp, "statistic-reset-all");
+ checkListCommands(rsp, "status-get");
+ checkListCommands(rsp, "shutdown");
+ checkListCommands(rsp, "version-get");
+}
+
+// This test verifies that the D2 server handles status-get commands
+TEST_F(CtrlChannelD2Test, statusGet) {
+ EXPECT_NO_THROW(createUnixChannelServer());
+
+ std::string response_txt;
+
+ // Send the version-get command
+ sendUnixCommand("{ \"command\": \"status-get\" }", response_txt);
+ ConstElementPtr response;
+ ASSERT_NO_THROW(response = Element::fromJSON(response_txt));
+ ASSERT_TRUE(response);
+ ASSERT_EQ(Element::map, response->getType());
+ EXPECT_EQ(2, response->size());
+ ConstElementPtr result = response->get("result");
+ ASSERT_TRUE(result);
+ ASSERT_EQ(Element::integer, result->getType());
+ EXPECT_EQ(0, result->intValue());
+ ConstElementPtr arguments = response->get("arguments");
+ ASSERT_EQ(Element::map, arguments->getType());
+
+ // The returned pid should be the pid of our process.
+ auto found_pid = arguments->get("pid");
+ ASSERT_TRUE(found_pid);
+ EXPECT_EQ(static_cast<int64_t>(getpid()), found_pid->intValue());
+
+ // It is hard to check the actual reload time as it is based
+ // on current time. Let's just make sure it is within a reasonable
+ // range.
+ auto found_reload = arguments->get("reload");
+ ASSERT_TRUE(found_reload);
+ EXPECT_LE(found_reload->intValue(), 5);
+ EXPECT_GE(found_reload->intValue(), 0);
+
+ /// @todo uptime is not available in this test, because the launch()
+ /// function is not called. This is not critical to test here,
+ /// because the same logic is tested for CA and in that case the
+ /// uptime is tested.
+}
+
+// Tests if the server returns its configuration using config-get.
+// Note there are separate tests that verify if toElement() called by the
+// config-get handler are actually converting the configuration correctly.
+TEST_F(CtrlChannelD2Test, configGet) {
+ EXPECT_NO_THROW(createUnixChannelServer());
+ string response;
+
+ sendUnixCommand("{ \"command\": \"config-get\" }", response);
+ ConstElementPtr rsp;
+
+ // The response should be a valid JSON.
+ EXPECT_NO_THROW(rsp = Element::fromJSON(response));
+ ASSERT_TRUE(rsp);
+
+ int status;
+ ConstElementPtr cfg = parseAnswer(status, rsp);
+ EXPECT_EQ(CONTROL_RESULT_SUCCESS, status);
+
+ // Ok, now roughly check if the response seems legit.
+ ASSERT_TRUE(cfg);
+ ASSERT_EQ(Element::map, cfg->getType());
+ EXPECT_TRUE(cfg->get("DhcpDdns"));
+}
+
+// Verify that the "config-test" command will do what we expect.
+TEST_F(CtrlChannelD2Test, configTest) {
+
+ // Define strings to permutate the config arguments.
+ // (Note the line feeds makes errors easy to find)
+ string config_test_txt = "{ \"command\": \"config-test\" \n";
+ string args_txt = " \"arguments\": { \n";
+ string d2_header =
+ " \"DhcpDdns\": \n";
+ string d2_cfg_txt =
+ " { \n"
+ " \"ip-address\": \"192.168.77.1\", \n"
+ " \"port\": 777, \n"
+ " \"forward-ddns\" : {}, \n"
+ " \"reverse-ddns\" : {}, \n"
+ " \"tsig-keys\": [ \n";
+ string key1 =
+ " {\"name\": \"d2_key.example.com\", \n"
+ " \"algorithm\": \"hmac-md5\", \n"
+ " \"secret\": \"LSWXnfkKZjdPJI5QxlpnfQ==\"} \n";
+ string key2 =
+ " {\"name\": \"d2_key.billcat.net\", \n"
+ " \"algorithm\": \"hmac-md5\", \n"
+ " \"digest-bits\": 120, \n"
+ " \"secret\": \"LSWXnfkKZjdPJI5QxlpnfQ==\"} \n";
+ string bad_key =
+ " {\"BOGUS\": \"d2_key.example.com\", \n"
+ " \"algorithm\": \"hmac-md5\", \n"
+ " \"secret\": \"LSWXnfkKZjdPJI5QxlpnfQ==\"} \n";
+ string key_footer =
+ " ] \n";
+ string control_socket_header =
+ " ,\"control-socket\": { \n"
+ " \"socket-type\": \"unix\", \n"
+ " \"socket-name\": \"";
+ string control_socket_footer =
+ "\" \n} \n";
+
+ ostringstream os;
+ // Create a valid config with all the parts should parse.
+ os << d2_cfg_txt
+ << key1
+ << key_footer
+ << control_socket_header
+ << socket_path_
+ << control_socket_footer
+ << "}\n";
+
+ ASSERT_TRUE(server_);
+
+ ConstElementPtr config;
+ ASSERT_NO_THROW(config = parseDHCPDDNS(os.str(), true));
+ ASSERT_NO_THROW(d2Controller()->initProcess());
+ D2ProcessPtr proc = d2Controller()->getProcess();
+ ASSERT_TRUE(proc);
+ ConstElementPtr answer = proc->configure(config, false);
+ ASSERT_TRUE(answer);
+ EXPECT_EQ("{ \"result\": 0, \"text\": \"Configuration applied successfully.\" }",
+ answer->str());
+ ASSERT_NO_THROW(d2Controller()->registerCommands());
+
+ // Check that the config was indeed applied.
+ D2CfgMgrPtr cfg_mgr = proc->getD2CfgMgr();
+ ASSERT_TRUE(cfg_mgr);
+ D2CfgContextPtr d2_context = cfg_mgr->getD2CfgContext();
+ ASSERT_TRUE(d2_context);
+ TSIGKeyInfoMapPtr keys = d2_context->getKeys();
+ ASSERT_TRUE(keys);
+ EXPECT_EQ(1, keys->size());
+
+ ASSERT_GT(CommandMgr::instance().getControlSocketFD(), -1);
+
+ // Create a config with malformed subnet that should fail to parse.
+ os.str("");
+ os << config_test_txt << ","
+ << args_txt
+ << d2_header
+ << d2_cfg_txt
+ << bad_key
+ << key_footer
+ << control_socket_header
+ << socket_path_
+ << control_socket_footer
+ << "}\n" // close DhcpDdns.
+ << "}}";
+
+ // Send the config-test command.
+ string response;
+ sendUnixCommand(os.str(), response);
+
+ // Should fail with a syntax error.
+ EXPECT_EQ("{ \"result\": 1, \"text\": \"missing parameter 'name' (<wire>:9:14)\" }",
+ response);
+
+ // Check that the config was not lost (fix: reacquire the context).
+ d2_context = cfg_mgr->getD2CfgContext();
+ keys = d2_context->getKeys();
+ ASSERT_TRUE(keys);
+ EXPECT_EQ(1, keys->size());
+
+ // Create a valid config with two keys and no command channel.
+ os.str("");
+ os << config_test_txt << ","
+ << args_txt
+ << d2_header
+ << d2_cfg_txt
+ << key1
+ << ",\n"
+ << key2
+ << key_footer
+ << "}\n" // close DhcpDdns.
+ << "}}";
+
+ // Verify the control channel socket exists.
+ ASSERT_TRUE(test::fileExists(socket_path_));
+
+ // Send the config-test command.
+ sendUnixCommand(os.str(), response);
+
+ // Verify the control channel socket still exists.
+ EXPECT_TRUE(test::fileExists(socket_path_));
+
+ // Verify the configuration was successful.
+ EXPECT_EQ("{ \"result\": 0, \"text\": \"Configuration check successful\" }",
+ response);
+
+ // Check that the config was not applied.
+ d2_context = cfg_mgr->getD2CfgContext();
+ keys = d2_context->getKeys();
+ ASSERT_TRUE(keys);
+ EXPECT_EQ(1, keys->size());
+}
+
+// Verify that the "config-set" command will do what we expect.
+TEST_F(CtrlChannelD2Test, configSet) {
+
+ // Define strings to permutate the config arguments.
+ // (Note the line feeds makes errors easy to find)
+ string config_set_txt = "{ \"command\": \"config-set\" \n";
+ string args_txt = " \"arguments\": { \n";
+ string d2_header =
+ " \"DhcpDdns\": \n";
+ string d2_cfg_txt =
+ " { \n"
+ " \"ip-address\": \"192.168.77.1\", \n"
+ " \"port\": 777, \n"
+ " \"forward-ddns\" : {}, \n"
+ " \"reverse-ddns\" : {}, \n"
+ " \"tsig-keys\": [ \n";
+ string key1 =
+ " {\"name\": \"d2_key.example.com\", \n"
+ " \"algorithm\": \"hmac-md5\", \n"
+ " \"secret\": \"LSWXnfkKZjdPJI5QxlpnfQ==\"} \n";
+ string key2 =
+ " {\"name\": \"d2_key.billcat.net\", \n"
+ " \"algorithm\": \"hmac-md5\", \n"
+ " \"digest-bits\": 120, \n"
+ " \"secret\": \"LSWXnfkKZjdPJI5QxlpnfQ==\"} \n";
+ string bad_key =
+ " {\"BOGUS\": \"d2_key.example.com\", \n"
+ " \"algorithm\": \"hmac-md5\", \n"
+ " \"secret\": \"LSWXnfkKZjdPJI5QxlpnfQ==\"} \n";
+ string key_footer =
+ " ] \n";
+ string control_socket_header =
+ " ,\"control-socket\": { \n"
+ " \"socket-type\": \"unix\", \n"
+ " \"socket-name\": \"";
+ string control_socket_footer =
+ "\" \n} \n";
+
+ ostringstream os;
+ // Create a valid config with all the parts should parse.
+ os << d2_cfg_txt
+ << key1
+ << key_footer
+ << control_socket_header
+ << socket_path_
+ << control_socket_footer
+ << "}\n";
+
+ ASSERT_TRUE(server_);
+
+ ConstElementPtr config;
+ ASSERT_NO_THROW(config = parseDHCPDDNS(os.str(), true));
+ ASSERT_NO_THROW(d2Controller()->initProcess());
+ D2ProcessPtr proc = d2Controller()->getProcess();
+ ASSERT_TRUE(proc);
+ ConstElementPtr answer = proc->configure(config, false);
+ ASSERT_TRUE(answer);
+ EXPECT_EQ("{ \"result\": 0, \"text\": \"Configuration applied successfully.\" }",
+ answer->str());
+ ASSERT_NO_THROW(d2Controller()->registerCommands());
+
+ // Check that the config was indeed applied.
+ D2CfgMgrPtr cfg_mgr = proc->getD2CfgMgr();
+ ASSERT_TRUE(cfg_mgr);
+ D2CfgContextPtr d2_context = cfg_mgr->getD2CfgContext();
+ ASSERT_TRUE(d2_context);
+ TSIGKeyInfoMapPtr keys = d2_context->getKeys();
+ ASSERT_TRUE(keys);
+ EXPECT_EQ(1, keys->size());
+
+ ASSERT_GT(CommandMgr::instance().getControlSocketFD(), -1);
+
+ // Create a config with malformed subnet that should fail to parse.
+ os.str("");
+ os << config_set_txt << ","
+ << args_txt
+ << d2_header
+ << d2_cfg_txt
+ << bad_key
+ << key_footer
+ << control_socket_header
+ << socket_path_
+ << control_socket_footer
+ << "}\n" // close DhcpDdns.
+ << "}}";
+
+ // Send the config-set command.
+ string response;
+ sendUnixCommand(os.str(), response);
+
+ // Should fail with a syntax error.
+ EXPECT_EQ("{ \"result\": 1, \"text\": \"missing parameter 'name' (<wire>:9:14)\" }",
+ response);
+
+ // Check that the config was not lost (fix: reacquire the context).
+ d2_context = cfg_mgr->getD2CfgContext();
+ keys = d2_context->getKeys();
+ ASSERT_TRUE(keys);
+ EXPECT_EQ(1, keys->size());
+
+ // Create a valid config with two keys and no command channel.
+ os.str("");
+ os << config_set_txt << ","
+ << args_txt
+ << d2_header
+ << d2_cfg_txt
+ << key1
+ << ",\n"
+ << key2
+ << key_footer
+ << "}\n" // close DhcpDdns.
+ << "}}";
+
+ // Verify the control channel socket exists.
+ ASSERT_TRUE(test::fileExists(socket_path_));
+
+ // Send the config-set command.
+ sendUnixCommand(os.str(), response);
+
+ // Verify the control channel socket no longer exists.
+ EXPECT_FALSE(test::fileExists(socket_path_));
+
+ // Verify the configuration was successful.
+ EXPECT_EQ("{ \"result\": 0, \"text\": \"Configuration applied successfully.\" }",
+ response);
+
+ // Check that the config was applied.
+ d2_context = cfg_mgr->getD2CfgContext();
+ keys = d2_context->getKeys();
+ ASSERT_TRUE(keys);
+ EXPECT_EQ(2, keys->size());
+}
+
+// Tests if config-write can be called without any parameters.
+TEST_F(CtrlChannelD2Test, writeConfigNoFilename) {
+ EXPECT_NO_THROW(createUnixChannelServer());
+ string response;
+
+ // This is normally set by the command line -c parameter.
+ server_->setConfigFile("test1.json");
+
+ // If the filename is not explicitly specified, the name used
+ // in -c command line switch is used.
+ sendUnixCommand("{ \"command\": \"config-write\" }", response);
+
+ checkConfigWrite(response, CONTROL_RESULT_SUCCESS, "test1.json");
+ ::remove("test1.json");
+}
+
+// Tests if config-write can be called with a valid filename as parameter.
+TEST_F(CtrlChannelD2Test, writeConfigFilename) {
+ EXPECT_NO_THROW(createUnixChannelServer());
+ string response;
+
+ sendUnixCommand("{ \"command\": \"config-write\", "
+ "\"arguments\": { \"filename\": \"test2.json\" } }",
+ response);
+ checkConfigWrite(response, CONTROL_RESULT_SUCCESS, "test2.json");
+ ::remove("test2.json");
+}
+
+// Tests if config-reload attempts to reload a file and reports that the
+// file is missing.
+TEST_F(CtrlChannelD2Test, configReloadMissingFile) {
+ EXPECT_NO_THROW(createUnixChannelServer());
+ string response;
+
+ // This is normally set to whatever value is passed to -c when the server is
+ // started, but we're not starting it that way, so need to set it by hand.
+ server_->setConfigFile("does-not-exist.json");
+
+ // Tell the server to reload its configuration. It should attempt to load
+ // does-not-exist.json (and fail, because the file is not there).
+ sendUnixCommand("{ \"command\": \"config-reload\" }", response);
+
+ // Verify the reload was rejected.
+ string expected = "{ \"result\": 1, \"text\": "
+ "\"Configuration parsing failed: "
+ "Unable to open file does-not-exist.json\" }";
+ EXPECT_EQ(expected, response);
+}
+
+// Tests if config-reload attempts to reload a file and reports that the
+// file is not a valid JSON.
+TEST_F(CtrlChannelD2Test, configReloadBrokenFile) {
+ EXPECT_NO_THROW(createUnixChannelServer());
+ string response;
+
+ // This is normally set to whatever value is passed to -c when the server is
+ // started, but we're not starting it that way, so need to set it by hand.
+ server_->setConfigFile("testbad.json");
+
+ // Although Kea is smart, its AI routines are not smart enough to handle
+ // this one... at least not yet.
+ ofstream f("testbad.json", ios::trunc);
+ f << "bla bla bla...";
+ f.close();
+
+ // Tell the server to reload its configuration. It should attempt to load
+ // testbad.json (and fail, because the file is not valid JSON).
+ // does-not-exist.json (and fail, because the file is not there).
+ sendUnixCommand("{ \"command\": \"config-reload\" }", response);
+
+ // Verify the reload was rejected.
+ string expected = "{ \"result\": 1, \"text\": "
+ "\"Configuration parsing failed: "
+ "testbad.json:1.1: Invalid character: b\" }";
+ EXPECT_EQ(expected, response);
+
+ // Remove the file.
+ ::remove("testbad.json");
+}
+
+// Tests if config-reload attempts to reload a file and reports that the
+// file is missing.
+TEST_F(CtrlChannelD2Test, configReloadFileValid) {
+ EXPECT_NO_THROW(createUnixChannelServer());
+ string response;
+
+ // This is normally set to whatever value is passed to -c when the server is
+ // started, but we're not starting it that way, so need to set it by hand.
+ server_->setConfigFile("testvalid.json");
+
+ // Ok, enough fooling around. Let's create a valid config.
+ ofstream f("testvalid.json", ios::trunc);
+ f << "{ \"DhcpDdns\": "
+ << "{"
+ << " \"ip-address\": \"192.168.77.1\" , "
+ << " \"port\": 777 , "
+ << "\"tsig-keys\": [], "
+ << "\"forward-ddns\" : {}, "
+ << "\"reverse-ddns\" : {} "
+ << "}"
+ << " }" << endl;
+ f.close();
+
+ // Tell the server to reload its configuration. It should attempt to load
+ // testvalid.json (and succeed).
+ sendUnixCommand("{ \"command\": \"config-reload\" }", response);
+
+ // Verify the reload was successful.
+ string expected = "{ \"result\": 0, \"text\": "
+ "\"Configuration applied successfully.\" }";
+ EXPECT_EQ(expected, response);
+
+ // Check that the config was indeed applied.
+ D2ProcessPtr proc = d2Controller()->getProcess();
+ ASSERT_TRUE(proc);
+ D2CfgMgrPtr d2_cfg_mgr = proc->getD2CfgMgr();
+ ASSERT_TRUE(d2_cfg_mgr);
+ D2ParamsPtr d2_params = d2_cfg_mgr->getD2Params();
+ ASSERT_TRUE(d2_params);
+
+ EXPECT_EQ("192.168.77.1", d2_params->getIpAddress().toText());
+ EXPECT_EQ(777, d2_params->getPort());
+ EXPECT_FALSE(d2_cfg_mgr->forwardUpdatesEnabled());
+ EXPECT_FALSE(d2_cfg_mgr->reverseUpdatesEnabled());
+
+ // Remove the file.
+ ::remove("testvalid.json");
+}
+
+/// Verify that concurrent connections over the control channel can be
+/// established. (@todo change when response will be sent in multiple chunks)
+TEST_F(CtrlChannelD2Test, concurrentConnections) {
+ EXPECT_NO_THROW(createUnixChannelServer());
+
+ boost::scoped_ptr<UnixControlClient> client1(new UnixControlClient());
+ ASSERT_TRUE(client1);
+
+ boost::scoped_ptr<UnixControlClient> client2(new UnixControlClient());
+ ASSERT_TRUE(client2);
+
+ // Client 1 connects.
+ ASSERT_TRUE(client1->connectToServer(socket_path_));
+ ASSERT_NO_THROW(getIOService()->poll());
+
+ // Client 2 connects.
+ ASSERT_TRUE(client2->connectToServer(socket_path_));
+ ASSERT_NO_THROW(getIOService()->poll());
+
+ // Send the command while another client is connected.
+ ASSERT_TRUE(client2->sendCommand("{ \"command\": \"list-commands\" }"));
+ ASSERT_NO_THROW(getIOService()->poll());
+
+ string response;
+ // The server should respond ok.
+ ASSERT_TRUE(client2->getResponse(response));
+ EXPECT_TRUE(response.find("\"result\": 0") != std::string::npos);
+
+ // Disconnect the servers.
+ client1->disconnectFromServer();
+ client2->disconnectFromServer();
+ ASSERT_NO_THROW(getIOService()->poll());
+}
+
+// This test verifies that the server can receive and process a large command.
+TEST_F(CtrlChannelD2Test, longCommand) {
+
+ ostringstream command;
+
+ // This is the desired size of the command sent to the server (1MB).
+ // The actual size sent will be slightly greater than that.
+ const size_t command_size = 1024 * 1000;
+
+ while (command.tellp() < command_size) {
+
+ // We're sending command 'foo' with arguments being a list of
+ // strings. If this is the first transmission, send command name
+ // and open the arguments list. Also insert the first argument
+ // so as all subsequent arguments can be prefixed with a comma.
+ if (command.tellp() == 0) {
+ command << "{ \"command\": \"foo\", \"arguments\": [ \"begin\"";
+
+ } else {
+ // Generate a random number and insert it into the stream as
+ // 10 digits long string.
+ ostringstream arg;
+ arg << setw(10) << std::rand();
+ // Append the argument in the command.
+ command << ", \"" << arg.str() << "\"\n";
+
+ // If we have hit the limit of the command size, close braces to
+ // get appropriate JSON.
+ if (command.tellp() > command_size) {
+ command << "] }";
+ }
+ }
+ }
+
+ ASSERT_NO_THROW(
+ CommandMgr::instance().registerCommand("foo",
+ std::bind(&CtrlChannelD2Test::longCommandHandler,
+ command.str(), ph::_1, ph::_2));
+ );
+
+ createUnixChannelServer();
+
+ string response;
+ std::thread th([this, &response, &command]() {
+
+ // IO service will be stopped automatically when this object goes
+ // out of scope and is destroyed. This is useful because we use
+ // asserts which may break the thread in various exit points.
+ IOServiceWork work(getIOService());
+
+ // Create client which we will use to send command to the server.
+ boost::scoped_ptr<UnixControlClient> client(new UnixControlClient());
+ ASSERT_TRUE(client);
+
+ // Connect to the server. This will trigger acceptor handler on the
+ // server side and create a new connection.
+ ASSERT_TRUE(client->connectToServer(socket_path_));
+
+ // Initially the remaining_string holds the entire command and we
+ // will be erasing the portions that we have sent.
+ string remaining_data = command.str();
+ while (!remaining_data.empty()) {
+ // Send the command in chunks of 1024 bytes.
+ const size_t l = remaining_data.size() < 1024 ? remaining_data.size() : 1024;
+ ASSERT_TRUE(client->sendCommand(remaining_data.substr(0, l)));
+ remaining_data.erase(0, l);
+ }
+
+ // Set timeout to 5 seconds to allow the time for the server to send
+ // a response.
+ const unsigned int timeout = 5;
+ ASSERT_TRUE(client->getResponse(response, timeout));
+
+ // We're done. Close the connection to the server.
+ client->disconnectFromServer();
+ });
+
+ // Run the server until the command has been processed and response
+ // received.
+ getIOService()->run();
+
+ // Wait for the thread to complete.
+ th.join();
+
+ EXPECT_EQ("{ \"result\": 0, \"text\": \"long command received ok\" }",
+ response);
+}
+
+// This test verifies that the server can send long response to the client.
+TEST_F(CtrlChannelD2Test, longResponse) {
+ // We need to generate large response. The simplest way is to create
+ // a command and a handler which will generate some static response
+ // of a desired size
+ ASSERT_NO_THROW(
+ CommandMgr::instance().registerCommand("foo",
+ std::bind(&CtrlChannelD2Test::longResponseHandler, ph::_1, ph::_2));
+ );
+
+ createUnixChannelServer();
+
+ // The UnixControlClient doesn't have any means to check that the entire
+ // response has been received. What we want to do is to generate a
+ // reference response using our command handler and then compare
+ // what we have received over the unix domain socket with this reference
+ // response to figure out when to stop receiving.
+ string reference_response = longResponseHandler("foo", ConstElementPtr())->str();
+
+ // In this stream we're going to collect out partial responses.
+ ostringstream response;
+
+ // The client is synchronous so it is useful to run it in a thread.
+ std::thread th([this, &response, reference_response]() {
+
+ // IO service will be stopped automatically when this object goes
+ // out of scope and is destroyed. This is useful because we use
+ // asserts which may break the thread in various exit points.
+ IOServiceWork work(getIOService());
+
+ // Remember the response size so as we know when we should stop
+ // receiving.
+ const size_t long_response_size = reference_response.size();
+
+ // Create the client and connect it to the server.
+ boost::scoped_ptr<UnixControlClient> client(new UnixControlClient());
+ ASSERT_TRUE(client);
+ ASSERT_TRUE(client->connectToServer(socket_path_));
+
+ // Send the stub command.
+ std::string command = "{ \"command\": \"foo\", \"arguments\": { } }";
+ ASSERT_TRUE(client->sendCommand(command));
+
+ // Keep receiving response data until we have received the full answer.
+ while (response.tellp() < long_response_size) {
+ std::string partial;
+ const unsigned int timeout = 5;
+ ASSERT_TRUE(client->getResponse(partial, timeout));
+ response << partial;
+ }
+
+ // We have received the entire response, so close the connection and
+ // stop the IO service.
+ client->disconnectFromServer();
+ });
+
+ // Run the server until the entire response has been received.
+ getIOService()->run();
+
+ // Wait for the thread to complete.
+ th.join();
+
+ // Make sure we have received correct response.
+ EXPECT_EQ(reference_response, response.str());
+}
+
+// This test verifies that the server signals timeout if the transmission
+// takes too long, after receiving a partial command
+TEST_F(CtrlChannelD2Test, connectionTimeoutPartialCommand) {
+ createUnixChannelServer();
+
+ // Set connection timeout to 2s to prevent long waiting time for the
+ // timeout during this test.
+ const unsigned short timeout = 2000;
+ CommandMgr::instance().setConnectionTimeout(timeout);
+
+ // Server's response will be assigned to this variable.
+ string response;
+
+ // It is useful to create a thread and run the server and the client
+ // at the same time and independently.
+ std::thread th([this, &response]() {
+
+ // IO service will be stopped automatically when this object goes
+ // out of scope and is destroyed. This is useful because we use
+ // asserts which may break the thread in various exit points.
+ IOServiceWork work(getIOService());
+
+ // Create the client and connect it to the server.
+ boost::scoped_ptr<UnixControlClient> client(new UnixControlClient());
+ ASSERT_TRUE(client);
+ ASSERT_TRUE(client->connectToServer(socket_path_));
+
+ // Send partial command. The server will be waiting for the remaining
+ // part to be sent and will eventually signal a timeout.
+ string command = "{ \"command\": \"foo\" ";
+ ASSERT_TRUE(client->sendCommand(command));
+
+ // Let's wait up to 15s for the server's response. The response
+ // should arrive sooner assuming that the timeout mechanism for
+ // the server is working properly.
+ const unsigned int timeout = 15;
+ ASSERT_TRUE(client->getResponse(response, timeout));
+
+ // Explicitly close the client's connection.
+ client->disconnectFromServer();
+ });
+
+ // Run the server until stopped.
+ getIOService()->run();
+
+ // Wait for the thread to return.
+ th.join();
+
+ // Check that the server has signalled a timeout.
+ EXPECT_EQ("{ \"result\": 1, \"text\": \"Connection over control channel timed out, discarded partial command of 19 bytes\" }" ,
+ response);
+}
+
+// This test verifies that the server signals timeout if the transmission
+// takes too long, having received no data from the client.
+TEST_F(CtrlChannelD2Test, connectionTimeoutNoData) {
+ createUnixChannelServer();
+
+ // Set connection timeout to 2s to prevent long waiting time for the
+ // timeout during this test.
+ const unsigned short timeout = 2000;
+ CommandMgr::instance().setConnectionTimeout(timeout);
+
+ // Server's response will be assigned to this variable.
+ string response;
+
+ // It is useful to create a thread and run the server and the client
+ // at the same time and independently.
+ std::thread th([this, &response]() {
+
+ // IO service will be stopped automatically when this object goes
+ // out of scope and is destroyed. This is useful because we use
+ // asserts which may break the thread in various exit points.
+ IOServiceWork work(getIOService());
+
+ // Create the client and connect it to the server.
+ boost::scoped_ptr<UnixControlClient> client(new UnixControlClient());
+ ASSERT_TRUE(client);
+ ASSERT_TRUE(client->connectToServer(socket_path_));
+
+ // Let's wait up to 15s for the server's response. The response
+ // should arrive sooner assuming that the timeout mechanism for
+ // the server is working properly.
+ const unsigned int timeout = 15;
+ ASSERT_TRUE(client->getResponse(response, timeout));
+
+ // Explicitly close the client's connection.
+ client->disconnectFromServer();
+ });
+
+ // Run the server until stopped.
+ getIOService()->run();
+
+ // Wait for the thread to return.
+ th.join();
+
+ // Check that the server has signalled a timeout.
+ EXPECT_EQ("{ \"result\": 1, \"text\": \"Connection over control channel timed out\" }",
+ response);
+}
+
+} // End of anonymous namespace
diff --git a/src/bin/d2/tests/d2_controller_unittests.cc b/src/bin/d2/tests/d2_controller_unittests.cc
new file mode 100644
index 0000000..0c6e5ec
--- /dev/null
+++ b/src/bin/d2/tests/d2_controller_unittests.cc
@@ -0,0 +1,303 @@
+// Copyright (C) 2013-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <asiolink/testutils/timed_signal.h>
+#include <cc/command_interpreter.h>
+#include <d2srv/testutils/nc_test_utils.h>
+#include <d2/d2_controller.h>
+#include <d2/d2_process.h>
+#include <process/testutils/d_test_stubs.h>
+
+#include <boost/pointer_cast.hpp>
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <gtest/gtest.h>
+
+#include <sstream>
+
+using namespace isc::asiolink::test;
+using namespace isc::process;
+using namespace boost::posix_time;
+
+namespace isc {
+namespace d2 {
+
+/// @brief Test fixture class for testing D2Controller class.
+///
+/// This class derives from DControllerTest and wraps a D2Controller. Much of
+/// the underlying functionality is in the DControllerBase class which has an
+/// extensive set of unit tests that are independent of DHCP-DDNS.
+/// @TODO Currently These tests are relatively light and duplicate some of
+/// the testing done on the base class. These tests are sufficient to ensure
+/// that D2Controller properly derives from its base class and to test the
+/// logic that is unique to D2Controller. These tests will be augmented and
+/// or new tests added as additional functionality evolves.
+/// Unlike the stub testing, there is no use of SimFailure to induce error
+/// conditions as this is production code.
+class D2ControllerTest : public DControllerTest {
+public:
+ /// @brief Constructor
+ /// Note the constructor passes in the static D2Controller instance
+ /// method.
+ D2ControllerTest() : DControllerTest(D2Controller::instance) {
+ }
+
+ /// @brief Fetches the D2Controller's D2Process
+ ///
+ /// @return A pointer to the process which may be null if it has not yet
+ /// been instantiated.
+ D2ProcessPtr getD2Process() {
+ return (boost::dynamic_pointer_cast<D2Process>(getProcess()));
+ }
+
+ /// @brief Fetches the D2Process's D2Configuration manager
+ ///
+ /// @return A pointer to the manager which may be null if it has not yet
+ /// been instantiated.
+ D2CfgMgrPtr getD2CfgMgr() {
+ D2CfgMgrPtr p;
+ if (getD2Process()) {
+ p = getD2Process()->getD2CfgMgr();
+ }
+
+ return (p);
+ }
+
+ /// @brief Fetches the D2Configuration manager's D2CfgContext
+ ///
+ /// @return A pointer to the context which may be null if it has not yet
+ /// been instantiated.
+ D2CfgContextPtr getD2CfgContext() {
+ D2CfgContextPtr p;
+ if (getD2CfgMgr()) {
+ p = getD2CfgMgr()->getD2CfgContext();
+ }
+
+ return (p);
+ }
+};
+
+/// @brief Basic Controller instantiation testing.
+/// Verifies that the controller singleton gets created and that the
+/// basic derivation from the base class is intact.
+TEST_F(D2ControllerTest, basicInstanceTesting) {
+ // Verify the singleton instance can be fetched and that
+ // it has the correct type.
+ DControllerBasePtr& controller = DControllerTest::getController();
+ ASSERT_TRUE(controller);
+ ASSERT_NO_THROW(boost::dynamic_pointer_cast<D2Controller>(controller));
+
+ // Verify that controller's app name is correct.
+ EXPECT_TRUE(checkAppName(D2Controller::d2_app_name_));
+
+ // Verify that controller's bin name is correct.
+ EXPECT_TRUE(checkBinName(D2Controller::d2_bin_name_));
+
+ // Verify that controller's IOService exists.
+ EXPECT_TRUE(checkIOService());
+
+ // Verify that the Process does NOT exist.
+ EXPECT_FALSE(checkProcess());
+}
+
+/// @brief Tests basic command line processing.
+/// Verifies that:
+/// 1. Standard command line options are supported.
+/// 2. Invalid options are detected.
+TEST_F(D2ControllerTest, commandLineArgs) {
+ char* argv[] = { const_cast<char*>("progName"),
+ const_cast<char*>("-c"),
+ const_cast<char*>(DControllerTest::CFG_TEST_FILE),
+ const_cast<char*>("-d") };
+ int argc = 4;
+
+ // Verify that verbose flag is false initially.
+ EXPECT_TRUE(checkVerbose(false));
+
+ // Verify that standard options can be parsed without error.
+ EXPECT_NO_THROW(parseArgs(argc, argv));
+
+ // Verify that verbose flag is true.
+ EXPECT_TRUE(checkVerbose(true));
+
+ // Verify configuration file name is correct.
+ EXPECT_TRUE(checkConfigFileName(DControllerTest::CFG_TEST_FILE));
+
+ // Verify that an unknown option is detected.
+ char* argv2[] = { const_cast<char*>("progName"),
+ const_cast<char*>("-x") };
+ argc = 2;
+ EXPECT_THROW(parseArgs(argc, argv2), InvalidUsage);
+}
+
+/// @brief Tests application process creation and initialization.
+/// Verifies that the process can be successfully created and initialized.
+TEST_F(D2ControllerTest, initProcessTesting) {
+ ASSERT_NO_THROW(initProcess());
+ EXPECT_TRUE(checkProcess());
+}
+
+/// @brief Tests launch and normal shutdown (stand alone mode).
+/// This creates an interval timer to generate a normal shutdown and then
+/// launches with a valid, stand-alone command line and no simulated errors.
+TEST_F(D2ControllerTest, launchNormalShutdown) {
+ // Write valid_d2_config and then run launch() for 1000 ms.
+ time_duration elapsed_time;
+ runWithConfig(valid_d2_config, 1000, elapsed_time);
+
+ // Give a generous margin to accommodate slower test environs.
+ EXPECT_TRUE(elapsed_time.total_milliseconds() >= 800 &&
+ elapsed_time.total_milliseconds() <= 1300);
+}
+
+/// @brief Configuration update event testing.
+/// This really tests just the ability of the handlers to invoke the necessary
+/// chain of methods and handle error conditions. Configuration parsing and
+/// retrieval should be tested as part of the d2 configuration management
+/// implementation.
+/// This test verifies that:
+/// 1. A valid configuration yields a successful parse result.
+/// 2. That an application process error in configuration updating is handled
+/// properly.
+TEST_F(D2ControllerTest, configUpdateTests) {
+ int rcode = -1;
+ isc::data::ConstElementPtr answer;
+
+ // Initialize the application process.
+ ASSERT_NO_THROW(initProcess());
+ EXPECT_TRUE(checkProcess());
+
+ // Create a configuration set using a small, valid D2 configuration.
+ isc::data::ElementPtr config_set =
+ isc::data::Element::fromJSON(valid_d2_config);
+
+ // Verify that given a valid config we get a successful update result.
+ answer = updateConfig(config_set);
+ isc::config::parseAnswer(rcode, answer);
+ EXPECT_EQ(0, rcode);
+
+ // Verify that given a valid config we get a successful check result.
+ answer = checkConfig(config_set);
+ isc::config::parseAnswer(rcode, answer);
+ EXPECT_EQ(0, rcode);
+
+ // Use an invalid configuration to verify parsing error return.
+ std::string config = "{ \"ip-address\": 1000 } ";
+ config_set = isc::data::Element::fromJSON(config);
+ answer = updateConfig(config_set);
+ isc::config::parseAnswer(rcode, answer);
+ EXPECT_EQ(1, rcode);
+
+ // Use an invalid configuration to verify checking error return.
+ answer = checkConfig(config_set);
+ isc::config::parseAnswer(rcode, answer);
+ EXPECT_EQ(1, rcode);
+}
+
+// Tests that the original configuration is retained after a SIGHUP triggered
+// reconfiguration fails due to invalid config content.
+TEST_F(D2ControllerTest, invalidConfigReload) {
+ // Schedule to replace the configuration file after launch. This way the
+ // file is updated after we have done the initial configuration.
+ scheduleTimedWrite("{ \"string_test\": BOGUS JSON }", 100);
+
+ // Setup to raise SIGHUP in 200 ms.
+ TimedSignal sighup(*getIOService(), SIGHUP, 200);
+
+ // Write valid_d2_config and then run launch() for a maximum of 500 ms.
+ time_duration elapsed_time;
+ runWithConfig(valid_d2_config, 500, elapsed_time);
+
+ // Context is still available post launch.
+ // Check to see that our configuration matches the original per
+ // valid_d2_config (see src/lib/process/testutils/d_test_stubs.cc)
+ D2CfgMgrPtr d2_cfg_mgr = getD2CfgMgr();
+ D2ParamsPtr d2_params = d2_cfg_mgr->getD2Params();
+ ASSERT_TRUE(d2_params);
+
+ EXPECT_EQ("127.0.0.1", d2_params->getIpAddress().toText());
+ EXPECT_EQ(5031, d2_params->getPort());
+ EXPECT_TRUE(d2_cfg_mgr->forwardUpdatesEnabled());
+ EXPECT_TRUE(d2_cfg_mgr->reverseUpdatesEnabled());
+
+ /// @todo add a way to trap log file and search it
+}
+
+// Tests that the original configuration is replaced after a SIGHUP triggered
+// reconfiguration succeeds.
+TEST_F(D2ControllerTest, validConfigReload) {
+ // Define a replacement config.
+ const char* second_cfg =
+ "{"
+ " \"ip-address\": \"192.168.77.1\" , "
+ " \"port\": 777 , "
+ "\"tsig-keys\": [], "
+ "\"forward-ddns\" : {}, "
+ "\"reverse-ddns\" : {} "
+ "}";
+
+ // Schedule to replace the configuration file after launch. This way the
+ // file is updated after we have done the initial configuration.
+ scheduleTimedWrite(second_cfg, 100);
+
+ // Setup to raise SIGHUP in 200 ms.
+ TimedSignal sighup(*getIOService(), SIGHUP, 200);
+
+ // Write valid_d2_config and then run launch() for a maximum of 500ms.
+ time_duration elapsed_time;
+ runWithConfig(valid_d2_config, 500, elapsed_time);
+
+ // Context is still available post launch.
+ // Check to see that our configuration matches the replacement config.
+ D2CfgMgrPtr d2_cfg_mgr = getD2CfgMgr();
+ D2ParamsPtr d2_params = d2_cfg_mgr->getD2Params();
+ ASSERT_TRUE(d2_params);
+
+ EXPECT_EQ("192.168.77.1", d2_params->getIpAddress().toText());
+ EXPECT_EQ(777, d2_params->getPort());
+ EXPECT_FALSE(d2_cfg_mgr->forwardUpdatesEnabled());
+ EXPECT_FALSE(d2_cfg_mgr->reverseUpdatesEnabled());
+
+ /// @todo add a way to trap log file and search it
+}
+
+// Tests that the SIGINT triggers a normal shutdown.
+TEST_F(D2ControllerTest, sigintShutdown) {
+ // Setup to raise SIGINT in 1 ms.
+ TimedSignal sighup(*getIOService(), SIGINT, 1);
+
+ // Write valid_d2_config and then run launch() for a maximum of 1000 ms.
+ time_duration elapsed_time;
+ runWithConfig(valid_d2_config, 1000, elapsed_time);
+
+ // Signaled shutdown should make our elapsed time much smaller than
+ // the maximum run time. Give generous margin to accommodate slow
+ // test environs.
+ EXPECT_TRUE(elapsed_time.total_milliseconds() < 300);
+
+ /// @todo add a way to trap log file and search it
+}
+
+// Tests that the SIGTERM triggers a normal shutdown.
+TEST_F(D2ControllerTest, sigtermShutdown) {
+ // Setup to raise SIGTERM in 1 ms.
+ TimedSignal sighup(*getIOService(), SIGTERM, 1);
+
+ // Write valid_d2_config and then run launch() for a maximum of 1 s.
+ time_duration elapsed_time;
+ runWithConfig(valid_d2_config, 1000, elapsed_time);
+
+ // Signaled shutdown should make our elapsed time much smaller than
+ // the maximum run time. Give generous margin to accommodate slow
+ // test environs.
+ EXPECT_TRUE(elapsed_time.total_milliseconds() < 300);
+
+ /// @todo add a way to trap log file and search it
+}
+
+}; // end of isc::d2 namespace
+}; // end of isc namespace
diff --git a/src/bin/d2/tests/d2_process_tests.sh.in b/src/bin/d2/tests/d2_process_tests.sh.in
new file mode 100644
index 0000000..895b9a2
--- /dev/null
+++ b/src/bin/d2/tests/d2_process_tests.sh.in
@@ -0,0 +1,332 @@
+#!/bin/sh
+
+# Copyright (C) 2014-2021 Internet Systems Consortium, Inc. ("ISC")
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# shellcheck disable=SC1091
+# SC1091: Not following: ... was not specified as input (see shellcheck -x).
+
+# shellcheck disable=SC2039
+# SC2039: In POSIX sh, 'local' is undefined.
+
+# Exit with error if commands exit with non-zero and if undefined variables are
+# used.
+set -eu
+
+# Path to the temporary configuration file.
+CFG_FILE="@abs_top_builddir@/src/bin/d2/tests/test_config.json"
+# Path to the D2 log file.
+LOG_FILE="@abs_top_builddir@/src/bin/d2/tests/test.log"
+# D2 configuration to be stored in the configuration file.
+CONFIG="{
+ \"DhcpDdns\":
+ {
+ \"ip-address\": \"127.0.0.1\",
+ \"port\": 53001,
+ \"tsig-keys\": [],
+ \"forward-ddns\" : {},
+ \"reverse-ddns\" : {},
+ \"loggers\": [
+ {
+ \"name\": \"kea-dhcp-ddns\",
+ \"output_options\": [
+ {
+ \"output\": \"$LOG_FILE\"
+ }
+ ],
+ \"severity\": \"DEBUG\"
+ }
+ ]
+ }
+}"
+
+# Invalid configuration (syntax error) to check that Kea can check syntax.
+CONFIG_BAD_SYNTAX="{
+ \"DhcpDdns\":
+ {
+ \"ip-address\": \"127.0.0.1\",
+ \"port\": BOGUS,
+ \"tsig-keys\": [],
+ \"forward-ddns\" : {},
+ \"reverse-ddns\" : {},
+ \"loggers\": [
+ {
+ \"name\": \"kea-dhcp-ddns\",
+ \"output_options\": [
+ {
+ \"output\": \"$LOG_FILE\"
+ }
+ ],
+ \"severity\": \"INFO\"
+ }
+ ]
+ }
+}"
+
+# Invalid configuration (out of range port) to check that Kea can check syntax.
+CONFIG_BAD_VALUE="{
+ \"DhcpDdns\":
+ {
+ \"ip-address\": \"127.0.0.1\",
+ \"port\": 80000,
+ \"tsig-keys\": [],
+ \"forward-ddns\" : {},
+ \"reverse-ddns\" : {},
+ \"loggers\": [
+ {
+ \"name\": \"kea-dhcp-ddns\",
+ \"output_options\": [
+ {
+ \"output\": \"$LOG_FILE\"
+ }
+ ],
+ \"severity\": \"INFO\"
+ }
+ ]
+ }
+}"
+
+# Invalid value configuration (invalid port) to check that D2
+# gracefully handles reconfiguration errors.
+CONFIG_INVALID="{
+ \"DhcpDdns\":
+ {
+ \"ip-address\": \"127.0.0.1\",
+ \"port\": BOGUS,
+ \"tsig-keys\": [],
+ \"forward-ddns\" : {},
+ \"reverse-ddns\" : {},
+ \"loggers\": [
+ {
+ \"name\": \"kea-dhcp-ddns\",
+ \"output_options\": [
+ {
+ \"output\": \"$LOG_FILE\"
+ }
+ ],
+ \"severity\": \"INFO\"
+ }
+ ]
+ }
+}"
+
+CONFIG_WITH_SECRET='
+{
+ "DhcpDdns": {
+ "tsig-keys": [
+ {
+ "algorithm": "HMAC-MD5",
+ "name": "d2.md5.key",
+ "secret": "sensitivejdPJI5QxlpnfQ=="
+ }
+ ],
+ "user-context": {
+ "password": "superadmin",
+ "secret": "superadmin",
+ "shared-info": {
+ "password": "superadmin",
+ "secret": "superadmin"
+ }
+ }
+ }
+}
+'
+
+# Set the location of the executable.
+bin="kea-dhcp-ddns"
+bin_path="@abs_top_builddir@/src/bin/d2"
+
+# Import common test library.
+. "@abs_top_builddir@/src/lib/testutils/dhcp_test_lib.sh"
+
+# This test verifies that syntax checking works properly. This function
+# requires 3 parameters:
+# test_name
+# config - string with a content of the config (will be written to a file)
+# expected_code - expected exit code returned by kea (0 - success, 1 - failure)
+syntax_check_test() {
+ local test_name="${1}"
+ local config="${2}"
+ local expected_code="${3}"
+
+ # Log the start of the test and print test name.
+ test_start "${test_name}"
+ # Create correct configuration file.
+ create_config "${config}"
+ # Check it
+ printf "Running command %s.\n" "\"${bin_path}/${bin} -t ${CFG_FILE}\""
+ run_command \
+ "${bin_path}/${bin}" -t "${CFG_FILE}"
+ if [ "${EXIT_CODE}" -ne "${expected_code}" ]; then
+ printf 'ERROR: expected exit code %s, got %s\n' "${expected_code}" "${EXIT_CODE}"
+ clean_exit 1
+ fi
+ test_finish 0
+}
+
+# This test verifies that D2 can be reconfigured with a SIGHUP signal.
+dynamic_reconfiguration_test() {
+ # Log the start of the test and print test name.
+ test_start "dhcp_ddns.dynamic_reconfiguration"
+ # Create new configuration file.
+ create_config "${CONFIG}"
+ # Instruct D2 to log to the specific file.
+ set_logger
+ # Start D2.
+ start_kea ${bin_path}/${bin}
+ # Wait up to 20s for D2 to start.
+ wait_for_kea 20
+ if [ "${_WAIT_FOR_KEA}" -eq 0 ]; then
+ printf "ERROR: timeout waiting for D2 to start.\n"
+ clean_exit 1
+ fi
+
+ # Check if it is still running. It could have terminated (e.g. as a result
+ # of configuration failure).
+ get_pid ${bin}
+ if [ "${_GET_PIDS_NUM}" -ne 1 ]; then
+ printf "ERROR: expected one D2 process to be started. Found %d processes\
+ started.\n" "${_GET_PIDS_NUM}"
+ clean_exit 1
+ fi
+
+ # Check in the log file, how many times server has been configured.
+ # It should be just once on startup.
+ get_reconfigs
+ if [ "${_GET_RECONFIGS}" -ne 1 ]; then
+ printf "ERROR: D2 hasn't been configured.\n"
+ clean_exit 1
+ else
+ printf "D2 successfully configured.\n"
+ fi
+
+ # Now use invalid configuration.
+ create_config "${CONFIG_INVALID}"
+
+ # Try to reconfigure by sending SIGHUP
+ send_signal 1 ${bin}
+
+ # Wait up to 10s for the D2Controller to log reload signal received.
+ wait_for_message 10 "DCTL_CFG_FILE_RELOAD_SIGNAL_RECVD" 1
+ if [ "${_WAIT_FOR_MESSAGE}" -eq 0 ]; then
+ printf "ERROR: D2 did report the reload signal receipt.\n"
+ clean_exit 1
+ fi
+
+ # After receiving SIGHUP the server should try to reconfigure itself.
+ # The configuration provided is invalid so it should result in
+ # reconfiguration failure but the server should still be running.
+ wait_for_message 10 "DCTL_CFG_FILE_RELOAD_ERROR" 1
+ if [ "${_WAIT_FOR_MESSAGE}" -eq 0 ]; then
+ printf "ERROR: D2 did not report reload error.\n"
+ clean_exit 1
+ fi
+
+ # Make sure the server is still operational.
+ get_pid ${bin}
+ if [ "${_GET_PIDS_NUM}" -ne 1 ]; then
+ printf "ERROR: D2 was killed when attempting reconfiguration.\n"
+ clean_exit 1
+ fi
+
+ # Restore the good configuration.
+ create_config "${CONFIG}"
+
+ # Reconfigure the server with SIGHUP.
+ send_signal 1 ${bin}
+
+ # There should be two occurrences of the DHCP4_CONFIG_COMPLETE messages.
+ # Wait for it up to 10s.
+ wait_for_message 10 "DCTL_CONFIG_COMPLETE" 2
+
+ # After receiving SIGHUP the server should get reconfigured and the
+ # reconfiguration should be noted in the log file. We should now
+ # have two configurations logged in the log file.
+ if [ "${_WAIT_FOR_MESSAGE}" -eq 0 ]; then
+ printf "ERROR: D2 hasn't been reconfigured.\n"
+ clean_exit 1
+ else
+ printf "D2 successfully reconfigured.\n"
+ fi
+
+ # Make sure the server is still operational.
+ get_pid ${bin}
+ if [ "${_GET_PIDS_NUM}" -ne 1 ]; then
+ printf "ERROR: D2 was killed when attempting reconfiguration.\n"
+ clean_exit 1
+ fi
+
+ # All ok. Shut down D2 and exit.
+ test_finish 0
+}
+
+# This test verifies that DHCPv4 server is shut down gracefully when it
+# receives a SIGINT or SIGTERM signal.
+shutdown_test() {
+ local test_name="${1}" # Test name
+ local signum="${2}" # Signal number
+ # Log the start of the test and print test name.
+ test_start "${test_name}"
+ # Create new configuration file.
+ create_config "${CONFIG}"
+ # Instruct D2 to log to the specific file.
+ set_logger
+ # Start D2.
+ start_kea ${bin_path}/${bin}
+ # Wait up to 20s for D2 to start.
+ wait_for_kea 20
+ if [ "${_WAIT_FOR_KEA}" -eq 0 ]; then
+ printf "ERROR: timeout waiting for D2 to start.\n"
+ clean_exit 1
+ fi
+
+ # Check if it is still running. It could have terminated (e.g. as a result
+ # of configuration failure).
+ get_pid ${bin}
+ if [ "${_GET_PIDS_NUM}" -ne 1 ]; then
+ printf "ERROR: expected one D2 process to be started. Found %d processes\
+ started.\n" "${_GET_PIDS_NUM}"
+ clean_exit 1
+ fi
+
+ # Check in the log file, how many times server has been configured.
+ # It should be just once on startup.
+ get_reconfigs
+ if [ "${_GET_RECONFIGS}" -ne 1 ]; then
+ printf "ERROR: server hasn't been configured.\n"
+ clean_exit 1
+ else
+ printf "Server successfully configured.\n"
+ fi
+
+ # Send signal to D2 (SIGTERM, SIGINT etc.)
+ send_signal "${signum}" "${bin}"
+
+ # Now wait for process to log that it is exiting.
+ wait_for_message 10 "DCTL_SHUTDOWN" 1
+ if [ "${_WAIT_FOR_MESSAGE}" -eq 0 ]; then
+ printf "ERROR: DHCP-DDNS did not log shutdown.\n"
+ clean_exit 1
+ fi
+
+ # Make sure the server is down.
+ wait_for_server_down 5 ${bin}
+ assert_eq 1 "${_WAIT_FOR_SERVER_DOWN}" \
+ "Expected wait_for_server_down return %d, returned %d"
+
+ test_finish 0
+}
+
+server_pid_file_test "${CONFIG}" DCTL_ALREADY_RUNNING
+dynamic_reconfiguration_test
+shutdown_test "dhcp-ddns.sigterm_test" 15
+shutdown_test "dhcp-ddns.sigint_test" 2
+version_test "dhcp-ddns.version"
+logger_vars_test "dhcp-ddns.variables"
+syntax_check_test "dhcp-ddns.syntax_check_success" "${CONFIG}" 0
+syntax_check_test "dhcp-ddns.syntax_check_bad_syntax" "${CONFIG_BAD_SYNTAX}" 1
+syntax_check_test "dhcp-ddns.syntax_check_bad_values" "${CONFIG_BAD_VALUE}" 1
+password_redact_test "dhcp-ddns.password_redact_test" "${CONFIG_WITH_SECRET}" 0
diff --git a/src/bin/d2/tests/d2_process_unittests.cc b/src/bin/d2/tests/d2_process_unittests.cc
new file mode 100644
index 0000000..4dda275
--- /dev/null
+++ b/src/bin/d2/tests/d2_process_unittests.cc
@@ -0,0 +1,693 @@
+// Copyright (C) 2013-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <asiolink/io_service.h>
+#include <cc/command_interpreter.h>
+#include <d2srv/testutils/nc_test_utils.h>
+#include <d2/d2_process.h>
+#include <d2/tests/test_configured_libraries.h>
+#include <dhcp_ddns/ncr_io.h>
+#include <process/testutils/d_test_stubs.h>
+
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <gtest/gtest.h>
+
+#include <functional>
+#include <sstream>
+
+using namespace std;
+using namespace isc;
+using namespace isc::config;
+using namespace isc::d2;
+using namespace isc::data;
+using namespace isc::process;
+using namespace boost::posix_time;
+
+namespace {
+
+/// @brief Valid configuration containing an unavailable IP address.
+const char* bad_ip_d2_config = "{ "
+ "\"ip-address\" : \"1.1.1.1\" , "
+ "\"port\" : 5031, "
+ "\"tsig-keys\": ["
+ "{ \"name\": \"d2_key.example.com\" , "
+ " \"algorithm\": \"HMAC-MD5\" ,"
+ " \"secret\": \"LSWXnfkKZjdPJI5QxlpnfQ==\" "
+ "} ],"
+ "\"forward-ddns\" : {"
+ "\"ddns-domains\": [ "
+ "{ \"name\": \"example.com\" , "
+ " \"key-name\": \"d2_key.example.com\" , "
+ " \"dns-servers\" : [ "
+ " { \"ip-address\": \"127.0.0.101\" } "
+ "] } ] }, "
+ "\"reverse-ddns\" : {"
+ "\"ddns-domains\": [ "
+ "{ \"name\": \" 0.168.192.in.addr.arpa.\" , "
+ " \"key-name\": \"d2_key.example.com\" , "
+ " \"dns-servers\" : [ "
+ " { \"ip-address\": \"127.0.0.101\" , "
+ " \"port\": 100 } ] } "
+ "] } }";
+
+/// @brief D2Process test fixture class
+//class D2ProcessTest : public D2Process, public ::testing::Test {
+class D2ProcessTest : public D2Process, public ConfigParseTest {
+public:
+
+ /// @brief Constructor
+ D2ProcessTest() :
+ D2Process("d2test",
+ asiolink::IOServicePtr(new isc::asiolink::IOService())) {
+ }
+
+ /// @brief Destructor
+ virtual ~D2ProcessTest() {
+ }
+
+ /// @brief Callback that will invoke shutdown method.
+ void genShutdownCallback() {
+ shutdown(ConstElementPtr());
+ }
+
+ /// @brief Callback that throws an exception.
+ void genFatalErrorCallback() {
+ isc_throw (DProcessBaseError, "simulated fatal error");
+ }
+
+ /// @brief Reconfigures and starts the queue manager given a configuration.
+ ///
+ /// This method emulates the reception of a new configuration and should
+ /// conclude with the Queue manager placed in the RUNNING state.
+ ///
+ /// @param config is the configuration to use
+ ///
+ /// @return Returns AssertionSuccess if the queue manager was successfully
+ /// reconfigured, AssertionFailure otherwise.
+ ::testing::AssertionResult runWithConfig(const char* config) {
+ int rcode = -1;
+ // Convert the string configuration into an Element set.
+ ::testing::AssertionResult res = fromJSON(config);
+ if (res != ::testing::AssertionSuccess()) {
+ return res;
+ }
+
+ ConstElementPtr answer = configure(config_set_, false);
+ ConstElementPtr comment;
+ comment = isc::config::parseAnswer(rcode, answer);
+
+ if (rcode) {
+ return (::testing::AssertionFailure(::testing::Message() <<
+ "configure() failed: "
+ << comment));
+ }
+
+ // Must call checkQueueStatus, to cause queue manager to reconfigure
+ // and start.
+ checkQueueStatus();
+ const D2QueueMgrPtr& queue_mgr = getD2QueueMgr();
+
+ // If queue manager isn't in the RUNNING state, return failure.
+ if (D2QueueMgr::RUNNING != queue_mgr->getMgrState()) {
+ return (::testing::AssertionFailure(::testing::Message() <<
+ "queue manager did not start"));
+ }
+
+ // Good to go.
+ return (::testing::AssertionSuccess());
+ }
+
+ /// @brief Checks if shutdown criteria would be met given a shutdown type.
+ ///
+ /// This method sets the D2Process shutdown type to the given value, and
+ /// calls the canShutdown() method, returning its return value.
+ ///
+ /// @return Returns the boolean result canShutdown.
+ bool checkCanShutdown(ShutdownType shutdown_type) {
+ setShutdownType(shutdown_type);
+ return (canShutdown());
+ }
+
+ /// @brief Replaces %LIBRARY% with specified library name.
+ ///
+ /// @param config input config text (should contain "%LIBRARY%" string).
+ /// @param lib_name %LIBRARY% will be replaced with that name.
+ /// @return configuration text with library name replaced.
+ string pathReplacer(const char* config, const char* lib_name) {
+ string txt(config);
+ txt.replace(txt.find("%LIBRARY%"), strlen("%LIBRARY%"), string(lib_name));
+ return (txt);
+ }
+};
+
+/// @brief Verifies D2Process construction behavior.
+/// 1. Verifies that constructor fails with an invalid IOService
+/// 2. Verifies that constructor succeeds with a valid IOService
+/// 3. Verifies that all managers are accessible
+TEST(D2Process, construction) {
+ // Verify that the constructor will fail if given an empty
+ // io service.
+ asiolink::IOServicePtr lcl_io_service;
+ EXPECT_THROW (D2Process("TestProcess", lcl_io_service), DProcessBaseError);
+
+ // Verify that the constructor succeeds with a valid io_service
+ lcl_io_service.reset(new isc::asiolink::IOService());
+ ASSERT_NO_THROW (D2Process("TestProcess", lcl_io_service));
+
+ // Verify that the configuration, queue, and update managers
+ // are all accessible after construction.
+ D2Process d2process("TestProcess", lcl_io_service);
+
+ D2CfgMgrPtr cfg_mgr = d2process.getD2CfgMgr();
+ ASSERT_TRUE(cfg_mgr);
+
+ D2QueueMgrPtr queue_mgr = d2process.getD2QueueMgr();
+ ASSERT_TRUE(queue_mgr);
+
+ const D2UpdateMgrPtr& update_mgr = d2process.getD2UpdateMgr();
+ ASSERT_TRUE(update_mgr);
+}
+
+/// @brief Verifies basic configure method behavior.
+/// This test primarily verifies that upon receipt of a new configuration,
+/// D2Process will reconfigure the queue manager if the configuration is valid,
+/// or leave queue manager unaffected if not. Currently, the queue manager is
+/// only D2 component that must adapt to new configurations. Other components,
+/// such as Transactions will be unaffected as they are transient and use
+/// whatever configuration was in play at the time they were created.
+/// If other components need to provide "dynamic" configuration responses,
+/// those tests would need to be added.
+TEST_F(D2ProcessTest, configure) {
+ // Verify the queue manager is not yet initialized.
+ D2QueueMgrPtr queue_mgr = getD2QueueMgr();
+ ASSERT_TRUE(queue_mgr);
+ ASSERT_EQ(D2QueueMgr::NOT_INITTED, queue_mgr->getMgrState());
+
+ // Verify that reconfigure queue manager flag is false.
+ ASSERT_FALSE(getReconfQueueFlag());
+
+ // Create a valid configuration set from text config.
+ ASSERT_TRUE(fromJSON(valid_d2_config));
+
+ // Invoke configure() with a valid D2 configuration.
+ ConstElementPtr answer = configure(config_set_, false);
+
+ // Verify that configure result is success and reconfigure queue manager
+ // flag is true.
+ ASSERT_TRUE(checkAnswer(answer, 0));
+ ASSERT_TRUE(getReconfQueueFlag());
+
+ // Call checkQueueStatus, to cause queue manager to reconfigure and start.
+ checkQueueStatus();
+
+ // Verify that queue manager is now in the RUNNING state, and flag is false.
+ ASSERT_EQ(D2QueueMgr::RUNNING, queue_mgr->getMgrState());
+ ASSERT_FALSE(getReconfQueueFlag());
+
+ // Create an invalid configuration set from text config.
+ ASSERT_TRUE(fromJSON("{ \"bogus\": 1000 } "));
+
+ // Invoke configure() with the invalid configuration.
+ answer = configure(config_set_, false);
+
+ // Verify that configure result is a success, as extra parameters are
+ // ignored. the reconfigure flag is false, and that the queue manager is
+ // still running.
+ ASSERT_TRUE(checkAnswer(answer, 0));
+ EXPECT_TRUE(getReconfQueueFlag());
+ EXPECT_EQ(D2QueueMgr::RUNNING, queue_mgr->getMgrState());
+
+ // Finally, try with an invalid configuration.
+ // Create an invalid configuration set from text config.
+ ASSERT_TRUE(fromJSON("{ \"ip-address\": \"950 Charter St.\" } "));
+ answer = configure(config_set_, false);
+ ASSERT_TRUE(checkAnswer(answer, 1));
+ EXPECT_FALSE(getReconfQueueFlag());
+ EXPECT_EQ(D2QueueMgr::RUNNING, queue_mgr->getMgrState());
+}
+
+/// @brief Tests checkQueueStatus() logic for stopping the queue on shutdown
+/// This test manually sets shutdown flag and verifies that queue manager
+/// stop is initiated.
+TEST_F(D2ProcessTest, queueStopOnShutdown) {
+ ASSERT_TRUE(runWithConfig(valid_d2_config));
+ const D2QueueMgrPtr& queue_mgr = getD2QueueMgr();
+
+ setShutdownFlag(true);
+
+ // Calling checkQueueStatus restart queue manager
+ checkQueueStatus();
+
+ // Verify that the queue manager is stopping.
+ EXPECT_EQ(D2QueueMgr::STOPPING, queue_mgr->getMgrState());
+
+ // Verify that a subsequent call with no events occurring in between,
+ // results in no change to queue manager
+ checkQueueStatus();
+
+ // Verify that the queue manager is still stopping.
+ EXPECT_EQ(D2QueueMgr::STOPPING, queue_mgr->getMgrState());
+
+ // Call runIO so the IO cancel event occurs and verify that queue manager
+ // has stopped.
+ runIO();
+ ASSERT_EQ(D2QueueMgr::STOPPED, queue_mgr->getMgrState());
+}
+
+/// @brief Tests checkQueueStatus() logic for stopping the queue on reconfigure.
+/// This test manually sets queue reconfiguration flag and verifies that queue
+/// manager stop is initiated.
+TEST_F(D2ProcessTest, queueStopOnReconf) {
+ ASSERT_TRUE(runWithConfig(valid_d2_config));
+ const D2QueueMgrPtr& queue_mgr = getD2QueueMgr();
+
+ // Manually set the reconfigure indicator.
+ setReconfQueueFlag(true);
+
+ // Calling checkQueueStatus should initiate stopping the queue manager.
+ checkQueueStatus();
+
+ // Verify that the queue manager is stopping.
+ EXPECT_EQ(D2QueueMgr::STOPPING, queue_mgr->getMgrState());
+
+ // Call runIO so the IO cancel event occurs and verify that queue manager
+ // has stopped.
+ runIO();
+ ASSERT_EQ(D2QueueMgr::STOPPED, queue_mgr->getMgrState());
+}
+
+/// @brief Tests checkQueueStatus() logic for recovering from queue full
+/// This test manually creates a receive queue full condition and then
+/// "drains" the queue until the queue manager resumes listening. This
+/// verifies D2Process's ability to recover from a queue full condition.
+TEST_F(D2ProcessTest, queueFullRecovery) {
+ // Valid test message, contents are unimportant.
+ const char* test_msg =
+ "{"
+ " \"change-type\" : 0 , "
+ " \"forward-change\" : true , "
+ " \"reverse-change\" : false , "
+ " \"fqdn\" : \"walah.walah.com\" , "
+ " \"ip-address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"010203040A7F8E3D\" , "
+ " \"lease-expires-on\" : \"20130121132405\" , "
+ " \"lease-length\" : 1300, "
+ " \"use-conflict-resolution\" : true "
+ "}";
+
+ // Start queue manager with known good config.
+ ASSERT_TRUE(runWithConfig(valid_d2_config));
+ const D2QueueMgrPtr& queue_mgr = getD2QueueMgr();
+
+ // Set the maximum queue size to manageable number.
+ size_t max_queue_size = 5;
+ queue_mgr->setMaxQueueSize(max_queue_size);
+
+ // Manually enqueue max requests.
+ dhcp_ddns::NameChangeRequestPtr ncr;
+ ASSERT_NO_THROW(ncr = dhcp_ddns::NameChangeRequest::fromJSON(test_msg));
+ for (int i = 0; i < max_queue_size; i++) {
+ // Verify that the request can be added to the queue and queue
+ // size increments accordingly.
+ ASSERT_NO_THROW(queue_mgr->enqueue(ncr));
+ ASSERT_EQ(i+1, queue_mgr->getQueueSize());
+ }
+
+ // Since we are not really receiving, we will simulate QUEUE FULL
+ // detection.
+ queue_mgr->stopListening(D2QueueMgr::STOPPED_QUEUE_FULL);
+ ASSERT_EQ(D2QueueMgr::STOPPING, queue_mgr->getMgrState());
+
+ // Call runIO so the IO cancel event occurs and verify that queue manager
+ // has stopped.
+ runIO();
+ ASSERT_EQ(D2QueueMgr::STOPPED_QUEUE_FULL, queue_mgr->getMgrState());
+
+ // Dequeue requests one at a time, calling checkQueueStatus after each
+ // dequeue, until we reach the resume threshold. This simulates update
+ // manager consuming jobs. Queue manager should remain stopped during
+ // this loop.
+ int resume_threshold = (max_queue_size * QUEUE_RESTART_PERCENT);
+ while (queue_mgr->getQueueSize() > resume_threshold) {
+ checkQueueStatus();
+ ASSERT_EQ(D2QueueMgr::STOPPED_QUEUE_FULL, queue_mgr->getMgrState());
+ ASSERT_NO_THROW(queue_mgr->dequeue());
+ }
+
+ // Dequeue one more, which brings us under the threshold and call
+ // checkQueueStatus.
+ // Verify that the queue manager is again running.
+ ASSERT_NO_THROW(queue_mgr->dequeue());
+ checkQueueStatus();
+ EXPECT_EQ(D2QueueMgr::RUNNING, queue_mgr->getMgrState());
+}
+
+/// @brief Tests checkQueueStatus() logic for queue receive error recovery
+/// This test manually creates a queue receive error condition and tests
+/// verifies that checkQueueStatus reacts properly to recover.
+TEST_F(D2ProcessTest, queueErrorRecovery) {
+ ASSERT_TRUE(runWithConfig(valid_d2_config));
+ const D2QueueMgrPtr& queue_mgr = getD2QueueMgr();
+
+ // Since we are not really receiving, we have to stage an error.
+ queue_mgr->stopListening(D2QueueMgr::STOPPED_RECV_ERROR);
+ ASSERT_EQ(D2QueueMgr::STOPPING, queue_mgr->getMgrState());
+
+ // Call runIO so the IO cancel event occurs and verify that queue manager
+ // has stopped.
+ runIO();
+ ASSERT_EQ(D2QueueMgr::STOPPED_RECV_ERROR, queue_mgr->getMgrState());
+
+ // Calling checkQueueStatus should restart queue manager
+ checkQueueStatus();
+
+ // Verify that queue manager is again running.
+ EXPECT_EQ(D2QueueMgr::RUNNING, queue_mgr->getMgrState());
+}
+
+/// @brief Verifies queue manager recovery from unusable configuration
+/// This test checks D2Process's gracefully handle a configuration which
+/// while valid is not operationally usable (i.e. IP address is unavailable),
+/// and to subsequently recover given a usable configuration.
+TEST_F(D2ProcessTest, badConfigureRecovery) {
+ D2QueueMgrPtr queue_mgr = getD2QueueMgr();
+ ASSERT_TRUE(queue_mgr);
+
+ // Verify the queue manager is not initialized.
+ EXPECT_EQ(D2QueueMgr::NOT_INITTED, queue_mgr->getMgrState());
+
+ // Invoke configure() with a valid config that contains an unusable IP
+ ASSERT_TRUE(fromJSON(bad_ip_d2_config));
+ ConstElementPtr answer = configure(config_set_, false);
+
+ // Verify that configure result is success and reconfigure queue manager
+ // flag is true.
+ ASSERT_TRUE(checkAnswer(answer, 0));
+ ASSERT_TRUE(getReconfQueueFlag());
+
+ // Call checkQueueStatus to cause queue manager to attempt to reconfigure.
+ checkQueueStatus();
+
+ // Verify that queue manager failed to start, (i.e. is in INITTED state),
+ // and the reconfigure flag is false.
+ ASSERT_EQ(D2QueueMgr::INITTED, queue_mgr->getMgrState());
+ ASSERT_FALSE(getReconfQueueFlag());
+
+ // Verify we can recover given a valid config with an usable IP address.
+ ASSERT_TRUE(fromJSON(valid_d2_config));
+ answer = configure(config_set_, false);
+
+ // Verify that configure result is success and reconfigure queue manager
+ // flag is true.
+ ASSERT_TRUE(checkAnswer(answer, 0));
+ ASSERT_TRUE(getReconfQueueFlag());
+
+ // Call checkQueueStatus to cause queue manager to reconfigure and start.
+ checkQueueStatus();
+
+ // Verify that queue manager is now in the RUNNING state, and reconfigure
+ // flag is false.
+ EXPECT_EQ(D2QueueMgr::RUNNING, queue_mgr->getMgrState());
+ EXPECT_FALSE(getReconfQueueFlag());
+}
+
+/// @brief Tests shutdown command argument parsing
+/// The shutdown command supports an optional "type" argument. This test
+/// checks that for valid values, the shutdown() method: sets the shutdown
+/// type to correct value, set the shutdown flag to true, and returns a
+/// success response; and for invalid values: sets the shutdown flag to false
+/// and returns a failure response.
+TEST_F(D2ProcessTest, shutdownArgs) {
+ ElementPtr args;
+ ConstElementPtr answer;
+ const char* default_args = "{}";
+ const char* normal_args = "{ \"type\" : \"normal\" }";
+ const char* drain_args = "{ \"type\" : \"drain_first\" }";
+ const char* now_args = "{ \"type\" : \"now\" }";
+ const char* bogus_args = "{ \"type\" : \"bogus\" }";
+
+ // Verify defaulting to SD_NORMAL if no argument is given.
+ ASSERT_NO_THROW(args = Element::fromJSON(default_args));
+ EXPECT_NO_THROW(answer = shutdown(args));
+ ASSERT_TRUE(checkAnswer(answer, 0));
+ EXPECT_EQ(SD_NORMAL, getShutdownType());
+ EXPECT_TRUE(shouldShutdown());
+
+ // Verify argument value "normal".
+ ASSERT_NO_THROW(args = Element::fromJSON(normal_args));
+ EXPECT_NO_THROW(answer = shutdown(args));
+ ASSERT_TRUE(checkAnswer(answer, 0));
+ EXPECT_EQ(SD_NORMAL, getShutdownType());
+ EXPECT_TRUE(shouldShutdown());
+
+ // Verify argument value "drain_first".
+ ASSERT_NO_THROW(args = Element::fromJSON(drain_args));
+ EXPECT_NO_THROW(answer = shutdown(args));
+ ASSERT_TRUE(checkAnswer(answer, 0));
+ EXPECT_EQ(SD_DRAIN_FIRST, getShutdownType());
+ EXPECT_TRUE(shouldShutdown());
+
+ // Verify argument value "now".
+ ASSERT_NO_THROW(args = Element::fromJSON(now_args));
+ EXPECT_NO_THROW(answer = shutdown(args));
+ ASSERT_TRUE(checkAnswer(answer, 0));
+ EXPECT_EQ(SD_NOW, getShutdownType());
+ EXPECT_TRUE(shouldShutdown());
+
+ // Verify correct handling of an invalid value.
+ ASSERT_NO_THROW(args = Element::fromJSON(bogus_args));
+ EXPECT_NO_THROW(answer = shutdown(args));
+ ASSERT_TRUE(checkAnswer(answer, 1));
+ EXPECT_FALSE(shouldShutdown());
+}
+
+/// @brief Tests shutdown criteria logic
+/// D2Process using the method canShutdown() to determine if a shutdown
+/// can be performed given the value of the shutdown flag and the type of
+/// shutdown requested. For each shutdown type certain criteria must be met
+/// before the shutdown is permitted. This method is invoked once each pass
+/// through the main event loop. This test checks the operation of the
+/// canShutdown method. It uses a convenience method, checkCanShutdown(),
+/// which sets the shutdown type to the given value and invokes canShutdown(),
+/// returning its result.
+TEST_F(D2ProcessTest, canShutdown) {
+ ASSERT_TRUE(runWithConfig(valid_d2_config));
+ const D2QueueMgrPtr& queue_mgr = getD2QueueMgr();
+
+ // Shutdown flag is false. Method should return false for all types.
+ EXPECT_FALSE(checkCanShutdown(SD_NORMAL));
+ EXPECT_FALSE(checkCanShutdown(SD_DRAIN_FIRST));
+ EXPECT_FALSE(checkCanShutdown(SD_NOW));
+
+ // Set shutdown flag to true.
+ setShutdownFlag(true);
+
+ // Queue Manager is running, queue is empty, no transactions.
+ // Only SD_NOW should return true.
+ EXPECT_FALSE(checkCanShutdown(SD_NORMAL));
+ EXPECT_FALSE(checkCanShutdown(SD_DRAIN_FIRST));
+ EXPECT_TRUE(checkCanShutdown(SD_NOW));
+
+ // Tell queue manager to stop.
+ queue_mgr->stopListening();
+ // Verify that the queue manager is stopping.
+ EXPECT_EQ(D2QueueMgr::STOPPING, queue_mgr->getMgrState());
+
+ // Queue Manager is stopping, queue is empty, no transactions.
+ // Only SD_NOW should return true.
+ EXPECT_FALSE(checkCanShutdown(SD_NORMAL));
+ EXPECT_FALSE(checkCanShutdown(SD_DRAIN_FIRST));
+ EXPECT_TRUE(checkCanShutdown(SD_NOW));
+
+ // Allow cancel event to process.
+ ASSERT_NO_THROW(runIO());
+ // Verify that queue manager is stopped.
+ EXPECT_EQ(D2QueueMgr::STOPPED, queue_mgr->getMgrState());
+
+ // Queue Manager is stopped, queue is empty, no transactions.
+ // All types should return true.
+ EXPECT_TRUE(checkCanShutdown(SD_NORMAL));
+ EXPECT_TRUE(checkCanShutdown(SD_DRAIN_FIRST));
+ EXPECT_TRUE(checkCanShutdown(SD_NOW));
+
+ const char* test_msg =
+ "{"
+ " \"change-type\" : 0 , "
+ " \"forward-change\" : true , "
+ " \"reverse-change\" : false , "
+ " \"fqdn\" : \"fish.example.com\" , "
+ " \"ip-address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"010203040A7F8E3D\" , "
+ " \"lease-expires-on\" : \"20130121132405\" , "
+ " \"lease-length\" : 1300, "
+ " \"use-conflict-resolution\" : true "
+ "}";
+
+ // Manually enqueue a request. This lets us test logic with queue
+ // not empty.
+ dhcp_ddns::NameChangeRequestPtr ncr;
+ ASSERT_NO_THROW(ncr = dhcp_ddns::NameChangeRequest::fromJSON(test_msg));
+ ASSERT_NO_THROW(queue_mgr->enqueue(ncr));
+ ASSERT_EQ(1, queue_mgr->getQueueSize());
+
+ // Queue Manager is stopped. Queue is not empty, no transactions.
+ // SD_DRAIN_FIRST should be false, SD_NORMAL and SD_NOW should be true.
+ EXPECT_TRUE(checkCanShutdown(SD_NORMAL));
+ EXPECT_FALSE(checkCanShutdown(SD_DRAIN_FIRST));
+ EXPECT_TRUE(checkCanShutdown(SD_NOW));
+
+ // Now use update manager to dequeue the request and make a transaction.
+ // This lets us verify transaction list not empty logic.
+ const D2UpdateMgrPtr& update_mgr = getD2UpdateMgr();
+ ASSERT_TRUE(update_mgr);
+ ASSERT_NO_THROW(update_mgr->sweep());
+ ASSERT_EQ(0, queue_mgr->getQueueSize());
+ ASSERT_EQ(1, update_mgr->getTransactionCount());
+
+ // Queue Manager is stopped. Queue is empty, one transaction.
+ // Only SD_NOW should be true.
+ EXPECT_FALSE(checkCanShutdown(SD_NORMAL));
+ EXPECT_FALSE(checkCanShutdown(SD_DRAIN_FIRST));
+ EXPECT_TRUE(checkCanShutdown(SD_NOW));
+}
+
+/// @brief Verifies that an "external" call to shutdown causes the run method
+/// to exit gracefully.
+TEST_F(D2ProcessTest, normalShutdown) {
+ // Use an asiolink IntervalTimer and callback to generate the
+ // shutdown invocation. (Note IntervalTimer setup is in milliseconds).
+ isc::asiolink::IntervalTimer timer(*getIoService());
+ timer.setup(std::bind(&D2ProcessTest::genShutdownCallback, this),
+ 2 * 1000);
+
+ // Record start time, and invoke run().
+ ptime start = microsec_clock::universal_time();
+ EXPECT_NO_THROW(run());
+
+ // Record stop time.
+ ptime stop = microsec_clock::universal_time();
+
+ // Verify that duration of the run invocation is the same as the
+ // timer duration. This demonstrates that the shutdown was driven
+ // by an io_service event and callback.
+ time_duration elapsed = stop - start;
+ EXPECT_TRUE(elapsed.total_milliseconds() >= 1900 &&
+ elapsed.total_milliseconds() <= 2200);
+}
+
+/// @brief Verifies that an "uncaught" exception thrown during event loop
+/// execution is treated as a fatal error.
+TEST_F(D2ProcessTest, fatalErrorShutdown) {
+ // Use an asiolink IntervalTimer and callback to generate the
+ // the exception. (Note IntervalTimer setup is in milliseconds).
+ isc::asiolink::IntervalTimer timer(*getIoService());
+ timer.setup(std::bind(&D2ProcessTest::genFatalErrorCallback, this),
+ 2 * 1000);
+
+ // Record start time, and invoke run().
+ ptime start = microsec_clock::universal_time();
+ EXPECT_THROW(run(), DProcessBaseError);
+
+ // Record stop time.
+ ptime stop = microsec_clock::universal_time();
+
+ // Verify that duration of the run invocation is the same as the
+ // timer duration. This demonstrates that the anomaly occurred
+ // during io callback processing.
+ time_duration elapsed = stop - start;
+ EXPECT_TRUE(elapsed.total_milliseconds() >= 1900 &&
+ elapsed.total_milliseconds() <= 2200);
+}
+
+/// @brief Used to permit visual inspection of logs to ensure
+/// DHCP_DDNS_NOT_ON_LOOPBACK is issued when ip_address is not
+/// loopback.
+TEST_F(D2ProcessTest, notLoopbackTest) {
+ const char* config = "{ "
+ "\"ip-address\" : \"0.0.0.0\" , "
+ "\"port\" : 53001, "
+ "\"tsig-keys\": [],"
+ "\"forward-ddns\" : {},"
+ "\"reverse-ddns\" : {}"
+ "}";
+
+ // Note we don't care nor can we predict if this
+ // succeeds or fails. The address and port may or may
+ // not be valid on the test host.
+ runWithConfig(config);
+}
+
+/// @brief Used to permit visual inspection of logs to ensure
+/// DHCP_DDNS_NOT_ON_LOOPBACK is not issued.
+TEST_F(D2ProcessTest, v4LoopbackTest) {
+ const char* config = "{ "
+ "\"ip-address\" : \"127.0.0.1\" , "
+ "\"port\" : 53001, "
+ "\"tsig-keys\": [],"
+ "\"forward-ddns\" : {},"
+ "\"reverse-ddns\" : {}"
+ "}";
+ ASSERT_TRUE(runWithConfig(config));
+}
+
+/// @brief Used to permit visual inspection of logs to ensure
+/// DHCP_DDNS_NOT_ON_LOOPBACK is not issued.
+TEST_F(D2ProcessTest, v6LoopbackTest) {
+ const char* config = "{ "
+ "\"ip-address\" : \"::1\" , "
+ "\"port\" : 53001, "
+ "\"tsig-keys\": [],"
+ "\"forward-ddns\" : {},"
+ "\"reverse-ddns\" : {}"
+ "}";
+ ASSERT_TRUE(runWithConfig(config));
+}
+
+/// @brief Check the configured callout (positive case).
+TEST_F(D2ProcessTest, configuredNoFail) {
+ const char* config = "{\n"
+ "\"hooks-libraries\": [ {\n"
+ " \"library\": \"%LIBRARY%\",\n"
+ " \"parameters\": {\n"
+ " } } ] }\n";
+ string cfg = pathReplacer(config, CONFIGURED_LIBRARY);
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = Element::fromJSON(cfg));
+ ConstElementPtr answer;
+ ASSERT_NO_THROW(answer = configure(json, false));
+ int rcode = -1;
+ ConstElementPtr comment;
+ comment = isc::config::parseAnswer(rcode, answer);
+ EXPECT_EQ(0, rcode) << comment->str();
+}
+
+/// @brief Check the configured callout (negative case).
+TEST_F(D2ProcessTest, configuredFail) {
+ const char* config = "{\n"
+ "\"user-context\": { \"error\": \"Fail!\" },\n"
+ "\"hooks-libraries\": [ {\n"
+ " \"library\": \"%LIBRARY%\",\n"
+ " \"parameters\": {\n"
+ " } } ] }\n";
+ string cfg = pathReplacer(config, CONFIGURED_LIBRARY);
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = Element::fromJSON(cfg));
+ ConstElementPtr answer;
+ ASSERT_NO_THROW(answer = configure(json, false));
+ int rcode = -1;
+ ConstElementPtr comment;
+ comment = isc::config::parseAnswer(rcode, answer);
+ EXPECT_EQ(1, rcode);
+ ASSERT_TRUE(comment);
+ ASSERT_EQ(Element::string, comment->getType());
+ EXPECT_EQ("Fail!", comment->stringValue());
+}
+
+} // end of anonymous namespace
diff --git a/src/bin/d2/tests/d2_queue_mgr_unittests.cc b/src/bin/d2/tests/d2_queue_mgr_unittests.cc
new file mode 100644
index 0000000..42177e6
--- /dev/null
+++ b/src/bin/d2/tests/d2_queue_mgr_unittests.cc
@@ -0,0 +1,457 @@
+// Copyright (C) 2013-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <asiolink/io_service.h>
+#include <asiolink/interval_timer.h>
+#include <d2/d2_queue_mgr.h>
+#include <d2srv/testutils/stats_test_utils.h>
+#include <dhcp_ddns/ncr_udp.h>
+#include <util/time_utilities.h>
+
+#include <gtest/gtest.h>
+#include <algorithm>
+#include <functional>
+#include <vector>
+
+using namespace std;
+using namespace isc;
+using namespace isc::dhcp_ddns;
+using namespace isc::d2;
+using namespace isc::d2::test;
+
+namespace {
+
+/// @brief Defines a list of valid JSON NameChangeRequest test messages.
+const char *valid_msgs[] =
+{
+ // Valid Add.
+ "{"
+ " \"change-type\" : 0 , "
+ " \"forward-change\" : true , "
+ " \"reverse-change\" : false , "
+ " \"fqdn\" : \"walah.walah.com\" , "
+ " \"ip-address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"010203040A7F8E3D\" , "
+ " \"lease-expires-on\" : \"20130121132405\" , "
+ " \"lease-length\" : 1300, "
+ " \"use-conflict-resolution\" : true "
+ "}",
+ // Valid Remove.
+ "{"
+ " \"change-type\" : 1 , "
+ " \"forward-change\" : true , "
+ " \"reverse-change\" : false , "
+ " \"fqdn\" : \"walah.walah.com\" , "
+ " \"ip-address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"010203040A7F8E3D\" , "
+ " \"lease-expires-on\" : \"20130121132405\" , "
+ " \"lease-length\" : 1300, "
+ " \"use-conflict-resolution\" : true "
+ "}",
+ // Valid Add with IPv6 address
+ "{"
+ " \"change-type\" : 0 , "
+ " \"forward-change\" : true , "
+ " \"reverse-change\" : false , "
+ " \"fqdn\" : \"walah.walah.com\" , "
+ " \"ip-address\" : \"fe80::2acf:e9ff:fe12:e56f\" , "
+ " \"dhcid\" : \"010203040A7F8E3D\" , "
+ " \"lease-expires-on\" : \"20130121132405\" , "
+ " \"lease-length\" : 1300, "
+ " \"use-conflict-resolution\" : true "
+ "}"
+};
+
+static const int VALID_MSG_CNT = sizeof(valid_msgs)/sizeof(char*);
+
+const char* TEST_ADDRESS = "127.0.0.1";
+const uint32_t LISTENER_PORT = 5301;
+const uint32_t SENDER_PORT = LISTENER_PORT+1;
+const long TEST_TIMEOUT = 5 * 1000;
+
+/// @brief Tests that construction with max queue size of zero is not allowed.
+TEST(D2QueueMgrBasicTest, construction1) {
+ asiolink::IOServicePtr io_service;
+
+ // Verify that constructing with null IOServicePtr is not allowed.
+ EXPECT_THROW((D2QueueMgr(io_service)), D2QueueMgrError);
+
+ io_service.reset(new isc::asiolink::IOService());
+ // Verify that constructing with max queue size of zero is not allowed.
+ EXPECT_THROW(D2QueueMgr(io_service, 0), D2QueueMgrError);
+}
+
+/// @brief Tests default construction works.
+TEST(D2QueueMgrBasicTest, construction2) {
+ asiolink::IOServicePtr io_service(new isc::asiolink::IOService());
+
+ // Verify that valid constructor works.
+ D2QueueMgrPtr queue_mgr;
+ ASSERT_NO_THROW(queue_mgr.reset(new D2QueueMgr(io_service)));
+ // Verify queue max is defaulted correctly.
+ EXPECT_EQ(D2QueueMgr::MAX_QUEUE_DEFAULT, queue_mgr->getMaxQueueSize());
+}
+
+/// @brief Tests construction with custom queue size works properly
+TEST(D2QueueMgrBasicTest, construction3) {
+ asiolink::IOServicePtr io_service(new isc::asiolink::IOService());
+
+ // Verify that custom queue size constructor works.
+ D2QueueMgrPtr queue_mgr;
+ ASSERT_NO_THROW(queue_mgr.reset(new D2QueueMgr(io_service, 100)));
+ // Verify queue max is the custom value.
+ EXPECT_EQ(100, queue_mgr->getMaxQueueSize());
+}
+
+/// @brief Tests QueueMgr's basic queue functions
+/// This test verifies that:
+/// 1. Following construction queue is empty
+/// 2. Attempting to peek at an empty queue is not allowed
+/// 3. Attempting to dequeue an empty queue is not allowed
+/// 4. Peek returns the first entry on the queue without altering queue content
+/// 5. Dequeue removes the first entry on the queue
+TEST(D2QueueMgrBasicTest, basicQueue) {
+ asiolink::IOServicePtr io_service(new isc::asiolink::IOService());
+
+ // Construct the manager with max queue size set to number of messages
+ // we'll use.
+ D2QueueMgrPtr queue_mgr;
+ ASSERT_NO_THROW(queue_mgr.reset(new D2QueueMgr(io_service, VALID_MSG_CNT)));
+ ASSERT_EQ(VALID_MSG_CNT, queue_mgr->getMaxQueueSize());
+
+ // Verify queue is empty after construction.
+ EXPECT_EQ(0, queue_mgr->getQueueSize());
+
+ // Verify that peek and dequeue both throw when queue is empty.
+ EXPECT_THROW(queue_mgr->peek(), D2QueueMgrQueueEmpty);
+ EXPECT_THROW(queue_mgr->dequeue(), D2QueueMgrQueueEmpty);
+
+ // Vector to keep track of the NCRs we que.
+ std::vector<NameChangeRequestPtr>ref_msgs;
+ NameChangeRequestPtr ncr;
+
+ // Iterate over the list of requests and add each to the queue.
+ for (int i = 0; i < VALID_MSG_CNT; i++) {
+ // Create the ncr and add to our reference list.
+ ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(valid_msgs[i]));
+ ref_msgs.push_back(ncr);
+
+ // Verify that the request can be added to the queue and queue
+ // size increments accordingly.
+ EXPECT_NO_THROW(queue_mgr->enqueue(ncr));
+ EXPECT_EQ(i+1, queue_mgr->getQueueSize());
+ }
+
+ // Loop through and verify that the queue contents match the
+ // reference list.
+ for (int i = 0; i < VALID_MSG_CNT; i++) {
+ // Verify that peek on a non-empty queue returns first entry
+ // without altering queue content.
+ EXPECT_NO_THROW(ncr = queue_mgr->peek());
+
+ // Verify the peeked entry is the one it should be.
+ ASSERT_TRUE(ncr);
+ EXPECT_TRUE (*(ref_msgs[i]) == *ncr);
+
+ // Verify that peek did not alter the queue size.
+ EXPECT_EQ(VALID_MSG_CNT - i, queue_mgr->getQueueSize());
+
+ // Verify the dequeuing from non-empty queue works
+ EXPECT_NO_THROW(queue_mgr->dequeue());
+
+ // Verify queue size decrements following dequeue.
+ EXPECT_EQ(VALID_MSG_CNT - (i + 1), queue_mgr->getQueueSize());
+ }
+
+ // Iterate over the list of requests and add each to the queue.
+ for (int i = 0; i < VALID_MSG_CNT; i++) {
+ // Create the ncr and add to our reference list.
+ ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(valid_msgs[i]));
+ ref_msgs.push_back(ncr);
+ EXPECT_NO_THROW(queue_mgr->enqueue(ncr));
+ }
+
+ // Verify queue count is correct.
+ EXPECT_EQ(VALID_MSG_CNT, queue_mgr->getQueueSize());
+
+ // Verify that peekAt returns the correct entry.
+ EXPECT_NO_THROW(ncr = queue_mgr->peekAt(1));
+ EXPECT_TRUE (*(ref_msgs[1]) == *ncr);
+
+ // Verify that dequeueAt removes the correct entry.
+ // Removing it, this should shift the queued entries forward by one.
+ EXPECT_NO_THROW(queue_mgr->dequeueAt(1));
+ EXPECT_NO_THROW(ncr = queue_mgr->peekAt(1));
+ EXPECT_TRUE (*(ref_msgs[2]) == *ncr);
+
+ // Verify the peekAt and dequeueAt throw when given indexes beyond the end.
+ EXPECT_THROW(queue_mgr->peekAt(VALID_MSG_CNT + 1), D2QueueMgrInvalidIndex);
+ EXPECT_THROW(queue_mgr->dequeueAt(VALID_MSG_CNT + 1),
+ D2QueueMgrInvalidIndex);
+}
+
+/// @brief Compares two NameChangeRequests for equality.
+bool checkSendVsReceived(NameChangeRequestPtr sent_ncr,
+ NameChangeRequestPtr received_ncr) {
+ return ((sent_ncr && received_ncr) &&
+ (*sent_ncr == *received_ncr));
+}
+
+/// @brief Text fixture that allows testing a listener and sender together
+/// It derives from both the receive and send handler classes and contains
+/// and instance of UDP listener and UDP sender.
+class QueueMgrUDPTest : public virtual ::testing::Test, public D2StatTest,
+ NameChangeSender::RequestSendHandler {
+public:
+ asiolink::IOServicePtr io_service_;
+ NameChangeSenderPtr sender_;
+ isc::asiolink::IntervalTimer test_timer_;
+ D2QueueMgrPtr queue_mgr_;
+
+ NameChangeSender::Result send_result_;
+ std::vector<NameChangeRequestPtr> sent_ncrs_;
+ std::vector<NameChangeRequestPtr> received_ncrs_;
+
+ QueueMgrUDPTest() : io_service_(new isc::asiolink::IOService()),
+ test_timer_(*io_service_),
+ send_result_(NameChangeSender::SUCCESS) {
+ isc::asiolink::IOAddress addr(TEST_ADDRESS);
+ // Create our sender instance. Note that reuse_address is true.
+ sender_.reset(new NameChangeUDPSender(addr, SENDER_PORT,
+ addr, LISTENER_PORT,
+ FMT_JSON, *this, 100, true));
+
+ // Set the test timeout to break any running tasks if they hang.
+ test_timer_.setup(std::bind(&QueueMgrUDPTest::testTimeoutHandler,
+ this),
+ TEST_TIMEOUT);
+ }
+
+ void reset_results() {
+ sent_ncrs_.clear();
+ received_ncrs_.clear();
+ }
+
+ /// @brief Implements the send completion handler.
+ virtual void operator ()(const NameChangeSender::Result result,
+ NameChangeRequestPtr& ncr) {
+ // save the result and the NCR sent.
+ send_result_ = result;
+ sent_ncrs_.push_back(ncr);
+ }
+
+ /// @brief Handler invoked when test timeout is hit.
+ ///
+ /// This callback stops all running (hanging) tasks on IO service.
+ void testTimeoutHandler() {
+ io_service_->stop();
+ FAIL() << "Test timeout hit.";
+ }
+};
+
+/// @brief Tests D2QueueMgr's state model.
+/// This test verifies that:
+/// 1. Upon construction, initial state is NOT_INITTED.
+/// 2. Cannot start listening from while state is NOT_INITTED.
+/// 3. Successful listener initialization transitions from NOT_INITTED
+/// to INITTED.
+/// 4. Attempting to initialize the listener from INITTED state is not
+/// allowed.
+/// 5. Starting listener from INITTED transitions to RUNNING.
+/// 6. Stopping the listener transitions from RUNNING to STOPPED.
+/// 7. Starting listener from STOPPED transitions to RUNNING.
+TEST_F (QueueMgrUDPTest, stateModel) {
+ // Create the queue manager.
+ ASSERT_NO_THROW(queue_mgr_.reset(new D2QueueMgr(io_service_,
+ VALID_MSG_CNT)));
+
+ // Verify that the initial state is NOT_INITTED.
+ EXPECT_EQ(D2QueueMgr::NOT_INITTED, queue_mgr_->getMgrState());
+
+ // Verify that trying to listen before when not initialized fails.
+ EXPECT_THROW(queue_mgr_->startListening(), D2QueueMgrError);
+
+ // Verify that initializing the listener moves us to INITTED state.
+ isc::asiolink::IOAddress addr(TEST_ADDRESS);
+ EXPECT_NO_THROW(queue_mgr_->initUDPListener(addr, LISTENER_PORT,
+ FMT_JSON, true));
+ EXPECT_EQ(D2QueueMgr::INITTED, queue_mgr_->getMgrState());
+
+ // Verify that attempting to initialize the listener, from INITTED
+ // is not allowed.
+ EXPECT_THROW(queue_mgr_->initUDPListener(addr, LISTENER_PORT,
+ FMT_JSON, true),
+ D2QueueMgrError);
+
+ // Verify that we can enter the RUNNING from INITTED by starting the
+ // listener.
+ EXPECT_NO_THROW(queue_mgr_->startListening());
+ EXPECT_EQ(D2QueueMgr::RUNNING, queue_mgr_->getMgrState());
+
+ // Verify that we can move from RUNNING to STOPPING by stopping the
+ // listener.
+ EXPECT_NO_THROW(queue_mgr_->stopListening());
+ EXPECT_EQ(D2QueueMgr::STOPPING, queue_mgr_->getMgrState());
+
+ // Stopping requires IO cancel, which result in a callback.
+ // So process one event and verify we are STOPPED.
+ io_service_->run_one();
+ EXPECT_EQ(D2QueueMgr::STOPPED, queue_mgr_->getMgrState());
+
+ // Verify that we can re-enter the RUNNING from STOPPED by starting the
+ // listener.
+ EXPECT_NO_THROW(queue_mgr_->startListening());
+ EXPECT_EQ(D2QueueMgr::RUNNING, queue_mgr_->getMgrState());
+
+ // Verify that we cannot remove the listener in the RUNNING state
+ EXPECT_THROW(queue_mgr_->removeListener(), D2QueueMgrError);
+
+ // Stop the listener.
+ EXPECT_NO_THROW(queue_mgr_->stopListening());
+ EXPECT_EQ(D2QueueMgr::STOPPING, queue_mgr_->getMgrState());
+
+ // Stopping requires IO cancel, which result in a callback.
+ // So process one event and verify we are STOPPED.
+ io_service_->run_one();
+ EXPECT_EQ(D2QueueMgr::STOPPED, queue_mgr_->getMgrState());
+
+ // Verify that we can remove the listener in the STOPPED state and
+ // end up back in NOT_INITTED.
+ EXPECT_NO_THROW(queue_mgr_->removeListener());
+ EXPECT_EQ(D2QueueMgr::NOT_INITTED, queue_mgr_->getMgrState());
+}
+
+/// @brief Tests D2QueueMgr's ability to manage received requests
+/// This test verifies that:
+/// 1. Requests can be received, queued, and dequeued
+/// 2. Once the queue is full, a subsequent request transitions
+/// manager to STOPPED_QUEUE_FULL state.
+/// 3. Starting listener returns manager to the RUNNING state.
+/// 4. Queue contents are preserved across state transitions.
+/// 5. Clearing the queue via the clearQueue() method works.
+/// 6. Requests can be received and queued normally after the queue
+/// has been emptied.
+/// 7. setQueueMax disallows values of 0 or less than current queue size.
+TEST_F (QueueMgrUDPTest, liveFeed) {
+ NameChangeRequestPtr send_ncr;
+ NameChangeRequestPtr received_ncr;
+
+ // Create the queue manager and start listening..
+ ASSERT_NO_THROW(queue_mgr_.reset(new D2QueueMgr(io_service_,
+ VALID_MSG_CNT)));
+ ASSERT_EQ(D2QueueMgr::NOT_INITTED, queue_mgr_->getMgrState());
+
+ // Verify that setting max queue size to 0 is not allowed.
+ EXPECT_THROW(queue_mgr_->setMaxQueueSize(0), D2QueueMgrError);
+ EXPECT_EQ(VALID_MSG_CNT, queue_mgr_->getMaxQueueSize());
+
+ isc::asiolink::IOAddress addr(TEST_ADDRESS);
+ ASSERT_NO_THROW(queue_mgr_->initUDPListener(addr, LISTENER_PORT,
+ FMT_JSON, true));
+ ASSERT_EQ(D2QueueMgr::INITTED, queue_mgr_->getMgrState());
+
+ ASSERT_NO_THROW(queue_mgr_->startListening());
+ ASSERT_EQ(D2QueueMgr::RUNNING, queue_mgr_->getMgrState());
+
+ // Place the sender into sending state.
+ ASSERT_NO_THROW(sender_->startSending(*io_service_));
+ ASSERT_TRUE(sender_->amSending());
+
+ // Iterate over the list of requests sending and receiving
+ // each one. Verify and dequeue as they arrive.
+ for (int i = 0; i < VALID_MSG_CNT; i++) {
+ // Create the ncr and add to our reference list.
+ ASSERT_NO_THROW(send_ncr = NameChangeRequest::fromJSON(valid_msgs[i]));
+ ASSERT_NO_THROW(sender_->sendRequest(send_ncr));
+
+ // running two should do the send then the receive
+ io_service_->run_one();
+ io_service_->run_one();
+
+ // Verify that the request can be added to the queue and queue
+ // size increments accordingly.
+ EXPECT_EQ(1, queue_mgr_->getQueueSize());
+
+ // Verify that peek shows the NCR we just sent
+ EXPECT_NO_THROW(received_ncr = queue_mgr_->peek());
+ EXPECT_TRUE(checkSendVsReceived(send_ncr, received_ncr));
+
+ // Verify that we and dequeue the request.
+ EXPECT_NO_THROW(queue_mgr_->dequeue());
+ EXPECT_EQ(0, queue_mgr_->getQueueSize());
+ }
+
+ StatMap stats_ncr = {
+ { "ncr-received", 3},
+ { "ncr-invalid", 0},
+ { "ncr-error", 0}
+ };
+ checkStats(stats_ncr);
+
+ // Iterate over the list of requests, sending and receiving
+ // each one. Allow them to accumulate in the queue.
+ for (int i = 0; i < VALID_MSG_CNT; i++) {
+ // Create the ncr and add to our reference list.
+ ASSERT_NO_THROW(send_ncr = NameChangeRequest::fromJSON(valid_msgs[i]));
+ ASSERT_NO_THROW(sender_->sendRequest(send_ncr));
+
+ // running two should do the send then the receive
+ EXPECT_NO_THROW(io_service_->run_one());
+ EXPECT_NO_THROW(io_service_->run_one());
+ EXPECT_EQ(i+1, queue_mgr_->getQueueSize());
+ }
+
+ StatMap stats_ncr_new = {
+ { "ncr-received", 6},
+ { "ncr-invalid", 0},
+ { "ncr-error", 0}
+ };
+ checkStats(stats_ncr_new);
+
+ // Verify that the queue is at max capacity.
+ EXPECT_EQ(queue_mgr_->getMaxQueueSize(), queue_mgr_->getQueueSize());
+
+ // Send another. The send should succeed.
+ ASSERT_NO_THROW(sender_->sendRequest(send_ncr));
+ EXPECT_NO_THROW(io_service_->run_one());
+
+ // Now execute the receive which should not throw but should move us
+ // to STOPPED_QUEUE_FULL state.
+ EXPECT_NO_THROW(io_service_->run_one());
+ EXPECT_EQ(D2QueueMgr::STOPPED_QUEUE_FULL, queue_mgr_->getMgrState());
+
+ // Verify queue size did not increase beyond max.
+ EXPECT_EQ(VALID_MSG_CNT, queue_mgr_->getQueueSize());
+
+ // Verify that setting max queue size to a value less than current size of
+ // the queue is not allowed.
+ EXPECT_THROW(queue_mgr_->setMaxQueueSize(VALID_MSG_CNT-1), D2QueueMgrError);
+ EXPECT_EQ(VALID_MSG_CNT, queue_mgr_->getQueueSize());
+
+ // Verify that we can re-enter RUNNING from STOPPED_QUEUE_FULL.
+ EXPECT_NO_THROW(queue_mgr_->startListening());
+ EXPECT_EQ(D2QueueMgr::RUNNING, queue_mgr_->getMgrState());
+
+ // Verify that the queue contents were preserved.
+ EXPECT_EQ(queue_mgr_->getMaxQueueSize(), queue_mgr_->getQueueSize());
+
+ // Verify that clearQueue works.
+ EXPECT_NO_THROW(queue_mgr_->clearQueue());
+ EXPECT_EQ(0, queue_mgr_->getQueueSize());
+
+ // Verify that we can again receive requests.
+ // Send should be fine.
+ ASSERT_NO_THROW(sender_->sendRequest(send_ncr));
+ EXPECT_NO_THROW(io_service_->run_one());
+
+ // Receive should succeed.
+ EXPECT_NO_THROW(io_service_->run_one());
+ EXPECT_EQ(1, queue_mgr_->getQueueSize());
+}
+
+} // end of anonymous namespace
diff --git a/src/bin/d2/tests/d2_simple_parser_unittest.cc b/src/bin/d2/tests/d2_simple_parser_unittest.cc
new file mode 100644
index 0000000..b236469
--- /dev/null
+++ b/src/bin/d2/tests/d2_simple_parser_unittest.cc
@@ -0,0 +1,1194 @@
+// Copyright (C) 2017-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.com/MPL/2.0/.
+
+#include <config.h>
+#include <gtest/gtest.h>
+#include <cc/data.h>
+#include <d2/tests/parser_unittest.h>
+#include <d2srv/d2_simple_parser.h>
+#include <testutils/test_to_element.h>
+
+#include <boost/lexical_cast.hpp>
+
+using namespace isc;
+using namespace isc::data;
+using namespace isc::d2;
+using namespace isc::test;
+
+namespace {
+
+/// @brief Checks if specified element matches the given integer default
+///
+/// @param element defaulted element to check
+/// @param deflt SimpleDefault which supplied the default value
+void checkIntegerValue(const ConstElementPtr& element,
+ const SimpleDefault& deflt) {
+ ASSERT_TRUE(element);
+
+ // Verify it is an integer.
+ ASSERT_EQ(Element::integer, element->getType());
+
+ // Turn default value string into an int.
+ int64_t default_value = 0;
+ ASSERT_NO_THROW(default_value = boost::lexical_cast<int64_t>(deflt.value_));
+
+ // Verify it has the expected value.
+ EXPECT_EQ(default_value, element->intValue());
+}
+
+/// @brief Checks if specified element matches the given boolean default
+///
+/// @param element defaulted element to check
+/// @param deflt SimpleDefault which supplied the default value
+void checkBooleanValue(const ConstElementPtr& element,
+ const SimpleDefault& deflt) {
+ ASSERT_TRUE(element);
+
+ // Verify it is a bool.
+ ASSERT_EQ(Element::boolean, element->getType());
+
+ // Turn default value string into a bool.
+ bool default_value = false;
+ ASSERT_NO_THROW(boost::lexical_cast<bool>(deflt.value_));
+
+ // Verify it has the expected value.
+ EXPECT_EQ(default_value, element->boolValue());
+}
+
+/// @brief Checks if specified element matches the given string default
+///
+/// @param element defaulted element to check
+/// @param deflt SimpleDefault which supplied the default value
+void checkStringValue(const ConstElementPtr& element,
+ const SimpleDefault& deflt) {
+ ASSERT_TRUE(element);
+
+ // Verify it's a string
+ ASSERT_EQ(Element::string, element->getType());
+
+ // Verify it has the expected value
+ EXPECT_EQ(deflt.value_, element->stringValue());
+ }
+
+/// TSIGKeyInfo against the given set of values, and that the TSIGKey
+/// member points to a key.
+///
+/// @param key is a pointer to the TSIGKeyInfo instance to verify
+/// @param name is the value to compare against key's name_.
+/// @param algorithm is the string value to compare against key's algorithm.
+/// @param secret is the value to compare against key's secret.
+///
+/// @return returns true if there is a match across the board, otherwise it
+/// returns false.
+bool checkKey(TSIGKeyInfoPtr key, const std::string& name,
+ const std::string& algorithm, const std::string& secret,
+ uint32_t digestbits = 0) {
+ // Return value, assume its a match.
+ return (((key) &&
+ (key->getName() == name) &&
+ (key->getAlgorithm() == algorithm) &&
+ (key->getDigestbits() == digestbits) &&
+ (key->getSecret() == secret) &&
+ (key->getTSIGKey())));
+}
+
+/// @brief Convenience function which compares the contents of the given
+/// DnsServerInfo against the given set of values.
+///
+/// It is structured in such a way that each value is checked, and output
+/// is generated for all that do not match.
+///
+/// @param server is a pointer to the server to check against.
+/// @param hostname is the value to compare against server's hostname_.
+/// @param ip_address is the string value to compare against server's
+/// ip_address_.
+/// @param port is the value to compare against server's port.
+///
+/// @return returns true if there is a match across the board, otherwise it
+/// returns false.
+bool checkServer(DnsServerInfoPtr server, const char* hostname,
+ const char *ip_address, uint32_t port)
+{
+ // Return value, assume its a match.
+ bool result = true;
+
+ if (!server) {
+ EXPECT_TRUE(server);
+ return false;
+ }
+
+ // Check hostname.
+ if (server->getHostname() != hostname) {
+ EXPECT_EQ(hostname, server->getHostname());
+ result = false;
+ }
+
+ // Check IP address.
+ if (server->getIpAddress().toText() != ip_address) {
+ EXPECT_EQ(ip_address, server->getIpAddress().toText());
+ result = false;
+ }
+
+ // Check port.
+ if (server->getPort() != port) {
+ EXPECT_EQ (port, server->getPort());
+ result = false;
+ }
+
+ return (result);
+}
+
+/// @brief Base class test fixture for testing JSON and element parsing
+/// for D2 configuration elements. It combines the three phases of
+/// configuration parsing normally orchestrated by D2CfgMgr:
+/// 1. Submit the JSON text to the JSON parser
+/// 2. Add defaults to the element tree produced by the JSON parser
+/// 3. Pass the element tree into the appropriate SimpleParser derivation
+/// to parse the element tree into D2 objects.
+class D2SimpleParserTest : public ::testing::Test {
+public:
+ /// @brief Constructor
+ ///
+ /// @param parser_type specifies the parsing starting point at which
+ /// the JSON parser should begin. It defaults to PARSER_JSON. See @c
+ /// D2ParserContext::ParserType for all possible values.
+ D2SimpleParserTest(const D2ParserContext::ParserType&
+ parser_type = D2ParserContext::PARSER_JSON)
+ : parser_type_(parser_type) {
+ reset();
+ }
+
+ /// @brief Destructor
+ virtual ~D2SimpleParserTest() {
+ reset();
+ }
+
+ /// @brief Parses JSON text and compares the results against an expected
+ /// outcome.
+ ///
+ /// The JSON text is submitted to the D2ParserContext for parsing. Any
+ /// errors emitted here are caught and compared against the expected
+ /// error or flagged as unexpected.
+ /// Next, the virtual method, setDefaults()is invoked. his method should
+ /// be used by derivations to add default values to the element tree
+ /// produced by the JSON parser.
+ /// Lastly, it passes the element tree into the virtual method,
+ /// parseElement(). This method should be used by derivations to create
+ /// the appropriate element parser to parse the element tree into the
+ /// appropriate D2 object(s).
+ ///
+ /// @param json JSON text to parse
+ /// @param exp_error exact text of the error message expected or ""
+ /// if parsing should succeed.
+ ::testing::AssertionResult parseOrFail(const std::string& json,
+ const std::string& exp_error) {
+ try {
+ // Free up objects created by previous invocation
+ reset();
+
+ // Submit JSON text to JSON parser. We convert the result to
+ // a mutable element tree to allow defaults to be added.
+ D2ParserContext context;
+ data::ElementPtr elem = boost::const_pointer_cast<Element>
+ (context.parseString(json, parser_type_));
+ // Add any defaults
+ setDefaults(elem);
+
+ // Now parse the element tree into object(s).
+ parseElement(elem);
+ } catch (const std::exception& ex) {
+ std::string caught_error = ex.what();
+ if (exp_error.empty()) {
+ return ::testing::AssertionFailure()
+ << "Unexpected error: " << caught_error
+ << "\n json: [" << json << "]";
+ }
+
+ if (exp_error != caught_error) {
+ return ::testing::AssertionFailure()
+ << "Wrong error detected, expected: "
+ << exp_error << ", got: " << caught_error
+ << "\n json: [" << json << "]";
+ }
+ return ::testing::AssertionSuccess();
+ }
+
+ if (!exp_error.empty()) {
+ return ::testing::AssertionFailure()
+ << "Unexpected parsing success "
+ << exp_error << "\n json: [" << json << "]";
+ }
+
+ return ::testing::AssertionSuccess();
+ }
+
+
+protected:
+ /// @brief Free up objects created by element parsing
+ /// This method is invoked at the beginning of @c parseOrFail() to
+ /// ensure any D2 object(s) that were created by a prior invocation are
+ /// destroyed. This permits parsing to be conducted more than once
+ /// in the same test.
+ virtual void reset(){};
+
+ /// @brief Adds default values to the given element tree
+ ///
+ /// Derivations are expected to use the appropriate methods in
+ /// D2SimpleParser to add defaults values.
+ ///
+ /// @param config element tree in which defaults should be added
+ /// @return the number of default items added to the tree
+ virtual size_t setDefaults(data::ElementPtr config) {
+ static_cast<void>(config);
+ return (0);
+ }
+
+ /// @brief Parses a given element tree into D2 object(s)
+ ///
+ /// Derivations are expected to create the appropriate element
+ /// parser and pass it the element tree for parsing. Any object(s)
+ /// created should likely be saved for content verification
+ /// outside of this method.
+ ///
+ /// @param config element tree to parse
+ virtual void parseElement(data::ConstElementPtr config) {
+ static_cast<void>(config);
+ }
+
+ D2ParserContext::ParserType parser_type_;
+};
+
+/// @brief Convenience macros for calling parseOrFail
+#define PARSE_OK(a) EXPECT_TRUE((parseOrFail(a, "")))
+#define PARSE_FAIL(a,b) EXPECT_TRUE((parseOrFail(a, b)))
+
+// This test checks if global defaults are properly set for D2.
+TEST_F(D2SimpleParserTest, globalD2Defaults) {
+
+ ElementPtr empty = isc::d2::test::parseJSON("{ }");
+ size_t num = 0;
+
+ EXPECT_NO_THROW(num = D2SimpleParser::setAllDefaults(empty));
+
+ // We expect 5 parameters to be inserted.
+ EXPECT_EQ(num, 8);
+
+ // Let's go over all parameters we have defaults for.
+ BOOST_FOREACH(SimpleDefault deflt, D2SimpleParser::D2_GLOBAL_DEFAULTS) {
+ ConstElementPtr x;
+ ASSERT_NO_THROW(x = empty->get(deflt.name_));
+
+ EXPECT_TRUE(x);
+ if (x) {
+ if (deflt.type_ == Element::integer) {
+ checkIntegerValue(x, deflt);
+ } else if (deflt.type_ == Element::boolean) {
+ checkBooleanValue(x, deflt);
+ } else if (deflt.type_ == Element::string) {
+ checkStringValue(x, deflt);
+ } else {
+ // add them if we need to. Like what do you if it's a map?
+ ADD_FAILURE() << "default type not supported:" << deflt.name_
+ << " ,type: " << deflt.type_;
+ }
+ }
+ }
+}
+
+/// @brief Test fixture class for testing TSIGKeyInfo parsing.
+class TSIGKeyInfoParserTest : public D2SimpleParserTest {
+public:
+ /// @brief Constructor
+ TSIGKeyInfoParserTest()
+ : D2SimpleParserTest(D2ParserContext::PARSER_TSIG_KEY) {
+ }
+
+ /// @brief Free up the keys created by parsing
+ virtual void reset() {
+ key_.reset();
+ };
+
+ /// @brief Destructor
+ virtual ~TSIGKeyInfoParserTest() {
+ reset();
+ };
+
+ /// @brief Adds TSIG Key default values to the given TSIG Key element
+ ///
+ /// @param config TSIG Key element to which defaults should be added
+ ///
+ /// @return the number of default items added to the tree
+ size_t setDefaults(data::ElementPtr config) {
+ return (SimpleParser::setDefaults(config, D2SimpleParser::
+ TSIG_KEY_DEFAULTS));
+ }
+
+ /// @brief Attempts to parse the given element into a TSIGKeyInfo
+ ///
+ /// Assumes the given element is a Map containing the attributes for
+ /// a TSIG Key. If parsing is successful the new TSIGKeyInfo instance
+ /// is retained in the member, key_;
+ ///
+ /// @param config element to parse
+ void parseElement(data::ConstElementPtr config) {
+ TSIGKeyInfoParser parser;
+ key_ = parser.parse(config);
+ }
+
+ /// @brief Retains the TSIGKeyInfo created by a successful parsing
+ TSIGKeyInfoPtr key_;
+};
+
+
+/// @brief Test fixture class for testing TSIGKeyInfo list parsing.
+class TSIGKeyInfoListParserTest : public D2SimpleParserTest {
+public:
+ /// @brief Constructor
+ TSIGKeyInfoListParserTest()
+ : D2SimpleParserTest(D2ParserContext::PARSER_TSIG_KEYS) {
+ }
+
+ /// @brief Destructor
+ virtual ~TSIGKeyInfoListParserTest() {
+ reset();
+ }
+
+ /// @brief Free up the keys created by parsing
+ virtual void reset() {
+ keys_.reset();
+ };
+
+ /// @brief Adds TSIG Key default values to a list of TSIG Key elements
+ ///
+ /// @param config list of TSIG Key elements to which defaults should be
+ /// added
+ ///
+ /// @return the number of default items added to the tree
+ size_t setDefaults(data::ElementPtr config) {
+ return (SimpleParser::setListDefaults(config, D2SimpleParser::
+ TSIG_KEY_DEFAULTS));
+ }
+
+ /// @brief Attempts to parse the given element into a list of TSIGKeyInfos
+ ///
+ /// Assumes the given element is a list containing one or more TSIG Keys
+ /// elements. If parsing is successful the list of TSIGKeyInfo instances
+ /// is retained in the member, keys_;
+ ///
+ /// @param config element to parse
+ void parseElement(data::ConstElementPtr config) {
+ TSIGKeyInfoListParser parser;
+ keys_ = parser.parse(config);
+ }
+
+ /// @brief Retains the TSIGKeyInfos created by a successful parsing
+ TSIGKeyInfoMapPtr keys_;
+};
+
+/// @brief Test fixture class for testing DnsServerInfo parsing.
+class DnsServerInfoParserTest : public D2SimpleParserTest {
+public:
+ /// @brief Constructor
+ DnsServerInfoParserTest()
+ : D2SimpleParserTest(D2ParserContext::PARSER_DNS_SERVER) {
+ }
+
+ /// @brief Destructor
+ virtual ~DnsServerInfoParserTest() {
+ reset();
+ }
+
+ /// @brief Free up the server created by parsing
+ virtual void reset() {
+ server_.reset();
+ }
+
+ /// @brief Adds DNS Server default values to the given DNS Server element
+ ///
+ /// @param config DNS Server element to which defaults should be added
+ ///
+ /// @return the number of default items added to the tree
+ virtual size_t setDefaults(data::ElementPtr config) {
+ return (SimpleParser::setDefaults(config, D2SimpleParser::
+ DNS_SERVER_DEFAULTS));
+ }
+
+ /// @brief Attempts to parse the given element into a DnsServerInfo
+ ///
+ /// Assumes the given element is a map containing the attributes for
+ /// a DNS Server. If parsing is successful the new DnsServerInfo instance
+ /// is retained in the member, server_;
+ ///
+ /// @param config element to parse
+ virtual void parseElement(data::ConstElementPtr config) {
+ DnsServerInfoParser parser;
+ std::string domain = "{ \"key-name\": \"\" }";
+ server_ = parser.parse(config, Element::fromJSON(domain), {});
+ }
+
+ /// @brief Retains the DnsServerInfo created by a successful parsing
+ DnsServerInfoPtr server_;
+};
+
+/// @brief Test fixture class for testing DnsServerInfoList parsing.
+class DnsServerInfoListParserTest : public D2SimpleParserTest {
+public:
+ /// @brief Constructor
+ DnsServerInfoListParserTest()
+ : D2SimpleParserTest(D2ParserContext::PARSER_DNS_SERVERS) {
+ }
+
+ /// @brief Destructor
+ virtual ~DnsServerInfoListParserTest() {
+ reset();
+ }
+
+ /// @brief Free up the servers created by parsing
+ virtual void reset() {
+ servers_.reset();
+ }
+
+ /// @brief Adds DNS Server default values to a list of DNS Server elements
+ ///
+ /// @param config list of DNS Server elements to which defaults should be
+ /// added
+ ///
+ /// @return the number of default items added to the tree
+ virtual size_t setDefaults(data::ElementPtr config) {
+ return (SimpleParser::setListDefaults(config, D2SimpleParser::
+ DNS_SERVER_DEFAULTS));
+ }
+
+ /// @brief Attempts to parse the given element into a list of DnsServerInfos
+ ///
+ /// Assumes the given element is a list containing one or more DNS Servers
+ /// elements. If parsing is successful the list of DnsServerInfo instances
+ /// is retained in the member, keys_;
+ ///
+ /// @param config element to parse
+ virtual void parseElement(data::ConstElementPtr config) {
+ DnsServerInfoListParser parser;
+ std::string domain = "{ \"key-name\": \"\" }";
+ servers_ = parser.parse(config, Element::fromJSON(domain), {});
+ }
+
+ /// @brief Retains the DnsServerInfos created by a successful parsing
+ DnsServerInfoStoragePtr servers_;
+};
+
+
+/// @brief Test fixture class for testing DDnsDomain parsing.
+class DdnsDomainParserTest : public D2SimpleParserTest {
+public:
+
+ /// @brief Constructor
+ DdnsDomainParserTest(const D2ParserContext::ParserType& parser_type
+ = D2ParserContext::PARSER_DDNS_DOMAIN)
+ : D2SimpleParserTest(parser_type), keys_(new TSIGKeyInfoMap()) {
+ }
+
+ /// @brief Destructor
+ virtual ~DdnsDomainParserTest() {
+ reset();
+ }
+
+ /// @brief Free up the domain created by parsing
+ virtual void reset() {
+ domain_.reset();
+ }
+
+ /// @brief Add TSIGKeyInfos to the key map
+ ///
+ /// @param name the name of the key
+ /// @param algorithm the algorithm of the key
+ /// @param secret the secret value of the key
+ void addKey(const std::string& name, const std::string& algorithm,
+ const std::string& secret) {
+ TSIGKeyInfoPtr key_info(new TSIGKeyInfo(name, algorithm, secret));
+ (*keys_)[name]=key_info;
+ }
+
+ /// @brief Adds DDNS Domain values to the given DDNS Domain element
+ ///
+ /// @param config DDNS Domain element to which defaults should be added
+ ///
+ /// @return the number of default items added to the tree
+ virtual size_t setDefaults(data::ElementPtr config) {
+ return (D2SimpleParser::setDdnsDomainDefaults(config, D2SimpleParser::
+ DDNS_DOMAIN_DEFAULTS));
+ }
+
+ /// @brief Attempts to parse the given element into a DdnsDomain
+ ///
+ /// Assumes the given element is a map containing the attributes for
+ /// a DDNS Domain. If parsing is successful the new DdnsDomain instance
+ /// is retained in the member, server_;
+ ///
+ /// @param config element to parse
+ virtual void parseElement(data::ConstElementPtr config) {
+ DdnsDomainParser parser;
+ domain_ = parser.parse(config, keys_);
+ }
+
+ /// @brief Retains the DdnsDomain created by a successful parsing
+ DdnsDomainPtr domain_;
+
+ /// @brief Storage for TSIGKeys, used by DdnsDomainParser to validate
+ /// domain keys
+ TSIGKeyInfoMapPtr keys_;
+};
+
+class DdnsDomainListParserTest : public DdnsDomainParserTest {
+public:
+ /// @brief Constructor
+ DdnsDomainListParserTest()
+ // We need the list context type to parse lists correctly
+ : DdnsDomainParserTest(D2ParserContext::PARSER_DDNS_DOMAINS) {
+ }
+
+ /// @brief Destructor
+ virtual ~DdnsDomainListParserTest() {
+ reset();
+ }
+
+ /// @brief Free up domains created by parsing
+ virtual void reset() {
+ domains_.reset();
+ }
+
+ /// @brief Adds DDNS Domain default values to a list of DDNS Domain elements
+ ///
+ /// @param config list of DDNS Domain elements to which defaults should be
+ /// added
+ ///
+ /// @return the number of default items added to the tree
+ virtual size_t setDefaults(data::ElementPtr config) {
+ size_t cnt = 0;
+ // We don't use SimpleParser::setListDefaults() as this does
+ // not handle sub-lists or sub-maps
+ BOOST_FOREACH(ElementPtr domain, config->listValue()) {
+ cnt += D2SimpleParser::
+ setDdnsDomainDefaults(domain, D2SimpleParser::
+ DDNS_DOMAIN_DEFAULTS);
+ }
+
+ return (cnt);
+ }
+
+ /// @brief Attempts to parse the given element into a list of DdnsDomains
+ ///
+ /// Assumes the given element is a list containing one or more DDNS Domains
+ /// elements. If parsing is successful the list of DdnsDomain instances
+ /// is retained in the member, keys_;
+ ///
+ /// @param config element to parse
+ virtual void parseElement(data::ConstElementPtr config) {
+ DdnsDomainListParser parser;
+ domains_ = parser.parse(config, keys_);
+ }
+
+ /// @brief Retains the DdnsDomains created by a successful parsing
+ DdnsDomainMapPtr domains_;
+};
+
+/// @brief Tests the enforcement of data validation when parsing TSIGKeyInfos.
+/// It verifies that:
+/// 1. Name cannot be blank.
+/// 2. Algorithm cannot be blank.
+/// 3. Secret cannot be blank.
+TEST_F(TSIGKeyInfoParserTest, invalidEntry) {
+
+ // Name cannot be blank.
+ std::string config = "{"
+ " \"name\": \"\" , "
+ " \"algorithm\": \"HMAC-MD5\" , "
+ " \"secret\": \"LSWXnfkKZjdPJI5QxlpnfQ==\" "
+ "}";
+ PARSE_FAIL(config, "<string>:1.9: TSIG key name cannot be blank");
+
+ // Algorithm cannot be be blank.
+ config = "{"
+ " \"name\": \"d2_key_one\" , "
+ " \"algorithm\": \"\" , "
+ " \"secret\": \"LSWXnfkKZjdPJI5QxlpnfQ==\" "
+ "}";
+ PARSE_FAIL(config, "<string>:1.38: TSIG key algorithm cannot be blank");
+
+ // Algorithm must be a valid algorithm
+ config = "{"
+ " \"name\": \"d2_key_one\" , "
+ " \"algorithm\": \"bogus\" , "
+ " \"secret\": \"LSWXnfkKZjdPJI5QxlpnfQ==\" "
+ "}";
+ PARSE_FAIL(config, "tsig-key : Unknown TSIG Key algorithm:"
+ " bogus (<string>:1:40)");
+
+ // Secret cannot be blank
+ config = "{"
+ " \"name\": \"d2_key_one\" , "
+ " \"algorithm\": \"HMAC-MD5\" , "
+ " \"secret\": \"\" "
+ "}";
+ PARSE_FAIL(config, "<string>:1.62: TSIG key secret cannot be blank");
+
+ // Secret must be valid for algorithm
+ config = "{"
+ " \"name\": \"d2_key_one\" , "
+ " \"algorithm\": \"HMAC-MD5\" , "
+ " \"digest-bits\": 120 , "
+ " \"secret\": \"bogus\" "
+ "}";
+ PARSE_FAIL(config, "Cannot make D2TsigKey: Incomplete input for base64:"
+ " bogus (<string>:1:1)");
+}
+
+
+/// @brief Verifies that TSIGKeyInfo parsing creates a proper TSIGKeyInfo
+/// when given a valid combination of entries.
+TEST_F(TSIGKeyInfoParserTest, validEntry) {
+ // Valid entries for TSIG key, all items are required.
+ std::string config = "{"
+ " \"name\": \"d2_key_one\" , "
+ " \"algorithm\": \"HMAC-MD5\" , "
+ " \"digest-bits\": 120 , "
+ " \"secret\": \"dGhpcyBrZXkgd2lsbCBtYXRjaA==\" "
+ "}";
+ // Verify that it parses.
+ PARSE_OK(config);
+ ASSERT_TRUE(key_);
+
+ // Verify the key contents.
+ EXPECT_TRUE(checkKey(key_, "d2_key_one", "HMAC-MD5",
+ "dGhpcyBrZXkgd2lsbCBtYXRjaA==", 120));
+
+ // Verify unparsing.
+ runToElementTest<TSIGKeyInfo>(config, *key_);
+}
+
+/// @brief Verifies that attempting to parse an invalid list of TSIGKeyInfo
+/// entries is detected.
+TEST_F(TSIGKeyInfoListParserTest, invalidTSIGKeyList) {
+ // Construct a list of keys with an invalid key entry.
+ std::string config = "["
+ " { \"name\": \"key1\" , "
+ " \"algorithm\": \"HMAC-MD5\" ,"
+ " \"digest-bits\": 120 , "
+ " \"secret\": \"GWG/Xfbju4O2iXGqkSu4PQ==\" "
+ " },"
+ // this entry has an invalid algorithm
+ " { \"name\": \"key2\" , "
+ " \"algorithm\": \"\" ,"
+ " \"digest-bits\": 120 , "
+ " \"secret\": \"GWG/Xfbju4O2iXGqkSu4PQ==\" "
+ " },"
+ " { \"name\": \"key3\" , "
+ " \"algorithm\": \"HMAC-MD5\" ,"
+ " \"secret\": \"GWG/Xfbju4O2iXGqkSu4PQ==\" "
+ " }"
+ " ]";
+
+ PARSE_FAIL(config, "<string>:1.151: TSIG key algorithm cannot be blank");
+}
+
+/// @brief Verifies that attempting to parse an invalid list of TSIGKeyInfo
+/// entries is detected.
+TEST_F(TSIGKeyInfoListParserTest, duplicateTSIGKey) {
+ // Construct a list of keys with an invalid key entry.
+ std::string config = "["
+ " { \"name\": \"key1\" , "
+ " \"algorithm\": \"HMAC-MD5\" ,"
+ " \"digest-bits\": 120 , "
+ " \"secret\": \"GWG/Xfbju4O2iXGqkSu4PQ==\" "
+ " },"
+ " { \"name\": \"key2\" , "
+ " \"algorithm\": \"HMAC-MD5\" ,"
+ " \"digest-bits\": 120 , "
+ " \"secret\": \"GWG/Xfbju4O2iXGqkSu4PQ==\" "
+ " },"
+ " { \"name\": \"key1\" , "
+ " \"algorithm\": \"HMAC-MD5\" ,"
+ " \"secret\": \"GWG/Xfbju4O2iXGqkSu4PQ==\" "
+ " }"
+ " ]";
+
+ PARSE_FAIL(config,
+ "Duplicate TSIG key name specified : key1 (<string>:1:239)");
+}
+
+/// @brief Verifies a valid list of TSIG Keys parses correctly.
+/// Also verifies that all of the supported algorithm names work.
+TEST_F(TSIGKeyInfoListParserTest, validTSIGKeyList) {
+ // Construct a valid list of keys.
+ std::string config = "["
+ " { \"name\": \"key1\" , "
+ " \"algorithm\": \"HMAC-MD5\" ,"
+ " \"digest-bits\": 80 , "
+ " \"secret\": \"dGhpcyBrZXkgd2lsbCBtYXRjaA==\" "
+ " },"
+ " { \"name\": \"key2\" , "
+ " \"algorithm\": \"HMAC-SHA1\" ,"
+ " \"digest-bits\": 80 , "
+ " \"secret\": \"dGhpcyBrZXkgd2lsbCBtYXRjaA==\" "
+ " },"
+ " { \"name\": \"key3\" , "
+ " \"algorithm\": \"HMAC-SHA256\" ,"
+ " \"digest-bits\": 128 , "
+ " \"secret\": \"dGhpcyBrZXkgd2lsbCBtYXRjaA==\" "
+ " },"
+ " { \"name\": \"key4\" , "
+ " \"algorithm\": \"HMAC-SHA224\" ,"
+ " \"digest-bits\": 112 , "
+ " \"secret\": \"dGhpcyBrZXkgd2lsbCBtYXRjaA==\" "
+ " },"
+ " { \"name\": \"key5\" , "
+ " \"algorithm\": \"HMAC-SHA384\" ,"
+ " \"digest-bits\": 192 , "
+ " \"secret\": \"dGhpcyBrZXkgd2lsbCBtYXRjaA==\" "
+ " },"
+ " { \"name\": \"key6\" , "
+ " \"algorithm\": \"HMAC-SHA512\" ,"
+ " \"digest-bits\": 256 , "
+ " \"secret\": \"dGhpcyBrZXkgd2lsbCBtYXRjaA==\" "
+ " }"
+ " ]";
+
+ PARSE_OK(config);
+ ASSERT_TRUE(keys_);
+
+ std::string ref_secret = "dGhpcyBrZXkgd2lsbCBtYXRjaA==";
+ // Verify the correct number of keys are present
+ int count = keys_->size();
+ ASSERT_EQ(6, count);
+
+ // Find the 1st key and retrieve it.
+ TSIGKeyInfoMap::iterator gotit = keys_->find("key1");
+ ASSERT_TRUE(gotit != keys_->end());
+ TSIGKeyInfoPtr& key = gotit->second;
+
+ // Verify the key contents.
+ EXPECT_TRUE(checkKey(key, "key1", TSIGKeyInfo::HMAC_MD5_STR,
+ ref_secret, 80));
+
+ // Find the 2nd key and retrieve it.
+ gotit = keys_->find("key2");
+ ASSERT_TRUE(gotit != keys_->end());
+ key = gotit->second;
+
+ // Verify the key contents.
+ EXPECT_TRUE(checkKey(key, "key2", TSIGKeyInfo::HMAC_SHA1_STR,
+ ref_secret, 80));
+
+ // Find the 3rd key and retrieve it.
+ gotit = keys_->find("key3");
+ ASSERT_TRUE(gotit != keys_->end());
+ key = gotit->second;
+
+ // Verify the key contents.
+ EXPECT_TRUE(checkKey(key, "key3", TSIGKeyInfo::HMAC_SHA256_STR,
+ ref_secret, 128));
+
+ // Find the 4th key and retrieve it.
+ gotit = keys_->find("key4");
+ ASSERT_TRUE(gotit != keys_->end());
+ key = gotit->second;
+
+ // Verify the key contents.
+ EXPECT_TRUE(checkKey(key, "key4", TSIGKeyInfo::HMAC_SHA224_STR,
+ ref_secret, 112));
+
+ // Find the 5th key and retrieve it.
+ gotit = keys_->find("key5");
+ ASSERT_TRUE(gotit != keys_->end());
+ key = gotit->second;
+
+ // Verify the key contents.
+ EXPECT_TRUE(checkKey(key, "key5", TSIGKeyInfo::HMAC_SHA384_STR,
+ ref_secret, 192));
+
+ // Find the 6th key and retrieve it.
+ gotit = keys_->find("key6");
+ ASSERT_TRUE(gotit != keys_->end());
+ key = gotit->second;
+
+ // Verify the key contents.
+ EXPECT_TRUE(checkKey(key, "key6", TSIGKeyInfo::HMAC_SHA512_STR,
+ ref_secret, 256));
+}
+
+/// @brief Tests the enforcement of data validation when parsing DnsServerInfos.
+/// It verifies that:
+/// 1. Specifying both a hostname and an ip address is not allowed.
+/// 2. Specifying both blank a hostname and blank ip address is not allowed.
+/// 3. Specifying a negative port number is not allowed.
+
+TEST_F(DnsServerInfoParserTest, invalidEntry) {
+ // Create a config in which both host and ip address are supplied.
+ // Verify that parsing fails.
+ std::string config = "{ \"hostname\": \"pegasus.example\", "
+ " \"ip-address\": \"127.0.0.1\", "
+ " \"port\": 100} ";
+ PARSE_FAIL(config, "<string>:1.13: hostname is not yet supported");
+
+
+ // Neither host nor ip address supplied
+ // Verify that builds fails.
+ config = "{ \"hostname\": \"\", "
+ " \"ip-address\": \"\", "
+ " \"port\": 100} ";
+ PARSE_FAIL(config, "Dns Server must specify one or the other"
+ " of hostname or IP address (<string>:1:1)");
+
+ // Create a config with a negative port number.
+ // Verify that build fails.
+ config = "{ \"hostname\": \"\", "
+ " \"ip-address\": \"192.168.5.6\" ,"
+ " \"port\": -100 }";
+ PARSE_FAIL(config, "<string>:1.60-63: port must be greater than zero but less than 65536");
+}
+
+
+/// @brief Verifies that DnsServerInfo parsing creates a proper DnsServerInfo
+/// when given a valid combination of entries.
+/// It verifies that:
+/// 1. A DnsServerInfo entry is correctly made, when given only a hostname.
+/// 2. A DnsServerInfo entry is correctly made, when given ip address and port.
+/// 3. A DnsServerInfo entry is correctly made, when given only an ip address.
+TEST_F(DnsServerInfoParserTest, validEntry) {
+ /// @todo When resolvable hostname is supported you'll need this test.
+ /// // Valid entries for dynamic host
+ /// std::string config = "{ \"hostname\": \"pegasus.example\" }";
+ /// ASSERT_TRUE(fromJSON(config));
+
+ /// // Verify that it builds and commits without throwing.
+ /// ASSERT_NO_THROW(parser_->build(config_set_));
+ /// ASSERT_NO_THROW(parser_->commit());
+
+ /// //Verify the correct number of servers are present
+ /// int count = servers_->size();
+ /// EXPECT_EQ(1, count);
+
+ /// Verify the server exists and has the correct values.
+ /// DnsServerInfoPtr server = (*servers_)[0];
+ /// EXPECT_TRUE(checkServer(server, "pegasus.example",
+ /// DnsServerInfo::EMPTY_IP_STR,
+ /// DnsServerInfo::STANDARD_DNS_PORT));
+
+ /// // Start over for a new test.
+ /// reset();
+
+ // Valid entries for static ip
+ std::string config = " { \"hostname\" : \"\", "
+ " \"ip-address\": \"127.0.0.1\" , "
+ " \"port\": 100 }";
+ PARSE_OK(config);
+ ASSERT_TRUE(server_);
+ EXPECT_TRUE(checkServer(server_, "", "127.0.0.1", 100));
+
+ // Verify unparsing.
+ runToElementTest<DnsServerInfo>(config, *server_);
+
+ // Valid entries for static ip, no port
+ // This will fail without invoking set defaults
+ config = " { \"ip-address\": \"192.168.2.5\" }";
+ PARSE_OK(config);
+ ASSERT_TRUE(server_);
+ EXPECT_TRUE(checkServer(server_, "", "192.168.2.5",
+ DnsServerInfo::STANDARD_DNS_PORT));
+}
+
+/// @brief Verifies that attempting to parse an invalid list of DnsServerInfo
+/// entries is detected.
+TEST_F(DnsServerInfoListParserTest, invalidServerList) {
+ // Construct a list of servers with an invalid server entry.
+ std::string config = "[ { \"ip-address\": \"127.0.0.1\" }, "
+ "{ \"ip-address\": \"\" }, "
+ "{ \"ip-address\": \"127.0.0.2\" } ]";
+ PARSE_FAIL(config, "Dns Server must specify one or the other"
+ " of hostname or IP address (<string>:1:34)");
+ ASSERT_FALSE(servers_);
+}
+
+/// @brief Verifies that a list of DnsServerInfo entries parses correctly given
+/// a valid configuration.
+TEST_F(DnsServerInfoListParserTest, validServerList) {
+ // Create a valid list of servers.
+ std::string config = "[ { \"ip-address\": \"127.0.0.1\" }, "
+ "{ \"ip-address\": \"127.0.0.2\" }, "
+ "{ \"ip-address\": \"127.0.0.3\" } ]";
+ PARSE_OK(config);
+
+ // Verify that the server storage contains the correct number of servers.
+ ASSERT_EQ(3, servers_->size());
+
+ // Verify the first server exists and has the correct values.
+ DnsServerInfoPtr server = (*servers_)[0];
+ EXPECT_TRUE(checkServer(server, "", "127.0.0.1",
+ DnsServerInfo::STANDARD_DNS_PORT));
+
+ // Verify the second server exists and has the correct values.
+ server = (*servers_)[1];
+ EXPECT_TRUE(checkServer(server, "", "127.0.0.2",
+ DnsServerInfo::STANDARD_DNS_PORT));
+
+ // Verify the third server exists and has the correct values.
+ server = (*servers_)[2];
+ EXPECT_TRUE(checkServer(server, "", "127.0.0.3",
+ DnsServerInfo::STANDARD_DNS_PORT));
+}
+
+/// @brief Tests the enforcement of data validation when parsing DdnsDomains.
+/// It verifies that:
+/// 1. Domain storage cannot be null when constructing a DdnsDomainParser.
+/// 2. The name entry is not optional.
+/// 3. The server list may not be empty.
+/// 4. That a mal-formed server entry is detected.
+/// 5. That an undefined key name is detected.
+TEST_F(DdnsDomainParserTest, invalidDomain) {
+ // Create a domain configuration without a name
+ std::string config = "{ \"key-name\": \"d2_key.example.com\" , "
+ " \"dns-servers\" : [ "
+ " { \"ip-address\": \"127.0.0.1\" , "
+ " \"port\": 100 },"
+ " { \"ip-address\": \"127.0.0.2\" , "
+ " \"port\": 200 },"
+ " { \"ip-address\": \"127.0.0.3\" , "
+ " \"port\": 300 } ] } ";
+ PARSE_FAIL(config, "missing parameter 'name' (<string>:1:1)");
+
+ // Create a domain configuration with an empty server list.
+ config = "{ \"name\": \"example.com\" , "
+ " \"key-name\": \"\" , "
+ " \"dns-servers\" : [ "
+ " ] } ";
+ PARSE_FAIL(config, "<string>:1.69: syntax error, unexpected ], expecting {");
+
+ // Create a domain configuration with a mal-formed server entry.
+ config = "{ \"name\": \"example.com\" , "
+ " \"key-name\": \"\" , "
+ " \"dns-servers\" : [ "
+ " { \"ip-address\": \"127.0.0.3\" , "
+ " \"port\": -1 } ] } ";
+ PARSE_FAIL(config, "<string>:1.111-112: port must be greater than zero but less than 65536");
+
+ // Create a domain configuration without an defined key name
+ config = "{ \"name\": \"example.com\" , "
+ " \"key-name\": \"d2_key.example.com\" , "
+ " \"dns-servers\" : [ "
+ " { \"ip-address\": \"127.0.0.3\" , "
+ " \"port\": 300 } ] } ";
+ PARSE_FAIL(config, "DdnsDomain : specifies"
+ " an undefined key: d2_key.example.com (<string>:1:41)");
+}
+
+/// @brief Verifies the basics of parsing of a DdnsDomain.
+TEST_F(DdnsDomainParserTest, validDomain) {
+ // Add a TSIG key to the test key map, so key validation will pass.
+ addKey("d2_key.example.com", "HMAC-MD5", "GWG/Xfbju4O2iXGqkSu4PQ==");
+
+ // Create a valid domain configuration entry containing three valid
+ // servers.
+ std::string config =
+ "{ \"name\": \"example.com\" , "
+ " \"key-name\": \"d2_key.example.com\" , "
+ " \"dns-servers\" : [ "
+ " { \"ip-address\": \"127.0.0.1\" , "
+ " \"port\": 100 },"
+ " { \"ip-address\": \"127.0.0.2\" , "
+ " \"port\": 200 },"
+ " { \"ip-address\": \"127.0.0.3\" , "
+ " \"port\": 300 } ] } ";
+ PARSE_OK(config);
+
+ // Domain should exist
+ ASSERT_TRUE(domain_);
+
+ // Verify the name and key_name values.
+ EXPECT_EQ("example.com", domain_->getName());
+ EXPECT_EQ("d2_key.example.com", domain_->getKeyName());
+
+ // Verify that the server list exists and contains the correct number of
+ // servers.
+ const DnsServerInfoStoragePtr& servers = domain_->getServers();
+ ASSERT_TRUE(servers);
+ EXPECT_EQ(3, servers->size());
+
+ // Fetch each server and verify its contents.
+ DnsServerInfoPtr server = (*servers)[0];
+ ASSERT_TRUE(server);
+
+ EXPECT_TRUE(checkServer(server, "", "127.0.0.1", 100));
+ ASSERT_TRUE(server->getTSIGKeyInfo());
+ EXPECT_TRUE(server->getTSIGKeyInfo()->getTSIGKey());
+
+ server = (*servers)[1];
+ ASSERT_TRUE(server);
+
+ EXPECT_TRUE(checkServer(server, "", "127.0.0.2", 200));
+ ASSERT_TRUE(server->getTSIGKeyInfo());
+ EXPECT_TRUE(server->getTSIGKeyInfo()->getTSIGKey());
+
+ server = (*servers)[2];
+ ASSERT_TRUE(server);
+
+ EXPECT_TRUE(checkServer(server, "", "127.0.0.3", 300));
+ ASSERT_TRUE(server->getTSIGKeyInfo());
+ EXPECT_TRUE(server->getTSIGKeyInfo()->getTSIGKey());
+
+ // Verify unparsing.
+ ElementPtr json;
+ ASSERT_NO_THROW(json = Element::fromJSON(config));
+ ConstElementPtr servers_json;
+ ASSERT_NO_THROW(servers_json = json->get("dns-servers"));
+ ASSERT_TRUE(servers_json);
+ ASSERT_EQ(Element::list, servers_json->getType());
+ for (size_t i = 0; i < servers_json->size(); ++i) {
+ ElementPtr server_json;
+ ASSERT_NO_THROW(server_json = servers_json->getNonConst(i));
+ ASSERT_NO_THROW(server_json->set("hostname",
+ Element::create(std::string())));
+ }
+ runToElementTest<DdnsDomain>(json, *domain_);
+}
+
+/// @brief Tests the fundamentals of parsing DdnsDomain lists.
+/// This test verifies that given a valid domain list configuration
+/// it will accurately parse and populate each domain in the list.
+TEST_F(DdnsDomainListParserTest, validList) {
+ // Add keys to key map so key validation passes.
+ addKey("d2_key.example.com", "HMAC-MD5", "GWG/Xfbju4O2iXGqkSu4PQ==");
+ addKey("d2_key.billcat.net", "HMAC-MD5", "GWG/Xfbju4O2iXGqkSu4PQ==");
+
+ // Create a valid domain list configuration, with two domains
+ // that have three servers each.
+ std::string config =
+ "[ "
+ "{ \"name\": \"example.com\" , "
+ " \"key-name\": \"d2_key.example.com\" , "
+ " \"dns-servers\" : [ "
+ " { \"ip-address\": \"127.0.0.1\" , "
+ " \"port\": 100 },"
+ " { \"ip-address\": \"127.0.0.2\" , "
+ " \"port\": 200 },"
+ " { \"ip-address\": \"127.0.0.3\" , "
+ " \"port\": 300 } ] } "
+ ", "
+ "{ \"name\": \"billcat.net\" , "
+ " \"key-name\": \"d2_key.billcat.net\" , "
+ " \"dns-servers\" : [ "
+ " { \"ip-address\": \"127.0.0.4\" , "
+ " \"port\": 400 },"
+ " { \"ip-address\": \"127.0.0.5\" , "
+ " \"port\": 500 },"
+ " { \"ip-address\": \"127.0.0.6\" , "
+ " \"port\": 600 } ] } "
+ "] ";
+
+ // Verify that the domain list parses without error.
+ PARSE_OK(config);
+ ASSERT_TRUE(domains_);
+ EXPECT_EQ(2, domains_->size());
+
+ // Verify that the first domain exists and can be retrieved.
+ DdnsDomainMap::iterator gotit = domains_->find("example.com");
+ ASSERT_TRUE(gotit != domains_->end());
+ DdnsDomainPtr& domain = gotit->second;
+
+ // Verify the name and key_name values of the first domain.
+ EXPECT_EQ("example.com", domain->getName());
+ EXPECT_EQ("d2_key.example.com", domain->getKeyName());
+
+ // Verify the each of the first domain's servers
+ DnsServerInfoStoragePtr servers = domain->getServers();
+ ASSERT_TRUE(servers);
+ EXPECT_EQ(3, servers->size());
+
+ DnsServerInfoPtr server = (*servers)[0];
+ ASSERT_TRUE(server);
+ EXPECT_TRUE(checkServer(server, "", "127.0.0.1", 100));
+
+ // Verify the TSIGKeyInfo name and that the actual key was created
+ ASSERT_TRUE(server->getTSIGKeyInfo());
+ EXPECT_EQ(domain->getKeyName(), server->getKeyName());
+ EXPECT_EQ(domain->getKeyName(), server->getTSIGKeyInfo()->getName());
+ EXPECT_TRUE(server->getTSIGKeyInfo()->getTSIGKey());
+
+ server = (*servers)[1];
+ ASSERT_TRUE(server);
+ EXPECT_TRUE(checkServer(server, "", "127.0.0.2", 200));
+ ASSERT_TRUE(server->getTSIGKeyInfo());
+ EXPECT_EQ(domain->getKeyName(), server->getKeyName());
+ EXPECT_EQ(domain->getKeyName(), server->getTSIGKeyInfo()->getName());
+ EXPECT_TRUE(server->getTSIGKeyInfo()->getTSIGKey());
+
+ server = (*servers)[2];
+ ASSERT_TRUE(server);
+ EXPECT_TRUE(checkServer(server, "", "127.0.0.3", 300));
+ ASSERT_TRUE(server->getTSIGKeyInfo());
+ EXPECT_EQ(domain->getKeyName(), server->getKeyName());
+ EXPECT_EQ(domain->getKeyName(), server->getTSIGKeyInfo()->getName());
+ EXPECT_TRUE(server->getTSIGKeyInfo()->getTSIGKey());
+
+ // Verify second domain
+ gotit = domains_->find("billcat.net");
+ ASSERT_TRUE(gotit != domains_->end());
+ domain = gotit->second;
+
+ // Verify the name and key_name values of the second domain.
+ EXPECT_EQ("billcat.net", domain->getName());
+ EXPECT_EQ("d2_key.billcat.net", domain->getKeyName());
+
+ // Verify the each of second domain's servers
+ servers = domain->getServers();
+ ASSERT_TRUE(servers);
+ EXPECT_EQ(3, servers->size());
+
+ server = (*servers)[0];
+ ASSERT_TRUE(server);
+ EXPECT_TRUE(checkServer(server, "", "127.0.0.4", 400));
+ ASSERT_TRUE(server->getTSIGKeyInfo());
+ EXPECT_EQ(domain->getKeyName(), server->getKeyName());
+ EXPECT_EQ(domain->getKeyName(), server->getTSIGKeyInfo()->getName());
+ EXPECT_TRUE(server->getTSIGKeyInfo()->getTSIGKey());
+
+ server = (*servers)[1];
+ ASSERT_TRUE(server);
+ EXPECT_TRUE(checkServer(server, "", "127.0.0.5", 500));
+ ASSERT_TRUE(server->getTSIGKeyInfo());
+ EXPECT_EQ(domain->getKeyName(), server->getKeyName());
+ EXPECT_EQ(domain->getKeyName(), server->getTSIGKeyInfo()->getName());
+ EXPECT_TRUE(server->getTSIGKeyInfo()->getTSIGKey());
+
+ server = (*servers)[2];
+ ASSERT_TRUE(server);
+ EXPECT_TRUE(checkServer(server, "", "127.0.0.6", 600));
+ ASSERT_TRUE(server->getTSIGKeyInfo());
+ EXPECT_EQ(domain->getKeyName(), server->getKeyName());
+ EXPECT_EQ(domain->getKeyName(), server->getTSIGKeyInfo()->getName());
+ EXPECT_TRUE(server->getTSIGKeyInfo()->getTSIGKey());
+}
+
+/// @brief Tests that a domain list configuration cannot contain duplicates.
+TEST_F(DdnsDomainListParserTest, duplicateDomain) {
+ // Create a domain list configuration that contains two domains with
+ // the same name.
+ std::string config =
+ "[ "
+ "{ \"name\": \"example.com\" , "
+ " \"dns-servers\" : [ "
+ " { \"ip-address\": \"127.0.0.3\" , "
+ " \"port\": 300 } ] } "
+ ", "
+ "{ \"name\": \"example.com\" , "
+ " \"dns-servers\" : [ "
+ " { \"ip-address\": \"127.0.0.3\" , "
+ " \"port\": 300 } ] } "
+ "] ";
+ // Verify that the parsing fails.
+ PARSE_FAIL(config,
+ "Duplicate domain specified:example.com (<string>:1:115)");
+}
+
+}
diff --git a/src/bin/d2/tests/d2_unittests.cc b/src/bin/d2/tests/d2_unittests.cc
new file mode 100644
index 0000000..4607550
--- /dev/null
+++ b/src/bin/d2/tests/d2_unittests.cc
@@ -0,0 +1,28 @@
+// Copyright (C) 2013-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <d2srv/d2_log.h>
+#include <log/logger_support.h>
+#include <gtest/gtest.h>
+
+int
+main(int argc, char* argv[]) {
+
+ ::testing::InitGoogleTest(&argc, argv);
+
+ // See the documentation of the KEA_* environment variables in
+ // src/lib/log/README for info on how to tweak logging
+ isc::log::initLogger();
+
+ // Override --localstatedir value for PID files
+ setenv("KEA_PIDFILE_DIR", TEST_DATA_BUILDDIR, 1);
+
+ int result = RUN_ALL_TESTS();
+
+ return (result);
+}
diff --git a/src/bin/d2/tests/d2_update_mgr_unittests.cc b/src/bin/d2/tests/d2_update_mgr_unittests.cc
new file mode 100644
index 0000000..d79b453
--- /dev/null
+++ b/src/bin/d2/tests/d2_update_mgr_unittests.cc
@@ -0,0 +1,989 @@
+// Copyright (C) 2013-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <asiolink/io_service.h>
+#include <d2srv/testutils/nc_test_utils.h>
+#include <d2/d2_update_mgr.h>
+#include <d2/nc_add.h>
+#include <d2/nc_remove.h>
+#include <d2/simple_add.h>
+#include <d2/simple_remove.h>
+#include <process/testutils/d_test_stubs.h>
+#include <util/time_utilities.h>
+
+#include <gtest/gtest.h>
+#include <algorithm>
+#include <vector>
+
+using namespace std;
+using namespace isc;
+using namespace isc::dhcp_ddns;
+using namespace isc::d2;
+using namespace isc::process;
+using namespace isc::util;
+
+namespace {
+
+/// @brief Wrapper class for D2UpdateMgr providing access to non-public methods.
+///
+/// This class facilitates testing by making non-public methods accessible so
+/// they can be invoked directly in test routines.
+class D2UpdateMgrWrapper : public D2UpdateMgr {
+public:
+ /// @brief Constructor
+ ///
+ /// Parameters match those needed by D2UpdateMgr.
+ D2UpdateMgrWrapper(D2QueueMgrPtr& queue_mgr, D2CfgMgrPtr& cfg_mgr,
+ asiolink::IOServicePtr& io_service,
+ const size_t max_transactions = MAX_TRANSACTIONS_DEFAULT)
+ : D2UpdateMgr(queue_mgr, cfg_mgr, io_service, max_transactions) {
+ }
+
+ /// @brief Destructor
+ virtual ~D2UpdateMgrWrapper() {
+ }
+
+ // Expose the protected methods to be tested.
+ using D2UpdateMgr::checkFinishedTransactions;
+ using D2UpdateMgr::pickNextJob;
+ using D2UpdateMgr::makeTransaction;
+};
+
+/// @brief Defines a pointer to a D2UpdateMgr instance.
+typedef boost::shared_ptr<D2UpdateMgrWrapper> D2UpdateMgrWrapperPtr;
+
+/// @brief Test fixture for testing D2UpdateMgr.
+///
+/// Note this class uses D2UpdateMgrWrapper class to exercise non-public
+/// aspects of D2UpdateMgr. D2UpdateMgr depends on both D2QueueMgr and
+/// D2CfgMgr. This fixture provides an instance of each, plus a canned,
+/// valid DHCP_DDNS configuration sufficient to test D2UpdateMgr's basic
+/// functions.
+class D2UpdateMgrTest : public TimedIO, public ConfigParseTest {
+public:
+ D2QueueMgrPtr queue_mgr_;
+ D2CfgMgrPtr cfg_mgr_;
+ D2UpdateMgrWrapperPtr update_mgr_;
+ std::vector<NameChangeRequestPtr> canned_ncrs_;
+ size_t canned_count_;
+
+ D2UpdateMgrTest() {
+ queue_mgr_.reset(new D2QueueMgr(io_service_));
+ cfg_mgr_.reset(new D2CfgMgr());
+ update_mgr_.reset(new D2UpdateMgrWrapper(queue_mgr_, cfg_mgr_,
+ io_service_));
+ makeCannedNcrs();
+ makeCannedConfig();
+ }
+
+ ~D2UpdateMgrTest() {
+ }
+
+ /// @brief Creates a list of valid NameChangeRequest.
+ ///
+ /// This method builds a list of NameChangeRequests from a single
+ /// JSON string request. Each request is assigned a unique DHCID.
+ void makeCannedNcrs() {
+ const char* msg_str =
+ "{"
+ " \"change-type\" : 0 , "
+ " \"forward-change\" : true , "
+ " \"reverse-change\" : false , "
+ " \"fqdn\" : \"my.example.com.\" , "
+ " \"ip-address\" : \"192.168.1.2\" , "
+ " \"dhcid\" : \"0102030405060708\" , "
+ " \"lease-expires-on\" : \"20130121132405\" , "
+ " \"lease-length\" : 1300, "
+ " \"use-conflict-resolution\" : true "
+ "}";
+
+ const char* dhcids[] = { "111111", "222222", "333333", "444444" };
+ canned_count_ = 4;
+ for (int i = 0; i < canned_count_; i++) {
+ dhcp_ddns::NameChangeRequestPtr ncr = NameChangeRequest::
+ fromJSON(msg_str);
+ ncr->setDhcid(dhcids[i]);
+ ncr->setChangeType(i % 2 == 0 ?
+ dhcp_ddns::CHG_ADD : dhcp_ddns::CHG_REMOVE);
+ canned_ncrs_.push_back(ncr);
+ }
+ }
+
+ /// @brief Seeds configuration manager with a valid DHCP_DDNS configuration.
+ void makeCannedConfig() {
+ std::string canned_config_ =
+ "{ "
+ "\"ip-address\" : \"192.168.1.33\" , "
+ "\"port\" : 88 , "
+ "\"tsig-keys\": [] ,"
+ "\"forward-ddns\" : {"
+ " \"ddns-domains\": [ "
+ " { \"name\": \"example.com.\" , "
+ " \"dns-servers\" : [ "
+ " { \"ip-address\": \"127.0.0.1\", \"port\" : 5301 } "
+ " ] },"
+ " { \"name\": \"org.\" , "
+ " \"dns-servers\" : [ "
+ " { \"ip-address\": \"127.0.0.1\" } "
+ " ] }"
+ " ] }, "
+ "\"reverse-ddns\" : { "
+ " \"ddns-domains\": [ "
+ " { \"name\": \"1.168.192.in-addr.arpa.\" , "
+ " \"dns-servers\" : [ "
+ " { \"ip-address\": \"127.0.0.1\", \"port\" : 5301 } "
+ " ] }, "
+ " { \"name\": \"2.0.3.0.8.B.D.0.1.0.0.2.ip6.arpa.\" , "
+ " \"dns-servers\" : [ "
+ " { \"ip-address\": \"127.0.0.1\" } "
+ " ] } "
+ " ] } }";
+
+ // If this configuration fails to parse most tests will fail.
+ ASSERT_TRUE(fromJSON(canned_config_));
+ answer_ = cfg_mgr_->simpleParseConfig(config_set_);
+ ASSERT_TRUE(checkAnswer(0));
+ }
+
+ /// @brief Fakes the completion of a given transaction.
+ ///
+ /// @param index index of the request from which the transaction was formed.
+ /// @param status completion status to assign to the request
+ void completeTransaction(const size_t index,
+ const dhcp_ddns::NameChangeStatus& status) {
+ // add test on index
+ if (index >= canned_count_) {
+ ADD_FAILURE() << "request index is out of range: " << index;
+ }
+
+ const dhcp_ddns::D2Dhcid key = canned_ncrs_[index]->getDhcid();
+
+ // locate the transaction based on the request DHCID
+ TransactionList::iterator pos = update_mgr_->findTransaction(key);
+ if (pos == update_mgr_->transactionListEnd()) {
+ ADD_FAILURE() << "cannot find transaction for key: " << key.toStr();
+ }
+
+ NameChangeTransactionPtr trans = (*pos).second;
+ // Update the status of the request
+ trans->getNcr()->setStatus(status);
+ // End the model.
+ trans->endModel();
+ }
+
+ /// @brief Determines if any transactions are waiting for IO completion.
+ ///
+ /// @returns True if isModelWaiting() is true for at least one of the current
+ /// transactions.
+ bool anyoneWaiting() {
+ TransactionList::iterator it = update_mgr_->transactionListBegin();
+ while (it != update_mgr_->transactionListEnd()) {
+ if (((*it).second)->isModelWaiting()) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /// @brief Process events until all requests have been completed.
+ ///
+ /// This method iteratively calls D2UpdateMgr::sweep and executes
+ /// IOService calls until both the request queue and transaction list
+ /// are empty or a timeout occurs. Note that in addition to the safety
+ /// timer, the number of passes through the loop is also limited to
+ /// a given number. This is a failsafe to guard against an infinite loop
+ /// in the test.
+ void processAll(size_t max_passes = 100) {
+ // Loop until all the transactions have been dequeued and run through to
+ // completion.
+ size_t passes = 0;
+ size_t handlers = 0;
+
+ // Set the timeout to slightly more than DNSClient timeout to allow
+ // timeout processing to occur naturally.
+ size_t timeout = cfg_mgr_->getD2Params()->getDnsServerTimeout() + 100;
+ while (update_mgr_->getQueueCount() ||
+ update_mgr_->getTransactionCount()) {
+ ++passes;
+ update_mgr_->sweep();
+ // If any transactions are waiting on IO, run the service.
+ if (anyoneWaiting()) {
+ int cnt = runTimedIO(timeout);
+
+ // If cnt is zero then the service stopped unexpectedly.
+ if (cnt == 0) {
+ ADD_FAILURE()
+ << "processALL: IO service stopped unexpectedly,"
+ << " passes: " << passes << ", handlers executed: "
+ << handlers;
+ }
+
+ handlers += cnt;
+ }
+
+ // This is a last resort fail safe to ensure we don't go around
+ // forever. We cut it off the number of passes at 100 (default
+ // value). This is roughly ten times the number for the longest
+ // test (currently, multiTransactionTimeout).
+ if (passes > max_passes) {
+ FAIL() << "processALL failed, too many passes: "
+ << passes << ", total handlers executed: " << handlers;
+ }
+ }
+ }
+
+};
+
+/// @brief Tests the D2UpdateMgr construction.
+/// This test verifies that:
+/// 1. Construction with invalid queue manager is not allowed
+/// 2. Construction with invalid configuration manager is not allowed
+/// 3. Construction with max transactions of zero is not allowed
+/// 4. Default construction works and max transactions is defaulted properly
+/// 5. Construction with custom max transactions works properly
+TEST(D2UpdateMgr, construction) {
+ asiolink::IOServicePtr io_service(new isc::asiolink::IOService());
+ D2QueueMgrPtr queue_mgr;
+ D2CfgMgrPtr cfg_mgr;
+ D2UpdateMgrPtr update_mgr;
+
+ // Verify that constructor fails if given an invalid queue manager.
+ ASSERT_NO_THROW(cfg_mgr.reset(new D2CfgMgr()));
+ EXPECT_THROW(D2UpdateMgr(queue_mgr, cfg_mgr, io_service),
+ D2UpdateMgrError);
+
+ // Verify that constructor fails if given an invalid config manager.
+ ASSERT_NO_THROW(queue_mgr.reset(new D2QueueMgr(io_service)));
+ ASSERT_NO_THROW(cfg_mgr.reset());
+ EXPECT_THROW(D2UpdateMgr(queue_mgr, cfg_mgr, io_service),
+ D2UpdateMgrError);
+
+ ASSERT_NO_THROW(cfg_mgr.reset(new D2CfgMgr()));
+
+ // Verify that constructor fails with invalid io_service.
+ io_service.reset();
+ EXPECT_THROW(D2UpdateMgr(queue_mgr, cfg_mgr, io_service),
+ D2UpdateMgrError);
+ io_service.reset(new isc::asiolink::IOService());
+
+ // Verify that max transactions cannot be zero.
+ EXPECT_THROW(D2UpdateMgr(queue_mgr, cfg_mgr, io_service, 0),
+ D2UpdateMgrError);
+
+ // Verify that given valid values, constructor works.
+ ASSERT_NO_THROW(update_mgr.reset(new D2UpdateMgr(queue_mgr, cfg_mgr,
+ io_service)));
+
+ // Verify that max transactions defaults properly.
+ EXPECT_EQ(D2UpdateMgr::MAX_TRANSACTIONS_DEFAULT,
+ update_mgr->getMaxTransactions());
+
+
+ // Verify that constructor permits custom max transactions.
+ ASSERT_NO_THROW(update_mgr.reset(new D2UpdateMgr(queue_mgr, cfg_mgr,
+ io_service, 100)));
+
+ // Verify that max transactions is correct.
+ EXPECT_EQ(100, update_mgr->getMaxTransactions());
+}
+
+/// @brief Tests the D2UpdateManager's transaction list services
+/// This test verifies that:
+/// 1. A transaction can be added to the list.
+/// 2. Finding a transaction in the list by key works correctly.
+/// 3. Looking for a non-existent transaction works properly.
+/// 4. Attempting to add a transaction for a DHCID already in the list fails.
+/// 5. Removing a transaction by key works properly.
+/// 6. Attempting to remove an non-existent transaction does no harm.
+TEST_F(D2UpdateMgrTest, transactionList) {
+ // Grab a canned request for test purposes.
+ NameChangeRequestPtr& ncr = canned_ncrs_[0];
+ TransactionList::iterator pos;
+
+ // Verify that we can add a transaction.
+ EXPECT_NO_THROW(update_mgr_->makeTransaction(ncr));
+ EXPECT_EQ(1, update_mgr_->getTransactionCount());
+
+ // Verify that we can find a transaction by key.
+ EXPECT_NO_THROW(pos = update_mgr_->findTransaction(ncr->getDhcid()));
+ EXPECT_TRUE(pos != update_mgr_->transactionListEnd());
+
+ // Verify that convenience method has same result.
+ EXPECT_TRUE(update_mgr_->hasTransaction(ncr->getDhcid()));
+
+ // Verify that we will not find a transaction that isn't there.
+ dhcp_ddns::D2Dhcid bogus_id("FFFF");
+ EXPECT_NO_THROW(pos = update_mgr_->findTransaction(bogus_id));
+ EXPECT_TRUE(pos == update_mgr_->transactionListEnd());
+
+ // Verify that convenience method has same result.
+ EXPECT_FALSE(update_mgr_->hasTransaction(bogus_id));
+
+ // Verify that adding a transaction for the same key fails.
+ EXPECT_THROW(update_mgr_->makeTransaction(ncr), D2UpdateMgrError);
+ EXPECT_EQ(1, update_mgr_->getTransactionCount());
+
+ // Verify the we can remove a transaction by key.
+ EXPECT_NO_THROW(update_mgr_->removeTransaction(ncr->getDhcid()));
+ EXPECT_EQ(0, update_mgr_->getTransactionCount());
+
+ // Verify the we can try to remove a non-existent transaction without harm.
+ EXPECT_NO_THROW(update_mgr_->removeTransaction(ncr->getDhcid()));
+}
+
+/// @brief Checks transaction creation when both update directions are enabled.
+/// Verifies that when both directions are enabled and servers are matched to
+/// the request, that the transaction is created with both directions turned on.
+TEST_F(D2UpdateMgrTest, bothEnabled) {
+ // Grab a canned request for test purposes.
+ NameChangeRequestPtr& ncr = canned_ncrs_[0];
+ ncr->setReverseChange(true);
+
+ // Verify we are requesting both directions.
+ ASSERT_TRUE(ncr->isForwardChange());
+ ASSERT_TRUE(ncr->isReverseChange());
+
+ // Verify both both directions are enabled.
+ ASSERT_TRUE(cfg_mgr_->forwardUpdatesEnabled());
+ ASSERT_TRUE(cfg_mgr_->reverseUpdatesEnabled());
+
+ // Attempt to make a transaction.
+ ASSERT_NO_THROW(update_mgr_->makeTransaction(ncr));
+
+ // Verify we create a transaction with both directions turned on.
+ EXPECT_EQ(1, update_mgr_->getTransactionCount());
+ EXPECT_TRUE(ncr->isForwardChange());
+ EXPECT_TRUE(ncr->isReverseChange());
+}
+
+/// @brief Checks transaction creation when reverse updates are disabled.
+/// Verifies that when reverse updates are disabled, and there matching forward
+/// servers, that the transaction is still created but with only the forward
+/// direction turned on.
+TEST_F(D2UpdateMgrTest, reverseDisable) {
+ // Make a NCR which requests both directions.
+ NameChangeRequestPtr& ncr = canned_ncrs_[0];
+ ncr->setReverseChange(true);
+
+ // Wipe out forward domain list.
+ DdnsDomainMapPtr emptyDomains(new DdnsDomainMap());
+ cfg_mgr_->getD2CfgContext()->getReverseMgr()->setDomains(emptyDomains);
+
+ // Verify enable methods are correct.
+ ASSERT_TRUE(cfg_mgr_->forwardUpdatesEnabled());
+ ASSERT_FALSE(cfg_mgr_->reverseUpdatesEnabled());
+
+ // Attempt to make a transaction.
+ ASSERT_NO_THROW(update_mgr_->makeTransaction(ncr));
+
+ // Verify we create a transaction with only forward turned on.
+ EXPECT_EQ(1, update_mgr_->getTransactionCount());
+ EXPECT_TRUE(ncr->isForwardChange());
+ EXPECT_FALSE(ncr->isReverseChange());
+}
+
+/// @brief Checks transaction creation when forward updates are disabled.
+/// Verifies that when forward updates are disabled, and there matching reverse
+/// servers, that the transaction is still created but with only the reverse
+/// direction turned on.
+TEST_F(D2UpdateMgrTest, forwardDisabled) {
+ // Make a NCR which requests both directions.
+ NameChangeRequestPtr& ncr = canned_ncrs_[0];
+ ncr->setReverseChange(true);
+
+ // Wipe out forward domain list.
+ DdnsDomainMapPtr emptyDomains(new DdnsDomainMap());
+ cfg_mgr_->getD2CfgContext()->getForwardMgr()->setDomains(emptyDomains);
+
+ // Verify enable methods are correct.
+ ASSERT_FALSE(cfg_mgr_->forwardUpdatesEnabled());
+ ASSERT_TRUE(cfg_mgr_->reverseUpdatesEnabled());
+
+ // Attempt to make a transaction.
+ ASSERT_NO_THROW(update_mgr_->makeTransaction(ncr));
+
+ // Verify we create a transaction with only reverse turned on.
+ EXPECT_EQ(1, update_mgr_->getTransactionCount());
+ EXPECT_FALSE(ncr->isForwardChange());
+ EXPECT_TRUE(ncr->isReverseChange());
+}
+
+
+/// @brief Checks transaction creation when neither update direction is enabled.
+/// Verifies that transactions are not created when both forward and reverse
+/// directions are disabled.
+TEST_F(D2UpdateMgrTest, bothDisabled) {
+ // Grab a canned request for test purposes.
+ NameChangeRequestPtr& ncr = canned_ncrs_[0];
+ ncr->setReverseChange(true);
+ TransactionList::iterator pos;
+
+ // Wipe out both forward and reverse domain lists.
+ DdnsDomainMapPtr emptyDomains(new DdnsDomainMap());
+ cfg_mgr_->getD2CfgContext()->getForwardMgr()->setDomains(emptyDomains);
+ cfg_mgr_->getD2CfgContext()->getReverseMgr()->setDomains(emptyDomains);
+
+ // Verify enable methods are correct.
+ EXPECT_FALSE(cfg_mgr_->forwardUpdatesEnabled());
+ EXPECT_FALSE(cfg_mgr_->reverseUpdatesEnabled());
+
+ // Attempt to make a transaction.
+ ASSERT_NO_THROW(update_mgr_->makeTransaction(ncr));
+
+ // Verify that do not create a transaction.
+ EXPECT_EQ(0, update_mgr_->getTransactionCount());
+}
+
+/// @brief Tests D2UpdateManager's checkFinishedTransactions method.
+/// This test verifies that:
+/// 1. Completed transactions are removed from the transaction list.
+/// 2. Failed transactions are removed from the transaction list.
+/// @todo This test will need to expand if and when checkFinishedTransactions
+/// method expands to do more than remove them from the list.
+TEST_F(D2UpdateMgrTest, checkFinishedTransaction) {
+ // Ensure we have at least 4 canned requests with which to work.
+ ASSERT_TRUE(canned_count_ >= 4);
+
+ // Create a transaction for each canned request.
+ for (int i = 0; i < canned_count_; i++) {
+ EXPECT_NO_THROW(update_mgr_->makeTransaction(canned_ncrs_[i]));
+ }
+ // Verify we have that the transaction count is correct.
+ EXPECT_EQ(canned_count_, update_mgr_->getTransactionCount());
+
+ // Verify that all four transactions have been started.
+ TransactionList::iterator pos;
+ EXPECT_NO_THROW(pos = update_mgr_->transactionListBegin());
+ while (pos != update_mgr_->transactionListEnd()) {
+ NameChangeTransactionPtr trans = (*pos).second;
+ ASSERT_EQ(dhcp_ddns::ST_PENDING, trans->getNcrStatus());
+ ASSERT_TRUE(trans->isModelRunning());
+ ++pos;
+ }
+
+ // Verify that invoking checkFinishedTransactions does not throw.
+ EXPECT_NO_THROW(update_mgr_->checkFinishedTransactions());
+
+ // Since nothing is running IOService, the all four transactions should
+ // still be in the list.
+ EXPECT_EQ(canned_count_, update_mgr_->getTransactionCount());
+
+ // Now "complete" two of the four.
+ // Simulate a successful completion.
+ completeTransaction(1, dhcp_ddns::ST_COMPLETED);
+
+ // Simulate a failed completion.
+ completeTransaction(3, dhcp_ddns::ST_FAILED);
+
+ // Verify that invoking checkFinishedTransactions does not throw.
+ EXPECT_NO_THROW(update_mgr_->checkFinishedTransactions());
+
+ // Verify that the list of transactions has decreased by two.
+ EXPECT_EQ(canned_count_ - 2, update_mgr_->getTransactionCount());
+
+ // Verify that the transaction list is correct.
+ EXPECT_TRUE(update_mgr_->hasTransaction(canned_ncrs_[0]->getDhcid()));
+ EXPECT_FALSE(update_mgr_->hasTransaction(canned_ncrs_[1]->getDhcid()));
+ EXPECT_TRUE(update_mgr_->hasTransaction(canned_ncrs_[2]->getDhcid()));
+ EXPECT_FALSE(update_mgr_->hasTransaction(canned_ncrs_[3]->getDhcid()));
+}
+
+/// @brief Tests D2UpdateManager's pickNextJob method.
+/// This test verifies that:
+/// 1. pickNextJob will select and make transactions from NCR queue.
+/// 2. Requests are removed from the queue once selected
+/// 3. Requests for DHCIDs with transactions already in progress are not
+/// selected.
+/// 4. Requests with no matching servers are removed from the queue and
+/// discarded.
+TEST_F(D2UpdateMgrTest, pickNextJob) {
+ // Ensure we have at least 4 canned requests with which to work.
+ ASSERT_TRUE(canned_count_ >= 4);
+
+ // Put each transaction on the queue.
+ for (int i = 0; i < canned_count_; i++) {
+ ASSERT_NO_THROW(queue_mgr_->enqueue(canned_ncrs_[i]));
+ }
+
+ // Invoke pickNextJob canned_count_ times which should create a
+ // transaction for each canned ncr.
+ for (int i = 0; i < canned_count_; i++) {
+ EXPECT_NO_THROW(update_mgr_->pickNextJob());
+ EXPECT_EQ(i + 1, update_mgr_->getTransactionCount());
+ EXPECT_TRUE(update_mgr_->hasTransaction(canned_ncrs_[i]->getDhcid()));
+ }
+
+ // Verify that the queue has been drained.
+ EXPECT_EQ(0, update_mgr_->getQueueCount());
+
+ // Now verify that a subsequent request for a DCHID for which a
+ // transaction is in progress, is not dequeued.
+ // First add the "subsequent" request.
+ dhcp_ddns::NameChangeRequestPtr
+ subsequent_ncr(new dhcp_ddns::NameChangeRequest(*(canned_ncrs_[2])));
+ EXPECT_NO_THROW(queue_mgr_->enqueue(subsequent_ncr));
+ EXPECT_EQ(1, update_mgr_->getQueueCount());
+
+ // Verify that invoking pickNextJob:
+ // 1. Does not throw
+ // 2. Does not make a new transaction
+ // 3. Does not dequeue the entry
+ EXPECT_NO_THROW(update_mgr_->pickNextJob());
+ EXPECT_EQ(canned_count_, update_mgr_->getTransactionCount());
+ EXPECT_EQ(1, update_mgr_->getQueueCount());
+
+ // Clear out the queue and transaction list.
+ queue_mgr_->clearQueue();
+ update_mgr_->clearTransactionList();
+
+ // Make a forward change NCR with an FQDN that has no forward match.
+ dhcp_ddns::NameChangeRequestPtr
+ bogus_ncr(new dhcp_ddns::NameChangeRequest(*(canned_ncrs_[0])));
+ bogus_ncr->setForwardChange(true);
+ bogus_ncr->setReverseChange(false);
+ bogus_ncr->setFqdn("bogus.forward.domain.com");
+
+ // Put it on the queue up
+ ASSERT_NO_THROW(queue_mgr_->enqueue(bogus_ncr));
+
+ // Verify that invoking pickNextJob:
+ // 1. Does not throw
+ // 2. Does not make a new transaction
+ // 3. Does dequeue the entry
+ EXPECT_NO_THROW(update_mgr_->pickNextJob());
+ EXPECT_EQ(0, update_mgr_->getTransactionCount());
+ EXPECT_EQ(0, update_mgr_->getQueueCount());
+
+ // Make a reverse change NCR with an FQDN that has no reverse match.
+ bogus_ncr.reset(new dhcp_ddns::NameChangeRequest(*(canned_ncrs_[0])));
+ bogus_ncr->setForwardChange(false);
+ bogus_ncr->setReverseChange(true);
+ bogus_ncr->setIpAddress("77.77.77.77");
+
+ // Verify that invoking pickNextJob:
+ // 1. does not throw
+ // 2. Does not make a new transaction
+ // 3. Does dequeue the entry
+ EXPECT_NO_THROW(update_mgr_->pickNextJob());
+ EXPECT_EQ(0, update_mgr_->getTransactionCount());
+ EXPECT_EQ(0, update_mgr_->getQueueCount());
+}
+
+/// @brief Tests D2UpdateManager's sweep method.
+/// Since sweep is primarily a wrapper around checkFinishedTransactions and
+/// pickNextJob, along with checks on maximum transaction limits, it mostly
+/// verifies that these three pieces work together to move process jobs.
+/// Most of what is tested here is tested above.
+TEST_F(D2UpdateMgrTest, sweep) {
+ // Ensure we have at least 4 canned requests with which to work.
+ ASSERT_TRUE(canned_count_ >= 4);
+
+ // Set max transactions to same as current transaction count.
+ EXPECT_NO_THROW(update_mgr_->setMaxTransactions(canned_count_));
+ EXPECT_EQ(canned_count_, update_mgr_->getMaxTransactions());
+
+ // Put each transaction on the queue.
+ for (int i = 0; i < canned_count_; i++) {
+ EXPECT_NO_THROW(queue_mgr_->enqueue(canned_ncrs_[i]));
+ }
+
+ // Invoke sweep canned_count_ times which should create a
+ // transaction for each canned ncr.
+ for (int i = 0; i < canned_count_; i++) {
+ EXPECT_NO_THROW(update_mgr_->sweep());
+ EXPECT_EQ(i + 1, update_mgr_->getTransactionCount());
+ EXPECT_TRUE(update_mgr_->hasTransaction(canned_ncrs_[i]->getDhcid()));
+ }
+
+ // Verify that the queue has been drained.
+ EXPECT_EQ(0, update_mgr_->getQueueCount());
+
+ // Verify max transactions can't be less than current transaction count.
+ EXPECT_THROW(update_mgr_->setMaxTransactions(1), D2UpdateMgrError);
+
+ // Queue up a request for a DHCID which has a transaction in progress.
+ dhcp_ddns::NameChangeRequestPtr
+ subsequent_ncr(new dhcp_ddns::NameChangeRequest(*(canned_ncrs_[2])));
+ EXPECT_NO_THROW(queue_mgr_->enqueue(subsequent_ncr));
+ EXPECT_EQ(1, update_mgr_->getQueueCount());
+
+ // Verify that invoking sweep, does not dequeue the job nor make a
+ // transaction for it.
+ EXPECT_NO_THROW(update_mgr_->sweep());
+ EXPECT_EQ(canned_count_, update_mgr_->getTransactionCount());
+ EXPECT_EQ(1, update_mgr_->getQueueCount());
+
+ // Mark the transaction complete.
+ completeTransaction(2, dhcp_ddns::ST_COMPLETED);
+
+ // Verify that invoking sweep, cleans up the completed transaction,
+ // dequeues the queued job and adds its transaction to the list.
+ EXPECT_NO_THROW(update_mgr_->sweep());
+ EXPECT_EQ(canned_count_, update_mgr_->getTransactionCount());
+ EXPECT_EQ(0, update_mgr_->getQueueCount());
+
+ // Queue up a request from a new DHCID.
+ dhcp_ddns::NameChangeRequestPtr
+ another_ncr(new dhcp_ddns::NameChangeRequest(*(canned_ncrs_[0])));
+ another_ncr->setDhcid("AABBCCDDEEFF");
+ EXPECT_NO_THROW(queue_mgr_->enqueue(another_ncr));
+ EXPECT_EQ(1, update_mgr_->getQueueCount());
+
+ // Verify that sweep does not dequeue the new request as we are at
+ // maximum transaction count.
+ EXPECT_NO_THROW(update_mgr_->sweep());
+ EXPECT_EQ(canned_count_, update_mgr_->getTransactionCount());
+ EXPECT_EQ(1, update_mgr_->getQueueCount());
+
+ // Set max transactions to same as current transaction count.
+ EXPECT_NO_THROW(update_mgr_->setMaxTransactions(canned_count_ + 1));
+
+ // Verify that invoking sweep, dequeues the request and creates
+ // a transaction for it.
+ EXPECT_NO_THROW(update_mgr_->sweep());
+ EXPECT_EQ(canned_count_ + 1, update_mgr_->getTransactionCount());
+ EXPECT_EQ(0, update_mgr_->getQueueCount());
+
+ // Verify that clearing transaction list works.
+ EXPECT_NO_THROW(update_mgr_->clearTransactionList());
+ EXPECT_EQ(0, update_mgr_->getTransactionCount());
+}
+
+/// @brief Tests integration of NameAddTransaction
+/// This test verifies that update manager can create and manage a
+/// NameAddTransaction from start to finish. It utilizes a fake server
+/// which responds to all requests sent with NOERROR, simulating a
+/// successful addition. The transaction processes both forward and
+/// reverse changes.
+TEST_F(D2UpdateMgrTest, addTransaction) {
+ // Put each transaction on the queue.
+ canned_ncrs_[0]->setChangeType(dhcp_ddns::CHG_ADD);
+ canned_ncrs_[0]->setReverseChange(true);
+ ASSERT_NO_THROW(queue_mgr_->enqueue(canned_ncrs_[0]));
+
+ // Call sweep once, this should:
+ // 1. Dequeue the request
+ // 2. Create the transaction
+ // 3. Start the transaction
+ ASSERT_NO_THROW(update_mgr_->sweep());
+
+ // Get a copy of the transaction.
+ TransactionList::iterator pos = update_mgr_->transactionListBegin();
+ ASSERT_TRUE (pos != update_mgr_->transactionListEnd());
+ NameChangeTransactionPtr trans = (*pos).second;
+ ASSERT_TRUE(trans);
+
+ // Verify the correct type of transaction was created.
+ NameAddTransaction* t = dynamic_cast<NameAddTransaction*>(trans.get());
+ ASSERT_TRUE(t);
+
+ // At this point the transaction should have constructed
+ // and sent the DNS request.
+ ASSERT_TRUE(trans->getCurrentServer());
+ ASSERT_TRUE(trans->isModelRunning());
+ ASSERT_EQ(1, trans->getUpdateAttempts());
+ ASSERT_EQ(StateModel::NOP_EVT, trans->getNextEvent());
+
+ // Create a server based on the transaction's current server, and
+ // start it listening.
+ FauxServer server(*io_service_, *(trans->getCurrentServer()));
+ server.receive(FauxServer::USE_RCODE, dns::Rcode::NOERROR());
+
+ // Run sweep and IO until everything is done.
+ processAll();
+
+ // Verify that model succeeded.
+ EXPECT_FALSE(trans->didModelFail());
+
+ // Both completion flags should be true.
+ EXPECT_TRUE(trans->getForwardChangeCompleted());
+ EXPECT_TRUE(trans->getReverseChangeCompleted());
+
+ // Verify that we went through success state.
+ EXPECT_EQ(NameChangeTransaction::PROCESS_TRANS_OK_ST,
+ trans->getPrevState());
+ EXPECT_EQ(NameChangeTransaction::UPDATE_OK_EVT,
+ trans->getLastEvent());
+}
+
+/// @brief Tests integration of NameRemoveTransaction
+/// This test verifies that update manager can create and manage a
+/// NameRemoveTransaction from start to finish. It utilizes a fake server
+/// which responds to all requests sent with NOERROR, simulating a
+/// successful addition. The transaction processes both forward and
+/// reverse changes.
+TEST_F(D2UpdateMgrTest, removeTransaction) {
+ // Put each transaction on the queue.
+ canned_ncrs_[0]->setChangeType(dhcp_ddns::CHG_REMOVE);
+ canned_ncrs_[0]->setReverseChange(true);
+ ASSERT_NO_THROW(queue_mgr_->enqueue(canned_ncrs_[0]));
+
+ // Call sweep once, this should:
+ // 1. Dequeue the request
+ // 2. Create the transaction
+ // 3. Start the transaction
+ ASSERT_NO_THROW(update_mgr_->sweep());
+
+ // Get a copy of the transaction.
+ TransactionList::iterator pos = update_mgr_->transactionListBegin();
+ ASSERT_TRUE (pos != update_mgr_->transactionListEnd());
+ NameChangeTransactionPtr trans = (*pos).second;
+ ASSERT_TRUE(trans);
+
+ // Verify the correct type of transaction was created.
+ NameRemoveTransaction* t = dynamic_cast<NameRemoveTransaction*>(trans.get());
+ ASSERT_TRUE(t);
+
+ // At this point the transaction should have constructed
+ // and sent the DNS request.
+ ASSERT_TRUE(trans->getCurrentServer());
+ ASSERT_TRUE(trans->isModelRunning());
+ ASSERT_EQ(1, trans->getUpdateAttempts());
+ ASSERT_EQ(StateModel::NOP_EVT, trans->getNextEvent());
+
+ // Create a server based on the transaction's current server,
+ // and start it listening.
+ FauxServer server(*io_service_, *(trans->getCurrentServer()));
+ server.receive(FauxServer::USE_RCODE, dns::Rcode::NOERROR());
+
+ // Run sweep and IO until everything is done.
+ processAll();
+
+ // Verify that model succeeded.
+ EXPECT_FALSE(trans->didModelFail());
+
+ // Both completion flags should be true.
+ EXPECT_TRUE(trans->getForwardChangeCompleted());
+ EXPECT_TRUE(trans->getReverseChangeCompleted());
+
+ // Verify that we went through success state.
+ EXPECT_EQ(NameChangeTransaction::PROCESS_TRANS_OK_ST,
+ trans->getPrevState());
+ EXPECT_EQ(NameChangeTransaction::UPDATE_OK_EVT,
+ trans->getLastEvent());
+}
+
+
+/// @brief Tests handling of a transaction which fails.
+/// This test verifies that update manager correctly concludes a transaction
+/// which fails to complete successfully. The failure simulated is repeated
+/// corrupt responses from the server, which causes an exhaustion of the
+/// available servers.
+TEST_F(D2UpdateMgrTest, errorTransaction) {
+ // Put each transaction on the queue.
+ ASSERT_NO_THROW(queue_mgr_->enqueue(canned_ncrs_[0]));
+
+ // Call sweep once, this should:
+ // 1. Dequeue the request
+ // 2. Create the transaction
+ // 3. Start the transaction
+ ASSERT_NO_THROW(update_mgr_->sweep());
+
+ // Get a copy of the transaction.
+ TransactionList::iterator pos = update_mgr_->transactionListBegin();
+ ASSERT_TRUE (pos != update_mgr_->transactionListEnd());
+ NameChangeTransactionPtr trans = (*pos).second;
+ ASSERT_TRUE(trans);
+
+ ASSERT_TRUE(trans->isModelRunning());
+ ASSERT_EQ(1, trans->getUpdateAttempts());
+ ASSERT_EQ(StateModel::NOP_EVT, trans->getNextEvent());
+ ASSERT_TRUE(trans->getCurrentServer());
+
+ // Create a server and start it listening.
+ FauxServer server(*io_service_, *(trans->getCurrentServer()));
+ server.receive(FauxServer::CORRUPT_RESP);
+
+ // Run sweep and IO until everything is done.
+ processAll();
+
+ // Verify that model succeeded.
+ EXPECT_FALSE(trans->didModelFail());
+
+ // Both completion flags should be false.
+ EXPECT_FALSE(trans->getForwardChangeCompleted());
+ EXPECT_FALSE(trans->getReverseChangeCompleted());
+
+ // Verify that we went through success state.
+ EXPECT_EQ(NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ trans->getPrevState());
+ EXPECT_EQ(NameChangeTransaction::NO_MORE_SERVERS_EVT,
+ trans->getLastEvent());
+
+
+}
+
+/// @brief Tests processing of multiple transactions.
+/// This test verifies that update manager can create and manage a multiple
+/// transactions, concurrently. It uses a fake server that responds to all
+/// requests sent with NOERROR, simulating successful DNS updates. The
+/// transactions are a mix of both adds and removes.
+TEST_F(D2UpdateMgrTest, multiTransaction) {
+ // Queue up all the requests.
+ int test_count = canned_count_;
+ for (int i = test_count; i > 0; i--) {
+ canned_ncrs_[i-1]->setReverseChange(true);
+ ASSERT_NO_THROW(queue_mgr_->enqueue(canned_ncrs_[i-1]));
+ }
+
+ // Create a server and start it listening. Note this relies on the fact
+ // that all of configured servers have the same address.
+ // and start it listening.
+ asiolink::IOAddress server_ip("127.0.0.1");
+ FauxServer server(*io_service_, server_ip, 5301);
+ server.receive(FauxServer::USE_RCODE, dns::Rcode::NOERROR());
+
+ // Run sweep and IO until everything is done.
+ processAll();
+
+ for (int i = 0; i < test_count; i++) {
+ EXPECT_EQ(dhcp_ddns::ST_COMPLETED, canned_ncrs_[i]->getStatus());
+ }
+}
+
+/// @brief Tests processing of multiple transactions.
+/// This test verifies that update manager can create and manage a multiple
+/// transactions, concurrently. It uses a fake server that responds to all
+/// requests sent with NOERROR, simulating successful DNS updates. The
+/// transactions are a mix of both adds and removes.
+TEST_F(D2UpdateMgrTest, multiTransactionTimeout) {
+ // Queue up all the requests.
+ int test_count = canned_count_;
+ for (int i = test_count; i > 0; i--) {
+ canned_ncrs_[i-1]->setReverseChange(true);
+ ASSERT_NO_THROW(queue_mgr_->enqueue(canned_ncrs_[i-1]));
+ }
+
+ // No server is running, so everything will time out.
+
+ // Run sweep and IO until everything is done.
+ processAll();
+
+ for (int i = 0; i < test_count; i++) {
+ EXPECT_EQ(dhcp_ddns::ST_FAILED, canned_ncrs_[i]->getStatus());
+ }
+}
+
+/// @brief Tests integration of SimpleAddTransaction
+/// This test verifies that update manager can create and manage a
+/// SimpleAddTransaction from start to finish. It utilizes a fake server
+/// which responds to all requests sent with NOERROR, simulating a
+/// successful addition. The transaction processes both forward and
+/// reverse changes.
+TEST_F(D2UpdateMgrTest, simpleAddTransaction) {
+ // Put each transaction on the queue.
+ canned_ncrs_[0]->setChangeType(dhcp_ddns::CHG_ADD);
+ canned_ncrs_[0]->setReverseChange(true);
+ canned_ncrs_[0]->setConflictResolution(false);
+ ASSERT_NO_THROW(queue_mgr_->enqueue(canned_ncrs_[0]));
+
+ // Call sweep once, this should:
+ // 1. Dequeue the request
+ // 2. Create the transaction
+ // 3. Start the transaction
+ ASSERT_NO_THROW(update_mgr_->sweep());
+
+ // Get a copy of the transaction.
+ TransactionList::iterator pos = update_mgr_->transactionListBegin();
+ ASSERT_TRUE (pos != update_mgr_->transactionListEnd());
+ NameChangeTransactionPtr trans = (*pos).second;
+ ASSERT_TRUE(trans);
+
+ // Verify the correct type of transaction was created.
+ SimpleAddTransaction* t = dynamic_cast<SimpleAddTransaction*>(trans.get());
+ ASSERT_TRUE(t);
+
+ // At this point the transaction should have constructed
+ // and sent the DNS request.
+ ASSERT_TRUE(trans->getCurrentServer());
+ ASSERT_TRUE(trans->isModelRunning());
+ ASSERT_EQ(1, trans->getUpdateAttempts());
+ ASSERT_EQ(StateModel::NOP_EVT, trans->getNextEvent());
+
+ // Create a server based on the transaction's current server, and
+ // start it listening.
+ FauxServer server(*io_service_, *(trans->getCurrentServer()));
+ server.receive(FauxServer::USE_RCODE, dns::Rcode::NOERROR());
+
+ // Run sweep and IO until everything is done.
+ processAll();
+
+ // Verify that model succeeded.
+ EXPECT_FALSE(trans->didModelFail());
+
+ // Both completion flags should be true.
+ EXPECT_TRUE(trans->getForwardChangeCompleted());
+ EXPECT_TRUE(trans->getReverseChangeCompleted());
+
+ // Verify that we went through success state.
+ EXPECT_EQ(NameChangeTransaction::PROCESS_TRANS_OK_ST,
+ trans->getPrevState());
+ EXPECT_EQ(NameChangeTransaction::UPDATE_OK_EVT,
+ trans->getLastEvent());
+}
+
+/// @brief Tests integration of SimpleRemoveTransaction
+/// This test verifies that update manager can create and manage a
+/// SimpleRemoveTransaction from start to finish. It utilizes a fake server
+/// which responds to all requests sent with NOERROR, simulating a
+/// successful addition. The transaction processes both forward and
+/// reverse changes.
+TEST_F(D2UpdateMgrTest, simpleRemoveTransaction) {
+ // Put each transaction on the queue.
+ canned_ncrs_[0]->setChangeType(dhcp_ddns::CHG_REMOVE);
+ canned_ncrs_[0]->setReverseChange(true);
+ canned_ncrs_[0]->setConflictResolution(false);
+ ASSERT_NO_THROW(queue_mgr_->enqueue(canned_ncrs_[0]));
+
+ // Call sweep once, this should:
+ // 1. Dequeue the request
+ // 2. Create the transaction
+ // 3. Start the transaction
+ ASSERT_NO_THROW(update_mgr_->sweep());
+
+ // Get a copy of the transaction.
+ TransactionList::iterator pos = update_mgr_->transactionListBegin();
+ ASSERT_TRUE (pos != update_mgr_->transactionListEnd());
+ NameChangeTransactionPtr trans = (*pos).second;
+ ASSERT_TRUE(trans);
+
+ // Verify the correct type of transaction was created.
+ SimpleRemoveTransaction* t = dynamic_cast<SimpleRemoveTransaction*>(trans.get());
+ ASSERT_TRUE(t);
+
+ // At this point the transaction should have constructed
+ // and sent the DNS request.
+ ASSERT_TRUE(trans->getCurrentServer());
+ ASSERT_TRUE(trans->isModelRunning());
+ ASSERT_EQ(1, trans->getUpdateAttempts());
+ ASSERT_EQ(StateModel::NOP_EVT, trans->getNextEvent());
+
+ // Create a server based on the transaction's current server,
+ // and start it listening.
+ FauxServer server(*io_service_, *(trans->getCurrentServer()));
+ server.receive(FauxServer::USE_RCODE, dns::Rcode::NOERROR());
+
+ // Run sweep and IO until everything is done.
+ processAll();
+
+ // Verify that model succeeded.
+ EXPECT_FALSE(trans->didModelFail());
+
+ // Both completion flags should be true.
+ EXPECT_TRUE(trans->getForwardChangeCompleted());
+ EXPECT_TRUE(trans->getReverseChangeCompleted());
+
+ // Verify that we went through success state.
+ EXPECT_EQ(NameChangeTransaction::PROCESS_TRANS_OK_ST,
+ trans->getPrevState());
+ EXPECT_EQ(NameChangeTransaction::UPDATE_OK_EVT,
+ trans->getLastEvent());
+}
+
+}
diff --git a/src/bin/d2/tests/get_config_unittest.cc b/src/bin/d2/tests/get_config_unittest.cc
new file mode 100644
index 0000000..0936e82
--- /dev/null
+++ b/src/bin/d2/tests/get_config_unittest.cc
@@ -0,0 +1,293 @@
+// Copyright (C) 2017-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <cc/command_interpreter.h>
+#include <cc/data.h>
+#include <d2/parser_context.h>
+#include <d2srv/d2_cfg_mgr.h>
+#include <d2srv/d2_config.h>
+#include <process/testutils/d_test_stubs.h>
+#include <testutils/user_context_utils.h>
+#include <gtest/gtest.h>
+
+#include <iostream>
+#include <fstream>
+#include <string>
+#include <sstream>
+
+#include "test_data_files_config.h"
+#include "test_callout_libraries.h"
+
+using namespace isc::config;
+using namespace isc::d2;
+using namespace isc::data;
+using namespace isc::process;
+using namespace isc::test;
+
+namespace {
+
+/// @name How to generate the testdata/get_config.json file
+///
+/// Define GENERATE_ACTION and recompile. Run d2_unittests on
+/// D2GetConfigTest redirecting the standard error to a temporary
+/// file, e.g. by
+/// @code
+/// ./d2_unittests --gtest_filter="D2GetConfig*" > /dev/null 2> u
+/// @endcode
+///
+/// Update testdata/get_config.json using the temporary file content,
+/// recompile without GENERATE_ACTION.
+
+/// @brief the generate action
+/// false means do nothing, true means unparse extracted configurations
+#ifdef GENERATE_ACTION
+const bool generate_action = true;
+#else
+const bool generate_action = false;
+#endif
+
+/// @brief Read a file into a string
+std::string
+readFile(const std::string& file_path) {
+ std::ifstream ifs(file_path);
+ if (!ifs.is_open()) {
+ ADD_FAILURE() << "readFile cannot open " << file_path;
+ isc_throw(isc::Unexpected, "readFile cannot open " << file_path);
+ }
+ std::string lines;
+ std::string line;
+ while (std::getline(ifs, line)) {
+ lines += line + "\n";
+ }
+ ifs.close();
+ return (lines);
+}
+
+/// @brief Runs parser in JSON mode
+ElementPtr
+parseJSON(const std::string& in, bool verbose = false) {
+ try {
+ D2ParserContext ctx;
+ return (ctx.parseString(in, D2ParserContext::PARSER_JSON));
+ } catch (const std::exception& ex) {
+ if (verbose) {
+ std::cout << "EXCEPTION: " << ex.what() << std::endl;
+ }
+ throw;
+ }
+}
+
+/// @brief Runs parser in DHCPDDNS mode
+ElementPtr
+parseDHCPDDNS(const std::string& in, bool verbose = false) {
+ try {
+ D2ParserContext ctx;
+ return (ctx.parseString(in, D2ParserContext::PARSER_DHCPDDNS));
+ } catch (const std::exception& ex) {
+ if (verbose) {
+ std::cout << "EXCEPTION: " << ex.what() << std::endl;
+ }
+ throw;
+ }
+}
+
+/// @brief Replace the library path
+void pathReplacer(ConstElementPtr d2_cfg) {
+ ConstElementPtr hooks_libs = d2_cfg->get("hooks-libraries");
+ if (!hooks_libs || hooks_libs->empty()) {
+ return;
+ }
+ ElementPtr first_lib = hooks_libs->getNonConst(0);
+ std::string lib_path(CALLOUT_LIBRARY);
+ first_lib->set("library", Element::create(lib_path));
+}
+
+}
+
+/// Test fixture class
+class D2GetConfigTest : public ConfigParseTest {
+public:
+ D2GetConfigTest()
+ : rcode_(-1) {
+ srv_.reset(new D2CfgMgr());
+ // Enforce not verbose mode.
+ Daemon::setVerbose(false);
+ // Create fresh context.
+ resetConfiguration();
+ }
+
+ ~D2GetConfigTest() {
+ resetConfiguration();
+ }
+
+ /// @brief Parse and Execute configuration
+ ///
+ /// Parses a configuration and executes a configuration of the server.
+ /// If the operation fails, the current test will register a failure.
+ ///
+ /// @param config Configuration to parse
+ /// @param operation Operation being performed. In the case of an error,
+ /// the error text will include the string "unable to <operation>.".
+ ///
+ /// @return true if the configuration succeeded, false if not.
+ bool
+ executeConfiguration(const std::string& config, const char* operation) {
+ // try JSON parser
+ ConstElementPtr json;
+ try {
+ json = parseJSON(config, true);
+ } catch (const std::exception& ex) {
+ ADD_FAILURE() << "invalid JSON for " << operation
+ << " failed with " << ex.what()
+ << " on\n" << config << "\n";
+ return (false);
+ }
+
+ // try DHCPDDNS parser
+ try {
+ json = parseDHCPDDNS(config, true);
+ } catch (...) {
+ ADD_FAILURE() << "parsing failed for " << operation
+ << " on\n" << prettyPrint(json) << "\n";
+ return (false);
+ }
+
+ // get DhcpDdns element
+ ConstElementPtr d2 = json->get("DhcpDdns");
+ if (!d2) {
+ ADD_FAILURE() << "cannot get DhcpDdns for " << operation
+ << " on\n" << prettyPrint(json) << "\n";
+ return (false);
+ }
+
+ // update hooks-libraries
+ pathReplacer(d2);
+
+ // try DHCPDDNS configure
+ ConstElementPtr status;
+ try {
+ status = srv_->simpleParseConfig(d2, false);
+ } catch (const std::exception& ex) {
+ ADD_FAILURE() << "configure for " << operation
+ << " failed with " << ex.what()
+ << " on\n" << prettyPrint(json) << "\n";
+ return (false);
+ }
+
+ // The status object must not be NULL
+ if (!status) {
+ ADD_FAILURE() << "configure for " << operation
+ << " returned null on\n"
+ << prettyPrint(json) << "\n";
+ return (false);
+ }
+
+ // Returned value should be 0 (configuration success)
+ comment_ = parseAnswer(rcode_, status);
+ if (rcode_ != 0) {
+ string reason = "";
+ if (comment_) {
+ reason = string(" (") + comment_->stringValue() + string(")");
+ }
+ ADD_FAILURE() << "configure for " << operation
+ << " returned error code "
+ << rcode_ << reason << " on\n"
+ << prettyPrint(json) << "\n";
+ return (false);
+ }
+ return (true);
+ }
+
+ /// @brief Reset configuration database.
+ ///
+ /// This function resets configuration data base by
+ /// removing control sockets, hooks, etc. Reset must
+ /// be performed after each test to make sure that
+ /// contents of the database do not affect result of
+ /// subsequent tests.
+ void resetConfiguration() {
+ string config = "{ \"DhcpDdns\": {"
+ " \"ip-address\": \"127.0.0.1\","
+ " \"port\": 53001,"
+ " \"dns-server-timeout\": 100,"
+ " \"ncr-protocol\": \"UDP\","
+ " \"ncr-format\": \"JSON\","
+ " \"tsig-keys\": [ ],"
+ " \"forward-ddns\": { },"
+ " \"reverse-ddns\": { } } }";
+ EXPECT_TRUE(executeConfiguration(config, "reset config"));
+ }
+
+ boost::scoped_ptr<D2CfgMgr> srv_; ///< D2 server under test
+ int rcode_; ///< Return code from element parsing
+ ConstElementPtr comment_; ///< Reason for parse fail
+};
+
+/// Test a configuration
+TEST_F(D2GetConfigTest, sample1) {
+
+ // get the sample1 configuration
+ std::string sample1_file = string(CFG_EXAMPLES) + "/" + "sample1.json";
+ std::string config;
+ ASSERT_NO_THROW(config = readFile(sample1_file));
+
+ // get the expected configuration
+ std::string expected_file =
+ std::string(D2_TEST_DATA_DIR) + "/" + "get_config.json";
+ std::string expected;
+ ASSERT_NO_THROW(expected = readFile(expected_file));
+
+ // execute the sample configuration
+ ASSERT_TRUE(executeConfiguration(config, "sample1 config"));
+
+ // unparse it
+ D2CfgContextPtr context = srv_->getD2CfgContext();
+ ConstElementPtr unparsed;
+ ASSERT_NO_THROW(unparsed = context->toElement());
+
+ // dump if wanted else check
+ if (generate_action) {
+ std::cerr << "/ Generated Configuration (remove this line)\n";
+ ASSERT_NO_THROW(expected = prettyPrint(unparsed));
+ prettyPrint(unparsed, std::cerr, 0, 4);
+ std::cerr << "\n";
+ } else {
+ // get the expected config using the d2 syntax parser
+ ElementPtr jsond;
+ ASSERT_NO_THROW(jsond = parseDHCPDDNS(expected, true));
+ // get the expected config using the generic JSON syntax parser
+ ElementPtr jsonj;
+ ASSERT_NO_THROW(jsonj = parseJSON(expected));
+ // the generic JSON parser does not handle comments
+ EXPECT_TRUE(isEquivalent(jsond, moveComments(jsonj)));
+ // replace the path by its actual value
+ ConstElementPtr d2;
+ ASSERT_NO_THROW(d2 = jsonj->get("DhcpDdns"));
+ ASSERT_TRUE(d2);
+ pathReplacer(d2);
+ // check that unparsed and expected values match
+ EXPECT_TRUE(isEquivalent(unparsed, jsonj));
+ // check on pretty prints too
+ std::string current = prettyPrint(unparsed, 0, 4);
+ std::string expected2 = prettyPrint(jsonj, 0, 4);
+ EXPECT_EQ(expected2, current);
+ if (expected2 != current) {
+ expected = current + "\n";
+ }
+ }
+
+ // execute the d2 configuration
+ EXPECT_TRUE(executeConfiguration(expected, "unparsed config"));
+
+ // is it a fixed point?
+ D2CfgContextPtr context2 = srv_->getD2CfgContext();
+ ConstElementPtr unparsed2;
+ ASSERT_NO_THROW(unparsed2 = context2->toElement());
+ ASSERT_TRUE(unparsed2);
+ EXPECT_TRUE(isEquivalent(unparsed, unparsed2));
+}
diff --git a/src/bin/d2/tests/nc_add_unittests.cc b/src/bin/d2/tests/nc_add_unittests.cc
new file mode 100644
index 0000000..3f36b46
--- /dev/null
+++ b/src/bin/d2/tests/nc_add_unittests.cc
@@ -0,0 +1,1713 @@
+// Copyright (C) 2013-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <asiolink/io_service.h>
+#include <d2/nc_add.h>
+#include <d2srv/d2_cfg_mgr.h>
+#include <d2srv/testutils/nc_test_utils.h>
+#include <dns/messagerenderer.h>
+
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::d2;
+using namespace isc::util;
+
+namespace {
+
+/// @brief Test class derived from NameAddTransaction to provide visibility
+// to protected methods.
+class NameAddStub : public NameAddTransaction {
+public:
+ NameAddStub(asiolink::IOServicePtr& io_service,
+ dhcp_ddns::NameChangeRequestPtr& ncr,
+ DdnsDomainPtr& forward_domain,
+ DdnsDomainPtr& reverse_domain,
+ D2CfgMgrPtr& cfg_mgr)
+ : NameAddTransaction(io_service, ncr, forward_domain, reverse_domain,
+ cfg_mgr),
+ simulate_send_exception_(false),
+ simulate_build_request_exception_(false) {
+ }
+
+ virtual ~NameAddStub() {
+ }
+
+ /// @brief Simulates sending update requests to the DNS server
+ ///
+ /// This method simulates the initiation of an asynchronous send of
+ /// a DNS update request. It overrides the actual sendUpdate method in
+ /// the base class, thus avoiding an actual send, yet still increments
+ /// the update attempt count and posts a next event of NOP_EVT.
+ ///
+ /// It will also simulate an exception-based failure of sendUpdate, if
+ /// the simulate_send_exception_ flag is true.
+ ///
+ /// @param comment Parameter is unused, but present in base class method.
+ ///
+ virtual void sendUpdate(const std::string& /*comment*/) {
+ if (simulate_send_exception_) {
+ // Make the flag a one-shot by resetting it.
+ simulate_send_exception_ = false;
+ // Transition to failed.
+ transition(PROCESS_TRANS_FAILED_ST, UPDATE_FAILED_EVT);
+ return;
+ }
+
+ // Update send attempt count and post a NOP_EVT.
+ setUpdateAttempts(getUpdateAttempts() + 1);
+ postNextEvent(StateModel::NOP_EVT);
+ }
+
+ /// @brief Prepares the initial D2UpdateMessage
+ ///
+ /// This method overrides the NameChangeTransaction implementation to
+ /// provide the ability to simulate an exception throw in the build
+ /// request logic.
+ /// If the one-shot flag, simulate_build_request_exception_ is true,
+ /// this method will throw an exception, otherwise it will invoke the
+ /// base class method, providing normal functionality.
+ ///
+ /// For parameter description see the NameChangeTransaction implementation.
+ virtual D2UpdateMessagePtr prepNewRequest(DdnsDomainPtr domain) {
+ if (simulate_build_request_exception_) {
+ simulate_build_request_exception_ = false;
+ isc_throw (NameAddTransactionError,
+ "Simulated build requests exception");
+ }
+
+ return (NameChangeTransaction::prepNewRequest(domain));
+ }
+
+ /// @brief Simulates receiving a response
+ ///
+ /// This method simulates the completion of a DNSClient send. This allows
+ /// the state handler logic devoted to dealing with IO completion to be
+ /// fully exercised without requiring any actual IO. The two primary
+ /// pieces of information gleaned from IO completion are the DNSClient
+ /// status which indicates whether or not the IO exchange was successful
+ /// and the rcode, which indicates the server's reaction to the request.
+ ///
+ /// This method updates the transaction's DNS status value to that of the
+ /// given parameter, and then constructs and DNS update response message
+ /// with the given rcode value. To complete the simulation it then posts
+ /// a next event of IO_COMPLETED_EVT.
+ ///
+ /// @param status simulated DNSClient status
+ /// @param rcode simulated server response code
+ void fakeResponse(const DNSClient::Status& status,
+ const dns::Rcode& rcode) {
+ // Set the DNS update status. This is normally set in
+ // DNSClient IO completion handler.
+ setDnsUpdateStatus(status);
+
+ // Construct an empty message with the given Rcode.
+ D2UpdateMessagePtr msg(new D2UpdateMessage(D2UpdateMessage::OUTBOUND));
+ msg->setRcode(rcode);
+
+ // Set the update response to the message.
+ setDnsUpdateResponse(msg);
+
+ // Post the IO completion event.
+ postNextEvent(NameChangeTransaction::IO_COMPLETED_EVT);
+ }
+
+ /// @brief Selects the first forward server.
+ /// Some state handlers require a server to have been selected.
+ /// This selects a server without going through the state
+ /// transition(s) to do so.
+ bool selectFwdServer() {
+ if (getForwardDomain()) {
+ initServerSelection(getForwardDomain());
+ selectNextServer();
+ return (getCurrentServer().get() != 0);
+ }
+
+ return (false);
+ }
+
+ /// @brief Selects the first reverse server.
+ /// Some state handlers require a server to have been selected.
+ /// This selects a server without going through the state
+ /// transition(s) to do so.
+ bool selectRevServer() {
+ if (getReverseDomain()) {
+ initServerSelection(getReverseDomain());
+ selectNextServer();
+ return (getCurrentServer().get() != 0);
+ }
+
+ return (false);
+ }
+
+ /// @brief One-shot flag which will simulate sendUpdate failure if true.
+ bool simulate_send_exception_;
+
+ /// @brief One-shot flag which will simulate an exception when sendUpdate
+ /// failure if true.
+ bool simulate_build_request_exception_;
+
+ using StateModel::postNextEvent;
+ using StateModel::setState;
+ using StateModel::initDictionaries;
+ using NameAddTransaction::defineEvents;
+ using NameAddTransaction::verifyEvents;
+ using NameAddTransaction::defineStates;
+ using NameAddTransaction::verifyStates;
+ using NameAddTransaction::readyHandler;
+ using NameAddTransaction::selectingFwdServerHandler;
+ using NameAddTransaction::getCurrentServer;
+ using NameAddTransaction::addingFwdAddrsHandler;
+ using NameAddTransaction::setDnsUpdateStatus;
+ using NameAddTransaction::replacingFwdAddrsHandler;
+ using NameAddTransaction::selectingRevServerHandler;
+ using NameAddTransaction::replacingRevPtrsHandler;
+ using NameAddTransaction::processAddOkHandler;
+ using NameAddTransaction::processAddFailedHandler;
+ using NameAddTransaction::buildAddFwdAddressRequest;
+ using NameAddTransaction::buildReplaceFwdAddressRequest;
+ using NameAddTransaction::buildReplaceRevPtrsRequest;
+};
+
+typedef boost::shared_ptr<NameAddStub> NameAddStubPtr;
+
+/// @brief Test fixture for testing NameAddTransaction
+///
+/// Note this class uses NameAddStub class to exercise non-public
+/// aspects of NameAddTransaction.
+class NameAddTransactionTest : public TransactionTest {
+public:
+
+ NameAddTransactionTest() {
+ }
+
+ virtual ~NameAddTransactionTest() {
+ }
+
+ /// @brief Creates a transaction which requests an IPv4 DNS update.
+ ///
+ /// The transaction is constructed around a predefined (i.e. "canned")
+ /// IPv4 NameChangeRequest. The request has both forward and reverse DNS
+ /// changes requested. Based upon the change mask, the transaction
+ /// will have either the forward, reverse, or both domains populated.
+ ///
+ /// @param change_mask determines which change directions are requested
+ NameAddStubPtr makeTransaction4(int change_mask = FWD_AND_REV_CHG) {
+ // Creates IPv4 remove request, forward, and reverse domains.
+ setupForIPv4Transaction(dhcp_ddns::CHG_ADD, change_mask);
+
+ // Now create the test transaction as would occur in update manager.
+ return (NameAddStubPtr(new NameAddStub(io_service_, ncr_,
+ forward_domain_,
+ reverse_domain_, cfg_mgr_)));
+ }
+
+ /// @brief Creates a transaction which requests an IPv6 DNS update.
+ ///
+ /// The transaction is constructed around a predefined (i.e. "canned")
+ /// IPv6 NameChangeRequest. The request has both forward and reverse DNS
+ /// changes requested. Based upon the change mask, the transaction
+ /// will have either the forward, reverse, or both domains populated.
+ ///
+ /// @param change_mask determines which change directions are requested
+ NameAddStubPtr makeTransaction6(int change_mask = FWD_AND_REV_CHG) {
+ // Creates IPv6 remove request, forward, and reverse domains.
+ setupForIPv6Transaction(dhcp_ddns::CHG_ADD, change_mask);
+
+ // Now create the test transaction as would occur in update manager.
+ return (NameAddStubPtr(new NameAddStub(io_service_, ncr_,
+ forward_domain_,
+ reverse_domain_,
+ cfg_mgr_)));
+ }
+
+ /// @brief Create a test transaction at a known point in the state model.
+ ///
+ /// Method prepares a new test transaction and sets its state and next
+ /// event values to those given. This makes the transaction appear to
+ /// be at that point in the state model without having to transition it
+ /// through prerequisite states. It also provides the ability to set
+ /// which change directions are requested: forward change only, reverse
+ /// change only, or both.
+ ///
+ /// @param state value to set as the current state
+ /// @param event value to post as the next event
+ /// @param change_mask determines which change directions are requested
+ /// @param family selects between an IPv4 (AF_INET) and IPv6 (AF_INET6)
+ /// transaction.
+ NameAddStubPtr prepHandlerTest(unsigned int state, unsigned int event,
+ unsigned int change_mask = FWD_AND_REV_CHG,
+ short family = AF_INET) {
+ NameAddStubPtr name_add = (family == AF_INET ?
+ makeTransaction4(change_mask) :
+ makeTransaction4(change_mask));
+ name_add->initDictionaries();
+ name_add->postNextEvent(event);
+ name_add->setState(state);
+ return (name_add);
+ }
+};
+
+/// @brief Tests NameAddTransaction construction.
+/// This test verifies that:
+/// 1. Construction with invalid type of request
+/// 2. Valid construction functions properly
+TEST(NameAddTransaction, construction) {
+ asiolink::IOServicePtr io_service(new isc::asiolink::IOService());
+ D2CfgMgrPtr cfg_mgr(new D2CfgMgr());
+
+ const char* msg_str =
+ "{"
+ " \"change-type\" : 1 , "
+ " \"forward-change\" : true , "
+ " \"reverse-change\" : true , "
+ " \"fqdn\" : \"example.com.\" , "
+ " \"ip-address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"0102030405060708\" , "
+ " \"lease-expires-on\" : \"20130121132405\" , "
+ " \"lease-length\" : 1300, "
+ " \"use-conflict-resolution\" : true "
+ "}";
+
+ dhcp_ddns::NameChangeRequestPtr ncr;
+ DnsServerInfoStoragePtr servers;
+ DdnsDomainPtr forward_domain;
+ DdnsDomainPtr reverse_domain;
+ DdnsDomainPtr empty_domain;
+
+ ASSERT_NO_THROW(ncr = dhcp_ddns::NameChangeRequest::fromJSON(msg_str));
+ ASSERT_NO_THROW(forward_domain.reset(new DdnsDomain("*", servers)));
+ ASSERT_NO_THROW(reverse_domain.reset(new DdnsDomain("*", servers)));
+
+ // Verify that construction with wrong change type fails.
+ EXPECT_THROW(NameAddTransaction(io_service, ncr,
+ forward_domain, reverse_domain, cfg_mgr),
+ NameAddTransactionError);
+
+ // Verify that a valid construction attempt works.
+ ncr->setChangeType(isc::dhcp_ddns::CHG_ADD);
+ EXPECT_NO_THROW(NameAddTransaction(io_service, ncr,
+ forward_domain, reverse_domain,
+ cfg_mgr));
+}
+
+/// @brief Tests event and state dictionary construction and verification.
+TEST_F(NameAddTransactionTest, dictionaryCheck) {
+ NameAddStubPtr name_add;
+ ASSERT_NO_THROW(name_add = makeTransaction4());
+ // Verify that the event and state dictionary validation fails prior
+ // dictionary construction.
+ ASSERT_THROW(name_add->verifyEvents(), StateModelError);
+ ASSERT_THROW(name_add->verifyStates(), StateModelError);
+
+ // Construct both dictionaries.
+ ASSERT_NO_THROW(name_add->defineEvents());
+ ASSERT_NO_THROW(name_add->defineStates());
+
+ // Verify both event and state dictionaries now pass validation.
+ ASSERT_NO_THROW(name_add->verifyEvents());
+ ASSERT_NO_THROW(name_add->verifyStates());
+}
+
+/// @brief Tests construction of a DNS update request for adding a forward
+/// dns entry.
+TEST_F(NameAddTransactionTest, buildForwardAdd) {
+ // Create a IPv4 forward add transaction.
+ // Verify the request builds without error.
+ // and then verify the request contents.
+ NameAddStubPtr name_add;
+ ASSERT_NO_THROW(name_add = makeTransaction4());
+ ASSERT_NO_THROW(name_add->buildAddFwdAddressRequest());
+ checkAddFwdAddressRequest(*name_add);
+
+ // Create a IPv6 forward add transaction.
+ // Verify the request builds without error.
+ // and then verify the request contents.
+ ASSERT_NO_THROW(name_add = makeTransaction6());
+ ASSERT_NO_THROW(name_add->buildAddFwdAddressRequest());
+ checkAddFwdAddressRequest(*name_add);
+}
+
+/// @brief Tests construction of a DNS update request for replacing a forward
+/// dns entry.
+TEST_F(NameAddTransactionTest, buildReplaceFwdAddressRequest) {
+ // Create a IPv4 forward replace transaction.
+ // Verify the request builds without error.
+ // and then verify the request contents.
+ NameAddStubPtr name_add;
+ ASSERT_NO_THROW(name_add = makeTransaction4());
+ ASSERT_NO_THROW(name_add->buildReplaceFwdAddressRequest());
+ checkReplaceFwdAddressRequest(*name_add);
+
+ // Create a IPv6 forward replace transaction.
+ // Verify the request builds without error.
+ // and then verify the request contents.
+ ASSERT_NO_THROW(name_add = makeTransaction6());
+ ASSERT_NO_THROW(name_add->buildReplaceFwdAddressRequest());
+ checkReplaceFwdAddressRequest(*name_add);
+}
+
+/// @brief Tests the construction of a DNS update request for replacing a
+/// reverse dns entry.
+TEST_F(NameAddTransactionTest, buildReplaceRevPtrsRequest) {
+ // Create a IPv4 reverse replace transaction.
+ // Verify the request builds without error.
+ // and then verify the request contents.
+ NameAddStubPtr name_add;
+ ASSERT_NO_THROW(name_add = makeTransaction4());
+ ASSERT_NO_THROW(name_add->buildReplaceRevPtrsRequest());
+ checkReplaceRevPtrsRequest(*name_add);
+
+ // Create a IPv6 reverse replace transaction.
+ // Verify the request builds without error.
+ // and then verify the request contents.
+ ASSERT_NO_THROW(name_add = makeTransaction6());
+ ASSERT_NO_THROW(name_add->buildReplaceRevPtrsRequest());
+ checkReplaceRevPtrsRequest(*name_add);
+}
+
+// Tests the readyHandler functionality.
+// It verifies behavior for the following scenarios:
+//
+// 1. Posted event is START_EVT and request includes only a forward change
+// 2. Posted event is START_EVT and request includes both a forward and a
+// reverse change
+// 3. Posted event is START_EVT and request includes only a reverse change
+// 4. Posted event is invalid
+//
+TEST_F(NameAddTransactionTest, readyHandler) {
+ NameAddStubPtr name_add;
+
+ // Create a transaction which includes only a forward change.
+ ASSERT_NO_THROW(name_add =
+ prepHandlerTest(NameChangeTransaction::READY_ST,
+ StateModel::START_EVT, FORWARD_CHG));
+ // Run readyHandler.
+ EXPECT_NO_THROW(name_add->readyHandler());
+
+ // Verify that a request requiring only a forward change, transitions to
+ // selecting a forward server.
+ EXPECT_EQ(NameChangeTransaction::SELECTING_FWD_SERVER_ST,
+ name_add->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::SELECT_SERVER_EVT,
+ name_add->getNextEvent());
+
+
+ // Create a transaction which includes both a forward and a reverse change.
+ ASSERT_NO_THROW(name_add =
+ prepHandlerTest(NameChangeTransaction::READY_ST,
+ StateModel::START_EVT, FWD_AND_REV_CHG));
+ // Run readyHandler.
+ EXPECT_NO_THROW(name_add->readyHandler());
+
+ // Verify that a request requiring both forward and reverse, starts with
+ // the forward change by transitioning to selecting a forward server.
+ EXPECT_EQ(NameChangeTransaction::SELECTING_FWD_SERVER_ST,
+ name_add->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::SELECT_SERVER_EVT,
+ name_add->getNextEvent());
+
+
+ // Create and prep a reverse only transaction.
+ ASSERT_NO_THROW(name_add =
+ prepHandlerTest(NameChangeTransaction::READY_ST,
+ StateModel::START_EVT, REVERSE_CHG));
+ // Run readyHandler.
+ EXPECT_NO_THROW(name_add->readyHandler());
+
+ // Verify that a request requiring only a reverse change, transitions to
+ // selecting a reverse server.
+ EXPECT_EQ(NameChangeTransaction::SELECTING_REV_SERVER_ST,
+ name_add->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::SELECT_SERVER_EVT,
+ name_add->getNextEvent());
+
+
+ // Create and prep transaction, poised to run the handler but with an
+ // invalid event.
+ ASSERT_NO_THROW(name_add =
+ prepHandlerTest(NameChangeTransaction::READY_ST,
+ StateModel::NOP_EVT));
+
+ // Running the readyHandler should throw.
+ EXPECT_THROW(name_add->readyHandler(), NameAddTransactionError);
+}
+
+// Tests the selectingFwdServerHandler functionality.
+// It verifies behavior for the following scenarios:
+//
+// 1. Posted event is SELECT_SERVER_EVT
+// 2. Posted event is SERVER_IO_ERROR_EVT
+// 3. Posted event is invalid
+//
+TEST_F(NameAddTransactionTest, selectingFwdServerHandler) {
+ NameAddStubPtr name_add;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(name_add =
+ prepHandlerTest(NameChangeTransaction::
+ SELECTING_FWD_SERVER_ST,
+ NameChangeTransaction::SELECT_SERVER_EVT));
+
+ // Call selectingFwdServerHandler enough times to select all of the
+ // servers in it's current domain. The first time, it will be with
+ // next event of SELECT_SERVER_EVT. Thereafter it will be with a next
+ // event of SERVER_IO_ERROR_EVT.
+ int num_servers = name_add->getForwardDomain()->getServers()->size();
+ for (int i = 0; i < num_servers; ++i) {
+ // Run selectingFwdServerHandler.
+ ASSERT_NO_THROW(name_add->selectingFwdServerHandler())
+ << " num_servers: " << num_servers
+ << " selections: " << i;
+
+ // Verify that a server was selected.
+ ASSERT_TRUE(name_add->getCurrentServer())
+ << " num_servers: " << num_servers << " selections: " << i;
+
+ // Verify that we transitioned correctly.
+ ASSERT_EQ(NameAddTransaction::ADDING_FWD_ADDRS_ST,
+ name_add->getCurrState())
+ << " num_servers: " << num_servers << " selections: " << i;
+ ASSERT_EQ(NameChangeTransaction::SERVER_SELECTED_EVT,
+ name_add->getNextEvent())
+ << " num_servers: " << num_servers << " selections: " << i;
+
+ // Post a server IO error event. This simulates an IO error occurring
+ // and a need to select the new server.
+ ASSERT_NO_THROW(name_add->postNextEvent(NameChangeTransaction::
+ SERVER_IO_ERROR_EVT))
+ << " num_servers: " << num_servers
+ << " selections: " << i;
+ }
+
+ // We should have exhausted the list of servers. Processing another
+ // SERVER_IO_ERROR_EVT should transition us to failure.
+ EXPECT_NO_THROW(name_add->selectingFwdServerHandler());
+ EXPECT_EQ(NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ name_add->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::NO_MORE_SERVERS_EVT,
+ name_add->getNextEvent());
+
+ // Create and prep transaction, poised to run the handler but with an
+ // invalid event.
+ ASSERT_NO_THROW(name_add =
+ prepHandlerTest(NameChangeTransaction::
+ SELECTING_FWD_SERVER_ST,
+ StateModel::NOP_EVT));
+
+ // Running the handler should throw.
+ EXPECT_THROW(name_add->selectingFwdServerHandler(),
+ NameAddTransactionError);
+}
+
+// ************************ addingFwdAddrHandler Tests *****************
+
+// Tests that addingFwdAddrsHandler rejects invalid events.
+TEST_F(NameAddTransactionTest, addingFwdAddrsHandler_InvalidEvent) {
+ NameAddStubPtr name_add;
+ // Create and prep a transaction, poised to run the handler but with
+ // an invalid event.
+ ASSERT_NO_THROW(name_add =
+ prepHandlerTest(NameAddTransaction::ADDING_FWD_ADDRS_ST,
+ NameChangeTransaction::
+ StateModel::NOP_EVT));
+
+ // Running the handler should throw.
+ EXPECT_THROW(name_add->addingFwdAddrsHandler(),
+ NameAddTransactionError);
+}
+
+// Tests addingFwdAddrsHandler with the following scenario:
+//
+// The request includes only a forward change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The update request is sent without error.
+// A server response is received which indicates successful update
+//
+TEST_F(NameAddTransactionTest, addingFwdAddrsHandler_FwdOnlyAddOK) {
+ NameAddStubPtr name_add;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(name_add =
+ prepHandlerTest(NameAddTransaction::ADDING_FWD_ADDRS_ST,
+ NameChangeTransaction::
+ SERVER_SELECTED_EVT, FORWARD_CHG));
+
+ // Should not be an update message yet.
+ D2UpdateMessagePtr update_msg = name_add->getDnsUpdateRequest();
+ ASSERT_FALSE(update_msg);
+
+ // At this point completion flags should be false.
+ EXPECT_FALSE(name_add->getForwardChangeCompleted());
+ EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+ // Run addingFwdAddrsHandler to construct and send the request.
+ EXPECT_NO_THROW(name_add->addingFwdAddrsHandler());
+
+ // Verify that an update message was constructed properly.
+ checkAddFwdAddressRequest(*name_add);
+
+ // Verify that we are still in this state and next event is NOP_EVT.
+ // This indicates we "sent" the message and are waiting for IO completion.
+ EXPECT_EQ(NameAddTransaction::ADDING_FWD_ADDRS_ST,
+ name_add->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::NOP_EVT,
+ name_add->getNextEvent());
+
+ // Simulate receiving a successful update response.
+ name_add->fakeResponse(DNSClient::SUCCESS, dns::Rcode::NOERROR());
+
+ // Run addingFwdAddrsHandler again to process the response.
+ EXPECT_NO_THROW(name_add->addingFwdAddrsHandler());
+
+ // Forward completion should be true, reverse should be false.
+ EXPECT_TRUE(name_add->getForwardChangeCompleted());
+ EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+ // Since it is a forward only change, we should be done.
+ // Verify that we transitioned correctly.
+ EXPECT_EQ(NameChangeTransaction::PROCESS_TRANS_OK_ST,
+ name_add->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::UPDATE_OK_EVT,
+ name_add->getNextEvent());
+}
+
+// Tests addingFwdAddrsHandler with the following scenario:
+//
+// The request includes a forward and reverse change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The update request is sent without error.
+// A server response is received which indicates successful update.
+//
+TEST_F(NameAddTransactionTest, addingFwdAddrsHandler_fwdAndRevAddOK) {
+ NameAddStubPtr name_add;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(name_add =
+ prepHandlerTest(NameAddTransaction::ADDING_FWD_ADDRS_ST,
+ NameChangeTransaction::
+ SERVER_SELECTED_EVT, FWD_AND_REV_CHG));
+
+ // Run addingFwdAddrsHandler to construct and send the request.
+ EXPECT_NO_THROW(name_add->addingFwdAddrsHandler());
+
+ // Simulate receiving a successful update response.
+ name_add->fakeResponse(DNSClient::SUCCESS, dns::Rcode::NOERROR());
+
+ // Run addingFwdAddrsHandler again to process the response.
+ EXPECT_NO_THROW(name_add->addingFwdAddrsHandler());
+
+ // Forward change completion should be true, reverse flag should be false.
+ EXPECT_TRUE(name_add->getForwardChangeCompleted());
+ EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+ // Since the request also includes a reverse change we should
+ // be poised to start it. Verify that we transitioned correctly.
+ EXPECT_EQ(NameChangeTransaction::SELECTING_REV_SERVER_ST,
+ name_add->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::SELECT_SERVER_EVT,
+ name_add->getNextEvent());
+}
+
+// Tests addingFwdAddrsHandler with the following scenario:
+//
+// The request includes a forward and reverse change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The update request is sent without error.
+// A server response is received which indicates the FQDN is in use.
+//
+TEST_F(NameAddTransactionTest, addingFwdAddrsHandler_FqdnInUse) {
+ NameAddStubPtr name_add;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(name_add =
+ prepHandlerTest(NameAddTransaction::ADDING_FWD_ADDRS_ST,
+ NameChangeTransaction::
+ SERVER_SELECTED_EVT));
+
+ // Run addingFwdAddrsHandler to construct and send the request.
+ EXPECT_NO_THROW(name_add->addingFwdAddrsHandler());
+
+ // Simulate receiving a FQDN in use response.
+ name_add->fakeResponse(DNSClient::SUCCESS, dns::Rcode::YXDOMAIN());
+
+ // Run addingFwdAddrsHandler again to process the response.
+ EXPECT_NO_THROW(name_add->addingFwdAddrsHandler());
+
+ // Completion flags should still be false.
+ EXPECT_FALSE(name_add->getForwardChangeCompleted());
+ EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+ // Since the FQDN is in use, per the RFC we must attempt to replace it.
+ // Verify that we transitioned correctly.
+ EXPECT_EQ(NameAddTransaction::REPLACING_FWD_ADDRS_ST,
+ name_add->getCurrState());
+ EXPECT_EQ(NameAddTransaction::FQDN_IN_USE_EVT,
+ name_add->getNextEvent());
+}
+
+// Tests addingFwdAddrsHandler with the following scenario:
+//
+// The request includes a forward and reverse change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The update request is sent without error.
+// A server response is received which indicates the update was rejected.
+//
+TEST_F(NameAddTransactionTest, addingFwdAddrsHandler_OtherRcode) {
+ NameAddStubPtr name_add;
+
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(name_add =
+ prepHandlerTest(NameAddTransaction::ADDING_FWD_ADDRS_ST,
+ NameChangeTransaction::
+ SERVER_SELECTED_EVT));
+
+ // Select a server to satisfy log statements.
+ ASSERT_TRUE(name_add->selectFwdServer());
+
+ // Run addingFwdAddrsHandler to construct and send the request.
+ EXPECT_NO_THROW(name_add->addingFwdAddrsHandler());
+
+ // Simulate receiving server rejection response. Per RFC, anything other
+ // than no error or FQDN in use is failure. Arbitrarily choosing refused.
+ name_add->fakeResponse(DNSClient::SUCCESS, dns::Rcode::REFUSED());
+
+ // Run addingFwdAddrsHandler again to process the response.
+ EXPECT_NO_THROW(name_add->addingFwdAddrsHandler());
+
+ // Completion flags should still be false.
+ EXPECT_FALSE(name_add->getForwardChangeCompleted());
+ EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+ // We should have failed the transaction. Verify that we transitioned
+ // correctly.
+ EXPECT_EQ(NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ name_add->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::UPDATE_FAILED_EVT,
+ name_add->getNextEvent());
+}
+
+// Tests addingFwdAddrsHandler with the following scenario:
+//
+// The request includes a forward and reverse change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The update request send times out MAX_UPDATE_TRIES_PER_SERVER times.
+//
+TEST_F(NameAddTransactionTest, addingFwdAddrsHandler_Timeout) {
+ NameAddStubPtr name_add;
+
+ // Create and prep a transaction, poised to run the handler.
+ // The log message issued when this test succeeds, displays the
+ // selected server, so we need to select a server before running this
+ // test.
+ ASSERT_NO_THROW(name_add =
+ prepHandlerTest(NameAddTransaction::ADDING_FWD_ADDRS_ST,
+ NameChangeTransaction::
+ SERVER_SELECTED_EVT));
+
+ // Select a server to satisfy log statements.
+ ASSERT_TRUE(name_add->selectFwdServer());
+
+ // Verify that we can make maximum number of update attempts permitted
+ // and then transition to selecting a new server.
+ int max_tries = NameChangeTransaction::MAX_UPDATE_TRIES_PER_SERVER;
+ for (int i = 1; i <= max_tries; ++i) {
+ // Run addingFwdAddrsHandler to send the request.
+ EXPECT_NO_THROW(name_add->addingFwdAddrsHandler());
+
+ // Simulate a server IO timeout.
+ name_add->setDnsUpdateStatus(DNSClient::TIMEOUT);
+ name_add->postNextEvent(NameChangeTransaction::IO_COMPLETED_EVT);
+
+ // Run addingFwdAddrsHandler again to process the response.
+ EXPECT_NO_THROW(name_add->addingFwdAddrsHandler());
+
+ // Completion flags should be false.
+ EXPECT_FALSE(name_add->getForwardChangeCompleted());
+ EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+ if (i < max_tries) {
+ // We should be ready to try again.
+ EXPECT_EQ(NameAddTransaction::ADDING_FWD_ADDRS_ST,
+ name_add->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::SERVER_SELECTED_EVT,
+ name_add->getNextEvent());
+ } else {
+ // Server retries should be exhausted, time for a new server.
+ EXPECT_EQ(NameAddTransaction::SELECTING_FWD_SERVER_ST,
+ name_add->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::SERVER_IO_ERROR_EVT,
+ name_add->getNextEvent());
+ }
+ }
+}
+
+// Tests addingFwdAddrsHandler with the following scenario:
+//
+// The request includes a forward and reverse change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The update request is sent but a corrupt response is received, this occurs
+// MAX_UPDATE_TRIES_PER_SERVER times.
+//
+TEST_F(NameAddTransactionTest, addingFwdAddrsHandler_InvalidResponse) {
+ NameAddStubPtr name_add;
+
+ // Create and prep a transaction, poised to run the handler.
+ // The log message issued when this test succeeds, displays the
+ // selected server, so we need to select a server before running this
+ // test.
+ ASSERT_NO_THROW(name_add =
+ prepHandlerTest(NameAddTransaction::ADDING_FWD_ADDRS_ST,
+ NameChangeTransaction::
+ SERVER_SELECTED_EVT));
+
+ // Select a server to satisfy log statements.
+ ASSERT_TRUE(name_add->selectFwdServer());
+
+ // Verify that we can make maximum number of update attempts permitted
+ // and then transition to selecting a new server.
+ int max_tries = NameChangeTransaction::MAX_UPDATE_TRIES_PER_SERVER;
+ for (int i = 1; i <= max_tries; ++i) {
+ // Run addingFwdAddrsHandler to construct send the request.
+ EXPECT_NO_THROW(name_add->addingFwdAddrsHandler());
+
+ // Simulate a corrupt server response.
+ name_add->setDnsUpdateStatus(DNSClient::INVALID_RESPONSE);
+ name_add->postNextEvent(NameChangeTransaction::IO_COMPLETED_EVT);
+
+ // Run addingFwdAddrsHandler again to process the response.
+ EXPECT_NO_THROW(name_add->addingFwdAddrsHandler());
+
+ // Completion flags should be false.
+ EXPECT_FALSE(name_add->getForwardChangeCompleted());
+ EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+ if (i < max_tries) {
+ // We should be ready to try again.
+ EXPECT_EQ(NameAddTransaction::ADDING_FWD_ADDRS_ST,
+ name_add->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::SERVER_SELECTED_EVT,
+ name_add->getNextEvent());
+ } else {
+ // Server retries should be exhausted, time for a new server.
+ EXPECT_EQ(NameAddTransaction::SELECTING_FWD_SERVER_ST,
+ name_add->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::SERVER_IO_ERROR_EVT,
+ name_add->getNextEvent());
+ }
+ }
+
+}
+
+// ************************ replacingFwdAddrHandler Tests *****************
+
+// Tests that replacingFwdAddrsHandler rejects invalid events.
+TEST_F(NameAddTransactionTest, replacingFwdAddrsHandler_InvalidEvent) {
+ NameAddStubPtr name_add;
+ // Create and prep a transaction, poised to run the handler but with
+ // an invalid event.
+ ASSERT_NO_THROW(name_add =
+ prepHandlerTest(NameAddTransaction::REPLACING_FWD_ADDRS_ST,
+ NameChangeTransaction::
+ StateModel::NOP_EVT));
+
+ // Running the handler should throw.
+ EXPECT_THROW(name_add->replacingFwdAddrsHandler(),
+ NameAddTransactionError);
+}
+
+// Tests replacingFwdAddrsHandler with the following scenario:
+//
+// The request includes only a forward change.
+// Initial posted event is FQDN_IN_USE_EVT.
+// The update request is sent without error.
+// A server response is received which indicates successful update.
+//
+TEST_F(NameAddTransactionTest, replacingFwdAddrsHandler_FwdOnlyAddOK) {
+ NameAddStubPtr name_add;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(name_add =
+ prepHandlerTest(NameAddTransaction::REPLACING_FWD_ADDRS_ST,
+ NameAddTransaction::
+ FQDN_IN_USE_EVT, FORWARD_CHG));
+
+ // Should not be an update message yet.
+ D2UpdateMessagePtr update_msg = name_add->getDnsUpdateRequest();
+ ASSERT_FALSE(update_msg);
+
+ // At this point completion flags should be false.
+ EXPECT_FALSE(name_add->getForwardChangeCompleted());
+ EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+ // Run replacingFwdAddrsHandler to construct and send the request.
+ EXPECT_NO_THROW(name_add->replacingFwdAddrsHandler());
+
+ // Verify that an update message was constructed properly.
+ checkReplaceFwdAddressRequest(*name_add);
+
+ // Verify that we are still in this state and next event is NOP_EVT.
+ // This indicates we "sent" the message and are waiting for IO completion.
+ EXPECT_EQ(NameAddTransaction::REPLACING_FWD_ADDRS_ST,
+ name_add->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::NOP_EVT,
+ name_add->getNextEvent());
+
+ // Simulate receiving a successful update response.
+ name_add->fakeResponse(DNSClient::SUCCESS, dns::Rcode::NOERROR());
+
+ // Run replacingFwdAddrsHandler again to process the response.
+ EXPECT_NO_THROW(name_add->replacingFwdAddrsHandler());
+
+ // Forward completion should be true, reverse should be false.
+ EXPECT_TRUE(name_add->getForwardChangeCompleted());
+ EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+ // Since it is a forward only change, we should be done.
+ // Verify that we transitioned correctly.
+ EXPECT_EQ(NameChangeTransaction::PROCESS_TRANS_OK_ST,
+ name_add->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::UPDATE_OK_EVT,
+ name_add->getNextEvent());
+}
+
+// Tests replacingFwdAddrsHandler with the following scenario:
+//
+// The request includes only a forward change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The update request is sent without error.
+// A server response is received which indicates successful update.
+//
+TEST_F(NameAddTransactionTest, replacingFwdAddrsHandler_FwdOnlyAddOK2) {
+ NameAddStubPtr name_add;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(name_add =
+ prepHandlerTest(NameAddTransaction::REPLACING_FWD_ADDRS_ST,
+ NameChangeTransaction::
+ SERVER_SELECTED_EVT, FORWARD_CHG));
+
+ // Run replacingFwdAddrsHandler to construct and send the request.
+ EXPECT_NO_THROW(name_add->replacingFwdAddrsHandler());
+
+ // Simulate receiving a successful update response.
+ name_add->fakeResponse(DNSClient::SUCCESS, dns::Rcode::NOERROR());
+
+ // Run replacingFwdAddrsHandler again to process the response.
+ EXPECT_NO_THROW(name_add->replacingFwdAddrsHandler());
+
+ // Forward completion should be true, reverse should be false.
+ EXPECT_TRUE(name_add->getForwardChangeCompleted());
+ EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+ // Since it is a forward only change, we should be done.
+ // Verify that we transitioned correctly.
+ EXPECT_EQ(NameChangeTransaction::PROCESS_TRANS_OK_ST,
+ name_add->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::UPDATE_OK_EVT,
+ name_add->getNextEvent());
+}
+
+// Tests replacingFwdAddrsHandler with the following scenario:
+//
+// The request includes a forward and reverse change.
+// Initial posted event is FQDN_IN_USE_EVT.
+// The update request is sent without error.
+// A server response is received which indicates successful update.
+//
+TEST_F(NameAddTransactionTest, replacingFwdAddrsHandler_FwdAndRevAddOK) {
+ NameAddStubPtr name_add;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(name_add =
+ prepHandlerTest(NameAddTransaction::REPLACING_FWD_ADDRS_ST,
+ NameAddTransaction::
+ FQDN_IN_USE_EVT, FWD_AND_REV_CHG));
+
+ // Run replacingFwdAddrsHandler to construct and send the request.
+ EXPECT_NO_THROW(name_add->replacingFwdAddrsHandler());
+
+ // Simulate receiving a successful update response.
+ name_add->fakeResponse(DNSClient::SUCCESS, dns::Rcode::NOERROR());
+
+ // Run replacingFwdAddrsHandler again to process the response.
+ EXPECT_NO_THROW(name_add->replacingFwdAddrsHandler());
+
+ // Forward change completion should be true, reverse flag should be false.
+ EXPECT_TRUE(name_add->getForwardChangeCompleted());
+ EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+ // Since the request also includes a reverse change we should
+ // be poised to start it. Verify that we transitioned correctly.
+ EXPECT_EQ(NameChangeTransaction::SELECTING_REV_SERVER_ST,
+ name_add->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::SELECT_SERVER_EVT,
+ name_add->getNextEvent());
+}
+
+
+// Tests replacingFwdAddrsHandler with the following scenario:
+//
+// The request includes a forward and reverse change.
+// Initial posted event is FQDN_IN_USE_EVT.
+// The update request is sent without error.
+// A server response is received which indicates the FQDN is NOT in use.
+//
+TEST_F(NameAddTransactionTest, replacingFwdAddrsHandler_FqdnNotInUse) {
+ NameAddStubPtr name_add;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(name_add =
+ prepHandlerTest(NameAddTransaction::REPLACING_FWD_ADDRS_ST,
+ NameAddTransaction::
+ FQDN_IN_USE_EVT, FWD_AND_REV_CHG));
+
+ // Run replacingFwdAddrsHandler to construct and send the request.
+ EXPECT_NO_THROW(name_add->replacingFwdAddrsHandler());
+
+ // Simulate receiving a FQDN not in use response.
+ name_add->fakeResponse(DNSClient::SUCCESS, dns::Rcode::NXDOMAIN());
+
+ // Run replacingFwdAddrsHandler again to process the response.
+ EXPECT_NO_THROW(name_add->replacingFwdAddrsHandler());
+
+ // Completion flags should still be false.
+ EXPECT_FALSE(name_add->getForwardChangeCompleted());
+ EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+ // Since the FQDN is no longer in use, per the RFC, try to add it.
+ // Verify that we transitioned correctly.
+ EXPECT_EQ(NameAddTransaction::ADDING_FWD_ADDRS_ST,
+ name_add->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::SERVER_SELECTED_EVT,
+ name_add->getNextEvent());
+}
+
+
+// Tests replacingFwdAddrsHandler with the following scenario:
+//
+// The request includes a forward and reverse change.
+// The update request is sent without error.
+// A server response is received which indicates the update was rejected.
+//
+TEST_F(NameAddTransactionTest, replacingFwdAddrsHandler_OtherRcode) {
+ NameAddStubPtr name_add;
+ // Create the transaction.
+ ASSERT_NO_THROW(name_add =
+ prepHandlerTest(NameAddTransaction::REPLACING_FWD_ADDRS_ST,
+ NameAddTransaction::
+ FQDN_IN_USE_EVT, FWD_AND_REV_CHG));
+
+ // Select a server to satisfy log statements.
+ ASSERT_TRUE(name_add->selectFwdServer());
+
+ // Run replacingFwdAddrsHandler to construct and send the request.
+ EXPECT_NO_THROW(name_add->replacingFwdAddrsHandler());
+
+ // Simulate receiving server rejection response. Per RFC, anything other
+ // than no error or FQDN in use is failure. Arbitrarily choosing refused.
+ name_add->fakeResponse(DNSClient::SUCCESS, dns::Rcode::REFUSED());
+
+ // Run replacingFwdAddrsHandler again to process the response.
+ EXPECT_NO_THROW(name_add->replacingFwdAddrsHandler());
+
+ // Completion flags should still be false.
+ EXPECT_FALSE(name_add->getForwardChangeCompleted());
+ EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+ // We should have failed the transaction. Verify that we transitioned
+ // correctly.
+ EXPECT_EQ(NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ name_add->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::UPDATE_FAILED_EVT,
+ name_add->getNextEvent());
+}
+
+// Tests replacingFwdAddrsHandler with the following scenario:
+//
+// The request includes a forward and reverse change.
+// Initial posted event is FQDN_IN_USE_EVT.
+// The update request send times out MAX_UPDATE_TRIES_PER_SERVER times.
+//
+TEST_F(NameAddTransactionTest, replacingFwdAddrsHandler_Timeout) {
+ NameAddStubPtr name_add;
+
+ // Create the transaction.
+ ASSERT_NO_THROW(name_add =
+ prepHandlerTest(NameAddTransaction::REPLACING_FWD_ADDRS_ST,
+ NameAddTransaction::
+ FQDN_IN_USE_EVT, FWD_AND_REV_CHG));
+
+ // Select a server to satisfy log statements.
+ ASSERT_TRUE(name_add->selectFwdServer());
+
+
+ // Verify that we can make maximum number of update attempts permitted
+ // and then transition to selecting a new server.
+ int max_tries = NameChangeTransaction::MAX_UPDATE_TRIES_PER_SERVER;
+ for (int i = 1; i <= max_tries; ++i) {
+ // Run replacingFwdAddrsHandler to send the request.
+ EXPECT_NO_THROW(name_add->replacingFwdAddrsHandler());
+
+ // Simulate a server IO timeout.
+ name_add->setDnsUpdateStatus(DNSClient::TIMEOUT);
+ name_add->postNextEvent(NameChangeTransaction::IO_COMPLETED_EVT);
+
+ // Run replacingFwdAddrsHandler again to process the response.
+ EXPECT_NO_THROW(name_add->replacingFwdAddrsHandler());
+
+ // Completion flags should be false.
+ EXPECT_FALSE(name_add->getForwardChangeCompleted());
+ EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+ if (i < max_tries) {
+ // We should be ready to try again.
+ EXPECT_EQ(NameAddTransaction::REPLACING_FWD_ADDRS_ST,
+ name_add->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::SERVER_SELECTED_EVT,
+ name_add->getNextEvent());
+ } else {
+ // Server retries should be exhausted, time for a new server.
+ EXPECT_EQ(NameAddTransaction::SELECTING_FWD_SERVER_ST,
+ name_add->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::SERVER_IO_ERROR_EVT,
+ name_add->getNextEvent());
+ }
+ }
+}
+
+// Tests replacingFwdAddrsHandler with the following scenario:
+//
+// The request includes a forward and reverse change.
+// Initial posted event is FQDN_IN_USE_EVT.
+// The update request is sent but a corrupt response is received, this occurs
+// MAX_UPDATE_TRIES_PER_SERVER times.
+//
+TEST_F(NameAddTransactionTest, replacingFwdAddrsHandler_CorruptResponse) {
+ NameAddStubPtr name_add;
+
+ // Create the transaction.
+ ASSERT_NO_THROW(name_add =
+ prepHandlerTest(NameAddTransaction::REPLACING_FWD_ADDRS_ST,
+ NameAddTransaction::
+ FQDN_IN_USE_EVT, FWD_AND_REV_CHG));
+
+ // Select a server to satisfy log statements.
+ ASSERT_TRUE(name_add->selectFwdServer());
+
+ // Verify that we can make maximum number of update attempts permitted
+ // and then transition to selecting a new server.
+ int max_tries = NameChangeTransaction::MAX_UPDATE_TRIES_PER_SERVER;
+ for (int i = 1; i <= max_tries; ++i) {
+ // Run replacingFwdAddrsHandler to send the request.
+ EXPECT_NO_THROW(name_add->replacingFwdAddrsHandler());
+
+ // Simulate a corrupt server response.
+ name_add->setDnsUpdateStatus(DNSClient::INVALID_RESPONSE);
+ name_add->postNextEvent(NameChangeTransaction::IO_COMPLETED_EVT);
+
+ // Run replacingFwdAddrsHandler again to process the response.
+ EXPECT_NO_THROW(name_add->replacingFwdAddrsHandler());
+
+ // Completion flags should be false.
+ EXPECT_FALSE(name_add->getForwardChangeCompleted());
+ EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+ if (i < max_tries) {
+ // We should be ready to try again.
+ EXPECT_EQ(NameAddTransaction::REPLACING_FWD_ADDRS_ST,
+ name_add->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::SERVER_SELECTED_EVT,
+ name_add->getNextEvent());
+ } else {
+ // Server retries should be exhausted, time for a new server.
+ EXPECT_EQ(NameAddTransaction::SELECTING_FWD_SERVER_ST,
+ name_add->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::SERVER_IO_ERROR_EVT,
+ name_add->getNextEvent());
+ }
+ }
+}
+
+// Tests the selectingRevServerHandler functionality.
+// It verifies behavior for the following scenarios:
+//
+// 1. Posted event is SELECT_SERVER_EVT
+// 2. Posted event is SERVER_IO_ERROR_EVT
+// 3. Posted event is invalid
+//
+TEST_F(NameAddTransactionTest, selectingRevServerHandler) {
+ NameAddStubPtr name_add;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(name_add =
+ prepHandlerTest(NameChangeTransaction::
+ SELECTING_REV_SERVER_ST,
+ NameChangeTransaction::SELECT_SERVER_EVT));
+
+ // Call selectingRevServerHandler enough times to select all of the
+ // servers in it's current domain. The first time, it will be with
+ // next event of SELECT_SERVER_EVT. Thereafter it will be with a next
+ // event of SERVER_IO_ERROR_EVT.
+ int num_servers = name_add->getReverseDomain()->getServers()->size();
+ for (int i = 0; i < num_servers; ++i) {
+ // Run selectingRevServerHandler.
+ ASSERT_NO_THROW(name_add->selectingRevServerHandler())
+ << " num_servers: " << num_servers
+ << " selections: " << i;
+
+ // Verify that a server was selected.
+ ASSERT_TRUE(name_add->getCurrentServer())
+ << " num_servers: " << num_servers
+ << " selections: " << i;
+
+ // Verify that we transitioned correctly.
+ ASSERT_EQ(NameAddTransaction::REPLACING_REV_PTRS_ST,
+ name_add->getCurrState())
+ << " num_servers: " << num_servers << " selections: " << i;
+ ASSERT_EQ(NameChangeTransaction::SERVER_SELECTED_EVT,
+ name_add->getNextEvent())
+ << " num_servers: " << num_servers << " selections: " << i;
+
+ // Post a server IO error event. This simulates an IO error occurring
+ // and a need to select the new server.
+ ASSERT_NO_THROW(name_add->postNextEvent(NameChangeTransaction::
+ SERVER_IO_ERROR_EVT))
+ << " num_servers: " << num_servers
+ << " selections: " << i;
+ }
+
+ // We should have exhausted the list of servers. Processing another
+ // SERVER_IO_ERROR_EVT should transition us to failure.
+ EXPECT_NO_THROW(name_add->selectingRevServerHandler());
+ EXPECT_EQ(NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ name_add->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::NO_MORE_SERVERS_EVT,
+ name_add->getNextEvent());
+
+ // Create and prep transaction, poised to run the handler but with an
+ // invalid event.
+ ASSERT_NO_THROW(name_add =
+ prepHandlerTest(NameChangeTransaction::
+ SELECTING_REV_SERVER_ST,
+ StateModel::NOP_EVT));
+
+ // Running the handler should throw.
+ EXPECT_THROW(name_add->selectingRevServerHandler(),
+ NameAddTransactionError);
+}
+
+//************************** replacingRevPtrsHandler tests *****************
+
+// Tests that replacingRevPtrsHandler rejects invalid events.
+TEST_F(NameAddTransactionTest, replacingRevPtrsHandler_InvalidEvent) {
+ NameAddStubPtr name_add;
+ // Create and prep a transaction, poised to run the handler but with
+ // an invalid event.
+ ASSERT_NO_THROW(name_add =
+ prepHandlerTest(NameAddTransaction::REPLACING_REV_PTRS_ST,
+ NameChangeTransaction::
+ StateModel::NOP_EVT));
+
+ // Running the handler should throw.
+ EXPECT_THROW(name_add->replacingRevPtrsHandler(),
+ NameAddTransactionError);
+}
+
+// Tests replacingRevPtrsHandler with the following scenario:
+//
+// The request includes only a reverse change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The update request is sent without error.
+// A server response is received which indicates successful update.
+//
+TEST_F(NameAddTransactionTest, replacingRevPtrsHandler_FwdOnlyAddOK) {
+ NameAddStubPtr name_add;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(name_add =
+ prepHandlerTest(NameAddTransaction::REPLACING_REV_PTRS_ST,
+ NameAddTransaction::
+ SERVER_SELECTED_EVT, REVERSE_CHG));
+
+ // Should not be an update message yet.
+ D2UpdateMessagePtr update_msg = name_add->getDnsUpdateRequest();
+ ASSERT_FALSE(update_msg);
+
+ // At this point completion flags should be false.
+ EXPECT_FALSE(name_add->getForwardChangeCompleted());
+ EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+ // Run replacingRevPtrsHandler to construct and send the request.
+ EXPECT_NO_THROW(name_add->replacingRevPtrsHandler());
+
+ // Verify that an update message was constructed properly.
+ checkReplaceRevPtrsRequest(*name_add);
+
+ // Verify that we are still in this state and next event is NOP_EVT.
+ // This indicates we "sent" the message and are waiting for IO completion.
+ EXPECT_EQ(NameAddTransaction::REPLACING_REV_PTRS_ST,
+ name_add->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::NOP_EVT,
+ name_add->getNextEvent());
+
+ // Simulate receiving a successful update response.
+ name_add->fakeResponse(DNSClient::SUCCESS, dns::Rcode::NOERROR());
+
+ // Run replacingRevPtrsHandler again to process the response.
+ EXPECT_NO_THROW(name_add->replacingRevPtrsHandler());
+
+ // Forward completion should be false, reverse should be true.
+ EXPECT_FALSE(name_add->getForwardChangeCompleted());
+ EXPECT_TRUE(name_add->getReverseChangeCompleted());
+
+ // Since it is a reverse change, we should be done.
+ // Verify that we transitioned correctly.
+ EXPECT_EQ(NameChangeTransaction::PROCESS_TRANS_OK_ST,
+ name_add->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::UPDATE_OK_EVT,
+ name_add->getNextEvent());
+}
+
+// Tests replacingRevPtrsHandler with the following scenario:
+//
+// The request includes only a reverse change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The update request is sent without error.
+// A server response is received which indicates the update was rejected.
+//
+TEST_F(NameAddTransactionTest, replacingRevPtrsHandler_OtherRcode) {
+ NameAddStubPtr name_add;
+ // Create the transaction.
+ ASSERT_NO_THROW(name_add =
+ prepHandlerTest(NameAddTransaction::REPLACING_REV_PTRS_ST,
+ NameAddTransaction::
+ SERVER_SELECTED_EVT, REVERSE_CHG));
+
+ // Select a server to satisfy log statements.
+ ASSERT_TRUE(name_add->selectRevServer());
+
+ // Run replacingRevPtrsHandler to construct and send the request.
+ EXPECT_NO_THROW(name_add->replacingRevPtrsHandler());
+
+ // Simulate receiving server rejection response. Per RFC, anything other
+ // than no error is failure. Arbitrarily choosing refused.
+ name_add->fakeResponse(DNSClient::SUCCESS, dns::Rcode::REFUSED());
+
+ // Run replacingRevPtrsHandler again to process the response.
+ EXPECT_NO_THROW(name_add->replacingRevPtrsHandler());
+
+ // Completion flags should still be false.
+ EXPECT_FALSE(name_add->getForwardChangeCompleted());
+ EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+ // We should have failed the transaction. Verify that we transitioned
+ // correctly.
+ EXPECT_EQ(NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ name_add->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::UPDATE_FAILED_EVT,
+ name_add->getNextEvent());
+}
+
+// Tests replacingRevPtrsHandler with the following scenario:
+//
+// The request includes only a reverse change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The update request send times out MAX_UPDATE_TRIES_PER_SERVER times.
+//
+TEST_F(NameAddTransactionTest, replacingRevPtrsHandler_Timeout) {
+ NameAddStubPtr name_add;
+ // Create the transaction.
+ ASSERT_NO_THROW(name_add =
+ prepHandlerTest(NameAddTransaction::REPLACING_REV_PTRS_ST,
+ NameAddTransaction::
+ SERVER_SELECTED_EVT, REVERSE_CHG));
+
+ // Select a server to satisfy log statements.
+ ASSERT_TRUE(name_add->selectRevServer());
+
+ // Verify that we can make maximum number of update attempts permitted
+ // and then transition to selecting a new server.
+ int max_tries = NameChangeTransaction::MAX_UPDATE_TRIES_PER_SERVER;
+ for (int i = 1; i <= max_tries; ++i) {
+ // Run replacingRevPtrsHandler to send the request.
+ EXPECT_NO_THROW(name_add->replacingRevPtrsHandler());
+
+ // Simulate a server IO timeout.
+ name_add->setDnsUpdateStatus(DNSClient::TIMEOUT);
+ name_add->postNextEvent(NameChangeTransaction::IO_COMPLETED_EVT);
+
+ // Run replacingRevPtrsHandler again to process the response.
+ EXPECT_NO_THROW(name_add->replacingRevPtrsHandler());
+
+ // Completion flags should be false.
+ EXPECT_FALSE(name_add->getForwardChangeCompleted());
+ EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+ if (i < max_tries) {
+ // We should be ready to try again.
+ EXPECT_EQ(NameAddTransaction::REPLACING_REV_PTRS_ST,
+ name_add->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::SERVER_SELECTED_EVT,
+ name_add->getNextEvent());
+ } else {
+ // Server retries should be exhausted, time for a new server.
+ EXPECT_EQ(NameAddTransaction::SELECTING_REV_SERVER_ST,
+ name_add->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::SERVER_IO_ERROR_EVT,
+ name_add->getNextEvent());
+ }
+ }
+}
+
+// Tests replacingRevPtrsHandler with the following scenario:
+//
+// The request includes only a reverse change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The update request is sent but a corrupt response is received, this occurs
+// MAX_UPDATE_TRIES_PER_SERVER times.
+//
+TEST_F(NameAddTransactionTest, replacingRevPtrsHandler_CorruptResponse) {
+ NameAddStubPtr name_add;
+ // Create the transaction.
+ ASSERT_NO_THROW(name_add =
+ prepHandlerTest(NameAddTransaction::REPLACING_REV_PTRS_ST,
+ NameAddTransaction::
+ SERVER_SELECTED_EVT, REVERSE_CHG));
+
+ // Select a server to satisfy log statements.
+ ASSERT_TRUE(name_add->selectRevServer());
+
+ // Verify that we can make maximum number of update attempts permitted
+ // and then transition to selecting a new server.
+ int max_tries = NameChangeTransaction::MAX_UPDATE_TRIES_PER_SERVER;
+ for (int i = 1; i <= max_tries; ++i) {
+ // Run replacingRevPtrsHandler to send the request.
+ EXPECT_NO_THROW(name_add->replacingRevPtrsHandler());
+
+ // Simulate a server corrupt response.
+ name_add->setDnsUpdateStatus(DNSClient::INVALID_RESPONSE);
+ name_add->postNextEvent(NameChangeTransaction::IO_COMPLETED_EVT);
+
+ // Run replacingRevPtrsHandler again to process the response.
+ EXPECT_NO_THROW(name_add->replacingRevPtrsHandler());
+
+ // Completion flags should be false.
+ EXPECT_FALSE(name_add->getForwardChangeCompleted());
+ EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+ if (i < max_tries) {
+ // We should be ready to try again.
+ EXPECT_EQ(NameAddTransaction::REPLACING_REV_PTRS_ST,
+ name_add->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::SERVER_SELECTED_EVT,
+ name_add->getNextEvent());
+ } else {
+ // Server retries should be exhausted, time for a new server.
+ EXPECT_EQ(NameAddTransaction::SELECTING_REV_SERVER_ST,
+ name_add->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::SERVER_IO_ERROR_EVT,
+ name_add->getNextEvent());
+ }
+ }
+}
+
+// Tests the processAddOkHandler functionality.
+// It verifies behavior for the following scenarios:
+//
+// 1. Posted event is UPDATE_OK_EVT
+// 2. Posted event is invalid
+//
+TEST_F(NameAddTransactionTest, processAddOkHandler) {
+ NameAddStubPtr name_add;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(name_add =
+ prepHandlerTest(NameChangeTransaction::PROCESS_TRANS_OK_ST,
+ NameChangeTransaction::UPDATE_OK_EVT));
+ // Run processAddOkHandler.
+ EXPECT_NO_THROW(name_add->processAddOkHandler());
+
+ // Verify that a server was selected.
+ EXPECT_EQ(dhcp_ddns::ST_COMPLETED, name_add->getNcrStatus());
+
+ // Verify that the model has ended.
+ EXPECT_EQ(StateModel::END_ST, name_add->getCurrState());
+ EXPECT_EQ(StateModel::END_EVT, name_add->getNextEvent());
+
+
+ // Create and prep transaction, poised to run the handler but with an
+ // invalid event.
+ ASSERT_NO_THROW(name_add =
+ prepHandlerTest(NameChangeTransaction::PROCESS_TRANS_OK_ST,
+ StateModel::NOP_EVT));
+ // Running the handler should throw.
+ EXPECT_THROW(name_add->processAddOkHandler(), NameAddTransactionError);
+}
+
+// Tests the processAddFailedHandler functionality.
+// It verifies behavior for the following scenarios:
+//
+// 1. Posted event is UPDATE_FAILED_EVT
+// 2. Posted event is invalid
+//
+TEST_F(NameAddTransactionTest, processAddFailedHandler) {
+ NameAddStubPtr name_add;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(name_add =
+ prepHandlerTest(NameChangeTransaction::
+ PROCESS_TRANS_FAILED_ST,
+ NameChangeTransaction::UPDATE_FAILED_EVT));
+ // Run processAddFailedHandler.
+ EXPECT_NO_THROW(name_add->processAddFailedHandler());
+
+ // Verify that a server was selected.
+ EXPECT_EQ(dhcp_ddns::ST_FAILED, name_add->getNcrStatus());
+
+ // Verify that the model has ended. (Remember, the transaction failed NOT
+ // the model. The model should have ended normally.)
+ EXPECT_EQ(StateModel::END_ST, name_add->getCurrState());
+ EXPECT_EQ(StateModel::END_EVT, name_add->getNextEvent());
+
+
+ // Create and prep transaction, poised to run the handler but with an
+ // invalid event.
+ ASSERT_NO_THROW(name_add =
+ prepHandlerTest(NameChangeTransaction::
+ PROCESS_TRANS_FAILED_ST,
+ StateModel::NOP_EVT));
+ // Running the handler should throw.
+ EXPECT_THROW(name_add->processAddFailedHandler(), NameAddTransactionError);
+}
+
+// Tests the processAddFailedHandler functionality.
+// It verifies behavior for posted event of NO_MORE_SERVERS_EVT.
+TEST_F(NameAddTransactionTest, processAddFailedHandler_NoMoreServers) {
+ NameAddStubPtr name_remove;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(name_remove =
+ prepHandlerTest(NameChangeTransaction::
+ PROCESS_TRANS_FAILED_ST,
+ NameChangeTransaction::
+ NO_MORE_SERVERS_EVT));
+
+ // Run processAddFailedHandler.
+ EXPECT_NO_THROW(name_remove->processAddFailedHandler());
+
+ // Verify that a server was selected.
+ EXPECT_EQ(dhcp_ddns::ST_FAILED, name_remove->getNcrStatus());
+
+ // Verify that the model has ended. (Remember, the transaction failed NOT
+ // the model. The model should have ended normally.)
+ EXPECT_EQ(StateModel::END_ST, name_remove->getCurrState());
+ EXPECT_EQ(StateModel::END_EVT, name_remove->getNextEvent());
+}
+
+// Tests addingFwdAddrsHandler with the following scenario:
+//
+// The request includes only a forward change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The send update request fails due to an unexpected exception.
+//
+TEST_F(NameAddTransactionTest, addingFwdAddrsHandler_sendUpdateException) {
+ NameAddStubPtr name_add;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(name_add =
+ prepHandlerTest(NameAddTransaction::ADDING_FWD_ADDRS_ST,
+ NameChangeTransaction::
+ SERVER_SELECTED_EVT, FORWARD_CHG));
+
+ name_add->simulate_send_exception_ = true;
+
+ // Run replacingFwdAddrsHandler to construct and send the request.
+ ASSERT_NO_THROW(name_add->addingFwdAddrsHandler());
+
+ // Completion flags should be false.
+ EXPECT_FALSE(name_add->getForwardChangeCompleted());
+ EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+ // Since IO exceptions should be gracefully handled, any that occur
+ // are unanticipated, and deemed unrecoverable, so the transaction should
+ // be transitioned to failure.
+ EXPECT_EQ(NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ name_add->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::UPDATE_FAILED_EVT,
+ name_add->getNextEvent());
+}
+
+// Tests replacingFwdAddrsHandler with the following scenario:
+//
+// The request includes only a forward change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The send update request fails due to an unexpected exception.
+//
+TEST_F(NameAddTransactionTest, replacingFwdAddrsHandler_SendUpdateException) {
+ NameAddStubPtr name_add;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(name_add =
+ prepHandlerTest(NameAddTransaction::REPLACING_FWD_ADDRS_ST,
+ NameChangeTransaction::
+ SERVER_SELECTED_EVT, FORWARD_CHG));
+
+ name_add->simulate_send_exception_ = true;
+
+ // Run replacingFwdAddrsHandler to construct and send the request.
+ ASSERT_NO_THROW(name_add->replacingFwdAddrsHandler());
+
+ // Completion flags should be false.
+ EXPECT_FALSE(name_add->getForwardChangeCompleted());
+ EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+ // Since IO exceptions should be gracefully handled, any that occur
+ // are unanticipated, and deemed unrecoverable, so the transaction should
+ // be transitioned to failure.
+ EXPECT_EQ(NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ name_add->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::UPDATE_FAILED_EVT,
+ name_add->getNextEvent());
+}
+
+// Tests replacingRevPtrHandler with the following scenario:
+//
+// The request includes only a reverse change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The send update request fails due to an unexpected exception.
+//
+TEST_F(NameAddTransactionTest, replacingRevPtrsHandler_SendUpdateException) {
+ NameAddStubPtr name_add;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(name_add =
+ prepHandlerTest(NameAddTransaction::REPLACING_REV_PTRS_ST,
+ NameChangeTransaction::
+ SERVER_SELECTED_EVT, REVERSE_CHG));
+
+ name_add->simulate_send_exception_ = true;
+
+ // Run replacingRevPtrsHandler to construct and send the request.
+ ASSERT_NO_THROW(name_add->replacingRevPtrsHandler());
+
+ // Completion flags should be false.
+ EXPECT_FALSE(name_add->getForwardChangeCompleted());
+ EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+ // Since IO exceptions should be gracefully handled, any that occur
+ // are unanticipated, and deemed unrecoverable, so the transaction should
+ // be transitioned to failure.
+ EXPECT_EQ(NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ name_add->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::UPDATE_FAILED_EVT,
+ name_add->getNextEvent());
+}
+
+// Tests addingFwdAddrsHandler with the following scenario:
+//
+// The request includes only a forward change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The request build fails due to an unexpected exception.
+//
+TEST_F(NameAddTransactionTest, addingFwdAddrsHandler_BuildRequestException) {
+ NameAddStubPtr name_add;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(name_add =
+ prepHandlerTest(NameAddTransaction::ADDING_FWD_ADDRS_ST,
+ NameChangeTransaction::
+ SERVER_SELECTED_EVT, FORWARD_CHG));
+
+ // Set the one-shot exception simulation flag.
+ name_add->simulate_build_request_exception_ = true;
+
+ // Run replacingRevPtrsHandler to construct and send the request.
+ // This should fail with a build request throw which should be caught
+ // in the state handler.
+ ASSERT_NO_THROW(name_add->addingFwdAddrsHandler());
+
+ // Verify we did not attempt to send anything.
+ EXPECT_EQ(0, name_add->getUpdateAttempts());
+
+ // Completion flags should be false.
+ EXPECT_FALSE(name_add->getForwardChangeCompleted());
+ EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+ // Since IO exceptions should be gracefully handled, any that occur
+ // are unanticipated, and deemed unrecoverable, so the transaction should
+ // be transitioned to failure.
+ EXPECT_EQ(NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ name_add->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::UPDATE_FAILED_EVT,
+ name_add->getNextEvent());
+}
+
+// Tests replacingFwdAddrsHandler with the following scenario:
+//
+// The request includes only a forward change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The request build fails due to an unexpected exception.
+//
+TEST_F(NameAddTransactionTest, replacingFwdAddrsHandler_BuildRequestException) {
+ NameAddStubPtr name_add;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(name_add =
+ prepHandlerTest(NameAddTransaction::REPLACING_FWD_ADDRS_ST,
+ NameChangeTransaction::
+ SERVER_SELECTED_EVT, FORWARD_CHG));
+
+ // Set the one-shot exception simulation flag.
+ name_add->simulate_build_request_exception_ = true;
+
+ // Run replacingFwdAddrsHandler to construct and send the request.
+ // This should fail with a build request throw which should be caught
+ // in the state handler.
+ ASSERT_NO_THROW(name_add->replacingFwdAddrsHandler());
+
+ // Verify we did not attempt to send anything.
+ EXPECT_EQ(0, name_add->getUpdateAttempts());
+
+ // Completion flags should be false.
+ EXPECT_FALSE(name_add->getForwardChangeCompleted());
+ EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+ // Since IO exceptions should be gracefully handled, any that occur
+ // are unanticipated, and deemed unrecoverable, so the transaction should
+ // be transitioned to failure.
+ EXPECT_EQ(NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ name_add->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::UPDATE_FAILED_EVT,
+ name_add->getNextEvent());
+}
+
+
+// Tests replacingRevPtrHandler with the following scenario:
+//
+// The request includes only a reverse change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The request build fails due to an unexpected exception.
+//
+TEST_F(NameAddTransactionTest, replacingRevPtrsHandler_BuildRequestException) {
+ NameAddStubPtr name_add;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(name_add =
+ prepHandlerTest(NameAddTransaction::REPLACING_REV_PTRS_ST,
+ NameChangeTransaction::
+ SERVER_SELECTED_EVT, REVERSE_CHG));
+
+ // Set the one-shot exception simulation flag.
+ name_add->simulate_build_request_exception_ = true;
+
+ // Run replacingRevPtrsHandler to construct and send the request.
+ // This should fail with a build request throw which should be caught
+ // in the state handler.
+ ASSERT_NO_THROW(name_add->replacingRevPtrsHandler());
+
+ // Verify we did not attempt to send anything.
+ EXPECT_EQ(0, name_add->getUpdateAttempts());
+
+ // Completion flags should be false.
+ EXPECT_FALSE(name_add->getForwardChangeCompleted());
+ EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+ // Since IO exceptions should be gracefully handled, any that occur
+ // are unanticipated, and deemed unrecoverable, so the transaction should
+ // be transitioned to failure.
+ EXPECT_EQ(NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ name_add->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::UPDATE_FAILED_EVT,
+ name_add->getNextEvent());
+}
+
+
+}
diff --git a/src/bin/d2/tests/nc_remove_unittests.cc b/src/bin/d2/tests/nc_remove_unittests.cc
new file mode 100644
index 0000000..4373811
--- /dev/null
+++ b/src/bin/d2/tests/nc_remove_unittests.cc
@@ -0,0 +1,1796 @@
+// Copyright (C) 2013-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <asiolink/io_service.h>
+#include <d2/nc_remove.h>
+#include <d2srv/d2_cfg_mgr.h>
+#include <d2srv/testutils/nc_test_utils.h>
+#include <dns/messagerenderer.h>
+
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::d2;
+using namespace isc::util;
+
+
+namespace {
+
+/// @brief Test class derived from NameRemoveTransaction to provide visibility
+// to protected methods.
+class NameRemoveStub : public NameRemoveTransaction {
+public:
+ NameRemoveStub(asiolink::IOServicePtr& io_service,
+ dhcp_ddns::NameChangeRequestPtr& ncr,
+ DdnsDomainPtr& forward_domain,
+ DdnsDomainPtr& reverse_domain,
+ D2CfgMgrPtr& cfg_mgr)
+ : NameRemoveTransaction(io_service, ncr, forward_domain,
+ reverse_domain, cfg_mgr),
+ simulate_send_exception_(false),
+ simulate_build_request_exception_(false) {
+ }
+
+ virtual ~NameRemoveStub() {
+ }
+
+ /// @brief Simulates sending update requests to the DNS server
+ ///
+ /// This method simulates the initiation of an asynchronous send of
+ /// a DNS update request. It overrides the actual sendUpdate method in
+ /// the base class, thus avoiding an actual send, yet still increments
+ /// the update attempt count and posts a next event of NOP_EVT.
+ ///
+ /// It will also simulate an exception-based failure of sendUpdate, if
+ /// the simulate_send_exception_ flag is true.
+ ///
+ /// @param comment Parameter is unused, but present in base class method
+ ///
+ virtual void sendUpdate(const std::string& /* comment */) {
+ if (simulate_send_exception_) {
+ // Make the flag a one-shot by resetting it.
+ simulate_send_exception_ = false;
+ // Transition to failed.
+ transition(PROCESS_TRANS_FAILED_ST, UPDATE_FAILED_EVT);
+ return;
+ }
+
+ // Update send attempt count and post a NOP_EVT.
+ setUpdateAttempts(getUpdateAttempts() + 1);
+ postNextEvent(StateModel::NOP_EVT);
+ }
+
+ /// @brief Prepares the initial D2UpdateMessage
+ ///
+ /// This method overrides the NameChangeTransaction implementation to
+ /// provide the ability to simulate an exception throw in the build
+ /// request logic.
+ /// If the one-shot flag, simulate_build_request_exception_ is true,
+ /// this method will throw an exception, otherwise it will invoke the
+ /// base class method, providing normal functionality.
+ ///
+ /// For parameter description see the NameChangeTransaction implementation.
+ virtual D2UpdateMessagePtr prepNewRequest(DdnsDomainPtr domain) {
+ if (simulate_build_request_exception_) {
+ simulate_build_request_exception_ = false;
+ isc_throw (NameRemoveTransactionError,
+ "Simulated build requests exception");
+ }
+
+ return (NameChangeTransaction::prepNewRequest(domain));
+ }
+
+ /// @brief Simulates receiving a response
+ ///
+ /// This method simulates the completion of a DNSClient send. This allows
+ /// the state handler logic devoted to dealing with IO completion to be
+ /// fully exercised without requiring any actual IO. The two primary
+ /// pieces of information gleaned from IO completion are the DNSClient
+ /// status which indicates whether or not the IO exchange was successful
+ /// and the rcode, which indicates the server's reaction to the request.
+ ///
+ /// This method updates the transaction's DNS status value to that of the
+ /// given parameter, and then constructs and DNS update response message
+ /// with the given rcode value. To complete the simulation it then posts
+ /// a next event of IO_COMPLETED_EVT.
+ ///
+ /// @param status simulated DNSClient status
+ /// @param rcode simulated server response code
+ void fakeResponse(const DNSClient::Status& status,
+ const dns::Rcode& rcode) {
+ // Set the DNS update status. This is normally set in
+ // DNSClient IO completion handler.
+ setDnsUpdateStatus(status);
+
+ // Construct an empty message with the given Rcode.
+ D2UpdateMessagePtr msg(new D2UpdateMessage(D2UpdateMessage::OUTBOUND));
+ msg->setRcode(rcode);
+
+ // Set the update response to the message.
+ setDnsUpdateResponse(msg);
+
+ // Post the IO completion event.
+ postNextEvent(NameChangeTransaction::IO_COMPLETED_EVT);
+ }
+
+ /// @brief Selects the first forward server.
+ /// Some state handlers require a server to have been selected.
+ /// This selects a server without going through the state
+ /// transition(s) to do so.
+ bool selectFwdServer() {
+ if (getForwardDomain()) {
+ initServerSelection(getForwardDomain());
+ selectNextServer();
+ return (getCurrentServer().get() != 0);
+ }
+
+ return (false);
+ }
+
+ /// @brief Selects the first reverse server.
+ /// Some state handlers require a server to have been selected.
+ /// This selects a server without going through the state
+ /// transition(s) to do so.
+ bool selectRevServer() {
+ if (getReverseDomain()) {
+ initServerSelection(getReverseDomain());
+ selectNextServer();
+ return (getCurrentServer().get() != 0);
+ }
+
+ return (false);
+ }
+
+ /// @brief One-shot flag which will simulate sendUpdate failure if true.
+ bool simulate_send_exception_;
+
+ /// @brief One-shot flag which will simulate an exception when sendUpdate
+ /// failure if true.
+ bool simulate_build_request_exception_;
+
+ using StateModel::postNextEvent;
+ using StateModel::setState;
+ using StateModel::initDictionaries;
+ using NameRemoveTransaction::defineEvents;
+ using NameRemoveTransaction::verifyEvents;
+ using NameRemoveTransaction::defineStates;
+ using NameRemoveTransaction::verifyStates;
+ using NameRemoveTransaction::readyHandler;
+ using NameRemoveTransaction::selectingFwdServerHandler;
+ using NameRemoveTransaction::getCurrentServer;
+ using NameRemoveTransaction::removingFwdAddrsHandler;
+ using NameRemoveTransaction::setDnsUpdateStatus;
+ using NameRemoveTransaction::removingFwdRRsHandler;
+ using NameRemoveTransaction::selectingRevServerHandler;
+ using NameRemoveTransaction::removingRevPtrsHandler;
+ using NameRemoveTransaction::processRemoveOkHandler;
+ using NameRemoveTransaction::processRemoveFailedHandler;
+ using NameRemoveTransaction::buildRemoveFwdAddressRequest;
+ using NameRemoveTransaction::buildRemoveFwdRRsRequest;
+ using NameRemoveTransaction::buildRemoveRevPtrsRequest;
+};
+
+typedef boost::shared_ptr<NameRemoveStub> NameRemoveStubPtr;
+
+/// @brief Test fixture for testing NameRemoveTransaction
+///
+/// Note this class uses NameRemoveStub class to exercise non-public
+/// aspects of NameRemoveTransaction.
+class NameRemoveTransactionTest : public TransactionTest {
+public:
+ NameRemoveTransactionTest() {
+ }
+
+ virtual ~NameRemoveTransactionTest() {
+ }
+
+ /// @brief Creates a transaction which requests an IPv4 DNS update.
+ ///
+ /// The transaction is constructed around a predefined (i.e. "canned")
+ /// IPv4 NameChangeRequest. The request has both forward and reverse DNS
+ /// changes requested. Based upon the change mask, the transaction
+ /// will have either the forward, reverse, or both domains populated.
+ ///
+ /// @param change_mask determines which change directions are requested
+ NameRemoveStubPtr makeTransaction4(int change_mask) {
+ // Creates IPv4 remove request, forward, and reverse domains.
+ setupForIPv4Transaction(dhcp_ddns::CHG_REMOVE, change_mask);
+
+ // Now create the test transaction as would occur in update manager.
+ return (NameRemoveStubPtr(new NameRemoveStub(io_service_, ncr_,
+ forward_domain_,
+ reverse_domain_,
+ cfg_mgr_)));
+ }
+
+ /// @brief Creates a transaction which requests an IPv6 DNS update.
+ ///
+ /// The transaction is constructed around a predefined (i.e. "canned")
+ /// IPv6 NameChangeRequest. The request has both forward and reverse DNS
+ /// changes requested. Based upon the change mask, the transaction
+ /// will have either the forward, reverse, or both domains populated.
+ ///
+ /// @param change_mask determines which change directions are requested
+ NameRemoveStubPtr makeTransaction6(int change_mask) {
+ // Creates IPv6 remove request, forward, and reverse domains.
+ setupForIPv6Transaction(dhcp_ddns::CHG_REMOVE, change_mask);
+
+ // Now create the test transaction as would occur in update manager.
+ return (NameRemoveStubPtr(new NameRemoveStub(io_service_, ncr_,
+ forward_domain_,
+ reverse_domain_,
+ cfg_mgr_)));
+ }
+
+ /// @brief Create a test transaction at a known point in the state model.
+ ///
+ /// Method prepares a new test transaction and sets its state and next
+ /// event values to those given. This makes the transaction appear to
+ /// be at that point in the state model without having to transition it
+ /// through prerequisite states. It also provides the ability to set
+ /// which change directions are requested: forward change only, reverse
+ /// change only, or both.
+ ///
+ /// @param state value to set as the current state
+ /// @param event value to post as the next event
+ /// @param change_mask determines which change directions are requested
+ /// @param family selects between an IPv4 (AF_INET) and IPv6 (AF_INET6)
+ /// transaction.
+ NameRemoveStubPtr prepHandlerTest(unsigned int state, unsigned int event,
+ unsigned int change_mask
+ = FWD_AND_REV_CHG,
+ short family = AF_INET) {
+ NameRemoveStubPtr name_remove = (family == AF_INET ?
+ makeTransaction4(change_mask) :
+ makeTransaction6(change_mask));
+ name_remove->initDictionaries();
+ name_remove->postNextEvent(event);
+ name_remove->setState(state);
+ return (name_remove);
+ }
+
+};
+
+/// @brief Tests NameRemoveTransaction construction.
+/// This test verifies that:
+/// 1. Construction with invalid type of request
+/// 2. Valid construction functions properly
+TEST(NameRemoveTransaction, construction) {
+ asiolink::IOServicePtr io_service(new isc::asiolink::IOService());
+ D2CfgMgrPtr cfg_mgr(new D2CfgMgr());
+
+ const char* msg_str =
+ "{"
+ " \"change-type\" : 0 , "
+ " \"forward-change\" : true , "
+ " \"reverse-change\" : true , "
+ " \"fqdn\" : \"example.com.\" , "
+ " \"ip-address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"0102030405060708\" , "
+ " \"lease-expires-on\" : \"20130121132405\" , "
+ " \"lease-length\" : 1300, "
+ " \"use-conflict-resolution\" : true "
+ "}";
+
+ dhcp_ddns::NameChangeRequestPtr ncr;
+ DnsServerInfoStoragePtr servers;
+ DdnsDomainPtr forward_domain;
+ DdnsDomainPtr reverse_domain;
+ DdnsDomainPtr empty_domain;
+
+ ASSERT_NO_THROW(ncr = dhcp_ddns::NameChangeRequest::fromJSON(msg_str));
+ ASSERT_NO_THROW(forward_domain.reset(new DdnsDomain("*", servers)));
+ ASSERT_NO_THROW(reverse_domain.reset(new DdnsDomain("*", servers)));
+
+ // Verify that construction with wrong change type fails.
+ EXPECT_THROW(NameRemoveTransaction(io_service, ncr,
+ forward_domain, reverse_domain, cfg_mgr),
+ NameRemoveTransactionError);
+
+ // Verify that a valid construction attempt works.
+ ncr->setChangeType(isc::dhcp_ddns::CHG_REMOVE);
+ EXPECT_NO_THROW(NameRemoveTransaction(io_service, ncr,
+ forward_domain, reverse_domain,
+ cfg_mgr));
+}
+
+/// @brief Tests event and state dictionary construction and verification.
+TEST_F(NameRemoveTransactionTest, dictionaryCheck) {
+ NameRemoveStubPtr name_remove;
+ ASSERT_NO_THROW(name_remove = makeTransaction4(FWD_AND_REV_CHG));
+ // Verify that the event and state dictionary validation fails prior
+ // dictionary construction.
+ ASSERT_THROW(name_remove->verifyEvents(), StateModelError);
+ ASSERT_THROW(name_remove->verifyStates(), StateModelError);
+
+ // Construct both dictionaries.
+ ASSERT_NO_THROW(name_remove->defineEvents());
+ ASSERT_NO_THROW(name_remove->defineStates());
+
+ // Verify both event and state dictionaries now pass validation.
+ ASSERT_NO_THROW(name_remove->verifyEvents());
+ ASSERT_NO_THROW(name_remove->verifyStates());
+}
+
+
+/// @brief Tests construction of a DNS update request for removing forward
+/// DNS address RRs.
+TEST_F(NameRemoveTransactionTest, buildRemoveFwdAddressRequest) {
+ // Create a IPv4 forward add transaction.
+ // Verify the request builds without error.
+ // and then verify the request contents.
+ NameRemoveStubPtr name_remove;
+ ASSERT_NO_THROW(name_remove = makeTransaction4(FORWARD_CHG));
+ ASSERT_NO_THROW(name_remove->buildRemoveFwdAddressRequest());
+ checkRemoveFwdAddressRequest(*name_remove);
+
+ // Create a IPv6 forward add transaction.
+ // Verify the request builds without error.
+ // and then verify the request contents.
+ ASSERT_NO_THROW(name_remove = makeTransaction6(FORWARD_CHG));
+ ASSERT_NO_THROW(name_remove->buildRemoveFwdAddressRequest());
+ checkRemoveFwdAddressRequest(*name_remove);
+}
+
+/// @brief Tests construction of a DNS update request for removing forward
+/// dns RR entries.
+TEST_F(NameRemoveTransactionTest, buildRemoveFwdRRsRequest) {
+ // Create a IPv4 forward replace transaction.
+ // Verify the request builds without error.
+ // and then verify the request contents.
+ NameRemoveStubPtr name_remove;
+ ASSERT_NO_THROW(name_remove = makeTransaction4(FORWARD_CHG));
+ ASSERT_NO_THROW(name_remove->buildRemoveFwdRRsRequest());
+ checkRemoveFwdRRsRequest(*name_remove);
+
+ // Create a IPv6 forward replace transaction.
+ // Verify the request builds without error.
+ // and then verify the request contents.
+ ASSERT_NO_THROW(name_remove = makeTransaction6(FORWARD_CHG));
+ ASSERT_NO_THROW(name_remove->buildRemoveFwdRRsRequest());
+ checkRemoveFwdRRsRequest(*name_remove);
+}
+
+/// @brief Tests the construction of a DNS update request for removing a
+/// reverse dns entry.
+TEST_F(NameRemoveTransactionTest, buildRemoveRevPtrsRequest) {
+ // Create a IPv4 reverse replace transaction.
+ // Verify the request builds without error.
+ // and then verify the request contents.
+ NameRemoveStubPtr name_remove;
+ ASSERT_NO_THROW(name_remove = makeTransaction4(REVERSE_CHG));
+ ASSERT_NO_THROW(name_remove->buildRemoveRevPtrsRequest());
+ checkRemoveRevPtrsRequest(*name_remove);
+
+ // Create a IPv6 reverse replace transaction.
+ // Verify the request builds without error.
+ // and then verify the request contents.
+ ASSERT_NO_THROW(name_remove = makeTransaction6(REVERSE_CHG));
+ ASSERT_NO_THROW(name_remove->buildRemoveRevPtrsRequest());
+ checkRemoveRevPtrsRequest(*name_remove);
+}
+
+// Tests the readyHandler functionality.
+// It verifies behavior for the following scenarios:
+//
+// 1. Posted event is START_EVT and request includes only a forward change
+// 2. Posted event is START_EVT and request includes both a forward and a
+// reverse change
+// 3. Posted event is START_EVT and request includes only a reverse change
+// 4. Posted event is invalid
+//
+TEST_F(NameRemoveTransactionTest, readyHandler) {
+ NameRemoveStubPtr name_remove;
+
+ // Create a transaction which includes only a forward change.
+ ASSERT_NO_THROW(name_remove =
+ prepHandlerTest(NameChangeTransaction::READY_ST,
+ StateModel::START_EVT, FORWARD_CHG));
+ // Run readyHandler.
+ EXPECT_NO_THROW(name_remove->readyHandler());
+
+ // Verify that a request requiring only a forward change, transitions to
+ // selecting a forward server.
+ EXPECT_EQ(NameChangeTransaction::SELECTING_FWD_SERVER_ST,
+ name_remove->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::SELECT_SERVER_EVT,
+ name_remove->getNextEvent());
+
+ // Create a transaction which includes both a forward and a reverse change.
+ ASSERT_NO_THROW(name_remove =
+ prepHandlerTest(NameChangeTransaction::READY_ST,
+ StateModel::START_EVT, FWD_AND_REV_CHG));
+ // Run readyHandler.
+ EXPECT_NO_THROW(name_remove->readyHandler());
+
+ // Verify that a request requiring both forward and reverse, starts with
+ // the forward change by transitioning to selecting a forward server.
+ EXPECT_EQ(NameChangeTransaction::SELECTING_FWD_SERVER_ST,
+ name_remove->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::SELECT_SERVER_EVT,
+ name_remove->getNextEvent());
+
+ // Create and prep a reverse only transaction.
+ ASSERT_NO_THROW(name_remove =
+ prepHandlerTest(NameChangeTransaction::READY_ST,
+ StateModel::START_EVT, REVERSE_CHG));
+ // Run readyHandler.
+ EXPECT_NO_THROW(name_remove->readyHandler());
+
+ // Verify that a request requiring only a reverse change, transitions to
+ // selecting a reverse server.
+ EXPECT_EQ(NameChangeTransaction::SELECTING_REV_SERVER_ST,
+ name_remove->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::SELECT_SERVER_EVT,
+ name_remove->getNextEvent());
+
+ // Create and prep transaction, poised to run the handler but with an
+ // invalid event.
+ ASSERT_NO_THROW(name_remove =
+ prepHandlerTest(NameChangeTransaction::READY_ST,
+ StateModel::NOP_EVT));
+
+ // Running the readyHandler should throw.
+ EXPECT_THROW(name_remove->readyHandler(), NameRemoveTransactionError);
+}
+
+
+// Tests the selectingFwdServerHandler functionality.
+// It verifies behavior for the following scenarios:
+//
+// 1. Posted event is SELECT_SERVER_EVT
+// 2. Posted event is SERVER_IO_ERROR_EVT
+// 3. Posted event is invalid
+//
+TEST_F(NameRemoveTransactionTest, selectingFwdServerHandler) {
+ NameRemoveStubPtr name_remove;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(name_remove =
+ prepHandlerTest(NameChangeTransaction::
+ SELECTING_FWD_SERVER_ST,
+ NameChangeTransaction::SELECT_SERVER_EVT));
+
+ // Call selectingFwdServerHandler enough times to select all of the
+ // servers in it's current domain. The first time, it will be with
+ // next event of SELECT_SERVER_EVT. Thereafter it will be with a next
+ // event of SERVER_IO_ERROR_EVT.
+ int num_servers = name_remove->getForwardDomain()->getServers()->size();
+ for (int i = 0; i < num_servers; ++i) {
+ // Run selectingFwdServerHandler.
+ ASSERT_NO_THROW(name_remove->selectingFwdServerHandler())
+ << " num_servers: " << num_servers
+ << " selections: " << i;
+
+ // Verify that a server was selected.
+ ASSERT_TRUE(name_remove->getCurrentServer())
+ << " num_servers: " << num_servers << " selections: " << i;
+
+ // Verify that we transitioned correctly.
+ ASSERT_EQ(NameRemoveTransaction::REMOVING_FWD_ADDRS_ST,
+ name_remove->getCurrState())
+ << " num_servers: " << num_servers << " selections: " << i;
+ ASSERT_EQ(NameChangeTransaction::SERVER_SELECTED_EVT,
+ name_remove->getNextEvent())
+ << " num_servers: " << num_servers << " selections: " << i;
+
+ // Post a server IO error event. This simulates an IO error occurring
+ // and a need to select the new server.
+ ASSERT_NO_THROW(name_remove->postNextEvent(NameChangeTransaction::
+ SERVER_IO_ERROR_EVT))
+ << " num_servers: " << num_servers
+ << " selections: " << i;
+ }
+
+ // We should have exhausted the list of servers. Processing another
+ // SERVER_IO_ERROR_EVT should transition us to failure.
+ EXPECT_NO_THROW(name_remove->selectingFwdServerHandler());
+ EXPECT_EQ(NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ name_remove->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::NO_MORE_SERVERS_EVT,
+ name_remove->getNextEvent());
+
+ // Create and prep transaction, poised to run the handler but with an
+ // invalid event.
+ ASSERT_NO_THROW(name_remove =
+ prepHandlerTest(NameChangeTransaction::
+ SELECTING_FWD_SERVER_ST,
+ StateModel::NOP_EVT));
+
+ // Running the handler should throw.
+ EXPECT_THROW(name_remove->selectingFwdServerHandler(),
+ NameRemoveTransactionError);
+}
+
+// ************************ addingFwdAddrHandler Tests *****************
+
+// Tests that removingFwdAddrsHandler rejects invalid events.
+TEST_F(NameRemoveTransactionTest, removingFwdAddrsHandler_InvalidEvent) {
+ NameRemoveStubPtr name_remove;
+ // Create and prep a transaction, poised to run the handler but with
+ // an invalid event.
+ ASSERT_NO_THROW(name_remove =
+ prepHandlerTest(NameRemoveTransaction::
+ REMOVING_FWD_ADDRS_ST,
+ StateModel::NOP_EVT));
+
+ // Running the handler should throw.
+ EXPECT_THROW(name_remove->removingFwdAddrsHandler(),
+ NameRemoveTransactionError);
+}
+
+
+// Tests addingFwdAddrsHandler with the following scenario:
+//
+// The request includes only a forward change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The update request is sent without error.
+// A server response is received which indicates successful update
+//
+TEST_F(NameRemoveTransactionTest, removingFwdAddrsHandler_FwdOnlyOK) {
+ NameRemoveStubPtr name_remove;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(name_remove =
+ prepHandlerTest(NameRemoveTransaction::
+ REMOVING_FWD_ADDRS_ST,
+ NameChangeTransaction::
+ SERVER_SELECTED_EVT, FORWARD_CHG));
+
+ // Should not be an update message yet.
+ D2UpdateMessagePtr update_msg = name_remove->getDnsUpdateRequest();
+ ASSERT_FALSE(update_msg);
+
+ // At this point completion flags should be false.
+ EXPECT_FALSE(name_remove->getForwardChangeCompleted());
+ EXPECT_FALSE(name_remove->getReverseChangeCompleted());
+
+ // Run removingFwdAddrsHandler to construct and send the request.
+ EXPECT_NO_THROW(name_remove->removingFwdAddrsHandler());
+
+ // Verify that an update message was constructed properly.
+ checkRemoveFwdAddressRequest(*name_remove);
+
+ // Verify that we are still in this state and next event is NOP_EVT.
+ // This indicates we "sent" the message and are waiting for IO completion.
+ EXPECT_EQ(NameRemoveTransaction::REMOVING_FWD_ADDRS_ST,
+ name_remove->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::NOP_EVT,
+ name_remove->getNextEvent());
+
+ // Simulate receiving a successful update response.
+ name_remove->fakeResponse(DNSClient::SUCCESS, dns::Rcode::NOERROR());
+
+ // Run removingFwdAddrsHandler again to process the response.
+ EXPECT_NO_THROW(name_remove->removingFwdAddrsHandler());
+
+ // Completion flags should both still be false, as we are only partly
+ // done with forward updates.
+ EXPECT_FALSE(name_remove->getForwardChangeCompleted());
+ EXPECT_FALSE(name_remove->getReverseChangeCompleted());
+
+ // Since we succeeded, we should now attempt to remove any remaining
+ // forward RRs.
+ // Verify that we transitioned correctly.
+ EXPECT_EQ(NameRemoveTransaction::REMOVING_FWD_RRS_ST,
+ name_remove->getCurrState());
+ EXPECT_EQ(NameRemoveTransaction::UPDATE_OK_EVT,
+ name_remove->getNextEvent());
+}
+
+// Tests removingFwdAddrsHandler with the following scenario:
+//
+// The request includes only a forward change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The update request is sent without error.
+// A server response is received which indicates FQDN is not in use.
+//
+TEST_F(NameRemoveTransactionTest, removingFwdAddrsHandler_FqdnNotInUse) {
+ NameRemoveStubPtr name_remove;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(name_remove =
+ prepHandlerTest(NameRemoveTransaction::
+ REMOVING_FWD_ADDRS_ST,
+ NameChangeTransaction::
+ SERVER_SELECTED_EVT, FORWARD_CHG));
+
+ // Run removingFwdAddrsHandler to construct and send the request.
+ EXPECT_NO_THROW(name_remove->removingFwdAddrsHandler());
+
+ // Simulate receiving a RRSET does not exist.
+ name_remove->fakeResponse(DNSClient::SUCCESS, dns::Rcode::NXRRSET());
+
+ // Run removingFwdAddrsHandler again to process the response.
+ EXPECT_NO_THROW(name_remove->removingFwdAddrsHandler());
+
+ // Completion flags should both still be false, as we are only partly
+ // done with forward updates.
+ EXPECT_FALSE(name_remove->getForwardChangeCompleted());
+ EXPECT_FALSE(name_remove->getReverseChangeCompleted());
+
+ // There was no address RR to remove, but we will still make sure there
+ // are no other RRs for this FQDN.
+ // Verify that we transitioned correctly.
+ EXPECT_EQ(NameRemoveTransaction::REMOVING_FWD_RRS_ST,
+ name_remove->getCurrState());
+ EXPECT_EQ(NameRemoveTransaction::UPDATE_OK_EVT,
+ name_remove->getNextEvent());
+}
+
+
+// Tests removingFwdAddrsHandler with the following scenario:
+//
+// The request includes a forward and reverse change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The update request is sent without error.
+// A server response is received which indicates the update was rejected.
+//
+TEST_F(NameRemoveTransactionTest, removingFwdAddrsHandler_OtherRcode) {
+ NameRemoveStubPtr name_remove;
+
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(name_remove =
+ prepHandlerTest(NameRemoveTransaction::
+ REMOVING_FWD_ADDRS_ST,
+ NameChangeTransaction::
+ SERVER_SELECTED_EVT));
+
+ // Select a server to satisfy log statements.
+ ASSERT_TRUE(name_remove->selectFwdServer());
+
+ // Run removingFwdAddrsHandler to construct and send the request.
+ EXPECT_NO_THROW(name_remove->removingFwdAddrsHandler());
+
+ // Simulate receiving server rejection response. Per RFC, anything other
+ // than no error or FQDN not in use is failure. Arbitrarily choosing
+ // refused.
+ name_remove->fakeResponse(DNSClient::SUCCESS, dns::Rcode::REFUSED());
+
+ // Run removingFwdAddrsHandler again to process the response.
+ EXPECT_NO_THROW(name_remove->removingFwdAddrsHandler());
+
+ // Completion flags should still be false.
+ EXPECT_FALSE(name_remove->getForwardChangeCompleted());
+ EXPECT_FALSE(name_remove->getReverseChangeCompleted());
+
+ // We should have failed the transaction. Verify that we transitioned
+ // correctly.
+ EXPECT_EQ(NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ name_remove->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::UPDATE_FAILED_EVT,
+ name_remove->getNextEvent());
+}
+
+
+// Tests removingFwdAddrsHandler with the following scenario:
+//
+// The request includes a forward and reverse change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The update request send times out MAX_UPDATE_TRIES_PER_SERVER times.
+//
+TEST_F(NameRemoveTransactionTest, removingFwdAddrsHandler_Timeout) {
+ NameRemoveStubPtr name_remove;
+
+ // Create and prep a transaction, poised to run the handler.
+ // The log message issued when this test succeeds, displays the
+ // selected server, so we need to select a server before running this
+ // test.
+ ASSERT_NO_THROW(name_remove =
+ prepHandlerTest(NameRemoveTransaction::
+ REMOVING_FWD_ADDRS_ST,
+ NameChangeTransaction::
+ SERVER_SELECTED_EVT));
+
+ // Select a server to satisfy log statements.
+ ASSERT_TRUE(name_remove->selectFwdServer());
+
+ // Verify that we can make maximum number of update attempts permitted
+ // and then transition to selecting a new server.
+ int max_tries = NameChangeTransaction::MAX_UPDATE_TRIES_PER_SERVER;
+ for (int i = 1; i <= max_tries; ++i) {
+ // Run removingFwdAddrsHandler to send the request.
+ EXPECT_NO_THROW(name_remove->removingFwdAddrsHandler());
+
+ // Simulate a server IO timeout.
+ name_remove->setDnsUpdateStatus(DNSClient::TIMEOUT);
+ name_remove->postNextEvent(NameChangeTransaction::IO_COMPLETED_EVT);
+
+ // Run removingFwdAddrsHandler again to process the response.
+ EXPECT_NO_THROW(name_remove->removingFwdAddrsHandler());
+
+ // Completion flags should be false.
+ EXPECT_FALSE(name_remove->getForwardChangeCompleted());
+ EXPECT_FALSE(name_remove->getReverseChangeCompleted());
+
+ if (i < max_tries) {
+ // We should be ready to try again.
+ EXPECT_EQ(NameRemoveTransaction::REMOVING_FWD_ADDRS_ST,
+ name_remove->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::SERVER_SELECTED_EVT,
+ name_remove->getNextEvent());
+ } else {
+ // Server retries should be exhausted, time for a new server.
+ EXPECT_EQ(NameRemoveTransaction::SELECTING_FWD_SERVER_ST,
+ name_remove->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::SERVER_IO_ERROR_EVT,
+ name_remove->getNextEvent());
+ }
+ }
+}
+
+// Tests removingFwdAddrsHandler with the following scenario:
+//
+// The request includes a forward and reverse change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The update request is sent but a corrupt response is received, this occurs
+// MAX_UPDATE_TRIES_PER_SERVER times.
+//
+TEST_F(NameRemoveTransactionTest, removingFwdAddrsHandler_InvalidResponse) {
+ NameRemoveStubPtr name_remove;
+
+ // Create and prep a transaction, poised to run the handler.
+ // The log message issued when this test succeeds, displays the
+ // selected server, so we need to select a server before running this
+ // test.
+ ASSERT_NO_THROW(name_remove =
+ prepHandlerTest(NameRemoveTransaction::
+ REMOVING_FWD_ADDRS_ST,
+ NameChangeTransaction::
+ SERVER_SELECTED_EVT));
+
+ // Select a server to satisfy log statements.
+ ASSERT_TRUE(name_remove->selectFwdServer());
+
+ // Verify that we can make maximum number of update attempts permitted
+ // and then transition to selecting a new server.
+ int max_tries = NameChangeTransaction::MAX_UPDATE_TRIES_PER_SERVER;
+ for (int i = 1; i <= max_tries; ++i) {
+ // Run removingFwdAddrsHandler to construct send the request.
+ EXPECT_NO_THROW(name_remove->removingFwdAddrsHandler());
+
+ // Simulate a corrupt server response.
+ name_remove->setDnsUpdateStatus(DNSClient::INVALID_RESPONSE);
+ name_remove->postNextEvent(NameChangeTransaction::IO_COMPLETED_EVT);
+
+ // Run removingFwdAddrsHandler again to process the response.
+ EXPECT_NO_THROW(name_remove->removingFwdAddrsHandler());
+
+ // Completion flags should be false.
+ EXPECT_FALSE(name_remove->getForwardChangeCompleted());
+ EXPECT_FALSE(name_remove->getReverseChangeCompleted());
+
+ if (i < max_tries) {
+ // We should be ready to try again.
+ EXPECT_EQ(NameRemoveTransaction::REMOVING_FWD_ADDRS_ST,
+ name_remove->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::SERVER_SELECTED_EVT,
+ name_remove->getNextEvent());
+ } else {
+ // Server retries should be exhausted, time for a new server.
+ EXPECT_EQ(NameRemoveTransaction::SELECTING_FWD_SERVER_ST,
+ name_remove->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::SERVER_IO_ERROR_EVT,
+ name_remove->getNextEvent());
+ }
+ }
+
+}
+
+// ************************ removingFwdRRsHandler Tests *****************
+
+// Tests that removingFwdRRsHandler rejects invalid events.
+TEST_F(NameRemoveTransactionTest, removingFwdRRsHandler_InvalidEvent) {
+ NameRemoveStubPtr name_remove;
+ // Create and prep a transaction, poised to run the handler but with
+ // an invalid event.
+ ASSERT_NO_THROW(name_remove =
+ prepHandlerTest(NameRemoveTransaction::
+ REMOVING_FWD_RRS_ST,
+ StateModel::NOP_EVT));
+
+ // Running the handler should throw.
+ EXPECT_THROW(name_remove->removingFwdRRsHandler(),
+ NameRemoveTransactionError);
+}
+
+// Tests removingFwdRRsHandler with the following scenario:
+//
+// The request includes only a forward change.
+// Initial posted event is UPDATE_OK_EVT.
+// The update request is sent without error.
+// A server response is received which indicates successful update.
+//
+TEST_F(NameRemoveTransactionTest, removingFwdRRsHandler_FwdOnlyOK) {
+ NameRemoveStubPtr name_remove;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(name_remove =
+ prepHandlerTest(NameRemoveTransaction::
+ REMOVING_FWD_ADDRS_ST,
+ NameChangeTransaction::
+ UPDATE_OK_EVT, FORWARD_CHG));
+
+ // Should not be an update message yet.
+ D2UpdateMessagePtr update_msg = name_remove->getDnsUpdateRequest();
+ ASSERT_FALSE(update_msg);
+
+ // At this point completion flags should be false.
+ EXPECT_FALSE(name_remove->getForwardChangeCompleted());
+ EXPECT_FALSE(name_remove->getReverseChangeCompleted());
+
+ // Run removingFwdRRsHandler to construct and send the request.
+ EXPECT_NO_THROW(name_remove->removingFwdRRsHandler());
+
+ // Verify that an update message was constructed properly.
+ checkRemoveFwdRRsRequest(*name_remove);
+
+ // Verify that we are still in this state and next event is NOP_EVT.
+ // This indicates we "sent" the message and are waiting for IO completion.
+ EXPECT_EQ(NameRemoveTransaction::REMOVING_FWD_ADDRS_ST,
+ name_remove->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::NOP_EVT,
+ name_remove->getNextEvent());
+
+ // Simulate receiving a successful update response.
+ name_remove->fakeResponse(DNSClient::SUCCESS, dns::Rcode::NOERROR());
+
+ // Run removingFwdRRsHandler again to process the response.
+ EXPECT_NO_THROW(name_remove->removingFwdRRsHandler());
+
+ // Forward completion should be true, reverse should be false.
+ EXPECT_TRUE(name_remove->getForwardChangeCompleted());
+ EXPECT_FALSE(name_remove->getReverseChangeCompleted());
+
+ // Since it is a forward only change, we should be done.
+ // Verify that we transitioned correctly.
+ EXPECT_EQ(NameChangeTransaction::PROCESS_TRANS_OK_ST,
+ name_remove->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::UPDATE_OK_EVT,
+ name_remove->getNextEvent());
+}
+
+// Tests removingFwdRRsHandler with the following scenario:
+//
+// The request includes only a forward change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The update request is sent without error.
+// A server response is received which indicates successful update.
+//
+TEST_F(NameRemoveTransactionTest, removingFwdRRsHandler_FwdOnlyOK2) {
+ NameRemoveStubPtr name_remove;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(name_remove =
+ prepHandlerTest(NameRemoveTransaction::
+ REMOVING_FWD_ADDRS_ST,
+ NameChangeTransaction::
+ SERVER_SELECTED_EVT, FORWARD_CHG));
+
+ // Run removingFwdRRsHandler to construct and send the request.
+ EXPECT_NO_THROW(name_remove->removingFwdRRsHandler());
+
+ // Simulate receiving a successful update response.
+ name_remove->fakeResponse(DNSClient::SUCCESS, dns::Rcode::NOERROR());
+
+ // Run removingFwdRRsHandler again to process the response.
+ EXPECT_NO_THROW(name_remove->removingFwdRRsHandler());
+
+ // Forward completion should be true, reverse should be false.
+ EXPECT_TRUE(name_remove->getForwardChangeCompleted());
+ EXPECT_FALSE(name_remove->getReverseChangeCompleted());
+
+ // Since it is a forward only change, we should be done.
+ // Verify that we transitioned correctly.
+ EXPECT_EQ(NameChangeTransaction::PROCESS_TRANS_OK_ST,
+ name_remove->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::UPDATE_OK_EVT,
+ name_remove->getNextEvent());
+}
+
+
+// Tests removingFwdRRsHandler with the following scenario:
+//
+// The request includes a forward and reverse change.
+// Initial posted event is UPDATE_OK_EVT.
+// The update request is sent without error.
+// A server response is received which indicates successful update.
+//
+TEST_F(NameRemoveTransactionTest, removingFwdRRsHandler_FwdAndRevOK) {
+ NameRemoveStubPtr name_remove;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(name_remove =
+ prepHandlerTest(NameRemoveTransaction::
+ REMOVING_FWD_ADDRS_ST,
+ NameChangeTransaction::
+ UPDATE_OK_EVT, FWD_AND_REV_CHG));
+
+ // Run removingFwdRRsHandler to construct and send the request.
+ EXPECT_NO_THROW(name_remove->removingFwdRRsHandler());
+
+ // Simulate receiving a successful update response.
+ name_remove->fakeResponse(DNSClient::SUCCESS, dns::Rcode::NOERROR());
+
+ // Run removingFwdRRsHandler again to process the response.
+ EXPECT_NO_THROW(name_remove->removingFwdRRsHandler());
+
+ // Forward change completion should be true, reverse flag should be false.
+ EXPECT_TRUE(name_remove->getForwardChangeCompleted());
+ EXPECT_FALSE(name_remove->getReverseChangeCompleted());
+
+ // Since the request also includes a reverse change we should
+ // be poised to start it. Verify that we transitioned correctly.
+ EXPECT_EQ(NameChangeTransaction::SELECTING_REV_SERVER_ST,
+ name_remove->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::SELECT_SERVER_EVT,
+ name_remove->getNextEvent());
+}
+
+// Tests removingFwdAddrsHandler with the following scenario:
+//
+// The request includes a forward and reverse change.
+// Initial posted event is UPDATE_OK_EVT.
+// The update request is sent without error.
+// A server response is received which indicates the FQDN is NOT in use.
+//
+TEST_F(NameRemoveTransactionTest, removingFwdRRsHandler_FqdnNotInUse) {
+ NameRemoveStubPtr name_remove;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(name_remove =
+ prepHandlerTest(NameRemoveTransaction::
+ REMOVING_FWD_ADDRS_ST,
+ NameChangeTransaction::
+ UPDATE_OK_EVT, FORWARD_CHG));
+
+ // Run removingFwdRRsHandler to construct and send the request.
+ EXPECT_NO_THROW(name_remove->removingFwdRRsHandler());
+
+ // Simulate receiving a RRSET does not exist response.
+ name_remove->fakeResponse(DNSClient::SUCCESS, dns::Rcode::NXRRSET());
+
+ // Run removingFwdRRsHandler again to process the response.
+ EXPECT_NO_THROW(name_remove->removingFwdRRsHandler());
+
+ // Forward completion flag should be true, reverse should still be false.
+ EXPECT_TRUE(name_remove->getForwardChangeCompleted());
+ EXPECT_FALSE(name_remove->getReverseChangeCompleted());
+
+ // The FQDN is no longer in use, RFC is unclear about this,
+ // but we will treat this as success.
+ // Since it is a forward only change, we should be done.
+ // Verify that we transitioned correctly.
+ EXPECT_EQ(NameChangeTransaction::PROCESS_TRANS_OK_ST,
+ name_remove->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::UPDATE_OK_EVT,
+ name_remove->getNextEvent());
+}
+
+// Tests removingFwdRRsHandler with the following scenario:
+//
+// The request includes a forward and reverse change.
+// The update request is sent without error.
+// A server response is received which indicates the update was rejected.
+//
+TEST_F(NameRemoveTransactionTest, removingFwdRRsHandler_OtherRcode) {
+ NameRemoveStubPtr name_remove;
+ // Create the transaction.
+ ASSERT_NO_THROW(name_remove =
+ prepHandlerTest(NameRemoveTransaction::
+ REMOVING_FWD_ADDRS_ST,
+ NameChangeTransaction::
+ UPDATE_OK_EVT, FWD_AND_REV_CHG));
+
+ // Select a server to satisfy log statements.
+ ASSERT_TRUE(name_remove->selectFwdServer());
+
+ // Run removingFwdRRsHandler to construct and send the request.
+ EXPECT_NO_THROW(name_remove->removingFwdRRsHandler());
+
+ // Simulate receiving server rejection response. Per RFC, anything other
+ // than no error is failure (we are also treating FQDN not in use is
+ // success). Arbitrarily choosing refused.
+ name_remove->fakeResponse(DNSClient::SUCCESS, dns::Rcode::REFUSED());
+
+ // Run removingFwdRRsHandler again to process the response.
+ EXPECT_NO_THROW(name_remove->removingFwdRRsHandler());
+
+ // Completion flags should still be false.
+ EXPECT_FALSE(name_remove->getForwardChangeCompleted());
+ EXPECT_FALSE(name_remove->getReverseChangeCompleted());
+
+ // We should have failed the transaction. Verify that we transitioned
+ // correctly.
+ EXPECT_EQ(NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ name_remove->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::UPDATE_FAILED_EVT,
+ name_remove->getNextEvent());
+}
+
+// Tests removingFwdRRsHandler with the following scenario:
+//
+// The request includes a forward and reverse change.
+// Initial posted event is UPDATE_OK_EVT.
+// The update request send times out MAX_UPDATE_TRIES_PER_SERVER times.
+//
+TEST_F(NameRemoveTransactionTest, removingFwdRRsHandler_Timeout) {
+ NameRemoveStubPtr name_remove;
+
+ // Create the transaction.
+ ASSERT_NO_THROW(name_remove =
+ prepHandlerTest(NameRemoveTransaction::
+ REMOVING_FWD_RRS_ST,
+ NameChangeTransaction::
+ UPDATE_OK_EVT, FWD_AND_REV_CHG));
+
+ // Select a server to satisfy log statements.
+ ASSERT_TRUE(name_remove->selectFwdServer());
+
+ // Verify that we can make maximum number of update attempts permitted
+ // and then transition to selecting a new server.
+ int max_tries = NameChangeTransaction::MAX_UPDATE_TRIES_PER_SERVER;
+ for (int i = 1; i <= max_tries; ++i) {
+ // Run removingFwdRRsHandler to send the request.
+ EXPECT_NO_THROW(name_remove->removingFwdRRsHandler());
+
+ // Simulate a server IO timeout.
+ name_remove->setDnsUpdateStatus(DNSClient::TIMEOUT);
+ name_remove->postNextEvent(NameChangeTransaction::IO_COMPLETED_EVT);
+
+ // Run removingFwdRRsHandler again to process the response.
+ EXPECT_NO_THROW(name_remove->removingFwdRRsHandler());
+
+ // Completion flags should be false.
+ EXPECT_FALSE(name_remove->getForwardChangeCompleted());
+ EXPECT_FALSE(name_remove->getReverseChangeCompleted());
+
+ if (i < max_tries) {
+ // We should be ready to try again.
+ EXPECT_EQ(NameRemoveTransaction::REMOVING_FWD_RRS_ST,
+ name_remove->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::SERVER_SELECTED_EVT,
+ name_remove->getNextEvent());
+ } else {
+ // Server retries should be exhausted.
+ // We should abandon the transaction.
+ EXPECT_EQ(NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ name_remove->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::SERVER_IO_ERROR_EVT,
+ name_remove->getNextEvent());
+ }
+ }
+}
+
+// Tests removingFwdRRsHandler with the following scenario:
+//
+// The request includes a forward and reverse change.
+// Initial posted event is UPDATE_OK_EVT.
+// The update request is sent but a corrupt response is received, this occurs
+// MAX_UPDATE_TRIES_PER_SERVER times.
+//
+TEST_F(NameRemoveTransactionTest, removingFwdRRsHandler_InvalidResponse) {
+ NameRemoveStubPtr name_remove;
+
+ // Create the transaction.
+ ASSERT_NO_THROW(name_remove =
+ prepHandlerTest(NameRemoveTransaction::
+ REMOVING_FWD_RRS_ST,
+ NameChangeTransaction::
+ UPDATE_OK_EVT, FWD_AND_REV_CHG));
+
+ // Select a server to satisfy log statements.
+ ASSERT_TRUE(name_remove->selectFwdServer());
+
+ // Verify that we can make maximum number of update attempts permitted
+ // and then transition to selecting a new server.
+ int max_tries = NameChangeTransaction::MAX_UPDATE_TRIES_PER_SERVER;
+ for (int i = 1; i <= max_tries; ++i) {
+ // Run removingFwdRRsHandler to send the request.
+ EXPECT_NO_THROW(name_remove->removingFwdRRsHandler());
+
+ // Simulate a corrupt server response.
+ name_remove->setDnsUpdateStatus(DNSClient::INVALID_RESPONSE);
+ name_remove->postNextEvent(NameChangeTransaction::IO_COMPLETED_EVT);
+
+ // Run removingFwdRRsHandler again to process the response.
+ EXPECT_NO_THROW(name_remove->removingFwdRRsHandler());
+
+ // Completion flags should be false.
+ EXPECT_FALSE(name_remove->getForwardChangeCompleted());
+ EXPECT_FALSE(name_remove->getReverseChangeCompleted());
+
+ if (i < max_tries) {
+ // We should be ready to try again.
+ EXPECT_EQ(NameRemoveTransaction::REMOVING_FWD_RRS_ST,
+ name_remove->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::SERVER_SELECTED_EVT,
+ name_remove->getNextEvent());
+ } else {
+ // Server retries should be exhausted.
+ // We should abandon the transaction.
+ EXPECT_EQ(NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ name_remove->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::SERVER_IO_ERROR_EVT,
+ name_remove->getNextEvent());
+ }
+ }
+}
+
+
+// Tests the selectingRevServerHandler functionality.
+// It verifies behavior for the following scenarios:
+//
+// 1. Posted event is SELECT_SERVER_EVT
+// 2. Posted event is SERVER_IO_ERROR_EVT
+// 3. Posted event is invalid
+//
+TEST_F(NameRemoveTransactionTest, selectingRevServerHandler) {
+ NameRemoveStubPtr name_remove;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(name_remove =
+ prepHandlerTest(NameChangeTransaction::
+ SELECTING_REV_SERVER_ST,
+ NameChangeTransaction::SELECT_SERVER_EVT));
+
+ // Call selectingRevServerHandler enough times to select all of the
+ // servers in it's current domain. The first time, it will be with
+ // next event of SELECT_SERVER_EVT. Thereafter it will be with a next
+ // event of SERVER_IO_ERROR_EVT.
+ int num_servers = name_remove->getReverseDomain()->getServers()->size();
+ for (int i = 0; i < num_servers; ++i) {
+ // Run selectingRevServerHandler.
+ ASSERT_NO_THROW(name_remove->selectingRevServerHandler())
+ << " num_servers: " << num_servers
+ << " selections: " << i;
+
+ // Verify that a server was selected.
+ ASSERT_TRUE(name_remove->getCurrentServer())
+ << " num_servers: " << num_servers
+ << " selections: " << i;
+
+ // Verify that we transitioned correctly.
+ ASSERT_EQ(NameRemoveTransaction::REMOVING_REV_PTRS_ST,
+ name_remove->getCurrState())
+ << " num_servers: " << num_servers << " selections: " << i;
+ ASSERT_EQ(NameChangeTransaction::SERVER_SELECTED_EVT,
+ name_remove->getNextEvent())
+ << " num_servers: " << num_servers << " selections: " << i;
+
+ // Post a server IO error event. This simulates an IO error occurring
+ // and a need to select the new server.
+ ASSERT_NO_THROW(name_remove->postNextEvent(NameChangeTransaction::
+ SERVER_IO_ERROR_EVT))
+ << " num_servers: " << num_servers
+ << " selections: " << i;
+ }
+
+ // We should have exhausted the list of servers. Processing another
+ // SERVER_IO_ERROR_EVT should transition us to failure.
+ EXPECT_NO_THROW(name_remove->selectingRevServerHandler());
+ EXPECT_EQ(NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ name_remove->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::NO_MORE_SERVERS_EVT,
+ name_remove->getNextEvent());
+
+ // Create and prep transaction, poised to run the handler but with an
+ // invalid event.
+ ASSERT_NO_THROW(name_remove =
+ prepHandlerTest(NameChangeTransaction::
+ SELECTING_REV_SERVER_ST,
+ StateModel::NOP_EVT));
+
+ // Running the handler should throw.
+ EXPECT_THROW(name_remove->selectingRevServerHandler(),
+ NameRemoveTransactionError);
+}
+
+//************************** removingRevPtrsHandler tests *****************
+
+// Tests that removingRevPtrsHandler rejects invalid events.
+TEST_F(NameRemoveTransactionTest, removingRevPtrsHandler_InvalidEvent) {
+ NameRemoveStubPtr name_remove;
+ // Create and prep a transaction, poised to run the handler but with
+ // an invalid event.
+ ASSERT_NO_THROW(name_remove =
+ prepHandlerTest(NameRemoveTransaction::
+ REMOVING_REV_PTRS_ST,
+ StateModel::NOP_EVT));
+
+ // Running the handler should throw.
+ EXPECT_THROW(name_remove->removingRevPtrsHandler(),
+ NameRemoveTransactionError);
+}
+
+// Tests removingRevPtrsHandler with the following scenario:
+//
+// The request includes only a reverse change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The update request is sent without error.
+// A server response is received which indicates successful update.
+//
+TEST_F(NameRemoveTransactionTest, removingRevPtrsHandler_RevOnlyOK) {
+ NameRemoveStubPtr name_remove;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(name_remove =
+ prepHandlerTest(NameRemoveTransaction::
+ REMOVING_REV_PTRS_ST,
+ NameChangeTransaction::
+ SERVER_SELECTED_EVT, REVERSE_CHG));
+
+ // Should not be an update message yet.
+ D2UpdateMessagePtr update_msg = name_remove->getDnsUpdateRequest();
+ ASSERT_FALSE(update_msg);
+
+ // At this point completion flags should be false.
+ EXPECT_FALSE(name_remove->getForwardChangeCompleted());
+ EXPECT_FALSE(name_remove->getReverseChangeCompleted());
+
+ // Run removingRevPtrsHandler to construct and send the request.
+ EXPECT_NO_THROW(name_remove->removingRevPtrsHandler());
+
+ // Verify that an update message was constructed properly.
+ checkRemoveRevPtrsRequest(*name_remove);
+
+ // Verify that we are still in this state and next event is NOP_EVT.
+ // This indicates we "sent" the message and are waiting for IO completion.
+ EXPECT_EQ(NameRemoveTransaction::REMOVING_REV_PTRS_ST,
+ name_remove->getCurrState());
+ EXPECT_EQ(StateModel::NOP_EVT,
+ name_remove->getNextEvent());
+
+ // Simulate receiving a successful update response.
+ name_remove->fakeResponse(DNSClient::SUCCESS, dns::Rcode::NOERROR());
+
+ // Run removingRevPtrsHandler again to process the response.
+ EXPECT_NO_THROW(name_remove->removingRevPtrsHandler());
+
+ // Forward completion should be false, reverse should be true.
+ EXPECT_FALSE(name_remove->getForwardChangeCompleted());
+ EXPECT_TRUE(name_remove->getReverseChangeCompleted());
+
+ // Since it is a reverse change, we should be done.
+ // Verify that we transitioned correctly.
+ EXPECT_EQ(NameChangeTransaction::PROCESS_TRANS_OK_ST,
+ name_remove->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::UPDATE_OK_EVT,
+ name_remove->getNextEvent());
+}
+
+// Tests removingRevPtrsHandler with the following scenario:
+//
+// The request includes only a reverse change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The update request is sent without error.
+// A server response is received which indicates FQDN is NOT in use.
+//
+TEST_F(NameRemoveTransactionTest, removingRevPtrsHandler_FqdnNotInUse) {
+ NameRemoveStubPtr name_remove;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(name_remove =
+ prepHandlerTest(NameRemoveTransaction::
+ REMOVING_REV_PTRS_ST,
+ NameChangeTransaction::
+ SERVER_SELECTED_EVT, REVERSE_CHG));
+
+ // Should not be an update message yet.
+ D2UpdateMessagePtr update_msg = name_remove->getDnsUpdateRequest();
+ ASSERT_FALSE(update_msg);
+
+ // At this point completion flags should be false.
+ EXPECT_FALSE(name_remove->getForwardChangeCompleted());
+ EXPECT_FALSE(name_remove->getReverseChangeCompleted());
+
+ // Run removingRevPtrsHandler to construct and send the request.
+ EXPECT_NO_THROW(name_remove->removingRevPtrsHandler());
+
+ // Verify that an update message was constructed properly.
+ checkRemoveRevPtrsRequest(*name_remove);
+
+ // Verify that we are still in this state and next event is NOP_EVT.
+ // This indicates we "sent" the message and are waiting for IO completion.
+ EXPECT_EQ(NameRemoveTransaction::REMOVING_REV_PTRS_ST,
+ name_remove->getCurrState());
+ EXPECT_EQ(StateModel::NOP_EVT,
+ name_remove->getNextEvent());
+
+ // Simulate receiving a RRSET does not exist.
+ name_remove->fakeResponse(DNSClient::SUCCESS, dns::Rcode::NXRRSET());
+
+ // Run removingRevPtrsHandler again to process the response.
+ EXPECT_NO_THROW(name_remove->removingRevPtrsHandler());
+
+ // Forward completion should be false, reverse should be true.
+ EXPECT_FALSE(name_remove->getForwardChangeCompleted());
+ EXPECT_TRUE(name_remove->getReverseChangeCompleted());
+
+ // Since it is a reverse change, we should be done.
+ // Verify that we transitioned correctly.
+ EXPECT_EQ(NameChangeTransaction::PROCESS_TRANS_OK_ST,
+ name_remove->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::UPDATE_OK_EVT,
+ name_remove->getNextEvent());
+}
+
+// Tests removingRevPtrsHandler with the following scenario:
+//
+// The request includes only a reverse change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The update request is sent without error.
+// A server response is received which indicates the update was rejected.
+//
+TEST_F(NameRemoveTransactionTest, removingRevPtrsHandler_OtherRcode) {
+ NameRemoveStubPtr name_remove;
+ // Create the transaction.
+ ASSERT_NO_THROW(name_remove =
+ prepHandlerTest(NameRemoveTransaction::
+ REMOVING_REV_PTRS_ST,
+ NameChangeTransaction::
+ SERVER_SELECTED_EVT, REVERSE_CHG));
+
+ // Select a server to satisfy log statements.
+ ASSERT_TRUE(name_remove->selectRevServer());
+
+ // Run removingRevPtrsHandler to construct and send the request.
+ EXPECT_NO_THROW(name_remove->removingRevPtrsHandler());
+
+ // Simulate receiving server rejection response. Per RFC, anything other
+ // than no error is failure. Arbitrarily choosing refused.
+ name_remove->fakeResponse(DNSClient::SUCCESS, dns::Rcode::REFUSED());
+
+ // Run removingRevPtrsHandler again to process the response.
+ EXPECT_NO_THROW(name_remove->removingRevPtrsHandler());
+
+ // Completion flags should still be false.
+ EXPECT_FALSE(name_remove->getForwardChangeCompleted());
+ EXPECT_FALSE(name_remove->getReverseChangeCompleted());
+
+ // We should have failed the transaction. Verify that we transitioned
+ // correctly.
+ EXPECT_EQ(NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ name_remove->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::UPDATE_FAILED_EVT,
+ name_remove->getNextEvent());
+}
+
+// Tests removingRevPtrsHandler with the following scenario:
+//
+// The request includes only a reverse change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The update request send times out MAX_UPDATE_TRIES_PER_SERVER times.
+//
+TEST_F(NameRemoveTransactionTest, removingRevPtrsHandler_Timeout) {
+ NameRemoveStubPtr name_remove;
+ // Create the transaction.
+ ASSERT_NO_THROW(name_remove =
+ prepHandlerTest(NameRemoveTransaction::
+ REMOVING_REV_PTRS_ST,
+ NameChangeTransaction::
+ SERVER_SELECTED_EVT, REVERSE_CHG));
+
+ // Select a server to satisfy log statements.
+ ASSERT_TRUE(name_remove->selectRevServer());
+
+ // Verify that we can make maximum number of update attempts permitted
+ // and then transition to selecting a new server.
+ int max_tries = NameChangeTransaction::MAX_UPDATE_TRIES_PER_SERVER;
+ for (int i = 1; i <= max_tries; ++i) {
+ // Run removingRevPtrsHandler to send the request.
+ EXPECT_NO_THROW(name_remove->removingRevPtrsHandler());
+
+ // Simulate a server IO timeout.
+ name_remove->setDnsUpdateStatus(DNSClient::TIMEOUT);
+ name_remove->postNextEvent(NameChangeTransaction::IO_COMPLETED_EVT);
+
+ // Run removingRevPtrsHandler again to process the response.
+ EXPECT_NO_THROW(name_remove->removingRevPtrsHandler());
+
+ // Completion flags should be false.
+ EXPECT_FALSE(name_remove->getForwardChangeCompleted());
+ EXPECT_FALSE(name_remove->getReverseChangeCompleted());
+
+ if (i < max_tries) {
+ // We should be ready to try again.
+ EXPECT_EQ(NameRemoveTransaction::REMOVING_REV_PTRS_ST,
+ name_remove->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::SERVER_SELECTED_EVT,
+ name_remove->getNextEvent());
+ } else {
+ // Server retries should be exhausted, time for a new server.
+ EXPECT_EQ(NameChangeTransaction::SELECTING_REV_SERVER_ST,
+ name_remove->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::SERVER_IO_ERROR_EVT,
+ name_remove->getNextEvent());
+ }
+ }
+}
+
+
+// Tests removingRevPtrsHandler with the following scenario:
+//
+// The request includes only a reverse change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The update request is sent but a corrupt response is received, this occurs
+// MAX_UPDATE_TRIES_PER_SERVER times.
+//
+TEST_F(NameRemoveTransactionTest, removingRevPtrsHandler_CorruptResponse) {
+ NameRemoveStubPtr name_remove;
+ // Create the transaction.
+ ASSERT_NO_THROW(name_remove =
+ prepHandlerTest(NameRemoveTransaction::
+ REMOVING_REV_PTRS_ST,
+ NameChangeTransaction::
+ SERVER_SELECTED_EVT, REVERSE_CHG));
+
+ // Select a server to satisfy log statements.
+ ASSERT_TRUE(name_remove->selectRevServer());
+
+ // Verify that we can make maximum number of update attempts permitted
+ // and then transition to selecting a new server.
+ int max_tries = NameChangeTransaction::MAX_UPDATE_TRIES_PER_SERVER;
+ for (int i = 1; i <= max_tries; ++i) {
+ // Run removingRevPtrsHandler to send the request.
+ EXPECT_NO_THROW(name_remove->removingRevPtrsHandler());
+
+ // Simulate a server corrupt response.
+ name_remove->setDnsUpdateStatus(DNSClient::INVALID_RESPONSE);
+ name_remove->postNextEvent(NameChangeTransaction::IO_COMPLETED_EVT);
+
+ // Run removingRevPtrsHandler again to process the response.
+ EXPECT_NO_THROW(name_remove->removingRevPtrsHandler());
+
+ // Completion flags should be false.
+ EXPECT_FALSE(name_remove->getForwardChangeCompleted());
+ EXPECT_FALSE(name_remove->getReverseChangeCompleted());
+
+ if (i < max_tries) {
+ // We should be ready to try again.
+ EXPECT_EQ(NameRemoveTransaction::REMOVING_REV_PTRS_ST,
+ name_remove->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::SERVER_SELECTED_EVT,
+ name_remove->getNextEvent());
+ } else {
+ // Server retries should be exhausted, time for a new server.
+ EXPECT_EQ(NameChangeTransaction::SELECTING_REV_SERVER_ST,
+ name_remove->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::SERVER_IO_ERROR_EVT,
+ name_remove->getNextEvent());
+ }
+ }
+}
+
+// Tests the processRemoveOkHandler functionality.
+// It verifies behavior for the following scenarios:
+//
+// 1. Posted event is UPDATE_OK_EVT
+// 2. Posted event is invalid
+//
+TEST_F(NameRemoveTransactionTest, processRemoveOkHandler) {
+ NameRemoveStubPtr name_remove;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(name_remove =
+ prepHandlerTest(NameChangeTransaction::PROCESS_TRANS_OK_ST,
+ NameChangeTransaction::UPDATE_OK_EVT));
+ // Run processRemoveOkHandler.
+ EXPECT_NO_THROW(name_remove->processRemoveOkHandler());
+
+ // Verify that a server was selected.
+ EXPECT_EQ(dhcp_ddns::ST_COMPLETED, name_remove->getNcrStatus());
+
+ // Verify that the model has ended.
+ EXPECT_EQ(StateModel::END_ST, name_remove->getCurrState());
+ EXPECT_EQ(StateModel::END_EVT, name_remove->getNextEvent());
+
+
+ // Create and prep transaction, poised to run the handler but with an
+ // invalid event.
+ ASSERT_NO_THROW(name_remove =
+ prepHandlerTest(NameChangeTransaction::PROCESS_TRANS_OK_ST,
+ StateModel::NOP_EVT));
+ // Running the handler should throw.
+ EXPECT_THROW(name_remove->processRemoveOkHandler(),
+ NameRemoveTransactionError);
+}
+
+// Tests the processRemoveFailedHandler functionality.
+// It verifies behavior for the following scenarios:
+//
+// 1. Posted event is UPDATE_FAILED_EVT
+// 2. Posted event is invalid
+//
+TEST_F(NameRemoveTransactionTest, processRemoveFailedHandler) {
+ NameRemoveStubPtr name_remove;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(name_remove =
+ prepHandlerTest(NameChangeTransaction::
+ PROCESS_TRANS_FAILED_ST,
+ NameChangeTransaction::UPDATE_FAILED_EVT));
+ // Run processRemoveFailedHandler.
+ EXPECT_NO_THROW(name_remove->processRemoveFailedHandler());
+
+ // Verify that a server was selected.
+ EXPECT_EQ(dhcp_ddns::ST_FAILED, name_remove->getNcrStatus());
+
+ // Verify that the model has ended. (Remember, the transaction failed NOT
+ // the model. The model should have ended normally.)
+ EXPECT_EQ(StateModel::END_ST, name_remove->getCurrState());
+ EXPECT_EQ(StateModel::END_EVT, name_remove->getNextEvent());
+
+
+ // Create and prep transaction, poised to run the handler but with an
+ // invalid event.
+ ASSERT_NO_THROW(name_remove =
+ prepHandlerTest(NameChangeTransaction::
+ PROCESS_TRANS_FAILED_ST,
+ StateModel::NOP_EVT));
+ // Running the handler should throw.
+ EXPECT_THROW(name_remove->processRemoveFailedHandler(),
+ NameRemoveTransactionError);
+}
+
+// Tests the processRemoveFailedHandler functionality.
+// It verifies behavior for posted event of NO_MORE_SERVERS_EVT.
+TEST_F(NameRemoveTransactionTest, processRemoveFailedHandler_NoMoreServers) {
+ NameRemoveStubPtr name_remove;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(name_remove =
+ prepHandlerTest(NameChangeTransaction::
+ PROCESS_TRANS_FAILED_ST,
+ NameChangeTransaction::
+ NO_MORE_SERVERS_EVT));
+
+ // Run processRemoveFailedHandler.
+ EXPECT_NO_THROW(name_remove->processRemoveFailedHandler());
+
+ // Verify that a server was selected.
+ EXPECT_EQ(dhcp_ddns::ST_FAILED, name_remove->getNcrStatus());
+
+ // Verify that the model has ended. (Remember, the transaction failed NOT
+ // the model. The model should have ended normally.)
+ EXPECT_EQ(StateModel::END_ST, name_remove->getCurrState());
+ EXPECT_EQ(StateModel::END_EVT, name_remove->getNextEvent());
+}
+
+// Tests the processRemoveFailedHandler functionality.
+// It verifies behavior for posted event of SERVER_IO_ERROR_EVT.
+TEST_F(NameRemoveTransactionTest, processRemoveFailedHandler_ServerIOError) {
+ NameRemoveStubPtr name_remove;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(name_remove =
+ prepHandlerTest(NameChangeTransaction::
+ PROCESS_TRANS_FAILED_ST,
+ NameChangeTransaction::
+ SERVER_IO_ERROR_EVT));
+
+ // Run processRemoveFailedHandler.
+ EXPECT_NO_THROW(name_remove->processRemoveFailedHandler());
+
+ // Verify that a server was selected.
+ EXPECT_EQ(dhcp_ddns::ST_FAILED, name_remove->getNcrStatus());
+
+ // Verify that the model has ended. (Remember, the transaction failed NOT
+ // the model. The model should have ended normally.)
+ EXPECT_EQ(StateModel::END_ST, name_remove->getCurrState());
+ EXPECT_EQ(StateModel::END_EVT, name_remove->getNextEvent());
+}
+
+// Tests removingFwdAddrsHandler with the following scenario:
+//
+// The request includes only a forward change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The send update request fails due to an unexpected exception.
+//
+TEST_F(NameRemoveTransactionTest, removingFwdAddrsHandler_sendUpdateException) {
+ NameRemoveStubPtr name_remove;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(name_remove =
+ prepHandlerTest(NameRemoveTransaction::
+ REMOVING_FWD_ADDRS_ST,
+ NameChangeTransaction::
+ SERVER_SELECTED_EVT, FORWARD_CHG));
+
+ name_remove->simulate_send_exception_ = true;
+
+ // Run removingFwdAddrsHandler to construct and send the request.
+ EXPECT_NO_THROW(name_remove->removingFwdAddrsHandler());
+
+ // Completion flags should be false.
+ EXPECT_FALSE(name_remove->getForwardChangeCompleted());
+ EXPECT_FALSE(name_remove->getReverseChangeCompleted());
+
+ // Since IO exceptions should be gracefully handled, any that occur
+ // are unanticipated, and deemed unrecoverable, so the transaction should
+ // be transitioned to failure.
+ EXPECT_EQ(NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ name_remove->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::UPDATE_FAILED_EVT,
+ name_remove->getNextEvent());
+}
+
+
+// Tests removingFwdRRsHandler with the following scenario:
+//
+// The request includes only a forward change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The send update request fails due to an unexpected exception.
+//
+TEST_F(NameRemoveTransactionTest, removingFwdRRsHandler_SendUpdateException) {
+ NameRemoveStubPtr name_remove;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(name_remove =
+ prepHandlerTest(NameRemoveTransaction::
+ REMOVING_FWD_RRS_ST,
+ NameChangeTransaction::
+ SERVER_SELECTED_EVT, FORWARD_CHG));
+
+ name_remove->simulate_send_exception_ = true;
+
+ // Run removingFwdRRsHandler to construct and send the request.
+ EXPECT_NO_THROW(name_remove->removingFwdRRsHandler());
+
+ // Completion flags should be false.
+ EXPECT_FALSE(name_remove->getForwardChangeCompleted());
+ EXPECT_FALSE(name_remove->getReverseChangeCompleted());
+
+ // Since IO exceptions should be gracefully handled, any that occur
+ // are unanticipated, and deemed unrecoverable, so the transaction should
+ // be transitioned to failure.
+ EXPECT_EQ(NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ name_remove->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::UPDATE_FAILED_EVT,
+ name_remove->getNextEvent());
+}
+
+// Tests removingRevPtrHandler with the following scenario:
+//
+// The request includes only a reverse change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The send update request fails due to an unexpected exception.
+//
+TEST_F(NameRemoveTransactionTest, removingRevPtrsHandler_SendUpdateException) {
+ NameRemoveStubPtr name_remove;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(name_remove =
+ prepHandlerTest(NameRemoveTransaction::
+ REMOVING_REV_PTRS_ST,
+ NameChangeTransaction::
+ SERVER_SELECTED_EVT, REVERSE_CHG));
+
+ name_remove->simulate_send_exception_ = true;
+
+ // Run removingRevPtrsHandler to construct and send the request.
+ EXPECT_NO_THROW(name_remove->removingRevPtrsHandler());
+
+ // Completion flags should be false.
+ EXPECT_FALSE(name_remove->getForwardChangeCompleted());
+ EXPECT_FALSE(name_remove->getReverseChangeCompleted());
+
+ // Since IO exceptions should be gracefully handled, any that occur
+ // are unanticipated, and deemed unrecoverable, so the transaction should
+ // be transitioned to failure.
+ EXPECT_EQ(NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ name_remove->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::UPDATE_FAILED_EVT,
+ name_remove->getNextEvent());
+}
+
+// Tests removingFwdAddrsHandler with the following scenario:
+//
+// The request includes only a forward change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The request build fails due to an unexpected exception.
+//
+TEST_F(NameRemoveTransactionTest,
+ removingFwdAddrsHandler_BuildRequestException) {
+ NameRemoveStubPtr name_remove;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(name_remove =
+ prepHandlerTest(NameRemoveTransaction::
+ REMOVING_FWD_ADDRS_ST,
+ NameChangeTransaction::
+ SERVER_SELECTED_EVT, FORWARD_CHG));
+
+ // Set the one-shot exception simulation flag.
+ name_remove->simulate_build_request_exception_ = true;
+
+ // Run removingFwdAddrsHandler to construct and send the request.
+ // This should fail with a build request throw which should be caught
+ // in the state handler.
+ ASSERT_NO_THROW(name_remove->removingFwdAddrsHandler());
+
+ // Verify we did not attempt to send anything.
+ EXPECT_EQ(0, name_remove->getUpdateAttempts());
+
+ // Completion flags should be false.
+ EXPECT_FALSE(name_remove->getForwardChangeCompleted());
+ EXPECT_FALSE(name_remove->getReverseChangeCompleted());
+
+ // Since IO exceptions should be gracefully handled, any that occur
+ // are unanticipated, and deemed unrecoverable, so the transaction should
+ // be transitioned to failure.
+ EXPECT_EQ(NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ name_remove->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::UPDATE_FAILED_EVT,
+ name_remove->getNextEvent());
+}
+
+// Tests removingFwdRRsHandler with the following scenario:
+//
+// The request includes only a forward change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The request build fails due to an unexpected exception.
+//
+TEST_F(NameRemoveTransactionTest,
+ removingFwdRRsHandler_BuildRequestException) {
+ NameRemoveStubPtr name_remove;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(name_remove =
+ prepHandlerTest(NameRemoveTransaction::
+ REMOVING_FWD_RRS_ST,
+ NameChangeTransaction::
+ SERVER_SELECTED_EVT, FORWARD_CHG));
+
+ // Set the one-shot exception simulation flag.
+ name_remove->simulate_build_request_exception_ = true;
+
+ // Run removingFwdRRsHandler to construct and send the request.
+ // This should fail with a build request throw which should be caught
+ // in the state handler.
+ ASSERT_NO_THROW(name_remove->removingFwdRRsHandler());
+
+ // Verify we did not attempt to send anything.
+ EXPECT_EQ(0, name_remove->getUpdateAttempts());
+
+ // Completion flags should be false.
+ EXPECT_FALSE(name_remove->getForwardChangeCompleted());
+ EXPECT_FALSE(name_remove->getReverseChangeCompleted());
+
+ // Since IO exceptions should be gracefully handled, any that occur
+ // are unanticipated, and deemed unrecoverable, so the transaction should
+ // be transitioned to failure.
+ EXPECT_EQ(NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ name_remove->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::UPDATE_FAILED_EVT,
+ name_remove->getNextEvent());
+}
+
+// Tests removingRevPTRsHandler with the following scenario:
+//
+// The request includes only a forward change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The request build fails due to an unexpected exception.
+//
+TEST_F(NameRemoveTransactionTest,
+ removingRevPTRsHandler_BuildRequestException) {
+ NameRemoveStubPtr name_remove;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(name_remove =
+ prepHandlerTest(NameRemoveTransaction::
+ REMOVING_REV_PTRS_ST,
+ NameChangeTransaction::
+ SERVER_SELECTED_EVT, FORWARD_CHG));
+
+ // Set the one-shot exception simulation flag.
+ name_remove->simulate_build_request_exception_ = true;
+
+ // Run removingRevPtrsHandler to construct and send the request.
+ // This should fail with a build request throw which should be caught
+ // in the state handler.
+ ASSERT_NO_THROW(name_remove->removingRevPtrsHandler());
+
+ // Verify we did not attempt to send anything.
+ EXPECT_EQ(0, name_remove->getUpdateAttempts());
+
+ // Completion flags should be false.
+ EXPECT_FALSE(name_remove->getForwardChangeCompleted());
+ EXPECT_FALSE(name_remove->getReverseChangeCompleted());
+
+ // Since IO exceptions should be gracefully handled, any that occur
+ // are unanticipated, and deemed unrecoverable, so the transaction should
+ // be transitioned to failure.
+ EXPECT_EQ(NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ name_remove->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::UPDATE_FAILED_EVT,
+ name_remove->getNextEvent());
+}
+
+}
diff --git a/src/bin/d2/tests/parser_unittest.cc b/src/bin/d2/tests/parser_unittest.cc
new file mode 100644
index 0000000..9100511
--- /dev/null
+++ b/src/bin/d2/tests/parser_unittest.cc
@@ -0,0 +1,871 @@
+// Copyright (C) 2017-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#include <config.h>
+
+#include <cc/data.h>
+#include <d2/parser_context.h>
+#include <d2/tests/parser_unittest.h>
+#include <testutils/io_utils.h>
+#include <testutils/log_utils.h>
+#include <testutils/user_context_utils.h>
+
+#include <gtest/gtest.h>
+
+#include <fstream>
+#include <set>
+
+#include <boost/algorithm/string.hpp>
+
+#include "test_data_files_config.h"
+
+using namespace isc::data;
+using namespace isc::test;
+using namespace std;
+
+namespace isc {
+namespace d2 {
+namespace test {
+
+/// @brief compares two JSON trees
+///
+/// If differences are discovered, gtest failure is reported (using EXPECT_EQ)
+///
+/// @param a first to be compared
+/// @param b second to be compared
+void compareJSON(ConstElementPtr a, ConstElementPtr b) {
+ ASSERT_TRUE(a);
+ ASSERT_TRUE(b);
+ EXPECT_EQ(a->str(), b->str());
+}
+
+/// @brief Tests if the input string can be parsed with specific parser
+///
+/// The input text will be passed to bison parser of specified type.
+/// Then the same input text is passed to legacy JSON parser and outputs
+/// from both parsers are compared. The legacy comparison can be disabled,
+/// if the feature tested is not supported by the old parser (e.g.
+/// new comment styles)
+///
+/// @param txt text to be compared
+/// @param parser_type bison parser type to be instantiated
+/// @param compare whether to compare the output with legacy JSON parser
+void testParser(const std::string& txt, D2ParserContext::ParserType parser_type,
+ bool compare = true) {
+ SCOPED_TRACE("\n=== tested config ===\n" + txt + "=====================");
+
+ ConstElementPtr test_json;
+ ASSERT_NO_THROW({
+ try {
+ D2ParserContext ctx;
+ test_json = ctx.parseString(txt, parser_type);
+ } catch (const std::exception &e) {
+ cout << "EXCEPTION: " << e.what() << endl;
+ throw;
+ }
+
+ });
+
+ if (!compare) {
+ return;
+ }
+
+ // Now compare if both representations are the same.
+ ElementPtr reference_json;
+ ASSERT_NO_THROW(reference_json = Element::fromJSON(txt, true));
+ compareJSON(reference_json, test_json);
+}
+
+// Generic JSON parsing tests
+TEST(ParserTest, mapInMap) {
+ string txt = "{ \"xyzzy\": { \"foo\": 123, \"baz\": 456 } }";
+ testParser(txt, D2ParserContext::PARSER_JSON);
+}
+
+TEST(ParserTest, listInList) {
+ string txt = "[ [ \"Britain\", \"Wales\", \"Scotland\" ], "
+ "[ \"Pomorze\", \"Wielkopolska\", \"Tatry\"] ]";
+ testParser(txt, D2ParserContext::PARSER_JSON);
+}
+
+TEST(ParserTest, nestedMaps) {
+ string txt = "{ \"europe\": { \"UK\": { \"London\": { \"street\": \"221B Baker\" }}}}";
+ testParser(txt, D2ParserContext::PARSER_JSON);
+}
+
+TEST(ParserTest, nestedLists) {
+ string txt = "[ \"half\", [ \"quarter\", [ \"eighth\", [ \"sixteenth\" ]]]]";
+ testParser(txt, D2ParserContext::PARSER_JSON);
+}
+
+TEST(ParserTest, listsInMaps) {
+ string txt = "{ \"constellations\": { \"orion\": [ \"rigel\", \"betelgeuse\" ], "
+ "\"cygnus\": [ \"deneb\", \"albireo\"] } }";
+ testParser(txt, D2ParserContext::PARSER_JSON);
+}
+
+TEST(ParserTest, mapsInLists) {
+ string txt = "[ { \"body\": \"earth\", \"gravity\": 1.0 },"
+ " { \"body\": \"mars\", \"gravity\": 0.376 } ]";
+ testParser(txt, D2ParserContext::PARSER_JSON);
+}
+
+TEST(ParserTest, types) {
+ string txt = "{ \"string\": \"foo\","
+ "\"integer\": 42,"
+ "\"boolean\": true,"
+ "\"map\": { \"foo\": \"bar\" },"
+ "\"list\": [ 1, 2, 3 ],"
+ "\"null\": null }";
+ testParser(txt, D2ParserContext::PARSER_JSON);
+}
+
+TEST(ParserTest, keywordJSON) {
+ string txt = "{ \"name\": \"user\","
+ "\"type\": \"password\","
+ "\"user\": \"name\","
+ "\"password\": \"type\" }";
+ testParser(txt, D2ParserContext::PARSER_JSON);
+}
+
+// PARSER_DHCPDDNS parser tests
+TEST(ParserTest, keywordDhcpDdns) {
+ string txt =
+ "{ \"DhcpDdns\" : \n"
+ "{ \n"
+ " \"ip-address\": \"192.168.77.1\", \n"
+ " \"port\": 777 , \n "
+ " \"ncr-protocol\": \"UDP\", \n"
+ " \"tsig-keys\": [], \n"
+ " \"forward-ddns\" : {}, \n"
+ " \"reverse-ddns\" : {} \n"
+ "} \n"
+ "} \n";
+ testParser(txt, D2ParserContext::PARSER_DHCPDDNS);
+}
+
+// Tests if bash (#) comments are supported. That's the only comment type that
+// was supported by the old parser.
+TEST(ParserTest, bashComments) {
+ string txt =
+ "{ \"DhcpDdns\" : \n"
+ "{ \n"
+ " \"ip-address\": \"192.168.77.1\", \n"
+ "# this is a comment\n"
+ " \"port\": 777, \n "
+ " \"ncr-protocol\": \"UDP\", \n"
+ "# lots of comments here\n"
+ "# and here\n"
+ "\"tsig-keys\": [], \n"
+ "\"forward-ddns\" : {}, \n"
+ "\"reverse-ddns\" : {} \n"
+ "} \n"
+ "} \n";
+ testParser(txt, D2ParserContext::PARSER_DHCPDDNS);
+}
+
+// Tests if C++ (//) comments can start anywhere, not just in the first line.
+TEST(ParserTest, cppComments) {
+ string txt =
+ "{ \"DhcpDdns\" : \n"
+ "{ \n"
+ " \"ip-address\": \"192.168.77.1\", \n"
+ " \"port\": 777, // this is a comment \n"
+ " \"ncr-protocol\": \"UDP\", // everything after // is ignored\n"
+ "\"tsig-keys\": [], // this will be ignored, too\n"
+ "\"forward-ddns\" : {}, \n"
+ "\"reverse-ddns\" : {} \n"
+ "} \n"
+ "} \n";
+ testParser(txt, D2ParserContext::PARSER_DHCPDDNS, false);
+}
+
+// Tests if bash (#) comments can start anywhere, not just in the first line.
+TEST(ParserTest, bashCommentsInline) {
+ string txt =
+ "{ \"DhcpDdns\" : \n"
+ "{ \n"
+ " \"ip-address\": \"192.168.77.1\", \n"
+ " \"port\": 777, # this is a comment \n"
+ " \"ncr-protocol\": \"UDP\", # everything after # is ignored\n"
+ "\"tsig-keys\": [], # this will be ignored, too\n"
+ "\"forward-ddns\" : {}, \n"
+ "\"reverse-ddns\" : {} \n"
+ "} \n"
+ "} \n";
+ testParser(txt, D2ParserContext::PARSER_DHCPDDNS, false);
+}
+
+// Tests if multi-line C style comments are handled correctly.
+TEST(ParserTest, multilineComments) {
+ string txt =
+ "{ \"DhcpDdns\" : \n"
+ "{ \n"
+ " \"ip-address\": \"192.168.77.1\", \n"
+ " \"port\": 777, /* this is a C style comment\n"
+ "that\n can \n span \n multiple \n lines */ \n"
+ " \"ncr-protocol\": \"UDP\", \n"
+ "\"tsig-keys\": [], \n"
+ "\"forward-ddns\" : {}, \n"
+ "\"reverse-ddns\" : {} \n"
+ "} \n"
+ "} \n";
+ testParser(txt, D2ParserContext::PARSER_DHCPDDNS, false);
+}
+
+// Tests if embedded comments are handled correctly.
+TEST(ParserTest, embbededComments) {
+ string txt =
+ "{ \"DhcpDdns\" : \n"
+ "{ \n"
+ "\"comment\": \"a comment\",\n"
+ " \"ip-address\": \"192.168.77.1\", \n"
+ " \"port\": 777, \n "
+ " \"ncr-protocol\": \"UDP\", \n"
+ "\"tsig-keys\" : [ { \n"
+ " \"name\" : \"d2.md5.key\", \n"
+ " \"user-context\" : { \"comment\" : \"indirect\" } } ], \n"
+ "\"forward-ddns\" : {}, \n"
+ "\"reverse-ddns\" : {}, \n"
+ "\"user-context\": { \"compatible\": true }"
+ "} \n"
+ "} \n";
+ testParser(txt, D2ParserContext::PARSER_DHCPDDNS, false);
+}
+
+/// @brief Loads specified example config file
+///
+/// This test loads specified example file twice: first, using the legacy
+/// JSON file and then second time using bison parser. Two created Element
+/// trees are then compared. The input is decommented before it is passed
+/// to legacy parser (as legacy support for comments is very limited).
+///
+/// @param fname name of the file to be loaded
+void testFile(const std::string& fname) {
+ ElementPtr json;
+ ElementPtr reference_json;
+ ConstElementPtr test_json;
+
+ string decommented = decommentJSONfile(fname);
+
+ cout << "Parsing file " << fname << " (" << decommented << ")" << endl;
+
+ ASSERT_NO_THROW(json = Element::fromJSONFile(decommented, true));
+ reference_json = moveComments(json);
+
+ // remove the temporary file
+ EXPECT_NO_THROW(::remove(decommented.c_str()));
+
+ EXPECT_NO_THROW(
+ try {
+ D2ParserContext ctx;
+ test_json = ctx.parseFile(fname, D2ParserContext::PARSER_DHCPDDNS);
+ } catch (const std::exception &x) {
+ cout << "EXCEPTION: " << x.what() << endl;
+ throw;
+ });
+
+ ASSERT_TRUE(reference_json);
+ ASSERT_TRUE(test_json);
+
+ compareJSON(reference_json, test_json);
+}
+
+// This test loads all available existing files. Each config is loaded
+// twice: first with the existing Element::fromJSONFile() and then
+// the second time with D2Parser. Both JSON trees are then compared.
+TEST(ParserTest, file) {
+ vector<string> configs;
+ configs.push_back("all-keys.json");
+ configs.push_back("all-keys-netconf.json");
+ configs.push_back("comments.json");
+ configs.push_back("gss-tsig.json");
+ configs.push_back("sample1.json");
+ configs.push_back("template.json");
+
+ for (int i = 0; i<configs.size(); i++) {
+ testFile(string(CFG_EXAMPLES) + "/" + configs[i]);
+ }
+}
+
+/// @brief Tests error conditions in D2Parser
+///
+/// @param txt text to be parsed
+/// @param parser_type type of the parser to be used in the test
+/// @param msg expected content of the exception
+void testError(const std::string& txt,
+ D2ParserContext::ParserType parser_type,
+ const std::string& msg) {
+ SCOPED_TRACE("\n=== tested config ===\n" + txt + "=====================");
+
+ try {
+ D2ParserContext ctx;
+ ConstElementPtr parsed = ctx.parseString(txt, parser_type);
+ FAIL() << "Expected D2ParseError but nothing was raised (expected: "
+ << msg << ")";
+ }
+ catch (const D2ParseError& ex) {
+ EXPECT_EQ(msg, ex.what());
+ }
+ catch (...) {
+ FAIL() << "Expected D2ParseError but something else was raised";
+ }
+}
+
+// Verify that error conditions are handled correctly.
+TEST(ParserTest, errors) {
+ // no input
+ testError("", D2ParserContext::PARSER_JSON,
+ "<string>:1.1: syntax error, unexpected end of file");
+ testError(" ", D2ParserContext::PARSER_JSON,
+ "<string>:1.2: syntax error, unexpected end of file");
+ testError("\n", D2ParserContext::PARSER_JSON,
+ "<string>:2.1: syntax error, unexpected end of file");
+ testError("\t", D2ParserContext::PARSER_JSON,
+ "<string>:1.2: syntax error, unexpected end of file");
+ testError("\r", D2ParserContext::PARSER_JSON,
+ "<string>:1.2: syntax error, unexpected end of file");
+
+ // comments
+ testError("# nothing\n",
+ D2ParserContext::PARSER_JSON,
+ "<string>:2.1: syntax error, unexpected end of file");
+ testError(" #\n",
+ D2ParserContext::PARSER_JSON,
+ "<string>:2.1: syntax error, unexpected end of file");
+ testError("// nothing\n",
+ D2ParserContext::PARSER_JSON,
+ "<string>:2.1: syntax error, unexpected end of file");
+ testError("/* nothing */\n",
+ D2ParserContext::PARSER_JSON,
+ "<string>:2.1: syntax error, unexpected end of file");
+ testError("/* no\nthing */\n",
+ D2ParserContext::PARSER_JSON,
+ "<string>:3.1: syntax error, unexpected end of file");
+ testError("/* no\nthing */\n\n",
+ D2ParserContext::PARSER_JSON,
+ "<string>:4.1: syntax error, unexpected end of file");
+ testError("/* nothing\n",
+ D2ParserContext::PARSER_JSON,
+ "Comment not closed. (/* in line 1");
+ testError("\n\n\n/* nothing\n",
+ D2ParserContext::PARSER_JSON,
+ "Comment not closed. (/* in line 4");
+ testError("{ /* */*/ }\n",
+ D2ParserContext::PARSER_JSON,
+ "<string>:1.3-8: Invalid character: *");
+ testError("{ /* // *// }\n",
+ D2ParserContext::PARSER_JSON,
+ "<string>:1.3-11: Invalid character: /");
+ testError("{ /* // */// }\n",
+ D2ParserContext::PARSER_JSON,
+ "<string>:2.1: syntax error, unexpected end of file, "
+ "expecting }");
+
+ // includes
+ testError("<?\n",
+ D2ParserContext::PARSER_JSON,
+ "Directive not closed.");
+ testError("<?include\n",
+ D2ParserContext::PARSER_JSON,
+ "Directive not closed.");
+ string file = string(CFG_EXAMPLES) + "/" + "sample1.json";
+ testError("<?include \"" + file + "\"\n",
+ D2ParserContext::PARSER_JSON,
+ "Directive not closed.");
+ testError("<?include \"/foo/bar\" ?>/n",
+ D2ParserContext::PARSER_JSON,
+ "Can't open include file /foo/bar");
+
+ // JSON keywords
+ testError("{ \"foo\": True }",
+ D2ParserContext::PARSER_JSON,
+ "<string>:1.10-13: JSON true reserved keyword is lower case only");
+ testError("{ \"foo\": False }",
+ D2ParserContext::PARSER_JSON,
+ "<string>:1.10-14: JSON false reserved keyword is lower case only");
+ testError("{ \"foo\": NULL }",
+ D2ParserContext::PARSER_JSON,
+ "<string>:1.10-13: JSON null reserved keyword is lower case only");
+ testError("{ \"foo\": Tru }",
+ D2ParserContext::PARSER_JSON,
+ "<string>:1.10: Invalid character: T");
+ testError("{ \"foo\": nul }",
+ D2ParserContext::PARSER_JSON,
+ "<string>:1.10: Invalid character: n");
+
+ // numbers
+ testError("123",
+ D2ParserContext::PARSER_DHCPDDNS,
+ "<string>:1.1-3: syntax error, unexpected integer, "
+ "expecting {");
+ testError("-456",
+ D2ParserContext::PARSER_DHCPDDNS,
+ "<string>:1.1-4: syntax error, unexpected integer, "
+ "expecting {");
+ testError("-0001",
+ D2ParserContext::PARSER_DHCPDDNS,
+ "<string>:1.1-5: syntax error, unexpected integer, "
+ "expecting {");
+ testError("1234567890123456789012345678901234567890",
+ D2ParserContext::PARSER_JSON,
+ "<string>:1.1-40: Failed to convert "
+ "1234567890123456789012345678901234567890"
+ " to an integer.");
+ testError("-3.14e+0",
+ D2ParserContext::PARSER_DHCPDDNS,
+ "<string>:1.1-8: syntax error, unexpected floating point, "
+ "expecting {");
+ testError("1e50000",
+ D2ParserContext::PARSER_JSON,
+ "<string>:1.1-7: Failed to convert 1e50000 "
+ "to a floating point.");
+
+ // strings
+ testError("\"aabb\"",
+ D2ParserContext::PARSER_DHCPDDNS,
+ "<string>:1.1-6: syntax error, unexpected constant string, "
+ "expecting {");
+ testError("{ \"aabb\"err",
+ D2ParserContext::PARSER_JSON,
+ "<string>:1.9: Invalid character: e");
+ testError("{ err\"aabb\"",
+ D2ParserContext::PARSER_JSON,
+ "<string>:1.3: Invalid character: e");
+ testError("\"a\n\tb\"",
+ D2ParserContext::PARSER_JSON,
+ "<string>:1.1-6 (near 2): Invalid control in \"a\n\tb\"");
+ testError("\"a\n\\u12\"",
+ D2ParserContext::PARSER_JSON,
+ "<string>:1.1-8 (near 2): Invalid control in \"a\n\\u12\"");
+ testError("\"a\\n\\tb\"",
+ D2ParserContext::PARSER_DHCPDDNS,
+ "<string>:1.1-8: syntax error, unexpected constant string, "
+ "expecting {");
+ testError("\"a\\x01b\"",
+ D2ParserContext::PARSER_JSON,
+ "<string>:1.1-8 (near 3): Bad escape in \"a\\x01b\"");
+ testError("\"a\\u0162\"",
+ D2ParserContext::PARSER_JSON,
+ "<string>:1.1-9 (near 4): Unsupported unicode escape "
+ "in \"a\\u0162\"");
+ testError("\"a\\u062z\"",
+ D2ParserContext::PARSER_JSON,
+ "<string>:1.1-9 (near 3): Bad escape in \"a\\u062z\"");
+ testError("\"abc\\\"",
+ D2ParserContext::PARSER_JSON,
+ "<string>:1.1-6 (near 6): Overflow escape in \"abc\\\"");
+ testError("\"a\\u006\"",
+ D2ParserContext::PARSER_JSON,
+ "<string>:1.1-8 (near 3): Overflow unicode escape "
+ "in \"a\\u006\"");
+ testError("\"\\u\"",
+ D2ParserContext::PARSER_JSON,
+ "<string>:1.1-4 (near 2): Overflow unicode escape in \"\\u\"");
+ testError("\"\\u\x02\"",
+ D2ParserContext::PARSER_JSON,
+ "<string>:1.1-5 (near 2): Bad escape in \"\\u\x02\"");
+ testError("\"\\u\\\"foo\"",
+ D2ParserContext::PARSER_JSON,
+ "<string>:1.1-5 (near 2): Bad escape in \"\\u\\\"...");
+ testError("\"\x02\\u\"",
+ D2ParserContext::PARSER_JSON,
+ "<string>:1.1-5 (near 1): Invalid control in \"\x02\\u\"");
+
+ // from data_unittest.c
+ testError("\\a",
+ D2ParserContext::PARSER_JSON,
+ "<string>:1.1: Invalid character: \\");
+ testError("\\",
+ D2ParserContext::PARSER_JSON,
+ "<string>:1.1: Invalid character: \\");
+ testError("\\\"\\\"",
+ D2ParserContext::PARSER_JSON,
+ "<string>:1.1: Invalid character: \\");
+
+ // want a map
+ testError("[]\n",
+ D2ParserContext::PARSER_DHCPDDNS,
+ "<string>:1.1: syntax error, unexpected [, "
+ "expecting {");
+ testError("[]\n",
+ D2ParserContext::PARSER_DHCPDDNS,
+ "<string>:1.1: syntax error, unexpected [, "
+ "expecting {");
+ testError("{ 123 }\n",
+ D2ParserContext::PARSER_JSON,
+ "<string>:1.3-5: syntax error, unexpected integer, "
+ "expecting }");
+ testError("{ 123 }\n",
+ D2ParserContext::PARSER_DHCPDDNS,
+ "<string>:1.3-5: syntax error, unexpected integer, "
+ "expecting DhcpDdns");
+ testError("{ \"foo\" }\n",
+ D2ParserContext::PARSER_JSON,
+ "<string>:1.9: syntax error, unexpected }, "
+ "expecting :");
+ testError("{ \"foo\" }\n",
+ D2ParserContext::PARSER_DHCPDDNS,
+ "<string>:1.3-7: syntax error, unexpected constant string, "
+ "expecting DhcpDdns");
+ testError("{ \"foo\":null }\n",
+ D2ParserContext::PARSER_DHCPDDNS,
+ "<string>:1.3-7: syntax error, unexpected constant string, "
+ "expecting DhcpDdns");
+ testError("{ \"Logging\":null }\n",
+ D2ParserContext::PARSER_DHCPDDNS,
+ "<string>:1.3-11: syntax error, unexpected constant string, "
+ "expecting DhcpDdns");
+ testError("{}{}\n",
+ D2ParserContext::PARSER_JSON,
+ "<string>:1.3: syntax error, unexpected {, "
+ "expecting end of file");
+
+ // duplicate in map
+ testError("{ \"foo\": 1, \"foo\": true }\n",
+ D2ParserContext::PARSER_JSON,
+ "<string>:1:13: duplicate foo entries in "
+ "JSON map (previous at <string>:1:10)");
+
+ // bad commas
+ testError("{ , }\n",
+ D2ParserContext::PARSER_JSON,
+ "<string>:1.3: syntax error, unexpected \",\", "
+ "expecting }");
+ testError("{ , \"foo\":true }\n",
+ D2ParserContext::PARSER_JSON,
+ "<string>:1.3: syntax error, unexpected \",\", "
+ "expecting }");
+
+ // bad type
+ testError("{ \"DhcpDdns\":{\n"
+ " \"dns-server-timeout\":false }}\n",
+ D2ParserContext::PARSER_DHCPDDNS,
+ "<string>:2.24-28: syntax error, unexpected boolean, "
+ "expecting integer");
+
+ // unknown keyword
+ testError("{ \"DhcpDdns\":{\n"
+ " \"totally-bogus\":600 }}\n",
+ D2ParserContext::PARSER_DHCPDDNS,
+ "<string>:2.2-16: got unexpected keyword "
+ "\"totally-bogus\" in DhcpDdns map.");
+
+ // user context and embedded comments
+ testError("{ \"DhcpDdns\":{\n"
+ " \"comment\": true,\n"
+ " \"dns-server-timeout\": 1000 }}\n",
+ D2ParserContext::PARSER_DHCPDDNS,
+ "<string>:2.14-17: syntax error, unexpected boolean, "
+ "expecting constant string");
+
+ testError("{ \"DhcpDdns\":{\n"
+ " \"user-context\": \"a comment\",\n"
+ " \"dns-server-timeout\": 1000 }}\n",
+ D2ParserContext::PARSER_DHCPDDNS,
+ "<string>:2.19-29: syntax error, unexpected constant string, "
+ "expecting {");
+
+ testError("{ \"DhcpDdns\":{\n"
+ " \"comment\": \"a comment\",\n"
+ " \"comment\": \"another one\",\n"
+ " \"dns-server-timeout\": 1000 }}\n",
+ D2ParserContext::PARSER_DHCPDDNS,
+ "<string>:3.3-11: duplicate user-context/comment entries "
+ "(previous at <string>:2:3)");
+
+ testError("{ \"DhcpDdns\":{\n"
+ " \"user-context\": { \"version\": 1 },\n"
+ " \"user-context\": { \"one\": \"only\" },\n"
+ " \"dns-server-timeout\": 1000 }}\n",
+ D2ParserContext::PARSER_DHCPDDNS,
+ "<string>:3.3-16: duplicate user-context entries "
+ "(previous at <string>:2:19)");
+
+ testError("{ \"DhcpDdns\":{\n"
+ " \"user-context\": { \"comment\": \"indirect\" },\n"
+ " \"comment\": \"a comment\",\n"
+ " \"dns-server-timeout\": 1000 }}\n",
+ D2ParserContext::PARSER_DHCPDDNS,
+ "<string>:3.3-11: duplicate user-context/comment entries "
+ "(previous at <string>:2:19)");
+
+ // duplicate DhcpDdns entries
+ testError("{ \"DhcpDdns\":{\n"
+ " \"comment\": \"first\" },\n"
+ " \"DhcpDdns\":{\n"
+ " \"comment\": \"second\" }}\n",
+ D2ParserContext::PARSER_DHCPDDNS,
+ "<string>:3.3-12: syntax error, unexpected DhcpDdns, expecting \",\" or }");
+
+ // duplicate of not string entries
+ testError("{ \"DhcpDdns\":{\n"
+ " \"port\": 53001,\n"
+ " \"port\": 53002 }}\n",
+ D2ParserContext::PARSER_DHCPDDNS,
+ "<string>:3:3: duplicate port entries in "
+ "DhcpDdns map (previous at <string>:2:11)");
+
+ // duplicate of string entries
+ testError("{ \"DhcpDdns\":{\n"
+ " \"ip-address\": \"127.0.0.1\",\n"
+ " \"ip-address\": \"::1\" }}\n",
+ D2ParserContext::PARSER_DHCPDDNS,
+ "<string>:3:3: duplicate ip-address entries in "
+ "DhcpDdns map (previous at <string>:2:17)");
+}
+
+// Check unicode escapes
+TEST(ParserTest, unicodeEscapes) {
+ ConstElementPtr result;
+ string json;
+
+ // check we can reread output
+ for (char c = -128; c < 127; ++c) {
+ string ins(" ");
+ ins[1] = c;
+ ConstElementPtr e(new StringElement(ins));
+ json = e->str();
+ ASSERT_NO_THROW(
+ try {
+ D2ParserContext ctx;
+ result = ctx.parseString(json, D2ParserContext::PARSER_JSON);
+ } catch (const std::exception &x) {
+ cout << "EXCEPTION: " << x.what() << endl;
+ throw;
+ });
+ ASSERT_EQ(Element::string, result->getType());
+ EXPECT_EQ(ins, result->stringValue());
+ }
+}
+
+// This test checks that all representations of a slash is recognized properly.
+TEST(ParserTest, unicodeSlash) {
+ // check the 4 possible encodings of solidus '/'
+ ConstElementPtr result;
+ string json = "\"/\\/\\u002f\\u002F\"";
+ ASSERT_NO_THROW(
+ try {
+ D2ParserContext ctx;
+ result = ctx.parseString(json, D2ParserContext::PARSER_JSON);
+ } catch (const std::exception &x) {
+ cout << "EXCEPTION: " << x.what() << endl;
+ throw;
+ });
+ ASSERT_EQ(Element::string, result->getType());
+ EXPECT_EQ("////", result->stringValue());
+}
+
+/// @brief Load a file into a JSON element.
+///
+/// @param fname The name of the file to load.
+/// @param list The JSON element list to add the parsing result to.
+void loadFile(const string& fname, ElementPtr list) {
+ D2ParserContext ctx;
+ ElementPtr json;
+ EXPECT_NO_THROW(json = ctx.parseFile(fname, D2ParserContext::PARSER_DHCPDDNS));
+ ASSERT_TRUE(json);
+ list->add(json);
+}
+
+// This test checks that all map entries are in the sample file.
+TEST(ParserTest, mapEntries) {
+ // Type of keyword set.
+ typedef set<string> KeywordSet;
+
+ // Get keywords from the syntax file (d2_parser.yy).
+ ifstream syntax_file(SYNTAX_FILE);
+ EXPECT_TRUE(syntax_file.is_open());
+ string line;
+ KeywordSet syntax_keys = { "user-context" };
+ // Code setting the map entry.
+ const string pattern = "ctx.stack_.back()->set(\"";
+ while (getline(syntax_file, line)) {
+ // Skip comments.
+ size_t comment = line.find("//");
+ if (comment <= pattern.size()) {
+ continue;
+ }
+ if (comment != string::npos) {
+ line.resize(comment);
+ }
+ // Search for the code pattern.
+ size_t key_begin = line.find(pattern);
+ if (key_begin == string::npos) {
+ continue;
+ }
+ // Extract keywords.
+ line = line.substr(key_begin + pattern.size());
+ size_t key_end = line.find_first_of('"');
+ EXPECT_NE(string::npos, key_end);
+ string keyword = line.substr(0, key_end);
+ // Ignore result when adding the keyword to the syntax keyword set.
+ static_cast<void>(syntax_keys.insert(keyword));
+ }
+ syntax_file.close();
+
+ // Get keywords from the example files.
+ string sample_dir(CFG_EXAMPLES);
+ sample_dir += "/";
+ ElementPtr sample_json = Element::createList();
+ loadFile(sample_dir + "all-keys.json", sample_json);
+ loadFile(sample_dir + "all-keys-netconf.json", sample_json);
+ KeywordSet sample_keys = {
+ "hostname"
+ };
+ // Recursively extract keywords.
+ static void (*extract)(ConstElementPtr, KeywordSet&) =
+ [] (ConstElementPtr json, KeywordSet& set) {
+ if (json->getType() == Element::list) {
+ // Handle lists.
+ for (auto elem : json->listValue()) {
+ extract(elem, set);
+ }
+ } else if (json->getType() == Element::map) {
+ // Handle maps.
+ for (auto elem : json->mapValue()) {
+ static_cast<void>(set.insert(elem.first));
+ // Skip entries with free content.
+ if ((elem.first != "user-context") &&
+ (elem.first != "parameters")) {
+ extract(elem.second, set);
+ }
+ }
+ }
+ };
+ extract(sample_json, sample_keys);
+
+ // Compare.
+ EXPECT_EQ(syntax_keys, sample_keys);
+}
+
+/// @brief Tests a duplicate entry.
+///
+/// The entry was duplicated by adding a new <name>DDDD entry.
+/// An error is expected, usually it is a duplicate but there are
+/// a few syntax errors when the syntax allows only one parameter.
+///
+/// @param json the JSON configuration with the duplicate entry.
+void testDuplicate(ConstElementPtr json) {
+ string config = json->str();
+ size_t where = config.find("DDDD");
+ ASSERT_NE(string::npos, where);
+ string before = config.substr(0, where);
+ string after = config.substr(where + 4, string::npos);
+ D2ParserContext ctx;
+ EXPECT_THROW(ctx.parseString(before + after,
+ D2ParserContext::PARSER_DHCPDDNS),
+ D2ParseError) << "config: " << config;
+}
+
+// This test checks that duplicate entries make parsing to fail.
+TEST(ParserTest, duplicateMapEntries) {
+ // Get the config to work with from the sample file.
+ string sample_fname(CFG_EXAMPLES);
+ sample_fname += "/all-keys.json";
+ D2ParserContext ctx;
+ ElementPtr sample_json;
+ EXPECT_NO_THROW(sample_json =
+ ctx.parseFile(sample_fname, D2ParserContext::PARSER_DHCPDDNS));
+ ASSERT_TRUE(sample_json);
+
+ // Recursively check duplicates.
+ static void (*test)(ElementPtr, ElementPtr, size_t&) =
+ [] (ElementPtr config, ElementPtr json, size_t& cnt) {
+ if (json->getType() == Element::list) {
+ // Handle lists.
+ for (auto elem : json->listValue()) {
+ test(config, elem, cnt);
+ }
+ } else if (json->getType() == Element::map) {
+ // Handle maps.
+ for (auto elem : json->mapValue()) {
+ // Skip entries with free content.
+ if ((elem.first == "user-context") ||
+ (elem.first == "parameters")) {
+ continue;
+ }
+
+ // Perform tests.
+ string dup = elem.first + "DDDD";
+ json->set(dup, elem.second);
+ testDuplicate(config);
+ json->remove(dup);
+ ++cnt;
+
+ // Recursive call.
+ ElementPtr mutable_json =
+ boost::const_pointer_cast<Element>(elem.second);
+ ASSERT_TRUE(mutable_json);
+ test(config, mutable_json, cnt);
+ }
+ }
+ };
+ size_t cnt = 0;
+ test(sample_json, sample_json, cnt);
+ cout << "checked " << cnt << " duplicated map entries\n";
+}
+
+/// @brief Test fixture for trailing commas.
+class TrailingCommasTest : public isc::dhcp::test::LogContentTest {
+public:
+ /// @brief Add a log entry.
+ ///
+ /// @param loc Location of the trailing comma.
+ void addLog(const string& loc) {
+ string log = "DHCP_DDNS_CONFIG_SYNTAX_WARNING DHCP-DDNS server ";
+ log += "configuration syntax warning: " + loc;
+ log += ": Extraneous comma. ";
+ log += "A piece of configuration may have been omitted.";
+ addString(log);
+ }
+};
+
+// Test that trailing commas are allowed.
+TEST_F(TrailingCommasTest, tests) {
+ string txt(R"({
+ "DhcpDdns": {
+ "forward-ddns": {},
+ "ip-address": "127.0.0.1",
+ "loggers": [
+ {
+ "name": "kea-dhcp-ddns",
+ "output_options": [
+ {
+ "output": "stdout"
+ },
+ ],
+ "severity": "DEBUG",
+ },
+ ],
+ "port": 53001,
+ "reverse-ddns": {},
+ "tsig-keys": [
+ {
+ "algorithm": "HMAC-MD5",
+ "name": "d2.md5.key",
+ "secret": "sensitivejdPJI5QxlpnfQ==",
+ },
+ ]
+ },
+})");
+ testParser(txt, D2ParserContext::PARSER_DHCPDDNS, false);
+
+ addLog("<string>:11.12");
+ addLog("<string>:13.28");
+ addLog("<string>:14.8");
+ addLog("<string>:22.45");
+ addLog("<string>:23.8");
+ addLog("<string>:25.4");
+ EXPECT_TRUE(checkFile());
+
+ // Test with many consecutive commas.
+ boost::replace_all(txt, ",", ",,,,");
+ testParser(txt, D2ParserContext::PARSER_DHCPDDNS, false);
+}
+
+} // namespace test
+} // namespace d2
+} // namespace isc
diff --git a/src/bin/d2/tests/parser_unittest.h b/src/bin/d2/tests/parser_unittest.h
new file mode 100644
index 0000000..bcefcb7
--- /dev/null
+++ b/src/bin/d2/tests/parser_unittest.h
@@ -0,0 +1,36 @@
+// Copyright (C) 2017-2020 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef PARSER_UNITTEST_H
+#define PARSER_UNITTEST_H
+
+#include <gtest/gtest.h>
+#include <cc/data.h>
+#include <d2/parser_context.h>
+
+using namespace isc::data;
+using namespace std;
+
+namespace isc {
+namespace d2 {
+namespace test {
+
+/// @brief Runs parser in JSON mode, useful for parser testing
+///
+/// @param in string to be parsed
+/// @return ElementPtr structure representing parsed JSON
+inline isc::data::ElementPtr
+parseJSON(const std::string& in)
+{
+ isc::d2::D2ParserContext ctx;
+ return (ctx.parseString(in, isc::d2::D2ParserContext::PARSER_JSON));
+}
+
+}
+}
+}
+
+#endif // PARSER_UNITTEST_H
diff --git a/src/bin/d2/tests/simple_add_unittests.cc b/src/bin/d2/tests/simple_add_unittests.cc
new file mode 100644
index 0000000..c47a2b6
--- /dev/null
+++ b/src/bin/d2/tests/simple_add_unittests.cc
@@ -0,0 +1,1155 @@
+// Copyright (C) 2020-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <asiolink/io_service.h>
+#include <d2/simple_add.h>
+#include <d2srv/d2_cfg_mgr.h>
+#include <d2srv/testutils/nc_test_utils.h>
+#include <dns/messagerenderer.h>
+
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::d2;
+using namespace isc::util;
+
+namespace {
+
+/// @brief Test class derived from SimpleAddTransaction to provide visibility
+// to protected methods.
+class SimpleAddStub : public SimpleAddTransaction {
+public:
+ SimpleAddStub(asiolink::IOServicePtr& io_service,
+ dhcp_ddns::NameChangeRequestPtr& ncr,
+ DdnsDomainPtr& forward_domain,
+ DdnsDomainPtr& reverse_domain,
+ D2CfgMgrPtr& cfg_mgr)
+ : SimpleAddTransaction(io_service, ncr, forward_domain, reverse_domain,
+ cfg_mgr),
+ simulate_send_exception_(false),
+ simulate_build_request_exception_(false) {
+ }
+
+ virtual ~SimpleAddStub() {
+ }
+
+ /// @brief Simulates sending update requests to the DNS server
+ ///
+ /// This method simulates the initiation of an asynchronous send of
+ /// a DNS update request. It overrides the actual sendUpdate method in
+ /// the base class, thus avoiding an actual send, yet still increments
+ /// the update attempt count and posts a next event of NOP_EVT.
+ ///
+ /// It will also simulate an exception-based failure of sendUpdate, if
+ /// the simulate_send_exception_ flag is true.
+ ///
+ /// @param comment Parameter is unused, but present in base class method.
+ ///
+ virtual void sendUpdate(const std::string& /*comment*/) {
+ if (simulate_send_exception_) {
+ // Make the flag a one-shot by resetting it.
+ simulate_send_exception_ = false;
+ // Transition to failed.
+ transition(PROCESS_TRANS_FAILED_ST, UPDATE_FAILED_EVT);
+ return;
+ }
+
+ // Update send attempt count and post a NOP_EVT.
+ setUpdateAttempts(getUpdateAttempts() + 1);
+ postNextEvent(StateModel::NOP_EVT);
+ }
+
+ /// @brief Prepares the initial D2UpdateMessage
+ ///
+ /// This method overrides the NameChangeTransaction implementation to
+ /// provide the ability to simulate an exception throw in the build
+ /// request logic.
+ /// If the one-shot flag, simulate_build_request_exception_ is true,
+ /// this method will throw an exception, otherwise it will invoke the
+ /// base class method, providing normal functionality.
+ ///
+ /// For parameter description see the NameChangeTransaction implementation.
+ virtual D2UpdateMessagePtr prepNewRequest(DdnsDomainPtr domain) {
+ if (simulate_build_request_exception_) {
+ simulate_build_request_exception_ = false;
+ isc_throw (SimpleAddTransactionError,
+ "Simulated build requests exception");
+ }
+
+ return (NameChangeTransaction::prepNewRequest(domain));
+ }
+
+ /// @brief Simulates receiving a response
+ ///
+ /// This method simulates the completion of a DNSClient send. This allows
+ /// the state handler logic devoted to dealing with IO completion to be
+ /// fully exercised without requiring any actual IO. The two primary
+ /// pieces of information gleaned from IO completion are the DNSClient
+ /// status which indicates whether or not the IO exchange was successful
+ /// and the rcode, which indicates the server's reaction to the request.
+ ///
+ /// This method updates the transaction's DNS status value to that of the
+ /// given parameter, and then constructs and DNS update response message
+ /// with the given rcode value. To complete the simulation it then posts
+ /// a next event of IO_COMPLETED_EVT.
+ ///
+ /// @param status simulated DNSClient status
+ /// @param rcode simulated server response code
+ void fakeResponse(const DNSClient::Status& status,
+ const dns::Rcode& rcode) {
+ // Set the DNS update status. This is normally set in
+ // DNSClient IO completion handler.
+ setDnsUpdateStatus(status);
+
+ // Construct an empty message with the given Rcode.
+ D2UpdateMessagePtr msg(new D2UpdateMessage(D2UpdateMessage::OUTBOUND));
+ msg->setRcode(rcode);
+
+ // Set the update response to the message.
+ setDnsUpdateResponse(msg);
+
+ // Post the IO completion event.
+ postNextEvent(NameChangeTransaction::IO_COMPLETED_EVT);
+ }
+
+ /// @brief Selects the first forward server.
+ /// Some state handlers require a server to have been selected.
+ /// This selects a server without going through the state
+ /// transition(s) to do so.
+ bool selectFwdServer() {
+ if (getForwardDomain()) {
+ initServerSelection(getForwardDomain());
+ selectNextServer();
+ return (getCurrentServer().get() != 0);
+ }
+
+ return (false);
+ }
+
+ /// @brief Selects the first reverse server.
+ /// Some state handlers require a server to have been selected.
+ /// This selects a server without going through the state
+ /// transition(s) to do so.
+ bool selectRevServer() {
+ if (getReverseDomain()) {
+ initServerSelection(getReverseDomain());
+ selectNextServer();
+ return (getCurrentServer().get() != 0);
+ }
+
+ return (false);
+ }
+
+ /// @brief One-shot flag which will simulate sendUpdate failure if true.
+ bool simulate_send_exception_;
+
+ /// @brief One-shot flag which will simulate an exception when sendUpdate
+ /// failure if true.
+ bool simulate_build_request_exception_;
+
+ using StateModel::postNextEvent;
+ using StateModel::setState;
+ using StateModel::initDictionaries;
+ using SimpleAddTransaction::defineEvents;
+ using SimpleAddTransaction::verifyEvents;
+ using SimpleAddTransaction::defineStates;
+ using SimpleAddTransaction::verifyStates;
+ using SimpleAddTransaction::readyHandler;
+ using SimpleAddTransaction::selectingFwdServerHandler;
+ using SimpleAddTransaction::getCurrentServer;
+ using SimpleAddTransaction::setDnsUpdateStatus;
+ using SimpleAddTransaction::replacingFwdAddrsHandler;
+ using SimpleAddTransaction::selectingRevServerHandler;
+ using SimpleAddTransaction::replacingRevPtrsHandler;
+ using SimpleAddTransaction::processAddOkHandler;
+ using SimpleAddTransaction::processAddFailedHandler;
+ using SimpleAddTransaction::buildReplaceFwdAddressRequest;
+ using SimpleAddTransaction::buildReplaceRevPtrsRequest;
+};
+
+typedef boost::shared_ptr<SimpleAddStub> SimpleAddStubPtr;
+
+/// @brief Test fixture for testing SimpleAddTransaction
+///
+/// Note this class uses SimpleAddStub class to exercise non-public
+/// aspects of SimpleAddTransaction.
+class SimpleAddTransactionTest : public TransactionTest {
+public:
+
+ SimpleAddTransactionTest() {
+ }
+
+ virtual ~SimpleAddTransactionTest() {
+ }
+
+ /// @brief Creates a transaction which requests an IPv4 DNS update.
+ ///
+ /// The transaction is constructed around a predefined (i.e. "canned")
+ /// IPv4 NameChangeRequest. The request has both forward and reverse DNS
+ /// changes requested. Based upon the change mask, the transaction
+ /// will have either the forward, reverse, or both domains populated.
+ ///
+ /// @param change_mask determines which change directions are requested
+ SimpleAddStubPtr makeTransaction4(int change_mask = FWD_AND_REV_CHG) {
+ // Creates IPv4 remove request, forward, and reverse domains.
+ setupForIPv4Transaction(dhcp_ddns::CHG_ADD, change_mask);
+
+ // Now create the test transaction as would occur in update manager.
+ return (SimpleAddStubPtr(new SimpleAddStub(io_service_, ncr_,
+ forward_domain_,
+ reverse_domain_, cfg_mgr_)));
+ }
+
+ /// @brief Creates a transaction which requests an IPv6 DNS update.
+ ///
+ /// The transaction is constructed around a predefined (i.e. "canned")
+ /// IPv6 NameChangeRequest. The request has both forward and reverse DNS
+ /// changes requested. Based upon the change mask, the transaction
+ /// will have either the forward, reverse, or both domains populated.
+ ///
+ /// @param change_mask determines which change directions are requested
+ SimpleAddStubPtr makeTransaction6(int change_mask = FWD_AND_REV_CHG) {
+ // Creates IPv6 remove request, forward, and reverse domains.
+ setupForIPv6Transaction(dhcp_ddns::CHG_ADD, change_mask);
+
+ // Now create the test transaction as would occur in update manager.
+ return (SimpleAddStubPtr(new SimpleAddStub(io_service_, ncr_,
+ forward_domain_,
+ reverse_domain_,
+ cfg_mgr_)));
+ }
+
+ /// @brief Create a test transaction at a known point in the state model.
+ ///
+ /// Method prepares a new test transaction and sets its state and next
+ /// event values to those given. This makes the transaction appear to
+ /// be at that point in the state model without having to transition it
+ /// through prerequisite states. It also provides the ability to set
+ /// which change directions are requested: forward change only, reverse
+ /// change only, or both.
+ ///
+ /// @param state value to set as the current state
+ /// @param event value to post as the next event
+ /// @param change_mask determines which change directions are requested
+ /// @param family selects between an IPv4 (AF_INET) and IPv6 (AF_INET6)
+ /// transaction.
+ SimpleAddStubPtr prepHandlerTest(unsigned int state, unsigned int event,
+ unsigned int change_mask = FWD_AND_REV_CHG,
+ short family = AF_INET) {
+ SimpleAddStubPtr name_add = (family == AF_INET ?
+ makeTransaction4(change_mask) :
+ makeTransaction6(change_mask));
+ name_add->initDictionaries();
+ name_add->postNextEvent(event);
+ name_add->setState(state);
+ return (name_add);
+ }
+};
+
+/// @brief Tests SimpleAddTransaction construction.
+/// This test verifies that:
+/// 1. Construction with invalid type of request
+/// 2. Valid construction functions properly
+TEST(SimpleAddTransaction, construction) {
+ asiolink::IOServicePtr io_service(new isc::asiolink::IOService());
+ D2CfgMgrPtr cfg_mgr(new D2CfgMgr());
+
+ const char* msg_str =
+ "{"
+ " \"change-type\" : 1 , "
+ " \"forward-change\" : true , "
+ " \"reverse-change\" : true , "
+ " \"fqdn\" : \"example.com.\" , "
+ " \"ip-address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"0102030405060708\" , "
+ " \"lease-expires-on\" : \"20130121132405\" , "
+ " \"lease-length\" : 1300, "
+ " \"use-conflict-resolution\" : true "
+ "}";
+
+ dhcp_ddns::NameChangeRequestPtr ncr;
+ DnsServerInfoStoragePtr servers;
+ DdnsDomainPtr forward_domain;
+ DdnsDomainPtr reverse_domain;
+ DdnsDomainPtr empty_domain;
+
+ ASSERT_NO_THROW(ncr = dhcp_ddns::NameChangeRequest::fromJSON(msg_str));
+ ASSERT_NO_THROW(forward_domain.reset(new DdnsDomain("*", servers)));
+ ASSERT_NO_THROW(reverse_domain.reset(new DdnsDomain("*", servers)));
+
+ // Verify that construction with wrong change type fails.
+ EXPECT_THROW(SimpleAddTransaction(io_service, ncr,
+ forward_domain, reverse_domain, cfg_mgr),
+ SimpleAddTransactionError);
+
+ // Verify that a valid construction attempt works.
+ ncr->setChangeType(isc::dhcp_ddns::CHG_ADD);
+ EXPECT_NO_THROW(SimpleAddTransaction(io_service, ncr,
+ forward_domain, reverse_domain,
+ cfg_mgr));
+}
+
+/// @brief Tests event and state dictionary construction and verification.
+TEST_F(SimpleAddTransactionTest, dictionaryCheck) {
+ SimpleAddStubPtr name_add;
+ ASSERT_NO_THROW(name_add = makeTransaction4());
+ // Verify that the event and state dictionary validation fails prior
+ // dictionary construction.
+ ASSERT_THROW(name_add->verifyEvents(), StateModelError);
+ ASSERT_THROW(name_add->verifyStates(), StateModelError);
+
+ // Construct both dictionaries.
+ ASSERT_NO_THROW(name_add->defineEvents());
+ ASSERT_NO_THROW(name_add->defineStates());
+
+ // Verify both event and state dictionaries now pass validation.
+ ASSERT_NO_THROW(name_add->verifyEvents());
+ ASSERT_NO_THROW(name_add->verifyStates());
+}
+
+/// @brief Tests construction of a DNS update request for replacing a forward
+/// dns entry.
+TEST_F(SimpleAddTransactionTest, buildReplaceFwdAddressRequest) {
+ // Create a IPv4 forward replace transaction.
+ // Verify the request builds without error.
+ // and then verify the request contents.
+ SimpleAddStubPtr name_add;
+ ASSERT_NO_THROW(name_add = makeTransaction4());
+ ASSERT_NO_THROW(name_add->buildReplaceFwdAddressRequest());
+ checkSimpleReplaceFwdAddressRequest(*name_add);
+
+ // Create a IPv6 forward replace transaction.
+ // Verify the request builds without error.
+ // and then verify the request contents.
+ ASSERT_NO_THROW(name_add = makeTransaction6());
+ ASSERT_NO_THROW(name_add->buildReplaceFwdAddressRequest());
+ checkSimpleReplaceFwdAddressRequest(*name_add);
+}
+
+/// @brief Tests the construction of a DNS update request for replacing a
+/// reverse dns entry.
+TEST_F(SimpleAddTransactionTest, buildReplaceRevPtrsRequest) {
+ // Create a IPv4 reverse replace transaction.
+ // Verify the request builds without error.
+ // and then verify the request contents.
+ SimpleAddStubPtr name_add;
+ ASSERT_NO_THROW(name_add = makeTransaction4());
+ ASSERT_NO_THROW(name_add->buildReplaceRevPtrsRequest());
+ checkReplaceRevPtrsRequest(*name_add);
+
+ // Create a IPv6 reverse replace transaction.
+ // Verify the request builds without error.
+ // and then verify the request contents.
+ ASSERT_NO_THROW(name_add = makeTransaction6());
+ ASSERT_NO_THROW(name_add->buildReplaceRevPtrsRequest());
+ checkReplaceRevPtrsRequest(*name_add);
+}
+
+// Tests the readyHandler functionality.
+// It verifies behavior for the following scenarios:
+//
+// 1. Posted event is START_EVT and request includes only a forward change
+// 2. Posted event is START_EVT and request includes both a forward and a
+// reverse change
+// 3. Posted event is START_EVT and request includes only a reverse change
+// 4. Posted event is invalid
+//
+TEST_F(SimpleAddTransactionTest, readyHandler) {
+ SimpleAddStubPtr name_add;
+
+ // Create a transaction which includes only a forward change.
+ ASSERT_NO_THROW(
+ name_add = prepHandlerTest(NameChangeTransaction::READY_ST,
+ StateModel::START_EVT, FORWARD_CHG)
+ );
+
+ // Run readyHandler.
+ EXPECT_NO_THROW(name_add->readyHandler());
+
+ // Verify that a request requiring only a forward change, transitions to
+ // selecting a forward server.
+ CHECK_CONTEXT(name_add, NameChangeTransaction::SELECTING_FWD_SERVER_ST,
+ NameChangeTransaction::SELECT_SERVER_EVT);
+
+ // Create a transaction which includes both a forward and a reverse change.
+ ASSERT_NO_THROW(
+ name_add = prepHandlerTest(NameChangeTransaction::READY_ST,
+ StateModel::START_EVT, FWD_AND_REV_CHG)
+ );
+
+ // Run readyHandler.
+ EXPECT_NO_THROW(name_add->readyHandler());
+
+ // Verify that a request requiring both forward and reverse, starts with
+ // the forward change by transitioning to selecting a forward server.
+ CHECK_CONTEXT(name_add, NameChangeTransaction::SELECTING_FWD_SERVER_ST,
+ NameChangeTransaction::SELECT_SERVER_EVT);
+
+ // Create and prep a reverse only transaction.
+ ASSERT_NO_THROW(
+ name_add = prepHandlerTest(NameChangeTransaction::READY_ST,
+ StateModel::START_EVT, REVERSE_CHG)
+ );
+
+ // Run readyHandler.
+ EXPECT_NO_THROW(name_add->readyHandler());
+
+ // Verify that a request requiring only a reverse change, transitions to
+ // selecting a reverse server.
+ CHECK_CONTEXT(name_add, NameChangeTransaction::SELECTING_REV_SERVER_ST,
+ NameChangeTransaction::SELECT_SERVER_EVT);
+
+ // Create and prep transaction, poised to run the handler but with an
+ // invalid event.
+ ASSERT_NO_THROW(
+ name_add = prepHandlerTest(NameChangeTransaction::READY_ST, StateModel::NOP_EVT)
+ );
+
+ // Running the readyHandler should throw.
+ EXPECT_THROW(name_add->readyHandler(), SimpleAddTransactionError);
+}
+
+// Tests the selectingFwdServerHandler functionality.
+// It verifies behavior for the following scenarios:
+//
+// 1. Posted event is SELECT_SERVER_EVT
+// 2. Posted event is SERVER_IO_ERROR_EVT
+// 3. Posted event is invalid
+//
+TEST_F(SimpleAddTransactionTest, selectingFwdServerHandler) {
+ SimpleAddStubPtr name_add;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(
+ name_add = prepHandlerTest(NameChangeTransaction::SELECTING_FWD_SERVER_ST,
+ NameChangeTransaction::SELECT_SERVER_EVT)
+ );
+
+ // Call selectingFwdServerHandler enough times to select all of the
+ // servers in it's current domain. The first time, it will be with
+ // next event of SELECT_SERVER_EVT. Thereafter it will be with a next
+ // event of SERVER_IO_ERROR_EVT.
+ int num_servers = name_add->getForwardDomain()->getServers()->size();
+ for (int i = 0; i < num_servers; ++i) {
+ SCOPED_TRACE(testing::Message() << " num_servers: " << num_servers
+ << " selections: " << i);
+ // Run selectingFwdServerHandler.
+ ASSERT_NO_THROW(name_add->selectingFwdServerHandler());
+
+ // Verify that a server was selected.
+ ASSERT_TRUE(name_add->getCurrentServer());
+
+ // Verify that we transitioned correctly.
+ CHECK_CONTEXT(name_add, SimpleAddTransaction::REPLACING_FWD_ADDRS_ST,
+ NameChangeTransaction::SERVER_SELECTED_EVT);
+
+ // Post a server IO error event. This simulates an IO error occurring
+ // and a need to select the new server.
+ ASSERT_NO_THROW(name_add->postNextEvent(NameChangeTransaction::
+ SERVER_IO_ERROR_EVT));
+ }
+
+ // We should have exhausted the list of servers. Processing another
+ // SERVER_IO_ERROR_EVT should transition us to failure.
+ EXPECT_NO_THROW(name_add->selectingFwdServerHandler());
+ CHECK_CONTEXT(name_add, NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ NameChangeTransaction::NO_MORE_SERVERS_EVT);
+
+ // Create and prep transaction, poised to run the handler but with an
+ // invalid event.
+ ASSERT_NO_THROW(
+ name_add = prepHandlerTest(NameChangeTransaction::SELECTING_FWD_SERVER_ST,
+ StateModel::NOP_EVT)
+ );
+
+ // Running the handler should throw.
+ EXPECT_THROW(name_add->selectingFwdServerHandler(),
+ SimpleAddTransactionError);
+}
+
+// ************************ replacingFwdAddrHandler Tests *****************
+
+// Tests that replacingFwdAddrsHandler rejects invalid events.
+TEST_F(SimpleAddTransactionTest, replacingFwdAddrsHandler_InvalidEvent) {
+ SimpleAddStubPtr name_add;
+ // Create and prep a transaction, poised to run the handler but with
+ // an invalid event.
+ ASSERT_NO_THROW(
+ name_add = prepHandlerTest(SimpleAddTransaction::REPLACING_FWD_ADDRS_ST,
+ NameChangeTransaction::StateModel::NOP_EVT)
+ );
+
+ // Running the handler should throw.
+ EXPECT_THROW(name_add->replacingFwdAddrsHandler(), SimpleAddTransactionError);
+}
+
+// Tests replacingFwdAddrsHandler with the following scenario:
+//
+// The request includes only a forward change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The update request is sent without error.
+// A server response is received which indicates successful update.
+//
+TEST_F(SimpleAddTransactionTest, replacingFwdAddrsHandler_FwdOnlyAddOK) {
+ SimpleAddStubPtr name_add;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(
+ name_add = prepHandlerTest(SimpleAddTransaction::REPLACING_FWD_ADDRS_ST,
+ SimpleAddTransaction::SERVER_SELECTED_EVT, FORWARD_CHG)
+ );
+
+ // Should not be an update message yet.
+ D2UpdateMessagePtr update_msg = name_add->getDnsUpdateRequest();
+ ASSERT_FALSE(update_msg);
+
+ // At this point completion flags should be false.
+ EXPECT_FALSE(name_add->getForwardChangeCompleted());
+ EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+ // Run replacingFwdAddrsHandler to construct and send the request.
+ EXPECT_NO_THROW(name_add->replacingFwdAddrsHandler());
+
+ // Verify that an update message was constructed properly.
+ checkSimpleReplaceFwdAddressRequest(*name_add);
+
+ // Verify that we are still in this state and next event is NOP_EVT.
+ // This indicates we "sent" the message and are waiting for IO completion.
+ CHECK_CONTEXT(name_add, SimpleAddTransaction::REPLACING_FWD_ADDRS_ST,
+ NameChangeTransaction::NOP_EVT);
+
+ // Simulate receiving a successful update response.
+ name_add->fakeResponse(DNSClient::SUCCESS, dns::Rcode::NOERROR());
+
+ // Run replacingFwdAddrsHandler again to process the response.
+ EXPECT_NO_THROW(name_add->replacingFwdAddrsHandler());
+
+ // Forward completion should be true, reverse should be false.
+ EXPECT_TRUE(name_add->getForwardChangeCompleted());
+ EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+ // Since it is a forward only change, we should be done.
+ // Verify that we transitioned correctly.
+ CHECK_CONTEXT(name_add, NameChangeTransaction::PROCESS_TRANS_OK_ST,
+ NameChangeTransaction::UPDATE_OK_EVT);
+}
+
+// Tests replacingFwdAddrsHandler with the following scenario:
+//
+// The request includes a forward and reverse change.
+// The update request is sent without error.
+// A server response is received which indicates the update was rejected.
+//
+TEST_F(SimpleAddTransactionTest, replacingFwdAddrsHandler_OtherRcode) {
+ SimpleAddStubPtr name_add;
+ // Create the transaction.
+ ASSERT_NO_THROW(
+ name_add = prepHandlerTest(SimpleAddTransaction::REPLACING_FWD_ADDRS_ST,
+ SimpleAddTransaction::SERVER_SELECTED_EVT, FWD_AND_REV_CHG)
+ );
+
+ // Select a server to satisfy log statements.
+ ASSERT_TRUE(name_add->selectFwdServer());
+
+ // Run replacingFwdAddrsHandler to construct and send the request.
+ EXPECT_NO_THROW(name_add->replacingFwdAddrsHandler());
+
+ // Simulate receiving server rejection response. Per RFC, anything other
+ // than no error or FQDN in use is failure. Arbitrarily choosing refused.
+ name_add->fakeResponse(DNSClient::SUCCESS, dns::Rcode::REFUSED());
+
+ // Run replacingFwdAddrsHandler again to process the response.
+ EXPECT_NO_THROW(name_add->replacingFwdAddrsHandler());
+
+ // Completion flags should still be false.
+ EXPECT_FALSE(name_add->getForwardChangeCompleted());
+ EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+ // We should have failed the transaction. Verify that we transitioned
+ // correctly.
+ CHECK_CONTEXT(name_add, NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ NameChangeTransaction::UPDATE_FAILED_EVT);
+}
+
+// Tests replacingFwdAddrsHandler with the following scenario:
+//
+// The request includes a forward and reverse change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The update request send times out MAX_UPDATE_TRIES_PER_SERVER times.
+//
+TEST_F(SimpleAddTransactionTest, replacingFwdAddrsHandler_Timeout) {
+ SimpleAddStubPtr name_add;
+
+ // Create the transaction.
+ ASSERT_NO_THROW(
+ name_add = prepHandlerTest(SimpleAddTransaction::REPLACING_FWD_ADDRS_ST,
+ SimpleAddTransaction::SERVER_SELECTED_EVT, FWD_AND_REV_CHG)
+ );
+
+ // Select a server to satisfy log statements.
+ ASSERT_TRUE(name_add->selectFwdServer());
+
+
+ // Verify that we can make maximum number of update attempts permitted
+ // and then transition to selecting a new server.
+ int max_tries = NameChangeTransaction::MAX_UPDATE_TRIES_PER_SERVER;
+ for (int i = 1; i <= max_tries; ++i) {
+ // Run replacingFwdAddrsHandler to send the request.
+ EXPECT_NO_THROW(name_add->replacingFwdAddrsHandler());
+
+ // Simulate a server IO timeout.
+ name_add->setDnsUpdateStatus(DNSClient::TIMEOUT);
+ name_add->postNextEvent(NameChangeTransaction::IO_COMPLETED_EVT);
+
+ // Run replacingFwdAddrsHandler again to process the response.
+ EXPECT_NO_THROW(name_add->replacingFwdAddrsHandler());
+
+ // Completion flags should be false.
+ EXPECT_FALSE(name_add->getForwardChangeCompleted());
+ EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+ if (i < max_tries) {
+ // We should be ready to try again.
+ CHECK_CONTEXT(name_add, SimpleAddTransaction::REPLACING_FWD_ADDRS_ST,
+ NameChangeTransaction::SERVER_SELECTED_EVT);
+ } else {
+ // Server retries should be exhausted, time for a new server.
+ CHECK_CONTEXT(name_add, SimpleAddTransaction::SELECTING_FWD_SERVER_ST,
+ NameChangeTransaction::SERVER_IO_ERROR_EVT);
+ }
+ }
+}
+
+// Tests replacingFwdAddrsHandler with the following scenario:
+//
+// The request includes a forward and reverse change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The update request is sent but a corrupt response is received, this occurs
+// MAX_UPDATE_TRIES_PER_SERVER times.
+//
+TEST_F(SimpleAddTransactionTest, replacingFwdAddrsHandler_CorruptResponse) {
+ SimpleAddStubPtr name_add;
+
+ // Create the transaction.
+ ASSERT_NO_THROW(
+ name_add = prepHandlerTest(SimpleAddTransaction::REPLACING_FWD_ADDRS_ST,
+ SimpleAddTransaction::SERVER_SELECTED_EVT, FWD_AND_REV_CHG)
+ );
+
+ // Select a server to satisfy log statements.
+ ASSERT_TRUE(name_add->selectFwdServer());
+
+ // Verify that we can make maximum number of update attempts permitted
+ // and then transition to selecting a new server.
+ int max_tries = NameChangeTransaction::MAX_UPDATE_TRIES_PER_SERVER;
+ for (int i = 1; i <= max_tries; ++i) {
+ // Run replacingFwdAddrsHandler to send the request.
+ EXPECT_NO_THROW(name_add->replacingFwdAddrsHandler());
+
+ // Simulate a corrupt server response.
+ name_add->setDnsUpdateStatus(DNSClient::INVALID_RESPONSE);
+ name_add->postNextEvent(NameChangeTransaction::IO_COMPLETED_EVT);
+
+ // Run replacingFwdAddrsHandler again to process the response.
+ EXPECT_NO_THROW(name_add->replacingFwdAddrsHandler());
+
+ // Completion flags should be false.
+ EXPECT_FALSE(name_add->getForwardChangeCompleted());
+ EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+ if (i < max_tries) {
+ // We should be ready to try again.
+ CHECK_CONTEXT(name_add, SimpleAddTransaction::REPLACING_FWD_ADDRS_ST,
+ NameChangeTransaction::SERVER_SELECTED_EVT);
+ } else {
+ // Server retries should be exhausted, time for a new server.
+ CHECK_CONTEXT(name_add, SimpleAddTransaction::SELECTING_FWD_SERVER_ST,
+ NameChangeTransaction::SERVER_IO_ERROR_EVT);
+ }
+ }
+}
+
+// Tests the selectingRevServerHandler functionality.
+// It verifies behavior for the following scenarios:
+//
+// 1. Posted event is SELECT_SERVER_EVT
+// 2. Posted event is SERVER_IO_ERROR_EVT
+// 3. Posted event is invalid
+//
+TEST_F(SimpleAddTransactionTest, selectingRevServerHandler) {
+ SimpleAddStubPtr name_add;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(
+ name_add = prepHandlerTest(NameChangeTransaction::SELECTING_REV_SERVER_ST,
+ NameChangeTransaction::SELECT_SERVER_EVT)
+ );
+
+ // Call selectingRevServerHandler enough times to select all of the
+ // servers in it's current domain. The first time, it will be with
+ // next event of SELECT_SERVER_EVT. Thereafter it will be with a next
+ // event of SERVER_IO_ERROR_EVT.
+ int num_servers = name_add->getReverseDomain()->getServers()->size();
+ for (int i = 0; i < num_servers; ++i) {
+ SCOPED_TRACE(testing::Message() << " num_servers: " << num_servers
+ << " selections: " << i);
+ // Run selectingRevServerHandler.
+ ASSERT_NO_THROW(name_add->selectingRevServerHandler());
+
+ // Verify that a server was selected.
+ ASSERT_TRUE(name_add->getCurrentServer());
+
+ // Verify that we transitioned correctly.
+ CHECK_CONTEXT(name_add, SimpleAddTransaction::REPLACING_REV_PTRS_ST,
+ NameChangeTransaction::SERVER_SELECTED_EVT);
+
+ // Post a server IO error event. This simulates an IO error occurring
+ // and a need to select the new server.
+ ASSERT_NO_THROW(name_add->postNextEvent(NameChangeTransaction::
+ SERVER_IO_ERROR_EVT));
+ }
+
+ // We should have exhausted the list of servers. Processing another
+ // SERVER_IO_ERROR_EVT should transition us to failure.
+ EXPECT_NO_THROW(name_add->selectingRevServerHandler());
+ CHECK_CONTEXT(name_add, NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ NameChangeTransaction::NO_MORE_SERVERS_EVT);
+
+ // Create and prep transaction, poised to run the handler but with an
+ // invalid event.
+ ASSERT_NO_THROW(
+ name_add = prepHandlerTest(NameChangeTransaction::SELECTING_REV_SERVER_ST,
+ StateModel::NOP_EVT)
+ );
+
+ // Running the handler should throw.
+ EXPECT_THROW(name_add->selectingRevServerHandler(),
+ SimpleAddTransactionError);
+}
+
+//************************** replacingRevPtrsHandler tests *****************
+
+// Tests that replacingRevPtrsHandler rejects invalid events.
+TEST_F(SimpleAddTransactionTest, replacingRevPtrsHandler_InvalidEvent) {
+ SimpleAddStubPtr name_add;
+ // Create and prep a transaction, poised to run the handler but with
+ // an invalid event.
+ ASSERT_NO_THROW(
+ name_add = prepHandlerTest(SimpleAddTransaction::REPLACING_REV_PTRS_ST,
+ NameChangeTransaction::StateModel::NOP_EVT)
+ );
+
+ // Running the handler should throw.
+ EXPECT_THROW(name_add->replacingRevPtrsHandler(),
+ SimpleAddTransactionError);
+}
+
+// Tests replacingRevPtrsHandler with the following scenario:
+//
+// The request includes only a reverse change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The update request is sent without error.
+// A server response is received which indicates successful update.
+//
+TEST_F(SimpleAddTransactionTest, replacingRevPtrsHandler_FwdOnlyAddOK) {
+ SimpleAddStubPtr name_add;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(
+ name_add = prepHandlerTest(SimpleAddTransaction::REPLACING_REV_PTRS_ST,
+ SimpleAddTransaction::SERVER_SELECTED_EVT, REVERSE_CHG)
+ );
+
+ // Should not be an update message yet.
+ D2UpdateMessagePtr update_msg = name_add->getDnsUpdateRequest();
+ ASSERT_FALSE(update_msg);
+
+ // At this point completion flags should be false.
+ EXPECT_FALSE(name_add->getForwardChangeCompleted());
+ EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+ // Run replacingRevPtrsHandler to construct and send the request.
+ EXPECT_NO_THROW(name_add->replacingRevPtrsHandler());
+
+ // Verify that an update message was constructed properly.
+ checkReplaceRevPtrsRequest(*name_add);
+
+ // Verify that we are still in this state and next event is NOP_EVT.
+ // This indicates we "sent" the message and are waiting for IO completion.
+ CHECK_CONTEXT(name_add, SimpleAddTransaction::REPLACING_REV_PTRS_ST,
+ NameChangeTransaction::NOP_EVT);
+
+ // Simulate receiving a successful update response.
+ name_add->fakeResponse(DNSClient::SUCCESS, dns::Rcode::NOERROR());
+
+ // Run replacingRevPtrsHandler again to process the response.
+ EXPECT_NO_THROW(name_add->replacingRevPtrsHandler());
+
+ // Forward completion should be false, reverse should be true.
+ EXPECT_FALSE(name_add->getForwardChangeCompleted());
+ EXPECT_TRUE(name_add->getReverseChangeCompleted());
+
+ // Since it is a reverse change, we should be done.
+ // Verify that we transitioned correctly.
+ CHECK_CONTEXT(name_add, NameChangeTransaction::PROCESS_TRANS_OK_ST,
+ NameChangeTransaction::UPDATE_OK_EVT);
+}
+
+// Tests replacingRevPtrsHandler with the following scenario:
+//
+// The request includes only a reverse change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The update request is sent without error.
+// A server response is received which indicates the update was rejected.
+//
+TEST_F(SimpleAddTransactionTest, replacingRevPtrsHandler_OtherRcode) {
+ SimpleAddStubPtr name_add;
+ // Create the transaction.
+ ASSERT_NO_THROW(
+ name_add = prepHandlerTest(SimpleAddTransaction::REPLACING_REV_PTRS_ST,
+ SimpleAddTransaction::SERVER_SELECTED_EVT, REVERSE_CHG)
+ );
+
+ // Select a server to satisfy log statements.
+ ASSERT_TRUE(name_add->selectRevServer());
+
+ // Run replacingRevPtrsHandler to construct and send the request.
+ EXPECT_NO_THROW(name_add->replacingRevPtrsHandler());
+
+ // Simulate receiving server rejection response. Per RFC, anything other
+ // than no error is failure. Arbitrarily choosing refused.
+ name_add->fakeResponse(DNSClient::SUCCESS, dns::Rcode::REFUSED());
+
+ // Run replacingRevPtrsHandler again to process the response.
+ EXPECT_NO_THROW(name_add->replacingRevPtrsHandler());
+
+ // Completion flags should still be false.
+ EXPECT_FALSE(name_add->getForwardChangeCompleted());
+ EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+ // We should have failed the transaction. Verify that we transitioned
+ // correctly.
+ CHECK_CONTEXT(name_add, NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ NameChangeTransaction::UPDATE_FAILED_EVT);
+}
+
+// Tests replacingRevPtrsHandler with the following scenario:
+//
+// The request includes only a reverse change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The update request send times out MAX_UPDATE_TRIES_PER_SERVER times.
+//
+TEST_F(SimpleAddTransactionTest, replacingRevPtrsHandler_Timeout) {
+ SimpleAddStubPtr name_add;
+ // Create the transaction.
+ ASSERT_NO_THROW(
+ name_add = prepHandlerTest(SimpleAddTransaction::REPLACING_REV_PTRS_ST,
+ SimpleAddTransaction::SERVER_SELECTED_EVT, REVERSE_CHG)
+ );
+
+ // Select a server to satisfy log statements.
+ ASSERT_TRUE(name_add->selectRevServer());
+
+ // Verify that we can make maximum number of update attempts permitted
+ // and then transition to selecting a new server.
+ int max_tries = NameChangeTransaction::MAX_UPDATE_TRIES_PER_SERVER;
+ for (int i = 1; i <= max_tries; ++i) {
+ // Run replacingRevPtrsHandler to send the request.
+ EXPECT_NO_THROW(name_add->replacingRevPtrsHandler());
+
+ // Simulate a server IO timeout.
+ name_add->setDnsUpdateStatus(DNSClient::TIMEOUT);
+ name_add->postNextEvent(NameChangeTransaction::IO_COMPLETED_EVT);
+
+ // Run replacingRevPtrsHandler again to process the response.
+ EXPECT_NO_THROW(name_add->replacingRevPtrsHandler());
+
+ // Completion flags should be false.
+ EXPECT_FALSE(name_add->getForwardChangeCompleted());
+ EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+ if (i < max_tries) {
+ // We should be ready to try again.
+ CHECK_CONTEXT(name_add, SimpleAddTransaction::REPLACING_REV_PTRS_ST,
+ NameChangeTransaction::SERVER_SELECTED_EVT);
+ } else {
+ // Server retries should be exhausted, time for a new server.
+ CHECK_CONTEXT(name_add, SimpleAddTransaction::SELECTING_REV_SERVER_ST,
+ NameChangeTransaction::SERVER_IO_ERROR_EVT);
+ }
+ }
+}
+
+// Tests replacingRevPtrsHandler with the following scenario:
+//
+// The request includes only a reverse change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The update request is sent but a corrupt response is received, this occurs
+// MAX_UPDATE_TRIES_PER_SERVER times.
+//
+TEST_F(SimpleAddTransactionTest, replacingRevPtrsHandler_CorruptResponse) {
+ SimpleAddStubPtr name_add;
+ // Create the transaction.
+ ASSERT_NO_THROW(
+ name_add = prepHandlerTest(SimpleAddTransaction::REPLACING_REV_PTRS_ST,
+ SimpleAddTransaction::SERVER_SELECTED_EVT, REVERSE_CHG)
+ );
+
+ // Select a server to satisfy log statements.
+ ASSERT_TRUE(name_add->selectRevServer());
+
+ // Verify that we can make maximum number of update attempts permitted
+ // and then transition to selecting a new server.
+ int max_tries = NameChangeTransaction::MAX_UPDATE_TRIES_PER_SERVER;
+ for (int i = 1; i <= max_tries; ++i) {
+ // Run replacingRevPtrsHandler to send the request.
+ EXPECT_NO_THROW(name_add->replacingRevPtrsHandler());
+
+ // Simulate a server corrupt response.
+ name_add->setDnsUpdateStatus(DNSClient::INVALID_RESPONSE);
+ name_add->postNextEvent(NameChangeTransaction::IO_COMPLETED_EVT);
+
+ // Run replacingRevPtrsHandler again to process the response.
+ EXPECT_NO_THROW(name_add->replacingRevPtrsHandler());
+
+ // Completion flags should be false.
+ EXPECT_FALSE(name_add->getForwardChangeCompleted());
+ EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+ if (i < max_tries) {
+ // We should be ready to try again.
+ CHECK_CONTEXT(name_add, SimpleAddTransaction::REPLACING_REV_PTRS_ST,
+ NameChangeTransaction::SERVER_SELECTED_EVT);
+ } else {
+ // Server retries should be exhausted, time for a new server.
+ CHECK_CONTEXT(name_add, SimpleAddTransaction::SELECTING_REV_SERVER_ST,
+ NameChangeTransaction::SERVER_IO_ERROR_EVT);
+ }
+ }
+}
+
+// Tests the processAddOkHandler functionality.
+// It verifies behavior for the following scenarios:
+//
+// 1. Posted event is UPDATE_OK_EVT
+// 2. Posted event is invalid
+//
+TEST_F(SimpleAddTransactionTest, processAddOkHandler) {
+ SimpleAddStubPtr name_add;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(
+ name_add = prepHandlerTest(NameChangeTransaction::PROCESS_TRANS_OK_ST,
+ NameChangeTransaction::UPDATE_OK_EVT)
+ );
+
+ // Run processAddOkHandler.
+ EXPECT_NO_THROW(name_add->processAddOkHandler());
+
+ // Verify that a server was selected.
+ EXPECT_EQ(dhcp_ddns::ST_COMPLETED, name_add->getNcrStatus());
+
+ // Verify that the model has ended.
+ CHECK_CONTEXT(name_add, StateModel::END_ST, StateModel::END_EVT);
+
+ // Create and prep transaction, poised to run the handler but with an
+ // invalid event.
+ ASSERT_NO_THROW(
+ name_add = prepHandlerTest(NameChangeTransaction::PROCESS_TRANS_OK_ST,
+ StateModel::NOP_EVT)
+ );
+
+ // Running the handler should throw.
+ EXPECT_THROW(name_add->processAddOkHandler(), SimpleAddTransactionError);
+}
+
+// Tests the processAddFailedHandler functionality.
+// It verifies behavior for the following scenarios:
+//
+// 1. Posted event is UPDATE_FAILED_EVT
+// 2. Posted event is invalid
+//
+TEST_F(SimpleAddTransactionTest, processAddFailedHandler) {
+ SimpleAddStubPtr name_add;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(
+ name_add = prepHandlerTest(NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ NameChangeTransaction::UPDATE_FAILED_EVT)
+ );
+
+ // Run processAddFailedHandler.
+ EXPECT_NO_THROW(name_add->processAddFailedHandler());
+
+ // Verify that a server was selected.
+ EXPECT_EQ(dhcp_ddns::ST_FAILED, name_add->getNcrStatus());
+
+ // Verify that the model has ended. (Remember, the transaction failed NOT
+ // the model. The model should have ended normally.)
+ CHECK_CONTEXT(name_add, StateModel::END_ST, StateModel::END_EVT);
+
+ // Create and prep transaction, poised to run the handler but with an
+ // invalid event.
+ ASSERT_NO_THROW(
+ name_add = prepHandlerTest(NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ StateModel::NOP_EVT)
+ );
+
+ // Running the handler should throw.
+ EXPECT_THROW(name_add->processAddFailedHandler(), SimpleAddTransactionError);
+}
+
+// Tests the processAddFailedHandler functionality.
+// It verifies behavior for posted event of NO_MORE_SERVERS_EVT.
+TEST_F(SimpleAddTransactionTest, processAddFailedHandler_NoMoreServers) {
+ SimpleAddStubPtr name_add;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(
+ name_add = prepHandlerTest(NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ NameChangeTransaction::NO_MORE_SERVERS_EVT)
+ );
+
+ // Run processAddFailedHandler.
+ EXPECT_NO_THROW(name_add->processAddFailedHandler());
+
+ // Verify that a server was selected.
+ EXPECT_EQ(dhcp_ddns::ST_FAILED, name_add->getNcrStatus());
+
+ // Verify that the model has ended. (Remember, the transaction failed NOT
+ // the model. The model should have ended normally.)
+ CHECK_CONTEXT(name_add, StateModel::END_ST, StateModel::END_EVT);
+}
+
+// Tests replacingFwdAddrsHandler with the following scenario:
+//
+// The request includes only a forward change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The send update request fails due to an unexpected exception.
+//
+TEST_F(SimpleAddTransactionTest, replacingFwdAddrsHandler_SendUpdateException) {
+ SimpleAddStubPtr name_add;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(
+ name_add = prepHandlerTest(SimpleAddTransaction::REPLACING_FWD_ADDRS_ST,
+ NameChangeTransaction::SERVER_SELECTED_EVT, FORWARD_CHG)
+ );
+
+ name_add->simulate_send_exception_ = true;
+
+ // Run replacingFwdAddrsHandler to construct and send the request.
+ ASSERT_NO_THROW(name_add->replacingFwdAddrsHandler());
+
+ // Completion flags should be false.
+ EXPECT_FALSE(name_add->getForwardChangeCompleted());
+ EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+ // Since IO exceptions should be gracefully handled, any that occur
+ // are unanticipated, and deemed unrecoverable, so the transaction should
+ // be transitioned to failure.
+ CHECK_CONTEXT(name_add, NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ NameChangeTransaction::UPDATE_FAILED_EVT);
+}
+
+// Tests replacingRevPtrHandler with the following scenario:
+//
+// The request includes only a reverse change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The send update request fails due to an unexpected exception.
+//
+TEST_F(SimpleAddTransactionTest, replacingRevPtrsHandler_SendUpdateException) {
+ SimpleAddStubPtr name_add;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(
+ name_add = prepHandlerTest(SimpleAddTransaction::REPLACING_REV_PTRS_ST,
+ NameChangeTransaction::SERVER_SELECTED_EVT, REVERSE_CHG)
+ );
+
+ name_add->simulate_send_exception_ = true;
+
+ // Run replacingRevPtrsHandler to construct and send the request.
+ ASSERT_NO_THROW(name_add->replacingRevPtrsHandler());
+
+ // Completion flags should be false.
+ EXPECT_FALSE(name_add->getForwardChangeCompleted());
+ EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+ // Since IO exceptions should be gracefully handled, any that occur
+ // are unanticipated, and deemed unrecoverable, so the transaction should
+ // be transitioned to failure.
+ CHECK_CONTEXT(name_add, NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ NameChangeTransaction::UPDATE_FAILED_EVT);
+}
+
+// Tests replacingFwdAddrsHandler with the following scenario:
+//
+// The request includes only a forward change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The request build fails due to an unexpected exception.
+//
+TEST_F(SimpleAddTransactionTest, replacingFwdAddrsHandler_BuildRequestException) {
+ SimpleAddStubPtr name_add;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(
+ name_add = prepHandlerTest(SimpleAddTransaction::REPLACING_FWD_ADDRS_ST,
+ NameChangeTransaction::SERVER_SELECTED_EVT, FORWARD_CHG)
+ );
+
+ // Set the one-shot exception simulation flag.
+ name_add->simulate_build_request_exception_ = true;
+
+ // Run replacingFwdAddrsHandler to construct and send the request.
+ // This should fail with a build request throw which should be caught
+ // in the state handler.
+ ASSERT_NO_THROW(name_add->replacingFwdAddrsHandler());
+
+ // Verify we did not attempt to send anything.
+ EXPECT_EQ(0, name_add->getUpdateAttempts());
+
+ // Completion flags should be false.
+ EXPECT_FALSE(name_add->getForwardChangeCompleted());
+ EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+ // Since IO exceptions should be gracefully handled, any that occur
+ // are unanticipated, and deemed unrecoverable, so the transaction should
+ // be transitioned to failure.
+ CHECK_CONTEXT(name_add, NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ NameChangeTransaction::UPDATE_FAILED_EVT);
+}
+
+// Tests replacingRevPtrHandler with the following scenario:
+//
+// The request includes only a reverse change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The request build fails due to an unexpected exception.
+//
+TEST_F(SimpleAddTransactionTest, replacingRevPtrsHandler_BuildRequestException) {
+ SimpleAddStubPtr name_add;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(
+ name_add = prepHandlerTest(SimpleAddTransaction::REPLACING_REV_PTRS_ST,
+ NameChangeTransaction::SERVER_SELECTED_EVT, REVERSE_CHG)
+ );
+
+ // Set the one-shot exception simulation flag.
+ name_add->simulate_build_request_exception_ = true;
+
+ // Run replacingRevPtrsHandler to construct and send the request.
+ // This should fail with a build request throw which should be caught
+ // in the state handler.
+ ASSERT_NO_THROW(name_add->replacingRevPtrsHandler());
+
+ // Verify we did not attempt to send anything.
+ EXPECT_EQ(0, name_add->getUpdateAttempts());
+
+ // Completion flags should be false.
+ EXPECT_FALSE(name_add->getForwardChangeCompleted());
+ EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+ // Since IO exceptions should be gracefully handled, any that occur
+ // are unanticipated, and deemed unrecoverable, so the transaction should
+ // be transitioned to failure.
+ CHECK_CONTEXT(name_add, NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ NameChangeTransaction::UPDATE_FAILED_EVT);
+}
+
+}
diff --git a/src/bin/d2/tests/simple_remove_unittests.cc b/src/bin/d2/tests/simple_remove_unittests.cc
new file mode 100644
index 0000000..1d53400
--- /dev/null
+++ b/src/bin/d2/tests/simple_remove_unittests.cc
@@ -0,0 +1,1238 @@
+// Copyright (C) 2020-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <asiolink/io_service.h>
+#include <d2/simple_remove.h>
+#include <d2srv/d2_cfg_mgr.h>
+#include <d2srv/testutils/nc_test_utils.h>
+#include <dns/messagerenderer.h>
+
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::d2;
+using namespace isc::util;
+
+
+namespace {
+
+/// @brief Test class derived from SimpleRemoveTransaction to provide visibility
+// to protected methods.
+class SimpleRemoveStub : public SimpleRemoveTransaction {
+public:
+ SimpleRemoveStub(asiolink::IOServicePtr& io_service,
+ dhcp_ddns::NameChangeRequestPtr& ncr,
+ DdnsDomainPtr& forward_domain,
+ DdnsDomainPtr& reverse_domain,
+ D2CfgMgrPtr& cfg_mgr)
+ : SimpleRemoveTransaction(io_service, ncr, forward_domain,
+ reverse_domain, cfg_mgr),
+ simulate_send_exception_(false),
+ simulate_build_request_exception_(false) {
+ }
+
+ virtual ~SimpleRemoveStub() {
+ }
+
+ /// @brief Simulates sending update requests to the DNS server
+ ///
+ /// This method simulates the initiation of an asynchronous send of
+ /// a DNS update request. It overrides the actual sendUpdate method in
+ /// the base class, thus avoiding an actual send, yet still increments
+ /// the update attempt count and posts a next event of NOP_EVT.
+ ///
+ /// It will also simulate an exception-based failure of sendUpdate, if
+ /// the simulate_send_exception_ flag is true.
+ ///
+ /// @param comment Parameter is unused, but present in base class method
+ ///
+ virtual void sendUpdate(const std::string& /* comment */) {
+ if (simulate_send_exception_) {
+ // Make the flag a one-shot by resetting it.
+ simulate_send_exception_ = false;
+ // Transition to failed.
+ transition(PROCESS_TRANS_FAILED_ST, UPDATE_FAILED_EVT);
+ return;
+ }
+
+ // Update send attempt count and post a NOP_EVT.
+ setUpdateAttempts(getUpdateAttempts() + 1);
+ postNextEvent(StateModel::NOP_EVT);
+ }
+
+ /// @brief Prepares the initial D2UpdateMessage
+ ///
+ /// This method overrides the NameChangeTransaction implementation to
+ /// provide the ability to simulate an exception throw in the build
+ /// request logic.
+ /// If the one-shot flag, simulate_build_request_exception_ is true,
+ /// this method will throw an exception, otherwise it will invoke the
+ /// base class method, providing normal functionality.
+ ///
+ /// For parameter description see the NameChangeTransaction implementation.
+ virtual D2UpdateMessagePtr prepNewRequest(DdnsDomainPtr domain) {
+ if (simulate_build_request_exception_) {
+ simulate_build_request_exception_ = false;
+ isc_throw (SimpleRemoveTransactionError,
+ "Simulated build requests exception");
+ }
+
+ return (NameChangeTransaction::prepNewRequest(domain));
+ }
+
+ /// @brief Simulates receiving a response
+ ///
+ /// This method simulates the completion of a DNSClient send. This allows
+ /// the state handler logic devoted to dealing with IO completion to be
+ /// fully exercised without requiring any actual IO. The two primary
+ /// pieces of information gleaned from IO completion are the DNSClient
+ /// status which indicates whether or not the IO exchange was successful
+ /// and the rcode, which indicates the server's reaction to the request.
+ ///
+ /// This method updates the transaction's DNS status value to that of the
+ /// given parameter, and then constructs and DNS update response message
+ /// with the given rcode value. To complete the simulation it then posts
+ /// a next event of IO_COMPLETED_EVT.
+ ///
+ /// @param status simulated DNSClient status
+ /// @param rcode simulated server response code
+ void fakeResponse(const DNSClient::Status& status,
+ const dns::Rcode& rcode) {
+ // Set the DNS update status. This is normally set in
+ // DNSClient IO completion handler.
+ setDnsUpdateStatus(status);
+
+ // Construct an empty message with the given Rcode.
+ D2UpdateMessagePtr msg(new D2UpdateMessage(D2UpdateMessage::OUTBOUND));
+ msg->setRcode(rcode);
+
+ // Set the update response to the message.
+ setDnsUpdateResponse(msg);
+
+ // Post the IO completion event.
+ postNextEvent(NameChangeTransaction::IO_COMPLETED_EVT);
+ }
+
+ /// @brief Selects the first forward server.
+ /// Some state handlers require a server to have been selected.
+ /// This selects a server without going through the state
+ /// transition(s) to do so.
+ bool selectFwdServer() {
+ if (getForwardDomain()) {
+ initServerSelection(getForwardDomain());
+ selectNextServer();
+ return (getCurrentServer().get() != 0);
+ }
+
+ return (false);
+ }
+
+ /// @brief Selects the first reverse server.
+ /// Some state handlers require a server to have been selected.
+ /// This selects a server without going through the state
+ /// transition(s) to do so.
+ bool selectRevServer() {
+ if (getReverseDomain()) {
+ initServerSelection(getReverseDomain());
+ selectNextServer();
+ return (getCurrentServer().get() != 0);
+ }
+
+ return (false);
+ }
+
+ /// @brief One-shot flag which will simulate sendUpdate failure if true.
+ bool simulate_send_exception_;
+
+ /// @brief One-shot flag which will simulate an exception when sendUpdate
+ /// failure if true.
+ bool simulate_build_request_exception_;
+
+ using StateModel::postNextEvent;
+ using StateModel::setState;
+ using StateModel::initDictionaries;
+ using SimpleRemoveTransaction::defineEvents;
+ using SimpleRemoveTransaction::verifyEvents;
+ using SimpleRemoveTransaction::defineStates;
+ using SimpleRemoveTransaction::verifyStates;
+ using SimpleRemoveTransaction::readyHandler;
+ using SimpleRemoveTransaction::selectingFwdServerHandler;
+ using SimpleRemoveTransaction::getCurrentServer;
+ using SimpleRemoveTransaction::setDnsUpdateStatus;
+ using SimpleRemoveTransaction::removingFwdRRsHandler;
+ using SimpleRemoveTransaction::selectingRevServerHandler;
+ using SimpleRemoveTransaction::removingRevPtrsHandler;
+ using SimpleRemoveTransaction::processRemoveOkHandler;
+ using SimpleRemoveTransaction::processRemoveFailedHandler;
+ using SimpleRemoveTransaction::buildRemoveFwdRRsRequest;
+ using SimpleRemoveTransaction::buildRemoveRevPtrsRequest;
+};
+
+typedef boost::shared_ptr<SimpleRemoveStub> SimpleRemoveStubPtr;
+
+/// @brief Test fixture for testing SimpleRemoveTransaction
+///
+/// Note this class uses SimpleRemoveStub class to exercise non-public
+/// aspects of SimpleRemoveTransaction.
+class SimpleRemoveTransactionTest : public TransactionTest {
+public:
+ SimpleRemoveTransactionTest() {
+ }
+
+ virtual ~SimpleRemoveTransactionTest() {
+ }
+
+ /// @brief Creates a transaction which requests an IPv4 DNS update.
+ ///
+ /// The transaction is constructed around a predefined (i.e. "canned")
+ /// IPv4 NameChangeRequest. The request has both forward and reverse DNS
+ /// changes requested. Based upon the change mask, the transaction
+ /// will have either the forward, reverse, or both domains populated.
+ ///
+ /// @param change_mask determines which change directions are requested
+ SimpleRemoveStubPtr makeTransaction4(int change_mask) {
+ // Creates IPv4 remove request, forward, and reverse domains.
+ setupForIPv4Transaction(dhcp_ddns::CHG_REMOVE, change_mask);
+
+ // Now create the test transaction as would occur in update manager.
+ return (SimpleRemoveStubPtr(new SimpleRemoveStub(io_service_, ncr_,
+ forward_domain_,
+ reverse_domain_,
+ cfg_mgr_)));
+ }
+
+ /// @brief Creates a transaction which requests an IPv6 DNS update.
+ ///
+ /// The transaction is constructed around a predefined (i.e. "canned")
+ /// IPv6 NameChangeRequest. The request has both forward and reverse DNS
+ /// changes requested. Based upon the change mask, the transaction
+ /// will have either the forward, reverse, or both domains populated.
+ ///
+ /// @param change_mask determines which change directions are requested
+ SimpleRemoveStubPtr makeTransaction6(int change_mask) {
+ // Creates IPv6 remove request, forward, and reverse domains.
+ setupForIPv6Transaction(dhcp_ddns::CHG_REMOVE, change_mask);
+
+ // Now create the test transaction as would occur in update manager.
+ return (SimpleRemoveStubPtr(new SimpleRemoveStub(io_service_, ncr_,
+ forward_domain_,
+ reverse_domain_,
+ cfg_mgr_)));
+ }
+
+ /// @brief Create a test transaction at a known point in the state model.
+ ///
+ /// Method prepares a new test transaction and sets its state and next
+ /// event values to those given. This makes the transaction appear to
+ /// be at that point in the state model without having to transition it
+ /// through prerequisite states. It also provides the ability to set
+ /// which change directions are requested: forward change only, reverse
+ /// change only, or both.
+ ///
+ /// @param state value to set as the current state
+ /// @param event value to post as the next event
+ /// @param change_mask determines which change directions are requested
+ /// @param family selects between an IPv4 (AF_INET) and IPv6 (AF_INET6)
+ /// transaction.
+ SimpleRemoveStubPtr prepHandlerTest(unsigned int state, unsigned int event,
+ unsigned int change_mask = FWD_AND_REV_CHG,
+ short family = AF_INET) {
+ SimpleRemoveStubPtr name_remove = (family == AF_INET ?
+ makeTransaction4(change_mask) :
+ makeTransaction6(change_mask));
+ name_remove->initDictionaries();
+ name_remove->postNextEvent(event);
+ name_remove->setState(state);
+ return (name_remove);
+ }
+
+};
+
+/// @brief Tests SimpleRemoveTransaction construction.
+/// This test verifies that:
+/// 1. Construction with invalid type of request
+/// 2. Valid construction functions properly
+TEST(SimpleRemoveTransaction, construction) {
+ asiolink::IOServicePtr io_service(new isc::asiolink::IOService());
+ D2CfgMgrPtr cfg_mgr(new D2CfgMgr());
+
+ const char* msg_str =
+ "{"
+ " \"change-type\" : 0 , "
+ " \"forward-change\" : true , "
+ " \"reverse-change\" : true , "
+ " \"fqdn\" : \"example.com.\" , "
+ " \"ip-address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"0102030405060708\" , "
+ " \"lease-expires-on\" : \"20130121132405\" , "
+ " \"lease-length\" : 1300, "
+ " \"use-conflict-resolution\" : true "
+ "}";
+
+ dhcp_ddns::NameChangeRequestPtr ncr;
+ DnsServerInfoStoragePtr servers;
+ DdnsDomainPtr forward_domain;
+ DdnsDomainPtr reverse_domain;
+ DdnsDomainPtr empty_domain;
+
+ ASSERT_NO_THROW(ncr = dhcp_ddns::NameChangeRequest::fromJSON(msg_str));
+ ASSERT_NO_THROW(forward_domain.reset(new DdnsDomain("*", servers)));
+ ASSERT_NO_THROW(reverse_domain.reset(new DdnsDomain("*", servers)));
+
+ // Verify that construction with wrong change type fails.
+ EXPECT_THROW(SimpleRemoveTransaction(io_service, ncr,
+ forward_domain, reverse_domain, cfg_mgr),
+ SimpleRemoveTransactionError);
+
+ // Verify that a valid construction attempt works.
+ ncr->setChangeType(isc::dhcp_ddns::CHG_REMOVE);
+ EXPECT_NO_THROW(SimpleRemoveTransaction(io_service, ncr,
+ forward_domain, reverse_domain,
+ cfg_mgr));
+}
+
+/// @brief Tests event and state dictionary construction and verification.
+TEST_F(SimpleRemoveTransactionTest, dictionaryCheck) {
+ SimpleRemoveStubPtr name_remove;
+ ASSERT_NO_THROW(name_remove = makeTransaction4(FWD_AND_REV_CHG));
+ // Verify that the event and state dictionary validation fails prior
+ // dictionary construction.
+ ASSERT_THROW(name_remove->verifyEvents(), StateModelError);
+ ASSERT_THROW(name_remove->verifyStates(), StateModelError);
+
+ // Construct both dictionaries.
+ ASSERT_NO_THROW(name_remove->defineEvents());
+ ASSERT_NO_THROW(name_remove->defineStates());
+
+ // Verify both event and state dictionaries now pass validation.
+ ASSERT_NO_THROW(name_remove->verifyEvents());
+ ASSERT_NO_THROW(name_remove->verifyStates());
+}
+
+/// @brief Tests construction of a DNS update request for removing forward
+/// dns RR entries.
+TEST_F(SimpleRemoveTransactionTest, buildRemoveFwdRRsRequest) {
+ // Create a IPv4 forward replace transaction.
+ // Verify the request builds without error.
+ // and then verify the request contents.
+ SimpleRemoveStubPtr name_remove;
+ ASSERT_NO_THROW(name_remove = makeTransaction4(FORWARD_CHG));
+ ASSERT_NO_THROW(name_remove->buildRemoveFwdRRsRequest());
+ checkSimpleRemoveFwdRRsRequest(*name_remove);
+
+ // Create a IPv6 forward replace transaction.
+ // Verify the request builds without error.
+ // and then verify the request contents.
+ ASSERT_NO_THROW(name_remove = makeTransaction6(FORWARD_CHG));
+ ASSERT_NO_THROW(name_remove->buildRemoveFwdRRsRequest());
+ checkSimpleRemoveFwdRRsRequest(*name_remove);
+}
+
+/// @brief Tests the construction of a DNS update request for removing a
+/// reverse dns entry.
+TEST_F(SimpleRemoveTransactionTest, buildRemoveRevPtrsRequest) {
+ // Create a IPv4 reverse replace transaction.
+ // Verify the request builds without error.
+ // and then verify the request contents.
+ SimpleRemoveStubPtr name_remove;
+ ASSERT_NO_THROW(name_remove = makeTransaction4(REVERSE_CHG));
+ ASSERT_NO_THROW(name_remove->buildRemoveRevPtrsRequest());
+ checkSimpleRemoveRevPtrsRequest(*name_remove);
+
+ // Create a IPv6 reverse replace transaction.
+ // Verify the request builds without error.
+ // and then verify the request contents.
+ ASSERT_NO_THROW(name_remove = makeTransaction6(REVERSE_CHG));
+ ASSERT_NO_THROW(name_remove->buildRemoveRevPtrsRequest());
+ checkSimpleRemoveRevPtrsRequest(*name_remove);
+}
+
+// Tests the readyHandler functionality.
+// It verifies behavior for the following scenarios:
+//
+// 1. Posted event is START_EVT and request includes only a forward change
+// 2. Posted event is START_EVT and request includes both a forward and a
+// reverse change
+// 3. Posted event is START_EVT and request includes only a reverse change
+// 4. Posted event is invalid
+//
+TEST_F(SimpleRemoveTransactionTest, readyHandler) {
+ SimpleRemoveStubPtr name_remove;
+
+ // Create a transaction which includes only a forward change.
+ ASSERT_NO_THROW(
+ name_remove = prepHandlerTest(NameChangeTransaction::READY_ST,
+ StateModel::START_EVT, FORWARD_CHG)
+ );
+
+ // Run readyHandler.
+ EXPECT_NO_THROW(name_remove->readyHandler());
+
+ // Verify that a request requiring only a forward change, transitions to
+ // selecting a forward server.
+ CHECK_CONTEXT(name_remove, NameChangeTransaction::SELECTING_FWD_SERVER_ST,
+ NameChangeTransaction::SELECT_SERVER_EVT);
+
+ // Create a transaction which includes both a forward and a reverse change.
+ ASSERT_NO_THROW(
+ name_remove = prepHandlerTest(NameChangeTransaction::READY_ST,
+ StateModel::START_EVT, FWD_AND_REV_CHG)
+ );
+
+ // Run readyHandler.
+ EXPECT_NO_THROW(name_remove->readyHandler());
+
+ // Verify that a request requiring both forward and reverse, starts with
+ // the forward change by transitioning to selecting a forward server.
+ CHECK_CONTEXT(name_remove, NameChangeTransaction::SELECTING_FWD_SERVER_ST,
+ NameChangeTransaction::SELECT_SERVER_EVT);
+
+ // Create and prep a reverse only transaction.
+ ASSERT_NO_THROW(
+ name_remove = prepHandlerTest(NameChangeTransaction::READY_ST,
+ StateModel::START_EVT, REVERSE_CHG)
+ );
+
+ // Run readyHandler.
+ EXPECT_NO_THROW(name_remove->readyHandler());
+
+ // Verify that a request requiring only a reverse change, transitions to
+ // selecting a reverse server.
+ CHECK_CONTEXT(name_remove, NameChangeTransaction::SELECTING_REV_SERVER_ST,
+ NameChangeTransaction::SELECT_SERVER_EVT);
+
+ // Create and prep transaction, poised to run the handler but with an
+ // invalid event.
+ ASSERT_NO_THROW(
+ name_remove = prepHandlerTest(NameChangeTransaction::READY_ST,
+ StateModel::NOP_EVT)
+ );
+
+ // Running the readyHandler should throw.
+ EXPECT_THROW(name_remove->readyHandler(), SimpleRemoveTransactionError);
+}
+
+
+// Tests the selectingFwdServerHandler functionality.
+// It verifies behavior for the following scenarios:
+//
+// 1. Posted event is SELECT_SERVER_EVT
+// 2. Posted event is SERVER_IO_ERROR_EVT
+// 3. Posted event is invalid
+//
+TEST_F(SimpleRemoveTransactionTest, selectingFwdServerHandler) {
+ SimpleRemoveStubPtr name_remove;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(
+ name_remove = prepHandlerTest(NameChangeTransaction::SELECTING_FWD_SERVER_ST,
+ NameChangeTransaction::SELECT_SERVER_EVT)
+ );
+
+ // Call selectingFwdServerHandler enough times to select all of the
+ // servers in it's current domain. The first time, it will be with
+ // next event of SELECT_SERVER_EVT. Thereafter it will be with a next
+ // event of SERVER_IO_ERROR_EVT.
+ int num_servers = name_remove->getForwardDomain()->getServers()->size();
+ for (int i = 0; i < num_servers; ++i) {
+ SCOPED_TRACE(testing::Message() << " num_servers: " << num_servers
+ << " selections:" << i);
+ // Run selectingFwdServerHandler.
+ ASSERT_NO_THROW(name_remove->selectingFwdServerHandler());
+
+ // Verify that a server was selected.
+ ASSERT_TRUE(name_remove->getCurrentServer());
+
+ // Verify that we transitioned correctly.
+ CHECK_CONTEXT(name_remove, SimpleRemoveTransaction::REMOVING_FWD_RRS_ST,
+ NameChangeTransaction::SERVER_SELECTED_EVT);
+
+ // Post a server IO error event. This simulates an IO error occurring
+ // and a need to select the new server.
+ ASSERT_NO_THROW(name_remove->postNextEvent(NameChangeTransaction::
+ SERVER_IO_ERROR_EVT));
+ }
+
+ // We should have exhausted the list of servers. Processing another
+ // SERVER_IO_ERROR_EVT should transition us to failure.
+ EXPECT_NO_THROW(name_remove->selectingFwdServerHandler());
+ CHECK_CONTEXT(name_remove, NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ NameChangeTransaction::NO_MORE_SERVERS_EVT);
+
+ // Create and prep transaction, poised to run the handler but with an
+ // invalid event.
+ ASSERT_NO_THROW(
+ name_remove = prepHandlerTest(NameChangeTransaction::SELECTING_FWD_SERVER_ST,
+ StateModel::NOP_EVT)
+ );
+
+ // Running the handler should throw.
+ EXPECT_THROW(name_remove->selectingFwdServerHandler(),
+ SimpleRemoveTransactionError);
+}
+
+// ************************ removingFwdRRsHandler Tests *****************
+
+// Tests that removingFwdRRsHandler rejects invalid events.
+TEST_F(SimpleRemoveTransactionTest, removingFwdRRsHandler_InvalidEvent) {
+ SimpleRemoveStubPtr name_remove;
+ // Create and prep a transaction, poised to run the handler but with
+ // an invalid event.
+ ASSERT_NO_THROW(
+ name_remove = prepHandlerTest(SimpleRemoveTransaction::REMOVING_FWD_RRS_ST,
+ StateModel::NOP_EVT)
+ );
+
+ // Running the handler should throw.
+ EXPECT_THROW(name_remove->removingFwdRRsHandler(),
+ SimpleRemoveTransactionError);
+}
+
+// Tests removingFwdRRsHandler with the following scenario:
+//
+// The request includes only a forward change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The update request is sent without error.
+// A server response is received which indicates successful update.
+//
+TEST_F(SimpleRemoveTransactionTest, removingFwdRRsHandler_FwdOnlyOK) {
+ SimpleRemoveStubPtr name_remove;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(
+ name_remove = prepHandlerTest(SimpleRemoveTransaction::REMOVING_FWD_RRS_ST,
+ NameChangeTransaction::SERVER_SELECTED_EVT, FORWARD_CHG)
+ );
+
+ // Should not be an update message yet.
+ D2UpdateMessagePtr update_msg = name_remove->getDnsUpdateRequest();
+ ASSERT_FALSE(update_msg);
+
+ // At this point completion flags should be false.
+ EXPECT_FALSE(name_remove->getForwardChangeCompleted());
+ EXPECT_FALSE(name_remove->getReverseChangeCompleted());
+
+ // Run removingFwdRRsHandler to construct and send the request.
+ EXPECT_NO_THROW(name_remove->removingFwdRRsHandler());
+
+ // Verify that an update message was constructed properly.
+ checkSimpleRemoveFwdRRsRequest(*name_remove);
+
+ // Verify that we are still in this state and next event is NOP_EVT.
+ // This indicates we "sent" the message and are waiting for IO completion.
+ CHECK_CONTEXT(name_remove, SimpleRemoveTransaction::REMOVING_FWD_RRS_ST,
+ NameChangeTransaction::NOP_EVT);
+
+ // Simulate receiving a successful update response.
+ name_remove->fakeResponse(DNSClient::SUCCESS, dns::Rcode::NOERROR());
+
+ // Run removingFwdRRsHandler again to process the response.
+ EXPECT_NO_THROW(name_remove->removingFwdRRsHandler());
+
+ // Forward completion should be true, reverse should be false.
+ EXPECT_TRUE(name_remove->getForwardChangeCompleted());
+ EXPECT_FALSE(name_remove->getReverseChangeCompleted());
+
+ // Since it is a forward only change, we should be done.
+ // Verify that we transitioned correctly.
+ CHECK_CONTEXT(name_remove, NameChangeTransaction::PROCESS_TRANS_OK_ST,
+ NameChangeTransaction::UPDATE_OK_EVT);
+}
+
+// Tests removingFwdRRsHandler with the following scenario:
+//
+// The request includes a forward and reverse change.
+// The update request is sent without error.
+// A server response is received which indicates the update was rejected.
+//
+TEST_F(SimpleRemoveTransactionTest, removingFwdRRsHandler_OtherRcode) {
+ SimpleRemoveStubPtr name_remove;
+ // Create the transaction.
+ ASSERT_NO_THROW(
+ name_remove = prepHandlerTest(SimpleRemoveTransaction::REMOVING_FWD_RRS_ST,
+ NameChangeTransaction::SERVER_SELECTED_EVT,
+ FWD_AND_REV_CHG)
+ );
+
+ // Select a server to satisfy log statements.
+ ASSERT_TRUE(name_remove->selectFwdServer());
+
+ // Run removingFwdRRsHandler to construct and send the request.
+ EXPECT_NO_THROW(name_remove->removingFwdRRsHandler());
+
+ // Simulate receiving server rejection response. Per RFC, anything other
+ // than no error is failure (we are also treating FQDN not in use is
+ // success). Arbitrarily choosing refused.
+ name_remove->fakeResponse(DNSClient::SUCCESS, dns::Rcode::REFUSED());
+
+ // Run removingFwdRRsHandler again to process the response.
+ EXPECT_NO_THROW(name_remove->removingFwdRRsHandler());
+
+ // Completion flags should still be false.
+ EXPECT_FALSE(name_remove->getForwardChangeCompleted());
+ EXPECT_FALSE(name_remove->getReverseChangeCompleted());
+
+ // We should have failed the transaction. Verify that we transitioned
+ // correctly.
+ CHECK_CONTEXT(name_remove, NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ NameChangeTransaction::UPDATE_FAILED_EVT);
+}
+
+// Tests removingFwdRRsHandler with the following scenario:
+//
+// The request includes a forward and reverse change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The update request send times out MAX_UPDATE_TRIES_PER_SERVER times.
+//
+TEST_F(SimpleRemoveTransactionTest, removingFwdRRsHandler_Timeout) {
+ SimpleRemoveStubPtr name_remove;
+
+ // Create the transaction.
+ ASSERT_NO_THROW(
+ name_remove = prepHandlerTest(SimpleRemoveTransaction::REMOVING_FWD_RRS_ST,
+ NameChangeTransaction::SERVER_SELECTED_EVT,
+ FWD_AND_REV_CHG)
+ );
+
+ // Select a server to satisfy log statements.
+ ASSERT_TRUE(name_remove->selectFwdServer());
+
+ // Verify that we can make maximum number of update attempts permitted
+ // and then transition to selecting a new server.
+ int max_tries = NameChangeTransaction::MAX_UPDATE_TRIES_PER_SERVER;
+ for (int i = 1; i <= max_tries; ++i) {
+ // Run removingFwdRRsHandler to send the request.
+ EXPECT_NO_THROW(name_remove->removingFwdRRsHandler());
+
+ // Simulate a server IO timeout.
+ name_remove->setDnsUpdateStatus(DNSClient::TIMEOUT);
+ name_remove->postNextEvent(NameChangeTransaction::IO_COMPLETED_EVT);
+
+ // Run removingFwdRRsHandler again to process the response.
+ EXPECT_NO_THROW(name_remove->removingFwdRRsHandler());
+
+ // Completion flags should be false.
+ EXPECT_FALSE(name_remove->getForwardChangeCompleted());
+ EXPECT_FALSE(name_remove->getReverseChangeCompleted());
+
+ if (i < max_tries) {
+ // We should be ready to try again.
+ CHECK_CONTEXT(name_remove, SimpleRemoveTransaction::REMOVING_FWD_RRS_ST,
+ NameChangeTransaction::SERVER_SELECTED_EVT);
+ } else {
+ // Server retries should be exhausted, time for a new server.
+ CHECK_CONTEXT(name_remove, SimpleRemoveTransaction::SELECTING_FWD_SERVER_ST,
+ NameChangeTransaction::SERVER_IO_ERROR_EVT);
+ }
+ }
+}
+
+// Tests removingFwdRRsHandler with the following scenario:
+//
+// The request includes a forward and reverse change.
+// Initial posted event is UPDATE_OK_EVT.
+// The update request is sent but a corrupt response is received, this occurs
+// MAX_UPDATE_TRIES_PER_SERVER times.
+//
+TEST_F(SimpleRemoveTransactionTest, removingFwdRRsHandler_InvalidResponse) {
+ SimpleRemoveStubPtr name_remove;
+
+ // Create the transaction.
+ ASSERT_NO_THROW(
+ name_remove = prepHandlerTest(SimpleRemoveTransaction::REMOVING_FWD_RRS_ST,
+ NameChangeTransaction::UPDATE_OK_EVT, FWD_AND_REV_CHG)
+ );
+
+ // Select a server to satisfy log statements.
+ ASSERT_TRUE(name_remove->selectFwdServer());
+
+ // Verify that we can make maximum number of update attempts permitted
+ // and then transition to selecting a new server.
+ int max_tries = NameChangeTransaction::MAX_UPDATE_TRIES_PER_SERVER;
+ for (int i = 1; i <= max_tries; ++i) {
+ // Run removingFwdRRsHandler to send the request.
+ EXPECT_NO_THROW(name_remove->removingFwdRRsHandler());
+
+ // Simulate a corrupt server response.
+ name_remove->setDnsUpdateStatus(DNSClient::INVALID_RESPONSE);
+ name_remove->postNextEvent(NameChangeTransaction::IO_COMPLETED_EVT);
+
+ // Run removingFwdRRsHandler again to process the response.
+ EXPECT_NO_THROW(name_remove->removingFwdRRsHandler());
+
+ // Completion flags should be false.
+ EXPECT_FALSE(name_remove->getForwardChangeCompleted());
+ EXPECT_FALSE(name_remove->getReverseChangeCompleted());
+
+ if (i < max_tries) {
+ // We should be ready to try again.
+ CHECK_CONTEXT(name_remove, SimpleRemoveTransaction::REMOVING_FWD_RRS_ST,
+ NameChangeTransaction::SERVER_SELECTED_EVT);
+ } else {
+ // Server retries should be exhausted, time for a new server.
+ CHECK_CONTEXT(name_remove, SimpleRemoveTransaction::SELECTING_FWD_SERVER_ST,
+ NameChangeTransaction::SERVER_IO_ERROR_EVT);
+ }
+ }
+}
+
+
+// Tests the selectingRevServerHandler functionality.
+// It verifies behavior for the following scenarios:
+//
+// 1. Posted event is SELECT_SERVER_EVT
+// 2. Posted event is SERVER_IO_ERROR_EVT
+// 3. Posted event is invalid
+//
+TEST_F(SimpleRemoveTransactionTest, selectingRevServerHandler) {
+ SimpleRemoveStubPtr name_remove;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(
+ name_remove = prepHandlerTest(NameChangeTransaction::SELECTING_REV_SERVER_ST,
+ NameChangeTransaction::SELECT_SERVER_EVT)
+ );
+
+ // Call selectingRevServerHandler enough times to select all of the
+ // servers in it's current domain. The first time, it will be with
+ // next event of SELECT_SERVER_EVT. Thereafter it will be with a next
+ // event of SERVER_IO_ERROR_EVT.
+ int num_servers = name_remove->getReverseDomain()->getServers()->size();
+ for (int i = 0; i < num_servers; ++i) {
+ SCOPED_TRACE(testing::Message() << " num_servers: " << num_servers
+ << " selections:" << i);
+ // Run selectingRevServerHandler.
+ ASSERT_NO_THROW(name_remove->selectingRevServerHandler());
+
+ // Verify that a server was selected.
+ ASSERT_TRUE(name_remove->getCurrentServer());
+
+ // Verify that we transitioned correctly.
+ CHECK_CONTEXT(name_remove, SimpleRemoveTransaction::REMOVING_REV_PTRS_ST,
+ NameChangeTransaction::SERVER_SELECTED_EVT);
+
+ // Post a server IO error event. This simulates an IO error occurring
+ // and a need to select the new server.
+ ASSERT_NO_THROW(name_remove->postNextEvent(NameChangeTransaction::
+ SERVER_IO_ERROR_EVT));
+ }
+
+ // We should have exhausted the list of servers. Processing another
+ // SERVER_IO_ERROR_EVT should transition us to failure.
+ EXPECT_NO_THROW(name_remove->selectingRevServerHandler());
+ CHECK_CONTEXT(name_remove, NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ NameChangeTransaction::NO_MORE_SERVERS_EVT);
+
+ // Create and prep transaction, poised to run the handler but with an
+ // invalid event.
+ ASSERT_NO_THROW(
+ name_remove = prepHandlerTest(NameChangeTransaction::SELECTING_REV_SERVER_ST,
+ StateModel::NOP_EVT)
+ );
+
+ // Running the handler should throw.
+ EXPECT_THROW(name_remove->selectingRevServerHandler(),
+ SimpleRemoveTransactionError);
+}
+
+//************************** removingRevPtrsHandler tests *****************
+
+// Tests that removingRevPtrsHandler rejects invalid events.
+TEST_F(SimpleRemoveTransactionTest, removingRevPtrsHandler_InvalidEvent) {
+ SimpleRemoveStubPtr name_remove;
+ // Create and prep a transaction, poised to run the handler but with
+ // an invalid event.
+ ASSERT_NO_THROW(
+ name_remove = prepHandlerTest(SimpleRemoveTransaction::REMOVING_REV_PTRS_ST,
+ StateModel::NOP_EVT)
+ );
+
+ // Running the handler should throw.
+ EXPECT_THROW(name_remove->removingRevPtrsHandler(),
+ SimpleRemoveTransactionError);
+}
+
+// Tests removingRevPtrsHandler with the following scenario:
+//
+// The request includes only a reverse change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The update request is sent without error.
+// A server response is received which indicates successful update.
+//
+TEST_F(SimpleRemoveTransactionTest, removingRevPtrsHandler_RevOnlyOK) {
+ SimpleRemoveStubPtr name_remove;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(
+ name_remove = prepHandlerTest(SimpleRemoveTransaction::REMOVING_REV_PTRS_ST,
+ NameChangeTransaction::SERVER_SELECTED_EVT, REVERSE_CHG)
+ );
+
+ // Should not be an update message yet.
+ D2UpdateMessagePtr update_msg = name_remove->getDnsUpdateRequest();
+ ASSERT_FALSE(update_msg);
+
+ // At this point completion flags should be false.
+ EXPECT_FALSE(name_remove->getForwardChangeCompleted());
+ EXPECT_FALSE(name_remove->getReverseChangeCompleted());
+
+ // Run removingRevPtrsHandler to construct and send the request.
+ EXPECT_NO_THROW(name_remove->removingRevPtrsHandler());
+
+ // Verify that an update message was constructed properly.
+ checkSimpleRemoveRevPtrsRequest(*name_remove);
+
+ // Verify that we are still in this state and next event is NOP_EVT.
+ // This indicates we "sent" the message and are waiting for IO completion.
+ CHECK_CONTEXT(name_remove, SimpleRemoveTransaction::REMOVING_REV_PTRS_ST,
+ StateModel::NOP_EVT);
+
+ // Simulate receiving a successful update response.
+ name_remove->fakeResponse(DNSClient::SUCCESS, dns::Rcode::NOERROR());
+
+ // Run removingRevPtrsHandler again to process the response.
+ EXPECT_NO_THROW(name_remove->removingRevPtrsHandler());
+
+ // Forward completion should be false, reverse should be true.
+ EXPECT_FALSE(name_remove->getForwardChangeCompleted());
+ EXPECT_TRUE(name_remove->getReverseChangeCompleted());
+
+ // Since it is a reverse change, we should be done.
+ // Verify that we transitioned correctly.
+ CHECK_CONTEXT(name_remove, NameChangeTransaction::PROCESS_TRANS_OK_ST,
+ NameChangeTransaction::UPDATE_OK_EVT);
+}
+
+// Tests removingRevPtrsHandler with the following scenario:
+//
+// The request includes only a reverse change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The update request is sent without error.
+// A server response is received which indicates FQDN is NOT in use.
+//
+TEST_F(SimpleRemoveTransactionTest, removingRevPtrsHandler_FqdnNotInUse) {
+ SimpleRemoveStubPtr name_remove;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(
+ name_remove = prepHandlerTest(SimpleRemoveTransaction::REMOVING_REV_PTRS_ST,
+ NameChangeTransaction::SERVER_SELECTED_EVT, REVERSE_CHG)
+ );
+
+ // Should not be an update message yet.
+ D2UpdateMessagePtr update_msg = name_remove->getDnsUpdateRequest();
+ ASSERT_FALSE(update_msg);
+
+ // At this point completion flags should be false.
+ EXPECT_FALSE(name_remove->getForwardChangeCompleted());
+ EXPECT_FALSE(name_remove->getReverseChangeCompleted());
+
+ // Run removingRevPtrsHandler to construct and send the request.
+ EXPECT_NO_THROW(name_remove->removingRevPtrsHandler());
+
+ // Verify that an update message was constructed properly.
+ checkSimpleRemoveRevPtrsRequest(*name_remove);
+
+ // Verify that we are still in this state and next event is NOP_EVT.
+ // This indicates we "sent" the message and are waiting for IO completion.
+ CHECK_CONTEXT(name_remove, SimpleRemoveTransaction::REMOVING_REV_PTRS_ST,
+ StateModel::NOP_EVT);
+
+ // Simulate receiving a RRSET does not exist.
+ name_remove->fakeResponse(DNSClient::SUCCESS, dns::Rcode::NXRRSET());
+
+ // Run removingRevPtrsHandler again to process the response.
+ EXPECT_NO_THROW(name_remove->removingRevPtrsHandler());
+
+ // Forward completion should be false, reverse should be true.
+ EXPECT_FALSE(name_remove->getForwardChangeCompleted());
+ EXPECT_TRUE(name_remove->getReverseChangeCompleted());
+
+ // Since it is a reverse change, we should be done.
+ // Verify that we transitioned correctly.
+ CHECK_CONTEXT(name_remove, NameChangeTransaction::PROCESS_TRANS_OK_ST,
+ NameChangeTransaction::UPDATE_OK_EVT);
+}
+
+// Tests removingRevPtrsHandler with the following scenario:
+//
+// The request includes only a reverse change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The update request is sent without error.
+// A server response is received which indicates the update was rejected.
+//
+TEST_F(SimpleRemoveTransactionTest, removingRevPtrsHandler_OtherRcode) {
+ SimpleRemoveStubPtr name_remove;
+ // Create the transaction.
+ ASSERT_NO_THROW(
+ name_remove = prepHandlerTest(SimpleRemoveTransaction::REMOVING_REV_PTRS_ST,
+ NameChangeTransaction::SERVER_SELECTED_EVT, REVERSE_CHG)
+ );
+
+ // Select a server to satisfy log statements.
+ ASSERT_TRUE(name_remove->selectRevServer());
+
+ // Run removingRevPtrsHandler to construct and send the request.
+ EXPECT_NO_THROW(name_remove->removingRevPtrsHandler());
+
+ // Simulate receiving server rejection response. Per RFC, anything other
+ // than no error is failure. Arbitrarily choosing refused.
+ name_remove->fakeResponse(DNSClient::SUCCESS, dns::Rcode::REFUSED());
+
+ // Run removingRevPtrsHandler again to process the response.
+ EXPECT_NO_THROW(name_remove->removingRevPtrsHandler());
+
+ // Completion flags should still be false.
+ EXPECT_FALSE(name_remove->getForwardChangeCompleted());
+ EXPECT_FALSE(name_remove->getReverseChangeCompleted());
+
+ // We should have failed the transaction. Verify that we transitioned
+ // correctly.
+ CHECK_CONTEXT(name_remove, NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ NameChangeTransaction::UPDATE_FAILED_EVT);
+}
+
+// Tests removingRevPtrsHandler with the following scenario:
+//
+// The request includes only a reverse change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The update request send times out MAX_UPDATE_TRIES_PER_SERVER times.
+//
+TEST_F(SimpleRemoveTransactionTest, removingRevPtrsHandler_Timeout) {
+ SimpleRemoveStubPtr name_remove;
+ // Create the transaction.
+ ASSERT_NO_THROW(
+ name_remove = prepHandlerTest(SimpleRemoveTransaction::REMOVING_REV_PTRS_ST,
+ NameChangeTransaction::SERVER_SELECTED_EVT,
+ REVERSE_CHG)
+ );
+
+ // Select a server to satisfy log statements.
+ ASSERT_TRUE(name_remove->selectRevServer());
+
+ // Verify that we can make maximum number of update attempts permitted
+ // and then transition to selecting a new server.
+ int max_tries = NameChangeTransaction::MAX_UPDATE_TRIES_PER_SERVER;
+ for (int i = 1; i <= max_tries; ++i) {
+ // Run removingRevPtrsHandler to send the request.
+ EXPECT_NO_THROW(name_remove->removingRevPtrsHandler());
+
+ // Simulate a server IO timeout.
+ name_remove->setDnsUpdateStatus(DNSClient::TIMEOUT);
+ name_remove->postNextEvent(NameChangeTransaction::IO_COMPLETED_EVT);
+
+ // Run removingRevPtrsHandler again to process the response.
+ EXPECT_NO_THROW(name_remove->removingRevPtrsHandler());
+
+ // Completion flags should be false.
+ EXPECT_FALSE(name_remove->getForwardChangeCompleted());
+ EXPECT_FALSE(name_remove->getReverseChangeCompleted());
+
+ if (i < max_tries) {
+ // We should be ready to try again.
+ CHECK_CONTEXT(name_remove, SimpleRemoveTransaction::REMOVING_REV_PTRS_ST,
+ NameChangeTransaction::SERVER_SELECTED_EVT);
+ } else {
+ // Server retries should be exhausted, time for a new server.
+ CHECK_CONTEXT(name_remove, NameChangeTransaction::SELECTING_REV_SERVER_ST,
+ NameChangeTransaction::SERVER_IO_ERROR_EVT);
+ }
+ }
+}
+
+
+// Tests removingRevPtrsHandler with the following scenario:
+//
+// The request includes only a reverse change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The update request is sent but a corrupt response is received, this occurs
+// MAX_UPDATE_TRIES_PER_SERVER times.
+//
+TEST_F(SimpleRemoveTransactionTest, removingRevPtrsHandler_CorruptResponse) {
+ SimpleRemoveStubPtr name_remove;
+ // Create the transaction.
+ ASSERT_NO_THROW(
+ name_remove = prepHandlerTest(SimpleRemoveTransaction::REMOVING_REV_PTRS_ST,
+ NameChangeTransaction::SERVER_SELECTED_EVT, REVERSE_CHG)
+ );
+
+ // Select a server to satisfy log statements.
+ ASSERT_TRUE(name_remove->selectRevServer());
+
+ // Verify that we can make maximum number of update attempts permitted
+ // and then transition to selecting a new server.
+ int max_tries = NameChangeTransaction::MAX_UPDATE_TRIES_PER_SERVER;
+ for (int i = 1; i <= max_tries; ++i) {
+ // Run removingRevPtrsHandler to send the request.
+ EXPECT_NO_THROW(name_remove->removingRevPtrsHandler());
+
+ // Simulate a server corrupt response.
+ name_remove->setDnsUpdateStatus(DNSClient::INVALID_RESPONSE);
+ name_remove->postNextEvent(NameChangeTransaction::IO_COMPLETED_EVT);
+
+ // Run removingRevPtrsHandler again to process the response.
+ EXPECT_NO_THROW(name_remove->removingRevPtrsHandler());
+
+ // Completion flags should be false.
+ EXPECT_FALSE(name_remove->getForwardChangeCompleted());
+ EXPECT_FALSE(name_remove->getReverseChangeCompleted());
+
+ if (i < max_tries) {
+ // We should be ready to try again.
+ CHECK_CONTEXT(name_remove, SimpleRemoveTransaction::REMOVING_REV_PTRS_ST,
+ NameChangeTransaction::SERVER_SELECTED_EVT);
+ } else {
+ // Server retries should be exhausted, time for a new server.
+ CHECK_CONTEXT(name_remove, NameChangeTransaction::SELECTING_REV_SERVER_ST,
+ NameChangeTransaction::SERVER_IO_ERROR_EVT);
+ }
+ }
+}
+
+// Tests the processRemoveOkHandler functionality.
+// It verifies behavior for the following scenarios:
+//
+// 1. Posted event is UPDATE_OK_EVT
+// 2. Posted event is invalid
+//
+TEST_F(SimpleRemoveTransactionTest, processRemoveOkHandler) {
+ SimpleRemoveStubPtr name_remove;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(
+ name_remove = prepHandlerTest(NameChangeTransaction::PROCESS_TRANS_OK_ST,
+ NameChangeTransaction::UPDATE_OK_EVT)
+ );
+
+ // Run processRemoveOkHandler.
+ EXPECT_NO_THROW(name_remove->processRemoveOkHandler());
+
+ // Verify that a server was selected.
+ EXPECT_EQ(dhcp_ddns::ST_COMPLETED, name_remove->getNcrStatus());
+
+ // Verify that the model has ended.
+ CHECK_CONTEXT(name_remove, StateModel::END_ST, StateModel::END_EVT);
+
+ // Create and prep transaction, poised to run the handler but with an
+ // invalid event.
+ ASSERT_NO_THROW(
+ name_remove = prepHandlerTest(NameChangeTransaction::PROCESS_TRANS_OK_ST,
+ StateModel::NOP_EVT)
+ );
+
+ // Running the handler should throw.
+ EXPECT_THROW(name_remove->processRemoveOkHandler(),
+ SimpleRemoveTransactionError);
+}
+
+// Tests the processRemoveFailedHandler functionality.
+// It verifies behavior for the following scenarios:
+//
+// 1. Posted event is UPDATE_FAILED_EVT
+// 2. Posted event is invalid
+//
+TEST_F(SimpleRemoveTransactionTest, processRemoveFailedHandler) {
+ SimpleRemoveStubPtr name_remove;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(
+ name_remove = prepHandlerTest(NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ NameChangeTransaction::UPDATE_FAILED_EVT)
+ );
+
+ // Run processRemoveFailedHandler.
+ EXPECT_NO_THROW(name_remove->processRemoveFailedHandler());
+
+ // Verify that a server was selected.
+ EXPECT_EQ(dhcp_ddns::ST_FAILED, name_remove->getNcrStatus());
+
+ // Verify that the model has ended. (Remember, the transaction failed NOT
+ // the model. The model should have ended normally.)
+ CHECK_CONTEXT(name_remove, StateModel::END_ST, StateModel::END_EVT);
+
+ // Create and prep transaction, poised to run the handler but with an
+ // invalid event.
+ ASSERT_NO_THROW(
+ name_remove = prepHandlerTest(NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ StateModel::NOP_EVT)
+ );
+
+ // Running the handler should throw.
+ EXPECT_THROW(name_remove->processRemoveFailedHandler(),
+ SimpleRemoveTransactionError);
+}
+
+// Tests the processRemoveFailedHandler functionality.
+// It verifies behavior for posted event of NO_MORE_SERVERS_EVT.
+TEST_F(SimpleRemoveTransactionTest, processRemoveFailedHandler_NoMoreServers) {
+ SimpleRemoveStubPtr name_remove;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(
+ name_remove = prepHandlerTest(NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ NameChangeTransaction::NO_MORE_SERVERS_EVT)
+ );
+
+ // Run processRemoveFailedHandler.
+ EXPECT_NO_THROW(name_remove->processRemoveFailedHandler());
+
+ // Verify that a server was selected.
+ EXPECT_EQ(dhcp_ddns::ST_FAILED, name_remove->getNcrStatus());
+
+ // Verify that the model has ended. (Remember, the transaction failed NOT
+ // the model. The model should have ended normally.)
+ CHECK_CONTEXT(name_remove, StateModel::END_ST, StateModel::END_EVT);
+}
+
+// Tests the processRemoveFailedHandler functionality.
+// It verifies behavior for posted event of SERVER_IO_ERROR_EVT.
+TEST_F(SimpleRemoveTransactionTest, processRemoveFailedHandler_ServerIOError) {
+ SimpleRemoveStubPtr name_remove;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(
+ name_remove = prepHandlerTest(NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ NameChangeTransaction::SERVER_IO_ERROR_EVT)
+ );
+
+ // Run processRemoveFailedHandler.
+ EXPECT_NO_THROW(name_remove->processRemoveFailedHandler());
+
+ // Verify that a server was selected.
+ EXPECT_EQ(dhcp_ddns::ST_FAILED, name_remove->getNcrStatus());
+
+ // Verify that the model has ended. (Remember, the transaction failed NOT
+ // the model. The model should have ended normally.)
+ CHECK_CONTEXT(name_remove, StateModel::END_ST, StateModel::END_EVT);
+}
+
+// Tests removingFwdRRsHandler with the following scenario:
+//
+// The request includes only a forward change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The send update request fails due to an unexpected exception.
+//
+TEST_F(SimpleRemoveTransactionTest, removingFwdRRsHandler_SendUpdateException) {
+ SimpleRemoveStubPtr name_remove;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(
+ name_remove = prepHandlerTest(SimpleRemoveTransaction::REMOVING_FWD_RRS_ST,
+ NameChangeTransaction::SERVER_SELECTED_EVT, FORWARD_CHG)
+ );
+
+ name_remove->simulate_send_exception_ = true;
+
+ // Run removingFwdRRsHandler to construct and send the request.
+ EXPECT_NO_THROW(name_remove->removingFwdRRsHandler());
+
+ // Completion flags should be false.
+ EXPECT_FALSE(name_remove->getForwardChangeCompleted());
+ EXPECT_FALSE(name_remove->getReverseChangeCompleted());
+
+ // Since IO exceptions should be gracefully handled, any that occur
+ // are unanticipated, and deemed unrecoverable, so the transaction should
+ // be transitioned to failure.
+ CHECK_CONTEXT(name_remove, NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ NameChangeTransaction::UPDATE_FAILED_EVT);
+}
+
+// Tests removingRevPtrHandler with the following scenario:
+//
+// The request includes only a reverse change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The send update request fails due to an unexpected exception.
+//
+TEST_F(SimpleRemoveTransactionTest, removingRevPtrsHandler_SendUpdateException) {
+ SimpleRemoveStubPtr name_remove;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(
+ name_remove = prepHandlerTest(SimpleRemoveTransaction::REMOVING_REV_PTRS_ST,
+ NameChangeTransaction::SERVER_SELECTED_EVT, REVERSE_CHG)
+ );
+
+ name_remove->simulate_send_exception_ = true;
+
+ // Run removingRevPtrsHandler to construct and send the request.
+ EXPECT_NO_THROW(name_remove->removingRevPtrsHandler());
+
+ // Completion flags should be false.
+ EXPECT_FALSE(name_remove->getForwardChangeCompleted());
+ EXPECT_FALSE(name_remove->getReverseChangeCompleted());
+
+ // Since IO exceptions should be gracefully handled, any that occur
+ // are unanticipated, and deemed unrecoverable, so the transaction should
+ // be transitioned to failure.
+ CHECK_CONTEXT(name_remove, NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ NameChangeTransaction::UPDATE_FAILED_EVT);
+}
+
+// Tests removingFwdRRsHandler with the following scenario:
+//
+// The request includes only a forward change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The request build fails due to an unexpected exception.
+//
+TEST_F(SimpleRemoveTransactionTest, removingFwdRRsHandler_BuildRequestException) {
+ SimpleRemoveStubPtr name_remove;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(
+ name_remove = prepHandlerTest(SimpleRemoveTransaction::REMOVING_FWD_RRS_ST,
+ NameChangeTransaction::SERVER_SELECTED_EVT, FORWARD_CHG)
+ );
+
+ // Set the one-shot exception simulation flag.
+ name_remove->simulate_build_request_exception_ = true;
+
+ // Run removingFwdRRsHandler to construct and send the request.
+ // This should fail with a build request throw which should be caught
+ // in the state handler.
+ ASSERT_NO_THROW(name_remove->removingFwdRRsHandler());
+
+ // Verify we did not attempt to send anything.
+ EXPECT_EQ(0, name_remove->getUpdateAttempts());
+
+ // Completion flags should be false.
+ EXPECT_FALSE(name_remove->getForwardChangeCompleted());
+ EXPECT_FALSE(name_remove->getReverseChangeCompleted());
+
+ // Since IO exceptions should be gracefully handled, any that occur
+ // are unanticipated, and deemed unrecoverable, so the transaction should
+ // be transitioned to failure.
+ CHECK_CONTEXT(name_remove, NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ NameChangeTransaction::UPDATE_FAILED_EVT);
+}
+
+// Tests removingRevPTRsHandler with the following scenario:
+//
+// The request includes only a forward change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The request build fails due to an unexpected exception.
+//
+TEST_F(SimpleRemoveTransactionTest, removingRevPTRsHandler_BuildRequestException) {
+ SimpleRemoveStubPtr name_remove;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(
+ name_remove = prepHandlerTest(SimpleRemoveTransaction::REMOVING_REV_PTRS_ST,
+ NameChangeTransaction::SERVER_SELECTED_EVT, FORWARD_CHG)
+ );
+
+ // Set the one-shot exception simulation flag.
+ name_remove->simulate_build_request_exception_ = true;
+
+ // Run removingRevPtrsHandler to construct and send the request.
+ // This should fail with a build request throw which should be caught
+ // in the state handler.
+ ASSERT_NO_THROW(name_remove->removingRevPtrsHandler());
+
+ // Verify we did not attempt to send anything.
+ EXPECT_EQ(0, name_remove->getUpdateAttempts());
+
+ // Completion flags should be false.
+ EXPECT_FALSE(name_remove->getForwardChangeCompleted());
+ EXPECT_FALSE(name_remove->getReverseChangeCompleted());
+
+ // Since IO exceptions should be gracefully handled, any that occur
+ // are unanticipated, and deemed unrecoverable, so the transaction should
+ // be transitioned to failure.
+ CHECK_CONTEXT(name_remove, NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ NameChangeTransaction::UPDATE_FAILED_EVT);
+}
+
+}
diff --git a/src/bin/d2/tests/test_callout_libraries.h.in b/src/bin/d2/tests/test_callout_libraries.h.in
new file mode 100644
index 0000000..a88d131
--- /dev/null
+++ b/src/bin/d2/tests/test_callout_libraries.h.in
@@ -0,0 +1,24 @@
+// Copyright (C) 2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef D2_TEST_CALLOUT_LIBRARIES_H
+#define D2_TEST_CALLOUT_LIBRARIES_H
+
+#include <config.h>
+
+namespace {
+
+// Names of the libraries used in these tests. These libraries are built using
+// libtool, so we need to look in the hidden ".libs" directory to locate the
+// .so file. Note that we access the .so file - libtool creates this as a
+// like to the real shared library.
+
+// Basic callout library with context_create and three "standard" callouts.
+static const char* CALLOUT_LIBRARY = "@abs_builddir@/.libs/libcallout.so";
+
+} // anonymous namespace
+
+#endif // D2_TEST_CALLOUT_LIBRARIES_H
diff --git a/src/bin/d2/tests/test_configured_libraries.h.in b/src/bin/d2/tests/test_configured_libraries.h.in
new file mode 100644
index 0000000..2b235ae
--- /dev/null
+++ b/src/bin/d2/tests/test_configured_libraries.h.in
@@ -0,0 +1,26 @@
+// Copyright (C) 2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef D2_TEST_CONFIGURED_LIBRARIES_H
+#define D2_TEST_CONFIGURED_LIBRARIES_H
+
+#include <config.h>
+
+namespace {
+
+// Names of the libraries used in these tests. These libraries are built using
+// libtool, so we need to look in the hidden ".libs" directory to locate the
+// .so file. Note that we access the .so file - libtool creates this as a
+// like to the real shared library.
+
+// Configured library with d2_srv_configured testing: if there is a toplevel
+// user context with an error entry the returned status is DROP with the
+// content of the error entry.
+static const char* CONFIGURED_LIBRARY = "@abs_builddir@/.libs/libconfigured.so";
+
+} // anonymous namespace
+
+#endif // D2_TEST_CONFIGURED_LIBRARIES_H
diff --git a/src/bin/d2/tests/test_data_files_config.h.in b/src/bin/d2/tests/test_data_files_config.h.in
new file mode 100644
index 0000000..40122c6
--- /dev/null
+++ b/src/bin/d2/tests/test_data_files_config.h.in
@@ -0,0 +1,10 @@
+// Copyright (C) 2013-2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+/// @brief Path to D2 source dir so tests against the dhcp-ddns.spec file
+/// can find it reliably.
+#define D2_SRC_DIR "@abs_top_srcdir@/src/bin/d2"
+#define D2_TEST_DATA_DIR "@abs_top_srcdir@/src/bin/d2/tests/testdata"
diff --git a/src/bin/d2/tests/testdata/d2_cfg_tests.json b/src/bin/d2/tests/testdata/d2_cfg_tests.json
new file mode 100644
index 0000000..6cb087f
--- /dev/null
+++ b/src/bin/d2/tests/testdata/d2_cfg_tests.json
@@ -0,0 +1,1577 @@
+# Copyright (C) 2014-2021 Internet Systems Consortium, Inc. ("ISC")
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+
+# File of DHCP-DDNS configuration permutation tests
+# Each test entry consists of:
+#
+# description - text describing the test (optional)
+# syntax-error - syntax error the JSON parsing should emit for this test
+# defaults to "" = no error
+# logic-error - indicates whether a post-JSON parsing logic error should occur
+# defaults to false
+# data {} - Configuration text to submit for parsing.
+#
+# The vast majority of the tests in this file are invalid and are expected
+# to fail either as a syntax error caught by the JSON parser or a logic error
+# caught during element processing. There are some that should succeed and are
+# used more or less as sanity checks.
+
+{ "test-list" : [
+#-----
+{
+# This test is a bit of sanity check for the "file of configs" test mechanism,
+# as well as validating this as the smallest config which makes writing
+# permutations easier.
+"description" : "D2 smallest, valid config",
+"data" :
+ {
+ "forward-ddns" : {},
+ "reverse-ddns" : {},
+ "tsig-keys" : []
+ }
+}
+
+#-----
+# Map should be supplied through setDefaults
+,{
+"description" : "D2 missing forward-ddns map",
+"data" :
+ {
+ "reverse-ddns" : {},
+ "tsig-keys" : []
+ }
+}
+
+#-----
+# Map should be supplied through setDefaults
+,{
+"description" : "D2 missing reverse-ddns map",
+"data" :
+ {
+ "forward-ddns" : {},
+ "tsig-keys" : []
+ }
+}
+
+
+#-----
+# Map should be supplied through setDefaults
+,{
+"description" : "D2 missing tsig-keys list",
+"data" :
+ {
+ "forward-ddns" : {},
+ "reverse-ddns" : {}
+ }
+}
+
+#-----
+,{
+"description" : "D2 unknown scalar",
+"syntax-error" : "<string>:1.3-16: got unexpected keyword \"bogus-scalar\" in DhcpDdns map.",
+"data" :
+ {
+ "bogus-scalar" : true,
+ "forward-ddns" : {},
+ "reverse-ddns" : {},
+ "tsig-keys" : []
+ }
+}
+
+#-----
+,{
+"description" : "D2 unknown map",
+"syntax-error" : "<string>:1.3-13: got unexpected keyword \"bogus-map\" in DhcpDdns map.",
+"data" :
+ {
+ "bogus-map" : {},
+ "forward-ddns" : {},
+ "reverse-ddns" : {},
+ "tsig-keys" : []
+ }
+}
+
+#-----
+,{
+"description" : "D2 unknown list",
+"syntax-error" : "<string>:1.3-14: got unexpected keyword \"bogus-list\" in DhcpDdns map.",
+"data" :
+ {
+ "bogus-list" : [],
+ "forward-ddns" : {},
+ "reverse-ddns" : {},
+ "tsig-keys" : []
+ }
+}
+
+#----- D2Params Test
+
+#----- D2Params.ip-address
+,{
+"description" : "D2Params.ip-address: valid v4",
+"data" :
+ {
+ "ip-address" : "192.168.0.1",
+ "forward-ddns" : {},
+ "reverse-ddns" : {},
+ "tsig-keys" : []
+ }
+}
+
+#-----
+,{
+"description" : "D2Params.ip-address: valid v6",
+"data" :
+ {
+ "ip-address" : "2001:db8::1",
+ "forward-ddns" : {},
+ "reverse-ddns" : {},
+ "tsig-keys" : []
+ }
+}
+
+#-----
+,{
+"description" : "D2Params.ip-address invalid value",
+"logic-error" : "Failed to convert 'bogus' to address: Failed to convert string to address 'bogus': Invalid argument(<string>:1:39)",
+"data" :
+ {
+ "ip-address" : "bogus",
+ "forward-ddns" : {},
+ "reverse-ddns" : {},
+ "tsig-keys" : []
+ }
+#-----
+}
+
+#----- D2Params.port
+,{
+"description" : "D2Params.port, valid value",
+"data" :
+ {
+ "port" : 100,
+ "forward-ddns" : {},
+ "reverse-ddns" : {},
+ "tsig-keys" : []
+ }
+}
+
+#-----
+,{
+"description" : "D2Params.port can't be 0",
+"syntax-error" : "<string>:1.33: port must be greater than zero but less than 65536",
+"data" :
+ {
+ "port" : 0,
+ "forward-ddns" : {},
+ "reverse-ddns" : {},
+ "tsig-keys" : []
+ }
+}
+
+#-----
+,{
+"description" : "D2Params.port, non numeric",
+"syntax-error" : "<string>:1.33-39: syntax error, unexpected constant string, expecting integer",
+"data" :
+ {
+ "port" : "bogus",
+ "forward-ddns" : {},
+ "reverse-ddns" : {},
+ "tsig-keys" : []
+ }
+}
+
+#----- D2Params.dns-server-timeout
+,{
+"description" : "D2Params.dns-server-timeout, valid value",
+"data" :
+ {
+ "dns-server-timeout" : 1000,
+ "forward-ddns" : {},
+ "reverse-ddns" : {},
+ "tsig-keys" : []
+ }
+}
+
+#-----
+,{
+"description" : "D2Params.dns-server-timeout can't be 0",
+"syntax-error" : "<string>:1.25: dns-server-timeout must be greater than zero",
+"data" :
+ {
+ "dns-server-timeout" : 0,
+ "forward-ddns" : {},
+ "reverse-ddns" : {},
+ "tsig-keys" : []
+ }
+}
+
+#-----
+,{
+"description" : "D2Params.dns-server-timeout, non numeric",
+"syntax-error" : "<string>:1.25-31: syntax error, unexpected constant string, expecting integer",
+"data" :
+ {
+ "dns-server-timeout" : "bogus",
+ "forward-ddns" : {},
+ "reverse-ddns" : {},
+ "tsig-keys" : []
+ }
+}
+#-----
+
+#----- D2Params.ncr-protocol
+,{
+"description" : "D2Params.ncr-protocol, valid UDP",
+"data" :
+ {
+ "ncr-protocol" : "UDP",
+ "forward-ddns" : {},
+ "reverse-ddns" : {},
+ "tsig-keys" : []
+ }
+}
+
+#-----
+,{
+"description" : "D2Params.ncr-protocol, unsupported TCP",
+"logic-error" : "ncr-protocol : TCP is not yet supported (<string>:1:41)",
+"data" :
+ {
+ "ncr-protocol" : "TCP",
+ "forward-ddns" : {},
+ "reverse-ddns" : {},
+ "tsig-keys" : []
+ }
+}
+
+
+#-----
+,{
+"description" : "D2Params.ncr-protocol, invalid value",
+"syntax-error" : "<string>:1.41-47: syntax error, unexpected constant string, expecting UDP or TCP",
+"data" :
+ {
+ "ncr-protocol" : "bogus",
+ "forward-ddns" : {},
+ "reverse-ddns" : {},
+ "tsig-keys" : []
+ }
+}
+
+
+#----- D2Params.ncr-format tests
+
+,{
+"description" : "D2Params.ncr-format, valid JSON",
+"data" :
+ {
+ "ncr-format" : "JSON",
+ "forward-ddns" : {},
+ "reverse-ddns" : {},
+ "tsig-keys" : []
+ }
+}
+
+#-----
+,{
+"description" : "D2Params.ncr-format, invalid value",
+"syntax-error" : "<string>:1.39-45: syntax error, unexpected constant string, expecting JSON",
+"data" :
+ {
+ "ncr-format" : "bogus",
+ "forward-ddns" : {},
+ "reverse-ddns" : {},
+ "tsig-keys" : []
+ }
+}
+
+#----- TSIGKey Tests
+
+#-----
+,{
+# This test is a sanity check that valid TSIG entries work.
+"description" : "D2.tsig-keys, valid key",
+"data" :
+ {
+ "forward-ddns" : {},
+ "reverse-ddns" : {},
+ "tsig-keys" :
+ [
+ {
+ "name" : "d2.md5.key",
+ "algorithm" : "HMAC-MD5",
+ "secret" : "LSWXnfkKZjdPJI5QxlpnfQ=="
+ }
+ ]
+ }
+}
+
+#-----
+,{
+"description" : "D2.tsig-keys, missing key name",
+"logic-error" : "missing parameter 'name' (<string>:1:62)",
+"data" :
+ {
+ "forward-ddns" : {},
+ "reverse-ddns" : {},
+ "tsig-keys" :
+ [
+ {
+ "algorithm" : "HMAC-MD5",
+ "secret" : "LSWXnfkKZjdPJI5QxlpnfQ=="
+ }
+ ]
+ }
+}
+
+#-----
+,{
+"description" : "D2.tsig-keys, blank key name",
+"syntax-error" : "<string>:1.95: TSIG key name cannot be blank",
+"data" :
+ {
+ "forward-ddns" : {},
+ "reverse-ddns" : {},
+ "tsig-keys" :
+ [
+ {
+ "name" : "",
+ "algorithm" : "HMAC-MD5",
+ "secret" : "LSWXnfkKZjdPJI5QxlpnfQ=="
+ }
+ ]
+ }
+}
+
+#-----
+,{
+"description" : "D2.tsig-keys, duplicate key name",
+"logic-error" : "Duplicate TSIG key name specified : first.key (<string>:1:185)",
+"data" :
+ {
+ "forward-ddns" : {},
+ "reverse-ddns" : {},
+ "tsig-keys" :
+ [
+ {
+ "name" : "first.key",
+ "algorithm" : "HMAC-MD5",
+ "secret" : "LSWXnfkKZjdPJI5QxlpnfQ=="
+ },
+ {
+ "name" : "first.key",
+ "algorithm" : "HMAC-MD5",
+ "secret" : "LSWXnfkKZjdPJI5QxlpnfQ=="
+ }
+ ]
+ }
+}
+
+#----- D2.tsig-keys, algorithm tests
+
+,{
+"description" : "D2.tsig-keys, all valid algorithms",
+"data" :
+ {
+ "forward-ddns" : {},
+ "reverse-ddns" : {},
+ "tsig-keys" :
+ [
+ {
+ "name" : "d2.md5.key",
+ "algorithm" : "HMAC-MD5",
+ "secret" : "LSWXnfkKZjdPJI5QxlpnfQ=="
+ },
+ {
+ "name" : "d2.sha1.key",
+ "algorithm" : "HMAC-SHA1",
+ "secret" : "hRrp29wzUv3uzSNRLlY68w=="
+ },
+ {
+ "name" : "d2.sha224.key",
+ "algorithm" : "HMAC-SHA224",
+ "secret" : "bZEG7Ow8OgAUPfLWV3aAUQ=="
+ },
+ {
+ "name" : "d2.sha256.key",
+ "algorithm" : "hmac-sha256",
+ "secret" : "bjF4hYhTfQ5MX0siagelsw=="
+ },
+ {
+ "name" : "d2.sha384.key",
+ "algorithm" : "hmac-sha384",
+ "secret" : "Gwk53fvy3CmbupoI9TgigA=="
+ },
+ {
+ "name" : "d2.sha512.key",
+ "algorithm" : "hmac-sha512",
+ "secret" : "wP+5FqMnKXCxBWljU/BZAA=="
+ }
+ ]
+ }
+}
+
+#----- D2.tsig-keys, algorithm tests
+,{
+"description" : "D2.tsig-keys, missing algorithm",
+"logic-error" : "missing parameter 'algorithm' (<string>:1:62)",
+"data" :
+ {
+ "forward-ddns" : {},
+ "reverse-ddns" : {},
+ "tsig-keys" :
+ [
+ {
+ "name" : "first.key",
+ "secret" : "LSWXnfkKZjdPJI5QxlpnfQ=="
+ }
+ ]
+ }
+}
+
+#-----
+,{
+"description" : "D2.tsig-keys, blank algorithm",
+"syntax-error" : "<string>:1.75: TSIG key algorithm cannot be blank",
+"data" :
+ {
+ "forward-ddns" : {},
+ "reverse-ddns" : {},
+ "tsig-keys" :
+ [
+ {
+ "name" : "first.key",
+ "algorithm" : "",
+ "secret" : "LSWXnfkKZjdPJI5QxlpnfQ=="
+ }
+ ]
+ }
+}
+
+#-----
+,{
+"description" : "D2.tsig-keys, invalid algorithm",
+"logic-error" : "tsig-key : Unknown TSIG Key algorithm: bogus (<string>:1:77)",
+"data" :
+ {
+ "forward-ddns" : {},
+ "reverse-ddns" : {},
+ "tsig-keys" :
+ [
+ {
+ "name" : "first.key",
+ "algorithm" : "bogus",
+ "secret" : "LSWXnfkKZjdPJI5QxlpnfQ=="
+ }
+ ]
+ }
+}
+
+#----- D2.tsig-keys, digest-bits tests
+,{
+"description" : "D2.tsig-keys, all valid algorithms",
+"data" :
+ {
+ "forward-ddns" : {},
+ "reverse-ddns" : {},
+ "tsig-keys" :
+ [
+ {
+ "name" : "d2.md5.key",
+ "algorithm" : "HMAC-MD5",
+ "digest-bits" : 80,
+ "secret" : "LSWXnfkKZjdPJI5QxlpnfQ=="
+ },
+ {
+ "name" : "d2.sha1.key",
+ "algorithm" : "HMAC-SHA1",
+ "digest-bits" : 80,
+ "secret" : "hRrp29wzUv3uzSNRLlY68w=="
+ },
+ {
+ "name" : "d2.sha224.key",
+ "algorithm" : "HMAC-SHA224",
+ "digest-bits" : 112,
+ "secret" : "bZEG7Ow8OgAUPfLWV3aAUQ=="
+ },
+ {
+ "name" : "d2.sha256.key",
+ "algorithm" : "hmac-sha256",
+ "digest-bits" : 128,
+ "secret" : "bjF4hYhTfQ5MX0siagelsw=="
+ },
+ {
+ "name" : "d2.sha384.key",
+ "algorithm" : "hmac-sha384",
+ "digest-bits" : 192,
+ "secret" : "Gwk53fvy3CmbupoI9TgigA=="
+ },
+ {
+ "name" : "d2.sha512.key",
+ "algorithm" : "hmac-sha512",
+ "digest-bits" : 256,
+ "secret" : "wP+5FqMnKXCxBWljU/BZAA=="
+ }
+ ]
+ }
+}
+
+#-----
+,{
+"description" : "D2.tsig-keys, invalid digest-bits",
+"syntax-error" : "<string>:1.104-105: TSIG key digest-bits must either be zero or a positive, multiple of eight",
+"data" :
+ {
+ "forward-ddns" : {},
+ "reverse-ddns" : {},
+ "tsig-keys" :
+ [
+ {
+ "name" : "d2.md5.key",
+ "algorithm" : "HMAC-MD5",
+ "digest-bits" : 84,
+ "secret" : "LSWXnfkKZjdPJI5QxlpnfQ=="
+ }
+ ]
+ }
+}
+
+#-----
+,{
+"description" : "D2.tsig-keys, too small truncated HMAC-MD5",
+"logic-error" : "tsig-key: digest-bits too small : (<string>:1:104)",
+"data" :
+ {
+ "forward-ddns" : {},
+ "reverse-ddns" : {},
+ "tsig-keys" :
+ [
+ {
+ "name" : "d2.md5.key",
+ "algorithm" : "HMAC-MD5",
+ "digest-bits" : 72,
+ "secret" : "LSWXnfkKZjdPJI5QxlpnfQ=="
+ }
+ ]
+ }
+}
+
+#-----
+,{
+"description" : "D2.tsig-keys, too small truncated HMAC-SHA1",
+"logic-error" : "tsig-key: digest-bits too small : (<string>:1:105)",
+"data" :
+ {
+ "forward-ddns" : {},
+ "reverse-ddns" : {},
+ "tsig-keys" :
+ [
+ {
+ "name" : "d2.sha1.key",
+ "algorithm" : "HMAC-SHA1",
+ "digest-bits" : 72,
+ "secret" : "hRrp29wzUv3uzSNRLlY68w=="
+ }
+ ]
+ }
+}
+
+#-----
+,{
+"description" : "D2.tsig-keys, too small truncated HMAC-SHA224",
+"logic-error" : "tsig-key: digest-bits too small : (<string>:1:107)",
+"data" :
+ {
+ "forward-ddns" : {},
+ "reverse-ddns" : {},
+ "tsig-keys" :
+ [
+ {
+ "name" : "d2.sha224.key",
+ "algorithm" : "HMAC-SHA224",
+ "digest-bits" : 104,
+ "secret" : "bZEG7Ow8OgAUPfLWV3aAUQ=="
+ }
+ ]
+ }
+}
+
+#-----
+,{
+"description" : "D2.tsig-keys, too small truncated HMAC-SHA256",
+"logic-error" : "tsig-key: digest-bits too small : (<string>:1:107)",
+"data" :
+ {
+ "forward-ddns" : {},
+ "reverse-ddns" : {},
+ "tsig-keys" :
+ [
+ {
+ "name" : "d2.sha256.key",
+ "algorithm" : "hmac-sha256",
+ "digest-bits" : 120,
+ "secret" : "bjF4hYhTfQ5MX0siagelsw=="
+ }
+ ]
+ }
+}
+
+#-----
+,{
+"description" : "D2.tsig-keys, too small truncated HMAC-SHA384",
+"logic-error" : "tsig-key: digest-bits too small : (<string>:1:107)",
+"data" :
+ {
+ "forward-ddns" : {},
+ "reverse-ddns" : {},
+ "tsig-keys" :
+ [
+ {
+ "name" : "d2.sha384.key",
+ "algorithm" : "hmac-sha384",
+ "digest-bits" : 184,
+ "secret" : "Gwk53fvy3CmbupoI9TgigA=="
+ }
+ ]
+ }
+}
+
+#-----
+,{
+"description" : "D2.tsig-keys, too small truncated HMAC-SHA512",
+"logic-error" : "tsig-key: digest-bits too small : (<string>:1:107)",
+"data" :
+ {
+ "forward-ddns" : {},
+ "reverse-ddns" : {},
+ "tsig-keys" :
+ [
+ {
+ "name" : "d2.sha512.key",
+ "algorithm" : "hmac-sha512",
+ "digest-bits" : 248,
+ "secret" : "wP+5FqMnKXCxBWljU/BZAA=="
+ }
+ ]
+ }
+}
+
+#----- D2.tsig-keys, secret tests
+,{
+"description" : "D2.tsig-keys, missing secret",
+"logic-error" : "missing parameter 'secret' (<string>:1:62)",
+"data" :
+ {
+ "forward-ddns" : {},
+ "reverse-ddns" : {},
+ "tsig-keys" :
+ [
+ {
+ "name" : "first.key",
+ "algorithm" : "HMAC-MD5"
+ }
+ ]
+ }
+}
+
+#-----
+,{
+"description" : "D2.tsig-keys, blank secret",
+"syntax-error" : "<string>:1.118: TSIG key secret cannot be blank",
+"data" :
+ {
+ "forward-ddns" : {},
+ "reverse-ddns" : {},
+ "tsig-keys" :
+ [
+ {
+ "name" : "first.key",
+ "algorithm" : "HMAC-MD5",
+ "secret" : ""
+ }
+ ]
+ }
+}
+
+#-----
+,{
+"description" : "D2.tsig-keys, invalid secret",
+"logic-error" : "Cannot make D2TsigKey: Incomplete input for base64: bogus (<string>:1:62)",
+"data" :
+ {
+ "forward-ddns" : {},
+ "reverse-ddns" : {},
+ "tsig-keys" :
+ [
+ {
+ "name" : "first.key",
+ "algorithm" : "HMAC-MD5",
+ "secret" : "bogus"
+ }
+ ]
+ }
+}
+
+#----- D2.forward-ddns tests
+,{
+"description" : "D2.forward-ddns, valid, empty ddns-domains",
+"data" :
+ {
+ "forward-ddns" :
+ {
+ "ddns-domains" : []
+ },
+ "reverse-ddns" : {},
+ "tsig-keys" : []
+ }
+}
+
+#------
+,{
+"description" : "D2.forward-ddns, unknown parameter",
+"syntax-error" : "<string>:1.21-27: got unexpected keyword \"bogus\" in forward-ddns map.",
+"data" :
+ {
+ "forward-ddns" :
+ {
+ "bogus" : true,
+ "ddns-domains" : []
+ },
+ "reverse-ddns" : {},
+ "tsig-keys" : []
+ }
+}
+
+#------
+,{
+"description" : "D2.forward-ddns, one valid, domain",
+"data" :
+ {
+ "forward-ddns" :
+ {
+ "ddns-domains" :
+ [
+ {
+ "name" : "four.example.com.",
+ "key-name" : "d2.md5.key",
+ "dns-servers" :
+ [
+ {
+ "ip-address" : "172.16.1.1"
+ }
+ ]
+ }
+ ]
+ },
+ "reverse-ddns" : {},
+ "tsig-keys" :
+ [
+ {
+ "name" : "d2.md5.key",
+ "algorithm" : "HMAC-MD5",
+ "secret" : "LSWXnfkKZjdPJI5QxlpnfQ=="
+ }
+ ]
+ }
+}
+
+#------
+,{
+"description" : "D2.forward-ddns, duplicate domain",
+"logic-error" : "Duplicate domain specified:four.example.com. (<string>:1:184)",
+"data" :
+ {
+ "forward-ddns" :
+ {
+ "ddns-domains" :
+ [
+ {
+ "name" : "four.example.com.",
+ "dns-servers" :
+ [
+ {
+ "ip-address" : "172.16.1.1"
+ }
+ ]
+ },
+ {
+ "name" : "four.example.com.",
+ "dns-servers" :
+ [
+ {
+ "ip-address" : "172.16.1.2"
+ }
+ ]
+ }
+ ]
+ },
+ "reverse-ddns" : {},
+ "tsig-keys" : []
+ }
+}
+
+#----- D2.forward-ddns.dhcp-ddns tests
+,{
+"description" : "D2.forward-ddns.dhcp-ddns, unknown parameter",
+"syntax-error" : "<string>:1.41-47: got unexpected keyword \"bogus\" in ddns-domains map.",
+"data" :
+ {
+ "forward-ddns" :
+ {
+ "ddns-domains" :
+ [
+ {
+ "bogus" : true
+ }
+ ]
+ },
+ "reverse-ddns" : {},
+ "tsig-keys" : []
+ }
+}
+
+#----- D2.forward-ddns.dhcp-ddns.name tests
+,{
+"description" : "D2.forward-ddns.dhcp-ddns, empty domain",
+"syntax-error" : "<string>:1.42: syntax error, unexpected }",
+"data" :
+ {
+ "forward-ddns" :
+ {
+ "ddns-domains" :
+ [
+ {
+ }
+ ]
+ },
+ "reverse-ddns" : {},
+ "tsig-keys" : []
+ }
+}
+
+#-----
+,{
+"description" : "D2.forward-ddns.dhcp-ddns, blank name",
+"syntax-error" : "<string>:1.47: Ddns domain name cannot be blank",
+"data" :
+ {
+ "forward-ddns" :
+ {
+ "ddns-domains" :
+ [
+ {
+ "name" : ""
+ }
+ ]
+ },
+ "reverse-ddns" : {},
+ "tsig-keys" : []
+ }
+}
+
+#------ "D2.forward-ddns.dhcp-ddns, key-name tests
+,{
+"description" : "D2.forward-ddns, no matching key name",
+"logic-error" : "DdnsDomain : specifies an undefined key: no.such.key (<string>:1:104)",
+"data" :
+ {
+ "forward-ddns" :
+ {
+ "ddns-domains" :
+ [
+ {
+ "name" : "four.example.com.",
+ "key-name" : "no.such.key",
+ "dns-servers" :
+ [
+ {
+ "ip-address" : "172.16.1.1"
+ }
+ ]
+ }
+ ]
+ },
+ "reverse-ddns" : {},
+ "tsig-keys" :
+ [
+ {
+ "name" : "d2.md5.key",
+ "algorithm" : "HMAC-MD5",
+ "secret" : "LSWXnfkKZjdPJI5QxlpnfQ=="
+ }
+ ]
+ }
+}
+
+#----- D2.forward-ddns.dhcp-ddns.dns-servers tests
+,{
+"description" : "D2.forward-ddns.dhcp-ddns.dns-servers, no servers",
+"syntax-error" : "<string>:1.59: syntax error, unexpected ], expecting {",
+"data" :
+ {
+ "forward-ddns" :
+ {
+ "ddns-domains" :
+ [
+ {
+ "name" : "four.example.com.",
+ "dns-servers" : []
+ }
+ ]
+ },
+ "reverse-ddns" : {},
+ "tsig-keys" : []
+ }
+}
+
+#----- D2.forward-ddns.dhcp-ddns.dns-servers tests
+,{
+"description" : "D2.forward-ddns.dhcp-ddns.dns-servers, unknown parameter",
+"syntax-error" : "<string>:1.60-66: got unexpected keyword \"bogus\" in dns-servers map.",
+"data" :
+ {
+ "forward-ddns" :
+ {
+ "ddns-domains" :
+ [
+ {
+ "name" : "four.example.com.",
+ "dns-servers" :
+ [
+ {
+ "bogus" : true
+ }
+ ]
+ }
+ ]
+ },
+ "reverse-ddns" : {},
+ "tsig-keys" : []
+ }
+}
+
+#-----
+,{
+"description" : "D2.forward-ddns.dhcp-ddns.dns-servers.hostname unsupported",
+"syntax-error" : "<string>:1.70: hostname is not yet supported",
+"data" :
+ {
+ "forward-ddns" :
+ {
+ "ddns-domains" :
+ [
+ {
+ "name" : "four.example.com.",
+ "dns-servers" :
+ [
+ {
+ "hostname" : "myhost.com"
+ }
+ ]
+ }
+ ]
+ },
+ "reverse-ddns" : {},
+ "tsig-keys" : []
+ }
+}
+
+#-----
+,{
+"description" : "D2.forward-ddns.dhcp-ddns.dns-servers.ip-address v4 address ",
+"data" :
+ {
+ "forward-ddns" :
+ {
+ "ddns-domains" :
+ [
+ {
+ "name" : "four.example.com.",
+ "dns-servers" :
+ [
+ {
+ "ip-address" : "172.16.1.1"
+ }
+ ]
+ }
+ ]
+ },
+ "reverse-ddns" : {},
+ "tsig-keys" : []
+ }
+}
+
+#-----
+,{
+"description" : "D2.forward-ddns.dhcp-ddns.dns-servers.ip-address v6 address ",
+"data" :
+ {
+ "forward-ddns" :
+ {
+ "ddns-domains" :
+ [
+ {
+ "name" : "four.example.com.",
+ "dns-servers" :
+ [
+ {
+ "ip-address" : "2001:db8::1"
+ }
+ ]
+ }
+ ]
+ },
+ "reverse-ddns" : {},
+ "tsig-keys" : []
+ }
+}
+
+#-----
+,{
+"description" : "D2.forward-ddns.dhcp-ddns.dns-servers.ip-address invalid address ",
+"logic-error" : "Dns Server : invalid IP address : bogus (<string>:1:74)",
+"data" :
+ {
+ "forward-ddns" :
+ {
+ "ddns-domains" :
+ [
+ {
+ "name" : "four.example.com.",
+ "dns-servers" :
+ [
+ {
+ "ip-address" : "bogus"
+ }
+ ]
+ }
+ ]
+ },
+ "reverse-ddns" : {},
+ "tsig-keys" : []
+ }
+}
+
+
+#-----
+,{
+"description" : "D2.forward-ddns.dhcp-ddns.dns-servers.port valid value ",
+"data" :
+ {
+ "forward-ddns" :
+ {
+ "ddns-domains" :
+ [
+ {
+ "name" : "four.example.com.",
+ "dns-servers" :
+ [
+ {
+ "ip-address" : "2001:db8::1",
+ "port" : 77
+ }
+ ]
+ }
+ ]
+ },
+ "reverse-ddns" : {},
+ "tsig-keys" : []
+ }
+}
+
+#-----
+,{
+"description" : "D2.forward-ddns.dhcp-ddns.dns-servers.port cannot be 0 ",
+"syntax-error" : "<string>:1.97: port must be greater than zero but less than 65536",
+"data" :
+ {
+ "forward-ddns" :
+ {
+ "ddns-domains" :
+ [
+ {
+ "name" : "four.example.com.",
+ "dns-servers" :
+ [
+ {
+ "ip-address" : "2001:db8::1",
+ "port" : 0
+ }
+ ]
+ }
+ ]
+ },
+ "reverse-ddns" : {},
+ "tsig-keys" : []
+ }
+}
+
+#-----
+,{
+"description" : "D2.forward-ddns.dhcp-ddns.dns-servers.key-name, no matching key name",
+"logic-error" : "Dns Server : specifies an undefined key: no.such.key (<string>:1:100)",
+"data" :
+ {
+ "forward-ddns" :
+ {
+ "ddns-domains" :
+ [
+ {
+ "name" : "four.example.com.",
+ "dns-servers" :
+ [
+ {
+ "ip-address" : "172.16.1.1",
+ "key-name" : "no.such.key"
+ }
+ ]
+ }
+ ]
+ },
+ "reverse-ddns" : {},
+ "tsig-keys" :
+ [
+ {
+ "name" : "d2.md5.key",
+ "algorithm" : "HMAC-MD5",
+ "secret" : "LSWXnfkKZjdPJI5QxlpnfQ=="
+ }
+ ]
+ }
+}
+
+#----- D2.reverse-ddns tests
+,{
+"description" : "D2.reverse-ddns, valid, empty ddns-domains",
+"data" :
+ {
+ "forward-ddns" : {},
+ "reverse-ddns" :
+ {
+ "ddns-domains" : []
+ },
+ "tsig-keys" : []
+ }
+}
+
+#------
+,{
+"description" : "D2.reverse-ddns, unknown parameter",
+"syntax-error" : "<string>:1.43-49: got unexpected keyword \"bogus\" in reverse-ddns map.",
+"data" :
+ {
+ "forward-ddns" : {},
+ "reverse-ddns" :
+ {
+ "bogus" : true,
+ "ddns-domains" : []
+ },
+ "tsig-keys" : []
+ }
+}
+
+#------
+,{
+"description" : "D2.reverse-ddns, one valid, domain",
+"data" :
+ {
+ "forward-ddns" : {},
+ "reverse-ddns" :
+ {
+ "ddns-domains" :
+ [
+ {
+ "name" : "2.0.192.in-addra.arpa.",
+ "key-name" : "d2.md5.key",
+ "dns-servers" :
+ [
+ {
+ "ip-address" : "172.16.1.1"
+ }
+ ]
+ }
+ ]
+ },
+ "tsig-keys" :
+ [
+ {
+ "name" : "d2.md5.key",
+ "algorithm" : "HMAC-MD5",
+ "secret" : "LSWXnfkKZjdPJI5QxlpnfQ=="
+ }
+ ]
+ }
+}
+
+#------
+,{
+"description" : "D2.reverse-ddns, duplicate domain",
+"logic-error" : "Duplicate domain specified:2.0.192.in-addra.arpa. (<string>:1:211)",
+"data" :
+ {
+ "forward-ddns" : {},
+ "reverse-ddns" :
+ {
+ "ddns-domains" :
+ [
+ {
+ "name" : "2.0.192.in-addra.arpa.",
+ "dns-servers" :
+ [
+ {
+ "ip-address" : "172.16.1.1"
+ }
+ ]
+ },
+ {
+ "name" : "2.0.192.in-addra.arpa.",
+ "dns-servers" :
+ [
+ {
+ "ip-address" : "172.16.1.2"
+ }
+ ]
+ }
+ ]
+ },
+ "tsig-keys" : []
+ }
+}
+
+#----- D2.reverse-ddns.dhcp-ddns tests
+,{
+"description" : "D2.reverse-ddns.dhcp-ddns, unknown parameter",
+"syntax-error" : "<string>:1.63-69: got unexpected keyword \"bogus\" in ddns-domains map.",
+"data" :
+ {
+ "forward-ddns" : {},
+ "reverse-ddns" :
+ {
+ "ddns-domains" :
+ [
+ {
+ "bogus" : true
+ }
+ ]
+ },
+ "tsig-keys" : []
+ }
+}
+
+#----- D2.reverse-ddns.dhcp-ddns.name tests
+,{
+"description" : "D2.reverse-ddns.dhcp-ddns, no name",
+"syntax-error" : "<string>:1.64: syntax error, unexpected }",
+"data" :
+ {
+ "forward-ddns" : {},
+ "reverse-ddns" :
+ {
+ "ddns-domains" :
+ [
+ {
+ }
+ ]
+ },
+ "tsig-keys" : []
+ }
+}
+
+#-----
+,{
+"description" : "D2.reverse-ddns.dhcp-ddns, blank name",
+"syntax-error" : "<string>:1.69: Ddns domain name cannot be blank",
+"data" :
+ {
+ "forward-ddns" : {},
+ "reverse-ddns" :
+ {
+ "ddns-domains" :
+ [
+ {
+ "name" : ""
+ }
+ ]
+ },
+ "tsig-keys" : []
+ }
+}
+
+#------ "D2.reverse-ddns.dhcp-ddns, key-name tests
+,{
+"description" : "D2.reverse-ddns, no matching key name",
+"logic-error" : "DdnsDomain : specifies an undefined key: no.such.key (<string>:1:126)",
+"data" :
+ {
+ "forward-ddns" : {},
+ "reverse-ddns" :
+ {
+ "ddns-domains" :
+ [
+ {
+ "name" : "2.0.192.in-addr.arpa.",
+ "key-name" : "no.such.key",
+ "dns-servers" :
+ [
+ {
+ "ip-address" : "172.16.1.1"
+ }
+ ]
+ }
+ ]
+ },
+ "tsig-keys" :
+ [
+ {
+ "name" : "d2.md5.key",
+ "algorithm" : "HMAC-MD5",
+ "secret" : "LSWXnfkKZjdPJI5QxlpnfQ=="
+ }
+ ]
+ }
+}
+
+#----- D2.reverse-ddns.dhcp-ddns.dns-servers tests
+,{
+"description" : "D2.reverse-ddns.dhcp-ddns.dns-servers, no servers",
+"syntax-error" : "<string>:1.81: syntax error, unexpected ], expecting {",
+"data" :
+ {
+ "forward-ddns" : {},
+ "reverse-ddns" :
+ {
+ "ddns-domains" :
+ [
+ {
+ "name" : "2.0.192.in-addr.arpa.",
+ "dns-servers" : []
+ }
+ ]
+ },
+ "tsig-keys" : []
+ }
+}
+
+#----- D2.reverse-ddns.dhcp-ddns.dns-servers tests
+,{
+"description" : "D2.reverse-ddns.dhcp-ddns.dns-servers, unknown parameter",
+"syntax-error" : "<string>:1.82-88: got unexpected keyword \"bogus\" in dns-servers map.",
+"data" :
+ {
+ "forward-ddns" : {},
+ "reverse-ddns" :
+ {
+ "ddns-domains" :
+ [
+ {
+ "name" : "2.0.192.in-addr.arpa.",
+ "dns-servers" :
+ [
+ {
+ "bogus" : true
+ }
+ ]
+ }
+ ]
+ },
+ "tsig-keys" : []
+ }
+}
+
+#-----
+,{
+"description" : "D2.reverse-ddns.dhcp-ddns.dns-servers.hostname unsupported",
+"syntax-error" : "<string>:1.92: hostname is not yet supported",
+"data" :
+ {
+ "forward-ddns" : {},
+ "reverse-ddns" :
+ {
+ "ddns-domains" :
+ [
+ {
+ "name" : "2.0.192.in-addr.arpa.",
+ "dns-servers" :
+ [
+ {
+ "hostname" : "myhost.com"
+ }
+ ]
+ }
+ ]
+ },
+ "tsig-keys" : []
+ }
+}
+
+#-----
+,{
+"description" : "D2.reverse-ddns.dhcp-ddns.dns-servers.ip-address v4 address ",
+"data" :
+ {
+ "forward-ddns" : {},
+ "reverse-ddns" :
+ {
+ "ddns-domains" :
+ [
+ {
+ "name" : "2.0.192.in-addr.arpa.",
+ "dns-servers" :
+ [
+ {
+ "ip-address" : "172.16.1.1"
+ }
+ ]
+ }
+ ]
+ },
+ "tsig-keys" : []
+ }
+}
+
+#-----
+,{
+"description" : "D2.reverse-ddns.dhcp-ddns.dns-servers.ip-address v6 address ",
+"data" :
+ {
+ "forward-ddns" : {},
+ "reverse-ddns" :
+ {
+ "ddns-domains" :
+ [
+ {
+ "name" : "2.0.192.in-addr.arpa.",
+ "dns-servers" :
+ [
+ {
+ "ip-address" : "2001:db8::1"
+ }
+ ]
+ }
+ ]
+ },
+ "tsig-keys" : []
+ }
+}
+#-----
+,{
+"description" : "D2.reverse-ddns.dhcp-ddns.dns-servers.ip-address invalid value",
+"logic-error" : "Dns Server : invalid IP address : bogus (<string>:1:96)",
+"data" :
+ {
+ "forward-ddns" : {},
+ "reverse-ddns" :
+ {
+ "ddns-domains" :
+ [
+ {
+ "name" : "2.0.192.in-addr.arpa.",
+ "dns-servers" :
+ [
+ {
+ "ip-address" : "bogus"
+ }
+ ]
+ }
+ ]
+ },
+ "tsig-keys" : []
+ }
+}
+
+
+#-----
+,{
+"description" : "D2.reverse-ddns.dhcp-ddns.dns-servers.port valid value ",
+"data" :
+ {
+ "forward-ddns" : {},
+ "reverse-ddns" :
+ {
+ "ddns-domains" :
+ [
+ {
+ "name" : "2.0.192.in-addr.arpa.",
+ "dns-servers" :
+ [
+ {
+ "ip-address" : "2001:db8::1",
+ "port" : 77
+ }
+ ]
+ }
+ ]
+ },
+ "tsig-keys" : []
+ }
+}
+
+#-----
+,{
+"description" : "D2.reverse-ddns.dhcp-ddns.dns-servers.port cannot be 0 ",
+"syntax-error" : "<string>:1.119: port must be greater than zero but less than 65536",
+"data" :
+ {
+ "forward-ddns" : {},
+ "reverse-ddns" :
+ {
+ "ddns-domains" :
+ [
+ {
+ "name" : "2.0.192.in-addr.arpa.",
+ "dns-servers" :
+ [
+ {
+ "ip-address" : "2001:db8::1",
+ "port" : 0
+ }
+ ]
+ }
+ ]
+ },
+ "tsig-keys" : []
+ }
+}
+
+#-----
+,{
+"description" : "D2.reverse-ddns.dhcp-ddns.dns-servers.key-name, no matching key name",
+"logic-error" : "Dns Server : specifies an undefined key: no.such.key (<string>:1:122)",
+"data" :
+ {
+ "forward-ddns" : {},
+ "reverse-ddns" :
+ {
+ "ddns-domains" :
+ [
+ {
+ "name" : "2.0.192.in-addr.arpa.",
+ "dns-servers" :
+ [
+ {
+ "ip-address" : "172.16.1.1",
+ "key-name" : "no.such.key"
+ }
+ ]
+ }
+ ]
+ },
+ "tsig-keys" :
+ [
+ {
+ "name" : "d2.md5.key",
+ "algorithm" : "HMAC-MD5",
+ "secret" : "LSWXnfkKZjdPJI5QxlpnfQ=="
+ }
+ ]
+ }
+}
+
+# ----- End of Tests
+]}
diff --git a/src/bin/d2/tests/testdata/get_config.json b/src/bin/d2/tests/testdata/get_config.json
new file mode 100644
index 0000000..d9613a3
--- /dev/null
+++ b/src/bin/d2/tests/testdata/get_config.json
@@ -0,0 +1,106 @@
+{
+ "DhcpDdns": {
+ "control-socket": {
+ "socket-name": "/tmp/kea-ddns-ctrl-socket",
+ "socket-type": "unix"
+ },
+ "dns-server-timeout": 1000,
+ "forward-ddns": {
+ "ddns-domains": [
+ {
+ "dns-servers": [
+ {
+ "hostname": "",
+ "ip-address": "172.16.1.1",
+ "port": 53
+ }
+ ],
+ "key-name": "d2.md5.key",
+ "name": "four.example.com.",
+ "user-context": {
+ "comment": "DdnsDomain example"
+ }
+ },
+ {
+ "dns-servers": [
+ {
+ "hostname": "",
+ "ip-address": "2001:db8:1::10",
+ "port": 7802
+ }
+ ],
+ "name": "six.example.com."
+ }
+ ]
+ },
+ "hooks-libraries": [
+ {
+ "library": "/opt/local/ddns-server-commands.so",
+ "parameters": {
+ "param1": "foo"
+ }
+ }
+ ],
+ "ip-address": "127.0.0.1",
+ "loggers": [
+ {
+ "debuglevel": 0,
+ "name": "kea-dhcp-ddns",
+ "output_options": [
+ {
+ "flush": true,
+ "output": "stdout",
+ "pattern": "%d [%c/%i] %m\n"
+ }
+ ],
+ "severity": "INFO"
+ }
+ ],
+ "ncr-format": "JSON",
+ "ncr-protocol": "UDP",
+ "port": 53001,
+ "reverse-ddns": {
+ "ddns-domains": [
+ {
+ "dns-servers": [
+ {
+ "hostname": "",
+ "ip-address": "172.16.1.1",
+ "port": 53001
+ },
+ {
+ "hostname": "",
+ "ip-address": "192.168.2.10",
+ "port": 53
+ }
+ ],
+ "key-name": "d2.sha1.key",
+ "name": "2.0.192.in-addr.arpa."
+ }
+ ]
+ },
+ "tsig-keys": [
+ {
+ "algorithm": "HMAC-MD5",
+ "digest-bits": 0,
+ "name": "d2.md5.key",
+ "secret": "LSWXnfkKZjdPJI5QxlpnfQ=="
+ },
+ {
+ "algorithm": "HMAC-SHA1",
+ "digest-bits": 0,
+ "name": "d2.sha1.key",
+ "secret": "hRrp29wzUv3uzSNRLlY68w=="
+ },
+ {
+ "algorithm": "HMAC-SHA512",
+ "digest-bits": 256,
+ "name": "d2.sha512.key",
+ "secret": "/4wklkm04jeH4anx2MKGJLcya+ZLHldL5d6mK+4q6UXQP7KJ9mS2QG29hh0SJR4LA0ikxNJTUMvir42gLx6fGQ=="
+ }
+ ],
+ "user-context": {
+ "version": 1
+ }
+ }
+}