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), 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 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
    if (!test_mode && check_only_) {
        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 = updateConfig(module_config, true);
            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;
    }   

132 133 134 135 136 137 138 139 140 141
    // 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_);

142 143 144 145
    // 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.
146
        Daemon::loggerInit(bin_name_.c_str(), verbose_);
147
    }
148

149 150 151
    try {
        createPIDFile();
    } catch (const dhcp::DaemonPIDExists& ex) {
152
        LOG_FATAL(dctl_logger, DCTL_ALREADY_RUNNING)
153 154 155
                  .arg(bin_name_).arg(ex.what());
        isc_throw (LaunchError, "Launch Failed: " << ex.what());
    } catch (const std::exception& ex) {
156
        LOG_FATAL(dctl_logger, DCTL_PID_FILE_ERROR)
157 158 159 160
                  .arg(app_name_).arg(ex.what());
        isc_throw (LaunchError, "Launch failed: " << ex.what());
    }

161 162 163
    // Log the starting of the service.
    LOG_INFO(dctl_logger, DCTL_STARTING)
        .arg(app_name_).arg(getpid()).arg(VERSION);
164
    try {
165
        // Step 2 is to create and initialize the application process object.
166
        initProcess();
167
    } catch (const std::exception& ex) {
168 169 170
        LOG_FATAL(dctl_logger, DCTL_INIT_PROCESS_FAIL)
                  .arg(app_name_).arg(ex.what());
        isc_throw (ProcessInitError,
171
                   "Application Process initialization failed: " << ex.what());
172
    }
173

174 175 176 177 178 179 180 181 182
    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
183
        isc_throw (ProcessInitError, "Could Not load configuration file: "
184
                   << comment->stringValue());
185
    }
186

187 188 189
    // Everything is clear for launch, so start the application's
    // event loop.
    try {
190 191
        // Now that we have a proces, we can set up signal handling.
        initSignalHandling();
192 193
        runProcess();
    } catch (const std::exception& ex) {
194 195 196
        LOG_FATAL(dctl_logger, DCTL_PROCESS_FAILED)
                  .arg(app_name_).arg(ex.what());
        isc_throw (ProcessRunError,
197
                   "Application process event loop failed: " << ex.what());
198 199
    }

200 201 202
    // All done, so bail out.
    LOG_INFO(dctl_logger, DCTL_SHUTDOWN)
        .arg(app_name_).arg(getpid()).arg(VERSION);
203 204 205 206 207
}

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

222
        case 'v':
223 224 225 226
            // 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;
227 228

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

234 235 236 237 238 239
        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;

240
        case 'c':
241
        case 't':
242 243 244 245 246
            // config file name
            if (optarg == NULL) {
                isc_throw(InvalidUsage, "configuration file name missing");
            }

247
            setConfigFile(optarg);
248 249 250 251

            if (ch == 't') {
                check_only_ = true;
            }
252 253 254 255
            break;

        case '?': {
            // We hit an invalid option.
256
            isc_throw(InvalidUsage, "unsupported option: ["
257 258
                      << static_cast<char>(optopt) << "] "
                      << (!optarg ? "" : optarg));
259 260 261

            break;
            }
262

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

    // There was too much information on the command line.
    if (argc > optind) {
277
        isc_throw(InvalidUsage, "extraneous command line information");
278 279 280
    }
}

281
bool
282 283 284 285 286 287 288 289
DControllerBase::customOption(int /* option */, char* /*optarg*/)
{
    // Default implementation returns false.
    return (false);
}

void
DControllerBase::initProcess() {
290
    LOG_DEBUG(dctl_logger, DBGLVL_START_SHUT, DCTL_INIT_PROCESS).arg(app_name_);
291 292

    // Invoke virtual method to instantiate the application process.
293
    try {
294
        process_.reset(createProcess());
295
    } catch (const std::exception& ex) {
296
        isc_throw(DControllerBaseError, std::string("createProcess failed: ")
297
                  + ex.what());
298 299 300 301
    }

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

305 306
    // Invoke application's init method (Note this call should throw
    // DProcessBaseError if it fails).
307 308 309
    process_->init();
}

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

327 328 329 330 331 332 333
        // 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);
        }
334

335 336 337 338
        // Let's configure logging before applying the configuration,
        // so we can log things during configuration process.

        // Temporary storage for logging configuration
339
        isc::dhcp::SrvConfigPtr storage =
340
            isc::dhcp::CfgMgr::instance().getStagingCfg();
341

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

346 347 348 349 350 351 352
        // 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.");
        }
353

354
        answer = updateConfig(module_config, false);
355 356 357 358 359 360 361 362 363
        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();
        }

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

374
    return (answer);
375 376
}

377

