Commit aff6b06b authored by Stephen Morris's avatar Stephen Morris
Browse files

[master] Merge branch 'trac2981'

parents abdd9be8 f73cd9bc
......@@ -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
......
......@@ -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());
......
......@@ -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,
......
......@@ -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": []
}
]
}
}
......@@ -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
......
......@@ -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)
......
// 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"
// 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"
// 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));
}
};
......@@ -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) {