mirror of
https://gitlab.isc.org/isc-projects/kea
synced 2025-08-30 21:45:37 +00:00
[3400] Changes after review:
- config-reload command added - config and command handlers cleaned up - files renamed - Majority of the backend code is now common - Added unit-test for config-reload command
This commit is contained in:
@@ -54,14 +54,15 @@ pkglibexec_PROGRAMS = b10-dhcp6
|
||||
b10_dhcp6_SOURCES = main.cc
|
||||
b10_dhcp6_SOURCES += dhcp6_log.cc dhcp6_log.h
|
||||
b10_dhcp6_SOURCES += dhcp6_srv.cc dhcp6_srv.h
|
||||
b10_dhcp6_SOURCES += ctrl_dhcp6_srv.h
|
||||
b10_dhcp6_SOURCES += ctrl_dhcp6_srv.cc ctrl_dhcp6_srv.h
|
||||
b10_dhcp6_SOURCES += json_config_parser.cc json_config_parser.h
|
||||
|
||||
if CONFIG_BACKEND_BIND10
|
||||
b10_dhcp6_SOURCES += json_config_parser.cc json_config_parser.h ctrl_bind10_dhcp6_srv.cc
|
||||
b10_dhcp6_SOURCES += bundy_backend.cc
|
||||
endif
|
||||
|
||||
if CONFIG_BACKEND_JSON
|
||||
b10_dhcp6_SOURCES += json_config_parser.cc json_config_parser.h ctrl_json_dhcp6_srv.cc
|
||||
b10_dhcp6_SOURCES += jsonfile_backend.cc
|
||||
endif
|
||||
|
||||
nodist_b10_dhcp6_SOURCES = dhcp6_messages.h dhcp6_messages.cc
|
||||
|
@@ -47,10 +47,33 @@ using namespace std;
|
||||
namespace isc {
|
||||
namespace dhcp {
|
||||
|
||||
ControlledDhcpv6Srv* ControlledDhcpv6Srv::server_ = NULL;
|
||||
/// @brief Helper session object that represents raw connection to msgq.
|
||||
isc::cc::Session* cc_session_ = NULL;
|
||||
|
||||
/// @brief Session that receives configuration and commands
|
||||
isc::config::ModuleCCSession* config_session_ = NULL;
|
||||
|
||||
/// @brief A dummy configuration handler that always returns success.
|
||||
///
|
||||
/// This configuration handler does not perform configuration
|
||||
/// parsing and always returns success. A dummy handler should
|
||||
/// be installed using \ref isc::config::ModuleCCSession ctor
|
||||
/// to get the initial configuration. This initial configuration
|
||||
/// comprises values for only those elements that were modified
|
||||
/// the previous session. The \ref dhcp6ConfigHandler can't be
|
||||
/// used to parse the initial configuration because it needs the
|
||||
/// full configuration to satisfy dependencies between the
|
||||
/// various configuration values. Installing the dummy handler
|
||||
/// that guarantees to return success causes initial configuration
|
||||
/// to be stored for the session being created and that it can
|
||||
/// be later accessed with
|
||||
/// \ref isc::config::ConfigData::getFullConfig().
|
||||
///
|
||||
/// @param new_config new configuration.
|
||||
///
|
||||
/// @return success configuration status.
|
||||
ConstElementPtr
|
||||
ControlledDhcpv6Srv::dhcp6StubConfigHandler(ConstElementPtr) {
|
||||
dhcp6StubConfigHandler(ConstElementPtr) {
|
||||
// This configuration handler is intended to be used only
|
||||
// when the initial configuration comes in. To receive this
|
||||
// configuration a pointer to this handler must be passed
|
||||
@@ -65,9 +88,9 @@ ControlledDhcpv6Srv::dhcp6StubConfigHandler(ConstElementPtr) {
|
||||
}
|
||||
|
||||
ConstElementPtr
|
||||
ControlledDhcpv6Srv::dhcp6ConfigHandler(ConstElementPtr new_config) {
|
||||
bundyConfigHandler(ConstElementPtr new_config) {
|
||||
|
||||
if (!server_ || !server_->config_session_) {
|
||||
if (!ControlledDhcpv6Srv::getInstance() || !config_session_) {
|
||||
// That should never happen as we install config_handler
|
||||
// after we instantiate the server.
|
||||
ConstElementPtr answer =
|
||||
@@ -91,7 +114,7 @@ ControlledDhcpv6Srv::dhcp6ConfigHandler(ConstElementPtr new_config) {
|
||||
// Let's create a new object that will hold the merged configuration.
|
||||
boost::shared_ptr<MapElement> merged_config(new MapElement());
|
||||
// Let's get the existing configuration.
|
||||
ConstElementPtr full_config = server_->config_session_->getFullConfig();
|
||||
ConstElementPtr full_config = config_session_->getFullConfig();
|
||||
// The full_config and merged_config should be always non-NULL
|
||||
// but to provide some level of exception safety we check that they
|
||||
// really are (in case we go out of memory).
|
||||
@@ -105,92 +128,14 @@ ControlledDhcpv6Srv::dhcp6ConfigHandler(ConstElementPtr new_config) {
|
||||
}
|
||||
|
||||
// Configure the server.
|
||||
ConstElementPtr answer = configureDhcp6Server(*server_, merged_config);
|
||||
|
||||
// Check that configuration was successful. If not, do not reopen sockets.
|
||||
int rcode = 0;
|
||||
parseAnswer(rcode, answer);
|
||||
if (rcode != 0) {
|
||||
return (answer);
|
||||
}
|
||||
|
||||
// Server will start DDNS communications if its enabled.
|
||||
try {
|
||||
server_->startD2();
|
||||
} catch (const std::exception& ex) {
|
||||
std::ostringstream err;
|
||||
err << "error starting DHCP_DDNS client "
|
||||
" after server reconfiguration: " << ex.what();
|
||||
return (isc::config::createAnswer(1, err.str()));
|
||||
}
|
||||
|
||||
// Configuration may change active interfaces. Therefore, we have to reopen
|
||||
// sockets according to new configuration. This operation is not exception
|
||||
// safe and we really don't want to emit exceptions to the callback caller.
|
||||
// Instead, catch an exception and create appropriate answer.
|
||||
try {
|
||||
server_->openActiveSockets(server_->getPort());
|
||||
} catch (const std::exception& ex) {
|
||||
std::ostringstream err;
|
||||
err << "failed to open sockets after server reconfiguration: " << ex.what();
|
||||
answer = isc::config::createAnswer(1, err.str());
|
||||
}
|
||||
return (answer);
|
||||
return (ControlledDhcpv6Srv::processConfig(merged_config));
|
||||
}
|
||||
|
||||
ConstElementPtr
|
||||
ControlledDhcpv6Srv::dhcp6CommandHandler(const string& command, ConstElementPtr args) {
|
||||
LOG_DEBUG(dhcp6_logger, DBG_DHCP6_COMMAND, DHCP6_COMMAND_RECEIVED)
|
||||
.arg(command).arg(args->str());
|
||||
|
||||
if (command == "shutdown") {
|
||||
if (ControlledDhcpv6Srv::server_) {
|
||||
ControlledDhcpv6Srv::server_->shutdown();
|
||||
} else {
|
||||
LOG_WARN(dhcp6_logger, DHCP6_NOT_RUNNING);
|
||||
ConstElementPtr answer = isc::config::createAnswer(1,
|
||||
"Shutdown failure.");
|
||||
return (answer);
|
||||
}
|
||||
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,
|
||||
"Unrecognized command.");
|
||||
|
||||
return (answer);
|
||||
}
|
||||
|
||||
void ControlledDhcpv6Srv::sessionReader(void) {
|
||||
// Process one asio event. If there are more events, iface_mgr will call
|
||||
// this callback more than once.
|
||||
if (server_) {
|
||||
server_->io_service_.run_one();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
ControlledDhcpv6Srv::init(const std::string& /* config_file*/) {
|
||||
// This is BIND10 configuration backed. It established control session
|
||||
// that is used to connect to BIND10 framework.
|
||||
// This is Bundy configuration backed. It established control session
|
||||
// that is used to connect to Bundy framework.
|
||||
//
|
||||
// Creates session that will be used to receive commands and updated
|
||||
// configuration from cfgmgr (or indirectly from user via bindctl).
|
||||
@@ -217,18 +162,18 @@ ControlledDhcpv6Srv::init(const std::string& /* config_file*/) {
|
||||
// been lost.
|
||||
config_session_ = new ModuleCCSession(specfile, *cc_session_,
|
||||
dhcp6StubConfigHandler,
|
||||
dhcp6CommandHandler, false);
|
||||
processCommand, false);
|
||||
config_session_->start();
|
||||
|
||||
// The constructor already pulled the configuration that had
|
||||
// been created in the previous session thanks to the dummy
|
||||
// handler. We can switch to the handler that will be
|
||||
// parsing future changes to the configuration.
|
||||
config_session_->setConfigHandler(dhcp6ConfigHandler);
|
||||
config_session_->setConfigHandler(bundyConfigHandler);
|
||||
|
||||
try {
|
||||
// Pull the full configuration out from the session.
|
||||
configureDhcp6Server(*this, config_session_->getFullConfig());
|
||||
processConfig(config_session_->getFullConfig());
|
||||
|
||||
// Server will start DDNS communications if its enabled.
|
||||
server_->startD2();
|
||||
@@ -242,7 +187,7 @@ ControlledDhcpv6Srv::init(const std::string& /* config_file*/) {
|
||||
|
||||
}
|
||||
|
||||
/// Integrate the asynchronous I/O model of BIND 10 configuration
|
||||
/// Integrate the asynchronous I/O model of Bundy configuration
|
||||
/// control with the "select" model of the DHCP server. This is
|
||||
/// fully explained in \ref dhcpv6Session.
|
||||
int ctrl_socket = cc_session_->getSocketDesc();
|
||||
@@ -271,34 +216,6 @@ void ControlledDhcpv6Srv::cleanup() {
|
||||
}
|
||||
}
|
||||
|
||||
ControlledDhcpv6Srv::ControlledDhcpv6Srv(uint16_t port)
|
||||
: Dhcpv6Srv(port), cc_session_(NULL), config_session_(NULL) {
|
||||
server_ = this; // remember this instance for use in callback
|
||||
}
|
||||
|
||||
void ControlledDhcpv6Srv::shutdown() {
|
||||
io_service_.stop(); // Stop ASIO transmissions
|
||||
Dhcpv6Srv::shutdown(); // Initiate DHCPv6 shutdown procedure.
|
||||
}
|
||||
|
||||
ControlledDhcpv6Srv::~ControlledDhcpv6Srv() {
|
||||
cleanup();
|
||||
|
||||
server_ = NULL; // forget this instance. There should be no callback anymore
|
||||
// at this stage anyway.
|
||||
}
|
||||
|
||||
isc::data::ConstElementPtr
|
||||
ControlledDhcpv6Srv::execDhcpv6ServerCommand(const std::string& command_id,
|
||||
isc::data::ConstElementPtr args) {
|
||||
try {
|
||||
return (dhcp6CommandHandler(command_id, args));
|
||||
} catch (const Exception& ex) {
|
||||
ConstElementPtr answer = isc::config::createAnswer(1, ex.what());
|
||||
return (answer);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Daemon::loggerInit(const char* log_name, bool verbose, bool stand_alone) {
|
||||
isc::log::initLogger(log_name,
|
||||
@@ -306,5 +223,5 @@ Daemon::loggerInit(const char* log_name, bool verbose, bool stand_alone) {
|
||||
isc::log::MAX_DEBUG_LEVEL, NULL, !stand_alone);
|
||||
}
|
||||
|
||||
};
|
||||
};
|
||||
}; // end of isc::dhcp namespace
|
||||
}; // end of isc namespace
|
183
src/bin/dhcp6/ctrl_dhcp6_srv.cc
Normal file
183
src/bin/dhcp6/ctrl_dhcp6_srv.cc
Normal file
@@ -0,0 +1,183 @@
|
||||
// Copyright (C) 2014 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 <config.h>
|
||||
#include <cc/data.h>
|
||||
#include <dhcp6/ctrl_dhcp6_srv.h>
|
||||
#include <dhcp6/dhcp6_log.h>
|
||||
#include <hooks/hooks_manager.h>
|
||||
#include <dhcp6/json_config_parser.h>
|
||||
|
||||
using namespace isc::data;
|
||||
using namespace isc::hooks;
|
||||
using namespace std;
|
||||
|
||||
namespace isc {
|
||||
namespace dhcp {
|
||||
|
||||
ControlledDhcpv6Srv* ControlledDhcpv6Srv::server_ = NULL;
|
||||
|
||||
ConstElementPtr
|
||||
ControlledDhcpv6Srv::commandShutdownHandler(const string&, ConstElementPtr) {
|
||||
if (ControlledDhcpv6Srv::server_) {
|
||||
ControlledDhcpv6Srv::server_->shutdown();
|
||||
} else {
|
||||
LOG_WARN(dhcp6_logger, DHCP6_NOT_RUNNING);
|
||||
ConstElementPtr answer = isc::config::createAnswer(1, "Shutdown failure.");
|
||||
return (answer);
|
||||
}
|
||||
ConstElementPtr answer = isc::config::createAnswer(0, "Shutting down.");
|
||||
return (answer);
|
||||
}
|
||||
|
||||
ConstElementPtr
|
||||
ControlledDhcpv6Srv::commandLibReloadHandler(const string&, ConstElementPtr) {
|
||||
// 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
|
||||
ControlledDhcpv6Srv::commandConfigReloadHandler(const string&, ConstElementPtr args) {
|
||||
|
||||
return (processConfig(args));
|
||||
}
|
||||
|
||||
isc::data::ConstElementPtr
|
||||
ControlledDhcpv6Srv::processCommand(const std::string& command,
|
||||
isc::data::ConstElementPtr args) {
|
||||
LOG_DEBUG(dhcp6_logger, DBG_DHCP6_COMMAND, DHCP6_COMMAND_RECEIVED)
|
||||
.arg(command).arg(args->str());
|
||||
|
||||
ControlledDhcpv6Srv* srv = ControlledDhcpv6Srv::getInstance();
|
||||
|
||||
if (!srv) {
|
||||
ConstElementPtr no_srv = isc::config::createAnswer(1,
|
||||
"Server object not initialized, can't process command '" +
|
||||
command + "'.");
|
||||
return (no_srv);
|
||||
}
|
||||
|
||||
try {
|
||||
if (command == "shutdown") {
|
||||
return (srv->commandShutdownHandler(command, args));
|
||||
|
||||
} else if (command == "libreload") {
|
||||
return (srv->commandLibReloadHandler(command, args));
|
||||
|
||||
} else if (command == "config-reload") {
|
||||
return (srv->commandConfigReloadHandler(command, args));
|
||||
}
|
||||
|
||||
return (isc::config::createAnswer(1, "Unrecognized command:"
|
||||
+ command));
|
||||
|
||||
} catch (const Exception& ex) {
|
||||
return (isc::config::createAnswer(1, "Error while processing command '"
|
||||
+ command + "':" + ex.what()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
isc::data::ConstElementPtr
|
||||
ControlledDhcpv6Srv::processConfig(isc::data::ConstElementPtr config) {
|
||||
ControlledDhcpv6Srv* srv = ControlledDhcpv6Srv::getInstance();
|
||||
|
||||
if (!srv) {
|
||||
ConstElementPtr no_srv = isc::config::createAnswer(1,
|
||||
"Server object not initialized, can't process config.");
|
||||
return (no_srv);
|
||||
}
|
||||
|
||||
ConstElementPtr answer = configureDhcp6Server(*srv, config);
|
||||
|
||||
// Check that configuration was successful. If not, do not reopen sockets
|
||||
// and don't bother with DDNS stuff.
|
||||
try {
|
||||
int rcode = 0;
|
||||
isc::config::parseAnswer(rcode, answer);
|
||||
if (rcode != 0) {
|
||||
return (answer);
|
||||
}
|
||||
} catch (const std::exception& ex) {
|
||||
return (isc::config::createAnswer(1, "Failed to process configuration:"
|
||||
+ string(ex.what())));
|
||||
}
|
||||
|
||||
// Server will start DDNS communications if its enabled.
|
||||
try {
|
||||
srv->startD2();
|
||||
} catch (const std::exception& ex) {
|
||||
std::ostringstream err;
|
||||
err << "error starting DHCP_DDNS client "
|
||||
" after server reconfiguration: " << ex.what();
|
||||
return (isc::config::createAnswer(1, err.str()));
|
||||
}
|
||||
|
||||
// Configuration may change active interfaces. Therefore, we have to reopen
|
||||
// sockets according to new configuration. This operation is not exception
|
||||
// safe and we really don't want to emit exceptions to the callback caller.
|
||||
// Instead, catch an exception and create appropriate answer.
|
||||
try {
|
||||
srv->openActiveSockets(srv->getPort());
|
||||
} catch (const std::exception& ex) {
|
||||
std::ostringstream err;
|
||||
err << "failed to open sockets after server reconfiguration: " << ex.what();
|
||||
answer = isc::config::createAnswer(1, err.str());
|
||||
}
|
||||
|
||||
return (answer);
|
||||
}
|
||||
|
||||
ControlledDhcpv6Srv::ControlledDhcpv6Srv(uint16_t port)
|
||||
: Dhcpv6Srv(port) {
|
||||
if (server_) {
|
||||
isc_throw(InvalidOperation,
|
||||
"There is another Dhcpv6Srv instance already.");
|
||||
}
|
||||
server_ = this; // remember this instance for use in callback
|
||||
}
|
||||
|
||||
void ControlledDhcpv6Srv::shutdown() {
|
||||
io_service_.stop(); // Stop ASIO transmissions
|
||||
Dhcpv6Srv::shutdown(); // Initiate DHCPv6 shutdown procedure.
|
||||
}
|
||||
|
||||
ControlledDhcpv6Srv::~ControlledDhcpv6Srv() {
|
||||
cleanup();
|
||||
|
||||
server_ = NULL; // forget this instance. There should be no callback anymore
|
||||
// at this stage anyway.
|
||||
}
|
||||
|
||||
void ControlledDhcpv6Srv::sessionReader(void) {
|
||||
// Process one asio event. If there are more events, iface_mgr will call
|
||||
// this callback more than once.
|
||||
if (server_) {
|
||||
server_->io_service_.run_one();
|
||||
}
|
||||
}
|
||||
|
||||
}; // end of isc::dhcp namespace
|
||||
}; // end of isc namespace
|
@@ -44,48 +44,53 @@ public:
|
||||
ControlledDhcpv6Srv(uint16_t port = DHCP6_SERVER_PORT);
|
||||
|
||||
/// @brief Destructor.
|
||||
~ControlledDhcpv6Srv();
|
||||
virtual ~ControlledDhcpv6Srv();
|
||||
|
||||
/// @brief Initializes the server.
|
||||
///
|
||||
/// Depending on the configuration backend, it establishes msgq session,
|
||||
/// reads the JSON file from disk or may perform any other setup
|
||||
/// operation. For specific details, see actual implementation in
|
||||
/// ctrl_*_dhcp6_srv.cc
|
||||
/// *_backend.cc
|
||||
///
|
||||
/// @return true if initialization was successful, false if it failed
|
||||
bool init(const std::string& config_file);
|
||||
|
||||
/// @brief Terminates existing msgq session.
|
||||
/// @brief Performs cleanup, immediately before termination
|
||||
///
|
||||
/// This method terminates existing session with msgq. After calling
|
||||
/// This method performs final clean up, just before the Dhcpv6Srv object
|
||||
/// is destroyed. The actual behavior is backend dependent. For Bundy
|
||||
/// backend, it terminates existing session with msgq. After calling
|
||||
/// it, no further messages over msgq (commands or configuration updates)
|
||||
/// may be received.
|
||||
/// may be received. For JSON backend, it is no-op.
|
||||
///
|
||||
/// It is ok to call this method when session is disconnected already.
|
||||
/// For specific details, see actual implementation in *_backend.cc
|
||||
void cleanup();
|
||||
|
||||
/// @brief Initiates shutdown procedure for the whole DHCPv6 server.
|
||||
void shutdown();
|
||||
|
||||
/// @brief Session callback, processes received commands.
|
||||
/// @brief command processor
|
||||
///
|
||||
/// This method is uniform for all config backends. It processes received
|
||||
/// command (as a string + JSON arguments). Internally, it's just a
|
||||
/// wrapper that calls process*Command() methods and catches exceptions
|
||||
/// in them.
|
||||
///
|
||||
/// @note It never throws.
|
||||
///
|
||||
/// @param command Text represenation of the command (e.g. "shutdown")
|
||||
/// @param args Optional parameters
|
||||
///
|
||||
/// @return status of the command
|
||||
static isc::data::ConstElementPtr
|
||||
execDhcpv6ServerCommand(const std::string& command,
|
||||
isc::data::ConstElementPtr args);
|
||||
processCommand(const std::string& command, isc::data::ConstElementPtr args);
|
||||
|
||||
protected:
|
||||
/// @brief Static pointer to the sole instance of the DHCP server.
|
||||
/// @brief configuration processor
|
||||
///
|
||||
/// This is required for config and command handlers to gain access to
|
||||
/// the server
|
||||
static ControlledDhcpv6Srv* server_;
|
||||
|
||||
/// @brief A callback for handling incoming configuration updates.
|
||||
/// This is a callback for handling incoming configuration updates.
|
||||
/// This method should be called by all configuration backends when the
|
||||
/// server is starting up or when configuration has changed.
|
||||
///
|
||||
/// As pointer to this method is used a callback in ASIO used in
|
||||
/// ModuleCCSession, it has to be static.
|
||||
@@ -94,53 +99,71 @@ protected:
|
||||
///
|
||||
/// @return status of the config update
|
||||
static isc::data::ConstElementPtr
|
||||
dhcp6ConfigHandler(isc::data::ConstElementPtr new_config);
|
||||
processConfig(isc::data::ConstElementPtr new_config);
|
||||
|
||||
/// @brief A dummy configuration handler that always returns success.
|
||||
/// @brief returns pointer to the sole instance of Dhcpv6Srv
|
||||
///
|
||||
/// This configuration handler does not perform configuration
|
||||
/// parsing and always returns success. A dummy handler should
|
||||
/// be installed using \ref isc::config::ModuleCCSession ctor
|
||||
/// to get the initial configuration. This initial configuration
|
||||
/// comprises values for only those elements that were modified
|
||||
/// the previous session. The \ref dhcp6ConfigHandler can't be
|
||||
/// used to parse the initial configuration because it needs the
|
||||
/// full configuration to satisfy dependencies between the
|
||||
/// various configuration values. Installing the dummy handler
|
||||
/// that guarantees to return success causes initial configuration
|
||||
/// to be stored for the session being created and that it can
|
||||
/// be later accessed with
|
||||
/// \ref isc::config::ConfigData::getFullConfig().
|
||||
///
|
||||
/// @param new_config new configuration.
|
||||
///
|
||||
/// @return success configuration status.
|
||||
static isc::data::ConstElementPtr
|
||||
dhcp6StubConfigHandler(isc::data::ConstElementPtr new_config);
|
||||
/// @note may return NULL, if called before server is spawned
|
||||
static ControlledDhcpv6Srv* getInstance() {
|
||||
return (server_);
|
||||
}
|
||||
|
||||
/// @brief A callback for handling incoming commands.
|
||||
protected:
|
||||
/// @brief Static pointer to the sole instance of the DHCP server.
|
||||
///
|
||||
/// @param command textual representation of the command
|
||||
/// @param args parameters of the command
|
||||
///
|
||||
/// @return status of the processed command
|
||||
static isc::data::ConstElementPtr
|
||||
dhcp6CommandHandler(const std::string& command, isc::data::ConstElementPtr args);
|
||||
/// This is required for config and command handlers to gain access to
|
||||
/// the server. Some of them need to be static methods.
|
||||
static ControlledDhcpv6Srv* server_;
|
||||
|
||||
/// @brief Callback that will be called from iface_mgr when command/config arrives.
|
||||
/// @brief Callback that will be called from iface_mgr when data
|
||||
/// is received over control socket.
|
||||
///
|
||||
/// This static callback method is called from IfaceMgr::receive6() method,
|
||||
/// when there is a new command or configuration sent over msgq.
|
||||
/// when there is a new command or configuration sent over control socket
|
||||
/// (that was sent from msgq if backend is Bundy, or some yet unspecified
|
||||
/// sender if the backend is JSON file).
|
||||
static void sessionReader(void);
|
||||
|
||||
/// @brief IOService object, used for all ASIO operations.
|
||||
isc::asiolink::IOService io_service_;
|
||||
|
||||
/// @brief Helper session object that represents raw connection to msgq.
|
||||
isc::cc::Session* cc_session_;
|
||||
/// @brief handler for processing 'shutdown' command
|
||||
///
|
||||
/// This handler processes shutdown command, which initializes shutdown
|
||||
/// procedure.
|
||||
/// @param command (parameter ignored)
|
||||
/// @param args (parameter ignored)
|
||||
///
|
||||
/// @return status of the command
|
||||
isc::data::ConstElementPtr
|
||||
commandShutdownHandler(const std::string& command,
|
||||
isc::data::ConstElementPtr args);
|
||||
|
||||
/// @brief Session that receives configuration and commands
|
||||
isc::config::ModuleCCSession* config_session_;
|
||||
/// @brief handler for processing 'libreload' command
|
||||
///
|
||||
/// This handler processes libreload command, which unloads all hook
|
||||
/// libraries and reloads them.
|
||||
///
|
||||
/// @param command (parameter ignored)
|
||||
/// @param args (parameter ignored)
|
||||
///
|
||||
/// @return status of the command
|
||||
isc::data::ConstElementPtr
|
||||
commandLibReloadHandler(const std::string& command,
|
||||
isc::data::ConstElementPtr args);
|
||||
|
||||
/// @brief handler for processing 'config-reload' command
|
||||
///
|
||||
/// This handler processes config-reload command, which processes
|
||||
/// configuration specified in args parameter.
|
||||
///
|
||||
/// @param command (parameter ignored)
|
||||
/// @param args configuration to be processed
|
||||
///
|
||||
/// @return status of the command
|
||||
isc::data::ConstElementPtr
|
||||
commandConfigReloadHandler(const std::string& command,
|
||||
isc::data::ConstElementPtr args);
|
||||
};
|
||||
|
||||
}; // namespace isc::dhcp
|
||||
|
@@ -42,13 +42,6 @@ using namespace std;
|
||||
namespace isc {
|
||||
namespace dhcp {
|
||||
|
||||
// Need to provide dummy reader. JSON-file backend does not use any control
|
||||
// readers for now. Eventually, we may consider having a socket (named socket?)
|
||||
// that other processes (like IPAM) could write to, triggering specific actions.
|
||||
// For now, it's a no-op method.
|
||||
void ControlledDhcpv6Srv::sessionReader(void) {
|
||||
}
|
||||
|
||||
bool
|
||||
ControlledDhcpv6Srv::init(const std::string& file_name) {
|
||||
// This is a configuration backend implementation that reads the
|
||||
@@ -70,7 +63,7 @@ ControlledDhcpv6Srv::init(const std::string& file_name) {
|
||||
json = Element::fromJSON(config);
|
||||
|
||||
// Use parsed JSON structures to configure the server
|
||||
result = configureDhcp6Server(*this, json);
|
||||
result = processCommand("config-reload", json);
|
||||
|
||||
} catch (const std::exception& ex) {
|
||||
LOG_ERROR(dhcp6_logger, DHCP6_CONFIG_LOAD_FAIL).arg(ex.what());
|
||||
@@ -101,12 +94,9 @@ ControlledDhcpv6Srv::init(const std::string& file_name) {
|
||||
return (false);
|
||||
}
|
||||
|
||||
// Configuration may disable or enable interfaces so we have to
|
||||
// reopen sockets according to new configuration.
|
||||
openActiveSockets(getPort());
|
||||
|
||||
// Server will start DDNS communications if its enabled.
|
||||
this->startD2();
|
||||
// We don't need to call openActiveSockets() or startD2() as these
|
||||
// methods are called in processConfig() which is called by
|
||||
// processCommand("reload-config", ...)
|
||||
|
||||
return (true);
|
||||
}
|
||||
@@ -115,23 +105,5 @@ void ControlledDhcpv6Srv::cleanup() {
|
||||
// Nothing to do here. No need to disconnect from anything.
|
||||
}
|
||||
|
||||
ControlledDhcpv6Srv::ControlledDhcpv6Srv(uint16_t port)
|
||||
: Dhcpv6Srv(port), cc_session_(NULL), config_session_(NULL) {
|
||||
}
|
||||
|
||||
void ControlledDhcpv6Srv::shutdown() {
|
||||
|
||||
// Stop ASIO transmissions. Even though we didn't use it for
|
||||
// configuration reading, there may be on-going transmissions
|
||||
// with D2.
|
||||
io_service_.stop();
|
||||
|
||||
Dhcpv6Srv::shutdown(); // Initiate DHCPv6 shutdown procedure.
|
||||
}
|
||||
|
||||
ControlledDhcpv6Srv::~ControlledDhcpv6Srv() {
|
||||
cleanup();
|
||||
}
|
||||
|
||||
};
|
||||
};
|
@@ -79,23 +79,26 @@ dhcp6_unittests_SOURCES += d2_unittest.cc d2_unittest.h
|
||||
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.h ../ctrl_dhcp6_srv.cc
|
||||
dhcp6_unittests_SOURCES += ctrl_dhcp6_srv_unittest.cc
|
||||
dhcp6_unittests_SOURCES += wireshark.cc
|
||||
dhcp6_unittests_SOURCES += dhcp6_client.cc dhcp6_client.h
|
||||
dhcp6_unittests_SOURCES += rebind_unittest.cc
|
||||
dhcp6_unittests_SOURCES += ../json_config_parser.cc ../json_config_parser.h
|
||||
dhcp6_unittests_SOURCES += config_parser_unittest.cc
|
||||
|
||||
if CONFIG_BACKEND_BIND10
|
||||
dhcp6_unittests_SOURCES += ../json_config_parser.cc ../json_config_parser.h ../ctrl_bind10_dhcp6_srv.cc
|
||||
dhcp6_unittests_SOURCES += ctrl_bind10_dhcp6_srv_unittest.cc
|
||||
dhcp6_unittests_SOURCES += config_parser_unittest.cc
|
||||
# For Bundy backend, we only need to run the usual tests. There are no
|
||||
# Bundy-specific tests.
|
||||
dhcp6_unittests_SOURCES += ../bundy_backend.cc
|
||||
dhcp6_unittests_SOURCES += bundy_backend_unittest.cc
|
||||
endif
|
||||
|
||||
if CONFIG_BACKEND_JSON
|
||||
dhcp6_unittests_SOURCES += ../json_config_parser.cc ../json_config_parser.h ../ctrl_json_dhcp6_srv.cc
|
||||
dhcp6_unittests_SOURCES += ctrl_json_dhcp6_srv_unittest.cc
|
||||
dhcp6_unittests_SOURCES += config_parser_unittest.cc
|
||||
dhcp6_unittests_SOURCES += ../jsonfile_backend.cc
|
||||
dhcp6_unittests_SOURCES += jsonfile_backend_unittest.cc
|
||||
endif
|
||||
|
||||
|
||||
nodist_dhcp6_unittests_SOURCES = ../dhcp6_messages.h ../dhcp6_messages.cc
|
||||
nodist_dhcp6_unittests_SOURCES += marker_file.h test_libraries.h
|
||||
|
||||
|
@@ -15,7 +15,7 @@
|
||||
#include <config.h>
|
||||
|
||||
#include <config/ccsession.h>
|
||||
#include <dhcp/dhcp6.h>
|
||||
#include <dhcpsrv/cfgmgr.h>
|
||||
#include <dhcp6/ctrl_dhcp6_srv.h>
|
||||
#include <hooks/hooks_manager.h>
|
||||
|
||||
@@ -25,17 +25,7 @@
|
||||
#include <boost/scoped_ptr.hpp>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <unistd.h>
|
||||
|
||||
using namespace std;
|
||||
using namespace isc;
|
||||
using namespace isc::asiolink;
|
||||
using namespace isc::config;
|
||||
using namespace isc::data;
|
||||
using namespace isc::dhcp;
|
||||
using namespace isc::dhcp::test;
|
||||
@@ -85,13 +75,13 @@ TEST_F(CtrlDhcpv6SrvTest, commands) {
|
||||
int rcode = -1;
|
||||
|
||||
// Case 1: send bogus command
|
||||
ConstElementPtr result = ControlledDhcpv6Srv::execDhcpv6ServerCommand("blah", params);
|
||||
ConstElementPtr comment = parseAnswer(rcode, result);
|
||||
ConstElementPtr result = ControlledDhcpv6Srv::processCommand("blah", params);
|
||||
ConstElementPtr comment = isc::config::parseAnswer(rcode, result);
|
||||
EXPECT_EQ(1, rcode); // expect failure (no such command as blah)
|
||||
|
||||
// Case 2: send shutdown command without any parameters
|
||||
result = ControlledDhcpv6Srv::execDhcpv6ServerCommand("shutdown", params);
|
||||
comment = parseAnswer(rcode, result);
|
||||
result = ControlledDhcpv6Srv::processCommand("shutdown", params);
|
||||
comment = isc::config::parseAnswer(rcode, result);
|
||||
EXPECT_EQ(0, rcode); // expect success
|
||||
|
||||
const pid_t pid(getpid());
|
||||
@@ -99,14 +89,21 @@ TEST_F(CtrlDhcpv6SrvTest, commands) {
|
||||
params->set("pid", x);
|
||||
|
||||
// Case 3: send shutdown command with 1 parameter: pid
|
||||
result = ControlledDhcpv6Srv::execDhcpv6ServerCommand("shutdown", params);
|
||||
comment = parseAnswer(rcode, result);
|
||||
result = ControlledDhcpv6Srv::processCommand("shutdown", params);
|
||||
comment = isc::config::parseAnswer(rcode, result);
|
||||
EXPECT_EQ(0, rcode); // Expect success
|
||||
}
|
||||
|
||||
// Check that the "libreload" command will reload libraries
|
||||
|
||||
TEST_F(CtrlDhcpv6SrvTest, libreload) {
|
||||
|
||||
// Sending commands for processing now requires a server that can process
|
||||
// them.
|
||||
boost::scoped_ptr<ControlledDhcpv6Srv> srv;
|
||||
ASSERT_NO_THROW(
|
||||
srv.reset(new ControlledDhcpv6Srv(0))
|
||||
);
|
||||
|
||||
// Ensure no marker files to start with.
|
||||
ASSERT_FALSE(checkMarkerFileExists(LOAD_MARKER_FILE));
|
||||
ASSERT_FALSE(checkMarkerFileExists(UNLOAD_MARKER_FILE));
|
||||
@@ -137,8 +134,8 @@ TEST_F(CtrlDhcpv6SrvTest, libreload) {
|
||||
int rcode = -1;
|
||||
|
||||
ConstElementPtr result =
|
||||
ControlledDhcpv6Srv::execDhcpv6ServerCommand("libreload", params);
|
||||
ConstElementPtr comment = parseAnswer(rcode, result);
|
||||
ControlledDhcpv6Srv::processCommand("libreload", params);
|
||||
ConstElementPtr comment = isc::config::parseAnswer(rcode, result);
|
||||
EXPECT_EQ(0, rcode); // Expect success
|
||||
|
||||
// Check that the libraries have unloaded and reloaded. The libraries are
|
||||
@@ -148,4 +145,58 @@ TEST_F(CtrlDhcpv6SrvTest, libreload) {
|
||||
EXPECT_TRUE(checkMarkerFile(LOAD_MARKER_FILE, "1212"));
|
||||
}
|
||||
|
||||
// Check that the "configReload" command will reload libraries
|
||||
TEST_F(CtrlDhcpv6SrvTest, configReload) {
|
||||
|
||||
// Sending commands for processing now requires a server that can process
|
||||
// them.
|
||||
boost::scoped_ptr<ControlledDhcpv6Srv> srv;
|
||||
ASSERT_NO_THROW(
|
||||
srv.reset(new ControlledDhcpv6Srv(0))
|
||||
);
|
||||
|
||||
// Now execute the "libreload" command. This should cause the libraries
|
||||
// to unload and to reload.
|
||||
|
||||
// Use empty parameters list
|
||||
// Prepare configuration file.
|
||||
string config_txt = "{ \"interfaces\": [ \"*\" ],"
|
||||
"\"preferred-lifetime\": 3000,"
|
||||
"\"rebind-timer\": 2000, "
|
||||
"\"renew-timer\": 1000, "
|
||||
"\"subnet6\": [ { "
|
||||
" \"pool\": [ \"2001:db8:1::/80\" ],"
|
||||
" \"subnet\": \"2001:db8:1::/64\" "
|
||||
" },"
|
||||
" {"
|
||||
" \"pool\": [ \"2001:db8:2::/80\" ],"
|
||||
" \"subnet\": \"2001:db8:2::/64\", "
|
||||
" \"id\": 0"
|
||||
" },"
|
||||
" {"
|
||||
" \"pool\": [ \"2001:db8:3::/80\" ],"
|
||||
" \"subnet\": \"2001:db8:3::/64\" "
|
||||
" } ],"
|
||||
"\"valid-lifetime\": 4000 }";
|
||||
|
||||
ElementPtr config = Element::fromJSON(config_txt);
|
||||
|
||||
// Make sure there are no subnets configured.
|
||||
CfgMgr::instance().deleteSubnets6();
|
||||
|
||||
// Now send the command
|
||||
int rcode = -1;
|
||||
ConstElementPtr result =
|
||||
ControlledDhcpv6Srv::processCommand("config-reload", config);
|
||||
ConstElementPtr comment = isc::config::parseAnswer(rcode, result);
|
||||
EXPECT_EQ(0, rcode); // Expect success
|
||||
|
||||
// Check that the config was indeed applied.
|
||||
const Subnet6Collection* subnets = CfgMgr::instance().getSubnets6();
|
||||
EXPECT_EQ(3, subnets->size());
|
||||
|
||||
// Clean up after the test.
|
||||
CfgMgr::instance().deleteSubnets6();
|
||||
}
|
||||
|
||||
} // End of anonymous namespace
|
@@ -46,12 +46,12 @@ public:
|
||||
};
|
||||
|
||||
|
||||
class ControlledJSONDhcpv6SrvTest : public ::testing::Test {
|
||||
class JSONFileBackendTest : public ::testing::Test {
|
||||
public:
|
||||
ControlledJSONDhcpv6SrvTest() {
|
||||
JSONFileBackendTest() {
|
||||
}
|
||||
|
||||
~ControlledJSONDhcpv6SrvTest() {
|
||||
~JSONFileBackendTest() {
|
||||
};
|
||||
|
||||
void writeFile(const std::string& file_name, const std::string& content) {
|
||||
@@ -66,10 +66,10 @@ public:
|
||||
static const char* TEST_FILE;
|
||||
};
|
||||
|
||||
const char* ControlledJSONDhcpv6SrvTest::TEST_FILE = "test-config.json";
|
||||
const char* JSONFileBackendTest::TEST_FILE = "test-config.json";
|
||||
|
||||
// This test checks if configuration can be read from a JSON file.
|
||||
TEST_F(ControlledJSONDhcpv6SrvTest, jsonFile) {
|
||||
TEST_F(JSONFileBackendTest, jsonFile) {
|
||||
|
||||
// Prepare configuration file.
|
||||
string config = "{ \"interfaces\": [ \"*\" ],"
|
||||
@@ -141,7 +141,7 @@ TEST_F(ControlledJSONDhcpv6SrvTest, jsonFile) {
|
||||
}
|
||||
|
||||
// This test checks if configuration can be read from a JSON file.
|
||||
TEST_F(ControlledJSONDhcpv6SrvTest, comments) {
|
||||
TEST_F(JSONFileBackendTest, comments) {
|
||||
|
||||
string config_hash_comments = "# This is a comment. It should be \n"
|
||||
"#ignored. Real config starts in line below\n"
|
Reference in New Issue
Block a user