Browse Source

[master] Merge branch 'trac2981'

Stephen Morris 11 years ago
parent
commit
aff6b06b24
34 changed files with 1810 additions and 114 deletions
  1. 5 0
      configure.ac
  2. 20 0
      src/bin/dhcp4/config_parser.cc
  3. 22 3
      src/bin/dhcp4/ctrl_dhcp4_srv.cc
  4. 21 0
      src/bin/dhcp4/dhcp4.spec
  5. 5 0
      src/bin/dhcp4/dhcp4_messages.mes
  6. 13 0
      src/bin/dhcp4/tests/Makefile.am
  7. 22 0
      src/bin/dhcp4/tests/callout_library_1.cc
  8. 22 0
      src/bin/dhcp4/tests/callout_library_2.cc
  9. 82 0
      src/bin/dhcp4/tests/callout_library_common.h
  10. 218 33
      src/bin/dhcp4/tests/config_parser_unittest.cc
  11. 68 2
      src/bin/dhcp4/tests/ctrl_dhcp4_srv_unittest.cc
  12. 66 0
      src/bin/dhcp4/tests/marker_file.cc
  13. 69 0
      src/bin/dhcp4/tests/marker_file.h.in
  14. 51 0
      src/bin/dhcp4/tests/test_libraries.h.in
  15. 21 0
      src/bin/dhcp6/config_parser.cc
  16. 19 0
      src/bin/dhcp6/ctrl_dhcp6_srv.cc
  17. 20 0
      src/bin/dhcp6/dhcp6.spec
  18. 5 0
      src/bin/dhcp6/dhcp6_messages.mes
  19. 17 4
      src/bin/dhcp6/tests/Makefile.am
  20. 22 0
      src/bin/dhcp6/tests/callout_library_1.cc
  21. 22 0
      src/bin/dhcp6/tests/callout_library_2.cc
  22. 82 0
      src/bin/dhcp6/tests/callout_library_common.h
  23. 233 38
      src/bin/dhcp6/tests/config_parser_unittest.cc
  24. 75 9
      src/bin/dhcp6/tests/ctrl_dhcp6_srv_unittest.cc
  25. 66 0
      src/bin/dhcp6/tests/marker_file.cc
  26. 69 0
      src/bin/dhcp6/tests/marker_file.h.in
  27. 51 0
      src/bin/dhcp6/tests/test_libraries.h.in
  28. 2 0
      src/lib/dhcpsrv/Makefile.am
  29. 76 3
      src/lib/dhcpsrv/dhcp_parsers.cc
  30. 88 1
      src/lib/dhcpsrv/dhcp_parsers.h
  31. 14 2
      src/lib/dhcpsrv/tests/Makefile.am
  32. 31 0
      src/lib/dhcpsrv/tests/callout_library.cc
  33. 162 19
      src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc
  34. 51 0
      src/lib/dhcpsrv/tests/test_libraries.h.in

+ 5 - 0
configure.ac

@@ -1413,6 +1413,10 @@ AC_OUTPUT([doc/version.ent
            src/bin/dbutil/run_dbutil.sh
            src/bin/dbutil/run_dbutil.sh
            src/bin/dbutil/tests/dbutil_test.sh
            src/bin/dbutil/tests/dbutil_test.sh
            src/bin/ddns/ddns.py
            src/bin/ddns/ddns.py
+           src/bin/dhcp4/tests/marker_file.h
+           src/bin/dhcp4/tests/test_libraries.h
+           src/bin/dhcp6/tests/marker_file.h
+           src/bin/dhcp6/tests/test_libraries.h
            src/bin/xfrin/tests/xfrin_test
            src/bin/xfrin/tests/xfrin_test
            src/bin/xfrin/xfrin.py
            src/bin/xfrin/xfrin.py
            src/bin/xfrin/run_b10-xfrin.sh
            src/bin/xfrin/run_b10-xfrin.sh
@@ -1458,6 +1462,7 @@ AC_OUTPUT([doc/version.ent
            src/bin/d2/tests/test_data_files_config.h
            src/bin/d2/tests/test_data_files_config.h
            src/bin/tests/process_rename_test.py
            src/bin/tests/process_rename_test.py
            src/lib/config/tests/data_def_unittests_config.h
            src/lib/config/tests/data_def_unittests_config.h
+           src/lib/dhcpsrv/tests/test_libraries.h
            src/lib/python/isc/config/tests/config_test
            src/lib/python/isc/config/tests/config_test
            src/lib/python/isc/cc/tests/cc_test
            src/lib/python/isc/cc/tests/cc_test
            src/lib/python/isc/notify/tests/notify_out_test
            src/lib/python/isc/notify/tests/notify_out_test

+ 20 - 0
src/bin/dhcp4/config_parser.cc

@@ -364,6 +364,8 @@ DhcpConfigParser* createGlobalDhcp4ConfigParser(const std::string& config_id) {
                                     globalContext()->string_values_);
                                     globalContext()->string_values_);
     } else if (config_id.compare("lease-database") == 0) {
     } else if (config_id.compare("lease-database") == 0) {
         parser = new DbAccessParser(config_id);
         parser = new DbAccessParser(config_id);
+    } else if (config_id.compare("hooks-libraries") == 0) {
+        parser = new HooksLibrariesParser(config_id);
     } else {
     } else {
         isc_throw(NotImplemented,
         isc_throw(NotImplemented,
                 "Parser error: Global configuration parameter not supported: "
                 "Parser error: Global configuration parameter not supported: "
@@ -399,6 +401,11 @@ configureDhcp4Server(Dhcpv4Srv&, isc::data::ConstElementPtr config_set) {
     ParserPtr option_parser;
     ParserPtr option_parser;
     ParserPtr iface_parser;
     ParserPtr iface_parser;
 
 
+    // Some of the parsers alter the state of the system in a way that can't
+    // easily be undone. (Or alter it in a way such that undoing the change has
+    // the same risk of failure as doing the change.)
+    ParserPtr hooks_parser_;
+
     // The subnet parsers implement data inheritance by directly
     // The subnet parsers implement data inheritance by directly
     // accessing global storage. For this reason the global data
     // accessing global storage. For this reason the global data
     // parsers must store the parsed data into global storages
     // parsers must store the parsed data into global storages
@@ -434,6 +441,12 @@ configureDhcp4Server(Dhcpv4Srv&, isc::data::ConstElementPtr config_set) {
                 // parser and can be run here before any other parsers.
                 // parser and can be run here before any other parsers.
                 iface_parser = parser;
                 iface_parser = parser;
                 parser->build(config_pair.second);
                 parser->build(config_pair.second);
+            } else if (config_pair.first == "hooks-libraries") {
+                // Executing commit will alter currently-loaded hooks
+                // libraries.  Check if the supplied libraries are valid,
+                // but defer the commit until everything else has committed.
+                hooks_parser_ = parser;
+                parser->build(config_pair.second);
             } else {
             } else {
                 // Those parsers should be started before other
                 // Those parsers should be started before other
                 // parsers so we can call build straight away.
                 // parsers so we can call build straight away.
@@ -493,6 +506,13 @@ configureDhcp4Server(Dhcpv4Srv&, isc::data::ConstElementPtr config_set) {
             if (iface_parser) {
             if (iface_parser) {
                 iface_parser->commit();
                 iface_parser->commit();
             }
             }
+
+            // This occurs last as if it succeeds, there is no easy way
+            // revert it.  As a result, the failure to commit a subsequent
+            // change causes problems when trying to roll back.
+            if (hooks_parser_) {
+                hooks_parser_->commit();
+            }
         }
         }
         catch (const isc::Exception& ex) {
         catch (const isc::Exception& ex) {
             LOG_ERROR(dhcp4_logger, DHCP4_PARSER_COMMIT_FAIL).arg(ex.what());
             LOG_ERROR(dhcp4_logger, DHCP4_PARSER_COMMIT_FAIL).arg(ex.what());

+ 22 - 3
src/bin/dhcp4/ctrl_dhcp4_srv.cc

@@ -19,24 +19,28 @@
 #include <cc/session.h>
 #include <cc/session.h>
 #include <config/ccsession.h>
 #include <config/ccsession.h>
 #include <dhcp/iface_mgr.h>
 #include <dhcp/iface_mgr.h>
-#include <dhcpsrv/dhcp_config_parser.h>
-#include <dhcpsrv/cfgmgr.h>
+#include <dhcp4/config_parser.h>
 #include <dhcp4/ctrl_dhcp4_srv.h>
 #include <dhcp4/ctrl_dhcp4_srv.h>
 #include <dhcp4/dhcp4_log.h>
 #include <dhcp4/dhcp4_log.h>
 #include <dhcp4/spec_config.h>
 #include <dhcp4/spec_config.h>
-#include <dhcp4/config_parser.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/dhcp_config_parser.h>
 #include <exceptions/exceptions.h>
 #include <exceptions/exceptions.h>
+#include <hooks/hooks_manager.h>
 #include <util/buffer.h>
 #include <util/buffer.h>
 
 
 #include <cassert>
 #include <cassert>
 #include <iostream>
 #include <iostream>
 #include <sstream>
 #include <sstream>
+#include <string>
+#include <vector>
 
 
 using namespace isc::asiolink;
 using namespace isc::asiolink;
 using namespace isc::cc;
 using namespace isc::cc;
 using namespace isc::config;
 using namespace isc::config;
 using namespace isc::data;
 using namespace isc::data;
 using namespace isc::dhcp;
 using namespace isc::dhcp;
+using namespace isc::hooks;
 using namespace isc::log;
 using namespace isc::log;
 using namespace isc::util;
 using namespace isc::util;
 using namespace std;
 using namespace std;
@@ -141,6 +145,21 @@ ControlledDhcpv4Srv::dhcp4CommandHandler(const string& command, ConstElementPtr
         ConstElementPtr answer = isc::config::createAnswer(0,
         ConstElementPtr answer = isc::config::createAnswer(0,
                                  "Shutting down.");
                                  "Shutting down.");
         return (answer);
         return (answer);
+
+    } else if (command == "libreload") {
+        // TODO delete any stored CalloutHandles referring to the old libraries
+        // Get list of currently loaded libraries and reload them.
+        vector<string> loaded = HooksManager::getLibraryNames();
+        bool status = HooksManager::loadLibraries(loaded);
+        if (!status) {
+            LOG_ERROR(dhcp4_logger, DHCP4_HOOKS_LIBS_RELOAD_FAIL);
+            ConstElementPtr answer = isc::config::createAnswer(1,
+                                     "Failed to reload hooks libraries.");
+            return (answer);
+        }
+        ConstElementPtr answer = isc::config::createAnswer(0,
+                                 "Hooks libraries successfully reloaded.");
+        return (answer);
     }
     }
 
 
     ConstElementPtr answer = isc::config::createAnswer(1,
     ConstElementPtr answer = isc::config::createAnswer(1,

+ 21 - 0
src/bin/dhcp4/dhcp4.spec

@@ -3,6 +3,20 @@
     "module_name": "Dhcp4",
     "module_name": "Dhcp4",
     "module_description": "DHCPv4 server daemon",
     "module_description": "DHCPv4 server daemon",
     "config_data": [
     "config_data": [
+      {
+        "item_name": "hooks-libraries",
+        "item_type": "list",
+        "item_optional": true,
+        "item_default": [],
+        "list_item_spec":
+        {
+          "item_name": "hooks-library",
+          "item_type": "string",
+          "item_optional": false,
+          "item_default": ""
+        }
+      },
+ 
       { "item_name": "interfaces",
       { "item_name": "interfaces",
         "item_type": "list",
         "item_type": "list",
         "item_optional": false,
         "item_optional": false,
@@ -272,7 +286,14 @@
                     "item_optional": true
                     "item_optional": true
                 }
                 }
             ]
             ]
+        },
+
+        {
+            "command_name": "libreload",
+            "command_description": "Reloads the current hooks libraries.", 
+            "command_args": []
         }
         }
+
     ]
     ]
   }
   }
 }
 }

+ 5 - 0
src/bin/dhcp4/dhcp4_messages.mes

@@ -89,6 +89,11 @@ point, the setting of the flag instructs the server not to choose a
 subnet, an action that severely limits further processing; the server
 subnet, an action that severely limits further processing; the server
 will be only able to offer global options - no addresses will be assigned.
 will be only able to offer global options - no addresses will be assigned.
 
 
+% DHCP4_HOOKS_LIBS_RELOAD_FAIL reload of hooks libraries failed
+A "libreload" command was issued to reload the hooks libraries but for
+some reason the reload failed.  Other error messages issued from the
+hooks framework will indicate the nature of the problem.
+
 % DHCP4_LEASE_ADVERT lease %1 advertised (client client-id %2, hwaddr %3)
 % DHCP4_LEASE_ADVERT lease %1 advertised (client client-id %2, hwaddr %3)
 This debug message indicates that the server successfully advertised
 This debug message indicates that the server successfully advertised
 a lease. It is up to the client to choose one server out of othe advertised
 a lease. It is up to the client to choose one server out of othe advertised

+ 13 - 0
src/bin/dhcp4/tests/Makefile.am

@@ -32,6 +32,7 @@ AM_CPPFLAGS += -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/bin/dhcp6/tests\"
 AM_CPPFLAGS += -DINSTALL_PROG=\"$(abs_top_srcdir)/install-sh\"
 AM_CPPFLAGS += -DINSTALL_PROG=\"$(abs_top_srcdir)/install-sh\"
 
 
 CLEANFILES = $(builddir)/interfaces.txt $(builddir)/logger_lockfile
 CLEANFILES = $(builddir)/interfaces.txt $(builddir)/logger_lockfile
+CLEANFILES += $(builddir)/load_marker.txt $(builddir)/unload_marker.txt
 
 
 AM_CXXFLAGS = $(B10_CXXFLAGS)
 AM_CXXFLAGS = $(B10_CXXFLAGS)
 if USE_CLANGPP
 if USE_CLANGPP
@@ -48,6 +49,16 @@ TESTS_ENVIRONMENT = \
 
 
 TESTS =
 TESTS =
 if HAVE_GTEST
 if HAVE_GTEST
+# Build shared libraries for testing.
+lib_LTLIBRARIES = libco1.la libco2.la
+
+libco1_la_SOURCES  = callout_library_1.cc callout_library_common.h
+libco1_la_CXXFLAGS = $(AM_CXXFLAGS)
+libco1_la_CPPFLAGS = $(AM_CPPFLAGS)
+
+libco2_la_SOURCES  = callout_library_2.cc callout_library_common.h
+libco2_la_CXXFLAGS = $(AM_CXXFLAGS)
+libco2_la_CPPFLAGS = $(AM_CPPFLAGS)
 
 
 TESTS += dhcp4_unittests
 TESTS += dhcp4_unittests
 
 
@@ -58,7 +69,9 @@ dhcp4_unittests_SOURCES += dhcp4_unittests.cc
 dhcp4_unittests_SOURCES += dhcp4_srv_unittest.cc
 dhcp4_unittests_SOURCES += dhcp4_srv_unittest.cc
 dhcp4_unittests_SOURCES += ctrl_dhcp4_srv_unittest.cc
 dhcp4_unittests_SOURCES += ctrl_dhcp4_srv_unittest.cc
 dhcp4_unittests_SOURCES += config_parser_unittest.cc
 dhcp4_unittests_SOURCES += config_parser_unittest.cc
+dhcp4_unittests_SOURCES += marker_file.cc
 nodist_dhcp4_unittests_SOURCES = ../dhcp4_messages.h ../dhcp4_messages.cc
 nodist_dhcp4_unittests_SOURCES = ../dhcp4_messages.h ../dhcp4_messages.cc
+nodist_dhcp4_unittests_SOURCES += marker_file.h test_libraries.h
 
 
 dhcp4_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
 dhcp4_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
 dhcp4_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
 dhcp4_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)