378 379
void
DControllerBase::runProcess() {
380
    LOG_DEBUG(dctl_logger, DBGLVL_START_SHUT, DCTL_RUN_PROCESS).arg(app_name_);
381 382 383 384 385
    if (!process_) {
        // This should not be possible.
        isc_throw(DControllerBaseError, "Process not initialized");
    }

386 387
    // Invoke the application process's run method. This may throw
    // DProcessBaseError
388 389 390
    process_->run();
}

391
// Instance method for handling new config
392
isc::data::ConstElementPtr
393 394 395
DControllerBase::updateConfig(isc::data::ConstElementPtr new_config,
                              bool check_only) {
    return (process_->configure(new_config, check_only));
396 397 398
}


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

    return (answer);
}

isc::data::ConstElementPtr
426
DControllerBase::customControllerCommand(const std::string& command,
427 428 429 430
                                     isc::data::ConstElementPtr /* args */) {

    // Default implementation always returns invalid command.
    return (isc::config::createAnswer(COMMAND_INVALID,
431
                                      "Unrecognized command: " + command));
432 433
}

434
isc::data::ConstElementPtr
435
DControllerBase::shutdownProcess(isc::data::ConstElementPtr args) {
436
    if (process_) {
437
        return (process_->shutdown(args));
438
    }
439

440 441 442
    // 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
443
    return (isc::config::createAnswer(0, "Process has not been initialized."));
444 445
}

446 447 448 449 450 451 452 453 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
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:
        {
488
            LOG_INFO(dctl_logger, DCTL_CFG_FILE_RELOAD_SIGNAL_RECVD)
489 490 491 492 493 494
                     .arg(signum).arg(getConfigFile());
            int rcode;
            isc::data::ConstElementPtr comment = isc::config::
                                                 parseAnswer(rcode,
                                                             configFromFile());
            if (rcode != 0) {
495
                LOG_ERROR(dctl_logger, DCTL_CFG_FILE_RELOAD_ERROR)
496 497 498 499 500 501 502 503 504
                          .arg(comment->stringValue());
            }

            break;
        }

        case SIGINT:
        case SIGTERM:
        {
505
            LOG_DEBUG(dctl_logger, DBGLVL_START_SHUT,
506
                      DCTL_SHUTDOWN_SIGNAL_RECVD).arg(signum);
507 508 509 510 511 512
            isc::data::ElementPtr arg_set;
            executeCommand(SHUT_DOWN_COMMAND, arg_set);
            break;
        }

        default:
513
            LOG_WARN(dctl_logger, DCTL_UNSUPPORTED_SIGNAL).arg(signum);
514 515 516 517
            break;
    }
}

518 519 520 521
void
DControllerBase::usage(const std::string & text)
{
    if (text != "") {
522
        std::cerr << "Usage error: " << text << std::endl;
523 524
    }

525
    std::cerr << "Usage: " << bin_name_ <<  std::endl
526 527
              << "  -v: print version number and exit" << std::endl
              << "  -V: print extended version information and exit"
528 529
              << std::endl
              << "  -W: display the configuration report and exit"
530 531 532
              << std::endl
              << "  -d: optional, verbose output " << std::endl
              << "  -c <config file name> : mandatory,"
533 534 535
              << " specify name of configuration file" << std::endl
              << "  -t <config file name> : check the"
              << " configuration file and exit" << std::endl;
536

537
    // add any derivation specific usage
538
    std::cerr << getUsageText() << std::endl;
539 540 541 542 543
}

DControllerBase::~DControllerBase() {
}

544 545 546
// 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
547
std::string
548
DControllerBase::getVersion(bool extended) {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
549 550 551 552
    std::stringstream tmp;

    tmp << VERSION;
    if (extended) {
553
        tmp << std::endl << EXTENDED_VERSION << std::endl;
554 555 556
        tmp << "linked with:" << std::endl;
        tmp << isc::log::Logger::getVersion() << std::endl;
        tmp << isc::cryptolink::CryptoLink::getVersion() << std::endl;
557
        tmp << "database:" << std::endl;
558
#ifdef HAVE_MYSQL
559
        tmp << isc::dhcp::MySqlLeaseMgr::getDBVersion() << std::endl;
560
#endif
561
#ifdef HAVE_PGSQL
562
        tmp << isc::dhcp::PgSqlLeaseMgr::getDBVersion() << std::endl;
563
#endif
Tomek Mrugalski's avatar
Tomek Mrugalski committed
564 565
#ifdef HAVE_CQL
        tmp << isc::dhcp::CqlLeaseMgr::getDBVersion() << std::endl;
566
#endif
567
        tmp << isc::dhcp::Memfile_LeaseMgr::getDBVersion();
568

569
        // @todo: more details about database runtime
Tomek Mrugalski's avatar
Tomek Mrugalski committed
570 571 572 573
    }

    return (tmp.str());
}
574

575
}; // namespace isc::process
576 577

}; // namespace isc