diff options
Diffstat (limited to 'src/lib/hooks')
66 files changed, 17203 insertions, 0 deletions
diff --git a/src/lib/hooks/Makefile.am b/src/lib/hooks/Makefile.am new file mode 100644 index 0000000..da8c7a7 --- /dev/null +++ b/src/lib/hooks/Makefile.am @@ -0,0 +1,104 @@ +SUBDIRS = . tests + +AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib +AM_CPPFLAGS += $(BOOST_INCLUDES) +AM_CXXFLAGS = $(KEA_CXXFLAGS) + +# Ensure that the message file is included in the distribution +EXTRA_DIST = hooks_messages.mes + +# Include developer's guide files +EXTRA_DIST += hooks_user.dox hooks_maintenance.dox hooks_component_developer.dox + +# Include images used in Developer's guide +EXTRA_DIST += images/DataScopeArgument.dia images/DataScopeArgument.png +EXTRA_DIST += images/DataScopeContext.dia images/DataScopeContext.png +EXTRA_DIST += images/HooksUml.dia images/HooksUml.png + +CLEANFILES = *.gcno *.gcda + +lib_LTLIBRARIES = libkea-hooks.la +libkea_hooks_la_SOURCES = +libkea_hooks_la_SOURCES += callout_handle.cc callout_handle.h +libkea_hooks_la_SOURCES += callout_handle_associate.cc callout_handle_associate.h +libkea_hooks_la_SOURCES += callout_manager.cc callout_manager.h +libkea_hooks_la_SOURCES += hooks.h +libkea_hooks_la_SOURCES += hooks_log.cc hooks_log.h +libkea_hooks_la_SOURCES += hooks_manager.cc hooks_manager.h +libkea_hooks_la_SOURCES += hooks_config.cc hooks_config.h +libkea_hooks_la_SOURCES += hooks_parser.cc hooks_parser.h +libkea_hooks_la_SOURCES += libinfo.cc libinfo.h +libkea_hooks_la_SOURCES += library_handle.cc library_handle.h +libkea_hooks_la_SOURCES += library_manager.cc library_manager.h +libkea_hooks_la_SOURCES += library_manager_collection.cc library_manager_collection.h +libkea_hooks_la_SOURCES += parking_lots.h +libkea_hooks_la_SOURCES += pointer_converter.h +libkea_hooks_la_SOURCES += server_hooks.cc server_hooks.h +libkea_hooks_la_SOURCES += hooks_messages.cc hooks_messages.h + +libkea_hooks_la_CXXFLAGS = $(AM_CXXFLAGS) +libkea_hooks_la_CPPFLAGS = $(AM_CPPFLAGS) +libkea_hooks_la_LDFLAGS = $(AM_LDFLAGS) -no-undefined -version-info 57:0:0 +libkea_hooks_la_LIBADD = +libkea_hooks_la_LIBADD += $(top_builddir)/src/lib/cc/libkea-cc.la +libkea_hooks_la_LIBADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la +libkea_hooks_la_LIBADD += $(top_builddir)/src/lib/log/libkea-log.la +libkea_hooks_la_LIBADD += $(top_builddir)/src/lib/util/libkea-util.la +libkea_hooks_la_LIBADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la +libkea_hooks_la_LIBADD += $(BOOST_LIBS) +libkea_hooks_la_LIBADD += $(LOG4CPLUS_LIBS) + +# If we want to get rid of all generated messages files, we need to use +# make maintainer-clean. The proper way to introduce custom commands for +# that operation is to define maintainer-clean-local target. However, +# make maintainer-clean also removes Makefile, so running configure script +# is required. To make it easy to rebuild messages without going through +# reconfigure, a new target messages-clean has been added. +maintainer-clean-local: + rm -f hooks_messages.h hooks_messages.cc + +# To regenerate messages files, one can do: +# +# make messages-clean +# make messages +# +# This is needed only when a .mes file is modified. +messages-clean: maintainer-clean-local + +if GENERATE_MESSAGES + +# Define rule to build logging source files from message file +messages: hooks_messages.h hooks_messages.cc + @echo Message files regenerated + +hooks_messages.h hooks_messages.cc: hooks_messages.mes + $(top_builddir)/src/lib/log/compiler/kea-msg-compiler $(top_srcdir)/src/lib/hooks/hooks_messages.mes + +else + +messages hooks_messages.h hooks_messages.cc: + @echo Messages generation disabled. Configure with --enable-generate-messages to enable it. + +endif + +# Specify the headers for copying into the installation directory tree. +libkea_hooks_includedir = $(pkgincludedir)/hooks +libkea_hooks_include_HEADERS = \ + callout_handle.h \ + callout_handle_associate.h \ + callout_manager.h \ + hooks.h \ + hooks_config.h \ + hooks_log.h \ + hooks_manager.h \ + hooks_messages.h \ + hooks_parser.h \ + libinfo.h \ + library_handle.h \ + library_manager.h \ + library_manager_collection.h \ + parking_lots.h \ + pointer_converter.h \ + server_hooks.h + + diff --git a/src/lib/hooks/Makefile.in b/src/lib/hooks/Makefile.in new file mode 100644 index 0000000..8f6d546 --- /dev/null +++ b/src/lib/hooks/Makefile.in @@ -0,0 +1,1140 @@ +# 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/lib/hooks +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \ + $(top_srcdir)/m4macros/ax_cpp11.m4 \ + $(top_srcdir)/m4macros/ax_crypto.m4 \ + $(top_srcdir)/m4macros/ax_find_library.m4 \ + $(top_srcdir)/m4macros/ax_gssapi.m4 \ + $(top_srcdir)/m4macros/ax_gtest.m4 \ + $(top_srcdir)/m4macros/ax_isc_rpath.m4 \ + $(top_srcdir)/m4macros/ax_sysrepo.m4 \ + $(top_srcdir)/m4macros/libtool.m4 \ + $(top_srcdir)/m4macros/ltoptions.m4 \ + $(top_srcdir)/m4macros/ltsugar.m4 \ + $(top_srcdir)/m4macros/ltversion.m4 \ + $(top_srcdir)/m4macros/lt~obsolete.m4 \ + $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(libkea_hooks_include_HEADERS) \ + $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +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)$(libdir)" \ + "$(DESTDIR)$(libkea_hooks_includedir)" +LTLIBRARIES = $(lib_LTLIBRARIES) +am__DEPENDENCIES_1 = +libkea_hooks_la_DEPENDENCIES = \ + $(top_builddir)/src/lib/cc/libkea-cc.la \ + $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \ + $(top_builddir)/src/lib/log/libkea-log.la \ + $(top_builddir)/src/lib/util/libkea-util.la \ + $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) +am_libkea_hooks_la_OBJECTS = libkea_hooks_la-callout_handle.lo \ + libkea_hooks_la-callout_handle_associate.lo \ + libkea_hooks_la-callout_manager.lo \ + libkea_hooks_la-hooks_log.lo libkea_hooks_la-hooks_manager.lo \ + libkea_hooks_la-hooks_config.lo \ + libkea_hooks_la-hooks_parser.lo libkea_hooks_la-libinfo.lo \ + libkea_hooks_la-library_handle.lo \ + libkea_hooks_la-library_manager.lo \ + libkea_hooks_la-library_manager_collection.lo \ + libkea_hooks_la-server_hooks.lo \ + libkea_hooks_la-hooks_messages.lo +libkea_hooks_la_OBJECTS = $(am_libkea_hooks_la_OBJECTS) +AM_V_lt = $(am__v_lt_@AM_V@) +am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) +am__v_lt_0 = --silent +am__v_lt_1 = +libkea_hooks_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \ + $(libkea_hooks_la_CXXFLAGS) $(CXXFLAGS) \ + $(libkea_hooks_la_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__maybe_remake_depfiles = depfiles +am__depfiles_remade = ./$(DEPDIR)/libkea_hooks_la-callout_handle.Plo \ + ./$(DEPDIR)/libkea_hooks_la-callout_handle_associate.Plo \ + ./$(DEPDIR)/libkea_hooks_la-callout_manager.Plo \ + ./$(DEPDIR)/libkea_hooks_la-hooks_config.Plo \ + ./$(DEPDIR)/libkea_hooks_la-hooks_log.Plo \ + ./$(DEPDIR)/libkea_hooks_la-hooks_manager.Plo \ + ./$(DEPDIR)/libkea_hooks_la-hooks_messages.Plo \ + ./$(DEPDIR)/libkea_hooks_la-hooks_parser.Plo \ + ./$(DEPDIR)/libkea_hooks_la-libinfo.Plo \ + ./$(DEPDIR)/libkea_hooks_la-library_handle.Plo \ + ./$(DEPDIR)/libkea_hooks_la-library_manager.Plo \ + ./$(DEPDIR)/libkea_hooks_la-library_manager_collection.Plo \ + ./$(DEPDIR)/libkea_hooks_la-server_hooks.Plo +am__mv = mv -f +CXXCOMPILE = $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \ + $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) +LTCXXCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CXXFLAGS) $(CXXFLAGS) +AM_V_CXX = $(am__v_CXX_@AM_V@) +am__v_CXX_ = $(am__v_CXX_@AM_DEFAULT_V@) +am__v_CXX_0 = @echo " CXX " $@; +am__v_CXX_1 = +CXXLD = $(CXX) +CXXLINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(AM_CXXFLAGS) \ + $(CXXFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CXXLD = $(am__v_CXXLD_@AM_V@) +am__v_CXXLD_ = $(am__v_CXXLD_@AM_DEFAULT_V@) +am__v_CXXLD_0 = @echo " CXXLD " $@; +am__v_CXXLD_1 = +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_@AM_V@) +am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) +am__v_CC_0 = @echo " CC " $@; +am__v_CC_1 = +CCLD = $(CC) +LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_@AM_V@) +am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) +am__v_CCLD_0 = @echo " CCLD " $@; +am__v_CCLD_1 = +SOURCES = $(libkea_hooks_la_SOURCES) +DIST_SOURCES = $(libkea_hooks_la_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 +HEADERS = $(libkea_hooks_include_HEADERS) +RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \ + distclean-recursive maintainer-clean-recursive +am__recursive_targets = \ + $(RECURSIVE_TARGETS) \ + $(RECURSIVE_CLEAN_TARGETS) \ + $(am__extra_recursive_targets) +AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \ + distdir distdir-am +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +DIST_SUBDIRS = $(SUBDIRS) +am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +am__relativize = \ + dir0=`pwd`; \ + sed_first='s,^\([^/]*\)/.*$$,\1,'; \ + sed_rest='s,^[^/]*/*,,'; \ + sed_last='s,^.*/\([^/]*\)$$,\1,'; \ + sed_butlast='s,/*[^/]*$$,,'; \ + while test -n "$$dir1"; do \ + first=`echo "$$dir1" | sed -e "$$sed_first"`; \ + if test "$$first" != "."; then \ + if test "$$first" = ".."; then \ + dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \ + dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \ + else \ + first2=`echo "$$dir2" | sed -e "$$sed_first"`; \ + if test "$$first2" = "$$first"; then \ + dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \ + else \ + dir2="../$$dir2"; \ + fi; \ + dir0="$$dir0"/"$$first"; \ + fi; \ + fi; \ + dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \ + done; \ + reldir="$$dir2" +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AR = @AR@ +ASCIIDOC = @ASCIIDOC@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BOOST_INCLUDES = @BOOST_INCLUDES@ +BOOST_LIBS = @BOOST_LIBS@ +BOTAN_TOOL = @BOTAN_TOOL@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +CONTRIB_DIR = @CONTRIB_DIR@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CRYPTO_CFLAGS = @CRYPTO_CFLAGS@ +CRYPTO_INCLUDES = @CRYPTO_INCLUDES@ +CRYPTO_LDFLAGS = @CRYPTO_LDFLAGS@ +CRYPTO_LIBS = @CRYPTO_LIBS@ +CRYPTO_PACKAGE = @CRYPTO_PACKAGE@ +CRYPTO_RPATH = @CRYPTO_RPATH@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DISTCHECK_BOOST_CONFIGURE_FLAG = @DISTCHECK_BOOST_CONFIGURE_FLAG@ +DISTCHECK_CONTRIB_CONFIGURE_FLAG = @DISTCHECK_CONTRIB_CONFIGURE_FLAG@ +DISTCHECK_CRYPTO_CONFIGURE_FLAG = @DISTCHECK_CRYPTO_CONFIGURE_FLAG@ +DISTCHECK_GTEST_CONFIGURE_FLAG = @DISTCHECK_GTEST_CONFIGURE_FLAG@ +DISTCHECK_KEA_SHELL_CONFIGURE_FLAG = @DISTCHECK_KEA_SHELL_CONFIGURE_FLAG@ +DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG = @DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG@ +DISTCHECK_PERFDHCP_CONFIGURE_FLAG = @DISTCHECK_PERFDHCP_CONFIGURE_FLAG@ +DISTCHECK_PREMIUM_CONFIGURE_FLAG = @DISTCHECK_PREMIUM_CONFIGURE_FLAG@ +DLLTOOL = @DLLTOOL@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +GENHTML = @GENHTML@ +GREP = @GREP@ +GSSAPI_CFLAGS = @GSSAPI_CFLAGS@ +GSSAPI_LIBS = @GSSAPI_LIBS@ +GTEST_CONFIG = @GTEST_CONFIG@ +GTEST_INCLUDES = @GTEST_INCLUDES@ +GTEST_LDADD = @GTEST_LDADD@ +GTEST_LDFLAGS = @GTEST_LDFLAGS@ +GTEST_SOURCE = @GTEST_SOURCE@ +HAVE_SYSREPO = @HAVE_SYSREPO@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +KEA_CXXFLAGS = @KEA_CXXFLAGS@ +KEA_SRCID = @KEA_SRCID@ +KRB5_CONFIG = @KRB5_CONFIG@ +LCOV = @LCOV@ +LD = @LD@ +LDFLAGS = @LDFLAGS@ +LEX = @LEX@ +LEXLIB = @LEXLIB@ +LEX_OUTPUT_ROOT = @LEX_OUTPUT_ROOT@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBTOOL = @LIBTOOL@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LOG4CPLUS_INCLUDES = @LOG4CPLUS_INCLUDES@ +LOG4CPLUS_LIBS = @LOG4CPLUS_LIBS@ +LTLIBOBJS = @LTLIBOBJS@ +LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +MYSQL_CPPFLAGS = @MYSQL_CPPFLAGS@ +MYSQL_LIBS = @MYSQL_LIBS@ +NM = @NM@ +NMEDIT = @NMEDIT@ +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PACKAGE_VERSION_TYPE = @PACKAGE_VERSION_TYPE@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PDFLATEX = @PDFLATEX@ +PERL = @PERL@ +PGSQL_CPPFLAGS = @PGSQL_CPPFLAGS@ +PGSQL_LIBS = @PGSQL_LIBS@ +PKGPYTHONDIR = @PKGPYTHONDIR@ +PKG_CONFIG = @PKG_CONFIG@ +PLANTUML = @PLANTUML@ +PREMIUM_DIR = @PREMIUM_DIR@ +PYTHON = @PYTHON@ +PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@ +PYTHON_PLATFORM = @PYTHON_PLATFORM@ +PYTHON_PREFIX = @PYTHON_PREFIX@ +PYTHON_VERSION = @PYTHON_VERSION@ +RANLIB = @RANLIB@ +SED = @SED@ +SEP = @SEP@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +SPHINXBUILD = @SPHINXBUILD@ +SRPD_PLUGINS_PATH = @SRPD_PLUGINS_PATH@ +SR_REPO_PATH = @SR_REPO_PATH@ +STRIP = @STRIP@ +SYSREPOCPP_VERSION = @SYSREPOCPP_VERSION@ +SYSREPO_CPPFLAGS = @SYSREPO_CPPFLAGS@ +SYSREPO_INCLUDEDIR = @SYSREPO_INCLUDEDIR@ +SYSREPO_LIBS = @SYSREPO_LIBS@ +SYSREPO_VERSION = @SYSREPO_VERSION@ +USE_LCOV = @USE_LCOV@ +VALGRIND = @VALGRIND@ +VERSION = @VERSION@ +WARNING_GCC_44_STRICT_ALIASING_CFLAG = @WARNING_GCC_44_STRICT_ALIASING_CFLAG@ +YACC = @YACC@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +pkgpyexecdir = @pkgpyexecdir@ +pkgpythondir = @pkgpythondir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +pyexecdir = @pyexecdir@ +pythondir = @pythondir@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +SUBDIRS = . tests +AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib \ + $(BOOST_INCLUDES) +AM_CXXFLAGS = $(KEA_CXXFLAGS) + +# Ensure that the message file is included in the distribution + +# Include developer's guide files + +# Include images used in Developer's guide +EXTRA_DIST = hooks_messages.mes hooks_user.dox hooks_maintenance.dox \ + hooks_component_developer.dox images/DataScopeArgument.dia \ + images/DataScopeArgument.png images/DataScopeContext.dia \ + images/DataScopeContext.png images/HooksUml.dia \ + images/HooksUml.png +CLEANFILES = *.gcno *.gcda +lib_LTLIBRARIES = libkea-hooks.la +libkea_hooks_la_SOURCES = callout_handle.cc callout_handle.h \ + callout_handle_associate.cc callout_handle_associate.h \ + callout_manager.cc callout_manager.h hooks.h hooks_log.cc \ + hooks_log.h hooks_manager.cc hooks_manager.h hooks_config.cc \ + hooks_config.h hooks_parser.cc hooks_parser.h libinfo.cc \ + libinfo.h library_handle.cc library_handle.h \ + library_manager.cc library_manager.h \ + library_manager_collection.cc library_manager_collection.h \ + parking_lots.h pointer_converter.h server_hooks.cc \ + server_hooks.h hooks_messages.cc hooks_messages.h +libkea_hooks_la_CXXFLAGS = $(AM_CXXFLAGS) +libkea_hooks_la_CPPFLAGS = $(AM_CPPFLAGS) +libkea_hooks_la_LDFLAGS = $(AM_LDFLAGS) -no-undefined -version-info 57:0:0 +libkea_hooks_la_LIBADD = $(top_builddir)/src/lib/cc/libkea-cc.la \ + $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \ + $(top_builddir)/src/lib/log/libkea-log.la \ + $(top_builddir)/src/lib/util/libkea-util.la \ + $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \ + $(BOOST_LIBS) $(LOG4CPLUS_LIBS) + +# Specify the headers for copying into the installation directory tree. +libkea_hooks_includedir = $(pkgincludedir)/hooks +libkea_hooks_include_HEADERS = \ + callout_handle.h \ + callout_handle_associate.h \ + callout_manager.h \ + hooks.h \ + hooks_config.h \ + hooks_log.h \ + hooks_manager.h \ + hooks_messages.h \ + hooks_parser.h \ + libinfo.h \ + library_handle.h \ + library_manager.h \ + library_manager_collection.h \ + parking_lots.h \ + pointer_converter.h \ + server_hooks.h + +all: all-recursive + +.SUFFIXES: +.SUFFIXES: .cc .lo .o .obj +$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/lib/hooks/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign src/lib/hooks/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): + +install-libLTLIBRARIES: $(lib_LTLIBRARIES) + @$(NORMAL_INSTALL) + @list='$(lib_LTLIBRARIES)'; test -n "$(libdir)" || list=; \ + list2=; for p in $$list; do \ + if test -f $$p; then \ + list2="$$list2 $$p"; \ + else :; fi; \ + done; \ + test -z "$$list2" || { \ + echo " $(MKDIR_P) '$(DESTDIR)$(libdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(libdir)" || exit 1; \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(libdir)'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(libdir)"; \ + } + +uninstall-libLTLIBRARIES: + @$(NORMAL_UNINSTALL) + @list='$(lib_LTLIBRARIES)'; test -n "$(libdir)" || list=; \ + for p in $$list; do \ + $(am__strip_dir) \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(libdir)/$$f'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(libdir)/$$f"; \ + done + +clean-libLTLIBRARIES: + -test -z "$(lib_LTLIBRARIES)" || rm -f $(lib_LTLIBRARIES) + @list='$(lib_LTLIBRARIES)'; \ + locs=`for p in $$list; do echo $$p; done | \ + sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ + sort -u`; \ + test -z "$$locs" || { \ + echo rm -f $${locs}; \ + rm -f $${locs}; \ + } + +libkea-hooks.la: $(libkea_hooks_la_OBJECTS) $(libkea_hooks_la_DEPENDENCIES) $(EXTRA_libkea_hooks_la_DEPENDENCIES) + $(AM_V_CXXLD)$(libkea_hooks_la_LINK) -rpath $(libdir) $(libkea_hooks_la_OBJECTS) $(libkea_hooks_la_LIBADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_hooks_la-callout_handle.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_hooks_la-callout_handle_associate.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_hooks_la-callout_manager.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_hooks_la-hooks_config.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_hooks_la-hooks_log.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_hooks_la-hooks_manager.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_hooks_la-hooks_messages.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_hooks_la-hooks_parser.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_hooks_la-libinfo.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_hooks_la-library_handle.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_hooks_la-library_manager.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_hooks_la-library_manager_collection.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_hooks_la-server_hooks.Plo@am__quote@ # am--include-marker + +$(am__depfiles_remade): + @$(MKDIR_P) $(@D) + @echo '# dummy' >$@-t && $(am__mv) $@-t $@ + +am--depfiles: $(am__depfiles_remade) + +.cc.o: +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ $< + +.cc.obj: +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +.cc.lo: +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LTCXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LTCXXCOMPILE) -c -o $@ $< + +libkea_hooks_la-callout_handle.lo: callout_handle.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_hooks_la_CPPFLAGS) $(CPPFLAGS) $(libkea_hooks_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_hooks_la-callout_handle.lo -MD -MP -MF $(DEPDIR)/libkea_hooks_la-callout_handle.Tpo -c -o libkea_hooks_la-callout_handle.lo `test -f 'callout_handle.cc' || echo '$(srcdir)/'`callout_handle.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_hooks_la-callout_handle.Tpo $(DEPDIR)/libkea_hooks_la-callout_handle.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='callout_handle.cc' object='libkea_hooks_la-callout_handle.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_hooks_la_CPPFLAGS) $(CPPFLAGS) $(libkea_hooks_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_hooks_la-callout_handle.lo `test -f 'callout_handle.cc' || echo '$(srcdir)/'`callout_handle.cc + +libkea_hooks_la-callout_handle_associate.lo: callout_handle_associate.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_hooks_la_CPPFLAGS) $(CPPFLAGS) $(libkea_hooks_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_hooks_la-callout_handle_associate.lo -MD -MP -MF $(DEPDIR)/libkea_hooks_la-callout_handle_associate.Tpo -c -o libkea_hooks_la-callout_handle_associate.lo `test -f 'callout_handle_associate.cc' || echo '$(srcdir)/'`callout_handle_associate.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_hooks_la-callout_handle_associate.Tpo $(DEPDIR)/libkea_hooks_la-callout_handle_associate.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='callout_handle_associate.cc' object='libkea_hooks_la-callout_handle_associate.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_hooks_la_CPPFLAGS) $(CPPFLAGS) $(libkea_hooks_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_hooks_la-callout_handle_associate.lo `test -f 'callout_handle_associate.cc' || echo '$(srcdir)/'`callout_handle_associate.cc + +libkea_hooks_la-callout_manager.lo: callout_manager.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_hooks_la_CPPFLAGS) $(CPPFLAGS) $(libkea_hooks_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_hooks_la-callout_manager.lo -MD -MP -MF $(DEPDIR)/libkea_hooks_la-callout_manager.Tpo -c -o libkea_hooks_la-callout_manager.lo `test -f 'callout_manager.cc' || echo '$(srcdir)/'`callout_manager.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_hooks_la-callout_manager.Tpo $(DEPDIR)/libkea_hooks_la-callout_manager.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='callout_manager.cc' object='libkea_hooks_la-callout_manager.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_hooks_la_CPPFLAGS) $(CPPFLAGS) $(libkea_hooks_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_hooks_la-callout_manager.lo `test -f 'callout_manager.cc' || echo '$(srcdir)/'`callout_manager.cc + +libkea_hooks_la-hooks_log.lo: hooks_log.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_hooks_la_CPPFLAGS) $(CPPFLAGS) $(libkea_hooks_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_hooks_la-hooks_log.lo -MD -MP -MF $(DEPDIR)/libkea_hooks_la-hooks_log.Tpo -c -o libkea_hooks_la-hooks_log.lo `test -f 'hooks_log.cc' || echo '$(srcdir)/'`hooks_log.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_hooks_la-hooks_log.Tpo $(DEPDIR)/libkea_hooks_la-hooks_log.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='hooks_log.cc' object='libkea_hooks_la-hooks_log.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_hooks_la_CPPFLAGS) $(CPPFLAGS) $(libkea_hooks_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_hooks_la-hooks_log.lo `test -f 'hooks_log.cc' || echo '$(srcdir)/'`hooks_log.cc + +libkea_hooks_la-hooks_manager.lo: hooks_manager.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_hooks_la_CPPFLAGS) $(CPPFLAGS) $(libkea_hooks_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_hooks_la-hooks_manager.lo -MD -MP -MF $(DEPDIR)/libkea_hooks_la-hooks_manager.Tpo -c -o libkea_hooks_la-hooks_manager.lo `test -f 'hooks_manager.cc' || echo '$(srcdir)/'`hooks_manager.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_hooks_la-hooks_manager.Tpo $(DEPDIR)/libkea_hooks_la-hooks_manager.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='hooks_manager.cc' object='libkea_hooks_la-hooks_manager.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_hooks_la_CPPFLAGS) $(CPPFLAGS) $(libkea_hooks_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_hooks_la-hooks_manager.lo `test -f 'hooks_manager.cc' || echo '$(srcdir)/'`hooks_manager.cc + +libkea_hooks_la-hooks_config.lo: hooks_config.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_hooks_la_CPPFLAGS) $(CPPFLAGS) $(libkea_hooks_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_hooks_la-hooks_config.lo -MD -MP -MF $(DEPDIR)/libkea_hooks_la-hooks_config.Tpo -c -o libkea_hooks_la-hooks_config.lo `test -f 'hooks_config.cc' || echo '$(srcdir)/'`hooks_config.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_hooks_la-hooks_config.Tpo $(DEPDIR)/libkea_hooks_la-hooks_config.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='hooks_config.cc' object='libkea_hooks_la-hooks_config.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_hooks_la_CPPFLAGS) $(CPPFLAGS) $(libkea_hooks_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_hooks_la-hooks_config.lo `test -f 'hooks_config.cc' || echo '$(srcdir)/'`hooks_config.cc + +libkea_hooks_la-hooks_parser.lo: hooks_parser.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_hooks_la_CPPFLAGS) $(CPPFLAGS) $(libkea_hooks_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_hooks_la-hooks_parser.lo -MD -MP -MF $(DEPDIR)/libkea_hooks_la-hooks_parser.Tpo -c -o libkea_hooks_la-hooks_parser.lo `test -f 'hooks_parser.cc' || echo '$(srcdir)/'`hooks_parser.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_hooks_la-hooks_parser.Tpo $(DEPDIR)/libkea_hooks_la-hooks_parser.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='hooks_parser.cc' object='libkea_hooks_la-hooks_parser.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_hooks_la_CPPFLAGS) $(CPPFLAGS) $(libkea_hooks_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_hooks_la-hooks_parser.lo `test -f 'hooks_parser.cc' || echo '$(srcdir)/'`hooks_parser.cc + +libkea_hooks_la-libinfo.lo: libinfo.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_hooks_la_CPPFLAGS) $(CPPFLAGS) $(libkea_hooks_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_hooks_la-libinfo.lo -MD -MP -MF $(DEPDIR)/libkea_hooks_la-libinfo.Tpo -c -o libkea_hooks_la-libinfo.lo `test -f 'libinfo.cc' || echo '$(srcdir)/'`libinfo.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_hooks_la-libinfo.Tpo $(DEPDIR)/libkea_hooks_la-libinfo.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='libinfo.cc' object='libkea_hooks_la-libinfo.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_hooks_la_CPPFLAGS) $(CPPFLAGS) $(libkea_hooks_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_hooks_la-libinfo.lo `test -f 'libinfo.cc' || echo '$(srcdir)/'`libinfo.cc + +libkea_hooks_la-library_handle.lo: library_handle.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_hooks_la_CPPFLAGS) $(CPPFLAGS) $(libkea_hooks_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_hooks_la-library_handle.lo -MD -MP -MF $(DEPDIR)/libkea_hooks_la-library_handle.Tpo -c -o libkea_hooks_la-library_handle.lo `test -f 'library_handle.cc' || echo '$(srcdir)/'`library_handle.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_hooks_la-library_handle.Tpo $(DEPDIR)/libkea_hooks_la-library_handle.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='library_handle.cc' object='libkea_hooks_la-library_handle.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_hooks_la_CPPFLAGS) $(CPPFLAGS) $(libkea_hooks_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_hooks_la-library_handle.lo `test -f 'library_handle.cc' || echo '$(srcdir)/'`library_handle.cc + +libkea_hooks_la-library_manager.lo: library_manager.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_hooks_la_CPPFLAGS) $(CPPFLAGS) $(libkea_hooks_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_hooks_la-library_manager.lo -MD -MP -MF $(DEPDIR)/libkea_hooks_la-library_manager.Tpo -c -o libkea_hooks_la-library_manager.lo `test -f 'library_manager.cc' || echo '$(srcdir)/'`library_manager.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_hooks_la-library_manager.Tpo $(DEPDIR)/libkea_hooks_la-library_manager.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='library_manager.cc' object='libkea_hooks_la-library_manager.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_hooks_la_CPPFLAGS) $(CPPFLAGS) $(libkea_hooks_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_hooks_la-library_manager.lo `test -f 'library_manager.cc' || echo '$(srcdir)/'`library_manager.cc + +libkea_hooks_la-library_manager_collection.lo: library_manager_collection.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_hooks_la_CPPFLAGS) $(CPPFLAGS) $(libkea_hooks_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_hooks_la-library_manager_collection.lo -MD -MP -MF $(DEPDIR)/libkea_hooks_la-library_manager_collection.Tpo -c -o libkea_hooks_la-library_manager_collection.lo `test -f 'library_manager_collection.cc' || echo '$(srcdir)/'`library_manager_collection.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_hooks_la-library_manager_collection.Tpo $(DEPDIR)/libkea_hooks_la-library_manager_collection.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='library_manager_collection.cc' object='libkea_hooks_la-library_manager_collection.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_hooks_la_CPPFLAGS) $(CPPFLAGS) $(libkea_hooks_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_hooks_la-library_manager_collection.lo `test -f 'library_manager_collection.cc' || echo '$(srcdir)/'`library_manager_collection.cc + +libkea_hooks_la-server_hooks.lo: server_hooks.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_hooks_la_CPPFLAGS) $(CPPFLAGS) $(libkea_hooks_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_hooks_la-server_hooks.lo -MD -MP -MF $(DEPDIR)/libkea_hooks_la-server_hooks.Tpo -c -o libkea_hooks_la-server_hooks.lo `test -f 'server_hooks.cc' || echo '$(srcdir)/'`server_hooks.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_hooks_la-server_hooks.Tpo $(DEPDIR)/libkea_hooks_la-server_hooks.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='server_hooks.cc' object='libkea_hooks_la-server_hooks.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_hooks_la_CPPFLAGS) $(CPPFLAGS) $(libkea_hooks_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_hooks_la-server_hooks.lo `test -f 'server_hooks.cc' || echo '$(srcdir)/'`server_hooks.cc + +libkea_hooks_la-hooks_messages.lo: hooks_messages.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_hooks_la_CPPFLAGS) $(CPPFLAGS) $(libkea_hooks_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_hooks_la-hooks_messages.lo -MD -MP -MF $(DEPDIR)/libkea_hooks_la-hooks_messages.Tpo -c -o libkea_hooks_la-hooks_messages.lo `test -f 'hooks_messages.cc' || echo '$(srcdir)/'`hooks_messages.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_hooks_la-hooks_messages.Tpo $(DEPDIR)/libkea_hooks_la-hooks_messages.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='hooks_messages.cc' object='libkea_hooks_la-hooks_messages.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_hooks_la_CPPFLAGS) $(CPPFLAGS) $(libkea_hooks_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_hooks_la-hooks_messages.lo `test -f 'hooks_messages.cc' || echo '$(srcdir)/'`hooks_messages.cc + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs +install-libkea_hooks_includeHEADERS: $(libkea_hooks_include_HEADERS) + @$(NORMAL_INSTALL) + @list='$(libkea_hooks_include_HEADERS)'; test -n "$(libkea_hooks_includedir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(libkea_hooks_includedir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(libkea_hooks_includedir)" || 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_HEADER) $$files '$(DESTDIR)$(libkea_hooks_includedir)'"; \ + $(INSTALL_HEADER) $$files "$(DESTDIR)$(libkea_hooks_includedir)" || exit $$?; \ + done + +uninstall-libkea_hooks_includeHEADERS: + @$(NORMAL_UNINSTALL) + @list='$(libkea_hooks_include_HEADERS)'; test -n "$(libkea_hooks_includedir)" || list=; \ + files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ + dir='$(DESTDIR)$(libkea_hooks_includedir)'; $(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 $(LTLIBRARIES) $(HEADERS) +installdirs: installdirs-recursive +installdirs-am: + for dir in "$(DESTDIR)$(libdir)" "$(DESTDIR)$(libkea_hooks_includedir)"; 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: + -test -z "$(CLEANFILES)" || rm -f $(CLEANFILES) + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-recursive + +clean-am: clean-generic clean-libLTLIBRARIES clean-libtool \ + mostlyclean-am + +distclean: distclean-recursive + -rm -f ./$(DEPDIR)/libkea_hooks_la-callout_handle.Plo + -rm -f ./$(DEPDIR)/libkea_hooks_la-callout_handle_associate.Plo + -rm -f ./$(DEPDIR)/libkea_hooks_la-callout_manager.Plo + -rm -f ./$(DEPDIR)/libkea_hooks_la-hooks_config.Plo + -rm -f ./$(DEPDIR)/libkea_hooks_la-hooks_log.Plo + -rm -f ./$(DEPDIR)/libkea_hooks_la-hooks_manager.Plo + -rm -f ./$(DEPDIR)/libkea_hooks_la-hooks_messages.Plo + -rm -f ./$(DEPDIR)/libkea_hooks_la-hooks_parser.Plo + -rm -f ./$(DEPDIR)/libkea_hooks_la-libinfo.Plo + -rm -f ./$(DEPDIR)/libkea_hooks_la-library_handle.Plo + -rm -f ./$(DEPDIR)/libkea_hooks_la-library_manager.Plo + -rm -f ./$(DEPDIR)/libkea_hooks_la-library_manager_collection.Plo + -rm -f ./$(DEPDIR)/libkea_hooks_la-server_hooks.Plo + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-recursive + +dvi-am: + +html: html-recursive + +html-am: + +info: info-recursive + +info-am: + +install-data-am: install-libkea_hooks_includeHEADERS + +install-dvi: install-dvi-recursive + +install-dvi-am: + +install-exec-am: install-libLTLIBRARIES + +install-html: install-html-recursive + +install-html-am: + +install-info: install-info-recursive + +install-info-am: + +install-man: + +install-pdf: install-pdf-recursive + +install-pdf-am: + +install-ps: install-ps-recursive + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-recursive + -rm -f ./$(DEPDIR)/libkea_hooks_la-callout_handle.Plo + -rm -f ./$(DEPDIR)/libkea_hooks_la-callout_handle_associate.Plo + -rm -f ./$(DEPDIR)/libkea_hooks_la-callout_manager.Plo + -rm -f ./$(DEPDIR)/libkea_hooks_la-hooks_config.Plo + -rm -f ./$(DEPDIR)/libkea_hooks_la-hooks_log.Plo + -rm -f ./$(DEPDIR)/libkea_hooks_la-hooks_manager.Plo + -rm -f ./$(DEPDIR)/libkea_hooks_la-hooks_messages.Plo + -rm -f ./$(DEPDIR)/libkea_hooks_la-hooks_parser.Plo + -rm -f ./$(DEPDIR)/libkea_hooks_la-libinfo.Plo + -rm -f ./$(DEPDIR)/libkea_hooks_la-library_handle.Plo + -rm -f ./$(DEPDIR)/libkea_hooks_la-library_manager.Plo + -rm -f ./$(DEPDIR)/libkea_hooks_la-library_manager_collection.Plo + -rm -f ./$(DEPDIR)/libkea_hooks_la-server_hooks.Plo + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic \ + maintainer-clean-local + +mostlyclean: mostlyclean-recursive + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-recursive + +pdf-am: + +ps: ps-recursive + +ps-am: + +uninstall-am: uninstall-libLTLIBRARIES \ + uninstall-libkea_hooks_includeHEADERS + +.MAKE: $(am__recursive_targets) install-am install-strip + +.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am \ + am--depfiles check check-am clean clean-generic \ + clean-libLTLIBRARIES clean-libtool cscopelist-am ctags \ + ctags-am distclean distclean-compile distclean-generic \ + distclean-libtool distclean-tags distdir dvi dvi-am html \ + html-am info info-am install install-am install-data \ + install-data-am install-dvi install-dvi-am install-exec \ + install-exec-am install-html install-html-am install-info \ + install-info-am install-libLTLIBRARIES \ + install-libkea_hooks_includeHEADERS 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 \ + maintainer-clean-local mostlyclean mostlyclean-compile \ + mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \ + tags tags-am uninstall uninstall-am uninstall-libLTLIBRARIES \ + uninstall-libkea_hooks_includeHEADERS + +.PRECIOUS: Makefile + + +# If we want to get rid of all generated messages files, we need to use +# make maintainer-clean. The proper way to introduce custom commands for +# that operation is to define maintainer-clean-local target. However, +# make maintainer-clean also removes Makefile, so running configure script +# is required. To make it easy to rebuild messages without going through +# reconfigure, a new target messages-clean has been added. +maintainer-clean-local: + rm -f hooks_messages.h hooks_messages.cc + +# To regenerate messages files, one can do: +# +# make messages-clean +# make messages +# +# This is needed only when a .mes file is modified. +messages-clean: maintainer-clean-local + +# Define rule to build logging source files from message file +@GENERATE_MESSAGES_TRUE@messages: hooks_messages.h hooks_messages.cc +@GENERATE_MESSAGES_TRUE@ @echo Message files regenerated + +@GENERATE_MESSAGES_TRUE@hooks_messages.h hooks_messages.cc: hooks_messages.mes +@GENERATE_MESSAGES_TRUE@ $(top_builddir)/src/lib/log/compiler/kea-msg-compiler $(top_srcdir)/src/lib/hooks/hooks_messages.mes + +@GENERATE_MESSAGES_FALSE@messages hooks_messages.h hooks_messages.cc: +@GENERATE_MESSAGES_FALSE@ @echo Messages generation disabled. Configure with --enable-generate-messages to enable it. + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/src/lib/hooks/callout_handle.cc b/src/lib/hooks/callout_handle.cc new file mode 100644 index 0000000..6877b65 --- /dev/null +++ b/src/lib/hooks/callout_handle.cc @@ -0,0 +1,167 @@ +// Copyright (C) 2013-2022 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <hooks/callout_handle.h> +#include <hooks/callout_manager.h> +#include <hooks/library_handle.h> +#include <hooks/server_hooks.h> + +#include <string> +#include <utility> +#include <vector> + +using namespace std; + +namespace isc { +namespace hooks { + +// Constructor. +CalloutHandle::CalloutHandle(const boost::shared_ptr<CalloutManager>& manager, + const boost::shared_ptr<LibraryManagerCollection>& lmcoll) + : lm_collection_(lmcoll), arguments_(), context_collection_(), + manager_(manager), server_hooks_(ServerHooks::getServerHooks()), + current_library_(-1), current_hook_(-1), next_step_(NEXT_STEP_CONTINUE) { + + // Call the "context_create" hook. We should be OK doing this - although + // the constructor has not finished running, all the member variables + // have been created. + manager_->callCallouts(ServerHooks::CONTEXT_CREATE, *this); +} + +// Destructor +CalloutHandle::~CalloutHandle() { + // Call the "context_destroy" hook. We should be OK doing this - although + // the destructor is being called, all the member variables are still in + // existence. + manager_->callCallouts(ServerHooks::CONTEXT_DESTROY, *this); + + // Explicitly clear the argument and context objects. This should free up + // all memory that could have been allocated by libraries that were loaded. + arguments_.clear(); + context_collection_.clear(); + + // Normal destruction of the remaining variables will include the + // destruction of lm_collection_, an action that decrements the reference + // count on the library manager collection (which holds the libraries that + // could have allocated memory in the argument and context members.) When + // that goes to zero, the libraries will be unloaded: at that point nothing + // in the hooks framework will be pointing to memory in the libraries' + // address space. + // + // It is possible that some other data structure in the server (the program + // using the hooks library) still references the address space and attempts + // to access it causing a segmentation fault. That issue is outside the + // scope of this framework and is not addressed by it. +} + +// Return the name of all argument items. + +vector<string> +CalloutHandle::getArgumentNames() const { + vector<string> names; + for (ElementCollection::const_iterator i = arguments_.begin(); + i != arguments_.end(); ++i) { + names.push_back(i->first); + } + + return (names); +} + +ParkingLotHandlePtr +CalloutHandle::getParkingLotHandlePtr() const { + return (boost::make_shared<ParkingLotHandle>(server_hooks_.getParkingLotPtr(current_hook_))); +} + +// Return the context for the currently pointed-to library. This version is +// used by the "setContext()" method and creates a context for the current +// library if it does not exist. + +CalloutHandle::ElementCollection& +CalloutHandle::getContextForLibrary() { + // Access a reference to the element collection for the given index, + // creating a new element collection if necessary, and return it. + return (context_collection_[current_library_]); +} + +// The "const" version of the above, used by the "getContext()" method. If +// the context for the current library doesn't exist, throw an exception. + +const CalloutHandle::ElementCollection& +CalloutHandle::getContextForLibrary() const { + auto libcontext = context_collection_.find(current_library_); + if (libcontext == context_collection_.end()) { + isc_throw(NoSuchCalloutContext, "unable to find callout context " + "associated with the current library index (" << current_library_ << + ")"); + } + + // Return a reference to the context's element collection. + return (libcontext->second); +} + +// Return the name of all items in the context associated with the current] +// library. + +vector<string> +CalloutHandle::getContextNames() const { + vector<string> names; + const ElementCollection& elements = getContextForLibrary(); + for (ElementCollection::const_iterator i = elements.begin(); + i != elements.end(); ++i) { + names.push_back(i->first); + } + + return (names); +} + +// Return name of current hook (the hook to which the current callout is +// attached) or the empty string if not called within the context of a +// callout. + +string +CalloutHandle::getHookName() const { + string hook = ""; + try { + hook = server_hooks_.getName(current_hook_); + } catch (const NoSuchHook&) { + // Hook index is invalid, so this methods probably called from outside + // a callout being executed via a call to CalloutManager::callCallouts. + // In this case, the empty string is returned. + } + + return (hook); +} + +ScopedCalloutHandleState:: +ScopedCalloutHandleState(const CalloutHandlePtr& callout_handle) + : callout_handle_(callout_handle) { + if (!callout_handle_) { + isc_throw(BadValue, "callout_handle argument must not be null"); + } + + resetState(); +} + +ScopedCalloutHandleState::~ScopedCalloutHandleState() { + resetState(); + + if (on_completion_) { + on_completion_(); + } +} + +void +ScopedCalloutHandleState::resetState() { + // No need to check if the handle is null because the constructor + // already checked that. + callout_handle_->deleteAllArguments(); + callout_handle_->setStatus(CalloutHandle::NEXT_STEP_CONTINUE); +} + +} // namespace hooks +} // namespace isc diff --git a/src/lib/hooks/callout_handle.h b/src/lib/hooks/callout_handle.h new file mode 100644 index 0000000..fb4b30a --- /dev/null +++ b/src/lib/hooks/callout_handle.h @@ -0,0 +1,512 @@ +// Copyright (C) 2013-2022 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef CALLOUT_HANDLE_H +#define CALLOUT_HANDLE_H + +#include <exceptions/exceptions.h> +#include <hooks/library_handle.h> +#include <hooks/parking_lots.h> +#include <util/dhcp_space.h> + +#include <boost/any.hpp> +#include <boost/shared_ptr.hpp> + +#include <map> +#include <string> +#include <vector> + +namespace isc { +namespace hooks { + +class ServerHooks; + +/// @brief No such argument +/// +/// Thrown if an attempt is made access an argument that does not exist. + +class NoSuchArgument : public Exception { +public: + NoSuchArgument(const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) {} +}; + +/// @brief No such callout context item +/// +/// Thrown if an attempt is made to get an item of data from this callout's +/// context and either the context or an item in the context with that name +/// does not exist. + +class NoSuchCalloutContext : public Exception { +public: + NoSuchCalloutContext(const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) {} +}; + +// Forward declaration of the library handle and related collection classes. + +class CalloutManager; +class LibraryManagerCollection; + +/// @brief Per-packet callout handle +/// +/// An object of this class is associated with every packet (or request) +/// processed by the server. It forms the principle means of passing data +/// between the server and the user-library callouts. +/// +/// The class allows access to the following information: +/// +/// - Arguments. When the callouts associated with a hook are called, they +/// are passed information by the server (and can return information to it) +/// through name/value pairs. Each of these pairs is an argument and the +/// information is accessed through the {get,set}Argument() methods. +/// +/// - Per-packet context. Each packet has a context associated with it, this +/// context being on a per-library basis. In other words, As a packet passes +/// through the callouts associated with a given library, the callouts can +/// associate and retrieve information with the packet. The per-library +/// nature of the context means that the callouts within a given library can +/// pass packet-specific information between one another, but they cannot pass +/// information to callous within another library. Typically such context +/// is created in the "context_create" callout and destroyed in the +/// "context_destroy" callout. The information is accessed through the +/// {get,set}Context() methods. + +class CalloutHandle { +public: + + /// @brief Specifies allowed next steps + /// + /// Those values are used to designate the next step in packet processing. + /// They are set by hook callouts and read by the Kea server. See + /// @ref setStatus for detailed description of each value. + enum CalloutNextStep { + NEXT_STEP_CONTINUE = 0, ///< continue normally + NEXT_STEP_SKIP = 1, ///< skip the next processing step + NEXT_STEP_DROP = 2, ///< drop the packet + NEXT_STEP_PARK = 3 ///< park the packet + }; + + + /// Typedef to allow abbreviation of iterator specification in methods. + /// The std::string is the argument name and the "boost::any" is the + /// corresponding value associated with it. + typedef std::map<std::string, boost::any> ElementCollection; + + /// Typedef to allow abbreviations in specifications when accessing + /// context. The ElementCollection is the name/value collection for + /// a particular context. The "int" corresponds to the index of an + /// associated library - there is a 1:1 correspondence between libraries + /// and a name.value collection. + /// + /// The collection of contexts is stored in a map, as not every library + /// will require creation of a context associated with each packet. In + /// addition, the structure is more flexible in that the size does not + /// need to be set when the CalloutHandle is constructed. + typedef std::map<int, ElementCollection> ContextCollection; + + /// @brief Constructor + /// + /// Creates the object and calls the callouts on the "context_create" + /// hook. + /// + /// Of the two arguments passed, only the pointer to the callout manager is + /// actively used. The second argument, the pointer to the library manager + /// collection, is used for lifetime control: after use, the callout handle + /// may contain pointers to memory allocated by the loaded libraries. The + /// used of a shared pointer to the collection of library managers means + /// that the libraries that could have allocated memory in a callout handle + /// will not be unloaded until all such handles have been destroyed. This + /// issue is discussed in more detail in the documentation for + /// isc::hooks::LibraryManager. + /// + /// @param manager Pointer to the callout manager object. + /// @param lmcoll Pointer to the library manager collection. This has a + /// null default for testing purposes. + CalloutHandle(const boost::shared_ptr<CalloutManager>& manager, + const boost::shared_ptr<LibraryManagerCollection>& lmcoll = + boost::shared_ptr<LibraryManagerCollection>()); + + /// @brief Destructor + /// + /// Calls the context_destroy callback to release any per-packet context. + /// It also clears stored data to avoid problems during member destruction. + ~CalloutHandle(); + + /// @brief Set argument + /// + /// Sets the value of an argument. The argument is created if it does not + /// already exist. + /// + /// @param name Name of the argument. + /// @param value Value to set. That can be of any data type. + template <typename T> + void setArgument(const std::string& name, T value) { + arguments_[name] = value; + } + + /// @brief Get argument + /// + /// Gets the value of an argument. + /// + /// @param name Name of the element in the argument list to get. + /// @param value [out] Value to set. The type of "value" is important: + /// it must match the type of the value set. + /// + /// @throw NoSuchArgument No argument with the given name is present. + /// @throw boost::bad_any_cast An argument with the given name is present, + /// but the data type of the value is not the same as the type of + /// the variable provided to receive the value. + template <typename T> + void getArgument(const std::string& name, T& value) const { + ElementCollection::const_iterator element_ptr = arguments_.find(name); + if (element_ptr == arguments_.end()) { + isc_throw(NoSuchArgument, "unable to find argument with name " << + name); + } + + value = boost::any_cast<T>(element_ptr->second); + } + + /// @brief Get argument names + /// + /// Returns a vector holding the names of arguments in the argument + /// vector. + /// + /// @return Vector of strings reflecting argument names. + std::vector<std::string> getArgumentNames() const; + + /// @brief Delete argument + /// + /// Deletes an argument of the given name. If an argument of that name + /// does not exist, the method is a no-op. + /// + /// N.B. If the element is a raw pointer, the pointed-to data is NOT deleted + /// by this method. + /// + /// @param name Name of the element in the argument list to set. + void deleteArgument(const std::string& name) { + static_cast<void>(arguments_.erase(name)); + } + + /// @brief Delete all arguments + /// + /// Deletes all arguments associated with this context. + /// + /// N.B. If any elements are raw pointers, the pointed-to data is NOT + /// deleted by this method. + void deleteAllArguments() { + arguments_.clear(); + } + + /// @brief Sets the next processing step. + /// + /// This method is used by the callouts to determine the next step + /// in processing. This method replaces former setSkip() method + /// that allowed only two values. + /// + /// Currently there are three possible value allowed: + /// NEXT_STEP_CONTINUE - tells the server to continue processing as usual + /// (equivalent of previous setSkip(false) ) + /// + /// NEXT_STEP_SKIP - tells the server to skip the processing. Exact meaning + /// is hook specific. See hook documentation for details. + /// (equivalent of previous setSkip(true)) + /// + /// NEXT_STEP_DROP - tells the server to unconditionally drop the packet + /// and do not process it further. + /// + /// NEXT_STEP_PARK - tells the server to "park" the packet. The packet will + /// wait in the queue for being unparked, e.g. as a result + /// of completion of the asynchronous performed by the + /// hooks library operation. + /// + /// This variable is interrogated by the server to see if the remaining + /// callouts associated with the current hook should be bypassed. + /// + /// @param next New value of the next step status. + void setStatus(const CalloutNextStep next) { + next_step_ = next; + } + + /// @brief Returns the next processing step. + /// + /// Gets the current value of the next step. See @ref setStatus for detailed + /// definition. + /// + /// @return Current value of the skip flag. + CalloutNextStep getStatus() const { + return (next_step_); + } + + /// @brief Set context + /// + /// Sets an element in the context associated with the current library. If + /// an element of the name is already present, it is replaced. + /// + /// @param name Name of the element in the context to set. + /// @param value Value to set. + template <typename T> + void setContext(const std::string& name, T value) { + getContextForLibrary()[name] = value; + } + + /// @brief Get context + /// + /// Gets an element from the context associated with the current library. + /// + /// @param name Name of the element in the context to get. + /// @param value [out] Value to set. The type of "value" is important: + /// it must match the type of the value set. + /// + /// @throw NoSuchCalloutContext Thrown if no context element with the name + /// "name" is present. + /// @throw boost::bad_any_cast Thrown if the context element is present + /// but the type of the data is not the same as the type of the + /// variable provided to receive its value. + template <typename T> + void getContext(const std::string& name, T& value) const { + const ElementCollection& lib_context = getContextForLibrary(); + + ElementCollection::const_iterator element_ptr = lib_context.find(name); + if (element_ptr == lib_context.end()) { + isc_throw(NoSuchCalloutContext, "unable to find callout context " + "item " << name << " in the context associated with " + "current library"); + } + + value = boost::any_cast<T>(element_ptr->second); + } + + /// @brief Get context names + /// + /// Returns a vector holding the names of items in the context associated + /// with the current library. + /// + /// @return Vector of strings reflecting the names of items in the callout + /// context associated with the current library. + std::vector<std::string> getContextNames() const; + + /// @brief Delete context element + /// + /// Deletes an item of the given name from the context associated with the + /// current library. If an item of that name does not exist, the method is + /// a no-op. + /// + /// N.B. If the element is a raw pointer, the pointed-to data is NOT deleted + /// by this. + /// + /// @param name Name of the context item to delete. + void deleteContext(const std::string& name) { + static_cast<void>(getContextForLibrary().erase(name)); + } + + /// @brief Delete all context items + /// + /// Deletes all items from the context associated with the current library. + /// + /// N.B. If any elements are raw pointers, the pointed-to data is NOT + /// deleted by this. + void deleteAllContext() { + getContextForLibrary().clear(); + } + + /// @brief Get hook name + /// + /// Get the name of the hook to which the current callout is attached. + /// This can be the null string if the CalloutHandle is being accessed + /// outside of the CalloutManager's "callCallouts" method. + /// + /// @return Name of the current hook or the empty string if none. + std::string getHookName() const; + + /// @brief Returns pointer to the parking lot handle for this hook point. + /// + /// @return pointer to the parking lot handle + ParkingLotHandlePtr getParkingLotHandlePtr() const; + + /// @brief Get current library index + /// + /// @return The current library index + int getCurrentLibrary() const { + return (current_library_); + } + + /// @brief Set current library index + /// + /// @param library_index The library index + void setCurrentLibrary(int library_index) { + current_library_ = library_index; + } + + /// @brief Get current hook index + /// + /// @return The current hook index + int getCurrentHook() const { + return (current_hook_); + } + + /// @brief Set current hook index + /// + /// @param hook_index The hook index + void setCurrentHook(int hook_index) { + current_hook_ = hook_index; + } + +private: + + /// @brief Check index + /// + /// Gets the current library index, throwing an exception if it is not set + /// or is invalid for the current library collection. + /// + /// @return Current library index, valid for this library collection. + /// + /// @throw InvalidIndex current library index is not valid for the library + /// handle collection. + int getLibraryIndex() const; + + /// @brief Return reference to context for current library + /// + /// Called by all context-setting functions, this returns a reference to + /// the callout context for the current library, creating a context if it + /// does not exist. + /// + /// @return Reference to the collection of name/value pairs associated + /// with the current library. + /// + /// @throw InvalidIndex current library index is not valid for the library + /// handle collection. + ElementCollection& getContextForLibrary(); + + /// @brief Return reference to context for current library (const version) + /// + /// Called by all context-accessing functions, this a reference to the + /// callout context for the current library. An exception is thrown if + /// it does not exist. + /// + /// @return Reference to the collection of name/value pairs associated + /// with the current library. + /// + /// @throw NoSuchCalloutContext Thrown if there is no ElementCollection + /// associated with the current library. + const ElementCollection& getContextForLibrary() const; + + // Member variables + + /// Pointer to the collection of libraries for which this handle has been + /// created. + boost::shared_ptr<LibraryManagerCollection> lm_collection_; + + /// Collection of arguments passed to the callouts + ElementCollection arguments_; + + /// Context collection - there is one entry per library context. + ContextCollection context_collection_; + + /// Callout manager. + boost::shared_ptr<CalloutManager> manager_; + + /// Reference to the singleton ServerHooks object. See the + /// @ref hooksmgMaintenanceGuide for information as to why the class holds + /// a reference instead of accessing the singleton within the code. + ServerHooks& server_hooks_; + + /// @brief Current library. + /// + /// When a call is made to @ref CalloutManager::callCallouts, this holds + /// the index of the current library. It is set to an invalid value (-1) + /// otherwise. + int current_library_; + + /// @brief Current hook. + /// + /// When a call is made to @ref CalloutManager::callCallouts, this holds + /// the index of the current hook. It is set to an invalid value (-1) + /// otherwise. + int current_hook_; + + /// Next processing step, indicating what the server should do next. + CalloutNextStep next_step_; +}; + +/// A shared pointer to a CalloutHandle object. +typedef boost::shared_ptr<CalloutHandle> CalloutHandlePtr; + +/// @brief Wrapper class around callout handle which automatically +/// resets handle's state. +/// +/// The Kea servers often require to associate processed packets with +/// @c CalloutHandle instances. This is to facilitate the case when the +/// hooks library passes information between the callouts using the +/// 'context' stored in the callout handle. The callouts invoked throughout +/// the packet lifetime have access to the context information for the +/// given packet. +/// +/// The association between the packets and the callout handles is +/// achieved by giving the ownership of the @c CalloutHandle objects to +/// the @c Pkt objects. When the @c Pkt object goes out of scope, it should +/// also release the pointer to the owned @c CalloutHandle object. +/// However, this causes a risk of circular dependency between the shared +/// pointer to the @c Pkt object and the shared pointer to the +/// @c CalloutHandle it owns, because the pointer to the packet is often +/// set as an argument of the callout handle prior to invoking a callout. +/// +/// In order to break the circular dependency, the arguments of the +/// callout handle must be deleted as soon as they are not needed +/// anymore. This class is a wrapper around the callout handle object, +/// which resets its state during construction and destruction. All +/// Kea hook points must use this class within the scope where the +/// @c HooksManager::callCallouts is invoked to reset the state of the +/// callout handle. The state is reset when this object goes out of +/// scope. +/// +/// Currently, the following operations are performed during the reset: +/// - all arguments of the callout handle are deleted, +/// - the next step status is set to @c CalloutHandle::NEXT_STEP CONTINUE +/// +/// This class must never be modified to also delete the context +/// information from the callout handle. The context is intended +/// to be used to share stateful data across callouts and hook points +/// and its contents must exist for the duration of the packet lifecycle. +/// Otherwise, we could simply re-create the callout handle for +/// each hook point and we wouldn't need this RAII class. +class ScopedCalloutHandleState { +public: + + /// @brief Constructor. + /// + /// Resets state of the callout handle. + /// + /// @param callout_handle reference to the pointer to the callout + /// handle which state should be reset. + /// @throw isc::BadValue if the callout handle is null. + explicit ScopedCalloutHandleState(const CalloutHandlePtr& callout_handle); + + /// @brief Destructor. + /// + /// Resets state of the callout handle. + ~ScopedCalloutHandleState(); + + /// @brief Continuation callback. + std::function<void()> on_completion_; + +private: + + /// @brief Resets the callout handle state. + /// + /// It is used internally by the constructor and destructor. + void resetState(); + + /// @brief Holds pointer to the wrapped callout handle. + CalloutHandlePtr callout_handle_; +}; + +} // namespace hooks +} // namespace isc + + +#endif // CALLOUT_HANDLE_H diff --git a/src/lib/hooks/callout_handle_associate.cc b/src/lib/hooks/callout_handle_associate.cc new file mode 100644 index 0000000..dc6a3dc --- /dev/null +++ b/src/lib/hooks/callout_handle_associate.cc @@ -0,0 +1,34 @@ +// Copyright (C) 2018-2022 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <hooks/callout_handle_associate.h> +#include <hooks/hooks_manager.h> + +namespace isc { +namespace hooks { + +CalloutHandleAssociate::CalloutHandleAssociate() + : callout_handle_() { +} + +CalloutHandlePtr +CalloutHandleAssociate::getCalloutHandle() { + if (!callout_handle_) { + callout_handle_ = HooksManager::createCalloutHandle(); + } + + return (callout_handle_); +} + +void +CalloutHandleAssociate::resetCalloutHandle() { + callout_handle_.reset(); +} + +} // end of namespace isc::hooks +} // end of namespace isc diff --git a/src/lib/hooks/callout_handle_associate.h b/src/lib/hooks/callout_handle_associate.h new file mode 100644 index 0000000..c222590 --- /dev/null +++ b/src/lib/hooks/callout_handle_associate.h @@ -0,0 +1,63 @@ +// Copyright (C) 2018-2022 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef CALLOUT_HANDLE_ASSOCIATE_H +#define CALLOUT_HANDLE_ASSOCIATE_H + +#include <hooks/callout_handle.h> + +namespace isc { +namespace hooks { + +/// @brief Base class for classes which need to be associated with +/// a @c CalloutHandle object. +/// +/// The @c CalloutHandle is an object used to pass various parameters +/// between Kea and the callouts. The Kea servers usually invoke +/// multiple different callouts for a single packet such as DHCP +/// packet, control command etc. Therefore, it is required to +/// associate this packet with an instance of the callout handle, so +/// this instance can be used for all callouts invoked for this +/// packet. +/// +/// Previously this association was made by the @c CalloutHandleStore +/// class. However, with the introduction of parallel processing +/// of packets (DHCP packets parking) it became awkward to use. +/// Attempts to extend this class to hold a map of associations +/// failed because of no easy way to garbage collect unused handles. +/// +/// The easiest way to deal with this is to provide ownership of the +/// @c CalloutHandle to the object with which it is associated. The +/// class of this object needs to derive from this class. When the +/// object (e.g. DHCP packet) goes out of scope and is destroyed +/// this instance is destroyed as well. +class CalloutHandleAssociate { +public: + + /// @brief Constructor. + CalloutHandleAssociate(); + + /// @brief Returns callout handle. + /// + /// The callout handle is created if it doesn't exist. Subsequent + /// calls to this method always return the same handle. + /// + /// @return Pointer to the callout handle. + CalloutHandlePtr getCalloutHandle(); + + /// @brief Reset callout handle. + void resetCalloutHandle(); + +protected: + + /// @brief Callout handle stored. + CalloutHandlePtr callout_handle_; +}; + +} // end of isc::hooks +} // end of isc + +#endif diff --git a/src/lib/hooks/callout_manager.cc b/src/lib/hooks/callout_manager.cc new file mode 100644 index 0000000..5505567 --- /dev/null +++ b/src/lib/hooks/callout_manager.cc @@ -0,0 +1,348 @@ +// Copyright (C) 2013-2021 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <hooks/callout_handle.h> +#include <hooks/callout_manager.h> +#include <hooks/hooks_log.h> +#include <hooks/pointer_converter.h> +#include <util/stopwatch.h> + +#include <boost/static_assert.hpp> + +#include <algorithm> +#include <climits> +#include <functional> +#include <utility> + +using namespace std; + +namespace isc { +namespace hooks { + +// Constructor +CalloutManager::CalloutManager(int num_libraries) + : server_hooks_(ServerHooks::getServerHooks()), current_library_(-1), + hook_vector_(ServerHooks::getServerHooks().getCount()), + library_handle_(*this), pre_library_handle_(*this, 0), + post_library_handle_(*this, INT_MAX), num_libraries_(num_libraries) { + if (num_libraries < 0) { + isc_throw(isc::BadValue, "number of libraries passed to the " + "CalloutManager must be >= 0"); + } +} + +// Check that the index of a library is valid. It can range from 1 - n +// (n is the number of libraries), 0 (pre-user library callouts), or INT_MAX +// (post-user library callouts). It can also be -1 to indicate an invalid +// value. + +void +CalloutManager::checkLibraryIndex(int library_index) const { + if (((library_index >= -1) && (library_index <= num_libraries_)) || + (library_index == INT_MAX)) { + return; + } + + isc_throw(NoSuchLibrary, "library index " << library_index << + " is not valid for the number of loaded libraries (" << + num_libraries_ << ")"); +} + +// Register a callout for the current library. + +void +CalloutManager::registerCallout(const std::string& name, + CalloutPtr callout, + int library_index) { + // Note the registration. + LOG_DEBUG(callouts_logger, HOOKS_DBG_CALLS, HOOKS_CALLOUT_REGISTRATION) + .arg(library_index).arg(name); + + // Sanity check that the current library index is set to a valid value. + checkLibraryIndex(library_index); + + // New hooks could have been registered since the manager was constructed. + ensureHookLibsVectorSize(); + + // Get the index associated with this hook (validating the name in the + // process). + int hook_index = server_hooks_.getIndex(name); + + // Iterate through the callout vector for the hook from start to end, + // looking for the first entry where the library index is greater than + // the present index. + for (CalloutVector::iterator i = hook_vector_[hook_index].begin(); + i != hook_vector_[hook_index].end(); ++i) { + if (i->first > library_index) { + // Found an element whose library index number is greater than the + // current index, so insert the new element ahead of this one. + hook_vector_[hook_index].insert(i, make_pair(library_index, + callout)); + return; + } + } + + // Reached the end of the vector, so there is no element in the (possibly + // empty) set of callouts with a library index greater than the current + // library index. Inset the callout at the end of the list. + hook_vector_[hook_index].push_back(make_pair(library_index, callout)); +} + +// Check if callouts are present for a given hook index. + +bool +CalloutManager::calloutsPresent(int hook_index) const { + // Validate the hook index. + if ((hook_index < 0) || (hook_index >= hook_vector_.size())) { + isc_throw(NoSuchHook, "hook index " << hook_index << + " is not valid for the list of registered hooks"); + } + + // Valid, so are there any callouts associated with that hook? + return (!hook_vector_[hook_index].empty()); +} + +bool +CalloutManager::commandHandlersPresent(const std::string& command_name) const { + // Check if the hook point for the specified command exists. + int index = ServerHooks::getServerHooks().findIndex( + ServerHooks::commandToHookName(command_name)); + if (index >= 0) { + // The hook point exits but it is possible that there are no + // callouts/command handlers. This is possible if there was a + // hook library supporting this command attached, but it was + // later unloaded. The hook points are not deregistered in + // this case. Only callouts are deregistered. + // Let's check if callouts are present for this hook point. + return (calloutsPresent(index)); + } + + // Hook point not created, so we don't support this command in + // any of the hooks libraries. + return (false); +} + + +// Call all the callouts for a given hook. + +void +CalloutManager::callCallouts(int hook_index, CalloutHandle& callout_handle) { + // Clear the "skip" flag so we don't carry state from a previous call. + // This is done regardless of whether callouts are present to avoid passing + // any state from the previous call of callCallouts(). + callout_handle.setStatus(CalloutHandle::NEXT_STEP_CONTINUE); + + // Only initialize and iterate if there are callouts present. This check + // also catches the case of an invalid index. + if (calloutsPresent(hook_index)) { + + // Set the current hook index. This is used should a callout wish to + // determine to what hook it is attached. + callout_handle.setCurrentHook(hook_index); + + // This object will be used to measure execution time of each callout + // and the total time spent in callouts for this hook point. + util::Stopwatch stopwatch; + + // Mark that the callouts begin for the hook. + LOG_DEBUG(callouts_logger, HOOKS_DBG_CALLS, HOOKS_CALLOUTS_BEGIN) + .arg(server_hooks_.getName(callout_handle.getCurrentHook())); + + // Call all the callouts. + for (CalloutVector::const_iterator i = hook_vector_[hook_index].begin(); + i != hook_vector_[hook_index].end(); ++i) { + // In case the callout requires access to the context associated + // with the library, set the current library index to the index + // associated with the library that registered the callout being + // called. + callout_handle.setCurrentLibrary(i->first); + + // Call the callout + try { + stopwatch.start(); + int status = (*i->second)(callout_handle); + stopwatch.stop(); + if (status == 0) { + LOG_DEBUG(callouts_logger, HOOKS_DBG_EXTENDED_CALLS, + HOOKS_CALLOUT_CALLED) + .arg(callout_handle.getCurrentLibrary()) + .arg(server_hooks_.getName(callout_handle.getCurrentHook())) + .arg(PointerConverter(i->second).dlsymPtr()) + .arg(stopwatch.logFormatLastDuration()); + } else { + LOG_ERROR(callouts_logger, HOOKS_CALLOUT_ERROR) + .arg(server_hooks_.getName(callout_handle.getCurrentHook())) + .arg(callout_handle.getCurrentLibrary()) + .arg(PointerConverter(i->second).dlsymPtr()) + .arg(stopwatch.logFormatLastDuration()); + } + } catch (const std::exception& e) { + // If an exception occurred, the stopwatch.stop() hasn't been + // called, so we have to call it here. + stopwatch.stop(); + // Any exception, not just ones based on isc::Exception + LOG_ERROR(callouts_logger, HOOKS_CALLOUT_EXCEPTION) + .arg(server_hooks_.getName(callout_handle.getCurrentHook())) + .arg(callout_handle.getCurrentLibrary()) + .arg(PointerConverter(i->second).dlsymPtr()) + .arg(e.what()) + .arg(stopwatch.logFormatLastDuration()); + } + + } + + // Mark end of callout execution. Include the total execution + // time for callouts. + LOG_DEBUG(callouts_logger, HOOKS_DBG_CALLS, HOOKS_CALLOUTS_COMPLETE) + .arg(server_hooks_.getName(callout_handle.getCurrentHook())) + .arg(stopwatch.logFormatTotalDuration()); + + // Reset the current hook and library indexes to an invalid value to + // catch any programming errors. + callout_handle.setCurrentHook(-1); + callout_handle.setCurrentLibrary(-1); + } +} + +void +CalloutManager::callCommandHandlers(const std::string& command_name, + CalloutHandle& callout_handle) { + // Get the index of the hook point for the specified command. + // This will throw an exception if the hook point doesn't exist. + // The caller should check if the hook point exists by calling + // commandHandlersPresent. + int index = ServerHooks::getServerHooks().getIndex( + ServerHooks::commandToHookName(command_name)); + // Call the handlers for this command. + callCallouts(index, callout_handle); +} + + +// Deregister a callout registered by the current library on a particular hook. + +bool +CalloutManager::deregisterCallout(const std::string& name, CalloutPtr callout, + int library_index) { + // Sanity check that the current library index is set to a valid value. + checkLibraryIndex(library_index); + + // New hooks could have been registered since the manager was constructed. + ensureHookLibsVectorSize(); + + // Get the index associated with this hook (validating the name in the + // process). + int hook_index = server_hooks_.getIndex(name); + + // New hooks can have been registered since the manager was constructed. + if (hook_index >= hook_vector_.size()) { + return (false); + } + + /// Construct a CalloutEntry matching the current library and the callout + /// we want to remove. + CalloutEntry target(library_index, callout); + + /// To decide if any entries were removed, we'll record the initial size + /// of the callout vector for the hook, and compare it with the size after + /// the removal. + size_t initial_size = hook_vector_[hook_index].size(); + + // The next bit is standard STL (see "Item 33" in "Effective STL" by + // Scott Meyers). + // + // remove_if reorders the hook vector so that all items not matching + // the predicate are at the start of the vector and returns a pointer + // to the next element. (In this case, the predicate is that the item + // is equal to the value of the passed callout.) The erase() call + // removes everything from that element to the end of the vector, i.e. + // all the matching elements. + hook_vector_[hook_index].erase(remove_if(hook_vector_[hook_index].begin(), + hook_vector_[hook_index].end(), + [&target] (CalloutEntry x) { + return (x == target); }), + hook_vector_[hook_index].end()); + + // Return an indication of whether anything was removed. + bool removed = initial_size != hook_vector_[hook_index].size(); + if (removed) { + LOG_DEBUG(callouts_logger, HOOKS_DBG_EXTENDED_CALLS, + HOOKS_CALLOUT_DEREGISTERED).arg(library_index).arg(name); + } + + return (removed); +} + +// Deregister all callouts on a given hook. + +bool +CalloutManager::deregisterAllCallouts(const std::string& name, + int library_index) { + // New hooks could have been registered since the manager was constructed. + ensureHookLibsVectorSize(); + + // Get the index associated with this hook (validating the name in the + // process). + int hook_index = server_hooks_.getIndex(name); + + /// Construct a CalloutEntry matching the current library (the callout + /// pointer is NULL as we are not checking that). + CalloutEntry target(library_index, static_cast<CalloutPtr>(0)); + + /// To decide if any entries were removed, we'll record the initial size + /// of the callout vector for the hook, and compare it with the size after + /// the removal. + size_t initial_size = hook_vector_[hook_index].size(); + + // Remove all callouts matching this library. + hook_vector_[hook_index].erase(remove_if(hook_vector_[hook_index].begin(), + hook_vector_[hook_index].end(), + [&target] (CalloutEntry x) { + return (x.first == target.first); + }), + hook_vector_[hook_index].end()); + + // Return an indication of whether anything was removed. + bool removed = initial_size != hook_vector_[hook_index].size(); + if (removed) { + LOG_DEBUG(callouts_logger, HOOKS_DBG_EXTENDED_CALLS, + HOOKS_ALL_CALLOUTS_DEREGISTERED).arg(library_index).arg(name); + } + + return (removed); +} + +void +CalloutManager::registerCommandHook(const std::string& command_name) { + // New hooks could have been registered since the manager was constructed. + ensureHookLibsVectorSize(); + + ServerHooks& hooks = ServerHooks::getServerHooks(); + int hook_index = hooks.findIndex(ServerHooks::commandToHookName(command_name)); + if (hook_index < 0) { + // Hook for this command doesn't exist. Let's create one. + hooks.registerHook(ServerHooks::commandToHookName(command_name)); + // Callout Manager's vector of hooks have to be resized to hold the + // information about callouts for this new hook point. This should + // add new element at the end of the hook_vector_. The index of this + // element will match the index of the hook point in the ServerHooks + // because ServerHooks allocates indexes incrementally. + hook_vector_.resize(server_hooks_.getCount()); + } +} + +void +CalloutManager::ensureHookLibsVectorSize() { + ServerHooks& hooks = ServerHooks::getServerHooks(); + if (hooks.getCount() > hook_vector_.size()) { + // Uh oh, there are more hook points that our vector allows. + hook_vector_.resize(hooks.getCount()); + } +} + +} // namespace util +} // namespace isc diff --git a/src/lib/hooks/callout_manager.h b/src/lib/hooks/callout_manager.h new file mode 100644 index 0000000..3ee1efd --- /dev/null +++ b/src/lib/hooks/callout_manager.h @@ -0,0 +1,438 @@ +// Copyright (C) 2013-2021 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef CALLOUT_MANAGER_H +#define CALLOUT_MANAGER_H + +#include <exceptions/exceptions.h> +#include <hooks/library_handle.h> +#include <hooks/server_hooks.h> + +#include <boost/shared_ptr.hpp> + +#include <climits> +#include <map> +#include <string> + +namespace isc { +namespace hooks { + +/// @brief No such library +/// +/// Thrown if an attempt is made to set the current library index to a value +/// that is invalid for the number of loaded libraries. +class NoSuchLibrary : public Exception { +public: + NoSuchLibrary(const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) {} +}; + +/// @brief Callout Manager +/// +/// This class manages the registration, deregistration and execution of the +/// library callouts. It is part of the hooks framework used by the Kea +/// server, and is not for use by user-written code in a hooks library. +/// +/// In operation, the class needs to know two items of data: +/// +/// - The list of server hooks, which is used in two ways. Firstly, when a +/// library registers or deregisters a hook, it does so by name: the +/// @ref isc::hooks::ServerHooks object supplies the names of registered +/// hooks. Secondly, when the callouts associated with a hook are called by +/// the server, the server supplies the index of the relevant hook: this is +/// validated by reference to the list of hooks. +/// +/// - The number of loaded libraries. Each callout registered by a user +/// library is associated with that library, the callout manager storing both +/// a pointer to the callout and the index of the library in the list of +/// loaded libraries. When calling a callout, the callout manager maintains +/// the idea of a "current library index": this is used to access the context +/// associated with the library. +/// +/// These two items of data are supplied when an object of this class is +/// constructed. The latter (number of libraries) can be updated after the +/// class is constructed. (Such an update is used during library loading where +/// the CalloutManager has to be constructed before the libraries are loaded, +/// but one of the libraries subsequently fails to load.) +/// +/// The library index is important because it determines in what order callouts +/// on a particular hook are called. For each hook, the CalloutManager +/// maintains a vector of callouts ordered by library index. When a callout +/// is added to the list, it is added at the end of the callouts associated +/// with the current library. To clarify this further, suppose that three +/// libraries are loaded, A (assigned an index 1), B (assigned an index 2) and +/// C (assigned an index 3). Suppose A registers two callouts on a given hook, +/// A1 and A2 (in that order) B registers B1 and B2 (in that order) and C +/// registers C1 and C2 (in that order). Internally, the callouts are stored +/// in the order A1, A2, B1, B2, C1, and C2: this is also the order in which +/// they are called. +/// +/// Indexes range between 1 and n (where n is the number of the libraries +/// loaded) and are assigned to libraries based on the order the libraries +/// presented to the hooks framework for loading (something that occurs in the +/// isc::hooks::HooksManager) class. However, two other indexes are recognized, +/// 0 and INT_MAX. These are used when the server itself registers callouts - +/// the server is able to register callouts that get called before any +/// user-library callouts, and ones that get called after user-library callouts. +/// In other words, assuming the callouts on a hook are A1, A2, B1, B2, B3, C2, +/// C2 as before, and that the server registers S1 (to run before the +/// user-registered callouts) and S2 (to run after them), the callouts are +/// stored (and executed) in the order S1, A1, A2, B1, B2, B3, C2, C2, S2. In +/// summary, the recognized index values are: +/// +/// - < 0: invalid. +/// - 0: used for server-registered callouts that are called before +/// user-registered callouts. +/// - 1 - n: callouts from user libraries. +/// - INT_MAX: used for server-registered callouts called after +/// user-registered callouts. +/// +/// Since Kea 1.3.0 release hook libraries can register callouts as control +/// command handlers. Such handlers are associated with dynamically created +/// hook points which names are created after command names. For example, +/// if a command name is 'foo-bar', the name of the hook point to which +/// callouts/command handlers are registered is '$foo_bar'. Prefixing the +/// hook point name with the dollar sign eliminates potential conflicts +/// between hook points dedicated to commands handling and other (fixed) +/// hook points. +/// +/// Prefixing hook names for command handlers with a dollar sign precludes +/// auto registration of command handlers, i.e. hooks framework is unable +/// to match hook points with names of functions implementing command +/// handlers, because the dollar sign is not legal in C++ function names. +/// This is intended because we want hook libraries to explicitly register +/// commands handlers for supported commands and not rely on Kea to register +/// hook points for them. Should we find use cases for auto registration of +/// command handlers, we may modify the +/// @ref ServerHooks::commandToHookName to use an encoding of hook +/// point names for command handlers that would only contain characters +/// allowed in function names. +/// +/// The @ref CalloutManager::registerCommandHook has been added to allow for +/// dynamically creating hook points for which command handlers are registered. +/// This method is called from the @ref LibraryHandle::registerCommandCallout +/// as a result of registering the command handlers by the hook library in +/// its @c load() function. If the hook point for the given command already +/// exists, this function doesn't do anything. The +/// @ref LibraryHandle::registerCommandCallout can install callouts on this +/// hook point. +/// +/// Note that the callout functions do not access the CalloutManager: instead, +/// they use a LibraryHandle object. This contains an internal pointer to +/// the CalloutManager, but provides a restricted interface. In that way, +/// callouts are unable to affect callouts supplied by other libraries. + +class CalloutManager { +private: + + // Private typedefs + + /// Element in the vector of callouts. The elements in the pair are the + /// index of the library from which this callout was registered, and a# + /// pointer to the callout itself. + typedef std::pair<int, CalloutPtr> CalloutEntry; + + /// An element in the hook vector. Each element is a vector of callouts + /// associated with a given hook. + typedef std::vector<CalloutEntry> CalloutVector; + +public: + + /// @brief Constructor + /// + /// Initializes member variables, in particular sizing the hook vector + /// (the vector of callout vectors) to the appropriate size. + /// + /// @param num_libraries Number of loaded libraries. + /// + /// @throw isc::BadValue if the number of libraries is less than 0, + CalloutManager(int num_libraries = 0); + + /// @brief Register a callout on a hook for the current library + /// + /// Registers a callout function for the current library with a given hook. + /// The callout is added to the end of the callouts for this library that + /// are associated with that hook. + /// + /// @param name Name of the hook to which the callout is added. + /// @param callout Pointer to the callout function to be registered. + /// @param library_index Library index used for registering the callout. + /// + /// @throw NoSuchHook The hook name is unrecognized. + /// @throw Unexpected The hook name is valid but an internal data structure + /// is of the wrong size. + void registerCallout(const std::string& name, + CalloutPtr callout, + int library_index); + + /// @brief De-Register a callout on a hook for the current library + /// + /// Searches through the functions registered by the current library + /// with the named hook and removes all entries matching the + /// callout. + /// + /// @param name Name of the hook from which the callout is removed. + /// @param callout Pointer to the callout function to be removed. + /// @param library_index Library index used for deregistering the callout. + /// + /// @return true if a one or more callouts were deregistered. + /// + /// @throw NoSuchHook The hook name is unrecognized. + /// @throw Unexpected The hook name is valid but an internal data structure + /// is of the wrong size. + bool deregisterCallout(const std::string& name, + CalloutPtr callout, + int library_index); + + /// @brief Removes all callouts on a hook for the current library + /// + /// Removes all callouts associated with a given hook that were registered + /// by the current library. + /// + /// @param name Name of the hook from which the callouts are removed. + /// @param library_index Library index used for deregistering all callouts. + /// + /// @return true if one or more callouts were deregistered. + /// + /// @throw NoSuchHook Thrown if the hook name is unrecognized. + bool deregisterAllCallouts(const std::string& name, int library_index); + + /// @brief Checks if callouts are present on a hook + /// + /// Checks all loaded libraries and returns true if at least one callout + /// has been registered by any of them for the given hook. + /// + /// @param hook_index Hook index for which callouts are checked. + /// + /// @return true if callouts are present, false if not. + /// + /// @throw NoSuchHook Given index does not correspond to a valid hook. + bool calloutsPresent(int hook_index) const; + + /// @brief Checks if control command handlers are present for the + /// specified command. + /// + /// @param command_name Command name for which handlers' presence should + /// be checked. + /// + /// @return true if there is a hook point associated with the specified + /// command and callouts/command handlers are installed for this hook + /// point, false otherwise. + bool commandHandlersPresent(const std::string& command_name) const; + + /// @brief Calls the callouts for a given hook + /// + /// Iterates through the library handles and calls the callouts associated + /// with the given hook index. + /// + /// @note This method invalidates the current library index set with + /// setLibraryIndex(). + /// + /// @param hook_index Index of the hook to call. + /// @param callout_handle Reference to the CalloutHandle object for the + /// current object being processed. + void callCallouts(int hook_index, CalloutHandle& callout_handle); + + /// @brief Calls the callouts/command handlers for a given command name. + /// + /// Iterates through the library handles and calls the command handlers + /// associated with the given command. It expects that the hook point + /// for this command exists (with a name being a command_name prefixed + /// with a dollar sign and with hyphens replaced with underscores). + /// + /// @param command_name Command name for which handlers should be called. + /// @param callout_handle Reference to the CalloutHandle object for the + /// current object being processed. + /// + /// @throw NoSuchHook if the hook point for the specified command does + /// not exist. + void callCommandHandlers(const std::string& command_name, + CalloutHandle& callout_handle); + + /// @brief Registers a hook point for the specified command name. + /// + /// If the hook point for such command already exists, this function + /// doesn't do anything. The registered hook point name is created + /// after command_name by prefixing it with a dollar sign and replacing + /// all hyphens with underscores, e.g. for the 'foo-bar' command the + /// following hook point name will be generated: '$foo_bar'. + /// + /// @param command_name Command name for which the hook point should be + /// registered. + void registerCommandHook(const std::string& command_name); + + /// @brief Get number of libraries + /// + /// Returns the number of libraries that this CalloutManager is expected + /// to serve. This is the number passed to its constructor. + /// + /// @return Number of libraries served by this CalloutManager. + int getNumLibraries() const { + return (num_libraries_); + } + + /// @brief Get current library index + /// + /// Returns the index of the "current" library. This the index associated + /// with the currently executing callout when callCallouts is executing. + /// When callCallouts() is not executing (as is the case when the load() + /// function in a user-library is called during the library load process), + /// the index can be set by setLibraryIndex(). + /// + /// @note The value set by this method is lost after a call to + /// callCallouts. + /// + /// @return Current library index. + int getLibraryIndex() const { + return (current_library_); + } + + /// @brief Set current library index + /// + /// Sets the current library index. This has the following valid values: + /// + /// - -1: invalidate current index. + /// - 0: pre-user library callout. + /// - 1 - numlib: user-library callout (where "numlib" is the number of + /// libraries loaded in the system, this figure being passed to this + /// object at construction time). + /// - INT_MAX: post-user library callout. + /// + /// @param library_index New library index. + /// + /// @throw NoSuchLibrary if the index is not valid. + void setLibraryIndex(int library_index) { + checkLibraryIndex(library_index); + current_library_ = library_index; + } + + /// @defgroup calloutManagerLibraryHandles Callout manager library handles + /// + /// The CalloutManager offers three library handles: + /// + /// - a "standard" one, used to register and deregister callouts for + /// the library index that is marked as current in the CalloutManager. + /// When a callout is called, it is passed this one. + /// - a pre-library callout handle, used by the server to register + // callouts to run prior to user-library callouts. + /// - a post-library callout handle, used by the server to register + /// callouts to run after the user-library callouts. + //@{ + + /// @brief Return library handle + /// + /// The library handle is available to the user callout via the callout + /// handle object. It provides a cut-down view of the CalloutManager, + /// allowing the callout to register and deregister callouts in the + /// library of which it is part, whilst denying access to anything that + /// may affect other libraries. + /// + /// @return Reference to library handle for this manager + LibraryHandle& getLibraryHandle() { + return (library_handle_); + } + + /// @brief Return pre-user callouts library handle + /// + /// The LibraryHandle to affect callouts that will run before the + /// user-library callouts. + /// + /// @return Reference to pre-user library handle for this manager + LibraryHandle& getPreLibraryHandle() { + return (pre_library_handle_); + } + + /// @brief Return post-user callouts library handle + /// + /// The LibraryHandle to affect callouts that will run before the + /// user-library callouts. + /// + /// @return Reference to post-user library handle for this manager + LibraryHandle& getPostLibraryHandle() { + return (post_library_handle_); + } + + //@} + + /// @brief Return number of currently available hooks + size_t getHookLibsVectorSize() const { + return (hook_vector_.size()); + } + +private: + + /// @brief This method checks whether the hook_vector_ size is sufficient + /// and extends it if necessary. + /// + /// The problem is as follows: some hooks are initialized statically + /// from global static objects. ServerHooks object creates hooks_ collection + /// and CalloutManager creates its own hook_vector_ and both are initialized + /// to the same size. All works well so far. However, if some code at a + /// later time (e.g. a hook library) registers new hook point, then + /// ServerHooks::registerHook() will extend its hooks_ collection, but + /// the CalloutManager will keep the old hook_vector_ that is too small by + /// one. Now when the library is unloaded, deregisterAllCallouts will + /// go through all hook points and will eventually hit the one that + /// will return index greater than the hook_vector_ size. + /// + /// To solve this problem, ensureVectorSize was implemented. It should + /// be called (presumably from ServerHooks) every time a new hook point + /// is registered. It checks whether the vector size is sufficient and + /// extends it if necessary. It is safe to call it multiple times. It + /// may grow the vector size, but will never shrink it. + void ensureHookLibsVectorSize(); + + /// @brief Check library index + /// + /// Ensures that the current library index is valid. This is called by + /// the hook registration functions. + /// + /// @param library_index Value to check for validity as a library index. + /// Valid values are 0 -> numlib + 1 and -1: see @ref setLibraryIndex + /// for the meaning of the various values. + /// + /// @throw NoSuchLibrary Library index is not valid. + void checkLibraryIndex(int library_index) const; + + // Member variables + + /// Reference to the singleton ServerHooks object. See the + /// @ref hooksmgMaintenanceGuide for information as to why the class holds + /// a reference instead of accessing the singleton within the code. + ServerHooks& server_hooks_; + + /// Current library index. When a call is made to any of the callout + /// registration methods, this variable indicates the index of the user + /// library that should be associated with the call. + int current_library_; + + /// Vector of callout vectors. There is one entry in this outer vector for + /// each hook. Each element is itself a vector, with one entry for each + /// callout registered for that hook. + std::vector<CalloutVector> hook_vector_; + + /// LibraryHandle object user by the callout to access the callout + /// registration methods on this CalloutManager object. The object is set + /// such that the index of the library associated with any operation is + /// whatever is currently set in the CalloutManager. + LibraryHandle library_handle_; + + /// LibraryHandle for callouts to be registered as being called before + /// the user-registered callouts. + LibraryHandle pre_library_handle_; + + /// LibraryHandle for callouts to be registered as being called after + /// the user-registered callouts. + LibraryHandle post_library_handle_; + + /// Number of libraries. + int num_libraries_; +}; + +} // namespace util +} // namespace isc + +#endif // CALLOUT_MANAGER_H diff --git a/src/lib/hooks/hooks.h b/src/lib/hooks/hooks.h new file mode 100644 index 0000000..e2d4f5e --- /dev/null +++ b/src/lib/hooks/hooks.h @@ -0,0 +1,71 @@ +// Copyright (C) 2013-2022 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef HOOKS_H +#define HOOKS_H + +#include <hooks/callout_handle.h> +#include <hooks/library_handle.h> + +namespace { + +// Version 20200 of the hooks framework, set for Kea 2.2.0 +const int KEA_HOOKS_VERSION = 20200; + +// Names of the framework functions. +const char* const LOAD_FUNCTION_NAME = "load"; +const char* const UNLOAD_FUNCTION_NAME = "unload"; +const char* const VERSION_FUNCTION_NAME = "version"; +const char* const MULTI_THREADING_COMPATIBLE_FUNCTION_NAME = + "multi_threading_compatible"; + +// Typedefs for pointers to the framework functions. +typedef int (*version_function_ptr)(); +typedef int (*load_function_ptr)(isc::hooks::LibraryHandle&); +typedef int (*unload_function_ptr)(); +typedef int (*multi_threading_compatible_function_ptr)(); + +} // Anonymous namespace + +namespace isc { +namespace hooks { + +/// @brief User-Library Initialization for Statically-Linked Kea +/// +/// If Kea is statically-linked, a user-created hooks library will not be +/// able to access symbols in it. In particular, it will not be able to access +/// singleton objects. +/// +/// The hooks framework handles some of this. For example, although there is +/// a singleton ServerHooks object, hooks framework objects store a reference +/// to it when they are created. When the user library needs to register a +/// callout (which requires access to the ServerHooks information), it accesses +/// the ServerHooks object through a pointer passed from the Kea image. +/// +/// The logging framework is more problematical. Here the code is partly +/// statically linked (the Kea logging library) and partly shared (the +/// log4cplus). The state of the former is not accessible to the user library, +/// but the state of the latter is. So within the user library, we need to +/// initialize the Kea logging library but not initialize the log4cplus +/// code. Some of the initialization is done when the library is loaded, but +/// other parts are done at run-time. +/// +/// This function - to be called by the user library code in its load() function +/// when running against a statically linked Kea - initializes the Kea +/// logging library. In particular, it loads the message dictionary with the +/// text of the Kea messages. +/// +/// @note This means that the virtual address space is loaded with two copies +/// of the message dictionary. Depending on how the user libraries are linked, +/// loading multiple user libraries may involve loading one message dictionary +/// per library. + +void hooksStaticLinkInit(); + +} // namespace hooks +} // namespace isc + +#endif // HOOKS_H diff --git a/src/lib/hooks/hooks_component_developer.dox b/src/lib/hooks/hooks_component_developer.dox new file mode 100644 index 0000000..baeb5ef --- /dev/null +++ b/src/lib/hooks/hooks_component_developer.dox @@ -0,0 +1,520 @@ +// Copyright (C) 2013-2020 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +/** +@page hooksComponentDeveloperGuide Guide to Hooks for the Kea Component Developer + +@section hooksComponentIntroduction Introduction + +The hooks framework is a Kea system that simplifies the way that +users can write code to modify the behavior of Kea. Instead of +altering the Kea source code, they write functions that are compiled +and linked into one or more dynamic shared objects, called here (for +historical reasons), shared libraries. The library is specified in the Kea +configuration and at run time Kea dynamically loads the library +into its address space. At various points in the processing, the component +"calls out" to functions in the library, passing to them the data is it +currently working on. They can examine and modify the data as required. + +This guide is aimed at Kea developers who want to write or modify a +Kea component to use hooks. It shows how the component should be written +to load a shared library at run-time and how to call functions in it. + +For information about writing a hooks library containing functions called by Kea +during its execution, see the document @ref hooksdgDevelopersGuide. + + +@subsection hooksComponentTerminology Terminology + +In the remainder of this guide, the following terminology is used: + +- Component - a Kea process, e.g. the DHCPv4 or DHCPv6 server. + +- Hook/Hook Point - used interchangeably, this is a point in the code at +which a call to user-written functions is made. Each hook has a name and +each hook can have any number (including 0) of user-written functions +attached to it. + +- Callout - a user-written function called by the component at a hook +point. This is so-named because the component "calls out" to the library +to execute a user-written function. + +- User code/user library - non-Kea code that is compiled into a +shared library and loaded by Kea into its address space. Multiple +user libraries can be loaded at the same time, each containing callouts for +the same hooks. The hooks framework calls these libraries one after the +other. (See the document @ref hooksdgDevelopersGuide for more details.) + + +@subsection hooksComponentLanguages Languages + +The core of Kea is written in C++ with some remaining legacy parts in Python. +While it is the intention to provide the hooks framework for all languages, +the initial version is for C++. All examples in this guide are in that language. + + +@section hooksComponentBasicIdeas Basic Ideas + +From the point of view of the component author, the basic ideas of the hooks +framework are quite simple: + +- The location of hook points in the code need to be determined. + +- Name the hook points and register them. + +- At each hook point, the component needs to complete the following steps to + execute callouts registered by the user-library: + -# copy data into the object used to pass information to the callout. + -# call the callout. + -# copy data back from the object used to exchange information. + -# take action based on information returned. + +Of course, to set up the system the libraries need to be loaded in the first +place. The component also needs to: + +- Define the configuration item that specifies the user libraries for this +component. + +- Handle configuration changes and load/unload the user libraries. + +The following sections will describe these tasks in more detail. + + +@section hooksComponentDefinition Determining the Hook Points + +Before any other action takes place, the location of the hook points +in the code need to be determined. This, of course, depends on the +component but, as a general guideline, hook locations should be located +where a callout is able to obtain useful information from Kea and/or +affect processing. Typically this means at the start or end of a major +step in the processing of a request, at a point where either useful +information can be passed to a callout and/or the callout can affect +the processing of the component. The latter is achieved in either or both +of the following ways: + +- Setting the nest step status. This is an enum that the callout can set + and is a quick way of passing information back to the component. It is used + to indicate that the component should perform certain actions. Currently + there are three statuses defined: CONTINUE (this is the default, the server + is expected to continue as usual), SKIP (the server is expected to skip the + next processing step, but otherwise continue as usual) and DROP (the server + is expected to drop the packet or request completely. The exact action is up + to the component. + +- Modifying data passed to it. The component should be prepared to continue + processing with the data returned by the callout. It is up to the component + author whether the data is validated before being used, but doing so will + have performance implications. + + +@section hooksComponentRegistration Naming and Registering the Hooks Points + +Once the location of the hook point has been determined, it should be +given a name. This name should be unique amongst all hook points and is +subject to certain restrictions (see below). + +Before the callouts at any hook point are called and any user libraries +loaded - so typically during component initialization - the component must +register the names of all the hooks. The registration is done using +the static method @c isc::hooks::HooksManager::registerHook(): + +@code + +#include <hooks/hooks_manager.h> + : + int example_index = HooksManager::registerHook("lease_allocate"); +@endcode + +The name of the hook is passed as the sole argument to the @c registerHook() +method. The value returned is the index of that hook point and should +be retained - it is needed to call the callouts attached to that hook. + +Note that a hook only needs to be registered once. There is no mechanism for +unregistering a hook and there is no need to do so. + + +@subsection hooksComponentAutomaticRegistration Automatic Registration of Hooks + +In some components, it may be convenient to set up a single initialization +function that registers all hooks. For others, it may be more convenient +for each module within the component to perform its own initialization. +Since the @c isc::hooks::HooksManager object is a singleton and is created when first +accessed, a useful trick is to automatically register the hooks when +the module is loaded. + +This technique involves declaring an object outside of any execution +unit in the module. When the module is loaded, the object's constructor +is run. By placing the hook registration calls in the constructor, +the hooks in the module are defined at load time, before any function in +the module is run. The code for such an initialization sequence would +be similar to: + +@code +#include <hooks/hooks_manager.h> + +namespace { + +// Declare structure to perform initialization and store the hook indexes. +// +struct MyHooks { + int pkt_rcvd; // Index of "packet received" hook + int pkt_sent; // Index of "packet sent" hook + + // Constructor + MyHooks() { + pkt_rcvd = HooksManager::registerHook("pkt_rcvd"); + pkt_sent = HooksManager::registerHook("pkt_sent"); + } +}; + +// Declare a "MyHooks" object. As this is outside any function or method, it +// will be instantiated (and the constructor run) when the module is loaded. +// As a result, the hook indexes will be defined before any method in this +// module is called. +MyHooks my_hooks; + +} // Anonymous namespace + +void Someclass::someFunction() { + : + // Check if any callouts are defined on the pkt_rcvd hook. + if (HooksManager::calloutPresent(my_hooks.pkt_rcvd)) { + : + } + : +} +@endcode + + +@subsection hooksComponentHookNames Hook Names + +Hook names are strings and in principle, any string can be used as the +name of a hook, even one containing spaces and non-printable characters. +However, the following guidelines should be observed: + +- The names <b>context_create</b> and <b>context_destroy</b> are reserved to +the hooks system and are automatically registered: an attempt to register +one of these will lead to a @c isc::hooks::DuplicateHook exception being thrown. + +- The hook name should be a valid "C" function name. If a user gives a +callout the same name as one of the hooks, the hooks framework will +automatically load that callout and attach it to the hook: the user does not +have to explicitly register it. + +- The hook name should not conflict with the name of a function in any of +the system libraries (e.g. naming a hook "sqrt" could lead to the +square-root function in the system's maths library being attached to the hook +as a callout). + +- Although hook names can be in any case (including mixed case), the Kea +convention is that they are lower-case. + + +@section hooksComponentCallingCallouts Calling Callouts on a Hook + + +@subsection hooksComponentArgument The Callout Handle + +Before describing how to call user code at a hook point, we must first consider +how to pass data to it. + +Each user callout has the signature: +@code +int callout_name(isc::hooks::CalloutHandle& handle); +@endcode + +The @c isc::hooks::CalloutHandle object is the object used to pass data to +and from the callout. This holds the data as a set of name/value pairs, +each pair being considered an argument to the callout. If there are +multiple callouts attached to a hook, the @c CalloutHandle is passed to +each in turn. Should a callout modify an argument, the updated data is +passed subsequent callouts (each of which could also modify it) before +being returned to the component. + +Two methods are provided to get and set the arguments passed to +the callout called (naturally enough) @c getArgument and @c setArgument. +Their usage is illustrated by the following code snippets. + +@code + int count = 10; + boost::shared_ptr<Pkt4> pktptr = ... // Set to appropriate value + + // Assume that "handle_ptr" has been created and is a pointer to a + // CalloutHandle. + handle_ptr->setArgument("data_count", count); + handle_ptr->setArgument("inpacket", pktptr); + + // Call the hook code. lease_assigned_index is the value returned from + // HooksManager::registerHook() when the hook was registered. + HooksManager::callCallouts(lease_assigned_index, *handle_ptr); + + // Retrieve the modified values + handle_ptr->getArgument("data_count", count); + handle_ptr->getArgument("inpacket", pktptr); +@endcode + +As can be seen @c getArgument is used to retrieve data from the +@c CalloutHandle, and @c setArgument used to put data into it. If a callout +wishes to alter data and pass it back to the component, it should retrieve +the data with getArgument, modify it, and call setArgument to send +it back. + +There are a couple points to be aware of: + +- The data type of the variable in the call to @c getArgument must +match the data type of the variable passed to the corresponding +@c setArgument <B>exactly</B>: using what would normally be considered +to be a "compatible" type is not enough. For example, if the callout +passed an argument back to the component as an @c int and the component +attempted to retrieve it as a @c long, an exception would be thrown even +though any value that can be stored in an @c int will fit into a @c long. +This restriction also applies the "const" attribute but only as applied to +data pointed to by pointers, e.g. if an argument is defined as a @c char*, +an exception will be thrown if an attempt is made to retrieve it into +a variable of type @c const @c char*. (However, if an argument is set as a +@c const @c int, it can be retrieved into an @c int.) The documentation of +a hook point should detail the exact data type of each argument. + +- If a pointer to an object is passed to a callout (either a "raw" +pointer, or a boost smart pointer (as in the example above), and the +underlying object is altered through that pointer, the change will be +reflected in the component even if the callout makes no call to setArgument. +This can be avoided by passing a pointer to a "const" object. + + +@subsection hooksComponentSkipFlag The Skip Flag (obsolete) + + +@subsection hooksComponentNextStep The next step status + +Although information is passed back to the component from callouts through +@c CalloutHandle arguments, a common action for callouts is to inform the component +that its flow of control should be altered. For example: + +- In the DHCP servers, there is a hook at the point at which a lease is + about to be assigned. Callouts attached to this hooks may handle the + lease assignment in special cases, in which case they set the next step + status to SKIP to indicate that the server should not perform lease assignment + in this case. +- A server may define a hook just after a packet is received. A callout + attached to the hook might inspect the source address and compare it + against a blacklist. If the address is on the list, the callout could set + the DROP flag to indicate to the server that the packet should be dropped. + +For ease of processing, the @c CalloutHandle contains +two methods, @c isc::hooks::CalloutHandle::getStatus() and +@c isc::hooks::CalloutHandle::setStatus(). It is only meaningful for the +component to use the "get" method. The next step status is cleared (set to +the default value of CONTINUE) by the hooks framework when the component +requests that callouts be executed, so any +value set by the component is lost. Callouts can both inspect the status (it +might have been set by callouts earlier in the callout list for the hook) +and set it. Note that the setting of the status by a callout does not +prevent callouts later in the list from being called: the next step status is +just an enum value - the only significance comes from its interpretation +by the component. + +An example of use could be: +@code +// Set up arguments for DHCP lease assignment. +handle->setArgument("query", query); +handle->setArgument("response", response); +HooksManager::callCallouts(lease_hook_index, *handle_ptr); +if (handle_ptr->getStatus() != CalloutHandle::NEXT_STEP_SKIP) { + // Skip flag not set, do the address allocation + : +} +@endcode + + +@subsection hooksComponentGettingHandle Getting the Callout Handle + +The @c CalloutHandle object is linked to the loaded libraries +for lifetime reasons (described below). Components +should retrieve a @c isc::hooks::CalloutHandle using +@c isc::hooks::HooksManager::createCalloutHandle(): +@code + CalloutHandlePtr handle_ptr = HooksManager::createCalloutHandle(); +@endcode +(@c isc::hooks::CalloutHandlePtr is a typedef for a Boost shared pointer to a +CalloutHandle.) The CalloutHandle so retrieved may be used for as +long as the libraries are loaded. + +The handle is deleted by resetting the pointer: +@code + handle_ptr.reset(); +@endcode +... or by letting the handle pointer go out of scope. The actual deletion +occurs when the CallHandle's reference count goes to zero. (The +current version of the hooks framework does not maintain any other +pointer to the returned CalloutHandle, so it gets destroyed when the +shared pointer to it is cleared or destroyed. However, this may change +in a future version.) + +When the handle is not created locally it is not destroyed so it can +keep ownership on arguments. In such case the code must call @c +isc::hooks::CalloutHandle::deleteAllArguments or simply use the RAII +helper @c isc::hooks::ScopedCalloutHandleState as in: +@code + CalloutHandlePtr handle_ptr = getCalloutHandle(query); + ScopedCalloutHandleState state(handle_ptr); +@endcode + +@subsection hooksComponentCallingCallout Calling the Callout + +Calling the callout is a simple matter of executing the +@c isc::hooks::HooksManager::callCallouts() method for the hook index in +question. For example, with the hook index "pkt_sent" defined as above, +the hook can be executed by: +@code + HooksManager::callCallouts(pkt_sent, *handle_ptr); +@endcode +... where "*handle_ptr" is a reference (note: not a pointer) to the +@c isc::hooks::CalloutHandle object holding the arguments. No status code +is returned. If a component needs to get data returned (other than that +provided by the next step status), it should define an argument through which +the callout can do so. + +@subsubsection hooksComponentConditionalCallout Conditionally Calling Hook Callouts + +Most hooks in a component will not have callouts attached to them. To +avoid the overhead of setting up arguments in the @c CalloutHandle, a +component can check for callouts before doing that processing using +@c isc::hooks::HooksManager::calloutsPresent(). Taking the index of a +hook as its sole argument, the function returns true if there are any +callouts attached to the hook and false otherwise. + +With this check, the code in the component for calling a hook would look +something like: +@code +if (HooksManager::calloutsPresent(lease_hook_index)) { + // Set up arguments for lease assignment + handle->setArgument("query", query); + handle->setArgument("response", response); + HooksManager::callCallouts(lease_hook_index, *handle); + if (handle->getStatus() != CalloutHandle::NEXT_STEP_DROP) { + // Next step allows us to continue, do the address allocation + : + } +} +@endcode + +@section hooksComponentLoadLibraries Loading the User Libraries + +Once hooks are defined, all the hooks code described above will +work, even if no libraries are loaded (and even if the library +loading method is not called). The @c CalloutHandle returned by +@c isc::hooks::HooksManager::createCalloutHandle() will be valid, +@c isc::hooks::HooksManager::calloutsPresent() will return false for every +index, and @c isc::hooks::HooksManager::callCallouts() will be a no-op. + +However, if user libraries are specified in the Kea configuration, +the component should load them. (Note the term "libraries": the hooks +framework allows multiple user libraries to be loaded.) This should take +place after the component's configuration has been read, and is achieved +by the @c isc::hooks::HooksManager::loadLibraries() method. The method is +passed a vector of strings, each giving the full file specification of +a user library: +@code + std::vector<std::string> libraries = ... // Get array of libraries + bool success = HooksManager::loadLibraries(libraries); +@endcode +@c loadLibraries() returns a boolean status which is true if all libraries +loaded successfully or false if one or more failed to load. Appropriate +error messages will have been logged in the latter case, the status +being more to allow the developer to decide whether the execution +should proceed in such circumstances. + +Before @c loadLibraries() can be called a second or subsequent time +(as a result of a reconfiguration), all existing libraries must be +successfully unloaded. If a library stays in memory from a programming +bug in Kea (for instance when no libraries were loaded) or in a +library (@ref hooksMemoryManagement) @c loadLibraries() throws a not +recoverable error. + +Unloading is done in two phases since Kea version 1.7.10: + +- call to @c isc::hooks::HooksManager::prepareUnloadLibraries() which +calls all unload() entry points and deregisters callout points. + +- call to @c isc::hooks::HooksManager::unloadLibraries() even when +the prepare failed. + +If a failure of @c unloadLibraries() is ignored any call to @c loadLibraries() +will throw. + + +@subsection hooksComponentUnloadIssues Unload and Reload Issues + +Unloading a shared library works by unmapping the part of the process's +virtual address space in which the library lies. This may lead to +problems if there are still references to that address space elsewhere +in the process. + +In many operating systems, heap storage allowed by a shared library will +lie in the virtual address allocated to the library. This has implications +in the hooks framework because: + +- Argument information stored in a @c CalloutHandle by a callout in a library +may lie in the library's address space. +- Data modified in objects passed as arguments may lie in the address +space. For example, it is common for a DHCP callout to add "options" +to a packet: the memory allocated for those options will most likely +lie in library address space. + +The problem really arises because of the extensive use by Kea of boost +smart pointers. When the pointer is destroyed, the pointed-to memory is +deallocated. If the pointer points to address space that is unmapped because +a library has been unloaded, the deletion causes a segmentation fault. + +The hooks framework addresses the issue for CalloutHandles by keeping in +that object a shared pointer to the object controlling library unloading. +Although a library can be unloaded at any time, it is only when all +CalloutHandles that could possibly reference address space in the library +have been deleted that the library will actually be unloaded and the +address space unmapped. + +The hooks framework cannot solve the second issue as the objects in +question are under control of the component. It is up to the component +developer to ensure that all such objects have been destroyed before +libraries are reloaded. In extreme cases this may mean the component +suspending all processing of incoming requests until all currently +executing requests have completed and data object destroyed, reloading +the libraries, then resuming processing. + +Since Kea 1.7.10 the unload() entry point is called as the first phase +of unloading. This gives more chance to hooks writer to perform +necessary cleanup actions so the second phase, memory unmapping +can safely happen. The @c isc::hooks::unloadLibraries() function +was updated too to return false when at least one active callout +handle remained. + + +@section hooksComponentCallouts Component-Defined Callouts + +Previous sections have discussed callout registration by user libraries. +It is possible for a component to register its own functions (i.e. within +its own address space) as hook callouts. These functions are called +in exactly the same way as user callouts, being passed their arguments +though a CalloutHandle object. (Guidelines for writing callouts can be +found in @ref hooksdgDevelopersGuide.) + +A component can associate with a hook callouts that run either before +user-registered callouts or after them. Registration is done via a +@c isc::hooks::LibraryHandle object, a reference to one being obtained +through the methods @c isc::hooks::HooksManager::preCalloutLibraryHandle() +(for a handle to register callouts to run before the user library +callouts) or @c isc::hooks::HooksManager::postCalloutLibraryHandle() (for +a handle to register callouts to run after the user callouts). Use of +the @c LibraryHandle to register and deregister callouts is described in +@ref hooksdgLibraryHandle. + +Finally, it should be noted that callouts registered in this way only +remain registered until the next call to @c isc::hooks::loadLibraries(). +It is up to the component to re-register the callouts after this +method has been called. + +*/ diff --git a/src/lib/hooks/hooks_config.cc b/src/lib/hooks/hooks_config.cc new file mode 100644 index 0000000..c6bf9a2 --- /dev/null +++ b/src/lib/hooks/hooks_config.cc @@ -0,0 +1,127 @@ +// Copyright (C) 2017-2021 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <hooks/hooks_config.h> +#include <hooks/hooks_manager.h> + +using namespace std; +using namespace isc; +using namespace isc::data; + +namespace isc { +namespace hooks { + +void +HooksConfig::verifyLibraries(const Element::Position& position) const { + // The code used to follow this logic: + // + // Check if the list of libraries has changed. If not, nothing is done + // - the command "DhcpN libreload" is required to reload the same + // libraries (this prevents needless reloads when anything else in the + // configuration is changed). + // + // We no longer rely on this. Parameters can change. And even if the + // parameters stay the same, they could point to files that could + // change. We can skip loading routines only if there were and there still + // are no libraries specified. + vector<string> current_libraries = HooksManager::getLibraryNames(); + if (current_libraries.empty() && libraries_.empty()) { + return; + } + + // Library list has changed, validate each of the libraries specified. + vector<string> lib_names = isc::hooks::extractNames(libraries_); + vector<string> error_libs = HooksManager::validateLibraries(lib_names); + if (!error_libs.empty()) { + + // Construct the list of libraries in error for the message. + string error_list = error_libs[0]; + for (size_t i = 1; i < error_libs.size(); ++i) { + error_list += (string(", ") + error_libs[i]); + } + isc_throw(InvalidHooksLibraries, + "hooks libraries failed to validate - " + "library or libraries in error are: " + << error_list << " (" << position << ")"); + } +} + +void +HooksConfig::loadLibraries() const { + /// Commits the list of libraries to the configuration manager storage if + /// the list of libraries has changed. + /// @todo: Delete any stored CalloutHandles before reloading the + /// libraries + if (!HooksManager::loadLibraries(libraries_)) { + isc_throw(InvalidHooksLibraries, + "One or more hook libraries failed to load"); + } +} + +bool +HooksConfig::equal(const HooksConfig& other) const { + + /// @todo: This comparision assumes that the library order is not relevant, + /// so [ lib1, lib2 ] is equal to [ lib2, lib1 ]. However, this is not strictly + /// true, because callouts execution is called in other they're loaded. Therefore + /// changing the libraries order may change the server behavior. + /// + /// We don't have any libraries that are interacting (or would change their behavior + /// depending on the order in which their callouts are executed), so the code is + /// ok for now. + for (isc::hooks::HookLibsCollection::const_iterator this_it = libraries_.begin(); + this_it != libraries_.end(); ++this_it) { + bool match = false; + for (isc::hooks::HookLibsCollection::const_iterator other_it = + other.libraries_.begin(); other_it != other.libraries_.end(); ++other_it) { + if (this_it->first != other_it->first) { + continue; + } + if (isNull(this_it->second) && isNull(other_it->second)) { + match = true; + break; + } + if (isNull(this_it->second) || isNull(other_it->second)) { + continue; + } + if (this_it->second->equals(*other_it->second)) { + match = true; + break; + } + } + // No match found for the particular hooks library so return false. + if (!match) { + return (false); + } + } + return (true); +} + +ElementPtr +HooksConfig::toElement() const { + // hooks-libraries is a list of maps + ElementPtr result = Element::createList(); + // Iterate through libraries + for (HookLibsCollection::const_iterator hl = libraries_.begin(); + hl != libraries_.end(); ++hl) { + // Entries are maps + ElementPtr map = Element::createMap(); + // Set the library name + map->set("library", Element::create(hl->first)); + // Set parameters (not set vs set empty map) + if (!isNull(hl->second)) { + map->set("parameters", hl->second); + } + // Push to the list + result->add(map); + } + return (result); +} + +}; +}; diff --git a/src/lib/hooks/hooks_config.h b/src/lib/hooks/hooks_config.h new file mode 100644 index 0000000..837727e --- /dev/null +++ b/src/lib/hooks/hooks_config.h @@ -0,0 +1,108 @@ +// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef HOOKS_CONFIG_H +#define HOOKS_CONFIG_H + +#include <exceptions/exceptions.h> +#include <cc/data.h> +#include <cc/cfg_to_element.h> +#include <hooks/libinfo.h> + +namespace isc { +namespace hooks { + +/// @brief Exception thrown when a library failed to validate +class InvalidHooksLibraries : public Exception { + public: + InvalidHooksLibraries(const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) { }; +}; + +/// @brief Wrapper class that holds hooks libraries configuration +/// +/// This was moved from HooksLibrariesParser +/// +/// However, this class does more than just check the list of library names. +/// It does two other things: +/// # validate libraries +/// # load libraries +/// and it provides a toElement() method to unparse a configuration. +/// +/// @todo add toElement() unit tests +class HooksConfig : public isc::data::CfgToElement { +public: + /// @brief Default constructor. + /// + HooksConfig() : libraries_() { } + + /// @brief Adds additional hooks libraries. + /// + /// @param libname full filename with path to the library. + /// @param parameters map of parameters that configure the library. + void add(std::string libname, isc::data::ConstElementPtr parameters) { + libraries_.push_back(make_pair(libname, parameters)); + } + + /// @brief Provides access to the configured hooks libraries. + /// + /// @note The const reference returned is only valid as long as the + /// object that returned it. + const isc::hooks::HookLibsCollection& get() const { + return libraries_; + } + + /// @brief Removes all configured hooks libraries. + void clear() { + libraries_.clear(); + } + + /// @brief Compares two Hooks Config classes for equality + /// + /// @param other other hooksconfig to compare with + bool equal(const HooksConfig& other) const; + + /// @brief Verifies that libraries stored in libraries_ are valid. + /// + /// This method is a smart wrapper around @ref + /// isc::hooks::HooksManager::validateLibraries(). + /// It tries to validate all the libraries stored in libraries_. + /// + /// @param position position of the hooks-library map for error reporting + /// @throw InvalidHooksLibraries if any issue is discovered. + void verifyLibraries(const isc::data::Element::Position& position) const; + + /// @brief Commits hooks libraries configuration. + /// + /// This method calls necessary methods in HooksManager that will unload + /// any libraries that may be currently loaded and will load the actual + /// libraries. Providing that the specified libraries are valid and are + /// different to those already loaded, this method loads the new set of + /// libraries (and unloads the existing set). + /// + /// @throw InvalidHooksLibraries if the call to HooksManager fails. + void loadLibraries() const; + + /// @brief Unparse a configuration object + /// + /// Returns an element which must parse into the same object, i.e. + /// @code + /// for all valid config C parse(parse(C)->toElement()) == parse(C) + /// @endcode + /// + /// @return a pointer to a configuration which can be parsed into + /// the initial configuration object + isc::data::ElementPtr toElement() const; + +private: + /// @brief List of hooks libraries with their configuration parameters + isc::hooks::HookLibsCollection libraries_; +}; + +}; +}; + +#endif // HOOKS_CONFIG_H diff --git a/src/lib/hooks/hooks_log.cc b/src/lib/hooks/hooks_log.cc new file mode 100644 index 0000000..141ab63 --- /dev/null +++ b/src/lib/hooks/hooks_log.cc @@ -0,0 +1,28 @@ +// Copyright (C) 2011-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/. + +/// Defines the logger used by the Hooks + +#include <config.h> + +#include <hooks/hooks_log.h> +#include <log/macros.h> + +namespace isc { +namespace hooks { + +isc::log::Logger hooks_logger("hooks"); + +isc::log::Logger callouts_logger("callouts"); + +extern const int HOOKS_DBG_TRACE = isc::log::DBGLVL_TRACE_BASIC; +extern const int HOOKS_DBG_CALLS = isc::log::DBGLVL_TRACE_BASIC_DATA; +extern const int HOOKS_DBG_EXTENDED_CALLS = isc::log::DBGLVL_TRACE_DETAIL_DATA; + + +} // namespace hooks +} // namespace isc + diff --git a/src/lib/hooks/hooks_log.h b/src/lib/hooks/hooks_log.h new file mode 100644 index 0000000..6b567c9 --- /dev/null +++ b/src/lib/hooks/hooks_log.h @@ -0,0 +1,49 @@ +// Copyright (C) 2013-2015,2017 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef HOOKS_LOG_H +#define HOOKS_LOG_H + +#include <log/macros.h> +#include <hooks/hooks_messages.h> + +namespace isc { +namespace hooks { + +/// @brief Hooks debug Logging levels +/// +/// Defines the levels used to output debug messages in the Hooks framework. +/// Note that higher numbers equate to more verbose (and detailed) output. + +// The first level traces normal operations, +extern const int HOOKS_DBG_TRACE; + +// The next level traces each call to hook code. +extern const int HOOKS_DBG_CALLS; + +// Additional information on the calls. Report each call to a callout (even +// if there are multiple callouts on a hook) and each status return. +extern const int HOOKS_DBG_EXTENDED_CALLS; + + +/// @brief Hooks Logger +/// +/// Define the logger used to log messages. We could define it in multiple +/// modules, but defining in a single module and linking to it saves time and +/// space. +extern isc::log::Logger hooks_logger; + +/// @brief Callouts logger +/// +/// This is the specialized logger used to log messages pertaining to the +/// callouts execution. In particular, it logs when the callout is invoked +/// and when it ends. It also logs the callout execution times. +extern isc::log::Logger callouts_logger; + +} // namespace hooks +} // namespace isc + +#endif // HOOKS_LOG_H diff --git a/src/lib/hooks/hooks_maintenance.dox b/src/lib/hooks/hooks_maintenance.dox new file mode 100644 index 0000000..75e1227 --- /dev/null +++ b/src/lib/hooks/hooks_maintenance.dox @@ -0,0 +1,381 @@ +// Copyright (C) 2013-2020 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +// Note: the prefix "hooksmg" to all labels is an abbreviation for "Hooks +// Maintenance Guide" and is used to prevent a clash with symbols in any +// other Doxygen file. + +/** + @page hooksmgMaintenanceGuide Hooks Maintenance Guide + + @section hooksmgIntroduction Introduction + + This document is aimed at Kea maintainers responsible for the hooks + system. It provides an overview of the classes that make up the hooks + framework and notes important aspects of processing. More detailed + information can be found in the source code. + + It is assumed that the reader is familiar with the contents of the @ref + hooksdgDevelopersGuide and the @ref hooksComponentDeveloperGuide. + + @section hooksmgObjects Hooks Framework Objects + + The relationships between the various objects in the hooks framework + is shown below: + + @image html HooksUml.png "High-Level Class Diagram of the Hooks Framework" + + (To avoid clutter, the @ref hooksmgServerHooks object, used to pass + information about registered hooks to the components, is not shown on + the diagram.) + + The hooks framework objects can be split into user-side objects and + server-side objects. The former are those objects used or referenced + by user-written hooks libraries. The latter are those objects used in + the hooks framework. + + @subsection hooksmgUserObjects User-Side Objects + + The user-side code is able to access two objects in the framework, + the @ref hooksmgCalloutHandle and the @ref hooksmgLibraryHandle. + The @ref hooksmgCalloutHandle is used to pass data between the Kea + component and the loaded library; the @ref hooksmgLibraryHandle is used + for registering callouts. + + @subsubsection hooksmgCalloutHandle Callout Handle + + The @ref isc::hooks::CalloutHandle has two functions: passing arguments + between the Kea component and the user-written library, and storing + per-request context between library calls. In both cases the data is + stored in a @c std::map structure, keyed by argument (or context item) name. + The actual data is stored in a @c boost::any object, which allows any + data type to be stored, although a penalty for this flexibility is + the restriction (mentioned in the @ref hooksdgDevelopersGuide) that + the type of data retrieved must be identical (and not just compatible) + with that stored. + + The storage of context data is slightly complex because there is + separate context for each user library. For this reason, the @ref + hooksmgCalloutHandle has multiple maps, one for each library loaded. + The maps are stored in another map, the appropriate map being identified + by the "current library index" (this index is explained further below). + The reason for the second map (rather than a structure such as a vector) + is to avoid creating individual context maps unless needed; given the + key to the map (in this case the current library index) accessing an + element in a map using the operator[] method returns the element in + question if it exists, or creates a new one (and stores it in the map) + if its doesn't. + + @subsubsection hooksmgLibraryHandle Library Handle + + Little more than a restricted interface to the @ref + hooksmgCalloutManager, the @ref isc::hooks::LibraryHandle allows a + callout to register and deregister callouts. However, there are some + quirks to callout registration which, although the processing involved + is in the @ref hooksmgCalloutManager, are best described here. + + Firstly, a callout can be deregistered by a function within a user + library only if it was registered by a function within that library. That + is to say, if library A registers the callout A_func() on hook "alpha" + and library B registers B_func(), functions within library A are only + able to remove A_func() (and functions in library B remove B_func()). + The restriction - here to prevent one library interfering with the + callouts of another - is enforced by means of the current library index. + As described below, each entry in the vector of callouts associated with + a hook is a pair object, comprising a pointer to the callout and + the index of the library with which it is associated. A callout + can only modify entries in that vector where the current library index + matches the index element of the pair. + + A second quirk is that when dynamically modifying the list of callouts, + the change only takes effect when the current call out from the server + completes. To clarify this, suppose that functions A_func(), B_func() + and C_func() are registered on a hook, and the server executes a callout + on the hook. Suppose also during this call, A_func() removes the callout + C_func() and that B_func() adds D_func(). As changes only take effect + when the current call out completes, the user callouts executed will be + A_func(), B_func() then C_func(). When the server calls the hook callouts + again, the functions executed will be A_func(), B_func() and D_func(). + + This restriction is down to implementation. When a set of callouts on a hook + is being called, the @ref hooksmgCalloutManager iterates through a + vector (the "callout vector") of (index, callout pointer) pairs. Since + registration or deregistration of a callout on that hook would change the + vector (and so potentially invalidate the iterators used to access the it), + a copy of the vector is taken before the iteration starts. The @ref + hooksmgCalloutManager iterates over this copy while any changes made + by the callout registration functions affect the relevant callout vector. + Such approach was chosen because of performance considerations. + + @subsection hooksmgServerObjects Server-Side Objects + + Those objects are not accessible by user libraries. Please do not + attempt to use them if you are developing user callouts. + + @subsubsection hooksmgServerHooks Server Hooks + + The singleton @ref isc::hooks::ServerHooks object is used to register + hooks. It is little more than a wrapper around a map of (hook index, + hook name), generating a unique number (the hook index) for each + hook registered. It also handles the registration of the pre-defined + context_create and context_destroy hooks. + + In operation, the @ref hooksmgHooksManager provides a thin wrapper + around it, so that the Kea component developer does not have to + worry about another object. + + @subsubsection hooksmgLibraryManager Library Manager + + An @ref isc::hooks::LibraryManager is created by the @ref + hooksmgHooksManager object for each shared library loaded. It + controls the loading and unloading of the library and in essence + represents the library in the hooks framework. It also handles the + registration of the standard callouts (functions in the library with + the same name as the hook name). + + Of particular importance is the "library's index", a number associated + with the library. This is passed to the LibraryManager at creation + time and is used to tag the callout pointers. It is discussed + further below. + + As the @c LibraryManager provides all the methods needed to manage the + shared library, it is the natural home for the static @c validateLibrary() + method. The function called the parsing of the Kea configuration, when + the "hooks-libraries" element is processed. It checks that shared library + exists, that it can be opened, that it contains the @c version() function + and that that function returns a valid value. It then closes the shared + library and returns an appropriate indication as to the library status. + + @subsubsection hooksmgLibraryManagerCollection Library Manager Collection + + The hooks framework can handle multiple libraries and as + a result will create a @ref hooksmgLibraryManager for each + of them. The collection of LibraryManagers is managed by the + @ref isc::hooks::LibraryManagerCollection object which, in most + cases has a method corresponding to a @ref hooksmgLibraryManager + method, e.g. it has a @c loadLibraries() that corresponds to the @ref + hooksmgLibraryManager's loadLibrary() call. As would be expected, methods + on the @c LibraryManagerCollection iterate through all specified libraries, + calling the corresponding LibraryManager method for each library. + + One point of note is that @c LibraryManagerCollection operates on an "all + or none" principle. When @c loadLibraries() is called, on exit either all + libraries have been successfully opened or none of them have. There + is no use-case in Kea where, after a user has specified the shared + libraries they want to load, the system will operate with only some of + them loaded. + + The @c LibraryManagerCollection is the place where each library's index is set. + Each library is assigned a number ranging from 1 through to the number + of libraries being loaded. As mentioned in the previous section, this + index is used to tag callout pointers, something that is discussed + in the next section. + + (Whilst on the subject of library index numbers, two additional + numbers - 0 and @c INT_MAX - are also valid as "current library index". + For flexibility, the Kea component is able to register its own + functions as hook callouts. It does this by obtaining a suitable @ref + hooksmgLibraryHandle from the @ref hooksmgHooksManager. A choice + of two is available: one @ref hooksmgLibraryHandle (with an index + of 0) can be used to register a callout on a hook to execute before + any user-supplied callouts. The second (with an index of @c INT_MAX) + is used to register a callout to run after user-specified callouts. + Apart from the index number, the hooks framework does not treat these + callouts any differently from user-supplied ones.) + + @subsubsection hooksmgCalloutManager Callout Manager + + The @ref isc::hooks::CalloutManager is the core of the framework insofar + as the registration and calling of callouts is concerned. + + It maintains a "hook vector" - a vector with one element for + each registered hook. Each element in this vector is itself a + vector (the callout vector), each element of which is a pair of + (library index, callback pointer). When a callout is registered, the + CalloutManager's current library index is used to supply the "library + index" part of the pair. The library index is set explicitly by the + @ref hooksmgLibraryManager prior to calling the user library's load() + function (and prior to registering the standard callbacks). + + The situation is slightly more complex when a callout is executing. In + order to execute a callout, the CalloutManager's @c callCallouts() + method must be called. This iterates through the callout vector for + a hook and for each element in the vector, uses the "library index" + part of the pair to set the "current library index" before calling the + callout function recorded in the second part of the pair. In most cases, + the setting of the library index has no effect on the callout. However, + if the callout wishes to dynamically register or deregister a callout, + the @ref hooksmgLibraryHandle (see above) calls a method on the + @ref hooksmgCalloutManager which in turn uses that information. + + @subsubsection hooksmgHooksManager Hooks Manager + + The @ref isc::hooks::HooksManager is the main object insofar as the + server is concerned. It controls the creation of the library-related + objects and provides the framework in which they interact. It also + provides a shell around objects such as @ref hooksmgServerHooks so that all + interaction with the hooks framework by the server is through the + HooksManager object. Apart from this, it supplies no functionality to + the hooks framework. + + @section hooksmgOtherIssues Other Issues + + @subsection hooksmgMemoryAllocation Memory Allocation + + Unloading a shared library works by unmapping the part of the process's + virtual address space in which the library lies. This may lead to + problems if there are still references to that address space elsewhere + in the process. + + In many operating systems, heap storage allowed by a shared library + will lie in the virtual address allocated to the library. This has + implications in the hooks framework because: + + - Argument information stored in a @ref hooksmgCalloutHandle by a + callout in a library may lie in the library's address space. + + - Data modified in objects passed as arguments may lie in the address + space. For example, it is common for a DHCP callout to add "options" + to a packet: the memory allocated for those options will most likely + lie in library address space. + + The problem really arises because of the extensive use by Kea of + boost smart pointers. When the pointer is destroyed, the pointed-to + memory is deallocated. If the pointer points to address space that is + unmapped because a library has been unloaded, the deletion causes a + segmentation fault. + + The hooks framework addresses the issue for the @ref hooksmgCalloutHandle + by keeping in that object a shared pointer to the object controlling + library unloading (the @ref hooksmgLibraryManagerCollection). Although + the libraries can be unloaded at any time, it is only when every + @ref hooksmgCalloutHandle that could possibly reference address space in the + library have been deleted that the library will actually be unloaded + and the address space unmapped. + + The hooks framework cannot solve the second issue as the objects in + question are under control of the Kea server incorporating the + hooks. It is up to the server developer to ensure that all such objects + have been destroyed before libraries are reloaded. In extreme cases + this may mean the server suspending all processing of incoming requests + until all currently executing requests have completed and data object + destroyed, reloading the libraries, then resuming processing. + + Since Kea 1.7.10 the unload() entry point is called as the first phase + of unloading. This gives more chance to hooks writer to perform + necessary cleanup actions so the second phase, memory unmapping + can safely happen. The @c isc::hooks::unloadLibraries() function + was updated too to return false when at least one active callout + handle remained. + + @subsection hooksmgStaticLinking Hooks and Statically-Linked Kea + + Kea has the configuration option to allow static linking. What this + means is that it links against the static Kea libraries and not + the sharable ones - although it links against the sharable system + libraries like "libc" and "libstdc++" and well as the sharable libraries + for third-party packages such as log4cplus and MySql. + + Static linking poses a problem for dynamically-loaded hooks libraries + as some of the code in them - in particular the hooks framework and + the logging code - depend on global objects created within the Kea + libraries. In the normal course of events (Kea linked against + shared libraries), when Kea is run and the operating system loads + a Kea shared library containing a global object, address space + is assigned for it. When the hooks framework loads a user-library + linked against the same Kea shared library, the operating system + recognizes that the library is already loaded (and initialized) and + uses its definition of the global object. Thus both the code in the + Kea image and the code in the user-written shared library + reference the same object. + + If Kea is statically linked, the linker allocates address space + in the Kea image for the global object and does not include any + reference to the shared library containing it. When Kea now loads + the user-written shared library - and so loads the Kea library code + containing the global object - the operating system does not know that + the object already exists. Instead, it allocates new address space. + The version of Kea in memory therefore has two copies of the object: + one referenced by code in the Kea image, and one referenced by code + in the user-written hooks library. This causes problems - information + put in one copy is not available to the other. + + Particular problems were encountered with global objects the hooks library + and in the logging library, so some code to alleviate the problem has been + included. + + The issue in the hooks library is the singleton @ref + isc::hooks::ServerHooks object, used by the user-written hooks library + if it attempts to register or deregister callouts. The contents of the + singleton - the names of the hook points and their index - are set by + the relevant Kea server; this information is not available in the + singleton created in the user's hooks library. + + Within the code users by the user's hooks library, the @c ServerHooks + object is used by @ref isc::hooks::CalloutHandle and @ref + isc::hooks::CalloutManager objects. Both these objects are passed to the + hooks library code when a callout is called: the former directly through + the callout argument list, the latter indirectly as a pointer to it is + stored in the CalloutHandle. This allows a solution to the problem: + instead of accessing the singleton via @c ServerHooks::getServerHooks(), + the constructors of these objects store a reference to the singleton + @c ServerHooks when they are created and use that reference to access + @c ServerHooks data. Since both @c CalloutHandle and @c CalloutManager are + created in the statically-linked Kea server, use of the reference + means that it is the singleton within the server - and not the one + within the user's hooks library - that is referenced. + + The solution of the logging problem is not so straightforward. Within + Kea, there are two logging components, the Kea logging framework + and the log4cplus libraries. Owing to static linking, there are two + instances of the former; but as static linking uses shared libraries of + third-party products, there is one instance of the latter. What further + complicates matters is that initialization of the logging framework is + in two parts: static initialization and run-time initialization. + + The logging initialization comprises the following: + + -# Static initialization of the log4cplus global variables. + -# Static initialization of messages in the various Kea libraries. + -# Static initialization of logging framework. + -# Run-time initialization of the logging framework. + -# Run-time initialization of log4cplus + + As both the Kea server and the user-written hooks libraries use the + log4cplus shared library, item 1 - the static initialization of the log4cplus + global variables is performed once. + + The next two tasks - static initialization of the messages in the Kea + libraries and the static initialization of the logging framework - + are performed twice, once in the context of the Kea server and + once in the context of the hooks library. For this reason, run-time + initialization of the logging framework needs to be performed twice, + once in the context of the Kea server and once in the context of the + user-written hooks library. However, the standard logging framework + initialization code also performs the last task, initialization of + log4cplus, something that causes problems if executed more than once. + + To get round this, the function @ref isc::hooks::hooksStaticLinkInit() + has been written. It executes the only part of the logging framework + run-time initialization that actually pertains to the logging framework + and not log4cplus, namely loading the message dictionary with the + statically-initialized messages in the Kea libraries. + This should be executed by any hooks library linking against a statically + initialized Kea. (In fact, running it against a dynamically-linked + Kea should have no effect, as the load operation discards any duplicate + message entries.) The hooks library tests do this, the code being + conditionally compiled within a test of the @c USE_STATIC_LINK macro, set + by the configure script. + + @note Not everything is completely rosy with logging and static linking. + In particular, there appears to be an issue with the scenario where a + user-written hooks library is run by a statically-linked Kea and then + unloaded. As far as can be determined, on unload the system attempts to + delete the same logger twice. This is alleviated by explicitly clearing + the loggerptr_ variable in the @ref isc::log::Logger destructor, but there + is a suspicion that some memory might be lost in these circumstances. + This is still under investigation. +*/ diff --git a/src/lib/hooks/hooks_manager.cc b/src/lib/hooks/hooks_manager.cc new file mode 100644 index 0000000..82b741e --- /dev/null +++ b/src/lib/hooks/hooks_manager.cc @@ -0,0 +1,275 @@ +// Copyright (C) 2013-2020 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <hooks/callout_handle.h> +#include <hooks/callout_manager.h> +#include <hooks/library_handle.h> +#include <hooks/library_manager_collection.h> +#include <hooks/hooks_manager.h> +#include <hooks/server_hooks.h> + +#include <boost/shared_ptr.hpp> +#include <boost/weak_ptr.hpp> + +#include <string> +#include <vector> + +using namespace std; + +namespace isc { +namespace hooks { + +// Constructor + +HooksManager::HooksManager() : test_mode_(false) { + // Nothing present, so create the collection with any empty set of + // libraries, and get the CalloutManager. + HookLibsCollection libraries; + lm_collection_.reset(new LibraryManagerCollection(libraries)); + lm_collection_->loadLibraries(); + callout_manager_ = lm_collection_->getCalloutManager(); +} + +// Return reference to singleton hooks manager. + +HooksManager& +HooksManager::getHooksManager() { + static HooksManager manager; + return (manager); +} + +// Are callouts present? + +bool +HooksManager::calloutsPresentInternal(int index) { + return (callout_manager_->calloutsPresent(index)); +} + +bool +HooksManager::calloutsPresent(int index) { + return (getHooksManager().calloutsPresentInternal(index)); +} + +bool +HooksManager::commandHandlersPresentInternal(const std::string& command_name) { + return (callout_manager_->commandHandlersPresent(command_name)); +} + +bool +HooksManager::commandHandlersPresent(const std::string& command_name) { + return (getHooksManager().commandHandlersPresentInternal(command_name)); +} + +// Call the callouts + +void +HooksManager::callCalloutsInternal(int index, CalloutHandle& handle) { + callout_manager_->callCallouts(index, handle); +} + +void +HooksManager::callCallouts(int index, CalloutHandle& handle) { + getHooksManager().callCalloutsInternal(index, handle); +} + +void +HooksManager::callCommandHandlersInternal(const std::string& command_name, + CalloutHandle& handle) { + callout_manager_->callCommandHandlers(command_name, handle); +} + +void +HooksManager::callCommandHandlers(const std::string& command_name, + CalloutHandle& handle) { + getHooksManager().callCommandHandlersInternal(command_name, handle); +} + +// Load the libraries. This will delete the previously-loaded libraries +// (if present) and load new ones. If loading libraries fails, initialize with +// empty list. + +bool +HooksManager::loadLibrariesInternal(const HookLibsCollection& libraries) { + if (test_mode_) { + return (true); + } + + ServerHooks::getServerHooks().getParkingLotsPtr()->clear(); + + // Keep a weak pointer on the existing library manager collection. + boost::weak_ptr<LibraryManagerCollection> weak_lmc(lm_collection_); + + // Create the library manager collection. + lm_collection_.reset(new LibraryManagerCollection(libraries)); + + // If there was another owner the previous library manager collection + // was not destroyed and libraries not closed. + if (!weak_lmc.expired()) { + isc_throw(LibrariesStillOpened, "some libraries are still opened"); + } + + // Load the libraries. + bool status = lm_collection_->loadLibraries(); + + if (status) { + // ... and obtain the callout manager for them if successful. + callout_manager_ = lm_collection_->getCalloutManager(); + } else { + // Unable to load libraries, reset to state before this function was + // called. + static_cast<void>(unloadLibrariesInternal()); + } + + return (status); +} + +bool +HooksManager::loadLibraries(const HookLibsCollection& libraries) { + return (getHooksManager().loadLibrariesInternal(libraries)); +} + +// Unload the libraries. This just deletes all internal objects (which will +// cause the libraries to be unloaded) and initializes them with empty list if +// requested. + +bool +HooksManager::unloadLibrariesInternal() { + if (test_mode_) { + return (true); + } + + ServerHooks::getServerHooks().getParkingLotsPtr()->clear(); + + // Keep a weak pointer on the existing library manager collection. + boost::weak_ptr<LibraryManagerCollection> weak_lmc(lm_collection_); + + // Create the collection with any empty set of libraries. + HookLibsCollection libraries; + lm_collection_.reset(new LibraryManagerCollection(libraries)); + + // If there was another owner the previous library manager collection + // was not destroyed and libraries not closed. + boost::shared_ptr<LibraryManagerCollection> still_here = weak_lmc.lock(); + if (still_here) { + // Restore the library manager collection. + lm_collection_ = still_here; + return (false); + } + + // Load the empty set of libraries. + lm_collection_->loadLibraries(); + + // Get the CalloutManager. + callout_manager_ = lm_collection_->getCalloutManager(); + + return (true); +} + +bool +HooksManager::unloadLibraries() { + return (getHooksManager().unloadLibrariesInternal()); +} + +void +HooksManager::prepareUnloadLibrariesInternal() { + if (test_mode_) { + return; + } + + static_cast<void>(lm_collection_->prepareUnloadLibraries()); +} + +void +HooksManager::prepareUnloadLibraries() { + getHooksManager().prepareUnloadLibrariesInternal(); +} + +// Create a callout handle + +boost::shared_ptr<CalloutHandle> +HooksManager::createCalloutHandleInternal() { + return (boost::make_shared<CalloutHandle>(callout_manager_, lm_collection_)); +} + +boost::shared_ptr<CalloutHandle> +HooksManager::createCalloutHandle() { + return (getHooksManager().createCalloutHandleInternal()); +} + +// Get the list of the names of loaded libraries. + +std::vector<std::string> +HooksManager::getLibraryNamesInternal() const { + return (lm_collection_->getLibraryNames()); +} + +HookLibsCollection +HooksManager::getLibraryInfoInternal() const { + return (lm_collection_->getLibraryInfo()); +} + +std::vector<std::string> +HooksManager::getLibraryNames() { + return (getHooksManager().getLibraryNamesInternal()); +} + +HookLibsCollection +HooksManager::getLibraryInfo() { + return (getHooksManager().getLibraryInfoInternal()); +} + +// Shell around ServerHooks::registerHook() + +int +HooksManager::registerHook(const std::string& name) { + return (ServerHooks::getServerHooks().registerHook(name)); +} + +// Return pre- and post- library handles. + +isc::hooks::LibraryHandle& +HooksManager::preCalloutsLibraryHandleInternal() { + return (callout_manager_->getPreLibraryHandle()); +} + +isc::hooks::LibraryHandle& +HooksManager::preCalloutsLibraryHandle() { + return (getHooksManager().preCalloutsLibraryHandleInternal()); +} + +isc::hooks::LibraryHandle& +HooksManager::postCalloutsLibraryHandleInternal() { + return (callout_manager_->getPostLibraryHandle()); +} + +isc::hooks::LibraryHandle& +HooksManager::postCalloutsLibraryHandle() { + return (getHooksManager().postCalloutsLibraryHandleInternal()); +} + +// Validate libraries + +std::vector<std::string> +HooksManager::validateLibraries(const std::vector<std::string>& libraries) { + return (LibraryManagerCollection::validateLibraries(libraries)); +} + +// Test mode + +void +HooksManager::setTestMode(bool mode) { + getHooksManager().test_mode_ = mode; +} + +bool +HooksManager::getTestMode() { + return (getHooksManager().test_mode_); +} + +} // namespace util +} // namespace isc diff --git a/src/lib/hooks/hooks_manager.h b/src/lib/hooks/hooks_manager.h new file mode 100644 index 0000000..647e14a --- /dev/null +++ b/src/lib/hooks/hooks_manager.h @@ -0,0 +1,499 @@ +// Copyright (C) 2013-2021 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef HOOKS_MANAGER_H +#define HOOKS_MANAGER_H + +#include <hooks/server_hooks.h> +#include <hooks/libinfo.h> + +#include <boost/noncopyable.hpp> +#include <boost/shared_ptr.hpp> + +#include <string> +#include <vector> + +namespace isc { +namespace hooks { + +/// @brief Libraries still opened. +/// +/// Thrown if an attempt is made to load libraries when some are still +/// in memory likely because they were not unloaded (logic error in Kea) +/// or they have some visible dangling pointers (logic error in a hook +/// library). +class LibrariesStillOpened : public Exception { +public: + LibrariesStillOpened(const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) {} +}; + +// Forward declarations +class CalloutHandle; +class CalloutManager; +class LibraryHandle; +class LibraryManagerCollection; + +/// @brief Hooks Manager +/// +/// This is the overall manager of the hooks framework and is the main class +/// used by a Kea module when handling hooks. It is responsible for the +/// loading and unloading of user libraries, and for calling the callouts on +/// each hook point. +/// +/// The class is a singleton, the single instance of the object being accessed +/// through the static getHooksManager() method. + +class HooksManager : boost::noncopyable { +public: + + /// @brief Load and reload libraries + /// + /// Loads the list of libraries into the server address space. For each + /// library, the "standard" functions (ones with the same names as the + /// hook points) are configured and the libraries' "load" function + /// called. + /// + /// @note this method now requires the libraries are unloaded before + /// being called. + /// + /// If any library fails to load, an error message will be logged. The + /// remaining libraries will be loaded if possible. + /// + /// @param libraries List of libraries to be loaded. The order is + /// important, as it determines the order that callouts on the same + /// hook will be called. + /// + /// @return true if all libraries loaded without a problem, false if one or + /// more libraries failed to load. In the latter case, message will + /// be logged that give the reason. + /// @throw LibrariesStillOpened when some libraries are already loaded. + static bool loadLibraries(const HookLibsCollection& libraries); + + /// @brief Unload libraries + /// + /// Unloads the loaded libraries and leaves the hooks subsystem in the + /// state it was after construction but before loadLibraries() is called. + /// + /// @note: This method should be called after @ref prepareUnloadLibraries + /// in order to destroy appropriate objects. See notes for + /// the class LibraryManager for pitfalls. + /// @note: if even after @ref prepareUnloadLibraries there are still + /// visible pointers (i.e. callout handles owning the + /// library manager collection) the method will fail to close + /// libraries and returns false. It is a fatal error as there + /// is no possible recovery. It is a logic error in the hook + /// code too so the solution is to fix it and to restart + /// the server with a correct hook library binary. + /// + /// @return true if all libraries unloaded successfully, false if they + /// are still in memory. + static bool unloadLibraries(); + + /// @brief Prepare the unloading of libraries + /// + /// Calls the unload functions when they exist and removes callouts. + /// + /// @note: after the call to this method there should be no visible + /// dangling pointers (i.e. callout handles owning the library + /// manager collection) nor invisible dangling pointers. + /// In the first case it will be impossible to close libraries + /// so they will remain in memory, in the second case a crash + /// is possible in particular at exit time during global + /// object finalization. In both cases the hook library code + /// causing the problem is incorrect and must be fixed. + /// @note: it is a logic error to not call this method before + /// @ref unloadLibraries even it hurts only with particular + /// hooks libraries. + static void prepareUnloadLibraries(); + + /// @brief Are callouts present? + /// + /// Checks loaded libraries and returns true if at lease one callout + /// has been registered by them for the given hook. + /// + /// @param index Hooks index for which callouts are checked. + /// + /// @return true if callouts are present, false if not. + /// @throw NoSuchHook Given index does not correspond to a valid hook. + static bool calloutsPresent(int index); + + /// @brief Checks if control command handlers are present for the + /// specified command. + /// + /// @param command_name Command name for which handlers' presence should + /// be checked. + /// + /// @return true if there is a hook point associated with the specified + /// command and callouts/command handlers are installed for this hook + /// point, false otherwise. + static bool commandHandlersPresent(const std::string& command_name); + + /// @brief Calls the callouts for a given hook + /// + /// Iterates through the library handles and calls the callouts associated + /// with the given hook index. + /// + /// @note This method invalidates the current library index set with + /// setLibraryIndex(). + /// + /// @param index Index of the hook to call. + /// @param handle Reference to the CalloutHandle object for the current + /// object being processed. + static void callCallouts(int index, CalloutHandle& handle); + + /// @brief Calls the callouts/command handlers for a given command name. + /// + /// Iterates through the library handles and calls the command handlers + /// associated with the given command. It expects that the hook point + /// for this command exists (with a name being a command_name prefixed + /// with a dollar sign and with hyphens replaced with underscores). + /// + /// @param command_name Command name for which handlers should be called. + /// @param handle Reference to the CalloutHandle object for the current + /// object being processed. + /// + /// @throw NoSuchHook if the hook point for the specified command does + /// not exist. + static void callCommandHandlers(const std::string& command_name, + CalloutHandle& handle); + + /// @brief Return pre-callouts library handle + /// + /// Returns a library handle that can be used by the server to register + /// callouts on a hook that are called _before_ any callouts belonging + /// to a library. + /// + /// @note Both the reference returned and the callouts registered with + /// this handle only remain valid until the next loadLibraries() or + /// unloadLibraries() call. If the callouts are to remain registered + /// after this time, a new handle will need to be obtained and the + /// callouts re-registered. + /// + /// @return Reference to library handle associated with pre-library callout + /// registration. + static LibraryHandle& preCalloutsLibraryHandle(); + + /// @brief Return post-callouts library handle + /// + /// Returns a library handle that can be used by the server to register + /// callouts on a hook that are called _after any callouts belonging + /// to a library. + /// + /// @note Both the reference returned and the callouts registered with + /// this handle only remain valid until the next loadLibraries() or + /// unloadLibraries() call. If the callouts are to remain registered + /// after this time, a new handle will need to be obtained and the + /// callouts re-registered. + /// + /// @return Reference to library handle associated with post-library callout + /// registration. + static LibraryHandle& postCalloutsLibraryHandle(); + + /// @brief Return callout handle + /// + /// Returns a callout handle to be associated with a request passed round + /// the system. + /// + /// @note This handle is valid only after a loadLibraries() call and then + /// only up to the next loadLibraries() call. + /// + /// @return Shared pointer to a CalloutHandle object. + static boost::shared_ptr<CalloutHandle> createCalloutHandle(); + + /// @brief Register Hook + /// + /// This is just a convenience shell around the ServerHooks::registerHook() + /// method. It - along with the definitions of the two hook indexes for + /// the context_create and context_destroy methods - means that server + /// authors only need to deal with HooksManager and CalloutHandle, and not + /// include any other hooks framework classes. + /// + /// @param name Name of the hook + /// + /// @return Index of the hook, to be used in subsequent hook-related calls. + /// This will be greater than or equal to zero (so allowing a + /// negative value to indicate an invalid index). + /// + /// @throws DuplicateHook A hook with the same name has already been + /// registered. + static int registerHook(const std::string& name); + + /// @brief Return list of loaded libraries + /// + /// Returns the names of the loaded libraries. + /// + /// @return List of loaded library names. + static std::vector<std::string> getLibraryNames(); + + /// @brief Return list of loaded libraries with its parameters. + /// + /// Returns the names of the loaded libraries and their parameters. + /// + /// @return List of loaded libraries (names + parameters) + static HookLibsCollection getLibraryInfo(); + + /// @brief Validate library list + /// + /// For each library passed to it, checks that the library can be opened + /// and that the "version" function is present and gives the right answer. + /// Each library is closed afterwards. + /// + /// This is used during the configuration parsing - when the list of hooks + /// libraries is changed, each of the new libraries is checked before the + /// change is committed. + /// + /// @param libraries List of libraries to be validated. + /// + /// @return An empty vector if all libraries validated. Otherwise it + /// holds the names of the libraries that failed validation. + static std::vector<std::string> validateLibraries( + const std::vector<std::string>& libraries); + + /// Index numbers for pre-defined hooks. + static const int CONTEXT_CREATE = ServerHooks::CONTEXT_CREATE; + static const int CONTEXT_DESTROY = ServerHooks::CONTEXT_DESTROY; + + /// @brief Park an object (packet). + /// + /// The typical use case for parking an object is when the server needs to + /// suspend processing of a packet to perform an asynchronous operation, + /// before the response is sent to a client. In this case, the object type + /// is a pointer to the processed packet. Therefore, further in this + /// description we're going to refer to the parked objects as "parked + /// packets". However, any other object can be parked if necessary. + /// + /// The following is the typical flow when packets are parked. The callouts + /// responsible for performing an asynchronous operation signal this need + /// to the server by returning the status @c NEXT_STEP_PARK, which instructs + /// the server to call this function. This function stops processing the + /// packet and puts it in, so called, parking lot. In order to be able to + /// resume the packet processing when instructed by the hooks library, the + /// parked packet is associated with the callback which, when called, will + /// resume packet processing. + /// + /// The hook library must increase a reference count on the parked object + /// by calling @c ParkingLotHandle::reference prior to returning the + /// @c NEXT_STEP_PARK status. This is important when multiple callouts + /// are installed on the same hook point and each of them schedules an + /// asynchronous operation. In this case, the packet must not be unparked + /// until all hook libraries call @c ParkingLotHandle::unpark to mark + /// that respective asynchronous operations are completed. + /// + /// @tparam Type of the parked object. + /// @param hook_name name of the hook point for which the packet is parked. + /// @param parked_object packet to be parked. + /// @param unpark_callback callback invoked when the packet is unparked. + template<typename T> + static void park(const std::string& hook_name, T parked_object, + std::function<void()> unpark_callback) { + ServerHooks::getServerHooks(). + getParkingLotPtr(hook_name)->park(parked_object, unpark_callback); + } + + /// @brief Forces unparking the object (packet). + /// + /// This method unparks the object regardless of the reference counting + /// value. This is used in the situations when the callouts fail to unpark + /// the packet for some reason. + /// + /// @tparam T type of the parked object. + /// @param hook_name name of the hook point for which the packet is parked. + /// @param parked_object parked object to be unparked. + /// @return true if the specified object has been found, false otherwise. + template<typename T> + static bool unpark(const std::string& hook_name, T parked_object) { + return (ServerHooks::getServerHooks(). + getParkingLotPtr(hook_name)->unpark(parked_object, true)); + } + + /// @brief Removes parked object without calling a callback. + /// + /// @tparam T type of the parked object. + /// @param hook_name name of the hook point for which the packet is parked. + /// @param parked_object parked object to be removed. + /// @return true if the specified object has been found false otherwise. + template<typename T> + static bool drop(const std::string& hook_name, T parked_object) { + return (ServerHooks::getServerHooks(). + getParkingLotPtr(hook_name)->drop(parked_object)); + } + + /// @brief Increases reference counter for the parked object. + /// + /// Reference counter must be increased at least to 1 before the @c park() + /// method can be called. + /// + /// @tparam Type of the parked object. + /// @param hook_name name of the hook point for which the packet is parked. + /// @param parked_object parked object for which reference counter should + /// be increased. + template<typename T> + static void reference(const std::string& hook_name, T parked_object) { + ServerHooks::getServerHooks(). + getParkingLotPtr(hook_name)->reference(parked_object); + } + + /// @brief Clears any parking packets. + /// + /// This method should be called during reconfiguration to ensure there + /// are no dangling pointers that could possibly prevent the library + /// from being unloaded. + static void clearParkingLots() { + ServerHooks::getServerHooks().getParkingLotsPtr()->clear(); + } + + /// @brief Set test mode + /// + /// If enabled by unit tests will permit to register callouts before calling + /// @ref loadLibraries which will return immediately without changing + /// current internal state. + /// + /// @param mode the test mode flag which enables or disabled the + /// functionality. + static void setTestMode(bool mode); + + /// @brief Get test mode + /// + /// @return the test mode flag. + static bool getTestMode(); + +private: + + /// @brief Constructor + /// + /// This is private as the object is a singleton and can only be addressed + /// through the getHooksManager() static method. + HooksManager(); + + /// @brief Get singleton hooks manager + /// + /// @return Reference to the singleton hooks manager. + static HooksManager& getHooksManager(); + + //@{ + /// The following methods correspond to similarly-named static methods, + /// but actually do the work on the singleton instance of the HooksManager. + /// See the descriptions of the static methods for more details. + + /// @brief Validate library list + /// + /// @param List of libraries to be validated. + /// + /// @return An empty string if all libraries validated. Otherwise it is + /// the name of the first library that failed validation. The + /// configuration code can return this to bindctl as an indication + /// of the problem. + std::string validateLibrariesInternal( + const std::vector<std::string>& libraries) const; + + /// @brief Load and reload libraries + /// + /// @param libraries List of libraries to be loaded. The order is + /// important, as it determines the order that callouts on the same + /// hook will be called. + /// + /// @return true if all libraries loaded without a problem, false if one or + /// more libraries failed to load. In the latter case, message will + /// be logged that give the reason. + bool loadLibrariesInternal(const HookLibsCollection& libraries); + + /// @brief Unload libraries + /// + /// @return true if all libraries unloaded successfully, false on an error. + /// In the latter case, an error message will have been output. + bool unloadLibrariesInternal(); + + /// @brief Prepare the unloading of libraries + void prepareUnloadLibrariesInternal(); + + /// @brief Are callouts present? + /// + /// @param index Hooks index for which callouts are checked. + /// + /// @return true if callouts are present, false if not. + /// @throw NoSuchHook Given index does not correspond to a valid hook. + bool calloutsPresentInternal(int index); + + /// @brief Checks if control command handlers are present for the + /// specified command. + /// + /// @param command_name Command name for which handlers' presence should + /// be checked. + /// + /// @return true if there is a hook point associated with the specified + /// command and callouts/command handlers are installed for this hook + /// point, false otherwise. + bool commandHandlersPresentInternal(const std::string& command_name); + + /// @brief Calls the callouts for a given hook + /// + /// @param index Index of the hook to call. + /// @param handle Reference to the CalloutHandle object for the current + /// object being processed. + void callCalloutsInternal(int index, CalloutHandle& handle); + + /// @brief Calls the callouts/command handlers for a given command name. + /// + /// @param command_name Command name for which handlers should be called. + /// @param handle Reference to the CalloutHandle object for the current + /// object being processed. + /// + /// @throw NoSuchHook if the hook point for the specified command does + /// not exist. + void callCommandHandlersInternal(const std::string& command_name, + CalloutHandle& handle); + + /// @brief Return callout handle + /// + /// @return Shared pointer to a CalloutHandle object. + boost::shared_ptr<CalloutHandle> createCalloutHandleInternal(); + + /// @brief Return pre-callouts library handle + /// + /// @return Reference to library handle associated with pre-library callout + /// registration. + LibraryHandle& preCalloutsLibraryHandleInternal(); + + /// @brief Return post-callouts library handle + /// + /// @return Reference to library handle associated with post-library callout + /// registration. + LibraryHandle& postCalloutsLibraryHandleInternal(); + + /// @brief Return list of loaded libraries + /// + /// @return List of loaded library names. + std::vector<std::string> getLibraryNamesInternal() const; + + /// @brief Return a collection of library names with parameters. + HookLibsCollection getLibraryInfoInternal() const; + + //@} + + // Members + + /// Set of library managers. + /// + /// @note: This should never be null. + boost::shared_ptr<LibraryManagerCollection> lm_collection_; + + /// Callout manager for the set of library managers. + /// + /// @note: This should never be null. + boost::shared_ptr<CalloutManager> callout_manager_; + + /// Test flag to keep @ref callout_manager_ when calling @ref loadLibraries + /// from unit tests (called by @ref configureDhcp[46]Server). + /// + /// @note: This will effectively make @ref loadLibraries return immediately. + bool test_mode_; +}; + +} // namespace util +} // namespace hooks + +#endif // HOOKS_MANAGER_H diff --git a/src/lib/hooks/hooks_messages.cc b/src/lib/hooks/hooks_messages.cc new file mode 100644 index 0000000..dc75fb4 --- /dev/null +++ b/src/lib/hooks/hooks_messages.cc @@ -0,0 +1,93 @@ +// File created from ../../../src/lib/hooks/hooks_messages.mes + +#include <cstddef> +#include <log/message_types.h> +#include <log/message_initializer.h> + +namespace isc { +namespace hooks { + +extern const isc::log::MessageID HOOKS_ALL_CALLOUTS_DEREGISTERED = "HOOKS_ALL_CALLOUTS_DEREGISTERED"; +extern const isc::log::MessageID HOOKS_CALLOUTS_BEGIN = "HOOKS_CALLOUTS_BEGIN"; +extern const isc::log::MessageID HOOKS_CALLOUTS_COMPLETE = "HOOKS_CALLOUTS_COMPLETE"; +extern const isc::log::MessageID HOOKS_CALLOUTS_REMOVED = "HOOKS_CALLOUTS_REMOVED"; +extern const isc::log::MessageID HOOKS_CALLOUT_CALLED = "HOOKS_CALLOUT_CALLED"; +extern const isc::log::MessageID HOOKS_CALLOUT_DEREGISTERED = "HOOKS_CALLOUT_DEREGISTERED"; +extern const isc::log::MessageID HOOKS_CALLOUT_ERROR = "HOOKS_CALLOUT_ERROR"; +extern const isc::log::MessageID HOOKS_CALLOUT_EXCEPTION = "HOOKS_CALLOUT_EXCEPTION"; +extern const isc::log::MessageID HOOKS_CALLOUT_REGISTRATION = "HOOKS_CALLOUT_REGISTRATION"; +extern const isc::log::MessageID HOOKS_CLOSE_ERROR = "HOOKS_CLOSE_ERROR"; +extern const isc::log::MessageID HOOKS_HOOK_LIST_RESET = "HOOKS_HOOK_LIST_RESET"; +extern const isc::log::MessageID HOOKS_INCORRECT_VERSION = "HOOKS_INCORRECT_VERSION"; +extern const isc::log::MessageID HOOKS_LIBRARY_CLOSED = "HOOKS_LIBRARY_CLOSED"; +extern const isc::log::MessageID HOOKS_LIBRARY_LOADED = "HOOKS_LIBRARY_LOADED"; +extern const isc::log::MessageID HOOKS_LIBRARY_LOADING = "HOOKS_LIBRARY_LOADING"; +extern const isc::log::MessageID HOOKS_LIBRARY_MULTI_THREADING_COMPATIBLE = "HOOKS_LIBRARY_MULTI_THREADING_COMPATIBLE"; +extern const isc::log::MessageID HOOKS_LIBRARY_MULTI_THREADING_NOT_COMPATIBLE = "HOOKS_LIBRARY_MULTI_THREADING_NOT_COMPATIBLE"; +extern const isc::log::MessageID HOOKS_LIBRARY_UNLOADED = "HOOKS_LIBRARY_UNLOADED"; +extern const isc::log::MessageID HOOKS_LIBRARY_UNLOADING = "HOOKS_LIBRARY_UNLOADING"; +extern const isc::log::MessageID HOOKS_LIBRARY_VERSION = "HOOKS_LIBRARY_VERSION"; +extern const isc::log::MessageID HOOKS_LOAD_ERROR = "HOOKS_LOAD_ERROR"; +extern const isc::log::MessageID HOOKS_LOAD_EXCEPTION = "HOOKS_LOAD_EXCEPTION"; +extern const isc::log::MessageID HOOKS_LOAD_FRAMEWORK_EXCEPTION = "HOOKS_LOAD_FRAMEWORK_EXCEPTION"; +extern const isc::log::MessageID HOOKS_LOAD_SUCCESS = "HOOKS_LOAD_SUCCESS"; +extern const isc::log::MessageID HOOKS_MULTI_THREADING_COMPATIBLE_EXCEPTION = "HOOKS_MULTI_THREADING_COMPATIBLE_EXCEPTION"; +extern const isc::log::MessageID HOOKS_NO_LOAD = "HOOKS_NO_LOAD"; +extern const isc::log::MessageID HOOKS_NO_UNLOAD = "HOOKS_NO_UNLOAD"; +extern const isc::log::MessageID HOOKS_NO_VERSION = "HOOKS_NO_VERSION"; +extern const isc::log::MessageID HOOKS_OPEN_ERROR = "HOOKS_OPEN_ERROR"; +extern const isc::log::MessageID HOOKS_STD_CALLOUT_REGISTERED = "HOOKS_STD_CALLOUT_REGISTERED"; +extern const isc::log::MessageID HOOKS_UNLOAD_ERROR = "HOOKS_UNLOAD_ERROR"; +extern const isc::log::MessageID HOOKS_UNLOAD_EXCEPTION = "HOOKS_UNLOAD_EXCEPTION"; +extern const isc::log::MessageID HOOKS_UNLOAD_FRAMEWORK_EXCEPTION = "HOOKS_UNLOAD_FRAMEWORK_EXCEPTION"; +extern const isc::log::MessageID HOOKS_UNLOAD_SUCCESS = "HOOKS_UNLOAD_SUCCESS"; +extern const isc::log::MessageID HOOKS_VERSION_EXCEPTION = "HOOKS_VERSION_EXCEPTION"; + +} // namespace hooks +} // namespace isc + +namespace { + +const char* values[] = { + "HOOKS_ALL_CALLOUTS_DEREGISTERED", "hook library at index %1 removed all callouts on hook %2", + "HOOKS_CALLOUTS_BEGIN", "begin all callouts for hook %1", + "HOOKS_CALLOUTS_COMPLETE", "completed callouts for hook %1 (total callouts duration: %2)", + "HOOKS_CALLOUTS_REMOVED", "callouts removed from hook %1 for library %2", + "HOOKS_CALLOUT_CALLED", "hooks library with index %1 has called a callout on hook %2 that has address %3 (callout duration: %4)", + "HOOKS_CALLOUT_DEREGISTERED", "hook library at index %1 deregistered a callout on hook %2", + "HOOKS_CALLOUT_ERROR", "error returned by callout on hook %1 registered by library with index %2 (callout address %3) (callout duration %4)", + "HOOKS_CALLOUT_EXCEPTION", "exception thrown by callout on hook %1 registered by library with index %2 (callout address %3): %4 (callout duration: %5)", + "HOOKS_CALLOUT_REGISTRATION", "hooks library with index %1 registering callout for hook '%2'", + "HOOKS_CLOSE_ERROR", "failed to close hook library %1: %2", + "HOOKS_HOOK_LIST_RESET", "the list of hooks has been reset", + "HOOKS_INCORRECT_VERSION", "hook library %1 is at version %2, require version %3", + "HOOKS_LIBRARY_CLOSED", "hooks library %1 successfully closed", + "HOOKS_LIBRARY_LOADED", "hooks library %1 successfully loaded", + "HOOKS_LIBRARY_LOADING", "loading hooks library %1", + "HOOKS_LIBRARY_MULTI_THREADING_COMPATIBLE", "hooks library %1 reports its multi-threading compatibility as %2", + "HOOKS_LIBRARY_MULTI_THREADING_NOT_COMPATIBLE", "hooks library %1 is not compatible with multi-threading", + "HOOKS_LIBRARY_UNLOADED", "hooks library %1 successfully unloaded", + "HOOKS_LIBRARY_UNLOADING", "unloading library %1", + "HOOKS_LIBRARY_VERSION", "hooks library %1 reports its version as %2", + "HOOKS_LOAD_ERROR", "'load' function in hook library %1 returned error %2", + "HOOKS_LOAD_EXCEPTION", "'load' function in hook library %1 threw an exception", + "HOOKS_LOAD_FRAMEWORK_EXCEPTION", "'load' function in hook library %1 threw an exception: reason %2", + "HOOKS_LOAD_SUCCESS", "'load' function in hook library %1 returned success", + "HOOKS_MULTI_THREADING_COMPATIBLE_EXCEPTION", "'multi_threading_compatible' function in hook library %1 threw an exception", + "HOOKS_NO_LOAD", "no 'load' function found in hook library %1", + "HOOKS_NO_UNLOAD", "no 'unload' function found in hook library %1", + "HOOKS_NO_VERSION", "no 'version' function found in hook library %1", + "HOOKS_OPEN_ERROR", "failed to open hook library %1: %2", + "HOOKS_STD_CALLOUT_REGISTERED", "hooks library %1 registered standard callout for hook %2 at address %3", + "HOOKS_UNLOAD_ERROR", "'unload' function in hook library %1 returned error %2", + "HOOKS_UNLOAD_EXCEPTION", "'unload' function in hook library %1 threw an exception", + "HOOKS_UNLOAD_FRAMEWORK_EXCEPTION", "'unload' function in hook library %1 threw an exception, reason %2", + "HOOKS_UNLOAD_SUCCESS", "'unload' function in hook library %1 returned success", + "HOOKS_VERSION_EXCEPTION", "'version' function in hook library %1 threw an exception", + NULL +}; + +const isc::log::MessageInitializer initializer(values); + +} // Anonymous namespace + diff --git a/src/lib/hooks/hooks_messages.h b/src/lib/hooks/hooks_messages.h new file mode 100644 index 0000000..a80626c --- /dev/null +++ b/src/lib/hooks/hooks_messages.h @@ -0,0 +1,50 @@ +// File created from ../../../src/lib/hooks/hooks_messages.mes + +#ifndef HOOKS_MESSAGES_H +#define HOOKS_MESSAGES_H + +#include <log/message_types.h> + +namespace isc { +namespace hooks { + +extern const isc::log::MessageID HOOKS_ALL_CALLOUTS_DEREGISTERED; +extern const isc::log::MessageID HOOKS_CALLOUTS_BEGIN; +extern const isc::log::MessageID HOOKS_CALLOUTS_COMPLETE; +extern const isc::log::MessageID HOOKS_CALLOUTS_REMOVED; +extern const isc::log::MessageID HOOKS_CALLOUT_CALLED; +extern const isc::log::MessageID HOOKS_CALLOUT_DEREGISTERED; +extern const isc::log::MessageID HOOKS_CALLOUT_ERROR; +extern const isc::log::MessageID HOOKS_CALLOUT_EXCEPTION; +extern const isc::log::MessageID HOOKS_CALLOUT_REGISTRATION; +extern const isc::log::MessageID HOOKS_CLOSE_ERROR; +extern const isc::log::MessageID HOOKS_HOOK_LIST_RESET; +extern const isc::log::MessageID HOOKS_INCORRECT_VERSION; +extern const isc::log::MessageID HOOKS_LIBRARY_CLOSED; +extern const isc::log::MessageID HOOKS_LIBRARY_LOADED; +extern const isc::log::MessageID HOOKS_LIBRARY_LOADING; +extern const isc::log::MessageID HOOKS_LIBRARY_MULTI_THREADING_COMPATIBLE; +extern const isc::log::MessageID HOOKS_LIBRARY_MULTI_THREADING_NOT_COMPATIBLE; +extern const isc::log::MessageID HOOKS_LIBRARY_UNLOADED; +extern const isc::log::MessageID HOOKS_LIBRARY_UNLOADING; +extern const isc::log::MessageID HOOKS_LIBRARY_VERSION; +extern const isc::log::MessageID HOOKS_LOAD_ERROR; +extern const isc::log::MessageID HOOKS_LOAD_EXCEPTION; +extern const isc::log::MessageID HOOKS_LOAD_FRAMEWORK_EXCEPTION; +extern const isc::log::MessageID HOOKS_LOAD_SUCCESS; +extern const isc::log::MessageID HOOKS_MULTI_THREADING_COMPATIBLE_EXCEPTION; +extern const isc::log::MessageID HOOKS_NO_LOAD; +extern const isc::log::MessageID HOOKS_NO_UNLOAD; +extern const isc::log::MessageID HOOKS_NO_VERSION; +extern const isc::log::MessageID HOOKS_OPEN_ERROR; +extern const isc::log::MessageID HOOKS_STD_CALLOUT_REGISTERED; +extern const isc::log::MessageID HOOKS_UNLOAD_ERROR; +extern const isc::log::MessageID HOOKS_UNLOAD_EXCEPTION; +extern const isc::log::MessageID HOOKS_UNLOAD_FRAMEWORK_EXCEPTION; +extern const isc::log::MessageID HOOKS_UNLOAD_SUCCESS; +extern const isc::log::MessageID HOOKS_VERSION_EXCEPTION; + +} // namespace hooks +} // namespace isc + +#endif // HOOKS_MESSAGES_H diff --git a/src/lib/hooks/hooks_messages.mes b/src/lib/hooks/hooks_messages.mes new file mode 100644 index 0000000..e33e7e4 --- /dev/null +++ b/src/lib/hooks/hooks_messages.mes @@ -0,0 +1,201 @@ +# Copyright (C) 2013-2020 Internet Systems Consortium, Inc. ("ISC") +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +$NAMESPACE isc::hooks + +% HOOKS_ALL_CALLOUTS_DEREGISTERED hook library at index %1 removed all callouts on hook %2 +A debug message issued when all callouts on the specified hook registered +by the library with the given index were removed. This is similar to +the HOOKS_CALLOUTS_REMOVED message (and the two are likely to be seen +together), but is issued at a lower-level in the hook framework. + +% HOOKS_CALLOUTS_BEGIN begin all callouts for hook %1 +This debug message is issued when callout manager begins to invoke callouts +for the hook. The argument specifies the hook name. + +% HOOKS_CALLOUTS_COMPLETE completed callouts for hook %1 (total callouts duration: %2) +This debug message is issued when callout manager has completed execution +of all callouts for the particular hook. The arguments specify the hook +name and total execution time for all callouts in milliseconds. + +% HOOKS_CALLOUTS_REMOVED callouts removed from hook %1 for library %2 +This is a debug message issued during library unloading. It notes that +one of more callouts registered by that library have been removed from +the specified hook. This is similar to the HOOKS_DEREGISTER_ALL_CALLOUTS +message (and the two are likely to be seen together), but is issued at a +higher-level in the hook framework. + +% HOOKS_CALLOUT_CALLED hooks library with index %1 has called a callout on hook %2 that has address %3 (callout duration: %4) +Only output at a high debugging level, this message indicates that +a callout on the named hook registered by the library with the given +index (in the list of loaded libraries) has been called and returned a +success state. The address of the callout is given in the message. +The message includes the callout execution time in milliseconds. + +% HOOKS_CALLOUT_DEREGISTERED hook library at index %1 deregistered a callout on hook %2 +A debug message issued when all instances of a particular callouts on +the hook identified in the message that were registered by the library +with the given index have been removed. + +% HOOKS_CALLOUT_ERROR error returned by callout on hook %1 registered by library with index %2 (callout address %3) (callout duration %4) +If a callout returns an error status when called, this error message +is issued. It identifies the hook to which the callout is attached, the +index of the library (in the list of loaded libraries) that registered +it and the address of the callout. The error is otherwise ignored. +The error message includes the callout execution time in milliseconds. + +% HOOKS_CALLOUT_EXCEPTION exception thrown by callout on hook %1 registered by library with index %2 (callout address %3): %4 (callout duration: %5) +If a callout throws an exception when called, this error message is +issued. It identifies the hook to which the callout is attached, the +index of the library (in the list of loaded libraries) that registered +it and the address of the callout. The error is otherwise ignored. +The error message includes the callout execution time in milliseconds. + +% HOOKS_CALLOUT_REGISTRATION hooks library with index %1 registering callout for hook '%2' +This is a debug message, output when a library (whose index in the list +of libraries (being) loaded is given) registers a callout. + +% HOOKS_CLOSE_ERROR failed to close hook library %1: %2 +Kea has failed to close the named hook library for the stated reason. +Although this is an error, this should not affect the running system +other than as a loss of resources. If this error persists, you should +restart Kea. + +% HOOKS_HOOK_LIST_RESET the list of hooks has been reset +This is a message indicating that the list of hooks has been reset. +While this is usual when running the Kea test suite, it should not be +seen when running Kea in a production environment. If this appears, +please report a bug through the usual channels. + +% HOOKS_INCORRECT_VERSION hook library %1 is at version %2, require version %3 +Kea has detected that the named hook library has been built against +a version of Kea that is incompatible with the version of Kea +running on your system. It has not loaded the library. + +This is most likely due to the installation of a new version of Kea +without rebuilding the hook library. A rebuild and re-install of the +library should fix the problem in most cases. + +% HOOKS_LIBRARY_CLOSED hooks library %1 successfully closed +This information message is issued when a user-supplied hooks library +has been successfully closed. + +% HOOKS_LIBRARY_LOADED hooks library %1 successfully loaded +This information message is issued when a user-supplied hooks library +has been successfully loaded. + +% HOOKS_LIBRARY_LOADING loading hooks library %1 +This is a debug message output just before the specified library is loaded. +If the action is successfully, it will be followed by the +HOOKS_LIBRARY_LOADED informational message. + +% HOOKS_LIBRARY_MULTI_THREADING_COMPATIBLE hooks library %1 reports its multi-threading compatibility as %2 +A debug message issued when the "multi_threading_compatible" function was +called. The returned value (0 means not compatible, others compatible) +is displayed. + +% HOOKS_LIBRARY_MULTI_THREADING_NOT_COMPATIBLE hooks library %1 is not compatible with multi-threading +When multi-threading is enabled and the library is not compatible (either +because the "multi_threading_compatible" function returned 0 or was not +implemented) this error message is issued. The library must be removed +from the configuration or the multi-threading disabled. + +% HOOKS_LIBRARY_UNLOADED hooks library %1 successfully unloaded +This information message is issued when a user-supplied hooks library +has been successfully unloaded. + +% HOOKS_LIBRARY_UNLOADING unloading library %1 +This is a debug message called when the specified library is +being unloaded. If all is successful, it will be followed by the +HOOKS_LIBRARY_UNLOADED informational message. + +% HOOKS_LIBRARY_VERSION hooks library %1 reports its version as %2 +A debug message issued when the version check on the hooks library +has succeeded. + +% HOOKS_LOAD_ERROR 'load' function in hook library %1 returned error %2 +A "load" function was found in the library named in the message and +was called. The function returned a non-zero status (also given in +the message) which was interpreted as an error. The library has been +unloaded and no callouts from it will be installed. + +% HOOKS_LOAD_EXCEPTION 'load' function in hook library %1 threw an exception +A "load" function was found in the library named in the message and +was called. The function threw an exception (an error indication) +during execution, which is an error condition. The library has been +unloaded and no callouts from it will be installed. + +% HOOKS_LOAD_FRAMEWORK_EXCEPTION 'load' function in hook library %1 threw an exception: reason %2 +A "load" function was found in the library named in the message and +was called. Either the hooks framework or the function threw an +exception (an error indication) during execution, which is an error +condition; the cause of the exception is recorded in the message. +The library has been unloaded and no callouts from it will be +installed. + +% HOOKS_LOAD_SUCCESS 'load' function in hook library %1 returned success +This is a debug message issued when the "load" function has been found +in a hook library and has been successfully called. + +% HOOKS_MULTI_THREADING_COMPATIBLE_EXCEPTION 'multi_threading_compatible' function in hook library %1 threw an exception +This error message is issued if the multi_threading_compatible() +function in the specified hooks library was called and generated an +exception. The library is considered unusable and will not be loaded. + +% HOOKS_NO_LOAD no 'load' function found in hook library %1 +This is a debug message saying that the specified library was loaded +but no function called "load" was found in it. Providing the library +contained some "standard" functions (i.e. functions with the names of +the hooks for the given server), this is not an issue. + +% HOOKS_NO_UNLOAD no 'unload' function found in hook library %1 +This is a debug message issued when the library is being unloaded. +It merely states that the library did not contain an "unload" function. + +% HOOKS_NO_VERSION no 'version' function found in hook library %1 +The shared library named in the message was found and successfully loaded, +but Kea did not find a function named "version" in it. This function +is required and should return the version of Kea against which the +library was built. The value is used to check that the library was built +against a compatible version of Kea. The library has not been loaded. + +% HOOKS_OPEN_ERROR failed to open hook library %1: %2 +Kea failed to open the specified hook library for the stated +reason. The library has not been loaded. Kea will continue to +function, but without the services offered by the library. + +% HOOKS_STD_CALLOUT_REGISTERED hooks library %1 registered standard callout for hook %2 at address %3 +This is a debug message, output when the library loading function has +located a standard callout (a callout with the same name as a hook point) +and registered it. The address of the callout is indicated. + +% HOOKS_UNLOAD_ERROR 'unload' function in hook library %1 returned error %2 +During the unloading of a library, an "unload" function was found. +It was called, but returned an error (non-zero) status, resulting in +the issuing of this message. The unload process continued after this +message and the library has been unloaded. + +% HOOKS_UNLOAD_EXCEPTION 'unload' function in hook library %1 threw an exception +During the unloading of a library, an "unload" function was found. It was +called, but in the process generated an exception (an error indication). +The unload process continued after this message and the library has +been unloaded. + +% HOOKS_UNLOAD_FRAMEWORK_EXCEPTION 'unload' function in hook library %1 threw an exception, reason %2 +During the unloading of a library, an "unload" function was found. +It was called, but in the process either it or the hooks framework +generated an exception (an error indication); the cause of the error +is recorded in the message. The unload process continued after +this message and the library has been unloaded. + +% HOOKS_UNLOAD_SUCCESS 'unload' function in hook library %1 returned success +This is a debug message issued when an "unload" function has been found +in a hook library during the unload process, called, and returned success. + +% HOOKS_VERSION_EXCEPTION 'version' function in hook library %1 threw an exception +This error message is issued if the version() function in the specified +hooks library was called and generated an exception. The library is +considered unusable and will not be loaded. diff --git a/src/lib/hooks/hooks_parser.cc b/src/lib/hooks/hooks_parser.cc new file mode 100644 index 0000000..cfd4a7e --- /dev/null +++ b/src/lib/hooks/hooks_parser.cc @@ -0,0 +1,110 @@ +// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <cc/data.h> +#include <cc/dhcp_config_error.h> +#include <hooks/hooks_parser.h> +#include <boost/algorithm/string.hpp> +#include <boost/foreach.hpp> +#include <util/strutil.h> +#include <vector> + +using namespace std; +using namespace isc::data; +using namespace isc::hooks; +using namespace isc::dhcp; + +namespace isc { +namespace hooks { + +// @todo use the flat style, split into list and item + +void +HooksLibrariesParser::parse(HooksConfig& libraries, ConstElementPtr value) { + // Initialize. + libraries.clear(); + + if (!value) { + isc_throw(DhcpConfigError, "Tried to parse null hooks libraries"); + } + + // This is the new syntax. Iterate through it and get each map. + BOOST_FOREACH(ConstElementPtr library_entry, value->listValue()) { + ConstElementPtr parameters; + + // Is it a map? + if (library_entry->getType() != Element::map) { + isc_throw(DhcpConfigError, "hooks library configuration error:" + " one or more entries in the hooks-libraries list is not" + " a map (" << library_entry->getPosition() << ")"); + } + + // Iterate through each element in the map. We check + // whether we have found a library element. + bool lib_found = false; + + string libname = ""; + + // Let's explicitly reset the parameters, so we won't cover old + // values from the previous loop round. + parameters.reset(); + + BOOST_FOREACH(auto entry_item, library_entry->mapValue()) { + if (entry_item.first == "library") { + if (entry_item.second->getType() != Element::string) { + isc_throw(DhcpConfigError, "hooks library configuration" + " error: value of 'library' element is not a string" + " giving the path to a hooks library (" << + entry_item.second->getPosition() << ")"); + } + + // Get the name of the library and add it to the list after + // removing quotes. + libname = (entry_item.second)->stringValue(); + + // Remove leading/trailing quotes and any leading/trailing + // spaces. + boost::erase_all(libname, "\""); + libname = isc::util::str::trim(libname); + if (libname.empty()) { + isc_throw(DhcpConfigError, "hooks library configuration" + " error: value of 'library' element must not be" + " blank (" << + entry_item.second->getPosition() << ")"); + } + + // Note we have found the library name. + lib_found = true; + continue; + } + + // If there are parameters, let's remember them. + if (entry_item.first == "parameters") { + parameters = entry_item.second; + continue; + } + + // For all other parameters we will throw. + isc_throw(DhcpConfigError, "unknown hooks library parameter: " + << entry_item.first << "(" + << library_entry->getPosition() << ")"); + } + + if (! lib_found) { + isc_throw(DhcpConfigError, "hooks library configuration error:" + " one or more hooks-libraries elements are missing the" + " name of the library" << + " (" << library_entry->getPosition() << ")"); + } + + libraries.add(libname, parameters); + } +} + +} +} diff --git a/src/lib/hooks/hooks_parser.h b/src/lib/hooks/hooks_parser.h new file mode 100644 index 0000000..4bec9a6 --- /dev/null +++ b/src/lib/hooks/hooks_parser.h @@ -0,0 +1,65 @@ +// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef HOOKS_PARSER_H +#define HOOKS_PARSER_H + +#include <cc/data.h> +#include <cc/simple_parser.h> +#include <hooks/hooks_config.h> + +namespace isc { +namespace hooks { + +/// @brief Parser for hooks library list +/// +/// This parser handles the list of hooks libraries. This is an optional list, +/// which may be empty, and is encapsulated into a @ref HooksConfig object. +class HooksLibrariesParser : public isc::data::SimpleParser { +public: + + /// @brief Parses parameters value + /// + /// Parses configuration entry (list of parameters) and adds each element + /// to the hooks libraries list. The method also checks whether the + /// list of libraries is the same as that already loaded. If not, it + /// checks each of the libraries in the list for validity (they exist and + /// have a "version" function that returns the correct value). + /// + /// The syntax for specifying hooks libraries allow for library-specific + /// parameters to be specified along with the library, e.g. + /// + /// @code + /// "hooks-libraries": [ + /// { + /// "library": "hook-lib-1.so", + /// "parameters": { + /// "alpha": "a string", + /// "beta": 42 + /// } + /// }, + /// : + /// ] + /// @endcode + /// + /// The parsing code only checks that: + /// + /// -# Each element in the hooks-libraries list is a map + /// -# The map contains an element "library" whose value is a not blank string + /// -# That there is an optional 'parameters' element. + /// -# That there are no other element. + /// + /// This method stores parsed libraries in libraries. + /// + /// @param libraries parsed libraries information will be stored here + /// @param value pointer to the content to be parsed + void parse(HooksConfig& libraries, isc::data::ConstElementPtr value); +}; + +}; // namespace isc::hooks +}; // namespace isc + +#endif diff --git a/src/lib/hooks/hooks_user.dox b/src/lib/hooks/hooks_user.dox new file mode 100644 index 0000000..2a51b93 --- /dev/null +++ b/src/lib/hooks/hooks_user.dox @@ -0,0 +1,1670 @@ +// Copyright (C) 2013-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/. + +// Note: the prefix "hooksdg" to all labels is an abbreviation for "Hooks +// Developer's Guide" and is used to prevent a clash with symbols in any +// other Doxygen file. + +/** + @page hooksdgDevelopersGuide Hooks Developer's Guide + + @section hooksdgIntroduction Introduction + +Although the Kea framework and its DHCP programs +provide comprehensive functionality, there will be times when it does +not quite do what you require: the processing has to be extended in some +way to solve your problem. + +Since the Kea source code is freely available (Kea being an +open-source project), one option is to modify it to do what +you want. Whilst perfectly feasible, there are drawbacks: + +- Although well-documented, Kea is a large program. Just +understanding how it works will take a significant amount of time. In +addition, despite the fact that its object-oriented design keeps the +coupling between modules to a minimum, an inappropriate change to one +part of the program during the extension could cause another to +behave oddly or to stop working altogether. + +- The change may need to be re-applied or re-written with every new +version of Kea. As new functionality is added or bugs are fixed, +the code or algorithms in the core software may change - and may change +significantly. + +To overcome these problems, Kea provides the "Hooks" interface - +a defined interface for third-party or user-written code. (For ease of +reference in the rest of this document, all such code will be referred +to as "user code".) At specific points in its processing +("hook points") Kea will make a call to this code. The call passes +data that the user code can examine and, if required, modify. +Kea uses the modified data in the remainder of its processing. + +In order to minimize the interaction between Kea and the user code, +the latter is built independently of Kea in the form of one or more +dynamic shared objects, called here (for historical reasons), shared +libraries. These are made known to Kea through its configuration +mechanism, and Kea loads the library at run time. Libraries can be +unloaded and reloaded as needed while Kea is running. + +Use of a defined API and the Kea configuration mechanism means that +as new versions of Kea are released, there is no need to modify +the user code. Unless there is a major change in an interface +(which will be clearly documented), all that will be required is a rebuild +of the libraries. + +@note Although the defined interface should not change, the internals +of some of the classes and structures referenced by the user code may +change between versions of Kea. These changes have to be reflected +in the compiled version of the software, hence the need for a rebuild. + + +@subsection hooksdgLanguages Languages + +The core of Kea is written in C++. While it is the intention to +provide interfaces into user code written in other languages, the initial +versions of the Hooks system required that user code be written in C++. +It is no longer the case and there are examples of hooks written for +instance in Python but this guide does not document how to do that. +All examples in this guide are in C++. + + +@subsection hooksdgTerminology Terminology + +In the remainder of this guide, the following terminology is used: + +- Hook/Hook Point - used interchangeably, this is a point in the code at +which a call to user functions is made. Each hook has a name and +each hook can have any number (including 0) of user functions +attached to it. + +- Callout - a user function called by the server at a hook +point. This is so-named because the server "calls out" to the library +to execute a user function. + +- Framework function - the functions that a user library needs to +supply in order for the hooks framework to load and unload the library. + +- User code/user library - non-Kea code that is compiled into a +shared library and loaded by Kea into its address space. + + +@section hooksdgTutorial Tutorial + +To illustrate how to write code that integrates with Kea, we will +use the following (rather contrived) example: + +<i>The Kea DHCPv4 server is used to allocate IPv4 addresses to clients +(as well as to pass them other information such as the address of DNS +servers). We will suppose that we need to classify clients requesting +IPv4 addresses according to their hardware address, and want to log both +the hardware address and allocated IP address for the clients of interest.</i> + +The following sections describe how to implement these requirements. +The code presented here is not efficient and there are better ways of +doing the task. The aim however, is to illustrate the main features of +user hooks code, not to provide an optimal solution. + + +@subsection hooksdgFrameworkFunctions Framework Functions + +Loading and initializing a library holding user code makes use +of three (user-supplied) functions: + +- version - defines the version of Kea code with which the user-library +is built +- load - called when the library is loaded by the server. +- unload - called when the library is unloaded by the server. +- multi_threading_compatible - defines the compatibility (or not) of +the user-library and a multi-threaded DHCP service. + +Of these, only "version" is mandatory, although in our example, all four +are used. + +@subsubsection hooksdgVersionFunction The "version" Function + +"version" is used by the hooks framework to check that the libraries +it is loading are compatible with the version of Kea being run. +Although the hooks system allows Kea and user code to interface +through a defined API, the relationship is somewhat tight in that the +user code will depend on the internal structures of Kea. If these +change - as they can between Kea releases - and Kea is run with +a version of user code built against an earlier version of Kea, a program +crash could result. + +To guard against this, the "version" function must be provided in every +library. It returns a constant defined in header files of the version +of Kea against which it was built. The hooks framework checks this +for compatibility with the running version of Kea before loading +the library. + +In this tutorial, we'll put "version" in its own file, version.cc. The +contents are: + +@code +// version.cc + +#include <hooks/hooks.h> + +extern "C" { + +int version() { + return (KEA_HOOKS_VERSION); +} + +} +@endcode + +The file "hooks/hooks.h" is specified relative to the Kea libraries +source directory - this is covered later in the section @ref hooksdgBuild. +It defines the symbol KEA_HOOKS_VERSION, which has a value that changes +on every release of Kea: this is the value that needs to be returned +to the hooks framework. + +A final point to note is that the definition of "version" is enclosed +within 'extern "C"' braces. All functions accessed by the hooks +framework use C linkage, mainly to avoid the name mangling that +accompanies use of the C++ compiler, but also to avoid issues related +to namespaces. + +@subsubsection hooksdgLoadUnloadFunctions The "load" and "unload" Functions + +As the names suggest, "load" is called when a library is loaded and +"unload" called when it is unloaded. (It is always guaranteed that +"load" is called: "unload" may not be called in some circumstances, +e.g., if the system shuts down abnormally.) These functions are the +places where any library-wide resources are allocated and deallocated. +"load" is also the place where any callouts with non-standard names +(names that are not hook point names) can be registered: +this is covered further in the section @ref hooksdgCalloutRegistration. + +The example does not make any use callouts with non-standard names. However, +as our design requires that the log file be open while Kea is active +and the library loaded, we'll open the file in the "load" function and close +it in "unload". + +We create two files, one for the file handle declaration: + +@code +// library_common.h + +#ifndef LIBRARY_COMMON_H +#define LIBRARY_COMMON_H + +#include <fstream> + +// "Interesting clients" log file handle declaration. +extern std::fstream interesting; + +#endif // LIBRARY_COMMON_H +@endcode + +... and one to hold the "load" and "unload" functions: + +@code +// load_unload.cc + +#include <hooks/hooks.h> +#include "library_common.h" + +using namespace isc::hooks; + +// "Interesting clients" log file handle definition. +std::fstream interesting; + +extern "C" { + +int load(LibraryHandle&) { + interesting.open("/data/clients/interesting.log", + std::fstream::out | std::fstream::app); + return (interesting ? 0 : 1); +} + +int unload() { + if (interesting) { + interesting.close(); + } + return (0); +} + +} +@endcode + +Notes: +- The file handle ("interesting") is declared in a header file and defined +outside of any function. This means it can be accessed by any function +within the user library. For convenience, the definition is in the +load_unload.cc file. +- "load" is called with a LibraryHandle argument, this being used in +the registration of functions. As no functions are being registered +in this example, the argument specification omits the variable name +(whilst retaining the type) to avoid an "unused variable" compiler +warning. (The LibraryHandle and its use is discussed in the section +@ref hooksdgLibraryHandle.) +- In the initial version of the hooks framework, it was not possible to pass +any configuration information to the "load" function. The name of the log +file had therefore to be hard-coded as an absolute path name or communicated +to the user code by some other means. +- "load" must return 0 on success and non-zero on error. The hooks framework +will abandon the loading of the library if "load" returns an error status. +(In this example, "interesting" can be tested as a boolean value, +returning "true" if the file opened successfully.) +- "unload" closes the log file if it is open and is a no-op otherwise. As +with "load", a zero value must be returned on success and a non-zero value +on an error. The hooks framework will record a non-zero status return +as an error in the current Kea log but otherwise ignore it. +- As before, the function definitions are enclosed in 'extern "C"' braces. + +In some cases to restrict the library loading to DHCP servers so it +cannot be loaded by the DDNS server or the Control Agent. +The best way to perform this is to check the process name returned +by @c isc::dhcp::Daemon::getProcName() static / class method declared +in process/daemon.h header against "kea-dhcp4" and "kea-dhcp6" +(other values are "kea-dhcp-ddns", "kea-ctrl-agent" and "kea-netconf"). +If you'd like to check the address family too it is returned in DHCP servers +by isc::dhcp::CfgMgr::instance().getFamily() declared in dhcpsrv/cfgmgr.h +with AF_INET and AF_INET6 values. + +@subsubsection hooksdgMultiThreadingCompatibleFunction The "multi_threading_compatible" function + +"multi_threading_compatible" is used by the hooks framework to check +if the libraries it is loading are compatible with the DHCPv4 or DHCPv6 +server multi-threading configuration. The value 0 means not compatible +and is the default when the function is not implemented. Non 0 values +mean compatible. + +If your code implements it and returns the value 0 it is recommended +to document the reason so someone revisiting the code will not by +accident change the code. + +To be compatible means: +- the code associated with DHCP packet processing callouts e.g. +pkt4_receive or pkt6_send must be thread safe so the multi-threaded +DHCP service can simultaneously call more than once one of these callouts. +- commands registered by a library are not required to be thread safe because +commands are executed by the main thread. Now it is a good idea to make +them thread safe and to document cases where they are not. +- when a library implements a thread safe backend API (e.g. host data +source) the service methods must be thread safe. +- a library which modifies the internal configuration of the server, +e.g. creates or deletes a subnet, it must enter a critical section using +the @c isc::util::MultiThreadingCriticalSection RAII class. + +In the tutorial, we'll put "multi_threading_compatible" in its own file, +multi_threading_compatible.cc. The contents are: + +@code +// multi_threading_compatible.cc + +extern "C" { + +int multi_threading_compatible() { + return (1); +} + +} +@endcode + +and for a command creating a new subnet: + +@code +#include <util/multi_threading_mgr.h> + +int commandHandler(CalloutHandle& handle) { + ... + { + // Enter the critical section. + isc::util::MultiThreadingCriticalSection ct; + <add the subnet> + // Leave the critical section. + } + ... +} +@endcode + +@ref hooksMultiThreading provides more details about thread safety +requirements. + +@subsection hooksdgCallouts Callouts + +Having sorted out the framework, we now come to the functions that +actually do something. These functions are known as "callouts" because +the Kea code "calls out" to them. Each Kea server has a number of +hooks to which callouts can be attached: server-specific documentation +describes in detail the points in the server at which the hooks are +present together with the data passed to callouts attached to them. + +Before we continue with the example, we'll discuss how arguments are +passed to callouts and information is returned to the server. We will +also discuss how information can be moved between callouts. + +@subsubsection hooksdgCalloutSignature The Callout Signature + +All callouts are declared with the signature: +@code +extern "C" { +int callout(CalloutHandle& handle); +} +@endcode + +(As before, the callout is declared with "C" linkage.) Information is passed +between Kea and the callout through name/value pairs in the @c CalloutHandle +object. The object is also used to pass information between callouts on a +per-request basis. (Both of these concepts are explained below.) + +A callout returns an @c int as a status return. A value of 0 indicates +success, anything else signifies an error. The status return has no +effect on server processing; the only difference between a success +and error code is that if the latter is returned, the server will +log an error, specifying both the library and hook that generated it. +Effectively the return status provides a quick way for a callout to log +error information to the Kea logging system. + +@subsubsection hooksdgArguments Callout Arguments + +The @c CalloutHandle object provides two methods to get and set the +arguments passed to the callout. These methods are called (naturally +enough) getArgument and setArgument. Their usage is illustrated by the +following code snippets. + +@code + // Server-side code snippet to show the setting of arguments + + int count = 10; + boost::shared_ptr<Pkt4> pktptr = ... // Set to appropriate value + + // Assume that "handle" has been created + handle.setArgument("data_count", count); + handle.setArgument("inpacket", pktptr); + + // Call the callouts attached to the hook + ... + + // Retrieve the modified values + handle.getArgument("data_count", count); + handle.getArgument("inpacket", pktptr); +@endcode + +In the callout + +@code + int number; + boost::shared_ptr<Pkt4> packet; + + // Retrieve data set by the server. + handle.getArgument("data_count", number); + handle.getArgument("inpacket", packet); + + // Modify "number" + number = ...; + + // Update the arguments to send the value back to the server. + handle.setArgument("data_count", number); +@endcode + +As can be seen @c getArgument is used to retrieve data from the +@c CalloutHandle, and @c setArgument used to put data into it. If a callout +wishes to alter data and pass it back to the server, it should retrieve +the data with @c getArgument, modify it, and call @c setArgument to send +it back. + +There are several points to be aware of: + +- the data type of the variable in the call to @c getArgument must match +the data type of the variable passed to the corresponding @c setArgument +<B>exactly</B>: using what would normally be considered to be a +"compatible" type is not enough. For example, if the server passed +an argument as an @c int and the callout attempted to retrieve it as a +@c long, an exception would be thrown even though any value that can +be stored in an @c int will fit into a @c long. This restriction also +applies the "const" attribute but only as applied to data pointed to by +pointers, e.g., if an argument is defined as a @c char*, an exception will +be thrown if an attempt is made to retrieve it into a variable of type +@c const @c char*. (However, if an argument is set as a @c const @c int, +it can be retrieved into an @c int.) The documentation of each hook +point will detail the data type of each argument. +- Although all arguments can be modified, some altered values may not +be read by the server. (These would be ones that the server considers +"read-only".) Consult the documentation of each hook to see whether an +argument can be used to transfer data back to the server. +- If a pointer to an object is passed to a callout (either a "raw" +pointer, or a boost smart pointer (as in the example above), and the +underlying object is altered through that pointer, the change will be +reflected in the server even if no call is made to setArgument. + +In all cases, consult the documentation for the particular hook to see whether +parameters can be modified. As a general rule: + +- Do not alter arguments unless you mean the change to be reflected in +the server. +- If you alter an argument, call @c CalloutHandle::setArgument to update the +value in the @c CalloutHandle object. + +@subsubsection hooksdgNextStep The Next step status + +Note: This functionality used to be provided in Kea 0.9.2 and earlier using +boolean skip flag. See @ref hooksdgSkipFlag for explanation and tips how +to migrate your hooks code to this new API. + +When a to callouts attached to a hook returns, the server will usually continue +its processing. However, a callout might have done something that means that +the server should follow another path. Possible actions a server could take +include: + +- Continue as usual. This is the default value. Unless callouts explicitly +change the status, the server will continue processing. There is no need +to set the status, unless one callout wants to override the status set +by another callout. This action is represented by CalloutHandle::NEXT_STEP_CONTINUE. + +- Skip the next stage of processing because the callout has already +done it. For example, a hook is located just before the DHCP server +allocates an address to the client. A callout may decide to allocate +special addresses for certain clients, in which case it needs to tell +the server not to allocate an address in this case. This action is +hook specific and is represented by CalloutHandle::NEXT_STEP_SKIP. + +- Drop the packet and continue with the next request. A possible scenario +is a server where a callout inspects the hardware address of the client +sending the packet and compares it against a black list; if the address +is on it, the callout notifies the server to drop the packet. This +action is represented by CalloutHandle::NEXT_STEP_DROP. + +To handle these common cases, the @c CalloutHandle has a setStatus method. +This is set by a callout when it wishes the server to change the normal +processing. Exact meaning is hook specific. Please consult hook API +documentation for details. For historic reasons (Kea 0.9.2 used a single +boolean flag called skip that also doubled in some cases as an indicator +to drop the packet) several hooks use SKIP status to drop the packet. + +The methods to get and set the "skip" or "drop" state are getStatus and +setStatus. Their usage is intuitive: + +@code + // Get the current setting of the next step status. + auto status = handle.getStatus(); + + if (status == CalloutHandle::NEXT_STEP_DROP) + // Do something... + : + + if (status == CalloutHandle::NEXT_STEP_SKIP) + // Do something... + : + + // Do some processing... + : + if (lease_allocated) { + // Flag the server to skip the next step of the processing as we + // already have an address. + handle.setStatus(CalloutHandle::NEXT_STEP_SKIP); + } + return; + +@endcode + +Like arguments, the next step status is passed to all callouts on a hook. Callouts +later in the list are able to examine (and modify) the settings of earlier ones. + +If using multiple libraries, when the library wants to drop the current packet, +the DROP status must be used instead of the SKIP status so that the packet +processing ends at that specific hook point. + +It is recommended for all callouts to check the status before doing any +processing. As callouts can modify the status, it is recommended to take good +care when doing so, because this will have impact on all remaining hooks as well. +It is highly recommended to not reset the SKIP or DROP status to CONTINUE, even +though possible, so that the rest of the loaded hooks and the server can check +and perform the proper action. + +Some hook points handle special functionality for the server, like pkt4_receive, +pkt6_receive, which handle unpacking of the received packet, pkt4_send, pkt6_send, +which handle packing of the response packet. + +If the hook handles these actions and sets the next step flag to SKIP, it should +also perform a check for the SKIP flag before anything else. If it is already +set, do not pack/unpack the packet (other library, or even the same library, if +loaded multiple times, has done it already). Some libraries might also need to +throw exceptions in such cases because they need to perform specific actions before +pack/unpack (eg. addOption/delOption before pack action), which have no effect if +pack/unpack action is done previously by some other library. + +@code + // Check if other library has already set SKIP flag and performed unpack + // so that unpack is skipped + if (handle.getStatus() != CalloutHandle::NEXT_STEP_SKIP) { + query->unpack(); + } +@endcode + +@code + // Check the status state. + auto status = handle.getStatus(); + if (status == CalloutHandle::NEXT_STEP_SKIP) { + isc_throw(InvalidOperation, "packet pack already handled"); + } + ... + response->delOption(DEL_OPTION_CODE); + ... + response->addOption(ADD_OPTION_CODE); + ... + response->pack(); +@endcode + +As stated before, the order of loading libraries is critical in achieving the +desired behavior, so please read @ref hooksdgMultipleLibraries when configuring +multiple libraries. + +@subsubsection hooksdgSkipFlag The "Skip" Flag (deprecated) + +In releases 0.9.2 and earlier, the functionality currently offered by next step +status (see @ref hooksdgNextStep) was provided by +a boolean flag called "Skip". However, since it only allowed to either continue +or skip the next processing step and was not extensible to other decisions, +setSkip(bool) call was replaced with a setStatus(enum) in Kea 1.0. This +new approach is extensible. If we decide to add new results (e.g., WAIT +or RATELIMIT), we will be able to do so without changing the API again. + +If you have your hooks libraries that take advantage of skip flag, migrating +to the next step status is very easy. See @ref hooksdgNextStep for detailed +explanation of the new status field. + +To migrate, replace this old code: +@code +handle.setSkip(false); // This is the default. + +handle.setSkip(true); // Tell the server to skip the next processing step. + +bool skip = hangle.getSkip(); // Check the skip flag state. +if (skip) { + ... +} +@endcode + +with this: + +@code +// This is the default. +handle.setStatus(CalloutHandle::NEXT_STEP_CONTINUE); + +// Tell the server to skip the next processing step. +handle.setStatus(CalloutHandle::NEXT_STEP_SKIP); + +// Check the status state. +auto status = handle.getStatus(); +if (status == CalloutHandle::NEXT_STEP_SKIP) { + ... +} +@endcode + +@subsubsection hooksdgCalloutContext Per-Request Context + +Although the Kea modules can be characterized as handling a single +packet at a time - e.g., the DHCPv4 server receives a DHCPDISCOVER packet, +processes it and responds with an DHCPOFFER, this may not always be true. +Future developments may have the server processing multiple packets +simultaneously, or to suspend processing on a packet and resume it at +a later time after other packets have been processed. + +As well as argument information, the @c CalloutHandle object can be used by +callouts to attach information to a packet being handled by the server. +This information (known as "context") is not used by the server: its purpose +is to allow callouts to pass information between one another on a +per-packet basis. + +Context associated with a packet only exists only for the duration of the +processing of that packet: when processing is completed, the context is +destroyed. A new packet starts with a new (empty) context. Context is +particularly useful in servers that may be processing multiple packets +simultaneously: callouts can effectively attach data to a packet that +follows the packet around the system. + +Context information is held as name/value pairs in the same way +as arguments, being accessed by the pair of methods @c setContext and +@c getContext. They have the same restrictions as the @c setArgument and +@c getArgument methods - the type of data retrieved from context must +<B>exactly</B> match the type of the data set. + +The example in the next section illustrates their use. + + +@subsection hooksdgExampleCallouts Example Callouts + +Continuing with the tutorial, the requirements need us to retrieve the +hardware address of the incoming packet, classify it, and write it, +together with the assigned IP address, to a log file. Although we could +do this in one callout, for this example we'll use two: + +- pkt4_receive - a callout on this hook is invoked when a packet has been +received and has been parsed. It is passed a single argument, "query4" +which is an isc::dhcp::Pkt4Ptr object, holding a pointer to the +isc::dhcp::Pkt4 object (representing a DHCPv4 packet). We will do the +classification here. + +- pkt4_send - called when a response is just about to be sent back to +the client. It is passed a single argument "response4". This is the +point at which the example code will write the hardware and IP addresses +to the log file. + +The standard for naming callouts is to give them the same name as +the hook. If this is done, the callouts will be automatically found +by the Hooks system (this is discussed further in section @ref +hooksdgCalloutRegistration). For our example, we will assume this is the +case, so the code for the first callout (used to classify the client's +hardware address) is: + +@code +// pkt_receive4.cc + +#include <hooks/hooks.h> +#include <dhcp/pkt4.h> +#include "library_common.h" + +#include <string> + +using namespace isc::dhcp; +using namespace isc::hooks; +using namespace std; + +extern "C" { + +// This callout is called at the "pkt4_receive" hook. +int pkt4_receive(CalloutHandle& handle) { + + // A pointer to the packet is passed to the callout via a "boost" smart + // pointer. The include file "pkt4.h" typedefs a pointer to the Pkt4 + // object as Pkt4Ptr. Retrieve a pointer to the object. + Pkt4Ptr query4_ptr; + handle.getArgument("query4", query4_ptr); + + // Point to the hardware address. + HWAddrPtr hwaddr_ptr = query4_ptr->getHWAddr(); + + // The hardware address is held in a public member variable. We'll classify + // it as interesting if the sum of all the bytes in it is divisible by 4. + // (This is a contrived example after all!) + long sum = 0; + for (int i = 0; i < hwaddr_ptr->hwaddr_.size(); ++i) { + sum += hwaddr_ptr->hwaddr_[i]; + } + + // Classify it. + if (sum % 4 == 0) { + // Store the text form of the hardware address in the context to pass + // to the next callout. + string hwaddr = hwaddr_ptr->toText(); + handle.setContext("hwaddr", hwaddr); + } + + return (0); +}; + +} +@endcode + +The "pkt4_receive" callout placed the hardware address of an interesting client in +the "hwaddr" context for the packet. Turning now to the callout that will +write this information to the log file: + +@code +// pkt4_send.cc + +#include <hooks/hooks.h> +#include <dhcp/pkt4.h> +#include "library_common.h" + +#include <string> + +using namespace isc::dhcp; +using namespace isc::hooks; +using namespace std; + +extern "C" { + +// This callout is called at the "pkt4_send" hook. +int pkt4_send(CalloutHandle& handle) { + + // Obtain the hardware address of the "interesting" client. We have to + // use a try...catch block here because if the client was not interesting, + // no information would be set and getArgument would thrown an exception. + string hwaddr; + try { + handle.getContext("hwaddr", hwaddr); + + // getContext didn't throw so the client is interesting. Get a pointer + // to the reply. + Pkt4Ptr response4_ptr; + handle.getArgument("response4", response4_ptr); + + // Get the string form of the IP address. + string ipaddr = response4_ptr->getYiaddr().toText(); + + // Write the information to the log file. + interesting << hwaddr << " " << ipaddr << "\n"; + + // ... and to guard against a crash, we'll flush the output stream. + flush(interesting); + + } catch (const NoSuchCalloutContext&) { + // No such element in the per-request context with the name "hwaddr". + // This means that the request was not an interesting, so do nothing + // and dismiss the exception. + } + + return (0); +} + +} +@endcode + + +@subsection hooksdgLogging Logging in the Hooks Library + +Hooks libraries take part in the DHCP message processing. They also often +modify the server's behavior by taking responsibility for processing +the DHCP message at certain stages and instructing the server to skip +the default processing for that stage. Thus, hooks libraries play an +important role in the DHCP server operation and, depending on their +purpose, they may have high complexity, which increases likelihood of the +defects in the libraries. + +All hooks libraries should use Kea logging system to facilitate diagnostics +of the defects in the libraries and issues with the DHCP server's operation. +Even if the issue doesn't originate in the hooks library itself, the use +of the library may uncover issues in the Kea code that only +manifest themselves in some special circumstances. + +Hooks libraries use the Kea logging system in the same way as any other +standard Kea library. A hooks library should have at least one logger +defined, but may have multiple loggers if it is desired +to separate log messages from different functional parts of the library. + +Assuming that it has been decided to use logging in the hooks library, the +implementor must select a unique name for the logger. Ideally the name +should have some relationship with the name of the library so that it is +easy to distinguish messages logged from this library. For example, +if the hooks library is used to capture incoming and outgoing DHCP +messages, and the name of the library is "libkea-packet-capture", +a suitable logger name could be "packet-capture". + +In order to use a logger within the library, the logger should be declared +in a header file, which must be included in all files using +the logger: + +@code +#ifndef PACKET_CAPTURE_LOG_H +#define PACKET_CAPTURE_LOG_H + +#include <log/message_initializer.h> +#include <log/macros.h> +#include <user_chk_messages.h> + +namespace packet_capture { + +extern isc::log::Logger packet_capture_logger; + +} + +#endif +@endcode + +The logger should be defined and initialized in the implementation file, +as illustrated below: + +@code +#include <packet_capture_log.h> + +namespace packet_capture { + +isc::log::Logger packet_capture_logger("packet-capture"); + +} +@endcode + +These files may contain multiple logger declarations and initializations +when the use of more than one logger is desired. + +The next step is to add the appropriate message file as described in the +@ref logMessageFiles. + +The implementor must make sure that log messages appear in the right +places and that they are logged at the appropriate level. The choice +of the place where the message should appear is not always obvious: +it depends if the particular function being called already logs enough +information and whether adding log message before and/or after the +call to this function would simply duplicate some messages. Sometimes +the choice whether the log message should appear within the function or +outside of it depends on the level of details available for logging. For +example, in many cases it is desirable to include the client identifier +or transaction id of the DHCP packet being processed in logging message. +If this information is available at the higher level but not in the +function being called, it is often better to place the log message at +higher level. However, the function parameters list could be extended +to include the additional information, and to be logged and the logging +call made from within the function. + +Ideally, the hooks library should contain debug log messages (traces) +in all significant decision points in the code, with the information as to +how the code hit this decision point, how it will proceed and why. +However, care should be taken when selecting the log level for those +messages, because selecting too high logging level may impact the +performance of the system. For this reason, traces (messages of +the debug severity) should use different debug levels for the +messages of different importance or having different performance +requirements to generate the log message. For example, generation of +a log message, which prints full details of a packet, usually requires +more CPU bandwidth than the generation of the message which only prints +the packet type and length. Thus, the former should be logged at +lower debug level (see @ref logSeverity for details of using +various debug levels using "dbglevel" parameter). + +All loggers defined within the hooks libraries derive the default +configuration from the root logger. For example, when the hooks +library is attached to the DHCPv4 server, the root logger name is +"kea-dhcp4", and the library by default uses configuration of this +logger. The configuration of the library's logger can +be modified by adding a configuration entry for it +to the configuration file. In case of the "packet-capture" +logger declared above, the full name of the logger in the +configuration file will be "kea-dhcp4.packet-capture". The +configuration specified for this logger will override the default +configuration derived from the root logger. + + +@subsection hooksdgBuild Building the Library + +Building the code requires building a sharable library. This requires +the the code be compiled as position-independent code (using the +compiler's "-fpic" switch) and linked as a shared library (with the +linker's "-shared" switch). The build command also needs to point to +the Kea include directory and link in the appropriate libraries. + +Assuming that Kea has been installed in the default location, the +command line needed to create the library using the Gnu C++ compiler on a +Linux system is: + +@code +g++ -I <install-dir>/include/kea -L <install-dir>/lib -fpic -shared -o example.so \ + load_unload.cc pkt4_receive.cc pkt4_send.cc version.cc \ + -lkea-dhcpsrv -lkea-dhcp++ -lkea-hooks -lkea-log -lkea-util -lkea-exceptions +@endcode + +Notes: +- Replace "<install-dir>" with the location in which you installed Kea. Unless +you specified the "--prefix" switch on the "configure" command line when +building Kea, it will be installed in the default location, usually /usr/local. +- The compilation command and switches required may vary depending on +your operating system and compiler - consult the relevant documentation +for details. +- The list of libraries that need to be included in the command line +depends on the functionality used by the hook code and the module to +which they are attached. Depending on operating system, you may also need +to explicitly list libraries on which the Kea libraries you link against depend. + + +@subsection hooksdgConfiguration Configuring the Hooks Library + +The final step is to make the library known to Kea. The configuration +keywords of all Kea modules to which hooks can be added contain the +"hooks-libraries" element and user libraries are added to this. (The Kea +hooks system can handle multiple libraries - this is discussed below.) + +To add the example library (assumed to be in /usr/local/lib) to the +DHCPv4 module, it must be listed in the "hooks-libraries" element of the +"Dhcp4" part of the configuration file: + +@code +"Dhcp4": { + : + "hooks-libraries": [ + { + "library": "/usr/local/lib/example.so" + } + ] + : +} +@endcode +(Note that "hooks" is plural.) + +Each entry in the "hooks-libraries" list is a structure (a "map" in JSON +parlance) that holds the following element: +- library - the name of the library to load. This must be a string. + +@note The syntax of the hooks-libraries configuration element has changed +since kea 0.9.2 (in that version, "hooks-libraries" was just a list of +libraries). This change is in preparation for the introduction of +library-specific parameters, which will be added to Kea in a version after 1.0. + +The DHCPv4 server will load the library and execute the callouts each time a +request is received. + +@note All the above assumes that the hooks library will be used with a +version of Kea that is dynamically-linked. For information regarding +running hooks libraries against a statically-linked Kea, see @ref +hooksdgStaticallyLinkedKea. + +@section hooksdgAdvancedTopics Advanced Topics + + +@subsection hooksdgContextCreateDestroy Context Creation and Destruction + +As well as the hooks defined by the server, the hooks framework defines +two hooks of its own, "context_create" and "context_destroy". The first +is called when a request is created in the server, before any of the +server-specific hooks gets called. It's purpose it to allow a library +to initialize per-request context. The second is called after all +server-defined hooks have been processed, and is to allow a library to +tidy up. + +As an example, the "pkt4_send" example above required that the code +check for an exception being thrown when accessing the "hwaddr" context +item in case it was not set. An alternative strategy would have been to +provide a callout for the "context_create" hook and set the context item +"hwaddr" to an empty string. Instead of needing to handle an exception, +"pkt4_send" would be guaranteed to get something when looking for +the hwaddr item and so could write or not write the output depending on +the value. + +In most cases, "context_destroy" is not needed as the Hooks system +automatically deletes context. An example where it could be required +is where memory has been allocated by a callout during the processing +of a request and a raw pointer to it stored in the context object. On +destruction of the context, that memory will not be automatically +released. Freeing in the memory in the "context_destroy" callout will solve +that problem. + +Actually, when the context is destroyed, the destructor +associated with any objects stored in it are run. Rather than point to +allocated memory with a raw pointer, a better idea would be to point to +it with a boost "smart" pointer and store that pointer in the context. +When the context is destroyed, the smart pointer's destructor is run, +which will automatically delete the pointed-to object. + +These approaches are illustrated in the following examples. +Here it is assumed that the hooks library is performing some form of +security checking on the packet and needs to maintain information in +a user-specified "SecurityInformation" object. (The details of this +fictitious object are of no concern here.) The object is created in +the "context_create" callout and used in both the "pkt4_receive" and the +"pkt4_send" callouts. + +@code +// Storing information in a "raw" pointer. Assume that the + +#include <hooks/hooks.h> + : + +extern "C" { + +// context_create callout - called when the request is created. +int context_create(CalloutHandle& handle) { + // Create the security information and store it in the context + // for this packet. + SecurityInformation* si = new SecurityInformation(); + handle.setContext("security_information", si); +} + +// Callouts that use the context +int pkt4_receive(CalloutHandle& handle) { + // Retrieve the pointer to the SecurityInformation object + SecurityInformation* si; + handle.getContext("security_information", si); + : + : + // Set the security information + si->setSomething(...); + + // The pointed-to information has been updated but the pointer has not been + // altered, so there is no need to call setContext() again. +} + +int pkt4_send(CalloutHandle& handle) { + // Retrieve the pointer to the SecurityInformation object + SecurityInformation* si; + handle.getContext("security_information", si); + : + : + // Retrieve security information + bool active = si->getSomething(...); + : +} + +// Context destruction. We need to delete the pointed-to SecurityInformation +// object because we will lose the pointer to it when the @c CalloutHandle is +// destroyed. +int context_destroy(CalloutHandle& handle) { + // Retrieve the pointer to the SecurityInformation object + SecurityInformation* si; + handle.getContext("security_information", si); + + // Delete the pointed-to memory. + delete si; +} +@endcode + +The requirement for the "context_destroy" callout can be eliminated if +a Boost shared ptr is used to point to the allocated memory: + +@code +// Storing information in a "raw" pointer. Assume that the + +#include <hooks/hooks.h> +#include <boost/shared_ptr.hpp> + : + +extern "C" { + +// context_create callout - called when the request is created. + +int context_create(CalloutHandle& handle) { + // Create the security information and store it in the context for this + // packet. + boost::shared_ptr<SecurityInformation> si(new SecurityInformation()); + handle.setContext("security_information", si); +} + +// Other than the data type, a shared pointer has similar semantics to a "raw" +// pointer. Only the code from "pkt4_receive" is shown here. + +int pkt4_receive(CalloutHandle& handle) { + // Retrieve the pointer to the SecurityInformation object + boost::shared_ptr<SecurityInformation> si; + handle.setContext("security_information", si); + : + : + // Modify the security information + si->setSomething(...); + + // The pointed-to information has been updated but the pointer has not + // altered, so there is no need to reset the context. +} + +// No context_destroy callout is needed to delete the allocated +// SecurityInformation object. When the @c CalloutHandle is destroyed, the shared +// pointer object will be destroyed. If that is the last shared pointer to the +// allocated memory, then it too will be deleted. +@endcode + +(Note that a Boost shared pointer - rather than any other Boost smart pointer - +should be used, as the pointer objects are copied within the hooks framework and +only shared pointers have the correct behavior for the copy operation.) + + +@subsection hooksdgCalloutRegistration Registering Callouts + +As briefly mentioned in @ref hooksdgExampleCallouts, the standard is for +callouts in the user library to have the same name as the name of the +hook to which they are being attached. This convention was followed +in the tutorial, e.g., the callout that needed to be attached to the +"pkt4_receive" hook was named pkt4_receive. + +The reason for this convention is that when the library is loaded, the +hook framework automatically searches the library for functions with +the same names as the server hooks. When it finds one, it attaches it +to the appropriate hook point. This simplifies the loading process and +bookkeeping required to create a library of callouts. + +However, the hooks system is flexible in this area: callouts can have +non-standard names, and multiple callouts can be registered on a hook. + + +@subsubsection hooksdgLibraryHandle The LibraryHandle Object + +The way into the part of the hooks framework that allows callout +registration is through the LibraryHandle object. This was briefly +introduced in the discussion of the framework functions, in that +an object of this type is pass to the "load" function. A LibraryHandle +can also be obtained from within a callout by calling the CalloutHandle's +@c getLibraryHandle() method. + +The LibraryHandle provides three methods to manipulate callouts: + +- @c registerCallout - register a callout on a hook. +- @c deregisterCallout - deregister a callout from a hook. +- @c deregisterAllCallouts - deregister all callouts on a hook. + +The following sections cover some of the ways in which these can be used. + +@subsubsection hooksdgNonstandardCalloutNames Non-Standard Callout Names + +The example in the tutorial used standard names for the callouts. As noted +above, it is possible to use non-standard names. Suppose, instead of the +callout names "pkt4_receive" and "pkt4_send", we had named our callouts +"classify" and "write_data". The hooks framework would not have registered +these callouts, so we would have needed to do it ourself. The place to +do this is the "load" framework function, and its code would have had to +been modified to: + +@code +int load(LibraryHandle& libhandle) { + // Register the callouts on the hooks. We assume that a header file + // declares the "classify" and "write_data" functions. + libhandle.registerCallout("pkt4_receive", classify); + libhandle.registerCallout("pkt4_send", write_data); + + // Open the log file + interesting.open("/data/clients/interesting.log", + std::fstream::out | std::fstream::app); + return (interesting ? 0 : 1); +} +@endcode + +It is possible for a library to contain callouts with both standard and +non-standard names: ones with standard names will be registered automatically, +ones with non-standard names need to be registered manually. + +@subsubsection hooksdgCommandHandlers Using Callouts as Command handlers + +Kea servers natively support a set of control commands to retrieve and update +runtime information, e.g. server configuration, basic statistics etc. In +many cases, however, DHCP deployments require support for additional commands +or the natively supported commands don't exactly fulfill one's requirements. + +Taking advantage of Kea's modularity and hooks framework, it is now possible +to easily extend the pool of supported commands by implementing additional +(non-standard) commands within hook libraries. + +A hook library needs to register command handlers for control commands within +its @c load function as follows: + +@code +int load(LibraryHandle& handle) { + handle.registerCommandCallout("diagnostics-enable", diagnostics_enable); + handle.registerCommandCallout("diagnostics-dump", diagnostics_dump); + return (0); +} +@endcode + +Internally, the @c LibraryHandle associates command handlers @c diagnostics_enable +and @c diagnostics_dump with dedicated hook points. These hook points are +given names after the command names, i.e. "$diagnostics_enable" and +"$diagnostics_dump". The dollar sign before the hook point name indicates +that the hook point is dedicated for a command handler, i.e. is not one of +the standard hook points used by the Kea servers. This is just a naming convention, +usually invisible to the hook library implementation and is mainly aimed at +minimizing a risk of collision between names of the hook points registered with +command handlers and standard hook points. + +Once the hook library is loaded and the command handlers supported by the +library are registered, the Kea servers will be able to recognize that those +specific commands are supported and will dispatch commands with the corresponding +names to the hook library (or multiple hook libraries) for processing. See the +documentation of the @ref isc::config::HookedCommandMgr for more details how +it uses @c HooksManager::commandHandlersPresent to determine if the received +command should be dispatched to a hook library for processing. + +The @c diagnostics_enable and @c diagnostics_dump command +handlers must be implemented within the hook library in analogous way to +regular callouts: + +@code +int diagnostics_enable(CalloutHandle& handle) { + ConstElementPtr response; + + try { + ConstElementPtr command; + handle.getArgument("command", command); + ConstElementPtr args; + static_cast<void>(isc::config::parseCommand(args, command)); + + // ... + // handle command here. + // ... + + response = createAnswer(CONTROL_RESULT_SUCCESS, "successful"); + + } catch (const std::exception& ex) { + response = createAnswer(CONTROL_RESULT_ERROR, ex.what()); + } + + handle.setArgument("response", response); + + return (0); +} +@endcode + +The sample code above retrieves the "command" argument which is always provided. +It represents the control command as sent by the controlling client. It includes +command name and command specific arguments. The generic @ref isc::config::parseCommand +can be used to retrieve arguments included in the command. The callout then interprets +these arguments, takes appropriate action and creates a response to the client. +Care should be taken to catch any non-fatal exceptions that may arise during the callout +that should be reported as a failure to the controlling client. In such case, the response +with @c CONTROL_RESULT_ERROR is returned and the callout should return the value of 0. +The non-zero result should only be returned by the callout in case of fatal errors, i.e. +errors which result in inability to generate a response to the client. If the response +is generated, the command handler must set it as "response" argument prior to return. + +It is uncommon but valid scenario to have multiple hook libraries providing command +handlers for the same command. They are invoked sequentially and each of them +can freely modify a response set by a previous callout. This includes entirely +replacing the response provided by previous callouts, if necessary. + +@subsubsection hooksdgMultipleCallouts Multiple Callouts on a Hook + +The Kea hooks framework allows multiple callouts to be attached to +a hook point. Although it is likely to be rare for user code to need to +do this, there may be instances where it make sense. + +To register multiple callouts on a hook, just call +@c LibraryHandle::registerCallout multiple times on the same hook, e.g., + +@code + libhandle.registerCallout("pkt4_receive", classify); + libhandle.registerCallout("pkt4_receive", write_data); +@endcode + +The hooks framework will call the callouts in the order they are +registered. The same @c CalloutHandle is passed between them, so any +change made to the CalloutHandle's arguments, "skip" flag, or per-request +context by the first is visible to the second. + + +@subsection hooksdgMultipleLibraries Multiple User Libraries + +As alluded to in the section @ref hooksdgConfiguration, Kea can load +multiple libraries. The libraries are loaded in the order specified in +the configuration, and the callouts attached to the hooks in the order +presented by the libraries. + +The following picture illustrates this, and also illustrates the scope of +data passed around the system. + +@image html DataScopeArgument.png "Scope of Arguments" + +In this illustration, a server has three hook points, alpha, beta +and gamma. Two libraries are configured, library 1 and library 2. +Library 1 registers the callout "authorize" for hook alpha, "check" for +hook beta and "add_option" for hook gamma. Library 2 registers "logpkt", +"validate" and "putopt" + +The horizontal red lines represent arguments to callouts. When the server +calls hook alpha, it creates an argument list and calls the +first callout for the hook, "authorize". When that callout returns, the +same (but possibly modified) argument list is passed to the next callout +in the chain, "logpkt". Another, separate argument list is created for +hook beta and passed to the callouts "check" and "validate" in +that order. A similar sequence occurs for hook gamma. + +The next picture shows the scope of the context associated with a +request. + +@image html DataScopeContext.png "Illustration of per-library context" + +The vertical blue lines represent callout context. Context is +per-packet but also per-library. When the server calls "authorize", +the CalloutHandle's @c getContext and @c setContext methods access a context +created purely for library 1. The next callout on the hook will access +context created for library 2. These contexts are passed to the callouts +associated with the next hook. So when "check" is called, it gets the +context data that was set by "authorize", when "validate" is called, +it gets the context data set by "logpkt". + +It is stressed that the context for callouts associated with different +libraries is entirely separate. For example, suppose "authorize" sets +the CalloutHandle's context item "foo" to 2 and "logpkt" sets an item of +the same name to the string "bar". When "check" accesses the context +item "foo", it gets a value of 2; when "validate" accesses an item of +the same name, it gets the value "bar". + +It is also stressed that all this context exists only for the life of the +request being processed. When that request is complete, all the +context associated with that request - for all libraries - is destroyed, +and new context created for the next request. + +This structure means that library authors can use per-request context +without worrying about the presence of other libraries. Other libraries +may be present, but will not affect the context values set by a library's +callouts. + +Configuring multiple libraries just requires listing the libraries +as separate elements of the hooks-libraries configuration element, e.g., + +@code +"Dhcp4": { + : + "hooks-libraries": [ + { + "library": "/usr/lib/library1.so" + }, + { + "library": "/opt/library2.so" + } + : + ] +} +@endcode + + +@subsection hooksdgInterLibraryData Passing Data Between Libraries + +In rare cases, it is possible that one library may want to pass +data to another. This can be done in a limited way by means of the +CalloutHandle's @c setArgument and @c getArgument calls. For example, in the +above diagram, the callout "add_option" can pass a value to "putopt" +by setting a name.value pair in the hook's argument list. "putopt" +would be able to read this, but would not be able to return information +back to "add_option". + +All argument names used by Kea will be a combination of letters +(both upper- and lower-case), digits, hyphens and underscores: no +other characters will be used. As argument names are simple strings, +it is suggested that if such a mechanism be used, the names of the data +values passed between the libraries include a special character such as +the dollar symbol or percent sign. In this way there is no danger that +a name will conflict with any existing or future Kea argument names. + + +@subsection hooksdgStaticallyLinkedKea Running Against a Statically-Linked Kea + +If Kea is built with the --enable-static-link switch (set when +running the "configure" script), no shared Kea libraries are built; +instead, archive libraries are created and Kea is linked to them. +If you create a hooks library also linked against these archive libraries, +when the library is loaded you end up with two copies of the library code, +one in Kea and one in your library. + +To run successfully, your library needs to perform run-time initialization +of the Kea code in your library (something performed by Kea +in the case of shared libraries). To do this, call the function +isc::hooks::hooksStaticLinkInit() as the first statement of the load() +function. (If your library does not include a load() function, you need +to add one.) For example: + +@code +#include <hooks/hooks.h> + +extern "C" { + +int version() { + return (KEA_HOOKS_VERSION); +} + +int load() { + isc::hooks::hooksStaticLinkInit(); + : +} + +// Other callout functions + : + +} +@endcode + + +@subsection hooksdgHooksConfig Configuring Hooks Libraries + +Sometimes it is useful for the hook library to have some configuration parameters. +This capability was introduced in Kea 1.1. This is often convenient to follow +generic Kea configuration approach rather than invent your own configuration +logic. Consider the following example: + +@code +"hooks-libraries": [ + { + "library": "/opt/first.so" + }, + { + "library": "/opt/second.so", + "parameters": { + } + }, + { + "library": "/opt/third.so", + "parameters": { + "mail": "spam@example.com", + "floor": 13, + "debug": false, + "users": [ "alice", "bob", "charlie" ], + "languages": { + "french": "bonjour", + "klingon": "yl'el" + } + } + } +] +@endcode + +This example has three hook libraries configured. The first and second have +no parameters. Note that parameters map is optional, but it's perfectly okay to +specify it as an empty map. The third library is more interesting. It has five +parameters specified. The first one called 'mail' is a string. The second one +is an integer and the third one is boolean. Fourth and fifth parameters are +slightly more complicated as they are a list and a map respectively. JSON +structures can be nested if necessary, e.g., you can have a list, which contains +maps, maps that contain maps that contain other maps etc. Any valid JSON +structure can be represented. One important limitation here is that the top +level "parameters" structure must either be a map or not present at all. + +Those parameters can be accessed in load() method. Passed isc::hooks::LibraryHandle +object has a method called getParameter that returns an instance of +isc::data::ConstElementPtr or null pointer if there was no parameter specified. This pointer +will point to an object derived from isc::data::Element class. For detailed +explanation how to use those objects, see isc::data::Element class. + +Here's a brief overview of how to use those elements: + + - x = getParameter("mail") will return instance of isc::data::StringElement. The content + can be accessed with x->stringValue() and will return std::string. + - x = getParameter("floor") will return an instance of isc::data::IntElement. + The content can be accessed with x->intValue() and will return int. + - x = getParameter("debug") will return an instance of isc::data::BoolElement. + Its value can be accessed with x->boolValue() and will return bool. + - x = getParameter("users") will return an instance of isc::data::ListElement. + Its content can be accessed with the following methods: + x->size(), x->get(index) + - x = getParameter("watch-list") will return an instance of isc::data::MapElement. + Its content can be accessed with the following methods: + x->find("klingon"), x->contains("french"), x->size() + +Keep in mind that the user can structure his config file incorrectly. +Remember to check if the structure has the expected type before using type specific +method. For example calling stringValue on IntElement will throw an exception. +You can do this by calling isc::data::Element::getType. + +Here's an example that obtains all of the parameters specified above. +If you want to get nested elements, Element::get(index) and Element::find(name) +will return ElementPtr, which can be iterated in similar manner. + +@code +int load(LibraryHandle& handle) { + ConstElementPtr mail = handle.getParameter("mail"); + ConstElementPtr floor = handle.getParameter("floor"); + ConstElementPtr debug = handle.getParameter("debug"); + ConstElementPtr users = handle.getParameter("users"); + ConstElementPtr lang = handle.getParameter("languages"); + + // String handling example + if (!mail) { + // Handle missing 'mail' parameter here. + return (1); + } + if (mail->getType() != Element::string) { + // Handle incorrect 'mail' parameter here. + return (1); + } + std::string mail_str = mail->stringValue(); + + // In the following examples safety checks are omitted for clarity. + // Make sure you do it properly similar to mail example above + // or you risk dereferencing null pointer or at least throwing + // an exception! + + // Integer handling example + int floor_num = floor->intValue(); + + // Boolean handling example + bool debug_flag = debug->boolValue(); + + // List handling example + std::cout << "There are " << users->size() << " users defined." << std::endl; + for (int i = 0; i < users->size(); i++) { + ConstElementPtr user = users->get(i); + std::cout << "User " << user->stringValue() << std::endl; + } + + // Map handling example + std::cout << "There are " << lang->size() << " languages defined." << std::endl; + if (lang->contains("french")) { + std::cout << "One of them is French!" << std::endl; + } + ConstElementPtr greeting = lang->find("klingon"); + if (greeting) { + std::cout << "Lt. Worf says " << greeting->stringValue() << std::endl; + } + + // All validation steps were successful. The library has all the parameters + // it needs, so we should report a success. + return (0); +} +@endcode + +A good sources of examples could be unit-tests in file src/lib/cc/tests/data_unittests.cc +which are dedicated to isc::data::Element testing and src/lib/hooks/tests/callout_params_library.cc, +which is an example library used in testing. This library expects exactly 3 parameters: +svalue (which is a string), ivalue (which is an integer) and bvalue (which is a boolean). + +@subsection hooksMemoryManagement Memory Management Considerations for Hooks Writer + +Both Kea server memory space and hook library memory space share a common +address space between the opening of the hook (call to dlopen() as the first +phase of the hook library loading) and the closing of the hook (call to +dlclose() as the last phase of the hook library unloading). There are +pointers between the two memory spaces with at least two bad consequences +when they are not correctly managed: + +- Kea uses shared pointers for its objects. If the hook ownership keeps +ownership of an object, this object will never be destroyed, leading to +a trivial memory leak. Some care is recommended when the hook library +uses a garbage collector to not postpone releases of no longer used +objects. Cycles should be avoided too, for instance using weak pointers. +Of course at the opposite, if a Kea object is needed ownership on, it must +be kept in order to not get a dangling pointer when it will be destroyed +at the end of its last reference lifetime. + +- Kea can take some pointers to the hook library memory space, for instance +when a hook object is registered. If these pointers are not destroyed +before the hook library memory space is unmapped by dlclose() this likely +leads to a crash. + +Communication between Kea code and hook library code is provided by +callout handles. For callout points related to a packet, the callout +handle is associated with the packet allowing to get the same callout handle +for all callout points called during processing of a query. + +Hook libraries are closed i.e. hook library memory spaces are unmapped +only when there is no active callout handles. This enforces a correct +behavior at two conditions: + +- there is no "wild" dangling pointers, for instance no registered +objects. + +- this can happen i.e. the hook library does not keep a shared pointer +to a query packet. + +To allow hook writers to fulfill these two conditions the unload() entry +point is called in the first phase of the unloading process since Kea +version 1.7.10. For instance if the hook library uses the PIMPL code +pattern the unload() entry point must reset the pointer to the +hook library implementation. + +@subsection hooksMultiThreading Multi-Threading Considerations for Hooks Writers + +Multi-threading programming in C++ is not easy. For instance STL containers +do not support simultaneous read and write accesses. Kea is written in C++ +so a priori for all Kea APIs one should never assume thread safety. + +When a hook library is internally multi-threaded, its code and any Kea API +used simultaneously by different threads must be thread safe. To mark +the difference between this and the other thread safety requirement this +is called "generic thread safe". + +When multi-threaded packet processing is enabled, Kea servers perform +some actions by the main thread and packet processing by members of +a thread pool. The multi-threading mode is returned by: +@code +isc::util::MultiThreadingMgr::instance().getMode() +@endcode +When it is false, Kea is single threaded and there is no thread safety +requirement, when it is true, the requirement is named Kea packet processing +thread safe shorten into "Kea thread safe". + +A typical Kea thread safe looks like: +@code +int Foo() { + if (MultiThreadingMgr::instance().getMode()) { + std::lock_guard<std::mutex> lock(mutex_); + return (FooInternal()); + } else { + return (FooInternal()); + } +} +@endcode + +The overhead of mutexes and other synchronization tools is far greater +than a test and branch so it is the recommended way to implement Kea +thread safety. + +When a hook library entry point can be called from a packet processing +thread, typically from a packet processing callout but also when +implementing a lease or host backend API, the entry point code must +be Kea thread safe. If it is not possible the hook library must +be marked as not multi-threading compatible (i.e. return 0 from +multi_threading_compatible). + +At the opposite during (re)configuration including libload command +and config backend, only the main thread runs, so version, load, unload, +multi_threading_compatible, dhcp4_srv_configured, dhcp6_srv_configured, +cb4_updated and cb6_updated have no thread safety requirements. + +Other hook library entry points are called by the main thread: + - io service (io context is recent boost versions) is polled by the main + thread + - external socket callbacks are executed by the main thread + - commands including command_process + +The packet processing threads are not stopped so either the entry +point code is Kea thread safe or it uses a critical section +(@c isc::util::MultiThreadingCriticalSection) to stop the packet +processing threads during the execution of the not Kea thread safe code. +Of course critical sections have an impact on performance so they should +be used only for particular cases where no better choice is available. + +Some Kea APIs were made thread safe mainly because they are used by the +packet processing: + - logging is generic thread safe and even multi process safe i.e. + messages logged into a file or by syslog from multiple processes + do not mix. + - statistics update is Kea thread safe. + - lease and host database backends are Kea thread safe. Note if you need to + perform a direct MySQL or PostgreSQL query you must use the connection pool. + - state model and watched thread are generic thread safe (libkea-util) + - interval timer setup and cancel are generic thread safe (libkea-asiolink) + - parking lots are generic thread safe (libkea-hooks) + - external sockets are generic thread safe (libkea-dhcp++) + - http client is Kea thread safe (libkea-http) + +Some other Kea APIs are intrinsically thread safe because they do not +involve a shared structure so for instance despite of its name the +interface manager send methods are generic thread safe. + +Per library documentation details thread safety to help hooks writers +and to provide an exhaustive list of Kea thread safe APIs: + - @ref utilMTConsiderations + - @ref logMTConsiderations + - @ref asiolinkMTConsiderations + - @ref ccMTConsiderations + - @ref databaseMTConsiderations + - @ref ctrlSocketMTConsiderations + - @ref libdhcpMTConsiderations + - @ref statsMTConsiderations + - @ref yangMTConsiderations + - @ref libdhcp_ddnsMTConsiderations + - @ref dhcpEvalMTConsiderations + - @ref cplMTConsiderations + - @ref dhcpDatabaseBackendsMTConsiderations + - @ref libdhcpsrvMTConsiderations + - @ref httpMTConsiderations + +*/ diff --git a/src/lib/hooks/images/DataScopeArgument.dia b/src/lib/hooks/images/DataScopeArgument.dia Binary files differnew file mode 100644 index 0000000..02a4f17 --- /dev/null +++ b/src/lib/hooks/images/DataScopeArgument.dia diff --git a/src/lib/hooks/images/DataScopeArgument.png b/src/lib/hooks/images/DataScopeArgument.png Binary files differnew file mode 100644 index 0000000..34a5bd1 --- /dev/null +++ b/src/lib/hooks/images/DataScopeArgument.png diff --git a/src/lib/hooks/images/DataScopeContext.dia b/src/lib/hooks/images/DataScopeContext.dia Binary files differnew file mode 100644 index 0000000..1e39f5b --- /dev/null +++ b/src/lib/hooks/images/DataScopeContext.dia diff --git a/src/lib/hooks/images/DataScopeContext.png b/src/lib/hooks/images/DataScopeContext.png Binary files differnew file mode 100644 index 0000000..ba18875 --- /dev/null +++ b/src/lib/hooks/images/DataScopeContext.png diff --git a/src/lib/hooks/images/HooksUml.dia b/src/lib/hooks/images/HooksUml.dia Binary files differnew file mode 100644 index 0000000..0972fca --- /dev/null +++ b/src/lib/hooks/images/HooksUml.dia diff --git a/src/lib/hooks/images/HooksUml.png b/src/lib/hooks/images/HooksUml.png Binary files differnew file mode 100644 index 0000000..3859e6a --- /dev/null +++ b/src/lib/hooks/images/HooksUml.png diff --git a/src/lib/hooks/libinfo.cc b/src/lib/hooks/libinfo.cc new file mode 100644 index 0000000..2abf3a1 --- /dev/null +++ b/src/lib/hooks/libinfo.cc @@ -0,0 +1,30 @@ +// Copyright (C) 2016 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <hooks/libinfo.h> + +namespace isc { +namespace hooks { + +/// @brief Extracts names from HookLibsCollection +/// +/// @param libraries Hook libraries collection +/// @return vector of strings with library names +std::vector<std::string> +extractNames(const isc::hooks::HookLibsCollection& libraries) { + std::vector<std::string> names; + + for (isc::hooks::HookLibsCollection::const_iterator it = libraries.begin(); + it != libraries.end(); ++it) { + names.push_back(it->first); + } + return (names); +} + +}; +}; diff --git a/src/lib/hooks/libinfo.h b/src/lib/hooks/libinfo.h new file mode 100644 index 0000000..42b0441 --- /dev/null +++ b/src/lib/hooks/libinfo.h @@ -0,0 +1,42 @@ +// Copyright (C) 2016 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef HOOKS_LIBINFO_H +#define HOOKS_LIBINFO_H + +#include <cc/data.h> + +#include <boost/shared_ptr.hpp> + +#include <string> +#include <vector> +#include <utility> + +namespace isc { +namespace hooks { + +/// @brief Entity that holds information about hook libraries and their +/// parameters. +/// +/// The first parameter is a full filename with path to the library. +/// The second parameter is a map of parameters that configure the +/// library. There's always at least one parameter: "library", which +/// contains the library name. +typedef std::pair<std::string, data::ConstElementPtr> HookLibInfo; + +/// @brief A storage for information about hook libraries. +typedef std::vector<HookLibInfo> HookLibsCollection; + +/// @brief Shared pointer to collection of hooks libraries. +typedef boost::shared_ptr<HookLibsCollection> HookLibsCollectionPtr; + +/// @brief Extracts library names from full library information structure +std::vector<std::string> extractNames(const HookLibsCollection& libinfo); + +}; +}; + +#endif diff --git a/src/lib/hooks/library_handle.cc b/src/lib/hooks/library_handle.cc new file mode 100644 index 0000000..b070448 --- /dev/null +++ b/src/lib/hooks/library_handle.cc @@ -0,0 +1,131 @@ +// Copyright (C) 2013-2020 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <hooks/callout_manager.h> +#include <hooks/library_handle.h> +#include <hooks/hooks_manager.h> + +#include <iostream> + +namespace isc { +namespace hooks { + +// Callout manipulation - all deferred to the CalloutManager. + +void +LibraryHandle::registerCallout(const std::string& name, CalloutPtr callout) { + int index = index_; + + if (index_ == -1) { + // -1 means that current index is stored in CalloutManager. + // So let's get the index from there. See comment for + // LibraryHandle::index_. + index = callout_manager_.getLibraryIndex(); + } + + // Register the callout. + callout_manager_.registerCallout(name, callout, index); +} + +void +LibraryHandle::registerCommandCallout(const std::string& command_name, + CalloutPtr callout) { + // Register hook point for this command, if one doesn't exist. + callout_manager_.registerCommandHook(command_name); + // Register the command handler as a callout. + registerCallout(ServerHooks::commandToHookName(command_name), callout); +} + + +bool +LibraryHandle::deregisterCallout(const std::string& name, CalloutPtr callout) { + int index = index_; + + if (index_ == -1) { + // -1 means that current index is stored in CalloutManager. + // So let's get the index from there. See comment for + // LibraryHandle::index_. + index = callout_manager_.getLibraryIndex(); + } + + return (callout_manager_.deregisterCallout(name, callout, index)); +} + +bool +LibraryHandle::deregisterAllCallouts(const std::string& name) { + int index = index_; + + if (index_ == -1) { + // -1 means that current index is stored in CalloutManager. + // So let's get the index from there. See comment for + // LibraryHandle::index_. + index = callout_manager_.getLibraryIndex(); + } + + return (callout_manager_.deregisterAllCallouts(name, index)); +} + +isc::data::ConstElementPtr +LibraryHandle::getParameters() { + HookLibsCollection libinfo = HooksManager::getLibraryInfo(); + + int index = index_; + + if (index == -1) { + // -1 means that current index is stored in CalloutManager. + // So let's get the index from there. See comment for + // LibraryHandle::index_. + index = callout_manager_.getLibraryIndex(); + } + + if ((index > libinfo.size()) || (index <= 0)) { + // Something is very wrong here. The library index is out of bounds. + // However, this is user facing interface, so we should not throw here. + return (isc::data::ConstElementPtr()); + } + + // Some indexes have special meaning: + // * 0 - pre-user library callout + // * 1 -> numlib - indexes for actual libraries + // * INT_MAX - post-user library callout + + return (libinfo[index - 1].second); +} + +isc::data::ConstElementPtr +LibraryHandle::getParameter(const std::string& name) { + // Try to find appropriate parameter. May return null pointer + isc::data::ConstElementPtr params = getParameters(); + if (!params || (params->getType() != isc::data::Element::map)) { + return (isc::data::ConstElementPtr()); + } + + // May return null pointer if there's no parameter. + return (params->get(name)); +} + +std::vector<std::string> +LibraryHandle::getParameterNames() { + std::vector<std::string> names; + // Find all parameter names. + isc::data::ConstElementPtr params = getParameters(); + if (!params || + (params->getType() != isc::data::Element::map) || + (params->size() == 0)) { + return (names); + } + auto map = params->mapValue(); + for (auto elem = map.begin(); elem != map.end(); ++elem) { + names.push_back(elem->first); + } + return (names); +} + + +} // namespace util +} // namespace isc diff --git a/src/lib/hooks/library_handle.h b/src/lib/hooks/library_handle.h new file mode 100644 index 0000000..14af40c --- /dev/null +++ b/src/lib/hooks/library_handle.h @@ -0,0 +1,242 @@ +// Copyright (C) 2013-2020 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef LIBRARY_HANDLE_H +#define LIBRARY_HANDLE_H + +#include <string> +#include <cc/data.h> + +namespace isc { +namespace hooks { + +// Forward declarations +class CalloutHandle; +class CalloutManager; + +/// Typedef for a callout pointer. (Callouts must have "C" linkage.) +extern "C" { + typedef int (*CalloutPtr)(CalloutHandle&); +}; + +/// @brief Library handle +/// +/// This class is accessed by the user library when registering callouts, +/// either by the library's load() function, or by one of the callouts +/// themselves. +/// +/// It is really little more than a shell around the CalloutManager. By +/// presenting this object to the user-library callouts, callouts can manage +/// the callout list for their own library, but cannot affect the callouts +/// registered by other libraries. +/// +/// (This restriction is achieved by the CalloutManager maintaining the concept +/// of the "current library". When a callout is registered - either by the +/// library's load() function, or by a callout in the library - the registration +/// information includes the library active at the time. When that callout is +/// called, the CalloutManager uses that information to set the "current +/// library": the registration functions only operator on data whose +/// associated library is equal to the "current library".) +/// +/// As of Kea 1.3.0 release, the @ref LibraryHandle can be used by the hook +/// libraries to install control command handlers and dynamically register +/// hook points with which the handlers are associated. For example, if the +/// hook library supports control-command 'foo-bar' it should register its +/// handler similarly to this: +/// @code +/// int load(LibraryHandle& libhandle) { +/// libhandle.registerCommandCallout("foo-bar", foo_bar_handler); +/// return (0); +/// } +/// @endcode +/// +/// which will result in automatic creation of the hook point for the command +/// (if one doesn't exist) and associating the callout 'foo_bar_handler' with +/// this hook point as a handler for the command. + +class LibraryHandle { +public: + + /// @brief Constructor + /// + /// @param manager Back reference to the containing CalloutManager. + /// This reference is used to access appropriate methods in that + /// object. Note that the reference is safe - the only instance + /// of the LibraryHandle in the system is as a member of the + /// CalloutManager to which it points. + /// + /// @param index Index of the library to which the LibraryHandle applies. + /// If negative, the library index as set in the CalloutManager is + /// used. Note: although -1 is a valid argument value for + /// @ref isc::hooks::CalloutManager::setLibraryIndex(), in this class + /// it is used as a sentinel to indicate that the library index in + /// @ref isc::hooks::CalloutManager should not be set or reset. + LibraryHandle(CalloutManager& manager, int index = -1) + : callout_manager_(manager), index_(index) {} + + /// @brief Register a callout on a hook + /// + /// Registers a callout function with a given hook. The callout is added + /// to the end of the callouts for the current library that are associated + /// with that hook. + /// + /// @param name Name of the hook to which the callout is added. + /// @param callout Pointer to the callout function to be registered. + /// + /// @throw NoSuchHook The hook name is unrecognized. + /// @throw Unexpected The hook name is valid but an internal data structure + /// is of the wrong size. + void registerCallout(const std::string& name, CalloutPtr callout); + + /// @brief Register control command handler + /// + /// Registers control command handler by creating a hook point for this + /// command (if it doesn't exist) and associating the callout as a command + /// handler. It is possible to register multiple command handlers for the + /// same control command because command handlers are implemented as callouts. + /// + /// @param command_name Command name for which handler should be installed. + /// @param callout Pointer to the command handler implemented as a callout. + void registerCommandCallout(const std::string& command_name, CalloutPtr callout); + + /// @brief De-Register a callout on a hook + /// + /// Searches through the functions registered by the current library with + /// the named hook and removes all entries matching the callout. It does + /// not affect callouts registered by other libraries. + /// + /// @param name Name of the hook from which the callout is removed. + /// @param callout Pointer to the callout function to be removed. + /// + /// @return true if a one or more callouts were deregistered. + /// + /// @throw NoSuchHook The hook name is unrecognized. + /// @throw Unexpected The hook name is valid but an internal data structure + /// is of the wrong size. + bool deregisterCallout(const std::string& name, CalloutPtr callout); + + /// @brief Removes all callouts on a hook + /// + /// Removes all callouts associated with a given hook that were registered. + /// by the current library. It does not affect callouts that were + /// registered by other libraries. + /// + /// @param name Name of the hook from which the callouts are removed. + /// + /// @return true if one or more callouts were deregistered. + /// + /// @throw NoSuchHook Thrown if the hook name is unrecognized. + bool deregisterAllCallouts(const std::string& name); + + + /// @brief Returns configuration parameter for the library. + /// + /// This method returns configuration parameters specified in the + /// configuration file. Here's the example. Let's assume that there + /// are two hook libraries configured: + /// + /// "hooks-libraries": [ + /// { + /// "library": "/opt/charging.so", + /// "parameters": {} + /// }, + /// { + /// "library": "/opt/local/notification.so", + /// "parameters": { + /// "mail": "alarm@example.com", + /// "floor": 42, + /// "debug": false, + /// "users": [ "alice", "bob", "charlie" ], + /// "header": { + /// "french": "bonjour", + /// "klingon": "yl'el" + /// } + /// } + /// } + ///] + /// + /// The first library has no parameters, so regardless of the name + /// specified, for that library getParameter will always return NULL. + /// + /// For the second parameter, depending the following calls will return: + /// - x = getParameter("mail") will return instance of + /// isc::data::StringElement. The content can be accessed with + /// x->stringValue() and will return std::string. + /// - x = getParameter("floor") will return an instance of isc::data::IntElement. + /// The content can be accessed with x->intValue() and will return int. + /// - x = getParameter("debug") will return an instance of isc::data::BoolElement. + /// Its value can be accessed with x->boolValue() and will return bool. + /// - x = getParameter("users") will return an instance of ListElement. + /// Its content can be accessed with the following methods: + /// x->size(), x->get(index) + /// - x = getParameter("header") will return an instance of isc::data::MapElement. + /// Its content can be accessed with the following methods: + /// x->find("klingon"), x->contains("french"), x->size() + /// + /// For more examples and complete API, see documentation for + /// @ref isc::data::Element class and its derivatives: + /// - @ref isc::data::IntElement + /// - @ref isc::data::DoubleElement + /// - @ref isc::data::BoolElement + /// - @ref isc::data::StringElement + /// - @ref isc::data::ListElement + /// - @ref isc::data::MapElement + /// + /// Another good way to learn how to use Element interface is to look at the + /// unittests in data_unittests.cc. + /// + /// @param name text name of the parameter. + /// @return ElementPtr representing requested parameter (may be null, if + /// there is no such parameter.) + isc::data::ConstElementPtr + getParameter(const std::string& name); + + /// @brief Get configuration parameter common code. + /// + /// @return configuration parameters. + isc::data::ConstElementPtr getParameters(); + + /// @brief Returns names of configuration parameters for the library. + /// + /// This method returns a vector of strings reflecting names of + /// configuration parameters specified in the configuration file. + /// + /// @note: kept for backward compatibility. + /// @return a vector with parameter entry names. + std::vector<std::string> getParameterNames(); + +private: + /// @brief Copy constructor + /// + /// Private (with no implementation) as it makes no sense to copy an object + /// of this type. All code receives a reference to an existing handle which + /// is tied to a particular CalloutManager. Creating a copy of that handle + /// runs the risk of a "dangling pointer" to the original handle's callout + /// manager. + /// + /// @param Unused - should be the object to copy. + LibraryHandle(const LibraryHandle&); + + /// @brief Assignment operator + /// + /// Declared private like the copy constructor for the same reasons. It too + /// has no implementation. + /// + /// @param Unused - should be the object to copy. + LibraryHandle& operator=(const LibraryHandle&); + + /// Back pointer to the collection object for the library + CalloutManager& callout_manager_; + + /// Library index to which this handle applies. -1 indicates that it + /// applies to whatever index is current in the CalloutManager. + int index_; +}; + +} // namespace util +} // namespace isc + +#endif // LIBRARY_HANDLE_H diff --git a/src/lib/hooks/library_manager.cc b/src/lib/hooks/library_manager.cc new file mode 100644 index 0000000..447c9eb --- /dev/null +++ b/src/lib/hooks/library_manager.cc @@ -0,0 +1,435 @@ +// Copyright (C) 2013-2020 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <exceptions/exceptions.h> +#include <hooks/hooks.h> +#include <hooks/hooks_log.h> +#include <hooks/callout_manager.h> +#include <hooks/library_handle.h> +#include <hooks/library_manager.h> +#include <hooks/pointer_converter.h> +#include <hooks/server_hooks.h> +#include <log/logger_manager.h> +#include <log/logger_support.h> +#include <log/message_initializer.h> +#include <util/multi_threading_mgr.h> + +#include <string> +#include <vector> + +#include <dlfcn.h> + +using namespace std; + +namespace isc { +namespace hooks { + +// Constructor (used by external agency) +LibraryManager::LibraryManager(const std::string& name, int index, + const boost::shared_ptr<CalloutManager>& manager) + : dl_handle_(NULL), index_(index), manager_(manager), + library_name_(name), + server_hooks_(ServerHooks::getServerHooksPtr()) +{ + if (!manager) { + isc_throw(NoCalloutManager, "must specify a CalloutManager when " + "instantiating a LibraryManager object"); + } +} + +// Constructor (used by "validate" for library validation). Note that this +// sets "manager_" to not point to anything, which means that methods such as +// registerStandardCallout() will fail, probably with a segmentation fault. +// There are no checks for this condition in those methods: this constructor +// is declared "private", so can only be executed by a method in this class. +// The only method to do so is "validateLibrary", which takes care not to call +// methods requiring a non-NULL manager. +LibraryManager::LibraryManager(const std::string& name) + : dl_handle_(NULL), index_(-1), manager_(), library_name_(name) +{} + +// Destructor. +LibraryManager::~LibraryManager() { + if (index_ >= 0) { + // LibraryManager instantiated to load a library, so ensure that + // it is unloaded before exiting. + static_cast<void>(prepareUnloadLibrary()); + } + + // LibraryManager instantiated to validate a library, so just ensure + // that it is closed before exiting. + static_cast<void>(closeLibrary()); +} + +// Open the library + +bool +LibraryManager::openLibrary() { + + // Open the library. We'll resolve names now, so that if there are any + // issues we don't bugcheck in the middle of apparently unrelated code. + dl_handle_ = dlopen(library_name_.c_str(), RTLD_NOW | RTLD_LOCAL); + if (dl_handle_ == NULL) { + LOG_ERROR(hooks_logger, HOOKS_OPEN_ERROR).arg(library_name_) + .arg(dlerror()); + } + + return (dl_handle_ != NULL); +} + +// Close the library if not already open + +bool +LibraryManager::closeLibrary() { + + // Close the library if it is open. (If not, this is a no-op.) + int status = 0; + if (dl_handle_ != NULL) { + status = dlclose(dl_handle_); + dl_handle_ = NULL; + if (status != 0) { + LOG_ERROR(hooks_logger, HOOKS_CLOSE_ERROR).arg(library_name_) + .arg(dlerror()); + } else { + LOG_INFO(hooks_logger, HOOKS_LIBRARY_CLOSED).arg(library_name_); + } + } + + return (status == 0); +} + +// Check the version of the library + +bool +LibraryManager::checkVersion() const { + + // Get the pointer to the "version" function. + PointerConverter pc(dlsym(dl_handle_, VERSION_FUNCTION_NAME)); + if (pc.versionPtr() != NULL) { + int version = KEA_HOOKS_VERSION - 1; // This is an invalid value + try { + version = (*pc.versionPtr())(); + } catch (...) { + LOG_ERROR(hooks_logger, HOOKS_VERSION_EXCEPTION).arg(library_name_); + return (false); + } + + if (version == KEA_HOOKS_VERSION) { + // All OK, version checks out + LOG_DEBUG(hooks_logger, HOOKS_DBG_CALLS, HOOKS_LIBRARY_VERSION) + .arg(library_name_).arg(version); + return (true); + + } else { + LOG_ERROR(hooks_logger, HOOKS_INCORRECT_VERSION).arg(library_name_) + .arg(version).arg(KEA_HOOKS_VERSION); + } + } else { + LOG_ERROR(hooks_logger, HOOKS_NO_VERSION).arg(library_name_); + } + + return (false); +} + +// Check the multi-threading compatibility of the library + +bool +LibraryManager::checkMultiThreadingCompatible() const { + + // Compatible with single-threaded. + if (!util::MultiThreadingMgr::instance().getMode()) { + return (true); + } + + // Get the pointer to the "multi_threading_compatible" function. + PointerConverter pc(dlsym(dl_handle_, MULTI_THREADING_COMPATIBLE_FUNCTION_NAME)); + int compatible = 0; + if (pc.multiThreadingCompatiblePtr()) { + try { + compatible = (*pc.multiThreadingCompatiblePtr())(); + } catch (...) { + LOG_ERROR(hooks_logger, HOOKS_MULTI_THREADING_COMPATIBLE_EXCEPTION) + .arg(library_name_); + return (false); + } + + LOG_DEBUG(hooks_logger, HOOKS_DBG_CALLS, + HOOKS_LIBRARY_MULTI_THREADING_COMPATIBLE) + .arg(library_name_) + .arg(compatible); + } + if (compatible == 0) { + LOG_ERROR(hooks_logger, HOOKS_LIBRARY_MULTI_THREADING_NOT_COMPATIBLE) + .arg(library_name_); + } + return (compatible != 0); +} + +// Register the standard callouts + +void +LibraryManager::registerStandardCallouts() { + // Set the library index for doing the registration. This is picked up + // when the library handle is created. + manager_->setLibraryIndex(index_); + + // Iterate through the list of known hooks + vector<string> hook_names = ServerHooks::getServerHooks().getHookNames(); + for (size_t i = 0; i < hook_names.size(); ++i) { + + // Look up the symbol + void* dlsym_ptr = dlsym(dl_handle_, hook_names[i].c_str()); + PointerConverter pc(dlsym_ptr); + if (pc.calloutPtr() != NULL) { + // Found a symbol, so register it. + manager_->getLibraryHandle().registerCallout(hook_names[i], + pc.calloutPtr()); + LOG_DEBUG(hooks_logger, HOOKS_DBG_CALLS, + HOOKS_STD_CALLOUT_REGISTERED).arg(library_name_) + .arg(hook_names[i]).arg(dlsym_ptr); + + } + } +} + +// Run the "load" function if present. + +bool +LibraryManager::runLoad() { + + // Get the pointer to the "load" function. + PointerConverter pc(dlsym(dl_handle_, LOAD_FUNCTION_NAME)); + if (pc.loadPtr() != NULL) { + + // Call the load() function with the library handle. We need to set + // the CalloutManager's index appropriately. We'll invalidate it + // afterwards. + + int status = -1; + try { + manager_->setLibraryIndex(index_); + status = (*pc.loadPtr())(manager_->getLibraryHandle()); + } catch (const isc::Exception& ex) { + LOG_ERROR(hooks_logger, HOOKS_LOAD_FRAMEWORK_EXCEPTION) + .arg(library_name_).arg(ex.what()); + return (false); + } catch (...) { + LOG_ERROR(hooks_logger, HOOKS_LOAD_EXCEPTION).arg(library_name_); + return (false); + } + + if (status != 0) { + LOG_ERROR(hooks_logger, HOOKS_LOAD_ERROR).arg(library_name_) + .arg(status); + return (false); + } else { + LOG_DEBUG(hooks_logger, HOOKS_DBG_TRACE, HOOKS_LOAD_SUCCESS) + .arg(library_name_); + } + + } else { + LOG_DEBUG(hooks_logger, HOOKS_DBG_TRACE, HOOKS_NO_LOAD) + .arg(library_name_); + } + + return (true); +} + + +// Run the "unload" function if present. + +bool +LibraryManager::prepareUnloadLibrary() { + + // Nothing to do. + if (dl_handle_ == NULL) { + return (true); + } + + // Call once. + if (index_ < 0) { + return (true); + } + + // Get the pointer to the "load" function. + bool result = false; + PointerConverter pc(dlsym(dl_handle_, UNLOAD_FUNCTION_NAME)); + if (pc.unloadPtr() != NULL) { + + // Call the load() function with the library handle. We need to set + // the CalloutManager's index appropriately. We'll invalidate it + // afterwards. + int status = -1; + try { + status = (*pc.unloadPtr())(); + result = true; + } catch (const isc::Exception& ex) { + LOG_ERROR(hooks_logger, HOOKS_UNLOAD_FRAMEWORK_EXCEPTION) + .arg(library_name_).arg(ex.what()); + } catch (...) { + // Exception generated. Note a warning as the unload will occur + // anyway. + LOG_WARN(hooks_logger, HOOKS_UNLOAD_EXCEPTION).arg(library_name_); + } + + if (result) { + if (status != 0) { + LOG_ERROR(hooks_logger, HOOKS_UNLOAD_ERROR).arg(library_name_) + .arg(status); + result = false; + } else { + LOG_DEBUG(hooks_logger, HOOKS_DBG_TRACE, HOOKS_UNLOAD_SUCCESS) + .arg(library_name_); + } + } + } else { + LOG_DEBUG(hooks_logger, HOOKS_DBG_TRACE, HOOKS_NO_UNLOAD) + .arg(library_name_); + result = true; + } + + // Regardless of status, remove all callouts associated with this + // library on all hooks. + vector<string> hooks = ServerHooks::getServerHooks().getHookNames(); + manager_->setLibraryIndex(index_); + for (size_t i = 0; i < hooks.size(); ++i) { + bool removed = manager_->deregisterAllCallouts(hooks[i], index_); + if (removed) { + LOG_DEBUG(hooks_logger, HOOKS_DBG_CALLS, HOOKS_CALLOUTS_REMOVED) + .arg(hooks[i]).arg(library_name_); + } + } + + // Mark as unload() ran. + index_ = -1; + + return (result); +} + +// The main library loading function. + +bool +LibraryManager::loadLibrary() { + LOG_DEBUG(hooks_logger, HOOKS_DBG_TRACE, HOOKS_LIBRARY_LOADING) + .arg(library_name_); + + // In the following, if a method such as openLibrary() fails, it will + // have issued an error message so there is no need to issue another one + // here. + + // Open the library (which is a check that it exists and is accessible). + if (openLibrary()) { + + // The hook libraries provide their own log messages and logger + // instances. This step is required to register log messages for + // the library being loaded in the global dictionary. Ideally, this + // should be called after all libraries have been loaded but we're + // going to call the version() and load() functions here and these + // functions may already contain logging statements. + isc::log::MessageInitializer::loadDictionary(); + + // The log messages registered by the new hook library may duplicate + // some of the existing messages. Log warning for each duplicated + // message now. + isc::log::LoggerManager::logDuplicatedMessages(); + + // Library opened OK, see if a version function is present and if so, + // check what value it returns. Check multi-threading compatibility. + if (checkVersion() && checkMultiThreadingCompatible()) { + // Version OK, so now register the standard callouts and call the + // library's load() function if present. + registerStandardCallouts(); + if (runLoad()) { + + // Success - the library has been successfully loaded. + LOG_INFO(hooks_logger, HOOKS_LIBRARY_LOADED).arg(library_name_); + return (true); + + } else { + + // The load function failed, so back out. We can't just close + // the library as (a) we need to call the library's "unload" + // function (if present) in case "load" allocated resources that + // need to be freed and (b) we need to remove any callouts that + // have been installed. + static_cast<void>(prepareUnloadLibrary()); + } + } + + // Either the version check or call to load() failed, so close the + // library and free up resources. Ignore the status return here - we + // already know there's an error and will have output a message. + static_cast<void>(closeLibrary()); + } + + return (false); +} + +// The library unloading function. Call the unload() function (if present), +// remove callouts from the callout manager, then close the library. This is +// only run if the library is still loaded and is a no-op if the library is +// not open. + +bool +LibraryManager::unloadLibrary() { + bool result = true; + if (dl_handle_ != NULL) { + LOG_DEBUG(hooks_logger, HOOKS_DBG_TRACE, HOOKS_LIBRARY_UNLOADING) + .arg(library_name_); + + // Call the unload() function if present. Note that this is done first + // - operations take place in the reverse order to which they were done + // when the library was loaded. + if (index_ >= 0) { + result = prepareUnloadLibrary(); + } + + // ... and close the library. + result = closeLibrary() && result; + if (result) { + + // Issue the informational message only if the library was unloaded + // with no problems. If there was an issue, an error message would + // have been issued. + LOG_INFO(hooks_logger, HOOKS_LIBRARY_UNLOADED).arg(library_name_); + } + } + return (result); +} + +// Validate the library. We must be able to open it, and the version function +// must both exist and return the right number. Note that this is a static +// method. + +bool +LibraryManager::validateLibrary(const std::string& name) { + // Instantiate a library manager for the validation. We use the private + // constructor as we don't supply a CalloutManager. + LibraryManager manager(name); + + // Try to open it and, if we succeed, check the version. + bool validated = manager.openLibrary() && manager.checkVersion() && + manager.checkMultiThreadingCompatible(); + + // Regardless of whether the version checked out, close the library. (This + // is a no-op if the library failed to open.) + static_cast<void>(manager.closeLibrary()); + + return (validated); +} + +// @note Moved from its own hooks.cc file to avoid undefined reference +// with static link. +void hooksStaticLinkInit() { + if (!isc::log::isLoggingInitialized()) { + isc::log::initLogger(std::string("userlib")); + } +} + +} // namespace hooks +} // namespace isc diff --git a/src/lib/hooks/library_manager.h b/src/lib/hooks/library_manager.h new file mode 100644 index 0000000..f3a376f --- /dev/null +++ b/src/lib/hooks/library_manager.h @@ -0,0 +1,244 @@ +// Copyright (C) 2013-2020 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef LIBRARY_MANAGER_H +#define LIBRARY_MANAGER_H + +#include <exceptions/exceptions.h> +#include <hooks/server_hooks.h> +#include <boost/shared_ptr.hpp> + +#include <string> + +namespace isc { +namespace hooks { + +/// @brief No Callout Manager +/// +/// Thrown if a library manager is instantiated by an external agency without +/// specifying a CalloutManager object. +class NoCalloutManager : public Exception { +public: + NoCalloutManager(const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) {} +}; + +class CalloutManager; +class LibraryHandle; +class LibraryManager; + +/// @brief Library manager +/// +/// This class handles the loading and unloading of a specific library. It also +/// provides a static method for checking that a library is valid (this is used +/// in configuration parsing). +/// +/// On loading, it opens the library using dlopen and checks the version (set +/// with the "version" method. If all is OK, it iterates through the list of +/// known hooks and locates their symbols, registering each callout as it does +/// so. Finally it locates the "load" function (if present) and calls it. +/// +/// On unload, it clears the callouts on all hooks, calls the "unload" +/// method if present, clears the callouts on all hooks, and +/// closes the library. +/// +/// @note Caution needs to be exercised when using the close method. During +/// normal use, data will pass between the server and the library. In +/// this process, the library may allocate memory and pass it back to the +/// server. This could happen by the server setting arguments or context +/// in the CalloutHandle object, or by the library modifying the content +/// of pointed-to data. If the library is closed, this memory may lie +/// in the virtual address space deleted in that process. (The word "may" +/// is used, as this could be operating-system specific.) Should this +/// happen, any reference to the memory will cause a segmentation fault. +/// This can occur in a quite obscure place, for example in the middle of +/// a destructor of an STL class when it is deleting memory allocated +/// when the data structure was extended by a function in the library. +/// +/// @note The only safe way to run the "close" function is to ensure that all +/// possible references to it are removed first. This means that all +/// CalloutHandles must be destroyed, as must any data items that were +/// passed to the callouts. In practice, it could mean that a server +/// suspends processing of new requests until all existing ones have +/// been serviced and all packet/context structures destroyed before +/// reloading the libraries. +/// +/// When validating a library, only the fact that the library can be opened and +/// version() exists and returns the correct number is checked. The library +/// is closed after the validation. + +class LibraryManager { +public: + /// @brief Constructor + /// + /// This constructor is used by external agencies (i.e. the + /// LibraryManagerCollection) when instantiating a LibraryManager. It + /// stores the library name - the actual actual loading is done in + /// loadLibrary(). + /// + /// @param name Name of the library to load. This should be an absolute + /// path name. + /// @param index Index of this library + /// @param manager CalloutManager object + /// + /// @throw NoCalloutManager Thrown if the manager argument is NULL. + LibraryManager(const std::string& name, int index, + const boost::shared_ptr<CalloutManager>& manager); + + /// @brief Destructor + /// + /// If the library is open, closes it. This is principally a safety + /// feature to ensure closure in the case of an exception destroying this + /// object. However, see the caveat in the class header about when it is + /// safe to close libraries. + ~LibraryManager(); + + /// @brief Validate library + /// + /// A static method that is used to validate a library. Validation checks + /// that the library can be opened, that "version" exists, and that it + /// returns the right number, and the multi-threading compatibility. + /// + /// @param name Name of the library to validate + /// + /// @return true if the library validated, false if not. If the library + /// fails to validate, the reason for the failure is logged. + static bool validateLibrary(const std::string& name); + + /// @brief Loads a library + /// + /// Open the library, check the version and the multi-threading + /// compatibility. If all is OK, load all standard symbols then + /// call "load" if present. + /// + /// It also calls the @c isc::log::MessageInitializer::loadDictionary, + /// prior to invoking the @c version function of the library, to + /// update the global logging dictionary with the log messages + /// registered by the loaded library. + /// + /// @return true if the library loaded successfully, false otherwise. + /// In the latter case, the library will be unloaded if possible. + bool loadLibrary(); + + /// @brief Prepares library unloading + /// + /// Searches for the "unload" framework function and, if present, runs it. + /// Regardless of status, remove all callouts associated with this + /// library on all hooks. + /// + /// @return bool true if not found or found and run successfully, + /// false on an error. In this case, an error message will + /// have been output. + bool prepareUnloadLibrary(); + + /// @brief Return library name + /// + /// @return Name of this library + std::string getName() const { + return (library_name_); + } + +protected: + // The following methods are protected as they are accessed in testing. + + /// @brief Unloads a library + /// + /// Calls the libraries "unload" function if present, the closes the + /// library. + /// + /// However, see the caveat in the class header about when it is safe to + /// unload libraries. + /// + /// @return true if the library unloaded successfully, false if an error + /// occurred in the process (most likely the unload() function + /// (if present) returned an error). Even if an error did occur, + /// the library is closed if possible. + bool unloadLibrary(); + + /// @brief Open library + /// + /// Opens the library associated with this LibraryManager. A message is + /// logged on an error. + /// + /// @return true if the library opened successfully, false otherwise. + bool openLibrary(); + + /// @brief Close library + /// + /// Closes the library associated with this LibraryManager. A message is + /// logged on an error. + /// + /// @return true if the library closed successfully, false otherwise. "true" + /// is also returned if the library were already closed when this + /// method was called. + bool closeLibrary(); + + /// @brief Check library version + /// + /// With the library open, accesses the "version()" function and, if + /// present, checks the returned value against the hooks version symbol + /// for the currently running Kea. The "version()" function is + /// mandatory and must be present (and return the correct value) for the + /// library to load. + /// + /// If there is no version() function, or if there is a mismatch in + /// version number, a message logged. + /// + /// @return bool true if the check succeeded + bool checkVersion() const; + + /// @brief Check multi-threading compatibility + /// + /// If the multi-threading mode is false returns true, else with + /// the library open, accesses the "multi_threading_compatible()" + /// function and returns false if not exists or has value 0, returns + /// true otherwise. + /// + /// @return bool true if the check succeeded + bool checkMultiThreadingCompatible() const; + + /// @brief Register standard callouts + /// + /// Loops through the list of hook names and searches the library for + /// functions with those names. Any that are found are registered as + /// callouts for that hook. + void registerStandardCallouts(); + + /// @brief Run the load function if present + /// + /// Searches for the "load" framework function and, if present, runs it. + /// + /// @return bool true if not found or found and run successfully, + /// false on an error. In this case, an error message will + /// have been output. + bool runLoad(); + +private: + /// @brief Validating constructor + /// + /// Constructor used when the LibraryManager is instantiated to validate + /// a library (i.e. by the "validateLibrary" static method). + /// + /// @param name Name of the library to load. This should be an absolute + /// path name. + LibraryManager(const std::string& name); + + // Member variables + + void* dl_handle_; ///< Handle returned by dlopen + int index_; ///< Index associated with this library + boost::shared_ptr<CalloutManager> manager_; + ///< Callout manager for registration + std::string library_name_; ///< Name of the library + + ServerHooksPtr server_hooks_; ///< Stores a pointer to ServerHooks. + +}; + +} // namespace hooks +} // namespace isc + +#endif // LIBRARY_MANAGER_H diff --git a/src/lib/hooks/library_manager_collection.cc b/src/lib/hooks/library_manager_collection.cc new file mode 100644 index 0000000..6c32b76 --- /dev/null +++ b/src/lib/hooks/library_manager_collection.cc @@ -0,0 +1,153 @@ +// Copyright (C) 2013-2020 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <hooks/callout_manager.h> +#include <hooks/hooks_manager.h> +#include <hooks/library_manager.h> +#include <hooks/library_manager_collection.h> + +namespace isc { +namespace hooks { + +// Return callout manager for the loaded libraries. This call is only valid +// after one has been created for the loaded libraries (which includes the +// case of no loaded libraries). +// +// Note that there is no real connection between the callout manager and the +// libraries, other than it knows the number of libraries so can do sanity +// checks on values passed to it. However, this may change in the future, +// so the hooks framework is written such that a callout manager is used only +// with the LibraryManagerCollection that created it. It is also the reason +// why each LibraryManager contains a pointer to this CalloutManager. + +boost::shared_ptr<CalloutManager> +LibraryManagerCollection::getCalloutManager() const { + + // Only return a pointer if we have a CalloutManager created. + if (!callout_manager_) { + isc_throw(LoadLibrariesNotCalled, "must load hooks libraries before " + "attempting to retrieve a CalloutManager for them"); + } + + return (callout_manager_); +} + +LibraryManagerCollection::LibraryManagerCollection(const HookLibsCollection& libraries) + : library_info_(libraries) { + + // We need to split hook libs into library names and library parameters. + for (HookLibsCollection::const_iterator it = libraries.begin(); + it != libraries.end(); ++it) { + library_names_.push_back(it->first); + } +} + +// Load a set of libraries + +bool +LibraryManagerCollection::loadLibraries() { + + // There must be no libraries still in memory. + if (!lib_managers_.empty()) { + isc_throw(LibrariesStillOpened, "some libraries are still opened"); + } + + // Access the callout manager, (re)creating it if required. + // + // A pointer to the callout manager is maintained by each as well as by + // the HooksManager itself. Note that the callout manager does not hold any + // memory allocated by a library: although a library registers a callout + // (and so causes the creation of an entry in the CalloutManager's callout + // list), that creation is done by the CalloutManager itself. The + // CalloutManager is created within the server. The upshot of this is that + // it is therefore safe for the CalloutManager to be deleted after all + // associated libraries are deleted, hence this link (LibraryManager -> + // CalloutManager) is safe. + // + // The call of this function will result in re-creating the callout manager. + // This deletes all callouts (including the pre-library and post- + // library) ones. It is up to the libraries to re-register their callouts. + // The pre-library and post-library callouts will also need to be + // re-registered. + callout_manager_.reset(new CalloutManager(library_names_.size())); + + // Now iterate through the libraries are load them one by one. We'll + for (size_t i = 0; i < library_names_.size(); ++i) { + // Create a pointer to the new library manager. The index of this + // library is determined by the number of library managers currently + // loaded: note that the library indexes run from 1 to (number of loaded + // libraries). + boost::shared_ptr<LibraryManager> manager( + new LibraryManager(library_names_[i], lib_managers_.size() + 1, + callout_manager_)); + + // Load the library. On success, add it to the list of loaded + // libraries. On failure, unload all currently loaded libraries, + // leaving the object in the state it was in before loadLibraries was + // called. + if (manager->loadLibrary()) { + lib_managers_.push_back(manager); + } else { + static_cast<void>(unloadLibraries()); + return (false); + } + } + + return (true); +} + +// Unload the libraries. + +void +LibraryManagerCollection::unloadLibraries() { + + // Delete the library managers in the reverse order to which they were + // created, then clear the library manager vector. + while (!lib_managers_.empty()) { + lib_managers_.pop_back(); + } + + // Get rid of the callout manager. (The other member, the list of library + // names, was cleared when the libraries were loaded.) + callout_manager_.reset(); +} + +// Prepare the unloading of libraries. +bool +LibraryManagerCollection::prepareUnloadLibraries() { + bool result = true; + // Iterate on library managers in reverse order. + for (auto lm = lib_managers_.rbegin(); lm != lib_managers_.rend(); ++lm) { + result = (*lm)->prepareUnloadLibrary() && result; + } + return (result); +} + +// Return number of loaded libraries. +int +LibraryManagerCollection::getLoadedLibraryCount() const { + return (lib_managers_.size()); +} + +// Validate the libraries. +std::vector<std::string> +LibraryManagerCollection::validateLibraries( + const std::vector<std::string>& libraries) { + + std::vector<std::string> failures; + for (size_t i = 0; i < libraries.size(); ++i) { + if (!LibraryManager::validateLibrary(libraries[i])) { + failures.push_back(libraries[i]); + } + } + + return (failures); +} + +} // namespace hooks +} // namespace isc diff --git a/src/lib/hooks/library_manager_collection.h b/src/lib/hooks/library_manager_collection.h new file mode 100644 index 0000000..53a9669 --- /dev/null +++ b/src/lib/hooks/library_manager_collection.h @@ -0,0 +1,187 @@ +// Copyright (C) 2013-2020 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef LIBRARY_MANAGER_COLLECTION_H +#define LIBRARY_MANAGER_COLLECTION_H + +#include <exceptions/exceptions.h> + +#include <boost/shared_ptr.hpp> +#include <hooks/libinfo.h> + +#include <vector> + +namespace isc { +namespace hooks { + +/// @brief LoadLibraries not called +/// +/// Thrown if an attempt is made get a CalloutManager before the libraries +/// have been loaded. +class LoadLibrariesNotCalled : public Exception { +public: + LoadLibrariesNotCalled(const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) {} +}; + + +// Forward declarations +class CalloutManager; +class LibraryManager; + +/// @brief Library manager collection +/// +/// The LibraryManagerCollection class, as the name implies, is responsible for +/// managing the collection of LibraryManager objects that describe the loaded +/// libraries. As such, it converts a single operation (e.g load libraries) +/// into multiple operations, one per library. However, the class does more +/// than that - it provides a single object with which to manage lifetimes. +/// +/// As described in the LibraryManager documentation, a CalloutHandle may end +/// up with pointers to memory within the address space of a loaded library. +/// If the library is closed before this address space is deleted, the +/// deletion of the CalloutHandle may attempt to free memory into the newly- +/// unmapped address space and cause a segmentation fault. +/// +/// To prevent this, each CalloutHandle maintains a shared pointer to the +/// LibraryManagerCollection current when it was created. In addition, the +/// containing HooksManager object also maintains a shared pointer to it. +/// A LibraryManagerCollection is never explicitly deleted: when a new set +/// of libraries is loaded, the HooksManager clears its pointer to the +/// collection. The LibraryManagerCollection is only destroyed when all +/// CallHandle objects referencing it are destroyed. +/// +/// Note that this does not completely solve the problem - a hook function may +/// have modified a packet being processed by the server and that packet may +/// hold a pointer to memory in the library's virtual address space. To avoid +/// a segmentation fault, that packet needs to free the memory before the +/// LibraryManagerCollection is destroyed and this places demands on the server +/// code. However, the link with the CalloutHandle does at least mean that +/// authors of server code do not need to be so careful about when they destroy +/// CalloutHandles. +/// +/// The collection object also provides a utility function to validate a set +/// of libraries. The function checks that each library exists, can be opened, +/// that the "version" function exists and return the right number. + +class LibraryManagerCollection { +public: + /// @brief Constructor + /// + /// @param libraries List of libraries that this collection will manage. + /// The order of the libraries is important. It holds the library + /// names and its configuration parameters. + LibraryManagerCollection(const HookLibsCollection& libraries); + + /// @brief Destructor + /// + /// Unloads all loaded libraries. + ~LibraryManagerCollection() { + static_cast<void>(unloadLibraries()); + } + + /// @brief Load libraries + /// + /// Loads the libraries. This creates the LibraryManager associated with + /// each library and calls its loadLibrary() method. If a library fails + /// to load, the loading is abandoned and all libraries loaded so far + /// are unloaded. + /// + /// @return true if all libraries loaded, false if one or more failed t + //// load. + bool loadLibraries(); + + /// @brief Get callout manager + /// + /// Returns a callout manager that can be used with this set of loaded + /// libraries (even if the number of loaded libraries is zero). This + /// method may only be called after loadLibraries() has been called. + /// + /// @return Pointer to a callout manager for this set of libraries. + /// + /// @throw LoadLibrariesNotCalled Thrown if this method is called between + /// construction and the time loadLibraries() is called. + boost::shared_ptr<CalloutManager> getCalloutManager() const; + + /// @brief Get library names + /// + /// Returns the list of library names. If called before loadLibraries(), + /// the list is the list of names to be loaded; if called afterwards, it + /// is the list of libraries that have been loaded. + std::vector<std::string> getLibraryNames() const { + return (library_names_); + } + + /// @brief Returns library info + /// + /// Returns a collection of libraries, each entry consisting of a library + /// name + all its parameters. + HookLibsCollection getLibraryInfo() const { + return (library_info_); + } + + /// @brief Get number of loaded libraries + /// + /// Mainly for testing, this returns the number of libraries that are + /// loaded. + /// + /// @return Number of libraries that are loaded. + int getLoadedLibraryCount() const; + + /// @brief Validate libraries + /// + /// Utility function to validate libraries. It checks that the libraries + /// exist, can be opened, that a "version" function is present in them, and + /// that it returns the right number. All errors are logged. + /// + /// @param libraries List of libraries to validate + /// + /// @return Vector of libraries that failed to validate, or an empty vector + /// if all validated. + static std::vector<std::string> + validateLibraries(const std::vector<std::string>& libraries); + + /// @brief Prepare libaries unloading + /// + /// Utility function to call before closing libraries. It runs the + /// unload() function when it exists and removes associated callout. + /// When this function returns either there is only one owner + /// (the hook manager) or some visible dangling pointers so + /// libraries are not closed to lower the probability of a crash. + /// See @ref LibraryManager::prepareUnloadLibrary. + /// + /// @return true if all libraries unload were not found or run + /// successfully, false on an error. + bool prepareUnloadLibraries(); + +protected: + /// @brief Unload libraries + /// + /// Unloads and closes all loaded libraries. They are unloaded in the + /// reverse order to the order in which they were loaded. + void unloadLibraries(); + +private: + + /// Vector of library names + std::vector<std::string> library_names_; + + /// Vector of library managers + std::vector<boost::shared_ptr<LibraryManager> > lib_managers_; + + /// Vector of library information. Each piece of information + /// consists of a pair of (library name, library parameters) + HookLibsCollection library_info_; + + /// Callout manager to be associated with the libraries + boost::shared_ptr<CalloutManager> callout_manager_; +}; + +} // namespace hooks +} // namespace isc + + +#endif // LIBRARY_MANAGER_COLLECTION_H diff --git a/src/lib/hooks/parking_lots.h b/src/lib/hooks/parking_lots.h new file mode 100644 index 0000000..e4e82bd --- /dev/null +++ b/src/lib/hooks/parking_lots.h @@ -0,0 +1,426 @@ +// Copyright (C) 2018-2021 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef PARKING_LOTS_H +#define PARKING_LOTS_H + +#include <exceptions/exceptions.h> +#include <boost/any.hpp> +#include <boost/make_shared.hpp> +#include <boost/shared_ptr.hpp> + +#include <functional> +#include <iostream> +#include <sstream> +#include <list> +#include <unordered_map> +#include <mutex> +#include <thread> + +namespace isc { +namespace hooks { + +/// @brief Parking lot for objects, e.g. packets, for a hook point. +/// +/// Callouts may instruct the servers to "park" processed packets, i.e. suspend +/// their processing until explicitly unparked. This is useful in cases when +/// callouts need to perform asynchronous operations related to the packet +/// processing and the packet must not be further processed until the +/// asynchronous operations are completed. While the packet is parked, the +/// new packets can be processed, so the server remains responsive to the +/// new requests. +/// +/// Parking lots are created per hook point, so the callouts installed on the +/// particular hook point only have access to the parking lots dedicated to +/// them. +/// +/// The parking lot object supports 5 actions: "park", "reference", +/// "dereference", "unpark", and "drop". +/// +/// In the typical case, the server parks the object and the callouts reference +/// and unpark the objects. Therefore, the @ref ParkingLot object is not passed +/// directly to the callouts. Instead, a ParkingLotHandle object is provided +/// to the callout, which only provides access to "reference", "dereference", +/// and "unpark" operations. +/// +/// Parking an object is performed, proactively by the server, before callouts +/// are invoked. Referencing (and dereferencing) an object is performed by the +/// callouts before the @c CalloutHandle::NEXT_STEP_PARK is returned to the +/// server. +/// +/// Trying to reference (or deference) and unparked object will result +/// in error. Referencing (reference counting) is an important part of the +/// parking mechanism, which allows multiple callouts, installed on the same +/// hook point, to perform asynchronous operations and guarantees that the +/// object remains parked until all those asynchronous operations complete. +/// Each such callout must call @c unpark() when it desires the object to +/// be unparked, but the object will only be unparked when all callouts call +/// this function, i.e. when all callouts signal completion of their respective +/// asynchronous operations. +/// +/// Dereferencing, decrements the reference count without invoking the unpark +/// callback. This allows hook callouts to proactively reference the object +/// in a callout and then cancel the reference should further processing +/// deem it the reference unnecessary. +/// +/// The types of the parked objects provided as T parameter of respective +/// functions are most often shared pointers. One should not use references +/// to parked objects nor references to shared pointers to avoid premature +/// destruction of the parked objects. +class ParkingLot { +public: + /// @brief Parks an object. + /// + /// @tparam Type of the parked object. + /// @param parked_object object to be parked, e.g. pointer to a packet. + /// @param unpark_callback callback function to be invoked when the object + /// is unparked. + /// @throw InvalidOperation if this object has already been parked. + template<typename T> + void park(T parked_object, std::function<void()> unpark_callback) { + std::lock_guard<std::mutex> lock(mutex_); + auto it = find(parked_object); + if (it != parking_.end()) { + isc_throw(InvalidOperation, "object is already parked!"); + } + + // Add the object to the parking lot. At this point refcount = 0. + ParkingInfo pinfo(parked_object, unpark_callback); + parking_[makeKey(parked_object)] = pinfo; + } + + /// @brief Increases reference counter for the parked object. + /// + /// This method is called by the callouts to increase a reference count + /// on the object to be parked. It may only be called after the object + /// has been parked + /// + /// @tparam Type of the parked object. + /// @param parked_object object which will be parked. + /// @return the integer number of references for this object. + template<typename T> + int reference(T parked_object) { + std::lock_guard<std::mutex> lock(mutex_); + auto it = find(parked_object); + if (it == parking_.end()) { + isc_throw(InvalidOperation, "cannot reference an object" + " that has not been parked."); + } + + // Bump and return the reference count + return (++it->second.refcount_); + } + + /// @brief Decreases the reference counter for the parked object. + /// + /// This method is called by the callouts to decrease the reference count + /// on a parked object. + /// + /// @tparam Type of the parked object. + /// @param parked_object parked object whose count should be reduced. + /// @return the integer number of references for this object. + template<typename T> + int dereference(T parked_object) { + std::lock_guard<std::mutex> lock(mutex_); + auto it = find(parked_object); + if (it == parking_.end()) { + isc_throw(InvalidOperation, "cannot dereference an object" + " that has not been parked."); + } + + // Decrement and return the reference count. + return (--it->second.refcount_); + } + + /// @brief Signals that the object should be unparked. + /// + /// If the specified object is parked in this parking lot, the reference + /// count is decreased as a result of this method. If the reference count + /// is 0, the object is unparked and the callback is invoked. Typically, the + /// callback points to a function which resumes processing of a packet. + /// + /// @tparam Type of the parked object. + /// @param parked_object parked object to be unparked. + /// @param force boolean value indicating if the reference counting should + /// be ignored and the object should be unparked immediately. + /// @return false if the object couldn't be unparked because there is + /// no such object, true otherwise. + template<typename T> + bool unpark(T parked_object, bool force = false) { + // Initialize as the empty function. + std::function<void()> cb; + { + std::lock_guard<std::mutex> lock(mutex_); + auto it = find(parked_object); + if (it == parking_.end()) { + // No such parked object. + return (false); + } + + if (force) { + it->second.refcount_ = 0; + } else { + --it->second.refcount_; + } + + if (it->second.refcount_ <= 0) { + // Unpark the packet and set the callback. + cb = it->second.unpark_callback_; + parking_.erase(it); + } + } + + // Invoke the callback if not empty. + if (cb) { + cb(); + } + + // Parked object found, so return true to indicate that the + // operation was successful. It doesn't necessarily mean + // that the object was unparked, but at least the reference + // count was decreased. + return (true); + } + + /// @brief Removes parked object without calling a callback. + /// + /// @tparam Type of the parked object. + /// @param parked_object parked object to be removed. + /// @return false if the object couldn't be removed because there is + /// no such object, true otherwise. + template<typename T> + bool drop(T parked_object) { + std::lock_guard<std::mutex> lock(mutex_); + auto it = find(parked_object); + if (it != parking_.end()) { + // Parked object found. + parking_.erase(it); + return (true); + } + + // No such object. + return (false); + } + + /// @brief Returns the current number of objects. + size_t size() { + std::lock_guard<std::mutex> lock(mutex_); + return (parking_.size()); + } + +public: + + /// @brief Holds information about parked object. + struct ParkingInfo { + /// @brief The parked object. + boost::any parked_object_; + + /// @brief The pointer to callback. + std::function<void()> unpark_callback_; + + /// @brief The current reference count. + int refcount_; + + /// @brief Constructor. + /// + /// Default constructor. + ParkingInfo() : refcount_(0) {} + + /// @brief Constructor. + /// + /// @param parked_object object being parked. + /// @param callback pointer to the callback. + ParkingInfo(const boost::any& parked_object, + std::function<void()> callback = 0) + : parked_object_(parked_object), unpark_callback_(callback), + refcount_(0) {} + + /// @brief Update parking information. + /// + /// @param parked_object parked object. + /// @param callback pointer to the callback. + void update(const boost::any& parked_object, + std::function<void()> callback) { + parked_object_ = parked_object; + unpark_callback_ = callback; + } + }; + +private: + + /// @brief Map which stores parked objects. + typedef std::unordered_map<std::string, ParkingInfo> ParkingInfoList; + + /// @brief Type of the iterator in the list of parked objects. + typedef ParkingInfoList::iterator ParkingInfoListIterator; + + /// @brief Container holding parked objects for this parking lot. + ParkingInfoList parking_; + + /// @brief Construct the key for a given parked object. + /// + /// @tparam T parked object type. + /// @param parked_object object from which the key should be constructed. + /// @return string containing the object's key. + template<typename T> + std::string makeKey(T parked_object) { + std::stringstream ss; + ss << boost::any_cast<T>(parked_object); + return (ss.str()); + } + + /// @brief Search for the information about the parked object. + /// + /// @tparam T parked object type. + /// @param parked_object object for which to search. + /// @return Iterator pointing to the parked object, or @c parking_.end() + /// if no such object found. + template<typename T> + ParkingInfoListIterator find(T parked_object) { + return (parking_.find(makeKey(parked_object))); + } + + /// @brief The mutex to protect parking lot internal state. + /// + /// All public methods must enter of lock guard with the mutex + /// before any access to the @c parking_ member. + std::mutex mutex_; +}; + +/// @brief Type of the pointer to the parking lot. +typedef boost::shared_ptr<ParkingLot> ParkingLotPtr; + +/// @brief Provides a limited view to the @c ParkingLot. +/// +/// The handle is provided to the callouts which can reference and unpark +/// parked objects. The callouts should not park objects, therefore this +/// operation is not available. +/// +/// The types of the parked objects provided as T parameter of respective +/// functions are most often shared pointers. One should not use references +/// to parked objects nor references to shared pointers to avoid premature +/// destruction of the parked objects. +class ParkingLotHandle { +public: + + /// @brief Constructor. + /// + /// @param parking_lot pointer to the parking lot for which the handle is + /// created. + ParkingLotHandle(const ParkingLotPtr& parking_lot) + : parking_lot_(parking_lot) { + } + + /// @brief Increases reference counter for the parked object. + /// + /// This method is called by the callouts to increase a reference count + /// on the object to be parked. It must be called before the object is + /// actually parked. + /// + /// @tparam Type of the parked object. + /// @param parked_object object which will be parked. + /// @return new reference count as an integer + template<typename T> + int reference(T parked_object) { + return (parking_lot_->reference(parked_object)); + } + + /// @brief Decreases the reference counter for the parked object. + /// + /// This method is called by the callouts to decrease the reference count + /// of a parked object. + /// + /// @tparam Type of the parked object. + /// @param parked_object object which will be parked. + /// @return new reference count as an integer + template<typename T> + int dereference(T parked_object) { + return (parking_lot_->dereference(parked_object)); + } + + /// @brief Signals that the object should be unparked. + /// + /// If the specified object is parked in this parking lot, the reference + /// count is decreased as a result of this method. If the reference count + /// is 0, the object is unparked and the callback is invoked. Typically, the + /// callback points to a function which resumes processing of a packet. + /// + /// @tparam Type of the parked object. + /// @param parked_object parked object to be unparked. + /// @return false if the object couldn't be unparked because there is + /// no such object, true otherwise. + template<typename T> + bool unpark(T parked_object) { + return (parking_lot_->unpark(parked_object)); + } + + /// @brief Removes parked object without calling a callback. + /// + /// It ignores any reference counts on the parked object. + /// + /// @tparam Type of the parked object. + /// @param parked_object parked object to be removed. + /// @return false if the object couldn't be removed because there is + /// no such object, true otherwise. + template<typename T> + bool drop(T parked_object) { + return (parking_lot_->drop(parked_object)); + } + +private: + + /// @brief Parking lot to which this handle points. + ParkingLotPtr parking_lot_; + +}; + +/// @brief Pointer to the parking lot handle. +typedef boost::shared_ptr<ParkingLotHandle> ParkingLotHandlePtr; + +/// @brief Collection of parking lots for various hook points. +class ParkingLots { +public: + + /// @brief Removes all parked objects. + /// + /// It doesn't invoke callbacks associated with the removed objects. + void clear() { + std::lock_guard<std::mutex> lock(mutex_); + parking_lots_.clear(); + } + + /// @brief Returns pointer to the parking lot for a hook points. + /// + /// If the parking lot for the specified hook point doesn't exist, it is + /// created. + /// + /// @param hook_index index of the hook point with which the parking + /// lot is associated. + /// @return Pointer to the parking lot. + ParkingLotPtr getParkingLotPtr(const int hook_index) { + std::lock_guard<std::mutex> lock(mutex_); + if (parking_lots_.count(hook_index) == 0) { + parking_lots_[hook_index] = boost::make_shared<ParkingLot>(); + } + return (parking_lots_[hook_index]); + } + +private: + + /// @brief Container holding parking lots for various hook points. + std::unordered_map<int, ParkingLotPtr> parking_lots_; + + /// @brief The mutex to protect parking lots internal state. + std::mutex mutex_; +}; + +/// @brief Type of the pointer to the parking lots. +typedef boost::shared_ptr<ParkingLots> ParkingLotsPtr; + +} // end of namespace hooks +} // end of namespace isc + +#endif diff --git a/src/lib/hooks/pointer_converter.h b/src/lib/hooks/pointer_converter.h new file mode 100644 index 0000000..5f51d8d --- /dev/null +++ b/src/lib/hooks/pointer_converter.h @@ -0,0 +1,122 @@ +// Copyright (C) 2013-2020 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef POINTER_CONVERTER_H +#define POINTER_CONVERTER_H + +#include <hooks/hooks.h> + +namespace isc { +namespace hooks { + +/// @brief Local class for conversion of void pointers to function pointers +/// +/// Converting between void* and function pointers in C++ is fraught with +/// difficulty and pitfalls, e.g. see +/// https://groups.google.com/forum/?hl=en&fromgroups#!topic/comp.lang.c++/37o0l8rtEE0 +/// +/// The method given in that article - convert using a union is used here. A +/// union is declared (and zeroed) and the appropriate member extracted when +/// needed. + +class PointerConverter { +public: + /// @brief Constructor + /// + /// Zeroes the union and stores the void* pointer we wish to convert (the + /// one returned by dlsym). + /// + /// @param dlsym_ptr void* pointer returned by call to dlsym() + PointerConverter(void* dlsym_ptr) { + memset(&pointers_, 0, sizeof(pointers_)); + pointers_.dlsym_ptr = dlsym_ptr; + } + + /// @brief Constructor + /// + /// Zeroes the union and stores the CalloutPtr pointer we wish to convert. + /// This constructor is used in debug messages; output of a pointer to + /// an object (including to a function) is, on some compilers, printed as + /// "1". + /// + /// @param callout_ptr Pointer to callout function + PointerConverter(CalloutPtr callout_ptr) { + memset(&pointers_, 0, sizeof(pointers_)); + pointers_.callout_ptr = callout_ptr; + } + + /// @name Pointer accessor functions + /// + /// It is up to the caller to ensure that the correct member is called so + /// that the correct type of pointer is returned. + /// + ///@{ + + /// @brief Return pointer returned by dlsym call + /// + /// @return void* pointer returned by the call to dlsym(). This can be + /// used in statements that print the hexadecimal value of the + /// symbol. + void* dlsymPtr() const { + return (pointers_.dlsym_ptr); + } + + /// @brief Return pointer to callout function + /// + /// @return Pointer to the callout function + CalloutPtr calloutPtr() const { + return (pointers_.callout_ptr); + } + + /// @brief Return pointer to load function + /// + /// @return Pointer to the load function + load_function_ptr loadPtr() const { + return (pointers_.load_ptr); + } + + /// @brief Return pointer to unload function + /// + /// @return Pointer to the unload function + unload_function_ptr unloadPtr() const { + return (pointers_.unload_ptr); + } + + /// @brief Return pointer to version function + /// + /// @return Pointer to the version function + version_function_ptr versionPtr() const { + return (pointers_.version_ptr); + } + + /// @brief Return pointer to multi_threading_compatible function + /// + /// @return Pointer to the multi_threading_compatible function + multi_threading_compatible_function_ptr multiThreadingCompatiblePtr() const { + return (pointers_.multi_threading_compatible_ptr); + } + + ///@} + +private: + + /// @brief Union linking void* and pointers to functions. + union { + void* dlsym_ptr; // void* returned by dlsym + CalloutPtr callout_ptr; // Pointer to callout + load_function_ptr load_ptr; // Pointer to load function + unload_function_ptr unload_ptr; // Pointer to unload function + version_function_ptr version_ptr; // Pointer to version function + multi_threading_compatible_function_ptr multi_threading_compatible_ptr; + // Pointer to multi_threading_compatible function + } pointers_; +}; + +} // namespace hooks +} // namespace isc + + +#endif // POINTER_CONVERTER_H diff --git a/src/lib/hooks/server_hooks.cc b/src/lib/hooks/server_hooks.cc new file mode 100644 index 0000000..5ca1a50 --- /dev/null +++ b/src/lib/hooks/server_hooks.cc @@ -0,0 +1,221 @@ +// Copyright (C) 2013-2018 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <exceptions/exceptions.h> +#include <hooks/hooks_log.h> +#include <hooks/server_hooks.h> + +#include <algorithm> +#include <utility> +#include <vector> + +using namespace std; +using namespace isc; + +namespace isc { +namespace hooks { + +// Constructor - register the pre-defined hooks and check that the indexes +// assigned to them are as expected. +// +// Note that there are no logging messages here or in registerHooks(). The +// recommended way to initialize hook names is to use static initialization. +// Here, a static object is declared in a file outside of any function or +// method. As a result, it is instantiated and its constructor run before the +// program starts. By putting calls to ServerHooks::registerHook() in there, +// hooks names are already registered when the program runs. However, at that +// point, the logging system is not initialized, so messages are unable to +// be output. + +ServerHooks::ServerHooks() { + initialize(); +} + +// Register a hook. The index assigned to the hook is the current number +// of entries in the collection, so ensuring that hook indexes are unique +// and non-negative. + +int +ServerHooks::registerHook(const string& name) { + + // Determine index for the new element and insert. + int index = hooks_.size(); + pair<HookCollection::iterator, bool> result = + hooks_.insert(make_pair(name, index)); + + /// @todo: We also need to call CalloutManager::ensureVectorSize(), so it + /// adjusts its vector. Since CalloutManager is not a singleton, there's + /// no getInstance() or similar. Also, CalloutManager uses ServerHooks, + /// so such a call would induce circular dependencies. Ugh. + + if (!result.second) { + + // There's a problem with hook libraries that need to be linked with + // libdhcpsrv. For example host_cmds hook library requires host + // parser, so it needs to be linked with libdhcpsrv. However, when + // unit-tests are started, the hook points are not registered. + // When the library is loaded new hook points are registered. + // This causes issues in the hooks framework, especially when + // LibraryManager::unloadLibrary() iterates through all hooks + // and then calls deregisterAllCallouts. This method gets + // hook_index that is greater than number of elements in + // hook_vector_ and then we have a read past the array boundary. + /// @todo: See ticket 5251 and 5208 for details. + return (getIndex(name)); + + // New element was not inserted because an element with the same name + // already existed. + //isc_throw(DuplicateHook, "hook with name " << name << + // " is already registered"); + } + + // Element was inserted, so add to the inverse hooks collection. + inverse_hooks_[index] = name; + + // ... and return numeric index. + return (index); +} + +// Set ServerHooks object to initial state. + +void +ServerHooks::initialize() { + + // Clear out the name->index and index->name maps. + hooks_.clear(); + inverse_hooks_.clear(); + parking_lots_.reset(new ParkingLots()); + + // Register the pre-defined hooks. + int create = registerHook("context_create"); + int destroy = registerHook("context_destroy"); + + // Check registration went as expected. + if ((create != CONTEXT_CREATE) || (destroy != CONTEXT_DESTROY)) { + isc_throw(Unexpected, "pre-defined hook indexes are not as expected. " + "context_create: expected = " << CONTEXT_CREATE << + ", actual = " << create << + ". context_destroy: expected = " << CONTEXT_DESTROY << + ", actual = " << destroy); + } +} + +// Reset ServerHooks object to initial state. + +void +ServerHooks::reset() { + + // Clear all hooks then initialize the pre-defined ones. + initialize(); + + // Log a warning - although this is done during testing, it should never be + // seen in a production system. + LOG_WARN(hooks_logger, HOOKS_HOOK_LIST_RESET); +} + +// Find the name associated with a hook index. + +std::string +ServerHooks::getName(int index) const { + + // Get iterator to matching element. + InverseHookCollection::const_iterator i = inverse_hooks_.find(index); + if (i == inverse_hooks_.end()) { + isc_throw(NoSuchHook, "hook index " << index << " is not recognized"); + } + + return (i->second); +} + +// Find the index associated with a hook name. + +int +ServerHooks::getIndex(const string& name) const { + + // Get iterator to matching element. + HookCollection::const_iterator i = hooks_.find(name); + if (i == hooks_.end()) { + isc_throw(NoSuchHook, "hook name " << name << " is not recognized"); + } + + return (i->second); +} + +int +ServerHooks::findIndex(const std::string& name) const { + // Get iterator to matching element. + auto i = hooks_.find(name); + return ((i == hooks_.end()) ? -1 : i->second); +} + +// Return vector of hook names. The names are not sorted - it is up to the +// caller to perform sorting if required. + +vector<string> +ServerHooks::getHookNames() const { + + vector<string> names; + HookCollection::const_iterator i; + for (i = hooks_.begin(); i != hooks_.end(); ++i) { + names.push_back(i->first); + } + + return (names); +} + +// Return global ServerHooks object + +ServerHooks& +ServerHooks::getServerHooks() { + return (*getServerHooksPtr()); +} + +ServerHooksPtr +ServerHooks::getServerHooksPtr() { + static ServerHooksPtr hooks(new ServerHooks()); + return (hooks); +} + +ParkingLotsPtr +ServerHooks::getParkingLotsPtr() const { + return (parking_lots_); +} + +ParkingLotPtr +ServerHooks::getParkingLotPtr(const int hook_index) { + return (parking_lots_->getParkingLotPtr(hook_index)); +} + +ParkingLotPtr +ServerHooks::getParkingLotPtr(const std::string& hook_name) { + return (parking_lots_->getParkingLotPtr(getServerHooks().getIndex(hook_name))); +} + +std::string +ServerHooks::commandToHookName(const std::string& command_name) { + // Prefix the command name with a dollar sign. + std::string hook_name = std::string("$") + command_name; + // Replace all hyphens with underscores. + std::replace(hook_name.begin(), hook_name.end(), '-', '_'); + return (hook_name); +} + +std::string +ServerHooks::hookToCommandName(const std::string& hook_name) { + if (!hook_name.empty() && hook_name.front() == '$') { + std::string command_name = hook_name.substr(1); + std::replace(command_name.begin(), command_name.end(), '_', '-'); + return (command_name); + } + return (""); +} + + + +} // namespace hooks +} // namespace isc diff --git a/src/lib/hooks/server_hooks.h b/src/lib/hooks/server_hooks.h new file mode 100644 index 0000000..2c9df97 --- /dev/null +++ b/src/lib/hooks/server_hooks.h @@ -0,0 +1,246 @@ +// Copyright (C) 2013-2018 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef SERVER_HOOKS_H +#define SERVER_HOOKS_H + +#include <exceptions/exceptions.h> +#include <hooks/parking_lots.h> + +#include <boost/noncopyable.hpp> +#include <boost/shared_ptr.hpp> + +#include <map> +#include <string> +#include <vector> + +namespace isc { +namespace hooks { + +/// @brief Duplicate hook +/// +/// Thrown if an attempt is made to register a hook with the same name as a +/// previously-registered hook. +class DuplicateHook : public Exception { +public: + DuplicateHook(const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) {} +}; + +/// @brief Invalid hook +/// +/// Thrown if an attempt is made to get the index for an invalid hook. +class NoSuchHook : public Exception { +public: + NoSuchHook(const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) {} +}; + +class ServerHooks; +typedef boost::shared_ptr<ServerHooks> ServerHooksPtr; + +/// @brief Server hook collection +/// +/// This class is used by the server-side code to register hooks - points in the +/// server processing at which libraries can register functions (callouts) that +/// the server will call. These functions can modify data and so affect the +/// processing of the server. +/// +/// The ServerHooks class is little more than a wrapper around the std::map +/// class. It stores a hook, assigning to it a unique index number. This +/// number is then used by the server code to identify the hook being called. +/// (Although it would be feasible to use a name as an index, using an integer +/// will speed up the time taken to locate the callouts, which may make a +/// difference in a frequently-executed piece of code.) +/// +/// ServerHooks is a singleton object and is only accessible by the static +/// method getServerHooks(). + +class ServerHooks : public boost::noncopyable { +public: + + /// Index numbers for pre-defined hooks. + static const int CONTEXT_CREATE = 0; + static const int CONTEXT_DESTROY = 1; + + /// @brief Reset to Initial State + /// + /// Resets the collection of hooks to the initial state, with just the + /// context_create and context_destroy hooks set. This used during + /// testing to reset the global ServerHooks object; it should never be + /// used in production. + /// + /// @throws isc::Unexpected if the registration of the pre-defined hooks + /// fails in some way. + void reset(); + + /// @brief Register a hook + /// + /// Registers a hook and returns the hook index. + /// + /// @param name Name of the hook + /// + /// @return Index of the hook, to be used in subsequent hook-related calls. + /// This will be greater than or equal to zero (so allowing a + /// negative value to indicate an invalid index). + /// + /// @throws DuplicateHook A hook with the same name has already been + /// registered. + int registerHook(const std::string& name); + + /// @brief Get hook name + /// + /// Returns the name of a hook given the index. This is most likely to be + /// used in log messages. + /// + /// @param index Index of the hook + /// + /// @return Name of the hook. + /// + /// @throw NoSuchHook if the hook index is invalid. + std::string getName(int index) const; + + /// @brief Get hook index + /// + /// Returns the index of a hook. + /// + /// @param name Name of the hook + /// + /// @return Index of the hook, to be used in subsequent calls. + /// + /// @throw NoSuchHook if the hook name is unknown to the caller. + int getIndex(const std::string& name) const; + + /// @brief Find hook index + /// + /// Provides exception safe method of retrieving an index of the + /// specified hook. + /// + /// @param name Name of the hook + /// + /// @return Index of the hook if the hook point exists, or -1 if the + /// hook point doesn't exist. + int findIndex(const std::string& name) const; + + /// @brief Return number of hooks + /// + /// Returns the total number of hooks registered. + /// + /// @return Number of hooks registered. + int getCount() const { + return (hooks_.size()); + } + + /// @brief Get hook names + /// + /// Return list of hooks registered in the object. + /// + /// @return Vector of strings holding hook names. + std::vector<std::string> getHookNames() const; + + /// @brief Return ServerHooks object + /// + /// Returns the global ServerHooks object. + /// + /// @return Reference to the global ServerHooks object. + static ServerHooks& getServerHooks(); + + /// @brief Returns pointer to ServerHooks object. + /// + /// @return Pointer to the global ServerHooks object. + static ServerHooksPtr getServerHooksPtr(); + + /// @brief Returns pointer to all parking lots. + /// + /// @return pointer to all parking lots. + ParkingLotsPtr getParkingLotsPtr() const; + + /// @brief Returns pointer to the ParkingLot for the specified hook index. + /// + /// @param hook_index index of the hook point for which the parking lot + /// should be returned. + /// @return Pointer to the ParkingLot object. + ParkingLotPtr getParkingLotPtr(const int hook_index); + + /// @brief Returns pointer to the ParkingLot for the specified hook name. + /// + /// @param hook_name name of the hook point for which the parking lot + /// should be returned. + /// @return Pointer to the ParkingLot object. + ParkingLotPtr getParkingLotPtr(const std::string& hook_name); + + /// @brief Generates hook point name for the given control command name. + /// + /// This function is called to generate the name of the hook point + /// when the hook point is used to install command handlers for the + /// given control command. + /// + /// The name of the hook point is generated as follows: + /// - command name is prefixed with a dollar sign, + /// - all hyphens are replaced with underscores. + /// + /// For example, if the command_name is 'foo-bar', the resulting hook + /// point name will be '$foo_bar'. + /// + /// @param command_name Command name for which the hook point name is + /// to be generated. + /// + /// @return Hook point name, or an empty string if the command name + /// can't be converted to a hook name (e.g. when it lacks dollar sign). + static std::string commandToHookName(const std::string& command_name); + + /// @brief Returns command name for a specified hook name. + /// + /// This function removes leading dollar sign and replaces underscores + /// with hyphens. + /// + /// @param hook_name Hook name for which command name should be returned. + /// + /// @return Command name. + static std::string hookToCommandName(const std::string& hook_name); + +private: + /// @brief Constructor + /// + /// This pre-registers two hooks, context_create and context_destroy, which + /// are called by the server before processing a packet and after processing + /// for the packet has completed. They allow the server code to allocate + /// and destroy per-packet context. + /// + /// The constructor is declared private to enforce the singleton nature of + /// the object. A reference to the singleton is obtainable through the + /// getServerHooks() static method. + /// + /// @throws isc::Unexpected if the registration of the pre-defined hooks + /// fails in some way. + ServerHooks(); + + /// @brief Initialize hooks + /// + /// Sets the collection of hooks to the initial state, with just the + /// context_create and context_destroy hooks set. This is used during + /// construction. + /// + /// @throws isc::Unexpected if the registration of the pre-defined hooks + /// fails in some way. + void initialize(); + + /// Useful typedefs. + typedef std::map<std::string, int> HookCollection; + typedef std::map<int, std::string> InverseHookCollection; + + /// Two maps, one for name->index, the other for index->name. (This is + /// simpler than using a multi-indexed container.) + HookCollection hooks_; ///< Hook name/index collection + InverseHookCollection inverse_hooks_; ///< Hook index/name collection + + ParkingLotsPtr parking_lots_; +}; + +} // namespace util +} // namespace isc + +#endif // SERVER_HOOKS_H diff --git a/src/lib/hooks/tests/Makefile.am b/src/lib/hooks/tests/Makefile.am new file mode 100644 index 0000000..b6fe8b9 --- /dev/null +++ b/src/lib/hooks/tests/Makefile.am @@ -0,0 +1,153 @@ +SUBDIRS = . + +AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib +AM_CPPFLAGS += $(BOOST_INCLUDES) + +AM_CXXFLAGS = $(KEA_CXXFLAGS) + +# Kea libraries against which the test user libraries are linked. +HOOKS_LIB = $(top_builddir)/src/lib/hooks/libkea-hooks.la +LOG_LIB = $(top_builddir)/src/lib/log/libkea-log.la +UTIL_LIB = $(top_builddir)/src/lib/util/libkea-util.la +EXCEPTIONS_LIB = $(top_builddir)/src/lib/exceptions/libkea-exceptions.la + +ALL_LIBS = $(HOOKS_LIB) $(LOG_LIB) $(UTIL_LIB) $(EXCEPTIONS_LIB) $(LOG4CPLUS_LIBS) + +# Files to clean include the file created by testing. +CLEANFILES = *.gcno *.gcda $(builddir)/marker_file.dat + +# Files generated by configure +DISTCLEANFILES = marker_file.h test_libraries.h + +TESTS_ENVIRONMENT = \ + $(LIBTOOL) --mode=execute $(VALGRIND_COMMAND) + +TESTS = +if HAVE_GTEST +# Build shared libraries for testing. The libtool way to create a shared library +# is to specify "-avoid-version -export-dynamic -module" in the library LDFLAGS +# (see http://www.gnu.org/software/libtool/manual/html_node/Link-mode.html). +# Use of these switches will guarantee that the .so files are created in the +# .libs folder and they can be dlopened. +# +# Note that the shared libraries with callouts should not be used together with +# the --enable-static-link option. With this option, the bind10 libraries are +# statically linked with the program and if the callout invokes the methods +# which belong to these libraries, the library with the callout will get its +# own copy of the static objects (e.g. logger, ServerHooks) and that will lead +# to unexpected errors. For this reason, the --enable-static-link option is +# ignored for unit tests built here. + +noinst_LTLIBRARIES = libnvl.la libivl.la libfxl.la libbcl.la liblcl.la \ + liblecl.la libucl.la libfcl.la libpcl.la libacl.la + +# -rpath /nowhere is a hack to trigger libtool to not create a +# convenience archive, resulting in shared modules + +# No version function +libnvl_la_SOURCES = no_version_library.cc +libnvl_la_CXXFLAGS = $(AM_CXXFLAGS) +libnvl_la_CPPFLAGS = $(AM_CPPFLAGS) +libnvl_la_LDFLAGS = -avoid-version -export-dynamic -module -rpath /nowhere + +# Incorrect version function +libivl_la_SOURCES = incorrect_version_library.cc +libivl_la_CXXFLAGS = $(AM_CXXFLAGS) +libivl_la_CPPFLAGS = $(AM_CPPFLAGS) +libivl_la_LDFLAGS = -avoid-version -export-dynamic -module -rpath /nowhere + +# All framework functions throw an exception +libfxl_la_SOURCES = framework_exception_library.cc +libfxl_la_CXXFLAGS = $(AM_CXXFLAGS) +libfxl_la_CPPFLAGS = $(AM_CPPFLAGS) +libfxl_la_LDFLAGS = -avoid-version -export-dynamic -module -rpath /nowhere + +# The basic callout library - contains standard callouts +libbcl_la_SOURCES = basic_callout_library.cc +libbcl_la_CXXFLAGS = $(AM_CXXFLAGS) +libbcl_la_CPPFLAGS = $(AM_CPPFLAGS) +libbcl_la_LDFLAGS = -avoid-version -export-dynamic -module -rpath /nowhere + +# The load callout library - contains a load function +liblcl_la_SOURCES = load_callout_library.cc +liblcl_la_CXXFLAGS = $(AM_CXXFLAGS) +liblcl_la_CPPFLAGS = $(AM_CPPFLAGS) +liblcl_la_LDFLAGS = -avoid-version -export-dynamic -module -rpath /nowhere + +# The load error callout library - contains a load function that returns +# an error. +liblecl_la_SOURCES = load_error_callout_library.cc +liblecl_la_CXXFLAGS = $(AM_CXXFLAGS) +liblecl_la_CPPFLAGS = $(AM_CPPFLAGS) +liblecl_la_LDFLAGS = -avoid-version -export-dynamic -module -rpath /nowhere + +# The unload callout library - contains an unload function that +# creates a marker file. +libucl_la_SOURCES = unload_callout_library.cc +libucl_la_CXXFLAGS = $(AM_CXXFLAGS) +libucl_la_CPPFLAGS = $(AM_CPPFLAGS) +libucl_la_LDFLAGS = -avoid-version -export-dynamic -module -rpath /nowhere + +# The full callout library - contains all three framework functions. +libfcl_la_SOURCES = full_callout_library.cc +libfcl_la_CXXFLAGS = $(AM_CXXFLAGS) +libfcl_la_CPPFLAGS = $(AM_CPPFLAGS) +libfcl_la_LDFLAGS = -avoid-version -export-dynamic -module -rpath /nowhere + +# The parameters checking callout library - expects +libpcl_la_SOURCES = callout_params_library.cc +libpcl_la_CXXFLAGS = $(AM_CXXFLAGS) +libpcl_la_CPPFLAGS = $(AM_CPPFLAGS) +libpcl_la_LDFLAGS = -avoid-version -export-dynamic -module -rpath /nowhere +libpcl_la_LDFLAGS += $(top_builddir)/src/lib/util/libkea-util.la + +# The async callout library - parks object for asynchronous task +libacl_la_SOURCES = async_callout_library.cc +libacl_la_CXXFLAGS = $(AM_CXXFLAGS) +libacl_la_CPPFLAGS = $(AM_CPPFLAGS) +libacl_la_LDFLAGS = -avoid-version -export-dynamic -module -rpath /nowhere + +TESTS += run_unittests +run_unittests_SOURCES = run_unittests.cc +run_unittests_SOURCES += callout_handle_unittest.cc +run_unittests_SOURCES += callout_handle_associate_unittest.cc +run_unittests_SOURCES += callout_manager_unittest.cc +run_unittests_SOURCES += common_test_class.h +run_unittests_SOURCES += handles_unittest.cc +run_unittests_SOURCES += hooks_manager_unittest.cc +run_unittests_SOURCES += library_manager_collection_unittest.cc +run_unittests_SOURCES += library_manager_unittest.cc +run_unittests_SOURCES += parking_lots_unittest.cc +run_unittests_SOURCES += server_hooks_unittest.cc + +nodist_run_unittests_SOURCES = marker_file.h +nodist_run_unittests_SOURCES += test_libraries.h + +run_unittests_CXXFLAGS = $(AM_CXXFLAGS) +run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES) +run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS) +if USE_STATIC_LINK +run_unittests_LDFLAGS += -static -export-dynamic +endif + +run_unittests_LDADD = $(AM_LDADD) +run_unittests_LDADD += $(ALL_LIBS) +run_unittests_LDADD += $(top_builddir)/src/lib/cc/libkea-cc.la +run_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la +run_unittests_LDADD += $(top_builddir)/src/lib/util/unittests/libutil_unittests.la +run_unittests_LDADD += $(GTEST_LDADD) $(LOG4CPLUS_LIBS) $(BOOST_LIBS) +# As noted in configure.ac, libtool doesn't work perfectly with Darwin: it +# embeds the final install path in dynamic libraries and loadable modules refer +# to that path even if its loaded within the source tree, so preventing tests +# from working - but only when linking statically. The solution used in other +# Makefiles (setting the path to the dynamic libraries via an environment +# variable) don't seem to work. What does work is to run the unit test using +# libtool and specifying paths via -dlopen switches. So... If running in an +# environment where we have to set the library path AND if linking statically, +# override the "check" target and run the unit tests ourselves. + +endif + +noinst_PROGRAMS = $(TESTS) + +EXTRA_DIST = marker_file.h.in test_libraries.h.in diff --git a/src/lib/hooks/tests/Makefile.in b/src/lib/hooks/tests/Makefile.in new file mode 100644 index 0000000..a8ba259 --- /dev/null +++ b/src/lib/hooks/tests/Makefile.in @@ -0,0 +1,1525 @@ +# Makefile.in generated by automake 1.16.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2018 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + + +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +TESTS = $(am__EXEEXT_1) +@HAVE_GTEST_TRUE@am__append_1 = run_unittests +@HAVE_GTEST_TRUE@@USE_STATIC_LINK_TRUE@am__append_2 = -static -export-dynamic +noinst_PROGRAMS = $(am__EXEEXT_2) +subdir = src/lib/hooks/tests +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \ + $(top_srcdir)/m4macros/ax_cpp11.m4 \ + $(top_srcdir)/m4macros/ax_crypto.m4 \ + $(top_srcdir)/m4macros/ax_find_library.m4 \ + $(top_srcdir)/m4macros/ax_gssapi.m4 \ + $(top_srcdir)/m4macros/ax_gtest.m4 \ + $(top_srcdir)/m4macros/ax_isc_rpath.m4 \ + $(top_srcdir)/m4macros/ax_sysrepo.m4 \ + $(top_srcdir)/m4macros/libtool.m4 \ + $(top_srcdir)/m4macros/ltoptions.m4 \ + $(top_srcdir)/m4macros/ltsugar.m4 \ + $(top_srcdir)/m4macros/ltversion.m4 \ + $(top_srcdir)/m4macros/lt~obsolete.m4 \ + $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = marker_file.h test_libraries.h +CONFIG_CLEAN_VPATH_FILES = +@HAVE_GTEST_TRUE@am__EXEEXT_1 = run_unittests$(EXEEXT) +am__EXEEXT_2 = $(am__EXEEXT_1) +PROGRAMS = $(noinst_PROGRAMS) +LTLIBRARIES = $(noinst_LTLIBRARIES) +libacl_la_LIBADD = +am__libacl_la_SOURCES_DIST = async_callout_library.cc +@HAVE_GTEST_TRUE@am_libacl_la_OBJECTS = \ +@HAVE_GTEST_TRUE@ libacl_la-async_callout_library.lo +libacl_la_OBJECTS = $(am_libacl_la_OBJECTS) +AM_V_lt = $(am__v_lt_@AM_V@) +am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) +am__v_lt_0 = --silent +am__v_lt_1 = +libacl_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(libacl_la_CXXFLAGS) \ + $(CXXFLAGS) $(libacl_la_LDFLAGS) $(LDFLAGS) -o $@ +@HAVE_GTEST_TRUE@am_libacl_la_rpath = +libbcl_la_LIBADD = +am__libbcl_la_SOURCES_DIST = basic_callout_library.cc +@HAVE_GTEST_TRUE@am_libbcl_la_OBJECTS = \ +@HAVE_GTEST_TRUE@ libbcl_la-basic_callout_library.lo +libbcl_la_OBJECTS = $(am_libbcl_la_OBJECTS) +libbcl_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(libbcl_la_CXXFLAGS) \ + $(CXXFLAGS) $(libbcl_la_LDFLAGS) $(LDFLAGS) -o $@ +@HAVE_GTEST_TRUE@am_libbcl_la_rpath = +libfcl_la_LIBADD = +am__libfcl_la_SOURCES_DIST = full_callout_library.cc +@HAVE_GTEST_TRUE@am_libfcl_la_OBJECTS = \ +@HAVE_GTEST_TRUE@ libfcl_la-full_callout_library.lo +libfcl_la_OBJECTS = $(am_libfcl_la_OBJECTS) +libfcl_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(libfcl_la_CXXFLAGS) \ + $(CXXFLAGS) $(libfcl_la_LDFLAGS) $(LDFLAGS) -o $@ +@HAVE_GTEST_TRUE@am_libfcl_la_rpath = +libfxl_la_LIBADD = +am__libfxl_la_SOURCES_DIST = framework_exception_library.cc +@HAVE_GTEST_TRUE@am_libfxl_la_OBJECTS = \ +@HAVE_GTEST_TRUE@ libfxl_la-framework_exception_library.lo +libfxl_la_OBJECTS = $(am_libfxl_la_OBJECTS) +libfxl_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(libfxl_la_CXXFLAGS) \ + $(CXXFLAGS) $(libfxl_la_LDFLAGS) $(LDFLAGS) -o $@ +@HAVE_GTEST_TRUE@am_libfxl_la_rpath = +libivl_la_LIBADD = +am__libivl_la_SOURCES_DIST = incorrect_version_library.cc +@HAVE_GTEST_TRUE@am_libivl_la_OBJECTS = \ +@HAVE_GTEST_TRUE@ libivl_la-incorrect_version_library.lo +libivl_la_OBJECTS = $(am_libivl_la_OBJECTS) +libivl_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(libivl_la_CXXFLAGS) \ + $(CXXFLAGS) $(libivl_la_LDFLAGS) $(LDFLAGS) -o $@ +@HAVE_GTEST_TRUE@am_libivl_la_rpath = +liblcl_la_LIBADD = +am__liblcl_la_SOURCES_DIST = load_callout_library.cc +@HAVE_GTEST_TRUE@am_liblcl_la_OBJECTS = \ +@HAVE_GTEST_TRUE@ liblcl_la-load_callout_library.lo +liblcl_la_OBJECTS = $(am_liblcl_la_OBJECTS) +liblcl_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(liblcl_la_CXXFLAGS) \ + $(CXXFLAGS) $(liblcl_la_LDFLAGS) $(LDFLAGS) -o $@ +@HAVE_GTEST_TRUE@am_liblcl_la_rpath = +liblecl_la_LIBADD = +am__liblecl_la_SOURCES_DIST = load_error_callout_library.cc +@HAVE_GTEST_TRUE@am_liblecl_la_OBJECTS = \ +@HAVE_GTEST_TRUE@ liblecl_la-load_error_callout_library.lo +liblecl_la_OBJECTS = $(am_liblecl_la_OBJECTS) +liblecl_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(liblecl_la_CXXFLAGS) \ + $(CXXFLAGS) $(liblecl_la_LDFLAGS) $(LDFLAGS) -o $@ +@HAVE_GTEST_TRUE@am_liblecl_la_rpath = +libnvl_la_LIBADD = +am__libnvl_la_SOURCES_DIST = no_version_library.cc +@HAVE_GTEST_TRUE@am_libnvl_la_OBJECTS = \ +@HAVE_GTEST_TRUE@ libnvl_la-no_version_library.lo +libnvl_la_OBJECTS = $(am_libnvl_la_OBJECTS) +libnvl_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(libnvl_la_CXXFLAGS) \ + $(CXXFLAGS) $(libnvl_la_LDFLAGS) $(LDFLAGS) -o $@ +@HAVE_GTEST_TRUE@am_libnvl_la_rpath = +libpcl_la_LIBADD = +am__libpcl_la_SOURCES_DIST = callout_params_library.cc +@HAVE_GTEST_TRUE@am_libpcl_la_OBJECTS = \ +@HAVE_GTEST_TRUE@ libpcl_la-callout_params_library.lo +libpcl_la_OBJECTS = $(am_libpcl_la_OBJECTS) +libpcl_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(libpcl_la_CXXFLAGS) \ + $(CXXFLAGS) $(libpcl_la_LDFLAGS) $(LDFLAGS) -o $@ +@HAVE_GTEST_TRUE@am_libpcl_la_rpath = +libucl_la_LIBADD = +am__libucl_la_SOURCES_DIST = unload_callout_library.cc +@HAVE_GTEST_TRUE@am_libucl_la_OBJECTS = \ +@HAVE_GTEST_TRUE@ libucl_la-unload_callout_library.lo +libucl_la_OBJECTS = $(am_libucl_la_OBJECTS) +libucl_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(libucl_la_CXXFLAGS) \ + $(CXXFLAGS) $(libucl_la_LDFLAGS) $(LDFLAGS) -o $@ +@HAVE_GTEST_TRUE@am_libucl_la_rpath = +am__run_unittests_SOURCES_DIST = run_unittests.cc \ + callout_handle_unittest.cc \ + callout_handle_associate_unittest.cc \ + callout_manager_unittest.cc common_test_class.h \ + handles_unittest.cc hooks_manager_unittest.cc \ + library_manager_collection_unittest.cc \ + library_manager_unittest.cc parking_lots_unittest.cc \ + server_hooks_unittest.cc +@HAVE_GTEST_TRUE@am_run_unittests_OBJECTS = \ +@HAVE_GTEST_TRUE@ run_unittests-run_unittests.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ run_unittests-callout_handle_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ run_unittests-callout_handle_associate_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ run_unittests-callout_manager_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ run_unittests-handles_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ run_unittests-hooks_manager_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ run_unittests-library_manager_collection_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ run_unittests-library_manager_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ run_unittests-parking_lots_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ run_unittests-server_hooks_unittest.$(OBJEXT) +nodist_run_unittests_OBJECTS = +run_unittests_OBJECTS = $(am_run_unittests_OBJECTS) \ + $(nodist_run_unittests_OBJECTS) +am__DEPENDENCIES_1 = +am__DEPENDENCIES_2 = $(HOOKS_LIB) $(LOG_LIB) $(UTIL_LIB) \ + $(EXCEPTIONS_LIB) $(am__DEPENDENCIES_1) +@HAVE_GTEST_TRUE@run_unittests_DEPENDENCIES = $(am__DEPENDENCIES_2) \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cc/libkea-cc.la \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/util/unittests/libutil_unittests.la \ +@HAVE_GTEST_TRUE@ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ +@HAVE_GTEST_TRUE@ $(am__DEPENDENCIES_1) +run_unittests_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \ + $(run_unittests_CXXFLAGS) $(CXXFLAGS) $(run_unittests_LDFLAGS) \ + $(LDFLAGS) -o $@ +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__maybe_remake_depfiles = depfiles +am__depfiles_remade = ./$(DEPDIR)/libacl_la-async_callout_library.Plo \ + ./$(DEPDIR)/libbcl_la-basic_callout_library.Plo \ + ./$(DEPDIR)/libfcl_la-full_callout_library.Plo \ + ./$(DEPDIR)/libfxl_la-framework_exception_library.Plo \ + ./$(DEPDIR)/libivl_la-incorrect_version_library.Plo \ + ./$(DEPDIR)/liblcl_la-load_callout_library.Plo \ + ./$(DEPDIR)/liblecl_la-load_error_callout_library.Plo \ + ./$(DEPDIR)/libnvl_la-no_version_library.Plo \ + ./$(DEPDIR)/libpcl_la-callout_params_library.Plo \ + ./$(DEPDIR)/libucl_la-unload_callout_library.Plo \ + ./$(DEPDIR)/run_unittests-callout_handle_associate_unittest.Po \ + ./$(DEPDIR)/run_unittests-callout_handle_unittest.Po \ + ./$(DEPDIR)/run_unittests-callout_manager_unittest.Po \ + ./$(DEPDIR)/run_unittests-handles_unittest.Po \ + ./$(DEPDIR)/run_unittests-hooks_manager_unittest.Po \ + ./$(DEPDIR)/run_unittests-library_manager_collection_unittest.Po \ + ./$(DEPDIR)/run_unittests-library_manager_unittest.Po \ + ./$(DEPDIR)/run_unittests-parking_lots_unittest.Po \ + ./$(DEPDIR)/run_unittests-run_unittests.Po \ + ./$(DEPDIR)/run_unittests-server_hooks_unittest.Po +am__mv = mv -f +CXXCOMPILE = $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \ + $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) +LTCXXCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CXXFLAGS) $(CXXFLAGS) +AM_V_CXX = $(am__v_CXX_@AM_V@) +am__v_CXX_ = $(am__v_CXX_@AM_DEFAULT_V@) +am__v_CXX_0 = @echo " CXX " $@; +am__v_CXX_1 = +CXXLD = $(CXX) +CXXLINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(AM_CXXFLAGS) \ + $(CXXFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CXXLD = $(am__v_CXXLD_@AM_V@) +am__v_CXXLD_ = $(am__v_CXXLD_@AM_DEFAULT_V@) +am__v_CXXLD_0 = @echo " CXXLD " $@; +am__v_CXXLD_1 = +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_@AM_V@) +am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) +am__v_CC_0 = @echo " CC " $@; +am__v_CC_1 = +CCLD = $(CC) +LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_@AM_V@) +am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) +am__v_CCLD_0 = @echo " CCLD " $@; +am__v_CCLD_1 = +SOURCES = $(libacl_la_SOURCES) $(libbcl_la_SOURCES) \ + $(libfcl_la_SOURCES) $(libfxl_la_SOURCES) $(libivl_la_SOURCES) \ + $(liblcl_la_SOURCES) $(liblecl_la_SOURCES) \ + $(libnvl_la_SOURCES) $(libpcl_la_SOURCES) $(libucl_la_SOURCES) \ + $(run_unittests_SOURCES) $(nodist_run_unittests_SOURCES) +DIST_SOURCES = $(am__libacl_la_SOURCES_DIST) \ + $(am__libbcl_la_SOURCES_DIST) $(am__libfcl_la_SOURCES_DIST) \ + $(am__libfxl_la_SOURCES_DIST) $(am__libivl_la_SOURCES_DIST) \ + $(am__liblcl_la_SOURCES_DIST) $(am__liblecl_la_SOURCES_DIST) \ + $(am__libnvl_la_SOURCES_DIST) $(am__libpcl_la_SOURCES_DIST) \ + $(am__libucl_la_SOURCES_DIST) \ + $(am__run_unittests_SOURCES_DIST) +RECURSIVE_TARGETS = all-recursive check-recursive cscopelist-recursive \ + ctags-recursive dvi-recursive html-recursive info-recursive \ + install-data-recursive install-dvi-recursive \ + install-exec-recursive install-html-recursive \ + install-info-recursive install-pdf-recursive \ + install-ps-recursive install-recursive installcheck-recursive \ + installdirs-recursive pdf-recursive ps-recursive \ + tags-recursive uninstall-recursive +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \ + distclean-recursive maintainer-clean-recursive +am__recursive_targets = \ + $(RECURSIVE_TARGETS) \ + $(RECURSIVE_CLEAN_TARGETS) \ + $(am__extra_recursive_targets) +AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \ + distdir distdir-am +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +am__tty_colors_dummy = \ + mgn= red= grn= lgn= blu= brg= std=; \ + am__color_tests=no +am__tty_colors = { \ + $(am__tty_colors_dummy); \ + if test "X$(AM_COLOR_TESTS)" = Xno; then \ + am__color_tests=no; \ + elif test "X$(AM_COLOR_TESTS)" = Xalways; then \ + am__color_tests=yes; \ + elif test "X$$TERM" != Xdumb && { test -t 1; } 2>/dev/null; then \ + am__color_tests=yes; \ + fi; \ + if test $$am__color_tests = yes; then \ + red='[0;31m'; \ + grn='[0;32m'; \ + lgn='[1;32m'; \ + blu='[1;34m'; \ + mgn='[0;35m'; \ + brg='[1m'; \ + std='[m'; \ + fi; \ +} +DIST_SUBDIRS = $(SUBDIRS) +am__DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/marker_file.h.in \ + $(srcdir)/test_libraries.h.in $(top_srcdir)/depcomp +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +am__relativize = \ + dir0=`pwd`; \ + sed_first='s,^\([^/]*\)/.*$$,\1,'; \ + sed_rest='s,^[^/]*/*,,'; \ + sed_last='s,^.*/\([^/]*\)$$,\1,'; \ + sed_butlast='s,/*[^/]*$$,,'; \ + while test -n "$$dir1"; do \ + first=`echo "$$dir1" | sed -e "$$sed_first"`; \ + if test "$$first" != "."; then \ + if test "$$first" = ".."; then \ + dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \ + dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \ + else \ + first2=`echo "$$dir2" | sed -e "$$sed_first"`; \ + if test "$$first2" = "$$first"; then \ + dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \ + else \ + dir2="../$$dir2"; \ + fi; \ + dir0="$$dir0"/"$$first"; \ + fi; \ + fi; \ + dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \ + done; \ + reldir="$$dir2" +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AR = @AR@ +ASCIIDOC = @ASCIIDOC@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BOOST_INCLUDES = @BOOST_INCLUDES@ +BOOST_LIBS = @BOOST_LIBS@ +BOTAN_TOOL = @BOTAN_TOOL@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +CONTRIB_DIR = @CONTRIB_DIR@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CRYPTO_CFLAGS = @CRYPTO_CFLAGS@ +CRYPTO_INCLUDES = @CRYPTO_INCLUDES@ +CRYPTO_LDFLAGS = @CRYPTO_LDFLAGS@ +CRYPTO_LIBS = @CRYPTO_LIBS@ +CRYPTO_PACKAGE = @CRYPTO_PACKAGE@ +CRYPTO_RPATH = @CRYPTO_RPATH@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DISTCHECK_BOOST_CONFIGURE_FLAG = @DISTCHECK_BOOST_CONFIGURE_FLAG@ +DISTCHECK_CONTRIB_CONFIGURE_FLAG = @DISTCHECK_CONTRIB_CONFIGURE_FLAG@ +DISTCHECK_CRYPTO_CONFIGURE_FLAG = @DISTCHECK_CRYPTO_CONFIGURE_FLAG@ +DISTCHECK_GTEST_CONFIGURE_FLAG = @DISTCHECK_GTEST_CONFIGURE_FLAG@ +DISTCHECK_KEA_SHELL_CONFIGURE_FLAG = @DISTCHECK_KEA_SHELL_CONFIGURE_FLAG@ +DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG = @DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG@ +DISTCHECK_PERFDHCP_CONFIGURE_FLAG = @DISTCHECK_PERFDHCP_CONFIGURE_FLAG@ +DISTCHECK_PREMIUM_CONFIGURE_FLAG = @DISTCHECK_PREMIUM_CONFIGURE_FLAG@ +DLLTOOL = @DLLTOOL@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +GENHTML = @GENHTML@ +GREP = @GREP@ +GSSAPI_CFLAGS = @GSSAPI_CFLAGS@ +GSSAPI_LIBS = @GSSAPI_LIBS@ +GTEST_CONFIG = @GTEST_CONFIG@ +GTEST_INCLUDES = @GTEST_INCLUDES@ +GTEST_LDADD = @GTEST_LDADD@ +GTEST_LDFLAGS = @GTEST_LDFLAGS@ +GTEST_SOURCE = @GTEST_SOURCE@ +HAVE_SYSREPO = @HAVE_SYSREPO@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +KEA_CXXFLAGS = @KEA_CXXFLAGS@ +KEA_SRCID = @KEA_SRCID@ +KRB5_CONFIG = @KRB5_CONFIG@ +LCOV = @LCOV@ +LD = @LD@ +LDFLAGS = @LDFLAGS@ +LEX = @LEX@ +LEXLIB = @LEXLIB@ +LEX_OUTPUT_ROOT = @LEX_OUTPUT_ROOT@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBTOOL = @LIBTOOL@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LOG4CPLUS_INCLUDES = @LOG4CPLUS_INCLUDES@ +LOG4CPLUS_LIBS = @LOG4CPLUS_LIBS@ +LTLIBOBJS = @LTLIBOBJS@ +LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +MYSQL_CPPFLAGS = @MYSQL_CPPFLAGS@ +MYSQL_LIBS = @MYSQL_LIBS@ +NM = @NM@ +NMEDIT = @NMEDIT@ +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PACKAGE_VERSION_TYPE = @PACKAGE_VERSION_TYPE@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PDFLATEX = @PDFLATEX@ +PERL = @PERL@ +PGSQL_CPPFLAGS = @PGSQL_CPPFLAGS@ +PGSQL_LIBS = @PGSQL_LIBS@ +PKGPYTHONDIR = @PKGPYTHONDIR@ +PKG_CONFIG = @PKG_CONFIG@ +PLANTUML = @PLANTUML@ +PREMIUM_DIR = @PREMIUM_DIR@ +PYTHON = @PYTHON@ +PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@ +PYTHON_PLATFORM = @PYTHON_PLATFORM@ +PYTHON_PREFIX = @PYTHON_PREFIX@ +PYTHON_VERSION = @PYTHON_VERSION@ +RANLIB = @RANLIB@ +SED = @SED@ +SEP = @SEP@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +SPHINXBUILD = @SPHINXBUILD@ +SRPD_PLUGINS_PATH = @SRPD_PLUGINS_PATH@ +SR_REPO_PATH = @SR_REPO_PATH@ +STRIP = @STRIP@ +SYSREPOCPP_VERSION = @SYSREPOCPP_VERSION@ +SYSREPO_CPPFLAGS = @SYSREPO_CPPFLAGS@ +SYSREPO_INCLUDEDIR = @SYSREPO_INCLUDEDIR@ +SYSREPO_LIBS = @SYSREPO_LIBS@ +SYSREPO_VERSION = @SYSREPO_VERSION@ +USE_LCOV = @USE_LCOV@ +VALGRIND = @VALGRIND@ +VERSION = @VERSION@ +WARNING_GCC_44_STRICT_ALIASING_CFLAG = @WARNING_GCC_44_STRICT_ALIASING_CFLAG@ +YACC = @YACC@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +pkgpyexecdir = @pkgpyexecdir@ +pkgpythondir = @pkgpythondir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +pyexecdir = @pyexecdir@ +pythondir = @pythondir@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +SUBDIRS = . +AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib \ + $(BOOST_INCLUDES) +AM_CXXFLAGS = $(KEA_CXXFLAGS) + +# Kea libraries against which the test user libraries are linked. +HOOKS_LIB = $(top_builddir)/src/lib/hooks/libkea-hooks.la +LOG_LIB = $(top_builddir)/src/lib/log/libkea-log.la +UTIL_LIB = $(top_builddir)/src/lib/util/libkea-util.la +EXCEPTIONS_LIB = $(top_builddir)/src/lib/exceptions/libkea-exceptions.la +ALL_LIBS = $(HOOKS_LIB) $(LOG_LIB) $(UTIL_LIB) $(EXCEPTIONS_LIB) $(LOG4CPLUS_LIBS) + +# Files to clean include the file created by testing. +CLEANFILES = *.gcno *.gcda $(builddir)/marker_file.dat + +# Files generated by configure +DISTCLEANFILES = marker_file.h test_libraries.h +TESTS_ENVIRONMENT = \ + $(LIBTOOL) --mode=execute $(VALGRIND_COMMAND) + +# Build shared libraries for testing. The libtool way to create a shared library +# is to specify "-avoid-version -export-dynamic -module" in the library LDFLAGS +# (see http://www.gnu.org/software/libtool/manual/html_node/Link-mode.html). +# Use of these switches will guarantee that the .so files are created in the +# .libs folder and they can be dlopened. +# +# Note that the shared libraries with callouts should not be used together with +# the --enable-static-link option. With this option, the bind10 libraries are +# statically linked with the program and if the callout invokes the methods +# which belong to these libraries, the library with the callout will get its +# own copy of the static objects (e.g. logger, ServerHooks) and that will lead +# to unexpected errors. For this reason, the --enable-static-link option is +# ignored for unit tests built here. +@HAVE_GTEST_TRUE@noinst_LTLIBRARIES = libnvl.la libivl.la libfxl.la libbcl.la liblcl.la \ +@HAVE_GTEST_TRUE@ liblecl.la libucl.la libfcl.la libpcl.la libacl.la + + +# -rpath /nowhere is a hack to trigger libtool to not create a +# convenience archive, resulting in shared modules + +# No version function +@HAVE_GTEST_TRUE@libnvl_la_SOURCES = no_version_library.cc +@HAVE_GTEST_TRUE@libnvl_la_CXXFLAGS = $(AM_CXXFLAGS) +@HAVE_GTEST_TRUE@libnvl_la_CPPFLAGS = $(AM_CPPFLAGS) +@HAVE_GTEST_TRUE@libnvl_la_LDFLAGS = -avoid-version -export-dynamic -module -rpath /nowhere + +# Incorrect version function +@HAVE_GTEST_TRUE@libivl_la_SOURCES = incorrect_version_library.cc +@HAVE_GTEST_TRUE@libivl_la_CXXFLAGS = $(AM_CXXFLAGS) +@HAVE_GTEST_TRUE@libivl_la_CPPFLAGS = $(AM_CPPFLAGS) +@HAVE_GTEST_TRUE@libivl_la_LDFLAGS = -avoid-version -export-dynamic -module -rpath /nowhere + +# All framework functions throw an exception +@HAVE_GTEST_TRUE@libfxl_la_SOURCES = framework_exception_library.cc +@HAVE_GTEST_TRUE@libfxl_la_CXXFLAGS = $(AM_CXXFLAGS) +@HAVE_GTEST_TRUE@libfxl_la_CPPFLAGS = $(AM_CPPFLAGS) +@HAVE_GTEST_TRUE@libfxl_la_LDFLAGS = -avoid-version -export-dynamic -module -rpath /nowhere + +# The basic callout library - contains standard callouts +@HAVE_GTEST_TRUE@libbcl_la_SOURCES = basic_callout_library.cc +@HAVE_GTEST_TRUE@libbcl_la_CXXFLAGS = $(AM_CXXFLAGS) +@HAVE_GTEST_TRUE@libbcl_la_CPPFLAGS = $(AM_CPPFLAGS) +@HAVE_GTEST_TRUE@libbcl_la_LDFLAGS = -avoid-version -export-dynamic -module -rpath /nowhere + +# The load callout library - contains a load function +@HAVE_GTEST_TRUE@liblcl_la_SOURCES = load_callout_library.cc +@HAVE_GTEST_TRUE@liblcl_la_CXXFLAGS = $(AM_CXXFLAGS) +@HAVE_GTEST_TRUE@liblcl_la_CPPFLAGS = $(AM_CPPFLAGS) +@HAVE_GTEST_TRUE@liblcl_la_LDFLAGS = -avoid-version -export-dynamic -module -rpath /nowhere + +# The load error callout library - contains a load function that returns +# an error. +@HAVE_GTEST_TRUE@liblecl_la_SOURCES = load_error_callout_library.cc +@HAVE_GTEST_TRUE@liblecl_la_CXXFLAGS = $(AM_CXXFLAGS) +@HAVE_GTEST_TRUE@liblecl_la_CPPFLAGS = $(AM_CPPFLAGS) +@HAVE_GTEST_TRUE@liblecl_la_LDFLAGS = -avoid-version -export-dynamic -module -rpath /nowhere + +# The unload callout library - contains an unload function that +# creates a marker file. +@HAVE_GTEST_TRUE@libucl_la_SOURCES = unload_callout_library.cc +@HAVE_GTEST_TRUE@libucl_la_CXXFLAGS = $(AM_CXXFLAGS) +@HAVE_GTEST_TRUE@libucl_la_CPPFLAGS = $(AM_CPPFLAGS) +@HAVE_GTEST_TRUE@libucl_la_LDFLAGS = -avoid-version -export-dynamic -module -rpath /nowhere + +# The full callout library - contains all three framework functions. +@HAVE_GTEST_TRUE@libfcl_la_SOURCES = full_callout_library.cc +@HAVE_GTEST_TRUE@libfcl_la_CXXFLAGS = $(AM_CXXFLAGS) +@HAVE_GTEST_TRUE@libfcl_la_CPPFLAGS = $(AM_CPPFLAGS) +@HAVE_GTEST_TRUE@libfcl_la_LDFLAGS = -avoid-version -export-dynamic -module -rpath /nowhere + +# The parameters checking callout library - expects +@HAVE_GTEST_TRUE@libpcl_la_SOURCES = callout_params_library.cc +@HAVE_GTEST_TRUE@libpcl_la_CXXFLAGS = $(AM_CXXFLAGS) +@HAVE_GTEST_TRUE@libpcl_la_CPPFLAGS = $(AM_CPPFLAGS) +@HAVE_GTEST_TRUE@libpcl_la_LDFLAGS = -avoid-version -export-dynamic \ +@HAVE_GTEST_TRUE@ -module -rpath /nowhere \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/util/libkea-util.la + +# The async callout library - parks object for asynchronous task +@HAVE_GTEST_TRUE@libacl_la_SOURCES = async_callout_library.cc +@HAVE_GTEST_TRUE@libacl_la_CXXFLAGS = $(AM_CXXFLAGS) +@HAVE_GTEST_TRUE@libacl_la_CPPFLAGS = $(AM_CPPFLAGS) +@HAVE_GTEST_TRUE@libacl_la_LDFLAGS = -avoid-version -export-dynamic -module -rpath /nowhere +@HAVE_GTEST_TRUE@run_unittests_SOURCES = run_unittests.cc \ +@HAVE_GTEST_TRUE@ callout_handle_unittest.cc \ +@HAVE_GTEST_TRUE@ callout_handle_associate_unittest.cc \ +@HAVE_GTEST_TRUE@ callout_manager_unittest.cc \ +@HAVE_GTEST_TRUE@ common_test_class.h handles_unittest.cc \ +@HAVE_GTEST_TRUE@ hooks_manager_unittest.cc \ +@HAVE_GTEST_TRUE@ library_manager_collection_unittest.cc \ +@HAVE_GTEST_TRUE@ library_manager_unittest.cc \ +@HAVE_GTEST_TRUE@ parking_lots_unittest.cc \ +@HAVE_GTEST_TRUE@ server_hooks_unittest.cc +@HAVE_GTEST_TRUE@nodist_run_unittests_SOURCES = marker_file.h \ +@HAVE_GTEST_TRUE@ test_libraries.h +@HAVE_GTEST_TRUE@run_unittests_CXXFLAGS = $(AM_CXXFLAGS) +@HAVE_GTEST_TRUE@run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES) +@HAVE_GTEST_TRUE@run_unittests_LDFLAGS = $(AM_LDFLAGS) \ +@HAVE_GTEST_TRUE@ $(GTEST_LDFLAGS) $(am__append_2) +@HAVE_GTEST_TRUE@run_unittests_LDADD = $(AM_LDADD) $(ALL_LIBS) \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cc/libkea-cc.la \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/util/unittests/libutil_unittests.la \ +@HAVE_GTEST_TRUE@ $(GTEST_LDADD) $(LOG4CPLUS_LIBS) \ +@HAVE_GTEST_TRUE@ $(BOOST_LIBS) +EXTRA_DIST = marker_file.h.in test_libraries.h.in +all: all-recursive + +.SUFFIXES: +.SUFFIXES: .cc .lo .o .obj +$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/lib/hooks/tests/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign src/lib/hooks/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): +marker_file.h: $(top_builddir)/config.status $(srcdir)/marker_file.h.in + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ +test_libraries.h: $(top_builddir)/config.status $(srcdir)/test_libraries.h.in + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ + +clean-noinstPROGRAMS: + @list='$(noinst_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list + +clean-noinstLTLIBRARIES: + -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES) + @list='$(noinst_LTLIBRARIES)'; \ + locs=`for p in $$list; do echo $$p; done | \ + sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ + sort -u`; \ + test -z "$$locs" || { \ + echo rm -f $${locs}; \ + rm -f $${locs}; \ + } + +libacl.la: $(libacl_la_OBJECTS) $(libacl_la_DEPENDENCIES) $(EXTRA_libacl_la_DEPENDENCIES) + $(AM_V_CXXLD)$(libacl_la_LINK) $(am_libacl_la_rpath) $(libacl_la_OBJECTS) $(libacl_la_LIBADD) $(LIBS) + +libbcl.la: $(libbcl_la_OBJECTS) $(libbcl_la_DEPENDENCIES) $(EXTRA_libbcl_la_DEPENDENCIES) + $(AM_V_CXXLD)$(libbcl_la_LINK) $(am_libbcl_la_rpath) $(libbcl_la_OBJECTS) $(libbcl_la_LIBADD) $(LIBS) + +libfcl.la: $(libfcl_la_OBJECTS) $(libfcl_la_DEPENDENCIES) $(EXTRA_libfcl_la_DEPENDENCIES) + $(AM_V_CXXLD)$(libfcl_la_LINK) $(am_libfcl_la_rpath) $(libfcl_la_OBJECTS) $(libfcl_la_LIBADD) $(LIBS) + +libfxl.la: $(libfxl_la_OBJECTS) $(libfxl_la_DEPENDENCIES) $(EXTRA_libfxl_la_DEPENDENCIES) + $(AM_V_CXXLD)$(libfxl_la_LINK) $(am_libfxl_la_rpath) $(libfxl_la_OBJECTS) $(libfxl_la_LIBADD) $(LIBS) + +libivl.la: $(libivl_la_OBJECTS) $(libivl_la_DEPENDENCIES) $(EXTRA_libivl_la_DEPENDENCIES) + $(AM_V_CXXLD)$(libivl_la_LINK) $(am_libivl_la_rpath) $(libivl_la_OBJECTS) $(libivl_la_LIBADD) $(LIBS) + +liblcl.la: $(liblcl_la_OBJECTS) $(liblcl_la_DEPENDENCIES) $(EXTRA_liblcl_la_DEPENDENCIES) + $(AM_V_CXXLD)$(liblcl_la_LINK) $(am_liblcl_la_rpath) $(liblcl_la_OBJECTS) $(liblcl_la_LIBADD) $(LIBS) + +liblecl.la: $(liblecl_la_OBJECTS) $(liblecl_la_DEPENDENCIES) $(EXTRA_liblecl_la_DEPENDENCIES) + $(AM_V_CXXLD)$(liblecl_la_LINK) $(am_liblecl_la_rpath) $(liblecl_la_OBJECTS) $(liblecl_la_LIBADD) $(LIBS) + +libnvl.la: $(libnvl_la_OBJECTS) $(libnvl_la_DEPENDENCIES) $(EXTRA_libnvl_la_DEPENDENCIES) + $(AM_V_CXXLD)$(libnvl_la_LINK) $(am_libnvl_la_rpath) $(libnvl_la_OBJECTS) $(libnvl_la_LIBADD) $(LIBS) + +libpcl.la: $(libpcl_la_OBJECTS) $(libpcl_la_DEPENDENCIES) $(EXTRA_libpcl_la_DEPENDENCIES) + $(AM_V_CXXLD)$(libpcl_la_LINK) $(am_libpcl_la_rpath) $(libpcl_la_OBJECTS) $(libpcl_la_LIBADD) $(LIBS) + +libucl.la: $(libucl_la_OBJECTS) $(libucl_la_DEPENDENCIES) $(EXTRA_libucl_la_DEPENDENCIES) + $(AM_V_CXXLD)$(libucl_la_LINK) $(am_libucl_la_rpath) $(libucl_la_OBJECTS) $(libucl_la_LIBADD) $(LIBS) + +run_unittests$(EXEEXT): $(run_unittests_OBJECTS) $(run_unittests_DEPENDENCIES) $(EXTRA_run_unittests_DEPENDENCIES) + @rm -f run_unittests$(EXEEXT) + $(AM_V_CXXLD)$(run_unittests_LINK) $(run_unittests_OBJECTS) $(run_unittests_LDADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libacl_la-async_callout_library.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libbcl_la-basic_callout_library.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libfcl_la-full_callout_library.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libfxl_la-framework_exception_library.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libivl_la-incorrect_version_library.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblcl_la-load_callout_library.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblecl_la-load_error_callout_library.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libnvl_la-no_version_library.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libpcl_la-callout_params_library.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libucl_la-unload_callout_library.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-callout_handle_associate_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-callout_handle_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-callout_manager_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-handles_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-hooks_manager_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-library_manager_collection_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-library_manager_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-parking_lots_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-run_unittests.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-server_hooks_unittest.Po@am__quote@ # am--include-marker + +$(am__depfiles_remade): + @$(MKDIR_P) $(@D) + @echo '# dummy' >$@-t && $(am__mv) $@-t $@ + +am--depfiles: $(am__depfiles_remade) + +.cc.o: +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ $< + +.cc.obj: +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +.cc.lo: +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LTCXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LTCXXCOMPILE) -c -o $@ $< + +libacl_la-async_callout_library.lo: async_callout_library.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libacl_la_CPPFLAGS) $(CPPFLAGS) $(libacl_la_CXXFLAGS) $(CXXFLAGS) -MT libacl_la-async_callout_library.lo -MD -MP -MF $(DEPDIR)/libacl_la-async_callout_library.Tpo -c -o libacl_la-async_callout_library.lo `test -f 'async_callout_library.cc' || echo '$(srcdir)/'`async_callout_library.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libacl_la-async_callout_library.Tpo $(DEPDIR)/libacl_la-async_callout_library.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='async_callout_library.cc' object='libacl_la-async_callout_library.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libacl_la_CPPFLAGS) $(CPPFLAGS) $(libacl_la_CXXFLAGS) $(CXXFLAGS) -c -o libacl_la-async_callout_library.lo `test -f 'async_callout_library.cc' || echo '$(srcdir)/'`async_callout_library.cc + +libbcl_la-basic_callout_library.lo: basic_callout_library.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libbcl_la_CPPFLAGS) $(CPPFLAGS) $(libbcl_la_CXXFLAGS) $(CXXFLAGS) -MT libbcl_la-basic_callout_library.lo -MD -MP -MF $(DEPDIR)/libbcl_la-basic_callout_library.Tpo -c -o libbcl_la-basic_callout_library.lo `test -f 'basic_callout_library.cc' || echo '$(srcdir)/'`basic_callout_library.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libbcl_la-basic_callout_library.Tpo $(DEPDIR)/libbcl_la-basic_callout_library.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='basic_callout_library.cc' object='libbcl_la-basic_callout_library.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libbcl_la_CPPFLAGS) $(CPPFLAGS) $(libbcl_la_CXXFLAGS) $(CXXFLAGS) -c -o libbcl_la-basic_callout_library.lo `test -f 'basic_callout_library.cc' || echo '$(srcdir)/'`basic_callout_library.cc + +libfcl_la-full_callout_library.lo: full_callout_library.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libfcl_la_CPPFLAGS) $(CPPFLAGS) $(libfcl_la_CXXFLAGS) $(CXXFLAGS) -MT libfcl_la-full_callout_library.lo -MD -MP -MF $(DEPDIR)/libfcl_la-full_callout_library.Tpo -c -o libfcl_la-full_callout_library.lo `test -f 'full_callout_library.cc' || echo '$(srcdir)/'`full_callout_library.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libfcl_la-full_callout_library.Tpo $(DEPDIR)/libfcl_la-full_callout_library.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='full_callout_library.cc' object='libfcl_la-full_callout_library.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libfcl_la_CPPFLAGS) $(CPPFLAGS) $(libfcl_la_CXXFLAGS) $(CXXFLAGS) -c -o libfcl_la-full_callout_library.lo `test -f 'full_callout_library.cc' || echo '$(srcdir)/'`full_callout_library.cc + +libfxl_la-framework_exception_library.lo: framework_exception_library.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libfxl_la_CPPFLAGS) $(CPPFLAGS) $(libfxl_la_CXXFLAGS) $(CXXFLAGS) -MT libfxl_la-framework_exception_library.lo -MD -MP -MF $(DEPDIR)/libfxl_la-framework_exception_library.Tpo -c -o libfxl_la-framework_exception_library.lo `test -f 'framework_exception_library.cc' || echo '$(srcdir)/'`framework_exception_library.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libfxl_la-framework_exception_library.Tpo $(DEPDIR)/libfxl_la-framework_exception_library.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='framework_exception_library.cc' object='libfxl_la-framework_exception_library.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libfxl_la_CPPFLAGS) $(CPPFLAGS) $(libfxl_la_CXXFLAGS) $(CXXFLAGS) -c -o libfxl_la-framework_exception_library.lo `test -f 'framework_exception_library.cc' || echo '$(srcdir)/'`framework_exception_library.cc + +libivl_la-incorrect_version_library.lo: incorrect_version_library.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libivl_la_CPPFLAGS) $(CPPFLAGS) $(libivl_la_CXXFLAGS) $(CXXFLAGS) -MT libivl_la-incorrect_version_library.lo -MD -MP -MF $(DEPDIR)/libivl_la-incorrect_version_library.Tpo -c -o libivl_la-incorrect_version_library.lo `test -f 'incorrect_version_library.cc' || echo '$(srcdir)/'`incorrect_version_library.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libivl_la-incorrect_version_library.Tpo $(DEPDIR)/libivl_la-incorrect_version_library.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='incorrect_version_library.cc' object='libivl_la-incorrect_version_library.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libivl_la_CPPFLAGS) $(CPPFLAGS) $(libivl_la_CXXFLAGS) $(CXXFLAGS) -c -o libivl_la-incorrect_version_library.lo `test -f 'incorrect_version_library.cc' || echo '$(srcdir)/'`incorrect_version_library.cc + +liblcl_la-load_callout_library.lo: load_callout_library.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(liblcl_la_CPPFLAGS) $(CPPFLAGS) $(liblcl_la_CXXFLAGS) $(CXXFLAGS) -MT liblcl_la-load_callout_library.lo -MD -MP -MF $(DEPDIR)/liblcl_la-load_callout_library.Tpo -c -o liblcl_la-load_callout_library.lo `test -f 'load_callout_library.cc' || echo '$(srcdir)/'`load_callout_library.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/liblcl_la-load_callout_library.Tpo $(DEPDIR)/liblcl_la-load_callout_library.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='load_callout_library.cc' object='liblcl_la-load_callout_library.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(liblcl_la_CPPFLAGS) $(CPPFLAGS) $(liblcl_la_CXXFLAGS) $(CXXFLAGS) -c -o liblcl_la-load_callout_library.lo `test -f 'load_callout_library.cc' || echo '$(srcdir)/'`load_callout_library.cc + +liblecl_la-load_error_callout_library.lo: load_error_callout_library.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(liblecl_la_CPPFLAGS) $(CPPFLAGS) $(liblecl_la_CXXFLAGS) $(CXXFLAGS) -MT liblecl_la-load_error_callout_library.lo -MD -MP -MF $(DEPDIR)/liblecl_la-load_error_callout_library.Tpo -c -o liblecl_la-load_error_callout_library.lo `test -f 'load_error_callout_library.cc' || echo '$(srcdir)/'`load_error_callout_library.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/liblecl_la-load_error_callout_library.Tpo $(DEPDIR)/liblecl_la-load_error_callout_library.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='load_error_callout_library.cc' object='liblecl_la-load_error_callout_library.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(liblecl_la_CPPFLAGS) $(CPPFLAGS) $(liblecl_la_CXXFLAGS) $(CXXFLAGS) -c -o liblecl_la-load_error_callout_library.lo `test -f 'load_error_callout_library.cc' || echo '$(srcdir)/'`load_error_callout_library.cc + +libnvl_la-no_version_library.lo: no_version_library.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnvl_la_CPPFLAGS) $(CPPFLAGS) $(libnvl_la_CXXFLAGS) $(CXXFLAGS) -MT libnvl_la-no_version_library.lo -MD -MP -MF $(DEPDIR)/libnvl_la-no_version_library.Tpo -c -o libnvl_la-no_version_library.lo `test -f 'no_version_library.cc' || echo '$(srcdir)/'`no_version_library.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libnvl_la-no_version_library.Tpo $(DEPDIR)/libnvl_la-no_version_library.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='no_version_library.cc' object='libnvl_la-no_version_library.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnvl_la_CPPFLAGS) $(CPPFLAGS) $(libnvl_la_CXXFLAGS) $(CXXFLAGS) -c -o libnvl_la-no_version_library.lo `test -f 'no_version_library.cc' || echo '$(srcdir)/'`no_version_library.cc + +libpcl_la-callout_params_library.lo: callout_params_library.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libpcl_la_CPPFLAGS) $(CPPFLAGS) $(libpcl_la_CXXFLAGS) $(CXXFLAGS) -MT libpcl_la-callout_params_library.lo -MD -MP -MF $(DEPDIR)/libpcl_la-callout_params_library.Tpo -c -o libpcl_la-callout_params_library.lo `test -f 'callout_params_library.cc' || echo '$(srcdir)/'`callout_params_library.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libpcl_la-callout_params_library.Tpo $(DEPDIR)/libpcl_la-callout_params_library.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='callout_params_library.cc' object='libpcl_la-callout_params_library.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libpcl_la_CPPFLAGS) $(CPPFLAGS) $(libpcl_la_CXXFLAGS) $(CXXFLAGS) -c -o libpcl_la-callout_params_library.lo `test -f 'callout_params_library.cc' || echo '$(srcdir)/'`callout_params_library.cc + +libucl_la-unload_callout_library.lo: unload_callout_library.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libucl_la_CPPFLAGS) $(CPPFLAGS) $(libucl_la_CXXFLAGS) $(CXXFLAGS) -MT libucl_la-unload_callout_library.lo -MD -MP -MF $(DEPDIR)/libucl_la-unload_callout_library.Tpo -c -o libucl_la-unload_callout_library.lo `test -f 'unload_callout_library.cc' || echo '$(srcdir)/'`unload_callout_library.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libucl_la-unload_callout_library.Tpo $(DEPDIR)/libucl_la-unload_callout_library.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='unload_callout_library.cc' object='libucl_la-unload_callout_library.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libucl_la_CPPFLAGS) $(CPPFLAGS) $(libucl_la_CXXFLAGS) $(CXXFLAGS) -c -o libucl_la-unload_callout_library.lo `test -f 'unload_callout_library.cc' || echo '$(srcdir)/'`unload_callout_library.cc + +run_unittests-run_unittests.o: run_unittests.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-run_unittests.o -MD -MP -MF $(DEPDIR)/run_unittests-run_unittests.Tpo -c -o run_unittests-run_unittests.o `test -f 'run_unittests.cc' || echo '$(srcdir)/'`run_unittests.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-run_unittests.Tpo $(DEPDIR)/run_unittests-run_unittests.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='run_unittests.cc' object='run_unittests-run_unittests.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-run_unittests.o `test -f 'run_unittests.cc' || echo '$(srcdir)/'`run_unittests.cc + +run_unittests-run_unittests.obj: run_unittests.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-run_unittests.obj -MD -MP -MF $(DEPDIR)/run_unittests-run_unittests.Tpo -c -o run_unittests-run_unittests.obj `if test -f 'run_unittests.cc'; then $(CYGPATH_W) 'run_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/run_unittests.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-run_unittests.Tpo $(DEPDIR)/run_unittests-run_unittests.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='run_unittests.cc' object='run_unittests-run_unittests.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-run_unittests.obj `if test -f 'run_unittests.cc'; then $(CYGPATH_W) 'run_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/run_unittests.cc'; fi` + +run_unittests-callout_handle_unittest.o: callout_handle_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-callout_handle_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-callout_handle_unittest.Tpo -c -o run_unittests-callout_handle_unittest.o `test -f 'callout_handle_unittest.cc' || echo '$(srcdir)/'`callout_handle_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-callout_handle_unittest.Tpo $(DEPDIR)/run_unittests-callout_handle_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='callout_handle_unittest.cc' object='run_unittests-callout_handle_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-callout_handle_unittest.o `test -f 'callout_handle_unittest.cc' || echo '$(srcdir)/'`callout_handle_unittest.cc + +run_unittests-callout_handle_unittest.obj: callout_handle_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-callout_handle_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-callout_handle_unittest.Tpo -c -o run_unittests-callout_handle_unittest.obj `if test -f 'callout_handle_unittest.cc'; then $(CYGPATH_W) 'callout_handle_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/callout_handle_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-callout_handle_unittest.Tpo $(DEPDIR)/run_unittests-callout_handle_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='callout_handle_unittest.cc' object='run_unittests-callout_handle_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-callout_handle_unittest.obj `if test -f 'callout_handle_unittest.cc'; then $(CYGPATH_W) 'callout_handle_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/callout_handle_unittest.cc'; fi` + +run_unittests-callout_handle_associate_unittest.o: callout_handle_associate_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-callout_handle_associate_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-callout_handle_associate_unittest.Tpo -c -o run_unittests-callout_handle_associate_unittest.o `test -f 'callout_handle_associate_unittest.cc' || echo '$(srcdir)/'`callout_handle_associate_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-callout_handle_associate_unittest.Tpo $(DEPDIR)/run_unittests-callout_handle_associate_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='callout_handle_associate_unittest.cc' object='run_unittests-callout_handle_associate_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-callout_handle_associate_unittest.o `test -f 'callout_handle_associate_unittest.cc' || echo '$(srcdir)/'`callout_handle_associate_unittest.cc + +run_unittests-callout_handle_associate_unittest.obj: callout_handle_associate_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-callout_handle_associate_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-callout_handle_associate_unittest.Tpo -c -o run_unittests-callout_handle_associate_unittest.obj `if test -f 'callout_handle_associate_unittest.cc'; then $(CYGPATH_W) 'callout_handle_associate_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/callout_handle_associate_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-callout_handle_associate_unittest.Tpo $(DEPDIR)/run_unittests-callout_handle_associate_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='callout_handle_associate_unittest.cc' object='run_unittests-callout_handle_associate_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-callout_handle_associate_unittest.obj `if test -f 'callout_handle_associate_unittest.cc'; then $(CYGPATH_W) 'callout_handle_associate_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/callout_handle_associate_unittest.cc'; fi` + +run_unittests-callout_manager_unittest.o: callout_manager_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-callout_manager_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-callout_manager_unittest.Tpo -c -o run_unittests-callout_manager_unittest.o `test -f 'callout_manager_unittest.cc' || echo '$(srcdir)/'`callout_manager_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-callout_manager_unittest.Tpo $(DEPDIR)/run_unittests-callout_manager_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='callout_manager_unittest.cc' object='run_unittests-callout_manager_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-callout_manager_unittest.o `test -f 'callout_manager_unittest.cc' || echo '$(srcdir)/'`callout_manager_unittest.cc + +run_unittests-callout_manager_unittest.obj: callout_manager_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-callout_manager_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-callout_manager_unittest.Tpo -c -o run_unittests-callout_manager_unittest.obj `if test -f 'callout_manager_unittest.cc'; then $(CYGPATH_W) 'callout_manager_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/callout_manager_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-callout_manager_unittest.Tpo $(DEPDIR)/run_unittests-callout_manager_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='callout_manager_unittest.cc' object='run_unittests-callout_manager_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-callout_manager_unittest.obj `if test -f 'callout_manager_unittest.cc'; then $(CYGPATH_W) 'callout_manager_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/callout_manager_unittest.cc'; fi` + +run_unittests-handles_unittest.o: handles_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-handles_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-handles_unittest.Tpo -c -o run_unittests-handles_unittest.o `test -f 'handles_unittest.cc' || echo '$(srcdir)/'`handles_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-handles_unittest.Tpo $(DEPDIR)/run_unittests-handles_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='handles_unittest.cc' object='run_unittests-handles_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-handles_unittest.o `test -f 'handles_unittest.cc' || echo '$(srcdir)/'`handles_unittest.cc + +run_unittests-handles_unittest.obj: handles_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-handles_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-handles_unittest.Tpo -c -o run_unittests-handles_unittest.obj `if test -f 'handles_unittest.cc'; then $(CYGPATH_W) 'handles_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/handles_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-handles_unittest.Tpo $(DEPDIR)/run_unittests-handles_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='handles_unittest.cc' object='run_unittests-handles_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-handles_unittest.obj `if test -f 'handles_unittest.cc'; then $(CYGPATH_W) 'handles_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/handles_unittest.cc'; fi` + +run_unittests-hooks_manager_unittest.o: hooks_manager_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-hooks_manager_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-hooks_manager_unittest.Tpo -c -o run_unittests-hooks_manager_unittest.o `test -f 'hooks_manager_unittest.cc' || echo '$(srcdir)/'`hooks_manager_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-hooks_manager_unittest.Tpo $(DEPDIR)/run_unittests-hooks_manager_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='hooks_manager_unittest.cc' object='run_unittests-hooks_manager_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-hooks_manager_unittest.o `test -f 'hooks_manager_unittest.cc' || echo '$(srcdir)/'`hooks_manager_unittest.cc + +run_unittests-hooks_manager_unittest.obj: hooks_manager_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-hooks_manager_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-hooks_manager_unittest.Tpo -c -o run_unittests-hooks_manager_unittest.obj `if test -f 'hooks_manager_unittest.cc'; then $(CYGPATH_W) 'hooks_manager_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/hooks_manager_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-hooks_manager_unittest.Tpo $(DEPDIR)/run_unittests-hooks_manager_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='hooks_manager_unittest.cc' object='run_unittests-hooks_manager_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-hooks_manager_unittest.obj `if test -f 'hooks_manager_unittest.cc'; then $(CYGPATH_W) 'hooks_manager_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/hooks_manager_unittest.cc'; fi` + +run_unittests-library_manager_collection_unittest.o: library_manager_collection_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-library_manager_collection_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-library_manager_collection_unittest.Tpo -c -o run_unittests-library_manager_collection_unittest.o `test -f 'library_manager_collection_unittest.cc' || echo '$(srcdir)/'`library_manager_collection_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-library_manager_collection_unittest.Tpo $(DEPDIR)/run_unittests-library_manager_collection_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='library_manager_collection_unittest.cc' object='run_unittests-library_manager_collection_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-library_manager_collection_unittest.o `test -f 'library_manager_collection_unittest.cc' || echo '$(srcdir)/'`library_manager_collection_unittest.cc + +run_unittests-library_manager_collection_unittest.obj: library_manager_collection_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-library_manager_collection_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-library_manager_collection_unittest.Tpo -c -o run_unittests-library_manager_collection_unittest.obj `if test -f 'library_manager_collection_unittest.cc'; then $(CYGPATH_W) 'library_manager_collection_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/library_manager_collection_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-library_manager_collection_unittest.Tpo $(DEPDIR)/run_unittests-library_manager_collection_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='library_manager_collection_unittest.cc' object='run_unittests-library_manager_collection_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-library_manager_collection_unittest.obj `if test -f 'library_manager_collection_unittest.cc'; then $(CYGPATH_W) 'library_manager_collection_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/library_manager_collection_unittest.cc'; fi` + +run_unittests-library_manager_unittest.o: library_manager_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-library_manager_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-library_manager_unittest.Tpo -c -o run_unittests-library_manager_unittest.o `test -f 'library_manager_unittest.cc' || echo '$(srcdir)/'`library_manager_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-library_manager_unittest.Tpo $(DEPDIR)/run_unittests-library_manager_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='library_manager_unittest.cc' object='run_unittests-library_manager_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-library_manager_unittest.o `test -f 'library_manager_unittest.cc' || echo '$(srcdir)/'`library_manager_unittest.cc + +run_unittests-library_manager_unittest.obj: library_manager_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-library_manager_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-library_manager_unittest.Tpo -c -o run_unittests-library_manager_unittest.obj `if test -f 'library_manager_unittest.cc'; then $(CYGPATH_W) 'library_manager_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/library_manager_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-library_manager_unittest.Tpo $(DEPDIR)/run_unittests-library_manager_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='library_manager_unittest.cc' object='run_unittests-library_manager_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-library_manager_unittest.obj `if test -f 'library_manager_unittest.cc'; then $(CYGPATH_W) 'library_manager_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/library_manager_unittest.cc'; fi` + +run_unittests-parking_lots_unittest.o: parking_lots_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-parking_lots_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-parking_lots_unittest.Tpo -c -o run_unittests-parking_lots_unittest.o `test -f 'parking_lots_unittest.cc' || echo '$(srcdir)/'`parking_lots_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-parking_lots_unittest.Tpo $(DEPDIR)/run_unittests-parking_lots_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='parking_lots_unittest.cc' object='run_unittests-parking_lots_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-parking_lots_unittest.o `test -f 'parking_lots_unittest.cc' || echo '$(srcdir)/'`parking_lots_unittest.cc + +run_unittests-parking_lots_unittest.obj: parking_lots_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-parking_lots_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-parking_lots_unittest.Tpo -c -o run_unittests-parking_lots_unittest.obj `if test -f 'parking_lots_unittest.cc'; then $(CYGPATH_W) 'parking_lots_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/parking_lots_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-parking_lots_unittest.Tpo $(DEPDIR)/run_unittests-parking_lots_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='parking_lots_unittest.cc' object='run_unittests-parking_lots_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-parking_lots_unittest.obj `if test -f 'parking_lots_unittest.cc'; then $(CYGPATH_W) 'parking_lots_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/parking_lots_unittest.cc'; fi` + +run_unittests-server_hooks_unittest.o: server_hooks_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-server_hooks_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-server_hooks_unittest.Tpo -c -o run_unittests-server_hooks_unittest.o `test -f 'server_hooks_unittest.cc' || echo '$(srcdir)/'`server_hooks_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-server_hooks_unittest.Tpo $(DEPDIR)/run_unittests-server_hooks_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='server_hooks_unittest.cc' object='run_unittests-server_hooks_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-server_hooks_unittest.o `test -f 'server_hooks_unittest.cc' || echo '$(srcdir)/'`server_hooks_unittest.cc + +run_unittests-server_hooks_unittest.obj: server_hooks_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-server_hooks_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-server_hooks_unittest.Tpo -c -o run_unittests-server_hooks_unittest.obj `if test -f 'server_hooks_unittest.cc'; then $(CYGPATH_W) 'server_hooks_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/server_hooks_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-server_hooks_unittest.Tpo $(DEPDIR)/run_unittests-server_hooks_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='server_hooks_unittest.cc' object='run_unittests-server_hooks_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-server_hooks_unittest.obj `if test -f 'server_hooks_unittest.cc'; then $(CYGPATH_W) 'server_hooks_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/server_hooks_unittest.cc'; fi` + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs + +# This directory's subdirectories are mostly independent; you can cd +# into them and run 'make' without going through this Makefile. +# To change the values of 'make' variables: instead of editing Makefiles, +# (1) if the variable is set in 'config.status', edit 'config.status' +# (which will cause the Makefiles to be regenerated when you run 'make'); +# (2) otherwise, pass the desired values on the 'make' command line. +$(am__recursive_targets): + @fail=; \ + if $(am__make_keepgoing); then \ + failcom='fail=yes'; \ + else \ + failcom='exit 1'; \ + fi; \ + dot_seen=no; \ + target=`echo $@ | sed s/-recursive//`; \ + case "$@" in \ + distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \ + *) list='$(SUBDIRS)' ;; \ + esac; \ + for subdir in $$list; do \ + echo "Making $$target in $$subdir"; \ + if test "$$subdir" = "."; then \ + dot_seen=yes; \ + local_target="$$target-am"; \ + else \ + local_target="$$target"; \ + fi; \ + ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \ + || eval $$failcom; \ + done; \ + if test "$$dot_seen" = "no"; then \ + $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \ + fi; test -z "$$fail" + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-recursive +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \ + include_option=--etags-include; \ + empty_fix=.; \ + else \ + include_option=--include; \ + empty_fix=; \ + fi; \ + list='$(SUBDIRS)'; for subdir in $$list; do \ + if test "$$subdir" = .; then :; else \ + test ! -f $$subdir/TAGS || \ + set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \ + fi; \ + done; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-recursive + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-recursive + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +check-TESTS: $(TESTS) + @failed=0; all=0; xfail=0; xpass=0; skip=0; \ + srcdir=$(srcdir); export srcdir; \ + list=' $(TESTS) '; \ + $(am__tty_colors); \ + if test -n "$$list"; then \ + for tst in $$list; do \ + if test -f ./$$tst; then dir=./; \ + elif test -f $$tst; then dir=; \ + else dir="$(srcdir)/"; fi; \ + if $(TESTS_ENVIRONMENT) $${dir}$$tst $(AM_TESTS_FD_REDIRECT); then \ + all=`expr $$all + 1`; \ + case " $(XFAIL_TESTS) " in \ + *[\ \ ]$$tst[\ \ ]*) \ + xpass=`expr $$xpass + 1`; \ + failed=`expr $$failed + 1`; \ + col=$$red; res=XPASS; \ + ;; \ + *) \ + col=$$grn; res=PASS; \ + ;; \ + esac; \ + elif test $$? -ne 77; then \ + all=`expr $$all + 1`; \ + case " $(XFAIL_TESTS) " in \ + *[\ \ ]$$tst[\ \ ]*) \ + xfail=`expr $$xfail + 1`; \ + col=$$lgn; res=XFAIL; \ + ;; \ + *) \ + failed=`expr $$failed + 1`; \ + col=$$red; res=FAIL; \ + ;; \ + esac; \ + else \ + skip=`expr $$skip + 1`; \ + col=$$blu; res=SKIP; \ + fi; \ + echo "$${col}$$res$${std}: $$tst"; \ + done; \ + if test "$$all" -eq 1; then \ + tests="test"; \ + All=""; \ + else \ + tests="tests"; \ + All="All "; \ + fi; \ + if test "$$failed" -eq 0; then \ + if test "$$xfail" -eq 0; then \ + banner="$$All$$all $$tests passed"; \ + else \ + if test "$$xfail" -eq 1; then failures=failure; else failures=failures; fi; \ + banner="$$All$$all $$tests behaved as expected ($$xfail expected $$failures)"; \ + fi; \ + else \ + if test "$$xpass" -eq 0; then \ + banner="$$failed of $$all $$tests failed"; \ + else \ + if test "$$xpass" -eq 1; then passes=pass; else passes=passes; fi; \ + banner="$$failed of $$all $$tests did not behave as expected ($$xpass unexpected $$passes)"; \ + fi; \ + fi; \ + dashes="$$banner"; \ + skipped=""; \ + if test "$$skip" -ne 0; then \ + if test "$$skip" -eq 1; then \ + skipped="($$skip test was not run)"; \ + else \ + skipped="($$skip tests were not run)"; \ + fi; \ + test `echo "$$skipped" | wc -c` -le `echo "$$banner" | wc -c` || \ + dashes="$$skipped"; \ + fi; \ + report=""; \ + if test "$$failed" -ne 0 && test -n "$(PACKAGE_BUGREPORT)"; then \ + report="Please report to $(PACKAGE_BUGREPORT)"; \ + test `echo "$$report" | wc -c` -le `echo "$$banner" | wc -c` || \ + dashes="$$report"; \ + fi; \ + dashes=`echo "$$dashes" | sed s/./=/g`; \ + if test "$$failed" -eq 0; then \ + col="$$grn"; \ + else \ + col="$$red"; \ + fi; \ + echo "$${col}$$dashes$${std}"; \ + echo "$${col}$$banner$${std}"; \ + test -z "$$skipped" || echo "$${col}$$skipped$${std}"; \ + test -z "$$report" || echo "$${col}$$report$${std}"; \ + echo "$${col}$$dashes$${std}"; \ + test "$$failed" -eq 0; \ + else :; fi + +distdir: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) distdir-am + +distdir-am: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done + @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \ + if test "$$subdir" = .; then :; else \ + $(am__make_dryrun) \ + || test -d "$(distdir)/$$subdir" \ + || $(MKDIR_P) "$(distdir)/$$subdir" \ + || exit 1; \ + dir1=$$subdir; dir2="$(distdir)/$$subdir"; \ + $(am__relativize); \ + new_distdir=$$reldir; \ + dir1=$$subdir; dir2="$(top_distdir)"; \ + $(am__relativize); \ + new_top_distdir=$$reldir; \ + echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \ + echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \ + ($(am__cd) $$subdir && \ + $(MAKE) $(AM_MAKEFLAGS) \ + top_distdir="$$new_top_distdir" \ + distdir="$$new_distdir" \ + am__remove_distdir=: \ + am__skip_length_check=: \ + am__skip_mode_fix=: \ + distdir) \ + || exit 1; \ + fi; \ + done +check-am: all-am + $(MAKE) $(AM_MAKEFLAGS) check-TESTS +check: check-recursive +all-am: Makefile $(PROGRAMS) $(LTLIBRARIES) +installdirs: installdirs-recursive +installdirs-am: +install: install-recursive +install-exec: install-exec-recursive +install-data: install-data-recursive +uninstall: uninstall-recursive + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-recursive +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + -test -z "$(CLEANFILES)" || rm -f $(CLEANFILES) + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + -test -z "$(DISTCLEANFILES)" || rm -f $(DISTCLEANFILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-recursive + +clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \ + clean-noinstPROGRAMS mostlyclean-am + +distclean: distclean-recursive + -rm -f ./$(DEPDIR)/libacl_la-async_callout_library.Plo + -rm -f ./$(DEPDIR)/libbcl_la-basic_callout_library.Plo + -rm -f ./$(DEPDIR)/libfcl_la-full_callout_library.Plo + -rm -f ./$(DEPDIR)/libfxl_la-framework_exception_library.Plo + -rm -f ./$(DEPDIR)/libivl_la-incorrect_version_library.Plo + -rm -f ./$(DEPDIR)/liblcl_la-load_callout_library.Plo + -rm -f ./$(DEPDIR)/liblecl_la-load_error_callout_library.Plo + -rm -f ./$(DEPDIR)/libnvl_la-no_version_library.Plo + -rm -f ./$(DEPDIR)/libpcl_la-callout_params_library.Plo + -rm -f ./$(DEPDIR)/libucl_la-unload_callout_library.Plo + -rm -f ./$(DEPDIR)/run_unittests-callout_handle_associate_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-callout_handle_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-callout_manager_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-handles_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-hooks_manager_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-library_manager_collection_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-library_manager_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-parking_lots_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-run_unittests.Po + -rm -f ./$(DEPDIR)/run_unittests-server_hooks_unittest.Po + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-recursive + +dvi-am: + +html: html-recursive + +html-am: + +info: info-recursive + +info-am: + +install-data-am: + +install-dvi: install-dvi-recursive + +install-dvi-am: + +install-exec-am: + +install-html: install-html-recursive + +install-html-am: + +install-info: install-info-recursive + +install-info-am: + +install-man: + +install-pdf: install-pdf-recursive + +install-pdf-am: + +install-ps: install-ps-recursive + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-recursive + -rm -f ./$(DEPDIR)/libacl_la-async_callout_library.Plo + -rm -f ./$(DEPDIR)/libbcl_la-basic_callout_library.Plo + -rm -f ./$(DEPDIR)/libfcl_la-full_callout_library.Plo + -rm -f ./$(DEPDIR)/libfxl_la-framework_exception_library.Plo + -rm -f ./$(DEPDIR)/libivl_la-incorrect_version_library.Plo + -rm -f ./$(DEPDIR)/liblcl_la-load_callout_library.Plo + -rm -f ./$(DEPDIR)/liblecl_la-load_error_callout_library.Plo + -rm -f ./$(DEPDIR)/libnvl_la-no_version_library.Plo + -rm -f ./$(DEPDIR)/libpcl_la-callout_params_library.Plo + -rm -f ./$(DEPDIR)/libucl_la-unload_callout_library.Plo + -rm -f ./$(DEPDIR)/run_unittests-callout_handle_associate_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-callout_handle_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-callout_manager_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-handles_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-hooks_manager_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-library_manager_collection_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-library_manager_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-parking_lots_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-run_unittests.Po + -rm -f ./$(DEPDIR)/run_unittests-server_hooks_unittest.Po + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-recursive + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-recursive + +pdf-am: + +ps: ps-recursive + +ps-am: + +uninstall-am: + +.MAKE: $(am__recursive_targets) check-am install-am install-strip + +.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am \ + am--depfiles check check-TESTS check-am clean clean-generic \ + clean-libtool clean-noinstLTLIBRARIES clean-noinstPROGRAMS \ + cscopelist-am ctags ctags-am distclean distclean-compile \ + distclean-generic distclean-libtool distclean-tags distdir dvi \ + dvi-am html html-am info info-am install install-am \ + install-data install-data-am install-dvi install-dvi-am \ + install-exec install-exec-am install-html install-html-am \ + install-info install-info-am install-man install-pdf \ + install-pdf-am install-ps install-ps-am install-strip \ + installcheck installcheck-am installdirs installdirs-am \ + maintainer-clean maintainer-clean-generic mostlyclean \ + mostlyclean-compile mostlyclean-generic mostlyclean-libtool \ + pdf pdf-am ps ps-am tags tags-am uninstall uninstall-am + +.PRECIOUS: Makefile + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/src/lib/hooks/tests/async_callout_library.cc b/src/lib/hooks/tests/async_callout_library.cc new file mode 100644 index 0000000..5f40e52 --- /dev/null +++ b/src/lib/hooks/tests/async_callout_library.cc @@ -0,0 +1,153 @@ +// Copyright (C) 2018 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +/// @file +/// @brief Async callout library +/// +/// This is source of a test library for testing a "parking" feature, i.e. +/// the callouts can schedule asynchronous operation and indicate that the +/// packet should be parked until the asynchronous operation completes and +/// the hooks library indicates that packet processing should be resumed. + +#include <config.h> +#include <hooks/hooks.h> +#include <hooks/parking_lots.h> +#include <log/logger.h> +#include <log/macros.h> +#include <log/message_initializer.h> +#include <algorithm> +#include <functional> +#include <sstream> +#include <string> +#include <vector> + +using namespace isc::hooks; +using namespace isc::log; +using namespace std; + +namespace { + +/// @brief Logger used by the library. +isc::log::Logger logger("acl"); + +/// @brief Log messages. +const char* log_messages[] = { + "ACL_LOAD_START", "async callout load %1", + "ACL_LOAD_END", "async callout load end", + "ACL_LOAD_END", "duplicate of async callout load end", + NULL +}; + +/// @brief Initializer for log messages. +const MessageInitializer message_initializer(log_messages); + +/// @brief Simple callback which unparks parked object. +/// +/// @param parking_lot parking lot where the object is parked. +/// @param parked_object parked object. +void unpark(ParkingLotHandlePtr parking_lot, const std::string& parked_object) { + parking_lot->unpark(parked_object); +} + +} // end of anonymous namespace + +extern "C" { + +/// @brief Callout scheduling object parking and providing function to unpark +/// it. +/// +/// This callout is crafted to test the following scenario. The callout returns +/// status "park" to indicate that the packet should be parked. The callout +/// performs asynchronous operation and indicates that the packet should be +/// unparked when this operation completes. Unparking the packet triggers a +/// function associated with the parked packet, e.g. a function which continues +/// processing of this packet. +/// +/// This test callout "parks" a string object instead of a packet. It assumes +/// that there might be multiple callouts installed on this hook point, which +/// all trigger asynchronous operation. The object must be unparked when the +/// last asynchronous operation completes. Therefore, it calls the @c reference +/// function on the parking lot object to increase the reference count. The +/// object remains parked as long as the reference counter is greater than +/// 0. +/// +/// The callout returns 1 or more pointers to the functions which should be +/// called by the unit tests to simulate completion of the asynchronous tasks. +/// When the test calls those functions, @c unpark function is called, which +/// decreases reference count on the parked object, or actually unparks the +/// object when the reference count reaches 0. +/// +/// @param handle Reference to callout handle used to set/get arguments. +int +hookpt_one(CalloutHandle& handle) { + // Using a string as "parked" object. + std::string parked_object; + handle.getArgument("parked_object", parked_object); + + // Retrieve the parking lot handle for this hook point. It allows for + // increasing a reference count on the parked object and also for + // scheduling packet unparking. + ParkingLotHandlePtr parking_lot = handle.getParkingLotHandlePtr(); + + // Increase the reference count to indicate that this callout needs the + // object to remain parked until the asynchronous operation completes. + // Otherwise, other callouts could potentially call unpark and cause the + // packet processing to continue before the asynchronous operation + // completes. + parking_lot->reference(parked_object); + + // Create pointer to the function that the test should call to simulate + // completion of the asynchronous operation scheduled by this callout. + std::function<void()> unpark_trigger_func = + std::bind(unpark, parking_lot, parked_object); + + // Every callout (if multiple callouts installed on this hook point) should + // return the function pointer under unique name. The base name is + // "unpark_trigger" and the callouts append consecutive numbers to this + // base name, e.g. "unpark_trigger1", "unpark_trigger2" etc. + + std::string fun_name; + std::vector<std::string> args = handle.getArgumentNames(); + unsigned i = 1; + do { + std::ostringstream candidate_name; + candidate_name << "unpark_trigger" << i; + if (std::find(args.begin(), args.end(), candidate_name.str()) == + args.end()) { + fun_name = candidate_name.str(); + + } else { + ++i; + } + } while (fun_name.empty()); + + handle.setArgument(fun_name, unpark_trigger_func); + + handle.setStatus(CalloutHandle::NEXT_STEP_PARK); + + return (0); +} + +// Framework functions. + +int +version() { + return (KEA_HOOKS_VERSION); +} + +// load() initializes the user library if the main image was statically linked. +int +load(isc::hooks::LibraryHandle&) { +#ifdef USE_STATIC_LINK + hooksStaticLinkInit(); +#endif + LOG_INFO(logger, "ACL_LOAD_START").arg("argument"); + LOG_INFO(logger, "ACL_LOAD_END"); + return (0); +} + +} + diff --git a/src/lib/hooks/tests/basic_callout_library.cc b/src/lib/hooks/tests/basic_callout_library.cc new file mode 100644 index 0000000..bd80555 --- /dev/null +++ b/src/lib/hooks/tests/basic_callout_library.cc @@ -0,0 +1,145 @@ +// Copyright (C) 2013-2015 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +/// @file +/// @brief Basic callout library +/// +/// This is source of a test library for various test (LibraryManager and +/// HooksManager). The characteristics of the library produced from this +/// file are: +/// +/// - Only the "version" framework function is supplied. +/// +/// - A context_create callout is supplied. +/// +/// - Three "standard" callouts are supplied corresponding to the hooks +/// "hookpt_one", "hookpt_two", "hookpt_three". All do some trivial +/// calculations on the arguments supplied to it and the context variables, +/// returning intermediate results through the "result" argument. The result +/// of executing all four callouts in order is: +/// +/// @f[ (10 + data_1) * data_2 - data_3 @f] +/// +/// ...where data_1, data_2 and data_3 are the values passed in arguments of +/// the same name to the three callouts (data_1 passed to hookpt_one, data_2 +/// to hookpt_two etc.) and the result is returned in the argument "result". +/// +/// - The logger instance is created and some log messages are defined. Some +/// log messages are duplicated purposely, to check that the logger handles +/// the duplicates correctly. + +#include <config.h> +#include <hooks/hooks.h> +#include <fstream> +#include <log/logger.h> +#include <log/macros.h> +#include <log/message_initializer.h> + +using namespace isc::hooks; +using namespace isc::log; +using namespace std; + +namespace { + +/// @brief Logger used by the library. +isc::log::Logger logger("bcl"); + +/// @brief Log messages. +const char* log_messages[] = { + "BCL_LOAD_START", "basic callout load %1", + "BCL_LOAD_END", "basic callout load end", + "BCL_LOAD_END", "duplicate of basic callout load end", + NULL +}; + +/// @brief Initializer for log messages. +const MessageInitializer message_initializer(log_messages); + +} + +extern "C" { + +// Callouts. All return their result through the "result" argument. + +int +context_create(CalloutHandle& handle) { + handle.setContext("result", static_cast<int>(10)); + handle.setArgument("result", static_cast<int>(10)); + return (0); +} + +// First callout adds the passed "data_1" argument to the initialized context +// value of 10. (Note that the value set by context_create is accessed through +// context and not the argument, so checking that context is correctly passed +// between callouts in the same library.) + +int +hookpt_one(CalloutHandle& handle) { + int data; + handle.getArgument("data_1", data); + + int result; + handle.getArgument("result", result); + + result += data; + handle.setArgument("result", result); + + return (0); +} + +// Second callout multiplies the current context value by the "data_2" +// argument. + +int +hookpt_two(CalloutHandle& handle) { + int data; + handle.getArgument("data_2", data); + + int result; + handle.getArgument("result", result); + + result *= data; + handle.setArgument("result", result); + + return (0); +} + +// Final callout subtracts the result in "data_3". + +int +hookpt_three(CalloutHandle& handle) { + int data; + handle.getArgument("data_3", data); + + int result; + handle.getArgument("result", result); + + result -= data; + handle.setArgument("result", result); + + return (0); +} + +// Framework functions. + +int +version() { + return (KEA_HOOKS_VERSION); +} + +// load() initializes the user library if the main image was statically linked. +int +load(isc::hooks::LibraryHandle&) { +#ifdef USE_STATIC_LINK + hooksStaticLinkInit(); +#endif + LOG_INFO(logger, "BCL_LOAD_START").arg("argument"); + LOG_INFO(logger, "BCL_LOAD_END"); + return (0); +} + +} + diff --git a/src/lib/hooks/tests/callout_handle_associate_unittest.cc b/src/lib/hooks/tests/callout_handle_associate_unittest.cc new file mode 100644 index 0000000..cf09388 --- /dev/null +++ b/src/lib/hooks/tests/callout_handle_associate_unittest.cc @@ -0,0 +1,35 @@ +// Copyright (C) 2018 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <hooks/callout_handle.h> +#include <hooks/callout_handle_associate.h> +#include <gtest/gtest.h> + +using namespace isc::hooks; + +namespace { + +// This test verifies that the callout handle can be created and +// retrieved from the CalloutHandleAssociate. +TEST(CalloutHandleAssociate, getCalloutHandle) { + CalloutHandleAssociate associate; + // The handle should be initialized and returned. + CalloutHandlePtr callout_handle = associate.getCalloutHandle(); + ASSERT_TRUE(callout_handle); + + // When calling the second time, the same handle should be returned. + CalloutHandlePtr callout_handle2 = associate.getCalloutHandle(); + EXPECT_TRUE(callout_handle == callout_handle2); + + // A different associate should produce a different handle. + CalloutHandleAssociate associate2; + callout_handle2 = associate2.getCalloutHandle(); + EXPECT_FALSE(callout_handle == callout_handle2); +} + +} // end of anonymous namespace diff --git a/src/lib/hooks/tests/callout_handle_unittest.cc b/src/lib/hooks/tests/callout_handle_unittest.cc new file mode 100644 index 0000000..bb20e0c --- /dev/null +++ b/src/lib/hooks/tests/callout_handle_unittest.cc @@ -0,0 +1,384 @@ +// Copyright (C) 2013-2020 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <hooks/callout_handle.h> +#include <hooks/callout_manager.h> +#include <hooks/library_handle.h> +#include <hooks/server_hooks.h> + +#include <boost/shared_ptr.hpp> + +#include <gtest/gtest.h> + +using namespace isc::hooks; +using namespace std; + +namespace { + +/// @file +/// @brief Holds the CalloutHandle argument tests +/// +/// Additional testing of the CalloutHandle - together with the interaction +/// of the LibraryHandle - is done in the handles_unittests set of tests. + +class CalloutHandleTest : public ::testing::Test { +public: + + /// @brief Constructor + /// + /// Sets up a callout manager to be referenced by the CalloutHandle in + /// these tests. (The "4" for the number of libraries in the + /// CalloutManager is arbitrary - it is not used in these tests.) + CalloutHandleTest() : manager_(new CalloutManager(4)) + {} + + /// Obtain hook manager + boost::shared_ptr<CalloutManager>& getCalloutManager() { + return (manager_); + } + +private: + /// Callout manager accessed by this CalloutHandle. + boost::shared_ptr<CalloutManager> manager_; +}; + +// *** Argument Tests *** +// +// The first set of tests check that the CalloutHandle can store and retrieve +// arguments. These are very similar to the LibraryHandle context tests. + +// Test that we can store multiple values of the same type and that they +// are distinct. + +TEST_F(CalloutHandleTest, ArgumentDistinctSimpleType) { + CalloutHandle handle(getCalloutManager()); + + // Store and retrieve an int (random value). + int a = 42; + handle.setArgument("integer1", a); + EXPECT_EQ(42, a); + + int b = 0; + handle.getArgument("integer1", b); + EXPECT_EQ(42, b); + + // Add another integer (another random value). + int c = 142; + handle.setArgument("integer2", c); + EXPECT_EQ(142, c); + + int d = 0; + handle.getArgument("integer2", d); + EXPECT_EQ(142, d); + + // Add a short (random value). + short e = -81; + handle.setArgument("short", e); + EXPECT_EQ(-81, e); + + short f = 0; + handle.getArgument("short", f); + EXPECT_EQ(-81, f); +} + +// Test that trying to get an unknown argument throws an exception. + +TEST_F(CalloutHandleTest, ArgumentUnknownName) { + CalloutHandle handle(getCalloutManager()); + + // Set an integer + int a = 42; + handle.setArgument("integer1", a); + EXPECT_EQ(42, a); + + // Check we can retrieve it + int b = 0; + handle.getArgument("integer1", b); + EXPECT_EQ(42, b); + + // Check that getting an unknown name throws an exception. + int c = 0; + EXPECT_THROW(handle.getArgument("unknown", c), NoSuchArgument); +} + +// Test that trying to get an argument with an incorrect type throws an +// exception. + +TEST_F(CalloutHandleTest, ArgumentIncorrectType) { + CalloutHandle handle(getCalloutManager()); + + // Set an integer + int a = 42; + handle.setArgument("integer1", a); + EXPECT_EQ(42, a); + + // Check we can retrieve it + long b = 0; + EXPECT_THROW(handle.getArgument("integer1", b), boost::bad_any_cast); +} + +// Now try with some very complex types. The types cannot be defined within +// the function and they should contain a copy constructor. For this reason, +// a simple "struct" is used. + +struct Alpha { + int a; + int b; + Alpha(int first = 0, int second = 0) : a(first), b(second) {} +}; + +struct Beta { + int c; + int d; + Beta(int first = 0, int second = 0) : c(first), d(second) {} +}; + +TEST_F(CalloutHandleTest, ComplexTypes) { + CalloutHandle handle(getCalloutManager()); + + // Declare two variables of different (complex) types. (Note as to the + // variable names: aleph and beth are the first two letters of the Hebrew + // alphabet.) + Alpha aleph(1, 2); + EXPECT_EQ(1, aleph.a); + EXPECT_EQ(2, aleph.b); + handle.setArgument("aleph", aleph); + + Beta beth(11, 22); + EXPECT_EQ(11, beth.c); + EXPECT_EQ(22, beth.d); + handle.setArgument("beth", beth); + + // Ensure we can extract the data correctly. + Alpha aleph2; + EXPECT_EQ(0, aleph2.a); + EXPECT_EQ(0, aleph2.b); + handle.getArgument("aleph", aleph2); + EXPECT_EQ(1, aleph2.a); + EXPECT_EQ(2, aleph2.b); + + Beta beth2; + EXPECT_EQ(0, beth2.c); + EXPECT_EQ(0, beth2.d); + handle.getArgument("beth", beth2); + EXPECT_EQ(11, beth2.c); + EXPECT_EQ(22, beth2.d); + + // Ensure that complex types also thrown an exception if we attempt to + // get a context element of the wrong type. + EXPECT_THROW(handle.getArgument("aleph", beth), boost::bad_any_cast); +} + +// Check that the context can store pointers. And also check that it respects +// that a "pointer to X" is not the same as a "pointer to const X". + +TEST_F(CalloutHandleTest, PointerTypes) { + CalloutHandle handle(getCalloutManager()); + + // Declare a couple of variables, const and non-const. + Alpha aleph(5, 10); + const Beta beth(15, 20); + + Alpha* pa = ℵ + const Beta* pcb = ℶ + + // Check pointers can be set and retrieved OK. + handle.setArgument("non_const_pointer", pa); + handle.setArgument("const_pointer", pcb); + + Alpha* pa2 = 0; + handle.getArgument("non_const_pointer", pa2); + EXPECT_TRUE(pa == pa2); + + const Beta* pcb2 = 0; + handle.getArgument("const_pointer", pcb2); + EXPECT_TRUE(pcb == pcb2); + + // Check that the "const" is protected in the context. + const Alpha* pca3; + EXPECT_THROW(handle.getArgument("non_const_pointer", pca3), + boost::bad_any_cast); + + Beta* pb3; + EXPECT_THROW(handle.getArgument("const_pointer", pb3), + boost::bad_any_cast); +} + +// Check that we can get the names of the arguments. + +TEST_F(CalloutHandleTest, ContextItemNames) { + CalloutHandle handle(getCalloutManager()); + + vector<string> expected_names; + + expected_names.push_back("faith"); + handle.setArgument("faith", 42); + expected_names.push_back("hope"); + handle.setArgument("hope", 43); + expected_names.push_back("charity"); + handle.setArgument("charity", 44); + + // Get the names and check against the expected names. We'll sort + // both arrays to simplify the checking. + vector<string> actual_names = handle.getArgumentNames(); + + sort(actual_names.begin(), actual_names.end()); + sort(expected_names.begin(), expected_names.end()); + EXPECT_TRUE(expected_names == actual_names); +} + +// Test that we can delete an argument. + +TEST_F(CalloutHandleTest, DeleteArgument) { + CalloutHandle handle(getCalloutManager()); + + int one = 1; + int two = 2; + int three = 3; + int four = 4; + int value; // Return value + + handle.setArgument("one", one); + handle.setArgument("two", two); + handle.setArgument("three", three); + handle.setArgument("four", four); + + // Delete "one". + handle.getArgument("one", value); + EXPECT_EQ(1, value); + handle.deleteArgument("one"); + + EXPECT_THROW(handle.getArgument("one", value), NoSuchArgument); + handle.getArgument("two", value); + EXPECT_EQ(2, value); + handle.getArgument("three", value); + EXPECT_EQ(3, value); + handle.getArgument("four", value); + EXPECT_EQ(4, value); + + // Delete "three". + handle.getArgument("three", value); + EXPECT_EQ(3, value); + handle.deleteArgument("three"); + + EXPECT_THROW(handle.getArgument("one", value), NoSuchArgument); + handle.getArgument("two", value); + EXPECT_EQ(2, value); + EXPECT_THROW(handle.getArgument("three", value), NoSuchArgument); + handle.getArgument("four", value); + EXPECT_EQ(4, value); +} + +// Test that we can delete all arguments. + +TEST_F(CalloutHandleTest, DeleteAllArguments) { + CalloutHandle handle(getCalloutManager()); + + int one = 1; + int two = 2; + int three = 3; + int four = 4; + int value; // Return value + + // Set the arguments. The previous test verifies that this works. + handle.setArgument("one", one); + handle.setArgument("two", two); + handle.setArgument("three", three); + handle.setArgument("four", four); + + // Delete all arguments... + handle.deleteAllArguments(); + + // ... and check that none are left. + EXPECT_THROW(handle.getArgument("one", value), NoSuchArgument); + EXPECT_THROW(handle.getArgument("two", value), NoSuchArgument); + EXPECT_THROW(handle.getArgument("three", value), NoSuchArgument); + EXPECT_THROW(handle.getArgument("four", value), NoSuchArgument); +} + +// Test the "status" field. +TEST_F(CalloutHandleTest, StatusField) { + CalloutHandle handle(getCalloutManager()); + + // Should be continue on construction. + EXPECT_EQ(CalloutHandle::NEXT_STEP_CONTINUE, handle.getStatus()); + + handle.setStatus(CalloutHandle::NEXT_STEP_SKIP); + EXPECT_EQ(CalloutHandle::NEXT_STEP_SKIP, handle.getStatus()); + + handle.setStatus(CalloutHandle::NEXT_STEP_DROP); + EXPECT_EQ(CalloutHandle::NEXT_STEP_DROP, handle.getStatus()); + + handle.setStatus(CalloutHandle::NEXT_STEP_CONTINUE); + EXPECT_EQ(CalloutHandle::NEXT_STEP_CONTINUE, handle.getStatus()); +} + +// Tests that ScopedCalloutHandleState object resets CalloutHandle state +// during construction and destruction. +TEST_F(CalloutHandleTest, scopedState) { + // Create pointer to the handle to be wrapped. + CalloutHandlePtr handle(new CalloutHandle(getCalloutManager())); + + // Set two arguments and the non-default status. + int one = 1; + int two = 2; + int three = 3; + handle->setArgument("one", one); + handle->setArgument("two", two); + handle->setContext("three", three); + handle->setStatus(CalloutHandle::NEXT_STEP_DROP); + + + int value = 0; + EXPECT_NO_THROW(handle->getArgument("one", value)); + EXPECT_NO_THROW(handle->getArgument("two", value)); + EXPECT_NO_THROW(handle->getContext("three", value)); + EXPECT_EQ(CalloutHandle::NEXT_STEP_DROP, handle->getStatus()); + + { + // Wrap the callout handle with the scoped state object, which should + // reset the state of the handle. + ScopedCalloutHandleState scoped_state(handle); + + // When state is reset, all arguments should be removed and the + // default status should be set. + EXPECT_THROW(handle->getArgument("one", value), NoSuchArgument); + EXPECT_THROW(handle->getArgument("two", value), NoSuchArgument); + EXPECT_EQ(CalloutHandle::NEXT_STEP_CONTINUE, handle->getStatus()); + + // Context should be intact. + ASSERT_NO_THROW(handle->getContext("three", value)); + EXPECT_EQ(three, value); + + // Set the arguments and status again prior to the destruction of + // the wrapper. + handle->setArgument("one", one); + handle->setArgument("two", two); + handle->setStatus(CalloutHandle::NEXT_STEP_DROP); + + EXPECT_NO_THROW(handle->getArgument("one", value)); + EXPECT_NO_THROW(handle->getArgument("two", value)); + EXPECT_EQ(CalloutHandle::NEXT_STEP_DROP, handle->getStatus()); + } + + // Arguments should be gone again and the status should be set to + // a default value. + EXPECT_THROW(handle->getArgument("one", value), NoSuchArgument); + EXPECT_THROW(handle->getArgument("two", value), NoSuchArgument); + EXPECT_EQ(CalloutHandle::NEXT_STEP_CONTINUE, handle->getStatus()); + + // Context should be intact. + ASSERT_NO_THROW(handle->getContext("three", value)); + EXPECT_EQ(three, value); +} + +// Further tests of the "skip" flag and tests of getting the name of the +// hook to which the current callout is attached is in the "handles_unittest" +// module. + +} // Anonymous namespace diff --git a/src/lib/hooks/tests/callout_manager_unittest.cc b/src/lib/hooks/tests/callout_manager_unittest.cc new file mode 100644 index 0000000..b487a36 --- /dev/null +++ b/src/lib/hooks/tests/callout_manager_unittest.cc @@ -0,0 +1,877 @@ +// Copyright (C) 2013-2020 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <exceptions/exceptions.h> +#include <hooks/callout_handle.h> +#include <hooks/callout_manager.h> +#include <hooks/library_handle.h> +#include <hooks/server_hooks.h> + +#include <boost/scoped_ptr.hpp> +#include <gtest/gtest.h> + +#include <algorithm> +#include <climits> +#include <string> +#include <vector> + +/// @file +/// @brief CalloutManager and LibraryHandle tests +/// +/// These set of tests check the CalloutManager and LibraryHandle. They are +/// together in the same file because the LibraryHandle is little more than a +/// restricted interface to the CalloutManager, and a lot of the support +/// structure for the tests is common. + +using namespace isc; +using namespace isc::hooks; +using namespace std; + +namespace { + +class CalloutManagerTest : public ::testing::Test { +public: + /// @brief Constructor + /// + /// Sets up a collection of three LibraryHandle objects to use in the test. + CalloutManagerTest() { + + // Set up the server hooks. There is one singleton for all tests, + // so reset it and explicitly set up the hooks for the test. + ServerHooks& hooks = ServerHooks::getServerHooks(); + hooks.reset(); + alpha_index_ = hooks.registerHook("alpha"); + beta_index_ = hooks.registerHook("beta"); + gamma_index_ = hooks.registerHook("gamma"); + delta_index_ = hooks.registerHook("delta"); + + // Set up the callout manager with these hooks. Assume a maximum of + // four libraries. + callout_manager_.reset(new CalloutManager(10)); + + // Set up the callout handle. + callout_handle_.reset(new CalloutHandle(callout_manager_)); + + // Initialize the static variable. + callout_value_ = 0; + } + + /// @brief Return the callout handle + CalloutHandle& getCalloutHandle() { + return (*callout_handle_); + } + + /// @brief Return the callout manager + boost::shared_ptr<CalloutManager> getCalloutManager() { + return (callout_manager_); + } + + /// Static variable used for accumulating information + static int callout_value_; + + /// Hook indexes. These are somewhat ubiquitous, so are made public for + /// ease of reference instead of being accessible by a function. + int alpha_index_; + int beta_index_; + int gamma_index_; + int delta_index_; + +private: + /// Callout handle used in calls + boost::shared_ptr<CalloutHandle> callout_handle_; + + /// Callout manager used for the test + boost::shared_ptr<CalloutManager> callout_manager_; +}; + +// Definition of the static variable. +int CalloutManagerTest::callout_value_ = 0; + +// Callout definitions +// +// The callouts defined here are structured in such a way that it is possible +// to determine the order in which they are called and whether they are called +// at all. The method used is simple - after a sequence of callouts, the digits +// in the value, reading left to right, determines the order of the callouts +// called. For example, callout one followed by two followed by three followed +// by two followed by one results in a value of 12321. +// +// Functions return a zero to indicate success. + +extern "C" { +int callout_general(int number) { + CalloutManagerTest::callout_value_ = + 10 * CalloutManagerTest::callout_value_ + number; + return (0); +} + +int callout_one(CalloutHandle&) { + return (callout_general(1)); +} + +int callout_two(CalloutHandle&) { + return (callout_general(2)); +} + +int callout_three(CalloutHandle&) { + return (callout_general(3)); +} + +int callout_four(CalloutHandle&) { + return (callout_general(4)); +} + +int callout_five(CalloutHandle&) { + return (callout_general(5)); +} + +int callout_six(CalloutHandle&) { + return (callout_general(6)); +} + +int callout_seven(CalloutHandle&) { + return (callout_general(7)); +} + +// The next functions are duplicates of some of the above, but return an error. + +int callout_one_error(CalloutHandle& handle) { + (void) callout_one(handle); + return (1); +} + +int callout_two_error(CalloutHandle& handle) { + (void) callout_two(handle); + return (1); +} + +int callout_three_error(CalloutHandle& handle) { + (void) callout_three(handle); + return (1); +} + +int callout_four_error(CalloutHandle& handle) { + (void) callout_four(handle); + return (1); +} + +}; // extern "C" + +// *** Callout Tests *** +// +// The next set of tests check that callouts can be called. + +// Constructor - check that we trap bad parameters. + +TEST_F(CalloutManagerTest, BadConstructorParameters) { + boost::scoped_ptr<CalloutManager> cm; + + // Invalid number of libraries + EXPECT_THROW(cm.reset(new CalloutManager(-1)), BadValue); +} + +// Check the number of libraries is reported successfully. + +TEST_F(CalloutManagerTest, NumberOfLibraries) { + boost::scoped_ptr<CalloutManager> cm; + + // Check two valid values of number of libraries to ensure that the + // GetNumLibraries() returns the value set. + EXPECT_NO_THROW(cm.reset(new CalloutManager())); + EXPECT_EQ(0, cm->getNumLibraries()); + + EXPECT_NO_THROW(cm.reset(new CalloutManager(0))); + EXPECT_EQ(0, cm->getNumLibraries()); + + EXPECT_NO_THROW(cm.reset(new CalloutManager(4))); + EXPECT_EQ(4, cm->getNumLibraries()); + + EXPECT_NO_THROW(cm.reset(new CalloutManager(42))); + EXPECT_EQ(42, cm->getNumLibraries()); +} + +// Check that we can only set the current library index to the correct values. + +TEST_F(CalloutManagerTest, CheckLibraryIndex) { + // Check valid indexes. As the callout manager is sized for 10 libraries, + // we expect: + // + // -1 to be valid as it is the standard "invalid" value. + // 0 to be valid for the pre-user library callouts + // 1-10 to be valid for the user-library callouts + // INT_MAX to be valid for the post-user library callouts + // + // All other values to be invalid. + for (int i = -1; i < 11; ++i) { + EXPECT_NO_THROW(getCalloutManager()->setLibraryIndex(i)); + EXPECT_EQ(i, getCalloutManager()->getLibraryIndex()); + } + EXPECT_NO_THROW(getCalloutManager()->setLibraryIndex(INT_MAX)); + EXPECT_EQ(INT_MAX, getCalloutManager()->getLibraryIndex()); + + // Check invalid ones + EXPECT_THROW(getCalloutManager()->setLibraryIndex(-2), NoSuchLibrary); + EXPECT_THROW(getCalloutManager()->setLibraryIndex(11), NoSuchLibrary); +} + +// Check that we can only register callouts on valid hook names. + +TEST_F(CalloutManagerTest, ValidHookNames) { + EXPECT_NO_THROW(getCalloutManager()->registerCallout("alpha", callout_one, 0)); + EXPECT_THROW(getCalloutManager()->registerCallout("unknown", callout_one, 0), + NoSuchHook); +} + + +// Check we can register callouts appropriately. + +TEST_F(CalloutManagerTest, RegisterCallout) { + // Ensure that no callouts are attached to any of the hooks. + EXPECT_FALSE(getCalloutManager()->calloutsPresent(alpha_index_)); + EXPECT_FALSE(getCalloutManager()->calloutsPresent(beta_index_)); + + // Set up so that hooks "alpha" and "beta" have callouts attached from a + // different libraries. + getCalloutManager()->registerCallout("alpha", callout_one, 0); + getCalloutManager()->registerCallout("beta", callout_two, 1); + + // Check all is as expected. + EXPECT_TRUE(getCalloutManager()->calloutsPresent(alpha_index_)); + EXPECT_TRUE(getCalloutManager()->calloutsPresent(beta_index_)); + EXPECT_FALSE(getCalloutManager()->calloutsPresent(gamma_index_)); + EXPECT_FALSE(getCalloutManager()->calloutsPresent(delta_index_)); + + // Check that calling the callouts returns as expected. (This is also a + // test of the callCallouts method.) + callout_value_ = 0; + getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle()); + EXPECT_EQ(1, callout_value_); + + callout_value_ = 0; + getCalloutManager()->callCallouts(beta_index_, getCalloutHandle()); + EXPECT_EQ(2, callout_value_); + + // Register some more callouts from different libraries on hook "alpha". + getCalloutManager()->registerCallout("alpha", callout_three, 2); + getCalloutManager()->registerCallout("alpha", callout_four, 2); + getCalloutManager()->registerCallout("alpha", callout_five, 3); + + // Check it is as expected. + callout_value_ = 0; + getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle()); + EXPECT_EQ(1345, callout_value_); + + // ... and check the additional callouts were not registered on the "beta" + // hook. + callout_value_ = 0; + getCalloutManager()->callCallouts(beta_index_, getCalloutHandle()); + EXPECT_EQ(2, callout_value_); + + // Add another callout to hook "alpha" from library index 2 - this should + // appear at the end of the callout list for that library. + getCalloutManager()->registerCallout("alpha", callout_six, 2); + callout_value_ = 0; + getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle()); + EXPECT_EQ(13465, callout_value_); + + // Add a callout from library index 1 - this should appear between the + // callouts from library index 0 and library index 2. + getCalloutManager()->registerCallout("alpha", callout_seven, 1); + callout_value_ = 0; + getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle()); + EXPECT_EQ(173465, callout_value_); +} + +// Check the "calloutsPresent()" method. + +TEST_F(CalloutManagerTest, CalloutsPresent) { + // Ensure that no callouts are attached to any of the hooks. + EXPECT_FALSE(getCalloutManager()->calloutsPresent(alpha_index_)); + EXPECT_FALSE(getCalloutManager()->calloutsPresent(beta_index_)); + EXPECT_FALSE(getCalloutManager()->calloutsPresent(gamma_index_)); + EXPECT_FALSE(getCalloutManager()->calloutsPresent(delta_index_)); + + // Set up so that hooks "alpha", "beta" and "delta" have callouts attached + // to them, and callout "gamma" does not. (In the statements below, the + // exact callouts attached to a hook are not relevant - only the fact + // that some callouts are). Chose the libraries for which the callouts + // are registered randomly. + getCalloutManager()->registerCallout("alpha", callout_one, 0); + getCalloutManager()->registerCallout("alpha", callout_two, 1); + getCalloutManager()->registerCallout("beta", callout_two, 1); + getCalloutManager()->registerCallout("alpha", callout_three, 3); + getCalloutManager()->registerCallout("delta", callout_four, 3); + + // Check all is as expected. + EXPECT_TRUE(getCalloutManager()->calloutsPresent(alpha_index_)); + EXPECT_TRUE(getCalloutManager()->calloutsPresent(beta_index_)); + EXPECT_FALSE(getCalloutManager()->calloutsPresent(gamma_index_)); + EXPECT_TRUE(getCalloutManager()->calloutsPresent(delta_index_)); + + // Check we fail on an invalid hook index. + EXPECT_THROW(getCalloutManager()->calloutsPresent(42), NoSuchHook); + EXPECT_THROW(getCalloutManager()->calloutsPresent(-1), NoSuchHook); +} + +// Test that calling a hook with no callouts on it returns success. + +TEST_F(CalloutManagerTest, CallNoCallouts) { + // Ensure that no callouts are attached to any of the hooks. + EXPECT_FALSE(getCalloutManager()->calloutsPresent(alpha_index_)); + EXPECT_FALSE(getCalloutManager()->calloutsPresent(beta_index_)); + EXPECT_FALSE(getCalloutManager()->calloutsPresent(gamma_index_)); + EXPECT_FALSE(getCalloutManager()->calloutsPresent(delta_index_)); + + // Call the callouts on an arbitrary hook and ensure that nothing happens. + callout_value_ = 475; + getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle()); + EXPECT_EQ(475, callout_value_); // Unchanged +} + +// Test that the callouts are called in the correct order (i.e. the callouts +// from the first library in the order they were registered, then the callouts +// from the second library in the order they were registered etc.) + +TEST_F(CalloutManagerTest, CallCalloutsSuccess) { + // Ensure that no callouts are attached to any of the hooks. + EXPECT_FALSE(getCalloutManager()->calloutsPresent(alpha_index_)); + EXPECT_FALSE(getCalloutManager()->calloutsPresent(beta_index_)); + EXPECT_FALSE(getCalloutManager()->calloutsPresent(gamma_index_)); + EXPECT_FALSE(getCalloutManager()->calloutsPresent(delta_index_)); + + // Each library contributes one callout on hook "alpha". + callout_value_ = 0; + getCalloutManager()->registerCallout("alpha", callout_one, 1); + getCalloutManager()->registerCallout("alpha", callout_two, 1); + getCalloutManager()->registerCallout("alpha", callout_three, 2); + getCalloutManager()->registerCallout("alpha", callout_four, 3); + getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle()); + EXPECT_EQ(1234, callout_value_); + + // Do a random selection of callouts on hook "beta". + callout_value_ = 0; + getCalloutManager()->registerCallout("beta", callout_one, 0); + getCalloutManager()->registerCallout("beta", callout_three, 0); + getCalloutManager()->registerCallout("beta", callout_two, 1); + getCalloutManager()->registerCallout("beta", callout_four, 3); + getCalloutManager()->callCallouts(beta_index_, getCalloutHandle()); + EXPECT_EQ(1324, callout_value_); + + // Ensure that calling the callouts on a hook with no callouts works. + callout_value_ = 0; + getCalloutManager()->callCallouts(gamma_index_, getCalloutHandle()); + EXPECT_EQ(0, callout_value_); +} + +// Test that the callouts are called in order, but that callouts occurring +// after a callout that returns an error are not called. +// +// (Note: in this test, the callouts that return an error set the value of +// callout_value_ before they return the error code.) + +TEST_F(CalloutManagerTest, CallCalloutsError) { + // Ensure that no callouts are attached to any of the hooks. + EXPECT_FALSE(getCalloutManager()->calloutsPresent(alpha_index_)); + EXPECT_FALSE(getCalloutManager()->calloutsPresent(beta_index_)); + EXPECT_FALSE(getCalloutManager()->calloutsPresent(gamma_index_)); + EXPECT_FALSE(getCalloutManager()->calloutsPresent(delta_index_)); + + // Each library contributing one callout on hook "alpha". The first callout + // returns an error (after adding its value to the result). + callout_value_ = 0; + getCalloutManager()->registerCallout("alpha", callout_one_error, 0); + getCalloutManager()->registerCallout("alpha", callout_two, 1); + getCalloutManager()->registerCallout("alpha", callout_three, 2); + getCalloutManager()->registerCallout("alpha", callout_four, 3); + getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle()); + EXPECT_EQ(1234, callout_value_); + + // Each library contributing multiple callouts on hook "beta". The last + // callout on the first library returns an error. + callout_value_ = 0; + getCalloutManager()->registerCallout("beta", callout_one, 0); + getCalloutManager()->registerCallout("beta", callout_one_error, 0); + getCalloutManager()->registerCallout("beta", callout_two, 1); + getCalloutManager()->registerCallout("beta", callout_two, 1); + getCalloutManager()->registerCallout("beta", callout_three, 1); + getCalloutManager()->registerCallout("beta", callout_three, 1); + getCalloutManager()->registerCallout("beta", callout_four, 3); + getCalloutManager()->registerCallout("beta", callout_four, 3); + getCalloutManager()->callCallouts(beta_index_, getCalloutHandle()); + EXPECT_EQ(11223344, callout_value_); + + // A callout in a random position in the callout list returns an error. + callout_value_ = 0; + getCalloutManager()->registerCallout("gamma", callout_one, 0); + getCalloutManager()->registerCallout("gamma", callout_one, 0); + getCalloutManager()->registerCallout("gamma", callout_two, 1); + getCalloutManager()->registerCallout("gamma", callout_two, 1); + getCalloutManager()->registerCallout("gamma", callout_four_error, 3); + getCalloutManager()->registerCallout("gamma", callout_four, 3); + getCalloutManager()->callCallouts(gamma_index_, getCalloutHandle()); + EXPECT_EQ(112244, callout_value_); + + // The last callout on a hook returns an error. + callout_value_ = 0; + getCalloutManager()->registerCallout("delta", callout_one, 0); + getCalloutManager()->registerCallout("delta", callout_one, 0); + getCalloutManager()->registerCallout("delta", callout_two, 1); + getCalloutManager()->registerCallout("delta", callout_two, 1); + getCalloutManager()->registerCallout("delta", callout_three, 2); + getCalloutManager()->registerCallout("delta", callout_three, 2); + getCalloutManager()->registerCallout("delta", callout_four, 3); + getCalloutManager()->registerCallout("delta", callout_four_error, 3); + getCalloutManager()->callCallouts(delta_index_, getCalloutHandle()); + EXPECT_EQ(11223344, callout_value_); +} + +// Now test that we can deregister a single callout on a hook. + +TEST_F(CalloutManagerTest, DeregisterSingleCallout) { + // Ensure that no callouts are attached to any of the hooks. + EXPECT_FALSE(getCalloutManager()->calloutsPresent(alpha_index_)); + EXPECT_FALSE(getCalloutManager()->calloutsPresent(beta_index_)); + EXPECT_FALSE(getCalloutManager()->calloutsPresent(gamma_index_)); + EXPECT_FALSE(getCalloutManager()->calloutsPresent(delta_index_)); + + // Add a callout to hook "alpha" and check it is added correctly. + callout_value_ = 0; + getCalloutManager()->registerCallout("alpha", callout_two, 0); + getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle()); + EXPECT_EQ(2, callout_value_); + + // Remove it and check that the no callouts are present. We have to reset + // the current library index here as it was invalidated by the call + // to callCallouts(). + EXPECT_TRUE(getCalloutManager()->calloutsPresent(alpha_index_)); + EXPECT_TRUE(getCalloutManager()->deregisterCallout("alpha", callout_two, 0)); + EXPECT_FALSE(getCalloutManager()->calloutsPresent(alpha_index_)); +} + +// Now test that we can deregister a single callout on a hook that has multiple +// callouts from the same library. + +TEST_F(CalloutManagerTest, DeregisterSingleCalloutSameLibrary) { + // Ensure that no callouts are attached to any of the hooks. + EXPECT_FALSE(getCalloutManager()->calloutsPresent(alpha_index_)); + EXPECT_FALSE(getCalloutManager()->calloutsPresent(beta_index_)); + EXPECT_FALSE(getCalloutManager()->calloutsPresent(gamma_index_)); + EXPECT_FALSE(getCalloutManager()->calloutsPresent(delta_index_)); + + // Add multiple callouts to hook "alpha". + callout_value_ = 0; + getCalloutManager()->registerCallout("alpha", callout_one, 0); + getCalloutManager()->registerCallout("alpha", callout_two, 0); + getCalloutManager()->registerCallout("alpha", callout_three, 0); + getCalloutManager()->registerCallout("alpha", callout_four, 0); + getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle()); + EXPECT_EQ(1234, callout_value_); + + // Remove the callout_two callout. We have to reset the current library + // index here as it was invalidated by the call to callCallouts(). + EXPECT_TRUE(getCalloutManager()->deregisterCallout("alpha", callout_two, 0)); + callout_value_ = 0; + getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle()); + EXPECT_EQ(134, callout_value_); + + // Try removing it again. + EXPECT_FALSE(getCalloutManager()->deregisterCallout("alpha", callout_two, 0)); + callout_value_ = 0; + getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle()); + EXPECT_EQ(134, callout_value_); + +} + +// Check we can deregister multiple callouts from the same library. + +TEST_F(CalloutManagerTest, DeregisterMultipleCalloutsSameLibrary) { + // Ensure that no callouts are attached to any of the hooks. + EXPECT_FALSE(getCalloutManager()->calloutsPresent(alpha_index_)); + EXPECT_FALSE(getCalloutManager()->calloutsPresent(beta_index_)); + EXPECT_FALSE(getCalloutManager()->calloutsPresent(gamma_index_)); + EXPECT_FALSE(getCalloutManager()->calloutsPresent(delta_index_)); + + // Each library contributes one callout on hook "alpha". + callout_value_ = 0; + getCalloutManager()->registerCallout("alpha", callout_one, 0); + getCalloutManager()->registerCallout("alpha", callout_two, 0); + getCalloutManager()->registerCallout("alpha", callout_one, 0); + getCalloutManager()->registerCallout("alpha", callout_two, 0); + getCalloutManager()->registerCallout("alpha", callout_three, 0); + getCalloutManager()->registerCallout("alpha", callout_four, 0); + getCalloutManager()->registerCallout("alpha", callout_three, 0); + getCalloutManager()->registerCallout("alpha", callout_four, 0); + getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle()); + EXPECT_EQ(12123434, callout_value_); + + // Remove the callout_two callouts. We have to reset the current library + // index here as it was invalidated by the call to callCallouts(). + EXPECT_TRUE(getCalloutManager()->deregisterCallout("alpha", callout_two, 0)); + callout_value_ = 0; + getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle()); + EXPECT_EQ(113434, callout_value_); + + // Try removing multiple callouts that includes one at the end of the + // list of callouts. + EXPECT_TRUE(getCalloutManager()->deregisterCallout("alpha", callout_four, 0)); + callout_value_ = 0; + getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle()); + EXPECT_EQ(1133, callout_value_); + + // ... and from the start. + EXPECT_TRUE(getCalloutManager()->deregisterCallout("alpha", callout_one, 0)); + callout_value_ = 0; + getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle()); + EXPECT_EQ(33, callout_value_); + + // ... and the remaining callouts. + EXPECT_TRUE(getCalloutManager()->deregisterCallout("alpha", callout_three, 0)); + callout_value_ = 0; + getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle()); + EXPECT_EQ(0, callout_value_); + + EXPECT_FALSE(getCalloutManager()->calloutsPresent(alpha_index_)); +} + +// Check we can deregister multiple callouts from multiple libraries. + +TEST_F(CalloutManagerTest, DeregisterMultipleCalloutsMultipleLibraries) { + // Ensure that no callouts are attached to any of the hooks. + EXPECT_FALSE(getCalloutManager()->calloutsPresent(alpha_index_)); + EXPECT_FALSE(getCalloutManager()->calloutsPresent(beta_index_)); + EXPECT_FALSE(getCalloutManager()->calloutsPresent(gamma_index_)); + EXPECT_FALSE(getCalloutManager()->calloutsPresent(delta_index_)); + + // Each library contributes two callouts to hook "alpha". + callout_value_ = 0; + getCalloutManager()->registerCallout("alpha", callout_one, 0); + getCalloutManager()->registerCallout("alpha", callout_two, 0); + getCalloutManager()->registerCallout("alpha", callout_three, 1); + getCalloutManager()->registerCallout("alpha", callout_four, 1); + getCalloutManager()->registerCallout("alpha", callout_five, 2); + getCalloutManager()->registerCallout("alpha", callout_two, 2); + getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle()); + EXPECT_EQ(123452, callout_value_); + + // Remove the callout_two callout from library 0. It should not affect + // the second callout_two callout registered by library 2. + EXPECT_TRUE(getCalloutManager()->deregisterCallout("alpha", callout_two, 0)); + callout_value_ = 0; + getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle()); + EXPECT_EQ(13452, callout_value_); +} + +// Check we can deregister all callouts from a single library. + +TEST_F(CalloutManagerTest, DeregisterAllCallouts) { + // Ensure that no callouts are attached to hook one. + EXPECT_FALSE(getCalloutManager()->calloutsPresent(alpha_index_)); + + // Each library contributes two callouts to hook "alpha". + callout_value_ = 0; + getCalloutManager()->registerCallout("alpha", callout_one, 0); + getCalloutManager()->registerCallout("alpha", callout_two, 0); + getCalloutManager()->registerCallout("alpha", callout_three, 1); + getCalloutManager()->registerCallout("alpha", callout_four, 1); + getCalloutManager()->registerCallout("alpha", callout_five, 2); + getCalloutManager()->registerCallout("alpha", callout_six, 2); + getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle()); + EXPECT_EQ(123456, callout_value_); + + // Remove all callouts from library index 1. + EXPECT_TRUE(getCalloutManager()->deregisterAllCallouts("alpha", 1)); + callout_value_ = 0; + getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle()); + EXPECT_EQ(1256, callout_value_); + + // Remove all callouts from library index 2. + EXPECT_TRUE(getCalloutManager()->deregisterAllCallouts("alpha", 2)); + callout_value_ = 0; + getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle()); + EXPECT_EQ(12, callout_value_); +} + +// Check that we can register/deregister callouts on different libraries +// and different hooks, and that the callout instances are regarded as +// unique and do not affect one another. + +TEST_F(CalloutManagerTest, MultipleCalloutsLibrariesHooks) { + // Ensure that no callouts are attached to any of the hooks. + EXPECT_FALSE(getCalloutManager()->calloutsPresent(alpha_index_)); + EXPECT_FALSE(getCalloutManager()->calloutsPresent(beta_index_)); + EXPECT_FALSE(getCalloutManager()->calloutsPresent(gamma_index_)); + EXPECT_FALSE(getCalloutManager()->calloutsPresent(delta_index_)); + + // Register callouts on the alpha hook. + callout_value_ = 0; + getCalloutManager()->registerCallout("alpha", callout_one, 0); + getCalloutManager()->registerCallout("alpha", callout_two, 0); + getCalloutManager()->registerCallout("alpha", callout_three, 1); + getCalloutManager()->registerCallout("alpha", callout_four, 1); + getCalloutManager()->registerCallout("alpha", callout_five, 2); + getCalloutManager()->registerCallout("alpha", callout_two, 2); + getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle()); + EXPECT_EQ(123452, callout_value_); + + // Register the same callouts on the beta hook, and check that those + // on the alpha hook are not affected. + callout_value_ = 0; + getCalloutManager()->registerCallout("beta", callout_five, 0); + getCalloutManager()->registerCallout("beta", callout_one, 0); + getCalloutManager()->registerCallout("beta", callout_four, 2); + getCalloutManager()->registerCallout("beta", callout_three, 2); + getCalloutManager()->callCallouts(beta_index_, getCalloutHandle()); + EXPECT_EQ(5143, callout_value_); + + // Check that the order of callouts on the alpha hook has not been affected. + callout_value_ = 0; + getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle()); + EXPECT_EQ(123452, callout_value_); + + // Remove callout four from beta and check that alpha is not affected. + EXPECT_TRUE(getCalloutManager()->deregisterCallout("beta", callout_four, 2)); + + callout_value_ = 0; + getCalloutManager()->callCallouts(beta_index_, getCalloutHandle()); + EXPECT_EQ(513, callout_value_); + + callout_value_ = 0; + getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle()); + EXPECT_EQ(123452, callout_value_); +} + +// Library handle tests. As by inspection the LibraryHandle can be seen to be +// little more than shell around CalloutManager, only a basic set of tests +// is done concerning registration and deregistration of functions. +// +// More extensive tests (i.e. checking that when a callout is called it can +// only register and deregister callouts within its library) require that +// the CalloutHandle object pass the appropriate LibraryHandle to the +// callout. These tests are done in the handles_unittest tests. + +TEST_F(CalloutManagerTest, LibraryHandleRegistration) { + // Ensure that no callouts are attached to any of the hooks. + EXPECT_FALSE(getCalloutManager()->calloutsPresent(alpha_index_)); + + // Set up so that hooks "alpha" and "beta" have callouts attached from a + // different libraries. + getCalloutManager()->setLibraryIndex(0); + getCalloutManager()->getLibraryHandle().registerCallout("alpha", callout_one); + getCalloutManager()->getLibraryHandle().registerCallout("alpha", callout_two); + getCalloutManager()->setLibraryIndex(1); + getCalloutManager()->getLibraryHandle().registerCallout("alpha", callout_three); + getCalloutManager()->getLibraryHandle().registerCallout("alpha", callout_four); + + // Check all is as expected. + EXPECT_TRUE(getCalloutManager()->calloutsPresent(alpha_index_)); + EXPECT_FALSE(getCalloutManager()->calloutsPresent(beta_index_)); + EXPECT_FALSE(getCalloutManager()->calloutsPresent(gamma_index_)); + EXPECT_FALSE(getCalloutManager()->calloutsPresent(delta_index_)); + + // Check that calling the callouts returns as expected. (This is also a + // test of the callCallouts method.) + callout_value_ = 0; + getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle()); + EXPECT_EQ(1234, callout_value_); + + // Deregister a callout on library index 0 (after we check we can't + // deregister it through library index 1). + EXPECT_FALSE(getCalloutManager()->deregisterCallout("alpha", callout_two, 1)); + callout_value_ = 0; + getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle()); + EXPECT_EQ(1234, callout_value_); + + EXPECT_TRUE(getCalloutManager()->deregisterCallout("alpha", callout_two, 0)); + callout_value_ = 0; + getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle()); + EXPECT_EQ(134, callout_value_); + + // Deregister all callouts on library index 1. + EXPECT_TRUE(getCalloutManager()->deregisterAllCallouts("alpha", 1)); + callout_value_ = 0; + getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle()); + EXPECT_EQ(1, callout_value_); +} + +// A repeat of the test above, but using the alternate constructor for the +// LibraryHandle. +TEST_F(CalloutManagerTest, LibraryHandleAlternateConstructor) { + // Ensure that no callouts are attached to any of the hooks. + EXPECT_FALSE(getCalloutManager()->calloutsPresent(alpha_index_)); + + // Set up so that hooks "alpha" and "beta" have callouts attached from a + // different libraries. + LibraryHandle lh0(*getCalloutManager().get(), 0); + lh0.registerCallout("alpha", callout_one); + lh0.registerCallout("alpha", callout_two); + + LibraryHandle lh1(*getCalloutManager().get(), 1); + lh1.registerCallout("alpha", callout_three); + lh1.registerCallout("alpha", callout_four); + + // Check all is as expected. + EXPECT_TRUE(getCalloutManager()->calloutsPresent(alpha_index_)); + EXPECT_FALSE(getCalloutManager()->calloutsPresent(beta_index_)); + EXPECT_FALSE(getCalloutManager()->calloutsPresent(gamma_index_)); + EXPECT_FALSE(getCalloutManager()->calloutsPresent(delta_index_)); + + // Check that calling the callouts returns as expected. (This is also a + // test of the callCallouts method.) + callout_value_ = 0; + getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle()); + EXPECT_EQ(1234, callout_value_); + + // Deregister a callout on library index 0 (after we check we can't + // deregister it through library index 1). + EXPECT_FALSE(lh1.deregisterCallout("alpha", callout_two)); + callout_value_ = 0; + getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle()); + EXPECT_EQ(1234, callout_value_); + + EXPECT_TRUE(lh0.deregisterCallout("alpha", callout_two)); + callout_value_ = 0; + getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle()); + EXPECT_EQ(134, callout_value_); + + // Deregister all callouts on library index 1. + EXPECT_TRUE(lh1.deregisterAllCallouts("alpha")); + callout_value_ = 0; + getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle()); + EXPECT_EQ(1, callout_value_); +} + +// Check that the pre- and post- user callout library handles work +// appropriately with no user libraries. + +TEST_F(CalloutManagerTest, LibraryHandlePrePostNoLibraries) { + // Create a local callout manager and callout handle to reflect no libraries + // being loaded. + boost::shared_ptr<CalloutManager> manager(new CalloutManager(0)); + CalloutHandle handle(manager); + + // Ensure that no callouts are attached to any of the hooks. + EXPECT_FALSE(manager->calloutsPresent(alpha_index_)); + + // Setup the pre-and post callouts. + manager->getPostLibraryHandle().registerCallout("alpha", callout_four); + manager->getPreLibraryHandle().registerCallout("alpha", callout_one); + // Check all is as expected. + EXPECT_TRUE(manager->calloutsPresent(alpha_index_)); + EXPECT_FALSE(manager->calloutsPresent(beta_index_)); + EXPECT_FALSE(manager->calloutsPresent(gamma_index_)); + EXPECT_FALSE(manager->calloutsPresent(delta_index_)); + + // Check that calling the callouts returns as expected. + callout_value_ = 0; + manager->callCallouts(alpha_index_, handle); + EXPECT_EQ(14, callout_value_); + + // Deregister the pre- library callout. + EXPECT_TRUE(manager->getPreLibraryHandle().deregisterAllCallouts("alpha")); + callout_value_ = 0; + manager->callCallouts(alpha_index_, handle); + EXPECT_EQ(4, callout_value_); +} + +// Repeat the tests with one user library. + +TEST_F(CalloutManagerTest, LibraryHandlePrePostUserLibrary) { + + // Setup the pre-, library and post callouts. + getCalloutManager()->getPostLibraryHandle().registerCallout("alpha", + callout_four); + getCalloutManager()->getPreLibraryHandle().registerCallout("alpha", + callout_one); + + // ... and set up a callout in between, on library number 2. + LibraryHandle lh1(*getCalloutManager().get(), 2); + lh1.registerCallout("alpha", callout_five); + + // Check all is as expected. + EXPECT_TRUE(getCalloutManager()->calloutsPresent(alpha_index_)); + EXPECT_FALSE(getCalloutManager()->calloutsPresent(beta_index_)); + EXPECT_FALSE(getCalloutManager()->calloutsPresent(gamma_index_)); + EXPECT_FALSE(getCalloutManager()->calloutsPresent(delta_index_)); + + // Check that calling the callouts returns as expected. + callout_value_ = 0; + getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle()); + EXPECT_EQ(154, callout_value_); +} + +// Test that control command handlers can be installed as callouts. + +TEST_F(CalloutManagerTest, LibraryHandleRegisterCommandHandler) { + CalloutHandle handle(getCalloutManager()); + + // Simulate creation of the two hook libraries. Fist library implements two + // handlers for the control command 'command-one'. Second library implements + // two control command handlers: one for the 'command-one', another one for + // 'command-two'. Each of the handlers for the 'command-one' must be called + // and they must be called in the appropriate order. Command handler for + // 'command-two' should also be called. + + getCalloutManager()->setLibraryIndex(0); + getCalloutManager()->getLibraryHandle().registerCommandCallout("command-one", callout_one); + getCalloutManager()->getLibraryHandle().registerCommandCallout("command-one", callout_four); + getCalloutManager()->setLibraryIndex(1); + getCalloutManager()->getLibraryHandle().registerCommandCallout("command-one", callout_two); + getCalloutManager()->getLibraryHandle().registerCommandCallout("command-two", callout_three); + + // Command handlers are installed for commands: 'command-one' and 'command-two'. + EXPECT_TRUE(getCalloutManager()->commandHandlersPresent("command-one")); + EXPECT_TRUE(getCalloutManager()->commandHandlersPresent("command-two")); + // There should be no handlers installed for 'command-three' and 'command-four'. + EXPECT_FALSE(getCalloutManager()->commandHandlersPresent("command-three")); + EXPECT_FALSE(getCalloutManager()->commandHandlersPresent("command-four")); + + // Call handlers for 'command-one'. There should be three handlers called in + // the following order: 1, 4, 2. + callout_value_ = 0; + ASSERT_NO_THROW(getCalloutManager()->callCommandHandlers("command-one", handle)); + EXPECT_EQ(142, callout_value_); + + // There should be one handler invoked for the 'command-two'. This handler has + // index of 3. + callout_value_ = 0; + ASSERT_NO_THROW(getCalloutManager()->callCommandHandlers("command-two", handle)); + EXPECT_EQ(3, callout_value_); + + // An attempt to call handlers for the commands for which no hook points + // were created should result in exception. + EXPECT_THROW(getCalloutManager()->callCommandHandlers("command-three", handle), + NoSuchHook); + EXPECT_THROW(getCalloutManager()->callCommandHandlers("command-four", handle), + NoSuchHook); +} + +// This test checks if the CalloutManager can adjust its own hook_vector_ size. +TEST_F(CalloutManagerTest, VectorSize) { + + size_t s = getCalloutManager()->getHookLibsVectorSize(); + + ServerHooks& hooks = ServerHooks::getServerHooks(); + + EXPECT_NO_THROW(hooks.registerHook("a_new_one")); + + // Now load a callout. Name of the hook point the new callout is installed + // on doesn't matter. CM should do sanity checks and adjust anyway. + getCalloutManager()->getPostLibraryHandle().registerCallout("alpha", + callout_four); + + // The vector size should have been increased by one, because there's + // one new hook point now. + EXPECT_EQ(s + 1, getCalloutManager()->getHookLibsVectorSize()); +} + + +// The setting of the hook index is checked in the handles_unittest +// set of tests, as access restrictions mean it is not easily tested +// on its own. + +} // Anonymous namespace diff --git a/src/lib/hooks/tests/callout_params_library.cc b/src/lib/hooks/tests/callout_params_library.cc new file mode 100644 index 0000000..d35fc00 --- /dev/null +++ b/src/lib/hooks/tests/callout_params_library.cc @@ -0,0 +1,129 @@ +// Copyright (C) 2016-2020 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +/// @file +/// @brief Callout Library +/// +/// This is the source of a test library for the DHCP parser tests that +/// specify parameters. It will attempt to obtain its own parameters. + +#include <config.h> +#include <hooks/hooks.h> +#include <iostream> + +using namespace std; +using namespace isc::hooks; +using namespace isc::data; + +extern "C" { + +// Framework functions +int +version() { + return (KEA_HOOKS_VERSION); +} + +/// @brief This method will be called when the hook library is loaded +/// +/// While its primary usage is for unit-testing, it also doubles as an +/// illustration referred to from Hooks Developer's Guide. As such, please +/// keep it simple, tidy and try to avoid referencing unnecessary code. +/// Parts of it can be used as copy-paste examples. +/// +/// @param handle passed by the hooks framework +/// @return 0 if load was successful, non-zero for errors +int load(LibraryHandle& handle) { + ConstElementPtr elems = handle.getParameters(); + ConstElementPtr string_elem = handle.getParameter("svalue"); + ConstElementPtr int_elem = handle.getParameter("ivalue"); + ConstElementPtr bool_elem = handle.getParameter("bvalue"); + ConstElementPtr nonexistent = handle.getParameter("nonexistent"); + vector<string> names = handle.getParameterNames(); + + // String handling example. + if (!string_elem) { + // Parameter was not specified at all. + return (1); + } + + if (string_elem->getType() != Element::string) { + // Parameter is specified, but it's not a string. + return (2); + } + + string str = string_elem->stringValue(); + if (str != "string value") { + // Parameter is specified, is a string, but has unexpected value. + // + // This library is used for testing, so it expects exact value of the + // parameter. Normal library would likely use whatever value user + // specified. + return (3); + } + + // Integer handling example + if (!int_elem) { + // Parameter was not specified at all. + return (4); + } + + if (int_elem->getType() != Element::integer) { + // Parameter is specified, but it's not an integer. + return (5); + } + + int int_value = int_elem->intValue(); + if (int_value != 42) { + // Parameter specified, is an integer, but has a value different than + // expected. + return (6); + } + + // Boolean handling example + if (!bool_elem) { + // Parameter was not specified at all. + return (7); + } + + if (bool_elem->getType() != Element::boolean) { + // Parameter is specified, but it's not a boolean. + return (8); + } + + bool flag = bool_elem->boolValue(); + if (flag != true) { + // Parameter specified, is a boolean, but has a value different than + // expected. + return (9); + } + + // Check names. As a side effect of what maps using strings are + // implemented names are sorted in alphabetical order. + if ((names.size() != 3) || (names[0] != "bvalue") || + (names[1] != "ivalue") || (names[2] != "svalue")) { + // Expect 3 names: bvalue, ivalue, svalue. + return (10); + } + + // Check elems map. + if (!elems) { + return (11); + } + string expected_str = "{ " + "\"bvalue\": true, " + "\"ivalue\": 42, " + "\"svalue\": \"string value\"" + " }"; + if (expected_str != elems->str()) { + return (12); + } + + // All validation steps were successful. The library has all the parameters + // it needs, so we should report a success. + return (0); +} + +} diff --git a/src/lib/hooks/tests/common_test_class.h b/src/lib/hooks/tests/common_test_class.h new file mode 100644 index 0000000..aa34bcf --- /dev/null +++ b/src/lib/hooks/tests/common_test_class.h @@ -0,0 +1,174 @@ +// Copyright (C) 2013-2021 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef COMMON_HOOKS_TEST_CLASS_H +#define COMMON_HOOKS_TEST_CLASS_H + +#include <hooks/callout_handle.h> +#include <hooks/callout_manager.h> +#include <hooks/server_hooks.h> +#include <hooks/tests/marker_file.h> + +#include <boost/shared_ptr.hpp> +#include <gtest/gtest.h> + +/// @brief Common hooks test class +/// +/// This class is a shared parent of the test fixture class in the tests of the +/// higher-level hooks classes (LibraryManager, LibraryManagerCollection and +/// HooksManager). It +/// +/// - sets the ServerHooks object with three hooks and stores their +/// indexes. +/// - executes the callouts (which are assumed to perform a calculation) +/// and checks the results. + +class HooksCommonTestClass { +public: + /// @brief Constructor + HooksCommonTestClass() { + + // Set up the server hooks. ServerHooks is a singleton, so we reset it + // between each test. + isc::hooks::ServerHooks& hooks = + isc::hooks::ServerHooks::getServerHooks(); + hooks.reset(); + hookpt_one_index_ = hooks.registerHook("hookpt_one"); + hookpt_two_index_ = hooks.registerHook("hookpt_two"); + hookpt_three_index_ = hooks.registerHook("hookpt_three"); + } + + /// @brief Call callouts test + /// + /// All of the loaded libraries for which callouts are called register four + /// callouts: a context_create callout and three callouts that are attached + /// to hooks hookpt_one, hookpt_two and hookpt_three. These four callouts, + /// executed in sequence, perform a series of calculations. Data is passed + /// between callouts in the argument list, in a variable named "result". + /// + /// context_create initializes the calculation by setting a seed + /// value, called r0 here. This value is dependent on the library being + /// loaded. Prior to that, the argument "result" is initialized to -1, + /// the purpose being to avoid exceptions when running this test with no + /// libraries loaded. + /// + /// Callout hookpt_one is passed a value d1 and performs a simple arithmetic + /// operation on it and r0 yielding a result r1. Hence we can say that + /// @f[ r1 = hookpt_one(r0, d1) @f] + /// + /// Callout hookpt_two is passed a value d2 and performs another simple + /// arithmetic operation on it and d2, yielding r2, i.e. + /// @f[ r2 = hookpt_two(d1, d2) @f] + /// + /// hookpt_three does a similar operation giving + /// @f[ r3 = hookpt_three(r2, d3) @f]. + /// + /// The details of the operations hookpt_one, hookpt_two and hookpt_three + /// depend on the library, so the results obtained not only depend on + /// the data, but also on the library loaded. This method is passed both + /// data and expected results. It executes the three callouts in sequence, + /// checking the intermediate and final results. Only if the expected + /// library has been loaded correctly and the callouts in it registered + /// correctly will be the results be as expected. + /// + /// It is assumed that callout_manager_ has been set up appropriately. + /// + /// @note The CalloutHandle used in the calls is declared locally here. + /// The advantage of this (apart from scope reduction) is that on + /// exit, it is destroyed. This removes any references to memory + /// allocated by loaded libraries while they are still loaded. + /// + /// @param manager CalloutManager to use for the test + /// @param r0...r3, d1..d3 Data (dN) and expected results (rN) - both + /// intermediate and final. The arguments are ordered so that they + /// appear in the argument list in the order they are used. + void executeCallCallouts( + const boost::shared_ptr<isc::hooks::CalloutManager>& manager, + int r0, int d1, int r1, int d2, int r2, int d3, int r3) { + static const char* COMMON_TEXT = " callout returned the wrong value"; + static const char* RESULT = "result"; + + int result; + + // Set up a callout handle for the calls. + isc::hooks::CalloutHandle handle(manager); + + // Initialize the argument RESULT. This simplifies testing by + // eliminating the generation of an exception when we try the unload + // test. In that case, RESULT is unchanged. + handle.setArgument(RESULT, -1); + + // Seed the calculation. + manager->callCallouts(isc::hooks::ServerHooks::CONTEXT_CREATE, handle); + handle.getArgument(RESULT, result); + EXPECT_EQ(r0, result) << "context_create" << COMMON_TEXT; + + // Perform the first calculation. + handle.setArgument("data_1", d1); + manager->callCallouts(hookpt_one_index_, handle); + handle.getArgument(RESULT, result); + EXPECT_EQ(r1, result) << "hookpt_one" << COMMON_TEXT; + + // ... the second ... + handle.setArgument("data_2", d2); + manager->callCallouts(hookpt_two_index_, handle); + handle.getArgument(RESULT, result); + EXPECT_EQ(r2, result) << "hookpt_two" << COMMON_TEXT; + + // ... and the third. + handle.setArgument("data_3", d3); + manager->callCallouts(hookpt_three_index_, handle); + handle.getArgument(RESULT, result); + EXPECT_EQ(r3, result) << "hookpt_three" << COMMON_TEXT; + } + + /// @brief Call command handlers test. + /// + /// This test is similar to @c executeCallCallouts but it uses + /// @ref CalloutManager::callCommandHandlers to execute the command + /// handlers for the following commands: 'command-one' and 'command-two'. + /// + /// @param manager CalloutManager to use for the test + /// @param r1..r2, d1..d2 Data (dN) and expected results (rN). + void executeCallCommandHandlers( + const boost::shared_ptr<isc::hooks::CalloutManager>& manager, + int d1, int r1, int d2, int r2) { + static const char* COMMON_TEXT = " command handler returned the wrong value"; + static const char* RESULT = "result"; + + int result; + + // Set up a callout handle for the calls. + isc::hooks::CalloutHandle handle(manager); + + // Initialize the argument RESULT. This simplifies testing by + // eliminating the generation of an exception when we try the unload + // test. In that case, RESULT is unchanged. + handle.setArgument(RESULT, -1); + + // Perform the first calculation: it should assign the data to the + // result. + handle.setArgument("data_1", d1); + manager->callCommandHandlers("command-one", handle); + handle.getArgument(RESULT, result); + EXPECT_EQ(r1, result) << "command-one" << COMMON_TEXT; + + // Perform the second calculation: it should multiply the data by 10 + // and return in the result. + handle.setArgument("data_2", d2); + manager->callCommandHandlers("command-two", handle); + handle.getArgument(RESULT, result); + EXPECT_EQ(r2, result) << "command-two" << COMMON_TEXT; + } + + + /// Hook indexes. These are are made public for ease of reference. + int hookpt_one_index_; + int hookpt_two_index_; + int hookpt_three_index_; +}; + +#endif // COMMON_HOOKS_TEST_CLASS_H diff --git a/src/lib/hooks/tests/framework_exception_library.cc b/src/lib/hooks/tests/framework_exception_library.cc new file mode 100644 index 0000000..c60c3f8 --- /dev/null +++ b/src/lib/hooks/tests/framework_exception_library.cc @@ -0,0 +1,41 @@ +// Copyright (C) 2013-2015 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +/// @file +/// @brief Framework exception library +/// +/// This is source of a test library for various test (LibraryManager and +/// HooksManager). The characteristics of the library produced from this +/// file are: +/// +/// - All three framework functions are supplied (version(), load() and +/// unload()) and all generate an exception. + +#include <config.h> + +#include <hooks/hooks.h> + +#include <exception> + +extern "C" { + +int +version() { + throw std::exception(); +} + +int +load(isc::hooks::LibraryHandle& /*handle*/) { + throw std::exception(); +} + +int +unload() { + throw std::exception(); +} + +}; + diff --git a/src/lib/hooks/tests/full_callout_library.cc b/src/lib/hooks/tests/full_callout_library.cc new file mode 100644 index 0000000..58ea8c7 --- /dev/null +++ b/src/lib/hooks/tests/full_callout_library.cc @@ -0,0 +1,177 @@ +// Copyright (C) 2013-2020 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +/// @file +/// @brief Full callout library +/// +/// This is source of a test library for various test (LibraryManager and +/// HooksManager). The characteristics of the library produced from this +/// file are: +/// +/// The characteristics of this library are: +/// +/// - All four framework functions are supplied (version(), load(), +/// unload() and multi_threading_compatible()), with unload() +/// creating a marker file. The test code checks for the presence +/// of this file, so verifying that unload() has been run. +/// +/// - One standard and two non-standard callouts are supplied, with the latter +/// being registered by the load() function. +/// +/// All callouts do trivial calculations, the result of all being called in +/// sequence being +/// +/// @f[ ((7 * data_1) - data_2) * data_3 @f] +/// +/// ...where data_1, data_2 and data_3 are the values passed in arguments of +/// the same name to the three callouts (data_1 passed to hookpt_one, data_2 +/// to hookpt_two etc.) and the result is returned in the argument "result". + +#include <config.h> + +#include <hooks/hooks.h> +#include <hooks/tests/marker_file.h> + +#include <fstream> + +using namespace isc::hooks; + +extern "C" { + +// Callouts + +int +context_create(CalloutHandle& handle) { + handle.setContext("result", static_cast<int>(7)); + handle.setArgument("result", static_cast<int>(7)); + return (0); +} + +// First callout adds the passed "data_1" argument to the initialized context +// value of 7. (Note that the value set by context_create is accessed through +// context and not the argument, so checking that context is correctly passed +// between callouts in the same library.) + +int +hookpt_one(CalloutHandle& handle) { + int data; + handle.getArgument("data_1", data); + + int result; + handle.getArgument("result", result); + + result *= data; + handle.setArgument("result", result); + + return (0); +} + +// Second callout subtracts the passed value of data_2 from the current +// running total. + +static int +hook_nonstandard_two(CalloutHandle& handle) { + int data; + handle.getArgument("data_2", data); + + int result; + handle.getArgument("result", result); + + result -= data; + handle.setArgument("result", result); + + return (0); +} + +// Final callout multiplies the current running total by data_3. + +static int +hook_nonstandard_three(CalloutHandle& handle) { + int data; + handle.getArgument("data_3", data); + + int result; + handle.getArgument("result", result); + + result *= data; + handle.setArgument("result", result); + + return (0); +} + +// First command handler assigns data to a result. + +static int +command_handler_one(CalloutHandle& handle) { + int data; + handle.getArgument("data_1", data); + + int result; + handle.getArgument("result", result); + + result = data; + handle.setArgument("result", result); + + return (0); +} + +// Second command handler multiples the result by data by 10. + +static int +command_handler_two(CalloutHandle& handle) { + int data; + handle.getArgument("data_2", data); + + int result; + handle.getArgument("result", result); + + result *= data * 10; + handle.setArgument("result", result); + + return (0); +} + +// Framework functions + +int +version() { + return (KEA_HOOKS_VERSION); +} + +int +load(LibraryHandle& handle) { + // Initialize if the main image was statically linked +#ifdef USE_STATIC_LINK + hooksStaticLinkInit(); +#endif + // Register the non-standard functions + handle.registerCallout("hookpt_two", hook_nonstandard_two); + handle.registerCallout("hookpt_three", hook_nonstandard_three); + + // Register command_handler_one as control command handler. + handle.registerCommandCallout("command-one", command_handler_one); + handle.registerCommandCallout("command-two", command_handler_two); + + return (0); +} + +int +unload() { + // Create the marker file. + std::fstream marker; + marker.open(MARKER_FILE, std::fstream::out); + marker.close(); + + return (0); +} + +int +multi_threading_compatible() { + return (1); +} + +}; + diff --git a/src/lib/hooks/tests/handles_unittest.cc b/src/lib/hooks/tests/handles_unittest.cc new file mode 100644 index 0000000..a9292d8 --- /dev/null +++ b/src/lib/hooks/tests/handles_unittest.cc @@ -0,0 +1,777 @@ +// Copyright (C) 2013-2020 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <hooks/callout_handle.h> +#include <hooks/callout_manager.h> +#include <hooks/library_handle.h> +#include <hooks/server_hooks.h> + +#include <boost/lexical_cast.hpp> +#include <boost/scoped_ptr.hpp> +#include <gtest/gtest.h> + +#include <algorithm> +#include <string> + +/// @file +/// CalloutHandle/LibraryHandle interaction tests +/// +/// This file holds unit tests checking the interaction between the +/// CalloutHandle/LibraryHandle and CalloutManager classes. In particular, +/// they check that: +/// +/// - A CalloutHandle's context is shared between callouts from the same +/// library, but there is a separate context for each library. +/// +/// - The various methods manipulating the items in the CalloutHandle's context +/// work correctly. +/// +/// - An active callout can only modify the registration of callouts registered +/// by its own library. + +using namespace isc::hooks; +using namespace std; + +namespace { + +class HandlesTest : public ::testing::Test { +public: + /// @brief Constructor + /// + /// Sets up the various elements used in each test. + HandlesTest() { + // Set up four hooks, although through gamma + ServerHooks& hooks = ServerHooks::getServerHooks(); + hooks.reset(); + alpha_index_ = hooks.registerHook("alpha"); + beta_index_ = hooks.registerHook("beta"); + gamma_index_ = hooks.registerHook("gamma"); + delta_index_ = hooks.registerHook("delta"); + + // Set up for three libraries. + manager_.reset(new CalloutManager(3)); + + // Initialize remaining variables. + common_string_ = ""; + } + + /// @brief Return callout manager + boost::shared_ptr<CalloutManager> getCalloutManager() { + return (manager_); + } + + /// Hook indexes - these are frequently accessed, so are accessed directly. + int alpha_index_; + int beta_index_; + int gamma_index_; + int delta_index_; + + /// String accessible by all callouts whatever the library + static std::string common_string_; + +private: + /// Callout manager. Declared static so that the callout functions can + /// access it. + boost::shared_ptr<CalloutManager> manager_; +}; + +/// Define the common string +std::string HandlesTest::common_string_; + + +// The next set of functions define the callouts used by the tests. They +// manipulate the data in such a way that callouts called - and the order in +// which they were called - can be determined. The functions also check that +// the "callout context" data areas are separate. +// +// Three libraries are assumed, and each supplies four callouts. All callouts +// manipulate two context elements the CalloutHandle, the elements being called +// "string" and "int" (which describe the type of data manipulated). +// +// For the string item, each callout shifts data to the left and inserts its own +// data. The data is a string of the form "nmc", where "n" is the number of +// the library, "m" is the callout number and "y" is the indication of what +// callout handle was passed as an argument ("1" or "2": "0" is used when no +// identification has been set in the callout handle). +// +// For simplicity, and to cut down the number of functions actually written, +// the callout indicator ("1" or "2") ) used in the in the CalloutHandle +// functions is passed via a CalloutArgument. The argument is named "string": +// use of a name the same as that of one of the context elements serves as a +// check that the argument name space and argument context space are separate. +// +// For integer data, the value starts at zero and an increment is added on each +// call. This increment is equal to: +// +// 100 * library number + 10 * callout number + callout handle +// +// Although this gives less information than the string value, the reasons for +// using it are: +// +// - It is a separate item in the context, so checks that the context can +// handle multiple items. +// - It provides an item that can be deleted by the context deletion +// methods. + + +// Values set in the CalloutHandle context. There are three libraries, so +// there are three contexts for the callout, one for each library. + +std::string& resultCalloutString(int index) { + static std::string result_callout_string[3]; + return (result_callout_string[index]); +} + +int& resultCalloutInt(int index) { + static int result_callout_int[3]; + return (result_callout_int[index]); +} + +// A simple function to zero the results. + +static void zero_results() { + for (int i = 0; i < 3; ++i) { + resultCalloutString(i) = ""; + resultCalloutInt(i) = 0; + } +} + + +// Library callouts. + +// Common code for setting the callout context values. + +int +execute(CalloutHandle& callout_handle, int library_num, int callout_num) { + + // Obtain the callout handle number + int handle_num = 0; + try { + callout_handle.getArgument("handle_num", handle_num); + } catch (const NoSuchArgument&) { + // handle_num argument not set: this is the case in the tests where + // the context_create hook check is tested. + handle_num = 0; + } + + // Create the basic data to be appended to the context value. + int idata = 100 * library_num + 10 * callout_num + handle_num; + string sdata = boost::lexical_cast<string>(idata); + + // Get the context data. As before, this will not exist for the first + // callout called. (In real life, the library should create it when the + // "context_create" hook gets called before any packet processing takes + // place.) + int int_value = 0; + try { + callout_handle.getContext("int", int_value); + } catch (const NoSuchCalloutContext&) { + int_value = 0; + } + + string string_value = ""; + try { + callout_handle.getContext("string", string_value); + } catch (const NoSuchCalloutContext&) { + string_value = ""; + } + + // Update the values and set them back in the callout context. + int_value += idata; + callout_handle.setContext("int", int_value); + + string_value += sdata; + callout_handle.setContext("string", string_value); + + return (0); +} + +// The following functions are the actual callouts - the name is of the +// form "callout_<library number>_<callout number>" + +int +callout11(CalloutHandle& callout_handle) { + return (execute(callout_handle, 1, 1)); +} + +int +callout12(CalloutHandle& callout_handle) { + return (execute(callout_handle, 1, 2)); +} + +int +callout13(CalloutHandle& callout_handle) { + return (execute(callout_handle, 1, 3)); +} + +int +callout21(CalloutHandle& callout_handle) { + return (execute(callout_handle, 2, 1)); +} + +int +callout22(CalloutHandle& callout_handle) { + return (execute(callout_handle, 2, 2)); +} + +int +callout23(CalloutHandle& callout_handle) { + return (execute(callout_handle, 2, 3)); +} + +int +callout31(CalloutHandle& callout_handle) { + return (execute(callout_handle, 3, 1)); +} + +int +callout32(CalloutHandle& callout_handle) { + return (execute(callout_handle, 3, 2)); +} + +int +callout33(CalloutHandle& callout_handle) { + return (execute(callout_handle, 3, 3)); +} + +// Common callout code for the fourth hook (which makes the data available for +// checking). It copies the library and callout context data to the global +// variables. + +int printExecute(CalloutHandle& callout_handle, int library_num) { + callout_handle.getContext("string", resultCalloutString(library_num - 1)); + callout_handle.getContext("int", resultCalloutInt(library_num - 1)); + + return (0); +} + +// These are the actual callouts. + +int +print1(CalloutHandle& callout_handle) { + return (printExecute(callout_handle, 1)); +} + +int +print2(CalloutHandle& callout_handle) { + return (printExecute(callout_handle, 2)); +} + +int +print3(CalloutHandle& callout_handle) { + return (printExecute(callout_handle, 3)); +} + +// This test checks the many-faced nature of the context for the CalloutContext. + +TEST_F(HandlesTest, ContextAccessCheck) { + // Register callouts for the different libraries. + CalloutHandle handle(getCalloutManager()); + + getCalloutManager()->registerCallout("alpha", callout11, 0); + getCalloutManager()->registerCallout("beta", callout12, 0); + getCalloutManager()->registerCallout("gamma", callout13, 0); + getCalloutManager()->registerCallout("delta", print1, 0); + + getCalloutManager()->registerCallout("alpha", callout21, 1); + getCalloutManager()->registerCallout("beta", callout22, 1); + getCalloutManager()->registerCallout("gamma", callout23, 1); + getCalloutManager()->registerCallout("delta", print2, 1); + + getCalloutManager()->registerCallout("alpha", callout31, 2); + getCalloutManager()->registerCallout("beta", callout32, 2); + getCalloutManager()->registerCallout("gamma", callout33, 2); + getCalloutManager()->registerCallout("delta", print3, 2); + + // Create the callout handles and distinguish them by setting the + // "handle_num" argument. + CalloutHandle callout_handle_1(getCalloutManager()); + callout_handle_1.setArgument("handle_num", static_cast<int>(1)); + + CalloutHandle callout_handle_2(getCalloutManager()); + callout_handle_2.setArgument("handle_num", static_cast<int>(2)); + + // Now call the callouts attached to the first three hooks. Each hook is + // called twice (once for each callout handle) before the next hook is + // called. + getCalloutManager()->callCallouts(alpha_index_, callout_handle_1); + getCalloutManager()->callCallouts(alpha_index_, callout_handle_2); + getCalloutManager()->callCallouts(beta_index_, callout_handle_1); + getCalloutManager()->callCallouts(beta_index_, callout_handle_2); + getCalloutManager()->callCallouts(gamma_index_, callout_handle_1); + getCalloutManager()->callCallouts(gamma_index_, callout_handle_2); + + // Get the results for each callout (the callout on hook "delta" copies + // the context values into a location the test can access). Explicitly + // zero the variables before getting the results so we are certain that + // the values are the results of the callouts. + + zero_results(); + + // To explain the expected callout context results. + // + // Each callout handle maintains a separate context for each library. When + // the first call to callCallouts() is made, "111" gets appended to + // the context for library 1 maintained by the first callout handle, "211" + // gets appended to the context maintained for library 2, and "311" to + // the context maintained for library 3. In each case, the first digit + // corresponds to the library number, the second to the callout number and + // the third to the "handle_num" of the callout handle. For the first call + // to callCallouts, handle 1 is used, so the last digit is always 1. + // + // The next call to callCallouts() calls the same callouts but for the + // second callout handle. It also maintains three contexts (one for + // each library) and they will get "112", "212", "312" appended to + // them. The explanation for the digits is the same as before, except that + // in this case, the callout handle is number 2, so the third digit is + // always 2. These additions don't affect the contexts maintained by + // callout handle 1. + // + // The process is then repeated for hooks "beta" and "gamma" which, for + // callout handle 1, append "121", "221" and "321" for hook "beta" and + // "311", "321" and "331" for hook "gamma". + // + // The expected integer values can be found by summing up the values + // corresponding to the elements of the strings. + + // At this point, we have only called the "print" function for callout + // handle "1", so the following results are checking the context values + // maintained in that callout handle. + + getCalloutManager()->callCallouts(delta_index_, callout_handle_1); + EXPECT_EQ("111121131", resultCalloutString(0)); + EXPECT_EQ("211221231", resultCalloutString(1)); + EXPECT_EQ("311321331", resultCalloutString(2)); + + EXPECT_EQ((111 + 121 + 131), resultCalloutInt(0)); + EXPECT_EQ((211 + 221 + 231), resultCalloutInt(1)); + EXPECT_EQ((311 + 321 + 331), resultCalloutInt(2)); + + // Repeat the checks for callout 2. + + zero_results(); + getCalloutManager()->callCallouts(delta_index_, callout_handle_2); + + EXPECT_EQ((112 + 122 + 132), resultCalloutInt(0)); + EXPECT_EQ((212 + 222 + 232), resultCalloutInt(1)); + EXPECT_EQ((312 + 322 + 332), resultCalloutInt(2)); + + EXPECT_EQ("112122132", resultCalloutString(0)); + EXPECT_EQ("212222232", resultCalloutString(1)); + EXPECT_EQ("312322332", resultCalloutString(2)); +} + +// Now repeat the test, but add a deletion callout to the list. The "beta" +// hook of library 2 will have an additional callout to delete the "int" +// element: the same hook for library 3 will delete both elements. In +// addition, the names of context elements for the libraries at this point +// will be printed. + +// List of context item names. + +vector<string>& +getItemNames(int index) { + static vector<string> context_items[3]; + return (context_items[index]); +} + +// Context item deletion functions. + +int +deleteIntContextItem(CalloutHandle& handle) { + handle.deleteContext("int"); + return (0); +} + +int +deleteAllContextItems(CalloutHandle& handle) { + handle.deleteAllContext(); + return (0); +} + +// Generic print function - copy names in sorted order. + +int +printContextNamesExecute(CalloutHandle& handle, int library_num) { + const int index = library_num - 1; + getItemNames(index) = handle.getContextNames(); + sort(getItemNames(index).begin(), getItemNames(index).end()); + return (0); +} + +int +printContextNames1(CalloutHandle& handle) { + return (printContextNamesExecute(handle, 1)); +} + +int +printContextNames2(CalloutHandle& handle) { + return (printContextNamesExecute(handle, 2)); +} + +int +printContextNames3(CalloutHandle& handle) { + return (printContextNamesExecute(handle, 3)); +} + +// Perform the test including deletion of context items. + +TEST_F(HandlesTest, ContextDeletionCheck) { + getCalloutManager()->registerCallout("alpha", callout11, 0); + getCalloutManager()->registerCallout("beta", callout12, 0); + getCalloutManager()->registerCallout("beta", printContextNames1, 0); + getCalloutManager()->registerCallout("gamma", callout13, 0); + getCalloutManager()->registerCallout("delta", print1, 0); + + getCalloutManager()->registerCallout("alpha", callout21, 1); + getCalloutManager()->registerCallout("beta", callout22, 1); + getCalloutManager()->registerCallout("beta", deleteIntContextItem, 1); + getCalloutManager()->registerCallout("beta", printContextNames2, 1); + getCalloutManager()->registerCallout("gamma", callout23, 1); + getCalloutManager()->registerCallout("delta", print2, 1); + + getCalloutManager()->registerCallout("alpha", callout31, 2); + getCalloutManager()->registerCallout("beta", callout32, 2); + getCalloutManager()->registerCallout("beta", deleteAllContextItems, 2); + getCalloutManager()->registerCallout("beta", printContextNames3, 2); + getCalloutManager()->registerCallout("gamma", callout33, 2); + getCalloutManager()->registerCallout("delta", print3, 2); + + // Create the callout handles and distinguish them by setting the "long" + // argument. + CalloutHandle callout_handle_1(getCalloutManager()); + callout_handle_1.setArgument("handle_num", static_cast<int>(1)); + + CalloutHandle callout_handle_2(getCalloutManager()); + callout_handle_2.setArgument("handle_num", static_cast<int>(2)); + + // Now call the callouts attached to the first three hooks. Each hook is + // called twice (once for each callout handle) before the next hook is + // called. + getCalloutManager()->callCallouts(alpha_index_, callout_handle_1); + getCalloutManager()->callCallouts(alpha_index_, callout_handle_2); + getCalloutManager()->callCallouts(beta_index_, callout_handle_1); + getCalloutManager()->callCallouts(beta_index_, callout_handle_2); + getCalloutManager()->callCallouts(gamma_index_, callout_handle_1); + getCalloutManager()->callCallouts(gamma_index_, callout_handle_2); + + // Get the results for each callout. Explicitly zero the variables before + // getting the results so we are certain that the values are the results + // of the callouts. + + zero_results(); + getCalloutManager()->callCallouts(delta_index_, callout_handle_1); + + // The logic by which the expected results are arrived at is described + // in the ContextAccessCheck test. The results here are different + // because context items have been modified along the way. + + EXPECT_EQ((111 + 121 + 131), resultCalloutInt(0)); + EXPECT_EQ(( 231), resultCalloutInt(1)); + EXPECT_EQ(( 331), resultCalloutInt(2)); + + EXPECT_EQ("111121131", resultCalloutString(0)); + EXPECT_EQ("211221231", resultCalloutString(1)); + EXPECT_EQ( "331", resultCalloutString(2)); + + // Repeat the checks for callout handle 2. + + zero_results(); + getCalloutManager()->callCallouts(delta_index_, callout_handle_2); + + EXPECT_EQ((112 + 122 + 132), resultCalloutInt(0)); + EXPECT_EQ(( 232), resultCalloutInt(1)); + EXPECT_EQ(( 332), resultCalloutInt(2)); + + EXPECT_EQ("112122132", resultCalloutString(0)); + EXPECT_EQ("212222232", resultCalloutString(1)); + EXPECT_EQ( "332", resultCalloutString(2)); + + // ... and check what the names of the context items are after the callouts + // for hook "beta". We know they are in sorted order. + + EXPECT_EQ(2, getItemNames(0).size()); + EXPECT_EQ(string("int"), getItemNames(0)[0]); + EXPECT_EQ(string("string"), getItemNames(0)[1]); + + EXPECT_EQ(1, getItemNames(1).size()); + EXPECT_EQ(string("string"), getItemNames(1)[0]); + + EXPECT_EQ(0, getItemNames(2).size()); +} + +// Tests that the CalloutHandle's constructor and destructor call the +// context_create and context_destroy callbacks (if registered). For +// simplicity, we'll use the same callout functions as used above. + +TEST_F(HandlesTest, ConstructionDestructionCallouts) { + + // Register context callouts. + getCalloutManager()->registerCallout("context_create", callout11, 0); + getCalloutManager()->registerCallout("context_create", print1, 0); + getCalloutManager()->registerCallout("context_destroy", callout12, 0); + getCalloutManager()->registerCallout("context_destroy", print1, 0); + + // Create the CalloutHandle and check that the constructor callout + // has run. + zero_results(); + boost::scoped_ptr<CalloutHandle> + callout_handle(new CalloutHandle(getCalloutManager())); + EXPECT_EQ("110", resultCalloutString(0)); + EXPECT_EQ(110, resultCalloutInt(0)); + + // Check that the destructor callout runs. Note that the "print1" callout + // didn't destroy the library context - it only copied it to where it + // could be examined. As a result, the destructor callout appends its + // elements to the constructor's values and the result is printed. + zero_results(); + callout_handle.reset(); + + EXPECT_EQ("110120", resultCalloutString(0)); + EXPECT_EQ((110 + 120), resultCalloutInt(0)); +} + +// Testing the operation of the "skip" flag. Callouts print the value +// they see in the flag and either leave it unchanged, set it or clear it. +int +calloutPrintSkip(CalloutHandle& handle) { + static const std::string YES("Y"); + static const std::string NO("N"); + static const std::string DROP("D"); + static const std::string PARK("P"); + + switch (handle.getStatus()) { + case CalloutHandle::NEXT_STEP_CONTINUE: + HandlesTest::common_string_ += NO; // skip = no + break; + case CalloutHandle::NEXT_STEP_SKIP: + HandlesTest::common_string_ += YES; // skip = yes + break; + case CalloutHandle::NEXT_STEP_DROP: + HandlesTest::common_string_ += DROP; // drop + break; + case CalloutHandle::NEXT_STEP_PARK: + HandlesTest::common_string_ += PARK; // park + break; + } + return (0); +} + +int +calloutSetSkip(CalloutHandle& handle) { + static_cast<void>(calloutPrintSkip(handle)); + handle.setStatus(CalloutHandle::NEXT_STEP_SKIP); + return (0); +} + +int +calloutClearSkip(CalloutHandle& handle) { + static_cast<void>(calloutPrintSkip(handle)); + handle.setStatus(CalloutHandle::NEXT_STEP_CONTINUE); + return (0); +} + +// Do a series of tests, returning with the skip flag set "true". + +TEST_F(HandlesTest, ReturnSkipSet) { + getCalloutManager()->registerCallout("alpha", calloutPrintSkip, 0); + getCalloutManager()->registerCallout("alpha", calloutSetSkip, 0); + getCalloutManager()->registerCallout("alpha", calloutSetSkip, 0); + getCalloutManager()->registerCallout("alpha", calloutClearSkip, 0); + + getCalloutManager()->registerCallout("alpha", calloutPrintSkip, 1); + getCalloutManager()->registerCallout("alpha", calloutSetSkip, 1); + getCalloutManager()->registerCallout("alpha", calloutSetSkip, 1); + getCalloutManager()->registerCallout("alpha", calloutClearSkip, 1); + getCalloutManager()->registerCallout("alpha", calloutClearSkip, 1); + + getCalloutManager()->registerCallout("alpha", calloutPrintSkip, 2); + getCalloutManager()->registerCallout("alpha", calloutSetSkip, 2); + getCalloutManager()->registerCallout("alpha", calloutClearSkip, 2); + getCalloutManager()->registerCallout("alpha", calloutSetSkip, 2); + + CalloutHandle callout_handle(getCalloutManager()); + getCalloutManager()->callCallouts(alpha_index_, callout_handle); + + // Check result. For ease of visual checking, the expected string is + // divided into sections corresponding to the blocks of callouts above. + EXPECT_EQ(std::string("NNYY" "NNYYN" "NNYN"), common_string_); + + // ... and check that the skip flag on exit from callCallouts is set. + EXPECT_EQ(CalloutHandle::NEXT_STEP_SKIP, callout_handle.getStatus()); +} + +// Repeat the test, returning with the skip flag clear. +TEST_F(HandlesTest, ReturnSkipClear) { + getCalloutManager()->registerCallout("alpha", calloutSetSkip, 0); + getCalloutManager()->registerCallout("alpha", calloutSetSkip, 0); + getCalloutManager()->registerCallout("alpha", calloutClearSkip, 0); + + getCalloutManager()->registerCallout("alpha", calloutPrintSkip, 1); + getCalloutManager()->registerCallout("alpha", calloutSetSkip, 1); + getCalloutManager()->registerCallout("alpha", calloutClearSkip, 1); + getCalloutManager()->registerCallout("alpha", calloutSetSkip, 1); + getCalloutManager()->registerCallout("alpha", calloutClearSkip, 1); + getCalloutManager()->registerCallout("alpha", calloutClearSkip, 1); + + getCalloutManager()->registerCallout("alpha", calloutClearSkip, 2); + getCalloutManager()->registerCallout("alpha", calloutPrintSkip, 2); + getCalloutManager()->registerCallout("alpha", calloutSetSkip, 2); + getCalloutManager()->registerCallout("alpha", calloutClearSkip, 2); + + CalloutHandle callout_handle(getCalloutManager()); + getCalloutManager()->callCallouts(alpha_index_, callout_handle); + + // Check result. For ease of visual checking, the expected string is + // divided into sections corresponding to the blocks of callouts above. + EXPECT_EQ(std::string("NYY" "NNYNYN" "NNNY"), common_string_); + + // ... and check that the skip flag on exit from callCallouts is set. + EXPECT_EQ(CalloutHandle::NEXT_STEP_CONTINUE, callout_handle.getStatus()); +} + +// Check that the skip flag is cleared when callouts are called - even if +// there are no callouts. + +TEST_F(HandlesTest, NoCalloutsSkipTest) { + // Note - no callouts are registered on any hook. + CalloutHandle callout_handle(getCalloutManager()); + + // Clear the skip flag and call a hook with no callouts. + callout_handle.setStatus(CalloutHandle::NEXT_STEP_CONTINUE); + getCalloutManager()->callCallouts(alpha_index_, callout_handle); + EXPECT_EQ(CalloutHandle::NEXT_STEP_CONTINUE, callout_handle.getStatus()); + + // Set the skip flag and call a hook with no callouts. + callout_handle.setStatus(CalloutHandle::NEXT_STEP_SKIP); + getCalloutManager()->callCallouts(alpha_index_, callout_handle); + EXPECT_EQ(CalloutHandle::NEXT_STEP_CONTINUE, callout_handle.getStatus()); +} + +// The next set of callouts do a similar thing to the above "skip" tests, +// but alter the value of a string argument. This is for testing that the +// a callout is able to change an argument and return it to the caller. + +const char* MODIFIED_ARG = "modified_arg"; + +int +calloutSetArgumentCommon(CalloutHandle& handle, const char* what) { + std::string modified_arg = ""; + + handle.getArgument(MODIFIED_ARG, modified_arg); + modified_arg = modified_arg + std::string(what); + handle.setArgument(MODIFIED_ARG, modified_arg); + return (0); +} + +int +calloutSetArgumentSkip(CalloutHandle& handle) { + return (calloutSetArgumentCommon(handle, "S")); +} + +int +calloutSetArgumentContinue(CalloutHandle& handle) { + return (calloutSetArgumentCommon(handle, "C")); +} + +int +calloutSetArgumentDrop(CalloutHandle& handle) { + return (calloutSetArgumentCommon(handle, "D")); +} + +int +calloutSetArgumentPark(CalloutHandle& handle) { + return (calloutSetArgumentCommon(handle, "P")); +} + +// ... and a callout to just copy the argument to the "common_string_" variable +// but otherwise not alter it. + +int +calloutPrintArgument(CalloutHandle& handle) { + handle.getArgument(MODIFIED_ARG, HandlesTest::common_string_); + return (0); +} + +// This test verifies that the next step status is processed appropriately. +// The test checks the following next step statuses: CONTINUE, SKIP, DROP. +TEST_F(HandlesTest, CheckModifiedArgument) { + getCalloutManager()->registerCallout("alpha", calloutSetArgumentSkip, 0); + getCalloutManager()->registerCallout("alpha", calloutSetArgumentContinue, 0); + getCalloutManager()->registerCallout("alpha", calloutSetArgumentContinue, 0); + + getCalloutManager()->registerCallout("alpha", calloutSetArgumentSkip, 1); + getCalloutManager()->registerCallout("alpha", calloutSetArgumentDrop, 1); + getCalloutManager()->registerCallout("alpha", calloutPrintArgument, 1); + getCalloutManager()->registerCallout("alpha", calloutSetArgumentDrop, 1); + getCalloutManager()->registerCallout("alpha", calloutSetArgumentContinue, 1); + + getCalloutManager()->registerCallout("alpha", calloutSetArgumentSkip, 2); + getCalloutManager()->registerCallout("alpha", calloutSetArgumentContinue, 2); + getCalloutManager()->registerCallout("alpha", calloutSetArgumentPark, 2); + getCalloutManager()->registerCallout("alpha", calloutSetArgumentSkip, 2); + getCalloutManager()->registerCallout("alpha", calloutSetArgumentPark, 2); + + // Create the argument with an initial empty string value. Then call the + // sequence of callouts above. + CalloutHandle callout_handle(getCalloutManager()); + std::string modified_arg = ""; + callout_handle.setArgument(MODIFIED_ARG, modified_arg); + getCalloutManager()->callCallouts(alpha_index_, callout_handle); + + // Check the intermediate and results. For visual checking, the expected + // string is divided into sections corresponding to the blocks of callouts + // above. + EXPECT_EQ(std::string("SCC" "SD"), common_string_); + + callout_handle.getArgument(MODIFIED_ARG, modified_arg); + EXPECT_EQ(std::string("SCC" "SDDC" "SCPSP"), modified_arg); +} + +// Test that the CalloutHandle provides the name of the hook to which the +// callout is attached. + +int +callout_hook_name(CalloutHandle& callout_handle) { + HandlesTest::common_string_ = callout_handle.getHookName(); + return (0); +} + +int +callout_hook_dummy(CalloutHandle&) { + return (0); +} + +TEST_F(HandlesTest, HookName) { + getCalloutManager()->registerCallout("alpha", callout_hook_name, 0); + getCalloutManager()->registerCallout("beta", callout_hook_name, 0); + + // Call alpha and beta callouts and check the hook to which they belong. + CalloutHandle callout_handle(getCalloutManager()); + + EXPECT_EQ(std::string(""), HandlesTest::common_string_); + + getCalloutManager()->callCallouts(alpha_index_, callout_handle); + EXPECT_EQ(std::string("alpha"), HandlesTest::common_string_); + + getCalloutManager()->callCallouts(beta_index_, callout_handle); + EXPECT_EQ(std::string("beta"), HandlesTest::common_string_); + + // Make sure that the callout accesses the name even if it is not the + // only callout in the list. + getCalloutManager()->registerCallout("gamma", callout_hook_dummy, 1); + getCalloutManager()->registerCallout("gamma", callout_hook_name, 1); + getCalloutManager()->registerCallout("gamma", callout_hook_dummy, 1); + + EXPECT_EQ(std::string("beta"), HandlesTest::common_string_); + getCalloutManager()->callCallouts(gamma_index_, callout_handle); + EXPECT_EQ(std::string("gamma"), HandlesTest::common_string_); +} + +} // Anonymous namespace + diff --git a/src/lib/hooks/tests/hooks_manager_unittest.cc b/src/lib/hooks/tests/hooks_manager_unittest.cc new file mode 100644 index 0000000..a36d42c --- /dev/null +++ b/src/lib/hooks/tests/hooks_manager_unittest.cc @@ -0,0 +1,1081 @@ +// Copyright (C) 2013-2021 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <hooks/callout_handle.h> +#include <hooks/hooks_manager.h> +#include <hooks/server_hooks.h> + +#include <hooks/tests/common_test_class.h> +#define TEST_ASYNC_CALLOUT +#include <hooks/tests/test_libraries.h> +#include <cc/data.h> + +#include <boost/shared_ptr.hpp> +#include <gtest/gtest.h> + +#include <algorithm> +#include <fstream> +#include <string> + +#include <unistd.h> + +using namespace isc; +using namespace isc::hooks; +using namespace isc::data; +using namespace std; + +namespace { + +/// @brief Hooks manager collection test class + +class HooksManagerTest : public ::testing::Test, + public HooksCommonTestClass { +public: + /// @brief Constructor + /// + /// Reset the hooks manager. The hooks manager is a singleton, so needs + /// to be reset for each test. + HooksManagerTest() { + HooksManager::setTestMode(false); + HooksManager::prepareUnloadLibraries(); + bool status = HooksManager::unloadLibraries(); + if (!status) { + cerr << "(fixture ctor) unloadLibraries failed" << endl; + } + // Ensure the marker file is not present at the start of a test. + static_cast<void>(remove(MARKER_FILE)); + } + + /// @brief Destructor + /// + /// Unload all libraries. + ~HooksManagerTest() { + static_cast<void>(remove(MARKER_FILE)); + HooksManager::setTestMode(false); + HooksManager::prepareUnloadLibraries(); + bool status = HooksManager::unloadLibraries(); + if (!status) { + cerr << "(fixture dtor) unloadLibraries failed" << endl; + } + } + + /// @brief Marker file present + /// + /// Convenience function to check whether a marker file is present. It + /// does this by opening the file. + /// + /// @return true if the marker file is present. + bool markerFilePresent() const { + + // Try to open it. + std::fstream marker; + marker.open(MARKER_FILE, std::fstream::in); + + // Check if it is open and close it if so. + bool exists = marker.is_open(); + if (exists) { + marker.close(); + } + + return (exists); + } + + /// @brief Call callouts test + /// + /// See the header for HooksCommonTestClass::execute for details. + /// + /// @param r0...r3, d1..d3 Values and intermediate values expected. They + /// are ordered so that the variables appear in the argument list in + /// the order they are used. + void executeCallCallouts(int r0, int d1, int r1, int d2, int r2, int d3, + int r3) { + static const char* COMMON_TEXT = " callout returned the wrong value"; + static const char* RESULT = "result"; + + // Get a CalloutHandle for the calculation. + CalloutHandlePtr handle = HooksManager::createCalloutHandle(); + + // Initialize the argument RESULT. This simplifies testing by + // eliminating the generation of an exception when we try the unload + // test. In that case, RESULT is unchanged. + int result = -1; + handle->setArgument(RESULT, result); + + // Seed the calculation. + HooksManager::callCallouts(isc::hooks::ServerHooks::CONTEXT_CREATE, + *handle); + handle->getArgument(RESULT, result); + EXPECT_EQ(r0, result) << "context_create" << COMMON_TEXT; + + // Perform the first calculation. + handle->setArgument("data_1", d1); + HooksManager::callCallouts(hookpt_one_index_, *handle); + handle->getArgument(RESULT, result); + EXPECT_EQ(r1, result) << "hookpt_one" << COMMON_TEXT; + + // ... the second ... + handle->setArgument("data_2", d2); + HooksManager::callCallouts(hookpt_two_index_, *handle); + handle->getArgument(RESULT, result); + EXPECT_EQ(r2, result) << "hookpt_two" << COMMON_TEXT; + + // ... and the third. + handle->setArgument("data_3", d3); + HooksManager::callCallouts(hookpt_three_index_, *handle); + handle->getArgument(RESULT, result); + EXPECT_EQ(r3, result) << "hookpt_three" << COMMON_TEXT; + } + + /// @brief Call command handlers test. + /// + /// This test is similar to @c executeCallCallouts but it uses + /// @ref HooksManager::callCommandHandlers to execute the command + /// handlers for the following commands: 'command-one' and 'command-two'. + /// + /// @param r1..r2, d1..d2 Data (dN) and expected results (rN). + void executeCallCommandHandlers(int d1, int r1, int d2, int r2) { + static const char* COMMON_TEXT = " command handler returned the wrong value"; + static const char* RESULT = "result"; + + int result; + + // Set up a callout handle for the calls. + CalloutHandlePtr handle = HooksManager::createCalloutHandle(); + + // Initialize the argument RESULT. This simplifies testing by + // eliminating the generation of an exception when we try the unload + // test. In that case, RESULT is unchanged. + handle->setArgument(RESULT, -1); + + // Perform the first calculation: it should assign the data to the + // result. + handle->setArgument("data_1", d1); + HooksManager::callCommandHandlers("command-one", *handle); + handle->getArgument(RESULT, result); + EXPECT_EQ(r1, result) << "command-one" << COMMON_TEXT; + + // Perform the second calculation: it should multiply the data by 10 + // and return in the result. + handle->setArgument("data_2", d2); + HooksManager::callCommandHandlers("command-two", *handle); + handle->getArgument(RESULT, result); + EXPECT_EQ(r2, result) << "command-two" << COMMON_TEXT; + } + +private: + /// To avoid unused variable errors + std::string dummy(int i) { + if (i == 0) { + return (LOAD_CALLOUT_LIBRARY); + } else { + return (LOAD_ERROR_CALLOUT_LIBRARY); + } + } +}; + +// This is effectively the same test as for LibraryManager, but using the +// HooksManager object. + +TEST_F(HooksManagerTest, LoadLibraries) { + + // Set up the list of libraries to be loaded. + HookLibsCollection library_names; + library_names.push_back(make_pair(std::string(FULL_CALLOUT_LIBRARY), + data::ConstElementPtr())); + library_names.push_back(make_pair(std::string(BASIC_CALLOUT_LIBRARY), + data::ConstElementPtr())); + + // Load the libraries. + EXPECT_TRUE(HooksManager::loadLibraries(library_names)); + + // Execute the callouts. The first library implements the calculation. + // + // r3 = (7 * d1 - d2) * d3 + // + // The last-loaded library implements the calculation + // + // r3 = (10 + d1) * d2 - d3 + // + // Putting the processing for each library together in the appropriate + // order, we get: + // + // r3 = ((10 * d1 + d1) - d2) * d2 * d3 - d3 + { + SCOPED_TRACE("Calculation with libraries loaded"); + executeCallCallouts(10, 3, 33, 2, 62, 3, 183); + } + + // r2 = 5 * 7 * 10 + { + SCOPED_TRACE("Calculation using command handlers"); + executeCallCommandHandlers(5, 5, 7, 350); + } + + // Try unloading the libraries. + EXPECT_NO_THROW(HooksManager::prepareUnloadLibraries()); + bool status = false; + EXPECT_NO_THROW(status = HooksManager::unloadLibraries()); + EXPECT_TRUE(status); + + // Re-execute the calculation - callouts can be called but as nothing + // happens, the result should always be -1. + { + SCOPED_TRACE("Calculation with libraries not loaded"); + executeCallCallouts(-1, 3, -1, 22, -1, 83, -1); + } +} + +// This is effectively the same test as above, but with a library generating +// an error when loaded. It is expected that the failing library will not be +// loaded, but others will be. + +TEST_F(HooksManagerTest, LoadLibrariesWithError) { + + // Set up the list of libraries to be loaded. + HookLibsCollection library_names; + library_names.push_back(make_pair(std::string(FULL_CALLOUT_LIBRARY), + data::ConstElementPtr())); + library_names.push_back(make_pair(std::string(INCORRECT_VERSION_LIBRARY), + data::ConstElementPtr())); + library_names.push_back(make_pair(std::string(BASIC_CALLOUT_LIBRARY), + data::ConstElementPtr())); + + // Load the libraries. We expect a failure return because one of the + // libraries fails to load. + EXPECT_FALSE(HooksManager::loadLibraries(library_names)); +} + +// Test that we can unload a set of libraries while we have a CalloutHandle +// created on them in existence, and can delete the handle afterwards. + +TEST_F(HooksManagerTest, CalloutHandleUnloadLibrary) { + + // Set up the list of libraries to be loaded. + HookLibsCollection library_names; + library_names.push_back(make_pair(std::string(FULL_CALLOUT_LIBRARY), + data::ConstElementPtr())); + + // Load the libraries. + EXPECT_TRUE(HooksManager::loadLibraries(library_names)); + + // Execute the callouts. This library implements: + // + // r3 = (7 * d1 - d2) * d3 + { + SCOPED_TRACE("Calculation with full callout library loaded"); + executeCallCallouts(7, 4, 28, 8, 20, 2, 40); + } + + // Get an outstanding callout handle on this library. + CalloutHandlePtr handle = HooksManager::createCalloutHandle(); + + // Execute once of the callouts again to ensure that the handle contains + // memory allocated by the library. + HooksManager::callCallouts(ServerHooks::CONTEXT_CREATE, *handle); + + // Unload the libraries. + HooksManager::prepareUnloadLibraries(); + EXPECT_FALSE(HooksManager::unloadLibraries()); + + // Deleting the callout handle should not cause a segmentation fault. + handle.reset(); + + // And allows unload. + EXPECT_TRUE(HooksManager::unloadLibraries()); +} + +// Test that we can load a new set of libraries while we have a CalloutHandle +// created on them in existence, and can delete the handle afterwards. + +TEST_F(HooksManagerTest, CalloutHandleLoadLibrary) { + + // Set up the list of libraries to be loaded. + HookLibsCollection library_names; + library_names.push_back(make_pair(std::string(FULL_CALLOUT_LIBRARY), + data::ConstElementPtr())); + + // Load the libraries. + EXPECT_TRUE(HooksManager::loadLibraries(library_names)); + + // Execute the callouts. This library implements: + // + // r3 = (7 * d1 - d2) * d3 + { + SCOPED_TRACE("Calculation with full callout library loaded"); + executeCallCallouts(7, 4, 28, 8, 20, 2, 40); + } + + // Get an outstanding callout handle on this library and execute one of + // the callouts again to ensure that the handle contains memory allocated + // by the library. + CalloutHandlePtr handle = HooksManager::createCalloutHandle(); + HooksManager::callCallouts(ServerHooks::CONTEXT_CREATE, *handle); + + // Load a new library that implements the calculation + // + // r3 = (10 + d1) * d2 - d3 + HookLibsCollection new_library_names; + new_library_names.push_back(make_pair(std::string(BASIC_CALLOUT_LIBRARY), + data::ConstElementPtr())); + + // Load the libraries. + EXPECT_THROW(HooksManager::loadLibraries(new_library_names), + LibrariesStillOpened); + + // Deleting the old callout handle should not cause a segmentation fault. + handle.reset(); + + // But it allows the load of the new library. + EXPECT_TRUE(HooksManager::loadLibraries(new_library_names)); + + // Execute the calculation. + { + SCOPED_TRACE("Calculation with basic callout library loaded"); + executeCallCallouts(10, 7, 17, 3, 51, 16, 35); + } +} + +// This is effectively the same test as the LoadLibraries test. + +TEST_F(HooksManagerTest, ReloadSameLibraries) { + + // Set up the list of libraries to be loaded. + HookLibsCollection library_names; + library_names.push_back(make_pair(std::string(FULL_CALLOUT_LIBRARY), + data::ConstElementPtr())); + library_names.push_back(make_pair(std::string(BASIC_CALLOUT_LIBRARY), + data::ConstElementPtr())); + + // Load the libraries. + EXPECT_TRUE(HooksManager::loadLibraries(library_names)); + + // Execute the callouts. See the LoadLibraries test for an explanation of + // the calculation. + { + SCOPED_TRACE("Calculation with libraries loaded"); + executeCallCallouts(10, 3, 33, 2, 62, 3, 183); + } + + // Try reloading the libraries and re-execute the calculation - we should + // get the same results. + EXPECT_NO_THROW(HooksManager::loadLibraries(library_names)); + { + SCOPED_TRACE("Calculation with libraries reloaded"); + executeCallCallouts(10, 3, 33, 2, 62, 3, 183); + } +} + +TEST_F(HooksManagerTest, ReloadLibrariesReverseOrder) { + + // Set up the list of libraries to be loaded and load them. + HookLibsCollection library_names; + library_names.push_back(make_pair(std::string(FULL_CALLOUT_LIBRARY), + data::ConstElementPtr())); + library_names.push_back(make_pair(std::string(BASIC_CALLOUT_LIBRARY), + data::ConstElementPtr())); + EXPECT_TRUE(HooksManager::loadLibraries(library_names)); + + // Execute the callouts. The first library implements the calculation. + // + // r3 = (7 * d1 - d2) * d3 + // + // The last-loaded library implements the calculation + // + // r3 = (10 + d1) * d2 - d3 + // + // Putting the processing for each library together in the given order + // gives. + // + // r3 = ((10 * d1 + d1) - d2) * d2 * d3 - d3 + { + SCOPED_TRACE("Calculation with libraries loaded"); + executeCallCallouts(10, 3, 33, 2, 62, 3, 183); + } + + // Reload the libraries in the reverse order. + std::reverse(library_names.begin(), library_names.end()); + EXPECT_TRUE(HooksManager::loadLibraries(library_names)); + + // The calculation in the reverse order gives: + // + // r3 = ((((7 + d1) * d1) * d2 - d2) - d3) * d3 + { + SCOPED_TRACE("Calculation with libraries loaded in reverse order"); + executeCallCallouts(7, 3, 30, 3, 87, 7, 560); + } +} + +// Local callouts for the test of server-registered callouts. + +namespace { + + int +testPreCallout(CalloutHandle& handle) { + handle.setArgument("result", static_cast<int>(1027)); + return (0); +} + +int +testPostCallout(CalloutHandle& handle) { + int result; + handle.getArgument("result", result); + result *= 2; + handle.setArgument("result", result); + return (0); +} + +} + +// The next test registers the pre and post- callouts above for hook hookpt_two, +// and checks they are called. + +TEST_F(HooksManagerTest, PrePostCalloutTest) { + + // Load a single library. + HookLibsCollection library_names; + library_names.push_back(make_pair(std::string(FULL_CALLOUT_LIBRARY), + data::ConstElementPtr())); + EXPECT_TRUE(HooksManager::loadLibraries(library_names)); + + // Load the pre- and post- callouts. + HooksManager::preCalloutsLibraryHandle().registerCallout("hookpt_two", + testPreCallout); + HooksManager::postCalloutsLibraryHandle().registerCallout("hookpt_two", + testPostCallout); + + // Execute the callouts. hookpt_two implements the calculation: + // + // "result - data_2" + // + // With the pre- and post- callouts above, the result expected is + // + // (1027 - data_2) * 2 + CalloutHandlePtr handle = HooksManager::createCalloutHandle(); + handle->setArgument("result", static_cast<int>(0)); + handle->setArgument("data_2", static_cast<int>(15)); + + HooksManager::callCallouts(hookpt_two_index_, *handle); + + int result = 0; + handle->getArgument("result", result); + EXPECT_EQ(2024, result); + + // Reset the handle to allow a reload. + handle.reset(); + + // ... and check that the pre- and post- callout functions don't survive a + // reload. + EXPECT_NO_THROW(HooksManager::prepareUnloadLibraries()); + bool status = false; + EXPECT_NO_THROW(status = HooksManager::unloadLibraries()); + EXPECT_TRUE(status); + EXPECT_TRUE(HooksManager::loadLibraries(library_names)); + handle = HooksManager::createCalloutHandle(); + + handle->setArgument("result", static_cast<int>(0)); + handle->setArgument("data_2", static_cast<int>(15)); + + HooksManager::callCallouts(hookpt_two_index_, *handle); + + result = 0; + handle->getArgument("result", result); + EXPECT_EQ(-15, result); +} + +// Test with test mode enabled and the pre- and post- callout functions survive +// a reload + +TEST_F(HooksManagerTest, TestModeEnabledPrePostSurviveLoad) { + + // Load a single library. + HookLibsCollection library_names; + library_names.push_back(make_pair(std::string(FULL_CALLOUT_LIBRARY), + data::ConstElementPtr())); + + // Load the pre- and post- callouts. + HooksManager::preCalloutsLibraryHandle().registerCallout("hookpt_two", + testPreCallout); + HooksManager::postCalloutsLibraryHandle().registerCallout("hookpt_two", + testPostCallout); + + HooksManager::setTestMode(true); + + // With the pre- and post- callouts above, the result expected is + // + // 1027 * 2 + CalloutHandlePtr handle = HooksManager::createCalloutHandle(); + handle->setArgument("result", static_cast<int>(0)); + handle->setArgument("data_2", static_cast<int>(15)); + + HooksManager::callCallouts(hookpt_two_index_, *handle); + + int result = 0; + handle->getArgument("result", result); + EXPECT_EQ(2054, result); + + // Reset the handle to allow a reload. + handle.reset(); + + // ... and check that the pre- and post- callout functions survive a + // reload. + EXPECT_NO_THROW(HooksManager::prepareUnloadLibraries()); + bool status = false; + EXPECT_NO_THROW(status = HooksManager::unloadLibraries()); + EXPECT_TRUE(status); + EXPECT_TRUE(HooksManager::loadLibraries(library_names)); + handle = HooksManager::createCalloutHandle(); + + handle->setArgument("result", static_cast<int>(0)); + handle->setArgument("data_2", static_cast<int>(15)); + + HooksManager::callCallouts(hookpt_two_index_, *handle); + + // Expect same value i.e. 1027 * 2 + result = 0; + handle->getArgument("result", result); + EXPECT_EQ(2054, result); +} + +// Test with test mode disabled and the pre- and post- callout functions do not +// survive a reload + +TEST_F(HooksManagerTest, TestModeDisabledPrePostDoNotSurviveLoad) { + + // Load a single library. + HookLibsCollection library_names; + library_names.push_back(make_pair(std::string(FULL_CALLOUT_LIBRARY), + data::ConstElementPtr())); + + // Load the pre- and post- callouts. + HooksManager::preCalloutsLibraryHandle().registerCallout("hookpt_two", + testPreCallout); + HooksManager::postCalloutsLibraryHandle().registerCallout("hookpt_two", + testPostCallout); + + HooksManager::setTestMode(false); + + // With the pre- and post- callouts above, the result expected is + // + // 1027 * 2 + CalloutHandlePtr handle = HooksManager::createCalloutHandle(); + handle->setArgument("result", static_cast<int>(0)); + handle->setArgument("data_2", static_cast<int>(15)); + + HooksManager::callCallouts(hookpt_two_index_, *handle); + + int result = 0; + handle->getArgument("result", result); + EXPECT_EQ(2054, result); + + // Reset the handle to allow a reload. + handle.reset(); + + // ... and check that the pre- and post- callout functions don't survive a + // reload. + EXPECT_NO_THROW(HooksManager::prepareUnloadLibraries()); + bool status = false; + EXPECT_NO_THROW(status = HooksManager::unloadLibraries()); + EXPECT_TRUE(status); + EXPECT_TRUE(HooksManager::loadLibraries(library_names)); + handle = HooksManager::createCalloutHandle(); + + handle->setArgument("result", static_cast<int>(0)); + handle->setArgument("data_2", static_cast<int>(15)); + + HooksManager::callCallouts(hookpt_two_index_, *handle); + + result = 0; + handle->getArgument("result", result); + EXPECT_EQ(-15, result); +} + +// Test with test mode enabled and the pre- and post- callout functions do not +// survive a reload if the test mode is set too late. + +TEST_F(HooksManagerTest, TestModeEnabledTooLatePrePostDoNotSurvive) { + + // Load a single library. + HookLibsCollection library_names; + library_names.push_back(make_pair(std::string(FULL_CALLOUT_LIBRARY), + data::ConstElementPtr())); + + // Load the pre- and post- callouts. + HooksManager::preCalloutsLibraryHandle().registerCallout("hookpt_two", + testPreCallout); + HooksManager::postCalloutsLibraryHandle().registerCallout("hookpt_two", + testPostCallout); + + // With the pre- and post- callouts above, the result expected is + // + // 1027 * 2 + CalloutHandlePtr handle = HooksManager::createCalloutHandle(); + handle->setArgument("result", static_cast<int>(0)); + handle->setArgument("data_2", static_cast<int>(15)); + + HooksManager::callCallouts(hookpt_two_index_, *handle); + + int result = 0; + handle->getArgument("result", result); + EXPECT_EQ(2054, result); + + // Reset the handle to allow a reload. + handle.reset(); + + // ... and check that the pre- and post- callout functions don't survive a + // reload. + EXPECT_NO_THROW(HooksManager::prepareUnloadLibraries()); + bool status = false; + EXPECT_NO_THROW(status = HooksManager::unloadLibraries()); + EXPECT_TRUE(status); + EXPECT_TRUE(HooksManager::loadLibraries(library_names)); + handle = HooksManager::createCalloutHandle(); + + HooksManager::setTestMode(true); + + handle->setArgument("result", static_cast<int>(0)); + handle->setArgument("data_2", static_cast<int>(15)); + + HooksManager::callCallouts(hookpt_two_index_, *handle); + + result = 0; + handle->getArgument("result", result); + EXPECT_EQ(-15, result); +} + +// Check that everything works even with no libraries loaded. First that +// calloutsPresent() always returns false. + +TEST_F(HooksManagerTest, NoLibrariesCalloutsPresent) { + // No callouts should be present on any hooks. + EXPECT_FALSE(HooksManager::calloutsPresent(hookpt_one_index_)); + EXPECT_FALSE(HooksManager::calloutsPresent(hookpt_two_index_)); + EXPECT_FALSE(HooksManager::calloutsPresent(hookpt_three_index_)); + EXPECT_FALSE(HooksManager::commandHandlersPresent("command-one")); + EXPECT_FALSE(HooksManager::commandHandlersPresent("command-two")); +} + +TEST_F(HooksManagerTest, NoLibrariesCallCallouts) { + executeCallCallouts(-1, 3, -1, 22, -1, 83, -1); +} + +// Test the encapsulation of the ServerHooks::registerHook() method. + +TEST_F(HooksManagerTest, RegisterHooks) { + ServerHooks::getServerHooks().reset(); + EXPECT_EQ(2, ServerHooks::getServerHooks().getCount()); + + // Check that the hook indexes are as expected. (Use temporary variables + // as it appears that Google test can't access the constants.) + int sh_cc = ServerHooks::CONTEXT_CREATE; + int hm_cc = HooksManager::CONTEXT_CREATE; + EXPECT_EQ(sh_cc, hm_cc); + + int sh_cd = ServerHooks::CONTEXT_DESTROY; + int hm_cd = HooksManager::CONTEXT_DESTROY; + EXPECT_EQ(sh_cd, hm_cd); + + // Register a few hooks and check we have the indexes as expected. + EXPECT_EQ(2, HooksManager::registerHook(string("alpha"))); + EXPECT_EQ(3, HooksManager::registerHook(string("beta"))); + EXPECT_EQ(4, HooksManager::registerHook(string("gamma"))); + + + // The code used to throw, but it now allows to register the same + // hook several times. It simply returns existing index. + //EXPECT_THROW(static_cast<void>(HooksManager::registerHook(string("alpha"))), + // DuplicateHook); + EXPECT_EQ(2, HooksManager::registerHook(string("alpha"))); + + // ... an check the hooks are as we expect. + EXPECT_EQ(5, ServerHooks::getServerHooks().getCount()); + vector<string> names = ServerHooks::getServerHooks().getHookNames(); + sort(names.begin(), names.end()); + + EXPECT_EQ(string("alpha"), names[0]); + EXPECT_EQ(string("beta"), names[1]); + EXPECT_EQ(string("context_create"), names[2]); + EXPECT_EQ(string("context_destroy"), names[3]); + EXPECT_EQ(string("gamma"), names[4]); +} + +// Check that we can get the names of the libraries. + +TEST_F(HooksManagerTest, LibraryNames) { + + // Set up the list of libraries to be loaded. + HookLibsCollection library_names; + library_names.push_back(make_pair(std::string(FULL_CALLOUT_LIBRARY), + data::ConstElementPtr())); + library_names.push_back(make_pair(std::string(BASIC_CALLOUT_LIBRARY), + data::ConstElementPtr())); + + // Check the names before the libraries are loaded. + std::vector<std::string> loaded_names = HooksManager::getLibraryNames(); + EXPECT_TRUE(loaded_names.empty()); + + // Load the libraries and check the names again. + EXPECT_TRUE(HooksManager::loadLibraries(library_names)); + loaded_names = HooksManager::getLibraryNames(); + EXPECT_TRUE(extractNames(library_names) == loaded_names); + + // Unload the libraries and check again. + EXPECT_NO_THROW(HooksManager::prepareUnloadLibraries()); + bool status = false; + EXPECT_NO_THROW(status = HooksManager::unloadLibraries()); + EXPECT_TRUE(status); + loaded_names = HooksManager::getLibraryNames(); + EXPECT_TRUE(loaded_names.empty()); +} + +// Test the library validation function. + +TEST_F(HooksManagerTest, validateLibraries) { + // Vector of libraries that failed validation + std::vector<std::string> failed; + + // Test different vectors of libraries. + + // No libraries should return a success. + std::vector<std::string> libraries; + + failed = HooksManager::validateLibraries(libraries); + EXPECT_TRUE(failed.empty()); + + // Single valid library should validate. + libraries.clear(); + libraries.push_back(BASIC_CALLOUT_LIBRARY); + + failed = HooksManager::validateLibraries(libraries); + EXPECT_TRUE(failed.empty()); + + // Multiple valid libraries should succeed. + libraries.clear(); + libraries.push_back(BASIC_CALLOUT_LIBRARY); + libraries.push_back(FULL_CALLOUT_LIBRARY); + libraries.push_back(UNLOAD_CALLOUT_LIBRARY); + + failed = HooksManager::validateLibraries(libraries); + EXPECT_TRUE(failed.empty()); + + // Single invalid library should fail. + libraries.clear(); + libraries.push_back(NOT_PRESENT_LIBRARY); + + failed = HooksManager::validateLibraries(libraries); + EXPECT_TRUE(failed == libraries); + + // Multiple invalid libraries should fail. + libraries.clear(); + libraries.push_back(INCORRECT_VERSION_LIBRARY); + libraries.push_back(NO_VERSION_LIBRARY); + libraries.push_back(FRAMEWORK_EXCEPTION_LIBRARY); + + failed = HooksManager::validateLibraries(libraries); + EXPECT_TRUE(failed == libraries); + + // Combination of valid and invalid (first one valid) should fail. + libraries.clear(); + libraries.push_back(FULL_CALLOUT_LIBRARY); + libraries.push_back(INCORRECT_VERSION_LIBRARY); + libraries.push_back(NO_VERSION_LIBRARY); + + std::vector<std::string> expected_failures; + expected_failures.push_back(INCORRECT_VERSION_LIBRARY); + expected_failures.push_back(NO_VERSION_LIBRARY); + + failed = HooksManager::validateLibraries(libraries); + EXPECT_TRUE(failed == expected_failures); + + // Combination of valid and invalid (first one invalid) should fail. + libraries.clear(); + libraries.push_back(NO_VERSION_LIBRARY); + libraries.push_back(FULL_CALLOUT_LIBRARY); + libraries.push_back(INCORRECT_VERSION_LIBRARY); + + expected_failures.clear(); + expected_failures.push_back(NO_VERSION_LIBRARY); + expected_failures.push_back(INCORRECT_VERSION_LIBRARY); + + failed = HooksManager::validateLibraries(libraries); + EXPECT_TRUE(failed == expected_failures); +} + +// This test verifies that unload is called by the prepare method. +TEST_F(HooksManagerTest, prepareUnload) { + + // Set up the list of libraries to be loaded and load them. + HookLibsCollection library_names; + library_names.push_back(make_pair(std::string(UNLOAD_CALLOUT_LIBRARY), + data::ConstElementPtr())); + EXPECT_TRUE(HooksManager::loadLibraries(library_names)); + + // Check that the marker file is not present. + EXPECT_FALSE(markerFilePresent()); + + // Prepare unload libraries runs unload functions. + HooksManager::prepareUnloadLibraries(); + + // Now the marker file is present. + EXPECT_TRUE(markerFilePresent()); +} + +// This test verifies that the specified parameters are accessed properly. +TEST_F(HooksManagerTest, LibraryParameters) { + + // Prepare parameters for the callout parameters library. + ElementPtr params = Element::createMap(); + params->set("svalue", Element::create("string value")); + params->set("ivalue", Element::create(42)); + params->set("bvalue", Element::create(true)); + + // Set up the list of libraries to be loaded. + HookLibsCollection library_names; + library_names.push_back(make_pair(std::string(BASIC_CALLOUT_LIBRARY), + data::ConstElementPtr())); + library_names.push_back(make_pair(std::string(CALLOUT_PARAMS_LIBRARY), + params)); + + // Load the libraries. Note that callout params library checks if + // all mandatory parameters are there, so if anything is missing, its + // load() function will return error, thus causing the library to not + // load. + EXPECT_TRUE(HooksManager::loadLibraries(library_names)); + + // Try unloading the libraries. + EXPECT_NO_THROW(HooksManager::prepareUnloadLibraries()); + bool status = false; + EXPECT_NO_THROW(status = HooksManager::unloadLibraries()); + EXPECT_TRUE(status); +} + +// This test verifies that an object can be parked in two different +// callouts and that it is unparked when the last callout calls the +// unpark function. +TEST_F(HooksManagerTest, Parking) { + // Load the same library twice. Both installed callouts will trigger + // asynchronous operation. + HookLibsCollection library_names; + library_names.push_back(make_pair(std::string(ASYNC_CALLOUT_LIBRARY), + data::ConstElementPtr())); + library_names.push_back(make_pair(std::string(ASYNC_CALLOUT_LIBRARY), + data::ConstElementPtr())); + + // Load the libraries. + EXPECT_TRUE(HooksManager::loadLibraries(library_names)); + + CalloutHandlePtr handle = HooksManager::createCalloutHandle(); + + // We could be parked any object. Typically it will be a pointer to the + // packet. In this case, however, it is simpler to just use a string. + std::string parked_object = "foo"; + handle->setArgument("parked_object", parked_object); + + // This boolean value will be set to true when the packet gets unparked. + bool unparked = false; + + // The callouts instruct us to park the object. We associated the callback + // function with the parked object, which sets "unparked" flag to true. We + // can later test the value of this flag to verify when exactly the packet + // got unparked. + ASSERT_NO_THROW( + HooksManager::park<std::string>("hookpt_one", "foo", + [&unparked] { + unparked = true; + }) + ); + + // Call both installed callouts. + HooksManager::callCallouts(hookpt_one_index_, *handle); + + // We have two callouts which should have returned pointers to the + // functions which we can call to simulate completion of asynchronous + // tasks. + std::function<void()> unpark_trigger_func1; + handle->getArgument("unpark_trigger1", unpark_trigger_func1); + // Call the first function. It should cause the hook library to call the + // "unpark" function. However, the object should not be unparked yet, + // because the other callout hasn't completed its scheduled asynchronous + // operation (keeps a reference on the parked object). + unpark_trigger_func1(); + EXPECT_FALSE(unparked); + + // Call the second function. This should decrease the reference count to + // 0 and the packet should be unparked. + std::function<void()> unpark_trigger_func2; + handle->getArgument("unpark_trigger2", unpark_trigger_func2); + unpark_trigger_func2(); + EXPECT_TRUE(unparked); + + // Resetting the handle makes return from test body to crash. + + // Try unloading the libraries. + EXPECT_NO_THROW(HooksManager::prepareUnloadLibraries()); + bool status = false; + EXPECT_NO_THROW(status = HooksManager::unloadLibraries()); + // Handle is still active. + EXPECT_FALSE(status); +} + +// This test verifies that the server can also unpark the packet. +TEST_F(HooksManagerTest, ServerUnpark) { + // Load the same library twice. Both installed callouts will trigger + // asynchronous operation. + HookLibsCollection library_names; + library_names.push_back(make_pair(std::string(ASYNC_CALLOUT_LIBRARY), + data::ConstElementPtr())); + library_names.push_back(make_pair(std::string(ASYNC_CALLOUT_LIBRARY), + data::ConstElementPtr())); + // Load libraries. + EXPECT_TRUE(HooksManager::loadLibraries(library_names)); + + CalloutHandlePtr handle = HooksManager::createCalloutHandle(); + + // We could be parked any object. Typically it will be a pointer to the + // packet. In this case, however, it is simpler to just use a string. + std::string parked_object = "foo"; + handle->setArgument("parked_object", parked_object); + + // This boolean value will be set to true when the packet gets unparked. + bool unparked = false; + + // The callouts instruct us to park the object. We associated the callback + // function with the parked object, which sets "unparked" flag to true. We + // can later test the value of this flag to verify when exactly the packet + // got unparked. + HooksManager::park<std::string>("hookpt_one", "foo", + [&unparked] { + unparked = true; + }); + + // It should be possible for the server to increase reference counter. + ASSERT_NO_THROW(HooksManager::reference<std::string>("hookpt_one", "foo")); + + // Call installed callout. + HooksManager::callCallouts(hookpt_one_index_, *handle); + + // Server can force unparking the object. + EXPECT_TRUE(HooksManager::unpark<std::string>("hookpt_one", "foo")); + + EXPECT_TRUE(unparked); + + // Reset the handle to allow unload. + handle.reset(); + + // Try unloading the libraries. + EXPECT_NO_THROW(HooksManager::prepareUnloadLibraries()); + bool status = false; + EXPECT_NO_THROW(status = HooksManager::unloadLibraries()); + EXPECT_TRUE(status); +} + +// This test verifies that the server can drop parked packet. +TEST_F(HooksManagerTest, ServerDropParked) { + // Load library. + HookLibsCollection library_names; + library_names.push_back(make_pair(std::string(ASYNC_CALLOUT_LIBRARY), + data::ConstElementPtr())); + EXPECT_TRUE(HooksManager::loadLibraries(library_names)); + + CalloutHandlePtr handle = HooksManager::createCalloutHandle(); + + // We could be parked any object. Typically it will be a pointer to the + // packet. In this case, however, it is simpler to just use a string. + std::string parked_object = "foo"; + handle->setArgument("parked_object", parked_object); + + // This boolean value will be set to true when the packet gets unparked. + bool unparked = false; + + // The callouts instruct us to park the object. We associated the callback + // function with the parked object, which sets "unparked" flag to true. We + // can later test the value of this flag to verify when exactly the packet + // got unparked. + HooksManager::park<std::string>("hookpt_one", "foo", + [&unparked] { + unparked = true; + }); + + // It should be possible for the server to increase reference counter. + ASSERT_NO_THROW(HooksManager::reference<std::string>("hookpt_one", "foo")); + + // Call installed callout. + HooksManager::callCallouts(hookpt_one_index_, *handle); + + // Drop the parked packet. The callback should not be called. + EXPECT_TRUE(HooksManager::drop<std::string>("hookpt_one", "foo")); + + EXPECT_FALSE(unparked); + + // An attempt to unpark the packet should return false, as this packet + // is not parked anymore. + EXPECT_FALSE(HooksManager::unpark<std::string>("hookpt_one", "foo")); + + // Reset the handle to allow unload. + handle.reset(); + + // Try unloading the libraries. + EXPECT_NO_THROW(HooksManager::prepareUnloadLibraries()); + bool status = false; + EXPECT_NO_THROW(status = HooksManager::unloadLibraries()); + EXPECT_TRUE(status); +} + +// This test verifies that parked objects are removed when libraries are +// unloaded. +TEST_F(HooksManagerTest, UnloadBeforeUnpark) { + // Load the same library twice. Both installed callouts will trigger + // asynchronous operation. + HookLibsCollection library_names; + library_names.push_back(make_pair(std::string(ASYNC_CALLOUT_LIBRARY), + data::ConstElementPtr())); + library_names.push_back(make_pair(std::string(ASYNC_CALLOUT_LIBRARY), + data::ConstElementPtr())); + // Load libraries. + EXPECT_TRUE(HooksManager::loadLibraries(library_names)); + + CalloutHandlePtr handle = HooksManager::createCalloutHandle(); + + // We could be parked any object. Typically it will be a pointer to the + // packet. In this case, however, it is simpler to just use a string. + std::string parked_object = "foo"; + handle->setArgument("parked_object", parked_object); + + // This boolean value will be set to true when the packet gets unparked. + bool unparked = false; + + // The callouts instruct us to park the object. We associated the callback + // function with the parked object, which sets "unparked" flag to true. We + // can later test the value of this flag to verify when exactly the packet + // got unparked. + HooksManager::park<std::string>("hookpt_one", "foo", + [&unparked] { + unparked = true; + }); + + // Call installed callout. + HooksManager::callCallouts(hookpt_one_index_, *handle); + + // Reset the handle to allow a reload. + handle.reset(); + + // Try reloading the libraries. + EXPECT_NO_THROW(HooksManager::prepareUnloadLibraries()); + bool status = false; + EXPECT_NO_THROW(status = HooksManager::unloadLibraries()); + EXPECT_TRUE(status); + EXPECT_TRUE(HooksManager::loadLibraries(library_names)); + + // Parked object should have been removed. + EXPECT_FALSE(HooksManager::unpark<std::string>("hookpt_one", "foo")); + + // Callback should not be called. + EXPECT_FALSE(unparked); +} + + +} // Anonymous namespace diff --git a/src/lib/hooks/tests/incorrect_version_library.cc b/src/lib/hooks/tests/incorrect_version_library.cc new file mode 100644 index 0000000..f4d7850 --- /dev/null +++ b/src/lib/hooks/tests/incorrect_version_library.cc @@ -0,0 +1,26 @@ +// Copyright (C) 2013-2015 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +/// @file +/// @brief Incorrect version function test +/// +/// This is source of a test library for various test (LibraryManager and +/// HooksManager). The characteristics of the library produced from this +/// file are: +/// +/// - It contains the version() framework function only, which returns an +/// incorrect version number. + +#include <config.h> +#include <hooks/hooks.h> + +extern "C" { + +int version() { + return (KEA_HOOKS_VERSION + 1); +} + +}; diff --git a/src/lib/hooks/tests/library_manager_collection_unittest.cc b/src/lib/hooks/tests/library_manager_collection_unittest.cc new file mode 100644 index 0000000..9b30cb3 --- /dev/null +++ b/src/lib/hooks/tests/library_manager_collection_unittest.cc @@ -0,0 +1,314 @@ +// Copyright (C) 2013-2020 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <hooks/callout_handle.h> +#include <hooks/callout_manager.h> +#include <hooks/library_manager.h> +#include <hooks/library_manager_collection.h> +#include <hooks/libinfo.h> + +#include <hooks/tests/common_test_class.h> +#include <hooks/tests/test_libraries.h> + +#include <boost/shared_ptr.hpp> +#include <gtest/gtest.h> + +#include <algorithm> +#include <string> + + +using namespace isc; +using namespace isc::hooks; +using namespace std; + +namespace { + +/// @brief Library manager collection test class + +class LibraryManagerCollectionTest : public ::testing::Test, + public HooksCommonTestClass { +private: + + /// To avoid unused variable errors + std::string dummy(int i) { + if (i == 0) { + return (MARKER_FILE); + } else if (i > 0) { + return (LOAD_CALLOUT_LIBRARY); + } else { + return (LOAD_ERROR_CALLOUT_LIBRARY); + } + } +}; + +} // namespace + +namespace isc { +namespace hooks { +/// @brief Public library manager collection class +/// +/// This is an instance of the LibraryManagerCollection class but with the +/// protected methods made public for test purposes. + +class PublicLibraryManagerCollection : public LibraryManagerCollection { +public: + /// @brief Constructor + /// + /// @param List of libraries that this collection will manage. The order + /// of the libraries is important. + PublicLibraryManagerCollection(const HookLibsCollection& libraries) + : LibraryManagerCollection(libraries) { + } + + /// Public methods that call protected methods on the superclass. + using LibraryManagerCollection::unloadLibraries; +}; + +} // namespace hooks +} // namespace isc + +namespace { +// This is effectively the same test as for LibraryManager, but using the +// LibraryManagerCollection object. + +TEST_F(LibraryManagerCollectionTest, LoadLibraries) { + + // Set up the list of libraries to be loaded. + HookLibsCollection library_names; + library_names.push_back(make_pair(std::string(FULL_CALLOUT_LIBRARY), + data::ConstElementPtr())); + library_names.push_back(make_pair(std::string(BASIC_CALLOUT_LIBRARY), + data::ConstElementPtr())); + + // Set up the library manager collection and get the callout manager we'll + // be using. + PublicLibraryManagerCollection lm_collection(library_names); + + // Load the libraries. + EXPECT_TRUE(lm_collection.loadLibraries()); + EXPECT_EQ(2, lm_collection.getLoadedLibraryCount()); + + // Execute the callouts. The first library implements the calculation. + // + // r3 = (7 * d1 - d2) * d3 + // + // The last-loaded library implements the calculation + // + // r3 = (10 + d1) * d2 - d3 + // + // Putting the processing for each library together in the appropriate + // order, we get: + // + // r3 = ((10 * d1 + d1) - d2) * d2 * d3 - d3 + boost::shared_ptr<CalloutManager> manager = + lm_collection.getCalloutManager(); + { + SCOPED_TRACE("Doing calculation with libraries loaded"); + executeCallCallouts(manager, 10, 3, 33, 2, 62, 3, 183); + } + + // Try unloading the libraries. + EXPECT_NO_THROW(lm_collection.unloadLibraries()); + EXPECT_EQ(0, lm_collection.getLoadedLibraryCount()); + + // Re-execute the calculation - callouts can be called but as nothing + // happens, the result should always be -1. + { + SCOPED_TRACE("Doing calculation with libraries not loaded"); + executeCallCallouts(manager, -1, 3, -1, 22, -1, 83, -1); + } +} + +// This is effectively the same test as above, but with a library generating +// an error when loaded. It is expected that no libraries will be loaded. + +TEST_F(LibraryManagerCollectionTest, LoadLibrariesWithError) { + + // Set up the list of libraries to be loaded. + HookLibsCollection library_names; + library_names.push_back(make_pair(std::string(FULL_CALLOUT_LIBRARY), + data::ConstElementPtr())); + library_names.push_back(make_pair(std::string(INCORRECT_VERSION_LIBRARY), + data::ConstElementPtr())); + library_names.push_back(make_pair(std::string(BASIC_CALLOUT_LIBRARY), + data::ConstElementPtr())); + + // Set up the library manager collection and get the callout manager we'll + // be using. + PublicLibraryManagerCollection lm_collection(library_names); + + // Load the libraries. We expect a failure status to be returned as + // one of the libraries failed to load. + EXPECT_FALSE(lm_collection.loadLibraries()); + + // Expect no libraries were loaded. + EXPECT_EQ(0, lm_collection.getLoadedLibraryCount()); +} + +// Check that everything works even with no libraries loaded. + +TEST_F(LibraryManagerCollectionTest, NoLibrariesLoaded) { + // Set up the list of libraries to be loaded. + HookLibsCollection library_names; + + // Set up the library manager collection and get the callout manager we'll + // be using. + LibraryManagerCollection lm_collection(library_names); + EXPECT_TRUE(lm_collection.loadLibraries()); + EXPECT_EQ(0, lm_collection.getLoadedLibraryCount()); + boost::shared_ptr<CalloutManager> manager = + lm_collection.getCalloutManager(); + + // Execute the calculation - callouts can be called but as nothing + // happens, the result should always be -1. + executeCallCallouts(manager, -1, 3, -1, 22, -1, 83, -1); +} + +// Check that we can get the names of the libraries. + +TEST_F(LibraryManagerCollectionTest, LibraryNames) { + + // Set up the list of libraries to be loaded. + HookLibsCollection libraries; + libraries.push_back(make_pair(std::string(FULL_CALLOUT_LIBRARY), + data::ConstElementPtr())); + libraries.push_back(make_pair(std::string(BASIC_CALLOUT_LIBRARY), + data::ConstElementPtr())); + + // Set up the library manager collection and get the callout manager we'll + // be using. + PublicLibraryManagerCollection lm_collection(libraries); + + // Check the names before the libraries are loaded. + std::vector<std::string> collection_names = lm_collection.getLibraryNames(); + EXPECT_TRUE(extractNames(libraries) == collection_names); + + // Load the libraries and check the names again. + EXPECT_TRUE(lm_collection.loadLibraries()); + EXPECT_EQ(2, lm_collection.getLoadedLibraryCount()); + collection_names = lm_collection.getLibraryNames(); + EXPECT_TRUE(extractNames(libraries) == collection_names); +} + +// Test the library validation function. + +TEST_F(LibraryManagerCollectionTest, validateLibraries) { + // Vector of libraries that failed validation + std::vector<std::string> failed; + + // Test different vectors of libraries. + + // No libraries should return a success. + std::vector<std::string> libraries; + + failed = LibraryManagerCollection::validateLibraries(libraries); + EXPECT_TRUE(failed.empty()); + + // Single valid library should validate. + libraries.clear(); + libraries.push_back(BASIC_CALLOUT_LIBRARY); + + failed = LibraryManagerCollection::validateLibraries(libraries); + EXPECT_TRUE(failed.empty()); + + // Multiple valid libraries should succeed. + libraries.clear(); + libraries.push_back(BASIC_CALLOUT_LIBRARY); + libraries.push_back(FULL_CALLOUT_LIBRARY); + libraries.push_back(UNLOAD_CALLOUT_LIBRARY); + libraries.push_back(CALLOUT_PARAMS_LIBRARY); + + failed = LibraryManagerCollection::validateLibraries(libraries); + EXPECT_TRUE(failed.empty()); + + // Single invalid library should fail. + libraries.clear(); + libraries.push_back(NOT_PRESENT_LIBRARY); + + failed = LibraryManagerCollection::validateLibraries(libraries); + EXPECT_TRUE(failed == libraries); + + // Multiple invalid libraries should fail. + libraries.clear(); + libraries.push_back(INCORRECT_VERSION_LIBRARY); + libraries.push_back(NO_VERSION_LIBRARY); + libraries.push_back(FRAMEWORK_EXCEPTION_LIBRARY); + + failed = LibraryManagerCollection::validateLibraries(libraries); + EXPECT_TRUE(failed == libraries); + + // Combination of valid and invalid (first one valid) should fail. + libraries.clear(); + libraries.push_back(FULL_CALLOUT_LIBRARY); + libraries.push_back(INCORRECT_VERSION_LIBRARY); + libraries.push_back(NO_VERSION_LIBRARY); + + std::vector<std::string> expected_failures; + expected_failures.push_back(INCORRECT_VERSION_LIBRARY); + expected_failures.push_back(NO_VERSION_LIBRARY); + + failed = LibraryManagerCollection::validateLibraries(libraries); + EXPECT_TRUE(failed == expected_failures); + + // Combination of valid and invalid (first one invalid) should fail. + libraries.clear(); + libraries.push_back(NO_VERSION_LIBRARY); + libraries.push_back(FULL_CALLOUT_LIBRARY); + libraries.push_back(INCORRECT_VERSION_LIBRARY); + + expected_failures.clear(); + expected_failures.push_back(NO_VERSION_LIBRARY); + expected_failures.push_back(INCORRECT_VERSION_LIBRARY); + + failed = LibraryManagerCollection::validateLibraries(libraries); + EXPECT_TRUE(failed == expected_failures); +} + +// This test verifies if getLibraryNames and getLibraryInfo are returning +// expected values if there are no libraries configured. +TEST_F(LibraryManagerCollectionTest, libraryGetEmpty) { + + HookLibsCollection empty; + boost::shared_ptr<LibraryManagerCollection> mgr; + + // Instantiate library manager collection with no libraries + EXPECT_NO_THROW(mgr.reset(new LibraryManagerCollection(empty))); + + // Check that getLibraryInfo returns empty list properly. + HookLibsCollection returned = mgr->getLibraryInfo(); + EXPECT_TRUE(returned.empty()); + + // Check that getLibraryNames return empty list, too. + vector<string> names(3, "rubbish"); // just put something in it. + EXPECT_NO_THROW(names = mgr->getLibraryNames()); + EXPECT_TRUE(names.empty()); +} + +// This test verifies if getLibraryNames and getLibraryInfo are returning +// expected values when there are libraries configured. +TEST_F(LibraryManagerCollectionTest, libraryGet) { + using namespace data; + + HookLibsCollection libs; + ElementPtr param1(Element::fromJSON("{ \"param1\": \"foo\" }")); + ElementPtr param2(Element::fromJSON("{ \"param2\": \"bar\" }")); + libs.push_back(make_pair("libone", param1)); + libs.push_back(make_pair("libtwo", param2)); + + boost::shared_ptr<LibraryManagerCollection> mgr; + EXPECT_NO_THROW(mgr.reset(new LibraryManagerCollection(libs))); + EXPECT_TRUE(libs == mgr->getLibraryInfo()); + + vector<string> exp_names; + exp_names.push_back("libone"); + exp_names.push_back("libtwo"); + + EXPECT_TRUE(exp_names == mgr->getLibraryNames()); +} + +} // Anonymous namespace diff --git a/src/lib/hooks/tests/library_manager_unittest.cc b/src/lib/hooks/tests/library_manager_unittest.cc new file mode 100644 index 0000000..c0174a4 --- /dev/null +++ b/src/lib/hooks/tests/library_manager_unittest.cc @@ -0,0 +1,741 @@ +// Copyright (C) 2013-2020 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <hooks/callout_handle.h> +#include <hooks/callout_manager.h> +#include <hooks/library_manager.h> +#include <hooks/server_hooks.h> + +#include <hooks/tests/common_test_class.h> +#include <hooks/tests/marker_file.h> +#include <hooks/tests/test_libraries.h> + +#include <log/message_dictionary.h> +#include <log/message_initializer.h> + +#include <util/multi_threading_mgr.h> + +#include <gtest/gtest.h> + +#include <algorithm> +#include <fstream> +#include <string> + +#include <unistd.h> + + +using namespace isc; +using namespace isc::hooks; +using namespace isc::log; +using namespace isc::util; +using namespace std; + +namespace { + +/// @brief Library manager test class + +class LibraryManagerTest : public ::testing::Test, + public HooksCommonTestClass { +public: + /// @brief Constructor + /// + /// Initializes the CalloutManager object used in the tests. It sets it + /// up with the hooks initialized in the HooksCommonTestClass object and + /// with four libraries. + LibraryManagerTest() { + callout_manager_.reset(new CalloutManager(4)); + + // Ensure the marker file is not present at the start of a test. + static_cast<void>(remove(MARKER_FILE)); + + // Disable multi-threading. + MultiThreadingMgr::instance().setMode(false); + } + + /// @brief Destructor + /// + /// Ensures a marker file is removed after each test. + ~LibraryManagerTest() { + static_cast<void>(remove(MARKER_FILE)); + + // Disable multi-threading. + MultiThreadingMgr::instance().setMode(false); + } + + /// @brief Marker file present + /// + /// Convenience function to check whether a marker file is present. It + /// does this by opening the file. + /// + /// @return true if the marker file is present. + bool markerFilePresent() const { + + // Try to open it. + std::fstream marker; + marker.open(MARKER_FILE, std::fstream::in); + + // Check if it is open and close it if so. + bool exists = marker.is_open(); + if (exists) { + marker.close(); + } + + return (exists); + } + + /// @brief Call callouts test + /// + /// A wrapper around the method of the same name in the HooksCommonTestClass + /// object, this passes this class's CalloutManager to that method. + /// + /// @param r0...r3, d1..d3 Values and intermediate values expected. They + /// are ordered so that the variables appear in the argument list in + /// the order they are used. See HooksCommonTestClass::execute for + /// a full description. (rN is used to indicate an expected result, + /// dN is data to be passed to the calculation.) + void executeCallCallouts(int r0, int d1, int r1, int d2, int r2, int d3, + int r3) { + HooksCommonTestClass::executeCallCallouts(callout_manager_, r0, d1, + r1, d2, r2, d3, r3); + } + + /// @brief Call command handlers test. + /// + /// A wrapper around the method of the same name in the HooksCommonTestClass + /// object, this passes this class's CalloutManager to that method. + /// + /// @param r1..r2, d1..d2 Values and intermediate values expected. + void executeCallCommandHandlers(int d1, int r1, int d2, int r2) { + HooksCommonTestClass::executeCallCommandHandlers(callout_manager_, + d1, r1, d2, r2); + } + + /// Callout manager used for the test. + boost::shared_ptr<CalloutManager> callout_manager_; +}; + +/// @brief Library manager class +/// +/// This is an instance of the LibraryManager class but with the protected +/// methods made public for test purposes. + +class PublicLibraryManager : public isc::hooks::LibraryManager { +public: + /// @brief Constructor + /// + /// Stores the library name. The actual loading is done in loadLibrary(). + /// + /// @param name Name of the library to load. This should be an absolute + /// path name. + /// @param index Index of this library. For all these tests, it will be + /// zero, as we are only using one library. + /// @param manager CalloutManager object + PublicLibraryManager(const std::string& name, int index, + const boost::shared_ptr<CalloutManager>& manager) + : LibraryManager(name, index, manager) + {} + + /// Public methods that call protected methods on the superclass. + using LibraryManager::unloadLibrary; + using LibraryManager::openLibrary; + using LibraryManager::closeLibrary; + using LibraryManager::checkVersion; + using LibraryManager::checkMultiThreadingCompatible; + using LibraryManager::registerStandardCallouts; + using LibraryManager::runLoad; + using LibraryManager::prepareUnloadLibrary; +}; + + +// Check that LibraryManager constructor requires a not null manager + +TEST_F(LibraryManagerTest, NullManager) { + boost::shared_ptr<CalloutManager> null_manager; + EXPECT_THROW(PublicLibraryManager(std::string("foo"), 0, null_manager), + NoCalloutManager); +} + +// Check that openLibrary() reports an error when it can't find the specified +// library. + +TEST_F(LibraryManagerTest, NoLibrary) { + PublicLibraryManager lib_manager(std::string(NOT_PRESENT_LIBRARY), + 0, callout_manager_); + EXPECT_FALSE(lib_manager.openLibrary()); +} + +// Check that the openLibrary() and closeLibrary() methods work. + +TEST_F(LibraryManagerTest, OpenClose) { + PublicLibraryManager lib_manager(std::string(BASIC_CALLOUT_LIBRARY), + 0, callout_manager_); + + // Open and close the library + EXPECT_TRUE(lib_manager.openLibrary()); + EXPECT_TRUE(lib_manager.closeLibrary()); + + // Check that a second close on an already closed library does not report + // an error. + EXPECT_TRUE(lib_manager.closeLibrary()); +} + +// Check that the code handles the case of a library with no version function. + +TEST_F(LibraryManagerTest, NoVersion) { + PublicLibraryManager lib_manager(std::string(NO_VERSION_LIBRARY), + 0, callout_manager_); + // Open should succeed. + EXPECT_TRUE(lib_manager.openLibrary()); + + // Version check should fail. + EXPECT_FALSE(lib_manager.checkVersion()); + + // Tidy up. + EXPECT_TRUE(lib_manager.closeLibrary()); +} + +// Check that the code handles the case of a library with a version function +// that returns an incorrect version number. + +TEST_F(LibraryManagerTest, WrongVersion) { + PublicLibraryManager lib_manager(std::string(INCORRECT_VERSION_LIBRARY), + 0, callout_manager_); + // Open should succeed. + EXPECT_TRUE(lib_manager.openLibrary()); + + // Version check should fail. + EXPECT_FALSE(lib_manager.checkVersion()); + + // Tidy up. + EXPECT_TRUE(lib_manager.closeLibrary()); +} + +// Check that the code handles the case of a library where the version function +// throws an exception. + +TEST_F(LibraryManagerTest, VersionException) { + PublicLibraryManager lib_manager(std::string(FRAMEWORK_EXCEPTION_LIBRARY), + 0, callout_manager_); + // Open should succeed. + EXPECT_TRUE(lib_manager.openLibrary()); + + // Version check should fail. + EXPECT_FALSE(lib_manager.checkVersion()); + + // Tidy up. + EXPECT_TRUE(lib_manager.closeLibrary()); +} + +// Tests that checkVersion() function succeeds in the case of a library with a +// version function that returns the correct version number. + +TEST_F(LibraryManagerTest, CorrectVersionReturned) { + PublicLibraryManager lib_manager(std::string(BASIC_CALLOUT_LIBRARY), + 0, callout_manager_); + // Open should succeed. + EXPECT_TRUE(lib_manager.openLibrary()); + + // Version check should succeed. + EXPECT_TRUE(lib_manager.checkVersion()); + + // Tidy up. + EXPECT_TRUE(lib_manager.closeLibrary()); +} + +// Checks that the code handles the case of a library with no +// multi_threading_compatible function. + +TEST_F(LibraryManagerTest, NoMultiThreadingCompatible) { + PublicLibraryManager lib_manager(std::string(BASIC_CALLOUT_LIBRARY), + 0, callout_manager_); + + // Open should succeed. + EXPECT_TRUE(lib_manager.openLibrary()); + + // Not multi-threading compatible: does not matter without MT. + EXPECT_TRUE(lib_manager.checkMultiThreadingCompatible()); + + // Not multi-threading compatible: does matter with MT. + MultiThreadingMgr::instance().setMode(true); + EXPECT_FALSE(lib_manager.checkMultiThreadingCompatible()); + + // Tidy up. + EXPECT_TRUE(lib_manager.closeLibrary()); +} + +// Checks that the code handles the case of a library with a +// multi_threading_compatible function returning 0 (not compatible). + +TEST_F(LibraryManagerTest, multiThreadingNotCompatible) { + PublicLibraryManager lib_manager(std::string(LOAD_ERROR_CALLOUT_LIBRARY), + 0, callout_manager_); + + // Open should succeed. + EXPECT_TRUE(lib_manager.openLibrary()); + + // Not multi-threading compatible: does not matter without MT. + EXPECT_TRUE(lib_manager.checkMultiThreadingCompatible()); + + // Not multi-threading compatible: does matter with MT. + MultiThreadingMgr::instance().setMode(true); + EXPECT_FALSE(lib_manager.checkMultiThreadingCompatible()); + + // Tidy up. + EXPECT_TRUE(lib_manager.closeLibrary()); +} + +// Checks that the code handles the case of a library with a +// multi_threading_compatible function returning 1 (compatible) + +TEST_F(LibraryManagerTest, multiThreadingCompatible) { + PublicLibraryManager lib_manager(std::string(FULL_CALLOUT_LIBRARY), + 0, callout_manager_); + + // Open should succeed. + EXPECT_TRUE(lib_manager.openLibrary()); + + // Multi-threading compatible: does not matter without MT. + EXPECT_TRUE(lib_manager.checkMultiThreadingCompatible()); + + // Multi-threading compatible: does matter with MT. + MultiThreadingMgr::instance().setMode(true); + EXPECT_TRUE(lib_manager.checkMultiThreadingCompatible()); + + // Tidy up. + EXPECT_TRUE(lib_manager.closeLibrary()); +} + +// Checks that the code handles the case of a library with a +// multi_threading_compatible function returning 1 (compatible) + +TEST_F(LibraryManagerTest, multiThreadingCompatibleException) { + PublicLibraryManager lib_manager(std::string(FRAMEWORK_EXCEPTION_LIBRARY), + 0, callout_manager_); + + // Open should succeed. + EXPECT_TRUE(lib_manager.openLibrary()); + + // Throw exception: does not matter without MT. + EXPECT_TRUE(lib_manager.checkMultiThreadingCompatible()); + + // Throw exception: does matter with MT. + MultiThreadingMgr::instance().setMode(true); + EXPECT_FALSE(lib_manager.checkMultiThreadingCompatible()); + + // Tidy up. + EXPECT_TRUE(lib_manager.closeLibrary()); +} + +// Checks the registration of standard callouts. + +TEST_F(LibraryManagerTest, RegisterStandardCallouts) { + + // Load the only library, specifying the index of 0 as it's the only + // library. This should load all callouts. + PublicLibraryManager lib_manager(std::string(BASIC_CALLOUT_LIBRARY), + 0, callout_manager_); + EXPECT_TRUE(lib_manager.openLibrary()); + + // Check the version of the library. + EXPECT_TRUE(lib_manager.checkVersion()); + + // Load the standard callouts + EXPECT_NO_THROW(lib_manager.registerStandardCallouts()); + + // Now execute the callouts in the order expected. The library performs + // the calculation: + // + // r3 = (10 + d1) * d2 - d3 + executeCallCallouts(10, 5, 15, 7, 105, 17, 88); + + // Tidy up + EXPECT_TRUE(lib_manager.closeLibrary()); +} + +// Test that the "load" function is called correctly. + +TEST_F(LibraryManagerTest, CheckLoadCalled) { + + // Load the only library, specifying the index of 0 as it's the only + // library. This should load all callouts. + PublicLibraryManager lib_manager(std::string(LOAD_CALLOUT_LIBRARY), + 0, callout_manager_); + EXPECT_TRUE(lib_manager.openLibrary()); + + // Check the version of the library. + EXPECT_TRUE(lib_manager.checkVersion()); + + // Load the standard callouts + EXPECT_NO_THROW(lib_manager.registerStandardCallouts()); + + // Check that only context_create and hookpt_one have callouts registered. + EXPECT_TRUE(callout_manager_->calloutsPresent( + ServerHooks::CONTEXT_CREATE)); + EXPECT_TRUE(callout_manager_->calloutsPresent(hookpt_one_index_)); + EXPECT_FALSE(callout_manager_->calloutsPresent(hookpt_two_index_)); + EXPECT_FALSE(callout_manager_->calloutsPresent(hookpt_three_index_)); + EXPECT_FALSE(callout_manager_->commandHandlersPresent("command-one")); + EXPECT_FALSE(callout_manager_->commandHandlersPresent("command-two")); + EXPECT_FALSE(callout_manager_->calloutsPresent( + ServerHooks::CONTEXT_DESTROY)); + + // Call the runLoad() method to run the load() function. + EXPECT_TRUE(lib_manager.runLoad()); + EXPECT_TRUE(callout_manager_->calloutsPresent( + ServerHooks::CONTEXT_CREATE)); + EXPECT_TRUE(callout_manager_->calloutsPresent(hookpt_one_index_)); + EXPECT_TRUE(callout_manager_->calloutsPresent(hookpt_two_index_)); + EXPECT_TRUE(callout_manager_->calloutsPresent(hookpt_three_index_)); + EXPECT_TRUE(callout_manager_->commandHandlersPresent("command-one")); + EXPECT_TRUE(callout_manager_->commandHandlersPresent("command-two")); + EXPECT_FALSE(callout_manager_->calloutsPresent( + ServerHooks::CONTEXT_DESTROY)); + + // Now execute the callouts in the order expected. The library performs + // the calculation: + // + // r3 = (5 * d1 + d2) * d3 + executeCallCallouts(5, 5, 25, 7, 32, 10, 320); + + // Execute command handlers for 'command-one' and 'command-two'. + // + // r2 = d1 * d2 * 10; + executeCallCommandHandlers(5, 5, 7, 350); + + // Tidy up + EXPECT_TRUE(lib_manager.closeLibrary()); +} + +// Check handling of a "load" function that throws an exception + +TEST_F(LibraryManagerTest, CheckLoadException) { + + // Load the only library, specifying the index of 0 as it's the only + // library. This should load all callouts. + PublicLibraryManager lib_manager(std::string(FRAMEWORK_EXCEPTION_LIBRARY), + 0, callout_manager_); + EXPECT_TRUE(lib_manager.openLibrary()); + + // Running the load function should fail. + EXPECT_FALSE(lib_manager.runLoad()); + + // Tidy up + EXPECT_TRUE(lib_manager.closeLibrary()); +} + +// Check handling of a "load" function that returns an error. + +TEST_F(LibraryManagerTest, CheckLoadError) { + + // Load the only library, specifying the index of 0 as it's the only + // library. This should load all callouts. + PublicLibraryManager lib_manager(std::string(LOAD_ERROR_CALLOUT_LIBRARY), + 0, callout_manager_); + EXPECT_TRUE(lib_manager.openLibrary()); + + // Check that we catch a load error + EXPECT_FALSE(lib_manager.runLoad()); + + // Tidy up + EXPECT_TRUE(lib_manager.closeLibrary()); +} + +// No unload function + +TEST_F(LibraryManagerTest, CheckNoUnload) { + + // Load the only library, specifying the index of 0 as it's the only + // library. This should load all callouts. + PublicLibraryManager lib_manager(std::string(BASIC_CALLOUT_LIBRARY), + 0, callout_manager_); + EXPECT_TRUE(lib_manager.openLibrary()); + + // Check that no unload function returns true. + EXPECT_TRUE(lib_manager.prepareUnloadLibrary()); + + // Tidy up + EXPECT_TRUE(lib_manager.closeLibrary()); +} + +// Unload function returns an error + +TEST_F(LibraryManagerTest, CheckUnloadError) { + + // Load the only library, specifying the index of 0 as it's the only + // library. This should load all callouts. + PublicLibraryManager lib_manager(std::string(LOAD_ERROR_CALLOUT_LIBRARY), + 0, callout_manager_); + EXPECT_TRUE(lib_manager.openLibrary()); + + // Check that unload function returning an error returns false. + EXPECT_FALSE(lib_manager.prepareUnloadLibrary()); + + // Tidy up + EXPECT_TRUE(lib_manager.closeLibrary()); +} + +// Unload function throws an exception. + +TEST_F(LibraryManagerTest, CheckUnloadException) { + + // Load the only library, specifying the index of 0 as it's the only + // library. This should load all callouts. + PublicLibraryManager lib_manager(std::string(FRAMEWORK_EXCEPTION_LIBRARY), + 0, callout_manager_); + EXPECT_TRUE(lib_manager.openLibrary()); + + // Check that we detect that the unload function throws an exception. + EXPECT_FALSE(lib_manager.prepareUnloadLibrary()); + + // Tidy up + EXPECT_TRUE(lib_manager.closeLibrary()); +} + +// Check that the case of the library's unload() function returning a +// success is handled correctly. + +TEST_F(LibraryManagerTest, CheckUnload) { + + // Load the only library, specifying the index of 0 as it's the only + // library. This should load all callouts. + PublicLibraryManager lib_manager(std::string(UNLOAD_CALLOUT_LIBRARY), + 0, callout_manager_); + EXPECT_TRUE(lib_manager.openLibrary()); + + + // Check that the marker file is not present (at least that the file + // open fails). + EXPECT_FALSE(markerFilePresent()); + + // Check that unload function runs and returns a success + EXPECT_TRUE(lib_manager.prepareUnloadLibrary()); + + // Check that the marker file was created. + EXPECT_TRUE(markerFilePresent()); + + // Tidy up + EXPECT_TRUE(lib_manager.closeLibrary()); +} + +// Test the operation of unloadLibrary(). We load a library with a set +// of callouts then unload it. We need to check that the callouts have been +// removed. We'll also check that the library's unload() function was called +// as well. + +TEST_F(LibraryManagerTest, LibUnload) { + + // Load the only library, specifying the index of 0 as it's the only + // library. This should load all callouts. + PublicLibraryManager lib_manager(std::string(LOAD_CALLOUT_LIBRARY), + 0, callout_manager_); + EXPECT_TRUE(lib_manager.openLibrary()); + + // Check the version of the library. + EXPECT_TRUE(lib_manager.checkVersion()); + + // No callouts should be registered at the moment. + EXPECT_FALSE(callout_manager_->calloutsPresent(hookpt_one_index_)); + EXPECT_FALSE(callout_manager_->calloutsPresent(hookpt_two_index_)); + EXPECT_FALSE(callout_manager_->calloutsPresent(hookpt_three_index_)); + EXPECT_FALSE(callout_manager_->commandHandlersPresent("command-one")); + EXPECT_FALSE(callout_manager_->commandHandlersPresent("command-two")); + + // Load the single standard callout and check it is registered correctly. + EXPECT_NO_THROW(lib_manager.registerStandardCallouts()); + EXPECT_TRUE(callout_manager_->calloutsPresent(hookpt_one_index_)); + EXPECT_FALSE(callout_manager_->calloutsPresent(hookpt_two_index_)); + EXPECT_FALSE(callout_manager_->calloutsPresent(hookpt_three_index_)); + EXPECT_FALSE(callout_manager_->commandHandlersPresent("command-one")); + EXPECT_FALSE(callout_manager_->commandHandlersPresent("command-two")); + + // Call the load function to load the other callouts. + EXPECT_TRUE(lib_manager.runLoad()); + EXPECT_TRUE(callout_manager_->calloutsPresent(hookpt_one_index_)); + EXPECT_TRUE(callout_manager_->calloutsPresent(hookpt_two_index_)); + EXPECT_TRUE(callout_manager_->calloutsPresent(hookpt_three_index_)); + EXPECT_TRUE(callout_manager_->commandHandlersPresent("command-one")); + EXPECT_TRUE(callout_manager_->commandHandlersPresent("command-two")); + + // Unload the library and check that the callouts have been removed from + // the CalloutManager. + lib_manager.unloadLibrary(); + EXPECT_FALSE(callout_manager_->calloutsPresent(hookpt_one_index_)); + EXPECT_FALSE(callout_manager_->calloutsPresent(hookpt_two_index_)); + EXPECT_FALSE(callout_manager_->calloutsPresent(hookpt_three_index_)); + EXPECT_FALSE(callout_manager_->commandHandlersPresent("command-one")); + EXPECT_FALSE(callout_manager_->commandHandlersPresent("command-two")); +} + +// Now come the loadLibrary() tests that make use of all the methods tested +// above. These tests are really to make sure that the methods have been +// tied together correctly. + +// First test the basic error cases - no library, no version function, version +// function returning an error. + +TEST_F(LibraryManagerTest, LoadLibraryNoLibrary) { + LibraryManager lib_manager(std::string(NOT_PRESENT_LIBRARY), 0, + callout_manager_); + EXPECT_FALSE(lib_manager.loadLibrary()); +} + +// Check that the code handles the case of a library with no version function. + +TEST_F(LibraryManagerTest, LoadLibraryNoVersion) { + LibraryManager lib_manager(std::string(NO_VERSION_LIBRARY), 0, + callout_manager_); + EXPECT_FALSE(lib_manager.loadLibrary()); +} + +// Check that the code handles the case of a library with a version function +// that returns an incorrect version number. + +TEST_F(LibraryManagerTest, LoadLibraryWrongVersion) { + LibraryManager lib_manager(std::string(INCORRECT_VERSION_LIBRARY), 0, + callout_manager_); + EXPECT_FALSE(lib_manager.loadLibrary()); +} + +// Check that the full loadLibrary call works. + +TEST_F(LibraryManagerTest, LoadLibrary) { + PublicLibraryManager lib_manager(std::string(FULL_CALLOUT_LIBRARY), 0, + callout_manager_); + EXPECT_TRUE(lib_manager.loadLibrary()); + + // Now execute the callouts in the order expected. The library performs + // the calculation: + // + // r3 = (7 * d1 - d2) * d3 + executeCallCallouts(7, 5, 35, 9, 26, 3, 78); + + EXPECT_TRUE(lib_manager.unloadLibrary()); + + // Check that the callouts have been removed from the callout manager. + EXPECT_FALSE(callout_manager_->calloutsPresent(hookpt_one_index_)); + EXPECT_FALSE(callout_manager_->calloutsPresent(hookpt_two_index_)); + EXPECT_FALSE(callout_manager_->calloutsPresent(hookpt_three_index_)); +} + +// Now test for multiple libraries. We'll load the full callout library +// first, then load some of the libraries with missing framework functions. +// This will check that when searching for framework functions, only the +// specified library is checked, not other loaded libraries. We will +// load a second library with suitable callouts and check that the callouts +// are added correctly. Finally, we'll unload one of the libraries and +// check that only the callouts belonging to that library were removed. + +TEST_F(LibraryManagerTest, LoadMultipleLibraries) { + // Load a library with all framework functions. + PublicLibraryManager lib_manager_1(std::string(FULL_CALLOUT_LIBRARY), + 0, callout_manager_); + EXPECT_TRUE(lib_manager_1.loadLibrary()); + + // Attempt to load a library with no version() function. We should detect + // this and not end up calling the function from the already loaded + // library. + PublicLibraryManager lib_manager_2(std::string(NO_VERSION_LIBRARY), + 1, callout_manager_); + EXPECT_FALSE(lib_manager_2.loadLibrary()); + + // Attempt to load the library with an incorrect version. This should + // be detected. + PublicLibraryManager lib_manager_3(std::string(INCORRECT_VERSION_LIBRARY), + 1, callout_manager_); + EXPECT_FALSE(lib_manager_3.loadLibrary()); + + // Load the basic callout library. This only has standard callouts so, + // if the first library's load() function gets called, some callouts + // will be registered twice and lead to incorrect results. + PublicLibraryManager lib_manager_4(std::string(BASIC_CALLOUT_LIBRARY), + 1, callout_manager_); + EXPECT_TRUE(lib_manager_4.loadLibrary()); + + // Execute the callouts. The first library implements the calculation. + // + // r3 = (7 * d1 - d2) * d3 + // + // The last-loaded library implements the calculation + // + // r3 = (10 + d1) * d2 - d3 + // + // Putting the processing for each library together in the appropriate + // order, we get: + // + // r3 = ((10 * d1 + d1) - d2) * d2 * d3 - d3 + executeCallCallouts(10, 3, 33, 2, 62, 3, 183); + + // All done, so unload the first library. + EXPECT_TRUE(lib_manager_1.unloadLibrary()); + + // Now execute the callouts again and check that the results are as + // expected for the new calculation. + executeCallCallouts(10, 5, 15, 7, 105, 17, 88); + + // ... and tidy up. + EXPECT_TRUE(lib_manager_4.unloadLibrary()); +} + +// Check that libraries can be validated. + +TEST_F(LibraryManagerTest, validateLibraries) { + EXPECT_TRUE(LibraryManager::validateLibrary(BASIC_CALLOUT_LIBRARY)); + EXPECT_TRUE(LibraryManager::validateLibrary(FULL_CALLOUT_LIBRARY)); + EXPECT_FALSE(LibraryManager::validateLibrary(FRAMEWORK_EXCEPTION_LIBRARY)); + EXPECT_FALSE(LibraryManager::validateLibrary(INCORRECT_VERSION_LIBRARY)); + EXPECT_TRUE(LibraryManager::validateLibrary(LOAD_CALLOUT_LIBRARY)); + EXPECT_TRUE(LibraryManager::validateLibrary(LOAD_ERROR_CALLOUT_LIBRARY)); + EXPECT_FALSE(LibraryManager::validateLibrary(NOT_PRESENT_LIBRARY)); + EXPECT_FALSE(LibraryManager::validateLibrary(NO_VERSION_LIBRARY)); + EXPECT_TRUE(LibraryManager::validateLibrary(UNLOAD_CALLOUT_LIBRARY)); + EXPECT_TRUE(LibraryManager::validateLibrary(CALLOUT_PARAMS_LIBRARY)); + + MultiThreadingMgr::instance().setMode(true); + + EXPECT_FALSE(LibraryManager::validateLibrary(BASIC_CALLOUT_LIBRARY)); + EXPECT_TRUE(LibraryManager::validateLibrary(FULL_CALLOUT_LIBRARY)); + EXPECT_FALSE(LibraryManager::validateLibrary(FRAMEWORK_EXCEPTION_LIBRARY)); + EXPECT_FALSE(LibraryManager::validateLibrary(INCORRECT_VERSION_LIBRARY)); + EXPECT_FALSE(LibraryManager::validateLibrary(LOAD_CALLOUT_LIBRARY)); + EXPECT_FALSE(LibraryManager::validateLibrary(LOAD_ERROR_CALLOUT_LIBRARY)); + EXPECT_FALSE(LibraryManager::validateLibrary(NOT_PRESENT_LIBRARY)); + EXPECT_FALSE(LibraryManager::validateLibrary(NO_VERSION_LIBRARY)); + EXPECT_FALSE(LibraryManager::validateLibrary(UNLOAD_CALLOUT_LIBRARY)); + EXPECT_FALSE(LibraryManager::validateLibrary(CALLOUT_PARAMS_LIBRARY)); +} + +// Check that log messages are properly registered and unregistered. + +TEST_F(LibraryManagerTest, libraryLoggerSetup) { + // Load a library with all framework functions. + PublicLibraryManager lib_manager(std::string(BASIC_CALLOUT_LIBRARY), + 0, callout_manager_); + EXPECT_TRUE(lib_manager.loadLibrary()); + + // After loading the library, the global logging dictionary should + // contain log messages registered for this library. + const MessageDictionaryPtr& dict = MessageDictionary::globalDictionary(); + EXPECT_EQ("basic callout load %1", dict->getText("BCL_LOAD_START")); + EXPECT_EQ("basic callout load end", dict->getText("BCL_LOAD_END")); + // Some of the messages defined by the hook library are duplicates. But, + // the loadLibrary function should have logged the duplicates and clear + // the duplicates list. By checking that the list of duplicates is empty + // we test that the LibraryManager handles the duplicates (logs and + // clears them). + EXPECT_TRUE(MessageInitializer::getDuplicates().empty()); + + // After unloading the library, the messages should be unregistered. + EXPECT_TRUE(lib_manager.unloadLibrary()); + // The musl libc does not implement dlclose +#ifndef LIBC_MUSL + EXPECT_TRUE(dict->getText("BCL_LOAD_START").empty()); + EXPECT_TRUE(dict->getText("BCL_LOAD_END").empty()); +#endif +} + +} // Anonymous namespace diff --git a/src/lib/hooks/tests/load_callout_library.cc b/src/lib/hooks/tests/load_callout_library.cc new file mode 100644 index 0000000..613ec2c --- /dev/null +++ b/src/lib/hooks/tests/load_callout_library.cc @@ -0,0 +1,152 @@ +// Copyright (C) 2013-2017 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +/// @file +/// @brief Basic library with load() function +/// +/// This is source of a test library for various test (LibraryManager and +/// HooksManager). The characteristics of the library produced from this +/// file are: +/// +/// - The "version" and "load" framework functions are supplied. One "standard" +/// callout is supplied ("hookpt_one") and two non-standard ones which are +/// registered during the call to "load" on the hooks "hookpt_two" and +/// "hookpt_three". +/// +/// All callouts do trivial calculations, the result of all being called in +/// sequence being +/// +/// @f[ ((5 * data_1) + data_2) * data_3 @f] +/// +/// ...where data_1, data_2 and data_3 are the values passed in arguments of +/// the same name to the three callouts (data_1 passed to hookpt_one, data_2 +/// to hookpt_two etc.) and the result is returned in the argument "result". + +#include <config.h> +#include <hooks/hooks.h> + +using namespace isc::hooks; + +extern "C" { + +// Callouts + +int +context_create(CalloutHandle& handle) { + handle.setContext("result", static_cast<int>(5)); + handle.setArgument("result", static_cast<int>(5)); + return (0); +} + +// First callout adds the passed "data_1" argument to the initialized context +// value of 5. (Note that the value set by context_create is accessed through +// context and not the argument, so checking that context is correctly passed +// between callouts in the same library.) + +int +hookpt_one(CalloutHandle& handle) { + int data; + handle.getArgument("data_1", data); + + int result; + handle.getContext("result", result); + + result *= data; + handle.setArgument("result", result); + + return (0); +} + +// Second callout multiplies the current context value by the "data_2" +// argument. + +static int +hook_nonstandard_two(CalloutHandle& handle) { + int data; + handle.getArgument("data_2", data); + + int result; + handle.getArgument("result", result); + + result += data; + handle.setArgument("result", result); + + return (0); +} + +// Third callout adds "data_3" to the result. + +static int +hook_nonstandard_three(CalloutHandle& handle) { + int data; + handle.getArgument("data_3", data); + + int result; + handle.getArgument("result", result); + + result *= data; + handle.setArgument("result", result); + + return (0); +} + +// First command handler assigns data to a result. + +static int +command_handler_one(CalloutHandle& handle) { + int data; + handle.getArgument("data_1", data); + + int result; + handle.getArgument("result", result); + + result = data; + handle.setArgument("result", result); + + return (0); +} + +// Second command handler multiples the result by data by 10. + +static int +command_handler_two(CalloutHandle& handle) { + int data; + handle.getArgument("data_2", data); + + int result; + handle.getArgument("result", result); + + result *= data * 10; + handle.setArgument("result", result); + + return (0); +} + +// Framework functions + +int +version() { + return (KEA_HOOKS_VERSION); +} + +int load(LibraryHandle& handle) { + // Initialize the user library if the main image was statically linked +#ifdef USE_STATIC_LINK + hooksStaticLinkInit(); +#endif + // Register the non-standard functions + handle.registerCallout("hookpt_two", hook_nonstandard_two); + handle.registerCallout("hookpt_three", hook_nonstandard_three); + + // Register command_handler_one as control command handler. + handle.registerCommandCallout("command-one", command_handler_one); + handle.registerCommandCallout("command-two", command_handler_two); + + return (0); +} + +}; + diff --git a/src/lib/hooks/tests/load_error_callout_library.cc b/src/lib/hooks/tests/load_error_callout_library.cc new file mode 100644 index 0000000..66b1359 --- /dev/null +++ b/src/lib/hooks/tests/load_error_callout_library.cc @@ -0,0 +1,47 @@ +// Copyright (C) 2013-2020 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +/// @file +/// @brief Error load library +/// +/// This is source of a test library for various test (LibraryManager and +/// HooksManager). The characteristics of the library produced from this +/// file are: +/// +/// - All framework functions are supplied. "version" returns the correct +/// value, but "load" and unload return an error. + +#include <config.h> +#include <hooks/hooks.h> + +using namespace isc::hooks; + +extern "C" { + +// Framework functions + +int +version() { + return (KEA_HOOKS_VERSION); +} + +int +load(LibraryHandle&) { + return (1); +} + +int +unload() { + return (1); +} + +int +multi_threading_compatible() { + return (0); +} + +}; + diff --git a/src/lib/hooks/tests/marker_file.h.in b/src/lib/hooks/tests/marker_file.h.in new file mode 100644 index 0000000..368ad37 --- /dev/null +++ b/src/lib/hooks/tests/marker_file.h.in @@ -0,0 +1,19 @@ +// Copyright (C) 2013-2015 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef MARKER_FILE_H +#define MARKER_FILE_H + +/// @file +/// Define a marker file that is used in tests to prove that an "unload" +/// function has been called. + +namespace { +const char* MARKER_FILE = "@abs_builddir@/marker_file.dat"; +} + +#endif // MARKER_FILE_H + diff --git a/src/lib/hooks/tests/no_version_library.cc b/src/lib/hooks/tests/no_version_library.cc new file mode 100644 index 0000000..a4c4bb5 --- /dev/null +++ b/src/lib/hooks/tests/no_version_library.cc @@ -0,0 +1,24 @@ +// Copyright (C) 2013-2020 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +/// @file +/// @brief No version function library +/// +/// This is source of a test library for various test (LibraryManager and +/// HooksManager). The characteristics of the library produced from this +/// file are: +/// +/// - No version() function is present. + +extern "C" { + +int no_version() { + return (0); +} + +}; diff --git a/src/lib/hooks/tests/parking_lots_unittest.cc b/src/lib/hooks/tests/parking_lots_unittest.cc new file mode 100644 index 0000000..96f8814 --- /dev/null +++ b/src/lib/hooks/tests/parking_lots_unittest.cc @@ -0,0 +1,339 @@ +// Copyright (C) 2018-2021 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <exceptions/exceptions.h> +#include <hooks/parking_lots.h> +#include <boost/weak_ptr.hpp> +#include <testutils/gtest_utils.h> +#include <gtest/gtest.h> +#include <string> + +using namespace isc; +using namespace isc::hooks; + +namespace { + +// Defines a pointer to a string. The tests use shared pointers +// as parked objects to ensure key matching works correctly with +// them. We're doing this because real-world use parked objects +// are typically pointers to packets. +typedef boost::shared_ptr<std::string> StringPtr; + +// Test that it is possible to create and retrieve parking lots for +// specified hook points. +TEST(ParkingLotsTest, createGetParkingLot) { + ParkingLots parking_lots; + + ParkingLotPtr parking_lot0 = parking_lots.getParkingLotPtr(1); + ParkingLotPtr parking_lot1 = parking_lots.getParkingLotPtr(2); + ParkingLotPtr parking_lot2 = parking_lots.getParkingLotPtr(1); + + ASSERT_TRUE(parking_lot0); + ASSERT_TRUE(parking_lot1); + ASSERT_TRUE(parking_lot2); + + EXPECT_EQ(0, parking_lot0->size()); + EXPECT_EQ(0, parking_lot1->size()); + EXPECT_EQ(0, parking_lot2->size()); + + EXPECT_FALSE(parking_lot0 == parking_lot1); + EXPECT_TRUE(parking_lot0 == parking_lot2); + + ASSERT_NO_THROW(parking_lots.clear()); + + ParkingLotPtr parking_lot3 = parking_lots.getParkingLotPtr(1); + ASSERT_TRUE(parking_lot3); + + EXPECT_FALSE(parking_lot3 == parking_lot0); +} + +// Verify that an object can be parked. +TEST(ParkingLotsTest, park) { + ParkingLot parking_lot; + + // Create object to park. + StringPtr parked_object(new std::string("foo")); + + // Verify that we can park an object that has not been parked. + ASSERT_NO_THROW(parking_lot.park(parked_object, [] {})); + + EXPECT_EQ(1, parking_lot.size()); + + // Verify that we cannot park an object that has been parked + EXPECT_THROW(parking_lot.park(parked_object, [] {}), + InvalidOperation); +} + +// Verify that an object can be referenced. +TEST(ParkingLotsTest, reference) { + ParkingLotPtr parking_lot = boost::make_shared<ParkingLot>(); + ParkingLotHandlePtr parking_lot_handle = + boost::make_shared<ParkingLotHandle>(parking_lot); + + // Create an object. + StringPtr parked_object(new std::string("foo")); + + // Cannot reference an object that has not been parked. + ASSERT_THROW(parking_lot_handle->reference(parked_object), + InvalidOperation); + + // Park the object. + ASSERT_NO_THROW(parking_lot->park(parked_object, [] {})); + + EXPECT_EQ(1, parking_lot->size()); + + // Reference the object. Reference count should one. + int ref_count = 0; + ASSERT_NO_THROW(ref_count = parking_lot_handle->reference(parked_object)); + ASSERT_EQ(1, ref_count); + + // Reference the object again. Reference count should two. + ASSERT_NO_THROW(ref_count = parking_lot_handle->reference(parked_object)); + ASSERT_EQ(2, ref_count); + + EXPECT_EQ(1, parking_lot->size()); +} + +// Test that object can be parked and then unparked. +TEST(ParkingLotsTest, unpark) { + ParkingLotPtr parking_lot = boost::make_shared<ParkingLot>(); + ParkingLotHandlePtr parking_lot_handle = + boost::make_shared<ParkingLotHandle>(parking_lot); + + StringPtr parked_object(new std::string("foo")); + + // Unparking should return false if the object isn't parked. + EXPECT_FALSE(parking_lot->unpark(parked_object)); + + EXPECT_EQ(0, parking_lot->size()); + + // This flag will indicate if the callback has been called. + bool unparked = false; + + ASSERT_NO_THROW(parking_lot->park(parked_object, [&unparked] { + unparked = true; + })); + + EXPECT_EQ(1, parking_lot->size()); + + // Reference the parked object twice because we're going to test that + // reference counting works fine. + ASSERT_NO_THROW(parking_lot_handle->reference(parked_object)); + ASSERT_NO_THROW(parking_lot_handle->reference(parked_object)); + + EXPECT_EQ(1, parking_lot->size()); + + // Try to unpark the object. It should decrease the reference count, but not + // unpark the packet yet. + EXPECT_TRUE(parking_lot_handle->unpark(parked_object)); + EXPECT_FALSE(unparked); + + EXPECT_EQ(1, parking_lot->size()); + + // Try to unpark the object. This time it should be successful, because the + // reference count goes to 0. + EXPECT_TRUE(parking_lot_handle->unpark(parked_object)); + EXPECT_TRUE(unparked); + + EXPECT_EQ(0, parking_lot->size()); + + // Calling unpark again should return false to indicate that the object is + // not parked. + EXPECT_FALSE(parking_lot_handle->unpark(parked_object)); + + EXPECT_EQ(0, parking_lot->size()); +} + +// Test that parked object can be dropped. +TEST(ParkingLotsTest, drop) { + ParkingLotPtr parking_lot = boost::make_shared<ParkingLot>(); + ParkingLotHandlePtr parking_lot_handle = + boost::make_shared<ParkingLotHandle>(parking_lot); + + StringPtr parked_object(new std::string("foo")); + + // This flag will indicate if the callback has been called. + bool unparked = false; + ASSERT_NO_THROW(parking_lot->park(parked_object, [&unparked] { + unparked = true; + })); + + EXPECT_EQ(1, parking_lot->size()); + + // Reference object twice to test that dropping the packet ignores + // reference counting. + ASSERT_NO_THROW(parking_lot_handle->reference(parked_object)); + ASSERT_NO_THROW(parking_lot_handle->reference(parked_object)); + + // Drop parked object. The callback should not be invoked. + EXPECT_TRUE(parking_lot_handle->drop(parked_object)); + EXPECT_FALSE(unparked); + + EXPECT_EQ(0, parking_lot->size()); + + // Expect that an attempt to unpark return false, as the object + // has been dropped. + EXPECT_FALSE(parking_lot_handle->unpark(parked_object)); +} + +// Test that parked lots can be cleared. +TEST(ParkingLotsTest, clear) { + ParkingLotsPtr parking_lots = boost::make_shared<ParkingLots>(); + ParkingLotPtr parking_lot = parking_lots->getParkingLotPtr(1234); + ASSERT_TRUE(parking_lot); + ParkingLotHandlePtr parking_lot_handle = + boost::make_shared<ParkingLotHandle>(parking_lot); + + boost::shared_ptr<std::string> parked_object = + boost::make_shared<std::string>("foo"); + boost::weak_ptr<std::string> weak_parked_object(parked_object); + + // This flag will indicate if the callback has been called. + bool unparked = false; + ASSERT_NO_THROW(parking_lot->park(parked_object, [&unparked] { + unparked = true; + })); + + // Reference object twice to test that clearing the parking lots + // ignores reference counting. + ASSERT_NO_THROW(parking_lot_handle->reference(parked_object)); + ASSERT_NO_THROW(parking_lot_handle->reference(parked_object)); + + // Drop reference on objects. + parking_lot.reset(); + parking_lot_handle.reset(); + parked_object.reset(); + + // The parked object is still alive. + EXPECT_FALSE(weak_parked_object.expired()); + + // Clear the parking lots. + ASSERT_NO_THROW(parking_lots->clear()); + + // The callback should not be invoked. + EXPECT_FALSE(unparked); + + // The parked object was destroyed. + EXPECT_TRUE(weak_parked_object.expired()); +} + +// Verify that an object can be dereferenced. +TEST(ParkingLotsTest, dereference) { + ParkingLotPtr parking_lot = boost::make_shared<ParkingLot>(); + ParkingLotHandlePtr parking_lot_handle = + boost::make_shared<ParkingLotHandle>(parking_lot); + + // Create an object. + StringPtr parked_object(new std::string("foo")); + + // Verify that an object that hasn't been parked, cannot be + // dereferenced. + ASSERT_THROW(parking_lot_handle->dereference(parked_object), + InvalidOperation); + + // Park the object. + // This flag will indicate if the callback has been called. + bool unparked = false; + ASSERT_NO_THROW(parking_lot->park(parked_object, [&unparked] { + unparked = true; + })); + + EXPECT_EQ(1, parking_lot->size()); + + // Reference the parked object twice. + int ref_count = 0; + ASSERT_NO_THROW(ref_count = parking_lot_handle->reference(parked_object)); + ASSERT_EQ(1, ref_count); + ASSERT_NO_THROW(ref_count = parking_lot_handle->reference(parked_object)); + ASSERT_EQ(2, ref_count); + + EXPECT_EQ(1, parking_lot->size()); + + // Try to dereference the object. It should decrease the reference count, + // but not unpark the packet or invoke the callback. + ASSERT_NO_THROW(ref_count = parking_lot_handle->dereference(parked_object)); + ASSERT_EQ(1, ref_count); + EXPECT_FALSE(unparked); + + EXPECT_EQ(1, parking_lot->size()); + + // Try to dereference the object. It should decrease the reference count, + // but not unpark the packet or invoke the callback. + ASSERT_NO_THROW(ref_count = parking_lot_handle->dereference(parked_object)); + ASSERT_EQ(0, ref_count); + EXPECT_FALSE(unparked); + + EXPECT_EQ(1, parking_lot->size()); + + // Try to dereference the object. It should decrement to -1 + // but not unpark the packet or invoke the callback. + ASSERT_NO_THROW(ref_count = parking_lot_handle->dereference(parked_object)); + EXPECT_EQ(-1, ref_count); + EXPECT_FALSE(unparked); + + EXPECT_EQ(1, parking_lot->size()); + + // Calling unpark should invoke the callback. + ASSERT_TRUE(parking_lot_handle->unpark(parked_object)); + EXPECT_TRUE(unparked); + + EXPECT_EQ(0, parking_lot->size()); +} + +// Verify that parked objects are correctly distinguished from +// one another. +TEST(ParkingLotsTest, multipleObjects) { + ParkingLotPtr parking_lot = boost::make_shared<ParkingLot>(); + ParkingLotHandlePtr parking_lot_handle = + boost::make_shared<ParkingLotHandle>(parking_lot); + + // Create an object and park it. + StringPtr object_one(new std::string("one")); + int unparked_one = 0; + ASSERT_NO_THROW(parking_lot->park(object_one, [&unparked_one] { + ++unparked_one; + })); + + // Create a second object and park it. + StringPtr object_two(new std::string("two")); + int unparked_two = 0; + ASSERT_NO_THROW(parking_lot->park(object_two, [&unparked_two] { + ++unparked_two; + })); + + EXPECT_EQ(2, parking_lot->size()); + + // Create a third object but don't park it. + StringPtr object_three(new std::string("three")); + + // Try to unpark object_three. It should fail, and no callbacks + // should get invoked. + EXPECT_FALSE(parking_lot_handle->unpark(object_three)); + EXPECT_EQ(unparked_one, 0); + EXPECT_EQ(unparked_two, 0); + + EXPECT_EQ(2, parking_lot->size()); + + // Unpark object one. It should succeed and its callback should + // get invoked. + EXPECT_TRUE(parking_lot_handle->unpark(object_one)); + EXPECT_EQ(unparked_one, 1); + EXPECT_EQ(unparked_two, 0); + + EXPECT_EQ(1, parking_lot->size()); + + // Unpark object two. It should succeed and its callback should + // get invoked. + EXPECT_TRUE(parking_lot_handle->unpark(object_two)); + EXPECT_EQ(unparked_one, 1); + EXPECT_EQ(unparked_two, 1); + + EXPECT_EQ(0, parking_lot->size()); +} + +} diff --git a/src/lib/hooks/tests/run_unittests.cc b/src/lib/hooks/tests/run_unittests.cc new file mode 100644 index 0000000..cd6fc2b --- /dev/null +++ b/src/lib/hooks/tests/run_unittests.cc @@ -0,0 +1,19 @@ +// Copyright (C) 2009-2015 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <log/logger_support.h> +#include <util/unittests/run_all.h> + +#include <gtest/gtest.h> + +int +main(int argc, char* argv[]) { + ::testing::InitGoogleTest(&argc, argv); + isc::log::initLogger(); + return (isc::util::unittests::run_all()); +} diff --git a/src/lib/hooks/tests/server_hooks_unittest.cc b/src/lib/hooks/tests/server_hooks_unittest.cc new file mode 100644 index 0000000..f9b20cb --- /dev/null +++ b/src/lib/hooks/tests/server_hooks_unittest.cc @@ -0,0 +1,232 @@ +// Copyright (C) 2013-2018 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <hooks/server_hooks.h> + +#include <gtest/gtest.h> + +#include <algorithm> +#include <string> +#include <vector> + +using namespace isc; +using namespace isc::hooks; +using namespace std; + +namespace { + +// Checks the registration of hooks and the interrogation methods. As the +// constructor registers two hooks, this is also a test of the constructor. + +TEST(ServerHooksTest, RegisterHooks) { + ServerHooks& hooks = ServerHooks::getServerHooks(); + hooks.reset(); + + // There should be two hooks already registered, with indexes 0 and 1. + EXPECT_EQ(2, hooks.getCount()); + EXPECT_EQ(0, hooks.getIndex("context_create")); + EXPECT_EQ(0, hooks.findIndex("context_create")); + EXPECT_EQ(1, hooks.getIndex("context_destroy")); + EXPECT_EQ(1, hooks.findIndex("context_destroy")); + + // Check that the constants are as expected. (The intermediate variables + // are used because of problems with g++ 4.6.1/Ubuntu 11.10 when resolving + // the value of the ServerHooks constants when they appeared within the + // gtest macro.) + const int create_value = ServerHooks::CONTEXT_CREATE; + const int destroy_value = ServerHooks::CONTEXT_DESTROY; + EXPECT_EQ(0, create_value); + EXPECT_EQ(1, destroy_value); + + // Register another couple of hooks. The test on returned index is based + // on knowledge that the hook indexes are assigned in ascending order. + int alpha = hooks.registerHook("alpha"); + EXPECT_EQ(2, alpha); + EXPECT_EQ(2, hooks.getIndex("alpha")); + + int beta = hooks.registerHook("beta"); + EXPECT_EQ(3, beta); + EXPECT_EQ(3, hooks.getIndex("beta")); + + // Should be four hooks now + EXPECT_EQ(4, hooks.getCount()); +} + +// Check that duplicate names cannot be registered. +// This test has been updated. See #5251 for details. The old +// code is retained in case we decide to get back to it. +TEST(ServerHooksTest, DISABLED_OldDuplicateHooks) { + ServerHooks& hooks = ServerHooks::getServerHooks(); + hooks.reset(); + + // Ensure we can't duplicate one of the existing names. + EXPECT_THROW(hooks.registerHook("context_create"), DuplicateHook); + + // Check we can't duplicate a newly registered hook. + int gamma = hooks.registerHook("gamma"); + EXPECT_EQ(2, gamma); + EXPECT_THROW(hooks.registerHook("gamma"), DuplicateHook); +} + +// Check that duplicate names are handled properly. The code used to throw, +// but it now returns the existing index. See #5251 for details. +TEST(ServerHooksTest, NewDuplicateHooks) { + ServerHooks& hooks = ServerHooks::getServerHooks(); + hooks.reset(); + + int index = hooks.getIndex("context_create"); + + // Ensure we can duplicate one of the existing names. + // Instead of throwing, we just check that a reasonable + // index has been returned. + EXPECT_EQ(index, hooks.registerHook("context_create")); + + // Check that mutiple attempts to register the same hook will return + // existing index. + int gamma = hooks.registerHook("gamma"); + EXPECT_EQ(2, gamma); + EXPECT_EQ(gamma, hooks.registerHook("gamma")); + EXPECT_EQ(gamma, hooks.registerHook("gamma")); + EXPECT_EQ(gamma, hooks.registerHook("gamma")); + EXPECT_EQ(gamma, hooks.registerHook("gamma")); +} + +// Checks that we can get the name of the hooks. + +TEST(ServerHooksTest, GetHookNames) { + ServerHooks& hooks = ServerHooks::getServerHooks(); + hooks.reset(); + vector<string> expected_names; + + // Add names into the hooks object and to the set of expected names. + expected_names.push_back("alpha"); + expected_names.push_back("beta"); + expected_names.push_back("gamma"); + expected_names.push_back("delta"); + for (size_t i = 0; i < expected_names.size(); ++i) { + hooks.registerHook(expected_names[i].c_str()); + }; + + // Update the expected names to include the pre-defined hook names. + expected_names.push_back("context_create"); + expected_names.push_back("context_destroy"); + + // Get the actual hook names + vector<string> actual_names = hooks.getHookNames(); + + // For comparison, sort the names into alphabetical order and do a straight + // vector comparison. + sort(expected_names.begin(), expected_names.end()); + sort(actual_names.begin(), actual_names.end()); + + EXPECT_TRUE(expected_names == actual_names); +} + +// Test the inverse hooks functionality (i.e. given an index, get the name). + +TEST(ServerHooksTest, GetHookIndexes) { + ServerHooks& hooks = ServerHooks::getServerHooks(); + hooks.reset(); + + int alpha = hooks.registerHook("alpha"); + int beta = hooks.registerHook("beta"); + int gamma = hooks.registerHook("gamma"); + + EXPECT_EQ(std::string("context_create"), + hooks.getName(ServerHooks::CONTEXT_CREATE)); + EXPECT_EQ(std::string("context_destroy"), + hooks.getName(ServerHooks::CONTEXT_DESTROY)); + EXPECT_EQ(std::string("alpha"), hooks.getName(alpha)); + EXPECT_EQ(std::string("beta"), hooks.getName(beta)); + EXPECT_EQ(std::string("gamma"), hooks.getName(gamma)); + + // Check for an invalid index + EXPECT_THROW(hooks.getName(-1), NoSuchHook); + EXPECT_THROW(hooks.getName(42), NoSuchHook); +} + +// Test the reset functionality. + +TEST(ServerHooksTest, Reset) { + ServerHooks& hooks = ServerHooks::getServerHooks(); + hooks.reset(); + + int alpha = hooks.registerHook("alpha"); + int beta = hooks.registerHook("beta"); + int gamma = hooks.registerHook("gamma"); + + EXPECT_EQ(std::string("alpha"), hooks.getName(alpha)); + EXPECT_EQ(std::string("beta"), hooks.getName(beta)); + EXPECT_EQ(std::string("gamma"), hooks.getName(gamma)); + + // Check the counts before and after a reset. + EXPECT_EQ(5, hooks.getCount()); + hooks.reset(); + EXPECT_EQ(2, hooks.getCount()); + + // ... and check that the hooks are as expected. + EXPECT_EQ(0, hooks.getIndex("context_create")); + EXPECT_EQ(1, hooks.getIndex("context_destroy")); +} + +// Check that getting an unknown name throws an exception. + +TEST(ServerHooksTest, UnknownHookName) { + ServerHooks& hooks = ServerHooks::getServerHooks(); + hooks.reset(); + + EXPECT_THROW(static_cast<void>(hooks.getIndex("unknown")), NoSuchHook); + EXPECT_EQ(-1, hooks.findIndex("unknown")); +} + +// Check that the count of hooks is correct. + +TEST(ServerHooksTest, HookCount) { + ServerHooks& hooks = ServerHooks::getServerHooks(); + hooks.reset(); + + // Insert the names into the hooks object + hooks.registerHook("alpha"); + hooks.registerHook("beta"); + hooks.registerHook("gamma"); + hooks.registerHook("delta"); + + // Should be two more hooks that the number we have registered. + EXPECT_EQ(6, hooks.getCount()); +} + +// Check that the hook name is correctly generated for a control command name +// and vice versa. + +TEST(ServerHooksTest, CommandToHookName) { + EXPECT_EQ("$x_y_z", ServerHooks::commandToHookName("x-y-z")); + EXPECT_EQ("$foo_bar_foo", ServerHooks::commandToHookName("foo-bar_foo")); + EXPECT_EQ("$", ServerHooks::commandToHookName("")); +} + +TEST(ServerHooksTest, HookToCommandName) { + // Underscores replaced by hyphens. + EXPECT_EQ("x-y-z", ServerHooks::hookToCommandName("$x_y_z")); + EXPECT_EQ("foo-bar-foo", ServerHooks::hookToCommandName("$foo_bar-foo")); + // Single dollar is converted to empty string. + EXPECT_TRUE(ServerHooks::hookToCommandName("$").empty()); + // If no dollar, it is not a hook name. Return empty string. + EXPECT_TRUE(ServerHooks::hookToCommandName("abc").empty()); +} + +TEST(ServerHooksTest, getParkingLots) { + ServerHooks& hooks = ServerHooks::getServerHooks(); + hooks.reset(); + int alpha_hook = hooks.registerHook("alpha"); + + ASSERT_TRUE(hooks.getParkingLotsPtr()); + ASSERT_TRUE(hooks.getParkingLotPtr(alpha_hook)); + ASSERT_TRUE(hooks.getParkingLotPtr("alpha")); +} + +} // Anonymous namespace diff --git a/src/lib/hooks/tests/test_libraries.h.in b/src/lib/hooks/tests/test_libraries.h.in new file mode 100644 index 0000000..afad62f --- /dev/null +++ b/src/lib/hooks/tests/test_libraries.h.in @@ -0,0 +1,61 @@ +// Copyright (C) 2013-2018 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef TEST_LIBRARIES_H +#define TEST_LIBRARIES_H + +#include <config.h> + +namespace { + +// Names of the libraries used in these tests. These libraries are built using +// libtool, so we need to look in the hidden ".libs" directory to locate the +// .so file. Note that we access the .so file - libtool creates this as a +// like to the real shared library. + +// Basic library with context_create and three "standard" callouts. +static const char* BASIC_CALLOUT_LIBRARY = "@abs_builddir@/.libs/libbcl.so"; + +// Library with context_create and three "standard" callouts, as well as +// load() and unload() functions. +static const char* FULL_CALLOUT_LIBRARY = "@abs_builddir@/.libs/libfcl.so"; + +// Library where the all framework functions throw an exception +static const char* FRAMEWORK_EXCEPTION_LIBRARY = "@abs_builddir@/.libs/libfxl.so"; + +// Library where the version() function returns an incorrect result. +static const char* INCORRECT_VERSION_LIBRARY = "@abs_builddir@/.libs/libivl.so"; + +// Library where some of the callout registration is done with the load() +// function. +static const char* LOAD_CALLOUT_LIBRARY = "@abs_builddir@/.libs/liblcl.so"; + +// Library where the load() function returns an error. +static const char* LOAD_ERROR_CALLOUT_LIBRARY = + "@abs_builddir@/.libs/liblecl.so"; + +// Name of a library which is not present. +static const char* NOT_PRESENT_LIBRARY = "@abs_builddir@/.libs/libnothere.so"; + +// Library that does not include a version function. +static const char* NO_VERSION_LIBRARY = "@abs_builddir@/.libs/libnvl.so"; + +// Library where there is an unload() function. +static const char* UNLOAD_CALLOUT_LIBRARY = "@abs_builddir@/.libs/libucl.so"; + +// Library where parameters are checked. +static const char* CALLOUT_PARAMS_LIBRARY = "@abs_builddir@/.libs/libpcl.so"; + +// Library which tests objects parking. +// Used only by hooks_manager_unittest.cc. +#ifdef TEST_ASYNC_CALLOUT +static const char* ASYNC_CALLOUT_LIBRARY = "@abs_builddir@/.libs/libacl.so"; +#endif + +} // anonymous namespace + + +#endif // TEST_LIBRARIES_H diff --git a/src/lib/hooks/tests/unload_callout_library.cc b/src/lib/hooks/tests/unload_callout_library.cc new file mode 100644 index 0000000..a15b870 --- /dev/null +++ b/src/lib/hooks/tests/unload_callout_library.cc @@ -0,0 +1,46 @@ +// Copyright (C) 2013-2015 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +/// @file +/// @brief Basic unload library +/// +/// This is source of a test library for various test (LibraryManager and +/// HooksManager). The characteristics of the library produced from this +/// file are: +/// +/// - The "version" and "unload" framework functions are supplied. "version" +/// returns a valid value and "unload" creates a marker file and returns +/// success. + +#include <config.h> + +#include <hooks/hooks.h> +#include <hooks/tests/marker_file.h> + +#include <fstream> + +using namespace isc::hooks; + +extern "C" { + +// Framework functions + +int +version() { + return (KEA_HOOKS_VERSION); +} + +int +unload() { + // Create the marker file. + std::fstream marker; + marker.open(MARKER_FILE, std::fstream::out); + marker.close(); + + return (0); +} + +}; |