d_controller.cc 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477
  1. // Copyright (C) 2013-2015 Internet Systems Consortium, Inc. ("ISC")
  2. //
  3. // Permission to use, copy, modify, and/or distribute this software for any
  4. // purpose with or without fee is hereby granted, provided that the above
  5. // copyright notice and this permission notice appear in all copies.
  6. //
  7. // THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
  8. // REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
  9. // AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
  10. // INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
  11. // LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
  12. // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
  13. // PERFORMANCE OF THIS SOFTWARE.
  14. #include <config.h>
  15. #include <d2/d2_log.h>
  16. #include <config/ccsession.h>
  17. #include <d2/d_controller.h>
  18. #include <exceptions/exceptions.h>
  19. #include <log/logger_support.h>
  20. #include <dhcpsrv/cfgmgr.h>
  21. #include <cryptolink/cryptolink.h>
  22. #include <log/logger.h>
  23. #include <cfgrpt/config_report.h>
  24. #include <sstream>
  25. #include <unistd.h>
  26. namespace isc {
  27. namespace d2 {
  28. DControllerBasePtr DControllerBase::controller_;
  29. // Note that the constructor instantiates the controller's primary IOService.
  30. DControllerBase::DControllerBase(const char* app_name, const char* bin_name)
  31. : app_name_(app_name), bin_name_(bin_name),
  32. verbose_(false), spec_file_name_(""),
  33. io_service_(new isc::asiolink::IOService()),
  34. signal_set_(), io_signal_queue_() {
  35. }
  36. void
  37. DControllerBase::setController(const DControllerBasePtr& controller) {
  38. if (controller_) {
  39. // This shouldn't happen, but let's make sure it can't be done.
  40. // It represents a programmatic error.
  41. isc_throw (DControllerBaseError,
  42. "Multiple controller instances attempted.");
  43. }
  44. controller_ = controller;
  45. }
  46. void
  47. DControllerBase::launch(int argc, char* argv[], const bool test_mode) {
  48. // Step 1 is to parse the command line arguments.
  49. try {
  50. parseArgs(argc, argv);
  51. } catch (const InvalidUsage& ex) {
  52. usage(ex.what());
  53. throw; // rethrow it
  54. }
  55. // It is important that we set a default logger name because this name
  56. // will be used when the user doesn't provide the logging configuration
  57. // in the Kea configuration file.
  58. isc::dhcp::CfgMgr::instance().setDefaultLoggerName(bin_name_);
  59. // Logger's default configuration depends on whether we are in the
  60. // verbose mode or not. CfgMgr manages the logger configuration so
  61. // the verbose mode is set for CfgMgr.
  62. isc::dhcp::CfgMgr::instance().setVerbose(verbose_);
  63. // Do not initialize logger here if we are running unit tests. It would
  64. // replace an instance of unit test specific logger.
  65. if (!test_mode) {
  66. // Now that we know what the mode flags are, we can init logging.
  67. Daemon::loggerInit(bin_name_.c_str(), verbose_);
  68. }
  69. // Log the starting of the service. Although this is the controller
  70. // module, use a "DHCP_DDNS_" prefix to the module (to conform to the
  71. // principle of least astonishment).
  72. LOG_INFO(dctl_logger, DHCP_DDNS_STARTING).arg(getpid()).arg(VERSION);
  73. try {
  74. // Step 2 is to create and initialize the application process object.
  75. initProcess();
  76. } catch (const std::exception& ex) {
  77. LOG_FATAL(dctl_logger, DCTL_INIT_PROCESS_FAIL)
  78. .arg(app_name_).arg(ex.what());
  79. isc_throw (ProcessInitError,
  80. "Application Process initialization failed: " << ex.what());
  81. }
  82. LOG_DEBUG(dctl_logger, DBGLVL_START_SHUT, DCTL_STANDALONE).arg(app_name_);
  83. // Step 3 is to load configuration from file.
  84. int rcode;
  85. isc::data::ConstElementPtr comment
  86. = isc::config::parseAnswer(rcode, configFromFile());
  87. if (rcode != 0) {
  88. LOG_FATAL(dctl_logger, DCTL_CONFIG_FILE_LOAD_FAIL)
  89. .arg(app_name_).arg(comment->stringValue());
  90. isc_throw (ProcessInitError, "Could Not load configration file: "
  91. << comment->stringValue());
  92. }
  93. // Everything is clear for launch, so start the application's
  94. // event loop.
  95. try {
  96. // Now that we have a proces, we can set up signal handling.
  97. initSignalHandling();
  98. runProcess();
  99. } catch (const std::exception& ex) {
  100. LOG_FATAL(dctl_logger, DCTL_PROCESS_FAILED)
  101. .arg(app_name_).arg(ex.what());
  102. isc_throw (ProcessRunError,
  103. "Application process event loop failed: " << ex.what());
  104. }
  105. // All done, so bail out. Log the event (using a DHCP_DDNS_ prefix
  106. // for the same reason as used for DHCP_DDNS_STARTING).
  107. LOG_INFO(dctl_logger, DHCP_DDNS_SHUTDOWN);
  108. }
  109. void
  110. DControllerBase::parseArgs(int argc, char* argv[])
  111. {
  112. // Iterate over the given command line options. If its a stock option
  113. // ("s" or "v") handle it here. If its a valid custom option, then
  114. // invoke customOption.
  115. int ch;
  116. opterr = 0;
  117. optind = 1;
  118. std::string opts("dvVc:" + getCustomOpts());
  119. while ((ch = getopt(argc, argv, opts.c_str())) != -1) {
  120. switch (ch) {
  121. case 'd':
  122. // Enables verbose logging.
  123. verbose_ = true;
  124. break;
  125. case 'v':
  126. // gather Kea version and throw so main() can catch and return
  127. // rather than calling exit() here which disrupts gtest.
  128. isc_throw(VersionMessage, getVersion(false));
  129. break;
  130. case 'V':
  131. // gather Kea version and throw so main() can catch and return
  132. // rather than calling exit() here which disrupts gtest.
  133. isc_throw(VersionMessage, getVersion(true));
  134. break;
  135. case 'c':
  136. // config file name
  137. if (optarg == NULL) {
  138. isc_throw(InvalidUsage, "configuration file name missing");
  139. }
  140. Daemon::init(optarg);
  141. break;
  142. case '?': {
  143. // We hit an invalid option.
  144. isc_throw(InvalidUsage, "unsupported option: ["
  145. << static_cast<char>(optopt) << "] "
  146. << (!optarg ? "" : optarg));
  147. break;
  148. }
  149. default:
  150. // We hit a valid custom option
  151. if (!customOption(ch, optarg)) {
  152. // This would be a programmatic error.
  153. isc_throw(InvalidUsage, " Option listed but implemented?: ["
  154. << static_cast<char>(ch) << "] "
  155. << (!optarg ? "" : optarg));
  156. }
  157. break;
  158. }
  159. }
  160. // There was too much information on the command line.
  161. if (argc > optind) {
  162. isc_throw(InvalidUsage, "extraneous command line information");
  163. }
  164. }
  165. bool
  166. DControllerBase::customOption(int /* option */, char* /*optarg*/)
  167. {
  168. // Default implementation returns false.
  169. return (false);
  170. }
  171. void
  172. DControllerBase::initProcess() {
  173. LOG_DEBUG(dctl_logger, DBGLVL_START_SHUT, DCTL_INIT_PROCESS).arg(app_name_);
  174. // Invoke virtual method to instantiate the application process.
  175. try {
  176. process_.reset(createProcess());
  177. } catch (const std::exception& ex) {
  178. isc_throw(DControllerBaseError, std::string("createProcess failed: ")
  179. + ex.what());
  180. }
  181. // This is pretty unlikely, but will test for it just to be safe..
  182. if (!process_) {
  183. isc_throw(DControllerBaseError, "createProcess returned NULL");
  184. }
  185. // Invoke application's init method (Note this call should throw
  186. // DProcessBaseError if it fails).
  187. process_->init();
  188. }
  189. isc::data::ConstElementPtr
  190. DControllerBase::configFromFile() {
  191. // Rollback any previous staging configuration. For D2, only a
  192. // logger configuration is used here.
  193. isc::dhcp::CfgMgr::instance().rollback();
  194. // Will hold configuration.
  195. isc::data::ConstElementPtr module_config;
  196. // Will receive configuration result.
  197. isc::data::ConstElementPtr answer;
  198. try {
  199. std::string config_file = getConfigFile();
  200. if (config_file.empty()) {
  201. // Basic sanity check: file name must not be empty.
  202. isc_throw(BadValue, "JSON configuration file not specified. Please "
  203. "use -c command line option.");
  204. }
  205. // Read contents of the file and parse it as JSON
  206. isc::data::ConstElementPtr whole_config =
  207. isc::data::Element::fromJSONFile(config_file, true);
  208. // Let's configure logging before applying the configuration,
  209. // so we can log things during configuration process.
  210. // Temporary storage for logging configuration
  211. isc::dhcp::SrvConfigPtr storage =
  212. isc::dhcp::CfgMgr::instance().getStagingCfg();
  213. // Get 'Logging' element from the config and use it to set up
  214. // logging. If there's no such element, we'll just pass NULL.
  215. Daemon::configureLogger(whole_config->get("Logging"), storage);
  216. // Extract derivation-specific portion of the configuration.
  217. module_config = whole_config->get(getAppName());
  218. if (!module_config) {
  219. isc_throw(BadValue, "Config file " << config_file <<
  220. " does not include '" <<
  221. getAppName() << "' entry.");
  222. }
  223. answer = updateConfig(module_config);
  224. int rcode = 0;
  225. isc::config::parseAnswer(rcode, answer);
  226. if (!rcode) {
  227. // Configuration successful, so apply the logging configuration
  228. // to log4cplus.
  229. isc::dhcp::CfgMgr::instance().getStagingCfg()->applyLoggingCfg();
  230. isc::dhcp::CfgMgr::instance().commit();
  231. }
  232. } catch (const std::exception& ex) {
  233. // Rollback logging configuration.
  234. isc::dhcp::CfgMgr::instance().rollback();
  235. // build an error result
  236. isc::data::ConstElementPtr error =
  237. isc::config::createAnswer(1,
  238. std::string("Configuration parsing failed: ") + ex.what());
  239. return (error);
  240. }
  241. return (answer);
  242. }
  243. void
  244. DControllerBase::runProcess() {
  245. LOG_DEBUG(dctl_logger, DBGLVL_START_SHUT, DCTL_RUN_PROCESS).arg(app_name_);
  246. if (!process_) {
  247. // This should not be possible.
  248. isc_throw(DControllerBaseError, "Process not initialized");
  249. }
  250. // Invoke the application process's run method. This may throw
  251. // DProcessBaseError
  252. process_->run();
  253. }
  254. // Instance method for handling new config
  255. isc::data::ConstElementPtr
  256. DControllerBase::updateConfig(isc::data::ConstElementPtr new_config) {
  257. return (process_->configure(new_config));
  258. }
  259. // Instance method for executing commands
  260. isc::data::ConstElementPtr
  261. DControllerBase::executeCommand(const std::string& command,
  262. isc::data::ConstElementPtr args) {
  263. // Shutdown is universal. If its not that, then try it as
  264. // an custom command supported by the derivation. If that
  265. // doesn't pan out either, than send to it the application
  266. // as it may be supported there.
  267. isc::data::ConstElementPtr answer;
  268. if (command.compare(SHUT_DOWN_COMMAND) == 0) {
  269. answer = shutdownProcess(args);
  270. } else {
  271. // It wasn't shutdown, so may be a custom controller command.
  272. int rcode = 0;
  273. answer = customControllerCommand(command, args);
  274. isc::config::parseAnswer(rcode, answer);
  275. if (rcode == COMMAND_INVALID)
  276. {
  277. // It wasn't controller command, so may be an application command.
  278. answer = process_->command(command, args);
  279. }
  280. }
  281. return (answer);
  282. }
  283. isc::data::ConstElementPtr
  284. DControllerBase::customControllerCommand(const std::string& command,
  285. isc::data::ConstElementPtr /* args */) {
  286. // Default implementation always returns invalid command.
  287. return (isc::config::createAnswer(COMMAND_INVALID,
  288. "Unrecognized command: " + command));
  289. }
  290. isc::data::ConstElementPtr
  291. DControllerBase::shutdownProcess(isc::data::ConstElementPtr args) {
  292. if (process_) {
  293. return (process_->shutdown(args));
  294. }
  295. // Not really a failure, but this condition is worth noting. In reality
  296. // it should be pretty hard to cause this.
  297. LOG_WARN(dctl_logger, DCTL_NOT_RUNNING).arg(app_name_);
  298. return (isc::config::createAnswer(0, "Process has not been initialzed."));
  299. }
  300. void
  301. DControllerBase::initSignalHandling() {
  302. /// @todo block everything we don't handle
  303. // Create our signal queue.
  304. io_signal_queue_.reset(new IOSignalQueue(io_service_));
  305. // Install the on-receipt handler
  306. util::SignalSet::setOnReceiptHandler(boost::bind(&DControllerBase::
  307. osSignalHandler,
  308. this, _1));
  309. // Register for the signals we wish to handle.
  310. signal_set_.reset(new util::SignalSet(SIGHUP,SIGINT,SIGTERM));
  311. }
  312. bool
  313. DControllerBase::osSignalHandler(int signum) {
  314. // Create a IOSignal to propagate the signal to IOService.
  315. io_signal_queue_->pushSignal(signum, boost::bind(&DControllerBase::
  316. ioSignalHandler,
  317. this, _1));
  318. return (true);
  319. }
  320. void
  321. DControllerBase::ioSignalHandler(IOSignalId sequence_id) {
  322. // Pop the signal instance off the queue. This should make us
  323. // the only one holding it, so when we leave it should be freed.
  324. // (note that popSignal will throw if signal is not found, which
  325. // in turn will caught, logged, and swallowed by IOSignal callback
  326. // invocation code.)
  327. IOSignalPtr io_signal = io_signal_queue_->popSignal(sequence_id);
  328. // Now call virtual signal processing method.
  329. processSignal(io_signal->getSignum());
  330. }
  331. void
  332. DControllerBase::processSignal(int signum) {
  333. switch (signum) {
  334. case SIGHUP:
  335. {
  336. LOG_INFO(dctl_logger, DHCP_DDNS_CFG_FILE_RELOAD_SIGNAL_RECVD)
  337. .arg(signum).arg(getConfigFile());
  338. int rcode;
  339. isc::data::ConstElementPtr comment = isc::config::
  340. parseAnswer(rcode,
  341. configFromFile());
  342. if (rcode != 0) {
  343. LOG_ERROR(dctl_logger, DHCP_DDNS_CFG_FILE_RELOAD_ERROR)
  344. .arg(comment->stringValue());
  345. }
  346. break;
  347. }
  348. case SIGINT:
  349. case SIGTERM:
  350. {
  351. LOG_DEBUG(dctl_logger, DBGLVL_START_SHUT,
  352. DHCP_DDNS_SHUTDOWN_SIGNAL_RECVD).arg(signum);
  353. isc::data::ElementPtr arg_set;
  354. executeCommand(SHUT_DOWN_COMMAND, arg_set);
  355. break;
  356. }
  357. default:
  358. LOG_WARN(dctl_logger, DHCP_DDNS_UNSUPPORTED_SIGNAL).arg(signum);
  359. break;
  360. }
  361. }
  362. void
  363. DControllerBase::usage(const std::string & text)
  364. {
  365. if (text != "") {
  366. std::cerr << "Usage error: " << text << std::endl;
  367. }
  368. std::cerr << "Usage: " << bin_name_ << std::endl
  369. << " -c <config file name> : mandatory,"
  370. << " specifies name of configuration file " << std::endl
  371. << " -d: optional, verbose output " << std::endl
  372. << " -v: print version number and exit" << std::endl
  373. << " -V: print extended version information and exit"
  374. << std::endl;
  375. // add any derivation specific usage
  376. std::cerr << getUsageText() << std::endl;
  377. }
  378. DControllerBase::~DControllerBase() {
  379. }
  380. // Refer to config_report so it will be embedded in the binary
  381. const char* const* d2_config_report = isc::detail::config_report;
  382. std::string
  383. DControllerBase::getVersion(bool extended) {
  384. std::stringstream tmp;
  385. tmp << VERSION;
  386. if (extended) {
  387. tmp << std::endl << EXTENDED_VERSION << std::endl;
  388. tmp << "linked with " << log::Logger::getVersion() << std::endl;
  389. tmp << "and " << cryptolink::CryptoLink::getVersion()
  390. << std::endl;
  391. #ifdef HAVE_MYSQL
  392. tmp << "database: MySQL";
  393. #else
  394. #ifdef HAVE_PGSQL
  395. tmp << "database: PostgreSQL";
  396. #else
  397. tmp << "no database";
  398. #endif
  399. #endif
  400. // @todo: more details about database runtime
  401. }
  402. return (tmp.str());
  403. }
  404. }; // namespace isc::d2
  405. }; // namespace isc