Kea 1.9.11
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_controller.h>
12#include <d2/d2_process.h>
13#include <d2srv/d2_cfg_mgr.h>
14#include <d2srv/d2_log.h>
15#include <hooks/hooks.h>
16#include <hooks/hooks_manager.h>
17
18using namespace isc::hooks;
19using namespace isc::process;
20
21namespace {
22
24struct 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.
38D2ProcessHooks Hooks;
39
40}
41
42namespace isc {
43namespace d2 {
44
45// Setting to 80% for now. This is an arbitrary choice and should probably
46// be configurable.
47const unsigned int D2Process::QUEUE_RESTART_PERCENT = 80;
48
49D2Process::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.
64 update_mgr_.reset(new D2UpdateMgr(queue_mgr_, tmp, getIoService()));
65};
66
67void
69 // CommandMgr uses IO service to run asynchronous socket operations.
71};
72
73void
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
124size_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
151bool
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
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 std::string error("");
275 if (HooksManager::calloutsPresent(Hooks.hooks_index_d2_srv_configured_)) {
276 CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
277
278 callout_handle->setArgument("io_context", getIoService());
279 callout_handle->setArgument("json_config", config_set);
280 callout_handle->setArgument("server_config",
281 getD2CfgMgr()->getD2CfgContext());
282 callout_handle->setArgument("error", error);
283
284 HooksManager::callCallouts(Hooks.hooks_index_d2_srv_configured_,
285 *callout_handle);
286
287 // The config can be rejected by a hook.
288 if (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_DROP) {
289 callout_handle->getArgument("error", error);
291 .arg(error);
292 reconf_queue_flag_ = false;
293 answer = isc::config::createAnswer(1, error);
294 return (answer);
295 }
296 }
297
298 // If we are here, configuration was valid, at least it parsed correctly
299 // and therefore contained no invalid values.
300 // Return the success answer from above.
301 return (answer);
302}
303
304void
306 switch (queue_mgr_->getMgrState()){
308 if (reconf_queue_flag_ || shouldShutdown()) {
309 // If we need to reconfigure the queue manager or we have been
310 // told to shutdown, then stop listening first. Stopping entails
311 // canceling active listening which may generate an IO event, so
312 // instigate the stop and get out.
313 try {
316 .arg(reconf_queue_flag_ ? "reconfiguration" : "shutdown");
317 queue_mgr_->stopListening();
318 } catch (const isc::Exception& ex) {
319 // It is very unlikely that we would experience an error
320 // here, but theoretically possible.
322 .arg(ex.what());
323 }
324 }
325 break;
326
328 // Resume receiving once the queue has decreased by twenty
329 // percent. This is an arbitrary choice. @todo this value should
330 // probably be configurable.
331 size_t threshold = (((queue_mgr_->getMaxQueueSize()
332 * QUEUE_RESTART_PERCENT)) / 100);
333 if (queue_mgr_->getQueueSize() <= threshold) {
335 .arg(threshold).arg(queue_mgr_->getMaxQueueSize());
336 try {
337 queue_mgr_->startListening();
338 } catch (const isc::Exception& ex) {
340 .arg(ex.what());
341 }
342 }
343
344 break;
345 }
346
348 // If the receive error is not due to some fallout from shutting
349 // down then we will attempt to recover by reconfiguring the listener.
350 // This will close and destruct the current listener and make a new
351 // one with new resources.
352 // @todo This may need a safety valve such as retry count or a timer
353 // to keep from endlessly retrying over and over, with little time
354 // in between.
355 if (!shouldShutdown()) {
358 }
359 break;
360
362 // We are waiting for IO to cancel, so this is a NOP.
363 // @todo Possible timer for self-defense? We could conceivably
364 // get into a condition where we never get the event, which would
365 // leave us stuck in stopping. This is hugely unlikely but possible?
366 break;
367
368 default:
369 // If the reconfigure flag is set, then we are in a state now where
370 // we can do the reconfigure. In other words, we aren't RUNNING or
371 // STOPPING.
372 if (reconf_queue_flag_) {
376 }
377 break;
378 }
379}
380
381void
383 // Set reconfigure flag to false. We are only here because we have
384 // a valid configuration to work with so if we fail below, it will be
385 // an operational issue, such as a busy IP address. That will leave
386 // queue manager in INITTED state, which is fine.
387 // What we don't want is to continually attempt to reconfigure so set
388 // the flag false now.
389 // @todo This method assumes only 1 type of listener. This will change
390 // to support at least a TCP version, possibly some form of RDBMS listener
391 // as well.
392 reconf_queue_flag_ = false;
393 try {
394 // Wipe out the current listener.
395 queue_mgr_->removeListener();
396
397 // Get the configuration parameters that affect Queue Manager.
398 const D2ParamsPtr& d2_params = getD2CfgMgr()->getD2Params();
399
400 // Warn the user if the server address is not the loopback.
402 std::string ip_address = d2_params->getIpAddress().toText();
403 if (ip_address != "127.0.0.1" && ip_address != "::1") {
405 }
406
407 // Instantiate the listener.
408 if (d2_params->getNcrProtocol() == dhcp_ddns::NCR_UDP) {
409 queue_mgr_->initUDPListener(d2_params->getIpAddress(),
410 d2_params->getPort(),
411 d2_params->getNcrFormat(), true);
412 } else {
414 // We should never get this far but if we do deal with it.
415 isc_throw(DProcessBaseError, "Unsupported NCR listener protocol:"
416 << dhcp_ddns::ncrProtocolToString(d2_params->
417 getNcrProtocol()));
418 }
419
420 // Now start it. This assumes that starting is a synchronous,
421 // blocking call that executes quickly. @todo Should that change then
422 // we will have to expand the state model to accommodate this.
423 queue_mgr_->startListening();
424 } catch (const isc::Exception& ex) {
425 // Queue manager failed to initialize and therefore not listening.
426 // This is most likely due to an unavailable IP address or port,
427 // which is a configuration issue.
429 }
430}
431
433};
434
437 // The base class gives a base class pointer to our configuration manager.
438 // Since we are D2, and we need D2 specific extensions, we need a pointer
439 // to D2CfgMgr for some things.
440 return (boost::dynamic_pointer_cast<D2CfgMgr>(getCfgMgr()));
441}
442
444 const char* str = "invalid";
445 switch (type) {
446 case SD_NORMAL:
447 str = "normal";
448 break;
449 case SD_DRAIN_FIRST:
450 str = "drain_first";
451 break;
452 case SD_NOW:
453 str = "now";
454 break;
455 default:
456 break;
457 }
458
459 return (str);
460}
461
462void
464 // Get new socket configuration.
465 isc::data::ConstElementPtr sock_cfg = getD2CfgMgr()->getControlSocketInfo();
466
467 // Determine if the socket configuration has changed. It has if
468 // both old and new configuration is specified but respective
469 // data elements aren't equal.
470 bool sock_changed = (sock_cfg && current_control_socket_ &&
471 !sock_cfg->equals(*current_control_socket_));
472
473 // If the previous or new socket configuration doesn't exist or
474 // the new configuration differs from the old configuration we
475 // close the existing socket and open a new socket as appropriate.
476 // Note that closing an existing socket means the client will not
477 // receive the configuration result.
478 if (!sock_cfg || !current_control_socket_ || sock_changed) {
479 // Close the existing socket.
480 if (current_control_socket_) {
482 current_control_socket_.reset();
483 }
484
485 // Open the new socket.
486 if (sock_cfg) {
488 }
489 }
490
491 // Commit the new socket configuration.
492 current_control_socket_ = sock_cfg;
493}
494
495} // namespace isc::d2
496} // 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:628
static CommandMgr & instance()
CommandMgr is a singleton class.
Definition: command_mgr.cc:651
void setIOService(const asiolink::IOServicePtr &io_service)
Sets IO service to be used by the command manager.
Definition: command_mgr.cc:657
void openCommandSocket(const isc::data::ConstElementPtr &socket_info)
Opens control socket with parameters specified in socket_info.
Definition: command_mgr.cc:624
DHCP-DDNS Configuration Manager.
Definition: d2_cfg_mgr.h:161
static process::DControllerBasePtr & instance()
Static singleton instance method.
D2Process(const char *name, const asiolink::IOServicePtr &io_service)
Constructor.
Definition: d2_process.cc:49
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:152
virtual void checkQueueStatus()
Monitors current queue manager state, takes action accordingly.
Definition: d2_process.cc:305
virtual ~D2Process()
Destructor.
Definition: d2_process.cc:432
virtual void run()
Implements the process's event loop.
Definition: d2_process.cc:74
virtual void init()
Called after instantiation to perform initialization unique to D2.
Definition: d2_process.cc:68
D2CfgMgrPtr getD2CfgMgr()
Returns a pointer to the configuration manager.
Definition: d2_process.cc:436
virtual isc::data::ConstElementPtr configure(isc::data::ConstElementPtr config_set, bool check_only=false)
Processes the given configuration.
Definition: d2_process.cc:232
void reconfigureCommandChannel()
(Re-)Configure the command channel.
Definition: d2_process.cc:463
virtual void reconfigureQueueMgr()
Initializes then starts the queue manager.
Definition: d2_process.cc:382
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:197
static const char * getShutdownTypeStr(const ShutdownType &type)
Returns a text label for the given shutdown type.
Definition: d2_process.cc:443
virtual size_t runIO()
Allows IO processing to run until at least callback is invoked.
Definition: d2_process.cc:125
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: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
bool shouldShutdown() const
Checks if the process has been instructed to shut down.
Definition: d_process.h:159
DCfgMgrBasePtr & getCfgMgr()
Fetches the process's configuration manager.
Definition: d_process.h:195
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(const int status_code, const std::string &text, const ConstElementPtr &arg)
ConstElementPtr parseAnswer(int &rcode, const ConstElementPtr &msg)
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:53
const isc::log::MessageID DHCP_DDNS_QUEUE_MGR_STOP_ERROR
Definition: d2_messages.h:61
const isc::log::MessageID DHCP_DDNS_FAILED
Definition: d2_messages.h:21
const isc::log::MessageID DHCP_DDNS_QUEUE_MGR_START_ERROR
Definition: d2_messages.h:58
const isc::log::MessageID DHCP_DDNS_SHUTDOWN_COMMAND
Definition: d2_messages.h:79
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:52
const isc::log::MessageID DHCP_DDNS_RUN_EXIT
Definition: d2_messages.h:78
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:55
const isc::log::MessageID DHCP_DDNS_STARTED
Definition: d2_messages.h:80
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:56
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:60
const isc::log::MessageID DHCP_DDNS_NOT_ON_LOOPBACK
Definition: d2_messages.h:44
boost::shared_ptr< const Element > ConstElementPtr
Definition: data.h:27
@ error
Definition: db_log.h:118
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.