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