d_controller.cc 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590
  1. // Copyright (C) 2013-2017 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. #include <config.h>
  7. #include <cc/command_interpreter.h>
  8. #include <cfgrpt/config_report.h>
  9. #include <cryptolink/cryptolink.h>
  10. #include <dhcpsrv/cfgmgr.h>
  11. #include <exceptions/exceptions.h>
  12. #include <log/logger.h>
  13. #include <log/logger_support.h>
  14. #include <process/d_log.h>
  15. #include <process/d_controller.h>
  16. #ifdef HAVE_MYSQL
  17. #include <dhcpsrv/mysql_lease_mgr.h>
  18. #endif
  19. #ifdef HAVE_PGSQL
  20. #include <dhcpsrv/pgsql_lease_mgr.h>
  21. #endif
  22. #ifdef HAVE_CQL
  23. #include <dhcpsrv/cql_lease_mgr.h>
  24. #endif
  25. #include <dhcpsrv/memfile_lease_mgr.h>
  26. #include <sstream>
  27. #include <unistd.h>
  28. namespace isc {
  29. namespace process {
  30. DControllerBasePtr DControllerBase::controller_;
  31. // Note that the constructor instantiates the controller's primary IOService.
  32. DControllerBase::DControllerBase(const char* app_name, const char* bin_name)
  33. : app_name_(app_name), bin_name_(bin_name),
  34. verbose_(false), check_only_(false), spec_file_name_(""),
  35. io_service_(new isc::asiolink::IOService()),
  36. io_signal_queue_() {
  37. }
  38. void
  39. DControllerBase::setController(const DControllerBasePtr& controller) {
  40. if (controller_) {
  41. // This shouldn't happen, but let's make sure it can't be done.
  42. // It represents a programmatic error.
  43. isc_throw (DControllerBaseError,
  44. "Multiple controller instances attempted.");
  45. }
  46. controller_ = controller;
  47. }
  48. isc::data::ConstElementPtr
  49. DControllerBase::parseFile(const std::string&) {
  50. isc::data::ConstElementPtr elements;
  51. return (elements);
  52. }
  53. void
  54. DControllerBase::launch(int argc, char* argv[], const bool test_mode) {
  55. // Step 1 is to parse the command line arguments.
  56. try {
  57. parseArgs(argc, argv);
  58. } catch (const InvalidUsage& ex) {
  59. usage(ex.what());
  60. // rethrow it with an empty message
  61. isc_throw(InvalidUsage, "");
  62. }
  63. setProcName(bin_name_);
  64. if (isCheckOnly()) {
  65. checkConfigOnly();
  66. return;
  67. }
  68. // It is important that we set a default logger name because this name
  69. // will be used when the user doesn't provide the logging configuration
  70. // in the Kea configuration file.
  71. isc::dhcp::CfgMgr::instance().setDefaultLoggerName(bin_name_);
  72. // Logger's default configuration depends on whether we are in the
  73. // verbose mode or not. CfgMgr manages the logger configuration so
  74. // the verbose mode is set for CfgMgr.
  75. isc::dhcp::CfgMgr::instance().setVerbose(verbose_);
  76. // Do not initialize logger here if we are running unit tests. It would
  77. // replace an instance of unit test specific logger.
  78. if (!test_mode) {
  79. // Now that we know what the mode flags are, we can init logging.
  80. Daemon::loggerInit(bin_name_.c_str(), verbose_);
  81. }
  82. try {
  83. createPIDFile();
  84. } catch (const dhcp::DaemonPIDExists& ex) {
  85. LOG_FATAL(dctl_logger, DCTL_ALREADY_RUNNING)
  86. .arg(bin_name_).arg(ex.what());
  87. isc_throw (LaunchError, "Launch Failed: " << ex.what());
  88. } catch (const std::exception& ex) {
  89. LOG_FATAL(dctl_logger, DCTL_PID_FILE_ERROR)
  90. .arg(app_name_).arg(ex.what());
  91. isc_throw (LaunchError, "Launch failed: " << ex.what());
  92. }
  93. // Log the starting of the service.
  94. LOG_INFO(dctl_logger, DCTL_STARTING)
  95. .arg(app_name_).arg(getpid()).arg(VERSION);
  96. try {
  97. // Step 2 is to create and initialize the application process object.
  98. initProcess();
  99. } catch (const std::exception& ex) {
  100. LOG_FATAL(dctl_logger, DCTL_INIT_PROCESS_FAIL)
  101. .arg(app_name_).arg(ex.what());
  102. isc_throw (ProcessInitError,
  103. "Application Process initialization failed: " << ex.what());
  104. }
  105. LOG_DEBUG(dctl_logger, isc::log::DBGLVL_START_SHUT, DCTL_STANDALONE)
  106. .arg(app_name_);
  107. // Step 3 is to load configuration from file.
  108. int rcode;
  109. isc::data::ConstElementPtr comment
  110. = isc::config::parseAnswer(rcode, configFromFile());
  111. if (rcode != 0) {
  112. LOG_FATAL(dctl_logger, DCTL_CONFIG_FILE_LOAD_FAIL)
  113. .arg(app_name_).arg(comment->stringValue());
  114. isc_throw (ProcessInitError, "Could Not load configuration file: "
  115. << comment->stringValue());
  116. }
  117. // Everything is clear for launch, so start the application's
  118. // event loop.
  119. try {
  120. // Now that we have a proces, we can set up signal handling.
  121. initSignalHandling();
  122. runProcess();
  123. } catch (const std::exception& ex) {
  124. LOG_FATAL(dctl_logger, DCTL_PROCESS_FAILED)
  125. .arg(app_name_).arg(ex.what());
  126. isc_throw (ProcessRunError,
  127. "Application process event loop failed: " << ex.what());
  128. }
  129. // All done, so bail out.
  130. LOG_INFO(dctl_logger, DCTL_SHUTDOWN)
  131. .arg(app_name_).arg(getpid()).arg(VERSION);
  132. }
  133. void
  134. DControllerBase::checkConfigOnly() {
  135. try {
  136. // We need to initialize logging, in case any error
  137. // messages are to be printed.
  138. // This is just a test, so we don't care about lockfile.
  139. setenv("KEA_LOCKFILE_DIR", "none", 0);
  140. isc::dhcp::CfgMgr::instance().setDefaultLoggerName(bin_name_);
  141. isc::dhcp::CfgMgr::instance().setVerbose(verbose_);
  142. Daemon::loggerInit(bin_name_.c_str(), verbose_);
  143. // Check the syntax first.
  144. std::string config_file = getConfigFile();
  145. if (config_file.empty()) {
  146. // Basic sanity check: file name must not be empty.
  147. isc_throw(InvalidUsage, "JSON configuration file not specified");
  148. }
  149. isc::data::ConstElementPtr whole_config = parseFile(config_file);
  150. if (!whole_config) {
  151. // No fallback to fromJSONFile
  152. isc_throw(InvalidUsage, "No configuration found");
  153. }
  154. if (verbose_) {
  155. std::cerr << "Syntax check OK" << std::endl;
  156. }
  157. // Check the logic next.
  158. isc::data::ConstElementPtr module_config;
  159. module_config = whole_config->get(getAppName());
  160. if (!module_config) {
  161. isc_throw(InvalidUsage, "Config file " << config_file <<
  162. " does not include '" << getAppName() << "' entry");
  163. }
  164. // Get an application process object.
  165. initProcess();
  166. isc::data::ConstElementPtr answer;
  167. answer = checkConfig(module_config);
  168. int rcode = 0;
  169. answer = isc::config::parseAnswer(rcode, answer);
  170. if (rcode != 0) {
  171. isc_throw(InvalidUsage, "Error encountered: "
  172. << answer->stringValue());
  173. }
  174. } catch (const VersionMessage&) {
  175. throw;
  176. } catch (const InvalidUsage&) {
  177. throw;
  178. } catch (const std::exception& ex) {
  179. isc_throw(InvalidUsage, "Syntax check failed with: " << ex.what());
  180. }
  181. return;
  182. }
  183. void
  184. DControllerBase::parseArgs(int argc, char* argv[])
  185. {
  186. // Iterate over the given command line options. If its a stock option
  187. // ("c" or "d") handle it here. If its a valid custom option, then
  188. // invoke customOption.
  189. int ch;
  190. opterr = 0;
  191. optind = 1;
  192. std::string opts("dvVWc:t:" + getCustomOpts());
  193. while ((ch = getopt(argc, argv, opts.c_str())) != -1) {
  194. switch (ch) {
  195. case 'd':
  196. // Enables verbose logging.
  197. verbose_ = true;
  198. break;
  199. case 'v':
  200. // gather Kea version and throw so main() can catch and return
  201. // rather than calling exit() here which disrupts gtest.
  202. isc_throw(VersionMessage, getVersion(false));
  203. break;
  204. case 'V':
  205. // gather Kea version and throw so main() can catch and return
  206. // rather than calling exit() here which disrupts gtest.
  207. isc_throw(VersionMessage, getVersion(true));
  208. break;
  209. case 'W':
  210. // gather Kea config report and throw so main() can catch and
  211. // return rather than calling exit() here which disrupts gtest.
  212. isc_throw(VersionMessage, isc::detail::getConfigReport());
  213. break;
  214. case 'c':
  215. case 't':
  216. // config file name
  217. if (optarg == NULL) {
  218. isc_throw(InvalidUsage, "configuration file name missing");
  219. }
  220. setConfigFile(optarg);
  221. if (ch == 't') {
  222. check_only_ = true;
  223. }
  224. break;
  225. case '?': {
  226. // We hit an invalid option.
  227. isc_throw(InvalidUsage, "unsupported option: ["
  228. << static_cast<char>(optopt) << "] "
  229. << (!optarg ? "" : optarg));
  230. break;
  231. }
  232. default:
  233. // We hit a valid custom option
  234. if (!customOption(ch, optarg)) {
  235. // This would be a programmatic error.
  236. isc_throw(InvalidUsage, " Option listed but implemented?: ["
  237. << static_cast<char>(ch) << "] "
  238. << (!optarg ? "" : optarg));
  239. }
  240. break;
  241. }
  242. }
  243. // There was too much information on the command line.
  244. if (argc > optind) {
  245. isc_throw(InvalidUsage, "extraneous command line information");
  246. }
  247. }
  248. bool
  249. DControllerBase::customOption(int /* option */, char* /*optarg*/)
  250. {
  251. // Default implementation returns false.
  252. return (false);
  253. }
  254. void
  255. DControllerBase::initProcess() {
  256. LOG_DEBUG(dctl_logger, isc::log::DBGLVL_START_SHUT, DCTL_INIT_PROCESS)
  257. .arg(app_name_);
  258. // Invoke virtual method to instantiate the application process.
  259. try {
  260. process_.reset(createProcess());
  261. } catch (const std::exception& ex) {
  262. isc_throw(DControllerBaseError, std::string("createProcess failed: ")
  263. + ex.what());
  264. }
  265. // This is pretty unlikely, but will test for it just to be safe..
  266. if (!process_) {
  267. isc_throw(DControllerBaseError, "createProcess returned NULL");
  268. }
  269. // Invoke application's init method (Note this call should throw
  270. // DProcessBaseError if it fails).
  271. process_->init();
  272. }
  273. isc::data::ConstElementPtr
  274. DControllerBase::configFromFile() {
  275. // Rollback any previous staging configuration. For D2, only a
  276. // logger configuration is used here.
  277. isc::dhcp::CfgMgr::instance().rollback();
  278. // Will hold configuration.
  279. isc::data::ConstElementPtr module_config;
  280. // Will receive configuration result.
  281. isc::data::ConstElementPtr answer;
  282. try {
  283. std::string config_file = getConfigFile();
  284. if (config_file.empty()) {
  285. // Basic sanity check: file name must not be empty.
  286. isc_throw(BadValue, "JSON configuration file not specified. Please "
  287. "use -c command line option.");
  288. }
  289. // If parseFile returns an empty pointer, then pass the file onto the
  290. // original JSON parser.
  291. isc::data::ConstElementPtr whole_config = parseFile(config_file);
  292. if (!whole_config) {
  293. // Read contents of the file and parse it as JSON
  294. whole_config = isc::data::Element::fromJSONFile(config_file, true);
  295. }
  296. // Let's configure logging before applying the configuration,
  297. // so we can log things during configuration process.
  298. // Temporary storage for logging configuration
  299. isc::dhcp::SrvConfigPtr storage =
  300. isc::dhcp::CfgMgr::instance().getStagingCfg();
  301. // Get 'Logging' element from the config and use it to set up
  302. // logging. If there's no such element, we'll just pass NULL.
  303. Daemon::configureLogger(whole_config->get("Logging"), storage);
  304. // Extract derivation-specific portion of the configuration.
  305. module_config = whole_config->get(getAppName());
  306. if (!module_config) {
  307. isc_throw(BadValue, "Config file " << config_file <<
  308. " does not include '" <<
  309. getAppName() << "' entry.");
  310. }
  311. answer = updateConfig(module_config);
  312. int rcode = 0;
  313. isc::config::parseAnswer(rcode, answer);
  314. if (!rcode) {
  315. // Configuration successful, so apply the logging configuration
  316. // to log4cplus.
  317. isc::dhcp::CfgMgr::instance().getStagingCfg()->applyLoggingCfg();
  318. isc::dhcp::CfgMgr::instance().commit();
  319. }
  320. } catch (const std::exception& ex) {
  321. // Rollback logging configuration.
  322. isc::dhcp::CfgMgr::instance().rollback();
  323. // build an error result
  324. isc::data::ConstElementPtr error =
  325. isc::config::createAnswer(1,
  326. std::string("Configuration parsing failed: ") + ex.what());
  327. return (error);
  328. }
  329. return (answer);
  330. }
  331. void
  332. DControllerBase::runProcess() {
  333. LOG_DEBUG(dctl_logger, isc::log::DBGLVL_START_SHUT, DCTL_RUN_PROCESS)
  334. .arg(app_name_);
  335. if (!process_) {
  336. // This should not be possible.
  337. isc_throw(DControllerBaseError, "Process not initialized");
  338. }
  339. // Invoke the application process's run method. This may throw
  340. // DProcessBaseError
  341. process_->run();
  342. }
  343. // Instance method for handling new config
  344. isc::data::ConstElementPtr
  345. DControllerBase::updateConfig(isc::data::ConstElementPtr new_config) {
  346. return (process_->configure(new_config, false));
  347. }
  348. // Instance method for checking new config
  349. isc::data::ConstElementPtr
  350. DControllerBase::checkConfig(isc::data::ConstElementPtr new_config) {
  351. return (process_->configure(new_config, true));
  352. }
  353. // Instance method for executing commands
  354. isc::data::ConstElementPtr
  355. DControllerBase::executeCommand(const std::string& command,
  356. isc::data::ConstElementPtr args) {
  357. // Shutdown is universal. If its not that, then try it as
  358. // a custom command supported by the derivation. If that
  359. // doesn't pan out either, than send to it the application
  360. // as it may be supported there.
  361. isc::data::ConstElementPtr answer;
  362. if (command.compare(SHUT_DOWN_COMMAND) == 0) {
  363. answer = shutdownProcess(args);
  364. } else {
  365. // It wasn't shutdown, so it may be a custom controller command.
  366. int rcode = 0;
  367. answer = customControllerCommand(command, args);
  368. isc::config::parseAnswer(rcode, answer);
  369. if (rcode == COMMAND_INVALID)
  370. {
  371. // It wasn't a controller command, so it may be an application command.
  372. answer = process_->command(command, args);
  373. }
  374. }
  375. return (answer);
  376. }
  377. isc::data::ConstElementPtr
  378. DControllerBase::customControllerCommand(const std::string& command,
  379. isc::data::ConstElementPtr /* args */) {
  380. // Default implementation always returns invalid command.
  381. return (isc::config::createAnswer(COMMAND_INVALID,
  382. "Unrecognized command: " + command));
  383. }
  384. isc::data::ConstElementPtr
  385. DControllerBase::shutdownProcess(isc::data::ConstElementPtr args) {
  386. if (process_) {
  387. return (process_->shutdown(args));
  388. }
  389. // Not really a failure, but this condition is worth noting. In reality
  390. // it should be pretty hard to cause this.
  391. LOG_WARN(dctl_logger, DCTL_NOT_RUNNING).arg(app_name_);
  392. return (isc::config::createAnswer(0, "Process has not been initialized."));
  393. }
  394. void
  395. DControllerBase::initSignalHandling() {
  396. /// @todo block everything we don't handle
  397. // Create our signal queue.
  398. io_signal_queue_.reset(new IOSignalQueue(io_service_));
  399. // Install the on-receipt handler
  400. util::SignalSet::setOnReceiptHandler(boost::bind(&DControllerBase::
  401. osSignalHandler,
  402. this, _1));
  403. // Register for the signals we wish to handle.
  404. signal_set_.reset(new util::SignalSet(SIGHUP,SIGINT,SIGTERM));
  405. }
  406. bool
  407. DControllerBase::osSignalHandler(int signum) {
  408. // Create a IOSignal to propagate the signal to IOService.
  409. io_signal_queue_->pushSignal(signum, boost::bind(&DControllerBase::
  410. ioSignalHandler,
  411. this, _1));
  412. return (true);
  413. }
  414. void
  415. DControllerBase::ioSignalHandler(IOSignalId sequence_id) {
  416. // Pop the signal instance off the queue. This should make us
  417. // the only one holding it, so when we leave it should be freed.
  418. // (note that popSignal will throw if signal is not found, which
  419. // in turn will caught, logged, and swallowed by IOSignal callback
  420. // invocation code.)
  421. IOSignalPtr io_signal = io_signal_queue_->popSignal(sequence_id);
  422. // Now call virtual signal processing method.
  423. processSignal(io_signal->getSignum());
  424. }
  425. void
  426. DControllerBase::processSignal(int signum) {
  427. switch (signum) {
  428. case SIGHUP:
  429. {
  430. LOG_INFO(dctl_logger, DCTL_CFG_FILE_RELOAD_SIGNAL_RECVD)
  431. .arg(signum).arg(getConfigFile());
  432. int rcode;
  433. isc::data::ConstElementPtr comment = isc::config::
  434. parseAnswer(rcode,
  435. configFromFile());
  436. if (rcode != 0) {
  437. LOG_ERROR(dctl_logger, DCTL_CFG_FILE_RELOAD_ERROR)
  438. .arg(comment->stringValue());
  439. }
  440. break;
  441. }
  442. case SIGINT:
  443. case SIGTERM:
  444. {
  445. LOG_DEBUG(dctl_logger, isc::log::DBGLVL_START_SHUT,
  446. DCTL_SHUTDOWN_SIGNAL_RECVD).arg(signum);
  447. isc::data::ElementPtr arg_set;
  448. executeCommand(SHUT_DOWN_COMMAND, arg_set);
  449. break;
  450. }
  451. default:
  452. LOG_WARN(dctl_logger, DCTL_UNSUPPORTED_SIGNAL).arg(signum);
  453. break;
  454. }
  455. }
  456. void
  457. DControllerBase::usage(const std::string & text)
  458. {
  459. if (text != "") {
  460. std::cerr << "Usage error: " << text << std::endl;
  461. }
  462. std::cerr << "Usage: " << bin_name_ << std::endl
  463. << " -v: print version number and exit" << std::endl
  464. << " -V: print extended version information and exit"
  465. << std::endl
  466. << " -W: display the configuration report and exit"
  467. << std::endl
  468. << " -d: optional, verbose output " << std::endl
  469. << " -c <config file name> : mandatory,"
  470. << " specify name of configuration file" << std::endl
  471. << " -t <config file name> : check the"
  472. << " configuration file and exit" << std::endl;
  473. // add any derivation specific usage
  474. std::cerr << getUsageText() << std::endl;
  475. }
  476. DControllerBase::~DControllerBase() {
  477. }
  478. // Refer to config_report so it will be embedded in the binary
  479. const char* const* d2_config_report = isc::detail::config_report;
  480. std::string
  481. DControllerBase::getVersion(bool extended) {
  482. std::stringstream tmp;
  483. tmp << VERSION;
  484. if (extended) {
  485. tmp << std::endl << EXTENDED_VERSION << std::endl;
  486. tmp << "linked with:" << std::endl;
  487. tmp << isc::log::Logger::getVersion() << std::endl;
  488. tmp << isc::cryptolink::CryptoLink::getVersion() << std::endl;
  489. tmp << "database:" << std::endl;
  490. #ifdef HAVE_MYSQL
  491. tmp << isc::dhcp::MySqlLeaseMgr::getDBVersion() << std::endl;
  492. #endif
  493. #ifdef HAVE_PGSQL
  494. tmp << isc::dhcp::PgSqlLeaseMgr::getDBVersion() << std::endl;
  495. #endif
  496. #ifdef HAVE_CQL
  497. tmp << isc::dhcp::CqlLeaseMgr::getDBVersion() << std::endl;
  498. #endif
  499. tmp << isc::dhcp::Memfile_LeaseMgr::getDBVersion();
  500. // @todo: more details about database runtime
  501. }
  502. return (tmp.str());
  503. }
  504. }; // namespace isc::process
  505. }; // namespace isc