diff options
Diffstat (limited to '')
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='[0;31m'; \ + grn='[0;32m'; \ + lgn='[1;32m'; \ + blu='[1;34m'; \ + mgn='[0;35m'; \ + brg='[1m'; \ + std='[m'; \ + 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 + } + } +} |