d_controller.cc 17 KB
Newer Older
1
// Copyright (C) 2013-2017 Internet Systems Consortium, Inc. ("ISC")
2
//
3 4 5
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
6

7
#include <config.h>
8
#include <cc/command_interpreter.h>
9
#include <cfgrpt/config_report.h>
10
#include <cryptolink/cryptolink.h>
11 12
#include <dhcpsrv/cfgmgr.h>
#include <exceptions/exceptions.h>
13
#include <log/logger.h>
14
#include <log/logger_support.h>
15
#include <process/d_log.h>
16
#include <process/d_controller.h>
17

18 19 20 21 22 23
#ifdef HAVE_MYSQL
#include <dhcpsrv/mysql_lease_mgr.h>
#endif
#ifdef HAVE_PGSQL
#include <dhcpsrv/pgsql_lease_mgr.h>
#endif
Tomek Mrugalski's avatar
Tomek Mrugalski committed
24 25
#ifdef HAVE_CQL
#include <dhcpsrv/cql_lease_mgr.h>
26
#endif
27 28
#include <dhcpsrv/memfile_lease_mgr.h>

29
#include <sstream>
30
#include <unistd.h>
31 32

namespace isc {
33
namespace process {
34 35 36 37

DControllerBasePtr DControllerBase::controller_;

// Note that the constructor instantiates the controller's primary IOService.
38
DControllerBase::DControllerBase(const char* app_name, const char* bin_name)
39
    : app_name_(app_name), bin_name_(bin_name),
40
      verbose_(false), spec_file_name_(""),
41
      io_service_(new isc::asiolink::IOService()),
42
      io_signal_queue_() {
43 44
}

45
void
46
DControllerBase::setController(const DControllerBasePtr& controller) {
47 48 49 50 51 52 53
    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.");
    }

54
    controller_ = controller;
55 56
}

57 58 59 60 61 62
isc::data::ConstElementPtr
DControllerBase::parseFile(const std::string&) {
    isc::data::ConstElementPtr elements;
    return (elements);
}

63
void
64
DControllerBase::launch(int argc, char* argv[], const bool test_mode) {
65

66 67 68 69 70
    // Step 1 is to parse the command line arguments.
    try {
        parseArgs(argc, argv);
    } catch (const InvalidUsage& ex) {
        usage(ex.what());
71
        throw; // rethrow it
72 73
    }

74
    setProcName(bin_name_);
75

76 77 78 79 80 81 82 83 84 85
    // It is important that we set a default logger name because this name
    // will be used when the user doesn't provide the logging configuration
    // in the Kea configuration file.
    isc::dhcp::CfgMgr::instance().setDefaultLoggerName(bin_name_);

    // Logger's default configuration depends on whether we are in the
    // verbose mode or not. CfgMgr manages the logger configuration so
    // the verbose mode is set for CfgMgr.
    isc::dhcp::CfgMgr::instance().setVerbose(verbose_);

86 87 88 89
    // 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.
90
        Daemon::loggerInit(bin_name_.c_str(), verbose_);
91
    }
92

93 94 95
    try {
        createPIDFile();
    } catch (const dhcp::DaemonPIDExists& ex) {
96
        LOG_FATAL(dctl_logger, DCTL_ALREADY_RUNNING)
97 98 99
                  .arg(bin_name_).arg(ex.what());
        isc_throw (LaunchError, "Launch Failed: " << ex.what());
    } catch (const std::exception& ex) {
100
        LOG_FATAL(dctl_logger, DCTL_PID_FILE_ERROR)
101 102 103 104
                  .arg(app_name_).arg(ex.what());
        isc_throw (LaunchError, "Launch failed: " << ex.what());
    }

105 106 107
    // Log the starting of the service.
    LOG_INFO(dctl_logger, DCTL_STARTING)
        .arg(app_name_).arg(getpid()).arg(VERSION);
108
    try {
109
        // Step 2 is to create and initialize the application process object.
110
        initProcess();
111
    } catch (const std::exception& ex) {
112 113 114
        LOG_FATAL(dctl_logger, DCTL_INIT_PROCESS_FAIL)
                  .arg(app_name_).arg(ex.what());
        isc_throw (ProcessInitError,
115
                   "Application Process initialization failed: " << ex.what());
116
    }
117

118 119 120 121 122 123 124 125 126
    LOG_DEBUG(dctl_logger, DBGLVL_START_SHUT, DCTL_STANDALONE).arg(app_name_);

