d_controller.cc 19.5 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), check_only_(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 72
        // rethrow it with an empty message
        isc_throw(InvalidUsage, "");
73 74
    }

75
    setProcName(bin_name_);
76

77 78
    if (isCheckOnly()) {
        checkConfigOnly();
79
        return;
80
    }
81

82 83 84 85 86 87 88 89 90 91
    // 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_);

92 93 94 95
    // 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.
96
        Daemon::loggerInit(bin_name_.c_str(), verbose_);
97
    }
98

99 100 101
    try {
        createPIDFile();
    } catch (const dhcp::DaemonPIDExists& ex) {
102
        LOG_FATAL(dctl_logger, DCTL_ALREADY_RUNNING)
103 104 105
                  .arg(bin_name_).arg(ex.what());
        isc_throw (LaunchError, "Launch Failed: " << ex.what());
    } catch (const std::exception& ex) {
106
        LOG_FATAL(dctl_logger, DCTL_PID_FILE_ERROR)
107 108 109 110
                  .arg(app_name_).arg(ex.what());
        isc_throw (LaunchError, "Launch failed: " << ex.what());
    }

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

124 125 126 127 128 129 130 131 132
    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
133
        isc_throw (ProcessInitError, "Could Not load configuration file: "
134
                   << comment->stringValue());
135
    }
136

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

150 151 152
    // All done, so bail out.
    LOG_INFO(dctl_logger, DCTL_SHUTDOWN)
        .arg(app_name_).arg(getpid()).arg(VERSION);
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 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209
void
DControllerBase::checkConfigOnly() {
    try {
        // We need to initialize logging, in case any error
        // messages are to be printed.
        // This is just a test, so we don't care about lockfile.
        setenv("KEA_LOCKFILE_DIR", "none", 0);
        isc::dhcp::CfgMgr::instance().setDefaultLoggerName(bin_name_);
        isc::dhcp::CfgMgr::instance().setVerbose(verbose_);
        Daemon::loggerInit(bin_name_.c_str(), verbose_);

        // Check the syntax first.
        std::string config_file = getConfigFile();
        if (config_file.empty()) {
            // Basic sanity check: file name must not be empty.
            isc_throw(InvalidUsage, "JSON configuration file not specified");
        }
        isc::data::ConstElementPtr whole_config = parseFile(config_file);
        if (!whole_config) {
            // No fallback to fromJSONFile
            isc_throw(InvalidUsage, "No configuration found");
        }
        if (verbose_) {
            std::cerr << "Syntax check OK" << std::endl;
        }

        // Check the logic next.
        isc::data::ConstElementPtr module_config;
        module_config = whole_config->get(getAppName());
        if (!module_config) {
            isc_throw(InvalidUsage, "Config file " << config_file <<
                      " does not include '" << getAppName() << "' entry");
        }

        // Get an application process object.
        initProcess();

        isc::data::ConstElementPtr answer;
        answer = checkConfig(module_config);
        int rcode = 0;
        answer = isc::config::parseAnswer(rcode, answer);
        if (rcode != 0) {
            isc_throw(InvalidUsage, "Error encountered: "
                      << answer->stringValue());
        }
    } catch (const VersionMessage&) {
        throw;
    } catch (const InvalidUsage&) {
        throw;
    } catch (const std::exception& ex) {
        isc_throw(InvalidUsage, "Syntax check failed with: " << ex.what());
    }
    return;
}

210 211 212
void
DControllerBase::parseArgs(int argc, char* argv[])
{
213
    // Iterate over the given command line options. If its a stock option
214
    // ("c" or "d") handle it here.  If its a valid custom option, then
215 216
    // invoke customOption.
    int ch;
217
    opterr = 0;
218
    optind = 1;
219
    std::string opts("dvVWc:t:" + getCustomOpts());
220 221
    while ((ch = getopt(argc, argv, opts.c_str())) != -1) {
        switch (ch) {
222
        case 'd':
223 224 225 226
            // Enables verbose logging.
            verbose_ = true;
            break;

227
        case 'v':
228 229 230 231
            // 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;
232 233

        case 'V':
234 235 236 237
            // 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;
238

239 240 241 242 243 244
        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;

245
        case 'c':
246
        case 't':
247 248 249 250 251
            // config file name
            if (optarg == NULL) {
                isc_throw(InvalidUsage, "configuration file name missing");
            }

252
            setConfigFile(optarg);
253 254 255 256

            if (ch == 't') {
                check_only_ = true;
            }
257 258 259 260
            break;

        case '?': {
            // We hit an invalid option.
261
            isc_throw(InvalidUsage, "unsupported option: ["
262 263
                      << static_cast<char>(optopt) << "] "
                      << (!optarg ? "" : optarg));
264 265 266

            break;
            }
267

268 269 270
        default:
            // We hit a valid custom option
            if (!customOption(ch, optarg)) {
271
                // This would be a programmatic error.
272
                isc_throw(InvalidUsage, " Option listed but implemented?: ["
273 274
                          << static_cast<char>(ch) << "] "
                          << (!optarg ? "" : optarg));
275 276 277 278 279 280 281
            }
            break;
        }
    }

    // There was too much information on the command line.
    if (argc > optind) {
282
        isc_throw(InvalidUsage, "extraneous command line information");
283 284 285
    }
}

