Kea  1.9.9-git
d2_process.cc
Go to the documentation of this file.
1 // Copyright (C) 2013-2021 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>
10 #include <config/command_mgr.h>
11 #include <d2/d2_log.h>
12 #include <d2/d2_cfg_mgr.h>
13 #include <d2/d2_controller.h>
14 #include <d2/d2_process.h>
15 #include <hooks/hooks.h>
16 #include <hooks/hooks_manager.h>
17 
18 using namespace isc::hooks;
19 using namespace isc::process;
20 
21 namespace {
22 
24 struct D2ProcessHooks {
25  int hooks_index_d2_srv_configured_;
26 
28  D2ProcessHooks() {
29  hooks_index_d2_srv_configured_ = HooksManager::registerHook("d2_srv_configured");
30  }
31 
32 };
33 
34 // Declare a Hooks object. As this is outside any function or method, it
35 // will be instantiated (and the constructor run) when the module is loaded.
36 // As a result, the hook indexes will be defined before any method in this
37 // module is called.
38 D2ProcessHooks Hooks;
39 
40 }
41 
42 namespace isc {
43 namespace d2 {
44 
45 // Setting to 80% for now. This is an arbitrary choice and should probably
46 // be configurable.
47 const unsigned int D2Process::QUEUE_RESTART_PERCENT = 80;
48 
49 D2Process::D2Process(const char* name, const asiolink::IOServicePtr& io_service)
50  : DProcessBase(name, io_service, DCfgMgrBasePtr(new D2CfgMgr())),
51  reconf_queue_flag_(false), shutdown_type_(SD_NORMAL) {
52 
53  // Instantiate queue manager. Note that queue manager does not start
54  // listening at this point. That can only occur after configuration has
55  // been received. This means that until we receive the configuration,
56  // D2 will neither receive nor process NameChangeRequests.
57  // Pass in IOService for NCR IO event processing.
58  queue_mgr_.reset(new D2QueueMgr(getIoService()));
59 
60  // Instantiate update manager.
61  // Pass in both queue manager and configuration manager.
62  // Pass in IOService for DNS update transaction IO event processing.
63  D2CfgMgrPtr tmp = getD2CfgMgr();
64  update_mgr_.reset(new D2UpdateMgr(queue_mgr_, tmp, getIoService()));
65 };
66 
67 void
69  // CommandMgr uses IO service to run asynchronous socket operations.
71 };
72 
73 void
75  LOG_INFO(d2_logger, DHCP_DDNS_STARTED).arg(VERSION);
76  D2ControllerPtr controller =
77  boost::dynamic_pointer_cast<D2Controller>(D2Controller::instance());
78  try {
79  // Now logging was initialized so commands can be registered.
80  controller->registerCommands();
81 
82  // Loop forever until we are allowed to shutdown.
83  while (!canShutdown()) {
84  // Check on the state of the request queue. Take any
85  // actions necessary regarding it.
87 
88  // Give update manager a time slice to queue new jobs and
89  // process finished ones.
90  update_mgr_->sweep();
91 
92  // Wait on IO event(s) - block until one or more of the following
93  // has occurred:
94  // a. NCR message has been received
95  // b. Transaction IO has completed
96  // c. Interval timer expired
97  // d. Control channel event
98  // e. Something stopped IO service (runIO returns 0)
99  if (runIO() == 0) {
100  // Pretty sure this amounts to an unexpected stop and we
101  // should bail out now. Normal shutdowns do not utilize
102  // stopping the IOService.
104  "Primary IO service stopped unexpectedly");
105  }
106  }
107  } catch (const std::exception& ex) {
108  LOG_FATAL(d2_logger, DHCP_DDNS_FAILED).arg(ex.what());
109  controller->deregisterCommands();
111  "Process run method failed: " << ex.what());
112  }
113 
114  // @todo - if queue isn't empty, we may need to persist its contents
115  // this might be the place to do it, once there is a persistence mgr.
116  // This may also be better in checkQueueStatus.
117 
118  controller->deregisterCommands();
119 
121 
122 };
123 
124 size_t
126  // We want to block until at least one handler is called. We'll use
127  // boost::asio::io_service directly for two reasons. First off
128  // asiolink::IOService::run_one is a void and boost::asio::io_service::stopped
129  // is not present in older versions of boost. We need to know if any
130  // handlers ran or if the io_service was stopped. That latter represents
131  // some form of error and the application cannot proceed with a stopped
132  // service. Secondly, asiolink::IOService does not provide the poll
133  // method. This is a handy method which runs all ready handlers without
134  // blocking.
136  boost::asio::io_service& asio_io_service = io->get_io_service();
137 
138  // Poll runs all that are ready. If none are ready it returns immediately
139  // with a count of zero.
140  size_t cnt = asio_io_service.poll();
141  if (!cnt) {
142  // Poll ran no handlers either none are ready or the service has been
143  // stopped. Either way, call run_one to wait for a IO event. If the
144  // service is stopped it will return immediately with a cnt of zero.
145  cnt = asio_io_service.run_one();
146  }
147 
148  return (cnt);
149 }
150 
151 bool
153  bool all_clear = false;
154 
155  // If we have been told to shutdown, find out if we are ready to do so.
156  if (shouldShutdown()) {
157  switch (shutdown_type_) {
158  case SD_NORMAL:
159  // For a normal shutdown we need to stop the queue manager but
160  // wait until we have finished all the transactions in progress.
161  all_clear = (((queue_mgr_->getMgrState() != D2QueueMgr::RUNNING) &&
162  (queue_mgr_->getMgrState() != D2QueueMgr::STOPPING))
163  && (update_mgr_->getTransactionCount() == 0));
164  break;
165 
166  case SD_DRAIN_FIRST:
167  // For a drain first shutdown we need to stop the queue manager but
168  // process all of the requests in the receive queue first.
169  all_clear = (((queue_mgr_->getMgrState() != D2QueueMgr::RUNNING) &&
170  (queue_mgr_->getMgrState() != D2QueueMgr::STOPPING))
171  && (queue_mgr_->getQueueSize() == 0)
172  && (update_mgr_->getTransactionCount() == 0));
173  break;
174 
175  case SD_NOW:
176  // Get out right now, no niceties.
177  all_clear = true;
178  break;
179 
180  default:
181  // shutdown_type_ is an enum and should only be one of the above.
182  // if its getting through to this, something is whacked.
183  break;
184  }
185 
186  if (all_clear) {
189  .arg(getShutdownTypeStr(shutdown_type_));
190  }
191  }
192 
193  return (all_clear);
194 }
195 
200  .arg(args ? args->str() : "(no arguments)");
201 
202  // Default shutdown type is normal.
203  std::string type_str(getShutdownTypeStr(SD_NORMAL));
204  shutdown_type_ = SD_NORMAL;
205 
206  if (args) {
207  if ((args->getType() == isc::data::Element::map) &&
208  args->contains("type")) {
209  type_str = args->get("type")->stringValue();
210 
211  if (type_str == getShutdownTypeStr(SD_NORMAL)) {
212  shutdown_type_ = SD_NORMAL;
213  } else if (type_str == getShutdownTypeStr(SD_DRAIN_FIRST)) {
214  shutdown_type_ = SD_DRAIN_FIRST;
215  } else if (type_str == getShutdownTypeStr(SD_NOW)) {
216  shutdown_type_ = SD_NOW;
217  } else {
218  setShutdownFlag(false);
219  return (isc::config::createAnswer(1, "Invalid Shutdown type: "
220  + type_str));
221  }
222  }
223  }
224 
225  // Set the base class's shutdown flag.
226  setShutdownFlag(true);
227  return (isc::config::createAnswer(0, "Shutdown initiated, type is: "
228  + type_str));
229 }
230 
232 D2Process::configure(isc::data::ConstElementPtr config_set, bool check_only) {
234  .arg(check_only ? "check" : "update")
235  .arg(getD2CfgMgr()->redactConfig(config_set)->str());
236 
238  answer = getCfgMgr()->simpleParseConfig(config_set, check_only,
239  std::bind(&D2Process::reconfigureCommandChannel, this));
240  if (check_only) {
241  return (answer);
242  }
243 
244  int rcode = 0;
246  comment = isc::config::parseAnswer(rcode, answer);
247 
248  if (rcode) {
249  // Non-zero means we got an invalid configuration, take no further
250  // action. In integrated mode, this will send a failed response back
251  // to the configuration backend.
252  reconf_queue_flag_ = false;
253  return (answer);
254  }
255 
256  // Set the reconf_queue_flag to indicate that we need to reconfigure
257  // the queue manager. Reconfiguring the queue manager may be asynchronous
258  // and require one or more events to occur, therefore we set a flag
259  // indicating it needs to be done but we cannot do it here. It must
260  // be done over time, while events are being processed. Remember that
261  // the method we are in now is invoked as part of the configuration event
262  // callback. This means you can't wait for events here, you are already
263  // in one.
264  // (@todo NOTE This could be turned into a bitmask of flags if we find other
265  // things that need reconfiguration. It might also be useful if we
266  // did some analysis to decide what if anything we need to do.)
267  reconf_queue_flag_ = true;
268 
269  // This hook point notifies hooks libraries that the configuration of the
270  // D2 server has completed. It provides the hook library with the pointer
271  // to the common IO service object, new server configuration in the JSON
272  // format and with the pointer to the configuration storage where the
273  // parsed configuration is stored.
274  if (HooksManager::calloutsPresent(Hooks.hooks_index_d2_srv_configured_)) {
275  CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
276 
277  callout_handle->setArgument("io_context", getIoService());
278  callout_handle->setArgument("json_config", config_set);
279  callout_handle->setArgument("server_config",
280  getD2CfgMgr()->getD2CfgContext());
281 
282  HooksManager::callCallouts(Hooks.hooks_index_d2_srv_configured_,
283  *callout_handle);
284 
285  // Ignore status code as none of them would have an effect on further
286  // operation.
287  }
288 
289  // If we are here, configuration was valid, at least it parsed correctly
290  // and therefore contained no invalid values.
291  // Return the success answer from above.
292  return (answer);
293 }
294 
295 void
297  switch (queue_mgr_->getMgrState()){
298  case D2QueueMgr::RUNNING:
299  if (reconf_queue_flag_ || shouldShutdown()) {
300  // If we need to reconfigure the queue manager or we have been
301  // told to shutdown, then stop listening first. Stopping entails
302  // canceling active listening which may generate an IO event, so
303  // instigate the stop and get out.
304  try {
307  .arg(reconf_queue_flag_ ? "reconfiguration" : "shutdown");
308  queue_mgr_->stopListening();
309  } catch (const isc::Exception& ex) {
310  // It is very unlikely that we would experience an error
311  // here, but theoretically possible.
313  .arg(ex.what());
314  }
315  }
316  break;
317 
319  // Resume receiving once the queue has decreased by twenty
320  // percent. This is an arbitrary choice. @todo this value should
321  // probably be configurable.
322  size_t threshold = (((queue_mgr_->getMaxQueueSize()
323  * QUEUE_RESTART_PERCENT)) / 100);
324  if (queue_mgr_->getQueueSize() <= threshold) {
326  .arg(threshold).arg(queue_mgr_->getMaxQueueSize());
327  try {
328  queue_mgr_->startListening();
329  } catch (const isc::Exception& ex) {
331  .arg(ex.what());
332  }
333  }
334 
335  break;
336  }
337 
339  // If the receive error is not due to some fallout from shutting
340  // down then we will attempt to recover by reconfiguring the listener.
341  // This will close and destruct the current listener and make a new
342  // one with new resources.
343  // @todo This may need a safety valve such as retry count or a timer
344  // to keep from endlessly retrying over and over, with little time
345  // in between.
346  if (!shouldShutdown()) {
349  }
350  break;
351 
353  // We are waiting for IO to cancel, so this is a NOP.
354  // @todo Possible timer for self-defense? We could conceivably
355  // get into a condition where we never get the event, which would
356  // leave us stuck in stopping. This is hugely unlikely but possible?
357  break;
358 
359  default:
360  // If the reconfigure flag is set, then we are in a state now where
361  // we can do the reconfigure. In other words, we aren't RUNNING or
362  // STOPPING.
363  if (reconf_queue_flag_) {
367  }
368  break;
369  }
370 }
371 
372 void
374  // Set reconfigure flag to false. We are only here because we have
375  // a valid configuration to work with so if we fail below, it will be
376  // an operational issue, such as a busy IP address. That will leave
377  // queue manager in INITTED state, which is fine.
378  // What we don't want is to continually attempt to reconfigure so set
379  // the flag false now.
380  // @todo This method assumes only 1 type of listener. This will change
381  // to support at least a TCP version, possibly some form of RDBMS listener
382  // as well.
383  reconf_queue_flag_ = false;
384  try {
385  // Wipe out the current listener.
386  queue_mgr_->removeListener();
387 
388  // Get the configuration parameters that affect Queue Manager.
389  const D2ParamsPtr& d2_params = getD2CfgMgr()->getD2Params();
390 
391  // Warn the user if the server address is not the loopback.
393  std::string ip_address = d2_params->getIpAddress().toText();
394  if (ip_address != "127.0.0.1" && ip_address != "::1") {
395  LOG_WARN(d2_logger, DHCP_DDNS_NOT_ON_LOOPBACK).arg(ip_address);
396  }
397 
398  // Instantiate the listener.
399  if (d2_params->getNcrProtocol() == dhcp_ddns::NCR_UDP) {
400  queue_mgr_->initUDPListener(d2_params->getIpAddress(),
401  d2_params->getPort(),
402  d2_params->getNcrFormat(), true);
403  } else {
405  // We should never get this far but if we do deal with it.
406  isc_throw(DProcessBaseError, "Unsupported NCR listener protocol:"
407  << dhcp_ddns::ncrProtocolToString(d2_params->
408  getNcrProtocol()));
409  }
410 
411  // Now start it. This assumes that starting is a synchronous,
412  // blocking call that executes quickly. @todo Should that change then
413  // we will have to expand the state model to accommodate this.
414  queue_mgr_->startListening();
415  } catch (const isc::Exception& ex) {
416  // Queue manager failed to initialize and therefore not listening.
417  // This is most likely due to an unavailable IP address or port,
418  // which is a configuration issue.
420  }
421 }
422 
424 };
425 
428  // The base class gives a base class pointer to our configuration manager.
429  // Since we are D2, and we need D2 specific extensions, we need a pointer
430  // to D2CfgMgr for some things.
431  return (boost::dynamic_pointer_cast<D2CfgMgr>(getCfgMgr()));
432 }
433 
434 const char* D2Process::getShutdownTypeStr(const ShutdownType& type) {
435  const char* str = "invalid";
436  switch (type) {
437  case SD_NORMAL:
438  str = "normal";
439  break;
440  case SD_DRAIN_FIRST:
441  str = "drain_first";
442  break;
443  case SD_NOW:
444  str = "now";
445  break;
446  default:
447  break;
448  }
449 
450  return (str);
451 }
452 
453 void
455  // Get new socket configuration.
456  isc::data::ConstElementPtr sock_cfg = getD2CfgMgr()->getControlSocketInfo();
457 
458  // Determine if the socket configuration has changed. It has if
459  // both old and new configuration is specified but respective
460  // data elements aren't equal.
461  bool sock_changed = (sock_cfg && current_control_socket_ &&
462  !sock_cfg->equals(*current_control_socket_));
463 
464  // If the previous or new socket configuration doesn't exist or
465  // the new configuration differs from the old configuration we
466  // close the existing socket and open a new socket as appropriate.
467  // Note that closing an existing socket means the client will not
468  // receive the configuration result.
469  if (!sock_cfg || !current_control_socket_ || sock_changed) {
470  // Close the existing socket.
471  if (current_control_socket_) {
473  current_control_socket_.reset();
474  }
475 
476  // Open the new socket.
477  if (sock_cfg) {
479  }
480  }
481 
482  // Commit the new socket configuration.
483  current_control_socket_ = sock_cfg;
484 }
485 
486 } // namespace isc::d2
487 } // namespace isc
#define LOG_WARN(LOGGER, MESSAGE)
Macro to conveniently test warn output and log it.
Definition: macros.h:26
boost::shared_ptr< D2Params > D2ParamsPtr
Defines a pointer for D2Params instances.
Definition: d2_config.h:256
virtual isc::data::ConstElementPtr shutdown(isc::data::ConstElementPtr args)
Initiates the D2Process shutdown process.
Definition: d2_process.cc:197
void registerCommands()
Register commands.
const int DBGLVL_TRACE_BASIC
Trace basic operations.
Definition: log_dbglevels.h:65
const isc::log::MessageID DHCP_DDNS_SHUTDOWN_COMMAND
Definition: d2_messages.h:78
void setShutdownFlag(bool value)
Sets the process shut down flag to the given value.
Definition: d_process.h:166
#define LOG_INFO(LOGGER, MESSAGE)
Macro to conveniently test info output and log it.
Definition: macros.h:20
ConstElementPtr createAnswer(const int status_code, const std::string &text, const ConstElementPtr &arg)
const isc::log::MessageID DHCP_DDNS_STARTED
Definition: d2_messages.h:79
boost::shared_ptr< DCfgMgrBase > DCfgMgrBasePtr
Defines a shared pointer to DCfgMgrBase.
Definition: d_cfg_mgr.h:247
D2QueueMgr creates and manages a queue of DNS update requests.
Definition: d2_queue_mgr.h:131
virtual void init()
Called after instantiation to perform initialization unique to D2.
Definition: d2_process.cc:68
ConstElementPtr redactConfig(ConstElementPtr const &element, list< string > const &json_path)
Redact a configuration.
static const char * getShutdownTypeStr(const ShutdownType &type)
Returns a text label for the given shutdown type.
Definition: d2_process.cc:434
#define LOG_ERROR(LOGGER, MESSAGE)
Macro to conveniently test error output and log it.
Definition: macros.h:32
D2CfgMgrPtr getD2CfgMgr()
Returns a pointer to the configuration manager.
Definition: d2_process.cc:427
D2UpdateMgr creates and manages update transactions.
Definition: d2_update_mgr.h:65
static int registerHook(const std::string &name)
Register Hook.
const isc::log::MessageID DHCP_DDNS_CONFIGURE
Definition: d2_messages.h:17
const isc::log::MessageID DHCP_DDNS_RUN_EXIT
Definition: d2_messages.h:77
const isc::log::MessageID DHCP_DDNS_QUEUE_MGR_RECOVERING
Definition: d2_messages.h:52
static process::DControllerBasePtr & instance()
Static singleton instance method.
void reconfigureCommandChannel()
(Re-)Configure the command channel.
Definition: d2_process.cc:454
virtual void checkQueueStatus()
Monitors current queue manager state, takes action accordingly.
Definition: d2_process.cc:296
boost::shared_ptr< D2Controller > D2ControllerPtr
Pointer to a process controller.
Definition: d2_controller.h:15
bool shouldShutdown() const
Checks if the process has been instructed to shut down.
Definition: d_process.h:159
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
virtual ~D2Process()
Destructor.
Definition: d2_process.cc:423
std::string ncrProtocolToString(NameChangeProtocol protocol)
Function which converts NameChangeProtocol enums to text labels.
Definition: ncr_io.cc:36
const isc::log::MessageID DHCP_DDNS_QUEUE_MGR_RESUME_ERROR
Definition: d2_messages.h:54
DCfgMgrBasePtr & getCfgMgr()
Fetches the process's configuration manager.
Definition: d_process.h:195
const isc::log::MessageID DHCP_DDNS_QUEUE_MGR_START_ERROR
Definition: d2_messages.h:57
void closeCommandSocket()
Shuts down any open control sockets.
Definition: command_mgr.cc:626
const isc::log::MessageID DHCP_DDNS_NOT_ON_LOOPBACK
Definition: d2_messages.h:43
virtual bool canShutdown() const
Indicates whether or not the process can perform a shutdown.
Definition: d2_process.cc:152
const isc::log::MessageID DHCP_DDNS_CLEARED_FOR_SHUTDOWN
Definition: d2_messages.h:15
const isc::log::MessageID DHCP_DDNS_FAILED
Definition: d2_messages.h:20
virtual void run()
Implements the process's event loop.
Definition: d2_process.cc:74
boost::shared_ptr< D2CfgMgr > D2CfgMgrPtr
Defines a shared pointer to D2CfgMgr.
Definition: d2_cfg_mgr.h:334
boost::shared_ptr< const Element > ConstElementPtr
Definition: data.h:23
const isc::log::MessageID DHCP_DDNS_QUEUE_MGR_STOP_ERROR
Definition: d2_messages.h:60
void setIOService(const asiolink::IOServicePtr &io_service)
Sets IO service to be used by the command manager.
Definition: command_mgr.cc:655
virtual const char * what() const
Returns a C-style character string of the cause of the exception.
ShutdownType
Defines the shutdown types supported by D2Process.
Definition: d2_process.h:36
This is a base class for exceptions thrown from the DNS library module.
ConstElementPtr parseAnswer(int &rcode, const ConstElementPtr &msg)
void openCommandSocket(const isc::data::ConstElementPtr &socket_info)
Opens control socket with parameters specified in socket_info.
Definition: command_mgr.cc:622
Defines the logger used by the top-level component of kea-dhcp-ddns.
isc::log::Logger d2_logger("dhcpddns")
Defines the logger used within D2.
Definition: d2_log.h:18
boost::shared_ptr< CalloutHandle > CalloutHandlePtr
A shared pointer to a CalloutHandle object.
This file contains several functions and constants that are used for handling commands and responses ...
asiolink::IOServicePtr & getIoService()
Fetches the controller's IOService.
Definition: d_process.h:180
CtrlAgentHooks Hooks
Exception thrown if the process encountered an operational error.
Definition: d_process.h:24
virtual void reconfigureQueueMgr()
Initializes then starts the queue manager.
Definition: d2_process.cc:373
const isc::log::MessageID DHCP_DDNS_QUEUE_MGR_STOPPING
Definition: d2_messages.h:59
#define LOG_DEBUG(LOGGER, LEVEL, MESSAGE)
Macro to conveniently test debug output and log it.
Definition: macros.h:14
const isc::log::MessageID DHCP_DDNS_QUEUE_MGR_RECONFIGURING
Definition: d2_messages.h:51
virtual isc::data::ConstElementPtr configure(isc::data::ConstElementPtr config_set, bool check_only=false)
Processes the given configuration.
Definition: d2_process.cc:232
Application Process Interface.
Definition: d_process.h:81
#define LOG_FATAL(LOGGER, MESSAGE)
Macro to conveniently test fatal output and log it.
Definition: macros.h:38
DHCP-DDNS Configuration Manager.
Definition: d2_cfg_mgr.h:161
const int DBGLVL_START_SHUT
This is given a value of 0 as that is the level selected if debugging is enabled without giving a lev...
Definition: log_dbglevels.h:50
static CommandMgr & instance()
CommandMgr is a singleton class.
Definition: command_mgr.cc:649
Process Controller for D2 Process This class is the DHCP-DDNS specific derivation of DControllerBase...
Definition: d2_controller.h:29
const isc::log::MessageID DHCP_DDNS_QUEUE_MGR_RESUMING
Definition: d2_messages.h:55
static const unsigned int QUEUE_RESTART_PERCENT
Defines the point at which to resume receiving requests.
Definition: d2_process.h:48
virtual size_t runIO()
Allows IO processing to run until at least callback is invoked.
Definition: d2_process.cc:125