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
14using namespace isc::process;
15
16namespace isc {
17namespace d2 {
18
19// Setting to 80% for now. This is an arbitrary choice and should probably
20// be configurable.
21const unsigned int D2Process::QUEUE_RESTART_PERCENT = 80;
22
23D2Process::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.
38 update_mgr_.reset(new D2UpdateMgr(queue_mgr_, tmp, getIoService()));
39};
40
41void
43};
44
45void
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
84
85};
86
87size_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
114bool
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
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
237void
239 switch (queue_mgr_->getMgrState()){
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
314void
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
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
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.
DHCP-DDNS Configuration Manager.
Definition: d2_cfg_mgr.h:130
D2Process(const char *name, const asiolink::IOServicePtr &io_service)
Constructor.
Definition: d2_process.cc:23
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:115
virtual void checkQueueStatus()
Monitors current queue manager state, takes action accordingly.
Definition: d2_process.cc:238
virtual ~D2Process()
Destructor.
Definition: d2_process.cc:365
virtual void run()
Implements the process's event loop.
Definition: d2_process.cc:46
virtual void init()
Called after instantiation to perform initialization unique to D2.
Definition: d2_process.cc:42
D2CfgMgrPtr getD2CfgMgr()
Returns a pointer to the configuration manager.
Definition: d2_process.cc:369
virtual isc::data::ConstElementPtr configure(isc::data::ConstElementPtr config_set, bool check_only=false)
Processes the given configuration.
Definition: d2_process.cc:195
virtual void reconfigureQueueMgr()
Initializes then starts the queue manager.
Definition: d2_process.cc:315
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:160
static const char * getShutdownTypeStr(const ShutdownType &type)
Returns a text label for the given shutdown type.
Definition: d2_process.cc:376
virtual size_t runIO()
Allows IO processing to run until at least callback is invoked.
Definition: d2_process.cc:88
D2QueueMgr creates and manages a queue of DNS update requests.
Definition: d2_queue_mgr.h:132
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:22
Application Process Interface.
Definition: d_process.h:67
void setShutdownFlag(bool value)
Sets the process shut down flag to the given value.
Definition: d_process.h:152
asiolink::IOServicePtr & getIoService()
Fetches the controller's IOService.
Definition: d_process.h:166
bool shouldShutdown() const
Checks if the process has been instructed to shut down.
Definition: d_process.h:145
DCfgMgrBasePtr & getCfgMgr()
Fetches the process's configuration manager.
Definition: d_process.h:181
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
ConstElementPtr createAnswer()
Creates a standard config/command level success answer message (i.e.
ConstElementPtr parseAnswer(int &rcode, const ConstElementPtr &msg)
boost::shared_ptr< D2CfgMgr > D2CfgMgrPtr
Defines a shared pointer to D2CfgMgr.
Definition: d2_cfg_mgr.h:297
isc::log::Logger d2_logger("dhcpddns")
Defines the logger used within D2.
Definition: d2_log.h:18
boost::shared_ptr< D2Params > D2ParamsPtr
Defines a pointer for D2Params instances.
Definition: d2_config.h:251
boost::shared_ptr< const Element > ConstElementPtr
Definition: data.h:23
std::string ncrProtocolToString(NameChangeProtocol protocol)
Function which converts NameChangeProtocol enums to text labels.
Definition: ncr_io.cc:30
const int DBGLVL_TRACE_BASIC
Trace basic operations.
Definition: log_dbglevels.h:65
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:219
Defines the logger used by the top-level component of kea-dhcp-ddns.