123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423 |
- // 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 <d2/d2_log.h>
- #include <d2/d_controller.h>
- #include <exceptions/exceptions.h>
- #include <log/logger_support.h>
- #include <sstream>
- namespace isc {
- namespace d2 {
- DControllerBasePtr DControllerBase::controller_;
- // Note that the constructor instantiates the controller's primary IOService.
- DControllerBase::DControllerBase(const char* app_name, const char* bin_name)
- : app_name_(app_name), bin_name_(bin_name), stand_alone_(false),
- verbose_(false), spec_file_name_(""),
- io_service_(new isc::asiolink::IOService()){
- }
- void
- DControllerBase::setController(const DControllerBasePtr& controller) {
- if (controller_) {
- // This shouldn't happen, but let's make sure it can't be done.
- // It represents a programmatic error.
- isc_throw (DControllerBaseError,
- "Multiple controller instances attempted.");
- }
- controller_ = controller;
- }
- void
- DControllerBase::launch(int argc, char* argv[], const bool test_mode) {
- // Step 1 is to parse the command line arguments.
- try {
- parseArgs(argc, argv);
- } catch (const InvalidUsage& ex) {
- usage(ex.what());
- 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(bin_name_,
- ((verbose_ && stand_alone_)
- ? isc::log::DEBUG : isc::log::INFO),
- isc::log::MAX_DEBUG_LEVEL, NULL, !stand_alone_);
- }
- LOG_DEBUG(dctl_logger, DBGLVL_START_SHUT, DCTL_STARTING)
- .arg(app_name_).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(app_name_).arg(ex.what());
- isc_throw (ProcessInitError,
- "Application Process initialization failed: " << ex.what());
- }
- // Next we connect if we are running integrated.
- if (stand_alone_) {
- LOG_DEBUG(dctl_logger, DBGLVL_START_SHUT, DCTL_STANDALONE)
- .arg(app_name_);
- } else {
- try {
- establishSession();
- } catch (const std::exception& ex) {
- LOG_FATAL(dctl_logger, DCTL_SESSION_FAIL).arg(ex.what());
- isc_throw (SessionStartError,
- "Session start up failed: " << ex.what());
- }
- }
- // 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(app_name_).arg(ex.what());
- isc_throw (ProcessRunError,
- "Application process event loop failed: " << ex.what());
- }
- // If running integrated, disconnect.
- if (!stand_alone_) {
- try {
- disconnectSession();
- } catch (const std::exception& ex) {
- LOG_ERROR(dctl_logger, DCTL_DISCONNECT_FAIL)
- .arg(app_name_).arg(ex.what());
- isc_throw (SessionEndError, "Session end failed: " << ex.what());
- }
- }
- // All done, so bail out.
- LOG_INFO(dctl_logger, DCTL_STOPPING).arg(app_name_);
- }
- void
- DControllerBase::parseArgs(int argc, char* argv[])
- {
- // 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.
- isc_throw(InvalidUsage, "unsupported option: ["
- << static_cast<char>(optopt) << "] "
- << (!optarg ? "" : optarg));
- break;
- }
- default:
- // We hit a valid custom option
- if (!customOption(ch, optarg)) {
- // This would be a programmatic error.
- isc_throw(InvalidUsage, " Option listed but implemented?: ["
- << static_cast<char>(ch) << "] "
- << (!optarg ? "" : optarg));
- }
- break;
- }
- }
- // There was too much information on the command line.
- if (argc > optind) {
- isc_throw(InvalidUsage, "extraneous command line information");
- }
- }
- bool
- DControllerBase::customOption(int /* option */, char* /*optarg*/)
- {
- // Default implementation returns false.
- return (false);
- }
- void
- DControllerBase::initProcess() {
- LOG_DEBUG(dctl_logger, DBGLVL_START_SHUT, DCTL_INIT_PROCESS).arg(app_name_);
- // Invoke virtual method to instantiate the application process.
- try {
- process_.reset(createProcess());
- } catch (const std::exception& ex) {
- isc_throw(DControllerBaseError, std::string("createProcess failed: ")
- + ex.what());
- }
- // This is pretty unlikely, but will test for it just to be safe..
- if (!process_) {
- isc_throw(DControllerBaseError, "createProcess returned NULL");
- }
- // Invoke application's init method (Note this call should throw
- // DProcessBaseError if it fails).
- process_->init();
- }
- void
- DControllerBase::establishSession() {
- LOG_DEBUG(dctl_logger, DBGLVL_START_SHUT, DCTL_CCSESSION_STARTING)
- .arg(app_name_).arg(spec_file_name_);
- // Create the BIND10 command control session with the our IOService.
- cc_session_ = SessionPtr(new isc::cc::Session(
- io_service_->get_io_service()));
- // Create the BIND10 config session with the stub configuration handler.
- // This handler is internally invoked by the constructor and on success
- // the constructor updates the current session with the configuration that
- // had been committed in the previous session. If we do not install
- // the dummy handler, the previous configuration would be lost.
- config_session_ = ModuleCCSessionPtr(new isc::config::ModuleCCSession(
- spec_file_name_, *cc_session_,
- dummyConfigHandler, commandHandler,
- false));
- // Enable configuration even processing.
- config_session_->start();
- // We initially create ModuleCCSession() with a dummy configHandler, as
- // the session module is too eager to send partial configuration.
- // Replace the dummy config handler with the real handler.
- config_session_->setConfigHandler(configHandler);
- // Call the real configHandler with the full configuration retrieved
- // from the config session.
- isc::data::ConstElementPtr answer = configHandler(
- config_session_->getFullConfig());
- // Parse the answer returned from the configHandler. Log the error but
- // keep running. This provides an opportunity for the user to correct
- // the configuration dynamically.
- int ret = 0;
- isc::data::ConstElementPtr comment = isc::config::parseAnswer(ret, answer);
- if (ret) {
- LOG_ERROR(dctl_logger, DCTL_CONFIG_LOAD_FAIL)
- .arg(app_name_).arg(comment->str());
- }
- // Lastly, call onConnect. This allows deriving class to execute custom
- // logic predicated by session connect.
- onSessionConnect();
- }
- void
- DControllerBase::runProcess() {
- LOG_DEBUG(dctl_logger, DBGLVL_START_SHUT, DCTL_RUN_PROCESS).arg(app_name_);
- if (!process_) {
- // This should not be possible.
- isc_throw(DControllerBaseError, "Process not initialized");
- }
- // Invoke the application process's run method. This may throw
- // DProcessBaseError
- process_->run();
- }
- void DControllerBase::disconnectSession() {
- LOG_DEBUG(dctl_logger, DBGLVL_START_SHUT, DCTL_CCSESSION_ENDING)
- .arg(app_name_);
- // Call virtual onDisconnect. Allows deriving class to execute custom
- // logic prior to session loss.
- onSessionDisconnect();
- // Destroy the BIND10 config session.
- if (config_session_) {
- config_session_.reset();
- }
- // Destroy the BIND10 command and control session.
- if (cc_session_) {
- cc_session_->disconnect();
- cc_session_.reset();
- }
- }
- isc::data::ConstElementPtr
- DControllerBase::dummyConfigHandler(isc::data::ConstElementPtr) {
- LOG_DEBUG(dctl_logger, DBGLVL_START_SHUT, DCTL_CONFIG_STUB)
- .arg(controller_->getAppName());
- return (isc::config::createAnswer(0, "Configuration accepted."));
- }
- isc::data::ConstElementPtr
- DControllerBase::configHandler(isc::data::ConstElementPtr new_config) {
- LOG_DEBUG(dctl_logger, DBGLVL_COMMAND, DCTL_CONFIG_UPDATE)
- .arg(controller_->getAppName()).arg(new_config->str());
- // Invoke the instance method on the controller singleton.
- return (controller_->updateConfig(new_config));
- }
- // Static callback which invokes non-static handler on singleton
- isc::data::ConstElementPtr
- DControllerBase::commandHandler(const std::string& command,
- isc::data::ConstElementPtr args) {
- LOG_DEBUG(dctl_logger, DBGLVL_COMMAND, DCTL_COMMAND_RECEIVED)
- .arg(controller_->getAppName()).arg(command)
- .arg(args ? args->str() : "(no args)");
- // Invoke the instance method on the controller singleton.
- return (controller_->executeCommand(command, args));
- }
- isc::data::ConstElementPtr
- DControllerBase::updateConfig(isc::data::ConstElementPtr new_config) {
- isc::data::ConstElementPtr full_config;
- if (stand_alone_) {
- // @todo Until there is a configuration manager to provide retrieval
- // we'll just assume the incoming config is the full configuration set.
- // It may also make more sense to isolate the controller from the
- // configuration manager entirely. We could do something like
- // process_->getFullConfig() here for stand-alone mode?
- full_config = new_config;
- } else {
- if (!config_session_) {
- // That should never happen as we install config_handler
- // after we instantiate the server.
- isc::data::ConstElementPtr answer =
- isc::config::createAnswer(1, "Configuration rejected,"
- " Session has not started.");
- return (answer);
- }
- // Let's get the existing configuration.
- full_config = config_session_->getFullConfig();
- }
- // The configuration passed to this handler function is partial.
- // In other words, it just includes the values being modified.
- // In the same time, there may be dependencies between various
- // configuration parsers. For example: the option value can
- // be set if the definition of this option is set. If someone removes
- // an existing option definition then the partial configuration that
- // removes that definition is triggered while a relevant option value
- // may remain configured. This eventually results in the
- // configuration being in the inconsistent state.
- // In order to work around this problem we need to merge the new
- // configuration with the existing (full) configuration.
- // Let's create a new object that will hold the merged configuration.
- boost::shared_ptr<isc::data::MapElement>
- merged_config(new isc::data::MapElement());
- // Merge an existing and new configuration.
- merged_config->setValue(full_config->mapValue());
- isc::data::merge(merged_config, new_config);
- // Send the merged configuration to the application.
- return (process_->configure(merged_config));
- }
- isc::data::ConstElementPtr
- DControllerBase::executeCommand(const std::string& command,
- isc::data::ConstElementPtr args) {
- // Shutdown is universal. 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(args);
- } 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(isc::data::ConstElementPtr args) {
- if (process_) {
- return (process_->shutdown(args));
- }
- // Not really a failure, but this condition is worth noting. In reality
- // it should be pretty hard to cause this.
- LOG_WARN(dctl_logger, DCTL_NOT_RUNNING).arg(app_name_);
- return (isc::config::createAnswer(0, "Process has not been initialzed."));
- }
- void
- DControllerBase::usage(const std::string & text)
- {
- if (text != "") {
- std::cerr << "Usage error: " << text << std::endl;
- }
- std::cerr << "Usage: " << bin_name_ << std::endl;
- std::cerr << " -v: verbose output" << std::endl;
- std::cerr << " -s: stand-alone mode (don't connect to BIND10)"
- << std::endl;
- std::cerr << getUsageText() << std::endl;
- }
- DControllerBase::~DControllerBase() {
- }
- }; // namespace isc::d2
- }; // namespace isc
|