Browse Source

[2957] This is the second of a two part commit for D2CfgMgr. It Adds a
collection of classes for housing and parsing the application configuration
necessary for the DHCP-DDNS application. This rounds out the D2CfgMgr initial
implementation.

New files:
src/bin/d2/d2_config.cc
src/bin/d2/d2_config.h
src/bin/d2/tests/d2_cfg_mgr_unittests.cc

Thomas Markwalder 12 years ago
parent
commit
be6122f520

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

@@ -53,6 +53,7 @@ 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
 
 nodist_b10_dhcp_ddns_SOURCES = d2_messages.h d2_messages.cc

+ 68 - 9
src/bin/d2/d2_cfg_mgr.cc

@@ -15,6 +15,8 @@
 #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;
@@ -26,12 +28,21 @@ namespace d2 {
 
 // *********************** D2CfgContext  *************************
 
-D2CfgContext::D2CfgContext() {
-    // @TODO - initialize D2 specific storage
+D2CfgContext::D2CfgContext()
+    : forward_mgr_(new DdnsDomainListMgr("forward_mgr")),
+      reverse_mgr_(new DdnsDomainListMgr("reverse_mgr")) {
 }
 
-D2CfgContext::D2CfgContext(const D2CfgContext& rhs) : DCfgContextBase(rhs)
-    /* @TODO copy D2 specific storage  */ {
+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());
+    }
 }
 
 D2CfgContext::~D2CfgContext() {
@@ -45,12 +56,60 @@ D2CfgMgr::D2CfgMgr() : DCfgMgrBase(DCfgContextBasePtr(new D2CfgContext())) {
 D2CfgMgr::~D2CfgMgr() {
 }
 
+bool
+D2CfgMgr::matchForward(const std::string& fqdn, DdnsDomainPtr& domain) {
+    if (fqdn == "") {
+        // 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 == "") {
+        // 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& element_id) {
-    // @TODO This is only enough implementation for integration.
-    // This will expand to support the top level D2 elements.
-    // For now we will simply return a debug parser for everything.
-    return (isc::dhcp::ParserPtr(new isc::dhcp::DebugParser(element_id)));
+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());
+    } else if (config_id ==  "reverse_ddns") {
+        parser = new DdnsDomainListMgrParser("reverse_mgr",
+                                             context->getReverseMgr());
+    } else {
+        isc_throw(NotImplemented,
+                  "parser error: D2CfgMgr parameter not supported: "
+                  << config_id);
+    }
+
+    return (isc::dhcp::ParserPtr(parser));
 }
 
 }; // end of isc::dhcp namespace

+ 62 - 10
src/bin/d2/d2_cfg_mgr.h

@@ -12,9 +12,11 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
+#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>
@@ -28,11 +30,9 @@ namespace d2 {
 /// @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 context specific information that needs to be accessible
+/// and any other DHCP-DDNS specific information that needs to be accessible
 /// during configuration parsing as well as to the application as a whole.
-/// @TODO - add in storage of D2 specific storage like domain-to-dns_server
-/// mapping.  This is the initial implementation necessary to integrate
-/// configuration management into D2.
+/// It is derived from the context base class, DCfgContextBase.
 class D2CfgContext : public DCfgContextBase {
 public:
     /// @brief Constructor
@@ -42,11 +42,26 @@ public:
     virtual ~D2CfgContext();
 
     /// @brief Creates a clone of this context object.
+    ///
     /// @return returns a raw pointer to the new clone.
     virtual D2CfgContext* clone() {
             return (new D2CfgContext(*this));
     }
 
+    /// @brief Fetches the forward DNS domain list manager.
+    ///
+    /// @return returns a pointer reference to the forward manager.
+    DdnsDomainListMgrPtr& getForwardMgr() {
+        return (forward_mgr_);
+    }
+
+    /// @brief Fetches the reverse DNS domain list manager.
+    ///
+    /// @return returns a pointer reference to the reverse manager.
+    DdnsDomainListMgrPtr& getReverseMgr() {
+        return (reverse_mgr_);
+    }
+
 protected:
     /// @brief Copy constructor for use by derivations in clone().
     D2CfgContext(const D2CfgContext& rhs);
@@ -55,9 +70,17 @@ private:
     /// @brief Private assignment operator to avoid potential for slicing.
     D2CfgContext& operator=(const D2CfgContext& rhs);
 
-    /// @TODO storage for DNS domain-server mapping will be added here
+    /// @brief Forward domain list manager.
+    DdnsDomainListMgrPtr forward_mgr_;
+
+    /// @brief Reverse domain list manager.
+    DdnsDomainListMgrPtr reverse_mgr_;
 };
 
+/// @brief Defines a pointer for DdnsDomain instances.
+typedef boost::shared_ptr<DdnsDomainListMgr> DdnsDomainListMgrPtr;
+
+
 /// @brief Pointer to a configuration context.
 typedef boost::shared_ptr<D2CfgContext> D2CfgContextPtr;
 
@@ -67,7 +90,6 @@ typedef boost::shared_ptr<D2CfgContext> D2CfgContextPtr;
 /// configuration.  This includes services for parsing sets of configuration
 /// values, storing the parsed information in its converted form,
 /// and retrieving the information on demand.
-/// @TODO add in D2 specific parsing
 class D2CfgMgr : public DCfgMgrBase {
 public:
     /// @brief Constructor
@@ -85,13 +107,43 @@ public:
         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& fdqn, 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& fdqn, DdnsDomainPtr &domain);
+
 protected:
     /// @brief Given an element_id returns an instance of the appropriate
     /// parser.
-    /// @TODO The initial implementation simply returns a DebugParser for any
-    /// element_id value.  This is sufficient to integrate configuration
-    /// management into D2. Specific parsers will be added as the DHCP-DDNS
-    /// specific configuration is constructed.
+    ///
+    /// 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.

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

@@ -0,0 +1,465 @@
+// 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 {
+
+// *********************** DnsServerInfo  *************************
+
+const uint32_t DnsServerInfo::standard_dns_port = 53;
+
+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  *************************
+
+DdnsDomainListMgr::DdnsDomainListMgr(const std::string& name) : name_(name),
+    domains_(new DdnsDomainStorage()) {
+}
+
+
+DdnsDomainListMgr::~DdnsDomainListMgr () {
+}
+
+void 
+DdnsDomainListMgr::setDomains(DdnsDomainStoragePtr domains) {
+    if (!domains) {
+        isc_throw(D2CfgError, 
+                  "DdnsDomainListMgr::setDomains: Domain list may not be null");
+    }
+
+    domains_ = domains;
+
+    // Iterate over the domain list lookfing 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 match. 
+    DdnsDomainPtrPair domain_pair;
+    BOOST_FOREACH(domain_pair, (*domains_)) {
+        DdnsDomainPtr domain = domain_pair.second;
+        const std::string& domain_name = domain->getName();
+
+        if (domain_name == "*") {
+            wildcard_domain_ = domain;
+            break;
+        }
+    }
+}
+
+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);
+        DdnsDomainStorage::iterator gotit = domains_->find(match_name);
+        if (gotit != domains_->end()) {
+            domain = gotit->second;
+            break;
+        }
+
+        start_pos = match_name.find_first_of(".");
+        if (start_pos != std::string::npos) {
+            start_pos++;
+        }
+    }
+
+    if (!domain) {
+        // 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_;
+        } else {
+            LOG_WARN(dctl_logger, DHCP_DDNS_NO_MATCH).arg(fqdn);
+            return (false);
+        }
+    } 
+
+    return (true);
+}
+
+// *************************** PARSERS ***********************************
+
+// *********************** DnsServerInfoParser  *************************
+
+DnsServerInfoParser::DnsServerInfoParser(const std::string& entry_name,
+    DnsServerInfoStoragePtr servers) 
+    : entry_name_(entry_name), servers_(servers), local_scalars_() {
+    if (!servers_) {
+        isc_throw(D2CfgError, "DdnsServerInfoParser 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 paresed 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 cannot specify both hostname and ip_address.
+    if ((hostname != "") && (ip_address != "")) {
+        isc_throw(D2CfgError,
+          "Dns Server cannot specify both hostname and static IP address");
+    } 
+
+    // The configuration must specify one or the other.
+    if ((hostname == "") && (ip_address == "")) {
+        isc_throw(D2CfgError,
+          "Dns Server must specify either hostname or static IP address");
+    }
+
+    DnsServerInfo* serverInfo = NULL;
+    if (hostname != "") {
+        // When  hostname is specified, create a valid, blank IOAddress and
+        // then create the DnsServerInfo.
+        isc::asiolink::IOAddress io_addr(DnsServerInfo::empty_ip_str);
+        serverInfo = 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 = 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(DnsServerInfoPtr(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,
+                                   DdnsDomainStoragePtr domains) 
+    : entry_name_(entry_name), domains_(domains), 
+    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 == "") {
+        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 and for now, unused.  It is intended to be
+    // used as the name of the TSIG key this domain should use.
+    local_scalars_.getParam("key_name", key_name, DCfgContextBase::optional_);
+
+    // 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,
+                                       DdnsDomainStoragePtr domains)
+    :list_name_(list_name), domains_(domains), 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_));
+        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) 
+    : entry_name_(entry_name), mgr_(mgr), 
+    local_domains_(new DdnsDomainStorage()), 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_);
+    } 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

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

