Browse Source

3401 - D2 now supports with-kea-config switch

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

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

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

This controller is tested in tests/bundy_d2_controller_unittests.cc

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

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

This controller is tested in tests/d2_controller_unittests.cc

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

The stand-alone mode flag has been removed from all controllers.
Thomas Markwalder 11 years ago
parent
commit
1c4d345d90

+ 11 - 3
src/bin/d2/Makefile.am

@@ -50,12 +50,12 @@ BUILT_SOURCES = spec_config.h d2_messages.h d2_messages.cc
 pkglibexec_PROGRAMS = b10-dhcp-ddns
 
 b10_dhcp_ddns_SOURCES  = main.cc
+b10_dhcp_ddns_SOURCES += d_process.h
+b10_dhcp_ddns_SOURCES += d_controller.cc d_controller.h
+b10_dhcp_ddns_SOURCES += d_cfg_mgr.cc d_cfg_mgr.h
 b10_dhcp_ddns_SOURCES += d2_asio.h
 b10_dhcp_ddns_SOURCES += d2_log.cc d2_log.h
 b10_dhcp_ddns_SOURCES += d2_process.cc d2_process.h
-b10_dhcp_ddns_SOURCES += d_controller.cc d_controller.h
-b10_dhcp_ddns_SOURCES += d2_controller.cc d2_controller.h
-b10_dhcp_ddns_SOURCES += d_cfg_mgr.cc d_cfg_mgr.h
 b10_dhcp_ddns_SOURCES += d2_config.cc d2_config.h
 b10_dhcp_ddns_SOURCES += d2_cfg_mgr.cc d2_cfg_mgr.h
 b10_dhcp_ddns_SOURCES += d2_queue_mgr.cc d2_queue_mgr.h
@@ -69,6 +69,14 @@ b10_dhcp_ddns_SOURCES += nc_remove.cc nc_remove.h
 b10_dhcp_ddns_SOURCES += nc_trans.cc nc_trans.h
 b10_dhcp_ddns_SOURCES += state_model.cc state_model.h
 
+if CONFIG_BACKEND_BUNDY
+b10_dhcp_ddns_SOURCES += bundy_d2_controller.cc bundy_d2_controller.h
+else
+if CONFIG_BACKEND_JSON
+b10_dhcp_ddns_SOURCES += d2_controller.cc d2_controller.h
+endif
+endif
+
 nodist_b10_dhcp_ddns_SOURCES = d2_messages.h d2_messages.cc
 EXTRA_DIST += d2_messages.mes
 

+ 294 - 0
src/bin/d2/bundy_d2_controller.cc

@@ -0,0 +1,294 @@
+// Copyright (C) 2014  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <d2/bundy_d2_controller.h>
+#include <d2/d2_process.h>
+#include <d2/spec_config.h>
+
+#include <sstream>
+
+namespace isc {
+namespace d2 {
+
+/// @brief Defines the application name, this is passed into base class
+/// it may be used to locate configuration data and appears in log statement.
+const char* D2Controller::d2_app_name_ = "DHCP-DDNS";
+
+/// @brief Defines the executable name. This is passed into the base class
+const char* D2Controller::d2_bin_name_ = "b10-dhcp-ddns";
+
+DControllerBasePtr&
+D2Controller::instance() {
+    // If the instance hasn't been created yet, create it.  Note this method
+    // must use the base class singleton instance methods.  The base class
+    // must have access to the singleton in order to use it within BUNDY
+    // static function callbacks.
+    if (!getController()) {
+        DControllerBasePtr controller_ptr(new D2Controller());
+        setController(controller_ptr);
+    }
+
+    return (getController());
+}
+
+DProcessBase* D2Controller::createProcess() {
+    // Instantiate and return an instance of the D2 application process. Note
+    // that the process is passed the controller's io_service.
+    return (new D2Process(getAppName().c_str(), getIOService()));
+}
+
+D2Controller::D2Controller()
+    : DControllerBase(d2_app_name_, d2_bin_name_) {
+    // set the spec file either from the environment or
+    // use the production value.
+    if (getenv("B10_FROM_BUILD")) {
+        setSpecFileName(std::string(getenv("B10_FROM_BUILD")) +
+            "/src/bin/d2/dhcp-ddns.spec");
+    } else {
+        setSpecFileName(D2_SPECFILE_LOCATION);
+    }
+}
+
+D2Controller::~D2Controller() {
+}
+
+void
+D2Controller::launch(int argc, char* argv[], const bool test_mode) {
+    // Step 1 is to parse the command line arguments.
+    try {
+        parseArgs(argc, argv);
+    } catch (const InvalidUsage& ex) {
+        usage(ex.what());
+        throw; // rethrow it
+    }
+
+    // Do not initialize logger here if we are running unit tests. It would
+    // replace an instance of unit test specific logger.
+    if (!test_mode) {
+        // Now that we know what the mode flags are, we can init logging.
+        // If standalone is enabled, do not buffer initial log messages
+        isc::log::initLogger(getBinName(),
+                             (isVerbose() ? isc::log::DEBUG : isc::log::INFO),
+                             isc::log::MAX_DEBUG_LEVEL, NULL, true);
+    }
+
+    LOG_DEBUG(dctl_logger, DBGLVL_START_SHUT, DCTL_STARTING)
+              .arg(getAppName()).arg(getpid());
+    try {
+        // Step 2 is to create and initialize the application process object.
+        initProcess();
+    } catch (const std::exception& ex) {
+        LOG_FATAL(dctl_logger, DCTL_INIT_PROCESS_FAIL)
+                  .arg(getAppName()).arg(ex.what());
+        isc_throw (ProcessInitError,
+                   "Application Process initialization failed: " << ex.what());
+    }
+
+    // Next we connect to Bundy.
+    try {
+        establishSession();
+    } catch (const std::exception& ex) {
+        LOG_FATAL(dctl_logger, DCTL_SESSION_FAIL).arg(ex.what());
+        isc_throw (SessionStartError,
+                   "Session start up failed: " << ex.what());
+    }
+
+    // Everything is clear for launch, so start the application's
+    // event loop.
+    try {
+        runProcess();
+    } catch (const std::exception& ex) {
+        LOG_FATAL(dctl_logger, DCTL_PROCESS_FAILED)
+                  .arg(getAppName()).arg(ex.what());
+        isc_throw (ProcessRunError,
+                   "Application process event loop failed: " << ex.what());
+    }
+
+    // Disconnect from Bundy.
+    try {
+        disconnectSession();
+    } catch (const std::exception& ex) {
+        LOG_ERROR(dctl_logger, DCTL_DISCONNECT_FAIL)
+                  .arg(getAppName()).arg(ex.what());
+        isc_throw (SessionEndError, "Session end failed: " << ex.what());
+    }
+
+    // All done, so bail out.
+    LOG_INFO(dctl_logger, DCTL_STOPPING).arg(getAppName());
+}
+
+void
+D2Controller::parseArgs(int argc, char* argv[])
+{
+    // Iterate over the given command line options. If its a stock option
+    // ("s" or "v") handle it here.  If its a valid custom option, then
+    // invoke customOption.
+    int ch;
+    opterr = 0;
+    optind = 1;
+    std::string opts(":v" + getCustomOpts());
+    while ((ch = getopt(argc, argv, opts.c_str())) != -1) {
+        switch (ch) {
+        case 'v':
+            // Enables verbose logging.
+            setVerbose(true);
+            break;
+
+        case '?': {
+            // We hit an invalid option.
+            isc_throw(InvalidUsage, "unsupported option: ["
+                      << static_cast<char>(optopt) << "] "
+                      << (!optarg ? "" : optarg));
+
+            break;
+            }
+
+        default:
+            // We hit a valid custom option
+            if (!customOption(ch, optarg)) {
+                // This would be a programmatic error.
+                isc_throw(InvalidUsage, " Option listed but implemented?: ["
+                          << static_cast<char>(ch) << "] "
+                          << (!optarg ? "" : optarg));
+            }
+            break;
+        }
+    }
+
+    // There was too much information on the command line.
+    if (argc > optind) {
+        isc_throw(InvalidUsage, "extraneous command line information");
+    }
+}
+
+void
+D2Controller::establishSession() {
+    LOG_DEBUG(dctl_logger, DBGLVL_START_SHUT, DCTL_CCSESSION_STARTING)
+              .arg(getAppName()).arg(getSpecFileName());
+
+    // Create the BIND10 command control session with the our IOService.
+    cc_session_ = SessionPtr(new isc::cc::Session(
+                             getIOService()->get_io_service()));
+
+    // Create the BIND10 config session with the stub configuration handler.
+    // This handler is internally invoked by the constructor and on success
+    // the constructor updates the current session with the configuration that
+    // had been committed in the previous session. If we do not install
+    // the dummy handler, the previous configuration would be lost.
+    config_session_ = ModuleCCSessionPtr(new isc::config::ModuleCCSession(
+                                         getSpecFileName(), *cc_session_,
+                                         dummyConfigHandler, 
+                                         DControllerBase::commandHandler,
+                                         false));
+    // Enable configuration even processing.
+    config_session_->start();
+
+    // We initially create ModuleCCSession() with a dummy configHandler, as
+    // the session module is too eager to send partial configuration.
+    // Replace the dummy config handler with the real handler.
+    config_session_->setConfigHandler(DControllerBase::configHandler);
+
+    // Call the real configHandler with the full configuration retrieved
+    // from the config session.
+    isc::data::ConstElementPtr answer = configHandler(
+                                            config_session_->getFullConfig());
+
+    // Parse the answer returned from the configHandler.  Log the error but
+    // keep running. This provides an opportunity for the user to correct
+    // the configuration dynamically.
+    int ret = 0;
+    isc::data::ConstElementPtr comment = isc::config::parseAnswer(ret, answer);
+    if (ret) {
+        LOG_ERROR(dctl_logger, DCTL_CONFIG_LOAD_FAIL)
+                  .arg(getAppName()).arg(comment->str());
+    }
+}
+
+void D2Controller::disconnectSession() {
+    LOG_DEBUG(dctl_logger, DBGLVL_START_SHUT, DCTL_CCSESSION_ENDING)
+              .arg(getAppName());
+
+    // Destroy the BIND10 config session.
+    if (config_session_) {
+        config_session_.reset();
+    }
+
+    // Destroy the BIND10 command and control session.
+    if (cc_session_) {
+        cc_session_->disconnect();
+        cc_session_.reset();
+    }
+}
+
+isc::data::ConstElementPtr
+D2Controller::dummyConfigHandler(isc::data::ConstElementPtr) {
+    LOG_DEBUG(dctl_logger, DBGLVL_START_SHUT, DCTL_CONFIG_STUB)
+             .arg(getController()->getAppName());
+    return (isc::config::createAnswer(0, "Configuration accepted."));
+}
+
+isc::data::ConstElementPtr
+D2Controller::updateConfig(isc::data::ConstElementPtr new_config) {
+    if (!config_session_) {
+        // That should never happen as we install config_handler
+        // after we instantiate the server.
+        isc::data::ConstElementPtr answer =
+            isc::config::createAnswer(1, "Configuration rejected,"
+                                              " Session has not started.");
+        return (answer);
+    }
+
+    // Let's get the existing configuration.
+    isc::data::ConstElementPtr full_config = config_session_->getFullConfig();
+
+    // The configuration passed to this handler function is partial.
+    // In other words, it just includes the values being modified.
+    // In the same time, there may be dependencies between various
+    // configuration parsers. For example: the option value can
+    // be set if the definition of this option is set. If someone removes
+    // an existing option definition then the partial configuration that
+    // removes that definition is triggered while a relevant option value
+    // may remain configured. This eventually results in the
+    // configuration being in the inconsistent state.
+    // In order to work around this problem we need to merge the new
+    // configuration with the existing (full) configuration.
+
+    // Let's create a new object that will hold the merged configuration.
+    boost::shared_ptr<isc::data::MapElement>
+                            merged_config(new isc::data::MapElement());
+
+    // Merge an existing and new configuration.
+    merged_config->setValue(full_config->mapValue());
+    isc::data::merge(merged_config, new_config);
+
+    // Send the merged configuration to the application.
+    return (getProcess()->configure(merged_config));
+}
+
+
+void
+D2Controller::usage(const std::string & text)
+{
+    if (text != "") {
+        std::cerr << "Usage error: " << text << std::endl;
+    }
+
+    std::cerr << "Usage: " << getBinName() <<  std::endl;
+    std::cerr << "  -v: verbose output" << std::endl;
+    std::cerr << getUsageText() << std::endl;
+}
+
+
+}; // namespace isc::d2
+}; // namespace isc