+ 22 - 0
src/bin/dhcp4/tests/callout_library_1.cc

@@ -0,0 +1,22 @@
+// Copyright (C) 2013  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+/// @file
+/// @brief Marker file callout library
+///
+/// This is the source of a test library for the DHCP parser and configuration
+/// tests.  See callout_common.cc for details.
+
+static const int LIBRARY_NUMBER = 1;
+#include "callout_library_common.h"

+ 22 - 0
src/bin/dhcp4/tests/callout_library_2.cc

@@ -0,0 +1,22 @@
+// Copyright (C) 2013  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+/// @file
+/// @brief Marker file callout library
+///
+/// This is the source of a test library for the DHCP parser and configuration
+/// tests.  See callout_common.cc for details.
+
+static const int LIBRARY_NUMBER = 2;
+#include "callout_library_common.h"

+ 82 - 0
src/bin/dhcp4/tests/callout_library_common.h

@@ -0,0 +1,82 @@
+// Copyright (C) 2013  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+/// @file
+/// @brief Marker file callout library
+///
+/// This is the source of a test library for the DHCP parser and configuration
+/// tests.
+///
+/// To check that they libraries are loaded and unloaded correctly, the load
+/// and unload functions in this library maintain two marker files - the load
+/// marker file and the unload marker file.  The functions append a single
+/// line to the file, creating the file if need be.  In this way, the test code
+/// can determine whether the load/unload functions have been run and, if so,
+/// in what order.
+///
+/// This file is the common library file for the tests.  It will not compile
+/// by itself - it is included into each callout library which specifies the
+/// missing constant LIBRARY_NUMBER before the inclusion.
+
+#include <hooks/hooks.h>
+#include "marker_file.h"
+
+#include <fstream>
+
+using namespace isc::hooks;
+using namespace std;
+
+extern "C" {
+
+/// @brief Append digit to marker file
+///
+/// If the marker file does not exist, create it.  Then append the single
+/// digit (given by the constant LIBRARY_NUMBER) defined earlier to it and
+/// close the file.
+///
+/// @param name Name of the file to open
+///
+/// @return 0 on success, non-zero on error.
+int
+appendDigit(const char* name) {
+    // Open the file and check if successful.
+    fstream file(name, fstream::out | fstream::app);
+    if (!file.good()) {
+        return (1);
+    }
+
+    // Add the library number to it and close.
+    file << LIBRARY_NUMBER;
+    file.close();
+
+    return (0);
+}
+
+// Framework functions
+int
+version() {
+    return (BIND10_HOOKS_VERSION);
+}
+
+int
+load(LibraryHandle&) {
+    return (appendDigit(LOAD_MARKER_FILE));
+}
+
+int
+unload() {
+    return (appendDigit(UNLOAD_MARKER_FILE));
+}
+
+};

+ 218 - 33
src/bin/dhcp4/tests/config_parser_unittest.cc

@@ -25,6 +25,10 @@
 #include <dhcp/option_int.h>
 #include <dhcp/option_int.h>
 #include <dhcpsrv/subnet.h>
 #include <dhcpsrv/subnet.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/cfgmgr.h>
+#include <hooks/hooks_manager.h>
+
+#include "marker_file.h"
+#include "test_libraries.h"
 
 
 #include <boost/foreach.hpp>
 #include <boost/foreach.hpp>
 #include <boost/scoped_ptr.hpp>
 #include <boost/scoped_ptr.hpp>
@@ -34,12 +38,14 @@
 #include <sstream>
 #include <sstream>
 #include <limits.h>
 #include <limits.h>
 
 
-using namespace std;
 using namespace isc;
 using namespace isc;
-using namespace isc::dhcp;
 using namespace isc::asiolink;
 using namespace isc::asiolink;
-using namespace isc::data;
 using namespace isc::config;
 using namespace isc::config;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+using namespace isc::hooks;
+using namespace std;
 
 
 namespace {
 namespace {
 
 
@@ -54,6 +60,15 @@ public:
         CfgMgr::instance().deleteActiveIfaces();
         CfgMgr::instance().deleteActiveIfaces();
     }
     }
 
 
+    // Check that no hooks libraries are loaded.  This is a pre-condition for
+    // a number of tests, so is checked in one place.  As this uses an
+    // ASSERT call - and it is not clear from the documentation that Gtest
+    // predicates can be used in a constructor - the check is placed in SetUp.
+    void SetUp() {
+        std::vector<std::string> libraries = HooksManager::getLibraryNames();
+        ASSERT_TRUE(libraries.empty());
+    }
+
     // Checks if global parameter of name have expected_value
     // Checks if global parameter of name have expected_value
     void checkGlobalUint32(string name, uint32_t expected_value) {
     void checkGlobalUint32(string name, uint32_t expected_value) {
         const Uint32StoragePtr uint32_defaults =
         const Uint32StoragePtr uint32_defaults =
@@ -78,6 +93,10 @@ public:
 
 
     ~Dhcp4ParserTest() {
     ~Dhcp4ParserTest() {
         resetConfiguration();
         resetConfiguration();
+
+        // ... and delete the hooks library marker files if present
+        unlink(LOAD_MARKER_FILE);
+        unlink(UNLOAD_MARKER_FILE);
     };
     };
 
 
     /// @brief Create the simple configuration with single option.
     /// @brief Create the simple configuration with single option.
@@ -236,56 +255,81 @@ public:
                             expected_data_len));
                             expected_data_len));
     }
     }
 
 
-    /// @brief Reset configuration database.
+    /// @brief Parse and Execute configuration
     ///
     ///
-    /// This function resets configuration data base by
-    /// removing all subnets and option-data. Reset must
-    /// be performed after each test to make sure that
-    /// contents of the database do not affect result of
-    /// subsequent tests.
-    void resetConfiguration() {
+    /// Parses a configuration and executes a configuration of the server.
+    /// If the operation fails, the current test will register a failure.
+    ///
+    /// @param config Configuration to parse
+    /// @param operation Operation being performed.  In the case of an error,
+    ///        the error text will include the string "unable to <operation>.".
+    ///
+    /// @return true if the configuration succeeded, false if not.  In the
+    ///         latter case, a failure will have been added to the current test.
+    bool
+    executeConfiguration(const std::string& config, const char* operation) {
         ConstElementPtr status;
         ConstElementPtr status;
-
-        string config = "{ \"interfaces\": [ \"*\" ],"
-            "\"rebind-timer\": 2000, "
-            "\"renew-timer\": 1000, "
-            "\"valid-lifetime\": 4000, "
-            "\"subnet4\": [ ], "
-            "\"option-def\": [ ], "
-            "\"option-data\": [ ] }";
-
         try {
         try {
             ElementPtr json = Element::fromJSON(config);
             ElementPtr json = Element::fromJSON(config);
             status = configureDhcp4Server(*srv_, json);
             status = configureDhcp4Server(*srv_, json);
         } catch (const std::exception& ex) {
         } catch (const std::exception& ex) {
-            FAIL() << "Fatal error: unable to reset configuration database"
-                   << " after the test. The following configuration was used"
-                   << " to reset database: " << std::endl
+            ADD_FAILURE() << "Unable to " << operation << ". "
+                   << "The following configuration was used: " << std::endl
                    << config << std::endl
                    << config << std::endl
                    << " and the following error message was returned:"
                    << " and the following error message was returned:"
                    << ex.what() << std::endl;
                    << ex.what() << std::endl;
+            return (false);
         }
         }
 
 
-        // status object must not be NULL
+        // The status object must not be NULL
         if (!status) {
         if (!status) {
-            FAIL() << "Fatal error: unable to reset configuration database"
-                   << " after the test. Configuration function returned"
-                   << " NULL pointer" << std::endl;
+            ADD_FAILURE() << "Unable to " << operation << ". "
+                   << "The configuration function returned a null pointer.";
+            return (false);
         }
         }
 
 
+        // Store the answer if we need it.
+
+        // Returned value should be 0 (configuration success)
         comment_ = parseAnswer(rcode_, status);
         comment_ = parseAnswer(rcode_, status);
-        // returned value should be 0 (configuration success)
         if (rcode_ != 0) {
         if (rcode_ != 0) {
-            FAIL() << "Fatal error: unable to reset configuration database"
-                   << " after the test. Configuration function returned"
-                   << " error code " << rcode_ << std::endl;
+            string reason = "";
+            if (comment_) {
+                reason = string(" (") + comment_->stringValue() + string(")");
+            }
+            ADD_FAILURE() << "Unable to " << operation << ". "
+                   << "The configuration function returned error code "
+                   << rcode_ << reason;
+            return (false);
         }
         }
+
+        return (true);
+    }
+
+    /// @brief Reset configuration database.
+    ///
+    /// This function resets configuration data base by
+    /// removing all subnets and option-data. Reset must
+    /// be performed after each test to make sure that
+    /// contents of the database do not affect result of
+    /// subsequent tests.
+    void resetConfiguration() {
+        string config = "{ \"interfaces\": [ \"*\" ],"
+            "\"hooks-libraries\": [ ], "
+            "\"rebind-timer\": 2000, "
+            "\"renew-timer\": 1000, "
+            "\"valid-lifetime\": 4000, "
+            "\"subnet4\": [ ], "
+            "\"option-def\": [ ], "
+            "\"option-data\": [ ] }";
+        static_cast<void>(executeConfiguration(config,
+                                               "reset configuration database"));
     }
     }
 
 
-    boost::scoped_ptr<Dhcpv4Srv> srv_;
 
 
-    int rcode_;
-    ConstElementPtr comment_;
+    boost::scoped_ptr<Dhcpv4Srv> srv_;      // DHCP4 server under test
+    int rcode_;                             // Return code from element parsing
+    ConstElementPtr comment_;               // Reason for parse fail
 };
 };
 
 
 // Goal of this test is a verification if a very simple config update
 // Goal of this test is a verification if a very simple config update
@@ -1750,6 +1794,147 @@ TEST_F(Dhcp4ParserTest, stdOptionDataEncapsulate) {
     EXPECT_FALSE(desc.option->getOption(3));
     EXPECT_FALSE(desc.option->getOption(3));
 }
 }
 
 
