Commit 1c4d345d authored by Thomas Markwalder's avatar Thomas Markwalder
Browse files

3401 - D2 now supports with-kea-config switch

The configuration switch --with-kea-config, now selects between
two versions of D2Controller:

1. One which must run as a BUNDY module and is implemented in
bundy_d2_controller.(h/cc)

All of the BIND10 support was extracted from DControllerBase and moved
into this version of D2Controller.

This controller is tested in tests/bundy_d2_controller_unittests.cc

2. One that runs as a stand alone executable which must be supplied
with a configuration file via the command line and is implemented in
d2_controller.(h/cc).

This version of D2Controller is nearly identical the the original.
DControllerBase supports configuration from file.

This controller is tested in tests/d2_controller_unittests.cc

DControllerBase now inherits from Daemon which keeps it in step with
K4 and K6.

The stand-alone mode flag has been removed from all controllers.
parent b1ed4d7d
......@@ -50,12 +50,12 @@ BUILT_SOURCES = spec_config.h d2_messages.h d2_messages.cc
pkglibexec_PROGRAMS = b10-dhcp-ddns
b10_dhcp_ddns_SOURCES = main.cc
b10_dhcp_ddns_SOURCES += d_process.h
b10_dhcp_ddns_SOURCES += d_controller.cc d_controller.h
b10_dhcp_ddns_SOURCES += d_cfg_mgr.cc d_cfg_mgr.h
b10_dhcp_ddns_SOURCES += d2_asio.h
b10_dhcp_ddns_SOURCES += d2_log.cc d2_log.h
b10_dhcp_ddns_SOURCES += d2_process.cc d2_process.h
b10_dhcp_ddns_SOURCES += d_controller.cc d_controller.h
b10_dhcp_ddns_SOURCES += d2_controller.cc d2_controller.h
b10_dhcp_ddns_SOURCES += d_cfg_mgr.cc d_cfg_mgr.h
b10_dhcp_ddns_SOURCES += d2_config.cc d2_config.h
b10_dhcp_ddns_SOURCES += d2_cfg_mgr.cc d2_cfg_mgr.h
b10_dhcp_ddns_SOURCES += d2_queue_mgr.cc d2_queue_mgr.h
......@@ -69,6 +69,14 @@ b10_dhcp_ddns_SOURCES += nc_remove.cc nc_remove.h
b10_dhcp_ddns_SOURCES += nc_trans.cc nc_trans.h
b10_dhcp_ddns_SOURCES += state_model.cc state_model.h
if CONFIG_BACKEND_BUNDY
b10_dhcp_ddns_SOURCES += bundy_d2_controller.cc bundy_d2_controller.h
else
if CONFIG_BACKEND_JSON
b10_dhcp_ddns_SOURCES += d2_controller.cc d2_controller.h
endif
endif
nodist_b10_dhcp_ddns_SOURCES = d2_messages.h d2_messages.cc
EXTRA_DIST += d2_messages.mes
......
// 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 <d2/bundy_d2_controller.h>
#include <d2/d2_process.h>
#include <d2/spec_config.h>
#include <sstream>
namespace isc {
namespace d2 {
/// @brief Defines the application name, this is passed into base class
/// it may be used to locate configuration data and appears in log statement.
const char* D2Controller::d2_app_name_ = "DHCP-DDNS";
/// @brief Defines the executable name. This is passed into the base class
const char* D2Controller::d2_bin_name_ = "b10-dhcp-ddns";
DControllerBasePtr&
D2Controller::instance() {
// If the instance hasn't been created yet, create it. Note this method
// must use the base class singleton instance methods. The base class
// must have access to the singleton in order to use it within BUNDY
// static function callbacks.
if (!getController()) {
DControllerBasePtr controller_ptr(new D2Controller());
setController(controller_ptr);
}
return (getController());
}
DProcessBase* D2Controller::createProcess() {
// Instantiate and return an instance of the D2 application process. Note
// that the process is passed the controller's io_service.
return (new D2Process(getAppName().c_str(), getIOService()));
}
D2Controller::D2Controller()
: DControllerBase(d2_app_name_, d2_bin_name_) {
// set the spec file either from the environment or
// use the production value.
if (getenv("B10_FROM_BUILD")) {
setSpecFileName(std::string(getenv("B10_FROM_BUILD")) +
"/src/bin/d2/dhcp-ddns.spec");
} else {
setSpecFileName(D2_SPECFILE_LOCATION);
}
}
D2Controller::~D2Controller() {
}
void
D2Controller::launch(int argc, char* argv[], const bool test_mode) {
// Step 1 is to parse the command line arguments.
try {
parseArgs(argc, argv);
} catch (const InvalidUsage& ex) {
usage(ex.what());
throw; // rethrow it
}
// Do not initialize logger here if we are running unit tests. It would
// replace an instance of unit test specific logger.
if (!test_mode) {
// Now that we know what the mode flags are, we can init logging.
// If standalone is enabled, do not buffer initial log messages
isc::log::initLogger(getBinName(),
(isVerbose() ? isc::log::DEBUG : isc::log::INFO),
isc::log::MAX_DEBUG_LEVEL, NULL, true);
}
LOG_DEBUG(dctl_logger, DBGLVL_START_SHUT, DCTL_STARTING)
.arg(getAppName()).arg(getpid());
try {
// Step 2 is to create and initialize the application process object.
initProcess();
} catch (const std::exception& ex) {
LOG_FATAL(dctl_logger, DCTL_INIT_PROCESS_FAIL)
.arg(getAppName()).arg(ex.what());
isc_throw (ProcessInitError,
"Application Process initialization failed: " << ex.what());
}
// Next we connect to Bundy.
try {
establishSession();
} catch (const std::exception& ex) {
LOG_FATAL(dctl_logger, DCTL_SESSION_FAIL).arg(ex.what());
isc_throw (SessionStartError,
"Session start up failed: " << ex.what());
}
// Everything is clear for launch, so start the application's
// event loop.
try {
runProcess();
} catch (const std::exception& ex) {
LOG_FATAL(dctl_logger, DCTL_PROCESS_FAILED)
.arg(getAppName()).arg(ex.what());
isc_throw (ProcessRunError,
"Application process event loop failed: " << ex.what());
}
// Disconnect from Bundy.
try {
disconnectSession();
} catch (const std::exception& ex) {
LOG_ERROR(dctl_logger, DCTL_DISCONNECT_FAIL)
.arg(getAppName()).arg(ex.what());
isc_throw (SessionEndError, "Session end failed: " << ex.what());
}
// All done, so bail out.
LOG_INFO(dctl_logger, DCTL_STOPPING).arg(getAppName());
}
void
D2Controller::parseArgs(int argc, char* argv[])
{
// Iterate over the given command line options. If its a stock option
// ("s" or "v") handle it here. If its a valid custom option, then
// invoke customOption.
int ch;
opterr = 0;
optind = 1;
std::string opts(":v" + getCustomOpts());
while ((ch = getopt(argc, argv, opts.c_str())) != -1) {
switch (ch) {
case 'v':
// Enables verbose logging.
setVerbose(true);
break;
case '?': {
// We hit an invalid option.
isc_throw(InvalidUsage, "unsupported option: ["
<< static_cast<char>(optopt) << "] "
<< (!optarg ? "" : optarg));
break;
}
default:
// We hit a valid custom option
if (!customOption(ch, optarg)) {
// This would be a programmatic error.
isc_throw(InvalidUsage, " Option listed but implemented?: ["
<< static_cast<char>(ch) << "] "
<< (!optarg ? "" : optarg));
}
break;
}
}
// There was too much information on the command line.
if (argc > optind) {
isc_throw(InvalidUsage, "extraneous command line information");
}
}
void
D2Controller::establishSession() {
LOG_DEBUG(dctl_logger, DBGLVL_START_SHUT, DCTL_CCSESSION_STARTING)
.arg(getAppName()).arg(getSpecFileName());
// Create the BIND10 command control session with the our IOService.
cc_session_ = SessionPtr(new isc::cc::Session(
getIOService()->get_io_service()));
// Create the BIND10 config session with the stub configuration handler.
// This handler is internally invoked by the constructor and on success
// the constructor updates the current session with the configuration that
// had been committed in the previous session. If we do not install
// the dummy handler, the previous configuration would be lost.
config_session_ = ModuleCCSessionPtr(new isc::config::ModuleCCSession(
getSpecFileName(), *cc_session_,
dummyConfigHandler,
DControllerBase::commandHandler,
false));
// Enable configuration even processing.
config_session_->start();
// We initially create ModuleCCSession() with a dummy configHandler, as
// the session module is too eager to send partial configuration.
// Replace the dummy config handler with the real handler.
config_session_->setConfigHandler(DControllerBase::configHandler);
// Call the real configHandler with the full configuration retrieved
// from the config session.
isc::data::ConstElementPtr answer = configHandler(
config_session_->getFullConfig());
// Parse the answer returned from the configHandler. Log the error but
// keep running. This provides an opportunity for the user to correct
// the configuration dynamically.
int ret = 0;
isc::data::ConstElementPtr comment = isc::config::parseAnswer(ret, answer);
if (ret) {
LOG_ERROR(dctl_logger, DCTL_CONFIG_LOAD_FAIL)
.arg(getAppName()).arg(comment->str());
}
}
void D2Controller::disconnectSession() {
LOG_DEBUG(dctl_logger, DBGLVL_START_SHUT, DCTL_CCSESSION_ENDING)
.arg(getAppName());
// Destroy the BIND10 config session.
if (config_session_) {
config_session_.reset();
}
// Destroy the BIND10 command and control session.
if (cc_session_) {
cc_session_->disconnect();
cc_session_.reset();
}
}
isc::data::ConstElementPtr
D2Controller::dummyConfigHandler(isc::data::ConstElementPtr) {
LOG_DEBUG(dctl_logger, DBGLVL_START_SHUT, DCTL_CONFIG_STUB)
.arg(getController()->getAppName());
return (isc::config::createAnswer(0, "Configuration accepted."));
}
isc::data::ConstElementPtr
D2Controller::updateConfig(isc::data::ConstElementPtr new_config) {
if (!config_session_) {
// That should never happen as we install config_handler
// after we instantiate the server.
isc::data::ConstElementPtr answer =
isc::config::createAnswer(1, "Configuration rejected,"
" Session has not started.");
return (answer);
}
// Let's get the existing configuration.
isc::data::ConstElementPtr full_config = config_session_->getFullConfig();
// The configuration passed to this handler function is partial.
// In other words, it just includes the values being modified.
// In the same time, there may be dependencies between various
// configuration parsers. For example: the option value can
// be set if the definition of this option is set. If someone removes
// an existing option definition then the partial configuration that
// removes that definition is triggered while a relevant option value
// may remain configured. This eventually results in the
// configuration being in the inconsistent state.
// In order to work around this problem we need to merge the new
// configuration with the existing (full) configuration.
// Let's create a new object that will hold the merged configuration.
boost::shared_ptr<isc::data::MapElement>
merged_config(new isc::data::MapElement());
// Merge an existing and new configuration.
merged_config->setValue(full_config->mapValue());
isc::data::merge(merged_config, new_config);
// Send the merged configuration to the application.
return (getProcess()->configure(merged_config));
}
void
D2Controller::usage(const std::string & text)
{
if (text != "") {
std::cerr << "Usage error: " << text << std::endl;
}
std::cerr << "Usage: " << getBinName() << std::endl;
std::cerr << " -v: verbose output" << std::endl;
std::cerr << getUsageText() << std::endl;
}
}; // namespace isc::d2
}; // namespace isc
// 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.
#ifndef BUNDY_D2_CONTROLLER_H
#define BUNDY_D2_CONTROLLER_H
#include <cc/data.h>
#include <cc/session.h>
#include <config/ccsession.h>
#include <d2/d2_asio.h>
#include <d2/d2_log.h>
#include <d2/d_controller.h>
#include <d2/d_process.h>
#include <exceptions/exceptions.h>
#include <log/logger_support.h>
#include <boost/shared_ptr.hpp>
#include <boost/noncopyable.hpp>
namespace isc {
namespace d2 {
/// @brief Exception thrown when the session start up fails.
class SessionStartError: public isc::Exception {
public:
SessionStartError (const char* file, size_t line, const char* what) :
isc::Exception(file, line, what) { };
};
/// @brief Exception thrown when the session end fails.
class SessionEndError: public isc::Exception {
public:
SessionEndError (const char* file, size_t line, const char* what) :
isc::Exception(file, line, what) { };
};
/// @brief Defines a shared pointer to a Session.
typedef boost::shared_ptr<isc::cc::Session> SessionPtr;
/// @brief Defines a shared pointer to a ModuleCCSession.
typedef boost::shared_ptr<isc::config::ModuleCCSession> ModuleCCSessionPtr;
/// @brief Bundy-integrated Process Controller for D2 Process
/// This class is the DHCP-DDNS specific derivation of DControllerBase which
/// is managed by BUNDY. It creates and manages an instance of the DHCP-DDNS
/// application process, D2Process.
///
/// D2 will be constructed with this class if the project is configured with
/// --with-kea-config=BUNDY
class D2Controller : public DControllerBase {
public:
/// @brief Static singleton instance method. This method returns the
/// base class singleton instance member. It instantiates the singleton
/// and sets the base class instance member upon first invocation.
///
/// @return returns the pointer reference to the singleton instance.
static DControllerBasePtr& instance();
/// @brief Destructor.
virtual ~D2Controller();
/// @brief Defines the application name, this is passed into base class
/// and appears in log statements.
static const char* d2_app_name_;
/// @brief Defines the executable name. This is passed into the base class
/// by convention this should match the BUNDY module name.
static const char* d2_bin_name_;
/// @brief Acts as the primary entry point into the controller execution
/// and provides the outermost application control logic:
///
/// 1. parse command line arguments
/// 2. instantiate and initialize the application process
/// 3. establish BUNDY session(s) if in integrated mode
/// 4. start and wait on the application process event loop
/// 5. upon event loop completion, disconnect from BUNDY (if needed)
/// 6. exit to the caller
///
/// It is intended to be called from main() and be given the command line
/// arguments. Note this method is deliberately not virtual to ensure the
/// proper sequence of events occur.
///
/// This function can be run in the test mode. It prevents initialization
/// of D2 module logger. This is used in unit tests which initialize logger
/// in their main function. Such logger uses environmental variables to
/// control severity, verbosity etc. Reinitialization of logger by this
/// function would replace unit tests specific logger configuration with
/// this suitable for D2 running as a bind10 module.
///
/// @param argc is the number of command line arguments supplied
/// @param argv is the array of string (char *) command line arguments
/// @param test_mode is a bool value which indicates if
/// @c DControllerBase::launch should be run in the test mode (if true).
/// This parameter doesn't have default value to force test implementers to
/// enable test mode explicitly.
///
/// @throw throws one of the following exceptions:
/// InvalidUsage - Indicates invalid command line.
/// ProcessInitError - Failed to create and initialize application
/// process object.
/// SessionStartError - Could not connect to BUNDY (integrated mode only).
/// ProcessRunError - A fatal error occurred while in the application
/// process event loop.
/// SessionEndError - Could not disconnect from BUNDY (integrated mode
/// only).
virtual void launch(int argc, char* argv[], const bool test_mode);
/// @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 D2 configuration parsing can't be
/// used to parse the initial configuration because it may need 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
dummyConfigHandler(isc::data::ConstElementPtr new_config);
/// @brief Instance method invoked by the configuration event handler and
/// which processes the actual configuration update. Provides behavioral
/// path for both integrated and stand-alone modes. The current
/// implementation will merge the configuration update into the existing
/// configuration and then invoke the application process' configure method.
///
/// @todo This implementation is will evolve as the D2 configuration
/// management task is implemented (trac #2957).
///
/// @param new_config is the new configuration
///
/// @return returns an Element that contains the results of configuration
/// update composed of an integer status value (0 means successful,
/// non-zero means failure), and a string explanation of the outcome.
virtual isc::data::ConstElementPtr
updateConfig(isc::data::ConstElementPtr new_config);
protected:
/// @brief Processes the command line arguments. It is the first step
/// taken after the controller has been launched. It combines the stock
/// list of options with those returned by getCustomOpts(), and uses
/// cstdlib's getopt to loop through the command line. The stock options
/// It handles stock options directly, and passes any custom options into
/// the customOption method. Currently there are only two stock options
/// -s for stand alone mode, and -v for verbose logging.
///
/// @param argc is the number of command line arguments supplied
/// @param argv is the array of string (char *) command line arguments
///
/// @throw throws InvalidUsage when there are usage errors.
void parseArgs(int argc, char* argv[]);
/// @brief Establishes connectivity with BUNDY. This method is used
/// invoked during launch, if running in integrated mode, following
/// successful process initialization. It is responsible for establishing
/// the BUNDY control and config sessions. During the session creation,
/// it passes in the controller's IOService and the callbacks for command
/// directives and config events. Lastly, it will invoke the onConnect
/// method providing the derivation an opportunity to execute any custom
/// logic associated with session establishment.
///
/// @throw the BUNDY framework may throw std::exceptions.
void establishSession();
/// @brief Terminates connectivity with BUNDY. This method is invoked
/// in integrated mode after the application event loop has exited. It
/// first calls the onDisconnect method providing the derivation an
/// opportunity to execute custom logic if needed, and then terminates the
/// BUNDY config and control sessions.
///
/// @throw the BUNDY framework may throw std:exceptions.
void disconnectSession();
/// @brief Prints the program usage text to std error.
///
/// @param text is a string message which will preceded the usage text.
/// This is intended to be used for specific usage violation messages.
void usage(const std::string& text);
private:
/// @brief Creates an instance of the DHCP-DDNS specific application
/// process. This method is invoked during the process initialization
/// step of the controller launch.
///
/// @return returns a DProcessBase* to the application process created.
/// Note the caller is responsible for destructing the process. This
/// is handled by the base class, which wraps this pointer with a smart
/// pointer.
virtual DProcessBase* createProcess();
/// @brief Helper session object that represents raw connection to msgq.
SessionPtr cc_session_;
/// @brief Session that receives configuration and commands.
ModuleCCSessionPtr config_session_;
/// @brief Constructor is declared private to maintain the integrity of
/// the singleton instance.
D2Controller();
// DControllerTest is named a friend class to facilitate unit testing while
// leaving the intended member scopes intact.
friend class DControllerTest;
};
}; // namespace isc::d2
}; // namespace isc
#endif
// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2013-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
......@@ -22,11 +22,10 @@ namespace isc {
namespace d2 {
/// @brief Defines the application name, this is passed into base class
/// and appears in log statements.
const char* D2Controller::d2_app_name_ = "DHCP-DDNS";
/// it may be used to locate configuration data and appears in log statement.
const char* D2Controller::d2_app_name_ = "DhcpDdns";
/// @brief Defines the executable name. This is passed into the base class
/// by convention this should match the BIND10 module name.
const char* D2Controller::d2_bin_name_ = "b10-dhcp-ddns";
DControllerBasePtr&
......@@ -51,7 +50,7 @@ DProcessBase* D2Controller::createProcess() {
D2Controller::D2Controller()
: DControllerBase(d2_app_name_, d2_bin_name_) {
// set the BIND10 spec file either from the environment or
// set the spec file either from the environment or
// use the production value.
if (getenv("B10_FROM_BUILD")) {
setSpecFileName(std::string(getenv("B10_FROM_BUILD")) +
......
// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2013-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
......@@ -47,7 +47,7 @@ public:
static const char* d2_app_name_;
/// @brief Defines the executable name. This is passed into the base class
/// by convention this should match the BIND10 module name.
/// by convention this should match the executable name.
static const char* d2_bin_name_;
private:
......
......@@ -37,6 +37,10 @@ This critical error message indicates that the initial application
configuration has failed. The service will start, but will not
process requests until the configuration has been corrected.
% DCTL_CONFIG_FILE_LOAD_FAIL %1 configuration could not be loaded from file: %2
This fatal error message indicates that the application attempted to load its
initial configuration from file and has failed. The service will exit.
% DCTL_CONFIG_START parsing new configuration: %1
A debug message indicating that the application process has received an
updated configuration and has passed it to its configuration manager
......
// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2013-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
......@@ -14,6 +14,7 @@