@@ -0,0 +1,661 @@
+// 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 <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>
+
+#ifndef D2_CONFIG_H
+#define D2_CONFIG_H
+
+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 and
+/// two managed lists of domains: one list for forward domains and one list for
+/// reverse domains.
+///
+/// Each managed 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.  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 th server.
+
+/// @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 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;
+
+    /// @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,
+                  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.
+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.
+    /// (@TODO TSIG is not yet functional).
+    /// @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.
+    /// @TODO TSIG is not yet functional).
+    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 storage container for DdnsDomain pointers.
+typedef std::map<std::string, DdnsDomainPtr> DdnsDomainStorage;
+
+/// @brief Defines a pointer to DdnsDomain storage containers.
+typedef boost::shared_ptr<DdnsDomainStorage> DdnsDomainStoragePtr;
+
+/// @brief Defines a domain and domain key pair for iterating.
+typedef std::pair<std::string, DdnsDomainPtr> DdnsDomainPtrPair;
+
+
+/// @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 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
+    /// the 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.
+    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 DdnsDomainStoragePtr &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(DdnsDomainStoragePtr domains);
+
+private:
+    /// @brief An arbitrary label assigned to this manager.
+    std::string name_;
+
+    /// @brief Storage for the list of domains.
+    DdnsDomainStoragePtr 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 simple
+/// configuration items (e.g. ints, bools, strings...) without explicitly adding
+/// storage for each individual type needed by the parser.
+class DScalarContext : public DCfgContextBase {
+public:
+
+    /// @brief Constructor
+    DScalarContext() {
+    };
+
+    /// @brief Destructor
+    virtual ~DScalarContext() {
+    }
+
+    /// @brief Creates a clone of a DStubContext.
+    ///
+    /// @return returns a raw pointer to the new clone.
+    virtual DScalarContext* clone() {
+        return (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 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
+    /// should commit the newly created DdnsDomain instance.
+    DdnsDomainParser(const std::string& entry_name,
+                     DdnsDomainStoragePtr domains);
+
+    /// @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 server_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.
+    DdnsDomainStoragePtr domains_;
+
+    /// @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
+    /// should commit the newly created DdnsDomain instance.
+    DdnsDomainListParser(const std::string& list_name,
+                            DdnsDomainStoragePtr domains_);
+
+    /// @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.
+    DdnsDomainStoragePtr domains_;
+
+    /// @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.
+    /// @throw throws D2CfgError if mgr pointer is empty.
+    DdnsDomainListMgrParser(const std::string& entry_name,
+                     DdnsDomainListMgrPtr& mgr);
+
+    /// @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 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.
+    DdnsDomainStoragePtr 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

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

@@ -66,7 +66,7 @@ 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
 A debug message which indicates that configuration being parsed includes
@@ -119,6 +119,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 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.

+ 44 - 2
src/bin/d2/d_cfg_mgr.cc

@@ -39,6 +39,9 @@ namespace isc {
 namespace d2 {
 
 // *********************** DCfgContextBase  *************************
+
+const bool DCfgContextBase::optional_ = true;
+
 DCfgContextBase::DCfgContextBase():
         boolean_values_(new BooleanStorage()),
         uint32_values_(new Uint32Storage()),
@@ -51,6 +54,45 @@ DCfgContextBase::DCfgContextBase(const DCfgContextBase& rhs):
         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() {
 }
 
@@ -140,7 +182,7 @@ DCfgMgrBase::parseConfig(isc::data::ConstElementPtr config_set) {
         LOG_INFO(dctl_logger, DCTL_CONFIG_COMPLETE).arg("");
         answer = isc::config::createAnswer(0, "Configuration committed.");
 
-    } catch (const DCfgMgrBaseError& ex) {
+    } 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() +
@@ -148,6 +190,7 @@ DCfgMgrBase::parseConfig(isc::data::ConstElementPtr config_set) {
 
         // An error occurred, so make sure that we restore original context.
         context_ = original_context;
+        return (answer);
     }
 
     return (answer);
@@ -157,7 +200,6 @@ 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));
     ParserPtr parser = createConfigParser(element_id);
     if (!parser) {
         isc_throw(DCfgMgrBaseError, std::string("Could not create parser"));

+ 21 - 12
src/bin/d2/d_cfg_mgr.h

@@ -54,6 +54,9 @@ public:
 ///
 class DCfgContextBase {
 public:
+    /// @brief Indicator that a configuration parameter is optional.
+    static const bool optional_;
+
     /// @brief Constructor
     DCfgContextBase();
 
@@ -66,11 +69,13 @@ public:
     /// @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.
-    void getParam(const std::string& name, bool& value) {
-        value = boolean_values_->getParam(name);
-    }
+    /// 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.
@@ -78,11 +83,13 @@ public:
     /// @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.
-    void getParam(const std::string& name, uint32_t& value) {
-        value = uint32_values_->getParam(name);
-    }
+    /// 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.
@@ -90,11 +97,13 @@ public:
     /// @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.
-    void getParam(const std::string& name, std::string& value) {
-        value = string_values_->getParam(name);
-    }
+    /// 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.

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

@@ -1,21 +1,173 @@
+{ 
+"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": "foward_ddns",
+        "item_type": "map",
+        "item_optional": false,
+         "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": false,
+         "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": [
+            ]
+        }
     ]
   }
 }

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

@@ -57,6 +57,7 @@ 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 += d_test_stubs.cc d_test_stubs.h
 d2_unittests_SOURCES += d2_unittests.cc
@@ -64,6 +65,7 @@ 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
 nodist_d2_unittests_SOURCES = ../d2_messages.h ../d2_messages.cc
 
 d2_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)

+ 891 - 0
src/bin/d2/tests/d2_cfg_mgr_unittests.cc

@@ -0,0 +1,891 @@
+// 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/module_spec.h>
+#include <d2/d2_config.h>
+#include <d2/d2_cfg_mgr.h>
+#include <d_test_stubs.h>
+
+#include <boost/foreach.hpp>
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::d2;
+
+namespace {
+
+/// @brief Test fixture class for testing D2CfgMgr class.
+/// It maintains an member instance of D2CfgMgr and provides methods for
+/// converting JSON strings to configuration element sets, checking parse
+/// results, and accessing the configuration context.
+class D2CfgMgrTest : public ConfigParseTest {
+public:
+
+    /// @brief Constructor
+    D2CfgMgrTest():cfg_mgr_(new D2CfgMgr) {
+    }
+
+    /// @brief Destructor
+    ~D2CfgMgrTest() {
+    }
+
+    /// @brief Configuration manager instance.
+    D2CfgMgrPtr cfg_mgr_;
+};
+
+/// @brief Tests that the spec file is valid.
+/// Verifies that the BIND10 DHCP-DDNS configuration specification file
+//  is valid.
+TEST(D2SpecTest, basicSpecTest) {
+    ASSERT_NO_THROW(isc::config::moduleSpecFromFile("../dhcp-ddns.spec"));
+}
+
+/// @brief Convenience function which compares the contents of the given
+/// DnsServerInfo against the given set of values.
+///
+/// It is structured in such a way that each value is checked, and output
+/// is generate for all that do not match.
+///
+/// @param server is a pointer to the server to check against.
+/// @param hostname is the value to compare against server's hostname_.
+/// @param ip_address is the string value to compare against server's
+/// ip_address_.
+/// @param port is the value to compare against server's port.
+///
+/// @return returns true if there is a match across the board, otherwise it
+/// returns false.
+bool checkServer(DnsServerInfoPtr server, const char* hostname,
+                 const char *ip_address, uint32_t port)
+{
+    // Return value, assume its a match.
+    bool result = true;
+
+    if (!server)
+    {
+        EXPECT_TRUE(server);
+        return false;
+    }
+
+    // Check hostname.
+    if (server->getHostname() != hostname) {
+        EXPECT_EQ(server->getHostname(),hostname);
+        result = false;
+    }
+
+    // Check IP address.
+    if (server->getIpAddress().toText() != ip_address) {
+        EXPECT_EQ(server->getIpAddress().toText(), ip_address);
+        result = false;
+    }
+
+    // Check port.
+    if (server->getPort() !=  port) {
+        EXPECT_EQ (server->getPort(), port);
+        result = false;
+    }
+
+    return (result);
+}
+
+/// @brief Test fixture class for testing DnsServerInfo parsing.
+class DnsServerInfoTest : public ConfigParseTest {
+public:
+
+    /// @brief Constructor
+    DnsServerInfoTest() {
+        reset();
+    }
+
+    /// @brief Destructor
+    ~DnsServerInfoTest() {
+    }
+
+    /// @brief Wipe out the current storage and parser and replace
+    /// them with new ones.
+    void reset() {
+        servers_.reset(new DnsServerInfoStorage());
+        parser_.reset(new DnsServerInfoParser("test", servers_));
+    }
+
+    /// @brief Storage for "committing" servers.
+    DnsServerInfoStoragePtr servers_;
+
+    /// @brief Pointer to the current parser instance.
+    isc::dhcp::ParserPtr parser_;
+};
+
+/// @brief Tests the enforcement of data validation when parsing DnsServerInfos.
+/// It verifies that:
+/// 1. Specifying both a hostname and an ip address is not allowed.
+/// 2. Specifying both blank a hostname and blank ip address is not allowed.
+/// 3. Specifying a negative port number is not allowed.
+TEST_F(DnsServerInfoTest, invalidEntyTests) {
+    // Create a config in which both host and ip address are supplied.
+    // Verify that it builds without throwing but commit fails.
+    std::string config = "{ \"hostname\": \"pegasus.tmark\", "
+                         "  \"ip_address\": \"127.0.0.1\" } ";
+    ASSERT_NO_THROW(fromJSON(config));
+    EXPECT_NO_THROW(parser_->build(config_set_));
+    EXPECT_THROW(parser_->commit(), D2CfgError);
+
+    // Neither host nor ip address supplied
+    // Verify that it builds without throwing but commit fails.
+    config = "{ \"hostname\": \"\", "
+             "  \"ip_address\": \"\" } ";
+    ASSERT_NO_THROW(fromJSON(config));
+    EXPECT_NO_THROW(parser_->build(config_set_));
+    EXPECT_THROW(parser_->commit(), D2CfgError);
+
+    // Create a config with a negative port number.
+    // Verify that build fails.
+    config = "{ \"ip_address\": \"192.168.5.6\" ,"
+             "  \"port\": -100 }";
+    ASSERT_NO_THROW(fromJSON(config));
+    EXPECT_THROW (parser_->build(config_set_), isc::BadValue);
+}
+
+/// @brief Verifies that DnsServerInfo parsing creates a proper DnsServerInfo
+/// when given a valid combination of entries.
+/// It verifies that:
+/// 1. A DnsServerInfo entry is correctly made, when given only a hostname.
+/// 2. A DnsServerInfo entry is correctly made, when given ip address and port.
+/// 3. A DnsServerInfo entry is correctly made, when given only an ip address.
+TEST_F(DnsServerInfoTest, validEntyTests) {
+    // Valid entries for dynamic host
+    std::string config = "{ \"hostname\": \"pegasus.tmark\" }";
+    ASSERT_NO_THROW(fromJSON(config));
+
+    // Verify that it builds and commits without throwing.
+    ASSERT_NO_THROW(parser_->build(config_set_));
+    ASSERT_NO_THROW(parser_->commit());
+
+    // Verify the correct number of servers are present
+    int count =  servers_->size();
+    EXPECT_EQ(count, 1);
+
+    // Verify the server exists and has the correct values.
+    DnsServerInfoPtr server = (*servers_)[0];
+    EXPECT_TRUE(checkServer(server, "pegasus.tmark",
+                            DnsServerInfo::empty_ip_str,
+                            DnsServerInfo::standard_dns_port));
+
+    // Start over for a new test.
+    reset();
+
+    // Valid entries for static ip
+    config = " { \"ip_address\": \"127.0.0.1\" , "
+             "  \"port\": 100 }";
+    ASSERT_NO_THROW(fromJSON(config));
+
+    // Verify that it builds and commits without throwing.
+    ASSERT_NO_THROW(parser_->build(config_set_));
+    ASSERT_NO_THROW(parser_->commit());
+
+    // Verify the correct number of servers are present
+    count =  servers_->size();
+    EXPECT_EQ(count, 1);
+
+    // Verify the server exists and has the correct values.
+    server = (*servers_)[0];
+    EXPECT_TRUE(checkServer(server, "", "127.0.0.1", 100));
+
+    // Start over for a new test.
+    reset();
+
+    // Valid entries for static ip, no port
+    config = " { \"ip_address\": \"192.168.2.5\" }";
+    ASSERT_NO_THROW(fromJSON(config));
+
+    // Verify that it builds and commits without throwing.
+    ASSERT_NO_THROW(parser_->build(config_set_));
+    ASSERT_NO_THROW(parser_->commit());
+
+    // Verify the correct number of servers are present
+    count =  servers_->size();
+    EXPECT_EQ(count, 1);
+
+    // Verify the server exists and has the correct values.
+    server = (*servers_)[0];
+    EXPECT_TRUE(checkServer(server, "", "192.168.2.5",
+                            DnsServerInfo::standard_dns_port));
+}
+
+/// @brief Verifies that attempting to parse an invalid list of DnsServerInfo
+/// entries is detected.
+TEST_F(ConfigParseTest, invalidServerList) {
+    // Construct a list of servers with an invalid server entry.
+    std::string config = "[ { \"hostname\": \"one.tmark\" }, "
+                        "{ \"hostname\": \"\" }, "
+                        "{ \"hostname\": \"three.tmark\" } ]";
+    ASSERT_NO_THROW(fromJSON(config));
+
+    // Create the server storage and list parser.
+    DnsServerInfoStoragePtr servers(new DnsServerInfoStorage());
+    isc::dhcp::ParserPtr parser;
+    ASSERT_NO_THROW(parser.reset(new DnsServerInfoListParser("test", servers)));
+
+    // Verify that the list builds without errors.
+    ASSERT_NO_THROW(parser->build(config_set_));
+
+    // Verify that the list commit fails.
+    EXPECT_THROW(parser->commit(), D2CfgError);
+}
+
+/// @brief Verifies that a list of DnsServerInfo entries parses correctly given
+/// a valid configuration.
+TEST_F(ConfigParseTest, validServerList) {
+    // Create a valid list of servers.
+    std::string config = "[ { \"hostname\": \"one.tmark\" }, "
+                        "{ \"hostname\": \"two.tmark\" }, "
+                        "{ \"hostname\": \"three.tmark\" } ]";
+    ASSERT_NO_THROW(fromJSON(config));
+
+    // Create the server storage and list parser.
+    DnsServerInfoStoragePtr servers(new DnsServerInfoStorage());
+    isc::dhcp::ParserPtr parser;
+    ASSERT_NO_THROW(parser.reset(new DnsServerInfoListParser("test", servers)));
+
+    // Verfiy that the list builds and commits without error.
+    ASSERT_NO_THROW(parser->build(config_set_));
+    ASSERT_NO_THROW(parser->commit());
+
+    // Verify that the server storage contains the correct number of servers.
+    int count =  servers->size();
+    EXPECT_EQ(count, 3);
+
+    // Verify the first server exists and has the correct values.
+    DnsServerInfoPtr server = (*servers)[0];
+    EXPECT_TRUE(checkServer(server, "one.tmark", DnsServerInfo::empty_ip_str,
+                            DnsServerInfo::standard_dns_port));
+
+    // Verify the second server exists and has the correct values.
+    server = (*servers)[1];
+    EXPECT_TRUE(checkServer(server, "two.tmark", DnsServerInfo::empty_ip_str,
+                            DnsServerInfo::standard_dns_port));
+
+    // Verify the third server exists and has the correct values.
+    server = (*servers)[2];
+    EXPECT_TRUE(checkServer(server, "three.tmark", DnsServerInfo::empty_ip_str,
+                            DnsServerInfo::standard_dns_port));
+}
+
+/// @brief Tests the enforcement of data validation when parsing DdnsDomains.
+/// It verifies that:
+/// 1. Domain storage cannot be null when constructing a DdnsDomainParser.
+/// 2. The name entry is not optional.
+/// 3. The server list man not be empty.
+/// 4. That a mal-formed server entry is detected.
+TEST_F(ConfigParseTest, invalidDdnsDomainEntry) {
+    // Verify that attempting to construct the parser with null storage fails.
+    DdnsDomainStoragePtr domains;
+    ASSERT_THROW(new DdnsDomainParser("test", domains), D2CfgError);
+
+    // Create domain storage for the parser, and then instantiate the
+    // parser.
+    domains.reset(new DdnsDomainStorage());
+    DdnsDomainParser *parser = NULL;
+    ASSERT_NO_THROW(parser = new DdnsDomainParser("test", domains));
+
+    // Create a domain configuration without a name
+    std::string config = "{  \"key_name\": \"d2_key.tmark.org\" , "
+                         "  \"dns_servers\" : [ "
+                         "  {  \"ip_address\": \"127.0.0.1\" , "
+                         "    \"port\": 100 },"
+                         "  { \"ip_address\": \"127.0.0.2\" , "
+                         "    \"port\": 200 },"
+                         "  {  \"ip_address\": \"127.0.0.3\" , "
+                         "    \"port\": 300 } ] } ";
+    ASSERT_NO_THROW(fromJSON(config));
+
+    // Verify that the domain configuration builds but commit fails.
+    ASSERT_NO_THROW(parser->build(config_set_));
+    ASSERT_THROW(parser->commit(), isc::dhcp::DhcpConfigError);
+
+    // Create a domain configuration with an empty server list.
+    config = "{ \"name\": \"tmark.org\" , "
+             "  \"key_name\": \"d2_key.tmark.org\" , "
+             "  \"dns_servers\" : [ "
+             "   ] } ";
+    ASSERT_NO_THROW(fromJSON(config));
+
+    // Verify that the domain configuration build fails.
+    ASSERT_THROW(parser->build(config_set_), D2CfgError);
+
+    // Create a domain configuration with a mal-formed server entry.
+    config = "{ \"name\": \"tmark.org\" , "
+             "  \"key_name\": \"d2_key.tmark.org\" , "
+             "  \"dns_servers\" : [ "
+             "  {  \"ip_address\": \"127.0.0.3\" , "
+             "    \"port\": -1 } ] } ";
+    ASSERT_NO_THROW(fromJSON(config));
+
+    // Verify that the domain configuration build fails.
+    ASSERT_THROW(parser->build(config_set_), isc::BadValue);
+}
+
+
+/// @brief Verifies the basics of parsing DdnsDomains.
+/// It verifies that:
+/// 1. Valid construction of DdnsDomainParser functions.
+/// 2. Given a valid, configuration entry, DdnsDomainParser parses
+/// correctly.
+/// (It indirectly verifies the operation of DdnsDomainStorage).
+TEST_F(ConfigParseTest, ddnsDomainParsing) {
+    // Create a valid domain configuration entry containing three valid
+    // servers.
+    std::string config =
+                        "{ \"name\": \"tmark.org\" , "
+                        "  \"key_name\": \"d2_key.tmark.org\" , "
+                        "  \"dns_servers\" : [ "
+                        "  {  \"ip_address\": \"127.0.0.1\" , "
+                        "    \"port\": 100 },"
+                        "  { \"ip_address\": \"127.0.0.2\" , "
+                        "    \"port\": 200 },"
+                        "  {  \"ip_address\": \"127.0.0.3\" , "
+                        "    \"port\": 300 } ] } ";
+    ASSERT_NO_THROW(fromJSON(config));
+
+    // Create domain storage for the parser, and then instantiate the
+    // parser.  This verifies that valid parser construction.
+    DdnsDomainStoragePtr domains(new DdnsDomainStorage());
+    DdnsDomainParser *parser = NULL;
+    ASSERT_NO_THROW(parser = new DdnsDomainParser("test", domains));
+
+    // Verify that the domain configuration builds and commits without error.
+    ASSERT_NO_THROW(parser->build(config_set_));
+    ASSERT_NO_THROW(parser->commit());
+
+    // Verify that the domain storage contains the correct number of domains.
+    int count =  domains->size();
+    EXPECT_EQ(count, 1);
+
+    // Verify that the expected domain exists and can be retrieved from
+    // the storage.
+    DdnsDomainStorage::iterator gotit = domains->find("tmark.org");
+    ASSERT_TRUE(gotit != domains->end());
+    DdnsDomainPtr& domain = gotit->second;
+
+    // Verify the name and key_name values.
+    EXPECT_EQ(domain->getName(), "tmark.org");
+    EXPECT_EQ(domain->getKeyName(), "d2_key.tmark.org");
+
+    // Verify that the server list exists and contains the correct number of
+    // servers.
+    const DnsServerInfoStoragePtr& servers = domain->getServers();
+    EXPECT_TRUE(servers);
+    count =  servers->size();
+    EXPECT_EQ(count, 3);
+
+    // Fetch each server and verify its contents.
+    DnsServerInfoPtr server = (*servers)[0];
+    EXPECT_TRUE(server);
+
+    EXPECT_TRUE(checkServer(server, "", "127.0.0.1", 100));
+
+    server = (*servers)[1];
+    EXPECT_TRUE(server);
+
+    EXPECT_TRUE(checkServer(server, "", "127.0.0.2", 200));
+
+    server = (*servers)[2];
+    EXPECT_TRUE(server);
+
+    EXPECT_TRUE(checkServer(server, "", "127.0.0.3", 300));
+}
+
+/// @brief Tests the fundamentals of parsing DdnsDomain lists.
+/// This test verifies that given a valid domain list configuration
+/// it will accurately parse and populate each domain in the list.
+TEST_F(ConfigParseTest, DdnsDomainListParsing) {
+    // Create a valid domain list configuration, with two domains
+    // that have three servers each.
+    std::string config =
+                        "[ "
+                        "{ \"name\": \"tmark.org\" , "
+                        "  \"key_name\": \"d2_key.tmark.org\" , "
+                        "  \"dns_servers\" : [ "
+                        "  { \"ip_address\": \"127.0.0.1\" , "
+                        "    \"port\": 100 },"
+                        "  { \"ip_address\": \"127.0.0.2\" , "
+                        "    \"port\": 200 },"
+                        "  { \"ip_address\": \"127.0.0.3\" , "
+                        "    \"port\": 300 } ] } "
+                        ", "
+                        "{ \"name\": \"billcat.net\" , "
+                        "  \"key_name\": \"d2_key.billcat.net\" , "
+                        "  \"dns_servers\" : [ "
+                        "  { \"ip_address\": \"127.0.0.4\" , "
+                        "    \"port\": 400 },"
+                        "  { \"ip_address\": \"127.0.0.5\" , "
+                        "    \"port\": 500 },"
+                        "  { \"ip_address\": \"127.0.0.6\" , "
+                        "    \"port\": 600 } ] } "
+                        "] ";
+
+    ASSERT_NO_THROW(fromJSON(config));
+
+    // Create domain storage for the parser, and then instantiate the
+    // parser.
+    DdnsDomainStoragePtr domains(new DdnsDomainStorage());
+    DdnsDomainListParser *parser = NULL;
+    ASSERT_NO_THROW(parser = new DdnsDomainListParser("test", domains));
+
+    // Verify that the domain configuration builds and commits without error.
+    ASSERT_NO_THROW(parser->build(config_set_));
+    ASSERT_NO_THROW(parser->commit());
+
+    // Verify that the domain storage contains the correct number of domains.
+    int count =  domains->size();
+    EXPECT_EQ(count, 2);
+
+    // Verify that the first domain exists and can be retrieved.
+    DdnsDomainStorage::iterator gotit = domains->find("tmark.org");
+    ASSERT_TRUE(gotit != domains->end());
+    DdnsDomainPtr& domain = gotit->second;
+
+    // Verify the name and key_name values of the first domain.
+    EXPECT_EQ(domain->getName(), "tmark.org");
+    EXPECT_EQ(domain->getKeyName(), "d2_key.tmark.org");
+
+    // Verify the each of the first domain's servers
+    DnsServerInfoStoragePtr servers = domain->getServers();
+    EXPECT_TRUE(servers);
+    count =  servers->size();
+    EXPECT_EQ(count, 3);
+
+    DnsServerInfoPtr server = (*servers)[0];
+    EXPECT_TRUE(server);
+    EXPECT_TRUE(checkServer(server, "", "127.0.0.1", 100));
+
+    server = (*servers)[1];
+    EXPECT_TRUE(server);
+    EXPECT_TRUE(checkServer(server, "", "127.0.0.2", 200));
+
+    server = (*servers)[2];
+    EXPECT_TRUE(server);
+    EXPECT_TRUE(checkServer(server, "", "127.0.0.3", 300));
+
+    // Verify second domain
+    gotit = domains->find("billcat.net");
+    ASSERT_TRUE(gotit != domains->end());
+    domain = gotit->second;
+
+    // Verify the name and key_name values of the second domain.
+    EXPECT_EQ(domain->getName(), "billcat.net");
+    EXPECT_EQ(domain->getKeyName(), "d2_key.billcat.net");
+
+    // Verify the each of second domain's servers
+    servers = domain->getServers();
+    EXPECT_TRUE(servers);
+    count =  servers->size();
+    EXPECT_EQ(count, 3);
+
+    server = (*servers)[0];
+    EXPECT_TRUE(server);
+    EXPECT_TRUE(checkServer(server, "", "127.0.0.4", 400));
+
+    server = (*servers)[1];
+    EXPECT_TRUE(server);
+    EXPECT_TRUE(checkServer(server, "", "127.0.0.5", 500));
+
+    server = (*servers)[2];
+    EXPECT_TRUE(server);
+    EXPECT_TRUE(checkServer(server, "", "127.0.0.6", 600));
+}
+
+/// @brief Tests that a domain list configuration cannot contain duplicates.
+TEST_F(ConfigParseTest, duplicateDomainTest) {
+    // Create a domain list configuration that contains two domains with
+    // the same name.
+    std::string config =
+                        "[ "
+                        "{ \"name\": \"tmark.org\" , "
+                        "  \"dns_servers\" : [ "
+                        "  { \"ip_address\": \"127.0.0.3\" , "
+                        "    \"port\": 300 } ] } "
+                        ", "
+                        "{ \"name\": \"tmark.org\" , "
+                        "  \"dns_servers\" : [ "
+                        "  { \"ip_address\": \"127.0.0.3\" , "
+                        "    \"port\": 300 } ] } "
+                        "] ";
+
+    ASSERT_NO_THROW(fromJSON(config));
+
+    // Create the domain storage pointer and the parser.
+    DdnsDomainStoragePtr domains(new DdnsDomainStorage());
+    DdnsDomainListParser *parser = NULL;
+    ASSERT_NO_THROW(parser = new DdnsDomainListParser("test", domains));
+
+    // Verify that the parse build succeeds but the commit fails.
+    ASSERT_NO_THROW(parser->build(config_set_));
+    ASSERT_THROW(parser->commit(), D2CfgError);
+}
+
+/// @brief Tests construction of D2CfgMgr
+/// This test verifies that a D2CfgMgr constructs properly.
+TEST(D2CfgMgr, construction) {
+    D2CfgMgr *cfg_mgr = NULL;
+
+    // Verify that configuration manager constructions without error.
+    ASSERT_NO_THROW(cfg_mgr=new D2CfgMgr());
+
+    // Verify that the context can be retrieved and is not null.
+    D2CfgContextPtr context;
+    ASSERT_NO_THROW(context = cfg_mgr->getD2CfgContext());
+    EXPECT_TRUE(context);
+
+    // Verify that the forward manager can be retrieved and is not null.
+    EXPECT_TRUE(context->getForwardMgr());
+
+    // Verify that the reverse manager can be retrieved and is not null.
+    EXPECT_TRUE(context->getReverseMgr());
+
+    // Verify that the manager can be destructed without error.
+    EXPECT_NO_THROW(delete cfg_mgr);
+}
+
+/// @brief Tests the parsing of a complete, valid DHCP-DDNS configuration.
+/// This tests passes the configuration into an instance of D2CfgMgr just
+/// as it would be done by d2_process in response to a configuration update
+/// event.
+TEST_F(D2CfgMgrTest, fullConfigTest) {
+    // Create a configuration with all of application level parameters, plus
+    // both the forward and reverse ddns managers.  Both managers have two
+    // domains with three servers per domain.
+    std::string config = "{ "
+                        "\"interface\" : \"eth1\" , "
+                        "\"ip_address\" : \"192.168.1.33\" , "
+                        "\"port\" : 88 , "
+                        "\"forward_ddns\" : {"
+                        "\"ddns_domains\": [ "
+                        "{ \"name\": \"tmark.org\" , "
+                        "  \"key_name\": \"d2_key.tmark.org\" , "
+                        "  \"dns_servers\" : [ "
+                        "  { \"hostname\": \"one.tmark\" } , "
+                        "  { \"hostname\": \"two.tmark\" } , "
+                        "  { \"hostname\": \"three.tmark\"} "
+                        "  ] } "
+                        ", "
+                        "{ \"name\": \"billcat.net\" , "
+                        "  \"key_name\": \"d2_key.billcat.net\" , "
+                        "  \"dns_servers\" : [ "
+                        "  { \"hostname\": \"four.billcat\" } , "
+                        "  { \"hostname\": \"five.billcat\" } , "
+                        "  { \"hostname\": \"six.billcat\" } "
+                        "  ] } "
+                        "] },"
+                        "\"reverse_ddns\" : {"
+                        "\"ddns_domains\": [ "
+                        "{ \"name\": \" 0.168.192.in.addr.arpa.\" , "
+                        "  \"key_name\": \"d2_key.tmark.org\" , "
+                        "  \"dns_servers\" : [ "
+                        "  { \"hostname\": \"one.rev\" } , "
+                        "  { \"hostname\": \"two.rev\" } , "
+                        "  { \"hostname\": \"three.rev\" } "
+                        "  ] } "
+                        ", "
+                        "{ \"name\": \" 0.247.106.in.addr.arpa.\" , "
+                        "  \"key_name\": \"d2_key.billcat.net\" , "
+                        "  \"dns_servers\" : [ "
+                        "  { \"hostname\": \"four.rev\" }, "
+                        "  { \"hostname\": \"five.rev\" } , "
+                        "  { \"hostname\": \"six.rev\" } "
+                        "  ] } "
+                        "] } }";
+    ASSERT_NO_THROW(fromJSON(config));
+
+    // Verify that we can parse the configuration.
+    answer_ = cfg_mgr_->parseConfig(config_set_);
+    EXPECT_TRUE(checkAnswer(0));
+
+    // Verify that the D2 context can be retrieved and is not null.
+    D2CfgContextPtr context;
+    ASSERT_NO_THROW(context = cfg_mgr_->getD2CfgContext());
+
+    // Verify that the application level scalars have the proper values.
+    std::string interface;
+    EXPECT_NO_THROW (context->getParam("interface", interface));
+    EXPECT_EQ(interface, "eth1");
+
+    std::string ip_address;
+    EXPECT_NO_THROW (context->getParam("ip_address", ip_address));
+    EXPECT_EQ(ip_address, "192.168.1.33");
+
+    uint32_t port = 0;
+    EXPECT_NO_THROW (context->getParam("port", port));
+    EXPECT_EQ(port, 88);
+
+    // Verify that the forward manager can be retrieved.
+    DdnsDomainListMgrPtr mgr = context->getForwardMgr();
+    ASSERT_TRUE(mgr);
+
+    // Verify that the forward manager has the correct number of domains.
+    DdnsDomainStoragePtr domains = mgr->getDomains();
+    ASSERT_TRUE(domains);
+    int count =  domains->size();
+    EXPECT_EQ(count, 2);
+
+    // Verify that the server count in each of the forward manager domains.
+    // NOTE that since prior tests have validated server parsing, we are are
+    // assuming that the servers did in fact parse correctly if the correct
+    // number of them are there.
+    DdnsDomainPtrPair domain_pair;
+    BOOST_FOREACH(domain_pair, (*domains)) {
+        DdnsDomainPtr domain = domain_pair.second;
+        DnsServerInfoStoragePtr servers = domain->getServers();
+        count = servers->size();
+        EXPECT_TRUE(servers);
+        EXPECT_EQ(count, 3);
+    }
+
+    // Verify that the reverse manager can be retrieved.
+    mgr = context->getReverseMgr();
+    ASSERT_TRUE(mgr);
+
+    // Verify that the reverse manager has the correct number of domains.
+    domains = mgr->getDomains();
+    count =  domains->size();
+    EXPECT_EQ(count, 2);
+
+    // Verify that the server count in each of the reverse manager domains.
+    // NOTE that since prior tests have validated server parsing, we are are
+    // assuming that the servers did in fact parse correctly if the correct
+    // number of them are there.
+    BOOST_FOREACH(domain_pair, (*domains)) {
+        DdnsDomainPtr domain = domain_pair.second;
+        DnsServerInfoStoragePtr servers = domain->getServers();
+        count = servers->size();
+        EXPECT_TRUE(servers);
+        EXPECT_EQ(count, 3);
+    }
+}
+
+/// @brief Tests the basics of the D2CfgMgr FQDN-domain matching
+/// This test uses a valid configuration to exercise the D2CfgMgr
+/// forward FQDN-to-domain matching.
+/// It verifies that:
+/// 1. Given an FQDN which exactly matches a domain's name, that domain is
+/// returned as match.
+/// 2. Given a FQDN for sub-domain in the list, returns the proper match.
+/// 3. Given a FQDN that matches no domain name, returns the wild card domain
+/// as a match.
+TEST_F(D2CfgMgrTest, forwardMatchTest) {
+    // Create  configuration with one domain, one sub domain, and the wild
+    // card.
+    std::string config = "{ "
+                        "\"interface\" : \"eth1\" , "
+                        "\"ip_address\" : \"192.168.1.33\" , "
+                        "\"port\" : 88 , "
+                        "\"forward_ddns\" : {"
+                        "\"ddns_domains\": [ "
+                        "{ \"name\": \"tmark.org\" , "
+                        "  \"dns_servers\" : [ "
+                        "  { \"ip_address\": \"127.0.0.1\" } "
+                        "  ] } "
+                        ", "
+                        "{ \"name\": \"one.tmark.org\" , "
+                        "  \"dns_servers\" : [ "
+                        "  { \"ip_address\": \"127.0.0.2\" } "
+                        "  ] } "
+                        ", "
+                        "{ \"name\": \"*\" , "
+                        "  \"dns_servers\" : [ "
+                        "  { \"hostname\": \"global.net\" } "
+                        "  ] } "
+                        "] } }";
+
+    ASSERT_NO_THROW(fromJSON(config));
+    // Verify that we can parse the configuration.
+    answer_ = cfg_mgr_->parseConfig(config_set_);
+    ASSERT_TRUE(checkAnswer(0));
+
+    // Verify that the D2 context can be retrieved and is not null.
+    D2CfgContextPtr context;
+    ASSERT_NO_THROW(context = cfg_mgr_->getD2CfgContext());
+
+    DdnsDomainPtr match;
+    // Verify that an exact match works.
+    EXPECT_TRUE(cfg_mgr_->matchForward("tmark.org", match));
+    EXPECT_EQ("tmark.org", match->getName());
+
+    // Verify that an exact match works.
+    EXPECT_TRUE(cfg_mgr_->matchForward("one.tmark.org", match));
+    EXPECT_EQ("one.tmark.org", match->getName());
+
+    // Verify that a FQDN for sub-domain matches.
+    EXPECT_TRUE(cfg_mgr_->matchForward("blue.tmark.org", match));
+    EXPECT_EQ("tmark.org", match->getName());
+
+    // Verify that a FQDN for sub-domain matches.
+    EXPECT_TRUE(cfg_mgr_->matchForward("red.one.tmark.org", match));
+    EXPECT_EQ("one.tmark.org", match->getName());
+
+    // Verify that an FQDN with no match, returns the wild card domain.
+    EXPECT_TRUE(cfg_mgr_->matchForward("shouldbe.wildcard", match));
+    EXPECT_EQ("*", match->getName());
+
+    // Verify that an attempt to match an empty FQDN throws.
+    ASSERT_THROW(cfg_mgr_->matchForward("", match), D2CfgError);
+}
+
+/// @brief Tests domain matching when there is no wild card domain.
+/// This test verifies that matches are found only for FQDNs that match
+/// some or all of a domain name.  FQDNs without matches should not return
+/// a match.
+TEST_F(D2CfgMgrTest, matchNoWildcard) {
+    // Create a configuration with one domain, one sub-domain, and NO wild card.
+    std::string config = "{ "
+                        "\"interface\" : \"eth1\" , "
+                        "\"ip_address\" : \"192.168.1.33\" , "
+                        "\"port\" : 88 , "
+                        "\"forward_ddns\" : {"
+                        "\"ddns_domains\": [ "
+                        "{ \"name\": \"tmark.org\" , "
+                        "  \"dns_servers\" : [ "
+                        "  { \"ip_address\": \"127.0.0.1\" } "
+                        "  ] } "
+                        ", "
+                        "{ \"name\": \"one.tmark.org\" , "
+                        "  \"dns_servers\" : [ "
+                        "  { \"ip_address\": \"127.0.0.2\" } "
+                        "  ] } "
+                        "] } }";
+
+    ASSERT_NO_THROW(fromJSON(config));
+
+    // Verify that we can parse the configuration.
+    answer_ = cfg_mgr_->parseConfig(config_set_);
+    ASSERT_TRUE(checkAnswer(0));
+
+    // Verify that the D2 context can be retrieved and is not null.
+    D2CfgContextPtr context;
+    ASSERT_NO_THROW(context = cfg_mgr_->getD2CfgContext());
+
+    DdnsDomainPtr match;
+    // Verify that full or partial matches, still match.
+    EXPECT_TRUE(cfg_mgr_->matchForward("tmark.org", match));
+    EXPECT_EQ("tmark.org", match->getName());
+
+    EXPECT_TRUE(cfg_mgr_->matchForward("blue.tmark.org", match));
+    EXPECT_EQ("tmark.org", match->getName());
+
+    EXPECT_TRUE(cfg_mgr_->matchForward("red.one.tmark.org", match));
+    EXPECT_EQ("one.tmark.org", match->getName());
+
+    // Verify that a FQDN with no match, fails to match.
+    EXPECT_FALSE(cfg_mgr_->matchForward("shouldbe.wildcard", match));
+}
+
+/// @brief Tests domain matching when there is ONLY a wild card domain.
+/// This test verifies that any FQDN matches the wild card.
+TEST_F(D2CfgMgrTest, matchAll) {
+    std::string config = "{ "
+                        "\"interface\" : \"eth1\" , "
+                        "\"ip_address\" : \"192.168.1.33\" , "
+                        "\"port\" : 88 , "
+                        "\"forward_ddns\" : {"
+                        "\"ddns_domains\": [ "
+                        "{ \"name\": \"*\" , "
+                        "  \"dns_servers\" : [ "
+                        "  { \"ip_address\": \"127.0.0.1\" } "
+                        "  ] } "
+                        "] } }";
+
+    ASSERT_NO_THROW(fromJSON(config));
+
+    // Verify that we can parse the configuration.
+    answer_ = cfg_mgr_->parseConfig(config_set_);
+    ASSERT_TRUE(checkAnswer(0));
+
+    // Verify that the D2 context can be retrieved and is not null.
+    D2CfgContextPtr context;
+    ASSERT_NO_THROW(context = cfg_mgr_->getD2CfgContext());
+
+    // Verify that wild card domain is returned for any FQDN.
+    DdnsDomainPtr match;
+    EXPECT_TRUE(cfg_mgr_->matchForward("tmark.org", match));
+    EXPECT_EQ("*", match->getName());
+    EXPECT_TRUE(cfg_mgr_->matchForward("shouldbe.wildcard", match));
+    EXPECT_EQ("*", match->getName());
+
+    // Verify that an attempt to match an empty FQDN still throws.
+    ASSERT_THROW(cfg_mgr_->matchReverse("", match), D2CfgError);
+
+}
+
+/// @brief Tests the basics of the D2CfgMgr reverse FQDN-domain matching
+/// This test uses a valid configuration to exercise the D2CfgMgr's
+/// reverse FQDN-to-domain matching.
+/// It verifies that:
+/// 1. Given an FQDN which exactly matches a domain's name, that domain is
+/// returned as match.
+/// 2. Given a FQDN for sub-domain in the list, returns the proper match.
+/// 3. Given a FQDN that matches no domain name, returns the wild card domain
+/// as a match.
+TEST_F(D2CfgMgrTest, matchReverse) {
+    std::string config = "{ "
+                        "\"interface\" : \"eth1\" , "
+                        "\"ip_address\" : \"192.168.1.33\" , "
+                        "\"port\" : 88 , "
+                        "\"reverse_ddns\" : {"
+                        "\"ddns_domains\": [ "
+                        "{ \"name\": \"100.168.192.in-addr.arpa\" , "
+                        "  \"dns_servers\" : [ "
+                        "  { \"ip_address\": \"127.0.0.1\" } "
+                        "  ] }, "
+                        "{ \"name\": \"168.192.in-addr.arpa\" , "
+                        "  \"dns_servers\" : [ "
+                        "  { \"ip_address\": \"127.0.0.1\" } "
+                        "  ] }, "
+                        "{ \"name\": \"*\" , "
+                        "  \"dns_servers\" : [ "
+                        "  { \"ip_address\": \"127.0.0.1\" } "
+                        "  ] } "
+                        "] } }";
+
+    ASSERT_NO_THROW(fromJSON(config));
+
+    // Verify that we can parse the configuration.
+    answer_ = cfg_mgr_->parseConfig(config_set_);
+    ASSERT_TRUE(checkAnswer(0));
+
+    // Verify that the D2 context can be retrieved and is not null.
+    D2CfgContextPtr context;
+    ASSERT_NO_THROW(context = cfg_mgr_->getD2CfgContext());
+
+    DdnsDomainPtr match;
+    // Verify an exact match.
+    EXPECT_TRUE(cfg_mgr_->matchReverse("100.168.192.in-addr.arpa", match));
+    EXPECT_EQ("100.168.192.in-addr.arpa", match->getName());
+
+    // Verify a sub-domain match.
+    EXPECT_TRUE(cfg_mgr_->matchReverse("27.100.168.192.in-addr.arpa", match));
+    EXPECT_EQ("100.168.192.in-addr.arpa", match->getName());
+
+    // Verify a sub-domain match.
+    EXPECT_TRUE(cfg_mgr_->matchReverse("30.133.168.192.in-addr.arpa", match));
+    EXPECT_EQ("168.192.in-addr.arpa", match->getName());
+
+    // Verify a wild card match.
+    EXPECT_TRUE(cfg_mgr_->matchReverse("shouldbe.wildcard", match));
+    EXPECT_EQ("*", match->getName());
+
+    // Verify that an attempt to match an empty FQDN throws.
+    ASSERT_THROW(cfg_mgr_->matchReverse("", match), D2CfgError);
+}
+
+
+} // end of anonymous namespace

+ 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.

+ 5 - 45
src/bin/d2/tests/d_cfg_mgr_unittests.cc

@@ -53,10 +53,11 @@ public:
 };
 
 /// @brief Test fixture class for testing DCfgMgrBase class.