286
bool
287 288 289 290 291 292 293 294
DControllerBase::customOption(int /* option */, char* /*optarg*/)
{
    // Default implementation returns false.
    return (false);
}

void
DControllerBase::initProcess() {
295
    LOG_DEBUG(dctl_logger, DBGLVL_START_SHUT, DCTL_INIT_PROCESS).arg(app_name_);
296 297

    // Invoke virtual method to instantiate the application process.
298
    try {
299
        process_.reset(createProcess());
300
    } catch (const std::exception& ex) {
301
        isc_throw(DControllerBaseError, std::string("createProcess failed: ")
302
                  + ex.what());
303 304 305 306
    }

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

310 311
    // Invoke application's init method (Note this call should throw
    // DProcessBaseError if it fails).
312 313 314
    process_->init();
}

315 316
isc::data::ConstElementPtr
DControllerBase::configFromFile() {
317 318 319 320
    // Rollback any previous staging configuration. For D2, only a
    // logger configuration is used here.
    isc::dhcp::CfgMgr::instance().rollback();
    // Will hold configuration.
321
    isc::data::ConstElementPtr module_config;
322 323
    // Will receive configuration result.
    isc::data::ConstElementPtr answer;
324
    try {
325
        std::string config_file = getConfigFile();
326 327 328 329 330 331
        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.");
        }

332 333 334 335 336 337 338
        // 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);
        }
339

340 341 342 343
        // Let's configure logging before applying the configuration,
        // so we can log things during configuration process.

        // Temporary storage for logging configuration
344
        isc::dhcp::SrvConfigPtr storage =
345
            isc::dhcp::CfgMgr::instance().getStagingCfg();
346

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

351 352 353 354 355 356 357
        // 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.");
        }
358

359
        answer = updateConfig(module_config);
360 361 362 363 364 365 366 367 368
        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();
        }

369
    } catch (const std::exception& ex) {
370 371
        // Rollback logging configuration.
        isc::dhcp::CfgMgr::instance().rollback();
372 373 374 375 376
        // build an error result
        isc::data::ConstElementPtr error =
            isc::config::createAnswer(1,
                std::string("Configuration parsing failed: ") + ex.what());
        return (error);
377 378
    }

379
    return (answer);
380 381
}

382

383 384
void
DControllerBase::runProcess() {
385
    LOG_DEBUG(dctl_logger, DBGLVL_START_SHUT, DCTL_RUN_PROCESS).arg(app_name_);
386 387 388 389 390
    if (!process_) {
        // This should not be possible.
        isc_throw(DControllerBaseError, "Process not initialized");
    }

391 392
    // Invoke the application process's run method. This may throw
    // DProcessBaseError
393 394 395
    process_->run();
}

396
// Instance method for handling new config
397
isc::data::ConstElementPtr
398 399 400 401 402 403 404 405
DControllerBase::updateConfig(isc::data::ConstElementPtr new_config) {
    return (process_->configure(new_config, false));
}

// Instance method for checking new config
isc::data::ConstElementPtr
DControllerBase::checkConfig(isc::data::ConstElementPtr new_config) {
    return (process_->configure(new_config, true));
406 407 408
}


409
// Instance method for executing commands
410
isc::data::ConstElementPtr
411
DControllerBase::executeCommand(const std::string& command,
412
                            isc::data::ConstElementPtr args) {
413
    // Shutdown is universal.  If its not that, then try it as
414
    // a custom command supported by the derivation.  If that
415 416 417 418
    // 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) {
419
        answer = shutdownProcess(args);
420
    } else {
421
        // It wasn't shutdown, so it may be a custom controller command.
422 423
        int rcode = 0;
        answer = customControllerCommand(command, args);
424
        isc::config::parseAnswer(rcode, answer);
425 426
        if (rcode == COMMAND_INVALID)
        {
427
            // It wasn't a controller command, so it may be an application command.
428
            answer = process_->command(command, args);
429 430 431 432 433 434 435
        }
    }

    return (answer);
}

