Browse Source

[2981] Checkpoint prior to rebasing on updated master

Stephen Morris 11 years ago
parent
commit
c706b28ae1

+ 2 - 0
configure.ac

@@ -1381,6 +1381,8 @@ AC_OUTPUT([doc/version.ent
            src/bin/dbutil/run_dbutil.sh
            src/bin/dbutil/tests/dbutil_test.sh
            src/bin/ddns/ddns.py
+           src/bin/dhcp6/tests/marker_file.h
+           src/bin/dhcp6/tests/test_libraries.h
            src/bin/xfrin/tests/xfrin_test
            src/bin/xfrin/xfrin.py
            src/bin/xfrin/run_b10-xfrin.sh

+ 2 - 0
src/bin/dhcp4/ctrl_dhcp4_srv.cc

@@ -121,6 +121,8 @@ ControlledDhcpv4Srv::dhcp4CommandHandler(const string& command, ConstElementPtr
         ConstElementPtr answer = isc::config::createAnswer(0,
                                  "Shutting down.");
         return (answer);
+    } else if (command == "libreload") {
+        // TODO Reload libraries
     }
 
     ConstElementPtr answer = isc::config::createAnswer(1,

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

@@ -430,6 +430,8 @@ DhcpConfigParser* createGlobal6DhcpConfigParser(const std::string& config_id) {
                                    globalContext()->string_values_);
     } else if (config_id.compare("lease-database") == 0) {
         parser = new DbAccessParser(config_id);
+    } else if (config_id.compare("hooks-libraries") == 0) {
+        parser = new HooksLibrariesParser(config_id);
     } else {
         isc_throw(NotImplemented,
                 "Parser error: Global configuration parameter not supported: "
@@ -464,6 +466,11 @@ configureDhcp6Server(Dhcpv6Srv&, isc::data::ConstElementPtr config_set) {
     ParserPtr subnet_parser;
     ParserPtr option_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
     // accessing global storage. For this reason the global data
     // parsers must store the parsed data into global storages
@@ -495,6 +502,14 @@ configureDhcp6Server(Dhcpv6Srv&, isc::data::ConstElementPtr config_set) {
                 subnet_parser = parser;
             } else if (config_pair.first == "option-data") {
                 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 {
                 // Those parsers should be started before other
                 // parsers so we can call build straight away.
@@ -571,6 +586,11 @@ configureDhcp6Server(Dhcpv6Srv&, isc::data::ConstElementPtr config_set) {
         return (answer);
     }
 
+    // Now commit any changes that have been validated but not yet committed.
+    if (hooks_parser) {
+        hooks_parser->commit();
+    }
+
     LOG_INFO(dhcp6_logger, DHCP6_CONFIG_COMPLETE).arg(config_details);
 
     // Everything was fine. Configuration is successful.

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

@@ -3,6 +3,19 @@
     "module_name": "Dhcp6",
     "module_description": "DHCPv6 server daemon",
     "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": true,
+        }
+      },
+ 
       { "item_name": "interface",
         "item_type": "list",
         "item_optional": false,
@@ -295,6 +308,11 @@
                     "item_optional": true
                 }
             ]
+        },
+
+        {
+            "command_name": "libreload",
+            "command_description": "Reloads the current hooks libraries.", 
         }
     ]
   }

+ 16 - 3
src/bin/dhcp6/tests/Makefile.am

@@ -27,7 +27,8 @@ AM_CPPFLAGS += -I$(top_srcdir)/src/bin
 AM_CPPFLAGS += $(BOOST_INCLUDES)
 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)
 if USE_CLANGPP
@@ -44,9 +45,18 @@ TESTS_ENVIRONMENT = \
 
 TESTS =
 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_srv_unittest.cc
 dhcp6_unittests_SOURCES += ctrl_dhcp6_srv_unittest.cc
@@ -55,7 +65,9 @@ dhcp6_unittests_SOURCES += ../dhcp6_srv.h ../dhcp6_srv.cc
 dhcp6_unittests_SOURCES += ../dhcp6_log.h ../dhcp6_log.cc
 dhcp6_unittests_SOURCES += ../ctrl_dhcp6_srv.cc
 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_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
@@ -68,6 +80,7 @@ dhcp6_unittests_LDADD += $(top_builddir)/src/lib/dhcpsrv/libb10-dhcpsrv.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/util/libb10-util.la
+
 endif
 
 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"

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

