lfc_controller.cc 13.5 KB
Newer Older
1
// Copyright (C) 2015 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 8
#include <config.h>

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

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

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

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

39 40 41
namespace isc {
namespace lfc {

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

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

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

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

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

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

64 65 66 67 68 69 70 71 72 73 74 75 76 77
    // 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
    }
78

Shawn Routhier's avatar
Shawn Routhier committed
79 80
    // Start up the logging system.
    startLogger(test_mode);
81 82

    LOG_INFO(lfc_logger, LFC_START);
83

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

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

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

101 102 103 104 105
    // 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()) {
106 107 108
        LOG_INFO(lfc_logger, LFC_PROCESSING)
          .arg(previous_file_)
          .arg(copy_file_);
109 110

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

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

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

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

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

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

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

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

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

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

175 176 177 178 179
        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
180 181 182 183 184 185
        case 'd':
            // Verbose output.
            verbose_ = true;
            break;

        case 'p':
186 187 188 189 190 191 192 193 194
            // 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
195
            if (optarg == NULL) {
196
                isc_throw(InvalidUsage, "Previous (ex) file name missing");
Shawn Routhier's avatar
Shawn Routhier committed
197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217
            }
            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':
218
            // Finish file name.
Shawn Routhier's avatar
Shawn Routhier committed
219 220 221 222 223 224 225
            if (optarg == NULL) {
                isc_throw(InvalidUsage, "Finish file name missing");
            }
            finish_file_ = optarg;
            break;

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

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

        case '?':
            // Unknown argument
            isc_throw(InvalidUsage, "Unknown argument");

        case ':':
            // Missing option argument
            isc_throw(InvalidUsage, "Missing option argument");
244 245

        default:
246 247 248
            // 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
249
        }
250 251 252 253 254 255 256
    }

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

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

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

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

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

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

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

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

    // If verbose is set echo the input information
286
    if (verbose_) {
287
        std::cout << "Protocol version:    DHCPv" << protocol_version_ << std::endl
288 289 290 291 292 293 294
                  << "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;
295 296 297 298
    }
}

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

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

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

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

330
    return (version_stream.str());
331 332
}

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

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

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

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

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

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

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

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

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

    // Rename the finish file to be the previous file
392
    if (rename(finish_file_.c_str(), previous_file_.c_str()) != 0) {
393 394 395
        isc_throw(RunTimeFail, "Unable to move finish (" << finish_file_
                  << ") to previous (" << previous_file_
                  << ") error: " << strerror(errno));
396
    }
397
}
Shawn Routhier's avatar
Shawn Routhier committed
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

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);
    }
}

433 434
}; // namespace isc::lfc
}; // namespace isc