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