Commit 004f6cec authored by Thomas Markwalder's avatar Thomas Markwalder
Browse files

[master] Merge branch 'trac3401'

D2 now supports with-kea-config switch.
parents f10f7902 19e52ec7
......@@ -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
......@@ -52,39 +52,45 @@ base are shown in the following class diagram:
- isc::d2::DControllerBase - provides all of the services necessary to manage
an application process class derived from isc::d2::DProcess. These services include:
- Command line argument handling
- Process instantiation and initialization
- Process instantiation and initialization0
- Support for stand-alone execution
- Support for integrated operation as a BIND10 module (session management
- Support for integrated operation as a BUNDY module (session management
and event handling)
- Process event loop invocation and shutdown
.
It creates and manages an instance of isc::d2::DProcessBase. The CPL is designed
for asynchronous event processing applications. It is constructed to use ASIO
library for IO processing. DControllerBase own an isc::asiolink::io_service instance and it pas ses this into the @c DProcessBase constructor and it is this
service that is used drivel the process's event loop.
@c DControllerBase also provides the ability to operate one of two modes:
"stand-alone" mode or "managed" mode. In stand-alone mode, the controller has
no IO of it's own, thus there is two-way control communication between the application and the outside world.
In "managed mode" the controller creates a BIND10 Session, allowing it to
participate as a BIND10 module and therefore receive control commands such as
configuration updates, status requests, and shutdown. BIND10 modules are
required to supply two callbacks: one for configuration events and one for
command events. @c DControllerBase supplies these callbacks which largely
pass the information through to its @c DProcessBase instance. The key aspect
to take from this is that the controller handles the interface for receiving
asynchronous commands and the invokes the appropriate services in the process's
interface.
@todo DControllerBase does yet support reading the configuration from a
command line argument. It's command line argument processing can be very easily
extended to do so.
@todo At the time of this writing Kea is being separated from the BIND10
framework. As such, use of the BIND10 Session will be removed but could
readily be replaced with an authenticated socket connection for receiving and
responding to directives asynchronously.
It creates and manages an instance of isc::d2::DProcessBase. The CPL is
designed for asynchronous event processing applications. It is constructed
to use ASIO library for IO processing. @c DControllerBase owns an
isc::asiolink::IOService instance and it passes this into the @c
DProcessBase constructor. It is this @c IOService that is used to drive the
process's event loop. The controller is designed to provide any interfaces
between the process it controls and the outside world.
@c DControllerBase provides configuration for its process via a JSON file
specified as a mandatory command line argument. The file structure is
expected be as follows:
{ "<module-name>": {<module-config>} }
where:
- module-name : is a label which uniquely identifies the
configuration data for the (i