@@ -0,0 +1,80 @@
+// 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
+/// to the single line in 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));
+}
+
+};

+ 280 - 33
src/bin/dhcp6/tests/config_parser_unittest.cc

@@ -24,15 +24,23 @@
 #include <dhcp6/dhcp6_srv.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/subnet.h>
+#include <hooks/hooks_manager.h>
+
+#include "test_libraries.h"
+#include "marker_file.h"
 
 #include <boost/foreach.hpp>
 #include <gtest/gtest.h>
 
 #include <fstream>
 #include <iostream>
+#include <fstream>
 #include <sstream>
+#include <string>
+#include <vector>
 
 #include <arpa/inet.h>
+#include <unistd.h>
 
 using namespace std;
 using namespace isc;
@@ -40,6 +48,7 @@ using namespace isc::dhcp;
 using namespace isc::asiolink;
 using namespace isc::data;
 using namespace isc::config;
+using namespace isc::hooks;
 
 namespace {
 
@@ -71,6 +80,10 @@ public:
     ~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
@@ -169,50 +182,75 @@ public:
         return (stream.str());
     }
 
-    /// @brief Reset configuration database.
+    /// @brief Parse 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;
-
-        string config = "{ \"interface\": [ \"all\" ],"
-            "\"preferred-lifetime\": 3000,"
-            "\"rebind-timer\": 2000, "
-            "\"renew-timer\": 1000, "
-            "\"valid-lifetime\": 4000, "
-            "\"subnet6\": [ ], "
-            "\"option-def\": [ ], "
-            "\"option-data\": [ ] }";
-
         try {
             ElementPtr json = Element::fromJSON(config);
             status = configureDhcp6Server(srv_, json);
         } 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
                    << " and the following error message was returned:"
                    << ex.what() << std::endl;
+            return (false);
         }
 
-        // status object must not be NULL
+        // The status object must not be NULL
         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);
-        // returned value should be 0 (configuration success)
         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 = "{ \"interface\": [ \"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"));
     }
 
     /// @brief Test invalid option parameter value.
@@ -277,13 +315,66 @@ public:
                             expected_data_len));
     }
 
-    int rcode_; ///< return core (see @ref isc::config::parseAnswer)
-    Dhcpv6Srv srv_; ///< instance of the Dhcp6Srv used during tests
+    /// @brief Check marker file
+    ///
+    /// 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.  Therefore a value of NULL
+    ///        is used to signify that the marker file is not expected to be
+    ///        present.
+    ///
+    /// @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) {
+        // Open the file for input
+        fstream file(name, fstream::in);
+
+        // Is it open?
+        if (!file.is_open()) {
+
+            // No.  This is OK if we don't expected is to be present but is
+            // a failure otherwise.
+            if (expected == NULL) {
+                return (true);
+            }
+            ADD_FAILURE() << "Unable to open " << name << ". It was expected "
+                          << "to be present and to contain the string '"
+                          << expected << "'";
+            return (false);
+        } else if (expected == NULL) {
+
+            // File is open but we don't expect it to be present.
+            ADD_FAILURE() << "Opened " << name << " but it is not expected to "
+                          << "be present.";
+            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);
 
-    ConstElementPtr comment_; ///< comment (see @ref isc::config::parseAnswer)
+        string expected_str(expected);
+        EXPECT_EQ(expected_str, content) << "Data was read from " << name;
+        file.close();
 
-    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)
+        return (expected_str == content);
+    }
+
+    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
@@ -1850,4 +1941,160 @@ TEST_F(Dhcp6ParserTest, stdOptionDataEncapsulate) {
     EXPECT_FALSE(desc.option->getOption(112));
 }
 
