lfc_controller.cc 13.6 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14
// Copyright (C) 2015  Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.

15
#include <lfc/lfc_controller.h>
16
#include <lfc/lfc_log.h>
17
#include <util/pid_file.h>
18
#include <exceptions/exceptions.h>
19 20 21 22 23
#include <dhcpsrv/csv_lease_file4.h>
#include <dhcpsrv/csv_lease_file6.h>
#include <dhcpsrv/memfile_lease_storage.h>
#include <dhcpsrv/lease_mgr.h>
#include <dhcpsrv/lease_file_loader.h>
24 25
#include <log/logger_manager.h>
#include <log/logger_name.h>
Shawn Routhier's avatar
Shawn Routhier committed
26
#include <config.h>
27

28
#include <iostream>
29 30
#include <sstream>
#include <unistd.h>
31
#include <stdlib.h>
32
#include <cerrno>
33 34

using namespace std;
35
using namespace isc::util;
36
using namespace isc::dhcp;
37
using namespace isc::log;
38

39 40 41 42 43
namespace {
/// @brief Maximum number of errors to allow when reading leases from the file.
const uint32_t MAX_LEASE_ERRORS = 100;
}; // namespace anonymous

44 45 46 47 48
namespace isc {
namespace lfc {

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

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

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

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

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

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

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

    LOG_INFO(lfc_logger, LFC_START);
85

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Shawn Routhier's avatar
Shawn Routhier committed
177 178 179 180 181 182
        case 'd':
            // Verbose output.
            verbose_ = true;
            break;

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

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

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

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

        case ':':
            // Missing option argument
            isc_throw(InvalidUsage, "Missing option argument");
241 242

        default:
243 244 245
            // 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
246
        }
247 248 249 250 251 252 253
    }

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

254
    if (protocol_version_ == 0) {
255 256 257
        isc_throw(InvalidUsage, "DHCP version required");
    }

258 259 260 261
    if (pid_file_.empty()) {
        isc_throw(InvalidUsage, "PID file not specified");
    }

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

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

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

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

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

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

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

    std::cerr << "Usage: " << lfc_bin_name_ << std::endl
302
              << " [-4|-6] -p file -x file -i file -o file -f file -c file" << std::endl
303
              << "   -4 or -6 clean a set of v4 or v6 lease files" << std::endl
304 305
              << "   -p <file>: PID file" << std::endl
              << "   -x <file>: previous or ex lease file" << std::endl
306 307
              << "   -i <file>: copy of lease file" << std::endl
              << "   -o <file>: output lease file" << std::endl
308
              << "   -f <file>: finish file" << std::endl
309 310 311 312
              << "   -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
313
              << "   -h: print this message " << std::endl
314 315 316
              << std::endl;
}

317
std::string
318 319
LFCController::getVersion(const bool extended) const{
    std::stringstream version_stream;
320

321
    version_stream << VERSION;
322
    if (extended) {
323
        version_stream << std::endl << EXTENDED_VERSION;
324 325
    }

326
    return (version_stream.str());
327 328
}

329
template<typename LeaseObjectType, typename LeaseFileType, typename StorageType>
330 331
void
LFCController::processLeases() const {
332 333 334
    StorageType storage;

    // If a previous file exists read the entries into storage
335
    LeaseFileType lf_prev(getPreviousFile());
336 337 338
    if (lf_prev.exists()) {
        LeaseFileLoader::load<LeaseObjectType>(lf_prev, storage,
                                               MAX_LEASE_ERRORS);
339 340
    }

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

    // Write the result out to the output file
349
    LeaseFileType lf_output(getOutputFile());
350
    LeaseFileLoader::write<LeaseObjectType>(lf_output, storage);
351

352
    // If desired log the stats
353
    LOG_INFO(lfc_logger, LFC_READ_STATS)
354 355 356 357
      .arg(lf_prev.getReadLeases() + lf_copy.getReadLeases())
      .arg(lf_prev.getReads() + lf_copy.getReads())
      .arg(lf_prev.getReadErrs() + lf_copy.getReadErrs());

358
    LOG_INFO(lfc_logger, LFC_WRITE_STATS)
359 360 361 362
      .arg(lf_output.getWriteLeases())
      .arg(lf_output.getWrites())
      .arg(lf_output.getWriteErrs());

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

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

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

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

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

429 430
}; // namespace isc::lfc
}; // namespace isc