    // Step 3 is to load configuration from file.
    int rcode;
    isc::data::ConstElementPtr comment
        = isc::config::parseAnswer(rcode, configFromFile());
    if (rcode != 0) {
        LOG_FATAL(dctl_logger, DCTL_CONFIG_FILE_LOAD_FAIL)
                  .arg(app_name_).arg(comment->stringValue());
Vincent Legout's avatar
Vincent Legout committed
127
        isc_throw (ProcessInitError, "Could Not load configuration file: "
128
                   << comment->stringValue());
129
    }
130

131 132 133
    // Everything is clear for launch, so start the application's
    // event loop.
    try {
134 135
        // Now that we have a proces, we can set up signal handling.
        initSignalHandling();
136 137
        runProcess();
    } catch (const std::exception& ex) {
138 139 140
        LOG_FATAL(dctl_logger, DCTL_PROCESS_FAILED)
                  .arg(app_name_).arg(ex.what());
        isc_throw (ProcessRunError,
141
                   "Application process event loop failed: " << ex.what());
142 143
    }

144 145 146
    // All done, so bail out.
    LOG_INFO(dctl_logger, DCTL_SHUTDOWN)
        .arg(app_name_).arg(getpid()).arg(VERSION);
147 148 149 150 151
}

void
DControllerBase::parseArgs(int argc, char* argv[])
{
152
    // Iterate over the given command line options. If its a stock option
Francis Dupont's avatar
Francis Dupont committed
153
    // ("s" or "v") handle it here.  If its a valid custom option, then
154 155
    // invoke customOption.
    int ch;
156
    opterr = 0;
157
    optind = 1;
Francis Dupont's avatar
Francis Dupont committed
158
    std::string opts("dvVWc:" + getCustomOpts());
159 160
    while ((ch = getopt(argc, argv, opts.c_str())) != -1) {
        switch (ch) {
161
        case 'd':
162 163 164 165
            // Enables verbose logging.
            verbose_ = true;
            break;

166
        case 'v':
167 168 169 170
            // gather Kea version and throw so main() can catch and return
            // rather than calling exit() here which disrupts gtest.
            isc_throw(VersionMessage, getVersion(false));
            break;
171 172

        case 'V':
173 174 175 176
            // gather Kea version and throw so main() can catch and return
            // rather than calling exit() here which disrupts gtest.
            isc_throw(VersionMessage, getVersion(true));
            break;
177

178 179 180 181 182 183
        case 'W':
            // gather Kea config report and throw so main() can catch and
            // return rather than calling exit() here which disrupts gtest.
            isc_throw(VersionMessage, isc::detail::getConfigReport());
            break;

184 185 186 187 188 189
        case 'c':
            // config file name
            if (optarg == NULL) {
                isc_throw(InvalidUsage, "configuration file name missing");
            }

190
            setConfigFile(optarg);
191 192 193 194
            break;

        case '?': {
            // We hit an invalid option.
195
            isc_throw(InvalidUsage, "unsupported option: ["
196 197
                      << static_cast<char>(optopt) << "] "
                      << (!optarg ? "" : optarg));
198 199 200

            break;
            }
201

202 203 204
        default:
            // We hit a valid custom option
            if (!customOption(ch, optarg)) {
205
                // This would be a programmatic error.
206
                isc_throw(InvalidUsage, " Option listed but implemented?: ["
207 208
                          << static_cast<char>(ch) << "] "
                          << (!optarg ? "" : optarg));
209 210 211 212 213 214 215
            }
            break;
        }
    }

    // There was too much information on the command line.
    if (argc > optind) {
216
        isc_throw(InvalidUsage, "extraneous command line information");
217 218 219
    }
}

220
bool
221 222 223 224 225 226 227 228
DControllerBase::customOption(int /* option */, char* /*optarg*/)
{
    // Default implementation returns false.
    return (false);
}

void
DControllerBase::initProcess() {
229
    LOG_DEBUG(dctl_logger, DBGLVL_START_SHUT, DCTL_INIT_PROCESS).arg(app_name_);
230 231

    // Invoke virtual method to instantiate the application process.
232
    try {
233
        process_.reset(createProcess());
234
    } catch (const std::exception& ex) {
235
        isc_throw(DControllerBaseError, std::string("createProcess failed: ")
236
                  + ex.what());
237 238 239 240
    }

    // This is pretty unlikely, but will test for it just to be safe..
    if (!process_) {
241
        isc_throw(DControllerBaseError, "createProcess returned NULL");
242
    }
243

244 245
    // Invoke application's init method (Note this call should throw
    // DProcessBaseError if it fails).
246 247 248
    process_->init();
}