+// Tests of the hooks libraries configuration.  All tests have the pre-
+// condition (checked in the test fixture's SetUp() method) that no hooks
+// libraries are loaded at the start of the tests.
+
+// Helper function to return a configuration containing an arbitrary number
+// of hooks libraries.
+std::string
+buildHooksLibrariesConfig(const std::vector<std::string>& libraries) {
+    const string quote("\"");
+
+    // Create the first part of the configuration string.
+    string config =
+        "{ \"interfaces\": [ \"*\" ],"
+            "\"hooks-libraries\": [";
+
+    // Append the libraries (separated by commas if needed)
+    for (int i = 0; i < libraries.size(); ++i) {
+        if (i > 0) {
+            config += string(", ");
+        }
+        config += (quote + libraries[i] + quote);
+    }
+
+    // Append the remainder of the configuration.
+    config += string(
+        "],"
+        "\"rebind-timer\": 2000,"
+        "\"renew-timer\": 1000,"
+        "\"option-data\": [ {"
+        "    \"name\": \"dhcp-message\","
+        "    \"space\": \"dhcp4\","
+        "    \"code\": 56,"
+        "    \"data\": \"AB CDEF0105\","
+        "    \"csv-format\": False"
+        " },"
+        " {"
+        "    \"name\": \"foo\","
+        "    \"space\": \"isc\","
+        "    \"code\": 56,"
+        "    \"data\": \"1234\","
+        "    \"csv-format\": True"
+        " } ],"
+        "\"option-def\": [ {"
+        "    \"name\": \"foo\","
+        "    \"code\": 56,"
+        "    \"type\": \"uint32\","
+        "    \"array\": False,"
+        "    \"record-types\": \"\","
+        "    \"space\": \"isc\","
+        "    \"encapsulate\": \"\""
+        " } ],"
+        "\"subnet4\": [ { "
+        "    \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ],"
+        "    \"subnet\": \"192.0.2.0/24\""
+        " } ]"
+        "}");
+
+    return (config);
+}
+
+// Convenience function for creating hooks library configuration with one or
+// two character string constants.
+std::string
+buildHooksLibrariesConfig(const char* library1 = NULL,
+                          const char* library2 = NULL) {
+    std::vector<std::string> libraries;
+    if (library1 != NULL) {
+        libraries.push_back(string(library1));
+        if (library2 != NULL) {
+            libraries.push_back(string(library2));
+        }
+    }
+    return (buildHooksLibrariesConfig(libraries));
+}
+
+
+// The goal of this test is to verify the configuration of hooks libraries if
+// none are specified.
+TEST_F(Dhcp4ParserTest, NoHooksLibraries) {
+    // Parse a configuration containing no names.
+    string config = buildHooksLibrariesConfig();
+    if (!executeConfiguration(config,
+                              "set configuration with no hooks libraries")) {
+        FAIL() << "Unable to execute configuration";
+
+    } else {
+        // No libraries should be loaded at the end of the test.
+        std::vector<std::string> libraries = HooksManager::getLibraryNames();
+        EXPECT_TRUE(libraries.empty());
+    }
+}
+
+// Verify parsing fails with one library that will fail validation.
+TEST_F(Dhcp4ParserTest, InvalidLibrary) {
+    // Parse a configuration containing a failing library.
+    string config = buildHooksLibrariesConfig(NOT_PRESENT_LIBRARY);
+
+    ConstElementPtr status;
+    ElementPtr json = Element::fromJSON(config);
+    ASSERT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+
+    // The status object must not be NULL
+    ASSERT_TRUE(status);
+
+    // Returned value should not be 0
+    comment_ = parseAnswer(rcode_, status);
+    EXPECT_NE(0, rcode_);
+}
+
+// Verify the configuration of hooks libraries with two being specified.
+TEST_F(Dhcp4ParserTest, LibrariesSpecified) {
+    // Marker files should not be present.
+    EXPECT_FALSE(checkMarkerFileExists(LOAD_MARKER_FILE));
+    EXPECT_FALSE(checkMarkerFileExists(UNLOAD_MARKER_FILE));
+
+    // Set up the configuration with two libraries and load them.
+    string config = buildHooksLibrariesConfig(CALLOUT_LIBRARY_1,
+                                              CALLOUT_LIBRARY_2);
+    ASSERT_TRUE(executeConfiguration(config,
+                                     "load two valid libraries"));
+
+    // Expect two libraries to be loaded in the correct order (load marker file
+    // is present, no unload marker file).
+    std::vector<std::string> libraries = HooksManager::getLibraryNames();
+    ASSERT_EQ(2, libraries.size());
+    EXPECT_TRUE(checkMarkerFile(LOAD_MARKER_FILE, "12"));
+    EXPECT_FALSE(checkMarkerFileExists(UNLOAD_MARKER_FILE));
+
+    // Unload the libraries.  The load file should not have changed, but
+    // the unload one should indicate the unload() functions have been run.
+    config = buildHooksLibrariesConfig();
+    ASSERT_TRUE(executeConfiguration(config, "unloading libraries"));
+    EXPECT_TRUE(checkMarkerFile(LOAD_MARKER_FILE, "12"));
+    EXPECT_TRUE(checkMarkerFile(UNLOAD_MARKER_FILE, "21"));
+
+    // Expect the hooks system to say that none are loaded.
+    libraries = HooksManager::getLibraryNames();
+    EXPECT_TRUE(libraries.empty());
+
+}
+
 // This test verifies that it is possible to select subset of interfaces
 // This test verifies that it is possible to select subset of interfaces
 // on which server should listen.
 // on which server should listen.
 TEST_F(Dhcp4ParserTest, selectedInterfaces) {
 TEST_F(Dhcp4ParserTest, selectedInterfaces) {

+ 68 - 2
src/bin/dhcp4/tests/ctrl_dhcp4_srv_unittest.cc

@@ -17,6 +17,10 @@
 #include <config/ccsession.h>
 #include <config/ccsession.h>
 #include <dhcp/dhcp4.h>
 #include <dhcp/dhcp4.h>
 #include <dhcp4/ctrl_dhcp4_srv.h>
 #include <dhcp4/ctrl_dhcp4_srv.h>
+#include <hooks/hooks_manager.h>
+
+#include "marker_file.h"
+#include "test_libraries.h"
 
 
 #include <boost/scoped_ptr.hpp>
 #include <boost/scoped_ptr.hpp>
 #include <gtest/gtest.h>
 #include <gtest/gtest.h>
@@ -26,13 +30,16 @@
 #include <sstream>
 #include <sstream>
 
 
 #include <arpa/inet.h>
 #include <arpa/inet.h>
+#include <unistd.h>
 
 
 using namespace std;
 using namespace std;
 using namespace isc;
 using namespace isc;
-using namespace isc::dhcp;
 using namespace isc::asiolink;
 using namespace isc::asiolink;
-using namespace isc::data;
 using namespace isc::config;
 using namespace isc::config;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+using namespace isc::hooks;
 
 
 namespace {
 namespace {
 
 
@@ -45,10 +52,25 @@ public:
 class CtrlDhcpv4SrvTest : public ::testing::Test {
 class CtrlDhcpv4SrvTest : public ::testing::Test {
 public:
 public:
     CtrlDhcpv4SrvTest() {
     CtrlDhcpv4SrvTest() {
+        reset();
     }
     }
 
 
     ~CtrlDhcpv4SrvTest() {
     ~CtrlDhcpv4SrvTest() {
+        reset();
     };
     };
+
+    /// @brief Reset hooks data
+    ///
+    /// Resets the data for the hooks-related portion of the test by ensuring
+    /// that no libraries are loaded and that any marker files are deleted.
+    void reset() {
+        // Unload any previously-loaded libraries.
+        HooksManager::unloadLibraries();
+
+        // Get rid of any marker files.
+        static_cast<void>(unlink(LOAD_MARKER_FILE));
+        static_cast<void>(unlink(UNLOAD_MARKER_FILE));
+    }
 };
 };
 
 
 TEST_F(CtrlDhcpv4SrvTest, commands) {
 TEST_F(CtrlDhcpv4SrvTest, commands) {
@@ -82,4 +104,48 @@ TEST_F(CtrlDhcpv4SrvTest, commands) {
     EXPECT_EQ(0, rcode); // expect success
     EXPECT_EQ(0, rcode); // expect success
 }
 }
 
 
+// Check that the "libreload" command will reload libraries
+
+TEST_F(CtrlDhcpv4SrvTest, libreload) {
+    // Ensure no marker files to start with.
+    ASSERT_FALSE(checkMarkerFileExists(LOAD_MARKER_FILE));
+    ASSERT_FALSE(checkMarkerFileExists(UNLOAD_MARKER_FILE));
+
+    // Load two libraries
+    std::vector<std::string> libraries;
+    libraries.push_back(CALLOUT_LIBRARY_1);
+    libraries.push_back(CALLOUT_LIBRARY_2);
+    HooksManager::loadLibraries(libraries);
+
+    // Check they are loaded.
+    std::vector<std::string> loaded_libraries =
+        HooksManager::getLibraryNames();
+    ASSERT_TRUE(libraries == loaded_libraries);
+
+    // ... which also included checking that the marker file created by the
+    // load functions exists and holds the correct value (of "12" - the
+    // first library appends "1" to the file, the second appends "2"). Also
+    // check that the unload marker file does not yet exist.
+    EXPECT_TRUE(checkMarkerFile(LOAD_MARKER_FILE, "12"));
+    EXPECT_FALSE(checkMarkerFileExists(UNLOAD_MARKER_FILE));
+
+    // Now execute the "libreload" command.  This should cause the libraries
+    // to unload and to reload.
+
+    // Use empty parameters list
+    ElementPtr params(new isc::data::MapElement());
+    int rcode = -1;
+
+    ConstElementPtr result =
+        ControlledDhcpv4Srv::execDhcpv4ServerCommand("libreload", params);
+    ConstElementPtr comment = parseAnswer(rcode, result);
+    EXPECT_EQ(0, rcode); // Expect success
+
+    // Check that the libraries have unloaded and reloaded.  The libraries are
+    // unloaded in the reverse order to which they are loaded.  When they load,
+    // they should append information to the loading marker file.
+    EXPECT_TRUE(checkMarkerFile(UNLOAD_MARKER_FILE, "21"));
+    EXPECT_TRUE(checkMarkerFile(LOAD_MARKER_FILE, "1212"));
+}
+
 } // End of anonymous namespace
 } // End of anonymous namespace

+ 66 - 0
src/bin/dhcp4/tests/marker_file.cc

@@ -0,0 +1,66 @@
+// Copyright (C) 2013  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include "marker_file.h"
+
+#include <gtest/gtest.h>
+
+#include <fstream>
+#include <string>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+using namespace std;
+
+// Check the marker file.
+
+bool
+checkMarkerFile(const char* name, const char* expected) {
+    // Open the file for input
+    fstream file(name, fstream::in);
+
+    // Is it open?
+    if (!file.is_open()) {
+        ADD_FAILURE() << "Unable to open " << name << ". It was expected "
+                      << "to be present and to contain the string '"
+                      << expected << "'";
+        return (false);
+    }
+
+    // OK, is open, so read the data and see what we have.  Compare it
+    // against what is expected.
+    string content;
+    getline(file, content);
+
+    string expected_str(expected);
+    EXPECT_EQ(expected_str, content) << "Marker file " << name
+        << "did not contain the expected data";
+    file.close();
+
+    return (expected_str == content);
+}
+
+// Check if the marker file exists - this is a wrapper for "access(2)" and
+// really tests if the file exists and is accessible
+
+bool
+checkMarkerFileExists(const char* name) {
+    return (access(name, F_OK) == 0);
+}
+
+} // namespace test
+} // namespace dhcp
+} // namespace isc

+ 69 - 0
src/bin/dhcp4/tests/marker_file.h.in

@@ -0,0 +1,69 @@
+// Copyright (C) 2013  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#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* const LOAD_MARKER_FILE = "@abs_builddir@/load_marker.txt";
+const char* const UNLOAD_MARKER_FILE = "@abs_builddir@/unload_marker.txt";
+}
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+/// @brief Check marker file
+///
+/// This function is used in some of the DHCP server tests.
+///
+/// Marker files are used by the load/unload functions in the hooks
+/// libraries in these tests to signal whether they have been loaded or
+/// unloaded.  The file (if present) contains a single line holding
+/// a set of characters.
+///
+/// This convenience function checks the file to see if the characters
+/// are those expected.
+///
+/// @param name Name of the marker file.
+/// @param expected Characters expected.  If a marker file is present,
+///        it is expected to contain characters.
+///
+/// @return true if all tests pass, false if not (in which case a failure
+///         will have been logged).
+bool
+checkMarkerFile(const char* name, const char* expected);
+
+/// @brief Check marker file exists
+///
+/// This function is used in some of the DHCP server tests.
+///
+/// Checkes that the specified file does NOT exist.
+///
+/// @param name Name of the marker file.
+///
+/// @return true if file exists, false if not.
+bool
+checkMarkerFileExists(const char* name);
+
+} // namespace test
+} // namespace dhcp
+} // namespace isc
+
+#endif // MARKER_FILE_H
+

+ 51 - 0
src/bin/dhcp4/tests/test_libraries.h.in

@@ -0,0 +1,51 @@
+// Copyright (C) 2013  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef TEST_LIBRARIES_H
+#define TEST_LIBRARIES_H
+
+#include <config.h>
+
+namespace {
+
+
+// Take care of differences in DLL naming between operating systems.
+
+#ifdef OS_OSX
+#define DLL_SUFFIX ".dylib"
+
+#else
+#define DLL_SUFFIX ".so"
+
+#endif
+
+
+// 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
+// shared library.
+
+// Library with load/unload functions creating marker files to check their
+// operation.
+const char* const CALLOUT_LIBRARY_1 = "@abs_builddir@/.libs/libco1"
+                                           DLL_SUFFIX;
+const char* const CALLOUT_LIBRARY_2 = "@abs_builddir@/.libs/libco2"
+                                           DLL_SUFFIX;
+
+// Name of a library which is not present.
+const char* const NOT_PRESENT_LIBRARY = "@abs_builddir@/.libs/libnothere"
+                                         DLL_SUFFIX;
+} // anonymous namespace
+
+
+#endif // TEST_LIBRARIES_H

