d2_process.cc 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406
  1. // Copyright (C) 2013-2014 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/ccsession.h>
  15. #include <d2/d2_log.h>
  16. #include <d2/d2_cfg_mgr.h>
  17. #include <d2/d2_process.h>
  18. #include <asio.hpp>
  19. namespace isc {
  20. namespace d2 {
  21. // Setting to 80% for now. This is an arbitrary choice and should probably
  22. // be configurable.
  23. const unsigned int D2Process::QUEUE_RESTART_PERCENT = 80;
  24. D2Process::D2Process(const char* name, IOServicePtr io_service)
  25. : DProcessBase(name, io_service, DCfgMgrBasePtr(new D2CfgMgr())),
  26. reconf_queue_flag_(false), shutdown_type_(SD_NORMAL) {
  27. // Instantiate queue manager. Note that queue manager does not start
  28. // listening at this point. That can only occur after configuration has
  29. // been received. This means that until we receive the configuration,
  30. // D2 will neither receive nor process NameChangeRequests.
  31. // Pass in IOService for NCR IO event processing.
  32. queue_mgr_.reset(new D2QueueMgr(getIoService()));
  33. // Instantiate update manager.
  34. // Pass in both queue manager and configuration manager.
  35. // Pass in IOService for DNS update transaction IO event processing.
  36. D2CfgMgrPtr tmp = getD2CfgMgr();
  37. update_mgr_.reset(new D2UpdateMgr(queue_mgr_, tmp, getIoService()));
  38. };
  39. void
  40. D2Process::init() {
  41. };
  42. void
  43. D2Process::run() {
  44. LOG_DEBUG(dctl_logger, DBGLVL_START_SHUT, DHCP_DDNS_RUN_ENTER);
  45. // Loop forever until we are allowed to shutdown.
  46. while (!canShutdown()) {
  47. try {
  48. // Check on the state of the request queue. Take any
  49. // actions necessary regarding it.
  50. checkQueueStatus();
  51. // Give update manager a time slice to queue new jobs and
  52. // process finished ones.
  53. update_mgr_->sweep();
  54. // Wait on IO event(s) - block until one or more of the following
  55. // has occurred:
  56. // a. NCR message has been received
  57. // b. Transaction IO has completed
  58. // c. Interval timer expired
  59. // d. Something stopped IO service (runIO returns 0)
  60. if (runIO() == 0) {
  61. // Pretty sure this amounts to an unexpected stop and we
  62. // should bail out now. Normal shutdowns do not utilize
  63. // stopping the IOService.
  64. isc_throw(DProcessBaseError,
  65. "Primary IO service stopped unexpectedly");
  66. }
  67. } catch (const std::exception& ex) {
  68. LOG_FATAL(dctl_logger, DHCP_DDNS_FAILED).arg(ex.what());
  69. isc_throw (DProcessBaseError,
  70. "Process run method failed: " << ex.what());
  71. }
  72. }
  73. // @todo - if queue isn't empty, we may need to persist its contents
  74. // this might be the place to do it, once there is a persistence mgr.
  75. // This may also be better in checkQueueStatus.
  76. LOG_DEBUG(dctl_logger, DBGLVL_START_SHUT, DHCP_DDNS_RUN_EXIT);
  77. };
  78. size_t
  79. D2Process::runIO() {
  80. // We want to block until at least one handler is called. We'll use
  81. // asio::io_service directly for two reasons. First off
  82. // asiolink::IOService::run_one is a void and asio::io_service::stopped
  83. // is not present in older versions of boost. We need to know if any
  84. // handlers ran or if the io_service was stopped. That latter represents
  85. // some form of error and the application cannot proceed with a stopped
  86. // service. Secondly, asiolink::IOService does not provide the poll
  87. // method. This is a handy method which runs all ready handlers without
  88. // blocking.
  89. IOServicePtr& io = getIoService();
  90. asio::io_service& asio_io_service = io->get_io_service();
  91. // Poll runs all that are ready. If none are ready it returns immediately
  92. // with a count of zero.
  93. size_t cnt = asio_io_service.poll();
  94. if (!cnt) {
  95. // Poll ran no handlers either none are ready or the service has been
  96. // stopped. Either way, call run_one to wait for a IO event. If the
  97. // service is stopped it will return immediately with a cnt of zero.
  98. cnt = asio_io_service.run_one();
  99. }
  100. return (cnt);
  101. }
  102. bool
  103. D2Process::canShutdown() const {
  104. bool all_clear = false;
  105. // If we have been told to shutdown, find out if we are ready to do so.
  106. if (shouldShutdown()) {
  107. switch (shutdown_type_) {
  108. case SD_NORMAL:
  109. // For a normal shutdown we need to stop the queue manager but
  110. // wait until we have finished all the transactions in progress.
  111. all_clear = (((queue_mgr_->getMgrState() != D2QueueMgr::RUNNING) &&
  112. (queue_mgr_->getMgrState() != D2QueueMgr::STOPPING))
  113. && (update_mgr_->getTransactionCount() == 0));
  114. break;
  115. case SD_DRAIN_FIRST:
  116. // For a drain first shutdown we need to stop the queue manager but
  117. // process all of the requests in the receive queue first.
  118. all_clear = (((queue_mgr_->getMgrState() != D2QueueMgr::RUNNING) &&
  119. (queue_mgr_->getMgrState() != D2QueueMgr::STOPPING))
  120. && (queue_mgr_->getQueueSize() == 0)
  121. && (update_mgr_->getTransactionCount() == 0));
  122. break;
  123. case SD_NOW:
  124. // Get out right now, no niceties.
  125. all_clear = true;
  126. break;
  127. default:
  128. // shutdown_type_ is an enum and should only be one of the above.
  129. // if its getting through to this, something is whacked.
  130. break;
  131. }
  132. if (all_clear) {
  133. LOG_INFO(dctl_logger,DHCP_DDNS_CLEARED_FOR_SHUTDOWN)
  134. .arg(getShutdownTypeStr(shutdown_type_));
  135. }
  136. }
  137. return (all_clear);
  138. }
  139. isc::data::ConstElementPtr
  140. D2Process::shutdown(isc::data::ConstElementPtr args) {
  141. LOG_INFO(dctl_logger, DHCP_DDNS_SHUTDOWN).arg(args ? args->str()
  142. : "(no args)");
  143. // Default shutdown type is normal.
  144. std::string type_str(getShutdownTypeStr(SD_NORMAL));
  145. shutdown_type_ = SD_NORMAL;
  146. if (args) {
  147. if ((args->getType() == isc::data::Element::map) &&
  148. args->contains("type")) {
  149. type_str = args->get("type")->stringValue();
  150. if (type_str == getShutdownTypeStr(SD_NORMAL)) {
  151. shutdown_type_ = SD_NORMAL;
  152. } else if (type_str == getShutdownTypeStr(SD_DRAIN_FIRST)) {
  153. shutdown_type_ = SD_DRAIN_FIRST;
  154. } else if (type_str == getShutdownTypeStr(SD_NOW)) {
  155. shutdown_type_ = SD_NOW;
  156. } else {
  157. setShutdownFlag(false);
  158. return (isc::config::createAnswer(1, "Invalid Shutdown type: "
  159. + type_str));
  160. }
  161. }
  162. }
  163. // Set the base class's shutdown flag.
  164. setShutdownFlag(true);
  165. return (isc::config::createAnswer(0, "Shutdown initiated, type is: "
  166. + type_str));
  167. }
  168. isc::data::ConstElementPtr
  169. D2Process::configure(isc::data::ConstElementPtr config_set) {
  170. LOG_DEBUG(dctl_logger, DBGLVL_TRACE_BASIC,
  171. DHCP_DDNS_CONFIGURE).arg(config_set->str());
  172. int rcode = 0;
  173. isc::data::ConstElementPtr comment;
  174. isc::data::ConstElementPtr answer = getCfgMgr()->parseConfig(config_set);;
  175. comment = isc::config::parseAnswer(rcode, answer);
  176. if (rcode) {
  177. // Non-zero means we got an invalid configuration, take no further
  178. // action. In integrated mode, this will send a failed response back
  179. // to BIND10.
  180. reconf_queue_flag_ = false;
  181. return (answer);
  182. }
  183. // Set the reconf_queue_flag to indicate that we need to reconfigure
  184. // the queue manager. Reconfiguring the queue manager may be asynchronous
  185. // and require one or more events to occur, therefore we set a flag
  186. // indicating it needs to be done but we cannot do it here. It must
  187. // be done over time, while events are being processed. Remember that
  188. // the method we are in now is invoked as part of the configuration event
  189. // callback. This means you can't wait for events here, you are already
  190. // in one.
  191. // (@todo NOTE This could be turned into a bitmask of flags if we find other
  192. // things that need reconfiguration. It might also be useful if we
  193. // did some analysis to decide what if anything we need to do.)
  194. reconf_queue_flag_ = true;
  195. // If we are here, configuration was valid, at least it parsed correctly
  196. // and therefore contained no invalid values.
  197. // Return the success answer from above.
  198. return (answer);
  199. }
  200. void
  201. D2Process::checkQueueStatus() {
  202. switch (queue_mgr_->getMgrState()){
  203. case D2QueueMgr::RUNNING:
  204. if (reconf_queue_flag_ || shouldShutdown()) {
  205. // If we need to reconfigure the queue manager or we have been
  206. // told to shutdown, then stop listening first. Stopping entails
  207. // canceling active listening which may generate an IO event, so
  208. // instigate the stop and get out.
  209. try {
  210. LOG_INFO(dctl_logger, DHCP_DDNS_QUEUE_MGR_STOPPING)
  211. .arg(reconf_queue_flag_ ? "reconfiguration"
  212. : "shutdown");
  213. queue_mgr_->stopListening();
  214. } catch (const isc::Exception& ex) {
  215. // It is very unlikey that we would experience an error
  216. // here, but theoretically possible.
  217. LOG_ERROR(dctl_logger, DHCP_DDNS_QUEUE_MGR_STOP_ERROR)
  218. .arg(ex.what());
  219. }
  220. }
  221. break;
  222. case D2QueueMgr::STOPPED_QUEUE_FULL: {
  223. // Resume receiving once the queue has decreased by twenty
  224. // percent. This is an arbitrary choice. @todo this value should
  225. // probably be configurable.
  226. size_t threshold = (((queue_mgr_->getMaxQueueSize()
  227. * QUEUE_RESTART_PERCENT)) / 100);
  228. if (queue_mgr_->getQueueSize() <= threshold) {
  229. LOG_INFO (dctl_logger, DHCP_DDNS_QUEUE_MGR_RESUMING)
  230. .arg(threshold).arg(queue_mgr_->getMaxQueueSize());
  231. try {
  232. queue_mgr_->startListening();
  233. } catch (const isc::Exception& ex) {
  234. LOG_ERROR(dctl_logger, DHCP_DDNS_QUEUE_MGR_RESUME_ERROR)
  235. .arg(ex.what());
  236. }
  237. }
  238. break;
  239. }
  240. case D2QueueMgr::STOPPED_RECV_ERROR:
  241. // If the receive error is not due to some fallout from shutting
  242. // down then we will attempt to recover by reconfiguring the listener.
  243. // This will close and destruct the current listener and make a new
  244. // one with new resources.
  245. // @todo This may need a safety valve such as retry count or a timer
  246. // to keep from endlessly retrying over and over, with little time
  247. // in between.
  248. if (!shouldShutdown()) {
  249. LOG_INFO (dctl_logger, DHCP_DDNS_QUEUE_MGR_RECOVERING);
  250. reconfigureQueueMgr();
  251. }
  252. break;
  253. case D2QueueMgr::STOPPING:
  254. // We are waiting for IO to cancel, so this is a NOP.
  255. // @todo Possible timer for self-defense? We could conceivably
  256. // get into a condition where we never get the event, which would
  257. // leave us stuck in stopping. This is hugely unlikely but possible?
  258. break;
  259. default:
  260. // If the reconfigure flag is set, then we are in a state now where
  261. // we can do the reconfigure. In other words, we aren't RUNNING or
  262. // STOPPING.
  263. if (reconf_queue_flag_) {
  264. LOG_INFO (dctl_logger, DHCP_DDNS_QUEUE_MGR_RECONFIGURING);
  265. reconfigureQueueMgr();
  266. }
  267. break;
  268. }
  269. }
  270. void
  271. D2Process::reconfigureQueueMgr() {
  272. // Set reconfigure flag to false. We are only here because we have
  273. // a valid configuration to work with so if we fail below, it will be
  274. // an operational issue, such as a busy IP address. That will leave
  275. // queue manager in INITTED state, which is fine.
  276. // What we dont' want is to continually attempt to reconfigure so set
  277. // the flag false now.
  278. // @todo This method assumes only 1 type of listener. This will change
  279. // to support at least a TCP version, possibly some form of RDBMS listener
  280. // as well.
  281. reconf_queue_flag_ = false;
  282. try {
  283. // Wipe out the current listener.
  284. queue_mgr_->removeListener();
  285. // Get the configuration parameters that affect Queue Manager.
  286. const D2ParamsPtr& d2_params = getD2CfgMgr()->getD2Params();
  287. // Warn the user if the server address is not the loopback.
  288. /// @todo Remove this once we provide a secure mechanism.
  289. std::string ip_address = d2_params->getIpAddress().toText();
  290. if (ip_address != "127.0.0.1" && ip_address != "::1") {
  291. LOG_WARN(dctl_logger, DHCP_DDNS_NOT_ON_LOOPBACK).arg(ip_address);
  292. }
  293. // Instantiate the listener.
  294. if (d2_params->getNcrProtocol() == dhcp_ddns::NCR_UDP) {
  295. queue_mgr_->initUDPListener(d2_params->getIpAddress(),
  296. d2_params->getPort(),
  297. d2_params->getNcrFormat(), true);
  298. } else {
  299. /// @todo Add TCP/IP once it's supported
  300. // We should never get this far but if we do deal with it.
  301. isc_throw(DProcessBaseError, "Unsupported NCR listener protocol:"
  302. << dhcp_ddns::ncrProtocolToString(d2_params->
  303. getNcrProtocol()));
  304. }
  305. // Now start it. This assumes that starting is a synchronous,
  306. // blocking call that executes quickly. @todo Should that change then
  307. // we will have to expand the state model to accommodate this.
  308. queue_mgr_->startListening();
  309. } catch (const isc::Exception& ex) {
  310. // Queue manager failed to initialize and therefore not listening.
  311. // This is most likely due to an unavailable IP address or port,
  312. // which is a configuration issue.
  313. LOG_ERROR(dctl_logger, DHCP_DDNS_QUEUE_MGR_START_ERROR).arg(ex.what());
  314. }
  315. }
  316. isc::data::ConstElementPtr
  317. D2Process::command(const std::string& command,
  318. isc::data::ConstElementPtr args) {
  319. // @todo This is the initial implementation. If and when D2 is extended
  320. // to support its own commands, this implementation must change. Otherwise
  321. // it should reject all commands as it does now.
  322. LOG_DEBUG(dctl_logger, DBGLVL_TRACE_BASIC, DHCP_DDNS_COMMAND)
  323. .arg(command).arg(args ? args->str() : "(no args)");
  324. return (isc::config::createAnswer(COMMAND_INVALID, "Unrecognized command: "
  325. + command));
  326. }
  327. D2Process::~D2Process() {
  328. };
  329. D2CfgMgrPtr
  330. D2Process::getD2CfgMgr() {
  331. // The base class gives a base class pointer to our configuration manager.
  332. // Since we are D2, and we need D2 specific extensions, we need a pointer
  333. // to D2CfgMgr for some things.
  334. return (boost::dynamic_pointer_cast<D2CfgMgr>(getCfgMgr()));
  335. }
  336. const char* D2Process::getShutdownTypeStr(const ShutdownType& type) {
  337. const char* str = "invalid";
  338. switch (type) {
  339. case SD_NORMAL:
  340. str = "normal";
  341. break;
  342. case SD_DRAIN_FIRST:
  343. str = "drain_first";
  344. break;
  345. case SD_NOW:
  346. str = "now";
  347. break;
  348. default:
  349. break;
  350. }
  351. return (str);
  352. }
  353. }; // namespace isc::d2
  354. }; // namespace isc