Kea  2.3.6-git
lfc_controller.cc
Go to the documentation of this file.
1 // Copyright (C) 2015-2023 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>
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 
30 using namespace std;
31 using namespace isc::util;
32 using namespace isc::dhcp;
33 using namespace isc::log;
34 
35 namespace {
37 const uint32_t MAX_LEASE_ERRORS = 100;
38 }; // namespace anonymous
39 
40 namespace isc {
41 namespace lfc {
42 
45 const char* LFCController::lfc_app_name_ = "DhcpLFC";
46 
48 const char* LFCController::lfc_bin_name_ = "kea-lfc";
49 
50 LFCController::LFCController()
51  : protocol_version_(0), verbose_(false), config_file_(""), previous_file_(""),
52  copy_file_(""), output_file_(""), finish_file_(""), pid_file_("") {
53 }
54 
56 }
57 
58 void
59 LFCController::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) {
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 
145 void
146 LFCController::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 
297 void
298 LFCController::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 
319 std::string
320 LFCController::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) {
327  db_version = Memfile_LeaseMgr::getDBVersion(Memfile_LeaseMgr::V4);
328  } else if (protocol_version_ == 6) {
329  db_version = Memfile_LeaseMgr::getDBVersion(Memfile_LeaseMgr::V6);
330  }
331  if (!db_version.empty()) {
332  db_version = "database: " + db_version;
333  }
334  version_stream << std::endl
335  << EXTENDED_VERSION << std::endl
336  << db_version;
337  }
338 
339  return (version_stream.str());
340 }
341 
342 template<typename LeaseObjectType, typename LeaseFileType, typename StorageType>
343 void
344 LFCController::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 
384 void
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 
408 void
409 LFCController::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
423  keaLoggerDbglevel(0));
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
const isc::log::MessageID LFC_FAIL_PID_DEL
Definition: lfc_messages.h:12
const std::string & getRootLoggerName()
Get root logger name.
Definition: logger_name.cc:33
bool exists() const
Checks if the CSV file exists and can be opened for reading.
Definition: csv_file.cc:133
void addOutputOption(const OutputOption &option)
Add output option.
isc::log::Logger lfc_logger("DhcpLFC")
Defines the logger used within LFC.
Definition: lfc_log.h:18
Exception thrown when an error occurs during PID file processing.
Definition: pid_file.h:20
~LFCController()
Destructor.
#define LOG_INFO(LOGGER, MESSAGE)
Macro to conveniently test info output and log it.
Definition: macros.h:20
isc::log::Severity keaLoggerSeverity(isc::log::Severity defseverity)
Obtains logging severity from KEA_LOGGER_SEVERITY.
void process(T start, T finish)
Process Specifications.
const isc::log::MessageID LFC_RUNNING
Definition: lfc_messages.h:18
An abstract API for lease database.
int keaLoggerDbglevel(int defdbglevel)
Obtains logging debug level from KEA_LOGGER_DBGLEVEL.
const isc::log::MessageID LFC_FAIL_PID_CREATE
Definition: lfc_messages.h:11
std::string getOutputFile() const
Gets the output file name.
Logger Manager.
std::string getFinishFile() const
Gets the finish file name.
STL namespace.
Class to help with processing PID files.
Definition: pid_file.h:40
void deleteFile() const
Delete the PID file.
Definition: pid_file.cc:84
Exceptions thrown when a method is unable to manipulate (remove or rename) a file.
virtual const char * what() const
Returns a C-style character string of the cause of the exception.
const isc::log::MessageID LFC_FAIL_PROCESS
Definition: lfc_messages.h:13
void launch(int argc, char *argv[], const bool test_mode)
Acts as the primary entry point to start execution of the process.
const isc::log::MessageID LFC_START
Definition: lfc_messages.h:19
const isc::log::MessageID LFC_READ_STATS
Definition: lfc_messages.h:16
int getProtocolVersion() const
Gets the protocol version of the leases files.
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
Definition: edns.h:19
void parseArgs(int argc, char *argv[])
Process the command line arguments.
const isc::log::MessageID LFC_PROCESSING
Definition: lfc_messages.h:15
static const char * lfc_bin_name_
Defines the executable name, by convention this should match the executable name. ...
Destination destination
Members.
Definition: output_option.h:68
const isc::log::MessageID LFC_FAIL_ROTATE
Definition: lfc_messages.h:14
void write(int) const
Write the PID to the file.
Definition: pid_file.cc:63
static const char * lfc_app_name_
Defines the application name, it may be used to locate configuration data and appears in log statemen...
Exception thrown when the command line is invalid.
Defines the logger used by the top-level component of kea-lfc.
const isc::log::MessageID LFC_TERMINATE
Definition: lfc_messages.h:20
const isc::log::MessageID LFC_ROTATING
Definition: lfc_messages.h:17
void fileRotate() const
Rotate files.
std::string getPreviousFile() const
Gets the previous file name.
std::string getCopyFile() const
Gets the copy file name.
Provides input/output access to CSV files.
Definition: csv_file.h:358
std::string getConfigReport()
Definition: cfgrpt.cc:20
#define LOG_FATAL(LOGGER, MESSAGE)
Macro to conveniently test fatal output and log it.
Definition: macros.h:38
void initLogger(const string &root, isc::log::Severity severity, int dbglevel, const char *file, bool buffer)
Run-time initialization.
const isc::log::MessageID LFC_WRITE_STATS
Definition: lfc_messages.h:21
int check() const
Read the PID in from the file and check it.
Definition: pid_file.cc:26