Browse Source

[master] Merged branch 'trac2957'. This adds configuration parsing
support for DHCP-DDNS.

Thomas Markwalder 11 years ago
parent
commit
c04fb71fa4

+ 1 - 0
configure.ac

@@ -1405,6 +1405,7 @@ AC_OUTPUT([doc/version.ent
            src/bin/dhcp4/spec_config.h.pre
            src/bin/dhcp6/spec_config.h.pre
            src/bin/d2/spec_config.h.pre
+           src/bin/d2/tests/test_data_files_config.h
            src/bin/tests/process_rename_test.py
            src/lib/config/tests/data_def_unittests_config.h
            src/lib/python/isc/config/tests/config_test

+ 4 - 1
src/bin/d2/Makefile.am

@@ -48,10 +48,12 @@ pkglibexec_PROGRAMS = b10-dhcp-ddns
 
 b10_dhcp_ddns_SOURCES  = main.cc
 b10_dhcp_ddns_SOURCES += d2_log.cc d2_log.h
-b10_dhcp_ddns_SOURCES += d_process.h
 b10_dhcp_ddns_SOURCES += d2_process.cc d2_process.h
 b10_dhcp_ddns_SOURCES += d_controller.cc d_controller.h
 b10_dhcp_ddns_SOURCES += d2_controller.cc d2_controller.h
+b10_dhcp_ddns_SOURCES += d_cfg_mgr.cc d_cfg_mgr.h
+b10_dhcp_ddns_SOURCES += d2_config.cc d2_config.h
+b10_dhcp_ddns_SOURCES += d2_cfg_mgr.cc d2_cfg_mgr.h
 b10_dhcp_ddns_SOURCES += d2_update_message.cc d2_update_message.h
 b10_dhcp_ddns_SOURCES += d2_zone.cc d2_zone.h
 
@@ -63,6 +65,7 @@ b10_dhcp_ddns_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
 b10_dhcp_ddns_LDADD += $(top_builddir)/src/lib/cc/libb10-cc.la
 b10_dhcp_ddns_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
 b10_dhcp_ddns_LDADD += $(top_builddir)/src/lib/config/libb10-cfgclient.la
+b10_dhcp_ddns_LDADD += $(top_builddir)/src/lib/dhcpsrv/libb10-dhcpsrv.la 
 b10_dhcp_ddns_LDADD += $(top_builddir)/src/lib/dns/libb10-dns++.la
 
 b10_dhcp_ddnsdir = $(pkgdatadir)

+ 132 - 0
src/bin/d2/d2_cfg_mgr.cc

@@ -0,0 +1,132 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <d2/d2_log.h>
+#include <d2/d2_cfg_mgr.h>
+
+#include <boost/foreach.hpp>
+
+using namespace std;
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::data;
+using namespace isc::asiolink;
+
+namespace isc {
+namespace d2 {
+
+// *********************** D2CfgContext  *************************
+
+D2CfgContext::D2CfgContext()
+    : forward_mgr_(new DdnsDomainListMgr("forward_mgr")),
+      reverse_mgr_(new DdnsDomainListMgr("reverse_mgr")),
+      keys_(new TSIGKeyInfoMap()) {
+}
+
+D2CfgContext::D2CfgContext(const D2CfgContext& rhs) : DCfgContextBase(rhs) {
+    if (rhs.forward_mgr_) {
+        forward_mgr_.reset(new DdnsDomainListMgr(rhs.forward_mgr_->getName()));
+        forward_mgr_->setDomains(rhs.forward_mgr_->getDomains());
+    }
+
+    if (rhs.reverse_mgr_) {
+        reverse_mgr_.reset(new DdnsDomainListMgr(rhs.reverse_mgr_->getName()));
+        reverse_mgr_->setDomains(rhs.reverse_mgr_->getDomains());
+    }
+
+    keys_ = rhs.keys_; 
+}
+
+D2CfgContext::~D2CfgContext() {
+}
+
+// *********************** D2CfgMgr  *************************
+
+D2CfgMgr::D2CfgMgr() : DCfgMgrBase(DCfgContextBasePtr(new D2CfgContext())) {
+    // TSIG keys need to parse before the Domains, so we can catch Domains
+    // that specify undefined keys. Create the necessary parsing order now.
+    addToParseOrder("interface");
+    addToParseOrder("ip_address");
+    addToParseOrder("port");
+    addToParseOrder("tsig_keys");
+    addToParseOrder("forward_ddns");
+    addToParseOrder("reverse_ddns");
+}
+
+D2CfgMgr::~D2CfgMgr() {
+}
+
+bool
+D2CfgMgr::matchForward(const std::string& fqdn, DdnsDomainPtr& domain) {
+    if (fqdn.empty()) {
+        // This is a programmatic error and should not happen.
+        isc_throw(D2CfgError, "matchForward passed an empty fqdn");
+    }
+
+    // Fetch the forward manager from the D2 context.
+    DdnsDomainListMgrPtr mgr = getD2CfgContext()->getForwardMgr();
+
+    // Call the manager's match method and return the result.
+    return (mgr->matchDomain(fqdn, domain));
+}
+
+bool
+D2CfgMgr::matchReverse(const std::string& fqdn, DdnsDomainPtr& domain) {
+    if (fqdn.empty()) {
+        // This is a programmatic error and should not happen.
+        isc_throw(D2CfgError, "matchReverse passed a null or empty fqdn");
+    }
+
+    // Fetch the reverse manager from the D2 context.
+    DdnsDomainListMgrPtr mgr = getD2CfgContext()->getReverseMgr();
+
+    // Call the manager's match method and return the result.
+    return (mgr->matchDomain(fqdn, domain));
+}
+
+
+isc::dhcp::ParserPtr
+D2CfgMgr::createConfigParser(const std::string& config_id) {
+    // Get D2 specific context.
+    D2CfgContextPtr context = getD2CfgContext();
+
+    // Create parser instance based on element_id.
+    DhcpConfigParser* parser = NULL;
+    if ((config_id == "interface")  ||
+        (config_id == "ip_address")) {
+        parser = new StringParser(config_id, context->getStringStorage());
+    } else if (config_id == "port") {
+        parser = new Uint32Parser(config_id, context->getUint32Storage());
+    } else if (config_id ==  "forward_ddns") {
+        parser = new DdnsDomainListMgrParser("forward_mgr",
+                                             context->getForwardMgr(),
+                                             context->getKeys());
+    } else if (config_id ==  "reverse_ddns") {
+        parser = new DdnsDomainListMgrParser("reverse_mgr",
+                                             context->getReverseMgr(),
+                                             context->getKeys());
+    } else if (config_id ==  "tsig_keys") {
+        parser = new TSIGKeyInfoListParser("tsig_key_list", context->getKeys());
+    } else {
+        isc_throw(NotImplemented,
+                  "parser error: D2CfgMgr parameter not supported: "
+                  << config_id);
+    }
+
+    return (isc::dhcp::ParserPtr(parser));
+}
+
+}; // end of isc::dhcp namespace
+}; // end of isc namespace
+

+ 175 - 0
src/bin/d2/d2_cfg_mgr.h

@@ -0,0 +1,175 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef D2_CFG_MGR_H
+#define D2_CFG_MGR_H
+
+#include <asiolink/io_address.h>
+#include <cc/data.h>
+#include <exceptions/exceptions.h>
+#include <d2/d_cfg_mgr.h>
+#include <d2/d2_config.h>
+
+#include <stdint.h>
+#include <string>
+
+namespace isc {
+namespace d2 {
+
+class D2CfgContext;
+/// @brief Pointer to a configuration context.
+typedef boost::shared_ptr<D2CfgContext> D2CfgContextPtr;
+
+/// @brief  DHCP-DDNS Configuration Context
+///
+/// Implements the storage container for configuration context.
+/// It provides a single enclosure for the storage of configuration parameters
+/// and any other DHCP-DDNS specific information that needs to be accessible
+/// during configuration parsing as well as to the application as a whole.
+/// It is derived from the context base class, DCfgContextBase.
+class D2CfgContext : public DCfgContextBase {
+public:
+    /// @brief Constructor
+    D2CfgContext();
+
+    /// @brief Destructor
+    virtual ~D2CfgContext();
+
+    /// @brief Creates a clone of this context object.
+    ///
+    /// @return returns a pointer to the new clone.
+    virtual DCfgContextBasePtr clone() {
+        return (DCfgContextBasePtr(new D2CfgContext(*this)));
+    }
+
+    /// @brief Fetches the forward DNS domain list manager.
+    ///
+    /// @return returns a pointer to the forward manager.
+    DdnsDomainListMgrPtr getForwardMgr() {
+        return (forward_mgr_);
+    }
+
+    /// @brief Fetches the reverse DNS domain list manager.
+    ///
+    /// @return returns a pointer to the reverse manager.
+    DdnsDomainListMgrPtr getReverseMgr() {
+        return (reverse_mgr_);
+    }
+
+    /// @brief Fetches the map of TSIG keys.
+    ///
+    /// @return returns a pointer to the key map.
+    TSIGKeyInfoMapPtr getKeys() {
+        return (keys_);
+    }
+
+protected:
+    /// @brief Copy constructor for use by derivations in clone().
+    D2CfgContext(const D2CfgContext& rhs);
+
+private:
+    /// @brief Private assignment operator to avoid potential for slicing.
+    D2CfgContext& operator=(const D2CfgContext& rhs);
+
+    /// @brief Forward domain list manager.
+    DdnsDomainListMgrPtr forward_mgr_;
+
+    /// @brief Reverse domain list manager.
+    DdnsDomainListMgrPtr reverse_mgr_;
+
+    /// @brief Storage for the map of TSIGKeyInfos
+    TSIGKeyInfoMapPtr keys_;
+};
+
+/// @brief Defines a pointer for DdnsDomain instances.
+typedef boost::shared_ptr<DdnsDomainListMgr> DdnsDomainListMgrPtr;
+
+
+
+/// @brief DHCP-DDNS Configuration Manager
+///
+/// Provides the mechanisms for managing the DHCP-DDNS application's
+/// configuration.  This includes services for parsing sets of configuration
+/// values, storing the parsed information in its converted form,
+/// and retrieving the information on demand.
+class D2CfgMgr : public DCfgMgrBase {
+public:
+    /// @brief Constructor
+    D2CfgMgr();
+
+    /// @brief Destructor
+    virtual ~D2CfgMgr();
+
+    /// @brief Convenience method that returns the D2 configuration context.
+    ///
+    /// @return returns a pointer to the configuration context.
+    D2CfgContextPtr getD2CfgContext() {
+        return (boost::dynamic_pointer_cast<D2CfgContext>(getContext()));
+    }
+
+    /// @brief Matches a given FQDN to a forward domain.
+    /// 
+    /// This calls the matchDomain method of the forward domain manager to
+    /// match the given FQDN to a forward domain.  
+    ///
+    /// @param fqdn is the name for which to look.
+    /// @param domain receives the matching domain. Note that it will be reset
+    /// upon entry and only set if a match is subsequently found.
+    ///
+    /// @return returns true if a match is found, false otherwise.
+    /// @throw throws D2CfgError if given an invalid fqdn. 
+    bool matchForward(const std::string& fqdn, DdnsDomainPtr &domain);
+
+    /// @brief Matches a given FQDN to a reverse domain.
+    ///
+    /// This calls the matchDomain method of the reverse domain manager to
+    /// match the given FQDN to a forward domain.  
+    ///
+    /// @param fqdn is the name for which to look.
+    /// @param domain receives the matching domain. Note that it will be reset
+    /// upon entry and only set if a match is subsequently found.
+    ///
+    /// @return returns true if a match is found, false otherwise.
+    /// @throw throws D2CfgError if given an invalid fqdn. 
+    bool matchReverse(const std::string& fqdn, DdnsDomainPtr &domain);
+
+protected:
+    /// @brief Given an element_id returns an instance of the appropriate
+    /// parser.
+    ///
+    /// It is responsible for top-level or outermost DHCP-DDNS configuration
+    /// elements (see dhcp-ddns.spec):
+    ///     1. interface
+    ///     2. ip_address
+    ///     3. port
+    ///     4. forward_ddns
+    ///     5. reverse_ddns
+    ///
+    /// @param element_id is the string name of the element as it will appear
+    /// in the configuration set.
+    ///
+    /// @return returns a ParserPtr to the parser instance.
+    /// @throw throws DCfgMgrBaseError if an error occurs.
+    virtual isc::dhcp::ParserPtr
+    createConfigParser(const std::string& element_id);
+};
+
+/// @brief Defines a shared pointer to D2CfgMgr.
+typedef boost::shared_ptr<D2CfgMgr> D2CfgMgrPtr;
+
+
+}; // end of isc::d2 namespace
+}; // end of isc namespace
+
+#endif // D2_CFG_MGR_H

+ 605 - 0
src/bin/d2/d2_config.cc

@@ -0,0 +1,605 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <d2/d2_log.h>
+#include <d2/d2_cfg_mgr.h>
+#include <dhcpsrv/dhcp_parsers.h>
+#include <exceptions/exceptions.h>
+#include <asiolink/io_error.h>
+
+#include <boost/foreach.hpp>
+#include <boost/lexical_cast.hpp>
+
+namespace isc {
+namespace d2 {
+
+// *********************** TSIGKeyInfo  *************************
+
+TSIGKeyInfo::TSIGKeyInfo(const std::string& name, const std::string& algorithm,
+                         const std::string& secret)
+    :name_(name), algorithm_(algorithm), secret_(secret) {
+}
+
+TSIGKeyInfo::~TSIGKeyInfo() {
+}
+
+
+// *********************** DnsServerInfo  *************************
+
+const char* DnsServerInfo::EMPTY_IP_STR = "0.0.0.0";
+
+DnsServerInfo::DnsServerInfo(const std::string& hostname,
+                             isc::asiolink::IOAddress ip_address, uint32_t port,
+                             bool enabled)
+    :hostname_(hostname), ip_address_(ip_address), port_(port),
+    enabled_(enabled) {
+}
+
+DnsServerInfo::~DnsServerInfo() {
+}
+
+// *********************** DdnsDomain  *************************
+
+DdnsDomain::DdnsDomain(const std::string& name, const std::string& key_name,
+                       DnsServerInfoStoragePtr servers)
+    : name_(name), key_name_(key_name), servers_(servers) {
+}
+
+DdnsDomain::~DdnsDomain() {
+}
+
+// *********************** DdnsDomainLstMgr  *************************
+
+const char* DdnsDomainListMgr::wildcard_domain_name_ = "*";
+
+DdnsDomainListMgr::DdnsDomainListMgr(const std::string& name) : name_(name),
+    domains_(new DdnsDomainMap()) {
+}
+
+
+DdnsDomainListMgr::~DdnsDomainListMgr () {
+}
+
+void
+DdnsDomainListMgr::setDomains(DdnsDomainMapPtr domains) {
+    if (!domains) {
+        isc_throw(D2CfgError,
+                  "DdnsDomainListMgr::setDomains: Domain list may not be null");
+    }
+
+    domains_ = domains;
+
+    // Look for the wild card domain. If present, set the member variable
+    // to remember it.  This saves us from having to look for it every time
+    // we attempt a match.
+    DdnsDomainMap::iterator gotit = domains_->find(wildcard_domain_name_);
+    if (gotit != domains_->end()) {
+            wildcard_domain_ = gotit->second;
+    }
+}
+
+bool
+DdnsDomainListMgr::matchDomain(const std::string& fqdn, DdnsDomainPtr& domain) {
+    // Clear the return parameter.
+    domain.reset();
+
+    // First check the case of one domain to rule them all.
+    if ((size() == 1) && (wildcard_domain_)) {
+        domain = wildcard_domain_;
+        return (true);
+    }
+
+    // Start with the longest version of the fqdn and search the list.
+    // Continue looking for shorter versions of fqdn so long as no match is
+    // found.
+    // @todo This can surely be optimized, time permitting.
+    std::string match_name = fqdn;
+    std::size_t start_pos = 0;
+    while (start_pos != std::string::npos) {
+        match_name = match_name.substr(start_pos, std::string::npos);
+        DdnsDomainMap::iterator gotit = domains_->find(match_name);
+        if (gotit != domains_->end()) {
+            domain = gotit->second;
+            return (true);
+        }
+
+        start_pos = match_name.find_first_of(".");
+        if (start_pos != std::string::npos) {
+            ++start_pos;
+        }
+    }
+
+    // There's no match. If they specified a wild card domain use it
+    // otherwise there's no domain for this entry.
+    if (wildcard_domain_) {
+        domain = wildcard_domain_;
+        return (true);
+    }
+
+    LOG_WARN(dctl_logger, DHCP_DDNS_NO_MATCH).arg(fqdn);
+    return (false);
+}
+
+// *************************** PARSERS ***********************************
+
+// *********************** TSIGKeyInfoParser  *************************
+
+TSIGKeyInfoParser::TSIGKeyInfoParser(const std::string& entry_name,
+    TSIGKeyInfoMapPtr keys)
+    : entry_name_(entry_name), keys_(keys), local_scalars_() {
+    if (!keys_) {
+        isc_throw(D2CfgError, "TSIGKeyInfoParser ctor:"
+                  " key storage cannot be null");
+    }
+}
+
+TSIGKeyInfoParser::~TSIGKeyInfoParser() {
+}
+
+void
+TSIGKeyInfoParser::build(isc::data::ConstElementPtr key_config) {
+    isc::dhcp::ConfigPair config_pair;
+    // For each element in the key configuration:
+    // 1. Create a parser for the element.
+    // 2. Invoke the parser's build method passing in the element's
+    // configuration.
+    // 3. Invoke the parser's commit method to store the element's parsed
+    // data to the parser's local storage.
+    BOOST_FOREACH (config_pair, key_config->mapValue()) {
+        isc::dhcp::ParserPtr parser(createConfigParser(config_pair.first));
+        parser->build(config_pair.second);
+        parser->commit();
+    }
+}
+
+isc::dhcp::ParserPtr
+TSIGKeyInfoParser::createConfigParser(const std::string& config_id) {
+    DhcpConfigParser* parser = NULL;
+    // Based on the configuration id of the element, create the appropriate
+    // parser. Scalars are set to use the parser's local scalar storage.
+    if ((config_id == "name")  ||
+        (config_id == "algorithm") ||
+        (config_id == "secret")) {
+        parser = new isc::dhcp::StringParser(config_id,
+                                             local_scalars_.getStringStorage());
+    } else {
+        isc_throw(NotImplemented,
+                  "parser error: TSIGKeyInfo parameter not supported: "
+                  << config_id);
+    }
+
+    // Return the new parser instance.
+    return (isc::dhcp::ParserPtr(parser));
+}
+
+void
+TSIGKeyInfoParser::commit() {
+    std::string name;
+    std::string algorithm;
+    std::string secret;
+
+    // Fetch the key configuration's parsed scalar values from parser's
+    // local storage.
+    local_scalars_.getParam("name", name);
+    local_scalars_.getParam("algorithm", algorithm);
+    local_scalars_.getParam("secret", secret);
+
+    // @todo Validation here is very superficial. This will expand as TSIG
+    // Key use is more fully implemented.
+
+    // Name cannot be blank.
+    if (name.empty()) {
+        isc_throw(D2CfgError, "TSIG Key Info must specify name");
+    }
+
+    // Algorithme cannot be blank.
+    if (algorithm.empty()) {
+        isc_throw(D2CfgError, "TSIG Key Info must specify algorithm");
+    }
+
+    // Secret cannot be blank.
+    if (secret.empty()) {
+        isc_throw(D2CfgError, "TSIG Key Info must specify secret");
+    }
+
+    // Currently, the premise is that key storage is always empty prior to
+    // parsing so we are always adding keys never replacing them. Duplicates
+    // are not allowed and should be flagged as a configuration error.
+    if (keys_->find(name) != keys_->end()) {
+        isc_throw(D2CfgError, "Duplicate TSIG key specified:" << name);
+    }
+
+    TSIGKeyInfoPtr key_info(new TSIGKeyInfo(name, algorithm, secret));
+
+    // Add the new TSIGKeyInfo to the key storage.
+    (*keys_)[name]=key_info;
+}
+
+// *********************** TSIGKeyInfoListParser  *************************
+
+TSIGKeyInfoListParser::TSIGKeyInfoListParser(const std::string& list_name,
+                                       TSIGKeyInfoMapPtr keys)
+    :list_name_(list_name), keys_(keys), parsers_() {
+    if (!keys_) {
+        isc_throw(D2CfgError, "TSIGKeyInfoListParser ctor:"
+                  " key storage cannot be null");
+    }
+}
+
+TSIGKeyInfoListParser::~TSIGKeyInfoListParser(){
+}
+
+void
+TSIGKeyInfoListParser::
+build(isc::data::ConstElementPtr key_list){
+    int i = 0;
+    isc::data::ConstElementPtr key_config;
+    // For each key element in the key list:
+    // 1. Create a parser for the key element.
+    // 2. Invoke the parser's build method passing in the key's
+    // configuration.
+    // 3. Add the parser to a local collection of parsers.
+    BOOST_FOREACH(key_config, key_list->listValue()) {
+        // 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,
+                                                            keys_));
+        parser->build(key_config);
+        parsers_.push_back(parser);
+    }
+}
+
+void
+TSIGKeyInfoListParser::commit() {
+    // Invoke commit on each server parser. This will cause each one to
+    // create it's server instance and commit it to storage.
+    BOOST_FOREACH(isc::dhcp::ParserPtr parser, parsers_) {
+        parser->commit();
+    }
+}
+
+// *********************** DnsServerInfoParser  *************************
+
+DnsServerInfoParser::DnsServerInfoParser(const std::string& entry_name,
+    DnsServerInfoStoragePtr servers)
+    : entry_name_(entry_name), servers_(servers), local_scalars_() {
+    if (!servers_) {
+        isc_throw(D2CfgError, "DnsServerInfoParser ctor:"
+                  " server storage cannot be null");
+    }
+}
+
+DnsServerInfoParser::~DnsServerInfoParser() {
+}
+
+void
+DnsServerInfoParser::build(isc::data::ConstElementPtr server_config) {
+    isc::dhcp::ConfigPair config_pair;
+    // For each element in the server configuration:
+    // 1. Create a parser for the element.
+    // 2. Invoke the parser's build method passing in the element's
+    // configuration.
+    // 3. Invoke the parser's commit method to store the element's parsed
+    // data to the parser's local storage.
+    BOOST_FOREACH (config_pair, server_config->mapValue()) {
+        isc::dhcp::ParserPtr parser(createConfigParser(config_pair.first));
+        parser->build(config_pair.second);
+        parser->commit();
+    }
+
+}
+
+isc::dhcp::ParserPtr
+DnsServerInfoParser::createConfigParser(const std::string& config_id) {
+    DhcpConfigParser* parser = NULL;
+    // Based on the configuration id of the element, create the appropriate
+    // parser. Scalars are set to use the parser's local scalar storage.
+    if ((config_id == "hostname")  ||
+        (config_id == "ip_address")) {
+        parser = new isc::dhcp::StringParser(config_id,
+                                             local_scalars_.getStringStorage());
+    } else if (config_id == "port") {
+        parser = new isc::dhcp::Uint32Parser(config_id,
+                                             local_scalars_.getUint32Storage());
+    } else {
+        isc_throw(NotImplemented,
+                  "parser error: DnsServerInfo parameter not supported: "
+                  << config_id);
+    }
+
+    // Return the new parser instance.
+    return (isc::dhcp::ParserPtr(parser));
+}
+
+void
+DnsServerInfoParser::commit() {
+    std::string hostname;
+    std::string ip_address;
+    uint32_t port = DnsServerInfo::STANDARD_DNS_PORT;
+
+    // Fetch the server configuration's parsed scalar values from parser's
+    // local storage.
+    local_scalars_.getParam("hostname", hostname, DCfgContextBase::OPTIONAL);
+    local_scalars_.getParam("ip_address", ip_address,
+                            DCfgContextBase::OPTIONAL);
+    local_scalars_.getParam("port", port, DCfgContextBase::OPTIONAL);
+
+    // The configuration must specify one or the other.
+    if (hostname.empty() == ip_address.empty()) {
+        isc_throw(D2CfgError, "Dns Server must specify one or the other"
+                  " of hostname and IP address");
+    }
+
+    DnsServerInfoPtr serverInfo;
+    if (!hostname.empty()) {
+        // When  hostname is specified, create a valid, blank IOAddress and
+        // then create the DnsServerInfo.
+        isc::asiolink::IOAddress io_addr(DnsServerInfo::EMPTY_IP_STR);
+        serverInfo.reset(new DnsServerInfo(hostname, io_addr, port));
+    } else {
+        try {
+            // Create an IOAddress from the IP address string given and then
+            // create the DnsServerInfo.
+            isc::asiolink::IOAddress io_addr(ip_address);
+            serverInfo.reset(new DnsServerInfo(hostname, io_addr, port));
+        } catch (const isc::asiolink::IOError& ex) {
+            isc_throw(D2CfgError, "Invalid IP address:" << ip_address);
+        }
+    }
+
+    // Add the new DnsServerInfo to the server storage.
+    servers_->push_back(serverInfo);
+}
+
+// *********************** DnsServerInfoListParser  *************************
+
+DnsServerInfoListParser::DnsServerInfoListParser(const std::string& list_name,
+                                       DnsServerInfoStoragePtr servers)
+    :list_name_(list_name), servers_(servers), parsers_() {
+    if (!servers_) {
+        isc_throw(D2CfgError, "DdnsServerInfoListParser ctor:"
+                  " server storage cannot be null");
+    }
+}
+
+DnsServerInfoListParser::~DnsServerInfoListParser(){
+}
+
+void
+DnsServerInfoListParser::
+build(isc::data::ConstElementPtr server_list){
+    int i = 0;
+    isc::data::ConstElementPtr server_config;
+    // For each server element in the server list:
+    // 1. Create a parser for the server element.
+    // 2. Invoke the parser's build method passing in the server's
+    // configuration.
+    // 3. Add the parser to a local collection of parsers.
+    BOOST_FOREACH(server_config, server_list->listValue()) {
+        // 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 DnsServerInfoParser(entry_name,
+                                                            servers_));
+        parser->build(server_config);
+        parsers_.push_back(parser);
+    }
+}
+
+void
+DnsServerInfoListParser::commit() {
+    // Domains must have at least one server.
+    if (parsers_.size() == 0) {
+        isc_throw (D2CfgError, "Server List must contain at least one server");
+    }
+
+    // Invoke commit on each server parser. This will cause each one to
+    // create it's server instance and commit it to storage.
+    BOOST_FOREACH(isc::dhcp::ParserPtr parser, parsers_) {
+        parser->commit();
+    }
+}
+
+// *********************** DdnsDomainParser  *************************
+
+DdnsDomainParser::DdnsDomainParser(const std::string& entry_name,
+                                   DdnsDomainMapPtr domains,
+                                   TSIGKeyInfoMapPtr keys)
+    : entry_name_(entry_name), domains_(domains), keys_(keys),
+    local_servers_(new DnsServerInfoStorage()), local_scalars_() {
+    if (!domains_) {
+        isc_throw(D2CfgError,
+                  "DdnsDomainParser ctor, domain storage cannot be null");
+    }
+}
+
+
+DdnsDomainParser::~DdnsDomainParser() {
+}
+
+void
+DdnsDomainParser::build(isc::data::ConstElementPtr domain_config) {
+    // For each element in the domain configuration:
+    // 1. Create a parser for the element.
+    // 2. Invoke the parser's build method passing in the element's
+    // configuration.
+    // 3. Invoke the parser's commit method to store the element's parsed
+    // data to the parser's local storage.
+    isc::dhcp::ConfigPair config_pair;
+    BOOST_FOREACH(config_pair, domain_config->mapValue()) {
+        isc::dhcp::ParserPtr parser(createConfigParser(config_pair.first));
+        parser->build(config_pair.second);
+        parser->commit();
+    }
+}
+
+isc::dhcp::ParserPtr
+DdnsDomainParser::createConfigParser(const std::string& config_id) {
+    DhcpConfigParser* parser = NULL;
+    // Based on the configuration id of the element, create the appropriate
+    // parser. Scalars are set to use the parser's local scalar storage.
+    if ((config_id == "name")  ||
+        (config_id == "key_name")) {
+        parser = new isc::dhcp::StringParser(config_id,
+                                             local_scalars_.getStringStorage());
+    } else if (config_id == "dns_servers") {
+       // Server list parser is given in our local server storage. It will pass
+       // this down to its server parsers and is where they will write their
+       // server instances upon commit.
+       parser = new DnsServerInfoListParser(config_id, local_servers_);
+    } else {
+       isc_throw(NotImplemented,
+                "parser error: DdnsDomain parameter not supported: "
+                << config_id);
+    }
+
+    // Return the new domain parser instance.
+    return (isc::dhcp::ParserPtr(parser));
+}
+
+void
+DdnsDomainParser::commit() {
+    std::string name;
+    std::string key_name;
+
+    // Domain name is not optional. The get will throw if its not there.
+    local_scalars_.getParam("name", name);
+
+    // Blank domain names are not allowed.
+    if (name.empty()) {
+        isc_throw(D2CfgError, "Domain name cannot be blank");
+    }
+
+    // Currently, the premise is that domain storage is always empty
+    // prior to parsing so always adding domains never replacing them.
+    // Duplicates are not allowed and should be flagged as a configuration
+    // error.
+    if (domains_->find(name) != domains_->end()) {
+        isc_throw(D2CfgError, "Duplicate domain specified:" << name);
+    }
+
+    // Key name is optional. If it is not blank, then validate it against
+    // the defined list of keys.
+    local_scalars_.getParam("key_name", key_name, DCfgContextBase::OPTIONAL);
+    if (!key_name.empty()) {
+        if ((!keys_) || (keys_->find(key_name) == keys_->end())) {
+            isc_throw(D2CfgError, "DdnsDomain :" << name <<
+                     " specifies and undefined key:" << key_name);
+        }
+    }
+
+    // Instantiate the new domain and add it to domain storage.
+    DdnsDomainPtr domain(new DdnsDomain(name, key_name, local_servers_));
+
+    // Add the new domain to the domain storage.
+    (*domains_)[name]=domain;
+}
+
+// *********************** DdnsDomainListParser  *************************
+
+DdnsDomainListParser::DdnsDomainListParser(const std::string& list_name,
+                                           DdnsDomainMapPtr domains,
+                                           TSIGKeyInfoMapPtr keys)
+    :list_name_(list_name), domains_(domains), keys_(keys), parsers_() {
+    if (!domains_) {
+        isc_throw(D2CfgError, "DdnsDomainListParser ctor:"
+                  " domain storage cannot be null");
+    }
+}
+
+DdnsDomainListParser::~DdnsDomainListParser(){
+}
+
+void
+DdnsDomainListParser::
+build(isc::data::ConstElementPtr domain_list){
+    // For each domain element in the domain list:
+    // 1. Create a parser for the domain element.
+    // 2. Invoke the parser's build method passing in the domain's
+    // configuration.
+    // 3. Add the parser to the local collection of parsers.
+    int i = 0;
+    isc::data::ConstElementPtr domain_config;
+    BOOST_FOREACH(domain_config, domain_list->listValue()) {
+        std::string entry_name = boost::lexical_cast<std::string>(i++);
+        isc::dhcp::ParserPtr parser(new DdnsDomainParser(entry_name,
+                                                         domains_, keys_));
+        parser->build(domain_config);
+        parsers_.push_back(parser);
+    }
+}
+
+void
+DdnsDomainListParser::commit() {
+    // Invoke commit on each server parser. This will cause each one to
+    // create it's server instance and commit it to storage.
+    BOOST_FOREACH(isc::dhcp::ParserPtr parser, parsers_) {
+        parser->commit();
+    }
+}
+
+
+// *********************** DdnsDomainListMgrParser  *************************
+
+DdnsDomainListMgrParser::DdnsDomainListMgrParser(const std::string& entry_name,
+                              DdnsDomainListMgrPtr mgr, TSIGKeyInfoMapPtr keys)
+    : entry_name_(entry_name), mgr_(mgr), keys_(keys),
+    local_domains_(new DdnsDomainMap()), local_scalars_() {
+}
+
+
+DdnsDomainListMgrParser::~DdnsDomainListMgrParser() {
+}
+
+void
+DdnsDomainListMgrParser::build(isc::data::ConstElementPtr domain_config) {
+    // For each element in the domain manager configuration:
+    // 1. Create a parser for the element.
+    // 2. Invoke the parser's build method passing in the element's
+    // configuration.
+    // 3. Invoke the parser's commit method to store the element's parsed
+    // data to the parser's local storage.
+    isc::dhcp::ConfigPair config_pair;
+    BOOST_FOREACH(config_pair, domain_config->mapValue()) {
+        isc::dhcp::ParserPtr parser(createConfigParser(config_pair.first));
+        parser->build(config_pair.second);
+        parser->commit();
+    }
+}
+
+isc::dhcp::ParserPtr
+DdnsDomainListMgrParser::createConfigParser(const std::string& config_id) {
+    DhcpConfigParser* parser = NULL;
+    if (config_id == "ddns_domains") {
+       // Domain list parser is given our local domain storage. It will pass
+       // this down to its domain parsers and is where they will write their
+       // domain instances upon commit.
+       parser = new DdnsDomainListParser(config_id, local_domains_, keys_);
+    } else {
+       isc_throw(NotImplemented, "parser error: "
+                 "DdnsDomainListMgr parameter not supported: " << config_id);
+    }
+
+    // Return the new domain parser instance.
+    return (isc::dhcp::ParserPtr(parser));
+}
+
+void
+DdnsDomainListMgrParser::commit() {
+    // Add the new domain to the domain storage.
+    mgr_->setDomains(local_domains_);
+}
+
+
+}; // end of isc::dhcp namespace
+}; // end of isc namespace