+// Tests of the hooks libraries configuration.
+
+// 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 =
+        "{ \"interface\": [ \"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) {
+//    std::vector<std::string> libraries = HooksManager::getLibraryNames();
+//   ASSERT_TRUE(libraries.empty());
+
+    // Parse a configuration containing no names.
+    string config = buildHooksLibrariesConfig();
+    if (!executeConfiguration(config,
+                              "set configuration with no hooks libraries")) {
+        return;
+    }
+//    libraries = HooksManager::getLibraryNames();
+//   ASSERT_TRUE(libraries.empty());
+}
+
+// Verify parsing fails with one library that will fail validation.
+TEST_F(Dhcp6ParserTest, InvalidLibrary) {
+//    std::vector<std::string> libraries = HooksManager::getLibraryNames();
+//   ASSERT_TRUE(libraries.empty());
+
+    // 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_FALSE(status);
+
+    // Returned value should not be 0
+    comment_ = parseAnswer(rcode_, status);
+    EXPECT_NE(0, rcode_);
+    std::cerr << "Reason for success: " << comment_;
+}
+
+// Verify the configuration of hooks libraries with two being specified.
+TEST_F(Dhcp6ParserTest, LibrariesSpecified) {
+//    std::vector<std::string> libraries = HooksManager::getLibraryNames();
+//   ASSERT_TRUE(libraries.empty());
+
+    // Marker files should not be present.
+    EXPECT_TRUE(checkMarkerFile(LOAD_MARKER_FILE, NULL));
+    EXPECT_TRUE(checkMarkerFile(UNLOAD_MARKER_FILE, NULL));
+
+    // Set up the configuration with two libraries and load them.
+    string config = buildHooksLibrariesConfig(CALLOUT_LIBRARY_1,
+                                              CALLOUT_LIBRARY_2);
+    ASSERT_TRUE(executeConfiguration(config,
+                                     "loading 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_TRUE(checkMarkerFile(UNLOAD_MARKER_FILE, NULL));
+
+    // 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());
+
+    }
+//    libraries = HooksManager::getLibraryNames();
+//   ASSERT_TRUE(libraries.empty());
+
+
+
+
 };

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

@@ -0,0 +1,28 @@
+// 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* LOAD_MARKER_FILE = "@abs_builddir@/load_marker.txt";
+const char* UNLOAD_MARKER_FILE = "@abs_builddir@/unload_marker.txt";
+}
+
+#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.
+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

+ 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_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/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/cc/libb10-cc.la
 libb10_dhcpsrv_la_LDFLAGS  = -no-undefined -version-info 3:0:0

+ 0 - 18
src/lib/dhcpsrv/cfgmgr.cc

@@ -266,24 +266,6 @@ std::string CfgMgr::getDataDir() {
     return (datadir_);
 }
 