isc::data::ConstElementPtr
436
DControllerBase::customControllerCommand(const std::string& command,
437 438 439 440
                                     isc::data::ConstElementPtr /* args */) {

    // Default implementation always returns invalid command.
    return (isc::config::createAnswer(COMMAND_INVALID,
441
                                      "Unrecognized command: " + command));
442 443
}

444
isc::data::ConstElementPtr
445
DControllerBase::shutdownProcess(isc::data::ConstElementPtr args) {
446
    if (process_) {
447
        return (process_->shutdown(args));
448
    }
449

450 451 452
    // 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
453
    return (isc::config::createAnswer(0, "Process has not been initialized."));
454 455
}

456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497
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:
        {
498
            LOG_INFO(dctl_logger, DCTL_CFG_FILE_RELOAD_SIGNAL_RECVD)
499 500 501 502 503 504
                     .arg(signum).arg(getConfigFile());
            int rcode;
            isc::data::ConstElementPtr comment = isc::config::
                                                 parseAnswer(rcode,
                                                             configFromFile());
            if (rcode != 0) {
505
                LOG_ERROR(dctl_logger, DCTL_CFG_FILE_RELOAD_ERROR)
506 507 508 509 510 511 512 513 514
                          .arg(comment->stringValue());
            }

            break;
        }

        case SIGINT:
        case SIGTERM:
        {
515
            LOG_DEBUG(dctl_logger, DBGLVL_START_SHUT,
516
                      DCTL_SHUTDOWN_SIGNAL_RECVD).arg(signum);
517 518 519 520 521 522
            isc::data::ElementPtr arg_set;
            executeCommand(SHUT_DOWN_COMMAND, arg_set);
            break;
        }

        default:
523
            LOG_WARN(dctl_logger, DCTL_UNSUPPORTED_SIGNAL).arg(signum);
524 525 526 527
            break;
    }
}

528 529 530 531
void
DControllerBase::usage(const std::string & text)
{
    if (text != "") {
532
        std::cerr << "Usage error: " << text << std::endl;
533 534
    }

535
    std::cerr << "Usage: " << bin_name_ <<  std::endl
536 537
              << "  -v: print version number and exit" << std::endl
              << "  -V: print extended version information and exit"
538 539
              << std::endl
              << "  -W: display the configuration report and exit"
540 541 542
              << std::endl
              << "  -d: optional, verbose output " << std::endl
              << "  -c <config file name> : mandatory,"
543 544 545
              << " specify name of configuration file" << std::endl
              << "  -t <config file name> : check the"
              << " configuration file and exit" << std::endl;
546

547
    // add any derivation specific usage
548
    std::cerr << getUsageText() << std::endl;
549 550 551 552 553
}

DControllerBase::~DControllerBase() {
}

554 555 556
// 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
557
std::string
558
DControllerBase::getVersion(bool extended) {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
559 560 561 562
    std::stringstream tmp;

    tmp << VERSION;
    if (extended) {
563
        tmp << std::endl << EXTENDED_VERSION << std::endl;
564 565 566
        tmp << "linked with:" << std::endl;
        tmp << isc::log::Logger::getVersion() << std::endl;
        tmp << isc::cryptolink::CryptoLink::getVersion() << std::endl;
567
        tmp << "database:" << std::endl;
568
#ifdef HAVE_MYSQL
569
        tmp << isc::dhcp::MySqlLeaseMgr::getDBVersion() << std::endl;
570
#endif
571
#ifdef HAVE_PGSQL
572
        tmp << isc::dhcp::PgSqlLeaseMgr::getDBVersion() << std::endl;
573
#endif
Tomek Mrugalski's avatar
Tomek Mrugalski committed
574 575
#ifdef HAVE_CQL
        tmp << isc::dhcp::CqlLeaseMgr::getDBVersion() << std::endl;
576
#endif
577
        tmp << isc::dhcp::Memfile_LeaseMgr::getDBVersion();
578

579
        // @todo: more details about database runtime
Tomek Mrugalski's avatar
Tomek Mrugalski committed
580 581 582 583
    }

    return (tmp.str());
}
584

585
}; // namespace isc::process
586 587

}; // namespace isc