will be required to make d2 build.
Modified files:
Makefile.am
d2_log.cc
d2_log.h
d2_messages.mes
d2.spec
main.cc
tests/Makefile.am
tests/d2_test.py
New files:
d2_controller.cc
d2_controller.h
d_controller.cc
d_controller.h
spec_config.h
tests/d2_controller_unittests.cc
tests/d_controller_unittests.cc
tests/d_test_stubs.cc
tests/d_test_stubs.h
pkglibexecdir = $(libexecdir)/@PACKAGE@
-CLEANFILES = *.gcno *.gcda spec_config.h d2_srv_messages.h d2_srv_messages.cc
+CLEANFILES = *.gcno *.gcda spec_config.h d2_messages.h d2_messages.cc
man_MANS = b10-d2.8
DISTCLEANFILES = $(man_MANS)
b10_d2_SOURCES = main.cc
b10_d2_SOURCES += d2_log.cc d2_log.h
+b10_d2_SOURCES += d_process.h
+b10_d2_SOURCES += d2_process.cc d2_process.h
+b10_d2_SOURCES += d_controller.cc d_controller.h
+b10_d2_SOURCES += d2_controller.cc d2_controller.h
nodist_b10_d2_SOURCES = d2_messages.h d2_messages.cc
EXTRA_DIST += d2_messages.mes
b10_d2_LDADD = $(top_builddir)/src/lib/log/libb10-log.la
b10_d2_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
+b10_d2_LDADD += $(top_builddir)/src/lib/cc/libb10-cc.la
+b10_d2_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
+b10_d2_LDADD += $(top_builddir)/src/lib/config/libb10-cfgclient.la
b10_d2dir = $(pkgdatadir)
b10_d2_DATA = d2.spec
{
"module_spec": {
"module_name": "D2",
- "module_description": "DHCP-DDNS process",
+ "module_description": "DHPC-DDNS Service",
"config_data": [
],
"commands": [
- {
- "command_name": "shutdown",
- "command_description": "Shuts down the D2 process.",
- "command_args": [
- {
- "item_name": "pid",
- "item_type": "integer",
- "item_optional": true
- }
- ]
- }
+ {
+ "command_name": "shutdown",
+ "command_description": "Shut down the stats httpd",
+ "command_args": [
+ {
+ "item_name": "pid",
+ "item_type": "integer",
+ "item_optional": true
+ }
+ ]
+ }
]
}
}
--- /dev/null
+// Copyright (C) 2012 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/d2_controller.h>
+#include <d2/d2_process.h>
+#include <d2/spec_config.h>
+
+namespace isc {
+namespace d2 {
+
+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 own the singleton in order to use it within BIND10 static function
+ // callbacks.
+ if (!getController()) {
+ setController(new D2Controller());
+ }
+
+ 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(getName().c_str(), getIOService()));
+}
+
+D2Controller::D2Controller()
+ : DControllerBase(D2_MODULE_NAME) {
+ // set the BIND10 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/d2.spec");
+ } else {
+ setSpecFileName(D2_SPECFILE_LOCATION);
+ }
+}
+
+D2Controller::~D2Controller() {
+}
+
+}; // end namespace isc::d2
+}; // end namespace isc
--- /dev/null
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef D2_CONTROLLER_H
+#define D2_CONTROLLER_H
+
+#include <d2/d_controller.h>
+
+namespace isc {
+namespace d2 {
+
+/// @brief Process Controller for D2 Process
+/// This class is the DHCP-DDNS specific derivation of DControllerBase. It
+/// creates and manages an instance of the DCHP-DDNS application process,
+/// D2Process.
+/// @TODO Currently, this class provides only the minimum required specialized
+/// behavior to run the DHCP-DDNS service. It may very well expand as the
+/// service implementation evolves. Some thought was given to making
+/// DControllerBase a templated class but the labor savings versus the
+/// potential number of virtual methods which may be overridden didn't seem
+/// worth the clutter at this point.
+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 a pointer reference to the singleton instance.
+ static DControllerBasePtr& instance();
+
+ /// @brief Destructor.
+ virtual ~D2Controller();
+
+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 applicatio 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 Constructor is declared private to maintain the integrity of
+ /// the singleton instance.
+ D2Controller();
+};
+
+}; // namespace isc::d2
+}; // namespace isc
+
+#endif
namespace isc {
namespace d2 {
-isc::log::Logger d2_logger("d2");
+const char* const D2_MODULE_NAME = "b10-d2";
+isc::log::Logger d2_logger(D2_MODULE_NAME);
} // namespace d2
} // namespace isc
namespace isc {
namespace d2 {
+/// @TODO need brief
+extern const char* const D2_MODULE_NAME;
+
/// Define the logger for the "d2" module part of b10-d2. We could define
/// a logger in each file, but we would want to define a common name to avoid
/// spelling mistakes, so it is just one small step from there to define a
/// module-common logger.
extern isc::log::Logger d2_logger;
+
} // namespace d2
} // namespace isc
$NAMESPACE isc::d2
-% D2_STARTING : process starting
-This is a debug message issued during a D2 process startup.
+% D2CTL_STARTING DHCP-DDNS controller starting, pid: %1
+This is an informational message issued when controller for DHCP-DDNS
+service first starts.
-% D2_START_INFO pid: %1, verbose: %2, standalone: %3
-This is a debug message issued during the D2 process startup.
-It lists some information about the parameters with which the
-process is running.
+% D2CTL_STOPPING DHCP-DDNS controller is exiting
+This is an informational message issued when the controller is exiting
+following a shut down (normal or otherwise) of the DDHCP-DDNS process.
-% D2_SHUTDOWN : process is performing a normal shutting down
-This is a debug message issued when a D2 process shuts down
-normally in response to command to stop.
+% D2PRC_SHUTDOWN DHCP-DDNS process is performing a normal shut down
+This is a debug message issued when the service process has been instructed
+to shut down by the controller.
+
+% D2PRC_RUN_ENTER process has entered the event loop
+This is a debug message issued when the D2 process enters it's
+run method.
+
+% D2PRC_RUN_EXIT process is exiting the event loop
+This is a debug message issued when the D2 process exits the
+in event loop.
+
+% D2PRC_FAILED process experienced a fatal error: %1
+This is a debug message issued when the D2 process encounters an
+unrecoverable error from within the event loop.
+
+% D2PRC_CONFIGURE new configuration received: %1
+This is a debug message issued when the D2 process configure method
+has been invoked.
+
+% D2PRC_COMMAND command directive received, command: %1 - args: %2
+This is a debug message issued when the D2 process command method
+has been invoked.
+
+% D2CTL_INIT_PROCESS initializing application proces
+This debug message is issued just before the controller attempts
+to create and initialize it's process instance.
+
+% D2CTL_SESSION_FAIL failed to establish BIND 10 session: %1
+The controller has failed to establish communication with the rest of BIND
+10 and will exit.
+
+% D2CTL_DISCONNECT_FAIL failed to disconnect from BIND 10 session: %1
+The controller has failed to terminate communication with the rest of BIND
+10.
+
+% D2CTL_STANDALONE skipping message queue, running standalone
+This is a debug message indicating that the controller is running in the
+process in standalone mode. This means it will not connected to the BIND10
+message queue. Standalone mode is only useful during program development,
+and should not be used in a production environment.
+
+% D2CTL_RUN_PROCESS starting application proces event loop
+This debug message is issued just before the controller invokes
+the application process run method.
+
+% D2CTL_FAILED process failed: %1
+The controller has encountered a fatal error and is terminating.
+The reason for the failure is included in the message.
+
+% D2CTL_CCSESSION_STARTING starting control channel session, specfile: %1
+This debug message is issued just before the controller attempts
+to establish a session with the BIND 10 control channel.
+
+% D2CTL_CCSESSION_ENDING ending control channel session
+This debug message is issued just before the controller attempts
+to disconnect from its session with the BIND 10 control channel.
+
+% D2CTL_CONFIG_STUB configuration stub handler called
+This debug message is issued when the dummy handler for configuration
+events is called. This only happens during intial startup.
+
+% D2CTL_CONFIG_LOAD_FAIL failed to load configuration: %1
+This critical error message indicates that the initial process
+configuration has failed. The service will start, but will not
+process requests until the configuration has been corrected.
+
+% D2CTL_COMMAND_RECEIVED received command %1, arguments: %2
+A debug message listing the command (and possible arguments) received
+from the BIND 10 control system by the controller.
+
+% D2CTL_NOT_RUNNING The application process instance is not running
+A warning message is issued when an attempt is made to shut down the
+the process when it is not running.
+
+% D2CTL_CONFIG_UPDATE updated configuration received: %1
+A debug message indicating that the controller has received an
+updated configuration from the BIND 10 configuration system.
--- /dev/null
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+
+#include <d2/d2_log.h>
+#include <d2/d_controller.h>
+#include <exceptions/exceptions.h>
+#include <log/logger_support.h>
+
+#include <sstream>
+
+namespace isc {
+namespace d2 {
+
+DControllerBasePtr DControllerBase::controller_;
+
+// Note that the constructor instantiates the controller's primary IOService.
+DControllerBase::DControllerBase(const char* name)
+ : name_(name), stand_alone_(false), verbose_(false),
+ spec_file_name_(""), io_service_(new isc::asiolink::IOService()){
+}
+
+void
+DControllerBase::setController(DControllerBase* controller) {
+ if (controller_) {
+ // This shouldn't happen, but let's make sure it can't be done.
+ // It represents a programmatic error.
+ isc_throw (DControllerBaseError,
+ "Multiple controller instances attempted.");
+ }
+
+ controller_ = DControllerBasePtr(controller);
+}
+
+int
+DControllerBase::launch(int argc, char* argv[]) {
+ int ret = EXIT_SUCCESS;
+
+ // Step 1 is to parse the command line arguments.
+ try {
+ parseArgs(argc, argv);
+ } catch (const InvalidUsage& ex) {
+ usage(ex.what());
+ return (EXIT_FAILURE);
+ }
+
+#if 1
+ //@TODO During initial development default to max log, no buffer
+ isc::log::initLogger(name_, isc::log::DEBUG,
+ isc::log::MAX_DEBUG_LEVEL, NULL, false);
+#else
+ // 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(name_,
+ ((verbose_ && stand_alone_)
+ ? isc::log::DEBUG : isc::log::INFO),
+ isc::log::MAX_DEBUG_LEVEL, NULL, !stand_alone_);
+#endif
+
+ LOG_DEBUG(d2_logger, DBGLVL_START_SHUT, D2CTL_STARTING).arg(getpid());
+ try {
+ // Step 2 is to create and init the application process.
+ initProcess();
+
+ // Next we connect if we are running integrated.
+ if (!stand_alone_) {
+ try {
+ establishSession();
+ } catch (const std::exception& ex) {
+ LOG_ERROR(d2_logger, D2CTL_SESSION_FAIL).arg(ex.what());
+ return (EXIT_FAILURE);
+ }
+ } else {
+ LOG_DEBUG(d2_logger, DBGLVL_START_SHUT, D2CTL_STANDALONE);
+ }
+
+ // Everything is clear for launch, so start the application's
+ // event loop.
+ runProcess();
+ } catch (const std::exception& ex) {
+ LOG_FATAL(d2_logger, D2CTL_FAILED).arg(ex.what());
+ ret = EXIT_FAILURE;
+ }
+
+ // If running integrated, always try to disconnect.
+ if (!stand_alone_) {
+ try {
+ disconnectSession();
+ } catch (const std::exception& ex) {
+ LOG_ERROR(d2_logger, D2CTL_DISCONNECT_FAIL).arg(ex.what());
+ ret = EXIT_FAILURE;
+ }
+ }
+
+ // All done, so bail out.
+ LOG_INFO(d2_logger, D2CTL_STOPPING);
+ return (ret);
+}
+
+void
+DControllerBase::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(":vs" + getCustomOpts());
+ while ((ch = getopt(argc, argv, opts.c_str())) != -1) {
+ switch (ch) {
+ case 'v':
+ // Enables verbose logging.
+ verbose_ = true;
+ break;
+
+ case 's':
+ // Enables stand alone or "BINDLESS" operation.
+ stand_alone_ = true;
+ break;
+
+ case '?': {
+ // We hit an invalid option.
+ std::stringstream tmp;
+ tmp << " unsupported option: [" << (char)optopt << "] "
+ << (!optarg ? "" : optarg);
+
+ isc_throw(InvalidUsage,tmp.str());
+ break;
+ }
+
+ default:
+ // We hit a valid custom option
+ if (!customOption(ch, optarg)) {
+ // this would be a programmatic err
+ std::stringstream tmp;
+ tmp << " Option listed but implemented?: [" <<
+ (char)ch << "] " << (!optarg ? "" : optarg);
+ isc_throw(InvalidUsage,tmp.str());
+ }
+ break;
+ }
+ }
+
+ // There was too much information on the command line.
+ if (argc > optind) {
+ std::stringstream tmp;
+ tmp << "extraneous command line information";
+ isc_throw(InvalidUsage,tmp.str());
+ }
+}
+
+bool
+DControllerBase::customOption(int /* option */, char* /*optarg*/)
+{
+ // Default implementation returns false.
+ return (false);
+}
+
+void
+DControllerBase::initProcess() {
+ LOG_DEBUG(d2_logger, DBGLVL_START_SHUT, D2CTL_INIT_PROCESS);
+
+ // Invoke virtual method to instantiate the application process.
+ try {
+ process_.reset(createProcess());
+ } catch (const std::exception& ex) {
+ isc_throw (DControllerBaseError, std::string("createProcess failed:")
+ + ex.what());
+ }
+
+ // This is pretty unlikely, but will test for it just to be safe..
+ if (!process_) {
+ isc_throw (DControllerBaseError, "createProcess returned NULL");
+ }
+
+ // Invoke application's init method
+ // @TODO This call may throw DProcessError
+ process_->init();
+}
+
+void
+DControllerBase::establishSession() {
+ LOG_DEBUG(d2_logger, DBGLVL_START_SHUT, D2CTL_CCSESSION_STARTING)
+ .arg(spec_file_name_);
+
+ // Create the BIND10 command control session with the our IOService.
+ cc_session_ = SessionPtr(new isc::cc::Session(
+ io_service_->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(
+ spec_file_name_, *cc_session_,
+ dummyConfigHandler, 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(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(d2_logger, D2CTL_CONFIG_LOAD_FAIL).arg(comment->str());
+ }
+
+ // Lastly, call onConnect. This allows deriving class to execute custom
+ // logic predicated by session connect.
+ onSessionConnect();
+}
+
+void
+DControllerBase::runProcess() {
+ LOG_DEBUG(d2_logger, DBGLVL_START_SHUT, D2CTL_RUN_PROCESS);
+ if (!process_) {
+ // This should not be possible.
+ isc_throw(DControllerBaseError, "Process not initialized");
+ }
+
+ // Invoke the applicatio process's run method. This may throw DProcessError
+ process_->run();
+}
+
+void DControllerBase::disconnectSession() {
+ LOG_DEBUG(d2_logger, DBGLVL_START_SHUT, D2CTL_CCSESSION_ENDING);
+
+ // Call virtual onDisconnect. Allows deriving class to execute custom
+ // logic prior to session loss.
+ onSessionDisconnect();
+
+ // 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
+DControllerBase::dummyConfigHandler(isc::data::ConstElementPtr) {
+ LOG_DEBUG(d2_logger, DBGLVL_START_SHUT, D2CTL_CONFIG_STUB);
+ return (isc::config::createAnswer(0, "Configuration accepted."));
+}
+
+isc::data::ConstElementPtr
+DControllerBase::configHandler(isc::data::ConstElementPtr new_config) {
+
+ LOG_DEBUG(d2_logger, DBGLVL_COMMAND, D2CTL_CONFIG_UPDATE)
+ .arg(new_config->str());
+
+ if (!controller_) {
+ // This should never happen as we install the handler after we
+ // instantiate the server.
+ isc::data::ConstElementPtr answer =
+ isc::config::createAnswer(1, "Configuration rejected,"
+ " Controller has not been initialized.");
+ return (answer);
+ }
+
+ // Invoke the instance method on the controller singleton.
+ return (controller_->updateConfig(new_config));
+}
+
+// Static callback which invokes non-static handler on singleton
+isc::data::ConstElementPtr
+DControllerBase::commandHandler(const std::string& command,
+ isc::data::ConstElementPtr args) {
+
+ LOG_DEBUG(d2_logger, DBGLVL_COMMAND, D2CTL_COMMAND_RECEIVED)
+ .arg(command).arg(args->str());
+
+ if (!controller_ ) {
+ // This should never happen as we install the handler after we
+ // instantiate the server.
+ isc::data::ConstElementPtr answer =
+ isc::config::createAnswer(1, "Command rejected,"
+ " Controller has not been initialized.");
+ return (answer);
+ }
+
+ // Invoke the instance method on the controller singleton.
+ return (controller_->executeCommand(command, args));
+}
+
+isc::data::ConstElementPtr
+DControllerBase::updateConfig(isc::data::ConstElementPtr new_config) {
+ isc::data::ConstElementPtr full_config;
+ if (stand_alone_) {
+ // @TODO Until there is a configuration manager to provide retrieval
+ // we'll just assume the incoming config is the full configuration set.
+ // It may also make more sense to isolate the controller from the
+ // configuration manager entirely. We could do something like
+ // process_->getFullConfig() here for stand-alone mode?
+ full_config = new_config;
+ } else {
+ 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.
+ 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 (process_->configure(merged_config));
+}
+
+
+isc::data::ConstElementPtr
+DControllerBase::executeCommand(const std::string& command,
+ isc::data::ConstElementPtr args) {
+ // Shutdown is univeral. If its not that, then try it as
+ // an custom command supported by the derivation. If that
+ // doesn't pan out either, than send to it the application
+ // as it may be supported there.
+ isc::data::ConstElementPtr answer;
+ if (command.compare(SHUT_DOWN_COMMAND) == 0) {
+ answer = shutdown();
+ } else {
+ // It wasn't shutdown, so may be a custom controller command.
+ int rcode = 0;
+ answer = customControllerCommand(command, args);
+ isc::config::parseAnswer(rcode, answer);
+ if (rcode == COMMAND_INVALID)
+ {
+ // It wasn't controller command, so may be an application command.
+ answer = process_->command(command,args);
+ }
+ }
+
+ return (answer);
+}
+
+isc::data::ConstElementPtr
+DControllerBase::customControllerCommand(const std::string& command,
+ isc::data::ConstElementPtr /* args */) {
+
+ // Default implementation always returns invalid command.
+ return (isc::config::createAnswer(COMMAND_INVALID,
+ "Unrecognized command:" + command));
+}
+
+isc::data::ConstElementPtr
+DControllerBase::shutdown() {
+ // @TODO TKM - not sure about io_service_->stop here
+ // IF application is using this service for all of its IO, stopping
+ // here would mean, no more work by the application.. UNLESS it resets
+ // it. People have discussed letting the application finish any in-progress
+ // updates before shutting down. If we don't stop it here, then
+ // application can't use io_service_->run(), it will never "see" the
+ // shutdown.
+ io_service_->stop();
+ if (process_) {
+ process_->shutdown();
+ } else {
+ // Not really a failure, but this condition is worth noting. In reality
+ // it should be pretty hard to cause this.
+ LOG_WARN(d2_logger, D2CTL_NOT_RUNNING);
+ }
+
+ return (isc::config::createAnswer(0, "Shutting down."));
+}
+
+void
+DControllerBase::usage(const std::string & text)
+{
+ if (text != "") {
+ std::cerr << "Usage error:" << text << std::endl;
+ }
+
+ std::cerr << "Usage: " << name_ << std::endl;
+ std::cerr << " -v: verbose output" << std::endl;
+ std::cerr << " -s: stand-alone mode (don't connect to BIND10)"
+ << std::endl;
+
+ std::cerr << getUsageText() << std::endl;
+
+ exit(EXIT_FAILURE);
+
+}
+
+DControllerBase::~DControllerBase() {
+}
+
+}; // namespace isc::d2
+}; // namespace isc
--- /dev/null
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef D_CONTROLLER_H
+#define D_CONTROLLER_H
+
+#include <asiolink/asiolink.h>
+#include <cc/data.h>
+#include <cc/session.h>
+#include <config/ccsession.h>
+#include <d2/d2_log.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 command line is invalid.
+class InvalidUsage : public isc::Exception {
+public:
+ InvalidUsage(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Exception thrown when the controller encounters an operational error.
+class DControllerBaseError : public isc::Exception {
+public:
+ DControllerBaseError (const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+
+/// @brief Defines a shared pointer to DControllerBaseBase.
+class DControllerBase;
+typedef boost::shared_ptr<DControllerBase> DControllerBasePtr;
+
+/// @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 Application Controller
+///
+/// DControllerBase is an abstract singleton which provides the framework and
+/// services for managing an application process that implements the
+/// DProcessBase interface. It allows the process to run either in
+/// integrated mode as a BIND10 module or stand-alone. It coordinates command
+/// line argument parsing, process instantiation and intialization, and runtime
+/// control through external command and configuration event handling.
+/// It creates the io_service_ instance which is used for runtime control
+/// events and passes the io_service into the application process at process
+/// creation. In integrated mode it is responsible for establishing BIND10
+/// session(s) and passes this io_service_ into the session creation method(s).
+/// It also provides the callback handlers for command and configuration events.
+/// NOTE: Derivations must supply their own static singleton instance method(s)
+/// for creating and fetching the instance. The base class declares the instance
+/// member in order for it to be available for BIND10 callback functions. This
+/// would not be required if BIND10 supported instance method callbacks.
+class DControllerBase : public boost::noncopyable {
+public:
+ /// @brief Constructor
+ ///
+ /// @param name name is a text label for the controller. Typically this
+ /// would be the BIND10 module name.
+ DControllerBase(const char* name);
+
+ /// @brief Destructor
+ virtual ~DControllerBase();
+
+ /// @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 BIND10 session(s) if in integrated mode
+ /// 4. start and wait on the application process event loop
+ /// 5. upon event loop completion, disconnect from BIND10 (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.
+ ///
+ /// @param argc is the number of command line arguments supplied
+ /// @param argv is the array of string (char *) command line arguments
+ ///
+ /// @retrun returns EXIT_SUCCESS upon normal application shutdown and
+ /// EXIT_FAILURE under abnormal terminations.
+ int launch(int argc, char* argv[]);
+
+ /// @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::ConfigData::getFullConfig.
+ ///
+ /// @param new_config new configuration.
+ ///
+ /// @return success configuration status.
+ static isc::data::ConstElementPtr
+ dummyConfigHandler(isc::data::ConstElementPtr new_config);
+
+ /// @brief A callback for handling all incoming configuration updates.
+ ///
+ /// As a pointer to this method is used as a callback in ASIO for
+ /// ModuleCCSession, it has to be static. It acts as a wrapper around
+ /// the virtual instance method, updateConfig.
+ ///
+ /// @param new_config textual representation of the new configuration
+ ///
+ /// @return status of the config update
+ static isc::data::ConstElementPtr
+ configHandler(isc::data::ConstElementPtr new_config);
+
+ /// @brief A callback for handling all incoming commands.
+ ///
+ /// As a pointer to this method is used as a callback in ASIO for
+ /// ModuleCCSession, it has to be static. It acts as a wrapper around
+ /// the virtual instance method, executeCommand.
+ ///
+ /// @param command textual representation of the command
+ /// @param args parameters of the command
+ ///
+ /// @return status of the processed command
+ static isc::data::ConstElementPtr
+ commandHandler(const std::string& command, isc::data::ConstElementPtr args);
+
+ /// @brief Instance method invoked by the configuration event handler and
+ /// which processes the actual configuration update. Provides behaviorial
+ /// 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 #h2957).
+ ///
+ /// @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);
+
+
+ /// @brief Instance method invoked by the command event handler and which
+ /// processes the actual command directive.
+ ///
+ /// It supports the execution of:
+ ///
+ /// 1. Stock controller commands - commands common to all DControllerBase
+ /// derivations. Currently there is only one, the shutdown command.
+ ///
+ /// 2. Custom controller commands - commands that the deriving controller
+ /// class implements. These commands are executed by the deriving
+ /// controller.
+ ///
+ /// 3. Custom application commands - commands supported by the application
+ /// process implementation. These commands are executed by the application
+ /// process.
+ ///
+ /// @param command is a string label representing the command to execute.
+ /// @param args is a set of arguments (if any) required for the given
+ /// command.
+ ///
+ /// @return an Element that contains the results of command composed
+ /// of an integer status value and a string explanation of the outcome.
+ /// The status value is one of the following:
+ /// D2::COMMAND_SUCCESS - Command executed successfully
+ /// D2::COMMAND_ERROR - Command is valid but suffered an operational
+ /// failure.
+ /// D2::COMMAND_INVALID - Command is not recognized as valid be either
+ /// the controller or the application process.
+ virtual isc::data::ConstElementPtr
+ executeCommand(const std::string& command, isc::data::ConstElementPtr args);
+
+protected:
+ /// @brief Virtual method that provides derivations the opportunity to
+ /// support additional command line options. It is invoked during command
+ /// line argument parsing (see parseArgs method) if the option is not
+ /// recognized as a stock DControllerBase option.
+ ///
+ /// @param option is the option "character" from the command line, without
+ /// any prefixing hypen(s)
+ /// @optarg optarg is the argument value (if one) associated with the option
+ ///
+ /// @return must return true if the option was valid, false is it is
+ /// invalid. (Note the default implementation always returns false.)
+ virtual bool customOption(int option, char *optarg);
+
+ /// @brief Abstract method that is responsible for instantiating the
+ /// application process instance. It is invoked by the controller after
+ /// command line argument parsing as part of the process initialization
+ /// (see initProcess method).
+ ///
+ /// @return returns a pointer to the new process instance (DProcessBase*)
+ /// or NULL if the create fails.
+ /// Note this value is subsequently wrapped in a smart pointer.
+ virtual DProcessBase* createProcess() = 0;
+
+ /// @brief Virtual method that provides derivations the opportunity to
+ /// support custom external commands executed by the controller. This
+ /// method is invoked by the processCommand if the received command is
+ /// not a stock controller command.
+ ///
+ /// @param command is a string label representing the command to execute.
+ /// @param args is a set of arguments (if any) required for the given
+ /// command.
+ ///
+ /// @return an Element that contains the results of command composed
+ /// of an integer status value and a string explanation of the outcome.
+ /// The status value is one of the following:
+ /// D2::COMMAND_SUCCESS - Command executed successfully
+ /// D2::COMMAND_ERROR - Command is valid but suffered an operational
+ /// failure.
+ /// D2::COMMAND_INVALID - Command is not recognized as a valid custom
+ /// controller command.
+ virtual isc::data::ConstElementPtr customControllerCommand(
+ const std::string& command, isc::data::ConstElementPtr args);
+
+ /// @brief Virtual method which is invoked after the controller successfully
+ /// establishes BIND10 connectivity. It provides an opportunity for the
+ /// derivation to execute any custom behavior associated with session
+ /// establishment.
+ ///
+ /// Note, it is not called when running stand-alone.
+ ///
+ /// @throw should hrow a DControllerBaseError if it fails.
+ virtual void onSessionConnect(){};
+
+ /// @brief Virtual method which is invoked as the first action taken when
+ /// the controller is terminating the session(s) with BIND10. It provides
+ /// an opportunity for the derivation to execute any custom behavior
+ /// associated with session termination.
+ ///
+ /// Note, it is not called when running stand-alone.
+ ///
+ /// @throw should hrow a DControllerBaseError if it fails.
+ virtual void onSessionDisconnect(){};
+
+ /// @brief Virtual method which can be used to contribute derivation
+ /// specific usage text. It is invoked by the usage() method under
+ /// invalid usage conditions.
+ ///
+ /// @return returns the desired text.
+ virtual const std::string getUsageText() {
+ return ("");
+ }
+
+ /// @brief Virtual method which returns a string containing the option
+ /// letters for any custom command line options supported by the derivaiton.
+ /// These are added to the stock options of "s" and "v" during command
+ /// line interpretation.
+ ///
+ /// @return returns a string containing the custom option letters.
+ virtual const std::string getCustomOpts() {
+ return ("");
+ }
+
+ /// @brief Supplies the controller name.
+ ///
+ /// @return returns the controller name string
+ const std::string& getName() {
+ return (name_);
+ }
+
+ /// @brief Supplies whether or not the controller is in stand alone mode.
+ ///
+ /// @return returns true if in stand alone mode, false otherwise
+ bool isStandAlone() {
+ return (stand_alone_);
+ }
+
+ /// @brief Method for enabling or disabling stand alone mode.
+ ///
+ /// @param value is the new value to assign the flag.
+ void setStandAlone(bool value) {
+ stand_alone_ = value;
+ }
+
+ /// @brief Supplies whether or not verbose logging is enabled.
+ ///
+ /// @return returns true if verbose logging is enabled.
+ bool isVerbose() {
+ return (verbose_);
+ }
+
+ /// @brief Method for enabling or disabling verbose logging.
+ ///
+ /// @param value is the new value to assign the flag.
+ void setVerbose(bool value) {
+ verbose_ = value;
+ }
+
+ /// @brief Getter for fetching the controller's IOService
+ ///
+ /// @return returns a pointer reference to the IOService.
+ IOServicePtr& getIOService() {
+ return (io_service_);
+ }
+
+ /// @brief Getter for fetching the name of the controller's BIND10 spec
+ /// file.
+ ///
+ /// @return returns a the file name string.
+ const std::string& getSpecFileName() {
+ return (spec_file_name_);
+ }
+
+ /// @brief Setter for setting the name of the controller's BIND10 spec file.
+ ///
+ /// @param value is the file name string.
+ void setSpecFileName(const std::string& spec_file_name) {
+ spec_file_name_ = spec_file_name;
+ }
+
+ /// @brief Static getter which returns the singleton instance.
+ ///
+ /// @return returns a pointer reference to the private singleton instance
+ /// member.
+ static DControllerBasePtr& getController() {
+ return (controller_);
+ }
+
+ /// @brief Static setter which returns the singleton instance.
+ ///
+ /// @return returns a pointer reference to the private singleton instance
+ /// member.
+ /// @throw throws DControllerBase error if an attempt is made to set the
+ /// instance a second time.
+ static void setController(DControllerBase* controller);
+
+private:
+ /// @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 Instantiates the application process and then initializes it.
+ /// This is the second step taken during launch, following successful
+ /// command line parsing. It is used to invoke the derivation-specific
+ /// implementation of createProcess, following by an invoking of the
+ /// newly instanatiated process's init method.
+ ///
+ /// @throw throws DControllerBaseError or indirectly DProcessBaseError
+ /// if there is a failure creating or initializing the application process.
+ void initProcess();
+
+ /// @brief Establishes connectivity with BIND10. This method is used
+ /// invoked during launch, if running in integrated mode, following
+ /// successful process initialization. It is responsible for establishing
+ /// the BIND10 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 BIND10 framework may throw std::exceptions.
+ void establishSession();
+
+ /// @brief Invokes the application process's event loop,(DBaseProcess::run).
+ /// It is called during launch only after successfully completing the
+ /// requisted setup: comamnd line parsing, application initialization,
+ /// and session establishment (if not stand-alone).
+ /// The process event loop is expected to only return upon application
+ /// shutdown either in response to the shutdown command or due to an
+ /// unrecovarable error.
+ ///
+ // @throw throws DControllerBaseError or indirectly DProcessBaseError
+ void runProcess();
+
+ /// @brief Terminates connectivity with BIND10. 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
+ /// BIND10 config and control sessions.
+ ///
+ /// @throw the BIND10 framework may throw std:exceptions.
+ void disconnectSession();
+
+ /// @brief Initiates shutdown procedure. This method is invoked
+ /// by executeCommand in response to the shutdown command. It will invoke
+ /// the application process's shutdown method, which causes the process to
+ /// exit it's event loop.
+ ///
+ /// @return returns an Element that contains the results of shutdown
+ /// attempt composed of an integer status value (0 means successful,
+ /// non-zero means failure), and a string explanation of the outcome.
+ isc::data::ConstElementPtr shutdown();
+
+ /// @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 Text label for the controller. Typically this would be the
+ /// BIND10 module name.
+ std::string name_;
+
+ /// @brief Indicates if the controller stand alone mode is enabled. When
+ /// enabled, the controller will not establish connectivity with BIND10.
+ bool stand_alone_;
+ /// @brief Indicates if the verbose logging mode is enabled.
+
+ bool verbose_;
+ /// @brief The absolute file name of the BIND10 spec file.
+ std::string spec_file_name_;
+
+ /// @brief Pointer to the instance of the process.
+ ///
+ /// This is required for config and command handlers to gain access to
+ /// the process
+ DProcessBasePtr process_;
+
+ /// @brief Shared pointer to an IOService object, used for ASIO operations.
+ IOServicePtr io_service_;
+
+ /// @brief Helper session object that represents raw connection to msgq.
+ SessionPtr cc_session_;
+
+ /// @brief Session that receives configuration and commands.
+ ModuleCCSessionPtr config_session_;
+
+ /// @brief Singleton instance value.
+ static DControllerBasePtr controller_;
+
+// DControllerTest is named a friend class to faciliate unit testing while
+// leaving the intended member scopes intact.
+friend class DControllerTest;
+};
+
+}; // namespace isc::d2
+}; // namespace isc
+
+#endif
#include <config.h>
#include <d2/d2_log.h>
+#include <d2/d2_controller.h>
#include <log/logger_support.h>
#include <log/logger_manager.h>
using namespace std;
/// This file contains entry point (main() function) for standard DHCP-DDNS
-/// process, b10-d2, component for BIND10 framework. It parses command-line
-/// arguments and instantiates D2Controller class that is responsible for
-/// establishing connection with msgq (receiving commands and configuration)
-/// and also creating D2Server object as well.
-///
-/// For detailed explanation or relations between main(), D2Controller,
-/// D2Server and other classes, see \ref d2Session.
-
-namespace {
-
-const char* const D2_NAME = "b10-d2";
-
-void
-usage() {
- cerr << "Usage: " << D2_NAME << " [-v] [-s]" << endl;
- cerr << " -s: stand-alone mode (don't connect to BIND10)" << endl;
- cerr << " -v: verbose output (only when in stand-alone mode" << endl;
- exit(EXIT_FAILURE);
-}
-} // end of anonymous namespace
-
+/// process, b10-dhcp-ddns, component for BIND10 framework. It fetches
+/// the D2Controller singleton instance and turns control over to it.
+/// The controller will return with upon shutdown with a avlue of either
+/// EXIT_SUCCESS or EXIT_FAILURE.
int
main(int argc, char* argv[]) {
- int ch;
-
- // @TODO NOTE these parameters are preliminary only. They are here to
- // for symmetry with the DHCP servers. They may or may not
- // become part of the eventual implementation.
-
- bool stand_alone = false; // Should be connect to BIND10 msgq?
- bool verbose_mode = false; // Should server be verbose?
-
- while ((ch = getopt(argc, argv, "vsp:")) != -1) {
- switch (ch) {
- case 'v':
- verbose_mode = true;
- break;
-
- case 's':
- stand_alone = true;
- break;
-
- default:
- usage();
- }
- }
- // Check for extraneous parameters.
- if (argc > optind) {
- usage();
- }
+ // Instantiate/fetch the DHCP-DDNS application controller singleton.
+ DControllerBasePtr& controller = D2Controller::instance();
- // Initialize logging. If verbose, we'll use maximum verbosity.
- // If standalone is enabled, do not buffer initial log messages
- // Verbose logging is only enabled when in stand alone mode.
- isc::log::initLogger(D2_NAME,
- ((verbose_mode && stand_alone)
- ? isc::log::DEBUG : isc::log::INFO),
- isc::log::MAX_DEBUG_LEVEL, NULL, !stand_alone);
- LOG_INFO(d2_logger, D2_STARTING);
- LOG_DEBUG(d2_logger, DBGLVL_START_SHUT, D2_START_INFO)
- .arg(getpid()).arg(verbose_mode ? "yes" : "no")
- .arg(stand_alone ? "yes" : "no" );
+ // Launch the controller passing in command line arguments.
+ int ret = controller->launch(argc, argv);
- // For now we will sleep awhile to simulate doing something.
- // Without at least a sleep, the process will start, exit and be
- // restarted by Bind10/Init endlessley in a rapid succession.
- sleep(1000);
- LOG_INFO(d2_logger, D2_SHUTDOWN);
- return (EXIT_SUCCESS);
+ // Exit program with the controller's return code.
+ return (ret);
}
--- /dev/null
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#define D2_SPECFILE_LOCATION "/labs/toms_lab/var/test_1/share/bind10/d2.spec"
TESTS += d2_unittests
d2_unittests_SOURCES = ../d2_log.h ../d2_log.cc
+d2_unittests_SOURCES += ../d_process.h
+d2_unittests_SOURCES += ../d_controller.cc ../d2_controller.h
+d2_unittests_SOURCES += ../d2_process.cc ../d2_process.h
+d2_unittests_SOURCES += ../d2_controller.cc ../d2_controller.h
+d2_unittests_SOURCES += d_test_stubs.cc d_test_stubs.h
d2_unittests_SOURCES += d2_unittests.cc
+d2_unittests_SOURCES += d2_process_unittests.cc
+d2_unittests_SOURCES += d_controller_unittests.cc
+d2_unittests_SOURCES += d2_controller_unittests.cc
nodist_d2_unittests_SOURCES = ../d2_messages.h ../d2_messages.cc
d2_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
d2_unittests_LDADD = $(GTEST_LDADD)
d2_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
d2_unittests_LDADD += $(top_builddir)/src/lib/log/libb10-log.la
+d2_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
+d2_unittests_LDADD += $(top_builddir)/src/lib/cc/libb10-cc.la
+d2_unittests_LDADD += $(top_builddir)/src/lib/config/libb10-cfgclient.la
endif
noinst_PROGRAMS = $(TESTS)
--- /dev/null
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config/ccsession.h>
+#include <d_test_stubs.h>
+#include <d2/d2_controller.h>
+#include <d2/spec_config.h>
+
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <gtest/gtest.h>
+
+#include <config.h>
+#include <sstream>
+
+using namespace boost::posix_time;
+
+namespace isc {
+namespace d2 {
+
+//typedef DControllerTestWrapper<D2Controller> D2ControllerTest;
+
+class D2ControllerTest : public DControllerTest {
+public:
+ /// @brief Constructor
+ D2ControllerTest() : DControllerTest(D2Controller::instance) {
+ }
+
+ /// @brief Destructor
+ ~D2ControllerTest() {
+ }
+};
+
+/// @brief basic instantiation
+// @TODO This test is simplistic and will need to be augmented
+TEST_F(D2ControllerTest, basicInstanceTesting) {
+ DControllerBasePtr& controller = DControllerTest::getController();
+ ASSERT_TRUE(controller);
+ EXPECT_TRUE(checkName(D2_MODULE_NAME));
+ EXPECT_TRUE(checkSpecFileName(D2_SPECFILE_LOCATION));
+ EXPECT_TRUE(checkIOService());
+
+ // Process should NOT exist yet
+ EXPECT_FALSE(checkProcess());
+}
+
+/// @TODO brief Verifies command line processing.
+TEST_F(D2ControllerTest, commandLineArgs) {
+ char* argv[] = { (char*)"progName", (char*)"-s", (char*)"-v" };
+ int argc = 3;
+
+ EXPECT_TRUE(checkStandAlone(false));
+ EXPECT_TRUE(checkVerbose(false));
+
+ EXPECT_NO_THROW(parseArgs(argc, argv));
+
+ EXPECT_TRUE(checkStandAlone(true));
+ EXPECT_TRUE(checkVerbose(true));
+
+ char* argv2[] = { (char*)"progName", (char*)"-bs" };
+ argc = 2;
+ EXPECT_THROW (parseArgs(argc, argv2), InvalidUsage);
+}
+
+/// @TODO brief initProcess testing.
+TEST_F(D2ControllerTest, initProcessTesting) {
+ ASSERT_NO_THROW(initProcess());
+ EXPECT_TRUE(checkProcess());
+}
+
+/// @TODO brief test launch
+TEST_F(D2ControllerTest, launchDirectShutdown) {
+ // command line to run standalone
+ char* argv[] = { (char*)"progName", (char*)"-s", (char*)"-v" };
+ int argc = 3;
+
+ // Use an asiolink IntervalTimer and callback to generate the
+ // shutdown invocation. (Note IntervalTimer setup is in milliseconds).
+ isc::asiolink::IntervalTimer timer(*getIOService());
+ timer.setup(genShutdownCallback, 2 * 1000);
+
+ // Record start time, and invoke launch().
+ ptime start = microsec_clock::universal_time();
+ int rcode = launch(argc, argv);
+
+ // Record stop time.
+ ptime stop = microsec_clock::universal_time();
+
+ // Verify normal shutdown status.
+ EXPECT_EQ(EXIT_SUCCESS, rcode);
+
+ // Verify that duration of the run invocation is the same as the
+ // timer duration. This demonstrates that the shutdown was driven
+ // by an io_service event and callback.
+ time_duration elapsed = stop - start;
+ EXPECT_TRUE(elapsed.total_milliseconds() >= 1900 &&
+ elapsed.total_milliseconds() <= 2100);
+}
+
+/// @TODO brief test launch
+TEST_F(D2ControllerTest, launchRuntimeError) {
+ // command line to run standalone
+ char* argv[] = { (char*)"progName", (char*)"-s", (char*)"-v" };
+ int argc = 3;
+
+ // Use an asiolink IntervalTimer and callback to generate the
+ // shutdown invocation. (Note IntervalTimer setup is in milliseconds).
+ isc::asiolink::IntervalTimer timer(*getIOService());
+ timer.setup(genFatalErrorCallback, 2 * 1000);
+
+ // Record start time, and invoke launch().
+ ptime start = microsec_clock::universal_time();
+ int rcode = launch(argc, argv);
+
+ // Record stop time.
+ ptime stop = microsec_clock::universal_time();
+
+ // Verify normal shutdown status.
+ EXPECT_EQ(EXIT_SUCCESS, rcode);
+
+ // Verify that duration of the run invocation is the same as the
+ // timer duration. This demonstrates that the shutdown was driven
+ // by an io_service event and callback.
+ time_duration elapsed = stop - start;
+ EXPECT_TRUE(elapsed.total_milliseconds() >= 1900 &&
+ elapsed.total_milliseconds() <= 2100);
+}
+
+/// @TODO brief test configUpateTests
+/// This really tests just the ability of the handlers to invoke the necessary
+/// chain, and error conditions. Configuration parsing and retrieval should be
+/// tested as part of the d2 configuration management implementation.
+TEST_F(D2ControllerTest, configUpdateTests) {
+ int rcode = -1;
+ isc::data::ConstElementPtr answer;
+
+ ASSERT_NO_THROW(initProcess());
+ EXPECT_TRUE(checkProcess());
+
+ // Create a configuration set. Content is arbitrary, just needs to be
+ // valid JSON.
+ std::string config = "{ \"test-value\": 1000 } ";
+ isc::data::ElementPtr config_set = isc::data::Element::fromJSON(config);
+
+ // We are not stand-alone, so configuration should be rejected as there is
+ // no session. This is a pretty contrived situation that shouldn't be
+ // possible other than the handler being called directly (like this does).
+ answer = DControllerBase::configHandler(config_set);
+ isc::config::parseAnswer(rcode, answer);
+ EXPECT_EQ(1, rcode);
+
+ // Verify that in stand alone we get a successful update result.
+ setStandAlone(true);
+ answer = DControllerBase::configHandler(config_set);
+ isc::config::parseAnswer(rcode, answer);
+ EXPECT_EQ(0, rcode);
+}
+
+TEST_F(D2ControllerTest, executeCommandTests) {
+ int rcode = -1;
+ isc::data::ConstElementPtr answer;
+ isc::data::ElementPtr arg_set;
+
+ ASSERT_NO_THROW(initProcess());
+ EXPECT_TRUE(checkProcess());
+
+ // Verify that shutdown command returns CommandSuccess response.
+ //answer = executeCommand(SHUT_DOWN_COMMAND, isc::data::ElementPtr());
+ answer = DControllerBase::commandHandler(SHUT_DOWN_COMMAND, arg_set);
+ isc::config::parseAnswer(rcode, answer);
+ EXPECT_EQ(COMMAND_SUCCESS, rcode);
+
+ // Verify that an unknown command returns an InvalidCommand response.
+ std::string bogus_command("bogus");
+ answer = DControllerBase::commandHandler(bogus_command, arg_set);
+ isc::config::parseAnswer(rcode, answer);
+ EXPECT_EQ(COMMAND_INVALID, rcode);
+}
+
+}; // end of isc::d2 namespace
+}; // end of isc namespace
print("Note: Simple test to verify that D2 server can be started.")
# note that "-s" for stand alone is necessary in order to flush the log output
# soon enough to catch it.
- (returncode, output, error) = self.runCommand(["../b10-d2", "-s"])
+ (returncode, output, error) = self.runCommand(["../b10-d2", "-s", "-v"])
output_text = str(output) + str(error)
- self.assertEqual(output_text.count("D2_STARTING"), 1)
+ self.assertEqual(output_text.count("D2CTL_STARTING"), 1)
if __name__ == '__main__':
unittest.main()
--- /dev/null
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config/ccsession.h>
+#include <d_test_stubs.h>
+#include <d2/spec_config.h>
+
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <gtest/gtest.h>
+
+#include <config.h>
+#include <sstream>
+
+using namespace boost::posix_time;
+
+namespace isc {
+namespace d2 {
+
+/// @brief basic instantiation
+// @TODO This test is simplistic and will need to be augmented
+TEST_F(DStubControllerTest, basicInstanceTesting) {
+ DControllerBasePtr& controller = DControllerTest::getController();
+ ASSERT_TRUE(controller);
+ EXPECT_TRUE(checkName(D2_MODULE_NAME));
+ EXPECT_TRUE(checkSpecFileName(D2_SPECFILE_LOCATION));
+ EXPECT_TRUE(checkIOService());
+
+ // Process should NOT exist yet
+ EXPECT_FALSE(checkProcess());
+}
+
+/// @TODO brief Verifies command line processing.
+TEST_F(DStubControllerTest, commandLineArgs) {
+ char* argv[] = { (char*)"progName", (char*)"-s", (char*)"-v" };
+ int argc = 3;
+
+ EXPECT_TRUE(checkStandAlone(false));
+ EXPECT_TRUE(checkVerbose(false));
+
+ EXPECT_NO_THROW(parseArgs(argc, argv));
+
+ EXPECT_TRUE(checkStandAlone(true));
+ EXPECT_TRUE(checkVerbose(true));
+
+ char* argv1[] = { (char*)"progName", (char*)"-x" };
+ argc = 2;
+ EXPECT_NO_THROW (parseArgs(argc, argv1));
+
+ char* argv2[] = { (char*)"progName", (char*)"-bs" };
+ argc = 2;
+ EXPECT_THROW (parseArgs(argc, argv2), InvalidUsage);
+}
+
+/// @TODO brief initProcess testing.
+TEST_F(DStubControllerTest, initProcessTesting) {
+
+ SimFailure::set(SimFailure::ftCreateProcessException);
+ EXPECT_THROW(initProcess(), DControllerBaseError);
+ EXPECT_FALSE(checkProcess());
+
+ SimFailure::set(SimFailure::ftCreateProcessNull);
+ EXPECT_THROW(initProcess(), DControllerBaseError);
+ EXPECT_FALSE(checkProcess());
+
+ resetController();
+ SimFailure::set(SimFailure::ftProcessInit);
+ EXPECT_THROW(initProcess(), DProcessBaseError);
+
+ resetController();
+ EXPECT_FALSE(checkProcess());
+
+ ASSERT_NO_THROW(initProcess());
+ EXPECT_TRUE(checkProcess());
+}
+
+/// @TODO brief establishSession failure testing.
+/// Testing with BIND10 is out of scope for unit tests
+TEST_F(DStubControllerTest, sessionFailureTesting) {
+ ASSERT_NO_THROW(initProcess());
+ EXPECT_TRUE(checkProcess());
+
+ EXPECT_THROW(establishSession(), std::exception);
+}
+
+/// @TODO brief test launch
+TEST_F(DStubControllerTest, launchDirectShutdown) {
+ // command line to run standalone
+ char* argv[] = { (char*)"progName", (char*)"-s", (char*)"-v" };
+ int argc = 3;
+
+ // Use an asiolink IntervalTimer and callback to generate the
+ // shutdown invocation. (Note IntervalTimer setup is in milliseconds).
+ isc::asiolink::IntervalTimer timer(*getIOService());
+ timer.setup(genShutdownCallback, 2 * 1000);
+
+ // Record start time, and invoke launch().
+ ptime start = microsec_clock::universal_time();
+ int rcode = launch(argc, argv);
+
+ // Record stop time.
+ ptime stop = microsec_clock::universal_time();
+
+ // Verify normal shutdown status.
+ EXPECT_EQ(EXIT_SUCCESS, rcode);
+
+ // Verify that duration of the run invocation is the same as the
+ // timer duration. This demonstrates that the shutdown was driven
+ // by an io_service event and callback.
+ time_duration elapsed = stop - start;
+ EXPECT_TRUE(elapsed.total_milliseconds() >= 1900 &&
+ elapsed.total_milliseconds() <= 2100);
+}
+
+/// @TODO brief test launch
+TEST_F(DStubControllerTest, launchRuntimeError) {
+ // command line to run standalone
+ char* argv[] = { (char*)"progName", (char*)"-s", (char*)"-v" };
+ int argc = 3;
+
+ // Use an asiolink IntervalTimer and callback to generate the
+ // shutdown invocation. (Note IntervalTimer setup is in milliseconds).
+ isc::asiolink::IntervalTimer timer(*getIOService());
+ timer.setup(genFatalErrorCallback, 2 * 1000);
+
+ // Record start time, and invoke launch().
+ ptime start = microsec_clock::universal_time();
+ int rcode = launch(argc, argv);
+
+ // Record stop time.
+ ptime stop = microsec_clock::universal_time();
+
+ // Verify normal shutdown status.
+ EXPECT_EQ(EXIT_SUCCESS, rcode);
+
+ // Verify that duration of the run invocation is the same as the
+ // timer duration. This demonstrates that the shutdown was driven
+ // by an io_service event and callback.
+ time_duration elapsed = stop - start;
+ EXPECT_TRUE(elapsed.total_milliseconds() >= 1900 &&
+ elapsed.total_milliseconds() <= 2100);
+}
+
+/// @TODO brief test configUpateTests
+/// This really tests just the ability of the handlers to invoke the necessary
+/// chain, and error conditions. Configuration parsing and retrieval should be
+/// tested as part of the d2 configuration management implementation.
+TEST_F(DStubControllerTest, configUpdateTests) {
+ int rcode = -1;
+ isc::data::ConstElementPtr answer;
+
+ ASSERT_NO_THROW(initProcess());
+ EXPECT_TRUE(checkProcess());
+
+ // Create a configuration set. Content is arbitrary, just needs to be
+ // valid JSON.
+ std::string config = "{ \"test-value\": 1000 } ";
+ isc::data::ElementPtr config_set = isc::data::Element::fromJSON(config);
+
+ // We are not stand-alone, so configuration should be rejected as there is
+ // no session. This is a pretty contrived situation that shouldn't be
+ // possible other than the handler being called directly (like this does).
+ answer = DControllerBase::configHandler(config_set);
+ isc::config::parseAnswer(rcode, answer);
+ EXPECT_EQ(1, rcode);
+
+ // Verify that in stand alone we get a successful update result.
+ setStandAlone(true);
+ answer = DControllerBase::configHandler(config_set);
+ isc::config::parseAnswer(rcode, answer);
+ EXPECT_EQ(0, rcode);
+
+ // Verify that an error in process configure method is handled.
+ SimFailure::set(SimFailure::ftProcessConfigure);
+ answer = DControllerBase::configHandler(config_set);
+ isc::config::parseAnswer(rcode, answer);
+ EXPECT_EQ(1, rcode);
+
+}
+
+TEST_F(DStubControllerTest, executeCommandTests) {
+ int rcode = -1;
+ isc::data::ConstElementPtr answer;
+ isc::data::ElementPtr arg_set;
+
+ ASSERT_NO_THROW(initProcess());
+ EXPECT_TRUE(checkProcess());
+
+ // Verify that shutdown command returns CommandSuccess response.
+ answer = DControllerBase::commandHandler(SHUT_DOWN_COMMAND, arg_set);
+ isc::config::parseAnswer(rcode, answer);
+ EXPECT_EQ(COMMAND_SUCCESS, rcode);
+
+ // Verify that a valid custom controller command returns CommandSuccess
+ // response.
+ answer = DControllerBase::commandHandler(DStubController::
+ custom_ctl_command_, arg_set);
+ isc::config::parseAnswer(rcode, answer);
+ EXPECT_EQ(COMMAND_SUCCESS, rcode);
+
+ // Verify that an unknown command returns an InvalidCommand response.
+ std::string bogus_command("bogus");
+ answer = DControllerBase::commandHandler(bogus_command, arg_set);
+ isc::config::parseAnswer(rcode, answer);
+ EXPECT_EQ(COMMAND_INVALID, rcode);
+
+ // Verify that a valid custom process command returns CommandSuccess
+ // response.
+ answer = DControllerBase::commandHandler(DStubProcess::
+ custom_process_command_, arg_set);
+ isc::config::parseAnswer(rcode, answer);
+ EXPECT_EQ(COMMAND_SUCCESS, rcode);
+}
+
+}; // end of isc::d2 namespace
+}; // end of isc namespace
--- /dev/null
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <d2/spec_config.h>
+#include <d2/tests/d_test_stubs.h>
+
+using namespace asio;
+
+namespace isc {
+namespace d2 {
+
+SimFailure::FailureType SimFailure::failure_type_ = SimFailure::ftNoFailure;
+
+const std::string DStubProcess::custom_process_command_("valid_prc_command");
+
+DStubProcess::DStubProcess(const char* name, IOServicePtr io_service)
+ : DProcessBase(name, io_service) {
+};
+
+void
+DStubProcess::init() {
+ if (SimFailure::shouldFailOn(SimFailure::ftProcessInit)) {
+ // Simulates a failure to instantiate the process.
+ isc_throw(DProcessBaseError, "DStubProcess simulated init failure");
+ }
+};
+
+int
+DStubProcess::run() {
+ // Until shut down or an fatal error occurs, wait for and
+ // execute a single callback. This is a preliminary implementation
+ // that is likely to evolve as development progresses.
+ // To use run(), the "managing" layer must issue an io_service::stop
+ // or the call to run will continue to block, and shutdown will not
+ // occur.
+ LOG_DEBUG(d2_logger, DBGLVL_START_SHUT, D2PRC_RUN_ENTER);
+ IOServicePtr& io_service = getIoService();
+ while (!shouldShutdown()) {
+ try {
+ io_service->run_one();
+ } catch (const std::exception& ex) {
+ LOG_FATAL(d2_logger, D2PRC_FAILED).arg(ex.what());
+ return (EXIT_FAILURE);
+ }
+ }
+
+ LOG_DEBUG(d2_logger, DBGLVL_START_SHUT, D2PRC_RUN_EXIT);
+ return (EXIT_SUCCESS);
+};
+
+int
+DStubProcess::shutdown() {
+ LOG_DEBUG(d2_logger, DBGLVL_START_SHUT, D2PRC_SHUTDOWN);
+ setShutdownFlag(true);
+ return (0);
+}
+
+isc::data::ConstElementPtr
+DStubProcess::configure(isc::data::ConstElementPtr config_set) {
+ LOG_DEBUG(d2_logger, DBGLVL_TRACE_BASIC,
+ D2PRC_CONFIGURE).arg(config_set->str());
+
+ if (SimFailure::shouldFailOn(SimFailure::ftProcessConfigure)) {
+ return (isc::config::createAnswer(1,
+ "Simulated process configuration error."));
+ }
+
+ return (isc::config::createAnswer(0, "Configuration accepted."));
+}
+
+isc::data::ConstElementPtr
+DStubProcess::command(const std::string& command, isc::data::ConstElementPtr args){
+ LOG_DEBUG(d2_logger, DBGLVL_TRACE_BASIC,
+ D2PRC_COMMAND).arg(command).arg(args->str());
+
+ isc::data::ConstElementPtr answer;
+ if (SimFailure::shouldFailOn(SimFailure::ftProcessCommand)) {
+ answer = isc::config::createAnswer(COMMAND_ERROR,
+ "SimFailure::ftProcessCommand");
+ } else if (command.compare(custom_process_command_) == 0) {
+ answer = isc::config::createAnswer(COMMAND_SUCCESS, "Command accepted");
+ } else {
+ answer = isc::config::createAnswer(COMMAND_INVALID,
+ "Unrecognized command:" + command);
+ }
+
+ return (answer);
+}
+
+DStubProcess::~DStubProcess() {
+};
+
+//************************** DStubController *************************
+
+const std::string DStubController::custom_ctl_command_("valid_ctrl_command");
+
+DControllerBasePtr&
+DStubController::instance() {
+ if (!getController()) {
+ setController(new DStubController());
+ }
+
+ return (getController());
+}
+
+DStubController::DStubController()
+ : DControllerBase(D2_MODULE_NAME) {
+
+ if (getenv("B10_FROM_BUILD")) {
+ setSpecFileName(std::string(getenv("B10_FROM_BUILD")) +
+ "/src/bin/d2/d2.spec");
+ } else {
+ setSpecFileName(D2_SPECFILE_LOCATION);
+ }
+}
+
+bool
+DStubController::customOption(int option, char* /* optarg */)
+{
+ // Default implementation returns false
+ if (option == 'x') {
+ return (true);
+ }
+
+ return (false);
+}
+
+DProcessBase* DStubController::createProcess() {
+ if (SimFailure::shouldFailOn(SimFailure::ftCreateProcessException)) {
+ // Simulates a failure to instantiate the process due to exception.
+ throw std::runtime_error("SimFailure::ftCreateProcess");
+ }
+
+ if (SimFailure::shouldFailOn(SimFailure::ftCreateProcessNull)) {
+ // Simulates a failure to instantiate the process.
+ return (NULL);
+ }
+
+ // This should be a successful instantiation.
+ return (new DStubProcess(getName().c_str(), getIOService()));
+}
+
+isc::data::ConstElementPtr
+DStubController::customControllerCommand(const std::string& command,
+ isc::data::ConstElementPtr /* args */) {
+ isc::data::ConstElementPtr answer;
+ if (SimFailure::shouldFailOn(SimFailure::ftControllerCommand)) {
+ answer = isc::config::createAnswer(COMMAND_ERROR,
+ "SimFailure::ftControllerCommand");
+ } else if (command.compare(custom_ctl_command_) == 0) {
+ answer = isc::config::createAnswer(COMMAND_SUCCESS, "Command accepted");
+ } else {
+ answer = isc::config::createAnswer(COMMAND_INVALID,
+ "Unrecognized command:" + command);
+ }
+
+ return (answer);
+}
+
+const std::string DStubController::getCustomOpts(){
+ return (std::string("x"));
+}
+
+DStubController::~DStubController() {
+}
+
+DControllerBasePtr DControllerTest::controller_under_test_;
+DControllerTest::InstanceGetter DControllerTest::instanceGetter_ = NULL;
+
+}; // namespace isc::d2
+}; // namespace isc
--- /dev/null
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef D_TEST_STUBS_H
+#define D_TEST_STUBS_H
+
+#include <asiolink/asiolink.h>
+#include <cc/data.h>
+#include <cc/session.h>
+#include <config/ccsession.h>
+
+#include <d2/d_controller.h>
+#include <gtest/gtest.h>
+
+namespace isc {
+namespace d2 {
+
+/// @brief Class is used to set a globally accessible value that indicates
+/// a specific type of failure to simulate. Test derivations of base classes
+/// can excercize error handling code paths by testing for specific SimFailure
+/// values at the appropriate places and then causing the error to "occur".
+/// The class consists of an enumerated set of failures, and static methods
+/// for getting, setting, and testing the current value.
+class SimFailure {
+public:
+ enum FailureType {
+ ftUnknown = -1,
+ ftNoFailure = 0,
+ ftCreateProcessException,
+ ftCreateProcessNull,
+ ftProcessInit,
+ ftProcessConfigure,
+ ftControllerCommand,
+ ftProcessCommand
+ };
+
+ /// @brief Sets the SimFailure value to the given value.
+ ///
+ /// @param value is the new value to assign to the global value.
+ static void set(enum FailureType value) {
+ failure_type_ = value;
+ }
+
+ /// @brief Gets the current global SimFailure value
+ ///
+ /// @return returns the current SimFailure value
+ static enum FailureType get() {
+ return (failure_type_);
+ }
+
+ /// @brief One-shot test of the SimFailure value. If the global
+ /// SimFailure value is equal to the given value, clear the global
+ /// value and return true. This makes it convenient for code to
+ /// test and react without having to explicitly clear the global
+ /// value.
+ ///
+ /// @param value is the value against which the global value is
+ /// to be compared.
+ ///
+ /// @return returns true if current SimFailure value matches the
+ /// given value.
+ static bool shouldFailOn(enum FailureType value) {
+ if (failure_type_ == value) {
+ clear();
+ return (true);
+ }
+
+ return (false);
+ }
+
+ static void clear() {
+ failure_type_ = ftNoFailure;
+ }
+
+ static enum FailureType failure_type_;
+};
+
+/// @brief Test Derivation of the DProcessBase class.
+///
+/// This class is used primarily to server as a test process class for testing
+/// DControllerBase. It provides minimal, but sufficient implementation to
+/// test the majority of DControllerBase functionality.
+class DStubProcess : public DProcessBase {
+public:
+
+ /// @brief Static constant that defines a custom process command string.
+ static const std::string custom_process_command_;
+
+ /// @brief Constructor
+ ///
+ /// @param name name is a text label for the process. Generally used
+ /// in log statements, but otherwise arbitrary.
+ /// @param io_service is the io_service used by the caller for
+ /// asynchronous event handling.
+ ///
+ /// @throw DProcessBaseError is io_service is NULL.
+ DStubProcess(const char* name, IOServicePtr io_service);
+
+ /// @brief Invoked after process instantiation to perform initialization.
+ /// This implementation supports simulating an error initializing the
+ /// process by throwing a DProcesssBaseError if SimFailure is set to
+ /// ftProcessInit.
+ virtual void init();
+
+ /// @brief Implements the process's event loop.
+ /// This implementation is quite basic, surrounding calls to
+ /// io_service->runOne() with a test of the shutdown flag. Once invoked,
+ /// the method will continue until the process itself is exiting due to a
+ /// request to shutdown or some anomaly forces an exit.
+ /// @return returns 0 upon a successful, "normal" termination, non-zero to
+ /// indicate an abnormal termination.
+ virtual int run();
+
+ /// @brief Implements the process shutdown procedure. Currently this is
+ /// limited to setting the instance shutdown flag, which is monitored in
+ /// run().
+ virtual int shutdown();
+
+ /// @brief Processes the given configuration.
+ ///
+ /// This implementation fails if SimFailure is set to ftProcessConfigure.
+ /// Otherwise it will complete successfully. It does not check the content
+ /// of the inbound configuration.
+ ///
+ /// @param config_set a new configuration (JSON) for the process
+ /// @return an Element that contains the results of configuration composed
+ /// of an integer status value (0 means successful, non-zero means failure),
+ /// and a string explanation of the outcome.
+ virtual isc::data::ConstElementPtr configure(isc::data::ConstElementPtr
+ config_set);
+
+ /// @brief Executes the given command.
+ ///
+ /// This implementation will recognizes one "custom" process command,
+ /// custom_process_command_. It will fail if SimFailure is set to
+ /// ftProcessCommand.
+ ///
+ /// @param command is a string label representing the command to execute.
+ /// @param args is a set of arguments (if any) required for the given
+ /// command.
+ /// @return an Element that contains the results of command composed
+ /// of an integer status value and a string explanation of the outcome.
+ /// The status value is:
+ /// COMMAND_SUCCESS if the command is recongized and executes successfully.
+ /// COMMAND_ERROR if the command is recongized but fails to execute.
+ /// COMMAND_INVALID if the command is not recognized.
+ virtual isc::data::ConstElementPtr command(const std::string& command,
+ isc::data::ConstElementPtr args);
+
+ // @brief Destructor
+ virtual ~DStubProcess();
+};
+
+
+/// @brief Test Derivation of the DControllerBase class.
+///
+/// DControllerBase is an abstract class and therefore requires a derivation
+/// for testing. It allows testing the majority of the base classs code
+/// without polluting production derivations (e.g. D2Process). It uses
+/// D2StubProcess as its application process class. It is a full enough
+/// implementation to support running both stand alone and integrated.
+/// Obviously BIND10 connectivity is not available under unit tests, so
+/// testing here is limited to "failures" to communicate with BIND10.
+class DStubController : 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 a pointer reference to the singleton instance.
+ static DControllerBasePtr& instance();
+
+ /// @brief Defines a custom controller command string. This is a
+ /// custom command supported by DStubController.
+ static const std::string custom_ctl_command_;
+
+protected:
+ /// @brief Handles additional command line options that are supported
+ /// by DStubController. This implementation supports an option "-x".
+ ///
+ /// @param option is the option "character" from the command line, without
+ /// any prefixing hypen(s)
+ /// @optarg optarg is the argument value (if one) associated with the option
+ ///
+ /// @return returns true if the option is "x", otherwise ti returns false.
+ virtual bool customOption(int option, char *optarg);
+
+ /// @brief Instantiates an instance of DStubProcess.
+ ///
+ /// This implementation will fail if SimFailure is set to
+ /// ftCreateProcessException OR ftCreateProcessNull.
+ ///
+ /// @return returns a pointer to the new process instance (DProcessBase*)
+ /// or NULL if SimFailure is set to ftCreateProcessNull.
+ /// @throw throws std::runtime_error if SimFailure is ste to
+ /// ftCreateProcessException.
+ virtual DProcessBase* createProcess();
+
+ /// @brief Executes custom controller commands are supported by
+ /// DStubController. This implementation supports one custom controller
+ /// command, custom_controller_command_. It will fail if SimFailure is set
+ /// to ftControllerCommand.
+ ///
+ /// @param command is a string label representing the command to execute.
+ /// @param args is a set of arguments (if any) required for the given
+ /// command.
+ /// @return an Element that contains the results of command composed
+ /// of an integer status value and a string explanation of the outcome.
+ /// The status value is:
+ /// COMMAND_SUCCESS if the command is recongized and executes successfully.
+ /// COMMAND_ERROR if the command is recongized but fails to execute.
+ /// COMMAND_INVALID if the command is not recognized.
+ virtual isc::data::ConstElementPtr customControllerCommand(
+ const std::string& command, isc::data::ConstElementPtr args);
+
+ /// @brief Provides a string of the additional command line options
+ /// supported by DStubController.
+ ///
+ /// @return returns a string containing the option letters.
+ virtual const std::string getCustomOpts();
+
+private:
+ /// @brief Constructor is private to protect singleton integrity.
+ DStubController();
+
+public:
+ virtual ~DStubController();
+};
+
+/// @brief Test fixture class that wraps a DControllerBase. This class
+/// is a friend class of DControllerBase which allows it access to class
+/// content to facilitate testing. It provides numerous wrapper methods for
+/// the protected and private methods and member of the base class. Note that
+/// this class specifically supports DStubController testing, it is designed
+/// to be reused, by simply by overriding the init_controller and
+/// destroy_controller methods.
+class DControllerTest : public ::testing::Test {
+public:
+
+ typedef DControllerBasePtr& (*InstanceGetter)();
+ static InstanceGetter instanceGetter_;
+
+#if 0
+ /// @brief Constructor - Invokes virtual init_controller method which
+ /// initializes the controller class's singleton instance.
+ DControllerTest() {
+ init_controller();
+ }
+#endif
+
+ DControllerTest(InstanceGetter instanceGetter) {
+ instanceGetter_ = instanceGetter;
+ }
+
+ /// @brief Destructor - Invokes virtual init_controller method which
+ /// initializes the controller class's singleton instance. This is
+ /// essential to ensure a clean start between tests.
+ virtual ~DControllerTest() {
+ destroy_controller();
+ }
+
+ /// @brief Virtual method which sets static copy of the controller class's
+ /// singleton instance. We need this copy so we can generically access
+ /// the singleton in derivations.
+ void init_controller() {
+#if 0
+ std::cout << "Calling DController test init_controller" << std::endl;
+ controller_under_test_ = DStubController::instance();
+#else
+ getController();
+#endif
+ }
+
+
+ void destroy_controller() {
+#if 0
+ std::cout << "Calling DController test destroy_controller" << std::endl;
+ DStubController::instance().reset();
+#else
+ getController().reset();
+#endif
+ }
+
+ void resetController() {
+ destroy_controller();
+ init_controller();
+ }
+
+ static DControllerBasePtr& getController() {
+#if 0
+ return (controller_under_test_);
+#else
+ return ((*instanceGetter_)());
+#endif
+ }
+
+ bool checkName(const std::string& should_be) {
+ return (getController()->getName().compare(should_be) == 0);
+ }
+
+ bool checkSpecFileName(const std::string& should_be) {
+ return (getController()->getSpecFileName().compare(should_be) == 0);
+ }
+
+ bool checkProcess() {
+ return (getController()->process_);
+ }
+
+ bool checkIOService() {
+ return (getController()->io_service_);
+ }
+
+ IOServicePtr& getIOService() {
+ return (getController()->io_service_);
+ }
+
+ bool checkStandAlone(bool value) {
+ return (getController()->isStandAlone() == value);
+ }
+
+ void setStandAlone(bool value) {
+ getController()->setStandAlone(value);
+ }
+
+ bool checkVerbose(bool value) {
+ return (getController()->isVerbose() == value);
+ }
+
+
+ void parseArgs(int argc, char* argv[]) {
+ getController()->parseArgs(argc, argv);
+ }
+
+ void initProcess() {
+ getController()->initProcess();
+ }
+
+ void establishSession() {
+ getController()->establishSession();
+ }
+
+ int launch(int argc, char* argv[]) {
+ optind = 1;
+ return (getController()->launch(argc, argv));
+ }
+
+ void disconnectSession() {
+ getController()->disconnectSession();
+ }
+
+ isc::data::ConstElementPtr updateConfig(isc::data::ConstElementPtr
+ new_config) {
+ return (getController()->updateConfig(new_config));
+ }
+
+ isc::data::ConstElementPtr executeCommand(const std::string& command,
+ isc::data::ConstElementPtr args){
+ return (getController()->executeCommand(command, args));
+ }
+
+ /// @brief Callback that will generate shutdown command via the
+ /// command callback function.
+ static void genShutdownCallback() {
+ isc::data::ElementPtr arg_set;
+ DControllerBase::commandHandler(SHUT_DOWN_COMMAND, arg_set);
+ }
+
+ /// @brief Callback that throws an exception.
+ static void genFatalErrorCallback() {
+ isc_throw (DProcessBaseError, "simulated fatal error");
+ }
+
+ /// @brief Static member that retains a copy of controller singleton
+ /// instance. This is needed for static asio callback handlers.
+ static DControllerBasePtr controller_under_test_;
+};
+
+class DStubControllerTest : public DControllerTest {
+public:
+
+ DStubControllerTest() : DControllerTest (DStubController::instance) {
+ }
+
+ virtual ~DStubControllerTest() {
+ }
+};
+
+
+
+}; // namespace isc::d2
+}; // namespace isc
+
+#endif