From ac77ccbc5df39cbb75d509bae7695636244b10b7 Mon Sep 17 00:00:00 2001 From: Tomek Date: Sat, 15 Jul 2017 16:03:06 +0200 Subject: [PATCH] [client] d_controller.cc|h copied from lib/process --- src/bin/client/d_controller.cc | 687 +++++++++++++++++++++++++++++++++ src/bin/client/d_controller.h | 643 ++++++++++++++++++++++++++++++ 2 files changed, 1330 insertions(+) create mode 100644 src/bin/client/d_controller.cc create mode 100644 src/bin/client/d_controller.h diff --git a/src/bin/client/d_controller.cc b/src/bin/client/d_controller.cc new file mode 100644 index 0000000000..6f31a83fe8 --- /dev/null +++ b/src/bin/client/d_controller.cc @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_MYSQL +#include +#endif +#ifdef HAVE_PGSQL +#include +#endif +#ifdef HAVE_CQL +#include +#endif +#include + +#include +#include + +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(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(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(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 : mandatory," + << " specify name of configuration file" << std::endl + << " -t : 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 index 0000000000..63d00c87be --- /dev/null +++ b/src/bin/client/d_controller.h @@ -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 +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +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 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 + /// { "": {} + /// + /// # Logging element is optional + /// ,"Logging": {(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 -- 2.47.2