diff options
Diffstat (limited to 'src/bin/agent/tests')
21 files changed, 5939 insertions, 0 deletions
diff --git a/src/bin/agent/tests/Makefile.am b/src/bin/agent/tests/Makefile.am new file mode 100644 index 0000000..100d246 --- /dev/null +++ b/src/bin/agent/tests/Makefile.am @@ -0,0 +1,118 @@ +SUBDIRS = . + +# Add to the tarball: +EXTRA_DIST = testdata/get_config.json +EXTRA_DIST += testdata/hiddenp +EXTRA_DIST += testdata/hiddens +EXTRA_DIST += testdata/hiddenu + +TESTS_ENVIRONMENT = $(LIBTOOL) --mode=execute $(VALGRIND_COMMAND) + +# Shell tests +SHTESTS = ca_process_tests.sh + +# As with every file generated by ./configure, clean them up when running +# "make distclean", but not on "make clean". +DISTCLEANFILES = $(SHTESTS) +DISTCLEANFILES += test_basic_auth_libraries.h +DISTCLEANFILES += test_callout_libraries.h +DISTCLEANFILES += test_data_files_config.h + +if HAVE_GTEST + +# C++ tests +PROGRAM_TESTS = ca_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) $(CRYPTO_CFLAGS) $(CRYPTO_INCLUDES) +AM_CPPFLAGS += -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/bin/agent/tests\" +AM_CPPFLAGS += -DINSTALL_PROG=\"$(abs_top_srcdir)/install-sh\" +AM_CPPFLAGS += -DCFG_EXAMPLES=\"$(abs_top_srcdir)/doc/examples/agent\" +AM_CPPFLAGS += -DSYNTAX_FILE=\"$(abs_srcdir)/../agent_parser.yy\" + +AM_CXXFLAGS = $(KEA_CXXFLAGS) + +if USE_STATIC_LINK +AM_LDFLAGS = -static +endif + +ca_unittests_SOURCES = ca_cfg_mgr_unittests.cc +ca_unittests_SOURCES += ca_command_mgr_unittests.cc +ca_unittests_SOURCES += ca_controller_unittests.cc +ca_unittests_SOURCES += ca_process_unittests.cc +ca_unittests_SOURCES += ca_response_creator_unittests.cc +ca_unittests_SOURCES += ca_response_creator_factory_unittests.cc +ca_unittests_SOURCES += ca_unittests.cc +ca_unittests_SOURCES += parser_unittests.cc +ca_unittests_SOURCES += get_config_unittest.cc + +ca_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES) +ca_unittests_LDFLAGS = $(AM_LDFLAGS) $(CRYPTO_LDFLAGS) $(GTEST_LDFLAGS) + +ca_unittests_LDADD = $(top_builddir)/src/bin/agent/libagent.la +ca_unittests_LDADD += $(top_builddir)/src/lib/process/testutils/libprocesstest.la +ca_unittests_LDADD += $(top_builddir)/src/lib/process/libkea-process.la +ca_unittests_LDADD += $(top_builddir)/src/lib/eval/libkea-eval.la +ca_unittests_LDADD += $(top_builddir)/src/lib/dhcp_ddns/libkea-dhcp_ddns.la +ca_unittests_LDADD += $(top_builddir)/src/lib/stats/libkea-stats.la +ca_unittests_LDADD += $(top_builddir)/src/lib/config/libkea-cfgclient.la +ca_unittests_LDADD += $(top_builddir)/src/lib/http/libkea-http.la +ca_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la +ca_unittests_LDADD += $(top_builddir)/src/lib/hooks/libkea-hooks.la +ca_unittests_LDADD += $(top_builddir)/src/lib/testutils/libkea-testutils.la +ca_unittests_LDADD += $(top_builddir)/src/lib/cc/libkea-cc.la +ca_unittests_LDADD += $(top_builddir)/src/lib/asiolink/testutils/libasiolinktest.la +ca_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la +ca_unittests_LDADD += $(top_builddir)/src/lib/dns/libkea-dns++.la +ca_unittests_LDADD += $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la +ca_unittests_LDADD += $(top_builddir)/src/lib/log/libkea-log.la +ca_unittests_LDADD += $(top_builddir)/src/lib/util/libkea-util.la +ca_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la +ca_unittests_LDADD += $(LOG4CPLUS_LIBS) $(CRYPTO_LIBS) +ca_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 basic HTTP auth as a callout library +libbasicauth_la_SOURCES = basic_auth_library.cc +libbasicauth_la_CXXFLAGS = $(AM_CXXFLAGS) +libbasicauth_la_CPPFLAGS = $(AM_CPPFLAGS) +libbasicauth_la_LIBADD = $(top_builddir)/src/lib/http/libkea-http.la +libbasicauth_la_LIBADD += $(top_builddir)/src/lib/hooks/libkea-hooks.la +libbasicauth_la_LIBADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la +libbasicauth_la_LIBADD += $(top_builddir)/src/lib/cc/libkea-cc.la +libbasicauth_la_LIBADD += $(top_builddir)/src/lib/log/libkea-log.la +libbasicauth_la_LIBADD += $(top_builddir)/src/lib/util/libkea-util.la +libbasicauth_la_LIBADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la +libbasicauth_la_LIBADD += $(LOG4CPLUS_LIBS) $(BOOST_LIBS) +libbasicauth_la_LDFLAGS = -avoid-version -export-dynamic -module -rpath /nowhere + +nodist_ca_unittests_SOURCES = test_data_files_config.h +nodist_ca_unittests_SOURCES += test_basic_auth_libraries.h +nodist_ca_unittests_SOURCES += test_callout_libraries.h + +# Run C++ tests on "make check". +TESTS = $(PROGRAM_TESTS) + +# Run shell tests on "make check". +check_SCRIPTS = $(SHTESTS) +TESTS += $(SHTESTS) + +# Don't install test libraries. +noinst_LTLIBRARIES = libcallout.la libbasicauth.la + +# Don't install C++ tests. +noinst_PROGRAMS = $(PROGRAM_TESTS) + +endif + +# Don't install shell tests. +noinst_SCRIPTS = $(SHTESTS) diff --git a/src/bin/agent/tests/Makefile.in b/src/bin/agent/tests/Makefile.in new file mode 100644 index 0000000..2471ef5 --- /dev/null +++ b/src/bin/agent/tests/Makefile.in @@ -0,0 +1,1340 @@ +# 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@ +@HAVE_GTEST_TRUE@TESTS = $(am__EXEEXT_1) $(SHTESTS) +@HAVE_GTEST_TRUE@noinst_PROGRAMS = $(am__EXEEXT_1) +subdir = src/bin/agent/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_cpp20.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_netconf.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 = ca_process_tests.sh test_basic_auth_libraries.h \ + test_callout_libraries.h test_data_files_config.h +CONFIG_CLEAN_VPATH_FILES = +@HAVE_GTEST_TRUE@am__EXEEXT_1 = ca_unittests$(EXEEXT) +PROGRAMS = $(noinst_PROGRAMS) +LTLIBRARIES = $(noinst_LTLIBRARIES) +am__DEPENDENCIES_1 = +@HAVE_GTEST_TRUE@libbasicauth_la_DEPENDENCIES = \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/http/libkea-http.la \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/hooks/libkea-hooks.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/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__libbasicauth_la_SOURCES_DIST = basic_auth_library.cc +@HAVE_GTEST_TRUE@am_libbasicauth_la_OBJECTS = \ +@HAVE_GTEST_TRUE@ libbasicauth_la-basic_auth_library.lo +libbasicauth_la_OBJECTS = $(am_libbasicauth_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 = +libbasicauth_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \ + $(libbasicauth_la_CXXFLAGS) $(CXXFLAGS) \ + $(libbasicauth_la_LDFLAGS) $(LDFLAGS) -o $@ +@HAVE_GTEST_TRUE@am_libbasicauth_la_rpath = +@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) +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__ca_unittests_SOURCES_DIST = ca_cfg_mgr_unittests.cc \ + ca_command_mgr_unittests.cc ca_controller_unittests.cc \ + ca_process_unittests.cc ca_response_creator_unittests.cc \ + ca_response_creator_factory_unittests.cc ca_unittests.cc \ + parser_unittests.cc get_config_unittest.cc +@HAVE_GTEST_TRUE@am_ca_unittests_OBJECTS = \ +@HAVE_GTEST_TRUE@ ca_unittests-ca_cfg_mgr_unittests.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ ca_unittests-ca_command_mgr_unittests.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ ca_unittests-ca_controller_unittests.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ ca_unittests-ca_process_unittests.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ ca_unittests-ca_response_creator_unittests.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ ca_unittests-ca_response_creator_factory_unittests.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ ca_unittests-ca_unittests.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ ca_unittests-parser_unittests.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ ca_unittests-get_config_unittest.$(OBJEXT) +nodist_ca_unittests_OBJECTS = +ca_unittests_OBJECTS = $(am_ca_unittests_OBJECTS) \ + $(nodist_ca_unittests_OBJECTS) +@HAVE_GTEST_TRUE@ca_unittests_DEPENDENCIES = \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/bin/agent/libagent.la \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/process/testutils/libprocesstest.la \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/process/libkea-process.la \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/eval/libkea-eval.la \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/dhcp_ddns/libkea-dhcp_ddns.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/hooks/libkea-hooks.la \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/testutils/libkea-testutils.la \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cc/libkea-cc.la \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/asiolink/testutils/libasiolinktest.la \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/asiolink/libkea-asiolink.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/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) +ca_unittests_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(AM_CXXFLAGS) \ + $(CXXFLAGS) $(ca_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)/ca_unittests-ca_cfg_mgr_unittests.Po \ + ./$(DEPDIR)/ca_unittests-ca_command_mgr_unittests.Po \ + ./$(DEPDIR)/ca_unittests-ca_controller_unittests.Po \ + ./$(DEPDIR)/ca_unittests-ca_process_unittests.Po \ + ./$(DEPDIR)/ca_unittests-ca_response_creator_factory_unittests.Po \ + ./$(DEPDIR)/ca_unittests-ca_response_creator_unittests.Po \ + ./$(DEPDIR)/ca_unittests-ca_unittests.Po \ + ./$(DEPDIR)/ca_unittests-get_config_unittest.Po \ + ./$(DEPDIR)/ca_unittests-parser_unittests.Po \ + ./$(DEPDIR)/libbasicauth_la-basic_auth_library.Plo \ + ./$(DEPDIR)/libcallout_la-callout_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 = $(libbasicauth_la_SOURCES) $(libcallout_la_SOURCES) \ + $(ca_unittests_SOURCES) $(nodist_ca_unittests_SOURCES) +DIST_SOURCES = $(am__libbasicauth_la_SOURCES_DIST) \ + $(am__libcallout_la_SOURCES_DIST) \ + $(am__ca_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; \ +} +DIST_SUBDIRS = $(SUBDIRS) +am__DIST_COMMON = $(srcdir)/Makefile.in \ + $(srcdir)/ca_process_tests.sh.in \ + $(srcdir)/test_basic_auth_libraries.h.in \ + $(srcdir)/test_callout_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_GSSAPI_CONFIGURE_FLAG = @DISTCHECK_GSSAPI_CONFIGURE_FLAG@ +DISTCHECK_GTEST_CONFIGURE_FLAG = @DISTCHECK_GTEST_CONFIGURE_FLAG@ +DISTCHECK_KEA_SHELL_CONFIGURE_FLAG = @DISTCHECK_KEA_SHELL_CONFIGURE_FLAG@ +DISTCHECK_LIBYANGCPP_CONFIGURE_FLAG = @DISTCHECK_LIBYANGCPP_CONFIGURE_FLAG@ +DISTCHECK_LIBYANG_CONFIGURE_FLAG = @DISTCHECK_LIBYANG_CONFIGURE_FLAG@ +DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG = @DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG@ +DISTCHECK_MYSQL_CONFIGURE_FLAG = @DISTCHECK_MYSQL_CONFIGURE_FLAG@ +DISTCHECK_PERFDHCP_CONFIGURE_FLAG = @DISTCHECK_PERFDHCP_CONFIGURE_FLAG@ +DISTCHECK_PGSQL_CONFIGURE_FLAG = @DISTCHECK_PGSQL_CONFIGURE_FLAG@ +DISTCHECK_PREMIUM_CONFIGURE_FLAG = @DISTCHECK_PREMIUM_CONFIGURE_FLAG@ +DISTCHECK_SYSREPOCPP_CONFIGURE_FLAG = @DISTCHECK_SYSREPOCPP_CONFIGURE_FLAG@ +DISTCHECK_SYSREPO_CONFIGURE_FLAG = @DISTCHECK_SYSREPO_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_NETCONF = @HAVE_NETCONF@ +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@ +LIBYANGCPP_CPPFLAGS = @LIBYANGCPP_CPPFLAGS@ +LIBYANGCPP_INCLUDEDIR = @LIBYANGCPP_INCLUDEDIR@ +LIBYANGCPP_LIBS = @LIBYANGCPP_LIBS@ +LIBYANGCPP_PREFIX = @LIBYANGCPP_PREFIX@ +LIBYANGCPP_VERSION = @LIBYANGCPP_VERSION@ +LIBYANG_CPPFLAGS = @LIBYANG_CPPFLAGS@ +LIBYANG_INCLUDEDIR = @LIBYANG_INCLUDEDIR@ +LIBYANG_LIBS = @LIBYANG_LIBS@ +LIBYANG_PREFIX = @LIBYANG_PREFIX@ +LIBYANG_VERSION = @LIBYANG_VERSION@ +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_PLUGINS_PATH = @SR_PLUGINS_PATH@ +SR_REPO_PATH = @SR_REPO_PATH@ +STRIP = @STRIP@ +SYSREPOCPP_CPPFLAGS = @SYSREPOCPP_CPPFLAGS@ +SYSREPOCPP_INCLUDEDIR = @SYSREPOCPP_INCLUDEDIR@ +SYSREPOCPP_LIBS = @SYSREPOCPP_LIBS@ +SYSREPOCPP_PREFIX = @SYSREPOCPP_PREFIX@ +SYSREPOCPP_VERSION = @SYSREPOCPP_VERSION@ +SYSREPO_CPPFLAGS = @SYSREPO_CPPFLAGS@ +SYSREPO_INCLUDEDIR = @SYSREPO_INCLUDEDIR@ +SYSREPO_LIBS = @SYSREPO_LIBS@ +SYSREPO_PREFIX = @SYSREPO_PREFIX@ +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/get_config.json testdata/hiddenp \ + testdata/hiddens testdata/hiddenu +TESTS_ENVIRONMENT = $(LIBTOOL) --mode=execute $(VALGRIND_COMMAND) + +# Shell tests +SHTESTS = ca_process_tests.sh + +# As with every file generated by ./configure, clean them up when running +# "make distclean", but not on "make clean". +DISTCLEANFILES = $(SHTESTS) test_basic_auth_libraries.h \ + test_callout_libraries.h test_data_files_config.h + +# C++ tests +@HAVE_GTEST_TRUE@PROGRAM_TESTS = ca_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@ $(CRYPTO_CFLAGS) $(CRYPTO_INCLUDES) \ +@HAVE_GTEST_TRUE@ -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/bin/agent/tests\" \ +@HAVE_GTEST_TRUE@ -DINSTALL_PROG=\"$(abs_top_srcdir)/install-sh\" \ +@HAVE_GTEST_TRUE@ -DCFG_EXAMPLES=\"$(abs_top_srcdir)/doc/examples/agent\" \ +@HAVE_GTEST_TRUE@ -DSYNTAX_FILE=\"$(abs_srcdir)/../agent_parser.yy\" +@HAVE_GTEST_TRUE@AM_CXXFLAGS = $(KEA_CXXFLAGS) +@HAVE_GTEST_TRUE@@USE_STATIC_LINK_TRUE@AM_LDFLAGS = -static +@HAVE_GTEST_TRUE@ca_unittests_SOURCES = ca_cfg_mgr_unittests.cc \ +@HAVE_GTEST_TRUE@ ca_command_mgr_unittests.cc \ +@HAVE_GTEST_TRUE@ ca_controller_unittests.cc \ +@HAVE_GTEST_TRUE@ ca_process_unittests.cc \ +@HAVE_GTEST_TRUE@ ca_response_creator_unittests.cc \ +@HAVE_GTEST_TRUE@ ca_response_creator_factory_unittests.cc \ +@HAVE_GTEST_TRUE@ ca_unittests.cc parser_unittests.cc \ +@HAVE_GTEST_TRUE@ get_config_unittest.cc +@HAVE_GTEST_TRUE@ca_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES) +@HAVE_GTEST_TRUE@ca_unittests_LDFLAGS = $(AM_LDFLAGS) $(CRYPTO_LDFLAGS) $(GTEST_LDFLAGS) +@HAVE_GTEST_TRUE@ca_unittests_LDADD = \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/bin/agent/libagent.la \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/process/testutils/libprocesstest.la \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/process/libkea-process.la \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/eval/libkea-eval.la \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/dhcp_ddns/libkea-dhcp_ddns.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/hooks/libkea-hooks.la \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/testutils/libkea-testutils.la \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cc/libkea-cc.la \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/asiolink/testutils/libasiolinktest.la \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/asiolink/libkea-asiolink.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/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 basic HTTP auth as a callout library +@HAVE_GTEST_TRUE@libbasicauth_la_SOURCES = basic_auth_library.cc +@HAVE_GTEST_TRUE@libbasicauth_la_CXXFLAGS = $(AM_CXXFLAGS) +@HAVE_GTEST_TRUE@libbasicauth_la_CPPFLAGS = $(AM_CPPFLAGS) +@HAVE_GTEST_TRUE@libbasicauth_la_LIBADD = \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/http/libkea-http.la \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/hooks/libkea-hooks.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/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@libbasicauth_la_LDFLAGS = -avoid-version -export-dynamic -module -rpath /nowhere +@HAVE_GTEST_TRUE@nodist_ca_unittests_SOURCES = \ +@HAVE_GTEST_TRUE@ test_data_files_config.h \ +@HAVE_GTEST_TRUE@ test_basic_auth_libraries.h \ +@HAVE_GTEST_TRUE@ test_callout_libraries.h + +# Run shell tests on "make check". +@HAVE_GTEST_TRUE@check_SCRIPTS = $(SHTESTS) + +# Don't install test libraries. +@HAVE_GTEST_TRUE@noinst_LTLIBRARIES = libcallout.la libbasicauth.la + +# Don't install shell tests. +noinst_SCRIPTS = $(SHTESTS) +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/agent/tests/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign src/bin/agent/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): +ca_process_tests.sh: $(top_builddir)/config.status $(srcdir)/ca_process_tests.sh.in + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ +test_basic_auth_libraries.h: $(top_builddir)/config.status $(srcdir)/test_basic_auth_libraries.h.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_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}; \ + } + +libbasicauth.la: $(libbasicauth_la_OBJECTS) $(libbasicauth_la_DEPENDENCIES) $(EXTRA_libbasicauth_la_DEPENDENCIES) + $(AM_V_CXXLD)$(libbasicauth_la_LINK) $(am_libbasicauth_la_rpath) $(libbasicauth_la_OBJECTS) $(libbasicauth_la_LIBADD) $(LIBS) + +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) + +ca_unittests$(EXEEXT): $(ca_unittests_OBJECTS) $(ca_unittests_DEPENDENCIES) $(EXTRA_ca_unittests_DEPENDENCIES) + @rm -f ca_unittests$(EXEEXT) + $(AM_V_CXXLD)$(ca_unittests_LINK) $(ca_unittests_OBJECTS) $(ca_unittests_LDADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ca_unittests-ca_cfg_mgr_unittests.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ca_unittests-ca_command_mgr_unittests.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ca_unittests-ca_controller_unittests.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ca_unittests-ca_process_unittests.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ca_unittests-ca_response_creator_factory_unittests.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ca_unittests-ca_response_creator_unittests.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ca_unittests-ca_unittests.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ca_unittests-get_config_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ca_unittests-parser_unittests.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libbasicauth_la-basic_auth_library.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libcallout_la-callout_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 $@ $< + +libbasicauth_la-basic_auth_library.lo: basic_auth_library.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libbasicauth_la_CPPFLAGS) $(CPPFLAGS) $(libbasicauth_la_CXXFLAGS) $(CXXFLAGS) -MT libbasicauth_la-basic_auth_library.lo -MD -MP -MF $(DEPDIR)/libbasicauth_la-basic_auth_library.Tpo -c -o libbasicauth_la-basic_auth_library.lo `test -f 'basic_auth_library.cc' || echo '$(srcdir)/'`basic_auth_library.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libbasicauth_la-basic_auth_library.Tpo $(DEPDIR)/libbasicauth_la-basic_auth_library.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='basic_auth_library.cc' object='libbasicauth_la-basic_auth_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) $(libbasicauth_la_CPPFLAGS) $(CPPFLAGS) $(libbasicauth_la_CXXFLAGS) $(CXXFLAGS) -c -o libbasicauth_la-basic_auth_library.lo `test -f 'basic_auth_library.cc' || echo '$(srcdir)/'`basic_auth_library.cc + +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 + +ca_unittests-ca_cfg_mgr_unittests.o: ca_cfg_mgr_unittests.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(ca_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT ca_unittests-ca_cfg_mgr_unittests.o -MD -MP -MF $(DEPDIR)/ca_unittests-ca_cfg_mgr_unittests.Tpo -c -o ca_unittests-ca_cfg_mgr_unittests.o `test -f 'ca_cfg_mgr_unittests.cc' || echo '$(srcdir)/'`ca_cfg_mgr_unittests.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/ca_unittests-ca_cfg_mgr_unittests.Tpo $(DEPDIR)/ca_unittests-ca_cfg_mgr_unittests.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='ca_cfg_mgr_unittests.cc' object='ca_unittests-ca_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) $(ca_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o ca_unittests-ca_cfg_mgr_unittests.o `test -f 'ca_cfg_mgr_unittests.cc' || echo '$(srcdir)/'`ca_cfg_mgr_unittests.cc + +ca_unittests-ca_cfg_mgr_unittests.obj: ca_cfg_mgr_unittests.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(ca_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT ca_unittests-ca_cfg_mgr_unittests.obj -MD -MP -MF $(DEPDIR)/ca_unittests-ca_cfg_mgr_unittests.Tpo -c -o ca_unittests-ca_cfg_mgr_unittests.obj `if test -f 'ca_cfg_mgr_unittests.cc'; then $(CYGPATH_W) 'ca_cfg_mgr_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/ca_cfg_mgr_unittests.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/ca_unittests-ca_cfg_mgr_unittests.Tpo $(DEPDIR)/ca_unittests-ca_cfg_mgr_unittests.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='ca_cfg_mgr_unittests.cc' object='ca_unittests-ca_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) $(ca_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o ca_unittests-ca_cfg_mgr_unittests.obj `if test -f 'ca_cfg_mgr_unittests.cc'; then $(CYGPATH_W) 'ca_cfg_mgr_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/ca_cfg_mgr_unittests.cc'; fi` + +ca_unittests-ca_command_mgr_unittests.o: ca_command_mgr_unittests.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(ca_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT ca_unittests-ca_command_mgr_unittests.o -MD -MP -MF $(DEPDIR)/ca_unittests-ca_command_mgr_unittests.Tpo -c -o ca_unittests-ca_command_mgr_unittests.o `test -f 'ca_command_mgr_unittests.cc' || echo '$(srcdir)/'`ca_command_mgr_unittests.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/ca_unittests-ca_command_mgr_unittests.Tpo $(DEPDIR)/ca_unittests-ca_command_mgr_unittests.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='ca_command_mgr_unittests.cc' object='ca_unittests-ca_command_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) $(ca_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o ca_unittests-ca_command_mgr_unittests.o `test -f 'ca_command_mgr_unittests.cc' || echo '$(srcdir)/'`ca_command_mgr_unittests.cc + +ca_unittests-ca_command_mgr_unittests.obj: ca_command_mgr_unittests.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(ca_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT ca_unittests-ca_command_mgr_unittests.obj -MD -MP -MF $(DEPDIR)/ca_unittests-ca_command_mgr_unittests.Tpo -c -o ca_unittests-ca_command_mgr_unittests.obj `if test -f 'ca_command_mgr_unittests.cc'; then $(CYGPATH_W) 'ca_command_mgr_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/ca_command_mgr_unittests.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/ca_unittests-ca_command_mgr_unittests.Tpo $(DEPDIR)/ca_unittests-ca_command_mgr_unittests.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='ca_command_mgr_unittests.cc' object='ca_unittests-ca_command_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) $(ca_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o ca_unittests-ca_command_mgr_unittests.obj `if test -f 'ca_command_mgr_unittests.cc'; then $(CYGPATH_W) 'ca_command_mgr_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/ca_command_mgr_unittests.cc'; fi` + +ca_unittests-ca_controller_unittests.o: ca_controller_unittests.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(ca_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT ca_unittests-ca_controller_unittests.o -MD -MP -MF $(DEPDIR)/ca_unittests-ca_controller_unittests.Tpo -c -o ca_unittests-ca_controller_unittests.o `test -f 'ca_controller_unittests.cc' || echo '$(srcdir)/'`ca_controller_unittests.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/ca_unittests-ca_controller_unittests.Tpo $(DEPDIR)/ca_unittests-ca_controller_unittests.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='ca_controller_unittests.cc' object='ca_unittests-ca_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) $(ca_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o ca_unittests-ca_controller_unittests.o `test -f 'ca_controller_unittests.cc' || echo '$(srcdir)/'`ca_controller_unittests.cc + +ca_unittests-ca_controller_unittests.obj: ca_controller_unittests.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(ca_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT ca_unittests-ca_controller_unittests.obj -MD -MP -MF $(DEPDIR)/ca_unittests-ca_controller_unittests.Tpo -c -o ca_unittests-ca_controller_unittests.obj `if test -f 'ca_controller_unittests.cc'; then $(CYGPATH_W) 'ca_controller_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/ca_controller_unittests.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/ca_unittests-ca_controller_unittests.Tpo $(DEPDIR)/ca_unittests-ca_controller_unittests.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='ca_controller_unittests.cc' object='ca_unittests-ca_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) $(ca_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o ca_unittests-ca_controller_unittests.obj `if test -f 'ca_controller_unittests.cc'; then $(CYGPATH_W) 'ca_controller_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/ca_controller_unittests.cc'; fi` + +ca_unittests-ca_process_unittests.o: ca_process_unittests.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(ca_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT ca_unittests-ca_process_unittests.o -MD -MP -MF $(DEPDIR)/ca_unittests-ca_process_unittests.Tpo -c -o ca_unittests-ca_process_unittests.o `test -f 'ca_process_unittests.cc' || echo '$(srcdir)/'`ca_process_unittests.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/ca_unittests-ca_process_unittests.Tpo $(DEPDIR)/ca_unittests-ca_process_unittests.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='ca_process_unittests.cc' object='ca_unittests-ca_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) $(ca_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o ca_unittests-ca_process_unittests.o `test -f 'ca_process_unittests.cc' || echo '$(srcdir)/'`ca_process_unittests.cc + +ca_unittests-ca_process_unittests.obj: ca_process_unittests.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(ca_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT ca_unittests-ca_process_unittests.obj -MD -MP -MF $(DEPDIR)/ca_unittests-ca_process_unittests.Tpo -c -o ca_unittests-ca_process_unittests.obj `if test -f 'ca_process_unittests.cc'; then $(CYGPATH_W) 'ca_process_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/ca_process_unittests.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/ca_unittests-ca_process_unittests.Tpo $(DEPDIR)/ca_unittests-ca_process_unittests.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='ca_process_unittests.cc' object='ca_unittests-ca_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) $(ca_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o ca_unittests-ca_process_unittests.obj `if test -f 'ca_process_unittests.cc'; then $(CYGPATH_W) 'ca_process_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/ca_process_unittests.cc'; fi` + +ca_unittests-ca_response_creator_unittests.o: ca_response_creator_unittests.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(ca_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT ca_unittests-ca_response_creator_unittests.o -MD -MP -MF $(DEPDIR)/ca_unittests-ca_response_creator_unittests.Tpo -c -o ca_unittests-ca_response_creator_unittests.o `test -f 'ca_response_creator_unittests.cc' || echo '$(srcdir)/'`ca_response_creator_unittests.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/ca_unittests-ca_response_creator_unittests.Tpo $(DEPDIR)/ca_unittests-ca_response_creator_unittests.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='ca_response_creator_unittests.cc' object='ca_unittests-ca_response_creator_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) $(ca_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o ca_unittests-ca_response_creator_unittests.o `test -f 'ca_response_creator_unittests.cc' || echo '$(srcdir)/'`ca_response_creator_unittests.cc + +ca_unittests-ca_response_creator_unittests.obj: ca_response_creator_unittests.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(ca_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT ca_unittests-ca_response_creator_unittests.obj -MD -MP -MF $(DEPDIR)/ca_unittests-ca_response_creator_unittests.Tpo -c -o ca_unittests-ca_response_creator_unittests.obj `if test -f 'ca_response_creator_unittests.cc'; then $(CYGPATH_W) 'ca_response_creator_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/ca_response_creator_unittests.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/ca_unittests-ca_response_creator_unittests.Tpo $(DEPDIR)/ca_unittests-ca_response_creator_unittests.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='ca_response_creator_unittests.cc' object='ca_unittests-ca_response_creator_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) $(ca_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o ca_unittests-ca_response_creator_unittests.obj `if test -f 'ca_response_creator_unittests.cc'; then $(CYGPATH_W) 'ca_response_creator_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/ca_response_creator_unittests.cc'; fi` + +ca_unittests-ca_response_creator_factory_unittests.o: ca_response_creator_factory_unittests.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(ca_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT ca_unittests-ca_response_creator_factory_unittests.o -MD -MP -MF $(DEPDIR)/ca_unittests-ca_response_creator_factory_unittests.Tpo -c -o ca_unittests-ca_response_creator_factory_unittests.o `test -f 'ca_response_creator_factory_unittests.cc' || echo '$(srcdir)/'`ca_response_creator_factory_unittests.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/ca_unittests-ca_response_creator_factory_unittests.Tpo $(DEPDIR)/ca_unittests-ca_response_creator_factory_unittests.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='ca_response_creator_factory_unittests.cc' object='ca_unittests-ca_response_creator_factory_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) $(ca_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o ca_unittests-ca_response_creator_factory_unittests.o `test -f 'ca_response_creator_factory_unittests.cc' || echo '$(srcdir)/'`ca_response_creator_factory_unittests.cc + +ca_unittests-ca_response_creator_factory_unittests.obj: ca_response_creator_factory_unittests.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(ca_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT ca_unittests-ca_response_creator_factory_unittests.obj -MD -MP -MF $(DEPDIR)/ca_unittests-ca_response_creator_factory_unittests.Tpo -c -o ca_unittests-ca_response_creator_factory_unittests.obj `if test -f 'ca_response_creator_factory_unittests.cc'; then $(CYGPATH_W) 'ca_response_creator_factory_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/ca_response_creator_factory_unittests.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/ca_unittests-ca_response_creator_factory_unittests.Tpo $(DEPDIR)/ca_unittests-ca_response_creator_factory_unittests.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='ca_response_creator_factory_unittests.cc' object='ca_unittests-ca_response_creator_factory_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) $(ca_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o ca_unittests-ca_response_creator_factory_unittests.obj `if test -f 'ca_response_creator_factory_unittests.cc'; then $(CYGPATH_W) 'ca_response_creator_factory_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/ca_response_creator_factory_unittests.cc'; fi` + +ca_unittests-ca_unittests.o: ca_unittests.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(ca_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT ca_unittests-ca_unittests.o -MD -MP -MF $(DEPDIR)/ca_unittests-ca_unittests.Tpo -c -o ca_unittests-ca_unittests.o `test -f 'ca_unittests.cc' || echo '$(srcdir)/'`ca_unittests.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/ca_unittests-ca_unittests.Tpo $(DEPDIR)/ca_unittests-ca_unittests.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='ca_unittests.cc' object='ca_unittests-ca_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) $(ca_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o ca_unittests-ca_unittests.o `test -f 'ca_unittests.cc' || echo '$(srcdir)/'`ca_unittests.cc + +ca_unittests-ca_unittests.obj: ca_unittests.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(ca_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT ca_unittests-ca_unittests.obj -MD -MP -MF $(DEPDIR)/ca_unittests-ca_unittests.Tpo -c -o ca_unittests-ca_unittests.obj `if test -f 'ca_unittests.cc'; then $(CYGPATH_W) 'ca_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/ca_unittests.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/ca_unittests-ca_unittests.Tpo $(DEPDIR)/ca_unittests-ca_unittests.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='ca_unittests.cc' object='ca_unittests-ca_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) $(ca_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o ca_unittests-ca_unittests.obj `if test -f 'ca_unittests.cc'; then $(CYGPATH_W) 'ca_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/ca_unittests.cc'; fi` + +ca_unittests-parser_unittests.o: parser_unittests.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(ca_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT ca_unittests-parser_unittests.o -MD -MP -MF $(DEPDIR)/ca_unittests-parser_unittests.Tpo -c -o ca_unittests-parser_unittests.o `test -f 'parser_unittests.cc' || echo '$(srcdir)/'`parser_unittests.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/ca_unittests-parser_unittests.Tpo $(DEPDIR)/ca_unittests-parser_unittests.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='parser_unittests.cc' object='ca_unittests-parser_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) $(ca_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o ca_unittests-parser_unittests.o `test -f 'parser_unittests.cc' || echo '$(srcdir)/'`parser_unittests.cc + +ca_unittests-parser_unittests.obj: parser_unittests.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(ca_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT ca_unittests-parser_unittests.obj -MD -MP -MF $(DEPDIR)/ca_unittests-parser_unittests.Tpo -c -o ca_unittests-parser_unittests.obj `if test -f 'parser_unittests.cc'; then $(CYGPATH_W) 'parser_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/parser_unittests.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/ca_unittests-parser_unittests.Tpo $(DEPDIR)/ca_unittests-parser_unittests.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='parser_unittests.cc' object='ca_unittests-parser_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) $(ca_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o ca_unittests-parser_unittests.obj `if test -f 'parser_unittests.cc'; then $(CYGPATH_W) 'parser_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/parser_unittests.cc'; fi` + +ca_unittests-get_config_unittest.o: get_config_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(ca_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT ca_unittests-get_config_unittest.o -MD -MP -MF $(DEPDIR)/ca_unittests-get_config_unittest.Tpo -c -o ca_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)/ca_unittests-get_config_unittest.Tpo $(DEPDIR)/ca_unittests-get_config_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='get_config_unittest.cc' object='ca_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) $(ca_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o ca_unittests-get_config_unittest.o `test -f 'get_config_unittest.cc' || echo '$(srcdir)/'`get_config_unittest.cc + +ca_unittests-get_config_unittest.obj: get_config_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(ca_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT ca_unittests-get_config_unittest.obj -MD -MP -MF $(DEPDIR)/ca_unittests-get_config_unittest.Tpo -c -o ca_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)/ca_unittests-get_config_unittest.Tpo $(DEPDIR)/ca_unittests-get_config_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='get_config_unittest.cc' object='ca_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) $(ca_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o ca_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` + +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)/ca_unittests-ca_cfg_mgr_unittests.Po + -rm -f ./$(DEPDIR)/ca_unittests-ca_command_mgr_unittests.Po + -rm -f ./$(DEPDIR)/ca_unittests-ca_controller_unittests.Po + -rm -f ./$(DEPDIR)/ca_unittests-ca_process_unittests.Po + -rm -f ./$(DEPDIR)/ca_unittests-ca_response_creator_factory_unittests.Po + -rm -f ./$(DEPDIR)/ca_unittests-ca_response_creator_unittests.Po + -rm -f ./$(DEPDIR)/ca_unittests-ca_unittests.Po + -rm -f ./$(DEPDIR)/ca_unittests-get_config_unittest.Po + -rm -f ./$(DEPDIR)/ca_unittests-parser_unittests.Po + -rm -f ./$(DEPDIR)/libbasicauth_la-basic_auth_library.Plo + -rm -f ./$(DEPDIR)/libcallout_la-callout_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)/ca_unittests-ca_cfg_mgr_unittests.Po + -rm -f ./$(DEPDIR)/ca_unittests-ca_command_mgr_unittests.Po + -rm -f ./$(DEPDIR)/ca_unittests-ca_controller_unittests.Po + -rm -f ./$(DEPDIR)/ca_unittests-ca_process_unittests.Po + -rm -f ./$(DEPDIR)/ca_unittests-ca_response_creator_factory_unittests.Po + -rm -f ./$(DEPDIR)/ca_unittests-ca_response_creator_unittests.Po + -rm -f ./$(DEPDIR)/ca_unittests-ca_unittests.Po + -rm -f ./$(DEPDIR)/ca_unittests-get_config_unittest.Po + -rm -f ./$(DEPDIR)/ca_unittests-parser_unittests.Po + -rm -f ./$(DEPDIR)/libbasicauth_la-basic_auth_library.Plo + -rm -f ./$(DEPDIR)/libcallout_la-callout_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/agent/tests/basic_auth_library.cc b/src/bin/agent/tests/basic_auth_library.cc new file mode 100644 index 0000000..924d656 --- /dev/null +++ b/src/bin/agent/tests/basic_auth_library.cc @@ -0,0 +1,289 @@ +// Copyright (C) 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/. + +/// @file +/// @brief Basic HTTP Authentication callout library + +#include <config.h> + +#include <cc/command_interpreter.h> +#include <cc/data.h> +#include <exceptions/exceptions.h> +#include <hooks/hooks.h> +#include <http/basic_auth_config.h> +#include <http/post_request_json.h> +#include <http/response_creator.h> +#include <boost/shared_ptr.hpp> +#include <boost/pointer_cast.hpp> + +using namespace isc::data; +using namespace isc::hooks; +using namespace isc::http; +using namespace isc; +using namespace std; + +namespace { + +/// @brief Response creator. +class ResponseCreator : public HttpResponseCreator { +public: + /// @brief Create a new request. + /// @return Pointer to the new instance of the @ref + /// isc::http::PostHttpRequestJson. + virtual HttpRequestPtr + createNewHttpRequest() const; + + /// @brief Create stock HTTP response. + /// + /// @param request Pointer to an object representing HTTP request. + /// @param status_code Status code of the response. + /// @return Pointer to an @ref isc::http::HttpResponseJson object + /// representing stock HTTP response. + virtual HttpResponsePtr + createStockHttpResponse(const HttpRequestPtr& request, + const HttpStatusCode& status_code) const; + + /// @brief Creates implementation specific HTTP response. + /// + /// @param request Pointer to an object representing HTTP request. + /// @return Pointer to an object representing HTTP response. + virtual HttpResponsePtr + createDynamicHttpResponse(HttpRequestPtr request); +}; + +HttpRequestPtr +ResponseCreator::createNewHttpRequest() const { + return (HttpRequestPtr(new PostHttpRequestJson())); +} + +HttpResponsePtr +ResponseCreator::createStockHttpResponse(const HttpRequestPtr& /*request*/, + const HttpStatusCode& status_code) const { + HttpVersion http_version(1, 1); + HttpResponsePtr response(new HttpResponseJson(http_version, status_code)); + response->finalize(); + return (response); +} + +HttpResponsePtr +ResponseCreator::createDynamicHttpResponse(HttpRequestPtr /*request*/) { + isc_throw(NotImplemented, "createDynamicHttpResponse should not be called"); +} + +/// @brief The type of shared pointers to response creators. +typedef boost::shared_ptr<ResponseCreator> ResponseCreatorPtr; + +/// @brief Implementation. +class Impl { +public: + + /// @brief Constructor. + Impl(); + + /// @brief Destructor. + ~Impl(); + + /// @brief Configure. + /// + /// @param config element pointer to client list. + void configure(ConstElementPtr config); + + /// @brief Basic HTTP authentication configuration. + BasicHttpAuthConfigPtr config_; + + /// @brief Response creator. + ResponseCreatorPtr creator_; +}; + +Impl::Impl() + : config_(new BasicHttpAuthConfig()), creator_(new ResponseCreator()) { +} + +Impl::~Impl() { +} + +void +Impl::configure(ConstElementPtr config) { + config_->parse(config); +} + +/// @brief The type of shared pointers to implementations. +typedef boost::shared_ptr<Impl> ImplPtr; + +/// @brief The implementation. +ImplPtr impl; + +extern "C" { + +// Framework functions. + +/// @brief returns Kea hooks version. +int +version() { + return (KEA_HOOKS_VERSION); +} + +/// @brief This function is called when the library is loaded. +/// +/// @param handle library handle. +/// @return 0 when initialization is successful, 1 otherwise. +int +load(LibraryHandle& handle) { +#ifdef USE_STATIC_LINK + hooksStaticLinkInit(); +#endif + try { + impl.reset(new Impl()); + ConstElementPtr config = handle.getParameter("config"); + impl->configure(config); + } catch (const std::exception& ex) { + std::cerr << "load error: " << ex.what() << std::endl; + return (1); + } + + return (0); +} + +/// @brief This function is called when the library is unloaded. +/// +/// @return always 0. +int +unload() { + impl.reset(); + return (0); +} + +// Callout functions. + +/// @brief This callout is called at the "auth" hook. +/// +/// @param handle CalloutHandle. +/// @return 0 upon success, non-zero otherwise. +int +auth(CalloutHandle& handle) { + // Sanity. + if (!impl) { + std::cerr << "no implementation" << std::endl; + return (0); + } + + // Get the parameters. + HttpRequestPtr request; + HttpResponseJsonPtr response; + handle.getArgument("request", request); + handle.getArgument("response", response); + + if (response) { + std::cerr << "response already set" << std::endl; + return (0); + } + if (!request) { + std::cerr << "no request" << std::endl; + return (0); + } + PostHttpRequestJsonPtr request_json = + boost::dynamic_pointer_cast<PostHttpRequestJson>(request); + if (!request_json) { + std::cerr << "no json post request" << std::endl; + return (0); + } + ConstElementPtr command = request_json->getBodyAsJson(); + if (!command) { + std::cerr << "no command" << std::endl; + return (0); + } + if (command->getType() != Element::map) { + std::cerr << "command is not a map" << std::endl; + return (0); + } + + // Modify request. + int extra = 0; + ConstElementPtr extra_elem = command->get("extra"); + ElementPtr mutable_command = boost::const_pointer_cast<Element>(command); + if (extra_elem) { + if (extra_elem->getType() == Element::integer) { + extra = extra_elem->intValue(); + } + mutable_command->remove("extra"); + request_json->setBodyAsJson(command); + } + handle.setContext("extra", extra); + + // Perform authentication. + response = impl->config_->checkAuth(*impl->creator_, request); + + // Set parameters. + handle.setArgument("request", request); + handle.setArgument("response", response); + return (0); +} + +/// @brief This callout is called at the "response" hook. +/// +/// @param handle CalloutHandle. +/// @return 0 upon success, non-zero otherwise. +int +response(CalloutHandle& handle) { + // Sanity. + if (!impl) { + std::cerr << "no implementation" << std::endl; + return (0); + } + + // Get the parameters. + HttpRequestPtr request; + HttpResponseJsonPtr response; + handle.getArgument("request", request); + handle.getArgument("response", response); + + if (!request) { + std::cerr << "no request" << std::endl; + return (0); + } + if (!response) { + std::cerr << "no response" << std::endl; + return (0); + } + + // Modify response. + ConstElementPtr body = response->getBodyAsJson(); + if (!body) { + std::cerr << "no body" << std::endl; + return (0); + } + if (body->getType() != Element::list) { + std::cerr << "body is not a list" << std::endl; + return (0); + } + if (body->size() < 1) { + std::cerr << "body is empty" << std::endl; + return (0); + } + ConstElementPtr answer = body->get(0); + if (!answer || (answer->getType() != Element::map)) { + std::cerr << "answer is not map" << std::endl; + return (0); + } + ElementPtr mutable_answer = boost::const_pointer_cast<Element>(answer); + try { + int extra = 0; + handle.getContext("extra", extra); + mutable_answer->set("got", Element::create(extra)); + } catch (const NoSuchCalloutContext&) { + std::cerr << "can't find 'extra' context\n"; + } catch (...) { + std::cerr << "getContext('extra') failed\n"; + } + response->setBodyAsJson(body); + + // Set parameters. + handle.setArgument("response", response); + return (0); +} + +} +} diff --git a/src/bin/agent/tests/ca_cfg_mgr_unittests.cc b/src/bin/agent/tests/ca_cfg_mgr_unittests.cc new file mode 100644 index 0000000..a7b40c5 --- /dev/null +++ b/src/bin/agent/tests/ca_cfg_mgr_unittests.cc @@ -0,0 +1,703 @@ +// Copyright (C) 2016-2022 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 <agent/ca_cfg_mgr.h> +#include <agent/parser_context.h> +#include <exceptions/exceptions.h> +#include <process/testutils/d_test_stubs.h> +#include <process/d_cfg_mgr.h> +#include <http/basic_auth_config.h> +#include <agent/tests/test_callout_libraries.h> +#include <agent/tests/test_data_files_config.h> +#include <boost/pointer_cast.hpp> +#include <boost/scoped_ptr.hpp> +#include <gtest/gtest.h> + +using namespace isc::agent; +using namespace isc::data; +using namespace isc::hooks; +using namespace isc::http; +using namespace isc::process; + +namespace { + +/// @brief Almost regular agent CfgMgr with internal parse method exposed. +class NakedAgentCfgMgr : public CtrlAgentCfgMgr { +public: + using CtrlAgentCfgMgr::parse; +}; + +// Tests construction of CtrlAgentCfgMgr class. +TEST(CtrlAgentCfgMgr, construction) { + boost::scoped_ptr<CtrlAgentCfgMgr> cfg_mgr; + + // Verify that configuration manager constructions without error. + ASSERT_NO_THROW(cfg_mgr.reset(new CtrlAgentCfgMgr())); + + // Verify that the context can be retrieved and is not null. + CtrlAgentCfgContextPtr context; + ASSERT_NO_THROW(context = cfg_mgr->getCtrlAgentCfgContext()); + EXPECT_TRUE(context); + + // Verify that the manager can be destructed without error. + EXPECT_NO_THROW(cfg_mgr.reset()); +} + +// Tests if getContext can be retrieved. +TEST(CtrlAgentCfgMgr, getContext) { + CtrlAgentCfgMgr cfg_mgr; + + CtrlAgentCfgContextPtr ctx; + ASSERT_NO_THROW(ctx = cfg_mgr.getCtrlAgentCfgContext()); + ASSERT_TRUE(ctx); +} + +// Tests if context can store and retrieve HTTP parameters +TEST(CtrlAgentCfgMgr, contextHttpParams) { + CtrlAgentCfgContext ctx; + + // Check http parameters + ctx.setHttpPort(12345); + EXPECT_EQ(12345, ctx.getHttpPort()); + + ctx.setHttpHost("alnitak"); + EXPECT_EQ("alnitak", ctx.getHttpHost()); +} + +// Tests if context can store and retrieve TLS parameters. +TEST(CtrlAgentCfgMgr, contextTlsParams) { + CtrlAgentCfgContext ctx; + + // Check TLS parameters + ctx.setTrustAnchor("my-ca"); + EXPECT_EQ("my-ca", ctx.getTrustAnchor()); + + ctx.setCertFile("my-cert"); + EXPECT_EQ("my-cert", ctx.getCertFile()); + + ctx.setKeyFile("my-key"); + EXPECT_EQ("my-key", ctx.getKeyFile()); + + EXPECT_TRUE(ctx.getCertRequired()); + ctx.setCertRequired(false); + EXPECT_FALSE(ctx.getCertRequired()); +} + +// Tests if context can store and retrieve control socket information. +TEST(CtrlAgentCfgMgr, contextSocketInfo) { + + CtrlAgentCfgContext ctx; + + // Check control socket parameters + // By default, there are no control sockets stored. + EXPECT_FALSE(ctx.getControlSocketInfo("d2")); + EXPECT_FALSE(ctx.getControlSocketInfo("dhcp4")); + EXPECT_FALSE(ctx.getControlSocketInfo("dhcp6")); + + ConstElementPtr socket1 = Element::fromJSON("{ \"socket-type\": \"unix\",\n" + " \"socket-name\": \"socket1\" }"); + ConstElementPtr socket2 = Element::fromJSON("{ \"socket-type\": \"unix\",\n" + " \"socket-name\": \"socket2\" }"); + ConstElementPtr socket3 = Element::fromJSON("{ \"socket-type\": \"unix\",\n" + " \"socket-name\": \"socket3\" }"); + // Ok, now set the control socket for D2 + EXPECT_NO_THROW(ctx.setControlSocketInfo(socket1, "d2")); + + // Now check the values returned + EXPECT_EQ(socket1, ctx.getControlSocketInfo("d2")); + EXPECT_FALSE(ctx.getControlSocketInfo("dhcp4")); + EXPECT_FALSE(ctx.getControlSocketInfo("dhcp6")); + + // Now set the v6 socket and sanity check again + EXPECT_NO_THROW(ctx.setControlSocketInfo(socket2, "dhcp6")); + + // Should be possible to retrieve two sockets. + EXPECT_EQ(socket1, ctx.getControlSocketInfo("d2")); + EXPECT_EQ(socket2, ctx.getControlSocketInfo("dhcp6")); + EXPECT_FALSE(ctx.getControlSocketInfo("dhcp4")); + + // Finally, set the third control socket. + EXPECT_NO_THROW(ctx.setControlSocketInfo(socket3, "dhcp4")); + EXPECT_EQ(socket1, ctx.getControlSocketInfo("d2")); + EXPECT_EQ(socket2, ctx.getControlSocketInfo("dhcp6")); + EXPECT_EQ(socket3, ctx.getControlSocketInfo("dhcp4")); +} + +// Tests if copied context retains all parameters. +TEST(CtrlAgentCfgMgr, contextSocketInfoCopy) { + + CtrlAgentCfgContext ctx; + + ConstElementPtr socket1 = Element::fromJSON("{ \"socket-type\": \"unix\",\n" + " \"socket-name\": \"socket1\" }"); + ConstElementPtr socket2 = Element::fromJSON("{ \"socket-type\": \"unix\",\n" + " \"socket-name\": \"socket2\" }"); + ConstElementPtr socket3 = Element::fromJSON("{ \"socket-type\": \"unix\",\n" + " \"socket-name\": \"socket3\" }"); + // Ok, now set the control sockets + EXPECT_NO_THROW(ctx.setControlSocketInfo(socket1, "d2")); + EXPECT_NO_THROW(ctx.setControlSocketInfo(socket2, "dhcp4")); + EXPECT_NO_THROW(ctx.setControlSocketInfo(socket3, "dhcp6")); + + EXPECT_NO_THROW(ctx.setHttpPort(12345)); + EXPECT_NO_THROW(ctx.setHttpHost("bellatrix")); + + HooksConfig& libs = ctx.getHooksConfig(); + string exp_name("testlib1.so"); + ConstElementPtr exp_param(new StringElement("myparam")); + libs.add(exp_name, exp_param); + + BasicHttpAuthConfigPtr auth(new BasicHttpAuthConfig()); + auth->setRealm("foobar"); + auth->add("foo", "", "bar", ""); + EXPECT_NO_THROW(ctx.setAuthConfig(auth)); + + // Make a copy. + ConfigPtr copy_base(ctx.clone()); + CtrlAgentCfgContextPtr copy = boost::dynamic_pointer_cast<CtrlAgentCfgContext>(copy_base); + ASSERT_TRUE(copy); + + // Now check the values returned + EXPECT_EQ(12345, copy->getHttpPort()); + EXPECT_EQ("bellatrix", copy->getHttpHost()); + + // Check socket info + ASSERT_TRUE(copy->getControlSocketInfo("d2")); + ASSERT_TRUE(copy->getControlSocketInfo("dhcp4")); + ASSERT_TRUE(copy->getControlSocketInfo("dhcp6")); + EXPECT_EQ(socket1->str(), copy->getControlSocketInfo("d2")->str()); + EXPECT_EQ(socket2->str(), copy->getControlSocketInfo("dhcp4")->str()); + EXPECT_EQ(socket3->str(), copy->getControlSocketInfo("dhcp6")->str()); + + // Check hook libs + const HookLibsCollection& libs2 = copy->getHooksConfig().get(); + ASSERT_EQ(1, libs2.size()); + EXPECT_EQ(exp_name, libs2[0].first); + ASSERT_TRUE(libs2[0].second); + EXPECT_EQ(exp_param->str(), libs2[0].second->str()); + + // Check authentication + const HttpAuthConfigPtr& auth2 = copy->getAuthConfig(); + ASSERT_TRUE(auth2); + EXPECT_EQ(auth->toElement()->str(), auth2->toElement()->str()); +} + + +// Tests if the context can store and retrieve hook libs information. +TEST(CtrlAgentCfgMgr, contextHookParams) { + CtrlAgentCfgContext ctx; + + // By default there should be no hooks. + HooksConfig& libs = ctx.getHooksConfig(); + EXPECT_TRUE(libs.get().empty()); + + libs.add("libone.so", ConstElementPtr()); + libs.add("libtwo.so", Element::fromJSON("{\"foo\": true}")); + libs.add("libthree.so", Element::fromJSON("{\"bar\": 42}")); + + const HooksConfig& stored_libs = ctx.getHooksConfig(); + EXPECT_EQ(3, stored_libs.get().size()); + + // @todo add a == operator to HooksConfig + EXPECT_EQ(libs.get(), stored_libs.get()); +} + +// Test if the context can store and retrieve basic HTTP authentication +// configuration. +TEST(CtrlAgentCfgMgr, contextAuthConfig) { + CtrlAgentCfgContext ctx; + + // By default there should be no authentication. + EXPECT_FALSE(ctx.getAuthConfig()); + BasicHttpAuthConfigPtr auth(new BasicHttpAuthConfig()); + EXPECT_NO_THROW(ctx.setAuthConfig(auth)); + + auth->setRealm("foobar"); + auth->add("foo", "", "bar", ""); + auth->add("test", "", "123\xa3", ""); + + const HttpAuthConfigPtr& stored_auth = ctx.getAuthConfig(); + ASSERT_TRUE(stored_auth); + EXPECT_FALSE(stored_auth->empty()); + EXPECT_EQ(auth->toElement()->str(), stored_auth->toElement()->str()); +} + +// Test if the context can store and retrieve basic HTTP authentication +// configuration using files. +TEST(CtrlAgentCfgMgr, contextAuthConfigFile) { + CtrlAgentCfgContext ctx; + + // By default there should be no authentication. + EXPECT_FALSE(ctx.getAuthConfig()); + BasicHttpAuthConfigPtr auth(new BasicHttpAuthConfig()); + EXPECT_NO_THROW(ctx.setAuthConfig(auth)); + + auth->setRealm("foobar"); + auth->setDirectory("/tmp"); + auth->add("", "/tmp/foo", "", "/tmp/bar"); + auth->add("", "/tmp/test", "", "/tmp/pwd"); + + const HttpAuthConfigPtr& stored_auth = ctx.getAuthConfig(); + ASSERT_TRUE(stored_auth); + EXPECT_FALSE(stored_auth->empty()); + EXPECT_EQ(auth->toElement()->str(), stored_auth->toElement()->str()); +} + +/// Control Agent configurations used in tests. +const char* AGENT_CONFIGS[] = { + + // configuration 0: empty (nothing specified) + "{ }", + + // Configuration 1: http parameters only (no control sockets, not hooks) + "{ \"http-host\": \"betelgeuse\",\n" + " \"http-port\": 8001\n" + "}", + + // Configuration 2: http and 1 socket + "{\n" + " \"http-host\": \"betelgeuse\",\n" + " \"http-port\": 8001,\n" + " \"control-sockets\": {\n" + " \"dhcp4\": {\n" + " \"socket-name\": \"/tmp/socket-v4\"\n" + " }\n" + " }\n" + "}", + + // Configuration 3: http and all 3 sockets + "{\n" + " \"http-host\": \"betelgeuse\",\n" + " \"http-port\": 8001,\n" + " \"control-sockets\": {\n" + " \"dhcp4\": {\n" + " \"socket-name\": \"/tmp/socket-v4\"\n" + " },\n" + " \"dhcp6\": {\n" + " \"socket-name\": \"/tmp/socket-v6\"\n" + " },\n" + " \"d2\": {\n" + " \"socket-name\": \"/tmp/socket-d2\"\n" + " }\n" + " }\n" + "}", + + // Configuration 4: http, 1 socket and hooks + // CA is able to load hook libraries that augment its operation. + // The primary functionality is the ability to add new commands. + "{\n" + " \"http-host\": \"betelgeuse\",\n" + " \"http-port\": 8001,\n" + " \"control-sockets\": {\n" + " \"dhcp4\": {\n" + " \"socket-name\": \"/tmp/socket-v4\"\n" + " }\n" + " },\n" + " \"hooks-libraries\": [" + " {" + " \"library\": \"%LIBRARY%\"," + " \"parameters\": {\n" + " \"param1\": \"foo\"\n" + " }\n" + " }\n" + " ]\n" + "}", + + // Configuration 5: http and 1 socket (d2 only) + "{\n" + " \"http-host\": \"betelgeuse\",\n" + " \"http-port\": 8001,\n" + " \"control-sockets\": {\n" + " \"d2\": {\n" + " \"socket-name\": \"/tmp/socket-d2\"\n" + " }\n" + " }\n" + "}", + + // Configuration 6: http and 1 socket (dhcp6 only) + "{\n" + " \"http-host\": \"betelgeuse\",\n" + " \"http-port\": 8001,\n" + " \"control-sockets\": {\n" + " \"dhcp6\": {\n" + " \"socket-name\": \"/tmp/socket-v6\"\n" + " }\n" + " }\n" + "}", + + // Configuration 7: http, 1 socket and authentication + "{\n" + " \"http-host\": \"betelgeuse\",\n" + " \"http-port\": 8001,\n" + " \"authentication\": {\n" + " \"type\": \"basic\",\n" + " \"realm\": \"foobar\",\n" + " \"clients\": [" + " {" + " \"user\": \"foo\",\n" + " \"password\": \"bar\"\n" + " },{\n" + " \"user\": \"test\",\n" + " \"password\": \"123\\u00a3\"\n" + " }\n" + " ]\n" + " },\n" + " \"control-sockets\": {\n" + " \"dhcp4\": {\n" + " \"socket-name\": \"/tmp/socket-v4\"\n" + " }\n" + " }\n" + "}", + + // Configuration 8: http and 2 sockets with user contexts and comments + "{\n" + " \"user-context\": { \"comment\": \"Indirect comment\" },\n" + " \"http-host\": \"betelgeuse\",\n" + " \"http-port\": 8001,\n" + " \"authentication\": {\n" + " \"comment\": \"basic HTTP authentication\",\n" + " \"type\": \"basic\",\n" + " \"realm\": \"foobar\",\n" + " \"clients\": [" + " {" + " \"comment\": \"foo is authorized\",\n" + " \"user\": \"foo\",\n" + " \"password\": \"bar\"\n" + " },{\n" + " \"user\": \"test\",\n" + " \"user-context\": { \"no password\": true }\n" + " }\n" + " ]\n" + " },\n" + " \"control-sockets\": {\n" + " \"dhcp4\": {\n" + " \"comment\": \"dhcp4 socket\",\n" + " \"socket-name\": \"/tmp/socket-v4\"\n" + " },\n" + " \"dhcp6\": {\n" + " \"socket-name\": \"/tmp/socket-v6\",\n" + " \"user-context\": { \"version\": 1 }\n" + " }\n" + " }\n" + "}", + + // Configuration 9: https aka http over TLS + "{\n" + " \"http-host\": \"betelgeuse\",\n" + " \"http-port\": 8001,\n" + " \"trust-anchor\": \"my-ca\",\n" + " \"cert-file\": \"my-cert\",\n" + " \"key-file\": \"my-key\",\n" + " \"cert-required\": false\n" + "}", + + // Configuration 10: http, 1 socket and authentication using files + "{\n" + " \"http-host\": \"betelgeuse\",\n" + " \"http-port\": 8001,\n" + " \"authentication\": {\n" + " \"type\": \"basic\",\n" + " \"realm\": \"foobar\",\n" + " \"directory\": \"" CA_TEST_DATA_DIR "\",\n" + " \"clients\": [" + " {" + " \"user-file\": \"hiddenu\",\n" + " \"password-file\": \"hiddenp\"\n" + " },{\n" + " \"password-file\": \"hiddens\"\n" + " }\n" + " ]\n" + " },\n" + " \"control-sockets\": {\n" + " \"dhcp4\": {\n" + " \"socket-name\": \"/tmp/socket-v4\"\n" + " }\n" + " }\n" + "}" +}; + +/// @brief Class used for testing CfgMgr +class AgentParserTest : public isc::process::ConfigParseTest { +public: + + /// @brief Tries to load input text as a configuration + /// + /// @param config text containing input configuration + /// @param expected_answer expected result of configuration (0 = success) + void configParse(const char* config, int expected_answer) { + isc::agent::ParserContext parser; + ConstElementPtr json = parser.parseString(config, ParserContext::PARSER_SUB_AGENT); + + EXPECT_NO_THROW(answer_ = cfg_mgr_.parse(json, false)); + EXPECT_TRUE(checkAnswer(expected_answer)); + } + + /// @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); + } + + /// Configuration Manager (used in tests) + NakedAgentCfgMgr cfg_mgr_; +}; + +// This test verifies if an empty config is handled properly. In practice such +// a config makes little sense, but perhaps it's ok for a default deployment. +// Sadly, our bison parser requires at last one parameter to be present. +// Until we determine whether we want the empty config to be allowed or not, +// this test remains disabled. +TEST_F(AgentParserTest, DISABLED_configParseEmpty) { + configParse(AGENT_CONFIGS[0], 0); +} + +// This test checks if a config with only HTTP parameters is parsed properly. +TEST_F(AgentParserTest, configParseHttpOnly) { + configParse(AGENT_CONFIGS[1], 0); + + CtrlAgentCfgContextPtr ctx = cfg_mgr_.getCtrlAgentCfgContext(); + ASSERT_TRUE(ctx); + EXPECT_EQ("betelgeuse", ctx->getHttpHost()); + EXPECT_EQ(8001, ctx->getHttpPort()); +} + +// Tests if a single socket can be configured. BTW this test also checks +// if default value for socket-type is specified (the config doesn't have it, +// so the default value should be filed in). +TEST_F(AgentParserTest, configParseSocketDhcp4) { + configParse(AGENT_CONFIGS[2], 0); + + CtrlAgentCfgContextPtr ctx = cfg_mgr_.getCtrlAgentCfgContext(); + ASSERT_TRUE(ctx); + ConstElementPtr socket = ctx->getControlSocketInfo("dhcp4"); + ASSERT_TRUE(socket); + EXPECT_EQ("{ \"socket-name\": \"/tmp/socket-v4\", \"socket-type\": \"unix\" }", + socket->str()); + EXPECT_FALSE(ctx->getControlSocketInfo("dhcp6")); + EXPECT_FALSE(ctx->getControlSocketInfo("d2")); +} + +// Tests if a single socket can be configured. BTW this test also checks +// if default value for socket-type is specified (the config doesn't have it, +// so the default value should be filed in). +TEST_F(AgentParserTest, configParseSocketD2) { + configParse(AGENT_CONFIGS[5], 0); + + CtrlAgentCfgContextPtr ctx = cfg_mgr_.getCtrlAgentCfgContext(); + ASSERT_TRUE(ctx); + ConstElementPtr socket = ctx->getControlSocketInfo("d2"); + ASSERT_TRUE(socket); + EXPECT_EQ("{ \"socket-name\": \"/tmp/socket-d2\", \"socket-type\": \"unix\" }", + socket->str()); + + EXPECT_FALSE(ctx->getControlSocketInfo("dhcp4")); + EXPECT_FALSE(ctx->getControlSocketInfo("dhcp6")); +} + +// Tests if a single socket can be configured. BTW this test also checks +// if default value for socket-type is specified (the config doesn't have it, +// so the default value should be filed in). +TEST_F(AgentParserTest, configParseSocketDhcp6) { + configParse(AGENT_CONFIGS[6], 0); + + CtrlAgentCfgContextPtr ctx = cfg_mgr_.getCtrlAgentCfgContext(); + ASSERT_TRUE(ctx); + ConstElementPtr socket = ctx->getControlSocketInfo("dhcp6"); + ASSERT_TRUE(socket); + EXPECT_EQ("{ \"socket-name\": \"/tmp/socket-v6\", \"socket-type\": \"unix\" }", + socket->str()); + EXPECT_FALSE(ctx->getControlSocketInfo("dhcp4")); + EXPECT_FALSE(ctx->getControlSocketInfo("d2")); +} + +// This tests if all 3 sockets can be configured and makes sure the parser +// doesn't confuse them. +TEST_F(AgentParserTest, configParse3Sockets) { + configParse(AGENT_CONFIGS[3], 0); + CtrlAgentCfgContextPtr ctx = cfg_mgr_.getCtrlAgentCfgContext(); + ASSERT_TRUE(ctx); + ConstElementPtr socket2 = ctx->getControlSocketInfo("d2"); + ConstElementPtr socket4 = ctx->getControlSocketInfo("dhcp4"); + ConstElementPtr socket6 = ctx->getControlSocketInfo("dhcp6"); + ASSERT_TRUE(socket2); + EXPECT_EQ("{ \"socket-name\": \"/tmp/socket-d2\", \"socket-type\": \"unix\" }", + socket2->str()); + ASSERT_TRUE(socket4); + EXPECT_EQ("{ \"socket-name\": \"/tmp/socket-v4\", \"socket-type\": \"unix\" }", + socket4->str()); + ASSERT_TRUE(socket6); + EXPECT_EQ("{ \"socket-name\": \"/tmp/socket-v6\", \"socket-type\": \"unix\" }", + socket6->str()); +} + +// This test checks that the config file with hook library specified can be +// loaded. This one is a bit tricky, because the parser sanity checks the lib +// name. In particular, it checks if such a library exists. Therefore we +// can't use AGENT_CONFIGS[4] as is, but need to run it through path replacer. +TEST_F(AgentParserTest, configParseHooks) { + // Create the configuration with proper lib path. + std::string cfg = pathReplacer(AGENT_CONFIGS[4], CALLOUT_LIBRARY); + // The configuration should be successful. + configParse(cfg.c_str(), 0); + + // The context now should have the library specified. + CtrlAgentCfgContextPtr ctx = cfg_mgr_.getCtrlAgentCfgContext(); + const HookLibsCollection libs = ctx->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()); +} + +// This test checks that the config file with basic HTTP authentication can be +// loaded. +TEST_F(AgentParserTest, configParseAuth) { + configParse(AGENT_CONFIGS[7], 0); + CtrlAgentCfgContextPtr ctx = cfg_mgr_.getCtrlAgentCfgContext(); + const HttpAuthConfigPtr& auth = ctx->getAuthConfig(); + ASSERT_TRUE(auth); + const BasicHttpAuthConfigPtr& basic_auth = + boost::dynamic_pointer_cast<BasicHttpAuthConfig>(auth); + ASSERT_TRUE(basic_auth); + + // Check realm + EXPECT_EQ("foobar", basic_auth->getRealm()); + + // Check credentials + auto credentials = basic_auth->getCredentialMap(); + EXPECT_EQ(2, credentials.size()); + std::string user; + EXPECT_NO_THROW(user = credentials.at("Zm9vOmJhcg==")); + EXPECT_EQ("foo", user); + EXPECT_NO_THROW(user = credentials.at("dGVzdDoxMjPCow==")); + EXPECT_EQ("test", user); + + // Check clients. + BasicHttpAuthConfig expected; + expected.setRealm("foobar"); + expected.add("foo", "", "bar", ""); + expected.add("test", "", "123\xa3", ""); + EXPECT_EQ(expected.toElement()->str(), basic_auth->toElement()->str()); +} + +// This test checks comments. +TEST_F(AgentParserTest, comments) { + configParse(AGENT_CONFIGS[8], 0); + CtrlAgentCfgContextPtr agent_ctx = cfg_mgr_.getCtrlAgentCfgContext(); + ASSERT_TRUE(agent_ctx); + + // Check global user context. + ConstElementPtr ctx = agent_ctx->getContext(); + ASSERT_TRUE(ctx); + ASSERT_EQ(1, ctx->size()); + ASSERT_TRUE(ctx->get("comment")); + EXPECT_EQ("\"Indirect comment\"", ctx->get("comment")->str()); + + // There is a DHCP4 control socket. + ConstElementPtr socket4 = agent_ctx->getControlSocketInfo("dhcp4"); + ASSERT_TRUE(socket4); + + // Check DHCP4 control socket user context. + ConstElementPtr ctx4 = socket4->get("user-context"); + ASSERT_TRUE(ctx4); + ASSERT_EQ(1, ctx4->size()); + ASSERT_TRUE(ctx4->get("comment")); + EXPECT_EQ("\"dhcp4 socket\"", ctx4->get("comment")->str()); + + // There is a DHCP6 control socket. + ConstElementPtr socket6 = agent_ctx->getControlSocketInfo("dhcp6"); + ASSERT_TRUE(socket6); + + // Check DHCP6 control socket user context. + ConstElementPtr ctx6 = socket6->get("user-context"); + ASSERT_TRUE(ctx6); + ASSERT_EQ(1, ctx6->size()); + ASSERT_TRUE(ctx6->get("version")); + EXPECT_EQ("1", ctx6->get("version")->str()); + + // Check authentication comment. + const HttpAuthConfigPtr& auth = agent_ctx->getAuthConfig(); + ASSERT_TRUE(auth); + ConstElementPtr ctx7 = auth->getContext(); + ASSERT_TRUE(ctx7); + ASSERT_EQ(1, ctx7->size()); + ASSERT_TRUE(ctx7->get("comment")); + EXPECT_EQ("\"basic HTTP authentication\"", ctx7->get("comment")->str()); + + // Check basic HTTP authentication client comment. + const BasicHttpAuthConfigPtr& basic_auth = + boost::dynamic_pointer_cast<BasicHttpAuthConfig>(auth); + ASSERT_TRUE(basic_auth); + auto clients = basic_auth->getClientList(); + ASSERT_EQ(2, clients.size()); + ConstElementPtr ctx8 = clients.front().getContext(); + ASSERT_TRUE(ctx8); + ASSERT_EQ(1, ctx8->size()); + ASSERT_TRUE(ctx8->get("comment")); + EXPECT_EQ("\"foo is authorized\"", ctx8->get("comment")->str()); + + // Check basic HTTP authentication client user context. + ConstElementPtr ctx9 = clients.back().getContext(); + ASSERT_TRUE(ctx9); + ASSERT_EQ(1, ctx9->size()); + ASSERT_TRUE(ctx9->get("no password")); + EXPECT_EQ("true", ctx9->get("no password")->str()); +} + +// This test checks if a config with TLS parameters is parsed properly. +TEST_F(AgentParserTest, configParseTls) { + configParse(AGENT_CONFIGS[9], 0); + + CtrlAgentCfgContextPtr ctx = cfg_mgr_.getCtrlAgentCfgContext(); + ASSERT_TRUE(ctx); + EXPECT_EQ("my-ca", ctx->getTrustAnchor()); + EXPECT_EQ("my-cert", ctx->getCertFile()); + EXPECT_EQ("my-key", ctx->getKeyFile()); + EXPECT_FALSE(ctx->getCertRequired()); +} + +// This test checks that the config file with basic HTTP authentication +// using files can be loaded. +TEST_F(AgentParserTest, configParseAuthFile) { + configParse(AGENT_CONFIGS[10], 0); + CtrlAgentCfgContextPtr ctx = cfg_mgr_.getCtrlAgentCfgContext(); + const HttpAuthConfigPtr& auth = ctx->getAuthConfig(); + ASSERT_TRUE(auth); + const BasicHttpAuthConfigPtr& basic_auth = + boost::dynamic_pointer_cast<BasicHttpAuthConfig>(auth); + ASSERT_TRUE(basic_auth); + + // Check realm + EXPECT_EQ("foobar", basic_auth->getRealm()); + + // Check directory + EXPECT_EQ(std::string(CA_TEST_DATA_DIR), basic_auth->getDirectory()); + + // Check credentials + auto credentials = basic_auth->getCredentialMap(); + EXPECT_EQ(2, credentials.size()); + std::string user; + EXPECT_NO_THROW(user = credentials.at("a2VhdGVzdDpLZWFUZXN0")); + EXPECT_EQ("keatest", user); + EXPECT_NO_THROW(user = credentials.at("a2VhOnRlc3Q=")); + EXPECT_EQ("kea", user); + + // Check clients. + BasicHttpAuthConfig expected; + expected.setRealm("foobar"); + expected.setDirectory(std::string(CA_TEST_DATA_DIR)); + expected.add("", "hiddenu", "", "hiddenp"); + expected.add("", "", "", "hiddens", true); + EXPECT_EQ(expected.toElement()->str(), basic_auth->toElement()->str()); +} + +} // end of anonymous namespace diff --git a/src/bin/agent/tests/ca_command_mgr_unittests.cc b/src/bin/agent/tests/ca_command_mgr_unittests.cc new file mode 100644 index 0000000..6353916 --- /dev/null +++ b/src/bin/agent/tests/ca_command_mgr_unittests.cc @@ -0,0 +1,425 @@ +// 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 <agent/ca_cfg_mgr.h> +#include <agent/ca_command_mgr.h> +#include <agent/ca_controller.h> +#include <agent/ca_process.h> +#include <asiolink/asio_wrapper.h> +#include <asiolink/interval_timer.h> +#include <asiolink/io_service.h> +#include <asiolink/testutils/test_server_unix_socket.h> +#include <cc/command_interpreter.h> +#include <cc/data.h> +#include <process/testutils/d_test_stubs.h> +#include <boost/pointer_cast.hpp> +#include <gtest/gtest.h> +#include <testutils/sandbox.h> +#include <cstdlib> +#include <functional> +#include <vector> +#include <thread> + +using namespace isc::agent; +using namespace isc::asiolink; +using namespace isc::data; +using namespace isc::process; + +namespace { + +/// @brief Test timeout in ms. +const long TEST_TIMEOUT = 10000; + +/// @brief Test fixture class for @ref CtrlAgentCommandMgr. +/// +/// @todo Add tests for various commands, including the cases when the +/// commands are forwarded to other servers via unix sockets. +/// Meanwhile, this is just a placeholder for the tests. +class CtrlAgentCommandMgrTest : public DControllerTest { +public: + isc::test::Sandbox sandbox; + + /// @brief Constructor. + /// + /// Deregisters all commands except 'list-commands'. + CtrlAgentCommandMgrTest() + : DControllerTest(CtrlAgentController::instance), + mgr_(CtrlAgentCommandMgr::instance()) { + mgr_.deregisterAll(); + removeUnixSocketFile(); + initProcess(); + } + + /// @brief Destructor. + /// + /// Deregisters all commands except 'list-commands'. + virtual ~CtrlAgentCommandMgrTest() { + mgr_.deregisterAll(); + removeUnixSocketFile(); + } + + /// @brief Verifies received answer + /// + /// @todo Add better checks for failure cases and for + /// verification of the response parameters. + /// + /// @param answer answer to be verified + /// @param expected_code0 code expected to be returned in first result within + /// the answer. + /// @param expected_code1 code expected to be returned in second result within + /// the answer. + /// @param expected_code2 code expected to be returned in third result within + /// the answer. + void checkAnswer(const ConstElementPtr& answer, const int expected_code0 = 0, + const int expected_code1 = -1, const int expected_code2 = -1) { + std::vector<int> expected_codes; + if (expected_code0 >= 0) { + expected_codes.push_back(expected_code0); + } + + if (expected_code1 >= 0) { + expected_codes.push_back(expected_code1); + } + + if (expected_code2 >= 0) { + expected_codes.push_back(expected_code2); + } + + int status_code; + // There may be multiple answers returned within a list. + std::vector<ElementPtr> answer_list = answer->listValue(); + + ASSERT_EQ(expected_codes.size(), answer_list.size()); + // Check all answers. + for (auto ans = answer_list.cbegin(); ans != answer_list.cend(); + ++ans) { + ConstElementPtr text; + ASSERT_NO_THROW(text = isc::config::parseAnswer(status_code, *ans)); + EXPECT_EQ(expected_codes[std::distance(answer_list.cbegin(), ans)], + status_code) + << "answer contains text: " << text->stringValue(); + } + } + + /// @brief Returns socket file path. + /// + /// If the KEA_SOCKET_TEST_DIR environment variable is specified, the + /// socket file is created in the location pointed to by this variable. + /// Otherwise, it is created in the build directory. + std::string unixSocketFilePath() { + std::string socket_path; + const char* env = getenv("KEA_SOCKET_TEST_DIR"); + if (env) { + socket_path = std::string(env) + "/test-socket"; + } else { + socket_path = sandbox.join("test-socket"); + } + return (socket_path); + } + + /// @brief Removes unix socket descriptor. + void removeUnixSocketFile() { + static_cast<void>(remove(unixSocketFilePath().c_str())); + } + + /// @brief Returns pointer to CtrlAgentProcess instance. + CtrlAgentProcessPtr getCtrlAgentProcess() { + return (boost::dynamic_pointer_cast<CtrlAgentProcess>(getProcess())); + } + + /// @brief Returns pointer to CtrlAgentCfgMgr instance for a process. + CtrlAgentCfgMgrPtr getCtrlAgentCfgMgr() { + CtrlAgentCfgMgrPtr p; + if (getCtrlAgentProcess()) { + p = getCtrlAgentProcess()->getCtrlAgentCfgMgr(); + } + return (p); + } + + /// @brief Returns a pointer to the configuration context. + CtrlAgentCfgContextPtr getCtrlAgentCfgContext() { + CtrlAgentCfgContextPtr p; + if (getCtrlAgentCfgMgr()) { + p = getCtrlAgentCfgMgr()->getCtrlAgentCfgContext(); + } + return (p); + } + + /// @brief Adds configuration of the control socket. + /// + /// @param service Service for which socket configuration is to be added. + void + configureControlSocket(const std::string& service) { + CtrlAgentCfgContextPtr ctx = getCtrlAgentCfgContext(); + ASSERT_TRUE(ctx); + + ElementPtr control_socket = Element::createMap(); + control_socket->set("socket-name", + Element::create(unixSocketFilePath())); + ctx->setControlSocketInfo(control_socket, service); + } + + /// @brief Create and bind server side socket. + /// + /// @param response Stub response to be sent from the server socket to the + /// client. + /// @param use_thread Indicates if the IO service will be ran in thread. + void bindServerSocket(const std::string& response, + const bool use_thread = false) { + server_socket_.reset(new test::TestServerUnixSocket(*getIOService(), + unixSocketFilePath(), + response)); + server_socket_->startTimer(TEST_TIMEOUT); + server_socket_->bindServerSocket(use_thread); + } + + /// @brief Creates command with no arguments. + /// + /// @param command_name Command name. + /// @param service Service value to be added to the command. This value is + /// specified as a list of comma separated values, e.g. "dhcp4, dhcp6". + /// + /// @return Pointer to the instance of the created command. + ConstElementPtr createCommand(const std::string& command_name, + const std::string& service) { + ElementPtr command = Element::createMap(); + command->set("command", Element::create(command_name)); + + // Only add the 'service' parameter if non-empty. + if (!service.empty()) { + std::string s = boost::replace_all_copy(service, ",", "\",\""); + s = std::string("[ \"") + s + std::string("\" ]"); + command->set("service", Element::fromJSON(s)); + } + + command->set("arguments", Element::createMap()); + + return (command); + } + + /// @brief Test forwarding the command. + /// + /// @param server_type Server for which the client socket should be + /// configured. + /// @param service Service to be included in the command. + /// @param expected_result0 Expected first result in response from the server. + /// @param expected_result1 Expected second result in response from the server. + /// @param expected_result2 Expected third result in response from the server. + /// server socket after which the IO service should be stopped. + /// @param expected_responses Number of responses after which the test finishes. + /// @param server_response Stub response to be sent by the server. + void testForward(const std::string& configured_service, + const std::string& service, + const int expected_result0, + const int expected_result1 = -1, + const int expected_result2 = -1, + const size_t expected_responses = 1, + const std::string& server_response = "{ \"result\": 0 }") { + // Configure client side socket. + configureControlSocket(configured_service); + // Create server side socket. + bindServerSocket(server_response, true); + + // The client side communication is synchronous. To be able to respond + // to this we need to run the server side socket at the same time as the + // client. Running IO service in a thread guarantees that the server + //responds as soon as it receives the control command. + std::thread th(std::bind(&IOService::run, getIOService().get())); + + + // Wait for the IO service in thread to actually run. + server_socket_->waitForRunning(); + + ConstElementPtr command = createCommand("foo", service); + ConstElementPtr answer = mgr_.processCommand(command); + + // Stop IO service immediately and let the thread die. + getIOService()->stop(); + + // Wait for the thread to finish. + th.join(); + + // Cancel all asynchronous operations on the server. + server_socket_->stopServer(); + + // We have some cancelled operations for which we need to invoke the + // handlers with the operation_aborted error code. + getIOService()->get_io_service().reset(); + getIOService()->poll(); + + EXPECT_EQ(expected_responses, server_socket_->getResponseNum()); + checkAnswer(answer, expected_result0, expected_result1, expected_result2); + } + + /// @brief a convenience reference to control agent command manager + CtrlAgentCommandMgr& mgr_; + + /// @brief Pointer to the test server unix socket. + test::TestServerUnixSocketPtr server_socket_; +}; + +/// Just a basic test checking that non-existent command is handled +/// properly. +TEST_F(CtrlAgentCommandMgrTest, bogus) { + ConstElementPtr answer; + EXPECT_NO_THROW(answer = mgr_.processCommand(createCommand("fish-and-chips-please", ""))); + checkAnswer(answer, isc::config::CONTROL_RESULT_COMMAND_UNSUPPORTED); +}; + +// Test verifying that parameter other than command, arguments and service is +// rejected and that the correct error is returned. +TEST_F(CtrlAgentCommandMgrTest, extraParameter) { + ElementPtr command = Element::createMap(); + command->set("command", Element::create("list-commands")); + command->set("arguments", Element::createMap()); + command->set("extra-arg", Element::createMap()); + + ConstElementPtr answer; + EXPECT_NO_THROW(answer = mgr_.processCommand(command)); + checkAnswer(answer, isc::config::CONTROL_RESULT_ERROR); +} + +/// Just a basic test checking that 'list-commands' is supported. +TEST_F(CtrlAgentCommandMgrTest, listCommands) { + ConstElementPtr answer; + EXPECT_NO_THROW(answer = mgr_.processCommand(createCommand("list-commands", ""))); + + checkAnswer(answer, isc::config::CONTROL_RESULT_SUCCESS); +}; + +/// Check that control command is successfully forwarded to the DHCPv4 server. +TEST_F(CtrlAgentCommandMgrTest, forwardToDHCPv4Server) { + testForward("dhcp4", "dhcp4", isc::config::CONTROL_RESULT_SUCCESS); +} + +/// Check that control command is successfully forwarded to the DHCPv6 server. +TEST_F(CtrlAgentCommandMgrTest, forwardToDHCPv6Server) { + testForward("dhcp6", "dhcp6", isc::config::CONTROL_RESULT_SUCCESS); +} + +/// Check that control command is successfully forwarded to the D2 server. +TEST_F(CtrlAgentCommandMgrTest, forwardToD2Server) { + testForward("d2", "d2", isc::config::CONTROL_RESULT_SUCCESS); +} + +/// Check that the same command is forwarded to multiple servers. +TEST_F(CtrlAgentCommandMgrTest, forwardToBothDHCPServers) { + configureControlSocket("dhcp6"); + + testForward("dhcp4", "dhcp4,dhcp6", isc::config::CONTROL_RESULT_SUCCESS, + isc::config::CONTROL_RESULT_SUCCESS, -1, 2); +} + +/// Check that the same command is forwarded to all servers. +TEST_F(CtrlAgentCommandMgrTest, forwardToAllServers) { + configureControlSocket("dhcp6"); + configureControlSocket("d2"); + + testForward("dhcp4", "dhcp4,dhcp6,d2", isc::config::CONTROL_RESULT_SUCCESS, + isc::config::CONTROL_RESULT_SUCCESS, + isc::config::CONTROL_RESULT_SUCCESS, 3); +} + +/// Check that the command may forwarded to the second server even if +/// forwarding to a first server fails. +TEST_F(CtrlAgentCommandMgrTest, failForwardToServer) { + testForward("dhcp6", "dhcp4,dhcp6", + isc::config::CONTROL_RESULT_ERROR, + isc::config::CONTROL_RESULT_SUCCESS); +} + +/// Check that control command is not forwarded if the service is not specified. +TEST_F(CtrlAgentCommandMgrTest, noService) { + testForward("dhcp6", "", + isc::config::CONTROL_RESULT_COMMAND_UNSUPPORTED, + -1, -1, 0); +} + +/// Check that error is returned to the client when the server to which the +/// command was forwarded sent an invalid message. +TEST_F(CtrlAgentCommandMgrTest, invalidAnswer) { + testForward("dhcp6", "dhcp6", + isc::config::CONTROL_RESULT_ERROR, -1, -1, 1, + "{ \"result\": }"); +} + +/// Check that connection is dropped if it takes too long. The test checks +/// client's behavior when partial JSON is returned. Client will be waiting +/// for the '}' and will timeout because it is never received. +/// @todo Currently this test is disabled because we don't have configurable +/// timeout value. It is hardcoded to 5 sec, which is too long for the +/// unit test to run. +TEST_F(CtrlAgentCommandMgrTest, DISABLED_connectionTimeout) { + testForward("dhcp6", "dhcp6", + isc::config::CONTROL_RESULT_ERROR, -1, -1, 1, + "{ \"result\": 0"); +} + +/// Check that error is returned to the client if the forwarding socket is +/// not configured for the given service. +TEST_F(CtrlAgentCommandMgrTest, noClientSocket) { + ConstElementPtr command = createCommand("foo", "dhcp4"); + ConstElementPtr answer = mgr_.handleCommand("foo", ConstElementPtr(), + command); + + checkAnswer(answer, isc::config::CONTROL_RESULT_ERROR); +} + +/// Check that error is returned to the client if the remote server to +/// which the control command is to be forwarded is not available. +TEST_F(CtrlAgentCommandMgrTest, noServerSocket) { + configureControlSocket("dhcp6"); + + ConstElementPtr command = createCommand("foo", "dhcp6"); + ConstElementPtr answer = mgr_.handleCommand("foo", ConstElementPtr(), + command); + + checkAnswer(answer, isc::config::CONTROL_RESULT_ERROR); +} + +// Check that list-commands command is forwarded when the service +// value is specified. +TEST_F(CtrlAgentCommandMgrTest, forwardListCommands) { + // Configure client side socket. + configureControlSocket("dhcp4"); + // Create server side socket. + bindServerSocket("{ \"result\" : 3 }", true); + + // The client side communication is synchronous. To be able to respond + // to this we need to run the server side socket at the same time. + // Running IO service in a thread guarantees that the server responds + // as soon as it receives the control command. + std::thread th(std::bind(&IOService::run, getIOService().get())); + + // Wait for the IO service in thread to actually run. + server_socket_->waitForRunning(); + + ConstElementPtr command = createCommand("list-commands", "dhcp4"); + ConstElementPtr answer = mgr_.handleCommand("list-commands", ConstElementPtr(), + command); + + // Stop IO service immediately and let the thread die. + getIOService()->stop(); + + // Wait for the thread to finish. + th.join(); + + // Cancel all asynchronous operations on the server. + server_socket_->stopServer(); + + // We have some cancelled operations for which we need to invoke the + // handlers with the operation_aborted error code. + getIOService()->get_io_service().reset(); + getIOService()->poll(); + + // Answer of 3 is specific to the stub response we send when the + // command is forwarded. So having this value returned means that + // the command was forwarded as expected. + checkAnswer(answer, 3); +} + +} diff --git a/src/bin/agent/tests/ca_controller_unittests.cc b/src/bin/agent/tests/ca_controller_unittests.cc new file mode 100644 index 0000000..f7b2c8b --- /dev/null +++ b/src/bin/agent/tests/ca_controller_unittests.cc @@ -0,0 +1,837 @@ +// Copyright (C) 2016-2023 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 <agent/ca_controller.h> +#include <agent/ca_process.h> +#include <agent/ca_command_mgr.h> +#include <cc/data.h> +#include <cc/command_interpreter.h> +#include <process/testutils/d_test_stubs.h> +#include <boost/pointer_cast.hpp> +#include <sstream> +#include <unistd.h> + +using namespace isc::asiolink::test; +using namespace isc::agent; +using namespace isc::data; +using namespace isc::http; +using namespace isc::process; +using namespace boost::posix_time; +using namespace std; + +namespace { + +/// @brief Valid Control Agent Config used in tests. +const char* valid_agent_config = + "{" + " \"http-host\": \"127.0.0.1\"," + " \"http-port\": 8081," + " \"control-sockets\": {" + " \"dhcp4\": {" + " \"socket-type\": \"unix\"," + " \"socket-name\": \"/first/dhcp4/socket\"" + " }," + " \"dhcp6\": {" + " \"socket-type\": \"unix\"," + " \"socket-name\": \"/first/dhcp6/socket\"" + " }" + " }" + "}"; + +/// @brief test fixture class for testing CtrlAgentController class. +/// +/// This class derives from DControllerTest and wraps CtrlAgentController. Much +/// of the underlying functionality is in the DControllerBase class which +/// has extensive set of unit tests that are independent from the Control +/// Agent. +class CtrlAgentControllerTest : public DControllerTest { +public: + + /// @brief Constructor. + CtrlAgentControllerTest() + : DControllerTest(CtrlAgentController::instance) { + } + + /// @brief Returns pointer to CtrlAgentProcess instance. + CtrlAgentProcessPtr getCtrlAgentProcess() { + return (boost::dynamic_pointer_cast<CtrlAgentProcess>(getProcess())); + } + + /// @brief Returns pointer to CtrlAgentCfgMgr instance for a process. + CtrlAgentCfgMgrPtr getCtrlAgentCfgMgr() { + CtrlAgentCfgMgrPtr p; + if (getCtrlAgentProcess()) { + p = getCtrlAgentProcess()->getCtrlAgentCfgMgr(); + } + return (p); + } + + /// @brief Returns a pointer to the configuration context. + CtrlAgentCfgContextPtr getCtrlAgentCfgContext() { + CtrlAgentCfgContextPtr p; + if (getCtrlAgentCfgMgr()) { + p = getCtrlAgentCfgMgr()->getCtrlAgentCfgContext(); + } + return (p); + } + + /// @brief Tests that socket info structure contains 'unix' socket-type + /// value and the expected socket-name. + /// + /// @param service Service type. + /// @param exp_socket_name Expected socket name. + void testUnixSocketInfo(const std::string& service, + const std::string& exp_socket_name) { + CtrlAgentCfgContextPtr ctx = getCtrlAgentCfgContext(); + ASSERT_TRUE(ctx); + + ConstElementPtr sock_info = ctx->getControlSocketInfo(service); + ASSERT_TRUE(sock_info); + ASSERT_TRUE(sock_info->contains("socket-type")); + EXPECT_EQ("unix", sock_info->get("socket-type")->stringValue()); + ASSERT_TRUE(sock_info->contains("socket-name")); + EXPECT_EQ(exp_socket_name, + sock_info->get("socket-name")->stringValue()); + } + + /// @brief Compares the status in the given parse result to a given value. + /// + /// @param answer Element set containing an integer response and string + /// comment. + /// @param exp_status is an integer against which to compare the status. + /// @param exp_txt is expected text (not checked if "") + /// + void checkAnswer(isc::data::ConstElementPtr answer, + int exp_status, + string exp_txt = "") { + int rcode = 0; + isc::data::ConstElementPtr comment; + comment = isc::config::parseAnswer(rcode, answer); + + if (rcode != exp_status) { + ADD_FAILURE() << "Expected status code " << exp_status + << " but received " << rcode << ", comment: " + << (comment ? comment->str() : "(none)"); + } + + // Ok, parseAnswer interface is weird. If there are no arguments, + // it returns content of text. But if there is an argument, + // it returns the argument and it's not possible to retrieve + // "text" (i.e. comment). + if (comment->getType() != Element::string) { + comment = answer->get("text"); + } + + if (!exp_txt.empty()) { + EXPECT_EQ(exp_txt, comment->stringValue()); + } + } + + /// @brief Checks whether specified command is registered + /// + /// @param name name of the command to be checked + /// @param expect_true true - must be registered, false - must not be + void checkCommandRegistered(const std::string& name, bool expect_true = true) { + + // First get the list of registered commands + ConstElementPtr lst = Element::fromJSON("{ \"command\": \"list-commands\" }"); + ConstElementPtr rsp = CtrlAgentCommandMgr::instance().processCommand(lst); + + // The response must be an array with at least one element + ASSERT_TRUE(rsp); + ASSERT_EQ(Element::list, rsp->getType()); + ASSERT_LE(1, rsp->size()); + ConstElementPtr args = rsp->get(0)->get("arguments"); + ASSERT_TRUE(args); + + string args_txt = args->str(); + + if (expect_true) { + EXPECT_TRUE(args_txt.find(name) != string::npos); + } else { + EXPECT_TRUE(args_txt.find(name) == string::npos); + } + } + +}; + +// Basic Controller instantiation testing. +// Verifies that the controller singleton gets created and that the +// basic derivation from the base class is intact. +TEST_F(CtrlAgentControllerTest, 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<CtrlAgentController>(controller)); + + // Verify that controller's app name is correct. + EXPECT_TRUE(checkAppName(CtrlAgentController::agent_app_name_)); + + // Verify that controller's bin name is correct. + EXPECT_TRUE(checkBinName(CtrlAgentController::agent_bin_name_)); + + // Verify that controller's IOService exists. + EXPECT_TRUE(checkIOService()); + + // Verify that the Process does NOT exist. + EXPECT_FALSE(checkProcess()); +} + + +// Tests basic command line processing. +// Verifies that: +// 1. Standard command line options are supported. +// 2. Invalid options are detected. +TEST_F(CtrlAgentControllerTest, 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); +} + +// Tests application process creation and initialization. +// Verifies that the process can be successfully created and initialized. +TEST_F(CtrlAgentControllerTest, initProcessTesting) { + ASSERT_NO_THROW(initProcess()); + EXPECT_TRUE(checkProcess()); +} + +// 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(CtrlAgentControllerTest, launchNormalShutdown) { + // Write valid_agent_config and then run launch() for 1000 ms. + time_duration elapsed_time; + runWithConfig(valid_agent_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); +} + +// Tests that the SIGINT triggers a normal shutdown. +TEST_F(CtrlAgentControllerTest, sigintShutdown) { + // Setup to raise SIGHUP in 1 ms. + TimedSignal sighup(*getIOService(), SIGINT, 1); + + // Write valid_agent_config and then run launch() for a maximum + // of 1000 ms. + time_duration elapsed_time; + runWithConfig(valid_agent_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); +} + +// Tests that the SIGTERM triggers a normal shutdown. +TEST_F(CtrlAgentControllerTest, sigtermShutdown) { + // Setup to raise SIGTERM in 1 ms. + TimedSignal sighup(*getIOService(), SIGTERM, 1); + + // Write valid_agent_config and then run launch() for a maximum of 1 s. + time_duration elapsed_time; + runWithConfig(valid_agent_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); +} + +// Tests that the sockets settings are updated upon successful reconfiguration. +TEST_F(CtrlAgentControllerTest, successfulConfigUpdate) { + // This configuration should be used to override the initial configuration. + const char* second_config = + "{" + " \"http-host\": \"127.0.0.1\"," + " \"http-port\": 8080," + " \"control-sockets\": {" + " \"dhcp4\": {" + " \"socket-type\": \"unix\"," + " \"socket-name\": \"/second/dhcp4/socket\"" + " }," + " \"dhcp6\": {" + " \"socket-type\": \"unix\"," + " \"socket-name\": \"/second/dhcp6/socket\"" + " }" + " }" + "}"; + + // This check callback is called before the shutdown. + auto check_callback = [&] { + CtrlAgentProcessPtr process = getCtrlAgentProcess(); + ASSERT_TRUE(process); + + // Check that the HTTP listener still exists after reconfiguration. + ConstHttpListenerPtr listener = process->getHttpListener(); + ASSERT_TRUE(listener); + EXPECT_TRUE(process->isListening()); + + // The listener should have been reconfigured to use new address and port. + EXPECT_EQ("127.0.0.1", listener->getLocalAddress().toText()); + EXPECT_EQ(8080, listener->getLocalPort()); + }; + + // Schedule reconfiguration. + scheduleTimedWrite(second_config, 100); + // Schedule SIGHUP signal to trigger reconfiguration. + TimedSignal sighup(*getIOService(), SIGHUP, 200); + + // Start the server. + time_duration elapsed_time; + runWithConfig(valid_agent_config, 500, + static_cast<const TestCallback&>(check_callback), + elapsed_time); + + CtrlAgentCfgContextPtr ctx = getCtrlAgentCfgContext(); + ASSERT_TRUE(ctx); + + // The server should now hold the new listener configuration. + EXPECT_EQ("127.0.0.1", ctx->getHttpHost()); + EXPECT_EQ(8080, ctx->getHttpPort()); + + // The forwarding configuration should have been updated too. + testUnixSocketInfo("dhcp4", "/second/dhcp4/socket"); + testUnixSocketInfo("dhcp6", "/second/dhcp6/socket"); + + // After the shutdown the HTTP listener no longer exists. + CtrlAgentProcessPtr process = getCtrlAgentProcess(); + ASSERT_TRUE(process); + ConstHttpListenerPtr listener = process->getHttpListener(); + ASSERT_FALSE(listener); + EXPECT_FALSE(process->isListening()); +} + +// Tests that the server continues to use an old configuration when the listener +// reconfiguration is unsuccessful. +TEST_F(CtrlAgentControllerTest, unsuccessfulConfigUpdate) { + // This is invalid configuration. We're using restricted port number and + // IP address of 1.1.1.1. + const char* second_config = + "{" + " \"http-host\": \"1.1.1.1\"," + " \"http-port\": 1," + " \"control-sockets\": {" + " \"dhcp4\": {" + " \"socket-type\": \"unix\"," + " \"socket-name\": \"/second/dhcp4/socket\"" + " }," + " \"dhcp6\": {" + " \"socket-type\": \"unix\"," + " \"socket-name\": \"/second/dhcp6/socket\"" + " }" + " }" + "}"; + + // This check callback is called before the shutdown. + auto check_callback = [&] { + CtrlAgentProcessPtr process = getCtrlAgentProcess(); + ASSERT_TRUE(process); + + // We should still be using an original listener. + ConstHttpListenerPtr listener = process->getHttpListener(); + ASSERT_TRUE(listener); + EXPECT_TRUE(process->isListening()); + + EXPECT_EQ("127.0.0.1", listener->getLocalAddress().toText()); + EXPECT_EQ(8081, listener->getLocalPort()); + }; + + // Schedule reconfiguration. + scheduleTimedWrite(second_config, 100); + // Schedule SIGHUP signal to trigger reconfiguration. + TimedSignal sighup(*getIOService(), SIGHUP, 200); + + // Start the server. + time_duration elapsed_time; + runWithConfig(valid_agent_config, 500, + static_cast<const TestCallback&>(check_callback), + elapsed_time); + + CtrlAgentCfgContextPtr ctx = getCtrlAgentCfgContext(); + ASSERT_TRUE(ctx); + + // The reconfiguration should have been unsuccessful, and the server should + // still use the original configuration. + EXPECT_EQ("127.0.0.1", ctx->getHttpHost()); + EXPECT_EQ(8081, ctx->getHttpPort()); + + // Same for forwarding. + testUnixSocketInfo("dhcp4", "/first/dhcp4/socket"); + testUnixSocketInfo("dhcp6", "/first/dhcp6/socket"); + + // After the shutdown the HTTP listener no longer exists. + CtrlAgentProcessPtr process = getCtrlAgentProcess(); + ASSERT_TRUE(process); + ConstHttpListenerPtr listener = process->getHttpListener(); + ASSERT_FALSE(listener); + EXPECT_FALSE(process->isListening()); +} + +// Tests that it is possible to update the configuration in such a way that the +// listener configuration remains the same. The server should continue using the +// listener instance it has been using prior to the reconfiguration. +TEST_F(CtrlAgentControllerTest, noListenerChange) { + // This configuration should be used to override the initial configuration. + const char* second_config = + "{" + " \"http-host\": \"127.0.0.1\"," + " \"http-port\": 8081," + " \"control-sockets\": {" + " \"dhcp4\": {" + " \"socket-type\": \"unix\"," + " \"socket-name\": \"/second/dhcp4/socket\"" + " }," + " \"dhcp6\": {" + " \"socket-type\": \"unix\"," + " \"socket-name\": \"/second/dhcp6/socket\"" + " }" + " }" + "}"; + + // This check callback is called before the shutdown. + auto check_callback = [&] { + CtrlAgentProcessPtr process = getCtrlAgentProcess(); + ASSERT_TRUE(process); + + // Check that the HTTP listener still exists after reconfiguration. + ConstHttpListenerPtr listener = process->getHttpListener(); + ASSERT_TRUE(listener); + EXPECT_TRUE(process->isListening()); + + EXPECT_EQ("127.0.0.1", listener->getLocalAddress().toText()); + EXPECT_EQ(8081, listener->getLocalPort()); + }; + + // Schedule reconfiguration. + scheduleTimedWrite(second_config, 100); + // Schedule SIGHUP signal to trigger reconfiguration. + TimedSignal sighup(*getIOService(), SIGHUP, 200); + + // Start the server. + time_duration elapsed_time; + runWithConfig(valid_agent_config, 500, + static_cast<const TestCallback&>(check_callback), + elapsed_time); + + CtrlAgentCfgContextPtr ctx = getCtrlAgentCfgContext(); + ASSERT_TRUE(ctx); + + // The server should use a correct listener configuration. + EXPECT_EQ("127.0.0.1", ctx->getHttpHost()); + EXPECT_EQ(8081, ctx->getHttpPort()); + + // The forwarding configuration should have been updated. + testUnixSocketInfo("dhcp4", "/second/dhcp4/socket"); + testUnixSocketInfo("dhcp6", "/second/dhcp6/socket"); + + CtrlAgentProcessPtr process = getCtrlAgentProcess(); + ASSERT_TRUE(process); + ConstHttpListenerPtr listener = process->getHttpListener(); + ASSERT_FALSE(listener); + EXPECT_FALSE(process->isListening()); +} + +// Tests that registerCommands actually registers anything. +TEST_F(CtrlAgentControllerTest, registeredCommands) { + ASSERT_NO_THROW(initProcess()); + EXPECT_TRUE(checkProcess()); + + // The framework available makes it very difficult to test the actual + // code as CtrlAgentController is not initialized the same way it is + // in production code. In particular, the way CtrlAgentController + // is initialized in tests does not call registerCommands(). + // This is a crude workaround for this problem. Proper solution should + // be developed sooner rather than later. + const DControllerBasePtr& base = getController(); + const CtrlAgentControllerPtr& ctrl = + boost::dynamic_pointer_cast<CtrlAgentController>(base); + ASSERT_TRUE(ctrl); + ctrl->registerCommands(); + + // Check that the following command are really available. + checkCommandRegistered("build-report"); + checkCommandRegistered("config-get"); + checkCommandRegistered("config-hash-get"); + checkCommandRegistered("config-reload"); + checkCommandRegistered("config-set"); + checkCommandRegistered("config-test"); + checkCommandRegistered("config-write"); + checkCommandRegistered("list-commands"); + checkCommandRegistered("shutdown"); + checkCommandRegistered("status-get"); + checkCommandRegistered("version-get"); + + ctrl->deregisterCommands(); +} + +// Tests that config-write really writes a config file that contains +// Control-agent configuration and not some other random nonsense. +TEST_F(CtrlAgentControllerTest, configWrite) { + ASSERT_NO_THROW(initProcess()); + EXPECT_TRUE(checkProcess()); + + // The framework available makes it very difficult to test the actual + // code as CtrlAgentController is not initialized the same way it is + // in production code. In particular, the way CtrlAgentController + // is initialized in tests does not call registerCommands(). + // This is a crude workaround for this problem. Proper solution should + // be developed sooner rather than later. + const DControllerBasePtr& base = getController(); + const CtrlAgentControllerPtr& ctrl + = boost::dynamic_pointer_cast<CtrlAgentController>(base); + ASSERT_TRUE(ctrl); + // Now clean up after ourselves. + ctrl->registerCommands(); + + // First, build the command: + string file = string(TEST_DATA_BUILDDIR) + string("/config-write.json"); + string cmd_txt = "{ \"command\": \"config-write\" }"; + ConstElementPtr cmd = Element::fromJSON(cmd_txt); + ConstElementPtr params = Element::fromJSON("{\"filename\": \"" + file + "\" }"); + CtrlAgentCommandMgr& mgr_ = CtrlAgentCommandMgr::instance(); + + // Send the command + ConstElementPtr answer = mgr_.handleCommand("config-write", params, cmd); + + // Check that the command was successful + checkAnswer(answer, isc::config::CONTROL_RESULT_SUCCESS); + + // Now check that the file is there. + ifstream f(file.c_str()); + ASSERT_TRUE(f.good()); + + // Now that's some rough check that the config written really contains + // something that looks like Control-agent configuration. + ConstElementPtr from_file = Element::fromJSONFile(file, true); + ASSERT_TRUE(from_file); + ConstElementPtr ca = from_file->get("Control-agent"); + ASSERT_TRUE(ca); + EXPECT_TRUE(ca->get("control-sockets")); + EXPECT_TRUE(ca->get("hooks-libraries")); + EXPECT_TRUE(ca->get("http-host")); + EXPECT_TRUE(ca->get("http-port")); + + // Remove the file. + ::remove(file.c_str()); + + // Now clean up after ourselves. + ctrl->deregisterCommands(); +} + +// Tests if config-reload attempts to reload a file and reports that the +// file is missing. +TEST_F(CtrlAgentControllerTest, configReloadMissingFile) { + ASSERT_NO_THROW(initProcess()); + EXPECT_TRUE(checkProcess()); + + // The framework available makes it very difficult to test the actual + // code as CtrlAgentController is not initialized the same way it is + // in production code. In particular, the way CtrlAgentController + // is initialized in tests does not call registerCommands(). + // This is a crude workaround for this problem. Proper solution should + // be developed sooner rather than later. + const DControllerBasePtr& base = getController(); + const CtrlAgentControllerPtr& ctrl + = boost::dynamic_pointer_cast<CtrlAgentController>(base); + ASSERT_TRUE(ctrl); + // Now clean up after ourselves. + ctrl->registerCommands(); + + // 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. + getController()->setConfigFile("does-not-exist.json"); + + // Build and execute the command. + string cmd_txt = "{ \"command\": \"config-reload\" }"; + ConstElementPtr cmd = Element::fromJSON(cmd_txt); + ConstElementPtr params; + ConstElementPtr answer; + answer = CtrlAgentCommandMgr::instance().handleCommand("config-reload", + params, cmd); + + // Verify the reload was rejected. + string expected = "{ \"result\": 1, \"text\": " + "\"Configuration parsing failed: " + "Unable to open file does-not-exist.json\" }"; + EXPECT_EQ(expected, answer->str()); + + // Now clean up after ourselves. + ctrl->deregisterCommands(); +} + +// Tests if config-reload attempts to reload a file and reports that the +// file is not a valid JSON. +TEST_F(CtrlAgentControllerTest, configReloadBrokenFile) { + ASSERT_NO_THROW(initProcess()); + EXPECT_TRUE(checkProcess()); + + // The framework available makes it very difficult to test the actual + // code as CtrlAgentController is not initialized the same way it is + // in production code. In particular, the way CtrlAgentController + // is initialized in tests does not call registerCommands(). + // This is a crude workaround for this problem. Proper solution should + // be developed sooner rather than later. + const DControllerBasePtr& base = getController(); + const CtrlAgentControllerPtr& ctrl + = boost::dynamic_pointer_cast<CtrlAgentController>(base); + ASSERT_TRUE(ctrl); + // Now clean up after ourselves. + ctrl->registerCommands(); + + // 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. + getController()->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(); + + // Build and execute the command. + string cmd_txt = "{ \"command\": \"config-reload\" }"; + ConstElementPtr cmd = Element::fromJSON(cmd_txt); + ConstElementPtr params; + ConstElementPtr answer; + answer = CtrlAgentCommandMgr::instance().handleCommand("config-reload", + params, cmd); + + // Verify the reload was rejected. + string expected = "{ \"result\": 1, \"text\": " + "\"Configuration parsing failed: " + "testbad.json:1.1: Invalid character: b\" }"; + EXPECT_EQ(expected, answer->str()); + + // Remove the file. + ::remove("testbad.json"); + + // Now clean up after ourselves. + ctrl->deregisterCommands(); +} + +// Tests if config-reload attempts to reload a file and reports that the +// file is missing. +TEST_F(CtrlAgentControllerTest, configReloadFileValid) { + ASSERT_NO_THROW(initProcess()); + EXPECT_TRUE(checkProcess()); + + // The framework available makes it very difficult to test the actual + // code as CtrlAgentController is not initialized the same way it is + // in production code. In particular, the way CtrlAgentController + // is initialized in tests does not call registerCommands(). + // This is a crude workaround for this problem. Proper solution should + // be developed sooner rather than later. + const DControllerBasePtr& base = getController(); + const CtrlAgentControllerPtr& ctrl + = boost::dynamic_pointer_cast<CtrlAgentController>(base); + ASSERT_TRUE(ctrl); + // Now clean up after ourselves. + ctrl->registerCommands(); + + // 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. + getController()->setConfigFile("testvalid.json"); + + // Ok, enough fooling around. Let's create a valid config. + ofstream f("testvalid.json", ios::trunc); + f << "{ \"Control-agent\": " + << string(valid_agent_config) + << " }" << endl; + f.close(); + + // Build and execute the command. + string cmd_txt = "{ \"command\": \"config-reload\" }"; + ConstElementPtr cmd = Element::fromJSON(cmd_txt); + ConstElementPtr params; + ConstElementPtr answer; + answer = CtrlAgentCommandMgr::instance().handleCommand("config-reload", + params, cmd); + + + // Verify the reload was successful. + string expected = "{ \"result\": 0, \"text\": " + "\"Configuration applied successfully.\" }"; + EXPECT_EQ(expected, answer->str()); + + // Check that the config was indeed applied? + + // Remove the file. + ::remove("testvalid.json"); + + // Now clean up after ourselves. + ctrl->deregisterCommands(); +} + +// Tests that status-get returns expected info (pid, uptime and reload). +TEST_F(CtrlAgentControllerTest, statusGet) { + // Start the server. + time_duration elapsed_time; + runWithConfig(valid_agent_config, 500, elapsed_time); + + const DControllerBasePtr& ctrl = getController(); + ConstElementPtr response; + ASSERT_NO_THROW(response = ctrl->statusGetHandler("status-get", ConstElementPtr())); + 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 uptime (and reload) as it is based + // on current time. Let's just make sure it is within a reasonable + // range. + auto found_uptime = arguments->get("uptime"); + ASSERT_TRUE(found_uptime); + EXPECT_LE(found_uptime->intValue(), 5); + EXPECT_GE(found_uptime->intValue(), 0); + + auto found_reload = arguments->get("reload"); + ASSERT_TRUE(found_reload); + EXPECT_LE(found_reload->intValue(), 5); + EXPECT_GE(found_reload->intValue(), 0); +} + +TEST_F(CtrlAgentControllerTest, shutdown) { + ASSERT_NO_THROW(initProcess()); + EXPECT_TRUE(checkProcess()); + + // The framework available makes it very difficult to test the actual + // code as CtrlAgentController is not initialized the same way it is + // in production code. In particular, the way CtrlAgentController + // is initialized in tests does not call registerCommands(). + // This is a crude workaround for this problem. Proper solution should + // be developed sooner rather than later. + const DControllerBasePtr& base = getController(); + const CtrlAgentControllerPtr& ctrl + = boost::dynamic_pointer_cast<CtrlAgentController>(base); + ASSERT_TRUE(ctrl); + // Now clean up after ourselves. + ctrl->registerCommands(); + + // 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. + getController()->setConfigFile("testvalid.json"); + + // Ok, enough fooling around. Let's create a valid config. + ofstream f("testvalid.json", ios::trunc); + f << "{ \"Control-agent\": " + << string(valid_agent_config) + << " }" << endl; + f.close(); + + // Build and execute the command. + + ConstElementPtr cmd = Element::fromJSON("{ \"command\": \"shutdown\"}"); + ConstElementPtr params; + ConstElementPtr answer; + answer = CtrlAgentCommandMgr::instance().handleCommand("shutdown", + params, cmd); + + // Verify the reload was successful. + string expected = "{ \"result\": 0, \"text\": " + "\"Control Agent is shutting down\" }"; + + EXPECT_EQ(expected, answer->str()); + + int exit_value = ctrl->getExitValue(); + EXPECT_EQ(0, exit_value); + + // Remove the file. + ::remove("testvalid.json"); + + // Now clean up after ourselves. + ctrl->deregisterCommands(); +} + + +TEST_F(CtrlAgentControllerTest, shutdownExitValue) { + ASSERT_NO_THROW(initProcess()); + EXPECT_TRUE(checkProcess()); + + // The framework available makes it very difficult to test the actual + // code as CtrlAgentController is not initialized the same way it is + // in production code. In particular, the way CtrlAgentController + // is initialized in tests does not call registerCommands(). + // This is a crude workaround for this problem. Proper solution should + // be developed sooner rather than later. + const DControllerBasePtr& base = getController(); + const CtrlAgentControllerPtr& ctrl + = boost::dynamic_pointer_cast<CtrlAgentController>(base); + ASSERT_TRUE(ctrl); + // Now clean up after ourselves. + ctrl->registerCommands(); + + // 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. + getController()->setConfigFile("testvalid.json"); + + // Ok, enough fooling around. Let's create a valid config. + ofstream f("testvalid.json", ios::trunc); + f << "{ \"Control-agent\": " + << string(valid_agent_config) + << " }" << endl; + f.close(); + + // Build and execute the command. + + ConstElementPtr cmd = Element::fromJSON("{ \"command\": \"shutdown\"}"); + ConstElementPtr params = Element::fromJSON("{ \"exit-value\": 77 }"); + ConstElementPtr answer; + answer = CtrlAgentCommandMgr::instance().handleCommand("shutdown", + params, cmd); + + // Verify the reload was successful. + string expected = "{ \"result\": 0, \"text\": " + "\"Control Agent is shutting down\" }"; + + EXPECT_EQ(expected, answer->str()); + + int exit_value = ctrl->getExitValue(); + EXPECT_EQ(77, exit_value); + + // Remove the file. + ::remove("testvalid.json"); + + // Now clean up after ourselves. + ctrl->deregisterCommands(); +} + +} diff --git a/src/bin/agent/tests/ca_process_tests.sh.in b/src/bin/agent/tests/ca_process_tests.sh.in new file mode 100644 index 0000000..b533152 --- /dev/null +++ b/src/bin/agent/tests/ca_process_tests.sh.in @@ -0,0 +1,174 @@ +#!/bin/sh + +# Copyright (C) 2016-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 + +# Include common test library. +. "@abs_top_builddir@/src/lib/testutils/dhcp_test_lib.sh" + +# Path to the temporary configuration file. +CFG_FILE="@abs_top_builddir@/src/bin/agent/tests/test_config.json" +# Path to the Control Agent log file. +LOG_FILE="@abs_top_builddir@/src/bin/agent/tests/test.log" + +# Control Agent configuration to be stored in the configuration file. +CONFIG="{ + \"Control-agent\": + { + \"http-host\": \"127.0.0.1\", + \"loggers\": [ + { + \"name\": \"kea-ctrl-agent\", + \"output_options\": [ + { + \"output\": \"$LOG_FILE\" + } + ], + \"severity\": \"DEBUG\" + } + ] + } +}" + +# Invalid configuration (syntax error) to check that Kea can check syntax. +CONFIG_BAD_SYNTAX="{ + \"Control-agent\": + { + \"http-port\": BOGUS + } +}" + +# Invalid configuration (out of range port) to check that Kea can check syntax. +CONFIG_BAD_VALUE="{ + \"Control-agent\": + { + \"http-port\": 80000 + } +}" + +# Configuration with a password. +CONFIG_PWD="{ + \"Control-agent\": + { + \"http-host\": \"127.0.0.1\", + \"authentication\": + { + \"clients\": [ + { + \"password\": \"sensitive\", + \"user\": \"superadmin\" + } + ], + \"type\": \"basic\" + } + } +}" + +bin="kea-ctrl-agent" +bin_path="@abs_top_builddir@/src/bin/agent" + +# 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 Control Agent is shut down gracefully when it +# receives a SIGINT or SIGTERM signal. +shutdown_test() { + test_name=${1} # Test name + 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 Control Agent to log to the specific file. + set_logger + # Start Control Agent. + start_kea ${bin_path}/${bin} + # Wait up to 20s for Control Agent to start. + wait_for_kea 20 + if [ "${_WAIT_FOR_KEA}" -eq 0 ]; then + printf "ERROR: timeout waiting for Control Agent 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 Control Agent 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 been configured %s time(s), but exactly 1 was expected.\n' "${_GET_RECONFIGS}" + clean_exit 1 + else + printf "Server successfully configured.\n" + fi + + # Send signal to Control Agent (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: Control Agent 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 +shutdown_test "ctrl-agent.sigterm_test" 15 +shutdown_test "ctrl-agent.sigint_test" 2 +syntax_check_test "ctrl-agent.syntax_check_success" "${CONFIG}" 0 +syntax_check_test "ctrl-agent.syntax_check_bad_syntax" "${CONFIG_BAD_SYNTAX}" 1 +syntax_check_test "ctrl-agent.syntax_check_bad_values" "${CONFIG_BAD_VALUE}" 1 +password_redact_test "ctrl-agent.password_redact_test" "${CONFIG_PWD}" 0 diff --git a/src/bin/agent/tests/ca_process_unittests.cc b/src/bin/agent/tests/ca_process_unittests.cc new file mode 100644 index 0000000..37e06f6 --- /dev/null +++ b/src/bin/agent/tests/ca_process_unittests.cc @@ -0,0 +1,90 @@ +// Copyright (C) 2016-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/. + +#include <config.h> +#include <agent/ca_cfg_mgr.h> +#include <agent/ca_process.h> +#include <asiolink/interval_timer.h> +#include <asiolink/io_service.h> +#include <process/testutils/d_test_stubs.h> +#include <boost/date_time/posix_time/posix_time.hpp> +#include <gtest/gtest.h> +#include <functional> + +using namespace boost::posix_time; +using namespace isc; +using namespace isc::agent; +using namespace isc::asiolink; +using namespace isc::process; + +namespace { + +/// @brief CtrlAgentProcess test fixture class. +class CtrlAgentProcessTest : public CtrlAgentProcess, public ::testing::Test { +public: + /// @brief Constructor + CtrlAgentProcessTest() : + CtrlAgentProcess("agent-test", + IOServicePtr(new isc::asiolink::IOService())) { + CtrlAgentCfgContextPtr ctx = getCtrlAgentCfgMgr()->getCtrlAgentCfgContext(); + ctx->setHttpHost("127.0.0.1"); + ctx->setHttpPort(8081); + } + + /// @brief Destructor + virtual ~CtrlAgentProcessTest() { + } + + /// @brief Callback that will invoke shutdown method. + void genShutdownCallback() { + shutdown(isc::data::ConstElementPtr()); + } +}; + +// Test construction of the CtrlAgentProcess object. +TEST(CtrlAgentProcess, construction) { + // Verify that the constructor will fail if given an empty + // io service. + IOServicePtr lcl_io_service; + EXPECT_THROW(CtrlAgentProcess("TestProcess", lcl_io_service), + DProcessBaseError); + + // Verify that the constructor succeeds with a valid io_service + lcl_io_service.reset(new IOService()); + ASSERT_NO_THROW(CtrlAgentProcess("TestProcess", lcl_io_service)); + + // Verify tha the configuration is accessible after construction. + CtrlAgentProcess agent_process("TestProcess", lcl_io_service); + CtrlAgentCfgMgrPtr cfg_mgr = agent_process.getCtrlAgentCfgMgr(); + ASSERT_TRUE(cfg_mgr); +} + +// Verifies that en external call to shutdown causes the run method to +// exit gracefully. +TEST_F(CtrlAgentProcessTest, shutdown) { + // Use an asiolink IntervalTimer and callback to generate the + // shutdown invocation. (Note IntervalTimer setup is in milliseconds). + IntervalTimer timer(*getIoService()); + timer.setup(std::bind(&CtrlAgentProcessTest::genShutdownCallback, this), + 200); + + // 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() >= 100 && + elapsed.total_milliseconds() <= 400); +} + + +} diff --git a/src/bin/agent/tests/ca_response_creator_factory_unittests.cc b/src/bin/agent/tests/ca_response_creator_factory_unittests.cc new file mode 100644 index 0000000..8cef6fe --- /dev/null +++ b/src/bin/agent/tests/ca_response_creator_factory_unittests.cc @@ -0,0 +1,39 @@ +// Copyright (C) 2017 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 <agent/ca_response_creator.h> +#include <agent/ca_response_creator_factory.h> +#include <boost/pointer_cast.hpp> +#include <gtest/gtest.h> + +using namespace isc::agent; + +namespace { + +// This test verifies that create() method always returns the same +// instance of the CtrlAgentResponseCreator object. +TEST(CtrlAgentResponseCreatorFactory, create) { + CtrlAgentResponseCreatorFactory factory; + + // Invoke twice. + CtrlAgentResponseCreatorPtr response1; + CtrlAgentResponseCreatorPtr response2; + ASSERT_NO_THROW(response1 = boost::dynamic_pointer_cast< + CtrlAgentResponseCreator>(factory.create())); + ASSERT_NO_THROW(response2 = boost::dynamic_pointer_cast< + CtrlAgentResponseCreator>(factory.create())); + + // It must always return non-null object. + ASSERT_TRUE(response1); + ASSERT_TRUE(response2); + + // And it must always return the same object. + EXPECT_TRUE(response1 == response2); + +} + +} diff --git a/src/bin/agent/tests/ca_response_creator_unittests.cc b/src/bin/agent/tests/ca_response_creator_unittests.cc new file mode 100644 index 0000000..174bd27 --- /dev/null +++ b/src/bin/agent/tests/ca_response_creator_unittests.cc @@ -0,0 +1,465 @@ +// Copyright (C) 2017-2023 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 <agent/ca_controller.h> +#include <agent/ca_process.h> +#include <agent/ca_command_mgr.h> +#include <agent/ca_response_creator.h> +#include <cc/command_interpreter.h> +#include <cryptolink/crypto_rng.h> +#include <hooks/hooks_manager.h> +#include <http/basic_auth_config.h> +#include <http/post_request.h> +#include <http/post_request_json.h> +#include <http/response_json.h> +#include <process/testutils/d_test_stubs.h> +#include <agent/tests/test_basic_auth_libraries.h> +#include <gtest/gtest.h> +#include <boost/pointer_cast.hpp> +#include <functional> + +using namespace isc; +using namespace isc::agent; +using namespace isc::config; +using namespace isc::data; +using namespace isc::hooks; +using namespace isc::http; +using namespace isc::process; +namespace ph = std::placeholders; + +namespace { + +/// @brief Test fixture class for @ref CtrlAgentResponseCreator. +class CtrlAgentResponseCreatorTest : public DControllerTest { +public: + + /// @brief Constructor. + /// + /// Creates instance of the response creator and uses this instance to + /// create "empty" request. It also removes registered commands from the + /// command manager. + CtrlAgentResponseCreatorTest() + : DControllerTest(CtrlAgentController::instance), + response_creator_(), + request_(response_creator_.createNewHttpRequest()) { + // Deregisters commands. + CtrlAgentCommandMgr::instance().deregisterAll(); + CtrlAgentCommandMgr::instance(). + registerCommand("foo", std::bind(&CtrlAgentResponseCreatorTest:: + fooCommandHandler, + this, ph::_1, ph::_2)); + + // Make sure that the request has been initialized properly. + if (!request_) { + ADD_FAILURE() << "CtrlAgentResponseCreator::createNewHttpRequest" + " returns NULL!"; + } + HttpRequest::recordBasicAuth_ = true; + // Initialize process and cfgmgr. + try { + initProcess(); + static_cast<void>(getCtrlAgentCfgContext()); + } catch (const std::exception& ex) { + ADD_FAILURE() << "Initialization failed: " << ex.what(); + } + } + + /// @brief Destructor. + /// + /// Removes registered commands from the command manager. + virtual ~CtrlAgentResponseCreatorTest() { + HttpRequest::recordBasicAuth_ = false; + CtrlAgentCommandMgr::instance().deregisterAll(); + HooksManager::prepareUnloadLibraries(); + static_cast<void>(HooksManager::unloadLibraries()); + } + + /// @brief Fills request context with required data to create new request. + /// + /// @param request Request which context should be configured. + void setBasicContext(const HttpRequestPtr& request) { + request->context()->method_ = "POST"; + request->context()->http_version_major_ = 1; + request->context()->http_version_minor_ = 1; + request->context()->uri_ = "/foo"; + + // Content-Type + HttpHeaderContext content_type; + content_type.name_ = "Content-Type"; + content_type.value_ = "application/json"; + request->context()->headers_.push_back(content_type); + + // Content-Length + HttpHeaderContext content_length; + content_length.name_ = "Content-Length"; + content_length.value_ = "0"; + request->context()->headers_.push_back(content_length); + } + + /// @brief Test creation of stock response. + /// + /// @param status_code Status code to be included in the response. + /// @param must_contain Text that must be present in the textual + /// representation of the generated response. + void testStockResponse(const HttpStatusCode& status_code, + const std::string& must_contain) { + HttpResponsePtr response; + ASSERT_NO_THROW( + response = response_creator_.createStockHttpResponse(request_, + status_code) + ); + ASSERT_TRUE(response); + HttpResponseJsonPtr response_json = boost::dynamic_pointer_cast< + HttpResponseJson>(response); + ASSERT_TRUE(response_json); + // Make sure the response contains the string specified as argument. + EXPECT_TRUE(response_json->toString().find(must_contain) != std::string::npos); + + } + + /// @brief Handler for the 'foo' test command. + /// + /// @param command_name Command name, i.e. 'foo'. + /// @param command_arguments Command arguments (empty). + /// + /// @return Returns response with a single string "bar". + ConstElementPtr fooCommandHandler(const std::string& /*command_name*/, + const ConstElementPtr& /*command_arguments*/) { + ElementPtr arguments = Element::createList(); + arguments->add(Element::create("bar")); + return (createAnswer(CONTROL_RESULT_SUCCESS, arguments)); + } + + /// @brief Returns a pointer to the configuration context. + CtrlAgentCfgContextPtr getCtrlAgentCfgContext() { + CtrlAgentProcessPtr process = + boost::dynamic_pointer_cast<CtrlAgentProcess>(getProcess()); + if (!process) { + isc_throw(Unexpected, "no process"); + } + CtrlAgentCfgMgrPtr cfgmgr = process->getCtrlAgentCfgMgr(); + if (!cfgmgr) { + isc_throw(Unexpected, "no cfgmgr"); + } + CtrlAgentCfgContextPtr ctx = cfgmgr->getCtrlAgentCfgContext(); + if (!ctx) { + isc_throw(Unexpected, "no context"); + } + return (ctx); + } + + /// @brief Instance of the response creator. + CtrlAgentResponseCreator response_creator_; + + /// @brief Instance of the "empty" request. + /// + /// The context belonging to this request may be modified by the unit + /// tests to verify various scenarios of response creation. + HttpRequestPtr request_; +}; + +// This test verifies that the created "empty" request has valid type. +TEST_F(CtrlAgentResponseCreatorTest, createNewHttpRequest) { + // The request must be of PostHttpRequestJson type. + PostHttpRequestJsonPtr request_json = boost::dynamic_pointer_cast< + PostHttpRequestJson>(request_); + ASSERT_TRUE(request_json); +} + +// Test that HTTP version of stock response is set to 1.0 if the request +// context doesn't specify any version. +TEST_F(CtrlAgentResponseCreatorTest, createStockHttpResponseNoVersion) { + testStockResponse(HttpStatusCode::BAD_REQUEST, "HTTP/1.0 400 Bad Request"); +} + +// Test that HTTP version of stock response is set to 1.0 if the request +// version is higher than 1.1. +TEST_F(CtrlAgentResponseCreatorTest, createStockHttpResponseHighVersion) { + request_->context()->http_version_major_ = 2; + testStockResponse(HttpStatusCode::REQUEST_TIMEOUT, + "HTTP/1.0 408 Request Timeout"); +} + +// Test that the server responds with version 1.1 if request version is 1.1. +TEST_F(CtrlAgentResponseCreatorTest, createStockHttpResponseCorrectVersion) { + request_->context()->http_version_major_ = 1; + request_->context()->http_version_minor_ = 1; + testStockResponse(HttpStatusCode::NO_CONTENT, "HTTP/1.1 204 No Content"); +} + +// Test successful server response when the client specifies valid command. +TEST_F(CtrlAgentResponseCreatorTest, createDynamicHttpResponse) { + setBasicContext(request_); + + // Body: "foo" command has been registered in the test fixture constructor. + request_->context()->body_ = "{ \"command\": \"foo\" }"; + + // All requests must be finalized before they can be processed. + ASSERT_NO_THROW(request_->finalize()); + + // Create response from the request. + HttpResponsePtr response; + ASSERT_NO_THROW(response = response_creator_.createHttpResponse(request_)); + ASSERT_TRUE(response); + + // Response must be convertible to HttpResponseJsonPtr. + HttpResponseJsonPtr response_json = boost::dynamic_pointer_cast< + HttpResponseJson>(response); + ASSERT_TRUE(response_json); + + // Response must be successful. + EXPECT_TRUE(response_json->toString().find("HTTP/1.1 200 OK") != + std::string::npos); + // Response must contain JSON body with "result" of 0. + EXPECT_TRUE(response_json->toString().find("\"result\": 0") != + std::string::npos); +} + +// This test verifies that Internal Server Error is returned when invalid C++ +// request type is used. This is considered an error in the server logic. +TEST_F(CtrlAgentResponseCreatorTest, createDynamicHttpResponseInvalidType) { + PostHttpRequestPtr request(new PostHttpRequest()); + setBasicContext(request); + + // Body: "list-commands" is natively supported by the command manager. + request->context()->body_ = "{ \"command\": \"list-commands\" }"; + + // All requests must be finalized before they can be processed. + ASSERT_NO_THROW(request->finalize()); + + HttpResponsePtr response; + ASSERT_NO_THROW(response = response_creator_.createHttpResponse(request)); + ASSERT_TRUE(response); + + // Response must be convertible to HttpResponseJsonPtr. + HttpResponseJsonPtr response_json = boost::dynamic_pointer_cast< + HttpResponseJson>(response); + ASSERT_TRUE(response_json); + + // Response must contain Internal Server Error status code. + EXPECT_TRUE(response_json->toString().find("HTTP/1.1 500 Internal Server Error") != + std::string::npos); +} + +// This test verifies that Unauthorized is returned when authentication is +// required but not provided by request. +TEST_F(CtrlAgentResponseCreatorTest, noAuth) { + setBasicContext(request_); + + // Body: "list-commands" is natively supported by the command manager. + request_->context()->body_ = "{ \"command\": \"list-commands\" }"; + + // All requests must be finalized before they can be processed. + ASSERT_NO_THROW(request_->finalize()); + + // Require authentication. + CtrlAgentCfgContextPtr ctx = getCtrlAgentCfgContext(); + ASSERT_TRUE(ctx); + BasicHttpAuthConfigPtr auth(new BasicHttpAuthConfig()); + ASSERT_NO_THROW(ctx->setAuthConfig(auth)); + auth->setRealm("ISC.ORG"); + auth->add("foo", "", "bar", ""); + + HttpResponsePtr response; + ASSERT_NO_THROW(response = response_creator_.createHttpResponse(request_)); + EXPECT_TRUE(request_->getBasicAuth().empty()); + ASSERT_TRUE(response); + + // Response must be convertible to HttpResponseJsonPtr. + HttpResponseJsonPtr response_json = boost::dynamic_pointer_cast< + HttpResponseJson>(response); + ASSERT_TRUE(response_json); + + // Response must contain Unauthorized status code. + std::string expected = "HTTP/1.1 401 Unauthorized"; + EXPECT_TRUE(response_json->toString().find(expected) != std::string::npos); + // Reponse should contain WWW-Authenticate header with configured realm. + expected = "WWW-Authenticate: Basic realm=\"ISC.ORG\""; + EXPECT_TRUE(response_json->toString().find(expected) != std::string::npos); +} + +// Test successful server response when the client is authenticated. +TEST_F(CtrlAgentResponseCreatorTest, basicAuth) { + setBasicContext(request_); + + // Body: "list-commands" is natively supported by the command manager. + request_->context()->body_ = "{ \"command\": \"list-commands\" }"; + + // Add basic HTTP authentication header. + const BasicHttpAuth& basic_auth = BasicHttpAuth("foo", "bar"); + const BasicAuthHttpHeaderContext& basic_auth_header = + BasicAuthHttpHeaderContext(basic_auth); + request_->context()->headers_.push_back(basic_auth_header); + + // All requests must be finalized before they can be processed. + ASSERT_NO_THROW(request_->finalize()); + + // Require authentication. + CtrlAgentCfgContextPtr ctx = getCtrlAgentCfgContext(); + ASSERT_TRUE(ctx); + BasicHttpAuthConfigPtr auth(new BasicHttpAuthConfig()); + ASSERT_NO_THROW(ctx->setAuthConfig(auth)); + // In fact the realm is used only on errors... set it anyway. + auth->setRealm("ISC.ORG"); + auth->add("foo", "", "bar", ""); + + HttpResponsePtr response; + ASSERT_NO_THROW(response = response_creator_.createHttpResponse(request_)); + EXPECT_EQ("foo", request_->getBasicAuth()); + ASSERT_TRUE(response); + + // Response must be convertible to HttpResponseJsonPtr. + HttpResponseJsonPtr response_json = boost::dynamic_pointer_cast< + HttpResponseJson>(response); + ASSERT_TRUE(response_json); + + // Response must be successful. + EXPECT_TRUE(response_json->toString().find("HTTP/1.1 200 OK") != + std::string::npos); + // Response must contain JSON body with "result" of 0. + EXPECT_TRUE(response_json->toString().find("\"result\": 0") != + std::string::npos); +} + +// This test verifies that Unauthorized is returned when authentication is +// required but not provided by request using the hook. +TEST_F(CtrlAgentResponseCreatorTest, hookNoAuth) { + setBasicContext(request_); + + // Body: "list-commands" is natively supported by the command manager. + // We add a random value in the extra entry: see next unit test + // for an explanation about how it is used. + auto r32 = isc::cryptolink::random(4); + ASSERT_EQ(4, r32.size()); + int extra = r32[0]; + extra = (extra << 8) | r32[1]; + extra = (extra << 8) | r32[2]; + extra = (extra << 8) | r32[3]; + request_->context()->body_ = "{ \"command\": \"list-commands\", "; + request_->context()->body_ += "\"extra\": " + std::to_string(extra) + " }"; + + // All requests must be finalized before they can be processed. + ASSERT_NO_THROW(request_->finalize()); + + // Setup hook. + CtrlAgentCfgContextPtr ctx = getCtrlAgentCfgContext(); + ASSERT_TRUE(ctx); + HooksConfig& hooks_cfg = ctx->getHooksConfig(); + std::string auth_cfg = "{ \"config\": {\n" + "\"type\": \"basic\",\n" + "\"realm\": \"ISC.ORG\",\n" + "\"clients\": [{\n" + " \"user\": \"foo\",\n" + " \"password\": \"bar\"\n" + " }]}}"; + ConstElementPtr auth_json; + ASSERT_NO_THROW(auth_json = Element::fromJSON(auth_cfg)); + hooks_cfg.add(std::string(BASIC_AUTH_LIBRARY), auth_json); + ASSERT_NO_THROW(hooks_cfg.loadLibraries(false)); + + HttpResponsePtr response; + ASSERT_NO_THROW(response = response_creator_.createHttpResponse(request_)); + EXPECT_TRUE(request_->getBasicAuth().empty()); + ASSERT_TRUE(response); + + // Request should have no extra. + EXPECT_EQ("{ \"command\": \"list-commands\" }", + request_->context()->body_); + + // Response must be convertible to HttpResponseJsonPtr. + HttpResponseJsonPtr response_json = boost::dynamic_pointer_cast< + HttpResponseJson>(response); + ASSERT_TRUE(response_json); + + // Response must contain Unauthorized status code. + std::string expected = "HTTP/1.1 401 Unauthorized"; + EXPECT_TRUE(response_json->toString().find(expected) != std::string::npos); + // Reponse should contain WWW-Authenticate header with configured realm. + expected = "WWW-Authenticate: Basic realm=\"ISC.ORG\""; + EXPECT_TRUE(response_json->toString().find(expected) != std::string::npos); +} + +// Test successful server response when the client is authenticated. +TEST_F(CtrlAgentResponseCreatorTest, hookBasicAuth) { + setBasicContext(request_); + + // Body: "list-commands" is natively supported by the command manager. + // We add a random value in the extra entry: + // - this proves that the auth callout can get the request argument + // - this proves that the auth callout can modify the request argument + // before the request is executed (the extra entry if still present + // would make the command to be rejected as malformed) + // - this proves that a value can be communicate between the auth + // and response callout points + // - this proves that the response callout can get the response argument + // - this proves that the response callout can modify the response + // argument + auto r32 = isc::cryptolink::random(4); + ASSERT_EQ(4, r32.size()); + int extra = r32[0]; + extra = (extra << 8) | r32[1]; + extra = (extra << 8) | r32[2]; + extra = (extra << 8) | r32[3]; + if (extra == 0) { + extra = 1; + } + std::string extra_str = std::to_string(extra); + request_->context()->body_ = "{ \"command\": \"list-commands\", "; + request_->context()->body_ += "\"extra\": " + extra_str + " }"; + + // Add basic HTTP authentication header. + const BasicHttpAuth& basic_auth = BasicHttpAuth("foo", "bar"); + const BasicAuthHttpHeaderContext& basic_auth_header = + BasicAuthHttpHeaderContext(basic_auth); + request_->context()->headers_.push_back(basic_auth_header); + + // All requests must be finalized before they can be processed. + ASSERT_NO_THROW(request_->finalize()); + + // Setup hook. + CtrlAgentCfgContextPtr ctx = getCtrlAgentCfgContext(); + ASSERT_TRUE(ctx); + HooksConfig& hooks_cfg = ctx->getHooksConfig(); + std::string auth_cfg = "{ \"config\": {\n" + "\"type\": \"basic\",\n" + "\"realm\": \"ISC.ORG\",\n" + "\"clients\": [{\n" + " \"user\": \"foo\",\n" + " \"password\": \"bar\"\n" + " }]}}"; + ConstElementPtr auth_json; + ASSERT_NO_THROW(auth_json = Element::fromJSON(auth_cfg)); + hooks_cfg.add(std::string(BASIC_AUTH_LIBRARY), auth_json); + ASSERT_NO_THROW(hooks_cfg.loadLibraries(false)); + + HttpResponsePtr response; + ASSERT_NO_THROW(response = response_creator_.createHttpResponse(request_)); + EXPECT_EQ("foo", request_->getBasicAuth()); + ASSERT_TRUE(response); + + // Request should have no extra. + EXPECT_EQ("{ \"command\": \"list-commands\" }", + request_->context()->body_); + + // Response must be convertible to HttpResponseJsonPtr. + HttpResponseJsonPtr response_json = boost::dynamic_pointer_cast< + HttpResponseJson>(response); + ASSERT_TRUE(response_json); + + // Response must be successful. + EXPECT_TRUE(response_json->toString().find("HTTP/1.1 200 OK") != + std::string::npos); + // Response must contain JSON body with "result" of 0. + EXPECT_TRUE(response_json->toString().find("\"result\": 0") != + std::string::npos); + // Response must contain JSON body with "comment": "got". + std::string expected = "\"got\": " + extra_str + ", "; + EXPECT_TRUE(response_json->toString().find(expected) != + std::string::npos); +} + +} diff --git a/src/bin/agent/tests/ca_unittests.cc b/src/bin/agent/tests/ca_unittests.cc new file mode 100644 index 0000000..85b494c --- /dev/null +++ b/src/bin/agent/tests/ca_unittests.cc @@ -0,0 +1,27 @@ +// Copyright (C) 2016-2018 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 <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 + // the developer's guide 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/agent/tests/callout_library.cc b/src/bin/agent/tests/callout_library.cc new file mode 100644 index 0000000..e001c6f --- /dev/null +++ b/src/bin/agent/tests/callout_library.cc @@ -0,0 +1,72 @@ +// 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/. + +/// @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/agent/tests/get_config_unittest.cc b/src/bin/agent/tests/get_config_unittest.cc new file mode 100644 index 0000000..40d9c50 --- /dev/null +++ b/src/bin/agent/tests/get_config_unittest.cc @@ -0,0 +1,309 @@ +// Copyright (C) 2017-2022 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 <cc/command_interpreter.h> +#include <testutils/user_context_utils.h> +#include <process/testutils/d_test_stubs.h> +#include <agent/ca_cfg_mgr.h> +#include <agent/parser_context.h> +#include <boost/scoped_ptr.hpp> +#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::agent; +using namespace isc::config; +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 ca_unittests on +/// CtrlAgentGetCfgTest redirecting the standard error to a temporary +/// file, e.g. by +/// @code +/// ./ca_unittests --gtest_filter="CtrlAgentGetCfg*" > /dev/null 2> u +/// @endcode +/// +/// Update testdata/get_config.json using the temporary file content, +/// (removing head comment and restoring hook library path), +/// 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 { + ParserContext ctx; + return (ctx.parseString(in, ParserContext::PARSER_JSON)); + } catch (const std::exception& ex) { + if (verbose) { + std::cout << "EXCEPTION: " << ex.what() << std::endl; + } + throw; + } +} + +/// @brief Runs parser in AGENT mode +ElementPtr +parseAGENT(const std::string& in, bool verbose = false) { + try { + ParserContext ctx; + return (ctx.parseString(in, ParserContext::PARSER_AGENT)); + } catch (const std::exception& ex) { + if (verbose) { + std::cout << "EXCEPTION: " << ex.what() << std::endl; + } + throw; + } +} + +/// @brief Replace the library path +void +pathReplacer(ConstElementPtr ca_cfg) { + ConstElementPtr hooks_libs = ca_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)); +} + +/// @brief Replace the credential directory +void +dirReplacer(ConstElementPtr ca_cfg) { + ConstElementPtr auth = ca_cfg->get("authentication"); + if (!auth || auth->empty()) { + return; + } + ElementPtr mutable_auth = boost::const_pointer_cast<Element>(auth); + std::string dir_path(CA_TEST_DATA_DIR); + mutable_auth->set("directory", Element::create(dir_path)); +} + +/// @brief Almost regular agent CfgMgr with internal parse method exposed. +class NakedAgentCfgMgr : public CtrlAgentCfgMgr { +public: + using CtrlAgentCfgMgr::parse; +}; + +} + +/// Test fixture class +class CtrlAgentGetCfgTest : public ConfigParseTest { +public: + CtrlAgentGetCfgTest() + : rcode_(-1) { + srv_.reset(new NakedAgentCfgMgr()); + // Create fresh context. + resetConfiguration(); + } + + ~CtrlAgentGetCfgTest() { + 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 AGENT parser + try { + json = parseAGENT(config, true); + } catch (...) { + ADD_FAILURE() << "parsing failed for " << operation + << " on\n" << prettyPrint(json) << "\n"; + return (false); + } + + // get Control-agent element + ConstElementPtr ca = json->get("Control-agent"); + if (!ca) { + ADD_FAILURE() << "cannot get Control-agent for " << operation + << " on\n" << prettyPrint(json) << "\n"; + return (false); + } + + // update hooks-libraries + pathReplacer(ca); + + // update authentication directory + dirReplacer(ca); + + // try AGENT configure + ConstElementPtr status; + try { + status = srv_->parse(ca, true); + } 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 and hooks. 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 = "{ \"Control-agent\": {" + " \"http-host\": \"\"," + " \"http-port\": 0 } }"; + EXPECT_TRUE(executeConfiguration(config, "reset config")); + } + + boost::scoped_ptr<NakedAgentCfgMgr> srv_; ///< CA server under test + int rcode_; ///< Return code from element parsing + ConstElementPtr comment_; ///< Reason for parse fail +}; + +/// Test a configuration +TEST_F(CtrlAgentGetCfgTest, simple) { + + // get the simple configuration + std::string simple_file = string(CFG_EXAMPLES) + "/" + "simple.json"; + std::string config; + ASSERT_NO_THROW(config = readFile(simple_file)); + + // get the expected configuration + std::string expected_file = + std::string(CA_TEST_DATA_DIR) + "/" + "get_config.json"; + std::string expected; + ASSERT_NO_THROW(expected = readFile(expected_file)); + + // execute the sample configuration + ASSERT_TRUE(executeConfiguration(config, "simple config")); + + // unparse it + CtrlAgentCfgContextPtr context = srv_->getCtrlAgentCfgContext(); + 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 agent syntax parser + ElementPtr jsond; + ASSERT_NO_THROW(jsond = parseAGENT(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 paths by their actual values + ConstElementPtr ca; + ASSERT_NO_THROW(ca = jsonj->get("Control-agent")); + ASSERT_TRUE(ca); + pathReplacer(ca); + dirReplacer(ca); + // check that unparsed and updated 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 control agent configuration + EXPECT_TRUE(executeConfiguration(expected, "unparsed config")); + + // is it a fixed point? + CtrlAgentCfgContextPtr context2 = srv_->getCtrlAgentCfgContext(); + ConstElementPtr unparsed2; + ASSERT_NO_THROW(unparsed2 = context2->toElement()); + ASSERT_TRUE(unparsed2); + EXPECT_TRUE(isEquivalent(unparsed, unparsed2)); +} diff --git a/src/bin/agent/tests/parser_unittests.cc b/src/bin/agent/tests/parser_unittests.cc new file mode 100644 index 0000000..df80ab2 --- /dev/null +++ b/src/bin/agent/tests/parser_unittests.cc @@ -0,0 +1,936 @@ +// Copyright (C) 2017-2023 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 <agent/parser_context.h> +#include <cc/data.h> +#include <cc/dhcp_config_error.h> +#include <testutils/io_utils.h> +#include <testutils/log_utils.h> +#include <testutils/test_to_element.h> +#include <testutils/user_context_utils.h> + +#include <gtest/gtest.h> + +#include <fstream> +#include <set> + +#include <boost/algorithm/string.hpp> + +using namespace isc::data; +using namespace isc::test; +using namespace std; + +namespace isc { +namespace agent { +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()) +#ifdef HAVE_CREATE_UNIFIED_DIFF + << "\nDiff:\n" << generateDiff(prettyPrint(a), prettyPrint(b)) << "\n" +#endif + ; +} + +/// @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, ParserContext::ParserType parser_type, + bool compare = true) { + SCOPED_TRACE("\n=== tested config ===\n" + txt + "====================="); + + ConstElementPtr test_json; + ASSERT_NO_THROW({ + try { + ParserContext 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); +} + +TEST(ParserTest, mapInMap) { + string txt = "{ \"xyzzy\": { \"foo\": 123, \"baz\": 456 } }"; + testParser(txt, ParserContext::PARSER_JSON); +} + +TEST(ParserTest, listInList) { + string txt = "[ [ \"Britain\", \"Wales\", \"Scotland\" ], " + "[ \"Pomorze\", \"Wielkopolska\", \"Tatry\"] ]"; + testParser(txt, ParserContext::PARSER_JSON); +} + +TEST(ParserTest, nestedMaps) { + string txt = "{ \"europe\": { \"UK\": { \"London\": { \"street\": \"221B Baker\" }}}}"; + testParser(txt, ParserContext::PARSER_JSON); +} + +TEST(ParserTest, nestedLists) { + string txt = "[ \"half\", [ \"quarter\", [ \"eighth\", [ \"sixteenth\" ]]]]"; + testParser(txt, ParserContext::PARSER_JSON); +} + +TEST(ParserTest, listsInMaps) { + string txt = "{ \"constellations\": { \"orion\": [ \"rigel\", \"betelgeuse\" ], " + "\"cygnus\": [ \"deneb\", \"albireo\"] } }"; + testParser(txt, ParserContext::PARSER_JSON); +} + +TEST(ParserTest, mapsInLists) { + string txt = "[ { \"body\": \"earth\", \"gravity\": 1.0 }," + " { \"body\": \"mars\", \"gravity\": 0.376 } ]"; + testParser(txt, ParserContext::PARSER_JSON); +} + +TEST(ParserTest, types) { + string txt = "{ \"string\": \"foo\"," + "\"integer\": 42," + "\"boolean\": true," + "\"map\": { \"foo\": \"bar\" }," + "\"list\": [ 1, 2, 3 ]," + "\"null\": null }"; + testParser(txt, ParserContext::PARSER_JSON); +} + +TEST(ParserTest, keywordJSON) { + string txt = "{ \"name\": \"user\"," + "\"type\": \"password\"," + "\"user\": \"name\"," + "\"password\": \"type\" }"; + testParser(txt, ParserContext::PARSER_JSON); +} + +// This test checks if full config (with top level and Control-agent objects) can +// be parsed with syntactic checking (and as pure JSON). +TEST(ParserTest, keywordAgent) { + string txt = "{ \"Control-agent\": {\n" + " \"http-host\": \"localhost\",\n" + " \"http-port\": 8000,\n" + " \"control-sockets\": {" + " \"dhcp4\": {" + " \"socket-type\": \"unix\"," + " \"socket-name\": \"/tmp/kea4-ctrl-socket\"" + " }," + " \"dhcp6\": {" + " \"socket-type\": \"unix\"," + " \"socket-name\": \"/tmp/kea6-ctrl-socket\"" + " }," + " \"d2\": {" + " \"socket-type\": \"unix\"," + " \"socket-name\": \"/tmp/kea-ddns-ctrl-socket\"" + " }" + " }," + " \"hooks-libraries\": [" + " {" + " \"library\": \"/opt/local/control-agent-commands.so\"," + " \"parameters\": {" + " \"param1\": \"foo\"" + " }" + " }" + " ]" + "} }"; + // This is a full config, so we'll parse it as full config (PARSER_AGENT) + testParser(txt, ParserContext::PARSER_AGENT); + testParser(txt, ParserContext::PARSER_JSON); +} + +// This test checks if simplified config (without top level and Control-agent +// objects) can be parsed with syntactic checking (and as pure JSON). +TEST(ParserTest, keywordSubAgent) { + + // This is similar to previous test, but note the lack of outer + // map and Control-agent. + string txt = "{\n" + " \"http-host\": \"localhost\",\n" + " \"http-port\": 8000,\n" + " \"control-sockets\": {" + " \"dhcp4\": {" + " \"socket-type\": \"unix\"," + " \"socket-name\": \"/tmp/kea4-ctrl-socket\"" + " }," + " \"dhcp6\": {" + " \"socket-type\": \"unix\"," + " \"socket-name\": \"/tmp/kea6-ctrl-socket\"" + " }," + " \"d2\": {" + " \"socket-type\": \"unix\"," + " \"socket-name\": \"/tmp/kea-ddns-ctrl-socket\"" + " }" + " }," + " \"hooks-libraries\": [" + " {" + " \"library\": \"/opt/local/control-agent-commands.so\"," + " \"parameters\": {" + " \"param1\": \"foo\"" + " }" + " }" + " ]" + "}"; + + // This is only a subset of full config, so we'll parse with PARSER_SUB_AGENT. + testParser(txt, ParserContext::PARSER_SUB_AGENT); + testParser(txt, ParserContext::PARSER_JSON); +} + +// Tests if bash (#) comments are supported. That's the only comment type that +// was supported by the old parser. +TEST(ParserTest, bashComments) { + string txt= "{ \"Control-agent\": {" + " \"http-host\": \"localhost\"," + " \"http-port\": 9000,\n" + " \"control-sockets\": {\n" + " \"d2\": {\n" + "# this is a comment\n" + "\"socket-type\": \"unix\", \n" + "# This socket is mine. I can name it whatever\n" + "# I like, ok?\n" + "\"socket-name\": \"Hector\" \n" + "} } } }"; + testParser(txt, ParserContext::PARSER_AGENT); +} + +// Tests if C++ (//) comments can start anywhere, not just in the first line. +TEST(ParserTest, cppComments) { + string txt= "{ \"Control-agent\": {" + " \"http-host\": \"localhost\"," + " \"http-port\": 9001, // the level is over 9000!\n" + " \"control-sockets\": {\n" + " // Let's try talking to D2. Sadly, it never talks" + " // to us back :( Maybe he doesn't like his name?\n" + " \"d2\": {" + "\"socket-type\": \"unix\", \n" + "\"socket-name\": \"Hector\" \n" + "} } } }"; + + testParser(txt, ParserContext::PARSER_AGENT, false); +} + +// Tests if bash (#) comments can start anywhere, not just in the first line. +TEST(ParserTest, bashCommentsInline) { + string txt= "{ \"Control-agent\": {" + " \"http-host\": \"localhost\"," + " \"http-port\": 9000,\n" + " \"control-sockets\": {\n" + " \"d2\": {" + "\"socket-type\": \"unix\", # Maybe Hector is not really a \n" + "\"socket-name\": \"Hector\" # Unix process?\n" + "# Oh no! He's a windows one and just pretending!\n" + "} } } }"; + testParser(txt, ParserContext::PARSER_AGENT, false); +} + +// Tests if multi-line C style comments are handled correctly. +TEST(ParserTest, multilineComments) { + string txt= "{ \"Control-agent\": {" + " \"http-host\": \"localhost\"," + " \"http-port\": 9000,\n" + " \"control-sockets\": {\n" + " \"dhcp4\": {\n" + " \"socket-type\": \"unix\"\n" + " }\n" + " /* Ok, forget about it. If Hector doesn't want to talk,\n" + " we won't talk to him either. We now have quiet days. */\n" + " /* \"d2\": {" + " \"socket-type\": \"unix\",\n" + "\"socket-name\": \"Hector\"\n" + "}*/ } } }"; + testParser(txt, ParserContext::PARSER_AGENT, false); +} + +// Tests if embedded comments are handled correctly. +TEST(ParserTest, embbededComments) { + string txt= "{ \"Control-agent\": {" + " \"comment\": \"a comment\"," + " \"http-host\": \"localhost\"," + " \"http-port\": 9000,\n" + " \"control-sockets\": {\n" + " \"dhcp4\": {\n" + " \"user-context\": { \"comment\": \"indirect\" },\n" + " \"socket-type\": \"unix\"\n" + " } },\n" + " \"user-context\": { \"compatible\": true }\n" + "} }"; + testParser(txt, ParserContext::PARSER_AGENT, 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; + + EXPECT_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 { + ParserContext ctx; + test_json = ctx.parseFile(fname, ParserContext::PARSER_AGENT); + } 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 AgentParser. Both JSON trees are then compared. +// Hopefully the list of example configs will grow over time. +TEST(ParserTest, file) { + vector<string> configs; + configs.push_back("comments.json"); + configs.push_back("https.json"); + configs.push_back("simple.json"); + + for (int i = 0; i<configs.size(); i++) { + testFile(string(CFG_EXAMPLES) + "/" + configs[i]); + } +} + +/// @brief Tests error conditions in AgentParser +/// +/// @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, + ParserContext::ParserType parser_type, + const std::string& msg) { + SCOPED_TRACE("\n=== tested config ===\n" + txt + "====================="); + + try { + ParserContext ctx; + ConstElementPtr parsed = ctx.parseString(txt, parser_type); + FAIL() << "Expected ParseError but nothing was raised (expected: " + << msg << ")"; + } + catch (const ParseError& ex) { + EXPECT_EQ(msg, ex.what()); + } + catch (...) { + FAIL() << "Expected ParseError but something else was raised"; + } +} + +// Verify that error conditions are handled correctly. +TEST(ParserTest, errors) { + // no input + testError("", ParserContext::PARSER_JSON, + "<string>:1.1: syntax error, unexpected end of file"); + testError(" ", ParserContext::PARSER_JSON, + "<string>:1.2: syntax error, unexpected end of file"); + testError("\n", ParserContext::PARSER_JSON, + "<string>:2.1: syntax error, unexpected end of file"); + testError("\t", ParserContext::PARSER_JSON, + "<string>:1.2: syntax error, unexpected end of file"); + testError("\r", ParserContext::PARSER_JSON, + "<string>:1.2: syntax error, unexpected end of file"); + + // comments + testError("# nothing\n", + ParserContext::PARSER_JSON, + "<string>:2.1: syntax error, unexpected end of file"); + testError(" #\n", + ParserContext::PARSER_JSON, + "<string>:2.1: syntax error, unexpected end of file"); + testError("// nothing\n", + ParserContext::PARSER_JSON, + "<string>:2.1: syntax error, unexpected end of file"); + testError("/* nothing */\n", + ParserContext::PARSER_JSON, + "<string>:2.1: syntax error, unexpected end of file"); + testError("/* no\nthing */\n", + ParserContext::PARSER_JSON, + "<string>:3.1: syntax error, unexpected end of file"); + testError("/* no\nthing */\n\n", + ParserContext::PARSER_JSON, + "<string>:4.1: syntax error, unexpected end of file"); + testError("/* nothing\n", + ParserContext::PARSER_JSON, + "Comment not closed. (/* in line 1"); + testError("\n\n\n/* nothing\n", + ParserContext::PARSER_JSON, + "Comment not closed. (/* in line 4"); + testError("{ /* */*/ }\n", + ParserContext::PARSER_JSON, + "<string>:1.3-8: Invalid character: *"); + testError("{ /* // *// }\n", + ParserContext::PARSER_JSON, + "<string>:1.3-11: Invalid character: /"); + testError("{ /* // */// }\n", + ParserContext::PARSER_JSON, + "<string>:2.1: syntax error, unexpected end of file, " + "expecting }"); + + // includes + testError("<?\n", + ParserContext::PARSER_JSON, + "Directive not closed."); + testError("<?include\n", + ParserContext::PARSER_JSON, + "Directive not closed."); + string file = string(CFG_EXAMPLES) + "/" + "simple.json"; + testError("<?include \"" + file + "\"\n", + ParserContext::PARSER_JSON, + "Directive not closed."); + testError("<?include \"/foo/bar\" ?>/n", + ParserContext::PARSER_JSON, + "Can't open include file /foo/bar"); + + // JSON keywords + testError("{ \"foo\": True }", + ParserContext::PARSER_JSON, + "<string>:1.10-13: JSON true reserved keyword is lower case only"); + testError("{ \"foo\": False }", + ParserContext::PARSER_JSON, + "<string>:1.10-14: JSON false reserved keyword is lower case only"); + testError("{ \"foo\": NULL }", + ParserContext::PARSER_JSON, + "<string>:1.10-13: JSON null reserved keyword is lower case only"); + testError("{ \"foo\": Tru }", + ParserContext::PARSER_JSON, + "<string>:1.10: Invalid character: T"); + testError("{ \"foo\": nul }", + ParserContext::PARSER_JSON, + "<string>:1.10: Invalid character: n"); + + // numbers + testError("123", + ParserContext::PARSER_AGENT, + "<string>:1.1-3: syntax error, unexpected integer, " + "expecting {"); + testError("-456", + ParserContext::PARSER_AGENT, + "<string>:1.1-4: syntax error, unexpected integer, " + "expecting {"); + testError("-0001", + ParserContext::PARSER_AGENT, + "<string>:1.1-5: syntax error, unexpected integer, " + "expecting {"); + testError("1234567890123456789012345678901234567890", + ParserContext::PARSER_JSON, + "<string>:1.1-40: Failed to convert " + "1234567890123456789012345678901234567890" + " to an integer."); + testError("-3.14e+0", + ParserContext::PARSER_AGENT, + "<string>:1.1-8: syntax error, unexpected floating point, " + "expecting {"); + testError("1e50000", + ParserContext::PARSER_JSON, + "<string>:1.1-7: Failed to convert 1e50000 " + "to a floating point."); + + // strings + testError("\"aabb\"", + ParserContext::PARSER_AGENT, + "<string>:1.1-6: syntax error, unexpected constant string, " + "expecting {"); + testError("{ \"aabb\"err", + ParserContext::PARSER_JSON, + "<string>:1.9: Invalid character: e"); + testError("{ err\"aabb\"", + ParserContext::PARSER_JSON, + "<string>:1.3: Invalid character: e"); + testError("\"a\n\tb\"", + ParserContext::PARSER_JSON, + "<string>:1.1-6 (near 2): Invalid control in \"a\n\tb\""); + testError("\"a\n\\u12\"", + ParserContext::PARSER_JSON, + "<string>:1.1-8 (near 2): Invalid control in \"a\n\\u12\""); + testError("\"a\\n\\tb\"", + ParserContext::PARSER_AGENT, + "<string>:1.1-8: syntax error, unexpected constant string, " + "expecting {"); + testError("\"a\\x01b\"", + ParserContext::PARSER_JSON, + "<string>:1.1-8 (near 3): Bad escape in \"a\\x01b\""); + testError("\"a\\u0162\"", + ParserContext::PARSER_JSON, + "<string>:1.1-9 (near 4): Unsupported unicode escape " + "in \"a\\u0162\""); + testError("\"a\\u062z\"", + ParserContext::PARSER_JSON, + "<string>:1.1-9 (near 3): Bad escape in \"a\\u062z\""); + testError("\"abc\\\"", + ParserContext::PARSER_JSON, + "<string>:1.1-6 (near 6): Overflow escape in \"abc\\\""); + testError("\"a\\u006\"", + ParserContext::PARSER_JSON, + "<string>:1.1-8 (near 3): Overflow unicode escape " + "in \"a\\u006\""); + testError("\"\\u\"", + ParserContext::PARSER_JSON, + "<string>:1.1-4 (near 2): Overflow unicode escape in \"\\u\""); + testError("\"\\u\x02\"", + ParserContext::PARSER_JSON, + "<string>:1.1-5 (near 2): Bad escape in \"\\u\x02\""); + testError("\"\\u\\\"foo\"", + ParserContext::PARSER_JSON, + "<string>:1.1-5 (near 2): Bad escape in \"\\u\\\"..."); + testError("\"\x02\\u\"", + ParserContext::PARSER_JSON, + "<string>:1.1-5 (near 1): Invalid control in \"\x02\\u\""); + + // from data_unittest.c + testError("\\a", + ParserContext::PARSER_JSON, + "<string>:1.1: Invalid character: \\"); + testError("\\", + ParserContext::PARSER_JSON, + "<string>:1.1: Invalid character: \\"); + testError("\\\"\\\"", + ParserContext::PARSER_JSON, + "<string>:1.1: Invalid character: \\"); + + // want a map + testError("[]\n", + ParserContext::PARSER_AGENT, + "<string>:1.1: syntax error, unexpected [, " + "expecting {"); + testError("[]\n", + ParserContext::PARSER_AGENT, + "<string>:1.1: syntax error, unexpected [, " + "expecting {"); + testError("{ 123 }\n", + ParserContext::PARSER_JSON, + "<string>:1.3-5: syntax error, unexpected integer, " + "expecting }"); + testError("{ 123 }\n", + ParserContext::PARSER_AGENT, + "<string>:1.3-5: syntax error, unexpected integer, " + "expecting Control-agent"); + testError("{ \"foo\" }\n", + ParserContext::PARSER_JSON, + "<string>:1.9: syntax error, unexpected }, " + "expecting :"); + testError("{ \"foo\" }\n", + ParserContext::PARSER_AGENT, + "<string>:1.3-7: syntax error, unexpected constant string, " + "expecting Control-agent"); + testError("{ \"foo\":null }\n", + ParserContext::PARSER_AGENT, + "<string>:1.3-7: syntax error, unexpected constant string, " + "expecting Control-agent"); + testError("{ \"Logging\":null }\n", + ParserContext::PARSER_AGENT, + "<string>:1.3-11: syntax error, unexpected constant string, " + "expecting Control-agent"); + testError("{ \"Control-agent\" }\n", + ParserContext::PARSER_AGENT, + "<string>:1.19: syntax error, unexpected }, " + "expecting :"); + testError("{ \"Control-agent\":", + ParserContext::PARSER_AGENT, + "<string>:1.19: syntax error, unexpected end of file, " + "expecting {"); + testError("{}{}\n", + ParserContext::PARSER_JSON, + "<string>:1.3: syntax error, unexpected {, " + "expecting end of file"); + + // duplicate in map + testError("{ \"foo\": 1, \"foo\": true }\n", + ParserContext::PARSER_JSON, + "<string>:1:13: duplicate foo entries in " + "JSON map (previous at <string>:1:10)"); + + // bad commas + testError("{ , }\n", + ParserContext::PARSER_JSON, + "<string>:1.3: syntax error, unexpected \",\", " + "expecting }"); + testError("{ , \"foo\":true }\n", + ParserContext::PARSER_JSON, + "<string>:1.3: syntax error, unexpected \",\", " + "expecting }"); + + // bad type + testError("{ \"Control-agent\":{\n" + " \"http-port\":false }}\n", + ParserContext::PARSER_AGENT, + "<string>:2.15-19: syntax error, unexpected boolean, " + "expecting integer"); + + // unknown keyword + testError("{ \"Control-agent\":{\n" + " \"topping\": \"Mozzarella\" }}\n", + ParserContext::PARSER_AGENT, + "<string>:2.2-10: got unexpected keyword " + "\"topping\" in Control-agent map."); + + // user context and embedded comments + testError("{ \"Control-agent\":{\n" + " \"comment\": true,\n" + " \"http-port\": 9000 }}\n", + ParserContext::PARSER_AGENT, + "<string>:2.14-17: syntax error, unexpected boolean, " + "expecting constant string"); + + testError("{ \"Control-agent\":{\n" + " \"user-context\": \"a comment\",\n" + " \"http-port\": 9000 }}\n", + ParserContext::PARSER_AGENT, + "<string>:2.19-29: syntax error, unexpected constant string, " + "expecting {"); + + testError("{ \"Control-agent\":{\n" + " \"comment\": \"a comment\",\n" + " \"comment\": \"another one\",\n" + " \"http-port\": 9000 }}\n", + ParserContext::PARSER_AGENT, + "<string>:3.3-11: duplicate user-context/comment entries " + "(previous at <string>:2:3)"); + + testError("{ \"Control-agent\":{\n" + " \"user-context\": { \"version\": 1 },\n" + " \"user-context\": { \"one\": \"only\" },\n" + " \"http-port\": 9000 }}\n", + ParserContext::PARSER_AGENT, + "<string>:3.3-16: duplicate user-context entries " + "(previous at <string>:2:19)"); + + testError("{ \"Control-agent\":{\n" + " \"user-context\": { \"comment\": \"indirect\" },\n" + " \"comment\": \"a comment\",\n" + " \"http-port\": 9000 }}\n", + ParserContext::PARSER_AGENT, + "<string>:3.3-11: duplicate user-context/comment entries " + "(previous at <string>:2:19)"); + + // duplicate Control-agent entries + testError("{ \"Control-agent\":{\n" + " \"comment\": \"first\" },\n" + " \"Control-agent\":{\n" + " \"comment\": \"second\" }}\n", + ParserContext::PARSER_AGENT, + "<string>:3.3-17: syntax error, unexpected Control-agent, expecting \",\" or }"); + + // duplicate of not string entries + testError("{ \"Control-agent\":{\n" + " \"http-port\": 8000,\n" + " \"http-port\": 8001 }}\n", + ParserContext::PARSER_AGENT, + "<string>:3:2: duplicate http-port entries in " + "Control-agent map (previous at <string>:2:15)"); + + // duplicate of string entries + testError("{ \"Control-agent\":{\n" + " \"http-host\": \"127.0.0.1\",\n" + " \"http-host\": \"::1\" }}\n", + ParserContext::PARSER_AGENT, + "<string>:3:2: duplicate http-host entries in " + "Control-agent map (previous at <string>:2:15)"); +} + +// 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 { + ParserContext ctx; + result = ctx.parseString(json, ParserContext::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 { + ParserContext ctx; + result = ctx.parseString(json, ParserContext::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) { + ParserContext ctx; + ElementPtr json; + EXPECT_NO_THROW(json = ctx.parseFile(fname, ParserContext::PARSER_AGENT)); + 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 (agent_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 sample files + string sample_dir(CFG_EXAMPLES); + sample_dir += "/"; + ElementPtr sample_json = Element::createList(); + loadFile(sample_dir + "https.json", sample_json); + loadFile(sample_dir + "simple.json", sample_json); + KeywordSet sample_keys; + // 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); + ParserContext ctx; + EXPECT_THROW(ctx.parseString(before + after, + ParserContext::PARSER_AGENT), + ParseError) << "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 += "/simple.json"; + ParserContext ctx; + ElementPtr sample_json; + EXPECT_NO_THROW(sample_json = + ctx.parseFile(sample_fname, ParserContext::PARSER_AGENT)); + 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 = "CTRL_AGENT_CONFIG_SYNTAX_WARNING Control Agent "; + 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"({ + "Control-agent": { + "control-sockets": { + "d2": { + "socket-name": "/tmp/kea-dhcp-ddns-ctrl.sock", + "socket-type": "unix", + }, + "dhcp4": { + "socket-name": "/tmp/kea-dhcp4-ctrl.sock", + "socket-type": "unix", + }, + "dhcp6": { + "socket-name": "/tmp/kea-dhcp6-ctrl.sock", + "socket-type": "unix", + }, + }, + "http-host": "10.1.0.2", + "http-port": 8080, + "loggers": [ + { + "debuglevel": 99, + "name": "kea-ctrl-agent", + "output_options": [ + { + "output": "stdout", + }, + ], + "severity": "DEBUG", + }, + ], + }, +})"); + testParser(txt, ParserContext::PARSER_AGENT, false); + + addLog("<string>:6.30"); + addLog("<string>:10.30"); + addLog("<string>:14.30"); + addLog("<string>:15.8"); + addLog("<string>:25.31"); + addLog("<string>:26.12"); + addLog("<string>:28.28"); + addLog("<string>:29.8"); + addLog("<string>:30.6"); + addLog("<string>:31.4"); + EXPECT_TRUE(checkFile()); + + // Test with many consecutive commas. + boost::replace_all(txt, ",", ",,,,"); + testParser(txt, ParserContext::PARSER_AGENT, false); +} + +} // namespace test +} // namespace agent +} // namespace isc diff --git a/src/bin/agent/tests/test_basic_auth_libraries.h.in b/src/bin/agent/tests/test_basic_auth_libraries.h.in new file mode 100644 index 0000000..afa3da3 --- /dev/null +++ b/src/bin/agent/tests/test_basic_auth_libraries.h.in @@ -0,0 +1,24 @@ +// Copyright (C) 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 AGENT_TEST_BASIC_AUTH_LIBRARIES_H +#define AGENT_TEST_BASIC_AUTH_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 HTTP authentication as a callout library. +static const char* BASIC_AUTH_LIBRARY = "@abs_builddir@/.libs/libbasicauth.so"; + +} // anonymous namespace + +#endif // TEST_BASIC_AUTH_LIBRARIES_H diff --git a/src/bin/agent/tests/test_callout_libraries.h.in b/src/bin/agent/tests/test_callout_libraries.h.in new file mode 100644 index 0000000..78f51c8 --- /dev/null +++ b/src/bin/agent/tests/test_callout_libraries.h.in @@ -0,0 +1,24 @@ +// 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 AGENT_TEST_CALLOUT_LIBRARIES_H +#define AGENT_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 // TEST_LIBRARIES_H diff --git a/src/bin/agent/tests/test_data_files_config.h.in b/src/bin/agent/tests/test_data_files_config.h.in new file mode 100644 index 0000000..b1d1124 --- /dev/null +++ b/src/bin/agent/tests/test_data_files_config.h.in @@ -0,0 +1,9 @@ +// Copyright (C) 2017 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 agent source dir +#define CA_SRC_DIR "@abs_top_srcdir@/src/bin/agent" +#define CA_TEST_DATA_DIR "@abs_top_srcdir@/src/bin/agent/tests/testdata" diff --git a/src/bin/agent/tests/testdata/get_config.json b/src/bin/agent/tests/testdata/get_config.json new file mode 100644 index 0000000..3117d66 --- /dev/null +++ b/src/bin/agent/tests/testdata/get_config.json @@ -0,0 +1,55 @@ +{ + "Control-agent": { + "authentication": { + "clients": [ + { + "password": "1234", + "user": "admin", + "user-context": { + "comment": "admin is authorized" + } + }, + { + "password-file": "hiddenp", + "user-file": "hiddenu" + }, + { + "password-file": "hiddens" + } + ], + "directory": "/tmp/kea-creds", + "realm": "kea-control-agent", + "type": "basic" + }, + "control-sockets": { + "d2": { + "socket-name": "/tmp/kea-ddns-ctrl-socket", + "socket-type": "unix", + "user-context": { + "in-use": false + } + }, + "dhcp4": { + "socket-name": "/tmp/kea4-ctrl-socket", + "socket-type": "unix", + "user-context": { + "comment": "socket to DHCPv4 server" + } + }, + "dhcp6": { + "socket-name": "/tmp/kea6-ctrl-socket", + "socket-type": "unix" + } + }, + "hooks-libraries": [ + { + "library": "/opt/local/control-agent-commands.so", + "parameters": { + "param1": "foo" + } + } + ], + "http-host": "127.0.0.1", + "http-port": 8000 + } +} diff --git a/src/bin/agent/tests/testdata/hiddenp b/src/bin/agent/tests/testdata/hiddenp new file mode 100644 index 0000000..e8b2395 --- /dev/null +++ b/src/bin/agent/tests/testdata/hiddenp @@ -0,0 +1 @@ +KeaTest
\ No newline at end of file diff --git a/src/bin/agent/tests/testdata/hiddens b/src/bin/agent/tests/testdata/hiddens new file mode 100644 index 0000000..f52fb83 --- /dev/null +++ b/src/bin/agent/tests/testdata/hiddens @@ -0,0 +1 @@ +kea:test
\ No newline at end of file diff --git a/src/bin/agent/tests/testdata/hiddenu b/src/bin/agent/tests/testdata/hiddenu new file mode 100644 index 0000000..801489a --- /dev/null +++ b/src/bin/agent/tests/testdata/hiddenu @@ -0,0 +1 @@ +keatest
\ No newline at end of file |