Kea: lfc_controller.cc Source File

| Kea 3.2.0-git

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