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