BIND 10 master, updated. 6ce7437c65b714361b3c100154af4c352859bd13 [master] ChangeLog for #2981
BIND 10 source code commits
bind10-changes at lists.isc.org
Thu Aug 15 14:44:43 UTC 2013
The branch, master has been updated
via 6ce7437c65b714361b3c100154af4c352859bd13 (commit)
via aff6b06b2490fe4fa6568e7575a9a9105cfd7fae (commit)
via f73cd9bc4a0bbc14f106cec09d3843bfec1971e9 (commit)
via 83645c8498c57f27907113a4d062fe47f74739d2 (commit)
via f8e9c2933fd726cb1499fc03c480c0518e23a89a (commit)
via 578910f51f1dc96876a67a02d353c396cc4d2ec8 (commit)
via a6cd22451be6684f4bebbc34d5344371afdeaa4b (commit)
via aac023f8e10f6922400400d614d0a0b141e79c53 (commit)
via c7b293f631c53764081428f4723b8c30b5a85fa8 (commit)
via e11e8c1488cf3e418fc264f5121d1ef711118264 (commit)
via be68664ae1064cad2e9440fe89511b6166755424 (commit)
via 3c285e7ef1b8890caa12ea83353b2e03f2bec23e (commit)
via c706b28ae11ad17fde72a2f06a2cdf17f62a5514 (commit)
via 721eff49ce716e79b898ad578e21efb96e684684 (commit)
via ed363b36987c46c37452c3e83b9efce0a5117cf1 (commit)
from abdd9be83ca3ec7267e4d04f014584fa823f7f6c (commit)
Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.
- Log -----------------------------------------------------------------
commit 6ce7437c65b714361b3c100154af4c352859bd13
Author: Stephen Morris <stephen at isc.org>
Date: Thu Aug 15 15:44:14 2013 +0100
[master] ChangeLog for #2981
commit aff6b06b2490fe4fa6568e7575a9a9105cfd7fae
Merge: abdd9be f73cd9b
Author: Stephen Morris <stephen at isc.org>
Date: Thu Aug 15 15:30:43 2013 +0100
[master] Merge branch 'trac2981'
-----------------------------------------------------------------------
Summary of changes:
ChangeLog | 5 +
configure.ac | 5 +
src/bin/dhcp4/config_parser.cc | 20 ++
src/bin/dhcp4/ctrl_dhcp4_srv.cc | 25 +-
src/bin/dhcp4/dhcp4.spec | 21 ++
src/bin/dhcp4/dhcp4_messages.mes | 5 +
src/bin/dhcp4/tests/Makefile.am | 13 +
.../dhcp4/tests/callout_library_1.cc} | 17 +-
.../dhcp4/tests/callout_library_2.cc} | 17 +-
src/bin/dhcp4/tests/callout_library_common.h | 82 ++++++
src/bin/dhcp4/tests/config_parser_unittest.cc | 251 +++++++++++++++---
src/bin/dhcp4/tests/ctrl_dhcp4_srv_unittest.cc | 70 ++++-
src/bin/dhcp4/tests/marker_file.cc | 66 +++++
src/bin/dhcp4/tests/marker_file.h.in | 69 +++++
src/bin/dhcp4/tests/test_libraries.h.in | 51 ++++
src/bin/dhcp6/config_parser.cc | 21 ++
src/bin/dhcp6/ctrl_dhcp6_srv.cc | 19 ++
src/bin/dhcp6/dhcp6.spec | 20 ++
src/bin/dhcp6/dhcp6_messages.mes | 5 +
src/bin/dhcp6/tests/Makefile.am | 21 +-
.../dhcp6/tests/callout_library_1.cc} | 17 +-
.../dhcp6/tests/callout_library_2.cc} | 17 +-
src/bin/dhcp6/tests/callout_library_common.h | 82 ++++++
src/bin/dhcp6/tests/config_parser_unittest.cc | 271 +++++++++++++++++---
src/bin/dhcp6/tests/ctrl_dhcp6_srv_unittest.cc | 84 +++++-
src/bin/dhcp6/tests/marker_file.cc | 66 +++++
src/bin/dhcp6/tests/marker_file.h.in | 69 +++++
src/bin/dhcp6/tests/test_libraries.h.in | 51 ++++
src/lib/dhcpsrv/Makefile.am | 2 +
src/lib/dhcpsrv/dhcp_parsers.cc | 79 +++++-
src/lib/dhcpsrv/dhcp_parsers.h | 89 ++++++-
src/lib/dhcpsrv/tests/Makefile.am | 16 +-
.../tests/callout_library.cc} | 22 +-
src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc | 181 +++++++++++--
src/lib/dhcpsrv/tests/test_libraries.h.in | 51 ++++
35 files changed, 1733 insertions(+), 167 deletions(-)
copy src/{lib/hooks/tests/marker_file.h.in => bin/dhcp4/tests/callout_library_1.cc} (76%)
copy src/{lib/hooks/tests/marker_file.h.in => bin/dhcp4/tests/callout_library_2.cc} (76%)
create mode 100644 src/bin/dhcp4/tests/callout_library_common.h
create mode 100644 src/bin/dhcp4/tests/marker_file.cc
create mode 100644 src/bin/dhcp4/tests/marker_file.h.in
create mode 100644 src/bin/dhcp4/tests/test_libraries.h.in
copy src/{lib/hooks/tests/marker_file.h.in => bin/dhcp6/tests/callout_library_1.cc} (76%)
copy src/{lib/hooks/tests/marker_file.h.in => bin/dhcp6/tests/callout_library_2.cc} (76%)
create mode 100644 src/bin/dhcp6/tests/callout_library_common.h
create mode 100644 src/bin/dhcp6/tests/marker_file.cc
create mode 100644 src/bin/dhcp6/tests/marker_file.h.in
create mode 100644 src/bin/dhcp6/tests/test_libraries.h.in
copy src/lib/{hooks/tests/marker_file.h.in => dhcpsrv/tests/callout_library.cc} (75%)
create mode 100644 src/lib/dhcpsrv/tests/test_libraries.h.in
-----------------------------------------------------------------------
diff --git a/ChangeLog b/ChangeLog
index d371062..1ce3959 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,8 @@
+659. [func] stephen
+ Added capability to configure the hooks libraries for the b10-dhcp4 and
+ b10-dhcp6 servers through the BIND 10 configuration mechanism.
+ (Trac #2981, git aff6b06b2490fe4fa6568e7575a9a9105cfd7fae)
+
658. [func]* vorner
The resolver, being experimental, is no longer installed by default.
If you really want to use it, even when it is known to be buggy, use
diff --git a/configure.ac b/configure.ac
index 7857fd9..35bc5b4 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1413,6 +1413,10 @@ 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/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/xfrin.py
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/tests/process_rename_test.py
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/cc/tests/cc_test
src/lib/python/isc/notify/tests/notify_out_test
diff --git a/src/bin/dhcp4/config_parser.cc b/src/bin/dhcp4/config_parser.cc
index 726a4a6..98393c1 100644
--- a/src/bin/dhcp4/config_parser.cc
+++ b/src/bin/dhcp4/config_parser.cc
@@ -364,6 +364,8 @@ DhcpConfigParser* createGlobalDhcp4ConfigParser(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: "
@@ -399,6 +401,11 @@ configureDhcp4Server(Dhcpv4Srv&, isc::data::ConstElementPtr config_set) {
ParserPtr option_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
// accessing global storage. For this reason the global data
// 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.
iface_parser = parser;
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 {
// Those parsers should be started before other
// parsers so we can call build straight away.
@@ -493,6 +506,13 @@ configureDhcp4Server(Dhcpv4Srv&, isc::data::ConstElementPtr config_set) {
if (iface_parser) {
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) {
LOG_ERROR(dhcp4_logger, DHCP4_PARSER_COMMIT_FAIL).arg(ex.what());
diff --git a/src/bin/dhcp4/ctrl_dhcp4_srv.cc b/src/bin/dhcp4/ctrl_dhcp4_srv.cc
index 46a7ca0..43c08c9 100644
--- a/src/bin/dhcp4/ctrl_dhcp4_srv.cc
+++ b/src/bin/dhcp4/ctrl_dhcp4_srv.cc
@@ -19,24 +19,28 @@
#include <cc/session.h>
#include <config/ccsession.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/dhcp4_log.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 <hooks/hooks_manager.h>
#include <util/buffer.h>
#include <cassert>
#include <iostream>
#include <sstream>
+#include <string>
+#include <vector>
using namespace isc::asiolink;
using namespace isc::cc;
using namespace isc::config;
using namespace isc::data;
using namespace isc::dhcp;
+using namespace isc::hooks;
using namespace isc::log;
using namespace isc::util;
using namespace std;
@@ -141,6 +145,21 @@ ControlledDhcpv4Srv::dhcp4CommandHandler(const string& command, ConstElementPtr
ConstElementPtr answer = isc::config::createAnswer(0,
"Shutting down.");
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,
diff --git a/src/bin/dhcp4/dhcp4.spec b/src/bin/dhcp4/dhcp4.spec
index e940cb7..b979d45 100644
--- a/src/bin/dhcp4/dhcp4.spec
+++ b/src/bin/dhcp4/dhcp4.spec
@@ -3,6 +3,20 @@
"module_name": "Dhcp4",
"module_description": "DHCPv4 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": false,
+ "item_default": ""
+ }
+ },
+
{ "item_name": "interfaces",
"item_type": "list",
"item_optional": false,
@@ -272,7 +286,14 @@
"item_optional": true
}
]
+ },
+
+ {
+ "command_name": "libreload",
+ "command_description": "Reloads the current hooks libraries.",
+ "command_args": []
}
+
]
}
}
diff --git a/src/bin/dhcp4/dhcp4_messages.mes b/src/bin/dhcp4/dhcp4_messages.mes
index f70dde3..8d3252f 100644
--- a/src/bin/dhcp4/dhcp4_messages.mes
+++ b/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
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)
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
diff --git a/src/bin/dhcp4/tests/Makefile.am b/src/bin/dhcp4/tests/Makefile.am
index d3c7171..fc7eabf 100644
--- a/src/bin/dhcp4/tests/Makefile.am
+++ b/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\"
CLEANFILES = $(builddir)/interfaces.txt $(builddir)/logger_lockfile
+CLEANFILES += $(builddir)/load_marker.txt $(builddir)/unload_marker.txt
AM_CXXFLAGS = $(B10_CXXFLAGS)
if USE_CLANGPP
@@ -48,6 +49,16 @@ TESTS_ENVIRONMENT = \
TESTS =
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
@@ -58,7 +69,9 @@ dhcp4_unittests_SOURCES += dhcp4_unittests.cc
dhcp4_unittests_SOURCES += dhcp4_srv_unittest.cc
dhcp4_unittests_SOURCES += ctrl_dhcp4_srv_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 += marker_file.h test_libraries.h
dhcp4_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
dhcp4_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
diff --git a/src/bin/dhcp4/tests/callout_library_1.cc b/src/bin/dhcp4/tests/callout_library_1.cc
new file mode 100644
index 0000000..471bb6f
--- /dev/null
+++ b/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"
diff --git a/src/bin/dhcp4/tests/callout_library_2.cc b/src/bin/dhcp4/tests/callout_library_2.cc
new file mode 100644
index 0000000..b0b4637
--- /dev/null
+++ b/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"
diff --git a/src/bin/dhcp4/tests/callout_library_common.h b/src/bin/dhcp4/tests/callout_library_common.h
new file mode 100644
index 0000000..cbabcda
--- /dev/null
+++ b/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));
+}
+
+};
diff --git a/src/bin/dhcp4/tests/config_parser_unittest.cc b/src/bin/dhcp4/tests/config_parser_unittest.cc
index 05c951d..7c01145 100644
--- a/src/bin/dhcp4/tests/config_parser_unittest.cc
+++ b/src/bin/dhcp4/tests/config_parser_unittest.cc
@@ -25,6 +25,10 @@
#include <dhcp/option_int.h>
#include <dhcpsrv/subnet.h>
#include <dhcpsrv/cfgmgr.h>
+#include <hooks/hooks_manager.h>
+
+#include "marker_file.h"
+#include "test_libraries.h"
#include <boost/foreach.hpp>
#include <boost/scoped_ptr.hpp>
@@ -34,12 +38,14 @@
#include <sstream>
#include <limits.h>
-using namespace std;
using namespace isc;
-using namespace isc::dhcp;
using namespace isc::asiolink;
-using namespace isc::data;
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 {
@@ -54,6 +60,15 @@ public:
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
void checkGlobalUint32(string name, uint32_t expected_value) {
const Uint32StoragePtr uint32_defaults =
@@ -78,6 +93,10 @@ public:
~Dhcp4ParserTest() {
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.
@@ -236,56 +255,81 @@ public:
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;
-
- string config = "{ \"interfaces\": [ \"*\" ],"
- "\"rebind-timer\": 2000, "
- "\"renew-timer\": 1000, "
- "\"valid-lifetime\": 4000, "
- "\"subnet4\": [ ], "
- "\"option-def\": [ ], "
- "\"option-data\": [ ] }";
-
try {
ElementPtr json = Element::fromJSON(config);
status = configureDhcp4Server(*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 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
@@ -1750,6 +1794,147 @@ TEST_F(Dhcp4ParserTest, stdOptionDataEncapsulate) {
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
// on which server should listen.
TEST_F(Dhcp4ParserTest, selectedInterfaces) {
diff --git a/src/bin/dhcp4/tests/ctrl_dhcp4_srv_unittest.cc b/src/bin/dhcp4/tests/ctrl_dhcp4_srv_unittest.cc
index 13b57be..e6b500b 100644
--- a/src/bin/dhcp4/tests/ctrl_dhcp4_srv_unittest.cc
+++ b/src/bin/dhcp4/tests/ctrl_dhcp4_srv_unittest.cc
@@ -17,6 +17,10 @@
#include <config/ccsession.h>
#include <dhcp/dhcp4.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 <gtest/gtest.h>
@@ -26,13 +30,16 @@
#include <sstream>
#include <arpa/inet.h>
+#include <unistd.h>
using namespace std;
using namespace isc;
-using namespace isc::dhcp;
using namespace isc::asiolink;
-using namespace isc::data;
using namespace isc::config;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+using namespace isc::hooks;
namespace {
@@ -45,10 +52,25 @@ public:
class CtrlDhcpv4SrvTest : public ::testing::Test {
public:
CtrlDhcpv4SrvTest() {
+ reset();
}
~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) {
@@ -82,4 +104,48 @@ TEST_F(CtrlDhcpv4SrvTest, commands) {
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
diff --git a/src/bin/dhcp4/tests/marker_file.cc b/src/bin/dhcp4/tests/marker_file.cc
new file mode 100644
index 0000000..d1c4aba
--- /dev/null
+++ b/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
diff --git a/src/bin/dhcp4/tests/marker_file.h.in b/src/bin/dhcp4/tests/marker_file.h.in
new file mode 100644
index 0000000..52fc006
--- /dev/null
+++ b/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
+
diff --git a/src/bin/dhcp4/tests/test_libraries.h.in b/src/bin/dhcp4/tests/test_libraries.h.in
new file mode 100644
index 0000000..8b03dc2
--- /dev/null
+++ b/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
diff --git a/src/bin/dhcp6/config_parser.cc b/src/bin/dhcp6/config_parser.cc
index cc6bd02..63bda52 100644
--- a/src/bin/dhcp6/config_parser.cc
+++ b/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: "
@@ -465,6 +467,11 @@ configureDhcp6Server(Dhcpv6Srv&, isc::data::ConstElementPtr config_set) {
ParserPtr option_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
// accessing global storage. For this reason the global data
// parsers must store the parsed data into global storages
@@ -496,6 +503,13 @@ 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 if (config_pair.first == "interfaces") {
// The interface parser is independent from any other parser and
// can be run here before other parsers.
@@ -558,6 +572,13 @@ configureDhcp6Server(Dhcpv6Srv&, isc::data::ConstElementPtr config_set) {
if (iface_parser) {
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) {
LOG_ERROR(dhcp6_logger, DHCP6_PARSER_COMMIT_FAIL).arg(ex.what());
diff --git a/src/bin/dhcp6/ctrl_dhcp6_srv.cc b/src/bin/dhcp6/ctrl_dhcp6_srv.cc
index dffac61..7168b91 100644
--- a/src/bin/dhcp6/ctrl_dhcp6_srv.cc
+++ b/src/bin/dhcp6/ctrl_dhcp6_srv.cc
@@ -26,16 +26,20 @@
#include <dhcp6/dhcp6_log.h>
#include <dhcp6/spec_config.h>
#include <exceptions/exceptions.h>
+#include <hooks/hooks_manager.h>
#include <util/buffer.h>
#include <cassert>
#include <iostream>
+#include <string>
+#include <vector>
using namespace isc::asiolink;
using namespace isc::cc;
using namespace isc::config;
using namespace isc::data;
using namespace isc::dhcp;
+using namespace isc::hooks;
using namespace isc::log;
using namespace isc::util;
using namespace std;
@@ -141,6 +145,21 @@ ControlledDhcpv6Srv::dhcp6CommandHandler(const string& command, ConstElementPtr
ConstElementPtr answer = isc::config::createAnswer(0,
"Shutting down.");
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,
diff --git a/src/bin/dhcp6/dhcp6.spec b/src/bin/dhcp6/dhcp6.spec
index b61e811..634b046 100644
--- a/src/bin/dhcp6/dhcp6.spec
+++ b/src/bin/dhcp6/dhcp6.spec
@@ -3,6 +3,20 @@
"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": false,
+ "item_default": ""
+ }
+ },
+
{ "item_name": "interfaces",
"item_type": "list",
"item_optional": false,
@@ -295,6 +309,12 @@
"item_optional": true
}
]
+ },
+
+ {
+ "command_name": "libreload",
+ "command_description": "Reloads the current hooks libraries.",
+ "command_args": []
}
]
}
diff --git a/src/bin/dhcp6/dhcp6_messages.mes b/src/bin/dhcp6/dhcp6_messages.mes
index f66fc54..c936f68 100644
--- a/src/bin/dhcp6/dhcp6_messages.mes
+++ b/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 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)
This debug message indicates that the server successfully advertised
a lease. It is up to the client to choose one server out of the
diff --git a/src/bin/dhcp6/tests/Makefile.am b/src/bin/dhcp6/tests/Makefile.am
index 293173c..928598e 100644
--- a/src/bin/dhcp6/tests/Makefile.am
+++ b/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,20 +45,32 @@ 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 += hooks_unittest.cc
dhcp6_unittests_SOURCES += dhcp6_test_utils.h
dhcp6_unittests_SOURCES += ctrl_dhcp6_srv_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_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)
@@ -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/dhcp/libb10-dhcp++.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/log/libb10-log.la
dhcp6_unittests_LDADD += $(top_builddir)/src/lib/util/libb10-util.la
-dhcp6_unittests_LDADD += $(top_builddir)/src/lib/hooks/libb10-hooks.la
endif
noinst_PROGRAMS = $(TESTS)
diff --git a/src/bin/dhcp6/tests/callout_library_1.cc b/src/bin/dhcp6/tests/callout_library_1.cc
new file mode 100644
index 0000000..471bb6f
--- /dev/null
+++ b/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"
diff --git a/src/bin/dhcp6/tests/callout_library_2.cc b/src/bin/dhcp6/tests/callout_library_2.cc
new file mode 100644
index 0000000..b0b4637
--- /dev/null
+++ b/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"
diff --git a/src/bin/dhcp6/tests/callout_library_common.h b/src/bin/dhcp6/tests/callout_library_common.h
new file mode 100644
index 0000000..cbabcda
--- /dev/null
+++ b/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));
+}
+
+};
diff --git a/src/bin/dhcp6/tests/config_parser_unittest.cc b/src/bin/dhcp6/tests/config_parser_unittest.cc
index cda9066..0c46d26 100644
--- a/src/bin/dhcp6/tests/config_parser_unittest.cc
+++ b/src/bin/dhcp6/tests/config_parser_unittest.cc
@@ -24,22 +24,32 @@
#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;
-using namespace isc::dhcp;
using namespace isc::asiolink;
-using namespace isc::data;
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 {
@@ -71,7 +81,22 @@ public:
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() {
+ // 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
@@ -170,51 +195,75 @@ public:
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;
-
- string config = "{ \"interfaces\": [ \"*\" ],"
- "\"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 = "{ \"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
// properly test interface configuration we disable listening on
// all interfaces before each test and later check that this setting
@@ -284,13 +333,11 @@ public:
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
@@ -1857,6 +1904,154 @@ TEST_F(Dhcp6ParserTest, stdOptionDataEncapsulate) {
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
// which server should listen.
TEST_F(Dhcp6ParserTest, selectedInterfaces) {
diff --git a/src/bin/dhcp6/tests/ctrl_dhcp6_srv_unittest.cc b/src/bin/dhcp6/tests/ctrl_dhcp6_srv_unittest.cc
index 9209de6..6dfb981 100644
--- a/src/bin/dhcp6/tests/ctrl_dhcp6_srv_unittest.cc
+++ b/src/bin/dhcp6/tests/ctrl_dhcp6_srv_unittest.cc
@@ -14,30 +14,37 @@
#include <config.h>
+#include <config/ccsession.h>
#include <dhcp/dhcp6.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 <gtest/gtest.h>
-#include <iostream>
#include <fstream>
+#include <iostream>
#include <sstream>
#include <arpa/inet.h>
+#include <unistd.h>
using namespace std;
using namespace isc;
-using namespace isc::dhcp;
using namespace isc::asiolink;
-using namespace isc::data;
using namespace isc::config;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+using namespace isc::hooks;
namespace {
class NakedControlledDhcpv6Srv: public ControlledDhcpv6Srv {
- // "naked" DHCPv6 server, exposes internal fields
+ // "Naked" DHCPv6 server, exposes internal fields
public:
NakedControlledDhcpv6Srv():ControlledDhcpv6Srv(DHCP6_SERVER_PORT + 10000) { }
};
@@ -45,10 +52,25 @@ public:
class CtrlDhcpv6SrvTest : public ::testing::Test {
public:
CtrlDhcpv6SrvTest() {
+ reset();
}
~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) {
@@ -62,12 +84,12 @@ TEST_F(CtrlDhcpv6SrvTest, commands) {
ElementPtr params(new isc::data::MapElement());
int rcode = -1;
- // case 1: send bogus command
+ // Case 1: send bogus command
ConstElementPtr result = ControlledDhcpv6Srv::execDhcpv6ServerCommand("blah", params);
ConstElementPtr comment = parseAnswer(rcode, result);
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);
comment = parseAnswer(rcode, result);
EXPECT_EQ(0, rcode); // expect success
@@ -76,10 +98,54 @@ TEST_F(CtrlDhcpv6SrvTest, commands) {
ConstElementPtr x(new isc::data::IntElement(pid));
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);
comment = parseAnswer(rcode, result);
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
diff --git a/src/bin/dhcp6/tests/marker_file.cc b/src/bin/dhcp6/tests/marker_file.cc
new file mode 100644
index 0000000..d1c4aba
--- /dev/null
+++ b/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
diff --git a/src/bin/dhcp6/tests/marker_file.h.in b/src/bin/dhcp6/tests/marker_file.h.in
new file mode 100644
index 0000000..52fc006
--- /dev/null
+++ b/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
+
diff --git a/src/bin/dhcp6/tests/test_libraries.h.in b/src/bin/dhcp6/tests/test_libraries.h.in
new file mode 100644
index 0000000..8b03dc2
--- /dev/null
+++ b/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
diff --git a/src/lib/dhcpsrv/Makefile.am b/src/lib/dhcpsrv/Makefile.am
index 0925182..8641f4f 100644
--- a/src/lib/dhcpsrv/Makefile.am
+++ b/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_LIBADD += $(top_builddir)/src/lib/hooks/libb10-hooks.la
diff --git a/src/lib/dhcpsrv/dhcp_parsers.cc b/src/lib/dhcpsrv/dhcp_parsers.cc
index 52c226b..fb1b3a0 100644
--- a/src/lib/dhcpsrv/dhcp_parsers.cc
+++ b/src/lib/dhcpsrv/dhcp_parsers.cc
@@ -16,19 +16,21 @@
#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/foreach.hpp>
-#include <boost/lexical_cast.hpp>
#include <boost/algorithm/string.hpp>
+#include <boost/foreach.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 {
@@ -215,6 +217,77 @@ InterfaceListConfigParser::isIfaceAdded(const std::string& iface) const {
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(const std::string&, OptionStoragePtr options,
ParserContextPtr global_context)
diff --git a/src/lib/dhcpsrv/dhcp_parsers.h b/src/lib/dhcpsrv/dhcp_parsers.h
index 37dc950..950deb2 100644
--- a/src/lib/dhcpsrv/dhcp_parsers.h
+++ b/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>
@@ -33,7 +35,6 @@ namespace dhcp {
/// @brief Storage for option definitions.
typedef OptionSpaceContainer<OptionDefContainer,
OptionDefinitionPtr> OptionDefStorage;
-
/// @brief Shared pointer to option definitions storage.
typedef boost::shared_ptr<OptionDefStorage> OptionDefStoragePtr;
@@ -44,6 +45,8 @@ typedef OptionSpaceContainer<Subnet::OptionContainer,
/// @brief Shared pointer to option storage.
typedef boost::shared_ptr<OptionStorage> OptionStoragePtr;
+
+
/// @brief A template class that stores named elements of a given data type.
///
/// This template class is provides data value storage for configuration parameters
@@ -151,6 +154,20 @@ public:
/// @brief Storage for option definitions.
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.
Option::Universe universe_;
@@ -321,6 +338,76 @@ private:
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.
///
diff --git a/src/lib/dhcpsrv/tests/Makefile.am b/src/lib/dhcpsrv/tests/Makefile.am
index 238cef6..0ea5591 100644
--- a/src/lib/dhcpsrv/tests/Makefile.am
+++ b/src/lib/dhcpsrv/tests/Makefile.am
@@ -24,6 +24,18 @@ TESTS_ENVIRONMENT = \
TESTS =
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
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/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/log/libb10-log.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
diff --git a/src/lib/dhcpsrv/tests/callout_library.cc b/src/lib/dhcpsrv/tests/callout_library.cc
new file mode 100644
index 0000000..09da3cd
--- /dev/null
+++ b/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);
+}
+
+};
diff --git a/src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc b/src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc
index 6704c57..3da0584 100644
--- a/src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc
+++ b/src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc
@@ -20,19 +20,23 @@
#include <dhcpsrv/cfgmgr.h>
#include <dhcpsrv/subnet.h>
#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 {
@@ -308,7 +312,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) {
@@ -348,22 +352,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 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.
- 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_);
+ 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: "
@@ -373,15 +389,15 @@ 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.
/// @param config is the configuration string to parse
///
/// @return retuns 0 if the configuration parsed successfully,
/// non-zero otherwise failure.
- int parseConfiguration (std::string &config) {
+ int parseConfiguration(const std::string& config) {
int rcode_ = 1;
// Turn config into elements.
// Test json just to make sure its valid.
@@ -389,7 +405,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_);
@@ -461,10 +478,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.
@@ -492,6 +520,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);
@@ -549,3 +578,117 @@ TEST_F(ParseConfigTest, basicOptionDataTest) {
}; // 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_;
+}
diff --git a/src/lib/dhcpsrv/tests/test_libraries.h.in b/src/lib/dhcpsrv/tests/test_libraries.h.in
new file mode 100644
index 0000000..b5e80a0
--- /dev/null
+++ b/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
More information about the bind10-changes
mailing list