-void
-CfgMgr::setHooksLibraries(const std::vector<std::string>& hooks_libraries) {
-    hooks_libraries_.reset(new std::vector<std::string>(hooks_libraries));
-}
-
-boost::shared_ptr<std::vector<std::string> >
-CfgMgr::getAndClearHooksLibraries() {
-    // Create shared pointer pointing to nothing.
-    boost::shared_ptr<std::vector<std::string> > libraries;
-
-    // Set the new variable to point to the stored hooks libraries and clear
-    // the stored value.
-    libraries.swap(hooks_libraries_);
-
-    return (libraries);
-}
-
-
 
 CfgMgr::CfgMgr()
     :datadir_(DHCP_DATA_DIR) {

+ 5 - 57
src/lib/dhcpsrv/cfgmgr.h

@@ -38,21 +38,10 @@ namespace dhcp {
 /// @brief Configuration Manager
 ///
 /// This singleton class holds the whole configuration for DHCPv4 and DHCPv6
-/// servers. It currently holds information about:
-/// - Zero or more subnets.  Each subnet may contain zero or more pools. Pool4
-///   and Pool6 is the most basic "chunk" of configuration. It contains a range
-///   of assignable addresses.
-/// - Hook libraries. Hooks library names are stored here, but only up to the
-///   point that the libraries are reloaded.  In more detail: libraries
-///   containing hooks callouts can only be loaded and reloaded safely (or, more
-///   accurately, unloaded safely) when no data structure in the server contains
-///   a reference to any memory allocated by a function in them.  In practice
-///   this means when there are no packets being activly processed.  Rather than
-///   take a chance that the configuration code will do the unload/load at the
-///   right time, the configuration code sets the names of the new libraries in
-///   this object and the server decides when to reconfigure the hooks.  The
-///   presence or absence of the names of the hooks libraries here is an
-///   indication of whether the libraries should be reloaded.
+/// servers. It currently holds information about zero or more subnets6.
+/// Each subnet may contain zero or more pools. Pool4 and Pool6 is the most
+/// basic "chunk" of configuration. It contains a range of assignable
+/// addresses.
 ///
 /// Below is a sketch of configuration inheritance (not implemented yet).
 /// Let's investigate the following configuration:
@@ -79,7 +68,6 @@ namespace dhcp {
 /// routines, so there is no storage capability in a global scope for
 /// subnet-specific parameters.
 ///
-/// ARE THESE DONE?
 /// @todo: Implement Subnet4 support (ticket #2237)
 /// @todo: Implement option definition support
 /// @todo: Implement parameter inheritance
@@ -241,6 +229,7 @@ public:
     /// completely new?
     void deleteSubnets4();
 
+
     /// @brief returns path do the data directory
     ///
     /// This method returns a path to writeable directory that DHCP servers
@@ -248,25 +237,6 @@ public:
     /// @return data directory
     std::string getDataDir();
 
-    /// @brief Sets list of hooks libraries
-    ///
-    /// Sets the list of hooks libraries.  It is possible for there to be no
-    /// hooks libraries, in which case this is indicated by an emopty vector.
-    ///
-    /// @param hooks_libraries Vector (possibly empty) of current hook libraries.
-    void setHooksLibraries(const std::vector<std::string>& hooks_libraries);
-
-    /// @brief Get and clear list of hooks libraries
-    ///
-    /// Gets the currently-set vector of hooks libraries.  If there is no data
-    /// (as opposed to an empty vector), there has been no change to the data
-    /// since the last time this method was called. Should there be a necessity
-    /// to know this information, it can be obtained from the HooksManager.
-    ///
-    /// @return Pointer to vector of strings listing the hooks libraries.  This
-    ///         may be empty.
-    boost::shared_ptr<std::vector<std::string> > getAndClearHooksLibraries();
-
 protected:
 
     /// @brief Protected constructor.
@@ -313,28 +283,6 @@ private:
 
     /// @brief directory where data files (e.g. server-id) are stored
     std::string datadir_;
-
-    /// @brief Hooks libraries
-    ///
-    /// Unlike other configuration items that can be referenced all the time,
-    /// this is a "one-shot" item.  When the list of libraries is altered, the
-    /// server needs to know about the change: once the libraries are loaded,
-    /// the list is ignored.  As configuration updates cause an update of the
-    /// entire configuration and we wish to reload the libraries only if the
-    /// list has changed, we could check the library list against that stored
-    /// in the hooks manager.  Unfortunately, since the libraries are reloaded
-    /// when a new packet is received, this would mean a set of string
-    /// comparisons on each packet.  Instead, the data is flagged to indicate
-    /// that it has changed.
-    ///
-    /// The parsing checks the set of hooks libraries in the configuration
-    /// against the list stored in the HooksManager and only updates the data
-    /// here if they have changed.  Although a flag could be used to indicate
-    /// a change, a more streamlined approach is used: the data in this object
-    /// is cleared when it is read.  As the data is a vector of strings and as
-    /// an empty vector is valid data, we'll store the data as a shared pointer
-    /// to a vector of strings.  The pointer is zeroed when the data is read.
-    boost::shared_ptr<std::vector<std::string> > hooks_libraries_;
 };
 
 } // namespace isc::dhcp

+ 50 - 45
src/lib/dhcpsrv/dhcp_parsers.cc

@@ -16,19 +16,22 @@
 #include <dhcp/libdhcp++.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/dhcp_parsers.h>
+#include <hooks/hooks_manager.h>
 #include <util/encode/hex.h>
 #include <util/strutil.h>
 
+#include <boost/algorithm/string.hpp>
 #include <boost/foreach.hpp>
 #include <boost/lexical_cast.hpp>
-#include <boost/algorithm/string.hpp>
 #include <boost/lexical_cast.hpp>
 
-#include <string>
 #include <map>
+#include <string>
+#include <vector>
 
 using namespace std;
 using namespace isc::data;
+using namespace isc::hooks;
 
 namespace isc {
 namespace dhcp {
@@ -41,7 +44,6 @@ ParserContext::ParserContext(Option::Universe universe):
         string_values_(new StringStorage()),
         options_(new OptionStorage()),
         option_defs_(new OptionDefStorage()),
-        hooks_libraries_(new HooksLibrariesStorage()),
         universe_(universe) {
     }
 
@@ -51,7 +53,6 @@ ParserContext::ParserContext(const ParserContext& rhs):
         string_values_(new StringStorage(*(rhs.string_values_))),
         options_(new OptionStorage(*(rhs.options_))),
         option_defs_(new OptionDefStorage(*(rhs.option_defs_))),
-        hooks_libraries_(new HooksLibrariesStorage(*(rhs.hooks_libraries_))),
         universe_(rhs.universe_) {
     }
 
@@ -67,9 +68,6 @@ ParserContext::operator=(const ParserContext& rhs) {
             options_ = OptionStoragePtr(new OptionStorage(*(rhs.options_)));
             option_defs_ = 
                 OptionDefStoragePtr(new OptionDefStorage(*(rhs.option_defs_)));
-            hooks_libraries_ =
-                HooksLibrariesStoragePtr(new HooksLibrariesStorage(
-                                                    *(rhs.hooks_libraries_)));
             universe_ = rhs.universe_;
         }
         return (*this);
@@ -168,12 +166,11 @@ InterfaceListConfigParser::commit() {
 
 // ******************** HooksLibrariesParser *************************
 
-HooksLibrariesParser::HooksLibrariesParser(const std::string& param_name,
-                                           ParserContextPtr global_context)
-    : libraries_(), global_context_(global_context) {
-
-    // SanitY check on the name.
-    if (param_name != "hooks_libraries") {
+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);
     }
@@ -181,46 +178,54 @@ HooksLibrariesParser::HooksLibrariesParser(const std::string& param_name,
 
 void 
 HooksLibrariesParser::build(ConstElementPtr value) {
+    // Initialize.
+    libraries_.clear();
+    changed_ = false;
 
-    /// Extract the list of libraries.
-    HooksLibrariesStoragePtr libraries(new HooksLibrariesStorage());
+    // Extract the list of libraries.
     BOOST_FOREACH(ConstElementPtr iface, value->listValue()) {
         string libname = iface->str();
         boost::erase_all(libname, "\"");
-        libraries->push_back(libname);
-    }
-    /// @todo A two-stage process.  The first stage checks if the libraries
-    /// element has changed.  If not, nothing is done - the command
-    /// "DhcpN reload_hooks" is required to reload the same libraries (this
-    /// prevents needless reloads when anything in the configuration is
-    /// changed).
-    ///
-    /// If the libraries have changed, the next step is to validate each of the
-    /// libraries.  This should be a method on HooksManager which should create
-    /// a LibraryManager for it and call a new method "validateLibrary()".
-    /// That method will open a library (verifying that it exists) and check
-    /// version() (both that it exists and returned the right value).  If these
-    /// checks succeed, it is considered a success.  The library is closed when
-    /// the LibraryManager is deleted.
-
-    /// @TODO Validate the library list
-
-    /// The library list has changed, so store the new list. (This clears the
-    /// local pointer libraries as a side-effect, but as that is being
-    /// destroyed on exit, it is not an issue).
-    libraries_.swap(libraries);
+        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.
+    string error_libs = HooksManager::validateLibraries(libraries_);
+    if (!error_libs.empty()) {
+        isc_throw(DhcpConfigError, "hooks libraries failed to validate - "
+                  "library or libraries in error are: " + error_libs);
+    }
+*/
+    // 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.
-    /// Note that the list stored here could be empty, which will signify
-    /// no change.
-    ///
-    /// We use "swap" to reduce overhead - as this parser is being destroyed
-    /// after the commit, there is no reason to retain a pointer to the hooks
-    /// library data in it.
-    global_context_->hooks_libraries_.swap(libraries_);
+    /// Commits the list of libraries to the configuration manager storage if
+    /// the list of libraries has changed.
+    if (changed_) {
+        HooksManager::loadLibraries(libraries_);
+    }
+}
+
+// Method for testing
+void
+HooksLibrariesParser::getLibraries(std::vector<std::string>& libraries,
+                                   bool& changed) {
+    libraries = libraries_;
+    changed = changed_;
 }
 
 // **************************** OptionDataParser *************************

+ 48 - 25
src/lib/dhcpsrv/dhcp_parsers.h

@@ -23,6 +23,8 @@
 #include <dhcpsrv/subnet.h>
 #include <exceptions/exceptions.h>
 
+#include <boost/shared_ptr.hpp>
+
 #include <stdint.h>
 #include <string>
 #include <vector>
@@ -43,11 +45,6 @@ typedef OptionSpaceContainer<Subnet::OptionContainer,
 /// @brief Shared pointer to option storage.
 typedef boost::shared_ptr<OptionStorage> OptionStoragePtr;
 
-/// @brief Storage for hooks libraries
-typedef std::vector<std::string> HooksLibrariesStorage;
-/// @brief Pointer to storage for hooks libraries
-typedef boost::shared_ptr<HooksLibrariesStorage> HooksLibrariesStoragePtr;
-
 
 
 /// @brief A template class that stores named elements of a given data type.
@@ -169,13 +166,7 @@ public:
     /// 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).
-    ///
-    /// The same applies to the parser context.  The pointer is null (which
-    /// means that the isc::dhcp::HooksLibrariesParser::build method has
-    /// compared the library list in the configuration against the library
-    /// list in the HooksManager and has found no change) or it is non-null
-    /// (in which case this is the list of new library names).
-    HooksLibrariesStoragePtr hooks_libraries_;
+    boost::shared_ptr<std::vector<std::string> > hooks_libraries_;
 
     /// @brief The parsing universe of this context.
     Option::Universe universe_;
@@ -336,39 +327,71 @@ private:
 ///
 /// 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
+    /// @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.
-    /// @param GERBIL
+    ///
     /// @throw BadValue if supplied parameter name is not "hooks_libraries"
-    HooksLibrariesParser(const std::string& param_name,
-                         ParserContextPtr global_context);
+    HooksLibrariesParser(const std::string& param_name);
 
-    /// @brief parses parameters value
+    /// @brief Parses parameters value
     ///
     /// Parses configuration entry (list of parameters) and adds each element
-    /// to the hooks libraries list.
+    /// 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
+    /// @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 libraries (out) List of libraries that were specified in the
+    ///        new configuration.
+    /// @param changed (out) true if the list is different from that currently
+    ///        loaded.
+    void getLibraries(std::vector<std::string>& libraries, bool& changed);
+
 private:
-    /// List of hooks libraries.  This will be NULL if there is no change to
-    /// the list.
-    HooksLibrariesStoragePtr libraries_;
+    /// List of hooks libraries.
+    std::vector<std::string> libraries_;
 
-    /// Parsing context which contains global values, options and option 
-    /// definitions.
-    ParserContextPtr global_context_;
+    /// Indicator flagging that the list of libraries has changed.
+    bool changed_;
 };
 
 /// @brief Parser for option data value.

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

@@ -67,8 +67,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/cc/libb10-cc.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/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)
 endif
 

+ 0 - 33
src/lib/dhcpsrv/tests/cfgmgr_unittest.cc

@@ -165,7 +165,6 @@ public:
         CfgMgr::instance().deleteSubnets4();
         CfgMgr::instance().deleteSubnets6();
         CfgMgr::instance().deleteOptionDefs();
-        static_cast<void>(CfgMgr::instance().getAndClearHooksLibraries());
     }
 
     /// @brief generates interface-id option based on provided text
@@ -183,7 +182,6 @@ public:
         CfgMgr::instance().deleteSubnets4();
         CfgMgr::instance().deleteSubnets6();
         CfgMgr::instance().deleteOptionDefs();
-        static_cast<void>(CfgMgr::instance().getAndClearHooksLibraries());
     }
 };
 
