Kea  1.5.0
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 
83  LOG_INFO(lfc_logger, LFC_START);
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
91  LOG_FATAL(lfc_logger, LFC_RUNNING);
92  return;
93  }
94 
95  // create the pid file for this instance
96  pid_file.write();
97  } catch (const PIDFileError& pid_ex) {
98  LOG_FATAL(lfc_logger, LFC_FAIL_PID_CREATE).arg(pid_ex.what());
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()) {
107  LOG_INFO(lfc_logger, LFC_PROCESSING)
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) {
129  LOG_INFO(lfc_logger, LFC_ROTATING);
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 
145  LOG_INFO(lfc_logger, LFC_TERMINATE);
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
359  LOG_INFO(lfc_logger, LFC_READ_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 
364  LOG_INFO(lfc_logger, LFC_WRITE_STATS)
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
logger_manager.h
isc::dhcp::Memfile_LeaseMgr::getDBVersion
static std::string getDBVersion()
Local version of getDBVersion() class method.
Definition: memfile_lease_mgr.cc:687
isc::log
Definition: buffer_appender_impl.cc:17
isc::lfc::InvalidUsage
Exception thrown when the command line is invalid.
Definition: lfc_controller.h:17
isc::lfc::LFCController::getPreviousFile
std::string getPreviousFile() const
Gets the previous file name.
Definition: lfc_controller.h:122
isc::log::LoggerSpecification
Definition: logger_specification.h:29
isc::log::getRootLoggerName
const std::string & getRootLoggerName()
Get root logger name.
Definition: logger_name.cc:33
lfc_log.h
lease_file_loader.h
config_report.h
isc::util::PIDFile
Class to help with processing PID files.
Definition: pid_file.h:40
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
memfile_lease_storage.h
csv_lease_file6.h
isc::log::OutputOption
Definition: output_option.h:37
isc::util::CSVFile::exists
bool exists() const
Checks if the CSV file exists and can be opened for reading.
Definition: csv_file.cc:102
isc::log::keaLoggerDbglevel
int keaLoggerDbglevel(int defdbglevel)
Obtains logging debug level from KEA_LOGGER_DBGLEVEL.
Definition: logger_unittest_support.cc:44
csv_lease_file4.h
isc::lfc::LFCController::getOutputFile
std::string getOutputFile() const
Gets the output file name.
Definition: lfc_controller.h:136
pid_file.h
lease_mgr.h
An abstract API for lease database.
isc::util
Definition: edns.h:19
isc
Defines the logger used by the top-level component of kea-dhcp-ddns.
Definition: agent_parser.cc:144
isc::log::LoggerManager
Logger Manager.
Definition: logger_manager.h:38
isc::util::PIDFileError
Exception thrown when an error occurs during PID file processing.
Definition: pid_file.h:20
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_throw
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
Definition: exceptions/exceptions.h:192
isc::util::PIDFile::write
void write(int) const
Write the PID to the file.
Definition: pid_file.cc:63
isc::dhcp
Definition: ctrl_dhcp4_srv.cc:75
LOG_FATAL
#define LOG_FATAL(LOGGER, MESSAGE)
Macro to conveniently test fatal output and log it.
Definition: macros.h:38
isc::util::CSVFile
Provides input/output access to CSV files.
Definition: csv_file.h:290
memfile_lease_mgr.h
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:43
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:62
isc::util::PIDFile::check
int check() const
Read the PID in from the file and check it.
Definition: pid_file.cc:26
isc::lfc::LFCController::~LFCController
~LFCController()
Destructor.
Definition: lfc_controller.cc:58
isc::lfc::LFCController::getFinishFile
std::string getFinishFile() const
Gets the finish file name.
Definition: lfc_controller.h:143
isc::lfc::lfc_config_report
const char *const * lfc_config_report
Definition: lfc_controller.cc:44
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::log::LoggerManager::process
void process(T start, T finish)
Process Specifications.
Definition: logger_manager.h:55
isc::detail::config_report
const char *const config_report[]
isc::util::PIDFile::deleteFile
void deleteFile() const
Delete the PID file.
Definition: pid_file.cc:84
isc::lfc::LFCController::getProtocolVersion
int getProtocolVersion() const
Gets the protocol version of the leases files.
Definition: lfc_controller.h:108
logger_name.h
isc::detail::getConfigReport
std::string getConfigReport()
Definition: cfgrpt.cc:20
exceptions.h
isc::lfc::LFCController::fileRotate
void fileRotate() const
Rotate files.
Definition: lfc_controller.cc:378
lfc_controller.h
isc::log::INFO
@ INFO
Definition: logger_level.h:26
isc::log::OutputOption::destination
Destination destination
Members.
Definition: output_option.h:61
isc::lfc::LFCController::getCopyFile
std::string getCopyFile() const
Gets the copy file name.
Definition: lfc_controller.h:129
isc::log::keaLoggerSeverity
isc::log::Severity keaLoggerSeverity(isc::log::Severity defseverity)
Obtains logging severity from KEA_LOGGER_SEVERITY.
Definition: logger_unittest_support.cc:32
LOG_INFO
#define LOG_INFO(LOGGER, MESSAGE)
Macro to conveniently test info output and log it.
Definition: macros.h:20
isc::lfc::LFCController::parseArgs
void parseArgs(int argc, char *argv[])
Process the command line arguments.
Definition: lfc_controller.cc:149
isc::lfc::lfc_logger
isc::log::Logger lfc_logger("DhcpLFC")
Defines the logger used within LFC.
Definition: lfc_log.h:18
isc::lfc::RunTimeFail
Exceptions thrown when a method is unable to manipulate (remove or rename) a file.
Definition: lfc_controller.h:25