Browse Source

[2956] Interrim checkin to allow merge with 2955. Note a subsequent commit
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

Thomas Markwalder 12 years ago
parent
commit
fd911f4775

+ 8 - 1
src/bin/d2/Makefile.am

@@ -16,7 +16,7 @@ endif
 
 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)
@@ -48,12 +48,19 @@ pkglibexec_PROGRAMS = b10-d2
 
 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

+ 12 - 12
src/bin/d2/d2.spec

@@ -1,21 +1,21 @@
 {
   "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
+          }
+        ]
+      }
     ]
   }
 }

+ 57 - 0
src/bin/d2/d2_controller.cc

@@ -0,0 +1,57 @@
+// 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

+ 64 - 0
src/bin/d2/d2_controller.h

@@ -0,0 +1,64 @@
+// 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

+ 2 - 1
src/bin/d2/d2_log.cc

@@ -19,7 +19,8 @@
 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

+ 4 - 0
src/bin/d2/d2_log.h

@@ -22,12 +22,16 @@
 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
 

+ 84 - 9
src/bin/d2/d2_messages.mes

@@ -14,15 +14,90 @@
 
 $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.
 

+ 441 - 0
src/bin/d2/d_controller.cc

@@ -0,0 +1,441 @@
+// 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

+ 473 - 0
src/bin/d2/d_controller.h

@@ -0,0 +1,473 @@
+// 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

+ 11 - 65
src/bin/d2/main.cc

@@ -14,6 +14,7 @@
 
 #include <config.h>
 #include <d2/d2_log.h>
+#include <d2/d2_controller.h>
 #include <log/logger_support.h>
 #include <log/logger_manager.h>
 
@@ -23,75 +24,20 @@ using namespace isc::d2;
 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);
 }
 

+ 15 - 0
src/bin/d2/spec_config.h

@@ -0,0 +1,15 @@
+// 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"

+ 11 - 0
src/bin/d2/tests/Makefile.am

@@ -52,7 +52,15 @@ if HAVE_GTEST
 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)
@@ -60,6 +68,9 @@ d2_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
 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)

+ 191 - 0
src/bin/d2/tests/d2_controller_unittests.cc

@@ -0,0 +1,191 @@
+// 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

+ 2 - 2
src/bin/d2/tests/d2_test.py

@@ -159,9 +159,9 @@ class TestD2Daemon(unittest.TestCase):
         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()

+ 226 - 0
src/bin/d2/tests/d_controller_unittests.cc

@@ -0,0 +1,226 @@
+// 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

+ 182 - 0
src/bin/d2/tests/d_test_stubs.cc

@@ -0,0 +1,182 @@
+// 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

+ 404 - 0
src/bin/d2/tests/d_test_stubs.h

@@ -0,0 +1,404 @@
+// 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