summaryrefslogtreecommitdiffstats
path: root/src/bin/admin
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 12:15:43 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 12:15:43 +0000
commitf5f56e1a1c4d9e9496fcb9d81131066a964ccd23 (patch)
tree49e44c6f87febed37efb953ab5485aa49f6481a7 /src/bin/admin
parentInitial commit. (diff)
downloadisc-kea-upstream.tar.xz
isc-kea-upstream.zip
Adding upstream version 2.4.1.upstream/2.4.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/bin/admin')
-rw-r--r--src/bin/admin/Makefile.am9
-rw-r--r--src/bin/admin/Makefile.in834
-rw-r--r--src/bin/admin/admin-utils.sh.in224
-rw-r--r--src/bin/admin/kea-admin.in1090
-rw-r--r--src/bin/admin/tests/Makefile.am32
-rw-r--r--src/bin/admin/tests/Makefile.in880
-rw-r--r--src/bin/admin/tests/admin_tests.sh.in147
-rw-r--r--src/bin/admin/tests/data/Makefile.am3
-rw-r--r--src/bin/admin/tests/data/Makefile.in551
-rw-r--r--src/bin/admin/tests/data/lease4_dump_test.reference.csv4
-rw-r--r--src/bin/admin/tests/data/lease6_dump_test.reference.csv4
-rw-r--r--src/bin/admin/tests/dhcpdb_create_1.0.mysql131
-rw-r--r--src/bin/admin/tests/dhcpdb_create_1.0.pgsql122
-rw-r--r--src/bin/admin/tests/memfile_tests.sh.in239
-rw-r--r--src/bin/admin/tests/mysql_tests.sh.in2892
-rw-r--r--src/bin/admin/tests/pgsql_tests.sh.in2068
16 files changed, 9230 insertions, 0 deletions
diff --git a/src/bin/admin/Makefile.am b/src/bin/admin/Makefile.am
new file mode 100644
index 0000000..94b88dd
--- /dev/null
+++ b/src/bin/admin/Makefile.am
@@ -0,0 +1,9 @@
+SUBDIRS = . tests
+
+# Install kea-admin in sbin.
+sbin_SCRIPTS = kea-admin
+
+DISTCLEANFILES = $(sbin_SCRIPTS)
+
+adminscriptsdir = ${datarootdir}/${PACKAGE_NAME}/scripts
+adminscripts_DATA = admin-utils.sh
diff --git a/src/bin/admin/Makefile.in b/src/bin/admin/Makefile.in
new file mode 100644
index 0000000..7503e6e
--- /dev/null
+++ b/src/bin/admin/Makefile.in
@@ -0,0 +1,834 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/bin/admin
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.m4 \
+ $(top_srcdir)/m4macros/ax_cpp20.m4 \
+ $(top_srcdir)/m4macros/ax_crypto.m4 \
+ $(top_srcdir)/m4macros/ax_find_library.m4 \
+ $(top_srcdir)/m4macros/ax_gssapi.m4 \
+ $(top_srcdir)/m4macros/ax_gtest.m4 \
+ $(top_srcdir)/m4macros/ax_isc_rpath.m4 \
+ $(top_srcdir)/m4macros/ax_netconf.m4 \
+ $(top_srcdir)/m4macros/libtool.m4 \
+ $(top_srcdir)/m4macros/ltoptions.m4 \
+ $(top_srcdir)/m4macros/ltsugar.m4 \
+ $(top_srcdir)/m4macros/ltversion.m4 \
+ $(top_srcdir)/m4macros/lt~obsolete.m4 \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES = admin-utils.sh kea-admin
+CONFIG_CLEAN_VPATH_FILES =
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+am__installdirs = "$(DESTDIR)$(sbindir)" \
+ "$(DESTDIR)$(adminscriptsdir)"
+SCRIPTS = $(sbin_SCRIPTS)
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+SOURCES =
+DIST_SOURCES =
+RECURSIVE_TARGETS = all-recursive check-recursive cscopelist-recursive \
+ ctags-recursive dvi-recursive html-recursive info-recursive \
+ install-data-recursive install-dvi-recursive \
+ install-exec-recursive install-html-recursive \
+ install-info-recursive install-pdf-recursive \
+ install-ps-recursive install-recursive installcheck-recursive \
+ installdirs-recursive pdf-recursive ps-recursive \
+ tags-recursive uninstall-recursive
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+DATA = $(adminscripts_DATA)
+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 $(srcdir)/admin-utils.sh.in \
+ $(srcdir)/kea-admin.in
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+am__relativize = \
+ dir0=`pwd`; \
+ sed_first='s,^\([^/]*\)/.*$$,\1,'; \
+ sed_rest='s,^[^/]*/*,,'; \
+ sed_last='s,^.*/\([^/]*\)$$,\1,'; \
+ sed_butlast='s,/*[^/]*$$,,'; \
+ while test -n "$$dir1"; do \
+ first=`echo "$$dir1" | sed -e "$$sed_first"`; \
+ if test "$$first" != "."; then \
+ if test "$$first" = ".."; then \
+ dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \
+ dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \
+ else \
+ first2=`echo "$$dir2" | sed -e "$$sed_first"`; \
+ if test "$$first2" = "$$first"; then \
+ dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \
+ else \
+ dir2="../$$dir2"; \
+ fi; \
+ dir0="$$dir0"/"$$first"; \
+ fi; \
+ fi; \
+ dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \
+ done; \
+ reldir="$$dir2"
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+ASCIIDOC = @ASCIIDOC@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BOOST_INCLUDES = @BOOST_INCLUDES@
+BOOST_LIBS = @BOOST_LIBS@
+BOTAN_TOOL = @BOTAN_TOOL@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CONTRIB_DIR = @CONTRIB_DIR@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CRYPTO_CFLAGS = @CRYPTO_CFLAGS@
+CRYPTO_INCLUDES = @CRYPTO_INCLUDES@
+CRYPTO_LDFLAGS = @CRYPTO_LDFLAGS@
+CRYPTO_LIBS = @CRYPTO_LIBS@
+CRYPTO_PACKAGE = @CRYPTO_PACKAGE@
+CRYPTO_RPATH = @CRYPTO_RPATH@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DISTCHECK_BOOST_CONFIGURE_FLAG = @DISTCHECK_BOOST_CONFIGURE_FLAG@
+DISTCHECK_CONTRIB_CONFIGURE_FLAG = @DISTCHECK_CONTRIB_CONFIGURE_FLAG@
+DISTCHECK_CRYPTO_CONFIGURE_FLAG = @DISTCHECK_CRYPTO_CONFIGURE_FLAG@
+DISTCHECK_GSSAPI_CONFIGURE_FLAG = @DISTCHECK_GSSAPI_CONFIGURE_FLAG@
+DISTCHECK_GTEST_CONFIGURE_FLAG = @DISTCHECK_GTEST_CONFIGURE_FLAG@
+DISTCHECK_KEA_SHELL_CONFIGURE_FLAG = @DISTCHECK_KEA_SHELL_CONFIGURE_FLAG@
+DISTCHECK_LIBYANGCPP_CONFIGURE_FLAG = @DISTCHECK_LIBYANGCPP_CONFIGURE_FLAG@
+DISTCHECK_LIBYANG_CONFIGURE_FLAG = @DISTCHECK_LIBYANG_CONFIGURE_FLAG@
+DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG = @DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG@
+DISTCHECK_MYSQL_CONFIGURE_FLAG = @DISTCHECK_MYSQL_CONFIGURE_FLAG@
+DISTCHECK_PERFDHCP_CONFIGURE_FLAG = @DISTCHECK_PERFDHCP_CONFIGURE_FLAG@
+DISTCHECK_PGSQL_CONFIGURE_FLAG = @DISTCHECK_PGSQL_CONFIGURE_FLAG@
+DISTCHECK_PREMIUM_CONFIGURE_FLAG = @DISTCHECK_PREMIUM_CONFIGURE_FLAG@
+DISTCHECK_SYSREPOCPP_CONFIGURE_FLAG = @DISTCHECK_SYSREPOCPP_CONFIGURE_FLAG@
+DISTCHECK_SYSREPO_CONFIGURE_FLAG = @DISTCHECK_SYSREPO_CONFIGURE_FLAG@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GENHTML = @GENHTML@
+GREP = @GREP@
+GSSAPI_CFLAGS = @GSSAPI_CFLAGS@
+GSSAPI_LIBS = @GSSAPI_LIBS@
+GTEST_CONFIG = @GTEST_CONFIG@
+GTEST_INCLUDES = @GTEST_INCLUDES@
+GTEST_LDADD = @GTEST_LDADD@
+GTEST_LDFLAGS = @GTEST_LDFLAGS@
+GTEST_SOURCE = @GTEST_SOURCE@
+HAVE_NETCONF = @HAVE_NETCONF@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+KEA_CXXFLAGS = @KEA_CXXFLAGS@
+KEA_SRCID = @KEA_SRCID@
+KRB5_CONFIG = @KRB5_CONFIG@
+LCOV = @LCOV@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LEX = @LEX@
+LEXLIB = @LEXLIB@
+LEX_OUTPUT_ROOT = @LEX_OUTPUT_ROOT@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIBYANGCPP_CPPFLAGS = @LIBYANGCPP_CPPFLAGS@
+LIBYANGCPP_INCLUDEDIR = @LIBYANGCPP_INCLUDEDIR@
+LIBYANGCPP_LIBS = @LIBYANGCPP_LIBS@
+LIBYANGCPP_PREFIX = @LIBYANGCPP_PREFIX@
+LIBYANGCPP_VERSION = @LIBYANGCPP_VERSION@
+LIBYANG_CPPFLAGS = @LIBYANG_CPPFLAGS@
+LIBYANG_INCLUDEDIR = @LIBYANG_INCLUDEDIR@
+LIBYANG_LIBS = @LIBYANG_LIBS@
+LIBYANG_PREFIX = @LIBYANG_PREFIX@
+LIBYANG_VERSION = @LIBYANG_VERSION@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LOG4CPLUS_INCLUDES = @LOG4CPLUS_INCLUDES@
+LOG4CPLUS_LIBS = @LOG4CPLUS_LIBS@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MYSQL_CPPFLAGS = @MYSQL_CPPFLAGS@
+MYSQL_LIBS = @MYSQL_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PACKAGE_VERSION_TYPE = @PACKAGE_VERSION_TYPE@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PDFLATEX = @PDFLATEX@
+PERL = @PERL@
+PGSQL_CPPFLAGS = @PGSQL_CPPFLAGS@
+PGSQL_LIBS = @PGSQL_LIBS@
+PKGPYTHONDIR = @PKGPYTHONDIR@
+PKG_CONFIG = @PKG_CONFIG@
+PLANTUML = @PLANTUML@
+PREMIUM_DIR = @PREMIUM_DIR@
+PYTHON = @PYTHON@
+PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@
+PYTHON_PLATFORM = @PYTHON_PLATFORM@
+PYTHON_PREFIX = @PYTHON_PREFIX@
+PYTHON_VERSION = @PYTHON_VERSION@
+RANLIB = @RANLIB@
+SED = @SED@
+SEP = @SEP@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SPHINXBUILD = @SPHINXBUILD@
+SRPD_PLUGINS_PATH = @SRPD_PLUGINS_PATH@
+SR_PLUGINS_PATH = @SR_PLUGINS_PATH@
+SR_REPO_PATH = @SR_REPO_PATH@
+STRIP = @STRIP@
+SYSREPOCPP_CPPFLAGS = @SYSREPOCPP_CPPFLAGS@
+SYSREPOCPP_INCLUDEDIR = @SYSREPOCPP_INCLUDEDIR@
+SYSREPOCPP_LIBS = @SYSREPOCPP_LIBS@
+SYSREPOCPP_PREFIX = @SYSREPOCPP_PREFIX@
+SYSREPOCPP_VERSION = @SYSREPOCPP_VERSION@
+SYSREPO_CPPFLAGS = @SYSREPO_CPPFLAGS@
+SYSREPO_INCLUDEDIR = @SYSREPO_INCLUDEDIR@
+SYSREPO_LIBS = @SYSREPO_LIBS@
+SYSREPO_PREFIX = @SYSREPO_PREFIX@
+SYSREPO_VERSION = @SYSREPO_VERSION@
+USE_LCOV = @USE_LCOV@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+WARNING_GCC_44_STRICT_ALIASING_CFLAG = @WARNING_GCC_44_STRICT_ALIASING_CFLAG@
+YACC = @YACC@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+pkgpyexecdir = @pkgpyexecdir@
+pkgpythondir = @pkgpythondir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+pyexecdir = @pyexecdir@
+pythondir = @pythondir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+SUBDIRS = . tests
+
+# Install kea-admin in sbin.
+sbin_SCRIPTS = kea-admin
+DISTCLEANFILES = $(sbin_SCRIPTS)
+adminscriptsdir = ${datarootdir}/${PACKAGE_NAME}/scripts
+adminscripts_DATA = admin-utils.sh
+all: all-recursive
+
+.SUFFIXES:
+$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/bin/admin/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/bin/admin/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):
+admin-utils.sh: $(top_builddir)/config.status $(srcdir)/admin-utils.sh.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+kea-admin: $(top_builddir)/config.status $(srcdir)/kea-admin.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+install-sbinSCRIPTS: $(sbin_SCRIPTS)
+ @$(NORMAL_INSTALL)
+ @list='$(sbin_SCRIPTS)'; test -n "$(sbindir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(sbindir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(sbindir)" || exit 1; \
+ fi; \
+ for p in $$list; do \
+ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+ if test -f "$$d$$p"; then echo "$$d$$p"; echo "$$p"; else :; fi; \
+ done | \
+ sed -e 'p;s,.*/,,;n' \
+ -e 'h;s|.*|.|' \
+ -e 'p;x;s,.*/,,;$(transform)' | sed 'N;N;N;s,\n, ,g' | \
+ $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1; } \
+ { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \
+ if ($$2 == $$4) { files[d] = files[d] " " $$1; \
+ if (++n[d] == $(am__install_max)) { \
+ print "f", d, files[d]; n[d] = 0; files[d] = "" } } \
+ else { print "f", d "/" $$4, $$1 } } \
+ END { for (d in files) print "f", d, files[d] }' | \
+ while read type dir files; do \
+ if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \
+ test -z "$$files" || { \
+ echo " $(INSTALL_SCRIPT) $$files '$(DESTDIR)$(sbindir)$$dir'"; \
+ $(INSTALL_SCRIPT) $$files "$(DESTDIR)$(sbindir)$$dir" || exit $$?; \
+ } \
+ ; done
+
+uninstall-sbinSCRIPTS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(sbin_SCRIPTS)'; test -n "$(sbindir)" || exit 0; \
+ files=`for p in $$list; do echo "$$p"; done | \
+ sed -e 's,.*/,,;$(transform)'`; \
+ dir='$(DESTDIR)$(sbindir)'; $(am__uninstall_files_from_dir)
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+install-adminscriptsDATA: $(adminscripts_DATA)
+ @$(NORMAL_INSTALL)
+ @list='$(adminscripts_DATA)'; test -n "$(adminscriptsdir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(adminscriptsdir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(adminscriptsdir)" || exit 1; \
+ fi; \
+ for p in $$list; do \
+ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+ echo "$$d$$p"; \
+ done | $(am__base_list) | \
+ while read files; do \
+ echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(adminscriptsdir)'"; \
+ $(INSTALL_DATA) $$files "$(DESTDIR)$(adminscriptsdir)" || exit $$?; \
+ done
+
+uninstall-adminscriptsDATA:
+ @$(NORMAL_UNINSTALL)
+ @list='$(adminscripts_DATA)'; test -n "$(adminscriptsdir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(adminscriptsdir)'; $(am__uninstall_files_from_dir)
+
+# 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 $(SCRIPTS) $(DATA)
+installdirs: installdirs-recursive
+installdirs-am:
+ for dir in "$(DESTDIR)$(sbindir)" "$(DESTDIR)$(adminscriptsdir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-recursive
+install-exec: install-exec-recursive
+install-data: install-data-recursive
+uninstall: uninstall-recursive
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-recursive
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+ -test -z "$(DISTCLEANFILES)" || rm -f $(DISTCLEANFILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-recursive
+
+clean-am: clean-generic clean-libtool mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -f Makefile
+distclean-am: clean-am distclean-generic distclean-tags
+
+dvi: dvi-recursive
+
+dvi-am:
+
+html: html-recursive
+
+html-am:
+
+info: info-recursive
+
+info-am:
+
+install-data-am: install-adminscriptsDATA
+
+install-dvi: install-dvi-recursive
+
+install-dvi-am:
+
+install-exec-am: install-sbinSCRIPTS
+
+install-html: install-html-recursive
+
+install-html-am:
+
+install-info: install-info-recursive
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-recursive
+
+install-pdf-am:
+
+install-ps: install-ps-recursive
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-recursive
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-recursive
+
+mostlyclean-am: mostlyclean-generic mostlyclean-libtool
+
+pdf: pdf-recursive
+
+pdf-am:
+
+ps: ps-recursive
+
+ps-am:
+
+uninstall-am: uninstall-adminscriptsDATA uninstall-sbinSCRIPTS
+
+.MAKE: $(am__recursive_targets) install-am install-strip
+
+.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am check \
+ check-am clean clean-generic clean-libtool cscopelist-am ctags \
+ ctags-am distclean distclean-generic distclean-libtool \
+ distclean-tags distdir dvi dvi-am html html-am info info-am \
+ install install-adminscriptsDATA 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-sbinSCRIPTS install-strip \
+ installcheck installcheck-am installdirs installdirs-am \
+ maintainer-clean maintainer-clean-generic mostlyclean \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-adminscriptsDATA uninstall-am \
+ uninstall-sbinSCRIPTS
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/bin/admin/admin-utils.sh.in b/src/bin/admin/admin-utils.sh.in
new file mode 100644
index 0000000..62fdc6f
--- /dev/null
+++ b/src/bin/admin/admin-utils.sh.in
@@ -0,0 +1,224 @@
+#!/bin/sh
+
+# Copyright (C) 2014-2023 Internet Systems Consortium, Inc. ("ISC")
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# This is an utility script that is being included by other scripts.
+
+# shellcheck disable=SC2086
+# SC2086: Double quote to prevent globbing and word splitting.
+# Reason for disable: explicitly don't quote db_port_full_parameter so it
+# doesn't expand to empty string if it is not set and explicitly don't quote
+# extra_arguments so it is considered multiple arguments instead of one.
+
+# shellcheck disable=SC2154
+# SC2154: ... is referenced but not assigned.
+# Some variables are assigned in kea-admin.
+
+# Exit with error if commands exit with non-zero and if undefined variables are
+# used.
+set -eu
+
+# These are the default parameters. They will likely not work in any
+# specific deployment. Also used in unit tests.
+db_host='localhost'
+db_user='keatest'
+db_password='keatest'
+db_name='keatest'
+extra_arguments=
+
+# Runs all the given arguments as a single command. Maintains quoting. Places
+# output in ${OUTPUT} and exit code in ${EXIT_CODE}. Does not support pipes and
+# redirections. Support for them could be added through eval and single
+# parameter assignment, but eval is not recommended.
+run_command() {
+ if test -n "${DEBUG+x}"; then
+ printf '%s\n' "${*}" >&2
+ fi
+ set +e
+ OUTPUT=$("${@}")
+ EXIT_CODE=${?}
+ set -e
+}
+
+mysql_sanity_checks() {
+ # https://bugs.mysql.com/bug.php?id=55796#c321360
+ # https://dev.mysql.com/doc/refman/8.0/en/connecting.html
+ # On Unix, MySQL programs treat the host name localhost specially, in a way
+ # that is likely different from what you expect compared to other
+ # network-based programs: the client connects using a Unix socket file.
+ if test -n "${db_port+x}" && \
+ { test "${db_host}" = 'localhost' || \
+ test "${db_host}" = '127.0.0.1'; }; then
+ printf 'Warning: the MySQL client uses the default unix socket ' >&2
+ printf 'instead of TCP port %s ' "${db_port}" >&2
+ printf 'when connecting to localhost. Continuing...\n' >&2
+ fi
+}
+
+# Executes a given MySQL statement.
+# mysql_execute SQL_QUERY PARAM1 PARAM2 .. PARAMN - Additional parameters
+# may be specified. They are passed directly to mysql.
+#
+# It returns the mysql command exit status to the caller as $?
+mysql_execute() {
+ QUERY=$1
+ shift
+
+ mysql_sanity_checks
+
+ if [ $# -gt 0 ]; then
+ mysql -N -B --host="${db_host}" ${db_port_full_parameter-} \
+ --database="${db_name}" --user="${db_user}" \
+ --password="${db_password}" ${extra_arguments} \
+ --execute "${QUERY}" "${@}"
+ else
+ mysql -N -B --host="${db_host}" ${db_port_full_parameter-} \
+ --database="${db_name}" --user="${db_user}" \
+ --password="${db_password}" ${extra_arguments} \
+ --execute "${QUERY}"
+ fi
+}
+
+# Submits SQL in a given file to MySQL.
+# pgsql_execute SQL_FILE PARAM1 PARAM2 .. PARAMN - Additional parameters
+# may be specified. They are passed directly to pgsql.
+#
+# It returns the mysql command exit status to the caller as $?
+mysql_execute_script() {
+ file=$1
+ shift
+
+ mysql_sanity_checks
+
+ if [ $# -gt 0 ]; then
+ mysql -N -B --host="${db_host}" ${db_port_full_parameter-} \
+ --database="${db_name}" --user="${db_user}" \
+ --password="${db_password}" ${extra_arguments} "${@}" < "${file}"
+ else
+ mysql -N -B --host="${db_host}" ${db_port_full_parameter-} \
+ --database="${db_name}" --user="${db_user}" \
+ --password="${db_password}" ${extra_arguments} < "${file}"
+ fi
+}
+
+mysql_version() {
+ mysql_execute "SELECT CONCAT_WS('.', version, minor) FROM schema_version" "$@"
+}
+
+checked_mysql_version() {
+ run_command \
+ mysql_execute "SELECT CONCAT_WS('.', version, minor) FROM schema_version" "$@"
+
+ if [ "${EXIT_CODE}" -ne 0 ]
+ then
+ printf "Failed to get schema version, mysql status %s\n" "${EXIT_CODE}"
+ fi
+
+ printf '%s\n' "${OUTPUT}"
+ return "${EXIT_CODE}"
+}
+
+# Submits given SQL text to PostgreSQL
+# pgsql_execute SQL_QUERY PARAM1 PARAM2 .. PARAMN - Additional parameters
+# may be specified. They are passed directly to pgsql.
+#
+# It returns the pgsql command exit status to the caller as $?
+pgsql_execute() {
+ QUERY=$1
+ shift
+
+ # Prioritize externally set PGPASSWORD. wipe_data.sh sets it for example.
+ if test -z "${PGPASSWORD-}"; then
+ PGPASSWORD="${db_password}"
+ fi
+ export PGPASSWORD
+
+ if [ $# -gt 0 ]; then
+ printf '%s' "${QUERY}" | psql --set ON_ERROR_STOP=1 -A -t \
+ -h "${db_host}" ${db_port_full_parameter-} -q -U "${db_user}" \
+ -d "${db_name}" ${extra_arguments} "${@}"
+ else
+ printf '%s' "${QUERY}" | psql --set ON_ERROR_STOP=1 -A -t \
+ -h "${db_host}" ${db_port_full_parameter-} -q -U "${db_user}" \
+ -d "${db_name}" ${extra_arguments}
+ fi
+}
+
+# Submits SQL in a given file to PostgreSQL
+# pgsql_execute SQL_FILE PARAM1 PARAM2 .. PARAMN - Additional parameters
+# may be specified. They are passed directly to pgsql.
+#
+# It returns the pgsql command exit status to the caller as $?
+pgsql_execute_script() {
+ file=$1
+ shift
+
+ # Prioritize externally set PGPASSWORD. wipe_data.sh sets it for example.
+ if test -z "${PGPASSWORD-}"; then
+ PGPASSWORD="${db_password}"
+ fi
+ export PGPASSWORD
+
+ if [ $# -gt 0 ]; then
+ psql --set ON_ERROR_STOP=1 -A -t -h "${db_host}" \
+ ${db_port_full_parameter-} -q -U "${db_user}" -d "${db_name}" \
+ ${extra_arguments} -f "${file}" "${@}"
+ else
+ psql --set ON_ERROR_STOP=1 -A -t -h "${db_host}" \
+ ${db_port_full_parameter-} -q -U "${db_user}" -d "${db_name}" \
+ ${extra_arguments} -f "${file}"
+ fi
+}
+
+pgsql_version() {
+ pgsql_execute "SELECT version || '.' || minor FROM schema_version" "$@"
+}
+
+checked_pgsql_version() {
+ run_command \
+ pgsql_execute "SELECT version || '.' || minor FROM schema_version" "$@"
+
+ if [ "${EXIT_CODE}" -ne 0 ]
+ then
+ printf "Failed to get schema version, pgsql status %s\n" "${EXIT_CODE}"
+ fi
+
+ printf '%s\n' "${OUTPUT}"
+ return "${EXIT_CODE}"
+}
+
+# recount IPv4 leases from scratch
+_RECOUNT4_QUERY=\
+"
+START TRANSACTION; \
+DELETE FROM lease4_stat; \
+INSERT INTO lease4_stat (subnet_id, state, leases) \
+ SELECT subnet_id, state, COUNT(*) \
+ FROM lease4 WHERE state = 0 OR state = 1 \
+ GROUP BY subnet_id, state; \
+DELETE FROM lease4_pool_stat; \
+INSERT INTO lease4_pool_stat (subnet_id, pool_id, state, leases) \
+ SELECT subnet_id, pool_id, state, count(*) FROM lease4 \
+ WHERE state = 0 OR state = 1 GROUP BY subnet_id, pool_id, state; \
+COMMIT;"
+export _RECOUNT4_QUERY
+
+# recount IPv6 leases from scratch
+_RECOUNT6_QUERY=\
+"
+START TRANSACTION; \
+DELETE FROM lease6_stat; \
+INSERT INTO lease6_stat (subnet_id, lease_type, state, leases) \
+ SELECT subnet_id, lease_type, state, COUNT(*) \
+ FROM lease6 WHERE state = 0 OR state = 1 \
+ GROUP BY subnet_id, lease_type, state; \
+DELETE FROM lease6_pool_stat; \
+INSERT INTO lease6_pool_stat (subnet_id, pool_id, lease_type, state, leases) \
+ SELECT subnet_id, pool_id, lease_type, state, count(*) FROM lease6 \
+ WHERE state = 0 OR state = 1 GROUP BY subnet_id, pool_id, lease_type, state; \
+COMMIT;"
+export _RECOUNT6_QUERY
diff --git a/src/bin/admin/kea-admin.in b/src/bin/admin/kea-admin.in
new file mode 100644
index 0000000..034a0ee
--- /dev/null
+++ b/src/bin/admin/kea-admin.in
@@ -0,0 +1,1090 @@
+#!/bin/sh
+
+# Copyright (C) 2014-2023 Internet Systems Consortium, Inc. ("ISC")
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# This is kea-admin script that conducts administrative tasks on the Kea
+# installation. Currently supported operations are:
+#
+# - database init
+# - database version check
+# - database version upgrade
+# - lease database dump
+# - lease upload to the database
+# - lease database recount
+
+# shellcheck disable=SC1091
+# SC1091: Not following: ... was not specified as input (see shellcheck -x).
+
+# shellcheck disable=SC2039
+# SC2039: In POSIX sh, 'local' is undefined.
+
+# shellcheck disable=SC2086
+# SC2086: Double quote to prevent globbing and word splitting.
+# Reason for disable: explicitly don't quote extra_arguments so it is
+# considered multiple arguments instead of one.
+
+# Exit with error if commands exit with non-zero and if undefined variables are
+# used.
+set -eu
+
+# Shell ${variables} derived from autoconf @variables@. Some depend on others, so mind the order.
+prefix="@prefix@"
+export prefix
+exec_prefix="@exec_prefix@"
+export exec_prefix
+SCRIPTS_DIR_DEFAULT="@datarootdir@/@PACKAGE@/scripts"
+scripts_dir="${SCRIPTS_DIR_DEFAULT}"
+VERSION="@PACKAGE_VERSION@"
+
+assume_yes=0
+
+# lease dump parameters
+dhcp_version=0
+dump_file=""
+dump_qry=""
+
+# Include the installed admin-utils.sh if available. Fallback to sources otherwise.
+if test -f "@datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh"; then
+ . "@datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh"
+else
+ . "@abs_top_builddir@/src/bin/admin/admin-utils.sh"
+fi
+
+# Find the installed kea-lfc if available. Fallback to sources otherwise.
+if test -x "@sbindir@/kea-lfc"; then
+ kea_lfc="@sbindir@/kea-lfc"
+else
+ kea_lfc="@abs_top_builddir@/src/bin/lfc/kea-lfc"
+fi
+
+# Prints out usage version.
+usage() {
+ printf \
+'
+kea-admin %s
+
+This is a kea-admin script that conducts administrative tasks on
+the Kea installation.
+
+Usage: %s COMMAND BACKEND [parameters]
+
+COMMAND: Currently supported operations are:
+
+ - db-init: Initializes new database. Useful for first time installation.
+ - db-version: Checks version of the existing database schema. Useful
+ - for checking database version when preparing for an upgrade.
+ - db-upgrade: Upgrades your database schema.
+ - lease-dump: Dumps current leases to a memfile-ready CSV file.
+ - lease-upload: Uploads leases from a CSV file to the database.
+ - stats-recount: Recounts lease statistics.
+
+BACKEND - one of the supported backends: memfile|mysql|pgsql
+
+PARAMETERS: Parameters are optional in general, but may be required
+ for specific operations.
+ -h or --host hostname - specifies a hostname of a database to connect to
+ -P or --port port - specifies the TCP port to use for the database connection
+ -u or --user name - specifies username when connecting to a database
+ -p or --password [password] - specifies a password for the database connection;
+ if omitted from the command line,
+ then the user will be prompted for a password
+ -n or --name database - specifies a database name to connect to
+ -d or --directory - path to upgrade scripts (default: %s)
+ -v or --version - print kea-admin version and quit.
+ -x or --extra - specifies extra argument(s) to pass to the database command
+
+ Parameters specific to lease-dump, lease-upload:
+ -4 to dump IPv4 leases to file
+ -6 to dump IPv6 leases to file
+ -i or --input to specify the name of file from which leases will be uploaded
+ -o or --output to specify the name of file to which leases will be dumped
+ -y or --yes - assume yes on overwriting temporary files
+' "${VERSION}" "${0}" "${SCRIPTS_DIR_DEFAULT}"
+}
+
+### Logging functions ###
+
+# Logs message at the error level.
+# Takes one parameter that is printed as is.
+log_error() {
+ printf "ERROR/kea-admin: %s\n" "${1}"
+}
+
+# Logs message at the warning level.
+# Takes one parameter that is printed as is.
+log_warning() {
+ printf "WARNING/kea-admin: %s\n" "${1}"
+}
+
+# Logs message at the info level.
+# Takes one parameter that is printed as is.
+log_info() {
+ printf "INFO/kea-admin: %s\n" "${1}"
+}
+
+### Convenience functions ###
+
+# Checks if the value is in the list. An example usage of this function
+# is to determine whether the kea-admin command belongs to the list of
+# supported commands.
+is_in_list() {
+ local member="${1-}" # Value to be checked
+ local list="${2-}" # Comma separated list of items
+ _inlist=0 # Return value: 0 if not in list, 1 otherwise.
+ if [ -z "${member}" ]; then
+ log_error "missing member (need to specify a string as first param)"
+ fi
+ # Iterate over all items on the list and compare with the member.
+ # If they match, return, otherwise log error and exit.
+ for item in ${list}
+ do
+ if [ "${item}" = "${member}" ]; then
+ _inlist=1
+ return
+ fi
+ done
+}
+
+### Functions that implement database initialization commands
+
+memfile_init() {
+ # Useless as Kea converts CSV versions at startup.
+ log_error "NOT IMPLEMENTED"
+ exit 1
+}
+
+# Validates that the MySQL db_users's permissions are sufficient to
+# create the schema.
+mysql_can_create() {
+
+ # Let's grab the version for possible debugging issues. It also
+ # determines basic functional access to db.
+ run_command \
+ mysql_execute "select @@global.version;"
+ if [ "${EXIT_CODE}" -ne 0 ]
+ then
+ log_error "mysql_can_create: get MySQL version failed, mysql status = ${EXIT_CODE}"
+ exit 1
+ fi
+
+ # shellcheck disable=SC2153
+ # SC2153: Possible misspelling: ... may not be assigned, but ... is.
+ # Reason for disable: OUTPUT is assigned in run_command.
+ printf "MySQL Version is: %s\n" "${OUTPUT}"
+
+ # SQL to drop our test table and trigger
+ cleanup_sql="DROP TABLE IF EXISTS kea_dummy_table; DROP PROCEDURE IF EXISTS kea_dummy_trigger;"
+
+ # SQL to create our test table
+ table_sql="CREATE TABLE kea_dummy_table(dummy INT UNSIGNED PRIMARY KEY NOT NULL);"
+
+ # SQL to create our test trigger
+ trigger_sql="\
+CREATE TRIGGER kea_dummy_trigger BEFORE insert ON kea_dummy_table FOR EACH ROW\n \
+BEGIN\n \
+END;"
+
+ # Let's clean up just in case.
+ run_command \
+ mysql_execute "$cleanup_sql"
+ if [ "${EXIT_CODE}" -ne 0 ]
+ then
+ log_error "mysql_can_create cannot run pre cleanup, mysql status = ${EXIT_CODE}"
+ exit 1;
+ fi
+
+ # Now make the dummy table.
+ perms_ok=1
+ run_command \
+ mysql_execute "$table_sql"
+ if [ "${EXIT_CODE}" -ne 0 ]
+ then
+ log_error "mysql_can_create cannot create table, check user permissions, mysql status = ${EXIT_CODE}"
+ perms_ok=0;
+ else
+ # Now attempt to make trigger
+ run_command \
+ mysql_execute "$trigger_sql"
+ if [ "${EXIT_CODE}" -ne 0 ]
+ then
+ log_error "mysql_can_create cannot trigger, check user permissions, mysql status = ${EXIT_CODE}"
+ perms_ok=0;
+ fi
+ fi
+
+ # Try to cleanup no matter what happened above
+ run_command \
+ mysql_execute "$cleanup_sql"
+ if [ "${EXIT_CODE}" -ne 0 ]
+ then
+ log_error "mysql_can_create cannot run post cleanup, mysql status = ${EXIT_CODE}"
+ exit 1;
+ fi
+
+ if [ $perms_ok -ne 1 ]
+ then
+ log_error "Create failed, the user, $db_user, has insufficient privileges."
+ exit 1;
+ fi
+}
+
+# Initializes a new, empty MySQL database.
+# It essentially calls scripts/mysql/dhcpdb_create.mysql script, with
+# some extra sanity checks. It will refuse to use it if there are any
+# existing tables. It's better safe than sorry.
+mysql_init() {
+ printf 'Checking if there is a database initialized already...\n'
+
+ # Let's try to count the number of tables. Anything above 0 means that there
+ # is some database in place. If there is anything, we abort. Note that
+ # mysql may spit out connection or access errors to stderr, we ignore those.
+ # We should not hide them as they may give hints to user what is wrong with
+ # his setup.
+ run_command \
+ mysql_execute "SHOW TABLES;"
+ if [ "${EXIT_CODE}" -ne 0 ]
+ then
+ log_error "mysql_init table query failed, mysql status = ${EXIT_CODE}"
+ exit 1
+ fi
+
+ count=$(printf '%s' "${OUTPUT}" | wc -w)
+ if [ "${count}" -gt 0 ]; then
+ # Let's start with a new line. mysql could have printed something out.
+ printf '\n'
+ log_error "Expected empty database ${db_name}. Aborting, the following tables are present:
+ ${OUTPUT}"
+ exit 1
+ fi
+
+ # Beginning with MySQL 8.0, the db user needs additional settings or SUPER
+ # privileges to create triggers and or functions. Call mysql_can_create to find
+ # out if we're good to go. If not, it will exit.
+ printf "Verifying create permissions for %s\n" "$db_user"
+ mysql_can_create
+
+ printf "Initializing database using script %s\n" $scripts_dir/mysql/dhcpdb_create.mysql
+ mysql -B --host="${db_host}" --user="${db_user}" \
+ --password="${db_password}" \
+ "${db_name}" ${extra_arguments} < "${scripts_dir}/mysql/dhcpdb_create.mysql"
+
+ printf "mysql returned status code %s\n" "${EXIT_CODE}"
+
+ if [ "${EXIT_CODE}" -eq 0 ]; then
+ printf "Database version reported after initialization: "
+ checked_mysql_version
+ printf '\n'
+ fi
+
+ exit "${EXIT_CODE}"
+}
+
+pgsql_init() {
+ printf 'Checking if there is a database initialized already...\n'
+
+ # Let's try to count the number of tables. Anything above 0 means that there
+ # is some database in place. If there is anything, we abort.
+ run_command \
+ pgsql_execute "\d"
+ if [ "${EXIT_CODE}" -ne 0 ]; then
+ log_error "pgsql_init: table query failed, status code: ${EXIT_CODE}?"
+ exit 1
+ fi
+
+ count=$(printf '%s' "${OUTPUT}" | wc -w)
+ if [ "${count}" -gt 0 ]; then
+ # Let's start with a new line. pgsql could have printed something out.
+ printf '\n'
+ log_error "Expected empty database ${db_name}. Aborting, the following tables are present:
+ ${OUTPUT}"
+ exit 2
+ fi
+
+ init_script="$scripts_dir/pgsql/dhcpdb_create.pgsql"
+ printf "Initializing database using script %s\n" $init_script
+ run_command \
+ pgsql_execute_script $init_script
+ if [ "${EXIT_CODE}" -ne 0 ]; then
+ log_error "Database initialization failed, status code: ${EXIT_CODE}?"
+ exit 1
+ fi
+
+ version=$(checked_pgsql_version)
+ printf "Database version reported after initialization: %s\n" "$version"
+ exit 0
+}
+
+### Functions that implement database version checking commands
+memfile_version() {
+ # @todo Implement this?
+ log_error "NOT IMPLEMENTED"
+ exit 1
+}
+
+### Functions used for upgrade
+memfile_upgrade() {
+ # Useless as Kea converts CSV versions at startup.
+ log_error "NOT IMPLEMENTED"
+ exit 1
+}
+
+# Upgrades existing MySQL database installation. The idea is that
+# it will go over all upgrade scripts from (prefix)/share/kea/scripts/mysql
+# and run them one by one. They will be named properly, so they will
+# be run in order.
+#
+# This function prints version before and after upgrade.
+mysql_upgrade() {
+
+ printf "Database version reported before upgrade: "
+ checked_mysql_version
+ printf '\n'
+
+ upgrade_scripts_dir=${scripts_dir}/mysql
+
+ # Check if the scripts directory exists at all.
+ if [ ! -d ${upgrade_scripts_dir} ]; then
+ log_error "Invalid scripts directory: ${upgrade_scripts_dir}"
+ exit 1
+ fi
+
+ # Check if there are any files in it
+ num_files=$(find "${upgrade_scripts_dir}" -name 'upgrade*.sh' -type f | wc -l)
+ if [ "$num_files" -eq 0 ]; then
+ upgrade_scripts_dir=@abs_top_builddir@/src/share/database/scripts/mysql
+
+ # Check if the scripts directory exists at all.
+ if [ ! -d ${upgrade_scripts_dir} ]; then
+ log_error "Invalid scripts directory: ${upgrade_scripts_dir}"
+ exit 1
+ fi
+
+ # Check if there are any files in it
+ num_files=$(find "${upgrade_scripts_dir}" -name 'upgrade*.sh' -type f | wc -l)
+ fi
+
+ if [ "$num_files" -eq 0 ]; then
+ log_error "No scripts in ${upgrade_scripts_dir} or the directory is not readable or does not have any upgrade* scripts."
+ exit 1
+ fi
+
+ # Beginning with MySQL 8.0, the db user needs additional settings or SUPER
+ # privileges to create triggers and or functions. Call mysql_can_create to find
+ # out if we're good to go. If not, it will exit.
+ printf "Verifying upgrade permissions for %s\n" "$db_user"
+ mysql_can_create
+
+ for script in "${upgrade_scripts_dir}"/upgrade*.sh
+ do
+ echo "Processing $script file..."
+ "${script}" --host="${db_host}" --user="${db_user}" \
+ --password="${db_password}" "${db_name}" ${extra_arguments}
+ done
+
+ printf "Database version reported after upgrade: "
+ checked_mysql_version
+ printf '\n'
+}
+
+pgsql_upgrade() {
+ version=$(checked_pgsql_version)
+ printf "Database version reported before upgrade: %s\n" "$version"
+
+ upgrade_scripts_dir=${scripts_dir}/pgsql
+
+ # Check if the scripts directory exists at all.
+ if [ ! -d ${upgrade_scripts_dir} ]; then
+ log_error "Invalid scripts directory: ${upgrade_scripts_dir}"
+ exit 1
+ fi
+
+ # Check if there are any files in it
+ num_files=$(find "${upgrade_scripts_dir}" -name 'upgrade*.sh' -type f | wc -l)
+ if [ "$num_files" -eq 0 ]; then
+ upgrade_scripts_dir=@abs_top_builddir@/src/share/database/scripts/pgsql
+
+ # Check if the scripts directory exists at all.
+ if [ ! -d ${upgrade_scripts_dir} ]; then
+ log_error "Invalid scripts directory: ${upgrade_scripts_dir}"
+ exit 1
+ fi
+
+ # Check if there are any files in it
+ num_files=$(find "${upgrade_scripts_dir}" -name 'upgrade*.sh' -type f | wc -l)
+ fi
+
+ if [ "$num_files" -eq 0 ]; then
+ log_error "No scripts in ${upgrade_scripts_dir} or the directory is not readable or does not have any upgrade* scripts."
+ exit 1
+ fi
+
+ # Postgres psql does not accept pw on command line, but can do it
+ # thru an env
+ export PGPASSWORD=$db_password
+
+ for script in "${upgrade_scripts_dir}"/upgrade*.sh
+ do
+ echo "Processing $script file..."
+ "${script}" -U "${db_user}" -h "${db_host}" \
+ -d "${db_name}" ${extra_arguments}
+ done
+
+ version=$(checked_pgsql_version)
+ printf "Database version reported after upgrade: %s\n" "$version"
+ exit 0
+}
+
+# Remove a file if it exists
+remove_file () {
+ local file="${1}"
+ if [ -e "${file}" ]
+ then
+ log_info "Removing file ${file}..."
+ rm -f "${file}"
+ fi
+}
+
+# Utility function which tests if the given file exists and
+# if so notifies the user and provides them the opportunity
+# to abort the current command.
+check_file_overwrite () {
+ local file="${1}"
+ if [ $assume_yes -eq 1 ]
+ then
+ remove_file "${file}"
+ elif [ -e "${file}" ]
+ then
+ echo "Output file, $file, exists and will be overwritten."
+ echo "Do you wish to continue? (y/N)"
+
+ # Ask for an answer only on an interactive shell to prevent blocking in
+ # automated or non-interactive scenarios where the answer defaults to no.
+ if test -t 0; then
+ read -r ans
+ else
+ log_warning 'Non-interactive tty detected. Assuming no.'
+ ans='N'
+ fi
+
+ if [ "${ans}" != "y" ]
+ then
+ echo "$command aborted by user."
+ exit 1
+ fi
+ fi
+}
+
+### Functions used for dump
+
+# Sets the global variable, dump_qry, to the schema-version specific
+# SQL text needed to dump the lease data for the current backend
+# and protocol
+get_dump_query() {
+ local version="${1}"
+
+ case ${backend} in
+ mysql)
+ invoke="call"
+ ;;
+ pgsql)
+ invoke="select * from"
+ ;;
+ *)
+ log_error "unsupported backend ${backend}"
+ usage
+ exit 1
+ ;;
+ esac
+
+ dump_qry="${invoke} lease${dhcp_version}DumpHeader();${invoke} lease${dhcp_version}DumpData();";
+}
+
+memfile_dump() {
+ log_error "lease-dump is not supported for memfile"
+ exit 1
+}
+
+mysql_dump() {
+
+ # Check the lease type was given
+ if [ ${dhcp_version} -eq 0 ]; then
+ log_error "lease-dump: lease type ( -4 or -6 ) needs to be specified"
+ usage
+ exit 1
+ fi
+
+ # get the correct dump query
+ run_command \
+ mysql_version
+ version="${OUTPUT}"
+ if [ "${EXIT_CODE}" -ne 0 ]
+ then
+ log_error "lease-dump: mysql_version failed, exit code ${EXIT_CODE}"
+ exit 1
+ fi
+
+ # Fetch the correct SQL text. Note this function will exit
+ # if it fails.
+ get_dump_query "$version"
+
+ # Make sure they specified a file
+ if [ "$dump_file" = "" ]; then
+ log_error "you must specify an output file for lease-dump"
+ usage
+ exit 1
+
+ fi
+
+ # If output file exists, notify user, allow them a chance to bail
+ check_file_overwrite "$dump_file"
+
+ # Check the temp file too
+ tmp_file="/tmp/$(basename "${dump_file}").tmp"
+ check_file_overwrite $tmp_file
+
+ # Run the sql to output tab-delimited lease data to a temp file.
+ # By using a temp file we can check for MySQL errors before using
+ # 'tr' to translate tabs to commas. We do not use MySQL's output
+ # to file as that requires linux superuser privileges to execute
+ # the select.
+ if ! mysql_execute "${dump_qry}" > $tmp_file; then
+ log_error "lease-dump: mysql_execute failed, exit code ${EXIT_CODE}"
+ exit 1
+ fi
+
+ # Now translate tabs to commas.
+ if ! tr '\t' ',' < "${tmp_file}" > "${dump_file}"; then
+ log_error "lease-dump: reformatting failed";
+ exit 1
+ fi
+
+ # Clean up the temporary file.
+ rm -f "${tmp_file}"
+ log_info "Removed temporary file ${tmp_file}."
+
+ log_info "Successfully dumped lease${dhcp_version} to ${dump_file}."
+ exit 0
+}
+
+### Functions used for dump
+pgsql_dump() {
+ # Check the lease type was given
+ if [ ${dhcp_version} -eq 0 ]; then
+ log_error "lease-dump: lease type ( -4 or -6 ) needs to be specified"
+ usage
+ exit 1
+ fi
+
+ version=$(pgsql_version)
+ get_dump_query "$version"
+
+ # Make sure they specified a file
+ if [ "$dump_file" = "" ]; then
+ log_error "you must specify an output file for lease-dump"
+ usage
+ exit 1
+
+ fi
+
+ # If output file exists, notify user, allow them a chance to bail
+ check_file_overwrite "$dump_file"
+
+ # psql does not accept password as a parameter but will look in the environment
+ export PGPASSWORD=$db_password
+
+ # Call psql and redirect output to the dump file. We don't use psql "to csv"
+ # as it can only be run as db superuser. Check for errors.
+ if ! (
+ echo "${dump_qry}" | \
+ psql --set ON_ERROR_STOP=1 -t -h "${db_host}" -q --user="${db_user}" \
+ --dbname="${db_name}" -w --no-align --field-separator=',' \
+ ${extra_arguments} > "${dump_file}"
+ ); then
+ log_error "lease-dump: psql call failed, exit code: ${?}"
+ exit 1
+ fi
+
+ echo lease${dhcp_version} successfully dumped to "${dump_file}"
+ exit 0
+}
+
+######################## functions used in lease-upload ########################
+
+# Finds the position of a column by name, starting with 1.
+get_column_position() {
+ local column_name="${1}"; shift
+
+ # Look only in the header, count the number of commas up to the column.
+ position=$(head -n 1 "${input_file}" | grep -Eo ",|${column_name}" | uniq -c | \
+ head -n 1 | grep ',' | tr -s ' ' | cut -d ' ' -f 2)
+
+ if test -z "${position}"; then
+ # If no commas, that means position 1.
+ printf '1'
+ else
+ # Else increment, so that it starts at 1.
+ printf '%s' "$((position + 1))"
+ fi
+}
+
+# Adds quotes around values at given positions starting with 1 in a CSV line.
+stringify_positions_in_line() {
+ local positions="${1}"; shift
+ local line="${1}"; shift
+
+ i=1
+ output=
+ for p in ${positions}; do
+ # Get everything up to position p.
+ if test "${i}" -lt "${p}"; then
+ up_until_p=$(printf '%s' "${line}" | cut -d ',' -f "${i}-$((p - 1))")
+ else
+ up_until_p=''
+ fi
+
+ # Get value at position p.
+ p_word=$(printf '%s' "${line}" | cut -d ',' -f "${p}")
+
+ # Add comma unless we're doing the first append.
+ if test "${i}" != 1; then
+ output="${output},"
+ fi
+
+ # Append everything up to position p.
+ output="${output}${up_until_p}"
+
+ # Add comma if we're not stringifying position 1 and if there is
+ # anything up to position p. In the second case, the comma was already
+ # added at a previous step.
+ if test "${p}" != 1 && test -n "${up_until_p}"; then
+ output="${output},"
+ fi
+
+ # Add value at position p.
+ output="${output}'${p_word}'"
+
+ # Skip position p when getting the values between positions next time.
+ i=$((p + 1))
+ done
+
+ # The last position might be somewhere in the middle, so add everything
+ # until the end.
+ the_rest=$(printf '%s' "${line}" | cut -d ',' -f ${i}-)
+
+ # Add comma unless we're adding the whole line or nothing.
+ if test "${i}" != 1 && test -n "${the_rest}"; then
+ output="${output},"
+ fi
+
+ # Append.
+ output="${output}${the_rest}"
+
+ # Print back to the caller.
+ printf '%s' "${output}"
+}
+
+# Entry point for the lease-upload command.
+lease_upload() {
+ # Check the lease type was given
+ if [ ${dhcp_version} -eq 0 ]; then
+ log_error "lease-upload: lease type ( -4 or -6 ) needs to be specified"
+ usage
+ exit 1
+ fi
+
+ # Check that an input file was specified.
+ if test -z "${input_file-}"; then
+ log_error 'you must specify an input file with -i or --input for lease-upload'
+ usage
+ exit 1
+ fi
+
+ # Check that the input file has at least one row of values.
+ input_file_line_length=$(wc -l < "${input_file}")
+ if test "${input_file_line_length}" -le 1; then
+ log_error 'CSV file has no leases'
+ exit 1
+ fi
+
+ # Invoke LFC on the input file.
+ log_info "Looking at ${input_file_line_length} lines of CSV in ${input_file}..."
+ cleaned_up_csv="/tmp/$(basename "${input_file}").tmp"
+ check_file_overwrite "${cleaned_up_csv}"
+ cp "${input_file}" "${cleaned_up_csv}"
+ "${kea_lfc}" "-${dhcp_version}" -x "${cleaned_up_csv}" \
+ -i "${cleaned_up_csv}.1" -o "${cleaned_up_csv}.output" \
+ -f "${cleaned_up_csv}.completed" -p "${cleaned_up_csv}.pid" \
+ -cignored-path
+ cleaned_up_csv_line_length=$(wc -l < "${cleaned_up_csv}")
+ log_info "Reduced to ${cleaned_up_csv_line_length} lines in ${cleaned_up_csv}."
+
+ # Determine the columns whose values need to be stringified to avoid syntax
+ # errors in the MySQL client. These are columns which are VARCHARs or need
+ # to be further processed by a procedure.
+ if test "${dhcp_version}" = '4'; then
+ string_columns='address hwaddr client_id hostname user_context'
+ else
+ string_columns='address duid hostname hwaddr user_context'
+ fi
+
+ # Get positions of string columns.
+ string_positions=
+ for i in ${string_columns}; do
+ string_positions="${string_positions} $(get_column_position "${i}")"
+ done
+
+ # Construct the SQL insert statements.
+ header_parsed=false
+ sql_statement='START TRANSACTION;'
+ while read -r line; do
+ if "${header_parsed}"; then
+ line=$(stringify_positions_in_line "${string_positions}" "${line}")
+ if test "${backend}" = 'mysql'; then
+ sql_statement="${sql_statement} CALL lease${dhcp_version}Upload(${line}); "
+ elif test "${backend}" = 'pgsql'; then
+ sql_statement="${sql_statement} SELECT lease${dhcp_version}Upload(${line}); "
+ else
+ log_error "lease-upload not implemented for ${backend}"
+ exit 1
+ fi
+ else
+ header_parsed=true
+ fi
+ done < "${cleaned_up_csv}"
+ sql_statement="${sql_statement} COMMIT;"
+
+ # Execute the SQL insert statements.
+ if test "${backend}" = 'mysql'; then
+ output="$(mysql_execute "${sql_statement}")"
+ elif test "${backend}" = 'pgsql'; then
+ output="$(pgsql_execute "${sql_statement}")"
+ else
+ log_error "lease-upload not implemented for ${backend}"
+ exit 1
+ fi
+
+ # Clean up the temporary CSV.
+ rm -f "${cleaned_up_csv}"
+ log_info "Removed temporary file ${cleaned_up_csv}."
+
+ # Print a confirmation message.
+ log_info "Successfully updated table lease${dhcp_version}."
+}
+
+### Functions used for recounting statistics
+mysql_recount() {
+ printf "Recount lease statistics from database\n"
+
+ run_command \
+ mysql_execute "$_RECOUNT4_QUERY"
+ if [ "${EXIT_CODE}" -ne 0 ]
+ then
+ log_error "mysql failed to recount IPv4 leases, mysql status = ${EXIT_CODE}"
+ exit 1
+ fi
+
+ run_command \
+ mysql_execute "$_RECOUNT6_QUERY"
+ if [ "${EXIT_CODE}" -ne 0 ]
+ then
+ log_error "mysql failed to recount IPv6 leases, mysql status = ${EXIT_CODE}"
+ exit 1
+ fi
+}
+
+pgsql_recount() {
+ printf "Recount lease statistics from database\n"
+
+ run_command \
+ pgsql_execute "$_RECOUNT4_QUERY"
+ if [ "${EXIT_CODE}" -ne 0 ]
+ then
+ log_error "pgsql failed to recount IPv4 leases, pgsql status = ${EXIT_CODE}"
+ exit 1
+ fi
+
+ run_command \
+ pgsql_execute "$_RECOUNT6_QUERY"
+ if [ "${EXIT_CODE}" -ne 0 ]
+ then
+ log_error "pgsql failed to recount IPv6 leases, pgsql status = ${EXIT_CODE}"
+ exit 1
+ fi
+}
+
+### Script starts here ###
+
+# First, find what the command is
+command=${1-}
+if [ -z "${command}" ]; then
+ log_error "missing command"
+ usage
+ exit 1
+fi
+
+# Check if this is a simple question about version.
+if test "${command}" = "-v" || test "${command}" = "--version" ; then
+ echo "${VERSION}"
+ exit 0
+fi
+
+is_in_list "${command}" "db-init db-version db-upgrade lease-dump lease-upload stats-recount"
+if [ "${_inlist}" -eq 0 ]; then
+ log_error "invalid command: ${command}"
+ usage
+ exit 1
+fi
+shift
+
+# Second, check what's the backend
+backend=${1-}
+if [ -z "${backend}" ]; then
+ log_error "missing backend"
+ usage
+ exit 1
+fi
+is_in_list "${backend}" "memfile mysql pgsql"
+if [ "${_inlist}" -eq 0 ]; then
+ log_error "invalid backend: ${backend}"
+ exit 1
+fi
+shift
+
+# Ok, let's process parameters (if there are any)
+while test "${#}" -gt 0
+do
+ option=${1}
+ case ${option} in
+ # Specify database host
+ -h|--host)
+ shift
+ db_host=${1-}
+ if [ -z "${db_host}" ]; then
+ log_error "-h or --host requires a parameter"
+ usage
+ exit 1
+ fi
+ ;;
+ # Specify database port
+ -P|--port)
+ shift
+ if test -z "${1+x}"; then
+ log_error '-P or --port requires a parameter'
+ usage
+ exit 1
+ fi
+ db_port=${1}
+ export db_port_full_parameter="--port=${db_port}"
+ ;;
+ # Specify database user
+ -u|--user)
+ shift
+ db_user=${1-}
+ if [ -z "${db_user}" ]; then
+ log_error "-u or --user requires a parameter"
+ usage
+ exit 1
+ fi
+ ;;
+ # Specify database password
+ -p|--password)
+ password_parameter_passed=true
+ # If there is at least one more parameter following...
+ if test "${#}" -gt 1; then
+ # Then take it as password.
+ shift
+ db_password=${1}
+ else
+ # If it's an interactive shell...
+ if test -t 0; then
+ # Read from standard input while hiding feedback to the terminal.
+ printf 'Password: '
+ stty -echo
+ read -r db_password
+ stty echo
+ printf '\n'
+ else
+ log_warning 'Non-interactive tty detected. Assuming empty password.'
+ db_password=''
+ fi
+ fi
+ ;;
+ # Specify database name
+ -n|--name)
+ shift
+ db_name=${1-}
+ if [ -z "${db_name}" ]; then
+ log_error "-n or --name requires a parameter"
+ usage
+ exit 1
+ fi
+ ;;
+ -d|--directory)
+ shift
+ scripts_dir=${1-}
+ if [ -z "${scripts_dir}" ]; then
+ log_error "-d or --directory requires a parameter"
+ usage
+ exit 1
+ fi
+ ;;
+ # specify DHCPv4 lease type
+ -4)
+ if [ ${dhcp_version} -eq 6 ]; then
+ log_error "you may not specify both -4 and -6"
+ usage
+ exit 1
+ fi
+ dhcp_version=4
+ ;;
+ # specify DHCPv6 lease type
+ -6)
+ if [ ${dhcp_version} -eq 4 ]; then
+ log_error "you may not specify both -4 and -6"
+ usage
+ exit 1
+ fi
+ dhcp_version=6
+ ;;
+ # specify input file, used by lease-upload
+ -i|--input)
+ shift
+ input_file=${1-}
+ if [ -z "${input_file}" ]; then
+ log_error '-i or --input requires a parameter'
+ usage
+ exit 1
+ fi
+ ;;
+ # specify output file, currently only used by lease dump
+ -o|--output)
+ shift
+ dump_file=${1-}
+ if [ -z "${dump_file}" ]; then
+ log_error "-o or --output requires a parameter"
+ usage
+ exit 1
+ fi
+ ;;
+ # specify extra arguments to pass to the database command
+ -x|--extra)
+ shift
+ if [ -z "${1-}" ]; then
+ log_error "-x or --extra requires a parameter"
+ usage
+ exit 1
+ fi
+ if [ -z "${extra_arguments}" ]; then
+ extra_arguments=${1}
+ else
+ extra_arguments="${extra_arguments} ${1}"
+ fi
+ ;;
+ -y|--yes)
+ assume_yes=1
+ ;;
+ *)
+ log_error "invalid option: ${option}"
+ usage
+ exit 1
+ esac
+ shift
+done
+
+# After all the parameters have been parsed, check environment variables.
+if test -z "${password_parameter_passed+x}"; then
+ if test -n "${KEA_ADMIN_DB_PASSWORD+x}"; then
+ printf 'Using the value of KEA_ADMIN_DB_PASSWORD for authentication...\n'
+ db_password="${KEA_ADMIN_DB_PASSWORD}"
+ fi
+fi
+
+case ${command} in
+ # Initialize the database
+ db-init)
+ case ${backend} in
+ memfile)
+ memfile_init
+ ;;
+ mysql)
+ mysql_init
+ ;;
+ pgsql)
+ pgsql_init
+ ;;
+ esac
+ ;;
+ db-version)
+ case ${backend} in
+ memfile)
+ memfile_version
+ ;;
+ mysql)
+ checked_mysql_version
+ printf '\n'
+ ;;
+ pgsql)
+ checked_pgsql_version
+ ;;
+ esac
+ ;;
+ db-upgrade)
+ case ${backend} in
+ memfile)
+ memfile_upgrade
+ ;;
+ mysql)
+ mysql_upgrade
+ ;;
+ pgsql)
+ pgsql_upgrade
+ ;;
+ esac
+ ;;
+ lease-dump)
+ case ${backend} in
+ memfile)
+ memfile_dump
+ ;;
+ mysql)
+ mysql_dump
+ ;;
+ pgsql)
+ pgsql_dump
+ ;;
+ esac
+ ;;
+ lease-upload)
+ case ${backend} in
+ memfile)
+ log_error 'lease-upload is not supported for memfile'
+ exit 1
+ ;;
+ mysql)
+ lease_upload
+ ;;
+ pgsql)
+ lease_upload
+ ;;
+ esac
+ ;;
+ stats-recount)
+ case ${backend} in
+ memfile)
+ log_info "memfile does not keep lease statistics"
+ ;;
+ mysql)
+ mysql_recount
+ ;;
+ pgsql)
+ pgsql_recount
+ ;;
+ esac
+ ;;
+esac
+
+exit 0
diff --git a/src/bin/admin/tests/Makefile.am b/src/bin/admin/tests/Makefile.am
new file mode 100644
index 0000000..9e0b2c2
--- /dev/null
+++ b/src/bin/admin/tests/Makefile.am
@@ -0,0 +1,32 @@
+SUBDIRS = data .
+
+# Add to the tarball:
+EXTRA_DIST =
+EXTRA_DIST += dhcpdb_create_1.0.mysql
+EXTRA_DIST += dhcpdb_create_1.0.pgsql
+
+# Shell tests
+SHTESTS =
+SHTESTS += admin_tests.sh
+SHTESTS += memfile_tests.sh
+if HAVE_MYSQL
+SHTESTS += mysql_tests.sh
+endif
+if HAVE_PGSQL
+SHTESTS += pgsql_tests.sh
+endif
+
+# As with every file generated by ./configure, clean them up when running
+# "make distclean", but not on "make clean".
+DISTCLEANFILES = $(SHTESTS)
+
+if HAVE_GTEST
+
+# Run tests on "make check".
+check_SCRIPTS = $(SHTESTS)
+TESTS = $(SHTESTS)
+
+endif
+
+# Don't install shell tests.
+noinst_SCRIPTS = $(SHTESTS)
diff --git a/src/bin/admin/tests/Makefile.in b/src/bin/admin/tests/Makefile.in
new file mode 100644
index 0000000..b48b20c
--- /dev/null
+++ b/src/bin/admin/tests/Makefile.in
@@ -0,0 +1,880 @@
+# 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_MYSQL_TRUE@am__append_1 = mysql_tests.sh
+@HAVE_PGSQL_TRUE@am__append_2 = pgsql_tests.sh
+subdir = src/bin/admin/tests
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.m4 \
+ $(top_srcdir)/m4macros/ax_cpp20.m4 \
+ $(top_srcdir)/m4macros/ax_crypto.m4 \
+ $(top_srcdir)/m4macros/ax_find_library.m4 \
+ $(top_srcdir)/m4macros/ax_gssapi.m4 \
+ $(top_srcdir)/m4macros/ax_gtest.m4 \
+ $(top_srcdir)/m4macros/ax_isc_rpath.m4 \
+ $(top_srcdir)/m4macros/ax_netconf.m4 \
+ $(top_srcdir)/m4macros/libtool.m4 \
+ $(top_srcdir)/m4macros/ltoptions.m4 \
+ $(top_srcdir)/m4macros/ltsugar.m4 \
+ $(top_srcdir)/m4macros/ltversion.m4 \
+ $(top_srcdir)/m4macros/lt~obsolete.m4 \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES = admin_tests.sh memfile_tests.sh mysql_tests.sh \
+ pgsql_tests.sh
+CONFIG_CLEAN_VPATH_FILES =
+SCRIPTS = $(noinst_SCRIPTS)
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+SOURCES =
+DIST_SOURCES =
+RECURSIVE_TARGETS = all-recursive check-recursive cscopelist-recursive \
+ ctags-recursive dvi-recursive html-recursive info-recursive \
+ install-data-recursive install-dvi-recursive \
+ install-exec-recursive install-html-recursive \
+ install-info-recursive install-pdf-recursive \
+ install-ps-recursive install-recursive installcheck-recursive \
+ installdirs-recursive pdf-recursive ps-recursive \
+ tags-recursive uninstall-recursive
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \
+ distclean-recursive maintainer-clean-recursive
+am__recursive_targets = \
+ $(RECURSIVE_TARGETS) \
+ $(RECURSIVE_CLEAN_TARGETS) \
+ $(am__extra_recursive_targets)
+AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \
+ distdir distdir-am
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__tty_colors_dummy = \
+ mgn= red= grn= lgn= blu= brg= std=; \
+ am__color_tests=no
+am__tty_colors = { \
+ $(am__tty_colors_dummy); \
+ if test "X$(AM_COLOR_TESTS)" = Xno; then \
+ am__color_tests=no; \
+ elif test "X$(AM_COLOR_TESTS)" = Xalways; then \
+ am__color_tests=yes; \
+ elif test "X$$TERM" != Xdumb && { test -t 1; } 2>/dev/null; then \
+ am__color_tests=yes; \
+ fi; \
+ if test $$am__color_tests = yes; then \
+ red=''; \
+ grn=''; \
+ lgn=''; \
+ blu=''; \
+ mgn=''; \
+ brg=''; \
+ std=''; \
+ fi; \
+}
+DIST_SUBDIRS = $(SUBDIRS)
+am__DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/admin_tests.sh.in \
+ $(srcdir)/memfile_tests.sh.in $(srcdir)/mysql_tests.sh.in \
+ $(srcdir)/pgsql_tests.sh.in
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+am__relativize = \
+ dir0=`pwd`; \
+ sed_first='s,^\([^/]*\)/.*$$,\1,'; \
+ sed_rest='s,^[^/]*/*,,'; \
+ sed_last='s,^.*/\([^/]*\)$$,\1,'; \
+ sed_butlast='s,/*[^/]*$$,,'; \
+ while test -n "$$dir1"; do \
+ first=`echo "$$dir1" | sed -e "$$sed_first"`; \
+ if test "$$first" != "."; then \
+ if test "$$first" = ".."; then \
+ dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \
+ dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \
+ else \
+ first2=`echo "$$dir2" | sed -e "$$sed_first"`; \
+ if test "$$first2" = "$$first"; then \
+ dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \
+ else \
+ dir2="../$$dir2"; \
+ fi; \
+ dir0="$$dir0"/"$$first"; \
+ fi; \
+ fi; \
+ dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \
+ done; \
+ reldir="$$dir2"
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+ASCIIDOC = @ASCIIDOC@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BOOST_INCLUDES = @BOOST_INCLUDES@
+BOOST_LIBS = @BOOST_LIBS@
+BOTAN_TOOL = @BOTAN_TOOL@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CONTRIB_DIR = @CONTRIB_DIR@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CRYPTO_CFLAGS = @CRYPTO_CFLAGS@
+CRYPTO_INCLUDES = @CRYPTO_INCLUDES@
+CRYPTO_LDFLAGS = @CRYPTO_LDFLAGS@
+CRYPTO_LIBS = @CRYPTO_LIBS@
+CRYPTO_PACKAGE = @CRYPTO_PACKAGE@
+CRYPTO_RPATH = @CRYPTO_RPATH@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DISTCHECK_BOOST_CONFIGURE_FLAG = @DISTCHECK_BOOST_CONFIGURE_FLAG@
+DISTCHECK_CONTRIB_CONFIGURE_FLAG = @DISTCHECK_CONTRIB_CONFIGURE_FLAG@
+DISTCHECK_CRYPTO_CONFIGURE_FLAG = @DISTCHECK_CRYPTO_CONFIGURE_FLAG@
+DISTCHECK_GSSAPI_CONFIGURE_FLAG = @DISTCHECK_GSSAPI_CONFIGURE_FLAG@
+DISTCHECK_GTEST_CONFIGURE_FLAG = @DISTCHECK_GTEST_CONFIGURE_FLAG@
+DISTCHECK_KEA_SHELL_CONFIGURE_FLAG = @DISTCHECK_KEA_SHELL_CONFIGURE_FLAG@
+DISTCHECK_LIBYANGCPP_CONFIGURE_FLAG = @DISTCHECK_LIBYANGCPP_CONFIGURE_FLAG@
+DISTCHECK_LIBYANG_CONFIGURE_FLAG = @DISTCHECK_LIBYANG_CONFIGURE_FLAG@
+DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG = @DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG@
+DISTCHECK_MYSQL_CONFIGURE_FLAG = @DISTCHECK_MYSQL_CONFIGURE_FLAG@
+DISTCHECK_PERFDHCP_CONFIGURE_FLAG = @DISTCHECK_PERFDHCP_CONFIGURE_FLAG@
+DISTCHECK_PGSQL_CONFIGURE_FLAG = @DISTCHECK_PGSQL_CONFIGURE_FLAG@
+DISTCHECK_PREMIUM_CONFIGURE_FLAG = @DISTCHECK_PREMIUM_CONFIGURE_FLAG@
+DISTCHECK_SYSREPOCPP_CONFIGURE_FLAG = @DISTCHECK_SYSREPOCPP_CONFIGURE_FLAG@
+DISTCHECK_SYSREPO_CONFIGURE_FLAG = @DISTCHECK_SYSREPO_CONFIGURE_FLAG@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GENHTML = @GENHTML@
+GREP = @GREP@
+GSSAPI_CFLAGS = @GSSAPI_CFLAGS@
+GSSAPI_LIBS = @GSSAPI_LIBS@
+GTEST_CONFIG = @GTEST_CONFIG@
+GTEST_INCLUDES = @GTEST_INCLUDES@
+GTEST_LDADD = @GTEST_LDADD@
+GTEST_LDFLAGS = @GTEST_LDFLAGS@
+GTEST_SOURCE = @GTEST_SOURCE@
+HAVE_NETCONF = @HAVE_NETCONF@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+KEA_CXXFLAGS = @KEA_CXXFLAGS@
+KEA_SRCID = @KEA_SRCID@
+KRB5_CONFIG = @KRB5_CONFIG@
+LCOV = @LCOV@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LEX = @LEX@
+LEXLIB = @LEXLIB@
+LEX_OUTPUT_ROOT = @LEX_OUTPUT_ROOT@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIBYANGCPP_CPPFLAGS = @LIBYANGCPP_CPPFLAGS@
+LIBYANGCPP_INCLUDEDIR = @LIBYANGCPP_INCLUDEDIR@
+LIBYANGCPP_LIBS = @LIBYANGCPP_LIBS@
+LIBYANGCPP_PREFIX = @LIBYANGCPP_PREFIX@
+LIBYANGCPP_VERSION = @LIBYANGCPP_VERSION@
+LIBYANG_CPPFLAGS = @LIBYANG_CPPFLAGS@
+LIBYANG_INCLUDEDIR = @LIBYANG_INCLUDEDIR@
+LIBYANG_LIBS = @LIBYANG_LIBS@
+LIBYANG_PREFIX = @LIBYANG_PREFIX@
+LIBYANG_VERSION = @LIBYANG_VERSION@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LOG4CPLUS_INCLUDES = @LOG4CPLUS_INCLUDES@
+LOG4CPLUS_LIBS = @LOG4CPLUS_LIBS@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MYSQL_CPPFLAGS = @MYSQL_CPPFLAGS@
+MYSQL_LIBS = @MYSQL_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PACKAGE_VERSION_TYPE = @PACKAGE_VERSION_TYPE@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PDFLATEX = @PDFLATEX@
+PERL = @PERL@
+PGSQL_CPPFLAGS = @PGSQL_CPPFLAGS@
+PGSQL_LIBS = @PGSQL_LIBS@
+PKGPYTHONDIR = @PKGPYTHONDIR@
+PKG_CONFIG = @PKG_CONFIG@
+PLANTUML = @PLANTUML@
+PREMIUM_DIR = @PREMIUM_DIR@
+PYTHON = @PYTHON@
+PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@
+PYTHON_PLATFORM = @PYTHON_PLATFORM@
+PYTHON_PREFIX = @PYTHON_PREFIX@
+PYTHON_VERSION = @PYTHON_VERSION@
+RANLIB = @RANLIB@
+SED = @SED@
+SEP = @SEP@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SPHINXBUILD = @SPHINXBUILD@
+SRPD_PLUGINS_PATH = @SRPD_PLUGINS_PATH@
+SR_PLUGINS_PATH = @SR_PLUGINS_PATH@
+SR_REPO_PATH = @SR_REPO_PATH@
+STRIP = @STRIP@
+SYSREPOCPP_CPPFLAGS = @SYSREPOCPP_CPPFLAGS@
+SYSREPOCPP_INCLUDEDIR = @SYSREPOCPP_INCLUDEDIR@
+SYSREPOCPP_LIBS = @SYSREPOCPP_LIBS@
+SYSREPOCPP_PREFIX = @SYSREPOCPP_PREFIX@
+SYSREPOCPP_VERSION = @SYSREPOCPP_VERSION@
+SYSREPO_CPPFLAGS = @SYSREPO_CPPFLAGS@
+SYSREPO_INCLUDEDIR = @SYSREPO_INCLUDEDIR@
+SYSREPO_LIBS = @SYSREPO_LIBS@
+SYSREPO_PREFIX = @SYSREPO_PREFIX@
+SYSREPO_VERSION = @SYSREPO_VERSION@
+USE_LCOV = @USE_LCOV@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+WARNING_GCC_44_STRICT_ALIASING_CFLAG = @WARNING_GCC_44_STRICT_ALIASING_CFLAG@
+YACC = @YACC@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+pkgpyexecdir = @pkgpyexecdir@
+pkgpythondir = @pkgpythondir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+pyexecdir = @pyexecdir@
+pythondir = @pythondir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+SUBDIRS = data .
+
+# Add to the tarball:
+EXTRA_DIST = dhcpdb_create_1.0.mysql dhcpdb_create_1.0.pgsql
+
+# Shell tests
+SHTESTS = admin_tests.sh memfile_tests.sh $(am__append_1) \
+ $(am__append_2)
+
+# As with every file generated by ./configure, clean them up when running
+# "make distclean", but not on "make clean".
+DISTCLEANFILES = $(SHTESTS)
+
+# Run tests on "make check".
+@HAVE_GTEST_TRUE@check_SCRIPTS = $(SHTESTS)
+@HAVE_GTEST_TRUE@TESTS = $(SHTESTS)
+
+# Don't install shell tests.
+noinst_SCRIPTS = $(SHTESTS)
+all: all-recursive
+
+.SUFFIXES:
+$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/bin/admin/tests/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/bin/admin/tests/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+admin_tests.sh: $(top_builddir)/config.status $(srcdir)/admin_tests.sh.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+memfile_tests.sh: $(top_builddir)/config.status $(srcdir)/memfile_tests.sh.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+mysql_tests.sh: $(top_builddir)/config.status $(srcdir)/mysql_tests.sh.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+pgsql_tests.sh: $(top_builddir)/config.status $(srcdir)/pgsql_tests.sh.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+# This directory's subdirectories are mostly independent; you can cd
+# into them and run 'make' without going through this Makefile.
+# To change the values of 'make' variables: instead of editing Makefiles,
+# (1) if the variable is set in 'config.status', edit 'config.status'
+# (which will cause the Makefiles to be regenerated when you run 'make');
+# (2) otherwise, pass the desired values on the 'make' command line.
+$(am__recursive_targets):
+ @fail=; \
+ if $(am__make_keepgoing); then \
+ failcom='fail=yes'; \
+ else \
+ failcom='exit 1'; \
+ fi; \
+ dot_seen=no; \
+ target=`echo $@ | sed s/-recursive//`; \
+ case "$@" in \
+ distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \
+ *) list='$(SUBDIRS)' ;; \
+ esac; \
+ for subdir in $$list; do \
+ echo "Making $$target in $$subdir"; \
+ if test "$$subdir" = "."; then \
+ dot_seen=yes; \
+ local_target="$$target-am"; \
+ else \
+ local_target="$$target"; \
+ fi; \
+ ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \
+ || eval $$failcom; \
+ done; \
+ if test "$$dot_seen" = "no"; then \
+ $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \
+ fi; test -z "$$fail"
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-recursive
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \
+ include_option=--etags-include; \
+ empty_fix=.; \
+ else \
+ include_option=--include; \
+ empty_fix=; \
+ fi; \
+ list='$(SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ test ! -f $$subdir/TAGS || \
+ set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \
+ fi; \
+ done; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-recursive
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-recursive
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+check-TESTS: $(TESTS)
+ @failed=0; all=0; xfail=0; xpass=0; skip=0; \
+ srcdir=$(srcdir); export srcdir; \
+ list=' $(TESTS) '; \
+ $(am__tty_colors); \
+ if test -n "$$list"; then \
+ for tst in $$list; do \
+ if test -f ./$$tst; then dir=./; \
+ elif test -f $$tst; then dir=; \
+ else dir="$(srcdir)/"; fi; \
+ if $(TESTS_ENVIRONMENT) $${dir}$$tst $(AM_TESTS_FD_REDIRECT); then \
+ all=`expr $$all + 1`; \
+ case " $(XFAIL_TESTS) " in \
+ *[\ \ ]$$tst[\ \ ]*) \
+ xpass=`expr $$xpass + 1`; \
+ failed=`expr $$failed + 1`; \
+ col=$$red; res=XPASS; \
+ ;; \
+ *) \
+ col=$$grn; res=PASS; \
+ ;; \
+ esac; \
+ elif test $$? -ne 77; then \
+ all=`expr $$all + 1`; \
+ case " $(XFAIL_TESTS) " in \
+ *[\ \ ]$$tst[\ \ ]*) \
+ xfail=`expr $$xfail + 1`; \
+ col=$$lgn; res=XFAIL; \
+ ;; \
+ *) \
+ failed=`expr $$failed + 1`; \
+ col=$$red; res=FAIL; \
+ ;; \
+ esac; \
+ else \
+ skip=`expr $$skip + 1`; \
+ col=$$blu; res=SKIP; \
+ fi; \
+ echo "$${col}$$res$${std}: $$tst"; \
+ done; \
+ if test "$$all" -eq 1; then \
+ tests="test"; \
+ All=""; \
+ else \
+ tests="tests"; \
+ All="All "; \
+ fi; \
+ if test "$$failed" -eq 0; then \
+ if test "$$xfail" -eq 0; then \
+ banner="$$All$$all $$tests passed"; \
+ else \
+ if test "$$xfail" -eq 1; then failures=failure; else failures=failures; fi; \
+ banner="$$All$$all $$tests behaved as expected ($$xfail expected $$failures)"; \
+ fi; \
+ else \
+ if test "$$xpass" -eq 0; then \
+ banner="$$failed of $$all $$tests failed"; \
+ else \
+ if test "$$xpass" -eq 1; then passes=pass; else passes=passes; fi; \
+ banner="$$failed of $$all $$tests did not behave as expected ($$xpass unexpected $$passes)"; \
+ fi; \
+ fi; \
+ dashes="$$banner"; \
+ skipped=""; \
+ if test "$$skip" -ne 0; then \
+ if test "$$skip" -eq 1; then \
+ skipped="($$skip test was not run)"; \
+ else \
+ skipped="($$skip tests were not run)"; \
+ fi; \
+ test `echo "$$skipped" | wc -c` -le `echo "$$banner" | wc -c` || \
+ dashes="$$skipped"; \
+ fi; \
+ report=""; \
+ if test "$$failed" -ne 0 && test -n "$(PACKAGE_BUGREPORT)"; then \
+ report="Please report to $(PACKAGE_BUGREPORT)"; \
+ test `echo "$$report" | wc -c` -le `echo "$$banner" | wc -c` || \
+ dashes="$$report"; \
+ fi; \
+ dashes=`echo "$$dashes" | sed s/./=/g`; \
+ if test "$$failed" -eq 0; then \
+ col="$$grn"; \
+ else \
+ col="$$red"; \
+ fi; \
+ echo "$${col}$$dashes$${std}"; \
+ echo "$${col}$$banner$${std}"; \
+ test -z "$$skipped" || echo "$${col}$$skipped$${std}"; \
+ test -z "$$report" || echo "$${col}$$report$${std}"; \
+ echo "$${col}$$dashes$${std}"; \
+ test "$$failed" -eq 0; \
+ else :; fi
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+ @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ $(am__make_dryrun) \
+ || test -d "$(distdir)/$$subdir" \
+ || $(MKDIR_P) "$(distdir)/$$subdir" \
+ || exit 1; \
+ dir1=$$subdir; dir2="$(distdir)/$$subdir"; \
+ $(am__relativize); \
+ new_distdir=$$reldir; \
+ dir1=$$subdir; dir2="$(top_distdir)"; \
+ $(am__relativize); \
+ new_top_distdir=$$reldir; \
+ echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \
+ echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \
+ ($(am__cd) $$subdir && \
+ $(MAKE) $(AM_MAKEFLAGS) \
+ top_distdir="$$new_top_distdir" \
+ distdir="$$new_distdir" \
+ am__remove_distdir=: \
+ am__skip_length_check=: \
+ am__skip_mode_fix=: \
+ distdir) \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+ $(MAKE) $(AM_MAKEFLAGS) $(check_SCRIPTS)
+ $(MAKE) $(AM_MAKEFLAGS) check-TESTS
+check: check-recursive
+all-am: Makefile $(SCRIPTS)
+installdirs: installdirs-recursive
+installdirs-am:
+install: install-recursive
+install-exec: install-exec-recursive
+install-data: install-data-recursive
+uninstall: uninstall-recursive
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-recursive
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+ -test -z "$(DISTCLEANFILES)" || rm -f $(DISTCLEANFILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-recursive
+
+clean-am: clean-generic clean-libtool mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -f Makefile
+distclean-am: clean-am distclean-generic distclean-tags
+
+dvi: dvi-recursive
+
+dvi-am:
+
+html: html-recursive
+
+html-am:
+
+info: info-recursive
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-recursive
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-recursive
+
+install-html-am:
+
+install-info: install-info-recursive
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-recursive
+
+install-pdf-am:
+
+install-ps: install-ps-recursive
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-recursive
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-recursive
+
+mostlyclean-am: mostlyclean-generic mostlyclean-libtool
+
+pdf: pdf-recursive
+
+pdf-am:
+
+ps: ps-recursive
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: $(am__recursive_targets) check-am install-am install-strip
+
+.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am check \
+ check-TESTS check-am clean clean-generic clean-libtool \
+ cscopelist-am ctags ctags-am distclean distclean-generic \
+ distclean-libtool distclean-tags distdir dvi dvi-am html \
+ html-am info info-am install install-am install-data \
+ install-data-am install-dvi install-dvi-am install-exec \
+ install-exec-am install-html install-html-am install-info \
+ install-info-am install-man install-pdf install-pdf-am \
+ install-ps install-ps-am install-strip installcheck \
+ installcheck-am installdirs installdirs-am maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-generic \
+ mostlyclean-libtool pdf pdf-am ps ps-am tags tags-am uninstall \
+ uninstall-am
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/bin/admin/tests/admin_tests.sh.in b/src/bin/admin/tests/admin_tests.sh.in
new file mode 100644
index 0000000..c6b8a26
--- /dev/null
+++ b/src/bin/admin/tests/admin_tests.sh.in
@@ -0,0 +1,147 @@
+#!/bin/sh
+
+# Copyright (C) 2021-2023 Internet Systems Consortium, Inc. ("ISC")
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# shellcheck disable=SC1091
+# SC1091: Not following: ... was not specified as input (see shellcheck -x).
+
+# Exit with error if commands exit with non-zero and if undefined variables are
+# used.
+set -eu
+
+# Include common test library.
+. "@abs_top_builddir@/src/lib/testutils/dhcp_test_lib.sh"
+
+# Include admin utilities
+. "@abs_top_builddir@/src/bin/admin/admin-utils.sh"
+
+# Set location of the kea-admin.
+kea_admin="@abs_top_builddir@/src/bin/admin/kea-admin"
+
+kea_admin_error_reporting_tests() {
+ test_start 'kea_admin_error_reporting_test.db_init'
+ run_command \
+ "${kea_admin}" db-init
+ assert_eq 1 "${EXIT_CODE}"
+ assert_str_eq 'ERROR/kea-admin: missing backend' "$(printf '%s\n' "${OUTPUT}" | head -n 1)"
+ test_finish ${?}
+
+ test_start 'kea_admin_error_reporting_test.db_init.mysql.u'
+ run_command \
+ "${kea_admin}" db-init mysql -u
+ assert_eq 1 "${EXIT_CODE}"
+ assert_str_eq 'ERROR/kea-admin: -u or --user requires a parameter' "$(printf '%s\n' "${OUTPUT}" | head -n 1)"
+ test_finish ${?}
+
+ test_start 'kea_admin_error_reporting_test.db_init.mysql.user'
+ run_command \
+ "${kea_admin}" db-init mysql --user
+ assert_eq 1 "${EXIT_CODE}"
+ assert_str_eq 'ERROR/kea-admin: -u or --user requires a parameter' "$(printf '%s\n' "${OUTPUT}" | head -n 1)"
+ test_finish ${?}
+
+ test_start 'kea_admin_error_reporting_test.db_init.mysql.h'
+ run_command \
+ "${kea_admin}" db-init mysql -h
+ assert_str_eq 'ERROR/kea-admin: -h or --host requires a parameter' "$(printf '%s\n' "${OUTPUT}" | head -n 1)"
+ assert_eq 1 "${EXIT_CODE}"
+ test_finish ${?}
+
+ test_start 'kea_admin_error_reporting_test.db_init.mysql.host'
+ run_command \
+ "${kea_admin}" db-init mysql --host
+ assert_eq 1 "${EXIT_CODE}"
+ assert_str_eq 'ERROR/kea-admin: -h or --host requires a parameter' "$(printf '%s\n' "${OUTPUT}" | head -n 1)"
+ test_finish ${?}
+
+ test_start 'kea_admin_error_reporting_test.db_init.mysql.n'
+ run_command \
+ "${kea_admin}" db-init mysql -n
+ assert_eq 1 "${EXIT_CODE}"
+ assert_str_eq 'ERROR/kea-admin: -n or --name requires a parameter' "$(printf '%s\n' "${OUTPUT}" | head -n 1)"
+ test_finish ${?}
+
+ test_start 'kea_admin_error_reporting_test.db_init.mysql.name'
+ run_command \
+ "${kea_admin}" db-init mysql --name
+ assert_eq 1 "${EXIT_CODE}"
+ assert_str_eq 'ERROR/kea-admin: -n or --name requires a parameter' "$(printf '%s\n' "${OUTPUT}" | head -n 1)"
+ test_finish ${?}
+
+ test_start 'kea_admin_error_reporting_test.db_init.mysql.d'
+ run_command \
+ "${kea_admin}" db-init mysql -d
+ assert_eq 1 "${EXIT_CODE}"
+ assert_str_eq 'ERROR/kea-admin: -d or --directory requires a parameter' "$(printf '%s\n' "${OUTPUT}" | head -n 1)"
+ test_finish ${?}
+
+ test_start 'kea_admin_error_reporting_test.db_init.mysql.directory'
+ run_command \
+ "${kea_admin}" db-init mysql --directory
+ assert_eq 1 "${EXIT_CODE}"
+ assert_str_eq 'ERROR/kea-admin: -d or --directory requires a parameter' "$(printf '%s\n' "${OUTPUT}" | head -n 1)"
+ test_finish ${?}
+
+ test_start 'kea_admin_error_reporting_test.lease_upload.mysql.i'
+ run_command \
+ "${kea_admin}" lease-upload mysql -i
+ assert_eq 1 "${EXIT_CODE}"
+ assert_str_eq 'ERROR/kea-admin: -i or --input requires a parameter' "$(printf '%s\n' "${OUTPUT}" | head -n 1)"
+ test_finish ${?}
+
+ test_start 'kea_admin_error_reporting_test.lease_upload.mysql.input'
+ run_command \
+ "${kea_admin}" lease-upload mysql --input
+ assert_eq 1 "${EXIT_CODE}"
+ assert_str_eq 'ERROR/kea-admin: -i or --input requires a parameter' "$(printf '%s\n' "${OUTPUT}" | head -n 1)"
+ test_finish ${?}
+
+ test_start 'kea_admin_error_reporting_test.lease_dump.mysql.o'
+ run_command \
+ "${kea_admin}" lease-dump mysql -o
+ assert_eq 1 "${EXIT_CODE}"
+ assert_str_eq 'ERROR/kea-admin: -o or --output requires a parameter' "$(printf '%s\n' "${OUTPUT}" | head -n 1)"
+ test_finish ${?}
+
+ test_start 'kea_admin_error_reporting_test.lease_dump.mysql.output'
+ run_command \
+ "${kea_admin}" lease-dump mysql --output
+ assert_eq 1 "${EXIT_CODE}"
+ assert_str_eq 'ERROR/kea-admin: -o or --output requires a parameter' "$(printf '%s\n' "${OUTPUT}" | head -n 1)"
+ test_finish ${?}
+
+ test_start 'kea_admin_error_reporting_test.lease_dump.mysql.lease_type_missing'
+ run_command \
+ "${kea_admin}" lease-dump mysql
+ assert_eq 1 "${EXIT_CODE}"
+ assert_str_eq 'ERROR/kea-admin: lease-dump: lease type ( -4 or -6 ) needs to be specified' "$(printf '%s\n' "${OUTPUT}" | head -n 1)"
+ test_finish ${?}
+
+ test_start 'kea_admin_error_reporting_test.lease_upload.mysql.lease_type_missing'
+ run_command \
+ "${kea_admin}" lease-upload mysql
+ assert_eq 1 "${EXIT_CODE}"
+ assert_str_eq 'ERROR/kea-admin: lease-upload: lease type ( -4 or -6 ) needs to be specified' "$(printf '%s\n' "${OUTPUT}" | head -n 1)"
+ test_finish ${?}
+
+ test_start 'kea_admin_error_reporting_test.db_version.mysql.x'
+ run_command \
+ "${kea_admin}" db-version mysql -x
+ assert_eq 1 "${EXIT_CODE}"
+ assert_str_eq 'ERROR/kea-admin: -x or --extra requires a parameter' "$(printf '%s\n' "${OUTPUT}" | head -n 1)"
+ test_finish ${?}
+
+ test_start 'kea_admin_error_reporting_test.db_version.mysql.extra'
+ run_command \
+ "${kea_admin}" db-version mysql --extra
+ assert_eq 1 "${EXIT_CODE}"
+ assert_str_eq 'ERROR/kea-admin: -x or --extra requires a parameter' "$(printf '%s\n' "${OUTPUT}" | head -n 1)"
+ test_finish ${?}
+}
+
+# Run tests.
+kea_admin_error_reporting_tests
diff --git a/src/bin/admin/tests/data/Makefile.am b/src/bin/admin/tests/data/Makefile.am
new file mode 100644
index 0000000..7ce6289
--- /dev/null
+++ b/src/bin/admin/tests/data/Makefile.am
@@ -0,0 +1,3 @@
+EXTRA_DIST = \
+ lease4_dump_test.reference.csv \
+ lease6_dump_test.reference.csv
diff --git a/src/bin/admin/tests/data/Makefile.in b/src/bin/admin/tests/data/Makefile.in
new file mode 100644
index 0000000..43b751f
--- /dev/null
+++ b/src/bin/admin/tests/data/Makefile.in
@@ -0,0 +1,551 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/bin/admin/tests/data
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.m4 \
+ $(top_srcdir)/m4macros/ax_cpp20.m4 \
+ $(top_srcdir)/m4macros/ax_crypto.m4 \
+ $(top_srcdir)/m4macros/ax_find_library.m4 \
+ $(top_srcdir)/m4macros/ax_gssapi.m4 \
+ $(top_srcdir)/m4macros/ax_gtest.m4 \
+ $(top_srcdir)/m4macros/ax_isc_rpath.m4 \
+ $(top_srcdir)/m4macros/ax_netconf.m4 \
+ $(top_srcdir)/m4macros/libtool.m4 \
+ $(top_srcdir)/m4macros/ltoptions.m4 \
+ $(top_srcdir)/m4macros/ltsugar.m4 \
+ $(top_srcdir)/m4macros/ltversion.m4 \
+ $(top_srcdir)/m4macros/lt~obsolete.m4 \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+SOURCES =
+DIST_SOURCES =
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+am__DIST_COMMON = $(srcdir)/Makefile.in
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+ASCIIDOC = @ASCIIDOC@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BOOST_INCLUDES = @BOOST_INCLUDES@
+BOOST_LIBS = @BOOST_LIBS@
+BOTAN_TOOL = @BOTAN_TOOL@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CONTRIB_DIR = @CONTRIB_DIR@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CRYPTO_CFLAGS = @CRYPTO_CFLAGS@
+CRYPTO_INCLUDES = @CRYPTO_INCLUDES@
+CRYPTO_LDFLAGS = @CRYPTO_LDFLAGS@
+CRYPTO_LIBS = @CRYPTO_LIBS@
+CRYPTO_PACKAGE = @CRYPTO_PACKAGE@
+CRYPTO_RPATH = @CRYPTO_RPATH@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DISTCHECK_BOOST_CONFIGURE_FLAG = @DISTCHECK_BOOST_CONFIGURE_FLAG@
+DISTCHECK_CONTRIB_CONFIGURE_FLAG = @DISTCHECK_CONTRIB_CONFIGURE_FLAG@
+DISTCHECK_CRYPTO_CONFIGURE_FLAG = @DISTCHECK_CRYPTO_CONFIGURE_FLAG@
+DISTCHECK_GSSAPI_CONFIGURE_FLAG = @DISTCHECK_GSSAPI_CONFIGURE_FLAG@
+DISTCHECK_GTEST_CONFIGURE_FLAG = @DISTCHECK_GTEST_CONFIGURE_FLAG@
+DISTCHECK_KEA_SHELL_CONFIGURE_FLAG = @DISTCHECK_KEA_SHELL_CONFIGURE_FLAG@
+DISTCHECK_LIBYANGCPP_CONFIGURE_FLAG = @DISTCHECK_LIBYANGCPP_CONFIGURE_FLAG@
+DISTCHECK_LIBYANG_CONFIGURE_FLAG = @DISTCHECK_LIBYANG_CONFIGURE_FLAG@
+DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG = @DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG@
+DISTCHECK_MYSQL_CONFIGURE_FLAG = @DISTCHECK_MYSQL_CONFIGURE_FLAG@
+DISTCHECK_PERFDHCP_CONFIGURE_FLAG = @DISTCHECK_PERFDHCP_CONFIGURE_FLAG@
+DISTCHECK_PGSQL_CONFIGURE_FLAG = @DISTCHECK_PGSQL_CONFIGURE_FLAG@
+DISTCHECK_PREMIUM_CONFIGURE_FLAG = @DISTCHECK_PREMIUM_CONFIGURE_FLAG@
+DISTCHECK_SYSREPOCPP_CONFIGURE_FLAG = @DISTCHECK_SYSREPOCPP_CONFIGURE_FLAG@
+DISTCHECK_SYSREPO_CONFIGURE_FLAG = @DISTCHECK_SYSREPO_CONFIGURE_FLAG@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GENHTML = @GENHTML@
+GREP = @GREP@
+GSSAPI_CFLAGS = @GSSAPI_CFLAGS@
+GSSAPI_LIBS = @GSSAPI_LIBS@
+GTEST_CONFIG = @GTEST_CONFIG@
+GTEST_INCLUDES = @GTEST_INCLUDES@
+GTEST_LDADD = @GTEST_LDADD@
+GTEST_LDFLAGS = @GTEST_LDFLAGS@
+GTEST_SOURCE = @GTEST_SOURCE@
+HAVE_NETCONF = @HAVE_NETCONF@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+KEA_CXXFLAGS = @KEA_CXXFLAGS@
+KEA_SRCID = @KEA_SRCID@
+KRB5_CONFIG = @KRB5_CONFIG@
+LCOV = @LCOV@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LEX = @LEX@
+LEXLIB = @LEXLIB@
+LEX_OUTPUT_ROOT = @LEX_OUTPUT_ROOT@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIBYANGCPP_CPPFLAGS = @LIBYANGCPP_CPPFLAGS@
+LIBYANGCPP_INCLUDEDIR = @LIBYANGCPP_INCLUDEDIR@
+LIBYANGCPP_LIBS = @LIBYANGCPP_LIBS@
+LIBYANGCPP_PREFIX = @LIBYANGCPP_PREFIX@
+LIBYANGCPP_VERSION = @LIBYANGCPP_VERSION@
+LIBYANG_CPPFLAGS = @LIBYANG_CPPFLAGS@
+LIBYANG_INCLUDEDIR = @LIBYANG_INCLUDEDIR@
+LIBYANG_LIBS = @LIBYANG_LIBS@
+LIBYANG_PREFIX = @LIBYANG_PREFIX@
+LIBYANG_VERSION = @LIBYANG_VERSION@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LOG4CPLUS_INCLUDES = @LOG4CPLUS_INCLUDES@
+LOG4CPLUS_LIBS = @LOG4CPLUS_LIBS@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MYSQL_CPPFLAGS = @MYSQL_CPPFLAGS@
+MYSQL_LIBS = @MYSQL_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PACKAGE_VERSION_TYPE = @PACKAGE_VERSION_TYPE@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PDFLATEX = @PDFLATEX@
+PERL = @PERL@
+PGSQL_CPPFLAGS = @PGSQL_CPPFLAGS@
+PGSQL_LIBS = @PGSQL_LIBS@
+PKGPYTHONDIR = @PKGPYTHONDIR@
+PKG_CONFIG = @PKG_CONFIG@
+PLANTUML = @PLANTUML@
+PREMIUM_DIR = @PREMIUM_DIR@
+PYTHON = @PYTHON@
+PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@
+PYTHON_PLATFORM = @PYTHON_PLATFORM@
+PYTHON_PREFIX = @PYTHON_PREFIX@
+PYTHON_VERSION = @PYTHON_VERSION@
+RANLIB = @RANLIB@
+SED = @SED@
+SEP = @SEP@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SPHINXBUILD = @SPHINXBUILD@
+SRPD_PLUGINS_PATH = @SRPD_PLUGINS_PATH@
+SR_PLUGINS_PATH = @SR_PLUGINS_PATH@
+SR_REPO_PATH = @SR_REPO_PATH@
+STRIP = @STRIP@
+SYSREPOCPP_CPPFLAGS = @SYSREPOCPP_CPPFLAGS@
+SYSREPOCPP_INCLUDEDIR = @SYSREPOCPP_INCLUDEDIR@
+SYSREPOCPP_LIBS = @SYSREPOCPP_LIBS@
+SYSREPOCPP_PREFIX = @SYSREPOCPP_PREFIX@
+SYSREPOCPP_VERSION = @SYSREPOCPP_VERSION@
+SYSREPO_CPPFLAGS = @SYSREPO_CPPFLAGS@
+SYSREPO_INCLUDEDIR = @SYSREPO_INCLUDEDIR@
+SYSREPO_LIBS = @SYSREPO_LIBS@
+SYSREPO_PREFIX = @SYSREPO_PREFIX@
+SYSREPO_VERSION = @SYSREPO_VERSION@
+USE_LCOV = @USE_LCOV@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+WARNING_GCC_44_STRICT_ALIASING_CFLAG = @WARNING_GCC_44_STRICT_ALIASING_CFLAG@
+YACC = @YACC@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+pkgpyexecdir = @pkgpyexecdir@
+pkgpythondir = @pkgpythondir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+pyexecdir = @pyexecdir@
+pythondir = @pythondir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+EXTRA_DIST = \
+ lease4_dump_test.reference.csv \
+ lease6_dump_test.reference.csv
+
+all: all-am
+
+.SUFFIXES:
+$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/bin/admin/tests/data/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/bin/admin/tests/data/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):
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+tags TAGS:
+
+ctags CTAGS:
+
+cscope cscopelist:
+
+
+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
+check-am: all-am
+check: check-am
+all-am: Makefile
+installdirs:
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+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-am
+
+clean-am: clean-generic clean-libtool mostlyclean-am
+
+distclean: distclean-am
+ -rm -f Makefile
+distclean-am: clean-am distclean-generic
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-generic mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: install-am install-strip
+
+.PHONY: all all-am check check-am clean clean-generic clean-libtool \
+ cscopelist-am ctags-am distclean distclean-generic \
+ distclean-libtool 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 \
+ maintainer-clean maintainer-clean-generic mostlyclean \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags-am uninstall uninstall-am
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/bin/admin/tests/data/lease4_dump_test.reference.csv b/src/bin/admin/tests/data/lease4_dump_test.reference.csv
new file mode 100644
index 0000000..05eb50c
--- /dev/null
+++ b/src/bin/admin/tests/data/lease4_dump_test.reference.csv
@@ -0,0 +1,4 @@
+address,hwaddr,client_id,valid_lifetime,expire,subnet_id,fqdn_fwd,fqdn_rev,hostname,state,user_context,pool_id
+0.0.0.10,32:30,33:30,40,1642000000,50,1,1,one.example.com,0,,0
+0.0.0.11,,31:32:33,40,1643210000,50,1,1,,1,{ },0
+0.0.0.12,32:32,,40,1643212345,50,1,1,three&#x2cexample&#x2ccom,2,{ "a": 1&#x2c "b": "c" },0
diff --git a/src/bin/admin/tests/data/lease6_dump_test.reference.csv b/src/bin/admin/tests/data/lease6_dump_test.reference.csv
new file mode 100644
index 0000000..3693537
--- /dev/null
+++ b/src/bin/admin/tests/data/lease6_dump_test.reference.csv
@@ -0,0 +1,4 @@
+address,duid,valid_lifetime,expire,subnet_id,pref_lifetime,lease_type,iaid,prefix_len,fqdn_fwd,fqdn_rev,hostname,hwaddr,state,user_context,hwtype,hwaddr_source,pool_id
+::10,32:30:33,30,1642000000,40,50,1,60,128,1,1,one.example.com,38:30,0,,90,16,0
+::11,32:31:33,30,1643210000,40,50,1,60,128,1,1,,38:30,1,{ },90,1,0
+::12,32:32:33,30,1643212345,40,50,1,60,128,1,1,three&#x2cexample&#x2ccom,38:30,2,{ "a": 1&#x2c "b": "c" },90,4,0
diff --git a/src/bin/admin/tests/dhcpdb_create_1.0.mysql b/src/bin/admin/tests/dhcpdb_create_1.0.mysql
new file mode 100644
index 0000000..181d536
--- /dev/null
+++ b/src/bin/admin/tests/dhcpdb_create_1.0.mysql
@@ -0,0 +1,131 @@
+# Copyright (C) 2012-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/.
+
+# This is the Kea schema 1.0 specification for MySQL.
+# Note: this is outdated version on purpose and it used to test upgrade
+# process. Do not update this file to 2.0 or any later.
+#
+# The schema is reasonably portable (with the exception of the engine
+# specification, which is MySQL-specific). Minor changes might be needed for
+# other databases.
+
+# To create the schema, either type the command:
+#
+# mysql -u <user> -p <password> <database> < dhcpdb_create.mysql
+#
+# ... at the command prompt, or log in to the MySQL database and at the "mysql>"
+# prompt, issue the command:
+#
+# source dhcpdb_create.mysql
+
+
+# Holds the IPv4 leases.
+CREATE TABLE lease4 (
+ address INT UNSIGNED PRIMARY KEY NOT NULL, # IPv4 address
+ hwaddr VARBINARY(20), # Hardware address
+ client_id VARBINARY(128), # Client ID
+ valid_lifetime INT UNSIGNED, # Length of the lease (seconds)
+ expire TIMESTAMP, # Expiration time of the lease
+ subnet_id INT UNSIGNED, # Subnet identification
+ fqdn_fwd BOOL, # Has forward DNS update been performed by a server
+ fqdn_rev BOOL, # Has reverse DNS update been performed by a server
+ hostname VARCHAR(255) # The FQDN of the client
+ ) ENGINE = INNODB;
+
+
+# Create search indexes for lease4 table.
+# index by hwaddr and subnet_id
+CREATE INDEX lease4_by_hwaddr_subnet_id ON lease4 (hwaddr, subnet_id);
+
+# index by client_id and subnet_id
+CREATE INDEX lease4_by_client_id_subnet_id ON lease4 (client_id, subnet_id);
+
+# Holds the IPv6 leases.
+# N.B. The use of a VARCHAR for the address is temporary for development:
+# it will eventually be replaced by BINARY(16).
+CREATE TABLE lease6 (
+ address VARCHAR(39) PRIMARY KEY NOT NULL, # IPv6 address
+ duid VARBINARY(128), # DUID
+ valid_lifetime INT UNSIGNED, # Length of the lease (seconds)
+ expire TIMESTAMP, # Expiration time of the lease
+ subnet_id INT UNSIGNED, # Subnet identification
+ pref_lifetime INT UNSIGNED, # Preferred lifetime
+ lease_type TINYINT, # Lease type (see lease6_types
+ # table for possible values)
+ iaid INT UNSIGNED, # See Section 12 of RFC 8415
+ prefix_len TINYINT UNSIGNED, # For IA_PD only
+ fqdn_fwd BOOL, # Has forward DNS update been performed by a server
+ fqdn_rev BOOL, # Has reverse DNS update been performed by a server
+ hostname VARCHAR(255) # The FQDN of the client
+
+ ) ENGINE = INNODB;
+
+# Create search indexes for lease4 table.
+# index by iaid, subnet_id, and duid
+CREATE INDEX lease6_by_iaid_subnet_id_duid ON lease6 (iaid, subnet_id, duid);
+
+# ... and a definition of lease6 types. This table is a convenience for
+# users of the database - if they want to view the lease table and use the
+# type names, they can join this table with the lease6 table.
+# Make sure those values match Lease6::LeaseType enum (see src/bin/dhcpsrv/
+# lease_mgr.h)
+CREATE TABLE lease6_types (
+ lease_type TINYINT PRIMARY KEY NOT NULL, # Lease type code.
+ name VARCHAR(5) # Name of the lease type
+ ) ENGINE = INNODB;
+START TRANSACTION;
+INSERT INTO lease6_types VALUES (0, 'IA_NA'); # Non-temporary v6 addresses
+INSERT INTO lease6_types VALUES (1, 'IA_TA'); # Temporary v6 addresses
+INSERT INTO lease6_types VALUES (2, 'IA_PD'); # Prefix delegations
+COMMIT;
+
+# Finally, the version of the schema. We start at 0.1 during development.
+# This table is only modified during schema upgrades. For historical reasons
+# (related to the names of the columns in the BIND 10 DNS database file), the
+# first column is called 'version' and not 'major'.
+# Note: This MUST be kept in step with src/share/database/scripts/mysql/dhcpdb_create.mysql,
+# which defines the schema for the unit tests.
+CREATE TABLE schema_version (
+ version INT PRIMARY KEY NOT NULL, # Major version number
+ minor INT # Minor version number
+ ) ENGINE = INNODB;
+START TRANSACTION;
+INSERT INTO schema_version VALUES (1, 0);
+COMMIT;
+
+# Notes:
+#
+# Indexes
+# =======
+# It is likely that additional indexes will be needed. However, the
+# increase in lookup performance from these will come at the expense
+# of a decrease in performance during insert operations due to the need
+# to update the indexes. For this reason, the need for additional indexes
+# will be determined by experiment during performance tests.
+#
+# The most likely additional indexes will cover the following columns:
+#
+# expire
+# To speed up the deletion of expired leases from the database.
+#
+# hwaddr and client_id
+# For lease stability: if a client requests a new lease, try to find an
+# existing or recently expired lease for it so that it can keep using the
+# same IP address.
+#
+# Field Sizes
+# ===========
+# If any of the VARxxx field sizes are altered, the lengths in the MySQL
+# backend source file (mysql_lease_mgr.cc) must be correspondingly changed.
+#
+# Portability
+# ===========
+# The "ENGINE = INNODB" on some tables is not portable to another database
+# and will need to be removed.
+#
+# Some columns contain binary data so are stored as VARBINARY instead of
+# VARCHAR. This may be non-portable between databases: in this case, the
+# definition should be changed to VARCHAR.
diff --git a/src/bin/admin/tests/dhcpdb_create_1.0.pgsql b/src/bin/admin/tests/dhcpdb_create_1.0.pgsql
new file mode 100644
index 0000000..cbe9ff9
--- /dev/null
+++ b/src/bin/admin/tests/dhcpdb_create_1.0.pgsql
@@ -0,0 +1,122 @@
+-- Copyright (C) 2012-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/.
+
+-- This is the Kea DHCP schema specification for PostgreSQL schema 1.0.
+-- It is used to create a 1.0 schema database for testing kea-admin's
+-- ability to upgrade Postgres databases.
+
+-- The schema is reasonably portable (with the exception of some field types
+-- specification, which are PostgreSQL-specific). Minor changes might be needed
+-- for other databases.
+
+-- To create the schema, either type the command:
+
+-- psql -U <user> -W <password> <database> < dhcpdb_create.pgsql
+
+-- ... at the command prompt, or log in to the PostgreSQL database and at the "postgres=#"
+-- prompt, issue the command:
+
+-- @dhcpdb_create.pgsql
+
+
+-- Holds the IPv4 leases.
+CREATE TABLE lease4 (
+ address BIGINT PRIMARY KEY NOT NULL, -- IPv4 address
+ hwaddr BYTEA, -- Hardware address
+ client_id BYTEA, -- Client ID
+ valid_lifetime BIGINT, -- Length of the lease (seconds)
+ expire TIMESTAMP WITH TIME ZONE, -- Expiration time of the lease
+ subnet_id BIGINT, -- Subnet identification
+ fqdn_fwd BOOLEAN, -- Has forward DNS update been performed by a server
+ fqdn_rev BOOLEAN, -- Has reverse DNS update been performed by a server
+ hostname VARCHAR(255) -- The FQDN of the client
+ );
+
+
+-- Create search indexes for lease4 table
+-- index by hwaddr and subnet_id
+CREATE INDEX lease4_by_hwaddr_subnet_id ON lease4 (hwaddr, subnet_id);
+
+-- index by client_id and subnet_id
+CREATE INDEX lease4_by_client_id_subnet_id ON lease4 (client_id, subnet_id);
+
+-- Holds the IPv6 leases.
+-- N.B. The use of a VARCHAR for the address is temporary for development:
+-- it will eventually be replaced by BINARY(16).
+CREATE TABLE lease6 (
+ address VARCHAR(39) PRIMARY KEY NOT NULL, -- IPv6 address
+ duid BYTEA, -- DUID
+ valid_lifetime BIGINT, -- Length of the lease (seconds)
+ expire TIMESTAMP WITH TIME ZONE, -- Expiration time of the lease
+ subnet_id BIGINT, -- Subnet identification
+ pref_lifetime BIGINT, -- Preferred lifetime
+ lease_type SMALLINT, -- Lease type (see lease6_types
+ -- table for possible values)
+ iaid INT, -- See Section 12 of RFC 8415
+ prefix_len SMALLINT, -- For IA_PD only
+ fqdn_fwd BOOLEAN, -- Has forward DNS update been performed by a server
+ fqdn_rev BOOLEAN, -- Has reverse DNS update been performed by a server
+ hostname VARCHAR(255) -- The FQDN of the client
+ );
+
+-- Create search indexes for lease4 table
+-- index by iaid, subnet_id, and duid
+CREATE INDEX lease6_by_iaid_subnet_id_duid ON lease6 (iaid, subnet_id, duid);
+
+-- ... and a definition of lease6 types. This table is a convenience for
+-- users of the database - if they want to view the lease table and use the
+-- type names, they can join this table with the lease6 table
+CREATE TABLE lease6_types (
+ lease_type SMALLINT PRIMARY KEY NOT NULL, -- Lease type code.
+ name VARCHAR(5) -- Name of the lease type
+ );
+START TRANSACTION;
+INSERT INTO lease6_types VALUES (0, 'IA_NA'); -- Non-temporary v6 addresses
+INSERT INTO lease6_types VALUES (1, 'IA_TA'); -- Temporary v6 addresses
+INSERT INTO lease6_types VALUES (2, 'IA_PD'); -- Prefix delegations
+COMMIT;
+
+-- Finally, the version of the schema. We start at 1.0 during development.
+-- This table is only modified during schema upgrades. For historical reasons
+-- (related to the names of the columns in the BIND 10 DNS database file), the
+-- first column is called "version" and not "major".
+-- Note: This MUST be kept in step with src/share/database/scripts/pgsql/dhcpdb_create.pgsql,
+-- which defines the schema for the unit tests.
+CREATE TABLE schema_version (
+ version INT PRIMARY KEY NOT NULL, -- Major version number
+ minor INT -- Minor version number
+ );
+START TRANSACTION;
+INSERT INTO schema_version VALUES (1, 0);
+COMMIT;
+
+-- Notes:
+
+-- Indexes
+-- =======
+-- It is likely that additional indexes will be needed. However, the
+-- increase in lookup performance from these will come at the expense
+-- of a decrease in performance during insert operations due to the need
+-- to update the indexes. For this reason, the need for additional indexes
+-- will be determined by experiment during performance tests.
+
+-- The most likely additional indexes will cover the following columns:
+
+-- hwaddr and client_id
+-- For lease stability: if a client requests a new lease, try to find an
+-- existing or recently expired lease for it so that it can keep using the
+-- same IP address.
+
+-- Field Sizes
+-- ===========
+-- If any of the VARxxx field sizes are altered, the lengths in the PgSQL
+-- backend source file (pgsql_lease_mgr.cc) must be correspondingly changed.
+
+-- Portability
+-- ===========
+-- Some columns contain binary data so are stored as BYTEA instead of
+-- VARCHAR. This may be non-portable between databases: in this case, the
+-- definition should be changed to VARCHAR.
diff --git a/src/bin/admin/tests/memfile_tests.sh.in b/src/bin/admin/tests/memfile_tests.sh.in
new file mode 100644
index 0000000..9a71b87
--- /dev/null
+++ b/src/bin/admin/tests/memfile_tests.sh.in
@@ -0,0 +1,239 @@
+#!/bin/sh
+
+# Copyright (C) 2014-2023 Internet Systems Consortium, Inc. ("ISC")
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# shellcheck disable=SC1091
+# SC1091: Not following: ... was not specified as input (see shellcheck -x).
+
+# shellcheck disable=SC2039
+# SC2039: In POSIX sh, 'local' is undefined.
+
+# Exit with error if commands exit with non-zero and if undefined variables are
+# used.
+set -eu
+
+# Include common test library.
+. "@abs_top_builddir@/src/lib/testutils/dhcp_test_lib.sh"
+
+# Locations of memfile tools
+kea_admin="@abs_top_builddir@/src/bin/admin/kea-admin"
+kea_lfc="@abs_top_builddir@/src/bin/lfc/kea-lfc"
+
+# Clean up any files used in testing.
+clean_up() {
+ remove_if_exists \
+ "${config_file-}" \
+ "${csv-}" \
+ "${csv_2-}" \
+ "@abs_top_builddir@/src/bin/admin/tests/kea.log" \
+ "@abs_top_builddir@/src/bin/admin/tests/kea.log.lock"
+}
+
+# Print location of CSV file. Accepts 4 or 6 as parameter.
+csv_file() {
+ local v="${1}"
+ printf '%s' "@abs_top_builddir@/src/bin/admin/tests/kea-dhcp${v}.csv"
+}
+
+# Print location of kea-dhcp[46] binaries. Accepts 4 or 6 as parameter.
+kea_dhcp() {
+ local v="${1}"
+ printf '%s' "@abs_top_builddir@/src/bin/dhcp${v}/kea-dhcp${v}"
+}
+
+# Print the minimum allowed number of header columns for v4.
+incomplete_memfile_header_v4() {
+ printf 'address,hwaddr,client_id,valid_lifetime,expire,subnet_id,fqdn_fwd,fqdn_rev,hostname'
+}
+
+# Print the minimum allowed number of header columns for v6.
+incomplete_memfile_header_v6() {
+ printf 'address,duid,valid_lifetime,expire,subnet_id,pref_lifetime,lease_type,iaid,prefix_len,fqdn_fwd,fqdn_rev,hostname'
+}
+
+# Print the entire header for v4.
+memfile_header_v4() {
+ printf '%s,state,user_context,pool_id' "$(incomplete_memfile_header_v4)"
+}
+
+# Print the entire header for v6.
+memfile_header_v6() {
+ printf '%s,hwaddr,state,user_context,hwtype,hwaddr_source,pool_id' "$(incomplete_memfile_header_v6)"
+}
+
+# Print data copied from mysql_upgrade_12_to_13_test and pgsql_upgrade_7_0_to_8_0.
+# @{
+memfile_data_v4() {
+ printf '0.0.0.10,32:30,33:30,40,1678900000,50,1,1,one&#x2cexample&#x2ccom,0,{"a":1&#x2c"b":2},0'
+}
+memfile_data_v6() {
+ printf '::10,32:30:33,30,1678900000,40,50,1,60,70,1,1,one&#x2cexample&#x2ccom,38:30,0,{"a":1&#x2c"b":2},90,16,0'
+}
+# @}
+
+# Print "server-id" configuration. Not available for v4.
+server_id_v4() {
+ :
+}
+
+# Print "server-id" configuration. Not available for v4.
+server_id_v6() {
+ printf ',
+ "server-id": {
+ "persist": false,
+ "type": "EN"
+ }
+'
+}
+
+# Starts Kea and sets PID. It logs to stdout and stderr if DEBUG is enabled.
+# Accepts 4 or 6 as parameter.
+start_kea_dhcp() {
+ local v="${1}"
+ "$(kea_dhcp "${v}")" -c "${config_file}" >> "@abs_top_builddir@/src/bin/admin/tests/kea.log" 2>&1 &
+ PID=${!}
+ sleep 1
+}
+
+# Test that Kea creates a correctly populated CSV file if configured with
+# persisting memfile.
+memfile_init_test() {
+ test_start 'memfile.init'
+
+ for v in 4 6; do
+ for i in no-file some-data; do
+ if test -n "${DEBUG+x}"; then
+ printf 'TRACE %s, %s\n' "${v}" "${i}"
+ fi
+ config=$(printf '%s%s%s' '
+{
+ "Dhcpx": {
+ "lease-database": {
+ "name": "@abs_top_builddir@/src/bin/admin/tests/kea-dhcpx.csv",
+ "persist": true,
+ "type": "memfile"
+ },
+ "loggers": [
+ {
+ "debuglevel": 99,
+ "name": "kea-dhcpx",
+ "output_options": [
+ {
+ "output": "@abs_top_builddir@/src/bin/admin/tests/kea.log"
+ }
+ ],
+ "severity": "DEBUG"
+ }
+ ]' \
+ "$(server_id_v${v})" \
+ '
+ }
+}
+')
+ config_file="@abs_top_builddir@/src/bin/admin/tests/kea-dhcp${v}.conf"
+ csv=$(csv_file "${v}")
+
+ if test "${i}" = 'some-data'; then
+ # Test that Kea accepts the output of lease-dump in memfile.
+ {
+ "memfile_header_v${v}"
+ printf '\n'
+ "memfile_data_v${v}"
+ printf '\n'
+ } > "${csv}"
+ else
+ # Make sure there is no CSV as the test requires.
+ rm -f "${csv}"
+ fi
+
+ # Set DHCP version in config.
+ printf '%s\n' "${config}" | \
+ sed "s#Dhcpx#Dhcp${v}#g;
+ s#dhcpx#dhcp${v}#g" \
+ > "${config_file}"
+
+ start_kea_dhcp "${v}"
+ # This assumes that the CSV creation + writing to CSV is atomic. Not
+ # sure if it is, but if this ever fails on the comparison further below,
+ # consider waiting here for line DHCPSRV_MEMFILE_LFC_SETUP in logs, even
+ # though it doesn't clearly signal end of CSV writing.
+ if ! wait_for_file "${csv}"; then
+ clean_up
+ clean_exit 1
+ fi
+ kill "${PID}" || true # process may have exited early due to errors
+ if ! wait_for_process_to_stop "${PID}"; then
+ clean_up
+ clean_exit 2
+ fi
+ content=$(head -n 1 "${csv}")
+ expected=$(memfile_header_v${v})
+ if test "${content}" != "${expected}"; then
+ printf 'ERROR: %s does not contain expected header.\n< %s\n> %s\n' \
+ "${csv}" "${content}" "${expected}" >&2
+ clean_up
+ clean_exit 3
+ fi
+
+ if grep -Fi ERROR "@abs_top_builddir@/src/bin/admin/tests/kea.log"; then
+ printf 'ERROR: loading memfile failed. See error above.\n' >&2
+ cat "${csv}"
+ clean_up
+ clean_exit 4
+ fi
+
+ clean_up
+ done
+ done
+
+ test_finish 0
+}
+
+# Test that kea-lfc is able to upgrade a CSV file with incomplete header.
+memfile_upgrade_test() {
+ test_start 'memfile.upgrade'
+
+ for v in 4 6; do
+ csv=$(csv_file "${v}")
+ "incomplete_memfile_header_v${v}" > "${csv}"
+ printf '\n' >> "${csv}"
+ csv_2="${csv}.2"
+
+ "${kea_lfc}" "-${v}" \
+ -c 'ignored-path' \
+ -f "${csv}.completed" \
+ -i "${csv}" \
+ -o "${csv}.output" \
+ -p "${csv}.pid" \
+ -x "${csv_2}"
+
+ content=$(cat "${csv_2}")
+ expected=$(memfile_header_v${v})
+ if test "${content}" != "${expected}"; then
+ printf 'ERROR: %s does not contain expected header.\n< %s\n> %s\n' \
+ "${csv}" "${content}" "${expected}" >&2
+ clean_up
+ clean_exit 1
+ fi
+
+ clean_up
+ done
+
+ test_finish 0
+}
+
+# shellcheck disable=SC2034
+# SC2034: ... appears unused. Verify use (or export if used externally).
+# reason: bin and bin_path are used in version_test
+{
+bin=$(basename "${kea_admin}")
+bin_path=$(dirname "${kea_admin}")
+version_test 'memfile.version' 'long_version_too_please'
+}
+
+memfile_init_test
+memfile_upgrade_test
diff --git a/src/bin/admin/tests/mysql_tests.sh.in b/src/bin/admin/tests/mysql_tests.sh.in
new file mode 100644
index 0000000..889ce52
--- /dev/null
+++ b/src/bin/admin/tests/mysql_tests.sh.in
@@ -0,0 +1,2892 @@
+#!/bin/sh
+
+# Copyright (C) 2014-2023 Internet Systems Consortium, Inc. ("ISC")
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# shellcheck disable=SC1091
+# SC1091: Not following: ... was not specified as input (see shellcheck -x).
+
+# shellcheck disable=SC2154
+# SC2154: ... is referenced but not assigned.
+# Reason: some variables are sourced.
+
+# Exit with error if commands exit with non-zero and if undefined variables are
+# used.
+set -eu
+
+# Include common test library.
+. "@abs_top_builddir@/src/lib/testutils/dhcp_test_lib.sh"
+
+# Include admin utilities
+. "@abs_top_builddir@/src/bin/admin/admin-utils.sh"
+
+# Set path to the production schema scripts
+db_scripts_dir="@abs_top_srcdir@/src/share/database/scripts"
+
+# Set location of the kea-admin.
+kea_admin="@abs_top_builddir@/src/bin/admin/kea-admin"
+
+# Convenience function for running an SQL statement
+# param hdr - text message to prepend to any error
+# param qry - SQL statement to run
+# param exp_value - optional expected value. This can be used IF the SQL statement
+# generates a single value, such as a SELECT which returns one column for one row.
+# Examples:
+#
+# qry="insert into lease6 (address, lease_type, subnet_id, state) values ($addr,$ltype,1,0)"
+# run_statement "#2" "$qry"
+#
+# qry="select leases from lease6_stat where subnet_id = 1 and lease_type = $ltype and state = 0"
+# run_statement "#3" "$qry" 1
+run_statement() {
+ hdr="$1";shift
+ qry="$1";shift
+ exp_value="${1-}" # Optional value. If not given, replace with empty string.
+
+ # Execute the statement
+ run_command \
+ mysql_execute "${qry}"
+ # shellcheck disable=SC2153
+ # SC2153: Possible misspelling: ... may not be assigned, but ... is.
+ # Reason for disable: OUTPUT is assigned in run_command.
+ value="${OUTPUT}"
+
+ # Execution should succeed
+ assert_eq 0 "${EXIT_CODE}" "$hdr: SQL=[$qry] failed: (expected status code %d, returned %d)"
+
+ # If there's an expected value, test it
+ if [ "x$exp_value" != "x" ]
+ then
+ assert_str_eq "$exp_value" "$value" "$hdr: SQL=[$qry] wrong: (expected value %s, returned %s)"
+ fi
+}
+
+# Wipe all tables from the DB:
+mysql_wipe() {
+ printf "Wiping whole database %s...\n" "${db_name}"
+
+ run_command \
+ mysql_execute_script "${db_scripts_dir}/mysql/dhcpdb_drop.mysql"
+ assert_eq 0 "${EXIT_CODE}" "mysql-wipe: drop table sql failed, expected %d, returned %d"
+}
+
+mysql_db_init_test() {
+ test_start "mysql.db-init"
+
+ # Let's wipe the whole database
+ mysql_wipe
+
+ # Ok, now let's initialize the database
+ run_command \
+ "${kea_admin}" db-init mysql -u "${db_user}" -p "${db_password}" -n "${db_name}" -d "${db_scripts_dir}"
+ assert_eq 0 "${EXIT_CODE}" "kea-admin db-init mysql failed, expected %d, returned non-zero status code %d"
+
+ # Ok, now let's check if the tables are indeed there.
+ # First table: schema_version. Should have 2 columns: version and minor.
+ run_command \
+ mysql -u"${db_user}" -p"${db_password}" "${db_name}" -e \
+ 'SELECT version, minor FROM schema_version'
+ assert_eq 0 "${EXIT_CODE}" "schema_version table is missing or broken. (expected status code %d, returned %d)"
+
+ # Second table: lease4
+ run_command \
+ mysql -u"${db_user}" -p"${db_password}" "${db_name}" -e \
+ 'SELECT address, hwaddr, client_id, valid_lifetime, expire, subnet_id, fqdn_fwd, fqdn_rev, hostname FROM lease4'
+ assert_eq 0 "${EXIT_CODE}" "lease4 table is missing or broken. (expected status code %d, returned %d)"
+
+ # Third table: lease6
+ run_command \
+ mysql -u"${db_user}" -p"${db_password}" "${db_name}" -e \
+ 'SELECT address, duid, valid_lifetime, expire, subnet_id, pref_lifetime, lease_type, iaid, prefix_len, fqdn_fwd, fqdn_rev, hostname, hwaddr, hwtype, hwaddr_source FROM lease6'
+ assert_eq 0 "${EXIT_CODE}" "lease6 table is missing or broken. (expected status code %d, returned %d)"
+
+ # Fourth table: lease6_types
+ run_command \
+ mysql -u"${db_user}" -p"${db_password}" "${db_name}" -e \
+ 'SELECT lease_type, name FROM lease6_types'
+ assert_eq 0 "${EXIT_CODE}" "lease6_types table is missing or broken. (expected status code %d, returned %d)"
+
+ # Fifth table: lease_hwaddr_source
+ run_command \
+ mysql -u"${db_user}" -p"${db_password}" "${db_name}" -e \
+ 'SELECT hwaddr_source, name FROM lease_hwaddr_source'
+ assert_eq 0 "${EXIT_CODE}" "lease_hwaddr_source table is missing or broken. (expected status code %d, returned %d)"
+
+ # Let's wipe the whole database
+ mysql_wipe
+
+ test_finish 0
+}
+
+mysql_db_version_test() {
+ test_start "mysql.db-version"
+
+ # Let's wipe the whole database
+ mysql_wipe
+
+ # Do not create any table so db-version will raise an error
+ printf 'Checking db-version error case...\n'
+ run_command \
+ "${kea_admin}" db-version mysql -u "${db_user}" -p "${db_password}" -n "${db_name}"
+ assert_eq 1 "${EXIT_CODE}" "schema_version table still exists. (expected %d, exit code %d)"
+
+ # Ok, now let's create a version 1.7
+ run_command \
+ mysql -u"${db_user}" -p"${db_password}" "${db_name}" -e \
+'CREATE TABLE schema_version (
+ version INT PRIMARY KEY NOT NULL,
+ minor INT
+);
+INSERT INTO schema_version VALUES (1, 7)'
+ assert_eq 0 "${EXIT_CODE}" "schema_version table cannot be created. (expected %d, exit code %d)"
+
+ run_command \
+ "${kea_admin}" db-version mysql -u "${db_user}" -p "${db_password}" -n "${db_name}"
+ version="${OUTPUT}"
+ assert_str_eq "1.7" "${version}" "Expected kea-admin to return %s, returned value was %s"
+
+ # Let's wipe the whole database
+ mysql_wipe
+
+ test_finish 0
+}
+
+mysql_db_version_with_extra_test() {
+ test_start "mysql.db-version_with_extra"
+
+ # Let's wipe the whole database
+ mysql_wipe
+
+ # Do not create any table so db-version will raise an error
+ printf 'Checking db-version error case...\n'
+ run_command \
+ "${kea_admin}" db-version mysql -u "${db_user}" -p "${db_password}" -n "${db_name}"
+ assert_eq 1 "${EXIT_CODE}" "schema_version table still exists. (expected %d, exit code %d)"
+
+ # Ok, now let's create a version 1.7
+ run_command \
+ mysql -u"${db_user}" -p"${db_password}" "${db_name}" -e \
+'CREATE TABLE schema_version (
+ version INT PRIMARY KEY NOT NULL,
+ minor INT
+);
+INSERT INTO schema_version VALUES (1, 7)'
+ assert_eq 0 "${EXIT_CODE}" "schema_version table cannot be created. (expected %d, exit code %d)"
+
+ # Single -x.
+ run_command \
+ "${kea_admin}" db-version mysql -u "${db_user}" -p "${db_password}" -n "${db_name}" -x --protocol=TCP
+ version="${OUTPUT}"
+ assert_eq 0 "${EXIT_CODE}" "kea-admin -x failed. (expected %d, exit code %d)"
+ assert_str_eq "1.7" "${version}" "Expected kea-admin to return %s, returned value was %s"
+
+ # Multiple -x.
+ run_command \
+ "${kea_admin}" db-version mysql -u "${db_user}" -p "${db_password}" -n "${db_name}" \
+ -x --protocol=TCP -x --hello 2> "@abs_top_builddir@/src/bin/admin/test-data"
+ assert_eq 2 "${EXIT_CODE}" "kea-admin -x -x succeeded. (expected %d, exit code %d)"
+ if ! grep -F "unknown option '--hello'" "@abs_top_builddir@/src/bin/admin/test-data"; then
+ printf 'second parameter --hello was not passed to mysql with -x\n'
+ test_finish 1
+ fi
+ rm -f "@abs_top_builddir@/src/bin/admin/test-data"
+
+ # Let's wipe the whole database
+ mysql_wipe
+
+ test_finish 0
+}
+
+mysql_host_reservation_init_test() {
+ test_start "mysql.host_reservation-init"
+
+ # Let's wipe the whole database
+ mysql_wipe
+
+ # Ok, now let's initialize the database
+ run_command \
+ "${kea_admin}" db-init mysql -u "${db_user}" -p "${db_password}" -n "${db_name}" -d "${db_scripts_dir}"
+ assert_eq 0 "${EXIT_CODE}" "kea-admin db-init mysql failed, expected %d, returned non-zero status code %d"
+
+ # Ok, now let's check if the tables are indeed there.
+ # First table: schema_version. Should have 2 columns: version and minor.
+ run_command \
+ mysql -u"${db_user}" -p"${db_password}" "${db_name}" -e \
+ 'SELECT version, minor FROM schema_version'
+ assert_eq 0 "${EXIT_CODE}" "schema_version table is missing or broken. (expected status code %d, returned %d)"
+
+ # Second table: hosts
+ run_command \
+ mysql -u"${db_user}" -p"${db_password}" "${db_name}" -e \
+ 'SELECT host_id, dhcp_identifier, dhcp_identifier_type, dhcp4_subnet_id, dhcp6_subnet_id, ipv4_address, hostname, dhcp4_client_classes, dhcp6_client_classes, dhcp4_next_server, dhcp4_server_hostname, dhcp4_boot_file_name, auth_key FROM hosts'
+ assert_eq 0 "${EXIT_CODE}" "hosts table is missing or broken. (expected status code %d, returned %d)"
+
+ # Third table: ipv6_reservations
+ run_command \
+ mysql -u"${db_user}" -p"${db_password}" "${db_name}" -e \
+ 'SELECT reservation_id, address, prefix_len, type, dhcp6_iaid, host_id FROM ipv6_reservations'
+ assert_eq 0 "${EXIT_CODE}" "ipv6_reservations table is missing or broken. (expected status code %d, returned %d)"
+
+ # Fourth table: dhcp4_options
+ run_command \
+ mysql -u"${db_user}" -p"${db_password}" "${db_name}" -e \
+ 'SELECT option_id, code, value, formatted_value, space, persistent, dhcp_client_class, dhcp4_subnet_id, host_id, scope_id FROM dhcp4_options'
+ assert_eq 0 "${EXIT_CODE}" "dhcp4_options table is missing or broken. (expected status code %d, returned %d)"
+
+ # Fifth table: dhcp6_options
+ run_command \
+ mysql -u"${db_user}" -p"${db_password}" "${db_name}" -e \
+ 'SELECT option_id, code, value, formatted_value, space, persistent, dhcp_client_class, dhcp6_subnet_id, host_id, scope_id FROM dhcp6_options'
+ assert_eq 0 "${EXIT_CODE}" "dhcp6_options table is missing or broken. (expected status code %d, returned %d)"
+
+ # Sixth table: host_identifier_type
+ run_command \
+ mysql -u"${db_user}" -p"${db_password}" "${db_name}" -e \
+ 'SELECT type, name FROM host_identifier_type'
+ assert_eq 0 "${EXIT_CODE}" "host_identifier_type table is missing or broken. (expected status code %d, returned %d)"
+
+ # Seventh table: dhcp_option_scope
+ run_command \
+ mysql -u"${db_user}" -p"${db_password}" "${db_name}" -e \
+ 'SELECT scope_id, scope_name FROM dhcp_option_scope'
+ assert_eq 0 "${EXIT_CODE}" "dhcp_option_scope table is missing or broken. (expected status code %d, returned %d)"
+
+ # Let's wipe the whole database
+ mysql_wipe
+
+ test_finish 0
+}
+
+# Upgrades an existing schema to a target newer version
+# param target_version - desired schema version as "major.minor"
+mysql_upgrade_schema_to_version() {
+ target_version=$1
+
+ upgrade_scripts_dir=${db_scripts_dir}/mysql
+
+ # Check if the scripts directory exists at all.
+ if [ ! -d ${upgrade_scripts_dir} ]; then
+ log_error "Invalid scripts directory: ${upgrade_scripts_dir}"
+ exit 1
+ fi
+
+ # Check if there are any files in it
+ num_files=$(find ${upgrade_scripts_dir} -name 'upgrade*.sh' -type f | wc -l)
+ if [ "${num_files}" -eq 0 ]; then
+ upgrade_scripts_dir=@abs_top_builddir@/src/share/database/scripts/mysql
+
+ # Check if the scripts directory exists at all.
+ if [ ! -d ${upgrade_scripts_dir} ]; then
+ log_error "Invalid scripts directory: ${upgrade_scripts_dir}"
+ exit 1
+ fi
+
+ # Check if there are any files in it
+ num_files=$(find "${upgrade_scripts_dir}" -name 'upgrade*.sh' -type f | wc -l)
+ fi
+
+ if [ "${num_files}" -eq 0 ]; then
+ log_error "No scripts in ${upgrade_scripts_dir}?"
+ exit 1
+ fi
+
+ for script in "${upgrade_scripts_dir}"/upgrade*.sh
+ do
+ version=$(mysql_version)
+ if [ "${version}" = "${target_version}" ]
+ then
+ break
+ fi
+
+ echo "Processing $script file..."
+ "${script}" --user="${db_user}" --password="${db_password}" "${db_name}"
+ done
+
+ echo "Schema upgraded to $version"
+}
+
+mysql_upgrade_12_to_13_test() {
+ # Check the output of colonSeparatedHex().
+ run_command \
+ mysql_execute 'SELECT colonSeparatedHex(HEX(0xF123456789))'
+ assert_eq 0 "${EXIT_CODE}" 'colonSeparatedHex() failed, expected exit code %d, actual %d'
+ assert_str_eq 'f1:23:45:67:89' "${OUTPUT}"
+
+ run_command \
+ mysql_execute 'SELECT colonSeparatedHex("")'
+ assert_eq 0 "${EXIT_CODE}" 'colonSeparatedHex() failed, expected exit code %d, actual %d'
+ assert_str_eq '' "${OUTPUT}"
+
+ run_command \
+ mysql_execute 'SELECT colonSeparatedHex(HEX(0xF))'
+ assert_eq 0 "${EXIT_CODE}" 'colonSeparatedHex() failed, expected exit code %d, actual %d'
+ assert_str_eq '0f' "${OUTPUT}"
+
+ run_command \
+ mysql_execute 'SELECT colonSeparatedHex(HEX(0xF1))'
+ assert_eq 0 "${EXIT_CODE}" 'colonSeparatedHex() failed, expected exit code %d, actual %d'
+ assert_str_eq 'f1' "${OUTPUT}"
+
+ run_command \
+ mysql_execute 'SELECT colonSeparatedHex(HEX(0xF12))'
+ assert_eq 0 "${EXIT_CODE}" 'colonSeparatedHex() failed, expected exit code %d, actual %d'
+ assert_str_eq '0f:12' "${OUTPUT}"
+
+ run_command \
+ mysql_execute 'SELECT colonSeparatedHex(HEX(458753))'
+ assert_eq 0 "${EXIT_CODE}" 'colonSeparatedHex() failed, expected exit code %d, actual %d'
+ assert_str_eq '07:00:01' "${OUTPUT}"
+
+ # Check lease4Dump*().
+ run_command \
+ mysql_execute "INSERT INTO lease4 VALUES(10,20,30,40,(SELECT FROM_UNIXTIME(1678900000)),50,1,1,'one,example,com',0,'{ \"a\": 1, \"b\": 2 }',NULL,NULL,0)"
+ assert_eq 0 "${EXIT_CODE}" 'INSERT INTO lease4 failed, expected exit code %d, actual %d'
+ assert_str_eq '' "${OUTPUT}"
+
+ run_command \
+ mysql_execute "CALL lease4DumpHeader()"
+ assert_eq 0 "${EXIT_CODE}" 'lease4DumpHeader() failed, expected exit code %d, actual %d'
+ assert_str_eq 'address,hwaddr,client_id,valid_lifetime,expire,subnet_id,fqdn_fwd,fqdn_rev,hostname,state,user_context,pool_id' "${OUTPUT}"
+
+ run_command \
+ mysql_execute "CALL lease4DumpData()"
+ assert_eq 0 "${EXIT_CODE}" 'lease4DumpData() failed, expected exit code %d, actual %d'
+ output=$(printf '%s' "${OUTPUT}" | sed 's/\t/,/g') # turn tabs into commas
+ assert_str_eq '0.0.0.10,32:30,33:30,40,1678900000,50,1,1,one&#x2cexample&#x2ccom,0,{ "a": 1&#x2c "b": 2 },0' "${output}"
+
+ # Check lease6Dump*().
+ run_command \
+ mysql_execute "INSERT INTO lease6 VALUES(inet6_aton('::10'),20,30,(SELECT FROM_UNIXTIME(1678900000)),40,50,1,60,70,1,1,'one,example,com',80,90,16,0,'{ \"a\": 1, \"b\": 2 }',0)"
+ assert_eq 0 "${EXIT_CODE}" 'INSERT INTO lease6 failed, expected exit code %d, actual %d'
+ assert_str_eq '' "${OUTPUT}"
+
+ run_command \
+ mysql_execute "CALL lease6DumpHeader()"
+ assert_eq 0 "${EXIT_CODE}" 'lease6DumpHeader() failed, expected exit code %d, actual %d'
+ assert_str_eq 'address,duid,valid_lifetime,expire,subnet_id,pref_lifetime,lease_type,iaid,prefix_len,fqdn_fwd,fqdn_rev,hostname,hwaddr,state,user_context,hwtype,hwaddr_source,pool_id' "${OUTPUT}"
+
+ run_command \
+ mysql_execute "CALL lease6DumpData()"
+ assert_eq 0 "${EXIT_CODE}" 'lease6DumpData() failed, expected exit code %d, actual %d'
+ output=$(printf '%s' "${OUTPUT}" | sed 's/\t/,/g') # turn tabs into commas
+ assert_str_eq '::10,32:30,30,1678900000,40,50,1,60,70,1,1,one&#x2cexample&#x2ccom,38:30,0,{ "a": 1&#x2c "b": 2 },90,16,0' "${output}"
+
+ # Check lease4Upload().
+ run_command \
+ mysql_execute "CALL lease4Upload('192.0.0.0','ff0102030405','01ff0102030405',7200,1234567890,1,0,0,'',0,'',0)"
+ assert_eq 0 "${EXIT_CODE}" 'lease4Upload() failed, expected exit code %d, actual %d'
+ assert_str_eq '' "${OUTPUT}"
+
+ # Check lease6Upload().
+ run_command \
+ mysql_execute "CALL lease6Upload('2001:db8::','000100012955cb80ff0102030407',7200,1234567890,1,3600,0,1,128,0,0,'','ff0102030407',0,'',90,16,0)"
+ assert_eq 0 "${EXIT_CODE}" 'lease6Upload() failed, expected exit code %d, actual %d'
+ assert_str_eq '' "${OUTPUT}"
+}
+
+mysql_upgrade_13_to_14_test() {
+ # Check function source code
+ run_command \
+ mysql_execute "select action_statement from information_schema.TRIGGERS where trigger_schema = '${db_name}' and trigger_name = 'dhcp4_shared_network_BDEL'"
+ assert_eq 0 "${EXIT_CODE}" "function func_dhcp4_shared_network_BDEL() broken or missing. (expected status code %d, returned %d)"
+
+ count=$(echo "${OUTPUT}" | grep -Eci 'UPDATE dhcp4_subnet SET shared_network_name = NULL') || true
+ assert_eq 1 "${count}" "function func_dhcp4_shared_network_BDEL() is missing changed line. (expected count %d, returned %d)"
+
+ # Check function source code
+ run_command \
+ mysql_execute "select action_statement from information_schema.TRIGGERS where trigger_schema = '${db_name}' and trigger_name = 'dhcp6_shared_network_BDEL'"
+ assert_eq 0 "${EXIT_CODE}" "function func_dhcp6_shared_network_BDEL() broken or missing. (expected status code %d, returned %d)"
+
+ count=$(echo "${OUTPUT}" | grep -Eci 'UPDATE dhcp6_subnet SET shared_network_name = NULL') || true
+ assert_eq 1 "${count}" "function func_dhcp6_shared_network_BDEL() is missing changed line. (expected count %d, returned %d)"
+
+ # user_context should have been added to dhcp4_client_class
+ qry="select user_context from dhcp4_client_class limit 1"
+ run_command \
+ mysql_execute "${qry}"
+ assert_eq 0 "${EXIT_CODE}" "${qry}. (expected status code %d, returned %d)"
+
+ # user_context should have been added to dhcp6_client_class
+ qry="select user_context from dhcp6_client_class limit 1"
+ run_command \
+ mysql_execute "${qry}"
+ assert_eq 0 "${EXIT_CODE}" "${qry}. (expected status code %d, returned %d)"
+
+ # -- lease counting tests --
+
+ # Check that @json_supported is NULL by default.
+ query="SELECT @json_supported"
+ run_command \
+ mysql_execute "${query}"
+ assert_eq 0 "${EXIT_CODE}" "${query}: expected %d, returned %d"
+ assert_str_eq "NULL" "${OUTPUT}" "${query}: expected output %s, returned %s"
+
+ # Clean up.
+ query="DELETE FROM lease4; DELETE FROM lease6"
+ run_command \
+ mysql_execute "${query}"
+ assert_eq 0 "${EXIT_CODE}" "${query}: expected %d, returned %d"
+ assert_str_eq "" "${OUTPUT}" "${query}: expected output %s, returned %s"
+
+ # Populate the lease tables. Also check that @json_supported is NULL at
+ # first, and then it is set after inserting leases.
+ run_command \
+ mysql_execute "
+ SELECT @json_supported;
+ INSERT INTO lease4 (address, subnet_id, state, user_context) VALUES (100,1,0,
+ '{\"ISC\": {\"client-classes\": [\"ALL\", \"KNOWN\", \"bar\", \"foo\"] } }');
+ SELECT @json_supported;
+ INSERT INTO lease4 (address, subnet_id, state, user_context) VALUES (101,1,1,
+ '{\"ISC\": {\"client-classes\": [\"ALL\", \"KNOWN\", \"bar\", \"foo\"] } }');
+ INSERT INTO lease4 (address, subnet_id, state, user_context) VALUES (102,1,2,
+ '{\"ISC\": {\"client-classes\": [\"ALL\", \"KNOWN\", \"bar\", \"foo\"] } }');
+ INSERT INTO lease4 (address, subnet_id, state, user_context) VALUES (103,1,0,
+ '{\"ISC\": {\"client-classes\": [\"ALL\", \"KNOWN\", \"bar\", \"foo\"] } }');
+ INSERT INTO lease4 (address, subnet_id, state, user_context) VALUES (104,1,1,
+ '{\"ISC\": {\"client-classes\": [\"ALL\", \"KNOWN\", \"bar\", \"foo\"] } }');
+ INSERT INTO lease4 (address, subnet_id, state, user_context) VALUES (105,1,1,
+ '{\"ISC\": {\"client-classes\": [\"ALL\", \"KNOWN\", \"bar\", \"foo\"] } }');
+ INSERT INTO lease6 (address, lease_type, subnet_id, state, user_context) VALUES (100,0,1,0,
+ '{\"ISC\": {\"client-classes\": [\"ALL\", \"KNOWN\", \"bar\", \"foo\"] } }');
+ INSERT INTO lease6 (address, lease_type, subnet_id, state, user_context) VALUES (101,0,1,1,
+ '{\"ISC\": {\"client-classes\": [\"ALL\", \"KNOWN\", \"bar\", \"foo\"] } }');
+ INSERT INTO lease6 (address, lease_type, subnet_id, state, user_context) VALUES (102,0,1,0,
+ '{\"ISC\": {\"client-classes\": [\"ALL\", \"KNOWN\", \"bar\", \"foo\"] } }');
+ INSERT INTO lease6 (address, lease_type, subnet_id, state, user_context) VALUES (103,0,1,1,
+ '{\"ISC\": {\"client-classes\": [\"ALL\", \"KNOWN\", \"bar\", \"foo\"] } }');
+ INSERT INTO lease6 (address, lease_type, subnet_id, state, user_context) VALUES (104,2,1,0,
+ '{\"ISC\": {\"client-classes\": [\"ALL\", \"KNOWN\", \"bar\", \"foo\"] } }');
+ INSERT INTO lease6 (address, lease_type, subnet_id, state, user_context) VALUES (105,2,1,1,
+ '{\"ISC\": {\"client-classes\": [\"ALL\", \"KNOWN\", \"bar\", \"foo\"] } }');
+ INSERT INTO lease6 (address, lease_type, subnet_id, state, user_context) VALUES (106,2,1,0,
+ '{\"ISC\": {\"client-classes\": [\"ALL\", \"KNOWN\", \"bar\", \"foo\"] } }');
+ INSERT INTO lease6 (address, lease_type, subnet_id, state, user_context) VALUES (107,2,1,1,
+ '{\"ISC\": {\"client-classes\": [\"ALL\", \"KNOWN\", \"bar\", \"foo\"] } }');
+ SELECT @json_supported;
+ "
+ assert_eq 0 "${EXIT_CODE}" 'INSERT INTO leases when upgrading from 13 to 14 failed. expected %d, returned %d'
+ one_line=$(printf '%s' "${OUTPUT}" | tr '\n' ' ')
+ json_supported=$(printf '%s' "${one_line}" | grep -Eo '[0-1]$') || true
+ if test "${json_supported}" != 0 && test "${json_supported}" != 1; then
+ assert_str_eq '[01]' "${json_supported}" "INSERT INTO leases when upgrading from 13 to 14 does not set @json_supported. expected '[01]', returned '${json_supported}'"
+ fi
+ if ! printf '%s' "${one_line}" | grep -E "NULL ${json_supported} ${json_supported}" > /dev/null; then
+ assert_str_eq 'NULL [01] [01]' "${one_line}" "INSERT INTO leases when upgrading from 13 to 14 does not set @json_supported. expected 'NULL [01] [01]', returned '${one_line}'"
+ fi
+
+ for v in 4 6; do
+ # Check that client classes were counted correctly.
+ query="SELECT leases FROM lease${v}_stat_by_client_class WHERE client_class = 'foo' LIMIT 1"
+ run_command \
+ mysql_execute "${query}"
+ assert_eq 0 "${EXIT_CODE}" "${query}: expected %d, returned %d"
+ if test "${json_supported}" = 1; then
+ assert_str_eq 2 "${OUTPUT}" "${query}: expected output %s, returned %s"
+ else
+ assert_str_eq '' "${OUTPUT}" "${query}: expected output %s, returned %s"
+ fi
+
+ # -- Verify some calls to checkLeaseXLimits(). --
+
+ query="SELECT checkLease${v}Limits('')"
+ run_command \
+ mysql_execute "${query}"
+ # Should fail with ERROR 4037 (HY000): Unexpected end of JSON text in argument 1 to function 'json_extract'
+ assert_eq 1 "${EXIT_CODE}" "${query}: expected %d, returned %d"
+ assert_str_eq '' "${OUTPUT}" "${query}: expected output %s, returned %s"
+
+ query="SELECT checkLease${v}Limits('{}')"
+ run_command \
+ mysql_execute "${query}"
+ if test "${json_supported}" = 1; then
+ assert_eq 0 "${EXIT_CODE}" "${query}: expected %d, returned %d"
+ else
+ # Should fail with ERROR 1305 (42000) at line 1: FUNCTION keatest.JSON_EXTRACT does not exist
+ assert_eq 1 "${EXIT_CODE}" "${query}: expected %d, returned %d"
+ fi
+ assert_str_eq '' "${OUTPUT}" "${query}: expected output %s, returned %s"
+
+ query="SELECT checkLease${v}Limits('{ \"ISC\": { \"limits\": { \"client-classes\": [ { \"name\": \"foo\", \"address-limit\": 1 } ] } } }')"
+ run_command \
+ mysql_execute "${query}"
+ if test "${json_supported}" = 1; then
+ assert_eq 0 "${EXIT_CODE}" "${query}: expected %d, returned %d"
+ assert_str_eq "address limit 1 for client class \"foo\", current lease count 2" "${OUTPUT}" "${query}: expected output %s, returned %s"
+ else
+ # Should fail with ERROR 1305 (42000) at line 1: FUNCTION keatest.JSON_EXTRACT does not exist
+ assert_eq 1 "${EXIT_CODE}" "${query}: expected %d, returned %d"
+ assert_str_eq '' "${OUTPUT}" "${query}: expected output %s, returned %s"
+ fi
+
+ query="SELECT checkLease${v}Limits('{ \"ISC\": { \"limits\": { \"subnet\": { \"id\": 1, \"address-limit\": 1 } } } }')"
+ run_command \
+ mysql_execute "${query}"
+ if test "${json_supported}" = 1; then
+ assert_eq 0 "${EXIT_CODE}" "${query}: expected %d, returned %d"
+ assert_str_eq "address limit 1 for subnet ID 1, current lease count 2" "${OUTPUT}" "${query}: expected output %s, returned %s"
+ else
+ # Should fail with ERROR 1305 (42000) at line 1: FUNCTION keatest.JSON_EXTRACT does not exist
+ assert_eq 1 "${EXIT_CODE}" "${query}: expected %d, returned %d"
+ assert_str_eq '' "${OUTPUT}" "${query}: expected output %s, returned %s"
+ fi
+
+ query="SELECT checkLease${v}Limits('{ \"ISC\": { \"limits\": { \"client-classes\": [ { \"name\": \"foo\", \"address-limit\": 2 } ] } } }')"
+ run_command \
+ mysql_execute "${query}"
+ if test "${json_supported}" = 1; then
+ assert_eq 0 "${EXIT_CODE}" "${query}: expected %d, returned %d"
+ assert_str_eq "address limit 2 for client class \"foo\", current lease count 2" "${OUTPUT}" "${query}: expected output %s, returned %s"
+ else
+ # Should fail with ERROR 1305 (42000) at line 1: FUNCTION keatest.JSON_EXTRACT does not exist
+ assert_eq 1 "${EXIT_CODE}" "${query}: expected %d, returned %d"
+ assert_str_eq '' "${OUTPUT}" "${query}: expected output %s, returned %s"
+ fi
+
+ query="SELECT checkLease${v}Limits('{ \"ISC\": { \"limits\": { \"subnet\": { \"id\": 1, \"address-limit\": 2 } } } }')"
+ run_command \
+ mysql_execute "${query}"
+ if test "${json_supported}" = 1; then
+ assert_eq 0 "${EXIT_CODE}" "${query}: expected %d, returned %d"
+ assert_str_eq "address limit 2 for subnet ID 1, current lease count 2" "${OUTPUT}" "${query}: expected output %s, returned %s"
+ else
+ # Should fail with ERROR 1305 (42000) at line 1: FUNCTION keatest.JSON_EXTRACT does not exist
+ assert_eq 1 "${EXIT_CODE}" "${query}: expected %d, returned %d"
+ assert_str_eq '' "${OUTPUT}" "${query}: expected output %s, returned %s"
+ fi
+
+ query="SELECT checkLease${v}Limits('{ \"ISC\": { \"limits\": { \"client-classes\": [ { \"name\": \"foo\", \"address-limit\": 4 } ] } } }')"
+ run_command \
+ mysql_execute "${query}"
+ if test "${json_supported}" = 1; then
+ assert_eq 0 "${EXIT_CODE}" "${query}: expected %d, returned %d"
+ else
+ # Should fail with ERROR 1305 (42000) at line 1: FUNCTION keatest.JSON_EXTRACT does not exist
+ assert_eq 1 "${EXIT_CODE}" "${query}: expected %d, returned %d"
+ fi
+ assert_str_eq '' "${OUTPUT}" "${query}: expected output %s, returned %s"
+
+ query="SELECT checkLease${v}Limits('{ \"ISC\": { \"limits\": { \"subnet\": { \"id\": 1, \"address-limit\": 4 } } } }')"
+ run_command \
+ mysql_execute "${query}"
+ if test "${json_supported}" = 1; then
+ assert_eq 0 "${EXIT_CODE}" "${query}: expected %d, returned %d"
+ else
+ # Should fail with ERROR 1305 (42000) at line 1: FUNCTION keatest.JSON_EXTRACT does not exist
+ assert_eq 1 "${EXIT_CODE}" "${query}: expected %d, returned %d"
+ fi
+ assert_str_eq '' "${OUTPUT}" "${query}: expected output %s, returned %s"
+
+ query="SELECT checkLease${v}Limits('{ \"ISC\": { \"limits\": { \"client-classes\": [ { \"name\": \"foo\", \"address-limit\": 1 }, { \"name\": \"bar\", \"address-limit\": 1 } ], \"subnet\": { \"id\": 1, \"address-limit\": 1 } } } }')"
+ run_command \
+ mysql_execute "${query}"
+ if test "${json_supported}" = 1; then
+ assert_eq 0 "${EXIT_CODE}" "${query}: expected %d, returned %d"
+ assert_str_eq "address limit 1 for client class \"foo\", current lease count 2" "${OUTPUT}" "${query}: expected output %s, returned %s"
+ else
+ # Should fail with ERROR 1305 (42000) at line 1: FUNCTION keatest.JSON_EXTRACT does not exist
+ assert_eq 1 "${EXIT_CODE}" "${query}: expected %d, returned %d"
+ assert_str_eq '' "${OUTPUT}" "${query}: expected output %s, returned %s"
+ fi
+
+ query="SELECT checkLease${v}Limits('{ \"ISC\": { \"limits\": { \"client-classes\": [ { \"name\": \"foo\", \"address-limit\": 2 }, { \"name\": \"bar\", \"address-limit\": 4 } ], \"subnet\": { \"id\": 1, \"address-limit\": 4 } } } }')"
+ run_command \
+ mysql_execute "${query}"
+ if test "${json_supported}" = 1; then
+ assert_eq 0 "${EXIT_CODE}" "${query}: expected %d, returned %d"
+ assert_str_eq "address limit 2 for client class \"foo\", current lease count 2" "${OUTPUT}" "${query}: expected output %s, returned %s"
+ else
+ # Should fail with ERROR 1305 (42000) at line 1: FUNCTION keatest.JSON_EXTRACT does not exist
+ assert_eq 1 "${EXIT_CODE}" "${query}: expected %d, returned %d"
+ assert_str_eq '' "${OUTPUT}" "${query}: expected output %s, returned %s"
+ fi
+
+ query="SELECT checkLease${v}Limits('{ \"ISC\": { \"limits\": { \"client-classes\": [ { \"name\": \"foo\", \"address-limit\": 4 }, { \"name\": \"bar\", \"address-limit\": 4 } ], \"subnet\": { \"id\": 1, \"address-limit\": 2 } } } }')"
+ run_command \
+ mysql_execute "${query}"
+ if test "${json_supported}" = 1; then
+ assert_eq 0 "${EXIT_CODE}" "${query}: expected %d, returned %d"
+ assert_str_eq "address limit 2 for subnet ID 1, current lease count 2" "${OUTPUT}" "${query}: expected output %s, returned %s"
+ else
+ # Should fail with ERROR 1305 (42000) at line 1: FUNCTION keatest.JSON_EXTRACT does not exist
+ assert_eq 1 "${EXIT_CODE}" "${query}: expected %d, returned %d"
+ assert_str_eq '' "${OUTPUT}" "${query}: expected output %s, returned %s"
+ fi
+
+ query="SELECT checkLease${v}Limits('{ \"ISC\": { \"limits\": { \"client-classes\": [ { \"name\": \"foo\", \"address-limit\": 4 }, { \"name\": \"bar\", \"address-limit\": 4 } ], \"subnet\": { \"id\": 1, \"address-limit\": 4 } } } }')"
+ run_command \
+ mysql_execute "${query}"
+ if test "${json_supported}" = 1; then
+ assert_eq 0 "${EXIT_CODE}" "${query}: expected %d, returned %d"
+ else
+ # Should fail with ERROR 1305 (42000) at line 1: FUNCTION keatest.JSON_EXTRACT does not exist
+ assert_eq 1 "${EXIT_CODE}" "${query}: expected %d, returned %d"
+ fi
+ assert_str_eq '' "${OUTPUT}" "${query}: expected output %s, returned %s"
+ done
+
+ # Check that leases counters cannot go negative.
+ for v in 4 6; do
+ query="SELECT leases FROM lease${v}_stat WHERE subnet_id = 1 AND state = 0 LIMIT 1"
+ run_command \
+ mysql_execute "${query}"
+ assert_eq 0 "${EXIT_CODE}" "${query}: expected %d, returned %d"
+ assert_str_eq '2' "${OUTPUT}" "${query}: expected output %s, returned %s"
+
+ # Artificially change the subnet counter from 2 down to 1.
+ query="UPDATE lease${v}_stat SET leases = 1 WHERE subnet_id = 1 AND state = 0"
+ run_command \
+ mysql_execute "${query}"
+ assert_eq 0 "${EXIT_CODE}" "${query}: expected %d, returned %d"
+ assert_str_eq '' "${OUTPUT}" "${query}: expected output %s, returned %s"
+
+ if test "${json_supported}" = 1; then
+ query="SELECT leases FROM lease${v}_stat_by_client_class WHERE client_class = 'foo' LIMIT 1"
+ run_command \
+ mysql_execute "${query}"
+ assert_eq 0 "${EXIT_CODE}" "${query}: expected %d, returned %d"
+ assert_str_eq '2' "${OUTPUT}" "${query}: expected output %s, returned %s"
+
+ # Artificially change the client class counter from 2 down to 1.
+ query="UPDATE lease${v}_stat_by_client_class SET leases = 1 WHERE client_class = 'foo'"
+ run_command \
+ mysql_execute "${query}"
+ assert_eq 0 "${EXIT_CODE}" "${query}: expected %d, returned %d"
+ assert_str_eq '' "${OUTPUT}" "${query}: expected output %s, returned %s"
+ fi
+
+ # Clean up.
+ query="DELETE FROM lease${v}"
+ run_command \
+ mysql_execute "${query}"
+ assert_eq 0 "${EXIT_CODE}" "${query}: expected %d, returned %d"
+ assert_str_eq '' "${OUTPUT}" "${query}: expected output %s, returned %s"
+
+ # SELECT should finish successfully and the subnet counter should be 0.
+ query="SELECT leases FROM lease${v}_stat WHERE subnet_id = 1 AND state = 0 LIMIT 1"
+ run_command \
+ mysql_execute "${query}"
+ assert_eq 0 "${EXIT_CODE}" "${query}: expected %d, returned %d"
+ assert_str_eq '0' "${OUTPUT}" "${query}: expected output %s, returned %s"
+
+ if test "${json_supported}" = 1; then
+ # SELECT should finish successfully and the client class counter should be 0.
+ query="SELECT leases FROM lease${v}_stat_by_client_class WHERE client_class = 'foo' LIMIT 1"
+ run_command \
+ mysql_execute "${query}"
+ assert_eq 0 "${EXIT_CODE}" "${query}: expected %d, returned %d"
+ assert_str_eq '0' "${OUTPUT}" "${query}: expected output %s, returned %s"
+ fi
+ done
+}
+
+mysql_upgrade_14_to_15_test() {
+ # table: dhcp4_options new cancelled column.
+ qry="select cancelled from dhcp4_options"
+ run_statement "dhcp4_options" "$qry"
+
+ # table: dhcp6_options new cancelled column.
+ qry="select cancelled from dhcp6_options"
+ run_statement "dhcp6_options" "$qry"
+
+ # Check if offer_lifetime was added to dhcp4_shared_network table.
+ qry="SELECT offer_lifetime from dhcp4_shared_network limit 1"
+ run_command \
+ mysql_execute "${qry}"
+ assert_eq 0 "${EXIT_CODE}" "${qry}. (expected status code %d, returned %d)"
+
+ # Check if offer_lifetime was added to dhcp4_subnet table.
+ qry="SELECT offer_lifetime from dhcp4_subnet limit 1"
+ run_command \
+ mysql_execute "${qry}"
+ assert_eq 0 "${EXIT_CODE}" "${qry}. (expected status code %d, returned %d)"
+
+ # Check if offer_lifetime was added to dhcp4_client_class table.
+ qry="SELECT offer_lifetime from dhcp4_client_class limit 1"
+ run_command \
+ mysql_execute "${qry}"
+ assert_eq 0 "${EXIT_CODE}" "${qry}. (expected status code %d, returned %d)"
+}
+
+mysql_upgrade_16_to_17_test() {
+ # Check if allocator was added to dhcp4_shared_network table.
+ qry="SELECT allocator from dhcp4_shared_network limit 1"
+ run_command \
+ mysql_execute "${qry}"
+ assert_eq 0 "${EXIT_CODE}" "${qry}. (expected status code %d, returned %d)"
+
+ # Check if allocator was added to dhcp6_shared_network table.
+ qry="SELECT allocator from dhcp6_shared_network limit 1"
+ run_command \
+ mysql_execute "${qry}"
+ assert_eq 0 "${EXIT_CODE}" "${qry}. (expected status code %d, returned %d)"
+
+ # Check if pd_allocator was added to dhcp6_shared_network table.
+ qry="SELECT pd_allocator from dhcp6_shared_network limit 1"
+ run_command \
+ mysql_execute "${qry}"
+ assert_eq 0 "${EXIT_CODE}" "${qry}. (expected status code %d, returned %d)"
+
+ # Check if allocator was added to dhcp4_subnet table.
+ qry="SELECT allocator from dhcp4_subnet limit 1"
+ run_command \
+ mysql_execute "${qry}"
+ assert_eq 0 "${EXIT_CODE}" "${qry}. (expected status code %d, returned %d)"
+
+ # Check if allocator was added to dhcp6_subnet table.
+ qry="SELECT allocator from dhcp6_subnet limit 1"
+ run_command \
+ mysql_execute "${qry}"
+ assert_eq 0 "${EXIT_CODE}" "${qry}. (expected status code %d, returned %d)"
+
+ # Check if pd_allocator was added to dhcp6_subnet table.
+ qry="SELECT pd_allocator from dhcp6_subnet limit 1"
+ run_command \
+ mysql_execute "${qry}"
+ assert_eq 0 "${EXIT_CODE}" "${qry}. (expected status code %d, returned %d)"
+}
+
+mysql_upgrade_17_to_18_test() {
+ # lease4 client_id should support 255 long strings.
+ qry="insert into lease4 (address, client_id, subnet_id) values (1, '123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345', 1)"
+ run_statement "lease4_255_long_client_id" "$qry"
+
+ # lease4 relay_id should support 255 long strings.
+ qry="insert into lease4 (address, remote_id, subnet_id) values (2, '123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345', 1)"
+ run_statement "lease4_255_long_relay_id" "$qry"
+
+ # lease4 remote_id should support 255 long strings.
+ qry="insert into lease4 (address, remote_id, subnet_id) values (3, '123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345', 1)"
+ run_statement "lease4_255_long_remote_id" "$qry"
+
+ # hosts dhcp_identifier should support 255 long strings.
+ qry="insert into hosts (dhcp_identifier, dhcp_identifier_type) values ('123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345', 0)"
+ run_statement "hosts_255_long_dhcp_identifier" "$qry"
+
+ #lease6 duid should support 130 long strings.
+ qry="insert into lease6 values(inet6_aton('::10'),12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890,30,(SELECT FROM_UNIXTIME(1642000000)),40,50,1,60,70,1,1,'one.example.com',80,90,16,0,NULL,0)"
+ run_statement "lease6_130_long_duid" "$qry"
+
+ #lease4_pool_stat new table.
+ run_command \
+ mysql -u"${db_user}" -p"${db_password}" "${db_name}" -e \
+ 'SELECT subnet_id, pool_id, state, leases FROM lease4_pool_stat'
+ assert_eq 0 "${EXIT_CODE}" "lease4_pool_stat table is missing or broken. (expected status code %d, returned %d)"
+
+ #lease6_pool_stat new table.
+ run_command \
+ mysql -u"${db_user}" -p"${db_password}" "${db_name}" -e \
+ 'SELECT subnet_id, pool_id, lease_type, state, leases FROM lease6_pool_stat'
+ assert_eq 0 "${EXIT_CODE}" "lease6_pool_stat table is missing or broken. (expected status code %d, returned %d)"
+
+ #lease6_relay_id new table.
+ run_command \
+ mysql -u"${db_user}" -p"${db_password}" "${db_name}" -e \
+ 'SELECT extended_info_id, relay_id, lease_addr FROM lease6_relay_id'
+ assert_eq 0 "${EXIT_CODE}" "lease6_relay_id table is missing or broken. (expected status code %d, returned %d)"
+
+ #lease6_remote_id new table.
+ run_command \
+ mysql -u"${db_user}" -p"${db_password}" "${db_name}" -e \
+ 'SELECT extended_info_id, remote_id, lease_addr FROM lease6_remote_id'
+ assert_eq 0 "${EXIT_CODE}" "lease6_remote_id table is missing or broken. (expected status code %d, returned %d)"
+}
+
+mysql_upgrade_18_to_19_test() {
+ # Verify that lease6 address is binary. This is sort of overkill as many of
+ # the prior upgrade tests manipulate lease6 records.
+ qry="insert into lease6 values(inet6_aton('3001::99'),'18219',30,(SELECT FROM_UNIXTIME(1642000000)),40,50,1,60,70,1,1,'one.example.com',80,90,16,0,NULL,0)"
+ run_statement "lease6_insert" "$qry"
+
+ qry="select inet6_ntoa(address) from lease6 where duid = '18219';"
+ run_statement "lease6_insert" "$qry" "3001::99"
+
+ # Verify that ipv6_reservations address is binary.
+ qry="\
+ insert into hosts(host_id, dhcp_identifier, dhcp_identifier_type) values (18219, '18219', 1); \
+ insert into ipv6_reservations (address, prefix_len, type, dhcp6_iaid, host_id) \
+ values (inet6_aton('3001::99'), 128, 1, 123, 18219); \
+ select inet6_ntoa(address) from ipv6_reservations where host_id = 18219;"
+
+ run_statement "ipv6_reservations_insert" "$qry" "3001::99"
+}
+
+mysql_upgrade_test() {
+
+ test_start "mysql.upgrade"
+
+ # Let's wipe the whole database
+ mysql_wipe
+
+ # Initialize database to schema 1.0.
+ mysql -u"${db_user}" -p"${db_password}" "${db_name}" < "@abs_top_srcdir@/src/bin/admin/tests/dhcpdb_create_1.0.mysql"
+
+ # Sanity check - verify that it reports version 1.0.
+ version=$("${kea_admin}" db-version mysql -u "${db_user}" -p "${db_password}" -n "${db_name}" -d "${db_scripts_dir}")
+ assert_str_eq "1.0" "${version}" "Expected kea-admin to return %s, returned value was %s"
+
+ # Let's upgrade it to the latest version.
+ run_command \
+ "${kea_admin}" db-upgrade mysql -u "${db_user}" -p "${db_password}" -n "${db_name}" -d "${db_scripts_dir}"
+ assert_eq 0 "${EXIT_CODE}" "kea-admin db-upgrade mysql failed, expected %d, returned non-zero status code %d\n"
+
+ # Verify that the upgraded schema reports the latest version.
+ version=$("${kea_admin}" db-version mysql -u "${db_user}" -p "${db_password}" -n "${db_name}" -d "${db_scripts_dir}")
+ assert_str_eq "19.0" "${version}" "Expected kea-admin to return %s, returned value was %s"
+
+ # Let's check that the new tables are indeed there.
+
+ #table: lease6 (upgrade 1.0 -> 2.0)
+ run_command \
+ mysql -u"${db_user}" -p"${db_password}" "${db_name}" -e \
+ 'SELECT hwaddr, hwtype, hwaddr_source FROM lease6'
+ assert_eq 0 "${EXIT_CODE}" "lease6 table not upgraded to 2.0 (expected status code %d, returned %d)"
+
+ #table: lease_hwaddr_source (upgrade 1.0 -> 2.0)
+ run_command \
+ mysql -u"${db_user}" -p"${db_password}" "${db_name}" -e \
+ 'SELECT hwaddr_source, name FROM lease_hwaddr_source'
+ assert_eq 0 "${EXIT_CODE}" "lease_hwaddr_source table is missing or broken. (expected status code %d, returned %d)"
+
+ #table: hosts (upgrade 2.0 -> 3.0)
+ run_command \
+ mysql -u"${db_user}" -p"${db_password}" "${db_name}" -e \
+ 'SELECT host_id, dhcp_identifier, dhcp_identifier_type, dhcp4_subnet_id, dhcp6_subnet_id, ipv4_address, hostname, dhcp4_client_classes, dhcp6_client_classes FROM hosts'
+ assert_eq 0 "${EXIT_CODE}" "hosts table is missing or broken. (expected status code %d, returned %d)"
+
+ #table: ipv6_reservations (upgrade 2.0 -> 3.0)
+ run_command \
+ mysql -u"${db_user}" -p"${db_password}" "${db_name}" -e \
+ 'SELECT reservation_id, address, prefix_len, type, dhcp6_iaid, host_id FROM ipv6_reservations'
+ assert_eq 0 "${EXIT_CODE}" "ipv6_reservations table is missing or broken. (expected status code %d, returned %d)"
+
+ #table: dhcp4_options (upgrade 2.0 -> 3.0)
+ run_command \
+ mysql -u"${db_user}" -p"${db_password}" "${db_name}" -e \
+ 'SELECT option_id, code, value, formatted_value, space, persistent, dhcp_client_class, dhcp4_subnet_id, host_id FROM dhcp4_options'
+ assert_eq 0 "${EXIT_CODE}" "dhcp4_options table is missing or broken. (expected status code %d, returned %d)"
+
+ #table: dhcp6_options (upgrade 2.0 -> 3.0)
+ run_command \
+ mysql -u"${db_user}" -p"${db_password}" "${db_name}" -e \
+ 'SELECT option_id, code, value, formatted_value, space, persistent, dhcp_client_class, dhcp6_subnet_id, host_id FROM dhcp6_options'
+ assert_eq 0 "${EXIT_CODE}" "dhcp6_options table is missing or broken. (expected status code %d, returned %d)"
+
+ #table: lease_state table added (upgrade 3.0 -> 4.0)
+ run_command \
+ mysql -u"${db_user}" -p"${db_password}" "${db_name}" -e \
+ 'SELECT state,name from lease_state'
+ assert_eq 0 "${EXIT_CODE}" "dhcp6_options table is missing or broken. (expected status code %d, returned %d)"
+
+ #table: state column added to lease4 (upgrade 3.0 -> 4.0)
+ run_command \
+ mysql -u"${db_user}" -p"${db_password}" "${db_name}" -e \
+ 'SELECT state from lease4'
+ assert_eq 0 "${EXIT_CODE}" "lease4 is missing state column. (expected status code %d, returned %d)"
+
+ #table: state column added to lease6 (upgrade 3.0 -> 4.0)
+ run_command \
+ mysql -u"${db_user}" -p"${db_password}" "${db_name}" -e \
+ 'SELECT state from lease6'
+ assert_eq 0 "${EXIT_CODE}" "lease6 is missing state column. (expected status code %d, returned %d)"
+
+ #table: stored procedures for lease dumps added (upgrade 3.0 -> 4.0)
+ run_command \
+ mysql -u"${db_user}" -p"${db_password}" "${db_name}" -e \
+ 'call lease4DumpHeader(); call lease4DumpData(); call lease6DumpHeader(); call lease6DumpHeader()'
+ assert_eq 0 "${EXIT_CODE}" "lease dump stored procedures are missing or broken. (expected status code %d, returned %d)"
+
+ #lease_hardware_source should have row for source = 0 (upgrade 4.0 -> 4.1)
+ qry="select count(hwaddr_source) from lease_hwaddr_source where hwaddr_source = 0 and name='HWADDR_SOURCE_UNKNOWN'"
+ run_command \
+ mysql_execute "${qry}"
+ count="${OUTPUT}"
+ assert_eq 0 "${EXIT_CODE}" "select from lease_hwaddr_source failed. (expected status code %d, returned %d)"
+ assert_eq 1 "${count}" "lease_hwaddr_source does not contain entry for HWADDR_SOURCE_UNKNOWN. (record count %d, expected %d)"
+
+ # table: stored procedures for lease data dumps were modified (upgrade 4.0 -> 4.1)
+ # verify lease4DumpData has order by lease address
+ qry="show create procedure lease4DumpData"
+ run_command \
+ mysql_execute "${qry}"
+ assert_eq 0 "${EXIT_CODE}" "procedure text fetch for lease4DumpData failed. (returned status code %d, expected %d)"
+ count=$(echo "${OUTPUT}" | grep -Eci 'order by [a-z]*[\.]?address') || true
+ assert_eq 1 "${count}" "lease4DumpData is missing order by clause. (expected count %d, returned %d)"
+
+ # verify lease6DumpData has order by lease address
+ qry="show create procedure lease6DumpData"
+ run_command \
+ mysql_execute "${qry}"
+ assert_eq 0 "${EXIT_CODE}" "procedure text fetch for lease6DumpData failed. (returned status code %d, expected %d)"
+ count=$(echo "${OUTPUT}" | grep -Eci 'order by [a-z]*[\.]?address') || true
+ assert_eq 1 "${count}" "lease6DumpData doesn't have order by clause. (returned count %d, expected %d)"
+
+ #table: host_identifier_type (upgrade 4.1 -> 5.0)
+ # verify that host_identifier_type table exists.
+ qry="select count(*) from host_identifier_type"
+ run_command \
+ mysql_execute "${qry}"
+ count="${OUTPUT}"
+ assert_eq 0 "${EXIT_CODE}" "select from host_identifier_type failed. (expected status code %d, returned %d)"
+ assert_eq 5 "${count}" "host_identifier_type does not contain correct number of entries. (expected count %d, returned %d)"
+
+ # verify that foreign key fk_host_identifier_type exists
+ qry="show create table hosts"
+ run_command \
+ mysql_execute "${qry}"
+ count=$(echo "${OUTPUT}" | grep -Fci -m 1 'fk_host_identifier_type') || true
+ assert_eq 0 "${EXIT_CODE}" "show create table hosts failed. (expected status code %d, returned %d)"
+ assert_eq 1 "${count}" "show create table hosts did not return correct number of fk_host_identifier_type instances. (expected %d, returned %d)"
+
+ #table: dhcp_option_scope (upgrade 4.1 -> 5.0)
+ # verify that dhcp_option_scope table exists.
+ qry="select count(*) from dhcp_option_scope"
+ run_command \
+ mysql_execute "${qry}"
+ count="${OUTPUT}"
+ assert_eq 0 "${EXIT_CODE}" "select from dhcp_option_scope failed. (expected status code %d, returned %d)"
+ # verify that dhcp_option_scope table contains correct number of entries.
+ assert_eq 7 "${count}" "dhcp_option_scope does not contain correct number of entries. (expected %d, returned %d)"
+
+ #table: scope_id columns to dhcp4_options (upgrade 4.1 -> 5.0)
+ # verify that dhcp4_options table includes scope_id
+ qry="select scope_id from dhcp4_options"
+ run_command \
+ mysql_execute "${qry}"
+ count="${OUTPUT}"
+ assert_eq 0 "${EXIT_CODE}" "select scope_id from dhcp4_options failed. (expected status code %d, returned %d)"
+
+ #table: scope_id columns to dhcp6_options (upgrade 4.1 -> 5.0)
+ # verify that dhcp6_options table includes scope_id
+ qry="select scope_id from dhcp6_options"
+ run_command \
+ mysql_execute "${qry}"
+ count="${OUTPUT}"
+ assert_eq 0 "${EXIT_CODE}" "select scope_id from dhcp6_options failed. (expected status code %d, returned %d)"
+
+ #table: DHCPv4 fixed field columns (upgrade 4.1 -> 5.0)
+ # verify that hosts table has columns holding values for DHCPv4 fixed fields
+ qry="select dhcp4_next_server, dhcp4_server_hostname, dhcp4_boot_file_name, auth_key from hosts"
+ run_command \
+ mysql_execute "${qry}"
+ count="${OUTPUT}"
+ assert_eq 0 "${EXIT_CODE}" "select dhcp4_next_server, dhcp4_server_hostname, dhcp4_boot_file_name, auth_key failed. (expected status code %d, returned %d)"
+
+ # verify that dhcp4_subnet_id is unsigned
+ qry="show columns from hosts like 'dhcp4_subnet_id'"
+ run_command \
+ mysql_execute "${qry}"
+ assert_eq 0 "${EXIT_CODE}" "show columns from hosts like 'dhcp4_subnet_id' failed. (expected status code %d, returned %d)"
+ count=$(echo "${OUTPUT}" | grep -Fci unsigned) || true
+ assert_eq 1 "${count}" "dhcp4_subnet_id is not of unsigned type. (returned count %d, expected %d)"
+
+ # verify that dhcp6_subnet_id is unsigned
+ qry="show columns from hosts like 'dhcp6_subnet_id'"
+ run_command \
+ mysql_execute "${qry}"
+ assert_eq 0 "${EXIT_CODE}" "show columns from hosts like 'dhcp6_subnet_id' failed. (expected status code %d, returned %d)"
+ count=$(echo "${OUTPUT}" | grep -Fci unsigned) || true
+ assert_eq 1 "${count}" "dhcp6_subnet_id is not of unsigned type. (expected count %d, returned %d)"
+
+ #host_identifier_type should have rows for types 3 and 4 (upgrade 5.0 -> 5.1)
+ qry="select count(*) from host_identifier_type"
+ run_command \
+ mysql_execute "${qry}"
+ count="${OUTPUT}"
+ assert_eq 0 "${EXIT_CODE}" "select from host_identifier_type failed. (expected status code %d, returned %d)"
+ assert_eq 5 "${count}" "host_identifier_type does not contain correct number of entries. (expected count %d, returned %d)"
+
+ #table: user_context columns to hosts, dhcp4_options and dhcp6_options (upgrade 5.2 -> 6.0)
+ # verify that hosts table includes user_context
+ qry="select user_context from hosts"
+ run_command \
+ mysql_execute "${qry}"
+ count="${OUTPUT}"
+ assert_eq 0 "${EXIT_CODE}" "select user_context from hosts failed. (expected status code %d, returned %d)"
+
+ # verify that dhcp4_options table includes user_context
+ qry="select user_context from dhcp4_options"
+ run_command \
+ mysql_execute "${qry}"
+ count="${OUTPUT}"
+ assert_eq 0 "${EXIT_CODE}" "select user_context from dhcp4_options failed. (expected status code %d, returned %d)"
+
+ # verify that dhcp6_options table includes user_context
+ qry="select user_context from dhcp6_options"
+ run_command \
+ mysql_execute "${qry}"
+ count="${OUTPUT}"
+ assert_eq 0 "${EXIT_CODE}" "select user_context from dhcp6_options failed. (expected status code %d, returned %d)"
+
+ # lease4/6_stats changes are tested separately
+
+ #table: user_context to lease4 and lease6 (upgrade 6.0 -> 7.0)
+ # verify that lease4 table includes user_context
+ qry="select user_context from lease4"
+ run_command \
+ mysql_execute "${qry}"
+ count="${OUTPUT}"
+ assert_eq 0 "${EXIT_CODE}" "select user_context from lease4 failed. (expected status code %d, returned %d)"
+
+ # verify that lease6 table includes user_context
+ qry="select user_context from lease6"
+ run_command \
+ mysql_execute "${qry}"
+ count="${OUTPUT}"
+ assert_eq 0 "${EXIT_CODE}" "select user_context from lease6 failed. (expected status code %d, returned %d)"
+
+ #table: logs (upgrade 6.0 -> 7.0)
+ run_command \
+ mysql -u"${db_user}" -p"${db_password}" "${db_name}" -e \
+ 'SELECT timestamp, address, log FROM logs'
+ assert_eq 0 "${EXIT_CODE}" "logs table is missing or broken. (expected status code %d, returned %d)"
+
+ # table: modification (upgrade 6.0 -> 7.0)
+ qry="select id, modification_type from modification"
+ run_statement "modification" "$qry"
+
+ # table: modification table should have 3 entries (upgrade 6.0 -> 7.0)
+ qry="select count(*) from modification"
+ run_statement "modification count" "$qry" 3
+
+ # table: dhcp4_server
+ qry="select id, tag, description, modification_ts from dhcp4_server"
+ run_statement "dhcp4_server" "$qry"
+
+ # table: dhcp4_server - check if it contains default entry
+ qry="select count(*) from dhcp4_server"
+ run_statement "dhcp4_server" "$qry" 1
+
+ # table: dhcp4_audit
+ qry="select id, object_type, object_id, modification_type from dhcp4_audit"
+ run_statement "dhcp4_audit" "$qry"
+
+ # table: dhcp4_global_parameter
+ qry="select id, name, value, parameter_type, modification_ts from dhcp4_global_parameter"
+ run_statement "dhcp4_global_parameter" "$qry"
+
+ # table: dhcp4_global_parameter_server
+ qry="select parameter_id, server_id, modification_ts from dhcp4_global_parameter_server"
+ run_statement "dhcp4_global_parameter_server" "$qry"
+
+ # table: dhcp4_option_def
+ qry="select id, code, name, space, type, modification_ts, is_array, encapsulate, record_types, user_context from dhcp4_option_def"
+ run_statement "dhcp4_option_def" "$qry"
+
+ # table: dhcp4_option_def_server
+ qry="select option_def_id, server_id, modification_ts from dhcp4_option_def_server"
+ run_statement "dhcp4_option_def_server" "$qry"
+
+ # table: dhcp4_shared_network
+ qry="select id, name, client_class, interface, match_client_id, modification_ts, rebind_timer, relay, renew_timer, require_client_classes, user_context, valid_lifetime, authoritative, calculate_tee_times, t1_percent, t2_percent, boot_file_name, next_server, server_hostname from dhcp4_shared_network"
+ run_statement "dhcp4_shared_network" "$qry"
+
+ # table: dhcp4_shared_network_server
+ qry="select shared_network_id, server_id, modification_ts from dhcp4_shared_network_server"
+ run_statement "dhcp4_shared_network_server" "$qry"
+
+ # table: dhcp4_subnet
+ qry="select subnet_prefix, 4o6_interface, 4o6_interface_id, 4o6_subnet, boot_file_name, client_class, interface, match_client_id, modification_ts, next_server, rebind_timer, relay, renew_timer, require_client_classes, server_hostname, shared_network_name, subnet_id, user_context, valid_lifetime, authoritative, calculate_tee_times, t1_percent, t2_percent from dhcp4_subnet"
+ run_statement "dhcp4_subnet" "$qry"
+
+ # table: dhcp4_pool
+ qry="select id, start_address, end_address, subnet_id, modification_ts from dhcp4_pool"
+ run_statement "dhcp4_pool" "$qry"
+
+ # table: dhcp4_subnet_server
+ qry="select subnet_id, server_id, modification_ts from dhcp4_subnet_server"
+ run_statement "dhcp4_subnet_server" "$qry"
+
+ # table: dhcp4_options (should include three new columns)
+ qry="select shared_network_name, pool_id, modification_ts from dhcp4_options"
+ run_statement "dhcp4_options" "$qry"
+
+ # table: dhcp4_options_server
+ qry="select option_id, server_id, modification_ts from dhcp4_options_server"
+ run_statement "dhcp4_options_server" "$qry"
+
+ # table: dhcp6_server
+ qry="select id, tag, description, modification_ts from dhcp6_server"
+ run_statement "dhcp6_server" "$qry"
+
+ # table: dhcp6_server - check if it contains default entry
+ qry="select count(*) from dhcp6_server"
+ run_statement "dhcp6_server" "$qry" 1
+
+ # table: dhcp6_audit
+ qry="select id, object_type, object_id, modification_type from dhcp6_audit"
+ run_statement "dhcp6_audit" "$qry"
+
+ # table: dhcp6_global_parameter
+ qry="select id, name, value, parameter_type, modification_ts from dhcp6_global_parameter"
+ run_statement "dhcp6_global_parameter" "$qry"
+
+ # table: dhcp6_global_parameter_server
+ qry="select parameter_id, server_id, modification_ts from dhcp6_global_parameter_server"
+ run_statement "dhcp6_global_parameter_server" "$qry"
+
+ # table: dhcp6_option_def
+ qry="select id, code, name, space, type, modification_ts, is_array, encapsulate, record_types, user_context from dhcp6_option_def"
+ run_statement "dhcp6_option_def" "$qry"
+
+ # table: dhcp6_option_def_server
+ qry="select option_def_id, server_id, modification_ts from dhcp6_option_def_server"
+ run_statement "dhcp6_option_def_server" "$qry"
+
+ # table: dhcp6_shared_network
+ qry="select id, name, client_class, interface, modification_ts, preferred_lifetime, rapid_commit, rebind_timer, relay, renew_timer, require_client_classes, user_context, valid_lifetime, calculate_tee_times, t1_percent, t2_percent, interface_id from dhcp6_shared_network"
+ run_statement "dhcp6_shared_network" "$qry"
+
+ # table: dhcp6_shared_network_server
+ qry="select shared_network_id, server_id, modification_ts from dhcp6_shared_network_server"
+ run_statement "dhcp6_shared_network" "$qry"
+
+ # table: dhcp6_subnet
+ qry="select subnet_prefix, client_class, interface, modification_ts, preferred_lifetime, rapid_commit, rebind_timer, relay, renew_timer, require_client_classes, shared_network_name, subnet_id, user_context, valid_lifetime, calculate_tee_times, t1_percent, t2_percent, interface_id from dhcp6_subnet"
+ run_statement "dhcp6_subnet" "$qry"
+
+ # table: dhcp6_subnet_server
+ qry="select subnet_id, server_id, modification_ts from dhcp6_subnet_server"
+ run_statement "dhcp6_subnet_server" "$qry"
+
+ # table: dhcp6_pd_pool
+ qry="select id, prefix_length, delegated_prefix_length, subnet_id, modification_ts from dhcp6_pd_pool"
+ run_statement "dhcp6_pd_pool" "$qry"
+
+ # table: dhcp6_pool
+ qry="select id, start_address, end_address, subnet_id, modification_ts from dhcp6_pool"
+ run_statement "dhcp6_pool" "$qry"
+
+ # table: dhcp6_options (should include four new columns)
+ qry="select shared_network_name, pool_id, pd_pool_id, modification_ts from dhcp6_options"
+ run_statement "dhcp6_options" "$qry"
+
+ # table: dhcp6_options_server
+ qry="select option_id, server_id, modification_ts from dhcp6_options_server"
+ run_statement "dhcp6_options_server" "$qry"
+
+ # Schema upgrade from 7.0 to 8.0
+
+ # Test that createAuditRevisionDHCP4 exists and creates entry in
+ # the dhcp4_audit_revision table.
+ qry="CALL createAuditRevisionDHCP4('2019-01-28 23:59:11', 'all', 'some log message', 0)"
+ run_statement "createAuditRevisionDHCP4" "$qry"
+
+ qry="SELECT COUNT(*) from dhcp4_audit_revision"
+ run_statement "dhcp4_audit_revision count" "$qry" 1
+
+ qry="SELECT id, modification_ts, server_id, log_message FROM dhcp4_audit_revision"
+ run_statement "dhcp4_audit_revision" "$qry"
+
+ # Test that createAuditEntryDHCP4 exists and creates entry in
+ # the dhcp4_audit table.
+ qry="SET @audit_revision_id = (SELECT id FROM dhcp4_audit_revision LIMIT 1); CALL createAuditEntryDHCP4('dhcp4_subnet', 1, 'create')"
+ run_statement "createAuditEntryDHCP4" "$qry"
+
+ qry="SELECT COUNT(*) FROM dhcp4_audit"
+ run_statement "dhcp4_audit count" "$qry" 1
+
+ qry="SELECT id, object_type, object_id, modification_type, revision_id FROM dhcp4_audit"
+ run_statement "dhcp4_audit" "$qry"
+
+ # Test that createOptionAuditDHCP4 exists can create an audit
+ # entry.
+
+ # First set the cascade_transaction session variable to check that
+ # the procedure won't create the audit entry for the option when
+ # this flag is set.
+ qry="SET @audit_revision_id = (SELECT id FROM dhcp4_audit_revision LIMIT 1); SET @cascade_transaction = 1; CALL createOptionAuditDHCP4('create', 0, 1024, NULL, NULL, NULL, NULL, now())"
+ run_statement "createOptionAuditDHCP4 cascade update" "$qry"
+
+ # The number of rows matching the audit entry should be 0.
+ qry="SELECT COUNT(*) FROM dhcp4_audit WHERE object_type = 'dhcp4_options' AND object_id = 1024"
+ run_statement "createOptionAuditDHCP4 cascade update, entry not inserted" "$qry" 0;
+
+ # This time set the cascade_update to 0 and expect that the
+ # audit entry will be created for the option.
+ qry="SET @audit_revision_id = (SELECT id FROM dhcp4_audit_revision LIMIT 1); SET @cascade_transaction = 0; CALL createOptionAuditDHCP4('create', 0, 1024, NULL, NULL, NULL, NULL, now())"
+ run_statement "createOptionAuditDHCP4 cascade update" "$qry"
+
+ qry="SELECT COUNT(*) FROM dhcp4_audit WHERE object_type = 'dhcp4_options' AND object_id = 1024"
+ run_statement "createOptionAuditDHCP4 cascade update, entry not inserted" "$qry" 1;
+
+ # Test that createAuditRevisionDHCP6 exists and creates entry in
+ # the dhcp6_audit_revision table.
+ qry="CALL createAuditRevisionDHCP6('2019-01-28 23:59:11', 'all', 'some log message', 0)"
+ run_statement "createAuditRevisionDHCP6" "$qry"
+
+ qry="SELECT COUNT(*) from dhcp6_audit_revision"
+ run_statement "dhcp6_audit_revision count" "$qry" 1
+
+ qry="SELECT id, modification_ts, server_id, log_message FROM dhcp6_audit_revision"
+ run_statement "dhcp6_audit_revision" "$qry"
+
+ # Test that createAuditEntryDHCP6 exists and creates entry in
+ # the dhcp6_audit table.
+ qry="SET @audit_revision_id = (SELECT id FROM dhcp6_audit_revision LIMIT 1); CALL createAuditEntryDHCP6('dhcp6_subnet', 1, 'create')"
+ run_statement "createAuditEntryDHCP6" "$qry"
+
+ qry="SELECT COUNT(*) FROM dhcp6_audit"
+ run_statement "dhcp6_audit count" "$qry" 1
+
+ qry="SELECT id, object_type, object_id, modification_type, revision_id FROM dhcp6_audit"
+ run_statement "dhcp6_audit" "$qry"
+
+ # Test that createOptionAuditDHCP6 exists can create an audit
+ # entry.
+
+ # First set the cascade_transaction session variable to check that
+ # the procedure won't create the audit entry for the option when
+ # this flag is set.
+ qry="SET @audit_revision_id = (SELECT id FROM dhcp6_audit_revision LIMIT 1); SET @cascade_transaction = 1; CALL createOptionAuditDHCP6('create', 0, 1024, NULL, NULL, NULL, NULL, NULL, now())"
+ run_statement "createOptionAuditDHCP6 cascade update" "$qry"
+
+ # The number of rows matching the audit entry should be 0.
+ qry="SELECT COUNT(*) FROM dhcp6_audit WHERE object_type = 'dhcp6_options' AND object_id = 1024"
+ run_statement "createOptionAuditDHCP6 cascade update, entry not inserted" "$qry" 0;
+
+ # This time set the cascade_update to 0 and expect that the
+ # audit entry will be created for the option.
+ qry="SET @audit_revision_id = (SELECT id FROM dhcp6_audit_revision LIMIT 1); SET @cascade_transaction = 0; CALL createOptionAuditDHCP6('create', 0, 1024, NULL, NULL, NULL, NULL, NULL,now())"
+ run_statement "createOptionAuditDHCP6 cascade update" "$qry"
+
+ qry="SELECT COUNT(*) FROM dhcp6_audit WHERE object_type = 'dhcp6_options' AND object_id = 1024"
+ run_statement "createOptionAuditDHCP6 cascade update, entry not inserted" "$qry" 1;
+
+ # New triggers aren't tested here because the extensive tests are
+ # provided with the backend implementations.
+
+ # parameter_data_type must exist and must have 4 rows.
+ qry="SELECT COUNT(*) FROM parameter_data_type"
+ run_statement "parameter_data_type count" "$qry" 4;
+
+ # Schema upgrade from 8.0 to 8.2
+
+ # New lifetime bounds.
+
+ # table: dhcp4_shared_network
+ qry="select id, name, client_class, interface, match_client_id, modification_ts, rebind_timer, relay, renew_timer, require_client_classes, user_context, valid_lifetime, min_valid_lifetime, max_valid_lifetime, authoritative, calculate_tee_times, t1_percent, t2_percent, boot_file_name, next_server, server_hostname from dhcp4_shared_network"
+ run_statement "dhcp4_shared_network" "$qry"
+
+ # table: dhcp4_subnet
+ qry="select subnet_prefix, 4o6_interface, 4o6_interface_id, 4o6_subnet, boot_file_name, client_class, interface, match_client_id, modification_ts, next_server, rebind_timer, relay, renew_timer, require_client_classes, server_hostname, shared_network_name, subnet_id, user_context, valid_lifetime, min_valid_lifetime, max_valid_lifetime, authoritative, calculate_tee_times, t1_percent, t2_percent from dhcp4_subnet"
+ run_statement "dhcp4_subnet" "$qry"
+
+ # table: dhcp6_shared_network
+ qry="select id, name, client_class, interface, modification_ts, preferred_lifetime, min_preferred_lifetime, max_preferred_lifetime,rapid_commit, rebind_timer, relay, renew_timer, require_client_classes, user_context, valid_lifetime, min_valid_lifetime, max_valid_lifetime, calculate_tee_times, t1_percent, t2_percent from dhcp6_shared_network"
+ run_statement "dhcp6_shared_network" "$qry"
+
+ # table: dhcp6_subnet
+ qry="select subnet_prefix, client_class, interface, modification_ts, preferred_lifetime, min_preferred_lifetime, max_preferred_lifetime, rapid_commit, rebind_timer, relay, renew_timer, require_client_classes, shared_network_name, subnet_id, user_context, valid_lifetime, min_valid_lifetime, max_valid_lifetime, calculate_tee_times, t1_percent, t2_percent from dhcp6_subnet"
+ run_statement "dhcp6_subnet" "$qry"
+
+ # table: dhcp4_pool (should include three new columns)
+ qry="select client_class, require_client_classes, user_context from dhcp4_pool"
+ run_statement "dhcp4_pool" "$qry"
+
+ # table: dhcp6_pd_pool (should include five new columns)
+ qry="select excluded_prefix, excluded_prefix_length, client_class, require_client_classes, user_context from dhcp6_pd_pool"
+ run_statement "dhcp6_pd_pool" "$qry"
+
+ # table: dhcp6_pool (should include three new columns)
+ qry="select client_class, require_client_classes, user_context from dhcp6_pool"
+ run_statement "dhcp6_pool" "$qry"
+
+ # Verify that dhcp4_option_def column name is is_array
+ qry="select is_array from dhcp4_option_def"
+ run_statement "dhcp4_option_def verify is_array column" "$qry"
+
+ # Verify that dhcp6_option_def column name is is_array
+ qry="select is_array from dhcp6_option_def"
+ run_statement "dhcp6_option_def verify is_array column" "$qry"
+
+ # Schema upgrade from 8.2 to 9.3
+
+ # New DDNS columns.
+
+ # table: dhcp4_shared_network (should include six new columns)
+ qry="select ddns_send_updates, ddns_override_no_update, ddns_override_client_update, ddns_replace_client_name, ddns_generated_prefix, ddns_qualifying_suffix from dhcp4_shared_network"
+ run_statement "dhcp4_shared_network" "$qry"
+
+ # table: dhcp6_shared_network (should include six new columns)
+ qry="select ddns_send_updates, ddns_override_no_update, ddns_override_client_update, ddns_replace_client_name, ddns_generated_prefix, ddns_qualifying_suffix from dhcp6_shared_network"
+ run_statement "dhcp6_shared_network" "$qry"
+
+ # table: dhcp4_subnet (should include six new columns)
+ qry="select ddns_send_updates, ddns_override_no_update, ddns_override_client_update, ddns_replace_client_name, ddns_generated_prefix, ddns_qualifying_suffix from dhcp4_subnet"
+ run_statement "dhcp4_subnet" "$qry"
+
+ # table: dhcp6_subnet (should include six new columns)
+ qry="select ddns_send_updates, ddns_override_no_update, ddns_override_client_update, ddns_replace_client_name, ddns_generated_prefix, ddns_qualifying_suffix from dhcp6_subnet"
+ run_statement "dhcp6_subnet" "$qry"
+
+ # Schema upgrade from 9.3 to 9.4.
+
+ # Non unique indexes on hosts allowing multiple reservation for the same IP.
+
+ insert_sql="\
+insert into hosts(dhcp_identifier, dhcp_identifier_type, dhcp4_subnet_id, ipv4_address) values (hex('010101010101'), 0, 1, inet_aton('192.0.2.0'));\
+insert into hosts(dhcp_identifier, dhcp_identifier_type, dhcp4_subnet_id, ipv4_address) values (hex('010101010102'), 0, 1, inet_aton('192.0.2.0'))"
+ run_command \
+ mysql_execute "$insert_sql"
+ assert_eq 0 "${EXIT_CODE}" "insert into hosts failed, expected exit code %d, actual %d"
+
+ # Schema upgrade from 9.4 to 9.5.
+
+ # table: dhcp4_shared_network (reservation_mode replaced by reservations flags)
+ qry="select reservations_global, reservations_in_subnet, reservations_out_of_pool from dhcp4_shared_network"
+ run_statement "dhcp4_shared_network" "$qry"
+
+ qry="show columns from dhcp4_shared_network like 'reservation_mode'"
+ run_command \
+ mysql_execute "${qry}"
+ assert_eq 0 "${EXIT_CODE}" "show columns from dhcp4_shared_network like 'reservation_mode' failed. (expected status code %d, returned %d)"
+ count=$(echo "${OUTPUT}" | grep -Fci reservation) || true
+ assert_eq 0 "${count}" "dhcp4_shared_network has still reservation_mode column. (returned count %d, expected %d)"
+
+ # table: dhcp4_subnet (reservation_mode replaced by reservations flags)
+ qry="select reservations_global, reservations_in_subnet, reservations_out_of_pool from dhcp4_subnet"
+ run_statement "dhcp4_subnet" "$qry"
+
+ qry="show columns from dhcp4_subnet like 'reservation_mode'"
+ run_command \
+ mysql_execute "${qry}"
+ assert_eq 0 "${EXIT_CODE}" "show columns from dhcp4_subnet like 'reservation_mode' failed. (expected status code %d, returned %d)"
+ count=$(echo "${OUTPUT}" | grep -Fci reservation) || true
+ assert_eq 0 "${count}" "dhcp4_subnet has still reservation_mode column. (returned count %d, expected %d)"
+
+ # table: dhcp6_shared_network (reservation_mode replaced by reservations flags)
+ qry="select reservations_global, reservations_in_subnet, reservations_out_of_pool from dhcp6_shared_network"
+ run_statement "dhcp6_shared_network" "$qry"
+
+ qry="show columns from dhcp6_shared_network like 'reservation_mode'"
+ run_command \
+ mysql_execute "${qry}"
+ assert_eq 0 "${EXIT_CODE}" "show columns from dhcp6_shared_network like 'reservation_mode' failed. (expected status code %d, returned %d)"
+ count=$(echo "${OUTPUT}" | grep -Fci reservation) || true
+ assert_eq 0 "${count}" "dhcp6_shared_network has still reservation_mode column. (returned count %d, expected %d)"
+
+ # table: dhcp6_subnet (reservation_mode replaced by reservations flags)
+ qry="select reservations_global, reservations_in_subnet, reservations_out_of_pool from dhcp6_subnet"
+ run_statement "dhcp6_subnet" "$qry"
+
+ qry="show columns from dhcp6_subnet like 'reservation_mode'"
+ run_command \
+ mysql_execute "${qry}"
+ assert_eq 0 "${EXIT_CODE}" "show columns from dhcp6_subnet like 'reservation_mode' failed. (expected status code %d, returned %d)"
+ count=$(echo "${OUTPUT}" | grep -Fci reservation) || true
+ assert_eq 0 "${count}" "dhcp6_subnet has still reservation_mode column. (returned count %d, expected %d)"
+
+ # Schema upgrade from 9.5 to 9.6.
+
+ # table: dhcp4_shared_network new cache_threshold and cache_max_age columns
+ qry="select cache_threshold, cache_max_age from dhcp4_shared_network"
+ run_statement "dhcp4_shared_network" "$qry"
+
+ # table: dhcp4_subnet new cache_threshold and cache_max_age columns
+ qry="select cache_threshold, cache_max_age from dhcp4_subnet"
+ run_statement "dhcp4_shared_network" "$qry"
+
+ # table: dhcp6_shared_network new cache_threshold and cache_max_age columns
+ qry="select cache_threshold, cache_max_age from dhcp6_shared_network"
+ run_statement "dhcp6_shared_network" "$qry"
+
+ # table: dhcp6_subnet new cache_threshold and cache_max_age columns
+ qry="select cache_threshold, cache_max_age from dhcp6_subnet"
+ run_statement "dhcp6_shared_network" "$qry"
+
+ qry='SELECT id FROM logs'
+ run_command \
+ mysql_execute "${qry}"
+ assert_eq 0 "${EXIT_CODE}" "${qry} failed: expected status code %d, returned %d"
+
+ # Check upgrade from 10.0 to 11.0.
+ qry="show indexes from lease4 where key_name = 'lease4_by_expire_state'"
+ run_command \
+ mysql_execute "${qry}"
+ assert_eq 0 "${EXIT_CODE}" "show indexes from lease4 failed. (expected status code %d, returned %d)"
+ count=$(echo "${OUTPUT}" | grep -Fci lease4_by_expire_state)
+ assert_eq 2 "${count}" "lease4_by_expire_state wrong or missing. (expected count %d, actual %d)"
+
+ qry="show indexes from lease6 where key_name = 'lease6_by_expire_state'"
+ run_command \
+ mysql_execute "${qry}"
+ assert_eq 0 "${EXIT_CODE}" "show indexes from lease6 failed. (expected status code %d, returned %d)"
+ count=$(echo "${OUTPUT}" | grep -Fci lease6_by_expire_state)
+ assert_eq 2 "${count}" "lease6_by_expire_state wrong or missing. (expected count %d, actual %d)"
+
+ # Verify preferred lifetime columns exist.
+ qry="select preferred_lifetime,min_preferred_lifetime,max_preferred_lifetime from dhcp6_client_class where name=''"
+ run_command \
+ mysql_execute "${qry}"
+ assert_eq 0 "${EXIT_CODE}" "$qry failed. dhcp6_client_classes preferred lifetime columns missing?"
+
+ # Check upgrade from 11.0 to 12.0.
+
+ # Add classes with associated options.
+ qry="\
+SET @disable_audit = 1;\
+INSERT INTO dhcp4_client_class(name, modification_ts) VALUES ('foo', now());\
+INSERT INTO dhcp4_options(code, scope_id, dhcp_client_class, modification_ts) VALUES (222, 2, 'foo', now());\
+INSERT INTO dhcp6_client_class(name, modification_ts) VALUES ('foo', now());\
+INSERT INTO dhcp6_options(code, scope_id, dhcp_client_class, modification_ts) VALUES (222, 2, 'foo', now());\
+SET @disable_audit = 0"
+ run_command \
+ mysql_execute "$qry"
+ assert_eq 0 "${EXIT_CODE}" "inserting classes and options failed, expected exit code %d, actual %d"
+
+ # Delete the classes.
+ qry="\
+SET @disable_audit = 1;\
+DELETE FROM dhcp4_client_class;\
+DELETE FROM dhcp6_client_class;\
+SET @disable_audit = 0"
+ run_command \
+ mysql_execute "$qry"
+ assert_eq 0 "${EXIT_CODE}" "deleting classes failed, expected exit code %d, actual %d"
+
+ # Ensure that the DHCPv4 option was deleted.
+ qry="SELECT COUNT(*) from dhcp4_options"
+ run_statement "dhcp4_options count" "$qry" 0
+
+ # Ensure that the DHCPv6 option was deleted.
+ qry="SELECT COUNT(*) from dhcp6_options"
+ run_statement "dhcp6_options count" "$qry" 0
+
+ # Check upgrade from 12.0 to 13.0.
+ mysql_upgrade_12_to_13_test
+
+ # Check upgrade from 13.0 to 14.0.
+ mysql_upgrade_13_to_14_test
+
+ # Check upgrade from 14.0 to 15.0.
+ mysql_upgrade_14_to_15_test
+
+ # Check upgrade from 15.0 to 16.0.
+
+ # table: lease4 new relay_id remote_id column and index.
+ qry="select relay_id from lease4"
+ run_statement "lease4" "$qry"
+
+ qry="show indexes from lease4 where key_name = 'lease4_by_relay_id'"
+ run_command \
+ mysql_execute "${qry}"
+ assert_eq 0 "${EXIT_CODE}" "show indexes from lease4 failed. (expected status code %d, returned %d)"
+ count=$(echo "${OUTPUT}" | grep -Fci lease4_by_relay_id)
+ assert_eq 1 "${count}" "lease4_by_relay_id wrong or missing. (expected count %d, actual %d)"
+
+ # table: lease4 new remote_id column and index.
+ qry="select remote_id from lease4"
+ run_statement "lease4" "$qry"
+
+ qry="show indexes from lease4 where key_name = 'lease4_by_remote_id'"
+ run_command \
+ mysql_execute "${qry}"
+ assert_eq 0 "${EXIT_CODE}" "show indexes from lease4 failed. (expected status code %d, returned %d)"
+ count=$(echo "${OUTPUT}" | grep -Fci lease4_by_remote_id)
+ assert_eq 1 "${count}" "lease4_by_remote_id wrong or missing. (expected count %d, actual %d)"
+
+ # Check upgrade from 16.0 to 17.0.
+ mysql_upgrade_16_to_17_test
+
+ # Check upgrade from 17.0 to 18.0.
+ mysql_upgrade_17_to_18_test
+
+ # Check upgrade from 18.0 to 19.0.
+ mysql_upgrade_18_to_19_test
+
+ # Let's wipe the whole database
+ mysql_wipe
+
+ test_finish 0
+}
+
+# May accept additional parameters to be passed to lease-dump.
+mysql_lease4_dump_test() {
+ test_start "mysql.lease4_dump_test${1-}"
+
+ test_dir="@abs_top_srcdir@/src/bin/admin/tests"
+ output_dir="@abs_top_builddir@/src/bin/admin/tests"
+
+ output_file="$output_dir/data/mysql.lease4_dump_test.output.csv"
+ ref_file="$test_dir/data/lease4_dump_test.reference.csv"
+
+ # Clean up any test files left from prior failed runs unless -y was provided in which case
+ # explicitly create the file to check that it will be automatically deleted.
+ # files should be removed by kea-admin itself.
+ for i in "${output_file}" \
+ "${output_file}.tmp" \
+ "/tmp/$(basename "${output_file}").tmp" \
+ ; do
+ if printf '%s' "$@" | grep 'y' > /dev/null; then
+ touch "${i}"
+ else
+ rm -f "${i}"
+ fi
+ done
+
+ # Let's wipe the whole database
+ mysql_wipe
+
+ # Ok, now let's initialize the database
+ run_command \
+ "${kea_admin}" db-init mysql -u "${db_user}" -p "${db_password}" -n "${db_name}" \
+ -d "${db_scripts_dir}"
+ assert_eq 0 "${EXIT_CODE}" "could not create database, expected exit code %d, actual %d"
+
+ # Insert the reference record
+ insert_sql="\
+insert into lease4 values(10,20,30,40,(SELECT FROM_UNIXTIME(1642000000)),50,1,1,'one.example.com',0,NULL,NULL,NULL,0);
+insert into lease4 values(11,NULL,123,40,(SELECT FROM_UNIXTIME(1643210000)),50,1,1,'',1,'{ }',NULL,NULL,0);\
+insert into lease4 values(12,22,NULL,40,(SELECT FROM_UNIXTIME(1643212345)),50,1,1,'three,example,com',2,'{ \"a\": 1, \"b\": \"c\" }',NULL,NULL,0)"
+
+ run_command \
+ mysql_execute "$insert_sql"
+ assert_eq 0 "${EXIT_CODE}" "insert into lease4 failed, expected exit code %d, actual %d"
+
+ # Dump lease4 to output_file
+ run_command \
+ "${kea_admin}" lease-dump mysql -4 -u "${db_user}" -p "${db_password}" -n "${db_name}" \
+ -d "${db_scripts_dir}" -o "${output_file}" "$@"
+ assert_eq 0 "${EXIT_CODE}" "kea-admin lease-dump -4 failed, expected exit code %d, actual %d"
+
+ # Compare the dump output to reference file, they should be identical
+ run_command \
+ cmp -s "${output_file}" "${ref_file}"
+ assert_eq 0 "${EXIT_CODE}" "dump file does not match reference file, expected exit code %d, actual %d, diff:\n$(diff ${ref_file} ${output_file})"
+
+ # Remove the files.
+ rm -f "${output_file}"
+ rm -f "${output_file}.tmp"
+
+ # Let's wipe the whole database
+ mysql_wipe
+
+ test_finish 0
+}
+
+# May accept additional parameters to be passed to lease-dump.
+mysql_lease6_dump_test() {
+ test_start "mysql.lease6_dump_test${1-}"
+
+ test_dir="@abs_top_srcdir@/src/bin/admin/tests"
+ output_dir="@abs_top_builddir@/src/bin/admin/tests"
+
+ output_file="$output_dir/data/mysql.lease6_dump_test.output.csv"
+ ref_file="$test_dir/data/lease6_dump_test.reference.csv"
+
+ # Clean up any test files left from prior failed runs unless -y was provided in which case
+ # explicitly create the file to check that it will be automatically deleted.
+ # files should be removed by kea-admin itself.
+ for i in "${output_file}" \
+ "${output_file}.tmp" \
+ "/tmp/$(basename "${output_file}").tmp" \
+ ; do
+ if printf '%s' "$@" | grep 'y' > /dev/null; then
+ touch "${i}"
+ else
+ rm -f "${i}"
+ fi
+ done
+
+ # Let's wipe the whole database
+ mysql_wipe
+
+ # Ok, now let's initialize the database
+ run_command \
+ "${kea_admin}" db-init mysql -u "${db_user}" -p "${db_password}" -n "${db_name}" -d "${db_scripts_dir}"
+ assert_eq 0 "${EXIT_CODE}" "could not create database, expected exit code %d, actual %d"
+
+ # Insert the reference record
+ insert_sql="\
+insert into lease6 values(inet6_aton('::10'),203,30,(SELECT FROM_UNIXTIME(1642000000)),40,50,1,60,128,1,1,'one.example.com',80,90,16,0,NULL,0);\
+insert into lease6 values(inet6_aton('::11'),213,30,(SELECT FROM_UNIXTIME(1643210000)),40,50,1,60,128,1,1,'',80,90,1,1,'{ }',0);\
+insert into lease6 values(inet6_aton('::12'),223,30,(SELECT FROM_UNIXTIME(1643212345)),40,50,1,60,128,1,1,'three,example,com',80,90,4,2,'{ \"a\": 1, \"b\": \"c\" }',0)"
+
+ run_command \
+ mysql_execute "$insert_sql"
+ assert_eq 0 "${EXIT_CODE}" "insert into lease6 failed, expected exit code %d, actual %d"
+
+ # Dump lease4 to output_file
+ run_command \
+ "${kea_admin}" lease-dump mysql -6 -u "${db_user}" -p "${db_password}" -n "${db_name}" \
+ -d "${db_scripts_dir}" -o "${output_file}" "$@"
+ assert_eq 0 "${EXIT_CODE}" "kea-admin lease-dump -6 failed, expected exit code %d, actual %d"
+
+ # Compare the dump output to reference file, they should be identical
+ run_command \
+ cmp -s "${output_file}" "${ref_file}"
+ assert_eq 0 "${EXIT_CODE}" "dump file does not match reference file, expected exit code %d, actual %d, diff:\n$(diff ${ref_file} ${output_file})"
+
+ # Remove the files.
+ rm -f "${output_file}"
+ rm -f "${output_file}.tmp"
+
+ # Let's wipe the whole database
+ mysql_wipe
+
+ test_finish 0
+}
+
+# May accept additional parameters to be passed to lease-dump or to lease-upload.
+mysql_lease4_upload_test() {
+ test_start "mysql.lease4_upload_test${1-}"
+
+ input_file="@abs_top_srcdir@/src/bin/admin/tests/data/lease4_dump_test.reference.csv"
+ input_file_cp="@abs_top_builddir@/src/bin/admin/tests/data/lease4_dump_test.reference.csv"
+ output_file="@abs_top_builddir@/src/bin/admin/tests/data/lease4_dump_test.output.csv"
+
+ if [ "${input_file}" != "${input_file_cp}" ]; then
+ cp -f ${input_file} ${input_file_cp}
+ input_file=${input_file_cp}
+ input_file_cp=""
+ fi
+
+ # Wipe the whole database.
+ mysql_wipe
+
+ # Clean up any test files left from prior failed runs unless -y was provided in which case
+ # explicitly create the file to check that it will be automatically deleted.
+ # files should be removed by kea-admin itself.
+ for i in "${input_file}.tmp" \
+ "${output_file}" \
+ "${output_file}.tmp" \
+ "/tmp/$(basename "${input_file}").tmp" \
+ ; do
+ if printf '%s' "$@" | grep 'y' > /dev/null; then
+ touch "${i}"
+ else
+ rm -f "${i}"
+ fi
+ done
+
+ # Initialize the database.
+ run_command \
+ "${kea_admin}" db-init mysql -u "${db_user}" -p "${db_password}" \
+ -n "${db_name}" -d "${db_scripts_dir}"
+ assert_eq 0 "${EXIT_CODE}" "could not create database, expected exit code %d, actual %d"
+
+ # Upload leases.
+ run_command \
+ "${kea_admin}" lease-upload mysql -4 -u "${db_user}" \
+ -p "${db_password}" -n "${db_name}" -d "${db_scripts_dir}" \
+ -i "${input_file}" "$@"
+ assert_eq 0 "${EXIT_CODE}" "kea-admin lease-upload -4 failed, expected exit code %d, actual %d"
+
+ # Dump leases.
+ run_command \
+ "${kea_admin}" lease-dump mysql -4 -u "${db_user}" \
+ -p "${db_password}" -n "${db_name}" -d "${db_scripts_dir}" \
+ -o "${output_file}" "$@"
+ assert_eq 0 "${EXIT_CODE}" "kea-admin lease-dump -4 failed, expected exit code %d, actual %d"
+
+ # Compare the initial file used for upload to the file retrieved from dump, they should be identical.
+ run_command \
+ cmp -s "${input_file}" "${output_file}"
+ assert_eq 0 "${EXIT_CODE}" "file resulted from dump after upload does not match file used for upload, expected exit code %d, actual %d, diff:\n$(diff ${input_file} ${output_file})"
+
+ # Remove the files.
+ if [ "${input_file}" != "${input_file_cp}" ]; then
+ rm -f "${input_file}"
+ fi
+ rm -f "${input_file}.tmp"
+ rm -f "${output_file}"
+ rm -f "${output_file}.tmp"
+
+ # Wipe the whole database.
+ mysql_wipe
+
+ test_finish 0
+}
+
+# May accept additional parameters to be passed to lease-dump or to lease-upload.
+mysql_lease6_upload_test() {
+ test_start "mysql.lease6_upload_test${1-}"
+
+ input_file="@abs_top_srcdir@/src/bin/admin/tests/data/lease6_dump_test.reference.csv"
+ input_file_cp="@abs_top_builddir@/src/bin/admin/tests/data/lease6_dump_test.reference.csv"
+ output_file="@abs_top_builddir@/src/bin/admin/tests/data/lease6_dump_test.output.csv"
+
+ if [ "${input_file}" != "${input_file_cp}" ]; then
+ cp -f ${input_file} ${input_file_cp}
+ input_file=${input_file_cp}
+ input_file_cp=""
+ fi
+
+ # Wipe the whole database.
+ mysql_wipe
+
+ # Clean up any test files left from prior failed runs unless -y was provided in which case
+ # explicitly create the file to check that it will be automatically deleted.
+ # files should be removed by kea-admin itself.
+ for i in "${input_file}.tmp" \
+ "${output_file}" \
+ "${output_file}.tmp" \
+ "/tmp/$(basename "${input_file}").tmp" \
+ ; do
+ if printf '%s' "$@" | grep 'y' > /dev/null; then
+ touch "${i}"
+ else
+ rm -f "${i}"
+ fi
+ done
+
+ # Initialize the database.
+ run_command \
+ "${kea_admin}" db-init mysql -u "${db_user}" -p "${db_password}" \
+ -n "${db_name}" -d "${db_scripts_dir}"
+ assert_eq 0 "${EXIT_CODE}" "could not create database, expected exit code %d, actual %d"
+
+ # Upload leases.
+ run_command \
+ "${kea_admin}" lease-upload mysql -6 -u "${db_user}" \
+ -p "${db_password}" -n "${db_name}" -d "${db_scripts_dir}" \
+ -i "${input_file}" "$@"
+ assert_eq 0 "${EXIT_CODE}" "kea-admin lease-upload -6 failed, expected exit code %d, actual %d"
+
+ # Dump leases.
+ run_command \
+ "${kea_admin}" lease-dump mysql -6 -u "${db_user}" \
+ -p "${db_password}" -n "${db_name}" -d "${db_scripts_dir}" \
+ -o "${output_file}" "$@"
+ assert_eq 0 "${EXIT_CODE}" "kea-admin lease-dump -6 failed, expected exit code %d, actual %d"
+
+ # Compare the initial file used for upload to the file retrieved from dump, they should be identical.
+ run_command \
+ cmp -s "${input_file}" "${output_file}"
+ assert_eq 0 "${EXIT_CODE}" "file resulted from dump after upload does not match file used for upload, expected exit code %d, actual %d, diff:\n$(diff ${input_file} ${output_file})"
+
+ # Remove the files.
+ if [ "${input_file}" != "${input_file_cp}" ]; then
+ rm -f "${input_file}"
+ fi
+ rm -f "${input_file}.tmp"
+ rm -f "${output_file}"
+ rm -f "${output_file}.tmp"
+
+ # Wipe the whole database.
+ mysql_wipe
+
+ test_finish 0
+}
+
+# Verifies lease4_stat trigger operations on
+# an new, empty database. It inserts, updates, and
+# deletes various leases, checking lease4_stat
+# values along the way.
+mysql_lease4_stat_test() {
+ test_start "mysql.lease4_stat_test"
+
+ # Let's wipe the whole database
+ mysql_wipe
+
+ # Ok, now let's initialize the database
+ run_command \
+ "${kea_admin}" db-init mysql -u "${db_user}" -p "${db_password}" -n "${db_name}" -d "${db_scripts_dir}"
+ assert_eq 0 "${EXIT_CODE}" "kea-admin db-init mysql failed, expected %d, returned non-zero status code %d"
+
+ # Verify lease4 stat table is present
+ qry="select count(subnet_id) from lease4_stat"
+ run_statement "#1" "$qry" 0
+
+ # Insert lease4
+ qry="insert into lease4 (address, subnet_id, state) values (111,1,0)"
+ run_statement "#2" "$qry"
+
+ # Assigned state count should be 1
+ qry="select leases from lease4_stat where subnet_id = 1 and state = 0"
+ run_statement "#3" "$qry" 1
+
+ # Assigned state count should be 1
+ qry="select leases from lease4_pool_stat where subnet_id = 1 and pool_id = 0 and state = 0"
+ run_statement "#4" "$qry" 1
+
+ # Set lease state to declined
+ qry="update lease4 set state = 1 where address = 111"
+ run_statement "#5" "$qry"
+
+ # Leases state count for assigned should be 0
+ qry="select leases from lease4_stat where subnet_id = 1 and state = 0"
+ run_statement "#6" "$qry" 0
+
+ # Leases state count for assigned should be 0
+ qry="select leases from lease4_pool_stat where subnet_id = 1 and pool_id = 0 and state = 0"
+ run_statement "#7" "$qry" 0
+
+ # Leases state count for declined should be 1
+ qry="select leases from lease4_stat where subnet_id = 1 and state = 1"
+ run_statement "#8" "$qry" 1
+
+ # Leases state count for declined should be 1
+ qry="select leases from lease4_pool_stat where subnet_id = 1 and pool_id = 0 and state = 1"
+ run_statement "#9" "$qry" 1
+
+ # Delete the lease
+ qry="delete from lease4 where address = 111"
+ run_statement "#10" "$qry"
+
+ # Leases state count for declined should be 0
+ qry="select leases from lease4_stat where subnet_id = 1 and state = 1"
+ run_statement "#11" "$qry" 0
+
+ # Leases state count for declined should be 0
+ qry="select leases from lease4_pool_stat where subnet_id = 1 and pool_id = 0 and state = 1"
+ run_statement "#12" "$qry" 0
+
+ # Insert lease4
+ qry="insert into lease4 (address, subnet_id, pool_id, state) values (112,1,1,0)"
+ run_statement "#13" "$qry"
+
+ # Assigned state count should be 1
+ qry="select leases from lease4_stat where subnet_id = 1 and state = 0"
+ run_statement "#14" "$qry" 1
+
+ # Assigned state count should be 1
+ qry="select leases from lease4_pool_stat where subnet_id = 1 and pool_id = 1 and state = 0"
+ run_statement "#15" "$qry" 1
+
+ # Insert lease4
+ qry="insert into lease4 (address, subnet_id, pool_id, state) values (113,1,2,0)"
+ run_statement "#16" "$qry"
+
+ # Assigned state count should be 2
+ qry="select leases from lease4_stat where subnet_id = 1 and state = 0"
+ run_statement "#17" "$qry" 2
+
+ # Assigned state count should be 1
+ qry="select leases from lease4_pool_stat where subnet_id = 1 and pool_id = 1 and state = 0"
+ run_statement "#18" "$qry" 1
+
+ # Assigned state count should be 1
+ qry="select leases from lease4_pool_stat where subnet_id = 1 and pool_id = 2 and state = 0"
+ run_statement "#19" "$qry" 1
+
+ # Let's wipe the whole database
+ mysql_wipe
+
+ test_finish 0
+}
+
+# Verifies that lease6_stat triggers operate correctly
+# for using a given address and lease_type. It will
+# insert a lease, update it, and delete checking the
+# lease stat counts along the way. It assumes the
+# database has been created but is empty.
+# param addr - address to use to add to subnet 1
+# param ltype - type of lease to create
+mysql_lease6_stat_per_type() {
+ addr=$1;shift
+ addr1=$1;shift
+ addr2=$1;shift
+ ltype=$1
+
+ # insert a lease6 for addr and ltype, state assigned
+ qry="insert into lease6 (address, lease_type, subnet_id, state) values (inet6_aton('$addr'),$ltype,1,0)"
+ run_statement "#2" "$qry"
+
+ # assigned stat should be 1
+ qry="select leases from lease6_stat where subnet_id = 1 and lease_type = $ltype and state = 0"
+ run_statement "#3" "$qry" 1
+
+ # assigned stat should be 1
+ qry="select leases from lease6_pool_stat where subnet_id = 1 and lease_type = $ltype and pool_id = 0 and state = 0"
+ run_statement "#4" "$qry" 1
+
+ # update the lease, changing state to declined
+ qry="update lease6 set state = 1 where address = inet6_aton('$addr')"
+ run_statement "#5" "$qry"
+
+ # leases stat for assigned state should be 0
+ qry="select leases from lease6_stat where subnet_id = 1 and lease_type = $ltype and state = 0"
+ run_statement "#6" "$qry" 0
+
+ # leases stat for assigned state should be 0
+ qry="select leases from lease6_pool_stat where subnet_id = 1 and lease_type = $ltype and pool_id = 0 and state = 0"
+ run_statement "#7" "$qry" 0
+
+ # leases count for declined state should be 1
+ qry="select leases from lease6_stat where subnet_id = 1 and lease_type = $ltype and state = 1"
+ run_statement "#8" "$qry" 1
+
+ # leases count for declined state should be 1
+ qry="select leases from lease6_pool_stat where subnet_id = 1 and lease_type = $ltype and pool_id = 0 and state = 1"
+ run_statement "#9" "$qry" 1
+
+ # delete the lease
+ qry="delete from lease6 where address = inet6_aton('$addr')"
+ run_statement "#10" "$qry"
+
+ # leases count for declined state should be 0
+ qry="select leases from lease6_stat where subnet_id = 1 and lease_type = $ltype and state = 0"
+ run_statement "#11" "$qry" 0
+
+ # leases count for declined state should be 0
+ qry="select leases from lease6_pool_stat where subnet_id = 1 and lease_type = $ltype and pool_id = 0 and state = 0"
+ run_statement "#12" "$qry" 0
+
+ # insert a lease6 for addr and ltype, state assigned
+ qry="insert into lease6 (address, lease_type, subnet_id, pool_id, state) values (inet6_aton('$addr1'),$ltype,1,1,0)"
+ run_statement "#13" "$qry"
+
+ # assigned stat should be 1
+ qry="select leases from lease6_stat where subnet_id = 1 and lease_type = $ltype and state = 0"
+ run_statement "#14" "$qry" 1
+
+ # assigned stat should be 1
+ qry="select leases from lease6_pool_stat where subnet_id = 1 and lease_type = $ltype and pool_id = 1 and state = 0"
+ run_statement "#15" "$qry" 1
+
+ # insert a lease6 for addr and ltype, state assigned
+ qry="insert into lease6 (address, lease_type, subnet_id, pool_id, state) values (inet6_aton('$addr2'),$ltype,1,2,0)"
+ run_statement "#16" "$qry"
+
+ # assigned stat should be 2
+ qry="select leases from lease6_stat where subnet_id = 1 and lease_type = $ltype and state = 0"
+ run_statement "#17" "$qry" 2
+
+ # assigned stat should be 1
+ qry="select leases from lease6_pool_stat where subnet_id = 1 and lease_type = $ltype and pool_id = 1 and state = 0"
+ run_statement "#18" "$qry" 1
+
+ # assigned stat should be 1
+ qry="select leases from lease6_pool_stat where subnet_id = 1 and lease_type = $ltype and pool_id = 2 and state = 0"
+ run_statement "#19" "$qry" 1
+}
+
+# Verifies that lease6_stat triggers operation correctly
+# for both NA and PD lease types, mysql_lease6_stat_per_type()
+mysql_lease6_stat_test() {
+
+ test_start "mysql.lease6_stat_test"
+
+ # Let's wipe the whole database
+ mysql_wipe
+
+ # Ok, now let's initialize the database
+ run_command \
+ "${kea_admin}" db-init mysql -u "${db_user}" -p "${db_password}" -n "${db_name}" -d "${db_scripts_dir}"
+ assert_eq 0 "${EXIT_CODE}" "kea-admin db-init mysql failed, expected %d, returned non-zero status code %d"
+
+ # verify lease6 stat table is present
+ qry="select count(subnet_id) from lease6_stat"
+ run_statement "#1" "$qry"
+
+ # Test for address ::11, NA lease type
+ mysql_lease6_stat_per_type "::11" "::12" "::13" "0"
+
+ # Test for address ::22, PD lease type
+ mysql_lease6_stat_per_type "::22" "::23" "::24" "1"
+
+ # Let's wipe the whole database
+ mysql_wipe
+
+ test_finish 0
+}
+
+# Verifies that you can upgrade from earlier version and
+# lease<4/6>_stat tables will be populated based on existing
+# leases and that the stat triggers work properly.
+mysql_lease_stat_upgrade_test() {
+ test_start "mysql.lease_stat_upgrade_test"
+
+ # Let's wipe the whole database
+ mysql_wipe
+
+ # We need to create an older database with lease data so we can
+ # verify the upgrade mechanisms which prepopulate the lease stat
+ # tables.
+ #
+ # Initialize database to schema 1.0.
+ mysql -u"${db_user}" -p"${db_password}" "${db_name}" < "@abs_top_srcdir@/src/bin/admin/tests/dhcpdb_create_1.0.mysql"
+
+ # Now upgrade to schema 4.0, this has lease_state in it
+ mysql_upgrade_schema_to_version 4.0
+
+ # Now we need insert some leases to "migrate" for both v4 and v6
+ qry=\
+"insert into lease4 (address, subnet_id, state) values (111,10,0);\
+ insert into lease4 (address, subnet_id, state) values (222,10,0);\
+ insert into lease4 (address, subnet_id, state) values (333,10,1);\
+ insert into lease4 (address, subnet_id, state) values (444,10,2);\
+ insert into lease4 (address, subnet_id, state) values (555,77,0)"
+ run_statement "insert v4 leases" "$qry"
+
+ qry=\
+"insert into lease6 (address, lease_type, subnet_id, state) values (inet6_aton('::11'),0,40,0);\
+ insert into lease6 (address, lease_type, subnet_id, state) values (inet6_aton('::22'),0,40,1);\
+ insert into lease6 (address, lease_type, subnet_id, state) values (inet6_aton('::33'),1,40,0);\
+ insert into lease6 (address, lease_type, subnet_id, state) values (inet6_aton('::44'),1,50,0);\
+ insert into lease6 (address, lease_type, subnet_id, state) values (inet6_aton('::55'),1,50,0);\
+ insert into lease6 (address, lease_type, subnet_id, state) values (inet6_aton('::66'),1,40,2)"
+ run_statement "insert v6 leases" "$qry"
+
+ # Let's upgrade it to the latest version.
+ run_command \
+ "${kea_admin}" db-upgrade mysql -u "${db_user}" -p "${db_password}" -n "${db_name}" -d "${db_scripts_dir}"
+
+ #
+ # First we'll verify lease4_stats are correct after migration.
+ #
+
+ # Assigned leases for subnet 10 should be 2
+ qry="select leases from lease4_stat where subnet_id = 10 and state = 0"
+ run_statement "#4.1" "$qry" 2
+
+ # Assigned leases for subnet 10 should be 2
+ qry="select leases from lease4_pool_stat where subnet_id = 10 and pool_id = 0 and state = 0"
+ run_statement "#4.2" "$qry" 2
+
+ # Assigned leases for subnet 77 should be 1
+ qry="select leases from lease4_stat where subnet_id = 77 and state = 0"
+ run_statement "#4.3" "$qry" 1
+
+ # Assigned leases for subnet 77 should be 1
+ qry="select leases from lease4_pool_stat where subnet_id = 77 and pool_id = 0 and state = 0"
+ run_statement "#4.4" "$qry" 1
+
+ # Should be no records for EXPIRED
+ qry="select count(subnet_id) from lease4_stat where state = 2"
+ run_statement "#4.5" "$qry" 0
+
+ # Should be no records for EXPIRED
+ qry="select count(subnet_id) from lease4_pool_stat where state = 2"
+ run_statement "#4.6" "$qry" 0
+
+ #
+ # Now we'll verify v4 trigger operation for insert, update, and delete
+ #
+
+ # Insert a new lease subnet 77
+ qry="insert into lease4 (address, subnet_id, pool_id, state) values (777,77,1,0)"
+ run_statement "#4.7" "$qry"
+
+ # Assigned count for subnet 77 should be 2
+ qry="select leases from lease4_stat where subnet_id = 77 and state = 0"
+ run_statement "#4.8" "$qry" 2
+
+ # Assigned count for subnet 77 should be 1
+ qry="select leases from lease4_pool_stat where subnet_id = 77 and pool_id = 0 and state = 0"
+ run_statement "#4.9" "$qry" 1
+
+ # Assigned count for subnet 77 should be 1
+ qry="select leases from lease4_pool_stat where subnet_id = 77 and pool_id = 1 and state = 0"
+ run_statement "#4.10" "$qry" 1
+
+ # Update the state of the new lease to declined
+ qry="update lease4 set state = 1 where address = 777"
+ run_statement "#4.11" "$qry"
+
+ # Assigned count for subnet 77 should be 1 again
+ qry="select leases from lease4_stat where subnet_id = 77 and state = 0"
+ run_statement "#4.12" "$qry" 1
+
+ # Assigned count for subnet 77 should be 1 again
+ qry="select leases from lease4_pool_stat where subnet_id = 77 and pool_id = 0 and state = 0"
+ run_statement "#4.13" "$qry" 1
+
+ # Assigned count for subnet 77 should be 0 again
+ qry="select leases from lease4_pool_stat where subnet_id = 77 and pool_id = 1 and state = 0"
+ run_statement "#4.14" "$qry" 0
+
+ # Declined count for subnet 77 should be 1
+ qry="select leases from lease4_stat where subnet_id = 77 and state = 1"
+ run_statement "#4.15" "$qry" 1
+
+ # Declined count for subnet 77 should be 1
+ qry="select leases from lease4_pool_stat where subnet_id = 77 and pool_id = 1 and state = 1"
+ run_statement "#4.16" "$qry" 1
+
+ # Delete the lease.
+ qry="delete from lease4 where address = 777"
+ run_statement "#4.17" "$qry"
+
+ # Declined count for subnet 77 should be 0
+ qry="select leases from lease4_stat where subnet_id = 77 and state = 1"
+ run_statement "#4.18" "$qry" 0
+
+ # Declined count for subnet 77 should be 0
+ qry="select leases from lease4_pool_stat where subnet_id = 77 and pool_id = 1 and state = 1"
+ run_statement "#4.19" "$qry" 0
+
+ #
+ # Next we'll verify lease6_stats are correct after migration.
+ #
+
+ # Assigned leases for subnet 40 should be 1
+ qry="select leases from lease6_stat where subnet_id = 40 and lease_type = 0 and state = 0"
+ run_statement "#6.1" "$qry" 1
+
+ # Assigned leases for subnet 40 should be 1
+ qry="select leases from lease6_pool_stat where subnet_id = 40 and lease_type = 0 and pool_id = 0 and state = 0"
+ run_statement "#6.2" "$qry" 1
+
+ # Assigned (PD) leases for subnet 40 should be 1
+ qry="select leases from lease6_stat where subnet_id = 40 and lease_type = 1 and state = 0"
+ run_statement "#6.3" "$qry" 1
+
+ # Assigned (PD) leases for subnet 40 should be 1
+ qry="select leases from lease6_pool_stat where subnet_id = 40 and lease_type = 1 and pool_id = 0 and state = 0"
+ run_statement "#6.4" "$qry" 1
+
+ # Declined leases for subnet 40 should be 1
+ qry="select leases from lease6_stat where subnet_id = 40 and lease_type = 0 and state = 1"
+ run_statement "#6.5" "$qry" 1
+
+ # Declined leases for subnet 40 should be 1
+ qry="select leases from lease6_pool_stat where subnet_id = 40 and lease_type = 0 and pool_id = 0 and state = 1"
+ run_statement "#6.6" "$qry" 1
+
+ # Assigned (PD) leases for subnet 50 should be 2
+ qry="select leases from lease6_stat where subnet_id = 50 and lease_type = 1 and state = 0"
+ run_statement "#6.7" "$qry" 2
+
+ # Assigned (PD) leases for subnet 50 should be 2
+ qry="select leases from lease6_pool_stat where subnet_id = 50 and lease_type = 1 and pool_id = 0 and state = 0"
+ run_statement "#6.8" "$qry" 2
+
+ # Should be no records for EXPIRED
+ qry="select count(subnet_id) from lease6_stat where state = 2"
+ run_statement "#6.9" "$qry" 0
+
+ # Should be no records for EXPIRED
+ qry="select count(subnet_id) from lease6_pool_stat where state = 2"
+ run_statement "#6.10" "$qry" 0
+
+ #
+ # Finally we'll verify v6 trigger operation for insert, update, and delete
+ #
+
+ # Insert a new lease subnet 50
+ qry="insert into lease6 (address, subnet_id, pool_id, lease_type, state) values (inet6_aton('::77'),50,1,1,0)"
+ run_statement "#6.11" "$qry"
+
+ # Assigned count for subnet 50 should be 3
+ qry="select leases from lease6_stat where subnet_id = 50 and lease_type = 1 and state = 0"
+ run_statement "#6.12" "$qry" 3
+
+ # Assigned count for subnet 50 should be 2
+ qry="select leases from lease6_pool_stat where subnet_id = 50 and lease_type = 1 and pool_id = 0 and state = 0"
+ run_statement "#6.13" "$qry" 2
+
+ # Assigned count for subnet 50 should be 1
+ qry="select leases from lease6_pool_stat where subnet_id = 50 and lease_type = 1 and pool_id = 1 and state = 0"
+ run_statement "#6.14" "$qry" 1
+
+ # Update the state of the new lease to expired
+ qry="update lease6 set state = 2 where address = inet6_aton('::77')"
+ run_statement "#6.15" "$qry"
+
+ # Assigned count for subnet 50 should be 2 again
+ qry="select leases from lease6_stat where subnet_id = 50 and lease_type = 1 and state = 0"
+ run_statement "#6.16" "$qry" 2
+
+ # Assigned count for subnet 50 should be 0 again
+ qry="select leases from lease6_pool_stat where subnet_id = 50 and lease_type = 1 and pool_id = 1 and state = 0"
+ run_statement "#6.17" "$qry" 0
+
+ # Delete another PD lease.
+ qry="delete from lease6 where address = inet6_aton('::55')"
+ run_statement "#6.18" "$qry"
+
+ # Assigned leases for subnet 50 should be 1
+ qry="select leases from lease6_stat where subnet_id = 50 and lease_type = 1 and state = 0"
+ run_statement "#6.19" "$qry" 1
+
+ # Assigned leases for subnet 50 should be 1
+ qry="select leases from lease6_pool_stat where subnet_id = 50 and lease_type = 1 and pool_id = 0 and state = 0"
+ run_statement "#6.20" "$qry" 1
+
+ # Let's wipe the whole database
+ mysql_wipe
+
+ test_finish 0
+}
+
+mysql_lease_stat_recount_test() {
+ test_start "mysql.lease_stat_recount_test"
+
+ # Let's wipe the whole database
+ mysql_wipe
+
+ # Ok, now let's initialize the database
+ run_command \
+ "${kea_admin}" db-init mysql -u "${db_user}" -p "${db_password}" -n "${db_name}" -d "${db_scripts_dir}"
+ assert_eq 0 "${EXIT_CODE}" "kea-admin db-init mysql failed, expected %d, returned non-zero status code %d"
+
+ # Now we need insert some leases to "recount"
+ qry=\
+"insert into lease4 (address, subnet_id, state) values (111,10,0);\
+ insert into lease4 (address, subnet_id, pool_id, state) values (222,10,1,0);\
+ insert into lease4 (address, subnet_id, state) values (333,10,1);\
+ insert into lease4 (address, subnet_id, state) values (444,10,2);\
+ insert into lease4 (address, subnet_id, pool_id, state) values (555,77,2,0)"
+ run_statement "insert v4 leases" "$qry"
+
+ qry=\
+"insert into lease6 (address, lease_type, subnet_id, state) values ('::111',0,40,0);\
+ insert into lease6 (address, lease_type, subnet_id, pool_id, state) values ('::222',0,40,1,1);\
+ insert into lease6 (address, lease_type, subnet_id, state) values ('::333',1,40,0);\
+ insert into lease6 (address, lease_type, subnet_id, state) values ('::444',1,50,0);\
+ insert into lease6 (address, lease_type, subnet_id, pool_id, state) values ('::555',1,50,2,0);\
+ insert into lease6 (address, lease_type, subnet_id, state) values ('::666',1,40,2)"
+ run_statement "insert v6 leases" "$qry"
+
+ # Now we change some counters.
+ qry=\
+"insert into lease4_stat (subnet_id, state, leases) values (20,0,1);\
+ update lease4_stat set leases = 5 where subnet_id = 10 and state = 0;\
+ delete from lease4_stat where subnet_id = 10 and state = 2"
+ run_statement "change v4 stats" "$qry"
+
+ qry=\
+"insert into lease4_pool_stat (subnet_id, pool_id, state, leases) values (20,3,0,1);\
+ update lease4_pool_stat set leases = 5 where subnet_id = 10 and pool_id = 0 and state = 0;\
+ delete from lease4_pool_stat where subnet_id = 10 and pool_id = 0 and state = 2"
+ run_statement "change v4 stats" "$qry"
+
+ qry=\
+"insert into lease6_stat (subnet_id, lease_type, state, leases) values (20,1,0,1);\
+ update lease6_stat set leases = 5 where subnet_id = 40 and lease_type = 0 and state = 0;\
+ delete from lease6_stat where subnet_id = 40 and lease_type = 1 and state = 2"
+ run_statement "change v6 stats" "$qry"
+
+ qry=\
+"insert into lease6_pool_stat (subnet_id, pool_id, lease_type, state, leases) values (20,3,1,0,1);\
+ update lease6_pool_stat set leases = 5 where subnet_id = 40 and lease_type = 0 and pool_id = 0 and state = 0;\
+ delete from lease6_pool_stat where subnet_id = 40 and lease_type = 1 and pool_id = 0 and state = 2"
+ run_statement "change v6 stats" "$qry"
+
+ # Recount all statistics from scratch.
+ run_command \
+ "${kea_admin}" stats-recount mysql -u "${db_user}" -p "${db_password}" -n "${db_name}"
+ assert_eq 0 "${EXIT_CODE}" "kea-admin stats-recount mysql failed, expected %d, returned non-zero status code %d"
+
+ #
+ # First we'll verify lease4_stats are correct after recount.
+ #
+
+ # Assigned leases for subnet 10 should be 2
+ qry="select leases from lease4_stat where subnet_id = 10 and state = 0"
+ run_statement "#4.1" "$qry" 2
+
+ # Assigned leases for subnet 10 should be 1
+ qry="select leases from lease4_pool_stat where subnet_id = 10 and pool_id = 0 and state = 0"
+ run_statement "#4.2" "$qry" 1
+
+ # Assigned leases for subnet 10 should be 1
+ qry="select leases from lease4_pool_stat where subnet_id = 10 and pool_id = 1 and state = 0"
+ run_statement "#4.3" "$qry" 1
+
+ # Declined leases for subnet 10 should be 1
+ qry="select leases from lease4_stat where subnet_id = 10 and state = 1"
+ run_statement "#4.4" "$qry" 1
+
+ # Assigned leases for subnet 10 should be 1
+ qry="select leases from lease4_pool_stat where subnet_id = 10 and pool_id = 0 and state = 1"
+ run_statement "#4.5" "$qry" 1
+
+ # Assigned leases for subnet 77 should be 1
+ qry="select leases from lease4_stat where subnet_id = 77 and state = 0"
+ run_statement "#4.6" "$qry" 1
+
+ # Assigned leases for subnet 77 should be 1
+ qry="select leases from lease4_pool_stat where subnet_id = 77 and pool_id = 2 and state = 0"
+ run_statement "#4.7" "$qry" 1
+
+ # Should be no records for EXPIRED
+ qry="select count(subnet_id) from lease4_stat where state = 2"
+ run_statement "#4.8" "$qry" 0
+
+ # Should be no records for EXPIRED
+ qry="select count(subnet_id) from lease4_pool_stat where state = 2"
+ run_statement "#4.9" "$qry" 0
+
+ #
+ # Next we'll verify lease6_stats are correct after recount.
+ #
+
+ # Assigned leases for subnet 40 should be 1
+ qry="select leases from lease6_stat where subnet_id = 40 and lease_type = 0 and state = 0"
+ run_statement "#6.1" "$qry" 1
+
+ # Assigned leases for subnet 40 should be 1
+ qry="select leases from lease6_pool_stat where subnet_id = 40 and lease_type = 0 and pool_id = 0 and state = 0"
+ run_statement "#6.2" "$qry" 1
+
+ # Assigned (PD) leases for subnet 40 should be 1
+ qry="select leases from lease6_stat where subnet_id = 40 and lease_type = 1 and state = 0"
+ run_statement "#6.3" "$qry" 1
+
+ # Assigned (PD) leases for subnet 40 should be 1
+ qry="select leases from lease6_pool_stat where subnet_id = 40 and lease_type = 1 and pool_id = 0 and state = 0"
+ run_statement "#6.4" "$qry" 1
+
+ # Declined leases for subnet 40 should be 1
+ qry="select leases from lease6_stat where subnet_id = 40 and lease_type = 0 and state = 1"
+ run_statement "#6.5" "$qry" 1
+
+ # Declined leases for subnet 40 should be 1
+ qry="select leases from lease6_pool_stat where subnet_id = 40 and lease_type = 0 and pool_id = 1 and state = 1"
+ run_statement "#6.6" "$qry" 1
+
+ # Assigned (PD) leases for subnet 50 should be 2
+ qry="select leases from lease6_stat where subnet_id = 50 and lease_type = 1 and state = 0"
+ run_statement "#6.7" "$qry" 2
+
+ # Assigned (PD) leases for subnet 50 should be 1
+ qry="select leases from lease6_pool_stat where subnet_id = 50 and lease_type = 1 and pool_id = 0 and state = 0"
+ run_statement "#6.8" "$qry" 1
+
+ # Assigned (PD) leases for subnet 50 should be 1
+ qry="select leases from lease6_pool_stat where subnet_id = 50 and lease_type = 1 and pool_id = 2 and state = 0"
+ run_statement "#6.9" "$qry" 1
+
+ # Should be no records for EXPIRED
+ qry="select count(subnet_id) from lease6_stat where state = 2"
+ run_statement "#6.10" "$qry" 0
+
+ # Should be no records for EXPIRED
+ qry="select count(subnet_id) from lease6_pool_stat where state = 2"
+ run_statement "#6.11" "$qry" 0
+
+ # Let's wipe the whole database
+ mysql_wipe
+
+ test_finish 0
+}
+
+# Verifies that you can upgrade from an earlier version and
+# that unused subnet ID values in hosts and options tables are
+# converted to NULL.
+mysql_unused_subnet_id_test() {
+ test_start "mysql.unused_subnet_id_test"
+
+ # Let's wipe the whole database
+ mysql_wipe
+
+ # We need to create an older database with lease data so we can
+ # verify the upgrade mechanisms which convert subnet id values
+ #
+ # Initialize database to schema 1.0.
+ mysql -u"${db_user}" -p"${db_password}" "${db_name}" < "@abs_top_srcdir@/src/bin/admin/tests/dhcpdb_create_1.0.mysql"
+
+ # Now upgrade to schema 6.0, this has lease_state in it
+ mysql_upgrade_schema_to_version 6.0
+
+ # Now we need insert some hosts to "migrate" for both v4 and v6
+ qry=\
+"insert into hosts (dhcp_identifier_type, dhcp_identifier, dhcp4_subnet_id, dhcp6_subnet_id, hostname)\
+ values (0, '0123456', 0, 0, 'both'); \
+ insert into hosts (dhcp_identifier_type, dhcp_identifier, dhcp4_subnet_id, dhcp6_subnet_id, hostname)\
+ values (0, '1123456', 4, 0, 'v4only');
+ insert into hosts (dhcp_identifier_type, dhcp_identifier, dhcp4_subnet_id, dhcp6_subnet_id, hostname)\
+ values (0, '2123456', 0, 6, 'v6only');\
+ insert into hosts (dhcp_identifier_type, dhcp_identifier, dhcp4_subnet_id, dhcp6_subnet_id, hostname) \
+ values (0, '3123456', 4, 6, 'neither')"
+
+ run_statement "insert hosts" "$qry"
+
+ # Now we need insert some options to "migrate" for both v4 and v6
+ qry=\
+"insert into dhcp4_options (code, dhcp4_subnet_id, scope_id) values (1, 4, 0);\
+ insert into dhcp4_options (code, dhcp4_subnet_id, scope_id) values (2, 0, 0);\
+ insert into dhcp6_options (code, dhcp6_subnet_id, scope_id) values (1, 6, 0);\
+ insert into dhcp6_options (code, dhcp6_subnet_id, scope_id) values (2, 0, 0)"
+
+ run_statement "insert options" "$qry"
+
+ # Ok, we have a 6.0 database with hosts and options. Let's upgrade it to 7.0
+ # For versions higher than 7.0 some new constraints fail to be added
+ # with the not empty tables, for instance the 9.1 -> 9.2 upgrade script
+ # can raise a MySQL error 1452 for fk_dhcp4_options_subnet constraint.
+ mysql_upgrade_schema_to_version 7.0
+
+ # Version should be new 7.0
+ version=$("${kea_admin}" db-version mysql -u "${db_user}" -p "${db_password}" -n "${db_name}" -d "${db_scripts_dir}")
+ assert_str_eq "7.0" "${version}" "Expected kea-admin to return %s, returned value was %s"
+
+ # Two hosts should have null v4 subnet ids
+ qry="select count(host_id) from hosts where dhcp4_subnet_id is null"
+ run_statement "#hosts.1" "$qry" 2
+
+ # Two hosts should have v4 subnet ids = 4
+ qry="select count(host_id) from hosts where dhcp4_subnet_id = 4"
+ run_statement "#hosts.2" "$qry" 2
+
+ # Two hosts should have null v6 subnet ids
+ qry="select count(host_id) from hosts where dhcp6_subnet_id is null"
+ run_statement "#hosts.3" "$qry" 2
+
+ # Two hosts should should have v6 subnet ids = 6
+ qry="select count(host_id) from hosts where dhcp6_subnet_id = 6"
+ run_statement "#hosts.4" "$qry" 2
+
+ # One option should have null v4 subnet id
+ qry="select count(option_id) from dhcp4_options where dhcp4_subnet_id is null"
+ run_statement "#options.1" "$qry" 1
+
+ # One option should have v4 subnet id = 4
+ qry="select count(option_id) from dhcp4_options where dhcp4_subnet_id = 4"
+ run_statement "#options.2" "$qry" 1
+
+ # One option should have null v6 subnet id
+ qry="select count(option_id) from dhcp6_options where dhcp6_subnet_id is null"
+ run_statement "#options.3" "$qry" 1
+
+ # One option should have v4 subnet id = 6
+ qry="select count(option_id) from dhcp6_options where dhcp6_subnet_id = 6"
+ run_statement "#options.4" "$qry" 1
+
+ # Let's wipe the whole database
+ mysql_wipe
+
+ test_finish 0
+}
+
+# Verifies that you can upgrade from an earlier version and
+# that reservation_mode values in subnet and shared network tables are
+# converted to new reservations flags.
+mysql_reservation_mode_upgrade_test() {
+ test_start "mysql.reservation_mode_upgrade_test"
+
+ # Let's wipe the whole database
+ mysql_wipe
+
+ # We need to create an older database with lease data so we can
+ # verify the upgrade mechanisms which convert reservation values
+ #
+ # Initialize database to schema 1.0.
+ mysql -u"${db_user}" -p"${db_password}" "${db_name}" < "@abs_top_srcdir@/src/bin/admin/tests/dhcpdb_create_1.0.mysql"
+
+ # Now upgrade to schema 9.4, the last version with reservation_mode
+ mysql_upgrade_schema_to_version 9.4
+
+ # Now we need insert some subnets and shared networks.
+ sql=\
+"set @disable_audit = 1; \
+ insert into dhcp4_shared_network (name, modification_ts, reservation_mode)\
+ values ('test0', current_timestamp, 0);\
+ insert into dhcp4_shared_network (name, modification_ts, reservation_mode)\
+ values ('test1', current_timestamp, 1);\
+ insert into dhcp4_shared_network (name, modification_ts, reservation_mode)\
+ values ('test2', current_timestamp, 2);\
+ insert into dhcp4_shared_network (name, modification_ts, reservation_mode)\
+ values ('test3', current_timestamp, 3);\
+ insert into dhcp4_subnet (subnet_id, subnet_prefix, modification_ts, reservation_mode)\
+ values (1234, '192.0.0.0/24', current_timestamp, 0);\
+ insert into dhcp4_subnet (subnet_id, subnet_prefix, modification_ts, reservation_mode)\
+ values (2345, '192.0.1.0/24', current_timestamp, 1);\
+ insert into dhcp4_subnet (subnet_id, subnet_prefix, modification_ts, reservation_mode)\
+ values (3456, '192.0.2.0/24', current_timestamp, 2);\
+ insert into dhcp4_subnet (subnet_id, subnet_prefix, modification_ts, reservation_mode)\
+ values (4567, '192.0.3.0/24', current_timestamp, 3);\
+ insert into dhcp6_shared_network (name, modification_ts, reservation_mode)\
+ values ('test0', current_timestamp, 0);\
+ insert into dhcp6_shared_network (name, modification_ts, reservation_mode)\
+ values ('test1', current_timestamp, 1);\
+ insert into dhcp6_shared_network (name, modification_ts, reservation_mode)\
+ values ('test2', current_timestamp, 2);\
+ insert into dhcp6_shared_network (name, modification_ts, reservation_mode)\
+ values ('test3', current_timestamp, 3);\
+ insert into dhcp6_subnet (subnet_id, subnet_prefix, modification_ts, reservation_mode)\
+ values (1234, '2001:db8::/64', current_timestamp, 0);\
+ insert into dhcp6_subnet (subnet_id, subnet_prefix, modification_ts, reservation_mode)\
+ values (2345, '2001:db8:1::/64', current_timestamp, 1);\
+ insert into dhcp6_subnet (subnet_id, subnet_prefix, modification_ts, reservation_mode)\
+ values (3456, '2001:db8:2::/64', current_timestamp, 2);\
+ insert into dhcp6_subnet (subnet_id, subnet_prefix, modification_ts, reservation_mode)\
+ values (4567, '2001:db8:3::/64', current_timestamp, 3);\
+ set @disable_audit = 0"
+
+ run_statement "insert reservation_mode" "$sql"
+
+ qry="select count(*) from dhcp4_shared_network"
+ run_statement "#get 4_shared count before update" "$qry" 4
+
+ qry="select count(*) from dhcp4_subnet"
+ run_statement "#get 4_subnet count before update" "$qry" 4
+
+ qry="select count(*) from dhcp6_shared_network"
+ run_statement "#get 6_shared count before update" "$qry" 4
+
+ qry="select count(*) from dhcp6_subnet"
+ run_statement "#get 6_subnet count before update" "$qry" 4
+
+ # Upgrade to schema 9.5.
+ mysql_upgrade_schema_to_version 9.5
+
+ # Test DISABLED (0) -> false, false, null
+ qry="select count(id) from dhcp4_shared_network where reservations_global = false and reservations_in_subnet = false and reservations_out_of_pool is null and name = 'test0'"
+ run_statement "#4_shared_disabled" "$qry" 1
+
+ # Test OUT_OF_POOL (1) -> false, true, true
+ qry="select count(id) from dhcp4_shared_network where reservations_global = false and reservations_in_subnet = true and reservations_out_of_pool = true and name = 'test1'"
+ run_statement "#4_shared_out_of_pool" "$qry" 1
+
+ # Test GLOBAL (2) -> true, false, null
+ qry="select count(id) from dhcp4_shared_network where reservations_global = true and reservations_in_subnet = false and reservations_out_of_pool is null and name = 'test2'"
+ run_statement "#4_shared_global" "$qry" 1
+
+ # Test ALL (3) -> false, true, false
+ qry="select count(id) from dhcp4_shared_network where reservations_global = false and reservations_in_subnet = true and reservations_out_of_pool = false and name = 'test3'"
+ run_statement "#4_shared_all" "$qry" 1
+
+ # Test DISABLED (0) -> false, false, null
+ qry="select count(subnet_id) from dhcp4_subnet where reservations_global = false and reservations_in_subnet = false and reservations_out_of_pool is null and subnet_prefix = '192.0.0.0/24'"
+ run_statement "#4_subnet_disabled" "$qry" 1
+
+ # Test OUT_OF_POOL (1) -> false, true, true
+ qry="select count(subnet_id) from dhcp4_subnet where reservations_global = false and reservations_in_subnet = true and reservations_out_of_pool = true and subnet_prefix = '192.0.1.0/24'"
+ run_statement "#4_subnet_out_of_pool" "$qry" 1
+
+ # Test GLOBAL (2) -> true, false, null
+ qry="select count(subnet_id) from dhcp4_subnet where reservations_global = true and reservations_in_subnet = false and reservations_out_of_pool is null and subnet_prefix = '192.0.2.0/24'"
+ run_statement "#4_subnet_global" "$qry" 1
+
+ # Test ALL (3) -> false, true, false
+ qry="select count(subnet_id) from dhcp4_subnet where reservations_global = false and reservations_in_subnet = true and reservations_out_of_pool = false and subnet_prefix = '192.0.3.0/24'"
+ run_statement "#4_subnet_all" "$qry" 1
+
+ # Test DISABLED (0) -> false, false, null
+ qry="select count(id) from dhcp6_shared_network where reservations_global = false and reservations_in_subnet = false and reservations_out_of_pool is null and name = 'test0'"
+ run_statement "#6_shared_disabled" "$qry" 1
+
+ # Test OUT_OF_POOL (1) -> false, true, true
+ qry="select count(id) from dhcp6_shared_network where reservations_global = false and reservations_in_subnet = true and reservations_out_of_pool = true and name = 'test1'"
+ run_statement "#6_shared_out_of_pool" "$qry" 1
+
+ # Test GLOBAL (2) -> true, false, null
+ qry="select count(id) from dhcp6_shared_network where reservations_global = true and reservations_in_subnet = false and reservations_out_of_pool is null and name = 'test2'"
+ run_statement "#6_shared_global" "$qry" 1
+
+ # Test ALL (3) -> false, true, false
+ qry="select count(id) from dhcp6_shared_network where reservations_global = false and reservations_in_subnet = true and reservations_out_of_pool = false and name = 'test3'"
+ run_statement "#6_shared_all" "$qry" 1
+
+ # Test DISABLED (0) -> false, false, null
+ qry="select count(subnet_id) from dhcp6_subnet where reservations_global = false and reservations_in_subnet = false and reservations_out_of_pool is null and subnet_prefix = '2001:db8::/64'"
+ run_statement "#6_subnet_disabled" "$qry" 1
+
+ # Test OUT_OF_POOL (1) -> false, true, true
+ qry="select count(subnet_id) from dhcp6_subnet where reservations_global = false and reservations_in_subnet = true and reservations_out_of_pool = true and subnet_prefix = '2001:db8:1::/64'"
+ run_statement "#6_subnet_out_of_pool" "$qry" 1
+
+ # Test GLOBAL (2) -> true, false, null
+ qry="select count(subnet_id) from dhcp6_subnet where reservations_global = true and reservations_in_subnet = false and reservations_out_of_pool is null and subnet_prefix = '2001:db8:2::/64'"
+ run_statement "#6_subnet_global" "$qry" 1
+
+ # Test ALL (3) -> false, true, false
+ qry="select count(subnet_id) from dhcp6_subnet where reservations_global = false and reservations_in_subnet = true and reservations_out_of_pool = false and subnet_prefix = '2001:db8:3::/64'"
+ run_statement "#6_subnet_all" "$qry" 1
+
+ qry="select count(*) from dhcp4_shared_network"
+ run_statement "#get 4_shared count before update" "$qry" 4
+
+ qry="select count(*) from dhcp4_subnet"
+ run_statement "#get 4_subnet count before update" "$qry" 4
+
+ qry="select count(*) from dhcp6_shared_network"
+ run_statement "#get 6_shared count before update" "$qry" 4
+
+ qry="select count(*) from dhcp6_subnet"
+ run_statement "#get 6_subnet count before update" "$qry" 4
+
+ # Let's wipe the whole database
+ mysql_wipe
+
+ test_finish 0
+}
+
+# Verifies that several tables for holding client classes are created
+# and the triggers and stored procedures positioning the client classes
+# and validating their dependencies behave correctly.
+mysql_client_class_test() {
+ table_prefix="$1"
+
+ test_start "mysql.client_classes_test.${table_prefix}"
+
+ # Let's wipe the whole database
+ mysql_wipe
+
+ # We need to create an older database with lease data so we can
+ # verify the upgrade mechanisms which validates client classes and
+ # dependencies behave correctly
+ #
+ # Initialize database to schema 1.0.
+ mysql -u"${db_user}" -p"${db_password}" "${db_name}" < "@abs_top_srcdir@/src/bin/admin/tests/dhcpdb_create_1.0.mysql"
+
+ # Now upgrade to schema 10.0 that can contain client classes.
+ mysql_upgrade_schema_to_version 10.0
+
+ # Insert a new server.
+ sql=\
+"SET @disable_audit = 1; \
+ INSERT INTO ${table_prefix}_server (tag, modification_ts) VALUES ('server1', NOW()); \
+ SET @disable_audit = 0"
+
+ run_statement "insert servers" "$sql"
+
+ # Insert client class foo at the top of the hierarchy. It has no dependencies.
+ sql=\
+"START TRANSACTION; \
+ SET @disable_audit = 1; \
+ INSERT INTO ${table_prefix}_client_class (name, modification_ts, follow_class_name, depend_on_known_directly) VALUES ('foo', NOW(), NULL, 1); \
+ SET @last_id = LAST_INSERT_ID(); \
+ INSERT INTO ${table_prefix}_client_class_server (class_id, server_id) \
+ VALUES (@last_id, (SELECT id FROM ${table_prefix}_server WHERE tag = 'all')); \
+ SET @disable_audit = 0; \
+ COMMIT"
+ run_statement "insert client class foo" "$sql"
+
+ # Insert client class foobar after the foo class.
+ sql=\
+"START TRANSACTION; \
+ SET @disable_audit = 1; \
+ INSERT INTO ${table_prefix}_client_class (name, modification_ts, follow_class_name) VALUES ('foobar', NOW(), NULL); \
+ SET @last_id = LAST_INSERT_ID(); \
+ INSERT INTO ${table_prefix}_client_class_server (class_id, server_id) \
+ VALUES (@last_id, (SELECT id FROM ${table_prefix}_server WHERE tag = 'server1')); \
+ SET @disable_audit = 0; \
+ COMMIT"
+ run_statement "insert client class foobar" "$sql"
+
+ # Insert the client class bar at the end. This class depends on the client
+ # class foo.
+ sql=\
+"START TRANSACTION; \
+ SET @disable_audit = 1; \
+ INSERT INTO ${table_prefix}_client_class (name, modification_ts, follow_class_name) VALUES ('bar', NOW(), 'foo'); \
+ SET @last_id = LAST_INSERT_ID(); \
+ INSERT INTO ${table_prefix}_client_class_server (class_id, server_id) \
+ VALUES (@last_id, (SELECT id FROM ${table_prefix}_server WHERE tag = 'server1')); \
+ INSERT INTO ${table_prefix}_client_class_dependency (class_id, dependency_id) \
+ VALUES (@last_id, (SELECT id FROM ${table_prefix}_client_class WHERE name = 'foo')); \
+ SET @disable_audit = 0; \
+ COMMIT"
+ run_statement "insert client class bar" "$sql"
+
+ # Ensure that all three classes have been added in the expected order.
+ sql="SELECT o.order_index FROM ${table_prefix}_client_class AS c \
+ INNER JOIN ${table_prefix}_client_class_order AS o \
+ ON c.id = o.class_id WHERE c.name = 'foo'"
+ run_statement "#get order index of class foo" "$sql" 1
+
+ sql="SELECT o.order_index FROM ${table_prefix}_client_class AS c \
+ INNER JOIN ${table_prefix}_client_class_order AS o \
+ ON c.id = o.class_id WHERE c.name = 'bar'"
+ run_statement "#get order index of class bar" "$sql" 2
+
+ sql="SELECT o.order_index FROM ${table_prefix}_client_class AS c \
+ INNER JOIN ${table_prefix}_client_class_order AS o \
+ ON c.id = o.class_id WHERE c.name = 'foobar'"
+ run_statement "#get order index of class foobar" "$sql" 3
+
+ # Update the class bar moving behind the foobar class.
+ sql=\
+"START TRANSACTION; \
+ SET @disable_audit = 1; \
+ UPDATE ${table_prefix}_client_class SET follow_class_name = 'foobar' WHERE name = 'bar'; \
+ SET @disable_audit = 0; \
+ COMMIT"
+ run_statement "update client class bar with re-positioning" "$sql"
+
+ # Check that the order of the last two classes was changed.
+ sql="SELECT o.order_index FROM ${table_prefix}_client_class AS c \
+ INNER JOIN ${table_prefix}_client_class_order AS o \
+ ON c.id = o.class_id WHERE c.name = 'bar'"
+ run_statement "#get order index of class bar" "$sql" 4
+
+ sql="SELECT o.order_index FROM ${table_prefix}_client_class AS c \
+ INNER JOIN ${table_prefix}_client_class_order AS o \
+ ON c.id = o.class_id WHERE c.name = 'foobar'"
+ run_statement "#get order index of class foobar" "$sql" 3
+
+ # Check that the first class is still at the first position.
+ sql="SELECT o.order_index FROM ${table_prefix}_client_class AS c \
+ INNER JOIN ${table_prefix}_client_class_order AS o \
+ ON c.id = o.class_id WHERE c.name = 'foo'"
+ run_statement "#get order index of class foo" "$sql" 1
+
+ sql=\
+"SET @disable_audit = 1; \
+ INSERT INTO ${table_prefix}_options(code, scope_id, dhcp_client_class, modification_ts) \
+ VALUES (222, 0, '', now()); \
+ SET @disable_audit = 0"
+ run_statement "add option with an empty dhcp_client class" "$sql"
+
+ # Let's make sure that we can upgrade to version 12.0. This version
+ # introduces a foreign key between dhcpX_client_class and dhcpX_options
+ # table. The migration should set the dhcp_client_class to NULL.
+ mysql_upgrade_schema_to_version 12.0
+
+ # Let's wipe the whole database
+ mysql_wipe
+
+ test_finish 0
+}
+
+# Verifies that the migration 9.6 to 10.0 modifies the length of
+# the tag column in the dhcp4_server and dhcp6_server tables.
+mysql_shrink_server_tag_test() {
+
+ test_start "mysql.shrink_server_tag_test"
+
+ mysql_wipe
+
+ mysql -u"${db_user}" -p"${db_password}" "${db_name}" < "@abs_top_srcdir@/src/bin/admin/tests/dhcpdb_create_1.0.mysql"
+ # Now upgrade to schema 9.6.
+ mysql_upgrade_schema_to_version 9.6
+
+ # Unfortunately, this schema version already contains 64 character
+ # long server tags. Let's extend it back, but not to 256 characters
+ # because it is proven to cause errors in some configurations.
+ sql=\
+"ALTER TABLE dhcp4_server MODIFY COLUMN tag VARCHAR(128) NOT NULL"
+ run_statement "extend server DHCPv4 server tag column", "$sql"
+
+ sql=\
+"ALTER TABLE dhcp6_server MODIFY COLUMN tag VARCHAR(128) NOT NULL"
+ run_statement "extend server DHCPv6 server tag column", "$sql"
+
+ mysql_upgrade_schema_to_version 10.0
+
+ # Ensure that the migration corrected the lengths.
+ sql=\
+"SELECT CHARACTER_MAXIMUM_LENGTH \
+ FROM INFORMATION_SCHEMA.COLUMNS \
+ WHERE TABLE_SCHEMA='${db_name}' AND TABLE_NAME='dhcp4_server' AND COLUMN_NAME='tag'"
+ run_statement "get new tag column length" "$sql" 64
+
+ sql=\
+"SELECT CHARACTER_MAXIMUM_LENGTH \
+ FROM INFORMATION_SCHEMA.COLUMNS \
+ WHERE TABLE_SCHEMA='${db_name}' AND TABLE_NAME='dhcp6_server' AND COLUMN_NAME='tag'"
+ run_statement "get new tag column length" "$sql" 64
+
+ mysql_wipe
+
+ test_finish 0
+}
+
+# Verifies that you can upgrade from earlier version and that initial EMPTY DUID
+# (0x00) value in lease6 table is updated to proper value (0x000000).
+mysql_update_empty_duid_test() {
+ test_start "mysql.update_empty_duid_test"
+
+ # Let's wipe the whole database
+ mysql_wipe
+
+ # We need to create an older database with lease data so we can
+ # verify the upgrade mechanisms which convert empty duid values
+ #
+ # Initialize database to schema 1.0.
+ mysql -u"${db_user}" -p"${db_password}" "${db_name}" < "@abs_top_srcdir@/src/bin/admin/tests/dhcpdb_create_1.0.mysql"
+
+ # Now upgrade to schema 16.0
+ mysql_upgrade_schema_to_version 16.0
+
+ sql=\
+"insert into lease6 values('::10',203,30,(SELECT FROM_UNIXTIME(1642000000)),40,50,1,60,70,1,1,'one.example.com',80,90,16,0,NULL);\
+ insert into lease6 values('::11',UNHEX('00'),30,(SELECT FROM_UNIXTIME(1643210000)),40,50,1,60,70,1,1,'',80,90,1,1,'{ }')"
+
+ run_statement "insert v6 leases" "$sql"
+
+ # Let's upgrade it to the latest version.
+ run_command \
+ "${kea_admin}" db-upgrade mysql -u "${db_user}" -p "${db_password}" -n "${db_name}" -d "${db_scripts_dir}"
+
+ # leases count for declined state should be 1 with DUID updated (0x000000)
+ qry="select count(*) from lease6 where address = inet6_aton('::11') and duid = 0x000000 and state = 1"
+ run_statement "#2" "$qry" 1
+
+ # leases count for non declined state should be 1 with DUID unchanged (0x323033)
+ qry="select count(*) from lease6 where address = inet6_aton('::10') and duid = 0x323033 and state = 0"
+ run_statement "#3" "$qry" 1
+
+ # Let's wipe the whole database
+ mysql_wipe
+
+ test_finish 0
+}
+
+# Verifies that converting from lease6.address to binary column works
+# while preserving data.
+mysql_update_v6_addresses_to_binary() {
+ test_start "mysql.update_lease6_address_to_binary"
+
+ # Let's wipe the whole database
+ mysql_wipe
+
+ # We need to create an older database with lease data so we can
+ # verify the upgrade mechanisms which convert empty duid values
+ #
+ # Initialize database to schema 1.0.
+ mysql -u"${db_user}" -p"${db_password}" "${db_name}" < "@abs_top_srcdir@/src/bin/admin/tests/dhcpdb_create_1.0.mysql"
+
+ # Now upgrade to schema 18.0
+ mysql_upgrade_schema_to_version 18.0
+
+ sql=\
+"insert into lease6 (address, lease_type, subnet_id) values('2601:19e:8100:1e10:b1b:51a8:f616:cf14', 1, 1);
+insert into lease6 (address, lease_type, subnet_id) values('2601:19e:8100:1e10:b1b:51a8:f616:cf15', 1, 1);"
+
+ run_statement "insert v6 leases" "$sql"
+
+ # Insert ipv6_reservations address is binary.
+ sql=\
+"insert into hosts(host_id, dhcp_identifier, dhcp_identifier_type) values (18219, '18219', 1); \
+insert into ipv6_reservations (address, prefix_len, type, dhcp6_iaid, host_id) \
+ values ('2601:19e:8100:1e10:b1b:51a8:f616:cf16', 128, 1, 123, 18219);"
+
+ run_statement "insert an ipv6 reservation" "$sql"
+
+ # Let's upgrade it to the latest version.
+ run_command \
+ "${kea_admin}" db-upgrade mysql -u "${db_user}" -p "${db_password}" -n "${db_name}" -d "${db_scripts_dir}"
+
+ # leases count for declined state should be 1 with DUID updated (0x000000)
+ qry="select count(*) from lease6 where address = inet6_aton('2601:19e:8100:1e10:b1b:51a8:f616:cf14');"
+ run_statement "#2" "$qry" 1
+
+ # leases count for non declined state should be 1 with DUID unchanged (0x323033)
+ qry="select count(*) from lease6 where address = inet6_aton('2601:19e:8100:1e10:b1b:51a8:f616:cf15');"
+ run_statement "#3" "$qry" 1
+
+ # verify the reservation is intact
+ qry="select inet6_ntoa(address) from ipv6_reservations where host_id = 18219;"
+ run_statement "ipv6_reservations_insert" "$qry" "2601:19e:8100:1e10:b1b:51a8:f616:cf16"
+
+ # Let's wipe the whole database
+ mysql_wipe
+
+ test_finish 0
+}
+
+# Run tests.
+mysql_db_init_test
+mysql_host_reservation_init_test
+mysql_db_version_test
+mysql_db_version_with_extra_test
+mysql_upgrade_test
+mysql_lease4_dump_test
+mysql_lease4_dump_test -y
+mysql_lease6_dump_test
+mysql_lease6_dump_test -y
+mysql_lease4_upload_test
+mysql_lease4_upload_test -y
+mysql_lease6_upload_test
+mysql_lease6_upload_test -y
+mysql_lease4_stat_test
+mysql_lease6_stat_test
+mysql_lease_stat_upgrade_test
+mysql_lease_stat_recount_test
+mysql_unused_subnet_id_test
+mysql_reservation_mode_upgrade_test
+mysql_client_class_test dhcp4
+mysql_client_class_test dhcp6
+mysql_shrink_server_tag_test
+mysql_update_empty_duid_test
+mysql_update_v6_addresses_to_binary
diff --git a/src/bin/admin/tests/pgsql_tests.sh.in b/src/bin/admin/tests/pgsql_tests.sh.in
new file mode 100644
index 0000000..f9b839e
--- /dev/null
+++ b/src/bin/admin/tests/pgsql_tests.sh.in
@@ -0,0 +1,2068 @@
+#!/bin/sh
+
+# Copyright (C) 2015-2023 Internet Systems Consortium, Inc. ("ISC")
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# shellcheck disable=SC1091
+# SC1091: Not following: ... was not specified as input (see shellcheck -x).
+
+# shellcheck disable=SC2154
+# SC2154: ... is referenced but not assigned.
+# Reason: some variables are sourced.
+
+# Exit with error if commands exit with non-zero and if undefined variables are
+# used.
+set -eu
+
+# Include common test library.
+. "@abs_top_builddir@/src/lib/testutils/dhcp_test_lib.sh"
+
+# Include admin utilities
+. "@abs_top_builddir@/src/bin/admin/admin-utils.sh"
+
+# Set path to the production schema scripts
+db_scripts_dir="@abs_top_srcdir@/src/share/database/scripts"
+
+# Set location of the kea-admin.
+kea_admin="@abs_top_builddir@/src/bin/admin/kea-admin"
+
+# Convenience function for running an SQL statement
+# param hdr - text message to prepend to any error
+# param qry - SQL statement to run
+# param exp_value - optional expected value. This can be used IF the SQL statement
+# generates a single value, such as a SELECT which returns one column for one row.
+# Examples:
+#
+# qry="insert into lease6 (address, lease_type, subnet_id, state) values ($addr,$ltype,1,0)"
+# run_statement "#2" "$qry"
+#
+# qry="select leases from lease6_stat where subnet_id = 1 and lease_type = $ltype and state = 0"
+# run_statement "#3" "$qry" 1
+run_statement() {
+ hdr="$1";shift
+ qry="$1";shift
+ exp_value="${1-}" # Optional value. If not given, replace with empty string.
+
+ # Execute the statement
+ run_command \
+ pgsql_execute "${qry}"
+ value="${OUTPUT}"
+
+ # Execution should succeed
+ assert_eq 0 "${EXIT_CODE}" "$hdr: SQL=[$qry] failed: (expected status code %d, returned %d)"
+
+ # If there's an expected value, test it
+ if [ "x$exp_value" != "x" ]
+ then
+ assert_str_eq "$exp_value" "$value" "$hdr: SQL=[$qry] wrong: (expected value %s, returned %s)"
+ fi
+}
+
+# Wipe all tables from the DB:
+pgsql_wipe() {
+ printf "Wiping whole database %s...\n" "${db_name}"
+ export PGPASSWORD="${db_password}"
+
+ run_command \
+ psql --set ON_ERROR_STOP=1 -A -t -q -U keatest -d keatest -f "${db_scripts_dir}/pgsql/dhcpdb_drop.pgsql"
+ assert_eq 0 "${EXIT_CODE}" "pgsql_wipe drop failed, expected exit code: %d, actual: %d"
+}
+
+pgsql_db_init_test() {
+ test_start "pgsql.db-init"
+
+ # Let's wipe the whole database
+ pgsql_wipe
+
+ # Create the database
+ run_command \
+ "${kea_admin}" db-init pgsql -u "${db_user}" -p "${db_password}" -n "${db_name}" -d "${db_scripts_dir}"
+ assert_eq 0 "${EXIT_CODE}" "kea-admin db-init pgsql failed, expected exit code: %d, actual: %d"
+
+ # Verify that all the expected tables exist
+
+ # Check schema_version table
+ run_command \
+ pgsql_execute "SELECT version, minor FROM schema_version"
+ assert_eq 0 "${EXIT_CODE}" "schema_version table check failed, expected exit code: %d, actual: %d"
+
+ # Check lease4 table
+ run_command \
+ pgsql_execute "SELECT address, hwaddr, client_id, valid_lifetime, expire, subnet_id, fqdn_fwd, fqdn_rev, hostname, state, user_context FROM lease4"
+ assert_eq 0 "${EXIT_CODE}" "lease4 table check failed, expected exit code: %d, actual: %d"
+
+ # Check lease6 table
+ run_command \
+ pgsql_execute "SELECT address, duid, valid_lifetime, expire, subnet_id, pref_lifetime, lease_type, iaid, prefix_len, fqdn_fwd, fqdn_rev, hostname, state, user_context FROM lease6"
+ assert_eq 0 "${EXIT_CODE}" "lease6 table check failed, expected exit code: %d, actual: %d"
+
+ # Check lease6_types table
+ run_command \
+ pgsql_execute "SELECT lease_type, name FROM lease6_types"
+ assert_eq 0 "${EXIT_CODE}" "lease6_types table check failed, expected exit code: %d, actual: %d"
+
+ # Check lease_state table
+ run_command \
+ pgsql_execute "SELECT state, name FROM lease_state"
+ assert_eq 0 "${EXIT_CODE}" "lease_state table check failed, expected exit code: %d, actual: %d"
+
+ # Trying to create it again should fail. This verifies the db present
+ # check
+ printf '\nDB created successfully, make sure we are not allowed to try it again:\n'
+ run_command \
+ "${kea_admin}" db-init pgsql -u "${db_user}" -p "${db_password}" -n "${db_name}" -d "${db_scripts_dir}"
+ assert_eq 2 "${EXIT_CODE}" "kea-admin failed to deny db-init, expected exit code: %d, actual: %d"
+
+ # Let's wipe the whole database
+ pgsql_wipe
+
+ test_finish 0
+}
+
+pgsql_db_version_test() {
+ test_start "pgsql.db-version"
+
+ # Wipe the whole database
+ pgsql_wipe
+
+ # Do not create any table so db-version will raise an error
+ printf 'Checking db-version error case...\n'
+ run_command \
+ "${kea_admin}" db-version pgsql -u "${db_user}" -p "${db_password}" -n "${db_name}"
+ assert_eq 3 "${EXIT_CODE}" "schema_version table still exists. (expected %d, exit code %d)"
+
+ # Create the database
+ run_command \
+ "${kea_admin}" db-init pgsql -u "${db_user}" -p "${db_password}" -n "${db_name}" -d "${db_scripts_dir}"
+ assert_eq 0 "${EXIT_CODE}" "cannot initialize the database, expected exit code: %d, actual: %d"
+
+ # Verify that kea-admin db-version returns the latest version.
+ run_command \
+ "${kea_admin}" db-version pgsql -u "${db_user}" -p "${db_password}" -n "${db_name}"
+ version="${OUTPUT}"
+ assert_str_eq "18.0" "${version}" "Expected kea-admin to return %s, returned value was %s"
+
+ # Let's wipe the whole database
+ pgsql_wipe
+
+ test_finish 0
+}
+
+pgsql_upgrade_1_0_to_2_0_test() {
+ # Added state column to lease4
+ run_command \
+ pgsql_execute "select state from lease4"
+ assert_eq 0 "${EXIT_CODE}" "lease4 is missing state column. (expected status code %d, returned %d)"
+
+ # Added state column to lease6
+ run_command \
+ pgsql_execute "select state from lease6"
+ assert_eq 0 "${EXIT_CODE}" "lease6 is missing state column. (expected status code %d, returned %d)"
+
+ # Added stored procedures for lease dumps
+ run_command \
+ pgsql_execute "select lease4DumpHeader from lease4DumpHeader()"
+ assert_eq 0 "${EXIT_CODE}" "function lease4DumpHeader() broken or missing. (expected status code %d, returned %d)"
+
+ run_command \
+ pgsql_execute "select address from lease4DumpData()"
+ assert_eq 0 "${EXIT_CODE}" "function lease4DumpData() broken or missing. (expected status code %d, returned %d)"
+
+ run_command \
+ pgsql_execute "select lease6DumpHeader from lease6DumpHeader()"
+ assert_eq 0 "${EXIT_CODE}" "function lease6DumpHeader() broken or missing. (expected status code %d, returned %d)"
+
+ run_command \
+ pgsql_execute "select address from lease6DumpData()"
+ assert_eq 0 "${EXIT_CODE}" "function lease6DumpData() broken or missing. (expected status code %d, returned %d)"
+}
+
+pgsql_upgrade_2_0_to_3_0_test() {
+ # Added hwaddr, hwtype, and hwaddr_source columns to lease6 table
+ run_command \
+ pgsql_execute "select hwaddr, hwtype, hwaddr_source from lease6"
+ assert_eq 0 "${EXIT_CODE}" "lease6 table not upgraded to 3.0 (expected status code %d, returned %d)"
+
+ # Added lease_hwaddr_source table
+ run_command \
+ pgsql_execute "select hwaddr_source, name from lease_hwaddr_source"
+ assert_eq 0 "${EXIT_CODE}" "lease_hwaddr_source table is missing or broken. (expected status code %d, returned %d)"
+
+ # Added hosts table
+ run_command \
+ pgsql_execute "select host_id, dhcp_identifier, dhcp_identifier_type, dhcp4_subnet_id, dhcp6_subnet_id, ipv4_address, hostname, dhcp4_client_classes, dhcp6_client_classes, dhcp4_next_server, dhcp4_server_hostname, dhcp4_boot_file_name, auth_key from hosts"
+ assert_eq 0 "${EXIT_CODE}" "hosts table is missing or broken. (expected status code %d, returned %d)"
+
+ # Added ipv6_reservations table
+ run_command \
+ pgsql_execute "select reservation_id, address, prefix_len, type, dhcp6_iaid, host_id from ipv6_reservations"
+ assert_eq 0 "${EXIT_CODE}" "ipv6_reservations table is missing or broken. (expected status code %d, returned %d)"
+
+ # Added dhcp4_options table
+ run_command \
+ pgsql_execute "select option_id, code, value, formatted_value, space, persistent, dhcp_client_class, dhcp4_subnet_id, host_id, scope_id from dhcp4_options"
+ assert_eq 0 "${EXIT_CODE}" "dhcp4_options table is missing or broken. (expected status code %d, returned %d)"
+
+ # Added dhcp6_options table
+ run_command \
+ pgsql_execute "select option_id, code, value, formatted_value, space, persistent, dhcp_client_class, dhcp6_subnet_id, host_id,scope_id from dhcp6_options"
+ assert_eq 0 "${EXIT_CODE}" "dhcp6_options table is missing or broken. (expected status code %d, returned %d)"
+
+ # Added host_identifier_type table
+ run_command \
+ pgsql_execute "select type, name from host_identifier_type"
+ assert_eq 0 "${EXIT_CODE}" "host_identifier_type table is missing or broken. (expected status code %d, returned %d)"
+
+ # Added dhcp_option_scope table
+ run_command \
+ pgsql_execute "select scope_id, scope_name from dhcp_option_scope"
+ assert_eq 0 "${EXIT_CODE}" "dhcp_option_scope table is missing or broken. (expected status code %d, returned %d)"
+
+ # Added dhcp6_options table
+ run_command \
+ pgsql_execute "select option_id, code, value, formatted_value, space, persistent, dhcp_client_class, dhcp6_subnet_id, host_id,scope_id from dhcp6_options"
+ assert_eq 0 "${EXIT_CODE}" "dhcp6_options table is missing or broken. (expected status code %d, returned %d)"
+
+ # Added order by clause to lease4DumpData
+ run_command \
+ pgsql_execute "select address from lease4DumpData()"
+ assert_eq 0 "${EXIT_CODE}" "function lease4DumpData() broken or missing. (expected status code %d, returned %d)"
+ run_command \
+ pgsql_execute "\sf lease4DumpData"
+ assert_eq 0 "${EXIT_CODE}" "\sf of lease4DumpData failed. (expected status code %d, returned %d)"
+ count=$(echo "${OUTPUT}" | grep -Eci 'order by [a-z]*[\.]?address') || true
+ assert_eq 1 "${count}" "lease4DumpData is missing order by clause. (expected count %d, returned %d)"
+
+ # Added hwaddr columns to lease6DumpHeader
+ run_command \
+ pgsql_execute "select lease6DumpHeader from lease6DumpHeader()"
+ assert_eq 0 "${EXIT_CODE}" "function lease6DumpHeader() broken or missing. (expected status code %d, returned %d)"
+ count=$(echo "${OUTPUT}" | grep -Fci 'hwaddr') || true
+ assert_eq 1 "${count}" "lease6DumpHeader is missing the hwaddr column"
+ count=$(echo "${OUTPUT}" | grep -Fci 'hwtype') || true
+ assert_eq 1 "${count}" "lease6DumpHeader is missing the hwtype column"
+ count=$(echo "${OUTPUT}" | grep -Fci 'hwaddr_source') || true
+ assert_eq 1 "${count}" "lease6DumpHeader is missing the hwaddr_source column"
+
+ # Added hwaddr columns to lease6DumpData
+ run_command \
+ pgsql_execute "select hwaddr,hwtype,hwaddr_source from lease6DumpData()"
+ assert_eq 0 "${EXIT_CODE}" "function lease6DumpData() broken or missing. (expected status code %d, returned %d)"
+
+ # Added order by clause to lease6DumpData
+ run_command \
+ pgsql_execute "\sf lease6DumpData"
+ assert_eq 0 "${EXIT_CODE}" "\sf of lease6DumpData failed. (expected status code %d, returned %d)"
+ count=$(echo "${OUTPUT}" | grep -Eci 'order by [a-z]*[\.]?address') || true
+ assert_eq 1 "${count}" "lease6DumpData is missing order by clause. (expected count %d, returned %d)"
+
+ # lease_hardware_source should have row for source = 0
+ run_command \
+ pgsql_execute "select count(hwaddr_source) from lease_hwaddr_source where hwaddr_source = 0 and name='HWADDR_SOURCE_UNKNOWN'"
+ assert_eq 0 "${EXIT_CODE}" "select from lease_hwaddr_source failed. (expected status code %d, returned %d)"
+ assert_eq 1 "${OUTPUT}" "lease_hwaddr_source does not contain entry for HWADDR_SOURCE_UNKNOWN. (record count %d, expected %d)"
+}
+
+pgsql_upgrade_3_0_to_6_1_test() {
+ # Added user_context to lease4
+ run_command \
+ pgsql_execute "select user_context from lease4"
+ assert_eq 0 "${EXIT_CODE}" "lease4 is missing user_context column. (expected status code %d, returned %d)"
+
+ # Added user_context to lease6
+ run_command \
+ pgsql_execute "select user_context from lease6"
+ assert_eq 0 "${EXIT_CODE}" "lease6 is missing user_context column. (expected status code %d, returned %d)"
+
+ # Added logs table
+ run_command \
+ pgsql_execute "select timestamp, address, log from logs"
+ assert_eq 0 "${EXIT_CODE}" "logs table is missing or broken. (expected status code %d, returned %d)"
+}
+
+pgsql_upgrade_6_1_to_6_2_test() {
+ insert_sql="\
+insert into hosts(dhcp_identifier, dhcp_identifier_type, dhcp4_subnet_id, ipv4_address) values (decode('010101010101', 'hex'), 0, 1, x'FFAF0002'::int);\
+insert into hosts(dhcp_identifier, dhcp_identifier_type, dhcp4_subnet_id, ipv4_address) values (decode('010101010102', 'hex'), 0, 1, x'FFAF0002'::int)"
+ run_command \
+ pgsql_execute "$insert_sql"
+ assert_eq 0 "${EXIT_CODE}" "insert into hosts failed, expected exit code %d, actual %d"
+}
+
+pgsql_upgrade_6_2_to_7_0_test() {
+ # dhcp4_server should have a single entry for 'all'
+ select_sql="SELECT id, tag, description, modification_ts from dhcp4_server where id = 1 and tag = 'all'"
+ run_command \
+ pgsql_execute "$select_sql"
+ assert_eq 0 "${EXIT_CODE}" "the dhcp4_server table is broken or missing. (expected status code %d, returned %d)"
+
+ # dhcp6_server should have a single entry for 'all'
+ select_sql="SELECT id, tag, description, modification_ts from dhcp6_server where id = 1 and tag = 'all'"
+ run_command \
+ pgsql_execute "$select_sql"
+ assert_eq 0 "${EXIT_CODE}" "the dhcp6_server table is broken or missing. (expected status code %d, returned %d)"
+
+ # Verify that session variable setting is present and functional.
+ session_sql="\
+select get_session_value('kea.text'); \
+select set_session_value('kea.text', 'booya'); \
+select get_session_value('kea.text'); \
+select get_session_boolean('kea.bool'); \
+select set_session_value('kea.bool', true); \
+select get_session_boolean('kea.bool'); \
+select get_session_big_int('kea.bigint'); \
+select set_session_value('kea.bigint', cast('1984' as BIGINT)); \
+select get_session_big_int('kea.bigint'); \
+"
+ run_command \
+ pgsql_execute "$session_sql"
+ assert_eq 0 "${EXIT_CODE}" "session variable handling broken. (expected status code %d, returned %d)"
+ clean_out=$(echo "${OUTPUT}" | tr '\n' ' ')
+ assert_str_eq " booya f t 0 1984 " "${clean_out}" "session variable output incorrect"
+}
+
+pgsql_upgrade_7_0_to_8_0_test() {
+ run_command \
+ pgsql_execute "$session_sql"
+
+ # Added class_id to dhcp4_option_def
+ run_command \
+ pgsql_execute "select class_id from dhcp4_option_def"
+ assert_eq 0 "${EXIT_CODE}" "dhcp4_option_def is missing class_id column. (expected status code %d, returned %d)"
+
+ # Added class_id to dhcp6_option_def
+ run_command \
+ pgsql_execute "select class_id from dhcp6_option_def"
+ assert_eq 0 "${EXIT_CODE}" "dhcp6_option_def is missing class_id column. (expected status code %d, returned %d)"
+
+ # Added preferred lifetime columns to dhcp6_client_class.
+ run_command \
+ pgsql_execute "select preferred_lifetime, min_preferred_lifetime, max_preferred_lifetime from dhcp6_client_class"
+ assert_eq 0 "${EXIT_CODE}" "dhcp6_client_class is missing preferred lifetime column(s). (expected status code %d, returned %d)"
+
+ # Check the output of colonSeparatedHex().
+ run_command \
+ pgsql_execute "SELECT colonSeparatedHex('f123456789')"
+ assert_eq 0 "${EXIT_CODE}" 'colonSeparatedHex() failed, expected exit code %d, actual %d'
+ assert_str_eq 'f1:23:45:67:89' "${OUTPUT}"
+
+ run_command \
+ pgsql_execute "SELECT colonSeparatedHex('')"
+ assert_eq 0 "${EXIT_CODE}" 'colonSeparatedHex() failed, expected exit code %d, actual %d'
+ assert_str_eq '' "${OUTPUT}"
+
+ run_command \
+ pgsql_execute "SELECT colonSeparatedHex('f')"
+ assert_eq 0 "${EXIT_CODE}" 'colonSeparatedHex() failed, expected exit code %d, actual %d'
+ assert_str_eq '0f' "${OUTPUT}"
+
+ run_command \
+ pgsql_execute "SELECT colonSeparatedHex('f1')"
+ assert_eq 0 "${EXIT_CODE}" 'colonSeparatedHex() failed, expected exit code %d, actual %d'
+ assert_str_eq 'f1' "${OUTPUT}"
+
+ run_command \
+ pgsql_execute "SELECT colonSeparatedHex('f12')"
+ assert_eq 0 "${EXIT_CODE}" 'colonSeparatedHex() failed, expected exit code %d, actual %d'
+ assert_str_eq '0f:12' "${OUTPUT}"
+
+ # Check lease4Dump*().
+ run_command \
+ pgsql_execute "INSERT INTO lease4 VALUES(10,E'\\\\x3230',E'\\\\x3330',40,TO_TIMESTAMP(1678900000),50,'t','t','one,example,com',0,'{ \"a\": 1, \"b\": 2 }')"
+ assert_eq 0 "${EXIT_CODE}" 'INSERT INTO lease4 failed, expected exit code %d, actual %d'
+ assert_str_eq '' "${OUTPUT}"
+
+ run_command \
+ pgsql_execute "SELECT * FROM lease4DumpHeader()"
+ assert_eq 0 "${EXIT_CODE}" 'lease4DumpHeader() failed, expected exit code %d, actual %d'
+ assert_str_eq 'address,hwaddr,client_id,valid_lifetime,expire,subnet_id,fqdn_fwd,fqdn_rev,hostname,state,user_context,pool_id' "${OUTPUT}"
+
+ run_command \
+ pgsql_execute "SELECT * FROM lease4DumpData()" --field-separator=','
+ assert_eq 0 "${EXIT_CODE}" 'lease4DumpData() failed, expected exit code %d, actual %d'
+ assert_str_eq '0.0.0.10,32:30,33:30,40,1678900000,50,1,1,one&#x2cexample&#x2ccom,0,{ "a": 1&#x2c "b": 2 },0' "${OUTPUT}"
+
+ # Check lease6Dump*().
+ run_command \
+ pgsql_execute "INSERT INTO lease6 VALUES(cast('::10' as inet),E'\\\\x3230',30,TO_TIMESTAMP(1678900000),40,50,1,60,70,'t','t','one,example,com',0,E'\\\\x3830',16,0,'{ \"a\": 1, \"b\": 2 }',0)"
+ assert_eq 0 "${EXIT_CODE}" 'INSERT INTO lease6 failed, expected exit code %d, actual %d'
+ assert_str_eq '' "${OUTPUT}"
+
+ run_command \
+ pgsql_execute "SELECT * FROM lease6DumpHeader()"
+ assert_eq 0 "${EXIT_CODE}" 'lease6DumpHeader() failed, expected exit code %d, actual %d'
+ assert_str_eq 'address,duid,valid_lifetime,expire,subnet_id,pref_lifetime,lease_type,iaid,prefix_len,fqdn_fwd,fqdn_rev,hostname,hwaddr,state,user_context,hwtype,hwaddr_source,pool_id' "${OUTPUT}"
+
+ run_command \
+ pgsql_execute "SELECT * FROM lease6DumpData()" --field-separator=','
+ assert_eq 0 "${EXIT_CODE}" 'lease6DumpData() failed, expected exit code %d, actual %d'
+ assert_str_eq '::10,32:30,30,1678900000,40,50,1,60,70,1,1,one&#x2cexample&#x2ccom,38:30,0,{ "a": 1&#x2c "b": 2 },16,0,0' "${OUTPUT}"
+
+ # Check lease4Upload().
+ run_command \
+ pgsql_execute "SELECT lease4Upload('192.0.0.0','ff0102030405','01ff0102030405',7200,1234567890,1,0,0,'',0,'',0)"
+ assert_eq 0 "${EXIT_CODE}" 'lease4Upload() failed, expected exit code %d, actual %d'
+ assert_str_eq '' "${OUTPUT}"
+
+ # Check lease6Upload().
+ run_command \
+ pgsql_execute "SELECT lease6Upload('2001:db8::','000100012955cb80ff0102030407',7200,1234567890,1,3600,0,1,128,0,0,'','ff0102030407',0,'',90,16,0)"
+ assert_eq 0 "${EXIT_CODE}" 'lease6Upload() failed, expected exit code %d, actual %d'
+ assert_str_eq '' "${OUTPUT}"
+}
+
+pgsql_upgrade_8_0_to_9_0_test() {
+ run_command \
+ pgsql_execute "$session_sql"
+
+ # Most changes are not readily testable without querying the information schema,
+ # not sure the effort is worthwhile. Verify that function gmt_epoch() was created.
+ run_command \
+ pgsql_execute "select gmt_epoch(now())"
+ assert_eq 0 "${EXIT_CODE}" "function gmt_epoch() broken or missing. (expected status code %d, returned %d)"
+}
+
+pgsql_upgrade_9_0_to_10_test() {
+ run_command \
+ pgsql_execute "$session_sql"
+
+ # Get function source code so we can check that it returns NEW.
+ # Function name must be lower case for WHERE clause.
+ run_command \
+ pgsql_execute "select proname,prosrc from pg_proc where proname='func_dhcp6_client_class_check_dependency_bins'"
+ assert_eq 0 "${EXIT_CODE}" "function func_dhcp6_client_class_check_dependency_BINS() broken or missing. (expected status code %d, returned %d)"
+
+ count=$(echo "${OUTPUT}" | grep -Eci 'RETURN NEW') || true
+ assert_eq 1 "${count}" "func_dhcp6_client_class_check_dependency_BINS is missing RETURN NEW. (expected count %d, returned %d)"
+}
+
+pgsql_upgrade_10_to_11_test() {
+ run_command \
+ pgsql_execute "$session_sql"
+
+ # Get function source code so we can check that it returns NEW.
+ # Function name must be lower case for WHERE clause.
+ run_command \
+ pgsql_execute "select proname,prosrc from pg_proc where proname='createoptionauditdhcp6'"
+ assert_eq 0 "${EXIT_CODE}" "function createOptionAuditDHCP6() broken or missing. (expected status code %d, returned %d)"
+
+ count=$(echo "${OUTPUT}" | grep -Eci 'SELECT dhcp6_pd_pool.subnet_id INTO sid FROM dhcp6_pd_pool WHERE id = pd_pool_id') || true
+ assert_eq 1 "${count}" "function createOptionAuditDHCP6() is missing changed line. (expected count %d, returned %d)"
+}
+
+pgsql_upgrade_11_to_12_test() {
+ run_command \
+ pgsql_execute "$session_sql"
+
+ # Check function source code
+ run_command \
+ pgsql_execute "select proname,prosrc from pg_proc where proname='func_dhcp4_shared_network_bdel'"
+ assert_eq 0 "${EXIT_CODE}" "function func_dhcp4_shared_network_BDEL() broken or missing. (expected status code %d, returned %d)"
+
+ count=$(echo "${OUTPUT}" | grep -Eci 'UPDATE dhcp4_subnet SET shared_network_name = NULL') || true
+ assert_eq 1 "${count}" "function func_dhcp4_shared_network_BDEL() is missing changed line. (expected count %d, returned %d)"
+
+ # Check function source code
+ run_command \
+ pgsql_execute "select proname,prosrc from pg_proc where proname='func_dhcp6_shared_network_bdel'"
+ assert_eq 0 "${EXIT_CODE}" "function func_dhcp6_shared_network_BDEL() broken or missing. (expected status code %d, returned %d)"
+
+ count=$(echo "${OUTPUT}" | grep -Eci 'UPDATE dhcp6_subnet SET shared_network_name = NULL') || true
+ assert_eq 1 "${count}" "function func_dhcp6_shared_network_BDEL() is missing changed line. (expected count %d, returned %d)"
+
+ # user_context should have been added to dhcp4_client_class
+ qry="select user_context from dhcp4_client_class limit 1"
+ run_command \
+ pgsql_execute "${qry}"
+ assert_eq 0 "${EXIT_CODE}" "${qry}. (expected status code %d, returned %d)"
+
+ # user_context should have been added to dhcp6_client_class
+ qry="select user_context from dhcp6_client_class limit 1"
+ run_command \
+ pgsql_execute "${qry}"
+ assert_eq 0 "${EXIT_CODE}" "${qry}. (expected status code %d, returned %d)"
+}
+
+pgsql_upgrade_12_to_13_test() {
+ # -- lease counting tests --
+
+ # Clean up.
+ query="DELETE FROM lease4; DELETE FROM lease6"
+ run_command \
+ pgsql_execute "${query}"
+ assert_eq 0 "${EXIT_CODE}" "${query}: expected %d, returned %d"
+ assert_str_eq '' "${OUTPUT}" "${query}: expected output %s, returned %s"
+
+ # Populate the lease tables. Also check that @json_supported is NULL at
+ # first, and then it is set after inserting leases.
+ run_command \
+ pgsql_execute "
+ INSERT INTO lease4 (address, subnet_id, state, user_context) VALUES (100,1,0,
+ '{\"ISC\": {\"client-classes\": [\"ALL\", \"KNOWN\", \"bar\", \"foo\"] } }');
+ INSERT INTO lease4 (address, subnet_id, state, user_context) VALUES (101,1,1,
+ '{\"ISC\": {\"client-classes\": [\"ALL\", \"KNOWN\", \"bar\", \"foo\"] } }');
+ INSERT INTO lease4 (address, subnet_id, state, user_context) VALUES (102,1,2,
+ '{\"ISC\": {\"client-classes\": [\"ALL\", \"KNOWN\", \"bar\", \"foo\"] } }');
+ INSERT INTO lease4 (address, subnet_id, state, user_context) VALUES (103,1,0,
+ '{\"ISC\": {\"client-classes\": [\"ALL\", \"KNOWN\", \"bar\", \"foo\"] } }');
+ INSERT INTO lease4 (address, subnet_id, state, user_context) VALUES (104,1,1,
+ '{\"ISC\": {\"client-classes\": [\"ALL\", \"KNOWN\", \"bar\", \"foo\"] } }');
+ INSERT INTO lease4 (address, subnet_id, state, user_context) VALUES (105,1,1,
+ '{\"ISC\": {\"client-classes\": [\"ALL\", \"KNOWN\", \"bar\", \"foo\"] } }');
+ INSERT INTO lease6 (address, lease_type, subnet_id, state, user_context) VALUES (cast('::10' as inet),0,1,0,
+ '{\"ISC\": {\"client-classes\": [\"ALL\", \"KNOWN\", \"bar\", \"foo\"] } }');
+ INSERT INTO lease6 (address, lease_type, subnet_id, state, user_context) VALUES (cast('::11' as inet),0,1,1,
+ '{\"ISC\": {\"client-classes\": [\"ALL\", \"KNOWN\", \"bar\", \"foo\"] } }');
+ INSERT INTO lease6 (address, lease_type, subnet_id, state, user_context) VALUES (cast('::12' as inet),0,1,0,
+ '{\"ISC\": {\"client-classes\": [\"ALL\", \"KNOWN\", \"bar\", \"foo\"] } }');
+ INSERT INTO lease6 (address, lease_type, subnet_id, state, user_context) VALUES (cast('::13' as inet),0,1,1,
+ '{\"ISC\": {\"client-classes\": [\"ALL\", \"KNOWN\", \"bar\", \"foo\"] } }');
+ INSERT INTO lease6 (address, lease_type, subnet_id, state, user_context) VALUES (cast('::14' as inet),2,1,0,
+ '{\"ISC\": {\"client-classes\": [\"ALL\", \"KNOWN\", \"bar\", \"foo\"] } }');
+ INSERT INTO lease6 (address, lease_type, subnet_id, state, user_context) VALUES (cast('::15' as inet),2,1,1,
+ '{\"ISC\": {\"client-classes\": [\"ALL\", \"KNOWN\", \"bar\", \"foo\"] } }');
+ INSERT INTO lease6 (address, lease_type, subnet_id, state, user_context) VALUES (cast('::16' as inet),2,1,0,
+ '{\"ISC\": {\"client-classes\": [\"ALL\", \"KNOWN\", \"bar\", \"foo\"] } }');
+ INSERT INTO lease6 (address, lease_type, subnet_id, state, user_context) VALUES (cast('::17' as inet),2,1,1,
+ '{\"ISC\": {\"client-classes\": [\"ALL\", \"KNOWN\", \"bar\", \"foo\"] } }');
+ "
+ assert_eq 0 "${EXIT_CODE}" 'INSERT INTO leases when upgrading from 11 to 12 failed. expected %d, returned %d'
+ assert_str_eq '' "${OUTPUT}" "INSERT INTO leases when upgrading from 11 to 12 failed. expected output %s, returned %s"
+
+ # Check that @json_supported is NULL by default.
+ query="SELECT isJsonSupported()"
+ run_command \
+ pgsql_execute "${query}"
+ assert_eq 0 "${EXIT_CODE}" "${query}: expected %d, returned %d"
+ json_supported="${OUTPUT}"
+ if test "${json_supported}" != 'f' && test "${json_supported}" != 't'; then
+ assert_str_eq '[ft]' "${json_supported}" "${query}. expected '[ft]', returned '${json_supported}'"
+ fi
+
+ for v in 4 6; do
+ # Check that client classes were counted correctly.
+ query="SELECT leases FROM lease${v}_stat_by_client_class WHERE client_class = 'foo' LIMIT 1"
+ run_command \
+ pgsql_execute "${query}"
+ assert_eq 0 "${EXIT_CODE}" "${query}: expected %d, returned %d"
+ if test "${json_supported}" = 't'; then
+ assert_str_eq 2 "${OUTPUT}" "${query}: expected output %s, returned %s"
+ else
+ assert_str_eq '' "${OUTPUT}" "${query}: expected output %s, returned %s"
+ fi
+
+ # -- Verify some calls to checkLeaseXLimits(). --
+
+ query="SELECT checkLease${v}Limits('')"
+ run_command \
+ pgsql_execute "${query}"
+ if test "${json_supported}" = 't'; then
+ assert_eq 0 "${EXIT_CODE}" "${query}: expected %d, returned %d"
+ else
+ # Should fail with ERROR: operator does not exist: json -> unknown
+ assert_eq 3 "${EXIT_CODE}" "${query}: expected %d, returned %d"
+ fi
+ assert_str_eq '' "${OUTPUT}" "${query}: expected output %s, returned %s"
+
+ query="SELECT checkLease${v}Limits('{}')"
+ run_command \
+ pgsql_execute "${query}"
+ if test "${json_supported}" = 't'; then
+ assert_eq 0 "${EXIT_CODE}" "${query}: expected %d, returned %d"
+ else
+ # Should fail with ERROR: operator does not exist: json -> unknown
+ assert_eq 3 "${EXIT_CODE}" "${query}: expected %d, returned %d"
+ fi
+ assert_str_eq '' "${OUTPUT}" "${query}: expected output %s, returned %s"
+
+ query="SELECT checkLease${v}Limits('{ \"ISC\": { \"limits\": { \"client-classes\": [ { \"name\": \"foo\", \"address-limit\": 1 } ] } } }')"
+ run_command \
+ pgsql_execute "${query}"
+ if test "${json_supported}" = 't'; then
+ assert_eq 0 "${EXIT_CODE}" "${query}: expected %d, returned %d"
+ assert_str_eq "address limit 1 for client class \"foo\", current lease count 2" "${OUTPUT}" "${query}: expected output %s, returned %s"
+ else
+ # Should fail with ERROR: operator does not exist: json -> unknown
+ assert_eq 3 "${EXIT_CODE}" "${query}: expected %d, returned %d"
+ assert_str_eq '' "${OUTPUT}" "${query}: expected output %s, returned %s"
+ fi
+
+ query="SELECT checkLease${v}Limits('{ \"ISC\": { \"limits\": { \"subnet\": { \"id\": 1, \"address-limit\": 1 } } } }')"
+ run_command \
+ pgsql_execute "${query}"
+ if test "${json_supported}" = 't'; then
+ assert_eq 0 "${EXIT_CODE}" "${query}: expected %d, returned %d"
+ assert_str_eq "address limit 1 for subnet ID 1, current lease count 2" "${OUTPUT}" "${query}: expected output %s, returned %s"
+ else
+ # Should fail with ERROR: operator does not exist: json -> unknown
+ assert_eq 3 "${EXIT_CODE}" "${query}: expected %d, returned %d"
+ assert_str_eq '' "${OUTPUT}" "${query}: expected output %s, returned %s"
+ fi
+
+ query="SELECT checkLease${v}Limits('{ \"ISC\": { \"limits\": { \"client-classes\": [ { \"name\": \"foo\", \"address-limit\": 2 } ] } } }')"
+ run_command \
+ pgsql_execute "${query}"
+ if test "${json_supported}" = 't'; then
+ assert_eq 0 "${EXIT_CODE}" "${query}: expected %d, returned %d"
+ assert_str_eq "address limit 2 for client class \"foo\", current lease count 2" "${OUTPUT}" "${query}: expected output %s, returned %s"
+ else
+ # Should fail with ERROR: operator does not exist: json -> unknown
+ assert_eq 3 "${EXIT_CODE}" "${query}: expected %d, returned %d"
+ assert_str_eq '' "${OUTPUT}" "${query}: expected output %s, returned %s"
+ fi
+
+ query="SELECT checkLease${v}Limits('{ \"ISC\": { \"limits\": { \"subnet\": { \"id\": 1, \"address-limit\": 2 } } } }')"
+ run_command \
+ pgsql_execute "${query}"
+ if test "${json_supported}" = 't'; then
+ assert_eq 0 "${EXIT_CODE}" "${query}: expected %d, returned %d"
+ assert_str_eq "address limit 2 for subnet ID 1, current lease count 2" "${OUTPUT}" "${query}: expected output %s, returned %s"
+ else
+ # Should fail with ERROR: operator does not exist: json -> unknown
+ assert_eq 3 "${EXIT_CODE}" "${query}: expected %d, returned %d"
+ assert_str_eq '' "${OUTPUT}" "${query}: expected output %s, returned %s"
+ fi
+
+ query="SELECT checkLease${v}Limits('{ \"ISC\": { \"limits\": { \"client-classes\": [ { \"name\": \"foo\", \"address-limit\": 4 } ] } } }')"
+ run_command \
+ pgsql_execute "${query}"
+ if test "${json_supported}" = 't'; then
+ assert_eq 0 "${EXIT_CODE}" "${query}: expected %d, returned %d"
+ else
+ # Should fail with ERROR: operator does not exist: json -> unknown
+ assert_eq 3 "${EXIT_CODE}" "${query}: expected %d, returned %d"
+ fi
+ assert_str_eq '' "${OUTPUT}" "${query}: expected output %s, returned %s"
+
+ query="SELECT checkLease${v}Limits('{ \"ISC\": { \"limits\": { \"subnet\": { \"id\": 1, \"address-limit\": 4 } } } }')"
+ run_command \
+ pgsql_execute "${query}"
+ if test "${json_supported}" = 't'; then
+ assert_eq 0 "${EXIT_CODE}" "${query}: expected %d, returned %d"
+ else
+ # Should fail with ERROR: operator does not exist: json -> unknown
+ assert_eq 3 "${EXIT_CODE}" "${query}: expected %d, returned %d"
+ fi
+ assert_str_eq '' "${OUTPUT}" "${query}: expected output %s, returned %s"
+
+ query="SELECT checkLease${v}Limits('{ \"ISC\": { \"limits\": { \"client-classes\": [ { \"name\": \"foo\", \"address-limit\": 1 }, { \"name\": \"bar\", \"address-limit\": 1 } ], \"subnet\": { \"id\": 1, \"address-limit\": 1 } } } }')"
+ run_command \
+ pgsql_execute "${query}"
+ if test "${json_supported}" = 't'; then
+ assert_eq 0 "${EXIT_CODE}" "${query}: expected %d, returned %d"
+ assert_str_eq "address limit 1 for client class \"foo\", current lease count 2" "${OUTPUT}" "${query}: expected output %s, returned %s"
+ else
+ # Should fail with ERROR: operator does not exist: json -> unknown
+ assert_eq 3 "${EXIT_CODE}" "${query}: expected %d, returned %d"
+ assert_str_eq '' "${OUTPUT}" "${query}: expected output %s, returned %s"
+ fi
+
+ query="SELECT checkLease${v}Limits('{ \"ISC\": { \"limits\": { \"client-classes\": [ { \"name\": \"foo\", \"address-limit\": 2 }, { \"name\": \"bar\", \"address-limit\": 4 } ], \"subnet\": { \"id\": 1, \"address-limit\": 4 } } } }')"
+ run_command \
+ pgsql_execute "${query}"
+ if test "${json_supported}" = 't'; then
+ assert_eq 0 "${EXIT_CODE}" "${query}: expected %d, returned %d"
+ assert_str_eq "address limit 2 for client class \"foo\", current lease count 2" "${OUTPUT}" "${query}: expected output %s, returned %s"
+ else
+ # Should fail with ERROR: operator does not exist: json -> unknown
+ assert_eq 3 "${EXIT_CODE}" "${query}: expected %d, returned %d"
+ assert_str_eq '' "${OUTPUT}" "${query}: expected output %s, returned %s"
+ fi
+
+ query="SELECT checkLease${v}Limits('{ \"ISC\": { \"limits\": { \"client-classes\": [ { \"name\": \"foo\", \"address-limit\": 4 }, { \"name\": \"bar\", \"address-limit\": 4 } ], \"subnet\": { \"id\": 1, \"address-limit\": 2 } } } }')"
+ run_command \
+ pgsql_execute "${query}"
+ if test "${json_supported}" = 't'; then
+ assert_eq 0 "${EXIT_CODE}" "${query}: expected %d, returned %d"
+ assert_str_eq "address limit 2 for subnet ID 1, current lease count 2" "${OUTPUT}" "${query}: expected output %s, returned %s"
+ else
+ # Should fail with ERROR: operator does not exist: json -> unknown
+ assert_eq 3 "${EXIT_CODE}" "${query}: expected %d, returned %d"
+ assert_str_eq '' "${OUTPUT}" "${query}: expected output %s, returned %s"
+ fi
+
+ query="SELECT checkLease${v}Limits('{ \"ISC\": { \"limits\": { \"client-classes\": [ { \"name\": \"foo\", \"address-limit\": 4 }, { \"name\": \"bar\", \"address-limit\": 4 } ], \"subnet\": { \"id\": 1, \"address-limit\": 4 } } } }')"
+ run_command \
+ pgsql_execute "${query}"
+ if test "${json_supported}" = 't'; then
+ assert_eq 0 "${EXIT_CODE}" "${query}: expected %d, returned %d"
+ else
+ # Should fail with ERROR: operator does not exist: json -> unknown
+ assert_eq 3 "${EXIT_CODE}" "${query}: expected %d, returned %d"
+ fi
+ assert_str_eq '' "${OUTPUT}" "${query}: expected output %s, returned %s"
+ done
+
+ # Check that leases counters cannot go negative.
+ for v in 4 6; do
+ query="SELECT leases FROM lease${v}_stat WHERE subnet_id = 1 AND state = 0 LIMIT 1"
+ run_command \
+ pgsql_execute "${query}"
+ assert_eq 0 "${EXIT_CODE}" "${query}: expected %d, returned %d"
+ assert_str_eq '2' "${OUTPUT}" "${query}: expected output %s, returned %s"
+
+ # Artificially change the subnet counter from 2 down to 1.
+ query="UPDATE lease${v}_stat SET leases = 1 WHERE subnet_id = 1 AND state = 0"
+ run_command \
+ pgsql_execute "${query}"
+ assert_eq 0 "${EXIT_CODE}" "${query}: expected %d, returned %d"
+ assert_str_eq '' "${OUTPUT}" "${query}: expected output %s, returned %s"
+
+ if test "${json_supported}" = 't'; then
+ query="SELECT leases FROM lease${v}_stat_by_client_class WHERE client_class = 'foo' LIMIT 1"
+ run_command \
+ pgsql_execute "${query}"
+ assert_eq 0 "${EXIT_CODE}" "${query}: expected %d, returned %d"
+ assert_str_eq '2' "${OUTPUT}" "${query}: expected output %s, returned %s"
+
+ # Artificially change the client class counter from 2 down to 1.
+ query="UPDATE lease${v}_stat_by_client_class SET leases = 1 WHERE client_class = 'foo'"
+ run_command \
+ pgsql_execute "${query}"
+ assert_eq 0 "${EXIT_CODE}" "${query}: expected %d, returned %d"
+ assert_str_eq '' "${OUTPUT}" "${query}: expected output %s, returned %s"
+ fi
+
+ # Clean up.
+ query="DELETE FROM lease${v}"
+ run_command \
+ pgsql_execute "${query}"
+ assert_eq 0 "${EXIT_CODE}" "${query}: expected %d, returned %d"
+ assert_str_eq '' "${OUTPUT}" "${query}: expected output %s, returned %s"
+
+ # SELECT should finish successfully and the subnet counter should be 0.
+ query="SELECT leases FROM lease${v}_stat WHERE subnet_id = 1 AND state = 0 LIMIT 1"
+ run_command \
+ pgsql_execute "${query}"
+ assert_eq 0 "${EXIT_CODE}" "${query}: expected %d, returned %d"
+ assert_str_eq '0' "${OUTPUT}" "${query}: expected output %s, returned %s"
+
+ if test "${json_supported}" = 't'; then
+ # SELECT should finish successfully and the client class counter should be 0.
+ query="SELECT leases FROM lease${v}_stat_by_client_class WHERE client_class = 'foo' LIMIT 1"
+ run_command \
+ pgsql_execute "${query}"
+ assert_eq 0 "${EXIT_CODE}" "${query}: expected %d, returned %d"
+ assert_str_eq '0' "${OUTPUT}" "${query}: expected output %s, returned %s"
+ fi
+ done
+}
+
+pgsql_upgrade_13_to_14_test() {
+ run_command \
+ pgsql_execute "$session_sql"
+
+ # Added cancelled column to dhcp4_options
+ run_command \
+ pgsql_execute "select cancelled from dhcp4_options"
+ assert_eq 0 "${EXIT_CODE}" "dhcp4_options is missing cancelled column. (expected status code %d, returned %d)"
+
+ # Added cancelled column to dhcp6_options
+ run_command \
+ pgsql_execute "select cancelled from dhcp6_options"
+ assert_eq 0 "${EXIT_CODE}" "dhcp6_options is missing cancelled column. (expected status code %d, returned %d)"
+
+ # Check if offer_lifetime was added to dhcp4_shared_network table.
+ qry="SELECT offer_lifetime from dhcp4_shared_network limit 1"
+ run_command \
+ pgsql_execute "${qry}"
+ assert_eq 0 "${EXIT_CODE}" "${qry}. (expected status code %d, returned %d)"
+
+ # Check if offer_lifetime was added to dhcp4_subnet table.
+ qry="SELECT offer_lifetime from dhcp4_subnet limit 1"
+ run_command \
+ pgsql_execute "${qry}"
+ assert_eq 0 "${EXIT_CODE}" "${qry}. (expected status code %d, returned %d)"
+
+ # Check if offer_lifetime was added to dhcp4_client_class table.
+ qry="SELECT offer_lifetime from dhcp4_client_class limit 1"
+ run_command \
+ pgsql_execute "${qry}"
+ assert_eq 0 "${EXIT_CODE}" "${qry}. (expected status code %d, returned %d)"
+}
+
+pgsql_upgrade_14_to_15_test() {
+ # Added relay_id column to lease4
+ run_command \
+ pgsql_execute "select relay_id from lease4"
+ assert_eq 0 "${EXIT_CODE}" "lease4 is missing relay_id column. (expected status code %d, returned %d)"
+
+ # Added remote_id column to lease4
+ run_command \
+ pgsql_execute "select remote_id from lease4"
+ assert_eq 0 "${EXIT_CODE}" "lease4 is missing remote_id column. (expected status code %d, returned %d)"
+}
+
+pgsql_upgrade_15_to_16_test() {
+ # Added allocator column to dhcp4_shared_network
+ run_command \
+ pgsql_execute "select allocator from dhcp4_shared_network"
+ assert_eq 0 "${EXIT_CODE}" "dhcp4_shared_network is missing allocator column. (expected status code %d, returned %d)"
+
+ # Added allocator column to dhcp6_shared_network
+ run_command \
+ pgsql_execute "select allocator from dhcp6_shared_network"
+ assert_eq 0 "${EXIT_CODE}" "dhcp6_shared_network is missing allocator column. (expected status code %d, returned %d)"
+
+ # Added pd_allocator column to dhcp6_shared_network
+ run_command \
+ pgsql_execute "select pd_allocator from dhcp6_shared_network"
+ assert_eq 0 "${EXIT_CODE}" "dhcp6_shared_network is missing pd_allocator column. (expected status code %d, returned %d)"
+
+ # Added allocator column to dhcp4_subnet
+ run_command \
+ pgsql_execute "select allocator from dhcp4_subnet"
+ assert_eq 0 "${EXIT_CODE}" "dhcp4_subnet is missing allocator column. (expected status code %d, returned %d)"
+
+ # Added allocator column to dhcp6_subnet
+ run_command \
+ pgsql_execute "select allocator from dhcp6_subnet"
+ assert_eq 0 "${EXIT_CODE}" "dhcp6_subnet is missing allocator column. (expected status code %d, returned %d)"
+
+ # Added pd_allocator column to dhcp6_subnet
+ run_command \
+ pgsql_execute "select pd_allocator from dhcp6_subnet"
+ assert_eq 0 "${EXIT_CODE}" "dhcp6_subnet is missing pd_allocator column. (expected status code %d, returned %d)"
+}
+
+pgsql_upgrade_16_to_17_test() {
+ # Added lease4_pool_stat table
+ run_command \
+ pgsql_execute "SELECT subnet_id, pool_id, state, leases FROM lease4_pool_stat"
+ assert_eq 0 "${EXIT_CODE}" "lease4_pool_stat table is missing or broken. (expected status code %d, returned %d)"
+
+ # Added lease6_pool_stat table
+ run_command \
+ pgsql_execute "SELECT subnet_id, pool_id, lease_type, state, leases FROM lease6_pool_stat"
+ assert_eq 0 "${EXIT_CODE}" "lease6_pool_stat table is missing or broken. (expected status code %d, returned %d)"
+
+ # Added lease6_relay_id table
+ run_command \
+ pgsql_execute "select extended_info_id, relay_id, lease_addr from lease6_relay_id"
+ assert_eq 0 "${EXIT_CODE}" "lease6_relay_id table is missing or broken. (expected status code %d, returned %d)"
+
+ # Added lease6_remote_id table
+ run_command \
+ pgsql_execute "select extended_info_id, remote_id, lease_addr from lease6_remote_id"
+ assert_eq 0 "${EXIT_CODE}" "lease6_remote_id table is missing or broken. (expected status code %d, returned %d)"
+}
+
+pgsql_upgrade_17_to_18_test() {
+ # Verify that lease6 address is binary.
+ qry="insert into lease6 (address,duid,prefix_len,lease_type,subnet_id) values(cast('3001::99' as inet),'18219',128,1,0);"
+ run_statement "lease6_insert" "$qry"
+
+ qry="select host(address) from lease6 where duid = '18219';"
+ run_statement "lease6_insert" "$qry" "3001::99"
+
+ # Verify that ipv6_reservations address is binary.
+ qry="\
+ insert into hosts(host_id, dhcp_identifier, dhcp_identifier_type) values (18219, '18219', 1); \
+ insert into ipv6_reservations (address, prefix_len, type, dhcp6_iaid, host_id) \
+ values (cast('3001::99' as inet), 128, 1, 123, 18219); \
+ select host(address) from ipv6_reservations where host_id = 18219;"
+
+ run_statement "ipv6_reservations_insert" "$qry" "3001::99"
+}
+
+pgsql_upgrade_test() {
+ test_start "pgsql.upgrade"
+
+ # Wipe the whole database
+ pgsql_wipe
+
+ # Initialize database to schema 1.0.
+ run_command \
+ pgsql_execute_script "@abs_top_srcdir@/src/bin/admin/tests/dhcpdb_create_1.0.pgsql"
+ assert_eq 0 "${EXIT_CODE}" "cannot initialize the database, expected exit code: %d, actual: %d"
+
+ # Let's upgrade it to the latest version.
+ run_command \
+ "${kea_admin}" db-upgrade pgsql -u "${db_user}" -p "${db_password}" -n "${db_name}" -d "${db_scripts_dir}"
+ assert_eq 0 "${EXIT_CODE}" "db-upgrade failed, expected exit code: %d, actual: %d"
+
+ # Verify upgraded schema reports the latest version.
+ version=$("${kea_admin}" db-version pgsql -u "${db_user}" -p "${db_password}" -n "${db_name}" -d "${db_scripts_dir}")
+ assert_str_eq "18.0" "${version}" 'Expected kea-admin to return %s, returned value was %s'
+
+ # Check 1.0 to 2.0 upgrade
+ pgsql_upgrade_1_0_to_2_0_test
+
+ # Check 2.0 to 3.0 upgrade
+ pgsql_upgrade_2_0_to_3_0_test
+
+ # Check 3.0 to 6.1 upgrade
+ pgsql_upgrade_3_0_to_6_1_test
+
+ # Check 6.1 to 6.2 upgrade
+ pgsql_upgrade_6_1_to_6_2_test
+
+ # Check 6.2 to 7.0 upgrade
+ pgsql_upgrade_6_2_to_7_0_test
+
+ # Check 7.0 to 8.0 upgrade
+ pgsql_upgrade_7_0_to_8_0_test
+
+ # Check 8.0 to 9.0 upgrade
+ pgsql_upgrade_8_0_to_9_0_test
+
+ # Check 9.0 to 10 upgrade
+ pgsql_upgrade_9_0_to_10_test
+
+ # Check 10.0 to 11.0 upgrade
+ pgsql_upgrade_10_to_11_test
+
+ # Check 11.0 to 12.0 upgrade
+ pgsql_upgrade_11_to_12_test
+
+ # Check 12.0 to 13.0 upgrade
+ pgsql_upgrade_12_to_13_test
+
+ # Check 13.0 to 14.0 upgrade
+ pgsql_upgrade_13_to_14_test
+
+ # Check 14.0 to 15.0 upgrade
+ pgsql_upgrade_14_to_15_test
+
+ # Check 15 to 16 upgrade
+ pgsql_upgrade_15_to_16_test
+
+ # Check 16 to 17 upgrade
+ pgsql_upgrade_16_to_17_test
+
+ # Check 17 to 18 upgrade
+ pgsql_upgrade_17_to_18_test
+
+ # Let's wipe the whole database
+ pgsql_wipe
+
+ test_finish 0
+}
+
+# Test verifies the ability to dump lease4 data to CSV file
+# The dump output file is compared against a reference file.
+# If the dump is successful, the file contents will be the
+# same. Note that the expire field in the lease4 table
+# is of data type "timestamp with timezone". This means that
+# the dumped file content is dependent upon the timezone
+# setting the PostgreSQL server is using. To account for
+# this the reference data contains a tag, "<timestamp>"
+# where the expire column's data would normally be. This
+# tag is replaced during text execution with a value
+# determined by querying the PostgreSQL server. This
+# updated reference data is captured in a temporary file
+# which is used for the actual comparison.
+# May accept additional parameters to be passed to lease-dump.
+pgsql_lease4_dump_test() {
+ test_start "pgsql.lease4_dump_test"
+
+ test_dir="@abs_top_srcdir@/src/bin/admin/tests"
+ output_dir="@abs_top_builddir@/src/bin/admin/tests"
+
+ output_file="$output_dir/data/pgsql.lease4_dump_test.output.csv"
+ ref_file="$test_dir/data/lease4_dump_test.reference.csv"
+
+ # Clean up any test files left from prior failed runs unless -y was provided in which case
+ # explicitly create the file to check that it will be automatically deleted.
+ # files should be removed by kea-admin itself.
+ if printf '%s' "$@" | grep 'y' > /dev/null; then
+ touch "${output_file}"
+ touch "${output_file}.tmp"
+ else
+ rm -f "${output_file}"
+ rm -f "${output_file}.tmp"
+ fi
+
+ # Let's wipe the whole database
+ pgsql_wipe
+
+ # Ok, now let's initialize the database
+ run_command \
+ "${kea_admin}" db-init pgsql -u "${db_user}" -p "${db_password}" -n "${db_name}" -d "${db_scripts_dir}"
+ assert_eq 0 "${EXIT_CODE}" "could not create database, expected exit code %d, actual %d"
+
+ # Insert the reference records. Normally, for the bytea values, you would have two backslashes.
+ # Because shell evaluates the double quoted string one more time, they need to be doubled.
+ # Otherwise, the value is interpreted as ASCII instead of raw bytes.
+ insert_sql="\
+insert into lease4 values(10,E'\\\\x3230',E'\\\\x3330',40,TO_TIMESTAMP(1642000000),50,'t','t','one.example.com',0,'');\
+insert into lease4 values(11,'',E'\\\\x313233',40,TO_TIMESTAMP(1643210000),50,'t','t','',1,'{ }');\
+insert into lease4 values(12,E'\\\\x3232','',40,TO_TIMESTAMP(1643212345),50,'t','t','three,example,com',2,'{ \"a\": 1, \"b\": \"c\" }')"
+
+ run_command \
+ pgsql_execute "$insert_sql"
+ assert_eq 0 "${EXIT_CODE}" "insert into lease4 failed, expected exit code %d, actual %d"
+
+ # Dump lease4 to output_file
+ run_command \
+ "${kea_admin}" lease-dump pgsql -4 -u "${db_user}" -p "${db_password}" -n "${db_name}" \
+ -d "${db_scripts_dir}" -o "${output_file}" "$@"
+ assert_eq 0 "${EXIT_CODE}" "kea-admin lease-dump -4 failed, expected exit code %d, actual %d"
+
+ # Compare the dump output to reference file, they should be identical
+ run_command \
+ cmp -s "${output_file}" "${ref_file}"
+ assert_eq 0 "${EXIT_CODE}" "dump file does not match reference file, expected exit code %d, actual %d, diff:\n$(diff "${ref_file}" "${output_file}")"
+
+ # Remove the files.
+ rm -f "${output_file}"
+ rm -f "${output_file}.tmp"
+
+ # Let's wipe the whole database
+ pgsql_wipe
+
+ test_finish 0
+}
+
+# Test verifies the ability to dump lease6 data to CSV file
+# The dump output file is compared against a reference file.
+# If the dump is successful, the file contents will be the
+# same. Note that the expire field in the lease6 table
+# is of data type "timestamp with timezone". This means that
+# the dumped file content is dependent upon the timezone
+# setting the PostgreSQL server is using. To account for
+# this the reference data contains a tag, "<timestamp>"
+# where the expire column's data would normally be. This
+# tag is replaced during text execution with a value
+# determined by querying the PostgreSQL server. This
+# updated reference data is captured in a temporary file
+# which is used for the actual comparison.
+pgsql_lease6_dump_test() {
+ test_start "pgsql.lease6_dump_test"
+
+ test_dir="@abs_top_srcdir@/src/bin/admin/tests"
+ output_dir="@abs_top_builddir@/src/bin/admin/tests"
+
+ output_file="$output_dir/data/pgsql.lease6_dump_test.output.csv"
+ ref_file="$test_dir/data/lease6_dump_test.reference.csv"
+
+ # Clean up any test files left from prior failed runs unless -y was provided in which case
+ # explicitly create the file to check that it will be automatically deleted.
+ # files should be removed by kea-admin itself.
+ if printf '%s' "$@" | grep 'y' > /dev/null; then
+ touch "${output_file}"
+ touch "${output_file}.tmp"
+ else
+ rm -f "${output_file}"
+ rm -f "${output_file}.tmp"
+ fi
+
+ # Let's wipe the whole database
+ pgsql_wipe
+
+ # Ok, now let's initialize the database
+ run_command \
+ "${kea_admin}" db-init pgsql -u "${db_user}" -p "${db_password}" -n "${db_name}" -d "${db_scripts_dir}"
+ assert_eq 0 "${EXIT_CODE}" "could not create database, status code %d"
+
+ # Insert the reference records. Normally, for the bytea values, you would have two backslashes.
+ # Because shell evaluates the double quoted string one more time, they need to be doubled.
+ # Otherwise, the value is interpreted as ASCII instead of raw bytes.
+ insert_sql="\
+insert into lease6 values(cast('::10' as inet),E'\\\\x323033',30,TO_TIMESTAMP(1642000000),40,50,1,60,128,'t','t','one.example.com',0,decode(encode('80','hex'),'hex'),90,16,'',0); \
+insert into lease6 values(cast('::11' as inet),E'\\\\x323133',30,TO_TIMESTAMP(1643210000),40,50,1,60,128,'t','t','',1,decode(encode('80','hex'),'hex'),90,1,'{ }',0); \
+insert into lease6 values(cast('::12' as inet),E'\\\\x323233',30,TO_TIMESTAMP(1643212345),40,50,1,60,128,'t','t','three,example,com',2,decode(encode('80','hex'),'hex'),90,4,'{ \"a\": 1, \"b\": \"c\" }',0)"
+
+ run_command \
+ pgsql_execute "$insert_sql"
+ assert_eq 0 "${EXIT_CODE}" "insert into lease6 failed, expected exit code %d, actual %d"
+
+ # Dump lease6 to output_file
+ run_command \
+ "${kea_admin}" lease-dump pgsql -6 -u "${db_user}" -p "${db_password}" -n "${db_name}" \
+ -d "${db_scripts_dir}" -o "${output_file}" "$@"
+ assert_eq 0 "${EXIT_CODE}" "kea-admin lease-dump -6 failed, expected exit code %d, actual %d"
+
+ # Compare the dump output to reference file, they should be identical
+ run_command \
+ cmp -s "${output_file}" "${ref_file}"
+ assert_eq 0 "${EXIT_CODE}" "dump file does not match reference file, expected exit code %d, actual %d, diff:\n$(diff "${ref_file}" "${output_file}")"
+
+ # Remove the files.
+ rm -f "${output_file}"
+ rm -f "${output_file}.tmp"
+
+ # Let's wipe the whole database
+ pgsql_wipe
+
+ test_finish 0
+}
+
+# May accept additional parameters to be passed to lease-dump or to lease-upload.
+pgsql_lease4_upload_test() {
+ test_start "pgsql.lease4_upload_test"
+
+ input_file="@abs_top_srcdir@/src/bin/admin/tests/data/lease4_dump_test.reference.csv"
+ input_file_cp="@abs_top_builddir@/src/bin/admin/tests/data/lease4_dump_test.reference.csv"
+ output_file="@abs_top_builddir@/src/bin/admin/tests/data/lease4_dump_test.output.csv"
+
+ if [ "${input_file}" != "${input_file_cp}" ]; then
+ cp -f ${input_file} ${input_file_cp}
+ input_file=${input_file_cp}
+ input_file_cp=""
+ fi
+
+ # Wipe the whole database.
+ pgsql_wipe
+
+ # Clean up any test files left from prior failed runs unless -y was provided in which case
+ # explicitly create the file to check that it will be automatically deleted.
+ # files should be removed by kea-admin itself.
+ if printf '%s' "$@" | grep 'y' > /dev/null; then
+ touch "${input_file}.tmp"
+ touch "${output_file}"
+ touch "${output_file}.tmp"
+ else
+ rm -f "${input_file}.tmp"
+ rm -f "${output_file}"
+ rm -f "${output_file}.tmp"
+ fi
+
+ # Initialize the database.
+ run_command \
+ "${kea_admin}" db-init pgsql -u "${db_user}" -p "${db_password}" \
+ -n "${db_name}" -d "${db_scripts_dir}"
+ assert_eq 0 "${EXIT_CODE}" "could not create database, expected exit code %d, actual %d"
+
+ # Upload leases.
+ run_command \
+ "${kea_admin}" lease-upload pgsql -4 -u "${db_user}" \
+ -p "${db_password}" -n "${db_name}" -d "${db_scripts_dir}" \
+ -i "${input_file}" "$@"
+ assert_eq 0 "${EXIT_CODE}" "kea-admin lease-upload -4 failed, expected exit code %d, actual %d"
+
+ # Dump leases.
+ run_command \
+ "${kea_admin}" lease-dump pgsql -4 -u "${db_user}" \
+ -p "${db_password}" -n "${db_name}" -d "${db_scripts_dir}" \
+ -o "${output_file}" "$@"
+ assert_eq 0 "${EXIT_CODE}" "kea-admin lease-dump -4 failed, expected exit code %d, actual %d"
+
+ # Compare the initial file used for upload to the file retrieved from dump, they should be identical.
+ run_command \
+ cmp -s "${input_file}" "${output_file}"
+ assert_eq 0 "${EXIT_CODE}" "file resulted from dump after upload does not match file used for upload, expected exit code %d, actual %d, diff:\n$(diff "${input_file}" "${output_file}")"
+
+ # Remove the files.
+ if [ "${input_file}" != "${input_file_cp}" ]; then
+ rm -f "${input_file}"
+ fi
+ rm -f "${input_file}.tmp"
+ rm -f "${output_file}"
+ rm -f "${output_file}.tmp"
+
+ # Wipe the whole database.
+ pgsql_wipe
+
+ test_finish 0
+}
+
+pgsql_lease6_upload_test() {
+ test_start "pgsql.lease6_upload_test"
+
+ input_file="@abs_top_srcdir@/src/bin/admin/tests/data/lease6_dump_test.reference.csv"
+ input_file_cp="@abs_top_builddir@/src/bin/admin/tests/data/lease6_dump_test.reference.csv"
+ output_file="@abs_top_builddir@/src/bin/admin/tests/data/lease6_dump_test.output.csv"
+
+ if [ "${input_file}" != "${input_file_cp}" ]; then
+ cp -f ${input_file} ${input_file_cp}
+ input_file=${input_file_cp}
+ input_file_cp=""
+ fi
+
+ # Wipe the whole database.
+ pgsql_wipe
+
+ # Clean up any test files left from prior failed runs unless -y was provided in which case
+ # explicitly create the file to check that it will be automatically deleted.
+ # files should be removed by kea-admin itself.
+ if printf '%s' "$@" | grep 'y' > /dev/null; then
+ touch "${input_file}.tmp"
+ touch "${output_file}"
+ touch "${output_file}.tmp"
+ else
+ rm -f "${input_file}.tmp"
+ rm -f "${output_file}"
+ rm -f "${output_file}.tmp"
+ fi
+
+ # Initialize the database.
+ run_command \
+ "${kea_admin}" db-init pgsql -u "${db_user}" -p "${db_password}" \
+ -n "${db_name}" -d "${db_scripts_dir}"
+ assert_eq 0 "${EXIT_CODE}" "could not create database, expected exit code %d, actual %d"
+
+ # Upload leases.
+ run_command \
+ "${kea_admin}" lease-upload pgsql -6 -u "${db_user}" \
+ -p "${db_password}" -n "${db_name}" -d "${db_scripts_dir}" \
+ -i "${input_file}" "$@"
+ assert_eq 0 "${EXIT_CODE}" "kea-admin lease-upload -6 failed, expected exit code %d, actual %d"
+
+ # Dump leases.
+ run_command \
+ "${kea_admin}" lease-dump pgsql -6 -u "${db_user}" \
+ -p "${db_password}" -n "${db_name}" -d "${db_scripts_dir}" \
+ -o "${output_file}" "$@"
+ assert_eq 0 "${EXIT_CODE}" "kea-admin lease-dump -6 failed, expected exit code %d, actual %d"
+
+ # Compare the initial file used for upload to the file retrieved from dump, they should be identical.
+ run_command \
+ cmp -s "${input_file}" "${output_file}"
+ assert_eq 0 "${EXIT_CODE}" "file resulted from dump after upload does not match file used for upload, expected exit code %d, actual %d, diff:\n$(diff "${input_file}" "${output_file}")"
+
+ # Remove the files.
+ if [ "${input_file}" != "${input_file_cp}" ]; then
+ rm -f "${input_file}"
+ fi
+ rm -f "${input_file}.tmp"
+ rm -f "${output_file}"
+ rm -f "${output_file}.tmp"
+
+ # Wipe the whole database.
+ pgsql_wipe
+
+ test_finish 0
+}
+
+# Upgrades an existing schema to a target newer version
+# param target_version - desired schema version as "major.minor"
+pgsql_upgrade_schema_to_version() {
+ target_version=$1
+
+ upgrade_scripts_dir=${db_scripts_dir}/pgsql
+
+ # Check if the scripts directory exists at all.
+ if [ ! -d ${upgrade_scripts_dir} ]; then
+ log_error "Invalid scripts directory: ${upgrade_scripts_dir}"
+ exit 1
+ fi
+
+ # Check if there are any files in it
+ num_files=$(find ${upgrade_scripts_dir} -name 'upgrade*.sh' -type f | wc -l)
+ if [ "${num_files}" -eq 0 ]; then
+ upgrade_scripts_dir=@abs_top_builddir@/src/share/database/scripts/pgsql
+
+ # Check if the scripts directory exists at all.
+ if [ ! -d ${upgrade_scripts_dir} ]; then
+ log_error "Invalid scripts directory: ${upgrade_scripts_dir}"
+ exit 1
+ fi
+
+ # Check if there are any files in it
+ num_files=$(find "${upgrade_scripts_dir}" -name 'upgrade*.sh' -type f | wc -l)
+ fi
+
+ if [ "${num_files}" -eq 0 ]; then
+ log_error "No scripts in ${upgrade_scripts_dir}?"
+ exit 1
+ fi
+
+ # Postgres psql does not accept pw on command line, but can do it
+ # thru an env
+ export PGPASSWORD=$db_password
+
+ for script in "${upgrade_scripts_dir}"/upgrade*.sh
+ do
+ version=$(pgsql_version)
+ if [ "${version}" = "${target_version}" ]
+ then
+ break
+ fi
+
+ echo "Processing $script file..."
+ "${script}" -U "${db_user}" -d "${db_name}"
+ done
+
+ echo "Schema upgraded to $version"
+}
+
+# Verifies lease4_stat trigger operations on
+# an new, empty database. It inserts, updates, and
+# deletes various leases, checking lease4_stat
+# values along the way.
+pgsql_lease4_stat_test() {
+ test_start "pgsql.lease4_stat_test"
+
+ # Let's wipe the whole database
+ pgsql_wipe
+
+ # Ok, now let's initialize the database
+ run_command \
+ "${kea_admin}" db-init pgsql -u "${db_user}" -p "${db_password}" -n "${db_name}" -d "${db_scripts_dir}"
+ assert_eq 0 "${EXIT_CODE}" "kea-admin db-init pgsql failed, expected %d, returned non-zero status code %d"
+
+ # Verify lease4 stat table is present
+ qry="select count(subnet_id) from lease4_stat"
+ run_statement "#1" "$qry" 0
+
+ # Insert lease4
+ qry="insert into lease4 (address, subnet_id, state) values (111,1,0)"
+ run_statement "#2" "$qry"
+
+ # Assigned state count should be 1
+ qry="select leases from lease4_stat where subnet_id = 1 and state = 0"
+ run_statement "#3" "$qry" 1
+
+ # Assigned state count should be 1
+ qry="select leases from lease4_pool_stat where subnet_id = 1 and pool_id = 0 and state = 0"
+ run_statement "#4" "$qry" 1
+
+ # Set lease state to declined
+ qry="update lease4 set state = 1 where address = 111"
+ run_statement "#5" "$qry"
+
+ # Leases state count for assigned should be 0
+ qry="select leases from lease4_stat where subnet_id = 1 and state = 0"
+ run_statement "#6" "$qry" 0
+
+ # Leases state count for assigned should be 0
+ qry="select leases from lease4_pool_stat where subnet_id = 1 and pool_id = 0 and state = 0"
+ run_statement "#7" "$qry" 0
+
+ # Leases state count for declined should be 1
+ qry="select leases from lease4_stat where subnet_id = 1 and state = 1"
+ run_statement "#8" "$qry" 1
+
+ # Leases state count for declined should be 1
+ qry="select leases from lease4_pool_stat where subnet_id = 1 and pool_id = 0 and state = 1"
+ run_statement "#9" "$qry" 1
+
+ # Delete the lease
+ qry="delete from lease4 where address = 111"
+ run_statement "#10" "$qry"
+
+ # Leases state count for declined should be 0
+ qry="select leases from lease4_stat where subnet_id = 1 and state = 1"
+ run_statement "#11" "$qry" 0
+
+ # Leases state count for declined should be 0
+ qry="select leases from lease4_pool_stat where subnet_id = 1 and pool_id = 0 and state = 1"
+ run_statement "#12" "$qry" 0
+
+ # Insert lease4
+ qry="insert into lease4 (address, subnet_id, pool_id, state) values (112,1,1,0)"
+ run_statement "#13" "$qry"
+
+ # Assigned state count should be 1
+ qry="select leases from lease4_stat where subnet_id = 1 and state = 0"
+ run_statement "#14" "$qry" 1
+
+ # Assigned state count should be 1
+ qry="select leases from lease4_pool_stat where subnet_id = 1 and pool_id = 1 and state = 0"
+ run_statement "#15" "$qry" 1
+
+ # Insert lease4
+ qry="insert into lease4 (address, subnet_id, pool_id, state) values (113,1,2,0)"
+ run_statement "#16" "$qry"
+
+ # Assigned state count should be 2
+ qry="select leases from lease4_stat where subnet_id = 1 and state = 0"
+ run_statement "#17" "$qry" 2
+
+ # Assigned state count should be 1
+ qry="select leases from lease4_pool_stat where subnet_id = 1 and pool_id = 1 and state = 0"
+ run_statement "#18" "$qry" 1
+
+ # Assigned state count should be 1
+ qry="select leases from lease4_pool_stat where subnet_id = 1 and pool_id = 2 and state = 0"
+ run_statement "#19" "$qry" 1
+
+ # Let's wipe the whole database
+ pgsql_wipe
+
+ test_finish 0
+}
+
+# Verifies that lease6_stat triggers operate correctly
+# for using a given address and lease_type. It will
+# insert a lease, update it, and delete checking the
+# lease stat counts along the way. It assumes the
+# database has been created but is empty.
+# param addr - address to use to add to subnet 1
+# param ltype - type of lease to create
+pgsql_lease6_stat_per_type() {
+ addr=$1;shift
+ addr1=$1;shift
+ addr2=$1;shift
+ ltype=$1
+
+ # insert a lease6 for addr and ltype, state assigned
+ qry="insert into lease6 (address, lease_type, subnet_id, state) values (cast('$addr' as inet),$ltype,1,0)"
+ run_statement "#2" "$qry"
+
+ # assigned stat should be 1
+ qry="select leases from lease6_stat where subnet_id = 1 and lease_type = $ltype and state = 0"
+ run_statement "#3" "$qry" 1
+
+ # assigned stat should be 1
+ qry="select leases from lease6_pool_stat where subnet_id = 1 and lease_type = $ltype and pool_id = 0 and state = 0"
+ run_statement "#4" "$qry" 1
+
+ # update the lease, changing state to declined
+ qry="update lease6 set state = 1 where address = cast('$addr' as inet)"
+ run_statement "#5" "$qry"
+
+ # leases stat for assigned state should be 0
+ qry="select leases from lease6_stat where subnet_id = 1 and lease_type = $ltype and state = 0"
+ run_statement "#6" "$qry" 0
+
+ # leases stat for assigned state should be 0
+ qry="select leases from lease6_pool_stat where subnet_id = 1 and lease_type = $ltype and pool_id = 0 and state = 0"
+ run_statement "#7" "$qry" 0
+
+ # leases count for declined state should be 1
+ qry="select leases from lease6_stat where subnet_id = 1 and lease_type = $ltype and state = 1"
+ run_statement "#8" "$qry" 1
+
+ # leases count for declined state should be 1
+ qry="select leases from lease6_pool_stat where subnet_id = 1 and lease_type = $ltype and pool_id = 0 and state = 1"
+ run_statement "#9" "$qry" 1
+
+ # delete the lease
+ qry="delete from lease6 where address = '$addr'"
+ run_statement "#10" "$qry"
+
+ # leases count for declined state should be 0
+ qry="select leases from lease6_stat where subnet_id = 1 and lease_type = $ltype and state = 0"
+ run_statement "#11" "$qry" 0
+
+ # leases count for declined state should be 0
+ qry="select leases from lease6_pool_stat where subnet_id = 1 and lease_type = $ltype and pool_id = 0 and state = 0"
+ run_statement "#12" "$qry" 0
+
+ # insert a lease6 for addr and ltype, state assigned
+ qry="insert into lease6 (address, lease_type, subnet_id, pool_id, state) values (cast('$addr1' as inet),$ltype,1,1,0)"
+ run_statement "#13" "$qry"
+
+ # assigned stat should be 1
+ qry="select leases from lease6_stat where subnet_id = 1 and lease_type = $ltype and state = 0"
+ run_statement "#14" "$qry" 1
+
+ # assigned stat should be 1
+ qry="select leases from lease6_pool_stat where subnet_id = 1 and lease_type = $ltype and pool_id = 1 and state = 0"
+ run_statement "#15" "$qry" 1
+
+ # insert a lease6 for addr and ltype, state assigned
+ qry="insert into lease6 (address, lease_type, subnet_id, pool_id, state) values (cast('$addr2' as inet),$ltype,1,2,0)"
+ run_statement "#16" "$qry"
+
+ # assigned stat should be 2
+ qry="select leases from lease6_stat where subnet_id = 1 and lease_type = $ltype and state = 0"
+ run_statement "#17" "$qry" 2
+
+ # assigned stat should be 1
+ qry="select leases from lease6_pool_stat where subnet_id = 1 and lease_type = $ltype and pool_id = 1 and state = 0"
+ run_statement "#18" "$qry" 1
+
+ # assigned stat should be 1
+ qry="select leases from lease6_pool_stat where subnet_id = 1 and lease_type = $ltype and pool_id = 2 and state = 0"
+ run_statement "#19" "$qry" 1
+}
+
+# Verifies that lease6_stat triggers operation correctly
+# for both NA and PD lease types, pgsql_lease6_stat_per_type()
+pgsql_lease6_stat_test() {
+
+ test_start "pgsql.lease6_stat_test"
+
+ # Let's wipe the whole database
+ pgsql_wipe
+
+ # Ok, now let's initialize the database
+ run_command \
+ "${kea_admin}" db-init pgsql -u "${db_user}" -p "${db_password}" -n "${db_name}" -d "${db_scripts_dir}"
+ assert_eq 0 "${EXIT_CODE}" "kea-admin db-init pgsql failed, expected %d, returned non-zero status code %d"
+
+ # verify lease6 stat table is present
+ qry="select count(subnet_id) from lease6_stat"
+ run_statement "#1" "$qry"
+
+ # Test for address 111, NA lease type
+ pgsql_lease6_stat_per_type "::11" "::12" "::13" "0"
+
+ # Test for address 222, PD lease type
+ pgsql_lease6_stat_per_type "::22" "::23" "::24" "1"
+
+ # Let's wipe the whole database
+ pgsql_wipe
+
+ test_finish 0
+}
+
+# Verifies that you can upgrade from earlier version and
+# lease<4/6>_stat tables will be populated based on existing
+# leases and that the stat triggers work properly.
+pgsql_lease_stat_upgrade_test() {
+ test_start "pgsql.lease_stat_upgrade_test"
+
+ # Let's wipe the whole database
+ pgsql_wipe
+
+ # We need to create an older database with lease data so we can
+ # verify the upgrade mechanisms which prepopulate the lease stat
+ # tables.
+ #
+ # Initialize database to schema 1.0.
+ pgsql_execute_script "@abs_top_srcdir@/src/bin/admin/tests/dhcpdb_create_1.0.pgsql"
+ assert_eq 0 "${EXIT_CODE}" "cannot initialize 1.0 database, expected exit code: %d, actual: %d"
+
+ # Now upgrade to schema 2.0, this has lease_state in it
+ pgsql_upgrade_schema_to_version 2.0
+
+ # Now we need insert some leases to "migrate" for both v4 and v6
+ qry=\
+"insert into lease4 (address, subnet_id, state) values (111,10,0);\
+ insert into lease4 (address, subnet_id, state) values (222,10,0);\
+ insert into lease4 (address, subnet_id, state) values (333,10,1);\
+ insert into lease4 (address, subnet_id, state) values (444,10,2);\
+ insert into lease4 (address, subnet_id, state) values (555,77,0)"
+ run_statement "insert v4 leases" "$qry"
+
+ qry=\
+"insert into lease6 (address, lease_type, subnet_id, state) values ('::11',0,40,0);\
+ insert into lease6 (address, lease_type, subnet_id, state) values ('::22',0,40,1);\
+ insert into lease6 (address, lease_type, subnet_id, state) values ('::33',1,40,0);\
+ insert into lease6 (address, lease_type, subnet_id, state) values ('::44',1,50,0);\
+ insert into lease6 (address, lease_type, subnet_id, state) values ('::55',1,50,0);\
+ insert into lease6 (address, lease_type, subnet_id, state) values ('::66',1,40,2)"
+ run_statement "insert v6 leases" "$qry"
+
+ # Let's upgrade it to the latest version.
+ run_command \
+ "${kea_admin}" db-upgrade pgsql -u "${db_user}" -p "${db_password}" -n "${db_name}" -d "${db_scripts_dir}"
+
+ #
+ # First we'll verify lease4_stats are correct after migration.
+ #
+
+ # Assigned leases for subnet 10 should be 2
+ qry="select leases from lease4_stat where subnet_id = 10 and state = 0"
+ run_statement "#4.1" "$qry" 2
+
+ # Assigned leases for subnet 10 should be 2
+ qry="select leases from lease4_pool_stat where subnet_id = 10 and pool_id = 0 and state = 0"
+ run_statement "#4.2" "$qry" 2
+
+ # Assigned leases for subnet 77 should be 1
+ qry="select leases from lease4_stat where subnet_id = 77 and state = 0"
+ run_statement "#4.3" "$qry" 1
+
+ # Assigned leases for subnet 77 should be 1
+ qry="select leases from lease4_pool_stat where subnet_id = 77 and pool_id = 0 and state = 0"
+ run_statement "#4.4" "$qry" 1
+
+ # Should be no records for EXPIRED
+ qry="select count(subnet_id) from lease4_stat where state = 2"
+ run_statement "#4.5" "$qry" 0
+
+ # Should be no records for EXPIRED
+ qry="select count(subnet_id) from lease4_pool_stat where state = 2"
+ run_statement "#4.6" "$qry" 0
+
+ #
+ # Now we'll verify v4 trigger operation for insert, update, and delete
+ #
+
+ # Insert a new lease subnet 77
+ qry="insert into lease4 (address, subnet_id, pool_id, state) values (777,77,1,0)"
+ run_statement "#4.7" "$qry"
+
+ # Assigned count for subnet 77 should be 2
+ qry="select leases from lease4_stat where subnet_id = 77 and state = 0"
+ run_statement "#4.8" "$qry" 2
+
+ # Assigned count for subnet 77 should be 1
+ qry="select leases from lease4_pool_stat where subnet_id = 77 and pool_id = 0 and state = 0"
+ run_statement "#4.9" "$qry" 1
+
+ # Assigned count for subnet 77 should be 1
+ qry="select leases from lease4_pool_stat where subnet_id = 77 and pool_id = 1 and state = 0"
+ run_statement "#4.10" "$qry" 1
+
+ # Update the state of the new lease to declined
+ qry="update lease4 set state = 1 where address = 777"
+ run_statement "#4.11" "$qry"
+
+ # Assigned count for subnet 77 should be 1 again
+ qry="select leases from lease4_stat where subnet_id = 77 and state = 0"
+ run_statement "#4.12" "$qry" 1
+
+ # Assigned count for subnet 77 should be 1 again
+ qry="select leases from lease4_pool_stat where subnet_id = 77 and pool_id = 0 and state = 0"
+ run_statement "#4.13" "$qry" 1
+
+ # Assigned count for subnet 77 should be 0 again
+ qry="select leases from lease4_pool_stat where subnet_id = 77 and pool_id = 1 and state = 0"
+ run_statement "#4.14" "$qry" 0
+
+ # Declined count for subnet 77 should be 1
+ qry="select leases from lease4_stat where subnet_id = 77 and state = 1"
+ run_statement "#4.15" "$qry" 1
+
+ # Declined count for subnet 77 should be 1
+ qry="select leases from lease4_pool_stat where subnet_id = 77 and pool_id = 1 and state = 1"
+ run_statement "#4.16" "$qry" 1
+
+ # Delete the lease.
+ qry="delete from lease4 where address = 777"
+ run_statement "#4.17" "$qry"
+
+ # Declined count for subnet 77 should be 0
+ qry="select leases from lease4_stat where subnet_id = 77 and state = 1"
+ run_statement "#4.18" "$qry" 0
+
+ # Declined count for subnet 77 should be 0
+ qry="select leases from lease4_pool_stat where subnet_id = 77 and pool_id = 1 and state = 1"
+ run_statement "#4.19" "$qry" 0
+
+ #
+ # Next we'll verify lease6_stats are correct after migration.
+ #
+
+ # Assigned leases for subnet 40 should be 1
+ qry="select leases from lease6_stat where subnet_id = 40 and lease_type = 0 and state = 0"
+ run_statement "#6.1" "$qry" 1
+
+ # Assigned leases for subnet 40 should be 1
+ qry="select leases from lease6_pool_stat where subnet_id = 40 and lease_type = 0 and pool_id = 0 and state = 0"
+ run_statement "#6.2" "$qry" 1
+
+ # Assigned (PD) leases for subnet 40 should be 1
+ qry="select leases from lease6_stat where subnet_id = 40 and lease_type = 1 and state = 0"
+ run_statement "#6.3" "$qry" 1
+
+ # Assigned (PD) leases for subnet 40 should be 1
+ qry="select leases from lease6_pool_stat where subnet_id = 40 and lease_type = 1 and pool_id = 0 and state = 0"
+ run_statement "#6.4" "$qry" 1
+
+ # Declined leases for subnet 40 should be 1
+ qry="select leases from lease6_stat where subnet_id = 40 and lease_type = 0 and state = 1"
+ run_statement "#6.5" "$qry" 1
+
+ # Declined leases for subnet 40 should be 1
+ qry="select leases from lease6_pool_stat where subnet_id = 40 and lease_type = 0 and pool_id = 0 and state = 1"
+ run_statement "#6.6" "$qry" 1
+
+ # Assigned (PD) leases for subnet 50 should be 2
+ qry="select leases from lease6_stat where subnet_id = 50 and lease_type = 1 and state = 0"
+ run_statement "#6.7" "$qry" 2
+
+ # Assigned (PD) leases for subnet 50 should be 2
+ qry="select leases from lease6_pool_stat where subnet_id = 50 and lease_type = 1 and pool_id = 0 and state = 0"
+ run_statement "#6.8" "$qry" 2
+
+ # Should be no records for EXPIRED
+ qry="select count(subnet_id) from lease6_stat where state = 2"
+ run_statement "#6.9" "$qry" 0
+
+ # Should be no records for EXPIRED
+ qry="select count(subnet_id) from lease6_pool_stat where state = 2"
+ run_statement "#6.10" "$qry" 0
+
+ #
+ # Finally we'll verify v6 trigger operation for insert, update, and delete
+ #
+
+ # Insert a new lease subnet 50
+ qry="insert into lease6 (address, subnet_id, pool_id, lease_type, state) values ('::77',50,1,1,0)"
+ run_statement "#6.11" "$qry"
+
+ # Assigned count for subnet 50 should be 3
+ qry="select leases from lease6_stat where subnet_id = 50 and lease_type = 1 and state = 0"
+ run_statement "#6.12" "$qry" 3
+
+ # Assigned count for subnet 50 should be 2
+ qry="select leases from lease6_pool_stat where subnet_id = 50 and lease_type = 1 and pool_id = 0 and state = 0"
+ run_statement "#6.13" "$qry" 2
+
+ # Assigned count for subnet 50 should be 1
+ qry="select leases from lease6_pool_stat where subnet_id = 50 and lease_type = 1 and pool_id = 1 and state = 0"
+ run_statement "#6.14" "$qry" 1
+
+ # Update the state of the new lease to expired
+ qry="update lease6 set state = 2 where address = '::77'"
+ run_statement "#6.15" "$qry"
+
+ # Assigned count for subnet 50 should be 2 again
+ qry="select leases from lease6_stat where subnet_id = 50 and lease_type = 1 and state = 0"
+ run_statement "#6.16" "$qry" 2
+
+ # Assigned count for subnet 50 should be 0 again
+ qry="select leases from lease6_pool_stat where subnet_id = 50 and lease_type = 1 and pool_id = 1 and state = 0"
+ run_statement "#6.17" "$qry" 0
+
+ # Delete another PD lease.
+ qry="delete from lease6 where address = '::55'"
+ run_statement "#6.18" "$qry"
+
+ # Assigned leases for subnet 50 should be 1
+ qry="select leases from lease6_stat where subnet_id = 50 and lease_type = 1 and state = 0"
+ run_statement "#6.19" "$qry" 1
+
+ # Assigned leases for subnet 50 should be 1
+ qry="select leases from lease6_pool_stat where subnet_id = 50 and lease_type = 1 and pool_id = 0 and state = 0"
+ run_statement "#6.20" "$qry" 1
+
+ # Let's wipe the whole database
+ pgsql_wipe
+
+ test_finish 0
+}
+
+pgsql_lease_stat_recount_test() {
+ test_start "pgsql.lease_stat_recount_test"
+
+ # Let's wipe the whole database
+ pgsql_wipe
+
+ # Ok, now let's initialize the database
+ run_command \
+ "${kea_admin}" db-init pgsql -u "${db_user}" -p "${db_password}" -n "${db_name}" -d "${db_scripts_dir}"
+ assert_eq 0 "${EXIT_CODE}" "kea-admin db-init pgsql failed, expected %d, returned non-zero status code %d"
+
+ # Now we need insert some leases to "recount"
+ qry=\
+"insert into lease4 (address, subnet_id, state) values (111,10,0);\
+ insert into lease4 (address, subnet_id, pool_id, state) values (222,10,1,0);\
+ insert into lease4 (address, subnet_id, state) values (333,10,1);\
+ insert into lease4 (address, subnet_id, state) values (444,10,2);\
+ insert into lease4 (address, subnet_id, pool_id, state) values (555,77,2,0)"
+ run_statement "insert v4 leases" "$qry"
+
+ qry=\
+"insert into lease6 (address, lease_type, subnet_id, state) values (cast('::11' as inet),0,40,0);\
+ insert into lease6 (address, lease_type, subnet_id, pool_id, state) values (cast('::22' as inet),0,40,1,1);\
+ insert into lease6 (address, lease_type, subnet_id, state) values (cast('::33' as inet),1,40,0);\
+ insert into lease6 (address, lease_type, subnet_id, state) values (cast('::44' as inet),1,50,0);\
+ insert into lease6 (address, lease_type, subnet_id, pool_id, state) values (cast('::55' as inet),1,50,2,0);\
+ insert into lease6 (address, lease_type, subnet_id, state) values (cast('::66' as inet),1,40,2)"
+ run_statement "insert v6 leases" "$qry"
+
+ # Now we change some counters.
+ qry=\
+"insert into lease4_stat (subnet_id, state, leases) values (20,0,1);\
+ update lease4_stat set leases = 5 where subnet_id = 10 and state = 0;\
+ delete from lease4_stat where subnet_id = 10 and state = 2"
+ run_statement "change v4 stats" "$qry"
+
+ qry=\
+"insert into lease4_pool_stat (subnet_id, pool_id, state, leases) values (20,3,0,1);\
+ update lease4_pool_stat set leases = 5 where subnet_id = 10 and pool_id = 0 and state = 0;\
+ delete from lease4_pool_stat where subnet_id = 10 and pool_id = 0 and state = 2"
+ run_statement "change v4 stats" "$qry"
+
+ qry=\
+"insert into lease6_stat (subnet_id, lease_type, state, leases) values (20,1,0,1);\
+ update lease6_stat set leases = 5 where subnet_id = 40 and lease_type = 0 and state = 0;\
+ delete from lease6_stat where subnet_id = 40 and lease_type = 1 and state = 2"
+ run_statement "change v6 stats" "$qry"
+
+ qry=\
+"insert into lease6_pool_stat (subnet_id, pool_id, lease_type, state, leases) values (20,3,1,0,1);\
+ update lease6_pool_stat set leases = 5 where subnet_id = 40 and lease_type = 0 and pool_id = 0 and state = 0;\
+ delete from lease6_pool_stat where subnet_id = 40 and lease_type = 1 and pool_id = 0 and state = 2"
+ run_statement "change v6 stats" "$qry"
+
+ # Recount all statistics from scratch.
+ run_command \
+ "${kea_admin}" stats-recount pgsql -u "${db_user}" -p "${db_password}" -n "${db_name}"
+ assert_eq 0 "${EXIT_CODE}" "kea-admin stats-recount pgsql failed, expected %d, returned non-zero status code %d"
+
+ #
+ # First we'll verify lease4_stats are correct after recount.
+ #
+
+ # Assigned leases for subnet 10 should be 2
+ qry="select leases from lease4_stat where subnet_id = 10 and state = 0"
+ run_statement "#4.1" "$qry" 2
+
+ # Assigned leases for subnet 10 should be 1
+ qry="select leases from lease4_pool_stat where subnet_id = 10 and pool_id = 0 and state = 0"
+ run_statement "#4.2" "$qry" 1
+
+ # Assigned leases for subnet 10 should be 1
+ qry="select leases from lease4_pool_stat where subnet_id = 10 and pool_id = 1 and state = 0"
+ run_statement "#4.3" "$qry" 1
+
+ # Declined leases for subnet 10 should be 1
+ qry="select leases from lease4_stat where subnet_id = 10 and state = 1"
+ run_statement "#4.4" "$qry" 1
+
+ # Declined leases for subnet 10 should be 1
+ qry="select leases from lease4_pool_stat where subnet_id = 10 and pool_id = 0 and state = 0"
+ run_statement "#4.5" "$qry" 1
+
+ # Assigned leases for subnet 77 should be 1
+ qry="select leases from lease4_stat where subnet_id = 77 and state = 0"
+ run_statement "#4.6" "$qry" 1
+
+ # Assigned leases for subnet 77 should be 1
+ qry="select leases from lease4_pool_stat where subnet_id = 77 and pool_id = 2 and state = 0"
+ run_statement "#4.7" "$qry" 1
+
+ # Should be no records for EXPIRED
+ qry="select count(subnet_id) from lease4_stat where state = 2"
+ run_statement "#4.8" "$qry" 0
+
+ # Should be no records for EXPIRED
+ qry="select count(subnet_id) from lease4_pool_stat where state = 2"
+ run_statement "#4.9" "$qry" 0
+
+ #
+ # Next we'll verify lease6_stats are correct after recount.
+ #
+
+ # Assigned leases for subnet 40 should be 1
+ qry="select leases from lease6_stat where subnet_id = 40 and lease_type = 0 and state = 0"
+ run_statement "#6.1" "$qry" 1
+
+ # Assigned leases for subnet 40 should be 1
+ qry="select leases from lease6_pool_stat where subnet_id = 40 and lease_type = 0 and pool_id = 0 and state = 0"
+ run_statement "#6.2" "$qry" 1
+
+ # Assigned (PD) leases for subnet 40 should be 1
+ qry="select leases from lease6_stat where subnet_id = 40 and lease_type = 1 and state = 0"
+ run_statement "#6.3" "$qry" 1
+
+ # Assigned (PD) leases for subnet 40 should be 1
+ qry="select leases from lease6_pool_stat where subnet_id = 40 and lease_type = 1 and pool_id = 0 and state = 0"
+ run_statement "#6.4" "$qry" 1
+
+ # Declined leases for subnet 40 should be 1
+ qry="select leases from lease6_stat where subnet_id = 40 and lease_type = 0 and state = 1"
+ run_statement "#6.5" "$qry" 1
+
+ # Declined leases for subnet 40 should be 1
+ qry="select leases from lease6_pool_stat where subnet_id = 40 and lease_type = 0 and pool_id = 1 and state = 1"
+ run_statement "#6.6" "$qry" 1
+
+ # Assigned (PD) leases for subnet 50 should be 2
+ qry="select leases from lease6_stat where subnet_id = 50 and lease_type = 1 and state = 0"
+ run_statement "#6.7" "$qry" 2
+
+ # Assigned (PD) leases for subnet 50 should be 1
+ qry="select leases from lease6_pool_stat where subnet_id = 50 and lease_type = 1 and pool_id = 0 and state = 0"
+ run_statement "#6.8" "$qry" 1
+
+ # Assigned (PD) leases for subnet 50 should be 1
+ qry="select leases from lease6_pool_stat where subnet_id = 50 and lease_type = 1 and pool_id = 2 and state = 0"
+ run_statement "#6.9" "$qry" 1
+
+ # Should be no records for EXPIRED
+ qry="select count(subnet_id) from lease6_stat where state = 2"
+ run_statement "#6.10" "$qry" 0
+
+ # Should be no records for EXPIRED
+ qry="select count(subnet_id) from lease6_pool_stat where state = 2"
+ run_statement "#6.11" "$qry" 0
+
+ # Let's wipe the whole database
+ pgsql_wipe
+
+ test_finish 0
+}
+
+# Verifies that you can upgrade from an earlier version and
+# that unused subnet ID values in hosts and options tables are
+# converted to NULL.
+pgsql_unused_subnet_id_test() {
+ test_start "pgsql.unused_subnet_id_test"
+
+ # Let's wipe the whole database
+ pgsql_wipe
+
+ # We need to create an older database with lease data so we can
+ # verify the upgrade mechanisms which prepopulate the lease stat
+ # tables.
+ #
+ # Initialize database to schema 1.0.
+ pgsql_execute_script "@abs_top_srcdir@/src/bin/admin/tests/dhcpdb_create_1.0.pgsql"
+ assert_eq 0 "${EXIT_CODE}" "cannot initialize 1.0 database, expected exit code: %d, actual: %d"
+
+ # Now upgrade to schema 4.0
+ pgsql_upgrade_schema_to_version 4.0
+
+ # Now we need insert some hosts to "migrate" for both v4 and v6
+ qry=\
+"insert into hosts (dhcp_identifier_type, dhcp_identifier, dhcp4_subnet_id, dhcp6_subnet_id, hostname)\
+ values (0, '0123456', 0, 0, 'both'); \
+ insert into hosts (dhcp_identifier_type, dhcp_identifier, dhcp4_subnet_id, dhcp6_subnet_id, hostname)\
+ values (0, '1123456', 4, 0, 'v4only');
+ insert into hosts (dhcp_identifier_type, dhcp_identifier, dhcp4_subnet_id, dhcp6_subnet_id, hostname)\
+ values (0, '2123456', 0, 6, 'v6only');\
+ insert into hosts (dhcp_identifier_type, dhcp_identifier, dhcp4_subnet_id, dhcp6_subnet_id, hostname) \
+ values (0, '3123456', 4, 6, 'neither')"
+
+ run_statement "insert hosts" "$qry"
+
+ # Now we need insert some options to "migrate" for both v4 and v6
+ qry=\
+"insert into dhcp4_options (code, dhcp4_subnet_id, scope_id) values (1, 4, 0);\
+ insert into dhcp4_options (code, dhcp4_subnet_id, scope_id) values (2, 0, 0);\
+ insert into dhcp6_options (code, dhcp6_subnet_id, scope_id) values (1, 6, 0);\
+ insert into dhcp6_options (code, dhcp6_subnet_id, scope_id) values (2, 0, 0)"
+
+ run_statement "insert options" "$qry"
+
+ # Let's upgrade it to the latest version.
+ run_command \
+ "${kea_admin}" db-upgrade pgsql -u "${db_user}" -p "${db_password}" -n "${db_name}" -d "${db_scripts_dir}"
+
+ # Upgrade should succeed
+ assert_eq 0 "${EXIT_CODE}" "upgrade failed"
+
+ # Two hosts should have null v4 subnet ids
+ qry="select count(host_id) from hosts where dhcp4_subnet_id is null"
+ run_statement "#hosts.1" "$qry" 2
+
+ # Two hosts should have v4 subnet ids = 4
+ qry="select count(host_id) from hosts where dhcp4_subnet_id = 4"
+ run_statement "#hosts.2" "$qry" 2
+
+ # Two hosts should have null v6 subnet ids
+ qry="select count(host_id) from hosts where dhcp6_subnet_id is null"
+ run_statement "#hosts.3" "$qry" 2
+
+ # Two hosts should should have v6 subnet ids = 6
+ qry="select count(host_id) from hosts where dhcp6_subnet_id = 6"
+ run_statement "#hosts.4" "$qry" 2
+
+ # One option should have null v4 subnet id
+ qry="select count(option_id) from dhcp4_options where dhcp4_subnet_id is null"
+ run_statement "#options.1" "$qry" 1
+
+ # One option should have v4 subnet id = 4
+ qry="select count(option_id) from dhcp4_options where dhcp4_subnet_id = 4"
+ run_statement "#options.2" "$qry" 1
+
+ # One option should have null v6 subnet id
+ qry="select count(option_id) from dhcp6_options where dhcp6_subnet_id is null"
+ run_statement "#options.3" "$qry" 1
+
+ # One option should have v4 subnet id = 6
+ qry="select count(option_id) from dhcp6_options where dhcp6_subnet_id = 6"
+ run_statement "#options.4" "$qry" 1
+
+ # Let's wipe the whole database
+ pgsql_wipe
+
+ test_finish 0
+}
+
+# Verifies that you can upgrade from earlier version and that initial EMPTY DUID
+# (0x00) value in lease6 table is updated to proper value (0x000000).
+pgsql_update_empty_duid_test() {
+ test_start "pgsql.update_empty_duid_test"
+
+ # Let's wipe the whole database
+ pgsql_wipe
+
+ # We need to create an older database with lease data so we can
+ # verify the upgrade mechanisms which prepopulate the lease stat
+ # tables.
+ #
+ # Initialize database to schema 1.0.
+ pgsql_execute_script "@abs_top_srcdir@/src/bin/admin/tests/dhcpdb_create_1.0.pgsql"
+ assert_eq 0 "${EXIT_CODE}" "cannot initialize 1.0 database, expected exit code: %d, actual: %d"
+
+ # Now upgrade to schema 15.0
+ pgsql_upgrade_schema_to_version 15.0
+
+ qry=\
+"insert into lease6 values('::10',E'\\\\x323033',30,TO_TIMESTAMP(1642000000),40,50,1,60,70,'t','t','one.example.com',0,decode(encode('80','hex'),'hex'),90,16,''); \
+ insert into lease6 values('::11',E'\\\\x00',30,TO_TIMESTAMP(1643210000),40,50,1,60,70,'t','t','',1,decode(encode('80','hex'),'hex'),90,1,'{ }')"
+
+ run_statement "insert v6 leases" "$qry"
+
+ # Let's upgrade it to the latest version.
+ run_command \
+ "${kea_admin}" db-upgrade pgsql -u "${db_user}" -p "${db_password}" -n "${db_name}" -d "${db_scripts_dir}"
+
+ # leases count for declined state should be 1 with DUID updated (0x000000)
+ qry="select count(*) from lease6 where address = '::11' and duid = E'\\\\x000000' and state = 1"
+ run_statement "#2" "$qry" 1
+
+ # leases count for non declined state should be 1 with DUID unchanged (0x323033)
+ qry="select count(*) from lease6 where address = '::10' and duid = E'\\\\x323033' and state = 0"
+ run_statement "#3" "$qry" 1
+
+ # Let's wipe the whole database
+ pgsql_wipe
+
+ test_finish 0
+}
+
+# Verifies that converting from lease6.address to binary column works
+# while preserving data.
+pgsql_update_v6_addresses_to_binary() {
+ test_start "pgsql.update_v6_address_to_binary"
+
+ # Let's wipe the whole database
+ pgsql_wipe
+
+ # Initialize database to schema 1.0.
+ pgsql_execute_script "@abs_top_srcdir@/src/bin/admin/tests/dhcpdb_create_1.0.pgsql"
+ assert_eq 0 "${EXIT_CODE}" "cannot initialize 1.0 database, expected exit code: %d, actual: %d"
+
+ # Now upgrade to schema 16.0
+ pgsql_upgrade_schema_to_version 16.0
+
+ sql=\
+"insert into lease6 (address, lease_type, subnet_id) values('2601:19e:8100:1e10:b1b:51a8:f616:cf14', 1, 1);
+insert into lease6 (address, lease_type, subnet_id) values('2601:19e:8100:1e10:b1b:51a8:f616:cf15', 1, 1);"
+
+ run_statement "insert v6 leases" "$sql"
+
+ # Insert ipv6_reservations address is binary.
+ sql=\
+"insert into hosts(host_id, dhcp_identifier, dhcp_identifier_type) values (18219, '18219', 1); \
+insert into ipv6_reservations (address, prefix_len, type, dhcp6_iaid, host_id) \
+ values ('2601:19e:8100:1e10:b1b:51a8:f616:cf16', 128, 1, 123, 18219);"
+
+ run_statement "insert an ipv6 reservation" "$sql"
+
+ # Let's upgrade it to the latest version.
+ run_command \
+ "${kea_admin}" db-upgrade pgsql -u "${db_user}" -p "${db_password}" -n "${db_name}" -d "${db_scripts_dir}"
+
+ # leases count for declined state should be 1 with DUID updated (0x000000)
+ qry="select count(*) from lease6 where address = cast('2601:19e:8100:1e10:b1b:51a8:f616:cf14' as inet);"
+ run_statement "#2" "$qry" 1
+
+ # leases count for non declined state should be 1 with DUID unchanged (0x323033)
+ qry="select count(*) from lease6 where address = cast('2601:19e:8100:1e10:b1b:51a8:f616:cf15' as inet);"
+ run_statement "#3" "$qry" 1
+
+ # verify the reservation is intact
+ qry="select host(address) from ipv6_reservations where host_id = 18219;"
+ run_statement "ipv6_reservations_insert" "$qry" "2601:19e:8100:1e10:b1b:51a8:f616:cf16"
+
+ # Let's wipe the whole database
+ pgsql_wipe
+
+ test_finish 0
+}
+
+# Run tests.
+pgsql_db_init_test
+pgsql_db_version_test
+pgsql_upgrade_test
+pgsql_lease4_dump_test
+pgsql_lease4_dump_test -y
+pgsql_lease6_dump_test
+pgsql_lease6_dump_test -y
+pgsql_lease4_upload_test
+pgsql_lease4_upload_test -y
+pgsql_lease6_upload_test
+pgsql_lease6_upload_test -y
+pgsql_lease4_stat_test
+pgsql_lease6_stat_test
+pgsql_lease_stat_upgrade_test
+pgsql_lease_stat_recount_test
+pgsql_unused_subnet_id_test
+pgsql_update_empty_duid_test
+pgsql_update_v6_addresses_to_binary