Kea 2.7.5
lfc_controller.cc
Go to the documentation of this file.
1// Copyright (C) 2015-2024 Internet Systems Consortium, Inc. ("ISC")
2//
3// This Source Code Form is subject to the terms of the Mozilla Public
4// License, v. 2.0. If a copy of the MPL was not distributed with this
5// file, You can obtain one at http://mozilla.org/MPL/2.0/.
6
7#include <config.h>
8#include <kea_version.h>
9
10#include <lfc/lfc_controller.h>
11#include <lfc/lfc_log.h>
12#include <util/pid_file.h>
18#include <dhcpsrv/lease_mgr.h>
20#include <log/logger_manager.h>
21#include <log/logger_name.h>
23
24#include <iostream>
25#include <sstream>
26#include <unistd.h>
27#include <stdlib.h>
28#include <cerrno>
29
30using namespace std;
31using namespace isc::util;
32using namespace isc::dhcp;
33using namespace isc::log;
34
35namespace {
37const uint32_t MAX_LEASE_ERRORS = 100;
38} // namespace anonymous
39
40namespace isc {
41namespace lfc {
42
45const char* LFCController::lfc_app_name_ = "DhcpLFC";
46
48const char* LFCController::lfc_bin_name_ = "kea-lfc";
49
51 : protocol_version_(0), verbose_(false), config_file_(""), previous_file_(""),
52 copy_file_(""), output_file_(""), finish_file_(""), pid_file_("") {
53}
54
57
58void
59LFCController::launch(int argc, char* argv[], const bool test_mode) {
60 bool do_rotate = true;
61
62 // It would be nice to set up the logger as the first step
63 // in the process, but we don't know where to send logging
64 // info until after we have parsed our arguments. As we
65 // don't currently log anything when trying to parse the
66 // arguments we do the parse before the logging setup. If
67 // we do decide to log something then the code will need
68 // to move around a bit.
69
70 try {
71 parseArgs(argc, argv);
72 } catch (const InvalidUsage& ex) {
73 usage(ex.what());
74 throw; // rethrow it
75 }
76
77 // Start up the logging system.
78 startLogger(test_mode);
79
81
82 // verify we are the only instance
83 PIDFile pid_file(pid_file_);
84
85 try {
86 if (pid_file.check()) {
87 // Already running instance, bail out
89 return;
90 }
91
92 // create the pid file for this instance
93 pid_file.write();
94 } catch (const PIDFileError& pid_ex) {
95 LOG_FATAL(lfc_logger, LFC_FAIL_PID_CREATE).arg(pid_ex.what());
96 return;
97 }
98
99 // If we don't have a finish file do the processing. We
100 // don't know the exact type of the finish file here but
101 // all we care about is if it exists so that's okay
102 CSVFile lf_finish(getFinishFile());
103 if (!lf_finish.exists()) {
105 .arg(previous_file_)
106 .arg(copy_file_);
107
108 try {
109 if (getProtocolVersion() == 4) {
110 processLeases<Lease4, CSVLeaseFile4, Lease4Storage>();
111 } else {
112 processLeases<Lease6, CSVLeaseFile6, Lease6Storage>();
113 }
114 } catch (const std::exception& proc_ex) {
115 // We don't want to do the cleanup but do want to get rid of the pid
116 do_rotate = false;
117 LOG_FATAL(lfc_logger, LFC_FAIL_PROCESS).arg(proc_ex.what());
118 }
119 }
120
121 // If do_rotate is true We either already had a finish file or
122 // were able to create one. We now want to do the file cleanup,
123 // we don't want to return after the catch as we
124 // still need to cleanup the pid file
125 if (do_rotate) {
127
128 try {
129 fileRotate();
130 } catch (const RunTimeFail& run_ex) {
131 LOG_FATAL(lfc_logger, LFC_FAIL_ROTATE).arg(run_ex.what());
132 }
133 }
134
135 // delete the pid file for this instance
136 try {
137 pid_file.deleteFile();
138 } catch (const PIDFileError& pid_ex) {
139 LOG_FATAL(lfc_logger, LFC_FAIL_PID_DEL).arg(pid_ex.what());
140 }
141
143}
144
145void
146LFCController::parseArgs(int argc, char* argv[]) {
147 int ch;
148
149 opterr = 0;
150 optind = 1;
151 while ((ch = getopt(argc, argv, ":46dhvVWp:x:i:o:c:f:")) != -1) {
152 switch (ch) {
153 case '4':
154 // Process DHCPv4 lease files.
155 protocol_version_ = 4;
156 break;
157
158 case '6':
159 // Process DHCPv6 lease files.
160 protocol_version_ = 6;
161 break;
162
163 case 'v':
164 // Print just Kea version and exit.
165 std::cout << getVersion(false) << std::endl;
166 exit(EXIT_SUCCESS);
167
168 case 'V':
169 // Print extended Kea version and exit.
170 std::cout << getVersion(true) << std::endl;
171 exit(EXIT_SUCCESS);
172
173 case 'W':
174 // Display the configuration report and exit.
175 std::cout << isc::detail::getConfigReport() << std::endl;
176 exit(EXIT_SUCCESS);
177
178 case 'd':
179 // Verbose output.
180 verbose_ = true;
181 break;
182
183 case 'p':
184 // PID file name.
185 if (optarg == NULL) {
186 isc_throw(InvalidUsage, "PID file name missing");
187 }
188 pid_file_ = optarg;
189 break;
190
191 case 'x':
192 // Previous (or ex) file name.
193 if (optarg == NULL) {
194 isc_throw(InvalidUsage, "Previous (ex) file name missing");
195 }
196 previous_file_ = optarg;
197 break;
198
199 case 'i':
200 // Copy file name.
201 if (optarg == NULL) {
202 isc_throw(InvalidUsage, "Copy file name missing");
203 }
204 copy_file_ = optarg;
205 break;
206
207 case 'o':
208 // Output file name.
209 if (optarg == NULL) {
210 isc_throw(InvalidUsage, "Output file name missing");
211 }
212 output_file_ = optarg;
213 break;
214
215 case 'f':
216 // Finish file name.
217 if (optarg == NULL) {
218 isc_throw(InvalidUsage, "Finish file name missing");
219 }
220 finish_file_ = optarg;
221 break;
222
223 case 'c':
224 // Configuration file name
225 if (optarg == NULL) {
226 isc_throw(InvalidUsage, "Configuration file name missing");
227 }
228 config_file_ = optarg;
229 break;
230
231 case 'h':
232 usage("");
233 exit(EXIT_SUCCESS);
234
235 case '?':
236 // Unknown argument
237 // note this will catch all the previous ... name missing
238 isc_throw(InvalidUsage, "Unknown argument");
239
240 case ':':
241 // Missing option argument
242 isc_throw(InvalidUsage, "Missing option argument");
243
244 default:
245 // I don't think we should get here as the unknown arguments
246 // and missing options cases should cover everything else
247 isc_throw(InvalidUsage, "Invalid command line");
248 }
249 }
250
251 // Check for extraneous parameters.
252 if (argc > optind) {
253 isc_throw(InvalidUsage, "Extraneous parameters.");
254 }
255
256 if (protocol_version_ == 0) {
257 isc_throw(InvalidUsage, "DHCP version required");
258 }
259
260 if (pid_file_.empty()) {
261 isc_throw(InvalidUsage, "PID file not specified");
262 }
263
264 if (previous_file_.empty()) {
265 isc_throw(InvalidUsage, "Previous file not specified");
266 }
267
268 if (copy_file_.empty()) {
269 isc_throw(InvalidUsage, "Copy file not specified");
270 }
271
272 if (output_file_.empty()) {
273 isc_throw(InvalidUsage, "Output file not specified");
274 }
275
276 if (finish_file_.empty()) {
277 isc_throw(InvalidUsage, "Finish file not specified");
278 }
279
280 if (config_file_.empty()) {
281 isc_throw(InvalidUsage, "Config file not specified");
282 }
283
284 // If verbose is set echo the input information
285 if (verbose_) {
286 std::cout << "Protocol version: DHCPv" << protocol_version_ << std::endl
287 << "Previous or ex lease file: " << previous_file_ << std::endl
288 << "Copy lease file: " << copy_file_ << std::endl
289 << "Output lease file: " << output_file_ << std::endl
290 << "Finish file: " << finish_file_ << std::endl
291 << "Config file: " << config_file_ << std::endl
292 << "PID file: " << pid_file_ << std::endl
293 << std::endl;
294 }
295}
296
297void
298LFCController::usage(const std::string& text) {
299 if (!text.empty()) {
300 std::cerr << "Usage error: " << text << std::endl;
301 }
302
303 std::cerr << "Usage: " << lfc_bin_name_ << std::endl
304 << " [-4|-6] -p file -x file -i file -o file -f file -c file" << std::endl
305 << " -4 or -6 clean a set of v4 or v6 lease files" << std::endl
306 << " -p <file>: PID file" << std::endl
307 << " -x <file>: previous or ex lease file" << std::endl
308 << " -i <file>: copy of lease file" << std::endl
309 << " -o <file>: output lease file" << std::endl
310 << " -f <file>: finish file" << std::endl
311 << " -c <file>: configuration file" << std::endl
312 << " -v: print version number and exit" << std::endl
313 << " -V: print extended version information and exit" << std::endl
314 << " -d: optional, verbose output " << std::endl
315 << " -h: print this message " << std::endl
316 << std::endl;
317}
318
319std::string
320LFCController::getVersion(const bool extended) const{
321 std::stringstream version_stream;
322
323 version_stream << VERSION;
324 if (extended) {
325 std::string db_version;
326 if (protocol_version_ == 4) {
328 } else if (protocol_version_ == 6) {
330 }
331
332 version_stream << " (" << EXTENDED_VERSION << ")";
333 if (!db_version.empty()) {
334 db_version = "backend: " + db_version;
335 version_stream << std::endl << db_version;
336 }
337 }
338
339 return (version_stream.str());
340}
341
342template<typename LeaseObjectType, typename LeaseFileType, typename StorageType>
343void
344LFCController::processLeases() const {
345 StorageType storage;
346
347 // If a previous file exists read the entries into storage
348 LeaseFileType lf_prev(getPreviousFile());
349 if (lf_prev.exists()) {
350 LeaseFileLoader::load<LeaseObjectType>(lf_prev, storage,
351 MAX_LEASE_ERRORS);
352 }
353
354 // Follow that with the copy of the current lease file
355 LeaseFileType lf_copy(getCopyFile());
356 if (lf_copy.exists()) {
357 LeaseFileLoader::load<LeaseObjectType>(lf_copy, storage,
358 MAX_LEASE_ERRORS);
359 }
360
361 // Write the result out to the output file
362 LeaseFileType lf_output(getOutputFile());
363 LeaseFileLoader::write<LeaseObjectType>(lf_output, storage);
364
365 // If desired log the stats
367 .arg(lf_prev.getReadLeases() + lf_copy.getReadLeases())
368 .arg(lf_prev.getReads() + lf_copy.getReads())
369 .arg(lf_prev.getReadErrs() + lf_copy.getReadErrs());
370
372 .arg(lf_output.getWriteLeases())
373 .arg(lf_output.getWrites())
374 .arg(lf_output.getWriteErrs());
375
376 // Once we've finished the output file move it to the complete file
377 if (rename(getOutputFile().c_str(), getFinishFile().c_str()) != 0) {
378 isc_throw(RunTimeFail, "Unable to move output (" << output_file_
379 << ") to complete (" << finish_file_
380 << ") error: " << strerror(errno));
381 }
382}
383
384void
386 // Remove the old previous file
387 if ((remove(getPreviousFile().c_str()) != 0) &&
388 (errno != ENOENT)) {
389 isc_throw(RunTimeFail, "Unable to delete previous file '"
390 << previous_file_ << "' error: " << strerror(errno));
391 }
392
393 // Remove the copy file
394 if ((remove(getCopyFile().c_str()) != 0) &&
395 (errno != ENOENT)) {
396 isc_throw(RunTimeFail, "Unable to delete copy file '"
397 << copy_file_ << "' error: " << strerror(errno));
398 }
399
400 // Rename the finish file to be the previous file
401 if (rename(finish_file_.c_str(), previous_file_.c_str()) != 0) {
402 isc_throw(RunTimeFail, "Unable to move finish (" << finish_file_
403 << ") to previous (" << previous_file_
404 << ") error: " << strerror(errno));
405 }
406}
407
408void
409LFCController::startLogger(const bool test_mode) const {
410 // If we are running in test mode use the environment variables
411 // else use our defaults
412 if (test_mode) {
413 initLogger();
414 } else {
415 OutputOption option;
416 LoggerManager manager;
417
418 initLogger(lfc_app_name_, INFO, 0, NULL, false);
419
420 // Prepare the objects to define the logging specification
424
425 // If we are running in verbose (debugging) mode
426 // we send the output to the console, otherwise
427 // by default we send it to the SYSLOG
428 if (verbose_) {
429 option.destination = OutputOption::DEST_CONSOLE;
430 } else {
431 option.destination = OutputOption::DEST_SYSLOG;
432 }
433
434 // ... and set the destination
435 spec.addOutputOption(option);
436
437 manager.process(spec);
438 }
439}
440
441} // namespace isc::lfc
442} // namespace isc
static std::string getDBVersionInternal(Universe const &u)
Local version of getDBVersion() class method.
Exception thrown when the command line is invalid.
void fileRotate() const
Rotate files.
void launch(int argc, char *argv[], const bool test_mode)
Acts as the primary entry point to start execution of the process.
int getProtocolVersion() const
Gets the protocol version of the leases files.
std::string getFinishFile() const
Gets the finish file name.
std::string getCopyFile() const
Gets the copy file name.
static const char * lfc_bin_name_
Defines the executable name, by convention this should match the executable name.
void parseArgs(int argc, char *argv[])
Process the command line arguments.
static const char * lfc_app_name_
Defines the application name, it may be used to locate configuration data and appears in log statemen...
std::string getPreviousFile() const
Gets the previous file name.
std::string getOutputFile() const
Gets the output file name.
Exceptions thrown when a method is unable to manipulate (remove or rename) a file.
Provides input/output access to CSV files.
Definition csv_file.h:358
Exception thrown when an error occurs during PID file processing.
Definition pid_file.h:20
Class to help with processing PID files.
Definition pid_file.h:40
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
An abstract API for lease database.
void usage()
Print Usage.
#define LOG_INFO(LOGGER, MESSAGE)
Macro to conveniently test info output and log it.
Definition macros.h:20
#define LOG_FATAL(LOGGER, MESSAGE)
Macro to conveniently test fatal output and log it.
Definition macros.h:38
std::string getConfigReport()
Definition cfgrpt.cc:20
const isc::log::MessageID LFC_FAIL_PROCESS
const isc::log::MessageID LFC_START
const isc::log::MessageID LFC_TERMINATE
const isc::log::MessageID LFC_FAIL_PID_DEL
const isc::log::MessageID LFC_FAIL_ROTATE
const isc::log::MessageID LFC_RUNNING
const isc::log::MessageID LFC_WRITE_STATS
const isc::log::MessageID LFC_FAIL_PID_CREATE
isc::log::Logger lfc_logger("DhcpLFC")
Defines the logger used within LFC.
Definition lfc_log.h:18
const isc::log::MessageID LFC_PROCESSING
const isc::log::MessageID LFC_ROTATING
const isc::log::MessageID LFC_READ_STATS
const std::string & getRootLoggerName()
Get root logger name.
void initLogger(const string &root, isc::log::Severity severity, int dbglevel, const char *file, bool buffer)
Run-time initialization.
int keaLoggerDbglevel(int defdbglevel)
Obtains logging debug level from KEA_LOGGER_DBGLEVEL.
isc::log::Severity keaLoggerSeverity(isc::log::Severity defseverity)
Obtains logging severity from KEA_LOGGER_SEVERITY.
Defines the logger used by the top-level component of kea-lfc.