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