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 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

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

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

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

45 46 47 48 49
namespace isc {
namespace lfc {

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

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

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

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

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

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

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

    LOG_INFO(lfc_logger, LFC_START);
86

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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