summaryrefslogtreecommitdiffstats
path: root/src/bin/agent/tests
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 11:36:04 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 11:36:04 +0000
commit040eee1aa49b49df4698d83a05af57c220127fd1 (patch)
treef635435954e6ccde5eee9893889e24f30ca68346 /src/bin/agent/tests
parentInitial commit. (diff)
downloadisc-kea-upstream.tar.xz
isc-kea-upstream.zip
Adding upstream version 2.2.0.upstream/2.2.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/bin/agent/tests')
-rw-r--r--src/bin/agent/tests/Makefile.am120
-rw-r--r--src/bin/agent/tests/Makefile.in1324
-rw-r--r--src/bin/agent/tests/basic_auth_library.cc289
-rw-r--r--src/bin/agent/tests/ca_cfg_mgr_unittests.cc703
-rw-r--r--src/bin/agent/tests/ca_command_mgr_unittests.cc425
-rw-r--r--src/bin/agent/tests/ca_controller_unittests.cc836
-rw-r--r--src/bin/agent/tests/ca_process_tests.sh.in174
-rw-r--r--src/bin/agent/tests/ca_process_unittests.cc90
-rw-r--r--src/bin/agent/tests/ca_response_creator_factory_unittests.cc39
-rw-r--r--src/bin/agent/tests/ca_response_creator_unittests.cc465
-rw-r--r--src/bin/agent/tests/ca_unittests.cc27
-rw-r--r--src/bin/agent/tests/callout_library.cc72
-rw-r--r--src/bin/agent/tests/get_config_unittest.cc309
-rw-r--r--src/bin/agent/tests/parser_unittests.cc931
-rw-r--r--src/bin/agent/tests/test_basic_auth_libraries.h.in24
-rw-r--r--src/bin/agent/tests/test_callout_libraries.h.in24
-rw-r--r--src/bin/agent/tests/test_data_files_config.h.in9
-rw-r--r--src/bin/agent/tests/testdata/get_config.json55
-rw-r--r--src/bin/agent/tests/testdata/hiddenp1
-rw-r--r--src/bin/agent/tests/testdata/hiddens1
-rw-r--r--src/bin/agent/tests/testdata/hiddenu1
21 files changed, 5919 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..7cfedfe
--- /dev/null
+++ b/src/bin/agent/tests/Makefile.am
@@ -0,0 +1,120 @@
+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
+
+# Run shell tests on "make check".
+check_SCRIPTS = $(SHTESTS)
+TESTS = $(SHTESTS)
+
+# As with every file generated by ./configure, clean them up when running
+# "make distclean", but not on "make clean".
+DISTCLEANFILES = $(SHTESTS)
+DISTCLEANFILES += test_basic_auth_libraries.h
+DISTCLEANFILES += test_callout_libraries.h
+DISTCLEANFILES += test_data_files_config.h
+
+# Don't install shell tests.
+noinst_SCRIPTS = $(SHTESTS)
+
+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/cfgrpt/libcfgrpt.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/asiolink/testutils/libasiolinktest.la
+ca_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
+ca_unittests_LDADD += $(top_builddir)/src/lib/cc/libkea-cc.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/hooks/libkea-hooks.la
+ca_unittests_LDADD += $(top_builddir)/src/lib/log/libkea-log.la
+ca_unittests_LDADD += $(top_builddir)/src/lib/testutils/libkea-testutils.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)
+
+# Don't install test libraries.
+noinst_LTLIBRARIES = libcallout.la libbasicauth.la
+
+# Don't install C++ tests.
+noinst_PROGRAMS = $(PROGRAM_TESTS)
+
+endif
diff --git a/src/bin/agent/tests/Makefile.in b/src/bin/agent/tests/Makefile.in
new file mode 100644
index 0000000..84f3345
--- /dev/null
+++ b/src/bin/agent/tests/Makefile.in
@@ -0,0 +1,1324 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+TESTS = $(SHTESTS) $(am__EXEEXT_2)
+
+# Run C++ tests on "make check".
+@HAVE_GTEST_TRUE@am__append_1 = $(PROGRAM_TESTS)
+@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_crypto.m4 \
+ $(top_srcdir)/m4macros/ax_find_library.m4 \
+ $(top_srcdir)/m4macros/ax_gssapi.m4 \
+ $(top_srcdir)/m4macros/ax_gtest.m4 \
+ $(top_srcdir)/m4macros/ax_isc_rpath.m4 \
+ $(top_srcdir)/m4macros/ax_sysrepo.m4 \
+ $(top_srcdir)/m4macros/libtool.m4 \
+ $(top_srcdir)/m4macros/ltoptions.m4 \
+ $(top_srcdir)/m4macros/ltsugar.m4 \
+ $(top_srcdir)/m4macros/ltversion.m4 \
+ $(top_srcdir)/m4macros/lt~obsolete.m4 \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES = 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/cfgrpt/libcfgrpt.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/asiolink/testutils/libasiolinktest.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cc/libkea-cc.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/dns/libkea-dns++.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/hooks/libkea-hooks.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/log/libkea-log.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/testutils/libkea-testutils.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=''; \
+ grn=''; \
+ lgn=''; \
+ blu=''; \
+ mgn=''; \
+ brg=''; \
+ std=''; \
+ fi; \
+}
+@HAVE_GTEST_TRUE@am__EXEEXT_2 = $(am__EXEEXT_1)
+DIST_SUBDIRS = $(SUBDIRS)
+am__DIST_COMMON = $(srcdir)/Makefile.in \
+ $(srcdir)/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_GTEST_CONFIGURE_FLAG = @DISTCHECK_GTEST_CONFIGURE_FLAG@
+DISTCHECK_KEA_SHELL_CONFIGURE_FLAG = @DISTCHECK_KEA_SHELL_CONFIGURE_FLAG@
+DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG = @DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG@
+DISTCHECK_PERFDHCP_CONFIGURE_FLAG = @DISTCHECK_PERFDHCP_CONFIGURE_FLAG@
+DISTCHECK_PREMIUM_CONFIGURE_FLAG = @DISTCHECK_PREMIUM_CONFIGURE_FLAG@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GENHTML = @GENHTML@
+GREP = @GREP@
+GSSAPI_CFLAGS = @GSSAPI_CFLAGS@
+GSSAPI_LIBS = @GSSAPI_LIBS@
+GTEST_CONFIG = @GTEST_CONFIG@
+GTEST_INCLUDES = @GTEST_INCLUDES@
+GTEST_LDADD = @GTEST_LDADD@
+GTEST_LDFLAGS = @GTEST_LDFLAGS@
+GTEST_SOURCE = @GTEST_SOURCE@
+HAVE_SYSREPO = @HAVE_SYSREPO@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+KEA_CXXFLAGS = @KEA_CXXFLAGS@
+KEA_SRCID = @KEA_SRCID@
+KRB5_CONFIG = @KRB5_CONFIG@
+LCOV = @LCOV@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LEX = @LEX@
+LEXLIB = @LEXLIB@
+LEX_OUTPUT_ROOT = @LEX_OUTPUT_ROOT@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LOG4CPLUS_INCLUDES = @LOG4CPLUS_INCLUDES@
+LOG4CPLUS_LIBS = @LOG4CPLUS_LIBS@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MYSQL_CPPFLAGS = @MYSQL_CPPFLAGS@
+MYSQL_LIBS = @MYSQL_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PACKAGE_VERSION_TYPE = @PACKAGE_VERSION_TYPE@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PDFLATEX = @PDFLATEX@
+PERL = @PERL@
+PGSQL_CPPFLAGS = @PGSQL_CPPFLAGS@
+PGSQL_LIBS = @PGSQL_LIBS@
+PKGPYTHONDIR = @PKGPYTHONDIR@
+PKG_CONFIG = @PKG_CONFIG@
+PLANTUML = @PLANTUML@
+PREMIUM_DIR = @PREMIUM_DIR@
+PYTHON = @PYTHON@
+PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@
+PYTHON_PLATFORM = @PYTHON_PLATFORM@
+PYTHON_PREFIX = @PYTHON_PREFIX@
+PYTHON_VERSION = @PYTHON_VERSION@
+RANLIB = @RANLIB@
+SED = @SED@
+SEP = @SEP@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SPHINXBUILD = @SPHINXBUILD@
+SRPD_PLUGINS_PATH = @SRPD_PLUGINS_PATH@
+SR_REPO_PATH = @SR_REPO_PATH@
+STRIP = @STRIP@
+SYSREPOCPP_VERSION = @SYSREPOCPP_VERSION@
+SYSREPO_CPPFLAGS = @SYSREPO_CPPFLAGS@
+SYSREPO_INCLUDEDIR = @SYSREPO_INCLUDEDIR@
+SYSREPO_LIBS = @SYSREPO_LIBS@
+SYSREPO_VERSION = @SYSREPO_VERSION@
+USE_LCOV = @USE_LCOV@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+WARNING_GCC_44_STRICT_ALIASING_CFLAG = @WARNING_GCC_44_STRICT_ALIASING_CFLAG@
+YACC = @YACC@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+pkgpyexecdir = @pkgpyexecdir@
+pkgpythondir = @pkgpythondir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+pyexecdir = @pyexecdir@
+pythondir = @pythondir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+SUBDIRS = .
+
+# Add to the tarball:
+EXTRA_DIST = testdata/get_config.json testdata/hiddenp \
+ testdata/hiddens testdata/hiddenu
+TESTS_ENVIRONMENT = \
+ $(LIBTOOL) --mode=execute $(VALGRIND_COMMAND)
+
+
+# Shell tests
+SHTESTS = ca_process_tests.sh
+
+# Run shell tests on "make check".
+check_SCRIPTS = $(SHTESTS)
+
+# As with every file generated by ./configure, clean them up when running
+# "make distclean", but not on "make clean".
+DISTCLEANFILES = $(SHTESTS) test_basic_auth_libraries.h \
+ test_callout_libraries.h test_data_files_config.h
+
+# Don't install shell tests.
+noinst_SCRIPTS = $(SHTESTS)
+
+# 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/cfgrpt/libcfgrpt.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/asiolink/testutils/libasiolinktest.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cc/libkea-cc.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/dns/libkea-dns++.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/hooks/libkea-hooks.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/log/libkea-log.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/testutils/libkea-testutils.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
+
+# Don't install test libraries.
+@HAVE_GTEST_TRUE@noinst_LTLIBRARIES = libcallout.la libbasicauth.la
+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..f0f8848
--- /dev/null
+++ b/src/bin/agent/tests/ca_controller_unittests.cc
@@ -0,0 +1,836 @@
+// 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/.
+
+#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-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..fe3895d
--- /dev/null
+++ b/src/bin/agent/tests/ca_response_creator_unittests.cc
@@ -0,0 +1,465 @@
+// 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 <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());
+
+ 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());
+
+ 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..2b35ac6
--- /dev/null
+++ b/src/bin/agent/tests/parser_unittests.cc
@@ -0,0 +1,931 @@
+// 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 <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/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());
+}
+
+/// @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..ce3df54
--- /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 DHCP4 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