249 250
isc::data::ConstElementPtr
DControllerBase::configFromFile() {
251 252 253 254
    // Rollback any previous staging configuration. For D2, only a
    // logger configuration is used here.
    isc::dhcp::CfgMgr::instance().rollback();
    // Will hold configuration.
255
    isc::data::ConstElementPtr module_config;
256 257
    // Will receive configuration result.
    isc::data::ConstElementPtr answer;
258
    try {
259
        std::string config_file = getConfigFile();
260 261 262 263 264 265
        if (config_file.empty()) {
            // Basic sanity check: file name must not be empty.
            isc_throw(BadValue, "JSON configuration file not specified. Please "
                                "use -c command line option.");
        }

266 267 268 269 270 271 272
        // If parseFile returns an empty pointer, then pass the file onto the
        // original JSON parser.
        isc::data::ConstElementPtr whole_config = parseFile(config_file);
        if (!whole_config) {
            // Read contents of the file and parse it as JSON
            whole_config = isc::data::Element::fromJSONFile(config_file, true);
        }
273

274 275 276 277
        // Let's configure logging before applying the configuration,
        // so we can log things during configuration process.

        // Temporary storage for logging configuration
278
        isc::dhcp::SrvConfigPtr storage =
279
            isc::dhcp::CfgMgr::instance().getStagingCfg();
280

Tomek Mrugalski's avatar
Tomek Mrugalski committed
281 282
        // Get 'Logging' element from the config and use it to set up
        // logging. If there's no such element, we'll just pass NULL.
283
        Daemon::configureLogger(whole_config->get("Logging"), storage);
284

285 286 287 288 289 290 291
        // Extract derivation-specific portion of the configuration.
        module_config = whole_config->get(getAppName());
        if (!module_config) {
            isc_throw(BadValue, "Config file " << config_file <<
                                " does not include '" <<
                                 getAppName() << "' entry.");
        }
292 293 294 295 296 297 298 299 300 301 302

        answer = updateConfig(module_config);
        int rcode = 0;
        isc::config::parseAnswer(rcode, answer);
        if (!rcode) {
            // Configuration successful, so apply the logging configuration
            // to log4cplus.
            isc::dhcp::CfgMgr::instance().getStagingCfg()->applyLoggingCfg();
            isc::dhcp::CfgMgr::instance().commit();
        }

303
    } catch (const std::exception& ex) {
304 305
        // Rollback logging configuration.
        isc::dhcp::CfgMgr::instance().rollback();
306 307 308 309 310
        // build an error result
        isc::data::ConstElementPtr error =
            isc::config::createAnswer(1,
                std::string("Configuration parsing failed: ") + ex.what());
        return (error);
311 312
    }

313
    return (answer);
314 315
}

316

317 318
void
DControllerBase::runProcess() {
319
    LOG_DEBUG(dctl_logger, DBGLVL_START_SHUT, DCTL_RUN_PROCESS).arg(app_name_);
320 321 322 323 324
    if (!process_) {
        // This should not be possible.
        isc_throw(DControllerBaseError, "Process not initialized");
    }

325 326
    // Invoke the application process's run method. This may throw
    // DProcessBaseError
327 328 329
    process_->run();
}

330
// Instance method for handling new config
331 332
isc::data::ConstElementPtr
DControllerBase::updateConfig(isc::data::ConstElementPtr new_config) {
333
    return (process_->configure(new_config, false));
334 335 336
}


337
// Instance method for executing commands
338
isc::data::ConstElementPtr
339
DControllerBase::executeCommand(const std::string& command,
340
                            isc::data::ConstElementPtr args) {
341
    // Shutdown is universal.  If its not that, then try it as
342
    // a custom command supported by the derivation.  If that
343 344 345 346
    // 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) {
347
        answer = shutdownProcess(args);
348
    } else {
349
        // It wasn't shutdown, so it may be a custom controller command.
350 351
        int rcode = 0;
        answer = customControllerCommand(command, args);
352
        isc::config::parseAnswer(rcode, answer);
353 354
        if (rcode == COMMAND_INVALID)
        {
355
            // It wasn't a controller command, so it may be an application command.
356
            answer = process_->command(command, args);
357 358 359 360 361 362 363
        }
    }

    return (answer);
}

isc::data::ConstElementPtr
364
DControllerBase::customControllerCommand(const std::string& command,
365 366 367 368
                                     isc::data::ConstElementPtr /* args */) {

    // Default implementation always returns invalid command.
    return (isc::config::createAnswer(COMMAND_INVALID,
369
                                      "Unrecognized command: " + command));
370 371
}

372
isc::data::ConstElementPtr
373
DControllerBase::shutdownProcess(isc::data::ConstElementPtr args) {
374
    if (process_) {
375
        return (process_->shutdown(args));
376
    }
377

378 379 380
    // 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_);
Vincent Legout's avatar
Vincent Legout committed
381
    return (isc::config::createAnswer(0, "Process has not been initialized."));
382 383
}

