bundy_d2_controller.cc 11.5 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
// 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 <d2/bundy_d2_controller.h>
#include <d2/d2_process.h>
#include <d2/spec_config.h>

#include <sstream>

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
29
const char* D2Controller::d2_bin_name_ = "kea-dhcp-ddns";
30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179

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<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");
    }
}

void
D2Controller::establishSession() {
    LOG_DEBUG(dctl_logger, DBGLVL_START_SHUT, DCTL_CCSESSION_STARTING)
              .arg(getAppName()).arg(getSpecFileName());

180
    // Create the Bundy command control session with the our IOService.
181 182 183
    cc_session_ = SessionPtr(new isc::cc::Session(
                             getIOService()->get_io_service()));

184
    // Create the Bundy config session with the stub configuration handler.
185 186 187 188 189 190 191
    // 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, 
192
                                         commandHandler,
193 194 195 196 197 198 199
                                         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.
200
    config_session_->setConfigHandler(configHandler);
201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221

    // 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());

222
    // Destroy the Bundy config session.
223 224 225 226
    if (config_session_) {
        config_session_.reset();
    }

227
    // Destroy the Bundy command and control session.
228 229 230 231 232 233 234 235 236 237 238 239 240
    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."));
}

241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263
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));
}

264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317
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<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 (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