lfc_controller.cc 13.6 KB
Newer Older
1
// Copyright (C) 2015-2018 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 <kea_version.h>
9

10
#include <lfc/lfc_controller.h>
11
#include <lfc/lfc_log.h>
12
#include <util/pid_file.h>
13
#include <exceptions/exceptions.h>
14 15
#include <dhcpsrv/csv_lease_file4.h>
#include <dhcpsrv/csv_lease_file6.h>
16
#include <dhcpsrv/memfile_lease_mgr.h>
17 18 19
#include <dhcpsrv/memfile_lease_storage.h>
#include <dhcpsrv/lease_mgr.h>
#include <dhcpsrv/lease_file_loader.h>
20 21
#include <log/logger_manager.h>
#include <log/logger_name.h>
22
#include <cfgrpt/config_report.h>
23

24
#include <iostream>
25 26
#include <sstream>
#include <unistd.h>
27
#include <stdlib.h>
28
#include <cerrno>
29 30

using namespace std;
31
using namespace isc::util;
32
using namespace isc::dhcp;
33
using namespace isc::log;
34

35 36 37 38 39
namespace {
/// @brief Maximum number of errors to allow when reading leases from the file.
const uint32_t MAX_LEASE_ERRORS = 100;
}; // namespace anonymous

40 41 42
namespace isc {
namespace lfc {

43 44 45
// Refer to config_report so it will be embedded in the binary
const char* const* lfc_config_report = isc::detail::config_report;

46 47
/// @brief Defines the application name, it may be used to locate
/// configuration data and appears in log statements.
48
const char* LFCController::lfc_app_name_ = "DhcpLFC";
49 50

/// @brief Defines the executable name.
51
const char* LFCController::lfc_bin_name_ = "kea-lfc";
52

53
LFCController::LFCController()
54
    : protocol_version_(0), verbose_(false), config_file_(""), previous_file_(""),
Shawn Routhier's avatar
Shawn Routhier committed
55
      copy_file_(""), output_file_(""), finish_file_(""), pid_file_("") {
56 57
}

58
LFCController::~LFCController() {
59 60 61
}

void
62
LFCController::launch(int argc, char* argv[], const bool test_mode) {
63
    bool do_rotate = true;
64

65 66 67 68 69 70 71 72 73 74 75 76 77 78
    // It would be nice to set up the logger as the first step
    // in the process, but we don't know where to send logging
    // info until after we have parsed our arguments.  As we
    // don't currently log anything when trying to parse the
    // arguments we do the parse before the logging setup.  If
    // we do decide to log something then the code will need
    // to move around a bit.

    try {
        parseArgs(argc, argv);
    } catch (const InvalidUsage& ex) {
        usage(ex.what());
        throw;  // rethrow it
    }
79

80 81
    // Start up the logging system.
    startLogger(test_mode);
82 83

    LOG_INFO(lfc_logger, LFC_START);
84

85 86
    // verify we are the only instance
    PIDFile pid_file(pid_file_);
87 88

    try {
89
        if (pid_file.check()) {
90
            // Already running instance, bail out
91
            LOG_FATAL(lfc_logger, LFC_RUNNING);
92 93
            return;
        }
94

95
        // create the pid file for this instance
96 97
        pid_file.write();
    } catch (const PIDFileError& pid_ex) {
98
        LOG_FATAL(lfc_logger, LFC_FAIL_PID_CREATE).arg(pid_ex.what());
99 100 101
        return;
    }

102 103 104 105 106
    // If we don't have a finish file do the processing.  We
    // don't know the exact type of the finish file here but
    // all we care about is if it exists so that's okay
    CSVFile lf_finish(getFinishFile());
    if (!lf_finish.exists()) {
107 108 109
        LOG_INFO(lfc_logger, LFC_PROCESSING)
          .arg(previous_file_)
          .arg(copy_file_);
110 111

        try {
112
            if (getProtocolVersion() == 4) {
113 114 115 116
                processLeases<Lease4, CSVLeaseFile4, Lease4Storage>();
            } else {
                processLeases<Lease6, CSVLeaseFile6, Lease6Storage>();
            }
117
        } catch (const std::exception& proc_ex) {
118
            // We don't want to do the cleanup but do want to get rid of the pid
119
            do_rotate = false;
120
            LOG_FATAL(lfc_logger, LFC_FAIL_PROCESS).arg(proc_ex.what());
121
        }
122
    }
123

124
    // If do_rotate is true We either already had a finish file or
125 126
    // were able to create one.  We now want to do the file cleanup,
    // we don't want to return after the catch as we
127
    // still need to cleanup the pid file
128
    if (do_rotate) {
129
        LOG_INFO(lfc_logger, LFC_ROTATING);
130

131
        try {
132
            fileRotate();
133
        } catch (const RunTimeFail& run_ex) {
134
          LOG_FATAL(lfc_logger, LFC_FAIL_ROTATE).arg(run_ex.what());
135
        }
136
    }
137 138

    // delete the pid file for this instance
139 140 141
    try {
        pid_file.deleteFile();
    } catch (const PIDFileError& pid_ex) {
142
          LOG_FATAL(lfc_logger, LFC_FAIL_PID_DEL).arg(pid_ex.what());
143
    }
144

145
    LOG_INFO(lfc_logger, LFC_TERMINATE);
146 147 148
}

void
149
LFCController::parseArgs(int argc, char* argv[]) {
150 151
    int ch;

152 153
    opterr = 0;
    optind = 1;
154
    while ((ch = getopt(argc, argv, ":46dhvVWp:x:i:o:c:f:")) != -1) {
155
        switch (ch) {
Shawn Routhier's avatar
Shawn Routhier committed
156
        case '4':
157
            // Process DHCPv4 lease files.
158
            protocol_version_ = 4;
159 160
            break;

Shawn Routhier's avatar
Shawn Routhier committed
161
        case '6':
162
            // Process DHCPv6 lease files.
163
            protocol_version_ = 6;
164 165
            break;

Shawn Routhier's avatar
Shawn Routhier committed
166
        case 'v':
Francis Dupont's avatar
Francis Dupont committed
167
            // Print just Kea version and exit.
Shawn Routhier's avatar
Shawn Routhier committed
168
            std::cout << getVersion(false) << std::endl;
169 170
            exit(EXIT_SUCCESS);

Shawn Routhier's avatar
Shawn Routhier committed
171
        case 'V':
Francis Dupont's avatar
Francis Dupont committed
172
            // Print extended  Kea version and exit.
Shawn Routhier's avatar
Shawn Routhier committed
173
            std::cout << getVersion(true) << std::endl;
174 175
            exit(EXIT_SUCCESS);

176 177 178 179 180
        case 'W':
            // Display the configuration report and exit.
            std::cout << isc::detail::getConfigReport() << std::endl;
            exit(EXIT_SUCCESS);

Shawn Routhier's avatar
Shawn Routhier committed
181 182 183 184 185 186
        case 'd':
            // Verbose output.
            verbose_ = true;
            break;

        case 'p':
187 188 189 190 191 192 193 194 195
            // PID file name.
            if (optarg == NULL) {
                isc_throw(InvalidUsage, "PID file name missing");
            }
            pid_file_ = optarg;
            break;

        case 'x':
            // Previous (or ex) file name.
Shawn Routhier's avatar
Shawn Routhier committed
196
            if (optarg == NULL) {
197
                isc_throw(InvalidUsage, "Previous (ex) file name missing");
Shawn Routhier's avatar
Shawn Routhier committed
198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218
            }
            previous_file_ = optarg;
            break;

        case 'i':
            // Copy file name.
            if (optarg == NULL) {
                isc_throw(InvalidUsage, "Copy file name missing");
            }
            copy_file_ = optarg;
            break;

        case 'o':
            // Output file name.
            if (optarg == NULL) {
                isc_throw(InvalidUsage, "Output file name missing");
            }
            output_file_ = optarg;
            break;

        case 'f':
219
            // Finish file name.
Shawn Routhier's avatar
Shawn Routhier committed
220 221 222 223 224 225 226
            if (optarg == NULL) {
                isc_throw(InvalidUsage, "Finish file name missing");
            }
            finish_file_ = optarg;
            break;

        case 'c':
227
            // Configuration file name
Shawn Routhier's avatar
Shawn Routhier committed
228 229 230 231 232 233
            if (optarg == NULL) {
                isc_throw(InvalidUsage, "Configuration file name missing");
            }
            config_file_ = optarg;
            break;

234
        case 'h':
Shawn Routhier's avatar
Shawn Routhier committed
235
            usage("");
236 237 238 239
            exit(EXIT_SUCCESS);

        case '?':
            // Unknown argument
Josh Soref's avatar
Josh Soref committed
240
            // note this will catch all the previous ... name missing
241 242 243 244 245
            isc_throw(InvalidUsage, "Unknown argument");

        case ':':
            // Missing option argument
            isc_throw(InvalidUsage, "Missing option argument");
246 247

        default:
248 249 250
            // I don't think we should get here as the unknown arguments
            // and missing options cases should cover everything else
            isc_throw(InvalidUsage, "Invalid command line");
Shawn Routhier's avatar
Shawn Routhier committed
251
        }
252 253 254 255 256 257 258
    }

    // Check for extraneous parameters.
    if (argc > optind) {
        isc_throw(InvalidUsage, "Extraneous parameters.");
    }

259
    if (protocol_version_ == 0) {
260 261 262
        isc_throw(InvalidUsage, "DHCP version required");
    }

263 264 265 266
    if (pid_file_.empty()) {
        isc_throw(InvalidUsage, "PID file not specified");
    }

267
    if (previous_file_.empty()) {
Shawn Routhier's avatar
Shawn Routhier committed
268
        isc_throw(InvalidUsage, "Previous file not specified");
269 270 271
    }

    if (copy_file_.empty()) {
Shawn Routhier's avatar
Shawn Routhier committed
272
        isc_throw(InvalidUsage, "Copy file not specified");
273 274 275
    }

    if (output_file_.empty()) {
Shawn Routhier's avatar
Shawn Routhier committed
276
        isc_throw(InvalidUsage, "Output file not specified");
277 278
    }

279
    if (finish_file_.empty()) {
Shawn Routhier's avatar
Shawn Routhier committed
280
        isc_throw(InvalidUsage, "Finish file not specified");
281 282
    }

283
    if (config_file_.empty()) {
Shawn Routhier's avatar
Shawn Routhier committed
284
        isc_throw(InvalidUsage, "Config file not specified");
285 286 287
    }

    // If verbose is set echo the input information
288
    if (verbose_) {
289
        std::cout << "Protocol version:    DHCPv" << protocol_version_ << std::endl
290 291 292 293 294 295 296
                  << "Previous or ex lease file: " << previous_file_ << std::endl
                  << "Copy lease file:           " << copy_file_ << std::endl
                  << "Output lease file:         " << output_file_ << std::endl
                  << "Finish file:               " << finish_file_ << std::endl
                  << "Config file:               " << config_file_ << std::endl
                  << "PID file:                  " << pid_file_ << std::endl
                  << std::endl;
297 298 299 300
    }
}

void
301
LFCController::usage(const std::string& text) {
302
    if (!text.empty()) {
303 304 305 306
        std::cerr << "Usage error: " << text << std::endl;
    }

    std::cerr << "Usage: " << lfc_bin_name_ << std::endl
307
              << " [-4|-6] -p file -x file -i file -o file -f file -c file" << std::endl
308
              << "   -4 or -6 clean a set of v4 or v6 lease files" << std::endl
309 310
              << "   -p <file>: PID file" << std::endl
              << "   -x <file>: previous or ex lease file" << std::endl
311 312
              << "   -i <file>: copy of lease file" << std::endl
              << "   -o <file>: output lease file" << std::endl
313
              << "   -f <file>: finish file" << std::endl
314 315
              << "   -c <file>: configuration file" << std::endl
              << "   -v: print version number and exit" << std::endl
316
              << "   -V: print extended version information and exit" << std::endl
317
              << "   -d: optional, verbose output " << std::endl
318
              << "   -h: print this message " << std::endl
319 320 321
              << std::endl;
}

322
std::string
323 324
LFCController::getVersion(const bool extended) const{
    std::stringstream version_stream;
325

326
    version_stream << VERSION;
327
    if (extended) {
328 329
        version_stream << std::endl << EXTENDED_VERSION << std::endl
        << "database: " << isc::dhcp::Memfile_LeaseMgr::getDBVersion();
330 331
    }

332
    return (version_stream.str());
333 334
}

335
template<typename LeaseObjectType, typename LeaseFileType, typename StorageType>
336 337
void
LFCController::processLeases() const {
338 339 340
    StorageType storage;

    // If a previous file exists read the entries into storage
341
    LeaseFileType lf_prev(getPreviousFile());
342 343 344
    if (lf_prev.exists()) {
        LeaseFileLoader::load<LeaseObjectType>(lf_prev, storage,
                                               MAX_LEASE_ERRORS);
345 346
    }

347
    // Follow that with the copy of the current lease file
348
    LeaseFileType lf_copy(getCopyFile());
349 350 351
    if (lf_copy.exists()) {
        LeaseFileLoader::load<LeaseObjectType>(lf_copy, storage,
                                               MAX_LEASE_ERRORS);
352 353 354
    }

    // Write the result out to the output file
355
    LeaseFileType lf_output(getOutputFile());
356
    LeaseFileLoader::write<LeaseObjectType>(lf_output, storage);
357

358
    // If desired log the stats
359
    LOG_INFO(lfc_logger, LFC_READ_STATS)
360 361 362 363
      .arg(lf_prev.getReadLeases() + lf_copy.getReadLeases())
      .arg(lf_prev.getReads() + lf_copy.getReads())
      .arg(lf_prev.getReadErrs() + lf_copy.getReadErrs());

364
    LOG_INFO(lfc_logger, LFC_WRITE_STATS)
365 366 367 368
      .arg(lf_output.getWriteLeases())
      .arg(lf_output.getWrites())
      .arg(lf_output.getWriteErrs());

369
    // Once we've finished the output file move it to the complete file
370
    if (rename(getOutputFile().c_str(), getFinishFile().c_str()) != 0) {
371 372 373
        isc_throw(RunTimeFail, "Unable to move output (" << output_file_
                  << ") to complete (" << finish_file_
                  << ") error: " << strerror(errno));
374
    }
375 376 377
}

void
378
LFCController::fileRotate() const {
379
    // Remove the old previous file
380
    if ((remove(getPreviousFile().c_str()) != 0) &&
381 382 383 384 385 386
        (errno != ENOENT)) {
        isc_throw(RunTimeFail, "Unable to delete previous file '"
                  << previous_file_ << "' error: " << strerror(errno));
    }

    // Remove the copy file
387
    if ((remove(getCopyFile().c_str()) != 0) &&
388 389 390 391 392 393
        (errno != ENOENT)) {
        isc_throw(RunTimeFail, "Unable to delete copy file '"
                  << copy_file_ << "' error: " << strerror(errno));
    }

    // Rename the finish file to be the previous file
394
    if (rename(finish_file_.c_str(), previous_file_.c_str()) != 0) {
395 396 397
        isc_throw(RunTimeFail, "Unable to move finish (" << finish_file_
                  << ") to previous (" << previous_file_
                  << ") error: " << strerror(errno));
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 426 427 428 429 430 431 432 433 434

void
LFCController::startLogger(const bool test_mode) const {
    // If we are running in test mode use the environment variables
    // else use our defaults
    if (test_mode) {
        initLogger();
    }
    else {
        OutputOption option;
        LoggerManager manager;

        initLogger(lfc_app_name_, INFO, 0, NULL, false);

        // Prepare the objects to define the logging specification
        LoggerSpecification spec(getRootLoggerName(),
                                 keaLoggerSeverity(INFO),
                                 keaLoggerDbglevel(0));

        // If we are running in verbose (debugging) mode
        // we send the output to the console, otherwise
        // by default we send it to the SYSLOG
        if (verbose_) {
            option.destination = OutputOption::DEST_CONSOLE;
        } else {
            option.destination = OutputOption::DEST_SYSLOG;
        }

        // ... and set the destination
        spec.addOutputOption(option);

        manager.process(spec);
    }
}

435 436
}; // namespace isc::lfc
}; // namespace isc