@@ -579,35 +577,4 @@ TEST_F(CfgMgrTest, optionSpace6) {
 // in Dhcpv6SrvTest.selectSubnetAddr and Dhcpv6SrvTest.selectSubnetIface
 // (see src/bin/dhcp6/tests/dhcp6_srv_unittest.cc)
 
-// Checks that the hooks libraries can be set correctly.
-TEST_F(CfgMgrTest, hooksLibraries) {
-    std::vector<std::string> test_libraries;
-    test_libraries.push_back("/usr/lib/alpha.so");
-    test_libraries.push_back("/usr/lib/beta.so");
-
-    std::vector<std::string> no_libraries;
-
-    // The pointer should be empty initially.
-    boost::shared_ptr<std::vector<std::string> > config_libraries =
-        CfgMgr::instance().getAndClearHooksLibraries();
-    EXPECT_FALSE(config_libraries);
-
-    // Set the new set of libraries and get them.
-    CfgMgr::instance().setHooksLibraries(test_libraries);
-    config_libraries = CfgMgr::instance().getAndClearHooksLibraries();
-    ASSERT_TRUE(config_libraries);
-    EXPECT_TRUE(test_libraries == *config_libraries);
-
-    // Expect the get operation to have cleared the stored libraries.
-    config_libraries = CfgMgr::instance().getAndClearHooksLibraries();
-    EXPECT_FALSE(config_libraries);
-
-    // Check that the methods also work with an empty library vector.
-    CfgMgr::instance().setHooksLibraries(no_libraries);
-    config_libraries = CfgMgr::instance().getAndClearHooksLibraries();
-    ASSERT_TRUE(config_libraries);
-    EXPECT_TRUE(config_libraries->empty());
-}
-
-
 } // end of anonymous namespace

