]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[client] d_controller.cc|h copied from lib/process
authorTomek <tomasz.mrugalski@gmail.com>
Sat, 15 Jul 2017 14:03:06 +0000 (16:03 +0200)
committerTomek Mrugalski <tomasz@isc.org>
Mon, 18 Nov 2019 19:26:54 +0000 (03:26 +0800)
src/bin/client/d_controller.cc [new file with mode: 0644]
src/bin/client/d_controller.h [new file with mode: 0644]

diff --git a/src/bin/client/d_controller.cc b/src/bin/client/d_controller.cc
new file mode 100644 (file)
index 0000000..6f31a83
--- /dev/null
@@ -0,0 +1,687 @@
+// Copyright (C) 2013-2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <cc/command_interpreter.h>
+#include <cfgrpt/config_report.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <exceptions/exceptions.h>
+#include <log/logger.h>
+#include <log/logger_support.h>
+#include <process/d_log.h>
+#include <process/d_controller.h>
+
+#ifdef HAVE_MYSQL
+#include <dhcpsrv/mysql_lease_mgr.h>
+#endif
+#ifdef HAVE_PGSQL
+#include <dhcpsrv/pgsql_lease_mgr.h>
+#endif
+#ifdef HAVE_CQL
+#include <dhcpsrv/cql_lease_mgr.h>
+#endif
+#include <dhcpsrv/memfile_lease_mgr.h>
+
+#include <sstream>
+#include <unistd.h>
+
+using namespace isc::data;
+using namespace isc::config;
+
+namespace isc {
+namespace process {
+
+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),
+      verbose_(false), check_only_(false), spec_file_name_(""),
+      io_service_(new isc::asiolink::IOService()),
+      io_signal_queue_() {
+}
+
+void
+DControllerBase::setController(const DControllerBasePtr& controller) {
+    if (controller_) {
+        // This shouldn't happen, but let's make sure it can't be done.
+        // It represents a programmatic error.
+        isc_throw (DControllerBaseError,
+                "Multiple controller instances attempted.");
+    }
+
+    controller_ = controller;
+}
+
+ConstElementPtr
+DControllerBase::parseFile(const std::string&) {
+    ConstElementPtr elements;
+    return (elements);
+}
+
+void
+DControllerBase::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());
+        // rethrow it with an empty message
+        isc_throw(InvalidUsage, "");
+    }
+
+    setProcName(bin_name_);
+
+    if (isCheckOnly()) {
+        checkConfigOnly();
+        return;
+    }
+
+    // It is important that we set a default logger name because this name
+    // will be used when the user doesn't provide the logging configuration
+    // in the Kea configuration file.
+    isc::dhcp::CfgMgr::instance().setDefaultLoggerName(bin_name_);
+
+    // Logger's default configuration depends on whether we are in the
+    // verbose mode or not. CfgMgr manages the logger configuration so
+    // the verbose mode is set for CfgMgr.
+    isc::dhcp::CfgMgr::instance().setVerbose(verbose_);
+
+    // 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.
+        Daemon::loggerInit(bin_name_.c_str(), verbose_);
+    }
+
+    try {
+        createPIDFile();
+    } catch (const dhcp::DaemonPIDExists& ex) {
+        LOG_FATAL(dctl_logger, DCTL_ALREADY_RUNNING)
+                  .arg(bin_name_).arg(ex.what());
+        isc_throw (LaunchError, "Launch Failed: " << ex.what());
+    } catch (const std::exception& ex) {
+        LOG_FATAL(dctl_logger, DCTL_PID_FILE_ERROR)
+                  .arg(app_name_).arg(ex.what());
+        isc_throw (LaunchError, "Launch failed: " << ex.what());
+    }
+
+    // Log the starting of the service.
+    LOG_INFO(dctl_logger, DCTL_STARTING)
+        .arg(app_name_).arg(getpid()).arg(VERSION);
+    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(app_name_).arg(ex.what());
+        isc_throw (ProcessInitError,
+                   "Application Process initialization failed: " << ex.what());
+    }
+
+    LOG_DEBUG(dctl_logger, isc::log::DBGLVL_START_SHUT, DCTL_STANDALONE)
+        .arg(app_name_);
+
+    // Step 3 is to load configuration from file.
+    int rcode;
+    ConstElementPtr comment = 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 configuration file: "
+                   << comment->stringValue());
+    }
+
+    // Everything is clear for launch, so start the application's
+    // event loop.
+    try {
+        // Now that we have a proces, we can set up signal handling.
+        initSignalHandling();
+        runProcess();
+    } catch (const std::exception& ex) {
+        LOG_FATAL(dctl_logger, DCTL_PROCESS_FAILED)
+                  .arg(app_name_).arg(ex.what());
+        isc_throw (ProcessRunError,
+                   "Application process event loop failed: " << ex.what());
+    }
+
+    // All done, so bail out.
+    LOG_INFO(dctl_logger, DCTL_SHUTDOWN)
+        .arg(app_name_).arg(getpid()).arg(VERSION);
+}
+
+void
+DControllerBase::checkConfigOnly() {
+    try {
+        // We need to initialize logging, in case any error
+        // messages are to be printed.
+        // This is just a test, so we don't care about lockfile.
+        setenv("KEA_LOCKFILE_DIR", "none", 0);
+        isc::dhcp::CfgMgr::instance().setDefaultLoggerName(bin_name_);
+        isc::dhcp::CfgMgr::instance().setVerbose(verbose_);
+        Daemon::loggerInit(bin_name_.c_str(), verbose_);
+
+        // Check the syntax first.
+        std::string config_file = getConfigFile();
+        if (config_file.empty()) {
+            // Basic sanity check: file name must not be empty.
+            isc_throw(InvalidUsage, "JSON configuration file not specified");
+        }
+        ConstElementPtr whole_config = parseFile(config_file);
+        if (!whole_config) {
+            // No fallback to fromJSONFile
+            isc_throw(InvalidUsage, "No configuration found");
+        }
+        if (verbose_) {
+            std::cerr << "Syntax check OK" << std::endl;
+        }
+
+        // Check the logic next.
+        ConstElementPtr module_config;
+        module_config = whole_config->get(getAppName());
+        if (!module_config) {
+            isc_throw(InvalidUsage, "Config file " << config_file <<
+                      " does not include '" << getAppName() << "' entry");
+        }
+
+        // Get an application process object.
+        initProcess();
+
+        ConstElementPtr answer = checkConfig(module_config);
+        int rcode = 0;
+        answer = parseAnswer(rcode, answer);
+        if (rcode != 0) {
+            isc_throw(InvalidUsage, "Error encountered: "
+                      << answer->stringValue());
+        }
+    } catch (const VersionMessage&) {
+        throw;
+    } catch (const InvalidUsage&) {
+        throw;
+    } catch (const std::exception& ex) {
+        isc_throw(InvalidUsage, "Syntax check failed with: " << ex.what());
+    }
+    return;
+}
+
+void
+DControllerBase::parseArgs(int argc, char* argv[])
+{
+    // Iterate over the given command line options. If its a stock option
+    // ("c" or "d") handle it here.  If its a valid custom option, then
+    // invoke customOption.
+    int ch;
+    opterr = 0;
+    optind = 1;
+    std::string opts("dvVWc:t:" + getCustomOpts());
+    while ((ch = getopt(argc, argv, opts.c_str())) != -1) {
+        switch (ch) {
+        case 'd':
+            // Enables verbose logging.
+            verbose_ = true;
+            break;
+
+        case 'v':
+            // gather Kea version and throw so main() can catch and return
+            // rather than calling exit() here which disrupts gtest.
+            isc_throw(VersionMessage, getVersion(false));
+            break;
+
+        case 'V':
+            // gather Kea version and throw so main() can catch and return
+            // rather than calling exit() here which disrupts gtest.
+            isc_throw(VersionMessage, getVersion(true));
+            break;
+
+        case 'W':
+            // gather Kea config report and throw so main() can catch and
+            // return rather than calling exit() here which disrupts gtest.
+            isc_throw(VersionMessage, isc::detail::getConfigReport());
+            break;
+
+        case 'c':
+        case 't':
+            // config file name
+            if (optarg == NULL) {
+                isc_throw(InvalidUsage, "configuration file name missing");
+            }
+
+            setConfigFile(optarg);
+
+            if (ch == 't') {
+                check_only_ = 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");
+    }
+}
+
+bool
+DControllerBase::customOption(int /* option */, char* /*optarg*/)
+{
+    // Default implementation returns false.
+    return (false);
+}
+
+void
+DControllerBase::initProcess() {
+    LOG_DEBUG(dctl_logger, isc::log::DBGLVL_START_SHUT, DCTL_INIT_PROCESS)
+        .arg(app_name_);
+
+    // Invoke virtual method to instantiate the application process.
+    try {
+        process_.reset(createProcess());
+    } catch (const std::exception& ex) {
+        isc_throw(DControllerBaseError, std::string("createProcess failed: ")
+                  + ex.what());
+    }
+
+    // This is pretty unlikely, but will test for it just to be safe..
+    if (!process_) {
+        isc_throw(DControllerBaseError, "createProcess returned NULL");
+    }
+
+    // Invoke application's init method (Note this call should throw
+    // DProcessBaseError if it fails).
+    process_->init();
+}
+
+ConstElementPtr
+DControllerBase::configFromFile() {
+    // Rollback any previous staging configuration. For D2, only a
+    // logger configuration is used here.
+    isc::dhcp::CfgMgr::instance().rollback();
+    // Will hold configuration.
+    ConstElementPtr module_config;
+    // Will receive configuration result.
+    ConstElementPtr answer;
+    try {
+        std::string config_file = getConfigFile();
+        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.");
+        }
+
+        // If parseFile returns an empty pointer, then pass the file onto the
+        // original JSON parser.
+        ConstElementPtr whole_config = parseFile(config_file);
+        if (!whole_config) {
+            // Read contents of the file and parse it as JSON
+            whole_config = Element::fromJSONFile(config_file, true);
+        }
+
+        // Let's configure logging before applying the configuration,
+        // so we can log things during configuration process.
+
+        // Temporary storage for logging configuration
+        isc::dhcp::SrvConfigPtr storage =
+            isc::dhcp::CfgMgr::instance().getStagingCfg();
+
+        // Get 'Logging' element from the config and use it to set up
+        // logging. If there's no such element, we'll just pass NULL.
+        Daemon::configureLogger(whole_config->get("Logging"), storage);
+
+        // 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.");
+        }
+
+        answer = updateConfig(module_config);
+        int rcode = 0;
+        parseAnswer(rcode, answer);
+        if (!rcode) {
+            // Configuration successful, so apply the logging configuration
+            // to log4cplus.
+            isc::dhcp::CfgMgr::instance().getStagingCfg()->applyLoggingCfg();
+            isc::dhcp::CfgMgr::instance().commit();
+        }
+
+    } catch (const std::exception& ex) {
+        // Rollback logging configuration.
+        isc::dhcp::CfgMgr::instance().rollback();
+        // build an error result
+        ConstElementPtr error = createAnswer(COMMAND_ERROR,
+                 std::string("Configuration parsing failed: ") + ex.what());
+        return (error);
+    }
+
+    return (answer);
+}
+
+
+void
+DControllerBase::runProcess() {
+    LOG_DEBUG(dctl_logger, isc::log::DBGLVL_START_SHUT, DCTL_RUN_PROCESS)
+        .arg(app_name_);
+    if (!process_) {
+        // This should not be possible.
+        isc_throw(DControllerBaseError, "Process not initialized");
+    }
+
+    // Invoke the application process's run method. This may throw
+    // DProcessBaseError
+    process_->run();
+}
+
+// Instance method for handling new config
+ConstElementPtr
+DControllerBase::updateConfig(ConstElementPtr new_config) {
+    return (process_->configure(new_config, false));
+}
+
+// Instance method for checking new config
+ConstElementPtr
+DControllerBase::checkConfig(ConstElementPtr new_config) {
+    return (process_->configure(new_config, true));
+}
+
+ConstElementPtr
+DControllerBase::configGetHandler(const std::string&,
+                                  ConstElementPtr /*args*/) {
+    ConstElementPtr config = process_->getCfgMgr()->getContext()->toElement();
+
+    return (createAnswer(COMMAND_SUCCESS, config));
+}
+
+ConstElementPtr
+DControllerBase::configWriteHandler(const std::string&,
+                                    ConstElementPtr args) {
+    std::string filename;
+
+    if (args) {
+        if (args->getType() != Element::map) {
+            return (createAnswer(COMMAND_ERROR, "Argument must be a map"));
+        }
+        ConstElementPtr filename_param = args->get("filename");
+        if (filename_param) {
+            if (filename_param->getType() != Element::string) {
+                return (createAnswer(COMMAND_ERROR,
+                                     "passed parameter 'filename' "
+                                     "is not a string"));
+            }
+            filename = filename_param->stringValue();
+        }
+    }
+
+    if (filename.empty()) {
+        // filename parameter was not specified, so let's use
+        // whatever we remember
+        filename = getConfigFile();
+        if (filename.empty()) {
+            return (createAnswer(COMMAND_ERROR,
+                                 "Unable to determine filename."
+                                 "Please specify filename explicitly."));
+        }
+    }
+
+
+    // Ok, it's time to write the file.
+    size_t size = 0;
+    ElementPtr cfg = process_->getCfgMgr()->getContext()->toElement();
+
+    // Logging storage is messed up in CA. During its configuration (see
+    // DControllerBase::configFromFile() it calls Daemon::configureLogger()
+    // that stores the logging info in isc::dhcp::CfgMgr::getStagingCfg().
+    // This is later moved to getCurrentCfg() when the configuration is
+    // commited. All control-agent specific configuration is stored in
+    // a structure accessible by process_->getCfgMgr()->getContext(). Note
+    // logging information is not stored there.
+    //
+    // As a result, we need to extract the CA configuration from one
+    // place and logging from another.
+    ConstElementPtr loginfo = isc::dhcp::CfgMgr::instance().getCurrentCfg()->toElement();
+    if (loginfo) {
+        // If there was a config stored in dhcp::CfgMgr, try to get Logging info from it.
+        loginfo = loginfo->get("Logging");
+    }
+    if (loginfo) {
+        // If there is some logging information, add it to our config.
+        cfg->set("Logging", loginfo);
+    }
+
+    try {
+        size = writeConfigFile(filename, cfg);
+    } catch (const isc::Exception& ex) {
+        return (createAnswer(COMMAND_ERROR,
+                             std::string("Error during write-config:")
+                             + ex.what()));
+    }
+    if (size == 0) {
+        return (createAnswer(COMMAND_ERROR,
+                             "Error writing configuration to " + filename));
+    }
+
+    // Ok, it's time to return the successful response.
+    ElementPtr params = Element::createMap();
+    params->set("size", Element::create(static_cast<long long>(size)));
+    params->set("filename", Element::create(filename));
+
+    return (createAnswer(CONTROL_RESULT_SUCCESS, "Configuration written to "
+                         + filename + " successful", params));
+}
+
+ConstElementPtr
+DControllerBase::configTestHandler(const std::string&, ConstElementPtr args) {
+    const int status_code = COMMAND_ERROR; // 1 indicates an error
+    ConstElementPtr module_config;
+    std::string app_name = getAppName();
+    std::string message;
+
+    // Command arguments are expected to be:
+    // { "Module": { ... }, "Logging": { ... } }
+    // The Logging component is technically optional. If it's not supplied
+    // logging will revert to default logging.
+    if (!args) {
+        message = "Missing mandatory 'arguments' parameter.";
+    } else {
+      module_config = args->get(app_name);
+        if (!module_config) {
+            message = "Missing mandatory '" + app_name + "' parameter.";
+        } else if (module_config->getType() != Element::map) {
+            message = "'" + app_name + "' parameter expected to be a map.";
+        }
+    }
+
+    if (!message.empty()) {
+        // Something is amiss with arguments, return a failure response.
+        ConstElementPtr result = isc::config::createAnswer(status_code,
+                                                           message);
+        return (result);
+    }
+
+    // We are starting the configuration process so we should remove any
+    // staging configuration that has been created during previous
+    // configuration attempts.
+    isc::dhcp::CfgMgr::instance().rollback();
+
+    // Now we check the server proper.
+    return (checkConfig(module_config));
+}
+
+ConstElementPtr
+DControllerBase::versionGetHandler(const std::string&, ConstElementPtr) {
+    ConstElementPtr answer;
+
+    // For version-get put the extended version in arguments
+    ElementPtr extended = Element::create(getVersion(true));
+    ElementPtr arguments = Element::createMap();
+    arguments->set("extended", extended);
+    answer = createAnswer(COMMAND_SUCCESS, getVersion(false), arguments);
+    return (answer);
+}
+
+ConstElementPtr
+DControllerBase::buildReportHandler(const std::string&, ConstElementPtr) {
+    return (createAnswer(COMMAND_SUCCESS, isc::detail::getConfigReport()));
+}
+
+ConstElementPtr
+DControllerBase::shutdownHandler(const std::string&, ConstElementPtr args) {
+    // Shutdown is universal.  If its not that, then try it as
+    // a custom command supported by the derivation.  If that
+    // doesn't pan out either, than send to it the application
+    // as it may be supported there.
+    return (shutdownProcess(args));
+}
+
+ConstElementPtr
+DControllerBase::shutdownProcess(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.
+    LOG_WARN(dctl_logger, DCTL_NOT_RUNNING).arg(app_name_);
+    return (createAnswer(COMMAND_SUCCESS, "Process has not been initialized"));
+}
+
+void
+DControllerBase::initSignalHandling() {
+    /// @todo block everything we don't handle
+
+    // Create our signal queue.
+    io_signal_queue_.reset(new IOSignalQueue(io_service_));
+
+    // Install the on-receipt handler
+    util::SignalSet::setOnReceiptHandler(boost::bind(&DControllerBase::
+                                                     osSignalHandler,
+                                                     this, _1));
+    // Register for the signals we wish to handle.
+    signal_set_.reset(new util::SignalSet(SIGHUP,SIGINT,SIGTERM));
+}
+
+bool
+DControllerBase::osSignalHandler(int signum) {
+    // Create a IOSignal to propagate the signal to IOService.
+    io_signal_queue_->pushSignal(signum, boost::bind(&DControllerBase::
+                                                     ioSignalHandler,
+                                                     this, _1));
+    return (true);
+}
+
+void
+DControllerBase::ioSignalHandler(IOSignalId sequence_id) {
+    // Pop the signal instance off the queue.  This should make us
+    // the only one holding it, so when we leave it should be freed.
+    // (note that popSignal will throw if signal is not found, which
+    // in turn will caught, logged, and swallowed by IOSignal callback
+    // invocation code.)
+    IOSignalPtr io_signal = io_signal_queue_->popSignal(sequence_id);
+
+    // Now call virtual signal processing method.
+    processSignal(io_signal->getSignum());
+}
+
+void
+DControllerBase::processSignal(int signum) {
+    switch (signum) {
+        case SIGHUP:
+        {
+            LOG_INFO(dctl_logger, DCTL_CFG_FILE_RELOAD_SIGNAL_RECVD)
+                     .arg(signum).arg(getConfigFile());
+            int rcode;
+            ConstElementPtr comment = parseAnswer(rcode, configFromFile());
+            if (rcode != 0) {
+                LOG_ERROR(dctl_logger, DCTL_CFG_FILE_RELOAD_ERROR)
+                          .arg(comment->stringValue());
+            }
+
+            break;
+        }
+
+        case SIGINT:
+        case SIGTERM:
+        {
+            LOG_DEBUG(dctl_logger, isc::log::DBGLVL_START_SHUT,
+                      DCTL_SHUTDOWN_SIGNAL_RECVD).arg(signum);
+            ElementPtr arg_set;
+            shutdownHandler(SHUT_DOWN_COMMAND, arg_set);
+            break;
+        }
+
+        default:
+            LOG_WARN(dctl_logger, DCTL_UNSUPPORTED_SIGNAL).arg(signum);
+            break;
+    }
+}
+
+void
+DControllerBase::usage(const std::string & text)
+{
+    if (text != "") {
+        std::cerr << "Usage error: " << text << std::endl;
+    }
+
+    std::cerr << "Usage: " << bin_name_ <<  std::endl
+              << "  -v: print version number and exit" << std::endl
+              << "  -V: print extended version information and exit"
+              << std::endl
+              << "  -W: display the configuration report and exit"
+              << std::endl
+              << "  -d: optional, verbose output " << std::endl
+              << "  -c <config file name> : mandatory,"
+              << " specify name of configuration file" << std::endl
+              << "  -t <config file name> : check the"
+              << " configuration file and exit" << std::endl;
+
+    // add any derivation specific usage
+    std::cerr << getUsageText() << std::endl;
+}
+
+DControllerBase::~DControllerBase() {
+}
+
+// Refer to config_report so it will be embedded in the binary
+const char* const* d2_config_report = isc::detail::config_report;
+
+std::string
+DControllerBase::getVersion(bool extended) {
+    std::stringstream tmp;
+
+    tmp << VERSION;
+    if (extended) {
+        tmp << std::endl << EXTENDED_VERSION << std::endl;
+        tmp << "linked with:" << std::endl;
+        tmp << isc::log::Logger::getVersion() << std::endl;
+        tmp << getVersionAddendum();
+    }
+
+    return (tmp.str());
+}
+
+}; // namespace isc::process
+
+}; // namespace isc
diff --git a/src/bin/client/d_controller.h b/src/bin/client/d_controller.h
new file mode 100644 (file)
index 0000000..63d00c8
--- /dev/null
@@ -0,0 +1,643 @@
+// Copyright (C) 2013-2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef D_CONTROLLER_H
+#define D_CONTROLLER_H
+
+#include <asiolink/io_service.h>
+#include <cc/data.h>
+#include <dhcpsrv/daemon.h>
+#include <exceptions/exceptions.h>
+#include <log/logger_support.h>
+#include <process/d_log.h>
+#include <process/d_process.h>
+#include <process/io_service_signal.h>
+
+#include <boost/shared_ptr.hpp>
+#include <boost/noncopyable.hpp>
+
+#include <string>
+#include <set>
+
+namespace isc {
+namespace process {
+
+/// @brief Exception thrown when the command line is invalid.
+/// Can be used to transmit negative messages too.
+class InvalidUsage : public isc::Exception {
+public:
+    InvalidUsage(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+/// @brief Exception used to convey version info upwards.
+/// Since command line argument parsing is done as part of
+/// DControllerBase::launch(), it uses this exception to propagate
+/// version information up to main(), when command line argument
+/// -v, -V or -W is given. Can be used to transmit positive messages too.
+class VersionMessage : public isc::Exception {
+public:
+    VersionMessage(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+/// @brief Exception thrown when the controller launch fails.
+class LaunchError: public isc::Exception {
+public:
+    LaunchError (const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+/// @brief Exception thrown when the application process fails.
+class ProcessInitError: public isc::Exception {
+public:
+    ProcessInitError (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 {
+public:
+    ProcessRunError (const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+/// @brief Exception thrown when the controller encounters an operational error.
+class DControllerBaseError : public isc::Exception {
+public:
+    DControllerBaseError (const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+
+/// @brief Defines a shared pointer to DControllerBase.
+class DControllerBase;
+typedef boost::shared_ptr<DControllerBase> DControllerBasePtr;
+
+/// @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 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.
+/// It provides the callback handlers for command and configuration events
+/// which could be triggered by an external source.  Such sources are intended
+/// to be registered with and monitored by the controller's IOService such that
+/// the appropriate handler can be invoked.
+///
+/// DControllerBase provides dynamic configuration file reloading upon receipt
+/// of SIGHUP, and graceful shutdown upon receipt of either SIGINT or SIGTERM.
+///
+/// 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 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.
+    DControllerBase(const char* app_name, const char* bin_name);
+
+    /// @brief Destructor
+    virtual ~DControllerBase();
+
+    /// @brief returns Kea version on stdout and exit.
+    /// redeclaration/redefinition. @ref isc::dhcp::Daemon::getVersion()
+    std::string getVersion(bool extended);
+
+    /// @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. load the configuration file
+    /// 4. initialize signal handling
+    /// 5. start and wait on the application process event loop
+    /// 6. exit to the caller
+    ///
+    /// It is intended to be called from main() and be given the command line
+    /// arguments.
+    ///
+    /// This function can be run in "test mode". It prevents initialization
+    /// of module logger. This is used in unit tests which initialize logger
+    /// 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
+    /// @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.
+    /// ProcessRunError - A fatal error occurred while in the application
+    /// process event loop.
+    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
+    /// 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.
+    ///
+    /// @param  new_config is the new configuration
+    ///
+    /// @return returns an Element that contains the results of configuration
+    /// update composed of an integer status value (0 means successful,
+    /// non-zero means failure), and a string explanation of the outcome.
+    virtual isc::data::ConstElementPtr updateConfig(isc::data::ConstElementPtr
+                                                    new_config);
+
+    /// @brief Instance method invoked by the configuration event handler and
+    /// which processes the actual configuration check.  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
+    /// with a final rollback.
+    ///
+    /// @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 checkConfig(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>}
+    ///
+    ///   # Logging element is optional
+    ///   ,"Logging": {<logger connfig}
+    ///  }
+    ///
+    ///  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's configuration values
+    /// @endcode
+    ///
+    /// To translate the JSON content into Elements, @c parseFile() is called
+    /// first.  This virtual method provides derivations a means to parse the
+    /// file content using an alternate parser.  If it returns an empty pointer
+    /// than the JSON parsing providing by Element::fromJSONFile() is called.
+    ///
+    /// Once parsed, the method looks for the Element "Logging" and, if present
+    /// uses it to configure loging.
+    ///
+    /// It then extracts the set of configuration elements for the
+    /// module-name that matches the controller's app_name_ and passes that
+    /// set into @c updateConfig() (or @c checkConfig()).
+    ///
+    /// 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 Fetches the name of the application under control.
+    ///
+    /// @return returns the controller service name string
+    std::string getAppName() const {
+        return (app_name_);
+    }
+
+    /// @brief Fetches the name of the application executable.
+    ///
+    /// @return returns the controller logger name string
+    std::string getBinName() const {
+        return (bin_name_);
+    }
+
+    /// @brief handler for version-get command
+    ///
+    /// This method handles the version-get command. It returns the basic and
+    /// extended version.
+    ///
+    /// @param command (ignored)
+    /// @param args (ignored)
+    /// @return answer with version details.
+    isc::data::ConstElementPtr
+    versionGetHandler(const std::string& command,
+                      isc::data::ConstElementPtr args);
+
+    /// @brief handler for 'build-report' command
+    ///
+    /// This method handles build-report command. It returns the output printed
+    /// by configure script which contains most compilation parameters.
+    ///
+    /// @param command (ignored)
+    /// @param args (ignored)
+    /// @return answer with build report
+    isc::data::ConstElementPtr
+    buildReportHandler(const std::string& command,
+                       isc::data::ConstElementPtr args);
+
+    /// @brief handler for config-get command
+    ///
+    /// This method handles the config-get command, which retrieves
+    /// the current configuration and returns it in response.
+    ///
+    /// @param command (ignored)
+    /// @param args (ignored)
+    /// @return current configuration wrapped in a response
+    isc::data::ConstElementPtr
+    configGetHandler(const std::string& command,
+                     isc::data::ConstElementPtr args);
+
+    /// @brief handler for config-write command
+    ///
+    /// This handle processes write-config comamnd, which writes the
+    /// current configuration to disk. This command takes one optional
+    /// parameter called filename. If specified, the current configuration
+    /// will be written to that file. If not specified, the file used during
+    /// Kea start-up will be used. To avoid any exploits, the path is
+    /// always relative and .. is not allowed in the filename. This is
+    /// a security measure against exploiting file writes remotely.
+    ///
+    /// @param command (ignored)
+    /// @param args may contain optional string argument filename
+    /// @return status of the configuration file write
+    isc::data::ConstElementPtr
+    configWriteHandler(const std::string& command,
+                       isc::data::ConstElementPtr args);
+
+    /// @brief handler for config-test command
+    ///
+    /// This method handles the config-test command, which checks
+    /// configuration specified in args parameter.
+    ///
+    /// @param command (ignored)
+    /// @param args configuration to be checked.
+    /// @return status of the command
+    isc::data::ConstElementPtr
+    configTestHandler(const std::string& command,
+                      isc::data::ConstElementPtr args);
+
+    /// @brief handler for 'shutdown' command
+    ///
+    /// This method handles shutdown command. It initiates the shutdown procedure
+    /// using CPL methods.
+    /// @param command (ignored)
+    /// @param args (ignored)
+    /// @return answer confirming that the shutdown procedure is started
+    isc::data::ConstElementPtr
+    shutdownHandler(const std::string& command,
+                    isc::data::ConstElementPtr args);
+
+protected:
+    /// @brief Virtual method that provides derivations the opportunity to
+    /// support additional command line options.  It is invoked during command
+    /// line argument parsing (see parseArgs method) if the option is not
+    /// recognized as a stock DControllerBase option.
+    ///
+    /// @param option is the option "character" from the command line, without
+    /// any prefixing hyphen(s)
+    /// @param optarg is the argument value (if one) associated with the option
+    ///
+    /// @return must return true if the option was valid, false if it is
+    /// invalid. (Note the default implementation always returns false.)
+    virtual bool customOption(int option, char *optarg);
+
+    /// @brief Abstract method that is responsible for instantiating the
+    /// application process object. It is invoked by the controller after
+    /// command line argument parsing as part of the process initialization
+    /// (see initProcess method).
+    ///
+    /// @return returns a pointer to the new process object (DProcessBase*)
+    /// or NULL if the create fails.
+    /// Note this value is subsequently wrapped in a smart pointer.
+    virtual DProcessBase* createProcess() = 0;
+
+    /// @brief Virtual method which can be used to contribute derivation
+    /// specific usage text.  It is invoked by the usage() method under
+    /// invalid usage conditions.
+    ///
+    /// @return returns the desired text.
+    virtual const std::string getUsageText() const {
+        return ("");
+    }
+
+    /// @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 "c", "d", ..., during command
+    /// line interpretation.
+    ///
+    /// @return returns a string containing the custom option letters.
+    virtual const std::string getCustomOpts() const {
+        return ("");
+    }
+
+    /// @brief Check the configuration
+    ///
+    /// Called by @c launch() when @c check_only_ mode is enabled
+    /// @throw VersionMessage when successful but a message should be displayed
+    /// @throw InvalidUsage when an error was detected
+    void checkConfigOnly();
+
+    /// @brief Application-level signal processing method.
+    ///
+    /// This method is the last step in processing a OS signal occurrence.  It
+    /// is invoked when an IOSignal's internal timer callback is executed by
+    /// IOService.  It currently supports the following signals as follows:
+    /// -# SIGHUP - instigates reloading the configuration file
+    /// -# SIGINT - instigates a graceful shutdown
+    /// -# SIGTERM - instigates a graceful shutdown
+    /// If it receives any other signal, it will issue a debug statement and
+    /// discard it.
+    /// Derivations wishing to support additional signals could override this
+    /// method with one that: processes the signal if it is one of additional
+    /// signals, otherwise invoke this method (DControllerBase::processSignal())
+    /// with the signal value.
+    /// @todo Provide a convenient way for derivations to register additional
+    /// signals.
+    virtual void processSignal(int signum);
+
+    /// @brief Supplies whether or not verbose logging is enabled.
+    ///
+    /// @return returns true if verbose logging is enabled.
+    bool isVerbose() const {
+        return (verbose_);
+    }
+
+    /// @brief Method for enabling or disabling verbose logging.
+    ///
+    /// @param value is the new value to assign the flag.
+    void setVerbose(bool value) {
+        verbose_ = value;
+    }
+
+    /// @brief Supplies whether or not check only mode is enabled.
+    ///
+    /// @return returns true if check only is enabled.
+    bool isCheckOnly() const {
+        return (check_only_);
+    }
+
+    /// @brief Method for enabling or disabling check only mode.
+    ///
+    /// @todo this method and @c setVerbose are currently not used.
+    ///
+    /// @param value is the new value to assign the flag.
+    void setCheckOnly(bool value) {
+        check_only_ = value;
+    }
+
+    /// @brief Getter for fetching the controller's IOService
+    ///
+    /// @return returns a pointer reference to the IOService.
+    asiolink::IOServicePtr& getIOService() {
+        return (io_service_);
+    }
+
+    /// @brief Getter for fetching the name of the controller's config spec
+    /// file.
+    ///
+    /// @return returns the file name string.
+    const std::string getSpecFileName() const {
+        return (spec_file_name_);
+    }
+
+    /// @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) {
+        spec_file_name_ = spec_file_name;
+    }
+
+    /// @brief Static getter which returns the singleton instance.
+    ///
+    /// @return returns a pointer reference to the private singleton instance
+    /// member.
+    static DControllerBasePtr& getController() {
+        return (controller_);
+    }
+
+    /// @brief Static setter which sets the singleton instance.
+    ///
+    /// @param controller is a pointer to the singleton instance.
+    ///
+    /// @throw throws DControllerBase error if an attempt is made to set the
+    /// instance a second time.
+    static void setController(const DControllerBasePtr& controller);
+
+    /// @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.
+    /// It handles stock options directly, and passes any custom options into
+    /// the customOption method.  Currently there are only some stock options
+    /// -c/t for specifying the configuration file, -d for verbose logging,
+    /// and -v/V/W for version reports.
+    ///
+    /// @param argc  is the number of command line arguments supplied
+    /// @param argv  is the array of string (char *) command line arguments
+    ///
+    /// @throw InvalidUsage when there are usage errors.
+    /// @throw VersionMessage if the -v, -V or -W arguments is given.
+    void parseArgs(int argc, char* argv[]);
+
+
+    ///@brief Parse a given file into Elements
+    ///
+    /// This method provides a means for deriving classes to use alternate
+    /// parsing mechanisms to parse configuration files into the corresponding
+    /// isc::data::Elements. The elements produced must be equivalent to those
+    /// which would be produced by the original JSON parsing.  Implementations
+    /// should throw when encountering errors.
+    ///
+    /// The default implementation returns an empty pointer, signifying to
+    /// callers that they should submit the file to the original parser.
+    ///
+    /// @param file_name pathname of the file to parse
+    ///
+    /// @return pointer to the elements created
+    ///
+    virtual isc::data::ConstElementPtr parseFile(const std::string& file_name);
+
+    ///@brief Parse text into Elements
+    ///
+    /// This method provides a means for deriving classes to use alternate
+    /// parsing mechanisms to parse configuration text into the corresponding
+    /// isc::data::Elements. The elements produced must be equivalent to those
+    /// which would be produced by the original JSON parsing.  Implementations
+    /// should throw when encountering errors.
+    ///
+    /// The default implementation returns an empty pointer, signifying to
+    /// callers that they should submit the text to the original parser.
+    ///
+    /// @param input text to parse
+    ///
+    /// @return pointer to the elements created
+    ///
+    virtual isc::data::ConstElementPtr parseText(const std::string& input) {
+        static_cast<void>(input); // just tu shut up the unused parameter warning
+        isc::data::ConstElementPtr elements;
+        return (elements);
+    }
+
+    /// @brief Instantiates the application process and then initializes it.
+    /// This is the second step taken during launch, following successful
+    /// command line parsing. It is used to invoke the derivation-specific
+    /// implementation of createProcess, following by an invoking of the
+    /// newly instantiated process's init method.
+    ///
+    /// @throw throws DControllerBaseError or indirectly DProcessBaseError
+    /// if there is a failure creating or initializing the application process.
+    void initProcess();
+
+    /// @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,
+    /// and session establishment (if not stand-alone).
+    /// The process event loop is expected to only return upon application
+    /// shutdown either in response to the shutdown command or due to an
+    /// unrecoverable error.
+    ///
+    // @throw throws DControllerBaseError or indirectly DProcessBaseError
+    void runProcess();
+
+    /// @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
+    /// to begin its shutdown process.
+    ///
+    /// Note, it is assumed that the process of shutting down is neither
+    /// instantaneous nor synchronous.  This method does not "block" waiting
+    /// 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.
+    ///
+    /// @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.
+    ///
+    /// @param args is a set of derivation-specific arguments (if any)
+    /// for the shutdown command.
+    isc::data::ConstElementPtr shutdownProcess(isc::data::ConstElementPtr args);
+
+    /// @brief Initializes signal handling
+    ///
+    /// This method configures the controller to catch and handle signals.
+    /// It instantiates an IOSignalQueue, registers @c osSignalHandler() as
+    /// the SignalSet "on-receipt" handler, and lastly instantiates a SignalSet
+    /// which listens for SIGHUP, SIGINT, and SIGTERM.
+    void initSignalHandling();
+
+    /// @brief Handler for processing OS-level signals
+    ///
+    /// This method is installed as the SignalSet "on-receipt" handler. Upon
+    /// invocation, it uses the controller's IOSignalQueue to schedule an
+    /// IOSignal with for the given signal value.
+    ///
+    /// @param signum OS signal value (e.g. SIGINT, SIGUSR1 ...) to received
+    ///
+    /// @return SignalSet "on-receipt" handlers are required to return a
+    /// boolean indicating if the OS signal has been processed (true) or if it
+    /// should be saved for deferred processing (false).  Currently this
+    /// method processes all received signals, so it always returns true.
+    bool osSignalHandler(int signum);
+
+    /// @brief Handler for processing IOSignals
+    ///
+    /// This method is supplied as the callback when IOSignals are scheduled.
+    /// It fetches the IOSignal for the given sequence_id and then invokes
+    /// the virtual method, @c processSignal() passing it the signal value
+    /// obtained from the IOSignal.  This allows derivations to supply a
+    /// custom signal processing method, while ensuring IOSignalQueue
+    /// integrity.
+    ///
+    /// @param sequence_id id of the IOSignal instance "received"
+    void ioSignalHandler(IOSignalId sequence_id);
+
+    /// @brief Fetches the current process
+    ///
+    /// @return a pointer to the current process instance.
+    DProcessBasePtr getProcess() {
+        return (process_);
+    }
+
+    /// @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);
+
+    /// @brief Fetches text containing additional version specifics
+    ///
+    /// This method is provided so derivations can append any additional
+    /// desired information such as library dependencies to the extended
+    /// version text returned when DControllerBase::getVersion(true) is
+    /// invoked.
+    /// @return a string containing additonal version info
+    virtual std::string getVersionAddendum() { return (""); }
+
+private:
+    /// @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 executable name. It is also used to
+    /// establish the logger name.
+    std::string bin_name_;
+
+    /// @brief Indicates if the verbose logging mode is enabled.
+    bool verbose_;
+
+    /// @brief Indicates if the check only mode for the configuration
+    /// is enabled (usually specified by the command line -t argument).
+    bool check_only_;
+
+    /// @brief The absolute file name of the JSON spec file.
+    std::string spec_file_name_;
+
+    /// @brief Pointer to the instance of the process.
+    ///
+    /// This is required for config and command handlers to gain access to
+    /// the process
+    DProcessBasePtr process_;
+
+    /// @brief Shared pointer to an IOService object, used for ASIO operations.
+    asiolink::IOServicePtr io_service_;
+
+    /// @brief Queue for propagating caught signals to the IOService.
+    IOSignalQueuePtr io_signal_queue_;
+
+    /// @brief Singleton instance value.
+    static DControllerBasePtr controller_;
+
+// DControllerTest is named a friend class to facilitate unit testing while
+// leaving the intended member scopes intact.
+friend class DControllerTest;
+};
+
+}; // namespace isc::process
+}; // namespace isc
+
+#endif