Kea  1.9.9-git
lfc_controller.cc
Go to the documentation of this file.
1 // Copyright (C) 2015-2018 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  version_stream << std::endl << EXTENDED_VERSION << std::endl
329  << "database: " << isc::dhcp::Memfile_LeaseMgr::getDBVersion();
330  }
331 
332  return (version_stream.str());
333 }
334 
335 template<typename LeaseObjectType, typename LeaseFileType, typename StorageType>
336 void
337 LFCController::processLeases() const {
338  StorageType storage;
339 
340  // If a previous file exists read the entries into storage
341  LeaseFileType lf_prev(getPreviousFile());
342  if (lf_prev.exists()) {
343  LeaseFileLoader::load<LeaseObjectType>(lf_prev, storage,
344  MAX_LEASE_ERRORS);
345  }
346 
347  // Follow that with the copy of the current lease file
348  LeaseFileType lf_copy(getCopyFile());
349  if (lf_copy.exists()) {
350  LeaseFileLoader::load<LeaseObjectType>(lf_copy, storage,
351  MAX_LEASE_ERRORS);
352  }
353 
354  // Write the result out to the output file
355  LeaseFileType lf_output(getOutputFile());
356  LeaseFileLoader::write<LeaseObjectType>(lf_output, storage);
357 
358  // If desired log the stats
360  .arg(lf_prev.getReadLeases() + lf_copy.getReadLeases())
361  .arg(lf_prev.getReads() + lf_copy.getReads())
362  .arg(lf_prev.getReadErrs() + lf_copy.getReadErrs());
363 
365  .arg(lf_output.getWriteLeases())
366  .arg(lf_output.getWrites())
367  .arg(lf_output.getWriteErrs());
368 
369  // Once we've finished the output file move it to the complete file
370  if (rename(getOutputFile().c_str(), getFinishFile().c_str()) != 0) {
371  isc_throw(RunTimeFail, "Unable to move output (" << output_file_
372  << ") to complete (" << finish_file_
373  << ") error: " << strerror(errno));
374  }
375 }
376 
377 void
379  // Remove the old previous file
380  if ((remove(getPreviousFile().c_str()) != 0) &&
381  (errno != ENOENT)) {
382  isc_throw(RunTimeFail, "Unable to delete previous file '"
383  << previous_file_ << "' error: " << strerror(errno));
384  }
385 
386  // Remove the copy file
387  if ((remove(getCopyFile().c_str()) != 0) &&
388  (errno != ENOENT)) {
389  isc_throw(RunTimeFail, "Unable to delete copy file '"
390  << copy_file_ << "' error: " << strerror(errno));
391  }
392 
393  // Rename the finish file to be the previous file
394  if (rename(finish_file_.c_str(), previous_file_.c_str()) != 0) {
395  isc_throw(RunTimeFail, "Unable to move finish (" << finish_file_
396  << ") to previous (" << previous_file_
397  << ") error: " << strerror(errno));
398  }
399 }
400 
401 void
402 LFCController::startLogger(const bool test_mode) const {
403  // If we are running in test mode use the environment variables
404  // else use our defaults
405  if (test_mode) {
406  initLogger();
407  }
408  else {
409  OutputOption option;
410  LoggerManager manager;
411 
412  initLogger(lfc_app_name_, INFO, 0, NULL, false);
413 
414  // Prepare the objects to define the logging specification
417  keaLoggerDbglevel(0));
418 
419  // If we are running in verbose (debugging) mode
420  // we send the output to the console, otherwise
421  // by default we send it to the SYSLOG
422  if (verbose_) {
423  option.destination = OutputOption::DEST_CONSOLE;
424  } else {
425  option.destination = OutputOption::DEST_SYSLOG;
426  }
427 
428  // ... and set the destination
429  spec.addOutputOption(option);
430 
431  manager.process(spec);
432  }
433 }
434 
435 }; // namespace isc::lfc
436 }; // namespace isc
const isc::log::MessageID LFC_FAIL_PID_DEL
Definition: lfc_messages.h:12
void deleteFile() const
Delete the PID file.
Definition: pid_file.cc:84
const std::string & getRootLoggerName()
Get root logger name.
Definition: logger_name.cc:33
int check() const
Read the PID in from the file and check it.
Definition: pid_file.cc:26
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 getProtocolVersion() const
Gets the protocol version of the leases files.
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
Logger Manager.
STL namespace.
Class to help with processing PID files.
Definition: pid_file.h:40
Exceptions thrown when a method is unable to manipulate (remove or rename) a file.
static std::string getDBVersion()
Local version of getDBVersion() class method.
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
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
std::string getCopyFile() const
Gets the copy file name.
Definition: edns.h:19
void parseArgs(int argc, char *argv[])
Process the command line arguments.
const char *const config_report[]
Definition: config_report.h:15
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
std::string getPreviousFile() const
Gets the previous file name.
const isc::log::MessageID LFC_FAIL_ROTATE
Definition: lfc_messages.h:14
void fileRotate() const
Rotate files.
virtual const char * what() const
Returns a C-style character string of the cause of the exception.
std::string getOutputFile() const
Gets the output file name.
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-dhcp-ddns.
const isc::log::MessageID LFC_TERMINATE
Definition: lfc_messages.h:20
const isc::log::MessageID LFC_ROTATING
Definition: lfc_messages.h:17
bool exists() const
Checks if the CSV file exists and can be opened for reading.
Definition: csv_file.cc:133
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 write(int) const
Write the PID to the file.
Definition: pid_file.cc:63
void initLogger(const string &root, isc::log::Severity severity, int dbglevel, const char *file, bool buffer)
Run-time initialization.
std::string getFinishFile() const
Gets the finish file name.
const isc::log::MessageID LFC_WRITE_STATS
Definition: lfc_messages.h:21