Commit b4d53d93 authored by Tomek Mrugalski's avatar Tomek Mrugalski 🛰

[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
parent c77bbb16
......@@ -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
// 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);
/// @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.
static isc::data::ConstElementPtr
dhcp6StubConfigHandler(isc::data::ConstElementPtr new_config);
processConfig(isc::data::ConstElementPtr new_config);
/// @brief A callback for handling incoming commands.
/// @brief returns pointer to the sole instance of Dhcpv6Srv
///
/// @param command textual representation of the command
/// @param args parameters of the command
/// @note may return NULL, if called before server is spawned
static ControlledDhcpv6Srv* getInstance() {
return (server_);
}
protected:
/// @brief Static pointer to the sole instance of the DHCP server.
///
/// @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 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 Session that receives configuration and commands
isc::config::ModuleCCSession* config_session_;
/// @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