+ 21 - 0
src/bin/dhcp6/config_parser.cc

@@ -430,6 +430,8 @@ DhcpConfigParser* createGlobal6DhcpConfigParser(const std::string& config_id) {
                                    globalContext()->string_values_);
                                    globalContext()->string_values_);
     } else if (config_id.compare("lease-database") == 0) {
     } else if (config_id.compare("lease-database") == 0) {
         parser = new DbAccessParser(config_id);
         parser = new DbAccessParser(config_id);
+    } else if (config_id.compare("hooks-libraries") == 0) {
+        parser = new HooksLibrariesParser(config_id);
     } else {
     } else {
         isc_throw(NotImplemented,
         isc_throw(NotImplemented,
                 "Parser error: Global configuration parameter not supported: "
                 "Parser error: Global configuration parameter not supported: "
@@ -465,6 +467,11 @@ configureDhcp6Server(Dhcpv6Srv&, isc::data::ConstElementPtr config_set) {
     ParserPtr option_parser;
     ParserPtr option_parser;
     ParserPtr iface_parser;
     ParserPtr iface_parser;
 
 
+    // Some of the parsers alter state of the system that can't easily
+    // be undone. (Or alter it in a way such that undoing the change
+    // has the same risk of failure as doing the change.)
+    ParserPtr hooks_parser;
+
     // The subnet parsers implement data inheritance by directly
     // The subnet parsers implement data inheritance by directly
     // accessing global storage. For this reason the global data
     // accessing global storage. For this reason the global data
     // parsers must store the parsed data into global storages
     // parsers must store the parsed data into global storages
@@ -496,6 +503,13 @@ configureDhcp6Server(Dhcpv6Srv&, isc::data::ConstElementPtr config_set) {
                 subnet_parser = parser;
                 subnet_parser = parser;
             } else if (config_pair.first == "option-data") {
             } else if (config_pair.first == "option-data") {
                 option_parser = parser;
                 option_parser = parser;
+            } else if (config_pair.first == "hooks-libraries") {
+                // Executing the commit will alter currently loaded hooks
+                // libraries. Check if the supplied libraries are valid,
+                // but defer the commit until after everything else has
+                // committed.
+                hooks_parser = parser;
+                hooks_parser->build(config_pair.second);
             } else if (config_pair.first == "interfaces") {
             } else if (config_pair.first == "interfaces") {
                 // The interface parser is independent from any other parser and
                 // The interface parser is independent from any other parser and
                 // can be run here before other parsers.
                 // can be run here before other parsers.
@@ -558,6 +572,13 @@ configureDhcp6Server(Dhcpv6Srv&, isc::data::ConstElementPtr config_set) {
             if (iface_parser) {
             if (iface_parser) {
                 iface_parser->commit();
                 iface_parser->commit();
             }
             }
+
+            // This occurs last as if it succeeds, there is no easy way to
+            // revert it.  As a result, the failure to commit a subsequent
+            // change causes problems when trying to roll back.
+            if (hooks_parser) {
+                hooks_parser->commit();
+            }
         }
         }
         catch (const isc::Exception& ex) {
         catch (const isc::Exception& ex) {
             LOG_ERROR(dhcp6_logger, DHCP6_PARSER_COMMIT_FAIL).arg(ex.what());
             LOG_ERROR(dhcp6_logger, DHCP6_PARSER_COMMIT_FAIL).arg(ex.what());

+ 19 - 0
src/bin/dhcp6/ctrl_dhcp6_srv.cc

@@ -26,16 +26,20 @@
 #include <dhcp6/dhcp6_log.h>
 #include <dhcp6/dhcp6_log.h>
 #include <dhcp6/spec_config.h>
 #include <dhcp6/spec_config.h>
 #include <exceptions/exceptions.h>
 #include <exceptions/exceptions.h>
+#include <hooks/hooks_manager.h>
 #include <util/buffer.h>
 #include <util/buffer.h>
 
 
 #include <cassert>
 #include <cassert>
 #include <iostream>
 #include <iostream>
+#include <string>
+#include <vector>
 
 
 using namespace isc::asiolink;
 using namespace isc::asiolink;
 using namespace isc::cc;
 using namespace isc::cc;
 using namespace isc::config;
 using namespace isc::config;
 using namespace isc::data;
 using namespace isc::data;
 using namespace isc::dhcp;
 using namespace isc::dhcp;
+using namespace isc::hooks;
 using namespace isc::log;
 using namespace isc::log;
 using namespace isc::util;
 using namespace isc::util;
 using namespace std;
 using namespace std;
@@ -141,6 +145,21 @@ ControlledDhcpv6Srv::dhcp6CommandHandler(const string& command, ConstElementPtr
         ConstElementPtr answer = isc::config::createAnswer(0,
         ConstElementPtr answer = isc::config::createAnswer(0,
                                  "Shutting down.");
                                  "Shutting down.");
         return (answer);
         return (answer);
+
+    } else if (command == "libreload") {
+        // TODO delete any stored CalloutHandles referring to the old libraries
+        // Get list of currently loaded libraries and reload them.
+        vector<string> loaded = HooksManager::getLibraryNames();
+        bool status = HooksManager::loadLibraries(loaded);
+        if (!status) {
+            LOG_ERROR(dhcp6_logger, DHCP6_HOOKS_LIBS_RELOAD_FAIL);
+            ConstElementPtr answer = isc::config::createAnswer(1,
+                                     "Failed to reload hooks libraries.");
+            return (answer);
+        }
+        ConstElementPtr answer = isc::config::createAnswer(0,
+                                 "Hooks libraries successfully reloaded.");
+        return (answer);
     }
     }
 
 
     ConstElementPtr answer = isc::config::createAnswer(1,
     ConstElementPtr answer = isc::config::createAnswer(1,

+ 20 - 0
src/bin/dhcp6/dhcp6.spec

@@ -3,6 +3,20 @@
     "module_name": "Dhcp6",
     "module_name": "Dhcp6",
     "module_description": "DHCPv6 server daemon",
     "module_description": "DHCPv6 server daemon",
     "config_data": [
     "config_data": [
+      {
+        "item_name": "hooks-libraries",
+        "item_type": "list",
+        "item_optional": true,
+        "item_default": [],
+        "list_item_spec":
+        {
+          "item_name": "hooks-library",
+          "item_type": "string",
+          "item_optional": false,
+          "item_default": ""
+        }
+      },
+ 
       { "item_name": "interfaces",
       { "item_name": "interfaces",
         "item_type": "list",
         "item_type": "list",
         "item_optional": false,
         "item_optional": false,
@@ -295,6 +309,12 @@
                     "item_optional": true
                     "item_optional": true
                 }
                 }
             ]
             ]
+        },
+
+        {
+            "command_name": "libreload",
+            "command_description": "Reloads the current hooks libraries.", 
+            "command_args": []
         }
         }
     ]
     ]
   }
   }

+ 5 - 0
src/bin/dhcp6/dhcp6_messages.mes

@@ -124,6 +124,11 @@ subnet, an action that severely limits further processing; the server
 will be only able to offer global options - no addresses or prefixes
 will be only able to offer global options - no addresses or prefixes
 will be assigned.
 will be assigned.
 
 
+% DHCP6_HOOKS_LIBS_RELOAD_FAIL reload of hooks libraries failed
+A "libreload" command was issued to reload the hooks libraries but for
+some reason the reload failed.  Other error messages issued from the
+hooks framework will indicate the nature of the problem.
+
 % DHCP6_LEASE_ADVERT lease %1 advertised (client duid=%2, iaid=%3)
 % DHCP6_LEASE_ADVERT lease %1 advertised (client duid=%2, iaid=%3)
 This debug message indicates that the server successfully advertised
 This debug message indicates that the server successfully advertised
 a lease. It is up to the client to choose one server out of the
 a lease. It is up to the client to choose one server out of the

+ 17 - 4
src/bin/dhcp6/tests/Makefile.am

@@ -27,7 +27,8 @@ AM_CPPFLAGS += -I$(top_srcdir)/src/bin
 AM_CPPFLAGS += $(BOOST_INCLUDES)
 AM_CPPFLAGS += $(BOOST_INCLUDES)
 AM_CPPFLAGS += -DINSTALL_PROG=\"$(abs_top_srcdir)/install-sh\"
 AM_CPPFLAGS += -DINSTALL_PROG=\"$(abs_top_srcdir)/install-sh\"
 
 
-CLEANFILES = $(builddir)/interfaces.txt $(builddir)/logger_lockfile
+CLEANFILES  = $(builddir)/interfaces.txt $(builddir)/logger_lockfile
+CLEANFILES += $(builddir)/load_marker.txt $(builddir)/unload_marker.txt
 
 
 AM_CXXFLAGS = $(B10_CXXFLAGS)
 AM_CXXFLAGS = $(B10_CXXFLAGS)
 if USE_CLANGPP
 if USE_CLANGPP
@@ -44,20 +45,32 @@ TESTS_ENVIRONMENT = \
 
 
 TESTS =
 TESTS =
 if HAVE_GTEST
 if HAVE_GTEST
+# Build shared libraries for testing.
+lib_LTLIBRARIES = libco1.la libco2.la
 
 
-TESTS += dhcp6_unittests
+libco1_la_SOURCES  = callout_library_1.cc callout_library_common.h
+libco1_la_CXXFLAGS = $(AM_CXXFLAGS)
+libco1_la_CPPFLAGS = $(AM_CPPFLAGS)
+
+libco2_la_SOURCES  = callout_library_2.cc callout_library_common.h
+libco2_la_CXXFLAGS = $(AM_CXXFLAGS)
+libco2_la_CPPFLAGS = $(AM_CPPFLAGS)
 
 
+TESTS += dhcp6_unittests
 dhcp6_unittests_SOURCES  = dhcp6_unittests.cc
 dhcp6_unittests_SOURCES  = dhcp6_unittests.cc
 dhcp6_unittests_SOURCES += dhcp6_srv_unittest.cc
 dhcp6_unittests_SOURCES += dhcp6_srv_unittest.cc
 dhcp6_unittests_SOURCES += hooks_unittest.cc
 dhcp6_unittests_SOURCES += hooks_unittest.cc
 dhcp6_unittests_SOURCES += dhcp6_test_utils.h
 dhcp6_unittests_SOURCES += dhcp6_test_utils.h
 dhcp6_unittests_SOURCES += ctrl_dhcp6_srv_unittest.cc
 dhcp6_unittests_SOURCES += ctrl_dhcp6_srv_unittest.cc
 dhcp6_unittests_SOURCES += config_parser_unittest.cc
 dhcp6_unittests_SOURCES += config_parser_unittest.cc
+dhcp6_unittests_SOURCES += marker_file.cc
 dhcp6_unittests_SOURCES += ../dhcp6_srv.h ../dhcp6_srv.cc
 dhcp6_unittests_SOURCES += ../dhcp6_srv.h ../dhcp6_srv.cc
 dhcp6_unittests_SOURCES += ../dhcp6_log.h ../dhcp6_log.cc
 dhcp6_unittests_SOURCES += ../dhcp6_log.h ../dhcp6_log.cc
 dhcp6_unittests_SOURCES += ../ctrl_dhcp6_srv.cc
 dhcp6_unittests_SOURCES += ../ctrl_dhcp6_srv.cc
 dhcp6_unittests_SOURCES += ../config_parser.cc ../config_parser.h
 dhcp6_unittests_SOURCES += ../config_parser.cc ../config_parser.h
-nodist_dhcp6_unittests_SOURCES = ../dhcp6_messages.h ../dhcp6_messages.cc
+
+nodist_dhcp6_unittests_SOURCES  = ../dhcp6_messages.h ../dhcp6_messages.cc
+nodist_dhcp6_unittests_SOURCES += marker_file.h test_libraries.h
 
 
 dhcp6_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
 dhcp6_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
 dhcp6_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
 dhcp6_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
@@ -67,10 +80,10 @@ dhcp6_unittests_LDADD += $(top_builddir)/src/lib/cc/libb10-cc.la
 dhcp6_unittests_LDADD += $(top_builddir)/src/lib/config/libb10-cfgclient.la
 dhcp6_unittests_LDADD += $(top_builddir)/src/lib/config/libb10-cfgclient.la
 dhcp6_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la
 dhcp6_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la
 dhcp6_unittests_LDADD += $(top_builddir)/src/lib/dhcpsrv/libb10-dhcpsrv.la
 dhcp6_unittests_LDADD += $(top_builddir)/src/lib/dhcpsrv/libb10-dhcpsrv.la
+dhcp6_unittests_LDADD += $(top_builddir)/src/lib/hooks/libb10-hooks.la
 dhcp6_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
 dhcp6_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
 dhcp6_unittests_LDADD += $(top_builddir)/src/lib/log/libb10-log.la
 dhcp6_unittests_LDADD += $(top_builddir)/src/lib/log/libb10-log.la
 dhcp6_unittests_LDADD += $(top_builddir)/src/lib/util/libb10-util.la
 dhcp6_unittests_LDADD += $(top_builddir)/src/lib/util/libb10-util.la
-dhcp6_unittests_LDADD += $(top_builddir)/src/lib/hooks/libb10-hooks.la
 endif
 endif
 
 
 noinst_PROGRAMS = $(TESTS)
 noinst_PROGRAMS = $(TESTS)

+ 22 - 0
src/bin/dhcp6/tests/callout_library_1.cc

@@ -0,0 +1,22 @@
+// Copyright (C) 2013  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+/// @file
+/// @brief Marker file callout library
+///
+/// This is the source of a test library for the DHCP parser and configuration
+/// tests.  See callout_common.cc for details.
+
+static const int LIBRARY_NUMBER = 1;
+#include "callout_library_common.h"

+ 22 - 0
src/bin/dhcp6/tests/callout_library_2.cc

@@ -0,0 +1,22 @@
+// Copyright (C) 2013  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+/// @file
+/// @brief Marker file callout library
+///
+/// This is the source of a test library for the DHCP parser and configuration
+/// tests.  See callout_common.cc for details.
+
+static const int LIBRARY_NUMBER = 2;
+#include "callout_library_common.h"

+ 82 - 0
src/bin/dhcp6/tests/callout_library_common.h

@@ -0,0 +1,82 @@
+// Copyright (C) 2013  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+/// @file
+/// @brief Marker file callout library
+///
+/// This is the source of a test library for the DHCP parser and configuration
+/// tests.
+///
+/// To check that they libraries are loaded and unloaded correctly, the load
+/// and unload functions in this library maintain two marker files - the load
+/// marker file and the unload marker file.  The functions append a single
+/// line to the file, creating the file if need be.  In this way, the test code
+/// can determine whether the load/unload functions have been run and, if so,
+/// in what order.
+///
+/// This file is the common library file for the tests.  It will not compile
+/// by itself - it is included into each callout library which specifies the
+/// missing constant LIBRARY_NUMBER before the inclusion.
+
+#include <hooks/hooks.h>
+#include "marker_file.h"
+
+#include <fstream>
+
+using namespace isc::hooks;
+using namespace std;
+
+extern "C" {
+
+/// @brief Append digit to marker file
+///
+/// If the marker file does not exist, create it.  Then append the single
+/// digit (given by the constant LIBRARY_NUMBER) defined earlier to it and
+/// close the file.
+///
+/// @param name Name of the file to open
+///
+/// @return 0 on success, non-zero on error.
+int
+appendDigit(const char* name) {
+    // Open the file and check if successful.
+    fstream file(name, fstream::out | fstream::app);
+    if (!file.good()) {
+        return (1);
+    }
+
+    // Add the library number to it and close.
+    file << LIBRARY_NUMBER;
+    file.close();
+
+    return (0);
+}
+
+// Framework functions
+int
+version() {
+    return (BIND10_HOOKS_VERSION);
+}
+
+int
+load(LibraryHandle&) {
+    return (appendDigit(LOAD_MARKER_FILE));
+}
+
+int
+unload() {
+    return (appendDigit(UNLOAD_MARKER_FILE));
+}
+
+};

+ 233 - 38
src/bin/dhcp6/tests/config_parser_unittest.cc

@@ -24,22 +24,32 @@
 #include <dhcp6/dhcp6_srv.h>
 #include <dhcp6/dhcp6_srv.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/subnet.h>
 #include <dhcpsrv/subnet.h>
+#include <hooks/hooks_manager.h>
+
+#include "test_libraries.h"
+#include "marker_file.h"
 
 
 #include <boost/foreach.hpp>
 #include <boost/foreach.hpp>
 #include <gtest/gtest.h>
 #include <gtest/gtest.h>
 
 
 #include <fstream>
 #include <fstream>
 #include <iostream>
 #include <iostream>
+#include <fstream>
 #include <sstream>
 #include <sstream>
+#include <string>
+#include <vector>
 
 
 #include <arpa/inet.h>
 #include <arpa/inet.h>
+#include <unistd.h>
 
 
-using namespace std;
 using namespace isc;
 using namespace isc;
-using namespace isc::dhcp;
 using namespace isc::asiolink;
 using namespace isc::asiolink;
-using namespace isc::data;
 using namespace isc::config;
 using namespace isc::config;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+using namespace isc::hooks;
+using namespace std;
 
 
 namespace {
 namespace {
 
 
@@ -71,7 +81,22 @@ public:
         resetConfiguration();
         resetConfiguration();
     }
     }
 
 
+    // Check that no hooks libraries are loaded.  This is a pre-condition for
+    // a number of tests, so is checked in one place.  As this uses an
+    // ASSERT call - and it is not clear from the documentation that Gtest
+    // predicates can be used in a constructor - the check is placed in SetUp.
+    void SetUp() {
+        std::vector<std::string> libraries = HooksManager::getLibraryNames();
+        ASSERT_TRUE(libraries.empty());
+    }
+
     ~Dhcp6ParserTest() {
     ~Dhcp6ParserTest() {
+        // Reset configuration database after each test.
+        resetConfiguration();
+
+        // ... and delete the hooks library marker files if present
+        unlink(LOAD_MARKER_FILE);
+        unlink(UNLOAD_MARKER_FILE);
     };
     };
 
 
     // Checks if config_result (result of DHCP server configuration) has
     // Checks if config_result (result of DHCP server configuration) has
@@ -170,51 +195,75 @@ public:
         return (stream.str());
         return (stream.str());
     }
     }
 
 