-/// It maintains an member instance of DStubCfgMgr and provides methods for
-/// converting JSON strings to configuration element sets, checking parse
-/// results, and accessing the configuration context.
-class DStubCfgMgrTest : public ::testing::Test {
+/// 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
@@ -67,41 +68,6 @@ public:
     ~DStubCfgMgrTest() {
     }
 
-    /// @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 true if the conversion is successful, false otherwise.
-    bool fromJSON(std::string& json_text) {
-        try  {
-            config_set_ = isc::data::Element::fromJSON(json_text);
-        } catch (...) {
-            // This is so we can diagnose parsing mistakes during test
-            // development.
-            std::cerr << "fromJSON failed to parse text" << json_text
-                      << std::endl;
-            return (false);
-        }
-
-        return (true);
-    }
-
-    /// @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 true if the status value is equal to the given value.
-    bool checkAnswer(int should_be) {
-        int rcode = 0;
-        isc::data::ConstElementPtr comment;
-        comment = isc::config::parseAnswer(rcode, answer_);
-        //std::cout << "checkAnswer rcode:" << rcode << " comment: "
-        //          << *comment_ << std::endl;
-        return (rcode == should_be);
-    }
-
     /// @brief Convenience method which returns a DStubContextPtr to the
     /// configuration context.
     ///
@@ -113,12 +79,6 @@ public:
 
     /// @brief Configuration manager instance.
     DStubCfgMgrPtr cfg_mgr_;
-
-    /// @brief Configuration set being tested.
-    isc::data::ElementPtr config_set_;
-
-    /// @brief Results of most recent elemnt parsing.
-    isc::data::ConstElementPtr answer_;
 };
 
 ///@brief Tests basic construction/destruction of configuration manager.

