d_controller.cc 17 KB
Newer Older
1
// Copyright (C) 2013-2016 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
153
154
155
    // ("s" or "v") handle it here.  If its a valid custom option, then
    // invoke customOption.
    int ch;
156
    opterr = 0;
157
    optind = 1;
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));
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