lfc_controller.cc 11.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 <util/pid_file.h>
17
#include <exceptions/exceptions.h>
18 19 20 21 22
#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>
Shawn Routhier's avatar
Shawn Routhier committed
23
#include <config.h>
24

25
#include <iostream>
26 27
#include <sstream>
#include <unistd.h>
28
#include <stdlib.h>
29
#include <cerrno>
30 31

using namespace std;
32
using namespace isc::util;
33
using namespace isc::dhcp;
34 35 36 37 38 39

namespace isc {
namespace lfc {

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

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

45
/// @brief Maximum number of errors to allow when reading leases from the file.
46 47
const uint32_t MAX_LEASE_ERRORS = 100;

48
LFCController::LFCController()
49
    : protocol_version_(0), verbose_(false), config_file_(""), previous_file_(""),
Shawn Routhier's avatar
Shawn Routhier committed
50
      copy_file_(""), output_file_(""), finish_file_(""), pid_file_("") {
51 52
}

53
LFCController::~LFCController() {
54 55 56
}

void
57
LFCController::launch(int argc, char* argv[]) {
58 59
    bool do_clean = true;

60 61 62 63 64 65 66
    try {
        parseArgs(argc, argv);
    } catch (const InvalidUsage& ex) {
        usage(ex.what());
        throw;  // rethrow it
    }

67 68
    if (verbose_ == true)
        std::cerr << "Starting lease file cleanup" << std::endl;
69 70 71

    // verify we are the only instance
    PIDFile pid_file(pid_file_);
72 73 74 75 76 77 78 79 80

    try {
        if (pid_file.check() == true) {
            // Already running instance, bail out
            std::cerr << "LFC instance already running" <<  std::endl;
            return;
        }
    } catch (const PIDFileError& pid_ex) {
        std::cerr << pid_ex.what() << std::endl;
81 82 83 84 85 86 87 88 89 90 91
        return;
    }

    // create the pid file for this instance
    try {
        pid_file.write();
    } catch (const PIDFileError& pid_ex) {
        std::cerr << pid_ex.what() << std::endl;
        return;
    }

92
    // If we don't have a finish file do the processing
93
    if (access(finish_file_.c_str(), F_OK) == -1) {
94 95 96 97 98 99 100 101 102 103 104 105 106 107
        if (verbose_ == true)
            std::cerr << "LFC Processing files" << std::endl;

        try {
            if (protocol_version_ == 4) {
                processLeases<Lease4, CSVLeaseFile4, Lease4Storage>();
            } else {
                processLeases<Lease6, CSVLeaseFile6, Lease6Storage>();
            }
        } catch (const isc::Exception& proc_ex) {
            // We don't want to do the cleanup but do want to get rid of the pid
            do_clean = false;
            std::cerr << "Processing failed: " << proc_ex.what() << std::endl;
        }
108
    }
109

110 111 112
    // If do_clean 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
113
    // still need to cleanup the pid file
114 115 116 117 118 119 120 121
    if (do_clean == true) {
        if (verbose_ == true)
            std::cerr << "LFC cleaning files" << std::endl;
        try {
            fileCleanup();
        } catch (const RunTimeFail& run_ex) {
            std::cerr << run_ex.what() << std::endl;
        }
122
    }
123 124

    // delete the pid file for this instance
125 126 127 128 129
    try {
        pid_file.deleteFile();
    } catch (const PIDFileError& pid_ex) {
        std::cerr << pid_ex.what() << std::endl;
    }
130

131 132
    if (verbose_ == true)
        std::cerr << "LFC complete" << std::endl;
133 134 135
}

void
136
LFCController::parseArgs(int argc, char* argv[]) {
137 138
    int ch;

139 140
    opterr = 0;
    optind = 1;
141
    while ((ch = getopt(argc, argv, ":46dvVp:x:i:o:c:f:")) != -1) {
142
        switch (ch) {
Shawn Routhier's avatar
Shawn Routhier committed
143
        case '4':
144
            // Process DHCPv4 lease files.
145
            protocol_version_ = 4;
146 147
            break;

Shawn Routhier's avatar
Shawn Routhier committed
148
        case '6':
149
            // Process DHCPv6 lease files.
150
            protocol_version_ = 6;
151 152
            break;

Shawn Routhier's avatar
Shawn Routhier committed
153
        case 'v':
154
            // Print just Kea vesion and exit.
Shawn Routhier's avatar
Shawn Routhier committed
155
            std::cout << getVersion(false) << std::endl;
156 157
            exit(EXIT_SUCCESS);

Shawn Routhier's avatar
Shawn Routhier committed
158
        case 'V':
159
            // Print extended  Kea vesion and exit.
Shawn Routhier's avatar
Shawn Routhier committed
160
            std::cout << getVersion(true) << std::endl;
161 162
            exit(EXIT_SUCCESS);

Shawn Routhier's avatar
Shawn Routhier committed
163 164 165 166 167 168
        case 'd':
            // Verbose output.
            verbose_ = true;
            break;

        case 'p':
169 170 171 172 173 174 175 176 177
            // 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
178
            if (optarg == NULL) {
179
                isc_throw(InvalidUsage, "Previous (ex) file name missing");
Shawn Routhier's avatar
Shawn Routhier committed
180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200
            }
            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':
201
            // Finish file name.
Shawn Routhier's avatar
Shawn Routhier committed
202 203 204 205 206 207 208
            if (optarg == NULL) {
                isc_throw(InvalidUsage, "Finish file name missing");
            }
            finish_file_ = optarg;
            break;

        case 'c':
209
            // Configuration file name
Shawn Routhier's avatar
Shawn Routhier committed
210 211 212 213 214 215
            if (optarg == NULL) {
                isc_throw(InvalidUsage, "Configuration file name missing");
            }
            config_file_ = optarg;
            break;

216
        case 'h':
Shawn Routhier's avatar
Shawn Routhier committed
217
            usage("");
218 219 220 221 222 223 224 225 226
            exit(EXIT_SUCCESS);

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

        case ':':
            // Missing option argument
            isc_throw(InvalidUsage, "Missing option argument");
227 228

        default:
229 230 231
            // 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
232
        }
233 234 235 236 237 238 239
    }

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

240
    if (protocol_version_ == 0) {
241 242 243
        isc_throw(InvalidUsage, "DHCP version required");
    }

244 245 246 247
    if (pid_file_.empty()) {
        isc_throw(InvalidUsage, "PID file not specified");
    }

248
    if (previous_file_.empty()) {
Shawn Routhier's avatar
Shawn Routhier committed
249
        isc_throw(InvalidUsage, "Previous file not specified");
250 251 252
    }

    if (copy_file_.empty()) {
Shawn Routhier's avatar
Shawn Routhier committed
253
        isc_throw(InvalidUsage, "Copy file not specified");
254 255 256
    }

    if (output_file_.empty()) {
Shawn Routhier's avatar
Shawn Routhier committed
257
        isc_throw(InvalidUsage, "Output file not specified");
258 259
    }

260
    if (finish_file_.empty()) {
Shawn Routhier's avatar
Shawn Routhier committed
261
        isc_throw(InvalidUsage, "Finish file not specified");
262 263
    }

264
    if (config_file_.empty()) {
Shawn Routhier's avatar
Shawn Routhier committed
265
        isc_throw(InvalidUsage, "Config file not specified");
266 267 268 269
    }

    // If verbose is set echo the input information
    if (verbose_ == true) {
270 271 272 273 274 275 276 277
        std::cerr << "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;
278 279 280 281
    }
}

void
282
LFCController::usage(const std::string& text) {
283 284 285 286 287
    if (text != "") {
        std::cerr << "Usage error: " << text << std::endl;
    }

    std::cerr << "Usage: " << lfc_bin_name_ << std::endl
288
              << " [-4|-6] -p file -x file -i file -o file -f file -c file" << std::endl
289
              << "   -4 or -6 clean a set of v4 or v6 lease files" << std::endl
290 291
              << "   -p <file>: PID file" << std::endl
              << "   -x <file>: previous or ex lease file" << std::endl
292 293
              << "   -i <file>: copy of lease file" << std::endl
              << "   -o <file>: output lease file" << std::endl
294
              << "   -f <file>: finish file" << std::endl
295 296 297 298
              << "   -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
299
              << "   -h: print this message " << std::endl
300 301 302
              << std::endl;
}

303
std::string
304 305
LFCController::getVersion(const bool extended) const{
    std::stringstream version_stream;
306

307
    version_stream << VERSION;
308
    if (extended) {
309
        version_stream << std::endl << EXTENDED_VERSION;
310 311
    }

312
    return (version_stream.str());
313 314
}

315
template<typename LeaseObjectType, typename LeaseFileType, typename StorageType>
316 317
void
LFCController::processLeases() const {
318 319 320
    LeaseFileType lf_prev(previous_file_.c_str());
    LeaseFileType lf_copy(copy_file_.c_str());
    LeaseFileType lf_output(output_file_.c_str());
321 322 323 324
    StorageType storage;
    storage.clear();

    // If a previous file exists read the entries into storage
325 326 327
    if (lf_prev.exists()) {
        LeaseFileLoader::load<LeaseObjectType>(lf_prev, storage,
                                               MAX_LEASE_ERRORS);
328 329
    }

330 331 332 333
    // Follow that with the copy of the current lease file
    if (lf_copy.exists()) {
        LeaseFileLoader::load<LeaseObjectType>(lf_copy, storage,
                                               MAX_LEASE_ERRORS);
334 335 336
    }

    // Write the result out to the output file
337
    LeaseFileLoader::write<LeaseObjectType>(lf_output, storage);
338 339 340 341 342 343

    // Once we've finished the output file move it to the complete file
    if (rename(output_file_.c_str(), finish_file_.c_str()) != 0)
        isc_throw(RunTimeFail, "Unable to move output (" << output_file_
                  << ") to complete (" << finish_file_
                  << ") error: " << strerror(errno));
344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367
}

void
LFCController::fileCleanup() const {
    // Remove the old previous file
    if ((remove(previous_file_.c_str()) != 0) &&
        (errno != ENOENT)) {
        isc_throw(RunTimeFail, "Unable to delete previous file '"
                  << previous_file_ << "' error: " << strerror(errno));
    }

    // Remove the copy file
    if ((remove(copy_file_.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));
}
368 369
}; // namespace isc::lfc
}; // namespace isc