+ 20 - 0
src/bin/d2/tests/d_test_stubs.cc

@@ -21,6 +21,26 @@ using namespace asio;
 namespace isc {
 namespace d2 {
 
+const char* valid_d2_config = "{ "
+                        "\"interface\" : \"eth1\" , "
+                        "\"ip_address\" : \"192.168.1.33\" , "
+                        "\"port\" : 88 , "
+                        "\"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;
 

+ 70 - 0
src/bin/d2/tests/d_test_stubs.h

@@ -28,6 +28,11 @@
 namespace isc {
 namespace d2 {
 
+/// @brief Provides a valid DHCP-DDNS configuraiton 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
@@ -85,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_;
 };
 
@@ -576,6 +583,69 @@ public:
 /// @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 true if the conversion is successful, false otherwise.
+    bool fromJSON(std::string& json_text) {
+        try  {
+            config_set_ = isc::data::Element::fromJSON(json_text);
+        } catch (...) {
+            // This is so we can diagnose parsing mistakes during test
+            // development.
+            std::cerr << "fromJSON failed to parse text" << json_text
+                      << std::endl;
+            return (false);
+        }
+
+        return (true);
+    }
+
+    /// @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 true if the status value is equal to the given value.
+    bool checkAnswer(int should_be) {
+        int rcode = 0;
+        isc::data::ConstElementPtr comment;
+        comment = isc::config::parseAnswer(rcode, answer_);
+        // Handy for diagnostics
+        // if (rcode != 0) {
+        //    std::cout << "checkAnswer rcode:" << rcode << " comment: "
+        //          << *comment << std::endl;
+        //}
+        return (rcode == should_be);
+    }
+
+    /// @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