diff options
Diffstat (limited to 'src/lib/dhcpsrv/testutils')
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 |