+ 230 - 0
src/bin/d2/bundy_d2_controller.h

@@ -0,0 +1,230 @@
+// Copyright (C) 2014  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef BUNDY_D2_CONTROLLER_H
+#define BUNDY_D2_CONTROLLER_H
+
+#include <cc/data.h>
+#include <cc/session.h>
+#include <config/ccsession.h>
+#include <d2/d2_asio.h>
+#include <d2/d2_log.h>
+#include <d2/d_controller.h>
+#include <d2/d_process.h>
+#include <exceptions/exceptions.h>
+#include <log/logger_support.h>
+
+#include <boost/shared_ptr.hpp>
+#include <boost/noncopyable.hpp>
+
+
+namespace isc {
+namespace d2 {
+
+/// @brief Exception thrown when the session start up fails.
+class SessionStartError: public isc::Exception {
+public:
+    SessionStartError (const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+/// @brief Exception thrown when the session end fails.
+class SessionEndError: public isc::Exception {
+public:
+    SessionEndError (const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+/// @brief Defines a shared pointer to a Session.
+typedef boost::shared_ptr<isc::cc::Session> SessionPtr;
+
+/// @brief Defines a shared pointer to a ModuleCCSession.
+typedef boost::shared_ptr<isc::config::ModuleCCSession> ModuleCCSessionPtr;
+
+/// @brief Bundy-integrated Process Controller for D2 Process 
+/// This class is the DHCP-DDNS specific derivation of DControllerBase which
+/// is managed by BUNDY. It creates and manages an instance of the DHCP-DDNS 
+/// application process, D2Process.
+///
+/// D2 will be constructed with this class if the project is configured with
+/// --with-kea-config=BUNDY
+class D2Controller : public DControllerBase {
+public:
+    /// @brief Static singleton instance method. This method returns the
+    /// base class singleton instance member.  It instantiates the singleton
+    /// and sets the base class instance member upon first invocation.
+    ///
+    /// @return returns the pointer reference to the singleton instance.
+    static DControllerBasePtr& instance();
+
+    /// @brief Destructor.
+    virtual ~D2Controller();
+
+    /// @brief Defines the application name, this is passed into base class
+    /// and appears in log statements.
+    static const char* d2_app_name_;
+
+    /// @brief Defines the executable name. This is passed into the base class
+    /// by convention this should match the BUNDY module name.
+    static const char* d2_bin_name_;
+
+    /// @brief Acts as the primary entry point into the controller execution
+    /// and provides the outermost application control logic:
+    ///
+    /// 1. parse command line arguments
+    /// 2. instantiate and initialize the application process
+    /// 3. establish BUNDY session(s) if in integrated mode
+    /// 4. start and wait on the application process event loop
+    /// 5. upon event loop completion, disconnect from BUNDY (if needed)
+    /// 6. exit to the caller
+    ///
+    /// It is intended to be called from main() and be given the command line
+    /// arguments. Note this method is deliberately not virtual to ensure the
+    /// proper sequence of events occur.
+    ///
+    /// This function can be run in the test mode. It prevents initialization
+    /// of D2 module logger. This is used in unit tests which initialize logger
+    /// in their main function. Such logger uses environmental variables to
+    /// control severity, verbosity etc. Reinitialization of logger by this
+    /// function would replace unit tests specific logger configuration with
+    /// this suitable for D2 running as a bind10 module.
+    ///
+    /// @param argc  is the number of command line arguments supplied
+    /// @param argv  is the array of string (char *) command line arguments
+    /// @param test_mode is a bool value which indicates if
+    /// @c DControllerBase::launch should be run in the test mode (if true).
+    /// This parameter doesn't have default value to force test implementers to
+    /// enable test mode explicitly.
+    ///
+    /// @throw throws one of the following exceptions:
+    /// InvalidUsage - Indicates invalid command line.
+    /// ProcessInitError  - Failed to create and initialize application
+    /// process object.
+    /// SessionStartError  - Could not connect to BUNDY (integrated mode only).
+    /// ProcessRunError - A fatal error occurred while in the application
+    /// process event loop.
+    /// SessionEndError - Could not disconnect from BUNDY (integrated mode
+    /// only).
+    virtual void launch(int argc, char* argv[], const bool test_mode);
+
+    /// @brief A dummy configuration handler that always returns success.
+    ///
+    /// This configuration handler does not perform configuration
+    /// parsing and always returns success. A dummy handler should
+    /// be installed using \ref isc::config::ModuleCCSession ctor
+    /// to get the initial configuration. This initial configuration
+    /// comprises values for only those elements that were modified
+    /// the previous session. The D2 configuration parsing can't be
+    /// used to parse the initial configuration because it may need the
+    /// full configuration to satisfy dependencies between the
+    /// various configuration values. Installing the dummy handler
+    /// that guarantees to return success causes initial configuration
+    /// to be stored for the session being created and that it can
+    /// be later accessed with \ref isc::config::ConfigData::getFullConfig.
+    ///
+    /// @param new_config new configuration.
+    ///
+    /// @return success configuration status.
+    static isc::data::ConstElementPtr
+    dummyConfigHandler(isc::data::ConstElementPtr new_config);
+
+    /// @brief Instance method invoked by the configuration event handler and
+    /// which processes the actual configuration update.  Provides behavioral
+    /// path for both integrated and stand-alone modes. The current
+    /// implementation will merge the configuration update into the existing
+    /// configuration and then invoke the application process' configure method.
+    ///
+    /// @todo This implementation is will evolve as the D2 configuration
+    /// management task is implemented (trac #2957).
+    ///
+    /// @param  new_config is the new configuration
+    ///
+    /// @return returns an Element that contains the results of configuration
+    /// update composed of an integer status value (0 means successful,
+    /// non-zero means failure), and a string explanation of the outcome.
+    virtual isc::data::ConstElementPtr
+    updateConfig(isc::data::ConstElementPtr new_config);
+
+protected:
+    /// @brief Processes the command line arguments. It is the first step
+    /// taken after the controller has been launched.  It combines the stock
+    /// list of options with those returned by getCustomOpts(), and uses
+    /// cstdlib's getopt to loop through the command line.  The stock options
+    /// It handles stock options directly, and passes any custom options into
+    /// the customOption method.  Currently there are only two stock options
+    /// -s for stand alone mode, and -v for verbose logging.
+    ///
+    /// @param argc  is the number of command line arguments supplied
+    /// @param argv  is the array of string (char *) command line arguments
+    ///
+    /// @throw throws InvalidUsage when there are usage errors.
+    void parseArgs(int argc, char* argv[]);
+
+    /// @brief Establishes connectivity with BUNDY.  This method is used
+    /// invoked during launch, if running in integrated mode, following
+    /// successful process initialization.  It is responsible for establishing
+    /// the BUNDY control and config sessions. During the session creation,
+    /// it passes in the controller's IOService and the callbacks for command
+    /// directives and config events.  Lastly, it will invoke the onConnect
+    /// method providing the derivation an opportunity to execute any custom
+    /// logic associated with session establishment.
+    ///
+    /// @throw the BUNDY framework may throw std::exceptions.
+    void establishSession();
+
+    /// @brief Terminates connectivity with BUNDY. This method is invoked
+    /// in integrated mode after the application event loop has exited. It
+    /// first calls the onDisconnect method providing the derivation an
+    /// opportunity to execute custom logic if needed, and then terminates the
+    /// BUNDY config and control sessions.
+    ///
+    /// @throw the BUNDY framework may throw std:exceptions.
+    void disconnectSession();
+
+    /// @brief Prints the program usage text to std error.
+    ///
+    /// @param text is a string message which will preceded the usage text.
+    /// This is intended to be used for specific usage violation messages.
+    void usage(const std::string& text);
+
+private:
+    /// @brief Creates an instance of the DHCP-DDNS specific application
+    /// process.  This method is invoked during the process initialization
+    /// step of the controller launch.
+    ///
+    /// @return returns a DProcessBase* to the application process created.
+    /// Note the caller is responsible for destructing the process. This
+    /// is handled by the base class, which wraps this pointer with a smart
+    /// pointer.
+    virtual DProcessBase* createProcess();
+
+    /// @brief Helper session object that represents raw connection to msgq.
+    SessionPtr cc_session_;
+
+    /// @brief Session that receives configuration and commands.
+    ModuleCCSessionPtr config_session_;
+
+    /// @brief Constructor is declared private to maintain the integrity of
+    /// the singleton instance.
+    D2Controller();
+
+    // DControllerTest is named a friend class to facilitate unit testing while
+    // leaving the intended member scopes intact.
+    friend class DControllerTest;
+};
+
+}; // namespace isc::d2
+}; // namespace isc
+
+#endif

+ 4 - 5
src/bin/d2/d2_controller.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2013  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2014 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -22,11 +22,10 @@ namespace isc {
 namespace d2 {
 
 /// @brief Defines the application name, this is passed into base class
-/// and appears in log statements.
-const char* D2Controller::d2_app_name_ = "DHCP-DDNS";
+/// it may be used to locate configuration data and appears in log statement.
+const char* D2Controller::d2_app_name_ = "DhcpDdns";
 
 /// @brief Defines the executable name. This is passed into the base class
-/// by convention this should match the BIND10 module name.
 const char* D2Controller::d2_bin_name_ = "b10-dhcp-ddns";
 
 DControllerBasePtr&
@@ -51,7 +50,7 @@ DProcessBase* D2Controller::createProcess() {
 
 D2Controller::D2Controller()
     : DControllerBase(d2_app_name_, d2_bin_name_) {
-    // set the BIND10 spec file either from the environment or
+    // set the spec file either from the environment or
     // use the production value.
     if (getenv("B10_FROM_BUILD")) {
         setSpecFileName(std::string(getenv("B10_FROM_BUILD")) +

+ 2 - 2
src/bin/d2/d2_controller.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2013  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2014 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -47,7 +47,7 @@ public:
     static const char* d2_app_name_;
 
     /// @brief Defines the executable name. This is passed into the base class
-    /// by convention this should match the BIND10 module name.
+    /// by convention this should match the executable name.
     static const char* d2_bin_name_;
 
 private:

+ 4 - 0
src/bin/d2/d2_messages.mes

@@ -37,6 +37,10 @@ This critical error message indicates that the initial application
 configuration has failed. The service will start, but will not
 process requests until the configuration has been corrected.
 
+% DCTL_CONFIG_FILE_LOAD_FAIL %1 configuration could not be loaded from file: %2
+This fatal error message indicates that the application attempted to load its
+initial configuration from file and has failed. The service will exit.
+
 % DCTL_CONFIG_START parsing new configuration: %1
 A debug message indicating that the application process has received an
 updated configuration and has passed it to its configuration manager

+ 102 - 179
src/bin/d2/d_controller.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2013  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2014 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -14,6 +14,7 @@
 
 
 #include <d2/d2_log.h>
+#include <config/ccsession.h>
 #include <d2/d_controller.h>
 #include <exceptions/exceptions.h>
 #include <log/logger_support.h>
@@ -27,12 +28,11 @@ DControllerBasePtr DControllerBase::controller_;
 
 // Note that the constructor instantiates the controller's primary IOService.
 DControllerBase::DControllerBase(const char* app_name, const char* bin_name)
-    : app_name_(app_name), bin_name_(bin_name), stand_alone_(false),
+    : app_name_(app_name), bin_name_(bin_name),
       verbose_(false), spec_file_name_(""),
       io_service_(new isc::asiolink::IOService()){
 }
 
-
 void
 DControllerBase::setController(const DControllerBasePtr& controller) {
     if (controller_) {
@@ -47,6 +47,7 @@ DControllerBase::setController(const DControllerBasePtr& controller) {
 
 void
 DControllerBase::launch(int argc, char* argv[], const bool test_mode) {
+
     // Step 1 is to parse the command line arguments.
     try {
         parseArgs(argc, argv);
@@ -59,11 +60,7 @@ DControllerBase::launch(int argc, char* argv[], const bool test_mode) {
     // replace an instance of unit test specific logger.
     if (!test_mode) {
         // Now that we know what the mode flags are, we can init logging.
-        // If standalone is enabled, do not buffer initial log messages
-        isc::log::initLogger(bin_name_,
-                             ((verbose_ && stand_alone_)
-                              ? isc::log::DEBUG : isc::log::INFO),
-                             isc::log::MAX_DEBUG_LEVEL, NULL, !stand_alone_);
+        Daemon::loggerInit(bin_name_.c_str(), verbose_);
     }
 
     LOG_DEBUG(dctl_logger, DBGLVL_START_SHUT, DCTL_STARTING)
@@ -78,18 +75,17 @@ DControllerBase::launch(int argc, char* argv[], const bool test_mode) {
                    "Application Process initialization failed: " << ex.what());
     }
 
-    // Next we connect if we are running integrated.
-    if (stand_alone_) {
-        LOG_DEBUG(dctl_logger, DBGLVL_START_SHUT, DCTL_STANDALONE)
-                  .arg(app_name_);
-    } else {
-        try {
-            establishSession();
-        } catch (const std::exception& ex) {
-            LOG_FATAL(dctl_logger, DCTL_SESSION_FAIL).arg(ex.what());
-            isc_throw (SessionStartError,
-                       "Session start up failed: " << ex.what());
-        }
+    LOG_DEBUG(dctl_logger, DBGLVL_START_SHUT, DCTL_STANDALONE).arg(app_name_);
+
+    // Step 3 is to load configuration from file.
+    int rcode;
+    isc::data::ConstElementPtr comment
+        = isc::config::parseAnswer(rcode, configFromFile());
+    if (rcode != 0) {
+        LOG_FATAL(dctl_logger, DCTL_CONFIG_FILE_LOAD_FAIL)
+                  .arg(app_name_).arg(comment->stringValue());
+        isc_throw (ProcessInitError, "Could Not load configration file: "
+                   << comment->stringValue());
     }
 
     // Everything is clear for launch, so start the application's
@@ -103,22 +99,10 @@ DControllerBase::launch(int argc, char* argv[], const bool test_mode) {
                    "Application process event loop failed: " << ex.what());
     }
 
-    // If running integrated, disconnect.
-    if (!stand_alone_) {
-        try {
-            disconnectSession();
-        } catch (const std::exception& ex) {
-            LOG_ERROR(dctl_logger, DCTL_DISCONNECT_FAIL)
-                      .arg(app_name_).arg(ex.what());
-            isc_throw (SessionEndError, "Session end failed: " << ex.what());
-        }
-    }
-
     // All done, so bail out.
     LOG_INFO(dctl_logger, DCTL_STOPPING).arg(app_name_);
 }
 
-
 void
 DControllerBase::parseArgs(int argc, char* argv[])
 {
@@ -128,7 +112,7 @@ DControllerBase::parseArgs(int argc, char* argv[])
     int ch;
     opterr = 0;
     optind = 1;
-    std::string opts(":vs" + getCustomOpts());
+    std::string opts("vc:" + getCustomOpts());
     while ((ch = getopt(argc, argv, opts.c_str())) != -1) {
         switch (ch) {
         case 'v':
@@ -136,9 +120,13 @@ DControllerBase::parseArgs(int argc, char* argv[])
             verbose_ = true;
             break;
 
-        case 's':
-            // Enables stand alone or "BINDLESS" operation.
-            stand_alone_ = true;
+        case 'c':
+            // config file name
+            if (optarg == NULL) {
+                isc_throw(InvalidUsage, "configuration file name missing");
+            }
+
+            Daemon::init(optarg);
             break;
 
         case '?': {
@@ -168,6 +156,28 @@ DControllerBase::parseArgs(int argc, char* argv[])
     }
 }
 
+isc::data::ConstElementPtr
+DControllerBase::configHandler(isc::data::ConstElementPtr new_config) {
+    LOG_DEBUG(dctl_logger, DBGLVL_COMMAND, DCTL_CONFIG_UPDATE)
+              .arg(controller_->getAppName()).arg(new_config->str());
+
+    // 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(dctl_logger, DBGLVL_COMMAND, DCTL_COMMAND_RECEIVED)
+        .arg(controller_->getAppName()).arg(command)
+        .arg(args ? args->str() : "(no args)");
+
+    // Invoke the instance method on the controller singleton.
+    return (controller_->executeCommand(command, args));
+}
+
 bool
 DControllerBase::customOption(int /* option */, char* /*optarg*/)
 {
@@ -197,52 +207,41 @@ DControllerBase::initProcess() {
     process_->init();
 }
 
-void
-DControllerBase::establishSession() {
-    LOG_DEBUG(dctl_logger, DBGLVL_START_SHUT, DCTL_CCSESSION_STARTING)
-              .arg(app_name_).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(dctl_logger, DCTL_CONFIG_LOAD_FAIL)
-                  .arg(app_name_).arg(comment->str());
+isc::data::ConstElementPtr
+DControllerBase::configFromFile() {
+    isc::data::ConstElementPtr module_config;
+
+    try {
+        std::string config_file = getConfigFileName();
+        if (config_file.empty()) {
+            // Basic sanity check: file name must not be empty.
+            isc_throw(BadValue, "JSON configuration file not specified. Please "
+                                "use -c command line option.");
+        }
+
+        // Read contents of the file and parse it as JSON
+        isc::data::ConstElementPtr whole_config =
+            isc::data::Element::fromJSONFile(config_file, true);
+
+        // Extract derivation-specific portion of the configuration.
+        module_config = whole_config->get(getAppName());
+        if (!module_config) {
+            isc_throw(BadValue, "Config file " << config_file <<
+                                " does not include '" <<
+                                 getAppName() << "' entry.");
+        }
+    } catch (const std::exception& ex) {
+        // build an error result
+        isc::data::ConstElementPtr error =
+            isc::config::createAnswer(1,
+                std::string("Configuration parsing failed: ") + ex.what());
+        return (error);
     }
 
-    // Lastly, call onConnect. This allows deriving class to execute custom
-    // logic predicated by session connect.
-    onSessionConnect();
+    return (updateConfig(module_config));
 }
 
+
 void
 DControllerBase::runProcess() {
     LOG_DEBUG(dctl_logger, DBGLVL_START_SHUT, DCTL_RUN_PROCESS).arg(app_name_);
@@ -256,105 +255,14 @@ DControllerBase::runProcess() {
     process_->run();
 }
 
-void DControllerBase::disconnectSession() {
-    LOG_DEBUG(dctl_logger, DBGLVL_START_SHUT, DCTL_CCSESSION_ENDING)
-              .arg(app_name_);
-
-    // 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(dctl_logger, DBGLVL_START_SHUT, DCTL_CONFIG_STUB)
-             .arg(controller_->getAppName());
-    return (isc::config::createAnswer(0, "Configuration accepted."));
-}
-
-isc::data::ConstElementPtr
-DControllerBase::configHandler(isc::data::ConstElementPtr new_config) {
-
-    LOG_DEBUG(dctl_logger, DBGLVL_COMMAND, DCTL_CONFIG_UPDATE)
-              .arg(controller_->getAppName()).arg(new_config->str());
-
-    // 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(dctl_logger, DBGLVL_COMMAND, DCTL_COMMAND_RECEIVED)
-        .arg(controller_->getAppName()).arg(command)
-        .arg(args ? args->str() : "(no args)");
-
-    // Invoke the instance method on the controller singleton.
-    return (controller_->executeCommand(command, args));
-}
-
+// Instance method for handling new config
 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));
+    return (process_->configure(new_config));
 }
 
 
+// Instance method for executing commands
 isc::data::ConstElementPtr
 DControllerBase::executeCommand(const std::string& command,
                             isc::data::ConstElementPtr args) {
@@ -364,7 +272,7 @@ DControllerBase::executeCommand(const std::string& command,
     // as it may be supported there.
     isc::data::ConstElementPtr answer;
     if (command.compare(SHUT_DOWN_COMMAND) == 0) {
-        answer = shutdown(args);
+        answer = shutdownProcess(args);
     } else {
         // It wasn't shutdown, so may be a custom controller command.
         int rcode = 0;
@@ -390,10 +298,10 @@ DControllerBase::customControllerCommand(const std::string& command,
 }
 
 isc::data::ConstElementPtr
-DControllerBase::shutdown(isc::data::ConstElementPtr args) {
+DControllerBase::shutdownProcess(isc::data::ConstElementPtr args) {
     if (process_) {
         return (process_->shutdown(args));
-    } 
+    }
 
     // Not really a failure, but this condition is worth noting. In reality
     // it should be pretty hard to cause this.
@@ -408,16 +316,31 @@ DControllerBase::usage(const std::string & text)
         std::cerr << "Usage error: " << text << std::endl;
     }
 
-    std::cerr << "Usage: " << bin_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 << "Usage: " << bin_name_ <<  std::endl
+              << "  -c <config file name> : mandatory,"
+              <<   " specifies name of configuration file " << std::endl
+              << "  -v: optional, verbose output " << std::endl;
 
+    // add any derivation specific usage
     std::cerr << getUsageText() << std::endl;
 }
 
 DControllerBase::~DControllerBase() {
 }
 
+std::string
+DControllerBase::getConfigFileName() {
+    return (Daemon::getConfigFile());
+}
+
 }; // namespace isc::d2
+
+// Provide an implementation until we figure out a better way to do this.
+void
+dhcp::Daemon::loggerInit(const char* log_name, bool verbose) {
+    isc::log::initLogger(log_name,
+                         (verbose ? isc::log::DEBUG : isc::log::INFO),
+                         isc::log::MAX_DEBUG_LEVEL, NULL, true);
+}
+
 }; // namespace isc

+ 120 - 205
src/bin/d2/d_controller.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2013  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2014 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -16,11 +16,10 @@
 #define D_CONTROLLER_H
 
 #include <cc/data.h>
-#include <cc/session.h>
-#include <config/ccsession.h>
 #include <d2/d2_asio.h>
 #include <d2/d2_log.h>
 #include <d2/d_process.h>
+#include <dhcpsrv/daemon.h>
 #include <exceptions/exceptions.h>
 #include <log/logger_support.h>
 
@@ -49,13 +48,6 @@ public:
         isc::Exception(file, line, what) { };
 };
 
-/// @brief Exception thrown when the session start up fails.
-class SessionStartError: public isc::Exception {
-public:
-    SessionStartError (const char* file, size_t line, const char* what) :
-        isc::Exception(file, line, what) { };
-};
-
 /// @brief Exception thrown when the application process encounters an
 /// operation in its event loop (i.e. run method).
 class ProcessRunError: public isc::Exception {
@@ -64,14 +56,6 @@ public:
         isc::Exception(file, line, what) { };
 };
 
-/// @brief Exception thrown when the session end fails.
-class SessionEndError: public isc::Exception {
-public:
-    SessionEndError (const char* file, size_t line, const char* what) :
-        isc::Exception(file, line, what) { };
-};
-
-
 /// @brief Exception thrown when the controller encounters an operational error.
 class DControllerBaseError : public isc::Exception {
 public:
@@ -84,47 +68,33 @@ public:
 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 initialization, and runtime
-/// control through external command and configuration event handling.
+/// DProcessBase interface.  It runs the process like a stand-alone, command
+/// line driven executable, which must be supplied a configuration file at
+/// startup. It coordinates command line argument parsing, process
+/// instantiation and initialization, and runtime control through external
+/// command and configuration event handling.
 /// It creates the IOService instance which is used for runtime control
 /// events and passes the IOService into the application process at process
-/// creation.  In integrated mode it is responsible for establishing BIND10
-/// session(s) and passes this IOService into the session creation method(s).
-/// It also provides the callback handlers for command and configuration events
-/// received from the external framework (aka BIND10).  For example, when
-/// running in integrated mode and a user alters the configuration with the
-/// bindctl tool, BIND10 will emit a configuration message which is sensed by
-/// the controller's IOService. The IOService in turn invokes the configuration
-/// callback, DControllerBase::configHandler().  If the user issues a command
-/// such as shutdown via bindctl,  BIND10 will emit a command message, which is
-/// sensed by controller's IOService which invokes the command callback,
-/// DControllerBase::commandHandler().
+/// creation.
+/// It provides the callback handlers for command and configuration events
+/// which could be triggered by an external source.  Such sources are intended
+/// to be registed with and monitored by the controller's IOService such that
+/// the appropriate handler can be invoked.
 ///
 /// 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 {
+/// member in order for it to be available for static callback functions.
+class DControllerBase : public dhcp::Daemon {
 public:
     /// @brief Constructor
     ///
     /// @param app_name is display name of the application under control. This
     /// name appears in log statements.
-    /// @param bin_name is the name of the application executable. Typically
-    /// this matches the BIND10 module name.
+    /// @param bin_name is the name of the application executable.
     DControllerBase(const char* app_name, const char* bin_name);
 
     /// @brief Destructor
@@ -135,21 +105,17 @@ public:
     ///
     /// 1. parse command line arguments
     /// 2. instantiate and initialize the application process
-    /// 3. establish BIND10 session(s) if in integrated mode
+    /// 3. load the configuration file
     /// 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
+    /// 5. 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.
+    /// arguments.
     ///
-    /// This function can be run in the test mode. It prevents initialization
+    /// This function can be run in "test mode". It prevents initialization
     /// of D2 module logger. This is used in unit tests which initialize logger
-    /// in their main function. Such logger uses environmental variables to
-    /// control severity, verbosity etc. Reinitialization of logger by this
-    /// function would replace unit tests specific logger configuration with
-    /// this suitable for D2 running as a bind10 module.
+    /// in their main function. Such a logger uses environmental variables to
+    /// control severity, verbosity etc.
     ///
     /// @param argc  is the number of command line arguments supplied
     /// @param argv  is the array of string (char *) command line arguments
@@ -162,59 +128,9 @@ public:
     /// InvalidUsage - Indicates invalid command line.
     /// ProcessInitError  - Failed to create and initialize application
     /// process object.
-    /// SessionStartError  - Could not connect to BIND10 (integrated mode only).
     /// ProcessRunError - A fatal error occurred while in the application
     /// process event loop.
-    /// SessionEndError - Could not disconnect from BIND10 (integrated mode
-    /// only).
-    void launch(int argc, char* argv[], const bool test_mode);
-
-    /// @brief A dummy configuration handler that always returns success.
-    ///
-    /// This configuration handler does not perform configuration
-    /// parsing and always returns success. A dummy handler should
-    /// be installed using \ref isc::config::ModuleCCSession ctor
-    /// to get the initial configuration. This initial configuration
-    /// comprises values for only those elements that were modified
-    /// the previous session. The D2 configuration parsing can't be
-    /// used to parse the initial configuration because it may need the
-    /// full configuration to satisfy dependencies between the
-    /// various configuration values. Installing the dummy handler
-    /// that guarantees to return success causes initial configuration
-    /// to be stored for the session being created and that it can
-    /// be later accessed with \ref isc::config::ConfigData::getFullConfig.
-    ///
-    /// @param new_config new configuration.
-    ///
-    /// @return success configuration status.
-    static isc::data::ConstElementPtr
-    dummyConfigHandler(isc::data::ConstElementPtr new_config);
-
-    /// @brief 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. It can be NULL pointer if no
-    /// arguments exist for a particular command.
-    ///
-    /// @return status of the processed command
-    static isc::data::ConstElementPtr
-    commandHandler(const std::string& command, isc::data::ConstElementPtr args);
+    virtual void launch(int argc, char* argv[], const bool test_mode);
 
     /// @brief Instance method invoked by the configuration event handler and
     /// which processes the actual configuration update.  Provides behavioral
@@ -222,17 +138,40 @@ public:
     /// implementation will merge the configuration update into the existing
     /// configuration and then invoke the application process' configure method.
     ///
-    /// @todo This implementation is will evolve as the D2 configuration
-    /// management task is implemented (trac #2957).
-    ///
     /// @param  new_config is the new configuration
     ///
     /// @return returns an Element that contains the results of configuration
     /// update composed of an integer status value (0 means successful,
     /// non-zero means failure), and a string explanation of the outcome.
-    virtual isc::data::ConstElementPtr
-    updateConfig(isc::data::ConstElementPtr new_config);
+    virtual isc::data::ConstElementPtr updateConfig(isc::data::ConstElementPtr
+                                                    new_config);
 
+    /// @brief Reconfigures the process from a configuration file
+    ///
+    /// By default the file is assumed to be a JSON text file whose contents
+    /// include at least:
+    ///
+    /// @code
+    ///  { "<module-name>": {<module-config>} }
+    ///
+    ///  where:
+    ///     module-name : is a label which uniquely identifies the
+    ///                   configuration data for this controller's application
+    ///
+    ///     module-config: a set of zero or more JSON elements which comprise
+    ///                    the application'ss configuration values
+    /// @endcode
+    ///
+    /// The method extracts the set of configuration elements for the
+    /// module-name which matches the controller's app_name_ and passes that
+    /// set into @c udpateConfig().
+    ///
+    /// The file may contain an arbitrary number of other modules.
+    ///
+    /// @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 configFromFile();
 
     /// @brief Instance method invoked by the command event handler and  which
     /// processes the actual command directive.
@@ -262,8 +201,51 @@ public:
     ///   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);
+    virtual isc::data::ConstElementPtr executeCommand(const std::string&
+                                                      command,
+                                                      isc::data::
+                                                      ConstElementPtr args);
+
+    /// @brief A callback for handling all incoming configuration updates.
+    ///
+    /// Provides a static callback that can be used to handle asynchronsouly
+    /// received configurations. It acts as a wrapper around the singleton's
+    /// 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.
+    ///
+    /// Provides a static callback that can be used to handle asynchronsouly
+    /// received commands. It acts as a wrapper around the singleton's virtual
+    /// instance method, executeCommand.
+    ///
+    /// @param command textual representation of the command
+    /// @param args parameters of the command. It can be NULL pointer if no
+    /// arguments exist for a particular command.
+    ///
+    /// @return status of the processed command
+    static isc::data::ConstElementPtr commandHandler(const std::string& command,
+                                                     isc::data::ConstElementPtr
+                                                     args);
+
+    /// @brief Fetches the name of the application under control.
+    ///
+    /// @return returns the controller service name string
+    const std::string getAppName() const {
+        return (app_name_);
+    }
+
+    /// @brief Fetches the name of the application executable.
+    ///
+    /// @return returns the controller logger name string
+    const std::string getBinName() const {
+        return (bin_name_);
+    }
 
 protected:
     /// @brief Virtual method that provides derivations the opportunity to
@@ -309,26 +291,6 @@ protected:
     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 throw 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 throw 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.
@@ -340,7 +302,7 @@ protected:
 
     /// @brief Virtual method which returns a string containing the option
     /// letters for any custom command line options supported by the derivation.
-    /// These are added to the stock options of "s" and "v" during command
+    /// These are added to the stock options of "c" and "v" during command
     /// line interpretation.
     ///
     /// @return returns a string containing the custom option letters.
@@ -348,34 +310,6 @@ protected:
         return ("");
     }
 
-    /// @brief Fetches the name of the application under control.
-    ///
-    /// @return returns the controller service name string
-    const std::string getAppName() const {
-        return (app_name_);
-    }
-
-    /// @brief Fetches the name of the application executable.
-    ///
-    /// @return returns the controller logger name string
-    const std::string getBinName() const {
-        return (bin_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() const {
-        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.
@@ -397,7 +331,7 @@ protected:
         return (io_service_);
     }
 
-    /// @brief Getter for fetching the name of the controller's BIND10 spec
+    /// @brief Getter for fetching the name of the controller's config spec
     /// file.
     ///
     /// @return returns the file name string.
@@ -405,7 +339,7 @@ protected:
         return (spec_file_name_);
     }
 
-    /// @brief Setter for setting the name of the controller's BIND10 spec file.
+    /// @brief Setter for setting the name of the controller's config spec file.
     ///
     /// @param spec_file_name the file name string.
     void setSpecFileName(const std::string& spec_file_name) {
@@ -428,14 +362,13 @@ protected:
     /// instance a second time.
     static void setController(const DControllerBasePtr& 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.
+    /// -c for specifying the configuration file, 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
@@ -453,18 +386,6 @@ private:
     /// 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
     /// requested setup: command line parsing, application initialization,
@@ -476,15 +397,6 @@ private:
     // @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
@@ -495,12 +407,24 @@ private:
     /// until the process has halted.  Rather it is used to convey the
     /// need to shutdown.  A successful return indicates that the shutdown
     /// has successfully commenced, but does not indicate that the process
-    /// has actually exited. 
+    /// has actually exited.
     ///
     /// @return returns an Element that contains the results of shutdown
     /// command composed of an integer status value (0 means successful,
     /// non-zero means failure), and a string explanation of the outcome.
-    isc::data::ConstElementPtr shutdown(isc::data::ConstElementPtr args);
+    isc::data::ConstElementPtr shutdownProcess(isc::data::ConstElementPtr args);
+
+    /// @brief Fetches the name of the configuration file
+    ///
+    /// @return the configuration file name as a string
+    virtual std::string getConfigFileName();
+
+    /// @brief Fetches the current process
+    ///
+    /// @return the a pointer to the current process instance.
+    DProcessBasePtr getProcess() {
+        return (process_);
+    }
 
     /// @brief Prints the program usage text to std error.
     ///
@@ -509,23 +433,20 @@ private:
     void usage(const std::string& text);
 
 private:
-    /// @brief Display name of the service under control. This name
-    /// appears in log statements.
+    /// @brief Name of the service under control.
+    /// This name is used as the configuration module name and appears in log
+    /// statements.
     std::string app_name_;
 
-    /// @brief Name of the service executable. By convention this matches
-    /// the BIND10 module name. It is also used to establish the logger
-    /// name.
+    /// @brief Name of the service executable.
+    /// By convention this matches the executable nam. It is also used to
+    /// establish the logger name.
     std::string bin_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.
+    /// @brief The absolute file name of the JSON spec file.
     std::string spec_file_name_;
 
     /// @brief Pointer to the instance of the process.
@@ -537,12 +458,6 @@ private:
     /// @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_;
 

+ 13 - 5
src/bin/d2/tests/Makefile.am

@@ -54,10 +54,9 @@ TESTS += d2_unittests
 d2_unittests_SOURCES = ../d2_asio.h
 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_controller.cc ../d_controller.h
 d2_unittests_SOURCES += ../d_cfg_mgr.cc ../d_cfg_mgr.h
+d2_unittests_SOURCES += ../d2_process.cc ../d2_process.h
 d2_unittests_SOURCES += ../d2_config.cc ../d2_config.h
 d2_unittests_SOURCES += ../d2_cfg_mgr.cc ../d2_cfg_mgr.h
 d2_unittests_SOURCES += ../d2_queue_mgr.cc ../d2_queue_mgr.h
@@ -73,8 +72,6 @@ d2_unittests_SOURCES += ../state_model.cc ../state_model.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
 d2_unittests_SOURCES += d_cfg_mgr_unittests.cc
 d2_unittests_SOURCES += d2_cfg_mgr_unittests.cc
 d2_unittests_SOURCES += d2_queue_mgr_unittests.cc
@@ -90,6 +87,17 @@ d2_unittests_SOURCES += nc_trans_unittests.cc
 d2_unittests_SOURCES += state_model_unittests.cc
 nodist_d2_unittests_SOURCES = ../d2_messages.h ../d2_messages.cc
 
+if CONFIG_BACKEND_BUNDY
+d2_unittests_SOURCES += ../bundy_d2_controller.cc ../bundy_d2_controller.h
+d2_unittests_SOURCES += bundy_d2_controller_unittests.cc
+else
+if CONFIG_BACKEND_JSON
+d2_unittests_SOURCES += ../d2_controller.cc ../d2_controller.h
+d2_unittests_SOURCES += d2_controller_unittests.cc
+d2_unittests_SOURCES += d_controller_unittests.cc
+endif
+endif
+
 d2_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
 d2_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
 d2_unittests_LDADD = $(GTEST_LDADD)

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

@@ -0,0 +1,191 @@
+// Copyright (C) 2014  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config/ccsession.h>
+#include <d_test_stubs.h>
+#include <d2/bundy_d2_controller.h>
+#include <d2/spec_config.h>
+
+#include <boost/pointer_cast.hpp>
+#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 Test fixture class for testing D2Controller class. This class
+/// derives from DControllerTest and wraps a D2Controller.  Much of the
+/// underlying functionality is in the DControllerBase class which has an
+/// extensive set of unit tests that are independent of DHCP-DDNS.
+/// @TODO Currently These tests are relatively light and duplicate some of
+/// the testing done on the base class.  These tests are sufficient to ensure
+/// that D2Controller properly derives from its base class and to test the
+/// logic that is unique to D2Controller. These tests will be augmented and
+/// or new tests added as additional functionality evolves.
+/// Unlike the stub testing, there is no use of SimFailure to induce error
+/// conditions as this is production code.
+class BundyD2ControllerTest : public DControllerTest {
+public:
+    /// @brief Constructor
+    /// Note the constructor passes in the static D2Controller instance
+    /// method.
+    BundyD2ControllerTest() : DControllerTest(D2Controller::instance) {
+    }
+
+    /// @brief Destructor
+    ~BundyD2ControllerTest() {
+    }
+};
+
+/// @brief Basic Controller instantiation testing.
+/// Verifies that the controller singleton gets created and that the
+/// basic derivation from the base class is intact.
+TEST_F(BundyD2ControllerTest, basicInstanceTesting) {
+    // Verify the we can the singleton instance can be fetched and that
+    // it is the correct type.
+    DControllerBasePtr& controller = DControllerTest::getController();
+    ASSERT_TRUE(controller);
+    ASSERT_NO_THROW(boost::dynamic_pointer_cast<D2Controller>(controller));
+
+    // Verify that controller's app name is correct.
+    EXPECT_TRUE(checkAppName(D2Controller::d2_app_name_));
+
+    // Verify that controller's bin name is correct.
+    EXPECT_TRUE(checkBinName(D2Controller::d2_bin_name_));
+
+    // Verify that controller's spec file name is correct.
+    EXPECT_TRUE(checkSpecFileName(D2_SPECFILE_LOCATION));
+
+    // Verify that controller's IOService exists.
+    EXPECT_TRUE(checkIOService());
+
+    // Verify that the Process does NOT exist.
+    EXPECT_FALSE(checkProcess());
+}
+
+/// @brief Tests basic command line processing.
+/// Verifies that:
+/// 1. Standard command line options are supported.
+/// 2. Invalid options are detected.
+TEST_F(BundyD2ControllerTest, commandLineArgs) {
+    char* argv[] = { const_cast<char*>("progName"),
+                     const_cast<char*>("-v") };
+    int argc = 2;
+
+    // Verify that verbose flag is false initially.
+    EXPECT_TRUE(checkVerbose(false));
+
+    // Verify that standard options can be parsed without error.
+    EXPECT_NO_THROW(parseArgs(argc, argv));
+
+    // Verify that verbose flag is now true.
+    EXPECT_TRUE(checkVerbose(true));
+
+    // Verify that an unknown option is detected.
+    char* argv2[] = { const_cast<char*>("progName"),
+                      const_cast<char*>("-x") };
+    argc = 2;
+    EXPECT_THROW(parseArgs(argc, argv2), InvalidUsage);
+}
+
+/// @brief Tests application process creation and initialization.
+/// Verifies that the process can be successfully created and initialized.
+TEST_F(BundyD2ControllerTest, initProcessTesting) {
+    ASSERT_NO_THROW(initProcess());
+    EXPECT_TRUE(checkProcess());
+}
+
+/// @brief Configuration update event testing.
+/// This really tests just the ability of the handlers to invoke the necessary
+/// chain of methods and handle error conditions. Configuration parsing and
+/// retrieval should be tested as part of the d2 configuration management
+/// implementation.  Note that this testing calls the configuration update event
+/// callback, configHandler, directly.
+/// This test verifies that:
+/// 1. Configuration will be rejected in integrated mode when there is no
+/// session established. (This is a very contrived situation).
+/// 2. In stand-alone mode a configuration update results in successful
+/// status return.
+/// 3. That an application process error in configuration updating is handled
+/// properly.
+TEST_F(BundyD2ControllerTest, configUpdateTests) {
+    int rcode = -1;
+    isc::data::ConstElementPtr answer;
+
+    // Initialize the application process.
+    ASSERT_NO_THROW(initProcess());
+    EXPECT_TRUE(checkProcess());
+
+    // Create a configuration set using a small, valid D2 configuration.
+    isc::data::ElementPtr config_set =
+                                isc::data::Element::fromJSON(valid_d2_config);
+
+    // 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);
+}
+
+/// @brief Command execution tests.
+/// This really tests just the ability of the handler to invoke the necessary
+/// chain of methods and to handle error conditions. Note that this testing
+/// calls the command callback, commandHandler, directly.
+/// This test verifies that:
+/// 1. That an unrecognized command is detected and returns a status of
+/// d2::COMMAND_INVALID.
+/// 2. Shutdown command is recognized and returns a d2::COMMAND_SUCCESS status.
+TEST_F(BundyD2ControllerTest, executeCommandTests) {
+    int rcode = -1;
+    isc::data::ConstElementPtr answer;
+    isc::data::ElementPtr arg_set;
+
+    // Initialize the application process.
+    ASSERT_NO_THROW(initProcess());
+    EXPECT_TRUE(checkProcess());
+
+    // Verify that an unknown command returns an COMMAND_INVALID 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 shutdown command returns COMMAND_SUCCESS 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);
+}
+
+/// @brief Tests launch with a session establishment failure.
+/// This test launches with a valid command line for integrated mode and no.
+/// Attempting to connect to BIND10 should fail, even if BIND10 is running
+/// UNLESS the test is run as root.  Launch should throw SessionStartError.
+TEST_F(BundyD2ControllerTest, launchSessionFailure) {
+    // Command line to run integrated
+    char* argv[] = { (char*)"progName" };
+    int argc = 1;
+
+    // Launch the controller in integrated mode.
+    EXPECT_THROW(launch(argc, argv), SessionStartError);
+}
+
+}; // end of isc::d2 namespace
+}; // end of isc namespace

+ 19 - 24
src/bin/d2/tests/d2_controller_unittests.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2013  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2014 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -85,21 +85,23 @@ TEST_F(D2ControllerTest, basicInstanceTesting) {
 /// 2. Invalid options are detected.
 TEST_F(D2ControllerTest, commandLineArgs) {
     char* argv[] = { const_cast<char*>("progName"),
-                     const_cast<char*>("-s"),
+                     const_cast<char*>("-c"),
+                     const_cast<char*>(DControllerTest::CFG_TEST_FILE),
                      const_cast<char*>("-v") };
-    int argc = 3;
+    int argc = 4;
 
-    // Verify that both flags are false initially.
-    EXPECT_TRUE(checkStandAlone(false));
+    // Verify that verbose flag is false initially.
     EXPECT_TRUE(checkVerbose(false));
 
     // Verify that standard options can be parsed without error.
     EXPECT_NO_THROW(parseArgs(argc, argv));
 
-    // Verify that flags are now true.
-    EXPECT_TRUE(checkStandAlone(true));
+    // Verify that verbose flag is true.
     EXPECT_TRUE(checkVerbose(true));
 
+    // Verify configuration file name is correct.
+    EXPECT_TRUE(checkConfigFileName(DControllerTest::CFG_TEST_FILE));
+
     // Verify that an unknown option is detected.
     char* argv2[] = { const_cast<char*>("progName"),
                       const_cast<char*>("-x") };
@@ -120,9 +122,13 @@ TEST_F(D2ControllerTest, initProcessTesting) {
 TEST_F(D2ControllerTest, launchNormalShutdown) {
     // command line to run standalone
     char* argv[] = { const_cast<char*>("progName"),
-                     const_cast<char*>("-s"),
+                     const_cast<char*>("-c"),
+                     const_cast<char*>(DControllerTest::CFG_TEST_FILE),
                      const_cast<char*>("-v") };
-    int argc = 3;
+    int argc = 4;
+
+    // Create a valid D2 configuration file.
+    writeFile(valid_d2_config);
 
     // Use an asiolink IntervalTimer and callback to generate the
     // shutdown invocation. (Note IntervalTimer setup is in milliseconds).
@@ -151,11 +157,8 @@ TEST_F(D2ControllerTest, launchNormalShutdown) {
 /// implementation.  Note that this testing calls the configuration update event
 /// callback, configHandler, directly.
 /// This test verifies that:
-/// 1. Configuration will be rejected in integrated mode when there is no
-/// session established. (This is a very contrived situation).
-/// 2. In stand-alone mode a configuration update results in successful
-/// status return.
-/// 3. That an application process error in configuration updating is handled
+/// 1. A valid configuration yields a successful parse result.
+/// 2. That an application process error in configuration updating is handled
 /// properly.
 TEST_F(D2ControllerTest, configUpdateTests) {
     int rcode = -1;
@@ -169,15 +172,7 @@ TEST_F(D2ControllerTest, configUpdateTests) {
     isc::data::ElementPtr config_set =
                                 isc::data::Element::fromJSON(valid_d2_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);
+    // Verify that given a valid config we get a successful update result.
     answer = DControllerBase::configHandler(config_set);
     isc::config::parseAnswer(rcode, answer);
     EXPECT_EQ(0, rcode);
@@ -193,7 +188,7 @@ TEST_F(D2ControllerTest, configUpdateTests) {
 /// @brief Command execution tests.
 /// This really tests just the ability of the handler to invoke the necessary
 /// chain of methods and to handle error conditions. Note that this testing
-/// calls the command callback, commandHandler, directly.
+/// calls the executeCommand method directly.
 /// This test verifies that:
 /// 1. That an unrecognized command is detected and returns a status of
 /// d2::COMMAND_INVALID.

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

@@ -1,4 +1,4 @@
-# Copyright (C) 2013 Internet Systems Consortium.
+# Copyright (C) 2013-2014 Internet Systems Consortium.
 #
 # Permission to use, copy, modify, and distribute this software for any
 # purpose with or without fee is hereby granted, provided that the above
@@ -159,8 +159,7 @@ 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-dhcp-ddns", 
-                                                       "-s", "-v"])
+        (returncode, output, error) = self.runCommand(["../b10-dhcp-ddns", "-v"])
         output_text = str(output) + str(error)
         self.assertEqual(output_text.count("DCTL_STARTING"), 1)
 

+ 67 - 41
src/bin/d2/tests/d_controller_unittests.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2013  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2014 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -76,21 +76,23 @@ TEST_F(DStubControllerTest, basicInstanceTesting) {
 /// 4. Extraneous command line information is detected.
 TEST_F(DStubControllerTest, commandLineArgs) {
 
-    // Verify that both flags are false initially.
-    EXPECT_TRUE(checkStandAlone(false));
+    // Verify that verbose flag is false initially.
     EXPECT_TRUE(checkVerbose(false));
 
     // Verify that standard options can be parsed without error.
     char* argv[] = { const_cast<char*>("progName"),
-                     const_cast<char*>("-s"),
+                     const_cast<char*>("-c"),
+                     const_cast<char*>("cfgName"),
                      const_cast<char*>("-v") };
-    int argc = 3;
+    int argc = 4;
     EXPECT_NO_THROW(parseArgs(argc, argv));
 
-    // Verify that flags are now true.
-    EXPECT_TRUE(checkStandAlone(true));
+    // Verify that verbose is true.
     EXPECT_TRUE(checkVerbose(true));
 
+    // Verify configuration file name is correct
+    EXPECT_TRUE(checkConfigFileName("cfgName"));
+
     // Verify that the custom command line option is parsed without error.
     char xopt[3] = "- ";
     xopt[1] =  *DStubController::stub_option_x_;
@@ -166,9 +168,10 @@ TEST_F(DStubControllerTest, launchInvalidUsage) {
 TEST_F(DStubControllerTest, launchProcessInitError) {
     // Command line to run integrated
     char* argv[] = { const_cast<char*>("progName"),
-                     const_cast<char*>("-s"),
+                     const_cast<char*>("-c"),
+                     const_cast<char*>(DControllerTest::CFG_TEST_FILE),
                      const_cast<char*>("-v") };
-    int argc = 3;
+    int argc = 4;
 
     // Launch the controller in stand alone mode.
     SimFailure::set(SimFailure::ftCreateProcessException);
@@ -177,13 +180,19 @@ TEST_F(DStubControllerTest, launchProcessInitError) {
 
 /// @brief Tests launch and normal shutdown (stand alone mode).
 /// This creates an interval timer to generate a normal shutdown and then
-/// launches with a valid, stand-alone command line and no simulated errors.
+/// launches with a valid, command line, with a valid configuration file
+///  and no simulated errors.
 TEST_F(DStubControllerTest, launchNormalShutdown) {
     // command line to run standalone
     char* argv[] = { const_cast<char*>("progName"),
-                     const_cast<char*>("-s"),
+                     const_cast<char*>("-c"),
+                     const_cast<char*>(DControllerTest::CFG_TEST_FILE),
                      const_cast<char*>("-v") };
-    int argc = 3;
+    int argc = 4;
+
+    // Create a non-empty, config file.  writeFile will wrap the contents
+    // with the module name for us.
+    writeFile("{}");
 
     // Use an asiolink IntervalTimer and callback to generate the
     // shutdown invocation. (Note IntervalTimer setup is in milliseconds).
@@ -205,6 +214,42 @@ TEST_F(DStubControllerTest, launchNormalShutdown) {
                 elapsed.total_milliseconds() <= 2200);
 }
 
+/// @brief Tests launch with an nonexistant configuration file.
+TEST_F(DStubControllerTest, nonexistantConfigFile) {
+    // command line to run standalone
+    char* argv[] = { const_cast<char*>("progName"),
+                     const_cast<char*>("-c"),
+                     const_cast<char*>("bogus-file"),
+                     const_cast<char*>("-v") };
+    int argc = 4;
+
+    // Record start time, and invoke launch().
+    EXPECT_THROW(launch(argc, argv), ProcessInitError);
+}
+
+/// @brief Tests launch with configuration file argument but no file name
+TEST_F(DStubControllerTest, missingConfigFileName) {
+    // command line to run standalone
+    char* argv[] = { const_cast<char*>("progName"),
+                     const_cast<char*>("-c"),
+                     const_cast<char*>("-v") };
+    int argc = 3;
+
+    // Record start time, and invoke launch().
+    EXPECT_THROW(launch(argc, argv), ProcessInitError);
+}
+
+/// @brief Tests launch with no configuration file argument
+TEST_F(DStubControllerTest, missingConfigFileArgument) {
+    // command line to run standalone
+    char* argv[] = { const_cast<char*>("progName"),
+                     const_cast<char*>("-v") };
+    int argc = 2;
+
+    // Record start time, and invoke launch().
+    EXPECT_THROW(launch(argc, argv), ProcessInitError);
+}
+
 /// @brief Tests launch with an operational error during application execution.
 /// This test creates an interval timer to generate a runtime exception during
 /// the process event loop. It launches wih a valid, stand-alone command line
@@ -212,9 +257,14 @@ TEST_F(DStubControllerTest, launchNormalShutdown) {
 TEST_F(DStubControllerTest, launchRuntimeError) {
     // command line to run standalone
     char* argv[] = { const_cast<char*>("progName"),
-                     const_cast<char*>("-s"),
+                     const_cast<char*>("-c"),
+                     const_cast<char*>(DControllerTest::CFG_TEST_FILE),
                      const_cast<char*>("-v") };
-    int argc = 3;
+    int argc = 4;
+
+    // Create a non-empty, config file.  writeFile will wrap the contents
+    // with the module name for us.
+    writeFile("{}");
 
     // Use an asiolink IntervalTimer and callback to generate the
     // shutdown invocation. (Note IntervalTimer setup is in milliseconds).
@@ -236,19 +286,6 @@ TEST_F(DStubControllerTest, launchRuntimeError) {
                 elapsed.total_milliseconds() <= 2200);
 }
 
-/// @brief Tests launch with a session establishment failure.
-/// This test launches with a valid command line for integrated mode and no.
-/// Attempting to connect to BIND10 should fail, even if BIND10 is running
-/// UNLESS the test is run as root.  Launch should throw SessionStartError.
-TEST_F(DStubControllerTest, launchSessionFailure) {
-    // Command line to run integrated
-    char* argv[] = { (char*)"progName" };
-    int argc = 1;
-
-    // Launch the controller in integrated mode.
-    EXPECT_THROW(launch(argc, argv), SessionStartError);
-}
-
 /// @brief Configuration update event testing.
 /// This really tests just the ability of the handlers to invoke the necessary
 /// chain of methods and handle error conditions. Configuration parsing and
@@ -256,11 +293,8 @@ TEST_F(DStubControllerTest, launchSessionFailure) {
 /// implementation.  Note that this testing calls the configuration update event
 /// callback, configHandler, directly.
 /// This test verifies that:
-/// 1. Configuration will be rejected in integrated mode when there is no
-/// session established. (This is a very contrived situation).
-/// 2. In stand-alone mode a configuration update results in successful
-/// status return.
-/// 3. That an application process error in configuration updating is handled
+/// 1. That a valid configuration update results in successful status return.
+/// 2. That an application process error in configuration updating is handled
 /// properly.
 TEST_F(DStubControllerTest, configUpdateTests) {
     int rcode = -1;
@@ -275,15 +309,7 @@ TEST_F(DStubControllerTest, configUpdateTests) {
     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);
+    // Verify that a valid config gets a successful update result.
     answer = DControllerBase::configHandler(config_set);
     isc::config::parseAnswer(rcode, answer);
     EXPECT_EQ(0, rcode);

+ 6 - 1
src/bin/d2/tests/d_test_stubs.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2013  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2014 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -55,6 +55,7 @@ DStubProcess::DStubProcess(const char* name, IOServicePtr io_service)
     : DProcessBase(name, io_service, DCfgMgrBasePtr(new DStubCfgMgr())) {
 };
 
+
 void
 DStubProcess::init() {
     if (SimFailure::shouldFailOn(SimFailure::ftProcessInit)) {
@@ -140,6 +141,7 @@ const char* DStubController::stub_app_name_ = "TestService";
 /// @brief Defines the bin name used to construct the controller
 const char* DStubController::stub_bin_name_ = "TestBin";
 
+
 DControllerBasePtr&
 DStubController::instance() {
     // If the singleton hasn't been created, do it now.
@@ -217,6 +219,9 @@ DStubController::~DStubController() {
 // Initialize controller wrapper's static instance getter member.
 DControllerTest::InstanceGetter DControllerTest::instanceGetter_ = NULL;
 
+/// @brief Defines the name of the configuration file to use
+const char* DControllerTest::CFG_TEST_FILE = "d2-test-config.json";
+
 //************************** ObjectParser *************************
 
 ObjectParser::ObjectParser(const std::string& param_name,

+ 48 - 30
src/bin/d2/tests/d_test_stubs.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2013  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2014 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -25,6 +25,10 @@
 
 #include <gtest/gtest.h>
 
+#include <fstream>
+#include <iostream>
+#include <sstream>
+
 namespace isc {
 namespace d2 {
 
@@ -293,6 +297,7 @@ public:
     /// a clean start between tests.
     virtual ~DControllerTest() {
         getController().reset();
+        static_cast<void>(unlink(CFG_TEST_FILE));
     }
 
     /// @brief Convenience method that destructs and then recreates the
@@ -361,31 +366,22 @@ public:
         return (getController()->io_service_);
     }
 
-    /// @brief Compares stand alone flag with the given value.
+    /// @brief Compares verbose flag with the given value.
     ///
     /// @param value
     ///
-    /// @return returns true if the stand alone flag is equal to the given
-    /// value.
-    bool checkStandAlone(bool value) {
-        return (getController()->isStandAlone() == value);
-    }
-
-    /// @brief Sets the controller's stand alone flag to the given value.
-    ///
-    /// @param value is the new value to assign.
-    ///
-    void setStandAlone(bool value) {
-        getController()->setStandAlone(value);
+    /// @return returns true if the verbose flag is equal to the given value.
+    bool checkVerbose(bool value) {
+        return (getController()->isVerbose() == value);
     }
 
-    /// @brief Compares verbose flag with the given value.
+    /// @brief Compares configuration file name with the given value.
     ///
-    /// @param value
+    /// @param value file name to compare against
     ///
     /// @return returns true if the verbose flag is equal to the given value.
-    bool checkVerbose(bool value) {
-        return (getController()->isVerbose() == value);
+    bool checkConfigFileName(const std::string& value) {
+        return (getController()->getConfigFileName() == value);
     }
 
     /// @Wrapper to invoke the Controller's parseArgs method.  Please refer to
@@ -400,12 +396,6 @@ public:
         getController()->initProcess();
     }
 
-    /// @Wrapper to invoke the Controller's establishSession method.  Please
-    /// refer to DControllerBase::establishSession for details.
-    void establishSession() {
-        getController()->establishSession();
-    }
-
     /// @Wrapper to invoke the Controller's launch method.  Please refer to
     /// DControllerBase::launch for details.
     void launch(int argc, char* argv[]) {
@@ -413,12 +403,6 @@ public:
         getController()->launch(argc, argv, true);
     }
 
-    /// @Wrapper to invoke the Controller's disconnectSession method.  Please
-    /// refer to DControllerBase::disconnectSession for details.
-    void disconnectSession() {
-        getController()->disconnectSession();
-    }
-
     /// @Wrapper to invoke the Controller's updateConfig method.  Please
     /// refer to DControllerBase::updateConfig for details.
     isc::data::ConstElementPtr updateConfig(isc::data::ConstElementPtr
@@ -444,6 +428,40 @@ public:
     static void genFatalErrorCallback() {
         isc_throw (DProcessBaseError, "simulated fatal error");
     }
+
+    /// @brief writes specified content to a well known file
+    ///
+    /// Writes given JSON content to CFG_TEST_FILE. It will wrap the
+    /// content within a JSON element whose name is equal to the controller's
+    /// app name or the given module name if not blank:
+    ///
+    /// @code
+    ///    { "<app_name>" : <content> }
+    /// @endcod
+    ///
+    /// suffix the content within a JSON element with the given module
+    /// name or  wrapped by a JSON
+    /// element  . Tests will
+    /// attempt to read that file.
+    ///
+    /// @param content JSON text to be written to file
+    /// @param module_name  content content to be written to file
+    void writeFile(const std::string& content,
+                   const std::string module_name = "") {
+        std::ofstream out(CFG_TEST_FILE, std::ios::trunc);
+        ASSERT_TRUE(out.is_open());
+
+        out << "{ \"" << (!module_name.empty() ? module_name :
+                          getController()->getAppName())
+             << "\": " << std::endl;
+
+        out << content;
+        out << " } " << std::endl;
+        out.close();
+    }
+
+    /// Name of a config file used during tests
+    static const char* CFG_TEST_FILE;
 };
 
 /// @brief a collection of elements that store uint32 values