Kea  2.3.4-git
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>
13 #include <exceptions/exceptions.h>
18 #include <dhcpsrv/lease_mgr.h>
20 #include <log/logger_manager.h>
21 #include <log/logger_name.h>
22 #include <cfgrpt/config_report.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 
43 // Refer to config_report so it will be embedded in the binary
45 
48 const char* LFCController::lfc_app_name_ = "DhcpLFC";
49 
51 const char* LFCController::lfc_bin_name_ = "kea-lfc";
52 
53 LFCController::LFCController()
54  : protocol_version_(0), verbose_(false), config_file_(""), previous_file_(""),
55  copy_file_(""), output_file_(""), finish_file_(""), pid_file_("") {
56 }
57 
59 }
60 
61 void
62 LFCController::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) {
134  LOG_FATAL(lfc_logger, LFC_FAIL_ROTATE).arg(run_ex.what());
135  }
136  }
137 
138  // delete the pid file for this instance
139  try {
140  pid_file.deleteFile();
141  } catch (const PIDFileError& pid_ex) {
142  LOG_FATAL(lfc_logger, LFC_FAIL_PID_DEL).arg(pid_ex.what());
143  }
144 
146 }
147 
148 void
149 LFCController::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 
300 void
301 LFCController::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 
322 std::string
323 LFCController::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 
345 template<typename LeaseObjectType, typename LeaseFileType, typename StorageType>
346 void
347 LFCController::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 
387 void
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 
411 void
412 LFCController::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
426  keaLoggerDbglevel(0));
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
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
const char *const config_report[]
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
const char *const * lfc_config_report
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