-    /// @brief Reset configuration database.
+    /// @brief Parse and Execute configuration
     ///
     ///
-    /// This function resets configuration data base by
-    /// removing all subnets and option-data. Reset must
-    /// be performed before each test to make sure that
-    /// contents of the database do not affect result of
-    /// the test being executed.
-    void resetConfiguration() {
+    /// Parses a configuration and executes a configuration of the server.
+    /// If the operation fails, the current test will register a failure.
+    ///
+    /// @param config Configuration to parse
+    /// @param operation Operation being performed.  In the case of an error,
+    ///        the error text will include the string "unable to <operation>.".
+    ///
+    /// @return true if the configuration succeeded, false if not.  In the
+    ///         latter case, a failure will have been added to the current test.
+    bool
+    executeConfiguration(const std::string& config, const char* operation) {
         ConstElementPtr status;
         ConstElementPtr status;
-
-        string config = "{ \"interfaces\": [ \"*\" ],"
-            "\"preferred-lifetime\": 3000,"
-            "\"rebind-timer\": 2000, "
-            "\"renew-timer\": 1000, "
-            "\"valid-lifetime\": 4000, "
-            "\"subnet6\": [ ], "
-            "\"option-def\": [ ], "
-            "\"option-data\": [ ] }";
-
         try {
         try {
             ElementPtr json = Element::fromJSON(config);
             ElementPtr json = Element::fromJSON(config);
             status = configureDhcp6Server(srv_, json);
             status = configureDhcp6Server(srv_, json);
         } catch (const std::exception& ex) {
         } catch (const std::exception& ex) {
-            FAIL() << "Fatal error: unable to reset configuration database"
-                   << " after the test. The following configuration was used"
-                   << " to reset database: " << std::endl
+            ADD_FAILURE() << "Unable to " << operation << ". "
+                   << "The following configuration was used: " << std::endl
                    << config << std::endl
                    << config << std::endl
                    << " and the following error message was returned:"
                    << " and the following error message was returned:"
                    << ex.what() << std::endl;
                    << ex.what() << std::endl;
+            return (false);
         }
         }
 
 
-        // status object must not be NULL
+        // The status object must not be NULL
         if (!status) {
         if (!status) {
-            FAIL() << "Fatal error: unable to reset configuration database"
-                   << " after the test. Configuration function returned"
-                   << " NULL pointer" << std::endl;
+            ADD_FAILURE() << "Unable to " << operation << ". "
+                   << "The configuration function returned a null pointer.";
+            return (false);
         }
         }
+
+        // Store the answer if we need it.
+
+        // Returned value should be 0 (configuration success)
         comment_ = parseAnswer(rcode_, status);
         comment_ = parseAnswer(rcode_, status);
-        // returned value should be 0 (configuration success)
         if (rcode_ != 0) {
         if (rcode_ != 0) {
-            FAIL() << "Fatal error: unable to reset configuration database"
-                   << " after the test. Configuration function returned"
-                   << " error code " << rcode_ << std::endl;
+            string reason = "";
+            if (comment_) {
+                reason = string(" (") + comment_->stringValue() + string(")");
+            }
+            ADD_FAILURE() << "Unable to " << operation << ". "
+                   << "The configuration function returned error code "
+                   << rcode_ << reason;
+            return (false);
         }
         }
 
 
+        return (true);
+    }
+
+    /// @brief Reset configuration database.
+    ///
+    /// This function resets configuration data base by removing all subnets
+    /// option-data, and hooks libraries. The reset must be performed after each
+    /// test to make sure that contents of the database do not affect the
+    /// results of subsequent tests.
+    void resetConfiguration() {
+        string config = "{ \"interfaces\": [ \"all\" ],"
+            "\"hooks-libraries\": [ ],"
+            "\"preferred-lifetime\": 3000,"
+            "\"rebind-timer\": 2000, "
+            "\"renew-timer\": 1000, "
+            "\"valid-lifetime\": 4000, "
+            "\"subnet6\": [ ], "
+            "\"option-def\": [ ], "
+            "\"option-data\": [ ] }";
+        static_cast<void>(executeConfiguration(config,
+                                               "reset configuration database"));
         // The default setting is to listen on all interfaces. In order to
         // The default setting is to listen on all interfaces. In order to
         // properly test interface configuration we disable listening on
         // properly test interface configuration we disable listening on
         // all interfaces before each test and later check that this setting
         // all interfaces before each test and later check that this setting
@@ -284,13 +333,11 @@ public:
                             expected_data_len));
                             expected_data_len));
     }
     }
 
 
-    int rcode_; ///< return core (see @ref isc::config::parseAnswer)
-    Dhcpv6Srv srv_; ///< instance of the Dhcp6Srv used during tests
-
-    ConstElementPtr comment_; ///< comment (see @ref isc::config::parseAnswer)
-
-    string valid_iface_; ///< name of a valid network interface (present in system)
-    string bogus_iface_; ///< name of a invalid network interface (not present in system)
+    int rcode_;          ///< Return code (see @ref isc::config::parseAnswer)
+    Dhcpv6Srv srv_;      ///< Instance of the Dhcp6Srv used during tests
+    ConstElementPtr comment_; ///< Comment (see @ref isc::config::parseAnswer)
+    string valid_iface_; ///< Valid network interface name (present in system)
+    string bogus_iface_; ///< invalid network interface name (not in system)
 };
 };
 
 
 // Goal of this test is a verification if a very simple config update
 // Goal of this test is a verification if a very simple config update
@@ -1857,6 +1904,154 @@ TEST_F(Dhcp6ParserTest, stdOptionDataEncapsulate) {
     EXPECT_FALSE(desc.option->getOption(112));
     EXPECT_FALSE(desc.option->getOption(112));
 }
 }
 
 