+ 105 - 70
src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc

@@ -22,18 +22,21 @@
 #include <dhcpsrv/dhcp_parsers.h>
 #include <dhcpsrv/tests/test_libraries.h>
 #include <exceptions/exceptions.h>
+#include <hooks/hooks_manager.h>
 
 #include <gtest/gtest.h>
 #include <boost/foreach.hpp>
+#include <boost/pointer_cast.hpp>
 
 #include <map>
 #include <string>
 
 using namespace std;
 using namespace isc;
-using namespace isc::dhcp;
-using namespace isc::data;
 using namespace isc::config;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::hooks;
 
 namespace {
 
@@ -277,7 +280,7 @@ public:
 
         ConfigPair config_pair;
         try {
-            // Iteraate over the config elements.
+            // Iterate over the config elements.
             const std::map<std::string, ConstElementPtr>& values_map =
                                                       config_set->mapValue();
             BOOST_FOREACH(config_pair, values_map) {
@@ -317,24 +320,34 @@ public:
 
     /// @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 raw pointer to DhcpConfigParser. Note caller is
-    /// responsible for deleting it once no longer needed.
+    ///
+    /// @return returns a shared pointer to DhcpConfigParser.
+    ///
     /// @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) {
-            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) {
-            parser  = new OptionDefListParser(config_id, 
-                                          parser_context_->option_defs_);
-        } else if (config_id.compare("hooks_libraries") == 0) {
-            parser = new HooksLibrariesParser(config_id, parser_context_);
+            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 {
             isc_throw(NotImplemented,
                 "Parser error: configuration parameter not supported: "
@@ -344,7 +357,7 @@ public:
         return (parser);
     }
 
-    /// @brief Convenicee method for parsing a configuration 
+    /// @brief Convenience method for parsing a configuration 
     /// 
     /// Given a configuration string, convert it into Elements
     /// and parse them. 
@@ -360,7 +373,8 @@ public:
         EXPECT_TRUE(json);
         if (json) {
             ConstElementPtr status = parseElementSet(json);
-            ConstElementPtr comment_ = parseAnswer(rcode_, status);
+            ConstElementPtr comment = parseAnswer(rcode_, status);
+            error_text_ = comment->stringValue();
         }
 
         return (rcode_);
@@ -424,14 +438,6 @@ public:
         return (option_ptr); 
     }
 
-    /// @brief Returns hooks libraries from the parser context
-    ///
-    /// Returns the pointer to the vector of strings from the parser context.
-    HooksLibrariesStoragePtr getHooksLibrariesPtr() {
-        return (parser_context_->hooks_libraries_);
-    }
-   
-
     /// @brief Wipes the contents of the context to allowing another parsing 
     /// during a given test if needed.
     void reset_context(){
@@ -440,10 +446,21 @@ public:
         CfgMgr::instance().deleteSubnets6();
         CfgMgr::instance().deleteOptionDefs();
         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
     ParserContextPtr parser_context_;
+
+    /// @brief Error string if the parsing failed
+    std::string error_text_;
 };
 
 /// @brief Check Basic parsing of option definitions.
@@ -471,6 +488,7 @@ TEST_F(ParseConfigTest, basicOptionDefTest) {
     int rcode = parseConfiguration(config);
     ASSERT_TRUE(rcode == 0);
 
+
     // Verify that the option definition can be retrieved.
     OptionDefinitionPtr def = getOptionDef("isc", 100); 
     ASSERT_TRUE(def);
@@ -528,41 +546,58 @@ TEST_F(ParseConfigTest, basicOptionDataTest) {
 
 };  // Anonymous namespace
 
-/// @brief Check Basic parsing of hooks libraries
-/// 
 /// These tests check basic operation of the HooksLibrariesParser.
-TEST_F(ParseConfigTest, emptyHooksLibrariesTest) {
 
-    // @todo Initialize global library context to null
+// hooks-libraries that do not contain anything.
+TEST_F(ParseConfigTest, noHooksLibrariesTest) {
 
-    // Configuration string.  This contains a valid library.
-    const std::string config =
-        "{ \"hooks_libraries\": [ "
-        "   ]"
-        "}";
+    // Configuration with hooks-libraries not present.
+    string config = "{ \"hooks-libraries\": [] }";
 
     // Verify that the configuration string parses.
     int rcode = parseConfiguration(config);
-    ASSERT_TRUE(rcode == 0);
+    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(BASIC_CALLOUT_LIBRARY));
+    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);
 
-    // @todo modify after the hooks check has been added.  At the moment, the
-    // string should parse to an empty string.
-    HooksLibrariesStoragePtr ptr = getHooksLibrariesPtr();
-    EXPECT_TRUE(ptr);
-    EXPECT_EQ(0, ptr->size());
 }
 
-TEST_F(ParseConfigTest, validHooksLibrariesTest) {
 
-    // @todo Initialize global library context to null
+TEST_F(ParseConfigTest, validHooksLibrariesTest) {
 
-    // Configuration string.  This contains a valid library.
+    // 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\": [") +
+            std::string("\"hooks-libraries\": [") +
                 quote + std::string(BASIC_CALLOUT_LIBRARY) + quote + comma +
                 quote + std::string(FULL_CALLOUT_LIBRARY)  + quote +
             std::string("]") +
@@ -570,24 +605,34 @@ TEST_F(ParseConfigTest, validHooksLibrariesTest) {
 
     // Verify that the configuration string parses.
     int rcode = parseConfiguration(config);
-    ASSERT_TRUE(rcode == 0);
+    ASSERT_TRUE(rcode == 0) << error_text_;
 
-    // @todo modify after the hooks check has been added.  At the moment, the
-    // string should parse to an empty string.
-    HooksLibrariesStoragePtr ptr = getHooksLibrariesPtr();
-    EXPECT_TRUE(ptr);
+    // 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(BASIC_CALLOUT_LIBRARY);
     expected.push_back(FULL_CALLOUT_LIBRARY);
+    EXPECT_TRUE(expected == libraries);
+
+    // Parse the string again.
+    rcode = parseConfiguration(config);
+    ASSERT_TRUE(rcode == 0) << error_text_;
 
-    ASSERT_TRUE(ptr);
-    EXPECT_TRUE(expected == *ptr);
+    // 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);
 }
 
-// Now parse
+// Check with a set of libraries, some of which are invalid.
 TEST_F(ParseConfigTest, invalidHooksLibrariesTest) {
 
     // @todo Initialize global library context to null
@@ -599,29 +644,19 @@ TEST_F(ParseConfigTest, invalidHooksLibrariesTest) {
 
     const std::string config =
         std::string("{ ") +
-            std::string("\"hooks_libraries\": [") +
+            std::string("\"hooks-libraries\": [") +
                 quote + std::string(BASIC_CALLOUT_LIBRARY) + quote + comma +
                 quote + std::string(NOT_PRESENT_LIBRARY) + quote + comma +
                 quote + std::string(FULL_CALLOUT_LIBRARY)  + quote +
             std::string("]") +
         std::string("}");
 
-    // Verify that the configuration string parses.
+    // Verify that the configuration fails to parse. (Syntactically it's OK,
+    // but the library is invalid).
     int rcode = parseConfiguration(config);
-    ASSERT_TRUE(rcode == 0);
-
-    // @todo modify after the hooks check has been added.  At the moment, the
-    // string should parse to an empty string.
-    HooksLibrariesStoragePtr ptr = getHooksLibrariesPtr();
-    EXPECT_TRUE(ptr);
-
-    // The expected libraries should be the list of libraries specified
-    // in the given order.
-    std::vector<std::string> expected;
-    expected.push_back(BASIC_CALLOUT_LIBRARY);
-    expected.push_back(NOT_PRESENT_LIBRARY);
-    expected.push_back(FULL_CALLOUT_LIBRARY);
+    ASSERT_FALSE(rcode == 0) << error_text_;
 
-    ASSERT_TRUE(ptr);
-    EXPECT_TRUE(expected == *ptr);
+    // 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_;
 }

+ 7 - 0
src/lib/hooks/hooks_manager.h

@@ -86,6 +86,13 @@ public:
     ///         In the latter case, an error message will have been output.
     static void unloadLibraries();
 
+    /// @brief Reload libraries
+    ///
+    /// Reloads the current libraries.  This causes all the libraries' "unload"
+    /// functions to run, the libraries to be closed, reopened, and all the
+    /// "load" functions run.
+    static void reloadLibraries();
+
     /// @brief Are callouts present?
     ///
     /// Checks loaded libraries and returns true if at lease one callout