summaryrefslogtreecommitdiffstats
path: root/src/bin/netconf/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/netconf/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/netconf/tests')
-rw-r--r--src/bin/netconf/tests/Makefile.am91
-rw-r--r--src/bin/netconf/tests/Makefile.in1224
-rw-r--r--src/bin/netconf/tests/basic_library.cc72
-rw-r--r--src/bin/netconf/tests/control_socket_unittests.cc866
-rw-r--r--src/bin/netconf/tests/get_config_unittest.cc294
-rw-r--r--src/bin/netconf/tests/netconf_cfg_mgr_unittests.cc738
-rw-r--r--src/bin/netconf/tests/netconf_controller_unittests.cc194
-rw-r--r--src/bin/netconf/tests/netconf_process_unittests.cc92
-rw-r--r--src/bin/netconf/tests/netconf_unittests.cc1212
-rw-r--r--src/bin/netconf/tests/parser_unittests.cc988
-rw-r--r--src/bin/netconf/tests/run_unittests.cc27
-rw-r--r--src/bin/netconf/tests/shtests/Makefile.am15
-rw-r--r--src/bin/netconf/tests/shtests/Makefile.in841
-rw-r--r--src/bin/netconf/tests/shtests/netconf_tests.sh.in216
-rw-r--r--src/bin/netconf/tests/test_data_files_config.h.in9
-rw-r--r--src/bin/netconf/tests/test_libraries.h.in24
-rw-r--r--src/bin/netconf/tests/testdata/get_config.json24
17 files changed, 6927 insertions, 0 deletions
diff --git a/src/bin/netconf/tests/Makefile.am b/src/bin/netconf/tests/Makefile.am
new file mode 100644
index 0000000..e25e62a
--- /dev/null
+++ b/src/bin/netconf/tests/Makefile.am
@@ -0,0 +1,91 @@
+SUBDIRS = . shtests
+
+EXTRA_DIST = testdata/get_config.json
+
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CPPFLAGS += -I$(top_srcdir)/src -I$(top_builddir)/src
+AM_CPPFLAGS += -I$(top_srcdir)/src/bin -I$(top_builddir)/src/bin
+AM_CPPFLAGS += -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/bin/netconf/tests\"
+AM_CPPFLAGS += -DCFG_EXAMPLES=\"$(abs_top_srcdir)/doc/examples/netconf\"
+AM_CPPFLAGS += -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/bin/netconf/tests\"
+AM_CPPFLAGS += -DSYNTAX_FILE=\"$(abs_srcdir)/../netconf_parser.yy\"
+AM_CPPFLAGS += $(BOOST_INCLUDES) $(CRYPTO_CFLAGS) $(CRYPTO_INCLUDES)
+AM_CPPFLAGS += $(SYSREPO_CPPFLAGS)
+AM_CPPFLAGS += $(SYSREPO_INCLUDEDIR)
+
+CLEANFILES = *.json *.log
+
+DISTCLEANFILES = test_data_files_config.h test_libraries.h
+
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+
+if USE_STATIC_LINK
+AM_LDFLAGS = -static
+endif
+
+TESTS_ENVIRONMENT = \
+ $(LIBTOOL) --mode=execute $(VALGRIND_COMMAND)
+
+TESTS =
+if HAVE_GTEST
+
+noinst_LTLIBRARIES = libbasic.la
+
+TESTS += netconf_unittests
+
+netconf_unittests_SOURCES = control_socket_unittests.cc
+netconf_unittests_SOURCES += get_config_unittest.cc
+netconf_unittests_SOURCES += netconf_cfg_mgr_unittests.cc
+netconf_unittests_SOURCES += netconf_controller_unittests.cc
+netconf_unittests_SOURCES += netconf_process_unittests.cc
+netconf_unittests_SOURCES += netconf_unittests.cc
+netconf_unittests_SOURCES += parser_unittests.cc
+netconf_unittests_SOURCES += run_unittests.cc
+
+netconf_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+netconf_unittests_LDFLAGS = $(AM_LDFLAGS) $(CRYPTO_LDFLAGS)
+
+
+netconf_unittests_LDADD = $(top_builddir)/src/bin/netconf/libnetconf.la
+netconf_unittests_LDADD += $(top_builddir)/src/lib/process/testutils/libprocesstest.la
+netconf_unittests_LDADD += $(top_builddir)/src/lib/process/libkea-process.la
+netconf_unittests_LDADD += $(top_builddir)/src/lib/cfgrpt/libcfgrpt.la
+#netconf_unittests_LDADD += $(top_builddir)/src/lib/dhcpsrv/libkea-dhcpsrv.la
+#netconf_unittests_LDADD += $(top_builddir)/src/lib/dhcpsrv/testutils/libdhcpsrvtest.la
+#netconf_unittests_LDADD += $(top_builddir)/src/lib/eval/libkea-eval.la
+#netconf_unittests_LDADD += $(top_builddir)/src/lib/dhcp_ddns/libkea-dhcp_ddns.la
+netconf_unittests_LDADD += $(top_builddir)/src/lib/yang/testutils/libyangtest.la
+netconf_unittests_LDADD += $(top_builddir)/src/lib/yang/libkea-yang.la
+#netconf_unittests_LDADD += $(top_builddir)/src/lib/stats/libkea-stats.la
+netconf_unittests_LDADD += $(top_builddir)/src/lib/config/libkea-cfgclient.la
+netconf_unittests_LDADD += $(top_builddir)/src/lib/http/libkea-http.la
+#netconf_unittests_LDADD += $(top_builddir)/src/lib/dhcp/tests/libdhcptest.la
+netconf_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la
+netconf_unittests_LDADD += $(top_builddir)/src/lib/hooks/libkea-hooks.la
+netconf_unittests_LDADD += $(top_builddir)/src/lib/testutils/libkea-testutils.la
+netconf_unittests_LDADD += $(top_builddir)/src/lib/cc/libkea-cc.la
+netconf_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
+netconf_unittests_LDADD += $(top_builddir)/src/lib/dns/libkea-dns++.la
+netconf_unittests_LDADD += $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la
+netconf_unittests_LDADD += $(top_builddir)/src/lib/log/libkea-log.la
+netconf_unittests_LDADD += $(top_builddir)/src/lib/util/unittests/libutil_unittests.la
+netconf_unittests_LDADD += $(top_builddir)/src/lib/util/libkea-util.la
+netconf_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+netconf_unittests_LDADD += $(LOG4CPLUS_LIBS) $(CRYPTO_LIBS)
+netconf_unittests_LDADD += $(BOOST_LIBS) $(GTEST_LDADD) $(SYSREPO_LIBS)
+
+# The basic callout library - contains standard callouts
+libbasic_la_SOURCES = basic_library.cc
+libbasic_la_CXXFLAGS = $(AM_CXXFLAGS)
+libbasic_la_CPPFLAGS = $(AM_CPPFLAGS)
+libbasic_la_LIBADD = $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+libbasic_la_LIBADD += $(top_builddir)/src/lib/hooks/libkea-hooks.la
+libbasic_la_LIBADD += $(top_builddir)/src/lib/log/libkea-log.la
+libbasic_la_LDFLAGS = -avoid-version -export-dynamic -module -rpath /nowhere
+
+nodist_netconf_unittests_SOURCES = test_data_files_config.h test_libraries.h
+endif
+
+noinst_EXTRA_DIST = configs-list.txt
+
+noinst_PROGRAMS = $(TESTS)
diff --git a/src/bin/netconf/tests/Makefile.in b/src/bin/netconf/tests/Makefile.in
new file mode 100644
index 0000000..e2b84ae
--- /dev/null
+++ b/src/bin/netconf/tests/Makefile.in
@@ -0,0 +1,1224 @@
+# 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 = $(am__EXEEXT_1)
+@HAVE_GTEST_TRUE@am__append_1 = netconf_unittests
+noinst_PROGRAMS = $(am__EXEEXT_2)
+subdir = src/bin/netconf/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 = test_data_files_config.h test_libraries.h
+CONFIG_CLEAN_VPATH_FILES =
+@HAVE_GTEST_TRUE@am__EXEEXT_1 = netconf_unittests$(EXEEXT)
+am__EXEEXT_2 = $(am__EXEEXT_1)
+PROGRAMS = $(noinst_PROGRAMS)
+LTLIBRARIES = $(noinst_LTLIBRARIES)
+@HAVE_GTEST_TRUE@libbasic_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__libbasic_la_SOURCES_DIST = basic_library.cc
+@HAVE_GTEST_TRUE@am_libbasic_la_OBJECTS = \
+@HAVE_GTEST_TRUE@ libbasic_la-basic_library.lo
+libbasic_la_OBJECTS = $(am_libbasic_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 =
+libbasic_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(libbasic_la_CXXFLAGS) \
+ $(CXXFLAGS) $(libbasic_la_LDFLAGS) $(LDFLAGS) -o $@
+@HAVE_GTEST_TRUE@am_libbasic_la_rpath =
+am__netconf_unittests_SOURCES_DIST = control_socket_unittests.cc \
+ get_config_unittest.cc netconf_cfg_mgr_unittests.cc \
+ netconf_controller_unittests.cc netconf_process_unittests.cc \
+ netconf_unittests.cc parser_unittests.cc run_unittests.cc
+@HAVE_GTEST_TRUE@am_netconf_unittests_OBJECTS = netconf_unittests-control_socket_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ netconf_unittests-get_config_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ netconf_unittests-netconf_cfg_mgr_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ netconf_unittests-netconf_controller_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ netconf_unittests-netconf_process_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ netconf_unittests-netconf_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ netconf_unittests-parser_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ netconf_unittests-run_unittests.$(OBJEXT)
+nodist_netconf_unittests_OBJECTS =
+netconf_unittests_OBJECTS = $(am_netconf_unittests_OBJECTS) \
+ $(nodist_netconf_unittests_OBJECTS)
+am__DEPENDENCIES_1 =
+@HAVE_GTEST_TRUE@netconf_unittests_DEPENDENCIES = $(top_builddir)/src/bin/netconf/libnetconf.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/process/testutils/libprocesstest.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/process/libkea-process.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cfgrpt/libcfgrpt.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/yang/testutils/libyangtest.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/yang/libkea-yang.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/config/libkea-cfgclient.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/http/libkea-http.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/hooks/libkea-hooks.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/testutils/libkea-testutils.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cc/libkea-cc.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/dns/libkea-dns++.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/log/libkea-log.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/util/unittests/libutil_unittests.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) \
+@HAVE_GTEST_TRUE@ $(am__DEPENDENCIES_1)
+netconf_unittests_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
+ $(AM_CXXFLAGS) $(CXXFLAGS) $(netconf_unittests_LDFLAGS) \
+ $(LDFLAGS) -o $@
+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)/libbasic_la-basic_library.Plo \
+ ./$(DEPDIR)/netconf_unittests-control_socket_unittests.Po \
+ ./$(DEPDIR)/netconf_unittests-get_config_unittest.Po \
+ ./$(DEPDIR)/netconf_unittests-netconf_cfg_mgr_unittests.Po \
+ ./$(DEPDIR)/netconf_unittests-netconf_controller_unittests.Po \
+ ./$(DEPDIR)/netconf_unittests-netconf_process_unittests.Po \
+ ./$(DEPDIR)/netconf_unittests-netconf_unittests.Po \
+ ./$(DEPDIR)/netconf_unittests-parser_unittests.Po \
+ ./$(DEPDIR)/netconf_unittests-run_unittests.Po
+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 = $(libbasic_la_SOURCES) $(netconf_unittests_SOURCES) \
+ $(nodist_netconf_unittests_SOURCES)
+DIST_SOURCES = $(am__libbasic_la_SOURCES_DIST) \
+ $(am__netconf_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; \
+}
+DIST_SUBDIRS = $(SUBDIRS)
+am__DIST_COMMON = $(srcdir)/Makefile.in \
+ $(srcdir)/test_data_files_config.h.in \
+ $(srcdir)/test_libraries.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 = . shtests
+EXTRA_DIST = testdata/get_config.json
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib \
+ -I$(top_srcdir)/src -I$(top_builddir)/src \
+ -I$(top_srcdir)/src/bin -I$(top_builddir)/src/bin \
+ -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/bin/netconf/tests\" \
+ -DCFG_EXAMPLES=\"$(abs_top_srcdir)/doc/examples/netconf\" \
+ -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/bin/netconf/tests\" \
+ -DSYNTAX_FILE=\"$(abs_srcdir)/../netconf_parser.yy\" \
+ $(BOOST_INCLUDES) $(CRYPTO_CFLAGS) $(CRYPTO_INCLUDES) \
+ $(SYSREPO_CPPFLAGS) $(SYSREPO_INCLUDEDIR)
+CLEANFILES = *.json *.log
+DISTCLEANFILES = test_data_files_config.h test_libraries.h
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+@USE_STATIC_LINK_TRUE@AM_LDFLAGS = -static
+TESTS_ENVIRONMENT = \
+ $(LIBTOOL) --mode=execute $(VALGRIND_COMMAND)
+
+@HAVE_GTEST_TRUE@noinst_LTLIBRARIES = libbasic.la
+@HAVE_GTEST_TRUE@netconf_unittests_SOURCES = \
+@HAVE_GTEST_TRUE@ control_socket_unittests.cc \
+@HAVE_GTEST_TRUE@ get_config_unittest.cc \
+@HAVE_GTEST_TRUE@ netconf_cfg_mgr_unittests.cc \
+@HAVE_GTEST_TRUE@ netconf_controller_unittests.cc \
+@HAVE_GTEST_TRUE@ netconf_process_unittests.cc \
+@HAVE_GTEST_TRUE@ netconf_unittests.cc parser_unittests.cc \
+@HAVE_GTEST_TRUE@ run_unittests.cc
+@HAVE_GTEST_TRUE@netconf_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+@HAVE_GTEST_TRUE@netconf_unittests_LDFLAGS = $(AM_LDFLAGS) $(CRYPTO_LDFLAGS)
+#netconf_unittests_LDADD += $(top_builddir)/src/lib/dhcpsrv/libkea-dhcpsrv.la
+#netconf_unittests_LDADD += $(top_builddir)/src/lib/dhcpsrv/testutils/libdhcpsrvtest.la
+#netconf_unittests_LDADD += $(top_builddir)/src/lib/eval/libkea-eval.la
+#netconf_unittests_LDADD += $(top_builddir)/src/lib/dhcp_ddns/libkea-dhcp_ddns.la
+#netconf_unittests_LDADD += $(top_builddir)/src/lib/stats/libkea-stats.la
+#netconf_unittests_LDADD += $(top_builddir)/src/lib/dhcp/tests/libdhcptest.la
+@HAVE_GTEST_TRUE@netconf_unittests_LDADD = $(top_builddir)/src/bin/netconf/libnetconf.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/process/testutils/libprocesstest.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/process/libkea-process.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cfgrpt/libcfgrpt.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/yang/testutils/libyangtest.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/yang/libkea-yang.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/config/libkea-cfgclient.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/http/libkea-http.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/hooks/libkea-hooks.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/testutils/libkea-testutils.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cc/libkea-cc.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/dns/libkea-dns++.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/log/libkea-log.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/util/unittests/libutil_unittests.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) $(SYSREPO_LIBS)
+
+# The basic callout library - contains standard callouts
+@HAVE_GTEST_TRUE@libbasic_la_SOURCES = basic_library.cc
+@HAVE_GTEST_TRUE@libbasic_la_CXXFLAGS = $(AM_CXXFLAGS)
+@HAVE_GTEST_TRUE@libbasic_la_CPPFLAGS = $(AM_CPPFLAGS)
+@HAVE_GTEST_TRUE@libbasic_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@libbasic_la_LDFLAGS = -avoid-version -export-dynamic -module -rpath /nowhere
+@HAVE_GTEST_TRUE@nodist_netconf_unittests_SOURCES = test_data_files_config.h test_libraries.h
+noinst_EXTRA_DIST = configs-list.txt
+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/netconf/tests/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/bin/netconf/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):
+test_data_files_config.h: $(top_builddir)/config.status $(srcdir)/test_data_files_config.h.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+test_libraries.h: $(top_builddir)/config.status $(srcdir)/test_libraries.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}; \
+ }
+
+libbasic.la: $(libbasic_la_OBJECTS) $(libbasic_la_DEPENDENCIES) $(EXTRA_libbasic_la_DEPENDENCIES)
+ $(AM_V_CXXLD)$(libbasic_la_LINK) $(am_libbasic_la_rpath) $(libbasic_la_OBJECTS) $(libbasic_la_LIBADD) $(LIBS)
+
+netconf_unittests$(EXEEXT): $(netconf_unittests_OBJECTS) $(netconf_unittests_DEPENDENCIES) $(EXTRA_netconf_unittests_DEPENDENCIES)
+ @rm -f netconf_unittests$(EXEEXT)
+ $(AM_V_CXXLD)$(netconf_unittests_LINK) $(netconf_unittests_OBJECTS) $(netconf_unittests_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libbasic_la-basic_library.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/netconf_unittests-control_socket_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/netconf_unittests-get_config_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/netconf_unittests-netconf_cfg_mgr_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/netconf_unittests-netconf_controller_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/netconf_unittests-netconf_process_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/netconf_unittests-netconf_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/netconf_unittests-parser_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/netconf_unittests-run_unittests.Po@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 $@ $<
+
+libbasic_la-basic_library.lo: basic_library.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libbasic_la_CPPFLAGS) $(CPPFLAGS) $(libbasic_la_CXXFLAGS) $(CXXFLAGS) -MT libbasic_la-basic_library.lo -MD -MP -MF $(DEPDIR)/libbasic_la-basic_library.Tpo -c -o libbasic_la-basic_library.lo `test -f 'basic_library.cc' || echo '$(srcdir)/'`basic_library.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libbasic_la-basic_library.Tpo $(DEPDIR)/libbasic_la-basic_library.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='basic_library.cc' object='libbasic_la-basic_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) $(libbasic_la_CPPFLAGS) $(CPPFLAGS) $(libbasic_la_CXXFLAGS) $(CXXFLAGS) -c -o libbasic_la-basic_library.lo `test -f 'basic_library.cc' || echo '$(srcdir)/'`basic_library.cc
+
+netconf_unittests-control_socket_unittests.o: control_socket_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(netconf_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT netconf_unittests-control_socket_unittests.o -MD -MP -MF $(DEPDIR)/netconf_unittests-control_socket_unittests.Tpo -c -o netconf_unittests-control_socket_unittests.o `test -f 'control_socket_unittests.cc' || echo '$(srcdir)/'`control_socket_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/netconf_unittests-control_socket_unittests.Tpo $(DEPDIR)/netconf_unittests-control_socket_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='control_socket_unittests.cc' object='netconf_unittests-control_socket_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) $(netconf_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o netconf_unittests-control_socket_unittests.o `test -f 'control_socket_unittests.cc' || echo '$(srcdir)/'`control_socket_unittests.cc
+
+netconf_unittests-control_socket_unittests.obj: control_socket_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(netconf_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT netconf_unittests-control_socket_unittests.obj -MD -MP -MF $(DEPDIR)/netconf_unittests-control_socket_unittests.Tpo -c -o netconf_unittests-control_socket_unittests.obj `if test -f 'control_socket_unittests.cc'; then $(CYGPATH_W) 'control_socket_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/control_socket_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/netconf_unittests-control_socket_unittests.Tpo $(DEPDIR)/netconf_unittests-control_socket_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='control_socket_unittests.cc' object='netconf_unittests-control_socket_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) $(netconf_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o netconf_unittests-control_socket_unittests.obj `if test -f 'control_socket_unittests.cc'; then $(CYGPATH_W) 'control_socket_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/control_socket_unittests.cc'; fi`
+
+netconf_unittests-get_config_unittest.o: get_config_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(netconf_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT netconf_unittests-get_config_unittest.o -MD -MP -MF $(DEPDIR)/netconf_unittests-get_config_unittest.Tpo -c -o netconf_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)/netconf_unittests-get_config_unittest.Tpo $(DEPDIR)/netconf_unittests-get_config_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='get_config_unittest.cc' object='netconf_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) $(netconf_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o netconf_unittests-get_config_unittest.o `test -f 'get_config_unittest.cc' || echo '$(srcdir)/'`get_config_unittest.cc
+
+netconf_unittests-get_config_unittest.obj: get_config_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(netconf_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT netconf_unittests-get_config_unittest.obj -MD -MP -MF $(DEPDIR)/netconf_unittests-get_config_unittest.Tpo -c -o netconf_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)/netconf_unittests-get_config_unittest.Tpo $(DEPDIR)/netconf_unittests-get_config_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='get_config_unittest.cc' object='netconf_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) $(netconf_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o netconf_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`
+
+netconf_unittests-netconf_cfg_mgr_unittests.o: netconf_cfg_mgr_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(netconf_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT netconf_unittests-netconf_cfg_mgr_unittests.o -MD -MP -MF $(DEPDIR)/netconf_unittests-netconf_cfg_mgr_unittests.Tpo -c -o netconf_unittests-netconf_cfg_mgr_unittests.o `test -f 'netconf_cfg_mgr_unittests.cc' || echo '$(srcdir)/'`netconf_cfg_mgr_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/netconf_unittests-netconf_cfg_mgr_unittests.Tpo $(DEPDIR)/netconf_unittests-netconf_cfg_mgr_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='netconf_cfg_mgr_unittests.cc' object='netconf_unittests-netconf_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) $(netconf_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o netconf_unittests-netconf_cfg_mgr_unittests.o `test -f 'netconf_cfg_mgr_unittests.cc' || echo '$(srcdir)/'`netconf_cfg_mgr_unittests.cc
+
+netconf_unittests-netconf_cfg_mgr_unittests.obj: netconf_cfg_mgr_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(netconf_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT netconf_unittests-netconf_cfg_mgr_unittests.obj -MD -MP -MF $(DEPDIR)/netconf_unittests-netconf_cfg_mgr_unittests.Tpo -c -o netconf_unittests-netconf_cfg_mgr_unittests.obj `if test -f 'netconf_cfg_mgr_unittests.cc'; then $(CYGPATH_W) 'netconf_cfg_mgr_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/netconf_cfg_mgr_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/netconf_unittests-netconf_cfg_mgr_unittests.Tpo $(DEPDIR)/netconf_unittests-netconf_cfg_mgr_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='netconf_cfg_mgr_unittests.cc' object='netconf_unittests-netconf_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) $(netconf_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o netconf_unittests-netconf_cfg_mgr_unittests.obj `if test -f 'netconf_cfg_mgr_unittests.cc'; then $(CYGPATH_W) 'netconf_cfg_mgr_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/netconf_cfg_mgr_unittests.cc'; fi`
+
+netconf_unittests-netconf_controller_unittests.o: netconf_controller_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(netconf_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT netconf_unittests-netconf_controller_unittests.o -MD -MP -MF $(DEPDIR)/netconf_unittests-netconf_controller_unittests.Tpo -c -o netconf_unittests-netconf_controller_unittests.o `test -f 'netconf_controller_unittests.cc' || echo '$(srcdir)/'`netconf_controller_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/netconf_unittests-netconf_controller_unittests.Tpo $(DEPDIR)/netconf_unittests-netconf_controller_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='netconf_controller_unittests.cc' object='netconf_unittests-netconf_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) $(netconf_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o netconf_unittests-netconf_controller_unittests.o `test -f 'netconf_controller_unittests.cc' || echo '$(srcdir)/'`netconf_controller_unittests.cc
+
+netconf_unittests-netconf_controller_unittests.obj: netconf_controller_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(netconf_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT netconf_unittests-netconf_controller_unittests.obj -MD -MP -MF $(DEPDIR)/netconf_unittests-netconf_controller_unittests.Tpo -c -o netconf_unittests-netconf_controller_unittests.obj `if test -f 'netconf_controller_unittests.cc'; then $(CYGPATH_W) 'netconf_controller_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/netconf_controller_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/netconf_unittests-netconf_controller_unittests.Tpo $(DEPDIR)/netconf_unittests-netconf_controller_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='netconf_controller_unittests.cc' object='netconf_unittests-netconf_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) $(netconf_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o netconf_unittests-netconf_controller_unittests.obj `if test -f 'netconf_controller_unittests.cc'; then $(CYGPATH_W) 'netconf_controller_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/netconf_controller_unittests.cc'; fi`
+
+netconf_unittests-netconf_process_unittests.o: netconf_process_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(netconf_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT netconf_unittests-netconf_process_unittests.o -MD -MP -MF $(DEPDIR)/netconf_unittests-netconf_process_unittests.Tpo -c -o netconf_unittests-netconf_process_unittests.o `test -f 'netconf_process_unittests.cc' || echo '$(srcdir)/'`netconf_process_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/netconf_unittests-netconf_process_unittests.Tpo $(DEPDIR)/netconf_unittests-netconf_process_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='netconf_process_unittests.cc' object='netconf_unittests-netconf_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) $(netconf_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o netconf_unittests-netconf_process_unittests.o `test -f 'netconf_process_unittests.cc' || echo '$(srcdir)/'`netconf_process_unittests.cc
+
+netconf_unittests-netconf_process_unittests.obj: netconf_process_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(netconf_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT netconf_unittests-netconf_process_unittests.obj -MD -MP -MF $(DEPDIR)/netconf_unittests-netconf_process_unittests.Tpo -c -o netconf_unittests-netconf_process_unittests.obj `if test -f 'netconf_process_unittests.cc'; then $(CYGPATH_W) 'netconf_process_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/netconf_process_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/netconf_unittests-netconf_process_unittests.Tpo $(DEPDIR)/netconf_unittests-netconf_process_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='netconf_process_unittests.cc' object='netconf_unittests-netconf_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) $(netconf_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o netconf_unittests-netconf_process_unittests.obj `if test -f 'netconf_process_unittests.cc'; then $(CYGPATH_W) 'netconf_process_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/netconf_process_unittests.cc'; fi`
+
+netconf_unittests-netconf_unittests.o: netconf_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(netconf_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT netconf_unittests-netconf_unittests.o -MD -MP -MF $(DEPDIR)/netconf_unittests-netconf_unittests.Tpo -c -o netconf_unittests-netconf_unittests.o `test -f 'netconf_unittests.cc' || echo '$(srcdir)/'`netconf_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/netconf_unittests-netconf_unittests.Tpo $(DEPDIR)/netconf_unittests-netconf_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='netconf_unittests.cc' object='netconf_unittests-netconf_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) $(netconf_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o netconf_unittests-netconf_unittests.o `test -f 'netconf_unittests.cc' || echo '$(srcdir)/'`netconf_unittests.cc
+
+netconf_unittests-netconf_unittests.obj: netconf_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(netconf_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT netconf_unittests-netconf_unittests.obj -MD -MP -MF $(DEPDIR)/netconf_unittests-netconf_unittests.Tpo -c -o netconf_unittests-netconf_unittests.obj `if test -f 'netconf_unittests.cc'; then $(CYGPATH_W) 'netconf_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/netconf_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/netconf_unittests-netconf_unittests.Tpo $(DEPDIR)/netconf_unittests-netconf_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='netconf_unittests.cc' object='netconf_unittests-netconf_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) $(netconf_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o netconf_unittests-netconf_unittests.obj `if test -f 'netconf_unittests.cc'; then $(CYGPATH_W) 'netconf_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/netconf_unittests.cc'; fi`
+
+netconf_unittests-parser_unittests.o: parser_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(netconf_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT netconf_unittests-parser_unittests.o -MD -MP -MF $(DEPDIR)/netconf_unittests-parser_unittests.Tpo -c -o netconf_unittests-parser_unittests.o `test -f 'parser_unittests.cc' || echo '$(srcdir)/'`parser_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/netconf_unittests-parser_unittests.Tpo $(DEPDIR)/netconf_unittests-parser_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='parser_unittests.cc' object='netconf_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) $(netconf_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o netconf_unittests-parser_unittests.o `test -f 'parser_unittests.cc' || echo '$(srcdir)/'`parser_unittests.cc
+
+netconf_unittests-parser_unittests.obj: parser_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(netconf_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT netconf_unittests-parser_unittests.obj -MD -MP -MF $(DEPDIR)/netconf_unittests-parser_unittests.Tpo -c -o netconf_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)/netconf_unittests-parser_unittests.Tpo $(DEPDIR)/netconf_unittests-parser_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='parser_unittests.cc' object='netconf_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) $(netconf_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o netconf_unittests-parser_unittests.obj `if test -f 'parser_unittests.cc'; then $(CYGPATH_W) 'parser_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/parser_unittests.cc'; fi`
+
+netconf_unittests-run_unittests.o: run_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(netconf_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT netconf_unittests-run_unittests.o -MD -MP -MF $(DEPDIR)/netconf_unittests-run_unittests.Tpo -c -o netconf_unittests-run_unittests.o `test -f 'run_unittests.cc' || echo '$(srcdir)/'`run_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/netconf_unittests-run_unittests.Tpo $(DEPDIR)/netconf_unittests-run_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='run_unittests.cc' object='netconf_unittests-run_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) $(netconf_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o netconf_unittests-run_unittests.o `test -f 'run_unittests.cc' || echo '$(srcdir)/'`run_unittests.cc
+
+netconf_unittests-run_unittests.obj: run_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(netconf_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT netconf_unittests-run_unittests.obj -MD -MP -MF $(DEPDIR)/netconf_unittests-run_unittests.Tpo -c -o netconf_unittests-run_unittests.obj `if test -f 'run_unittests.cc'; then $(CYGPATH_W) 'run_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/run_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/netconf_unittests-run_unittests.Tpo $(DEPDIR)/netconf_unittests-run_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='run_unittests.cc' object='netconf_unittests-run_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) $(netconf_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o netconf_unittests-run_unittests.obj `if test -f 'run_unittests.cc'; then $(CYGPATH_W) 'run_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/run_unittests.cc'; fi`
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+# This directory's subdirectories are mostly independent; you can cd
+# into them and run 'make' without going through this Makefile.
+# To change the values of 'make' variables: instead of editing Makefiles,
+# (1) if the variable is set in 'config.status', edit 'config.status'
+# (which will cause the Makefiles to be regenerated when you run 'make');
+# (2) otherwise, pass the desired values on the 'make' command line.
+$(am__recursive_targets):
+ @fail=; \
+ if $(am__make_keepgoing); then \
+ failcom='fail=yes'; \
+ else \
+ failcom='exit 1'; \
+ fi; \
+ dot_seen=no; \
+ target=`echo $@ | sed s/-recursive//`; \
+ case "$@" in \
+ distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \
+ *) list='$(SUBDIRS)' ;; \
+ esac; \
+ for subdir in $$list; do \
+ echo "Making $$target in $$subdir"; \
+ if test "$$subdir" = "."; then \
+ dot_seen=yes; \
+ local_target="$$target-am"; \
+ else \
+ local_target="$$target"; \
+ fi; \
+ ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \
+ || eval $$failcom; \
+ done; \
+ if test "$$dot_seen" = "no"; then \
+ $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \
+ fi; test -z "$$fail"
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-recursive
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \
+ include_option=--etags-include; \
+ empty_fix=.; \
+ else \
+ include_option=--include; \
+ empty_fix=; \
+ fi; \
+ list='$(SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ test ! -f $$subdir/TAGS || \
+ set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \
+ fi; \
+ done; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-recursive
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-recursive
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+check-TESTS: $(TESTS)
+ @failed=0; all=0; xfail=0; xpass=0; skip=0; \
+ srcdir=$(srcdir); export srcdir; \
+ list=' $(TESTS) '; \
+ $(am__tty_colors); \
+ if test -n "$$list"; then \
+ for tst in $$list; do \
+ if test -f ./$$tst; then dir=./; \
+ elif test -f $$tst; then dir=; \
+ else dir="$(srcdir)/"; fi; \
+ if $(TESTS_ENVIRONMENT) $${dir}$$tst $(AM_TESTS_FD_REDIRECT); then \
+ all=`expr $$all + 1`; \
+ case " $(XFAIL_TESTS) " in \
+ *[\ \ ]$$tst[\ \ ]*) \
+ xpass=`expr $$xpass + 1`; \
+ failed=`expr $$failed + 1`; \
+ col=$$red; res=XPASS; \
+ ;; \
+ *) \
+ col=$$grn; res=PASS; \
+ ;; \
+ esac; \
+ elif test $$? -ne 77; then \
+ all=`expr $$all + 1`; \
+ case " $(XFAIL_TESTS) " in \
+ *[\ \ ]$$tst[\ \ ]*) \
+ xfail=`expr $$xfail + 1`; \
+ col=$$lgn; res=XFAIL; \
+ ;; \
+ *) \
+ failed=`expr $$failed + 1`; \
+ col=$$red; res=FAIL; \
+ ;; \
+ esac; \
+ else \
+ skip=`expr $$skip + 1`; \
+ col=$$blu; res=SKIP; \
+ fi; \
+ echo "$${col}$$res$${std}: $$tst"; \
+ done; \
+ if test "$$all" -eq 1; then \
+ tests="test"; \
+ All=""; \
+ else \
+ tests="tests"; \
+ All="All "; \
+ fi; \
+ if test "$$failed" -eq 0; then \
+ if test "$$xfail" -eq 0; then \
+ banner="$$All$$all $$tests passed"; \
+ else \
+ if test "$$xfail" -eq 1; then failures=failure; else failures=failures; fi; \
+ banner="$$All$$all $$tests behaved as expected ($$xfail expected $$failures)"; \
+ fi; \
+ else \
+ if test "$$xpass" -eq 0; then \
+ banner="$$failed of $$all $$tests failed"; \
+ else \
+ if test "$$xpass" -eq 1; then passes=pass; else passes=passes; fi; \
+ banner="$$failed of $$all $$tests did not behave as expected ($$xpass unexpected $$passes)"; \
+ fi; \
+ fi; \
+ dashes="$$banner"; \
+ skipped=""; \
+ if test "$$skip" -ne 0; then \
+ if test "$$skip" -eq 1; then \
+ skipped="($$skip test was not run)"; \
+ else \
+ skipped="($$skip tests were not run)"; \
+ fi; \
+ test `echo "$$skipped" | wc -c` -le `echo "$$banner" | wc -c` || \
+ dashes="$$skipped"; \
+ fi; \
+ report=""; \
+ if test "$$failed" -ne 0 && test -n "$(PACKAGE_BUGREPORT)"; then \
+ report="Please report to $(PACKAGE_BUGREPORT)"; \
+ test `echo "$$report" | wc -c` -le `echo "$$banner" | wc -c` || \
+ dashes="$$report"; \
+ fi; \
+ dashes=`echo "$$dashes" | sed s/./=/g`; \
+ if test "$$failed" -eq 0; then \
+ col="$$grn"; \
+ else \
+ col="$$red"; \
+ fi; \
+ echo "$${col}$$dashes$${std}"; \
+ echo "$${col}$$banner$${std}"; \
+ test -z "$$skipped" || echo "$${col}$$skipped$${std}"; \
+ test -z "$$report" || echo "$${col}$$report$${std}"; \
+ echo "$${col}$$dashes$${std}"; \
+ test "$$failed" -eq 0; \
+ else :; fi
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+ @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ $(am__make_dryrun) \
+ || test -d "$(distdir)/$$subdir" \
+ || $(MKDIR_P) "$(distdir)/$$subdir" \
+ || exit 1; \
+ dir1=$$subdir; dir2="$(distdir)/$$subdir"; \
+ $(am__relativize); \
+ new_distdir=$$reldir; \
+ dir1=$$subdir; dir2="$(top_distdir)"; \
+ $(am__relativize); \
+ new_top_distdir=$$reldir; \
+ echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \
+ echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \
+ ($(am__cd) $$subdir && \
+ $(MAKE) $(AM_MAKEFLAGS) \
+ top_distdir="$$new_top_distdir" \
+ distdir="$$new_distdir" \
+ am__remove_distdir=: \
+ am__skip_length_check=: \
+ am__skip_mode_fix=: \
+ distdir) \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+ $(MAKE) $(AM_MAKEFLAGS) check-TESTS
+check: check-recursive
+all-am: Makefile $(PROGRAMS) $(LTLIBRARIES)
+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:
+ -test -z "$(CLEANFILES)" || rm -f $(CLEANFILES)
+
+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)/libbasic_la-basic_library.Plo
+ -rm -f ./$(DEPDIR)/netconf_unittests-control_socket_unittests.Po
+ -rm -f ./$(DEPDIR)/netconf_unittests-get_config_unittest.Po
+ -rm -f ./$(DEPDIR)/netconf_unittests-netconf_cfg_mgr_unittests.Po
+ -rm -f ./$(DEPDIR)/netconf_unittests-netconf_controller_unittests.Po
+ -rm -f ./$(DEPDIR)/netconf_unittests-netconf_process_unittests.Po
+ -rm -f ./$(DEPDIR)/netconf_unittests-netconf_unittests.Po
+ -rm -f ./$(DEPDIR)/netconf_unittests-parser_unittests.Po
+ -rm -f ./$(DEPDIR)/netconf_unittests-run_unittests.Po
+ -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)/libbasic_la-basic_library.Plo
+ -rm -f ./$(DEPDIR)/netconf_unittests-control_socket_unittests.Po
+ -rm -f ./$(DEPDIR)/netconf_unittests-get_config_unittest.Po
+ -rm -f ./$(DEPDIR)/netconf_unittests-netconf_cfg_mgr_unittests.Po
+ -rm -f ./$(DEPDIR)/netconf_unittests-netconf_controller_unittests.Po
+ -rm -f ./$(DEPDIR)/netconf_unittests-netconf_process_unittests.Po
+ -rm -f ./$(DEPDIR)/netconf_unittests-netconf_unittests.Po
+ -rm -f ./$(DEPDIR)/netconf_unittests-parser_unittests.Po
+ -rm -f ./$(DEPDIR)/netconf_unittests-run_unittests.Po
+ -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/netconf/tests/basic_library.cc b/src/bin/netconf/tests/basic_library.cc
new file mode 100644
index 0000000..f2186e0
--- /dev/null
+++ b/src/bin/netconf/tests/basic_library.cc
@@ -0,0 +1,72 @@
+// Copyright (C) 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/.
+
+/// @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/netconf/tests/control_socket_unittests.cc b/src/bin/netconf/tests/control_socket_unittests.cc
new file mode 100644
index 0000000..fc4b35e
--- /dev/null
+++ b/src/bin/netconf/tests/control_socket_unittests.cc
@@ -0,0 +1,866 @@
+// Copyright (C) 2018-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 <netconf/netconf_config.h>
+#include <netconf/http_control_socket.h>
+#include <netconf/stdout_control_socket.h>
+#include <netconf/unix_control_socket.h>
+#include <asiolink/asio_wrapper.h>
+#include <asiolink/interval_timer.h>
+#include <asiolink/io_service.h>
+#include <http/listener.h>
+#include <http/post_request_json.h>
+#include <http/response_creator.h>
+#include <http/response_creator_factory.h>
+#include <http/response_json.h>
+#include <http/tests/response_test.h>
+#include <testutils/threaded_test.h>
+#include <testutils/sandbox.h>
+#include <yang/tests/sysrepo_setup.h>
+
+#include <gtest/gtest.h>
+
+#include <sstream>
+#include <thread>
+
+using namespace std;
+using namespace isc;
+using namespace isc::netconf;
+using namespace isc::asiolink;
+using namespace isc::data;
+using namespace isc::http;
+using namespace isc::http::test;
+using namespace isc::test;
+
+using isc::yang::test::SysrepoSetup;
+
+namespace {
+
+/// @brief Type definition for the pointer to Thread objects.
+typedef boost::shared_ptr<thread> ThreadPtr;
+
+//////////////////////////////// STDOUT ////////////////////////////////
+
+/// @brief Test derived StdoutControlSocket class.
+///
+/// This class exposes the constructor taking the output stream.
+class TestStdoutControlSocket : public StdoutControlSocket {
+public:
+ /// @brief Constructor.
+ ///
+ /// @param ctrl_sock The control socket configuration.
+ /// @param output The output stream.
+ TestStdoutControlSocket(CfgControlSocketPtr ctrl_sock, ostream& output)
+ : StdoutControlSocket(ctrl_sock, output) {
+ }
+
+ /// @brief Destructor.
+ virtual ~TestStdoutControlSocket() {
+ }
+};
+
+/// @brief Type definition for the pointer to the @c TestStdoutControlSocket.
+typedef boost::shared_ptr<TestStdoutControlSocket> TestStdoutControlSocketPtr;
+
+// Verifies that the createControlSocket template can create a stdout
+// control socket.
+TEST(StdoutControlSocketTest, createControlSocket) {
+ CfgControlSocketPtr cfg(new
+ CfgControlSocket(CfgControlSocket::Type::STDOUT,
+ "",
+ Url("http://127.0.0.1:8000/")));
+ ASSERT_TRUE(cfg);
+ ControlSocketBasePtr cs = controlSocketFactory(cfg);
+ ASSERT_TRUE(cs);
+ StdoutControlSocketPtr scs =
+ boost::dynamic_pointer_cast<StdoutControlSocket>(cs);
+ EXPECT_TRUE(scs);
+}
+
+// Verifies that a stdout control socket does not implement configGet.
+TEST(StdoutControlSocketTest, configGet) {
+ CfgControlSocketPtr cfg(new
+ CfgControlSocket(CfgControlSocket::Type::STDOUT,
+ "",
+ Url("http://127.0.0.1:8000/")));
+ ASSERT_TRUE(cfg);
+ StdoutControlSocketPtr scs(new StdoutControlSocket(cfg));
+ ASSERT_TRUE(scs);
+ EXPECT_THROW(scs->configGet("foo"), NotImplemented);
+}
+
+// Verifies that a stdout control socket does not nothing for configTest.
+TEST(StdoutControlSocketTest, configTest) {
+ CfgControlSocketPtr cfg(new
+ CfgControlSocket(CfgControlSocket::Type::STDOUT,
+ "",
+ Url("http://127.0.0.1:8000/")));
+ ASSERT_TRUE(cfg);
+ StdoutControlSocketPtr scs(new StdoutControlSocket(cfg));
+ ASSERT_TRUE(scs);
+ ConstElementPtr answer;
+ ASSERT_NO_THROW_LOG(answer = scs->configTest(ConstElementPtr(), "foo"));
+
+ // Check answer.
+ ASSERT_TRUE(answer);
+ EXPECT_EQ("{ \"result\": 0 }", answer->str());
+}
+
+// Verifies that a stdout control socket outputs configSet argument.
+TEST(StdoutControlSocketTest, configSet) {
+ CfgControlSocketPtr cfg(new
+ CfgControlSocket(CfgControlSocket::Type::STDOUT,
+ "",
+ Url("http://127.0.0.1:8000/")));
+ ASSERT_TRUE(cfg);
+ ostringstream os;
+ TestStdoutControlSocketPtr tscs(new TestStdoutControlSocket(cfg, os));
+ ASSERT_TRUE(tscs);
+ ConstElementPtr json = Element::fromJSON("{ \"bar\": 1 }");
+ ConstElementPtr answer;
+ ASSERT_NO_THROW_LOG(answer = tscs->configSet(json, "foo"));
+
+ // Check answer.
+ ASSERT_TRUE(answer);
+ EXPECT_EQ("{ \"result\": 0 }", answer->str());
+
+ // Check output.
+ string expected = "{\n \"bar\": 1\n}\n";
+ EXPECT_EQ(expected, os.str());
+}
+
+//////////////////////////////// UNIX ////////////////////////////////
+
+/// @brief Test timeout in ms.
+const long TEST_TIMEOUT = 10000;
+
+/// @brief Test fixture class for unix control sockets.
+class UnixControlSocketTest : public ThreadedTest {
+public:
+ isc::test::Sandbox sandbox;
+
+ /// @brief Constructor.
+ UnixControlSocketTest()
+ : ThreadedTest(), io_service_() {
+ }
+
+
+ void SetUp() override {
+ SysrepoSetup::cleanSharedMemory();
+ removeUnixSocketFile();
+ }
+
+ void TearDown() override {
+ if (thread_) {
+ thread_->join();
+ thread_.reset();
+ }
+ // io_service must be stopped after the thread returns,
+ // otherwise the thread may never return if it is
+ // waiting for the completion of some asynchronous tasks.
+ io_service_.stop();
+ removeUnixSocketFile();
+ }
+
+ /// @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.
+ 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 Create configuration of the control socket.
+ ///
+ /// @return a pointer to a control socket configuration.
+ CfgControlSocketPtr createCfgControlSocket() {
+ CfgControlSocketPtr cfg;
+ cfg.reset(new CfgControlSocket(CfgControlSocket::Type::UNIX,
+ unixSocketFilePath(),
+ Url("http://127.0.0.1:8000/")));
+ return (cfg);
+ }
+
+ /// @brief Thread reflecting server function.
+ void reflectServer();
+
+ /// @brief IOService object.
+ IOService io_service_;
+};
+
+/// @brief Server method running in a thread reflecting the command.
+///
+/// It creates the server socket, accepts client connection, read
+/// the command and send it back in a received JSON map.
+void
+UnixControlSocketTest::reflectServer() {
+ // Acceptor.
+ boost::asio::local::stream_protocol::acceptor
+ acceptor(io_service_.get_io_service());
+ EXPECT_NO_THROW(acceptor.open());
+ boost::asio::local::stream_protocol::endpoint
+ endpoint(unixSocketFilePath());
+ EXPECT_NO_THROW(acceptor.bind(endpoint));
+ EXPECT_NO_THROW(acceptor.listen());
+ boost::asio::local::stream_protocol::socket
+ socket(io_service_.get_io_service());
+
+ // Ready.
+ signalReady();
+
+ // Timeout.
+ IntervalTimer timer(io_service_);
+ bool timeout = false;
+ timer.setup([&timeout]() {
+ timeout = true;
+ FAIL() << "timeout";
+ }, 1500, IntervalTimer::ONE_SHOT);
+
+ // Accept.
+ bool accepted = false;
+ boost::system::error_code ec;
+ acceptor.async_accept(socket,
+ [&ec, &accepted]
+ (const boost::system::error_code& error) {
+ ec = error;
+ accepted = true;
+ });
+ while (!accepted && !timeout) {
+ io_service_.run_one();
+ }
+ ASSERT_FALSE(ec);
+
+ // Receive command.
+ string rbuf(1024, ' ');
+ size_t received = 0;
+ socket.async_receive(boost::asio::buffer(&rbuf[0], rbuf.size()),
+ [&ec, &received]
+ (const boost::system::error_code& error, size_t cnt) {
+ ec = error;
+ received = cnt;
+ });
+ while (!received && !timeout) {
+ io_service_.run_one();
+ }
+ ASSERT_FALSE(ec);
+ rbuf.resize(received);
+
+ // Reflect.
+ ElementPtr map = Element::createMap();
+ map->set("received", Element::create(rbuf));
+ string sbuf = map->str();
+
+ // Send back.
+ size_t sent = 0;
+ socket.async_send(boost::asio::buffer(&sbuf[0], sbuf.size()),
+ [&ec, &sent]
+ (const boost::system::error_code& error, size_t cnt) {
+ ec = error;
+ sent = cnt;
+ });
+ while (!sent && !timeout) {
+ io_service_.run_one();
+ }
+ ASSERT_FALSE(ec);
+
+ // Stop timer.
+ timer.cancel();
+
+ // Close socket.
+ if (socket.is_open()) {
+ EXPECT_NO_THROW(socket.close());
+ }
+
+ EXPECT_FALSE(timeout);
+ EXPECT_TRUE(accepted);
+ EXPECT_TRUE(received);
+ EXPECT_TRUE(sent);
+ EXPECT_EQ(sent, sbuf.size());
+}
+
+// Verifies that the createControlSocket template can create an unix
+// control socket.
+TEST_F(UnixControlSocketTest, createControlSocket) {
+ CfgControlSocketPtr cfg = createCfgControlSocket();
+ ASSERT_TRUE(cfg);
+ ControlSocketBasePtr cs = controlSocketFactory(cfg);
+ ASSERT_TRUE(cs);
+ UnixControlSocketPtr ucs =
+ boost::dynamic_pointer_cast<UnixControlSocket>(cs);
+ EXPECT_TRUE(ucs);
+}
+
+// Verifies that unix control sockets handle configGet() as expected.
+TEST_F(UnixControlSocketTest, configGet) {
+ CfgControlSocketPtr cfg = createCfgControlSocket();
+ ASSERT_TRUE(cfg);
+ UnixControlSocketPtr ucs(new UnixControlSocket(cfg));
+ ASSERT_TRUE(ucs);
+
+ // Run a reflecting server in a thread.
+ thread_.reset(new thread([this]() { reflectServer(); }));
+
+ waitReady();
+
+ // Try configGet.
+ ConstElementPtr reflected;
+ EXPECT_NO_THROW(reflected = ucs->configGet("foo"));
+ ASSERT_TRUE(reflected);
+ ASSERT_EQ(Element::map, reflected->getType());
+ ConstElementPtr command = reflected->get("received");
+ ASSERT_TRUE(command);
+ ASSERT_EQ(Element::string, command->getType());
+ string expected = "{ \"command\": \"config-get\" }";
+ EXPECT_EQ(expected, command->stringValue());
+}
+
+// Verifies that unix control sockets handle configTest() as expected.
+TEST_F(UnixControlSocketTest, configTest) {
+ CfgControlSocketPtr cfg = createCfgControlSocket();
+ ASSERT_TRUE(cfg);
+ UnixControlSocketPtr ucs(new UnixControlSocket(cfg));
+ ASSERT_TRUE(ucs);
+
+ // Run a reflecting server in a thread.
+ thread_.reset(new thread([this]() { reflectServer(); }));
+
+ waitReady();
+
+ // Prepare a config to test.
+ ConstElementPtr json = Element::fromJSON("{ \"bar\": 1 }");
+
+ ConstElementPtr reflected;
+ EXPECT_NO_THROW(reflected = ucs->configTest(json, "foo"));
+ ASSERT_TRUE(reflected);
+ ASSERT_EQ(Element::map, reflected->getType());
+ ConstElementPtr command = reflected->get("received");
+ ASSERT_TRUE(command);
+ ASSERT_EQ(Element::string, command->getType());
+ string expected = "{ \"arguments\": { \"bar\": 1 }, "
+ "\"command\": \"config-test\" }";
+ EXPECT_EQ(expected, command->stringValue());
+}
+
+// Verifies that unix control sockets handle configSet() as expected.
+TEST_F(UnixControlSocketTest, configSet) {
+ CfgControlSocketPtr cfg = createCfgControlSocket();
+ ASSERT_TRUE(cfg);
+ UnixControlSocketPtr ucs(new UnixControlSocket(cfg));
+ ASSERT_TRUE(ucs);
+
+ // Run a reflecting server in a thread.
+ thread_.reset(new thread([this]() { reflectServer(); }));
+
+ waitReady();
+
+ // Prepare a config to set.
+ ConstElementPtr json = Element::fromJSON("{ \"bar\": 1 }");
+
+ ConstElementPtr reflected;
+ EXPECT_NO_THROW(reflected = ucs->configSet(json, "foo"));
+ ASSERT_TRUE(reflected);
+ ASSERT_EQ(Element::map, reflected->getType());
+ ConstElementPtr command = reflected->get("received");
+ ASSERT_TRUE(command);
+ ASSERT_EQ(Element::string, command->getType());
+ string expected = "{ \"arguments\": { \"bar\": 1 }, "
+ "\"command\": \"config-set\" }";
+ EXPECT_EQ(expected, command->stringValue());
+}
+
+// Verifies that unix control sockets handle timeouts.
+TEST_F(UnixControlSocketTest, timeout) {
+ CfgControlSocketPtr cfg = createCfgControlSocket();
+ ASSERT_TRUE(cfg);
+ UnixControlSocketPtr ucs(new UnixControlSocket(cfg));
+ ASSERT_TRUE(ucs);
+
+ // Run a timeout server in a thread.
+ thread_.reset(new thread([this]() { waitReady(); }));
+
+ // Try configGet: it should get a communication error,
+ EXPECT_THROW(ucs->configGet("foo"), ControlSocketError);
+ signalReady();
+}
+
+//////////////////////////////// HTTP ////////////////////////////////
+
+/// @brief IP address to which HTTP service is bound.
+const string SERVER_ADDRESS = "127.0.0.1";
+
+/// @brief Port number to which HTTP service is bound.
+const uint16_t SERVER_PORT = 18123;
+
+/// @brief Test HTTP JSON response.
+typedef TestHttpResponseBase<HttpResponseJson> Response;
+
+/// @brief Pointer to test HTTP JSON response.
+typedef boost::shared_ptr<Response> ResponsePtr;
+
+/// @brief Generic test HTTP response.
+typedef TestHttpResponseBase<HttpResponse> GenericResponse;
+
+/// @brief Pointer to generic test HTTP response.
+typedef boost::shared_ptr<GenericResponse> GenericResponsePtr;
+
+/// @brief Implementation of the HttpResponseCreator.
+///
+/// Send back the request in a received JSON map.
+class TestHttpResponseCreator : public HttpResponseCreator {
+public:
+ /// @brief Create a new request.
+ ///
+ /// @return Pointer to the new instance of the HttpRequest.
+ virtual HttpRequestPtr
+ createNewHttpRequest() const {
+ return (HttpRequestPtr(new PostHttpRequestJson()));
+ }
+
+protected:
+ /// @brief Creates HTTP response.
+ ///
+ /// @param request Pointer to the HTTP request.
+ /// @return Pointer to the generated HTTP response.
+ virtual HttpResponsePtr
+ createStockHttpResponse(const HttpRequestPtr& request,
+ const HttpStatusCode& status_code) const {
+ // Data is in the request context.
+ HttpVersion http_version(request->context()->http_version_major_,
+ request->context()->http_version_minor_);
+ ResponsePtr response(new Response(http_version, status_code));
+ response->finalize();
+ return (response);
+ }
+
+ /// @brief Creates HTTP response.
+ ///
+ /// Build a response with reflected request in a received JSON map.
+ /// It can be told to respond with a partial JSON.
+ ///
+ /// @param request Pointer to the HTTP request.
+ /// @return Pointer to an object representing HTTP response.
+ virtual HttpResponsePtr
+ createDynamicHttpResponse(HttpRequestPtr request) {
+ // Request must always be JSON.
+ PostHttpRequestJsonPtr request_json =
+ boost::dynamic_pointer_cast<PostHttpRequestJson>(request);
+ if (!request_json) {
+ isc_throw(Unexpected, "request is not JSON");
+ }
+ ConstElementPtr body = request_json->getBodyAsJson();
+ if (!body) {
+ isc_throw(Unexpected, "can't get JSON from request");
+ }
+
+ // Check for the special partial JSON.
+ ConstElementPtr arguments = body->get("arguments");
+ if (arguments && (arguments->contains("want-partial"))) {
+ // Use a generic response.
+ GenericResponsePtr
+ response(new GenericResponse(request->getHttpVersion(),
+ HttpStatusCode::OK));
+ HttpResponseContextPtr ctx = response->context();
+ // Generate JSON response.
+ ctx->headers_.push_back(HttpHeaderContext("Content-Type",
+ "application/json"));
+ // Not closed JSON map so it will fail.
+ ctx->body_ = "{";
+ response->finalize();
+ // Take into account the missing '}'.
+ response->setContentLength(2);
+ return (response);
+ }
+
+ // Reflect.
+ ResponsePtr response(new Response(request->getHttpVersion(),
+ HttpStatusCode::OK));
+ ElementPtr map = Element::createMap();
+ map->set("received", Element::create(body->str()));
+ response->setBodyAsJson(map);
+ response->finalize();
+ return (response);
+ }
+};
+
+/// @brief Implementation of the test HttpResponseCreatorFactory.
+class TestHttpResponseCreatorFactory : public HttpResponseCreatorFactory {
+public:
+
+ /// @brief Creates @ref TestHttpResponseCreator instance.
+ virtual HttpResponseCreatorPtr create() const {
+ HttpResponseCreatorPtr response_creator(new TestHttpResponseCreator());
+ return (response_creator);
+ }
+};
+
+/// @brief Test fixture class for http control sockets.
+class HttpControlSocketTest : public ThreadedTest {
+public:
+ void SetUp() override {
+ SysrepoSetup::cleanSharedMemory();
+ }
+
+ void TearDown() override {
+ SysrepoSetup::cleanSharedMemory();
+ if (thread_) {
+ thread_->join();
+ thread_.reset();
+ }
+ // io_service must be stopped after the thread returns,
+ // otherwise the thread may never return if it is
+ // waiting for the completion of some asynchronous tasks.
+ io_service_.stop();
+ }
+
+ /// @brief Returns socket URL.
+ static Url httpSocketUrl() {
+ ostringstream s;
+ s << "http://" << SERVER_ADDRESS << ":" << SERVER_PORT << "/";
+ return (Url(s.str()));
+ }
+
+ /// @brief Create configuration of the control socket.
+ ///
+ /// @return a pointer to a control socket configuration.
+ CfgControlSocketPtr createCfgControlSocket() {
+ CfgControlSocketPtr cfg;
+ cfg.reset(new CfgControlSocket(CfgControlSocket::Type::HTTP,
+ "", httpSocketUrl()));
+ return (cfg);
+ }
+
+ /// @brief Create the reflecting listener.
+ void createReflectListener();
+
+ /// @brief Start listener.
+ ///
+ /// Run IO in a thread.
+ void start() {
+ thread_.reset(new thread([this]() {
+ // The thread is ready to go. Signal it to the main
+ // thread so it can start the actual test.
+ signalReady();
+ // Until stop() is called run IO service.
+ while (!isStopping()) {
+ io_service_.run_one();
+ }
+ // Main thread signalled that the thread should
+ // terminate. Launch any outstanding IO service
+ // handlers.
+ io_service_.poll();
+ // Nothing more to do. Signal that the thread is
+ // done so as the main thread can close HTTP
+ // listener and clean up after the test.
+ signalStopped();
+ }));
+
+ // Main thread waits here for the thread to start.
+ waitReady();
+
+ // If the thread is ready to go, start the listener.
+ if (listener_) {
+ ASSERT_NO_THROW_LOG(listener_->start());
+ }
+ }
+
+ /// @brief Stop listener.
+ ///
+ /// Post an empty action to finish current run_one.
+ void stop() {
+ // Notify the thread that it should terminate.
+ signalStopping();
+ // If the thread is blocked on running the IO
+ // service, post the empty handler to cause
+ // run_one to return.
+ io_service_.post([]() { return; });
+ // We asked that the thread stops. Let's wait
+ // for it to signal that it has stopped.
+ waitStopped();
+
+ // Thread has terminated. We can stop the HTTP
+ // listener safely.
+ if (listener_) {
+ ASSERT_NO_THROW_LOG(listener_->stop());
+ }
+ }
+
+ /// @brief IOService object.
+ IOService io_service_;
+
+ /// @brief Pointer to listener.
+ HttpListenerPtr listener_;
+};
+
+/// @brief Create the reflecting listener.
+void
+HttpControlSocketTest::createReflectListener() {
+ HttpResponseCreatorFactoryPtr
+ factory(new TestHttpResponseCreatorFactory());
+ listener_.reset(new
+ HttpListener(io_service_,
+ IOAddress(SERVER_ADDRESS), SERVER_PORT,
+ TlsContextPtr(), factory,
+ HttpListener::RequestTimeout(2000),
+ HttpListener::IdleTimeout(2000)));
+}
+
+// Verifies that the createControlSocket template can create a http
+// control socket.
+TEST_F(HttpControlSocketTest, createControlSocket) {
+ CfgControlSocketPtr cfg = createCfgControlSocket();
+ ASSERT_TRUE(cfg);
+ ControlSocketBasePtr cs = controlSocketFactory(cfg);
+ ASSERT_TRUE(cs);
+ HttpControlSocketPtr hcs =
+ boost::dynamic_pointer_cast<HttpControlSocket>(cs);
+ EXPECT_TRUE(hcs);
+}
+
+// Verifies that http control sockets handle configGet() as expected.
+TEST_F(HttpControlSocketTest, configGet) {
+ CfgControlSocketPtr cfg = createCfgControlSocket();
+ ASSERT_TRUE(cfg);
+ HttpControlSocketPtr hcs(new HttpControlSocket(cfg));
+ ASSERT_TRUE(hcs);
+
+ // Run a reflecting server in a thread.
+ createReflectListener();
+ start();
+
+ // Try configGet.
+ ConstElementPtr reflected;
+ EXPECT_NO_THROW(reflected = hcs->configGet("foo"));
+ stop();
+
+ // Check result.
+ ASSERT_TRUE(reflected);
+ ASSERT_EQ(Element::map, reflected->getType());
+ ConstElementPtr command = reflected->get("received");
+ ASSERT_TRUE(command);
+ ASSERT_EQ(Element::string, command->getType());
+ string expected = "{ \"command\": \"config-get\", "
+ "\"remote-address\": \"127.0.0.1\", \"service\": [ \"foo\" ] }";
+ EXPECT_EQ(expected, command->stringValue());
+}
+
+// Verifies that http control sockets handle configGet() for a control agent
+// as expected.
+TEST_F(HttpControlSocketTest, configGetCA) {
+ CfgControlSocketPtr cfg = createCfgControlSocket();
+ ASSERT_TRUE(cfg);
+ HttpControlSocketPtr hcs(new HttpControlSocket(cfg));
+ ASSERT_TRUE(hcs);
+
+ // Run a reflecting server in a thread.
+ createReflectListener();
+ start();
+
+ // Try configGet.
+ ConstElementPtr reflected;
+ EXPECT_NO_THROW(reflected = hcs->configGet("ca"));
+ stop();
+
+ // Check result.
+ ASSERT_TRUE(reflected);
+ ASSERT_EQ(Element::map, reflected->getType());
+ ConstElementPtr command = reflected->get("received");
+ ASSERT_TRUE(command);
+ ASSERT_EQ(Element::string, command->getType());
+ string expected = "{ \"command\": \"config-get\", \"remote-address\": \"127.0.0.1\" }";
+ EXPECT_EQ(expected, command->stringValue());
+}
+
+// Verifies that http control sockets handle configTest() as expected.
+TEST_F(HttpControlSocketTest, configTest) {
+ CfgControlSocketPtr cfg = createCfgControlSocket();
+ ASSERT_TRUE(cfg);
+ HttpControlSocketPtr hcs(new HttpControlSocket(cfg));
+ ASSERT_TRUE(hcs);
+
+ // Run a reflecting server in a thread.
+ createReflectListener();
+ start();
+
+ // Prepare a config to test.
+ ConstElementPtr json = Element::fromJSON("{ \"bar\": 1 }");
+
+ // Try configTest.
+ ConstElementPtr reflected;
+ EXPECT_NO_THROW(reflected = hcs->configTest(json, "foo"));
+ stop();
+
+ // Check result.
+ ASSERT_TRUE(reflected);
+ ASSERT_EQ(Element::map, reflected->getType());
+ ConstElementPtr command = reflected->get("received");
+ ASSERT_TRUE(command);
+ ASSERT_EQ(Element::string, command->getType());
+ string expected = "{ \"arguments\": { \"bar\": 1 }, "
+ "\"command\": \"config-test\", "
+ "\"remote-address\": \"127.0.0.1\", \"service\": [ \"foo\" ] }";
+ EXPECT_EQ(expected, command->stringValue());
+}
+
+// Verifies that http control sockets handle configTest() for a control agent
+// as expected.
+TEST_F(HttpControlSocketTest, configTestCA) {
+ CfgControlSocketPtr cfg = createCfgControlSocket();
+ ASSERT_TRUE(cfg);
+ HttpControlSocketPtr hcs(new HttpControlSocket(cfg));
+ ASSERT_TRUE(hcs);
+
+ // Run a reflecting server in a thread.
+ createReflectListener();
+ start();
+
+ // Prepare a config to test.
+ ConstElementPtr json = Element::fromJSON("{ \"bar\": 1 }");
+
+ // Try configTest.
+ ConstElementPtr reflected;
+ EXPECT_NO_THROW(reflected = hcs->configTest(json, "ca"));
+ stop();
+
+ // Check result.
+ ASSERT_TRUE(reflected);
+ ASSERT_EQ(Element::map, reflected->getType());
+ ConstElementPtr command = reflected->get("received");
+ ASSERT_TRUE(command);
+ ASSERT_EQ(Element::string, command->getType());
+ string expected = "{ \"arguments\": { \"bar\": 1 }, "
+ "\"command\": \"config-test\", \"remote-address\": \"127.0.0.1\" }";
+ EXPECT_EQ(expected, command->stringValue());
+}
+
+// Verifies that http control sockets handle configSet() as expected.
+TEST_F(HttpControlSocketTest, configSet) {
+ CfgControlSocketPtr cfg = createCfgControlSocket();
+ ASSERT_TRUE(cfg);
+ HttpControlSocketPtr hcs(new HttpControlSocket(cfg));
+ ASSERT_TRUE(hcs);
+
+ // Run a reflecting server in a thread.
+ createReflectListener();
+ start();
+
+ // Prepare a config to set.
+ ConstElementPtr json = Element::fromJSON("{ \"bar\": 1 }");
+
+ // Try configSet.
+ ConstElementPtr reflected;
+ EXPECT_NO_THROW(reflected = hcs->configSet(json, "foo"));
+ stop();
+
+ // Check result.
+ ASSERT_TRUE(reflected);
+ ASSERT_EQ(Element::map, reflected->getType());
+ ConstElementPtr command = reflected->get("received");
+ ASSERT_TRUE(command);
+ ASSERT_EQ(Element::string, command->getType());
+ string expected = "{ \"arguments\": { \"bar\": 1 }, "
+ "\"command\": \"config-set\", "
+ "\"remote-address\": \"127.0.0.1\", \"service\": [ \"foo\" ] }";
+ EXPECT_EQ(expected, command->stringValue());
+}
+
+// Verifies that http control sockets handle configSet() for a control agent
+// as expected.
+TEST_F(HttpControlSocketTest, configSetCA) {
+ CfgControlSocketPtr cfg = createCfgControlSocket();
+ ASSERT_TRUE(cfg);
+ HttpControlSocketPtr hcs(new HttpControlSocket(cfg));
+ ASSERT_TRUE(hcs);
+
+ // Run a reflecting server in a thread.
+ createReflectListener();
+ start();
+
+ // Prepare a config to set.
+ ConstElementPtr json = Element::fromJSON("{ \"bar\": 1 }");
+
+ // Try configSet.
+ ConstElementPtr reflected;
+ EXPECT_NO_THROW(reflected = hcs->configSet(json, "ca"));
+ stop();
+
+ // Check result.
+ ASSERT_TRUE(reflected);
+ ASSERT_EQ(Element::map, reflected->getType());
+ ConstElementPtr command = reflected->get("received");
+ ASSERT_TRUE(command);
+ ASSERT_EQ(Element::string, command->getType());
+ string expected = "{ \"arguments\": { \"bar\": 1 }, "
+ "\"command\": \"config-set\", \"remote-address\": \"127.0.0.1\" }";
+ EXPECT_EQ(expected, command->stringValue());
+}
+
+// Verifies that http control sockets handle can't connect errors.
+TEST_F(HttpControlSocketTest, connectionRefused) {
+ CfgControlSocketPtr cfg = createCfgControlSocket();
+ ASSERT_TRUE(cfg);
+ HttpControlSocketPtr hcs(new HttpControlSocket(cfg));
+ ASSERT_TRUE(hcs);
+
+ // Try configGet: it should get a communication error,
+ try {
+ hcs->configGet("foo");
+ } catch (const ControlSocketError& ex) {
+ EXPECT_EQ("communication error (code): Connection refused",
+ string(ex.what()));
+ } catch (const std::exception& ex) {
+ FAIL() << "unexpected exception: " << ex.what();
+ } catch (...) {
+ FAIL() << "unexpected exception";
+ }
+}
+
+// Verifies that http control sockets handle timeout errors.
+TEST_F(HttpControlSocketTest, partial) {
+ CfgControlSocketPtr cfg = createCfgControlSocket();
+ ASSERT_TRUE(cfg);
+ HttpControlSocketPtr hcs(new HttpControlSocket(cfg));
+ ASSERT_TRUE(hcs);
+
+ // Create the server but do not start it.
+ createReflectListener();
+ start();
+
+ // Prepare a special config to set.
+ ConstElementPtr json = Element::fromJSON("{ \"want-partial\": true }");
+
+ // Warn this makes time.
+ cout << "this test waits for 2 seconds" << endl;
+
+ // Try configSet: it should get a communication error,
+ try {
+ hcs->configSet(json, "foo");
+ } catch (const ControlSocketError& ex) {
+ EXPECT_EQ("communication error (code): End of file",
+ string(ex.what()));
+ } catch (const std::exception& ex) {
+ FAIL() << "unexpected exception: " << ex.what();
+ } catch (...) {
+ FAIL() << "unexpected exception";
+ }
+ stop();
+}
+
+} // namespace
diff --git a/src/bin/netconf/tests/get_config_unittest.cc b/src/bin/netconf/tests/get_config_unittest.cc
new file mode 100644
index 0000000..fd5c2e6
--- /dev/null
+++ b/src/bin/netconf/tests/get_config_unittest.cc
@@ -0,0 +1,294 @@
+// Copyright (C) 2018-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <cc/data.h>
+#include <cc/command_interpreter.h>
+#include <testutils/gtest_utils.h>
+#include <testutils/user_context_utils.h>
+#include <process/testutils/d_test_stubs.h>
+#include <netconf/netconf_cfg_mgr.h>
+#include <netconf/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_libraries.h"
+
+using namespace isc::netconf;
+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 netconf_unittests on
+/// NetconfGetCfgTest redirecting the standard error to a temporary
+/// file, e.g. by
+/// @code
+/// ./netconf_unittests --gtest_filter="NetconfGetCfg*" > /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 NETCONF mode
+ElementPtr
+parseNETCONF(const std::string& in, bool verbose = false) {
+ try {
+ ParserContext ctx;
+ return (ctx.parseString(in, ParserContext::PARSER_NETCONF));
+ } catch (const std::exception& ex) {
+ if (verbose) {
+ std::cout << "EXCEPTION: " << ex.what() << std::endl;
+ }
+ throw;
+ }
+}
+
+/// @brief Replace the library path
+void
+pathReplacer(ConstElementPtr netconf_cfg) {
+ ConstElementPtr hooks_libs = netconf_cfg->get("hooks-libraries");
+ if (!hooks_libs || hooks_libs->empty()) {
+ return;
+ }
+ ElementPtr first_lib = hooks_libs->getNonConst(0);
+ std::string lib_path(BASIC_CALLOUT_LIBRARY);
+ first_lib->set("library", Element::create(lib_path));
+}
+
+/// @brief Almost regular netconf CfgMgr with internal parse method exposed.
+class NakedNetconfCfgMgr : public NetconfCfgMgr {
+public:
+ using NetconfCfgMgr::parse;
+};
+
+} // namespace
+
+/// @brief Test fixture class
+class NetconfGetCfgTest : public ConfigParseTest {
+public:
+ NetconfGetCfgTest()
+ : rcode_(-1) {
+ srv_.reset(new NakedNetconfCfgMgr());
+ // Create fresh context.
+ resetConfiguration();
+ }
+
+ ~NetconfGetCfgTest() {
+ 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 NETCONF parser
+ try {
+ json = parseNETCONF(config, true);
+ } catch (...) {
+ ADD_FAILURE() << "parsing failed for " << operation
+ << " on\n" << prettyPrint(json) << "\n";
+ return (false);
+ }
+
+ // get Netconf element
+ ConstElementPtr ca = json->get("Netconf");
+ if (!ca) {
+ ADD_FAILURE() << "cannot get Netconf for " << operation
+ << " on\n" << prettyPrint(json) << "\n";
+ return (false);
+ }
+
+ // update hooks-libraries
+ pathReplacer(ca);
+
+ // try NETCONF 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 managed servers 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 = "{ \"Netconf\": { } }";
+ EXPECT_TRUE(executeConfiguration(config, "reset config"));
+ }
+
+ boost::scoped_ptr<NakedNetconfCfgMgr> srv_; ///< Netconf server under test
+ int rcode_; ///< Return code from element parsing
+ ConstElementPtr comment_; ///< Reason for parse fail
+};
+
+// Test a simple configuration.
+TEST_F(NetconfGetCfgTest, simple) {
+
+ // get the simple configuration
+ std::string simple_file = string(CFG_EXAMPLES) + "/" + "simple-dhcp4.json";
+ std::string config;
+ ASSERT_NO_THROW_LOG(config = readFile(simple_file));
+
+ // get the expected configuration
+ std::string expected_file =
+ std::string(NETCONF_TEST_DATA_DIR) + "/" + "get_config.json";
+ std::string expected;
+ ASSERT_NO_THROW_LOG(expected = readFile(expected_file));
+
+ // execute the sample configuration
+ ASSERT_TRUE(executeConfiguration(config, "simple config"));
+
+ // unparse it
+ NetconfConfigPtr context = srv_->getNetconfConfig();
+ ConstElementPtr unparsed;
+ ASSERT_NO_THROW_LOG(unparsed = context->toElement());
+
+ // dump if wanted else check
+ if (generate_action) {
+ std::cerr << "// Generated Configuration (remove this line)\n";
+ ASSERT_NO_THROW_LOG(expected = prettyPrint(unparsed));
+ prettyPrint(unparsed, std::cerr, 0, 4);
+ std::cerr << "\n";
+ } else {
+ // get the expected config using the netconf syntax parser
+ ElementPtr jsond;
+ ASSERT_NO_THROW_LOG(jsond = parseNETCONF(expected, true));
+ // get the expected config using the generic JSON syntax parser
+ ElementPtr jsonj;
+ ASSERT_NO_THROW_LOG(jsonj = parseJSON(expected));
+ // the generic JSON parser does not handle comments
+ EXPECT_TRUE(isEquivalent(jsond, moveComments(jsonj)));
+ // replace the path by its actual value
+ ConstElementPtr ca;
+ ASSERT_NO_THROW_LOG(ca = jsonj->get("Netconf"));
+ ASSERT_TRUE(ca);
+ pathReplacer(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 netconft configuration
+ EXPECT_TRUE(executeConfiguration(expected, "unparsed config"));
+
+ // is it a fixed point?
+ NetconfConfigPtr context2 = srv_->getNetconfConfig();
+ ConstElementPtr unparsed2;
+ ASSERT_NO_THROW_LOG(unparsed2 = context2->toElement());
+ ASSERT_TRUE(unparsed2);
+ EXPECT_TRUE(isEquivalent(unparsed, unparsed2));
+}
diff --git a/src/bin/netconf/tests/netconf_cfg_mgr_unittests.cc b/src/bin/netconf/tests/netconf_cfg_mgr_unittests.cc
new file mode 100644
index 0000000..1ec7b87
--- /dev/null
+++ b/src/bin/netconf/tests/netconf_cfg_mgr_unittests.cc
@@ -0,0 +1,738 @@
+// Copyright (C) 2018-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <netconf/netconf_cfg_mgr.h>
+#include <netconf/parser_context.h>
+#include <exceptions/exceptions.h>
+#include <cc/command_interpreter.h>
+#include <process/testutils/d_test_stubs.h>
+#include <process/d_cfg_mgr.h>
+#include <yang/yang_models.h>
+#include <testutils/gtest_utils.h>
+#include <testutils/test_to_element.h>
+#include <netconf/tests/test_libraries.h>
+
+#include <boost/scoped_ptr.hpp>
+
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::netconf;
+using namespace isc::config;
+using namespace isc::data;
+using namespace isc::hooks;
+using namespace isc::http;
+using namespace isc::process;
+using namespace isc::yang;
+
+namespace {
+
+/// @brief Almost regular netconf CfgMgr with internal parse method exposed.
+class NakedNetconfCfgMgr : public NetconfCfgMgr {
+public:
+ using NetconfCfgMgr::parse;
+};
+
+// Tests construction of NetconfCfgMgr class.
+TEST(NetconfCfgMgr, construction) {
+ boost::scoped_ptr<NetconfCfgMgr> cfg_mgr;
+
+ // Verify that configuration manager constructions without error.
+ ASSERT_NO_THROW_LOG(cfg_mgr.reset(new NetconfCfgMgr()));
+
+ // Verify that the context can be retrieved and is not null.
+ NetconfConfigPtr context;
+ ASSERT_NO_THROW_LOG(context = cfg_mgr->getNetconfConfig());
+ 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(NetconfCfgMgr, getContext) {
+ NetconfCfgMgr cfg_mgr;
+
+ NetconfConfigPtr ctx;
+ ASSERT_NO_THROW_LOG(ctx = cfg_mgr.getNetconfConfig());
+ ASSERT_TRUE(ctx);
+}
+
+// Tests if context can store and retrieve managed server information.
+TEST(NetconfCfgMgr, contextServer) {
+
+ NetconfConfig ctx;
+
+ // Check managed server parameters.
+ // By default, there are no server stored.
+ ASSERT_TRUE(ctx.getCfgServersMap());
+ EXPECT_EQ(0, ctx.getCfgServersMap()->size());
+
+ CfgControlSocketPtr
+ socket1(new CfgControlSocket(CfgControlSocket::Type::UNIX,
+ "socket1",
+ Url("http://127.0.0.1:8000/")));
+ CfgServerPtr server1(new CfgServer("model1", socket1));
+ CfgControlSocketPtr
+ socket2(new CfgControlSocket(CfgControlSocket::Type::UNIX,
+ "socket2",
+ Url("http://127.0.0.1:8000/")));
+ CfgServerPtr server2(new CfgServer("model2", socket2));
+ CfgControlSocketPtr
+ socket3(new CfgControlSocket(CfgControlSocket::Type::UNIX,
+ "socket3",
+ Url("http://127.0.0.1:8000/")));
+ CfgServerPtr server3(new CfgServer("model3", socket3));
+ CfgControlSocketPtr
+ socket4(new CfgControlSocket(CfgControlSocket::Type::UNIX,
+ "socket4",
+ Url("http://127.0.0.1:8000/")));
+ CfgServerPtr server4(new CfgServer("model4", socket4));
+
+ // Ok, now set the server for D2
+ EXPECT_NO_THROW(ctx.getCfgServersMap()->insert(make_pair("d2", server1)));
+
+ // Now check the values returned
+ EXPECT_EQ(1, ctx.getCfgServersMap()->size());
+ ASSERT_NO_THROW_LOG(ctx.getCfgServersMap()->at("d2"));
+ EXPECT_EQ(server1, ctx.getCfgServersMap()->at("d2"));
+ EXPECT_THROW(ctx.getCfgServersMap()->at("dhcp4"), std::out_of_range);
+
+ // Now set the v6 server and sanity check again
+ EXPECT_NO_THROW(ctx.getCfgServersMap()->insert(make_pair("dhcp6", server2)));
+
+ // Should be possible to retrieve two servers
+ EXPECT_EQ(2, ctx.getCfgServersMap()->size());
+ ASSERT_NO_THROW_LOG(ctx.getCfgServersMap()->at("dhcp6"));
+ EXPECT_EQ(server1, ctx.getCfgServersMap()->at("d2"));
+ EXPECT_EQ(server2, ctx.getCfgServersMap()->at("dhcp6"));
+
+ // Finally, set all servers.
+ EXPECT_NO_THROW(ctx.getCfgServersMap()->insert(make_pair("dhcp4", server3)));
+ EXPECT_NO_THROW(ctx.getCfgServersMap()->insert(make_pair("ca", server4)));
+ EXPECT_EQ(4, ctx.getCfgServersMap()->size());
+ ASSERT_NO_THROW_LOG(ctx.getCfgServersMap()->at("dhcp4"));
+ ASSERT_NO_THROW_LOG(ctx.getCfgServersMap()->at("ca"));
+ EXPECT_EQ(server3, ctx.getCfgServersMap()->at("dhcp4"));
+ EXPECT_EQ(server4, ctx.getCfgServersMap()->at("ca"));
+}
+
+// Tests if the context can store and retrieve hook libs information.
+TEST(NetconfCfgMgr, contextHookParams) {
+ NetconfConfig 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());
+
+ EXPECT_EQ(libs.get(), stored_libs.get());
+}
+
+// Tests if the context can store and retrieve globals.
+TEST(NetconfCfgMgr, contextGlobals) {
+ NetconfConfig ctx;
+
+ // By default there should be no globals.
+ ConstElementPtr globals = ctx.getConfiguredGlobals();
+ ASSERT_TRUE(globals);
+ ASSERT_EQ(Element::map, globals->getType());
+ EXPECT_EQ(0, globals->mapValue().size());
+
+ // Attempting to extract globals from a non-map should throw.
+ ASSERT_THROW(ctx.extractConfiguredGlobals(Element::create(777)), BadValue);
+
+ // Now let's create a configuration from which to extract global scalars.
+ // Extraction (currently) has no business logic, so the elements we use
+ // can be arbitrary.
+ ConstElementPtr global_cfg;
+ string global_cfg_str =
+ "{\n"
+ " \"astring\": \"okay\",\n"
+ " \"amap\": { \"not-this\":777, \"not-that\": \"poo\" },\n"
+ " \"anint\": 444,\n"
+ " \"alist\": [ 1, 2, 3 ],\n"
+ " \"abool\": true\n"
+ "}\n";
+ ASSERT_NO_THROW_LOG(global_cfg = Element::fromJSON(global_cfg_str));
+
+ // Extract globals from the config.
+ ASSERT_NO_THROW_LOG(ctx.extractConfiguredGlobals(global_cfg));
+
+ // Now see if the extract was correct.
+ globals = ctx.getConfiguredGlobals();
+ ASSERT_TRUE(globals);
+ ASSERT_EQ(Element::map, globals->getType());
+ EXPECT_NE(0, globals->mapValue().size());
+
+ // Maps and lists should be excluded.
+ for (auto it : globals->mapValue()) {
+ if (it.first == "astring") {
+ ASSERT_EQ(Element::string, it.second->getType());
+ EXPECT_EQ("okay", it.second->stringValue());
+ } else if (it.first == "anint") {
+ ASSERT_EQ(Element::integer, it.second->getType());
+ EXPECT_EQ(444, it.second->intValue());
+ } else if (it.first == "abool") {
+ ASSERT_EQ(Element::boolean, it.second->getType());
+ EXPECT_TRUE(it.second->boolValue());
+ } else {
+ ADD_FAILURE() << "unexpected element found:" << it.first;
+ }
+ }
+}
+
+/// @brief Netconf configurations used in tests.
+const char* NETCONF_CONFIGS[] = {
+
+ // configuration 0: empty (nothing specified)
+ "{ }",
+
+ // Configuration 1: global parameters only (no server, not hooks)
+ "{\n"
+ " \"boot-update\": false,\n"
+ " \"subscribe-changes\": false,\n"
+ " \"validate-changes\": false\n"
+ "}",
+
+ // Configuration 2: 1 server
+ "{\n"
+ " \"boot-update\": false,\n"
+ " \"managed-servers\": {\n"
+ " \"dhcp4\": {\n"
+ " \"boot-update\": true,\n"
+ " \"control-socket\": {\n"
+ " \"socket-name\": \"/tmp/socket-v4\"\n"
+ " }\n"
+ " }\n"
+ " }\n"
+ "}",
+
+ // Configuration 3: all 4 servers
+ "{\n"
+ " \"boot-update\": false,\n"
+ " \"managed-servers\": {\n"
+ " \"dhcp4\": {\n"
+ " \"boot-update\": true,\n"
+ " \"control-socket\": {\n"
+ " \"socket-name\": \"/tmp/socket-v4\"\n"
+ " }\n"
+ " },\n"
+ " \"dhcp6\": {\n"
+ " \"control-socket\": {\n"
+ " \"socket-name\": \"/tmp/socket-v6\"\n"
+ " }\n"
+ " },\n"
+ " \"d2\": {\n"
+ " \"subscribe-changes\": false,\n"
+ " \"control-socket\": {\n"
+ " \"socket-name\": \"/tmp/socket-d2\"\n"
+ " }\n"
+ " },\n"
+ " \"ca\": {\n"
+ " \"control-socket\": {\n"
+ " \"socket-name\": \"/tmp/socket-ca\"\n"
+ " }\n"
+ " }\n"
+ " }\n"
+ "}",
+
+ // Configuration 4: 1 server and hooks
+ // Netconf is able to load hook libraries that augment its operation.
+ // The primary functionality is the ability to add new commands.
+ "{\n"
+ " \"managed-servers\": {\n"
+ " \"dhcp4\": {\n"
+ " \"control-socket\": {\n"
+ " \"socket-name\": \"/tmp/socket-v4\"\n"
+ " }\n"
+ " }\n"
+ " },\n"
+ " \"hooks-libraries\": ["
+ " {"
+ " \"library\": \"%LIBRARY%\","
+ " \"parameters\": {\n"
+ " \"param1\": \"foo\"\n"
+ " }\n"
+ " }\n"
+ " ]\n"
+ "}",
+
+ // Configuration 5: 1 server (d2 only)
+ "{\n"
+ " \"managed-servers\": {\n"
+ " \"d2\": {\n"
+ " \"subscribe-changes\": false,\n"
+ " \"control-socket\": {\n"
+ " \"socket-name\": \"/tmp/socket-d2\"\n"
+ " }\n"
+ " }\n"
+ " }\n"
+ "}",
+
+ // Configuration 6: 1 server (dhcp6 only)
+ "{\n"
+ " \"managed-servers\": {\n"
+ " \"dhcp6\": {\n"
+ " \"control-socket\": {\n"
+ " \"socket-name\": \"/tmp/socket-v6\"\n"
+ " }\n"
+ " }\n"
+ " }\n"
+ "}",
+
+ // Configuration 7: 2 servers with user contexts and comments
+ "{\n"
+ " \"user-context\": { \"comment\": \"Indirect comment\" },\n"
+ " \"managed-servers\": {\n"
+ " \"dhcp4\": {\n"
+ " \"comment\": \"dhcp4 server\",\n"
+ " \"control-socket\": {\n"
+ " \"socket-name\": \"/tmp/socket-v4\"\n"
+ " }\n"
+ " },\n"
+ " \"dhcp6\": {\n"
+ " \"control-socket\": {\n"
+ " \"socket-name\": \"/tmp/socket-v6\",\n"
+ " \"user-context\": { \"version\": 1 }\n"
+ " }\n"
+ " }\n"
+ " }\n"
+ "}",
+
+ // Configuration 8: empty server with no control socket
+ "{\n"
+ " \"managed-servers\": {\n"
+ " \"dhcp4\": {\n"
+ " \"comment\": \"empty map not allowed\"\n"
+ " }\n"
+ " }\n"
+ "}",
+
+ // Configuration 9: empty control socket
+ "{\n"
+ " \"boot-update\": false,\n"
+ " \"subscribe-changes\": false,\n"
+ " \"validate-changes\": false,\n"
+ " \"managed-servers\": {\n"
+ " \"dhcp4\": {\n"
+ " \"control-socket\": {\n"
+ " \"comment\": \"empty map not allowed\"\n"
+ " }\n"
+ " }\n"
+ " }\n"
+ "}",
+
+ // Configuration 10: bad socket type
+ "{\n"
+ " \"managed-servers\": {\n"
+ " \"dhcp6\": {\n"
+ " \"control-socket\": {\n"
+ " \"socket-type\": \"tcp\"\n"
+ " }\n"
+ " }\n"
+ " }\n"
+ "}",
+
+ // Configuration 11: invalid socket Url
+ "{\n"
+ " \"managed-servers\": {\n"
+ " \"dhcp6\": {\n"
+ " \"control-socket\": {\n"
+ " \"socket-url\": \"bad\"\n"
+ " }\n"
+ " }\n"
+ " }\n"
+ "}"
+};
+
+// Tests the handling of bad socket type. Can't use the fixture class
+// because the Netconf parser does not allow bad socket types.
+TEST(NetconfParser, badSocketType) {
+ ConstElementPtr json;
+ ParserContext parser;
+ EXPECT_NO_THROW(json = parser.parseString(NETCONF_CONFIGS[10],
+ ParserContext::PARSER_JSON));
+ ConstElementPtr answer;
+ NakedNetconfCfgMgr cfg_mgr;
+ EXPECT_NO_THROW(answer = cfg_mgr.parse(json, false));
+ int rcode = 0;
+ string expected =
+ "\"Unknown control socket type: tcp 'tcp' (<string>:5:32)\"";
+ EXPECT_EQ(expected, parseAnswer(rcode, answer)->str());
+ EXPECT_EQ(1, rcode);
+}
+
+/// @brief Class used for testing CfgMgr
+class NetconfParserTest : 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::netconf::ParserContext parser;
+ ConstElementPtr json = parser.parseString(config, ParserContext::PARSER_SUB_NETCONF);
+
+ 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
+ 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)
+ NakedNetconfCfgMgr 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.
+TEST_F(NetconfParserTest, configParseEmpty) {
+ configParse(NETCONF_CONFIGS[0], 0);
+
+ NetconfConfigPtr ctx = cfg_mgr_.getNetconfConfig();
+ ASSERT_TRUE(ctx);
+ ASSERT_TRUE(ctx->getCfgServersMap());
+ EXPECT_EQ(0, ctx->getCfgServersMap()->size());
+}
+
+// This test verifies if a config with only globals is handled properly.
+TEST_F(NetconfParserTest, configParseGlobalOnly) {
+ configParse(NETCONF_CONFIGS[1], 0);
+
+ NetconfConfigPtr ctx = cfg_mgr_.getNetconfConfig();
+ ASSERT_TRUE(ctx);
+ ASSERT_TRUE(ctx->getCfgServersMap());
+ EXPECT_EQ(0, ctx->getCfgServersMap()->size());
+ ConstElementPtr globals = ctx->getConfiguredGlobals();
+ ASSERT_TRUE(globals);
+ string expected = "{ "
+ "\"boot-update\": false, "
+ "\"subscribe-changes\": false, "
+ "\"validate-changes\": false }";
+ EXPECT_EQ(expected, globals->str());
+}
+
+// Tests if an empty (i.e. without a control socket) can be configured.
+// Note that the syntax required the server map to not be really empty.
+TEST_F(NetconfParserTest, configParseEmptyCfgServer) {
+ configParse(NETCONF_CONFIGS[8], 0);
+
+ NetconfConfigPtr ctx = cfg_mgr_.getNetconfConfig();
+ ASSERT_TRUE(ctx);
+ ASSERT_TRUE(ctx->getCfgServersMap());
+ EXPECT_EQ(1, ctx->getCfgServersMap()->size());
+ ASSERT_NO_THROW_LOG(ctx->getCfgServersMap()->at("dhcp4"));
+ CfgServerPtr server = ctx->getCfgServersMap()->at("dhcp4");
+ ASSERT_TRUE(server);
+ EXPECT_EQ(KEA_DHCP4_SERVER, server->getModel());
+ // Defaults.
+ EXPECT_TRUE(server->getBootUpdate());
+ EXPECT_TRUE(server->getSubscribeChanges());
+ EXPECT_TRUE(server->getValidateChanges());
+ CfgControlSocketPtr socket = server->getCfgControlSocket();
+ EXPECT_FALSE(socket);
+}
+
+// This tests default values using a server with empty control socket
+// Note that the syntax required the control socket map to not be really empty.
+TEST_F(NetconfParserTest, configParseDefaults) {
+ configParse(NETCONF_CONFIGS[9], 0);
+
+ NetconfConfigPtr ctx = cfg_mgr_.getNetconfConfig();
+ ASSERT_TRUE(ctx);
+ ASSERT_TRUE(ctx->getCfgServersMap());
+ EXPECT_EQ(1, ctx->getCfgServersMap()->size());
+ ASSERT_NO_THROW_LOG(ctx->getCfgServersMap()->at("dhcp4"));
+ CfgServerPtr server = ctx->getCfgServersMap()->at("dhcp4");
+ ASSERT_TRUE(server);
+ EXPECT_EQ(KEA_DHCP4_SERVER, server->getModel());
+ // Globals overwrite defaults.
+ EXPECT_FALSE(server->getBootUpdate());
+ EXPECT_FALSE(server->getSubscribeChanges());
+ EXPECT_FALSE(server->getValidateChanges());
+ CfgControlSocketPtr socket = server->getCfgControlSocket();
+ ASSERT_TRUE(socket);
+
+ // Checking default.
+ EXPECT_EQ(CfgControlSocket::Type::STDOUT, socket->getType());
+ EXPECT_EQ("", socket->getName());
+ EXPECT_EQ("http://127.0.0.1:8000/", socket->getUrl().toText());
+}
+
+// Tests if a single DHCPv4 server can be configured.
+TEST_F(NetconfParserTest, configParseServerDhcp4) {
+ configParse(NETCONF_CONFIGS[2], 0);
+
+ NetconfConfigPtr ctx = cfg_mgr_.getNetconfConfig();
+ ASSERT_TRUE(ctx);
+ ASSERT_TRUE(ctx->getCfgServersMap());
+ EXPECT_EQ(1, ctx->getCfgServersMap()->size());
+ ASSERT_NO_THROW_LOG(ctx->getCfgServersMap()->at("dhcp4"));
+ CfgServerPtr server = ctx->getCfgServersMap()->at("dhcp4");
+ ASSERT_TRUE(server);
+ EXPECT_EQ(KEA_DHCP4_SERVER, server->getModel());
+ // Locals overwrite globals.
+ EXPECT_TRUE(server->getBootUpdate());
+ EXPECT_TRUE(server->getSubscribeChanges());
+ EXPECT_TRUE(server->getValidateChanges());
+ CfgControlSocketPtr socket = server->getCfgControlSocket();
+ ASSERT_TRUE(socket);
+ EXPECT_EQ(CfgControlSocket::Type::STDOUT, socket->getType());
+ EXPECT_EQ("/tmp/socket-v4", socket->getName());
+ EXPECT_EQ("http://127.0.0.1:8000/", socket->getUrl().toText());
+}
+
+// Tests if a single D2 server can be configured.
+TEST_F(NetconfParserTest, configParseServerD2) {
+ configParse(NETCONF_CONFIGS[5], 0);
+
+ NetconfConfigPtr ctx = cfg_mgr_.getNetconfConfig();
+ ASSERT_TRUE(ctx);
+ ASSERT_TRUE(ctx->getCfgServersMap());
+ EXPECT_EQ(1, ctx->getCfgServersMap()->size());
+ ASSERT_NO_THROW_LOG(ctx->getCfgServersMap()->at("d2"));
+ CfgServerPtr server = ctx->getCfgServersMap()->at("d2");
+ ASSERT_TRUE(server);
+ EXPECT_EQ(KEA_DHCP_DDNS, server->getModel());
+ EXPECT_TRUE(server->getBootUpdate());
+ EXPECT_FALSE(server->getSubscribeChanges());
+ EXPECT_TRUE(server->getValidateChanges());
+ CfgControlSocketPtr socket = server->getCfgControlSocket();
+ ASSERT_TRUE(socket);
+ EXPECT_EQ(CfgControlSocket::Type::STDOUT, socket->getType());
+ EXPECT_EQ("/tmp/socket-d2", socket->getName());
+ EXPECT_EQ("http://127.0.0.1:8000/", socket->getUrl().toText());
+}
+
+// Tests if a single DHCPv6 server can be configured.
+TEST_F(NetconfParserTest, configParseServerDhcp6) {
+ configParse(NETCONF_CONFIGS[6], 0);
+
+ NetconfConfigPtr ctx = cfg_mgr_.getNetconfConfig();
+ ASSERT_TRUE(ctx);
+ ASSERT_TRUE(ctx->getCfgServersMap());
+ EXPECT_EQ(1, ctx->getCfgServersMap()->size());
+ ASSERT_NO_THROW_LOG(ctx->getCfgServersMap()->at("dhcp6"));
+ CfgServerPtr server = ctx->getCfgServersMap()->at("dhcp6");
+ ASSERT_TRUE(server);
+ EXPECT_EQ(KEA_DHCP6_SERVER, server->getModel());
+ CfgControlSocketPtr socket = server->getCfgControlSocket();
+ ASSERT_TRUE(socket);
+ EXPECT_EQ(CfgControlSocket::Type::STDOUT, socket->getType());
+ EXPECT_EQ("/tmp/socket-v6", socket->getName());
+ EXPECT_EQ("http://127.0.0.1:8000/", socket->getUrl().toText());
+}
+
+// This tests if all 4 servers can be configured and makes sure the parser
+// doesn't confuse them.
+TEST_F(NetconfParserTest, configParse4Servers) {
+ configParse(NETCONF_CONFIGS[3], 0);
+
+ NetconfConfigPtr ctx = cfg_mgr_.getNetconfConfig();
+ ASSERT_TRUE(ctx);
+ ASSERT_TRUE(ctx->getCfgServersMap());
+ EXPECT_EQ(4, ctx->getCfgServersMap()->size());
+
+ ASSERT_NO_THROW_LOG(ctx->getCfgServersMap()->at("dhcp4"));
+ CfgServerPtr server = ctx->getCfgServersMap()->at("dhcp4");
+ ASSERT_TRUE(server);
+ EXPECT_EQ(KEA_DHCP4_SERVER, server->getModel());
+ EXPECT_TRUE(server->getBootUpdate());
+ EXPECT_TRUE(server->getSubscribeChanges());
+ EXPECT_TRUE(server->getValidateChanges());
+ CfgControlSocketPtr socket = server->getCfgControlSocket();
+ ASSERT_TRUE(socket);
+ EXPECT_EQ(CfgControlSocket::Type::STDOUT, socket->getType());
+ EXPECT_EQ("/tmp/socket-v4", socket->getName());
+ EXPECT_EQ("http://127.0.0.1:8000/", socket->getUrl().toText());
+
+ ASSERT_NO_THROW_LOG(ctx->getCfgServersMap()->at("dhcp6"));
+ server = ctx->getCfgServersMap()->at("dhcp6");
+ ASSERT_TRUE(server);
+ EXPECT_EQ(KEA_DHCP6_SERVER, server->getModel());
+ socket = server->getCfgControlSocket();
+ EXPECT_FALSE(server->getBootUpdate());
+ EXPECT_TRUE(server->getSubscribeChanges());
+ EXPECT_TRUE(server->getValidateChanges());
+ ASSERT_TRUE(socket);
+ EXPECT_EQ(CfgControlSocket::Type::STDOUT, socket->getType());
+ EXPECT_EQ("/tmp/socket-v6", socket->getName());
+ EXPECT_EQ("http://127.0.0.1:8000/", socket->getUrl().toText());
+
+ ASSERT_NO_THROW_LOG(ctx->getCfgServersMap()->at("d2"));
+ server = ctx->getCfgServersMap()->at("d2");
+ ASSERT_TRUE(server);
+ EXPECT_EQ(KEA_DHCP_DDNS, server->getModel());
+ EXPECT_FALSE(server->getBootUpdate());
+ EXPECT_FALSE(server->getSubscribeChanges());
+ EXPECT_TRUE(server->getValidateChanges());
+ socket = server->getCfgControlSocket();
+ ASSERT_TRUE(socket);
+ EXPECT_EQ(CfgControlSocket::Type::STDOUT, socket->getType());
+ EXPECT_EQ("/tmp/socket-d2", socket->getName());
+ EXPECT_EQ("http://127.0.0.1:8000/", socket->getUrl().toText());
+
+ ASSERT_NO_THROW_LOG(ctx->getCfgServersMap()->at("ca"));
+ server = ctx->getCfgServersMap()->at("ca");
+ ASSERT_TRUE(server);
+ EXPECT_EQ(KEA_CTRL_AGENT, server->getModel());
+ EXPECT_FALSE(server->getBootUpdate());
+ EXPECT_TRUE(server->getSubscribeChanges());
+ EXPECT_TRUE(server->getValidateChanges());
+ socket = server->getCfgControlSocket();
+ ASSERT_TRUE(socket);
+ EXPECT_EQ(CfgControlSocket::Type::STDOUT, socket->getType());
+ EXPECT_EQ("/tmp/socket-ca", socket->getName());
+ EXPECT_EQ("http://127.0.0.1:8000/", socket->getUrl().toText());
+
+ // Check unparsing.
+ string expected = "{\n"
+ " \"Netconf\": {\n"
+ " \"boot-update\": false,\n"
+ " \"managed-servers\": {\n"
+ " \"dhcp4\": {\n"
+ " \"model\": \"kea-dhcp4-server\",\n"
+ " \"boot-update\": true,\n"
+ " \"subscribe-changes\": true,\n"
+ " \"validate-changes\": true,\n"
+ " \"control-socket\": {\n"
+ " \"socket-type\": \"stdout\",\n"
+ " \"socket-name\": \"/tmp/socket-v4\",\n"
+ " \"socket-url\": \"http://127.0.0.1:8000/\"\n"
+ " }\n"
+ " },\n"
+ " \"dhcp6\": {\n"
+ " \"model\": \"kea-dhcp6-server\",\n"
+ " \"boot-update\": false,\n"
+ " \"subscribe-changes\": true,\n"
+ " \"validate-changes\": true,\n"
+ " \"control-socket\": {\n"
+ " \"socket-type\": \"stdout\",\n"
+ " \"socket-name\": \"/tmp/socket-v6\",\n"
+ " \"socket-url\": \"http://127.0.0.1:8000/\"\n"
+ " }\n"
+ " },\n"
+ " \"d2\": {\n"
+ " \"model\": \"kea-dhcp-ddns\",\n"
+ " \"boot-update\": false,\n"
+ " \"subscribe-changes\": false,\n"
+ " \"validate-changes\": true,\n"
+ " \"control-socket\": {\n"
+ " \"socket-type\": \"stdout\",\n"
+ " \"socket-name\": \"/tmp/socket-d2\",\n"
+ " \"socket-url\": \"http://127.0.0.1:8000/\"\n"
+ " }\n"
+ " },\n"
+ " \"ca\": {\n"
+ " \"model\": \"kea-ctrl-agent\",\n"
+ " \"boot-update\": false,\n"
+ " \"subscribe-changes\": true,\n"
+ " \"validate-changes\": true,\n"
+ " \"control-socket\": {\n"
+ " \"socket-type\": \"stdout\",\n"
+ " \"socket-name\": \"/tmp/socket-ca\",\n"
+ " \"socket-url\": \"http://127.0.0.1:8000/\"\n"
+ " }\n"
+ " }\n"
+ " },\n"
+ " \"hooks-libraries\": [ ]\n"
+ " }\n"
+ "}";
+ isc::test::runToElementTest<NetconfConfig>(expected, *ctx);
+}
+
+// Tests the handling of invalid socket URL.
+TEST_F(NetconfParserTest, configParseInvalidSocketUrl) {
+ configParse(NETCONF_CONFIGS[11], 1);
+ int rcode = 0;
+ string expected =
+ "\"invalid control socket url: url bad lacks http or https scheme "
+ "'bad' (<string>:5:31)\"";
+ EXPECT_EQ(expected, parseAnswer(rcode, answer_)->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 NETCONF_CONFIGS[4] as is, but need to run it through path replacer.
+TEST_F(NetconfParserTest, configParseHooks) {
+ // Create the configuration with proper lib path.
+ string cfg = pathReplacer(NETCONF_CONFIGS[4], BASIC_CALLOUT_LIBRARY);
+ // The configuration should be successful.
+ configParse(cfg.c_str(), 0);
+
+ // The context now should have the library specified.
+ NetconfConfigPtr ctx = cfg_mgr_.getNetconfConfig();
+ const HookLibsCollection libs = ctx->getHooksConfig().get();
+ ASSERT_EQ(1, libs.size());
+ EXPECT_EQ(string(BASIC_CALLOUT_LIBRARY), libs[0].first);
+ ASSERT_TRUE(libs[0].second);
+ EXPECT_EQ("{ \"param1\": \"foo\" }", libs[0].second->str());
+}
+
+// This test checks comments.
+TEST_F(NetconfParserTest, comments) {
+ configParse(NETCONF_CONFIGS[7], 0);
+ NetconfConfigPtr netconf_ctx = cfg_mgr_.getNetconfConfig();
+ ASSERT_TRUE(netconf_ctx);
+
+ // Check global user context.
+ ConstElementPtr ctx = netconf_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 server.
+ ASSERT_TRUE(netconf_ctx->getCfgServersMap());
+ ASSERT_NO_THROW_LOG(netconf_ctx->getCfgServersMap()->at("dhcp4"));
+ CfgServerPtr server = netconf_ctx->getCfgServersMap()->at("dhcp4");
+ ASSERT_TRUE(server);
+
+ // Check DHCP4 server user context.
+ ConstElementPtr ctx4 = server->getContext();
+ ASSERT_TRUE(ctx4);
+ ASSERT_EQ(1, ctx4->size());
+ ASSERT_TRUE(ctx4->get("comment"));
+ EXPECT_EQ("\"dhcp4 server\"", ctx4->get("comment")->str());
+
+ // There is a DHCP6 server.
+ ASSERT_NO_THROW_LOG(netconf_ctx->getCfgServersMap()->at("dhcp6"));
+ server = netconf_ctx->getCfgServersMap()->at("dhcp6");
+ ASSERT_TRUE(server);
+
+ // There is a DHCP6 control socket.
+ CfgControlSocketPtr socket = server->getCfgControlSocket();
+ ASSERT_TRUE(socket);
+
+ // Check DHCP6 control socket user context.
+ ConstElementPtr ctx6 = socket->getContext();
+ ASSERT_TRUE(ctx6);
+ ASSERT_EQ(1, ctx6->size());
+ ASSERT_TRUE(ctx6->get("version"));
+ EXPECT_EQ("1", ctx6->get("version")->str());
+}
+
+} // namespace
diff --git a/src/bin/netconf/tests/netconf_controller_unittests.cc b/src/bin/netconf/tests/netconf_controller_unittests.cc
new file mode 100644
index 0000000..dd4cb85
--- /dev/null
+++ b/src/bin/netconf/tests/netconf_controller_unittests.cc
@@ -0,0 +1,194 @@
+// Copyright (C) 2018-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <asiolink/testutils/timed_signal.h>
+#include <netconf/netconf_controller.h>
+#include <netconf/netconf_process.h>
+#include <cc/data.h>
+#include <process/testutils/d_test_stubs.h>
+#include <testutils/gtest_utils.h>
+
+#include <boost/pointer_cast.hpp>
+
+#include <sstream>
+
+using namespace isc::asiolink::test;
+using namespace isc::netconf;
+using namespace isc::data;
+using namespace isc::http;
+using namespace isc::process;
+using namespace boost::posix_time;
+using namespace std;
+
+namespace {
+
+/// @brief Valid Netconf Config used in tests.
+const char* valid_netconf_config =
+ "{"
+ " \"boot-update\": false,"
+ " \"subscribe-changes\": false,"
+ " \"managed-servers\": {"
+ " \"dhcp4\": {"
+ " \"control-socket\": {"
+ " \"socket-type\": \"unix\","
+ " \"socket-name\": \"/first/dhcp4/socket\""
+ " }"
+ " },"
+ " \"dhcp6\": {"
+ " \"control-socket\": {"
+ " \"socket-type\": \"unix\","
+ " \"socket-name\": \"/first/dhcp6/socket\""
+ " }"
+ " }"
+ " }"
+ "}";
+
+/// @brief test fixture class for testing NetconfController class.
+///
+/// This class derives from DControllerTest and wraps NetconfController. Much
+/// of the underlying functionality is in the DControllerBase class which
+/// has extensive set of unit tests that are independent from Netconf.
+class NetconfControllerTest : public DControllerTest {
+public:
+
+ /// @brief Constructor.
+ NetconfControllerTest()
+ : DControllerTest(NetconfController::instance) {
+ }
+
+ /// @brief Returns pointer to NetconfProcess instance.
+ NetconfProcessPtr getNetconfProcess() {
+ return (boost::dynamic_pointer_cast<NetconfProcess>(getProcess()));
+ }
+
+ /// @brief Returns pointer to NetconfCfgMgr instance for a process.
+ NetconfCfgMgrPtr getNetconfCfgMgr() {
+ NetconfCfgMgrPtr p;
+ if (getNetconfProcess()) {
+ p = getNetconfProcess()->getNetconfCfgMgr();
+ }
+ return (p);
+ }
+
+ /// @brief Returns a pointer to the configuration context.
+ NetconfConfigPtr getNetconfConfig() {
+ NetconfConfigPtr p;
+ if (getNetconfCfgMgr()) {
+ p = getNetconfCfgMgr()->getNetconfConfig();
+ }
+ return (p);
+ }
+};
+
+// Basic Controller instantiation testing.
+// Verifies that the controller singleton gets created and that the
+// basic derivation from the base class is intact.
+TEST_F(NetconfControllerTest, 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_LOG(boost::dynamic_pointer_cast<NetconfController>(controller));
+
+ // Verify that controller's app name is correct.
+ EXPECT_TRUE(checkAppName(NetconfController::netconf_app_name_));
+
+ // Verify that controller's bin name is correct.
+ EXPECT_TRUE(checkBinName(NetconfController::netconf_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(NetconfControllerTest, 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(NetconfControllerTest, initProcessTesting) {
+ ASSERT_NO_THROW_LOG(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(NetconfControllerTest, launchNormalShutdown) {
+ // Write valid_netconf_config and then run launch() for 200 ms.
+ time_duration elapsed_time;
+ runWithConfig(valid_netconf_config, 200, elapsed_time);
+
+ // Give a generous margin to accommodate slower test environs.
+ EXPECT_TRUE(elapsed_time.total_milliseconds() >= 100 &&
+ elapsed_time.total_milliseconds() <= 500);
+}
+
+// Tests that the SIGINT triggers a normal shutdown.
+TEST_F(NetconfControllerTest, sigintShutdown) {
+ // Setup to raise SIGINT in 1 ms.
+ TimedSignal sighup(*getIOService(), SIGINT, 1);
+
+ // Write valid_netconf_config and then run launch() for a maximum
+ // of 500 ms.
+ time_duration elapsed_time;
+ runWithConfig(valid_netconf_config, 500, 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(NetconfControllerTest, sigtermShutdown) {
+ // Setup to raise SIGTERM in 1 ms.
+ TimedSignal sighup(*getIOService(), SIGTERM, 1);
+
+ // Write valid_netconf_config and then run launch() for a maximum
+ // of 500 ms.
+ time_duration elapsed_time;
+ runWithConfig(valid_netconf_config, 500, 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);
+}
+
+}
diff --git a/src/bin/netconf/tests/netconf_process_unittests.cc b/src/bin/netconf/tests/netconf_process_unittests.cc
new file mode 100644
index 0000000..08d2d3c
--- /dev/null
+++ b/src/bin/netconf/tests/netconf_process_unittests.cc
@@ -0,0 +1,92 @@
+// Copyright (C) 2018-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <netconf/netconf_cfg_mgr.h>
+#include <netconf/netconf_process.h>
+#include <asiolink/interval_timer.h>
+#include <asiolink/io_service.h>
+#include <process/testutils/d_test_stubs.h>
+#include <testutils/gtest_utils.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::netconf;
+using namespace isc::asiolink;
+using namespace isc::process;
+
+namespace {
+
+/// @brief NetconfProcess test fixture class.
+class NetconfProcessTest : public NetconfProcess, public ::testing::Test {
+public:
+ /// @brief Constructor
+ NetconfProcessTest() :
+ NetconfProcess("netconf-test",
+ IOServicePtr(new isc::asiolink::IOService())) {
+ NetconfConfigPtr ctx = getNetconfCfgMgr()->getNetconfConfig();
+ }
+
+ /// @brief Destructor
+ virtual ~NetconfProcessTest() {
+ }
+
+ /// @brief Callback that will invoke shutdown method.
+ void genShutdownCallback() {
+ shutdown(isc::data::ConstElementPtr());
+ }
+};
+
+// Test construction of the NetconfProcess object.
+TEST(NetconfProcess, construction) {
+ // Verify that the constructor will fail if given an empty
+ // io service.
+ IOServicePtr lcl_io_service;
+ EXPECT_THROW(NetconfProcess("TestProcess", lcl_io_service),
+ DProcessBaseError);
+
+ // Verify that the constructor succeeds with a valid io_service
+ lcl_io_service.reset(new IOService());
+ ASSERT_NO_THROW_LOG(NetconfProcess("TestProcess", lcl_io_service));
+
+ // Verify tha the configuration is accessible after construction.
+ NetconfProcess netconf_process("TestProcess", lcl_io_service);
+ NetconfCfgMgrPtr cfg_mgr = netconf_process.getNetconfCfgMgr();
+ ASSERT_TRUE(cfg_mgr);
+}
+
+// Verifies that en external call to shutdown causes the run method to
+// exit gracefully.
+TEST_F(NetconfProcessTest, 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(&NetconfProcessTest::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/netconf/tests/netconf_unittests.cc b/src/bin/netconf/tests/netconf_unittests.cc
new file mode 100644
index 0000000..bd9be00
--- /dev/null
+++ b/src/bin/netconf/tests/netconf_unittests.cc
@@ -0,0 +1,1212 @@
+// Copyright (C) 2018-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <netconf/netconf.h>
+#include <netconf/netconf_process.h>
+#include <netconf/parser_context.h>
+#include <netconf/simple_parser.h>
+#include <netconf/unix_control_socket.h>
+#include <asiolink/asio_wrapper.h>
+#include <asiolink/interval_timer.h>
+#include <asiolink/io_service.h>
+#include <cc/command_interpreter.h>
+#include <yang/yang_models.h>
+#include <yang/yang_revisions.h>
+#include <yang/tests/sysrepo_setup.h>
+#include <yang/translator_config.h>
+#include <yang/testutils/translator_test.h>
+#include <testutils/log_utils.h>
+#include <testutils/threaded_test.h>
+#include <testutils/sandbox.h>
+
+#include <gtest/gtest.h>
+
+#include <sstream>
+#include <thread>
+
+using namespace std;
+using namespace isc;
+using namespace isc::netconf;
+using namespace isc::asiolink;
+using namespace isc::config;
+using namespace isc::data;
+using namespace isc::http;
+using namespace isc::test;
+using namespace isc::yang;
+using namespace isc::yang::test;
+using namespace sysrepo;
+
+using isc::yang::test::SysrepoSetup;
+
+namespace {
+
+/// @brief Test unix socket file name.
+const string TEST_SOCKET = "test-socket";
+
+/// @brief Type definition for the pointer to Thread objects.
+typedef boost::shared_ptr<thread> ThreadPtr;
+
+/// @brief Test version of the NetconfAgent class.
+class NakedNetconfAgent : public NetconfAgent {
+public:
+ /// @brief Constructor.
+ NakedNetconfAgent() {
+ }
+
+ /// @brief Destructor.
+ virtual ~NakedNetconfAgent() {
+ }
+
+ /// Export protected methods and fields.
+ using NetconfAgent::keaConfig;
+ using NetconfAgent::initSysrepo;
+ using NetconfAgent::checkModule;
+ using NetconfAgent::checkModules;
+ using NetconfAgent::yangConfig;
+ using NetconfAgent::subscribeConfig;
+ using NetconfAgent::conn_;
+ using NetconfAgent::startup_sess_;
+ using NetconfAgent::running_sess_;
+ using NetconfAgent::modules_;
+ using NetconfAgent::subscriptions_;
+};
+
+/// @brief Type definition for the pointer to NakedNetconfAgent objects.
+typedef boost::shared_ptr<NakedNetconfAgent> NakedNetconfAgentPtr;
+
+/// @brief Clear YANG configuration.
+///
+/// @param agent The naked netconf agent (fr its startup datastore session).
+void clearYang(NakedNetconfAgentPtr agent) {
+ if (agent && (agent->startup_sess_)) {
+ string xpath = "/kea-dhcp4-server:config";
+ EXPECT_NO_THROW(agent->startup_sess_->delete_item(xpath.c_str()));
+ EXPECT_NO_THROW(agent->startup_sess_->apply_changes());
+ }
+}
+
+
+// Empirically the requested subnets have sometimes returned in decreasing
+// order of subnet ID. To avoid flaky test failures, sort them before
+// comparing.
+void sortSubnets(ConstElementPtr const& map) {
+ boost::dynamic_pointer_cast<ListElement>(
+ boost::const_pointer_cast<Element>(
+ map->get("arguments")->get("Dhcp4")->get("subnet4")))
+ ->sort("id");
+}
+
+/// @brief Test fixture class for netconf agent.
+class NetconfAgentTest : public ThreadedTest {
+public:
+ isc::test::Sandbox sandbox;
+
+ void SetUp() override {
+ SysrepoSetup::cleanSharedMemory();
+ removeUnixSocketFile();
+ io_service_.reset(new IOService());
+ agent_.reset(new NakedNetconfAgent());
+ }
+
+ void TearDown() override {
+ if (thread_) {
+ thread_->join();
+ thread_.reset();
+ }
+ // io_service must be stopped after the thread returns,
+ // otherwise the thread may never return if it is
+ // waiting for the completion of some asynchronous tasks.
+ io_service_->stop();
+ io_service_.reset();
+ if (agent_) {
+ clearYang(agent_);
+ agent_->clear();
+ }
+ agent_.reset();
+ requests_.clear();
+ responses_.clear();
+ removeUnixSocketFile();
+ SysrepoSetup::cleanSharedMemory();
+ }
+
+ /// @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.
+ 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 Create configuration of the control socket.
+ ///
+ /// @return a pointer to a control socket configuration.
+ CfgControlSocketPtr createCfgControlSocket() {
+ CfgControlSocketPtr cfg;
+ cfg.reset(new CfgControlSocket(CfgControlSocket::Type::UNIX,
+ unixSocketFilePath(),
+ Url("http://127.0.0.1:8000/")));
+ return (cfg);
+ }
+
+ /// @brief Fake server (returns OK answer).
+ void fakeServer();
+
+ /// @brief IOService object.
+ IOServicePtr io_service_;
+
+ /// @brief Test netconf agent.
+ NakedNetconfAgentPtr agent_;
+
+ /// @brief Request list.
+ vector<string> requests_;
+
+ /// @brief Response list.
+ vector<string> responses_;
+};
+
+/// @brief Special test fixture for logging tests.
+class NetconfAgentLogTest : public dhcp::test::LogContentTest {
+public:
+ /// @brief Constructor.
+ NetconfAgentLogTest()
+ : finished_(false),
+ io_service_(new IOService()),
+ thread_(),
+ agent_(new NakedNetconfAgent) {
+ }
+
+ /// @brief Destructor.
+ virtual ~NetconfAgentLogTest() {
+ if (agent_) {
+ clearYang(agent_);
+ agent_->clear();
+ }
+ agent_.reset();
+ // io_service must be stopped to make the thread to return.
+ io_service_->stop();
+ if (thread_) {
+ thread_->join();
+ thread_.reset();
+ }
+ io_service_.reset();
+ }
+
+ /// @brief Default change callback (print changes and return OK).
+ sr_error_t callback(sysrepo::S_Session sess,
+ const char* module_name,
+ const char* /* xpath */,
+ sr_event_t /* event */,
+ uint32_t /* request_id */) {
+ NetconfAgent::logChanges(sess, module_name);
+ finished_ = true;
+ return (SR_ERR_OK);
+ }
+
+ /// @brief logChanges is called in another thread so we have to wait for it.
+ ///
+ /// @todo The better way to get notified and get rid of the sleep is with a
+ /// conditional variable.
+ void waitForCallback() {
+ while (!finished_) {
+ usleep(1000);
+ }
+ }
+
+ /// @brief To know when the callback was called.
+ std::atomic<bool> finished_;
+
+ /// @brief IOService object.
+ IOServicePtr io_service_;
+
+ /// @brief Pointer to server thread.
+ ThreadPtr thread_;
+
+ /// @brief Test netconf agent.
+ NakedNetconfAgentPtr agent_;
+};
+
+/// @brief Fake server (returns OK answer).
+void
+NetconfAgentTest::fakeServer() {
+ // Acceptor.
+ boost::asio::local::stream_protocol::acceptor
+ acceptor(io_service_->get_io_service());
+ EXPECT_NO_THROW(acceptor.open());
+ boost::asio::local::stream_protocol::endpoint
+ endpoint(unixSocketFilePath());
+ boost::asio::socket_base::reuse_address option(true);
+ acceptor.set_option(option);
+ EXPECT_NO_THROW(acceptor.bind(endpoint));
+ EXPECT_NO_THROW(acceptor.listen());
+ boost::asio::local::stream_protocol::socket
+ socket(io_service_->get_io_service());
+
+ // Ready.
+ signalReady();
+
+ // Timeout.
+ bool timeout = false;
+ IntervalTimer timer(*io_service_);
+ timer.setup([&timeout]() {
+ timeout = true;
+ FAIL() << "timeout";
+ }, 1500, IntervalTimer::ONE_SHOT);
+
+ // Accept.
+ boost::system::error_code ec;
+ bool accepted = false;
+ acceptor.async_accept(socket,
+ [&ec, &accepted]
+ (const boost::system::error_code& error) {
+ ec = error;
+ accepted = true;
+ });
+ while (!accepted && !timeout) {
+ io_service_->run_one();
+ }
+ ASSERT_FALSE(ec);
+
+ // Receive command.
+ string rbuf(1024, ' ');
+ size_t received = 0;
+ socket.async_receive(boost::asio::buffer(&rbuf[0], rbuf.size()),
+ [&ec, &received]
+ (const boost::system::error_code& error, size_t cnt) {
+ ec = error;
+ received = cnt;
+ });
+ while (!received && !timeout) {
+ io_service_->run_one();
+ }
+ ASSERT_FALSE(ec);
+ rbuf.resize(received);
+ requests_.push_back(rbuf);
+ ConstElementPtr json;
+ EXPECT_NO_THROW(json = Element::fromJSON(rbuf));
+ EXPECT_TRUE(json);
+ string command;
+ ConstElementPtr config;
+ if (json) {
+ ConstElementPtr arg;
+ EXPECT_NO_THROW(command = parseCommand(arg, json));
+ if (command == "config-get") {
+ config = Element::fromJSON("{ \"comment\": \"empty\" }");
+ }
+ }
+
+ // Send answer.
+ string sbuf = createAnswer(CONTROL_RESULT_SUCCESS, config)->str();
+ responses_.push_back(sbuf);
+ size_t sent = 0;
+ socket.async_send(boost::asio::buffer(&sbuf[0], sbuf.size()),
+ [&ec, &sent]
+ (const boost::system::error_code& error, size_t cnt) {
+ ec = error;
+ sent = cnt;
+ });
+ while (!sent && !timeout) {
+ io_service_->run_one();
+ }
+ ASSERT_FALSE(ec);
+
+ // Stop timer.
+ timer.cancel();
+
+ // Close socket and acceptor.
+ if (socket.is_open()) {
+ EXPECT_NO_THROW(socket.close());
+ }
+ EXPECT_NO_THROW(acceptor.close());
+ // Removed the socket file so it can be called again immediately.
+ removeUnixSocketFile();
+
+ /// Finished.
+ EXPECT_FALSE(timeout);
+ EXPECT_TRUE(accepted);
+ EXPECT_TRUE(received);
+ EXPECT_TRUE(sent);
+ EXPECT_EQ(sent, sbuf.size());
+
+ // signalStopped can't be called here because of the 2 runs for update.
+}
+
+// Verifies that the initSysrepo method opens sysrepo connection and sessions.
+TEST_F(NetconfAgentTest, initSysrepo) {
+ EXPECT_NO_THROW(agent_->initSysrepo());
+ EXPECT_TRUE(agent_->conn_);
+ EXPECT_TRUE(agent_->startup_sess_);
+ EXPECT_TRUE(agent_->running_sess_);
+ EXPECT_EQ(0, agent_->subscriptions_.size());
+ EXPECT_LE(16, agent_->modules_.size());
+}
+
+// Verifies that the checkModule method emits expected errors.
+TEST_F(NetconfAgentLogTest, checkModule) {
+ // keatest-module should not be in YANG_REVISIONS.
+ EXPECT_EQ(0, YANG_REVISIONS.count("keatest-module"));
+ // But kea-dhcp[46]-server must be in.
+ ASSERT_EQ(1, YANG_REVISIONS.count("kea-dhcp4-server"));
+ ASSERT_EQ(1, YANG_REVISIONS.count("kea-dhcp6-server"));
+
+ // kea-dhcp[46]-server should be available.
+ EXPECT_NO_THROW(agent_->initSysrepo());
+ EXPECT_EQ(1, agent_->modules_.count("kea-dhcp4-server"));
+ EXPECT_EQ(1, agent_->modules_.count("kea-dhcp6-server"));
+ EXPECT_TRUE(agent_->checkModule("kea-dhcp4-server"));
+ EXPECT_TRUE(agent_->checkModule("kea-dhcp6-server"));
+
+ // Unknown module should emit a missing error.
+ EXPECT_EQ(0, agent_->modules_.count("does-not-exist"));
+ EXPECT_FALSE(agent_->checkModule("does-not-exist"));
+ addString("NETCONF_MODULE_MISSING_ERR Missing essential module "
+ "does-not-exist in sysrepo");
+
+ // Patch the found revision to get a revision error.
+ const string& module = "kea-dhcp4-server";
+ auto it4 = agent_->modules_.find(module);
+ if (it4 != agent_->modules_.end()) {
+ agent_->modules_.erase(it4);
+ }
+ // The module was written far after 20180714...
+ const string& bad_revision = "2018-07-14";
+ agent_->modules_.insert(make_pair(module, bad_revision));
+ EXPECT_FALSE(agent_->checkModule(module));
+ ostringstream msg;
+ msg << "NETCONF_MODULE_REVISION_ERR Essential module " << module
+ << " does NOT have the right revision: expected "
+ << YANG_REVISIONS.at(module) << ", got " << bad_revision;
+ addString(msg.str());
+
+ EXPECT_TRUE(checkFile());
+}
+
+// Verifies that the checkModules method emits expected warnings.
+TEST_F(NetconfAgentLogTest, checkModules) {
+ // kea-dhcp[46]-server must be in YANG_REVISIONS.
+ ASSERT_EQ(1, YANG_REVISIONS.count("kea-dhcp4-server"));
+ ASSERT_EQ(1, YANG_REVISIONS.count("kea-dhcp6-server"));
+
+ // kea-dhcp[46]-server should be available.
+ EXPECT_NO_THROW(agent_->initSysrepo());
+ EXPECT_EQ(1, agent_->modules_.count("kea-dhcp4-server"));
+ EXPECT_EQ(1, agent_->modules_.count("kea-dhcp6-server"));
+
+ // Run checkModules but it will be indirectly checked as
+ // emitting nothing.
+ ASSERT_NO_THROW_LOG(agent_->checkModules());
+
+ // Remove kea-dhcp6-server.
+ const string& module = "kea-dhcp6-server";
+ auto it6 = agent_->modules_.find(module);
+ if (it6 != agent_->modules_.end()) {
+ agent_->modules_.erase(it6);
+ }
+ ASSERT_NO_THROW_LOG(agent_->checkModules());
+ ostringstream mmsg;
+ mmsg << "NETCONF_MODULE_MISSING_WARN Missing module " << module
+ << " in sysrepo";
+ addString(mmsg.str());
+
+ // Add it back with a bad revision.
+ const string& bad_revision = "2018-07-14";
+ agent_->modules_.insert(make_pair(module, bad_revision));
+ ASSERT_NO_THROW_LOG(agent_->checkModules());
+ ostringstream rmsg;
+ rmsg << "NETCONF_MODULE_REVISION_WARN Module " << module
+ << " does NOT have the right revision: expected "
+ << YANG_REVISIONS.at(module) << ", got " << bad_revision;
+ addString(rmsg.str());
+
+ EXPECT_TRUE(checkFile());
+}
+
+// Verifies that the logChanges method handles correctly changes.
+TEST_F(NetconfAgentLogTest, logChanges) {
+ // Initial YANG configuration.
+ const YRTree tree0 = YangRepr::buildTreeFromVector({
+ { "/kea-dhcp4-server:config", "", SR_CONTAINER_T, false },
+ { "/kea-dhcp4-server:config/subnet4[id='1']", "", SR_LIST_T, false },
+ { "/kea-dhcp4-server:config/subnet4[id='1']/id",
+ "1", SR_UINT32_T, false },
+ { "/kea-dhcp4-server:config/subnet4[id='1']/subnet",
+ "10.0.0.0/24", SR_STRING_T, true },
+ { "/kea-dhcp4-server:config/subnet4[id='2']", "", SR_LIST_T, false },
+ { "/kea-dhcp4-server:config/subnet4[id='2']/id",
+ "2", SR_UINT32_T, false },
+ { "/kea-dhcp4-server:config/subnet4[id='2']/subnet",
+ "10.0.2.0/24", SR_STRING_T, true }
+ });
+ // Load initial YANG configuration.
+ ASSERT_NO_THROW_LOG(agent_->initSysrepo());
+ YangRepr repr(KEA_DHCP4_SERVER);
+ ASSERT_NO_THROW_LOG(repr.set(tree0, agent_->startup_sess_));
+ EXPECT_NO_THROW(agent_->startup_sess_->apply_changes());
+
+ // Subscribe configuration changes.
+ S_Subscribe subs(new Subscribe(agent_->running_sess_));
+ auto cb = [&](sysrepo::S_Session sess, const char* module_name,
+ const char* xpath, sr_event_t event, uint32_t request_id) {
+ return callback(sess, module_name, xpath, event, request_id);
+ };
+ EXPECT_NO_THROW(subs->module_change_subscribe(KEA_DHCP4_SERVER.c_str(), cb,
+ nullptr, 0,
+ SR_SUBSCR_DONE_ONLY));
+ thread_.reset(new thread([this]() { io_service_->run(); }));
+
+ // Change configuration (subnet #1 moved from 10.0.0.0/24 to 10.0.1/0/24).
+ const YRTree tree1 = YangRepr::buildTreeFromVector({
+ { "/kea-dhcp4-server:config", "", SR_CONTAINER_T, false },
+ { "/kea-dhcp4-server:config/subnet4[id='1']", "", SR_LIST_T, false },
+ { "/kea-dhcp4-server:config/subnet4[id='1']/id",
+ "1", SR_UINT32_T, false },
+ { "/kea-dhcp4-server:config/subnet4[id='1']/subnet",
+ "10.0.1.0/24", SR_STRING_T, true }, // The change is here!
+ { "/kea-dhcp4-server:config/subnet4[id='2']", "", SR_LIST_T, false },
+ { "/kea-dhcp4-server:config/subnet4[id='2']/id",
+ "2", SR_UINT32_T, false },
+ { "/kea-dhcp4-server:config/subnet4[id='2']/subnet",
+ "10.0.2.0/24", SR_STRING_T, true }
+ });
+ EXPECT_NO_THROW(repr.set(tree1, agent_->running_sess_));
+ EXPECT_NO_THROW(agent_->running_sess_->apply_changes());
+
+ // Check that the debug output was correct.
+ addString(
+ "NETCONF_CONFIG_CHANGED_DETAIL YANG configuration changed: created: "
+ "/kea-dhcp4-server:config/subnet4[id='2'] (list instance)");
+ addString("NETCONF_CONFIG_CHANGED_DETAIL YANG configuration changed: "
+ "created: /kea-dhcp4-server:config/subnet4[id='2']/id = 2");
+ addString(
+ "NETCONF_CONFIG_CHANGED_DETAIL YANG configuration changed: created: "
+ "/kea-dhcp4-server:config/subnet4[id='2']/subnet = 10.0.2.0/24");
+ addString(
+ "NETCONF_CONFIG_CHANGED_DETAIL YANG configuration changed: created: "
+ "/kea-dhcp4-server:config/subnet4[id='2']/relay (container)");
+
+ waitForCallback();
+
+ EXPECT_TRUE(checkFile());
+}
+
+// Verifies that the logChanges method handles correctly changes.
+// Instead of the simple modified of the previous test, now there will
+// deleted, created and moved.
+TEST_F(NetconfAgentLogTest, logChanges2) {
+ // Initial YANG configuration.
+ const YRTree tree0 = YangRepr::buildTreeFromVector({
+ { "/kea-dhcp4-server:config", "", SR_CONTAINER_T, false },
+ { "/kea-dhcp4-server:config/subnet4[id='1']", "", SR_LIST_T, false },
+ { "/kea-dhcp4-server:config/subnet4[id='1']/id",
+ "1", SR_UINT32_T, false },
+ { "/kea-dhcp4-server:config/subnet4[id='1']/subnet",
+ "10.0.0.0/24", SR_STRING_T, true },
+ { "/kea-dhcp4-server:config/subnet4[id='2']", "", SR_LIST_T, false },
+ { "/kea-dhcp4-server:config/subnet4[id='2']/id",
+ "2", SR_UINT32_T, false },
+ { "/kea-dhcp4-server:config/subnet4[id='2']/subnet",
+ "10.0.2.0/24", SR_STRING_T, true }
+ });
+ // Load initial YANG configuration.
+ ASSERT_NO_THROW_LOG(agent_->initSysrepo());
+ YangRepr repr(KEA_DHCP4_SERVER);
+ ASSERT_NO_THROW_LOG(repr.set(tree0, agent_->startup_sess_));
+ EXPECT_NO_THROW(agent_->startup_sess_->apply_changes());
+
+ // Subscribe configuration changes.
+ S_Subscribe subs(new Subscribe(agent_->running_sess_));
+ auto cb = [&](sysrepo::S_Session sess, const char* module_name,
+ const char* xpath, sr_event_t event, uint32_t request_id) {
+ return callback(sess, module_name, xpath, event, request_id);
+ };
+ EXPECT_NO_THROW(subs->module_change_subscribe(KEA_DHCP4_SERVER.c_str(), cb,
+ nullptr, 0,
+ SR_SUBSCR_DONE_ONLY));
+ thread_.reset(new thread([this]() { io_service_->run(); }));
+
+ // Change configuration (subnet #1 moved to #10).
+ string xpath = "/kea-dhcp4-server:config/subnet4[id='1']";
+ EXPECT_NO_THROW(agent_->running_sess_->delete_item(xpath.c_str()));
+ const YRTree tree1 = YangRepr::buildTreeFromVector({
+ { "/kea-dhcp4-server:config", "", SR_CONTAINER_T, false },
+ { "/kea-dhcp4-server:config/subnet4[id='10']", "", SR_LIST_T, false },
+ { "/kea-dhcp4-server:config/subnet4[id='10']/id",
+ "10", SR_UINT32_T, false },
+ { "/kea-dhcp4-server:config/subnet4[id='10']/subnet",
+ "10.0.0.0/24", SR_STRING_T, true }, // The change is here!
+ { "/kea-dhcp4-server:config/subnet4[id='2']", "", SR_LIST_T, false },
+ { "/kea-dhcp4-server:config/subnet4[id='2']/id",
+ "2", SR_UINT32_T, false },
+ { "/kea-dhcp4-server:config/subnet4[id='2']/subnet",
+ "10.0.2.0/24", SR_STRING_T, true }
+ });
+ EXPECT_NO_THROW(repr.set(tree1, agent_->running_sess_));
+ EXPECT_NO_THROW(agent_->running_sess_->apply_changes());
+
+ // Check that the debug output was correct.
+ addString(
+ "NETCONF_CONFIG_CHANGED_DETAIL YANG configuration changed: deleted: "
+ "/kea-dhcp4-server:config/subnet4[id='1'] (list instance)");
+ addString("NETCONF_CONFIG_CHANGED_DETAIL YANG configuration changed: "
+ "deleted: /kea-dhcp4-server:config/subnet4[id='1']/id = 1");
+ addString(
+ "NETCONF_CONFIG_CHANGED_DETAIL YANG configuration changed: deleted: "
+ "/kea-dhcp4-server:config/subnet4[id='1']/subnet = 10.0.1.0/24");
+ addString(
+ "NETCONF_CONFIG_CHANGED_DETAIL YANG configuration changed: deleted: "
+ "/kea-dhcp4-server:config/subnet4[id='1']/relay (container)");
+
+ waitForCallback();
+
+ EXPECT_TRUE(checkFile());
+}
+
+// Verifies that the keaConfig method works as expected.
+TEST_F(NetconfAgentTest, keaConfig) {
+ // Netconf configuration.
+ string config_prefix = "{\n"
+ " \"Netconf\": {\n"
+ " \"managed-servers\": {\n"
+ " \"dhcp4\": {\n"
+ " \"control-socket\": {\n"
+ " \"socket-type\": \"unix\",\n"
+ " \"socket-name\": \"";
+ string config_trailer = "\"\n"
+ " }\n"
+ " }\n"
+ " }\n"
+ " }\n"
+ "}";
+ string config = config_prefix + unixSocketFilePath() + config_trailer;
+ NetconfConfigPtr ctx(new NetconfConfig());
+ ElementPtr json;
+ ParserContext parser_context;
+ EXPECT_NO_THROW(json =
+ parser_context.parseString(config, ParserContext::PARSER_NETCONF));
+ ASSERT_TRUE(json);
+ ASSERT_EQ(Element::map, json->getType());
+ ConstElementPtr netconf_json = json->get("Netconf");
+ ASSERT_TRUE(netconf_json);
+ json = boost::const_pointer_cast<Element>(netconf_json);
+ ASSERT_TRUE(json);
+ NetconfSimpleParser::setAllDefaults(json);
+ NetconfSimpleParser::deriveParameters(json);
+ NetconfSimpleParser parser;
+ EXPECT_NO_THROW(parser.parse(ctx, json, false));
+
+ // Get service pair.
+ CfgServersMapPtr servers_map = ctx->getCfgServersMap();
+ ASSERT_TRUE(servers_map);
+ ASSERT_EQ(1, servers_map->size());
+ CfgServersMapPair service_pair = *servers_map->begin();
+
+ // Launch server.
+ thread_.reset(new thread([this]() { fakeServer(); signalStopped(); }));
+
+ // Wait until the server is listening.
+ waitReady();
+
+ // Try keaConfig.
+ EXPECT_NO_THROW(agent_->keaConfig(service_pair));
+
+ // Wait server to be stopped.
+ waitStopped();
+
+ // Check request.
+ ASSERT_EQ(1, requests_.size());
+ const string& request_str = requests_[0];
+ ConstElementPtr request;
+ ASSERT_NO_THROW_LOG(request = Element::fromJSON(request_str));
+ string expected_str = "{\n"
+ "\"command\": \"config-get\"\n"
+ "}";
+ ConstElementPtr expected;
+ ASSERT_NO_THROW_LOG(expected = Element::fromJSON(expected_str));
+ EXPECT_TRUE(expected->equals(*request));
+ // Alternative showing more for debugging...
+ // EXPECT_EQ(prettyPrint(expected), prettyPrint(request));
+
+ // Check response.
+ ASSERT_EQ(1, responses_.size());
+ const string& response_str = responses_[0];
+ ConstElementPtr response;
+ ASSERT_NO_THROW_LOG(response = Element::fromJSON(response_str));
+ expected_str = "{\n"
+ "\"result\": 0,\n"
+ "\"arguments\": {\n"
+ " \"comment\": \"empty\"\n"
+ " }\n"
+ "}";
+ ASSERT_NO_THROW_LOG(expected = Element::fromJSON(expected_str));
+ EXPECT_TRUE(expected->equals(*response));
+}
+
+// Verifies that the yangConfig method works as expected: apply YANG config
+// to the server.
+TEST_F(NetconfAgentTest, yangConfig) {
+ // YANG configuration.
+ const YRTree tree = YangRepr::buildTreeFromVector({
+ { "/kea-dhcp4-server:config", "", SR_CONTAINER_T, false },
+ { "/kea-dhcp4-server:config/subnet4[id='1']", "", SR_LIST_T, false },
+ { "/kea-dhcp4-server:config/subnet4[id='1']/id",
+ "1", SR_UINT32_T, false },
+ { "/kea-dhcp4-server:config/subnet4[id='1']/subnet",
+ "10.0.0.0/24", SR_STRING_T, true },
+ { "/kea-dhcp4-server:config/subnet4[id='2']", "", SR_LIST_T, false },
+ { "/kea-dhcp4-server:config/subnet4[id='2']/id",
+ "2", SR_UINT32_T, false },
+ { "/kea-dhcp4-server:config/subnet4[id='2']/subnet",
+ "10.0.2.0/24", SR_STRING_T, true }
+ });
+ // Load YANG configuration.
+ ASSERT_NO_THROW_LOG(agent_->initSysrepo());
+ YangRepr repr(KEA_DHCP4_SERVER);
+ ASSERT_NO_THROW_LOG(repr.set(tree, agent_->startup_sess_));
+ EXPECT_NO_THROW(agent_->startup_sess_->apply_changes());
+
+ // Netconf configuration.
+ string config_prefix = "{\n"
+ " \"Netconf\": {\n"
+ " \"managed-servers\": {\n"
+ " \"dhcp4\": {\n"
+ " \"control-socket\": {\n"
+ " \"socket-type\": \"unix\",\n"
+ " \"socket-name\": \"";
+ string config_trailer = "\"\n"
+ " }\n"
+ " }\n"
+ " }\n"
+ " }\n"
+ "}";
+ string config = config_prefix + unixSocketFilePath() + config_trailer;
+ NetconfConfigPtr ctx(new NetconfConfig());
+ ElementPtr json;
+ ParserContext parser_context;
+ EXPECT_NO_THROW(json =
+ parser_context.parseString(config, ParserContext::PARSER_NETCONF));
+ ASSERT_TRUE(json);
+ ASSERT_EQ(Element::map, json->getType());
+ ConstElementPtr netconf_json = json->get("Netconf");
+ ASSERT_TRUE(netconf_json);
+ json = boost::const_pointer_cast<Element>(netconf_json);
+ ASSERT_TRUE(json);
+ NetconfSimpleParser::setAllDefaults(json);
+ NetconfSimpleParser::deriveParameters(json);
+ NetconfSimpleParser parser;
+ EXPECT_NO_THROW(parser.parse(ctx, json, false));
+
+ // Get service pair.
+ CfgServersMapPtr servers_map = ctx->getCfgServersMap();
+ ASSERT_TRUE(servers_map);
+ ASSERT_EQ(1, servers_map->size());
+ CfgServersMapPair service_pair = *servers_map->begin();
+
+ // Launch server.
+ thread_.reset(new thread([this]() { fakeServer(); signalStopped();}));
+
+ // Wait until the server is listening.
+ waitReady();
+
+ // Try yangConfig.
+ EXPECT_NO_THROW(agent_->yangConfig(service_pair));
+
+ // Wait server to be stopped.
+ waitStopped();
+
+ // Check request.
+ ASSERT_EQ(1, requests_.size());
+ const string& request_str = requests_[0];
+ ConstElementPtr request;
+ ASSERT_NO_THROW_LOG(request = Element::fromJSON(request_str));
+ string expected_str = "{\n"
+ "\"command\": \"config-set\",\n"
+ "\"arguments\": {\n"
+ " \"Dhcp4\": {\n"
+ " \"subnet4\": [\n"
+ " {\n"
+ " \"id\": 1,\n"
+ " \"subnet\": \"10.0.0.0/24\"\n"
+ " },\n"
+ " {\n"
+ " \"id\": 2,\n"
+ " \"subnet\": \"10.0.2.0/24\"\n"
+ " }\n"
+ " ]\n"
+ " }\n"
+ " }\n"
+ "}";
+ ConstElementPtr expected;
+ ASSERT_NO_THROW_LOG(expected = Element::fromJSON(expected_str));
+
+ sortSubnets(expected);
+ sortSubnets(request);
+ EXPECT_EQ(prettyPrint(expected), prettyPrint(request));
+
+ // Check response.
+ ASSERT_EQ(1, responses_.size());
+ const string& response_str = responses_[0];
+ ConstElementPtr response;
+ ASSERT_NO_THROW_LOG(response = Element::fromJSON(response_str));
+ expected_str = "{\n"
+ "\"result\": 0\n"
+ "}";
+ ASSERT_NO_THROW_LOG(expected = Element::fromJSON(expected_str));
+ EXPECT_TRUE(expected->equals(*response));
+}
+
+// Verifies that the subscribeConfig method works as expected.
+TEST_F(NetconfAgentTest, subscribeConfig) {
+ // Netconf configuration.
+ string config_prefix = "{\n"
+ " \"Netconf\": {\n"
+ " \"managed-servers\": {\n"
+ " \"dhcp4\": {\n"
+ " \"control-socket\": {\n"
+ " \"socket-type\": \"unix\",\n"
+ " \"socket-name\": \"";
+ string config_trailer = "\"\n"
+ " }\n"
+ " }\n"
+ " }\n"
+ " }\n"
+ "}";
+ string config = config_prefix + unixSocketFilePath() + config_trailer;
+ NetconfConfigPtr ctx(new NetconfConfig());
+ ElementPtr json;
+ ParserContext parser_context;
+ EXPECT_NO_THROW(json =
+ parser_context.parseString(config, ParserContext::PARSER_NETCONF));
+ ASSERT_TRUE(json);
+ ASSERT_EQ(Element::map, json->getType());
+ ConstElementPtr netconf_json = json->get("Netconf");
+ ASSERT_TRUE(netconf_json);
+ json = boost::const_pointer_cast<Element>(netconf_json);
+ ASSERT_TRUE(json);
+ NetconfSimpleParser::setAllDefaults(json);
+ NetconfSimpleParser::deriveParameters(json);
+ NetconfSimpleParser parser;
+ EXPECT_NO_THROW(parser.parse(ctx, json, false));
+
+ // Get service pair.
+ CfgServersMapPtr servers_map = ctx->getCfgServersMap();
+ ASSERT_TRUE(servers_map);
+ ASSERT_EQ(1, servers_map->size());
+ CfgServersMapPair service_pair = *servers_map->begin();
+
+ // Try subscribeConfig.
+ EXPECT_EQ(0, agent_->subscriptions_.size());
+ ASSERT_NO_THROW_LOG(agent_->initSysrepo());
+ EXPECT_EQ(0, agent_->subscriptions_.size());
+ EXPECT_NO_THROW(agent_->subscribeConfig(service_pair));
+ EXPECT_EQ(1, agent_->subscriptions_.size());
+
+ /// Unsubscribe.
+ EXPECT_NO_THROW(agent_->subscriptions_.clear());
+}
+
+// Verifies that the update method works as expected: apply new YANG configuration
+// to the server. Note it is called by the subscription callback.
+TEST_F(NetconfAgentTest, update) {
+ // Initial YANG configuration.
+ const YRTree tree0 = YangRepr::buildTreeFromVector({
+ { "/kea-dhcp4-server:config", "", SR_CONTAINER_T, false },
+ { "/kea-dhcp4-server:config/subnet4[id='1']", "", SR_LIST_T, false },
+ { "/kea-dhcp4-server:config/subnet4[id='1']/id",
+ "1", SR_UINT32_T, false },
+ { "/kea-dhcp4-server:config/subnet4[id='1']/subnet",
+ "10.0.0.0/24", SR_STRING_T, true },
+ { "/kea-dhcp4-server:config/subnet4[id='2']", "", SR_LIST_T, false },
+ { "/kea-dhcp4-server:config/subnet4[id='2']/id",
+ "2", SR_UINT32_T, false },
+ { "/kea-dhcp4-server:config/subnet4[id='2']/subnet",
+ "10.0.2.0/24", SR_STRING_T, true }
+ });
+ // Load initial YANG configuration.
+ ASSERT_NO_THROW_LOG(agent_->initSysrepo());
+ YangRepr repr(KEA_DHCP4_SERVER);
+ ASSERT_NO_THROW_LOG(repr.set(tree0, agent_->startup_sess_));
+ EXPECT_NO_THROW(agent_->startup_sess_->apply_changes());
+
+ // Netconf configuration.
+ // Set validate-changes to false to avoid validate() to be called.
+ string config_prefix = "{\n"
+ " \"Netconf\": {\n"
+ " \"validate-changes\": false,\n"
+ " \"managed-servers\": {\n"
+ " \"dhcp4\": {\n"
+ " \"control-socket\": {\n"
+ " \"socket-type\": \"unix\",\n"
+ " \"socket-name\": \"";
+ string config_trailer = "\"\n"
+ " }\n"
+ " }\n"
+ " }\n"
+ " }\n"
+ "}";
+ string config = config_prefix + unixSocketFilePath() + config_trailer;
+ NetconfConfigPtr ctx(new NetconfConfig());
+ ElementPtr json;
+ ParserContext parser_context;
+ EXPECT_NO_THROW(json =
+ parser_context.parseString(config, ParserContext::PARSER_NETCONF));
+ ASSERT_TRUE(json);
+ ASSERT_EQ(Element::map, json->getType());
+ ConstElementPtr netconf_json = json->get("Netconf");
+ ASSERT_TRUE(netconf_json);
+ json = boost::const_pointer_cast<Element>(netconf_json);
+ ASSERT_TRUE(json);
+ NetconfSimpleParser::setAllDefaults(json);
+ NetconfSimpleParser::deriveParameters(json);
+ NetconfSimpleParser parser;
+ EXPECT_NO_THROW(parser.parse(ctx, json, false));
+
+ // Get service pair.
+ CfgServersMapPtr servers_map = ctx->getCfgServersMap();
+ ASSERT_TRUE(servers_map);
+ ASSERT_EQ(1, servers_map->size());
+ CfgServersMapPair service_pair = *servers_map->begin();
+
+ // Subscribe YANG changes.
+ EXPECT_EQ(0, agent_->subscriptions_.size());
+ EXPECT_NO_THROW(agent_->subscribeConfig(service_pair));
+ EXPECT_EQ(1, agent_->subscriptions_.size());
+
+ // Launch server.
+ thread_.reset(new thread([this]() { fakeServer(); signalStopped(); }));
+
+ // Wait until the server is listening.
+ waitReady();
+
+ // Change configuration (subnet #1 moved from 10.0.0.0/24 to 10.0.1/0/24).
+ const YRTree tree1 = YangRepr::buildTreeFromVector({
+ { "/kea-dhcp4-server:config", "", SR_CONTAINER_T, false },
+ { "/kea-dhcp4-server:config/subnet4[id='1']", "", SR_LIST_T, false },
+ { "/kea-dhcp4-server:config/subnet4[id='1']/id",
+ "1", SR_UINT32_T, false },
+ { "/kea-dhcp4-server:config/subnet4[id='1']/subnet",
+ "10.0.1.0/24", SR_STRING_T, true }, // The change is here!
+ { "/kea-dhcp4-server:config/subnet4[id='2']", "", SR_LIST_T, false },
+ { "/kea-dhcp4-server:config/subnet4[id='2']/id",
+ "2", SR_UINT32_T, false },
+ { "/kea-dhcp4-server:config/subnet4[id='2']/subnet",
+ "10.0.2.0/24", SR_STRING_T, true }
+ });
+ EXPECT_NO_THROW(repr.set(tree1, agent_->running_sess_));
+ EXPECT_NO_THROW(agent_->running_sess_->apply_changes());
+
+ // Wait server to be stopped.
+ waitStopped();
+
+ // Check request.
+ ASSERT_EQ(1, requests_.size());
+ const string& request_str = requests_[0];
+ ConstElementPtr request;
+ ASSERT_NO_THROW_LOG(request = Element::fromJSON(request_str));
+ string expected_str = "{\n"
+ "\"command\": \"config-set\",\n"
+ "\"arguments\": {\n"
+ " \"Dhcp4\": {\n"
+ " \"subnet4\": [\n"
+ " {\n"
+ " \"id\": 1,\n"
+ " \"subnet\": \"10.0.1.0/24\"\n"
+ " },\n"
+ " {\n"
+ " \"id\": 2,\n"
+ " \"subnet\": \"10.0.2.0/24\"\n"
+ " }\n"
+ " ]\n"
+ " }\n"
+ " }\n"
+ "}";
+ ConstElementPtr expected;
+ ASSERT_NO_THROW_LOG(expected = Element::fromJSON(expected_str));
+
+ sortSubnets(expected);
+ sortSubnets(request);
+ EXPECT_EQ(prettyPrint(expected), prettyPrint(request));
+
+ // Check response.
+ ASSERT_EQ(1, responses_.size());
+ const string& response_str = responses_[0];
+ ConstElementPtr response;
+ ASSERT_NO_THROW_LOG(response = Element::fromJSON(response_str));
+ expected_str = "{\n"
+ "\"result\": 0\n"
+ "}";
+ ASSERT_NO_THROW_LOG(expected = Element::fromJSON(expected_str));
+ EXPECT_TRUE(expected->equals(*response));
+}
+
+// Verifies that the validate method works as expected: test new YANG configuration
+// with the server. Note it is called by the subscription callback and
+// update is called after.
+TEST_F(NetconfAgentTest, validate) {
+ // Initial YANG configuration.
+ const YRTree tree0 = YangRepr::buildTreeFromVector({
+ { "/kea-dhcp4-server:config", "", SR_CONTAINER_T, false },
+ { "/kea-dhcp4-server:config/subnet4[id='1']", "", SR_LIST_T, false },
+ { "/kea-dhcp4-server:config/subnet4[id='1']/id",
+ "1", SR_UINT32_T, false },
+ { "/kea-dhcp4-server:config/subnet4[id='1']/subnet",
+ "10.0.0.0/24", SR_STRING_T, true },
+ { "/kea-dhcp4-server:config/subnet4[id='2']", "", SR_LIST_T, false },
+ { "/kea-dhcp4-server:config/subnet4[id='2']/id",
+ "2", SR_UINT32_T, false },
+ { "/kea-dhcp4-server:config/subnet4[id='2']/subnet",
+ "10.0.2.0/24", SR_STRING_T, true }
+ });
+ // Load initial YANG configuration.
+ ASSERT_NO_THROW_LOG(agent_->initSysrepo());
+ YangRepr repr(KEA_DHCP4_SERVER);
+ ASSERT_NO_THROW_LOG(repr.set(tree0, agent_->startup_sess_));
+ EXPECT_NO_THROW(agent_->startup_sess_->apply_changes());
+
+ // Netconf configuration.
+ string config_prefix = "{\n"
+ " \"Netconf\": {\n"
+ " \"managed-servers\": {\n"
+ " \"dhcp4\": {\n"
+ " \"control-socket\": {\n"
+ " \"socket-type\": \"unix\",\n"
+ " \"socket-name\": \"";
+ string config_trailer = "\"\n"
+ " }\n"
+ " }\n"
+ " }\n"
+ " }\n"
+ "}";
+ string config = config_prefix + unixSocketFilePath() + config_trailer;
+ NetconfConfigPtr ctx(new NetconfConfig());
+ ElementPtr json;
+ ParserContext parser_context;
+ EXPECT_NO_THROW(json =
+ parser_context.parseString(config, ParserContext::PARSER_NETCONF));
+ ASSERT_TRUE(json);
+ ASSERT_EQ(Element::map, json->getType());
+ ConstElementPtr netconf_json = json->get("Netconf");
+ ASSERT_TRUE(netconf_json);
+ json = boost::const_pointer_cast<Element>(netconf_json);
+ ASSERT_TRUE(json);
+ NetconfSimpleParser::setAllDefaults(json);
+ NetconfSimpleParser::deriveParameters(json);
+ NetconfSimpleParser parser;
+ EXPECT_NO_THROW(parser.parse(ctx, json, false));
+
+ // Get service pair.
+ CfgServersMapPtr servers_map = ctx->getCfgServersMap();
+ ASSERT_TRUE(servers_map);
+ ASSERT_EQ(1, servers_map->size());
+ CfgServersMapPair service_pair = *servers_map->begin();
+
+ // Subscribe YANG changes.
+ EXPECT_EQ(0, agent_->subscriptions_.size());
+ EXPECT_NO_THROW(agent_->subscribeConfig(service_pair));
+ EXPECT_EQ(1, agent_->subscriptions_.size());
+
+ // Launch server twice.
+ thread_.reset(new thread([this]()
+ {
+ fakeServer();
+ fakeServer();
+ signalStopped();
+ }));
+
+ // Wait until the server is listening.
+ waitReady();
+
+ // Change configuration (subnet #1 moved from 10.0.0.0/24 to 10.0.1/0/24).
+ const YRTree tree1 = YangRepr::buildTreeFromVector({
+ { "/kea-dhcp4-server:config", "", SR_CONTAINER_T, false },
+ { "/kea-dhcp4-server:config/subnet4[id='1']", "", SR_LIST_T, false },
+ { "/kea-dhcp4-server:config/subnet4[id='1']/id",
+ "1", SR_UINT32_T, false },
+ { "/kea-dhcp4-server:config/subnet4[id='1']/subnet",
+ "10.0.1.0/24", SR_STRING_T, true }, // The change is here!
+ { "/kea-dhcp4-server:config/subnet4[id='2']", "", SR_LIST_T, false },
+ { "/kea-dhcp4-server:config/subnet4[id='2']/id",
+ "2", SR_UINT32_T, false },
+ { "/kea-dhcp4-server:config/subnet4[id='2']/subnet",
+ "10.0.2.0/24", SR_STRING_T, true }
+ });
+ EXPECT_NO_THROW(repr.set(tree1, agent_->running_sess_));
+ EXPECT_NO_THROW(agent_->running_sess_->apply_changes());
+
+ // Wait servers to be stopped.
+ waitStopped();
+
+ // Check that the fake server received the first request.
+ ASSERT_LE(1, requests_.size());
+ string request_str = requests_[0];
+ ConstElementPtr request;
+ ASSERT_NO_THROW_LOG(request = Element::fromJSON(request_str));
+ string expected_str = "{\n"
+ "\"command\": \"config-test\",\n"
+ "\"arguments\": {\n"
+ " \"Dhcp4\": {\n"
+ " \"subnet4\": [\n"
+ " {\n"
+ " \"id\": 1,\n"
+ " \"subnet\": \"10.0.1.0/24\"\n"
+ " },\n"
+ " {\n"
+ " \"id\": 2,\n"
+ " \"subnet\": \"10.0.2.0/24\"\n"
+ " }\n"
+ " ]\n"
+ " }\n"
+ " }\n"
+ "}";
+ ConstElementPtr expected;
+ ASSERT_NO_THROW_LOG(expected = Element::fromJSON(expected_str));
+
+ sortSubnets(expected);
+ sortSubnets(request);
+ EXPECT_EQ(prettyPrint(expected), prettyPrint(request));
+
+ // Check that the fake server received the second request.
+ ASSERT_EQ(2, requests_.size());
+ request_str = requests_[1];
+ ASSERT_NO_THROW_LOG(request = Element::fromJSON(request_str));
+ expected_str = "{\n"
+ "\"command\": \"config-set\",\n"
+ "\"arguments\": {\n"
+ " \"Dhcp4\": {\n"
+ " \"subnet4\": [\n"
+ " {\n"
+ " \"id\": 1,\n"
+ " \"subnet\": \"10.0.1.0/24\"\n"
+ " },\n"
+ " {\n"
+ " \"id\": 2,\n"
+ " \"subnet\": \"10.0.2.0/24\"\n"
+ " }\n"
+ " ]\n"
+ " }\n"
+ " }\n"
+ "}";
+ ASSERT_NO_THROW_LOG(expected = Element::fromJSON(expected_str));
+
+ sortSubnets(expected);
+ sortSubnets(request);
+ EXPECT_EQ(prettyPrint(expected), prettyPrint(request));
+
+ // Check responses.
+ ASSERT_EQ(2, responses_.size());
+ string response_str = responses_[0];
+ ConstElementPtr response;
+ ASSERT_NO_THROW_LOG(response = Element::fromJSON(response_str));
+ expected_str = "{\n"
+ "\"result\": 0\n"
+ "}";
+ ASSERT_NO_THROW_LOG(expected = Element::fromJSON(expected_str));
+ EXPECT_TRUE(expected->equals(*response));
+
+ response_str = responses_[1];
+ ASSERT_NO_THROW_LOG(response = Element::fromJSON(response_str));
+ expected_str = "{\n"
+ "\"result\": 0\n"
+ "}";
+ ASSERT_NO_THROW_LOG(expected = Element::fromJSON(expected_str));
+ EXPECT_TRUE(expected->equals(*response));
+}
+
+// Verifies what happens when the validate method returns an error.
+TEST_F(NetconfAgentTest, noValidate) {
+ // Initial YANG configuration.
+ const YRTree tree0 = YangRepr::buildTreeFromVector({
+ { "/kea-dhcp4-server:config", "", SR_CONTAINER_T, false },
+ { "/kea-dhcp4-server:config/subnet4[id='1']", "", SR_LIST_T, false },
+ { "/kea-dhcp4-server:config/subnet4[id='1']/id",
+ "1", SR_UINT32_T, false },
+ { "/kea-dhcp4-server:config/subnet4[id='1']/subnet",
+ "10.0.0.0/24", SR_STRING_T, true }
+ });
+ // Load initial YANG configuration.
+ ASSERT_NO_THROW_LOG(agent_->initSysrepo());
+ YangRepr repr(KEA_DHCP4_SERVER);
+ ASSERT_NO_THROW_LOG(repr.set(tree0, agent_->startup_sess_));
+ EXPECT_NO_THROW(agent_->startup_sess_->apply_changes());
+
+ // Netconf configuration.
+ string config_prefix = "{\n"
+ " \"Netconf\": {\n"
+ " \"managed-servers\": {\n"
+ " \"dhcp4\": {\n"
+ " \"control-socket\": {\n"
+ " \"socket-type\": \"unix\",\n"
+ " \"socket-name\": \"";
+ string config_trailer = "\"\n"
+ " }\n"
+ " }\n"
+ " }\n"
+ " }\n"
+ "}";
+ string config = config_prefix + unixSocketFilePath() + config_trailer;
+ NetconfConfigPtr ctx(new NetconfConfig());
+ ElementPtr json;
+ ParserContext parser_context;
+ EXPECT_NO_THROW(json =
+ parser_context.parseString(config, ParserContext::PARSER_NETCONF));
+ ASSERT_TRUE(json);
+ ASSERT_EQ(Element::map, json->getType());
+ ConstElementPtr netconf_json = json->get("Netconf");
+ ASSERT_TRUE(netconf_json);
+ json = boost::const_pointer_cast<Element>(netconf_json);
+ ASSERT_TRUE(json);
+ NetconfSimpleParser::setAllDefaults(json);
+ NetconfSimpleParser::deriveParameters(json);
+ NetconfSimpleParser parser;
+ EXPECT_NO_THROW(parser.parse(ctx, json, false));
+
+ // Get service pair.
+ CfgServersMapPtr servers_map = ctx->getCfgServersMap();
+ ASSERT_TRUE(servers_map);
+ ASSERT_EQ(1, servers_map->size());
+ CfgServersMapPair service_pair = *servers_map->begin();
+
+ // Subscribe YANG changes.
+ EXPECT_EQ(0, agent_->subscriptions_.size());
+ EXPECT_NO_THROW(agent_->subscribeConfig(service_pair));
+ EXPECT_EQ(1, agent_->subscriptions_.size());
+
+ // Change configuration (add invalid user context).
+ const YRTree tree1 = YangRepr::buildTreeFromVector({
+ { "/kea-dhcp4-server:config", "", SR_CONTAINER_T, false },
+ { "/kea-dhcp4-server:config/subnet4[id='1']", "", SR_LIST_T, false },
+ { "/kea-dhcp4-server:config/subnet4[id='1']/id",
+ "1", SR_UINT32_T, false },
+ { "/kea-dhcp4-server:config/subnet4[id='1']/subnet",
+ "10.0.0.0/24", SR_STRING_T, true },
+ { "/kea-dhcp4-server:config/subnet4[id='1']/user-context",
+ "BOGUS", SR_STRING_T, true }
+ });
+ try {
+ repr.set(tree1, agent_->running_sess_);
+ } catch (const sysrepo_exception& ex) {
+ EXPECT_EQ("User callback failed", string(ex.what()));
+ } catch (const std::exception& ex) {
+ ADD_FAILURE() << "unexpected exception: " << ex.what();
+ } catch (...) {
+ ADD_FAILURE() << "unexpected exception";
+ }
+}
+
+} // namespace
diff --git a/src/bin/netconf/tests/parser_unittests.cc b/src/bin/netconf/tests/parser_unittests.cc
new file mode 100644
index 0000000..b496470
--- /dev/null
+++ b/src/bin/netconf/tests/parser_unittests.cc
@@ -0,0 +1,988 @@
+// Copyright (C) 2018-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <cc/data.h>
+#include <cc/dhcp_config_error.h>
+#include <netconf/parser_context.h>
+#include <testutils/gtest_utils.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 netconf {
+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_LOG({
+ 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_LOG(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 Netconf objects) can
+// be parsed with syntactic checking (and as pure JSON).
+TEST(ParserTest, keywordNetconf) {
+ string txt = "{ \"Netconf\": {\n"
+ " \"boot-update\": true,"
+ " \"subscribe-changes\": true,"
+ " \"validate-changes\": true,"
+ " \"managed-servers\": {"
+ " \"dhcp4\": {"
+ " \"model\": \"kea-dhcp4-server\","
+ " \"boot-update\": false,"
+ " \"subscribe-changes\": false,"
+ " \"validate-changes\": false,"
+ " \"control-socket\": {"
+ " \"socket-type\": \"unix\","
+ " \"socket-name\": \"/tmp/kea4-ctrl-socket\""
+ " }"
+ " },"
+ " \"dhcp6\": {"
+ " \"model\": \"kea-dhcp6-server\","
+ " \"boot-update\": false,"
+ " \"subscribe-changes\": false,"
+ " \"validate-changes\": false,"
+ " \"control-socket\": {"
+ " \"socket-type\": \"http\","
+ " \"socket-url\": \"http://127.0.0.1:12345/\""
+ " }"
+ " },"
+ " \"d2\": {"
+ " \"model\": \"kea-dhcp-ddns\","
+ " \"boot-update\": false,"
+ " \"subscribe-changes\": false,"
+ " \"validate-changes\": false,"
+ " \"control-socket\": {"
+ " \"socket-type\": \"stdout\""
+ " }"
+ " },"
+ " \"ca\": {"
+ " \"model\": \"kea-ctrl-agent\","
+ " \"boot-update\": false,"
+ " \"subscribe-changes\": false,"
+ " \"validate-changes\": false,"
+ " \"control-socket\": {"
+ " \"socket-type\": \"http\","
+ " \"user-context\": { \"use default\": true }"
+ " }"
+ " }"
+ " },"
+ " \"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_NETCONF)
+ testParser(txt, ParserContext::PARSER_NETCONF);
+ testParser(txt, ParserContext::PARSER_JSON);
+}
+
+// This test checks if simplified config (without top level and Netconf
+// objects) can be parsed with syntactic checking (and as pure JSON).
+TEST(ParserTest, keywordSubNetconf) {
+
+ // This is similar to previous test, but note the lack of outer
+ // map and Netconf.
+ string txt = "{\n"
+ " \"boot-update\": true,"
+ " \"subscribe-changes\": true,"
+ " \"validate-changes\": true,"
+ " \"managed-servers\": {"
+ " \"dhcp4\": {"
+ " \"model\": \"kea-dhcp4-server\","
+ " \"boot-update\": false,"
+ " \"subscribe-changes\": false,"
+ " \"validate-changes\": false,"
+ " \"control-socket\": {"
+ " \"socket-type\": \"unix\","
+ " \"socket-name\": \"/tmp/kea4-ctrl-socket\""
+ " }"
+ " },"
+ " \"dhcp6\": {"
+ " \"model\": \"kea-dhcp6-server\","
+ " \"boot-update\": false,"
+ " \"subscribe-changes\": false,"
+ " \"validate-changes\": false,"
+ " \"control-socket\": {"
+ " \"socket-type\": \"http\","
+ " \"socket-url\": \"http://127.0.0.1:12345/\""
+ " }"
+ " },"
+ " \"d2\": {"
+ " \"model\": \"kea-dhcp-ddns\","
+ " \"boot-update\": false,"
+ " \"subscribe-changes\": false,"
+ " \"validate-changes\": false,"
+ " \"control-socket\": {"
+ " \"socket-type\": \"stdout\""
+ " }"
+ " },"
+ " \"ca\": {"
+ " \"model\": \"kea-ctrl-agent\","
+ " \"boot-update\": false,"
+ " \"subscribe-changes\": false,"
+ " \"validate-changes\": false,"
+ " \"control-socket\": {"
+ " \"socket-type\": \"http\","
+ " \"user-context\": { \"use default\": true }"
+ " }"
+ " }"
+ " },"
+ " \"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_NETCONF.
+ testParser(txt, ParserContext::PARSER_SUB_NETCONF);
+ 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= "{ \"Netconf\": {"
+ " \"managed-servers\": {\n"
+ " \"d2\": {\n"
+ " \"model\": \"foo\",\n"
+ " \"control-socket\": {\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_NETCONF);
+}
+
+// Tests if C++ (//) comments can start anywhere, not just in the first line.
+TEST(ParserTest, cppComments) {
+ string txt= "{ \"Netconf\": { // the level is over 9000!\n"
+ " \"managed-servers\": {\n"
+ " // Let's try talking to D2. Sadly, it never talks"
+ " // to us back :( Maybe he doesn't like his name?\n"
+ " \"d2\": {\n"
+ " \"model\": \"foo\",\n"
+ " \"control-socket\": {\n"
+ "\"socket-type\": \"unix\", \n"
+ "\"socket-name\": \"Hector\" \n"
+ "} } } } }";
+
+ testParser(txt, ParserContext::PARSER_NETCONF, false);
+}
+
+// Tests if bash (#) comments can start anywhere, not just in the first line.
+TEST(ParserTest, bashCommentsInline) {
+ string txt= "{ \"Netconf\": {"
+ " \"managed-servers\": {\n"
+ " \"d2\": {\n"
+ " \"model\": \"foo\",\n"
+ " \"control-socket\": {\n"
+ "\"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_NETCONF, false);
+}
+
+// Tests if multi-line C style comments are handled correctly.
+TEST(ParserTest, multilineComments) {
+ string txt= "{ \"Netconf\": {"
+ " \"managed-servers\": {\n"
+ " \"dhcp4\": {\n"
+ " \"model\": \"foo\",\n"
+ " \"control-socket\": {\n"
+ " \"socket-type\": \"stdout\"\n"
+ " }\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\": {"
+ " \"model\": \"bar\",\n"
+ " \"control-socket\": {\n"
+ " \"socket-type\": \"unix\",\n"
+ "\"socket-name\": \"Hector\"\n"
+ "} }*/ } } }";
+ testParser(txt, ParserContext::PARSER_NETCONF, false);
+}
+
+// Tests if embedded comments are handled correctly.
+TEST(ParserTest, embbededComments) {
+ string txt= "{ \"Netconf\": {"
+ " \"comment\": \"a comment\","
+ " \"managed-servers\": {\n"
+ " \"dhcp4\": {\n"
+ " \"control-socket\": {\n"
+ " \"user-context\": { \"comment\": \"indirect\" },\n"
+ " \"socket-type\": \"stdout\"\n"
+ " } } },\n"
+ " \"user-context\": { \"compatible\": true }\n"
+ "} }";
+ testParser(txt, ParserContext::PARSER_NETCONF, 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_NETCONF);
+ } 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 NetconfParser. 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("simple-dhcp4.json");
+ configs.push_back("simple-dhcp6.json");
+
+ for (int i = 0; i<configs.size(); i++) {
+ testFile(string(CFG_EXAMPLES) + "/" + configs[i]);
+ }
+}
+
+/// @brief Tests error conditions in NetconfParser
+///
+/// @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-dhcp4.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_NETCONF,
+ "<string>:1.1-3: syntax error, unexpected integer, "
+ "expecting {");
+ testError("-456",
+ ParserContext::PARSER_NETCONF,
+ "<string>:1.1-4: syntax error, unexpected integer, "
+ "expecting {");
+ testError("-0001",
+ ParserContext::PARSER_NETCONF,
+ "<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_NETCONF,
+ "<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_NETCONF,
+ "<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_NETCONF,
+ "<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_NETCONF,
+ "<string>:1.1: syntax error, unexpected [, "
+ "expecting {");
+ testError("[]\n",
+ ParserContext::PARSER_NETCONF,
+ "<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_NETCONF,
+ "<string>:1.3-5: syntax error, unexpected integer, "
+ "expecting Netconf");
+ testError("{ \"foo\" }\n",
+ ParserContext::PARSER_JSON,
+ "<string>:1.9: syntax error, unexpected }, "
+ "expecting :");
+ testError("{ \"foo\" }\n",
+ ParserContext::PARSER_NETCONF,
+ "<string>:1.3-7: syntax error, unexpected constant string, "
+ "expecting Netconf");
+ testError("{ \"foo\":null }\n",
+ ParserContext::PARSER_NETCONF,
+ "<string>:1.3-7: syntax error, unexpected constant string, "
+ "expecting Netconf");
+ testError("{ \"Logging\":null }\n",
+ ParserContext::PARSER_NETCONF,
+ "<string>:1.3-11: syntax error, unexpected constant string, "
+ "expecting Netconf");
+ testError("{ \"Netconf\" }\n",
+ ParserContext::PARSER_NETCONF,
+ "<string>:1.13: syntax error, unexpected }, "
+ "expecting :");
+ testError("{ \"Netconf\":",
+ ParserContext::PARSER_NETCONF,
+ "<string>:1.13: 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("{ \"Netconf\":{\n"
+ " \"managed-servers\":false }}\n",
+ ParserContext::PARSER_NETCONF,
+ "<string>:2.21-25: syntax error, unexpected boolean, "
+ "expecting {");
+
+ // unknown keyword
+ testError("{ \"Netconf\":{\n"
+ " \"topping\": \"Mozzarella\" }}\n",
+ ParserContext::PARSER_NETCONF,
+ "<string>:2.2-10: got unexpected keyword "
+ "\"topping\" in Netconf map.");
+
+ // user context and embedded comments
+ testError("{ \"Netconf\":{\n"
+ " \"comment\": true } }\n",
+ ParserContext::PARSER_NETCONF,
+ "<string>:2.14-17: syntax error, unexpected boolean, "
+ "expecting constant string");
+
+ testError("{ \"Netconf\":{\n"
+ " \"user-context\": \"a comment\" } }\n",
+ ParserContext::PARSER_NETCONF,
+ "<string>:2.19-29: syntax error, unexpected constant string, "
+ "expecting {");
+
+ testError("{ \"Netconf\":{\n"
+ " \"comment\": \"a comment\",\n"
+ " \"comment\": \"another one\" } }\n",
+ ParserContext::PARSER_NETCONF,
+ "<string>:3.3-11: duplicate user-context/comment entries "
+ "(previous at <string>:2:3)");
+
+ testError("{ \"Netconf\":{\n"
+ " \"user-context\": { \"version\": 1 },\n"
+ " \"user-context\": { \"one\": \"only\" } } }\n",
+ ParserContext::PARSER_NETCONF,
+ "<string>:3.3-16: duplicate user-context entries "
+ "(previous at <string>:2:19)");
+
+ testError("{ \"Netconf\":{\n"
+ " \"user-context\": { \"comment\": \"indirect\" },\n"
+ " \"comment\": \"a comment\" } }\n",
+ ParserContext::PARSER_NETCONF,
+ "<string>:3.3-11: duplicate user-context/comment entries "
+ "(previous at <string>:2:19)");
+
+ // duplicate Netconf entries
+ testError("{ \"Netconf\":{\n"
+ " \"comment\": \"first\" },\n"
+ " \"Netconf\":{\n"
+ " \"comment\": \"second\" }}\n",
+ ParserContext::PARSER_NETCONF,
+ "<string>:3.3-11: syntax error, unexpected Netconf, expecting \",\" or }");
+
+ // duplicate of not string entries
+ testError("{ \"Netconf\":{\n"
+ " \"boot-update\": true,\n"
+ " \"boot-update\": false }}\n",
+ ParserContext::PARSER_NETCONF,
+ "<string>:3:2: duplicate boot-update entries in "
+ "Netconf map (previous at <string>:2:17)");
+
+ // duplicate of string entries
+ testError("{ \"Netconf\":{\n"
+ " \"managed-servers\": {\n"
+ " \"d2\": {\n"
+ " \"model\": \"foo\",\n"
+ " \"model\": \"bar\" }}}}\n",
+ ParserContext::PARSER_NETCONF,
+ "<string>:5:7: duplicate model entries in "
+ "managed-servers entry map (previous at <string>:4:16)");
+}
+
+// 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_LOG(
+ 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_LOG(
+ 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_NETCONF));
+ 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 (netconf_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 file
+ string sample_dir(CFG_EXAMPLES);
+ sample_dir += "/";
+ ElementPtr sample_json = Element::createList();
+ loadFile(sample_dir + "simple-dhcp4.json", sample_json);
+ loadFile(sample_dir + "simple-dhcp6.json", sample_json);
+ KeywordSet sample_keys = {
+ "ca", "d2",
+ "hooks-libraries", "library", "parameters",
+ "socket-url"
+ };
+ // 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_NETCONF),
+ 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-dhcp6.json";
+ ParserContext ctx;
+ ElementPtr sample_json;
+ EXPECT_NO_THROW(sample_json =
+ ctx.parseFile(sample_fname, ParserContext::PARSER_NETCONF));
+ 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 = "NETCONF_CONFIG_SYNTAX_WARNING Netconf ";
+ 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"({
+ "Netconf": {
+ "boot-update": true,
+ "loggers": [
+ {
+ "name": "kea-netconf",
+ "severity": "DEBUG",
+ "debuglevel": 99,
+ },
+ ],
+ "managed-servers": {
+ "dhcp4": {
+ "control-socket": {
+ "socket-name": "/tmp/kea-dhcp4-ctrl.sock",
+ "socket-type": "unix",
+ },
+ "model": "kea-dhcp4-server",
+ },
+ "dhcp6": {
+ "control-socket": {
+ "socket-name": "/tmp/kea-dhcp6-ctrl.sock",
+ "socket-type": "unix",
+ },
+ "model": "kea-dhcp6-server",
+ },
+ },
+ "subscribe-changes": true,
+ "validate-changes": true,
+ },
+})");
+ testParser(txt, ParserContext::PARSER_NETCONF, false);
+
+ addLog("<string>:8.25");
+ addLog("<string>:9.8");
+ addLog("<string>:15.32");
+ addLog("<string>:17.36");
+ addLog("<string>:22.32");
+ addLog("<string>:24.36");
+ addLog("<string>:25.8");
+ addLog("<string>:28.29");
+ addLog("<string>:29.4");
+ EXPECT_TRUE(checkFile());
+
+ // Test with many consecutive commas.
+ boost::replace_all(txt, ",", ",,,,");
+ testParser(txt, ParserContext::PARSER_NETCONF, false);
+}
+
+} // namespace test
+} // namespace netconf
+} // namespace isc
diff --git a/src/bin/netconf/tests/run_unittests.cc b/src/bin/netconf/tests/run_unittests.cc
new file mode 100644
index 0000000..b735a8e
--- /dev/null
+++ b/src/bin/netconf/tests/run_unittests.cc
@@ -0,0 +1,27 @@
+// Copyright (C) 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 <gtest/gtest.h>
+#include <util/unittests/run_all.h>
+#include <log/logger_support.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/netconf/tests/shtests/Makefile.am b/src/bin/netconf/tests/shtests/Makefile.am
new file mode 100644
index 0000000..d05472c
--- /dev/null
+++ b/src/bin/netconf/tests/shtests/Makefile.am
@@ -0,0 +1,15 @@
+SUBDIRS = .
+
+# Shell tests
+SHTESTS = netconf_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)
+
+# Don't install shell tests.
+noinst_SCRIPTS = $(SHTESTS)
diff --git a/src/bin/netconf/tests/shtests/Makefile.in b/src/bin/netconf/tests/shtests/Makefile.in
new file mode 100644
index 0000000..8429d42
--- /dev/null
+++ b/src/bin/netconf/tests/shtests/Makefile.in
@@ -0,0 +1,841 @@
+# 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@
+subdir = src/bin/netconf/tests/shtests
+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 = netconf_tests.sh
+CONFIG_CLEAN_VPATH_FILES =
+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 =
+SOURCES =
+DIST_SOURCES =
+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; \
+}
+DIST_SUBDIRS = $(SUBDIRS)
+am__DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/netconf_tests.sh.in
+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 = .
+
+# Shell tests
+SHTESTS = netconf_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)
+
+# Don't install shell tests.
+noinst_SCRIPTS = $(SHTESTS)
+all: all-recursive
+
+.SUFFIXES:
+$(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/netconf/tests/shtests/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/bin/netconf/tests/shtests/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):
+netconf_tests.sh: $(top_builddir)/config.status $(srcdir)/netconf_tests.sh.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+
+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 $(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 mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -f Makefile
+distclean-am: clean-am 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 Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-recursive
+
+mostlyclean-am: 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 check \
+ check-TESTS check-am clean clean-generic clean-libtool \
+ cscopelist-am ctags ctags-am distclean 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-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/netconf/tests/shtests/netconf_tests.sh.in b/src/bin/netconf/tests/shtests/netconf_tests.sh.in
new file mode 100644
index 0000000..b6ba611
--- /dev/null
+++ b/src/bin/netconf/tests/shtests/netconf_tests.sh.in
@@ -0,0 +1,216 @@
+#!/bin/sh
+
+# Copyright (C) 2018-2021 Internet Systems Consortium, Inc. ("ISC")
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# shellcheck disable=SC1091
+# SC1091: Not following: ... was not specified as input (see shellcheck -x).
+
+# shellcheck disable=SC2039
+# SC2039: In POSIX sh, 'local' is undefined.
+
+# Exit with error if commands exit with non-zero and if undefined variables are
+# used.
+set -eu
+
+# Path to the temporary configuration file.
+CFG_FILE="@abs_top_builddir@/src/bin/netconf/tests/shtests/test_config.json"
+# Path to the Kea log file.
+LOG_FILE="@abs_top_builddir@/src/bin/netconf/tests/shtests/test.log"
+
+# Kea-netconf configuration to be stored in the configuration file.
+CONFIG="{
+ \"Netconf\":
+ {
+ \"managed-servers\":
+ {
+ \"dhcp4\":
+ {
+ \"comment\": \"simply use defaults...\"
+ }
+ },
+ \"loggers\": [
+ {
+ \"name\": \"kea-netconf\",
+ \"output_options\": [
+ {
+ \"output\": \"$LOG_FILE\"
+ }
+ ],
+ \"severity\": \"INFO\"
+ }
+ ]
+ }
+}"
+
+# Invalid configuration (syntax error) to check that Kea can check syntax.
+CONFIG_BAD_SYNTAX="{
+ \"Netconf\":
+ {
+ \"boot-update\": BOGUS
+ }
+}"
+
+# Invalid configuration (invalid url) to check that Kea can check syntax.
+CONFIG_BAD_VALUE="{
+ \"Netconf\":
+ {
+ \"managed-servers\":
+ {
+ \"dhcp4\":
+ {
+ \"control-socket\":
+ {
+ \"socket-type\": \"http\",
+ \"socket-url\": \"BOGUS\"
+ }
+ }
+ }
+ }
+}"
+
+# Set the location of the executable.
+bin="kea-netconf"
+bin_path="@abs_top_builddir@/src/bin/netconf"
+
+# Import common test library.
+. "@abs_top_builddir@/src/lib/testutils/dhcp_test_lib.sh"
+
+# This test verifies that help can be printed out.
+usage_test() {
+ local test_name="${1}"
+ local parameter="${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}" "${parameter}"
+ 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 no argument is not reported as a PID file error.
+no_argument_test() {
+ local test_name="${1}"
+ local expected_code="${2}"
+
+ # Log the start of the test and print test name.
+ test_start "${test_name}"
+
+ # Check it
+ printf "Running command %s.\n" "\"${bin_path}/${bin}\""
+ run_command \
+ "${bin_path}/${bin}"
+ 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 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 Netconf 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 Netconf Agent to log to the specific file.
+ set_logger
+ # Start Netconf Agent.
+ start_kea ${bin_path}/${bin}
+ # Wait up to 20s for Netconf Agent to start.
+ wait_for_kea 20
+ if [ "${_WAIT_FOR_KEA}" -eq 0 ]; then
+ printf "ERROR: timeout waiting for Netconf 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 Netconf 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 Netconf 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: Netconf 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
+}
+
+version_test "netconf.version"
+usage_test "netconf.invalid-param" "-f" 1
+usage_test "netconf.dash-h" "-h" 1
+usage_test "netconf.dash-v" "-v" 0
+no_argument_test "netconf.no-argument" 1
+shutdown_test "netconf.sigterm_test" 15
+shutdown_test "netconf.sigint_test" 2
+logger_vars_test "netconf.variables"
+syntax_check_test "netconf.syntax_check_success" "${CONFIG}" 0
+syntax_check_test "netconf.syntax_check_bad_syntax" "${CONFIG_BAD_SYNTAX}" 1
+syntax_check_test "netconf.syntax_check_bad_values" "${CONFIG_BAD_VALUE}" 1
diff --git a/src/bin/netconf/tests/test_data_files_config.h.in b/src/bin/netconf/tests/test_data_files_config.h.in
new file mode 100644
index 0000000..78f5c60
--- /dev/null
+++ b/src/bin/netconf/tests/test_data_files_config.h.in
@@ -0,0 +1,9 @@
+// Copyright (C) 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/.
+
+/// @brief Path to netconf source dir
+#define NETCONF_SRC_DIR "@abs_top_srcdir@/src/bin/netconf"
+#define NETCONF_TEST_DATA_DIR "@abs_top_srcdir@/src/bin/netconf/tests/testdata"
diff --git a/src/bin/netconf/tests/test_libraries.h.in b/src/bin/netconf/tests/test_libraries.h.in
new file mode 100644
index 0000000..f2a5a65
--- /dev/null
+++ b/src/bin/netconf/tests/test_libraries.h.in
@@ -0,0 +1,24 @@
+// Copyright (C) 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/.
+
+#ifndef AGENT_TEST_LIBRARIES_H
+#define AGENT_TEST_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 library with context_create and three "standard" callouts.
+static const char* BASIC_CALLOUT_LIBRARY = "@abs_builddir@/.libs/libbasic.so";
+
+} // anonymous namespace
+
+#endif // TEST_LIBRARIES_H
diff --git a/src/bin/netconf/tests/testdata/get_config.json b/src/bin/netconf/tests/testdata/get_config.json
new file mode 100644
index 0000000..d08aed2
--- /dev/null
+++ b/src/bin/netconf/tests/testdata/get_config.json
@@ -0,0 +1,24 @@
+{
+ "Netconf": {
+ "boot-update": true,
+ "hooks-libraries": [ ],
+ "managed-servers": {
+ "dhcp4": {
+ "boot-update": true,
+ "control-socket": {
+ "socket-name": "/tmp/kea4-ctrl-socket",
+ "socket-type": "unix",
+ "socket-url": "http://127.0.0.1:8000/"
+ },
+ "model": "kea-dhcp4-server",
+ "subscribe-changes": true,
+ "user-context": {
+ "comment": "Kea DHCP4 server serving network on floor 13"
+ },
+ "validate-changes": false
+ }
+ },
+ "subscribe-changes": true,
+ "validate-changes": true
+ }
+}