+// Tests of the hooks libraries configuration.  All tests have the pre-
+// condition (checked in the test fixture's SetUp() method) that no hooks
+// libraries are loaded at the start of the tests.
+
+
+// Helper function to return a configuration containing an arbitrary number
+// of hooks libraries.
+std::string
+buildHooksLibrariesConfig(const std::vector<std::string>& libraries) {
+    const string quote("\"");
+
+    // Create the first part of the configuration string.
+    string config =
+        "{ \"interfaces\": [ \"all\" ],"
+            "\"hooks-libraries\": [";
+
+    // Append the libraries (separated by commas if needed)
+    for (int i = 0; i < libraries.size(); ++i) {
+        if (i > 0) {
+            config += string(", ");
+        }
+        config += (quote + libraries[i] + quote);
+    }
+
+    // Append the remainder of the configuration.
+    config += string(
+        "],"
+        "\"rebind-timer\": 2000,"
+        "\"renew-timer\": 1000,"
+        "\"option-data\": [ {"
+        "    \"name\": \"foo\","
+        "    \"space\": \"vendor-opts-space\","
+        "    \"code\": 110,"
+        "    \"data\": \"1234\","
+        "    \"csv-format\": True"
+        " },"
+        " {"
+        "    \"name\": \"foo2\","
+        "    \"space\": \"vendor-opts-space\","
+        "    \"code\": 111,"
+        "    \"data\": \"192.168.2.1\","
+        "    \"csv-format\": True"
+        " } ],"
+        "\"option-def\": [ {"
+        "    \"name\": \"foo\","
+        "    \"code\": 110,"
+        "    \"type\": \"uint32\","
+        "    \"array\": False,"
+        "    \"record-types\": \"\","
+        "    \"space\": \"vendor-opts-space\","
+        "    \"encapsulate\": \"\""
+        " },"
+        " {"
+        "    \"name\": \"foo2\","
+        "    \"code\": 111,"
+        "    \"type\": \"ipv4-address\","
+        "    \"array\": False,"
+        "    \"record-types\": \"\","
+        "    \"space\": \"vendor-opts-space\","
+        "    \"encapsulate\": \"\""
+        " } ]"
+        "}");
+
+    return (config);
+}
+
+// Convenience function for creating hooks library configuration with one or
+// two character string constants.
+std::string
+buildHooksLibrariesConfig(const char* library1 = NULL,
+                          const char* library2 = NULL) {
+    std::vector<std::string> libraries;
+    if (library1 != NULL) {
+        libraries.push_back(string(library1));
+        if (library2 != NULL) {
+            libraries.push_back(string(library2));
+        }
+    }
+    return (buildHooksLibrariesConfig(libraries));
+}
+
+
+// The goal of this test is to verify the configuration of hooks libraries if
+// none are specified.
+TEST_F(Dhcp6ParserTest, NoHooksLibraries) {
+    // Parse a configuration containing no names.
+    string config = buildHooksLibrariesConfig();
+    if (!executeConfiguration(config,
+                              "set configuration with no hooks libraries")) {
+        FAIL() << "Unable to execute configuration";
+
+    } else {
+        // No libraries should be loaded at the end of the test.
+        std::vector<std::string> libraries = HooksManager::getLibraryNames();
+        EXPECT_TRUE(libraries.empty());
+    }
+}
+
+// Verify parsing fails with one library that will fail validation.
+TEST_F(Dhcp6ParserTest, InvalidLibrary) {
+    // Parse a configuration containing a failing library.
+    string config = buildHooksLibrariesConfig(NOT_PRESENT_LIBRARY);
+
+    ConstElementPtr status;
+    ElementPtr json = Element::fromJSON(config);
+    ASSERT_NO_THROW(status = configureDhcp6Server(srv_, json));
+
+    // The status object must not be NULL
+    ASSERT_TRUE(status);
+
+    // Returned value should not be 0
+    comment_ = parseAnswer(rcode_, status);
+    EXPECT_NE(0, rcode_);
+}
+
+// Verify the configuration of hooks libraries with two being specified.
+TEST_F(Dhcp6ParserTest, LibrariesSpecified) {
+    // Marker files should not be present.
+    EXPECT_FALSE(checkMarkerFileExists(LOAD_MARKER_FILE));
+    EXPECT_FALSE(checkMarkerFileExists(UNLOAD_MARKER_FILE));
+
+    // Set up the configuration with two libraries and load them.
+    string config = buildHooksLibrariesConfig(CALLOUT_LIBRARY_1,
+                                              CALLOUT_LIBRARY_2);
+    ASSERT_TRUE(executeConfiguration(config,
+                                     "load two valid libraries"));
+
+    // Expect two libraries to be loaded in the correct order (load marker file
+    // is present, no unload marker file).
+    std::vector<std::string> libraries = HooksManager::getLibraryNames();
+    ASSERT_EQ(2, libraries.size());
+    EXPECT_TRUE(checkMarkerFile(LOAD_MARKER_FILE, "12"));
+    EXPECT_FALSE(checkMarkerFileExists(UNLOAD_MARKER_FILE));
+
+    // Unload the libraries.  The load file should not have changed, but
+    // the unload one should indicate the unload() functions have been run.
+    config = buildHooksLibrariesConfig();
+    ASSERT_TRUE(executeConfiguration(config, "unloading libraries"));
+    EXPECT_TRUE(checkMarkerFile(LOAD_MARKER_FILE, "12"));
+    EXPECT_TRUE(checkMarkerFile(UNLOAD_MARKER_FILE, "21"));
+
+    // Expect the hooks system to say that none are loaded.
+    libraries = HooksManager::getLibraryNames();
+    EXPECT_TRUE(libraries.empty());
+
+}
+
+
 // This test verifies that it is possible to select subset of interfaces on
 // This test verifies that it is possible to select subset of interfaces on
 // which server should listen.
 // which server should listen.
 TEST_F(Dhcp6ParserTest, selectedInterfaces) {
 TEST_F(Dhcp6ParserTest, selectedInterfaces) {

+ 75 - 9
src/bin/dhcp6/tests/ctrl_dhcp6_srv_unittest.cc

@@ -14,30 +14,37 @@
 
 
 #include <config.h>
 #include <config.h>
 
 
+#include <config/ccsession.h>
 #include <dhcp/dhcp6.h>
 #include <dhcp/dhcp6.h>
 #include <dhcp6/ctrl_dhcp6_srv.h>
 #include <dhcp6/ctrl_dhcp6_srv.h>
-#include <config/ccsession.h>
+#include <hooks/hooks_manager.h>
+
+#include "marker_file.h"
+#include "test_libraries.h"
 
 
 #include <boost/scoped_ptr.hpp>
 #include <boost/scoped_ptr.hpp>
 #include <gtest/gtest.h>
 #include <gtest/gtest.h>
 
 
-#include <iostream>
 #include <fstream>
 #include <fstream>
+#include <iostream>
 #include <sstream>
 #include <sstream>
 
 
 #include <arpa/inet.h>
 #include <arpa/inet.h>
+#include <unistd.h>
 
 
 using namespace std;
 using namespace std;
 using namespace isc;
 using namespace isc;
-using namespace isc::dhcp;
 using namespace isc::asiolink;
 using namespace isc::asiolink;
-using namespace isc::data;
 using namespace isc::config;
 using namespace isc::config;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+using namespace isc::hooks;
 
 
 namespace {
 namespace {
 
 
 class NakedControlledDhcpv6Srv: public ControlledDhcpv6Srv {
 class NakedControlledDhcpv6Srv: public ControlledDhcpv6Srv {
-    // "naked" DHCPv6 server, exposes internal fields
+    // "Naked" DHCPv6 server, exposes internal fields
 public:
 public:
     NakedControlledDhcpv6Srv():ControlledDhcpv6Srv(DHCP6_SERVER_PORT + 10000) { }
     NakedControlledDhcpv6Srv():ControlledDhcpv6Srv(DHCP6_SERVER_PORT + 10000) { }
 };
 };
@@ -45,10 +52,25 @@ public:
 class CtrlDhcpv6SrvTest : public ::testing::Test {
 class CtrlDhcpv6SrvTest : public ::testing::Test {
 public:
 public:
     CtrlDhcpv6SrvTest() {
     CtrlDhcpv6SrvTest() {
+        reset();
     }
     }
 
 
     ~CtrlDhcpv6SrvTest() {
     ~CtrlDhcpv6SrvTest() {
+        reset();
     };
     };
+
+    /// @brief Reset hooks data
+    ///
+    /// Resets the data for the hooks-related portion of the test by ensuring
+    /// that no libraries are loaded and that any marker files are deleted.
+    void reset() {
+        // Unload any previously-loaded libraries.
+        HooksManager::unloadLibraries();
+
+        // Get rid of any marker files.
+        static_cast<void>(unlink(LOAD_MARKER_FILE));
+        static_cast<void>(unlink(UNLOAD_MARKER_FILE));
+    }
 };
 };
 
 
 TEST_F(CtrlDhcpv6SrvTest, commands) {
 TEST_F(CtrlDhcpv6SrvTest, commands) {
@@ -62,12 +84,12 @@ TEST_F(CtrlDhcpv6SrvTest, commands) {
     ElementPtr params(new isc::data::MapElement());
     ElementPtr params(new isc::data::MapElement());
     int rcode = -1;
     int rcode = -1;
 
 
-    // case 1: send bogus command
+    // Case 1: send bogus command
     ConstElementPtr result = ControlledDhcpv6Srv::execDhcpv6ServerCommand("blah", params);
     ConstElementPtr result = ControlledDhcpv6Srv::execDhcpv6ServerCommand("blah", params);
     ConstElementPtr comment = parseAnswer(rcode, result);
     ConstElementPtr comment = parseAnswer(rcode, result);
     EXPECT_EQ(1, rcode); // expect failure (no such command as blah)
     EXPECT_EQ(1, rcode); // expect failure (no such command as blah)
 
 
-    // case 2: send shutdown command without any parameters
+    // Case 2: send shutdown command without any parameters
     result = ControlledDhcpv6Srv::execDhcpv6ServerCommand("shutdown", params);
     result = ControlledDhcpv6Srv::execDhcpv6ServerCommand("shutdown", params);
     comment = parseAnswer(rcode, result);
     comment = parseAnswer(rcode, result);
     EXPECT_EQ(0, rcode); // expect success
     EXPECT_EQ(0, rcode); // expect success
@@ -76,10 +98,54 @@ TEST_F(CtrlDhcpv6SrvTest, commands) {
     ConstElementPtr x(new isc::data::IntElement(pid));
     ConstElementPtr x(new isc::data::IntElement(pid));
     params->set("pid", x);
     params->set("pid", x);
 
 
-    // case 3: send shutdown command with 1 parameter: pid
+    // Case 3: send shutdown command with 1 parameter: pid
     result = ControlledDhcpv6Srv::execDhcpv6ServerCommand("shutdown", params);
     result = ControlledDhcpv6Srv::execDhcpv6ServerCommand("shutdown", params);
     comment = parseAnswer(rcode, result);
     comment = parseAnswer(rcode, result);
     EXPECT_EQ(0, rcode); // Expect success
     EXPECT_EQ(0, rcode); // Expect success
 }
 }
 
 
-} // end of anonymous namespace
+// Check that the "libreload" command will reload libraries
+
+TEST_F(CtrlDhcpv6SrvTest, libreload) {
+    // Ensure no marker files to start with.
+    ASSERT_FALSE(checkMarkerFileExists(LOAD_MARKER_FILE));
+    ASSERT_FALSE(checkMarkerFileExists(UNLOAD_MARKER_FILE));
+
+    // Load two libraries
+    std::vector<std::string> libraries;
+    libraries.push_back(CALLOUT_LIBRARY_1);
+    libraries.push_back(CALLOUT_LIBRARY_2);
+    HooksManager::loadLibraries(libraries);
+
+    // Check they are loaded.
+    std::vector<std::string> loaded_libraries =
+        HooksManager::getLibraryNames();
+    ASSERT_TRUE(libraries == loaded_libraries);
+
+    // ... which also included checking that the marker file created by the
+    // load functions exists and holds the correct value (of "12" - the
+    // first library appends "1" to the file, the second appends "2"). Also
+    // check that the unload marker file does not yet exist.
+    EXPECT_TRUE(checkMarkerFile(LOAD_MARKER_FILE, "12"));
+    EXPECT_FALSE(checkMarkerFileExists(UNLOAD_MARKER_FILE));
+
+    // Now execute the "libreload" command.  This should cause the libraries
+    // to unload and to reload.
+
+    // Use empty parameters list
+    ElementPtr params(new isc::data::MapElement());
+    int rcode = -1;
+
+    ConstElementPtr result =
+        ControlledDhcpv6Srv::execDhcpv6ServerCommand("libreload", params);
+    ConstElementPtr comment = parseAnswer(rcode, result);
+    EXPECT_EQ(0, rcode); // Expect success
+
+    // Check that the libraries have unloaded and reloaded.  The libraries are
+    // unloaded in the reverse order to which they are loaded.  When they load,
+    // they should append information to the loading marker file.
+    EXPECT_TRUE(checkMarkerFile(UNLOAD_MARKER_FILE, "21"));
+    EXPECT_TRUE(checkMarkerFile(LOAD_MARKER_FILE, "1212"));
+}
+
+} // End of anonymous namespace

+ 66 - 0
src/bin/dhcp6/tests/marker_file.cc

@@ -0,0 +1,66 @@
+// Copyright (C) 2013  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include "marker_file.h"
+
+#include <gtest/gtest.h>
+
+#include <fstream>
+#include <string>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+using namespace std;
+
+// Check the marker file.
+
+bool
+checkMarkerFile(const char* name, const char* expected) {
+    // Open the file for input
+    fstream file(name, fstream::in);
+
+    // Is it open?
+    if (!file.is_open()) {
+        ADD_FAILURE() << "Unable to open " << name << ". It was expected "
+                      << "to be present and to contain the string '"
+                      << expected << "'";
+        return (false);
+    }
+
+    // OK, is open, so read the data and see what we have.  Compare it
+    // against what is expected.
+    string content;
+    getline(file, content);
+
+    string expected_str(expected);
+    EXPECT_EQ(expected_str, content) << "Marker file " << name
+        << "did not contain the expected data";
+    file.close();
+
+    return (expected_str == content);
+}
+
+// Check if the marker file exists - this is a wrapper for "access(2)" and
+// really tests if the file exists and is accessible
+
+bool
+checkMarkerFileExists(const char* name) {
+    return (access(name, F_OK) == 0);
+}
+
+} // namespace test
+} // namespace dhcp
+} // namespace isc

+ 69 - 0
src/bin/dhcp6/tests/marker_file.h.in

@@ -0,0 +1,69 @@
+// Copyright (C) 2013  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#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* const LOAD_MARKER_FILE = "@abs_builddir@/load_marker.txt";
+const char* const UNLOAD_MARKER_FILE = "@abs_builddir@/unload_marker.txt";
+}
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+/// @brief Check marker file
+///
+/// This function is used in some of the DHCP server tests.
+///
+/// Marker files are used by the load/unload functions in the hooks
+/// libraries in these tests to signal whether they have been loaded or
+/// unloaded.  The file (if present) contains a single line holding
+/// a set of characters.
+///
+/// This convenience function checks the file to see if the characters
+/// are those expected.
+///
+/// @param name Name of the marker file.
+/// @param expected Characters expected.  If a marker file is present,
+///        it is expected to contain characters.
+///
+/// @return true if all tests pass, false if not (in which case a failure
+///         will have been logged).
+bool
+checkMarkerFile(const char* name, const char* expected);
+
+/// @brief Check marker file exists
+///
+/// This function is used in some of the DHCP server tests.
+///
+/// Checkes that the specified file does NOT exist.
+///
+/// @param name Name of the marker file.
+///
+/// @return true if file exists, false if not.
+bool
+checkMarkerFileExists(const char* name);
+
+} // namespace test
+} // namespace dhcp
+} // namespace isc
+
+#endif // MARKER_FILE_H
+

+ 51 - 0
src/bin/dhcp6/tests/test_libraries.h.in

