lfc_controller.cc 14 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
#include <dhcpsrv/csv_lease_file4.h>
#include <dhcpsrv/csv_lease_file6.h>
23
#include <dhcpsrv/memfile_lease_mgr.h>
24 25 26
#include <dhcpsrv/memfile_lease_storage.h>
#include <dhcpsrv/lease_mgr.h>
#include <dhcpsrv/lease_file_loader.h>
27 28
#include <log/logger_manager.h>
#include <log/logger_name.h>
29
#include <cfgrpt/config_report.h>
30

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

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

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

47 48 49
namespace isc {
namespace lfc {

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

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

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

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

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

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

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

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

    LOG_INFO(lfc_logger, LFC_START);
91

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

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

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

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

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

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

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

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

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

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

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

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

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

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

183 184 185 186 187
        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
188 189 190 191 192 193
        case 'd':
            // Verbose output.
            verbose_ = true;
            break;

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

        case 'c':
234
            // Configuration file name
Shawn Routhier's avatar
Shawn Routhier committed
235 236 237 238 239 240
            if (optarg == NULL) {
                isc_throw(InvalidUsage, "Configuration file name missing");
            }
            config_file_ = optarg;
            break;

241
        case 'h':
Shawn Routhier's avatar
Shawn Routhier committed
242
            usage("");
243 244 245 246 247 248 249 250 251
            exit(EXIT_SUCCESS);

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

        case ':':
            // Missing option argument
            isc_throw(InvalidUsage, "Missing option argument");
252 253

        default:
254 255 256
            // 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
257
        }
258 259 260 261 262 263 264
    }

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

265
    if (protocol_version_ == 0) {
266 267 268
        isc_throw(InvalidUsage, "DHCP version required");
    }

269 270 271 272
    if (pid_file_.empty()) {
        isc_throw(InvalidUsage, "PID file not specified");
    }

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

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

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

285
    if (finish_file_.empty()) {
Shawn Routhier's avatar
Shawn Routhier committed
286
        isc_throw(InvalidUsage, "Finish file not specified");
287 288
    }

289
    if (config_file_.empty()) {
Shawn Routhier's avatar
Shawn Routhier committed
290
        isc_throw(InvalidUsage, "Config file not specified");
291 292 293
    }

    // If verbose is set echo the input information
294
    if (verbose_) {
295
        std::cout << "Protocol version:    DHCPv" << protocol_version_ << std::endl
296 297 298 299 300 301 302
                  << "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;
303 304 305 306
    }
}

void
307
LFCController::usage(const std::string& text) {
308
    if (!text.empty()) {
309 310 311 312
        std::cerr << "Usage error: " << text << std::endl;
    }

    std::cerr << "Usage: " << lfc_bin_name_ << std::endl
313
              << " [-4|-6] -p file -x file -i file -o file -f file -c file" << std::endl
314
              << "   -4 or -6 clean a set of v4 or v6 lease files" << std::endl
315 316
              << "   -p <file>: PID file" << std::endl
              << "   -x <file>: previous or ex lease file" << std::endl
317 318
              << "   -i <file>: copy of lease file" << std::endl
              << "   -o <file>: output lease file" << std::endl
319
              << "   -f <file>: finish file" << std::endl
320 321 322 323
              << "   -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
324
              << "   -h: print this message " << std::endl
325 326 327
              << std::endl;
}

328
std::string
329 330
LFCController::getVersion(const bool extended) const{
    std::stringstream version_stream;
331

332
    version_stream << VERSION;
333
    if (extended) {
334 335
        version_stream << std::endl << EXTENDED_VERSION << std::endl
        << "database: " << isc::dhcp::Memfile_LeaseMgr::getDBVersion();
336 337
    }

338
    return (version_stream.str());
339 340
}

341
template<typename LeaseObjectType, typename LeaseFileType, typename StorageType>
342 343
void
LFCController::processLeases() const {
344 345 346
    StorageType storage;

    // If a previous file exists read the entries into storage
347
    LeaseFileType lf_prev(getPreviousFile());
348 349 350
    if (lf_prev.exists()) {
        LeaseFileLoader::load<LeaseObjectType>(lf_prev, storage,
                                               MAX_LEASE_ERRORS);
351 352
    }

353
    // Follow that with the copy of the current lease file
354
    LeaseFileType lf_copy(getCopyFile());
355 356 357
    if (lf_copy.exists()) {
        LeaseFileLoader::load<LeaseObjectType>(lf_copy, storage,
                                               MAX_LEASE_ERRORS);
358 359 360
    }

    // Write the result out to the output file
361
    LeaseFileType lf_output(getOutputFile());
362
    LeaseFileLoader::write<LeaseObjectType>(lf_output, storage);
363

364
    // If desired log the stats
365
    LOG_INFO(lfc_logger, LFC_READ_STATS)
366 367 368 369
      .arg(lf_prev.getReadLeases() + lf_copy.getReadLeases())
      .arg(lf_prev.getReads() + lf_copy.getReads())
      .arg(lf_prev.getReadErrs() + lf_copy.getReadErrs());

370
    LOG_INFO(lfc_logger, LFC_WRITE_STATS)
371 372 373 374
      .arg(lf_output.getWriteLeases())
      .arg(lf_output.getWrites())
      .arg(lf_output.getWriteErrs());

375
    // Once we've finished the output file move it to the complete file
376
    if (rename(getOutputFile().c_str(), getFinishFile().c_str()) != 0) {
377 378 379
        isc_throw(RunTimeFail, "Unable to move output (" << output_file_
                  << ") to complete (" << finish_file_
                  << ") error: " << strerror(errno));
380
    }
381 382 383
}

void
384
LFCController::fileRotate() const {
385
    // Remove the old previous file
386
    if ((remove(getPreviousFile().c_str()) != 0) &&
387 388 389 390 391 392
        (errno != ENOENT)) {
        isc_throw(RunTimeFail, "Unable to delete previous file '"
                  << previous_file_ << "' error: " << strerror(errno));
    }

    // Remove the copy file
393
    if ((remove(getCopyFile().c_str()) != 0) &&
394 395 396 397 398 399
        (errno != ENOENT)) {
        isc_throw(RunTimeFail, "Unable to delete copy file '"
                  << copy_file_ << "' error: " << strerror(errno));
    }

    // Rename the finish file to be the previous file
400
    if (rename(finish_file_.c_str(), previous_file_.c_str()) != 0) {
401 402 403
        isc_throw(RunTimeFail, "Unable to move finish (" << finish_file_
                  << ") to previous (" << previous_file_
                  << ") error: " << strerror(errno));
404
    }
405
}
Shawn Routhier's avatar
Shawn Routhier committed
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 435 436 437 438 439 440

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

441 442
}; // namespace isc::lfc
}; // namespace isc