384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425
void
DControllerBase::initSignalHandling() {
    /// @todo block everything we don't handle

    // Create our signal queue.
    io_signal_queue_.reset(new IOSignalQueue(io_service_));

    // Install the on-receipt handler
    util::SignalSet::setOnReceiptHandler(boost::bind(&DControllerBase::
                                                     osSignalHandler,
                                                     this, _1));
    // Register for the signals we wish to handle.
    signal_set_.reset(new util::SignalSet(SIGHUP,SIGINT,SIGTERM));
}

bool
DControllerBase::osSignalHandler(int signum) {
    // Create a IOSignal to propagate the signal to IOService.
    io_signal_queue_->pushSignal(signum, boost::bind(&DControllerBase::
                                                     ioSignalHandler,
                                                     this, _1));
    return (true);
}

void
DControllerBase::ioSignalHandler(IOSignalId sequence_id) {
    // Pop the signal instance off the queue.  This should make us
    // the only one holding it, so when we leave it should be freed.
    // (note that popSignal will throw if signal is not found, which
    // in turn will caught, logged, and swallowed by IOSignal callback
    // invocation code.)
    IOSignalPtr io_signal = io_signal_queue_->popSignal(sequence_id);

    // Now call virtual signal processing method.
    processSignal(io_signal->getSignum());
}

void
DControllerBase::processSignal(int signum) {
    switch (signum) {
        case SIGHUP:
        {
426
            LOG_INFO(dctl_logger, DCTL_CFG_FILE_RELOAD_SIGNAL_RECVD)
427 428 429 430 431 432
                     .arg(signum).arg(getConfigFile());
            int rcode;
            isc::data::ConstElementPtr comment = isc::config::
                                                 parseAnswer(rcode,
                                                             configFromFile());
            if (rcode != 0) {
433
                LOG_ERROR(dctl_logger, DCTL_CFG_FILE_RELOAD_ERROR)
434 435 436 437 438 439 440 441 442
                          .arg(comment->stringValue());
            }

            break;
        }

        case SIGINT:
        case SIGTERM:
        {
443
            LOG_DEBUG(dctl_logger, DBGLVL_START_SHUT,
444
                      DCTL_SHUTDOWN_SIGNAL_RECVD).arg(signum);
445 446 447 448 449 450
            isc::data::ElementPtr arg_set;
            executeCommand(SHUT_DOWN_COMMAND, arg_set);
            break;
        }

        default:
451
            LOG_WARN(dctl_logger, DCTL_UNSUPPORTED_SIGNAL).arg(signum);
452 453 454 455
            break;
    }
}

456 457 458 459
void
DControllerBase::usage(const std::string & text)
{
    if (text != "") {
460
        std::cerr << "Usage error: " << text << std::endl;
461 462
    }

463
    std::cerr << "Usage: " << bin_name_ <<  std::endl
464 465
              << "  -v: print version number and exit" << std::endl
              << "  -V: print extended version information and exit"
466 467
              << std::endl
              << "  -W: display the configuration report and exit"
468 469 470 471
              << std::endl
              << "  -d: optional, verbose output " << std::endl
              << "  -c <config file name> : mandatory,"
              <<   " specifies name of configuration file " << std::endl;
472

473
    // add any derivation specific usage
474
    std::cerr << getUsageText() << std::endl;
475 476 477 478 479
}

DControllerBase::~DControllerBase() {
}

480 481 482
// Refer to config_report so it will be embedded in the binary
const char* const* d2_config_report = isc::detail::config_report;

Tomek Mrugalski's avatar
Tomek Mrugalski committed
483
std::string
484
DControllerBase::getVersion(bool extended) {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
485 486 487 488
    std::stringstream tmp;

    tmp << VERSION;
    if (extended) {
489
        tmp << std::endl << EXTENDED_VERSION << std::endl;
490 491 492
        tmp << "linked with:" << std::endl;
        tmp << isc::log::Logger::getVersion() << std::endl;
        tmp << isc::cryptolink::CryptoLink::getVersion() << std::endl;
493
        tmp << "database:" << std::endl;
494
#ifdef HAVE_MYSQL
495
        tmp << isc::dhcp::MySqlLeaseMgr::getDBVersion() << std::endl;
496
#endif
497
#ifdef HAVE_PGSQL
498
        tmp << isc::dhcp::PgSqlLeaseMgr::getDBVersion() << std::endl;
499
#endif
Tomek Mrugalski's avatar
Tomek Mrugalski committed
500 501
#ifdef HAVE_CQL
        tmp << isc::dhcp::CqlLeaseMgr::getDBVersion() << std::endl;
502
#endif
503
        tmp << isc::dhcp::Memfile_LeaseMgr::getDBVersion();
504

505
        // @todo: more details about database runtime
Tomek Mrugalski's avatar
Tomek Mrugalski committed
506 507 508 509
    }

    return (tmp.str());
}
510

511
}; // namespace isc::process
512 513

}; // namespace isc