@@ -0,0 +1,51 @@
+// Copyright (C) 2013  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef TEST_LIBRARIES_H
+#define TEST_LIBRARIES_H
+
+#include <config.h>
+
+namespace {
+
+
+// Take care of differences in DLL naming between operating systems.
+
+#ifdef OS_OSX
+#define DLL_SUFFIX ".dylib"
+
+#else
+#define DLL_SUFFIX ".so"
+
+#endif
+
+
+// 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
+// shared library.
+
+// Library with load/unload functions creating marker files to check their
+// operation.
+const char* const CALLOUT_LIBRARY_1 = "@abs_builddir@/.libs/libco1"
+                                           DLL_SUFFIX;
+const char* const CALLOUT_LIBRARY_2 = "@abs_builddir@/.libs/libco2"
+                                           DLL_SUFFIX;
+
+// Name of a library which is not present.
+const char* const NOT_PRESENT_LIBRARY = "@abs_builddir@/.libs/libnothere"
+                                         DLL_SUFFIX;
+} // anonymous namespace
+
+
+#endif // TEST_LIBRARIES_H

+ 2 - 0
src/lib/dhcpsrv/Makefile.am

@@ -59,6 +59,8 @@ libb10_dhcpsrv_la_CXXFLAGS = $(AM_CXXFLAGS)
 libb10_dhcpsrv_la_CPPFLAGS = $(AM_CPPFLAGS) $(LOG4CPLUS_INCLUDES)
 libb10_dhcpsrv_la_CPPFLAGS = $(AM_CPPFLAGS) $(LOG4CPLUS_INCLUDES)
 libb10_dhcpsrv_la_LIBADD   = $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
 libb10_dhcpsrv_la_LIBADD   = $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
 libb10_dhcpsrv_la_LIBADD  += $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la
 libb10_dhcpsrv_la_LIBADD  += $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la
+libb10_dhcpsrv_la_LIBADD  += $(top_builddir)/src/lib/hooks/libb10-hooks.la
+libb10_dhcpsrv_la_LIBADD  += $(top_builddir)/src/lib/log/libb10-log.la
 libb10_dhcpsrv_la_LIBADD  += $(top_builddir)/src/lib/util/libb10-util.la
 libb10_dhcpsrv_la_LIBADD  += $(top_builddir)/src/lib/util/libb10-util.la
 libb10_dhcpsrv_la_LIBADD  += $(top_builddir)/src/lib/cc/libb10-cc.la
 libb10_dhcpsrv_la_LIBADD  += $(top_builddir)/src/lib/cc/libb10-cc.la
 libb10_dhcpsrv_la_LIBADD  += $(top_builddir)/src/lib/hooks/libb10-hooks.la
 libb10_dhcpsrv_la_LIBADD  += $(top_builddir)/src/lib/hooks/libb10-hooks.la

+ 76 - 3
src/lib/dhcpsrv/dhcp_parsers.cc

@@ -16,19 +16,21 @@
 #include <dhcp/libdhcp++.h>
 #include <dhcp/libdhcp++.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/dhcp_parsers.h>
 #include <dhcpsrv/dhcp_parsers.h>
+#include <hooks/hooks_manager.h>
 #include <util/encode/hex.h>
 #include <util/encode/hex.h>
 #include <util/strutil.h>
 #include <util/strutil.h>
 
 
-#include <boost/foreach.hpp>
-#include <boost/lexical_cast.hpp>
 #include <boost/algorithm/string.hpp>
 #include <boost/algorithm/string.hpp>
+#include <boost/foreach.hpp>
 #include <boost/lexical_cast.hpp>
 #include <boost/lexical_cast.hpp>
 
 
-#include <string>
 #include <map>
 #include <map>
+#include <string>
+#include <vector>
 
 
 using namespace std;
 using namespace std;
 using namespace isc::data;
 using namespace isc::data;
