Commit c39eb9bb authored by Thomas Markwalder's avatar Thomas Markwalder
Browse files

[3075] Implemented main event loop in b10-dhcp-ddns

Added main process event loop to D2Process which is the
primary application "object" in b10-dchp-ddns.

Along the way it was necessary to adjust D2QueueManager to treat
stopping as an asyncrhonous event when IO is pending. This
requied a change in lib-dchp-ddns to have awareness of when
IO is pending.

Fixed a bug in D2 configuration parsing in which parsing a
a subsequent configuration caused duplicate TSIGKeyInfo
parent aba2bd5b
......@@ -253,7 +253,8 @@ TSIGKeyInfoParser::commit() {
TSIGKeyInfoListParser::TSIGKeyInfoListParser(const std::string& list_name,
TSIGKeyInfoMapPtr keys)
:list_name_(list_name), keys_(keys), parsers_() {
:list_name_(list_name), keys_(keys), local_keys_(new TSIGKeyInfoMap()),
parsers_() {
if (!keys_) {
isc_throw(D2CfgError, "TSIGKeyInfoListParser ctor:"
" key storage cannot be null");
......@@ -277,7 +278,7 @@ build(isc::data::ConstElementPtr key_list){
// Create a name for the parser based on its position in the list.
std::string entry_name = boost::lexical_cast<std::string>(i++);
isc::dhcp::ParserPtr parser(new TSIGKeyInfoParser(entry_name,
......@@ -290,6 +291,10 @@ TSIGKeyInfoListParser::commit() {
BOOST_FOREACH(isc::dhcp::ParserPtr parser, parsers_) {
// Now that we know we have a valid list, commit that list to the
// area given to us during construction (i.e. to the d2 context).
*keys_ = *local_keys_;
// *********************** DnsServerInfoParser *************************
......@@ -607,7 +607,11 @@ public:
/// @brief Iterates over the internal list of TSIGKeyInfoParsers,
/// invoking commit on each. This causes each parser to instantiate a
/// TSIGKeyInfo from its internal data values and add that that key
/// instance to the storage area, keys_.
/// instance to the local key storage area, local_keys_. If all of the
/// key parsers commit cleanly, then update the context key map (keys_)
/// with the contents of local_keys_. This is done to allow for duplicate
/// key detection while parsing the keys, but not get stumped by it
/// updating the context with a valid list.
virtual void commit();
......@@ -618,6 +622,9 @@ private:
/// the list of newly created TSIGKeyInfo instances. This is given to us
/// as a constructor argument by an upper level.
TSIGKeyInfoMapPtr keys_;
/// @brief Local storage area to which individual key parsers commit.
TSIGKeyInfoMapPtr local_keys_;
/// @brief Local storage of TSIGKeyInfoParser instances
isc::dhcp::ParserCollection parsers_;
# Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
# Permission to use, copy, modify, and/or distribute this software for any
......@@ -22,7 +23,7 @@ to disconnect from its session with the BIND10 control channel.
This debug message is issued just before the controller attempts
to establish a session with the BIND10 control channel.
% DCTL_COMMAND_RECEIVED %1 received command %2, arguments: %3
% DCTL_COMMAND_RECEIVED %1 received command: %2, arguments: %3
A debug message listing the command (and possible arguments) received
from the BIND10 control system by the controller.
......@@ -116,6 +117,10 @@ following a shut down (normal or otherwise) of the service.
This is a debug message that indicates that the application has DHCP_DDNS
requests in the queue but is working as many concurrent requests as allowed.
% DHCP_DDNS_CLEARED_FOR_SHUTDOWN application has met shutdown criteria for shutdown type: %1
This is an informational message issued when the application has been instructed
to shutdown and has met the required criteria to exit.
% DHCP_DDNS_COMMAND command directive received, command: %1 - args: %2
This is a debug message issued when the Dhcp-Ddns application command method
has been invoked.
......@@ -168,12 +173,69 @@ needs to be increased, the DHCP-DDNS clients are simply generating too many
requests too quickly, or perhaps upstream DNS servers are experiencing
load issues.
% DHCP_DDNS_QUEUE_MGR_RECONFIG application is reconfiguring the queue manager
This is an informational message indicating that DHCP_DDNS is reconfiguring the
queue manager as part of normal startup or in response to a new configuration.
% DHCP_DDNS_QUEUE_MGR_RECOVERING application is attempting to recover from a
queue manager IO error
This is an informational message indicating that DHCP_DDNS is attempting to
restart the queue manager after it suffered an IO error while receiving
% DHCP_DDNS_QUEUE_MGR_RECV_ERROR application's queue manager was notified of a request receive error by its listener.
This is an error message indicating that the NameChangeRequest listener used by
DHCP-DDNS to receive requests encountered a IO error. There should be
corresponding log messages from the listener layer with more details. This may
indicate a network connectivity or system resource issue.
% DHCP_DDNS_QUEUE_MGR_RESUME_ERROR application could not restart the queue manager, reason: %1
This is an error message indicating that DHCP_DDNS's Queue Manager could not
be restarted after stopping due to an a full receive queue. This means that
the application cannot receive requests. This is most likely due to DHCP_DDNS
configuration parameters referring to resources such as an IP address or port,
that is no longer unavailable. DHCP_DDNS will attempt to restart the queue
manager if given a new configuration.
% DHCP_DDNS_QUEUE_MGR_RESUMING application is resuming listening for requests now that the request queue size has reached %1 of a maximum %2 allowed
This is an informational message indicating that DHCP_DDNS, which had stopped
accpeting new requests, has processed enough entries from the receive queue to
resume accepting requests.
% DHCP_DDNS_QUEUE_MGR_STARTED application's queue manager has begun listening for requests.
This is a debug message indicating that DHCP_DDNS's Queue Manager has
successfully started and is now listening for NameChangeRequests.
% DHCP_DDNS_QUEUE_MGR_START_ERROR application could not start the queue manager, reason: %1
This is an error message indicating that DHCP_DDNS's Queue Manager could not
be started. This means that the application cannot receive requests. This is
most likely due to DHCP_DDNS configuration parameters referring to resources
such as an IP address or port, that are unavailable. DHCP_DDNS will attempt to
restart the queue manager if given a new configuration.
% DHCP_DDNS_QUEUE_MGR_STOPPED application's queue manager has stopped listening for requests.
This is an informational message indicating that DHCP_DDNS's Queue Manager has
stopped listening for NameChangeRequests. This may be because of normal event
such as reconfiguration or as a result of an error. There should be log
messages preceding this one to indicate why it has stopped.
% DHCP_DDNS_QUEUE_MGR_STOPPING application is stopping the queue manager for %1
This is an informational message indicating that DHCP_DDNS is stopping the
queue manager either to reconfigure it or as part of application shutdown.
% DHCP_DDNS_QUEUE_MGR_STOP_ERROR application encountered an error stopping the queue manager: %1
This is an error message indicating that DHCP_DDNS encountered an error while
trying to stop the queue manager. This error is unlikely to occur or to
impair the application's ability to function but it should be reported for
% DHCP_DDNS_QUEUE_MGR_UNEXPECTED_STOP application's queue manager receive was
aborted unexpectedly while queue manager state is: %1
This is an error message indicating that DHCP_DDNS's Queue Manager request
receive was unexpected interrupted. Normally, the read is receive is only
interrupted as a normal part of stopping the queue manager. This is most
likely a programmatic issue that should be reported.
% DHCP_DDNS_RUN_ENTER application has entered the event loop
This is a debug message issued when the Dhcp-Ddns application enters
its run method.
......@@ -182,6 +244,6 @@ its run method.
This is a debug message issued when the Dhcp-Ddns exits the
in event loop.
% DHCP_DDNS_SHUTDOWN application is performing a normal shut down
This is a debug message issued when the application has been instructed
% DHCP_DDNS_SHUTDOWN application received shutdown command with args: %1
This is informational message issued when the application has been instructed
to shut down by the controller.
......@@ -17,13 +17,37 @@
#include <d2/d2_cfg_mgr.h>
#include <d2/d2_process.h>
using namespace asio;
#include <asio.hpp>
namespace isc {
namespace d2 {
// String translations for ShutdownType enums.
const char* D2Process::SD_NORMAL_STR = "normal";
const char* D2Process::SD_DRAIN_FIRST_STR = "drain_first";
const char* D2Process::SD_NOW_STR = "now";
const char* D2Process::SD_INVALID_STR = "invalid";
// Setting to 80% for now. This is an arbitrary choice and should probably
// be configurable.
const float D2Process::QUEUE_RESTART_PERCENT = 0.80;
D2Process::D2Process(const char* name, IOServicePtr io_service)
: DProcessBase(name, io_service, DCfgMgrBasePtr(new D2CfgMgr())) {
: DProcessBase(name, io_service, DCfgMgrBasePtr(new D2CfgMgr())),
reconf_queue_flag_(false), shutdown_type_(SD_NORMAL) {
// Instantiate queue manager. Note that queue manager does not start
// listening at this point. That can only occur after configuration has
// been received. This means that until we receive the configuration,
// D2 will neither receive nor process NameChangeRequests.
// Pass in IOService for NCR IO event processing.
queue_mgr_.reset(new D2QueueMgr(*getIoService()));
// Instantiate update manager.
// Pass in both queue manager and configuration manager.
// Pass in IOService for DNS update transaction IO event processing.
D2CfgMgrPtr tmp = getD2CfgMgr();
update_mgr_.reset(new D2UpdateMgr(queue_mgr_, tmp, *getIoService()));
......@@ -32,17 +56,31 @@ D2Process::init() {
D2Process::run() {
// Until shut down or an fatal error occurs, wait for and
// execute a single callback. This is a preliminary implementation
// that is likely to evolve as development progresses.
// To use run(), the "managing" layer must issue an io_service::stop
// or the call to run will continue to block, and shutdown will not
// occur.
IOServicePtr& io_service = getIoService();
while (!shouldShutdown()) {
// Loop forever until we are allowed to shutdown.
while (!canShutdown()) {
try {
// Check on the state of the request queue. Take any
// actions necessary regarding it.
// Give update manager a time slice to queue new jobs and
// process finished ones.
// Wait on IO event(s) - block until one or more of the following
// has occurred:
// a. NCR message has been received
// b. Transaction IO has completed
// c. Interval timer expired
// d. Something stopped IO service (runIO returns 0)
if (runIO() == 0) {
// Pretty sure this amounts to an unexpected stop and we
// should bail out now. Normal shutdowns do not utilize
// stopping the IOService.
"Primary IO service stopped unexpectedly");
} catch (const std::exception& ex) {
LOG_FATAL(dctl_logger, DHCP_DDNS_FAILED).arg(ex.what());
isc_throw (DProcessBaseError,
......@@ -50,29 +88,269 @@ D2Process::run() {
// @todo - if queue isn't empty, we may need to persist its contents
// this might be the place to do it, once there is a persistence mgr.
// This may also be better in checkQueueStatus.
D2Process::shutdown() {
D2Process::runIO() {
// We want to block until at least one handler is called. We'll use
// asio::io_service directly for two reasons. First off
// asiolink::IOService::run_one is a void and asio::io_service::stopped
// is not present in older versions of boost. We need to know if any
// handlers ran or if the io_service was stopped. That latter represents
// some form of error and the application cannot proceed with a stopped
// service. Secondly, asiolink::IOService does not provide the poll
// method. This is a handy method which runs all ready handlers without
// blocking.
IOServicePtr& io = getIoService();
asio::io_service& asio_io_service = io->get_io_service();
// Poll runs all that are ready. If none are ready it returns immediately
// with a count of zero.
size_t cnt = asio_io_service.poll();
if (!cnt) {
// Poll ran no handlers either none are ready or the service has been
// stopped. Either way, call run_one to wait for a IO event. If the
// service is stopped it will return immediately with a cnt of zero.
cnt = asio_io_service.run_one();
return cnt;
D2Process::canShutdown() {
bool all_clear = false;
// If we have been told to shutdown, find out if we are ready to do so.
if (shouldShutdown()) {
switch (shutdown_type_) {
// For a normal shutdown we need to stop the queue manager but
// wait until we have finished all the transactions in progress.
all_clear = (((queue_mgr_->getMgrState() != D2QueueMgr::RUNNING) &&
(queue_mgr_->getMgrState() != D2QueueMgr::STOPPING))
&& (update_mgr_->getTransactionCount() == 0));
// For a drain first shutdown we need to stop the queue manager but
// process all of the requests in the receive queue first.
all_clear = (((queue_mgr_->getMgrState() != D2QueueMgr::RUNNING) &&
(queue_mgr_->getMgrState() != D2QueueMgr::STOPPING))
&& (queue_mgr_->getQueueSize() == 0)
&& (update_mgr_->getTransactionCount() == 0));
case SD_NOW:
// Get out right now, no niceties.
all_clear = true;
if (all_clear) {
return (all_clear);
D2Process::shutdown(isc::data::ConstElementPtr args) {
LOG_INFO(dctl_logger, DHCP_DDNS_SHUTDOWN).arg(args ? args->str()
: "(no args)");
// Default shutdown type is normal.
std::string type_str(SD_NORMAL_STR);
shutdown_type_ = SD_NORMAL;
if (args) {
if ((args->getType() == isc::data::Element::map) &&
args->contains("type")) {
type_str = args->get("type")->stringValue();
if (type_str == SD_NORMAL_STR) {
shutdown_type_ = SD_NORMAL;
} else if (type_str == SD_DRAIN_FIRST_STR) {
shutdown_type_ = SD_DRAIN_FIRST;
} else if (type_str == SD_NOW_STR) {
shutdown_type_ = SD_NOW;
} else {
return (isc::config::createAnswer(1, "Invalid Shutdown type: "
+ type_str));
// Set the base class's shutdown flag.
return (isc::config::createAnswer(0, "Shutdown initiated, type is: "
+ type_str));
D2Process::configure(isc::data::ConstElementPtr config_set) {
// @todo This is the initial implementation passes the configuration onto
// the D2CfgMgr. There may be additional steps taken added to handle
// configuration changes but for now, assume that D2CfgMgr is handling it
// all.
return (getCfgMgr()->parseConfig(config_set));
int rcode = 0;
isc::data::ConstElementPtr comment;
isc::data::ConstElementPtr answer = getCfgMgr()->parseConfig(config_set);;
comment = isc::config::parseAnswer(rcode, answer);
if (rcode) {
// Non-zero means we got an invalid configuration, take no further
// action. In integrated mode, this will send a failed response back
// to BIND10.
reconf_queue_flag_ = false;
return (answer);
// Set the reconf_queue_flag to indicate that we need to reconfigure
// the queue manager. Reconfiguring the queue manager may be asynchronous
// and require one or more events to occur, therefore we set a flag
// indicating it needs to be done but we cannot do it here. It must
// be done over time, while events are being processed. Remember that
// the method we are in now is invoked as part of the configuration event
// callback. This means you can't wait for events here, you are already
// in one.
// (@todo NOTE This could be turned into a bitmask of flags if we find other
// things that need reconfiguration. It might also be useful if we
// did some analysis to decide what if anything we need to do.)
reconf_queue_flag_ = true;
// If we are here, configuration was valid, at least it parsed correctly
// and therefore contained no invalid values.
// Return the success answer from above.
return (answer);
D2Process::checkQueueStatus() {
switch (queue_mgr_->getMgrState()){
case D2QueueMgr::RUNNING:
if (reconf_queue_flag_ || shouldShutdown()) {
// If we need to reconfigure the queue manager or we have been
// told to shutdown, then stop listening first. Stopping entails
// canceling active listening which may generate an IO event, so
// instigate the stop and get out.
try {
.arg(reconf_queue_flag_ ? "reconfiguration"
: "shutdown");
} catch (const isc::Exception& ex) {
// It is very unlikey that we would experience an error
// here, but theoretically possible.
case D2QueueMgr::STOPPED_QUEUE_FULL: {
// Resume receiving once the queue has decreased by twenty
// percent. This is an arbitrary choice. @todo this value should
// probably be configurable.
size_t threshold = (queue_mgr_->getMaxQueueSize()
if (queue_mgr_->getQueueSize() <= threshold) {
try {
} catch (const isc::Exception& ex) {
// If the receive error is not due to some fallout from shutting
// down then we will attempt to recover by reconfiguring the listener.
// This will close and destruct the current listener and make a new
// one with new resources.
// @todo This may need a safety valve such as retry count or a timer
// to keep from endlessly retrying over and over, with little time
// in between.
if (!shouldShutdown()) {
case D2QueueMgr::STOPPING:
// We are waiting for IO to cancel, so this is a NOP.
// @todo Possible timer for self-defense? We could conceivably
// get into a condition where we never get the event, which would
// leave us stuck in stopping. This is hugely unlikely but possible?
// If the reconfigure flag is set, then we are in a state now where
// we can do the reconfigure. In other words, we aren't RUNNING or
if (reconf_queue_flag_) {
D2Process::reconfigureQueueMgr() {
// Set reconfigure flag to false. We are only here because we have
// a valid configuration to work with so if we fail below, it will be
// an operational issue, such as a busy IP address. That will leave
// queue manager in INITTED state, which is fine.
// What we dont' want is to continually attempt to reconfigure so set
// the flag false now.
// @todo This method assumes only 1 type of listener. This will change
// to support at least a TCP version, possibly some form of RDBMS listener
// as well.
reconf_queue_flag_ = false;
try {
// Wipe out the current listener.
// Get the configuration parameters that affect Queue Manager.
// @todo Need to add parameters for listener TYPE, FORMAT, address reuse
std::string ip_address;
uint32_t port;
getCfgMgr()->getContext()->getParam("ip_address", ip_address);
getCfgMgr()->getContext()->getParam("port", port);
isc::asiolink::IOAddress addr(ip_address);
// Instantiate the listener.
queue_mgr_->initUDPListener(addr, port, dhcp_ddns::FMT_JSON, true);
// Now start it. This assumes that starting is a synchronous,
// blocking call that executes quickly. @todo Should that change then
// we will have to expand the state model to accommodate this.
} catch (const isc::Exception& ex) {
// Queue manager failed to initialize and therefore not listening.
// This is most likely due to an unavailable IP address or port,
// which is a configuration issue.
LOG_ERROR(dctl_logger, DHCP_DDNS_QUEUE_MGR_START_ERROR).arg(ex.what());
D2Process::command(const std::string& command, isc::data::ConstElementPtr args){
D2Process::command(const std::string& command,
isc::data::ConstElementPtr args) {
// @todo This is the initial implementation. If and when D2 is extended
// to support its own commands, this implementation must change. Otherwise
// it should reject all commands as it does now.
......@@ -86,5 +364,31 @@ D2Process::command(const std::string& command, isc::data::ConstElementPtr args){
D2Process::~D2Process() {
D2Process::getD2CfgMgr() {
// The base class gives a base class pointer to our configuration manager.
// Since we are D2, and we need D2 specific extensions, we need a pointer
// to D2CfgMgr for some things.
return (boost::dynamic_pointer_cast<D2CfgMgr>(getCfgMgr()));
const char* D2Process::getShutdownTypeStr(ShutdownType type) {
const char* str = SD_INVALID_STR;
switch (type) {
case SD_NOW:
str = SD_NOW_STR;
return (str);
}; // namespace isc::d2
}; // namespace isc
......@@ -16,6 +16,8 @@
#define D2_PROCESS_H
#include <d2/d_process.h>
#include <d2/d2_queue_mgr.h>
#include <d2/d2_update_mgr.h>
namespace isc {
namespace d2 {
......@@ -27,11 +29,37 @@ namespace d2 {
/// to receive DNS mapping change requests and carry them out.
/// It implements the DProcessBase interface, which structures it such that it
/// is a managed "application", controlled by a management layer.
class D2Process : public DProcessBase {
/// @brief Defines the shutdown types supported by D2Process
enum ShutdownType {
/// @brief Define text labels for the shutdown types.
static const char* SD_NORMAL_STR;
static const char* SD_DRAIN_FIRST_STR;
static const char* SD_NOW_STR;
static const char* SD_INVALID_STR;
/// @brief Defines the point at which to resume receiving requests.
/// If the receive queue has become full, D2Process will "pause" the
/// reception of requests by putting the queue manager in the stopped
/// state. Once the number of entries has decreased to this percentage
/// of the maximum allowed, D2Process will "resume" receiving requests
/// by restarting the queue manager.
static const float QUEUE_RESTART_PERCENT;
/// @brief Constructor
/// The construction process creates the configuration manager, the queue
/// manager, and the update manager.
/// @param name name is a text label for the process. Generally used
/// in log statements, but otherwise arbitrary.
/// @param io_service is the io_service used by the caller for
......@@ -40,27 +68,78 @@ public:
/// @throw DProcessBaseError is io_service is NULL.
D2Process(const char* name, IOServicePtr io_service);
/// @brief Will be used after instantiation to perform initialization
/// unique to D2. @todo This will likely include interactions with
/// QueueMgr and UpdateMgr, to prepare for request receipt and processing.
/// Current implementation successfully does nothing.
/// @throw throws a DProcessBaseError if the initialization fails.
/// @brief Called after instantiation to perform initialization unique to
/// D2.
/// This is called after command line arguments but PRIOR to configuration
/// reception. The base class provides this method as a place to perform
/// any derivation-specific initialization steps that are inapppropriate
/// for the constructor but necessary prior to launch. So far, no such
/// steps have been identified for D2, so its implementantion is empty.
/// @throw DProcessBaseError if the initialization fails.
virtual void init();
/// @brief Implements the process's event loop.
/// The initial implementation is quite basic, surrounding calls to
/// io_service->runOne() with a test of the shutdown flag.
/// Once invoked, the method will continue until the process itself is
/// exiting due to a request to shutdown or some anomaly forces an exit.
/// @throw throws a DProcessBaseError if an error is encountered.
/// Once entered, the main control thread remains inside this method
/// until shutdown. The event loop logic is as follows:
/// {{{
/// while should not down {
/// process queue manager state change
/// process completed jobs