+ 941 - 0
src/bin/d2/d2_config.h

@@ -0,0 +1,941 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef D2_CONFIG_H
+#define D2_CONFIG_H
+
+#include <asiolink/io_address.h>
+#include <cc/data.h>
+#include <d2/d_cfg_mgr.h>
+#include <dhcpsrv/dhcp_parsers.h>
+#include <exceptions/exceptions.h>
+
+#include <boost/foreach.hpp>
+
+#include <stdint.h>
+#include <string>
+
+namespace isc {
+namespace d2 {
+
+/// @file d2_config.h
+/// @brief A collection of classes for housing and parsing the application
+/// configuration necessary for the DHCP-DDNS application (aka D2).
+///
+/// This file contains the class declarations for the class hierarchy created
+/// from the D2 configuration and the parser classes used to create it.
+/// The application configuration consists of a set of scalar parameters,
+/// a list of TSIG keys, and two managed lists of domains: one list for
+/// forward domains and one list for reverse domains.
+///
+/// The key list consists of one or more TSIG keys, each entry described by
+/// a name, the algorithm method name, and its secret key component.
+///
+/// @todo  NOTE that TSIG configuration parsing is functional, the use of
+/// TSIG Keys during the actual DNS update transactions is not.  This will be
+/// implemented in a future release.
+///
+/// Each managed domain list consists of a list one or more domains and is
+/// represented by the class DdnsDomainListMgr.
+///
+/// Each domain consists of a set of scalars parameters and a list of DNS
+/// servers which support that domain. Among its scalars, is key_name, which
+/// is the name of the TSIG Key to use for with this domain.  This value should
+/// map to one of the TSIG Keys in the key list.  Domains are represented by
+/// the class, DdnsDomain.
+///
+/// Each server consists of a set of scalars used to describe the server such
+/// that the application can carry out DNS update exchanges with it. Servers
+/// are represented by the class, DnsServerInfo.
+///
+/// The configuration specification for use with BIND10 is detailed in the file
+/// dhcp-ddns.spec.
+///
+/// The parsing class hierarchy reflects this same scheme.  Working top down:
+///
+/// A DdnsDomainListMgrParser parses a managed domain list entry. It handles
+/// any scalars which belong to the manager as well as creating and invoking a
+/// DdnsDomainListParser to parse its list of domain entries.
+///
+/// A DdnsDomainLiatParser creates and invokes DdnsDomainListParser for each
+/// domain entry in its list.
+///
+/// A DdnsDomainParser handles the scalars which belong to the domain as well as
+/// creating and invoking a DnsSeverInfoListParser to parse its list of server
+/// entries.
+///
+/// A DnsServerInfoListParser creates and invokes a DnsServerInfoParser for
+/// each server entry in its list.
+///
+/// A DdnsServerInfoParser handles the scalars which belong to the server.
+/// The following is sample configuration in JSON form with extra spacing
+/// for clarity:
+///
+/// @code
+/// {
+///  "interface" : "eth1" ,
+///  "ip_address" : "192.168.1.33" ,
+///  "port" : 88 ,
+///  "tsig_keys":
+//// [
+///    {
+///     "name": "d2_key.tmark.org" ,
+///     "algorithm": "md5" ,
+///     "secret": "0123456989"
+///    }
+///  ],
+///  "forward_ddns" :
+///  {
+///    "ddns_domains":
+///    [
+///      {
+///        "name": "tmark.org." ,
+///        "key_name": "d2_key.tmark.org" ,
+///        "dns_servers" :
+///        [
+///          { "hostname": "fserver.tmark.org" },
+///          { "hostname": "f2server.tmark.org" }
+///        ]
+///      },
+///      {
+///        "name": "pub.tmark.org." ,
+///        "key_name": "d2_key.tmark.org" ,
+///        "dns_servers" :
+///        [
+///          { "hostname": "f3server.tmark.org" }
+///        ]
+///      }
+///    ]
+///  },
+///  "reverse_ddns" :
+///  {
+///    "ddns_domains":
+///    [
+///      {
+///        "name": " 0.168.192.in.addr.arpa." ,
+///        "key_name": "d2_key.tmark.org" ,
+///        "dns_servers" :
+///        [
+///          { "ip_address": "127.0.0.101" , "port": 100 }
+///        ]
+///      }
+///    ]
+///  }
+/// }
+/// @endcode
+
+/// @brief Exception thrown when the error during configuration handling
+/// occurs.
+class D2CfgError : public isc::Exception {
+public:
+    D2CfgError(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+/// @brief Represents a TSIG Key.
+///
+/// Currently, this is simple storage class containing the basic attributes of
+/// a TSIG Key.  It is intended primarily as a reference for working with
+/// actual keys and may eventually be replaced by isc::dns::TSIGKey.  TSIG Key
+/// functionality at this stage is strictly limited to configuration parsing.
+/// @todo full functionality for using TSIG during DNS updates will be added
+/// in a future release.
+class TSIGKeyInfo {
+public:
+
+    /// @brief Constructor
+    ///
+    /// @param name the unique label used to identify this key
+    /// @param algorithm the name of the encryption alogirthm this key uses.
+    /// (@todo This will be a fixed list of choices)
+    /// @param secret the secret component of this key
+    TSIGKeyInfo(const std::string& name, const std::string& algorithm,
+                const std::string& secret);
+
+    /// @brief Destructor
+    virtual ~TSIGKeyInfo();
+
+    /// @brief Getter which returns the key's name.
+    ///
+    /// @return returns the name as as std::string.
+    const std::string getName() const {
+        return (name_);
+    }
+
+    /// @brief Getter which returns the key's algorithm.
+    ///
+    /// @return returns the algorithm as as std::string.
+    const std::string getAlgorithm() const {
+        return (algorithm_);
+    }
+
+    /// @brief Getter which returns the key's secret.
+    ///
+    /// @return returns the secret as as std::string.
+    const std::string getSecret() const {
+        return (secret_);
+    }
+
+private:
+    /// @brief The name of the key.
+    ///
+    /// This value is the unique identifeir thay domains use to
+    /// to specify which TSIG key they need.
+    std::string name_;
+
+    /// @brief The algorithm that should be used for this key.
+    std::string algorithm_;
+
+    /// @brief The secret value component of this key.
+    std::string secret_;
+};
+
+/// @brief Defines a pointer for TSIGKeyInfo instances.
+typedef boost::shared_ptr<TSIGKeyInfo> TSIGKeyInfoPtr;
+
+/// @brief Defines a map of TSIGKeyInfos, keyed by the name.
+typedef std::map<std::string, TSIGKeyInfoPtr> TSIGKeyInfoMap;
+
+/// @brief Defines a iterator pairing of name and TSIGKeyInfo
+typedef std::pair<std::string, TSIGKeyInfoPtr> TSIGKeyInfoMapPair;
+
+/// @brief Defines a pointer to map of TSIGkeyInfos
+typedef boost::shared_ptr<TSIGKeyInfoMap> TSIGKeyInfoMapPtr;
+
+
+/// @brief Represents a specific DNS Server.
+/// It provides information about the server's network identity and typically
+/// belongs to a list of servers supporting DNS for a given domain. It will
+/// be used to establish communications with the server to carry out DNS
+/// updates.
+class DnsServerInfo {
+public:
+
+    /// @brief defines DNS standard port value
+    static const uint32_t STANDARD_DNS_PORT = 53;
+
+    /// @brief defines an "empty" string version of an ip address.
+    static const char* EMPTY_IP_STR;
+
+
+    /// @brief Constructor
+    ///
+    /// @param hostname is the resolvable name of the server. If not blank,
+    /// then the server address should be resolved at runtime.
+    /// @param ip_address is the static IP address of the server. If hostname
+    /// is blank, then this address should be used to connect to the server.
+    /// @param port is the port number on which the server listens.
+    /// primarily meant for testing purposes.  Normally, DNS traffic is on
+    /// is port 53. (NOTE the constructing code is responsible for setting
+    /// the default.)
+    /// @param enabled is a flag that indicates whether this server is
+    /// enabled for use. It defaults to true.
+    DnsServerInfo(const std::string& hostname,
+                  isc::asiolink::IOAddress ip_address,
+                  uint32_t port = STANDARD_DNS_PORT,
+                  bool enabled=true);
+
+    /// @brief Destructor
+    virtual ~DnsServerInfo();
+
+    /// @brief Getter which returns the server's hostname.
+    ///
+    /// @return returns the hostname as as std::string.
+    const std::string getHostname() const {
+        return (hostname_);
+    }
+
+    /// @brief Getter which returns the server's port number.
+    ///
+    /// @return returns the port number as a unsigned integer.
+    uint32_t getPort() const {
+        return (port_);
+    }
+
+    /// @brief Getter which returns the server's ip_address.
+    ///
+    /// @return returns the address as an IOAddress reference.
+    const isc::asiolink::IOAddress& getIpAddress() const {
+        return (ip_address_);
+    }
+
+    /// @brief Convenience method which returns whether or not the
+    /// server is enabled.
+    ///
+    /// @return returns true if the server is enabled, false otherwise.
+    bool isEnabled() const {
+        return (enabled_);
+    }
+
+    /// @brief Sets the server's enabled flag to true.
+    void enable() {
+        enabled_ = true;
+    }
+
+    /// @brief Sets the server's enabled flag to false.
+    void disable() {
+        enabled_ = false;
+    }
+
+
+private:
+    /// @brief The resolvable name of the server. If not blank, then the
+    /// server's IP address should be dynamically resolved at runtime.
+    std::string hostname_;
+
+    /// @brief The static IP address of the server. When hostname is blank,
+    /// then this address should be used to connect to the server.
+    isc::asiolink::IOAddress ip_address_;
+
+    /// @brief The port number on which the server listens for DNS traffic.
+    uint32_t port_;
+
+    /// @param enabled is a flag that indicates whether this server is
+    /// enabled for use. It defaults to true.
+    bool enabled_;
+};
+
+/// @brief Defines a pointer for DnsServerInfo instances.
+typedef boost::shared_ptr<DnsServerInfo> DnsServerInfoPtr;
+
+/// @brief Defines a storage container for DnsServerInfo pointers.
+typedef std::vector<DnsServerInfoPtr> DnsServerInfoStorage;
+
+/// @brief Defines a pointer to DnsServerInfo storage containers.
+typedef boost::shared_ptr<DnsServerInfoStorage> DnsServerInfoStoragePtr;
+
+
+/// @brief Represents a DNS domain that is may be updated dynamically.
+/// This class specifies a DNS domain and the list of DNS servers that support
+/// it.  It's primary use is to map a domain to the DNS server(s) responsible
+/// for it.
+/// @todo Currently the name entry for a domain is just an std::string. It
+/// may be worthwhile to change this to a dns::Name for purposes of better 
+/// validation and matching capabilities. 
+class DdnsDomain {
+public:
+    /// @brief Constructor
+    ///
+    /// @param name is the domain name of the domain.
+    /// @param key_name is the TSIG key name for use with this domain.
+    /// @param servers is the list of server(s) supporting this domain.
+    DdnsDomain(const std::string& name, const std::string& key_name,
+               DnsServerInfoStoragePtr servers);
+
+    /// @brief Destructor
+    virtual ~DdnsDomain();
+
+    /// @brief Getter which returns the domain's name.
+    ///
+    /// @return returns the name in an std::string.
+    const std::string getName() const {
+        return (name_);
+    }
+
+    /// @brief Getter which returns the domain's TSIG key name.
+    ///
+    /// @return returns the key name in an std::string.
+    const std::string getKeyName() const {
+        return (key_name_);
+    }
+
+    /// @brief Getter which returns the domain's list of servers.
+    ///
+    /// @return returns the pointer to the server storage.
+    const DnsServerInfoStoragePtr& getServers() {
+        return (servers_);
+    }
+
+private:
+    /// @brief The domain name of the domain.
+    std::string name_;
+
+    /// @brief The name of the TSIG key for use with this domain.
+    std::string key_name_;
+
+    /// @brief The list of server(s) supporting this domain.
+    DnsServerInfoStoragePtr servers_;
+};
+
+/// @brief Defines a pointer for DdnsDomain instances.
+typedef boost::shared_ptr<DdnsDomain> DdnsDomainPtr;
+
+/// @brief Defines a map of DdnsDomains, keyed by the domain name.
+typedef std::map<std::string, DdnsDomainPtr> DdnsDomainMap;
+
+/// @brief Defines a iterator pairing domain name and DdnsDomain
+typedef std::pair<std::string, DdnsDomainPtr> DdnsDomainMapPair;
+
+/// @brief Defines a pointer to DdnsDomain storage containers.
+typedef boost::shared_ptr<DdnsDomainMap> DdnsDomainMapPtr;
+
+/// @brief Provides storage for and management of a list of DNS domains.
+/// In addition to housing the domain list storage, it provides domain matching
+/// services.  These services are used to match a FQDN to a domain.  Currently
+/// it supports a single matching service, which will return the matching
+/// domain or a wild card domain if one is specified.  The wild card domain is
+/// specified as a domain whose name is "*".
+/// As matching capabilities evolve this class is expected to expand.
+class DdnsDomainListMgr {
+public:
+    /// @brief defines the domain name for denoting the wildcard domain.
+    static const char* wildcard_domain_name_;
+
+    /// @brief Constructor
+    ///
+    /// @param name is an arbitrary label assigned to this manager.
+    DdnsDomainListMgr(const std::string& name);
+
+    /// @brief Destructor
+    virtual ~DdnsDomainListMgr ();
+
+    /// @brief Matches a given name to a domain based on a longest match
+    /// scheme.
+    ///
+    /// Given a FQDN, search the list of domains, successively removing a
+    /// sub-domain from the FQDN until a match is found.  If no match is found
+    /// and the wild card domain is present in the list, then return it as the
+    /// match.  If the wild card domain is the only domain in the list, then
+    /// it will be returned immediately for any FQDN.
+    ///
+    /// @param fqdn is the name for which to look.
+    /// @param domain receives the matching domain. Note that it will be reset
+    /// upon entry and only set if a match is subsequently found.
+    ///
+    /// @return returns true if a match is found, false otherwise.
+    /// @todo This is a very basic match method, which expects valid FQDNs
+    /// both as input and for the DdnsDomain::getName().  Currently both are
+    /// simple strings and there is no normalization (i.e. added trailing dots
+    /// if missing).
+    virtual bool matchDomain(const std::string& fqdn, DdnsDomainPtr& domain);
+
+    /// @brief Fetches the manager's name.
+    ///
+    /// @return returns a std::string containing the name of the manager.
+    const std::string getName() const {
+        return (name_);
+    }
+
+    /// @brief Returns the number of domains in the domain list.
+    ///
+    /// @brief returns an unsigned int containing the domain count.
+    uint32_t size() const {
+        return (domains_->size());
+    }
+
+    /// @brief Fetches the wild card domain.
+    ///
+    /// @return returns a pointer reference to the domain.  The pointer will
+    /// empty if the wild card domain is not present.
+    const DdnsDomainPtr& getWildcardDomain() {
+        return (wildcard_domain_);
+    }
+
+    /// @brief Fetches the domain list.
+    ///
+    /// @return returns a pointer reference to the list of domains.
+    const DdnsDomainMapPtr &getDomains() {
+        return (domains_);
+    }
+
+    /// @brief Sets the manger's domain list to the given list of domains.
+    /// This method will scan the inbound list for the wild card domain and
+    /// set the internal wild card domain pointer accordingly.
+    void setDomains(DdnsDomainMapPtr domains);
+
+private:
+    /// @brief An arbitrary label assigned to this manager.
+    std::string name_;
+
+    /// @brief Map of the domains, keyed by name.
+    DdnsDomainMapPtr domains_;
+
+    /// @brief Pointer to the wild card domain.
+    DdnsDomainPtr wildcard_domain_;
+};
+
+/// @brief Defines a pointer for DdnsDomain instances.
+typedef boost::shared_ptr<DdnsDomainListMgr> DdnsDomainListMgrPtr;
+
+/// @brief Storage container for scalar configuration parameters.
+///
+/// This class is useful for implementing parsers for more complex configuration
+/// elements (e.g. those of item type "map").  It provides a convenient way to
+/// add storage to the parser for an arbitrary number and variety of scalar
+/// configuration items (e.g. ints, bools, strings...) without explicitly adding
+/// storage for each individual type needed by the parser.
+///
+/// This class implements a concrete version of the base class by supplying a
+/// "clone" method.
+class DScalarContext : public DCfgContextBase {
+public:
+
+    /// @brief Constructor
+    DScalarContext() {
+    };
+
+    /// @brief Destructor
+    virtual ~DScalarContext() {
+    }
+
+    /// @brief Creates a clone of a DStubContext.
+    ///
+    /// @return returns a pointer to the new clone.
+    virtual DCfgContextBasePtr clone() {
+        return (DCfgContextBasePtr(new DScalarContext(*this)));
+    }
+
+protected:
+    /// @brief Copy constructor
+    DScalarContext(const DScalarContext& rhs) : DCfgContextBase(rhs) {
+    }
+
+private:
+    /// @brief Private assignment operator, not implemented.
+    DScalarContext& operator=(const DScalarContext& rhs);
+};
+
+/// @brief Defines a pointer for DScalarContext instances.
+typedef boost::shared_ptr<DScalarContext> DScalarContextPtr;
+
+/// @brief Parser for  TSIGKeyInfo
+///
+/// This class parses the configuration element "tsig_key" defined in
+/// src/bin/d2/dhcp-ddns.spec and creates an instance of a TSIGKeyInfo.
+class TSIGKeyInfoParser : public  isc::dhcp::DhcpConfigParser {
+public:
+    /// @brief Constructor
+    ///
+    /// @param entry_name is an arbitrary label assigned to this configuration
+    /// definition. Since servers are specified in a list this value is likely
+    /// be something akin to "key:0", set during parsing.
+    /// @param keys is a pointer to the storage area to which the parser
+    /// should commit the newly created TSIGKeyInfo instance.
+    TSIGKeyInfoParser(const std::string& entry_name, TSIGKeyInfoMapPtr keys);
+
+    /// @brief Destructor
+    virtual ~TSIGKeyInfoParser();
+
+    /// @brief Performs the actual parsing of the given  "tsig_key" element.
+    ///
+    /// The results of the parsing are retained internally for use during
+    /// commit.
+    ///
+    /// @param key_config is the "tsig_key" configuration to parse
+    virtual void build(isc::data::ConstElementPtr key_config);
+
+    /// @brief Creates a parser for the given "tsig_key" member element id.
+    ///
+    /// The key elements currently supported are(see dhcp-ddns.spec):
+    ///   1. name
+    ///   2. algorithm
+    ///   3. secret
+    ///
+    /// @param config_id is the "item_name" for a specific member element of
+    /// the "tsig_key" specification.
+    ///
+    /// @return returns a pointer to newly created parser.
+    virtual isc::dhcp::ParserPtr createConfigParser(const std::string&
+                                                    config_id);
+
+    /// @brief Instantiates a DnsServerInfo from internal data values
+    /// saves it to the storage area pointed to by servers_.
+    virtual void commit();
+
+private:
+    /// @brief Arbitrary label assigned to this parser instance.
+    /// Since servers are specified in a list this value is likely be something
+    /// akin to "key:0", set during parsing.  Primarily here for diagnostics.
+    std::string entry_name_;
+
+    /// @brief Pointer to the storage area to which the parser should commit
+    /// the newly created TSIGKeyInfo instance. This is given to us as a
+    /// constructor argument by an upper level.
+    TSIGKeyInfoMapPtr keys_;
+
+    /// @brief Local storage area for scalar parameter values. Use to hold
+    /// data until time to commit.
+    DScalarContext local_scalars_;
+};
+
+/// @brief Parser for a list of TSIGKeyInfos
+///
+/// This class parses a list of "tsig_key" configuration elements.
+/// (see src/bin/d2/dhcp-ddns.spec). The TSIGKeyInfo instances are added
+/// to the given storage upon commit.
+class TSIGKeyInfoListParser : public isc::dhcp::DhcpConfigParser {
+public:
+
+    /// @brief Constructor
+    ///
+    /// @param list_name is an arbitrary label assigned to this parser instance.
+    /// @param keys is a pointer to the storage area to which the parser
+    /// should commit the newly created TSIGKeyInfo instance.
+    TSIGKeyInfoListParser(const std::string& list_name, TSIGKeyInfoMapPtr keys);
+
+    /// @brief Destructor
+    virtual ~TSIGKeyInfoListParser();
+
+    /// @brief Performs the parsing of the given list "tsig_key" elements.
+    ///
+    /// It iterates over each key entry in the list:
+    ///   1. Instantiate a TSIGKeyInfoParser for the entry
+    ///   2. Pass the element configuration to the parser's build method
+    ///   3. Add the parser instance to local storage
+    ///
+    /// The net effect is to parse all of the key entries in the list
+    /// prepping them for commit.
+    ///
+    /// @param key_list_config is the list of "tsig_key" elements to parse.
+    virtual void build(isc::data::ConstElementPtr key_list_config);
+
+    /// @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_.
+    virtual void commit();
+
+private:
+    /// @brief Arbitrary label assigned to this parser instance.
+    std::string list_name_;
+
+    /// @brief Pointer to the storage area to which the parser should commit
+    /// 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 of TSIGKeyInfoParser instances
+    isc::dhcp::ParserCollection parsers_;
+};
+
+/// @brief Parser for  DnsServerInfo
+///
+/// This class parses the configuration element "dns_server" defined in
+/// src/bin/d2/dhcp-ddns.spec and creates an instance of a DnsServerInfo.
+class DnsServerInfoParser : public  isc::dhcp::DhcpConfigParser {
+public:
+    /// @brief Constructor
+    ///
+    /// @param entry_name is an arbitrary label assigned to this configuration
+    /// definition. Since servers are specified in a list this value is likely
+    /// be something akin to "server:0", set during parsing.
+    /// @param servers is a pointer to the storage area to which the parser
+    /// should commit the newly created DnsServerInfo instance.
+    DnsServerInfoParser(const std::string& entry_name,
+                        DnsServerInfoStoragePtr servers);
+
+    /// @brief Destructor
+    virtual ~DnsServerInfoParser();
+
+    /// @brief Performs the actual parsing of the given  "dns_server" element.
+    /// The results of the parsing are retained internally for use during
+    /// commit.
+    ///
+    /// @param server_config is the "dns_server" configuration to parse
+    virtual void build(isc::data::ConstElementPtr server_config);
+
+    /// @brief Creates a parser for the given "dns_server" member element id.
+    ///
+    /// The server elements currently supported are(see dhcp-ddns.spec):
+    ///   1. hostname
+    ///   2. ip_address
+    ///   3. port
+    ///
+    /// @param config_id is the "item_name" for a specific member element of
+    /// the "dns_server" specification.
+    ///
+    /// @return returns a pointer to newly created parser.
+    virtual isc::dhcp::ParserPtr createConfigParser(const std::string&
+                                                    config_id);
+
+    /// @brief Instantiates a DnsServerInfo from internal data values
+    /// saves it to the storage area pointed to by servers_.
+    virtual void commit();
+
+private:
+    /// @brief Arbitrary label assigned to this parser instance.
+    /// Since servers are specified in a list this value is likely be something
+    /// akin to "server:0", set during parsing.  Primarily here for diagnostics.
+    std::string entry_name_;
+
+    /// @brief Pointer to the storage area to which the parser should commit
+    /// the newly created DnsServerInfo instance. This is given to us as a
+    /// constructor argument by an upper level.
+    DnsServerInfoStoragePtr servers_;
+
+    /// @brief Local storage area for scalar parameter values. Use to hold
+    /// data until time to commit.
+    DScalarContext local_scalars_;
+};
+
+/// @brief Parser for a list of DnsServerInfos
+///
+/// This class parses a list of "dns_server" configuration elements.
+/// (see src/bin/d2/dhcp-ddns.spec). The DnsServerInfo instances are added
+/// to the given storage upon commit.
+class DnsServerInfoListParser : public isc::dhcp::DhcpConfigParser {
+public:
+
+    /// @brief Constructor
+    ///
+    /// @param list_name is an arbitrary label assigned to this parser instance.
+    /// @param servers is a pointer to the storage area to which the parser
+    /// should commit the newly created DnsServerInfo instance.
+    DnsServerInfoListParser(const std::string& list_name,
+                            DnsServerInfoStoragePtr servers);
+
+    /// @brief Destructor
+    virtual ~DnsServerInfoListParser();
+
+    /// @brief Performs the actual parsing of the given list "dns_server"
+    /// elements.
+    /// It iterates over each server entry in the list:
+    ///   1. Instantiate a DnsServerInfoParser for the entry
+    ///   2. Pass the element configuration to the parser's build method
+    ///   3. Add the parser instance to local storage
+    ///
+    /// The net effect is to parse all of the server entries in the list
+    /// prepping them for commit.
+    ///
+    /// @param server_list_config is the list of "dns_server" elements to parse.
+    virtual void build(isc::data::ConstElementPtr server_list_config);
+
+    /// @brief Iterates over the internal list of DnsServerInfoParsers,
+    /// invoking commit on each.  This causes each parser to instantiate a
+    /// DnsServerInfo from its internal data values and add that that server
+    /// instance to the storage area, servers_.
+    virtual void commit();
+
+private:
+    /// @brief Arbitrary label assigned to this parser instance.
+    std::string list_name_;
+
+    /// @brief Pointer to the storage area to which the parser should commit
+    /// the list of newly created DnsServerInfo instances. This is given to us
+    /// as a constructor argument by an upper level.
+    DnsServerInfoStoragePtr servers_;
+
+    /// @brief Local storage of DnsServerInfoParser instances
+    isc::dhcp::ParserCollection parsers_;
+};
+
+/// @brief Parser for  DdnsDomain
+///
+/// This class parses the configuration element "ddns_domain" defined in
+/// src/bin/d2/dhcp-ddns.spec and creates an instance of a DdnsDomain.
+class DdnsDomainParser : public isc::dhcp::DhcpConfigParser {
+public:
+    /// @brief Constructor
+    ///
+    /// @param entry_name is an arbitrary label assigned to this configuration
+    /// definition. Since domains are specified in a list this value is likely
+    /// be something akin to "forward_ddns:0", set during parsing.
+    /// @param domains is a pointer to the storage area to which the parser
+    /// @param keys is a pointer to a map of the defined TSIG keys.
+    /// should commit the newly created DdnsDomain instance.
+    DdnsDomainParser(const std::string& entry_name, DdnsDomainMapPtr domains,
+                     TSIGKeyInfoMapPtr keys);
+
+    /// @brief Destructor
+    virtual ~DdnsDomainParser();
+
+    /// @brief Performs the actual parsing of the given  "ddns_domain" element.
+    /// The results of the parsing are retained internally for use during
+    /// commit.
+    ///
+    /// @param domain_config is the "ddns_domain" configuration to parse
+    virtual void build(isc::data::ConstElementPtr domain_config);
+
+    /// @brief Creates a parser for the given "ddns_domain" member element id.
+    ///
+    /// The domain elements currently supported are(see dhcp-ddns.spec):
+    ///   1. name
+    ///   2. key_name
+    ///   3. dns_servers
+    ///
+    /// @param config_id is the "item_name" for a specific member element of
+    /// the "ddns_domain" specification.
+    ///
+    /// @return returns a pointer to newly created parser.
+    virtual isc::dhcp::ParserPtr createConfigParser(const std::string&
+                                                    config_id);
+
+    /// @brief Instantiates a DdnsDomain from internal data values
+    /// saves it to the storage area pointed to by domains_.
+    virtual void commit();
+
+private:
+
+    /// @brief Arbitrary label assigned to this parser instance.
+    std::string entry_name_;
+
+    /// @brief Pointer to the storage area to which the parser should commit
+    /// the newly created DdnsDomain instance. This is given to us as a
+    /// constructor argument by an upper level.
+    DdnsDomainMapPtr domains_;
+
+    /// @brief Pointer to the map of defined TSIG keys.
+    /// This map is passed into us and contains all of the TSIG keys defined
+    /// for this configuration.  It is used to validate the key name entry of
+    /// DdnsDomains that specify one.
+    TSIGKeyInfoMapPtr keys_;
+
+    /// @brief Local storage for DnsServerInfo instances. This is passed into
+    /// DnsServerInfoListParser(s), which in turn passes it into each
+    /// DnsServerInfoParser.  When the DnsServerInfoParsers "commit" they add
+    /// their server instance to this storage.
+    DnsServerInfoStoragePtr local_servers_;
+
+    /// @brief Local storage area for scalar parameter values. Use to hold
+    /// data until time to commit.
+    DScalarContext local_scalars_;
+};
+
+/// @brief Parser for a list of DdnsDomains
+///
+/// This class parses a list of "ddns_domain" configuration elements.
+/// (see src/bin/d2/dhcp-ddns.spec). The DdnsDomain instances are added
+/// to the given storage upon commit.
+class DdnsDomainListParser : public isc::dhcp::DhcpConfigParser {
+public:
+
+    /// @brief Constructor
+    ///
+    /// @param list_name is an arbitrary label assigned to this parser instance.
+    /// @param domains is a pointer to the storage area to which the parser
+    /// @param keys is a pointer to a map of the defined TSIG keys.
+    /// should commit the newly created DdnsDomain instance.
+    DdnsDomainListParser(const std::string& list_name,
+                         DdnsDomainMapPtr domains, TSIGKeyInfoMapPtr keys);
+
+    /// @brief Destructor
+    virtual ~DdnsDomainListParser();
+
+    /// @brief Performs the actual parsing of the given list "ddns_domain"
+    /// elements.
+    /// It iterates over each server entry in the list:
+    ///   1. Instantiate a DdnsDomainParser for the entry
+    ///   2. Pass the element configuration to the parser's build method
+    ///   3. Add the parser instance to local storage
+    ///
+    /// The net effect is to parse all of the domain entries in the list
+    /// prepping them for commit.
+    ///
+    /// @param domain_list_config is the list of "ddns_domain" elements to
+    /// parse.
+    virtual void build(isc::data::ConstElementPtr domain_list_config);
+
+    /// @brief Iterates over the internal list of DdnsDomainParsers, invoking
+    /// commit on each.  This causes each parser to instantiate a DdnsDomain
+    /// from its internal data values and add that domain instance to the
+    /// storage area, domains_.
+    virtual void commit();
+
+private:
+    /// @brief Arbitrary label assigned to this parser instance.
+    std::string list_name_;
+
+    /// @brief Pointer to the storage area to which the parser should commit
+    /// the list of newly created DdnsDomain instances. This is given to us
+    /// as a constructor argument by an upper level.
+    DdnsDomainMapPtr domains_;
+
+    /// @brief Pointer to the map of defined TSIG keys.
+    /// This map is passed into us and contains all of the TSIG keys defined
+    /// for this configuration.  It is used to validate the key name entry of
+    /// DdnsDomains that specify one.
+    TSIGKeyInfoMapPtr keys_;
+
+    /// @brief Local storage of DdnsDomainParser instances
+    isc::dhcp::ParserCollection parsers_;
+};
+
+/// @brief Parser for DdnsDomainListMgr
+///
+/// This class parses the configuration elements "forward_ddns" and
+/// "reverse_ddns" as defined in src/bin/d2/dhcp-ddns.spec.  It populates the
+/// given DdnsDomainListMgr with parsed information upon commit.  Note that
+/// unlike other parsers, this parser does NOT instantiate the final object
+/// during the commit phase, it populates it.  It must pre-exist.
+class DdnsDomainListMgrParser : public isc::dhcp::DhcpConfigParser {
+public:
+    /// @brief Constructor
+    ///
+    /// @param entry_name is an arbitrary label assigned to this configuration
+    /// definition.
+    /// @param mgr is a pointer to the DdnsDomainListMgr to populate.
+    /// @param keys is a pointer to a map of the defined TSIG keys.
+    /// @throw throws D2CfgError if mgr pointer is empty.
+    DdnsDomainListMgrParser(const std::string& entry_name,
+                     DdnsDomainListMgrPtr mgr, TSIGKeyInfoMapPtr keys);
+
+    /// @brief Destructor
+    virtual ~DdnsDomainListMgrParser();
+
+    /// @brief Performs the actual parsing of the given manager element.
+    /// The results of the parsing are retained internally for use during
+    /// commit.
+    ///
+    /// @param mgr_config is the manager configuration to parse
+    virtual void build(isc::data::ConstElementPtr mgr_config);
+
+    /// @brief Creates a parser for the given manager member element id.
+    ///
+    /// The manager elements currently supported are (see dhcp-ddns.spec):
+    ///     1. ddns_domains
+    ///
+    /// @param config_id is the "item_name" for a specific member element of
+    /// the manager specification.
+    ///
+    /// @return returns a pointer to newly created parser.
+    virtual isc::dhcp::ParserPtr createConfigParser(const std::string&
+                                                    config_id);
+
+    /// @brief Populates the DdnsDomainListMgr from internal data values
+    /// set during parsing.
+    virtual void commit();
+
+private:
+    /// @brief Arbitrary label assigned to this parser instance.
+    std::string entry_name_;
+
+    /// @brief Pointer to manager instance to which the parser should commit
+    /// the parsed data. This is given to us as a constructor argument by an
+    /// upper level.
+    DdnsDomainListMgrPtr mgr_;
+
+    /// @brief Pointer to the map of defined TSIG keys.
+    /// This map is passed into us and contains all of the TSIG keys defined
+    /// for this configuration.  It is used to validate the key name entry of
+    /// DdnsDomains that specify one.
+    TSIGKeyInfoMapPtr keys_;
+
+    /// @brief Local storage for DdnsDomain instances. This is passed into a
+    /// DdnsDomainListParser(s), which in turn passes it into each
+    /// DdnsDomainParser.  When the DdnsDomainParsers "commit" they add their
+    /// domain instance to this storage.
+    DdnsDomainMapPtr local_domains_;
+
+    /// @brief Local storage area for scalar parameter values. Use to hold
+    /// data until time to commit.
+    /// @todo Currently, the manager has no scalars but this is likely to
+    /// change as matching capabilities expand.
+    DScalarContext local_scalars_;
+};
+
+
+}; // end of isc::d2 namespace
+}; // end of isc namespace
+
+#endif // D2_CONFIG_H

+ 1 - 1
src/bin/d2/d2_controller.h

@@ -24,7 +24,7 @@ namespace d2 {
 /// This class is the DHCP-DDNS specific derivation of DControllerBase. It
 /// creates and manages an instance of the DHCP-DDNS application process,
 /// D2Process.
-/// @TODO Currently, this class provides only the minimum required specialized
+/// @todo Currently, this class provides only the minimum required specialized
 /// behavior to run the DHCP-DDNS service. It may very well expand as the
 /// service implementation evolves.  Some thought was given to making
 /// DControllerBase a templated class but the labor savings versus the

+ 34 - 1
src/bin/d2/d2_messages.mes

@@ -26,11 +26,22 @@ to establish a session with the BIND10 control channel.
 A debug message listing the command (and possible arguments) received
 from the BIND10 control system by the controller.
 
+% DCTL_CONFIG_COMPLETE server has completed configuration: %1
+This is an informational message announcing the successful processing of a
+new configuration. It is output during server startup, and when an updated
+configuration is committed by the administrator.  Additional information
+may be provided.
+
 % DCTL_CONFIG_LOAD_FAIL %1 configuration failed to load: %2
 This critical error message indicates that the initial application
 configuration has failed. The service will start, but will not
 process requests until the configuration has been corrected.
 
+% DCTL_CONFIG_START parsing new configuration: %1
+A debug message indicating that the application process has received an
+updated configuration and has passed it to its configuration manager
+for parsing.
+
 % DCTL_CONFIG_STUB %1 configuration stub handler called
 This debug message is issued when the dummy handler for configuration
 events is called.  This only happens during initial startup.
@@ -55,7 +66,24 @@ application and will exit.
 
 % DCTL_NOT_RUNNING %1 application instance is not running
 A warning message is issued when an attempt is made to shut down the
-the application when it is not running.
+application when it is not running.
+
+% DCTL_ORDER_ERROR configuration contains more elements than the parsing order
+An error message which indicates that configuration being parsed includes
+element ids not specified the configuration manager's parse order list. This 
+is a programmatic error.
+
+% DCTL_ORDER_NO_ELEMENT element: %1 is in the parsing order but is missing from the configuration
+An error message output during a configuration update.  The program is
+expecting an item but has not found it in the new configuration.  This may
+mean that the BIND 10 configuration database is corrupt.
+
+% DCTL_PARSER_FAIL configuration parsing failed for configuration element: %1
+On receipt of message containing details to a change of its configuration,
+the server failed to create a parser to decode the contents of the named
+configuration element, or the creation succeeded but the parsing actions
+and committal of changes failed.  The reason for the failure is given in
+the message.
 
 % DCTL_PROCESS_FAILED %1 application execution failed: %2
 The controller has encountered a fatal error while running the
@@ -96,6 +124,11 @@ has been invoked.
 This is a debug message issued when the Dhcp-Ddns application encounters an
 unrecoverable error from within the event loop.
 
+% DHCP_DDNS_NO_MATCH No DNS servers match FQDN: %1
+This is warning message issued when there are no domains in the configuration
+which match the cited fully qualified domain name (FQDN).  The DNS Update 
+request for the FQDN cannot be processed.
+
 % DHCP_DDNS_PROCESS_INIT application init invoked
 This is a debug message issued when the Dhcp-Ddns application enters
 its init method.

+ 8 - 7
src/bin/d2/d2_process.cc

@@ -14,6 +14,7 @@
 
 #include <config/ccsession.h>
 #include <d2/d2_log.h>
+#include <d2/d2_cfg_mgr.h>
 #include <d2/d2_process.h>
 
 using namespace asio;
@@ -22,7 +23,7 @@ namespace isc {
 namespace d2 {
 
 D2Process::D2Process(const char* name, IOServicePtr io_service)
-    : DProcessBase(name, io_service) {
+    : DProcessBase(name, io_service, DCfgMgrBasePtr(new D2CfgMgr())) {
 };
 
 void
@@ -60,19 +61,19 @@ D2Process::shutdown() {
 
 isc::data::ConstElementPtr
 D2Process::configure(isc::data::ConstElementPtr config_set) {
-    // @TODO This is the initial implementation which simply accepts
-    // any content in config_set as valid.  This is sufficient to
-    // allow participation as a BIND10 module, while D2 configuration support
-    // is being developed.
+    // @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. 
     LOG_DEBUG(dctl_logger, DBGLVL_TRACE_BASIC,
               DHCP_DDNS_CONFIGURE).arg(config_set->str());
 
-    return (isc::config::createAnswer(0, "Configuration accepted."));
+    return (getCfgMgr()->parseConfig(config_set));
 }
 
 isc::data::ConstElementPtr
 D2Process::command(const std::string& command, isc::data::ConstElementPtr args){
-    // @TODO This is the initial implementation.  If and when D2 is extended
+    // @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.
     LOG_DEBUG(dctl_logger, DBGLVL_TRACE_BASIC,

+ 2 - 2
src/bin/d2/d2_process.h

@@ -41,7 +41,7 @@ public:
     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
+    /// 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.
@@ -58,7 +58,7 @@ public:
     /// @brief Implements the process's shutdown processing. When invoked, it
     /// should ensure that the process gracefully exits the run method.
     /// Current implementation simply sets the shutdown flag monitored by the
-    /// run method. @TODO this may need to expand as the implementation evolves.
+    /// run method. @todo this may need to expand as the implementation evolves.
     /// @throw throws a DProcessBaseError if an error is encountered.
     virtual void shutdown();
 

+ 240 - 0
src/bin/d2/d_cfg_mgr.cc

@@ -0,0 +1,240 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config/ccsession.h>
+#include <d2/d2_log.h>
+#include <dhcp/libdhcp++.h>
+#include <d2/d_cfg_mgr.h>
+#include <dhcpsrv/dhcp_parsers.h>
+#include <util/encode/hex.h>
+#include <util/strutil.h>
+
+#include <boost/foreach.hpp>
+#include <boost/lexical_cast.hpp>
+#include <boost/algorithm/string.hpp>
+
+#include <limits>
+#include <iostream>
+#include <vector>
+#include <map>
+
+using namespace std;
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::data;
+using namespace isc::asiolink;
+
+namespace isc {
+namespace d2 {
+
+// *********************** DCfgContextBase  *************************
+
+DCfgContextBase::DCfgContextBase():
+        boolean_values_(new BooleanStorage()),
+        uint32_values_(new Uint32Storage()),
+        string_values_(new StringStorage()) {
+    }
+
+DCfgContextBase::DCfgContextBase(const DCfgContextBase& rhs):
+        boolean_values_(new BooleanStorage(*(rhs.boolean_values_))),
+        uint32_values_(new Uint32Storage(*(rhs.uint32_values_))),
+        string_values_(new StringStorage(*(rhs.string_values_))) {
+}
+
+void
+DCfgContextBase::getParam(const std::string& name, bool& value, bool optional) {
+    try {
+        value = boolean_values_->getParam(name);
+    } catch (DhcpConfigError& ex) {
+        // If the parameter is not optional, re-throw the exception.
+        if (!optional) {
+            throw;
+        }
+    }
+}
+
+
+void
+DCfgContextBase::getParam(const std::string& name, uint32_t& value,
+                          bool optional) {
+    try {
+        value = uint32_values_->getParam(name);
+    } catch (DhcpConfigError& ex) {
+        // If the parameter is not optional, re-throw the exception.
+        if (!optional) {
+            throw;
+        }
+    }
+}
+
+void
+DCfgContextBase::getParam(const std::string& name, std::string& value,
+                          bool optional) {
+    try {
+        value = string_values_->getParam(name);
+    } catch (DhcpConfigError& ex) {
+        // If the parameter is not optional, re-throw the exception.
+        if (!optional) {
+            throw;
+        }
+    }
+}
+
+DCfgContextBase::~DCfgContextBase() {
+}
+
+// *********************** DCfgMgrBase  *************************
+
+DCfgMgrBase::DCfgMgrBase(DCfgContextBasePtr context)
+    : parse_order_(), context_(context) {
+    if (!context_) {
+        isc_throw(DCfgMgrBaseError, "DCfgMgrBase ctor: context cannot be NULL");
+    }
+}
+
+DCfgMgrBase::~DCfgMgrBase() {
+}
+
+isc::data::ConstElementPtr
+DCfgMgrBase::parseConfig(isc::data::ConstElementPtr config_set) {
+    LOG_DEBUG(dctl_logger, DBGLVL_COMMAND,
+                DCTL_CONFIG_START).arg(config_set->str());
+
+    if (!config_set) {
+        return (isc::config::createAnswer(1,
+                                    std::string("Can't parse NULL config")));
+    }
+
+    // The parsers implement data inheritance by directly accessing
+    // configuration context. For this reason the data parsers must store
+    // the parsed data into context immediately. This may cause data
+    // inconsistency if the parsing operation fails after the context has been
+    // modified. We need to preserve the original context here
+    // so as we can rollback changes when an error occurs.
+    DCfgContextBasePtr original_context = context_->clone();
+
+    // Answer will hold the result returned to the caller.
+    ConstElementPtr answer;
+
+    // Holds the name of the element being parsed.
+    std::string element_id;
+
+    try {
+        // Grab a map of element_ids and their data values from the new
+        // configuration set.
+        const std::map<std::string, ConstElementPtr>& values_map =
+                                                        config_set->mapValue();
+
+        // Use a pre-ordered list of element ids to parse the elements in a
+        // specific order if the list (parser_order_) is not empty; otherwise
+        // elements are parsed in the order the value_map presents them.
+
+        if (parse_order_.size() > 0) {
+            // For each element_id in the parse order list, look for it in the
+            // value map.  If the element exists in the map, pass it and it's
+            // associated data in for parsing.
+            // If there is no matching entry in the value map an error is
+            // thrown.  Note, that elements tagged as "optional" from the user
+            // perspective must still have default or empty entries in the
+            // configuration set to be parsed.
+            int parsed_count = 0;
+            std::map<std::string, ConstElementPtr>::const_iterator it;
+            BOOST_FOREACH(element_id, parse_order_) {
+                it = values_map.find(element_id);
+                if (it != values_map.end()) {
+                    ++parsed_count;
+                    buildAndCommit(element_id, it->second);
+                }
+                else {
+                    LOG_ERROR(dctl_logger, DCTL_ORDER_NO_ELEMENT)
+                              .arg(element_id);
+                    isc_throw(DCfgMgrBaseError, "Element:" << element_id <<
+                              " is listed in the parse order but is not "
+                              " present in the configuration");
+                }
+            }
+
+            // NOTE: When using ordered parsing, the parse order list MUST
+            // include every possible element id that the value_map may contain.
+            // Entries in the map that are not in the parse order, would not be
+            // parsed. For now we will flag this as a programmatic error.  One
+            // could attempt to adjust for this, by identifying such entries
+            // and parsing them either first or last but which would be correct?
+            // Better to hold the engineer accountable.  So, if we parsed none
+            // or we parsed fewer than are in the map; then either the parse i
+            // order is incomplete OR the map has unsupported values.
+            if (!parsed_count ||
+                (parsed_count && ((parsed_count + 1) < values_map.size()))) {
+                LOG_ERROR(dctl_logger, DCTL_ORDER_ERROR);
+                isc_throw(DCfgMgrBaseError,
+                        "Configuration contains elements not in parse order");
+            }
+        } else {
+            // Order doesn't matter so iterate over the value map directly.
+            // Pass each element and it's associated data in to be parsed.
+            ConfigPair config_pair;
+            BOOST_FOREACH(config_pair, values_map) {
+                element_id = config_pair.first;
+                buildAndCommit(element_id, config_pair.second);
+            }
+        }
+
+        // Everything was fine. Configuration set processed successfully.
+        LOG_INFO(dctl_logger, DCTL_CONFIG_COMPLETE).arg("");
+        answer = isc::config::createAnswer(0, "Configuration committed.");
+
+    } catch (const isc::Exception& ex) {
+        LOG_ERROR(dctl_logger, DCTL_PARSER_FAIL).arg(element_id).arg(ex.what());
+        answer = isc::config::createAnswer(1,
+                     string("Configuration parsing failed: ") + ex.what());
+
+        // An error occurred, so make sure that we restore original context.
+        context_ = original_context;
+        return (answer);
+    }
+
+    return (answer);
+}
+
+void DCfgMgrBase::buildAndCommit(std::string& element_id,
+                                 isc::data::ConstElementPtr value) {
+    // Call derivation's implementation to create the appropriate parser
+    // based on the element id.
+    ParserPtr parser = createConfigParser(element_id);
+    if (!parser) {
+        isc_throw(DCfgMgrBaseError, "Could not create parser");
+    }
+
+    try {
+        // Invoke the parser's build method passing in the value. This will
+        // "convert" the Element form of value into the actual data item(s)
+        // and store them in parser's local storage.
+        parser->build(value);
+
+        // Invoke the parser's commit method. This "writes" the the data
+        // item(s) stored locally by the parser into the context.  (Note that
+        // parsers are free to do more than update the context, but that is an
+        // nothing something we are concerned with here.)
+        parser->commit();
+    } catch (const isc::Exception& ex) {
+        isc_throw(DCfgMgrBaseError,
+                  "Could not build and commit: " << ex.what());
+    } catch (...) {
+        isc_throw(DCfgMgrBaseError, "Non-ISC exception occurred");
+    }
+}
+
+}; // end of isc::dhcp namespace
+}; // end of isc namespace
+

+ 331 - 0
src/bin/d2/d_cfg_mgr.h

@@ -0,0 +1,331 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef D_CFG_MGR_H
+#define D_CFG_MGR_H
+
+#include <cc/data.h>
+#include <exceptions/exceptions.h>
+#include <dhcpsrv/dhcp_parsers.h>
+
+#include <stdint.h>
+#include <string>
+
+namespace isc {
+namespace d2 {
+
+/// @brief Exception thrown if the configuration manager encounters an error.
+class DCfgMgrBaseError : public isc::Exception {
+public:
+    DCfgMgrBaseError(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+class DCfgContextBase;
+/// @brief Pointer to a configuration context.
+typedef boost::shared_ptr<DCfgContextBase> DCfgContextBasePtr;
+
+/// @brief Abstract class that implements a container for configuration context.
+/// It provides a single enclosure for the storage of configuration parameters
+/// and any other context specific information that needs to be accessible
+/// during configuration parsing as well as to the application as a whole.
+/// The base class supports storage for a small set of simple data types.
+/// Derivations simply add additional storage as needed.  Note that this class
+/// declares the pure virtual clone() method, its copy constructor is protected,
+/// and its copy operator is inaccessible.  Derivations must supply an
+/// implementation of clone that calls the base class copy constructor.
+/// This allows the management class to perform context backup and restoration
+/// without derivation specific knowledge using logic like
+/// the following:
+///
+///    // Make a backup copy
+///    DCfgContextBasePtr backup_copy(context_->clone());
+///    :
+///    // Restore from backup
+///    context_ = backup_copy;
+///
+class DCfgContextBase {
+public:
+    /// @brief Indicator that a configuration parameter is optional.
+    static const bool OPTIONAL = true;
+    static const bool REQUIRED = false;
+
+    /// @brief Constructor
+    DCfgContextBase();
+
+    /// @brief Destructor
+    virtual ~DCfgContextBase();
+
+    /// @brief Fetches the value for a given boolean configuration parameter
+    /// from the context.
+    ///
+    /// @param name is the name of the parameter to retrieve.
+    /// @param value is an output parameter in which to return the retrieved
+    /// value.
+    /// @param optional if true, the parameter is optional and the method
+    /// will not throw if the parameter is not found in the context. The
+    /// contents of the output parameter, value, will not be altered.
+    /// It defaults to false if not specified.
+    /// @throw throws DhcpConfigError if the context does not contain the
+    /// parameter and optional is false.
+    void getParam(const std::string& name, bool& value, bool optional=false);
+
+    /// @brief Fetches the value for a given uint32_t configuration parameter
+    /// from the context.
+    ///
+    /// @param name is the name of the parameter to retrieve.
+    /// @param value is an output parameter in which to return the retrieved
+    /// value.
+    /// @param optional if true, the parameter is optional and the method
+    /// will not throw if the parameter is not found in the context. The
+    /// contents of the output parameter, value, will not be altered.
+    /// @throw throws DhcpConfigError if the context does not contain the
+    /// parameter and optional is false.
+    void getParam(const std::string& name, uint32_t& value,
+                 bool optional=false);
+
+    /// @brief Fetches the value for a given string configuration parameter
+    /// from the context.
+    ///
+    /// @param name is the name of the parameter to retrieve.
+    /// @param value is an output parameter in which to return the retrieved
+    /// value.
+    /// @param optional if true, the parameter is optional and the method
+    /// will not throw if the parameter is not found in the context. The
+    /// contents of the output parameter, value, will not be altered.
+    /// @throw throws DhcpConfigError if the context does not contain the
+    /// parameter and optional is false.
+    void getParam(const std::string& name, std::string& value,
+                  bool optional=false);
+
+    /// @brief Fetches the Boolean Storage. Typically used for passing
+    /// into parsers.
+    ///
+    /// @return returns a pointer to the Boolean Storage.
+    isc::dhcp::BooleanStoragePtr getBooleanStorage() {
+        return (boolean_values_);
+    }
+
+    /// @brief Fetches the uint32 Storage. Typically used for passing
+    /// into parsers.
+    ///
+    /// @return returns a pointer to the uint32 Storage.
+    isc::dhcp::Uint32StoragePtr getUint32Storage() {
+        return (uint32_values_);
+    }
+
+    /// @brief Fetches the string Storage. Typically used for passing
+    /// into parsers.
+    ///
+    /// @return returns a pointer to the string Storage.
+    isc::dhcp::StringStoragePtr getStringStorage() {
+        return (string_values_);
+    }
+
+    /// @brief Creates a clone of this context object.
+    ///
+    /// As mentioned in the the class brief, derivation must supply an
+    /// implementation that initializes the base class storage as well as its
+    /// own.  Typically the derivation's clone method would return the result
+    /// of passing  "*this" into its own copy constructor:
+    ///
+    /// @code
+    /// class DStubContext : public DCfgContextBase {
+    /// public:
+    ///  :
+    ///     // Clone calls its own copy constructor
+    ///     virtual DCfgContextBasePtr clone() {
+    ///         return (DCfgContextBasePtr(new DStubContext(*this)));
+    ///     }
+    ///
+    ///     // Note that the copy constructor calls the base class copy ctor
+    ///     // then initializes its additional storage.
+    ///     DStubContext(const DStubContext& rhs) : DCfgContextBase(rhs),
+    ///         extra_values_(new Uint32Storage(*(rhs.extra_values_))) {
+    ///     }
+    ///  :
+    ///    // Here's the derivation's additional storage.
+    ///    isc::dhcp::Uint32StoragePtr extra_values_;
+    ///  :
+    /// @endcode
+    ///
+    /// @return returns a pointer to the new clone.
+    virtual DCfgContextBasePtr clone() = 0;
+
+protected:
+    /// @brief Copy constructor for use by derivations in clone().
+    DCfgContextBase(const DCfgContextBase& rhs);
+
+private:
+    /// @brief Private assignment operator to avoid potential for slicing.
+    DCfgContextBase& operator=(const DCfgContextBase& rhs);
+
+    /// @brief Storage for boolean parameters.
+    isc::dhcp::BooleanStoragePtr boolean_values_;
+
+    /// @brief Storage for uint32 parameters.
+    isc::dhcp::Uint32StoragePtr uint32_values_;
+
+    /// @brief Storage for string parameters.
+    isc::dhcp::StringStoragePtr string_values_;
+};
+
+/// @brief Defines an unsorted, list of string Element IDs.
+typedef std::vector<std::string> ElementIdList;
+
+/// @brief Configuration Manager
+///
+/// DCfgMgrBase is an abstract class that provides the mechanisms for managing
+/// an application's configuration.  This includes services for parsing sets of
+/// configuration values, storing the parsed information in its converted form,
+/// and retrieving the information on demand.  It is intended to be the worker
+/// class which is handed a set of configuration values to process by upper
+/// application management layers.
+///
+/// The class presents a public method for receiving new configurations,
+/// parseConfig.  This method coordinates the parsing effort as follows:
+///
+/// @code
+///    make backup copy of configuration context
+///    for each top level element in new configuration
+///        get derivation-specific parser for element
+///        run parser
+///        update context with parsed results
+///        break on error
+///
+///    if an error occurred
+///        restore configuration context from backup
+/// @endcode
+///
+/// After making a backup of the current context, it iterates over the top-level
+/// elements in the new configuration.  The order in which the elements are
+/// processed is either:
+///
+///    1. Natural order presented by the configuration set
+///    2. Specific order determined by a list of element ids
+///
+/// This allows a derivation to specify the order in which its elements are
+/// parsed if there are dependencies between elements.
+///
+/// To parse a given element, its id is passed into createConfigParser,
+/// which returns an instance of the appropriate parser.  This method is
+/// abstract so the derivation's implementation determines the type of parser
+/// created. This isolates the knowledge of specific element ids and which
+/// application specific parsers to derivation.
+///
+/// Once the parser has been created, it is used to parse the data value
+/// associated with the element id and update the context with the parsed
+/// results.
+///
+/// In the event that an error occurs, parsing is halted and the
+/// configuration context is restored from backup.
+class DCfgMgrBase {
+public:
+    /// @brief Constructor
+    ///
+    /// @param context is a pointer to the configuration context the manager
+    /// will use for storing parsed results.
+    ///
+    /// @throw throws DCfgMgrBaseError if context is null
+    DCfgMgrBase(DCfgContextBasePtr context);
+
+    /// @brief Destructor
+    virtual ~DCfgMgrBase();
+
+    /// @brief Acts as the receiver of new configurations and coordinates
+    /// the parsing as described in the class brief.
+    ///
+    /// @param config_set is a set of configuration elements to parsed.
+    ///
+    /// @return an Element that contains the results of configuration composed
+    /// of an integer status value (0 means successful, non-zero means failure),
+    /// and a string explanation of the outcome.
+    isc::data::ConstElementPtr parseConfig(isc::data::ConstElementPtr
+                                           config_set);
+
+    /// @brief Adds a given element id to the end of the parse order list.
+    ///
+    /// The order in which elements are retrieved from this is the order in
+    /// which they are added to the list. Derivations should use this method
+    /// to populate the parse order as part of their constructor.
+    ///
+    /// @param element_id is the string name of the element as it will appear
+    /// in the configuration set.
+    void addToParseOrder(const std::string& element_id){
+        parse_order_.push_back(element_id);
+    }
+
+    /// @brief Fetches the parse order list.
+    ///
+    /// @return returns a const reference to the list.
+    const ElementIdList& getParseOrder() const {
+        return (parse_order_);
+    }
+
+    /// @brief Fetches the configuration context.
+    ///
+    /// @return returns a pointer reference to the configuration context.
+    DCfgContextBasePtr& getContext() {
+        return (context_);
+    }
+
+protected:
+    /// @brief  Create a parser instance based on an element id.
+    ///
+    /// Given an element_id returns an instance of the appropriate parser.
+    /// This method is abstract, isolating any direct knowledge of element_ids
+    /// and parsers to within the application-specific derivation.
+    ///
+    /// @param element_id is the string name of the element as it will appear
+    /// in the configuration set.
+    ///
+    /// @return returns a ParserPtr to the parser instance.
+    /// @throw throws DCfgMgrBaseError if an error occurs.
+    virtual isc::dhcp::ParserPtr
+    createConfigParser(const std::string& element_id) = 0;
+
+private:
+
+    /// @brief Parse a configuration element.
+    ///
+    /// Given an element_id and data value, instantiate the appropriate
+    /// parser,  parse the data value, and commit the results.
+    ///
+    /// @param element_id is the string name of the element as it will appear
+    /// in the configuration set.
+    /// @param value is the data value to be parsed and associated with
+    /// element_id.
+    ///
+    /// @throw throws DCfgMgrBaseError if an error occurs.
+    void buildAndCommit(std::string& element_id,
+                        isc::data::ConstElementPtr value);
+
+    /// @brief A list of element ids which specifies the element parsing order.
+    ///
+    /// If the list is empty, the natural order in the configuration set
+    /// it used.
+    ElementIdList parse_order_;
+
+    /// @brief Pointer to the configuration context instance.
+    DCfgContextBasePtr context_;
+};
+
+/// @brief Defines a shared pointer to DCfgMgrBase.
+typedef boost::shared_ptr<DCfgMgrBase> DCfgMgrBasePtr;
+
+
+}; // end of isc::d2 namespace
+}; // end of isc namespace
+
+#endif // D_CFG_MGR_H

+ 1 - 1
src/bin/d2/d_controller.cc

@@ -305,7 +305,7 @@ isc::data::ConstElementPtr
 DControllerBase::updateConfig(isc::data::ConstElementPtr new_config) {
     isc::data::ConstElementPtr full_config;
     if (stand_alone_) {
-        // @TODO Until there is a configuration manager to provide retrieval
+        // @todo Until there is a configuration manager to provide retrieval
         // we'll just assume the incoming config is the full configuration set.
         // It may also make more sense to isolate the controller from the
         // configuration manager entirely. We could do something like

+ 4 - 4
src/bin/d2/d_controller.h

@@ -171,7 +171,7 @@ public:
     /// various configuration values. Installing the dummy handler
     /// that guarantees to return success causes initial configuration
     /// to be stored for the session being created and that it can
-    /// be later accessed with \ref isc::ConfigData::getFullConfig.
+    /// be later accessed with \ref isc::config::ConfigData::getFullConfig.
     ///
     /// @param new_config new configuration.
     ///
@@ -210,7 +210,7 @@ public:
     /// implementation will merge the configuration update into the existing
     /// configuration and then invoke the application process' configure method.
     ///
-    /// @TODO This implementation is will evolve as the D2 configuration
+    /// @todo This implementation is will evolve as the D2 configuration
     /// management task is implemented (trac #2957).
     ///
     /// @param  new_config is the new configuration
@@ -261,7 +261,7 @@ protected:
     ///
     /// @param option is the option "character" from the command line, without
     /// any prefixing hyphen(s)
-    /// @optarg optarg is the argument value (if one) associated with the option
+    /// @param optarg is the argument value (if one) associated with the option
     ///
     /// @return must return true if the option was valid, false is it is
     /// invalid. (Note the default implementation always returns false.)
@@ -395,7 +395,7 @@ protected:
 
     /// @brief Setter for setting the name of the controller's BIND10 spec file.
     ///
-    /// @param value is the file name string.
+    /// @param spec_file_name the file name string.
     void setSpecFileName(const std::string& spec_file_name) {
         spec_file_name_ = spec_file_name;
     }

+ 22 - 2
src/bin/d2/d_process.h

@@ -17,6 +17,8 @@
 
 #include <asiolink/asiolink.h>
 #include <cc/data.h>
+#include <d2/d_cfg_mgr.h>
+
 #include <boost/shared_ptr.hpp>
 
 #include <exceptions/exceptions.h>
@@ -58,13 +60,21 @@ public:
     /// in log statements, but otherwise arbitrary.
     /// @param io_service is the io_service used by the caller for
     /// asynchronous event handling.
+    /// @param cfg_mgr the configuration manager instance that handles
+    /// configuration parsing.
     ///
     /// @throw DProcessBaseError is io_service is NULL.
-    DProcessBase(const char* app_name, IOServicePtr io_service)
-        : app_name_(app_name), io_service_(io_service), shut_down_flag_(false) {
+    DProcessBase(const char* app_name, IOServicePtr io_service, 
+                 DCfgMgrBasePtr cfg_mgr)
+        : app_name_(app_name), io_service_(io_service), shut_down_flag_(false),
+        cfg_mgr_(cfg_mgr) {
         if (!io_service_) {
             isc_throw (DProcessBaseError, "IO Service cannot be null");
         }
+
+        if (!cfg_mgr_) {
+            isc_throw (DProcessBaseError, "CfgMgr cannot be null");
+        }
     };
 
     /// @brief May be used after instantiation to perform initialization unique
@@ -159,6 +169,13 @@ public:
         io_service_->stop();
     }
 
+    /// @brief Fetches the process's configuration manager.
+    ///
+    /// @return returns a reference to the configuration manager.
+    DCfgMgrBasePtr& getCfgMgr() {
+        return (cfg_mgr_);
+    }
+
 private:
     /// @brief Text label for the process. Generally used in log statements,
     /// but otherwise can be arbitrary.
@@ -169,6 +186,9 @@ private:
 
     /// @brief Boolean flag set when shutdown has been requested.
     bool shut_down_flag_;
+
+    /// @brief  Pointer to the configuration manager.
+    DCfgMgrBasePtr cfg_mgr_;
 };
 
 /// @brief Defines a shared pointer to DProcessBase.

+ 197 - 13
src/bin/d2/dhcp-ddns.spec

@@ -1,21 +1,205 @@
+{ 
+"module_spec": 
 {
-  "module_spec": {
     "module_name": "DhcpDdns",
     "module_description": "DHPC-DDNS Service",
     "config_data": [
-    ],
+    { 
+        "item_name": "interface",
+        "item_type": "string",
+        "item_optional": true,
+        "item_default": "eth0"
+    },
+
+    { 
+        "item_name": "ip_address",
+        "item_type": "string",
+        "item_optional": false,
+        "item_default": "127.0.0.1" 
+    },
+
+    { 
+        "item_name": "port",
+        "item_type": "integer",
+        "item_optional": true,
+        "item_default": 51771 
+    },
+    {
+        "item_name": "tsig_keys",
+        "item_type": "list",
+        "item_optional": true, 
+        "item_default": [],
+        "list_item_spec":
+        {
+            "item_name": "tsig_key",
+            "item_type": "map",
+            "item_optional": false,
+            "item_default": {"algorithm" : "hmac_md5"},
+            "map_item_spec": [ 
+            {
+                "item_name": "name",
+                "item_type": "string",
+                "item_optional": false,
+                "item_default": ""
+            },
+            {
+                "item_name": "algorithm",
+                "item_type": "string",
+                "item_optional": false,
+                "item_default": ""
+            },
+            {
+                "item_name": "secret",
+                "item_type": "string",
+                "item_optional": false,
+                "item_default": ""
+            }]
+        }
+    },
+    {
+        "item_name": "forward_ddns",
+        "item_type": "map",
+        "item_optional": true,
+         "item_default": {},
+         "map_item_spec": [ 
+         {
+            "item_name": "ddns_domains",
+            "item_type": "list",
+            "item_optional": false, 
+            "item_default": [],
+            "list_item_spec":
+            {
+                "item_name": "ddns_domain",
+                "item_type": "map",
+                "item_optional": false,
+                "item_default": {},
+                "map_item_spec": [ 
+                { 
+                    "item_name": "name",
+                    "item_type": "string",
+                    "item_optional": false,
+                    "item_default": ""
+                },
+
+                { 
+                    "item_name": "key_name",
+                    "item_type": "string",
+                    "item_optional": true,
+                    "item_default": "" 
+                },
+    
+                {
+                    "item_name": "dns_servers",
+                    "item_type": "list",
+                    "item_optional": false, 
+                    "item_default": [],
+                    "list_item_spec":
+                    {
+                        "item_name": "dns_server",
+                        "item_type": "map",
+                        "item_optional": false, 
+                        "item_default": {},
+                        "map_item_spec": [ 
+                        { 
+                            "item_name": "hostname",
+                            "item_type": "string",
+                            "item_optional": true,
+                            "item_default": ""
+                        },
+                        { 
+                            "item_name": "ip_address",
+                            "item_type": "string",
+                            "item_optional": true,
+                            "item_default": ""
+                        },
+                        { 
+                            "item_name": "port",
+                            "item_type": "integer",
+                            "item_optional": true,
+                            "item_default": 53 
+                        }]
+                    }
+                }]
+            }
+        }]
+    },
+
+    {
+        "item_name": "reverse_ddns",
+        "item_type": "map",
+        "item_optional": true,
+         "item_default": {},
+         "map_item_spec": [ 
+         { 
+            "item_name": "ddns_domains",
+            "item_type": "list",
+            "item_optional": false, 
+            "item_default": [],
+            "list_item_spec":
+            {
+                "item_name": "ddns_domain",
+                "item_type": "map",
+                "item_optional": false,
+                "item_default": {},
+                "map_item_spec": [ 
+                { 
+                    "item_name": "name",
+                    "item_type": "string",
+                    "item_optional": false,
+                    "item_default": ""
+                },
+
+                { 
+                    "item_name": "key_name",
+                    "item_type": "string",
+                    "item_optional": true,
+                    "item_default": "" 
+                },
+    
+                {
+                    "item_name": "dns_servers",
+                    "item_type": "list",
+                    "item_optional": false, 
+                    "item_default": [],
+                    "list_item_spec":
+                    {
+                        "item_name": "dns_server",
+                        "item_type": "map",
+                        "item_optional": false, 
+                        "item_default": {},
+                        "map_item_spec": [ 
+                        { 
+                            "item_name": "hostname",
+                            "item_type": "string",
+                            "item_optional": true,
+                            "item_default": ""
+                        },
+                        { 
+                            "item_name": "ip_address",
+                            "item_type": "string",
+                            "item_optional": true,
+                            "item_default": ""
+                        },
+                        { 
+                            "item_name": "port",
+                            "item_type": "integer",
+                            "item_optional": true,
+                            "item_default": 53 
+                        }]
+                    }
+                }]
+            }
+        }]
+    }],
+
     "commands": [
-      {
-        "command_name": "shutdown",
-        "command_description": "Shut down the DHCP-DDNS service",
-        "command_args": [
-          {
-            "item_name": "pid",
-            "item_type": "integer",
-            "item_optional": true
-          }
-        ]
-      }
+        {
+            "command_name": "shutdown",
+            "command_description": "Shuts down DHCPv6 server.",
+            "command_args": [
+            ]
+        }
     ]
   }
 }
+

+ 6 - 0
src/bin/d2/tests/Makefile.am

@@ -56,6 +56,9 @@ d2_unittests_SOURCES += ../d_process.h
 d2_unittests_SOURCES += ../d_controller.cc ../d2_controller.h
 d2_unittests_SOURCES += ../d2_process.cc ../d2_process.h
 d2_unittests_SOURCES += ../d2_controller.cc ../d2_controller.h
+d2_unittests_SOURCES += ../d_cfg_mgr.cc ../d_cfg_mgr.h
+d2_unittests_SOURCES += ../d2_config.cc ../d2_config.h
+d2_unittests_SOURCES += ../d2_cfg_mgr.cc ../d2_cfg_mgr.h
 d2_unittests_SOURCES += ../d2_update_message.cc ../d2_update_message.h
 d2_unittests_SOURCES += ../d2_zone.cc ../d2_zone.h
 d2_unittests_SOURCES += d_test_stubs.cc d_test_stubs.h
@@ -63,6 +66,8 @@ d2_unittests_SOURCES += d2_unittests.cc
 d2_unittests_SOURCES += d2_process_unittests.cc
 d2_unittests_SOURCES += d_controller_unittests.cc
 d2_unittests_SOURCES += d2_controller_unittests.cc
+d2_unittests_SOURCES += d_cfg_mgr_unittests.cc
+d2_unittests_SOURCES += d2_cfg_mgr_unittests.cc
 d2_unittests_SOURCES += d2_update_message_unittests.cc
 d2_unittests_SOURCES += d2_zone_unittests.cc
 nodist_d2_unittests_SOURCES = ../d2_messages.h ../d2_messages.cc
@@ -75,6 +80,7 @@ d2_unittests_LDADD += $(top_builddir)/src/lib/log/libb10-log.la
 d2_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
 d2_unittests_LDADD += $(top_builddir)/src/lib/cc/libb10-cc.la
 d2_unittests_LDADD += $(top_builddir)/src/lib/config/libb10-cfgclient.la
+d2_unittests_LDADD += $(top_builddir)/src/lib/dhcpsrv/libb10-dhcpsrv.la
 d2_unittests_LDADD += $(top_builddir)/src/lib/dns/libb10-dns++.la
 endif
 

File diff suppressed because it is too large
+ 1238 - 0
src/bin/d2/tests/d2_cfg_mgr_unittests.cc


+ 17 - 11
src/bin/d2/tests/d2_controller_unittests.cc

@@ -54,7 +54,7 @@ public:
 };
 
 /// @brief Basic Controller instantiation testing.
-/// Verfies that the controller singleton gets created and that the
+/// Verifies that the controller singleton gets created and that the
 /// basic derivation from the base class is intact.
 TEST_F(D2ControllerTest, basicInstanceTesting) {
     // Verify the we can the singleton instance can be fetched and that
@@ -80,12 +80,12 @@ TEST_F(D2ControllerTest, basicInstanceTesting) {
 }
 
 /// @brief Tests basic command line processing.
-/// Verfies that:
+/// Verifies that:
 /// 1. Standard command line options are supported.
 /// 2. Invalid options are detected.
 TEST_F(D2ControllerTest, commandLineArgs) {
-    char* argv[] = { const_cast<char*>("progName"), 
-                     const_cast<char*>("-s"), 
+    char* argv[] = { const_cast<char*>("progName"),
+                     const_cast<char*>("-s"),
                      const_cast<char*>("-v") };
     int argc = 3;
 
@@ -101,7 +101,7 @@ TEST_F(D2ControllerTest, commandLineArgs) {
     EXPECT_TRUE(checkVerbose(true));
 
     // Verify that an unknown option is detected.
-    char* argv2[] = { const_cast<char*>("progName"), 
+    char* argv2[] = { const_cast<char*>("progName"),
                       const_cast<char*>("-x") };
     argc = 2;
     EXPECT_THROW(parseArgs(argc, argv2), InvalidUsage);
@@ -119,8 +119,8 @@ TEST_F(D2ControllerTest, initProcessTesting) {
 /// launches with a valid, stand-alone command line and no simulated errors.
 TEST_F(D2ControllerTest, launchNormalShutdown) {
     // command line to run standalone
-    char* argv[] = { const_cast<char*>("progName"), 
-                     const_cast<char*>("-s"), 
+    char* argv[] = { const_cast<char*>("progName"),
+                     const_cast<char*>("-s"),
                      const_cast<char*>("-v") };
     int argc = 3;
 
@@ -165,10 +165,9 @@ TEST_F(D2ControllerTest, configUpdateTests) {
     ASSERT_NO_THROW(initProcess());
     EXPECT_TRUE(checkProcess());
 
-    // Create a configuration set. Content is arbitrary, just needs to be
-    // valid JSON.
-    std::string config = "{ \"test-value\": 1000 } ";
-    isc::data::ElementPtr config_set = isc::data::Element::fromJSON(config);
+    // Create a configuration set using a small, valid D2 configuration.
+    isc::data::ElementPtr config_set =
+                                isc::data::Element::fromJSON(valid_d2_config);
 
     // We are not stand-alone, so configuration should be rejected as there is
     // no session.  This is a pretty contrived situation that shouldn't be
@@ -182,6 +181,13 @@ TEST_F(D2ControllerTest, configUpdateTests) {
     answer = DControllerBase::configHandler(config_set);
     isc::config::parseAnswer(rcode, answer);
     EXPECT_EQ(0, rcode);
+
+    // Use an invalid configuration to verify parsing error return.
+    std::string config = "{ \"bogus\": 1000 } ";
+    config_set = isc::data::Element::fromJSON(config);
+    answer = DControllerBase::configHandler(config_set);
+    isc::config::parseAnswer(rcode, answer);
+    EXPECT_EQ(1, rcode);
 }
 
 /// @brief Command execution tests.

+ 12 - 5
src/bin/d2/tests/d2_process_unittests.cc

@@ -15,6 +15,7 @@
 
 #include <config/ccsession.h>
 #include <d2/d2_process.h>
+#include <d_test_stubs.h>
 
 #include <boost/date_time/posix_time/posix_time.hpp>
 #include <gtest/gtest.h>
@@ -83,17 +84,23 @@ TEST(D2Process, construction) {
 }
 
 /// @brief Verifies basic configure method behavior.
-/// @TODO This test is simplistic and will need to be augmented as configuration
+///  This test is simplistic and will need to be augmented as configuration
 /// ability is implemented.
 TEST_F(D2ProcessTest, configure) {
-    // Verify that given a configuration "set", configure returns
-    // a successful response.
     int rcode = -1;
-    string config = "{ \"test-value\": 1000 } ";
-    isc::data::ElementPtr json = isc::data::Element::fromJSON(config);
+
+    // Use a small, valid D2 configuration to verify successful parsing.
+    isc::data::ElementPtr json = isc::data::Element::fromJSON(valid_d2_config);
     isc::data::ConstElementPtr answer = process_->configure(json);
     isc::config::parseAnswer(rcode, answer);
     EXPECT_EQ(0, rcode);
+
+    // Use an invalid configuration to verify parsing error return.
+    string config = "{ \"bogus\": 1000 } ";
+    json = isc::data::Element::fromJSON(config);
+    answer = process_->configure(json);
+    isc::config::parseAnswer(rcode, answer);
+    EXPECT_EQ(1, rcode);
 }
 
 /// @brief Verifies basic command method behavior.

+ 386 - 0
src/bin/d2/tests/d_cfg_mgr_unittests.cc

@@ -0,0 +1,386 @@
+// Copyright (C) 2013  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config/ccsession.h>
+#include <config/module_spec.h>
+#include <dhcpsrv/dhcp_parsers.h>
+#include <d2/d_cfg_mgr.h>
+#include <d_test_stubs.h>
+
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <gtest/gtest.h>
+
+#include <config.h>
+#include <sstream>
+
+using namespace std;
+using namespace isc;
+using namespace isc::config;
+using namespace isc::d2;
+using namespace boost::posix_time;
+
+namespace {
+
+/// @brief Test Class for verifying that configuration context cannot be null
+/// during construction.
+class DCtorTestCfgMgr : public DCfgMgrBase {
+public:
+    /// @brief Constructor - Note that is passes in an empty configuration
+    /// pointer to the base class constructor.
+    DCtorTestCfgMgr() : DCfgMgrBase(DCfgContextBasePtr()) {
+    }
+
+    /// @brief Destructor
+    virtual ~DCtorTestCfgMgr() {
+    }
+
+    /// @brief Dummy implementation as this method is abstract.
+    virtual isc::dhcp::ParserPtr
+    createConfigParser(const std::string& /* element_id */) {
+        return (isc::dhcp::ParserPtr());
+    }
+};
+
+/// @brief Test fixture class for testing DCfgMgrBase class.
+/// It maintains an member instance of DStubCfgMgr and derives from
+/// ConfigParseTest fixture, thus providing methods for converting JSON
+/// strings to configuration element sets, checking parse results, and
+/// accessing the configuration context.
+class DStubCfgMgrTest : public ConfigParseTest {
+public:
+
+    /// @brief Constructor
+    DStubCfgMgrTest():cfg_mgr_(new DStubCfgMgr) {
+    }
+
+    /// @brief Destructor
+    ~DStubCfgMgrTest() {
+    }
+
+    /// @brief Convenience method which returns a DStubContextPtr to the
+    /// configuration context.
+    ///
+    /// @return returns a DStubContextPtr.
+    DStubContextPtr getStubContext() {
+        return (boost::dynamic_pointer_cast<DStubContext>
+                (cfg_mgr_->getContext()));
+    }
+
+    /// @brief Configuration manager instance.
+    DStubCfgMgrPtr cfg_mgr_;
+};
+
+///@brief Tests basic construction/destruction of configuration manager.
+/// Verifies that:
+/// 1. Proper construction succeeds.
+/// 2. Configuration context is initialized by construction.
+/// 3. Destruction works properly.
+/// 4. Construction with a null context is not allowed.
+TEST(DCfgMgrBase, construction) {
+    DCfgMgrBasePtr cfg_mgr;
+
+    // Verify that configuration manager constructions without error.
+    ASSERT_NO_THROW(cfg_mgr.reset(new DStubCfgMgr()));
+
+    // Verify that the context can be retrieved and is not null.
+    DCfgContextBasePtr context = cfg_mgr->getContext();
+    EXPECT_TRUE(context);
+
+    // Verify that the manager can be destructed without error.
+    EXPECT_NO_THROW(cfg_mgr.reset());
+
+    // Verify that an attempt to construct a manger with a null context fails.
+    ASSERT_THROW(DCtorTestCfgMgr(), DCfgMgrBaseError);
+}
+
+///@brief Tests fundamental aspects of configuration parsing.
+/// Verifies that:
+/// 1. A correctly formed simple configuration parses without error.
+/// 2. An error building the element is handled.
+/// 3. An error committing the element is handled.
+/// 4. An unknown element error is handled.
+TEST_F(DStubCfgMgrTest, basicParseTest) {
+    // Create a simple configuration.
+    string config = "{ \"test-value\": 1000 } ";
+    ASSERT_TRUE(fromJSON(config));
+
+    // Verify that we can parse a simple configuration.
+    answer_ = cfg_mgr_->parseConfig(config_set_);
+    EXPECT_TRUE(checkAnswer(0));
+
+    // Verify that an error building the element is caught and returns a
+    // failed parse result.
+    SimFailure::set(SimFailure::ftElementBuild);
+    answer_ = cfg_mgr_->parseConfig(config_set_);
+    EXPECT_TRUE(checkAnswer(1));
+
+    // Verify that an error committing the element is caught and returns a
+    // failed parse result.
+    SimFailure::set(SimFailure::ftElementCommit);
+    answer_ = cfg_mgr_->parseConfig(config_set_);
+    EXPECT_TRUE(checkAnswer(1));
+
+    // Verify that an unknown element error is caught and returns a failed
+    // parse result.
+    SimFailure::set(SimFailure::ftElementUnknown);
+    answer_ = cfg_mgr_->parseConfig(config_set_);
+    EXPECT_TRUE(checkAnswer(1));
+}
+
+///@brief Tests ordered and non-ordered element parsing
+/// This test verifies that:
+/// 1. Non-ordered parsing parses elements in the order they are presented
+/// by the configuration set (as-they-come).
+/// 2. A parse order list with too few elements is detected.
+/// 3. Ordered parsing parses the elements in the order specified by the
+/// configuration manager's parse order list.
+/// 4. A parse order list with too many elements is detected.
+TEST_F(DStubCfgMgrTest, parseOrderTest) {
+    // Element ids used for test.
+    std::string charlie("charlie");
+    std::string bravo("bravo");
+    std::string alpha("alpha");
+
+    // Create the test configuration with the elements in "random" order.
+
+    // NOTE that element sets produced by  isc::data::Element::fromJSON(),
+    // are in lexical order by element_id. This means that iterating over
+    // such an element set, will present the elements in lexical order. Should
+    // this change, this test will need to be modified accordingly.
+    string config = "{ \"bravo\": 2,  "
+                     " \"alpha\": 1,  "
+                     " \"charlie\": 3 } ";
+    ASSERT_TRUE(fromJSON(config));
+
+    // Verify that non-ordered parsing, results in an as-they-come parse order.
+    // Create an expected parse order.
+    // (NOTE that iterating over Element sets produced by fromJSON() will
+    // present the elements in lexical order.  Should this change, the expected
+    // order list below would need to be changed accordingly).
+    ElementIdList order_expected;
+    order_expected.push_back(alpha);
+    order_expected.push_back(bravo);
+    order_expected.push_back(charlie);
+
+    // Verify that the manager has an EMPTY parse order list. (Empty list
+    // instructs the manager to parse them as-they-come.)
+    EXPECT_EQ(0, cfg_mgr_->getParseOrder().size());
+
+    // Parse the configuration, verify it parses without error.
+    answer_ = cfg_mgr_->parseConfig(config_set_);
+    EXPECT_TRUE(checkAnswer(0));
+
+    // Verify that the parsed order matches what we expected.
+    EXPECT_TRUE(cfg_mgr_->parsed_order_ ==  order_expected);
+
+    // Clear the manager's parse order "memory".
+    cfg_mgr_->parsed_order_.clear();
+
+    // Create a parse order list that has too few entries.  Verify that
+    // when parsing the test config, it fails.
+    cfg_mgr_->addToParseOrder(charlie);
+    // Verify the parse order list is the size we expect.
+    EXPECT_EQ(1, cfg_mgr_->getParseOrder().size());
+
+    // Verify the configuration fails.
+    answer_ = cfg_mgr_->parseConfig(config_set_);
+    EXPECT_TRUE(checkAnswer(1));
+
+    // Verify that the configuration parses correctly, when the parse order
+    // is correct.  Add the needed entries to the parse order
+    cfg_mgr_->addToParseOrder(bravo);
+    cfg_mgr_->addToParseOrder(alpha);
+
+    // Verify the parse order list is the size we expect.
+    EXPECT_EQ(3, cfg_mgr_->getParseOrder().size());
+
+    // Clear the manager's parse order "memory".
+    cfg_mgr_->parsed_order_.clear();
+
+    // Verify the configuration parses without error.
+    answer_ = cfg_mgr_->parseConfig(config_set_);
+    EXPECT_TRUE(checkAnswer(0));
+
+    // Verify that the parsed order is the order we configured.
+    EXPECT_TRUE(cfg_mgr_->getParseOrder() == cfg_mgr_->parsed_order_);
+
+    // Create a parse order list that has too many entries.  Verify that
+    // when parsing the test config, it fails.
+    cfg_mgr_->addToParseOrder("delta");
+
+    // Verify the parse order list is the size we expect.
+    EXPECT_EQ(4, cfg_mgr_->getParseOrder().size());
+
+    // Verify the configuration fails.
+    answer_ = cfg_mgr_->parseConfig(config_set_);
+    EXPECT_TRUE(checkAnswer(1));
+}
+
+/// @brief Tests that element ids supported by the base class as well as those
+/// added by the derived class function properly.
+/// This test verifies that:
+/// 1. Boolean parameters can be parsed and retrieved.
+/// 2. Uint32 parameters can be parsed and retrieved.
+/// 3. String parameters can be parsed and retrieved.
+/// 4. Derivation-specific parameters can be parsed and retrieved.
+/// 5. Parsing a second configuration, updates the existing context values
+/// correctly.
+TEST_F(DStubCfgMgrTest, simpleTypesTest) {
+    // Fetch a derivation specific pointer to the context.
+    DStubContextPtr context = getStubContext();
+    ASSERT_TRUE(context);
+
+    // Create a configuration with all of the parameters.
+    string config = "{ \"bool_test\": true , "
+                    "  \"uint32_test\": 77 , "
+                    "  \"string_test\": \"hmmm chewy\" , "
+                    "  \"extra_test\": 430 } ";
+    ASSERT_TRUE(fromJSON(config));
+
+    // Verify that the configuration parses without error.
+    answer_ = cfg_mgr_->parseConfig(config_set_);
+    ASSERT_TRUE(checkAnswer(0));
+
+    // Verify that the boolean parameter was parsed correctly by retrieving
+    // its value from the context.
+    bool actual_bool = false;
+    EXPECT_NO_THROW(context->getParam("bool_test", actual_bool));
+    EXPECT_EQ(true, actual_bool);
+
+    // Verify that the uint32 parameter was parsed correctly by retrieving
+    // its value from the context.
+    uint32_t actual_uint32 = 0;
+    EXPECT_NO_THROW(context->getParam("uint32_test", actual_uint32));
+    EXPECT_EQ(77, actual_uint32);
+
+    // Verify that the string parameter was parsed correctly by retrieving
+    // its value from the context.
+    std::string actual_string = "";
+    EXPECT_NO_THROW(context->getParam("string_test", actual_string));
+    EXPECT_EQ("hmmm chewy", actual_string);
+
+    // Verify that the "extra" parameter was parsed correctly by retrieving
+    // its value from the context.
+    uint32_t actual_extra = 0;
+    EXPECT_NO_THROW(context->getExtraParam("extra_test", actual_extra));
+    EXPECT_EQ(430, actual_extra);
+
+    // Create a configuration which "updates" all of the parameter values.
+    string config2 = "{ \"bool_test\": false , "
+                    "  \"uint32_test\": 88 , "
+                    "  \"string_test\": \"ewww yuk!\" , "
+                    "  \"extra_test\": 11 } ";
+    ASSERT_TRUE(fromJSON(config2));
+
+    // Verify that the configuration parses without error.
+    answer_ = cfg_mgr_->parseConfig(config_set_);
+    EXPECT_TRUE(checkAnswer(0));
+
+    // Verify that the boolean parameter was updated correctly by retrieving
+    // its value from the context.
+    actual_bool = true;
+    EXPECT_NO_THROW(context->getParam("bool_test", actual_bool));
+    EXPECT_FALSE(actual_bool);
+
+    // Verify that the uint32 parameter was updated correctly by retrieving
+    // its value from the context.
+    actual_uint32 = 0;
+    EXPECT_NO_THROW(context->getParam("uint32_test", actual_uint32));
+    EXPECT_EQ(88, actual_uint32);
+
+    // Verify that the string parameter was updated correctly by retrieving
+    // its value from the context.
+    actual_string = "";
+    EXPECT_NO_THROW(context->getParam("string_test", actual_string));
+    EXPECT_EQ("ewww yuk!", actual_string);
+
+    // Verify that the "extra" parameter was updated correctly by retrieving
+    // its value from the context.
+    actual_extra = 0;
+    EXPECT_NO_THROW(context->getExtraParam("extra_test", actual_extra));
+    EXPECT_EQ(11, actual_extra);
+}
+
+/// @brief Tests that the configuration context is preserved after failure
+/// during parsing causes a rollback.
+/// 1. Verifies configuration context rollback.
+TEST_F(DStubCfgMgrTest, rollBackTest) {
+    // Fetch a derivation specific pointer to the context.
+    DStubContextPtr context = getStubContext();
+    ASSERT_TRUE(context);
+
+    // Create a configuration with all of the parameters.
+    string config = "{ \"bool_test\": true , "
+                    "  \"uint32_test\": 77 , "
+                    "  \"string_test\": \"hmmm chewy\" , "
+                    "  \"extra_test\": 430 } ";
+    ASSERT_TRUE(fromJSON(config));
+
+    // Verify that the configuration parses without error.
+    answer_ = cfg_mgr_->parseConfig(config_set_);
+    EXPECT_TRUE(checkAnswer(0));
+
+    // Verify that all of parameters have the expected values.
+    bool actual_bool = false;
+    EXPECT_NO_THROW(context->getParam("bool_test", actual_bool));
+    EXPECT_EQ(true, actual_bool);
+
+    uint32_t actual_uint32 = 0;
+    EXPECT_NO_THROW(context->getParam("uint32_test", actual_uint32));
+    EXPECT_EQ(77, actual_uint32);
+
+    std::string actual_string = "";
+    EXPECT_NO_THROW(context->getParam("string_test", actual_string));
+    EXPECT_EQ("hmmm chewy", actual_string);
+
+    uint32_t actual_extra = 0;
+    EXPECT_NO_THROW(context->getExtraParam("extra_test", actual_extra));
+    EXPECT_EQ(430, actual_extra);
+
+    // Create a configuration which "updates" all of the parameter values
+    // plus one unknown at the end.
+    string config2 = "{ \"bool_test\": false , "
+                    "  \"uint32_test\": 88 , "
+                    "  \"string_test\": \"ewww yuk!\" , "
+                    "  \"extra_test\": 11 , "
+                    "  \"zeta_unknown\": 33 } ";
+    ASSERT_TRUE(fromJSON(config2));
+
+    // Force a failure on the last element
+    SimFailure::set(SimFailure::ftElementUnknown);
+    answer_ = cfg_mgr_->parseConfig(config_set_);
+    EXPECT_TRUE(checkAnswer(1));
+
+    // Refresh our local pointer.
+    context = getStubContext();
+
+    // Verify that all of parameters have the original values.
+    actual_bool = false;
+    EXPECT_NO_THROW(context->getParam("bool_test", actual_bool));
+    EXPECT_EQ(true, actual_bool);
+
+    actual_uint32 = 0;
+    EXPECT_NO_THROW(context->getParam("uint32_test", actual_uint32));
+    EXPECT_EQ(77, actual_uint32);
+
+    actual_string = "";
+    EXPECT_NO_THROW(context->getParam("string_test", actual_string));
+    EXPECT_EQ("hmmm chewy", actual_string);
+
+    actual_extra = 0;
+    EXPECT_NO_THROW(context->getExtraParam("extra_test", actual_extra));
+    EXPECT_EQ(430, actual_extra);
+}
+
+} // end of anonymous namespace

+ 124 - 2
src/bin/d2/tests/d_test_stubs.cc

@@ -21,6 +21,31 @@ using namespace asio;
 namespace isc {
 namespace d2 {
 
+const char* valid_d2_config = "{ "
+                        "\"interface\" : \"eth1\" , "
+                        "\"ip_address\" : \"192.168.1.33\" , "
+                        "\"port\" : 88 , "
+                        "\"tsig_keys\": ["
+                        "{ \"name\": \"d2_key.tmark.org\" , "
+                        "   \"algorithm\": \"md5\" ,"
+                        "   \"secret\": \"0123456989\" "
+                        "} ],"
+                        "\"forward_ddns\" : {"
+                        "\"ddns_domains\": [ "
+                        "{ \"name\": \"tmark.org\" , "
+                        "  \"key_name\": \"d2_key.tmark.org\" , "
+                        "  \"dns_servers\" : [ "
+                        "  { \"hostname\": \"one.tmark\" } "
+                        "] } ] }, "
+                        "\"reverse_ddns\" : {"
+                        "\"ddns_domains\": [ "
+                        "{ \"name\": \" 0.168.192.in.addr.arpa.\" , "
+                        "  \"key_name\": \"d2_key.tmark.org\" , "
+                        "  \"dns_servers\" : [ "
+                        "  { \"ip_address\": \"127.0.0.101\" , "
+                        "    \"port\": 100 } ] } "
+                        "] } }";
+
 // Initialize the static failure flag.
 SimFailure::FailureType SimFailure::failure_type_ = SimFailure::ftNoFailure;
 
@@ -28,7 +53,7 @@ SimFailure::FailureType SimFailure::failure_type_ = SimFailure::ftNoFailure;
 const char*  DStubProcess::stub_proc_command_("cool_proc_cmd");
 
 DStubProcess::DStubProcess(const char* name, IOServicePtr io_service)
-    : DProcessBase(name, io_service) {
+    : DProcessBase(name, io_service, DCfgMgrBasePtr(new DStubCfgMgr())) {
 };
 
 void
@@ -130,7 +155,7 @@ DStubController::DStubController()
 
     if (getenv("B10_FROM_BUILD")) {
         setSpecFileName(std::string(getenv("B10_FROM_BUILD")) +
-            "/src/bin/d2/d2.spec");
+            "/src/bin/d2/dhcp-ddns.spec");
     } else {
         setSpecFileName(D2_SPECFILE_LOCATION);
     }
@@ -191,5 +216,102 @@ DStubController::~DStubController() {
 // Initialize controller wrapper's static instance getter member.
 DControllerTest::InstanceGetter DControllerTest::instanceGetter_ = NULL;
 
+//************************** TestParser *************************
+
+TestParser::TestParser(const std::string& param_name):param_name_(param_name) {
+}
+
+TestParser::~TestParser(){
+}
+
+void
+TestParser::build(isc::data::ConstElementPtr new_config) {
+    if (SimFailure::shouldFailOn(SimFailure::ftElementBuild)) {
+        // Simulates an error during element data parsing.
+        isc_throw (DCfgMgrBaseError, "Simulated build exception");
+    }
+
+    value_ = new_config;
+}
+
+void
+TestParser::commit() {
+    if (SimFailure::shouldFailOn(SimFailure::ftElementCommit)) {
+        // Simulates an error while committing the parsed element data.
+        throw std::runtime_error("Simulated commit exception");
+    }
+}
+
+//************************** DStubContext *************************
+
+DStubContext::DStubContext(): extra_values_(new isc::dhcp::Uint32Storage()) {
+}
+
+DStubContext::~DStubContext() {
+}
+
+void
+DStubContext::getExtraParam(const std::string& name, uint32_t& value) {
+    value = extra_values_->getParam(name);
+}
+
+isc::dhcp::Uint32StoragePtr
+DStubContext::getExtraStorage() {
+    return (extra_values_);
+}
+
+DCfgContextBasePtr
+DStubContext::clone() {
+    return (DCfgContextBasePtr(new DStubContext(*this)));
+}
+
+DStubContext::DStubContext(const DStubContext& rhs): DCfgContextBase(rhs),
+    extra_values_(new isc::dhcp::Uint32Storage(*(rhs.extra_values_))) {
+}
+
+//************************** DStubCfgMgr *************************
+
+DStubCfgMgr::DStubCfgMgr()
+    : DCfgMgrBase(DCfgContextBasePtr(new DStubContext())) {
+}
+
+DStubCfgMgr::~DStubCfgMgr() {
+}
+
+isc::dhcp::ParserPtr
+DStubCfgMgr::createConfigParser(const std::string& element_id) {
+    isc::dhcp::DhcpConfigParser* parser = NULL;
+    DStubContextPtr context =
+                    boost::dynamic_pointer_cast<DStubContext>(getContext());
+
+    if (element_id == "bool_test") {
+        parser = new isc::dhcp::BooleanParser(element_id,
+                                              context->getBooleanStorage());
+    } else if (element_id == "uint32_test") {
+        parser = new isc::dhcp::Uint32Parser(element_id,
+                                             context->getUint32Storage());
+    } else if (element_id == "string_test") {
+        parser = new isc::dhcp::StringParser(element_id,
+                                             context->getStringStorage());
+    } else if (element_id == "extra_test") {
+        parser = new isc::dhcp::Uint32Parser(element_id,
+                                             context->getExtraStorage());
+    } else {
+        // Fail only if SimFailure dictates we should.  This makes it easier
+        // to test parse ordering, by permitting a wide range of element ids
+        // to "succeed" without specifically supporting them.
+        if (SimFailure::shouldFailOn(SimFailure::ftElementUnknown)) {
+            isc_throw(DCfgMgrBaseError, "Configuration parameter not supported: "
+                      << element_id);
+        }
+
+        parsed_order_.push_back(element_id);
+        parser = new TestParser(element_id);
+    }
+
+    return (isc::dhcp::ParserPtr(parser));
+}
+
+
 }; // namespace isc::d2
 }; // namespace isc

+ 214 - 1
src/bin/d2/tests/d_test_stubs.h

@@ -21,11 +21,18 @@
 #include <config/ccsession.h>
 
 #include <d2/d_controller.h>
+#include <d2/d_cfg_mgr.h>
+
 #include <gtest/gtest.h>
 
 namespace isc {
 namespace d2 {
 
+/// @brief Provides a valid DHCP-DDNS configuration for testing basic
+/// parsing fundamentals.
+extern const char* valid_d2_config;
+
+
 /// @brief Class is used to set a globally accessible value that indicates
 /// a specific type of failure to simulate.  Test derivations of base classes
 /// can exercise error handling code paths by testing for specific SimFailure
@@ -43,7 +50,10 @@ public:
         ftProcessConfigure,
         ftControllerCommand,
         ftProcessCommand,
-        ftProcessShutdown
+        ftProcessShutdown,
+        ftElementBuild,
+        ftElementCommit,
+        ftElementUnknown
     };
 
     /// @brief Sets the SimFailure value to the given value.
@@ -80,10 +90,12 @@ public:
         return (false);
     }
 
+    /// @brief Resets the failure type to none.
     static void clear() {
        failure_type_ = ftNoFailure;
     }
 
+    /// @brief Static value for holding the failure type to simulate.
     static enum FailureType failure_type_;
 };
 
@@ -433,6 +445,207 @@ public:
     }
 };
 
+/// @brief Simple parser derivation for testing the basics of configuration
+/// parsing.
+class TestParser : public isc::dhcp::DhcpConfigParser {
+public:
+
+    /// @brief Constructor
+    ///
+    /// See @ref DhcpConfigParser class for details.
+    ///
+    /// @param param_name name of the parsed parameter
+    TestParser(const std::string& param_name);
+
+    /// @brief Destructor
+    virtual ~TestParser();
+
+    /// @brief Builds parameter value.
+    ///
+    /// See @ref DhcpConfigParser class for details.
+    ///
+    /// @param new_config pointer to the new configuration
+    /// @throw throws DCfgMgrBaseError if the SimFailure is set to
+    /// ftElementBuild. This allows for the simulation of an
+    /// exception during the build portion of parsing an element.
+    virtual void build(isc::data::ConstElementPtr new_config);
+
+    /// @brief Commits the parsed value to storage.
+    ///
+    /// See @ref DhcpConfigParser class for details.
+    ///
+    /// @throw throws DCfgMgrBaseError if SimFailure is set to ftElementCommit.
+    /// This allows for the simulation of an exception during the commit
+    /// portion of parsing an element.
+    virtual void commit();
+
+private:
+    /// name of the parsed parameter
+    std::string param_name_;
+
+    /// pointer to the parsed value of the parameter
+    isc::data::ConstElementPtr value_;
+};
+
+/// @brief Test Derivation of the DCfgContextBase class.
+///
+/// This class is used to test basic functionality of configuration context.
+/// It adds an additional storage container "extra values" to mimic an
+/// application extension of configuration storage.  This permits testing that
+/// both the base class content as well as the application content is
+/// correctly copied during cloning.  This is vital to configuration backup
+/// and rollback during configuration parsing.
+class DStubContext : public DCfgContextBase {
+public:
+
+    /// @brief Constructor
+    DStubContext();
+
+    /// @brief Destructor
+    virtual ~DStubContext();
+
+    /// @brief Fetches the value for a given "extra" configuration parameter
+    /// from the context.
+    ///
+    /// @param name is the name of the parameter to retrieve.
+    /// @param value is an output parameter in which to return the retrieved
+    /// value.
+    /// @throw throws DhcpConfigError if the context does not contain the
+    /// parameter.
+    void getExtraParam(const std::string& name, uint32_t& value);
+
+    /// @brief Fetches the extra storage.
+    ///
+    /// @return returns a pointer to the extra storage.
+    isc::dhcp::Uint32StoragePtr getExtraStorage();
+
+    /// @brief Creates a clone of a DStubContext.
+    ///
+    /// @return returns a pointer to the new clone.
+    virtual DCfgContextBasePtr clone();
+
+protected:
+    /// @brief Copy constructor
+    DStubContext(const DStubContext& rhs);
+
+private:
+    /// @brief Private assignment operator, not implemented.
+    DStubContext& operator=(const DStubContext& rhs);
+
+    /// @brief Extra storage for uint32 parameters.
+    isc::dhcp::Uint32StoragePtr extra_values_;
+};
+
+/// @brief Defines a pointer to DStubContext.
+typedef boost::shared_ptr<DStubContext> DStubContextPtr;
+
+/// @brief Test Derivation of the DCfgMgrBase class.
+///
+/// This class is used to test basic functionality of configuration management.
+/// It supports the following configuration elements:
+///
+/// "bool_test" - Boolean element, tests parsing and committing a boolean
+///               configuration parameter.
+/// "uint32_test" - Uint32 element, tests parsing and committing a uint32_t
+///               configuration parameter.
+/// "string_test" - String element, tests parsing and committing a string
+///               configuration parameter.
+/// "extra_test" - "Extra" element, tests parsing and committing an extra
+///               configuration parameter. (This is used to demonstrate
+///               derivation's addition of storage to configuration context.
+///
+/// It also keeps track of the element ids that are parsed in the order they
+/// are parsed.  This is used to test ordered and non-ordered parsing.
+class DStubCfgMgr : public DCfgMgrBase {
+public:
+    /// @brief Constructor
+    DStubCfgMgr();
+
+    /// @brief Destructor
+    virtual ~DStubCfgMgr();
+
+    /// @brief Given an element_id returns an instance of the appropriate
+    /// parser. It supports the element ids as described in the class brief.
+    ///
+    /// @param element_id is the string name of the element as it will appear
+    /// in the configuration set.
+    ///
+    /// @return returns a ParserPtr to the parser instance.
+    /// @throw throws DCfgMgrBaseError if SimFailure is ftElementUnknown.
+    virtual isc::dhcp::ParserPtr
+    createConfigParser(const std::string& element_id);
+
+    /// @brief A list for remembering the element ids in the order they were
+    /// parsed.
+    ElementIdList parsed_order_;
+};
+
+/// @brief Defines a pointer to DStubCfgMgr.
+typedef boost::shared_ptr<DStubCfgMgr> DStubCfgMgrPtr;
+
+/// @brief Test fixture base class for any fixtures which test parsing.
+/// It provides methods for converting JSON strings to configuration element
+/// sets and checking parse results
+class ConfigParseTest : public ::testing::Test {
+public:
+
+    /// @brief Constructor
+    ConfigParseTest(){
+    }
+
+    /// @brief Destructor
+    ~ConfigParseTest() {
+    }
+
+    /// @brief Converts a given JSON string into an Element set and stores the
+    /// result the member variable, config_set_.
+    ///
+    /// @param json_text contains the configuration text in JSON format to
+    /// convert.
+    /// @return returns AssertionSuccess if there were no parsing errors,
+    /// AssertionFailure otherwise.
+    ::testing::AssertionResult fromJSON(std::string& json_text) {
+        try  {
+            config_set_ = isc::data::Element::fromJSON(json_text);
+        } catch (const isc::Exception &ex) {
+            return  ::testing::AssertionFailure() 
+                << "JSON text failed to parse:" << ex.what();
+        }
+
+        return ::testing::AssertionSuccess();
+    }
+
+
+    /// @brief Compares the status in the  parse result stored in member
+    /// variable answer_ to a given value.
+    ///
+    /// @param should_be is an integer against which to compare the status.
+    ///
+    /// @return returns AssertionSuccess if there were no parsing errors,
+    /// AssertionFailure otherwise.
+    ::testing::AssertionResult checkAnswer(int should_be) {
+        int rcode = 0;
+        isc::data::ConstElementPtr comment;
+        comment = isc::config::parseAnswer(rcode, answer_);
+        if (rcode == should_be) {
+            return testing::AssertionSuccess();
+        }
+
+        return ::testing::AssertionFailure() << "checkAnswer rcode:" 
+               << rcode << " comment: " << *comment << std::endl;
+    }
+
+    /// @brief Configuration set being tested.
+    isc::data::ElementPtr config_set_;
+
+    /// @brief Results of most recent element parsing.
+    isc::data::ConstElementPtr answer_;
+};
+
+/// @brief Defines a small but valid DHCP-DDNS compliant configuration for
+/// testing configuration parsing fundamentals.
+extern const char* valid_d2_config;
+
 }; // namespace isc::d2
 }; // namespace isc
 

+ 17 - 0
src/bin/d2/tests/test_data_files_config.h.in

@@ -0,0 +1,17 @@
+// Copyright (C) 2009  Internet Systems Consortium, Inc. ("ISC")   
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+/// @brief Path to D2 source dir so tests against the dhcp-ddns.spec file
+/// can find it reliably.
+#define D2_SRC_DIR "@abs_top_srcdir@/src/bin/d2"

+ 1 - 1
src/lib/dhcpsrv/dhcp_parsers.cc

@@ -79,9 +79,9 @@ DebugParser::DebugParser(const std::string& param_name)
 
 void 
 DebugParser::build(ConstElementPtr new_config) {
+    value_ = new_config;
     std::cout << "Build for token: [" << param_name_ << "] = ["
         << value_->str() << "]" << std::endl; 
-    value_ = new_config;
 }
 
 void