+using namespace isc::hooks;
 
 
 namespace isc {
 namespace isc {
 namespace dhcp {
 namespace dhcp {
@@ -215,6 +217,77 @@ InterfaceListConfigParser::isIfaceAdded(const std::string& iface) const {
     return (false);
     return (false);
 }
 }
 
 
+// ******************** HooksLibrariesParser *************************
+
+HooksLibrariesParser::HooksLibrariesParser(const std::string& param_name)
+    : libraries_(), changed_(false)
+{
+    // Sanity check on the name.
+    if (param_name != "hooks-libraries") {
+        isc_throw(BadValue, "Internal error. Hooks libraries "
+            "parser called for the wrong parameter: " << param_name);
+    }
+}
+
+void 
+HooksLibrariesParser::build(ConstElementPtr value) {
+    // Initialize.
+    libraries_.clear();
+    changed_ = false;
+
+    // Extract the list of libraries.
+    BOOST_FOREACH(ConstElementPtr iface, value->listValue()) {
+        string libname = iface->str();
+        boost::erase_all(libname, "\"");
+        libraries_.push_back(libname);
+    }
+
+    // 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).
+    vector<string> current_libraries = HooksManager::getLibraryNames();
+    if (current_libraries == libraries_) {
+        return;
+    }
+
+    // Library list has changed, validate each of the libraries specified.
+    vector<string> error_libs = HooksManager::validateLibraries(libraries_);
+    if (!error_libs.empty()) {
+
+        // Construct the list of libraries in error for the message.
+        string error_list = error_libs[0];
+        for (int i = 1; i < error_libs.size(); ++i) {
+            error_list += (string(", ") + error_libs[i]);
+        }
+        isc_throw(DhcpConfigError, "hooks libraries failed to validate - "
+                  "library or libraries in error are: " + error_list);
+    }
+
+    // The library list has changed and the libraries are valid, so flag for
+    // update when commit() is called.
+    changed_ = true;
+}
+
+void 
+HooksLibrariesParser::commit() {
+    /// Commits the list of libraries to the configuration manager storage if
+    /// the list of libraries has changed.
+    if (changed_) {
+        // TODO Delete any stored CalloutHandles before reloading the
+        // libraries
+        HooksManager::loadLibraries(libraries_);
+    }
+}
+
+// Method for testing
+void
+HooksLibrariesParser::getLibraries(std::vector<std::string>& libraries,
+                                   bool& changed) {
+    libraries = libraries_;
+    changed = changed_;
+}
+
 // **************************** OptionDataParser *************************
 // **************************** OptionDataParser *************************
 OptionDataParser::OptionDataParser(const std::string&, OptionStoragePtr options,
 OptionDataParser::OptionDataParser(const std::string&, OptionStoragePtr options,
                                   ParserContextPtr global_context)
                                   ParserContextPtr global_context)

+ 88 - 1
src/lib/dhcpsrv/dhcp_parsers.h

@@ -23,6 +23,8 @@
 #include <dhcpsrv/subnet.h>
 #include <dhcpsrv/subnet.h>
 #include <exceptions/exceptions.h>
 #include <exceptions/exceptions.h>
 
 
+#include <boost/shared_ptr.hpp>
+
 #include <stdint.h>
 #include <stdint.h>
 #include <string>
 #include <string>
 #include <vector>
 #include <vector>
@@ -33,7 +35,6 @@ namespace dhcp {
 /// @brief Storage for option definitions.
 /// @brief Storage for option definitions.
 typedef OptionSpaceContainer<OptionDefContainer,
 typedef OptionSpaceContainer<OptionDefContainer,
                              OptionDefinitionPtr> OptionDefStorage;
                              OptionDefinitionPtr> OptionDefStorage;
-
 /// @brief Shared pointer to option definitions storage.
 /// @brief Shared pointer to option definitions storage.
 typedef boost::shared_ptr<OptionDefStorage> OptionDefStoragePtr;
 typedef boost::shared_ptr<OptionDefStorage> OptionDefStoragePtr;
 
 
@@ -44,6 +45,8 @@ typedef OptionSpaceContainer<Subnet::OptionContainer,
 /// @brief Shared pointer to option storage.
 /// @brief Shared pointer to option storage.
 typedef boost::shared_ptr<OptionStorage> OptionStoragePtr;
 typedef boost::shared_ptr<OptionStorage> OptionStoragePtr;
 
 
+
+
 /// @brief A template class that stores named elements of a given data type.
 /// @brief A template class that stores named elements of a given data type.
 ///
 ///
 /// This template class is provides data value storage for configuration parameters
 /// This template class is provides data value storage for configuration parameters
@@ -151,6 +154,20 @@ public:
     /// @brief Storage for option definitions.
     /// @brief Storage for option definitions.
     OptionDefStoragePtr option_defs_;
     OptionDefStoragePtr option_defs_;
 
 
+    /// @brief Hooks libraries pointer.
+    ///
+    /// The hooks libraries information is a vector of strings, each containing
+    /// the name of a library.  Hooks libraries should only be reloaded if the
+    /// list of names has changed, so the list of current DHCP parameters
+    /// (in isc::dhcp::CfgMgr) contains an indication as to whether the list has
+    /// altered.  This indication is implemented by storing a pointer to the
+    /// list of library names which is cleared when the libraries are loaded.
+    /// So either the pointer is null (meaning don't reload the libraries and
+    /// the list of current names can be obtained from the HooksManager) or it
+    /// is non-null (this is the new list of names, reload the libraries when
+    /// possible).
+    boost::shared_ptr<std::vector<std::string> > hooks_libraries_;
+
     /// @brief The parsing universe of this context.
     /// @brief The parsing universe of this context.
     Option::Universe universe_;
     Option::Universe universe_;
 
 
@@ -321,6 +338,76 @@ private:
     std::string param_name_;
     std::string param_name_;
 };
 };
 
 
+/// @brief Parser for hooks library list
+///
+/// This parser handles the list of hooks libraries.  This is an optional list,
+/// which may be empty.
+///
+/// However, the parser does more than just check the list of library names.
+/// It does two other things:
+///
+/// -# The problem faced with the hooks libraries is that we wish to avoid
+/// reloading the libraries if they have not changed.  (This would cause the
+/// "unload" and "load" methods to run.  Although libraries should be written
+/// to cope with this, it is feasible that such an action may be constly in
+/// terms of time and resources, or may cause side effects such as clearning
+/// an internal cache.)  To this end, the parser also checks the list against
+/// the list of libraries current loaded and notes if there are changes.
+/// -# If there are, the parser validates the libraries; it opens them and
+/// checks that the "version" function exists and returns the correct value.
+///
+/// Only if the library list has changed and the libraries are valid will the
+/// change be applied.
+class HooksLibrariesParser : public DhcpConfigParser {
+public:
+
+    /// @brief Constructor
+    ///
+    /// As this is a dedicated parser, it must be used to parse
+    /// "hooks-libraries" parameter only. All other types will throw exception.
+    ///
+    /// @param param_name name of the configuration parameter being parsed.
+    ///
+    /// @throw BadValue if supplied parameter name is not "hooks-libraries"
+    HooksLibrariesParser(const std::string& param_name);
+
+    /// @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).
+    ///
+    /// @param value pointer to the content of parsed values
+    virtual void build(isc::data::ConstElementPtr value);
+
+    /// @brief Commits hooks libraries data
+    ///
+    /// 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).
+    virtual void commit();
+
+    /// @brief Returns list of parsed libraries
+    ///
+    /// Principally for testing, this returns the list of libraries as well as
+    /// an indication as to whether the list is different from the list of
+    /// libraries already loaded.
+    ///
+    /// @param [out] libraries List of libraries that were specified in the
+    ///        new configuration.
+    /// @param [out] changed true if the list is different from that currently
+    ///        loaded.
+    void getLibraries(std::vector<std::string>& libraries, bool& changed);
+
+private:
+    /// List of hooks libraries.
+    std::vector<std::string> libraries_;
+
+    /// Indicator flagging that the list of libraries has changed.
+    bool changed_;
+};
 
 
 /// @brief Parser for option data value.
 /// @brief Parser for option data value.
 ///
 ///

+ 14 - 2
src/lib/dhcpsrv/tests/Makefile.am

@@ -24,6 +24,18 @@ TESTS_ENVIRONMENT = \
 
 
 TESTS =
 TESTS =
 if HAVE_GTEST
 if HAVE_GTEST
+# Build shared libraries for testing.
+lib_LTLIBRARIES = libco1.la libco2.la
+
+libco1_la_SOURCES  = callout_library.cc
+libco1_la_CXXFLAGS = $(AM_CXXFLAGS)
+libco1_la_CPPFLAGS = $(AM_CPPFLAGS) $(LOG4CPLUS_INCLUDES)
+
+libco2_la_SOURCES  = callout_library.cc
+libco2_la_CXXFLAGS = $(AM_CXXFLAGS)
+libco2_la_CPPFLAGS = $(AM_CPPFLAGS) $(LOG4CPLUS_INCLUDES)
+
+
 TESTS += libdhcpsrv_unittests
 TESTS += libdhcpsrv_unittests
 
 
 libdhcpsrv_unittests_SOURCES  = run_unittests.cc
 libdhcpsrv_unittests_SOURCES  = run_unittests.cc
@@ -67,9 +79,9 @@ libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la
 libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/config/libb10-cfgclient.la
 libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/config/libb10-cfgclient.la
 libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/cc/libb10-cc.la
 libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/cc/libb10-cc.la
 libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
 libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
-libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
-libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/log/libb10-log.la
 libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/hooks/libb10-hooks.la
 libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/hooks/libb10-hooks.la
+libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/log/libb10-log.la
+libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
 libdhcpsrv_unittests_LDADD += $(GTEST_LDADD)
 libdhcpsrv_unittests_LDADD += $(GTEST_LDADD)
 endif
 endif
 
 

+ 31 - 0
src/lib/dhcpsrv/tests/callout_library.cc

@@ -0,0 +1,31 @@
+// Copyright (C) 2013  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+/// @file
+/// @brief Callout Library
+///
+/// This is the source of a test library for the basic DHCP parser 
+/// tests.  It just has to load - nothing else.
+
+#include <hooks/hooks.h>
+
+extern "C" {
+
+// Framework functions
+int
+version() {
+    return (BIND10_HOOKS_VERSION);
+}
+
+};

+ 162 - 19
src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc

@@ -20,19 +20,23 @@
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/subnet.h>
 #include <dhcpsrv/subnet.h>
 #include <dhcpsrv/dhcp_parsers.h>
 #include <dhcpsrv/dhcp_parsers.h>
+#include <dhcpsrv/tests/test_libraries.h>
 #include <exceptions/exceptions.h>
 #include <exceptions/exceptions.h>
+#include <hooks/hooks_manager.h>
 
 
 #include <gtest/gtest.h>
 #include <gtest/gtest.h>
 #include <boost/foreach.hpp>
 #include <boost/foreach.hpp>
+#include <boost/pointer_cast.hpp>
 
 
 #include <map>
 #include <map>
 #include <string>
 #include <string>
 
 
 using namespace std;
 using namespace std;
 using namespace isc;
 using namespace isc;
-using namespace isc::dhcp;
-using namespace isc::data;
 using namespace isc::config;
 using namespace isc::config;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::hooks;
 
 
 namespace {
 namespace {
 
 
@@ -308,7 +312,7 @@ public:
 
 
         ConfigPair config_pair;
         ConfigPair config_pair;
         try {
         try {
-            // Iteraate over the config elements.
+            // Iterate over the config elements.
             const std::map<std::string, ConstElementPtr>& values_map =
             const std::map<std::string, ConstElementPtr>& values_map =
                                                       config_set->mapValue();
                                                       config_set->mapValue();
             BOOST_FOREACH(config_pair, values_map) {
             BOOST_FOREACH(config_pair, values_map) {
@@ -348,22 +352,34 @@ public:
 
 
     /// @brief Create an element parser based on the element name.
     /// @brief Create an element parser based on the element name.
     ///
     ///
-    /// Note that currently it only supports option-defs and option-data,
+    /// Creates a parser for the appropriate element and stores a pointer to it
+    /// in the appropriate class variable.
+    ///
+    /// Note that the method currently it only supports option-defs, option-data
+    /// and hooks-libraries.
+    /// 
+    /// @param config_id is the name of the configuration element. 
+    ///
+    /// @return returns a shared pointer to DhcpConfigParser.
     ///
     ///
-    /// @param config_id is the name of the configuration element.
-    /// @return returns a raw pointer to DhcpConfigParser. Note caller is
-    /// responsible for deleting it once no longer needed.
     /// @throw throws NotImplemented if element name isn't supported.
     /// @throw throws NotImplemented if element name isn't supported.
-    DhcpConfigParser* createConfigParser(const std::string& config_id) {
-        DhcpConfigParser* parser = NULL;
+    ParserPtr createConfigParser(const std::string& config_id) {
+        ParserPtr parser;
         if (config_id.compare("option-data") == 0) {
         if (config_id.compare("option-data") == 0) {
-            parser = new OptionDataListParser(config_id,
-                                          parser_context_->options_,
-                                          parser_context_,
-                                          UtestOptionDataParser::factory);
+            parser.reset(new OptionDataListParser(config_id, 
+                                              parser_context_->options_, 
+                                              parser_context_,
+                                              UtestOptionDataParser::factory));
+
         } else if (config_id.compare("option-def") == 0) {
         } else if (config_id.compare("option-def") == 0) {
-            parser  = new OptionDefListParser(config_id,
-                                          parser_context_->option_defs_);
+            parser.reset(new OptionDefListParser(config_id, 
+                                              parser_context_->option_defs_));
+
+        } else if (config_id.compare("hooks-libraries") == 0) {
+            parser.reset(new HooksLibrariesParser(config_id));
+            hooks_libraries_parser_ =
+                boost::dynamic_pointer_cast<HooksLibrariesParser>(parser);
+
         } else {
         } else {
             isc_throw(NotImplemented,
             isc_throw(NotImplemented,
                 "Parser error: configuration parameter not supported: "
                 "Parser error: configuration parameter not supported: "
@@ -373,15 +389,15 @@ public:
         return (parser);
         return (parser);
     }
     }
 
 
-    /// @brief Convenicee method for parsing a configuration
-    ///
+    /// @brief Convenience method for parsing a configuration 
+    /// 
     /// Given a configuration string, convert it into Elements
     /// Given a configuration string, convert it into Elements
     /// and parse them.
     /// and parse them.
     /// @param config is the configuration string to parse
     /// @param config is the configuration string to parse
     ///
     ///
     /// @return retuns 0 if the configuration parsed successfully,
     /// @return retuns 0 if the configuration parsed successfully,
     /// non-zero otherwise failure.
     /// non-zero otherwise failure.
-    int parseConfiguration (std::string &config) {
+    int parseConfiguration(const std::string& config) {    
         int rcode_ = 1;
         int rcode_ = 1;
         // Turn config into elements.
         // Turn config into elements.
         // Test json just to make sure its valid.
         // Test json just to make sure its valid.
@@ -389,7 +405,8 @@ public:
         EXPECT_TRUE(json);
         EXPECT_TRUE(json);
         if (json) {
         if (json) {
             ConstElementPtr status = parseElementSet(json);
             ConstElementPtr status = parseElementSet(json);
-            ConstElementPtr comment_ = parseAnswer(rcode_, status);
+            ConstElementPtr comment = parseAnswer(rcode_, status);
+            error_text_ = comment->stringValue();
         }
         }
 
 
         return (rcode_);
         return (rcode_);
@@ -461,10 +478,21 @@ public:
         CfgMgr::instance().deleteSubnets6();
         CfgMgr::instance().deleteSubnets6();
         CfgMgr::instance().deleteOptionDefs();
         CfgMgr::instance().deleteOptionDefs();
         parser_context_.reset(new ParserContext(Option::V6));
         parser_context_.reset(new ParserContext(Option::V6));
+
+        // Ensure no hooks libraries are loaded.
+        HooksManager::unloadLibraries();
     }
     }
 
 
+    /// @brief Parsers used in the parsing of the configuration
+    ///
+    /// Allows the tests to interrogate the state of the parsers (if required).
+    boost::shared_ptr<HooksLibrariesParser> hooks_libraries_parser_;
+
     /// @brief Parser context - provides storage for options and definitions
     /// @brief Parser context - provides storage for options and definitions
     ParserContextPtr parser_context_;
     ParserContextPtr parser_context_;
+
+    /// @brief Error string if the parsing failed
+    std::string error_text_;
 };
 };
 
 
 /// @brief Check Basic parsing of option definitions.
 /// @brief Check Basic parsing of option definitions.
@@ -492,6 +520,7 @@ TEST_F(ParseConfigTest, basicOptionDefTest) {
     int rcode = parseConfiguration(config);
     int rcode = parseConfiguration(config);
     ASSERT_TRUE(rcode == 0);
     ASSERT_TRUE(rcode == 0);
 
 
+
     // Verify that the option definition can be retrieved.
     // Verify that the option definition can be retrieved.
     OptionDefinitionPtr def = getOptionDef("isc", 100);
     OptionDefinitionPtr def = getOptionDef("isc", 100);
     ASSERT_TRUE(def);
     ASSERT_TRUE(def);
@@ -549,3 +578,117 @@ TEST_F(ParseConfigTest, basicOptionDataTest) {
 
 
 };  // Anonymous namespace
 };  // Anonymous namespace
 
 
+/// These tests check basic operation of the HooksLibrariesParser.
+
+// hooks-libraries that do not contain anything.
+TEST_F(ParseConfigTest, noHooksLibrariesTest) {
+
+    // Configuration with hooks-libraries not present.
+    string config = "{ \"hooks-libraries\": [] }";
+
+    // Verify that the configuration string parses.
+    int rcode = parseConfiguration(config);
+    ASSERT_TRUE(rcode == 0) << error_text_;
+
+    // Check that the parser recorded no change to the current state
+    // (as the test starts with no hooks libraries loaded).
+    std::vector<std::string> libraries;
+    bool changed;
+    hooks_libraries_parser_->getLibraries(libraries, changed);
+    EXPECT_TRUE(libraries.empty());
+    EXPECT_FALSE(changed);
+
+    // Load a single library and repeat the parse.
+    vector<string> basic_library;
+    basic_library.push_back(string(CALLOUT_LIBRARY_1));
+    HooksManager::loadLibraries(basic_library);
+
+    rcode = parseConfiguration(config);
+    ASSERT_TRUE(rcode == 0) << error_text_;
+
+    // This time the change should have been recorded.
+    hooks_libraries_parser_->getLibraries(libraries, changed);
+    EXPECT_TRUE(libraries.empty());
+    EXPECT_TRUE(changed);
+
+    // But repeating it again and we are back to no change.
+    rcode = parseConfiguration(config);
+    ASSERT_TRUE(rcode == 0) << error_text_;
+    hooks_libraries_parser_->getLibraries(libraries, changed);
+    EXPECT_TRUE(libraries.empty());
+    EXPECT_FALSE(changed);
+
+}
+
+
+TEST_F(ParseConfigTest, validHooksLibrariesTest) {
+
+    // Configuration string.  This contains a set of valid libraries.
+    const std::string quote("\"");
+    const std::string comma(", ");
+
+    const std::string config =
+        std::string("{ ") +
+            std::string("\"hooks-libraries\": [") +
+                quote + std::string(CALLOUT_LIBRARY_1) + quote + comma +
+                quote + std::string(CALLOUT_LIBRARY_2)  + quote +
+            std::string("]") +
+        std::string("}");
+
+    // Verify that the configuration string parses.
+    int rcode = parseConfiguration(config);
+    ASSERT_TRUE(rcode == 0) << error_text_;
+
+    // Check that the parser holds two libraries and the configuration is
+    // recorded as having changed.
+    std::vector<std::string> libraries;
+    bool changed;
+    hooks_libraries_parser_->getLibraries(libraries, changed);
+    EXPECT_EQ(2, libraries.size());
+    EXPECT_TRUE(changed);
+
+    // The expected libraries should be the list of libraries specified
+    // in the given order.
+    std::vector<std::string> expected;
+    expected.push_back(CALLOUT_LIBRARY_1);
+    expected.push_back(CALLOUT_LIBRARY_2);
+    EXPECT_TRUE(expected == libraries);
+
+    // Parse the string again.
+    rcode = parseConfiguration(config);
+    ASSERT_TRUE(rcode == 0) << error_text_;
+
+    // The list has not changed, and this is what we should see.
+    hooks_libraries_parser_->getLibraries(libraries, changed);
+    EXPECT_EQ(2, libraries.size());
+    EXPECT_FALSE(changed);
+}
+
+// Check with a set of libraries, some of which are invalid.
+TEST_F(ParseConfigTest, invalidHooksLibrariesTest) {
+
+    // @todo Initialize global library context to null
+
+    // Configuration string.  This contains an invalid library which should
+    // trigger an error in the "build" stage.
+    const std::string quote("\"");
+    const std::string comma(", ");
+
+    const std::string config =
+        std::string("{ ") +
+            std::string("\"hooks-libraries\": [") +
+                quote + std::string(CALLOUT_LIBRARY_1) + quote + comma +
+                quote + std::string(NOT_PRESENT_LIBRARY) + quote + comma +
+                quote + std::string(CALLOUT_LIBRARY_2)  + quote +
+            std::string("]") +
+        std::string("}");
+
+    // Verify that the configuration fails to parse. (Syntactically it's OK,
+    // but the library is invalid).
+    int rcode = parseConfiguration(config);
+    ASSERT_FALSE(rcode == 0) << error_text_;
+
+    // Check that the message contains the library in error.
+    EXPECT_FALSE(error_text_.find(NOT_PRESENT_LIBRARY) == string::npos) <<
+        "Error text returned from parse failure is " << error_text_;
+}

+ 51 - 0
src/lib/dhcpsrv/tests/test_libraries.h.in

@@ -0,0 +1,51 @@
+// Copyright (C) 2013  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef TEST_LIBRARIES_H
+#define TEST_LIBRARIES_H
+
+#include <config.h>
+
+namespace {
+
+
+// Take care of differences in DLL naming between operating systems.
+
+#ifdef OS_OSX
+#define DLL_SUFFIX ".dylib"
+
+#else
+#define DLL_SUFFIX ".so"
+
+#endif
+
+
+// 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
+// shared library.
+
+// Library with load/unload functions creating marker files to check their
+// operation.
+static const char* CALLOUT_LIBRARY_1 = "@abs_builddir@/.libs/libco1"
+                                           DLL_SUFFIX;
+static const char* CALLOUT_LIBRARY_2 = "@abs_builddir@/.libs/libco2"
+                                           DLL_SUFFIX;
+
+// Name of a library which is not present.
+static const char* NOT_PRESENT_LIBRARY = "@abs_builddir@/.libs/libnothere"
+                                         DLL_SUFFIX;
+} // anonymous namespace
+
+
+#endif // TEST_LIBRARIES_H