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