summaryrefslogtreecommitdiffstats
path: root/src/lib/dhcpsrv/testutils
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/dhcpsrv/testutils')
-rw-r--r--src/lib/dhcpsrv/testutils/Makefile.am64
-rw-r--r--src/lib/dhcpsrv/testutils/Makefile.in1065
-rw-r--r--src/lib/dhcpsrv/testutils/config_result_check.cc89
-rw-r--r--src/lib/dhcpsrv/testutils/config_result_check.h48
-rw-r--r--src/lib/dhcpsrv/testutils/dhcp4o6_test_ipc.cc41
-rw-r--r--src/lib/dhcpsrv/testutils/dhcp4o6_test_ipc.h90
-rw-r--r--src/lib/dhcpsrv/testutils/generic_backend_unittest.cc284
-rw-r--r--src/lib/dhcpsrv/testutils/generic_backend_unittest.h352
-rw-r--r--src/lib/dhcpsrv/testutils/generic_cb_dhcp4_unittest.cc4591
-rw-r--r--src/lib/dhcpsrv/testutils/generic_cb_dhcp4_unittest.h390
-rw-r--r--src/lib/dhcpsrv/testutils/generic_cb_dhcp6_unittest.cc4747
-rw-r--r--src/lib/dhcpsrv/testutils/generic_cb_dhcp6_unittest.h394
-rw-r--r--src/lib/dhcpsrv/testutils/generic_cb_recovery_unittest.cc376
-rw-r--r--src/lib/dhcpsrv/testutils/generic_cb_recovery_unittest.h164
-rw-r--r--src/lib/dhcpsrv/testutils/generic_host_data_source_unittest.cc3891
-rw-r--r--src/lib/dhcpsrv/testutils/generic_host_data_source_unittest.h937
-rw-r--r--src/lib/dhcpsrv/testutils/host_data_source_utils.cc409
-rw-r--r--src/lib/dhcpsrv/testutils/host_data_source_utils.h134
-rw-r--r--src/lib/dhcpsrv/testutils/lease_file_io.cc66
-rw-r--r--src/lib/dhcpsrv/testutils/lease_file_io.h63
-rw-r--r--src/lib/dhcpsrv/testutils/memory_host_data_source.cc390
-rw-r--r--src/lib/dhcpsrv/testutils/memory_host_data_source.h342
-rw-r--r--src/lib/dhcpsrv/testutils/mysql_generic_backend_unittest.cc43
-rw-r--r--src/lib/dhcpsrv/testutils/mysql_generic_backend_unittest.h45
-rw-r--r--src/lib/dhcpsrv/testutils/pgsql_generic_backend_unittest.cc48
-rw-r--r--src/lib/dhcpsrv/testutils/pgsql_generic_backend_unittest.h45
-rw-r--r--src/lib/dhcpsrv/testutils/test_config_backend.h121
-rw-r--r--src/lib/dhcpsrv/testutils/test_config_backend_dhcp4.cc1452
-rw-r--r--src/lib/dhcpsrv/testutils/test_config_backend_dhcp4.h550
-rw-r--r--src/lib/dhcpsrv/testutils/test_config_backend_dhcp6.cc1556
-rw-r--r--src/lib/dhcpsrv/testutils/test_config_backend_dhcp6.h580
-rw-r--r--src/lib/dhcpsrv/testutils/test_utils.cc153
-rw-r--r--src/lib/dhcpsrv/testutils/test_utils.h86
33 files changed, 23606 insertions, 0 deletions
diff --git a/src/lib/dhcpsrv/testutils/Makefile.am b/src/lib/dhcpsrv/testutils/Makefile.am
new file mode 100644
index 0000000..9e530c2
--- /dev/null
+++ b/src/lib/dhcpsrv/testutils/Makefile.am
@@ -0,0 +1,64 @@
+SUBDIRS = .
+
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += -DDATABASE_SCRIPTS_DIR=\"$(abs_top_srcdir)/src/share/database/scripts\"
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+
+CLEANFILES = *.gcno *.gcda
+
+if HAVE_GTEST
+
+noinst_LTLIBRARIES = libdhcpsrvtest.la
+
+libdhcpsrvtest_la_SOURCES = config_result_check.cc config_result_check.h
+libdhcpsrvtest_la_SOURCES += dhcp4o6_test_ipc.cc dhcp4o6_test_ipc.h
+libdhcpsrvtest_la_SOURCES += host_data_source_utils.cc host_data_source_utils.h
+libdhcpsrvtest_la_SOURCES += memory_host_data_source.cc memory_host_data_source.h
+libdhcpsrvtest_la_SOURCES += test_utils.cc test_utils.h
+libdhcpsrvtest_la_SOURCES += generic_backend_unittest.cc generic_backend_unittest.h
+libdhcpsrvtest_la_SOURCES += generic_host_data_source_unittest.cc generic_host_data_source_unittest.h
+libdhcpsrvtest_la_SOURCES += generic_cb_dhcp4_unittest.h generic_cb_dhcp4_unittest.cc
+libdhcpsrvtest_la_SOURCES += generic_cb_dhcp6_unittest.h generic_cb_dhcp6_unittest.cc
+libdhcpsrvtest_la_SOURCES += generic_cb_recovery_unittest.h generic_cb_recovery_unittest.cc
+libdhcpsrvtest_la_SOURCES += lease_file_io.cc lease_file_io.h
+libdhcpsrvtest_la_SOURCES += test_config_backend.h
+libdhcpsrvtest_la_SOURCES += test_config_backend_dhcp4.cc test_config_backend_dhcp4.h
+libdhcpsrvtest_la_SOURCES += test_config_backend_dhcp6.cc test_config_backend_dhcp6.h
+
+if HAVE_MYSQL
+libdhcpsrvtest_la_SOURCES += mysql_generic_backend_unittest.cc mysql_generic_backend_unittest.h
+endif
+
+if HAVE_PGSQL
+libdhcpsrvtest_la_SOURCES += pgsql_generic_backend_unittest.cc pgsql_generic_backend_unittest.h
+endif
+
+libdhcpsrvtest_la_CXXFLAGS = $(AM_CXXFLAGS)
+libdhcpsrvtest_la_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+
+libdhcpsrvtest_la_LIBADD =
+
+if HAVE_MYSQL
+libdhcpsrvtest_la_CPPFLAGS += $(MYSQL_CPPFLAGS)
+libdhcpsrvtest_la_LIBADD += $(top_builddir)/src/lib/mysql/libkea-mysql.la
+endif
+if HAVE_PGSQL
+libdhcpsrvtest_la_CPPFLAGS += $(PGSQL_CPPFLAGS)
+libdhcpsrvtest_la_LIBADD += $(top_builddir)/src/lib/pgsql/libkea-pgsql.la
+endif
+
+libdhcpsrvtest_la_LDFLAGS = $(AM_LDFLAGS)
+if HAVE_MYSQL
+libdhcpsrvtest_la_LDFLAGS += $(MYSQL_LIBS)
+endif
+if HAVE_PGSQL
+libdhcpsrvtest_la_LDFLAGS += $(PGSQL_LIBS)
+endif
+
+libdhcpsrvtest_la_LIBADD += $(top_builddir)/src/lib/database/libkea-database.la
+libdhcpsrvtest_la_LIBADD += $(top_builddir)/src/lib/cc/libkea-cc.la
+libdhcpsrvtest_la_LIBADD += $(top_builddir)/src/lib/log/libkea-log.la
+
+endif
diff --git a/src/lib/dhcpsrv/testutils/Makefile.in b/src/lib/dhcpsrv/testutils/Makefile.in
new file mode 100644
index 0000000..71d5fd7
--- /dev/null
+++ b/src/lib/dhcpsrv/testutils/Makefile.in
@@ -0,0 +1,1065 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+@HAVE_GTEST_TRUE@@HAVE_MYSQL_TRUE@am__append_1 = mysql_generic_backend_unittest.cc mysql_generic_backend_unittest.h
+@HAVE_GTEST_TRUE@@HAVE_PGSQL_TRUE@am__append_2 = pgsql_generic_backend_unittest.cc pgsql_generic_backend_unittest.h
+@HAVE_GTEST_TRUE@@HAVE_MYSQL_TRUE@am__append_3 = $(MYSQL_CPPFLAGS)
+@HAVE_GTEST_TRUE@@HAVE_MYSQL_TRUE@am__append_4 = $(top_builddir)/src/lib/mysql/libkea-mysql.la
+@HAVE_GTEST_TRUE@@HAVE_PGSQL_TRUE@am__append_5 = $(PGSQL_CPPFLAGS)
+@HAVE_GTEST_TRUE@@HAVE_PGSQL_TRUE@am__append_6 = $(top_builddir)/src/lib/pgsql/libkea-pgsql.la
+@HAVE_GTEST_TRUE@@HAVE_MYSQL_TRUE@am__append_7 = $(MYSQL_LIBS)
+@HAVE_GTEST_TRUE@@HAVE_PGSQL_TRUE@am__append_8 = $(PGSQL_LIBS)
+subdir = src/lib/dhcpsrv/testutils
+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 =
+CONFIG_CLEAN_VPATH_FILES =
+LTLIBRARIES = $(noinst_LTLIBRARIES)
+@HAVE_GTEST_TRUE@libdhcpsrvtest_la_DEPENDENCIES = $(am__append_4) \
+@HAVE_GTEST_TRUE@ $(am__append_6) \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/database/libkea-database.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cc/libkea-cc.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/log/libkea-log.la
+am__libdhcpsrvtest_la_SOURCES_DIST = config_result_check.cc \
+ config_result_check.h dhcp4o6_test_ipc.cc dhcp4o6_test_ipc.h \
+ host_data_source_utils.cc host_data_source_utils.h \
+ memory_host_data_source.cc memory_host_data_source.h \
+ test_utils.cc test_utils.h generic_backend_unittest.cc \
+ generic_backend_unittest.h \
+ generic_host_data_source_unittest.cc \
+ generic_host_data_source_unittest.h \
+ generic_cb_dhcp4_unittest.h generic_cb_dhcp4_unittest.cc \
+ generic_cb_dhcp6_unittest.h generic_cb_dhcp6_unittest.cc \
+ generic_cb_recovery_unittest.h generic_cb_recovery_unittest.cc \
+ lease_file_io.cc lease_file_io.h test_config_backend.h \
+ test_config_backend_dhcp4.cc test_config_backend_dhcp4.h \
+ test_config_backend_dhcp6.cc test_config_backend_dhcp6.h \
+ mysql_generic_backend_unittest.cc \
+ mysql_generic_backend_unittest.h \
+ pgsql_generic_backend_unittest.cc \
+ pgsql_generic_backend_unittest.h
+@HAVE_GTEST_TRUE@@HAVE_MYSQL_TRUE@am__objects_1 = libdhcpsrvtest_la-mysql_generic_backend_unittest.lo
+@HAVE_GTEST_TRUE@@HAVE_PGSQL_TRUE@am__objects_2 = libdhcpsrvtest_la-pgsql_generic_backend_unittest.lo
+@HAVE_GTEST_TRUE@am_libdhcpsrvtest_la_OBJECTS = \
+@HAVE_GTEST_TRUE@ libdhcpsrvtest_la-config_result_check.lo \
+@HAVE_GTEST_TRUE@ libdhcpsrvtest_la-dhcp4o6_test_ipc.lo \
+@HAVE_GTEST_TRUE@ libdhcpsrvtest_la-host_data_source_utils.lo \
+@HAVE_GTEST_TRUE@ libdhcpsrvtest_la-memory_host_data_source.lo \
+@HAVE_GTEST_TRUE@ libdhcpsrvtest_la-test_utils.lo \
+@HAVE_GTEST_TRUE@ libdhcpsrvtest_la-generic_backend_unittest.lo \
+@HAVE_GTEST_TRUE@ libdhcpsrvtest_la-generic_host_data_source_unittest.lo \
+@HAVE_GTEST_TRUE@ libdhcpsrvtest_la-generic_cb_dhcp4_unittest.lo \
+@HAVE_GTEST_TRUE@ libdhcpsrvtest_la-generic_cb_dhcp6_unittest.lo \
+@HAVE_GTEST_TRUE@ libdhcpsrvtest_la-generic_cb_recovery_unittest.lo \
+@HAVE_GTEST_TRUE@ libdhcpsrvtest_la-lease_file_io.lo \
+@HAVE_GTEST_TRUE@ libdhcpsrvtest_la-test_config_backend_dhcp4.lo \
+@HAVE_GTEST_TRUE@ libdhcpsrvtest_la-test_config_backend_dhcp6.lo \
+@HAVE_GTEST_TRUE@ $(am__objects_1) $(am__objects_2)
+libdhcpsrvtest_la_OBJECTS = $(am_libdhcpsrvtest_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 =
+libdhcpsrvtest_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
+ $(libdhcpsrvtest_la_CXXFLAGS) $(CXXFLAGS) \
+ $(libdhcpsrvtest_la_LDFLAGS) $(LDFLAGS) -o $@
+@HAVE_GTEST_TRUE@am_libdhcpsrvtest_la_rpath =
+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)/libdhcpsrvtest_la-config_result_check.Plo \
+ ./$(DEPDIR)/libdhcpsrvtest_la-dhcp4o6_test_ipc.Plo \
+ ./$(DEPDIR)/libdhcpsrvtest_la-generic_backend_unittest.Plo \
+ ./$(DEPDIR)/libdhcpsrvtest_la-generic_cb_dhcp4_unittest.Plo \
+ ./$(DEPDIR)/libdhcpsrvtest_la-generic_cb_dhcp6_unittest.Plo \
+ ./$(DEPDIR)/libdhcpsrvtest_la-generic_cb_recovery_unittest.Plo \
+ ./$(DEPDIR)/libdhcpsrvtest_la-generic_host_data_source_unittest.Plo \
+ ./$(DEPDIR)/libdhcpsrvtest_la-host_data_source_utils.Plo \
+ ./$(DEPDIR)/libdhcpsrvtest_la-lease_file_io.Plo \
+ ./$(DEPDIR)/libdhcpsrvtest_la-memory_host_data_source.Plo \
+ ./$(DEPDIR)/libdhcpsrvtest_la-mysql_generic_backend_unittest.Plo \
+ ./$(DEPDIR)/libdhcpsrvtest_la-pgsql_generic_backend_unittest.Plo \
+ ./$(DEPDIR)/libdhcpsrvtest_la-test_config_backend_dhcp4.Plo \
+ ./$(DEPDIR)/libdhcpsrvtest_la-test_config_backend_dhcp6.Plo \
+ ./$(DEPDIR)/libdhcpsrvtest_la-test_utils.Plo
+am__mv = mv -f
+CXXCOMPILE = $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \
+ $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS)
+LTCXXCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CXXFLAGS) $(CXXFLAGS)
+AM_V_CXX = $(am__v_CXX_@AM_V@)
+am__v_CXX_ = $(am__v_CXX_@AM_DEFAULT_V@)
+am__v_CXX_0 = @echo " CXX " $@;
+am__v_CXX_1 =
+CXXLD = $(CXX)
+CXXLINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(AM_CXXFLAGS) \
+ $(CXXFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CXXLD = $(am__v_CXXLD_@AM_V@)
+am__v_CXXLD_ = $(am__v_CXXLD_@AM_DEFAULT_V@)
+am__v_CXXLD_0 = @echo " CXXLD " $@;
+am__v_CXXLD_1 =
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libdhcpsrvtest_la_SOURCES)
+DIST_SOURCES = $(am__libdhcpsrvtest_la_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
+DIST_SUBDIRS = $(SUBDIRS)
+am__DIST_COMMON = $(srcdir)/Makefile.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 = .
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib \
+ -DDATABASE_SCRIPTS_DIR=\"$(abs_top_srcdir)/src/share/database/scripts\" \
+ $(BOOST_INCLUDES)
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+CLEANFILES = *.gcno *.gcda
+@HAVE_GTEST_TRUE@noinst_LTLIBRARIES = libdhcpsrvtest.la
+@HAVE_GTEST_TRUE@libdhcpsrvtest_la_SOURCES = config_result_check.cc \
+@HAVE_GTEST_TRUE@ config_result_check.h dhcp4o6_test_ipc.cc \
+@HAVE_GTEST_TRUE@ dhcp4o6_test_ipc.h host_data_source_utils.cc \
+@HAVE_GTEST_TRUE@ host_data_source_utils.h \
+@HAVE_GTEST_TRUE@ memory_host_data_source.cc \
+@HAVE_GTEST_TRUE@ memory_host_data_source.h test_utils.cc \
+@HAVE_GTEST_TRUE@ test_utils.h generic_backend_unittest.cc \
+@HAVE_GTEST_TRUE@ generic_backend_unittest.h \
+@HAVE_GTEST_TRUE@ generic_host_data_source_unittest.cc \
+@HAVE_GTEST_TRUE@ generic_host_data_source_unittest.h \
+@HAVE_GTEST_TRUE@ generic_cb_dhcp4_unittest.h \
+@HAVE_GTEST_TRUE@ generic_cb_dhcp4_unittest.cc \
+@HAVE_GTEST_TRUE@ generic_cb_dhcp6_unittest.h \
+@HAVE_GTEST_TRUE@ generic_cb_dhcp6_unittest.cc \
+@HAVE_GTEST_TRUE@ generic_cb_recovery_unittest.h \
+@HAVE_GTEST_TRUE@ generic_cb_recovery_unittest.cc \
+@HAVE_GTEST_TRUE@ lease_file_io.cc lease_file_io.h \
+@HAVE_GTEST_TRUE@ test_config_backend.h \
+@HAVE_GTEST_TRUE@ test_config_backend_dhcp4.cc \
+@HAVE_GTEST_TRUE@ test_config_backend_dhcp4.h \
+@HAVE_GTEST_TRUE@ test_config_backend_dhcp6.cc \
+@HAVE_GTEST_TRUE@ test_config_backend_dhcp6.h $(am__append_1) \
+@HAVE_GTEST_TRUE@ $(am__append_2)
+@HAVE_GTEST_TRUE@libdhcpsrvtest_la_CXXFLAGS = $(AM_CXXFLAGS)
+@HAVE_GTEST_TRUE@libdhcpsrvtest_la_CPPFLAGS = $(AM_CPPFLAGS) \
+@HAVE_GTEST_TRUE@ $(GTEST_INCLUDES) $(am__append_3) \
+@HAVE_GTEST_TRUE@ $(am__append_5)
+@HAVE_GTEST_TRUE@libdhcpsrvtest_la_LIBADD = $(am__append_4) \
+@HAVE_GTEST_TRUE@ $(am__append_6) \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/database/libkea-database.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cc/libkea-cc.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/log/libkea-log.la
+@HAVE_GTEST_TRUE@libdhcpsrvtest_la_LDFLAGS = $(AM_LDFLAGS) \
+@HAVE_GTEST_TRUE@ $(am__append_7) $(am__append_8)
+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/lib/dhcpsrv/testutils/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib/dhcpsrv/testutils/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):
+
+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}; \
+ }
+
+libdhcpsrvtest.la: $(libdhcpsrvtest_la_OBJECTS) $(libdhcpsrvtest_la_DEPENDENCIES) $(EXTRA_libdhcpsrvtest_la_DEPENDENCIES)
+ $(AM_V_CXXLD)$(libdhcpsrvtest_la_LINK) $(am_libdhcpsrvtest_la_rpath) $(libdhcpsrvtest_la_OBJECTS) $(libdhcpsrvtest_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrvtest_la-config_result_check.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrvtest_la-dhcp4o6_test_ipc.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrvtest_la-generic_backend_unittest.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrvtest_la-generic_cb_dhcp4_unittest.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrvtest_la-generic_cb_dhcp6_unittest.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrvtest_la-generic_cb_recovery_unittest.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrvtest_la-generic_host_data_source_unittest.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrvtest_la-host_data_source_utils.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrvtest_la-lease_file_io.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrvtest_la-memory_host_data_source.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrvtest_la-mysql_generic_backend_unittest.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrvtest_la-pgsql_generic_backend_unittest.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrvtest_la-test_config_backend_dhcp4.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrvtest_la-test_config_backend_dhcp6.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrvtest_la-test_utils.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.cc.o:
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ $<
+
+.cc.obj:
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.cc.lo:
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LTCXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LTCXXCOMPILE) -c -o $@ $<
+
+libdhcpsrvtest_la-config_result_check.lo: config_result_check.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrvtest_la_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrvtest_la_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrvtest_la-config_result_check.lo -MD -MP -MF $(DEPDIR)/libdhcpsrvtest_la-config_result_check.Tpo -c -o libdhcpsrvtest_la-config_result_check.lo `test -f 'config_result_check.cc' || echo '$(srcdir)/'`config_result_check.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrvtest_la-config_result_check.Tpo $(DEPDIR)/libdhcpsrvtest_la-config_result_check.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='config_result_check.cc' object='libdhcpsrvtest_la-config_result_check.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) $(libdhcpsrvtest_la_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrvtest_la_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrvtest_la-config_result_check.lo `test -f 'config_result_check.cc' || echo '$(srcdir)/'`config_result_check.cc
+
+libdhcpsrvtest_la-dhcp4o6_test_ipc.lo: dhcp4o6_test_ipc.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrvtest_la_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrvtest_la_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrvtest_la-dhcp4o6_test_ipc.lo -MD -MP -MF $(DEPDIR)/libdhcpsrvtest_la-dhcp4o6_test_ipc.Tpo -c -o libdhcpsrvtest_la-dhcp4o6_test_ipc.lo `test -f 'dhcp4o6_test_ipc.cc' || echo '$(srcdir)/'`dhcp4o6_test_ipc.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrvtest_la-dhcp4o6_test_ipc.Tpo $(DEPDIR)/libdhcpsrvtest_la-dhcp4o6_test_ipc.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='dhcp4o6_test_ipc.cc' object='libdhcpsrvtest_la-dhcp4o6_test_ipc.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) $(libdhcpsrvtest_la_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrvtest_la_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrvtest_la-dhcp4o6_test_ipc.lo `test -f 'dhcp4o6_test_ipc.cc' || echo '$(srcdir)/'`dhcp4o6_test_ipc.cc
+
+libdhcpsrvtest_la-host_data_source_utils.lo: host_data_source_utils.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrvtest_la_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrvtest_la_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrvtest_la-host_data_source_utils.lo -MD -MP -MF $(DEPDIR)/libdhcpsrvtest_la-host_data_source_utils.Tpo -c -o libdhcpsrvtest_la-host_data_source_utils.lo `test -f 'host_data_source_utils.cc' || echo '$(srcdir)/'`host_data_source_utils.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrvtest_la-host_data_source_utils.Tpo $(DEPDIR)/libdhcpsrvtest_la-host_data_source_utils.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='host_data_source_utils.cc' object='libdhcpsrvtest_la-host_data_source_utils.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) $(libdhcpsrvtest_la_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrvtest_la_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrvtest_la-host_data_source_utils.lo `test -f 'host_data_source_utils.cc' || echo '$(srcdir)/'`host_data_source_utils.cc
+
+libdhcpsrvtest_la-memory_host_data_source.lo: memory_host_data_source.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrvtest_la_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrvtest_la_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrvtest_la-memory_host_data_source.lo -MD -MP -MF $(DEPDIR)/libdhcpsrvtest_la-memory_host_data_source.Tpo -c -o libdhcpsrvtest_la-memory_host_data_source.lo `test -f 'memory_host_data_source.cc' || echo '$(srcdir)/'`memory_host_data_source.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrvtest_la-memory_host_data_source.Tpo $(DEPDIR)/libdhcpsrvtest_la-memory_host_data_source.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='memory_host_data_source.cc' object='libdhcpsrvtest_la-memory_host_data_source.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) $(libdhcpsrvtest_la_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrvtest_la_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrvtest_la-memory_host_data_source.lo `test -f 'memory_host_data_source.cc' || echo '$(srcdir)/'`memory_host_data_source.cc
+
+libdhcpsrvtest_la-test_utils.lo: test_utils.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrvtest_la_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrvtest_la_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrvtest_la-test_utils.lo -MD -MP -MF $(DEPDIR)/libdhcpsrvtest_la-test_utils.Tpo -c -o libdhcpsrvtest_la-test_utils.lo `test -f 'test_utils.cc' || echo '$(srcdir)/'`test_utils.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrvtest_la-test_utils.Tpo $(DEPDIR)/libdhcpsrvtest_la-test_utils.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='test_utils.cc' object='libdhcpsrvtest_la-test_utils.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) $(libdhcpsrvtest_la_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrvtest_la_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrvtest_la-test_utils.lo `test -f 'test_utils.cc' || echo '$(srcdir)/'`test_utils.cc
+
+libdhcpsrvtest_la-generic_backend_unittest.lo: generic_backend_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrvtest_la_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrvtest_la_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrvtest_la-generic_backend_unittest.lo -MD -MP -MF $(DEPDIR)/libdhcpsrvtest_la-generic_backend_unittest.Tpo -c -o libdhcpsrvtest_la-generic_backend_unittest.lo `test -f 'generic_backend_unittest.cc' || echo '$(srcdir)/'`generic_backend_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrvtest_la-generic_backend_unittest.Tpo $(DEPDIR)/libdhcpsrvtest_la-generic_backend_unittest.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='generic_backend_unittest.cc' object='libdhcpsrvtest_la-generic_backend_unittest.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) $(libdhcpsrvtest_la_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrvtest_la_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrvtest_la-generic_backend_unittest.lo `test -f 'generic_backend_unittest.cc' || echo '$(srcdir)/'`generic_backend_unittest.cc
+
+libdhcpsrvtest_la-generic_host_data_source_unittest.lo: generic_host_data_source_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrvtest_la_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrvtest_la_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrvtest_la-generic_host_data_source_unittest.lo -MD -MP -MF $(DEPDIR)/libdhcpsrvtest_la-generic_host_data_source_unittest.Tpo -c -o libdhcpsrvtest_la-generic_host_data_source_unittest.lo `test -f 'generic_host_data_source_unittest.cc' || echo '$(srcdir)/'`generic_host_data_source_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrvtest_la-generic_host_data_source_unittest.Tpo $(DEPDIR)/libdhcpsrvtest_la-generic_host_data_source_unittest.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='generic_host_data_source_unittest.cc' object='libdhcpsrvtest_la-generic_host_data_source_unittest.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) $(libdhcpsrvtest_la_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrvtest_la_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrvtest_la-generic_host_data_source_unittest.lo `test -f 'generic_host_data_source_unittest.cc' || echo '$(srcdir)/'`generic_host_data_source_unittest.cc
+
+libdhcpsrvtest_la-generic_cb_dhcp4_unittest.lo: generic_cb_dhcp4_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrvtest_la_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrvtest_la_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrvtest_la-generic_cb_dhcp4_unittest.lo -MD -MP -MF $(DEPDIR)/libdhcpsrvtest_la-generic_cb_dhcp4_unittest.Tpo -c -o libdhcpsrvtest_la-generic_cb_dhcp4_unittest.lo `test -f 'generic_cb_dhcp4_unittest.cc' || echo '$(srcdir)/'`generic_cb_dhcp4_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrvtest_la-generic_cb_dhcp4_unittest.Tpo $(DEPDIR)/libdhcpsrvtest_la-generic_cb_dhcp4_unittest.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='generic_cb_dhcp4_unittest.cc' object='libdhcpsrvtest_la-generic_cb_dhcp4_unittest.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) $(libdhcpsrvtest_la_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrvtest_la_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrvtest_la-generic_cb_dhcp4_unittest.lo `test -f 'generic_cb_dhcp4_unittest.cc' || echo '$(srcdir)/'`generic_cb_dhcp4_unittest.cc
+
+libdhcpsrvtest_la-generic_cb_dhcp6_unittest.lo: generic_cb_dhcp6_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrvtest_la_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrvtest_la_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrvtest_la-generic_cb_dhcp6_unittest.lo -MD -MP -MF $(DEPDIR)/libdhcpsrvtest_la-generic_cb_dhcp6_unittest.Tpo -c -o libdhcpsrvtest_la-generic_cb_dhcp6_unittest.lo `test -f 'generic_cb_dhcp6_unittest.cc' || echo '$(srcdir)/'`generic_cb_dhcp6_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrvtest_la-generic_cb_dhcp6_unittest.Tpo $(DEPDIR)/libdhcpsrvtest_la-generic_cb_dhcp6_unittest.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='generic_cb_dhcp6_unittest.cc' object='libdhcpsrvtest_la-generic_cb_dhcp6_unittest.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) $(libdhcpsrvtest_la_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrvtest_la_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrvtest_la-generic_cb_dhcp6_unittest.lo `test -f 'generic_cb_dhcp6_unittest.cc' || echo '$(srcdir)/'`generic_cb_dhcp6_unittest.cc
+
+libdhcpsrvtest_la-generic_cb_recovery_unittest.lo: generic_cb_recovery_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrvtest_la_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrvtest_la_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrvtest_la-generic_cb_recovery_unittest.lo -MD -MP -MF $(DEPDIR)/libdhcpsrvtest_la-generic_cb_recovery_unittest.Tpo -c -o libdhcpsrvtest_la-generic_cb_recovery_unittest.lo `test -f 'generic_cb_recovery_unittest.cc' || echo '$(srcdir)/'`generic_cb_recovery_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrvtest_la-generic_cb_recovery_unittest.Tpo $(DEPDIR)/libdhcpsrvtest_la-generic_cb_recovery_unittest.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='generic_cb_recovery_unittest.cc' object='libdhcpsrvtest_la-generic_cb_recovery_unittest.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) $(libdhcpsrvtest_la_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrvtest_la_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrvtest_la-generic_cb_recovery_unittest.lo `test -f 'generic_cb_recovery_unittest.cc' || echo '$(srcdir)/'`generic_cb_recovery_unittest.cc
+
+libdhcpsrvtest_la-lease_file_io.lo: lease_file_io.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrvtest_la_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrvtest_la_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrvtest_la-lease_file_io.lo -MD -MP -MF $(DEPDIR)/libdhcpsrvtest_la-lease_file_io.Tpo -c -o libdhcpsrvtest_la-lease_file_io.lo `test -f 'lease_file_io.cc' || echo '$(srcdir)/'`lease_file_io.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrvtest_la-lease_file_io.Tpo $(DEPDIR)/libdhcpsrvtest_la-lease_file_io.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='lease_file_io.cc' object='libdhcpsrvtest_la-lease_file_io.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) $(libdhcpsrvtest_la_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrvtest_la_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrvtest_la-lease_file_io.lo `test -f 'lease_file_io.cc' || echo '$(srcdir)/'`lease_file_io.cc
+
+libdhcpsrvtest_la-test_config_backend_dhcp4.lo: test_config_backend_dhcp4.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrvtest_la_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrvtest_la_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrvtest_la-test_config_backend_dhcp4.lo -MD -MP -MF $(DEPDIR)/libdhcpsrvtest_la-test_config_backend_dhcp4.Tpo -c -o libdhcpsrvtest_la-test_config_backend_dhcp4.lo `test -f 'test_config_backend_dhcp4.cc' || echo '$(srcdir)/'`test_config_backend_dhcp4.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrvtest_la-test_config_backend_dhcp4.Tpo $(DEPDIR)/libdhcpsrvtest_la-test_config_backend_dhcp4.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='test_config_backend_dhcp4.cc' object='libdhcpsrvtest_la-test_config_backend_dhcp4.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) $(libdhcpsrvtest_la_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrvtest_la_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrvtest_la-test_config_backend_dhcp4.lo `test -f 'test_config_backend_dhcp4.cc' || echo '$(srcdir)/'`test_config_backend_dhcp4.cc
+
+libdhcpsrvtest_la-test_config_backend_dhcp6.lo: test_config_backend_dhcp6.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrvtest_la_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrvtest_la_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrvtest_la-test_config_backend_dhcp6.lo -MD -MP -MF $(DEPDIR)/libdhcpsrvtest_la-test_config_backend_dhcp6.Tpo -c -o libdhcpsrvtest_la-test_config_backend_dhcp6.lo `test -f 'test_config_backend_dhcp6.cc' || echo '$(srcdir)/'`test_config_backend_dhcp6.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrvtest_la-test_config_backend_dhcp6.Tpo $(DEPDIR)/libdhcpsrvtest_la-test_config_backend_dhcp6.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='test_config_backend_dhcp6.cc' object='libdhcpsrvtest_la-test_config_backend_dhcp6.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) $(libdhcpsrvtest_la_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrvtest_la_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrvtest_la-test_config_backend_dhcp6.lo `test -f 'test_config_backend_dhcp6.cc' || echo '$(srcdir)/'`test_config_backend_dhcp6.cc
+
+libdhcpsrvtest_la-mysql_generic_backend_unittest.lo: mysql_generic_backend_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrvtest_la_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrvtest_la_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrvtest_la-mysql_generic_backend_unittest.lo -MD -MP -MF $(DEPDIR)/libdhcpsrvtest_la-mysql_generic_backend_unittest.Tpo -c -o libdhcpsrvtest_la-mysql_generic_backend_unittest.lo `test -f 'mysql_generic_backend_unittest.cc' || echo '$(srcdir)/'`mysql_generic_backend_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrvtest_la-mysql_generic_backend_unittest.Tpo $(DEPDIR)/libdhcpsrvtest_la-mysql_generic_backend_unittest.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='mysql_generic_backend_unittest.cc' object='libdhcpsrvtest_la-mysql_generic_backend_unittest.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) $(libdhcpsrvtest_la_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrvtest_la_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrvtest_la-mysql_generic_backend_unittest.lo `test -f 'mysql_generic_backend_unittest.cc' || echo '$(srcdir)/'`mysql_generic_backend_unittest.cc
+
+libdhcpsrvtest_la-pgsql_generic_backend_unittest.lo: pgsql_generic_backend_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrvtest_la_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrvtest_la_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrvtest_la-pgsql_generic_backend_unittest.lo -MD -MP -MF $(DEPDIR)/libdhcpsrvtest_la-pgsql_generic_backend_unittest.Tpo -c -o libdhcpsrvtest_la-pgsql_generic_backend_unittest.lo `test -f 'pgsql_generic_backend_unittest.cc' || echo '$(srcdir)/'`pgsql_generic_backend_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrvtest_la-pgsql_generic_backend_unittest.Tpo $(DEPDIR)/libdhcpsrvtest_la-pgsql_generic_backend_unittest.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pgsql_generic_backend_unittest.cc' object='libdhcpsrvtest_la-pgsql_generic_backend_unittest.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) $(libdhcpsrvtest_la_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrvtest_la_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrvtest_la-pgsql_generic_backend_unittest.lo `test -f 'pgsql_generic_backend_unittest.cc' || echo '$(srcdir)/'`pgsql_generic_backend_unittest.cc
+
+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
+
+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
+check: check-recursive
+all-am: Makefile $(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)
+
+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 \
+ mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -f ./$(DEPDIR)/libdhcpsrvtest_la-config_result_check.Plo
+ -rm -f ./$(DEPDIR)/libdhcpsrvtest_la-dhcp4o6_test_ipc.Plo
+ -rm -f ./$(DEPDIR)/libdhcpsrvtest_la-generic_backend_unittest.Plo
+ -rm -f ./$(DEPDIR)/libdhcpsrvtest_la-generic_cb_dhcp4_unittest.Plo
+ -rm -f ./$(DEPDIR)/libdhcpsrvtest_la-generic_cb_dhcp6_unittest.Plo
+ -rm -f ./$(DEPDIR)/libdhcpsrvtest_la-generic_cb_recovery_unittest.Plo
+ -rm -f ./$(DEPDIR)/libdhcpsrvtest_la-generic_host_data_source_unittest.Plo
+ -rm -f ./$(DEPDIR)/libdhcpsrvtest_la-host_data_source_utils.Plo
+ -rm -f ./$(DEPDIR)/libdhcpsrvtest_la-lease_file_io.Plo
+ -rm -f ./$(DEPDIR)/libdhcpsrvtest_la-memory_host_data_source.Plo
+ -rm -f ./$(DEPDIR)/libdhcpsrvtest_la-mysql_generic_backend_unittest.Plo
+ -rm -f ./$(DEPDIR)/libdhcpsrvtest_la-pgsql_generic_backend_unittest.Plo
+ -rm -f ./$(DEPDIR)/libdhcpsrvtest_la-test_config_backend_dhcp4.Plo
+ -rm -f ./$(DEPDIR)/libdhcpsrvtest_la-test_config_backend_dhcp6.Plo
+ -rm -f ./$(DEPDIR)/libdhcpsrvtest_la-test_utils.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-recursive
+
+dvi-am:
+
+html: html-recursive
+
+html-am:
+
+info: info-recursive
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-recursive
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-recursive
+
+install-html-am:
+
+install-info: install-info-recursive
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-recursive
+
+install-pdf-am:
+
+install-ps: install-ps-recursive
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-recursive
+ -rm -f ./$(DEPDIR)/libdhcpsrvtest_la-config_result_check.Plo
+ -rm -f ./$(DEPDIR)/libdhcpsrvtest_la-dhcp4o6_test_ipc.Plo
+ -rm -f ./$(DEPDIR)/libdhcpsrvtest_la-generic_backend_unittest.Plo
+ -rm -f ./$(DEPDIR)/libdhcpsrvtest_la-generic_cb_dhcp4_unittest.Plo
+ -rm -f ./$(DEPDIR)/libdhcpsrvtest_la-generic_cb_dhcp6_unittest.Plo
+ -rm -f ./$(DEPDIR)/libdhcpsrvtest_la-generic_cb_recovery_unittest.Plo
+ -rm -f ./$(DEPDIR)/libdhcpsrvtest_la-generic_host_data_source_unittest.Plo
+ -rm -f ./$(DEPDIR)/libdhcpsrvtest_la-host_data_source_utils.Plo
+ -rm -f ./$(DEPDIR)/libdhcpsrvtest_la-lease_file_io.Plo
+ -rm -f ./$(DEPDIR)/libdhcpsrvtest_la-memory_host_data_source.Plo
+ -rm -f ./$(DEPDIR)/libdhcpsrvtest_la-mysql_generic_backend_unittest.Plo
+ -rm -f ./$(DEPDIR)/libdhcpsrvtest_la-pgsql_generic_backend_unittest.Plo
+ -rm -f ./$(DEPDIR)/libdhcpsrvtest_la-test_config_backend_dhcp4.Plo
+ -rm -f ./$(DEPDIR)/libdhcpsrvtest_la-test_config_backend_dhcp6.Plo
+ -rm -f ./$(DEPDIR)/libdhcpsrvtest_la-test_utils.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-recursive
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-recursive
+
+pdf-am:
+
+ps: ps-recursive
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: $(am__recursive_targets) install-am install-strip
+
+.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am \
+ am--depfiles check check-am clean clean-generic clean-libtool \
+ clean-noinstLTLIBRARIES 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/lib/dhcpsrv/testutils/config_result_check.cc b/src/lib/dhcpsrv/testutils/config_result_check.cc
new file mode 100644
index 0000000..35b31fe
--- /dev/null
+++ b/src/lib/dhcpsrv/testutils/config_result_check.cc
@@ -0,0 +1,89 @@
+// Copyright (C) 2014-2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <cc/command_interpreter.h>
+#include <dhcpsrv/testutils/config_result_check.h>
+#include <boost/algorithm/string/classification.hpp>
+#include <boost/algorithm/string/constants.hpp>
+#include <boost/algorithm/string/split.hpp>
+#include <string>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+using namespace isc;
+using namespace isc::data;
+
+bool errorContainsPosition(ConstElementPtr error_element,
+ const std::string& file_name) {
+ if (error_element->contains(isc::config::CONTROL_RESULT)) {
+ ConstElementPtr result = error_element->get(isc::config::CONTROL_RESULT);
+ ConstElementPtr text = error_element->get(isc::config::CONTROL_TEXT);
+ if (!result || (result->getType() != Element::integer) || !text
+ || (text->getType() != Element::string)) {
+ return (false);
+ }
+
+ // Get the error message in the textual format.
+ std::string error_string = text->stringValue();
+
+ // The position of the data element causing an error has the following
+ // format: <filename>:<linenum>:<pos>. The <filename> has been specified
+ // by a caller, so let's first check if this file name is present in the
+ // error message.
+ size_t pos = error_string.find(file_name);
+
+ // If the file name is present, check that it is followed by the line
+ // number and position within the line.
+ if (pos != std::string::npos) {
+ // Split the string starting at the beginning of the <filename>. It
+ // should return a vector of strings.
+ std::string sub = error_string.substr(pos);
+ std::vector<std::string> split_pos;
+ boost::split(split_pos, sub, boost::is_any_of(":"),
+ boost::algorithm::token_compress_off);
+
+ // There should be at least three elements: <filename>, <linenum>
+ // and <pos>. There can be even more, because one error string may
+ // contain more positions of data elements for multiple
+ // configuration nesting levels. We want at least one position.
+ if ((split_pos.size() >= 3) && (split_pos[0] == file_name) &&
+ (!split_pos[1].empty()) && !(split_pos[2].empty())) {
+
+ // Make sure that the line number comprises only digits.
+ for (int i = 0; i < split_pos[1].size(); ++i) {
+ if (!isdigit(split_pos[1][i])) {
+ return (false);
+ }
+ }
+
+ // Go over digits of the position within the line.
+ int i = 0;
+ while (isdigit(split_pos[2][i])) {
+ ++i;
+ }
+
+ // Make sure that there has been at least one digit and that the
+ // position is followed by the paren.
+ if ((i == 0) || (split_pos[2][i] != ')')) {
+ return (false);
+ }
+
+ // All checks passed.
+ return (true);
+ }
+ }
+ }
+
+ return (false);
+}
+
+}
+}
+}
diff --git a/src/lib/dhcpsrv/testutils/config_result_check.h b/src/lib/dhcpsrv/testutils/config_result_check.h
new file mode 100644
index 0000000..29d4477
--- /dev/null
+++ b/src/lib/dhcpsrv/testutils/config_result_check.h
@@ -0,0 +1,48 @@
+// Copyright (C) 2014-2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef CONFIG_RESULT_CHECK_H
+#define CONFIG_RESULT_CHECK_H
+
+#include <cc/data.h>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+/// @brief Checks that the error string created by the configuration parsers
+/// contains the location of the data element...
+///
+/// This function checks that the error string returned by the configuration
+/// parsers contains the position of the element which caused an error. The
+/// error string is expected to contain at least one occurrence of the following:
+///
+/// @code
+/// [filename]:[linenum]:[pos]
+/// @endcode
+///
+/// where:
+/// - [filename] is a configuration file name (provided by a caller),
+/// - [linenum] is a line number of the element,
+/// - [pos] is a position of the element within the line.
+///
+/// Both [linenum] and [pos] must contain decimal digits. The [filename]
+/// must match the name provided by the caller.
+///
+/// @param error_element A result returned by the configuration.
+/// @param file_name A configuration file name.
+///
+/// @return true if the provided configuration result comprises a string
+/// which holds a position of the data element which caused the error;
+/// false otherwise.
+bool errorContainsPosition(isc::data::ConstElementPtr error_element,
+ const std::string& file_name);
+
+}
+}
+}
+
+#endif
diff --git a/src/lib/dhcpsrv/testutils/dhcp4o6_test_ipc.cc b/src/lib/dhcpsrv/testutils/dhcp4o6_test_ipc.cc
new file mode 100644
index 0000000..a50a5c2
--- /dev/null
+++ b/src/lib/dhcpsrv/testutils/dhcp4o6_test_ipc.cc
@@ -0,0 +1,41 @@
+// Copyright (C) 2015-2020 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <dhcp/iface_mgr.h>
+#include <dhcpsrv/testutils/dhcp4o6_test_ipc.h>
+#include <functional>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+Dhcp4o6TestIpc::Dhcp4o6TestIpc(uint16_t port, EndpointType endpoint_type)
+ : desired_port_(port), endpoint_type_(endpoint_type), pkt_received_() {
+}
+
+void
+Dhcp4o6TestIpc::open() {
+ // Use the base IPC to open the socket.
+ socket_fd_ = Dhcp4o6IpcBase::open(desired_port_, endpoint_type_);
+ // If the socket has been opened correctly, register it in the @c IfaceMgr.
+ // BTW if it has not an exception has been thrown so it is only
+ // a sanity / recommended check.
+ if (socket_fd_ != -1) {
+ IfaceMgr& iface_mgr = IfaceMgr::instance();
+ iface_mgr.addExternalSocket(socket_fd_,
+ std::bind(&Dhcp4o6TestIpc::receiveHandler, this));
+ }
+}
+
+void
+Dhcp4o6TestIpc::receiveHandler() {
+ pkt_received_ = receive();
+}
+
+} // end of isc::dhcp::test namespace
+} // end of isc::dhcp namespace
+} // end of isc namespace
diff --git a/src/lib/dhcpsrv/testutils/dhcp4o6_test_ipc.h b/src/lib/dhcpsrv/testutils/dhcp4o6_test_ipc.h
new file mode 100644
index 0000000..5109de0
--- /dev/null
+++ b/src/lib/dhcpsrv/testutils/dhcp4o6_test_ipc.h
@@ -0,0 +1,90 @@
+// Copyright (C) 2015-2016 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 DHCP4O6_TEST_IPC_H
+#define DHCP4O6_TEST_IPC_H
+
+#include <asiolink/io_address.h>
+#include <dhcp/iface_mgr.h>
+#include <dhcp/pkt6.h>
+#include <dhcpsrv/dhcp4o6_ipc.h>
+#include <boost/noncopyable.hpp>
+#include <stdint.h>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+/// @brief Implements a simple IPC for the test.
+class Dhcp4o6TestIpc : public Dhcp4o6IpcBase {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// @param port Desired port.
+ /// @param endpoint_type Type of the IPC endpoint. It should be 4 or 6.
+ Dhcp4o6TestIpc(uint16_t port, EndpointType endpoint_type);
+
+ /// @brief Sets new port to be used with @c open.
+ ///
+ /// @param desired_port New desired port.
+ void setDesiredPort(uint16_t desired_port) {
+ desired_port_ = desired_port;
+ }
+
+ /// @brief Opens the IPC socket and registers it in @c IfaceMgr.
+ ///
+ /// This method opens the IPC socket and registers it as external
+ /// socket in the IfaceMgr. The @c TestIpc::receiveHandler is used as a
+ /// callback to be called by the @c IfaceMgr when the data is received
+ /// over the socket.
+ virtual void open();
+
+ /// @brief Retrieve port which socket is bound to.
+ uint16_t getPort() const {
+ return (port_);
+ }
+
+ /// @brief Retrieve socket descriptor.
+ int getSocketFd() const {
+ return (socket_fd_);
+ }
+
+ /// @brief Pops and returns a received message.
+ ///
+ /// @return Pointer to the received message over the IPC.
+ Pkt6Ptr popPktReceived() {
+ // Copy the received message.
+ Pkt6Ptr pkt_copy(pkt_received_);
+ // Set the received message to NULL (pop).
+ pkt_received_.reset();
+ // Return the copy.
+ return (pkt_copy);
+ }
+
+private:
+
+ /// @brief Callback for the IPC socket.
+ ///
+ /// This callback is called by the @c IfaceMgr when the data is received
+ /// over the IPC socket.
+ void receiveHandler();
+
+ /// @brief Port number.
+ uint16_t desired_port_;
+
+ /// @brief Endpoint type, i.e. 4 or 6.
+ EndpointType endpoint_type_;
+
+ /// @brief Pointer to the last received message.
+ Pkt6Ptr pkt_received_;
+};
+
+}; // end of isc::dhcp::test namespace
+}; // end of isc::dhcp namespace
+}; // end of isc namespace
+
+#endif // DHCP4O6_TEST_IPC_H
diff --git a/src/lib/dhcpsrv/testutils/generic_backend_unittest.cc b/src/lib/dhcpsrv/testutils/generic_backend_unittest.cc
new file mode 100644
index 0000000..344d215
--- /dev/null
+++ b/src/lib/dhcpsrv/testutils/generic_backend_unittest.cc
@@ -0,0 +1,284 @@
+// 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 <dhcp/libdhcp++.h>
+#include <dhcp/option_vendor.h>
+#include <dhcpsrv/testutils/generic_backend_unittest.h>
+#include <util/buffer.h>
+#include <typeinfo>
+#include <testutils/gtest_utils.h>
+
+using namespace isc::data;
+using namespace isc::db;
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+GenericBackendTest::GenericBackendTest()
+ : timestamps_(), audit_entries_() {
+ LibDHCP::clearRuntimeOptionDefs();
+ initTimestamps();
+}
+
+GenericBackendTest::~GenericBackendTest() {
+ LibDHCP::clearRuntimeOptionDefs();
+}
+
+OptionDescriptor
+GenericBackendTest::createEmptyOption(const Option::Universe& universe,
+ const uint16_t option_type,
+ const bool persist) const {
+ OptionPtr option(new Option(universe, option_type));
+ OptionDescriptor desc(option, persist);
+ return (desc);
+}
+
+OptionDescriptor
+GenericBackendTest::createVendorOption(const Option::Universe& universe,
+ const bool persist,
+ const bool formatted,
+ const uint32_t vendor_id) const {
+ OptionVendorPtr option(new OptionVendor(universe, vendor_id));
+
+ std::ostringstream s;
+ if (formatted) {
+ // Vendor id comprises vendor-id field, for which we need to
+ // assign a value in the textual (formatted) format.
+ s << vendor_id;
+ }
+
+ OptionDescriptor desc(option, persist, s.str());
+ return (desc);
+}
+
+void
+GenericBackendTest::testOptionsEquivalent(const OptionDescriptor& ref_option,
+ const OptionDescriptor& tested_option) const {
+ // Make sure that all pointers are non-null.
+ ASSERT_TRUE(ref_option.option_);
+ ASSERT_TRUE(tested_option.option_);
+
+ // Get the reference to the tested option. Make should it has
+ // generic type.
+ Option& tested_option_reference = *tested_option.option_;
+ EXPECT_TRUE(typeid(tested_option_reference) == typeid(Option));
+
+ // Only test the binary data if the formatted value is not provided.
+ if (tested_option.formatted_value_.empty()) {
+
+ // Prepare on-wire data of the option under test.
+ isc::util::OutputBuffer tested_option_buf(1);
+ tested_option.option_->pack(tested_option_buf);
+ const uint8_t* tested_option_buf_data = static_cast<const uint8_t*>
+ (tested_option_buf.getData());
+ std::vector<uint8_t> tested_option_buf_vec(tested_option_buf_data,
+ tested_option_buf_data + tested_option_buf.getLength());
+
+ // Prepare on-wire data of the reference option.
+ isc::util::OutputBuffer ref_option_buf(1);
+ ref_option.option_->pack(ref_option_buf);
+ const uint8_t* ref_option_buf_data = static_cast<const uint8_t*>
+ (ref_option_buf.getData());
+ std::vector<uint8_t> ref_option_buf_vec(ref_option_buf_data,
+ ref_option_buf_data + ref_option_buf.getLength());
+
+ // Compare the on-wire data.
+ EXPECT_EQ(ref_option_buf_vec, tested_option_buf_vec);
+
+ } else {
+ // If the formatted value is non-empty the buffer should be empty.
+ EXPECT_TRUE(tested_option.option_->getData().empty());
+ }
+
+ // Compare other members of the @c OptionDescriptor, e.g. the
+ // tested option may contain formatted option data which can be
+ // later used to turn this option instance into a formatted
+ // option when an option definition is available.
+ EXPECT_EQ(ref_option.formatted_value_, tested_option.formatted_value_);
+ EXPECT_EQ(ref_option.persistent_, tested_option.persistent_);
+ EXPECT_EQ(ref_option.space_name_, tested_option.space_name_);
+}
+
+void
+GenericBackendTest::checkConfiguredGlobal(const SrvConfigPtr& srv_cfg,
+ const std::string &name,
+ ConstElementPtr exp_value) {
+ ConstCfgGlobalsPtr globals = srv_cfg->getConfiguredGlobals();
+ ConstElementPtr found_global = globals->get(name);
+ ASSERT_TRUE(found_global) << "expected global: "
+ << name << " not found";
+
+ ASSERT_EQ(exp_value->getType(), found_global->getType())
+ << "expected global: " << name << " has wrong type";
+
+ ASSERT_EQ(*exp_value, *found_global)
+ << "expected global: " << name << " has wrong value";
+}
+
+void
+GenericBackendTest::checkConfiguredGlobal(const SrvConfigPtr& srv_cfg,
+ StampedValuePtr& exp_global) {
+ checkConfiguredGlobal(srv_cfg, exp_global->getName(), exp_global->getElementValue());
+}
+
+
+void
+GenericBackendTest::testNewAuditEntry(const std::string& exp_object_type,
+ const AuditEntry::ModificationType& exp_modification_type,
+ const std::string& exp_log_message,
+ const ServerSelector& server_selector,
+ const size_t new_entries_num,
+ const size_t max_tested_entries) {
+ // Get the server tag for which the entries are fetched.
+ std::string tag;
+ if (server_selector.getType() == ServerSelector::Type::ALL) {
+ // Server tag is 'all'.
+ tag = "all";
+ } else {
+ const auto& tags = server_selector.getTags();
+ // This test is not meant to handle multiple server tags all at once.
+ if (tags.size() > 1) {
+ ADD_FAILURE() << "Test error: do not use multiple server tags";
+ } else if (tags.size() == 1) {
+ // Get the server tag for which we run the current test.
+ tag = tags.begin()->get();
+ }
+ }
+
+ auto audit_entries_size_save = audit_entries_[tag].size();
+
+ // Audit entries for different server tags are stored in separate
+ // containers.
+ ASSERT_NO_THROW_LOG(audit_entries_[tag]
+ = getRecentAuditEntries(server_selector, timestamps_["two days ago"], 0));
+
+ ASSERT_EQ(audit_entries_size_save + new_entries_num, audit_entries_[tag].size())
+ << logExistingAuditEntries(tag);
+
+ auto& mod_time_idx = audit_entries_[tag].get<AuditEntryModificationTimeIdTag>();
+
+ // Iterate over specified number of entries starting from the most recent
+ // one and check they have correct values.
+ for (auto audit_entry_it = mod_time_idx.rbegin();
+ ((std::distance(mod_time_idx.rbegin(), audit_entry_it) < new_entries_num) &&
+ (std::distance(mod_time_idx.rbegin(), audit_entry_it) < max_tested_entries));
+ ++audit_entry_it) {
+ auto audit_entry = *audit_entry_it;
+ EXPECT_EQ(exp_object_type, audit_entry->getObjectType())
+ << logExistingAuditEntries(tag);
+ EXPECT_EQ(exp_modification_type, audit_entry->getModificationType())
+ << logExistingAuditEntries(tag);
+ EXPECT_EQ(exp_log_message, audit_entry->getLogMessage())
+ << logExistingAuditEntries(tag);
+ }
+}
+
+void
+GenericBackendTest::testNewAuditEntry(const std::vector<ExpAuditEntry>& exp_entries,
+ const ServerSelector& server_selector) {
+ // Get the server tag for which the entries are fetched.
+ std::string tag;
+ if (server_selector.getType() == ServerSelector::Type::ALL) {
+ // Server tag is 'all'.
+ tag = "all";
+ } else {
+ const auto& tags = server_selector.getTags();
+ // This test is not meant to handle multiple server tags all at once.
+ if (tags.size() != 1) {
+ ADD_FAILURE() << "Test error: tags.size(): " << tags.size()
+ << ", you must specify one and only one server tag";
+ }
+
+ // Get the server tag for which we run the current test.
+ tag = tags.begin()->get();
+ }
+
+ size_t new_entries_num = exp_entries.size();
+
+ auto audit_entries_size_save = audit_entries_[tag].size();
+
+ // Audit entries for different server tags are stored in separate
+ // containers.
+ ASSERT_NO_THROW_LOG(audit_entries_[tag]
+ = getRecentAuditEntries(server_selector, timestamps_["two days ago"], 0));
+
+ ASSERT_EQ(audit_entries_size_save + new_entries_num, audit_entries_[tag].size())
+ << logExistingAuditEntries(tag);
+
+ auto& mod_time_idx = audit_entries_[tag].get<AuditEntryModificationTimeIdTag>();
+
+ // Iterate over specified number of entries starting from the most recent
+ // one and check they have correct values.
+ auto exp_entry = exp_entries.rbegin();
+ for (auto audit_entry_it = mod_time_idx.rbegin();
+ ((std::distance(mod_time_idx.rbegin(), audit_entry_it) < new_entries_num));
+ ++audit_entry_it) {
+
+ auto audit_entry = *audit_entry_it;
+ EXPECT_EQ((*exp_entry).object_type, audit_entry->getObjectType())
+ << logExistingAuditEntries(tag);
+ EXPECT_EQ((*exp_entry).modification_type, audit_entry->getModificationType())
+ << logExistingAuditEntries(tag);
+ EXPECT_EQ((*exp_entry).log_message, audit_entry->getLogMessage())
+ << logExistingAuditEntries(tag);
+
+ ++exp_entry;
+ }
+}
+
+void
+GenericBackendTest::initTimestamps() {
+ // Current time minus 1 hour to make sure it is in the past.
+ timestamps_["today"] = boost::posix_time::second_clock::local_time()
+ - boost::posix_time::hours(1);
+ // One second after today.
+ timestamps_["after today"] = timestamps_["today"] + boost::posix_time::seconds(1);
+ // Yesterday.
+ timestamps_["yesterday"] = timestamps_["today"] - boost::posix_time::hours(24);
+ // One second after yesterday.
+ timestamps_["after yesterday"] = timestamps_["yesterday"] + boost::posix_time::seconds(1);
+ // Two days ago.
+ timestamps_["two days ago"] = timestamps_["today"] - boost::posix_time::hours(48);
+ // Tomorrow.
+ timestamps_["tomorrow"] = timestamps_["today"] + boost::posix_time::hours(24);
+ // One second after tomorrow.
+ timestamps_["after tomorrow"] = timestamps_["tomorrow"] + boost::posix_time::seconds(1);
+}
+
+std::string
+GenericBackendTest::logExistingAuditEntries(const std::string& server_tag) {
+ std::ostringstream s;
+
+ auto& mod_time_idx = audit_entries_[server_tag].get<AuditEntryModificationTimeIdTag>();
+
+ for (auto audit_entry_it = mod_time_idx.begin();
+ audit_entry_it != mod_time_idx.end();
+ ++audit_entry_it) {
+ auto audit_entry = *audit_entry_it;
+ s << audit_entry->getObjectType() << ", "
+ << audit_entry->getObjectId() << ", "
+ << static_cast<int>(audit_entry->getModificationType()) << ", "
+ << audit_entry->getModificationTime() << ", "
+ << audit_entry->getRevisionId() << ", "
+ << audit_entry->getLogMessage()
+ << std::endl;
+ }
+
+ return (s.str());
+}
+
+AuditEntryCollection
+GenericBackendTest::getRecentAuditEntries(const ServerSelector&, const boost::posix_time::ptime&,
+ const uint64_t&) const {
+ return (AuditEntryCollection());
+}
+
+} // end of namespace isc::dhcp::test
+} // end of namespace isc::dhcp
+} // end of namespace isc
diff --git a/src/lib/dhcpsrv/testutils/generic_backend_unittest.h b/src/lib/dhcpsrv/testutils/generic_backend_unittest.h
new file mode 100644
index 0000000..f45ba3d
--- /dev/null
+++ b/src/lib/dhcpsrv/testutils/generic_backend_unittest.h
@@ -0,0 +1,352 @@
+// 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/.
+
+#ifndef GENERIC_BACKEND_UNITTEST_H
+#define GENERIC_BACKEND_UNITTEST_H
+
+#include <asiolink/io_address.h>
+#include <cc/stamped_value.h>
+#include <database/audit_entry.h>
+#include <database/server_selector.h>
+#include <dhcp/option.h>
+#include <dhcpsrv/cfg_option.h>
+#include <dhcpsrv/srv_config.h>
+#include <boost/shared_ptr.hpp>
+#include <gtest/gtest.h>
+#include <cstdint>
+#include <sstream>
+#include <string>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+/// @brief Describes an expected audit table entry.
+struct ExpAuditEntry {
+ /// @brief Type of object changed.
+ std::string object_type;
+
+ /// @brief Timestamp the change occurred.
+ db::AuditEntry::ModificationType modification_type;
+
+ /// @brief Log message describing the change.
+ std::string log_message;
+};
+
+/// @brief Generic test fixture class with utility functions for
+/// testing database backends.
+class GenericBackendTest : public ::testing::Test {
+public:
+
+ /// @brief Constructor.
+ GenericBackendTest();
+
+ /// @brief Virtual destructor.
+ virtual ~GenericBackendTest();
+
+ /// @brief Creates an option descriptor holding an empty option.
+ ///
+ /// @param universe V4 or V6.
+ /// @param option_type Option type.
+ /// @param persist A boolean flag indicating if the option is always
+ /// returned to the client or only when requested.
+ ///
+ /// @return Descriptor holding an empty option.
+ OptionDescriptor createEmptyOption(const Option::Universe& universe,
+ const uint16_t option_type,
+ const bool persist) const;
+
+ /// @brief Creates an instance of the option for which it is possible to
+ /// specify universe, option type, persistence flag and value in
+ /// the constructor.
+ ///
+ /// Examples of options that can be created using this function are:
+ /// - @ref OptionString
+ /// - different variants of @ref OptionInt.
+ ///
+ /// @param universe V4 or V6.
+ /// @param option_type Option type.
+ /// @param persist A boolean flag indicating if the option is always
+ /// returned to the client or only when requested.
+ /// @param formatted A boolean value selecting if the formatted option
+ /// value should be used (if true), or binary value (if false).
+ /// @param value Option value to be assigned to the option.
+ /// @tparam OptionType Class encapsulating the option.
+ /// @tparam DataType Option value data type.
+ ///
+ /// @return Descriptor holding an instance of the option created.
+ template<typename OptionType, typename DataType>
+ OptionDescriptor createOption(const Option::Universe& universe,
+ const uint16_t option_type,
+ const bool persist,
+ const bool formatted,
+ const DataType& value) const {
+ boost::shared_ptr<OptionType> option(new OptionType(universe, option_type,
+ value));
+ std::ostringstream s;
+ if (formatted) {
+ // Using formatted option value. Convert option value to a
+ // textual format.
+ s << value;
+ }
+ OptionDescriptor desc(option, persist, s.str());
+ return (desc);
+ }
+
+ /// @brief Creates an instance of the option for which it is possible to
+ /// specify option type, persistence flag and value in the constructor.
+ ///
+ /// Examples of options that can be created using this function are:
+ /// - @ref Option4AddrLst
+ /// - @ref Option6AddrLst
+ ///
+ /// @param option_type Option type.
+ /// @param persist A boolean flag indicating if the option is always
+ /// returned to the client or only when requested.
+ /// @param formatted A boolean value selecting if the formatted option
+ /// value should be used (if true), or binary value (if false).
+ /// @param value Option value to be assigned to the option.
+ /// @tparam OptionType Class encapsulating the option.
+ /// @tparam DataType Option value data type.
+ ///
+ /// @return Descriptor holding an instance of the option created.
+ template<typename OptionType, typename DataType>
+ OptionDescriptor createOption(const uint16_t option_type,
+ const bool persist,
+ const bool formatted,
+ const DataType& value) const {
+ boost::shared_ptr<OptionType> option(new OptionType(option_type, value));
+
+ std::ostringstream s;
+ if (formatted) {
+ // Using formatted option value. Convert option value to a
+ // textual format.
+ s << value;
+ }
+
+ OptionDescriptor desc(option, persist, s.str());
+ return (desc);
+ }
+
+ /// @brief Creates an instance of the option holding list of IP addresses.
+ ///
+ /// @param option_type Option type.
+ /// @param persist A boolean flag indicating if the option is always
+ /// returned to the client or only when requested.
+ /// @param formatted A boolean value selecting if the formatted option
+ /// value should be used (if true), or binary value (if false).
+ /// @param address1 First address to be included. If address is empty, it is
+ /// not included.
+ /// @param address2 Second address to be included. If address is empty, it
+ /// is not included.
+ /// @param address3 Third address to be included. If address is empty, it
+ /// is not included.
+ /// @tparam OptionType Class encapsulating the option.
+ ///
+ /// @return Descriptor holding an instance of the option created.
+ template<typename OptionType>
+ OptionDescriptor
+ createAddressOption(const uint16_t option_type,
+ const bool persist,
+ const bool formatted,
+ const std::string& address1 = "",
+ const std::string& address2 = "",
+ const std::string& address3 = "") const {
+ std::ostringstream s;
+ // First address.
+ typename OptionType::AddressContainer addresses;
+ if (!address1.empty()) {
+ addresses.push_back(asiolink::IOAddress(address1));
+ if (formatted) {
+ s << address1;
+ }
+ }
+ // Second address.
+ if (!address2.empty()) {
+ addresses.push_back(asiolink::IOAddress(address2));
+ if (formatted) {
+ if (s.tellp() != std::streampos(0)) {
+ s << ",";
+ }
+ s << address2;
+ }
+ }
+ // Third address.
+ if (!address3.empty()) {
+ addresses.push_back(asiolink::IOAddress(address3));
+ if (formatted) {
+ if (s.tellp() != std::streampos(0)) {
+ s << ",";
+ }
+ s << address3;
+ }
+ }
+
+ boost::shared_ptr<OptionType> option(new OptionType(option_type,
+ addresses));
+ OptionDescriptor desc(option, persist, s.str());
+ return (desc);
+ }
+
+ /// @brief Creates an instance of the vendor option.
+ ///
+ /// @param universe V4 or V6.
+ /// @param persist A boolean flag indicating if the option is always
+ /// returned to the client or only when requested.
+ /// @param formatted A boolean value selecting if the formatted option
+ /// value should be used (if true), or binary value (if false).
+ /// @param vendor_id Vendor identifier.
+ ///
+ /// @return Descriptor holding an instance of the option created.
+ OptionDescriptor createVendorOption(const Option::Universe& universe,
+ const bool persist,
+ const bool formatted,
+ const uint32_t vendor_id) const;
+
+ /// @brief Verify the option returned by the backend against a
+ /// reference option.
+ ///
+ /// DHCP option value can be specified in two ways. First, it can be
+ /// specified as a string of hexadecimal digits which is converted to
+ /// a binary option value. Second, it can be specified as a string of
+ /// comma separated values in a user readable form. The comma separated
+ /// values are parsed according to the definition of the given option
+ /// and then stored in the respective fields of the option. The second
+ /// approach always requires an option definition to be known to the
+ /// parser. It may either be a standard option definition or a runtime
+ /// option definition created by a user. While standard option
+ /// definitions are available in the Kea header files, the custom
+ /// option definitions may not be available to the Config Backend
+ /// fetching an option from the database for the following reasons:
+ ///
+ /// - the server to which the Config Backend is attached is not the
+ /// one for which the configuration is being returned.
+ /// - the server is starting up and hasn't yet configured its runtime
+ /// option definitions.
+ /// - the Config Backend implementation is not attached to the DHCP
+ /// server but to the Control Agent.
+ ///
+ /// Note that the last case it currently not supported but may be
+ /// supported in the future.
+ ///
+ /// Since the option definitions aren't always available to the Config
+ /// Backend fetching the options from the database, the backend doesn't
+ /// interpret formatted options (those that use comma separated values
+ /// notation). It simply creates an @c OptionDescriptor with the generic
+ /// option instance (containing an option code and no option value) and
+ /// the other @c OptionDescriptor parameters set appropriately. The
+ /// @c CfgOption class contains methods that can be used on demand to
+ /// replace these instances with the appropriate types (derived from
+ /// @c Option) which represent formatted option data, if necessary.
+ ///
+ /// This test verifies that the @c OptionDescriptor returned by the
+ /// Config Backend is correct in that:
+ /// - the @c option_ member is non-null,
+ /// - the option instance is of a @c Option type rather than any of the
+ /// derived types (is a raw option),
+ /// - the wire data of the returned option is equal to the wire data of
+ /// the reference option (the reference option can be of a type derived
+ /// from @c Option),
+ /// - the @c formatted_value_, @c persistent_ and @c space_name_ members
+ /// of the returned option are equal to the respective members of the
+ /// reference option.
+ ///
+ /// @param ref_option Reference option to compare tested option to.
+ /// @param tested_option Option returned by the backend to be tested.
+ void testOptionsEquivalent(const OptionDescriptor& ref_option,
+ const OptionDescriptor& tested_option) const;
+
+ /// @brief Tests that a given global is in the configured globals
+ ///
+ /// @param srv_cfg server config where the global should be checked.
+ /// @param name name of the global parameter
+ /// @param exp_value expected value of the global parameter as an Element
+ void checkConfiguredGlobal(const SrvConfigPtr& srv_cfg,
+ const std::string &name,
+ data::ConstElementPtr exp_value);
+
+ /// @brief Tests that a given global is in the configured globals
+ ///
+ /// @param srv_cfg server config where the global should be checked.
+ /// @param exp_global StampedValue representing the global value to verify
+ ///
+ /// @todo At the point in time StampedVlaue carries type, exp_type should be
+ /// replaced with exp_global->getType()
+ void checkConfiguredGlobal(const SrvConfigPtr& srv_cfg,
+ data::StampedValuePtr& exp_global);
+
+ /// @brief Tests that the new audit entry is added.
+ ///
+ /// This method retrieves a collection of the existing audit entries and
+ /// checks that the new one has been added at the end of this collection.
+ /// It then verifies the values of the audit entry against the values
+ /// specified by the caller.
+ ///
+ /// @param exp_object_type Expected object type.
+ /// @param exp_modification_type Expected modification type.
+ /// @param exp_log_message Expected log message.
+ /// @param server_selector Server selector to be used for next query.
+ /// @param new_entries_num Number of the new entries expected to be inserted.
+ /// @param max_tested_entries Maximum number of entries tested.
+ void testNewAuditEntry(const std::string& exp_object_type,
+ const db::AuditEntry::ModificationType& exp_modification_type,
+ const std::string& exp_log_message,
+ const db::ServerSelector& server_selector = db::ServerSelector::ALL(),
+ const size_t new_entries_num = 1,
+ const size_t max_tested_entries = 65535);
+
+ /// @brief Checks the new audit entries against a list of
+ /// expected entries.
+ ///
+ /// This method retrieves a collection of the existing audit entries and
+ /// checks that number and content of the expected new entries have been
+ /// added to the end of this collection.
+ ///
+ /// @param exp_entries a list of the expected new audit entries.
+ /// @param server_selector Server selector to be used for next query.
+ void testNewAuditEntry(const std::vector<ExpAuditEntry>& exp_entries,
+ const db::ServerSelector& server_selector);
+
+ /// @brief Logs audit entries in the @c audit_entries_ member.
+ ///
+ /// This function is called in case of an error.
+ ///
+ /// @param server_tag Server tag for which the audit entries should be logged.
+ std::string logExistingAuditEntries(const std::string& server_tag);
+
+ /// @brief Retrieves the most recent audit entries.
+ ///
+ /// Allowed server selectors: ALL, ONE.
+ /// Not allowed server selectors: ANY, UNASSIGNED, MULTIPLE.
+ ///
+ /// @param server_selector Server selector.
+ /// @param modification_time Timestamp being a lower limit for the returned
+ /// result set, i.e. entries later than specified time are returned.
+ /// @param modification_id Identifier being a lower limit for the returned
+ /// result set, used when two (or more) entries have the same
+ /// modification_time.
+ /// @return Collection of audit entries.
+ virtual db::AuditEntryCollection
+ getRecentAuditEntries(const db::ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time,
+ const uint64_t& modification_id) const ;
+
+ /// @brief Initialize posix time values used in tests.
+ void initTimestamps();
+
+ /// @brief Holds timestamp values used in tests.
+ std::map<std::string, boost::posix_time::ptime> timestamps_;
+
+ /// @brief Holds the most recent audit entries.
+ std::map<std::string, db::AuditEntryCollection> audit_entries_;
+};
+
+} // end of namespace isc::dhcp::test
+} // end of namespace isc::dhcp
+} // end of namespace isc
+
+#endif
+
diff --git a/src/lib/dhcpsrv/testutils/generic_cb_dhcp4_unittest.cc b/src/lib/dhcpsrv/testutils/generic_cb_dhcp4_unittest.cc
new file mode 100644
index 0000000..18dde4a
--- /dev/null
+++ b/src/lib/dhcpsrv/testutils/generic_cb_dhcp4_unittest.cc
@@ -0,0 +1,4591 @@
+// Copyright (C) 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 <asiolink/addr_utilities.h>
+#include <database/database_connection.h>
+#include <database/db_exceptions.h>
+#include <database/server.h>
+#include <database/testutils/schema.h>
+#include <dhcp/libdhcp++.h>
+#include <dhcp/option4_addrlst.h>
+#include <dhcp/option_int.h>
+#include <dhcp/option_space.h>
+#include <dhcp/option_string.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/client_class_def.h>
+#include <dhcpsrv/config_backend_dhcp4_mgr.h>
+#include <dhcpsrv/pool.h>
+#include <dhcpsrv/subnet.h>
+#include <dhcpsrv/testutils/generic_cb_dhcp4_unittest.h>
+#include <dhcpsrv/testutils/test_utils.h>
+#include <testutils/gtest_utils.h>
+
+#include <boost/make_shared.hpp>
+#include <boost/shared_ptr.hpp>
+#include <gtest/gtest.h>
+
+using namespace isc;
+using namespace isc::util;
+using namespace isc::asiolink;
+using namespace isc::db;
+using namespace isc::db::test;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+using namespace isc::process;
+using namespace isc::test;
+namespace ph = std::placeholders;
+
+void
+GenericConfigBackendDHCPv4Test::SetUp() {
+ // Ensure we have the proper schema with no transient data.
+ createSchema();
+
+ try {
+ // Create a connection parameter map and use it to start the backend.
+ DatabaseConnection::ParameterMap params =
+ DatabaseConnection::parse(validConnectionString());
+ cbptr_ = backendFactory(params);
+ } catch (...) {
+ std::cerr << "*** ERROR: unable to open database. The test\n"
+ "*** environment is broken and must be fixed before\n"
+ "*** the tests will run correctly.\n"
+ "*** The reason for the problem is described in the\n"
+ "*** accompanying exception output.\n";
+ throw;
+ }
+
+ // Create test data.
+ initTestServers();
+ initTestOptions();
+ initTestSubnets();
+ initTestSharedNetworks();
+ initTestOptionDefs();
+ initTestClientClasses();
+}
+
+void
+GenericConfigBackendDHCPv4Test::TearDown() {
+ cbptr_.reset();
+ // If data wipe enabled, delete transient data otherwise destroy the schema.
+ destroySchema();
+}
+
+db::AuditEntryCollection
+GenericConfigBackendDHCPv4Test::getRecentAuditEntries(const db::ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time,
+ const uint64_t& modification_id) const {
+ return (cbptr_->getRecentAuditEntries(server_selector, modification_time, modification_id));
+}
+
+void
+GenericConfigBackendDHCPv4Test::initTestServers() {
+ test_servers_.push_back(Server::create(ServerTag("server1"), "this is server 1"));
+ test_servers_.push_back(Server::create(ServerTag("server1"), "this is server 1 bis"));
+ test_servers_.push_back(Server::create(ServerTag("server2"), "this is server 2"));
+ test_servers_.push_back(Server::create(ServerTag("server3"), "this is server 3"));
+}
+
+void
+GenericConfigBackendDHCPv4Test::initTestSubnets() {
+ // First subnet includes all parameters.
+ std::string interface_id_text = "whale";
+ OptionBuffer interface_id(interface_id_text.begin(), interface_id_text.end());
+ ElementPtr user_context = Element::createMap();
+ user_context->set("foo", Element::create("bar"));
+
+ Subnet4Ptr subnet(new Subnet4(IOAddress("192.0.2.0"), 24,
+ 30, 40, 60, 1024));
+ subnet->get4o6().setIface4o6("eth0");
+ subnet->get4o6().setInterfaceId(OptionPtr(new Option(Option::V6, D6O_INTERFACE_ID,
+ interface_id)));
+ subnet->get4o6().setSubnet4o6(IOAddress("2001:db8:1::"), 64);
+ subnet->setFilename("/tmp/filename");
+ subnet->allowClientClass("home");
+ subnet->setIface("eth1");
+ subnet->setMatchClientId(false);
+ subnet->setSiaddr(IOAddress("10.1.2.3"));
+ subnet->setT2(323212);
+ subnet->addRelayAddress(IOAddress("10.2.3.4"));
+ subnet->addRelayAddress(IOAddress("10.5.6.7"));
+ subnet->setT1(1234);
+ subnet->requireClientClass("required-class1");
+ subnet->requireClientClass("required-class2");
+ subnet->setReservationsGlobal(false);
+ subnet->setReservationsInSubnet(false);
+ subnet->setSname("server-hostname");
+ subnet->setContext(user_context);
+ subnet->setValid(555555);
+ subnet->setAuthoritative(true);
+ subnet->setCalculateTeeTimes(true);
+ subnet->setT1Percent(0.345);
+ subnet->setT2Percent(0.444);
+ subnet->setDdnsSendUpdates(false);
+
+ Pool4Ptr pool1(new Pool4(IOAddress("192.0.2.10"),
+ IOAddress("192.0.2.20")));
+ subnet->addPool(pool1);
+
+ Pool4Ptr pool2(new Pool4(IOAddress("192.0.2.50"),
+ IOAddress("192.0.2.60")));
+ subnet->addPool(pool2);
+
+ // Add several options to the subnet.
+ subnet->getCfgOption()->add(test_options_[0]->option_,
+ test_options_[0]->persistent_,
+ test_options_[0]->space_name_);
+
+ subnet->getCfgOption()->add(test_options_[1]->option_,
+ test_options_[1]->persistent_,
+ test_options_[1]->space_name_);
+
+ subnet->getCfgOption()->add(test_options_[2]->option_,
+ test_options_[2]->persistent_,
+ test_options_[2]->space_name_);
+
+ test_subnets_.push_back(subnet);
+
+ // Adding another subnet with the same subnet id to test
+ // cases that this second instance can override existing
+ // subnet instance.
+ subnet.reset(new Subnet4(IOAddress("10.0.0.0"),
+ 8, 20, 30, 40, 1024));
+
+ pool1.reset(new Pool4(IOAddress("10.0.0.10"),
+ IOAddress("10.0.0.20")));
+ subnet->addPool(pool1);
+
+ pool1->getCfgOption()->add(test_options_[3]->option_,
+ test_options_[3]->persistent_,
+ test_options_[3]->space_name_);
+
+ pool1->getCfgOption()->add(test_options_[4]->option_,
+ test_options_[4]->persistent_,
+ test_options_[4]->space_name_);
+
+ pool2.reset(new Pool4(IOAddress("10.0.0.50"),
+ IOAddress("10.0.0.60")));
+
+ pool2->allowClientClass("work");
+ pool2->requireClientClass("required-class3");
+ pool2->requireClientClass("required-class4");
+ user_context = Element::createMap();
+ user_context->set("bar", Element::create("foo"));
+ pool2->setContext(user_context);
+
+ subnet->addPool(pool2);
+
+ test_subnets_.push_back(subnet);
+
+ subnet.reset(new Subnet4(IOAddress("192.0.3.0"), 24, 20, 30, 40, 2048));
+ Triplet<uint32_t> null_timer;
+ subnet->setT1(null_timer);
+ subnet->setT2(null_timer);
+ subnet->setValid(null_timer);
+ subnet->setDdnsSendUpdates(true);
+ subnet->setDdnsOverrideNoUpdate(true);
+ subnet->setDdnsOverrideClientUpdate(false);
+ subnet->setDdnsReplaceClientNameMode(D2ClientConfig::ReplaceClientNameMode::RCM_WHEN_PRESENT);
+ subnet->setDdnsGeneratedPrefix("myhost");
+ subnet->setDdnsQualifyingSuffix("example.org");
+
+ subnet->getCfgOption()->add(test_options_[0]->option_,
+ test_options_[0]->persistent_,
+ test_options_[0]->space_name_);
+
+ test_subnets_.push_back(subnet);
+
+ // Add a subnet with all defaults.
+ subnet.reset(new Subnet4(IOAddress("192.0.4.0"), 24,
+ Triplet<uint32_t>(), Triplet<uint32_t>(),
+ Triplet<uint32_t>(), 4096));
+ test_subnets_.push_back(subnet);
+}
+
+void
+GenericConfigBackendDHCPv4Test::initTestSharedNetworks() {
+ ElementPtr user_context = Element::createMap();
+ user_context->set("foo", Element::create("bar"));
+
+ SharedNetwork4Ptr shared_network(new SharedNetwork4("level1"));
+ shared_network->allowClientClass("foo");
+ shared_network->setIface("eth1");
+ shared_network->setMatchClientId(false);
+ shared_network->setT2(323212);
+ shared_network->addRelayAddress(IOAddress("10.2.3.4"));
+ shared_network->addRelayAddress(IOAddress("10.5.6.7"));
+ shared_network->setT1(1234);
+ shared_network->requireClientClass("required-class1");
+ shared_network->requireClientClass("required-class2");
+ shared_network->setReservationsGlobal(false);
+ shared_network->setReservationsInSubnet(false);
+ shared_network->setContext(user_context);
+ shared_network->setValid(5555);
+ shared_network->setCalculateTeeTimes(true);
+ shared_network->setT1Percent(0.345);
+ shared_network->setT2Percent(0.444);
+ shared_network->setSiaddr(IOAddress("192.0.1.2"));
+ shared_network->setSname("frog");
+ shared_network->setFilename("/dev/null");
+ shared_network->setAuthoritative(true);
+ shared_network->setDdnsSendUpdates(false);
+
+ // Add several options to the shared network.
+ shared_network->getCfgOption()->add(test_options_[2]->option_,
+ test_options_[2]->persistent_,
+ test_options_[2]->space_name_);
+
+ shared_network->getCfgOption()->add(test_options_[3]->option_,
+ test_options_[3]->persistent_,
+ test_options_[3]->space_name_);
+
+ shared_network->getCfgOption()->add(test_options_[4]->option_,
+ test_options_[4]->persistent_,
+ test_options_[4]->space_name_);
+
+ test_networks_.push_back(shared_network);
+
+ // Adding another shared network called "level1" to test
+ // cases that this second instance can override existing
+ // "level1" instance.
+ shared_network.reset(new SharedNetwork4("level1"));
+ test_networks_.push_back(shared_network);
+
+ // Add more shared networks.
+ shared_network.reset(new SharedNetwork4("level2"));
+ Triplet<uint32_t> null_timer;
+ shared_network->setT1(null_timer);
+ shared_network->setT2(null_timer);
+ shared_network->setValid(null_timer);
+ shared_network->setDdnsSendUpdates(true);
+ shared_network->setDdnsOverrideNoUpdate(true);
+ shared_network->setDdnsOverrideClientUpdate(false);
+ shared_network->setDdnsReplaceClientNameMode(D2ClientConfig::ReplaceClientNameMode::RCM_WHEN_PRESENT);
+ shared_network->setDdnsGeneratedPrefix("myhost");
+ shared_network->setDdnsQualifyingSuffix("example.org");
+
+ shared_network->getCfgOption()->add(test_options_[0]->option_,
+ test_options_[0]->persistent_,
+ test_options_[0]->space_name_);
+ test_networks_.push_back(shared_network);
+
+ shared_network.reset(new SharedNetwork4("level3"));
+ test_networks_.push_back(shared_network);
+}
+
+void
+GenericConfigBackendDHCPv4Test::initTestOptionDefs() {
+ ElementPtr user_context = Element::createMap();
+ user_context->set("foo", Element::create("bar"));
+
+ OptionDefinitionPtr option_def(new OptionDefinition("foo", 234,
+ DHCP4_OPTION_SPACE,
+ "string",
+ "espace"));
+ test_option_defs_.push_back(option_def);
+
+ option_def.reset(new OptionDefinition("bar", 234, DHCP4_OPTION_SPACE,
+ "uint32", true));
+ test_option_defs_.push_back(option_def);
+
+ option_def.reset(new OptionDefinition("fish", 235, DHCP4_OPTION_SPACE,
+ "record", true));
+ option_def->addRecordField("uint32");
+ option_def->addRecordField("string");
+ test_option_defs_.push_back(option_def);
+
+ option_def.reset(new OptionDefinition("whale", 236, "xyz", "string"));
+ test_option_defs_.push_back(option_def);
+
+ option_def.reset(new OptionDefinition("foobar", 234, DHCP4_OPTION_SPACE,
+ "uint64", true));
+ test_option_defs_.push_back(option_def);
+}
+
+void
+GenericConfigBackendDHCPv4Test::initTestOptions() {
+ ElementPtr user_context = Element::createMap();
+ user_context->set("foo", Element::create("bar"));
+
+ OptionDefSpaceContainer defs;
+
+ OptionDescriptor desc =
+ createOption<OptionString>(Option::V4, DHO_BOOT_FILE_NAME,
+ true, false, "my-boot-file");
+ desc.space_name_ = DHCP4_OPTION_SPACE;
+ desc.setContext(user_context);
+ test_options_.push_back(OptionDescriptorPtr(new OptionDescriptor(desc)));
+
+ desc = createOption<OptionUint8>(Option::V4, DHO_DEFAULT_IP_TTL,
+ false, true, 64);
+ desc.space_name_ = DHCP4_OPTION_SPACE;
+ test_options_.push_back(OptionDescriptorPtr(new OptionDescriptor(desc)));
+
+ desc = createOption<OptionUint32>(Option::V4, 1, false, false, 312131),
+ desc.space_name_ = "vendor-encapsulated-options";
+ test_options_.push_back(OptionDescriptorPtr(new OptionDescriptor(desc)));
+
+ desc = createAddressOption<Option4AddrLst>(254, true, true,
+ "192.0.2.3");
+ desc.space_name_ = DHCP4_OPTION_SPACE;
+ test_options_.push_back(OptionDescriptorPtr(new OptionDescriptor(desc)));
+
+ desc = createEmptyOption(Option::V4, 1, true);
+ desc.space_name_ = "isc";
+ test_options_.push_back(OptionDescriptorPtr(new OptionDescriptor(desc)));
+
+ desc = createAddressOption<Option4AddrLst>(2, false, true,
+ "10.0.0.5",
+ "10.0.0.3",
+ "10.0.3.4");
+ desc.space_name_ = "isc";
+ test_options_.push_back(OptionDescriptorPtr(new OptionDescriptor(desc)));
+
+ desc = createOption<OptionString>(Option::V4, DHO_BOOT_FILE_NAME,
+ true, false, "my-boot-file-2");
+ desc.space_name_ = DHCP4_OPTION_SPACE;
+ desc.setContext(user_context);
+ test_options_.push_back(OptionDescriptorPtr(new OptionDescriptor(desc)));
+
+ desc = createOption<OptionString>(Option::V4, DHO_BOOT_FILE_NAME,
+ true, false, "my-boot-file-3");
+ desc.space_name_ = DHCP4_OPTION_SPACE;
+ desc.setContext(user_context);
+ test_options_.push_back(OptionDescriptorPtr(new OptionDescriptor(desc)));
+
+ // Add definitions for DHCPv4 non-standard options in case we need to
+ // compare subnets, networks and pools in JSON format. In that case,
+ // the @c toElement functions require option definitions to generate the
+ // proper output.
+ defs.addItem(OptionDefinitionPtr(new OptionDefinition("vendor-encapsulated-1", 1,
+ "vendor-encapsulated-options",
+ "uint32")));
+ defs.addItem(OptionDefinitionPtr(new OptionDefinition("option-254", 254,
+ DHCP4_OPTION_SPACE,
+ "ipv4-address", true)));
+ defs.addItem(OptionDefinitionPtr(new OptionDefinition("isc-1", 1, "isc", "empty")));
+ defs.addItem(OptionDefinitionPtr(new OptionDefinition("isc-2", 2, "isc",
+ "ipv4-address", true)));
+
+ // Register option definitions.
+ LibDHCP::setRuntimeOptionDefs(defs);
+}
+
+void
+GenericConfigBackendDHCPv4Test::initTestClientClasses() {
+ ExpressionPtr match_expr = boost::make_shared<Expression>();
+ CfgOptionPtr cfg_option = boost::make_shared<CfgOption>();
+ auto class1 = boost::make_shared<ClientClassDef>("foo", match_expr, cfg_option);
+ class1->setCfgOptionDef(boost::make_shared<CfgOptionDef>());
+ class1->setRequired(true);
+ class1->setNextServer(IOAddress("1.2.3.4"));
+ class1->setSname("cool");
+ class1->setFilename("epc.cfg");
+ class1->setValid(Triplet<uint32_t>(30, 60, 90));
+ ElementPtr user_context = Element::createMap();
+ user_context->set("melon", Element::create("water"));
+ class1->setContext(user_context);
+ test_client_classes_.push_back(class1);
+
+ auto class2 = boost::make_shared<ClientClassDef>("bar", match_expr, cfg_option);
+ class2->setCfgOptionDef(boost::make_shared<CfgOptionDef>());
+ class2->setTest("member('foo')");
+ test_client_classes_.push_back(class2);
+
+ auto class3 = boost::make_shared<ClientClassDef>("foobar", match_expr, cfg_option);
+ class3->setCfgOptionDef(boost::make_shared<CfgOptionDef>());
+ class3->setTest("member('foo') and member('bar')");
+ test_client_classes_.push_back(class3);
+}
+
+void
+GenericConfigBackendDHCPv4Test::getTypeTest(const std::string& expected_type) {
+ DatabaseConnection::ParameterMap params;
+ params["name"] = "keatest";
+ params["password"] = "keatest";
+ params["user"] = "keatest";
+ ASSERT_NO_THROW_LOG(cbptr_ = backendFactory(params));
+ ASSERT_NE(cbptr_->getParameters(), DatabaseConnection::ParameterMap());
+ EXPECT_EQ(expected_type, cbptr_->getType());
+}
+
+void
+GenericConfigBackendDHCPv4Test::getHostTest() {
+ DatabaseConnection::ParameterMap params;
+ params["name"] = "keatest";
+ params["password"] = "keatest";
+ params["user"] = "keatest";
+ ASSERT_NO_THROW_LOG(cbptr_ = backendFactory(params));
+ ASSERT_NE(cbptr_->getParameters(), DatabaseConnection::ParameterMap());
+ EXPECT_EQ("localhost", cbptr_->getHost());
+}
+
+void
+GenericConfigBackendDHCPv4Test::getPortTest() {
+ DatabaseConnection::ParameterMap params;
+ params["name"] = "keatest";
+ params["password"] = "keatest";
+ params["user"] = "keatest";
+ ASSERT_NO_THROW_LOG(cbptr_ = backendFactory(params));
+ ASSERT_NE(cbptr_->getParameters(), DatabaseConnection::ParameterMap());
+ EXPECT_EQ(0, cbptr_->getPort());
+}
+
+void
+GenericConfigBackendDHCPv4Test::createUpdateDeleteServerTest() {
+ // Explicitly set modification time to make sure that the time
+ // returned from the database is correct.
+ test_servers_[0]->setModificationTime(timestamps_["yesterday"]);
+ test_servers_[1]->setModificationTime(timestamps_["today"]);
+
+ // Insert the server1 into the database.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer4(test_servers_[0]));
+
+ {
+ SCOPED_TRACE("CREATE audit entry for server");
+ testNewAuditEntry("dhcp4_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set");
+ }
+
+ // It should not be possible to create a duplicate of the logical
+ // server 'all'.
+ auto all_server = Server::create(ServerTag("all"), "this is logical server all");
+ ASSERT_THROW(cbptr_->createUpdateServer4(all_server), isc::InvalidOperation);
+
+ ServerPtr returned_server;
+
+ // An attempt to fetch the server that hasn't been inserted should return
+ // a null pointer.
+ ASSERT_NO_THROW_LOG(returned_server = cbptr_->getServer4(ServerTag("server2")));
+ EXPECT_FALSE(returned_server);
+
+ // Try to fetch the server which we expect to exist.
+ ASSERT_NO_THROW_LOG(returned_server = cbptr_->getServer4(ServerTag("server1")));
+ ASSERT_TRUE(returned_server);
+ EXPECT_EQ("server1", returned_server->getServerTagAsText());
+ EXPECT_EQ("this is server 1", returned_server->getDescription());
+ EXPECT_EQ(timestamps_["yesterday"], returned_server->getModificationTime());
+
+ // This call is expected to update the existing server.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer4(test_servers_[1]));
+
+ {
+ SCOPED_TRACE("UPDATE audit entry for server");
+ testNewAuditEntry("dhcp4_server",
+ AuditEntry::ModificationType::UPDATE,
+ "server set");
+ }
+
+ // Verify that the server has been updated.
+ ASSERT_NO_THROW_LOG(returned_server = cbptr_->getServer4(ServerTag("server1")));
+ ASSERT_TRUE(returned_server);
+ EXPECT_EQ("server1", returned_server->getServerTag().get());
+ EXPECT_EQ("this is server 1 bis", returned_server->getDescription());
+ EXPECT_EQ(timestamps_["today"], returned_server->getModificationTime());
+
+ uint64_t servers_deleted = 0;
+
+ // Try to delete non-existing server.
+ ASSERT_NO_THROW_LOG(servers_deleted = cbptr_->deleteServer4(ServerTag("server2")));
+ EXPECT_EQ(0, servers_deleted);
+
+ // Make sure that the server1 wasn't deleted.
+ ASSERT_NO_THROW_LOG(returned_server = cbptr_->getServer4(ServerTag("server1")));
+ EXPECT_TRUE(returned_server);
+
+ // Deleting logical server 'all' is not allowed.
+ ASSERT_THROW(cbptr_->deleteServer4(ServerTag()), isc::InvalidOperation);
+
+ // Delete the existing server.
+ ASSERT_NO_THROW_LOG(servers_deleted = cbptr_->deleteServer4(ServerTag("server1")));
+ EXPECT_EQ(1, servers_deleted);
+
+ {
+ SCOPED_TRACE("DELETE audit entry for server");
+ testNewAuditEntry("dhcp4_server",
+ AuditEntry::ModificationType::DELETE,
+ "deleting a server");
+ }
+
+ // Make sure that the server is gone.
+ ASSERT_NO_THROW_LOG(returned_server = cbptr_->getServer4(ServerTag("server1")));
+ EXPECT_FALSE(returned_server);
+}
+
+void
+GenericConfigBackendDHCPv4Test::getAndDeleteAllServersTest() {
+ for (auto i = 1; i < test_servers_.size(); ++i) {
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer4(test_servers_[i]));
+ }
+
+ ServerCollection servers;
+ ASSERT_NO_THROW_LOG(servers = cbptr_->getAllServers4());
+ ASSERT_EQ(test_servers_.size() - 1, servers.size());
+
+ // All servers should have been returned.
+ EXPECT_TRUE(ServerFetcher::get(servers, ServerTag("server1")));
+ EXPECT_TRUE(ServerFetcher::get(servers, ServerTag("server2")));
+ EXPECT_TRUE(ServerFetcher::get(servers, ServerTag("server3")));
+
+ // The logical server all should not be returned. We merely return the
+ // user configured servers.
+ EXPECT_FALSE(ServerFetcher::get(servers, ServerTag()));
+
+ // Delete all servers and make sure they are gone.
+ uint64_t deleted_servers = 0;
+ ASSERT_NO_THROW_LOG(deleted_servers = cbptr_->deleteAllServers4());
+
+ ASSERT_NO_THROW_LOG(servers = cbptr_->getAllServers4());
+ EXPECT_TRUE(servers.empty());
+
+ // All servers should be gone.
+ EXPECT_FALSE(ServerFetcher::get(servers, ServerTag("server1")));
+ EXPECT_FALSE(ServerFetcher::get(servers, ServerTag("server2")));
+ EXPECT_FALSE(ServerFetcher::get(servers, ServerTag("server3")));
+
+ // The number of deleted server should be equal to the number of
+ // inserted servers. The logical 'all' server should be excluded.
+ EXPECT_EQ(test_servers_.size() - 1, deleted_servers);
+
+ EXPECT_EQ(1, countRows("dhcp4_server"));
+}
+
+void
+GenericConfigBackendDHCPv4Test::createUpdateDeleteGlobalParameter4Test() {
+ StampedValuePtr global_parameter = StampedValue::create("global", "whale");
+
+ // Explicitly set modification time to make sure that the time
+ // returned from the database is correct.
+ global_parameter->setModificationTime(timestamps_["yesterday"]);
+ cbptr_->createUpdateGlobalParameter4(ServerSelector::ALL(),
+ global_parameter);
+
+ {
+ SCOPED_TRACE("CREATE audit entry for global parameter");
+ testNewAuditEntry("dhcp4_global_parameter",
+ AuditEntry::ModificationType::CREATE,
+ "global parameter set");
+ }
+
+ // Verify returned parameter and the modification time.
+ StampedValuePtr returned_global_parameter =
+ cbptr_->getGlobalParameter4(ServerSelector::ALL(), "global");
+ ASSERT_TRUE(returned_global_parameter);
+ EXPECT_EQ("global", returned_global_parameter->getName());
+ EXPECT_EQ("whale", returned_global_parameter->getValue());
+ EXPECT_TRUE(returned_global_parameter->getModificationTime() ==
+ global_parameter->getModificationTime());
+ ASSERT_EQ(1, returned_global_parameter->getServerTags().size());
+ EXPECT_EQ("all", returned_global_parameter->getServerTags().begin()->get());
+
+ // Because we have added the global parameter for all servers, it
+ // should be also returned for the explicitly specified server.
+ returned_global_parameter = cbptr_->getGlobalParameter4(ServerSelector::ONE("server1"),
+ "global");
+ ASSERT_TRUE(returned_global_parameter);
+ EXPECT_EQ("global", returned_global_parameter->getName());
+ EXPECT_EQ("whale", returned_global_parameter->getValue());
+ EXPECT_TRUE(returned_global_parameter->getModificationTime() ==
+ global_parameter->getModificationTime());
+ ASSERT_EQ(1, returned_global_parameter->getServerTags().size());
+ EXPECT_EQ("all", returned_global_parameter->getServerTags().begin()->get());
+
+ // Check that the parameter is updated when selector is specified correctly.
+ global_parameter = StampedValue::create("global", "fish");
+ cbptr_->createUpdateGlobalParameter4(ServerSelector::ALL(),
+ global_parameter);
+ returned_global_parameter = cbptr_->getGlobalParameter4(ServerSelector::ALL(),
+ "global");
+ ASSERT_TRUE(returned_global_parameter);
+ EXPECT_EQ("global", returned_global_parameter->getName());
+ EXPECT_EQ("fish", returned_global_parameter->getValue());
+ EXPECT_TRUE(returned_global_parameter->getModificationTime() ==
+ global_parameter->getModificationTime());
+ ASSERT_EQ(1, returned_global_parameter->getServerTags().size());
+ EXPECT_EQ("all", returned_global_parameter->getServerTags().begin()->get());
+
+ {
+ SCOPED_TRACE("UPDATE audit entry for the global parameter");
+ testNewAuditEntry("dhcp4_global_parameter",
+ AuditEntry::ModificationType::UPDATE,
+ "global parameter set");
+ }
+
+ // Should not delete parameter specified for all servers if explicit
+ // server name is provided.
+ EXPECT_EQ(0, cbptr_->deleteGlobalParameter4(ServerSelector::ONE("server1"),
+ "global"));
+
+ // Delete parameter and make sure it is gone.
+ cbptr_->deleteGlobalParameter4(ServerSelector::ALL(), "global");
+ returned_global_parameter = cbptr_->getGlobalParameter4(ServerSelector::ALL(),
+ "global");
+ EXPECT_FALSE(returned_global_parameter);
+
+ {
+ SCOPED_TRACE("DELETE audit entry for the global parameter");
+ testNewAuditEntry("dhcp4_global_parameter",
+ AuditEntry::ModificationType::DELETE,
+ "global parameter deleted");
+ }
+}
+
+void
+GenericConfigBackendDHCPv4Test::globalParameters4WithServerTagsTest() {
+ // Create three global parameters having the same name.
+ StampedValuePtr global_parameter1 = StampedValue::create("global", "value1");
+ StampedValuePtr global_parameter2 = StampedValue::create("global", "value2");
+ StampedValuePtr global_parameter3 = StampedValue::create("global", "value3");
+
+ // Try to insert one of them and associate with non-existing server.
+ // This should fail because the server must be inserted first.
+ ASSERT_THROW(cbptr_->createUpdateGlobalParameter4(ServerSelector::ONE("server1"),
+ global_parameter1),
+ NullKeyError);
+
+ // Create two servers.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer4(test_servers_[1]));
+ {
+ SCOPED_TRACE("server1 is created");
+ testNewAuditEntry("dhcp4_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set");
+ }
+
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer4(test_servers_[2]));
+ {
+ SCOPED_TRACE("server2 is created");
+ testNewAuditEntry("dhcp4_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set");
+ }
+
+ // This time inserting the global parameters for the server1 and server2 should
+ // be successful.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateGlobalParameter4(ServerSelector::ONE("server1"),
+ global_parameter1));
+ {
+ SCOPED_TRACE("Global parameter for server1 is set");
+ // The value of 3 means there should be 3 audit entries available for the
+ // server1, two that indicate creation of the servers and one that we
+ // validate, which sets the global value.
+ testNewAuditEntry("dhcp4_global_parameter",
+ AuditEntry::ModificationType::CREATE,
+ "global parameter set",
+ ServerSelector::ONE("server1"),
+ 3, 1);
+ }
+
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateGlobalParameter4(ServerSelector::ONE("server2"),
+ global_parameter2));
+ {
+ SCOPED_TRACE("Global parameter for server2 is set");
+ // Same as in case of the server2, there should be 3 audit entries of
+ // which one we validate.
+ testNewAuditEntry("dhcp4_global_parameter",
+ AuditEntry::ModificationType::CREATE,
+ "global parameter set",
+ ServerSelector::ONE("server2"),
+ 3, 1);
+ }
+
+ // The last parameter is associated with all servers.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateGlobalParameter4(ServerSelector::ALL(),
+ global_parameter3));
+ {
+ SCOPED_TRACE("Global parameter for all servers is set");
+ // There should be one new audit entry for all servers. It indicates
+ // the insertion of the global value.
+ testNewAuditEntry("dhcp4_global_parameter",
+ AuditEntry::ModificationType::CREATE,
+ "global parameter set",
+ ServerSelector::ALL(),
+ 1, 1);
+ }
+
+ StampedValuePtr returned_global;
+
+ // Try to fetch the value specified for all servers.
+ ASSERT_NO_THROW_LOG(
+ returned_global = cbptr_->getGlobalParameter4(ServerSelector::ALL(),
+ "global")
+ );
+ ASSERT_TRUE(returned_global);
+ EXPECT_EQ(global_parameter3->getValue(), returned_global->getValue());
+ ASSERT_EQ(1, returned_global->getServerTags().size());
+ EXPECT_EQ("all", returned_global->getServerTags().begin()->get());
+
+ // Try to fetch the value specified for the server1. This should override the
+ // value specified for all servers.
+ ASSERT_NO_THROW_LOG(
+ returned_global = cbptr_->getGlobalParameter4(ServerSelector::ONE("server1"),
+ "global")
+ );
+ ASSERT_TRUE(returned_global);
+ EXPECT_EQ(global_parameter1->getValue(), returned_global->getValue());
+
+ ASSERT_EQ(1, returned_global->getServerTags().size());
+ EXPECT_EQ("server1", returned_global->getServerTags().begin()->get());
+
+ // The same in case of the server2.
+ ASSERT_NO_THROW_LOG(
+ returned_global = cbptr_->getGlobalParameter4(ServerSelector::ONE("server2"),
+ "global")
+ );
+ ASSERT_TRUE(returned_global);
+ EXPECT_EQ(global_parameter2->getValue(), returned_global->getValue());
+ ASSERT_EQ(1, returned_global->getServerTags().size());
+ EXPECT_EQ("server2", returned_global->getServerTags().begin()->get());
+
+ StampedValueCollection returned_globals;
+
+ // Try to fetch the collection of globals for the server1, server2 and server3.
+ // The server3 does not have an explicit value so for this server we should get
+ /// the value for 'all'.
+ ASSERT_NO_THROW_LOG(
+ returned_globals = cbptr_->getAllGlobalParameters4(ServerSelector::
+ MULTIPLE({ "server1", "server2",
+ "server3" }));
+ );
+ ASSERT_EQ(3, returned_globals.size());
+
+ // Capture the returned values into the map so as we can check the
+ // values against the servers.
+ std::map<std::string, std::string> values;
+ for (auto g = returned_globals.begin(); g != returned_globals.end(); ++g) {
+ ASSERT_EQ(1, (*g)->getServerTags().size());
+ values[(*g)->getServerTags().begin()->get()] = ((*g)->getValue());
+ }
+
+ ASSERT_EQ(3, values.size());
+ EXPECT_EQ(global_parameter1->getValue(), values["server1"]);
+ EXPECT_EQ(global_parameter2->getValue(), values["server2"]);
+ EXPECT_EQ(global_parameter3->getValue(), values["all"]);
+
+ // Try to fetch the collection of global parameters specified for all servers.
+ // This excludes the values specific to server1 and server2. It returns only the
+ // common ones.
+ ASSERT_NO_THROW_LOG(
+ returned_globals = cbptr_->getAllGlobalParameters4(ServerSelector::ALL())
+ );
+ ASSERT_EQ(1, returned_globals.size());
+ returned_global = *returned_globals.begin();
+ EXPECT_EQ(global_parameter3->getValue(), returned_global->getValue());
+ ASSERT_EQ(1, returned_global->getServerTags().size());
+ EXPECT_EQ("all", returned_global->getServerTags().begin()->get());
+
+ // Delete the server1. It should remove associations of this server with the
+ // global parameter and the global parameter itself.
+ ASSERT_NO_THROW_LOG(cbptr_->deleteServer4(ServerTag("server1")));
+ ASSERT_NO_THROW_LOG(
+ returned_globals = cbptr_->getAllGlobalParameters4(ServerSelector::ONE("server1"))
+ );
+ ASSERT_EQ(1, returned_globals.size());
+ returned_global = *returned_globals.begin();
+ // As a result, the value fetched for the server1 should be the one available for
+ // all servers, rather than the one dedicated for server1. The association of
+ // the server1 specific value with the server1 should be gone.
+ EXPECT_EQ(global_parameter3->getValue(), returned_global->getValue());
+ ASSERT_EQ(1, returned_global->getServerTags().size());
+ EXPECT_EQ("all", returned_global->getServerTags().begin()->get());
+
+ {
+ SCOPED_TRACE("DELETE audit entry for the global parameter after server deletion");
+ // We expect two new audit entries for the server1, one indicating that the
+ // server has been deleted and another one indicating that the corresponding
+ // global value has been deleted. We check the latter entry.
+ testNewAuditEntry("dhcp4_global_parameter",
+ AuditEntry::ModificationType::DELETE,
+ "deleting a server", ServerSelector::ONE("server1"),
+ 2, 1);
+ }
+
+ // Attempt to delete global parameter for server1.
+ uint64_t deleted_num = 0;
+ ASSERT_NO_THROW_LOG(deleted_num = cbptr_->deleteGlobalParameter4(ServerSelector::ONE("server1"),
+ "global"));
+ // No parameters should be deleted. In particular, the parameter for the logical
+ // server 'all' should not be deleted.
+ EXPECT_EQ(0, deleted_num);
+
+ // Deleting the existing value for server2 should succeed.
+ ASSERT_NO_THROW_LOG(deleted_num = cbptr_->deleteGlobalParameter4(ServerSelector::ONE("server2"),
+ "global"));
+ EXPECT_EQ(1, deleted_num);
+
+ // Create it again to test that deletion of all server removes this too.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateGlobalParameter4(ServerSelector::ONE("server2"),
+ global_parameter2));
+
+ // Delete all servers, except 'all'.
+ ASSERT_NO_THROW_LOG(deleted_num = cbptr_->deleteAllServers4());
+ ASSERT_NO_THROW_LOG(
+ returned_globals = cbptr_->getAllGlobalParameters4(ServerSelector::ALL())
+ );
+ EXPECT_EQ(1, deleted_num);
+ ASSERT_EQ(1, returned_globals.size());
+ returned_global = *returned_globals.begin();
+ // The common value for all servers should still be available because 'all'
+ // logical server should not be deleted.
+ EXPECT_EQ(global_parameter3->getValue(), returned_global->getValue());
+ ASSERT_EQ(1, returned_global->getServerTags().size());
+ EXPECT_EQ("all", returned_global->getServerTags().begin()->get());
+
+ {
+ SCOPED_TRACE("DELETE audit entry for the global parameter after deletion of"
+ " all servers");
+ // There should be 4 new audit entries. One for deleting the global, one for
+ // re-creating it, one for deleting the server2 and one for deleting the
+ // global again as a result of deleting the server2.
+ testNewAuditEntry("dhcp4_global_parameter",
+ AuditEntry::ModificationType::DELETE,
+ "deleting all servers", ServerSelector::ONE("server2"),
+ 4, 1);
+ }
+}
+
+void
+GenericConfigBackendDHCPv4Test::getAllGlobalParameters4Test() {
+ // Create 3 parameters and put them into the database.
+ cbptr_->createUpdateGlobalParameter4(ServerSelector::ALL(),
+ StampedValue::create("name1", "value1"));
+ cbptr_->createUpdateGlobalParameter4(ServerSelector::ALL(),
+ StampedValue::create("name2", Element::create(static_cast<int64_t>(65))));
+ cbptr_->createUpdateGlobalParameter4(ServerSelector::ALL(),
+ StampedValue::create("name3", "value3"));
+ cbptr_->createUpdateGlobalParameter4(ServerSelector::ALL(),
+ StampedValue::create("name4", Element::create(static_cast<bool>(true))));
+ cbptr_->createUpdateGlobalParameter4(ServerSelector::ALL(),
+ StampedValue::create("name5", Element::create(static_cast<double>(1.65))));
+
+ // Fetch all parameters.
+ auto parameters = cbptr_->getAllGlobalParameters4(ServerSelector::ALL());
+ ASSERT_EQ(5, parameters.size());
+
+ const auto& parameters_index = parameters.get<StampedValueNameIndexTag>();
+
+ // Verify their values.
+ EXPECT_EQ("value1", (*parameters_index.find("name1"))->getValue());
+ EXPECT_EQ(65, (*parameters_index.find("name2"))->getIntegerValue());
+ EXPECT_EQ("value3", (*parameters_index.find("name3"))->getValue());
+ EXPECT_TRUE((*parameters_index.find("name4"))->getBoolValue());
+ EXPECT_EQ(1.65, (*parameters_index.find("name5"))->getDoubleValue());
+
+ for (auto param = parameters_index.begin(); param != parameters_index.end();
+ ++param) {
+ ASSERT_EQ(1, (*param)->getServerTags().size());
+ EXPECT_EQ("all", (*param)->getServerTags().begin()->get());
+ }
+
+ // Should be able to fetch these parameters when explicitly providing
+ // the server tag.
+ parameters = cbptr_->getAllGlobalParameters4(ServerSelector::ONE("server1"));
+ EXPECT_EQ(5, parameters.size());
+
+ // Deleting global parameters with non-matching server selector
+ // should fail.
+ EXPECT_EQ(0, cbptr_->deleteAllGlobalParameters4(ServerSelector::ONE("server1")));
+
+ // Delete all parameters and make sure they are gone.
+ EXPECT_EQ(5, cbptr_->deleteAllGlobalParameters4(ServerSelector::ALL()));
+ parameters = cbptr_->getAllGlobalParameters4(ServerSelector::ALL());
+ EXPECT_TRUE(parameters.empty());
+}
+
+void
+GenericConfigBackendDHCPv4Test::getModifiedGlobalParameters4Test() {
+ // Create 3 global parameters and assign modification times:
+ // "yesterday", "today" and "tomorrow" respectively.
+ StampedValuePtr value = StampedValue::create("name1", "value1");
+ value->setModificationTime(timestamps_["yesterday"]);
+ cbptr_->createUpdateGlobalParameter4(ServerSelector::ALL(),
+ value);
+
+ value = StampedValue::create("name2", Element::create(static_cast<int64_t>(65)));
+ value->setModificationTime(timestamps_["today"]);
+ cbptr_->createUpdateGlobalParameter4(ServerSelector::ALL(),
+ value);
+
+ value = StampedValue::create("name3", "value3");
+ value->setModificationTime(timestamps_["tomorrow"]);
+ cbptr_->createUpdateGlobalParameter4(ServerSelector::ALL(),
+ value);
+
+ // Get parameters modified after "today".
+ auto parameters = cbptr_->getModifiedGlobalParameters4(ServerSelector::ALL(),
+ timestamps_["after today"]);
+
+ const auto& parameters_index = parameters.get<StampedValueNameIndexTag>();
+
+ // It should be the one modified "tomorrow".
+ ASSERT_EQ(1, parameters_index.size());
+
+ auto parameter = parameters_index.find("name3");
+ ASSERT_FALSE(parameter == parameters_index.end());
+
+ ASSERT_TRUE(*parameter);
+ EXPECT_EQ("value3", (*parameter)->getValue());
+
+ // Should be able to fetct these parameters when explicitly providing
+ // the server tag.
+ parameters = cbptr_->getModifiedGlobalParameters4(ServerSelector::ONE("server1"),
+ timestamps_["after today"]);
+ EXPECT_EQ(1, parameters.size());
+}
+
+void
+GenericConfigBackendDHCPv4Test::nullKeyErrorTest() {
+ // Create a global parameter (it should work with any object type).
+ StampedValuePtr global_parameter = StampedValue::create("global", "value");
+
+ // Try to insert it and associate with non-existing server.
+ std::string msg;
+ try {
+ cbptr_->createUpdateGlobalParameter4(ServerSelector::ONE("server1"),
+ global_parameter);
+ msg = "got no exception";
+ } catch (const NullKeyError& ex) {
+ msg = ex.what();
+ } catch (const std::exception&) {
+ msg = "got another exception";
+ }
+ EXPECT_EQ("server 'server1' does not exist", msg);
+}
+
+void
+GenericConfigBackendDHCPv4Test::createUpdateSubnet4SelectorsTest() {
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer4(test_servers_[0]));
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer4(test_servers_[2]));
+
+ // Supported selectors.
+ Subnet4Ptr subnet = test_subnets_[0];
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet4(ServerSelector::ALL(),
+ subnet));
+ subnet = test_subnets_[2];
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet4(ServerSelector::ONE("server1"),
+ subnet));
+ subnet = test_subnets_[3];
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet4(ServerSelector::MULTIPLE({ "server1", "server2" }),
+ subnet));
+
+ // Not supported server selectors.
+ ASSERT_THROW(cbptr_->createUpdateSubnet4(ServerSelector::ANY(), subnet),
+ isc::InvalidOperation);
+
+ // Not implemented server selectors.
+ ASSERT_THROW(cbptr_->createUpdateSubnet4(ServerSelector::UNASSIGNED(),
+ subnet),
+ isc::NotImplemented);
+}
+
+void
+GenericConfigBackendDHCPv4Test::getSubnet4Test() {
+ // Insert the server2 into the database.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer4(test_servers_[2]));
+ {
+ SCOPED_TRACE("CREATE audit entry for server");
+ testNewAuditEntry("dhcp4_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set");
+ }
+
+ auto subnet = test_subnets_[0];
+ auto subnet2 = test_subnets_[2];
+
+ // An attempt to add a subnet to a non-existing server (server1) should fail.
+ ASSERT_THROW(cbptr_->createUpdateSubnet4(ServerSelector::MULTIPLE({ "server1", "server2" }),
+ subnet2),
+ NullKeyError);
+
+ // The subnet shouldn't have been added, even though one of the servers exists.
+ Subnet4Ptr returned_subnet;
+ ASSERT_NO_THROW_LOG(returned_subnet = cbptr_->getSubnet4(ServerSelector::ONE("server2"),
+ subnet2->getID()));
+ EXPECT_FALSE(returned_subnet);
+
+ // Insert two subnets, one for all servers and one for server2.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet4(ServerSelector::ALL(), subnet));
+ {
+ SCOPED_TRACE("A. CREATE audit entry for the subnet");
+ testNewAuditEntry("dhcp4_subnet",
+ AuditEntry::ModificationType::CREATE,
+ "subnet set");
+ }
+
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet4(ServerSelector::ONE("server2"), subnet2));
+ {
+ SCOPED_TRACE("B. CREATE audit entry for the subnet");
+ testNewAuditEntry("dhcp4_subnet",
+ AuditEntry::ModificationType::CREATE,
+ "subnet set", ServerSelector::ONE("subnet2"),
+ 2, 1);
+ }
+
+ // We are not going to support selection of a single entry for multiple servers.
+ ASSERT_THROW(cbptr_->getSubnet4(ServerSelector::MULTIPLE({ "server1", "server2" }),
+ subnet->getID()),
+ isc::InvalidOperation);
+
+ ASSERT_THROW(cbptr_->getSubnet4(ServerSelector::MULTIPLE({ "server1", "server2" }),
+ subnet->toText()),
+ isc::InvalidOperation);
+
+ // Test that this subnet will be fetched for various server selectors.
+ auto test_get_subnet = [this, &subnet] (const std::string& test_case_name,
+ const ServerSelector& server_selector,
+ const std::string& expected_tag = ServerTag::ALL) {
+ SCOPED_TRACE(test_case_name);
+
+ // Test fetching subnet by id.
+ Subnet4Ptr returned_subnet;
+ ASSERT_NO_THROW_LOG(returned_subnet = cbptr_->getSubnet4(server_selector, subnet->getID()));
+ ASSERT_TRUE(returned_subnet);
+
+ ASSERT_EQ(1, returned_subnet->getServerTags().size());
+ EXPECT_TRUE(returned_subnet->hasServerTag(ServerTag(expected_tag)));
+
+ ASSERT_EQ(subnet->toElement()->str(), returned_subnet->toElement()->str());
+
+ // Test fetching subnet by prefix.
+ ASSERT_NO_THROW_LOG(returned_subnet = cbptr_->getSubnet4(server_selector,
+ subnet->toText()));
+ ASSERT_TRUE(returned_subnet);
+
+ ASSERT_EQ(1, returned_subnet->getServerTags().size());
+ EXPECT_TRUE(returned_subnet->hasServerTag(ServerTag(expected_tag)));
+
+ EXPECT_EQ(subnet->toElement()->str(), returned_subnet->toElement()->str());
+ };
+
+ {
+ SCOPED_TRACE("testing various server selectors before update");
+ test_get_subnet("all servers", ServerSelector::ALL());
+ test_get_subnet("one server", ServerSelector::ONE("server1"));
+ test_get_subnet("any server", ServerSelector::ANY());
+ }
+
+ subnet = subnet2;
+ {
+ SCOPED_TRACE("testing server selectors for another server");
+ test_get_subnet("one server", ServerSelector::ONE("server2"), "server2");
+ test_get_subnet("any server", ServerSelector::ANY(), "server2");
+ }
+
+ // Update the subnet in the database (both use the same ID).
+ subnet = test_subnets_[1];
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet4(ServerSelector::ALL(), subnet));
+ {
+ SCOPED_TRACE("C. CREATE audit entry for the subnet");
+ testNewAuditEntry("dhcp4_subnet",
+ AuditEntry::ModificationType::UPDATE,
+ "subnet set");
+ }
+
+ {
+ SCOPED_TRACE("testing various server selectors after update");
+ test_get_subnet("all servers", ServerSelector::ALL());
+ test_get_subnet("one server", ServerSelector::ONE("server1"));
+ test_get_subnet("any server", ServerSelector::ANY());
+ }
+
+ // The server2 specific subnet should not be returned if the server selector
+ // is not matching.
+ EXPECT_FALSE(cbptr_->getSubnet4(ServerSelector::ALL(), subnet2->getID()));
+ EXPECT_FALSE(cbptr_->getSubnet4(ServerSelector::ALL(), subnet2->toText()));
+ EXPECT_FALSE(cbptr_->getSubnet4(ServerSelector::ONE("server1"), subnet2->getID()));
+ EXPECT_FALSE(cbptr_->getSubnet4(ServerSelector::ONE("server1"), subnet2->toText()));
+
+ // Update the subnet in the database (both use the same prefix).
+ subnet2.reset(new Subnet4(IOAddress("192.0.3.0"),
+ 24, 30, 40, 60, 8192));
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet4(ServerSelector::ONE("server2"), subnet2));
+
+ // Fetch again and verify.
+ returned_subnet = cbptr_->getSubnet4(ServerSelector::ONE("server2"), subnet2->toText());
+ ASSERT_TRUE(returned_subnet);
+ EXPECT_EQ(subnet2->toElement()->str(), returned_subnet->toElement()->str());
+
+ // Update the subnet when it conflicts same id and same prefix both
+ // with different subnets. This should throw.
+ // Subnets are 10.0.0.0/8 id 1024 and 192.0.3.0/24 id 8192
+ subnet2.reset(new Subnet4(IOAddress("10.0.0.0"),
+ 8, 30, 40, 60, 8192));
+ ASSERT_THROW(cbptr_->createUpdateSubnet4(ServerSelector::ONE("server2"), subnet2),
+ DuplicateEntry);
+}
+
+void
+GenericConfigBackendDHCPv4Test::getSubnet4byIdSelectorsTest() {
+ // Supported selectors.
+ ASSERT_NO_THROW_LOG(cbptr_->getSubnet4(ServerSelector::ANY(), SubnetID(1)));
+ ASSERT_NO_THROW_LOG(cbptr_->getSubnet4(ServerSelector::UNASSIGNED(), SubnetID(1)));
+ ASSERT_NO_THROW_LOG(cbptr_->getSubnet4(ServerSelector::ALL(), SubnetID(1)));
+ ASSERT_NO_THROW_LOG(cbptr_->getSubnet4(ServerSelector::ONE("server1"), SubnetID(1)));
+
+ // Not supported selectors.
+ ASSERT_THROW(cbptr_->getSubnet4(ServerSelector::MULTIPLE({ "server1", "server2" }),
+ SubnetID(1)),
+ isc::InvalidOperation);
+}
+
+void
+GenericConfigBackendDHCPv4Test::getSubnet4WithOptionalUnspecifiedTest() {
+ // Create a subnet and wrap it within a shared network. It is important
+ // to have the shared network to verify that the subnet doesn't inherit
+ // the values of the shared network but stores the NULL values in the
+ // for those parameters that are unspecified on the subnet level.
+ Subnet4Ptr subnet = test_subnets_[2];
+ SharedNetwork4Ptr shared_network = test_networks_[0];
+ shared_network->add(subnet);
+
+ // Need to add the shared network to the database because otherwise
+ // the subnet foreign key would fail.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork4(ServerSelector::ALL(), shared_network));
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet4(ServerSelector::ALL(), subnet));
+
+ // Fetch this subnet by subnet identifier.
+ Subnet4Ptr returned_subnet;
+ ASSERT_NO_THROW_LOG(returned_subnet = cbptr_->getSubnet4(ServerSelector::ALL(),
+ subnet->getID()));
+ ASSERT_TRUE(returned_subnet);
+
+ EXPECT_TRUE(returned_subnet->getIface().unspecified());
+ EXPECT_TRUE(returned_subnet->getIface().empty());
+
+ EXPECT_TRUE(returned_subnet->getClientClass().unspecified());
+ EXPECT_TRUE(returned_subnet->getClientClass().empty());
+
+ EXPECT_TRUE(returned_subnet->getValid().unspecified());
+ EXPECT_EQ(0, returned_subnet->getValid().get());
+
+ EXPECT_TRUE(returned_subnet->getT1().unspecified());
+ EXPECT_EQ(0, returned_subnet->getT1().get());
+
+ EXPECT_TRUE(returned_subnet->getT2().unspecified());
+ EXPECT_EQ(0, returned_subnet->getT2().get());
+
+ EXPECT_TRUE(returned_subnet->getReservationsGlobal().unspecified());
+ EXPECT_FALSE(returned_subnet->getReservationsGlobal().get());
+
+ EXPECT_TRUE(returned_subnet->getReservationsInSubnet().unspecified());
+ EXPECT_TRUE(returned_subnet->getReservationsInSubnet().get());
+
+ EXPECT_TRUE(returned_subnet->getReservationsOutOfPool().unspecified());
+ EXPECT_FALSE(returned_subnet->getReservationsOutOfPool().get());
+
+ EXPECT_TRUE(returned_subnet->getCalculateTeeTimes().unspecified());
+ EXPECT_FALSE(returned_subnet->getCalculateTeeTimes().get());
+
+ EXPECT_TRUE(returned_subnet->getT1Percent().unspecified());
+ EXPECT_EQ(0.0, returned_subnet->getT1Percent().get());
+
+ EXPECT_TRUE(returned_subnet->getT2Percent().unspecified());
+ EXPECT_EQ(0.0, returned_subnet->getT2Percent().get());
+
+ EXPECT_TRUE(returned_subnet->getMatchClientId().unspecified());
+ EXPECT_TRUE(returned_subnet->getMatchClientId().get());
+
+ EXPECT_TRUE(returned_subnet->getAuthoritative().unspecified());
+ EXPECT_FALSE(returned_subnet->getAuthoritative().get());
+
+ EXPECT_TRUE(returned_subnet->getSiaddr().unspecified());
+ EXPECT_TRUE(returned_subnet->getSiaddr().get().isV4Zero());
+
+ EXPECT_TRUE(returned_subnet->getSname().unspecified());
+ EXPECT_TRUE(returned_subnet->getSname().empty());
+
+ EXPECT_TRUE(returned_subnet->getFilename().unspecified());
+ EXPECT_TRUE(returned_subnet->getFilename().empty());
+
+ EXPECT_FALSE(returned_subnet->get4o6().enabled());
+
+ EXPECT_TRUE(returned_subnet->get4o6().getIface4o6().unspecified());
+ EXPECT_TRUE(returned_subnet->get4o6().getIface4o6().empty());
+
+ EXPECT_TRUE(returned_subnet->get4o6().getSubnet4o6().unspecified());
+ EXPECT_TRUE(returned_subnet->get4o6().getSubnet4o6().get().first.isV6Zero());
+ EXPECT_EQ(128, returned_subnet->get4o6().getSubnet4o6().get().second);
+
+ EXPECT_FALSE(returned_subnet->getDdnsSendUpdates().unspecified());
+ EXPECT_TRUE(returned_subnet->getDdnsSendUpdates().get());
+
+ EXPECT_FALSE(returned_subnet->getDdnsOverrideNoUpdate().unspecified());
+ EXPECT_TRUE(returned_subnet->getDdnsOverrideNoUpdate().get());
+
+ EXPECT_FALSE(returned_subnet->getDdnsOverrideClientUpdate().unspecified());
+ EXPECT_FALSE(returned_subnet->getDdnsOverrideClientUpdate().get());
+
+ EXPECT_FALSE(returned_subnet->getDdnsReplaceClientNameMode().unspecified());
+ EXPECT_EQ(D2ClientConfig::ReplaceClientNameMode::RCM_WHEN_PRESENT,
+ returned_subnet->getDdnsReplaceClientNameMode().get());
+
+ EXPECT_FALSE(returned_subnet->getDdnsGeneratedPrefix().unspecified());
+ EXPECT_EQ("myhost", returned_subnet->getDdnsGeneratedPrefix().get());
+
+ EXPECT_FALSE(returned_subnet->getDdnsQualifyingSuffix().unspecified());
+ EXPECT_EQ("example.org", returned_subnet->getDdnsQualifyingSuffix().get());
+
+ // The easiest way to verify whether the returned subnet matches the inserted
+ // subnet is to convert both to text.
+ EXPECT_EQ(subnet->toElement()->str(), returned_subnet->toElement()->str());
+}
+
+void
+GenericConfigBackendDHCPv4Test::getSubnet4SharedNetworkTest() {
+ Subnet4Ptr subnet = test_subnets_[0];
+ SharedNetwork4Ptr shared_network = test_networks_[0];
+
+ // Add subnet to a shared network.
+ shared_network->add(subnet);
+
+ // Store shared network in the database.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork4(ServerSelector::ALL(),
+ shared_network));
+
+ // Store subnet associated with the shared network in the database.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet4(ServerSelector::ALL(), subnet));
+
+ // Fetch this subnet by subnet identifier.
+ Subnet4Ptr returned_subnet = cbptr_->getSubnet4(ServerSelector::ALL(),
+ test_subnets_[0]->getID());
+ ASSERT_TRUE(returned_subnet);
+ ASSERT_EQ(1, returned_subnet->getServerTags().size());
+ EXPECT_EQ("all", returned_subnet->getServerTags().begin()->get());
+
+ // The easiest way to verify whether the returned subnet matches the inserted
+ // subnet is to convert both to text.
+ EXPECT_EQ(subnet->toElement()->str(), returned_subnet->toElement()->str());
+
+ // However, the check above doesn't verify whether shared network name was
+ // correctly returned from the database.
+ EXPECT_EQ(shared_network->getName(), returned_subnet->getSharedNetworkName());
+}
+
+void
+GenericConfigBackendDHCPv4Test::getSubnet4ByPrefixTest() {
+ // Insert subnet to the database.
+ Subnet4Ptr subnet = test_subnets_[0];
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet4(ServerSelector::ALL(), subnet));
+
+ // Fetch the subnet by prefix.
+ Subnet4Ptr returned_subnet = cbptr_->getSubnet4(ServerSelector::ALL(),
+ "192.0.2.0/24");
+ ASSERT_TRUE(returned_subnet);
+ ASSERT_EQ(1, returned_subnet->getServerTags().size());
+ EXPECT_EQ("all", returned_subnet->getServerTags().begin()->get());
+
+ // Verify subnet contents.
+ EXPECT_EQ(subnet->toElement()->str(), returned_subnet->toElement()->str());
+
+ // Fetching the subnet for an explicitly specified server tag should
+ // succeed too.
+ returned_subnet = cbptr_->getSubnet4(ServerSelector::ONE("server1"),
+ "192.0.2.0/24");
+ EXPECT_EQ(subnet->toElement()->str(), returned_subnet->toElement()->str());
+}
+
+void
+GenericConfigBackendDHCPv4Test::getSubnet4byPrefixSelectorsTest() {
+ // Supported selectors.
+ ASSERT_NO_THROW_LOG(cbptr_->getSubnet4(ServerSelector::ANY(), "192.0.2.0/24"));
+ ASSERT_NO_THROW_LOG(cbptr_->getSubnet4(ServerSelector::UNASSIGNED(), "192.0.2.0/24"));
+ ASSERT_NO_THROW_LOG(cbptr_->getSubnet4(ServerSelector::ALL(), "192.0.2.0/24"));
+ ASSERT_NO_THROW_LOG(cbptr_->getSubnet4(ServerSelector::ONE("server1"), "192.0.2.0/24"));
+
+ // Not supported selectors.
+ ASSERT_THROW(cbptr_->getSubnet4(ServerSelector::MULTIPLE({ "server1", "server2" }),
+ "192.0.2.0/24"),
+ isc::InvalidOperation);
+}
+
+void
+GenericConfigBackendDHCPv4Test::getAllSubnets4Test() {
+ // Insert test subnets into the database. Note that the second subnet will
+ // overwrite the first subnet as they use the same ID.
+ for (auto subnet : test_subnets_) {
+ cbptr_->createUpdateSubnet4(ServerSelector::ALL(), subnet);
+
+ // That subnet overrides the first subnet so the audit entry should
+ // indicate an update.
+ if (subnet->toText() == "10.0.0.0/8") {
+ SCOPED_TRACE("UPDATE audit entry for the subnet " + subnet->toText());
+ testNewAuditEntry("dhcp4_subnet",
+ AuditEntry::ModificationType::UPDATE,
+ "subnet set");
+
+ } else {
+ SCOPED_TRACE("CREATE audit entry for the subnet " + subnet->toText());
+ testNewAuditEntry("dhcp4_subnet",
+ AuditEntry::ModificationType::CREATE,
+ "subnet set");
+ }
+ }
+
+ // Fetch all subnets.
+ Subnet4Collection subnets = cbptr_->getAllSubnets4(ServerSelector::ALL());
+ ASSERT_EQ(test_subnets_.size() - 1, subnets.size());
+
+ // All subnets should also be returned for explicitly specified server tag.
+ subnets = cbptr_->getAllSubnets4(ServerSelector::ONE("server1"));
+ ASSERT_EQ(test_subnets_.size() - 1, subnets.size());
+
+ // See if the subnets are returned ok.
+ auto subnet_it = subnets.begin();
+ for (auto i = 0; i < subnets.size(); ++i, ++subnet_it) {
+ ASSERT_EQ(1, (*subnet_it)->getServerTags().size());
+ EXPECT_EQ("all", (*subnet_it)->getServerTags().begin()->get());
+ EXPECT_EQ(test_subnets_[i + 1]->toElement()->str(),
+ (*subnet_it)->toElement()->str());
+ }
+
+ // Attempt to remove the non existing subnet should return 0.
+ EXPECT_EQ(0, cbptr_->deleteSubnet4(ServerSelector::ALL(), 22));
+ EXPECT_EQ(0, cbptr_->deleteSubnet4(ServerSelector::ALL(),
+ "155.0.3.0/24"));
+ // All subnets should be still there.
+ ASSERT_EQ(test_subnets_.size() - 1, subnets.size());
+
+ // Should not delete the subnet for explicit server tag because
+ // our subnet is for all servers.
+ EXPECT_EQ(0, cbptr_->deleteSubnet4(ServerSelector::ONE("server1"),
+ test_subnets_[1]->getID()));
+
+ // Also, verify that behavior when deleting by prefix.
+ EXPECT_EQ(0, cbptr_->deleteSubnet4(ServerSelector::ONE("server1"),
+ test_subnets_[2]->toText()));
+
+ // Same for all subnets.
+ EXPECT_EQ(0, cbptr_->deleteAllSubnets4(ServerSelector::ONE("server1")));
+
+ // Delete first subnet by id and verify that it is gone.
+ EXPECT_EQ(1, cbptr_->deleteSubnet4(ServerSelector::ALL(),
+ test_subnets_[1]->getID()));
+
+ {
+ SCOPED_TRACE("DELETE first subnet audit entry");
+ testNewAuditEntry("dhcp4_subnet",
+ AuditEntry::ModificationType::DELETE,
+ "subnet deleted");
+ }
+
+ subnets = cbptr_->getAllSubnets4(ServerSelector::ALL());
+ ASSERT_EQ(test_subnets_.size() - 2, subnets.size());
+
+ // Delete second subnet by prefix and verify it is gone.
+ EXPECT_EQ(1, cbptr_->deleteSubnet4(ServerSelector::ALL(),
+ test_subnets_[2]->toText()));
+ subnets = cbptr_->getAllSubnets4(ServerSelector::ALL());
+ ASSERT_EQ(test_subnets_.size() - 3, subnets.size());
+
+ {
+ SCOPED_TRACE("DELETE second subnet audit entry");
+ testNewAuditEntry("dhcp4_subnet",
+ AuditEntry::ModificationType::DELETE,
+ "subnet deleted");
+ }
+
+ // Delete all.
+ EXPECT_EQ(1, cbptr_->deleteAllSubnets4(ServerSelector::ALL()));
+ subnets = cbptr_->getAllSubnets4(ServerSelector::ALL());
+ ASSERT_TRUE(subnets.empty());
+
+ {
+ SCOPED_TRACE("DELETE all subnets audit entry");
+ testNewAuditEntry("dhcp4_subnet",
+ AuditEntry::ModificationType::DELETE,
+ "deleted all subnets");
+ }
+}
+
+void
+GenericConfigBackendDHCPv4Test::getAllSubnets4SelectorsTest() {
+ // Supported selectors.
+ ASSERT_NO_THROW_LOG(cbptr_->getAllSubnets4(ServerSelector::UNASSIGNED()));
+ ASSERT_NO_THROW_LOG(cbptr_->getAllSubnets4(ServerSelector::ALL()));
+ ASSERT_NO_THROW_LOG(cbptr_->getAllSubnets4(ServerSelector::ONE("server1")));
+ ASSERT_NO_THROW_LOG(cbptr_->getAllSubnets4(ServerSelector::MULTIPLE({ "server1", "server2" })));
+
+ // Not supported selectors.
+ ASSERT_THROW(cbptr_->getAllSubnets4(ServerSelector::ANY()), isc::InvalidOperation);
+}
+
+void
+GenericConfigBackendDHCPv4Test::getAllSubnets4WithServerTagsTest() {
+ auto subnet1 = test_subnets_[0];
+ auto subnet2 = test_subnets_[2];
+ auto subnet3 = test_subnets_[3];
+
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer4(test_servers_[0]));
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer4(test_servers_[2]));
+
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet4(ServerSelector::ALL(),
+ subnet1));
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet4(ServerSelector::ONE("server1"),
+ subnet2));
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet4(ServerSelector::MULTIPLE({ "server1", "server2" }),
+ subnet3));
+
+ Subnet4Collection subnets;
+
+ // All three subnets are associated with the server1.
+ ASSERT_NO_THROW_LOG(subnets = cbptr_->getAllSubnets4(ServerSelector::ONE("server1")));
+ EXPECT_EQ(3, subnets.size());
+
+ // First subnet is associated with all servers.
+ auto returned_subnet = SubnetFetcher4::get(subnets, SubnetID(1024));
+ ASSERT_TRUE(returned_subnet);
+ EXPECT_TRUE(returned_subnet->hasAllServerTag());
+ EXPECT_FALSE(returned_subnet->hasServerTag(ServerTag("server1")));
+ EXPECT_FALSE(returned_subnet->hasServerTag(ServerTag("server2")));
+
+ // Second subnet is only associated with the server1.
+ returned_subnet = SubnetFetcher4::get(subnets, SubnetID(2048));
+ ASSERT_TRUE(returned_subnet);
+ EXPECT_FALSE(returned_subnet->hasAllServerTag());
+ EXPECT_TRUE(returned_subnet->hasServerTag(ServerTag("server1")));
+ EXPECT_FALSE(returned_subnet->hasServerTag(ServerTag("server2")));
+
+ // Third subnet is associated with both server1 and server2.
+ returned_subnet = SubnetFetcher4::get(subnets, SubnetID(4096));
+ ASSERT_TRUE(returned_subnet);
+ EXPECT_FALSE(returned_subnet->hasAllServerTag());
+ EXPECT_TRUE(returned_subnet->hasServerTag(ServerTag("server1")));
+ EXPECT_TRUE(returned_subnet->hasServerTag(ServerTag("server2")));
+
+ // For server2 we should only get two subnets, i.e. first and last.
+ ASSERT_NO_THROW_LOG(subnets = cbptr_->getAllSubnets4(ServerSelector::ONE("server2")));
+ EXPECT_EQ(2, subnets.size());
+
+ // First subnet is associated with all servers.
+ returned_subnet = SubnetFetcher4::get(subnets, SubnetID(1024));
+ ASSERT_TRUE(returned_subnet);
+ EXPECT_TRUE(returned_subnet->hasAllServerTag());
+ EXPECT_FALSE(returned_subnet->hasServerTag(ServerTag("server1")));
+ EXPECT_FALSE(returned_subnet->hasServerTag(ServerTag("server2")));
+
+ // Last subnet is associated with server1 and server2.
+ returned_subnet = SubnetFetcher4::get(subnets, SubnetID(4096));
+ ASSERT_TRUE(returned_subnet);
+ EXPECT_FALSE(returned_subnet->hasAllServerTag());
+ EXPECT_TRUE(returned_subnet->hasServerTag(ServerTag("server1")));
+ EXPECT_TRUE(returned_subnet->hasServerTag(ServerTag("server2")));
+
+ // Only the first subnet is associated with all servers.
+ ASSERT_NO_THROW_LOG(subnets = cbptr_->getAllSubnets4(ServerSelector::ALL()));
+ EXPECT_EQ(1, subnets.size());
+
+ returned_subnet = SubnetFetcher4::get(subnets, SubnetID(1024));
+ ASSERT_TRUE(returned_subnet);
+ EXPECT_TRUE(returned_subnet->hasAllServerTag());
+ EXPECT_FALSE(returned_subnet->hasServerTag(ServerTag("server1")));
+ EXPECT_FALSE(returned_subnet->hasServerTag(ServerTag("server2")));
+}
+
+void
+GenericConfigBackendDHCPv4Test::getModifiedSubnets4SelectorsTest() {
+ // Supported selectors.
+ ASSERT_NO_THROW_LOG(cbptr_->getModifiedSubnets4(ServerSelector::UNASSIGNED(),
+ timestamps_["yesterday"]));
+ ASSERT_NO_THROW_LOG(cbptr_->getModifiedSubnets4(ServerSelector::ALL(),
+ timestamps_["yesterday"]));
+ ASSERT_NO_THROW_LOG(cbptr_->getModifiedSubnets4(ServerSelector::ONE("server1"),
+ timestamps_["yesterday"]));
+ ASSERT_NO_THROW_LOG(cbptr_->getModifiedSubnets4(ServerSelector::MULTIPLE({ "server1", "server2" }),
+ timestamps_["yesterday"]));
+
+ // Not supported selectors.
+ EXPECT_THROW(cbptr_->getModifiedSubnets4(ServerSelector::ANY(),
+ timestamps_["yesterday"]),
+ isc::InvalidOperation);
+}
+
+void
+GenericConfigBackendDHCPv4Test::deleteSubnet4Test() {
+ // Create two servers in the database.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer4(test_servers_[0]));
+ {
+ SCOPED_TRACE("CREATE audit entry for server");
+ testNewAuditEntry("dhcp4_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set");
+ }
+
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer4(test_servers_[2]));
+ {
+ SCOPED_TRACE("CREATE audit entry for server");
+ testNewAuditEntry("dhcp4_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set");
+ }
+
+ auto subnet1 = test_subnets_[0];
+ auto subnet2 = test_subnets_[2];
+ auto subnet3 = test_subnets_[3];
+
+ auto create_test_subnets = [&] () {
+ // Insert three subnets, one for all servers, one for server2 and one for two
+ // servers: server1 and server2.
+ ASSERT_NO_THROW_LOG(
+ cbptr_->createUpdateSubnet4(ServerSelector::ALL(), subnet1)
+ );
+ ASSERT_NO_THROW_LOG(
+ cbptr_->createUpdateSubnet4(ServerSelector::ONE("server2"), subnet2)
+ );
+ ASSERT_NO_THROW_LOG(
+ cbptr_->createUpdateSubnet4(ServerSelector::MULTIPLE({ "server1", "server2" }),
+ subnet3)
+ );
+ };
+
+ create_test_subnets();
+
+ // Test that subnet is not deleted for a specified server selector.
+ auto test_no_delete = [this] (const std::string& test_case_name,
+ const ServerSelector& server_selector,
+ const Subnet4Ptr& subnet) {
+ SCOPED_TRACE(test_case_name);
+ uint64_t deleted_count = 0;
+ ASSERT_NO_THROW_LOG(
+ deleted_count = cbptr_->deleteSubnet4(server_selector, subnet->getID())
+ );
+ EXPECT_EQ(0, deleted_count);
+
+ deleted_count = 0;
+ ASSERT_NO_THROW_LOG(
+ deleted_count = cbptr_->deleteSubnet4(server_selector, subnet->toText())
+ );
+ EXPECT_EQ(0, deleted_count);
+ };
+
+ {
+ SCOPED_TRACE("Test valid but non matching server selectors");
+ test_no_delete("selector: one, actual: all", ServerSelector::ONE("server2"),
+ subnet1);
+ test_no_delete("selector: all, actual: one", ServerSelector::ALL(),
+ subnet2);
+ test_no_delete("selector: all, actual: multiple", ServerSelector::ALL(),
+ subnet3);
+ }
+
+ // Test successful deletion of a subnet by ID.
+ auto test_delete_by_id = [this] (const std::string& test_case_name,
+ const ServerSelector& server_selector,
+ const Subnet4Ptr& subnet) {
+ SCOPED_TRACE(test_case_name);
+ uint64_t deleted_count = 0;
+ ASSERT_NO_THROW_LOG(
+ deleted_count = cbptr_->deleteSubnet4(server_selector, subnet->getID())
+ );
+ EXPECT_EQ(1, deleted_count);
+
+ EXPECT_FALSE(cbptr_->getSubnet4(server_selector, subnet->getID()));
+ };
+
+ test_delete_by_id("all servers", ServerSelector::ALL(), subnet1);
+ test_delete_by_id("any server", ServerSelector::ANY(), subnet2);
+ test_delete_by_id("one server", ServerSelector::ONE("server1"), subnet3);
+
+ // Re-create deleted subnets.
+ create_test_subnets();
+
+ // Test successful deletion of a subnet by prefix.
+ auto test_delete_by_prefix = [this] (const std::string& test_case_name,
+ const ServerSelector& server_selector,
+ const Subnet4Ptr& subnet) {
+ SCOPED_TRACE(test_case_name);
+ uint64_t deleted_count = 0;
+ ASSERT_NO_THROW_LOG(
+ deleted_count = cbptr_->deleteSubnet4(server_selector, subnet->toText())
+ );
+ EXPECT_EQ(1, deleted_count);
+
+ EXPECT_FALSE(cbptr_->getSubnet4(server_selector, subnet->toText()));
+ };
+
+ test_delete_by_prefix("all servers", ServerSelector::ALL(), subnet1);
+ test_delete_by_prefix("any server", ServerSelector::ANY(), subnet2);
+ test_delete_by_prefix("one server", ServerSelector::ONE("server1"), subnet3);
+}
+
+void
+GenericConfigBackendDHCPv4Test::deleteSubnet4ByIdSelectorsTest() {
+ // Supported selectors.
+ ASSERT_NO_THROW_LOG(cbptr_->deleteSubnet4(ServerSelector::ANY(), SubnetID(1)));
+ ASSERT_NO_THROW_LOG(cbptr_->deleteSubnet4(ServerSelector::ALL(), SubnetID(1)));
+ ASSERT_NO_THROW_LOG(cbptr_->deleteSubnet4(ServerSelector::ONE("server1"), SubnetID(1)));
+
+ // Not supported selectors.
+ ASSERT_THROW(cbptr_->deleteSubnet4(ServerSelector::MULTIPLE({ "server1", "server2" }),
+ SubnetID(1)),
+ isc::InvalidOperation);
+
+ // Not implemented selectors.
+ ASSERT_THROW(cbptr_->deleteSubnet4(ServerSelector::UNASSIGNED(), SubnetID(1)),
+ isc::NotImplemented);
+}
+
+void
+GenericConfigBackendDHCPv4Test::deleteSubnet4ByPrefixSelectorsTest() {
+ // Supported selectors.
+ ASSERT_NO_THROW_LOG(cbptr_->deleteSubnet4(ServerSelector::ANY(), "192.0.2.0/24"));
+ ASSERT_NO_THROW_LOG(cbptr_->deleteSubnet4(ServerSelector::ALL(), "192.0.2.0/24"));
+ ASSERT_NO_THROW_LOG(cbptr_->deleteSubnet4(ServerSelector::ONE("server1"), "192.0.2.0/24"));
+
+ // Not supported selectors.
+ ASSERT_THROW(cbptr_->deleteSubnet4(ServerSelector::MULTIPLE({ "server1", "server2" }),
+ "192.0.2.0/24"),
+ isc::InvalidOperation);
+
+ // Not implemented selectors.
+ ASSERT_THROW(cbptr_->deleteSubnet4(ServerSelector::UNASSIGNED(), "192.0.2.0/24"),
+ isc::NotImplemented);
+}
+
+void
+GenericConfigBackendDHCPv4Test::deleteAllSubnets4SelectorsTest() {
+ // Supported selectors.
+ ASSERT_NO_THROW_LOG(cbptr_->deleteAllSubnets4(ServerSelector::UNASSIGNED()));
+ ASSERT_NO_THROW_LOG(cbptr_->deleteAllSubnets4(ServerSelector::ALL()));
+ ASSERT_NO_THROW_LOG(cbptr_->deleteAllSubnets4(ServerSelector::ONE("server1")));
+
+ // Not supported selectors.
+ ASSERT_THROW(cbptr_->deleteAllSubnets4(ServerSelector::ANY()),
+ isc::InvalidOperation);
+ ASSERT_THROW(cbptr_->deleteAllSubnets4(ServerSelector::MULTIPLE({ "server1", "server2" })),
+ isc::InvalidOperation);
+}
+
+void
+GenericConfigBackendDHCPv4Test::unassignedSubnet4Test() {
+ // Create the server.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer4(test_servers_[0]));
+
+ // Create the subnets and associate them with the server1.
+ auto subnet = test_subnets_[0];
+ auto subnet2 = test_subnets_[2];
+ ASSERT_NO_THROW_LOG(
+ cbptr_->createUpdateSubnet4(ServerSelector::ONE("server1"), subnet)
+ );
+ ASSERT_NO_THROW_LOG(
+ cbptr_->createUpdateSubnet4(ServerSelector::ONE("server1"), subnet2)
+ );
+
+ // Delete the server. The subnets should be preserved but are considered orphaned,
+ // i.e. do not belong to any server.
+ uint64_t deleted_count = 0;
+ ASSERT_NO_THROW_LOG(deleted_count = cbptr_->deleteServer4(ServerTag("server1")));
+ EXPECT_EQ(1, deleted_count);
+
+ // Trying to fetch the subnet by server tag should return no result.
+ Subnet4Ptr returned_subnet;
+ ASSERT_NO_THROW_LOG(returned_subnet = cbptr_->getSubnet4(ServerSelector::ONE("server1"),
+ subnet->getID()));
+ EXPECT_FALSE(returned_subnet);
+
+ // The same if we use other calls.
+ ASSERT_NO_THROW_LOG(returned_subnet = cbptr_->getSubnet4(ServerSelector::ONE("server1"),
+ subnet->toText()));
+ EXPECT_FALSE(returned_subnet);
+
+ Subnet4Collection returned_subnets;
+ ASSERT_NO_THROW_LOG(returned_subnets = cbptr_->getAllSubnets4(ServerSelector::ONE("server1")));
+ EXPECT_TRUE(returned_subnets.empty());
+
+ ASSERT_NO_THROW_LOG(
+ returned_subnets = cbptr_->getModifiedSubnets4(ServerSelector::ONE("server1"),
+ timestamps_["two days ago"])
+ );
+ EXPECT_TRUE(returned_subnets.empty());
+
+ // We should get the subnet if we ask for unassigned.
+ ASSERT_NO_THROW_LOG(returned_subnet = cbptr_->getSubnet4(ServerSelector::UNASSIGNED(),
+ subnet->getID()));
+ ASSERT_TRUE(returned_subnet);
+
+ ASSERT_NO_THROW_LOG(returned_subnet = cbptr_->getSubnet4(ServerSelector::UNASSIGNED(),
+ subnet->toText()));
+ ASSERT_TRUE(returned_subnet);
+
+ // Also if we ask for all unassigned subnets it should be returned.
+ ASSERT_NO_THROW_LOG(returned_subnets = cbptr_->getAllSubnets4(ServerSelector::UNASSIGNED()));
+ ASSERT_EQ(2, returned_subnets.size());
+
+ // Same for modified subnets.
+ ASSERT_NO_THROW_LOG(
+ returned_subnets = cbptr_->getModifiedSubnets4(ServerSelector::UNASSIGNED(),
+ timestamps_["two days ago"])
+ );
+ ASSERT_EQ(2, returned_subnets.size());
+
+ // If we ask for any subnet by subnet id, it should be returned too.
+ ASSERT_NO_THROW_LOG(returned_subnet = cbptr_->getSubnet4(ServerSelector::ANY(),
+ subnet->getID()));
+ ASSERT_TRUE(returned_subnet);
+
+ ASSERT_NO_THROW_LOG(returned_subnet = cbptr_->getSubnet4(ServerSelector::ANY(),
+ subnet->toText()));
+ ASSERT_TRUE(returned_subnet);
+
+ // Deleting the subnet with the mismatched server tag should not affect our
+ // subnet.
+ ASSERT_NO_THROW_LOG(
+ deleted_count = cbptr_->deleteSubnet4(ServerSelector::ONE("server1"),
+ subnet->getID())
+ );
+ EXPECT_EQ(0, deleted_count);
+
+ // Also, if we delete all subnets for server1.
+ ASSERT_NO_THROW_LOG(
+ deleted_count = cbptr_->deleteAllSubnets4(ServerSelector::ONE("server1"))
+ );
+ EXPECT_EQ(0, deleted_count);
+
+ // We can delete this subnet when we specify ANY and the matching id.
+ ASSERT_NO_THROW_LOG(
+ deleted_count = cbptr_->deleteSubnet4(ServerSelector::ANY(), subnet->getID())
+ );
+ EXPECT_EQ(1, deleted_count);
+
+ // We can delete all subnets using UNASSIGNED selector.
+ ASSERT_NO_THROW_LOG(
+ deleted_count = cbptr_->deleteAllSubnets4(ServerSelector::UNASSIGNED());
+ );
+ EXPECT_EQ(1, deleted_count);
+}
+
+void
+GenericConfigBackendDHCPv4Test::getModifiedSubnets4Test() {
+ // Explicitly set timestamps of subnets. First subnet has a timestamp
+ // pointing to the future. Second subnet has timestamp pointing to the
+ // past (yesterday). Third subnet has a timestamp pointing to the
+ // past (an hour ago).
+ test_subnets_[1]->setModificationTime(timestamps_["tomorrow"]);
+ test_subnets_[2]->setModificationTime(timestamps_["yesterday"]);
+ test_subnets_[3]->setModificationTime(timestamps_["today"]);
+
+ // Insert subnets into the database.
+ for (int i = 1; i < test_subnets_.size(); ++i) {
+ cbptr_->createUpdateSubnet4(ServerSelector::ALL(),
+ test_subnets_[i]);
+ }
+
+ // Fetch subnets with timestamp later than today. Only one subnet
+ // should be returned.
+ Subnet4Collection
+ subnets = cbptr_->getModifiedSubnets4(ServerSelector::ALL(),
+ timestamps_["after today"]);
+ ASSERT_EQ(1, subnets.size());
+
+ // All subnets should also be returned for explicitly specified server tag.
+ subnets = cbptr_->getModifiedSubnets4(ServerSelector::ONE("server1"),
+ timestamps_["after today"]);
+ ASSERT_EQ(1, subnets.size());
+
+ // Fetch subnets with timestamp later than yesterday. We should get
+ // two subnets.
+ subnets = cbptr_->getModifiedSubnets4(ServerSelector::ALL(),
+ timestamps_["after yesterday"]);
+ ASSERT_EQ(2, subnets.size());
+
+ // Fetch subnets with timestamp later than tomorrow. Nothing should
+ // be returned.
+ subnets = cbptr_->getModifiedSubnets4(ServerSelector::ALL(),
+ timestamps_["after tomorrow"]);
+ ASSERT_TRUE(subnets.empty());
+}
+
+void
+GenericConfigBackendDHCPv4Test::subnetLifetimeTest() {
+ // Insert new subnet with unspecified valid lifetime
+ Triplet<uint32_t> unspecified;
+ Subnet4Ptr subnet(new Subnet4(IOAddress("192.0.2.0"), 24, 30, 40,
+ unspecified, 1111));
+ subnet->setIface("eth1");
+ cbptr_->createUpdateSubnet4(ServerSelector::ALL(), subnet);
+
+ // Fetch this subnet by subnet identifier
+ Subnet4Ptr returned_subnet = cbptr_->getSubnet4(ServerSelector::ALL(),
+ subnet->getID());
+ ASSERT_TRUE(returned_subnet);
+
+ // Verified returned and original subnets match.
+ EXPECT_EQ(subnet->toElement()->str(), returned_subnet->toElement()->str());
+
+ // Update the valid lifetime.
+ subnet->setValid( Triplet<uint32_t>(100, 200, 300));
+ cbptr_->createUpdateSubnet4(ServerSelector::ALL(), subnet);
+
+ // Fetch and verify again.
+ returned_subnet = cbptr_->getSubnet4(ServerSelector::ALL(), subnet->getID());
+ ASSERT_TRUE(returned_subnet);
+ EXPECT_EQ(subnet->toElement()->str(), returned_subnet->toElement()->str());
+}
+
+void
+GenericConfigBackendDHCPv4Test::getSharedNetworkSubnets4Test() {
+ // Assign test subnets to shared networks level1 and level2.
+ test_subnets_[1]->setSharedNetworkName("level1");
+ test_subnets_[2]->setSharedNetworkName("level2");
+ test_subnets_[3]->setSharedNetworkName("level2");
+
+ // Store shared networks in the database.
+ for (auto network : test_networks_) {
+ cbptr_->createUpdateSharedNetwork4(ServerSelector::ALL(), network);
+ }
+
+ // Store subnets in the database.
+ for (auto subnet : test_subnets_) {
+ cbptr_->createUpdateSubnet4(ServerSelector::ALL(), subnet);
+ }
+
+ // Fetch all subnets belonging to shared network level1.
+ Subnet4Collection subnets = cbptr_->getSharedNetworkSubnets4(ServerSelector::ALL(),
+ "level1");
+ ASSERT_EQ(1, subnets.size());
+
+ // Returned subnet should match test subnet #1.
+ EXPECT_TRUE(isEquivalent(test_subnets_[1]->toElement(),
+ (*subnets.begin())->toElement()));
+
+ // All subnets should also be returned for ANY server.
+ subnets = cbptr_->getSharedNetworkSubnets4(ServerSelector::ANY(), "level1");
+ ASSERT_EQ(1, subnets.size());
+
+ // Returned subnet should match test subnet #1.
+ EXPECT_TRUE(isEquivalent(test_subnets_[1]->toElement(),
+ (*subnets.begin())->toElement()));
+
+ // Check server tag
+ ASSERT_EQ(1, (*subnets.begin())->getServerTags().size());
+ EXPECT_EQ("all", (*subnets.begin())->getServerTags().begin()->get());
+
+ // Fetch all subnets belonging to shared network level2.
+ subnets = cbptr_->getSharedNetworkSubnets4(ServerSelector::ALL(), "level2");
+ ASSERT_EQ(2, subnets.size());
+
+ ElementPtr test_list = Element::createList();
+ test_list->add(test_subnets_[2]->toElement());
+ test_list->add(test_subnets_[3]->toElement());
+
+ ElementPtr returned_list = Element::createList();
+ auto subnet = subnets.begin();
+ returned_list->add((*subnet)->toElement());
+ returned_list->add((*++subnet)->toElement());
+
+ EXPECT_TRUE(isEquivalent(returned_list, test_list));
+
+ // All subnets should also be returned for explicitly specified server tag.
+ subnets = cbptr_->getSharedNetworkSubnets4(ServerSelector::ONE("server1"), "level2");
+ ASSERT_EQ(2, subnets.size());
+
+ returned_list = Element::createList();
+ subnet = subnets.begin();
+ returned_list->add((*subnet)->toElement());
+ returned_list->add((*++subnet)->toElement());
+
+ EXPECT_TRUE(isEquivalent(returned_list, test_list));
+}
+
+void
+GenericConfigBackendDHCPv4Test::subnetUpdatePoolsTest() {
+
+ auto test_subnet_update = [this](const std::string& subnet_prefix,
+ const SubnetID& subnet_id) {
+ // Add the subnet with two pools.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet4(ServerSelector::ALL(),
+ test_subnets_[0]));
+ // Make sure that the pools have been added to the database.
+ EXPECT_EQ(2, countRows("dhcp4_pool"));
+
+ // Create the subnet without options which updates the existing
+ // subnet.
+ Subnet4Ptr subnet(new Subnet4(IOAddress(subnet_prefix), 24, 30, 40, 60,
+ subnet_id));
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet4(ServerSelector::ALL(), subnet));
+ // Check that options are gone.
+ EXPECT_EQ(0, countRows("dhcp4_pool"));
+ };
+
+ {
+ SCOPED_TRACE("update subnet, modify subnet id");
+ // Create another subnet with the same prefix as the original subnet but
+ // different id. This is legal to update the subnet id if the prefix is
+ // stable. However, the new subnet has no address pools, so we need to
+ // check of the pools associated with the existing subnet instance are
+ // gone after the update.
+ test_subnet_update("192.0.2.0", 2048);
+ }
+
+ {
+ SCOPED_TRACE("update subnet, modify prefix");
+ // Create a subnet with the same subnet id but different prefix.
+ // The prefix should be updated.
+ test_subnet_update("192.0.3.0", 1024);
+ }
+}
+
+void
+GenericConfigBackendDHCPv4Test::subnetOptionsTest() {
+ // Add the subnet with two pools and three options.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet4(ServerSelector::ALL(), test_subnets_[0]));
+ EXPECT_EQ(2, countRows("dhcp4_pool"));
+ EXPECT_EQ(3, countRows("dhcp4_options"));
+
+ // The second subnet uses the same subnet id, so this operation should replace
+ // the existing subnet and its options. The new instance has two pools, each
+ // including one option, so we should end up with two options.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet4(ServerSelector::ALL(), test_subnets_[1]));
+ EXPECT_EQ(2, countRows("dhcp4_pool"));
+ EXPECT_EQ(2, countRows("dhcp4_options"));
+
+ // Add third subnet with a single option. The number of options in the database
+ // should now be 3.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet4(ServerSelector::ALL(), test_subnets_[2]));
+ EXPECT_EQ(2, countRows("dhcp4_pool"));
+ EXPECT_EQ(3, countRows("dhcp4_options"));
+
+ // Delete the subnet. All options and pools it contains should also be removed, leaving
+ // the last added subnet and its sole option.
+ ASSERT_NO_THROW_LOG(cbptr_->deleteSubnet4(ServerSelector::ALL(), test_subnets_[1]->getID()));
+ EXPECT_EQ(1, countRows("dhcp4_subnet"));
+ EXPECT_EQ(0, countRows("dhcp4_pool"));
+ EXPECT_EQ(1, countRows("dhcp4_options"));
+
+ // Add the first subnet again. We should now have 4 options: 3 options from the
+ // newly added subnet and one option from the existing subnet.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet4(ServerSelector::ALL(), test_subnets_[0]));
+ EXPECT_EQ(2, countRows("dhcp4_pool"));
+ EXPECT_EQ(4, countRows("dhcp4_options"));
+
+ // Delete the subnet including 3 options. The option from the other subnet should not
+ // be affected.
+ ASSERT_NO_THROW_LOG(cbptr_->deleteSubnet4(ServerSelector::ALL(), test_subnets_[0]->getID()));
+ EXPECT_EQ(1, countRows("dhcp4_subnet"));
+ EXPECT_EQ(0, countRows("dhcp4_pool"));
+ EXPECT_EQ(1, countRows("dhcp4_options"));
+}
+
+void
+GenericConfigBackendDHCPv4Test::getSharedNetwork4Test() {
+ // Insert the server2 into the database.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer4(test_servers_[2]));
+ {
+ SCOPED_TRACE("CREATE audit entry for server");
+ testNewAuditEntry("dhcp4_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set");
+ }
+
+ auto shared_network = test_networks_[0];
+ auto shared_network2 = test_networks_[2];
+
+ // Insert two shared networks, one for all servers, and one for server2.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork4(ServerSelector::ALL(),
+ shared_network));
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork4(ServerSelector::ONE("server2"),
+ shared_network2));
+
+ // We are not going to support selection of a single entry for multiple servers.
+ ASSERT_THROW(cbptr_->getSharedNetwork4(ServerSelector::MULTIPLE({ "server1", "server2" }),
+ test_networks_[0]->getName()),
+ isc::InvalidOperation);
+
+ // Test that this shared network will be fetched for various server selectors.
+ auto test_get_network = [this, &shared_network] (const std::string& test_case_name,
+ const ServerSelector& server_selector,
+ const std::string& expected_tag = ServerTag::ALL) {
+ SCOPED_TRACE(test_case_name);
+ SharedNetwork4Ptr network;
+ ASSERT_NO_THROW_LOG(network = cbptr_->getSharedNetwork4(server_selector,
+ shared_network->getName()));
+ ASSERT_TRUE(network);
+
+ EXPECT_GT(network->getId(), 0);
+ ASSERT_EQ(1, network->getServerTags().size());
+ EXPECT_EQ(expected_tag, network->getServerTags().begin()->get());
+
+ // The easiest way to verify whether the returned shared network matches the
+ // inserted shared network is to convert both to text.
+ EXPECT_EQ(shared_network->toElement()->str(), network->toElement()->str());
+ };
+
+ {
+ SCOPED_TRACE("testing various server selectors before update");
+ test_get_network("all servers", ServerSelector::ALL());
+ test_get_network("one server", ServerSelector::ONE("server1"));
+ test_get_network("any server", ServerSelector::ANY());
+ }
+
+ {
+ SCOPED_TRACE("CREATE audit entry for a shared network");
+ testNewAuditEntry("dhcp4_shared_network",
+ AuditEntry::ModificationType::CREATE,
+ "shared network set");
+ }
+
+ // Update shared network in the database.
+ shared_network = test_networks_[1];
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork4(ServerSelector::ALL(),
+ shared_network));
+
+ {
+ SCOPED_TRACE("testing various server selectors after update");
+ test_get_network("all servers after update", ServerSelector::ALL());
+ test_get_network("one server after update", ServerSelector::ONE("server1"));
+ test_get_network("any server after update", ServerSelector::ANY());
+ }
+
+ {
+ SCOPED_TRACE("UPDATE audit entry for a shared network");
+ testNewAuditEntry("dhcp4_shared_network",
+ AuditEntry::ModificationType::UPDATE,
+ "shared network set");
+ }
+
+ // The server2 specific shared network should not be returned if the
+ // server selector is not matching.
+ EXPECT_FALSE(cbptr_->getSharedNetwork4(ServerSelector::ALL(),
+ shared_network2->getName()));
+ EXPECT_FALSE(cbptr_->getSharedNetwork4(ServerSelector::ONE("server1"),
+ shared_network2->getName()));
+
+ {
+ SCOPED_TRACE("testing selectors for server2 specific shared network");
+ shared_network = shared_network2;
+ test_get_network("one server", ServerSelector::ONE("server2"), "server2");
+ test_get_network("any server", ServerSelector::ANY(), "server2");
+ }
+}
+
+void
+GenericConfigBackendDHCPv4Test::getSharedNetwork4SelectorsTest() {
+ // Supported selectors.
+ ASSERT_NO_THROW_LOG(cbptr_->getSharedNetwork4(ServerSelector::ANY(), "level1"));
+ ASSERT_NO_THROW_LOG(cbptr_->getSharedNetwork4(ServerSelector::UNASSIGNED(), "level1"));
+ ASSERT_NO_THROW_LOG(cbptr_->getSharedNetwork4(ServerSelector::ALL(), "level1"));
+ ASSERT_NO_THROW_LOG(cbptr_->getSharedNetwork4(ServerSelector::ONE("server1"), "level1"));
+
+ // Not supported selectors.
+ ASSERT_THROW(cbptr_->getSharedNetwork4(ServerSelector::MULTIPLE({ "server1", "server2" }),
+ "level1"),
+ isc::InvalidOperation);
+}
+
+void
+GenericConfigBackendDHCPv4Test::createUpdateSharedNetwork4Test() {
+ auto shared_network = test_networks_[0];
+
+ // An attempt to insert the shared network for non-existing server should fail.
+ ASSERT_THROW(cbptr_->createUpdateSharedNetwork4(ServerSelector::ONE("server1"),
+ shared_network),
+ NullKeyError);
+
+ // Insert the server1 into the database.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer4(test_servers_[0]));
+ {
+ SCOPED_TRACE("CREATE audit entry for server");
+ testNewAuditEntry("dhcp4_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set");
+ }
+
+ // Insert the server2 into the database.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer4(test_servers_[2]));
+ {
+ SCOPED_TRACE("CREATE audit entry for server");
+ testNewAuditEntry("dhcp4_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set");
+ }
+
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork4(ServerSelector::ALL(),
+ shared_network));
+ {
+ SCOPED_TRACE("CREATE audit entry for shared network and ALL servers");
+ testNewAuditEntry("dhcp4_shared_network",
+ AuditEntry::ModificationType::CREATE,
+ "shared network set");
+ }
+
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork4(ServerSelector::MULTIPLE({ "server1", "server2" }),
+ shared_network));
+ {
+ SCOPED_TRACE("UPDATE audit entry for shared network and MULTIPLE servers");
+ testNewAuditEntry("dhcp4_shared_network",
+ AuditEntry::ModificationType::UPDATE,
+ "shared network set");
+ }
+
+ SharedNetwork4Ptr network;
+ ASSERT_NO_THROW_LOG(network = cbptr_->getSharedNetwork4(ServerSelector::ANY(),
+ shared_network->getName()));
+ ASSERT_TRUE(network);
+ EXPECT_TRUE(network->hasServerTag(ServerTag("server1")));
+ EXPECT_TRUE(network->hasServerTag(ServerTag("server2")));
+ EXPECT_FALSE(network->hasServerTag(ServerTag()));
+}
+
+void
+GenericConfigBackendDHCPv4Test::createUpdateSharedNetwork4SelectorsTest() {
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer4(test_servers_[0]));
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer4(test_servers_[2]));
+
+ // Supported selectors.
+ SharedNetwork4Ptr shared_network(new SharedNetwork4("all"));
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork4(ServerSelector::ALL(),
+ shared_network));
+ shared_network.reset(new SharedNetwork4("one"));
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork4(ServerSelector::ONE("server1"),
+ shared_network));
+ shared_network.reset(new SharedNetwork4("multiple"));
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork4(ServerSelector::MULTIPLE({ "server1", "server2" }),
+ shared_network));
+
+ // Not supported server selectors.
+ ASSERT_THROW(cbptr_->createUpdateSharedNetwork4(ServerSelector::ANY(), shared_network),
+ isc::InvalidOperation);
+
+ // Not implemented server selectors.
+ ASSERT_THROW(cbptr_->createUpdateSharedNetwork4(ServerSelector::UNASSIGNED(),
+ shared_network),
+ isc::NotImplemented);
+}
+
+void
+GenericConfigBackendDHCPv4Test::getSharedNetwork4WithOptionalUnspecifiedTest() {
+ // Insert new shared network.
+ SharedNetwork4Ptr shared_network = test_networks_[2];
+ cbptr_->createUpdateSharedNetwork4(ServerSelector::ALL(), shared_network);
+
+ // Fetch this shared network by name.
+ SharedNetwork4Ptr
+ returned_network = cbptr_->getSharedNetwork4(ServerSelector::ALL(),
+ test_networks_[2]->getName());
+ ASSERT_TRUE(returned_network);
+
+ EXPECT_TRUE(returned_network->getIface().unspecified());
+ EXPECT_TRUE(returned_network->getIface().empty());
+
+ EXPECT_TRUE(returned_network->getClientClass().unspecified());
+ EXPECT_TRUE(returned_network->getClientClass().empty());
+
+ EXPECT_TRUE(returned_network->getValid().unspecified());
+ EXPECT_EQ(0, returned_network->getValid().get());
+
+ EXPECT_TRUE(returned_network->getT1().unspecified());
+ EXPECT_EQ(0, returned_network->getT1().get());
+
+ EXPECT_TRUE(returned_network->getT2().unspecified());
+ EXPECT_EQ(0, returned_network->getT2().get());
+
+ EXPECT_TRUE(returned_network->getReservationsGlobal().unspecified());
+ EXPECT_FALSE(returned_network->getReservationsGlobal().get());
+
+ EXPECT_TRUE(returned_network->getReservationsInSubnet().unspecified());
+ EXPECT_TRUE(returned_network->getReservationsInSubnet().get());
+
+ EXPECT_TRUE(returned_network->getReservationsOutOfPool().unspecified());
+ EXPECT_FALSE(returned_network->getReservationsOutOfPool().get());
+
+ EXPECT_TRUE(returned_network->getCalculateTeeTimes().unspecified());
+ EXPECT_FALSE(returned_network->getCalculateTeeTimes().get());
+
+ EXPECT_TRUE(returned_network->getT1Percent().unspecified());
+ EXPECT_EQ(0.0, returned_network->getT1Percent().get());
+
+ EXPECT_TRUE(returned_network->getT2Percent().unspecified());
+ EXPECT_EQ(0.0, returned_network->getT2Percent().get());
+
+ EXPECT_TRUE(returned_network->getMatchClientId().unspecified());
+ EXPECT_TRUE(returned_network->getMatchClientId().get());
+
+ EXPECT_TRUE(returned_network->getAuthoritative().unspecified());
+ EXPECT_FALSE(returned_network->getAuthoritative().get());
+
+ EXPECT_FALSE(returned_network->getDdnsSendUpdates().unspecified());
+ EXPECT_TRUE(returned_network->getDdnsSendUpdates().get());
+
+ EXPECT_FALSE(returned_network->getDdnsOverrideNoUpdate().unspecified());
+ EXPECT_TRUE(returned_network->getDdnsOverrideNoUpdate().get());
+
+ EXPECT_FALSE(returned_network->getDdnsOverrideClientUpdate().unspecified());
+ EXPECT_FALSE(returned_network->getDdnsOverrideClientUpdate().get());
+
+ EXPECT_FALSE(returned_network->getDdnsReplaceClientNameMode().unspecified());
+ EXPECT_EQ(D2ClientConfig::ReplaceClientNameMode::RCM_WHEN_PRESENT,
+ returned_network->getDdnsReplaceClientNameMode().get());
+
+ EXPECT_FALSE(returned_network->getDdnsGeneratedPrefix().unspecified());
+ EXPECT_EQ("myhost", returned_network->getDdnsGeneratedPrefix().get());
+
+ EXPECT_FALSE(returned_network->getDdnsQualifyingSuffix().unspecified());
+ EXPECT_EQ("example.org", returned_network->getDdnsQualifyingSuffix().get());
+}
+
+void
+GenericConfigBackendDHCPv4Test::deleteSharedNetworkSubnets4Test() {
+ ASSERT_THROW(cbptr_->deleteSharedNetworkSubnets4(ServerSelector::UNASSIGNED(),
+ test_networks_[1]->getName()),
+ isc::InvalidOperation);
+ ASSERT_THROW(cbptr_->deleteSharedNetworkSubnets4(ServerSelector::ALL(),
+ test_networks_[1]->getName()),
+ isc::InvalidOperation);
+ ASSERT_THROW(cbptr_->deleteSharedNetworkSubnets4(ServerSelector::ONE("server1"),
+ test_networks_[1]->getName()),
+ isc::InvalidOperation);
+ ASSERT_THROW(cbptr_->deleteSharedNetworkSubnets4(ServerSelector::MULTIPLE({ "server1", "server2" }),
+ test_networks_[1]->getName()),
+ isc::InvalidOperation);
+}
+
+void
+GenericConfigBackendDHCPv4Test::getAllSharedNetworks4Test() {
+ // Insert test shared networks into the database. Note that the second shared
+ // network will overwrite the first shared network as they use the same name.
+ for (auto network : test_networks_) {
+ cbptr_->createUpdateSharedNetwork4(ServerSelector::ALL(), network);
+
+ // That shared network overrides the first one so the audit entry should
+ // indicate an update.
+ if ((network->getName() == "level1") && (!audit_entries_["all"].empty())) {
+ SCOPED_TRACE("UPDATE audit entry for the shared network " +
+ network->getName());
+ testNewAuditEntry("dhcp4_shared_network",
+ AuditEntry::ModificationType::UPDATE,
+ "shared network set");
+
+ } else {
+ SCOPED_TRACE("CREATE audit entry for the shared network " +
+ network->getName());
+ testNewAuditEntry("dhcp4_shared_network",
+ AuditEntry::ModificationType::CREATE,
+ "shared network set");
+ }
+ }
+
+ // Fetch all shared networks.
+ SharedNetwork4Collection networks =
+ cbptr_->getAllSharedNetworks4(ServerSelector::ALL());
+ ASSERT_EQ(test_networks_.size() - 1, networks.size());
+
+ // All shared networks should also be returned for explicitly specified
+ // server tag.
+ networks = cbptr_->getAllSharedNetworks4(ServerSelector::ONE("server1"));
+ ASSERT_EQ(test_networks_.size() - 1, networks.size());
+
+ // See if shared networks are returned ok.
+ for (auto i = 0; i < networks.size(); ++i) {
+ EXPECT_EQ(test_networks_[i + 1]->toElement()->str(),
+ networks[i]->toElement()->str());
+ ASSERT_EQ(1, networks[i]->getServerTags().size());
+ EXPECT_EQ("all", networks[i]->getServerTags().begin()->get());
+ }
+
+ // Add some subnets.
+ test_networks_[1]->add(test_subnets_[0]);
+ test_subnets_[2]->setSharedNetworkName("level2");
+ test_networks_[2]->add(test_subnets_[3]);
+ cbptr_->createUpdateSubnet4(ServerSelector::ALL(), test_subnets_[0]);
+ cbptr_->createUpdateSubnet4(ServerSelector::ALL(), test_subnets_[2]);
+ cbptr_->createUpdateSubnet4(ServerSelector::ALL(), test_subnets_[3]);
+
+ // Both ways to attach a subnet are equivalent.
+ Subnet4Ptr subnet = cbptr_->getSubnet4(ServerSelector::ALL(),
+ test_subnets_[0]->getID());
+ ASSERT_TRUE(subnet);
+ EXPECT_EQ("level1", subnet->getSharedNetworkName());
+
+ {
+ SCOPED_TRACE("CREATE audit entry for subnets");
+ testNewAuditEntry("dhcp4_subnet",
+ AuditEntry::ModificationType::CREATE,
+ "subnet set", ServerSelector::ALL(), 3);
+ }
+
+ // Deleting non-existing shared network should return 0.
+ EXPECT_EQ(0, cbptr_->deleteSharedNetwork4(ServerSelector::ALL(),
+ "big-fish"));
+ // All shared networks should be still there.
+ ASSERT_EQ(test_networks_.size() - 1, networks.size());
+
+ // Should not delete the shared network for explicit server tag
+ // because our shared network is for all servers.
+ EXPECT_EQ(0, cbptr_->deleteSharedNetwork4(ServerSelector::ONE("server1"),
+ test_networks_[1]->getName()));
+
+ // Same for all shared networks.
+ EXPECT_EQ(0, cbptr_->deleteAllSharedNetworks4(ServerSelector::ONE("server1")));
+
+ // Delete first shared network with it subnets and verify it is gone.
+ // Begin by its subnet.
+ EXPECT_EQ(1, cbptr_->deleteSharedNetworkSubnets4(ServerSelector::ANY(),
+ test_networks_[1]->getName()));
+
+ {
+ SCOPED_TRACE("DELETE audit entry for subnets of the first shared network");
+ testNewAuditEntry("dhcp4_subnet",
+ AuditEntry::ModificationType::DELETE,
+ "deleted all subnets for a shared network");
+ }
+
+ // Check that the subnet is gone..
+ subnet = cbptr_->getSubnet4(ServerSelector::ALL(),
+ test_subnets_[0]->getID());
+ EXPECT_FALSE(subnet);
+
+ // And after the shared network itself.
+ EXPECT_EQ(1, cbptr_->deleteSharedNetwork4(ServerSelector::ALL(),
+ test_networks_[1]->getName()));
+
+ networks = cbptr_->getAllSharedNetworks4(ServerSelector::ALL());
+ ASSERT_EQ(test_networks_.size() - 2, networks.size());
+
+ {
+ SCOPED_TRACE("DELETE audit entry for the first shared network");
+ testNewAuditEntry("dhcp4_shared_network",
+ AuditEntry::ModificationType::DELETE,
+ "shared network deleted");
+ }
+
+ // Delete all.
+ EXPECT_EQ(2, cbptr_->deleteAllSharedNetworks4(ServerSelector::ALL()));
+ networks = cbptr_->getAllSharedNetworks4(ServerSelector::ALL());
+ ASSERT_TRUE(networks.empty());
+
+ {
+ SCOPED_TRACE("DELETE audit entry for the remaining two shared networks");
+ // The last parameter indicates that we expect four new audit entries,
+ // two for deleted shared networks and two for updated subnets
+ std::vector<ExpAuditEntry> exp_entries({
+ {
+ "dhcp4_shared_network",
+ AuditEntry::ModificationType::DELETE, "deleted all shared networks"
+ },
+ {
+ "dhcp4_subnet",
+ AuditEntry::ModificationType::UPDATE, "deleted all shared networks"
+ },
+ {
+ "dhcp4_subnet",
+ AuditEntry::ModificationType::UPDATE, "deleted all shared networks"
+ },
+ {
+ "dhcp4_shared_network",
+ AuditEntry::ModificationType::DELETE, "deleted all shared networks"
+ }
+ });
+
+ testNewAuditEntry(exp_entries, ServerSelector::ALL());
+ }
+
+ // Check that subnets are still there but detached.
+ subnet = cbptr_->getSubnet4(ServerSelector::ALL(),
+ test_subnets_[2]->getID());
+ ASSERT_TRUE(subnet);
+ EXPECT_TRUE(subnet->getSharedNetworkName().empty());
+ subnet = cbptr_->getSubnet4(ServerSelector::ALL(),
+ test_subnets_[3]->getID());
+ ASSERT_TRUE(subnet);
+ EXPECT_TRUE(subnet->getSharedNetworkName().empty());
+}
+
+void
+GenericConfigBackendDHCPv4Test::getAllSharedNetworks4SelectorsTest() {
+ // Supported selectors.
+ ASSERT_NO_THROW_LOG(cbptr_->getAllSharedNetworks4(ServerSelector::UNASSIGNED()));
+ ASSERT_NO_THROW_LOG(cbptr_->getAllSharedNetworks4(ServerSelector::ALL()));
+ ASSERT_NO_THROW_LOG(cbptr_->getAllSharedNetworks4(ServerSelector::ONE("server1")));
+ ASSERT_NO_THROW_LOG(cbptr_->getAllSharedNetworks4(ServerSelector::MULTIPLE({ "server1", "server2" })));
+
+ // Not supported selectors.
+ ASSERT_THROW(cbptr_->getAllSharedNetworks4(ServerSelector::ANY()),
+ isc::InvalidOperation);
+}
+
+void
+GenericConfigBackendDHCPv4Test::getAllSharedNetworks4WithServerTagsTest() {
+ auto shared_network1 = test_networks_[0];
+ auto shared_network2 = test_networks_[2];
+ auto shared_network3 = test_networks_[3];
+
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer4(test_servers_[0]));
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer4(test_servers_[2]));
+
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork4(ServerSelector::ALL(),
+ shared_network1));
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork4(ServerSelector::ONE("server1"),
+ shared_network2));
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork4(ServerSelector::MULTIPLE({ "server1", "server2" }),
+ shared_network3));
+
+ SharedNetwork4Collection networks;
+
+ // All three networks are associated with the server1.
+ ASSERT_NO_THROW_LOG(networks = cbptr_->getAllSharedNetworks4(ServerSelector::ONE("server1")));
+ EXPECT_EQ(3, networks.size());
+
+ // First network is associated with all servers.
+ auto returned_network = SharedNetworkFetcher4::get(networks, "level1");
+ ASSERT_TRUE(returned_network);
+ EXPECT_TRUE(returned_network->hasAllServerTag());
+ EXPECT_FALSE(returned_network->hasServerTag(ServerTag("server1")));
+ EXPECT_FALSE(returned_network->hasServerTag(ServerTag("server2")));
+
+ // Second network is only associated with the server1.
+ returned_network = SharedNetworkFetcher4::get(networks, "level2");
+ ASSERT_TRUE(returned_network);
+ EXPECT_FALSE(returned_network->hasAllServerTag());
+ EXPECT_TRUE(returned_network->hasServerTag(ServerTag("server1")));
+ EXPECT_FALSE(returned_network->hasServerTag(ServerTag("server2")));
+
+ // Third network is associated with both server1 and server2.
+ returned_network = SharedNetworkFetcher4::get(networks, "level3");
+ ASSERT_TRUE(returned_network);
+ EXPECT_FALSE(returned_network->hasAllServerTag());
+ EXPECT_TRUE(returned_network->hasServerTag(ServerTag("server1")));
+ EXPECT_TRUE(returned_network->hasServerTag(ServerTag("server2")));
+
+ // For server2 we should only get two shared networks, i.e. first and last.
+ ASSERT_NO_THROW_LOG(networks = cbptr_->getAllSharedNetworks4(ServerSelector::ONE("server2")));
+ EXPECT_EQ(2, networks.size());
+
+ // First shared network is associated with all servers.
+ returned_network = SharedNetworkFetcher4::get(networks, "level1");
+ ASSERT_TRUE(returned_network);
+ EXPECT_TRUE(returned_network->hasAllServerTag());
+ EXPECT_FALSE(returned_network->hasServerTag(ServerTag("server1")));
+ EXPECT_FALSE(returned_network->hasServerTag(ServerTag("server2")));
+
+ // Last shared network is associated with server1 and server2.
+ returned_network = SharedNetworkFetcher4::get(networks, "level3");
+ ASSERT_TRUE(returned_network);
+ EXPECT_FALSE(returned_network->hasAllServerTag());
+ EXPECT_TRUE(returned_network->hasServerTag(ServerTag("server1")));
+ EXPECT_TRUE(returned_network->hasServerTag(ServerTag("server2")));
+
+ // Only the first shared network is associated with all servers.
+ ASSERT_NO_THROW_LOG(networks = cbptr_->getAllSharedNetworks4(ServerSelector::ALL()));
+ EXPECT_EQ(1, networks.size());
+
+ returned_network = SharedNetworkFetcher4::get(networks, "level1");
+ ASSERT_TRUE(returned_network);
+ EXPECT_TRUE(returned_network->hasAllServerTag());
+ EXPECT_FALSE(returned_network->hasServerTag(ServerTag("server1")));
+ EXPECT_FALSE(returned_network->hasServerTag(ServerTag("server2")));
+}
+
+void
+GenericConfigBackendDHCPv4Test::getModifiedSharedNetworks4Test() {
+ // Explicitly set timestamps of shared networks. First shared
+ // network has a timestamp pointing to the future. Second shared
+ // network has timestamp pointing to the past (yesterday).
+ // Third shared network has a timestamp pointing to the
+ // past (an hour ago).
+ test_networks_[1]->setModificationTime(timestamps_["tomorrow"]);
+ test_networks_[2]->setModificationTime(timestamps_["yesterday"]);
+ test_networks_[3]->setModificationTime(timestamps_["today"]);
+
+ // Insert shared networks into the database.
+ for (int i = 1; i < test_networks_.size(); ++i) {
+ cbptr_->createUpdateSharedNetwork4(ServerSelector::ALL(),
+ test_networks_[i]);
+ }
+
+ // Fetch shared networks with timestamp later than today. Only one
+ // shared network should be returned.
+ SharedNetwork4Collection
+ networks = cbptr_->getModifiedSharedNetworks4(ServerSelector::ALL(),
+ timestamps_["after today"]);
+ ASSERT_EQ(1, networks.size());
+
+ // Fetch shared networks with timestamp later than yesterday. We
+ // should get two shared networks.
+ networks = cbptr_->getModifiedSharedNetworks4(ServerSelector::ALL(),
+ timestamps_["after yesterday"]);
+ ASSERT_EQ(2, networks.size());
+
+ // Fetch shared networks with timestamp later than tomorrow. Nothing
+ // should be returned.
+ networks = cbptr_->getModifiedSharedNetworks4(ServerSelector::ALL(),
+ timestamps_["after tomorrow"]);
+ ASSERT_TRUE(networks.empty());
+}
+
+void
+GenericConfigBackendDHCPv4Test::getModifiedSharedNetworks4SelectorsTest() {
+ // Supported selectors.
+ ASSERT_NO_THROW_LOG(cbptr_->getModifiedSharedNetworks4(ServerSelector::UNASSIGNED(),
+ timestamps_["yesterday"]));
+ ASSERT_NO_THROW_LOG(cbptr_->getModifiedSharedNetworks4(ServerSelector::ALL(),
+ timestamps_["yesterday"]));
+ ASSERT_NO_THROW_LOG(cbptr_->getModifiedSharedNetworks4(ServerSelector::ONE("server1"),
+ timestamps_["yesterday"]));
+ ASSERT_NO_THROW_LOG(cbptr_->getModifiedSharedNetworks4(ServerSelector::MULTIPLE({ "server1", "server2" }),
+ timestamps_["yesterday"]));
+
+ // Not supported selectors.
+ ASSERT_THROW(cbptr_->getModifiedSharedNetworks4(ServerSelector::ANY(),
+ timestamps_["yesterday"]),
+ isc::InvalidOperation);
+}
+
+void
+GenericConfigBackendDHCPv4Test::deleteSharedNetwork4Test() {
+ // Create two servers in the database.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer4(test_servers_[0]));
+ {
+ SCOPED_TRACE("CREATE audit entry for server");
+ testNewAuditEntry("dhcp4_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set");
+ }
+
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer4(test_servers_[2]));
+ {
+ SCOPED_TRACE("CREATE audit entry for server");
+ testNewAuditEntry("dhcp4_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set");
+ }
+
+ auto shared_network1 = test_networks_[0];
+ auto shared_network2 = test_networks_[2];
+ auto shared_network3 = test_networks_[3];
+
+ // Insert three shared networks, one for all servers, one for server2 and
+ // one for two servers: server1 and server2.
+ ASSERT_NO_THROW_LOG(
+ cbptr_->createUpdateSharedNetwork4(ServerSelector::ALL(), shared_network1)
+ );
+ ASSERT_NO_THROW_LOG(
+ cbptr_->createUpdateSharedNetwork4(ServerSelector::ONE("server2"), shared_network2)
+ );
+ ASSERT_NO_THROW_LOG(
+ cbptr_->createUpdateSharedNetwork4(ServerSelector::MULTIPLE({ "server1", "server2" }),
+ shared_network3)
+ );
+
+ auto test_no_delete = [this] (const std::string& test_case_name,
+ const ServerSelector& server_selector,
+ const SharedNetwork4Ptr& shared_network) {
+ SCOPED_TRACE(test_case_name);
+ uint64_t deleted_count = 0;
+ ASSERT_NO_THROW_LOG(
+ deleted_count = cbptr_->deleteSharedNetwork4(server_selector,
+ shared_network->getName())
+ );
+ EXPECT_EQ(0, deleted_count);
+ };
+
+ {
+ SCOPED_TRACE("Test valid but non matching server selectors");
+ test_no_delete("selector: one, actual: all", ServerSelector::ONE("server2"),
+ shared_network1);
+ test_no_delete("selector: all, actual: one", ServerSelector::ALL(),
+ shared_network2);
+ test_no_delete("selector: all, actual: multiple", ServerSelector::ALL(),
+ shared_network3);
+ }
+
+ // We are not going to support deletion of a single entry for multiple servers.
+ ASSERT_THROW(cbptr_->deleteSharedNetwork4(ServerSelector::MULTIPLE({ "server1", "server2" }),
+ shared_network3->getName()),
+ isc::InvalidOperation);
+
+ // We currently don't support deleting a shared network with specifying
+ // an unassigned server tag. Use ANY to delete any subnet instead.
+ ASSERT_THROW(cbptr_->deleteSharedNetwork4(ServerSelector::UNASSIGNED(),
+ shared_network1->getName()),
+ isc::NotImplemented);
+
+ // Test successful deletion of a shared network.
+ auto test_delete = [this] (const std::string& test_case_name,
+ const ServerSelector& server_selector,
+ const SharedNetwork4Ptr& shared_network) {
+ SCOPED_TRACE(test_case_name);
+ uint64_t deleted_count = 0;
+ ASSERT_NO_THROW_LOG(
+ deleted_count = cbptr_->deleteSharedNetwork4(server_selector,
+ shared_network->getName())
+ );
+ EXPECT_EQ(1, deleted_count);
+
+ EXPECT_FALSE(cbptr_->getSharedNetwork4(server_selector,
+ shared_network->getName()));
+ };
+
+ test_delete("all servers", ServerSelector::ALL(), shared_network1);
+ test_delete("any server", ServerSelector::ANY(), shared_network2);
+ test_delete("one server", ServerSelector::ONE("server1"), shared_network3);
+}
+
+void
+GenericConfigBackendDHCPv4Test::deleteSharedNetwork4SelectorsTest() {
+ // Supported selectors.
+ ASSERT_NO_THROW_LOG(cbptr_->deleteSharedNetwork4(ServerSelector::ANY(), "level1"));
+ ASSERT_NO_THROW_LOG(cbptr_->deleteSharedNetwork4(ServerSelector::ALL(), "level1"));
+ ASSERT_NO_THROW_LOG(cbptr_->deleteSharedNetwork4(ServerSelector::ONE("server1"), "level1"));
+
+ // Not supported selectors.
+ ASSERT_THROW(cbptr_->deleteSharedNetwork4(ServerSelector::MULTIPLE({ "server1", "server2" }),
+ "level1"),
+ isc::InvalidOperation);
+
+ // Not implemented selectors.
+ ASSERT_THROW(cbptr_->deleteSharedNetwork4(ServerSelector::UNASSIGNED(), "level1"),
+ isc::NotImplemented);
+}
+
+void
+GenericConfigBackendDHCPv4Test::deleteAllSharedNetworks4SelectorsTest() {
+ // Supported selectors.
+ ASSERT_NO_THROW_LOG(cbptr_->deleteAllSharedNetworks4(ServerSelector::UNASSIGNED()));
+ ASSERT_NO_THROW_LOG(cbptr_->deleteAllSharedNetworks4(ServerSelector::ALL()));
+ ASSERT_NO_THROW_LOG(cbptr_->deleteAllSharedNetworks4(ServerSelector::ONE("server1")));
+
+ // Not supported selectors.
+ ASSERT_THROW(cbptr_->deleteAllSharedNetworks4(ServerSelector::ANY()),
+ isc::InvalidOperation);
+ ASSERT_THROW(cbptr_->deleteAllSharedNetworks4(ServerSelector::MULTIPLE({ "server1", "server2" })),
+ isc::InvalidOperation);
+}
+
+void
+GenericConfigBackendDHCPv4Test::unassignedSharedNetworkTest() {
+ // Create the server.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer4(test_servers_[0]));
+
+ // Create the shared networks and associate them with the server1.
+ auto shared_network = test_networks_[0];
+ auto shared_network2 = test_networks_[2];
+ ASSERT_NO_THROW_LOG(
+ cbptr_->createUpdateSharedNetwork4(ServerSelector::ONE("server1"), shared_network)
+ );
+ ASSERT_NO_THROW_LOG(
+ cbptr_->createUpdateSharedNetwork4(ServerSelector::ONE("server1"), shared_network2)
+ );
+
+ // Delete the server. The shared networks should be preserved but are
+ // considered orphaned, i.e. do not belong to any server.
+ uint64_t deleted_count = 0;
+ ASSERT_NO_THROW_LOG(deleted_count = cbptr_->deleteServer4(ServerTag("server1")));
+ EXPECT_EQ(1, deleted_count);
+
+ // Trying to fetch this shared network by server tag should return no result.
+ SharedNetwork4Ptr returned_network;
+ ASSERT_NO_THROW_LOG(returned_network = cbptr_->getSharedNetwork4(ServerSelector::ONE("server1"),
+ "level1"));
+ EXPECT_FALSE(returned_network);
+
+ // The same if we use other calls.
+ SharedNetwork4Collection returned_networks;
+ ASSERT_NO_THROW_LOG(
+ returned_networks = cbptr_->getAllSharedNetworks4(ServerSelector::ONE("server1"))
+ );
+ EXPECT_TRUE(returned_networks.empty());
+
+ ASSERT_NO_THROW_LOG(
+ returned_networks = cbptr_->getModifiedSharedNetworks4(ServerSelector::ONE("server1"),
+ timestamps_["two days ago"])
+ );
+ EXPECT_TRUE(returned_networks.empty());
+
+ // We should get the shared network if we ask for unassigned.
+ ASSERT_NO_THROW_LOG(returned_network = cbptr_->getSharedNetwork4(ServerSelector::UNASSIGNED(),
+ "level1"));
+ ASSERT_TRUE(returned_network);
+
+ // Also if we ask for all unassigned networks it should be returned.
+ ASSERT_NO_THROW_LOG(returned_networks = cbptr_->getAllSharedNetworks4(ServerSelector::UNASSIGNED()));
+ ASSERT_EQ(2, returned_networks.size());
+
+ // And all modified.
+ ASSERT_NO_THROW_LOG(
+ returned_networks = cbptr_->getModifiedSharedNetworks4(ServerSelector::UNASSIGNED(),
+ timestamps_["two days ago"])
+ );
+ ASSERT_EQ(2, returned_networks.size());
+
+ // If we ask for any network by name, it should be returned too.
+ ASSERT_NO_THROW_LOG(returned_network = cbptr_->getSharedNetwork4(ServerSelector::ANY(),
+ "level1"));
+ ASSERT_TRUE(returned_network);
+
+ // Deleting a shared network with the mismatched server tag should not affect
+ // our shared network.
+ ASSERT_NO_THROW_LOG(
+ deleted_count = cbptr_->deleteSharedNetwork4(ServerSelector::ONE("server1"),
+ "level1")
+ );
+ EXPECT_EQ(0, deleted_count);
+
+ // Also, if we delete all shared networks for server1.
+ ASSERT_NO_THROW_LOG(
+ deleted_count = cbptr_->deleteAllSharedNetworks4(ServerSelector::ONE("server1"))
+ );
+ EXPECT_EQ(0, deleted_count);
+
+ // We can delete this shared network when we specify ANY and the matching name.
+ ASSERT_NO_THROW_LOG(
+ deleted_count = cbptr_->deleteSharedNetwork4(ServerSelector::ANY(), "level1")
+ );
+ EXPECT_EQ(1, deleted_count);
+
+ // We can delete all networks using UNASSIGNED selector.
+ ASSERT_NO_THROW_LOG(
+ deleted_count = cbptr_->deleteAllSharedNetworks4(ServerSelector::UNASSIGNED());
+ );
+ EXPECT_EQ(1, deleted_count);
+}
+
+void
+GenericConfigBackendDHCPv4Test::sharedNetworkLifetimeTest() {
+ // Insert new shared network with unspecified valid lifetime
+ SharedNetwork4Ptr network(new SharedNetwork4("foo"));
+ Triplet<uint32_t> unspecified;
+ network->setValid(unspecified);
+ network->setIface("eth1");
+ cbptr_->createUpdateSharedNetwork4(ServerSelector::ALL(), network);
+
+ // Fetch this shared network.
+ SharedNetwork4Ptr returned_network =
+ cbptr_->getSharedNetwork4(ServerSelector::ALL(), "foo");
+ ASSERT_TRUE(returned_network);
+
+ // Verified returned and original shared networks match.
+ EXPECT_EQ(network->toElement()->str(),
+ returned_network->toElement()->str());
+
+ // Update the preferred and valid lifetime.
+ network->setValid( Triplet<uint32_t>(100, 200, 300));
+ cbptr_->createUpdateSharedNetwork4(ServerSelector::ALL(), network);
+
+ // Fetch and verify again.
+ returned_network = cbptr_->getSharedNetwork4(ServerSelector::ALL(), "foo");
+ ASSERT_TRUE(returned_network);
+ EXPECT_EQ(network->toElement()->str(),
+ returned_network->toElement()->str());
+}
+
+void
+GenericConfigBackendDHCPv4Test::sharedNetworkOptionsTest() {
+ // Add shared network with three options.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork4(ServerSelector::ALL(), test_networks_[0]));
+ EXPECT_EQ(3, countRows("dhcp4_options"));
+
+ // Add another shared network with a single option. The numnber of options in the
+ // database should now be 4.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork4(ServerSelector::ALL(), test_networks_[2]));
+ EXPECT_EQ(4, countRows("dhcp4_options"));
+
+ // The second shared network uses the same name as the first shared network, so
+ // this operation should replace the existing shared network and its options.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork4(ServerSelector::ALL(), test_networks_[1]));
+ EXPECT_EQ(1, countRows("dhcp4_options"));
+
+ // Remove the shared network. This should not affect options assigned to the
+ // other shared network.
+ ASSERT_NO_THROW_LOG(cbptr_->deleteSharedNetwork4(ServerSelector::ALL(),
+ test_networks_[1]->getName()));
+ EXPECT_EQ(1, countRows("dhcp4_shared_network"));
+ EXPECT_EQ(1, countRows("dhcp4_options"));
+
+ // Create the first option again. The number of options should be equal to the
+ // sum of options associated with both shared networks.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork4(ServerSelector::ALL(), test_networks_[0]));
+ EXPECT_EQ(4, countRows("dhcp4_options"));
+
+ // Delete this shared network. This should not affect the option associated
+ // with the remaining shared network.
+ ASSERT_NO_THROW_LOG(cbptr_->deleteSharedNetwork4(ServerSelector::ALL(),
+ test_networks_[0]->getName()));
+ EXPECT_EQ(1, countRows("dhcp4_shared_network"));
+ EXPECT_EQ(1, countRows("dhcp4_options"));
+}
+
+void
+GenericConfigBackendDHCPv4Test::getOptionDef4Test() {
+ // Insert new option definition.
+ OptionDefinitionPtr option_def = test_option_defs_[0];
+ cbptr_->createUpdateOptionDef4(ServerSelector::ALL(), option_def);
+
+ // Fetch this option_definition by subnet identifier.
+ OptionDefinitionPtr returned_option_def =
+ cbptr_->getOptionDef4(ServerSelector::ALL(),
+ test_option_defs_[0]->getCode(),
+ test_option_defs_[0]->getOptionSpaceName());
+ ASSERT_TRUE(returned_option_def);
+ EXPECT_GT(returned_option_def->getId(), 0);
+ ASSERT_EQ(1, returned_option_def->getServerTags().size());
+ EXPECT_EQ("all", returned_option_def->getServerTags().begin()->get());
+
+ EXPECT_TRUE(returned_option_def->equals(*option_def));
+
+ {
+ SCOPED_TRACE("CREATE audit entry for an option definition");
+ testNewAuditEntry("dhcp4_option_def",
+ AuditEntry::ModificationType::CREATE,
+ "option definition set");
+ }
+
+ // Update the option definition in the database.
+ OptionDefinitionPtr option_def2 = test_option_defs_[1];
+ cbptr_->createUpdateOptionDef4(ServerSelector::ALL(), option_def2);
+
+ // Fetch updated option definition and see if it matches.
+ returned_option_def = cbptr_->getOptionDef4(ServerSelector::ALL(),
+ test_option_defs_[1]->getCode(),
+ test_option_defs_[1]->getOptionSpaceName());
+ EXPECT_TRUE(returned_option_def->equals(*option_def2));
+
+ // Fetching option definition for an explicitly specified server tag
+ // should succeed too.
+ returned_option_def = cbptr_->getOptionDef4(ServerSelector::ONE("server1"),
+ test_option_defs_[1]->getCode(),
+ test_option_defs_[1]->getOptionSpaceName());
+ EXPECT_TRUE(returned_option_def->equals(*option_def2));
+
+ {
+ SCOPED_TRACE("UPDATE audit entry for an option definition");
+ testNewAuditEntry("dhcp4_option_def",
+ AuditEntry::ModificationType::UPDATE,
+ "option definition set");
+ }
+}
+
+void
+GenericConfigBackendDHCPv4Test::optionDefs4WithServerTagsTest() {
+ OptionDefinitionPtr option1 = test_option_defs_[0];
+ OptionDefinitionPtr option2 = test_option_defs_[1];
+ OptionDefinitionPtr option3 = test_option_defs_[4];
+
+ // An attempt to create option definition for non-existing server should
+ // fail.
+ ASSERT_THROW(cbptr_->createUpdateOptionDef4(ServerSelector::ONE("server1"),
+ option1),
+ NullKeyError);
+
+ // Create two servers.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer4(test_servers_[1]));
+ {
+ SCOPED_TRACE("server1 is created");
+ testNewAuditEntry("dhcp4_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set");
+ }
+
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer4(test_servers_[2]));
+ {
+ SCOPED_TRACE("server2 is created");
+ testNewAuditEntry("dhcp4_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set");
+ }
+
+ // This time creation of the option definition for the server1 should pass.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateOptionDef4(ServerSelector::ONE("server1"),
+ option1));
+ {
+ SCOPED_TRACE("option definition for server1 is set");
+ // The value of 3 means there should be 3 audit entries available for the
+ // server1, two that indicate creation of the servers and one that we
+ // validate, which sets the option definition.
+ testNewAuditEntry("dhcp4_option_def",
+ AuditEntry::ModificationType::CREATE,
+ "option definition set",
+ ServerSelector::ONE("server1"),
+ 3, 1);
+ }
+
+ // Creation of the option definition for the server2 should also pass.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateOptionDef4(ServerSelector::ONE("server2"),
+ option2));
+ {
+ SCOPED_TRACE("option definition for server2 is set");
+ // Same as in case of the server1, there should be 3 audit entries and
+ // we validate one of them.
+ testNewAuditEntry("dhcp4_option_def",
+ AuditEntry::ModificationType::CREATE,
+ "option definition set",
+ ServerSelector::ONE("server2"),
+ 3, 1);
+ }
+
+ // Finally, creation of the option definition for all servers should
+ // also pass.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateOptionDef4(ServerSelector::ALL(),
+ option3));
+ {
+ SCOPED_TRACE("option definition for server2 is set");
+ // There should be one new audit entry for all servers. It logs
+ // the insertion of the option definition.
+ testNewAuditEntry("dhcp4_option_def",
+ AuditEntry::ModificationType::CREATE,
+ "option definition set",
+ ServerSelector::ALL(),
+ 1, 1);
+ }
+
+ OptionDefinitionPtr returned_option_def;
+
+ // Try to fetch the option definition specified for all servers. It should
+ // return the third one.
+ ASSERT_NO_THROW_LOG(
+ returned_option_def = cbptr_->getOptionDef4(ServerSelector::ALL(),
+ option3->getCode(),
+ option3->getOptionSpaceName())
+ );
+ ASSERT_TRUE(returned_option_def);
+ EXPECT_TRUE(returned_option_def->equals(*option3));
+
+ // Try to fetch the option definition specified for server1. It should
+ // override the definition for all servers.
+ ASSERT_NO_THROW_LOG(
+ returned_option_def = cbptr_->getOptionDef4(ServerSelector::ONE("server1"),
+ option1->getCode(),
+ option1->getOptionSpaceName())
+ );
+ ASSERT_TRUE(returned_option_def);
+ EXPECT_TRUE(returned_option_def->equals(*option1));
+
+ // The same in case of the server2.
+ ASSERT_NO_THROW_LOG(
+ returned_option_def = cbptr_->getOptionDef4(ServerSelector::ONE("server2"),
+ option2->getCode(),
+ option2->getOptionSpaceName())
+ );
+ ASSERT_TRUE(returned_option_def);
+ EXPECT_TRUE(returned_option_def->equals(*option2));
+
+ OptionDefContainer returned_option_defs;
+
+ // Try to fetch the collection of the option definitions for server1, server2
+ // and server3. The server3 does not have an explicit option definition, so
+ // for this server we should get the definition associated with "all" servers.
+ ASSERT_NO_THROW_LOG(
+ returned_option_defs = cbptr_->getAllOptionDefs4(ServerSelector::
+ MULTIPLE({ "server1", "server2",
+ "server3" }));
+ );
+ ASSERT_EQ(3, returned_option_defs.size());
+
+ // Check that expected option definitions have been returned.
+ auto current_option = returned_option_defs.begin();
+ EXPECT_TRUE((*current_option)->equals(*option1));
+ EXPECT_TRUE((*(++current_option))->equals(*option2));
+ EXPECT_TRUE((*(++current_option))->equals(*option3));
+
+ // Try to fetch the collection of options specified for all servers.
+ // This excludes the options specific to server1 and server2. It returns
+ // only the common ones.
+ ASSERT_NO_THROW_LOG(
+ returned_option_defs = cbptr_->getAllOptionDefs4(ServerSelector::ALL());
+
+ );
+ ASSERT_EQ(1, returned_option_defs.size());
+ EXPECT_TRUE((*returned_option_defs.begin())->equals(*option3));
+
+ // Delete the server1. It should remove associations of this server with the
+ // option definitions and the option definition itself.
+ ASSERT_NO_THROW_LOG(cbptr_->deleteServer4(ServerTag("server1")));
+ ASSERT_NO_THROW_LOG(
+ returned_option_defs = cbptr_->getAllOptionDefs4(ServerSelector::ONE("server1"));
+
+ );
+ ASSERT_EQ(1, returned_option_defs.size());
+ EXPECT_TRUE((*returned_option_defs.begin())->equals(*option3));
+
+ {
+ SCOPED_TRACE("DELETE audit entry for the option definition after server deletion");
+ testNewAuditEntry("dhcp4_option_def",
+ AuditEntry::ModificationType::DELETE,
+ "deleting a server", ServerSelector::ONE("server1"),
+ 2, 1);
+ }
+
+ // Attempt to delete option definition for server1.
+ uint64_t deleted_num = 0;
+ ASSERT_NO_THROW_LOG(deleted_num = cbptr_->deleteOptionDef4(ServerSelector::ONE("server1"),
+ option1->getCode(),
+ option1->getOptionSpaceName()));
+ EXPECT_EQ(0, deleted_num);
+
+ // Deleting the existing option definition for server2 should succeed.
+ ASSERT_NO_THROW_LOG(deleted_num = cbptr_->deleteOptionDef4(ServerSelector::ONE("server2"),
+ option2->getCode(),
+ option2->getOptionSpaceName()));
+ EXPECT_EQ(1, deleted_num);
+
+ // Create this option definition again to test that deletion of all servers
+ // removes it too.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateOptionDef4(ServerSelector::ONE("server2"),
+ option2));
+
+ // Delete all servers, except 'all'.
+ ASSERT_NO_THROW_LOG(deleted_num = cbptr_->deleteAllServers4());
+ ASSERT_NO_THROW_LOG(
+ returned_option_defs = cbptr_->getAllOptionDefs4(ServerSelector::ALL());
+ );
+ EXPECT_EQ(1, deleted_num);
+ EXPECT_EQ(1, returned_option_defs.size());
+ EXPECT_TRUE((*returned_option_defs.begin())->equals(*option3));
+
+ {
+ SCOPED_TRACE("DELETE audit entry for the option definition after deletion of"
+ " all servers");
+ testNewAuditEntry("dhcp4_option_def",
+ AuditEntry::ModificationType::DELETE,
+ "deleting all servers", ServerSelector::ONE("server2"),
+ 4, 1);
+ }
+}
+
+void
+GenericConfigBackendDHCPv4Test::getAllOptionDefs4Test() {
+ // Insert test option definitions into the database. Note that the second
+ // option definition will overwrite the first option definition as they use
+ // the same code and space.
+ size_t updates_num = 0;
+ for (auto option_def : test_option_defs_) {
+ cbptr_->createUpdateOptionDef4(ServerSelector::ALL(), option_def);
+
+ // That option definition overrides the first one so the audit entry should
+ // indicate an update.
+ auto name = option_def->getName();
+ if (name.find("bar") != std::string::npos) {
+ SCOPED_TRACE("UPDATE audit entry for the option definition " + name);
+ testNewAuditEntry("dhcp4_option_def",
+ AuditEntry::ModificationType::UPDATE,
+ "option definition set");
+ ++updates_num;
+
+ } else {
+ SCOPED_TRACE("CREATE audit entry for the option definition " + name);
+ testNewAuditEntry("dhcp4_option_def",
+ AuditEntry::ModificationType::CREATE,
+ "option definition set");
+ }
+ }
+
+ // Fetch all option_definitions.
+ OptionDefContainer option_defs = cbptr_->getAllOptionDefs4(ServerSelector::ALL());
+ ASSERT_EQ(test_option_defs_.size() - updates_num, option_defs.size());
+
+ // All option definitions should also be returned for explicitly specified
+ // server tag.
+ option_defs = cbptr_->getAllOptionDefs4(ServerSelector::ONE("server1"));
+ ASSERT_EQ(test_option_defs_.size() - updates_num, option_defs.size());
+
+ // See if option definitions are returned ok.
+ for (auto def = option_defs.begin(); def != option_defs.end(); ++def) {
+ ASSERT_EQ(1, (*def)->getServerTags().size());
+ EXPECT_EQ("all", (*def)->getServerTags().begin()->get());
+ bool success = false;
+ for (auto i = 1; i < test_option_defs_.size(); ++i) {
+ if ((*def)->equals(*test_option_defs_[i])) {
+ success = true;
+ }
+ }
+ ASSERT_TRUE(success) << "failed for option definition " << (*def)->getCode()
+ << ", option space " << (*def)->getOptionSpaceName();
+ }
+
+ // Deleting non-existing option definition should return 0.
+ EXPECT_EQ(0, cbptr_->deleteOptionDef4(ServerSelector::ALL(),
+ 99, "non-exiting-space"));
+ // All option definitions should be still there.
+ ASSERT_EQ(test_option_defs_.size() - updates_num, option_defs.size());
+
+ // Should not delete option definition for explicit server tag
+ // because our option definition is for all servers.
+ EXPECT_EQ(0, cbptr_->deleteOptionDef4(ServerSelector::ONE("server1"),
+ test_option_defs_[1]->getCode(),
+ test_option_defs_[1]->getOptionSpaceName()));
+
+ // Same for all option definitions.
+ EXPECT_EQ(0, cbptr_->deleteAllOptionDefs4(ServerSelector::ONE("server1")));
+
+ // Delete one of the option definitions and see if it is gone.
+ EXPECT_EQ(1, cbptr_->deleteOptionDef4(ServerSelector::ALL(),
+ test_option_defs_[2]->getCode(),
+ test_option_defs_[2]->getOptionSpaceName()));
+ ASSERT_FALSE(cbptr_->getOptionDef4(ServerSelector::ALL(),
+ test_option_defs_[2]->getCode(),
+ test_option_defs_[2]->getOptionSpaceName()));
+
+ {
+ SCOPED_TRACE("DELETE audit entry for the first option definition");
+ testNewAuditEntry("dhcp4_option_def",
+ AuditEntry::ModificationType::DELETE,
+ "option definition deleted");
+ }
+
+ // Delete all remaining option definitions.
+ EXPECT_EQ(2, cbptr_->deleteAllOptionDefs4(ServerSelector::ALL()));
+ option_defs = cbptr_->getAllOptionDefs4(ServerSelector::ALL());
+ ASSERT_TRUE(option_defs.empty());
+
+ {
+ SCOPED_TRACE("DELETE audit entries for the remaining option definitions");
+ // The last parameter indicates that we expect two new audit entries.
+ testNewAuditEntry("dhcp4_option_def",
+ AuditEntry::ModificationType::DELETE,
+ "deleted all option definitions",
+ ServerSelector::ALL(), 2);
+ }
+}
+
+void
+GenericConfigBackendDHCPv4Test::getModifiedOptionDefs4Test() {
+ // Explicitly set timestamps of option definitions. First option
+ // definition has a timestamp pointing to the future. Second option
+ // definition has timestamp pointing to the past (yesterday).
+ // Third option definitions has a timestamp pointing to the
+ // past (an hour ago).
+ test_option_defs_[1]->setModificationTime(timestamps_["tomorrow"]);
+ test_option_defs_[2]->setModificationTime(timestamps_["yesterday"]);
+ test_option_defs_[3]->setModificationTime(timestamps_["today"]);
+
+ // Insert option definitions into the database.
+ for (int i = 1; i < test_networks_.size(); ++i) {
+ cbptr_->createUpdateOptionDef4(ServerSelector::ALL(),
+ test_option_defs_[i]);
+ }
+
+ // Fetch option definitions with timestamp later than today. Only one
+ // option definition should be returned.
+ OptionDefContainer
+ option_defs = cbptr_->getModifiedOptionDefs4(ServerSelector::ALL(),
+ timestamps_["after today"]);
+ ASSERT_EQ(1, option_defs.size());
+
+ // Fetch option definitions with timestamp later than yesterday. We
+ // should get two option definitions.
+ option_defs = cbptr_->getModifiedOptionDefs4(ServerSelector::ALL(),
+ timestamps_["after yesterday"]);
+ ASSERT_EQ(2, option_defs.size());
+
+ // Fetch option definitions with timestamp later than tomorrow. Nothing
+ // should be returned.
+ option_defs = cbptr_->getModifiedOptionDefs4(ServerSelector::ALL(),
+ timestamps_["after tomorrow"]);
+ ASSERT_TRUE(option_defs.empty());
+}
+
+void
+GenericConfigBackendDHCPv4Test::createUpdateDeleteOption4Test() {
+ // Add option to the database.
+ OptionDescriptorPtr opt_boot_file_name = test_options_[0];
+ cbptr_->createUpdateOption4(ServerSelector::ALL(),
+ opt_boot_file_name);
+
+ // Make sure we can retrieve this option and that it is equal to the
+ // option we have inserted into the database.
+ OptionDescriptorPtr returned_opt_boot_file_name =
+ cbptr_->getOption4(ServerSelector::ALL(),
+ opt_boot_file_name->option_->getType(),
+ opt_boot_file_name->space_name_);
+ ASSERT_TRUE(returned_opt_boot_file_name);
+
+ {
+ SCOPED_TRACE("verify created option");
+ testOptionsEquivalent(*opt_boot_file_name,
+ *returned_opt_boot_file_name);
+ }
+
+ {
+ SCOPED_TRACE("CREATE audit entry for an option");
+ testNewAuditEntry("dhcp4_options",
+ AuditEntry::ModificationType::CREATE,
+ "global option set");
+ }
+
+ // Modify option and update it in the database.
+ opt_boot_file_name->persistent_ = !opt_boot_file_name->persistent_;
+ cbptr_->createUpdateOption4(ServerSelector::ALL(),
+ opt_boot_file_name);
+
+ // Retrieve the option again and make sure that updates were
+ // properly propagated to the database. Use explicit server selector
+ // which should also return this option.
+ returned_opt_boot_file_name = cbptr_->getOption4(ServerSelector::ONE("server1"),
+ opt_boot_file_name->option_->getType(),
+ opt_boot_file_name->space_name_);
+ ASSERT_TRUE(returned_opt_boot_file_name);
+
+ {
+ SCOPED_TRACE("verify updated option");
+ testOptionsEquivalent(*opt_boot_file_name,
+ *returned_opt_boot_file_name);
+ }
+
+ {
+ SCOPED_TRACE("UPDATE audit entry for an option");
+ testNewAuditEntry("dhcp4_options",
+ AuditEntry::ModificationType::UPDATE,
+ "global option set");
+ }
+
+ // Deleting an option with explicitly specified server tag should fail.
+ EXPECT_EQ(0, cbptr_->deleteOption4(ServerSelector::ONE("server1"),
+ opt_boot_file_name->option_->getType(),
+ opt_boot_file_name->space_name_));
+
+ // Deleting option for all servers should succeed.
+ EXPECT_EQ(1, cbptr_->deleteOption4(ServerSelector::ALL(),
+ opt_boot_file_name->option_->getType(),
+ opt_boot_file_name->space_name_));
+
+ EXPECT_FALSE(cbptr_->getOption4(ServerSelector::ALL(),
+ opt_boot_file_name->option_->getType(),
+ opt_boot_file_name->space_name_));
+
+ {
+ SCOPED_TRACE("DELETE audit entry for an option");
+ testNewAuditEntry("dhcp4_options",
+ AuditEntry::ModificationType::DELETE,
+ "global option deleted");
+ }
+}
+
+void
+GenericConfigBackendDHCPv4Test::globalOptions4WithServerTagsTest() {
+ OptionDescriptorPtr opt_boot_file_name1 = test_options_[0];
+ OptionDescriptorPtr opt_boot_file_name2 = test_options_[6];
+ OptionDescriptorPtr opt_boot_file_name3 = test_options_[7];
+
+ ASSERT_THROW(cbptr_->createUpdateOption4(ServerSelector::ONE("server1"),
+ opt_boot_file_name1),
+ NullKeyError);
+
+ // Create two servers.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer4(test_servers_[1]));
+ {
+ SCOPED_TRACE("server1 is created");
+ testNewAuditEntry("dhcp4_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set");
+ }
+
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer4(test_servers_[2]));
+ {
+ SCOPED_TRACE("server2 is created");
+ testNewAuditEntry("dhcp4_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set");
+ }
+
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateOption4(ServerSelector::ONE("server1"),
+ opt_boot_file_name1));
+ {
+ SCOPED_TRACE("global option for server1 is set");
+ // The value of 3 means there should be 3 audit entries available for the
+ // server1, two that indicate creation of the servers and one that we
+ // validate, which sets the global option.
+ testNewAuditEntry("dhcp4_options",
+ AuditEntry::ModificationType::CREATE,
+ "global option set",
+ ServerSelector::ONE("server1"),
+ 3, 1);
+
+ }
+
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateOption4(ServerSelector::ONE("server2"),
+ opt_boot_file_name2));
+ {
+ SCOPED_TRACE("global option for server2 is set");
+ // Same as in case of the server1, there should be 3 audit entries and
+ // we validate one of them.
+ testNewAuditEntry("dhcp4_options",
+ AuditEntry::ModificationType::CREATE,
+ "global option set",
+ ServerSelector::ONE("server2"),
+ 3, 1);
+
+ }
+
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateOption4(ServerSelector::ALL(),
+ opt_boot_file_name3));
+ {
+ SCOPED_TRACE("global option for all servers is set");
+ // There should be one new audit entry for all servers. It logs
+ // the insertion of the global option.
+ testNewAuditEntry("dhcp4_options",
+ AuditEntry::ModificationType::CREATE,
+ "global option set",
+ ServerSelector::ALL(),
+ 1, 1);
+
+ }
+
+ OptionDescriptorPtr returned_option;
+
+ // Try to fetch the option specified for all servers. It should return
+ // the third option.
+ ASSERT_NO_THROW_LOG(
+ returned_option = cbptr_->getOption4(ServerSelector::ALL(),
+ opt_boot_file_name3->option_->getType(),
+ opt_boot_file_name3->space_name_);
+ );
+ ASSERT_TRUE(returned_option);
+ testOptionsEquivalent(*opt_boot_file_name3, *returned_option);
+
+ // Try to fetch the option specified for the server1. It should override the
+ // option specified for all servers.
+ ASSERT_NO_THROW_LOG(
+ returned_option = cbptr_->getOption4(ServerSelector::ONE("server1"),
+ opt_boot_file_name1->option_->getType(),
+ opt_boot_file_name1->space_name_);
+ );
+ ASSERT_TRUE(returned_option);
+ testOptionsEquivalent(*opt_boot_file_name1, *returned_option);
+
+ // The same in case of the server2.
+ ASSERT_NO_THROW_LOG(
+ returned_option = cbptr_->getOption4(ServerSelector::ONE("server2"),
+ opt_boot_file_name2->option_->getType(),
+ opt_boot_file_name2->space_name_);
+ );
+ ASSERT_TRUE(returned_option);
+ testOptionsEquivalent(*opt_boot_file_name2, *returned_option);
+
+ OptionContainer returned_options;
+
+ // Try to fetch the collection of global options for the server1, server2
+ // and server3. The server3 does not have an explicit value so for this server
+ // we should get the option associated with "all" servers.
+ ASSERT_NO_THROW_LOG(
+ returned_options = cbptr_->getAllOptions4(ServerSelector::
+ MULTIPLE({ "server1", "server2",
+ "server3" }));
+ );
+ ASSERT_EQ(3, returned_options.size());
+
+ // Check that expected options have been returned.
+ auto current_option = returned_options.begin();
+ testOptionsEquivalent(*opt_boot_file_name1, *current_option);
+ testOptionsEquivalent(*opt_boot_file_name2, *(++current_option));
+ testOptionsEquivalent(*opt_boot_file_name3, *(++current_option));
+
+ // Try to fetch the collection of options specified for all servers.
+ // This excludes the options specific to server1 and server2. It returns
+ // only the common ones.
+ ASSERT_NO_THROW_LOG(
+ returned_options = cbptr_->getAllOptions4(ServerSelector::ALL());
+ );
+ ASSERT_EQ(1, returned_options.size());
+ testOptionsEquivalent(*opt_boot_file_name3, *returned_options.begin());
+
+ // Delete the server1. It should remove associations of this server with the
+ // option and the option itself.
+ ASSERT_NO_THROW_LOG(cbptr_->deleteServer4(ServerTag("server1")));
+ ASSERT_NO_THROW_LOG(
+ returned_options = cbptr_->getAllOptions4(ServerSelector::ONE("server1"));
+ );
+ ASSERT_EQ(1, returned_options.size());
+ testOptionsEquivalent(*opt_boot_file_name3, *returned_options.begin());
+
+ {
+ SCOPED_TRACE("DELETE audit entry for the global option after server deletion");
+ testNewAuditEntry("dhcp4_options",
+ AuditEntry::ModificationType::DELETE,
+ "deleting a server", ServerSelector::ONE("server1"),
+ 2, 1);
+ }
+
+ // Attempt to delete global option for server1.
+ uint64_t deleted_num = 0;
+ ASSERT_NO_THROW_LOG(deleted_num = cbptr_->deleteOption4(ServerSelector::ONE("server1"),
+ opt_boot_file_name1->option_->getType(),
+ opt_boot_file_name1->space_name_));
+ EXPECT_EQ(0, deleted_num);
+
+ // Deleting the existing option for server2 should succeed.
+ ASSERT_NO_THROW_LOG(deleted_num = cbptr_->deleteOption4(ServerSelector::ONE("server2"),
+ opt_boot_file_name2->option_->getType(),
+ opt_boot_file_name2->space_name_));
+ EXPECT_EQ(1, deleted_num);
+
+ // Create this option again to test that deletion of all servers removes it too.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateOption4(ServerSelector::ONE("server2"),
+ opt_boot_file_name2));
+
+ // Delete all servers, except 'all'.
+ ASSERT_NO_THROW_LOG(deleted_num = cbptr_->deleteAllServers4());
+ ASSERT_NO_THROW_LOG(
+ returned_options = cbptr_->getAllOptions4(ServerSelector::ALL());
+ );
+ EXPECT_EQ(1, deleted_num);
+ ASSERT_EQ(1, returned_options.size());
+ testOptionsEquivalent(*opt_boot_file_name3, *returned_options.begin());
+
+ {
+ SCOPED_TRACE("DELETE audit entry for the global option after deletion of"
+ " all servers");
+ testNewAuditEntry("dhcp4_options",
+ AuditEntry::ModificationType::DELETE,
+ "deleting all servers", ServerSelector::ONE("server2"),
+ 4, 1);
+ }
+}
+
+void
+GenericConfigBackendDHCPv4Test::getAllOptions4Test() {
+ // Add three global options to the database.
+ cbptr_->createUpdateOption4(ServerSelector::ALL(),
+ test_options_[0]);
+ cbptr_->createUpdateOption4(ServerSelector::ALL(),
+ test_options_[1]);
+ cbptr_->createUpdateOption4(ServerSelector::ALL(),
+ test_options_[5]);
+
+ // Retrieve all these options.
+ OptionContainer returned_options = cbptr_->getAllOptions4(ServerSelector::ALL());
+ ASSERT_EQ(3, returned_options.size());
+
+ // Fetching global options with explicitly specified server tag should return
+ // the same result.
+ returned_options = cbptr_->getAllOptions4(ServerSelector::ONE("server1"));
+ ASSERT_EQ(3, returned_options.size());
+
+ // Get the container index used to search options by option code.
+ const OptionContainerTypeIndex& index = returned_options.get<1>();
+
+ // Verify that all options we put into the database were
+ // returned.
+ {
+ SCOPED_TRACE("verify test_options_[0]");
+ auto option0 = index.find(test_options_[0]->option_->getType());
+ ASSERT_FALSE(option0 == index.end());
+ testOptionsEquivalent(*test_options_[0], *option0);
+ EXPECT_GT(option0->getId(), 0);
+ ASSERT_EQ(1, option0->getServerTags().size());
+ EXPECT_EQ("all", option0->getServerTags().begin()->get());
+ }
+
+ {
+ SCOPED_TRACE("verify test_options_[1]");
+ auto option1 = index.find(test_options_[1]->option_->getType());
+ ASSERT_FALSE(option1 == index.end());
+ testOptionsEquivalent(*test_options_[1], *option1);
+ EXPECT_GT(option1->getId(), 0);
+ ASSERT_EQ(1, option1->getServerTags().size());
+ EXPECT_EQ("all", option1->getServerTags().begin()->get());
+ }
+
+ {
+ SCOPED_TRACE("verify test_options_[5]");
+ auto option5 = index.find(test_options_[5]->option_->getType());
+ ASSERT_FALSE(option5 == index.end());
+ testOptionsEquivalent(*test_options_[5], *option5);
+ EXPECT_GT(option5->getId(), 0);
+ ASSERT_EQ(1, option5->getServerTags().size());
+ EXPECT_EQ("all", option5->getServerTags().begin()->get());
+ }
+}
+
+void
+GenericConfigBackendDHCPv4Test::getModifiedOptions4Test() {
+ // Assign timestamps to the options we're going to store in the
+ // database.
+ test_options_[0]->setModificationTime(timestamps_["tomorrow"]);
+ test_options_[1]->setModificationTime(timestamps_["yesterday"]);
+ test_options_[5]->setModificationTime(timestamps_["today"]);
+
+ // Put options into the database.
+ cbptr_->createUpdateOption4(ServerSelector::ALL(),
+ test_options_[0]);
+ cbptr_->createUpdateOption4(ServerSelector::ALL(),
+ test_options_[1]);
+ cbptr_->createUpdateOption4(ServerSelector::ALL(),
+ test_options_[5]);
+
+ // Get options with the timestamp later than today. Only
+ // one option should be returned.
+ OptionContainer returned_options =
+ cbptr_->getModifiedOptions4(ServerSelector::ALL(),
+ timestamps_["after today"]);
+ ASSERT_EQ(1, returned_options.size());
+
+ // Fetching modified options with explicitly specified server selector
+ // should return the same result.
+ returned_options = cbptr_->getModifiedOptions4(ServerSelector::ONE("server1"),
+ timestamps_["after today"]);
+ ASSERT_EQ(1, returned_options.size());
+
+ // The returned option should be the one with the timestamp
+ // set to tomorrow.
+ const OptionContainerTypeIndex& index = returned_options.get<1>();
+ auto option0 = index.find(test_options_[0]->option_->getType());
+ ASSERT_FALSE(option0 == index.end());
+ {
+ SCOPED_TRACE("verify returned option");
+ testOptionsEquivalent(*test_options_[0], *option0);
+ }
+}
+
+void
+GenericConfigBackendDHCPv4Test::createUpdateDeleteSubnetOption4Test() {
+ // Insert new subnet.
+ Subnet4Ptr subnet = test_subnets_[1];
+ cbptr_->createUpdateSubnet4(ServerSelector::ALL(), subnet);
+
+ // Fetch this subnet by subnet identifier.
+ Subnet4Ptr returned_subnet = cbptr_->getSubnet4(ServerSelector::ALL(),
+ subnet->getID());
+ ASSERT_TRUE(returned_subnet);
+
+ {
+ SCOPED_TRACE("CREATE audit entry for a new subnet");
+ testNewAuditEntry("dhcp4_subnet",
+ AuditEntry::ModificationType::CREATE,
+ "subnet set");
+ }
+
+ // The inserted subnet contains two options.
+ ASSERT_EQ(2, countRows("dhcp4_options"));
+
+ OptionDescriptorPtr opt_boot_file_name = test_options_[0];
+ cbptr_->createUpdateOption4(ServerSelector::ANY(), subnet->getID(),
+ opt_boot_file_name);
+
+ returned_subnet = cbptr_->getSubnet4(ServerSelector::ALL(),
+ subnet->getID());
+ ASSERT_TRUE(returned_subnet);
+
+ OptionDescriptor returned_opt_boot_file_name =
+ returned_subnet->getCfgOption()->get(DHCP4_OPTION_SPACE, DHO_BOOT_FILE_NAME);
+ ASSERT_TRUE(returned_opt_boot_file_name.option_);
+
+ {
+ SCOPED_TRACE("verify returned option");
+ testOptionsEquivalent(*opt_boot_file_name, returned_opt_boot_file_name);
+ EXPECT_GT(returned_opt_boot_file_name.getId(), 0);
+ }
+
+ {
+ SCOPED_TRACE("UPDATE audit entry for an added subnet option");
+ // Instead of adding an audit entry for an option we add an audit
+ // entry for the entire subnet so as the server refreshes the
+ // subnet with the new option. Note that the server doesn't
+ // have means to retrieve only the newly added option.
+ testNewAuditEntry("dhcp4_subnet",
+ AuditEntry::ModificationType::UPDATE,
+ "subnet specific option set");
+ }
+
+ // We have added one option to the existing subnet. We should now have
+ // three options.
+ ASSERT_EQ(3, countRows("dhcp4_options"));
+
+ opt_boot_file_name->persistent_ = !opt_boot_file_name->persistent_;
+ cbptr_->createUpdateOption4(ServerSelector::ANY(), subnet->getID(),
+ opt_boot_file_name);
+
+ returned_subnet = cbptr_->getSubnet4(ServerSelector::ANY(),
+ subnet->getID());
+ ASSERT_TRUE(returned_subnet);
+ returned_opt_boot_file_name =
+ returned_subnet->getCfgOption()->get(DHCP4_OPTION_SPACE, DHO_BOOT_FILE_NAME);
+ ASSERT_TRUE(returned_opt_boot_file_name.option_);
+
+ {
+ SCOPED_TRACE("verify returned option with modified persistence");
+ testOptionsEquivalent(*opt_boot_file_name, returned_opt_boot_file_name);
+ }
+
+ {
+ SCOPED_TRACE("UPDATE audit entry for an updated subnet option");
+ testNewAuditEntry("dhcp4_subnet",
+ AuditEntry::ModificationType::UPDATE,
+ "subnet specific option set");
+ }
+
+ // Updating the option should replace the existing instance with the new
+ // instance. Therefore, we should still have three options.
+ ASSERT_EQ(3, countRows("dhcp4_options"));
+
+ // It should succeed for any server.
+ EXPECT_EQ(1, cbptr_->deleteOption4(ServerSelector::ANY(), subnet->getID(),
+ opt_boot_file_name->option_->getType(),
+ opt_boot_file_name->space_name_));
+
+ returned_subnet = cbptr_->getSubnet4(ServerSelector::ALL(),
+ subnet->getID());
+ ASSERT_TRUE(returned_subnet);
+
+ EXPECT_FALSE(returned_subnet->getCfgOption()->get(DHCP4_OPTION_SPACE, DHO_BOOT_FILE_NAME).option_);
+
+ {
+ SCOPED_TRACE("UPDATE audit entry for a deleted subnet option");
+ testNewAuditEntry("dhcp4_subnet",
+ AuditEntry::ModificationType::UPDATE,
+ "subnet specific option deleted");
+ }
+
+ // We should have only two options after deleting one of them.
+ ASSERT_EQ(2, countRows("dhcp4_options"));
+}
+
+void
+GenericConfigBackendDHCPv4Test::createUpdateDeletePoolOption4Test() {
+ // Insert new subnet.
+ Subnet4Ptr subnet = test_subnets_[1];
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet4(ServerSelector::ALL(), subnet));
+
+ {
+ SCOPED_TRACE("CREATE audit entry for a subnet");
+ testNewAuditEntry("dhcp4_subnet",
+ AuditEntry::ModificationType::CREATE,
+ "subnet set");
+ }
+
+ // Inserted subnet has two options.
+ ASSERT_EQ(2, countRows("dhcp4_options"));
+
+ // Add an option into the pool.
+ const PoolPtr pool = subnet->getPool(Lease::TYPE_V4,
+ IOAddress("192.0.2.10"));
+ ASSERT_TRUE(pool);
+ OptionDescriptorPtr opt_boot_file_name = test_options_[0];
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateOption4(ServerSelector::ANY(),
+ pool->getFirstAddress(),
+ pool->getLastAddress(),
+ opt_boot_file_name));
+
+ // Query for a subnet.
+ Subnet4Ptr returned_subnet = cbptr_->getSubnet4(ServerSelector::ALL(),
+ subnet->getID());
+ ASSERT_TRUE(returned_subnet);
+
+ // The returned subnet should include our pool.
+ const PoolPtr returned_pool = returned_subnet->getPool(Lease::TYPE_V4,
+ IOAddress("192.0.2.10"));
+ ASSERT_TRUE(returned_pool);
+
+ // The pool should contain option we added earlier.
+ OptionDescriptor returned_opt_boot_file_name =
+ returned_pool->getCfgOption()->get(DHCP4_OPTION_SPACE, DHO_BOOT_FILE_NAME);
+ ASSERT_TRUE(returned_opt_boot_file_name.option_);
+
+ {
+ SCOPED_TRACE("verify returned pool option");
+ testOptionsEquivalent(*opt_boot_file_name, returned_opt_boot_file_name);
+ EXPECT_GT(returned_opt_boot_file_name.getId(), 0);
+ }
+
+ {
+ SCOPED_TRACE("UPDATE audit entry for a subnet after adding an option "
+ "to the pool");
+ testNewAuditEntry("dhcp4_subnet",
+ AuditEntry::ModificationType::UPDATE,
+ "pool specific option set");
+ }
+
+ // With the newly inserted option we should now have three options.
+ ASSERT_EQ(3, countRows("dhcp4_options"));
+
+ // Modify the option and update it in the database.
+ opt_boot_file_name->persistent_ = !opt_boot_file_name->persistent_;
+ cbptr_->createUpdateOption4(ServerSelector::ANY(),
+ pool->getFirstAddress(),
+ pool->getLastAddress(),
+ opt_boot_file_name);
+
+ // Fetch the subnet and the corresponding pool.
+ returned_subnet = cbptr_->getSubnet4(ServerSelector::ALL(),
+ subnet->getID());
+ ASSERT_TRUE(returned_subnet);
+ const PoolPtr returned_pool1 = returned_subnet->getPool(Lease::TYPE_V4,
+ IOAddress("192.0.2.10"));
+ ASSERT_TRUE(returned_pool1);
+
+ // Test that the option has been correctly updated in the database.
+ returned_opt_boot_file_name =
+ returned_pool1->getCfgOption()->get(DHCP4_OPTION_SPACE, DHO_BOOT_FILE_NAME);
+ ASSERT_TRUE(returned_opt_boot_file_name.option_);
+
+ {
+ SCOPED_TRACE("verify updated option with modified persistence");
+ testOptionsEquivalent(*opt_boot_file_name, returned_opt_boot_file_name);
+ }
+
+ {
+ SCOPED_TRACE("UPDATE audit entry for a subnet when updating pool "
+ "specific option");
+ testNewAuditEntry("dhcp4_subnet",
+ AuditEntry::ModificationType::UPDATE,
+ "pool specific option set");
+ }
+
+ // The new option instance should replace the existing one, so we should
+ // still have three options.
+ ASSERT_EQ(3, countRows("dhcp4_options"));
+
+ // Delete option for any server should succeed.
+ EXPECT_EQ(1, cbptr_->deleteOption4(ServerSelector::ANY(),
+ pool->getFirstAddress(),
+ pool->getLastAddress(),
+ opt_boot_file_name->option_->getType(),
+ opt_boot_file_name->space_name_));
+
+ // Fetch the subnet and the pool from the database again to make sure
+ // that the option is really gone.
+ returned_subnet = cbptr_->getSubnet4(ServerSelector::ALL(),
+ subnet->getID());
+ ASSERT_TRUE(returned_subnet);
+ const PoolPtr returned_pool2 = returned_subnet->getPool(Lease::TYPE_V4,
+ IOAddress("192.0.2.10"));
+ ASSERT_TRUE(returned_pool2);
+
+ // Option should be gone.
+ EXPECT_FALSE(returned_pool2->getCfgOption()->get(DHCP4_OPTION_SPACE,
+ DHO_BOOT_FILE_NAME).option_);
+
+ {
+ SCOPED_TRACE("UPDATE audit entry for a subnet when deleting pool "
+ "specific option");
+ testNewAuditEntry("dhcp4_subnet",
+ AuditEntry::ModificationType::UPDATE,
+ "pool specific option deleted");
+ }
+
+ // The option has been deleted so the number of options should now
+ // be down to 2.
+ EXPECT_EQ(2, countRows("dhcp4_options"));
+}
+
+void
+GenericConfigBackendDHCPv4Test::createUpdateDeleteSharedNetworkOption4Test() {
+ // Insert new shared network.
+ SharedNetwork4Ptr shared_network = test_networks_[1];
+ cbptr_->createUpdateSharedNetwork4(ServerSelector::ALL(),
+ shared_network);
+
+ // Fetch this shared network by name.
+ SharedNetwork4Ptr returned_network =
+ cbptr_->getSharedNetwork4(ServerSelector::ALL(),
+ shared_network->getName());
+ ASSERT_TRUE(returned_network);
+
+ {
+ SCOPED_TRACE("CREATE audit entry for the new shared network");
+ testNewAuditEntry("dhcp4_shared_network",
+ AuditEntry::ModificationType::CREATE,
+ "shared network set");
+ }
+
+ // The inserted shared network has no options.
+ ASSERT_EQ(0, countRows("dhcp4_options"));
+
+ OptionDescriptorPtr opt_boot_file_name = test_options_[0];
+ cbptr_->createUpdateOption4(ServerSelector::ANY(),
+ shared_network->getName(),
+ opt_boot_file_name);
+
+ returned_network = cbptr_->getSharedNetwork4(ServerSelector::ALL(),
+ shared_network->getName());
+ ASSERT_TRUE(returned_network);
+
+ OptionDescriptor returned_opt_boot_file_name =
+ returned_network->getCfgOption()->get(DHCP4_OPTION_SPACE, DHO_BOOT_FILE_NAME);
+ ASSERT_TRUE(returned_opt_boot_file_name.option_);
+
+ {
+ SCOPED_TRACE("verify returned option");
+ testOptionsEquivalent(*opt_boot_file_name, returned_opt_boot_file_name);
+ EXPECT_GT(returned_opt_boot_file_name.getId(), 0);
+ }
+
+ {
+ SCOPED_TRACE("UPDATE audit entry for the added shared network option");
+ // Instead of adding an audit entry for an option we add an audit
+ // entry for the entire shared network so as the server refreshes the
+ // shared network with the new option. Note that the server doesn't
+ // have means to retrieve only the newly added option.
+ testNewAuditEntry("dhcp4_shared_network",
+ AuditEntry::ModificationType::UPDATE,
+ "shared network specific option set");
+ }
+
+ // One option should now be stored in the database.
+ ASSERT_EQ(1, countRows("dhcp4_options"));
+
+ opt_boot_file_name->persistent_ = !opt_boot_file_name->persistent_;
+ cbptr_->createUpdateOption4(ServerSelector::ANY(),
+ shared_network->getName(),
+ opt_boot_file_name);
+
+ returned_network = cbptr_->getSharedNetwork4(ServerSelector::ALL(),
+ shared_network->getName());
+ ASSERT_TRUE(returned_network);
+ returned_opt_boot_file_name =
+ returned_network->getCfgOption()->get(DHCP4_OPTION_SPACE, DHO_BOOT_FILE_NAME);
+ ASSERT_TRUE(returned_opt_boot_file_name.option_);
+
+ {
+ SCOPED_TRACE("verify updated option with modified persistence");
+ testOptionsEquivalent(*opt_boot_file_name, returned_opt_boot_file_name);
+ }
+
+ {
+ SCOPED_TRACE("UPDATE audit entry for the updated shared network option");
+ testNewAuditEntry("dhcp4_shared_network",
+ AuditEntry::ModificationType::UPDATE,
+ "shared network specific option set");
+ }
+
+ // The new option instance should replace the existing option instance,
+ // so we should still have one option.
+ ASSERT_EQ(1, countRows("dhcp4_options"));
+
+ // Deleting an option for any server should succeed.
+ EXPECT_EQ(1, cbptr_->deleteOption4(ServerSelector::ANY(),
+ shared_network->getName(),
+ opt_boot_file_name->option_->getType(),
+ opt_boot_file_name->space_name_));
+ returned_network = cbptr_->getSharedNetwork4(ServerSelector::ALL(),
+ shared_network->getName());
+ ASSERT_TRUE(returned_network);
+ EXPECT_FALSE(returned_network->getCfgOption()->get(DHCP4_OPTION_SPACE, DHO_BOOT_FILE_NAME).option_);
+
+ {
+ SCOPED_TRACE("UPDATE audit entry for the deleted shared network option");
+ testNewAuditEntry("dhcp4_shared_network",
+ AuditEntry::ModificationType::UPDATE,
+ "shared network specific option deleted");
+ }
+
+ // After deleting the option we should be back to 0.
+ EXPECT_EQ(0, countRows("dhcp4_options"));
+}
+
+void
+GenericConfigBackendDHCPv4Test::subnetOptionIdOrderTest() {
+
+ // Add a subnet with two pools with one option each.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet4(ServerSelector::ALL(), test_subnets_[1]));
+ EXPECT_EQ(2, countRows("dhcp4_pool"));
+ EXPECT_EQ(2, countRows("dhcp4_options"));
+
+ // Add a second subnet with a single option. The number of options in the database
+ // should now be 3.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet4(ServerSelector::ALL(), test_subnets_[2]));
+ EXPECT_EQ(2, countRows("dhcp4_pool"));
+ EXPECT_EQ(3, countRows("dhcp4_options"));
+
+ // Now replace the first subnet with three options and two pools. This will cause
+ // the option id values for this subnet to be larger than those in the second
+ // subnet.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet4(ServerSelector::ALL(), test_subnets_[0]));
+ EXPECT_EQ(2, countRows("dhcp4_pool"));
+ EXPECT_EQ(4, countRows("dhcp4_options"));
+
+ // Now fetch all subnets.
+ Subnet4Collection subnets;
+ ASSERT_NO_THROW_LOG(subnets = cbptr_->getAllSubnets4(ServerSelector::ALL()));
+ ASSERT_EQ(2, subnets.size());
+
+ // Verify that the subnets returned are as expected.
+ for (auto subnet : subnets) {
+ ASSERT_EQ(1, subnet->getServerTags().size());
+ EXPECT_EQ("all", subnet->getServerTags().begin()->get());
+ if (subnet->getID() == 1024) {
+ EXPECT_EQ(test_subnets_[0]->toElement()->str(), subnet->toElement()->str());
+ } else if (subnet->getID() == 2048) {
+ EXPECT_EQ(test_subnets_[2]->toElement()->str(), subnet->toElement()->str());
+ } else {
+ ADD_FAILURE() << "unexpected subnet id:" << subnet->getID();
+ }
+ }
+}
+
+void
+GenericConfigBackendDHCPv4Test::sharedNetworkOptionIdOrderTest() {
+ auto level1_options = test_networks_[0];
+ auto level1_no_options = test_networks_[1];
+ auto level2 = test_networks_[2];
+
+ // Insert two shared networks. We insert level1 without options first,
+ // then level2.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork4(ServerSelector::ALL(),
+ level1_no_options));
+
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork4(ServerSelector::ALL(),
+ level2));
+ // Fetch all shared networks.
+ SharedNetwork4Collection networks =
+ cbptr_->getAllSharedNetworks4(ServerSelector::ALL());
+
+ ASSERT_EQ(2, networks.size());
+
+ // See if shared networks are returned ok.
+ for (auto i = 0; i < networks.size(); ++i) {
+ if (i == 0) {
+ // level1_no_options
+ EXPECT_EQ(level1_no_options->toElement()->str(),
+ networks[i]->toElement()->str());
+ } else {
+ // bar
+ EXPECT_EQ(level2->toElement()->str(),
+ networks[i]->toElement()->str());
+ }
+ }
+
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork4(ServerSelector::ALL(),
+ level1_options));
+
+ // Fetch all shared networks.
+ networks = cbptr_->getAllSharedNetworks4(ServerSelector::ALL());
+ ASSERT_EQ(2, networks.size());
+
+ // See if shared networks are returned ok.
+ for (auto i = 0; i < networks.size(); ++i) {
+ if (i == 0) {
+ // level1_no_options
+ EXPECT_EQ(level1_options->toElement()->str(),
+ networks[i]->toElement()->str());
+ } else {
+ // bar
+ EXPECT_EQ(level2->toElement()->str(),
+ networks[i]->toElement()->str());
+ }
+ }
+}
+
+void
+GenericConfigBackendDHCPv4Test::setAndGetAllClientClasses4Test() {
+ // Create a server.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer4(test_servers_[0]));
+ {
+ SCOPED_TRACE("server1 is created");
+ testNewAuditEntry("dhcp4_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set",
+ ServerSelector::ONE("server1"));
+ }
+ // Create first class.
+ auto class1 = test_client_classes_[0];
+ class1->setTest("pkt4.msgtype == 1");
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass4(ServerSelector::ALL(), class1, ""));
+ {
+ SCOPED_TRACE("client class foo is created");
+ testNewAuditEntry("dhcp4_client_class",
+ AuditEntry::ModificationType::CREATE,
+ "client class set",
+ ServerSelector::ONE("server1"));
+ }
+ // Create second class.
+ auto class2 = test_client_classes_[1];
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass4(ServerSelector::ONE("server1"), class2, ""));
+ {
+ SCOPED_TRACE("client class bar is created");
+ testNewAuditEntry("dhcp4_client_class",
+ AuditEntry::ModificationType::CREATE,
+ "client class set",
+ ServerSelector::ONE("server1"));
+ }
+ // Create third class.
+ auto class3 = test_client_classes_[2];
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass4(ServerSelector::ONE("server1"), class3, ""));
+ {
+ SCOPED_TRACE("client class foobar is created");
+ testNewAuditEntry("dhcp4_client_class",
+ AuditEntry::ModificationType::CREATE,
+ "client class set",
+ ServerSelector::ONE("server1"));
+ }
+ // Update the third class to depend on the second class.
+ class3->setTest("member('foo')");
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass4(ServerSelector::ONE("server1"), class3, ""));
+ {
+ SCOPED_TRACE("client class bar is updated");
+ testNewAuditEntry("dhcp4_client_class",
+ AuditEntry::ModificationType::UPDATE,
+ "client class set",
+ ServerSelector::ONE("server1"));
+ }
+ // Only the first class should be returned for the server selector ALL.
+ auto client_classes = cbptr_->getAllClientClasses4(ServerSelector::ALL());
+ ASSERT_EQ(1, client_classes.getClasses()->size());
+ // All three classes should be returned for the server1.
+ client_classes = cbptr_->getAllClientClasses4(ServerSelector::ONE("server1"));
+ auto classes_list = client_classes.getClasses();
+ ASSERT_EQ(3, classes_list->size());
+
+ auto fetched_class = classes_list->begin();
+ ASSERT_EQ("foo", (*fetched_class)->getName());
+ EXPECT_FALSE((*fetched_class)->getMatchExpr());
+ EXPECT_EQ(class1->toElement()->str(), (*fetched_class)->toElement()->str());
+
+ ++fetched_class;
+ ASSERT_EQ("bar", (*fetched_class)->getName());
+ EXPECT_FALSE((*fetched_class)->getMatchExpr());
+ EXPECT_EQ(class2->toElement()->str(), (*fetched_class)->toElement()->str());
+
+ ++fetched_class;
+ ASSERT_EQ("foobar", (*fetched_class)->getName());
+ EXPECT_FALSE((*fetched_class)->getMatchExpr());
+ EXPECT_EQ(class3->toElement()->str(), (*fetched_class)->toElement()->str());
+
+ // Move the third class between the first and second class.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass4(ServerSelector::ONE("server1"), class3, "foo"));
+
+ // Ensure that the classes order has changed.
+ client_classes = cbptr_->getAllClientClasses4(ServerSelector::ONE("server1"));
+ classes_list = client_classes.getClasses();
+ ASSERT_EQ(3, classes_list->size());
+ EXPECT_EQ("foo", (*classes_list->begin())->getName());
+ EXPECT_FALSE((*classes_list->begin())->getMatchExpr());
+ EXPECT_EQ("foobar", (*(classes_list->begin() + 1))->getName());
+ EXPECT_FALSE((*(classes_list->begin() + 1))->getMatchExpr());
+ EXPECT_EQ("bar", (*(classes_list->begin() + 2))->getName());
+ EXPECT_FALSE((*(classes_list->begin() + 2))->getMatchExpr());
+
+ // Update the foobar class without specifying its position. It should not
+ // be moved.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass4(ServerSelector::ONE("server1"), class3, ""));
+
+ client_classes = cbptr_->getAllClientClasses4(ServerSelector::ONE("server1"));
+ classes_list = client_classes.getClasses();
+ ASSERT_EQ(3, classes_list->size());
+ EXPECT_EQ("foo", (*classes_list->begin())->getName());
+ EXPECT_FALSE((*classes_list->begin())->getMatchExpr());
+ EXPECT_EQ("foobar", (*(classes_list->begin() + 1))->getName());
+ EXPECT_FALSE((*(classes_list->begin() + 1))->getMatchExpr());
+ EXPECT_EQ("bar", (*(classes_list->begin() + 2))->getName());
+ EXPECT_FALSE((*(classes_list->begin() + 2))->getMatchExpr());
+}
+
+void
+GenericConfigBackendDHCPv4Test::getClientClass4Test() {
+ // Create a server.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer4(test_servers_[0]));
+
+ // Add classes.
+ auto class1 = test_client_classes_[0];
+ ASSERT_NO_THROW_LOG(class1->getCfgOption()->add(test_options_[0]->option_,
+ test_options_[0]->persistent_,
+ test_options_[0]->space_name_));
+ ASSERT_NO_THROW_LOG(class1->getCfgOption()->add(test_options_[1]->option_,
+ test_options_[1]->persistent_,
+ test_options_[1]->space_name_));
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass4(ServerSelector::ALL(), class1, ""));
+
+ auto class2 = test_client_classes_[1];
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass4(ServerSelector::ONE("server1"), class2, ""));
+
+ // Get the first client class and validate its contents.
+ ClientClassDefPtr client_class;
+ ASSERT_NO_THROW_LOG(client_class = cbptr_->getClientClass4(ServerSelector::ALL(), class1->getName()));
+ ASSERT_TRUE(client_class);
+ EXPECT_EQ("foo", client_class->getName());
+ EXPECT_TRUE(client_class->getRequired());
+ EXPECT_EQ("1.2.3.4", client_class->getNextServer().toText());
+ EXPECT_EQ("cool", client_class->getSname());
+ EXPECT_EQ("epc.cfg", client_class->getFilename());
+ EXPECT_EQ(30, client_class->getValid().getMin());
+ EXPECT_EQ(60, client_class->getValid().get());
+ EXPECT_EQ(90, client_class->getValid().getMax());
+
+ // Validate options belonging to this class.
+ ASSERT_TRUE(client_class->getCfgOption());
+ OptionDescriptor returned_opt_boot_file_name =
+ client_class->getCfgOption()->get(DHCP4_OPTION_SPACE, DHO_BOOT_FILE_NAME);
+ ASSERT_TRUE(returned_opt_boot_file_name.option_);
+
+ OptionDescriptor returned_opt_ip_ttl =
+ client_class->getCfgOption()->get(DHCP4_OPTION_SPACE, DHO_DEFAULT_IP_TTL);
+ ASSERT_TRUE(returned_opt_ip_ttl.option_);
+
+ // Fetch the same class using different server selectors.
+ ASSERT_NO_THROW_LOG(client_class = cbptr_->getClientClass4(ServerSelector::ANY(),
+ class1->getName()));
+ EXPECT_TRUE(client_class);
+
+ ASSERT_NO_THROW_LOG(client_class = cbptr_->getClientClass4(ServerSelector::ONE("server1"),
+ class1->getName()));
+ EXPECT_TRUE(client_class);
+
+ ASSERT_NO_THROW_LOG(client_class = cbptr_->getClientClass4(ServerSelector::UNASSIGNED(),
+ class1->getName()));
+ EXPECT_FALSE(client_class);
+
+ // Fetch the second client class using different selectors. This time the
+ // class should not be returned for the ALL server selector because it is
+ // associated with the server1.
+ ASSERT_NO_THROW_LOG(client_class = cbptr_->getClientClass4(ServerSelector::ALL(),
+ class2->getName()));
+ EXPECT_FALSE(client_class);
+
+ ASSERT_NO_THROW_LOG(client_class = cbptr_->getClientClass4(ServerSelector::ANY(),
+ class2->getName()));
+ EXPECT_TRUE(client_class);
+
+ ASSERT_NO_THROW_LOG(client_class = cbptr_->getClientClass4(ServerSelector::ONE("server1"),
+ class2->getName()));
+ EXPECT_TRUE(client_class);
+
+ ASSERT_NO_THROW_LOG(client_class = cbptr_->getClientClass4(ServerSelector::UNASSIGNED(),
+ class2->getName()));
+ EXPECT_FALSE(client_class);
+}
+
+void
+GenericConfigBackendDHCPv4Test::createUpdateClientClass4OptionsTest() {
+ // Add class with two options and two option definitions.
+ auto class1 = test_client_classes_[0];
+ ASSERT_NO_THROW_LOG(class1->getCfgOption()->add(test_options_[0]->option_,
+ test_options_[0]->persistent_,
+ test_options_[0]->space_name_));
+ ASSERT_NO_THROW_LOG(class1->getCfgOption()->add(test_options_[1]->option_,
+ test_options_[1]->persistent_,
+ test_options_[1]->space_name_));
+ auto cfg_option_def = boost::make_shared<CfgOptionDef>();
+ class1->setCfgOptionDef(cfg_option_def);
+ ASSERT_NO_THROW_LOG(class1->getCfgOptionDef()->add(test_option_defs_[0]));
+ ASSERT_NO_THROW_LOG(class1->getCfgOptionDef()->add(test_option_defs_[2]));
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass4(ServerSelector::ALL(), class1, ""));
+
+ // Fetch the class and the options from the database.
+ ClientClassDefPtr client_class;
+ ASSERT_NO_THROW_LOG(client_class = cbptr_->getClientClass4(ServerSelector::ALL(), class1->getName()));
+ ASSERT_TRUE(client_class);
+
+ // Validate options belonging to the class.
+ ASSERT_TRUE(client_class->getCfgOption());
+ OptionDescriptor returned_opt_boot_file_name =
+ client_class->getCfgOption()->get(DHCP4_OPTION_SPACE, DHO_BOOT_FILE_NAME);
+ ASSERT_TRUE(returned_opt_boot_file_name.option_);
+
+ OptionDescriptor returned_opt_ip_ttl =
+ client_class->getCfgOption()->get(DHCP4_OPTION_SPACE, DHO_DEFAULT_IP_TTL);
+ ASSERT_TRUE(returned_opt_ip_ttl.option_);
+
+ // Validate option definitions belonging to the class.
+ ASSERT_TRUE(client_class->getCfgOptionDef());
+ auto returned_def_foo = client_class->getCfgOptionDef()->get(test_option_defs_[0]->getOptionSpaceName(),
+ test_option_defs_[0]->getCode());
+ ASSERT_TRUE(returned_def_foo);
+ EXPECT_EQ(234, returned_def_foo->getCode());
+ EXPECT_EQ("foo", returned_def_foo->getName());
+ EXPECT_EQ(DHCP4_OPTION_SPACE, returned_def_foo->getOptionSpaceName());
+ EXPECT_EQ("espace", returned_def_foo->getEncapsulatedSpace());
+ EXPECT_EQ(OPT_STRING_TYPE, returned_def_foo->getType());
+ EXPECT_FALSE(returned_def_foo->getArrayType());
+
+ auto returned_def_fish = client_class->getCfgOptionDef()->get(test_option_defs_[2]->getOptionSpaceName(),
+ test_option_defs_[2]->getCode());
+ ASSERT_TRUE(returned_def_fish);
+ EXPECT_EQ(235, returned_def_fish->getCode());
+ EXPECT_EQ("fish", returned_def_fish->getName());
+ EXPECT_EQ(DHCP4_OPTION_SPACE, returned_def_fish->getOptionSpaceName());
+ EXPECT_TRUE(returned_def_fish->getEncapsulatedSpace().empty());
+ EXPECT_EQ(OPT_RECORD_TYPE, returned_def_fish->getType());
+ EXPECT_TRUE(returned_def_fish->getArrayType());
+
+ // Replace client class specific option definitions. Leave only one option
+ // definition.
+ cfg_option_def = boost::make_shared<CfgOptionDef>();
+ class1->setCfgOptionDef(cfg_option_def);
+ ASSERT_NO_THROW_LOG(class1->getCfgOptionDef()->add(test_option_defs_[2]));
+
+ // Delete one of the options and update the class.
+ class1->getCfgOption()->del(test_options_[0]->space_name_,
+ test_options_[0]->option_->getType());
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass4(ServerSelector::ALL(), class1, ""));
+ ASSERT_NO_THROW_LOG(client_class = cbptr_->getClientClass4(ServerSelector::ALL(), class1->getName()));
+ ASSERT_TRUE(client_class);
+
+ // Ensure that the first option definition is gone.
+ ASSERT_TRUE(client_class->getCfgOptionDef());
+ returned_def_foo = client_class->getCfgOptionDef()->get(test_option_defs_[0]->getOptionSpaceName(),
+ test_option_defs_[0]->getCode());
+ EXPECT_FALSE(returned_def_foo);
+
+ // The second option definition should be present.
+ returned_def_fish = client_class->getCfgOptionDef()->get(test_option_defs_[2]->getOptionSpaceName(),
+ test_option_defs_[2]->getCode());
+ EXPECT_TRUE(returned_def_fish);
+
+ // Make sure that the first option is gone.
+ ASSERT_TRUE(client_class->getCfgOption());
+ returned_opt_boot_file_name = client_class->getCfgOption()->get(DHCP4_OPTION_SPACE,
+ DHO_BOOT_FILE_NAME);
+ EXPECT_FALSE(returned_opt_boot_file_name.option_);
+
+ // The second option should be there.
+ returned_opt_ip_ttl = client_class->getCfgOption()->get(DHCP4_OPTION_SPACE,
+ DHO_DEFAULT_IP_TTL);
+ ASSERT_TRUE(returned_opt_ip_ttl.option_);
+}
+
+void
+GenericConfigBackendDHCPv4Test::getModifiedClientClasses4Test() {
+ // Create server1.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer4(test_servers_[0]));
+
+ // Add three classes to the database with different timestamps.
+ auto class1 = test_client_classes_[0];
+ class1->setModificationTime(timestamps_["yesterday"]);
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass4(ServerSelector::ALL(), class1, ""));
+
+ auto class2 = test_client_classes_[1];
+ class2->setModificationTime(timestamps_["today"]);
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass4(ServerSelector::ALL(), class2, ""));
+
+ auto class3 = test_client_classes_[2];
+ class3->setModificationTime(timestamps_["tomorrow"]);
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass4(ServerSelector::ONE("server1"), class3, ""));
+
+ // Get modified client classes configured for all servers.
+ auto client_classes = cbptr_->getModifiedClientClasses4(ServerSelector::ALL(),
+ timestamps_["two days ago"]);
+ EXPECT_EQ(2, client_classes.getClasses()->size());
+
+ // Get modified client classes appropriate for server1. It includes classes
+ // for all servers and for the server1.
+ client_classes = cbptr_->getModifiedClientClasses4(ServerSelector::ONE("server1"),
+ timestamps_["two days ago"]);
+ EXPECT_EQ(3, client_classes.getClasses()->size());
+
+ // Get the classes again but use the timestamp equal to the modification
+ // time of the first class.
+ client_classes = cbptr_->getModifiedClientClasses4(ServerSelector::ONE("server1"),
+ timestamps_["yesterday"]);
+ EXPECT_EQ(3, client_classes.getClasses()->size());
+
+ // Get modified classes starting from today. It should return only two.
+ client_classes = cbptr_->getModifiedClientClasses4(ServerSelector::ONE("server1"),
+ timestamps_["today"]);
+ EXPECT_EQ(2, client_classes.getClasses()->size());
+
+ // Get client classes modified in the future. It should return none.
+ client_classes = cbptr_->getModifiedClientClasses4(ServerSelector::ONE("server1"),
+ timestamps_["after tomorrow"]);
+ EXPECT_EQ(0, client_classes.getClasses()->size());
+
+ // Getting modified client classes for any server is unsupported.
+ ASSERT_THROW(cbptr_->getModifiedClientClasses4(ServerSelector::ANY(),
+ timestamps_["two days ago"]),
+ InvalidOperation);
+}
+
+void
+GenericConfigBackendDHCPv4Test::deleteClientClass4Test() {
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer4(test_servers_[0]));
+ {
+ SCOPED_TRACE("server1 is created");
+ testNewAuditEntry("dhcp4_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set",
+ ServerSelector::ONE("server1"));
+ }
+ {
+ SCOPED_TRACE("server1 is created");
+ testNewAuditEntry("dhcp4_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set",
+ ServerSelector::ONE("server2"));
+ }
+
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer4(test_servers_[2]));
+ {
+ SCOPED_TRACE("server1 is created and available for server1");
+ testNewAuditEntry("dhcp4_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set",
+ ServerSelector::ONE("server1"));
+ }
+ {
+ SCOPED_TRACE("server1 is created and available for server2");
+ testNewAuditEntry("dhcp4_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set",
+ ServerSelector::ONE("server2"));
+ }
+
+ auto class1 = test_client_classes_[0];
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass4(ServerSelector::ALL(), class1, ""));
+ {
+ SCOPED_TRACE("client class foo is created and available for server1");
+ testNewAuditEntry("dhcp4_client_class",
+ AuditEntry::ModificationType::CREATE,
+ "client class set",
+ ServerSelector::ONE("server1"));
+ }
+ {
+ SCOPED_TRACE("client class foo is created and available for server 2");
+ testNewAuditEntry("dhcp4_client_class",
+ AuditEntry::ModificationType::CREATE,
+ "client class set",
+ ServerSelector::ONE("server2"));
+ }
+
+ auto class2 = test_client_classes_[1];
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass4(ServerSelector::ONE("server1"), class2, ""));
+ {
+ SCOPED_TRACE("client class bar is created");
+ testNewAuditEntry("dhcp4_client_class",
+ AuditEntry::ModificationType::CREATE,
+ "client class set",
+ ServerSelector::ONE("server1"));
+ }
+
+ auto class3 = test_client_classes_[2];
+ class3->setTest("member('foo')");
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass4(ServerSelector::ONE("server2"), class3, ""));
+ {
+ SCOPED_TRACE("client class foobar is created");
+ testNewAuditEntry("dhcp4_client_class",
+ AuditEntry::ModificationType::CREATE,
+ "client class set",
+ ServerSelector::ONE("server2"));
+ }
+
+ uint64_t result;
+ ASSERT_NO_THROW_LOG(result = cbptr_->deleteClientClass4(ServerSelector::ONE("server1"),
+ class2->getName()));
+ EXPECT_EQ(1, result);
+ {
+ SCOPED_TRACE("client class bar is deleted");
+ testNewAuditEntry("dhcp4_client_class",
+ AuditEntry::ModificationType::DELETE,
+ "client class deleted",
+ ServerSelector::ONE("server1"));
+ }
+
+ ASSERT_NO_THROW_LOG(result = cbptr_->deleteClientClass4(ServerSelector::ONE("server2"),
+ class3->getName()));
+ EXPECT_EQ(1, result);
+ {
+ SCOPED_TRACE("client class foobar is deleted");
+ testNewAuditEntry("dhcp4_client_class",
+ AuditEntry::ModificationType::DELETE,
+ "client class deleted",
+ ServerSelector::ONE("server2"));
+ }
+
+ ASSERT_NO_THROW_LOG(result = cbptr_->deleteClientClass4(ServerSelector::ANY(),
+ class1->getName()));
+ EXPECT_EQ(1, result);
+ {
+ SCOPED_TRACE("client class foo is deleted and no longer available for the server1");
+ testNewAuditEntry("dhcp4_client_class",
+ AuditEntry::ModificationType::DELETE,
+ "client class deleted",
+ ServerSelector::ONE("server1"));
+ }
+ {
+ SCOPED_TRACE("client class foo is deleted and no longer available for the server2");
+ testNewAuditEntry("dhcp4_client_class",
+ AuditEntry::ModificationType::DELETE,
+ "client class deleted",
+ ServerSelector::ONE("server2"));
+ }
+}
+
+void
+GenericConfigBackendDHCPv4Test::deleteAllClientClasses4Test() {
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer4(test_servers_[0]));
+ {
+ SCOPED_TRACE("server1 is created");
+ testNewAuditEntry("dhcp4_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set",
+ ServerSelector::ONE("server1"));
+ }
+ {
+ SCOPED_TRACE("server1 is created");
+ testNewAuditEntry("dhcp4_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set",
+ ServerSelector::ONE("server2"));
+ }
+
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer4(test_servers_[2]));
+ {
+ SCOPED_TRACE("server1 is created and available for server1");
+ testNewAuditEntry("dhcp4_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set",
+ ServerSelector::ONE("server1"));
+ }
+ {
+ SCOPED_TRACE("server1 is created and available for server2");
+ testNewAuditEntry("dhcp4_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set",
+ ServerSelector::ONE("server2"));
+ }
+
+ auto class1 = test_client_classes_[0];
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass4(ServerSelector::ALL(), class1, ""));
+ {
+ SCOPED_TRACE("client class foo is created and available for server1");
+ testNewAuditEntry("dhcp4_client_class",
+ AuditEntry::ModificationType::CREATE,
+ "client class set",
+ ServerSelector::ONE("server1"));
+ }
+ {
+ SCOPED_TRACE("client class foo is created and available for server 2");
+ testNewAuditEntry("dhcp4_client_class",
+ AuditEntry::ModificationType::CREATE,
+ "client class set",
+ ServerSelector::ONE("server2"));
+ }
+
+ auto class2 = test_client_classes_[1];
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass4(ServerSelector::ONE("server1"), class2, ""));
+ {
+ SCOPED_TRACE("client class bar is created");
+ testNewAuditEntry("dhcp4_client_class",
+ AuditEntry::ModificationType::CREATE,
+ "client class set",
+ ServerSelector::ONE("server1"));
+ }
+
+ auto class3 = test_client_classes_[2];
+ class3->setTest("member('foo')");
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass4(ServerSelector::ONE("server2"), class3, ""));
+ {
+ SCOPED_TRACE("client class foobar is created");
+ testNewAuditEntry("dhcp4_client_class",
+ AuditEntry::ModificationType::CREATE,
+ "client class set",
+ ServerSelector::ONE("server2"));
+ }
+
+ uint64_t result;
+
+ ASSERT_NO_THROW_LOG(result = cbptr_->deleteAllClientClasses4(ServerSelector::UNASSIGNED()));
+ EXPECT_EQ(0, result);
+
+ ASSERT_NO_THROW_LOG(result = cbptr_->deleteAllClientClasses4(ServerSelector::ONE("server2")));
+ EXPECT_EQ(1, result);
+ {
+ SCOPED_TRACE("client classes for server2 deleted");
+ testNewAuditEntry("dhcp4_client_class",
+ AuditEntry::ModificationType::DELETE,
+ "deleted all client classes",
+ ServerSelector::ONE("server2"));
+ }
+
+ ASSERT_NO_THROW_LOG(result = cbptr_->deleteAllClientClasses4(ServerSelector::ONE("server2")));
+ EXPECT_EQ(0, result);
+
+ ASSERT_NO_THROW_LOG(result = cbptr_->deleteAllClientClasses4(ServerSelector::ONE("server1")));
+ EXPECT_EQ(1, result);
+ {
+ SCOPED_TRACE("client classes for server1 deleted");
+ testNewAuditEntry("dhcp4_client_class",
+ AuditEntry::ModificationType::DELETE,
+ "deleted all client classes",
+ ServerSelector::ONE("server1"));
+ }
+
+ ASSERT_NO_THROW_LOG(result = cbptr_->deleteAllClientClasses4(ServerSelector::ONE("server1")));
+ EXPECT_EQ(0, result);
+
+ ASSERT_NO_THROW_LOG(result = cbptr_->deleteAllClientClasses4(ServerSelector::ALL()));
+ EXPECT_EQ(1, result);
+ {
+ SCOPED_TRACE("client classes for all deleted");
+ testNewAuditEntry("dhcp4_client_class",
+ AuditEntry::ModificationType::DELETE,
+ "deleted all client classes",
+ ServerSelector::ONE("server1"));
+ }
+
+ ASSERT_NO_THROW_LOG(result = cbptr_->deleteAllClientClasses4(ServerSelector::ALL()));
+ EXPECT_EQ(0, result);
+
+ // Deleting multiple objects using ANY server tag is unsupported.
+ ASSERT_THROW(cbptr_->deleteAllClientClasses4(ServerSelector::ANY()), InvalidOperation);
+}
+
+void
+GenericConfigBackendDHCPv4Test::clientClassDependencies4Test() {
+ // Create a server.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer4(test_servers_[0]));
+
+ // Create first class. It depends on KNOWN built-in class.
+ auto class1 = test_client_classes_[0];
+ class1->setTest("member('KNOWN')");
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass4(ServerSelector::ALL(), class1, ""));
+
+ // Create second class which depends on the first class. This yelds indirect
+ // dependency on KNOWN class.
+ auto class2 = test_client_classes_[1];
+ class2->setTest("member('foo')");
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass4(ServerSelector::ALL(), class2, ""));
+
+ // Create third class depending on the second class. This also yelds indirect
+ // dependency on KNOWN class.
+ auto class3 = test_client_classes_[2];
+ class3->setTest("member('bar')");
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass4(ServerSelector::ALL(), class3, ""));
+
+ // An attempt to move the first class to the end of the class hierarchy should
+ // fail because other classes depend on it.
+ ASSERT_THROW(cbptr_->createUpdateClientClass4(ServerSelector::ALL(), class1, "bar"),
+ DbOperationError);
+
+ // Try to change the dependency of the first class. There are other classes
+ // having indirect dependency on KNOWN class via this class. Therefore, the
+ // update should be unsuccessful.
+ class1->setTest("member('HA_server1')");
+ ASSERT_THROW(cbptr_->createUpdateClientClass4(ServerSelector::ALL(), class1, ""),
+ DbOperationError);
+
+ // Try to change the dependency of the second class. This should result in
+ // an error because the third class depends on it.
+ class2->setTest("member('HA_server1')");
+ ASSERT_THROW(cbptr_->createUpdateClientClass4(ServerSelector::ALL(), class2, ""),
+ DbOperationError);
+
+ // Changing the indirect dependency of the third class should succeed, because
+ // no other classes depend on this class.
+ class3->setTest("member('HA_server1')");
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass4(ServerSelector::ALL(), class3, ""));
+}
+
+void
+GenericConfigBackendDHCPv4Test::multipleAuditEntriesTest() {
+ // Get current time.
+ boost::posix_time::ptime now = boost::posix_time::second_clock::local_time();
+
+ // Create a server.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer4(test_servers_[1]));
+
+ // Create a global parameter and update it many times.
+ const ServerSelector& server_selector = ServerSelector::ALL();
+ StampedValuePtr param;
+ ElementPtr value;
+ for (int i = 0; i < 100; ++i) {
+ value = Element::create(i);
+ param = StampedValue::create("my-parameter", value);
+ cbptr_->createUpdateGlobalParameter4(server_selector, param);
+ }
+
+ // Get all audit entries from now.
+ AuditEntryCollection audit_entries =
+ cbptr_->getRecentAuditEntries(server_selector, now, 0);
+
+ // Check that partial retrieves return the right count.
+ auto& mod_time_idx = audit_entries.get<AuditEntryModificationTimeIdTag>();
+ for (auto it = mod_time_idx.begin(); it != mod_time_idx.end(); ++it) {
+ size_t partial_size =
+ cbptr_->getRecentAuditEntries(server_selector,
+ (*it)->getModificationTime(),
+ (*it)->getRevisionId()).size();
+ EXPECT_EQ(partial_size + 1,
+ std::distance(it, mod_time_idx.end()));
+ }
+}
diff --git a/src/lib/dhcpsrv/testutils/generic_cb_dhcp4_unittest.h b/src/lib/dhcpsrv/testutils/generic_cb_dhcp4_unittest.h
new file mode 100644
index 0000000..a2fa5dc
--- /dev/null
+++ b/src/lib/dhcpsrv/testutils/generic_cb_dhcp4_unittest.h
@@ -0,0 +1,390 @@
+// Copyright (C) 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/.
+
+#ifndef GENERIC_CONFIG_BACKEND_DHCP4_H
+#define GENERIC_CONFIG_BACKEND_DHCP4_H
+
+#include <database/database_connection.h>
+#include <dhcpsrv/config_backend_dhcp4_mgr.h>
+#include <dhcpsrv/testutils/generic_backend_unittest.h>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+/// @brief Generic test fixture class for testing DHCPv4
+/// config backend operations.
+class GenericConfigBackendDHCPv4Test : public GenericBackendTest {
+public:
+ /// @brief Constructor.
+ GenericConfigBackendDHCPv4Test()
+ : test_subnets_(), test_networks_(), test_option_defs_(),
+ test_options_(), test_client_classes_(), test_servers_(), cbptr_() {
+ }
+
+ /// @brief Destructor.
+ virtual ~GenericConfigBackendDHCPv4Test() {};
+
+ /// @brief Prepares the class for a test.
+ ///
+ /// Invoked by gtest prior test entry, we create the
+ /// appropriate schema and create a basic host manager to
+ /// wipe out any prior instance
+ virtual void SetUp();
+
+ /// @brief Pre-test exit clean up
+ ///
+ /// Invoked by gtest upon test exit, we destroy the schema
+ /// we created.
+ virtual void TearDown();
+
+ /// @brief Abstract method for destroying the back end specific schema
+ virtual void destroySchema() = 0;
+
+ /// @brief Abstract method for creating the back end specific schema
+ virtual void createSchema() = 0;
+
+ /// @brief Abstract method which returns the back end specific connection
+ /// string
+ virtual std::string validConnectionString() = 0;
+
+ /// @brief Abstract method which instantiates an instance of a
+ /// DHCPv4 configuration back end.
+ ///
+ /// @params Connection parameters describing the back end to create.
+ /// @return Pointer to the newly created back end instance.
+ virtual ConfigBackendDHCPv4Ptr backendFactory(db::DatabaseConnection::ParameterMap&
+ params) = 0;
+
+ /// @brief Counts rows in a selected table in the back end database.
+ ///
+ /// This method can be used to verify that some configuration elements were
+ /// deleted from a selected table as a result of cascade delete or a trigger.
+ /// For example, deleting a subnet should trigger deletion of its address
+ /// pools and options. By counting the rows on each table we can determine
+ /// whether the deletion took place on all tables for which it was expected.
+ ///
+ /// @param table Table name.
+ /// @return Number of rows in the specified table.
+ virtual size_t countRows(const std::string& table) const = 0;
+
+ /// @brief Creates several servers used in tests.
+ void initTestServers();
+
+ /// @brief Creates several subnets used in tests.
+ void initTestSubnets();
+
+ /// @brief Creates several subnets used in tests.
+ void initTestSharedNetworks();
+
+ /// @brief Creates several option definitions used in tests.
+ void initTestOptionDefs();
+
+ /// @brief Creates several DHCP options used in tests.
+ void initTestOptions();
+
+ /// @brief Creates several client classes used in tests.
+ void initTestClientClasses();
+
+ /// @brief Tests that a backend of the given type can be instantiated.
+ ///
+ /// @param expected_type type of the back end created (i.e. "mysql",
+ /// "postgresql").
+ void getTypeTest(const std::string& expected_type);
+
+ /// @brief Verifies that a backend on the localhost can be instantiated.
+ void getHostTest();
+
+ /// @brief Verifies that a backend on the localhost port 0 can be instantiated.
+ void getPortTest();
+
+ /// @brief Retrieves the most recent audit entries.
+ ///
+ /// Allowed server selectors: ALL, ONE.
+ /// Not allowed server selectors: ANY, UNASSIGNED, MULTIPLE.
+ ///
+ /// @param server_selector Server selector.
+ /// @param modification_time Timestamp being a lower limit for the returned
+ /// result set, i.e. entries later than specified time are returned.
+ /// @param modification_id Identifier being a lower limit for the returned
+ /// result set, used when two (or more) entries have the same
+ /// modification_time.
+ /// @return Collection of audit entries.
+ virtual db::AuditEntryCollection
+ getRecentAuditEntries(const db::ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time,
+ const uint64_t& modification_id) const;
+
+ /// @brief This test verifies that the server can be added, updated and deleted.
+ void createUpdateDeleteServerTest();
+
+ /// @brief This test verifies that it is possible to retrieve all servers from the
+ /// database and then delete all of them.
+ void getAndDeleteAllServersTest();
+
+ /// @brief This test verifies that the global parameter can be added, updated and
+ /// deleted.
+ void createUpdateDeleteGlobalParameter4Test();
+
+ /// @brief This test verifies that it is possible to differentiate between the
+ /// global parameters by server tag and that the value specified for the
+ /// particular server overrides the value specified for all servers.
+ void globalParameters4WithServerTagsTest();
+
+ /// @brief This test verifies that all global parameters can be retrieved and deleted.
+ void getAllGlobalParameters4Test();
+
+ /// @brief This test verifies that modified global parameters can be retrieved.
+ void getModifiedGlobalParameters4Test();
+
+ /// @brief Test that the NullKeyError message is correctly updated.
+ void nullKeyErrorTest();
+
+ /// @brief Test that createUpdateSubnet4 throws appropriate exceptions for various
+ /// server selectors.
+ void createUpdateSubnet4SelectorsTest();
+
+ /// @brief Test that subnet can be inserted, fetched, updated and then fetched again.
+ void getSubnet4Test();
+
+ /// @brief Test that getSubnet4 by ID throws appropriate exceptions for various server
+ /// selectors.
+ void getSubnet4byIdSelectorsTest();
+
+ /// @brief Test that the information about unspecified optional parameters gets
+ /// propagated to the database.
+ void getSubnet4WithOptionalUnspecifiedTest();
+
+ /// @brief Test that subnet can be associated with a shared network.
+ void getSubnet4SharedNetworkTest();
+
+ /// @brief Test that subnet can be fetched by prefix.
+ void getSubnet4ByPrefixTest();
+
+ /// @brief Test that getSubnet4 by prefix throws appropriate exceptions for various server
+ /// selectors.
+ void getSubnet4byPrefixSelectorsTest();
+
+ /// @brief Test that all subnets can be fetched and then deleted.
+ void getAllSubnets4Test();
+
+ /// @brief Test that getAllSubnets4 throws appropriate exceptions for various
+ /// server selectors.
+ void getAllSubnets4SelectorsTest();
+
+ /// @brief Test that subnets with different server associations are returned.
+ void getAllSubnets4WithServerTagsTest();
+
+ /// @brief Test that getModifiedSubnets4 throws appropriate exceptions for various
+ /// server selectors.
+ void getModifiedSubnets4SelectorsTest();
+
+ /// @brief Test that selected subnet can be deleted.
+ void deleteSubnet4Test();
+
+ /// @brief Test that deleteSubnet4 by ID throws appropriate exceptions for various
+ /// server selectors.
+ void deleteSubnet4ByIdSelectorsTest();
+
+ /// @brief Test that deleteSubnet4 by prefix throws appropriate exceptions for various
+ /// server selectors.
+ void deleteSubnet4ByPrefixSelectorsTest();
+
+ /// @brief Test that deleteAllSubnets4 throws appropriate exceptions for various
+ /// server selectors.
+ void deleteAllSubnets4SelectorsTest();
+
+ /// @brief Test that it is possible to retrieve and delete orphaned subnet.
+ void unassignedSubnet4Test();
+
+ /// @brief Test that subnets modified after given time can be fetched.
+ void getModifiedSubnets4Test();
+
+ /// @brief Test that lifetimes in subnets are handled as expected.
+ void subnetLifetimeTest();
+
+ /// @brief Test that subnets belonging to a shared network can be retrieved.
+ void getSharedNetworkSubnets4Test();
+
+ /// @brief Test that pools are properly updated as a result a subnet update.
+ void subnetUpdatePoolsTest();
+
+ /// @brief Test that deleting a subnet triggers deletion of the options associated
+ /// with the subnet and pools.
+ void subnetOptionsTest();
+
+ /// @brief Test that shared network can be inserted, fetched, updated and then
+ /// fetched again.
+ void getSharedNetwork4Test();
+
+ /// @brief Test that getSharedNetwork4 throws appropriate exceptions for various
+ /// server selectors.
+ void getSharedNetwork4SelectorsTest();
+
+ /// @brief Test that shared network may be created and updated and the server tags
+ /// are properly assigned to it.
+ void createUpdateSharedNetwork4Test();
+
+ /// @brief Test that createUpdateSharedNetwork4 throws appropriate exceptions for various
+ /// server selectors.
+ void createUpdateSharedNetwork4SelectorsTest();
+
+ /// @brief Test that the information about unspecified optional parameters gets
+ /// propagated to the database.
+ void getSharedNetwork4WithOptionalUnspecifiedTest();
+
+ /// @brief Test that deleteSharedNetworkSubnets4 with not ANY selector throw.
+ void deleteSharedNetworkSubnets4Test();
+
+ /// @brief Test that all shared networks can be fetched.
+ void getAllSharedNetworks4Test();
+
+ /// @brief Test that getAllSharedNetworks4 throws appropriate exceptions for various
+ /// server selectors.
+ void getAllSharedNetworks4SelectorsTest();
+
+ /// @brief Test that shared networks with different server associations are returned.
+ void getAllSharedNetworks4WithServerTagsTest();
+
+ /// @brief Test that shared networks modified after given time can be fetched.
+ void getModifiedSharedNetworks4Test();
+
+ /// @brief Test that getModifiedSharedNetworks4 throws appropriate exceptions for various
+ /// server selectors.
+ void getModifiedSharedNetworks4SelectorsTest();
+
+ /// @brief Test that selected shared network can be deleted.
+ void deleteSharedNetwork4Test();
+
+ /// @brief Test that deleteSharedNetwork4 throws appropriate exceptions for various
+ /// server selectors.
+ void deleteSharedNetwork4SelectorsTest();
+
+ /// @brief Test that deleteAllSharedNetworks4 throws appropriate exceptions for various
+ /// server selectors.
+ void deleteAllSharedNetworks4SelectorsTest();
+
+ /// @brief Test that it is possible to retrieve and delete orphaned shared network.
+ void unassignedSharedNetworkTest();
+
+ /// @brief Test that lifetimes in shared networks are handled as expected.
+ void sharedNetworkLifetimeTest();
+
+ /// @brief Test that deleting a shared network triggers deletion of the options
+ /// associated with the shared network.
+ void sharedNetworkOptionsTest();
+
+ /// @brief Test that option definition can be inserted, fetched, updated and then
+ /// fetched again.
+ void getOptionDef4Test();
+
+ /// @brief This test verifies that it is possible to differentiate between the
+ /// option definitions by server tag and that the option definition
+ /// specified for the particular server overrides the definition for
+ /// all servers.
+ void optionDefs4WithServerTagsTest();
+
+ /// @brief Test that all option definitions can be fetched.
+ void getAllOptionDefs4Test();
+
+ /// @brief Test that option definitions modified after given time can be fetched.
+ void getModifiedOptionDefs4Test();
+
+ /// @brief This test verifies that global option can be added, updated and deleted.
+ void createUpdateDeleteOption4Test();
+
+ /// @brief This test verifies that it is possible to differentiate between the
+ /// global options by server tag and that the option specified for the
+ /// particular server overrides the value specified for all servers.
+ void globalOptions4WithServerTagsTest();
+
+ /// @brief This test verifies that all global options can be retrieved.
+ void getAllOptions4Test();
+
+ /// @brief This test verifies that modified global options can be retrieved.
+ void getModifiedOptions4Test();
+
+ /// @brief This test verifies that subnet level option can be added, updated and
+ /// deleted.
+ void createUpdateDeleteSubnetOption4Test();
+
+ /// @brief This test verifies that option can be inserted, updated and deleted
+ /// from the pool.
+ void createUpdateDeletePoolOption4Test();
+
+ /// @brief This test verifies that shared network level option can be added,
+ /// updated and deleted.
+ void createUpdateDeleteSharedNetworkOption4Test();
+
+ /// @brief This test verifies that option id values in one subnet do
+ /// not impact options returned in subsequent subnets when
+ /// fetching subnets from the backend.
+ void subnetOptionIdOrderTest();
+
+ /// @brief This test verifies that option id values in one shared network do
+ /// not impact options returned in subsequent shared networks when
+ /// fetching shared networks from the backend.
+ void sharedNetworkOptionIdOrderTest();
+
+ /// @brief This test verifies that it is possible to create client classes, update them
+ /// and retrieve all classes for a given server.
+ void setAndGetAllClientClasses4Test();
+
+ /// @brief This test verifies that a single class can be retrieved from the database.
+ void getClientClass4Test();
+
+ /// @brief This test verifies that client class specific DHCP options can be
+ /// modified during the class update.
+ void createUpdateClientClass4OptionsTest();
+
+ /// @brief This test verifies that modified client classes can be retrieved from the database.
+ void getModifiedClientClasses4Test();
+
+ /// @brief This test verifies that a specified client class can be deleted.
+ void deleteClientClass4Test();
+
+ /// @brief This test verifies that all client classes can be deleted using
+ /// a specified server selector.
+ void deleteAllClientClasses4Test();
+
+ /// @brief This test verifies that client class dependencies are tracked when the
+ /// classes are added to the database. It verifies that an attempt to update
+ /// a class violating the dependencies results in an error.
+ void clientClassDependencies4Test();
+
+ /// @brief This test verifies that audit entries can be retrieved from a given
+ /// timestamp and id including when two entries can get the same timestamp.
+ /// (either it is a common even and this should catch it, or it is a rare
+ /// event and it does not matter).
+ void multipleAuditEntriesTest();
+
+ /// @brief Holds pointers to subnets used in tests.
+ std::vector<Subnet4Ptr> test_subnets_;
+
+ /// @brief Holds pointers to shared networks used in tests.
+ std::vector<SharedNetwork4Ptr> test_networks_;
+
+ /// @brief Holds pointers to option definitions used in tests.
+ std::vector<OptionDefinitionPtr> test_option_defs_;
+
+ /// @brief Holds pointers to options used in tests.
+ std::vector<OptionDescriptorPtr> test_options_;
+
+ /// @brief Holds pointers to classes used in tests.
+ std::vector<ClientClassDefPtr> test_client_classes_;
+
+ /// @brief Holds pointers to the servers used in tests.
+ std::vector<db::ServerPtr> test_servers_;
+
+ /// @brief Holds pointer to the backend.
+ boost::shared_ptr<ConfigBackendDHCPv4> cbptr_;
+};
+
+} // namespace test
+} // namespace dhcp
+} // namespace isc
+
+#endif // GENERIC_CONFIG_BACKEND_DHCP4_H
diff --git a/src/lib/dhcpsrv/testutils/generic_cb_dhcp6_unittest.cc b/src/lib/dhcpsrv/testutils/generic_cb_dhcp6_unittest.cc
new file mode 100644
index 0000000..7322d52
--- /dev/null
+++ b/src/lib/dhcpsrv/testutils/generic_cb_dhcp6_unittest.cc
@@ -0,0 +1,4747 @@
+// Copyright (C) 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 <asiolink/addr_utilities.h>
+#include <database/database_connection.h>
+#include <database/db_exceptions.h>
+#include <database/server.h>
+#include <database/testutils/schema.h>
+#include <dhcp/libdhcp++.h>
+#include <dhcp/option6_addrlst.h>
+#include <dhcp/option_int.h>
+#include <dhcp/option_space.h>
+#include <dhcp/option_string.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/client_class_def.h>
+#include <dhcpsrv/config_backend_dhcp6_mgr.h>
+#include <dhcpsrv/pool.h>
+#include <dhcpsrv/subnet.h>
+#include <dhcpsrv/testutils/generic_cb_dhcp6_unittest.h>
+#include <dhcpsrv/testutils/test_utils.h>
+#include <testutils/gtest_utils.h>
+
+#include <boost/make_shared.hpp>
+#include <boost/shared_ptr.hpp>
+#include <gtest/gtest.h>
+
+using namespace isc;
+using namespace isc::util;
+using namespace isc::asiolink;
+using namespace isc::db;
+using namespace isc::db::test;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+using namespace isc::process;
+using namespace isc::test;
+namespace ph = std::placeholders;
+
+void
+GenericConfigBackendDHCPv6Test::SetUp() {
+ CfgMgr::instance().setFamily(AF_INET6);
+
+ // Ensure we have the proper schema with no transient data.
+ createSchema();
+
+ try {
+ // Create a connection parameter map and use it to start the backend.
+ DatabaseConnection::ParameterMap params =
+ DatabaseConnection::parse(validConnectionString());
+ cbptr_ = backendFactory(params);
+ } catch (...) {
+ std::cerr << "*** ERROR: unable to open database. The test\n"
+ "*** environment is broken and must be fixed before\n"
+ "*** the tests will run correctly.\n"
+ "*** The reason for the problem is described in the\n"
+ "*** accompanying exception output.\n";
+ throw;
+ }
+
+ // Create test data.
+ initTestServers();
+ initTestOptions();
+ initTestSubnets();
+ initTestSharedNetworks();
+ initTestOptionDefs();
+ initTestClientClasses();
+}
+
+void
+GenericConfigBackendDHCPv6Test::TearDown() {
+ cbptr_.reset();
+ // If data wipe enabled, delete transient data otherwise destroy the schema.
+ destroySchema();
+}
+
+db::AuditEntryCollection
+GenericConfigBackendDHCPv6Test::getRecentAuditEntries(const db::ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time,
+ const uint64_t& modification_id) const {
+ return (cbptr_->getRecentAuditEntries(server_selector, modification_time, modification_id));
+}
+
+void
+GenericConfigBackendDHCPv6Test::initTestServers() {
+ test_servers_.push_back(Server::create(ServerTag("server1"), "this is server 1"));
+ test_servers_.push_back(Server::create(ServerTag("server1"), "this is server 1 bis"));
+ test_servers_.push_back(Server::create(ServerTag("server2"), "this is server 2"));
+ test_servers_.push_back(Server::create(ServerTag("server3"), "this is server 3"));
+}
+
+void
+GenericConfigBackendDHCPv6Test::initTestSubnets() {
+ // First subnet includes all parameters.
+ std::string interface_id_text = "whale";
+ OptionBuffer interface_id(interface_id_text.begin(), interface_id_text.end());
+ OptionPtr opt_interface_id(new Option(Option::V6, D6O_INTERFACE_ID,
+ interface_id));
+ ElementPtr user_context = Element::createMap();
+ user_context->set("foo", Element::create("bar"));
+
+ Subnet6Ptr subnet(new Subnet6(IOAddress("2001:db8::"), 64,
+ 30, 40, 50, 60, 1024));
+ subnet->allowClientClass("home");
+ subnet->setIface("eth1");
+ subnet->setInterfaceId(opt_interface_id);
+ subnet->setT2(323212);
+ subnet->addRelayAddress(IOAddress("2001:db8:1::2"));
+ subnet->addRelayAddress(IOAddress("2001:db8:3::4"));
+ subnet->setT1(1234);
+ subnet->requireClientClass("required-class1");
+ subnet->requireClientClass("required-class2");
+ subnet->setReservationsGlobal(false);
+ subnet->setReservationsInSubnet(false);
+ subnet->setContext(user_context);
+ subnet->setValid(555555);
+ subnet->setPreferred(4444444);
+ subnet->setCalculateTeeTimes(true);
+ subnet->setT1Percent(0.345);
+ subnet->setT2Percent(0.444);
+ subnet->setDdnsSendUpdates(false);
+
+ Pool6Ptr pool1(new Pool6(Lease::TYPE_NA,
+ IOAddress("2001:db8::10"),
+ IOAddress("2001:db8::20")));
+ subnet->addPool(pool1);
+
+ Pool6Ptr pool2(new Pool6(Lease::TYPE_NA,
+ IOAddress("2001:db8::50"),
+ IOAddress("2001:db8::60")));
+ subnet->addPool(pool2);
+
+ Pool6Ptr pdpool1(new Pool6(Lease::TYPE_PD,
+ IOAddress("2001:db8:a::"), 48, 64));
+ subnet->addPool(pdpool1);
+
+ Pool6Ptr pdpool2(new Pool6(Lease::TYPE_PD,
+ IOAddress("2001:db8:b::"), 48, 64));
+ subnet->addPool(pdpool2);
+ // Add several options to the subnet.
+ subnet->getCfgOption()->add(test_options_[0]->option_,
+ test_options_[0]->persistent_,
+ test_options_[0]->space_name_);
+
+ subnet->getCfgOption()->add(test_options_[1]->option_,
+ test_options_[1]->persistent_,
+ test_options_[1]->space_name_);
+
+ subnet->getCfgOption()->add(test_options_[2]->option_,
+ test_options_[2]->persistent_,
+ test_options_[2]->space_name_);
+
+ test_subnets_.push_back(subnet);
+
+ // Adding another subnet with the same subnet id to test
+ // cases that this second instance can override existing
+ // subnet instance.
+ subnet.reset(new Subnet6(IOAddress("2001:db8:1::"),
+ 48, 20, 30, 40, 50, 1024));
+
+ pool1.reset(new Pool6(Lease::TYPE_NA,
+ IOAddress("2001:db8:1::10"),
+ IOAddress("2001:db8:1::20")));
+ subnet->addPool(pool1);
+
+ pool1->getCfgOption()->add(test_options_[3]->option_,
+ test_options_[3]->persistent_,
+ test_options_[3]->space_name_);
+
+ pool1->getCfgOption()->add(test_options_[4]->option_,
+ test_options_[4]->persistent_,
+ test_options_[4]->space_name_);
+
+ pool2.reset(new Pool6(Lease::TYPE_NA,
+ IOAddress("2001:db8:1::50"),
+ IOAddress("2001:db8:1::60")));
+ subnet->addPool(pool2);
+
+ pool2->allowClientClass("work");
+ pool2->requireClientClass("required-class3");
+ pool2->requireClientClass("required-class4");
+ user_context = Element::createMap();
+ user_context->set("bar", Element::create("foo"));
+ pool2->setContext(user_context);
+ pdpool1.reset(new Pool6(IOAddress("2001:db8:c::"), 48, 64,
+ IOAddress("2001:db8:c::1"), 96));
+ subnet->addPool(pdpool1);
+
+ pdpool1->getCfgOption()->add(test_options_[3]->option_,
+ test_options_[3]->persistent_,
+ test_options_[3]->space_name_);
+
+ pdpool1->getCfgOption()->add(test_options_[4]->option_,
+ test_options_[4]->persistent_,
+ test_options_[4]->space_name_);
+
+ // Create the prefix delegation pool with an excluded prefix.
+ pdpool2.reset(new Pool6(IOAddress("2001:db8:d::"), 48, 64,
+ IOAddress("2001:db8:d::2000"), 120));
+
+ subnet->addPool(pdpool2);
+
+ pdpool2->allowClientClass("work");
+ pdpool2->requireClientClass("required-class3");
+ pdpool2->requireClientClass("required-class4");
+ user_context = Element::createMap();
+ user_context->set("bar", Element::create("foo"));
+ pdpool2->setContext(user_context);
+
+ test_subnets_.push_back(subnet);
+
+ subnet.reset(new Subnet6(IOAddress("2001:db8:3::"), 64, 20, 30, 40, 50, 2048));
+ Triplet<uint32_t> null_timer;
+ subnet->setPreferred(null_timer);
+ subnet->setT1(null_timer);
+ subnet->setT2(null_timer);
+ subnet->setValid(null_timer);
+ subnet->setPreferred(null_timer);
+ subnet->setDdnsSendUpdates(true);
+ subnet->setDdnsOverrideNoUpdate(true);
+ subnet->setDdnsOverrideClientUpdate(false);
+ subnet->setDdnsReplaceClientNameMode(D2ClientConfig::ReplaceClientNameMode::RCM_WHEN_PRESENT);
+ subnet->setDdnsGeneratedPrefix("myhost");
+ subnet->setDdnsQualifyingSuffix("example.org");
+
+ subnet->getCfgOption()->add(test_options_[0]->option_,
+ test_options_[0]->persistent_,
+ test_options_[0]->space_name_);
+
+ test_subnets_.push_back(subnet);
+
+ // Add a subnet with all defaults.
+ subnet.reset(new Subnet6(IOAddress("2001:db8:4::"), 64,
+ Triplet<uint32_t>(), Triplet<uint32_t>(),
+ Triplet<uint32_t>(), Triplet<uint32_t>(),
+ 4096));
+ test_subnets_.push_back(subnet);
+}
+
+void
+GenericConfigBackendDHCPv6Test::initTestSharedNetworks() {
+ ElementPtr user_context = Element::createMap();
+ user_context->set("foo", Element::create("bar"));
+
+ std::string interface_id_text = "fish";
+ OptionBuffer interface_id(interface_id_text.begin(), interface_id_text.end());
+ OptionPtr opt_interface_id(new Option(Option::V6, D6O_INTERFACE_ID, interface_id));
+
+ SharedNetwork6Ptr shared_network(new SharedNetwork6("level1"));
+ shared_network->allowClientClass("foo");
+ shared_network->setIface("eth1");
+ shared_network->setInterfaceId(opt_interface_id);
+ shared_network->setT2(323212);
+ shared_network->addRelayAddress(IOAddress("2001:db8:1::2"));
+ shared_network->addRelayAddress(IOAddress("2001:db8:3::4"));
+ shared_network->setT1(1234);
+ shared_network->requireClientClass("required-class1");
+ shared_network->requireClientClass("required-class2");
+ shared_network->setReservationsGlobal(false);
+ shared_network->setReservationsInSubnet(false);
+ shared_network->setContext(user_context);
+ shared_network->setValid(5555);
+ shared_network->setPreferred(4444);
+ shared_network->setCalculateTeeTimes(true);
+ shared_network->setT1Percent(0.345);
+ shared_network->setT2Percent(0.444);
+ shared_network->setDdnsSendUpdates(false);
+
+ // Add several options to the shared network.
+ shared_network->getCfgOption()->add(test_options_[2]->option_,
+ test_options_[2]->persistent_,
+ test_options_[2]->space_name_);
+
+ shared_network->getCfgOption()->add(test_options_[3]->option_,
+ test_options_[3]->persistent_,
+ test_options_[3]->space_name_);
+
+ shared_network->getCfgOption()->add(test_options_[4]->option_,
+ test_options_[4]->persistent_,
+ test_options_[4]->space_name_);
+
+ test_networks_.push_back(shared_network);
+
+ // Adding another shared network called "level1" to test
+ // cases that this second instance can override existing
+ // "level1" instance.
+ shared_network.reset(new SharedNetwork6("level1"));
+ test_networks_.push_back(shared_network);
+
+ // Add more shared networks.
+ shared_network.reset(new SharedNetwork6("level2"));
+ Triplet<uint32_t> null_timer;
+ shared_network->setPreferred(null_timer);
+ shared_network->setT1(null_timer);
+ shared_network->setT2(null_timer);
+ shared_network->setValid(null_timer);
+ shared_network->setPreferred(null_timer);
+ shared_network->setDdnsSendUpdates(true);
+ shared_network->setDdnsOverrideNoUpdate(true);
+ shared_network->setDdnsOverrideClientUpdate(false);
+ shared_network->setDdnsReplaceClientNameMode(D2ClientConfig::ReplaceClientNameMode::RCM_WHEN_PRESENT);
+ shared_network->setDdnsGeneratedPrefix("myhost");
+ shared_network->setDdnsQualifyingSuffix("example.org");
+
+ shared_network->getCfgOption()->add(test_options_[0]->option_,
+ test_options_[0]->persistent_,
+ test_options_[0]->space_name_);
+ test_networks_.push_back(shared_network);
+
+ shared_network.reset(new SharedNetwork6("level3"));
+ test_networks_.push_back(shared_network);
+}
+
+void
+GenericConfigBackendDHCPv6Test::initTestOptionDefs() {
+ ElementPtr user_context = Element::createMap();
+ user_context->set("foo", Element::create("bar"));
+
+ OptionDefinitionPtr option_def(new OptionDefinition("foo", 1234,
+ DHCP6_OPTION_SPACE,
+ "string",
+ "espace"));
+ test_option_defs_.push_back(option_def);
+
+ option_def.reset(new OptionDefinition("bar", 1234, DHCP6_OPTION_SPACE,
+ "uint32", true));
+ test_option_defs_.push_back(option_def);
+
+ option_def.reset(new OptionDefinition("fish", 5235, DHCP6_OPTION_SPACE,
+ "record", true));
+ option_def->addRecordField("uint32");
+ option_def->addRecordField("string");
+ test_option_defs_.push_back(option_def);
+
+ option_def.reset(new OptionDefinition("whale", 20236, "xyz", "string"));
+ test_option_defs_.push_back(option_def);
+
+ option_def.reset(new OptionDefinition("bar", 1234, DHCP6_OPTION_SPACE,
+ "uint64", true));
+ test_option_defs_.push_back(option_def);
+}
+
+void
+GenericConfigBackendDHCPv6Test::initTestOptions() {
+ ElementPtr user_context = Element::createMap();
+ user_context->set("foo", Element::create("bar"));
+
+ OptionDefSpaceContainer defs;
+
+ OptionDescriptor desc =
+ createOption<OptionString>(Option::V6, D6O_NEW_POSIX_TIMEZONE,
+ true, false, "my-timezone");
+ desc.space_name_ = DHCP6_OPTION_SPACE;
+ desc.setContext(user_context);
+ test_options_.push_back(OptionDescriptorPtr(new OptionDescriptor(desc)));
+
+ desc = createOption<OptionUint8>(Option::V6, D6O_PREFERENCE,
+ false, true, 64);
+ desc.space_name_ = DHCP6_OPTION_SPACE;
+ test_options_.push_back(OptionDescriptorPtr(new OptionDescriptor(desc)));
+
+ desc = createOption<OptionUint32>(Option::V6, 1, false, false, 312131),
+ desc.space_name_ = "vendor-encapsulated-options";
+ test_options_.push_back(OptionDescriptorPtr(new OptionDescriptor(desc)));
+
+ desc = createAddressOption<Option6AddrLst>(1254, true, true,
+ "2001:db8::3");
+ desc.space_name_ = DHCP6_OPTION_SPACE;
+ test_options_.push_back(OptionDescriptorPtr(new OptionDescriptor(desc)));
+
+ desc = createEmptyOption(Option::V6, 1, true);
+ desc.space_name_ = "isc";
+ test_options_.push_back(OptionDescriptorPtr(new OptionDescriptor(desc)));
+
+ desc = createAddressOption<Option6AddrLst>(2, false, true,
+ "2001:db8:1::5",
+ "2001:db8:1::3",
+ "2001:db8:3::4");
+ desc.space_name_ = "isc";
+ test_options_.push_back(OptionDescriptorPtr(new OptionDescriptor(desc)));
+
+ desc = createOption<OptionString>(Option::V6, D6O_NEW_POSIX_TIMEZONE,
+ true, false, "my-timezone-2");
+ desc.space_name_ = DHCP6_OPTION_SPACE;
+ desc.setContext(user_context);
+ test_options_.push_back(OptionDescriptorPtr(new OptionDescriptor(desc)));
+
+ desc = createOption<OptionString>(Option::V6, D6O_NEW_POSIX_TIMEZONE,
+ true, false, "my-timezone-3");
+ desc.space_name_ = DHCP6_OPTION_SPACE;
+ desc.setContext(user_context);
+ test_options_.push_back(OptionDescriptorPtr(new OptionDescriptor(desc)));
+
+ // Add definitions for DHCPv6 non-standard options in case we need to
+ // compare subnets, networks and pools in JSON format. In that case,
+ // the @c toElement functions require option definitions to generate the
+ // proper output.
+ defs.addItem(OptionDefinitionPtr(new OptionDefinition("vendor-encapsulated-1", 1,
+ "vendor-encapsulated-options",
+ "uint32")));
+ defs.addItem(OptionDefinitionPtr(new OptionDefinition("option-1254", 1254,
+ DHCP6_OPTION_SPACE,
+ "ipv6-address", true)));
+ defs.addItem(OptionDefinitionPtr(new OptionDefinition("isc-1", 1, "isc", "empty")));
+ defs.addItem(OptionDefinitionPtr(new OptionDefinition("isc-2", 2, "isc",
+ "ipv6-address", true)));
+
+ // Register option definitions.
+ LibDHCP::setRuntimeOptionDefs(defs);
+}
+
+void
+GenericConfigBackendDHCPv6Test::initTestClientClasses() {
+ ExpressionPtr match_expr = boost::make_shared<Expression>();
+ CfgOptionPtr cfg_option = boost::make_shared<CfgOption>();
+ auto class1 = boost::make_shared<ClientClassDef>("foo", match_expr, cfg_option);
+ class1->setCfgOptionDef(boost::make_shared<CfgOptionDef>());
+ class1->setRequired(true);
+ class1->setValid(Triplet<uint32_t>(30, 60, 90));
+ class1->setPreferred(Triplet<uint32_t>(25, 55, 85));
+ test_client_classes_.push_back(class1);
+ ElementPtr user_context = Element::createMap();
+ user_context->set("melon", Element::create("water"));
+ class1->setContext(user_context);
+
+ auto class2 = boost::make_shared<ClientClassDef>("bar", match_expr, cfg_option);
+ class2->setCfgOptionDef(boost::make_shared<CfgOptionDef>());
+ class2->setTest("member('foo')");
+ test_client_classes_.push_back(class2);
+
+ auto class3 = boost::make_shared<ClientClassDef>("foobar", match_expr, cfg_option);
+ class3->setCfgOptionDef(boost::make_shared<CfgOptionDef>());
+ class3->setTest("member('foo') and member('bar')");
+ test_client_classes_.push_back(class3);
+}
+
+void
+GenericConfigBackendDHCPv6Test::getTypeTest(const std::string& expected_type) {
+ DatabaseConnection::ParameterMap params;
+ params["name"] = "keatest";
+ params["password"] = "keatest";
+ params["user"] = "keatest";
+ ASSERT_NO_THROW_LOG(cbptr_ = backendFactory(params));
+ ASSERT_NE(cbptr_->getParameters(), DatabaseConnection::ParameterMap());
+ EXPECT_EQ(expected_type, cbptr_->getType());
+}
+
+void
+GenericConfigBackendDHCPv6Test::getHostTest() {
+ DatabaseConnection::ParameterMap params;
+ params["name"] = "keatest";
+ params["password"] = "keatest";
+ params["user"] = "keatest";
+ ASSERT_NO_THROW_LOG(cbptr_ = backendFactory(params));
+ ASSERT_NE(cbptr_->getParameters(), DatabaseConnection::ParameterMap());
+ EXPECT_EQ("localhost", cbptr_->getHost());
+}
+
+void
+GenericConfigBackendDHCPv6Test::getPortTest() {
+ DatabaseConnection::ParameterMap params;
+ params["name"] = "keatest";
+ params["password"] = "keatest";
+ params["user"] = "keatest";
+ ASSERT_NO_THROW_LOG(cbptr_ = backendFactory(params));
+ ASSERT_NE(cbptr_->getParameters(), DatabaseConnection::ParameterMap());
+ EXPECT_EQ(0, cbptr_->getPort());
+}
+
+void
+GenericConfigBackendDHCPv6Test::createUpdateDeleteServerTest() {
+ // Explicitly set modification time to make sure that the time
+ // returned from the database is correct.
+ test_servers_[0]->setModificationTime(timestamps_["yesterday"]);
+ test_servers_[1]->setModificationTime(timestamps_["today"]);
+
+ // Insert the server1 into the database.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[0]));
+
+ {
+ SCOPED_TRACE("CREATE audit entry for server");
+ testNewAuditEntry("dhcp6_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set");
+ }
+
+ // It should not be possible to create a duplicate of the logical
+ // server 'all'.
+ auto all_server = Server::create(ServerTag("all"), "this is logical server all");
+ ASSERT_THROW(cbptr_->createUpdateServer6(all_server), isc::InvalidOperation);
+
+ ServerPtr returned_server;
+
+ // An attempt to fetch the server that hasn't been inserted should return
+ // a null pointer.
+ ASSERT_NO_THROW_LOG(returned_server = cbptr_->getServer6(ServerTag("server2")));
+ EXPECT_FALSE(returned_server);
+
+ // Try to fetch the server which we expect to exist.
+ ASSERT_NO_THROW_LOG(returned_server = cbptr_->getServer6(ServerTag("server1")));
+ ASSERT_TRUE(returned_server);
+ EXPECT_EQ("server1", returned_server->getServerTagAsText());
+ EXPECT_EQ("this is server 1", returned_server->getDescription());
+ EXPECT_EQ(timestamps_["yesterday"], returned_server->getModificationTime());
+
+ // This call is expected to update the existing server.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[1]));
+
+ {
+ SCOPED_TRACE("UPDATE audit entry for server");
+ testNewAuditEntry("dhcp6_server",
+ AuditEntry::ModificationType::UPDATE,
+ "server set");
+ }
+
+ // Verify that the server has been updated.
+ ASSERT_NO_THROW_LOG(returned_server = cbptr_->getServer6(ServerTag("server1")));
+ ASSERT_TRUE(returned_server);
+ EXPECT_EQ("server1", returned_server->getServerTag().get());
+ EXPECT_EQ("this is server 1 bis", returned_server->getDescription());
+ EXPECT_EQ(timestamps_["today"], returned_server->getModificationTime());
+
+ uint64_t servers_deleted = 0;
+
+ // Try to delete non-existing server.
+ ASSERT_NO_THROW_LOG(servers_deleted = cbptr_->deleteServer6(ServerTag("server2")));
+ EXPECT_EQ(0, servers_deleted);
+
+ // Make sure that the server1 wasn't deleted.
+ ASSERT_NO_THROW_LOG(returned_server = cbptr_->getServer6(ServerTag("server1")));
+ EXPECT_TRUE(returned_server);
+
+ // Deleting logical server 'all' is not allowed.
+ ASSERT_THROW(cbptr_->deleteServer6(ServerTag()), isc::InvalidOperation);
+
+ // Delete the existing server.
+ ASSERT_NO_THROW_LOG(servers_deleted = cbptr_->deleteServer6(ServerTag("server1")));
+ EXPECT_EQ(1, servers_deleted);
+
+ {
+ SCOPED_TRACE("DELETE audit entry for server");
+ testNewAuditEntry("dhcp6_server",
+ AuditEntry::ModificationType::DELETE,
+ "deleting a server");
+ }
+
+ // Make sure that the server is gone.
+ ASSERT_NO_THROW_LOG(returned_server = cbptr_->getServer6(ServerTag("server1")));
+ EXPECT_FALSE(returned_server);
+}
+
+void
+GenericConfigBackendDHCPv6Test::getAndDeleteAllServersTest() {
+ for (auto i = 1; i < test_servers_.size(); ++i) {
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[i]));
+ }
+
+ ServerCollection servers;
+ ASSERT_NO_THROW_LOG(servers = cbptr_->getAllServers6());
+ ASSERT_EQ(test_servers_.size() - 1, servers.size());
+
+ // All servers should have been returned.
+ EXPECT_TRUE(ServerFetcher::get(servers, ServerTag("server1")));
+ EXPECT_TRUE(ServerFetcher::get(servers, ServerTag("server2")));
+ EXPECT_TRUE(ServerFetcher::get(servers, ServerTag("server3")));
+
+ // The logical server all should not be returned. We merely return the
+ // user configured servers.
+ EXPECT_FALSE(ServerFetcher::get(servers, ServerTag()));
+
+ // Delete all servers and make sure they are gone.
+ uint64_t deleted_servers = 0;
+ ASSERT_NO_THROW_LOG(deleted_servers = cbptr_->deleteAllServers6());
+
+ ASSERT_NO_THROW_LOG(servers = cbptr_->getAllServers6());
+ EXPECT_TRUE(servers.empty());
+
+ // All servers should be gone.
+ EXPECT_FALSE(ServerFetcher::get(servers, ServerTag("server1")));
+ EXPECT_FALSE(ServerFetcher::get(servers, ServerTag("server2")));
+ EXPECT_FALSE(ServerFetcher::get(servers, ServerTag("server3")));
+
+ // The number of deleted server should be equal to the number of
+ // inserted servers. The logical 'all' server should be excluded.
+ EXPECT_EQ(test_servers_.size() - 1, deleted_servers);
+
+ EXPECT_EQ(1, countRows("dhcp6_server"));
+}
+
+void
+GenericConfigBackendDHCPv6Test::createUpdateDeleteGlobalParameter6Test() {
+ StampedValuePtr global_parameter = StampedValue::create("global", "whale");
+
+ // Explicitly set modification time to make sure that the time
+ // returned from the database is correct.
+ global_parameter->setModificationTime(timestamps_["yesterday"]);
+ cbptr_->createUpdateGlobalParameter6(ServerSelector::ALL(),
+ global_parameter);
+
+ {
+ SCOPED_TRACE("CREATE audit entry for global parameter");
+ testNewAuditEntry("dhcp6_global_parameter",
+ AuditEntry::ModificationType::CREATE,
+ "global parameter set");
+ }
+
+ // Verify returned parameter and the modification time.
+ StampedValuePtr returned_global_parameter =
+ cbptr_->getGlobalParameter6(ServerSelector::ALL(), "global");
+ ASSERT_TRUE(returned_global_parameter);
+ EXPECT_EQ("global", returned_global_parameter->getName());
+ EXPECT_EQ("whale", returned_global_parameter->getValue());
+ EXPECT_TRUE(returned_global_parameter->getModificationTime() ==
+ global_parameter->getModificationTime());
+ ASSERT_EQ(1, returned_global_parameter->getServerTags().size());
+ EXPECT_EQ("all", returned_global_parameter->getServerTags().begin()->get());
+
+ // Because we have added the global parameter for all servers, it
+ // should be also returned for the explicitly specified server.
+ returned_global_parameter = cbptr_->getGlobalParameter6(ServerSelector::ONE("server1"),
+ "global");
+ ASSERT_TRUE(returned_global_parameter);
+ EXPECT_EQ("global", returned_global_parameter->getName());
+ EXPECT_EQ("whale", returned_global_parameter->getValue());
+ EXPECT_TRUE(returned_global_parameter->getModificationTime() ==
+ global_parameter->getModificationTime());
+ ASSERT_EQ(1, returned_global_parameter->getServerTags().size());
+ EXPECT_EQ("all", returned_global_parameter->getServerTags().begin()->get());
+
+ // Check that the parameter is updated when selector is specified correctly.
+ global_parameter = StampedValue::create("global", "fish");
+ cbptr_->createUpdateGlobalParameter6(ServerSelector::ALL(),
+ global_parameter);
+ returned_global_parameter = cbptr_->getGlobalParameter6(ServerSelector::ALL(),
+ "global");
+ ASSERT_TRUE(returned_global_parameter);
+ EXPECT_EQ("global", returned_global_parameter->getName());
+ EXPECT_EQ("fish", returned_global_parameter->getValue());
+ EXPECT_TRUE(returned_global_parameter->getModificationTime() ==
+ global_parameter->getModificationTime());
+ ASSERT_EQ(1, returned_global_parameter->getServerTags().size());
+ EXPECT_EQ("all", returned_global_parameter->getServerTags().begin()->get());
+
+ {
+ SCOPED_TRACE("UPDATE audit entry for the global parameter");
+ testNewAuditEntry("dhcp6_global_parameter",
+ AuditEntry::ModificationType::UPDATE,
+ "global parameter set");
+ }
+
+ // Should not delete parameter specified for all servers if explicit
+ // server name is provided.
+ EXPECT_EQ(0, cbptr_->deleteGlobalParameter6(ServerSelector::ONE("server1"),
+ "global"));
+
+ // Delete parameter and make sure it is gone.
+ cbptr_->deleteGlobalParameter6(ServerSelector::ALL(), "global");
+ returned_global_parameter = cbptr_->getGlobalParameter6(ServerSelector::ALL(),
+ "global");
+ EXPECT_FALSE(returned_global_parameter);
+
+ {
+ SCOPED_TRACE("DELETE audit entry for the global parameter");
+ testNewAuditEntry("dhcp6_global_parameter",
+ AuditEntry::ModificationType::DELETE,
+ "global parameter deleted");
+ }
+}
+
+void
+GenericConfigBackendDHCPv6Test::globalParameters6WithServerTagsTest() {
+ // Create three global parameters having the same name.
+ StampedValuePtr global_parameter1 = StampedValue::create("global", "value1");
+ StampedValuePtr global_parameter2 = StampedValue::create("global", "value2");
+ StampedValuePtr global_parameter3 = StampedValue::create("global", "value3");
+
+ // Try to insert one of them and associate with non-existing server.
+ // This should fail because the server must be inserted first.
+ ASSERT_THROW(cbptr_->createUpdateGlobalParameter6(ServerSelector::ONE("server1"),
+ global_parameter1),
+ NullKeyError);
+
+ // Create two servers.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[1]));
+ {
+ SCOPED_TRACE("server1 is created");
+ testNewAuditEntry("dhcp6_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set");
+ }
+
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[2]));
+ {
+ SCOPED_TRACE("server2 is created");
+ testNewAuditEntry("dhcp6_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set");
+ }
+
+ // This time inserting the global parameters for the server1 and server2 should
+ // be successful.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateGlobalParameter6(ServerSelector::ONE("server1"),
+ global_parameter1));
+ {
+ SCOPED_TRACE("Global parameter for server1 is set");
+ // The value of 3 means there should be 3 audit entries available for the
+ // server1, two that indicate creation of the servers and one that we
+ // validate, which sets the global value.
+ testNewAuditEntry("dhcp6_global_parameter",
+ AuditEntry::ModificationType::CREATE,
+ "global parameter set",
+ ServerSelector::ONE("server1"),
+ 3, 1);
+ }
+
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateGlobalParameter6(ServerSelector::ONE("server2"),
+ global_parameter2));
+ {
+ SCOPED_TRACE("Global parameter for server2 is set");
+ // Same as in case of the server2, there should be 3 audit entries of
+ // which one we validate.
+ testNewAuditEntry("dhcp6_global_parameter",
+ AuditEntry::ModificationType::CREATE,
+ "global parameter set",
+ ServerSelector::ONE("server2"),
+ 3, 1);
+ }
+
+ // The last parameter is associated with all servers.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateGlobalParameter6(ServerSelector::ALL(),
+ global_parameter3));
+ {
+ SCOPED_TRACE("Global parameter for all servers is set");
+ // There should be one new audit entry for all servers. It indicates
+ // the insertion of the global value.
+ testNewAuditEntry("dhcp6_global_parameter",
+ AuditEntry::ModificationType::CREATE,
+ "global parameter set",
+ ServerSelector::ALL(),
+ 1, 1);
+ }
+
+ StampedValuePtr returned_global;
+
+ // Try to fetch the value specified for all servers.
+ ASSERT_NO_THROW_LOG(
+ returned_global = cbptr_->getGlobalParameter6(ServerSelector::ALL(),
+ "global")
+ );
+ ASSERT_TRUE(returned_global);
+ EXPECT_EQ(global_parameter3->getValue(), returned_global->getValue());
+ ASSERT_EQ(1, returned_global->getServerTags().size());
+ EXPECT_EQ("all", returned_global->getServerTags().begin()->get());
+
+ // Try to fetch the value specified for the server1. This should override the
+ // value specified for all servers.
+ ASSERT_NO_THROW_LOG(
+ returned_global = cbptr_->getGlobalParameter6(ServerSelector::ONE("server1"),
+ "global")
+ );
+ ASSERT_TRUE(returned_global);
+ EXPECT_EQ(global_parameter1->getValue(), returned_global->getValue());
+
+ ASSERT_EQ(1, returned_global->getServerTags().size());
+ EXPECT_EQ("server1", returned_global->getServerTags().begin()->get());
+
+ // The same in case of the server2.
+ ASSERT_NO_THROW_LOG(
+ returned_global = cbptr_->getGlobalParameter6(ServerSelector::ONE("server2"),
+ "global")
+ );
+ ASSERT_TRUE(returned_global);
+ EXPECT_EQ(global_parameter2->getValue(), returned_global->getValue());
+ ASSERT_EQ(1, returned_global->getServerTags().size());
+ EXPECT_EQ("server2", returned_global->getServerTags().begin()->get());
+
+ StampedValueCollection returned_globals;
+
+ // Try to fetch the collection of globals for the server1, server2 and server3.
+ // The server3 does not have an explicit value so for this server we should get
+ /// the value for 'all'.
+ ASSERT_NO_THROW_LOG(
+ returned_globals = cbptr_->getAllGlobalParameters6(ServerSelector::
+ MULTIPLE({ "server1", "server2",
+ "server3" }));
+ );
+ ASSERT_EQ(3, returned_globals.size());
+
+ // Capture the returned values into the map so as we can check the
+ // values against the servers.
+ std::map<std::string, std::string> values;
+ for (auto g = returned_globals.begin(); g != returned_globals.end(); ++g) {
+ ASSERT_EQ(1, (*g)->getServerTags().size());
+ values[(*g)->getServerTags().begin()->get()] = ((*g)->getValue());
+ }
+
+ ASSERT_EQ(3, values.size());
+ EXPECT_EQ(global_parameter1->getValue(), values["server1"]);
+ EXPECT_EQ(global_parameter2->getValue(), values["server2"]);
+ EXPECT_EQ(global_parameter3->getValue(), values["all"]);
+
+ // Try to fetch the collection of global parameters specified for all servers.
+ // This excludes the values specific to server1 and server2. It returns only the
+ // common ones.
+ ASSERT_NO_THROW_LOG(
+ returned_globals = cbptr_->getAllGlobalParameters6(ServerSelector::ALL())
+ );
+ ASSERT_EQ(1, returned_globals.size());
+ returned_global = *returned_globals.begin();
+ EXPECT_EQ(global_parameter3->getValue(), returned_global->getValue());
+ ASSERT_EQ(1, returned_global->getServerTags().size());
+ EXPECT_EQ("all", returned_global->getServerTags().begin()->get());
+
+ // Delete the server1. It should remove associations of this server with the
+ // global parameter and the global parameter itself.
+ ASSERT_NO_THROW_LOG(cbptr_->deleteServer6(ServerTag("server1")));
+ ASSERT_NO_THROW_LOG(
+ returned_globals = cbptr_->getAllGlobalParameters6(ServerSelector::ONE("server1"))
+ );
+ ASSERT_EQ(1, returned_globals.size());
+ returned_global = *returned_globals.begin();
+ // As a result, the value fetched for the server1 should be the one available for
+ // all servers, rather than the one dedicated for server1. The association of
+ // the server1 specific value with the server1 should be gone.
+ EXPECT_EQ(global_parameter3->getValue(), returned_global->getValue());
+ ASSERT_EQ(1, returned_global->getServerTags().size());
+ EXPECT_EQ("all", returned_global->getServerTags().begin()->get());
+
+ {
+ SCOPED_TRACE("DELETE audit entry for the global parameter after server deletion");
+ // We expect two new audit entries for the server1, one indicating that the
+ // server has been deleted and another one indicating that the corresponding
+ // global value has been deleted. We check the latter entry.
+ testNewAuditEntry("dhcp6_global_parameter",
+ AuditEntry::ModificationType::DELETE,
+ "deleting a server", ServerSelector::ONE("server1"),
+ 2, 1);
+ }
+
+ // Attempt to delete global parameter for server1.
+ uint64_t deleted_num = 0;
+ ASSERT_NO_THROW_LOG(deleted_num = cbptr_->deleteGlobalParameter6(ServerSelector::ONE("server1"),
+ "global"));
+ // No parameters should be deleted. In particular, the parameter for the logical
+ // server 'all' should not be deleted.
+ EXPECT_EQ(0, deleted_num);
+
+ // Deleting the existing value for server2 should succeed.
+ ASSERT_NO_THROW_LOG(deleted_num = cbptr_->deleteGlobalParameter6(ServerSelector::ONE("server2"),
+ "global"));
+ EXPECT_EQ(1, deleted_num);
+
+ // Create it again to test that deletion of all server removes this too.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateGlobalParameter6(ServerSelector::ONE("server2"),
+ global_parameter2));
+
+ // Delete all servers, except 'all'.
+ ASSERT_NO_THROW_LOG(deleted_num = cbptr_->deleteAllServers6());
+ ASSERT_NO_THROW_LOG(
+ returned_globals = cbptr_->getAllGlobalParameters6(ServerSelector::ALL())
+ );
+ EXPECT_EQ(1, deleted_num);
+ ASSERT_EQ(1, returned_globals.size());
+ returned_global = *returned_globals.begin();
+ // The common value for all servers should still be available because 'all'
+ // logical server should not be deleted.
+ EXPECT_EQ(global_parameter3->getValue(), returned_global->getValue());
+ ASSERT_EQ(1, returned_global->getServerTags().size());
+ EXPECT_EQ("all", returned_global->getServerTags().begin()->get());
+
+ {
+ SCOPED_TRACE("DELETE audit entry for the global parameter after deletion of"
+ " all servers");
+ // There should be 4 new audit entries. One for deleting the global, one for
+ // re-creating it, one for deleting the server2 and one for deleting the
+ // global again as a result of deleting the server2.
+ testNewAuditEntry("dhcp6_global_parameter",
+ AuditEntry::ModificationType::DELETE,
+ "deleting all servers", ServerSelector::ONE("server2"),
+ 4, 1);
+ }
+}
+
+void
+GenericConfigBackendDHCPv6Test::getAllGlobalParameters6Test() {
+ // Create 3 parameters and put them into the database.
+ cbptr_->createUpdateGlobalParameter6(ServerSelector::ALL(),
+ StampedValue::create("name1", "value1"));
+ cbptr_->createUpdateGlobalParameter6(ServerSelector::ALL(),
+ StampedValue::create("name2", Element::create(static_cast<int64_t>(65))));
+ cbptr_->createUpdateGlobalParameter6(ServerSelector::ALL(),
+ StampedValue::create("name3", "value3"));
+ cbptr_->createUpdateGlobalParameter6(ServerSelector::ALL(),
+ StampedValue::create("name4", Element::create(static_cast<bool>(true))));
+ cbptr_->createUpdateGlobalParameter6(ServerSelector::ALL(),
+ StampedValue::create("name5", Element::create(static_cast<double>(1.65))));
+
+ // Fetch all parameters.
+ auto parameters = cbptr_->getAllGlobalParameters6(ServerSelector::ALL());
+ ASSERT_EQ(5, parameters.size());
+
+ const auto& parameters_index = parameters.get<StampedValueNameIndexTag>();
+
+ // Verify their values.
+ EXPECT_EQ("value1", (*parameters_index.find("name1"))->getValue());
+ EXPECT_EQ(65, (*parameters_index.find("name2"))->getIntegerValue());
+ EXPECT_EQ("value3", (*parameters_index.find("name3"))->getValue());
+ EXPECT_TRUE((*parameters_index.find("name4"))->getBoolValue());
+ EXPECT_EQ(1.65, (*parameters_index.find("name5"))->getDoubleValue());
+
+ for (auto param = parameters_index.begin(); param != parameters_index.end();
+ ++param) {
+ ASSERT_EQ(1, (*param)->getServerTags().size());
+ EXPECT_EQ("all", (*param)->getServerTags().begin()->get());
+ }
+
+ // Should be able to fetch these parameters when explicitly providing
+ // the server tag.
+ parameters = cbptr_->getAllGlobalParameters6(ServerSelector::ONE("server1"));
+ EXPECT_EQ(5, parameters.size());
+
+ // Deleting global parameters with non-matching server selector
+ // should fail.
+ EXPECT_EQ(0, cbptr_->deleteAllGlobalParameters6(ServerSelector::ONE("server1")));
+
+ // Delete all parameters and make sure they are gone.
+ EXPECT_EQ(5, cbptr_->deleteAllGlobalParameters6(ServerSelector::ALL()));
+ parameters = cbptr_->getAllGlobalParameters6(ServerSelector::ALL());
+ EXPECT_TRUE(parameters.empty());
+}
+
+void
+GenericConfigBackendDHCPv6Test::getModifiedGlobalParameters6Test() {
+ // Create 3 global parameters and assign modification times:
+ // "yesterday", "today" and "tomorrow" respectively.
+ StampedValuePtr value = StampedValue::create("name1", "value1");
+ value->setModificationTime(timestamps_["yesterday"]);
+ cbptr_->createUpdateGlobalParameter6(ServerSelector::ALL(),
+ value);
+
+ value = StampedValue::create("name2", Element::create(static_cast<int64_t>(65)));
+ value->setModificationTime(timestamps_["today"]);
+ cbptr_->createUpdateGlobalParameter6(ServerSelector::ALL(),
+ value);
+
+ value = StampedValue::create("name3", "value3");
+ value->setModificationTime(timestamps_["tomorrow"]);
+ cbptr_->createUpdateGlobalParameter6(ServerSelector::ALL(),
+ value);
+
+ // Get parameters modified after "today".
+ auto parameters = cbptr_->getModifiedGlobalParameters6(ServerSelector::ALL(),
+ timestamps_["after today"]);
+
+ const auto& parameters_index = parameters.get<StampedValueNameIndexTag>();
+
+ // It should be the one modified "tomorrow".
+ ASSERT_EQ(1, parameters_index.size());
+
+ auto parameter = parameters_index.find("name3");
+ ASSERT_FALSE(parameter == parameters_index.end());
+
+ ASSERT_TRUE(*parameter);
+ EXPECT_EQ("value3", (*parameter)->getValue());
+
+ // Should be able to fetct these parameters when explicitly providing
+ // the server tag.
+ parameters = cbptr_->getModifiedGlobalParameters6(ServerSelector::ONE("server1"),
+ timestamps_["after today"]);
+ EXPECT_EQ(1, parameters.size());
+}
+
+void
+GenericConfigBackendDHCPv6Test::nullKeyErrorTest() {
+ // Create a global parameter (it should work with any object type).
+ StampedValuePtr global_parameter = StampedValue::create("global", "value");
+
+ // Try to insert it and associate with non-existing server.
+ std::string msg;
+ try {
+ cbptr_->createUpdateGlobalParameter6(ServerSelector::ONE("server1"),
+ global_parameter);
+ msg = "got no exception";
+ } catch (const NullKeyError& ex) {
+ msg = ex.what();
+ } catch (const std::exception&) {
+ msg = "got another exception";
+ }
+ EXPECT_EQ("server 'server1' does not exist", msg);
+}
+
+void
+GenericConfigBackendDHCPv6Test::createUpdateSubnet6SelectorsTest() {
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[0]));
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[2]));
+
+ // Supported selectors.
+ Subnet6Ptr subnet = test_subnets_[0];
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet6(ServerSelector::ALL(),
+ subnet));
+ subnet = test_subnets_[2];
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet6(ServerSelector::ONE("server1"),
+ subnet));
+ subnet = test_subnets_[3];
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet6(ServerSelector::MULTIPLE({ "server1", "server2" }),
+ subnet));
+
+ // Not supported server selectors.
+ ASSERT_THROW(cbptr_->createUpdateSubnet6(ServerSelector::ANY(), subnet),
+ isc::InvalidOperation);
+
+ // Not implemented server selectors.
+ ASSERT_THROW(cbptr_->createUpdateSubnet6(ServerSelector::UNASSIGNED(),
+ subnet),
+ isc::NotImplemented);
+}
+
+void
+GenericConfigBackendDHCPv6Test::getSubnet6Test() {
+ // Insert the server2 into the database.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[2]));
+ {
+ SCOPED_TRACE("CREATE audit entry for server");
+ testNewAuditEntry("dhcp6_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set");
+ }
+
+ auto subnet = test_subnets_[0];
+ auto subnet2 = test_subnets_[2];
+
+ // An attempt to add a subnet to a non-existing server (server1) should fail.
+ ASSERT_THROW(cbptr_->createUpdateSubnet6(ServerSelector::MULTIPLE({ "server1", "server2" }),
+ subnet2),
+ NullKeyError);
+
+ // The subnet shouldn't have been added, even though one of the servers exists.
+ Subnet6Ptr returned_subnet;
+ ASSERT_NO_THROW_LOG(returned_subnet = cbptr_->getSubnet6(ServerSelector::ONE("server2"),
+ subnet2->getID()));
+ EXPECT_FALSE(returned_subnet);
+
+ // Insert two subnets, one for all servers and one for server2.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet6(ServerSelector::ALL(), subnet));
+ {
+ SCOPED_TRACE("A. CREATE audit entry for the subnet");
+ testNewAuditEntry("dhcp6_subnet",
+ AuditEntry::ModificationType::CREATE,
+ "subnet set");
+ }
+
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet6(ServerSelector::ONE("server2"), subnet2));
+ {
+ SCOPED_TRACE("B. CREATE audit entry for the subnet");
+ testNewAuditEntry("dhcp6_subnet",
+ AuditEntry::ModificationType::CREATE,
+ "subnet set", ServerSelector::ONE("subnet2"),
+ 2, 1);
+ }
+
+ // We are not going to support selection of a single entry for multiple servers.
+ ASSERT_THROW(cbptr_->getSubnet6(ServerSelector::MULTIPLE({ "server1", "server2" }),
+ subnet->getID()),
+ isc::InvalidOperation);
+
+ ASSERT_THROW(cbptr_->getSubnet6(ServerSelector::MULTIPLE({ "server1", "server2" }),
+ subnet->toText()),
+ isc::InvalidOperation);
+
+ // Test that this subnet will be fetched for various server selectors.
+ auto test_get_subnet = [this, &subnet] (const std::string& test_case_name,
+ const ServerSelector& server_selector,
+ const std::string& expected_tag = ServerTag::ALL) {
+ SCOPED_TRACE(test_case_name);
+
+ // Test fetching subnet by id.
+ Subnet6Ptr returned_subnet;
+ ASSERT_NO_THROW_LOG(returned_subnet = cbptr_->getSubnet6(server_selector, subnet->getID()));
+ ASSERT_TRUE(returned_subnet);
+
+ ASSERT_EQ(1, returned_subnet->getServerTags().size());
+ EXPECT_TRUE(returned_subnet->hasServerTag(ServerTag(expected_tag)));
+
+ ASSERT_EQ(subnet->toElement()->str(), returned_subnet->toElement()->str());
+
+ // Test fetching subnet by prefix.
+ ASSERT_NO_THROW_LOG(returned_subnet = cbptr_->getSubnet6(server_selector,
+ subnet->toText()));
+ ASSERT_TRUE(returned_subnet);
+
+ ASSERT_EQ(1, returned_subnet->getServerTags().size());
+ EXPECT_TRUE(returned_subnet->hasServerTag(ServerTag(expected_tag)));
+
+ EXPECT_EQ(subnet->toElement()->str(), returned_subnet->toElement()->str());
+ };
+
+ {
+ SCOPED_TRACE("testing various server selectors before update");
+ test_get_subnet("all servers", ServerSelector::ALL());
+ test_get_subnet("one server", ServerSelector::ONE("server1"));
+ test_get_subnet("any server", ServerSelector::ANY());
+ }
+
+ subnet = subnet2;
+ {
+ SCOPED_TRACE("testing server selectors for another server");
+ test_get_subnet("one server", ServerSelector::ONE("server2"), "server2");
+ test_get_subnet("any server", ServerSelector::ANY(), "server2");
+ }
+
+ // Update the subnet in the database (both use the same ID).
+ subnet = test_subnets_[1];
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet6(ServerSelector::ALL(), subnet));
+ {
+ SCOPED_TRACE("C. CREATE audit entry for the subnet");
+ testNewAuditEntry("dhcp6_subnet",
+ AuditEntry::ModificationType::UPDATE,
+ "subnet set");
+ }
+
+ {
+ SCOPED_TRACE("testing various server selectors after update");
+ test_get_subnet("all servers", ServerSelector::ALL());
+ test_get_subnet("one server", ServerSelector::ONE("server1"));
+ test_get_subnet("any server", ServerSelector::ANY());
+ }
+
+ // The server2 specific subnet should not be returned if the server selector
+ // is not matching.
+ EXPECT_FALSE(cbptr_->getSubnet6(ServerSelector::ALL(), subnet2->getID()));
+ EXPECT_FALSE(cbptr_->getSubnet6(ServerSelector::ALL(), subnet2->toText()));
+ EXPECT_FALSE(cbptr_->getSubnet6(ServerSelector::ONE("server1"), subnet2->getID()));
+ EXPECT_FALSE(cbptr_->getSubnet6(ServerSelector::ONE("server1"), subnet2->toText()));
+
+ // Update the subnet in the database (both use the same prefix).
+ subnet2.reset(new Subnet6(IOAddress("2001:db8:3::"),
+ 64, 30, 40, 50, 80, 8192));
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet6(ServerSelector::ONE("server2"), subnet2));
+
+ // Fetch again and verify.
+ returned_subnet = cbptr_->getSubnet6(ServerSelector::ONE("server2"), subnet2->toText());
+ ASSERT_TRUE(returned_subnet);
+ EXPECT_EQ(subnet2->toElement()->str(), returned_subnet->toElement()->str());
+
+ // Update the subnet when it conflicts same id and same prefix both
+ // with different subnets. This should throw.
+ // Subnets are 2001:db8:1::/48 id 1024 and 2001:db8:3::/64 id 8192
+ subnet2.reset(new Subnet6(IOAddress("2001:db8:1::"),
+ 48, 30, 40, 50, 80, 8192));
+ ASSERT_THROW(cbptr_->createUpdateSubnet6(ServerSelector::ONE("server2"), subnet2),
+ DuplicateEntry);
+}
+
+void
+GenericConfigBackendDHCPv6Test::getSubnet6byIdSelectorsTest() {
+ // Supported selectors.
+ ASSERT_NO_THROW_LOG(cbptr_->getSubnet6(ServerSelector::ANY(), SubnetID(1)));
+ ASSERT_NO_THROW_LOG(cbptr_->getSubnet6(ServerSelector::UNASSIGNED(), SubnetID(1)));
+ ASSERT_NO_THROW_LOG(cbptr_->getSubnet6(ServerSelector::ALL(), SubnetID(1)));
+ ASSERT_NO_THROW_LOG(cbptr_->getSubnet6(ServerSelector::ONE("server1"), SubnetID(1)));
+
+ // Not supported selectors.
+ ASSERT_THROW(cbptr_->getSubnet6(ServerSelector::MULTIPLE({ "server1", "server2" }),
+ SubnetID(1)),
+ isc::InvalidOperation);
+}
+
+void
+GenericConfigBackendDHCPv6Test::getSubnet6WithOptionalUnspecifiedTest() {
+ // Create a subnet and wrap it within a shared network. It is important
+ // to have the shared network to verify that the subnet doesn't inherit
+ // the values of the shared network but stores the NULL values in the
+ // for those parameters that are unspecified on the subnet level.
+ Subnet6Ptr subnet = test_subnets_[2];
+ SharedNetwork6Ptr shared_network = test_networks_[0];
+ shared_network->add(subnet);
+
+ // Need to add the shared network to the database because otherwise
+ // the subnet foreign key would fail.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork6(ServerSelector::ALL(), shared_network));
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet6(ServerSelector::ALL(), subnet));
+
+ // Fetch this subnet by subnet identifier.
+ Subnet6Ptr returned_subnet;
+ ASSERT_NO_THROW_LOG(returned_subnet = cbptr_->getSubnet6(ServerSelector::ALL(),
+ subnet->getID()));
+ ASSERT_TRUE(returned_subnet);
+
+ EXPECT_TRUE(returned_subnet->getIface().unspecified());
+ EXPECT_TRUE(returned_subnet->getIface().empty());
+
+ EXPECT_TRUE(returned_subnet->getClientClass().unspecified());
+ EXPECT_TRUE(returned_subnet->getClientClass().empty());
+
+ EXPECT_TRUE(returned_subnet->getValid().unspecified());
+ EXPECT_EQ(0, returned_subnet->getValid().get());
+
+ EXPECT_TRUE(returned_subnet->getPreferred().unspecified());
+ EXPECT_EQ(0, returned_subnet->getPreferred().get());
+
+ EXPECT_TRUE(returned_subnet->getT1().unspecified());
+ EXPECT_EQ(0, returned_subnet->getT1().get());
+
+ EXPECT_TRUE(returned_subnet->getT2().unspecified());
+ EXPECT_EQ(0, returned_subnet->getT2().get());
+
+ EXPECT_TRUE(returned_subnet->getReservationsGlobal().unspecified());
+ EXPECT_FALSE(returned_subnet->getReservationsGlobal().get());
+
+ EXPECT_TRUE(returned_subnet->getReservationsInSubnet().unspecified());
+ EXPECT_TRUE(returned_subnet->getReservationsInSubnet().get());
+
+ EXPECT_TRUE(returned_subnet->getReservationsOutOfPool().unspecified());
+ EXPECT_FALSE(returned_subnet->getReservationsOutOfPool().get());
+
+ EXPECT_TRUE(returned_subnet->getCalculateTeeTimes().unspecified());
+ EXPECT_FALSE(returned_subnet->getCalculateTeeTimes().get());
+
+ EXPECT_TRUE(returned_subnet->getT1Percent().unspecified());
+ EXPECT_EQ(0.0, returned_subnet->getT1Percent().get());
+
+ EXPECT_TRUE(returned_subnet->getT2Percent().unspecified());
+ EXPECT_EQ(0.0, returned_subnet->getT2Percent().get());
+
+ EXPECT_TRUE(returned_subnet->getRapidCommit().unspecified());
+ EXPECT_FALSE(returned_subnet->getRapidCommit().get());
+
+ EXPECT_FALSE(returned_subnet->getDdnsSendUpdates().unspecified());
+ EXPECT_TRUE(returned_subnet->getDdnsSendUpdates().get());
+
+ EXPECT_FALSE(returned_subnet->getDdnsOverrideNoUpdate().unspecified());
+ EXPECT_TRUE(returned_subnet->getDdnsOverrideNoUpdate().get());
+
+ EXPECT_FALSE(returned_subnet->getDdnsOverrideClientUpdate().unspecified());
+ EXPECT_FALSE(returned_subnet->getDdnsOverrideClientUpdate().get());
+
+ EXPECT_FALSE(returned_subnet->getDdnsReplaceClientNameMode().unspecified());
+ EXPECT_EQ(D2ClientConfig::ReplaceClientNameMode::RCM_WHEN_PRESENT,
+ returned_subnet->getDdnsReplaceClientNameMode().get());
+
+ EXPECT_FALSE(returned_subnet->getDdnsGeneratedPrefix().unspecified());
+ EXPECT_EQ("myhost", returned_subnet->getDdnsGeneratedPrefix().get());
+
+ EXPECT_FALSE(returned_subnet->getDdnsQualifyingSuffix().unspecified());
+ EXPECT_EQ("example.org", returned_subnet->getDdnsQualifyingSuffix().get());
+
+ // The easiest way to verify whether the returned subnet matches the inserted
+ // subnet is to convert both to text.
+ EXPECT_EQ(subnet->toElement()->str(), returned_subnet->toElement()->str());
+}
+
+void
+GenericConfigBackendDHCPv6Test::getSubnet6SharedNetworkTest() {
+ Subnet6Ptr subnet = test_subnets_[0];
+ SharedNetwork6Ptr shared_network = test_networks_[0];
+
+ // Add subnet to a shared network.
+ shared_network->add(subnet);
+
+ // Store shared network in the database.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork6(ServerSelector::ALL(),
+ shared_network));
+
+ // Store subnet associated with the shared network in the database.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet6(ServerSelector::ALL(), subnet));
+
+ // Fetch this subnet by subnet identifier.
+ Subnet6Ptr returned_subnet = cbptr_->getSubnet6(ServerSelector::ALL(),
+ test_subnets_[0]->getID());
+ ASSERT_TRUE(returned_subnet);
+ ASSERT_EQ(1, returned_subnet->getServerTags().size());
+ EXPECT_EQ("all", returned_subnet->getServerTags().begin()->get());
+
+ // The easiest way to verify whether the returned subnet matches the inserted
+ // subnet is to convert both to text.
+ EXPECT_EQ(subnet->toElement()->str(), returned_subnet->toElement()->str());
+
+ // However, the check above doesn't verify whether shared network name was
+ // correctly returned from the database.
+ EXPECT_EQ(shared_network->getName(), returned_subnet->getSharedNetworkName());
+}
+
+void
+GenericConfigBackendDHCPv6Test::getSubnet6ByPrefixTest() {
+ // Insert subnet to the database.
+ Subnet6Ptr subnet = test_subnets_[0];
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet6(ServerSelector::ALL(), subnet));
+
+ // Fetch the subnet by prefix.
+ Subnet6Ptr returned_subnet = cbptr_->getSubnet6(ServerSelector::ALL(),
+ "2001:db8::/64");
+ ASSERT_TRUE(returned_subnet);
+ ASSERT_EQ(1, returned_subnet->getServerTags().size());
+ EXPECT_EQ("all", returned_subnet->getServerTags().begin()->get());
+
+ // Verify subnet contents.
+ EXPECT_EQ(subnet->toElement()->str(), returned_subnet->toElement()->str());
+
+ // Fetching the subnet for an explicitly specified server tag should
+ // succeed too.
+ returned_subnet = cbptr_->getSubnet6(ServerSelector::ONE("server1"),
+ "2001:db8::/64");
+ EXPECT_EQ(subnet->toElement()->str(), returned_subnet->toElement()->str());
+}
+
+void
+GenericConfigBackendDHCPv6Test::getSubnet6byPrefixSelectorsTest() {
+ // Supported selectors.
+ ASSERT_NO_THROW_LOG(cbptr_->getSubnet6(ServerSelector::ANY(), "192.0.2.0/26"));
+ ASSERT_NO_THROW_LOG(cbptr_->getSubnet6(ServerSelector::UNASSIGNED(), "192.0.2.0/26"));
+ ASSERT_NO_THROW_LOG(cbptr_->getSubnet6(ServerSelector::ALL(), "192.0.2.0/26"));
+ ASSERT_NO_THROW_LOG(cbptr_->getSubnet6(ServerSelector::ONE("server1"), "192.0.2.0/26"));
+
+ // Not supported selectors.
+ ASSERT_THROW(cbptr_->getSubnet6(ServerSelector::MULTIPLE({ "server1", "server2" }),
+ "192.0.2.0/26"),
+ isc::InvalidOperation);
+}
+
+void
+GenericConfigBackendDHCPv6Test::getAllSubnets6Test() {
+ // Insert test subnets into the database. Note that the second subnet will
+ // overwrite the first subnet as they use the same ID.
+ for (auto subnet : test_subnets_) {
+ cbptr_->createUpdateSubnet6(ServerSelector::ALL(), subnet);
+
+ // That subnet overrides the first subnet so the audit entry should
+ // indicate an update.
+ if (subnet->toText() == "2001:db8:1::/48") {
+ SCOPED_TRACE("UPDATE audit entry for the subnet " + subnet->toText());
+ testNewAuditEntry("dhcp6_subnet",
+ AuditEntry::ModificationType::UPDATE,
+ "subnet set");
+
+ } else {
+ SCOPED_TRACE("CREATE audit entry for the subnet " + subnet->toText());
+ testNewAuditEntry("dhcp6_subnet",
+ AuditEntry::ModificationType::CREATE,
+ "subnet set");
+ }
+ }
+
+ // Fetch all subnets.
+ Subnet6Collection subnets = cbptr_->getAllSubnets6(ServerSelector::ALL());
+ ASSERT_EQ(test_subnets_.size() - 1, subnets.size());
+
+ // All subnets should also be returned for explicitly specified server tag.
+ subnets = cbptr_->getAllSubnets6(ServerSelector::ONE("server1"));
+ ASSERT_EQ(test_subnets_.size() - 1, subnets.size());
+
+ // See if the subnets are returned ok.
+ auto subnet_it = subnets.begin();
+ for (auto i = 0; i < subnets.size(); ++i, ++subnet_it) {
+ ASSERT_EQ(1, (*subnet_it)->getServerTags().size());
+ EXPECT_EQ("all", (*subnet_it)->getServerTags().begin()->get());
+ EXPECT_EQ(test_subnets_[i + 1]->toElement()->str(),
+ (*subnet_it)->toElement()->str());
+ }
+
+ // Attempt to remove the non existing subnet should return 0.
+ EXPECT_EQ(0, cbptr_->deleteSubnet6(ServerSelector::ALL(), 22));
+ EXPECT_EQ(0, cbptr_->deleteSubnet6(ServerSelector::ALL(),
+ "2001:db8:555::/64"));
+ // All subnets should be still there.
+ ASSERT_EQ(test_subnets_.size() - 1, subnets.size());
+
+ // Should not delete the subnet for explicit server tag because
+ // our subnet is for all servers.
+ EXPECT_EQ(0, cbptr_->deleteSubnet6(ServerSelector::ONE("server1"),
+ test_subnets_[1]->getID()));
+
+ // Also, verify that behavior when deleting by prefix.
+ EXPECT_EQ(0, cbptr_->deleteSubnet6(ServerSelector::ONE("server1"),
+ test_subnets_[2]->toText()));
+
+ // Same for all subnets.
+ EXPECT_EQ(0, cbptr_->deleteAllSubnets6(ServerSelector::ONE("server1")));
+
+ // Delete first subnet by id and verify that it is gone.
+ EXPECT_EQ(1, cbptr_->deleteSubnet6(ServerSelector::ALL(),
+ test_subnets_[1]->getID()));
+
+ {
+ SCOPED_TRACE("DELETE first subnet audit entry");
+ testNewAuditEntry("dhcp6_subnet",
+ AuditEntry::ModificationType::DELETE,
+ "subnet deleted");
+ }
+
+ subnets = cbptr_->getAllSubnets6(ServerSelector::ALL());
+ ASSERT_EQ(test_subnets_.size() - 2, subnets.size());
+
+ // Delete second subnet by prefix and verify it is gone.
+ EXPECT_EQ(1, cbptr_->deleteSubnet6(ServerSelector::ALL(),
+ test_subnets_[2]->toText()));
+ subnets = cbptr_->getAllSubnets6(ServerSelector::ALL());
+ ASSERT_EQ(test_subnets_.size() - 3, subnets.size());
+
+ {
+ SCOPED_TRACE("DELETE second subnet audit entry");
+ testNewAuditEntry("dhcp6_subnet",
+ AuditEntry::ModificationType::DELETE,
+ "subnet deleted");
+ }
+
+ // Delete all.
+ EXPECT_EQ(1, cbptr_->deleteAllSubnets6(ServerSelector::ALL()));
+ subnets = cbptr_->getAllSubnets6(ServerSelector::ALL());
+ ASSERT_TRUE(subnets.empty());
+
+ {
+ SCOPED_TRACE("DELETE all subnets audit entry");
+ testNewAuditEntry("dhcp6_subnet",
+ AuditEntry::ModificationType::DELETE,
+ "deleted all subnets");
+ }
+}
+
+void
+GenericConfigBackendDHCPv6Test::getAllSubnets6SelectorsTest() {
+ // Supported selectors.
+ ASSERT_NO_THROW_LOG(cbptr_->getAllSubnets6(ServerSelector::UNASSIGNED()));
+ ASSERT_NO_THROW_LOG(cbptr_->getAllSubnets6(ServerSelector::ALL()));
+ ASSERT_NO_THROW_LOG(cbptr_->getAllSubnets6(ServerSelector::ONE("server1")));
+ ASSERT_NO_THROW_LOG(cbptr_->getAllSubnets6(ServerSelector::MULTIPLE({ "server1", "server2" })));
+
+ // Not supported selectors.
+ ASSERT_THROW(cbptr_->getAllSubnets6(ServerSelector::ANY()), isc::InvalidOperation);
+}
+
+void
+GenericConfigBackendDHCPv6Test::getAllSubnets6WithServerTagsTest() {
+ auto subnet1 = test_subnets_[0];
+ auto subnet2 = test_subnets_[2];
+ auto subnet3 = test_subnets_[3];
+
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[0]));
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[2]));
+
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet6(ServerSelector::ALL(),
+ subnet1));
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet6(ServerSelector::ONE("server1"),
+ subnet2));
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet6(ServerSelector::MULTIPLE({ "server1", "server2" }),
+ subnet3));
+
+ Subnet6Collection subnets;
+
+ // All three subnets are associated with the server1.
+ ASSERT_NO_THROW_LOG(subnets = cbptr_->getAllSubnets6(ServerSelector::ONE("server1")));
+ EXPECT_EQ(3, subnets.size());
+
+ // First subnet is associated with all servers.
+ auto returned_subnet = SubnetFetcher6::get(subnets, SubnetID(1024));
+ ASSERT_TRUE(returned_subnet);
+ EXPECT_TRUE(returned_subnet->hasAllServerTag());
+ EXPECT_FALSE(returned_subnet->hasServerTag(ServerTag("server1")));
+ EXPECT_FALSE(returned_subnet->hasServerTag(ServerTag("server2")));
+
+ // Second subnet is only associated with the server1.
+ returned_subnet = SubnetFetcher6::get(subnets, SubnetID(2048));
+ ASSERT_TRUE(returned_subnet);
+ EXPECT_FALSE(returned_subnet->hasAllServerTag());
+ EXPECT_TRUE(returned_subnet->hasServerTag(ServerTag("server1")));
+ EXPECT_FALSE(returned_subnet->hasServerTag(ServerTag("server2")));
+
+ // Third subnet is associated with both server1 and server2.
+ returned_subnet = SubnetFetcher6::get(subnets, SubnetID(4096));
+ ASSERT_TRUE(returned_subnet);
+ EXPECT_FALSE(returned_subnet->hasAllServerTag());
+ EXPECT_TRUE(returned_subnet->hasServerTag(ServerTag("server1")));
+ EXPECT_TRUE(returned_subnet->hasServerTag(ServerTag("server2")));
+
+ // For server2 we should only get two subnets, i.e. first and last.
+ ASSERT_NO_THROW_LOG(subnets = cbptr_->getAllSubnets6(ServerSelector::ONE("server2")));
+ EXPECT_EQ(2, subnets.size());
+
+ // First subnet is associated with all servers.
+ returned_subnet = SubnetFetcher6::get(subnets, SubnetID(1024));
+ ASSERT_TRUE(returned_subnet);
+ EXPECT_TRUE(returned_subnet->hasAllServerTag());
+ EXPECT_FALSE(returned_subnet->hasServerTag(ServerTag("server1")));
+ EXPECT_FALSE(returned_subnet->hasServerTag(ServerTag("server2")));
+
+ // Last subnet is associated with server1 and server2.
+ returned_subnet = SubnetFetcher6::get(subnets, SubnetID(4096));
+ ASSERT_TRUE(returned_subnet);
+ EXPECT_FALSE(returned_subnet->hasAllServerTag());
+ EXPECT_TRUE(returned_subnet->hasServerTag(ServerTag("server1")));
+ EXPECT_TRUE(returned_subnet->hasServerTag(ServerTag("server2")));
+
+ // Only the first subnet is associated with all servers.
+ ASSERT_NO_THROW_LOG(subnets = cbptr_->getAllSubnets6(ServerSelector::ALL()));
+ EXPECT_EQ(1, subnets.size());
+
+ returned_subnet = SubnetFetcher6::get(subnets, SubnetID(1024));
+ ASSERT_TRUE(returned_subnet);
+ EXPECT_TRUE(returned_subnet->hasAllServerTag());
+ EXPECT_FALSE(returned_subnet->hasServerTag(ServerTag("server1")));
+ EXPECT_FALSE(returned_subnet->hasServerTag(ServerTag("server2")));
+}
+
+void
+GenericConfigBackendDHCPv6Test::getModifiedSubnets6SelectorsTest() {
+ // Supported selectors.
+ ASSERT_NO_THROW_LOG(cbptr_->getModifiedSubnets6(ServerSelector::UNASSIGNED(),
+ timestamps_["yesterday"]));
+ ASSERT_NO_THROW_LOG(cbptr_->getModifiedSubnets6(ServerSelector::ALL(),
+ timestamps_["yesterday"]));
+ ASSERT_NO_THROW_LOG(cbptr_->getModifiedSubnets6(ServerSelector::ONE("server1"),
+ timestamps_["yesterday"]));
+ ASSERT_NO_THROW_LOG(cbptr_->getModifiedSubnets6(ServerSelector::MULTIPLE({ "server1", "server2" }),
+ timestamps_["yesterday"]));
+
+ // Not supported selectors.
+ EXPECT_THROW(cbptr_->getModifiedSubnets6(ServerSelector::ANY(),
+ timestamps_["yesterday"]),
+ isc::InvalidOperation);
+}
+
+void
+GenericConfigBackendDHCPv6Test::deleteSubnet6Test() {
+ // Create two servers in the database.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[0]));
+ {
+ SCOPED_TRACE("CREATE audit entry for server");
+ testNewAuditEntry("dhcp6_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set");
+ }
+
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[2]));
+ {
+ SCOPED_TRACE("CREATE audit entry for server");
+ testNewAuditEntry("dhcp6_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set");
+ }
+
+ auto subnet1 = test_subnets_[0];
+ auto subnet2 = test_subnets_[2];
+ auto subnet3 = test_subnets_[3];
+
+ auto create_test_subnets = [&] () {
+ // Insert three subnets, one for all servers, one for server2 and one for two
+ // servers: server1 and server2.
+ ASSERT_NO_THROW_LOG(
+ cbptr_->createUpdateSubnet6(ServerSelector::ALL(), subnet1)
+ );
+ ASSERT_NO_THROW_LOG(
+ cbptr_->createUpdateSubnet6(ServerSelector::ONE("server2"), subnet2)
+ );
+ ASSERT_NO_THROW_LOG(
+ cbptr_->createUpdateSubnet6(ServerSelector::MULTIPLE({ "server1", "server2" }),
+ subnet3)
+ );
+ };
+
+ create_test_subnets();
+
+ // Test that subnet is not deleted for a specified server selector.
+ auto test_no_delete = [this] (const std::string& test_case_name,
+ const ServerSelector& server_selector,
+ const Subnet6Ptr& subnet) {
+ SCOPED_TRACE(test_case_name);
+ uint64_t deleted_count = 0;
+ ASSERT_NO_THROW_LOG(
+ deleted_count = cbptr_->deleteSubnet6(server_selector, subnet->getID())
+ );
+ EXPECT_EQ(0, deleted_count);
+
+ deleted_count = 0;
+ ASSERT_NO_THROW_LOG(
+ deleted_count = cbptr_->deleteSubnet6(server_selector, subnet->toText())
+ );
+ EXPECT_EQ(0, deleted_count);
+ };
+
+ {
+ SCOPED_TRACE("Test valid but non matching server selectors");
+ test_no_delete("selector: one, actual: all", ServerSelector::ONE("server2"),
+ subnet1);
+ test_no_delete("selector: all, actual: one", ServerSelector::ALL(),
+ subnet2);
+ test_no_delete("selector: all, actual: multiple", ServerSelector::ALL(),
+ subnet3);
+ }
+
+ // Test successful deletion of a subnet by ID.
+ auto test_delete_by_id = [this] (const std::string& test_case_name,
+ const ServerSelector& server_selector,
+ const Subnet6Ptr& subnet) {
+ SCOPED_TRACE(test_case_name);
+ uint64_t deleted_count = 0;
+ ASSERT_NO_THROW_LOG(
+ deleted_count = cbptr_->deleteSubnet6(server_selector, subnet->getID())
+ );
+ EXPECT_EQ(1, deleted_count);
+
+ EXPECT_FALSE(cbptr_->getSubnet6(server_selector, subnet->getID()));
+ };
+
+ test_delete_by_id("all servers", ServerSelector::ALL(), subnet1);
+ test_delete_by_id("any server", ServerSelector::ANY(), subnet2);
+ test_delete_by_id("one server", ServerSelector::ONE("server1"), subnet3);
+
+ // Re-create deleted subnets.
+ create_test_subnets();
+
+ // Test successful deletion of a subnet by prefix.
+ auto test_delete_by_prefix = [this] (const std::string& test_case_name,
+ const ServerSelector& server_selector,
+ const Subnet6Ptr& subnet) {
+ SCOPED_TRACE(test_case_name);
+ uint64_t deleted_count = 0;
+ ASSERT_NO_THROW_LOG(
+ deleted_count = cbptr_->deleteSubnet6(server_selector, subnet->toText())
+ );
+ EXPECT_EQ(1, deleted_count);
+
+ EXPECT_FALSE(cbptr_->getSubnet6(server_selector, subnet->toText()));
+ };
+
+ test_delete_by_prefix("all servers", ServerSelector::ALL(), subnet1);
+ test_delete_by_prefix("any server", ServerSelector::ANY(), subnet2);
+ test_delete_by_prefix("one server", ServerSelector::ONE("server1"), subnet3);
+}
+
+void
+GenericConfigBackendDHCPv6Test::deleteSubnet6ByIdSelectorsTest() {
+ // Supported selectors.
+ ASSERT_NO_THROW_LOG(cbptr_->deleteSubnet6(ServerSelector::ANY(), SubnetID(1)));
+ ASSERT_NO_THROW_LOG(cbptr_->deleteSubnet6(ServerSelector::ALL(), SubnetID(1)));
+ ASSERT_NO_THROW_LOG(cbptr_->deleteSubnet6(ServerSelector::ONE("server1"), SubnetID(1)));
+
+ // Not supported selectors.
+ ASSERT_THROW(cbptr_->deleteSubnet6(ServerSelector::MULTIPLE({ "server1", "server2" }),
+ SubnetID(1)),
+ isc::InvalidOperation);
+
+ // Not implemented selectors.
+ ASSERT_THROW(cbptr_->deleteSubnet6(ServerSelector::UNASSIGNED(), SubnetID(1)),
+ isc::NotImplemented);
+}
+
+void
+GenericConfigBackendDHCPv6Test::deleteSubnet6ByPrefixSelectorsTest() {
+ // Supported selectors.
+ ASSERT_NO_THROW_LOG(cbptr_->deleteSubnet6(ServerSelector::ANY(), "192.0.2.0/26"));
+ ASSERT_NO_THROW_LOG(cbptr_->deleteSubnet6(ServerSelector::ALL(), "192.0.2.0/26"));
+ ASSERT_NO_THROW_LOG(cbptr_->deleteSubnet6(ServerSelector::ONE("server1"), "192.0.2.0/26"));
+
+ // Not supported selectors.
+ ASSERT_THROW(cbptr_->deleteSubnet6(ServerSelector::MULTIPLE({ "server1", "server2" }),
+ "192.0.2.0/26"),
+ isc::InvalidOperation);
+
+ // Not implemented selectors.
+ ASSERT_THROW(cbptr_->deleteSubnet6(ServerSelector::UNASSIGNED(), "192.0.2.0/26"),
+ isc::NotImplemented);
+}
+
+void
+GenericConfigBackendDHCPv6Test::deleteAllSubnets6SelectorsTest() {
+ // Supported selectors.
+ ASSERT_NO_THROW_LOG(cbptr_->deleteAllSubnets6(ServerSelector::UNASSIGNED()));
+ ASSERT_NO_THROW_LOG(cbptr_->deleteAllSubnets6(ServerSelector::ALL()));
+ ASSERT_NO_THROW_LOG(cbptr_->deleteAllSubnets6(ServerSelector::ONE("server1")));
+
+ // Not supported selectors.
+ ASSERT_THROW(cbptr_->deleteAllSubnets6(ServerSelector::ANY()),
+ isc::InvalidOperation);
+ ASSERT_THROW(cbptr_->deleteAllSubnets6(ServerSelector::MULTIPLE({ "server1", "server2" })),
+ isc::InvalidOperation);
+}
+
+void
+GenericConfigBackendDHCPv6Test::unassignedSubnet6Test() {
+ // Create the server.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[0]));
+
+ // Create the subnets and associate them with the server1.
+ auto subnet = test_subnets_[0];
+ auto subnet2 = test_subnets_[2];
+ ASSERT_NO_THROW_LOG(
+ cbptr_->createUpdateSubnet6(ServerSelector::ONE("server1"), subnet)
+ );
+ ASSERT_NO_THROW_LOG(
+ cbptr_->createUpdateSubnet6(ServerSelector::ONE("server1"), subnet2)
+ );
+
+ // Delete the server. The subnets should be preserved but are considered orphaned,
+ // i.e. do not belong to any server.
+ uint64_t deleted_count = 0;
+ ASSERT_NO_THROW_LOG(deleted_count = cbptr_->deleteServer6(ServerTag("server1")));
+ EXPECT_EQ(1, deleted_count);
+
+ // Trying to fetch the subnet by server tag should return no result.
+ Subnet6Ptr returned_subnet;
+ ASSERT_NO_THROW_LOG(returned_subnet = cbptr_->getSubnet6(ServerSelector::ONE("server1"),
+ subnet->getID()));
+ EXPECT_FALSE(returned_subnet);
+
+ // The same if we use other calls.
+ ASSERT_NO_THROW_LOG(returned_subnet = cbptr_->getSubnet6(ServerSelector::ONE("server1"),
+ subnet->toText()));
+ EXPECT_FALSE(returned_subnet);
+
+ Subnet6Collection returned_subnets;
+ ASSERT_NO_THROW_LOG(returned_subnets = cbptr_->getAllSubnets6(ServerSelector::ONE("server1")));
+ EXPECT_TRUE(returned_subnets.empty());
+
+ ASSERT_NO_THROW_LOG(
+ returned_subnets = cbptr_->getModifiedSubnets6(ServerSelector::ONE("server1"),
+ timestamps_["two days ago"])
+ );
+ EXPECT_TRUE(returned_subnets.empty());
+
+ // We should get the subnet if we ask for unassigned.
+ ASSERT_NO_THROW_LOG(returned_subnet = cbptr_->getSubnet6(ServerSelector::UNASSIGNED(),
+ subnet->getID()));
+ ASSERT_TRUE(returned_subnet);
+
+ ASSERT_NO_THROW_LOG(returned_subnet = cbptr_->getSubnet6(ServerSelector::UNASSIGNED(),
+ subnet->toText()));
+ ASSERT_TRUE(returned_subnet);
+
+ // Also if we ask for all unassigned subnets it should be returned.
+ ASSERT_NO_THROW_LOG(returned_subnets = cbptr_->getAllSubnets6(ServerSelector::UNASSIGNED()));
+ ASSERT_EQ(2, returned_subnets.size());
+
+ // Same for modified subnets.
+ ASSERT_NO_THROW_LOG(
+ returned_subnets = cbptr_->getModifiedSubnets6(ServerSelector::UNASSIGNED(),
+ timestamps_["two days ago"])
+ );
+ ASSERT_EQ(2, returned_subnets.size());
+
+ // If we ask for any subnet by subnet id, it should be returned too.
+ ASSERT_NO_THROW_LOG(returned_subnet = cbptr_->getSubnet6(ServerSelector::ANY(),
+ subnet->getID()));
+ ASSERT_TRUE(returned_subnet);
+
+ ASSERT_NO_THROW_LOG(returned_subnet = cbptr_->getSubnet6(ServerSelector::ANY(),
+ subnet->toText()));
+ ASSERT_TRUE(returned_subnet);
+
+ // Deleting the subnet with the mismatched server tag should not affect our
+ // subnet.
+ ASSERT_NO_THROW_LOG(
+ deleted_count = cbptr_->deleteSubnet6(ServerSelector::ONE("server1"),
+ subnet->getID())
+ );
+ EXPECT_EQ(0, deleted_count);
+
+ // Also, if we delete all subnets for server1.
+ ASSERT_NO_THROW_LOG(
+ deleted_count = cbptr_->deleteAllSubnets6(ServerSelector::ONE("server1"))
+ );
+ EXPECT_EQ(0, deleted_count);
+
+ // We can delete this subnet when we specify ANY and the matching id.
+ ASSERT_NO_THROW_LOG(
+ deleted_count = cbptr_->deleteSubnet6(ServerSelector::ANY(), subnet->getID())
+ );
+ EXPECT_EQ(1, deleted_count);
+
+ // We can delete all subnets using UNASSIGNED selector.
+ ASSERT_NO_THROW_LOG(
+ deleted_count = cbptr_->deleteAllSubnets6(ServerSelector::UNASSIGNED());
+ );
+ EXPECT_EQ(1, deleted_count);
+}
+
+void
+GenericConfigBackendDHCPv6Test::getModifiedSubnets6Test() {
+ // Explicitly set timestamps of subnets. First subnet has a timestamp
+ // pointing to the future. Second subnet has timestamp pointing to the
+ // past (yesterday). Third subnet has a timestamp pointing to the
+ // past (an hour ago).
+ test_subnets_[1]->setModificationTime(timestamps_["tomorrow"]);
+ test_subnets_[2]->setModificationTime(timestamps_["yesterday"]);
+ test_subnets_[3]->setModificationTime(timestamps_["today"]);
+
+ // Insert subnets into the database.
+ for (int i = 1; i < test_subnets_.size(); ++i) {
+ cbptr_->createUpdateSubnet6(ServerSelector::ALL(),
+ test_subnets_[i]);
+ }
+
+ // Fetch subnets with timestamp later than today. Only one subnet
+ // should be returned.
+ Subnet6Collection
+ subnets = cbptr_->getModifiedSubnets6(ServerSelector::ALL(),
+ timestamps_["after today"]);
+ ASSERT_EQ(1, subnets.size());
+
+ // All subnets should also be returned for explicitly specified server tag.
+ subnets = cbptr_->getModifiedSubnets6(ServerSelector::ONE("server1"),
+ timestamps_["after today"]);
+ ASSERT_EQ(1, subnets.size());
+
+ // Fetch subnets with timestamp later than yesterday. We should get
+ // two subnets.
+ subnets = cbptr_->getModifiedSubnets6(ServerSelector::ALL(),
+ timestamps_["after yesterday"]);
+ ASSERT_EQ(2, subnets.size());
+
+ // Fetch subnets with timestamp later than tomorrow. Nothing should
+ // be returned.
+ subnets = cbptr_->getModifiedSubnets6(ServerSelector::ALL(),
+ timestamps_["after tomorrow"]);
+ ASSERT_TRUE(subnets.empty());
+}
+
+void
+GenericConfigBackendDHCPv6Test::subnetLifetimeTest() {
+ // Insert new subnet with unspecified valid lifetime
+ Triplet<uint32_t> unspecified;
+ Subnet6Ptr subnet(new Subnet6(IOAddress("2001:db8::"), 64, 30, 40,
+ unspecified, unspecified, 1111));
+ subnet->setIface("eth1");
+ cbptr_->createUpdateSubnet6(ServerSelector::ALL(), subnet);
+
+ // Fetch this subnet by subnet identifier
+ Subnet6Ptr returned_subnet = cbptr_->getSubnet6(ServerSelector::ALL(),
+ subnet->getID());
+ ASSERT_TRUE(returned_subnet);
+
+ // Verified returned and original subnets match.
+ EXPECT_EQ(subnet->toElement()->str(), returned_subnet->toElement()->str());
+
+ // Update the preferred and valid lifetime.
+ subnet->setPreferred( Triplet<uint32_t>(100, 200, 300));
+ subnet->setValid( Triplet<uint32_t>(200, 300, 400));
+ cbptr_->createUpdateSubnet6(ServerSelector::ALL(), subnet);
+
+ // Fetch and verify again.
+ returned_subnet = cbptr_->getSubnet6(ServerSelector::ALL(), subnet->getID());
+ ASSERT_TRUE(returned_subnet);
+ EXPECT_EQ(subnet->toElement()->str(), returned_subnet->toElement()->str());
+}
+
+void
+GenericConfigBackendDHCPv6Test::getSharedNetworkSubnets6Test() {
+ // Assign test subnets to shared networks level1 and level2.
+ test_subnets_[1]->setSharedNetworkName("level1");
+ test_subnets_[2]->setSharedNetworkName("level2");
+ test_subnets_[3]->setSharedNetworkName("level2");
+
+ // Store shared networks in the database.
+ for (auto network : test_networks_) {
+ cbptr_->createUpdateSharedNetwork6(ServerSelector::ALL(), network);
+ }
+
+ // Store subnets in the database.
+ for (auto subnet : test_subnets_) {
+ cbptr_->createUpdateSubnet6(ServerSelector::ALL(), subnet);
+ }
+
+ // Fetch all subnets belonging to shared network level1.
+ Subnet6Collection subnets = cbptr_->getSharedNetworkSubnets6(ServerSelector::ALL(),
+ "level1");
+ ASSERT_EQ(1, subnets.size());
+
+ // Returned subnet should match test subnet #1.
+ EXPECT_TRUE(isEquivalent(test_subnets_[1]->toElement(),
+ (*subnets.begin())->toElement()));
+
+ // All subnets should also be returned for ANY server.
+ subnets = cbptr_->getSharedNetworkSubnets6(ServerSelector::ANY(), "level1");
+ ASSERT_EQ(1, subnets.size());
+
+ // Returned subnet should match test subnet #1.
+ EXPECT_TRUE(isEquivalent(test_subnets_[1]->toElement(),
+ (*subnets.begin())->toElement()));
+
+ // Check server tag
+ ASSERT_EQ(1, (*subnets.begin())->getServerTags().size());
+ EXPECT_EQ("all", (*subnets.begin())->getServerTags().begin()->get());
+
+ // Fetch all subnets belonging to shared network level2.
+ subnets = cbptr_->getSharedNetworkSubnets6(ServerSelector::ALL(), "level2");
+ ASSERT_EQ(2, subnets.size());
+
+ ElementPtr test_list = Element::createList();
+ test_list->add(test_subnets_[2]->toElement());
+ test_list->add(test_subnets_[3]->toElement());
+
+ ElementPtr returned_list = Element::createList();
+ auto subnet = subnets.begin();
+ returned_list->add((*subnet)->toElement());
+ returned_list->add((*++subnet)->toElement());
+
+ EXPECT_TRUE(isEquivalent(returned_list, test_list));
+
+ // All subnets should also be returned for explicitly specified server tag.
+ subnets = cbptr_->getSharedNetworkSubnets6(ServerSelector::ONE("server1"), "level2");
+ ASSERT_EQ(2, subnets.size());
+
+ returned_list = Element::createList();
+ subnet = subnets.begin();
+ returned_list->add((*subnet)->toElement());
+ returned_list->add((*++subnet)->toElement());
+
+ EXPECT_TRUE(isEquivalent(returned_list, test_list));
+}
+
+void
+GenericConfigBackendDHCPv6Test::subnetUpdatePoolsTest() {
+
+ auto test_subnet_update = [this](const std::string& subnet_prefix,
+ const SubnetID& subnet_id) {
+ // Add the subnet with two address pools and two prefix delegation
+ // pools.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet6(ServerSelector::ALL(),
+ test_subnets_[0]));
+ // Make sure that the pools have been added to the database.
+ EXPECT_EQ(2, countRows("dhcp6_pool"));
+ EXPECT_EQ(2, countRows("dhcp6_pd_pool"));
+
+ // Create the subnet without options which updates the existing
+ // subnet.
+ Subnet6Ptr subnet(new Subnet6(IOAddress(subnet_prefix), 64, 30, 60, 50, 60,
+ subnet_id));
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet6(ServerSelector::ALL(), subnet));
+ // Check that options are gone.
+ EXPECT_EQ(0, countRows("dhcp6_pool"));
+ EXPECT_EQ(0, countRows("dhcp6_pd_pool"));
+ };
+
+ {
+ SCOPED_TRACE("update subnet, modify subnet id");
+ // Create another subnet with the same prefix as the original subnet but
+ // different id. This is legal to update the subnet id if the prefix is
+ // stable. However, the new subnet has no address pools, so we need to
+ // check of the pools associated with the existing subnet instance are
+ // gone after the update.
+ test_subnet_update("2001:db8::", 2048);
+ }
+
+ {
+ SCOPED_TRACE("update subnet, modify prefix");
+ // Create a subnet with the same subnet id but different prefix.
+ // The prefix should be updated.
+ test_subnet_update("2001:db9::", 1024);
+ }
+}
+
+void
+GenericConfigBackendDHCPv6Test::subnetOptionsTest() {
+ // Add the subnet with two pools and three options.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet6(ServerSelector::ALL(), test_subnets_[0]));
+ EXPECT_EQ(2, countRows("dhcp6_pool"));
+ EXPECT_EQ(2, countRows("dhcp6_pd_pool"));
+ EXPECT_EQ(3, countRows("dhcp6_options"));
+
+ // The second subnet uses the same subnet id, so this operation should replace
+ // the existing subnet and its options. The new instance has four pools, each
+ // including one option, so we should end up with four options.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet6(ServerSelector::ALL(), test_subnets_[1]));
+ EXPECT_EQ(2, countRows("dhcp6_pool"));
+ EXPECT_EQ(2, countRows("dhcp6_pd_pool"));
+ EXPECT_EQ(4, countRows("dhcp6_options"));
+
+ // Add third subnet with a single option. The number of options in the database
+ // should now be 5.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet6(ServerSelector::ALL(), test_subnets_[2]));
+ EXPECT_EQ(2, countRows("dhcp6_pool"));
+ EXPECT_EQ(2, countRows("dhcp6_pd_pool"));
+ EXPECT_EQ(5, countRows("dhcp6_options"));
+
+ // Delete the subnet. All options and pools it contains should also be removed, leaving
+ // the last added subnet and its sole option.
+ ASSERT_NO_THROW_LOG(cbptr_->deleteSubnet6(ServerSelector::ALL(), test_subnets_[1]->getID()));
+ EXPECT_EQ(1, countRows("dhcp6_subnet"));
+ EXPECT_EQ(0, countRows("dhcp6_pool"));
+ EXPECT_EQ(0, countRows("dhcp6_pd_pool"));
+ EXPECT_EQ(1, countRows("dhcp6_options"));
+
+ // Add the first subnet again. We should now have 4 options: 3 options from the
+ // newly added subnet and one option from the existing subnet.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet6(ServerSelector::ALL(), test_subnets_[0]));
+ EXPECT_EQ(2, countRows("dhcp6_pool"));
+ EXPECT_EQ(2, countRows("dhcp6_pd_pool"));
+ EXPECT_EQ(4, countRows("dhcp6_options"));
+
+ // Delete the subnet including 3 options. The option from the other subnet should not
+ // be affected.
+ ASSERT_NO_THROW_LOG(cbptr_->deleteSubnet6(ServerSelector::ALL(), test_subnets_[0]->getID()));
+ EXPECT_EQ(1, countRows("dhcp6_subnet"));
+ EXPECT_EQ(0, countRows("dhcp6_pool"));
+ EXPECT_EQ(0, countRows("dhcp6_pd_pool"));
+ EXPECT_EQ(1, countRows("dhcp6_options"));
+}
+
+void
+GenericConfigBackendDHCPv6Test::getSharedNetwork6Test() {
+ // Insert the server2 into the database.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[2]));
+ {
+ SCOPED_TRACE("CREATE audit entry for server");
+ testNewAuditEntry("dhcp6_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set");
+ }
+
+ auto shared_network = test_networks_[0];
+ auto shared_network2 = test_networks_[2];
+
+ // Insert two shared networks, one for all servers, and one for server2.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork6(ServerSelector::ALL(),
+ shared_network));
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork6(ServerSelector::ONE("server2"),
+ shared_network2));
+
+ // We are not going to support selection of a single entry for multiple servers.
+ ASSERT_THROW(cbptr_->getSharedNetwork6(ServerSelector::MULTIPLE({ "server1", "server2" }),
+ test_networks_[0]->getName()),
+ isc::InvalidOperation);
+
+ // Test that this shared network will be fetched for various server selectors.
+ auto test_get_network = [this, &shared_network] (const std::string& test_case_name,
+ const ServerSelector& server_selector,
+ const std::string& expected_tag = ServerTag::ALL) {
+ SCOPED_TRACE(test_case_name);
+ SharedNetwork6Ptr network;
+ ASSERT_NO_THROW_LOG(network = cbptr_->getSharedNetwork6(server_selector,
+ shared_network->getName()));
+ ASSERT_TRUE(network);
+
+ EXPECT_GT(network->getId(), 0);
+ ASSERT_EQ(1, network->getServerTags().size());
+ EXPECT_EQ(expected_tag, network->getServerTags().begin()->get());
+
+ // The easiest way to verify whether the returned shared network matches the
+ // inserted shared network is to convert both to text.
+ EXPECT_EQ(shared_network->toElement()->str(), network->toElement()->str());
+ };
+
+ {
+ SCOPED_TRACE("testing various server selectors before update");
+ test_get_network("all servers", ServerSelector::ALL());
+ test_get_network("one server", ServerSelector::ONE("server1"));
+ test_get_network("any server", ServerSelector::ANY());
+ }
+
+ {
+ SCOPED_TRACE("CREATE audit entry for a shared network");
+ testNewAuditEntry("dhcp6_shared_network",
+ AuditEntry::ModificationType::CREATE,
+ "shared network set");
+ }
+
+ // Update shared network in the database.
+ shared_network = test_networks_[1];
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork6(ServerSelector::ALL(),
+ shared_network));
+
+ {
+ SCOPED_TRACE("testing various server selectors after update");
+ test_get_network("all servers after update", ServerSelector::ALL());
+ test_get_network("one server after update", ServerSelector::ONE("server1"));
+ test_get_network("any server after update", ServerSelector::ANY());
+ }
+
+ {
+ SCOPED_TRACE("UPDATE audit entry for a shared network");
+ testNewAuditEntry("dhcp6_shared_network",
+ AuditEntry::ModificationType::UPDATE,
+ "shared network set");
+ }
+
+ // The server2 specific shared network should not be returned if the
+ // server selector is not matching.
+ EXPECT_FALSE(cbptr_->getSharedNetwork6(ServerSelector::ALL(),
+ shared_network2->getName()));
+ EXPECT_FALSE(cbptr_->getSharedNetwork6(ServerSelector::ONE("server1"),
+ shared_network2->getName()));
+
+ {
+ SCOPED_TRACE("testing selectors for server2 specific shared network");
+ shared_network = shared_network2;
+ test_get_network("one server", ServerSelector::ONE("server2"), "server2");
+ test_get_network("any server", ServerSelector::ANY(), "server2");
+ }
+}
+
+void
+GenericConfigBackendDHCPv6Test::getSharedNetwork6SelectorsTest() {
+ // Supported selectors.
+ ASSERT_NO_THROW_LOG(cbptr_->getSharedNetwork6(ServerSelector::ANY(), "level1"));
+ ASSERT_NO_THROW_LOG(cbptr_->getSharedNetwork6(ServerSelector::UNASSIGNED(), "level1"));
+ ASSERT_NO_THROW_LOG(cbptr_->getSharedNetwork6(ServerSelector::ALL(), "level1"));
+ ASSERT_NO_THROW_LOG(cbptr_->getSharedNetwork6(ServerSelector::ONE("server1"), "level1"));
+
+ // Not supported selectors.
+ ASSERT_THROW(cbptr_->getSharedNetwork6(ServerSelector::MULTIPLE({ "server1", "server2" }),
+ "level1"),
+ isc::InvalidOperation);
+}
+
+void
+GenericConfigBackendDHCPv6Test::createUpdateSharedNetwork6Test() {
+ auto shared_network = test_networks_[0];
+
+ // An attempt to insert the shared network for non-existing server should fail.
+ ASSERT_THROW(cbptr_->createUpdateSharedNetwork6(ServerSelector::ONE("server1"),
+ shared_network),
+ NullKeyError);
+
+ // Insert the server1 into the database.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[0]));
+ {
+ SCOPED_TRACE("CREATE audit entry for server");
+ testNewAuditEntry("dhcp6_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set");
+ }
+
+ // Insert the server2 into the database.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[2]));
+ {
+ SCOPED_TRACE("CREATE audit entry for server");
+ testNewAuditEntry("dhcp6_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set");
+ }
+
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork6(ServerSelector::ALL(),
+ shared_network));
+ {
+ SCOPED_TRACE("CREATE audit entry for shared network and ALL servers");
+ testNewAuditEntry("dhcp6_shared_network",
+ AuditEntry::ModificationType::CREATE,
+ "shared network set");
+ }
+
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork6(ServerSelector::MULTIPLE({ "server1", "server2" }),
+ shared_network));
+ {
+ SCOPED_TRACE("UPDATE audit entry for shared network and MULTIPLE servers");
+ testNewAuditEntry("dhcp6_shared_network",
+ AuditEntry::ModificationType::UPDATE,
+ "shared network set");
+ }
+
+ SharedNetwork6Ptr network;
+ ASSERT_NO_THROW_LOG(network = cbptr_->getSharedNetwork6(ServerSelector::ANY(),
+ shared_network->getName()));
+ ASSERT_TRUE(network);
+ EXPECT_TRUE(network->hasServerTag(ServerTag("server1")));
+ EXPECT_TRUE(network->hasServerTag(ServerTag("server2")));
+ EXPECT_FALSE(network->hasServerTag(ServerTag()));
+}
+
+void
+GenericConfigBackendDHCPv6Test::createUpdateSharedNetwork6SelectorsTest() {
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[0]));
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[2]));
+
+ // Supported selectors.
+ SharedNetwork6Ptr shared_network(new SharedNetwork6("all"));
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork6(ServerSelector::ALL(),
+ shared_network));
+ shared_network.reset(new SharedNetwork6("one"));
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork6(ServerSelector::ONE("server1"),
+ shared_network));
+ shared_network.reset(new SharedNetwork6("multiple"));
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork6(ServerSelector::MULTIPLE({ "server1", "server2" }),
+ shared_network));
+
+ // Not supported server selectors.
+ ASSERT_THROW(cbptr_->createUpdateSharedNetwork6(ServerSelector::ANY(), shared_network),
+ isc::InvalidOperation);
+
+ // Not implemented server selectors.
+ ASSERT_THROW(cbptr_->createUpdateSharedNetwork6(ServerSelector::UNASSIGNED(),
+ shared_network),
+ isc::NotImplemented);
+}
+
+void
+GenericConfigBackendDHCPv6Test::getSharedNetwork6WithOptionalUnspecifiedTest() {
+ // Insert new shared network.
+ SharedNetwork6Ptr shared_network = test_networks_[2];
+ cbptr_->createUpdateSharedNetwork6(ServerSelector::ALL(), shared_network);
+
+ // Fetch this shared network by name.
+ SharedNetwork6Ptr
+ returned_network = cbptr_->getSharedNetwork6(ServerSelector::ALL(),
+ test_networks_[2]->getName());
+ ASSERT_TRUE(returned_network);
+
+ EXPECT_TRUE(returned_network->getIface().unspecified());
+ EXPECT_TRUE(returned_network->getIface().empty());
+
+ EXPECT_TRUE(returned_network->getClientClass().unspecified());
+ EXPECT_TRUE(returned_network->getClientClass().empty());
+
+ EXPECT_TRUE(returned_network->getValid().unspecified());
+ EXPECT_EQ(0, returned_network->getValid().get());
+
+ EXPECT_TRUE(returned_network->getPreferred().unspecified());
+ EXPECT_EQ(0, returned_network->getPreferred().get());
+
+ EXPECT_TRUE(returned_network->getT1().unspecified());
+ EXPECT_EQ(0, returned_network->getT1().get());
+
+ EXPECT_TRUE(returned_network->getT2().unspecified());
+ EXPECT_EQ(0, returned_network->getT2().get());
+
+ EXPECT_TRUE(returned_network->getReservationsGlobal().unspecified());
+ EXPECT_FALSE(returned_network->getReservationsGlobal().get());
+
+ EXPECT_TRUE(returned_network->getReservationsInSubnet().unspecified());
+ EXPECT_TRUE(returned_network->getReservationsInSubnet().get());
+
+ EXPECT_TRUE(returned_network->getReservationsOutOfPool().unspecified());
+ EXPECT_FALSE(returned_network->getReservationsOutOfPool().get());
+
+ EXPECT_TRUE(returned_network->getCalculateTeeTimes().unspecified());
+ EXPECT_FALSE(returned_network->getCalculateTeeTimes().get());
+
+ EXPECT_TRUE(returned_network->getT1Percent().unspecified());
+ EXPECT_EQ(0.0, returned_network->getT1Percent().get());
+
+ EXPECT_TRUE(returned_network->getT2Percent().unspecified());
+ EXPECT_EQ(0.0, returned_network->getT2Percent().get());
+
+ EXPECT_TRUE(returned_network->getRapidCommit().unspecified());
+ EXPECT_FALSE(returned_network->getRapidCommit().get());
+
+ EXPECT_FALSE(returned_network->getDdnsSendUpdates().unspecified());
+ EXPECT_TRUE(returned_network->getDdnsSendUpdates().get());
+
+ EXPECT_FALSE(returned_network->getDdnsOverrideNoUpdate().unspecified());
+ EXPECT_TRUE(returned_network->getDdnsOverrideNoUpdate().get());
+
+ EXPECT_FALSE(returned_network->getDdnsOverrideClientUpdate().unspecified());
+ EXPECT_FALSE(returned_network->getDdnsOverrideClientUpdate().get());
+
+ EXPECT_FALSE(returned_network->getDdnsReplaceClientNameMode().unspecified());
+ EXPECT_EQ(D2ClientConfig::ReplaceClientNameMode::RCM_WHEN_PRESENT,
+ returned_network->getDdnsReplaceClientNameMode().get());
+
+ EXPECT_FALSE(returned_network->getDdnsGeneratedPrefix().unspecified());
+ EXPECT_EQ("myhost", returned_network->getDdnsGeneratedPrefix().get());
+
+ EXPECT_FALSE(returned_network->getDdnsQualifyingSuffix().unspecified());
+ EXPECT_EQ("example.org", returned_network->getDdnsQualifyingSuffix().get());
+}
+
+void
+GenericConfigBackendDHCPv6Test::deleteSharedNetworkSubnets6Test() {
+ ASSERT_THROW(cbptr_->deleteSharedNetworkSubnets6(ServerSelector::UNASSIGNED(),
+ test_networks_[1]->getName()),
+ isc::InvalidOperation);
+ ASSERT_THROW(cbptr_->deleteSharedNetworkSubnets6(ServerSelector::ALL(),
+ test_networks_[1]->getName()),
+ isc::InvalidOperation);
+ ASSERT_THROW(cbptr_->deleteSharedNetworkSubnets6(ServerSelector::ONE("server1"),
+ test_networks_[1]->getName()),
+ isc::InvalidOperation);
+ ASSERT_THROW(cbptr_->deleteSharedNetworkSubnets6(ServerSelector::MULTIPLE({ "server1", "server2" }),
+ test_networks_[1]->getName()),
+ isc::InvalidOperation);
+}
+
+void
+GenericConfigBackendDHCPv6Test::getAllSharedNetworks6Test() {
+ // Insert test shared networks into the database. Note that the second shared
+ // network will overwrite the first shared network as they use the same name.
+ for (auto network : test_networks_) {
+ cbptr_->createUpdateSharedNetwork6(ServerSelector::ALL(), network);
+
+ // That shared network overrides the first one so the audit entry should
+ // indicate an update.
+ if ((network->getName() == "level1") && (!audit_entries_["all"].empty())) {
+ SCOPED_TRACE("UPDATE audit entry for the shared network " +
+ network->getName());
+ testNewAuditEntry("dhcp6_shared_network",
+ AuditEntry::ModificationType::UPDATE,
+ "shared network set");
+
+ } else {
+ SCOPED_TRACE("CREATE audit entry for the shared network " +
+ network->getName());
+ testNewAuditEntry("dhcp6_shared_network",
+ AuditEntry::ModificationType::CREATE,
+ "shared network set");
+ }
+ }
+
+ // Fetch all shared networks.
+ SharedNetwork6Collection networks =
+ cbptr_->getAllSharedNetworks6(ServerSelector::ALL());
+ ASSERT_EQ(test_networks_.size() - 1, networks.size());
+
+ // All shared networks should also be returned for explicitly specified
+ // server tag.
+ networks = cbptr_->getAllSharedNetworks6(ServerSelector::ONE("server1"));
+ ASSERT_EQ(test_networks_.size() - 1, networks.size());
+
+ // See if shared networks are returned ok.
+ for (auto i = 0; i < networks.size(); ++i) {
+ EXPECT_EQ(test_networks_[i + 1]->toElement()->str(),
+ networks[i]->toElement()->str());
+ ASSERT_EQ(1, networks[i]->getServerTags().size());
+ EXPECT_EQ("all", networks[i]->getServerTags().begin()->get());
+ }
+
+ // Add some subnets.
+ test_networks_[1]->add(test_subnets_[0]);
+ test_subnets_[2]->setSharedNetworkName("level2");
+ test_networks_[2]->add(test_subnets_[3]);
+ cbptr_->createUpdateSubnet6(ServerSelector::ALL(), test_subnets_[0]);
+ cbptr_->createUpdateSubnet6(ServerSelector::ALL(), test_subnets_[2]);
+ cbptr_->createUpdateSubnet6(ServerSelector::ALL(), test_subnets_[3]);
+
+ // Both ways to attach a subnet are equivalent.
+ Subnet6Ptr subnet = cbptr_->getSubnet6(ServerSelector::ALL(),
+ test_subnets_[0]->getID());
+ ASSERT_TRUE(subnet);
+ EXPECT_EQ("level1", subnet->getSharedNetworkName());
+
+ {
+ SCOPED_TRACE("CREATE audit entry for subnets");
+ testNewAuditEntry("dhcp6_subnet",
+ AuditEntry::ModificationType::CREATE,
+ "subnet set", ServerSelector::ALL(), 3);
+ }
+
+ // Deleting non-existing shared network should return 0.
+ EXPECT_EQ(0, cbptr_->deleteSharedNetwork6(ServerSelector::ALL(),
+ "big-fish"));
+ // All shared networks should be still there.
+ ASSERT_EQ(test_networks_.size() - 1, networks.size());
+
+ // Should not delete the shared network for explicit server tag
+ // because our shared network is for all servers.
+ EXPECT_EQ(0, cbptr_->deleteSharedNetwork6(ServerSelector::ONE("server1"),
+ test_networks_[1]->getName()));
+
+ // Same for all shared networks.
+ EXPECT_EQ(0, cbptr_->deleteAllSharedNetworks6(ServerSelector::ONE("server1")));
+
+ // Delete first shared network with it subnets and verify it is gone.
+ // Begin by its subnet.
+ EXPECT_EQ(1, cbptr_->deleteSharedNetworkSubnets6(ServerSelector::ANY(),
+ test_networks_[1]->getName()));
+
+ {
+ SCOPED_TRACE("DELETE audit entry for subnets of the first shared network");
+ testNewAuditEntry("dhcp6_subnet",
+ AuditEntry::ModificationType::DELETE,
+ "deleted all subnets for a shared network");
+ }
+
+ // Check that the subnet is gone..
+ subnet = cbptr_->getSubnet6(ServerSelector::ALL(),
+ test_subnets_[0]->getID());
+ EXPECT_FALSE(subnet);
+
+ // And after the shared network itself.
+ EXPECT_EQ(1, cbptr_->deleteSharedNetwork6(ServerSelector::ALL(),
+ test_networks_[1]->getName()));
+
+ networks = cbptr_->getAllSharedNetworks6(ServerSelector::ALL());
+ ASSERT_EQ(test_networks_.size() - 2, networks.size());
+
+ {
+ SCOPED_TRACE("DELETE audit entry for the first shared network");
+ testNewAuditEntry("dhcp6_shared_network",
+ AuditEntry::ModificationType::DELETE,
+ "shared network deleted");
+ }
+
+ // Delete all.
+ EXPECT_EQ(2, cbptr_->deleteAllSharedNetworks6(ServerSelector::ALL()));
+ networks = cbptr_->getAllSharedNetworks6(ServerSelector::ALL());
+ ASSERT_TRUE(networks.empty());
+
+ {
+ SCOPED_TRACE("DELETE audit entry for the remaining two shared networks");
+ // The last parameter indicates that we expect four new audit entries,
+ // two for deleted shared networks and two for updated subnets
+ std::vector<ExpAuditEntry> exp_entries({
+ {
+ "dhcp6_shared_network",
+ AuditEntry::ModificationType::DELETE, "deleted all shared networks"
+ },
+ {
+ "dhcp6_subnet",
+ AuditEntry::ModificationType::UPDATE, "deleted all shared networks"
+ },
+ {
+ "dhcp6_subnet",
+ AuditEntry::ModificationType::UPDATE, "deleted all shared networks"
+ },
+ {
+ "dhcp6_shared_network",
+ AuditEntry::ModificationType::DELETE, "deleted all shared networks"
+ }
+ });
+
+ testNewAuditEntry(exp_entries, ServerSelector::ALL());
+ }
+
+ // Check that subnets are still there but detached.
+ subnet = cbptr_->getSubnet6(ServerSelector::ALL(),
+ test_subnets_[2]->getID());
+ ASSERT_TRUE(subnet);
+ EXPECT_TRUE(subnet->getSharedNetworkName().empty());
+ subnet = cbptr_->getSubnet6(ServerSelector::ALL(),
+ test_subnets_[3]->getID());
+ ASSERT_TRUE(subnet);
+ EXPECT_TRUE(subnet->getSharedNetworkName().empty());
+}
+
+void
+GenericConfigBackendDHCPv6Test::getAllSharedNetworks6SelectorsTest() {
+ // Supported selectors.
+ ASSERT_NO_THROW_LOG(cbptr_->getAllSharedNetworks6(ServerSelector::UNASSIGNED()));
+ ASSERT_NO_THROW_LOG(cbptr_->getAllSharedNetworks6(ServerSelector::ALL()));
+ ASSERT_NO_THROW_LOG(cbptr_->getAllSharedNetworks6(ServerSelector::ONE("server1")));
+ ASSERT_NO_THROW_LOG(cbptr_->getAllSharedNetworks6(ServerSelector::MULTIPLE({ "server1", "server2" })));
+
+ // Not supported selectors.
+ ASSERT_THROW(cbptr_->getAllSharedNetworks6(ServerSelector::ANY()),
+ isc::InvalidOperation);
+}
+
+void
+GenericConfigBackendDHCPv6Test::getAllSharedNetworks6WithServerTagsTest() {
+ auto shared_network1 = test_networks_[0];
+ auto shared_network2 = test_networks_[2];
+ auto shared_network3 = test_networks_[3];
+
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[0]));
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[2]));
+
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork6(ServerSelector::ALL(),
+ shared_network1));
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork6(ServerSelector::ONE("server1"),
+ shared_network2));
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork6(ServerSelector::MULTIPLE({ "server1", "server2" }),
+ shared_network3));
+
+ SharedNetwork6Collection networks;
+
+ // All three networks are associated with the server1.
+ ASSERT_NO_THROW_LOG(networks = cbptr_->getAllSharedNetworks6(ServerSelector::ONE("server1")));
+ EXPECT_EQ(3, networks.size());
+
+ // First network is associated with all servers.
+ auto returned_network = SharedNetworkFetcher6::get(networks, "level1");
+ ASSERT_TRUE(returned_network);
+ EXPECT_TRUE(returned_network->hasAllServerTag());
+ EXPECT_FALSE(returned_network->hasServerTag(ServerTag("server1")));
+ EXPECT_FALSE(returned_network->hasServerTag(ServerTag("server2")));
+
+ // Second network is only associated with the server1.
+ returned_network = SharedNetworkFetcher6::get(networks, "level2");
+ ASSERT_TRUE(returned_network);
+ EXPECT_FALSE(returned_network->hasAllServerTag());
+ EXPECT_TRUE(returned_network->hasServerTag(ServerTag("server1")));
+ EXPECT_FALSE(returned_network->hasServerTag(ServerTag("server2")));
+
+ // Third network is associated with both server1 and server2.
+ returned_network = SharedNetworkFetcher6::get(networks, "level3");
+ ASSERT_TRUE(returned_network);
+ EXPECT_FALSE(returned_network->hasAllServerTag());
+ EXPECT_TRUE(returned_network->hasServerTag(ServerTag("server1")));
+ EXPECT_TRUE(returned_network->hasServerTag(ServerTag("server2")));
+
+ // For server2 we should only get two shared networks, i.e. first and last.
+ ASSERT_NO_THROW_LOG(networks = cbptr_->getAllSharedNetworks6(ServerSelector::ONE("server2")));
+ EXPECT_EQ(2, networks.size());
+
+ // First shared network is associated with all servers.
+ returned_network = SharedNetworkFetcher6::get(networks, "level1");
+ ASSERT_TRUE(returned_network);
+ EXPECT_TRUE(returned_network->hasAllServerTag());
+ EXPECT_FALSE(returned_network->hasServerTag(ServerTag("server1")));
+ EXPECT_FALSE(returned_network->hasServerTag(ServerTag("server2")));
+
+ // Last shared network is associated with server1 and server2.
+ returned_network = SharedNetworkFetcher6::get(networks, "level3");
+ ASSERT_TRUE(returned_network);
+ EXPECT_FALSE(returned_network->hasAllServerTag());
+ EXPECT_TRUE(returned_network->hasServerTag(ServerTag("server1")));
+ EXPECT_TRUE(returned_network->hasServerTag(ServerTag("server2")));
+
+ // Only the first shared network is associated with all servers.
+ ASSERT_NO_THROW_LOG(networks = cbptr_->getAllSharedNetworks6(ServerSelector::ALL()));
+ EXPECT_EQ(1, networks.size());
+
+ returned_network = SharedNetworkFetcher6::get(networks, "level1");
+ ASSERT_TRUE(returned_network);
+ EXPECT_TRUE(returned_network->hasAllServerTag());
+ EXPECT_FALSE(returned_network->hasServerTag(ServerTag("server1")));
+ EXPECT_FALSE(returned_network->hasServerTag(ServerTag("server2")));
+}
+
+void
+GenericConfigBackendDHCPv6Test::getModifiedSharedNetworks6Test() {
+ // Explicitly set timestamps of shared networks. First shared
+ // network has a timestamp pointing to the future. Second shared
+ // network has timestamp pointing to the past (yesterday).
+ // Third shared network has a timestamp pointing to the
+ // past (an hour ago).
+ test_networks_[1]->setModificationTime(timestamps_["tomorrow"]);
+ test_networks_[2]->setModificationTime(timestamps_["yesterday"]);
+ test_networks_[3]->setModificationTime(timestamps_["today"]);
+
+ // Insert shared networks into the database.
+ for (int i = 1; i < test_networks_.size(); ++i) {
+ cbptr_->createUpdateSharedNetwork6(ServerSelector::ALL(),
+ test_networks_[i]);
+ }
+
+ // Fetch shared networks with timestamp later than today. Only one
+ // shared network should be returned.
+ SharedNetwork6Collection
+ networks = cbptr_->getModifiedSharedNetworks6(ServerSelector::ALL(),
+ timestamps_["after today"]);
+ ASSERT_EQ(1, networks.size());
+
+ // Fetch shared networks with timestamp later than yesterday. We
+ // should get two shared networks.
+ networks = cbptr_->getModifiedSharedNetworks6(ServerSelector::ALL(),
+ timestamps_["after yesterday"]);
+ ASSERT_EQ(2, networks.size());
+
+ // Fetch shared networks with timestamp later than tomorrow. Nothing
+ // should be returned.
+ networks = cbptr_->getModifiedSharedNetworks6(ServerSelector::ALL(),
+ timestamps_["after tomorrow"]);
+ ASSERT_TRUE(networks.empty());
+}
+
+void
+GenericConfigBackendDHCPv6Test::getModifiedSharedNetworks6SelectorsTest() {
+ // Supported selectors.
+ ASSERT_NO_THROW_LOG(cbptr_->getModifiedSharedNetworks6(ServerSelector::UNASSIGNED(),
+ timestamps_["yesterday"]));
+ ASSERT_NO_THROW_LOG(cbptr_->getModifiedSharedNetworks6(ServerSelector::ALL(),
+ timestamps_["yesterday"]));
+ ASSERT_NO_THROW_LOG(cbptr_->getModifiedSharedNetworks6(ServerSelector::ONE("server1"),
+ timestamps_["yesterday"]));
+ ASSERT_NO_THROW_LOG(cbptr_->getModifiedSharedNetworks6(ServerSelector::MULTIPLE({ "server1", "server2" }),
+ timestamps_["yesterday"]));
+
+ // Not supported selectors.
+ ASSERT_THROW(cbptr_->getModifiedSharedNetworks6(ServerSelector::ANY(),
+ timestamps_["yesterday"]),
+ isc::InvalidOperation);
+}
+
+void
+GenericConfigBackendDHCPv6Test::deleteSharedNetwork6Test() {
+ // Create two servers in the database.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[0]));
+ {
+ SCOPED_TRACE("CREATE audit entry for server");
+ testNewAuditEntry("dhcp6_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set");
+ }
+
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[2]));
+ {
+ SCOPED_TRACE("CREATE audit entry for server");
+ testNewAuditEntry("dhcp6_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set");
+ }
+
+ auto shared_network1 = test_networks_[0];
+ auto shared_network2 = test_networks_[2];
+ auto shared_network3 = test_networks_[3];
+
+ // Insert three shared networks, one for all servers, one for server2 and
+ // one for two servers: server1 and server2.
+ ASSERT_NO_THROW_LOG(
+ cbptr_->createUpdateSharedNetwork6(ServerSelector::ALL(), shared_network1)
+ );
+ ASSERT_NO_THROW_LOG(
+ cbptr_->createUpdateSharedNetwork6(ServerSelector::ONE("server2"), shared_network2)
+ );
+ ASSERT_NO_THROW_LOG(
+ cbptr_->createUpdateSharedNetwork6(ServerSelector::MULTIPLE({ "server1", "server2" }),
+ shared_network3)
+ );
+
+ auto test_no_delete = [this] (const std::string& test_case_name,
+ const ServerSelector& server_selector,
+ const SharedNetwork6Ptr& shared_network) {
+ SCOPED_TRACE(test_case_name);
+ uint64_t deleted_count = 0;
+ ASSERT_NO_THROW_LOG(
+ deleted_count = cbptr_->deleteSharedNetwork6(server_selector,
+ shared_network->getName())
+ );
+ EXPECT_EQ(0, deleted_count);
+ };
+
+ {
+ SCOPED_TRACE("Test valid but non matching server selectors");
+ test_no_delete("selector: one, actual: all", ServerSelector::ONE("server2"),
+ shared_network1);
+ test_no_delete("selector: all, actual: one", ServerSelector::ALL(),
+ shared_network2);
+ test_no_delete("selector: all, actual: multiple", ServerSelector::ALL(),
+ shared_network3);
+ }
+
+ // We are not going to support deletion of a single entry for multiple servers.
+ ASSERT_THROW(cbptr_->deleteSharedNetwork6(ServerSelector::MULTIPLE({ "server1", "server2" }),
+ shared_network3->getName()),
+ isc::InvalidOperation);
+
+ // We currently don't support deleting a shared network with specifying
+ // an unassigned server tag. Use ANY to delete any subnet instead.
+ ASSERT_THROW(cbptr_->deleteSharedNetwork6(ServerSelector::UNASSIGNED(),
+ shared_network1->getName()),
+ isc::NotImplemented);
+
+ // Test successful deletion of a shared network.
+ auto test_delete = [this] (const std::string& test_case_name,
+ const ServerSelector& server_selector,
+ const SharedNetwork6Ptr& shared_network) {
+ SCOPED_TRACE(test_case_name);
+ uint64_t deleted_count = 0;
+ ASSERT_NO_THROW_LOG(
+ deleted_count = cbptr_->deleteSharedNetwork6(server_selector,
+ shared_network->getName())
+ );
+ EXPECT_EQ(1, deleted_count);
+
+ EXPECT_FALSE(cbptr_->getSharedNetwork6(server_selector,
+ shared_network->getName()));
+ };
+
+ test_delete("all servers", ServerSelector::ALL(), shared_network1);
+ test_delete("any server", ServerSelector::ANY(), shared_network2);
+ test_delete("one server", ServerSelector::ONE("server1"), shared_network3);
+}
+
+void
+GenericConfigBackendDHCPv6Test::deleteSharedNetwork6SelectorsTest() {
+ // Supported selectors.
+ ASSERT_NO_THROW_LOG(cbptr_->deleteSharedNetwork6(ServerSelector::ANY(), "level1"));
+ ASSERT_NO_THROW_LOG(cbptr_->deleteSharedNetwork6(ServerSelector::ALL(), "level1"));
+ ASSERT_NO_THROW_LOG(cbptr_->deleteSharedNetwork6(ServerSelector::ONE("server1"), "level1"));
+
+ // Not supported selectors.
+ ASSERT_THROW(cbptr_->deleteSharedNetwork6(ServerSelector::MULTIPLE({ "server1", "server2" }),
+ "level1"),
+ isc::InvalidOperation);
+
+ // Not implemented selectors.
+ ASSERT_THROW(cbptr_->deleteSharedNetwork6(ServerSelector::UNASSIGNED(), "level1"),
+ isc::NotImplemented);
+}
+
+void
+GenericConfigBackendDHCPv6Test::deleteAllSharedNetworks6SelectorsTest() {
+ // Supported selectors.
+ ASSERT_NO_THROW_LOG(cbptr_->deleteAllSharedNetworks6(ServerSelector::UNASSIGNED()));
+ ASSERT_NO_THROW_LOG(cbptr_->deleteAllSharedNetworks6(ServerSelector::ALL()));
+ ASSERT_NO_THROW_LOG(cbptr_->deleteAllSharedNetworks6(ServerSelector::ONE("server1")));
+
+ // Not supported selectors.
+ ASSERT_THROW(cbptr_->deleteAllSharedNetworks6(ServerSelector::ANY()),
+ isc::InvalidOperation);
+ ASSERT_THROW(cbptr_->deleteAllSharedNetworks6(ServerSelector::MULTIPLE({ "server1", "server2" })),
+ isc::InvalidOperation);
+}
+
+void
+GenericConfigBackendDHCPv6Test::unassignedSharedNetworkTest() {
+ // Create the server.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[0]));
+
+ // Create the shared networks and associate them with the server1.
+ auto shared_network = test_networks_[0];
+ auto shared_network2 = test_networks_[2];
+ ASSERT_NO_THROW_LOG(
+ cbptr_->createUpdateSharedNetwork6(ServerSelector::ONE("server1"), shared_network)
+ );
+ ASSERT_NO_THROW_LOG(
+ cbptr_->createUpdateSharedNetwork6(ServerSelector::ONE("server1"), shared_network2)
+ );
+
+ // Delete the server. The shared networks should be preserved but are
+ // considered orphaned, i.e. do not belong to any server.
+ uint64_t deleted_count = 0;
+ ASSERT_NO_THROW_LOG(deleted_count = cbptr_->deleteServer6(ServerTag("server1")));
+ EXPECT_EQ(1, deleted_count);
+
+ // Trying to fetch this shared network by server tag should return no result.
+ SharedNetwork6Ptr returned_network;
+ ASSERT_NO_THROW_LOG(returned_network = cbptr_->getSharedNetwork6(ServerSelector::ONE("server1"),
+ "level1"));
+ EXPECT_FALSE(returned_network);
+
+ // The same if we use other calls.
+ SharedNetwork6Collection returned_networks;
+ ASSERT_NO_THROW_LOG(
+ returned_networks = cbptr_->getAllSharedNetworks6(ServerSelector::ONE("server1"))
+ );
+ EXPECT_TRUE(returned_networks.empty());
+
+ ASSERT_NO_THROW_LOG(
+ returned_networks = cbptr_->getModifiedSharedNetworks6(ServerSelector::ONE("server1"),
+ timestamps_["two days ago"])
+ );
+ EXPECT_TRUE(returned_networks.empty());
+
+ // We should get the shared network if we ask for unassigned.
+ ASSERT_NO_THROW_LOG(returned_network = cbptr_->getSharedNetwork6(ServerSelector::UNASSIGNED(),
+ "level1"));
+ ASSERT_TRUE(returned_network);
+
+ // Also if we ask for all unassigned networks it should be returned.
+ ASSERT_NO_THROW_LOG(returned_networks = cbptr_->getAllSharedNetworks6(ServerSelector::UNASSIGNED()));
+ ASSERT_EQ(2, returned_networks.size());
+
+ // And all modified.
+ ASSERT_NO_THROW_LOG(
+ returned_networks = cbptr_->getModifiedSharedNetworks6(ServerSelector::UNASSIGNED(),
+ timestamps_["two days ago"])
+ );
+ ASSERT_EQ(2, returned_networks.size());
+
+ // If we ask for any network by name, it should be returned too.
+ ASSERT_NO_THROW_LOG(returned_network = cbptr_->getSharedNetwork6(ServerSelector::ANY(),
+ "level1"));
+ ASSERT_TRUE(returned_network);
+
+ // Deleting a shared network with the mismatched server tag should not affect
+ // our shared network.
+ ASSERT_NO_THROW_LOG(
+ deleted_count = cbptr_->deleteSharedNetwork6(ServerSelector::ONE("server1"),
+ "level1")
+ );
+ EXPECT_EQ(0, deleted_count);
+
+ // Also, if we delete all shared networks for server1.
+ ASSERT_NO_THROW_LOG(
+ deleted_count = cbptr_->deleteAllSharedNetworks6(ServerSelector::ONE("server1"))
+ );
+ EXPECT_EQ(0, deleted_count);
+
+ // We can delete this shared network when we specify ANY and the matching name.
+ ASSERT_NO_THROW_LOG(
+ deleted_count = cbptr_->deleteSharedNetwork6(ServerSelector::ANY(), "level1")
+ );
+ EXPECT_EQ(1, deleted_count);
+
+ // We can delete all networks using UNASSIGNED selector.
+ ASSERT_NO_THROW_LOG(
+ deleted_count = cbptr_->deleteAllSharedNetworks6(ServerSelector::UNASSIGNED());
+ );
+ EXPECT_EQ(1, deleted_count);
+}
+
+void
+GenericConfigBackendDHCPv6Test::sharedNetworkLifetimeTest() {
+ // Insert new shared network with unspecified valid lifetime
+ SharedNetwork6Ptr network(new SharedNetwork6("foo"));
+ Triplet<uint32_t> unspecified;
+ network->setPreferred(unspecified);
+ network->setValid(unspecified);
+ network->setIface("eth1");
+ cbptr_->createUpdateSharedNetwork6(ServerSelector::ALL(), network);
+
+ // Fetch this shared network.
+ SharedNetwork6Ptr returned_network =
+ cbptr_->getSharedNetwork6(ServerSelector::ALL(), "foo");
+ ASSERT_TRUE(returned_network);
+
+ // Verified returned and original shared networks match.
+ EXPECT_EQ(network->toElement()->str(),
+ returned_network->toElement()->str());
+
+ // Update the preferred and valid lifetime.
+ network->setPreferred( Triplet<uint32_t>(100, 200, 300));
+ network->setValid( Triplet<uint32_t>(200, 300, 400));
+ cbptr_->createUpdateSharedNetwork6(ServerSelector::ALL(), network);
+
+ // Fetch and verify again.
+ returned_network = cbptr_->getSharedNetwork6(ServerSelector::ALL(), "foo");
+ ASSERT_TRUE(returned_network);
+ EXPECT_EQ(network->toElement()->str(),
+ returned_network->toElement()->str());
+}
+
+void
+GenericConfigBackendDHCPv6Test::sharedNetworkOptionsTest() {
+ // Add shared network with three options.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork6(ServerSelector::ALL(), test_networks_[0]));
+ EXPECT_EQ(3, countRows("dhcp6_options"));
+
+ // Add another shared network with a single option. The numnber of options in the
+ // database should now be 4.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork6(ServerSelector::ALL(), test_networks_[2]));
+ EXPECT_EQ(4, countRows("dhcp6_options"));
+
+ // The second shared network uses the same name as the first shared network, so
+ // this operation should replace the existing shared network and its options.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork6(ServerSelector::ALL(), test_networks_[1]));
+ EXPECT_EQ(1, countRows("dhcp6_options"));
+
+ // Remove the shared network. This should not affect options assigned to the
+ // other shared network.
+ ASSERT_NO_THROW_LOG(cbptr_->deleteSharedNetwork6(ServerSelector::ALL(),
+ test_networks_[1]->getName()));
+ EXPECT_EQ(1, countRows("dhcp6_shared_network"));
+ EXPECT_EQ(1, countRows("dhcp6_options"));
+
+ // Create the first option again. The number of options should be equal to the
+ // sum of options associated with both shared networks.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork6(ServerSelector::ALL(), test_networks_[0]));
+ EXPECT_EQ(4, countRows("dhcp6_options"));
+
+ // Delete this shared network. This should not affect the option associated
+ // with the remaining shared network.
+ ASSERT_NO_THROW_LOG(cbptr_->deleteSharedNetwork6(ServerSelector::ALL(),
+ test_networks_[0]->getName()));
+ EXPECT_EQ(1, countRows("dhcp6_shared_network"));
+ EXPECT_EQ(1, countRows("dhcp6_options"));
+}
+
+void
+GenericConfigBackendDHCPv6Test::getOptionDef6Test() {
+ // Insert new option definition.
+ OptionDefinitionPtr option_def = test_option_defs_[0];
+ cbptr_->createUpdateOptionDef6(ServerSelector::ALL(), option_def);
+
+ // Fetch this option_definition by subnet identifier.
+ OptionDefinitionPtr returned_option_def =
+ cbptr_->getOptionDef6(ServerSelector::ALL(),
+ test_option_defs_[0]->getCode(),
+ test_option_defs_[0]->getOptionSpaceName());
+ ASSERT_TRUE(returned_option_def);
+ EXPECT_GT(returned_option_def->getId(), 0);
+ ASSERT_EQ(1, returned_option_def->getServerTags().size());
+ EXPECT_EQ("all", returned_option_def->getServerTags().begin()->get());
+
+ EXPECT_TRUE(returned_option_def->equals(*option_def));
+
+ {
+ SCOPED_TRACE("CREATE audit entry for an option definition");
+ testNewAuditEntry("dhcp6_option_def",
+ AuditEntry::ModificationType::CREATE,
+ "option definition set");
+ }
+
+ // Update the option definition in the database.
+ OptionDefinitionPtr option_def2 = test_option_defs_[1];
+ cbptr_->createUpdateOptionDef6(ServerSelector::ALL(), option_def2);
+
+ // Fetch updated option definition and see if it matches.
+ returned_option_def = cbptr_->getOptionDef6(ServerSelector::ALL(),
+ test_option_defs_[1]->getCode(),
+ test_option_defs_[1]->getOptionSpaceName());
+ EXPECT_TRUE(returned_option_def->equals(*option_def2));
+
+ // Fetching option definition for an explicitly specified server tag
+ // should succeed too.
+ returned_option_def = cbptr_->getOptionDef6(ServerSelector::ONE("server1"),
+ test_option_defs_[1]->getCode(),
+ test_option_defs_[1]->getOptionSpaceName());
+ EXPECT_TRUE(returned_option_def->equals(*option_def2));
+
+ {
+ SCOPED_TRACE("UPDATE audit entry for an option definition");
+ testNewAuditEntry("dhcp6_option_def",
+ AuditEntry::ModificationType::UPDATE,
+ "option definition set");
+ }
+}
+
+void
+GenericConfigBackendDHCPv6Test::optionDefs6WithServerTagsTest() {
+ OptionDefinitionPtr option1 = test_option_defs_[0];
+ OptionDefinitionPtr option2 = test_option_defs_[1];
+ OptionDefinitionPtr option3 = test_option_defs_[4];
+
+ // An attempt to create option definition for non-existing server should
+ // fail.
+ ASSERT_THROW(cbptr_->createUpdateOptionDef6(ServerSelector::ONE("server1"),
+ option1),
+ NullKeyError);
+
+ // Create two servers.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[1]));
+ {
+ SCOPED_TRACE("server1 is created");
+ testNewAuditEntry("dhcp6_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set");
+ }
+
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[2]));
+ {
+ SCOPED_TRACE("server2 is created");
+ testNewAuditEntry("dhcp6_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set");
+ }
+
+ // This time creation of the option definition for the server1 should pass.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateOptionDef6(ServerSelector::ONE("server1"),
+ option1));
+ {
+ SCOPED_TRACE("option definition for server1 is set");
+ // The value of 3 means there should be 3 audit entries available for the
+ // server1, two that indicate creation of the servers and one that we
+ // validate, which sets the option definition.
+ testNewAuditEntry("dhcp6_option_def",
+ AuditEntry::ModificationType::CREATE,
+ "option definition set",
+ ServerSelector::ONE("server1"),
+ 3, 1);
+ }
+
+ // Creation of the option definition for the server2 should also pass.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateOptionDef6(ServerSelector::ONE("server2"),
+ option2));
+ {
+ SCOPED_TRACE("option definition for server2 is set");
+ // Same as in case of the server1, there should be 3 audit entries and
+ // we validate one of them.
+ testNewAuditEntry("dhcp6_option_def",
+ AuditEntry::ModificationType::CREATE,
+ "option definition set",
+ ServerSelector::ONE("server2"),
+ 3, 1);
+ }
+
+ // Finally, creation of the option definition for all servers should
+ // also pass.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateOptionDef6(ServerSelector::ALL(),
+ option3));
+ {
+ SCOPED_TRACE("option definition for server2 is set");
+ // There should be one new audit entry for all servers. It logs
+ // the insertion of the option definition.
+ testNewAuditEntry("dhcp6_option_def",
+ AuditEntry::ModificationType::CREATE,
+ "option definition set",
+ ServerSelector::ALL(),
+ 1, 1);
+ }
+
+ OptionDefinitionPtr returned_option_def;
+
+ // Try to fetch the option definition specified for all servers. It should
+ // return the third one.
+ ASSERT_NO_THROW_LOG(
+ returned_option_def = cbptr_->getOptionDef6(ServerSelector::ALL(),
+ option3->getCode(),
+ option3->getOptionSpaceName())
+ );
+ ASSERT_TRUE(returned_option_def);
+ EXPECT_TRUE(returned_option_def->equals(*option3));
+
+ // Try to fetch the option definition specified for server1. It should
+ // override the definition for all servers.
+ ASSERT_NO_THROW_LOG(
+ returned_option_def = cbptr_->getOptionDef6(ServerSelector::ONE("server1"),
+ option1->getCode(),
+ option1->getOptionSpaceName())
+ );
+ ASSERT_TRUE(returned_option_def);
+ EXPECT_TRUE(returned_option_def->equals(*option1));
+
+ // The same in case of the server2.
+ ASSERT_NO_THROW_LOG(
+ returned_option_def = cbptr_->getOptionDef6(ServerSelector::ONE("server2"),
+ option2->getCode(),
+ option2->getOptionSpaceName())
+ );
+ ASSERT_TRUE(returned_option_def);
+ EXPECT_TRUE(returned_option_def->equals(*option2));
+
+ OptionDefContainer returned_option_defs;
+
+ // Try to fetch the collection of the option definitions for server1, server2
+ // and server3. The server3 does not have an explicit option definition, so
+ // for this server we should get the definition associated with "all" servers.
+ ASSERT_NO_THROW_LOG(
+ returned_option_defs = cbptr_->getAllOptionDefs6(ServerSelector::
+ MULTIPLE({ "server1", "server2",
+ "server3" }));
+ );
+ ASSERT_EQ(3, returned_option_defs.size());
+
+ // Check that expected option definitions have been returned.
+ auto current_option = returned_option_defs.begin();
+ EXPECT_TRUE((*current_option)->equals(*option1));
+ EXPECT_TRUE((*(++current_option))->equals(*option2));
+ EXPECT_TRUE((*(++current_option))->equals(*option3));
+
+ // Try to fetch the collection of options specified for all servers.
+ // This excludes the options specific to server1 and server2. It returns
+ // only the common ones.
+ ASSERT_NO_THROW_LOG(
+ returned_option_defs = cbptr_->getAllOptionDefs6(ServerSelector::ALL());
+
+ );
+ ASSERT_EQ(1, returned_option_defs.size());
+ EXPECT_TRUE((*returned_option_defs.begin())->equals(*option3));
+
+ // Delete the server1. It should remove associations of this server with the
+ // option definitions and the option definition itself.
+ ASSERT_NO_THROW_LOG(cbptr_->deleteServer6(ServerTag("server1")));
+ ASSERT_NO_THROW_LOG(
+ returned_option_defs = cbptr_->getAllOptionDefs6(ServerSelector::ONE("server1"));
+
+ );
+ ASSERT_EQ(1, returned_option_defs.size());
+ EXPECT_TRUE((*returned_option_defs.begin())->equals(*option3));
+
+ {
+ SCOPED_TRACE("DELETE audit entry for the option definition after server deletion");
+ testNewAuditEntry("dhcp6_option_def",
+ AuditEntry::ModificationType::DELETE,
+ "deleting a server", ServerSelector::ONE("server1"),
+ 2, 1);
+ }
+
+ // Attempt to delete option definition for server1.
+ uint64_t deleted_num = 0;
+ ASSERT_NO_THROW_LOG(deleted_num = cbptr_->deleteOptionDef6(ServerSelector::ONE("server1"),
+ option1->getCode(),
+ option1->getOptionSpaceName()));
+ EXPECT_EQ(0, deleted_num);
+
+ // Deleting the existing option definition for server2 should succeed.
+ ASSERT_NO_THROW_LOG(deleted_num = cbptr_->deleteOptionDef6(ServerSelector::ONE("server2"),
+ option2->getCode(),
+ option2->getOptionSpaceName()));
+ EXPECT_EQ(1, deleted_num);
+
+ // Create this option definition again to test that deletion of all servers
+ // removes it too.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateOptionDef6(ServerSelector::ONE("server2"),
+ option2));
+
+ // Delete all servers, except 'all'.
+ ASSERT_NO_THROW_LOG(deleted_num = cbptr_->deleteAllServers6());
+ ASSERT_NO_THROW_LOG(
+ returned_option_defs = cbptr_->getAllOptionDefs6(ServerSelector::ALL());
+ );
+ EXPECT_EQ(1, deleted_num);
+ EXPECT_EQ(1, returned_option_defs.size());
+ EXPECT_TRUE((*returned_option_defs.begin())->equals(*option3));
+
+ {
+ SCOPED_TRACE("DELETE audit entry for the option definition after deletion of"
+ " all servers");
+ testNewAuditEntry("dhcp6_option_def",
+ AuditEntry::ModificationType::DELETE,
+ "deleting all servers", ServerSelector::ONE("server2"),
+ 4, 1);
+ }
+}
+
+void
+GenericConfigBackendDHCPv6Test::getAllOptionDefs6Test() {
+ // Insert test option definitions into the database. Note that the second
+ // option definition will overwrite the first option definition as they use
+ // the same code and space.
+ size_t updates_num = 0;
+ for (auto option_def : test_option_defs_) {
+ cbptr_->createUpdateOptionDef6(ServerSelector::ALL(), option_def);
+
+ // That option definition overrides the first one so the audit entry should
+ // indicate an update.
+ auto name = option_def->getName();
+ if (name.find("bar") != std::string::npos) {
+ SCOPED_TRACE("UPDATE audit entry for the option definition " + name);
+ testNewAuditEntry("dhcp6_option_def",
+ AuditEntry::ModificationType::UPDATE,
+ "option definition set");
+ ++updates_num;
+
+ } else {
+ SCOPED_TRACE("CREATE audit entry for the option definition " + name);
+ testNewAuditEntry("dhcp6_option_def",
+ AuditEntry::ModificationType::CREATE,
+ "option definition set");
+ }
+ }
+
+ // Fetch all option_definitions.
+ OptionDefContainer option_defs = cbptr_->getAllOptionDefs6(ServerSelector::ALL());
+ ASSERT_EQ(test_option_defs_.size() - updates_num, option_defs.size());
+
+ // All option definitions should also be returned for explicitly specified
+ // server tag.
+ option_defs = cbptr_->getAllOptionDefs6(ServerSelector::ONE("server1"));
+ ASSERT_EQ(test_option_defs_.size() - updates_num, option_defs.size());
+
+ // See if option definitions are returned ok.
+ for (auto def = option_defs.begin(); def != option_defs.end(); ++def) {
+ ASSERT_EQ(1, (*def)->getServerTags().size());
+ EXPECT_EQ("all", (*def)->getServerTags().begin()->get());
+ bool success = false;
+ for (auto i = 1; i < test_option_defs_.size(); ++i) {
+ if ((*def)->equals(*test_option_defs_[i])) {
+ success = true;
+ }
+ }
+ ASSERT_TRUE(success) << "failed for option definition " << (*def)->getCode()
+ << ", option space " << (*def)->getOptionSpaceName();
+ }
+
+ // Deleting non-existing option definition should return 0.
+ EXPECT_EQ(0, cbptr_->deleteOptionDef6(ServerSelector::ALL(),
+ 99, "non-exiting-space"));
+ // All option definitions should be still there.
+ ASSERT_EQ(test_option_defs_.size() - updates_num, option_defs.size());
+
+ // Should not delete option definition for explicit server tag
+ // because our option definition is for all servers.
+ EXPECT_EQ(0, cbptr_->deleteOptionDef6(ServerSelector::ONE("server1"),
+ test_option_defs_[1]->getCode(),
+ test_option_defs_[1]->getOptionSpaceName()));
+
+ // Same for all option definitions.
+ EXPECT_EQ(0, cbptr_->deleteAllOptionDefs6(ServerSelector::ONE("server1")));
+
+ // Delete one of the option definitions and see if it is gone.
+ EXPECT_EQ(1, cbptr_->deleteOptionDef6(ServerSelector::ALL(),
+ test_option_defs_[2]->getCode(),
+ test_option_defs_[2]->getOptionSpaceName()));
+ ASSERT_FALSE(cbptr_->getOptionDef6(ServerSelector::ALL(),
+ test_option_defs_[2]->getCode(),
+ test_option_defs_[2]->getOptionSpaceName()));
+
+ {
+ SCOPED_TRACE("DELETE audit entry for the first option definition");
+ testNewAuditEntry("dhcp6_option_def",
+ AuditEntry::ModificationType::DELETE,
+ "option definition deleted");
+ }
+
+ // Delete all remaining option definitions.
+ EXPECT_EQ(2, cbptr_->deleteAllOptionDefs6(ServerSelector::ALL()));
+ option_defs = cbptr_->getAllOptionDefs6(ServerSelector::ALL());
+ ASSERT_TRUE(option_defs.empty());
+
+ {
+ SCOPED_TRACE("DELETE audit entries for the remaining option definitions");
+ // The last parameter indicates that we expect two new audit entries.
+ testNewAuditEntry("dhcp6_option_def",
+ AuditEntry::ModificationType::DELETE,
+ "deleted all option definitions",
+ ServerSelector::ALL(), 2);
+ }
+}
+
+void
+GenericConfigBackendDHCPv6Test::getModifiedOptionDefs6Test() {
+ // Explicitly set timestamps of option definitions. First option
+ // definition has a timestamp pointing to the future. Second option
+ // definition has timestamp pointing to the past (yesterday).
+ // Third option definitions has a timestamp pointing to the
+ // past (an hour ago).
+ test_option_defs_[1]->setModificationTime(timestamps_["tomorrow"]);
+ test_option_defs_[2]->setModificationTime(timestamps_["yesterday"]);
+ test_option_defs_[3]->setModificationTime(timestamps_["today"]);
+
+ // Insert option definitions into the database.
+ for (int i = 1; i < test_networks_.size(); ++i) {
+ cbptr_->createUpdateOptionDef6(ServerSelector::ALL(),
+ test_option_defs_[i]);
+ }
+
+ // Fetch option definitions with timestamp later than today. Only one
+ // option definition should be returned.
+ OptionDefContainer
+ option_defs = cbptr_->getModifiedOptionDefs6(ServerSelector::ALL(),
+ timestamps_["after today"]);
+ ASSERT_EQ(1, option_defs.size());
+
+ // Fetch option definitions with timestamp later than yesterday. We
+ // should get two option definitions.
+ option_defs = cbptr_->getModifiedOptionDefs6(ServerSelector::ALL(),
+ timestamps_["after yesterday"]);
+ ASSERT_EQ(2, option_defs.size());
+
+ // Fetch option definitions with timestamp later than tomorrow. Nothing
+ // should be returned.
+ option_defs = cbptr_->getModifiedOptionDefs6(ServerSelector::ALL(),
+ timestamps_["after tomorrow"]);
+ ASSERT_TRUE(option_defs.empty());
+}
+
+void
+GenericConfigBackendDHCPv6Test::createUpdateDeleteOption6Test() {
+ // Add option to the database.
+ OptionDescriptorPtr opt_posix_timezone = test_options_[0];
+ cbptr_->createUpdateOption6(ServerSelector::ALL(),
+ opt_posix_timezone);
+
+ // Make sure we can retrieve this option and that it is equal to the
+ // option we have inserted into the database.
+ OptionDescriptorPtr returned_opt_posix_timezone =
+ cbptr_->getOption6(ServerSelector::ALL(),
+ opt_posix_timezone->option_->getType(),
+ opt_posix_timezone->space_name_);
+ ASSERT_TRUE(returned_opt_posix_timezone);
+
+ {
+ SCOPED_TRACE("verify created option");
+ testOptionsEquivalent(*opt_posix_timezone,
+ *returned_opt_posix_timezone);
+ }
+
+ {
+ SCOPED_TRACE("CREATE audit entry for an option");
+ testNewAuditEntry("dhcp6_options",
+ AuditEntry::ModificationType::CREATE,
+ "global option set");
+ }
+
+ // Modify option and update it in the database.
+ opt_posix_timezone->persistent_ = !opt_posix_timezone->persistent_;
+ cbptr_->createUpdateOption6(ServerSelector::ALL(),
+ opt_posix_timezone);
+
+ // Retrieve the option again and make sure that updates were
+ // properly propagated to the database. Use explicit server selector
+ // which should also return this option.
+ returned_opt_posix_timezone = cbptr_->getOption6(ServerSelector::ONE("server1"),
+ opt_posix_timezone->option_->getType(),
+ opt_posix_timezone->space_name_);
+ ASSERT_TRUE(returned_opt_posix_timezone);
+
+ {
+ SCOPED_TRACE("verify updated option");
+ testOptionsEquivalent(*opt_posix_timezone,
+ *returned_opt_posix_timezone);
+ }
+
+ {
+ SCOPED_TRACE("UPDATE audit entry for an option");
+ testNewAuditEntry("dhcp6_options",
+ AuditEntry::ModificationType::UPDATE,
+ "global option set");
+ }
+
+ // Deleting an option with explicitly specified server tag should fail.
+ EXPECT_EQ(0, cbptr_->deleteOption6(ServerSelector::ONE("server1"),
+ opt_posix_timezone->option_->getType(),
+ opt_posix_timezone->space_name_));
+
+ // Deleting option for all servers should succeed.
+ EXPECT_EQ(1, cbptr_->deleteOption6(ServerSelector::ALL(),
+ opt_posix_timezone->option_->getType(),
+ opt_posix_timezone->space_name_));
+
+ EXPECT_FALSE(cbptr_->getOption6(ServerSelector::ALL(),
+ opt_posix_timezone->option_->getType(),
+ opt_posix_timezone->space_name_));
+
+ {
+ SCOPED_TRACE("DELETE audit entry for an option");
+ testNewAuditEntry("dhcp6_options",
+ AuditEntry::ModificationType::DELETE,
+ "global option deleted");
+ }
+}
+
+void
+GenericConfigBackendDHCPv6Test::globalOptions6WithServerTagsTest() {
+ OptionDescriptorPtr opt_timezone1 = test_options_[0];
+ OptionDescriptorPtr opt_timezone2 = test_options_[6];
+ OptionDescriptorPtr opt_timezone3 = test_options_[7];
+
+ ASSERT_THROW(cbptr_->createUpdateOption6(ServerSelector::ONE("server1"),
+ opt_timezone1),
+ NullKeyError);
+
+ // Create two servers.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[1]));
+ {
+ SCOPED_TRACE("server1 is created");
+ testNewAuditEntry("dhcp6_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set");
+ }
+
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[2]));
+ {
+ SCOPED_TRACE("server2 is created");
+ testNewAuditEntry("dhcp6_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set");
+ }
+
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateOption6(ServerSelector::ONE("server1"),
+ opt_timezone1));
+ {
+ SCOPED_TRACE("global option for server1 is set");
+ // The value of 3 means there should be 3 audit entries available for the
+ // server1, two that indicate creation of the servers and one that we
+ // validate, which sets the global option.
+ testNewAuditEntry("dhcp6_options",
+ AuditEntry::ModificationType::CREATE,
+ "global option set",
+ ServerSelector::ONE("server1"),
+ 3, 1);
+
+ }
+
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateOption6(ServerSelector::ONE("server2"),
+ opt_timezone2));
+ {
+ SCOPED_TRACE("global option for server2 is set");
+ // Same as in case of the server1, there should be 3 audit entries and
+ // we validate one of them.
+ testNewAuditEntry("dhcp6_options",
+ AuditEntry::ModificationType::CREATE,
+ "global option set",
+ ServerSelector::ONE("server2"),
+ 3, 1);
+
+ }
+
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateOption6(ServerSelector::ALL(),
+ opt_timezone3));
+ {
+ SCOPED_TRACE("global option for all servers is set");
+ // There should be one new audit entry for all servers. It logs
+ // the insertion of the global option.
+ testNewAuditEntry("dhcp6_options",
+ AuditEntry::ModificationType::CREATE,
+ "global option set",
+ ServerSelector::ALL(),
+ 1, 1);
+
+ }
+
+ OptionDescriptorPtr returned_option;
+
+ // Try to fetch the option specified for all servers. It should return
+ // the third option.
+ ASSERT_NO_THROW_LOG(
+ returned_option = cbptr_->getOption6(ServerSelector::ALL(),
+ opt_timezone3->option_->getType(),
+ opt_timezone3->space_name_);
+ );
+ ASSERT_TRUE(returned_option);
+ testOptionsEquivalent(*opt_timezone3, *returned_option);
+
+ // Try to fetch the option specified for the server1. It should override the
+ // option specified for all servers.
+ ASSERT_NO_THROW_LOG(
+ returned_option = cbptr_->getOption6(ServerSelector::ONE("server1"),
+ opt_timezone1->option_->getType(),
+ opt_timezone1->space_name_);
+ );
+ ASSERT_TRUE(returned_option);
+ testOptionsEquivalent(*opt_timezone1, *returned_option);
+
+ // The same in case of the server2.
+ ASSERT_NO_THROW_LOG(
+ returned_option = cbptr_->getOption6(ServerSelector::ONE("server2"),
+ opt_timezone2->option_->getType(),
+ opt_timezone2->space_name_);
+ );
+ ASSERT_TRUE(returned_option);
+ testOptionsEquivalent(*opt_timezone2, *returned_option);
+
+ OptionContainer returned_options;
+
+ // Try to fetch the collection of global options for the server1, server2
+ // and server3. The server3 does not have an explicit value so for this server
+ // we should get the option associated with "all" servers.
+ ASSERT_NO_THROW_LOG(
+ returned_options = cbptr_->getAllOptions6(ServerSelector::
+ MULTIPLE({ "server1", "server2",
+ "server3" }));
+ );
+ ASSERT_EQ(3, returned_options.size());
+
+ // Check that expected options have been returned.
+ auto current_option = returned_options.begin();
+ testOptionsEquivalent(*opt_timezone1, *current_option);
+ testOptionsEquivalent(*opt_timezone2, *(++current_option));
+ testOptionsEquivalent(*opt_timezone3, *(++current_option));
+
+ // Try to fetch the collection of options specified for all servers.
+ // This excludes the options specific to server1 and server2. It returns
+ // only the common ones.
+ ASSERT_NO_THROW_LOG(
+ returned_options = cbptr_->getAllOptions6(ServerSelector::ALL());
+ );
+ ASSERT_EQ(1, returned_options.size());
+ testOptionsEquivalent(*opt_timezone3, *returned_options.begin());
+
+ // Delete the server1. It should remove associations of this server with the
+ // option and the option itself.
+ ASSERT_NO_THROW_LOG(cbptr_->deleteServer6(ServerTag("server1")));
+ ASSERT_NO_THROW_LOG(
+ returned_options = cbptr_->getAllOptions6(ServerSelector::ONE("server1"));
+ );
+ ASSERT_EQ(1, returned_options.size());
+ testOptionsEquivalent(*opt_timezone3, *returned_options.begin());
+
+ {
+ SCOPED_TRACE("DELETE audit entry for the global option after server deletion");
+ testNewAuditEntry("dhcp6_options",
+ AuditEntry::ModificationType::DELETE,
+ "deleting a server", ServerSelector::ONE("server1"),
+ 2, 1);
+ }
+
+ // Attempt to delete global option for server1.
+ uint64_t deleted_num = 0;
+ ASSERT_NO_THROW_LOG(deleted_num = cbptr_->deleteOption6(ServerSelector::ONE("server1"),
+ opt_timezone1->option_->getType(),
+ opt_timezone1->space_name_));
+ EXPECT_EQ(0, deleted_num);
+
+ // Deleting the existing option for server2 should succeed.
+ ASSERT_NO_THROW_LOG(deleted_num = cbptr_->deleteOption6(ServerSelector::ONE("server2"),
+ opt_timezone2->option_->getType(),
+ opt_timezone2->space_name_));
+ EXPECT_EQ(1, deleted_num);
+
+ // Create this option again to test that deletion of all servers removes it too.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateOption6(ServerSelector::ONE("server2"),
+ opt_timezone2));
+
+ // Delete all servers, except 'all'.
+ ASSERT_NO_THROW_LOG(deleted_num = cbptr_->deleteAllServers6());
+ ASSERT_NO_THROW_LOG(
+ returned_options = cbptr_->getAllOptions6(ServerSelector::ALL());
+ );
+ EXPECT_EQ(1, deleted_num);
+ ASSERT_EQ(1, returned_options.size());
+ testOptionsEquivalent(*opt_timezone3, *returned_options.begin());
+
+ {
+ SCOPED_TRACE("DELETE audit entry for the global option after deletion of"
+ " all servers");
+ testNewAuditEntry("dhcp6_options",
+ AuditEntry::ModificationType::DELETE,
+ "deleting all servers", ServerSelector::ONE("server2"),
+ 4, 1);
+ }
+}
+
+void
+GenericConfigBackendDHCPv6Test::getAllOptions6Test() {
+ // Add three global options to the database.
+ cbptr_->createUpdateOption6(ServerSelector::ALL(),
+ test_options_[0]);
+ cbptr_->createUpdateOption6(ServerSelector::ALL(),
+ test_options_[1]);
+ cbptr_->createUpdateOption6(ServerSelector::ALL(),
+ test_options_[5]);
+
+ // Retrieve all these options.
+ OptionContainer returned_options = cbptr_->getAllOptions6(ServerSelector::ALL());
+ ASSERT_EQ(3, returned_options.size());
+
+ // Fetching global options with explicitly specified server tag should return
+ // the same result.
+ returned_options = cbptr_->getAllOptions6(ServerSelector::ONE("server1"));
+ ASSERT_EQ(3, returned_options.size());
+
+ // Get the container index used to search options by option code.
+ const OptionContainerTypeIndex& index = returned_options.get<1>();
+
+ // Verify that all options we put into the database were
+ // returned.
+ {
+ SCOPED_TRACE("verify test_options_[0]");
+ auto option0 = index.find(test_options_[0]->option_->getType());
+ ASSERT_FALSE(option0 == index.end());
+ testOptionsEquivalent(*test_options_[0], *option0);
+ EXPECT_GT(option0->getId(), 0);
+ ASSERT_EQ(1, option0->getServerTags().size());
+ EXPECT_EQ("all", option0->getServerTags().begin()->get());
+ }
+
+ {
+ SCOPED_TRACE("verify test_options_[1]");
+ auto option1 = index.find(test_options_[1]->option_->getType());
+ ASSERT_FALSE(option1 == index.end());
+ testOptionsEquivalent(*test_options_[1], *option1);
+ EXPECT_GT(option1->getId(), 0);
+ ASSERT_EQ(1, option1->getServerTags().size());
+ EXPECT_EQ("all", option1->getServerTags().begin()->get());
+ }
+
+ {
+ SCOPED_TRACE("verify test_options_[5]");
+ auto option5 = index.find(test_options_[5]->option_->getType());
+ ASSERT_FALSE(option5 == index.end());
+ testOptionsEquivalent(*test_options_[5], *option5);
+ EXPECT_GT(option5->getId(), 0);
+ ASSERT_EQ(1, option5->getServerTags().size());
+ EXPECT_EQ("all", option5->getServerTags().begin()->get());
+ }
+}
+
+void
+GenericConfigBackendDHCPv6Test::getModifiedOptions6Test() {
+ // Assign timestamps to the options we're going to store in the
+ // database.
+ test_options_[0]->setModificationTime(timestamps_["tomorrow"]);
+ test_options_[1]->setModificationTime(timestamps_["yesterday"]);
+ test_options_[5]->setModificationTime(timestamps_["today"]);
+
+ // Put options into the database.
+ cbptr_->createUpdateOption6(ServerSelector::ALL(),
+ test_options_[0]);
+ cbptr_->createUpdateOption6(ServerSelector::ALL(),
+ test_options_[1]);
+ cbptr_->createUpdateOption6(ServerSelector::ALL(),
+ test_options_[5]);
+
+ // Get options with the timestamp later than today. Only
+ // one option should be returned.
+ OptionContainer returned_options =
+ cbptr_->getModifiedOptions6(ServerSelector::ALL(),
+ timestamps_["after today"]);
+ ASSERT_EQ(1, returned_options.size());
+
+ // Fetching modified options with explicitly specified server selector
+ // should return the same result.
+ returned_options = cbptr_->getModifiedOptions6(ServerSelector::ONE("server1"),
+ timestamps_["after today"]);
+ ASSERT_EQ(1, returned_options.size());
+
+ // The returned option should be the one with the timestamp
+ // set to tomorrow.
+ const OptionContainerTypeIndex& index = returned_options.get<1>();
+ auto option0 = index.find(test_options_[0]->option_->getType());
+ ASSERT_FALSE(option0 == index.end());
+ {
+ SCOPED_TRACE("verify returned option");
+ testOptionsEquivalent(*test_options_[0], *option0);
+ }
+}
+
+void
+GenericConfigBackendDHCPv6Test::createUpdateDeleteSubnetOption6Test() {
+ // Insert new subnet.
+ Subnet6Ptr subnet = test_subnets_[1];
+ cbptr_->createUpdateSubnet6(ServerSelector::ALL(), subnet);
+
+ // Fetch this subnet by subnet identifier.
+ Subnet6Ptr returned_subnet = cbptr_->getSubnet6(ServerSelector::ALL(),
+ subnet->getID());
+ ASSERT_TRUE(returned_subnet);
+
+ {
+ SCOPED_TRACE("CREATE audit entry for a new subnet");
+ testNewAuditEntry("dhcp6_subnet",
+ AuditEntry::ModificationType::CREATE,
+ "subnet set");
+ }
+
+ // The inserted subnet contains four options.
+ ASSERT_EQ(4, countRows("dhcp6_options"));
+
+ OptionDescriptorPtr opt_posix_timezone = test_options_[0];
+ cbptr_->createUpdateOption6(ServerSelector::ANY(), subnet->getID(),
+ opt_posix_timezone);
+
+ returned_subnet = cbptr_->getSubnet6(ServerSelector::ALL(),
+ subnet->getID());
+ ASSERT_TRUE(returned_subnet);
+
+ OptionDescriptor returned_opt_posix_timezone =
+ returned_subnet->getCfgOption()->get(DHCP6_OPTION_SPACE, D6O_NEW_POSIX_TIMEZONE);
+ ASSERT_TRUE(returned_opt_posix_timezone.option_);
+
+ {
+ SCOPED_TRACE("verify returned option");
+ testOptionsEquivalent(*opt_posix_timezone, returned_opt_posix_timezone);
+ EXPECT_GT(returned_opt_posix_timezone.getId(), 0);
+ }
+
+ {
+ SCOPED_TRACE("UPDATE audit entry for an added subnet option");
+ // Instead of adding an audit entry for an option we add an audit
+ // entry for the entire subnet so as the server refreshes the
+ // subnet with the new option. Note that the server doesn't
+ // have means to retrieve only the newly added option.
+ testNewAuditEntry("dhcp6_subnet",
+ AuditEntry::ModificationType::UPDATE,
+ "subnet specific option set");
+ }
+
+ // We have added one option to the existing subnet. We should now have
+ // five options.
+ ASSERT_EQ(5, countRows("dhcp6_options"));
+
+ opt_posix_timezone->persistent_ = !opt_posix_timezone->persistent_;
+ cbptr_->createUpdateOption6(ServerSelector::ANY(), subnet->getID(),
+ opt_posix_timezone);
+
+ returned_subnet = cbptr_->getSubnet6(ServerSelector::ALL(),
+ subnet->getID());
+ ASSERT_TRUE(returned_subnet);
+ returned_opt_posix_timezone =
+ returned_subnet->getCfgOption()->get(DHCP6_OPTION_SPACE, D6O_NEW_POSIX_TIMEZONE);
+ ASSERT_TRUE(returned_opt_posix_timezone.option_);
+
+ {
+ SCOPED_TRACE("verify returned option with modified persistence");
+ testOptionsEquivalent(*opt_posix_timezone, returned_opt_posix_timezone);
+ }
+
+ {
+ SCOPED_TRACE("UPDATE audit entry for an updated subnet option");
+ testNewAuditEntry("dhcp6_subnet",
+ AuditEntry::ModificationType::UPDATE,
+ "subnet specific option set");
+ }
+
+ // Updating the option should replace the existing instance with the new
+ // instance. Therefore, we should still have five options.
+ ASSERT_EQ(5, countRows("dhcp6_options"));
+
+ // It should succeed for any server.
+ EXPECT_EQ(1, cbptr_->deleteOption6(ServerSelector::ANY(), subnet->getID(),
+ opt_posix_timezone->option_->getType(),
+ opt_posix_timezone->space_name_));
+
+ returned_subnet = cbptr_->getSubnet6(ServerSelector::ALL(),
+ subnet->getID());
+ ASSERT_TRUE(returned_subnet);
+
+ EXPECT_FALSE(returned_subnet->getCfgOption()->get(DHCP6_OPTION_SPACE, D6O_NEW_POSIX_TIMEZONE).option_);
+
+ {
+ SCOPED_TRACE("UPDATE audit entry for a deleted subnet option");
+ testNewAuditEntry("dhcp6_subnet",
+ AuditEntry::ModificationType::UPDATE,
+ "subnet specific option deleted");
+ }
+
+ // We should have only four options after deleting one of them.
+ ASSERT_EQ(4, countRows("dhcp6_options"));
+}
+
+void
+GenericConfigBackendDHCPv6Test::createUpdateDeletePoolOption6Test() {
+ // Insert new subnet.
+ Subnet6Ptr subnet = test_subnets_[1];
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet6(ServerSelector::ALL(), subnet));
+
+ {
+ SCOPED_TRACE("CREATE audit entry for a subnet");
+ testNewAuditEntry("dhcp6_subnet",
+ AuditEntry::ModificationType::CREATE,
+ "subnet set");
+ }
+
+ // Inserted subnet has four options.
+ ASSERT_EQ(4, countRows("dhcp6_options"));
+
+ // Add an option into the pool.
+ const PoolPtr pool = subnet->getPool(Lease::TYPE_NA,
+ IOAddress("2001:db8::10"));
+ ASSERT_TRUE(pool);
+ OptionDescriptorPtr opt_posix_timezone = test_options_[0];
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateOption6(ServerSelector::ANY(),
+ pool->getFirstAddress(),
+ pool->getLastAddress(),
+ opt_posix_timezone));
+
+ // Query for a subnet.
+ Subnet6Ptr returned_subnet = cbptr_->getSubnet6(ServerSelector::ALL(),
+ subnet->getID());
+ ASSERT_TRUE(returned_subnet);
+
+ // The returned subnet should include our pool.
+ const PoolPtr returned_pool = returned_subnet->getPool(Lease::TYPE_NA,
+ IOAddress("2001:db8::10"));
+ ASSERT_TRUE(returned_pool);
+
+ // The pool should contain option we added earlier.
+ OptionDescriptor returned_opt_posix_timezone =
+ returned_pool->getCfgOption()->get(DHCP6_OPTION_SPACE, D6O_NEW_POSIX_TIMEZONE);
+ ASSERT_TRUE(returned_opt_posix_timezone.option_);
+
+ {
+ SCOPED_TRACE("verify returned pool option");
+ testOptionsEquivalent(*opt_posix_timezone, returned_opt_posix_timezone);
+ EXPECT_GT(returned_opt_posix_timezone.getId(), 0);
+ }
+
+ {
+ SCOPED_TRACE("UPDATE audit entry for a subnet after adding an option "
+ "to the address pool");
+ testNewAuditEntry("dhcp6_subnet",
+ AuditEntry::ModificationType::UPDATE,
+ "address pool specific option set");
+ }
+
+ // With the newly inserted option we should now have five options.
+ ASSERT_EQ(5, countRows("dhcp6_options"));
+
+ // Modify the option and update it in the database.
+ opt_posix_timezone->persistent_ = !opt_posix_timezone->persistent_;
+ cbptr_->createUpdateOption6(ServerSelector::ANY(),
+ pool->getFirstAddress(),
+ pool->getLastAddress(),
+ opt_posix_timezone);
+
+ // Fetch the subnet and the corresponding pool.
+ returned_subnet = cbptr_->getSubnet6(ServerSelector::ALL(),
+ subnet->getID());
+ ASSERT_TRUE(returned_subnet);
+ const PoolPtr returned_pool1 = returned_subnet->getPool(Lease::TYPE_NA,
+ IOAddress("2001:db8::10"));
+ ASSERT_TRUE(returned_pool1);
+
+ // Test that the option has been correctly updated in the database.
+ returned_opt_posix_timezone =
+ returned_pool1->getCfgOption()->get(DHCP6_OPTION_SPACE, D6O_NEW_POSIX_TIMEZONE);
+ ASSERT_TRUE(returned_opt_posix_timezone.option_);
+
+ {
+ SCOPED_TRACE("verify updated option with modified persistence");
+ testOptionsEquivalent(*opt_posix_timezone, returned_opt_posix_timezone);
+ }
+
+ {
+ SCOPED_TRACE("UPDATE audit entry for a subnet when updating "
+ "address pool specific option");
+ testNewAuditEntry("dhcp6_subnet",
+ AuditEntry::ModificationType::UPDATE,
+ "address pool specific option set");
+ }
+
+ // The new option instance should replace the existing one, so we should
+ // still have five options.
+ ASSERT_EQ(5, countRows("dhcp6_options"));
+
+ // Delete option for any server should succeed.
+ EXPECT_EQ(1, cbptr_->deleteOption6(ServerSelector::ANY(),
+ pool->getFirstAddress(),
+ pool->getLastAddress(),
+ opt_posix_timezone->option_->getType(),
+ opt_posix_timezone->space_name_));
+
+ // Fetch the subnet and the pool from the database again to make sure
+ // that the option is really gone.
+ returned_subnet = cbptr_->getSubnet6(ServerSelector::ALL(),
+ subnet->getID());
+ ASSERT_TRUE(returned_subnet);
+ const PoolPtr returned_pool2 = returned_subnet->getPool(Lease::TYPE_NA,
+ IOAddress("2001:db8::10"));
+ ASSERT_TRUE(returned_pool2);
+
+ // Option should be gone.
+ EXPECT_FALSE(returned_pool2->getCfgOption()->get(DHCP6_OPTION_SPACE,
+ D6O_NEW_POSIX_TIMEZONE).option_);
+
+ {
+ SCOPED_TRACE("UPDATE audit entry for a subnet when deleting "
+ "address pool specific option");
+ testNewAuditEntry("dhcp6_subnet",
+ AuditEntry::ModificationType::UPDATE,
+ "address pool specific option deleted");
+ }
+
+ // The option has been deleted so the number of options should now
+ // be down to 4.
+ EXPECT_EQ(4, countRows("dhcp6_options"));
+}
+
+void
+GenericConfigBackendDHCPv6Test::createUpdateDeletePdPoolOption6Test() {
+ // Insert new subnet.
+ Subnet6Ptr subnet = test_subnets_[1];
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet6(ServerSelector::ALL(), subnet));
+
+ {
+ SCOPED_TRACE("CREATE audit entry for a subnet");
+ testNewAuditEntry("dhcp6_subnet",
+ AuditEntry::ModificationType::CREATE,
+ "subnet set");
+ }
+
+ // Inserted subnet has four options.
+ ASSERT_EQ(4, countRows("dhcp6_options"));
+
+ // Add an option into the pd pool.
+ const PoolPtr pd_pool = subnet->getPool(Lease::TYPE_PD,
+ IOAddress("2001:db8:a:10::"));
+ ASSERT_TRUE(pd_pool);
+ OptionDescriptorPtr opt_posix_timezone = test_options_[0];
+ int pd_pool_len = prefixLengthFromRange(pd_pool->getFirstAddress(),
+ pd_pool->getLastAddress());
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateOption6(ServerSelector::ANY(),
+ pd_pool->getFirstAddress(),
+ static_cast<uint8_t>(pd_pool_len),
+ opt_posix_timezone));
+
+ // Query for a subnet.
+ Subnet6Ptr returned_subnet = cbptr_->getSubnet6(ServerSelector::ALL(),
+ subnet->getID());
+ ASSERT_TRUE(returned_subnet);
+
+ // The returned subnet should include our pool.
+ const PoolPtr returned_pd_pool = returned_subnet->getPool(Lease::TYPE_PD,
+ IOAddress("2001:db8:a:10::"));
+ ASSERT_TRUE(returned_pd_pool);
+
+ // The pd pool should contain option we added earlier.
+ OptionDescriptor returned_opt_posix_timezone =
+ returned_pd_pool->getCfgOption()->get(DHCP6_OPTION_SPACE, D6O_NEW_POSIX_TIMEZONE);
+ ASSERT_TRUE(returned_opt_posix_timezone.option_);
+
+ {
+ SCOPED_TRACE("verify returned pool option");
+ testOptionsEquivalent(*opt_posix_timezone, returned_opt_posix_timezone);
+ EXPECT_GT(returned_opt_posix_timezone.getId(), 0);
+ }
+
+ {
+ SCOPED_TRACE("UPDATE audit entry for a subnet after adding an option "
+ "to the prefix delegation pool");
+ testNewAuditEntry("dhcp6_subnet",
+ AuditEntry::ModificationType::UPDATE,
+ "prefix delegation pool specific option set");
+ }
+
+ // With the newly inserted option we should now have five options.
+ ASSERT_EQ(5, countRows("dhcp6_options"));
+
+ // Modify the option and update it in the database.
+ opt_posix_timezone->persistent_ = !opt_posix_timezone->persistent_;
+ cbptr_->createUpdateOption6(ServerSelector::ANY(),
+ pd_pool->getFirstAddress(),
+ static_cast<uint8_t>(pd_pool_len),
+ opt_posix_timezone);
+
+ // Fetch the subnet and the corresponding pd pool.
+ returned_subnet = cbptr_->getSubnet6(ServerSelector::ALL(),
+ subnet->getID());
+ ASSERT_TRUE(returned_subnet);
+ const PoolPtr returned_pd_pool1 = returned_subnet->getPool(Lease::TYPE_PD,
+ IOAddress("2001:db8:a:10::"));
+ ASSERT_TRUE(returned_pd_pool1);
+
+ // Test that the option has been correctly updated in the database.
+ returned_opt_posix_timezone =
+ returned_pd_pool1->getCfgOption()->get(DHCP6_OPTION_SPACE, D6O_NEW_POSIX_TIMEZONE);
+ ASSERT_TRUE(returned_opt_posix_timezone.option_);
+
+ {
+ SCOPED_TRACE("verify updated option with modified persistence");
+ testOptionsEquivalent(*opt_posix_timezone, returned_opt_posix_timezone);
+ }
+
+ {
+ SCOPED_TRACE("UPDATE audit entry for a subnet when updating "
+ "prefix delegation pool specific option");
+ testNewAuditEntry("dhcp6_subnet",
+ AuditEntry::ModificationType::UPDATE,
+ "prefix delegation pool specific option set");
+ }
+
+ // The new option instance should replace the existing one, so we should
+ // still have five options.
+ ASSERT_EQ(5, countRows("dhcp6_options"));
+
+ // Delete option for any server should succeed.
+ EXPECT_EQ(1, cbptr_->deleteOption6(ServerSelector::ANY(),
+ pd_pool->getFirstAddress(),
+ static_cast<uint8_t>(pd_pool_len),
+ opt_posix_timezone->option_->getType(),
+ opt_posix_timezone->space_name_));
+
+ // Fetch the subnet and the pool from the database again to make sure
+ // that the option is really gone.
+ returned_subnet = cbptr_->getSubnet6(ServerSelector::ALL(),
+ subnet->getID());
+ ASSERT_TRUE(returned_subnet);
+ const PoolPtr returned_pd_pool2 = returned_subnet->getPool(Lease::TYPE_PD,
+ IOAddress("2001:db8:a:10::"));
+ ASSERT_TRUE(returned_pd_pool2);
+
+ // Option should be gone.
+ EXPECT_FALSE(returned_pd_pool2->getCfgOption()->get(DHCP6_OPTION_SPACE,
+ D6O_NEW_POSIX_TIMEZONE).option_);
+
+ {
+ SCOPED_TRACE("UPDATE audit entry for a subnet when deleting "
+ "prefix delegation pool specific option");
+ testNewAuditEntry("dhcp6_subnet",
+ AuditEntry::ModificationType::UPDATE,
+ "prefix delegation pool specific option deleted");
+ }
+
+ // The option has been deleted so the number of options should now
+ // be down to 4.
+ EXPECT_EQ(4, countRows("dhcp6_options"));
+}
+
+void
+GenericConfigBackendDHCPv6Test::createUpdateDeleteSharedNetworkOption6Test() {
+ // Insert new shared network.
+ SharedNetwork6Ptr shared_network = test_networks_[1];
+ cbptr_->createUpdateSharedNetwork6(ServerSelector::ALL(),
+ shared_network);
+
+ // Fetch this shared network by name.
+ SharedNetwork6Ptr returned_network =
+ cbptr_->getSharedNetwork6(ServerSelector::ALL(),
+ shared_network->getName());
+ ASSERT_TRUE(returned_network);
+
+ {
+ SCOPED_TRACE("CREATE audit entry for the new shared network");
+ testNewAuditEntry("dhcp6_shared_network",
+ AuditEntry::ModificationType::CREATE,
+ "shared network set");
+ }
+
+ // The inserted shared network has no options.
+ ASSERT_EQ(0, countRows("dhcp6_options"));
+
+ OptionDescriptorPtr opt_posix_timezone = test_options_[0];
+ cbptr_->createUpdateOption6(ServerSelector::ANY(),
+ shared_network->getName(),
+ opt_posix_timezone);
+
+ returned_network = cbptr_->getSharedNetwork6(ServerSelector::ALL(),
+ shared_network->getName());
+ ASSERT_TRUE(returned_network);
+
+ OptionDescriptor returned_opt_posix_timezone =
+ returned_network->getCfgOption()->get(DHCP6_OPTION_SPACE, D6O_NEW_POSIX_TIMEZONE);
+ ASSERT_TRUE(returned_opt_posix_timezone.option_);
+
+ {
+ SCOPED_TRACE("verify returned option");
+ testOptionsEquivalent(*opt_posix_timezone, returned_opt_posix_timezone);
+ EXPECT_GT(returned_opt_posix_timezone.getId(), 0);
+ }
+
+ {
+ SCOPED_TRACE("UPDATE audit entry for the added shared network option");
+ // Instead of adding an audit entry for an option we add an audit
+ // entry for the entire shared network so as the server refreshes the
+ // shared network with the new option. Note that the server doesn't
+ // have means to retrieve only the newly added option.
+ testNewAuditEntry("dhcp6_shared_network",
+ AuditEntry::ModificationType::UPDATE,
+ "shared network specific option set");
+ }
+
+ // One option should now be stored in the database.
+ ASSERT_EQ(1, countRows("dhcp6_options"));
+
+ opt_posix_timezone->persistent_ = !opt_posix_timezone->persistent_;
+ cbptr_->createUpdateOption6(ServerSelector::ANY(),
+ shared_network->getName(),
+ opt_posix_timezone);
+
+ returned_network = cbptr_->getSharedNetwork6(ServerSelector::ALL(),
+ shared_network->getName());
+ ASSERT_TRUE(returned_network);
+ returned_opt_posix_timezone =
+ returned_network->getCfgOption()->get(DHCP6_OPTION_SPACE, D6O_NEW_POSIX_TIMEZONE);
+ ASSERT_TRUE(returned_opt_posix_timezone.option_);
+
+ {
+ SCOPED_TRACE("verify updated option with modified persistence");
+ testOptionsEquivalent(*opt_posix_timezone, returned_opt_posix_timezone);
+ }
+
+ {
+ SCOPED_TRACE("UPDATE audit entry for the updated shared network option");
+ testNewAuditEntry("dhcp6_shared_network",
+ AuditEntry::ModificationType::UPDATE,
+ "shared network specific option set");
+ }
+
+ // The new option instance should replace the existing option instance,
+ // so we should still have one option.
+ ASSERT_EQ(1, countRows("dhcp6_options"));
+
+ // Deleting an option for any server should succeed.
+ EXPECT_EQ(1, cbptr_->deleteOption6(ServerSelector::ANY(),
+ shared_network->getName(),
+ opt_posix_timezone->option_->getType(),
+ opt_posix_timezone->space_name_));
+ returned_network = cbptr_->getSharedNetwork6(ServerSelector::ALL(),
+ shared_network->getName());
+ ASSERT_TRUE(returned_network);
+ EXPECT_FALSE(returned_network->getCfgOption()->get(DHCP6_OPTION_SPACE, D6O_NEW_POSIX_TIMEZONE).option_);
+
+ {
+ SCOPED_TRACE("UPDATE audit entry for the deleted shared network option");
+ testNewAuditEntry("dhcp6_shared_network",
+ AuditEntry::ModificationType::UPDATE,
+ "shared network specific option deleted");
+ }
+
+ // After deleting the option we should be back to 0.
+ EXPECT_EQ(0, countRows("dhcp6_options"));
+}
+
+void
+GenericConfigBackendDHCPv6Test::subnetOptionIdOrderTest() {
+
+ // Add a subnet with two pools with two options each.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet6(ServerSelector::ALL(), test_subnets_[1]));
+ EXPECT_EQ(2, countRows("dhcp6_pool"));
+ EXPECT_EQ(4, countRows("dhcp6_options"));
+
+ // Add a second subnet with a single option. The number of options in the database
+ // should now be 3.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet6(ServerSelector::ALL(), test_subnets_[2]));
+ EXPECT_EQ(2, countRows("dhcp6_pool"));
+ EXPECT_EQ(5, countRows("dhcp6_options"));
+
+ // Now replace the first subnet with three options and two pools. This will cause
+ // the option id values for this subnet to be larger than those in the second
+ // subnet.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet6(ServerSelector::ALL(), test_subnets_[0]));
+ EXPECT_EQ(2, countRows("dhcp6_pool"));
+ EXPECT_EQ(4, countRows("dhcp6_options"));
+
+ // Now fetch all subnets.
+ Subnet6Collection subnets;
+ ASSERT_NO_THROW_LOG(subnets = cbptr_->getAllSubnets6(ServerSelector::ALL()));
+ ASSERT_EQ(2, subnets.size());
+
+ // Verify that the subnets returned are as expected.
+ for (auto subnet : subnets) {
+ ASSERT_EQ(1, subnet->getServerTags().size());
+ EXPECT_EQ("all", subnet->getServerTags().begin()->get());
+ if (subnet->getID() == 1024) {
+ EXPECT_EQ(test_subnets_[0]->toElement()->str(), subnet->toElement()->str());
+ } else if (subnet->getID() == 2048) {
+ EXPECT_EQ(test_subnets_[2]->toElement()->str(), subnet->toElement()->str());
+ } else {
+ ADD_FAILURE() << "unexpected subnet id:" << subnet->getID();
+ }
+ }
+}
+
+void
+GenericConfigBackendDHCPv6Test::sharedNetworkOptionIdOrderTest() {
+ auto level1_options = test_networks_[0];
+ auto level1_no_options = test_networks_[1];
+ auto level2 = test_networks_[2];
+
+ // Insert two shared networks. We insert level1 without options first,
+ // then level2.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork6(ServerSelector::ALL(),
+ level1_no_options));
+
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork6(ServerSelector::ALL(),
+ level2));
+ // Fetch all shared networks.
+ SharedNetwork6Collection networks =
+ cbptr_->getAllSharedNetworks6(ServerSelector::ALL());
+
+ ASSERT_EQ(2, networks.size());
+
+ // See if shared networks are returned ok.
+ for (auto i = 0; i < networks.size(); ++i) {
+ if (i == 0) {
+ // level1_no_options
+ EXPECT_EQ(level1_no_options->toElement()->str(),
+ networks[i]->toElement()->str());
+ } else {
+ // bar
+ EXPECT_EQ(level2->toElement()->str(),
+ networks[i]->toElement()->str());
+ }
+ }
+
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork6(ServerSelector::ALL(),
+ level1_options));
+
+ // Fetch all shared networks.
+ networks = cbptr_->getAllSharedNetworks6(ServerSelector::ALL());
+ ASSERT_EQ(2, networks.size());
+
+ // See if shared networks are returned ok.
+ for (auto i = 0; i < networks.size(); ++i) {
+ if (i == 0) {
+ // level1_no_options
+ EXPECT_EQ(level1_options->toElement()->str(),
+ networks[i]->toElement()->str());
+ } else {
+ // bar
+ EXPECT_EQ(level2->toElement()->str(),
+ networks[i]->toElement()->str());
+ }
+ }
+}
+
+void
+GenericConfigBackendDHCPv6Test::setAndGetAllClientClasses6Test() {
+ // Create a server.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[0]));
+ {
+ SCOPED_TRACE("server1 is created");
+ testNewAuditEntry("dhcp6_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set",
+ ServerSelector::ONE("server1"));
+ }
+ // Create first class.
+ auto class1 = test_client_classes_[0];
+ class1->setTest("pkt6.msgtype == 1");
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass6(ServerSelector::ALL(), class1, ""));
+ {
+ SCOPED_TRACE("client class foo is created");
+ testNewAuditEntry("dhcp6_client_class",
+ AuditEntry::ModificationType::CREATE,
+ "client class set",
+ ServerSelector::ONE("server1"));
+ }
+ // Create second class.
+ auto class2 = test_client_classes_[1];
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass6(ServerSelector::ONE("server1"), class2, ""));
+ {
+ SCOPED_TRACE("client class bar is created");
+ testNewAuditEntry("dhcp6_client_class",
+ AuditEntry::ModificationType::CREATE,
+ "client class set",
+ ServerSelector::ONE("server1"));
+ }
+ // Create third class.
+ auto class3 = test_client_classes_[2];
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass6(ServerSelector::ONE("server1"), class3, ""));
+ {
+ SCOPED_TRACE("client class foobar is created");
+ testNewAuditEntry("dhcp6_client_class",
+ AuditEntry::ModificationType::CREATE,
+ "client class set",
+ ServerSelector::ONE("server1"));
+ }
+ // Update the third class to depend on the second class.
+ class3->setTest("member('foo')");
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass6(ServerSelector::ONE("server1"), class3, ""));
+ {
+ SCOPED_TRACE("client class bar is updated");
+ testNewAuditEntry("dhcp6_client_class",
+ AuditEntry::ModificationType::UPDATE,
+ "client class set",
+ ServerSelector::ONE("server1"));
+ }
+ // Only the first class should be returned for the server selector ALL.
+ auto client_classes = cbptr_->getAllClientClasses6(ServerSelector::ALL());
+ ASSERT_EQ(1, client_classes.getClasses()->size());
+ // All three classes should be returned for the server1.
+ client_classes = cbptr_->getAllClientClasses6(ServerSelector::ONE("server1"));
+ auto classes_list = client_classes.getClasses();
+ ASSERT_EQ(3, classes_list->size());
+
+ auto fetched_class = classes_list->begin();
+ ASSERT_EQ("foo", (*fetched_class)->getName());
+ EXPECT_FALSE((*fetched_class)->getMatchExpr());
+ EXPECT_EQ(class1->toElement()->str(), (*fetched_class)->toElement()->str());
+
+ ++fetched_class;
+ ASSERT_EQ("bar", (*fetched_class)->getName());
+ EXPECT_FALSE((*fetched_class)->getMatchExpr());
+ EXPECT_EQ(class2->toElement()->str(), (*fetched_class)->toElement()->str());
+
+ ++fetched_class;
+ ASSERT_EQ("foobar", (*fetched_class)->getName());
+ EXPECT_FALSE((*fetched_class)->getMatchExpr());
+ EXPECT_EQ(class3->toElement()->str(), (*fetched_class)->toElement()->str());
+
+ // Move the third class between the first and second class.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass6(ServerSelector::ONE("server1"), class3, "foo"));
+
+ // Ensure that the classes order has changed.
+ client_classes = cbptr_->getAllClientClasses6(ServerSelector::ONE("server1"));
+ classes_list = client_classes.getClasses();
+ ASSERT_EQ(3, classes_list->size());
+ EXPECT_EQ("foo", (*classes_list->begin())->getName());
+ EXPECT_FALSE((*classes_list->begin())->getMatchExpr());
+ EXPECT_EQ("foobar", (*(classes_list->begin() + 1))->getName());
+ EXPECT_FALSE((*(classes_list->begin() + 1))->getMatchExpr());
+ EXPECT_EQ("bar", (*(classes_list->begin() + 2))->getName());
+ EXPECT_FALSE((*(classes_list->begin() + 2))->getMatchExpr());
+
+ // Update the foobar class without specifying its position. It should not
+ // be moved.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass6(ServerSelector::ONE("server1"), class3, ""));
+
+ client_classes = cbptr_->getAllClientClasses6(ServerSelector::ONE("server1"));
+ classes_list = client_classes.getClasses();
+ ASSERT_EQ(3, classes_list->size());
+ EXPECT_EQ("foo", (*classes_list->begin())->getName());
+ EXPECT_FALSE((*classes_list->begin())->getMatchExpr());
+ EXPECT_EQ("foobar", (*(classes_list->begin() + 1))->getName());
+ EXPECT_FALSE((*(classes_list->begin() + 1))->getMatchExpr());
+ EXPECT_EQ("bar", (*(classes_list->begin() + 2))->getName());
+ EXPECT_FALSE((*(classes_list->begin() + 2))->getMatchExpr());
+}
+
+void
+GenericConfigBackendDHCPv6Test::getClientClass6Test() {
+ // Create a server.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[0]));
+
+ // Add classes.
+ auto class1 = test_client_classes_[0];
+ ASSERT_NO_THROW_LOG(class1->getCfgOption()->add(test_options_[0]->option_,
+ test_options_[0]->persistent_,
+ test_options_[0]->space_name_));
+ ASSERT_NO_THROW_LOG(class1->getCfgOption()->add(test_options_[1]->option_,
+ test_options_[1]->persistent_,
+ test_options_[1]->space_name_));
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass6(ServerSelector::ALL(), class1, ""));
+
+ auto class2 = test_client_classes_[1];
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass6(ServerSelector::ONE("server1"), class2, ""));
+
+ // Get the first client class and validate its contents.
+ ClientClassDefPtr client_class;
+ ASSERT_NO_THROW_LOG(client_class = cbptr_->getClientClass6(ServerSelector::ALL(), class1->getName()));
+ ASSERT_TRUE(client_class);
+ EXPECT_EQ("foo", client_class->getName());
+ EXPECT_TRUE(client_class->getRequired());
+ EXPECT_EQ(30, client_class->getValid().getMin());
+ EXPECT_EQ(60, client_class->getValid().get());
+ EXPECT_EQ(90, client_class->getValid().getMax());
+
+ // Validate options belonging to this class.
+ ASSERT_TRUE(client_class->getCfgOption());
+ OptionDescriptor returned_opt_new_posix_timezone =
+ client_class->getCfgOption()->get(DHCP6_OPTION_SPACE, D6O_NEW_POSIX_TIMEZONE);
+ ASSERT_TRUE(returned_opt_new_posix_timezone.option_);
+
+ OptionDescriptor returned_opt_preference =
+ client_class->getCfgOption()->get(DHCP6_OPTION_SPACE, D6O_PREFERENCE);
+ ASSERT_TRUE(returned_opt_preference.option_);
+
+ // Fetch the same class using different server selectors.
+ ASSERT_NO_THROW_LOG(client_class = cbptr_->getClientClass6(ServerSelector::ANY(),
+ class1->getName()));
+ EXPECT_TRUE(client_class);
+
+ ASSERT_NO_THROW_LOG(client_class = cbptr_->getClientClass6(ServerSelector::ONE("server1"),
+ class1->getName()));
+ EXPECT_TRUE(client_class);
+
+ ASSERT_NO_THROW_LOG(client_class = cbptr_->getClientClass6(ServerSelector::UNASSIGNED(),
+ class1->getName()));
+ EXPECT_FALSE(client_class);
+
+ // Fetch the second client class using different selectors. This time the
+ // class should not be returned for the ALL server selector because it is
+ // associated with the server1.
+ ASSERT_NO_THROW_LOG(client_class = cbptr_->getClientClass6(ServerSelector::ALL(),
+ class2->getName()));
+ EXPECT_FALSE(client_class);
+
+ ASSERT_NO_THROW_LOG(client_class = cbptr_->getClientClass6(ServerSelector::ANY(),
+ class2->getName()));
+ EXPECT_TRUE(client_class);
+
+ ASSERT_NO_THROW_LOG(client_class = cbptr_->getClientClass6(ServerSelector::ONE("server1"),
+ class2->getName()));
+ EXPECT_TRUE(client_class);
+
+ ASSERT_NO_THROW_LOG(client_class = cbptr_->getClientClass6(ServerSelector::UNASSIGNED(),
+ class2->getName()));
+ EXPECT_FALSE(client_class);
+}
+
+void
+GenericConfigBackendDHCPv6Test::createUpdateClientClass6OptionsTest() {
+ // Add class with two options and two option definitions.
+ auto class1 = test_client_classes_[0];
+ ASSERT_NO_THROW_LOG(class1->getCfgOption()->add(test_options_[0]->option_,
+ test_options_[0]->persistent_,
+ test_options_[0]->space_name_));
+ ASSERT_NO_THROW_LOG(class1->getCfgOption()->add(test_options_[1]->option_,
+ test_options_[1]->persistent_,
+ test_options_[1]->space_name_));
+ auto cfg_option_def = boost::make_shared<CfgOptionDef>();
+ class1->setCfgOptionDef(cfg_option_def);
+ ASSERT_NO_THROW_LOG(class1->getCfgOptionDef()->add(test_option_defs_[0]));
+ ASSERT_NO_THROW_LOG(class1->getCfgOptionDef()->add(test_option_defs_[2]));
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass6(ServerSelector::ALL(), class1, ""));
+
+ // Fetch the class and the options from the database.
+ ClientClassDefPtr client_class;
+ ASSERT_NO_THROW_LOG(client_class = cbptr_->getClientClass6(ServerSelector::ALL(), class1->getName()));
+ ASSERT_TRUE(client_class);
+
+ // Validate options belonging to the class.
+ ASSERT_TRUE(client_class->getCfgOption());
+ OptionDescriptor returned_opt_new_posix_timezone =
+ client_class->getCfgOption()->get(DHCP6_OPTION_SPACE, D6O_NEW_POSIX_TIMEZONE);
+ ASSERT_TRUE(returned_opt_new_posix_timezone.option_);
+
+ OptionDescriptor returned_opt_preference =
+ client_class->getCfgOption()->get(DHCP6_OPTION_SPACE, D6O_PREFERENCE);
+ ASSERT_TRUE(returned_opt_preference.option_);
+
+ // Validate option definitions belonging to the class.
+ ASSERT_TRUE(client_class->getCfgOptionDef());
+ auto returned_def_foo = client_class->getCfgOptionDef()->get(test_option_defs_[0]->getOptionSpaceName(),
+ test_option_defs_[0]->getCode());
+ ASSERT_TRUE(returned_def_foo);
+ EXPECT_EQ(1234, returned_def_foo->getCode());
+ EXPECT_EQ("foo", returned_def_foo->getName());
+ EXPECT_EQ(DHCP6_OPTION_SPACE, returned_def_foo->getOptionSpaceName());
+ EXPECT_EQ("espace", returned_def_foo->getEncapsulatedSpace());
+ EXPECT_EQ(OPT_STRING_TYPE, returned_def_foo->getType());
+ EXPECT_FALSE(returned_def_foo->getArrayType());
+
+ auto returned_def_fish = client_class->getCfgOptionDef()->get(test_option_defs_[2]->getOptionSpaceName(),
+ test_option_defs_[2]->getCode());
+ ASSERT_TRUE(returned_def_fish);
+ EXPECT_EQ(5235, returned_def_fish->getCode());
+ EXPECT_EQ("fish", returned_def_fish->getName());
+ EXPECT_EQ(DHCP6_OPTION_SPACE, returned_def_fish->getOptionSpaceName());
+ EXPECT_TRUE(returned_def_fish->getEncapsulatedSpace().empty());
+ EXPECT_EQ(OPT_RECORD_TYPE, returned_def_fish->getType());
+ EXPECT_TRUE(returned_def_fish->getArrayType());
+
+ // Replace client class specific option definitions. Leave only one option
+ // definition.
+ cfg_option_def = boost::make_shared<CfgOptionDef>();
+ class1->setCfgOptionDef(cfg_option_def);
+ ASSERT_NO_THROW_LOG(class1->getCfgOptionDef()->add(test_option_defs_[2]));
+
+ // Delete one of the options and update the class.
+ class1->getCfgOption()->del(test_options_[0]->space_name_,
+ test_options_[0]->option_->getType());
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass6(ServerSelector::ALL(), class1, ""));
+ ASSERT_NO_THROW_LOG(client_class = cbptr_->getClientClass6(ServerSelector::ALL(), class1->getName()));
+ ASSERT_TRUE(client_class);
+
+ // Ensure that the first option definition is gone.
+ ASSERT_TRUE(client_class->getCfgOptionDef());
+ returned_def_foo = client_class->getCfgOptionDef()->get(test_option_defs_[0]->getOptionSpaceName(),
+ test_option_defs_[0]->getCode());
+ EXPECT_FALSE(returned_def_foo);
+
+ // The second option definition should be present.
+ returned_def_fish = client_class->getCfgOptionDef()->get(test_option_defs_[2]->getOptionSpaceName(),
+ test_option_defs_[2]->getCode());
+ EXPECT_TRUE(returned_def_fish);
+
+ // Make sure that the first option is gone.
+ ASSERT_TRUE(client_class->getCfgOption());
+ returned_opt_new_posix_timezone = client_class->getCfgOption()->get(DHCP6_OPTION_SPACE,
+ D6O_NEW_POSIX_TIMEZONE);
+ EXPECT_FALSE(returned_opt_new_posix_timezone.option_);
+
+ // The second option should be there.
+ returned_opt_preference = client_class->getCfgOption()->get(DHCP6_OPTION_SPACE,
+ D6O_PREFERENCE);
+ ASSERT_TRUE(returned_opt_preference.option_);
+}
+
+void
+GenericConfigBackendDHCPv6Test::getModifiedClientClasses6Test() {
+ // Create server1.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[0]));
+
+ // Add three classes to the database with different timestamps.
+ auto class1 = test_client_classes_[0];
+ class1->setModificationTime(timestamps_["yesterday"]);
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass6(ServerSelector::ALL(), class1, ""));
+
+ auto class2 = test_client_classes_[1];
+ class2->setModificationTime(timestamps_["today"]);
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass6(ServerSelector::ALL(), class2, ""));
+
+ auto class3 = test_client_classes_[2];
+ class3->setModificationTime(timestamps_["tomorrow"]);
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass6(ServerSelector::ONE("server1"), class3, ""));
+
+ // Get modified client classes configured for all servers.
+ auto client_classes = cbptr_->getModifiedClientClasses6(ServerSelector::ALL(),
+ timestamps_["two days ago"]);
+ EXPECT_EQ(2, client_classes.getClasses()->size());
+
+ // Get modified client classes appropriate for server1. It includes classes
+ // for all servers and for the server1.
+ client_classes = cbptr_->getModifiedClientClasses6(ServerSelector::ONE("server1"),
+ timestamps_["two days ago"]);
+ EXPECT_EQ(3, client_classes.getClasses()->size());
+
+ // Get the classes again but use the timestamp equal to the modification
+ // time of the first class.
+ client_classes = cbptr_->getModifiedClientClasses6(ServerSelector::ONE("server1"),
+ timestamps_["yesterday"]);
+ EXPECT_EQ(3, client_classes.getClasses()->size());
+
+ // Get modified classes starting from today. It should return only two.
+ client_classes = cbptr_->getModifiedClientClasses6(ServerSelector::ONE("server1"),
+ timestamps_["today"]);
+ EXPECT_EQ(2, client_classes.getClasses()->size());
+
+ // Get client classes modified in the future. It should return none.
+ client_classes = cbptr_->getModifiedClientClasses6(ServerSelector::ONE("server1"),
+ timestamps_["after tomorrow"]);
+ EXPECT_EQ(0, client_classes.getClasses()->size());
+
+ // Getting modified client classes for any server is unsupported.
+ ASSERT_THROW(cbptr_->getModifiedClientClasses6(ServerSelector::ANY(),
+ timestamps_["two days ago"]),
+ InvalidOperation);
+}
+
+void
+GenericConfigBackendDHCPv6Test::deleteClientClass6Test() {
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[0]));
+ {
+ SCOPED_TRACE("server1 is created");
+ testNewAuditEntry("dhcp6_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set",
+ ServerSelector::ONE("server1"));
+ }
+ {
+ SCOPED_TRACE("server1 is created");
+ testNewAuditEntry("dhcp6_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set",
+ ServerSelector::ONE("server2"));
+ }
+
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[2]));
+ {
+ SCOPED_TRACE("server1 is created and available for server1");
+ testNewAuditEntry("dhcp6_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set",
+ ServerSelector::ONE("server1"));
+ }
+ {
+ SCOPED_TRACE("server1 is created and available for server2");
+ testNewAuditEntry("dhcp6_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set",
+ ServerSelector::ONE("server2"));
+ }
+
+ auto class1 = test_client_classes_[0];
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass6(ServerSelector::ALL(), class1, ""));
+ {
+ SCOPED_TRACE("client class foo is created and available for server1");
+ testNewAuditEntry("dhcp6_client_class",
+ AuditEntry::ModificationType::CREATE,
+ "client class set",
+ ServerSelector::ONE("server1"));
+ }
+ {
+ SCOPED_TRACE("client class foo is created and available for server 2");
+ testNewAuditEntry("dhcp6_client_class",
+ AuditEntry::ModificationType::CREATE,
+ "client class set",
+ ServerSelector::ONE("server2"));
+ }
+
+ auto class2 = test_client_classes_[1];
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass6(ServerSelector::ONE("server1"), class2, ""));
+ {
+ SCOPED_TRACE("client class bar is created");
+ testNewAuditEntry("dhcp6_client_class",
+ AuditEntry::ModificationType::CREATE,
+ "client class set",
+ ServerSelector::ONE("server1"));
+ }
+
+ auto class3 = test_client_classes_[2];
+ class3->setTest("member('foo')");
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass6(ServerSelector::ONE("server2"), class3, ""));
+ {
+ SCOPED_TRACE("client class foobar is created");
+ testNewAuditEntry("dhcp6_client_class",
+ AuditEntry::ModificationType::CREATE,
+ "client class set",
+ ServerSelector::ONE("server2"));
+ }
+
+ uint64_t result;
+ ASSERT_NO_THROW_LOG(result = cbptr_->deleteClientClass6(ServerSelector::ONE("server1"),
+ class2->getName()));
+ EXPECT_EQ(1, result);
+ {
+ SCOPED_TRACE("client class bar is deleted");
+ testNewAuditEntry("dhcp6_client_class",
+ AuditEntry::ModificationType::DELETE,
+ "client class deleted",
+ ServerSelector::ONE("server1"));
+ }
+
+ ASSERT_NO_THROW_LOG(result = cbptr_->deleteClientClass6(ServerSelector::ONE("server2"),
+ class3->getName()));
+ EXPECT_EQ(1, result);
+ {
+ SCOPED_TRACE("client class foobar is deleted");
+ testNewAuditEntry("dhcp6_client_class",
+ AuditEntry::ModificationType::DELETE,
+ "client class deleted",
+ ServerSelector::ONE("server2"));
+ }
+
+ ASSERT_NO_THROW_LOG(result = cbptr_->deleteClientClass6(ServerSelector::ANY(),
+ class1->getName()));
+ EXPECT_EQ(1, result);
+ {
+ SCOPED_TRACE("client class foo is deleted and no longer available for the server1");
+ testNewAuditEntry("dhcp6_client_class",
+ AuditEntry::ModificationType::DELETE,
+ "client class deleted",
+ ServerSelector::ONE("server1"));
+ }
+ {
+ SCOPED_TRACE("client class foo is deleted and no longer available for the server2");
+ testNewAuditEntry("dhcp6_client_class",
+ AuditEntry::ModificationType::DELETE,
+ "client class deleted",
+ ServerSelector::ONE("server2"));
+ }
+}
+
+void
+GenericConfigBackendDHCPv6Test::deleteAllClientClasses6Test() {
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[0]));
+ {
+ SCOPED_TRACE("server1 is created");
+ testNewAuditEntry("dhcp6_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set",
+ ServerSelector::ONE("server1"));
+ }
+ {
+ SCOPED_TRACE("server1 is created");
+ testNewAuditEntry("dhcp6_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set",
+ ServerSelector::ONE("server2"));
+ }
+
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[2]));
+ {
+ SCOPED_TRACE("server1 is created and available for server1");
+ testNewAuditEntry("dhcp6_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set",
+ ServerSelector::ONE("server1"));
+ }
+ {
+ SCOPED_TRACE("server1 is created and available for server2");
+ testNewAuditEntry("dhcp6_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set",
+ ServerSelector::ONE("server2"));
+ }
+
+ auto class1 = test_client_classes_[0];
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass6(ServerSelector::ALL(), class1, ""));
+ {
+ SCOPED_TRACE("client class foo is created and available for server1");
+ testNewAuditEntry("dhcp6_client_class",
+ AuditEntry::ModificationType::CREATE,
+ "client class set",
+ ServerSelector::ONE("server1"));
+ }
+ {
+ SCOPED_TRACE("client class foo is created and available for server 2");
+ testNewAuditEntry("dhcp6_client_class",
+ AuditEntry::ModificationType::CREATE,
+ "client class set",
+ ServerSelector::ONE("server2"));
+ }
+
+ auto class2 = test_client_classes_[1];
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass6(ServerSelector::ONE("server1"), class2, ""));
+ {
+ SCOPED_TRACE("client class bar is created");
+ testNewAuditEntry("dhcp6_client_class",
+ AuditEntry::ModificationType::CREATE,
+ "client class set",
+ ServerSelector::ONE("server1"));
+ }
+
+ auto class3 = test_client_classes_[2];
+ class3->setTest("member('foo')");
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass6(ServerSelector::ONE("server2"), class3, ""));
+ {
+ SCOPED_TRACE("client class foobar is created");
+ testNewAuditEntry("dhcp6_client_class",
+ AuditEntry::ModificationType::CREATE,
+ "client class set",
+ ServerSelector::ONE("server2"));
+ }
+
+ uint64_t result;
+
+ ASSERT_NO_THROW_LOG(result = cbptr_->deleteAllClientClasses6(ServerSelector::UNASSIGNED()));
+ EXPECT_EQ(0, result);
+
+ ASSERT_NO_THROW_LOG(result = cbptr_->deleteAllClientClasses6(ServerSelector::ONE("server2")));
+ EXPECT_EQ(1, result);
+ {
+ SCOPED_TRACE("client classes for server2 deleted");
+ testNewAuditEntry("dhcp6_client_class",
+ AuditEntry::ModificationType::DELETE,
+ "deleted all client classes",
+ ServerSelector::ONE("server2"));
+ }
+
+ ASSERT_NO_THROW_LOG(result = cbptr_->deleteAllClientClasses6(ServerSelector::ONE("server2")));
+ EXPECT_EQ(0, result);
+
+ ASSERT_NO_THROW_LOG(result = cbptr_->deleteAllClientClasses6(ServerSelector::ONE("server1")));
+ EXPECT_EQ(1, result);
+ {
+ SCOPED_TRACE("client classes for server1 deleted");
+ testNewAuditEntry("dhcp6_client_class",
+ AuditEntry::ModificationType::DELETE,
+ "deleted all client classes",
+ ServerSelector::ONE("server1"));
+ }
+
+ ASSERT_NO_THROW_LOG(result = cbptr_->deleteAllClientClasses6(ServerSelector::ONE("server1")));
+ EXPECT_EQ(0, result);
+
+ ASSERT_NO_THROW_LOG(result = cbptr_->deleteAllClientClasses6(ServerSelector::ALL()));
+ EXPECT_EQ(1, result);
+ {
+ SCOPED_TRACE("client classes for all deleted");
+ testNewAuditEntry("dhcp6_client_class",
+ AuditEntry::ModificationType::DELETE,
+ "deleted all client classes",
+ ServerSelector::ONE("server1"));
+ }
+
+ ASSERT_NO_THROW_LOG(result = cbptr_->deleteAllClientClasses6(ServerSelector::ALL()));
+ EXPECT_EQ(0, result);
+
+ // Deleting multiple objects using ANY server tag is unsupported.
+ ASSERT_THROW(cbptr_->deleteAllClientClasses6(ServerSelector::ANY()), InvalidOperation);
+}
+
+void
+GenericConfigBackendDHCPv6Test::clientClassDependencies6Test() {
+ // Create a server.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[0]));
+
+ // Create first class. It depends on KNOWN built-in class.
+ auto class1 = test_client_classes_[0];
+ class1->setTest("member('KNOWN')");
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass6(ServerSelector::ALL(), class1, ""));
+
+ // Create second class which depends on the first class. This yelds indirect
+ // dependency on KNOWN class.
+ auto class2 = test_client_classes_[1];
+ class2->setTest("member('foo')");
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass6(ServerSelector::ALL(), class2, ""));
+
+ // Create third class depending on the second class. This also yelds indirect
+ // dependency on KNOWN class.
+ auto class3 = test_client_classes_[2];
+ class3->setTest("member('bar')");
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass6(ServerSelector::ALL(), class3, ""));
+
+ // An attempt to move the first class to the end of the class hierarchy should
+ // fail because other classes depend on it.
+ ASSERT_THROW(cbptr_->createUpdateClientClass6(ServerSelector::ALL(), class1, "bar"),
+ DbOperationError);
+
+ // Try to change the dependency of the first class. There are other classes
+ // having indirect dependency on KNOWN class via this class. Therefore, the
+ // update should be unsuccessful.
+ class1->setTest("member('HA_server1')");
+ ASSERT_THROW(cbptr_->createUpdateClientClass6(ServerSelector::ALL(), class1, ""),
+ DbOperationError);
+
+ // Try to change the dependency of the second class. This should result in
+ // an error because the third class depends on it.
+ class2->setTest("member('HA_server1')");
+ ASSERT_THROW(cbptr_->createUpdateClientClass6(ServerSelector::ALL(), class2, ""),
+ DbOperationError);
+
+ // Changing the indirect dependency of the third class should succeed, because
+ // no other classes depend on this class.
+ class3->setTest("member('HA_server1')");
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass6(ServerSelector::ALL(), class3, ""));
+}
+
+void
+GenericConfigBackendDHCPv6Test::multipleAuditEntriesTest() {
+ // Get current time.
+ boost::posix_time::ptime now = boost::posix_time::second_clock::local_time();
+
+ // Create a server.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[1]));
+
+ // Create a global parameter and update it many times.
+ const ServerSelector& server_selector = ServerSelector::ALL();
+ StampedValuePtr param;
+ ElementPtr value;
+ for (int i = 0; i < 100; ++i) {
+ value = Element::create(i);
+ param = StampedValue::create("my-parameter", value);
+ cbptr_->createUpdateGlobalParameter6(server_selector, param);
+ }
+
+ // Get all audit entries from now.
+ AuditEntryCollection audit_entries =
+ cbptr_->getRecentAuditEntries(server_selector, now, 0);
+
+ // Check that partial retrieves return the right count.
+ auto& mod_time_idx = audit_entries.get<AuditEntryModificationTimeIdTag>();
+ for (auto it = mod_time_idx.begin(); it != mod_time_idx.end(); ++it) {
+ size_t partial_size =
+ cbptr_->getRecentAuditEntries(server_selector,
+ (*it)->getModificationTime(),
+ (*it)->getRevisionId()).size();
+ EXPECT_EQ(partial_size + 1,
+ std::distance(it, mod_time_idx.end()));
+ }
+}
diff --git a/src/lib/dhcpsrv/testutils/generic_cb_dhcp6_unittest.h b/src/lib/dhcpsrv/testutils/generic_cb_dhcp6_unittest.h
new file mode 100644
index 0000000..61e8564
--- /dev/null
+++ b/src/lib/dhcpsrv/testutils/generic_cb_dhcp6_unittest.h
@@ -0,0 +1,394 @@
+// Copyright (C) 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/.
+
+#ifndef GENERIC_CONFIG_BACKEND_DHCP6_H
+#define GENERIC_CONFIG_BACKEND_DHCP6_H
+
+#include <database/database_connection.h>
+#include <dhcpsrv/config_backend_dhcp6_mgr.h>
+#include <dhcpsrv/testutils/generic_backend_unittest.h>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+/// @brief Generic test fixture class for testing DHCPv6
+/// config backend operations.
+class GenericConfigBackendDHCPv6Test : public GenericBackendTest {
+public:
+ /// @brief Constructor.
+ GenericConfigBackendDHCPv6Test()
+ : test_subnets_(), test_networks_(), test_option_defs_(),
+ test_options_(), test_client_classes_(), test_servers_(), cbptr_() {
+ }
+
+ /// @brief Destructor.
+ virtual ~GenericConfigBackendDHCPv6Test() {};
+
+ /// @brief Prepares the class for a test.
+ ///
+ /// Invoked by gtest prior test entry, we create the
+ /// appropriate schema and create a basic host manager to
+ /// wipe out any prior instance
+ virtual void SetUp();
+
+ /// @brief Pre-test exit clean up
+ ///
+ /// Invoked by gtest upon test exit, we destroy the schema
+ /// we created.
+ virtual void TearDown();
+
+ /// @brief Abstract method for destroying the back end specific schema
+ virtual void destroySchema() = 0;
+
+ /// @brief Abstract method for creating the back end specific schema
+ virtual void createSchema() = 0;
+
+ /// @brief Abstract method which returns the back end specific connection
+ /// string
+ virtual std::string validConnectionString() = 0;
+
+ /// @brief Abstract method which instantiates an instance of a
+ /// DHCPv6 configuration back end.
+ ///
+ /// @params Connection parameters describing the back end to create.
+ /// @return Pointer to the newly created back end instance.
+ virtual ConfigBackendDHCPv6Ptr backendFactory(db::DatabaseConnection::ParameterMap&
+ params) = 0;
+
+ /// @brief Counts rows in a selected table in the back end database.
+ ///
+ /// This method can be used to verify that some configuration elements were
+ /// deleted from a selected table as a result of cascade delete or a trigger.
+ /// For example, deleting a subnet should trigger deletion of its address
+ /// pools and options. By counting the rows on each table we can determine
+ /// whether the deletion took place on all tables for which it was expected.
+ ///
+ /// @param table Table name.
+ /// @return Number of rows in the specified table.
+ virtual size_t countRows(const std::string& table) const = 0;
+
+ /// @brief Creates several servers used in tests.
+ void initTestServers();
+
+ /// @brief Creates several subnets used in tests.
+ void initTestSubnets();
+
+ /// @brief Creates several subnets used in tests.
+ void initTestSharedNetworks();
+
+ /// @brief Creates several option definitions used in tests.
+ void initTestOptionDefs();
+
+ /// @brief Creates several DHCP options used in tests.
+ void initTestOptions();
+
+ /// @brief Creates several client classes used in tests.
+ void initTestClientClasses();
+
+ /// @brief Tests that a backend of the given type can be instantiated.
+ ///
+ /// @param expected_type type of the back end created (i.e. "mysql",
+ /// "postgresql").
+ void getTypeTest(const std::string& expected_type);
+
+ /// @brief Verifies that a backend on the localhost can be instantiated.
+ void getHostTest();
+
+ /// @brief Verifies that a backend on the localhost port 0 can be instantiated.
+ void getPortTest();
+
+ /// @brief Retrieves the most recent audit entries.
+ ///
+ /// Allowed server selectors: ALL, ONE.
+ /// Not allowed server selectors: ANY, UNASSIGNED, MULTIPLE.
+ ///
+ /// @param server_selector Server selector.
+ /// @param modification_time Timestamp being a lower limit for the returned
+ /// result set, i.e. entries later than specified time are returned.
+ /// @param modification_id Identifier being a lower limit for the returned
+ /// result set, used when two (or more) entries have the same
+ /// modification_time.
+ /// @return Collection of audit entries.
+ virtual db::AuditEntryCollection
+ getRecentAuditEntries(const db::ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time,
+ const uint64_t& modification_id) const;
+
+ /// @brief This test verifies that the server can be added, updated and deleted.
+ void createUpdateDeleteServerTest();
+
+ /// @brief This test verifies that it is possible to retrieve all servers from the
+ /// database and then delete all of them.
+ void getAndDeleteAllServersTest();
+
+ /// @brief This test verifies that the global parameter can be added, updated and
+ /// deleted.
+ void createUpdateDeleteGlobalParameter6Test();
+
+ /// @brief This test verifies that it is possible to differentiate between the
+ /// global parameters by server tag and that the value specified for the
+ /// particular server overrides the value specified for all servers.
+ void globalParameters6WithServerTagsTest();
+
+ /// @brief This test verifies that all global parameters can be retrieved and deleted.
+ void getAllGlobalParameters6Test();
+
+ /// @brief This test verifies that modified global parameters can be retrieved.
+ void getModifiedGlobalParameters6Test();
+
+ /// @brief Test that the NullKeyError message is correctly updated.
+ void nullKeyErrorTest();
+
+ /// @brief Test that createUpdateSubnet6 throws appropriate exceptions for various
+ /// server selectors.
+ void createUpdateSubnet6SelectorsTest();
+
+ /// @brief Test that subnet can be inserted, fetched, updated and then fetched again.
+ void getSubnet6Test();
+
+ /// @brief Test that getSubnet6 by ID throws appropriate exceptions for various server
+ /// selectors.
+ void getSubnet6byIdSelectorsTest();
+
+ /// @brief Test that the information about unspecified optional parameters gets
+ /// propagated to the database.
+ void getSubnet6WithOptionalUnspecifiedTest();
+
+ /// @brief Test that subnet can be associated with a shared network.
+ void getSubnet6SharedNetworkTest();
+
+ /// @brief Test that subnet can be fetched by prefix.
+ void getSubnet6ByPrefixTest();
+
+ /// @brief Test that getSubnet6 by prefix throws appropriate exceptions for various server
+ /// selectors.
+ void getSubnet6byPrefixSelectorsTest();
+
+ /// @brief Test that all subnets can be fetched and then deleted.
+ void getAllSubnets6Test();
+
+ /// @brief Test that getAllSubnets6 throws appropriate exceptions for various
+ /// server selectors.
+ void getAllSubnets6SelectorsTest();
+
+ /// @brief Test that subnets with different server associations are returned.
+ void getAllSubnets6WithServerTagsTest();
+
+ /// @brief Test that getModifiedSubnets6 throws appropriate exceptions for various
+ /// server selectors.
+ void getModifiedSubnets6SelectorsTest();
+
+ /// @brief Test that selected subnet can be deleted.
+ void deleteSubnet6Test();
+
+ /// @brief Test that deleteSubnet6 by ID throws appropriate exceptions for various
+ /// server selectors.
+ void deleteSubnet6ByIdSelectorsTest();
+
+ /// @brief Test that deleteSubnet6 by prefix throws appropriate exceptions for various
+ /// server selectors.
+ void deleteSubnet6ByPrefixSelectorsTest();
+
+ /// @brief Test that deleteAllSubnets6 throws appropriate exceptions for various
+ /// server selectors.
+ void deleteAllSubnets6SelectorsTest();
+
+ /// @brief Test that it is possible to retrieve and delete orphaned subnet.
+ void unassignedSubnet6Test();
+
+ /// @brief Test that subnets modified after given time can be fetched.
+ void getModifiedSubnets6Test();
+
+ /// @brief Test that lifetimes in subnets are handled as expected.
+ void subnetLifetimeTest();
+
+ /// @brief Test that subnets belonging to a shared network can be retrieved.
+ void getSharedNetworkSubnets6Test();
+
+ /// @brief Test that pools are properly updated as a result a subnet update.
+ void subnetUpdatePoolsTest();
+
+ /// @brief Test that deleting a subnet triggers deletion of the options associated
+ /// with the subnet and pools.
+ void subnetOptionsTest();
+
+ /// @brief Test that shared network can be inserted, fetched, updated and then
+ /// fetched again.
+ void getSharedNetwork6Test();
+
+ /// @brief Test that getSharedNetwork6 throws appropriate exceptions for various
+ /// server selectors.
+ void getSharedNetwork6SelectorsTest();
+
+ /// @brief Test that shared network may be created and updated and the server tags
+ /// are properly assigned to it.
+ void createUpdateSharedNetwork6Test();
+
+ /// @brief Test that createUpdateSharedNetwork6 throws appropriate exceptions for various
+ /// server selectors.
+ void createUpdateSharedNetwork6SelectorsTest();
+
+ /// @brief Test that the information about unspecified optional parameters gets
+ /// propagated to the database.
+ void getSharedNetwork6WithOptionalUnspecifiedTest();
+
+ /// @brief Test that deleteSharedNetworkSubnets6 with not ANY selector throw.
+ void deleteSharedNetworkSubnets6Test();
+
+ /// @brief Test that all shared networks can be fetched.
+ void getAllSharedNetworks6Test();
+
+ /// @brief Test that getAllSharedNetworks6 throws appropriate exceptions for various
+ /// server selectors.
+ void getAllSharedNetworks6SelectorsTest();
+
+ /// @brief Test that shared networks with different server associations are returned.
+ void getAllSharedNetworks6WithServerTagsTest();
+
+ /// @brief Test that shared networks modified after given time can be fetched.
+ void getModifiedSharedNetworks6Test();
+
+ /// @brief Test that getModifiedSharedNetworks6 throws appropriate exceptions for various
+ /// server selectors.
+ void getModifiedSharedNetworks6SelectorsTest();
+
+ /// @brief Test that selected shared network can be deleted.
+ void deleteSharedNetwork6Test();
+
+ /// @brief Test that deleteSharedNetwork6 throws appropriate exceptions for various
+ /// server selectors.
+ void deleteSharedNetwork6SelectorsTest();
+
+ /// @brief Test that deleteAllSharedNetworks6 throws appropriate exceptions for various
+ /// server selectors.
+ void deleteAllSharedNetworks6SelectorsTest();
+
+ /// @brief Test that it is possible to retrieve and delete orphaned shared network.
+ void unassignedSharedNetworkTest();
+
+ /// @brief Test that lifetimes in shared networks are handled as expected.
+ void sharedNetworkLifetimeTest();
+
+ /// @brief Test that deleting a shared network triggers deletion of the options
+ /// associated with the shared network.
+ void sharedNetworkOptionsTest();
+
+ /// @brief Test that option definition can be inserted, fetched, updated and then
+ /// fetched again.
+ void getOptionDef6Test();
+
+ /// @brief This test verifies that it is possible to differentiate between the
+ /// option definitions by server tag and that the option definition
+ /// specified for the particular server overrides the definition for
+ /// all servers.
+ void optionDefs6WithServerTagsTest();
+
+ /// @brief Test that all option definitions can be fetched.
+ void getAllOptionDefs6Test();
+
+ /// @brief Test that option definitions modified after given time can be fetched.
+ void getModifiedOptionDefs6Test();
+
+ /// @brief This test verifies that global option can be added, updated and deleted.
+ void createUpdateDeleteOption6Test();
+
+ /// @brief This test verifies that it is possible to differentiate between the
+ /// global options by server tag and that the option specified for the
+ /// particular server overrides the value specified for all servers.
+ void globalOptions6WithServerTagsTest();
+
+ /// @brief This test verifies that all global options can be retrieved.
+ void getAllOptions6Test();
+
+ /// @brief This test verifies that modified global options can be retrieved.
+ void getModifiedOptions6Test();
+
+ /// @brief This test verifies that subnet level option can be added, updated and
+ /// deleted.
+ void createUpdateDeleteSubnetOption6Test();
+
+ /// @brief This test verifies that option can be inserted, updated and deleted
+ /// from the pool.
+ void createUpdateDeletePoolOption6Test();
+
+ /// @brief This test verifies that option can be inserted, updated and deleted
+ /// from the pd pool.
+ void createUpdateDeletePdPoolOption6Test();
+
+ /// @brief This test verifies that shared network level option can be added,
+ /// updated and deleted.
+ void createUpdateDeleteSharedNetworkOption6Test();
+
+ /// @brief This test verifies that option id values in one subnet do
+ /// not impact options returned in subsequent subnets when
+ /// fetching subnets from the backend.
+ void subnetOptionIdOrderTest();
+
+ /// @brief This test verifies that option id values in one shared network do
+ /// not impact options returned in subsequent shared networks when
+ /// fetching shared networks from the backend.
+ void sharedNetworkOptionIdOrderTest();
+
+ /// @brief This test verifies that it is possible to create client classes, update them
+ /// and retrieve all classes for a given server.
+ void setAndGetAllClientClasses6Test();
+
+ /// @brief This test verifies that a single class can be retrieved from the database.
+ void getClientClass6Test();
+
+ /// @brief This test verifies that client class specific DHCP options can be
+ /// modified during the class update.
+ void createUpdateClientClass6OptionsTest();
+
+ /// @brief This test verifies that modified client classes can be retrieved from the database.
+ void getModifiedClientClasses6Test();
+
+ /// @brief This test verifies that a specified client class can be deleted.
+ void deleteClientClass6Test();
+
+ /// @brief This test verifies that all client classes can be deleted using
+ /// a specified server selector.
+ void deleteAllClientClasses6Test();
+
+ /// @brief This test verifies that client class dependencies are tracked when the
+ /// classes are added to the database. It verifies that an attempt to update
+ /// a class violating the dependencies results in an error.
+ void clientClassDependencies6Test();
+
+ /// @brief This test verifies that audit entries can be retrieved from a given
+ /// timestamp and id including when two entries can get the same timestamp.
+ /// (either it is a common even and this should catch it, or it is a rare
+ /// event and it does not matter).
+ void multipleAuditEntriesTest();
+
+ /// @brief Holds pointers to subnets used in tests.
+ std::vector<Subnet6Ptr> test_subnets_;
+
+ /// @brief Holds pointers to shared networks used in tests.
+ std::vector<SharedNetwork6Ptr> test_networks_;
+
+ /// @brief Holds pointers to option definitions used in tests.
+ std::vector<OptionDefinitionPtr> test_option_defs_;
+
+ /// @brief Holds pointers to options used in tests.
+ std::vector<OptionDescriptorPtr> test_options_;
+
+ /// @brief Holds pointers to classes used in tests.
+ std::vector<ClientClassDefPtr> test_client_classes_;
+
+ /// @brief Holds pointers to the servers used in tests.
+ std::vector<db::ServerPtr> test_servers_;
+
+ /// @brief Holds pointer to the backend.
+ boost::shared_ptr<ConfigBackendDHCPv6> cbptr_;
+};
+
+} // namespace test
+} // namespace dhcp
+} // namespace isc
+
+#endif // GENERIC_CONFIG_BACKEND_DHCP6_H
diff --git a/src/lib/dhcpsrv/testutils/generic_cb_recovery_unittest.cc b/src/lib/dhcpsrv/testutils/generic_cb_recovery_unittest.cc
new file mode 100644
index 0000000..87e3ce2
--- /dev/null
+++ b/src/lib/dhcpsrv/testutils/generic_cb_recovery_unittest.cc
@@ -0,0 +1,376 @@
+// Copyright (C) 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 <database/db_exceptions.h>
+#include <database/server.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/testutils/generic_cb_recovery_unittest.h>
+#include <dhcpsrv/testutils/test_utils.h>
+#include <testutils/gtest_utils.h>
+
+#include <boost/make_shared.hpp>
+#include <boost/shared_ptr.hpp>
+#include <gtest/gtest.h>
+
+using namespace isc;
+using namespace isc::util;
+using namespace isc::asiolink;
+using namespace isc::db;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+using namespace isc::process;
+using namespace isc::test;
+namespace ph = std::placeholders;
+
+GenericConfigBackendDbLostCallbackTest::GenericConfigBackendDbLostCallbackTest()
+ : db_lost_callback_called_(0), db_recovered_callback_called_(0),
+ db_failed_callback_called_(0),
+ io_service_(boost::make_shared<IOService>()) {
+}
+
+GenericConfigBackendDbLostCallbackTest::~GenericConfigBackendDbLostCallbackTest() {
+}
+
+void
+GenericConfigBackendDbLostCallbackTest::SetUp() {
+ DatabaseConnection::db_lost_callback_ = 0;
+ DatabaseConnection::db_recovered_callback_ = 0;
+ DatabaseConnection::db_failed_callback_ = 0;
+ setConfigBackendImplIOService(io_service_);
+ isc::dhcp::TimerMgr::instance()->setIOService(io_service_);
+ isc::dhcp::CfgMgr::instance().clear();
+
+ // Ensure we have the proper schema with no transient data.
+ createSchema();
+ isc::dhcp::CfgMgr::instance().clear();
+ registerBackendType();
+}
+
+void
+GenericConfigBackendDbLostCallbackTest::TearDown() {
+ // If data wipe enabled, delete transient data otherwise destroy the schema
+ destroySchema();
+ isc::dhcp::CfgMgr::instance().clear();
+
+ unregisterBackendType();
+ DatabaseConnection::db_lost_callback_ = 0;
+ DatabaseConnection::db_recovered_callback_ = 0;
+ DatabaseConnection::db_failed_callback_ = 0;
+ setConfigBackendImplIOService(IOServicePtr());
+ isc::dhcp::TimerMgr::instance()->unregisterTimers();
+ isc::dhcp::CfgMgr::instance().clear();
+}
+
+void
+GenericConfigBackendDbLostCallbackTest::testNoCallbackOnOpenFailure() {
+ DatabaseConnection::db_lost_callback_ =
+ std::bind(&GenericConfigBackendDbLostCallbackTest::db_lost_callback, this, ph::_1);
+
+ // Set the connectivity recovered callback.
+ DatabaseConnection::db_recovered_callback_ =
+ std::bind(&GenericConfigBackendDbLostCallbackTest::db_recovered_callback, this, ph::_1);
+
+ // Set the connectivity failed callback.
+ DatabaseConnection::db_failed_callback_ =
+ std::bind(&GenericConfigBackendDbLostCallbackTest::db_failed_callback, this, ph::_1);
+
+ std::string access = invalidConnectionString();
+
+ // Connect to the CB backend.
+ ASSERT_THROW(addBackend(access), DbOpenError);
+
+ io_service_->poll();
+
+ EXPECT_EQ(0, db_lost_callback_called_);
+ EXPECT_EQ(0, db_recovered_callback_called_);
+ EXPECT_EQ(0, db_failed_callback_called_);
+}
+
+void
+GenericConfigBackendDbLostCallbackTest::testDbLostAndRecoveredCallback() {
+ // Set the connectivity lost callback.
+ DatabaseConnection::db_lost_callback_ =
+ std::bind(&GenericConfigBackendDbLostCallbackTest::db_lost_callback, this, ph::_1);
+
+ // Set the connectivity recovered callback.
+ DatabaseConnection::db_recovered_callback_ =
+ std::bind(&GenericConfigBackendDbLostCallbackTest::db_recovered_callback, this, ph::_1);
+
+ // Set the connectivity failed callback.
+ DatabaseConnection::db_failed_callback_ =
+ std::bind(&GenericConfigBackendDbLostCallbackTest::db_failed_callback, this, ph::_1);
+
+ std::string access = validConnectionString();
+
+ ConfigControlInfoPtr config_ctl_info(new ConfigControlInfo());
+ config_ctl_info->addConfigDatabase(access);
+ CfgMgr::instance().getCurrentCfg()->setConfigControlInfo(config_ctl_info);
+
+ // Find the most recently opened socket. Our SQL client's socket should
+ // be the next one.
+ int last_open_socket = findLastSocketFd();
+
+ // Fill holes.
+ FillFdHoles holes(last_open_socket);
+
+ // Connect to the CB backend.
+ ASSERT_NO_THROW(addBackend(access));
+
+ // Find the SQL client socket.
+ int sql_socket = findLastSocketFd();
+ ASSERT_TRUE(sql_socket > last_open_socket);
+
+ // Verify we can execute a query. We don't care about the answer.
+ ServerCollection servers;
+ ASSERT_NO_THROW_LOG(servers = getAllServers());
+
+ // Now close the sql socket out from under backend client
+ ASSERT_EQ(0, close(sql_socket));
+
+ // A query should fail with DbConnectionUnusable.
+ ASSERT_THROW(servers = getAllServers(), DbConnectionUnusable);
+
+ io_service_->poll();
+
+ // Our lost and recovered connectivity callback should have been invoked.
+ EXPECT_EQ(1, db_lost_callback_called_);
+ EXPECT_EQ(1, db_recovered_callback_called_);
+ EXPECT_EQ(0, db_failed_callback_called_);
+}
+
+void
+GenericConfigBackendDbLostCallbackTest::testDbLostAndFailedCallback() {
+ // Set the connectivity lost callback.
+ DatabaseConnection::db_lost_callback_ =
+ std::bind(&GenericConfigBackendDbLostCallbackTest::db_lost_callback, this, ph::_1);
+
+ // Set the connectivity recovered callback.
+ DatabaseConnection::db_recovered_callback_ =
+ std::bind(&GenericConfigBackendDbLostCallbackTest::db_recovered_callback, this, ph::_1);
+
+ // Set the connectivity failed callback.
+ DatabaseConnection::db_failed_callback_ =
+ std::bind(&GenericConfigBackendDbLostCallbackTest::db_failed_callback, this, ph::_1);
+
+ std::string access = validConnectionString();
+ ConfigControlInfoPtr config_ctl_info(new ConfigControlInfo());
+ config_ctl_info->addConfigDatabase(access);
+ CfgMgr::instance().getCurrentCfg()->setConfigControlInfo(config_ctl_info);
+
+ // Find the most recently opened socket. Our SQL client's socket should
+ // be the next one.
+ int last_open_socket = findLastSocketFd();
+
+ // Fill holes.
+ FillFdHoles holes(last_open_socket);
+
+ // Connect to the CB backend.
+ ASSERT_NO_THROW(addBackend(access));
+
+ // Find the SQL client socket.
+ int sql_socket = findLastSocketFd();
+ ASSERT_TRUE(sql_socket > last_open_socket);
+
+ // Verify we can execute a query. We don't care about the answer.
+ ServerCollection servers;
+ ASSERT_NO_THROW(servers = getAllServers());
+
+ access = invalidConnectionString();
+ CfgMgr::instance().clear();
+ // by adding an invalid access will cause the manager factory to throw
+ // resulting in failure to recreate the manager
+ config_ctl_info.reset(new ConfigControlInfo());
+ config_ctl_info->addConfigDatabase(access);
+ CfgMgr::instance().getCurrentCfg()->setConfigControlInfo(config_ctl_info);
+ const ConfigDbInfoList& cfg = CfgMgr::instance().getCurrentCfg()->getConfigControlInfo()->getConfigDatabases();
+ (const_cast<ConfigDbInfoList&>(cfg))[0].setAccessString(access, true);
+
+ // Now close the sql socket out from under backend client
+ ASSERT_EQ(0, close(sql_socket));
+
+ // A query should fail with DbConnectionUnusable.
+ ASSERT_THROW(servers = getAllServers(), DbConnectionUnusable);
+
+ io_service_->poll();
+
+ // Our lost and failed connectivity callback should have been invoked.
+ EXPECT_EQ(1, db_lost_callback_called_);
+ EXPECT_EQ(0, db_recovered_callback_called_);
+ EXPECT_EQ(1, db_failed_callback_called_);
+}
+
+void
+GenericConfigBackendDbLostCallbackTest::testDbLostAndRecoveredAfterTimeoutCallback() {
+ // Set the connectivity lost callback.
+ DatabaseConnection::db_lost_callback_ =
+ std::bind(&GenericConfigBackendDbLostCallbackTest::db_lost_callback, this, ph::_1);
+
+ // Set the connectivity recovered callback.
+ DatabaseConnection::db_recovered_callback_ =
+ std::bind(&GenericConfigBackendDbLostCallbackTest::db_recovered_callback, this, ph::_1);
+
+ // Set the connectivity failed callback.
+ DatabaseConnection::db_failed_callback_ =
+ std::bind(&GenericConfigBackendDbLostCallbackTest::db_failed_callback, this, ph::_1);
+
+ std::string access = validConnectionString();
+ std::string extra = " max-reconnect-tries=3 reconnect-wait-time=1";
+ access += extra;
+ ConfigControlInfoPtr config_ctl_info(new ConfigControlInfo());
+ config_ctl_info->addConfigDatabase(access);
+ CfgMgr::instance().getCurrentCfg()->setConfigControlInfo(config_ctl_info);
+
+ // Find the most recently opened socket. Our SQL client's socket should
+ // be the next one.
+ int last_open_socket = findLastSocketFd();
+
+ // Fill holes.
+ FillFdHoles holes(last_open_socket);
+
+ // Connect to the CB backend.
+ ASSERT_NO_THROW(addBackend(access));
+
+ // Find the SQL client socket.
+ int sql_socket = findLastSocketFd();
+ ASSERT_TRUE(sql_socket > last_open_socket);
+
+ // Verify we can execute a query. We don't care about the answer.
+ ServerCollection servers;
+ ASSERT_NO_THROW(servers = getAllServers());
+
+ access = invalidConnectionString();
+ access += extra;
+ CfgMgr::instance().clear();
+ // by adding an invalid access will cause the manager factory to throw
+ // resulting in failure to recreate the manager
+ config_ctl_info.reset(new ConfigControlInfo());
+ config_ctl_info->addConfigDatabase(access);
+ CfgMgr::instance().getCurrentCfg()->setConfigControlInfo(config_ctl_info);
+ const ConfigDbInfoList& cfg = CfgMgr::instance().getCurrentCfg()->getConfigControlInfo()->getConfigDatabases();
+ (const_cast<ConfigDbInfoList&>(cfg))[0].setAccessString(access, true);
+
+ // Now close the sql socket out from under backend client
+ ASSERT_EQ(0, close(sql_socket));
+
+ // A query should fail with DbConnectionUnusable.
+ ASSERT_THROW(servers = getAllServers(), DbConnectionUnusable);
+
+ io_service_->poll();
+
+ // Our lost connectivity callback should have been invoked.
+ EXPECT_EQ(1, db_lost_callback_called_);
+ EXPECT_EQ(0, db_recovered_callback_called_);
+ EXPECT_EQ(0, db_failed_callback_called_);
+
+ access = validConnectionString();
+ access += extra;
+ CfgMgr::instance().clear();
+ config_ctl_info.reset(new ConfigControlInfo());
+ config_ctl_info->addConfigDatabase(access);
+ CfgMgr::instance().getCurrentCfg()->setConfigControlInfo(config_ctl_info);
+
+ sleep(1);
+
+ io_service_->poll();
+
+ // Our lost and recovered connectivity callback should have been invoked.
+ EXPECT_EQ(2, db_lost_callback_called_);
+ EXPECT_EQ(1, db_recovered_callback_called_);
+ EXPECT_EQ(0, db_failed_callback_called_);
+
+ sleep(1);
+
+ io_service_->poll();
+
+ // No callback should have been invoked.
+ EXPECT_EQ(2, db_lost_callback_called_);
+ EXPECT_EQ(1, db_recovered_callback_called_);
+ EXPECT_EQ(0, db_failed_callback_called_);
+}
+
+void
+GenericConfigBackendDbLostCallbackTest::testDbLostAndFailedAfterTimeoutCallback() {
+ // Set the connectivity lost callback.
+ DatabaseConnection::db_lost_callback_ =
+ std::bind(&GenericConfigBackendDbLostCallbackTest::db_lost_callback, this, ph::_1);
+
+ // Set the connectivity recovered callback.
+ DatabaseConnection::db_recovered_callback_ =
+ std::bind(&GenericConfigBackendDbLostCallbackTest::db_recovered_callback, this, ph::_1);
+
+ // Set the connectivity failed callback.
+ DatabaseConnection::db_failed_callback_ =
+ std::bind(&GenericConfigBackendDbLostCallbackTest::db_failed_callback, this, ph::_1);
+
+ std::string access = validConnectionString();
+ std::string extra = " max-reconnect-tries=3 reconnect-wait-time=1";
+ access += extra;
+ ConfigControlInfoPtr config_ctl_info(new ConfigControlInfo());
+ config_ctl_info->addConfigDatabase(access);
+ CfgMgr::instance().getCurrentCfg()->setConfigControlInfo(config_ctl_info);
+
+ // Find the most recently opened socket. Our SQL client's socket should
+ // be the next one.
+ int last_open_socket = findLastSocketFd();
+
+ // Fill holes.
+ FillFdHoles holes(last_open_socket);
+
+ // Connect to the CB backend.
+ ASSERT_NO_THROW(addBackend(access));
+
+ // Find the SQL client socket.
+ int sql_socket = findLastSocketFd();
+ ASSERT_TRUE(sql_socket > last_open_socket);
+
+ // Verify we can execute a query. We don't care about the answer.
+ ServerCollection servers;
+ ASSERT_NO_THROW(servers = getAllServers());
+
+ access = invalidConnectionString();
+ access += extra;
+ CfgMgr::instance().clear();
+ // by adding an invalid access will cause the manager factory to throw
+ // resulting in failure to recreate the manager
+ config_ctl_info.reset(new ConfigControlInfo());
+ config_ctl_info->addConfigDatabase(access);
+ CfgMgr::instance().getCurrentCfg()->setConfigControlInfo(config_ctl_info);
+ const ConfigDbInfoList& cfg = CfgMgr::instance().getCurrentCfg()->getConfigControlInfo()->getConfigDatabases();
+ (const_cast<ConfigDbInfoList&>(cfg))[0].setAccessString(access, true);
+
+ // Now close the sql socket out from under backend client
+ ASSERT_EQ(0, close(sql_socket));
+
+ // A query should fail with DbConnectionUnusable.
+ ASSERT_THROW(servers = getAllServers(), DbConnectionUnusable);
+
+ io_service_->poll();
+
+ // Our lost connectivity callback should have been invoked.
+ EXPECT_EQ(1, db_lost_callback_called_);
+ EXPECT_EQ(0, db_recovered_callback_called_);
+ EXPECT_EQ(0, db_failed_callback_called_);
+
+ sleep(1);
+
+ io_service_->poll();
+
+ // Our lost connectivity callback should have been invoked.
+ EXPECT_EQ(2, db_lost_callback_called_);
+ EXPECT_EQ(0, db_recovered_callback_called_);
+ EXPECT_EQ(0, db_failed_callback_called_);
+
+ sleep(1);
+
+ io_service_->poll();
+
+ // Our lost and failed connectivity callback should have been invoked.
+ EXPECT_EQ(3, db_lost_callback_called_);
+ EXPECT_EQ(0, db_recovered_callback_called_);
+ EXPECT_EQ(1, db_failed_callback_called_);
+}
diff --git a/src/lib/dhcpsrv/testutils/generic_cb_recovery_unittest.h b/src/lib/dhcpsrv/testutils/generic_cb_recovery_unittest.h
new file mode 100644
index 0000000..af1ce4b
--- /dev/null
+++ b/src/lib/dhcpsrv/testutils/generic_cb_recovery_unittest.h
@@ -0,0 +1,164 @@
+// Copyright (C) 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/.
+
+#ifndef GENERIC_CONFIG_BACKEND_RECOVERY_H
+#define GENERIC_CONFIG_BACKEND_RECOVERY_H
+
+#include <util/reconnect_ctl.h>
+#include <database/server_collection.h>
+#include <dhcpsrv/config_backend_dhcp4_mgr.h>
+#include <dhcpsrv/testutils/generic_backend_unittest.h>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+/// @brief Test fixture for verifying config backend database connection
+/// loss-recovery behavior.
+class GenericConfigBackendDbLostCallbackTest : public ::testing::Test {
+public:
+ /// @brief Constructor
+ GenericConfigBackendDbLostCallbackTest();
+
+ /// @brief Destructor
+ virtual ~GenericConfigBackendDbLostCallbackTest();
+
+ /// @brief Abstract method for destroying the back end specific schema
+ virtual void destroySchema() = 0;
+
+ /// @brief Abstract method for creating the back end specific schema
+ virtual void createSchema() = 0;
+
+ /// @brief Abstract method which returns a valid back end specific connection
+ /// string
+ virtual std::string validConnectionString() = 0;
+
+ /// @brief Abstract method which returns an invalid back end specific connection
+ /// string
+ virtual std::string invalidConnectionString() = 0;
+
+ /// @brief Abstract method which registers a CB backend type.
+ virtual void registerBackendType() = 0;
+
+ /// @brief Abstract method which unregisters a CB backend type.
+ virtual void unregisterBackendType() = 0;
+
+ /// @brief Abstract method which sets the IOService instance in the CB
+ /// implementation object.
+ ///
+ /// @param io_service pointer to the IOService instance to use. It may be
+ /// an empty pointer.
+ virtual void setConfigBackendImplIOService(isc::asiolink::IOServicePtr io_service) = 0;
+
+ /// @brief Abstract method which sets the IOService instance in the CB
+ virtual void addBackend(const std::string& access) = 0;
+
+ /// @brief Abstract method which sets the IOService instance in the CB
+ virtual db::ServerCollection getAllServers() = 0;
+
+ /// @brief Prepares the class for a test.
+ ///
+ /// Invoked by gtest prior test entry, we create the
+ /// appropriate schema and create a basic DB manager to
+ /// wipe out any prior instance
+ virtual void SetUp();
+
+ /// @brief Pre-text exit clean up
+ ///
+ /// Invoked by gtest upon test exit, we destroy the schema
+ /// we created.
+ virtual void TearDown();
+
+ /// @brief Verifies open failures do NOT invoke db lost callback
+ ///
+ /// The db lost callback should only be invoked after successfully
+ /// opening the DB and then subsequently losing it. Failing to
+ /// open should be handled directly by the application layer.
+ void testNoCallbackOnOpenFailure();
+
+ /// @brief Verifies the CB manager's behavior if DB connection is lost
+ ///
+ /// This function creates a CB manager with a back end that supports
+ /// connectivity lost callback. It verifies connectivity by issuing a known
+ /// valid query. Next it simulates connectivity lost by identifying and
+ /// closing the socket connection to the CB backend. It then reissues the
+ /// query and verifies that:
+ /// -# The Query throws DbOperationError (rather than exiting)
+ /// -# The registered DbLostCallback was invoked
+ /// -# The registered DbRecoveredCallback was invoked
+ void testDbLostAndRecoveredCallback();
+
+ /// @brief Verifies the CB manager's behavior if DB connection is lost
+ ///
+ /// This function creates a CB manager with a back end that supports
+ /// connectivity lost callback. It verifies connectivity by issuing a known
+ /// valid query. Next it simulates connectivity lost by identifying and
+ /// closing the socket connection to the CB backend. It then reissues the
+ /// query and verifies that:
+ /// -# The Query throws DbOperationError (rather than exiting)
+ /// -# The registered DbLostCallback was invoked
+ /// -# The registered DbFailedCallback was invoked
+ void testDbLostAndFailedCallback();
+
+ /// @brief Verifies the CB manager's behavior if DB connection is lost
+ ///
+ /// This function creates a CB manager with a back end that supports
+ /// connectivity lost callback. It verifies connectivity by issuing a known
+ /// valid query. Next it simulates connectivity lost by identifying and
+ /// closing the socket connection to the CB backend. It then reissues the
+ /// query and verifies that:
+ /// -# The Query throws DbOperationError (rather than exiting)
+ /// -# The registered DbLostCallback was invoked
+ /// -# The registered DbRecoveredCallback was invoked after two reconnect
+ /// attempts (once failing and second triggered by timer)
+ void testDbLostAndRecoveredAfterTimeoutCallback();
+
+ /// @brief Verifies the CB manager's behavior if DB connection is lost
+ ///
+ /// This function creates a CB manager with a back end that supports
+ /// connectivity lost callback. It verifies connectivity by issuing a known
+ /// valid query. Next it simulates connectivity lost by identifying and
+ /// closing the socket connection to the CB backend. It then reissues the
+ /// query and verifies that:
+ /// -# The Query throws DbOperationError (rather than exiting)
+ /// -# The registered DbLostCallback was invoked
+ /// -# The registered DbFailedCallback was invoked after two reconnect
+ /// attempts (once failing and second triggered by timer)
+ void testDbLostAndFailedAfterTimeoutCallback();
+
+ /// @brief Callback function registered with the CB manager
+ bool db_lost_callback(util::ReconnectCtlPtr /* not_used */) {
+ return (++db_lost_callback_called_);
+ }
+
+ /// @brief Flag used to detect calls to db_lost_callback function
+ uint32_t db_lost_callback_called_;
+
+ /// @brief Callback function registered with the CB manager
+ bool db_recovered_callback(util::ReconnectCtlPtr /* not_used */) {
+ return (++db_recovered_callback_called_);
+ }
+
+ /// @brief Flag used to detect calls to db_recovered_callback function
+ uint32_t db_recovered_callback_called_;
+
+ /// @brief Callback function registered with the CB manager
+ bool db_failed_callback(util::ReconnectCtlPtr /* not_used */) {
+ return (++db_failed_callback_called_);
+ }
+
+ /// @brief Flag used to detect calls to db_failed_callback function
+ uint32_t db_failed_callback_called_;
+
+ /// The IOService object, used for all ASIO operations.
+ isc::asiolink::IOServicePtr io_service_;
+};
+
+} // namespace test
+} // namespace dhcp
+} // namespace isc
+
+#endif // GENERIC_CONFIG_BACKEND_RECOVERY_H
diff --git a/src/lib/dhcpsrv/testutils/generic_host_data_source_unittest.cc b/src/lib/dhcpsrv/testutils/generic_host_data_source_unittest.cc
new file mode 100644
index 0000000..d4c211c
--- /dev/null
+++ b/src/lib/dhcpsrv/testutils/generic_host_data_source_unittest.cc
@@ -0,0 +1,3891 @@
+// Copyright (C) 2015-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 <database/database_connection.h>
+#include <database/db_exceptions.h>
+#include <dhcp/dhcp6.h>
+#include <dhcp/libdhcp++.h>
+#include <dhcp/option4_addrlst.h>
+#include <dhcp/option6_addrlst.h>
+#include <dhcp/option_int.h>
+#include <dhcp/option_string.h>
+#include <dhcp/option_vendor.h>
+#include <dhcpsrv/host_data_source_factory.h>
+#include <dhcpsrv/testutils/generic_host_data_source_unittest.h>
+#include <dhcpsrv/testutils/host_data_source_utils.h>
+#include <dhcpsrv/testutils/test_utils.h>
+#include <database/testutils/schema.h>
+#include <util/buffer.h>
+
+#include <boost/foreach.hpp>
+#include <gtest/gtest.h>
+
+#include <chrono>
+#include <cstring>
+#include <list>
+#include <sstream>
+#include <string>
+#include <typeinfo>
+
+using namespace std;
+using namespace isc::asiolink;
+using namespace isc::db;
+using namespace isc::db::test;
+using namespace isc::util;
+using namespace isc::data;
+namespace ph = std::placeholders;
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+GenericHostDataSourceTest::GenericHostDataSourceTest()
+ : GenericBackendTest(), hdsptr_() {
+}
+
+GenericHostDataSourceTest::~GenericHostDataSourceTest() {
+ hdsptr_.reset();
+}
+
+bool
+GenericHostDataSourceTest::compareHostsForSort4(const ConstHostPtr& host1,
+ const ConstHostPtr& host2) {
+ if (host1->getIPv4SubnetID() < host2->getIPv4SubnetID()) {
+ return true;
+ }
+ return false;
+}
+
+bool
+GenericHostDataSourceTest::compareHostsForSort6(const ConstHostPtr& host1,
+ const ConstHostPtr& host2) {
+ if (host1->getIPv6SubnetID() < host2->getIPv6SubnetID()) {
+ return true;
+ }
+ return false;
+}
+
+bool
+GenericHostDataSourceTest::compareHostsIdentifier(const ConstHostPtr& host1,
+ const ConstHostPtr& host2) {
+ auto host1_i = host1->getIdentifier();
+ auto host2_i = host2->getIdentifier();
+ auto count1 = host1_i.size();
+ auto count2 = host2_i.size();
+ if (count1 > count2) {
+ count1 = count2;
+ }
+ for (uint8_t i = 0; i < count1; ++i) {
+ if (host1_i[i] != host2_i[i]) {
+ return (host1_i[i] < host2_i[i]);
+ }
+ }
+ return false;
+}
+
+DuidPtr
+GenericHostDataSourceTest::HWAddrToDuid(const HWAddrPtr& hwaddr) {
+ if (!hwaddr) {
+ return (DuidPtr());
+ }
+
+ return (DuidPtr(new DUID(hwaddr->hwaddr_)));
+}
+
+HWAddrPtr
+GenericHostDataSourceTest::DuidToHWAddr(const DuidPtr& duid) {
+ if (!duid) {
+ return (HWAddrPtr());
+ }
+
+ return (HWAddrPtr(new HWAddr(duid->getDuid(), HTYPE_ETHER)));
+}
+
+void
+GenericHostDataSourceTest::addTestOptions(const HostPtr& host,
+ const bool formatted,
+ const AddedOptions& added_options,
+ ConstElementPtr user_context) const {
+
+ OptionDefSpaceContainer defs;
+
+ if ((added_options == DHCP4_ONLY) || (added_options == DHCP4_AND_DHCP6)) {
+ // Add DHCPv4 options.
+ CfgOptionPtr opts = host->getCfgOption4();
+ OptionDescriptor desc =
+ createOption<OptionString>(Option::V4, DHO_BOOT_FILE_NAME,
+ true, formatted, "my-boot-file");
+ desc.setContext(user_context);
+ opts->add(desc, DHCP4_OPTION_SPACE);
+ opts->add(createOption<OptionUint8>(Option::V4, DHO_DEFAULT_IP_TTL,
+ false, formatted, 64),
+ DHCP4_OPTION_SPACE);
+ opts->add(createOption<OptionUint32>(Option::V4, 1, false, formatted, 312131),
+ "vendor-encapsulated-options");
+ opts->add(createAddressOption<Option4AddrLst>(254, false, formatted,
+ "192.0.2.3"), DHCP4_OPTION_SPACE);
+ opts->add(createEmptyOption(Option::V4, 1, true), "isc");
+ opts->add(createAddressOption<Option4AddrLst>(2, false, formatted, "10.0.0.5",
+ "10.0.0.3", "10.0.3.4"), "isc");
+
+ // Add definitions for DHCPv4 non-standard options.
+ defs.addItem(OptionDefinitionPtr(new OptionDefinition(
+ "vendor-encapsulated-1", 1,
+ "vendor-encapsulated-options", "uint32")));
+ defs.addItem(OptionDefinitionPtr(new OptionDefinition(
+ "option-254", 254, DHCP4_OPTION_SPACE,
+ "ipv4-address", true)));
+ defs.addItem(OptionDefinitionPtr(new OptionDefinition("isc-1", 1, "isc", "empty")));
+ defs.addItem(OptionDefinitionPtr(new OptionDefinition("isc-2", 2, "isc", "ipv4-address", true)));
+ }
+
+ if ((added_options == DHCP6_ONLY) || (added_options == DHCP4_AND_DHCP6)) {
+ // Add DHCPv6 options.
+ CfgOptionPtr opts = host->getCfgOption6();
+ OptionDescriptor desc =
+ createOption<OptionString>(Option::V6, D6O_BOOTFILE_URL,
+ true, formatted, "my-boot-file");
+ desc.setContext(user_context);
+ opts->add(desc, DHCP6_OPTION_SPACE);
+ opts->add(createOption<OptionUint32>(Option::V6, D6O_INFORMATION_REFRESH_TIME,
+ false, formatted, 3600),
+ DHCP6_OPTION_SPACE);
+ opts->add(createVendorOption(Option::V6, false, formatted, 2495),
+ DHCP6_OPTION_SPACE);
+ opts->add(createAddressOption<Option6AddrLst>(1024, false, formatted,
+ "2001:db8:1::1"),
+ DHCP6_OPTION_SPACE);
+ opts->add(createEmptyOption(Option::V6, 1, true), "isc2");
+ opts->add(createAddressOption<Option6AddrLst>(2, false, formatted, "3000::1",
+ "3000::2", "3000::3"), "isc2");
+
+ // Add definitions for DHCPv6 non-standard options.
+ defs.addItem(OptionDefinitionPtr(new OptionDefinition(
+ "option-1024", 1024, DHCP6_OPTION_SPACE,
+ "ipv6-address", true)));
+ defs.addItem(OptionDefinitionPtr(new OptionDefinition("option-1", 1, "isc2", "empty")));
+ defs.addItem(OptionDefinitionPtr(new OptionDefinition("option-2", 2, "isc2", "ipv6-address", true)));
+ }
+
+ // Register created "runtime" option definitions. They will be used by a
+ // host data source to convert option data into the appropriate option
+ // classes when the options are retrieved.
+ LibDHCP::setRuntimeOptionDefs(defs);
+}
+
+void
+GenericHostDataSourceTest::testReadOnlyDatabase(const char* valid_db_type) {
+ ASSERT_TRUE(hdsptr_);
+
+ // The database is initially opened in "read-write" mode. We can
+ // insert some data to the database.
+ HostPtr host = HostDataSourceUtils::initializeHost6("2001:db8::1", Host::IDENT_DUID, false);
+ ASSERT_TRUE(host);
+ ASSERT_NO_THROW(hdsptr_->add(host));
+
+ // Subnet id will be used in queries to the database.
+ SubnetID subnet_id = host->getIPv6SubnetID();
+
+ // Make sure that the host has been inserted and that the data can be
+ // retrieved.
+ ConstHostPtr host_by_id =
+ hdsptr_->get6(subnet_id, host->getIdentifierType(),
+ &host->getIdentifier()[0], host->getIdentifier().size());
+ ASSERT_TRUE(host_by_id);
+ ASSERT_NO_FATAL_FAILURE(HostDataSourceUtils::compareHosts(host, host_by_id));
+
+ // Close the database connection and reopen in "read-only" mode as
+ // specified by the "VALID_READONLY_DB" parameter.
+ HostMgr::create();
+ HostMgr::addBackend(connectionString(
+ valid_db_type, VALID_NAME, VALID_HOST, VALID_READONLY_USER,
+ VALID_PASSWORD, VALID_READONLY_DB));
+
+ hdsptr_ = HostMgr::instance().getHostDataSource();
+ ASSERT_NE(hdsptr_->getParameters(), DatabaseConnection::ParameterMap());
+
+ // Check that an attempt to insert new host would result in
+ // exception.
+ HostPtr host2 = HostDataSourceUtils::initializeHost6("2001:db8::2", Host::IDENT_DUID, false);
+ ASSERT_TRUE(host2);
+ ASSERT_THROW(hdsptr_->add(host2), ReadOnlyDb);
+ ASSERT_THROW(hdsptr_->commit(), ReadOnlyDb);
+ ASSERT_THROW(hdsptr_->rollback(), ReadOnlyDb);
+
+ // Reading from the database should still be possible, though.
+ host_by_id =
+ hdsptr_->get6(subnet_id, host->getIdentifierType(),
+ &host->getIdentifier()[0], host->getIdentifier().size());
+ ASSERT_TRUE(host_by_id);
+ ASSERT_NO_FATAL_FAILURE(HostDataSourceUtils::compareHosts(host, host_by_id));
+}
+
+void
+GenericHostDataSourceTest::testBasic4(const Host::IdentifierType& id) {
+ // Make sure we have the pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ // Create a host reservation.
+ HostPtr host = HostDataSourceUtils::initializeHost4("192.0.2.1", id);
+ ASSERT_TRUE(host); // Make sure the host is generate properly.
+ SubnetID subnet = host->getIPv4SubnetID();
+
+ // Try to add it to the host data source.
+ ASSERT_NO_THROW(hdsptr_->add(host));
+
+ // This should not return anything
+ ConstHostPtr from_hds = hdsptr_->get4(subnet, IOAddress("10.10.10.10"));
+ ASSERT_FALSE(from_hds);
+
+ // This time it should return a host
+ from_hds = hdsptr_->get4(subnet, IOAddress("192.0.2.1"));
+ ASSERT_TRUE(from_hds);
+
+ // Finally, let's check if what we got makes any sense.
+ HostDataSourceUtils::compareHosts(host, from_hds);
+}
+
+void
+GenericHostDataSourceTest::testGlobalSubnetId4() {
+ std::vector<uint8_t> ident;
+
+ ident = HostDataSourceUtils::generateIdentifier();
+ SubnetID subnet_id4 = SUBNET_ID_GLOBAL;
+ HostPtr host(new Host(&ident[0], ident.size(), Host::IDENT_DUID,
+ subnet_id4, SUBNET_ID_UNUSED, IOAddress("0.0.0.0")));
+
+ ASSERT_NO_THROW(addTestOptions(host, true, DHCP4_ONLY));
+ (hdsptr_->add(host));
+ //ASSERT_NO_THROW(hdsptr_->add(host));
+
+ // get4(subnet_id, identifier_type, identifier, identifier_size)
+ ConstHostPtr host_by_id = hdsptr_->get4(subnet_id4,
+ host->getIdentifierType(),
+ &host->getIdentifier()[0],
+ host->getIdentifier().size());
+
+ ASSERT_NO_FATAL_FAILURE(HostDataSourceUtils::compareHosts(host, host_by_id));
+
+ // Now try to delete it: del4(subnet4-id, identifier-type, identifier)
+ EXPECT_TRUE(hdsptr_->del4(subnet_id4, Host::IDENT_DUID, &ident[0],
+ ident.size()));
+
+ host_by_id = hdsptr_->get4(subnet_id4, host->getIdentifierType(),
+ &host->getIdentifier()[0],
+ host->getIdentifier().size());
+
+ EXPECT_FALSE(host_by_id);
+}
+
+void GenericHostDataSourceTest::testGlobalSubnetId6() {
+ std::vector<uint8_t> ident;
+
+ ident = HostDataSourceUtils::generateIdentifier();
+ SubnetID subnet_id6 = SUBNET_ID_GLOBAL;
+ HostPtr host(new Host(&ident[0], ident.size(), Host::IDENT_DUID,
+ SUBNET_ID_UNUSED, subnet_id6, IOAddress("0.0.0.0")));
+
+ ASSERT_NO_THROW(addTestOptions(host, true, DHCP6_ONLY));
+ ASSERT_NO_THROW(hdsptr_->add(host));
+
+ // get6(subnet_id, identifier_type, identifier, identifier_size)
+ ConstHostPtr host_by_id = hdsptr_->get6(subnet_id6,
+ host->getIdentifierType(),
+ &host->getIdentifier()[0],
+ host->getIdentifier().size());
+
+ ASSERT_NO_FATAL_FAILURE(HostDataSourceUtils::compareHosts(host, host_by_id));
+
+ // Now try to delete it: del6(subnet6-id, identifier-type, identifier)
+ EXPECT_TRUE(hdsptr_->del6(subnet_id6, Host::IDENT_DUID, &ident[0],
+ ident.size()));
+
+ host_by_id = hdsptr_->get4(subnet_id6, host->getIdentifierType(),
+ &host->getIdentifier()[0],
+ host->getIdentifier().size());
+
+ EXPECT_FALSE(host_by_id);
+}
+
+
+void
+GenericHostDataSourceTest::testMaxSubnetId4() {
+ std::vector<uint8_t> ident;
+
+ ident = HostDataSourceUtils::generateIdentifier();
+ SubnetID subnet_id4 = SUBNET_ID_MAX;
+ HostPtr host(new Host(&ident[0], ident.size(), Host::IDENT_DUID,
+ subnet_id4, SUBNET_ID_UNUSED, IOAddress("0.0.0.0")));
+
+ ASSERT_NO_THROW(addTestOptions(host, true, DHCP4_ONLY));
+ ASSERT_NO_THROW(hdsptr_->add(host));
+
+ // get4(subnet_id, identifier_type, identifier, identifier_size)
+ ConstHostPtr host_by_id = hdsptr_->get4(subnet_id4,
+ host->getIdentifierType(),
+ &host->getIdentifier()[0],
+ host->getIdentifier().size());
+
+ ASSERT_NO_FATAL_FAILURE(HostDataSourceUtils::compareHosts(host, host_by_id));
+
+ // Now try to delete it: del4(subnet4-id, identifier-type, identifier)
+ EXPECT_TRUE(hdsptr_->del4(subnet_id4, Host::IDENT_DUID, &ident[0],
+ ident.size()));
+
+ host_by_id = hdsptr_->get4(subnet_id4, host->getIdentifierType(),
+ &host->getIdentifier()[0],
+ host->getIdentifier().size());
+
+ EXPECT_FALSE(host_by_id);
+}
+
+void
+GenericHostDataSourceTest::testMaxSubnetId6() {
+ std::vector<uint8_t> ident;
+
+ ident = HostDataSourceUtils::generateIdentifier();
+ SubnetID subnet_id6 = SUBNET_ID_MAX;
+ HostPtr host(new Host(&ident[0], ident.size(), Host::IDENT_DUID,
+ SUBNET_ID_UNUSED, subnet_id6, IOAddress("0.0.0.0")));
+
+ ASSERT_NO_THROW(addTestOptions(host, true, DHCP6_ONLY));
+ ASSERT_NO_THROW(hdsptr_->add(host));
+
+ // get6(subnet_id, identifier_type, identifier, identifier_size)
+ ConstHostPtr host_by_id = hdsptr_->get6(subnet_id6,
+ host->getIdentifierType(),
+ &host->getIdentifier()[0],
+ host->getIdentifier().size());
+
+ ASSERT_NO_FATAL_FAILURE(HostDataSourceUtils::compareHosts(host, host_by_id));
+
+ // Now try to delete it: del6(subnet6-id, identifier-type, identifier)
+ EXPECT_TRUE(hdsptr_->del6(subnet_id6, Host::IDENT_DUID, &ident[0],
+ ident.size()));
+
+ host_by_id = hdsptr_->get4(subnet_id6, host->getIdentifierType(),
+ &host->getIdentifier()[0],
+ host->getIdentifier().size());
+
+ EXPECT_FALSE(host_by_id);
+}
+
+void
+GenericHostDataSourceTest::testGetAll4() {
+ // Make sure we have a pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ // Let's create a couple of hosts...
+ const Host::IdentifierType& id = Host::IDENT_HWADDR;
+ HostPtr host1 = HostDataSourceUtils::initializeHost4("192.0.2.1", id);
+ HostPtr host2 = HostDataSourceUtils::initializeHost4("192.0.2.2", id);
+ HostPtr host3 = HostDataSourceUtils::initializeHost4("192.0.2.3", id);
+ HostPtr host4 = HostDataSourceUtils::initializeHost4("192.0.2.4", id);
+
+ // Set them in the same subnets.
+ SubnetID subnet4 = host1->getIPv4SubnetID();
+ host2->setIPv4SubnetID(subnet4);
+ host3->setIPv4SubnetID(subnet4);
+ host4->setIPv4SubnetID(subnet4);
+ SubnetID subnet6 = host1->getIPv6SubnetID();
+ host2->setIPv6SubnetID(subnet6);
+ host3->setIPv6SubnetID(subnet6);
+ host4->setIPv6SubnetID(subnet6);
+
+ // ... and add them to the data source.
+ ASSERT_NO_THROW(hdsptr_->add(host1));
+ ASSERT_NO_THROW(hdsptr_->add(host2));
+ ASSERT_NO_THROW(hdsptr_->add(host3));
+ ASSERT_NO_THROW(hdsptr_->add(host4));
+
+ // And then try to retrieve them back.
+ ConstHostCollection from_hds = hdsptr_->getAll4(subnet4);
+
+ // Make sure we got something back.
+ ASSERT_EQ(4, from_hds.size());
+
+ HostDataSourceUtils::compareHosts(host1, from_hds[0]);
+ HostDataSourceUtils::compareHosts(host2, from_hds[1]);
+ HostDataSourceUtils::compareHosts(host3, from_hds[2]);
+ HostDataSourceUtils::compareHosts(host4, from_hds[3]);
+}
+
+void
+GenericHostDataSourceTest::testGetAll6() {
+ // Make sure we have a pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ // Let's create a couple of hosts...
+ const Host::IdentifierType& id = Host::IDENT_DUID;
+ HostPtr host1 = HostDataSourceUtils::initializeHost6("2001:db8::1", id, false);
+ HostPtr host2 = HostDataSourceUtils::initializeHost6("2001:db8::2", id, false);
+ HostPtr host3 = HostDataSourceUtils::initializeHost6("2001:db8::3", id, false);
+ HostPtr host4 = HostDataSourceUtils::initializeHost6("2001:db8::4", id, false);
+
+ // Set them in the same subnets.
+ SubnetID subnet4 = host1->getIPv4SubnetID();
+ host2->setIPv4SubnetID(subnet4);
+ host3->setIPv4SubnetID(subnet4);
+ host4->setIPv4SubnetID(subnet4);
+ SubnetID subnet6 = host1->getIPv6SubnetID();
+ host2->setIPv6SubnetID(subnet6);
+ host3->setIPv6SubnetID(subnet6);
+ host4->setIPv6SubnetID(subnet6);
+
+ // ... and add them to the data source.
+ ASSERT_NO_THROW(hdsptr_->add(host1));
+ ASSERT_NO_THROW(hdsptr_->add(host2));
+ ASSERT_NO_THROW(hdsptr_->add(host3));
+ ASSERT_NO_THROW(hdsptr_->add(host4));
+
+ // And then try to retrieve them back.
+ ConstHostCollection from_hds = hdsptr_->getAll6(subnet6);
+
+ // Make sure we got something back.
+ ASSERT_EQ(4, from_hds.size());
+
+ HostDataSourceUtils::compareHosts(host1, from_hds[0]);
+ HostDataSourceUtils::compareHosts(host2, from_hds[1]);
+ HostDataSourceUtils::compareHosts(host3, from_hds[2]);
+ HostDataSourceUtils::compareHosts(host4, from_hds[3]);
+}
+
+void
+GenericHostDataSourceTest::testGetAllbyHostname() {
+ // Make sure we have a pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ // Let's create some hosts...
+ Host::IdentifierType id = Host::IDENT_HWADDR;
+ HostPtr host1 = HostDataSourceUtils::initializeHost4("192.0.2.1", id);
+ host1->setHostname("host");
+
+ id = Host::IDENT_DUID;
+ HostPtr host2 = HostDataSourceUtils::initializeHost4("192.0.2.2", id);
+ host2->setHostname("Host");
+
+ HostPtr host3 = HostDataSourceUtils::initializeHost6("2001:db8::1", id, false);
+ host3->setHostname("hOSt");
+
+ HostPtr host4 = HostDataSourceUtils::initializeHost6("2001:db8::2", id, false);
+ host4->setHostname("host.example.com");
+
+ // Now add them all to the host data source.
+ ASSERT_NO_THROW(hdsptr_->add(host1));
+ ASSERT_NO_THROW(hdsptr_->add(host2));
+ ASSERT_NO_THROW(hdsptr_->add(host3));
+ ASSERT_NO_THROW(hdsptr_->add(host4));
+
+ // Retrieve unknown name.
+ ConstHostCollection from_hds = hdsptr_->getAllbyHostname("foo");
+ EXPECT_TRUE(from_hds.empty());
+
+ // Retrieve one reservation.
+ from_hds = hdsptr_->getAllbyHostname("host.example.com");
+ ASSERT_EQ(1, from_hds.size());
+ HostDataSourceUtils::compareHosts(host4, from_hds[0]);
+
+ // Retrieve all reservations with host hostname.
+ from_hds = hdsptr_->getAllbyHostname("host");
+ EXPECT_EQ(3, from_hds.size());
+ bool got1 = false;
+ bool got2 = false;
+ bool got3 = false;
+ for (auto host : from_hds) {
+ if (host->getIdentifierType() == Host::IDENT_HWADDR) {
+ EXPECT_FALSE(got1);
+ got1 = true;
+ HostDataSourceUtils::compareHosts(host1, host);
+ } else if (host->getIPv4Reservation().isV4Zero()) {
+ EXPECT_FALSE(got3);
+ got3 = true;
+ HostDataSourceUtils::compareHosts(host3, host);
+ } else {
+ EXPECT_FALSE(got2);
+ got2 = true;
+ HostDataSourceUtils::compareHosts(host2, host);
+ }
+ }
+ EXPECT_TRUE(got1);
+ EXPECT_TRUE(got2);
+ EXPECT_TRUE(got3);
+}
+
+void
+GenericHostDataSourceTest::testGetAllbyHostnameSubnet4() {
+ // Make sure we have a pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ // Let's create some hosts...
+ Host::IdentifierType id = Host::IDENT_HWADDR;
+ HostPtr host1 = HostDataSourceUtils::initializeHost4("192.0.2.1", id);
+ host1->setHostname("host");
+
+ id = Host::IDENT_DUID;
+ HostPtr host2 = HostDataSourceUtils::initializeHost4("192.0.2.2", id);
+ host2->setHostname("Host");
+ CfgOptionPtr opts = host2->getCfgOption4();
+ OptionDescriptor desc =
+ createOption<OptionString>(Option::V4, DHO_BOOT_FILE_NAME,
+ true, false, "my-boot-file");
+ opts->add(desc, DHCP4_OPTION_SPACE);
+
+ HostPtr host3 = HostDataSourceUtils::initializeHost4("192.0.2.3", id);
+ host3->setHostname("hOSt");
+
+ HostPtr host4 = HostDataSourceUtils::initializeHost4("192.0.2.4", id);
+ host4->setHostname("host.example.com");
+
+ HostPtr host5 = HostDataSourceUtils::initializeHost4("192.0.2.5", id);
+
+ // Set them in the same subnet at the exception of host5.
+ SubnetID subnet4 = host1->getIPv4SubnetID();
+ host2->setIPv4SubnetID(subnet4);
+ host3->setIPv4SubnetID(subnet4);
+ host4->setIPv4SubnetID(subnet4);
+ SubnetID subnet6 = host1->getIPv6SubnetID();
+ host2->setIPv6SubnetID(subnet6);
+ host3->setIPv6SubnetID(subnet6);
+ host4->setIPv6SubnetID(subnet6);
+
+ // Now add them all to the host data source.
+ ASSERT_NO_THROW(hdsptr_->add(host1));
+ ASSERT_NO_THROW(hdsptr_->add(host2));
+ ASSERT_NO_THROW(hdsptr_->add(host3));
+ ASSERT_NO_THROW(hdsptr_->add(host4));
+ ASSERT_NO_THROW(hdsptr_->add(host5));
+
+ // Retrieve unknown name.
+ ConstHostCollection from_hds = hdsptr_->getAllbyHostname4("foo", subnet4);
+ EXPECT_TRUE(from_hds.empty());
+
+ // Retrieve one reservation.
+ from_hds = hdsptr_->getAllbyHostname4("host.example.com", subnet4);
+ ASSERT_EQ(1, from_hds.size());
+ HostDataSourceUtils::compareHosts(host4, from_hds[0]);
+
+ // Check that the subnet is checked.
+ from_hds = hdsptr_->getAllbyHostname4("host.example.com", subnet4 + 1);
+ EXPECT_TRUE(from_hds.empty());
+
+ // Retrieve all reservations with host hostname.
+ from_hds = hdsptr_->getAllbyHostname4("host", subnet4);
+ EXPECT_EQ(3, from_hds.size());
+ bool got1 = false;
+ bool got2 = false;
+ bool got3 = false;
+ for (auto host : from_hds) {
+ if (host->getIdentifierType() == Host::IDENT_HWADDR) {
+ EXPECT_FALSE(got1);
+ got1 = true;
+ HostDataSourceUtils::compareHosts(host1, host);
+ } else if (!host->getCfgOption4()->empty()) {
+ EXPECT_FALSE(got2);
+ got2 = true;
+ HostDataSourceUtils::compareHosts(host2, host);
+ } else {
+ EXPECT_FALSE(got3);
+ got3 = true;
+ HostDataSourceUtils::compareHosts(host3, host);
+ }
+ }
+ EXPECT_TRUE(got1);
+ EXPECT_TRUE(got2);
+ EXPECT_TRUE(got3);
+}
+
+void
+GenericHostDataSourceTest::testGetAllbyHostnameSubnet6() {
+ // Make sure we have a pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ // Let's create some hosts...
+ Host::IdentifierType id = Host::IDENT_HWADDR;
+ HostPtr host1 = HostDataSourceUtils::initializeHost6("2001:db8::1", id, false);
+ host1->setHostname("host");
+
+ id = Host::IDENT_DUID;
+ HostPtr host2 = HostDataSourceUtils::initializeHost6("2001:db8::2", id, false);
+ host2->setHostname("Host");
+ CfgOptionPtr opts = host2->getCfgOption6();
+ OptionDescriptor desc =
+ createOption<OptionString>(Option::V6, D6O_BOOTFILE_URL,
+ true, true, "my-boot-file");
+ opts->add(desc, DHCP6_OPTION_SPACE);
+
+ HostPtr host3 = HostDataSourceUtils::initializeHost6("2001:db8::3", id, false);
+ host3->setHostname("hOSt");
+
+ HostPtr host4 = HostDataSourceUtils::initializeHost6("2001:db8::4", id, false);
+ host4->setHostname("host.example.com");
+
+ HostPtr host5 = HostDataSourceUtils::initializeHost6("2001:db8::5", id, false);
+
+ // Set them in the same subnet at the exception of host5.
+ SubnetID subnet4 = host1->getIPv4SubnetID();
+ host2->setIPv4SubnetID(subnet4);
+ host3->setIPv4SubnetID(subnet4);
+ host4->setIPv4SubnetID(subnet4);
+ SubnetID subnet6 = host1->getIPv6SubnetID();
+ host2->setIPv6SubnetID(subnet6);
+ host3->setIPv6SubnetID(subnet6);
+ host4->setIPv6SubnetID(subnet6);
+
+ // Now add them all to the host data source.
+ ASSERT_NO_THROW(hdsptr_->add(host1));
+ ASSERT_NO_THROW(hdsptr_->add(host2));
+ ASSERT_NO_THROW(hdsptr_->add(host3));
+ ASSERT_NO_THROW(hdsptr_->add(host4));
+ ASSERT_NO_THROW(hdsptr_->add(host5));
+
+ // Retrieve unknown name.
+ ConstHostCollection from_hds = hdsptr_->getAllbyHostname6("foo", subnet6);
+ EXPECT_TRUE(from_hds.empty());
+
+ // Retrieve one reservation.
+ from_hds = hdsptr_->getAllbyHostname6("host.example.com", subnet6);
+ ASSERT_EQ(1, from_hds.size());
+ HostDataSourceUtils::compareHosts(host4, from_hds[0]);
+
+ // Check that the subnet is checked.
+ from_hds = hdsptr_->getAllbyHostname6("host.example.com", subnet6 + 1);
+ EXPECT_TRUE(from_hds.empty());
+
+ // Retrieve all reservations with host hostname.
+ from_hds = hdsptr_->getAllbyHostname6("host", subnet6);
+ EXPECT_EQ(3, from_hds.size());
+ bool got1 = false;
+ bool got2 = false;
+ bool got3 = false;
+ for (auto host : from_hds) {
+ if (host->getIdentifierType() == Host::IDENT_HWADDR) {
+ EXPECT_FALSE(got1);
+ got1 = true;
+ HostDataSourceUtils::compareHosts(host1, host);
+ } else if (!host->getCfgOption6()->empty()) {
+ EXPECT_FALSE(got2);
+ got2 = true;
+ HostDataSourceUtils::compareHosts(host2, host);
+ } else {
+ EXPECT_FALSE(got3);
+ got3 = true;
+ HostDataSourceUtils::compareHosts(host3, host);
+ }
+ }
+ EXPECT_TRUE(got1);
+ EXPECT_TRUE(got2);
+ EXPECT_TRUE(got3);
+}
+
+void
+GenericHostDataSourceTest::testGetPage4() {
+ // Make sure we have a pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ // Let's create some hosts...
+ IOAddress addr("192.0.2.0");
+ SubnetID subnet4(4);
+ SubnetID subnet6(6);
+ const Host::IdentifierType& id = Host::IDENT_DUID;
+ for (unsigned i = 0; i < 25; ++i) {
+ addr = IOAddress::increase(addr);
+
+ HostPtr host = HostDataSourceUtils::initializeHost4(addr.toText(), id);
+ host->setIPv4SubnetID(subnet4);
+ host->setIPv6SubnetID(subnet6);
+
+ ASSERT_NO_THROW(hdsptr_->add(host));
+ }
+
+ // Get first page.
+ size_t idx(1);
+ uint64_t host_id(0);
+ HostPageSize page_size(10);
+ ConstHostCollection page;
+ ASSERT_NO_THROW(page = hdsptr_->getPage4(subnet4, idx, host_id, page_size));
+ ASSERT_EQ(10, page.size());
+ host_id = page[9]->getHostId();
+ ASSERT_NE(0, host_id);
+
+ // Get second and last pages.
+ ASSERT_NO_THROW(page = hdsptr_->getPage4(subnet4, idx, host_id, page_size));
+ ASSERT_EQ(10, page.size());
+ host_id = page[9]->getHostId();
+ ASSERT_NO_THROW(page = hdsptr_->getPage4(subnet4, idx, host_id, page_size));
+ ASSERT_EQ(5, page.size());
+ host_id = page[4]->getHostId();
+
+ // Verify we have everything.
+ ASSERT_NO_THROW(page = hdsptr_->getPage4(subnet4, idx, host_id, page_size));
+ ASSERT_EQ(0, page.size());
+ host_id = 0;
+
+ // Other subnets are empty.
+ ASSERT_NO_THROW(page = hdsptr_->getPage4(subnet6, idx, host_id, page_size));
+ ASSERT_EQ(0, page.size());
+}
+
+void
+GenericHostDataSourceTest::testGetPage6() {
+ // Make sure we have a pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ // Let's create some hosts...
+ IOAddress addr("2001:db8:1::");
+ SubnetID subnet4(4);
+ SubnetID subnet6(6);
+ const Host::IdentifierType& id = Host::IDENT_HWADDR;
+ for (unsigned i = 0; i < 25; ++i) {
+ addr = IOAddress::increase(addr);
+
+ HostPtr host = HostDataSourceUtils::initializeHost6(addr.toText(), id, false);
+ host->setIPv4SubnetID(subnet4);
+ host->setIPv6SubnetID(subnet6);
+
+ ASSERT_NO_THROW(hdsptr_->add(host));
+ }
+
+ // Get first page.
+ size_t idx(1);
+ uint64_t host_id(0);
+ HostPageSize page_size(10);
+ ConstHostCollection page;
+ ASSERT_NO_THROW(page = hdsptr_->getPage6(subnet6, idx, host_id, page_size));
+ ASSERT_EQ(10, page.size());
+ host_id = page[9]->getHostId();
+ ASSERT_NE(0, host_id);
+
+ // Get second and last pages.
+ ASSERT_NO_THROW(page = hdsptr_->getPage6(subnet6, idx, host_id, page_size));
+ ASSERT_EQ(10, page.size());
+ host_id = page[9]->getHostId();
+ ASSERT_NO_THROW(page = hdsptr_->getPage6(subnet6, idx, host_id, page_size));
+ ASSERT_EQ(5, page.size());
+ host_id = page[4]->getHostId();
+
+ // Verify we have everything.
+ ASSERT_NO_THROW(page = hdsptr_->getPage6(subnet6, idx, host_id, page_size));
+ ASSERT_EQ(0, page.size());
+ host_id = 0;
+
+ // Other subnets are empty.
+ ASSERT_NO_THROW(page = hdsptr_->getPage6(subnet4, idx, host_id, page_size));
+ ASSERT_EQ(0, page.size());
+}
+
+void
+GenericHostDataSourceTest::testGetPageLimit4(const Host::IdentifierType& id) {
+ // From the ticket: add 5 hosts each with 3 options.
+ // call getPage4 with limit of 4.
+ // The first page should return 4 hosts,
+ // the second should return one host.
+
+ // Make sure we have a pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ // Let's create some hosts...
+ IOAddress addr("192.0.2.0");
+ SubnetID subnet4(4);
+ SubnetID subnet6(6);
+ for (unsigned i = 0; i < 5; ++i) {
+ addr = IOAddress::increase(addr);
+
+ HostPtr host = HostDataSourceUtils::initializeHost4(addr.toText(), id);
+ host->setIPv4SubnetID(subnet4);
+ host->setIPv6SubnetID(subnet6);
+
+ // Add DHCPv4 options.
+ CfgOptionPtr opts = host->getCfgOption4();
+ OptionDescriptor desc =
+ createOption<OptionString>(Option::V4, DHO_BOOT_FILE_NAME,
+ true, false, "my-boot-file");
+ opts->add(desc, DHCP4_OPTION_SPACE);
+ opts->add(createOption<OptionUint8>(Option::V4, DHO_DEFAULT_IP_TTL,
+ false, false, 64 + i),
+ DHCP4_OPTION_SPACE);
+ opts->add(createEmptyOption(Option::V4, 1, true), "isc");
+
+ ASSERT_NO_THROW(hdsptr_->add(host));
+ }
+
+ // Get first page.
+ size_t idx(1);
+ uint64_t host_id(0);
+ HostPageSize page_size(4);
+ ConstHostCollection page;
+ ASSERT_NO_THROW(page = hdsptr_->getPage4(subnet4, idx, host_id, page_size));
+ ASSERT_EQ(4, page.size());
+ host_id = page[3]->getHostId();
+ ASSERT_NE(0, host_id);
+
+ // Get second and last pages.
+ ASSERT_NO_THROW(page = hdsptr_->getPage4(subnet4, idx, host_id, page_size));
+ ASSERT_EQ(1, page.size());
+ host_id = page[0]->getHostId();
+
+ // Verify we have everything.
+ ASSERT_NO_THROW(page = hdsptr_->getPage4(subnet4, idx, host_id, page_size));
+ ASSERT_EQ(0, page.size());
+}
+
+void
+GenericHostDataSourceTest::testGetPageLimit6(const Host::IdentifierType& id) {
+ // From the ticket: add several v6 hosts with multiple address/prefix
+ // reservations and multiple options.
+ // Get hosts by page with page size 1.
+ // Make sure all address/prefix reservations are returned.
+ // Make sure all options are returned as expected.
+
+ // Make sure we have a pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ // Let's create some hosts...
+ IOAddress addr("2001:db8:1::");
+ SubnetID subnet4(4);
+ SubnetID subnet6(6);
+
+ vector<HostPtr> hosts;
+
+ for (unsigned i = 0; i < 5; ++i) {
+ addr = IOAddress::increase(addr);
+ ostringstream pref;
+ pref << "2001:db8:2:" << 10 + i << "::";
+
+ HostPtr host = HostDataSourceUtils::initializeHost6(addr.toText(), id, false);
+ host->setIPv4SubnetID(subnet4);
+ host->setIPv6SubnetID(subnet6);
+
+ // Add address/prefix.
+ addr = IOAddress::increase(addr);
+ IPv6Resrv resva(IPv6Resrv::TYPE_NA, addr, 128);
+ host->addReservation(resva);
+ IPv6Resrv resvp(IPv6Resrv::TYPE_PD, IOAddress(pref.str()), 64);
+ host->addReservation(resvp);
+
+ // Add DHCPv6 options.
+ CfgOptionPtr opts = host->getCfgOption6();
+ OptionDescriptor desc =
+ createOption<OptionString>(Option::V6, D6O_BOOTFILE_URL,
+ true, false, "my-boot-file");
+ opts->add(desc, DHCP6_OPTION_SPACE);
+ opts->add(createOption<OptionUint32>(Option::V6,
+ D6O_INFORMATION_REFRESH_TIME,
+ false, false, 3600 + i),
+ DHCP6_OPTION_SPACE);
+ opts->add(createAddressOption<Option6AddrLst>(D6O_SIP_SERVERS_ADDR,
+ false, false,
+ addr.toText()),
+ DHCP6_OPTION_SPACE);
+
+ ASSERT_NO_THROW(hdsptr_->add(host));
+ hosts.push_back(host);
+ }
+
+ // Get first page.
+ size_t idx(1);
+ uint64_t host_id(0);
+ HostPageSize page_size(4);
+ ConstHostCollection page;
+ ConstHostCollection all_pages;
+ ASSERT_NO_THROW(page = hdsptr_->getPage6(subnet6, idx, host_id, page_size));
+ ASSERT_EQ(4, page.size());
+ host_id = page[3]->getHostId();
+ ASSERT_NE(0, host_id);
+
+ std::copy(page.begin(), page.end(), std::back_inserter(all_pages));
+
+ // Get second and last pages.
+ ASSERT_NO_THROW(page = hdsptr_->getPage6(subnet6, idx, host_id, page_size));
+ ASSERT_EQ(1, page.size());
+ host_id = page[0]->getHostId();
+
+ std::copy(page.begin(), page.end(), std::back_inserter(all_pages));
+
+ // Verify we have everything.
+ ASSERT_NO_THROW(page = hdsptr_->getPage6(subnet6, idx, host_id, page_size));
+ ASSERT_EQ(0, page.size());
+
+ // hosts are sorted by generated host_id (which is an auto increment for
+ // MySql and PostgreSql) so the hosts must be sorted by host identifier
+ std::sort(all_pages.begin(), all_pages.end(), compareHostsIdentifier);
+
+ // Verify we got what we expected.
+ for (size_t i = 0; i < 5; ++i) {
+ HostDataSourceUtils::compareHosts(hosts[i], all_pages[i]);
+ }
+}
+
+void
+GenericHostDataSourceTest::testGetPage4Subnets() {
+ // From the ticket: add one host to subnet1, add one host to subnet2.
+ // repeat 5 times. Get hosts from subnet1 with page size 3.
+ // Make sure the right hosts are returned and in expected page
+ // sizes (3, then 2).
+
+ // Make sure we have a pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ // Let's create some hosts...
+ const Host::IdentifierType& id = Host::IDENT_HWADDR;
+ IOAddress addr("192.0.2.0");
+ SubnetID subnet4(4);
+ SubnetID subnet6(6);
+ vector<HostPtr> hosts;
+ for (unsigned i = 0; i < 10; ++i) {
+ addr = IOAddress::increase(addr);
+
+ HostPtr host = HostDataSourceUtils::initializeHost4(addr.toText(), id);
+ host->setIPv4SubnetID(subnet4 + (i & 1));
+ host->setIPv6SubnetID(subnet6 + (i & 1));
+
+ ASSERT_NO_THROW(hdsptr_->add(host));
+ hosts.push_back(host);
+ }
+
+ // First subnet.
+
+ // Get first page.
+ size_t idx(1);
+ uint64_t host_id(0);
+ HostPageSize page_size(3);
+ ConstHostCollection page;
+ ConstHostCollection all_pages;
+ ASSERT_NO_THROW(page = hdsptr_->getPage4(subnet4, idx, host_id, page_size));
+ ASSERT_EQ(3, page.size());
+ host_id = page[2]->getHostId();
+ ASSERT_NE(0, host_id);
+
+ std::copy(page.begin(), page.end(), std::back_inserter(all_pages));
+
+ // Get second and last pages.
+ ASSERT_NO_THROW(page = hdsptr_->getPage4(subnet4, idx, host_id, page_size));
+ ASSERT_EQ(2, page.size());
+ host_id = page[1]->getHostId();
+
+ std::copy(page.begin(), page.end(), std::back_inserter(all_pages));
+
+ // Verify we have everything.
+ ASSERT_NO_THROW(page = hdsptr_->getPage4(subnet4, idx, host_id, page_size));
+ ASSERT_EQ(0, page.size());
+
+ // hosts are sorted by generated host_id (which is an auto increment for
+ // MySql and PostgreSql) so the hosts must be sorted by host identifier
+ std::sort(all_pages.begin(), all_pages.end(), compareHostsIdentifier);
+
+ // Verify we got what we expected.
+ for (size_t i = 0; i < 5; ++i) {
+ HostDataSourceUtils::compareHosts(hosts[i * 2], all_pages[i]);
+ }
+
+ all_pages.clear();
+
+ // Second subnet.
+ ++subnet4;
+
+ // Get first page.
+ idx = 0;
+ host_id = 0;
+ ASSERT_NO_THROW(page = hdsptr_->getPage4(subnet4, idx, host_id, page_size));
+ ASSERT_EQ(3, page.size());
+ host_id = page[2]->getHostId();
+ ASSERT_NE(0, host_id);
+
+ std::copy(page.begin(), page.end(), std::back_inserter(all_pages));
+
+ // Get second and last pages.
+ ASSERT_NO_THROW(page = hdsptr_->getPage4(subnet4, idx, host_id, page_size));
+ ASSERT_EQ(2, page.size());
+ host_id = page[1]->getHostId();
+
+ std::copy(page.begin(), page.end(), std::back_inserter(all_pages));
+
+ // Verify we have everything.
+ ASSERT_NO_THROW(page = hdsptr_->getPage4(subnet4, idx, host_id, page_size));
+ ASSERT_EQ(0, page.size());
+
+ // hosts are sorted by generated host_id (which is an auto increment for
+ // MySql and PostgreSql) so the hosts must be sorted by host identifier
+ std::sort(all_pages.begin(), all_pages.end(), compareHostsIdentifier);
+
+ // Verify we got what we expected.
+ for (size_t i = 0; i < 5; ++i) {
+ HostDataSourceUtils::compareHosts(hosts[i * 2 + 1], all_pages[i]);
+ }
+}
+
+void
+GenericHostDataSourceTest::testGetPage6Subnets() {
+ // From the ticket: add one host to subnet1, add one host to subnet2.
+ // repeat 5 times. Get hosts from subnet1 with page size 3.
+ // Make sure the right hosts are returned and in expected page
+ // sizes (3, then 2).
+
+ // Make sure we have a pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ // Let's create some hosts...
+ const Host::IdentifierType& id = Host::IDENT_DUID;
+ IOAddress addr("2001:db8:1::");
+ SubnetID subnet4(4);
+ SubnetID subnet6(6);
+ vector<HostPtr> hosts;
+ for (unsigned i = 0; i < 10; ++i) {
+ addr = IOAddress::increase(addr);
+
+ HostPtr host = HostDataSourceUtils::initializeHost6(addr.toText(), id, false);
+ host->setIPv4SubnetID(subnet4 + (i & 1));
+ host->setIPv6SubnetID(subnet6 + (i & 1));
+
+ ASSERT_NO_THROW(hdsptr_->add(host));
+ hosts.push_back(host);
+ }
+
+ // First subnet.
+
+ // Get first page.
+ size_t idx(1);
+ uint64_t host_id(0);
+ HostPageSize page_size(3);
+ ConstHostCollection page;
+ ConstHostCollection all_pages;
+ ASSERT_NO_THROW(page = hdsptr_->getPage6(subnet6, idx, host_id, page_size));
+ ASSERT_EQ(3, page.size());
+ host_id = page[2]->getHostId();
+ ASSERT_NE(0, host_id);
+
+ std::copy(page.begin(), page.end(), std::back_inserter(all_pages));
+
+ // Get second and last pages.
+ ASSERT_NO_THROW(page = hdsptr_->getPage6(subnet6, idx, host_id, page_size));
+ ASSERT_EQ(2, page.size());
+ host_id = page[1]->getHostId();
+
+ std::copy(page.begin(), page.end(), std::back_inserter(all_pages));
+
+ // Verify we have everything.
+ ASSERT_NO_THROW(page = hdsptr_->getPage6(subnet6, idx, host_id, page_size));
+ ASSERT_EQ(0, page.size());
+
+ // hosts are sorted by generated host_id (which is an auto increment for
+ // MySql and PostgreSql) so the hosts must be sorted by host identifier
+ std::sort(all_pages.begin(), all_pages.end(), compareHostsIdentifier);
+
+ // Verify we got what we expected.
+ for (size_t i = 0; i < 5; ++i) {
+ HostDataSourceUtils::compareHosts(hosts[i * 2], all_pages[i]);
+ }
+
+ all_pages.clear();
+
+ // Second subnet.
+ ++subnet6;
+
+ // Get first page.
+ idx = 0;
+ host_id = 0;
+ ASSERT_NO_THROW(page = hdsptr_->getPage6(subnet6, idx, host_id, page_size));
+ ASSERT_EQ(3, page.size());
+ host_id = page[2]->getHostId();
+ ASSERT_NE(0, host_id);
+
+ std::copy(page.begin(), page.end(), std::back_inserter(all_pages));
+
+ // Get second and last pages.
+ ASSERT_NO_THROW(page = hdsptr_->getPage6(subnet6, idx, host_id, page_size));
+ ASSERT_EQ(2, page.size());
+ host_id = page[1]->getHostId();
+
+ std::copy(page.begin(), page.end(), std::back_inserter(all_pages));
+
+ // Verify we have everything.
+ ASSERT_NO_THROW(page = hdsptr_->getPage6(subnet6, idx, host_id, page_size));
+ ASSERT_EQ(0, page.size());
+
+ // hosts are sorted by generated host_id (which is an auto increment for
+ // MySql and PostgreSql) so the hosts must be sorted by host identifier
+ std::sort(all_pages.begin(), all_pages.end(), compareHostsIdentifier);
+
+ // Verify we got what we expected.
+ for (size_t i = 0; i < 5; ++i) {
+ HostDataSourceUtils::compareHosts(hosts[i * 2 + 1], all_pages[i]);
+ }
+}
+
+void
+GenericHostDataSourceTest::testGetPage4All() {
+ // From the ticket: add one host to subnet1, add one host to subnet2.
+ // repeat 4 times. Get all hosts with page size 3.
+ // Make sure all hosts are returned and in expected page
+ // sizes (3, 3, then 2).
+
+ // Make sure we have a pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ // Let's create some hosts...
+ const Host::IdentifierType& id = Host::IDENT_HWADDR;
+ IOAddress addr("192.0.2.0");
+ SubnetID subnet4(4);
+ SubnetID subnet6(6);
+ vector<HostPtr> hosts;
+ for (unsigned i = 0; i < 8; ++i) {
+ addr = IOAddress::increase(addr);
+
+ HostPtr host = HostDataSourceUtils::initializeHost4(addr.toText(), id);
+ host->setIPv4SubnetID(subnet4 + (i & 1));
+ host->setIPv6SubnetID(subnet6 + (i & 1));
+
+ ASSERT_NO_THROW(hdsptr_->add(host));
+ hosts.push_back(host);
+ }
+
+ // Get first page.
+ size_t idx(1);
+ uint64_t host_id(0);
+ HostPageSize page_size(3);
+ ConstHostCollection page;
+ ConstHostCollection all_pages;
+ ASSERT_NO_THROW(page = hdsptr_->getPage4(idx, host_id, page_size));
+ ASSERT_EQ(3, page.size());
+ host_id = page[2]->getHostId();
+ ASSERT_NE(0, host_id);
+
+ std::copy(page.begin(), page.end(), std::back_inserter(all_pages));
+
+ // Get second page.
+ ASSERT_NO_THROW(page = hdsptr_->getPage4(idx, host_id, page_size));
+ ASSERT_EQ(3, page.size());
+ host_id = page[2]->getHostId();
+
+ std::copy(page.begin(), page.end(), std::back_inserter(all_pages));
+
+ // Get last page.
+ ASSERT_NO_THROW(page = hdsptr_->getPage4(idx, host_id, page_size));
+ ASSERT_EQ(2, page.size());
+ host_id = page[1]->getHostId();
+
+ std::copy(page.begin(), page.end(), std::back_inserter(all_pages));
+
+ // Verify we have everything.
+ ASSERT_NO_THROW(page = hdsptr_->getPage4(idx, host_id, page_size));
+ ASSERT_EQ(0, page.size());
+
+ // hosts are sorted by generated host_id (which is an auto increment for
+ // MySql and PostgreSql) so the hosts must be sorted by host identifier
+ std::sort(all_pages.begin(), all_pages.end(), compareHostsIdentifier);
+
+ // Verify we got what we expected.
+ for (size_t i = 0; i < 8; ++i) {
+ HostDataSourceUtils::compareHosts(hosts[i], all_pages[i]);
+ }
+}
+
+void
+GenericHostDataSourceTest::testGetPage6All() {
+ // From the ticket: add one host to subnet1, add one host to subnet2.
+ // repeat 4 times. Get all hosts with page size 3.
+ // Make sure all hosts are returned and in expected page
+ // sizes (3, 3, then 2).
+
+ // Make sure we have a pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ // Let's create some hosts...
+ const Host::IdentifierType& id = Host::IDENT_DUID;
+ IOAddress addr("2001:db8:1::");
+ SubnetID subnet4(4);
+ SubnetID subnet6(6);
+ vector<HostPtr> hosts;
+ for (unsigned i = 0; i < 8; ++i) {
+ addr = IOAddress::increase(addr);
+
+ HostPtr host = HostDataSourceUtils::initializeHost6(addr.toText(), id, false);
+ host->setIPv4SubnetID(subnet4 + (i & 1));
+ host->setIPv6SubnetID(subnet6 + (i & 1));
+
+ ASSERT_NO_THROW(hdsptr_->add(host));
+ hosts.push_back(host);
+ }
+
+ // Get first page.
+ size_t idx(1);
+ uint64_t host_id(0);
+ HostPageSize page_size(3);
+ ConstHostCollection page;
+ ConstHostCollection all_pages;
+ ASSERT_NO_THROW(page = hdsptr_->getPage6(idx, host_id, page_size));
+ ASSERT_EQ(3, page.size());
+ host_id = page[2]->getHostId();
+ ASSERT_NE(0, host_id);
+
+ std::copy(page.begin(), page.end(), std::back_inserter(all_pages));
+
+ // Get second page.
+ ASSERT_NO_THROW(page = hdsptr_->getPage6(idx, host_id, page_size));
+ ASSERT_EQ(3, page.size());
+ host_id = page[2]->getHostId();
+
+ std::copy(page.begin(), page.end(), std::back_inserter(all_pages));
+
+ // Get last page.
+ ASSERT_NO_THROW(page = hdsptr_->getPage6(idx, host_id, page_size));
+ ASSERT_EQ(2, page.size());
+ host_id = page[1]->getHostId();
+
+ std::copy(page.begin(), page.end(), std::back_inserter(all_pages));
+
+ // Verify we have everything.
+ ASSERT_NO_THROW(page = hdsptr_->getPage6(idx, host_id, page_size));
+ ASSERT_EQ(0, page.size());
+
+ // hosts are sorted by generated host_id (which is an auto increment for
+ // MySql and PostgreSql) so the hosts must be sorted by host identifier
+ std::sort(all_pages.begin(), all_pages.end(), compareHostsIdentifier);
+
+ // Verify we got what we expected.
+ for (size_t i = 0; i < 8; ++i) {
+ HostDataSourceUtils::compareHosts(hosts[i], all_pages[i]);
+ }
+}
+
+void
+GenericHostDataSourceTest::testGetByIPv4(const Host::IdentifierType& id) {
+ // Make sure we have a pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ // Let's create a couple of hosts...
+ HostPtr host1 = HostDataSourceUtils::initializeHost4("192.0.2.1", id);
+ HostPtr host2 = HostDataSourceUtils::initializeHost4("192.0.2.2", id);
+ HostPtr host3 = HostDataSourceUtils::initializeHost4("192.0.2.3", id);
+ HostPtr host4 = HostDataSourceUtils::initializeHost4("192.0.2.4", id);
+
+ // ... and add them to the data source.
+ ASSERT_NO_THROW(hdsptr_->add(host1));
+ ASSERT_NO_THROW(hdsptr_->add(host2));
+ ASSERT_NO_THROW(hdsptr_->add(host3));
+ ASSERT_NO_THROW(hdsptr_->add(host4));
+
+ SubnetID subnet1 = host1->getIPv4SubnetID();
+ SubnetID subnet2 = host2->getIPv4SubnetID();
+ SubnetID subnet3 = host3->getIPv4SubnetID();
+ SubnetID subnet4 = host4->getIPv4SubnetID();
+
+ // And then try to retrieve them back.
+ ConstHostPtr from_hds1 = hdsptr_->get4(subnet1, IOAddress("192.0.2.1"));
+ ConstHostPtr from_hds2 = hdsptr_->get4(subnet2, IOAddress("192.0.2.2"));
+ ConstHostPtr from_hds3 = hdsptr_->get4(subnet3, IOAddress("192.0.2.3"));
+ ConstHostPtr from_hds4 = hdsptr_->get4(subnet4, IOAddress("192.0.2.4"));
+
+ // Make sure we got something back.
+ ASSERT_TRUE(from_hds1);
+ ASSERT_TRUE(from_hds2);
+ ASSERT_TRUE(from_hds3);
+ ASSERT_TRUE(from_hds4);
+
+ // Then let's check that what we got seems correct.
+ HostDataSourceUtils::compareHosts(host1, from_hds1);
+ HostDataSourceUtils::compareHosts(host2, from_hds2);
+ HostDataSourceUtils::compareHosts(host3, from_hds3);
+ HostDataSourceUtils::compareHosts(host4, from_hds4);
+
+ // Ok, finally let's check that getting by a different address
+ // will not work.
+ EXPECT_FALSE(hdsptr_->get4(subnet1, IOAddress("192.0.1.5")));
+}
+
+void
+GenericHostDataSourceTest::testGet4ByIdentifier(
+ const Host::IdentifierType& identifier_type) {
+ // Make sure we have a pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ HostPtr host1 = HostDataSourceUtils::initializeHost4("192.0.2.1", identifier_type);
+ HostPtr host2 = HostDataSourceUtils::initializeHost4("192.0.2.2", identifier_type);
+
+ // Sanity check: make sure the hosts have different identifiers..
+ ASSERT_FALSE(host1->getIdentifier() == host2->getIdentifier());
+
+ // Try to add both of them to the host data source.
+ ASSERT_NO_THROW(hdsptr_->add(host1));
+ ASSERT_NO_THROW(hdsptr_->add(host2));
+
+ SubnetID subnet1 = host1->getIPv4SubnetID();
+ SubnetID subnet2 = host2->getIPv4SubnetID();
+
+ ConstHostPtr from_hds1 =
+ hdsptr_->get4(subnet1, identifier_type, &host1->getIdentifier()[0],
+ host1->getIdentifier().size());
+
+ ConstHostPtr from_hds2 =
+ hdsptr_->get4(subnet2, identifier_type, &host2->getIdentifier()[0],
+ host2->getIdentifier().size());
+
+ // Now let's check if we got what we expected.
+ ASSERT_TRUE(from_hds1);
+ ASSERT_TRUE(from_hds2);
+ HostDataSourceUtils::compareHosts(host1, from_hds1);
+ HostDataSourceUtils::compareHosts(host2, from_hds2);
+}
+
+void
+GenericHostDataSourceTest::testHWAddrNotClientId() {
+ // Make sure we have a pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ // Create a host with HW address
+ HostPtr host = HostDataSourceUtils::initializeHost4("192.0.2.1", Host::IDENT_HWADDR);
+ ASSERT_TRUE(host->getHWAddress());
+ ASSERT_FALSE(host->getDuid());
+
+ // Try to add it to the host data source.
+ ASSERT_NO_THROW(hdsptr_->add(host));
+
+ SubnetID subnet = host->getIPv4SubnetID();
+
+ DuidPtr duid = HWAddrToDuid(host->getHWAddress());
+
+ // Get the host by HW address (should succeed)
+ ConstHostPtr by_hwaddr =
+ hdsptr_->get4(subnet, Host::IDENT_HWADDR, &host->getIdentifier()[0],
+ host->getIdentifier().size());
+
+ // Get the host by DUID (should fail)
+ ConstHostPtr by_duid =
+ hdsptr_->get4(subnet, Host::IDENT_DUID, &host->getIdentifier()[0],
+ host->getIdentifier().size());
+
+ // Now let's check if we got what we expected.
+ EXPECT_TRUE(by_hwaddr);
+ EXPECT_FALSE(by_duid);
+}
+
+void
+GenericHostDataSourceTest::testClientIdNotHWAddr() {
+ // Make sure we have a pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ // Create a host with client-id
+ HostPtr host = HostDataSourceUtils::initializeHost4("192.0.2.1", Host::IDENT_DUID);
+ ASSERT_FALSE(host->getHWAddress());
+ ASSERT_TRUE(host->getDuid());
+
+ // Try to add it to the host data source.
+ ASSERT_NO_THROW(hdsptr_->add(host));
+
+ SubnetID subnet = host->getIPv4SubnetID();
+
+ HWAddrPtr hwaddr = DuidToHWAddr(host->getDuid());
+
+ // Get the host by DUID (should succeed)
+ ConstHostPtr by_duid =
+ hdsptr_->get4(subnet, Host::IDENT_DUID, &host->getIdentifier()[0],
+ host->getIdentifier().size());
+
+ // Get the host by HW address (should fail)
+ ConstHostPtr by_hwaddr =
+ hdsptr_->get4(subnet, Host::IDENT_HWADDR, &host->getIdentifier()[0],
+ host->getIdentifier().size());
+
+ // Now let's check if we got what we expected.
+ EXPECT_TRUE(by_duid);
+ EXPECT_FALSE(by_hwaddr);
+}
+
+void
+GenericHostDataSourceTest::testHostname(std::string name, int num) {
+ // Make sure we have a pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ // Initialize the address to 192.0.2.0 (this will be bumped
+ // up to 192.0.2.1 in the first iteration)
+ IOAddress addr("192.0.2.0");
+
+ vector<HostPtr> hosts;
+
+ // Prepare a vector of hosts with unique hostnames
+ for (int i = 0; i < num; ++i) {
+ addr = IOAddress::increase(addr);
+
+ HostPtr host = HostDataSourceUtils::initializeHost4(addr.toText(), Host::IDENT_DUID);
+
+ stringstream hostname;
+ hostname.str("");
+ if (num > 1) {
+ hostname << i;
+ }
+ hostname << name;
+ host->setHostname(hostname.str());
+
+ hosts.push_back(host);
+ }
+
+ // Now add them all to the host data source.
+ for (vector<HostPtr>::const_iterator it = hosts.begin(); it != hosts.end();
+ ++it) {
+ // Try to add both of the to the host data source.
+ ASSERT_NO_THROW(hdsptr_->add(*it));
+ }
+
+ // And finally retrieve them one by one and check
+ // if the hostname was preserved.
+ for (vector<HostPtr>::const_iterator it = hosts.begin(); it != hosts.end();
+ ++it) {
+ ConstHostPtr from_hds;
+ ASSERT_NO_THROW(from_hds = hdsptr_->get4((*it)->getIPv4SubnetID(),
+ (*it)->getIPv4Reservation()));
+ ASSERT_TRUE(from_hds);
+
+ EXPECT_EQ((*it)->getHostname(), from_hds->getHostname());
+ }
+}
+
+void
+GenericHostDataSourceTest::testUserContext(ConstElementPtr user_context) {
+
+ // Make sure we have a pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ // Create a host reservation.
+ HostPtr host = HostDataSourceUtils::initializeHost4("192.0.2.1", Host::IDENT_DUID);
+ ASSERT_TRUE(host); // Make sure the host is generated properly.
+ host->setContext(user_context);
+ SubnetID subnet = host->getIPv4SubnetID();
+
+ // Try to add it to the host data source.
+ ASSERT_NO_THROW(hdsptr_->add(host));
+
+ // Retrieve it.
+ ConstHostPtr from_hds = hdsptr_->get4(subnet, IOAddress("192.0.2.1"));
+ ASSERT_TRUE(from_hds);
+
+ // Finally, let's check if what we got makes any sense.
+ HostDataSourceUtils::compareHosts(host, from_hds);
+
+ // Retry with IPv6
+ host = HostDataSourceUtils::initializeHost6("2001:db8::1", Host::IDENT_HWADDR, false);
+ ASSERT_TRUE(host);
+ ASSERT_TRUE(host->getHWAddress());
+ host->setContext(user_context);
+ host->setHostname("foo.example.com");
+ subnet = host->getIPv6SubnetID();
+
+ ASSERT_NO_THROW(hdsptr_->add(host));
+
+ from_hds = hdsptr_->get6(subnet, Host::IDENT_HWADDR,
+ &host->getIdentifier()[0],
+ host->getIdentifier().size());
+ ASSERT_TRUE(from_hds);
+
+ HostDataSourceUtils::compareHosts(host, from_hds);
+}
+
+void
+GenericHostDataSourceTest::testMultipleSubnets(int subnets,
+ const Host::IdentifierType& id) {
+ // Make sure we have a pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ HostPtr host = HostDataSourceUtils::initializeHost4("192.0.2.1", id);
+ host->setIPv6SubnetID(SUBNET_ID_UNUSED);
+
+ for (int i = 0; i < subnets; ++i) {
+ host->setIPv4SubnetID(i + 1000);
+ ASSERT_NO_THROW(hdsptr_->add(host));
+ }
+
+ // Now check that the reservations can be retrieved by IPv4 address from
+ // each subnet separately.
+ for (int i = 0; i < subnets; ++i) {
+ // Try to retrieve the host by IPv4 address.
+ ConstHostPtr from_hds =
+ hdsptr_->get4(i + 1000, host->getIPv4Reservation());
+
+ ASSERT_TRUE(from_hds);
+ EXPECT_EQ(i + 1000, from_hds->getIPv4SubnetID());
+
+ // Try to retrieve the host by either HW address of client-id
+ from_hds = hdsptr_->get4(i + 1000, id, &host->getIdentifier()[0],
+ host->getIdentifier().size());
+ ASSERT_TRUE(from_hds);
+ EXPECT_EQ(i + 1000, from_hds->getIPv4SubnetID());
+ }
+
+ // Now check that they can be retrieved all at once, by IPv4 address.
+ ConstHostCollection all_by_addr = hdsptr_->getAll4(IOAddress("192.0.2.1"));
+ ASSERT_EQ(subnets, all_by_addr.size());
+
+ // Verify that the values returned are proper.
+ int i = 0;
+ for (ConstHostCollection::const_iterator it = all_by_addr.begin();
+ it != all_by_addr.end(); ++it) {
+ EXPECT_EQ(IOAddress("192.0.2.1"), (*it)->getIPv4Reservation());
+ EXPECT_EQ(1000 + i++, (*it)->getIPv4SubnetID());
+ }
+
+ // Finally, check that the hosts can be retrieved by HW address or DUID
+ ConstHostCollection all_by_id = hdsptr_->getAll(
+ id, &host->getIdentifier()[0], host->getIdentifier().size());
+ ASSERT_EQ(subnets, all_by_id.size());
+
+ // Check that the returned values are as expected.
+ i = 0;
+ for (ConstHostCollection::const_iterator it = all_by_id.begin();
+ it != all_by_id.end(); ++it) {
+ EXPECT_EQ(IOAddress("192.0.2.1"), (*it)->getIPv4Reservation());
+ EXPECT_EQ(1000 + i++, (*it)->getIPv4SubnetID());
+ }
+}
+
+void
+GenericHostDataSourceTest::testGet6ByHWAddr() {
+ // Make sure we have the pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ // Create a host reservations.
+ HostPtr host1 = HostDataSourceUtils::initializeHost6("2001:db8::1", Host::IDENT_HWADDR, false);
+ HostPtr host2 = HostDataSourceUtils::initializeHost6("2001:db8::2", Host::IDENT_HWADDR, false);
+
+ // Sanity check: make sure the hosts have different HW addresses.
+ ASSERT_TRUE(host1->getHWAddress());
+ ASSERT_TRUE(host2->getHWAddress());
+
+ HostDataSourceUtils::compareHwaddrs(host1, host2, false);
+
+ // Try to add both of them to the host data source.
+ ASSERT_NO_THROW(hdsptr_->add(host1));
+ ASSERT_NO_THROW(hdsptr_->add(host2));
+
+ SubnetID subnet1 = host1->getIPv6SubnetID();
+ SubnetID subnet2 = host2->getIPv6SubnetID();
+
+ ConstHostPtr from_hds1 = hdsptr_->get6(subnet1, Host::IDENT_HWADDR,
+ &host1->getIdentifier()[0],
+ host1->getIdentifier().size());
+
+ ConstHostPtr from_hds2 = hdsptr_->get6(subnet2, Host::IDENT_HWADDR,
+ &host2->getIdentifier()[0],
+ host2->getIdentifier().size());
+
+ // Now let's check if we got what we expected.
+ ASSERT_TRUE(from_hds1);
+ ASSERT_TRUE(from_hds2);
+ HostDataSourceUtils::compareHosts(host1, from_hds1);
+ HostDataSourceUtils::compareHosts(host2, from_hds2);
+}
+
+void
+GenericHostDataSourceTest::testGet6ByClientId() {
+ // Make sure we have the pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ // Create a host reservations.
+ HostPtr host1 = HostDataSourceUtils::initializeHost6("2001:db8::1", Host::IDENT_DUID, false);
+ HostPtr host2 = HostDataSourceUtils::initializeHost6("2001:db8::2", Host::IDENT_DUID, false);
+
+ // Sanity check: make sure the hosts have different HW addresses.
+ ASSERT_TRUE(host1->getDuid());
+ ASSERT_TRUE(host2->getDuid());
+
+ HostDataSourceUtils::compareDuids(host1, host2, false);
+
+ // Try to add both of them to the host data source.
+ ASSERT_NO_THROW(hdsptr_->add(host1));
+ ASSERT_NO_THROW(hdsptr_->add(host2));
+
+ SubnetID subnet1 = host1->getIPv6SubnetID();
+ SubnetID subnet2 = host2->getIPv6SubnetID();
+
+ ConstHostPtr from_hds1 = hdsptr_->get6(subnet1, Host::IDENT_DUID,
+ &host1->getIdentifier()[0],
+ host1->getIdentifier().size());
+
+ ConstHostPtr from_hds2 = hdsptr_->get6(subnet2, Host::IDENT_DUID,
+ &host2->getIdentifier()[0],
+ host2->getIdentifier().size());
+
+ // Now let's check if we got what we expected.
+ ASSERT_TRUE(from_hds1);
+ ASSERT_TRUE(from_hds2);
+ HostDataSourceUtils::compareHosts(host1, from_hds1);
+ HostDataSourceUtils::compareHosts(host2, from_hds2);
+}
+
+void
+GenericHostDataSourceTest::testSubnetId6(int subnets, Host::IdentifierType id) {
+ // Make sure we have a pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ HostPtr host;
+ IOAddress current_address("2001:db8::0");
+ for (int i = 0; i < subnets; ++i) {
+ // Last boolean value set to false indicates that the same identifier
+ // must be used for each generated host.
+ host = HostDataSourceUtils::initializeHost6(current_address.toText(),
+ id, true, false, "");
+
+ host->setIPv4SubnetID(i + 1000);
+ host->setIPv6SubnetID(i + 1000);
+
+ // Check that the same host can have reservations in multiple subnets.
+ EXPECT_NO_THROW(hdsptr_->add(host));
+
+ // Increase address to make sure we don't assign the same address
+ // in different subnets.
+ current_address = IOAddress::increase(current_address);
+ }
+
+ // Check that the reservations can be retrieved from each subnet separately.
+ for (int i = 0; i < subnets; ++i) {
+ // Try to retrieve the host
+ ConstHostPtr from_hds = hdsptr_->get6(i + 1000, id, &host->getIdentifier()[0],
+ host->getIdentifier().size());
+
+ ASSERT_TRUE(from_hds) << "failed for i=" << i;
+ EXPECT_EQ(i + 1000, from_hds->getIPv6SubnetID());
+ }
+
+ // Check that the hosts can all be retrieved by HW address or DUID
+ ConstHostCollection all_by_id = hdsptr_->getAll(id, &host->getIdentifier()[0],
+ host->getIdentifier().size());
+ ASSERT_EQ(subnets, all_by_id.size());
+
+ // Check that the returned values are as expected.
+ int i = 0;
+ for (ConstHostCollection::const_iterator it = all_by_id.begin();
+ it != all_by_id.end(); ++it) {
+ EXPECT_EQ(IOAddress("0.0.0.0"), (*it)->getIPv4Reservation());
+ EXPECT_EQ(1000 + i++, (*it)->getIPv6SubnetID());
+ }
+}
+
+void
+GenericHostDataSourceTest::testGetByIPv6(Host::IdentifierType id, bool prefix) {
+ // Make sure we have a pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ // Let's create a couple of hosts...
+ HostPtr host1 = HostDataSourceUtils::initializeHost6("2001:db8::1",
+ id, prefix, "key##1");
+ HostPtr host2 = HostDataSourceUtils::initializeHost6("2001:db8::2",
+ id, prefix, "key##2");
+ HostPtr host3 = HostDataSourceUtils::initializeHost6("2001:db8::3",
+ id, prefix, "key##3");
+ HostPtr host4 = HostDataSourceUtils::initializeHost6("2001:db8::4",
+ id, prefix, "key##4");
+
+ // ... and add them to the data source.
+ ASSERT_NO_THROW(hdsptr_->add(host1));
+ ASSERT_NO_THROW(hdsptr_->add(host2));
+ ASSERT_NO_THROW(hdsptr_->add(host3));
+ ASSERT_NO_THROW(hdsptr_->add(host4));
+
+ // Are we talking about addresses or prefixes?
+ uint8_t len = prefix ? 64 : 128;
+
+ // And then try to retrieve them back.
+ ConstHostPtr from_hds1 = hdsptr_->get6(IOAddress("2001:db8::1"), len);
+ ConstHostPtr from_hds2 = hdsptr_->get6(IOAddress("2001:db8::2"), len);
+ ConstHostPtr from_hds3 = hdsptr_->get6(IOAddress("2001:db8::3"), len);
+ ConstHostPtr from_hds4 = hdsptr_->get6(IOAddress("2001:db8::4"), len);
+
+ // Make sure we got something back.
+ ASSERT_TRUE(from_hds1);
+ ASSERT_TRUE(from_hds2);
+ ASSERT_TRUE(from_hds3);
+ ASSERT_TRUE(from_hds4);
+
+ // Then let's check that what we got seems correct.
+ HostDataSourceUtils::compareHosts(host1, from_hds1);
+ HostDataSourceUtils::compareHosts(host2, from_hds2);
+ HostDataSourceUtils::compareHosts(host3, from_hds3);
+ HostDataSourceUtils::compareHosts(host4, from_hds4);
+
+ // Ok, finally let's check that getting by a different address
+ // will not work.
+ EXPECT_FALSE(hdsptr_->get6(IOAddress("2001:db8::5"), len));
+}
+
+void
+GenericHostDataSourceTest::testGetBySubnetIPv6() {
+ // Make sure we have a pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ // Let's create a couple of hosts...
+ HostPtr host1 = HostDataSourceUtils::initializeHost6("2001:db8:1::", Host::IDENT_DUID, true);
+ HostPtr host2 = HostDataSourceUtils::initializeHost6("2001:db8:2::", Host::IDENT_DUID, true);
+ HostPtr host3 = HostDataSourceUtils::initializeHost6("2001:db8:3::", Host::IDENT_DUID, true);
+ HostPtr host4 = HostDataSourceUtils::initializeHost6("2001:db8:4::", Host::IDENT_DUID, true);
+
+ // ... and add them to the data source.
+ ASSERT_NO_THROW(hdsptr_->add(host1));
+ ASSERT_NO_THROW(hdsptr_->add(host2));
+ ASSERT_NO_THROW(hdsptr_->add(host3));
+ ASSERT_NO_THROW(hdsptr_->add(host4));
+
+ // And then try to retrieve them back.
+ ConstHostPtr from_hds1 = hdsptr_->get6(host1->getIPv6SubnetID(), IOAddress("2001:db8:1::"));
+ ConstHostPtr from_hds2 = hdsptr_->get6(host2->getIPv6SubnetID(), IOAddress("2001:db8:2::"));
+ ConstHostPtr from_hds3 = hdsptr_->get6(host3->getIPv6SubnetID(), IOAddress("2001:db8:3::"));
+ ConstHostPtr from_hds4 = hdsptr_->get6(host4->getIPv6SubnetID(), IOAddress("2001:db8:4::"));
+
+ // Make sure we got something back.
+ ASSERT_TRUE(from_hds1);
+ ASSERT_TRUE(from_hds2);
+ ASSERT_TRUE(from_hds3);
+ ASSERT_TRUE(from_hds4);
+
+ // Then let's check that what we got seems correct.
+ HostDataSourceUtils::compareHosts(host1, from_hds1);
+ HostDataSourceUtils::compareHosts(host2, from_hds2);
+ HostDataSourceUtils::compareHosts(host3, from_hds3);
+ HostDataSourceUtils::compareHosts(host4, from_hds4);
+}
+
+void
+GenericHostDataSourceTest::testAddDuplicate6WithSameDUID() {
+ // Make sure we have the pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ // Create a host reservations.
+ HostPtr host = HostDataSourceUtils::initializeHost6("2001:db8::1", Host::IDENT_DUID, true);
+
+ // Add this reservation once.
+ ASSERT_NO_THROW(hdsptr_->add(host));
+
+ // Then try to add it again, it should throw an exception.
+ ASSERT_THROW(hdsptr_->add(host), DuplicateEntry);
+}
+
+void
+GenericHostDataSourceTest::testAddDuplicate6WithSameHWAddr() {
+ // Make sure we have the pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ // Create a host reservations.
+ HostPtr host = HostDataSourceUtils::initializeHost6("2001:db8::1", Host::IDENT_HWADDR, true);
+
+ // Add this reservation once.
+ ASSERT_NO_THROW(hdsptr_->add(host));
+
+ // Then try to add it again, it should throw an exception.
+ ASSERT_THROW(hdsptr_->add(host), DuplicateEntry);
+}
+
+void
+GenericHostDataSourceTest::testAddDuplicateIPv6() {
+ // Make sure we have the pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ // Create a host reservation.
+ HostPtr host = HostDataSourceUtils::initializeHost6("2001:db8::1", Host::IDENT_HWADDR, true);
+
+ // Add this reservation once.
+ ASSERT_NO_THROW(hdsptr_->add(host));
+
+ // Create a host with a different identifier but the same IPv6 address. An attempt
+ // to create the reservation for the same IPv6 address should fail.
+ host = HostDataSourceUtils::initializeHost6("2001:db8::1", Host::IDENT_HWADDR, true);
+ EXPECT_THROW(hdsptr_->add(host), DuplicateEntry);
+}
+
+void
+GenericHostDataSourceTest::testAllowDuplicateIPv6() {
+ // Make sure we have the pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+ ASSERT_TRUE(hdsptr_->setIPReservationsUnique(false));
+
+ // Create a host reservations.
+ HostPtr host = HostDataSourceUtils::initializeHost6("2001:db8::1", Host::IDENT_HWADDR, true, true);
+ auto host_id = host->getHostId();
+ auto subnet_id = host->getIPv6SubnetID();
+
+ // Add this reservation once.
+ ASSERT_NO_THROW(hdsptr_->add(host));
+
+ // Then try to add it again, it should throw an exception because the
+ // HWADDR is the same.
+ host = HostDataSourceUtils::initializeHost6("2001:db8::1", Host::IDENT_HWADDR, true, false);
+ host->setHostId(++host_id);
+ host->setIPv6SubnetID(subnet_id);
+ ASSERT_THROW(hdsptr_->add(host), DuplicateEntry);
+
+ // This time use a different host identifier and try again.
+ // This update should succeed because we permitted to create
+ // multiple IP reservations for the same IP address but different
+ // identifier.
+ host = HostDataSourceUtils::initializeHost6("2001:db8::1", Host::IDENT_HWADDR, true, true);
+ host->setHostId(++host_id);
+ host->setIPv6SubnetID(subnet_id);
+ ASSERT_NO_THROW(hdsptr_->add(host));
+
+ ConstHostCollection returned;
+ ASSERT_NO_THROW(returned = hdsptr_->getAll6(host->getIPv6SubnetID(), IOAddress("2001:db8::1")));
+ EXPECT_EQ(2, returned.size());
+ EXPECT_NE(returned[0]->getIdentifierAsText(), returned[1]->getIdentifierAsText());
+
+ // Let's now try to delete the hosts by subnet_id and address.
+ bool deleted = false;
+ ASSERT_NO_THROW(deleted = hdsptr_->del(subnet_id, IOAddress("2001:db8::1")));
+ ASSERT_TRUE(deleted);
+ ASSERT_NO_THROW(returned = hdsptr_->getAll6(host->getIPv6SubnetID(), IOAddress("2001:db8::1")));
+ EXPECT_TRUE(returned.empty());
+}
+
+void
+GenericHostDataSourceTest::testAddDuplicateIPv4() {
+ // Make sure we have the pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ // Create a host reservations.
+ HostPtr host = HostDataSourceUtils::initializeHost4("192.0.2.1", Host::IDENT_DUID);
+
+ // Add this reservation once.
+ ASSERT_NO_THROW(hdsptr_->add(host));
+
+ // Then try to add it again, it should throw an exception.
+ ASSERT_THROW(hdsptr_->add(host), DuplicateEntry);
+
+ // This time use a different host identifier and try again.
+ // This update should be rejected because of duplicated
+ // address.
+ ASSERT_NO_THROW(host->setIdentifier("01:02:03:04:05:06", "hw-address"));
+ ASSERT_THROW(hdsptr_->add(host), DuplicateEntry);
+
+ // Modify address to avoid its duplication and make sure
+ // we can now add the host.
+ ASSERT_NO_THROW(host->setIPv4Reservation(IOAddress("192.0.2.3")));
+ EXPECT_NO_THROW(hdsptr_->add(host));
+}
+
+void
+GenericHostDataSourceTest::testAllowDuplicateIPv4() {
+ // Make sure we have the pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+ ASSERT_TRUE(hdsptr_->setIPReservationsUnique(false));
+
+ // Create a host reservations.
+ HostPtr host = HostDataSourceUtils::initializeHost4("192.0.2.1", Host::IDENT_DUID, true);
+ auto host_id = host->getHostId();
+ auto subnet_id = host->getIPv4SubnetID();
+
+ // Add this reservation once.
+ ASSERT_NO_THROW(hdsptr_->add(host));
+
+ // Then try to add it again, it should throw an exception because the
+ // DUID is the same.
+ host = HostDataSourceUtils::initializeHost4("192.0.2.1", Host::IDENT_DUID, false);
+ host->setHostId(++host_id);
+ host->setIPv4SubnetID(subnet_id);
+ ASSERT_THROW(hdsptr_->add(host), DuplicateEntry);
+
+ // This time use a different host identifier and try again.
+ // This update should succeed because we permitted to create
+ // multiple IP reservations for the same IP address but different
+ // identifier.
+ host = HostDataSourceUtils::initializeHost4("192.0.2.1", Host::IDENT_DUID, true);
+ host->setHostId(++host_id);
+ host->setIPv4SubnetID(subnet_id);
+ ASSERT_NO_THROW(hdsptr_->add(host));
+
+ ConstHostCollection returned;
+ ASSERT_NO_THROW(returned = hdsptr_->getAll4(host->getIPv4SubnetID(), IOAddress("192.0.2.1")));
+ EXPECT_EQ(2, returned.size());
+ EXPECT_NE(returned[0]->getIdentifierAsText(), returned[1]->getIdentifierAsText());
+
+ // Let's now try to delete the hosts by subnet_id and address.
+ bool deleted = false;
+ ASSERT_NO_THROW(deleted = hdsptr_->del(subnet_id, IOAddress("192.0.2.1")));
+ ASSERT_TRUE(deleted);
+ ASSERT_NO_THROW(returned = hdsptr_->getAll4(host->getIPv4SubnetID(), IOAddress("192.0.2.1")));
+ EXPECT_TRUE(returned.empty());
+}
+
+void
+GenericHostDataSourceTest::testDisallowDuplicateIP() {
+ // Make sure we have the pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+ // The backend does not support switching to the mode in which multiple
+ // reservations for the same address can be created.
+ EXPECT_FALSE(hdsptr_->setIPReservationsUnique(false));
+
+ // The default mode still can be used.
+ EXPECT_TRUE(hdsptr_->setIPReservationsUnique(true));
+}
+
+void
+GenericHostDataSourceTest::testAddr6AndPrefix() {
+ // Make sure we have the pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ // Create a host reservations with prefix reservation (prefix = true)
+ HostPtr host = HostDataSourceUtils::initializeHost6("2001:db8::1", Host::IDENT_DUID, true);
+
+ // Create IPv6 reservation (for an address) and add it to the host
+ IPv6Resrv resv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8::2"), 128);
+ host->addReservation(resv);
+
+ // Add this reservation
+ ASSERT_NO_THROW(hdsptr_->add(host));
+
+ // Get this host by DUID
+ ConstHostPtr from_hds =
+ hdsptr_->get6(host->getIPv6SubnetID(), Host::IDENT_DUID,
+ &host->getIdentifier()[0], host->getIdentifier().size());
+
+ // Make sure we got something back
+ ASSERT_TRUE(from_hds);
+
+ // Check if reservations are the same
+ HostDataSourceUtils::compareReservations6(host->getIPv6Reservations(),
+ from_hds->getIPv6Reservations());
+}
+
+void
+GenericHostDataSourceTest::testMultipleReservations() {
+ // Make sure we have the pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+ uint8_t len = 128;
+
+ HostPtr host = HostDataSourceUtils::initializeHost6("2001:db8::1", Host::IDENT_DUID, false);
+
+ // Add some reservations
+ IPv6Resrv resv1(IPv6Resrv::TYPE_NA, IOAddress("2001:db8::6"), len);
+ IPv6Resrv resv2(IPv6Resrv::TYPE_NA, IOAddress("2001:db8::7"), len);
+ IPv6Resrv resv3(IPv6Resrv::TYPE_NA, IOAddress("2001:db8::8"), len);
+ IPv6Resrv resv4(IPv6Resrv::TYPE_NA, IOAddress("2001:db8::9"), len);
+
+ host->addReservation(resv1);
+ host->addReservation(resv2);
+ host->addReservation(resv3);
+ host->addReservation(resv4);
+
+ ASSERT_NO_THROW(hdsptr_->add(host));
+
+ ConstHostPtr from_hds = hdsptr_->get6(IOAddress("2001:db8::1"), len);
+
+ // Make sure we got something back
+ ASSERT_TRUE(from_hds);
+
+ // Check if hosts are the same
+ HostDataSourceUtils::compareHosts(host, from_hds);
+}
+
+void
+GenericHostDataSourceTest::testMultipleReservationsDifferentOrder() {
+ // Make sure we have the pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+ uint8_t len = 128;
+
+ HostPtr host1 = HostDataSourceUtils::initializeHost6("2001:db8::1",
+ Host::IDENT_DUID, false, "key##1");
+ HostPtr host2 = HostDataSourceUtils::initializeHost6("2001:db8::1",
+ Host::IDENT_DUID, false, "key##1");
+
+ // Add some reservations
+ IPv6Resrv resv1(IPv6Resrv::TYPE_NA, IOAddress("2001:db8::6"), len);
+ IPv6Resrv resv2(IPv6Resrv::TYPE_NA, IOAddress("2001:db8::7"), len);
+ IPv6Resrv resv3(IPv6Resrv::TYPE_NA, IOAddress("2001:db8::8"), len);
+ IPv6Resrv resv4(IPv6Resrv::TYPE_NA, IOAddress("2001:db8::9"), len);
+
+ host1->addReservation(resv1);
+ host1->addReservation(resv2);
+ host1->addReservation(resv3);
+ host1->addReservation(resv4);
+
+ host2->addReservation(resv4);
+ host2->addReservation(resv3);
+ host2->addReservation(resv2);
+ host2->addReservation(resv1);
+
+ // Check if reservations are the same
+ HostDataSourceUtils::compareReservations6(host1->getIPv6Reservations(),
+ host2->getIPv6Reservations());
+}
+
+void
+GenericHostDataSourceTest::testOptionsReservations4(const bool formatted,
+ ConstElementPtr user_context) {
+ HostPtr host = HostDataSourceUtils::initializeHost4("192.0.2.5", Host::IDENT_HWADDR);
+ // Add a bunch of DHCPv4 and DHCPv6 options for the host.
+ ASSERT_NO_THROW(addTestOptions(host, formatted, DHCP4_ONLY, user_context));
+ // Insert host and the options into respective tables.
+ ASSERT_NO_THROW(hdsptr_->add(host));
+ // Subnet id will be used in queries to the database.
+ SubnetID subnet_id = host->getIPv4SubnetID();
+
+ // getAll4(subnet_id)
+ ConstHostCollection hosts_by_subnet = hdsptr_->getAll4(subnet_id);
+ ASSERT_EQ(1, hosts_by_subnet.size());
+ ASSERT_NO_FATAL_FAILURE(HostDataSourceUtils::compareHosts(host, *hosts_by_subnet.begin()));
+
+ // getAll4(address)
+ ConstHostCollection hosts_by_addr =
+ hdsptr_->getAll4(host->getIPv4Reservation());
+ ASSERT_EQ(1, hosts_by_addr.size());
+ ASSERT_NO_FATAL_FAILURE(HostDataSourceUtils::compareHosts(host, *hosts_by_addr.begin()));
+
+ // get4(subnet_id, identifier_type, identifier, identifier_size)
+ ConstHostPtr host_by_id =
+ hdsptr_->get4(subnet_id, host->getIdentifierType(),
+ &host->getIdentifier()[0], host->getIdentifier().size());
+ ASSERT_NO_FATAL_FAILURE(HostDataSourceUtils::compareHosts(host, host_by_id));
+
+ // get4(subnet_id, address)
+ ConstHostPtr host_by_addr =
+ hdsptr_->get4(subnet_id, IOAddress("192.0.2.5"));
+ ASSERT_NO_FATAL_FAILURE(HostDataSourceUtils::compareHosts(host, host_by_addr));
+}
+
+void
+GenericHostDataSourceTest::testOptionsReservations6(const bool formatted,
+ ConstElementPtr user_context) {
+ HostPtr host = HostDataSourceUtils::initializeHost6("2001:db8::1", Host::IDENT_DUID, false);
+ // Add a bunch of DHCPv4 and DHCPv6 options for the host.
+ ASSERT_NO_THROW(addTestOptions(host, formatted, DHCP6_ONLY, user_context));
+ // Insert host, options and IPv6 reservations into respective tables.
+ ASSERT_NO_THROW(hdsptr_->add(host));
+ // Subnet id will be used in queries to the database.
+ SubnetID subnet_id = host->getIPv6SubnetID();
+
+ // get6(subnet_id, identifier_type, identifier, identifier_size)
+ ConstHostPtr host_by_id =
+ hdsptr_->get6(subnet_id, host->getIdentifierType(),
+ &host->getIdentifier()[0], host->getIdentifier().size());
+ ASSERT_NO_FATAL_FAILURE(HostDataSourceUtils::compareHosts(host, host_by_id));
+
+ // get6(address, prefix_len)
+ ConstHostPtr host_by_addr = hdsptr_->get6(IOAddress("2001:db8::1"), 128);
+ ASSERT_NO_FATAL_FAILURE(HostDataSourceUtils::compareHosts(host, host_by_addr));
+}
+
+void
+GenericHostDataSourceTest::testOptionsReservations46(const bool formatted) {
+ HostPtr host = HostDataSourceUtils::initializeHost6("2001:db8::1", Host::IDENT_HWADDR, false);
+
+ // Add a bunch of DHCPv4 and DHCPv6 options for the host.
+ ASSERT_NO_THROW(addTestOptions(host, formatted, DHCP4_AND_DHCP6));
+ // Insert host, options and IPv6 reservations into respective tables.
+ ASSERT_NO_THROW(hdsptr_->add(host));
+ // Subnet id will be used in queries to the database.
+ SubnetID subnet_id = host->getIPv6SubnetID();
+
+ // getAll6(subnet_id)
+ ConstHostCollection hosts_by_subnet = hdsptr_->getAll6(subnet_id);
+ EXPECT_EQ(1, hosts_by_subnet.size());
+ // Don't compare as getAll6() returns the v6 part only.
+
+ // getAll(identifier_type, identifier, identifier_size)
+ ConstHostCollection hosts_by_id =
+ hdsptr_->getAll(host->getIdentifierType(), &host->getIdentifier()[0],
+ host->getIdentifier().size());
+ ASSERT_EQ(1, hosts_by_id.size());
+ ASSERT_NO_FATAL_FAILURE(HostDataSourceUtils::compareHosts(host, *hosts_by_id.begin()));
+}
+
+void
+GenericHostDataSourceTest::testMultipleClientClasses4() {
+ ASSERT_TRUE(hdsptr_);
+
+ // Create the Host object.
+ HostPtr host = HostDataSourceUtils::initializeHost4("192.0.2.5", Host::IDENT_HWADDR);
+
+ // Add v4 classes to the host.
+ for (int i = 0; i < 4; ++i) {
+ std::ostringstream os;
+ os << "class4_" << i;
+ host->addClientClass4(os.str());
+ }
+
+ // Add the host.
+ ASSERT_NO_THROW(hdsptr_->add(host));
+
+ // Subnet id will be used in queries to the database.
+ SubnetID subnet_id = host->getIPv4SubnetID();
+
+ // Fetch the host via:
+ // getAll(const Host::IdentifierType, const uint8_t* identifier_begin,
+ // const size_t identifier_len) const;
+ ConstHostCollection hosts_by_id =
+ hdsptr_->getAll(host->getIdentifierType(), &host->getIdentifier()[0],
+ host->getIdentifier().size());
+ ASSERT_EQ(1, hosts_by_id.size());
+ ASSERT_NO_FATAL_FAILURE(HostDataSourceUtils::compareHosts(host, *hosts_by_id.begin()));
+
+ // Fetch the host via
+ // getAll4(const asiolink::IOAddress& address) const;
+ hosts_by_id = hdsptr_->getAll4(IOAddress("192.0.2.5"));
+ ASSERT_EQ(1, hosts_by_id.size());
+ ASSERT_NO_FATAL_FAILURE(HostDataSourceUtils::compareHosts(host, *hosts_by_id.begin()));
+
+ // Fetch the host via
+ // get4(const SubnetID& subnet_id, const Host::IdentifierType&
+ // identifier_type,
+ // const uint8_t* identifier_begin, const size_t identifier_len) const;
+ ConstHostPtr from_hds =
+ hdsptr_->get4(subnet_id, host->getIdentifierType(),
+ &host->getIdentifier()[0], host->getIdentifier().size());
+ ASSERT_TRUE(from_hds);
+ ASSERT_NO_FATAL_FAILURE(HostDataSourceUtils::compareHosts(host, from_hds));
+
+ // Fetch the host via:
+ // get4(const SubnetID& subnet_id, const asiolink::IOAddress& address) const;
+ from_hds = hdsptr_->get4(subnet_id, IOAddress("192.0.2.5"));
+ ASSERT_TRUE(from_hds);
+ ASSERT_NO_FATAL_FAILURE(HostDataSourceUtils::compareHosts(host, from_hds));
+}
+
+void
+GenericHostDataSourceTest::testMultipleClientClasses6() {
+ ASSERT_TRUE(hdsptr_);
+
+ // Create the Host object.
+ HostPtr host = HostDataSourceUtils::initializeHost6("2001:db8::1", Host::IDENT_HWADDR, false);
+
+ // Add v6 classes to the host.
+ for (int i = 0; i < 4; ++i) {
+ std::ostringstream os;
+ os << "class6_" << i;
+ host->addClientClass6(os.str());
+ }
+
+ // Add the host.
+ ASSERT_NO_THROW(hdsptr_->add(host));
+
+ // Subnet id will be used in queries to the database.
+ SubnetID subnet_id = host->getIPv6SubnetID();
+
+ // getAll(const Host::IdentifierType& identifier_type,
+ // const uint8_t* identifier_begin,
+ // const size_t identifier_len) const;
+ ConstHostCollection hosts_by_id =
+ hdsptr_->getAll(host->getIdentifierType(), &host->getIdentifier()[0],
+ host->getIdentifier().size());
+ ASSERT_EQ(1, hosts_by_id.size());
+ ASSERT_NO_FATAL_FAILURE(HostDataSourceUtils::compareHosts(host, *hosts_by_id.begin()));
+
+ // Fetch the host via:
+ // get6(const SubnetID& subnet_id, const Host::IdentifierType&
+ // identifier_type,
+ // const uint8_t* identifier_begin, const size_t identifier_len) const;
+ ConstHostPtr from_hds =
+ hdsptr_->get6(subnet_id, Host::IDENT_HWADDR, &host->getIdentifier()[0],
+ host->getIdentifier().size());
+ ASSERT_TRUE(from_hds);
+ ASSERT_NO_FATAL_FAILURE(HostDataSourceUtils::compareHosts(host, from_hds));
+
+ // Fetch the host via:
+ // get6(const asiolink::IOAddress& prefix, const uint8_t prefix_len) const;
+ from_hds = hdsptr_->get6(IOAddress("2001:db8::1"), 128);
+ ASSERT_TRUE(from_hds);
+ ASSERT_NO_FATAL_FAILURE(HostDataSourceUtils::compareHosts(host, from_hds));
+}
+
+void
+GenericHostDataSourceTest::testMultipleClientClassesBoth() {
+ /// Add host reservation with a multiple v4 and v6 client-classes,
+ /// retrieve it and make sure that all client classes are retrieved
+ /// properly.
+ ASSERT_TRUE(hdsptr_);
+
+ // Create the Host object.
+ HostPtr host = HostDataSourceUtils::initializeHost6("2001:db8::1", Host::IDENT_HWADDR, false);
+
+ // Add v4 classes to the host.
+ for (int i = 0; i < 4; ++i) {
+ std::ostringstream os;
+ os << "class4_" << i;
+ host->addClientClass4(os.str());
+ }
+
+ // Add v6 classes to the host.
+ for (int i = 0; i < 4; ++i) {
+ std::ostringstream os;
+ os << "class6_" << i;
+ host->addClientClass6(os.str());
+ }
+
+ // Add the host.
+ ASSERT_NO_THROW(hdsptr_->add(host));
+
+ // Subnet id will be used in queries to the database.
+ SubnetID subnet_id = host->getIPv6SubnetID();
+
+ // Fetch the host from the source.
+ ConstHostPtr from_hds =
+ hdsptr_->get6(subnet_id, Host::IDENT_HWADDR, &host->getIdentifier()[0],
+ host->getIdentifier().size());
+ ASSERT_TRUE(from_hds);
+
+ // Verify they match.
+ ASSERT_NO_FATAL_FAILURE(HostDataSourceUtils::compareHosts(host, from_hds));
+}
+
+void
+GenericHostDataSourceTest::testMessageFields4() {
+ ASSERT_TRUE(hdsptr_);
+
+ // Create the Host object.
+ HostPtr host = HostDataSourceUtils::initializeHost4("192.0.2.5", Host::IDENT_HWADDR);
+ // And assign values for DHCPv4 message fields.
+ ASSERT_NO_THROW({
+ host->setNextServer(IOAddress("10.1.1.1"));
+ host->setServerHostname("server-name.example.org");
+ host->setBootFileName("bootfile.efi");
+ });
+
+ // Add the host.
+ ASSERT_NO_THROW(hdsptr_->add(host));
+
+ // Subnet id will be used in queries to the database.
+ SubnetID subnet_id = host->getIPv4SubnetID();
+
+ // Fetch the host via:
+ // getAll(const Host::IdentifierType, const uint8_t* identifier_begin,
+ // const size_t identifier_len) const;
+ ConstHostCollection hosts_by_id =
+ hdsptr_->getAll(host->getIdentifierType(), &host->getIdentifier()[0],
+ host->getIdentifier().size());
+ ASSERT_EQ(1, hosts_by_id.size());
+ ASSERT_NO_FATAL_FAILURE(HostDataSourceUtils::compareHosts(host, *hosts_by_id.begin()));
+
+ // Fetch the host via
+ // getAll4(const asiolink::IOAddress& address) const;
+ hosts_by_id = hdsptr_->getAll4(IOAddress("192.0.2.5"));
+ ASSERT_EQ(1, hosts_by_id.size());
+ ASSERT_NO_FATAL_FAILURE(HostDataSourceUtils::compareHosts(host, *hosts_by_id.begin()));
+
+ // Fetch the host via
+ // get4(const SubnetID& subnet_id, const Host::IdentifierType&
+ // identifier_type,
+ // const uint8_t* identifier_begin, const size_t identifier_len) const;
+ ConstHostPtr from_hds =
+ hdsptr_->get4(subnet_id, host->getIdentifierType(),
+ &host->getIdentifier()[0], host->getIdentifier().size());
+ ASSERT_TRUE(from_hds);
+ ASSERT_NO_FATAL_FAILURE(HostDataSourceUtils::compareHosts(host, from_hds));
+
+ // Fetch the host via:
+ // get4(const SubnetID& subnet_id, const asiolink::IOAddress& address) const;
+ from_hds = hdsptr_->get4(subnet_id, IOAddress("192.0.2.5"));
+ ASSERT_TRUE(from_hds);
+ ASSERT_NO_FATAL_FAILURE(HostDataSourceUtils::compareHosts(host, from_hds));
+}
+
+void
+GenericHostDataSourceTest::stressTest(unsigned int nOfHosts /* = 0xfffdU */) {
+ // Make sure we have a pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ // Make sure the variable part of the generated address fits in a 16-bit
+ // field.
+ ASSERT_LE(nOfHosts, 0xfffdU);
+
+ // Create hosts.
+ std::vector<HostPtr> hosts;
+ hosts.reserve(nOfHosts);
+ for (unsigned int i = 0x0001U; i < 0x0001U + nOfHosts; ++i) {
+ /// @todo: Check if this is written in hexadecimal format.
+ std::stringstream ss;
+ std::string n_host;
+ ss << std::hex << i;
+ ss >> n_host;
+
+ const std::string prefix = std::string("2001:db8::") + n_host;
+ hosts.push_back(HostDataSourceUtils::initializeHost6(prefix,
+ Host::IDENT_HWADDR, false, "key##1"));
+
+ IPv6ResrvRange range = hosts.back()->getIPv6Reservations();
+ ASSERT_EQ(1, std::distance(range.first, range.second));
+ EXPECT_TRUE(HostDataSourceUtils::reservationExists
+ (IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress(prefix)), range));
+ }
+ const size_t hosts_size = hosts.size();
+
+ std::cout << "Starting to add hosts..." << std::endl;
+ struct timespec start, end;
+ start = (struct timespec){0, 0};
+ end = (struct timespec){0, 0};
+ clock_gettime(CLOCK_THREAD_CPUTIME_ID, &start);
+ for (std::vector<HostPtr>::const_iterator it = hosts.begin();
+ it != hosts.end(); it++) {
+ ASSERT_NO_THROW(hdsptr_->add(*it));
+ }
+ clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end);
+ double s = static_cast<double>(end.tv_sec - start.tv_sec) +
+ static_cast<double>(end.tv_nsec - start.tv_nsec) / 1e9;
+ std::cout << "Adding " << hosts_size
+ << (hosts_size == 1 ? " host" : " hosts") << " took "
+ << std::fixed << std::setprecision(2) << s << " seconds."
+ << std::endl;
+
+ // And then try to retrieve them back.
+ std::cout << "Starting to retrieve hosts..." << std::endl;
+ start = (struct timespec){0, 0};
+ end = (struct timespec){0, 0};
+ clock_gettime(CLOCK_THREAD_CPUTIME_ID, &start);
+ for (std::vector<HostPtr>::const_iterator it = hosts.begin();
+ it != hosts.end(); it++) {
+ IPv6ResrvRange range = (*it)->getIPv6Reservations();
+ // This get6() call is particularly useful to test because it involves a
+ // subquery for MySQL and PostgreSQL.
+ ConstHostPtr from_hds =
+ hdsptr_->get6(range.first->second.getPrefix(), 128);
+ ASSERT_TRUE(from_hds);
+ HostDataSourceUtils::compareHosts(*it, from_hds);
+ }
+ clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end);
+ s = static_cast<double>(end.tv_sec - start.tv_sec) +
+ static_cast<double>(end.tv_nsec - start.tv_nsec) / 1e9;
+ std::cout << "Retrieving " << hosts_size
+ << (hosts_size == 1 ? " host" : " hosts") << " took "
+ << std::fixed << std::setprecision(2) << s << " seconds."
+ << std::endl;
+}
+
+void
+GenericHostDataSourceTest::testDeleteByAddr4() {
+ // Make sure we have a pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ // Let's create a v4 host...
+ HostPtr host1 = HostDataSourceUtils::initializeHost4("192.0.2.1", Host::IDENT_HWADDR);
+ SubnetID subnet1 = host1->getIPv4SubnetID();
+
+ // ... and add it to the data source.
+ ASSERT_NO_THROW(hdsptr_->add(host1));
+
+ // And then try to retrieve it back.
+ ConstHostPtr before = hdsptr_->get4(subnet1, IOAddress("192.0.2.1"));
+
+ // Now try to delete it: del(subnet-id, addr4)
+ EXPECT_TRUE(hdsptr_->del(subnet1, IOAddress("192.0.2.1")));
+
+ // Check if it's still there.
+ ConstHostPtr after = hdsptr_->get4(subnet1, IOAddress("192.0.2.1"));
+
+ // Make sure the host was there before...
+ EXPECT_TRUE(before);
+
+ // ... and that it's gone after deletion.
+ EXPECT_FALSE(after);
+
+ // An attempt to delete it should not cause an exception. It
+ // should return false.
+ bool result = false;
+ EXPECT_NO_THROW(result = hdsptr_->del(subnet1, IOAddress("192.0.2.1")));
+ EXPECT_FALSE(result);
+}
+
+void
+GenericHostDataSourceTest::testDeleteById4() {
+ // Make sure we have a pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ // Let's create a v4 host...
+ HostPtr host1 = HostDataSourceUtils::initializeHost4("192.0.2.1", Host::IDENT_HWADDR);
+ SubnetID subnet1 = host1->getIPv4SubnetID();
+
+ // ... and add it to the data source.
+ ASSERT_NO_THROW(hdsptr_->add(host1));
+
+ // And then try to retrieve it back.
+ ConstHostPtr before = hdsptr_->get4(subnet1,
+ host1->getIdentifierType(),
+ &host1->getIdentifier()[0],
+ host1->getIdentifier().size());
+
+ // Now try to delete it: del4(subnet4-id, identifier-type, identifier)
+ EXPECT_TRUE(hdsptr_->del4(subnet1, host1->getIdentifierType(),
+ &host1->getIdentifier()[0],
+ host1->getIdentifier().size()));
+
+ // Check if it's still there.
+ ConstHostPtr after = hdsptr_->get4(subnet1,
+ host1->getIdentifierType(),
+ &host1->getIdentifier()[0],
+ host1->getIdentifier().size());
+
+ // Make sure the host was there before...
+ EXPECT_TRUE(before);
+
+ // ... and that it's gone after deletion.
+ EXPECT_FALSE(after);
+
+ // An attempt to delete it should not cause an exception. It
+ // should return false.
+ bool result = false;
+ EXPECT_NO_THROW(result = hdsptr_->del4(subnet1, host1->getIdentifierType(),
+ &host1->getIdentifier()[0],
+ host1->getIdentifier().size()));
+ EXPECT_FALSE(result);
+}
+
+// Test checks when a IPv4 host with options is deleted that the options are
+// deleted as well.
+void
+GenericHostDataSourceTest::testDeleteById4Options() {
+ // Make sure we have a pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ // Let's create a v4 host...
+ HostPtr host1 = HostDataSourceUtils::initializeHost4("192.0.2.1", Host::IDENT_HWADDR);
+ // Add a bunch of DHCPv4 and DHCPv6 options for the host.
+ ASSERT_NO_THROW(addTestOptions(host1, true, DHCP4_ONLY));
+ // Insert host and the options into respective tables.
+
+ SubnetID subnet1 = host1->getIPv4SubnetID();
+
+ // ... and add it to the data source.
+ ASSERT_NO_THROW(hdsptr_->add(host1));
+
+ // There must be some options
+ EXPECT_NE(0, countDBOptions4());
+
+ // And then try to retrieve it back.
+ ConstHostPtr before = hdsptr_->get4(subnet1,
+ host1->getIdentifierType(),
+ &host1->getIdentifier()[0],
+ host1->getIdentifier().size());
+
+ // Now try to delete it: del4(subnet4-id, identifier-type, identifier)
+ EXPECT_TRUE(hdsptr_->del4(subnet1, host1->getIdentifierType(),
+ &host1->getIdentifier()[0],
+ host1->getIdentifier().size()));
+
+ // Check if it's still there.
+ ConstHostPtr after = hdsptr_->get4(subnet1,
+ host1->getIdentifierType(),
+ &host1->getIdentifier()[0],
+ host1->getIdentifier().size());
+
+ // Make sure the host was there before...
+ EXPECT_TRUE(before);
+
+ // ... and that it's gone after deletion.
+ EXPECT_FALSE(after);
+
+ // Check the options are indeed gone.
+ EXPECT_EQ(0, countDBOptions4());
+
+ // An attempt to delete it should not cause an exception. It
+ // should return false.
+ bool result = false;
+ EXPECT_NO_THROW(result = hdsptr_->del4(subnet1, host1->getIdentifierType(),
+ &host1->getIdentifier()[0],
+ host1->getIdentifier().size()));
+ EXPECT_FALSE(result);
+}
+
+void
+GenericHostDataSourceTest::testDeleteById6() {
+ // Make sure we have a pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ // Let's create a v6 host...
+ HostPtr host1 = HostDataSourceUtils::initializeHost6("2001:db8::1",
+ Host::IDENT_DUID, false, "key##1");
+ SubnetID subnet1 = host1->getIPv6SubnetID();
+
+ // ... and add it to the data source.
+ ASSERT_NO_THROW(hdsptr_->add(host1));
+
+ // And then try to retrieve it back.
+ ConstHostPtr before = hdsptr_->get6(subnet1,
+ host1->getIdentifierType(),
+ &host1->getIdentifier()[0],
+ host1->getIdentifier().size());
+
+ // Now try to delete it: del4(subnet4-id, identifier-type, identifier)
+ EXPECT_TRUE(hdsptr_->del6(subnet1, host1->getIdentifierType(),
+ &host1->getIdentifier()[0],
+ host1->getIdentifier().size()));
+
+ // Check if it's still there.
+ ConstHostPtr after = hdsptr_->get6(subnet1,
+ host1->getIdentifierType(),
+ &host1->getIdentifier()[0],
+ host1->getIdentifier().size());
+
+ // Make sure the host was there before...
+ EXPECT_TRUE(before);
+
+ // ... and that it's gone after deletion.
+ EXPECT_FALSE(after);
+
+ // An attempt to delete it should not cause an exception. It
+ // should return false.
+ bool result = false;
+ EXPECT_NO_THROW(result = hdsptr_->del6(subnet1, host1->getIdentifierType(),
+ &host1->getIdentifier()[0],
+ host1->getIdentifier().size()));
+ EXPECT_FALSE(result);
+}
+
+void
+GenericHostDataSourceTest::testDeleteById6Options() {
+ // Make sure we have a pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ // Let's create a v6 host...
+ HostPtr host1 = HostDataSourceUtils::initializeHost6("2001:db8::1", Host::IDENT_DUID, false);
+ SubnetID subnet1 = host1->getIPv6SubnetID();
+ ASSERT_NO_THROW(addTestOptions(host1, true, DHCP6_ONLY));
+
+ // ... and add it to the data source.
+ ASSERT_NO_THROW(hdsptr_->add(host1));
+
+ // Check that the options are stored...
+ EXPECT_NE(0, countDBOptions6());
+
+ // ... and so are v6 reservations.
+ EXPECT_NE(0, countDBReservations6());
+
+ // And then try to retrieve it back.
+ ConstHostPtr before = hdsptr_->get6(subnet1,
+ host1->getIdentifierType(),
+ &host1->getIdentifier()[0],
+ host1->getIdentifier().size());
+
+ // Now try to delete it: del4(subnet4-id, identifier-type, identifier)
+ EXPECT_TRUE(hdsptr_->del6(subnet1, host1->getIdentifierType(),
+ &host1->getIdentifier()[0],
+ host1->getIdentifier().size()));
+
+ // Check if it's still there.
+ ConstHostPtr after = hdsptr_->get6(subnet1,
+ host1->getIdentifierType(),
+ &host1->getIdentifier()[0],
+ host1->getIdentifier().size());
+
+ // Make sure the host was there before...
+ EXPECT_TRUE(before);
+
+ // ... and that it's gone after deletion.
+ EXPECT_FALSE(after);
+
+ // Check the options are indeed gone.
+ EXPECT_EQ(0, countDBOptions6());
+
+ // Check the options are indeed gone.
+ EXPECT_EQ(0, countDBReservations6());
+
+ // An attempt to delete it should not cause an exception. It
+ // should return false.
+ bool result = false;
+ EXPECT_NO_THROW(result = hdsptr_->del6(subnet1, host1->getIdentifierType(),
+ &host1->getIdentifier()[0],
+ host1->getIdentifier().size()));
+ EXPECT_FALSE(result);
+}
+
+void
+GenericHostDataSourceTest::testMultipleHostsNoAddress4() {
+ // Make sure we have a pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ // Create a host with zero IPv4 address.
+ HostPtr host1 = HostDataSourceUtils::initializeHost4("0.0.0.0", Host::IDENT_HWADDR);
+ host1->setIPv4SubnetID(1);
+ host1->setIPv6SubnetID(SUBNET_ID_UNUSED);
+ // Add the host to the database.
+ ASSERT_NO_THROW(hdsptr_->add(host1));
+
+ // An attempt to add this host again should fail due to client identifier
+ // duplication.
+ ASSERT_THROW(hdsptr_->add(host1), DuplicateEntry);
+
+ // Create another host with zero IPv4 address. Adding this host to the
+ // database should be successful because zero addresses are not counted
+ // in the unique index.
+ HostPtr host2 = HostDataSourceUtils::initializeHost4("0.0.0.0", Host::IDENT_HWADDR);
+ host2->setIPv4SubnetID(1);
+ host2->setIPv6SubnetID(SUBNET_ID_UNUSED);
+ ASSERT_NO_THROW(hdsptr_->add(host2));
+}
+
+void
+GenericHostDataSourceTest::testMultipleHosts6() {
+ // Make sure we have a pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ // Create first host.
+ HostPtr host1 = HostDataSourceUtils::initializeHost6("2001:db8::1", Host::IDENT_DUID, false);
+ host1->setIPv4SubnetID(SUBNET_ID_UNUSED);
+ host1->setIPv6SubnetID(1);
+ // Add the host to the database.
+ ASSERT_NO_THROW(hdsptr_->add(host1));
+
+ // An attempt to add this host again should fail due to client identifier
+ // duplication.
+ ASSERT_THROW(hdsptr_->add(host1), DuplicateEntry);
+
+ HostPtr host2 = HostDataSourceUtils::initializeHost6("2001:db8::2", Host::IDENT_DUID, false);
+ host2->setIPv4SubnetID(SUBNET_ID_UNUSED);
+ host2->setIPv6SubnetID(1);
+ // Add the host to the database.
+ ASSERT_NO_THROW(hdsptr_->add(host2));
+}
+
+void
+HostMgrDbLostCallbackTest::testNoCallbackOnOpenFailure() {
+ isc::db::DatabaseConnection::db_lost_callback_ =
+ std::bind(&HostMgrDbLostCallbackTest::db_lost_callback, this, ph::_1);
+
+ // Set the connectivity recovered callback.
+ isc::db::DatabaseConnection::db_recovered_callback_ =
+ std::bind(&HostMgrDbLostCallbackTest::db_recovered_callback, this, ph::_1);
+
+ // Set the connectivity failed callback.
+ isc::db::DatabaseConnection::db_failed_callback_ =
+ std::bind(&HostMgrDbLostCallbackTest::db_failed_callback, this, ph::_1);
+
+ // Connect to the host backend.
+ ASSERT_THROW(HostMgr::addBackend(invalidConnectString()), DbOpenError);
+
+ io_service_->poll();
+
+ EXPECT_EQ(0, db_lost_callback_called_);
+ EXPECT_EQ(0, db_recovered_callback_called_);
+ EXPECT_EQ(0, db_failed_callback_called_);
+}
+
+void
+HostMgrDbLostCallbackTest::testDbLostAndRecoveredCallback() {
+ // Set the connectivity lost callback.
+ isc::db::DatabaseConnection::db_lost_callback_ =
+ std::bind(&HostMgrDbLostCallbackTest::db_lost_callback, this, ph::_1);
+
+ // Set the connectivity recovered callback.
+ isc::db::DatabaseConnection::db_recovered_callback_ =
+ std::bind(&HostMgrDbLostCallbackTest::db_recovered_callback, this, ph::_1);
+
+ // Set the connectivity failed callback.
+ isc::db::DatabaseConnection::db_failed_callback_ =
+ std::bind(&HostMgrDbLostCallbackTest::db_failed_callback, this, ph::_1);
+
+ std::string access = validConnectString();
+ CfgMgr::instance().getCurrentCfg()->getCfgDbAccess()->setHostDbAccessString(access);
+
+ // Create the HostMgr.
+ HostMgr::create();
+
+ // Find the most recently opened socket. Our SQL client's socket should
+ // be the next one.
+ int last_open_socket = findLastSocketFd();
+
+ // Fill holes.
+ FillFdHoles holes(last_open_socket);
+
+ // Connect to the host backend.
+ ASSERT_NO_THROW(HostMgr::addBackend(access));
+
+ // Find the SQL client socket.
+ int sql_socket = findLastSocketFd();
+ ASSERT_TRUE(sql_socket > last_open_socket);
+
+ // Verify we can execute a query. We don't care about the answer.
+ ConstHostCollection hosts;
+ ASSERT_NO_THROW(hosts = HostMgr::instance().getAll4(IOAddress("192.0.2.5")));
+
+ // Now close the sql socket out from under backend client
+ ASSERT_EQ(0, close(sql_socket));
+
+ // A query should fail with DbConnectionUnusable.
+ ASSERT_THROW(hosts = HostMgr::instance().getAll4(IOAddress("192.0.2.5")),
+ DbConnectionUnusable);
+
+ io_service_->poll();
+
+ // Our lost and recovered connectivity callback should have been invoked.
+ EXPECT_EQ(1, db_lost_callback_called_);
+ EXPECT_EQ(1, db_recovered_callback_called_);
+ EXPECT_EQ(0, db_failed_callback_called_);
+}
+
+void
+HostMgrDbLostCallbackTest::testDbLostAndFailedCallback() {
+ // Set the connectivity lost callback.
+ isc::db::DatabaseConnection::db_lost_callback_ =
+ std::bind(&HostMgrDbLostCallbackTest::db_lost_callback, this, ph::_1);
+
+ // Set the connectivity recovered callback.
+ isc::db::DatabaseConnection::db_recovered_callback_ =
+ std::bind(&HostMgrDbLostCallbackTest::db_recovered_callback, this, ph::_1);
+
+ // Set the connectivity failed callback.
+ isc::db::DatabaseConnection::db_failed_callback_ =
+ std::bind(&HostMgrDbLostCallbackTest::db_failed_callback, this, ph::_1);
+
+ std::string access = validConnectString();
+ CfgMgr::instance().getCurrentCfg()->getCfgDbAccess()->setHostDbAccessString(access);
+
+ // Create the HostMgr.
+ HostMgr::create();
+
+ // Find the most recently opened socket. Our SQL client's socket should
+ // be the next one.
+ int last_open_socket = findLastSocketFd();
+
+ // Fill holes.
+ FillFdHoles holes(last_open_socket);
+
+ // Connect to the host backend.
+ ASSERT_NO_THROW(HostMgr::addBackend(access));
+
+ // Find the SQL client socket.
+ int sql_socket = findLastSocketFd();
+ ASSERT_TRUE(sql_socket > last_open_socket);
+
+ // Verify we can execute a query. We don't care about the answer.
+ ConstHostCollection hosts;
+ ASSERT_NO_THROW(hosts = HostMgr::instance().getAll4(IOAddress("192.0.2.5")));
+
+ access = invalidConnectString();
+ CfgMgr::instance().clear();
+ // by adding an invalid access will cause the manager factory to throw
+ // resulting in failure to recreate the manager
+ CfgMgr::instance().getCurrentCfg()->getCfgDbAccess()->setHostDbAccessString(access);
+
+ // Now close the sql socket out from under backend client
+ ASSERT_EQ(0, close(sql_socket));
+
+ // A query should fail with DbConnectionUnusable.
+ ASSERT_THROW(hosts = HostMgr::instance().getAll4(IOAddress("192.0.2.5")),
+ DbConnectionUnusable);
+
+ io_service_->poll();
+
+ // Our lost and failed connectivity callback should have been invoked.
+ EXPECT_EQ(1, db_lost_callback_called_);
+ EXPECT_EQ(0, db_recovered_callback_called_);
+ EXPECT_EQ(1, db_failed_callback_called_);
+}
+
+void
+HostMgrDbLostCallbackTest::testDbLostAndRecoveredAfterTimeoutCallback() {
+ // Set the connectivity lost callback.
+ isc::db::DatabaseConnection::db_lost_callback_ =
+ std::bind(&HostMgrDbLostCallbackTest::db_lost_callback, this, ph::_1);
+
+ // Set the connectivity recovered callback.
+ isc::db::DatabaseConnection::db_recovered_callback_ =
+ std::bind(&HostMgrDbLostCallbackTest::db_recovered_callback, this, ph::_1);
+
+ // Set the connectivity failed callback.
+ isc::db::DatabaseConnection::db_failed_callback_ =
+ std::bind(&HostMgrDbLostCallbackTest::db_failed_callback, this, ph::_1);
+
+ std::string access = validConnectString();
+ std::string extra = " max-reconnect-tries=3 reconnect-wait-time=1";
+ access += extra;
+ CfgMgr::instance().getCurrentCfg()->getCfgDbAccess()->setHostDbAccessString(access);
+
+ // Create the HostMgr.
+ HostMgr::create();
+
+ // Find the most recently opened socket. Our SQL client's socket should
+ // be the next one.
+ int last_open_socket = findLastSocketFd();
+
+ // Fill holes.
+ FillFdHoles holes(last_open_socket);
+
+ // Connect to the host backend.
+ ASSERT_NO_THROW(HostMgr::addBackend(access));
+
+ // Find the SQL client socket.
+ int sql_socket = findLastSocketFd();
+ ASSERT_TRUE(sql_socket > last_open_socket);
+
+ // Verify we can execute a query. We don't care about the answer.
+ ConstHostCollection hosts;
+ ASSERT_NO_THROW(hosts = HostMgr::instance().getAll4(IOAddress("192.0.2.5")));
+
+ access = invalidConnectString();
+ access += extra;
+ CfgMgr::instance().clear();
+ // by adding an invalid access will cause the manager factory to throw
+ // resulting in failure to recreate the manager
+ CfgMgr::instance().getCurrentCfg()->getCfgDbAccess()->setHostDbAccessString(access);
+
+ // Now close the sql socket out from under backend client
+ ASSERT_EQ(0, close(sql_socket));
+
+ // A query should fail with DbConnectionUnusable.
+ ASSERT_THROW(hosts = HostMgr::instance().getAll4(IOAddress("192.0.2.5")),
+ DbConnectionUnusable);
+
+ io_service_->poll();
+
+ // Our lost connectivity callback should have been invoked.
+ EXPECT_EQ(1, db_lost_callback_called_);
+ EXPECT_EQ(0, db_recovered_callback_called_);
+ EXPECT_EQ(0, db_failed_callback_called_);
+
+ access = validConnectString();
+ access += extra;
+ CfgMgr::instance().clear();
+ CfgMgr::instance().getCurrentCfg()->getCfgDbAccess()->setHostDbAccessString(access);
+
+ sleep(1);
+
+ io_service_->poll();
+
+ // Our lost and recovered connectivity callback should have been invoked.
+ EXPECT_EQ(2, db_lost_callback_called_);
+ EXPECT_EQ(1, db_recovered_callback_called_);
+ EXPECT_EQ(0, db_failed_callback_called_);
+
+ sleep(1);
+
+ io_service_->poll();
+
+ // No callback should have been invoked.
+ EXPECT_EQ(2, db_lost_callback_called_);
+ EXPECT_EQ(1, db_recovered_callback_called_);
+ EXPECT_EQ(0, db_failed_callback_called_);
+}
+
+void
+HostMgrDbLostCallbackTest::testDbLostAndFailedAfterTimeoutCallback() {
+ // Set the connectivity lost callback.
+ isc::db::DatabaseConnection::db_lost_callback_ =
+ std::bind(&HostMgrDbLostCallbackTest::db_lost_callback, this, ph::_1);
+
+ // Set the connectivity recovered callback.
+ isc::db::DatabaseConnection::db_recovered_callback_ =
+ std::bind(&HostMgrDbLostCallbackTest::db_recovered_callback, this, ph::_1);
+
+ // Set the connectivity failed callback.
+ isc::db::DatabaseConnection::db_failed_callback_ =
+ std::bind(&HostMgrDbLostCallbackTest::db_failed_callback, this, ph::_1);
+
+ std::string access = validConnectString();
+ std::string extra = " max-reconnect-tries=3 reconnect-wait-time=1";
+ access += extra;
+ CfgMgr::instance().getCurrentCfg()->getCfgDbAccess()->setHostDbAccessString(access);
+
+ // Create the HostMgr.
+ HostMgr::create();
+
+ // Find the most recently opened socket. Our SQL client's socket should
+ // be the next one.
+ int last_open_socket = findLastSocketFd();
+
+ // Fill holes.
+ FillFdHoles holes(last_open_socket);
+
+ // Connect to the host backend.
+ ASSERT_NO_THROW(HostMgr::addBackend(access));
+
+ // Find the SQL client socket.
+ int sql_socket = findLastSocketFd();
+ ASSERT_TRUE(sql_socket > last_open_socket);
+
+ // Verify we can execute a query. We don't care about the answer.
+ ConstHostCollection hosts;
+ ASSERT_NO_THROW(hosts = HostMgr::instance().getAll4(IOAddress("192.0.2.5")));
+
+ access = invalidConnectString();
+ access += extra;
+ CfgMgr::instance().clear();
+ // by adding an invalid access will cause the manager factory to throw
+ // resulting in failure to recreate the manager
+ CfgMgr::instance().getCurrentCfg()->getCfgDbAccess()->setHostDbAccessString(access);
+
+ // Now close the sql socket out from under backend client
+ ASSERT_EQ(0, close(sql_socket));
+
+ // A query should fail with DbConnectionUnusable.
+ ASSERT_THROW(hosts = HostMgr::instance().getAll4(IOAddress("192.0.2.5")),
+ DbConnectionUnusable);
+
+ io_service_->poll();
+
+ // Our lost connectivity callback should have been invoked.
+ EXPECT_EQ(1, db_lost_callback_called_);
+ EXPECT_EQ(0, db_recovered_callback_called_);
+ EXPECT_EQ(0, db_failed_callback_called_);
+
+ sleep(1);
+
+ io_service_->poll();
+
+ // Our lost connectivity callback should have been invoked.
+ EXPECT_EQ(2, db_lost_callback_called_);
+ EXPECT_EQ(0, db_recovered_callback_called_);
+ EXPECT_EQ(0, db_failed_callback_called_);
+
+ sleep(1);
+
+ io_service_->poll();
+
+ // Our lost and failed connectivity callback should have been invoked.
+ EXPECT_EQ(3, db_lost_callback_called_);
+ EXPECT_EQ(0, db_recovered_callback_called_);
+ EXPECT_EQ(1, db_failed_callback_called_);
+}
+
+void
+// cppcheck-suppress unusedFunction
+HostMgrTest::SetUp() {
+ // Remove all configuration which may be dangling from the previous test.
+ CfgMgr::instance().clear();
+ // Recreate HostMgr instance. It drops any previous state.
+ HostMgr::create();
+ // Create HW addresses from the template.
+ const uint8_t mac_template[] = {
+ 0x01, 0x02, 0x0A, 0xBB, 0x03, 0x00
+ };
+ for (uint8_t i = 0; i < 10; ++i) {
+ std::vector<uint8_t> vec(mac_template,
+ mac_template + sizeof(mac_template));
+ vec[vec.size() - 1] = i;
+ HWAddrPtr hwaddr(new HWAddr(vec, HTYPE_ETHER));
+ hwaddrs_.push_back(hwaddr);
+ }
+ // Create DUIDs from the template.
+ const uint8_t duid_template[] = {
+ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x00
+ };
+ for (uint8_t i = 0; i < 10; ++i) {
+ std::vector<uint8_t> vec(duid_template,
+ duid_template + sizeof(mac_template));
+ vec[vec.size() - 1] = i;
+ DuidPtr duid(new DUID(vec));
+ duids_.push_back(duid);
+ }
+}
+
+CfgHostsPtr
+HostMgrTest::getCfgHosts() const {
+ return (CfgMgr::instance().getStagingCfg()->getCfgHosts());
+}
+
+void
+HostMgrTest::addHost4(BaseHostDataSource& data_source,
+ const HWAddrPtr& hwaddr,
+ const SubnetID& subnet_id,
+ const IOAddress& address) {
+ data_source.add(HostPtr(new Host(hwaddr->toText(false),
+ "hw-address", subnet_id, SUBNET_ID_UNUSED,
+ address)));
+}
+
+void
+HostMgrTest::addHost6(BaseHostDataSource& data_source,
+ const DuidPtr& duid,
+ const SubnetID& subnet_id,
+ const IOAddress& address,
+ const uint8_t prefix_len) {
+ HostPtr new_host(new Host(duid->toText(), "duid", SubnetID(1),
+ subnet_id, IOAddress::IPV4_ZERO_ADDRESS()));
+ new_host->addReservation(IPv6Resrv(prefix_len == 128 ? IPv6Resrv::TYPE_NA :
+ IPv6Resrv::TYPE_PD,
+ address, prefix_len));
+ data_source.add(new_host);
+}
+
+
+void
+HostMgrTest::testGetAll(BaseHostDataSource& data_source1,
+ BaseHostDataSource& data_source2) {
+ // Initially, no reservations should be present.
+ ConstHostCollection hosts =
+ HostMgr::instance().getAll(Host::IDENT_HWADDR,
+ &hwaddrs_[1]->hwaddr_[0],
+ hwaddrs_[1]->hwaddr_.size());
+ ASSERT_TRUE(hosts.empty());
+
+ // Add two reservations for the same HW address. They differ by the IP
+ // address reserved and the IPv4 subnet.
+ addHost4(data_source1, hwaddrs_[0], SubnetID(1), IOAddress("192.0.2.5"));
+ addHost4(data_source2, hwaddrs_[0], SubnetID(10), IOAddress("192.0.3.10"));
+
+ CfgMgr::instance().commit();
+
+ // If there non-matching HW address is specified, nothing should be
+ // returned.
+ hosts = HostMgr::instance().getAll(Host::IDENT_HWADDR,
+ &hwaddrs_[1]->hwaddr_[0],
+ hwaddrs_[1]->hwaddr_.size());
+ ASSERT_TRUE(hosts.empty());
+
+ // For the correct HW address, there should be two reservations.
+ hosts = HostMgr::instance().getAll(Host::IDENT_HWADDR,
+ &hwaddrs_[0]->hwaddr_[0],
+ hwaddrs_[0]->hwaddr_.size());
+ ASSERT_EQ(2, hosts.size());
+
+ // We don't know the order in which the reservations are returned so
+ // we have to match with any of the two reservations returned.
+
+ // Look for the first reservation.
+ bool found = false;
+ for (unsigned i = 0; i < 2; ++i) {
+ if (hosts[0]->getIPv4Reservation() == IOAddress("192.0.2.5")) {
+ ASSERT_EQ(1, hosts[0]->getIPv4SubnetID());
+ found = true;
+ }
+ }
+ if (!found) {
+ ADD_FAILURE() << "Reservation for the IPv4 address 192.0.2.5"
+ " not found using getAll method";
+ }
+
+ // Look for the second reservation.
+ found = false;
+ for (unsigned i = 0; i < 2; ++i) {
+ if (hosts[1]->getIPv4Reservation() == IOAddress("192.0.3.10")) {
+ ASSERT_EQ(10, hosts[1]->getIPv4SubnetID());
+ found = true;
+ }
+ }
+ if (!found) {
+ ADD_FAILURE() << "Reservation for the IPv4 address 192.0.3.10"
+ " not found using getAll method";
+ }
+}
+
+void
+HostMgrTest::testGetAll4BySubnet(BaseHostDataSource& data_source1,
+ BaseHostDataSource& data_source2) {
+ // Initially, no reservations should be present.
+ ConstHostCollection hosts = HostMgr::instance().getAll4(SubnetID(1));
+ ASSERT_TRUE(hosts.empty());
+
+ // Add two reservations for the same subnet.
+ addHost4(data_source1, hwaddrs_[0], SubnetID(1), IOAddress("192.0.2.5"));
+ addHost4(data_source2, hwaddrs_[1], SubnetID(1), IOAddress("192.0.2.6"));
+
+ CfgMgr::instance().commit();
+
+ // If there non-matching subnet is specified, nothing should be returned.
+ hosts = HostMgr::instance().getAll4(SubnetID(100));
+ ASSERT_TRUE(hosts.empty());
+
+ // For the correct subnet, there should be two reservations.
+ hosts = HostMgr::instance().getAll4(SubnetID(1));
+ ASSERT_EQ(2, hosts.size());
+
+ // Make sure that subnet is correct.
+ EXPECT_EQ(1, hosts[0]->getIPv4SubnetID());
+ EXPECT_EQ(1, hosts[1]->getIPv4SubnetID());
+
+ // Make sure that two different hosts were returned.
+ EXPECT_EQ("192.0.2.5", hosts[0]->getIPv4Reservation().toText());
+ EXPECT_EQ("192.0.2.6", hosts[1]->getIPv4Reservation().toText());
+}
+
+void
+HostMgrTest::testGetAll6BySubnet(BaseHostDataSource& data_source1,
+ BaseHostDataSource& data_source2) {
+ // Initially, no reservations should be present.
+ ConstHostCollection hosts = HostMgr::instance().getAll6(SubnetID(1));
+ ASSERT_TRUE(hosts.empty());
+
+ // Add two reservations for the same subnet.
+ addHost6(data_source1, duids_[0], SubnetID(1), IOAddress("2001:db8:1::5"));
+ addHost6(data_source2, duids_[1], SubnetID(1), IOAddress("2001:db8:1::6"));
+
+ CfgMgr::instance().commit();
+
+ // If there non-matching subnet is specified, nothing should be returned.
+ hosts = HostMgr::instance().getAll6(SubnetID(100));
+ ASSERT_TRUE(hosts.empty());
+
+ // For the correct subnet, there should be two reservations.
+ hosts = HostMgr::instance().getAll6(SubnetID(1));
+ ASSERT_EQ(2, hosts.size());
+
+ // Make sure that subnet is correct.
+ EXPECT_EQ(1, hosts[0]->getIPv6SubnetID());
+ EXPECT_EQ(1, hosts[1]->getIPv6SubnetID());
+
+ // Make sure that two different hosts were returned.
+ EXPECT_TRUE(hosts[0]->hasReservation(
+ IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::5"))));
+ EXPECT_TRUE(hosts[1]->hasReservation(
+ IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::6"))));
+}
+
+void
+HostMgrTest::testGetAllbyHostname(BaseHostDataSource& data_source1,
+ BaseHostDataSource& data_source2) {
+ // Initially, no reservations should be present.
+ ConstHostCollection hosts =
+ HostMgr::instance().getAllbyHostname("host");
+ ASSERT_TRUE(hosts.empty());
+
+ // Add two reservations with the same hostname.
+ HostPtr host1(new Host(hwaddrs_[0]->toText(false), "hw-address",
+ SubnetID(1), SUBNET_ID_UNUSED,
+ IOAddress("192.0.2.5")));
+ host1->setHostname("Host");
+ data_source1.add(host1);
+ HostPtr host2(new Host(hwaddrs_[1]->toText(false), "hw-address",
+ SubnetID(10), SUBNET_ID_UNUSED,
+ IOAddress("192.0.3.10")));
+ host2->setHostname("hosT");
+ data_source2.add(host2);
+
+ CfgMgr::instance().commit();
+
+ // If there non-matching hostname is specified, nothing should be
+ // returned.
+ hosts = HostMgr::instance().getAllbyHostname("foobar");
+ EXPECT_TRUE(hosts.empty());
+
+ // For the correct hostname, there should be two reservations.
+ hosts = HostMgr::instance().getAllbyHostname("host");
+ ASSERT_EQ(2, hosts.size());
+
+ // Make sure that subnet is correct.
+ EXPECT_EQ(1, hosts[0]->getIPv4SubnetID());
+ EXPECT_EQ(10, hosts[1]->getIPv4SubnetID());
+
+ // Make sure that hostname is correct including its case.
+ EXPECT_EQ("Host", hosts[0]->getHostname());
+ EXPECT_EQ("hosT", hosts[1]->getHostname());
+}
+
+void
+HostMgrTest::testGetAllbyHostnameSubnet4(BaseHostDataSource& data_source1,
+ BaseHostDataSource& data_source2) {
+ // Initially, no reservations should be present.
+ ConstHostCollection hosts =
+ HostMgr::instance().getAllbyHostname4("host", SubnetID(1));
+ ASSERT_TRUE(hosts.empty());
+
+ // Add two reservations with the same hostname.
+ HostPtr host1(new Host(hwaddrs_[0]->toText(false), "hw-address",
+ SubnetID(1), SUBNET_ID_UNUSED,
+ IOAddress("192.0.2.5")));
+ host1->setHostname("Host");
+ data_source1.add(host1);
+ HostPtr host2(new Host(hwaddrs_[1]->toText(false), "hw-address",
+ SubnetID(1), SUBNET_ID_UNUSED,
+ IOAddress("192.0.2.6")));
+ host2->setHostname("hosT");
+ data_source2.add(host2);
+
+ CfgMgr::instance().commit();
+
+ // If there non-matching hostname is specified, nothing should be
+ // returned.
+ hosts = HostMgr::instance().getAllbyHostname4("foobar", SubnetID(1));
+ EXPECT_TRUE(hosts.empty());
+
+ // If there non-matching subnet is specified, nothing should be
+ // returned.
+ hosts = HostMgr::instance().getAllbyHostname4("host", SubnetID(10));
+ EXPECT_TRUE(hosts.empty());
+
+ // For the correct hostname, there should be two reservations.
+ hosts = HostMgr::instance().getAllbyHostname4("host", SubnetID(1));
+ ASSERT_EQ(2, hosts.size());
+
+ // Make sure that subnet is correct.
+ EXPECT_EQ(1, hosts[0]->getIPv4SubnetID());
+ EXPECT_EQ(1, hosts[1]->getIPv4SubnetID());
+
+ // Make sure that two different hosts were returned.
+ EXPECT_EQ("192.0.2.5", hosts[0]->getIPv4Reservation().toText());
+ EXPECT_EQ("192.0.2.6", hosts[1]->getIPv4Reservation().toText());
+
+ // Make sure that hostname is correct including its case.
+ EXPECT_EQ("Host", hosts[0]->getHostname());
+ EXPECT_EQ("hosT", hosts[1]->getHostname());
+}
+
+void
+HostMgrTest::testGetAllbyHostnameSubnet6(BaseHostDataSource& data_source1,
+ BaseHostDataSource& data_source2) {
+ // Initially, no reservations should be present.
+ ConstHostCollection hosts =
+ HostMgr::instance().getAllbyHostname6("host", SubnetID(1));
+ ASSERT_TRUE(hosts.empty());
+
+ // Add two reservations with the same hostname.
+ HostPtr host1(new Host(duids_[0]->toText(), "duid",
+ SubnetID(1), SubnetID(1),
+ IOAddress::IPV4_ZERO_ADDRESS()));
+ host1->addReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+ IOAddress("2001:db8:1::5"), 128));
+ host1->setHostname("Host");
+ data_source1.add(host1);
+ HostPtr host2(new Host(duids_[1]->toText(), "duid",
+ SubnetID(1), SubnetID(1),
+ IOAddress::IPV4_ZERO_ADDRESS()));
+ host2->addReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+ IOAddress("2001:db8:1::6"), 128));
+ host2->setHostname("hosT");
+ data_source2.add(host2);
+
+ CfgMgr::instance().commit();
+
+ // If there non-matching hostname is specified, nothing should be
+ // returned.
+ hosts = HostMgr::instance().getAllbyHostname6("foobar", SubnetID(1));
+ EXPECT_TRUE(hosts.empty());
+
+ // If there non-matching subnet is specified, nothing should be
+ // returned.
+ hosts = HostMgr::instance().getAllbyHostname6("host", SubnetID(10));
+ EXPECT_TRUE(hosts.empty());
+
+ // For the correct hostname, there should be two reservations.
+ hosts = HostMgr::instance().getAllbyHostname6("host", SubnetID(1));
+ ASSERT_EQ(2, hosts.size());
+
+ // Make sure that subnet is correct.
+ EXPECT_EQ(1, hosts[0]->getIPv6SubnetID());
+ EXPECT_EQ(1, hosts[1]->getIPv6SubnetID());
+
+ // Make sure that two different hosts were returned.
+ EXPECT_TRUE(hosts[0]->hasReservation(
+ IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::5"))));
+ EXPECT_TRUE(hosts[1]->hasReservation(
+ IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::6"))));
+
+ // Make sure that hostname is correct including its case.
+ EXPECT_EQ("Host", hosts[0]->getHostname());
+ EXPECT_EQ("hosT", hosts[1]->getHostname());
+}
+
+void
+HostMgrTest::testGetPage4(bool use_database) {
+ BaseHostDataSource& data_source1 = *getCfgHosts();
+ BaseHostDataSource& data_source2 = HostMgr::instance();
+
+ // Initially, no reservations should be present.
+ size_t idx(0);
+ HostPageSize page_size(10);
+ ConstHostCollection hosts =
+ HostMgr::instance().getPage4(SubnetID(1), idx, 0, page_size);
+ ASSERT_TRUE(hosts.empty());
+ if (use_database) {
+ EXPECT_EQ(2, idx);
+ } else {
+ EXPECT_EQ(1, idx);
+ }
+
+ // Add two reservations for the same subnet.
+ addHost4(data_source1, hwaddrs_[0], SubnetID(1), IOAddress("192.0.2.5"));
+ addHost4(use_database ? data_source2 : data_source1,
+ hwaddrs_[1], SubnetID(1), IOAddress("192.0.2.6"));
+
+ CfgMgr::instance().commit();
+
+ // If there non-matching subnet is specified, nothing should be returned.
+ idx = 0;
+ hosts = HostMgr::instance().getPage4(SubnetID(100), idx, 0, page_size);
+ ASSERT_TRUE(hosts.empty());
+
+ // For the correct subnet, there should be two reservations.
+ idx = 0;
+ hosts = HostMgr::instance().getPage4(SubnetID(1), idx, 0, page_size);
+ if (use_database) {
+ ASSERT_EQ(1, hosts.size());
+ } else {
+ ASSERT_EQ(2, hosts.size());
+ }
+
+ // Make sure that returned values are correct.
+ EXPECT_EQ(1, hosts[0]->getIPv4SubnetID());
+ EXPECT_EQ("192.0.2.5", hosts[0]->getIPv4Reservation().toText());
+ if (!use_database) {
+ EXPECT_EQ(1, hosts[1]->getIPv4SubnetID());
+ EXPECT_EQ("192.0.2.6", hosts[1]->getIPv4Reservation().toText());
+
+ // Check it was the last page.
+ uint64_t hid = hosts[1]->getHostId();
+ hosts = HostMgr::instance().getPage4(SubnetID(1), idx, hid, page_size);
+ ASSERT_EQ(0, hosts.size());
+ idx = 1;
+ hosts = HostMgr::instance().getPage4(SubnetID(1), idx, 0, page_size);
+ ASSERT_EQ(0, hosts.size());
+ }
+
+ if (use_database) {
+ uint64_t hid = hosts[0]->getHostId();
+ ASSERT_NE(0, hid);
+ ASSERT_EQ(0, idx);
+ hosts = HostMgr::instance().getPage4(SubnetID(1), idx, hid, page_size);
+ ASSERT_EQ(1, hosts.size());
+ ASSERT_NE(0, idx);
+ EXPECT_EQ(1, hosts[0]->getIPv4SubnetID());
+ EXPECT_EQ("192.0.2.6", hosts[0]->getIPv4Reservation().toText());
+
+ // Alternate way to use the database.
+ idx = 1;
+ hosts = HostMgr::instance().getPage4(SubnetID(1), idx, 0, page_size);
+ ASSERT_EQ(1, hosts.size());
+ EXPECT_EQ(1, hosts[0]->getIPv4SubnetID());
+ EXPECT_EQ("192.0.2.6", hosts[0]->getIPv4Reservation().toText());
+
+ // Check it was the last page.
+ hid = hosts[0]->getHostId();
+ ASSERT_NE(0, hid);
+ hosts = HostMgr::instance().getPage4(SubnetID(1), idx, hid, page_size);
+ ASSERT_EQ(0, hosts.size());
+ idx = 2;
+ hosts = HostMgr::instance().getPage4(SubnetID(1), idx, 0, page_size);
+ ASSERT_EQ(0, hosts.size());
+ }
+}
+
+void
+HostMgrTest::testGetPage6(bool use_database) {
+ BaseHostDataSource& data_source1 = *getCfgHosts();
+ BaseHostDataSource& data_source2 = HostMgr::instance();
+
+ // Initially, no reservations should be present.
+ size_t idx(0);
+ HostPageSize page_size(10);
+ ConstHostCollection hosts =
+ HostMgr::instance().getPage6(SubnetID(1), idx, 0, page_size);
+ ASSERT_TRUE(hosts.empty());
+ if (use_database) {
+ EXPECT_EQ(2, idx);
+ } else {
+ EXPECT_EQ(1, idx);
+ }
+
+ // Add two reservations for the same subnet.
+ addHost6(data_source1, duids_[0], SubnetID(1), IOAddress("2001:db8:1::5"));
+ addHost6(use_database ? data_source2 : data_source1,
+ duids_[1], SubnetID(1), IOAddress("2001:db8:1::6"));
+
+ CfgMgr::instance().commit();
+
+ // If there non-matching subnet is specified, nothing should be returned.
+ idx = 0;
+ hosts = HostMgr::instance().getPage6(SubnetID(100), idx, 0, page_size);
+ ASSERT_TRUE(hosts.empty());
+
+ // For the correct subnet, there should be two reservations.
+ idx = 0;
+ hosts = HostMgr::instance().getPage6(SubnetID(1), idx, 0, page_size);
+ if (use_database) {
+ ASSERT_EQ(1, hosts.size());
+ } else {
+ ASSERT_EQ(2, hosts.size());
+ }
+
+ // Make sure that returned values are correct.
+ EXPECT_EQ(1, hosts[0]->getIPv6SubnetID());
+ EXPECT_TRUE(hosts[0]->hasReservation(
+ IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::5"))));
+ if (!use_database) {
+ EXPECT_EQ(1, hosts[1]->getIPv6SubnetID());
+ EXPECT_TRUE(hosts[1]->hasReservation(
+ IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::6"))));
+
+ // Check it was the last page.
+ uint64_t hid = hosts[1]->getHostId();
+ hosts = HostMgr::instance().getPage6(SubnetID(1), idx, hid, page_size);
+ ASSERT_EQ(0, hosts.size());
+ idx = 1;
+ hosts = HostMgr::instance().getPage6(SubnetID(1), idx, 0, page_size);
+ ASSERT_EQ(0, hosts.size());
+ }
+
+ if (use_database) {
+ uint64_t hid = hosts[0]->getHostId();
+ ASSERT_NE(0, hid);
+ ASSERT_EQ(0, idx);
+ hosts = HostMgr::instance().getPage6(SubnetID(1), idx, hid, page_size);
+ ASSERT_EQ(1, hosts.size());
+ ASSERT_NE(0, idx);
+ EXPECT_EQ(1, hosts[0]->getIPv6SubnetID());
+ EXPECT_TRUE(hosts[0]->hasReservation(
+ IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::6"))));
+
+ // Alternate way to use the database.
+ idx = 1;
+ hosts = HostMgr::instance().getPage6(SubnetID(1), idx, 0, page_size);
+ ASSERT_EQ(1, hosts.size());
+ EXPECT_EQ(1, hosts[0]->getIPv6SubnetID());
+ EXPECT_TRUE(hosts[0]->hasReservation(
+ IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::6"))));
+
+ // Check it was the last page.
+ hid = hosts[0]->getHostId();
+ ASSERT_NE(0, hid);
+ hosts = HostMgr::instance().getPage6(SubnetID(1), idx, hid, page_size);
+ ASSERT_EQ(0, hosts.size());
+ idx = 2;
+ hosts = HostMgr::instance().getPage6(SubnetID(1), idx, 0, page_size);
+ ASSERT_EQ(0, hosts.size());
+ }
+}
+
+void
+HostMgrTest::testGetPage4All(bool use_database) {
+ BaseHostDataSource& data_source1 = *getCfgHosts();
+ BaseHostDataSource& data_source2 = HostMgr::instance();
+
+ // Initially, no reservations should be present.
+ size_t idx(0);
+ HostPageSize page_size(10);
+ ConstHostCollection hosts =
+ HostMgr::instance().getPage4(idx, 0, page_size);
+ ASSERT_TRUE(hosts.empty());
+ if (use_database) {
+ EXPECT_EQ(2, idx);
+ } else {
+ EXPECT_EQ(1, idx);
+ }
+
+ // Add two reservations.
+ addHost4(data_source1, hwaddrs_[0], SubnetID(1), IOAddress("192.0.2.5"));
+ addHost4(use_database ? data_source2 : data_source1,
+ hwaddrs_[1], SubnetID(2), IOAddress("192.0.2.6"));
+
+ CfgMgr::instance().commit();
+
+ // There should be two reservations.
+ idx = 0;
+ hosts = HostMgr::instance().getPage4(idx, 0, page_size);
+ if (use_database) {
+ ASSERT_EQ(1, hosts.size());
+ } else {
+ ASSERT_EQ(2, hosts.size());
+ }
+
+ // Make sure that returned values are correct.
+ EXPECT_EQ(1, hosts[0]->getIPv4SubnetID());
+ EXPECT_EQ("192.0.2.5", hosts[0]->getIPv4Reservation().toText());
+ if (!use_database) {
+ EXPECT_EQ(2, hosts[1]->getIPv4SubnetID());
+ EXPECT_EQ("192.0.2.6", hosts[1]->getIPv4Reservation().toText());
+
+ // Check it was the last page.
+ uint64_t hid = hosts[1]->getHostId();
+ hosts = HostMgr::instance().getPage4(idx, hid, page_size);
+ ASSERT_EQ(0, hosts.size());
+ idx = 1;
+ hosts = HostMgr::instance().getPage4(idx, 0, page_size);
+ ASSERT_EQ(0, hosts.size());
+ }
+
+ if (use_database) {
+ uint64_t hid = hosts[0]->getHostId();
+ ASSERT_NE(0, hid);
+ ASSERT_EQ(0, idx);
+ hosts = HostMgr::instance().getPage4(idx, hid, page_size);
+ ASSERT_EQ(1, hosts.size());
+ ASSERT_NE(0, idx);
+ EXPECT_EQ(2, hosts[0]->getIPv4SubnetID());
+ EXPECT_EQ("192.0.2.6", hosts[0]->getIPv4Reservation().toText());
+
+ // Alternate way to use the database.
+ idx = 1;
+ hosts = HostMgr::instance().getPage4(idx, 0, page_size);
+ ASSERT_EQ(1, hosts.size());
+ EXPECT_EQ(2, hosts[0]->getIPv4SubnetID());
+ EXPECT_EQ("192.0.2.6", hosts[0]->getIPv4Reservation().toText());
+
+ // Check it was the last page.
+ hid = hosts[0]->getHostId();
+ ASSERT_NE(0, hid);
+ hosts = HostMgr::instance().getPage4(idx, hid, page_size);
+ ASSERT_EQ(0, hosts.size());
+ idx = 2;
+ hosts = HostMgr::instance().getPage4(idx, 0, page_size);
+ ASSERT_EQ(0, hosts.size());
+ }
+}
+
+void
+HostMgrTest::testGetPage6All(bool use_database) {
+ BaseHostDataSource& data_source1 = *getCfgHosts();
+ BaseHostDataSource& data_source2 = HostMgr::instance();
+
+ // Initially, no reservations should be present.
+ size_t idx(0);
+ HostPageSize page_size(10);
+ ConstHostCollection hosts =
+ HostMgr::instance().getPage6(idx, 0, page_size);
+ ASSERT_TRUE(hosts.empty());
+ if (use_database) {
+ EXPECT_EQ(2, idx);
+ } else {
+ EXPECT_EQ(1, idx);
+ }
+
+ // Add two reservations.
+ addHost6(data_source1, duids_[0], SubnetID(1), IOAddress("2001:db8:1::5"));
+ addHost6(use_database ? data_source2 : data_source1,
+ duids_[1], SubnetID(2), IOAddress("2001:db8:1::6"));
+
+ CfgMgr::instance().commit();
+
+ // There should be two reservations.
+ idx = 0;
+ hosts = HostMgr::instance().getPage6(idx, 0, page_size);
+ if (use_database) {
+ ASSERT_EQ(1, hosts.size());
+ } else {
+ ASSERT_EQ(2, hosts.size());
+ }
+
+ // Make sure that returned values are correct.
+ EXPECT_EQ(1, hosts[0]->getIPv6SubnetID());
+ EXPECT_TRUE(hosts[0]->hasReservation(
+ IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::5"))));
+ if (!use_database) {
+ EXPECT_EQ(2, hosts[1]->getIPv6SubnetID());
+ EXPECT_TRUE(hosts[1]->hasReservation(
+ IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::6"))));
+
+ // Check it was the last page.
+ uint64_t hid = hosts[1]->getHostId();
+ hosts = HostMgr::instance().getPage6(idx, hid, page_size);
+ ASSERT_EQ(0, hosts.size());
+ idx = 1;
+ hosts = HostMgr::instance().getPage6(idx, 0, page_size);
+ ASSERT_EQ(0, hosts.size());
+ }
+
+ if (use_database) {
+ uint64_t hid = hosts[0]->getHostId();
+ ASSERT_NE(0, hid);
+ ASSERT_EQ(0, idx);
+ hosts = HostMgr::instance().getPage6(idx, hid, page_size);
+ ASSERT_EQ(1, hosts.size());
+ ASSERT_NE(0, idx);
+ EXPECT_EQ(2, hosts[0]->getIPv6SubnetID());
+ EXPECT_TRUE(hosts[0]->hasReservation(
+ IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::6"))));
+
+ // Alternate way to use the database.
+ idx = 1;
+ hosts = HostMgr::instance().getPage6(idx, 0, page_size);
+ ASSERT_EQ(1, hosts.size());
+ EXPECT_EQ(2, hosts[0]->getIPv6SubnetID());
+ EXPECT_TRUE(hosts[0]->hasReservation(
+ IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::6"))));
+
+ // Check it was the last page.
+ hid = hosts[0]->getHostId();
+ ASSERT_NE(0, hid);
+ hosts = HostMgr::instance().getPage6(idx, hid, page_size);
+ ASSERT_EQ(0, hosts.size());
+ idx = 2;
+ hosts = HostMgr::instance().getPage6(idx, 0, page_size);
+ ASSERT_EQ(0, hosts.size());
+ }
+}
+
+void
+HostMgrTest::testGetAll4(BaseHostDataSource& data_source1,
+ BaseHostDataSource& data_source2) {
+ // Initially, no hosts should be present.
+ ConstHostCollection hosts =
+ HostMgr::instance().getAll4(IOAddress("192.0.2.5"));
+ ASSERT_TRUE(hosts.empty());
+
+ // Add two hosts to different data sources.
+ addHost4(data_source1, hwaddrs_[0], SubnetID(1), IOAddress("192.0.2.5"));
+ addHost4(data_source2, hwaddrs_[1], SubnetID(10), IOAddress("192.0.2.5"));
+
+ CfgMgr::instance().commit();
+
+ // Retrieve all hosts, This should return hosts from both sources
+ // in a single container.
+ hosts = HostMgr::instance().getAll4(IOAddress("192.0.2.5"));
+ ASSERT_EQ(2, hosts.size());
+
+ // Make sure that IPv4 address is correct.
+ EXPECT_EQ("192.0.2.5", hosts[0]->getIPv4Reservation().toText());
+ EXPECT_EQ("192.0.2.5", hosts[1]->getIPv4Reservation().toText());
+
+ // Make sure that two different hosts were returned.
+ EXPECT_NE(hosts[0]->getIPv4SubnetID(), hosts[1]->getIPv4SubnetID());
+}
+
+void
+HostMgrTest::testGet4(BaseHostDataSource& data_source) {
+ // Initially, no host should be present.
+ ConstHostPtr host =
+ HostMgr::instance().get4(SubnetID(1), Host::IDENT_HWADDR,
+ &hwaddrs_[0]->hwaddr_[0],
+ hwaddrs_[0]->hwaddr_.size());
+ ASSERT_FALSE(host);
+
+ // Add new host to the database.
+ addHost4(data_source, hwaddrs_[0], SubnetID(1), IOAddress("192.0.2.5"));
+
+ CfgMgr::instance().commit();
+
+ // Retrieve the host from the database and expect that the parameters match.
+ host = HostMgr::instance().get4(SubnetID(1), Host::IDENT_HWADDR,
+ &hwaddrs_[0]->hwaddr_[0],
+ hwaddrs_[0]->hwaddr_.size());
+ ASSERT_TRUE(host);
+ EXPECT_EQ(1, host->getIPv4SubnetID());
+ EXPECT_EQ("192.0.2.5", host->getIPv4Reservation().toText());
+}
+
+void
+HostMgrTest::testGet4Any() {
+ // Initially, no host should be present.
+ ConstHostPtr host = HostMgr::instance().get4(SubnetID(1), Host::IDENT_DUID,
+ &duids_[0]->getDuid()[0],
+ duids_[0]->getDuid().size());
+ ASSERT_FALSE(host);
+ HostMgr::instance().get4Any(SubnetID(1), Host::IDENT_DUID,
+ &duids_[0]->getDuid()[0],
+ duids_[0]->getDuid().size());
+ ASSERT_FALSE(host);
+
+ // Add new host to the database.
+ HostPtr new_host(new Host(duids_[0]->toText(), "duid", SubnetID(1),
+ SUBNET_ID_UNUSED, IOAddress("192.0.2.5")));
+ // Abuse of the server's configuration.
+ getCfgHosts()->add(new_host);
+
+ CfgMgr::instance().commit();
+
+ // Retrieve the host from the database and expect that the parameters match.
+ host = HostMgr::instance().get4(SubnetID(1), Host::IDENT_DUID,
+ &duids_[0]->getDuid()[0],
+ duids_[0]->getDuid().size());
+ ASSERT_TRUE(host);
+ EXPECT_EQ(1, host->getIPv4SubnetID());
+ EXPECT_EQ("192.0.2.5", host->getIPv4Reservation().toText());
+
+ // Set the negative cache flag on the host.
+ new_host->setNegative(true);
+
+ // get4 is not supposed to get it.
+ host = HostMgr::instance().get4(SubnetID(1), Host::IDENT_DUID,
+ &duids_[0]->getDuid()[0],
+ duids_[0]->getDuid().size());
+ EXPECT_FALSE(host);
+
+ // But get4Any should.
+ host = HostMgr::instance().get4Any(SubnetID(1), Host::IDENT_DUID,
+ &duids_[0]->getDuid()[0],
+ duids_[0]->getDuid().size());
+ ASSERT_TRUE(host);
+ EXPECT_EQ(1, host->getIPv4SubnetID());
+ EXPECT_EQ("192.0.2.5", host->getIPv4Reservation().toText());
+ EXPECT_TRUE(host->getNegative());
+
+ // To be sure. Note we use the CfgHosts source so only this
+ // get4 overload works.
+ host = HostMgr::instance().get4(SubnetID(1), Host::IDENT_DUID,
+ &duids_[0]->getDuid()[0],
+ duids_[0]->getDuid().size());
+ EXPECT_FALSE(host);
+}
+
+void
+HostMgrTest::testGet6(BaseHostDataSource& data_source) {
+ // Initially, no host should be present.
+ ConstHostPtr host =
+ HostMgr::instance().get6(SubnetID(2), Host::IDENT_DUID,
+ &duids_[0]->getDuid()[0],
+ duids_[0]->getDuid().size());
+ ASSERT_FALSE(host);
+
+ // Add new host to the database.
+ addHost6(data_source, duids_[0], SubnetID(2), IOAddress("2001:db8:1::1"));
+
+ CfgMgr::instance().commit();
+
+ // Retrieve the host from the database and expect that the parameters match.
+ host = HostMgr::instance().get6(SubnetID(2), Host::IDENT_DUID,
+ &duids_[0]->getDuid()[0],
+ duids_[0]->getDuid().size());
+ ASSERT_TRUE(host);
+ EXPECT_EQ(2, host->getIPv6SubnetID());
+ EXPECT_TRUE(host->hasReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+ IOAddress("2001:db8:1::1"))));
+}
+
+void
+HostMgrTest::testGet6Any() {
+ // Initially, no host should be present.
+ ConstHostPtr host = HostMgr::instance().get6(SubnetID(2),
+ Host::IDENT_HWADDR,
+ &hwaddrs_[0]->hwaddr_[0],
+ hwaddrs_[0]->hwaddr_.size());
+ ASSERT_FALSE(host);
+ host = HostMgr::instance().get6Any(SubnetID(2), Host::IDENT_HWADDR,
+ &hwaddrs_[0]->hwaddr_[0],
+ hwaddrs_[0]->hwaddr_.size());
+ ASSERT_FALSE(host);
+
+ // Add new host to the database.
+ HostPtr new_host(new Host(hwaddrs_[0]->toText(false), "hw-address",
+ SubnetID(1), SubnetID(2),
+ IOAddress::IPV4_ZERO_ADDRESS()));
+ new_host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+ IOAddress("2001:db8:1::1"), 128));
+ // Abuse of the server's configuration.
+ getCfgHosts()->add(new_host);
+
+ CfgMgr::instance().commit();
+
+ // Retrieve the host from the database and expect that the parameters match.
+ host = HostMgr::instance().get6(SubnetID(2), Host::IDENT_HWADDR,
+ &hwaddrs_[0]->hwaddr_[0],
+ hwaddrs_[0]->hwaddr_.size());
+ ASSERT_TRUE(host);
+ EXPECT_EQ(2, host->getIPv6SubnetID());
+ EXPECT_TRUE(host->hasReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+ IOAddress("2001:db8:1::1"))));
+
+ // Set the negative cache flag on the host.
+ new_host->setNegative(true);
+
+ // get6 is not supposed to get it.
+ host = HostMgr::instance().get6(SubnetID(2), Host::IDENT_HWADDR,
+ &hwaddrs_[0]->hwaddr_[0],
+ hwaddrs_[0]->hwaddr_.size());
+ EXPECT_FALSE(host);
+
+ // But get6Any should.
+ host = HostMgr::instance().get6Any(SubnetID(2), Host::IDENT_HWADDR,
+ &hwaddrs_[0]->hwaddr_[0],
+ hwaddrs_[0]->hwaddr_.size());
+ ASSERT_TRUE(host);
+ EXPECT_EQ(2, host->getIPv6SubnetID());
+ EXPECT_TRUE(host->hasReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+ IOAddress("2001:db8:1::1"))));
+ EXPECT_TRUE(host->getNegative());
+
+ // To be sure. Note we use the CfgHosts source so only this
+ // get6 overload works.
+ host = HostMgr::instance().get6(SubnetID(2), Host::IDENT_HWADDR,
+ &hwaddrs_[0]->hwaddr_[0],
+ hwaddrs_[0]->hwaddr_.size());
+ EXPECT_FALSE(host);
+}
+
+void
+HostMgrTest::testGet6ByPrefix(BaseHostDataSource& data_source1,
+ BaseHostDataSource& data_source2) {
+ ConstHostPtr host = HostMgr::instance().get6(IOAddress("2001:db8:1::"), 64);
+ ASSERT_FALSE(host);
+
+ // Add a host with a reservation for a prefix 2001:db8:1::/64.
+ addHost6(data_source1, duids_[0], SubnetID(2), IOAddress("2001:db8:1::"), 64);
+
+ // Add another host having a reservation for prefix 2001:db8:1:0:6::/72.
+ addHost6(data_source2, duids_[1], SubnetID(3), IOAddress("2001:db8:1:0:6::"), 72);
+
+ CfgMgr::instance().commit();
+
+ // Retrieve first reservation.
+ host = HostMgr::instance().get6(IOAddress("2001:db8:1::"), 64);
+ ASSERT_TRUE(host);
+ EXPECT_TRUE(host->hasReservation(IPv6Resrv(IPv6Resrv::TYPE_PD,
+ IOAddress("2001:db8:1::"), 64)));
+
+ // Make sure the first reservation is not retrieved when the prefix
+ // length is incorrect.
+ host = HostMgr::instance().get6(IOAddress("2001:db8:1::"), 72);
+ EXPECT_FALSE(host);
+
+ // Retrieve second reservation.
+ host = HostMgr::instance().get6(IOAddress("2001:db8:1:0:6::"), 72);
+ ASSERT_TRUE(host);
+ EXPECT_TRUE(host->hasReservation(IPv6Resrv(IPv6Resrv::TYPE_PD,
+ IOAddress("2001:db8:1:0:6::"), 72)));
+
+ // Make sure the second reservation is not retrieved when the prefix
+ // length is incorrect.
+ host = HostMgr::instance().get6(IOAddress("2001:db8:1:0:6::"), 64);
+ EXPECT_FALSE(host);
+}
+
+void
+HostMgrTest::testGetAll4BySubnetIP(BaseHostDataSource& data_source1,
+ BaseHostDataSource& data_source2) {
+ // Set the mode of operation with multiple reservations for the same
+ // IP address.
+ ASSERT_TRUE(HostMgr::instance().setIPReservationsUnique(false));
+ CfgMgr::instance().getStagingCfg()->getCfgHosts()->setIPReservationsUnique(false);
+
+ // Initially, no reservations should be present.
+ ConstHostCollection hosts = HostMgr::instance().getAll4(SubnetID(1),
+ IOAddress("192.0.2.5"));
+ ASSERT_TRUE(hosts.empty());
+
+ // Add two reservations for the same subnet and IP address.
+ addHost4(data_source1, hwaddrs_[0], SubnetID(1), IOAddress("192.0.2.5"));
+ addHost4(data_source2, hwaddrs_[1], SubnetID(1), IOAddress("192.0.2.5"));
+
+ CfgMgr::instance().commit();
+
+ // If there non-matching subnet is specified, nothing should be returned.
+ hosts = HostMgr::instance().getAll4(SubnetID(100), IOAddress("192.0.2.5"));
+ ASSERT_TRUE(hosts.empty());
+
+ // For the correct subnet, there should be two reservations.
+ hosts = HostMgr::instance().getAll4(SubnetID(1), IOAddress("192.0.2.5"));
+ ASSERT_EQ(2, hosts.size());
+
+ // Make sure that subnet is correct.
+ EXPECT_EQ(1, hosts[0]->getIPv4SubnetID());
+ EXPECT_EQ(1, hosts[1]->getIPv4SubnetID());
+
+ // Make sure that two hosts were returned with different identifiers
+ // but the same address.
+ EXPECT_NE(hosts[0]->getIdentifierAsText(), hosts[1]->getIdentifierAsText());
+ EXPECT_EQ("192.0.2.5", hosts[0]->getIPv4Reservation().toText());
+ EXPECT_EQ("192.0.2.5", hosts[1]->getIPv4Reservation().toText());
+}
+
+void
+HostMgrTest::testGetAll6BySubnetIP(BaseHostDataSource& data_source1,
+ BaseHostDataSource& data_source2) {
+ // Set the mode of operation with multiple reservations for the same
+ // IP address.
+ ASSERT_TRUE(HostMgr::instance().setIPReservationsUnique(false));
+ CfgMgr::instance().getStagingCfg()->getCfgHosts()->setIPReservationsUnique(false);
+
+ // Initially, no reservations should be present.
+ ConstHostCollection hosts = HostMgr::instance().getAll6(SubnetID(1),
+ IOAddress("2001:db8:1::5"));
+ ASSERT_TRUE(hosts.empty());
+
+ // Add two reservations for the same subnet.
+ addHost6(data_source1, duids_[0], SubnetID(1), IOAddress("2001:db8:1::5"));
+ addHost6(data_source2, duids_[1], SubnetID(1), IOAddress("2001:db8:1::5"));
+
+ CfgMgr::instance().commit();
+
+ // If there non-matching subnet is specified, nothing should be returned.
+ hosts = HostMgr::instance().getAll6(SubnetID(100), IOAddress("2001:db8:1::5"));
+ ASSERT_TRUE(hosts.empty());
+
+ // For the correct subnet, there should be two reservations.
+ hosts = HostMgr::instance().getAll6(SubnetID(1), IOAddress("2001:db8:1::5"));
+ ASSERT_EQ(2, hosts.size());
+
+ // Make sure that subnet is correct.
+ EXPECT_EQ(1, hosts[0]->getIPv6SubnetID());
+ EXPECT_EQ(1, hosts[1]->getIPv6SubnetID());
+
+ // Make sure that two hosts were returned with different identifiers
+ // but the same address.
+ EXPECT_NE(hosts[0]->getIdentifierAsText(), hosts[1]->getIdentifierAsText());
+ EXPECT_TRUE(hosts[0]->hasReservation(
+ IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::5"))));
+ EXPECT_TRUE(hosts[1]->hasReservation(
+ IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::5"))));
+}
+
+} // namespace test
+} // namespace dhcp
+} // namespace isc
diff --git a/src/lib/dhcpsrv/testutils/generic_host_data_source_unittest.h b/src/lib/dhcpsrv/testutils/generic_host_data_source_unittest.h
new file mode 100644
index 0000000..0eb1ddf
--- /dev/null
+++ b/src/lib/dhcpsrv/testutils/generic_host_data_source_unittest.h
@@ -0,0 +1,937 @@
+// Copyright (C) 2015-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/.
+
+#ifndef GENERIC_HOST_DATA_SOURCE_UNITTEST_H
+#define GENERIC_HOST_DATA_SOURCE_UNITTEST_H
+
+#include <asiolink/io_address.h>
+#include <util/reconnect_ctl.h>
+#include <dhcpsrv/base_host_data_source.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/host.h>
+#include <dhcpsrv/host_mgr.h>
+#include <dhcpsrv/testutils/generic_backend_unittest.h>
+#include <dhcp/classify.h>
+#include <dhcp/option.h>
+#include <boost/algorithm/string/join.hpp>
+#include <boost/shared_ptr.hpp>
+#include <gtest/gtest.h>
+#include <sstream>
+#include <vector>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+/// @brief Test Fixture class with utility functions for HostDataSource backends
+///
+/// It contains utility functions for test purposes.
+/// All concrete HostDataSource test classes should be derived from it.
+class GenericHostDataSourceTest : public GenericBackendTest {
+public:
+
+ /// @brief Universe (V4 or V6).
+ enum Universe {
+ V4,
+ V6
+ };
+
+ /// @brief Options to be inserted into a host.
+ ///
+ /// Parameter of this type is passed to the @ref addTestOptions to
+ /// control which option types should be inserted into a host.
+ enum AddedOptions {
+ DHCP4_ONLY,
+ DHCP6_ONLY,
+ DHCP4_AND_DHCP6
+ };
+
+ /// @brief Default constructor.
+ GenericHostDataSourceTest();
+
+ /// @brief Virtual destructor.
+ virtual ~GenericHostDataSourceTest();
+
+ /// @brief Used to sort a host collection by IPv4 subnet id.
+ /// @param host1 first host to be compared
+ /// @param host2 second host to be compared
+ /// @result return true if host1's subnet id is smaller than host2's
+ /// subnet id
+ static bool compareHostsForSort4(const ConstHostPtr& host1,
+ const ConstHostPtr& host2);
+
+ /// @brief Used to sort a host collection by IPv6 subnet id.
+ /// @param host1 first host to be compared
+ /// @param host2 second host to be compared
+ /// @result return true if host1's subnet id is smaller than host2's
+ /// subnet id
+ static bool compareHostsForSort6(const ConstHostPtr& host1,
+ const ConstHostPtr& host2);
+
+ /// @brief Used to sort a host collection by host identifier.
+ /// @param host1 first host to be compared
+ /// @param host2 second host to be compared
+ /// @result return true if host1's identifier is smaller than host2's
+ /// identifier
+ static bool compareHostsIdentifier(const ConstHostPtr& host1,
+ const ConstHostPtr& host2);
+
+ /// @brief Returns number of entries in the v4 options table.
+ ///
+ /// This utility method is expected to be implemented by specific backends.
+ /// The code here is just a boilerplate for backends that do not store
+ /// host options in a table.
+ ///
+ /// @param number of existing entries in options table
+ virtual int countDBOptions4() {
+ return (-1);
+ }
+
+ /// @brief Returns number of entries in the v6 options table.
+ ///
+ /// This utility method is expected to be implemented by specific backends.
+ /// The code here is just a boilerplate for backends that do not store
+ /// host options in a table.
+ ///
+ /// @param number of existing entries in options table
+ virtual int countDBOptions6() {
+ return (-1);
+ }
+
+ /// @brief Returns number of entries in the v6 reservations table.
+ ///
+ /// This utility method is expected to be implemented by specific backends.
+ /// The code here is just a boilerplate for backends that do not store
+ /// v6 reservations in a table.
+ ///
+ /// @param number of existing entries in v6_reservations table
+ virtual int countDBReservations6() {
+ return (-1);
+ }
+
+ /// @brief Adds multiple options into the host.
+ ///
+ /// This method creates the following options into the host object:
+ /// - DHCPv4 boot file name option,
+ /// - DHCPv4 default ip ttl option,
+ /// - DHCPv4 option 1 within vendor-encapsulated-options space,
+ /// - DHCPv4 option 254 with a single IPv4 address,
+ /// - DHCPv4 option 1 within isc option space,
+ /// - DHCPv6 boot file url option,
+ /// - DHCPv6 information refresh time option,
+ /// - DHCPv6 vendor option with vendor id 2495,
+ /// - DHCPv6 option 1024, with a single IPv6 address,
+ /// - DHCPv6 empty option 1, within isc2 option space,
+ /// - DHCPv6 option 2, within isc2 option space with 3 IPv6 addresses,
+ ///
+ /// This method also creates option definitions for the non-standard
+ /// options and registers them in the LibDHCP as runtime option
+ /// definitions.
+ ///
+ /// @param host Host object into which options should be added.
+ /// @param formatted A boolean value selecting if the formatted option
+ /// value should be used (if true), or binary value (if false).
+ /// @param added_options Controls which options should be inserted into
+ /// a host: DHCPv4, DHCPv6 options or both.
+ /// @param user_context Optional user context
+ void addTestOptions(const HostPtr& host, const bool formatted,
+ const AddedOptions& added_options,
+ isc::data::ConstElementPtr user_context =
+ isc::data::ConstElementPtr()) const;
+
+ /// @brief Pointer to the host data source
+ HostDataSourcePtr hdsptr_;
+
+ /// @brief Test that backend can be started in read-only mode.
+ ///
+ /// Some backends can operate when the database is read only, e.g.
+ /// host reservation tables are read only, or the database user has
+ /// read only privileges on the entire database. In such cases, the
+ /// Kea server administrator can specify in the backend configuration
+ /// that the database should be opened in read only mode, i.e.
+ /// INSERT, UPDATE, DELETE statements can't be issued. If any of the
+ /// functions updating the database is called for the backend, the
+ /// error is reported. The database running in read only mode can
+ /// be merely used to retrieve existing host reservations from the
+ /// database. This test verifies that this is the case.
+ ///
+ /// @param valid_db_type Parameter specifying type of backend to
+ /// be used, e.g. type=mysql.
+ void testReadOnlyDatabase(const char* valid_db_type);
+
+ /// @brief Test that checks that simple host with IPv4 reservation
+ /// can be inserted and later retrieved.
+ ///
+ /// Uses gtest macros to report failures.
+ /// @param id Identifier type.
+ void testBasic4(const Host::IdentifierType& id);
+
+ /// @brief Test that verifies that an IPv4 host reservation with
+ /// options can have the global subnet id value.
+ ///
+ /// Uses gtest macros to report failures.
+ void testGlobalSubnetId4();
+
+ /// @brief Test that verifies that an IPv6 host reservation with
+ /// options can have the global subnet id value.
+ ///
+ /// Uses gtest macros to report failures.
+ void testGlobalSubnetId6();
+
+ /// @brief Test that verifies that an IPv4 host reservation with
+ /// options can have a max value for dhcp4_subnet id
+ ///
+ /// Uses gtest macros to report failures.
+ void testMaxSubnetId4();
+
+ /// @brief Test that Verifies that an IPv6 host reservation with
+ /// options can have a max value for dhcp6_subnet id
+ ///
+ /// Uses gtest macros to report failures.
+ void testMaxSubnetId6();
+
+ /// @brief Test that Verifies that IPv4 host reservations in the
+ /// same subnet can be retrieved properly.
+ ///
+ /// Uses gtest macros to report failures.
+ void testGetAll4();
+
+ /// @brief Test that Verifies that IPv6 host reservations in the
+ /// same subnet can be retrieved properly.
+ ///
+ /// Uses gtest macros to report failures.
+ void testGetAll6();
+
+ /// @brief Test that Verifies that host reservations with the same
+ /// hostname can be retrieved properly.
+ ///
+ /// Uses gtest macros to report failures.
+ void testGetAllbyHostname();
+
+ /// @brief Test that Verifies that IPv4 host reservations with the same
+ /// hostname and in the same subnet can be retrieved properly.
+ ///
+ /// Uses gtest macros to report failures.
+ void testGetAllbyHostnameSubnet4();
+
+ /// @brief Test that Verifies that IPv6 host reservations with the same
+ /// hostname and in the same subnet can be retrieved properly.
+ ///
+ /// Uses gtest macros to report failures.
+ void testGetAllbyHostnameSubnet6();
+
+ /// @brief Test that Verifies that pages of host reservations in the
+ /// same subnet can be retrieved properly.
+ ///
+ /// Uses gtest macros to report failures.
+ void testGetPage4();
+
+ /// @brief Test that Verifies that pages of host reservations in the
+ /// same subnet can be retrieved properly.
+ ///
+ /// Uses gtest macros to report failures.
+ void testGetPage6();
+
+ /// @brief Test that Verifies that pages of complex host reservations
+ /// are not truncated, i.e. the limit applies on the number of hosts
+ /// and not on the number of rows.
+ ///
+ /// Uses gtest macros to report failures.
+ /// @param id Identifier type (hwaddr or duid).
+ void testGetPageLimit4(const Host::IdentifierType& id);
+
+ /// @brief Test that Verifies that pages of complex host reservations
+ /// are not truncated, i.e. the limit applies on the number of hosts
+ /// and not on the number of rows.
+ ///
+ /// Uses gtest macros to report failures.
+ /// @param id Identifier type (hwaddr or duid).
+ void testGetPageLimit6(const Host::IdentifierType& id);
+
+ /// @brief Test that Verifies that pages of host reservations in the
+ /// same subnet can be retrieved properly even with multiple subnets.
+ ///
+ /// Uses gtest macros to report failures.
+ void testGetPage4Subnets();
+
+ /// @brief Test that Verifies that pages of host reservations in the
+ /// same subnet can be retrieved properly even with multiple subnets.
+ ///
+ /// Uses gtest macros to report failures.
+ void testGetPage6Subnets();
+
+ /// @brief Test that Verifies that pages of all host reservations
+ /// can be retrieved properly.
+ ///
+ /// Uses gtest macros to report failures.
+ void testGetPage4All();
+
+ /// @brief Test that Verifies that pages of all host reservations
+ /// can be retrieved properly.
+ ///
+ /// Uses gtest macros to report failures.
+ void testGetPage6All();
+
+ /// @brief Test inserts several hosts with unique IPv4 address and
+ /// checks that they can be retrieved properly.
+ ///
+ /// Uses gtest macros to report failures.
+ /// @param id Identifier type.
+ void testGetByIPv4(const Host::IdentifierType& id);
+
+ /// @brief Test that hosts can be retrieved by host identifier.
+ ///
+ /// Uses gtest macros to report failures.
+ void testGet4ByIdentifier(const Host::IdentifierType& identifier_type);
+
+ /// @brief Test that clients with stored HW address can't be retrieved
+ /// by DUID with the same value.
+ ///
+ /// Test procedure: add host reservation with hardware address X, try to retrieve
+ /// host by client-identifier X, verify that the reservation is not returned.
+ ///
+ /// Uses gtest macros to report failures.
+ void testHWAddrNotClientId();
+
+ /// @brief Test that clients with stored DUID can't be retrieved
+ /// by HW address of the same value.
+ ///
+ /// Test procedure: add host reservation with client identifier X, try to
+ /// retrieve host by hardware address X, verify that the reservation is not
+ /// returned.
+ ///
+ /// Uses gtest macros to report failures.
+ void testClientIdNotHWAddr();
+
+ /// @brief Test adds specified number of hosts with unique hostnames, then
+ /// retrieves them and checks that the hostnames are set properly.
+ ///
+ /// Uses gtest macros to report failures.
+ ///
+ /// @param name hostname to be used (if n>1, numbers will be appended)
+ /// @param num number of hostnames to be added.
+ void testHostname(std::string name, int num);
+
+ /// @brief Test insert and retrieve a host with user context.
+ ///
+ /// Uses gtest macros to report failures.
+ ///
+ /// @param user_context The user context.
+ void testUserContext(isc::data::ConstElementPtr user_context);
+
+ /// @brief Test inserts multiple reservations for the same host for different
+ /// subnets and check that they can be retrieved properly.
+ ///
+ /// Uses gtest macros to report failures.
+ ///
+ /// @param subnets number of subnets to test
+ /// @param id Host identifier type.
+ void testMultipleSubnets(int subnets, const Host::IdentifierType& id);
+
+ /// @brief Test inserts several hosts with unique IPv6 addresses and
+ /// checks that they can be retrieved properly.
+ ///
+ /// Uses gtest macros to report failures.
+ /// @param id type of the identifier to be used (IDENT_HWADDR or IDENT_DUID)
+ /// @param prefix true - reserve IPv6 prefix, false - reserve IPv6 address
+ void testGetByIPv6(Host::IdentifierType id, bool prefix);
+
+ /// @brief Test inserts several hosts with unique prefixes and checks
+ /// that the can be retrieved by subnet id and prefix value.
+ void testGetBySubnetIPv6();
+
+ /// @brief Test that hosts can be retrieved by hardware address.
+ ///
+ /// Uses gtest macros to report failures.
+ void testGet6ByHWAddr();
+
+ /// @brief Test that hosts can be retrieved by client-id
+ ///
+ /// Uses gtest macros to report failures.
+ void testGet6ByClientId();
+
+ /// @brief Test verifies if a host reservation can be stored with both
+ /// IPv6 address and prefix.
+ /// Uses gtest macros to report failures.
+ void testAddr6AndPrefix();
+
+ /// @brief Tests if host with multiple IPv6 reservations can be added and then
+ /// retrieved correctly.
+ void testMultipleReservations();
+
+ /// @brief Tests if compareIPv6Reservations() method treats same pool of
+ /// reservations but added in different order as equal.
+ void testMultipleReservationsDifferentOrder();
+
+ /// @brief Test if host reservations made for different IPv6 subnets
+ /// are handled correctly.
+ ///
+ /// Uses gtest macros to report failures.
+ ///
+ /// @param subnets number of subnets to test
+ /// @param id identifier type (IDENT_HWADDR or IDENT_DUID)
+ void testSubnetId6(int subnets, Host::IdentifierType id);
+
+ /// @brief Test if the duplicate host with same DUID can't be inserted.
+ ///
+ /// Uses gtest macros to report failures.
+ void testAddDuplicate6WithSameDUID();
+
+ /// @brief Test if the duplicate host with same HWAddr can't be inserted.
+ ///
+ /// Uses gtest macros to report failures.
+ void testAddDuplicate6WithSameHWAddr();
+
+ /// @brief Test that duplicate IPv6 reservation can't be inserted.
+ ///
+ /// Uses gtest macros to report failures.
+ void testAddDuplicateIPv6();
+
+ /// @brief Test if the reservation for the same IPv6 address can be
+ /// inserted when allowed by the configuration.
+ ///
+ /// Uses gtest macros to report failures.
+ void testAllowDuplicateIPv6();
+
+ /// @brief Test that duplicate IPv4 reservation can't be inserted.
+ ///
+ /// Uses gtest macros to report failures.
+ void testAddDuplicateIPv4();
+
+ /// @brief Test if the reservation for the same IPv4 address can be
+ /// inserted when allowed by the configuration.
+ ///
+ /// Uses gtest macros to report failures.
+ void testAllowDuplicateIPv4();
+
+ /// @brief Test that the backend does not support a mode in which multiple
+ /// host reservations for the same IP address can be created.
+ void testDisallowDuplicateIP();
+
+ /// @brief Test that DHCPv4 options can be inserted and retrieved from
+ /// the database.
+ ///
+ /// Uses gtest macros to report failures.
+ ///
+ /// @param formatted Boolean value indicating if the option values
+ /// should be stored in the textual format in the database.
+ /// @param user_context Optional user context.
+ void testOptionsReservations4(const bool formatted,
+ isc::data::ConstElementPtr user_context =
+ isc::data::ConstElementPtr());
+
+ /// @brief Test that DHCPv6 options can be inserted and retrieved from
+ /// the database.
+ ///
+ /// Uses gtest macros to report failures.
+ ///
+ /// @param formatted Boolean value indicating if the option values
+ /// should be stored in the textual format in the database.
+ /// @param user_context Optional user context.
+ void testOptionsReservations6(const bool formatted,
+ isc::data::ConstElementPtr user_context =
+ isc::data::ConstElementPtr());
+
+ /// @brief Test that DHCPv4 and DHCPv6 options can be inserted and retrieved
+ /// with a single query to the database.
+ ///
+ /// Uses gtest macros to report failures.
+ ///
+ /// @param formatted Boolean value indicating if the option values
+ /// should be stored in the textual format in the database.
+ void testOptionsReservations46(const bool formatted);
+
+ /// @brief Test that multiple client classes for IPv4 can be inserted and
+ /// retrieved for a given host reservation.
+ ///
+ /// Uses gtest macros to report failures.
+ ///
+ void testMultipleClientClasses4();
+
+ /// @brief Test that multiple client classes for IPv6 can be inserted and
+ /// retrieved for a given host reservation.
+ ///
+ /// Uses gtest macros to report failures.
+ ///
+ void testMultipleClientClasses6();
+
+ /// @brief Test that multiple client classes for both IPv4 and IPv6 can
+ /// be inserted and retrieved for a given host reservation.
+ ///
+ /// Uses gtest macros to report failures.
+ ///
+ void testMultipleClientClassesBoth();
+
+ /// @brief Test that siaddr, sname, file fields can be retrieved
+ /// from a database for a host.
+ ///
+ /// Uses gtest macros to report failures.
+ void testMessageFields4();
+
+ /// @brief Stress test on adding and retrieving hosts
+ ///
+ /// Rather than checking for correctness, this test gives interpretable
+ /// performance results.
+ ///
+ /// @param n_of_hosts number of hosts to insert into and retrieve from the
+ /// database
+ void stressTest(uint32_t n_of_hosts);
+ /// @brief Tests that delete(subnet-id, addr4) call works.
+ ///
+ /// Uses gtest macros to report failures.
+ void testDeleteByAddr4();
+
+ /// @brief Tests that delete(subnet4-id, identifier-type, identifier) works.
+ ///
+ /// Uses gtest macros to report failures.
+ void testDeleteById4();
+
+ /// @brief Tests that delete(subnet4-id, id-type, id) also deletes options.
+ void testDeleteById4Options();
+
+ /// @brief Tests that delete(subnet6-id, identifier-type, identifier) works.
+ ///
+ /// Uses gtest macros to report failures.
+ void testDeleteById6();
+
+ /// @brief Tests that delete(subnet6-id, id-type, id) also deletes options.
+ ///
+ /// Uses gtest macros to report failures.
+ void testDeleteById6Options();
+
+ /// @brief Tests that multiple reservations without IPv4 addresses can be
+ /// specified within a subnet.
+ ///
+ /// Uses gtest macros to report failures.
+ void testMultipleHostsNoAddress4();
+
+ /// @brief Tests that multiple hosts can be specified within an IPv6 subnet.
+ ///
+ /// Uses gtest macros to report failures.
+ void testMultipleHosts6();
+
+ /// @brief Returns DUID with identical content as specified HW address
+ ///
+ /// This method does not have any sense in real life and is only useful
+ /// in testing corner cases in the database backends (e.g. whether the DB
+ /// is able to tell the difference between hwaddr and duid)
+ ///
+ /// @param hwaddr hardware address to be copied
+ /// @return duid with the same value as specified HW address
+ DuidPtr HWAddrToDuid(const HWAddrPtr& hwaddr);
+
+ /// @brief Returns HW address with identical content as specified DUID
+ ///
+ /// This method does not have any sense in real life and is only useful
+ /// in testing corner cases in the database backends (e.g. whether the DB
+ /// is able to tell the difference between hwaddr and duid)
+ ///
+ /// @param duid DUID to be copied
+ /// @return HW address with the same value as specified DUID
+ HWAddrPtr DuidToHWAddr(const DuidPtr& duid);
+
+};
+
+class HostMgrDbLostCallbackTest : public ::testing::Test {
+public:
+ HostMgrDbLostCallbackTest()
+ : db_lost_callback_called_(0), db_recovered_callback_called_(0),
+ db_failed_callback_called_(0),
+ io_service_(boost::make_shared<isc::asiolink::IOService>()) {
+ isc::db::DatabaseConnection::db_lost_callback_ = 0;
+ isc::db::DatabaseConnection::db_recovered_callback_ = 0;
+ isc::db::DatabaseConnection::db_failed_callback_ = 0;
+ isc::dhcp::HostMgr::setIOService(io_service_);
+ isc::dhcp::TimerMgr::instance()->setIOService(io_service_);
+ isc::dhcp::CfgMgr::instance().clear();
+ }
+
+ virtual ~HostMgrDbLostCallbackTest() {
+ isc::db::DatabaseConnection::db_lost_callback_ = 0;
+ isc::db::DatabaseConnection::db_recovered_callback_ = 0;
+ isc::db::DatabaseConnection::db_failed_callback_ = 0;
+ isc::dhcp::HostMgr::setIOService(isc::asiolink::IOServicePtr());
+ isc::dhcp::TimerMgr::instance()->unregisterTimers();
+ isc::dhcp::CfgMgr::instance().clear();
+ }
+
+ /// @brief Prepares the class for a test.
+ ///
+ /// Invoked by gtest prior test entry, we create the
+ /// appropriate schema and create a basic host manager to
+ /// wipe out any prior instance
+ virtual void SetUp() {
+ // Ensure we have the proper schema with no transient data.
+ createSchema();
+ // Wipe out any pre-existing mgr
+ isc::dhcp::HostMgr::create();
+ isc::dhcp::CfgMgr::instance().clear();
+ }
+
+ /// @brief Pre-text exit clean up
+ ///
+ /// Invoked by gtest upon test exit, we destroy the schema
+ /// we created.
+ virtual void TearDown() {
+ // If data wipe enabled, delete transient data otherwise destroy the schema
+ destroySchema();
+ isc::dhcp::CfgMgr::instance().clear();
+ }
+
+ /// @brief Abstract method for destroying the back end specific schema
+ virtual void destroySchema() = 0;
+
+ /// @brief Abstract method for creating the back end specific schema
+ virtual void createSchema() = 0;
+
+ /// @brief Abstract method which returns the back end specific connection
+ /// string
+ virtual std::string validConnectString() = 0;
+
+ /// @brief Abstract method which returns invalid back end specific connection
+ /// string
+ virtual std::string invalidConnectString() = 0;
+
+ /// @brief Verifies open failures do NOT invoke db lost callback
+ ///
+ /// The db lost callback should only be invoked after successfully
+ /// opening the DB and then subsequently losing it. Failing to
+ /// open should be handled directly by the application layer.
+ void testNoCallbackOnOpenFailure();
+
+ /// @brief Verifies the host manager's behavior if DB connection is lost
+ ///
+ /// This function creates a host manager with a back end that supports
+ /// connectivity lost callback (currently only MySQL and PostgreSQL). It
+ /// verifies connectivity by issuing a known valid query. Next it simulates
+ /// connectivity lost by identifying and closing the socket connection to
+ /// the CB backend. It then reissues the query and verifies that:
+ /// -# The Query throws DbOperationError (rather than exiting)
+ /// -# The registered DbLostCallback was invoked
+ /// -# The registered DbRecoveredCallback was invoked
+ void testDbLostAndRecoveredCallback();
+
+ /// @brief Verifies the host manager's behavior if DB connection is lost
+ ///
+ /// This function creates a host manager with a back end that supports
+ /// connectivity lost callback (currently only MySQL and PostgreSQL). It
+ /// verifies connectivity by issuing a known valid query. Next it simulates
+ /// connectivity lost by identifying and closing the socket connection to
+ /// the CB backend. It then reissues the query and verifies that:
+ /// -# The Query throws DbOperationError (rather than exiting)
+ /// -# The registered DbLostCallback was invoked
+ /// -# The registered DbFailedCallback was invoked
+ void testDbLostAndFailedCallback();
+
+ /// @brief Verifies the host manager's behavior if DB connection is lost
+ ///
+ /// This function creates a host manager with a back end that supports
+ /// connectivity lost callback (currently only MySQL and PostgreSQL). It
+ /// verifies connectivity by issuing a known valid query. Next it simulates
+ /// connectivity lost by identifying and closing the socket connection to
+ /// the CB backend. It then reissues the query and verifies that:
+ /// -# The Query throws DbOperationError (rather than exiting)
+ /// -# The registered DbLostCallback was invoked
+ /// -# The registered DbRecoveredCallback was invoked after two reconnect
+ /// attempts (once failing and second triggered by timer)
+ void testDbLostAndRecoveredAfterTimeoutCallback();
+
+ /// @brief Verifies the host manager's behavior if DB connection is lost
+ ///
+ /// This function creates a host manager with a back end that supports
+ /// connectivity lost callback (currently only MySQL and PostgreSQL). It
+ /// verifies connectivity by issuing a known valid query. Next it simulates
+ /// connectivity lost by identifying and closing the socket connection to
+ /// the CB backend. It then reissues the query and verifies that:
+ /// -# The Query throws DbOperationError (rather than exiting)
+ /// -# The registered DbLostCallback was invoked
+ /// -# The registered DbFailedCallback was invoked after two reconnect
+ /// attempts (once failing and second triggered by timer)
+ void testDbLostAndFailedAfterTimeoutCallback();
+
+ /// @brief Callback function registered with the host manager
+ bool db_lost_callback(util::ReconnectCtlPtr /* not_used */) {
+ return (++db_lost_callback_called_);
+ }
+
+ /// @brief Flag used to detect calls to db_lost_callback function
+ uint32_t db_lost_callback_called_;
+
+ /// @brief Callback function registered with the host manager
+ bool db_recovered_callback(util::ReconnectCtlPtr /* not_used */) {
+ return (++db_recovered_callback_called_);
+ }
+
+ /// @brief Flag used to detect calls to db_recovered_callback function
+ uint32_t db_recovered_callback_called_;
+
+ /// @brief Callback function registered with the host manager
+ bool db_failed_callback(util::ReconnectCtlPtr /* not_used */) {
+ return (++db_failed_callback_called_);
+ }
+
+ /// @brief Flag used to detect calls to db_failed_callback function
+ uint32_t db_failed_callback_called_;
+
+ /// The IOService object, used for all ASIO operations.
+ isc::asiolink::IOServicePtr io_service_;
+};
+
+/// @brief Test fixture class for @c HostMgr class.
+class HostMgrTest : public ::testing::Test {
+public:
+
+ /// @brief Constructor
+ HostMgrTest() = default;
+
+ /// @brief Destructor
+ virtual ~HostMgrTest() = default;
+
+protected:
+
+ /// @brief Prepares the class for a test.
+ ///
+ /// This method crates a handful of unique HW address and DUID objects
+ /// for use in unit tests. These objects are held in the @c hwaddrs_ and
+ /// @c duids_ members respectively.
+ ///
+ /// This method also resets the @c CfgMgr configuration and re-creates
+ /// the @c HostMgr object.
+ virtual void SetUp();
+
+ /// @brief Convenience method returning a pointer to the @c CfgHosts object
+ /// in the @c CfgMgr.
+ CfgHostsPtr getCfgHosts() const;
+
+ /// @brief Inserts IPv4 reservation into the host data source.
+ ///
+ /// @param data_source Reference to the data source to which the reservation
+ /// should be inserted.
+ /// @param hwaddr Pointer to the hardware address to be associated with the
+ /// reservation.
+ /// @param subnet_id IPv4 subnet id.
+ /// @param address IPv4 address to be reserved.
+ void addHost4(BaseHostDataSource& data_source,
+ const HWAddrPtr& hwaddr,
+ const SubnetID& subnet_id,
+ const isc::asiolink::IOAddress& address);
+
+ /// @brief Inserts IPv6 reservation into the host data source.
+ ///
+ /// @param data_source Reference to the data source to which the reservation
+ /// should be inserted.
+ /// @param duid Pointer to the DUID to be associated with the reservation.
+ /// @param subnet_id IPv6 subnet id.
+ /// @param address IPv6 address/prefix to be reserved.
+ /// @param prefix_len Prefix length. The default value is 128 which
+ /// indicates that the reservation is for an IPv6 address rather than a
+ /// prefix.
+ void addHost6(BaseHostDataSource& data_source,
+ const DuidPtr& duid,
+ const SubnetID& subnet_id,
+ const isc::asiolink::IOAddress& address,
+ const uint8_t prefix_len = 128);
+
+ /// @brief This test verifies that HostMgr returns all reservations for the
+ /// specified HW address.
+ ///
+ /// If reservations are added to different host data sources, it is expected
+ /// that the @c HostMgr will retrieve reservations from both of them.
+ ///
+ /// @param data_source1 Host data source to which first reservation is
+ /// inserted.
+ /// @param data_source2 Host data source to which second reservation is
+ /// inserted.
+ void testGetAll(BaseHostDataSource& data_source1,
+ BaseHostDataSource& data_source2);
+
+ /// @brief This test verifies that HostMgr returns all reservations for the
+ /// specified DHCPv4 subnet.
+ ///
+ /// If reservations are added to different host data sources, it is expected
+ /// that the @c HostMgr will retrieve reservations from both of them.
+ ///
+ /// @param data_source1 Host data source to which first reservation is
+ /// inserted.
+ /// @param data_source2 Host data source to which second reservation is
+ /// inserted.
+ void testGetAll4BySubnet(BaseHostDataSource& data_source1,
+ BaseHostDataSource& data_source2);
+
+ /// @brief This test verifies that HostMgr returns all reservations for the
+ /// specified DHCPv6 subnet.
+ ///
+ /// If reservations are added to different host data sources, it is expected
+ /// that the @c HostMgr will retrieve reservations from both of them.
+ ///
+ /// @param data_source1 Host data source to which first reservation is
+ /// inserted.
+ /// @param data_source2 Host data source to which second reservation is
+ /// inserted.
+ void testGetAll6BySubnet(BaseHostDataSource& data_source1,
+ BaseHostDataSource& data_source2);
+
+ /// @brief This test verifies that HostMgr returns all reservations for the
+ /// specified hostname.
+ ///
+ /// If reservations are added to different host data sources, it is expected
+ /// that the @c HostMgr will retrieve reservations from both of them.
+ ///
+ /// @param data_source1 Host data source to which first reservation is
+ /// inserted.
+ /// @param data_source2 Host data source to which second reservation is
+ /// inserted.
+ void testGetAllbyHostname(BaseHostDataSource& data_source1,
+ BaseHostDataSource& data_source2);
+
+ /// @brief This test verifies that HostMgr returns all reservations for the
+ /// specified hostname and DHCPv4 subnet.
+ ///
+ /// If reservations are added to different host data sources, it is expected
+ /// that the @c HostMgr will retrieve reservations from both of them.
+ ///
+ /// @param data_source1 Host data source to which first reservation is
+ /// inserted.
+ /// @param data_source2 Host data source to which second reservation is
+ /// inserted.
+ void testGetAllbyHostnameSubnet4(BaseHostDataSource& data_source1,
+ BaseHostDataSource& data_source2);
+
+ /// @brief This test verifies that HostMgr returns all reservations for the
+ /// specified hostname and DHCPv6 subnet.
+ ///
+ /// If reservations are added to different host data sources, it is expected
+ /// that the @c HostMgr will retrieve reservations from both of them.
+ ///
+ /// @param data_source1 Host data source to which first reservation is
+ /// inserted.
+ /// @param data_source2 Host data source to which second reservation is
+ /// inserted.
+ void testGetAllbyHostnameSubnet6(BaseHostDataSource& data_source1,
+ BaseHostDataSource& data_source2);
+
+ /// @brief This test verifies that HostMgr returns all reservations for the
+ /// specified DHCPv4 subnet by pages.
+ ///
+ /// If reservations are added to different host data sources, it is expected
+ /// that the @c HostMgr will retrieve reservations from both of them.
+ ///
+ /// @param use_database True when the second reservation is inserted
+ /// in a database.
+ void testGetPage4(bool use_database);
+
+ /// @brief This test verifies that HostMgr returns all reservations for the
+ /// specified DHCPv6 subnet by pages.
+ ///
+ /// If reservations are added to different host data sources, it is expected
+ /// that the @c HostMgr will retrieve reservations from both of them.
+ ///
+ /// @param use_database True when the second reservation is inserted
+ /// in a database.
+ void testGetPage6(bool use_database);
+
+ /// @brief This test verifies that HostMgr returns all reservations
+ /// by pages.
+ ///
+ /// If reservations are added to different host data sources, it is expected
+ /// that the @c HostMgr will retrieve reservations from both of them.
+ ///
+ /// @param use_database True when the second reservation is inserted
+ /// in a database.
+ void testGetPage4All(bool use_database);
+
+ /// @brief This test verifies that HostMgr returns all reservations
+ /// by pages.
+ ///
+ /// If reservations are added to different host data sources, it is expected
+ /// that the @c HostMgr will retrieve reservations from both of them.
+ ///
+ /// @param use_database True when the second reservation is inserted
+ /// in a database.
+ void testGetPage6All(bool use_database);
+
+ /// @brief This test verifies that it is possible to retrieve IPv4
+ /// reservation for the particular host using HostMgr.
+ ///
+ /// If reservations are added to different host data sources, it is expected
+ /// that the @c HostMgr will retrieve reservations from both of them.
+ ///
+ /// @param data_source1 Host data source to which first reservation is
+ /// inserted.
+ /// @param data_source2 Host data source to which second reservation is
+ /// inserted.
+ void testGetAll4(BaseHostDataSource& data_source1,
+ BaseHostDataSource& data_source2);
+
+ /// @brief This test verifies that it is possible to retrieve an IPv4
+ /// reservation for the particular host using HostMgr.
+ ///
+ /// @param data_source Host data source to which reservation is inserted and
+ /// from which it will be retrieved.
+ void testGet4(BaseHostDataSource& data_source);
+
+ /// @brief This test verifies that it is possible to retrieve negative
+ /// cached reservation with and only with get4Any.
+ void testGet4Any();
+
+ /// @brief This test verifies that it is possible to retrieve an IPv6
+ /// reservation for the particular host using HostMgr.
+ ///
+ /// @param data_source Host data source to which reservation is inserted and
+ /// from which it will be retrieved.
+ void testGet6(BaseHostDataSource& data_source);
+
+ /// @brief This test verifies that it is possible to retrieve negative
+ /// cached reservation with and only with get6Any.
+ void testGet6Any();
+
+ /// @brief This test verifies that it is possible to retrieve an IPv6
+ /// prefix reservation for the particular host using HostMgr.
+ ///
+ /// @param data_source1 Host data source to which first reservation is
+ /// inserted.
+ /// @param data_source2 Host data source to which second reservation is
+ /// inserted.
+ void testGet6ByPrefix(BaseHostDataSource& data_source1,
+ BaseHostDataSource& data_source2);
+
+ /// @brief This test verifies that HostMgr returns all reservations for the
+ /// specified DHCPv4 subnet and IPv4 address.
+ ///
+ /// If reservations are added to different host data sources, it is expected
+ /// that the @c HostMgr will retrieve reservations from both of them.
+ ///
+ /// @param data_source1 Host data source to which first reservation is
+ /// inserted.
+ /// @param data_source2 Host data source to which second reservation is
+ /// inserted.
+ void testGetAll4BySubnetIP(BaseHostDataSource& data_source1,
+ BaseHostDataSource& data_source2);
+
+ /// @brief This test verifies that HostMgr returns all reservations for the
+ /// specified DHCPv6 subnet and IPv6 address.
+ ///
+ /// If reservations are added to different host data sources, it is expected
+ /// that the @c HostMgr will retrieve reservations from both of them.
+ ///
+ /// @param data_source1 Host data source to which first reservation is
+ /// inserted.
+ /// @param data_source2 Host data source to which second reservation is
+ /// inserted.
+ void testGetAll6BySubnetIP(BaseHostDataSource& data_source1,
+ BaseHostDataSource& data_source2);
+
+ /// @brief HW addresses to be used by the tests.
+ std::vector<HWAddrPtr> hwaddrs_;
+ /// @brief DUIDs to be used by the tests.
+ std::vector<DuidPtr> duids_;
+};
+
+} // namespace test
+} // namespace dhcp
+} // namespace isc
+
+#endif
diff --git a/src/lib/dhcpsrv/testutils/host_data_source_utils.cc b/src/lib/dhcpsrv/testutils/host_data_source_utils.cc
new file mode 100644
index 0000000..00cd1df
--- /dev/null
+++ b/src/lib/dhcpsrv/testutils/host_data_source_utils.cc
@@ -0,0 +1,409 @@
+// Copyright (C) 2018-2020 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <dhcpsrv/testutils/host_data_source_utils.h>
+#include <asiolink/io_address.h>
+#include <boost/foreach.hpp>
+#include <cc/data.h>
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc::data;
+using namespace isc::asiolink;
+using namespace isc::util;
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+std::vector<uint8_t>
+HostDataSourceUtils::generateHWAddr(const bool new_identifier) {
+ // Let's use something that is easily printable. That's convenient
+ // if you need to enter MySQL queries by hand.
+ static uint8_t hwaddr[] = {65, 66, 67, 68, 69, 70};
+
+ if (new_identifier) {
+ // Increase the address for the next time we use it.
+ // This is primitive, but will work for 65k unique
+ // addresses.
+ hwaddr[sizeof(hwaddr) - 1]++;
+ if (hwaddr[sizeof(hwaddr) - 1] == 0) {
+ hwaddr[sizeof(hwaddr) - 2]++;
+ }
+ }
+ return (std::vector<uint8_t>(hwaddr, hwaddr + sizeof(hwaddr)));
+}
+
+std::vector<uint8_t>
+HostDataSourceUtils::generateIdentifier(const bool new_identifier) {
+ // Let's use something that is easily printable. That's convenient
+ // if you need to enter MySQL queries by hand.
+ static uint8_t ident[] = {65, 66, 67, 68, 69, 70, 71, 72, 73, 74};
+
+ if (new_identifier) {
+ // Increase the identifier for the next time we use it.
+ // This is primitive, but will work for 65k unique identifiers.
+ ident[sizeof(ident) - 1]++;
+ if (ident[sizeof(ident) - 1] == 0) {
+ ident[sizeof(ident) - 2]++;
+ }
+ }
+ return (std::vector<uint8_t>(ident, ident + sizeof(ident)));
+}
+
+HostPtr
+HostDataSourceUtils::initializeHost4(const std::string& address,
+ const Host::IdentifierType& id,
+ const bool new_identifier) {
+ std::vector<uint8_t> ident;
+ if (id == Host::IDENT_HWADDR) {
+ ident = generateHWAddr(new_identifier);
+ } else {
+ ident = generateIdentifier(new_identifier);
+ }
+
+ // Let's create ever increasing subnet-ids. Let's keep those different,
+ // so subnet4 != subnet6. Useful for catching cases if the code confuses
+ // subnet4 with subnet6.
+ static SubnetID subnet4 = 0;
+ static SubnetID subnet6 = 100;
+ ++subnet4;
+ ++subnet6;
+
+ IOAddress addr(address);
+ HostPtr host(new Host(&ident[0], ident.size(), id, subnet4, subnet6, addr));
+
+ return (host);
+}
+
+HostPtr
+HostDataSourceUtils::initializeHost6(std::string address,
+ Host::IdentifierType identifier,
+ bool prefix,
+ bool new_identifier,
+ const std::string auth_key) {
+ std::vector<uint8_t> ident;
+ switch (identifier) {
+ case Host::IDENT_HWADDR:
+ ident = generateHWAddr(new_identifier);
+ break;
+ case Host::IDENT_DUID:
+ ident = generateIdentifier(new_identifier);
+ break;
+ default:
+ ADD_FAILURE() << "Unknown IdType: " << identifier;
+ return HostPtr();
+ }
+
+ // Let's create ever increasing subnet-ids. Let's keep those different,
+ // so subnet4 != subnet6. Useful for catching cases if the code confuses
+ // subnet4 with subnet6.
+ static SubnetID subnet4 = 0;
+ static SubnetID subnet6 = 100;
+ ++subnet4;
+ ++subnet6;
+
+ HostPtr host(new Host(&ident[0], ident.size(), identifier, subnet4, subnet6,
+ IOAddress("0.0.0.0")));
+
+ host->setKey(AuthKey(auth_key));
+
+ if (!prefix) {
+ // Create IPv6 reservation (for an address)
+ IPv6Resrv resv(IPv6Resrv::TYPE_NA, IOAddress(address), 128);
+ host->addReservation(resv);
+ } else {
+ // Create IPv6 reservation for a /64 prefix
+ IPv6Resrv resv(IPv6Resrv::TYPE_PD, IOAddress(address), 64);
+ host->addReservation(resv);
+ }
+ return (host);
+}
+
+bool
+HostDataSourceUtils::reservationExists(const IPv6Resrv& resrv,
+ const IPv6ResrvRange& range) {
+ for (IPv6ResrvIterator it = range.first; it != range.second; ++it) {
+ if (resrv == it->second) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void
+HostDataSourceUtils::compareHwaddrs(const ConstHostPtr& host1,
+ const ConstHostPtr& host2,
+ bool expect_match) {
+ ASSERT_TRUE(host1);
+ ASSERT_TRUE(host2);
+
+ // Compare if both have or have not HWaddress set.
+ if ((host1->getHWAddress() && !host2->getHWAddress()) ||
+ (!host1->getHWAddress() && host2->getHWAddress())) {
+ // One host has hardware address set while the other has not.
+ // Let's see if it's a problem.
+ if (expect_match) {
+ ADD_FAILURE() << "Host comparison failed: host1 hwaddress="
+ << host1->getHWAddress()
+ << ", host2 hwaddress=" << host2->getHWAddress();
+ }
+ return;
+ }
+
+ // Now we know that either both or neither have hw address set.
+ // If host1 has it, we can proceed to value comparison.
+ if (host1->getHWAddress()) {
+ if (expect_match) {
+ // Compare the actual address if they match.
+ EXPECT_TRUE(*host1->getHWAddress() == *host2->getHWAddress());
+ } else {
+ EXPECT_FALSE(*host1->getHWAddress() == *host2->getHWAddress());
+ }
+ if (*host1->getHWAddress() != *host2->getHWAddress()) {
+ cout << host1->getHWAddress()->toText(true) << endl;
+ cout << host2->getHWAddress()->toText(true) << endl;
+ }
+ }
+}
+
+void
+HostDataSourceUtils::compareDuids(const ConstHostPtr& host1,
+ const ConstHostPtr& host2,
+ bool expect_match) {
+ ASSERT_TRUE(host1);
+ ASSERT_TRUE(host2);
+
+ // compare if both have or have not DUID set
+ if ((host1->getDuid() && !host2->getDuid()) ||
+ (!host1->getDuid() && host2->getDuid())) {
+ // One host has a DUID and the other doesn't.
+ // Let's see if it's a problem.
+ if (expect_match) {
+ ADD_FAILURE() << "DUID comparison failed: host1 duid="
+ << host1->getDuid()
+ << ", host2 duid=" << host2->getDuid();
+ }
+ return;
+ }
+
+ // Now we know that either both or neither have DUID set.
+ // If host1 has it, we can proceed to value comparison.
+ if (host1->getDuid()) {
+ if (expect_match) {
+ EXPECT_TRUE(*host1->getDuid() == *host2->getDuid());
+ } else {
+ EXPECT_FALSE(*host1->getDuid() == *host2->getDuid());
+ }
+ if (*host1->getDuid() != *host2->getDuid()) {
+ cout << host1->getDuid()->toText() << endl;
+ cout << host2->getDuid()->toText() << endl;
+ }
+ }
+}
+
+void
+HostDataSourceUtils::compareHosts(const ConstHostPtr& host1,
+ const ConstHostPtr& host2) {
+ ASSERT_TRUE(host1);
+ ASSERT_TRUE(host2);
+ // Let's compare HW addresses and expect match.
+ compareHwaddrs(host1, host2, true);
+
+ // Now compare DUIDs
+ compareDuids(host1, host2, true);
+
+ // Now check that the identifiers returned as vectors are the same
+ EXPECT_EQ(host1->getIdentifierType(), host2->getIdentifierType());
+ EXPECT_TRUE(host1->getIdentifier() == host2->getIdentifier());
+
+ // Check host parameters
+ EXPECT_EQ(host1->getIPv4SubnetID(), host2->getIPv4SubnetID());
+ EXPECT_EQ(host1->getIPv6SubnetID(), host2->getIPv6SubnetID());
+ EXPECT_EQ(host1->getIPv4Reservation(), host2->getIPv4Reservation());
+ EXPECT_EQ(host1->getHostname(), host2->getHostname());
+ EXPECT_EQ(host1->getNextServer(), host2->getNextServer());
+ EXPECT_EQ(host1->getServerHostname(), host2->getServerHostname());
+ EXPECT_EQ(host1->getBootFileName(), host2->getBootFileName());
+ EXPECT_TRUE(host1->getKey() == host2->getKey());
+ ConstElementPtr ctx1 = host1->getContext();
+ ConstElementPtr ctx2 = host2->getContext();
+ if (ctx1) {
+ EXPECT_TRUE(ctx2);
+ if (ctx2) {
+ EXPECT_EQ(*ctx1, *ctx2);
+ }
+ } else {
+ EXPECT_FALSE(ctx2);
+ }
+
+ // Compare IPv6 reservations
+ compareReservations6(host1->getIPv6Reservations(),
+ host2->getIPv6Reservations());
+
+ // Compare client classification details
+ compareClientClasses(host1->getClientClasses4(),
+ host2->getClientClasses4());
+
+ compareClientClasses(host1->getClientClasses6(),
+ host2->getClientClasses6());
+
+ // Compare DHCPv4 and DHCPv6 options.
+ compareOptions(host1->getCfgOption4(), host2->getCfgOption4());
+ compareOptions(host1->getCfgOption6(), host2->getCfgOption6());
+}
+
+void
+HostDataSourceUtils::compareReservations6(IPv6ResrvRange resrv1,
+ IPv6ResrvRange resrv2) {
+ // Compare number of reservations for both hosts
+ if (std::distance(resrv1.first, resrv1.second) !=
+ std::distance(resrv2.first, resrv2.second)) {
+ ADD_FAILURE() << "Reservation comparison failed, "
+ "hosts got different number of reservations.";
+ return;
+ }
+
+ // Iterate over the range of reservations to find a match in the
+ // reference range.
+ for (IPv6ResrvIterator r1 = resrv1.first; r1 != resrv1.second; ++r1) {
+ IPv6ResrvIterator r2 = resrv2.first;
+ for (; r2 != resrv2.second; ++r2) {
+ // IPv6Resrv object implements equality operator.
+ if (r1->second == r2->second) {
+ break;
+ }
+ }
+ // If r2 iterator reached the end of the range it means that there
+ // is no match.
+ if (r2 == resrv2.second) {
+ ADD_FAILURE() << "No match found for reservation: "
+ << resrv1.first->second.getPrefix().toText();
+ }
+ }
+
+ if (std::distance(resrv1.first, resrv1.second) > 0) {
+ for (; resrv1.first != resrv1.second; resrv1.first++) {
+ IPv6ResrvIterator iter = resrv2.first;
+ while (iter != resrv2.second) {
+ if ((resrv1.first->second.getType() ==
+ iter->second.getType()) &&
+ (resrv1.first->second.getPrefixLen() ==
+ iter->second.getPrefixLen()) &&
+ (resrv1.first->second.getPrefix() ==
+ iter->second.getPrefix())) {
+ break;
+ }
+ iter++;
+ if (iter == resrv2.second) {
+ ADD_FAILURE() << "Reservation comparison failed, "
+ "no match for reservation: "
+ << resrv1.first->second.getPrefix().toText();
+ }
+ }
+ }
+ }
+}
+
+void
+HostDataSourceUtils::compareClientClasses(const ClientClasses& classes1,
+ const ClientClasses& classes2) {
+ EXPECT_TRUE(std::equal(classes1.cbegin(), classes1.cend(), classes2.cbegin()));
+}
+
+void
+HostDataSourceUtils::compareOptions(const ConstCfgOptionPtr& cfg1,
+ const ConstCfgOptionPtr& cfg2) {
+ ASSERT_TRUE(cfg1);
+ ASSERT_TRUE(cfg2);
+
+ // Combine option space names with vendor space names in a single list.
+ std::list<std::string> option_spaces = cfg2->getOptionSpaceNames();
+ std::list<std::string> vendor_spaces = cfg2->getVendorIdsSpaceNames();
+ option_spaces.insert(option_spaces.end(), vendor_spaces.begin(),
+ vendor_spaces.end());
+
+ // Make sure that the number of option spaces is equal in both
+ // configurations.
+ EXPECT_EQ(option_spaces.size(), cfg1->getOptionSpaceNames().size());
+ EXPECT_EQ(vendor_spaces.size(), cfg1->getVendorIdsSpaceNames().size());
+
+ // Iterate over all option spaces existing in cfg2.
+ BOOST_FOREACH (std::string space, option_spaces) {
+ // Retrieve options belonging to the current option space.
+ OptionContainerPtr options1 = cfg1->getAll(space);
+ OptionContainerPtr options2 = cfg2->getAll(space);
+ ASSERT_TRUE(options1) << "failed for option space " << space;
+ ASSERT_TRUE(options2) << "failed for option space " << space;
+
+ // If number of options doesn't match, the test fails.
+ ASSERT_EQ(options1->size(), options2->size())
+ << "failed for option space " << space;
+
+ // Iterate over all options within this option space.
+ BOOST_FOREACH (OptionDescriptor desc1, *options1) {
+ OptionDescriptor desc2 = cfg2->get(space, desc1.option_->getType());
+ // Compare persistent flag.
+ EXPECT_EQ(desc1.persistent_, desc2.persistent_)
+ << "failed for option " << space << "."
+ << desc1.option_->getType();
+ // Compare formatted value.
+ EXPECT_EQ(desc1.formatted_value_, desc2.formatted_value_)
+ << "failed for option " << space << "."
+ << desc1.option_->getType();
+
+ // Compare user context.
+ ConstElementPtr ctx1 = desc1.getContext();
+ ConstElementPtr ctx2 = desc2.getContext();
+ if (ctx1) {
+ EXPECT_TRUE(ctx2);
+ if (ctx2) {
+ EXPECT_EQ(*ctx1, *ctx2)
+ << "failed for option " << space << "." << desc1.option_->getType();
+ }
+ } else {
+ EXPECT_FALSE(ctx2);
+ }
+
+ // Retrieve options.
+ Option* option1 = desc1.option_.get();
+ Option* option2 = desc2.option_.get();
+
+ // Options must be represented by the same C++ class derived from
+ // the Option class.
+ EXPECT_TRUE(typeid(*option1) == typeid(*option2))
+ << "Compared DHCP options, having option code "
+ << desc1.option_->getType() << " and belonging to the " << space
+ << " option space, are represented "
+ "by different C++ classes: "
+ << typeid(*option1).name() << " vs " << typeid(*option2).name();
+
+ // Because we use different C++ classes to represent different
+ // options, the simplest way to make sure that the options are
+ // equal is to simply compare them in wire format.
+ OutputBuffer buf1(option1->len());
+ ASSERT_NO_THROW(option1->pack(buf1));
+ OutputBuffer buf2(option2->len());
+ ASSERT_NO_THROW(option2->pack(buf2));
+
+ ASSERT_EQ(buf1.getLength(), buf2.getLength())
+ << "failed for option " << space << "."
+ << desc1.option_->getType();
+ EXPECT_EQ(0,
+ memcmp(buf1.getData(), buf2.getData(), buf1.getLength()))
+ << "failed for option " << space << "."
+ << desc1.option_->getType();
+ }
+ }
+}
+
+
+}
+}
+}
+
diff --git a/src/lib/dhcpsrv/testutils/host_data_source_utils.h b/src/lib/dhcpsrv/testutils/host_data_source_utils.h
new file mode 100644
index 0000000..ac96f11
--- /dev/null
+++ b/src/lib/dhcpsrv/testutils/host_data_source_utils.h
@@ -0,0 +1,134 @@
+// Copyright (C) 2018-2020 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef HOST_DATA_SOURCE_UTILS_H
+#define HOST_DATA_SOURCE_UTILS_H
+
+#include <config.h>
+#include <dhcpsrv/host.h>
+#include <vector>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+/// @brief A helper class for manipulating hosts
+///
+/// Intended to be used in tests and benchmarks.
+class HostDataSourceUtils {
+public:
+ /// @brief Creates a host reservation for specified IPv4 address.
+ ///
+ /// @param address IPv4 address to be set
+ /// @param id Identifier type.
+ /// @param new_identifier Boolean value indicating if new host
+ /// identifier should be generated or the same as previously.
+ ///
+ /// @return generated Host object
+ static isc::dhcp::HostPtr initializeHost4(const std::string& address,
+ const Host::IdentifierType& id,
+ const bool new_identifier = true);
+
+ /// @brief Creates a host reservation for specified IPv6 address.
+ ///
+ /// @param address IPv6 address to be reserved
+ /// @param id type of identifier (IDENT_DUID or IDENT_HWADDR are supported)
+ /// @param prefix reservation type (true = prefix, false = address)
+ /// @param new_identifier Boolean value indicating if new host
+ /// identifier should be generated or the same as previously.
+ ///
+ /// @return generated Host object
+ static HostPtr initializeHost6(std::string address, Host::IdentifierType id,
+ bool prefix, bool new_identifier = true,
+ const std::string key = "");
+
+ /// @brief Generates a hardware address in text version.
+ ///
+ /// @param increase A boolean value indicating if new address (increased)
+ /// must be generated or the same address as previously.
+ /// @return HW address in textual form acceptable by Host constructor
+ static std::vector<uint8_t> generateHWAddr(const bool new_identifier = true);
+
+ /// @brief Generates a host identifier in a textual form..
+ ///
+ /// @param increase A boolean value indicating if new identifier (increased)
+ /// must be generated or the same identifier as previously.
+ /// @return Identifier in textual form acceptable by Host constructor
+ static std::vector<uint8_t> generateIdentifier(const bool new_identifier = true);
+
+ /// @brief Checks if the reservation is in the range of reservations.
+ ///
+ /// @param resrv Reservation to be searched for.
+ /// @param range Range of reservations returned by the @c Host object
+ /// in which the reservation will be searched
+ static bool reservationExists(const IPv6Resrv& resrv, const IPv6ResrvRange& range);
+
+ /// @brief Compares hardware addresses of the two hosts.
+ ///
+ /// This method compares two hardware address and uses gtest
+ /// macros to signal unexpected (mismatch if expect_match is true;
+ /// match if expect_match is false) values.
+ ///
+ /// @param host1 first host to be compared
+ /// @param host2 second host to be compared
+ /// @param expect_match true = HW addresses expected to be the same,
+ /// false = HW addresses expected to be different
+ static void compareHwaddrs(const ConstHostPtr& host1, const ConstHostPtr& host2,
+ bool expect_match);
+
+ /// @brief Compares DUIDs of the two hosts.
+ ///
+ /// This method compares two DUIDs (client-ids) and uses gtest
+ /// macros to signal unexpected (mismatch if expect_match is true;
+ /// match if expect_match is false) values.
+ ///
+ /// @param host1 first host to be compared
+ /// @param host2 second host to be compared
+ /// @param expect_match true = DUIDs expected to be the same,
+ /// false = DUIDs expected to be different
+ static void compareDuids(const ConstHostPtr& host1, const ConstHostPtr& host2,
+ bool expect_match);
+
+ /// @brief Compares two hosts
+ ///
+ /// This method uses gtest macros to signal errors.
+ ///
+ /// @param host1 first host to compare
+ /// @param host2 second host to compare
+ static void compareHosts(const isc::dhcp::ConstHostPtr& host1, const ConstHostPtr& host2);
+
+ /// @brief Compares two IPv6 reservation lists.
+ ///
+ /// This method uses gtest macros to signal errors.
+ ///
+ /// @param resv1 first IPv6 reservations list
+ /// @param resv2 second IPv6 reservations list
+ static void compareReservations6(IPv6ResrvRange resv1, IPv6ResrvRange resv2);
+
+ /// @brief Compares two client classes
+ ///
+ /// This method uses gtest macros to signal errors.
+ ///
+ /// @param classes1 first list of client classes
+ /// @param classes2 second list of client classes
+ static void compareClientClasses(const ClientClasses& classes1,
+ const ClientClasses& classes2);
+
+ /// @brief Compares options within two configurations.
+ ///
+ /// This method uses gtest macros to signal errors.
+ ///
+ /// @param cfg1 First configuration.
+ /// @param cfg2 Second configuration.
+ static void compareOptions(const ConstCfgOptionPtr& cfg1,
+ const ConstCfgOptionPtr& cfg2);
+};
+
+}
+}
+}
+
+#endif
diff --git a/src/lib/dhcpsrv/testutils/lease_file_io.cc b/src/lib/dhcpsrv/testutils/lease_file_io.cc
new file mode 100644
index 0000000..d641a69
--- /dev/null
+++ b/src/lib/dhcpsrv/testutils/lease_file_io.cc
@@ -0,0 +1,66 @@
+// Copyright (C) 2015-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 <dhcpsrv/testutils/lease_file_io.h>
+#include <fstream>
+#include <sstream>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+LeaseFileIO::LeaseFileIO(const std::string& filename, const bool recreate)
+ : testfile_(filename), recreate_(recreate) {
+ if (recreate_) {
+ removeFile();
+ }
+}
+
+LeaseFileIO::~LeaseFileIO() {
+ if (recreate_) {
+ removeFile();
+ }
+}
+
+bool
+LeaseFileIO::exists() const {
+ std::ifstream fs(testfile_.c_str());
+ bool ok = fs.good();
+ fs.close();
+ return (ok);
+}
+
+std::string
+LeaseFileIO::readFile() const {
+ std::ifstream fs(testfile_.c_str());
+ if (!fs.is_open()) {
+ return ("");
+ }
+ std::string contents((std::istreambuf_iterator<char>(fs)),
+ std::istreambuf_iterator<char>());
+ fs.close();
+ return (contents);
+}
+
+void
+LeaseFileIO::removeFile() const {
+ static_cast<void>(remove(testfile_.c_str()));
+}
+
+void
+LeaseFileIO::writeFile(const std::string& contents) const {
+ std::ofstream fs(testfile_.c_str(), std::ofstream::out);
+ if (fs.is_open()) {
+ fs << contents;
+ fs.close();
+ }
+}
+
+} // end of namespace isc::dhcp::test
+} // end of namespace isc::dhcp
+} // end of namespace isc
+
diff --git a/src/lib/dhcpsrv/testutils/lease_file_io.h b/src/lib/dhcpsrv/testutils/lease_file_io.h
new file mode 100644
index 0000000..25efd96
--- /dev/null
+++ b/src/lib/dhcpsrv/testutils/lease_file_io.h
@@ -0,0 +1,63 @@
+// Copyright (C) 2014-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 LEASE_FILE_IO_H
+#define LEASE_FILE_IO_H
+
+#include <string>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+/// @brief This class contains functions to perform IO operations on files.
+///
+/// This class is solely used by unit tests. Some tests often need files
+/// as an input. This class allows for easy creation of text files that can
+/// be later used by unit tests. It also provides method to read the contents
+/// of the existing file and remove existing file (cleanup after unit test).
+class LeaseFileIO {
+public:
+ /// @brief Constructor
+ ///
+ /// @param filename Absolute path to the file.
+ /// @param recreate A boolean flag indicating if the new file should
+ /// be created, even if one exists.
+ LeaseFileIO(const std::string& filename, const bool recreate = true);
+
+ /// @brief Destructor.
+ ~LeaseFileIO();
+
+ /// @brief Check if test file exists on disk.
+ bool exists() const;
+
+ /// @brief Reads whole lease file.
+ ///
+ /// @return Contents of the file.
+ std::string readFile() const;
+
+ /// @brief Removes existing file (if any).
+ void removeFile() const;
+
+ /// @brief Creates file with contents.
+ ///
+ /// @param contents Contents of the file.
+ void writeFile(const std::string& contents) const;
+
+ /// @brief Absolute path to the file used in the tests.
+ std::string testfile_;
+
+ /// @brief Indicates if the file should be recreated during object
+ /// construction and removed during destruction.
+ bool recreate_;
+
+};
+
+}
+}
+}
+
+#endif // LEASE_FILE_IO_H
diff --git a/src/lib/dhcpsrv/testutils/memory_host_data_source.cc b/src/lib/dhcpsrv/testutils/memory_host_data_source.cc
new file mode 100644
index 0000000..3ea2b83
--- /dev/null
+++ b/src/lib/dhcpsrv/testutils/memory_host_data_source.cc
@@ -0,0 +1,390 @@
+// 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 <dhcpsrv/testutils/memory_host_data_source.h>
+
+using namespace isc::db;
+using namespace std;
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+ConstHostCollection
+MemHostDataSource::getAll(const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin,
+ const size_t identifier_len) const {
+ vector<uint8_t> ident(identifier_begin, identifier_begin + identifier_len);
+ ConstHostCollection hosts;
+ for (auto h = store_.begin(); h != store_.end(); ++h) {
+ // If identifier type do not match, it's not for us
+ if ((*h)->getIdentifierType() != identifier_type) {
+ continue;
+ }
+ // If the identifier matches, we found one!
+ if ((*h)->getIdentifier() == ident) {
+ hosts.push_back(*h);
+ }
+ }
+ return (hosts);
+}
+
+ConstHostCollection
+MemHostDataSource::getAll4(const SubnetID& subnet_id) const {
+ ConstHostCollection hosts;
+ for (auto h = store_.begin(); h != store_.end(); ++h) {
+ // Keep it when subnet_id matches.
+ if ((*h)->getIPv4SubnetID() == subnet_id) {
+ hosts.push_back(*h);
+ }
+ }
+ return (hosts);
+}
+
+ConstHostCollection
+MemHostDataSource::getAll6(const SubnetID& subnet_id) const {
+ ConstHostCollection hosts;
+ for (auto h = store_.begin(); h != store_.end(); ++h) {
+ // Keep it when subnet_id matches.
+ if ((*h)->getIPv6SubnetID() == subnet_id) {
+ hosts.push_back(*h);
+ }
+ }
+ return (hosts);
+}
+
+ConstHostCollection
+MemHostDataSource::getAllbyHostname(const std::string& hostname) const {
+ ConstHostCollection hosts;
+ for (auto h = store_.begin(); h != store_.end(); ++h) {
+ // Keep it when hostname matches.
+ if ((*h)->getLowerHostname() == hostname) {
+ hosts.push_back(*h);
+ }
+ }
+ return (hosts);
+}
+
+ConstHostCollection
+MemHostDataSource::getAllbyHostname4(const std::string& hostname,
+ const SubnetID& subnet_id) const {
+ ConstHostCollection hosts;
+ for (auto h = store_.begin(); h != store_.end(); ++h) {
+ // Keep it when hostname and subnet_id match.
+ if (((*h)->getLowerHostname() == hostname) &&
+ ((*h)->getIPv4SubnetID() == subnet_id)) {
+ hosts.push_back(*h);
+ }
+ }
+ return (hosts);
+}
+
+ConstHostCollection
+MemHostDataSource::getAllbyHostname6(const std::string& hostname,
+ const SubnetID& subnet_id) const {
+ ConstHostCollection hosts;
+ for (auto h = store_.begin(); h != store_.end(); ++h) {
+ // Keep it when hostname and subnet_id match.
+ if (((*h)->getLowerHostname() == hostname) &&
+ ((*h)->getIPv6SubnetID() == subnet_id)) {
+ hosts.push_back(*h);
+ }
+ }
+ return (hosts);
+}
+
+ConstHostCollection
+MemHostDataSource::getPage4(const SubnetID& subnet_id,
+ size_t& /*source_index*/,
+ uint64_t lower_host_id,
+ const HostPageSize& page_size) const {
+ ConstHostCollection hosts;
+ for (auto h = store_.begin(); h != store_.end(); ++h) {
+ // Skip it when subnet_id does not match.
+ if ((*h)->getIPv4SubnetID() != subnet_id) {
+ continue;
+ }
+ if (lower_host_id && ((*h)->getHostId() <= lower_host_id)) {
+ continue;
+ }
+ hosts.push_back(*h);
+ if (hosts.size() == page_size.page_size_) {
+ break;
+ }
+ }
+ return (hosts);
+}
+
+ConstHostCollection
+MemHostDataSource::getPage6(const SubnetID& subnet_id,
+ size_t& /*source_index*/,
+ uint64_t lower_host_id,
+ const HostPageSize& page_size) const {
+ ConstHostCollection hosts;
+ for (auto h = store_.begin(); h != store_.end(); ++h) {
+ // Skip it when subnet_id does not match.
+ if ((*h)->getIPv6SubnetID() != subnet_id) {
+ continue;
+ }
+ if (lower_host_id && ((*h)->getHostId() <= lower_host_id)) {
+ continue;
+ }
+ hosts.push_back(*h);
+ if (hosts.size() == page_size.page_size_) {
+ break;
+ }
+ }
+ return (hosts);
+}
+
+ConstHostCollection
+MemHostDataSource::getPage4(size_t& /*source_index*/,
+ uint64_t lower_host_id,
+ const HostPageSize& page_size) const {
+ ConstHostCollection hosts;
+ for (auto h = store_.begin(); h != store_.end(); ++h) {
+ if (lower_host_id && ((*h)->getHostId() <= lower_host_id)) {
+ continue;
+ }
+ hosts.push_back(*h);
+ if (hosts.size() == page_size.page_size_) {
+ break;
+ }
+ }
+ return (hosts);
+}
+
+ConstHostCollection
+MemHostDataSource::getPage6(size_t& /*source_index*/,
+ uint64_t lower_host_id,
+ const HostPageSize& page_size) const {
+ ConstHostCollection hosts;
+ for (auto h = store_.begin(); h != store_.end(); ++h) {
+ if (lower_host_id && ((*h)->getHostId() <= lower_host_id)) {
+ continue;
+ }
+ hosts.push_back(*h);
+ if (hosts.size() == page_size.page_size_) {
+ break;
+ }
+ }
+ return (hosts);
+}
+
+ConstHostCollection
+MemHostDataSource::getAll4(const asiolink::IOAddress& /*address*/) const {
+ return (ConstHostCollection());
+}
+
+ConstHostPtr
+MemHostDataSource::get4(const SubnetID& subnet_id,
+ const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin,
+ const size_t identifier_len) const {
+ vector<uint8_t> ident(identifier_begin, identifier_begin + identifier_len);
+ for (auto h = store_.begin(); h != store_.end(); ++h) {
+ // If either subnet-id or identifier type do not match,
+ // it's not our host
+ if (((*h)->getIPv4SubnetID() != subnet_id) ||
+ ((*h)->getIdentifierType() != identifier_type)) {
+ continue;
+ }
+ // If the identifier matches, we found it!
+ if ((*h)->getIdentifier() == ident) {
+ return (*h);
+ }
+ }
+
+ // Nothing found
+ return (ConstHostPtr());
+}
+
+ConstHostPtr
+MemHostDataSource::get6(const SubnetID& subnet_id,
+ const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin,
+ const size_t identifier_len) const {
+ vector<uint8_t> ident(identifier_begin, identifier_begin + identifier_len);
+ for (auto h = store_.begin(); h != store_.end(); ++h) {
+ // If either subnet-id or identifier type do not match,
+ // it's not our host
+ if (((*h)->getIPv6SubnetID() != subnet_id) ||
+ ((*h)->getIdentifierType() != identifier_type)) {
+ continue;
+ }
+ // If the identifier matches, we found it!
+ if ((*h)->getIdentifier() == ident) {
+ return (*h);
+ }
+ }
+
+ return (ConstHostPtr());
+}
+
+ConstHostPtr
+MemHostDataSource::get4(const SubnetID& subnet_id,
+ const asiolink::IOAddress& address) const {
+ for (auto h = store_.begin(); h != store_.end(); ++h) {
+ if ((*h)->getIPv4SubnetID() == subnet_id &&
+ (*h)->getIPv4Reservation() == address) {
+ return (*h);
+ }
+ }
+
+ return (ConstHostPtr());
+}
+
+ConstHostCollection
+MemHostDataSource::getAll4(const SubnetID& subnet_id,
+ const asiolink::IOAddress& address) const {
+ ConstHostCollection hosts;
+ auto host = get4(subnet_id, address);
+ if (host) {
+ hosts.push_back(host);
+ }
+ return (hosts);
+}
+
+ConstHostPtr
+MemHostDataSource::get6(const asiolink::IOAddress& /*prefix*/,
+ const uint8_t /*prefix_len*/) const {
+ return (ConstHostPtr());
+}
+
+ConstHostPtr
+MemHostDataSource::get6(const SubnetID& subnet_id,
+ const asiolink::IOAddress& address) const {
+ for (auto h = store_.begin(); h != store_.end(); ++h) {
+
+ // Naive approach: check hosts one by one
+
+ // First check: subnet-id must match.
+ if ((*h)->getIPv6SubnetID() != subnet_id) {
+ // wrong subnet-id? ok, skip this one
+ continue;
+ }
+
+ // Second check: the v6 reservation must much. This is very simple
+ // as we ignore the reservation type.
+ auto resrvs = (*h)->getIPv6Reservations();
+ for (auto r = resrvs.first; r != resrvs.second; ++r) {
+ if ((*r).second.getPrefix() == address) {
+ return (*h);
+ }
+ }
+ }
+
+ return (ConstHostPtr());
+}
+
+ConstHostCollection
+MemHostDataSource::getAll6(const SubnetID& subnet_id,
+ const asiolink::IOAddress& address) const {
+ ConstHostCollection hosts;
+ auto host = get6(subnet_id, address);
+ if (host) {
+ hosts.push_back(host);
+ }
+ return (hosts);
+}
+
+void
+MemHostDataSource::add(const HostPtr& host) {
+ host->setHostId(++next_host_id_);
+ store_.push_back(host);
+}
+
+bool
+MemHostDataSource::del(const SubnetID& subnet_id,
+ const asiolink::IOAddress& addr) {
+ for (auto h = store_.begin(); h != store_.end(); ++h) {
+ if (addr.isV4()) {
+ if ((*h)->getIPv4SubnetID() == subnet_id &&
+ (*h)->getIPv4Reservation() == addr) {
+ store_.erase(h);
+ return (true);
+ }
+ } else {
+
+ if ((*h)->getIPv6SubnetID() != subnet_id) {
+ continue;
+ }
+
+ // Second check: the v6 reservation must much. This is very simple
+ // as we ignore the reservation type.
+ auto resrvs = (*h)->getIPv6Reservations();
+ for (auto r = resrvs.first; r != resrvs.second; ++r) {
+ if ((*r).second.getPrefix() == addr) {
+ store_.erase(h);
+ return (true);
+ }
+ }
+ }
+ }
+
+ return (false);
+}
+
+bool
+MemHostDataSource::del4(const SubnetID& subnet_id,
+ const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin,
+ const size_t identifier_len) {
+ vector<uint8_t> ident(identifier_begin, identifier_begin + identifier_len);
+ for (auto h = store_.begin(); h != store_.end(); ++h) {
+ // If either subnet-id or identifier type do not match,
+ // it's not our host
+ if (((*h)->getIPv4SubnetID() != subnet_id) ||
+ ((*h)->getIdentifierType() != identifier_type)) {
+ continue;
+ }
+ // If the identifier matches, we found it!
+ if ((*h)->getIdentifier() == ident) {
+ store_.erase(h);
+ return (true);
+ }
+ }
+
+ return (false);
+}
+
+bool
+MemHostDataSource::del6(const SubnetID& subnet_id,
+ const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin,
+ const size_t identifier_len) {
+ vector<uint8_t> ident(identifier_begin, identifier_begin + identifier_len);
+ for (auto h = store_.begin(); h != store_.end(); ++h) {
+ // If either subnet-id or identifier type do not match,
+ // it's not our host
+ if (((*h)->getIPv6SubnetID() != subnet_id) ||
+ ((*h)->getIdentifierType() != identifier_type)) {
+ continue;
+ }
+ // If the identifier matches, we found it!
+ if ((*h)->getIdentifier() == ident) {
+ store_.erase(h);
+ return (true);
+ }
+ }
+ return (false);
+}
+
+size_t
+MemHostDataSource::size() const {
+ return (store_.size());
+}
+
+HostDataSourcePtr
+memFactory(const DatabaseConnection::ParameterMap& /*parameters*/) {
+ return (HostDataSourcePtr(new MemHostDataSource()));
+}
+
+} // namespace isc::dhcp::test
+} // namespace isc::dhcp
+} // namespace isc
diff --git a/src/lib/dhcpsrv/testutils/memory_host_data_source.h b/src/lib/dhcpsrv/testutils/memory_host_data_source.h
new file mode 100644
index 0000000..799ab08
--- /dev/null
+++ b/src/lib/dhcpsrv/testutils/memory_host_data_source.h
@@ -0,0 +1,342 @@
+// 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/.
+
+#ifndef MEMORY_HOST_DATA_SOURCE_H
+#define MEMORY_HOST_DATA_SOURCE_H
+
+#include <dhcpsrv/host_data_source_factory.h>
+#include <boost/shared_ptr.hpp>
+#include <string>
+#include <vector>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+/// @brief Simple host data source implementation for tests.
+///
+/// It used vector<HostPtr> as a storage and iterates through all hosts when
+/// conducting operations. Most operations are skeleton methods that don't
+/// work, just several are implemented. Those are used in the tests.
+class MemHostDataSource : public virtual BaseHostDataSource {
+public:
+
+ /// @brief Constructor.
+ MemHostDataSource() : next_host_id_(0) {
+ }
+
+ /// @brief Destructor.
+ virtual ~MemHostDataSource() = default;
+
+ /// BaseHostDataSource methods.
+
+ /// @brief Return all hosts connected to any subnet for which reservations
+ /// have been made using a specified identifier.
+ ///
+ /// This may return hosts from multiple subnets.
+ ///
+ /// @param identifier_type Identifier type.
+ /// @param identifier_begin Pointer to a beginning of a buffer containing
+ /// an identifier.
+ /// @param identifier_len Identifier length.
+ virtual ConstHostCollection
+ getAll(const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin,
+ const size_t identifier_len) const;
+
+ /// @brief Return all hosts in a DHCPv4 subnet.
+ ///
+ /// @param subnet_id Subnet identifier.
+ virtual ConstHostCollection
+ getAll4(const SubnetID& subnet_id) const;
+
+ /// @brief Return all hosts in a DHCPv6 subnet.
+ ///
+ /// @param subnet_id Subnet identifier.
+ virtual ConstHostCollection
+ getAll6(const SubnetID& subnet_id) const;
+
+ /// @brief Return all hosts with a hostname.
+ ///
+ /// @param hostname The lower case hostname.
+ virtual ConstHostCollection
+ getAllbyHostname(const std::string& hostname) const;
+
+ /// @brief Return all hosts with a hostname in a DHCPv4 subnet.
+ ///
+ /// @param hostname The lower case hostname.
+ /// @param subnet_id Subnet identifier.
+ virtual ConstHostCollection
+ getAllbyHostname4(const std::string& hostname, const SubnetID& subnet_id) const;
+
+ /// @brief Return all hosts with a hostname in a DHCPv6 subnet.
+ ///
+ /// @param hostname The lower case hostname.
+ /// @param subnet_id Subnet identifier.
+ virtual ConstHostCollection
+ getAllbyHostname6(const std::string& hostname, const SubnetID& subnet_id) const;
+
+ /// @brief Return range of hosts in a DHCPv4 subnet.
+ ///
+ /// @param subnet_id Subnet identifier.
+ /// @param source_index Index of the source (unused).
+ /// @param lower_host_id Host identifier used as lower bound for the
+ /// returned range.
+ /// @param page_size maximum size of the page returned.
+ virtual ConstHostCollection
+ getPage4(const SubnetID& subnet_id,
+ size_t& source_index,
+ uint64_t lower_host_id,
+ const HostPageSize& page_size) const;
+
+ /// @brief Return range of hosts in a DHCPv6 subnet.
+ ///
+ /// @param subnet_id Subnet identifier.
+ /// @param source_index Index of the source (unused).
+ /// @param lower_host_id Host identifier used as lower bound for the
+ /// returned range.
+ /// @param page_size maximum size of the page returned.
+ virtual ConstHostCollection
+ getPage6(const SubnetID& subnet_id,
+ size_t& source_index,
+ uint64_t lower_host_id,
+ const HostPageSize& page_size) const;
+
+ /// @brief Return range of hosts.
+ ///
+ /// @param source_index Index of the source (unused).
+ /// @param lower_host_id Host identifier used as lower bound for the
+ /// returned range.
+ /// @param page_size maximum size of the page returned.
+ virtual ConstHostCollection
+ getPage4(size_t& source_index,
+ uint64_t lower_host_id,
+ const HostPageSize& page_size) const;
+
+ /// @brief Return range of hosts.
+ ///
+ /// @param source_index Index of the source (unused).
+ /// @param lower_host_id Host identifier used as lower bound for the
+ /// returned range.
+ /// @param page_size maximum size of the page returned.
+ virtual ConstHostCollection
+ getPage6(size_t& source_index,
+ uint64_t lower_host_id,
+ const HostPageSize& page_size) const;
+
+ /// @brief Returns a collection of hosts using the specified IPv4 address.
+ ///
+ /// Currently not implemented.
+ ///
+ /// @param address IPv4 address for which the @c Host object is searched.
+ /// @return Empty collection of const @c Host objects.
+ virtual ConstHostCollection
+ getAll4(const asiolink::IOAddress& address) const;
+
+
+ /// @brief Returns a host connected to the IPv4 subnet.
+ ///
+ /// @param subnet_id Subnet identifier.
+ /// @param identifier_type Identifier type.
+ /// @param identifier_begin Pointer to a beginning of a buffer containing
+ /// an identifier.
+ /// @param identifier_len Identifier length.
+ /// @return Const @c Host object for which reservation has been made using
+ /// the specified identifier.
+ virtual ConstHostPtr
+ get4(const SubnetID& subnet_id,
+ const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin,
+ const size_t identifier_len) const;
+
+ /// @brief Returns a host connected to the IPv4 subnet and having
+ /// a reservation for a specified IPv4 address.
+ ///
+ /// @param subnet_id Subnet identifier.
+ /// @param address reserved IPv4 address.
+ /// @return Const @c Host object using a specified IPv4 address.
+ virtual ConstHostPtr
+ get4(const SubnetID& subnet_id,
+ const asiolink::IOAddress& address) const;
+
+ /// @brief Returns all hosts connected to the IPv4 subnet and having
+ /// a reservation for a specified address.
+ ///
+ /// @param subnet_id Subnet identifier.
+ /// @param address reserved IPv4 address.
+ ///
+ /// @return Collection of const @c Host objects.
+ virtual ConstHostCollection
+ getAll4(const SubnetID& subnet_id,
+ const asiolink::IOAddress& address) const;
+
+ /// @brief Returns a host connected to the IPv6 subnet.
+ ///
+ /// @param subnet_id Subnet identifier.
+ /// @param identifier_type Identifier type.
+ /// @param identifier_begin Pointer to a beginning of a buffer containing
+ /// an identifier.
+ /// @param identifier_len Identifier length.
+ /// @return Const @c Host object for which reservation has been made using
+ /// the specified identifier.
+ virtual ConstHostPtr
+ get6(const SubnetID& subnet_id,
+ const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin,
+ const size_t identifier_len) const;
+
+ /// @brief Returns a host using the specified IPv6 prefix.
+ ///
+ /// Currently not implemented.
+ ///
+ /// @param prefix IPv6 prefix for which the @c Host object is searched.
+ /// @param prefix_len IPv6 prefix length.
+ /// @return Const @c Host object using a specified IPv6 prefix.
+ virtual ConstHostPtr
+ get6(const asiolink::IOAddress& prefix, const uint8_t prefix_len) const;
+
+ /// @brief Returns a host connected to the IPv6 subnet and having
+ /// a reservation for a specified IPv6 address or prefix.
+ ///
+ /// @param subnet_id Subnet identifier.
+ /// @param address reserved IPv6 address/prefix.
+ /// @return Const @c Host object using a specified IPv6 address/prefix.
+ virtual ConstHostPtr
+ get6(const SubnetID& subnet_id, const asiolink::IOAddress& address) const;
+
+ /// @brief Returns all hosts connected to the IPv6 subnet and having
+ /// a reservation for a specified address or delegated prefix (lease).
+ ///
+ /// @param subnet_id Subnet identifier.
+ /// @param address reserved IPv6 address/prefix.
+ ///
+ /// @return Collection of const @c Host objects.
+ virtual ConstHostCollection
+ getAll6(const SubnetID& subnet_id,
+ const asiolink::IOAddress& address) const;
+
+ /// @brief Adds a new host to the collection.
+ ///
+ /// @param host Pointer to the new @c Host object being added.
+ virtual void add(const HostPtr& host);
+
+ /// @brief Attempts to delete a host by (subnet-id, address)
+ ///
+ /// @param subnet_id subnet identifier.
+ /// @param addr specified address.
+ /// @return true if deletion was successful, false if the host was not there.
+ /// @throw various exceptions in case of errors
+ virtual bool del(const SubnetID& subnet_id, const asiolink::IOAddress& addr);
+
+ /// @brief Attempts to delete a host by (subnet-id4, identifier, identifier-type)
+ ///
+ /// @param subnet_id IPv4 Subnet identifier.
+ /// @param identifier_type Identifier type.
+ /// @param identifier_begin Pointer to a beginning of a buffer containing
+ /// an identifier.
+ /// @param identifier_len Identifier length.
+ /// @return true if deletion was successful, false if the host was not there.
+ /// @throw various exceptions in case of errors
+ virtual bool del4(const SubnetID& subnet_id,
+ const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin, const size_t identifier_len);
+
+ /// @brief Attempts to delete a host by (subnet-id6, identifier, identifier-type)
+ ///
+ /// @param subnet_id IPv6 Subnet identifier.
+ /// @param identifier_type Identifier type.
+ /// @param identifier_begin Pointer to a beginning of a buffer containing
+ /// an identifier.
+ /// @param identifier_len Identifier length.
+ /// @return true if deletion was successful, false if the host was not there.
+ /// @throw various exceptions in case of errors
+ virtual bool del6(const SubnetID& subnet_id,
+ const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin, const size_t identifier_len);
+
+ /// @brief Return backend type
+ ///
+ /// Returns the type of the backend (e.g. "mysql", "memfile" etc.)
+ ///
+ /// @return Type of the backend.
+ virtual std::string getType() const {
+ return ("mem");
+ }
+
+ /// More lease backend?
+
+ /// @brief Returns name of the database or file used by the backend
+ /// @return "mem" string"
+ virtual std::string getName() const {
+ return (std::string("mem"));
+ }
+
+ /// @brief Returns description of the backend.
+ /// @return description
+ virtual std::string getDescription() const {
+ return (std::string("In memory storage, mostly useful for testing."));
+ }
+
+ /// @brief Returns version this backend
+ /// @return two numbers that each represent practical value of this backend.
+ virtual std::pair<uint32_t, uint32_t> getVersion() const {
+ return (std::make_pair(0,0));
+ }
+
+ /// Specific methods.
+
+ /// @brief Returns store size.
+ ///
+ /// @return number of hosts in the store.
+ virtual size_t size() const;
+
+ /// @brief Controls whether IP reservations are unique or non-unique.
+ ///
+ /// In a typical case, the IP reservations are unique and backends verify
+ /// prior to adding a host reservation to the database that the reservation
+ /// for a given IP address does not exist. In some cases it may be required
+ /// to allow non-unique IP reservations, e.g. in the case when a host has
+ /// several interfaces and independently of which interface is used by this
+ /// host to communicate with the DHCP server the same IP address should be
+ /// assigned. In this case the @c unique value should be set to false to
+ /// disable the checks for uniqueness on the backend side.
+ ///
+ /// All backends are required to support the case when unique setting is
+ /// @c true and they must use this setting by default.
+ ///
+ /// @param unique boolean flag indicating if the IP reservations must be
+ /// unique or can be non-unique.
+ /// @return true if the new setting was accepted by the backend or false
+ /// otherwise.
+ virtual bool setIPReservationsUnique(const bool) {
+ return (true);
+ }
+
+protected:
+ // This is very simple storage.
+
+ /// @brief Store
+ std::vector<HostPtr> store_;
+
+ /// @brief Next host id
+ uint64_t next_host_id_;
+};
+
+/// Pointer to the Mem host data source.
+typedef boost::shared_ptr<MemHostDataSource> MemHostDataSourcePtr;
+
+/// @brief Factory function.
+///
+/// @param parameters
+/// @return A pointer to a base host data source instance.
+HostDataSourcePtr
+memFactory(const db::DatabaseConnection::ParameterMap& /*parameters*/);
+
+} // namespace isc::dhcp::test
+} // namespace isc::dhcp
+} // namespace isc
+
+#endif
diff --git a/src/lib/dhcpsrv/testutils/mysql_generic_backend_unittest.cc b/src/lib/dhcpsrv/testutils/mysql_generic_backend_unittest.cc
new file mode 100644
index 0000000..ace663e
--- /dev/null
+++ b/src/lib/dhcpsrv/testutils/mysql_generic_backend_unittest.cc
@@ -0,0 +1,43 @@
+// Copyright (C) 2019-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 <dhcpsrv/testutils/mysql_generic_backend_unittest.h>
+
+using namespace isc::db;
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+MySqlGenericBackendTest::MySqlGenericBackendTest()
+ : GenericBackendTest() {
+}
+
+size_t
+MySqlGenericBackendTest::countRows(MySqlConnection& conn, const std::string& table) {
+ // Execute a simple select query on all rows.
+ std::string query = "SELECT * FROM " + table;
+ auto status = mysql_query(conn.mysql_, query.c_str());
+ if (status != 0) {
+ ADD_FAILURE() << "Query failed: " << mysql_error(conn.mysql_);
+ return (0);
+ }
+
+ // Get the number of rows returned.
+ MYSQL_RES* res = mysql_store_result(conn.mysql_);
+ unsigned numrows = static_cast<unsigned>(mysql_num_rows(res));
+
+ // Free the result allocated.
+ mysql_free_result(res);
+
+ return (numrows);
+}
+
+
+} // end of namespace isc::dhcp::test
+} // end of namespace isc::dhcp
+} // end of namespace isc
diff --git a/src/lib/dhcpsrv/testutils/mysql_generic_backend_unittest.h b/src/lib/dhcpsrv/testutils/mysql_generic_backend_unittest.h
new file mode 100644
index 0000000..1526378
--- /dev/null
+++ b/src/lib/dhcpsrv/testutils/mysql_generic_backend_unittest.h
@@ -0,0 +1,45 @@
+// 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/.
+
+#ifndef MYSQL_GENERIC_BACKEND_UNITTEST_H
+#define MYSQL_GENERIC_BACKEND_UNITTEST_H
+
+#include <dhcpsrv/testutils/generic_backend_unittest.h>
+#include <mysql/mysql_connection.h>
+
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+/// @brief Generic test fixture class with utility functions for testing
+/// MySQL database backends.
+class MySqlGenericBackendTest : public GenericBackendTest {
+public:
+
+ /// @brief Constructor.
+ MySqlGenericBackendTest();
+
+ /// @brief Counts rows in a selected table in MySQL database.
+ ///
+ /// One of the common applications of this method is to check whether the
+ /// expected number of rows were deleted from the specified table. In
+ /// relational databases, a deletion of a raw in one table causes deletion of
+ /// rows in other tables, e.g. via cascaded delete or triggers. This method
+ /// can be used to verify that the deletion took place in the dependent
+ /// tables.
+ ///
+ /// @param conn MySql connection to be used for the query.
+ /// @param table Table name.
+ /// @return Number of rows in the specified table.
+ static size_t countRows(db::MySqlConnection& conn, const std::string& table);
+};
+
+}
+}
+}
+
+#endif
diff --git a/src/lib/dhcpsrv/testutils/pgsql_generic_backend_unittest.cc b/src/lib/dhcpsrv/testutils/pgsql_generic_backend_unittest.cc
new file mode 100644
index 0000000..412ff04
--- /dev/null
+++ b/src/lib/dhcpsrv/testutils/pgsql_generic_backend_unittest.cc
@@ -0,0 +1,48 @@
+// Copyright (C) 2021-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 <dhcpsrv/testutils/pgsql_generic_backend_unittest.h>
+
+using namespace isc::db;
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+PgSqlGenericBackendTest::PgSqlGenericBackendTest()
+ : GenericBackendTest() {
+}
+
+size_t
+PgSqlGenericBackendTest::countRows(PgSqlConnection& conn, const std::string& table) {
+ // Execute a simple select query on all rows.
+ std::string query = "SELECT * FROM " + table;
+ PGresult* result = PQexec(conn.conn_, query.c_str());
+ if (!result) {
+ ADD_FAILURE() << "Query failed and no status returned";
+ return (0);
+ }
+
+ ExecStatusType status = PQresultStatus(result);
+ if (status != PGRES_COMMAND_OK && status != PGRES_TUPLES_OK) {
+ ADD_FAILURE() << "The query returned status " << status;
+ }
+
+ // Get the number of rows returned.
+ // We don't care about the content, just the number of rows.
+ unsigned numrows = PQntuples(result);
+
+ // Free the result allocated.
+ PQclear(result);
+
+ return (numrows);
+}
+
+
+} // end of namespace isc::dhcp::test
+} // end of namespace isc::dhcp
+} // end of namespace isc
diff --git a/src/lib/dhcpsrv/testutils/pgsql_generic_backend_unittest.h b/src/lib/dhcpsrv/testutils/pgsql_generic_backend_unittest.h
new file mode 100644
index 0000000..dd944b3
--- /dev/null
+++ b/src/lib/dhcpsrv/testutils/pgsql_generic_backend_unittest.h
@@ -0,0 +1,45 @@
+// Copyright (C) 2021-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/.
+
+#ifndef PGSQL_GENERIC_BACKEND_UNITTEST_H
+#define PGSQL_GENERIC_BACKEND_UNITTEST_H
+
+#include <dhcpsrv/testutils/generic_backend_unittest.h>
+#include <pgsql/pgsql_connection.h>
+
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+/// @brief Generic test fixture class with utility functions for testing
+/// PgSQL database backends.
+class PgSqlGenericBackendTest : public GenericBackendTest {
+public:
+
+ /// @brief Constructor.
+ PgSqlGenericBackendTest();
+
+ /// @brief Counts rows in a selected table in PgSQL database.
+ ///
+ /// One of the common applications of this method is to check whether the
+ /// expected number of rows were deleted from the specified table. In
+ /// relational databases, a deletion of a raw in one table causes deletion of
+ /// rows in other tables, e.g. via cascaded delete or triggers. This method
+ /// can be used to verify that the deletion took place in the dependent
+ /// tables.
+ ///
+ /// @param conn PgSql connection to be used for the query.
+ /// @param table Table name.
+ /// @return Number of rows in the specified table.
+ static size_t countRows(db::PgSqlConnection& conn, const std::string& table);
+};
+
+}
+}
+}
+
+#endif
diff --git a/src/lib/dhcpsrv/testutils/test_config_backend.h b/src/lib/dhcpsrv/testutils/test_config_backend.h
new file mode 100644
index 0000000..d78f97f
--- /dev/null
+++ b/src/lib/dhcpsrv/testutils/test_config_backend.h
@@ -0,0 +1,121 @@
+// Copyright (C) 2018-2019 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 TEST_CONFIG_BACKEND_H
+#define TEST_CONFIG_BACKEND_H
+
+#include <config.h>
+
+#include <database/database_connection.h>
+#include <dhcpsrv/config_backend_dhcp4_mgr.h>
+#include <dhcpsrv/config_backend_dhcp6_mgr.h>
+#include <boost/shared_ptr.hpp>
+#include <boost/lexical_cast.hpp>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+/// @brief Type of pointers to stamped elements.
+typedef boost::shared_ptr<data::StampedElement> StampedElementPtr;
+
+/// @brief Base class for implementing fake backends
+template<typename ConfigBackendType>
+class TestConfigBackend : public ConfigBackendType {
+public:
+ /// @brief Constructor
+ ///
+ /// @param params database connection parameters
+ /// @throw BadValue if parameters do not include "type"
+ TestConfigBackend(const db::DatabaseConnection::ParameterMap& params)
+ : connection_(params) {
+ try {
+ db_type_ = connection_.getParameter("type");
+ } catch (...) {
+ isc_throw(BadValue, "Backend parameters must include \"type\"");
+ }
+
+ try {
+ host_ = connection_.getParameter("host");
+ } catch (...) {
+ host_ = "default_host";
+ }
+
+ try {
+ port_ = boost::lexical_cast<uint16_t>(connection_.getParameter("host"));
+ } catch (...) {
+ port_ = 0;
+ }
+ }
+
+ /// @brief virtual Destructor.
+ virtual ~TestConfigBackend(){};
+
+ /// @brief Returns backend type.
+ ///
+ /// @return string db_type name
+ virtual std::string getType() const {
+ return (db_type_);
+ }
+
+ /// @brief Returns backend host.
+ ///
+ /// @return string host
+ virtual std::string getHost() const {
+ return (host_);
+ }
+
+ /// @brief Returns backend port.
+ ///
+ /// @return uint16_t port
+ virtual uint16_t getPort() const {
+ return (port_);
+ }
+
+ /// @brief Returns server tag to be associated with the stored configuration.
+ ///
+ /// @param server_selector Server selector.
+ std::string getServerTag(const db::ServerSelector& server_selector) const {
+ if (server_selector.getType() == db::ServerSelector::Type::ALL) {
+ return ("all");
+ }
+ // Return first tag found.
+ auto tags = server_selector.getTags();
+ if (!tags.empty()) {
+ return (tags.begin()->get());
+ }
+ return ("");
+ }
+
+ /// @brief Merge server tags for a stamped element and a server selector.
+ ///
+ /// @param elem Stamped element to update.
+ /// @param server_selector Server selector.
+ void mergeServerTags(const StampedElementPtr& elem,
+ const db::ServerSelector& server_selector) const {
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ elem->setServerTag(tag.get());
+ }
+ }
+
+ /// @brief Fake database connection
+ db::DatabaseConnection connection_;
+
+ /// @brief Back end type
+ std::string db_type_;
+
+ /// @brief Back end host
+ std::string host_;
+
+ /// @brief Back end port
+ uint16_t port_;
+};
+
+} // namespace test
+} // namespace dhcp
+} // namespace isc
+
+#endif // TEST_CONFIG_BACKEND_H
diff --git a/src/lib/dhcpsrv/testutils/test_config_backend_dhcp4.cc b/src/lib/dhcpsrv/testutils/test_config_backend_dhcp4.cc
new file mode 100644
index 0000000..8648d97
--- /dev/null
+++ b/src/lib/dhcpsrv/testutils/test_config_backend_dhcp4.cc
@@ -0,0 +1,1452 @@
+// 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 <database/database_connection.h>
+#include <test_config_backend_dhcp4.h>
+#include <list>
+
+using namespace isc::data;
+using namespace isc::db;
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+bool
+TestConfigBackendDHCPv4::registerBackendType(ConfigBackendDHCPv4Mgr& mgr,
+ const std::string& db_type) {
+ return(mgr.registerBackendFactory(db_type,
+ [](const db::DatabaseConnection::ParameterMap& params)
+ -> dhcp::ConfigBackendDHCPv4Ptr {
+ return (TestConfigBackendDHCPv4Ptr(new TestConfigBackendDHCPv4(params)));
+ })
+ );
+}
+
+void
+TestConfigBackendDHCPv4::unregisterBackendType(ConfigBackendDHCPv4Mgr& mgr,
+ const std::string& db_type) {
+ mgr.unregisterBackendFactory(db_type);
+}
+
+Subnet4Ptr
+TestConfigBackendDHCPv4::getSubnet4(const db::ServerSelector& server_selector,
+ const std::string& subnet_prefix) const{
+ const auto& index = subnets_.get<SubnetPrefixIndexTag>();
+ auto subnet_it = index.find(subnet_prefix);
+ if (subnet_it == index.cend()) {
+ return (Subnet4Ptr());
+ }
+ Subnet4Ptr subnet = *subnet_it;
+ if (server_selector.amAny()) {
+ return (subnet);
+ }
+ if (server_selector.amUnassigned()) {
+ return (subnet->getServerTags().empty() ? subnet : Subnet4Ptr());
+ }
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ if (subnet->hasServerTag(ServerTag(tag))) {
+ return (subnet);
+ }
+ }
+ return (subnet->hasAllServerTag() ? subnet : Subnet4Ptr());
+}
+
+Subnet4Ptr
+TestConfigBackendDHCPv4::getSubnet4(const db::ServerSelector& server_selector,
+ const SubnetID& subnet_id) const {
+ const auto& index = subnets_.get<SubnetSubnetIdIndexTag>();
+ auto subnet_it = index.find(subnet_id);
+ if (subnet_it == index.cend()) {
+ return (Subnet4Ptr());
+ }
+ Subnet4Ptr subnet = *subnet_it;
+ if (server_selector.amAny()) {
+ return (subnet);
+ }
+ if (server_selector.amUnassigned()) {
+ return (subnet->getServerTags().empty() ? subnet : Subnet4Ptr());
+ }
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ if (subnet->hasServerTag(ServerTag(tag))) {
+ return (subnet);
+ }
+ }
+ return (subnet->hasAllServerTag() ? subnet : Subnet4Ptr());
+}
+
+Subnet4Collection
+TestConfigBackendDHCPv4::getAllSubnets4(const db::ServerSelector& server_selector) const {
+ Subnet4Collection subnets;
+ for (auto subnet : subnets_) {
+ if (server_selector.amAny()) {
+ subnets.insert(subnet);
+ continue;
+ }
+ if (server_selector.amUnassigned()) {
+ if (subnet->getServerTags().empty()) {
+ subnets.insert(subnet);
+ }
+ continue;
+ }
+ bool got = false;
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ if (subnet->hasServerTag(ServerTag(tag))) {
+ subnets.insert(subnet);
+ got = true;
+ break;
+ }
+ }
+ if (got) {
+ continue;
+ }
+ if (subnet->hasAllServerTag()) {
+ subnets.insert(subnet);
+ }
+ }
+ return (subnets);
+}
+
+Subnet4Collection
+TestConfigBackendDHCPv4::getModifiedSubnets4(const db::ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time) const {
+ const auto& index = subnets_.get<SubnetModificationTimeIndexTag>();
+ Subnet4Collection subnets;
+ auto lb = index.lower_bound(modification_time);
+ for (auto subnet = lb; subnet != index.end(); ++subnet) {
+ if (server_selector.amAny()) {
+ subnets.insert(*subnet);
+ continue;
+ }
+ if (server_selector.amUnassigned()) {
+ if ((*subnet)->getServerTags().empty()) {
+ subnets.insert(*subnet);
+ }
+ continue;
+ }
+ bool got = false;
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ if ((*subnet)->hasServerTag(ServerTag(tag))) {
+ subnets.insert(*subnet);
+ got = true;
+ break;
+ }
+ }
+ if (got) {
+ continue;
+ }
+ if ((*subnet)->hasAllServerTag()) {
+ subnets.insert(*subnet);
+ }
+ }
+ return (subnets);
+}
+
+Subnet4Collection
+TestConfigBackendDHCPv4::getSharedNetworkSubnets4(const db::ServerSelector& server_selector,
+ const std::string& shared_network_name) const {
+ Subnet4Collection subnets;
+
+ // Subnet collection does not include the index by shared network name.
+ // We need to iterate over the subnets and pick those that are associated
+ // with a shared network.
+ for (auto subnet : subnets_) {
+ // Skip subnets which do not match the server selector.
+ if (server_selector.amUnassigned() &&
+ !subnet->getServerTags().empty()) {
+ continue;
+ }
+ if (!server_selector.amAny()) {
+ bool got = false;
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ if (subnet->hasServerTag(ServerTag(tag))) {
+ got = true;
+ break;
+ }
+ }
+ if (!got && !subnet->hasAllServerTag()) {
+ continue;
+ }
+ }
+
+ // The subnet can be associated with a shared network instance or
+ // it may just point to the shared network name. The former is
+ // the case when the subnet belongs to the server configuration.
+ // The latter is the case when the subnet is fetched from the
+ // database.
+ SharedNetwork4Ptr network;
+ subnet->getSharedNetwork(network);
+ if (((network && (network->getName() == shared_network_name)) ||
+ (subnet->getSharedNetworkName() == shared_network_name))) {
+ subnets.insert(subnet);
+ }
+ }
+ return (subnets);
+}
+
+SharedNetwork4Ptr
+TestConfigBackendDHCPv4::getSharedNetwork4(const db::ServerSelector& server_selector,
+ const std::string& name) const {
+ const auto& index = shared_networks_.get<SharedNetworkNameIndexTag>();
+ auto network_it = index.find(name);
+ if (network_it == index.cend()) {
+ return (SharedNetwork4Ptr());
+ }
+ SharedNetwork4Ptr network = *network_it;
+ if (server_selector.amAny()) {
+ return (network);
+ }
+ if (server_selector.amUnassigned()) {
+ return (network->getServerTags().empty() ? network : SharedNetwork4Ptr());
+ }
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ if (network->hasServerTag(ServerTag(tag))) {
+ return (network);
+ }
+ }
+ return (network->hasAllServerTag() ? network : SharedNetwork4Ptr());
+}
+
+SharedNetwork4Collection
+TestConfigBackendDHCPv4::getAllSharedNetworks4(const db::ServerSelector& server_selector) const{
+ SharedNetwork4Collection shared_networks;
+ for (auto shared_network : shared_networks_) {
+ if (server_selector.amAny()) {
+ shared_networks.push_back(shared_network);
+ continue;
+ }
+ if (server_selector.amUnassigned()) {
+ if (shared_network->getServerTags().empty()) {
+ shared_networks.push_back(shared_network);
+ }
+ continue;
+ }
+ bool got = false;
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ if (shared_network->hasServerTag(ServerTag(tag))) {
+ shared_networks.push_back(shared_network);
+ got = true;
+ break;
+ }
+ }
+ if (got) {
+ continue;
+ }
+ if (shared_network->hasAllServerTag()) {
+ shared_networks.push_back(shared_network);
+ }
+ }
+ return (shared_networks);
+}
+
+SharedNetwork4Collection
+TestConfigBackendDHCPv4::getModifiedSharedNetworks4(const db::ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time) const {
+ const auto& index = shared_networks_.get<SharedNetworkModificationTimeIndexTag>();
+ SharedNetwork4Collection shared_networks;
+ auto lb = index.lower_bound(modification_time);
+ for (auto shared_network = lb; shared_network != index.end(); ++shared_network) {
+ if (server_selector.amAny()) {
+ shared_networks.push_back(*shared_network);
+ continue;
+ }
+ if (server_selector.amUnassigned()) {
+ if ((*shared_network)->getServerTags().empty()) {
+ shared_networks.push_back(*shared_network);
+ }
+ continue;
+ }
+ bool got = false;
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ if ((*shared_network)->hasServerTag(ServerTag(tag))) {
+ shared_networks.push_back(*shared_network);
+ got = true;
+ break;
+ }
+ }
+ if (got) {
+ continue;
+ }
+ if ((*shared_network)->hasAllServerTag()) {
+ shared_networks.push_back(*shared_network);
+ }
+ }
+ return (shared_networks);
+}
+
+OptionDefinitionPtr
+TestConfigBackendDHCPv4::getOptionDef4(const db::ServerSelector& server_selector,
+ const uint16_t code,
+ const std::string& space) const {
+ auto tags = server_selector.getTags();
+ auto candidate = OptionDefinitionPtr();
+ const auto& index = option_defs_.get<1>();
+ auto option_def_it_pair = index.equal_range(code);
+
+ for (auto option_def_it = option_def_it_pair.first;
+ option_def_it != option_def_it_pair.second;
+ ++option_def_it) {
+ if ((*option_def_it)->getOptionSpaceName() == space) {
+ for (auto tag : tags) {
+ if ((*option_def_it)->hasServerTag(ServerTag(tag))) {
+ return (*option_def_it);
+ }
+ }
+ if ((*option_def_it)->hasAllServerTag()) {
+ candidate = *option_def_it;
+ }
+ }
+ }
+ return (candidate);
+}
+
+OptionDefContainer
+TestConfigBackendDHCPv4::getAllOptionDefs4(const db::ServerSelector& server_selector) const {
+ auto tags = server_selector.getTags();
+ OptionDefContainer option_defs;
+ for (auto option_def : option_defs_) {
+ bool got = false;
+ if (server_selector.amUnassigned()) {
+ if (option_def->getServerTags().empty()) {
+ option_defs.push_back(option_def);
+ got = true;
+ }
+ } else {
+ for (auto tag : tags) {
+ if (option_def->hasServerTag(ServerTag(tag))) {
+ option_defs.push_back(option_def);
+ got = true;
+ break;
+ }
+ }
+ }
+ if (got) {
+ continue;
+ }
+ if (option_def->hasAllServerTag() && !server_selector.amUnassigned()) {
+ option_defs.push_back(option_def);
+ }
+ }
+ return (option_defs);
+}
+
+OptionDefContainer
+TestConfigBackendDHCPv4::getModifiedOptionDefs4(const db::ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time) const {
+ auto tags = server_selector.getTags();
+ OptionDefContainer option_defs;
+ const auto& index = option_defs_.get<3>();
+ auto lb = index.lower_bound(modification_time);
+ for (auto option_def = lb; option_def != index.end(); ++option_def) {
+ bool got = false;
+ for (auto tag : tags) {
+ if ((*option_def)->hasServerTag(ServerTag(tag))) {
+ option_defs.push_back(*option_def);
+ got = true;
+ break;
+ }
+ }
+ if (got) {
+ continue;
+ }
+ if ((*option_def)->hasAllServerTag()) {
+ option_defs.push_back(*option_def);
+ }
+ }
+ return (option_defs);
+}
+
+OptionDescriptorPtr
+TestConfigBackendDHCPv4::getOption4(const db::ServerSelector& server_selector,
+ const uint16_t code,
+ const std::string& space) const {
+ auto tags = server_selector.getTags();
+ auto candidate = OptionDescriptorPtr();
+ const auto& index = options_.get<1>();
+ auto option_it_pair = index.equal_range(code);
+
+ for (auto option_it = option_it_pair.first; option_it != option_it_pair.second;
+ ++option_it) {
+ if (option_it->space_name_ == space) {
+ for (auto tag : tags) {
+ if (option_it->hasServerTag(ServerTag(tag))) {
+ return (OptionDescriptorPtr(new OptionDescriptor(*option_it)));
+ }
+ }
+ if (option_it->hasAllServerTag()) {
+ candidate = OptionDescriptorPtr(new OptionDescriptor(*option_it));
+ }
+ }
+ }
+
+ return (candidate);
+}
+
+OptionContainer
+TestConfigBackendDHCPv4::getAllOptions4(const db::ServerSelector& server_selector) const {
+ auto tags = server_selector.getTags();
+ OptionContainer options;
+ for (auto option : options_) {
+ bool got = false;
+ for (auto tag : tags) {
+ if (option.hasServerTag(ServerTag(tag))) {
+ options.push_back(option);
+ got = true;
+ break;
+ }
+ }
+ if (got) {
+ continue;
+ }
+ if (option.hasAllServerTag()) {
+ options.push_back(option);
+ }
+ }
+ return (options);
+}
+
+OptionContainer
+TestConfigBackendDHCPv4::getModifiedOptions4(const db::ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time) const {
+ auto tags = server_selector.getTags();
+ OptionContainer options;
+ const auto& index = options_.get<3>();
+ auto lb = index.lower_bound(modification_time);
+ for (auto option = lb; option != index.end(); ++option) {
+ bool got = false;
+ for (auto tag : tags) {
+ if (option->hasServerTag(ServerTag(tag))) {
+ options.push_back(*option);
+ got = true;
+ break;
+ }
+ }
+ if (got) {
+ continue;
+ }
+ if (option->hasAllServerTag()) {
+ options.push_back(*option);
+ }
+ }
+ return (options);
+}
+
+StampedValuePtr
+TestConfigBackendDHCPv4::getGlobalParameter4(const db::ServerSelector& server_selector,
+ const std::string& name) const {
+ auto tags = server_selector.getTags();
+ auto candidate = StampedValuePtr();
+ const auto& index = globals_.get<StampedValueNameIndexTag>();
+ auto global_range = index.equal_range(name);
+ for (auto global_it = global_range.first; global_it != global_range.second;
+ ++global_it) {
+ for (auto tag : tags) {
+ if ((*global_it)->hasServerTag(ServerTag(tag))) {
+ return (*global_it);
+ }
+ }
+ if ((*global_it)->hasAllServerTag()) {
+ candidate = *global_it;
+ }
+ }
+
+ return (candidate);
+}
+
+
+StampedValueCollection
+TestConfigBackendDHCPv4::getAllGlobalParameters4(const db::ServerSelector& server_selector) const {
+ auto tags = server_selector.getTags();
+ StampedValueCollection globals;
+ for (auto global : globals_) {
+ bool got = false;
+ for (auto tag : tags) {
+ if (global->hasServerTag(ServerTag(tag))) {
+ globals.insert(global);
+ got = true;
+ break;
+ }
+ }
+ if (got) {
+ continue;
+ }
+ if (global->hasAllServerTag()) {
+ globals.insert(global);
+ }
+ }
+ return (globals);
+}
+
+StampedValueCollection
+TestConfigBackendDHCPv4::getModifiedGlobalParameters4(const db::ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time) const {
+ auto tags = server_selector.getTags();
+ StampedValueCollection globals;
+ const auto& index = globals_.get<StampedValueModificationTimeIndexTag>();
+ auto lb = index.lower_bound(modification_time);
+ for (auto global = lb; global != index.end(); ++global) {
+ bool got = false;
+ for (auto tag : tags) {
+ if ((*global)->hasServerTag(ServerTag(tag))) {
+ globals.insert(*global);
+ got = true;
+ break;
+ }
+ }
+ if (got) {
+ continue;
+ }
+ if ((*global)->hasAllServerTag()) {
+ globals.insert(*global);
+ }
+ }
+ return (globals);
+}
+
+ClientClassDefPtr
+TestConfigBackendDHCPv4::getClientClass4(const db::ServerSelector& server_selector,
+ const std::string& name) const {
+ ClientClassDefPtr client_class;
+ for (auto c : classes_) {
+ if (c->getName() == name) {
+ client_class = c;
+ break;
+ }
+ }
+ if (!client_class || server_selector.amAny()) {
+ return (client_class);
+ }
+ if (server_selector.amUnassigned()) {
+ return (client_class->getServerTags().empty() ? client_class : ClientClassDefPtr());
+ }
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ if (client_class->hasServerTag(ServerTag(tag))) {
+ return (client_class);
+ }
+ }
+ return (client_class->hasAllServerTag() ? client_class : ClientClassDefPtr());
+}
+
+ClientClassDictionary
+TestConfigBackendDHCPv4::getAllClientClasses4(const db::ServerSelector& server_selector) const {
+ auto tags = server_selector.getTags();
+ ClientClassDictionary all_classes;
+ for (auto client_class : classes_) {
+ if (server_selector.amAny()) {
+ all_classes.addClass(client_class);
+ continue;
+ }
+ if (server_selector.amUnassigned()) {
+ if (client_class->getServerTags().empty()) {
+ all_classes.addClass(client_class);
+ }
+ continue;
+ }
+ bool got = false;
+ for (auto tag : tags) {
+ if (client_class->hasServerTag(ServerTag(tag))) {
+ all_classes.addClass(client_class);
+ got = true;
+ break;
+ }
+ }
+ if (got) {
+ continue;
+ }
+ if (client_class->hasAllServerTag()) {
+ all_classes.addClass(client_class);
+ }
+ }
+ return (all_classes);
+}
+
+ClientClassDictionary
+TestConfigBackendDHCPv4::getModifiedClientClasses4(const db::ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time) const {
+ auto tags = server_selector.getTags();
+ ClientClassDictionary modified_classes;
+ for (auto client_class : classes_) {
+ if (client_class->getModificationTime() >= modification_time) {
+ bool got = false;
+ for (auto tag : tags) {
+ if (client_class->hasServerTag(ServerTag(tag))) {
+ modified_classes.addClass(client_class);
+ got = true;
+ break;
+ }
+ }
+ if (got) {
+ continue;
+ }
+ if (client_class->hasAllServerTag()) {
+ modified_classes.addClass(client_class);
+ }
+ }
+ }
+ return (modified_classes);
+}
+
+void
+TestConfigBackendDHCPv4::createUpdateClientClass4(const db::ServerSelector& server_selector,
+ const ClientClassDefPtr& client_class,
+ const std::string& follow_class_name) {
+ int follow_class_index = -1;
+ if (!follow_class_name.empty()) {
+ for (auto i = 0; i < classes_.size(); ++i) {
+ if (classes_[i]->getName() == follow_class_name) {
+ follow_class_index = i;
+ break;
+ }
+ }
+ if (follow_class_index < 0) {
+ isc_throw(BadValue, "class " << follow_class_name << " does not exist");
+
+ }
+ }
+
+ mergeServerTags(client_class, server_selector);
+
+ int existing_class_index = -1;
+ for (auto i = 0; i < classes_.size(); ++i) {
+ if (classes_[i]->getName() == client_class->getName()) {
+ existing_class_index = i;
+ break;
+ }
+ }
+
+ if (follow_class_index < 0) {
+ if (existing_class_index >= 0) {
+ classes_[existing_class_index] = client_class;
+ } else {
+ classes_.push_back(client_class);
+ }
+ } else {
+ if (existing_class_index < 0) {
+ classes_.insert(classes_.begin() + follow_class_index + 1, client_class);
+ } else {
+ classes_.erase(classes_.begin() + existing_class_index);
+ classes_.insert(classes_.begin() + follow_class_index + 1, client_class);
+ }
+ }
+}
+
+AuditEntryCollection
+TestConfigBackendDHCPv4::getRecentAuditEntries(const db::ServerSelector&,
+ const boost::posix_time::ptime&,
+ const uint64_t&) const {
+ return (AuditEntryCollection());
+}
+
+ServerCollection
+TestConfigBackendDHCPv4::getAllServers4() const {
+ return (servers_);
+}
+
+ServerPtr
+TestConfigBackendDHCPv4::getServer4(const ServerTag& server_tag) const {
+ const auto& index = servers_.get<ServerTagIndexTag>();
+ auto server_it = index.find(server_tag.get());
+ return ((server_it != index.cend()) ? (*server_it) : ServerPtr());
+}
+
+void
+TestConfigBackendDHCPv4::createUpdateSubnet4(const db::ServerSelector& server_selector,
+ const Subnet4Ptr& subnet) {
+ auto& index = subnets_.get<SubnetSubnetIdIndexTag>();
+ auto subnet_it = index.find(subnet->getID());
+
+ mergeServerTags(subnet, server_selector);
+
+ if (subnet_it != index.cend()) {
+ index.replace(subnet_it, subnet);
+ } else {
+ index.insert(subnet);
+ }
+}
+
+void
+TestConfigBackendDHCPv4::createUpdateSharedNetwork4(const db::ServerSelector& server_selector,
+ const SharedNetwork4Ptr& shared_network) {
+ auto& index = shared_networks_.get<SharedNetworkNameIndexTag>();
+ auto network_it = index.find(shared_network->getName());
+
+ mergeServerTags(shared_network, server_selector);
+
+ if (network_it != index.cend()) {
+ index.replace(network_it, shared_network);
+ } else {
+ index.insert(shared_network);
+ }
+}
+
+void
+TestConfigBackendDHCPv4::createUpdateOptionDef4(const db::ServerSelector& server_selector,
+ const OptionDefinitionPtr& option_def) {
+ auto tag = getServerTag(server_selector);
+ option_def->setServerTag(tag);
+
+ // Index #1 is by option code.
+ auto& index1 = option_defs_.get<1>();
+ auto option_def_it_pair1 = index1.equal_range(option_def->getCode());
+
+ for (auto option_def_it = option_def_it_pair1.first;
+ option_def_it != option_def_it_pair1.second;
+ option_def_it++) {
+ auto existing_option_def = *option_def_it;
+ if ((existing_option_def->getOptionSpaceName() == option_def->getOptionSpaceName()) &&
+ (existing_option_def->hasServerTag(ServerTag(tag)))) {
+ index1.replace(option_def_it, option_def);
+ return;
+ }
+ }
+
+ // Index #2 is by option name.
+ auto& index2 = option_defs_.get<2>();
+ auto option_def_it_pair2 = index2.equal_range(option_def->getName());
+
+ for (auto option_def_it = option_def_it_pair2.first;
+ option_def_it != option_def_it_pair2.second;
+ option_def_it++) {
+ auto existing_option_def = *option_def_it;
+ if ((existing_option_def->getOptionSpaceName() == option_def->getOptionSpaceName()) &&
+ (existing_option_def->hasServerTag(ServerTag(tag)))) {
+ index2.replace(option_def_it, option_def);
+ return;
+ }
+ }
+
+ option_defs_.push_back(option_def);
+}
+
+void
+TestConfigBackendDHCPv4::createUpdateOption4(const db::ServerSelector& server_selector,
+ const OptionDescriptorPtr& option) {
+ auto tag = getServerTag(server_selector);
+ option->setServerTag(tag);
+
+ auto& index = options_.get<1>();
+ auto option_it_pair = index.equal_range(option->option_->getType());
+
+ for (auto option_it = option_it_pair.first;
+ option_it != option_it_pair.second;
+ ++option_it) {
+ if ((option_it->space_name_ == option->space_name_) &&
+ (option_it->hasServerTag(ServerTag(tag)))) {
+ index.replace(option_it, *option);
+ return;
+ }
+ }
+
+ options_.push_back(*option);
+}
+
+void
+TestConfigBackendDHCPv4::createUpdateOption4(const db::ServerSelector& server_selector,
+ const std::string& shared_network_name,
+ const OptionDescriptorPtr& option) {
+ auto& index = shared_networks_.get<SharedNetworkNameIndexTag>();
+ auto network_it = index.find(shared_network_name);
+
+ if (network_it == index.end()) {
+ isc_throw(BadValue, "attempted to create or update option in a non existing "
+ "shared network " << shared_network_name);
+ }
+
+ auto shared_network = *network_it;
+ bool found = false;
+ if (server_selector.amUnassigned()) {
+ if (shared_network->getServerTags().empty()) {
+ found = true;
+ }
+ } else if (server_selector.amAny()) {
+ found = true;
+ } else if (shared_network->hasAllServerTag()) {
+ found = true;
+ } else {
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ if (shared_network->hasServerTag(ServerTag(tag))) {
+ found = true;
+ break;
+ }
+ }
+ }
+ if (!found) {
+ isc_throw(BadValue, "attempted to create or update option in a "
+ "shared network " << shared_network_name
+ << " not present in a selected server");
+ }
+
+ shared_network->getCfgOption()->del(option->space_name_, option->option_->getType());
+ shared_network->getCfgOption()->add(*option, option->space_name_);
+}
+
+void
+TestConfigBackendDHCPv4::createUpdateOption4(const db::ServerSelector& server_selector,
+ const SubnetID& subnet_id,
+ const OptionDescriptorPtr& option) {
+ auto& index = subnets_.get<SubnetSubnetIdIndexTag>();
+ auto subnet_it = index.find(subnet_id);
+
+ if (subnet_it == index.cend()) {
+ isc_throw(BadValue, "attempted to create or update option in a non existing "
+ "subnet ID " << subnet_id);
+ }
+
+ auto subnet = *subnet_it;
+ bool found = false;
+ if (server_selector.amUnassigned()) {
+ if (subnet->getServerTags().empty()) {
+ found = true;
+ }
+ } else if (server_selector.amAny()) {
+ found = true;
+ } else if (subnet->hasAllServerTag()) {
+ found = true;
+ } else {
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ if (subnet->hasServerTag(ServerTag(tag))) {
+ found = true;
+ break;
+ }
+ }
+ }
+ if (!found) {
+ isc_throw(BadValue, "attempted to create or update option in a "
+ "subnet ID " << subnet_id
+ << " not present in a selected server");
+ }
+
+ subnet->getCfgOption()->del(option->space_name_, option->option_->getType());
+ subnet->getCfgOption()->add(*option, option->space_name_);
+}
+
+void
+TestConfigBackendDHCPv4::createUpdateOption4(const db::ServerSelector& server_selector,
+ const asiolink::IOAddress& pool_start_address,
+ const asiolink::IOAddress& pool_end_address,
+ const OptionDescriptorPtr& option) {
+ auto not_in_selected_servers = false;
+ for (auto subnet : subnets_) {
+ // Get the pool: if it is not here we can directly go to the next subnet.
+ auto pool = subnet->getPool(Lease::TYPE_V4, pool_start_address);
+ if (!pool) {
+ continue;
+ }
+
+ // Verify the subnet is in a selected server.
+ if (server_selector.amUnassigned()) {
+ if (!subnet->getServerTags().empty()) {
+ not_in_selected_servers = true;
+ continue;
+ }
+ } else if (!server_selector.amAny() && !subnet->hasAllServerTag()) {
+ auto in_tags = false;
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ if (subnet->hasServerTag(ServerTag(tag))) {
+ in_tags = true;
+ break;
+ }
+ }
+ if (!in_tags) {
+ // Records the fact a subnet was found but not in a server.
+ not_in_selected_servers = true;
+ continue;
+ }
+ }
+
+ // Update the option.
+ pool->getCfgOption()->del(option->space_name_, option->option_->getType());
+ pool->getCfgOption()->add(*option, option->space_name_);
+
+ return;
+ }
+
+ if (not_in_selected_servers) {
+ isc_throw(BadValue, "attempted to create or update option in "
+ "a pool " << pool_start_address
+ << " - " << pool_end_address
+ << " not present in a selected server");
+ } else {
+ isc_throw(BadValue, "attempted to create or update option in "
+ "a non existing pool " << pool_start_address
+ << " - " << pool_end_address);
+ }
+}
+
+void
+TestConfigBackendDHCPv4::createUpdateGlobalParameter4(const db::ServerSelector& server_selector,
+ const data::StampedValuePtr& value) {
+ auto tag = getServerTag(server_selector);
+ value->setServerTag(tag);
+
+ auto& index = globals_.get<StampedValueNameIndexTag>();
+ auto global_it_pair = index.equal_range(value->getName());
+
+ for (auto global_it = global_it_pair.first; global_it != global_it_pair.second;
+ ++global_it) {
+ auto existing_value = *global_it;
+ if (existing_value->hasServerTag(ServerTag(tag))) {
+ index.replace(global_it, value);
+ return;
+ }
+ }
+
+ index.insert(value);
+}
+
+void
+TestConfigBackendDHCPv4::createUpdateServer4(const db::ServerPtr& server) {
+ auto& index = servers_.get<ServerTagIndexTag>();
+ auto server_it = index.find(server->getServerTagAsText());
+
+ if (server_it != index.end()) {
+ index.replace(server_it, server);
+
+ } else {
+ index.insert(server);
+ }
+}
+
+uint64_t
+TestConfigBackendDHCPv4::deleteSubnet4(const db::ServerSelector& server_selector,
+ const std::string& subnet_prefix) {
+ auto& index = subnets_.get<SubnetPrefixIndexTag>();
+ auto subnet_it = index.find(subnet_prefix);
+ if (subnet_it == index.end()) {
+ return (0);
+ }
+ if ((server_selector.amUnassigned()) &&
+ !(*subnet_it)->getServerTags().empty()) {
+ return (0);
+ }
+ if (!server_selector.amAny()) {
+ bool got = false;
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ if ((*subnet_it)->hasServerTag(ServerTag(tag))) {
+ got = true;
+ break;
+ }
+ }
+ if (!got && !(*subnet_it)->hasAllServerTag()) {
+ return (0);
+ }
+ }
+ return (index.erase(subnet_prefix));
+}
+
+uint64_t
+TestConfigBackendDHCPv4::deleteSubnet4(const db::ServerSelector& server_selector,
+ const SubnetID& subnet_id) {
+ auto& index = subnets_.get<SubnetSubnetIdIndexTag>();
+ auto subnet_it = index.find(subnet_id);
+ if (subnet_it == index.end()) {
+ return (0);
+ }
+ if ((server_selector.amUnassigned()) &&
+ !(*subnet_it)->getServerTags().empty()) {
+ return (0);
+ }
+ if (!server_selector.amAny()) {
+ bool got = false;
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ if ((*subnet_it)->hasServerTag(ServerTag(tag))) {
+ got = true;
+ break;
+ }
+ }
+ if (!got && !(*subnet_it)->hasAllServerTag()) {
+ return (0);
+ }
+ }
+ return (index.erase(subnet_id));
+}
+
+uint64_t
+TestConfigBackendDHCPv4::deleteAllSubnets4(const db::ServerSelector& server_selector) {
+ // Collect subnet to remove by ID.
+ std::list<SubnetID> ids;
+ for (auto subnet : subnets_) {
+ if (server_selector.amAny()) {
+ ids.push_back(subnet->getID());
+ continue;
+ }
+ if (server_selector.amUnassigned()) {
+ if (subnet->getServerTags().empty()) {
+ ids.push_back(subnet->getID());
+ }
+ continue;
+ }
+ bool got = false;
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ if (subnet->hasServerTag(ServerTag(tag))) {
+ ids.push_back(subnet->getID());
+ got = true;
+ break;
+ }
+ }
+ if (got) {
+ continue;
+ }
+ if (subnet->hasAllServerTag()) {
+ ids.push_back(subnet->getID());
+ }
+ }
+
+ // Erase subnets.
+ uint64_t erased = 0;
+ auto& index = subnets_.get<SubnetSubnetIdIndexTag>();
+ for (auto subnet_id : ids) {
+ erased += index.erase(subnet_id);
+ }
+ return (erased);
+}
+
+uint64_t
+TestConfigBackendDHCPv4::deleteSharedNetworkSubnets4(const db::ServerSelector& server_selector,
+ const std::string& shared_network_name) {
+ uint64_t cnt = 0;
+ for (auto subnet = subnets_.begin(); subnet != subnets_.end(); ) {
+ // Skip subnets which do not match the server selector.
+ if (server_selector.amUnassigned() &&
+ !(*subnet)->getServerTags().empty()) {
+ ++subnet;
+ continue;
+ }
+ if (!server_selector.amAny()) {
+ bool got = false;
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ if ((*subnet)->hasServerTag(ServerTag(tag))) {
+ got = true;
+ break;
+ }
+ }
+ if (!got && !(*subnet)->hasAllServerTag()) {
+ ++subnet;
+ continue;
+ }
+ }
+
+ SharedNetwork4Ptr network;
+ (*subnet)->getSharedNetwork(network);
+ if (network && (network->getName() == shared_network_name)) {
+ network->del((*subnet)->getID());
+ }
+
+ if ((network && (network->getName() == shared_network_name)) ||
+ ((*subnet)->getSharedNetworkName() == shared_network_name)) {
+ subnet = subnets_.erase(subnet);
+ ++cnt;
+ } else {
+ ++subnet;
+ }
+ }
+ return (cnt);
+}
+
+uint64_t
+TestConfigBackendDHCPv4::deleteSharedNetwork4(const db::ServerSelector& server_selector,
+ const std::string& name) {
+ auto& index = shared_networks_.get<SharedNetworkNameIndexTag>();
+ auto network_it = index.find(name);
+ if (network_it == index.end()) {
+ return (0);
+ }
+ if ((server_selector.amUnassigned()) &&
+ !(*network_it)->getServerTags().empty()) {
+ return (0);
+ }
+ if (!server_selector.amAny()) {
+ bool got = false;
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ if ((*network_it)->hasServerTag(ServerTag(tag))) {
+ got = true;
+ break;
+ }
+ }
+ if (!got && !(*network_it)->hasAllServerTag()) {
+ return (0);
+ }
+ }
+
+ // Remove this shared network.
+ for (auto subnet = subnets_.begin(); subnet != subnets_.end(); ++subnet) {
+ if ((*subnet)->getSharedNetworkName() == name) {
+ (*subnet)->setSharedNetworkName("");
+ }
+ }
+ (*network_it)->delAll();
+ return (index.erase(name));
+}
+
+uint64_t
+TestConfigBackendDHCPv4::deleteAllSharedNetworks4(const db::ServerSelector& server_selector) {
+ // Collect shared network to remove.
+ std::list<std::string> names;
+ for (auto shared_network : shared_networks_) {
+ if (server_selector.amAny()) {
+ names.push_back(shared_network->getName());
+ continue;
+ }
+ if (server_selector.amUnassigned()) {
+ if (shared_network->getServerTags().empty()) {
+ names.push_back(shared_network->getName());
+ }
+ continue;
+ }
+ bool got = false;
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ if (shared_network->hasServerTag(ServerTag(tag))) {
+ names.push_back(shared_network->getName());
+ got = true;
+ break;
+ }
+ }
+ if (got) {
+ continue;
+ }
+ if (shared_network->hasAllServerTag()) {
+ names.push_back(shared_network->getName());
+ }
+ }
+
+ // Erase shared networks.
+ uint64_t erased = 0;
+ auto& index = shared_networks_.get<SharedNetworkNameIndexTag>();
+ for (auto name : names) {
+ erased += index.erase(name);
+ }
+ return (erased);
+}
+
+uint64_t
+TestConfigBackendDHCPv4::deleteOptionDef4(const db::ServerSelector& server_selector,
+ const uint16_t code,
+ const std::string& space) {
+ auto tag = getServerTag(server_selector);
+ uint64_t erased = 0;
+ for (auto option_def_it = option_defs_.begin(); option_def_it != option_defs_.end(); ) {
+ if (((*option_def_it)->getCode() == code) &&
+ ((*option_def_it)->getOptionSpaceName() == space) &&
+ ((*option_def_it)->hasServerTag(ServerTag(tag)))) {
+ option_def_it = option_defs_.erase(option_def_it);
+ ++erased;
+ } else {
+ ++option_def_it;
+ }
+ }
+ return (erased);
+}
+
+uint64_t
+TestConfigBackendDHCPv4::deleteAllOptionDefs4(const db::ServerSelector& server_selector) {
+ auto tag = getServerTag(server_selector);
+ uint64_t erased = 0;
+ for (auto option_def_it = option_defs_.begin(); option_def_it != option_defs_.end(); ) {
+ if ((*option_def_it)->hasServerTag(ServerTag(tag))) {
+ option_def_it = option_defs_.erase(option_def_it);
+ ++erased;
+ } else {
+ ++option_def_it;
+ }
+ }
+ return (erased);
+}
+
+uint64_t
+TestConfigBackendDHCPv4::deleteOption4(const db::ServerSelector& server_selector,
+ const uint16_t code,
+ const std::string& space) {
+ auto tag = getServerTag(server_selector);
+ uint64_t erased = 0;
+ for (auto option_it = options_.begin(); option_it != options_.end(); ) {
+ if ((option_it->option_->getType() == code) &&
+ (option_it->space_name_ == space) &&
+ (option_it->hasServerTag(ServerTag(tag)))) {
+ option_it = options_.erase(option_it);
+ ++erased;
+ } else {
+ ++option_it;
+ }
+ }
+ return (erased);
+}
+
+uint64_t
+TestConfigBackendDHCPv4::deleteOption4(const db::ServerSelector& server_selector,
+ const std::string& shared_network_name,
+ const uint16_t code,
+ const std::string& space) {
+ auto& index = shared_networks_.get<SharedNetworkNameIndexTag>();
+ auto network_it = index.find(shared_network_name);
+
+ if (network_it == index.end()) {
+ isc_throw(BadValue, "attempted to delete an option in a non existing "
+ "shared network " << shared_network_name);
+ }
+
+ auto shared_network = *network_it;
+ bool found = false;
+ if (server_selector.amUnassigned()) {
+ if (shared_network->getServerTags().empty()) {
+ found = true;
+ }
+ } else if (server_selector.amAny()) {
+ found = true;
+ } else if (shared_network->hasAllServerTag()) {
+ found = true;
+ } else {
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ if (shared_network->hasServerTag(ServerTag(tag))) {
+ found = true;
+ break;
+ }
+ }
+ }
+ if (!found) {
+ isc_throw(BadValue, "attempted to delete option in a "
+ "shared network " << shared_network_name
+ << " not present in a selected server");
+ }
+
+ return (shared_network->getCfgOption()->del(space, code));
+}
+
+uint64_t
+TestConfigBackendDHCPv4::deleteOption4(const db::ServerSelector& server_selector,
+ const SubnetID& subnet_id,
+ const uint16_t code,
+ const std::string& space) {
+ auto& index = subnets_.get<SubnetSubnetIdIndexTag>();
+ auto subnet_it = index.find(subnet_id);
+
+ if (subnet_it == index.cend()) {
+ isc_throw(BadValue, "attempted to delete an option in a non existing "
+ "subnet ID " << subnet_id);
+ }
+
+ auto subnet = *subnet_it;
+ bool found = false;
+ if (server_selector.amUnassigned()) {
+ if (subnet->getServerTags().empty()) {
+ found = true;
+ }
+ } else if (server_selector.amAny()) {
+ found = true;
+ } else if (subnet->hasAllServerTag()) {
+ found = true;
+ } else {
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ if (subnet->hasServerTag(ServerTag(tag))) {
+ found = true;
+ break;
+ }
+ }
+ }
+ if (!found) {
+ isc_throw(BadValue, "attempted to delete option in a "
+ "subnet ID " << subnet_id
+ << " not present in a selected server");
+ }
+
+ return (subnet->getCfgOption()->del(space, code));
+}
+
+uint64_t
+TestConfigBackendDHCPv4::deleteOption4(const db::ServerSelector& server_selector,
+ const asiolink::IOAddress& pool_start_address,
+ const asiolink::IOAddress& pool_end_address,
+ const uint16_t code,
+ const std::string& space) {
+ auto not_in_selected_servers = false;
+ for (auto subnet : subnets_) {
+ // Get the pool: if it is not here we can directly go to the next subnet.
+
+ auto pool = subnet->getPool(Lease::TYPE_V4, pool_start_address);
+ if (!pool) {
+ continue;
+ }
+
+ // Verify the subnet is in a selected server.
+ if (server_selector.amUnassigned()) {
+ if (!subnet->getServerTags().empty()) {
+ not_in_selected_servers = true;
+ continue;
+ }
+ } else if (!server_selector.amAny() && !subnet->hasAllServerTag()) {
+ auto in_tags = false;
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ if (subnet->hasServerTag(ServerTag(tag))) {
+ in_tags = true;
+ break;
+ }
+ }
+ if (!in_tags) {
+ // Records the fact a subnet was found but not in a server.
+ not_in_selected_servers = true;
+ continue;
+ }
+ }
+
+ return (pool->getCfgOption()->del(space, code));
+ }
+
+ if (not_in_selected_servers) {
+ isc_throw(BadValue, "attempted to delete an option in a pool "
+ << pool_start_address << " - " << pool_end_address
+ << " not present in a selected server");
+ } else {
+ isc_throw(BadValue, "attempted to delete an option in a non existing "
+ "pool " << pool_start_address << " - " << pool_end_address);
+ }
+}
+
+uint64_t
+TestConfigBackendDHCPv4::deleteGlobalParameter4(const db::ServerSelector& server_selector,
+ const std::string& name) {
+ auto tag = getServerTag(server_selector);
+ auto& index = globals_.get<StampedValueNameIndexTag>();
+ auto global_it_pair = index.equal_range(name);
+
+ for (auto global_it = global_it_pair.first; global_it != global_it_pair.second;
+ ++global_it) {
+ auto value = *global_it;
+ if (value->hasServerTag(ServerTag(tag))) {
+ index.erase(global_it);
+ return (1);
+ }
+ }
+ return (0);
+}
+
+uint64_t
+TestConfigBackendDHCPv4::deleteAllGlobalParameters4(const db::ServerSelector& server_selector) {
+ auto tag = getServerTag(server_selector);
+ uint64_t cnt = 0;
+ for (auto global_it = globals_.begin(); global_it != globals_.end(); ) {
+ auto value = *global_it;
+ if (value->hasServerTag(ServerTag(tag))) {
+ global_it = globals_.erase(global_it);
+ cnt++;
+ } else {
+ ++global_it;
+ }
+ }
+ return (cnt);
+}
+
+uint64_t
+TestConfigBackendDHCPv4::deleteClientClass4(const db::ServerSelector& server_selector,
+ const std::string& name) {
+ ClientClassDefPtr existing_class;
+ auto c = classes_.begin();
+ for (; c != classes_.end(); ++c) {
+ if ((*c)->getName() == name) {
+ existing_class = (*c);
+ break;
+ }
+ }
+ if (!existing_class) {
+ return (0);
+ }
+ if ((server_selector.amUnassigned()) &&
+ !existing_class->getServerTags().empty()) {
+ return (0);
+ }
+ if (!server_selector.amAny()) {
+ bool got = false;
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ if (existing_class->hasServerTag(ServerTag(tag))) {
+ got = true;
+ break;
+ }
+ }
+ if (!got && !existing_class->hasAllServerTag()) {
+ return (0);
+ }
+ }
+ classes_.erase(c);
+ return (1);
+}
+
+uint64_t
+TestConfigBackendDHCPv4::deleteAllClientClasses4(const db::ServerSelector& server_selector) {
+ uint64_t count = 0;
+ for (auto c = classes_.begin(); c != classes_.end(); ++c) {
+ auto client_class = *c;
+ if (server_selector.amAny()) {
+ c = classes_.erase(c);
+ ++count;
+ continue;
+ }
+ if (server_selector.amUnassigned()) {
+ if (client_class->getServerTags().empty()) {
+ c = classes_.erase(c);
+ ++count;
+ }
+ continue;
+ }
+ bool got = false;
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ if (client_class->hasServerTag(ServerTag(tag))) {
+ c = classes_.erase(c);
+ ++count;
+ got = true;
+ break;
+ }
+ }
+ if (got) {
+ continue;
+ }
+ if (client_class->hasAllServerTag()) {
+ c = classes_.erase(c);
+ ++count;
+ }
+ }
+
+ return (count);
+}
+
+uint64_t
+TestConfigBackendDHCPv4::deleteServer4(const ServerTag& server_tag) {
+ auto& index = servers_.get<ServerTagIndexTag>();
+ return (index.erase(server_tag.get()));
+}
+
+uint64_t
+TestConfigBackendDHCPv4::deleteAllServers4() {
+ auto servers_size = servers_.size();
+ servers_.clear();
+ return (servers_size);
+}
+
+} // namespace test
+} // namespace dhcp
+} // namespace isc
diff --git a/src/lib/dhcpsrv/testutils/test_config_backend_dhcp4.h b/src/lib/dhcpsrv/testutils/test_config_backend_dhcp4.h
new file mode 100644
index 0000000..7791a0f
--- /dev/null
+++ b/src/lib/dhcpsrv/testutils/test_config_backend_dhcp4.h
@@ -0,0 +1,550 @@
+// 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/.
+
+#ifndef TEST_CONFIG_BACKEND_DHCP4
+#define TEST_CONFIG_BACKEND_DHCP4
+
+#include <config.h>
+
+#include <database/database_connection.h>
+#include <database/server.h>
+#include <database/server_collection.h>
+#include <dhcpsrv/client_class_def.h>
+#include <dhcpsrv/config_backend_dhcp4_mgr.h>
+#include <dhcpsrv/testutils/test_config_backend.h>
+
+#include <boost/shared_ptr.hpp>
+
+#include <map>
+#include <string>
+#include <vector>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+/// @brief Test config backend that implements all of the DHCPv4 API calls
+///
+/// This backend should be used for unit testing the DHCPv4 server and the
+/// commands which manipulate the configuration information stored in the
+/// database.
+///
+/// Server selectors supported by this test configuration backend are a
+/// superset of the server selectors allowed by the API. Therefore, if
+/// additional server selectors are allowed by the API in the future
+/// this backend should not require any additional changes to support them.
+///
+/// This backend stores server configuration information in memory.
+class TestConfigBackendDHCPv4 : public TestConfigBackend<ConfigBackendDHCPv4> {
+public:
+ /// @brief Constructor
+ ///
+ /// @param params Database connection parameters.
+ TestConfigBackendDHCPv4(const db::DatabaseConnection::ParameterMap& params)
+ : TestConfigBackend(params) {
+ }
+
+ /// @brief virtual Destructor.
+ virtual ~TestConfigBackendDHCPv4(){};
+
+ /// @brief Registers the backend type with the given backend manager
+ ///
+ /// @param mgr configuration manager to register with
+ /// @brief db_type back end type - Note you will need to
+ /// use the same value here as you do when creating backend instances.
+ static bool registerBackendType(ConfigBackendDHCPv4Mgr& mgr,
+ const std::string& db_type);
+
+ /// @brief Unregisters the backend from the given backend manager
+ ///
+ /// @param mgr configuration manager to unregister from
+ /// @brief db_type back end type - Note you will need to
+ /// use the same value here as you do when registering the backend type
+ static void unregisterBackendType(ConfigBackendDHCPv4Mgr& mgr,
+ const std::string& db_type);
+
+ /// @brief Retrieves a single subnet by subnet_prefix.
+ ///
+ /// @param server_selector Server selector.
+ /// @param subnet_prefix Prefix of the subnet to be retrieved.
+ /// @return Pointer to the retrieved subnet or NULL if not found.
+ virtual Subnet4Ptr
+ getSubnet4(const db::ServerSelector& server_selector,
+ const std::string& subnet_prefix) const;
+
+ /// @brief Retrieves a single subnet by subnet identifier.
+ ///
+ /// @param server_selector Server selector.
+ /// @param subnet_id Identifier of a subnet to be retrieved.
+ /// @return Pointer to the retrieved subnet or NULL if not found.
+ virtual Subnet4Ptr
+ getSubnet4(const db::ServerSelector& server_selector, const SubnetID& subnet_id) const;
+
+ /// @brief Retrieves all subnets.
+ ///
+ /// @param server_selector Server selector.
+ /// @return Collection of subnets or empty collection if no subnet found.
+ virtual Subnet4Collection
+ getAllSubnets4(const db::ServerSelector& server_selector) const;
+
+ /// @brief Retrieves subnets modified after specified time.
+ ///
+ /// @param server_selector Server selector.
+ /// @param modification_time Lower bound subnet modification time.
+ /// @return Collection of subnets or empty collection if no subnet found.
+ virtual Subnet4Collection
+ getModifiedSubnets4(const db::ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time) const;
+
+ /// @brief Retrieves all subnets belonging to a specified shared network.
+ ///
+ /// @param server_selector Server selector.
+ /// @param shared_network_name Name of the shared network for which the
+ /// subnets should be retrieved.
+ /// @return Collection of subnets or empty collection if no subnet found.
+ virtual Subnet4Collection
+ getSharedNetworkSubnets4(const db::ServerSelector& server_selector,
+ const std::string& shared_network_name) const;
+
+ /// @brief Retrieves shared network by name.
+ ///
+ /// @param server_selector Server selector.
+ /// @param name Name of the shared network to be retrieved.
+ /// @return Pointer to the shared network or NULL if not found.
+ virtual SharedNetwork4Ptr
+ getSharedNetwork4(const db::ServerSelector& server_selector,
+ const std::string& name) const;
+
+ /// @brief Retrieves all shared networks.
+ ///
+ /// @param server_selector Server selector.
+ /// @return Collection of shared network or empty collection if
+ /// no shared network found.
+ virtual SharedNetwork4Collection
+ getAllSharedNetworks4(const db::ServerSelector& server_selector) const;
+
+ /// @brief Retrieves shared networks modified after specified time.
+ ///
+ /// @param server_selector Server selector.
+ /// @param modification_time Lower bound shared network modification time.
+ /// @return Collection of shared network or empty collection if
+ /// no shared network found.
+ virtual SharedNetwork4Collection
+ getModifiedSharedNetworks4(const db::ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time) const;
+
+ /// @brief Retrieves single option definition by code and space.
+ ///
+ /// @param server_selector Server selector.
+ /// @param code Code of the option to be retrieved.
+ /// @param space Option space of the option to be retrieved.
+ /// @return Pointer to the option definition or NULL if not found.
+ virtual OptionDefinitionPtr
+ getOptionDef4(const db::ServerSelector& server_selector, const uint16_t code,
+ const std::string& space) const;
+
+ /// @brief Retrieves all option definitions.
+ ///
+ /// @param server_selector Server selector.
+ /// @return Collection of option definitions or empty collection if
+ /// no option definition found.
+ virtual OptionDefContainer
+ getAllOptionDefs4(const db::ServerSelector& server_selector) const;
+
+ /// @brief Retrieves option definitions modified after specified time.
+ ///
+ /// @param server_selector Server selector.
+ /// @param modification_time Lower bound option definition modification
+ /// time.
+ /// @return Collection of option definitions or empty collection if
+ /// no option definition found.
+ virtual OptionDefContainer
+ getModifiedOptionDefs4(const db::ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time) const;
+
+ /// @brief Retrieves single option by code and space.
+ ///
+ /// @param server_selector Server selector.
+ /// @return Pointer to the retrieved option descriptor or null if
+ /// no option was found.
+ virtual OptionDescriptorPtr
+ getOption4(const db::ServerSelector& server_selector, const uint16_t code,
+ const std::string& space) const;
+
+ /// @brief Retrieves all global options.
+ ///
+ /// @param server_selector Server selector.
+ /// @return Collection of global options or empty collection if no
+ /// option found.
+ virtual OptionContainer
+ getAllOptions4(const db::ServerSelector& server_selector) const;
+
+ /// @brief Retrieves option modified after specified time.
+ ///
+ /// @param selector Server selector.
+ /// @param modification_time Lower bound option modification time.
+ /// @return Collection of global options or empty collection if no
+ /// option found.
+ virtual OptionContainer
+ getModifiedOptions4(const db::ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time) const;
+
+ /// @brief Retrieves global parameter value.
+ ///
+ /// @param server_selector Server selector.
+ /// @param name Name of the global parameter to be retrieved.
+ /// @return Value of the global parameter or null if parameter doesn't
+ /// exist.
+ virtual data::StampedValuePtr
+ getGlobalParameter4(const db::ServerSelector& server_selector,
+ const std::string& name) const;
+
+ /// @brief Retrieves all global parameters.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @return Collection of global parameters.
+ virtual data::StampedValueCollection
+ getAllGlobalParameters4(const db::ServerSelector& server_selector) const;
+
+ /// @brief Retrieves global parameters modified after specified time.
+ ///
+ /// @param selector Server selector.
+ /// @return Collection of modified global parameters.
+ virtual data::StampedValueCollection
+ getModifiedGlobalParameters4(const db::ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time) const;
+
+ /// @brief Retrieves a client class by name.
+ ///
+ /// @param server_selector Server selector.
+ /// @param name Client class name.
+ /// @return Pointer to the retrieved client class.
+ virtual ClientClassDefPtr
+ getClientClass4(const db::ServerSelector& selector, const std::string& name) const;
+
+ /// @brief Retrieves all client classes.
+ ///
+ /// @param selector Server selector.
+ /// @return Collection of client classes.
+ virtual ClientClassDictionary
+ getAllClientClasses4(const db::ServerSelector& selector) const;
+
+ /// @brief Retrieves client classes modified after specified time.
+ ///
+ /// @param selector Server selector.
+ /// @param modification_time Modification time.
+ /// @return Collection of client classes.
+ virtual ClientClassDictionary
+ getModifiedClientClasses4(const db::ServerSelector& selector,
+ const boost::posix_time::ptime& modification_time) const;
+
+ /// @brief Retrieves the most recent audit entries.
+ ///
+ /// @param server_selector Server selector.
+ /// @param modification_time Timestamp being a lower limit for the returned
+ /// result set, i.e. entries later than specified time are returned.
+ /// @param modification_id Identifier being a lower limit for the returned
+ /// result set, used when two (or more) entries have the same
+ /// modification_time.
+ /// @return Collection of audit entries.
+ virtual db::AuditEntryCollection
+ getRecentAuditEntries(const db::ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time,
+ const uint64_t& modification_id) const;
+
+ /// @brief Retrieves all servers.
+ ///
+ /// @return Collection of servers from the backend.
+ virtual db::ServerCollection
+ getAllServers4() const;
+
+ /// @brief Retrieves a server.
+ ///
+ /// @param server_tag Tag of the server to be retrieved.
+ /// @return Pointer to the server instance or null pointer if no server
+ /// with the particular tag was found.
+ virtual db::ServerPtr
+ getServer4(const data::ServerTag& server_tag) const;
+
+ /// @brief Creates or updates a subnet.
+ ///
+ /// @param server_selector Server selector.
+ /// @param subnet Subnet to be added or updated.
+ virtual void
+ createUpdateSubnet4(const db::ServerSelector& server_selector,
+ const Subnet4Ptr& subnet);
+
+ /// @brief Creates or updates a shared network.
+ ///
+ /// @param server_selector Server selector.
+ /// @param shared_network Shared network to be added or updated.
+ virtual void
+ createUpdateSharedNetwork4(const db::ServerSelector& server_selector,
+ const SharedNetwork4Ptr& shared_network);
+
+ /// @brief Creates or updates an option definition.
+ ///
+ /// @param server_selector Server selector.
+ /// @param option_def Option definition to be added or updated.
+ virtual void
+ createUpdateOptionDef4(const db::ServerSelector& server_selector,
+ const OptionDefinitionPtr& option_def);
+
+ /// @brief Creates or updates global option.
+ ///
+ /// @param server_selector Server selector.
+ /// @param option Option to be added or updated.
+ virtual void
+ createUpdateOption4(const db::ServerSelector& server_selector,
+ const OptionDescriptorPtr& option);
+
+ /// @brief Creates or updates shared network level option.
+ ///
+ /// @param selector Server selector.
+ /// @param shared_network_name Name of a shared network to which option
+ /// belongs.
+ /// @param option Option to be added or updated.
+ virtual void
+ createUpdateOption4(const db::ServerSelector& server_selector,
+ const std::string& shared_network_name,
+ const OptionDescriptorPtr& option);
+
+ /// @brief Creates or updates subnet level option.
+ ///
+ /// @param server_selector Server selector.
+ /// @param subnet_id Identifier of a subnet to which option belongs.
+ /// @param option Option to be added or updated.
+ virtual void
+ createUpdateOption4(const db::ServerSelector& server_selector,
+ const SubnetID& subnet_id,
+ const OptionDescriptorPtr& option);
+
+ /// @brief Creates or updates pool level option.
+ ///
+ /// @param server_selector Server selector.
+ /// @param pool_start_address Lower bound address of the pool to which
+ /// the option belongs.
+ /// @param pool_end_address Upper bound address of the pool to which the
+ /// option belongs.
+ /// @param option Option to be added or updated.
+ virtual void
+ createUpdateOption4(const db::ServerSelector& server_selector,
+ const asiolink::IOAddress& pool_start_address,
+ const asiolink::IOAddress& pool_end_address,
+ const OptionDescriptorPtr& option);
+
+ /// @brief Creates or updates global parameter.
+ ///
+ /// @param server_selector Server selector.
+ /// @param value Value of the global parameter.
+ virtual void
+ createUpdateGlobalParameter4(const db::ServerSelector& server_selector,
+ const data::StampedValuePtr& value);
+
+ /// @brief Creates or updates DHCPv4 client class.
+ ///
+ /// @param server_selector Server selector.
+ /// @param client_class Client class to be added or updated.
+ /// @param follow_class_name name of the class after which the
+ /// new or updated class should be positioned. An empty value
+ /// causes the class to be appended at the end of the class
+ /// hierarchy.
+ virtual void
+ createUpdateClientClass4(const db::ServerSelector& server_selector,
+ const ClientClassDefPtr& client_class,
+ const std::string& follow_class_name);
+
+ /// @brief Creates or updates a server.
+ ///
+ /// @param server Instance of the server to be stored.
+ virtual void
+ createUpdateServer4(const db::ServerPtr& server);
+
+ /// @brief Deletes subnet by prefix.
+ ///
+ /// @param server_selector Server selector.
+ /// @param subnet_prefix Prefix of the subnet to be deleted.
+ /// @return Number of deleted subnets.
+ virtual uint64_t
+ deleteSubnet4(const db::ServerSelector& server_selector,
+ const std::string& subnet_prefix);
+
+ /// @brief Deletes subnet by identifier.
+ ///
+ /// @param server_selector Server selector.
+ /// @param subnet_id Identifier of the subnet to be deleted.
+ /// @return Number of deleted subnets.
+ virtual uint64_t
+ deleteSubnet4(const db::ServerSelector& server_selector, const SubnetID& subnet_id);
+
+ /// @brief Deletes all subnets.
+ ///
+ /// @param server_selector Server selector.
+ /// @return Number of deleted subnets.
+ virtual uint64_t
+ deleteAllSubnets4(const db::ServerSelector& server_selector);
+
+ /// @brief Deletes all subnets belonging to a specified shared network.
+ ///
+ /// @param server_selector Server selector.
+ /// @param shared_network_name Name of the shared network for which the
+ /// subnets should be deleted.
+ /// @return Number of deleted subnets.
+ virtual uint64_t
+ deleteSharedNetworkSubnets4(const db::ServerSelector& server_selector,
+ const std::string& shared_network_name);
+
+ /// @brief Deletes shared network by name.
+ ///
+ /// @param server_selector Server selector.
+ /// @param name Name of the shared network to be deleted.
+ /// @return Number of deleted shared networks..
+ virtual uint64_t
+ deleteSharedNetwork4(const db::ServerSelector& server_selector,
+ const std::string& name);
+
+ /// @brief Deletes all shared networks.
+ ///
+ /// @param server_selector Server selector.
+ /// @return Number of deleted shared networks.
+ virtual uint64_t
+ deleteAllSharedNetworks4(const db::ServerSelector& server_selector);
+
+ /// @brief Deletes option definition.
+ ///
+ /// @param server_selector Server selector.
+ /// @param code Code of the option to be deleted.
+ /// @param space Option space of the option to be deleted.
+ /// @return Number of deleted option definitions.
+ virtual uint64_t
+ deleteOptionDef4(const db::ServerSelector& server_selector, const uint16_t code,
+ const std::string& space);
+
+ /// @brief Deletes all option definitions.
+ ///
+ /// @param server_selector Server selector.
+ /// @return Number of deleted option definitions.
+ virtual uint64_t
+ deleteAllOptionDefs4(const db::ServerSelector& server_selector);
+
+ /// @brief Deletes global option.
+ ///
+ /// @param server_selector Server selector.
+ /// @param code Code of the option to be deleted.
+ /// @param space Option space of the option to be deleted.
+ /// @return Number of deleted options.
+ virtual uint64_t
+ deleteOption4(const db::ServerSelector& server_selector, const uint16_t code,
+ const std::string& space);
+
+ /// @brief Deletes shared network level option.
+ ///
+ /// @param selector Server selector.
+ /// @param shared_network_name Name of the shared network which option
+ /// belongs to.
+ /// @param code Code of the option to be deleted.
+ /// @param space Option space of the option to be deleted.
+ virtual uint64_t
+ deleteOption4(const db::ServerSelector& server_selector,
+ const std::string& shared_network_name,
+ const uint16_t code,
+ const std::string& space);
+
+ /// @brief Deletes subnet level option.
+ ///
+ /// @param server_selector Server selector.
+ /// @param subnet_id Identifier of the subnet to which deleted option
+ /// belongs.
+ /// @param code Code of the deleted option.
+ /// @param space Option space of the deleted option.
+ /// @return Number of deleted options.
+ virtual uint64_t
+ deleteOption4(const db::ServerSelector& server_selector, const SubnetID& subnet_id,
+ const uint16_t code, const std::string& space);
+
+ /// @brief Deletes pool level option.
+ ///
+ /// @param server_selector Server selector.
+ /// @param pool_start_address Lower bound address of the pool to which
+ /// deleted option belongs.
+ /// @param pool_end_address Upper bound address of the pool to which the
+ /// deleted option belongs.
+ /// @param code Code of the deleted option.
+ /// @param space Option space of the deleted option.
+ /// @return Number of deleted options.
+ virtual uint64_t
+ deleteOption4(const db::ServerSelector& server_selector,
+ const asiolink::IOAddress& pool_start_address,
+ const asiolink::IOAddress& pool_end_address,
+ const uint16_t code,
+ const std::string& space);
+
+ /// @brief Deletes global parameter.
+ ///
+ /// @param server_selector Server selector.
+ /// @param name Name of the global parameter to be deleted.
+ /// @return Number of deleted global parameters.
+ virtual uint64_t
+ deleteGlobalParameter4(const db::ServerSelector& server_selector,
+ const std::string& name);
+
+ /// @brief Deletes all global parameters.
+ ///
+ /// @param server_selector Server selector.
+ /// @return Number of deleted global parameters.
+ virtual uint64_t
+ deleteAllGlobalParameters4(const db::ServerSelector& server_selector);
+
+ /// @brief Deletes DHCPv4 client class.
+ ///
+ /// @param server_selector Server selector.
+ /// @param name Name of the class to be deleted.
+ /// @return Number of deleted client classes.
+ virtual uint64_t
+ deleteClientClass4(const db::ServerSelector& server_selector,
+ const std::string& name);
+
+ /// @brief Deletes all client classes.
+ ///
+ /// @param server_selector Server selector.
+ /// @return Number of deleted client classes.
+ virtual uint64_t
+ deleteAllClientClasses4(const db::ServerSelector& server_selector);
+
+ /// @brief Deletes a server from the backend.
+ ///
+ /// @param server_tag Tag of the server to be deleted.
+ /// @return Number of deleted servers.
+ virtual uint64_t
+ deleteServer4(const data::ServerTag& server_tag);
+
+ /// @brief Deletes all servers from the backend except the logical
+ /// server 'all'.
+ ///
+ /// @return Number of deleted servers.
+ virtual uint64_t
+ deleteAllServers4();
+
+/// @{
+/// @brief Containers used to house the "database" entries
+ Subnet4Collection subnets_;
+ SharedNetwork4Collection shared_networks_;
+ OptionDefContainer option_defs_;
+ OptionContainer options_;
+ data::StampedValueCollection globals_;
+ std::vector<ClientClassDefPtr> classes_;
+ db::ServerCollection servers_;
+/// @}
+};
+
+/// @brief Shared pointer to the @c TestConfigBackend.
+typedef boost::shared_ptr<TestConfigBackendDHCPv4> TestConfigBackendDHCPv4Ptr;
+
+} // namespace test
+} // namespace dhcp
+} // namespace isc
+
+#endif // TEST_CONFIG_BACKEND_DHCP4
diff --git a/src/lib/dhcpsrv/testutils/test_config_backend_dhcp6.cc b/src/lib/dhcpsrv/testutils/test_config_backend_dhcp6.cc
new file mode 100644
index 0000000..e59ad5d
--- /dev/null
+++ b/src/lib/dhcpsrv/testutils/test_config_backend_dhcp6.cc
@@ -0,0 +1,1556 @@
+// Copyright (C) 2019-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 <database/database_connection.h>
+#include <test_config_backend_dhcp6.h>
+
+using namespace isc::data;
+using namespace isc::db;
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+bool
+TestConfigBackendDHCPv6::registerBackendType(ConfigBackendDHCPv6Mgr& mgr,
+ const std::string& db_type) {
+ return(mgr.registerBackendFactory(db_type,
+ [](const db::DatabaseConnection::ParameterMap& params)
+ -> dhcp::ConfigBackendDHCPv6Ptr {
+ return (TestConfigBackendDHCPv6Ptr(new TestConfigBackendDHCPv6(params)));
+ })
+ );
+}
+
+void
+TestConfigBackendDHCPv6::unregisterBackendType(ConfigBackendDHCPv6Mgr& mgr,
+ const std::string& db_type) {
+ mgr.unregisterBackendFactory(db_type);
+}
+
+Subnet6Ptr
+TestConfigBackendDHCPv6::getSubnet6(const db::ServerSelector& server_selector,
+ const std::string& subnet_prefix) const{
+ const auto& index = subnets_.get<SubnetPrefixIndexTag>();
+ auto subnet_it = index.find(subnet_prefix);
+ if (subnet_it == index.cend()) {
+ return (Subnet6Ptr());
+ }
+ Subnet6Ptr subnet = *subnet_it;
+ if (server_selector.amAny()) {
+ return (subnet);
+ }
+ if (server_selector.amUnassigned()) {
+ return (subnet->getServerTags().empty() ? subnet : Subnet6Ptr());
+ }
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ if (subnet->hasServerTag(ServerTag(tag))) {
+ return (subnet);
+ }
+ }
+ return (subnet->hasAllServerTag() ? subnet : Subnet6Ptr());
+}
+
+Subnet6Ptr
+TestConfigBackendDHCPv6::getSubnet6(const db::ServerSelector& server_selector,
+ const SubnetID& subnet_id) const {
+ const auto& index = subnets_.get<SubnetSubnetIdIndexTag>();
+ auto subnet_it = index.find(subnet_id);
+ if (subnet_it == index.cend()) {
+ return (Subnet6Ptr());
+ }
+ Subnet6Ptr subnet = *subnet_it;
+ if (server_selector.amAny()) {
+ return (subnet);
+ }
+ if (server_selector.amUnassigned()) {
+ return (subnet->getServerTags().empty() ? subnet : Subnet6Ptr());
+ }
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ if (subnet->hasServerTag(ServerTag(tag))) {
+ return (subnet);
+ }
+ }
+ return (subnet->hasAllServerTag() ? subnet : Subnet6Ptr());
+}
+
+Subnet6Collection
+TestConfigBackendDHCPv6::getAllSubnets6(const db::ServerSelector& server_selector) const {
+ Subnet6Collection subnets;
+ for (auto subnet : subnets_) {
+ if (server_selector.amAny()) {
+ subnets.insert(subnet);
+ continue;
+ }
+ if (server_selector.amUnassigned()) {
+ if (subnet->getServerTags().empty()) {
+ subnets.insert(subnet);
+ }
+ continue;
+ }
+ bool got = false;
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ if (subnet->hasServerTag(ServerTag(tag))) {
+ subnets.insert(subnet);
+ got = true;
+ break;
+ }
+ }
+ if (got) {
+ continue;
+ }
+ if (subnet->hasAllServerTag()) {
+ subnets.insert(subnet);
+ }
+ }
+ return (subnets);
+}
+
+Subnet6Collection
+TestConfigBackendDHCPv6::getModifiedSubnets6(const db::ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time) const {
+ const auto& index = subnets_.get<SubnetModificationTimeIndexTag>();
+ Subnet6Collection subnets;
+ auto lb = index.lower_bound(modification_time);
+ for (auto subnet = lb; subnet != index.end(); ++subnet) {
+ if (server_selector.amAny()) {
+ subnets.insert(*subnet);
+ continue;
+ }
+ if (server_selector.amUnassigned()) {
+ if ((*subnet)->getServerTags().empty()) {
+ subnets.insert(*subnet);
+ }
+ continue;
+ }
+ bool got = false;
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ if ((*subnet)->hasServerTag(ServerTag(tag))) {
+ subnets.insert(*subnet);
+ got = true;
+ break;
+ }
+ }
+ if (got) {
+ continue;
+ }
+ if ((*subnet)->hasAllServerTag()) {
+ subnets.insert(*subnet);
+ }
+ }
+ return (subnets);
+}
+
+Subnet6Collection
+TestConfigBackendDHCPv6::getSharedNetworkSubnets6(const db::ServerSelector& server_selector,
+ const std::string& shared_network_name) const {
+ Subnet6Collection subnets;
+
+ // Subnet collection does not include the index by shared network name.
+ // We need to iterate over the subnets and pick those that are associated
+ // with a shared network.
+ for (auto subnet : subnets_) {
+ // Skip subnets which do not match the server selector.
+ if (server_selector.amUnassigned() &&
+ !subnet->getServerTags().empty()) {
+ continue;
+ }
+ if (!server_selector.amAny()) {
+ bool got = false;
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ if (subnet->hasServerTag(ServerTag(tag))) {
+ got = true;
+ break;
+ }
+ }
+ if (!got && !subnet->hasAllServerTag()) {
+ continue;
+ }
+ }
+
+ // The subnet can be associated with a shared network instance or
+ // it may just point to the shared network name. The former is
+ // the case when the subnet belongs to the server configuration.
+ // The latter is the case when the subnet is fetched from the
+ // database.
+ SharedNetwork6Ptr network;
+ subnet->getSharedNetwork(network);
+ if (((network && (network->getName() == shared_network_name)) ||
+ (subnet->getSharedNetworkName() == shared_network_name))) {
+ subnets.insert(subnet);
+ }
+ }
+ return (subnets);
+}
+
+SharedNetwork6Ptr
+TestConfigBackendDHCPv6::getSharedNetwork6(const db::ServerSelector& server_selector,
+ const std::string& name) const {
+ const auto& index = shared_networks_.get<SharedNetworkNameIndexTag>();
+ auto network_it = index.find(name);
+ if (network_it == index.cend()) {
+ return (SharedNetwork6Ptr());
+ }
+ SharedNetwork6Ptr network = *network_it;
+ if (server_selector.amAny()) {
+ return (network);
+ }
+ if (server_selector.amUnassigned()) {
+ return (network->getServerTags().empty() ? network : SharedNetwork6Ptr());
+ }
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ if (network->hasServerTag(ServerTag(tag))) {
+ return (network);
+ }
+ }
+ return (network->hasAllServerTag() ? network : SharedNetwork6Ptr());
+}
+
+SharedNetwork6Collection
+TestConfigBackendDHCPv6::getAllSharedNetworks6(const db::ServerSelector& server_selector) const{
+ SharedNetwork6Collection shared_networks;
+ for (auto shared_network : shared_networks_) {
+ if (server_selector.amAny()) {
+ shared_networks.push_back(shared_network);
+ continue;
+ }
+ if (server_selector.amUnassigned()) {
+ if (shared_network->getServerTags().empty()) {
+ shared_networks.push_back(shared_network);
+ }
+ continue;
+ }
+ bool got = false;
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ if (shared_network->hasServerTag(ServerTag(tag))) {
+ shared_networks.push_back(shared_network);
+ got = true;
+ break;
+ }
+ }
+ if (got) {
+ continue;
+ }
+ if (shared_network->hasAllServerTag()) {
+ shared_networks.push_back(shared_network);
+ }
+ }
+ return (shared_networks);
+}
+
+SharedNetwork6Collection
+TestConfigBackendDHCPv6::getModifiedSharedNetworks6(const db::ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time) const {
+ const auto& index = shared_networks_.get<SharedNetworkModificationTimeIndexTag>();
+ SharedNetwork6Collection shared_networks;
+ auto lb = index.lower_bound(modification_time);
+ for (auto shared_network = lb; shared_network != index.end(); ++shared_network) {
+ if (server_selector.amAny()) {
+ shared_networks.push_back(*shared_network);
+ continue;
+ }
+ if (server_selector.amUnassigned()) {
+ if ((*shared_network)->getServerTags().empty()) {
+ shared_networks.push_back(*shared_network);
+ }
+ continue;
+ }
+ bool got = false;
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ if ((*shared_network)->hasServerTag(ServerTag(tag))) {
+ shared_networks.push_back(*shared_network);
+ got = true;
+ break;
+ }
+ }
+ if (got) {
+ continue;
+ }
+ if ((*shared_network)->hasAllServerTag()) {
+ shared_networks.push_back(*shared_network);
+ }
+ }
+ return (shared_networks);
+}
+
+OptionDefinitionPtr
+TestConfigBackendDHCPv6::getOptionDef6(const db::ServerSelector& server_selector,
+ const uint16_t code,
+ const std::string& space) const {
+ auto tags = server_selector.getTags();
+ auto candidate = OptionDefinitionPtr();
+ const auto& index = option_defs_.get<1>();
+ auto option_def_it_pair = index.equal_range(code);
+
+ for (auto option_def_it = option_def_it_pair.first;
+ option_def_it != option_def_it_pair.second;
+ ++option_def_it) {
+ if ((*option_def_it)->getOptionSpaceName() == space) {
+ for (auto tag : tags) {
+ if ((*option_def_it)->hasServerTag(ServerTag(tag))) {
+ return (*option_def_it);
+ }
+ }
+ if ((*option_def_it)->hasAllServerTag()) {
+ candidate = *option_def_it;
+ }
+ }
+ }
+ return (candidate);
+}
+
+OptionDefContainer
+TestConfigBackendDHCPv6::getAllOptionDefs6(const db::ServerSelector& server_selector) const {
+ auto tags = server_selector.getTags();
+ OptionDefContainer option_defs;
+ for (auto option_def : option_defs_) {
+ bool got = false;
+ if (server_selector.amUnassigned()) {
+ if (option_def->getServerTags().empty()) {
+ option_defs.push_back(option_def);
+ got = true;
+ }
+ } else {
+ for (auto tag : tags) {
+ if (option_def->hasServerTag(ServerTag(tag))) {
+ option_defs.push_back(option_def);
+ got = true;
+ break;
+ }
+ }
+ }
+ if (got) {
+ continue;
+ }
+ if (option_def->hasAllServerTag() && !server_selector.amUnassigned()) {
+ option_defs.push_back(option_def);
+ }
+ }
+ return (option_defs);
+}
+
+OptionDefContainer
+TestConfigBackendDHCPv6::getModifiedOptionDefs6(const db::ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time) const {
+ auto tags = server_selector.getTags();
+ OptionDefContainer option_defs;
+ const auto& index = option_defs_.get<3>();
+ auto lb = index.lower_bound(modification_time);
+ for (auto option_def = lb; option_def != index.end(); ++option_def) {
+ bool got = false;
+ for (auto tag : tags) {
+ if ((*option_def)->hasServerTag(ServerTag(tag))) {
+ option_defs.push_back(*option_def);
+ got = true;
+ break;
+ }
+ }
+ if (got) {
+ continue;
+ }
+ if ((*option_def)->hasAllServerTag()) {
+ option_defs.push_back(*option_def);
+ }
+ }
+ return (option_defs);
+}
+
+OptionDescriptorPtr
+TestConfigBackendDHCPv6::getOption6(const db::ServerSelector& server_selector,
+ const uint16_t code,
+ const std::string& space) const {
+ auto tags = server_selector.getTags();
+ auto candidate = OptionDescriptorPtr();
+ const auto& index = options_.get<1>();
+ auto option_it_pair = index.equal_range(code);
+
+ for (auto option_it = option_it_pair.first; option_it != option_it_pair.second;
+ ++option_it) {
+ if (option_it->space_name_ == space) {
+ for (auto tag : tags) {
+ if (option_it->hasServerTag(ServerTag(tag))) {
+ return (OptionDescriptorPtr(new OptionDescriptor(*option_it)));
+ }
+ }
+ if (option_it->hasAllServerTag()) {
+ candidate = OptionDescriptorPtr(new OptionDescriptor(*option_it));
+ }
+ }
+ }
+
+ return (candidate);
+}
+
+OptionContainer
+TestConfigBackendDHCPv6::getAllOptions6(const db::ServerSelector& server_selector) const {
+ auto tags = server_selector.getTags();
+ OptionContainer options;
+ for (auto option : options_) {
+ bool got = false;
+ for (auto tag : tags) {
+ if (option.hasServerTag(ServerTag(tag))) {
+ options.push_back(option);
+ got = true;
+ break;
+ }
+ }
+ if (got) {
+ continue;
+ }
+ if (option.hasAllServerTag()) {
+ options.push_back(option);
+ }
+ }
+ return (options);
+}
+
+OptionContainer
+TestConfigBackendDHCPv6::getModifiedOptions6(const db::ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time) const {
+ auto tags = server_selector.getTags();
+ OptionContainer options;
+ const auto& index = options_.get<3>();
+ auto lb = index.lower_bound(modification_time);
+ for (auto option = lb; option != index.end(); ++option) {
+ bool got = false;
+ for (auto tag : tags) {
+ if (option->hasServerTag(ServerTag(tag))) {
+ options.push_back(*option);
+ got = true;
+ break;
+ }
+ }
+ if (got) {
+ continue;
+ }
+ if (option->hasAllServerTag()) {
+ options.push_back(*option);
+ }
+ }
+ return (options);
+}
+
+StampedValuePtr
+TestConfigBackendDHCPv6::getGlobalParameter6(const db::ServerSelector& server_selector,
+ const std::string& name) const {
+ auto tags = server_selector.getTags();
+ auto candidate = StampedValuePtr();
+ const auto& index = globals_.get<StampedValueNameIndexTag>();
+ auto global_range = index.equal_range(name);
+ for (auto global_it = global_range.first; global_it != global_range.second;
+ ++global_it) {
+ for (auto tag : tags) {
+ if ((*global_it)->hasServerTag(ServerTag(tag))) {
+ return (*global_it);
+ }
+ }
+ if ((*global_it)->hasAllServerTag()) {
+ candidate = *global_it;
+ }
+ }
+
+ return (candidate);
+}
+
+
+StampedValueCollection
+TestConfigBackendDHCPv6::getAllGlobalParameters6(const db::ServerSelector& server_selector) const {
+ auto tags = server_selector.getTags();
+ StampedValueCollection globals;
+ for (auto global : globals_) {
+ bool got = false;
+ for (auto tag : tags) {
+ if (global->hasServerTag(ServerTag(tag))) {
+ globals.insert(global);
+ got = true;
+ break;
+ }
+ }
+ if (got) {
+ continue;
+ }
+ if (global->hasAllServerTag()) {
+ globals.insert(global);
+ }
+ }
+ return (globals);
+}
+
+StampedValueCollection
+TestConfigBackendDHCPv6::getModifiedGlobalParameters6(const db::ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time) const {
+ auto tags = server_selector.getTags();
+ StampedValueCollection globals;
+ const auto& index = globals_.get<StampedValueModificationTimeIndexTag>();
+ auto lb = index.lower_bound(modification_time);
+ for (auto global = lb; global != index.end(); ++global) {
+ bool got = false;
+ for (auto tag : tags) {
+ if ((*global)->hasServerTag(ServerTag(tag))) {
+ globals.insert(*global);
+ got = true;
+ break;
+ }
+ }
+ if (got) {
+ continue;
+ }
+ if ((*global)->hasAllServerTag()) {
+ globals.insert(*global);
+ }
+ }
+ return (globals);
+}
+
+ClientClassDefPtr
+TestConfigBackendDHCPv6::getClientClass6(const db::ServerSelector& server_selector,
+ const std::string& name) const {
+ ClientClassDefPtr client_class;
+ for (auto c : classes_) {
+ if (c->getName() == name) {
+ client_class = c;
+ break;
+ }
+ }
+ if (!client_class || server_selector.amAny()) {
+ return (client_class);
+ }
+ if (server_selector.amUnassigned()) {
+ return (client_class->getServerTags().empty() ? client_class : ClientClassDefPtr());
+ }
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ if (client_class->hasServerTag(ServerTag(tag))) {
+ return (client_class);
+ }
+ }
+ return (client_class->hasAllServerTag() ? client_class : ClientClassDefPtr());
+}
+
+ClientClassDictionary
+TestConfigBackendDHCPv6::getAllClientClasses6(const db::ServerSelector& server_selector) const {
+ auto tags = server_selector.getTags();
+ ClientClassDictionary all_classes;
+ for (auto client_class : classes_) {
+ if (server_selector.amAny()) {
+ all_classes.addClass(client_class);
+ continue;
+ }
+ if (server_selector.amUnassigned()) {
+ if (client_class->getServerTags().empty()) {
+ all_classes.addClass(client_class);
+ }
+ continue;
+ }
+ bool got = false;
+ for (auto tag : tags) {
+ if (client_class->hasServerTag(ServerTag(tag))) {
+ all_classes.addClass(client_class);
+ got = true;
+ break;
+ }
+ }
+ if (got) {
+ continue;
+ }
+ if (client_class->hasAllServerTag()) {
+ all_classes.addClass(client_class);
+ }
+ }
+ return (all_classes);
+}
+
+ClientClassDictionary
+TestConfigBackendDHCPv6::getModifiedClientClasses6(const db::ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time) const {
+ auto tags = server_selector.getTags();
+ ClientClassDictionary modified_classes;
+ for (auto client_class : classes_) {
+ if (client_class->getModificationTime() >= modification_time) {
+ bool got = false;
+ for (auto tag : tags) {
+ if (client_class->hasServerTag(ServerTag(tag))) {
+ modified_classes.addClass(client_class);
+ got = true;
+ break;
+ }
+ }
+ if (got) {
+ continue;
+ }
+ if (client_class->hasAllServerTag()) {
+ modified_classes.addClass(client_class);
+ }
+ }
+ }
+ return (modified_classes);
+}
+
+void
+TestConfigBackendDHCPv6::createUpdateClientClass6(const db::ServerSelector& server_selector,
+ const ClientClassDefPtr& client_class,
+ const std::string& follow_class_name) {
+ int follow_class_index = -1;
+ if (!follow_class_name.empty()) {
+ for (auto i = 0; i < classes_.size(); ++i) {
+ if (classes_[i]->getName() == follow_class_name) {
+ follow_class_index = i;
+ break;
+ }
+ }
+ if (follow_class_index < 0) {
+ isc_throw(BadValue, "class " << follow_class_name << " does not exist");
+
+ }
+ }
+
+ mergeServerTags(client_class, server_selector);
+
+ int existing_class_index = -1;
+ for (auto i = 0; i < classes_.size(); ++i) {
+ if (classes_[i]->getName() == client_class->getName()) {
+ existing_class_index = i;
+ break;
+ }
+ }
+
+ if (follow_class_index < 0) {
+ if (existing_class_index >= 0) {
+ classes_[existing_class_index] = client_class;
+ } else {
+ classes_.push_back(client_class);
+ }
+ } else {
+ if (existing_class_index < 0) {
+ classes_.insert(classes_.begin() + follow_class_index + 1, client_class);
+ } else {
+ classes_.erase(classes_.begin() + existing_class_index);
+ classes_.insert(classes_.begin() + follow_class_index + 1, client_class);
+ }
+ }
+}
+
+AuditEntryCollection
+TestConfigBackendDHCPv6::getRecentAuditEntries(const db::ServerSelector&,
+ const boost::posix_time::ptime&,
+ const uint64_t&) const {
+ return (AuditEntryCollection());
+}
+
+ServerCollection
+TestConfigBackendDHCPv6::getAllServers6() const {
+ return (servers_);
+}
+
+ServerPtr
+TestConfigBackendDHCPv6::getServer6(const ServerTag& server_tag) const {
+ const auto& index = servers_.get<ServerTagIndexTag>();
+ auto server_it = index.find(server_tag.get());
+ return ((server_it != index.cend()) ? (*server_it) : ServerPtr());
+}
+
+void
+TestConfigBackendDHCPv6::createUpdateSubnet6(const db::ServerSelector& server_selector,
+ const Subnet6Ptr& subnet) {
+ auto& index = subnets_.get<SubnetSubnetIdIndexTag>();
+ auto subnet_it = index.find(subnet->getID());
+
+ mergeServerTags(subnet, server_selector);
+
+ if (subnet_it != index.cend()) {
+ index.replace(subnet_it, subnet);
+ } else {
+ index.insert(subnet);
+ }
+}
+
+void
+TestConfigBackendDHCPv6::createUpdateSharedNetwork6(const db::ServerSelector& server_selector,
+ const SharedNetwork6Ptr& shared_network) {
+ auto& index = shared_networks_.get<SharedNetworkNameIndexTag>();
+ auto network_it = index.find(shared_network->getName());
+
+ mergeServerTags(shared_network, server_selector);
+
+ if (network_it != index.cend()) {
+ index.replace(network_it, shared_network);
+ } else {
+ index.insert(shared_network);
+ }
+}
+
+void
+TestConfigBackendDHCPv6::createUpdateOptionDef6(const db::ServerSelector& server_selector,
+ const OptionDefinitionPtr& option_def) {
+ auto tag = getServerTag(server_selector);
+ option_def->setServerTag(tag);
+
+ // Index #1 is by option code.
+ auto& index1 = option_defs_.get<1>();
+ auto option_def_it_pair1 = index1.equal_range(option_def->getCode());
+
+ for (auto option_def_it = option_def_it_pair1.first;
+ option_def_it != option_def_it_pair1.second;
+ option_def_it++) {
+ auto existing_option_def = *option_def_it;
+ if ((existing_option_def->getOptionSpaceName() == option_def->getOptionSpaceName()) &&
+ (existing_option_def->hasServerTag(ServerTag(tag)))) {
+ index1.replace(option_def_it, option_def);
+ return;
+ }
+ }
+
+ // Index #2 is by option name.
+ auto& index2 = option_defs_.get<2>();
+ auto option_def_it_pair2 = index2.equal_range(option_def->getName());
+
+ for (auto option_def_it = option_def_it_pair2.first;
+ option_def_it != option_def_it_pair2.second;
+ option_def_it++) {
+ auto existing_option_def = *option_def_it;
+ if ((existing_option_def->getOptionSpaceName() == option_def->getOptionSpaceName()) &&
+ (existing_option_def->hasServerTag(ServerTag(tag)))) {
+ index2.replace(option_def_it, option_def);
+ return;
+ }
+ }
+
+ option_defs_.push_back(option_def);
+}
+
+void
+TestConfigBackendDHCPv6::createUpdateOption6(const db::ServerSelector& server_selector,
+ const OptionDescriptorPtr& option) {
+ auto tag = getServerTag(server_selector);
+ option->setServerTag(tag);
+
+ auto& index = options_.get<1>();
+ auto option_it_pair = index.equal_range(option->option_->getType());
+
+ for (auto option_it = option_it_pair.first;
+ option_it != option_it_pair.second;
+ ++option_it) {
+ if ((option_it->space_name_ == option->space_name_) &&
+ (option_it->hasServerTag(ServerTag(tag)))) {
+ index.replace(option_it, *option);
+ return;
+ }
+ }
+
+ options_.push_back(*option);
+}
+
+void
+TestConfigBackendDHCPv6::createUpdateOption6(const db::ServerSelector& server_selector,
+ const std::string& shared_network_name,
+ const OptionDescriptorPtr& option) {
+ auto& index = shared_networks_.get<SharedNetworkNameIndexTag>();
+ auto network_it = index.find(shared_network_name);
+
+ if (network_it == index.end()) {
+ isc_throw(BadValue, "attempted to create or update option in a non existing "
+ "shared network " << shared_network_name);
+ }
+
+ auto shared_network = *network_it;
+ bool found = false;
+ if (server_selector.amUnassigned()) {
+ if (shared_network->getServerTags().empty()) {
+ found = true;
+ }
+ } else if (server_selector.amAny()) {
+ found = true;
+ } else if (shared_network->hasAllServerTag()) {
+ found = true;
+ } else {
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ if (shared_network->hasServerTag(ServerTag(tag))) {
+ found = true;
+ break;
+ }
+ }
+ }
+ if (!found) {
+ isc_throw(BadValue, "attempted to create or update option in a "
+ "shared network " << shared_network_name
+ << " not present in a selected server");
+ }
+
+ shared_network->getCfgOption()->del(option->space_name_, option->option_->getType());
+ shared_network->getCfgOption()->add(*option, option->space_name_);
+}
+
+void
+TestConfigBackendDHCPv6::createUpdateOption6(const db::ServerSelector& server_selector,
+ const SubnetID& subnet_id,
+ const OptionDescriptorPtr& option) {
+ auto& index = subnets_.get<SubnetSubnetIdIndexTag>();
+ auto subnet_it = index.find(subnet_id);
+
+ if (subnet_it == index.cend()) {
+ isc_throw(BadValue, "attempted to create or update option in a non existing "
+ "subnet ID " << subnet_id);
+ }
+
+ auto subnet = *subnet_it;
+ bool found = false;
+ if (server_selector.amUnassigned()) {
+ if (subnet->getServerTags().empty()) {
+ found = true;
+ }
+ } else if (server_selector.amAny()) {
+ found = true;
+ } else if (subnet->hasAllServerTag()) {
+ found = true;
+ } else {
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ if (subnet->hasServerTag(ServerTag(tag))) {
+ found = true;
+ break;
+ }
+ }
+ }
+ if (!found) {
+ isc_throw(BadValue, "attempted to create or update option in a "
+ "subnet ID " << subnet_id
+ << " not present in a selected server");
+ }
+
+ subnet->getCfgOption()->del(option->space_name_, option->option_->getType());
+ subnet->getCfgOption()->add(*option, option->space_name_);
+}
+
+void
+TestConfigBackendDHCPv6::createUpdateOption6(const db::ServerSelector& server_selector,
+ const asiolink::IOAddress& pool_start_address,
+ const asiolink::IOAddress& pool_end_address,
+ const OptionDescriptorPtr& option) {
+ auto not_in_selected_servers = false;
+ for (auto subnet : subnets_) {
+ // Get the pool: if it is not here we can directly go to the next subnet.
+ auto pool = subnet->getPool(Lease::TYPE_NA, pool_start_address);
+ if (!pool) {
+ continue;
+ }
+
+ // Verify the subnet is in a selected server.
+ if (server_selector.amUnassigned()) {
+ if (!subnet->getServerTags().empty()) {
+ not_in_selected_servers = true;
+ continue;
+ }
+ } else if (!server_selector.amAny() && !subnet->hasAllServerTag()) {
+ auto in_tags = false;
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ if (subnet->hasServerTag(ServerTag(tag))) {
+ in_tags = true;
+ break;
+ }
+ }
+ if (!in_tags) {
+ // Records the fact a subnet was found but not in a server.
+ not_in_selected_servers = true;
+ continue;
+ }
+ }
+
+ // Update the option.
+ pool->getCfgOption()->del(option->space_name_, option->option_->getType());
+ pool->getCfgOption()->add(*option, option->space_name_);
+
+ return;
+ }
+
+ if (not_in_selected_servers) {
+ isc_throw(BadValue, "attempted to create or update option in "
+ "a pool " << pool_start_address
+ << " - " << pool_end_address
+ << " not present in a selected server");
+ } else {
+ isc_throw(BadValue, "attempted to create or update option in "
+ "a non existing pool " << pool_start_address
+ << " - " << pool_end_address);
+ }
+}
+
+void
+TestConfigBackendDHCPv6::createUpdateOption6(const db::ServerSelector& server_selector,
+ const asiolink::IOAddress& pd_pool_prefix,
+ const uint8_t pd_pool_prefix_length,
+ const OptionDescriptorPtr& option) {
+ auto not_in_selected_servers = false;
+ for (auto subnet : subnets_) {
+ // Get the pd pool: if it is not here we can directly go to the next subnet.
+ auto pdpool = subnet->getPool(Lease::TYPE_PD, pd_pool_prefix);
+ if (!pdpool) {
+ continue;
+ }
+
+ // Verify the subnet is in a selected server.
+ if (server_selector.amUnassigned()) {
+ if (!subnet->getServerTags().empty()) {
+ not_in_selected_servers = true;
+ continue;
+ }
+ } else if (!server_selector.amAny() && !subnet->hasAllServerTag()) {
+ auto in_tags = false;
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ if (subnet->hasServerTag(ServerTag(tag))) {
+ in_tags = true;
+ break;
+ }
+ }
+ if (!in_tags) {
+ // Records the fact a subnet was found but not in a server.
+ not_in_selected_servers = true;
+ continue;
+ }
+ }
+
+ // Update the option.
+ pdpool->getCfgOption()->del(option->space_name_, option->option_->getType());
+ pdpool->getCfgOption()->add(*option, option->space_name_);
+
+ return;
+ }
+
+ if (not_in_selected_servers) {
+ isc_throw(BadValue, "attempted to create or update option in "
+ "a prefix pool " << pd_pool_prefix
+ << "/" << static_cast<unsigned>(pd_pool_prefix_length)
+ << " not present in a selected server");
+ } else {
+ isc_throw(BadValue, "attempted to create or update option in "
+ "a non existing prefix pool " << pd_pool_prefix
+ << "/" << static_cast<unsigned>(pd_pool_prefix_length));
+ }
+}
+
+void
+TestConfigBackendDHCPv6::createUpdateGlobalParameter6(const db::ServerSelector& server_selector,
+ const data::StampedValuePtr& value) {
+ auto tag = getServerTag(server_selector);
+ value->setServerTag(tag);
+
+ auto& index = globals_.get<StampedValueNameIndexTag>();
+ auto global_it_pair = index.equal_range(value->getName());
+
+ for (auto global_it = global_it_pair.first; global_it != global_it_pair.second;
+ ++global_it) {
+ auto existing_value = *global_it;
+ if (existing_value->hasServerTag(ServerTag(tag))) {
+ index.replace(global_it, value);
+ return;
+ }
+ }
+
+ index.insert(value);
+}
+
+void
+TestConfigBackendDHCPv6::createUpdateServer6(const db::ServerPtr& server) {
+ auto& index = servers_.get<ServerTagIndexTag>();
+ auto server_it = index.find(server->getServerTagAsText());
+
+ if (server_it != index.end()) {
+ index.replace(server_it, server);
+
+ } else {
+ index.insert(server);
+ }
+}
+
+uint64_t
+TestConfigBackendDHCPv6::deleteSubnet6(const db::ServerSelector& server_selector,
+ const std::string& subnet_prefix) {
+ auto& index = subnets_.get<SubnetPrefixIndexTag>();
+ auto subnet_it = index.find(subnet_prefix);
+ if (subnet_it == index.end()) {
+ return (0);
+ }
+ if ((server_selector.amUnassigned()) &&
+ !(*subnet_it)->getServerTags().empty()) {
+ return (0);
+ }
+ if (!server_selector.amAny()) {
+ bool got = false;
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ if ((*subnet_it)->hasServerTag(ServerTag(tag))) {
+ got = true;
+ break;
+ }
+ }
+ if (!got && !(*subnet_it)->hasAllServerTag()) {
+ return (0);
+ }
+ }
+ return (index.erase(subnet_prefix));
+}
+
+uint64_t
+TestConfigBackendDHCPv6::deleteSubnet6(const db::ServerSelector& server_selector,
+ const SubnetID& subnet_id) {
+ auto& index = subnets_.get<SubnetSubnetIdIndexTag>();
+ auto subnet_it = index.find(subnet_id);
+ if (subnet_it == index.end()) {
+ return (0);
+ }
+ if ((server_selector.amUnassigned()) &&
+ !(*subnet_it)->getServerTags().empty()) {
+ return (0);
+ }
+ if (!server_selector.amAny()) {
+ bool got = false;
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ if ((*subnet_it)->hasServerTag(ServerTag(tag))) {
+ got = true;
+ break;
+ }
+ }
+ if (!got && !(*subnet_it)->hasAllServerTag()) {
+ return (0);
+ }
+ }
+ return (index.erase(subnet_id));
+}
+
+uint64_t
+TestConfigBackendDHCPv6::deleteAllSubnets6(const db::ServerSelector& server_selector) {
+ // Collect subnet to remove by ID.
+ std::list<SubnetID> ids;
+ for (auto subnet : subnets_) {
+ if (server_selector.amAny()) {
+ ids.push_back(subnet->getID());
+ continue;
+ }
+ if (server_selector.amUnassigned()) {
+ if (subnet->getServerTags().empty()) {
+ ids.push_back(subnet->getID());
+ }
+ continue;
+ }
+ bool got = false;
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ if (subnet->hasServerTag(ServerTag(tag))) {
+ ids.push_back(subnet->getID());
+ got = true;
+ break;
+ }
+ }
+ if (got) {
+ continue;
+ }
+ if (subnet->hasAllServerTag()) {
+ ids.push_back(subnet->getID());
+ }
+ }
+
+ // Erase subnets.
+ uint64_t erased = 0;
+ auto& index = subnets_.get<SubnetSubnetIdIndexTag>();
+ for (auto subnet_id : ids) {
+ erased += index.erase(subnet_id);
+ }
+ return (erased);
+}
+
+uint64_t
+TestConfigBackendDHCPv6::deleteSharedNetworkSubnets6(const db::ServerSelector& server_selector,
+ const std::string& shared_network_name) {
+ uint64_t cnt = 0;
+ for (auto subnet = subnets_.begin(); subnet != subnets_.end(); ) {
+ // Skip subnets which do not match the server selector.
+ if (server_selector.amUnassigned() &&
+ !(*subnet)->getServerTags().empty()) {
+ ++subnet;
+ continue;
+ }
+ if (!server_selector.amAny()) {
+ bool got = false;
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ if ((*subnet)->hasServerTag(ServerTag(tag))) {
+ got = true;
+ break;
+ }
+ }
+ if (!got && !(*subnet)->hasAllServerTag()) {
+ ++subnet;
+ continue;
+ }
+ }
+
+ SharedNetwork6Ptr network;
+ (*subnet)->getSharedNetwork(network);
+ if (network && (network->getName() == shared_network_name)) {
+ network->del((*subnet)->getID());
+ }
+
+ if ((network && (network->getName() == shared_network_name)) ||
+ ((*subnet)->getSharedNetworkName() == shared_network_name)) {
+ subnet = subnets_.erase(subnet);
+ ++cnt;
+ } else {
+ ++subnet;
+ }
+ }
+ return (cnt);
+}
+
+uint64_t
+TestConfigBackendDHCPv6::deleteSharedNetwork6(const db::ServerSelector& server_selector,
+ const std::string& name) {
+ auto& index = shared_networks_.get<SharedNetworkNameIndexTag>();
+ auto network_it = index.find(name);
+ if (network_it == index.end()) {
+ return (0);
+ }
+ if ((server_selector.amUnassigned()) &&
+ !(*network_it)->getServerTags().empty()) {
+ return (0);
+ }
+ if (!server_selector.amAny()) {
+ bool got = false;
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ if ((*network_it)->hasServerTag(ServerTag(tag))) {
+ got = true;
+ break;
+ }
+ }
+ if (!got && !(*network_it)->hasAllServerTag()) {
+ return (0);
+ }
+ }
+
+ // Remove this shared network.
+ for (auto subnet = subnets_.begin(); subnet != subnets_.end(); ++subnet) {
+ if ((*subnet)->getSharedNetworkName() == name) {
+ (*subnet)->setSharedNetworkName("");
+ }
+ }
+ (*network_it)->delAll();
+ return (index.erase(name));
+}
+
+uint64_t
+TestConfigBackendDHCPv6::deleteAllSharedNetworks6(const db::ServerSelector& server_selector) {
+ // Collect shared network to remove.
+ std::list<std::string> names;
+ for (auto shared_network : shared_networks_) {
+ if (server_selector.amAny()) {
+ names.push_back(shared_network->getName());
+ continue;
+ }
+ if (server_selector.amUnassigned()) {
+ if (shared_network->getServerTags().empty()) {
+ names.push_back(shared_network->getName());
+ }
+ continue;
+ }
+ bool got = false;
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ if (shared_network->hasServerTag(ServerTag(tag))) {
+ names.push_back(shared_network->getName());
+ got = true;
+ break;
+ }
+ }
+ if (got) {
+ continue;
+ }
+ if (shared_network->hasAllServerTag()) {
+ names.push_back(shared_network->getName());
+ }
+ }
+
+ // Erase shared networks.
+ uint64_t erased = 0;
+ auto& index = shared_networks_.get<SharedNetworkNameIndexTag>();
+ for (auto name : names) {
+ erased += index.erase(name);
+ }
+ return (erased);
+}
+
+uint64_t
+TestConfigBackendDHCPv6::deleteOptionDef6(const db::ServerSelector& server_selector,
+ const uint16_t code,
+ const std::string& space) {
+ auto tag = getServerTag(server_selector);
+ uint64_t erased = 0;
+ for (auto option_def_it = option_defs_.begin(); option_def_it != option_defs_.end(); ) {
+ if (((*option_def_it)->getCode() == code) &&
+ ((*option_def_it)->getOptionSpaceName() == space) &&
+ ((*option_def_it)->hasServerTag(ServerTag(tag)))) {
+ option_def_it = option_defs_.erase(option_def_it);
+ ++erased;
+ } else {
+ ++option_def_it;
+ }
+ }
+ return (erased);
+}
+
+uint64_t
+TestConfigBackendDHCPv6::deleteAllOptionDefs6(const db::ServerSelector& server_selector) {
+ auto tag = getServerTag(server_selector);
+ uint64_t erased = 0;
+ for (auto option_def_it = option_defs_.begin(); option_def_it != option_defs_.end(); ) {
+ if ((*option_def_it)->hasServerTag(ServerTag(tag))) {
+ option_def_it = option_defs_.erase(option_def_it);
+ ++erased;
+ } else {
+ ++option_def_it;
+ }
+ }
+ return (erased);
+}
+
+uint64_t
+TestConfigBackendDHCPv6::deleteOption6(const db::ServerSelector& server_selector,
+ const uint16_t code,
+ const std::string& space) {
+ auto tag = getServerTag(server_selector);
+ uint64_t erased = 0;
+ for (auto option_it = options_.begin(); option_it != options_.end(); ) {
+ if ((option_it->option_->getType() == code) &&
+ (option_it->space_name_ == space) &&
+ (option_it->hasServerTag(ServerTag(tag)))) {
+ option_it = options_.erase(option_it);
+ ++erased;
+ } else {
+ ++option_it;
+ }
+ }
+ return (erased);
+}
+
+uint64_t
+TestConfigBackendDHCPv6::deleteOption6(const db::ServerSelector& server_selector,
+ const std::string& shared_network_name,
+ const uint16_t code,
+ const std::string& space) {
+ auto& index = shared_networks_.get<SharedNetworkNameIndexTag>();
+ auto network_it = index.find(shared_network_name);
+
+ if (network_it == index.end()) {
+ isc_throw(BadValue, "attempted to delete an option in a non existing "
+ "shared network " << shared_network_name);
+ }
+
+ auto shared_network = *network_it;
+ bool found = false;
+ if (server_selector.amUnassigned()) {
+ if (shared_network->getServerTags().empty()) {
+ found = true;
+ }
+ } else if (server_selector.amAny()) {
+ found = true;
+ } else if (shared_network->hasAllServerTag()) {
+ found = true;
+ } else {
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ if (shared_network->hasServerTag(ServerTag(tag))) {
+ found = true;
+ break;
+ }
+ }
+ }
+ if (!found) {
+ isc_throw(BadValue, "attempted to delete option in a "
+ "shared network " << shared_network_name
+ << " not present in a selected server");
+ }
+
+ return (shared_network->getCfgOption()->del(space, code));
+}
+
+uint64_t
+TestConfigBackendDHCPv6::deleteOption6(const db::ServerSelector& server_selector,
+ const SubnetID& subnet_id,
+ const uint16_t code,
+ const std::string& space) {
+ auto& index = subnets_.get<SubnetSubnetIdIndexTag>();
+ auto subnet_it = index.find(subnet_id);
+
+ if (subnet_it == index.cend()) {
+ isc_throw(BadValue, "attempted to delete an option in a non existing "
+ "subnet ID " << subnet_id);
+ }
+
+ auto subnet = *subnet_it;
+ bool found = false;
+ if (server_selector.amUnassigned()) {
+ if (subnet->getServerTags().empty()) {
+ found = true;
+ }
+ } else if (server_selector.amAny()) {
+ found = true;
+ } else if (subnet->hasAllServerTag()) {
+ found = true;
+ } else {
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ if (subnet->hasServerTag(ServerTag(tag))) {
+ found = true;
+ break;
+ }
+ }
+ }
+ if (!found) {
+ isc_throw(BadValue, "attempted to delete option in a "
+ "subnet ID " << subnet_id
+ << " not present in a selected server");
+ }
+
+ return (subnet->getCfgOption()->del(space, code));
+}
+
+uint64_t
+TestConfigBackendDHCPv6::deleteOption6(const db::ServerSelector& server_selector,
+ const asiolink::IOAddress& pool_start_address,
+ const asiolink::IOAddress& pool_end_address,
+ const uint16_t code,
+ const std::string& space) {
+ auto not_in_selected_servers = false;
+ for (auto subnet : subnets_) {
+ // Get the pool: if it is not here we can directly go to the next subnet.
+
+ auto pool = subnet->getPool(Lease::TYPE_NA, pool_start_address);
+ if (!pool) {
+ continue;
+ }
+
+ // Verify the subnet is in a selected server.
+ if (server_selector.amUnassigned()) {
+ if (!subnet->getServerTags().empty()) {
+ not_in_selected_servers = true;
+ continue;
+ }
+ } else if (!server_selector.amAny() && !subnet->hasAllServerTag()) {
+ auto in_tags = false;
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ if (subnet->hasServerTag(ServerTag(tag))) {
+ in_tags = true;
+ break;
+ }
+ }
+ if (!in_tags) {
+ // Records the fact a subnet was found but not in a server.
+ not_in_selected_servers = true;
+ continue;
+ }
+ }
+
+ return (pool->getCfgOption()->del(space, code));
+ }
+
+ if (not_in_selected_servers) {
+ isc_throw(BadValue, "attempted to delete an option in a pool "
+ << pool_start_address << " - " << pool_end_address
+ << " not present in a selected server");
+ } else {
+ isc_throw(BadValue, "attempted to delete an option in a non existing "
+ "pool " << pool_start_address << " - " << pool_end_address);
+ }
+}
+
+uint64_t
+TestConfigBackendDHCPv6::deleteOption6(const db::ServerSelector& server_selector,
+ const asiolink::IOAddress& pd_pool_prefix,
+ const uint8_t pd_pool_prefix_length,
+ const uint16_t code,
+ const std::string& space) {
+ auto not_in_selected_servers = false;
+ for (auto subnet : subnets_) {
+ // Get the pd pool: if it is not here we can directly go to the next subnet.
+ auto pdpool = subnet->getPool(Lease::TYPE_PD, pd_pool_prefix);
+ if (!pdpool) {
+ continue;
+ }
+
+ // Verify the subnet is in a selected server.
+ if (server_selector.amUnassigned()) {
+ if (!subnet->getServerTags().empty()) {
+ not_in_selected_servers = true;
+ continue;
+ }
+ } else if (!server_selector.amAny() && !subnet->hasAllServerTag()) {
+ auto in_tags = false;
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ if (subnet->hasServerTag(ServerTag(tag))) {
+ in_tags = true;
+ break;
+ }
+ }
+ if (!in_tags) {
+ // Records the fact a subnet was found but not in a server.
+ not_in_selected_servers = true;
+ continue;
+ }
+ }
+
+ return (pdpool->getCfgOption()->del(space, code));
+ }
+
+ if (not_in_selected_servers) {
+ isc_throw(BadValue, "attempted to delete an option in "
+ "a prefix pool " << pd_pool_prefix
+ << "/" << static_cast<unsigned>(pd_pool_prefix_length)
+ << " not present in a selected server");
+ } else {
+ isc_throw(BadValue, "attempted to delete an option in "
+ "a non existing prefix pool " << pd_pool_prefix
+ << "/" << static_cast<unsigned>(pd_pool_prefix_length));
+ }
+}
+
+uint64_t
+TestConfigBackendDHCPv6::deleteGlobalParameter6(const db::ServerSelector& server_selector,
+ const std::string& name) {
+ auto tag = getServerTag(server_selector);
+ auto& index = globals_.get<StampedValueNameIndexTag>();
+ auto global_it_pair = index.equal_range(name);
+
+ for (auto global_it = global_it_pair.first; global_it != global_it_pair.second;
+ ++global_it) {
+ auto value = *global_it;
+ if (value->hasServerTag(ServerTag(tag))) {
+ index.erase(global_it);
+ return (1);
+ }
+ }
+ return (0);
+}
+
+uint64_t
+TestConfigBackendDHCPv6::deleteAllGlobalParameters6(const db::ServerSelector& server_selector) {
+ auto tag = getServerTag(server_selector);
+ uint64_t cnt = 0;
+ for (auto global_it = globals_.begin(); global_it != globals_.end(); ) {
+ auto value = *global_it;
+ if (value->hasServerTag(ServerTag(tag))) {
+ global_it = globals_.erase(global_it);
+ cnt++;
+ } else {
+ ++global_it;
+ }
+ }
+ return (cnt);
+}
+
+uint64_t
+TestConfigBackendDHCPv6::deleteClientClass6(const db::ServerSelector& server_selector,
+ const std::string& name) {
+ ClientClassDefPtr existing_class;
+ auto c = classes_.begin();
+ for (; c != classes_.end(); ++c) {
+ if ((*c)->getName() == name) {
+ existing_class = (*c);
+ break;
+ }
+ }
+ if (!existing_class) {
+ return (0);
+ }
+ if ((server_selector.amUnassigned()) &&
+ !existing_class->getServerTags().empty()) {
+ return (0);
+ }
+ if (!server_selector.amAny()) {
+ bool got = false;
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ if (existing_class->hasServerTag(ServerTag(tag))) {
+ got = true;
+ break;
+ }
+ }
+ if (!got && !existing_class->hasAllServerTag()) {
+ return (0);
+ }
+ }
+ classes_.erase(c);
+ return (1);
+}
+
+uint64_t
+TestConfigBackendDHCPv6::deleteAllClientClasses6(const db::ServerSelector& server_selector) {
+ uint64_t count = 0;
+ for (auto c = classes_.begin(); c != classes_.end(); ++c) {
+ auto client_class = *c;
+ if (server_selector.amAny()) {
+ c = classes_.erase(c);
+ ++count;
+ continue;
+ }
+ if (server_selector.amUnassigned()) {
+ if (client_class->getServerTags().empty()) {
+ c = classes_.erase(c);
+ ++count;
+ }
+ continue;
+ }
+ bool got = false;
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ if (client_class->hasServerTag(ServerTag(tag))) {
+ c = classes_.erase(c);
+ ++count;
+ got = true;
+ break;
+ }
+ }
+ if (got) {
+ continue;
+ }
+ if (client_class->hasAllServerTag()) {
+ c = classes_.erase(c);
+ ++count;
+ }
+ }
+
+ return (count);
+}
+
+uint64_t
+TestConfigBackendDHCPv6::deleteServer6(const ServerTag& server_tag) {
+ auto& index = servers_.get<ServerTagIndexTag>();
+ return (index.erase(server_tag.get()));
+}
+
+uint64_t
+TestConfigBackendDHCPv6::deleteAllServers6() {
+ auto servers_size = servers_.size();
+ servers_.clear();
+ return (servers_size);
+}
+
+} // namespace test
+} // namespace dhcp
+} // namespace isc
diff --git a/src/lib/dhcpsrv/testutils/test_config_backend_dhcp6.h b/src/lib/dhcpsrv/testutils/test_config_backend_dhcp6.h
new file mode 100644
index 0000000..8c65bb2
--- /dev/null
+++ b/src/lib/dhcpsrv/testutils/test_config_backend_dhcp6.h
@@ -0,0 +1,580 @@
+// Copyright (C) 2019-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef TEST_CONFIG_BACKEND_DHCP6
+#define TEST_CONFIG_BACKEND_DHCP6
+
+#include <config.h>
+
+#include <database/database_connection.h>
+#include <database/server.h>
+#include <database/server_collection.h>
+#include <dhcpsrv/config_backend_dhcp6_mgr.h>
+#include <dhcpsrv/testutils/test_config_backend.h>
+
+#include <boost/shared_ptr.hpp>
+
+#include <map>
+#include <string>
+#include <vector>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+/// @brief Test config backend that implements all of the DHCPv6 API calls
+///
+/// This backend should be used for unit testing the DHCPv6 server and the
+/// commands which manipulate the configuration information stored in the
+/// database.
+///
+/// Server selectors supported by this test configuration backend are a
+/// superset of the server selectors allowed by the API. Therefore, if
+/// additional server selectors are allowed by the API in the future
+/// this backend should not require any additional changes to support them.
+///
+/// This backend stores server configuration information in memory.
+class TestConfigBackendDHCPv6 : public TestConfigBackend<ConfigBackendDHCPv6> {
+public:
+ /// @brief Constructor
+ ///
+ /// @param params Database connection parameters.
+ TestConfigBackendDHCPv6(const db::DatabaseConnection::ParameterMap& params)
+ : TestConfigBackend(params) {
+ }
+
+ /// @brief virtual Destructor.
+ virtual ~TestConfigBackendDHCPv6(){};
+
+ /// @brief Registers the backend type with the given backend manager
+ ///
+ /// @param mgr configuration manager to register with
+ /// @brief db_type back end type - Note you will need to
+ /// use the same value here as you do when creating backend instances.
+ static bool registerBackendType(ConfigBackendDHCPv6Mgr& mgr,
+ const std::string& db_type);
+
+ /// @brief Unregisters the backend from the given backend manager
+ ///
+ /// @param mgr configuration manager to unregister from
+ /// @brief db_type back end type - Note you will need to
+ /// use the same value here as you do when registering the backend type
+ static void unregisterBackendType(ConfigBackendDHCPv6Mgr& mgr,
+ const std::string& db_type);
+
+ /// @brief Retrieves a single subnet by subnet_prefix.
+ ///
+ /// @param server_selector Server selector.
+ /// @param subnet_prefix Prefix of the subnet to be retrieved.
+ /// @return Pointer to the retrieved subnet or NULL if not found.
+ virtual Subnet6Ptr
+ getSubnet6(const db::ServerSelector& server_selector,
+ const std::string& subnet_prefix) const;
+
+ /// @brief Retrieves a single subnet by subnet identifier.
+ ///
+ /// @param server_selector Server selector.
+ /// @param subnet_id Identifier of a subnet to be retrieved.
+ /// @return Pointer to the retrieved subnet or NULL if not found.
+ virtual Subnet6Ptr
+ getSubnet6(const db::ServerSelector& server_selector, const SubnetID& subnet_id) const;
+
+ /// @brief Retrieves all subnets.
+ ///
+ /// @param server_selector Server selector.
+ /// @return Collection of subnets or empty collection if no subnet found.
+ virtual Subnet6Collection
+ getAllSubnets6(const db::ServerSelector& server_selector) const;
+
+ /// @brief Retrieves subnets modified after specified time.
+ ///
+ /// @param server_selector Server selector.
+ /// @param modification_time Lower bound subnet modification time.
+ /// @return Collection of subnets or empty collection if no subnet found.
+ virtual Subnet6Collection
+ getModifiedSubnets6(const db::ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time) const;
+
+ /// @brief Retrieves all subnets belonging to a specified shared network.
+ ///
+ /// @param server_selector Server selector.
+ /// @param shared_network_name Name of the shared network for which the
+ /// subnets should be retrieved.
+ /// @return Collection of subnets or empty collection if no subnet found.
+ virtual Subnet6Collection
+ getSharedNetworkSubnets6(const db::ServerSelector& server_selector,
+ const std::string& shared_network_name) const;
+
+ /// @brief Retrieves shared network by name.
+ ///
+ /// @param server_selector Server selector.
+ /// @param name Name of the shared network to be retrieved.
+ /// @return Pointer to the shared network or NULL if not found.
+ virtual SharedNetwork6Ptr
+ getSharedNetwork6(const db::ServerSelector& server_selector,
+ const std::string& name) const;
+
+ /// @brief Retrieves all shared networks.
+ ///
+ /// @param server_selector Server selector.
+ /// @return Collection of shared network or empty collection if
+ /// no shared network found.
+ virtual SharedNetwork6Collection
+ getAllSharedNetworks6(const db::ServerSelector& server_selector) const;
+
+ /// @brief Retrieves shared networks modified after specified time.
+ ///
+ /// @param server_selector Server selector.
+ /// @param modification_time Lower bound shared network modification time.
+ /// @return Collection of shared network or empty collection if
+ /// no shared network found.
+ virtual SharedNetwork6Collection
+ getModifiedSharedNetworks6(const db::ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time) const;
+
+ /// @brief Retrieves single option definition by code and space.
+ ///
+ /// @param server_selector Server selector.
+ /// @param code Code of the option to be retrieved.
+ /// @param space Option space of the option to be retrieved.
+ /// @return Pointer to the option definition or NULL if not found.
+ virtual OptionDefinitionPtr
+ getOptionDef6(const db::ServerSelector& server_selector, const uint16_t code,
+ const std::string& space) const;
+
+ /// @brief Retrieves all option definitions.
+ ///
+ /// @param server_selector Server selector.
+ /// @return Collection of option definitions or empty collection if
+ /// no option definition found.
+ virtual OptionDefContainer
+ getAllOptionDefs6(const db::ServerSelector& server_selector) const;
+
+ /// @brief Retrieves option definitions modified after specified time.
+ ///
+ /// @param server_selector Server selector.
+ /// @param modification_time Lower bound option definition modification
+ /// time.
+ /// @return Collection of option definitions or empty collection if
+ /// no option definition found.
+ virtual OptionDefContainer
+ getModifiedOptionDefs6(const db::ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time) const;
+
+ /// @brief Retrieves single option by code and space.
+ ///
+ /// @param server_selector Server selector.
+ /// @return Pointer to the retrieved option descriptor or null if
+ /// no option was found.
+ virtual OptionDescriptorPtr
+ getOption6(const db::ServerSelector& server_selector, const uint16_t code,
+ const std::string& space) const;
+
+ /// @brief Retrieves all global options.
+ ///
+ /// @param server_selector Server selector.
+ /// @return Collection of global options or empty collection if no
+ /// option found.
+ virtual OptionContainer
+ getAllOptions6(const db::ServerSelector& server_selector) const;
+
+ /// @brief Retrieves option modified after specified time.
+ ///
+ /// @param selector Server selector.
+ /// @param modification_time Lower bound option modification time.
+ /// @return Collection of global options or empty collection if no
+ /// option found.
+ virtual OptionContainer
+ getModifiedOptions6(const db::ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time) const;
+
+ /// @brief Retrieves global parameter value.
+ ///
+ /// @param server_selector Server selector.
+ /// @param name Name of the global parameter to be retrieved.
+ /// @return Value of the global parameter or null if parameter doesn't
+ /// exist.
+ virtual data::StampedValuePtr
+ getGlobalParameter6(const db::ServerSelector& server_selector,
+ const std::string& name) const;
+
+ /// @brief Retrieves all global parameters.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @return Collection of global parameters.
+ virtual data::StampedValueCollection
+ getAllGlobalParameters6(const db::ServerSelector& server_selector) const;
+
+ /// @brief Retrieves global parameters modified after specified time.
+ ///
+ /// @param selector Server selector.
+ /// @return Collection of modified global parameters.
+ virtual data::StampedValueCollection
+ getModifiedGlobalParameters6(const db::ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time) const;
+
+ /// @brief Retrieves a client class by name.
+ ///
+ /// @param server_selector Server selector.
+ /// @param name Client class name.
+ /// @return Pointer to the retrieved client class.
+ virtual ClientClassDefPtr
+ getClientClass6(const db::ServerSelector& selector, const std::string& name) const;
+
+ /// @brief Retrieves all client classes.
+ ///
+ /// @param selector Server selector.
+ /// @return Collection of client classes.
+ virtual ClientClassDictionary
+ getAllClientClasses6(const db::ServerSelector& selector) const;
+
+ /// @brief Retrieves client classes modified after specified time.
+ ///
+ /// @param selector Server selector.
+ /// @param modification_time Modification time.
+ /// @return Collection of client classes.
+ virtual ClientClassDictionary
+ getModifiedClientClasses6(const db::ServerSelector& selector,
+ const boost::posix_time::ptime& modification_time) const;
+
+ /// @brief Retrieves the most recent audit entries.
+ ///
+ /// @param server_selector Server selector.
+ /// @param modification_time Timestamp being a lower limit for the returned
+ /// result set, i.e. entries later than specified time are returned.
+ /// @param modification_id Identifier being a lower limit for the returned
+ /// result set, used when two (or more) entries have the same
+ /// modification_time.
+ /// @return Collection of audit entries.
+ virtual db::AuditEntryCollection
+ getRecentAuditEntries(const db::ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time,
+ const uint64_t& modification_id) const;
+
+ /// @brief Retrieves all servers.
+ ///
+ /// @return Collection of servers from the backend.
+ virtual db::ServerCollection
+ getAllServers6() const;
+
+ /// @brief Retrieves a server.
+ ///
+ /// @param server_tag Tag of the server to be retrieved.
+ /// @return Pointer to the server instance or null pointer if no server
+ /// with the particular tag was found.
+ virtual db::ServerPtr
+ getServer6(const data::ServerTag& server_tag) const;
+
+ /// @brief Creates or updates a subnet.
+ ///
+ /// @param server_selector Server selector.
+ /// @param subnet Subnet to be added or updated.
+ virtual void
+ createUpdateSubnet6(const db::ServerSelector& server_selector,
+ const Subnet6Ptr& subnet);
+
+ /// @brief Creates or updates a shared network.
+ ///
+ /// @param server_selector Server selector.
+ /// @param shared_network Shared network to be added or updated.
+ virtual void
+ createUpdateSharedNetwork6(const db::ServerSelector& server_selector,
+ const SharedNetwork6Ptr& shared_network);
+
+ /// @brief Creates or updates an option definition.
+ ///
+ /// @param server_selector Server selector.
+ /// @param option_def Option definition to be added or updated.
+ virtual void
+ createUpdateOptionDef6(const db::ServerSelector& server_selector,
+ const OptionDefinitionPtr& option_def);
+
+ /// @brief Creates or updates global option.
+ ///
+ /// @param server_selector Server selector.
+ /// @param option Option to be added or updated.
+ virtual void
+ createUpdateOption6(const db::ServerSelector& server_selector,
+ const OptionDescriptorPtr& option);
+
+ /// @brief Creates or updates shared network level option.
+ ///
+ /// @param selector Server selector.
+ /// @param shared_network_name Name of a shared network to which option
+ /// belongs.
+ /// @param option Option to be added or updated.
+ virtual void
+ createUpdateOption6(const db::ServerSelector& server_selector,
+ const std::string& shared_network_name,
+ const OptionDescriptorPtr& option);
+
+ /// @brief Creates or updates subnet level option.
+ ///
+ /// @param server_selector Server selector.
+ /// @param subnet_id Identifier of a subnet to which option belongs.
+ /// @param option Option to be added or updated.
+ virtual void
+ createUpdateOption6(const db::ServerSelector& server_selector,
+ const SubnetID& subnet_id,
+ const OptionDescriptorPtr& option);
+
+ /// @brief Creates or updates pool level option.
+ ///
+ /// @param server_selector Server selector.
+ /// @param pool_start_address Lower bound address of the pool to which
+ /// the option belongs.
+ /// @param pool_end_address Upper bound address of the pool to which the
+ /// option belongs.
+ /// @param option Option to be added or updated.
+ virtual void
+ createUpdateOption6(const db::ServerSelector& server_selector,
+ const asiolink::IOAddress& pool_start_address,
+ const asiolink::IOAddress& pool_end_address,
+ const OptionDescriptorPtr& option);
+
+ /// @brief Creates or updates pd pool level option.
+ ///
+ /// @param server_selector Server selector.
+ /// @param pd_pool_prefix Address part of the prefix of the pd pool
+ /// to which the option belongs.
+ /// @param pd_pool_prefix_length Prefix length of the pd pool to which
+ /// the option belongs.
+ /// @param option Option to be added or updated.
+ virtual void
+ createUpdateOption6(const db::ServerSelector& server_selector,
+ const asiolink::IOAddress& pd_pool_prefix,
+ const uint8_t pd_pool_prefix_length,
+ const OptionDescriptorPtr& option);
+
+ /// @brief Creates or updates global parameter.
+ ///
+ /// @param server_selector Server selector.
+ /// @param value Value of the global parameter.
+ virtual void
+ createUpdateGlobalParameter6(const db::ServerSelector& server_selector,
+ const data::StampedValuePtr& value);
+
+ /// @brief Creates or updates DHCPv6 client class.
+ ///
+ /// @param server_selector Server selector.
+ /// @param client_class Client class to be added or updated.
+ /// @param follow_class_name name of the class after which the
+ /// new or updated class should be positioned. An empty value
+ /// causes the class to be appended at the end of the class
+ /// hierarchy.
+ virtual void
+ createUpdateClientClass6(const db::ServerSelector& server_selector,
+ const ClientClassDefPtr& client_class,
+ const std::string& follow_class_name);
+
+ /// @brief Creates or updates a server.
+ ///
+ /// @param server Instance of the server to be stored.
+ virtual void
+ createUpdateServer6(const db::ServerPtr& server);
+
+ /// @brief Deletes subnet by prefix.
+ ///
+ /// @param server_selector Server selector.
+ /// @param subnet_prefix Prefix of the subnet to be deleted.
+ /// @return Number of deleted subnets.
+ virtual uint64_t
+ deleteSubnet6(const db::ServerSelector& server_selector,
+ const std::string& subnet_prefix);
+
+ /// @brief Deletes subnet by identifier.
+ ///
+ /// @param server_selector Server selector.
+ /// @param subnet_id Identifier of the subnet to be deleted.
+ /// @return Number of deleted subnets.
+ virtual uint64_t
+ deleteSubnet6(const db::ServerSelector& server_selector, const SubnetID& subnet_id);
+
+ /// @brief Deletes all subnets.
+ ///
+ /// @param server_selector Server selector.
+ /// @return Number of deleted subnets.
+ virtual uint64_t
+ deleteAllSubnets6(const db::ServerSelector& server_selector);
+
+ /// @brief Deletes all subnets belonging to a specified shared network.
+ ///
+ /// @param server_selector Server selector.
+ /// @param shared_network_name Name of the shared network for which the
+ /// subnets should be deleted.
+ /// @return Number of deleted subnets.
+ virtual uint64_t
+ deleteSharedNetworkSubnets6(const db::ServerSelector& server_selector,
+ const std::string& shared_network_name);
+
+ /// @brief Deletes shared network by name.
+ ///
+ /// @param server_selector Server selector.
+ /// @param name Name of the shared network to be deleted.
+ /// @return Number of deleted shared networks..
+ virtual uint64_t
+ deleteSharedNetwork6(const db::ServerSelector& server_selector,
+ const std::string& name);
+
+ /// @brief Deletes all shared networks.
+ ///
+ /// @param server_selector Server selector.
+ /// @return Number of deleted shared networks.
+ virtual uint64_t
+ deleteAllSharedNetworks6(const db::ServerSelector& server_selector);
+
+ /// @brief Deletes option definition.
+ ///
+ /// @param server_selector Server selector.
+ /// @param code Code of the option to be deleted.
+ /// @param space Option space of the option to be deleted.
+ /// @return Number of deleted option definitions.
+ virtual uint64_t
+ deleteOptionDef6(const db::ServerSelector& server_selector, const uint16_t code,
+ const std::string& space);
+
+ /// @brief Deletes all option definitions.
+ ///
+ /// @param server_selector Server selector.
+ /// @return Number of deleted option definitions.
+ virtual uint64_t
+ deleteAllOptionDefs6(const db::ServerSelector& server_selector);
+
+ /// @brief Deletes global option.
+ ///
+ /// @param server_selector Server selector.
+ /// @param code Code of the option to be deleted.
+ /// @param space Option space of the option to be deleted.
+ /// @return Number of deleted options.
+ virtual uint64_t
+ deleteOption6(const db::ServerSelector& server_selector, const uint16_t code,
+ const std::string& space);
+
+ /// @brief Deletes shared network level option.
+ ///
+ /// @param selector Server selector.
+ /// @param shared_network_name Name of the shared network which option
+ /// belongs to.
+ /// @param code Code of the option to be deleted.
+ /// @param space Option space of the option to be deleted.
+ virtual uint64_t
+ deleteOption6(const db::ServerSelector& server_selector,
+ const std::string& shared_network_name,
+ const uint16_t code,
+ const std::string& space);
+
+ /// @brief Deletes subnet level option.
+ ///
+ /// @param server_selector Server selector.
+ /// @param subnet_id Identifier of the subnet to which deleted option
+ /// belongs.
+ /// @param code Code of the deleted option.
+ /// @param space Option space of the deleted option.
+ /// @return Number of deleted options.
+ virtual uint64_t
+ deleteOption6(const db::ServerSelector& server_selector, const SubnetID& subnet_id,
+ const uint16_t code, const std::string& space);
+
+ /// @brief Deletes pool level option.
+ ///
+ /// @param server_selector Server selector.
+ /// @param pool_start_address Lower bound address of the pool to which
+ /// deleted option belongs.
+ /// @param pool_end_address Upper bound address of the pool to which the
+ /// deleted option belongs.
+ /// @param code Code of the deleted option.
+ /// @param space Option space of the deleted option.
+ /// @return Number of deleted options.
+ virtual uint64_t
+ deleteOption6(const db::ServerSelector& server_selector,
+ const asiolink::IOAddress& pool_start_address,
+ const asiolink::IOAddress& pool_end_address,
+ const uint16_t code,
+ const std::string& space);
+
+ /// @brief Deletes pd pool level option.
+ ///
+ /// @param server_selector Server selector.
+ /// @param pd_pool_prefix Address part of the prefix of the pd pool
+ /// to which the deleted option belongs.
+ /// @param pd_pool_prefix_length Prefix length of the pd pool to which
+ /// the deleted option belongs.
+ /// @param code Code of the deleted option.
+ /// @param space Option space of the deleted option.
+ /// @return Number of deleted options.
+ virtual uint64_t
+ deleteOption6(const db::ServerSelector& server_selector,
+ const asiolink::IOAddress& pd_pool_prefix,
+ const uint8_t pd_pool_prefix_length,
+ const uint16_t code,
+ const std::string& space);
+
+ /// @brief Deletes global parameter.
+ ///
+ /// @param server_selector Server selector.
+ /// @param name Name of the global parameter to be deleted.
+ /// @return Number of deleted global parameters.
+ virtual uint64_t
+ deleteGlobalParameter6(const db::ServerSelector& server_selector,
+ const std::string& name);
+
+ /// @brief Deletes all global parameters.
+ ///
+ /// @param server_selector Server selector.
+ /// @return Number of deleted global parameters.
+ virtual uint64_t
+ deleteAllGlobalParameters6(const db::ServerSelector& server_selector);
+
+ /// @brief Deletes DHCPv6 client class.
+ ///
+ /// @param server_selector Server selector.
+ /// @param name Name of the class to be deleted.
+ /// @return Number of deleted client classes.
+ virtual uint64_t
+ deleteClientClass6(const db::ServerSelector& server_selector,
+ const std::string& name);
+
+ /// @brief Deletes all client classes.
+ ///
+ /// @param server_selector Server selector.
+ /// @return Number of deleted client classes.
+ virtual uint64_t
+ deleteAllClientClasses6(const db::ServerSelector& server_selector);
+
+ /// @brief Deletes a server from the backend.
+ ///
+ /// @param server_tag Tag of the server to be deleted.
+ /// @return Number of deleted servers.
+ virtual uint64_t
+ deleteServer6(const data::ServerTag& server_tag);
+
+ /// @brief Deletes all servers from the backend except the logical
+ /// server 'all'.
+ ///
+ /// @return Number of deleted servers.
+ virtual uint64_t
+ deleteAllServers6();
+
+/// @{
+/// @brief Containers used to house the "database" entries
+ Subnet6Collection subnets_;
+ SharedNetwork6Collection shared_networks_;
+ OptionDefContainer option_defs_;
+ OptionContainer options_;
+ data::StampedValueCollection globals_;
+ std::vector<ClientClassDefPtr> classes_;
+ db::ServerCollection servers_;
+/// @}
+};
+
+/// @brief Shared pointer to the @c TestConfigBackend.
+typedef boost::shared_ptr<TestConfigBackendDHCPv6> TestConfigBackendDHCPv6Ptr;
+
+} // namespace test
+} // namespace dhcp
+} // namespace isc
+
+#endif // TEST_CONFIG_BACKEND_DHCP6
diff --git a/src/lib/dhcpsrv/testutils/test_utils.cc b/src/lib/dhcpsrv/testutils/test_utils.cc
new file mode 100644
index 0000000..4fe7d86
--- /dev/null
+++ b/src/lib/dhcpsrv/testutils/test_utils.cc
@@ -0,0 +1,153 @@
+// Copyright (C) 2012-2020 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include "test_utils.h"
+#include <asiolink/io_address.h>
+#include <gtest/gtest.h>
+#include <sstream>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+using namespace std;
+using namespace isc::asiolink;
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+void
+detailCompareLease(const Lease4Ptr& first, const Lease4Ptr& second) {
+ // Compare address strings. Comparison of address objects is not used, as
+ // odd things happen when they are different: the EXPECT_EQ macro appears to
+ // call the operator uint32_t() function, which causes an exception to be
+ // thrown for IPv6 addresses.
+ ASSERT_TRUE(first);
+ ASSERT_TRUE(second);
+ EXPECT_EQ(first->addr_, second->addr_);
+
+ // We need to compare the actual HWAddr objects, not pointers
+ EXPECT_TRUE(*first->hwaddr_ == *second->hwaddr_);
+
+ if (first->client_id_ && second->client_id_) {
+ EXPECT_TRUE(*first->client_id_ == *second->client_id_);
+ } else {
+ if (first->client_id_ && !second->client_id_) {
+
+ ADD_FAILURE() << "Client-id present in first lease ("
+ << first->client_id_->getClientId().size()
+ << " bytes), but missing in second.";
+ }
+ if (!first->client_id_ && second->client_id_) {
+ ADD_FAILURE() << "Client-id missing in first lease, but present in second ("
+ << second->client_id_->getClientId().size()
+ << " bytes).";
+ }
+ // else here would mean that both leases do not have client_id_
+ // which makes them equal in that regard. It is ok.
+ }
+ EXPECT_EQ(first->valid_lft_, second->valid_lft_);
+ EXPECT_EQ(first->cltt_, second->cltt_);
+ EXPECT_EQ(first->subnet_id_, second->subnet_id_);
+ EXPECT_EQ(first->fqdn_fwd_, second->fqdn_fwd_);
+ EXPECT_EQ(first->fqdn_rev_, second->fqdn_rev_);
+ EXPECT_EQ(first->hostname_, second->hostname_);
+ if (first->getContext()) {
+ EXPECT_TRUE(second->getContext());
+ if (second->getContext()) {
+ EXPECT_EQ(first->getContext()->str(), second->getContext()->str());
+ }
+ } else {
+ EXPECT_FALSE(second->getContext());
+ }
+}
+
+void
+detailCompareLease(const Lease6Ptr& first, const Lease6Ptr& second) {
+ ASSERT_TRUE(first);
+ ASSERT_TRUE(second);
+ EXPECT_EQ(first->type_, second->type_);
+
+ // Compare address strings. Comparison of address objects is not used, as
+ // odd things happen when they are different: the EXPECT_EQ macro appears to
+ // call the operator uint32_t() function, which causes an exception to be
+ // thrown for IPv6 addresses.
+ EXPECT_EQ(first->addr_, second->addr_);
+ EXPECT_EQ(first->prefixlen_, second->prefixlen_);
+ EXPECT_EQ(first->iaid_, second->iaid_);
+ ASSERT_TRUE(first->duid_);
+ ASSERT_TRUE(second->duid_);
+ EXPECT_TRUE(*first->duid_ == *second->duid_);
+ EXPECT_EQ(first->preferred_lft_, second->preferred_lft_);
+ EXPECT_EQ(first->valid_lft_, second->valid_lft_);
+ EXPECT_EQ(first->cltt_, second->cltt_);
+ EXPECT_EQ(first->subnet_id_, second->subnet_id_);
+ EXPECT_EQ(first->fqdn_fwd_, second->fqdn_fwd_);
+ EXPECT_EQ(first->fqdn_rev_, second->fqdn_rev_);
+ EXPECT_EQ(first->hostname_, second->hostname_);
+ if (first->getContext()) {
+ EXPECT_TRUE(second->getContext());
+ if (second->getContext()) {
+ EXPECT_EQ(first->getContext()->str(), second->getContext()->str());
+ }
+ } else {
+ EXPECT_FALSE(second->getContext());
+ }
+}
+
+int findLastSocketFd() {
+ int max_fd_number = getdtablesize();
+ int last_socket = -1;
+ struct stat stats;
+
+ // Iterate over the open fds
+ for (int fd = 0; fd <= max_fd_number; fd++ ) {
+ errno = 0;
+ fstat(fd, &stats);
+
+ if (errno == EBADF ) {
+ // Skip any that aren't open
+ continue;
+ }
+
+ // it's a socket, remember it
+ if (S_ISSOCK(stats.st_mode)) {
+ last_socket = fd;
+ }
+ }
+
+ return (last_socket);
+}
+
+FillFdHoles::FillFdHoles(int limit) : fds_() {
+ if (limit <= 0) {
+ return;
+ }
+ for (;;) {
+ int fd = open("/dev/null", O_RDWR, 0);
+ if (fd == -1) {
+ return;
+ }
+ if (fd < limit) {
+ fds_.push_front(fd);
+ } else {
+ static_cast<void>(close(fd));
+ return;
+ }
+ }
+}
+
+FillFdHoles::~FillFdHoles() {
+ while (!fds_.empty()) {
+ static_cast<void>(close(fds_.back()));
+ fds_.pop_back();
+ }
+}
+
+} // namespace test
+} // namespace dhcp
+} // namespace isc
diff --git a/src/lib/dhcpsrv/testutils/test_utils.h b/src/lib/dhcpsrv/testutils/test_utils.h
new file mode 100644
index 0000000..02af9ba
--- /dev/null
+++ b/src/lib/dhcpsrv/testutils/test_utils.h
@@ -0,0 +1,86 @@
+// Copyright (C) 2012-2020 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef LIBDHCPSRV_TEST_UTILS_H
+#define LIBDHCPSRV_TEST_UTILS_H
+
+#include <dhcpsrv/lease_mgr.h>
+#include <list>
+#include <vector>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+// @brief performs details comparison between two IPv6 leases
+//
+// @param first first lease to compare
+// @param second second lease to compare
+//
+// This method is intended to be run from gtest tests as it
+// uses gtest macros and possibly reports gtest failures.
+void
+detailCompareLease(const Lease6Ptr& first, const Lease6Ptr& second);
+
+// @brief performs details comparison between two IPv4 leases
+//
+// @param first first lease to compare
+// @param second second lease to compare
+//
+// This method is intended to be run from gtest tests as it
+// uses gtest macros and possibly reports gtest failures.
+void
+detailCompareLease(const Lease4Ptr& first, const Lease4Ptr& second);
+
+/// @brief Function that finds the last open socket descriptor
+///
+/// This function is used to attempt lost connectivity
+/// with backends, notably MySQL and Postgresql.
+///
+/// The theory being, that in a confined test environment the last
+/// such descriptor is the SQL client socket descriptor. This allows
+/// us to the close that descriptor and simulate a loss of server
+/// connectivity.
+///
+/// @return the descriptor of the last open socket or -1 if there
+/// are none.
+int findLastSocketFd();
+
+/// @brief RAII tool which fills holes in the file descriptor sequence
+///
+/// The @ref findLastSocketFd requires new socket descriptors are allocated
+/// after the last open socket descriptor so there is no hole i.e. a free
+/// file descriptor in the sequence.
+/// This tool detects and fills such holes. It uses the RAII idiom to avoid
+/// file descriptor leaks: the destructor called when the object goes out
+/// of scope closes all file descriptors which were opened by the constructor.
+class FillFdHoles {
+public:
+ /// @brief Constructor
+ ///
+ /// Holes between 0 and the specified limit will be filled by opening
+ /// the null device. Typically the limit argument is the result of
+ /// a previous call to @ref findLastSocketFd. Note if the limit is
+ /// 0 or negative the constructor returns doing nothing.
+ ///
+ /// @param limit Holes will be filled up to this limit
+ FillFdHoles(int limit);
+
+ /// @brief Destructor
+ ///
+ /// The destructor closes members of the list
+ ~FillFdHoles();
+
+private:
+ /// @brief The list of holes
+ std::list<int> fds_;
+};
+
+} // namespace test
+} // namespace dhcp
+} // namespace isc
+
+#endif