lfc_controller.cc 13.7 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 16
#include <config.h>

17
#include <lfc/lfc_controller.h>
18
#include <lfc/lfc_log.h>
19
#include <util/pid_file.h>
20
#include <exceptions/exceptions.h>
21 22 23 24 25
#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>
26 27
#include <log/logger_manager.h>
#include <log/logger_name.h>
28
#include <cfgrpt/config_report.h>
29

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

using namespace std;
37
using namespace isc::util;
38
using namespace isc::dhcp;
39
using namespace isc::log;
40

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

46 47 48
namespace isc {
namespace lfc {

49 50 51
// Refer to config_report so it will be embedded in the binary
const char* const* lfc_config_report = isc::detail::config_report;

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

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

59
LFCController::LFCController()
60
    : protocol_version_(0), verbose_(false), config_file_(""), previous_file_(""),
Shawn Routhier's avatar
Shawn Routhier committed
61
      copy_file_(""), output_file_(""), finish_file_(""), pid_file_("") {
62 63
}

64
LFCController::~LFCController() {
65 66 67
}

void
68
LFCController::launch(int argc, char* argv[], const bool test_mode) {
69
    bool do_rotate = true;
70

71 72 73 74 75 76 77 78 79 80 81 82 83 84
    // 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
    }
85

Shawn Routhier's avatar
Shawn Routhier committed
86 87
    // Start up the logging system.
    startLogger(test_mode);
88 89

    LOG_INFO(lfc_logger, LFC_START);
90

91 92
    // verify we are the only instance
    PIDFile pid_file(pid_file_);
93 94

    try {
95
        if (pid_file.check()) {
96
            // Already running instance, bail out
97
            LOG_FATAL(lfc_logger, LFC_RUNNING);
98 99
            return;
        }
100

101
        // create the pid file for this instance
102 103
        pid_file.write();
    } catch (const PIDFileError& pid_ex) {
104
        LOG_FATAL(lfc_logger, LFC_FAIL_PID_CREATE).arg(pid_ex.what());
105 106 107
        return;
    }

108 109 110 111 112
    // 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()) {
113 114 115
        LOG_INFO(lfc_logger, LFC_PROCESSING)
          .arg(previous_file_)
          .arg(copy_file_);
116 117

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

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

137
        try {
138
            fileRotate();
139
        } catch (const RunTimeFail& run_ex) {
140
          LOG_FATAL(lfc_logger, LFC_FAIL_ROTATE).arg(run_ex.what());
141
        }
142
    }
143 144

    // delete the pid file for this instance
145 146 147
    try {
        pid_file.deleteFile();
    } catch (const PIDFileError& pid_ex) {
148
          LOG_FATAL(lfc_logger, LFC_FAIL_PID_DEL).arg(pid_ex.what());
149
    }
150

151
    LOG_INFO(lfc_logger, LFC_TERMINATE);
152 153 154
}

void
155
LFCController::parseArgs(int argc, char* argv[]) {
156 157
    int ch;

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

Shawn Routhier's avatar
Shawn Routhier committed
167
        case '6':
168
            // Process DHCPv6 lease files.
169
            protocol_version_ = 6;
170 171
            break;

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

Shawn Routhier's avatar
Shawn Routhier committed
177
        case 'V':
178
            // Print extended  Kea vesion and exit.
Shawn Routhier's avatar
Shawn Routhier committed
179
            std::cout << getVersion(true) << std::endl;
180 181
            exit(EXIT_SUCCESS);

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

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

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

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

        case '?':
            // Unknown argument
            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 316 317
              << "   -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
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
        version_stream << std::endl << EXTENDED_VERSION;
329 330
    }

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

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

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

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

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

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

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

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

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

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

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

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

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