summaryrefslogtreecommitdiffstats
path: root/src/bin/lfc/lfc_controller.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/bin/lfc/lfc_controller.cc')
-rw-r--r--src/bin/lfc/lfc_controller.cc445
1 files changed, 445 insertions, 0 deletions
diff --git a/src/bin/lfc/lfc_controller.cc b/src/bin/lfc/lfc_controller.cc
new file mode 100644
index 0000000..4f51238
--- /dev/null
+++ b/src/bin/lfc/lfc_controller.cc
@@ -0,0 +1,445 @@
+// Copyright (C) 2015-2022 Internet Systems Consortium, Inc. ("ISC")
+//
+// 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/.
+
+#include <config.h>
+#include <kea_version.h>
+
+#include <lfc/lfc_controller.h>
+#include <lfc/lfc_log.h>
+#include <util/pid_file.h>
+#include <exceptions/exceptions.h>
+#include <dhcpsrv/csv_lease_file4.h>
+#include <dhcpsrv/csv_lease_file6.h>
+#include <dhcpsrv/memfile_lease_mgr.h>
+#include <dhcpsrv/memfile_lease_storage.h>
+#include <dhcpsrv/lease_mgr.h>
+#include <dhcpsrv/lease_file_loader.h>
+#include <log/logger_manager.h>
+#include <log/logger_name.h>
+#include <cfgrpt/config_report.h>
+
+#include <iostream>
+#include <sstream>
+#include <unistd.h>
+#include <stdlib.h>
+#include <cerrno>
+
+using namespace std;
+using namespace isc::util;
+using namespace isc::dhcp;
+using namespace isc::log;
+
+namespace {
+/// @brief Maximum number of errors to allow when reading leases from the file.
+const uint32_t MAX_LEASE_ERRORS = 100;
+}; // namespace anonymous
+
+namespace isc {
+namespace lfc {
+
+// Refer to config_report so it will be embedded in the binary
+const char* const* lfc_config_report = isc::detail::config_report;
+
+/// @brief Defines the application name, it may be used to locate
+/// configuration data and appears in log statements.
+const char* LFCController::lfc_app_name_ = "DhcpLFC";
+
+/// @brief Defines the executable name.
+const char* LFCController::lfc_bin_name_ = "kea-lfc";
+
+LFCController::LFCController()
+ : protocol_version_(0), verbose_(false), config_file_(""), previous_file_(""),
+ copy_file_(""), output_file_(""), finish_file_(""), pid_file_("") {
+}
+
+LFCController::~LFCController() {
+}
+
+void
+LFCController::launch(int argc, char* argv[], const bool test_mode) {
+ bool do_rotate = true;
+
+ // 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
+ }
+
+ // Start up the logging system.
+ startLogger(test_mode);
+
+ LOG_INFO(lfc_logger, LFC_START);
+
+ // verify we are the only instance
+ PIDFile pid_file(pid_file_);
+
+ try {
+ if (pid_file.check()) {
+ // Already running instance, bail out
+ LOG_FATAL(lfc_logger, LFC_RUNNING);
+ return;
+ }
+
+ // create the pid file for this instance
+ pid_file.write();
+ } catch (const PIDFileError& pid_ex) {
+ LOG_FATAL(lfc_logger, LFC_FAIL_PID_CREATE).arg(pid_ex.what());
+ return;
+ }
+
+ // 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()) {
+ LOG_INFO(lfc_logger, LFC_PROCESSING)
+ .arg(previous_file_)
+ .arg(copy_file_);
+
+ try {
+ if (getProtocolVersion() == 4) {
+ processLeases<Lease4, CSVLeaseFile4, Lease4Storage>();
+ } else {
+ processLeases<Lease6, CSVLeaseFile6, Lease6Storage>();
+ }
+ } catch (const std::exception& proc_ex) {
+ // We don't want to do the cleanup but do want to get rid of the pid
+ do_rotate = false;
+ LOG_FATAL(lfc_logger, LFC_FAIL_PROCESS).arg(proc_ex.what());
+ }
+ }
+
+ // If do_rotate is true We either already had a finish file or
+ // were able to create one. We now want to do the file cleanup,
+ // we don't want to return after the catch as we
+ // still need to cleanup the pid file
+ if (do_rotate) {
+ LOG_INFO(lfc_logger, LFC_ROTATING);
+
+ try {
+ fileRotate();
+ } catch (const RunTimeFail& run_ex) {
+ LOG_FATAL(lfc_logger, LFC_FAIL_ROTATE).arg(run_ex.what());
+ }
+ }
+
+ // delete the pid file for this instance
+ try {
+ pid_file.deleteFile();
+ } catch (const PIDFileError& pid_ex) {
+ LOG_FATAL(lfc_logger, LFC_FAIL_PID_DEL).arg(pid_ex.what());
+ }
+
+ LOG_INFO(lfc_logger, LFC_TERMINATE);
+}
+
+void
+LFCController::parseArgs(int argc, char* argv[]) {
+ int ch;
+
+ opterr = 0;
+ optind = 1;
+ while ((ch = getopt(argc, argv, ":46dhvVWp:x:i:o:c:f:")) != -1) {
+ switch (ch) {
+ case '4':
+ // Process DHCPv4 lease files.
+ protocol_version_ = 4;
+ break;
+
+ case '6':
+ // Process DHCPv6 lease files.
+ protocol_version_ = 6;
+ break;
+
+ case 'v':
+ // Print just Kea version and exit.
+ std::cout << getVersion(false) << std::endl;
+ exit(EXIT_SUCCESS);
+
+ case 'V':
+ // Print extended Kea version and exit.
+ std::cout << getVersion(true) << std::endl;
+ exit(EXIT_SUCCESS);
+
+ case 'W':
+ // Display the configuration report and exit.
+ std::cout << isc::detail::getConfigReport() << std::endl;
+ exit(EXIT_SUCCESS);
+
+ case 'd':
+ // Verbose output.
+ verbose_ = true;
+ break;
+
+ case 'p':
+ // PID file name.
+ if (optarg == NULL) {
+ isc_throw(InvalidUsage, "PID file name missing");
+ }
+ pid_file_ = optarg;
+ break;
+
+ case 'x':
+ // Previous (or ex) file name.
+ if (optarg == NULL) {
+ isc_throw(InvalidUsage, "Previous (ex) file name missing");
+ }
+ 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':
+ // Finish file name.
+ if (optarg == NULL) {
+ isc_throw(InvalidUsage, "Finish file name missing");
+ }
+ finish_file_ = optarg;
+ break;
+
+ case 'c':
+ // Configuration file name
+ if (optarg == NULL) {
+ isc_throw(InvalidUsage, "Configuration file name missing");
+ }
+ config_file_ = optarg;
+ break;
+
+ case 'h':
+ usage("");
+ exit(EXIT_SUCCESS);
+
+ case '?':
+ // Unknown argument
+ // note this will catch all the previous ... name missing
+ isc_throw(InvalidUsage, "Unknown argument");
+
+ case ':':
+ // Missing option argument
+ isc_throw(InvalidUsage, "Missing option argument");
+
+ default:
+ // 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");
+ }
+ }
+
+ // Check for extraneous parameters.
+ if (argc > optind) {
+ isc_throw(InvalidUsage, "Extraneous parameters.");
+ }
+
+ if (protocol_version_ == 0) {
+ isc_throw(InvalidUsage, "DHCP version required");
+ }
+
+ if (pid_file_.empty()) {
+ isc_throw(InvalidUsage, "PID file not specified");
+ }
+
+ if (previous_file_.empty()) {
+ isc_throw(InvalidUsage, "Previous file not specified");
+ }
+
+ if (copy_file_.empty()) {
+ isc_throw(InvalidUsage, "Copy file not specified");
+ }
+
+ if (output_file_.empty()) {
+ isc_throw(InvalidUsage, "Output file not specified");
+ }
+
+ if (finish_file_.empty()) {
+ isc_throw(InvalidUsage, "Finish file not specified");
+ }
+
+ if (config_file_.empty()) {
+ isc_throw(InvalidUsage, "Config file not specified");
+ }
+
+ // If verbose is set echo the input information
+ if (verbose_) {
+ std::cout << "Protocol version: DHCPv" << protocol_version_ << std::endl
+ << "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;
+ }
+}
+
+void
+LFCController::usage(const std::string& text) {
+ if (!text.empty()) {
+ std::cerr << "Usage error: " << text << std::endl;
+ }
+
+ std::cerr << "Usage: " << lfc_bin_name_ << std::endl
+ << " [-4|-6] -p file -x file -i file -o file -f file -c file" << std::endl
+ << " -4 or -6 clean a set of v4 or v6 lease files" << std::endl
+ << " -p <file>: PID file" << std::endl
+ << " -x <file>: previous or ex lease file" << std::endl
+ << " -i <file>: copy of lease file" << std::endl
+ << " -o <file>: output lease file" << std::endl
+ << " -f <file>: finish file" << std::endl
+ << " -c <file>: configuration file" << std::endl
+ << " -v: print version number and exit" << std::endl
+ << " -V: print extended version information and exit" << std::endl
+ << " -d: optional, verbose output " << std::endl
+ << " -h: print this message " << std::endl
+ << std::endl;
+}
+
+std::string
+LFCController::getVersion(const bool extended) const{
+ std::stringstream version_stream;
+
+ version_stream << VERSION;
+ if (extended) {
+ std::string db_version;
+ if (protocol_version_ == 4) {
+ db_version = Memfile_LeaseMgr::getDBVersion(Memfile_LeaseMgr::V4);
+ } else if (protocol_version_ == 6) {
+ db_version = Memfile_LeaseMgr::getDBVersion(Memfile_LeaseMgr::V6);
+ }
+ if (!db_version.empty()) {
+ db_version = "database: " + db_version;
+ }
+ version_stream << std::endl
+ << EXTENDED_VERSION << std::endl
+ << db_version;
+ }
+
+ return (version_stream.str());
+}
+
+template<typename LeaseObjectType, typename LeaseFileType, typename StorageType>
+void
+LFCController::processLeases() const {
+ StorageType storage;
+
+ // If a previous file exists read the entries into storage
+ LeaseFileType lf_prev(getPreviousFile());
+ if (lf_prev.exists()) {
+ LeaseFileLoader::load<LeaseObjectType>(lf_prev, storage,
+ MAX_LEASE_ERRORS);
+ }
+
+ // Follow that with the copy of the current lease file
+ LeaseFileType lf_copy(getCopyFile());
+ if (lf_copy.exists()) {
+ LeaseFileLoader::load<LeaseObjectType>(lf_copy, storage,
+ MAX_LEASE_ERRORS);
+ }
+
+ // Write the result out to the output file
+ LeaseFileType lf_output(getOutputFile());
+ LeaseFileLoader::write<LeaseObjectType>(lf_output, storage);
+
+ // If desired log the stats
+ LOG_INFO(lfc_logger, LFC_READ_STATS)
+ .arg(lf_prev.getReadLeases() + lf_copy.getReadLeases())
+ .arg(lf_prev.getReads() + lf_copy.getReads())
+ .arg(lf_prev.getReadErrs() + lf_copy.getReadErrs());
+
+ LOG_INFO(lfc_logger, LFC_WRITE_STATS)
+ .arg(lf_output.getWriteLeases())
+ .arg(lf_output.getWrites())
+ .arg(lf_output.getWriteErrs());
+
+ // Once we've finished the output file move it to the complete file
+ if (rename(getOutputFile().c_str(), getFinishFile().c_str()) != 0) {
+ isc_throw(RunTimeFail, "Unable to move output (" << output_file_
+ << ") to complete (" << finish_file_
+ << ") error: " << strerror(errno));
+ }
+}
+
+void
+LFCController::fileRotate() const {
+ // Remove the old previous file
+ if ((remove(getPreviousFile().c_str()) != 0) &&
+ (errno != ENOENT)) {
+ isc_throw(RunTimeFail, "Unable to delete previous file '"
+ << previous_file_ << "' error: " << strerror(errno));
+ }
+
+ // Remove the copy file
+ if ((remove(getCopyFile().c_str()) != 0) &&
+ (errno != ENOENT)) {
+ isc_throw(RunTimeFail, "Unable to delete copy file '"
+ << copy_file_ << "' error: " << strerror(errno));
+ }
+
+ // Rename the finish file to be the previous file
+ if (rename(finish_file_.c_str(), previous_file_.c_str()) != 0) {
+ isc_throw(RunTimeFail, "Unable to move finish (" << finish_file_
+ << ") to previous (" << previous_file_
+ << ") error: " << strerror(errno));
+ }
+}
+
+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);
+ }
+}
+
+}; // namespace isc::lfc
+}; // namespace isc