BIND 10 master, updated. c04fb71fa44c2a458aac57ae54eeb1711c017a49 [master] Merged branch 'trac2957'. This adds configuration parsing support for DHCP-DDNS.
BIND 10 source code commits
bind10-changes at lists.isc.org
Tue Jul 2 14:32:00 UTC 2013
The branch, master has been updated
via c04fb71fa44c2a458aac57ae54eeb1711c017a49 (commit)
via 5e61d601d9424aac561c0661061e19188ed8d93f (commit)
via 41d5b2ead11a334e89a7f04ab38571e2795083d0 (commit)
via 971e77005e139347d5d4fedf0cc87bfb26b2685f (commit)
via 6e99f2cb85dd4722f7a5c2d15ebf2458cecc7b32 (commit)
via 95d6573794927ff46242625736d894ca4758f55a (commit)
via ae6f706d85ae90ca971d2cfef2abfe7e5bc7856d (commit)
via 42e6736faa21f98de53a4ac0bc52306041581d91 (commit)
via be6122f520871344324be1ceb9f118195d508bba (commit)
via dac0b87d5b0466e3aaf0f49ec7b2362606c03415 (commit)
from 0fc40f1677c6bdaf1e2920c6f86b2bc6c15b4cb0 (commit)
Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.
- Log -----------------------------------------------------------------
commit c04fb71fa44c2a458aac57ae54eeb1711c017a49
Merge: 0fc40f1 5e61d60
Author: Thomas Markwalder <tmark at isc.org>
Date: Tue Jul 2 10:30:47 2013 -0400
[master] Merged branch 'trac2957'. This adds configuration parsing
support for DHCP-DDNS.
-----------------------------------------------------------------------
Summary of changes:
configure.ac | 1 +
src/bin/d2/Makefile.am | 5 +-
src/bin/d2/d2_cfg_mgr.cc | 132 +++
src/bin/d2/d2_cfg_mgr.h | 175 +++
src/bin/d2/d2_config.cc | 605 ++++++++++
src/bin/d2/d2_config.h | 941 +++++++++++++++
src/bin/d2/d2_controller.h | 2 +-
src/bin/d2/d2_messages.mes | 35 +-
src/bin/d2/d2_process.cc | 15 +-
src/bin/d2/d2_process.h | 4 +-
src/bin/d2/d_cfg_mgr.cc | 240 ++++
src/bin/d2/d_cfg_mgr.h | 331 ++++++
src/bin/d2/d_controller.cc | 2 +-
src/bin/d2/d_controller.h | 8 +-
src/bin/d2/d_process.h | 24 +-
src/bin/d2/dhcp-ddns.spec | 210 +++-
src/bin/d2/tests/Makefile.am | 6 +
src/bin/d2/tests/d2_cfg_mgr_unittests.cc | 1238 ++++++++++++++++++++
src/bin/d2/tests/d2_controller_unittests.cc | 28 +-
src/bin/d2/tests/d2_process_unittests.cc | 17 +-
src/bin/d2/tests/d_cfg_mgr_unittests.cc | 386 ++++++
src/bin/d2/tests/d_test_stubs.cc | 126 +-
src/bin/d2/tests/d_test_stubs.h | 215 +++-
.../d2/tests/test_data_files_config.h.in} | 5 +-
src/lib/dhcpsrv/dhcp_parsers.cc | 2 +-
25 files changed, 4699 insertions(+), 54 deletions(-)
create mode 100644 src/bin/d2/d2_cfg_mgr.cc
create mode 100644 src/bin/d2/d2_cfg_mgr.h
create mode 100644 src/bin/d2/d2_config.cc
create mode 100644 src/bin/d2/d2_config.h
create mode 100644 src/bin/d2/d_cfg_mgr.cc
create mode 100644 src/bin/d2/d_cfg_mgr.h
create mode 100644 src/bin/d2/tests/d2_cfg_mgr_unittests.cc
create mode 100644 src/bin/d2/tests/d_cfg_mgr_unittests.cc
copy src/{lib/config/tests/data_def_unittests_config.h.in => bin/d2/tests/test_data_files_config.h.in} (84%)
-----------------------------------------------------------------------
diff --git a/configure.ac b/configure.ac
index 8a0118e..6841a52 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1405,6 +1405,7 @@ AC_OUTPUT([doc/version.ent
src/bin/dhcp4/spec_config.h.pre
src/bin/dhcp6/spec_config.h.pre
src/bin/d2/spec_config.h.pre
+ src/bin/d2/tests/test_data_files_config.h
src/bin/tests/process_rename_test.py
src/lib/config/tests/data_def_unittests_config.h
src/lib/python/isc/config/tests/config_test
diff --git a/src/bin/d2/Makefile.am b/src/bin/d2/Makefile.am
index 6f889db..aa87354 100644
--- a/src/bin/d2/Makefile.am
+++ b/src/bin/d2/Makefile.am
@@ -48,10 +48,12 @@ pkglibexec_PROGRAMS = b10-dhcp-ddns
b10_dhcp_ddns_SOURCES = main.cc
b10_dhcp_ddns_SOURCES += d2_log.cc d2_log.h
-b10_dhcp_ddns_SOURCES += d_process.h
b10_dhcp_ddns_SOURCES += d2_process.cc d2_process.h
b10_dhcp_ddns_SOURCES += d_controller.cc d_controller.h
b10_dhcp_ddns_SOURCES += d2_controller.cc d2_controller.h
+b10_dhcp_ddns_SOURCES += d_cfg_mgr.cc d_cfg_mgr.h
+b10_dhcp_ddns_SOURCES += d2_config.cc d2_config.h
+b10_dhcp_ddns_SOURCES += d2_cfg_mgr.cc d2_cfg_mgr.h
b10_dhcp_ddns_SOURCES += d2_update_message.cc d2_update_message.h
b10_dhcp_ddns_SOURCES += d2_zone.cc d2_zone.h
@@ -63,6 +65,7 @@ b10_dhcp_ddns_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
b10_dhcp_ddns_LDADD += $(top_builddir)/src/lib/cc/libb10-cc.la
b10_dhcp_ddns_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
b10_dhcp_ddns_LDADD += $(top_builddir)/src/lib/config/libb10-cfgclient.la
+b10_dhcp_ddns_LDADD += $(top_builddir)/src/lib/dhcpsrv/libb10-dhcpsrv.la
b10_dhcp_ddns_LDADD += $(top_builddir)/src/lib/dns/libb10-dns++.la
b10_dhcp_ddnsdir = $(pkgdatadir)
diff --git a/src/bin/d2/d2_cfg_mgr.cc b/src/bin/d2/d2_cfg_mgr.cc
new file mode 100644
index 0000000..07068d6
--- /dev/null
+++ b/src/bin/d2/d2_cfg_mgr.cc
@@ -0,0 +1,132 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <d2/d2_log.h>
+#include <d2/d2_cfg_mgr.h>
+
+#include <boost/foreach.hpp>
+
+using namespace std;
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::data;
+using namespace isc::asiolink;
+
+namespace isc {
+namespace d2 {
+
+// *********************** D2CfgContext *************************
+
+D2CfgContext::D2CfgContext()
+ : forward_mgr_(new DdnsDomainListMgr("forward_mgr")),
+ reverse_mgr_(new DdnsDomainListMgr("reverse_mgr")),
+ keys_(new TSIGKeyInfoMap()) {
+}
+
+D2CfgContext::D2CfgContext(const D2CfgContext& rhs) : DCfgContextBase(rhs) {
+ if (rhs.forward_mgr_) {
+ forward_mgr_.reset(new DdnsDomainListMgr(rhs.forward_mgr_->getName()));
+ forward_mgr_->setDomains(rhs.forward_mgr_->getDomains());
+ }
+
+ if (rhs.reverse_mgr_) {
+ reverse_mgr_.reset(new DdnsDomainListMgr(rhs.reverse_mgr_->getName()));
+ reverse_mgr_->setDomains(rhs.reverse_mgr_->getDomains());
+ }
+
+ keys_ = rhs.keys_;
+}
+
+D2CfgContext::~D2CfgContext() {
+}
+
+// *********************** D2CfgMgr *************************
+
+D2CfgMgr::D2CfgMgr() : DCfgMgrBase(DCfgContextBasePtr(new D2CfgContext())) {
+ // TSIG keys need to parse before the Domains, so we can catch Domains
+ // that specify undefined keys. Create the necessary parsing order now.
+ addToParseOrder("interface");
+ addToParseOrder("ip_address");
+ addToParseOrder("port");
+ addToParseOrder("tsig_keys");
+ addToParseOrder("forward_ddns");
+ addToParseOrder("reverse_ddns");
+}
+
+D2CfgMgr::~D2CfgMgr() {
+}
+
+bool
+D2CfgMgr::matchForward(const std::string& fqdn, DdnsDomainPtr& domain) {
+ if (fqdn.empty()) {
+ // This is a programmatic error and should not happen.
+ isc_throw(D2CfgError, "matchForward passed an empty fqdn");
+ }
+
+ // Fetch the forward manager from the D2 context.
+ DdnsDomainListMgrPtr mgr = getD2CfgContext()->getForwardMgr();
+
+ // Call the manager's match method and return the result.
+ return (mgr->matchDomain(fqdn, domain));
+}
+
+bool
+D2CfgMgr::matchReverse(const std::string& fqdn, DdnsDomainPtr& domain) {
+ if (fqdn.empty()) {
+ // This is a programmatic error and should not happen.
+ isc_throw(D2CfgError, "matchReverse passed a null or empty fqdn");
+ }
+
+ // Fetch the reverse manager from the D2 context.
+ DdnsDomainListMgrPtr mgr = getD2CfgContext()->getReverseMgr();
+
+ // Call the manager's match method and return the result.
+ return (mgr->matchDomain(fqdn, domain));
+}
+
+
+isc::dhcp::ParserPtr
+D2CfgMgr::createConfigParser(const std::string& config_id) {
+ // Get D2 specific context.
+ D2CfgContextPtr context = getD2CfgContext();
+
+ // Create parser instance based on element_id.
+ DhcpConfigParser* parser = NULL;
+ if ((config_id == "interface") ||
+ (config_id == "ip_address")) {
+ parser = new StringParser(config_id, context->getStringStorage());
+ } else if (config_id == "port") {
+ parser = new Uint32Parser(config_id, context->getUint32Storage());
+ } else if (config_id == "forward_ddns") {
+ parser = new DdnsDomainListMgrParser("forward_mgr",
+ context->getForwardMgr(),
+ context->getKeys());
+ } else if (config_id == "reverse_ddns") {
+ parser = new DdnsDomainListMgrParser("reverse_mgr",
+ context->getReverseMgr(),
+ context->getKeys());
+ } else if (config_id == "tsig_keys") {
+ parser = new TSIGKeyInfoListParser("tsig_key_list", context->getKeys());
+ } else {
+ isc_throw(NotImplemented,
+ "parser error: D2CfgMgr parameter not supported: "
+ << config_id);
+ }
+
+ return (isc::dhcp::ParserPtr(parser));
+}
+
+}; // end of isc::dhcp namespace
+}; // end of isc namespace
+
diff --git a/src/bin/d2/d2_cfg_mgr.h b/src/bin/d2/d2_cfg_mgr.h
new file mode 100644
index 0000000..f9089cb
--- /dev/null
+++ b/src/bin/d2/d2_cfg_mgr.h
@@ -0,0 +1,175 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef D2_CFG_MGR_H
+#define D2_CFG_MGR_H
+
+#include <asiolink/io_address.h>
+#include <cc/data.h>
+#include <exceptions/exceptions.h>
+#include <d2/d_cfg_mgr.h>
+#include <d2/d2_config.h>
+
+#include <stdint.h>
+#include <string>
+
+namespace isc {
+namespace d2 {
+
+class D2CfgContext;
+/// @brief Pointer to a configuration context.
+typedef boost::shared_ptr<D2CfgContext> D2CfgContextPtr;
+
+/// @brief DHCP-DDNS Configuration Context
+///
+/// Implements the storage container for configuration context.
+/// It provides a single enclosure for the storage of configuration parameters
+/// and any other DHCP-DDNS specific information that needs to be accessible
+/// during configuration parsing as well as to the application as a whole.
+/// It is derived from the context base class, DCfgContextBase.
+class D2CfgContext : public DCfgContextBase {
+public:
+ /// @brief Constructor
+ D2CfgContext();
+
+ /// @brief Destructor
+ virtual ~D2CfgContext();
+
+ /// @brief Creates a clone of this context object.
+ ///
+ /// @return returns a pointer to the new clone.
+ virtual DCfgContextBasePtr clone() {
+ return (DCfgContextBasePtr(new D2CfgContext(*this)));
+ }
+
+ /// @brief Fetches the forward DNS domain list manager.
+ ///
+ /// @return returns a pointer to the forward manager.
+ DdnsDomainListMgrPtr getForwardMgr() {
+ return (forward_mgr_);
+ }
+
+ /// @brief Fetches the reverse DNS domain list manager.
+ ///
+ /// @return returns a pointer to the reverse manager.
+ DdnsDomainListMgrPtr getReverseMgr() {
+ return (reverse_mgr_);
+ }
+
+ /// @brief Fetches the map of TSIG keys.
+ ///
+ /// @return returns a pointer to the key map.
+ TSIGKeyInfoMapPtr getKeys() {
+ return (keys_);
+ }
+
+protected:
+ /// @brief Copy constructor for use by derivations in clone().
+ D2CfgContext(const D2CfgContext& rhs);
+
+private:
+ /// @brief Private assignment operator to avoid potential for slicing.
+ D2CfgContext& operator=(const D2CfgContext& rhs);
+
+ /// @brief Forward domain list manager.
+ DdnsDomainListMgrPtr forward_mgr_;
+
+ /// @brief Reverse domain list manager.
+ DdnsDomainListMgrPtr reverse_mgr_;
+
+ /// @brief Storage for the map of TSIGKeyInfos
+ TSIGKeyInfoMapPtr keys_;
+};
+
+/// @brief Defines a pointer for DdnsDomain instances.
+typedef boost::shared_ptr<DdnsDomainListMgr> DdnsDomainListMgrPtr;
+
+
+
+/// @brief DHCP-DDNS Configuration Manager
+///
+/// Provides the mechanisms for managing the DHCP-DDNS application's
+/// configuration. This includes services for parsing sets of configuration
+/// values, storing the parsed information in its converted form,
+/// and retrieving the information on demand.
+class D2CfgMgr : public DCfgMgrBase {
+public:
+ /// @brief Constructor
+ D2CfgMgr();
+
+ /// @brief Destructor
+ virtual ~D2CfgMgr();
+
+ /// @brief Convenience method that returns the D2 configuration context.
+ ///
+ /// @return returns a pointer to the configuration context.
+ D2CfgContextPtr getD2CfgContext() {
+ return (boost::dynamic_pointer_cast<D2CfgContext>(getContext()));
+ }
+
+ /// @brief Matches a given FQDN to a forward domain.
+ ///
+ /// This calls the matchDomain method of the forward domain manager to
+ /// match the given FQDN to a forward domain.
+ ///
+ /// @param fqdn is the name for which to look.
+ /// @param domain receives the matching domain. Note that it will be reset
+ /// upon entry and only set if a match is subsequently found.
+ ///
+ /// @return returns true if a match is found, false otherwise.
+ /// @throw throws D2CfgError if given an invalid fqdn.
+ bool matchForward(const std::string& fqdn, DdnsDomainPtr &domain);
+
+ /// @brief Matches a given FQDN to a reverse domain.
+ ///
+ /// This calls the matchDomain method of the reverse domain manager to
+ /// match the given FQDN to a forward domain.
+ ///
+ /// @param fqdn is the name for which to look.
+ /// @param domain receives the matching domain. Note that it will be reset
+ /// upon entry and only set if a match is subsequently found.
+ ///
+ /// @return returns true if a match is found, false otherwise.
+ /// @throw throws D2CfgError if given an invalid fqdn.
+ bool matchReverse(const std::string& fqdn, DdnsDomainPtr &domain);
+
+protected:
+ /// @brief Given an element_id returns an instance of the appropriate
+ /// parser.
+ ///
+ /// It is responsible for top-level or outermost DHCP-DDNS configuration
+ /// elements (see dhcp-ddns.spec):
+ /// 1. interface
+ /// 2. ip_address
+ /// 3. port
+ /// 4. forward_ddns
+ /// 5. reverse_ddns
+ ///
+ /// @param element_id is the string name of the element as it will appear
+ /// in the configuration set.
+ ///
+ /// @return returns a ParserPtr to the parser instance.
+ /// @throw throws DCfgMgrBaseError if an error occurs.
+ virtual isc::dhcp::ParserPtr
+ createConfigParser(const std::string& element_id);
+};
+
+/// @brief Defines a shared pointer to D2CfgMgr.
+typedef boost::shared_ptr<D2CfgMgr> D2CfgMgrPtr;
+
+
+}; // end of isc::d2 namespace
+}; // end of isc namespace
+
+#endif // D2_CFG_MGR_H
diff --git a/src/bin/d2/d2_config.cc b/src/bin/d2/d2_config.cc
new file mode 100644
index 0000000..fe5ad0b
--- /dev/null
+++ b/src/bin/d2/d2_config.cc
@@ -0,0 +1,605 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <d2/d2_log.h>
+#include <d2/d2_cfg_mgr.h>
+#include <dhcpsrv/dhcp_parsers.h>
+#include <exceptions/exceptions.h>
+#include <asiolink/io_error.h>
+
+#include <boost/foreach.hpp>
+#include <boost/lexical_cast.hpp>
+
+namespace isc {
+namespace d2 {
+
+// *********************** TSIGKeyInfo *************************
+
+TSIGKeyInfo::TSIGKeyInfo(const std::string& name, const std::string& algorithm,
+ const std::string& secret)
+ :name_(name), algorithm_(algorithm), secret_(secret) {
+}
+
+TSIGKeyInfo::~TSIGKeyInfo() {
+}
+
+
+// *********************** DnsServerInfo *************************
+
+const char* DnsServerInfo::EMPTY_IP_STR = "0.0.0.0";
+
+DnsServerInfo::DnsServerInfo(const std::string& hostname,
+ isc::asiolink::IOAddress ip_address, uint32_t port,
+ bool enabled)
+ :hostname_(hostname), ip_address_(ip_address), port_(port),
+ enabled_(enabled) {
+}
+
+DnsServerInfo::~DnsServerInfo() {
+}
+
+// *********************** DdnsDomain *************************
+
+DdnsDomain::DdnsDomain(const std::string& name, const std::string& key_name,
+ DnsServerInfoStoragePtr servers)
+ : name_(name), key_name_(key_name), servers_(servers) {
+}
+
+DdnsDomain::~DdnsDomain() {
+}
+
+// *********************** DdnsDomainLstMgr *************************
+
+const char* DdnsDomainListMgr::wildcard_domain_name_ = "*";
+
+DdnsDomainListMgr::DdnsDomainListMgr(const std::string& name) : name_(name),
+ domains_(new DdnsDomainMap()) {
+}
+
+
+DdnsDomainListMgr::~DdnsDomainListMgr () {
+}
+
+void
+DdnsDomainListMgr::setDomains(DdnsDomainMapPtr domains) {
+ if (!domains) {
+ isc_throw(D2CfgError,
+ "DdnsDomainListMgr::setDomains: Domain list may not be null");
+ }
+
+ domains_ = domains;
+
+ // Look for the wild card domain. If present, set the member variable
+ // to remember it. This saves us from having to look for it every time
+ // we attempt a match.
+ DdnsDomainMap::iterator gotit = domains_->find(wildcard_domain_name_);
+ if (gotit != domains_->end()) {
+ wildcard_domain_ = gotit->second;
+ }
+}
+
+bool
+DdnsDomainListMgr::matchDomain(const std::string& fqdn, DdnsDomainPtr& domain) {
+ // Clear the return parameter.
+ domain.reset();
+
+ // First check the case of one domain to rule them all.
+ if ((size() == 1) && (wildcard_domain_)) {
+ domain = wildcard_domain_;
+ return (true);
+ }
+
+ // Start with the longest version of the fqdn and search the list.
+ // Continue looking for shorter versions of fqdn so long as no match is
+ // found.
+ // @todo This can surely be optimized, time permitting.
+ std::string match_name = fqdn;
+ std::size_t start_pos = 0;
+ while (start_pos != std::string::npos) {
+ match_name = match_name.substr(start_pos, std::string::npos);
+ DdnsDomainMap::iterator gotit = domains_->find(match_name);
+ if (gotit != domains_->end()) {
+ domain = gotit->second;
+ return (true);
+ }
+
+ start_pos = match_name.find_first_of(".");
+ if (start_pos != std::string::npos) {
+ ++start_pos;
+ }
+ }
+
+ // There's no match. If they specified a wild card domain use it
+ // otherwise there's no domain for this entry.
+ if (wildcard_domain_) {
+ domain = wildcard_domain_;
+ return (true);
+ }
+
+ LOG_WARN(dctl_logger, DHCP_DDNS_NO_MATCH).arg(fqdn);
+ return (false);
+}
+
+// *************************** PARSERS ***********************************
+
+// *********************** TSIGKeyInfoParser *************************
+
+TSIGKeyInfoParser::TSIGKeyInfoParser(const std::string& entry_name,
+ TSIGKeyInfoMapPtr keys)
+ : entry_name_(entry_name), keys_(keys), local_scalars_() {
+ if (!keys_) {
+ isc_throw(D2CfgError, "TSIGKeyInfoParser ctor:"
+ " key storage cannot be null");
+ }
+}
+
+TSIGKeyInfoParser::~TSIGKeyInfoParser() {
+}
+
+void
+TSIGKeyInfoParser::build(isc::data::ConstElementPtr key_config) {
+ isc::dhcp::ConfigPair config_pair;
+ // For each element in the key configuration:
+ // 1. Create a parser for the element.
+ // 2. Invoke the parser's build method passing in the element's
+ // configuration.
+ // 3. Invoke the parser's commit method to store the element's parsed
+ // data to the parser's local storage.
+ BOOST_FOREACH (config_pair, key_config->mapValue()) {
+ isc::dhcp::ParserPtr parser(createConfigParser(config_pair.first));
+ parser->build(config_pair.second);
+ parser->commit();
+ }
+}
+
+isc::dhcp::ParserPtr
+TSIGKeyInfoParser::createConfigParser(const std::string& config_id) {
+ DhcpConfigParser* parser = NULL;
+ // Based on the configuration id of the element, create the appropriate
+ // parser. Scalars are set to use the parser's local scalar storage.
+ if ((config_id == "name") ||
+ (config_id == "algorithm") ||
+ (config_id == "secret")) {
+ parser = new isc::dhcp::StringParser(config_id,
+ local_scalars_.getStringStorage());
+ } else {
+ isc_throw(NotImplemented,
+ "parser error: TSIGKeyInfo parameter not supported: "
+ << config_id);
+ }
+
+ // Return the new parser instance.
+ return (isc::dhcp::ParserPtr(parser));
+}
+
+void
+TSIGKeyInfoParser::commit() {
+ std::string name;
+ std::string algorithm;
+ std::string secret;
+
+ // Fetch the key configuration's parsed scalar values from parser's
+ // local storage.
+ local_scalars_.getParam("name", name);
+ local_scalars_.getParam("algorithm", algorithm);
+ local_scalars_.getParam("secret", secret);
+
+ // @todo Validation here is very superficial. This will expand as TSIG
+ // Key use is more fully implemented.
+
+ // Name cannot be blank.
+ if (name.empty()) {
+ isc_throw(D2CfgError, "TSIG Key Info must specify name");
+ }
+
+ // Algorithme cannot be blank.
+ if (algorithm.empty()) {
+ isc_throw(D2CfgError, "TSIG Key Info must specify algorithm");
+ }
+
+ // Secret cannot be blank.
+ if (secret.empty()) {
+ isc_throw(D2CfgError, "TSIG Key Info must specify secret");
+ }
+
+ // Currently, the premise is that key storage is always empty prior to
+ // parsing so we are always adding keys never replacing them. Duplicates
+ // are not allowed and should be flagged as a configuration error.
+ if (keys_->find(name) != keys_->end()) {
+ isc_throw(D2CfgError, "Duplicate TSIG key specified:" << name);
+ }
+
+ TSIGKeyInfoPtr key_info(new TSIGKeyInfo(name, algorithm, secret));
+
+ // Add the new TSIGKeyInfo to the key storage.
+ (*keys_)[name]=key_info;
+}
+
+// *********************** TSIGKeyInfoListParser *************************
+
+TSIGKeyInfoListParser::TSIGKeyInfoListParser(const std::string& list_name,
+ TSIGKeyInfoMapPtr keys)
+ :list_name_(list_name), keys_(keys), parsers_() {
+ if (!keys_) {
+ isc_throw(D2CfgError, "TSIGKeyInfoListParser ctor:"
+ " key storage cannot be null");
+ }
+}
+
+TSIGKeyInfoListParser::~TSIGKeyInfoListParser(){
+}
+
+void
+TSIGKeyInfoListParser::
+build(isc::data::ConstElementPtr key_list){
+ int i = 0;
+ isc::data::ConstElementPtr key_config;
+ // For each key element in the key list:
+ // 1. Create a parser for the key element.
+ // 2. Invoke the parser's build method passing in the key's
+ // configuration.
+ // 3. Add the parser to a local collection of parsers.
+ BOOST_FOREACH(key_config, key_list->listValue()) {
+ // Create a name for the parser based on its position in the list.
+ std::string entry_name = boost::lexical_cast<std::string>(i++);
+ isc::dhcp::ParserPtr parser(new TSIGKeyInfoParser(entry_name,
+ keys_));
+ parser->build(key_config);
+ parsers_.push_back(parser);
+ }
+}
+
+void
+TSIGKeyInfoListParser::commit() {
+ // Invoke commit on each server parser. This will cause each one to
+ // create it's server instance and commit it to storage.
+ BOOST_FOREACH(isc::dhcp::ParserPtr parser, parsers_) {
+ parser->commit();
+ }
+}
+
+// *********************** DnsServerInfoParser *************************
+
+DnsServerInfoParser::DnsServerInfoParser(const std::string& entry_name,
+ DnsServerInfoStoragePtr servers)
+ : entry_name_(entry_name), servers_(servers), local_scalars_() {
+ if (!servers_) {
+ isc_throw(D2CfgError, "DnsServerInfoParser ctor:"
+ " server storage cannot be null");
+ }
+}
+
+DnsServerInfoParser::~DnsServerInfoParser() {
+}
+
+void
+DnsServerInfoParser::build(isc::data::ConstElementPtr server_config) {
+ isc::dhcp::ConfigPair config_pair;
+ // For each element in the server configuration:
+ // 1. Create a parser for the element.
+ // 2. Invoke the parser's build method passing in the element's
+ // configuration.
+ // 3. Invoke the parser's commit method to store the element's parsed
+ // data to the parser's local storage.
+ BOOST_FOREACH (config_pair, server_config->mapValue()) {
+ isc::dhcp::ParserPtr parser(createConfigParser(config_pair.first));
+ parser->build(config_pair.second);
+ parser->commit();
+ }
+
+}
+
+isc::dhcp::ParserPtr
+DnsServerInfoParser::createConfigParser(const std::string& config_id) {
+ DhcpConfigParser* parser = NULL;
+ // Based on the configuration id of the element, create the appropriate
+ // parser. Scalars are set to use the parser's local scalar storage.
+ if ((config_id == "hostname") ||
+ (config_id == "ip_address")) {
+ parser = new isc::dhcp::StringParser(config_id,
+ local_scalars_.getStringStorage());
+ } else if (config_id == "port") {
+ parser = new isc::dhcp::Uint32Parser(config_id,
+ local_scalars_.getUint32Storage());
+ } else {
+ isc_throw(NotImplemented,
+ "parser error: DnsServerInfo parameter not supported: "
+ << config_id);
+ }
+
+ // Return the new parser instance.
+ return (isc::dhcp::ParserPtr(parser));
+}
+
+void
+DnsServerInfoParser::commit() {
+ std::string hostname;
+ std::string ip_address;
+ uint32_t port = DnsServerInfo::STANDARD_DNS_PORT;
+
+ // Fetch the server configuration's parsed scalar values from parser's
+ // local storage.
+ local_scalars_.getParam("hostname", hostname, DCfgContextBase::OPTIONAL);
+ local_scalars_.getParam("ip_address", ip_address,
+ DCfgContextBase::OPTIONAL);
+ local_scalars_.getParam("port", port, DCfgContextBase::OPTIONAL);
+
+ // The configuration must specify one or the other.
+ if (hostname.empty() == ip_address.empty()) {
+ isc_throw(D2CfgError, "Dns Server must specify one or the other"
+ " of hostname and IP address");
+ }
+
+ DnsServerInfoPtr serverInfo;
+ if (!hostname.empty()) {
+ // When hostname is specified, create a valid, blank IOAddress and
+ // then create the DnsServerInfo.
+ isc::asiolink::IOAddress io_addr(DnsServerInfo::EMPTY_IP_STR);
+ serverInfo.reset(new DnsServerInfo(hostname, io_addr, port));
+ } else {
+ try {
+ // Create an IOAddress from the IP address string given and then
+ // create the DnsServerInfo.
+ isc::asiolink::IOAddress io_addr(ip_address);
+ serverInfo.reset(new DnsServerInfo(hostname, io_addr, port));
+ } catch (const isc::asiolink::IOError& ex) {
+ isc_throw(D2CfgError, "Invalid IP address:" << ip_address);
+ }
+ }
+
+ // Add the new DnsServerInfo to the server storage.
+ servers_->push_back(serverInfo);
+}
+
+// *********************** DnsServerInfoListParser *************************
+
+DnsServerInfoListParser::DnsServerInfoListParser(const std::string& list_name,
+ DnsServerInfoStoragePtr servers)
+ :list_name_(list_name), servers_(servers), parsers_() {
+ if (!servers_) {
+ isc_throw(D2CfgError, "DdnsServerInfoListParser ctor:"
+ " server storage cannot be null");
+ }
+}
+
+DnsServerInfoListParser::~DnsServerInfoListParser(){
+}
+
+void
+DnsServerInfoListParser::
+build(isc::data::ConstElementPtr server_list){
+ int i = 0;
+ isc::data::ConstElementPtr server_config;
+ // For each server element in the server list:
+ // 1. Create a parser for the server element.
+ // 2. Invoke the parser's build method passing in the server's
+ // configuration.
+ // 3. Add the parser to a local collection of parsers.
+ BOOST_FOREACH(server_config, server_list->listValue()) {
+ // Create a name for the parser based on its position in the list.
+ std::string entry_name = boost::lexical_cast<std::string>(i++);
+ isc::dhcp::ParserPtr parser(new DnsServerInfoParser(entry_name,
+ servers_));
+ parser->build(server_config);
+ parsers_.push_back(parser);
+ }
+}
+
+void
+DnsServerInfoListParser::commit() {
+ // Domains must have at least one server.
+ if (parsers_.size() == 0) {
+ isc_throw (D2CfgError, "Server List must contain at least one server");
+ }
+
+ // Invoke commit on each server parser. This will cause each one to
+ // create it's server instance and commit it to storage.
+ BOOST_FOREACH(isc::dhcp::ParserPtr parser, parsers_) {
+ parser->commit();
+ }
+}
+
+// *********************** DdnsDomainParser *************************
+
+DdnsDomainParser::DdnsDomainParser(const std::string& entry_name,
+ DdnsDomainMapPtr domains,
+ TSIGKeyInfoMapPtr keys)
+ : entry_name_(entry_name), domains_(domains), keys_(keys),
+ local_servers_(new DnsServerInfoStorage()), local_scalars_() {
+ if (!domains_) {
+ isc_throw(D2CfgError,
+ "DdnsDomainParser ctor, domain storage cannot be null");
+ }
+}
+
+
+DdnsDomainParser::~DdnsDomainParser() {
+}
+
+void
+DdnsDomainParser::build(isc::data::ConstElementPtr domain_config) {
+ // For each element in the domain configuration:
+ // 1. Create a parser for the element.
+ // 2. Invoke the parser's build method passing in the element's
+ // configuration.
+ // 3. Invoke the parser's commit method to store the element's parsed
+ // data to the parser's local storage.
+ isc::dhcp::ConfigPair config_pair;
+ BOOST_FOREACH(config_pair, domain_config->mapValue()) {
+ isc::dhcp::ParserPtr parser(createConfigParser(config_pair.first));
+ parser->build(config_pair.second);
+ parser->commit();
+ }
+}
+
+isc::dhcp::ParserPtr
+DdnsDomainParser::createConfigParser(const std::string& config_id) {
+ DhcpConfigParser* parser = NULL;
+ // Based on the configuration id of the element, create the appropriate
+ // parser. Scalars are set to use the parser's local scalar storage.
+ if ((config_id == "name") ||
+ (config_id == "key_name")) {
+ parser = new isc::dhcp::StringParser(config_id,
+ local_scalars_.getStringStorage());
+ } else if (config_id == "dns_servers") {
+ // Server list parser is given in our local server storage. It will pass
+ // this down to its server parsers and is where they will write their
+ // server instances upon commit.
+ parser = new DnsServerInfoListParser(config_id, local_servers_);
+ } else {
+ isc_throw(NotImplemented,
+ "parser error: DdnsDomain parameter not supported: "
+ << config_id);
+ }
+
+ // Return the new domain parser instance.
+ return (isc::dhcp::ParserPtr(parser));
+}
+
+void
+DdnsDomainParser::commit() {
+ std::string name;
+ std::string key_name;
+
+ // Domain name is not optional. The get will throw if its not there.
+ local_scalars_.getParam("name", name);
+
+ // Blank domain names are not allowed.
+ if (name.empty()) {
+ isc_throw(D2CfgError, "Domain name cannot be blank");
+ }
+
+ // Currently, the premise is that domain storage is always empty
+ // prior to parsing so always adding domains never replacing them.
+ // Duplicates are not allowed and should be flagged as a configuration
+ // error.
+ if (domains_->find(name) != domains_->end()) {
+ isc_throw(D2CfgError, "Duplicate domain specified:" << name);
+ }
+
+ // Key name is optional. If it is not blank, then validate it against
+ // the defined list of keys.
+ local_scalars_.getParam("key_name", key_name, DCfgContextBase::OPTIONAL);
+ if (!key_name.empty()) {
+ if ((!keys_) || (keys_->find(key_name) == keys_->end())) {
+ isc_throw(D2CfgError, "DdnsDomain :" << name <<
+ " specifies and undefined key:" << key_name);
+ }
+ }
+
+ // Instantiate the new domain and add it to domain storage.
+ DdnsDomainPtr domain(new DdnsDomain(name, key_name, local_servers_));
+
+ // Add the new domain to the domain storage.
+ (*domains_)[name]=domain;
+}
+
+// *********************** DdnsDomainListParser *************************
+
+DdnsDomainListParser::DdnsDomainListParser(const std::string& list_name,
+ DdnsDomainMapPtr domains,
+ TSIGKeyInfoMapPtr keys)
+ :list_name_(list_name), domains_(domains), keys_(keys), parsers_() {
+ if (!domains_) {
+ isc_throw(D2CfgError, "DdnsDomainListParser ctor:"
+ " domain storage cannot be null");
+ }
+}
+
+DdnsDomainListParser::~DdnsDomainListParser(){
+}
+
+void
+DdnsDomainListParser::
+build(isc::data::ConstElementPtr domain_list){
+ // For each domain element in the domain list:
+ // 1. Create a parser for the domain element.
+ // 2. Invoke the parser's build method passing in the domain's
+ // configuration.
+ // 3. Add the parser to the local collection of parsers.
+ int i = 0;
+ isc::data::ConstElementPtr domain_config;
+ BOOST_FOREACH(domain_config, domain_list->listValue()) {
+ std::string entry_name = boost::lexical_cast<std::string>(i++);
+ isc::dhcp::ParserPtr parser(new DdnsDomainParser(entry_name,
+ domains_, keys_));
+ parser->build(domain_config);
+ parsers_.push_back(parser);
+ }
+}
+
+void
+DdnsDomainListParser::commit() {
+ // Invoke commit on each server parser. This will cause each one to
+ // create it's server instance and commit it to storage.
+ BOOST_FOREACH(isc::dhcp::ParserPtr parser, parsers_) {
+ parser->commit();
+ }
+}
+
+
+// *********************** DdnsDomainListMgrParser *************************
+
+DdnsDomainListMgrParser::DdnsDomainListMgrParser(const std::string& entry_name,
+ DdnsDomainListMgrPtr mgr, TSIGKeyInfoMapPtr keys)
+ : entry_name_(entry_name), mgr_(mgr), keys_(keys),
+ local_domains_(new DdnsDomainMap()), local_scalars_() {
+}
+
+
+DdnsDomainListMgrParser::~DdnsDomainListMgrParser() {
+}
+
+void
+DdnsDomainListMgrParser::build(isc::data::ConstElementPtr domain_config) {
+ // For each element in the domain manager configuration:
+ // 1. Create a parser for the element.
+ // 2. Invoke the parser's build method passing in the element's
+ // configuration.
+ // 3. Invoke the parser's commit method to store the element's parsed
+ // data to the parser's local storage.
+ isc::dhcp::ConfigPair config_pair;
+ BOOST_FOREACH(config_pair, domain_config->mapValue()) {
+ isc::dhcp::ParserPtr parser(createConfigParser(config_pair.first));
+ parser->build(config_pair.second);
+ parser->commit();
+ }
+}
+
+isc::dhcp::ParserPtr
+DdnsDomainListMgrParser::createConfigParser(const std::string& config_id) {
+ DhcpConfigParser* parser = NULL;
+ if (config_id == "ddns_domains") {
+ // Domain list parser is given our local domain storage. It will pass
+ // this down to its domain parsers and is where they will write their
+ // domain instances upon commit.
+ parser = new DdnsDomainListParser(config_id, local_domains_, keys_);
+ } else {
+ isc_throw(NotImplemented, "parser error: "
+ "DdnsDomainListMgr parameter not supported: " << config_id);
+ }
+
+ // Return the new domain parser instance.
+ return (isc::dhcp::ParserPtr(parser));
+}
+
+void
+DdnsDomainListMgrParser::commit() {
+ // Add the new domain to the domain storage.
+ mgr_->setDomains(local_domains_);
+}
+
+
+}; // end of isc::dhcp namespace
+}; // end of isc namespace
diff --git a/src/bin/d2/d2_config.h b/src/bin/d2/d2_config.h
new file mode 100644
index 0000000..7350291
--- /dev/null
+++ b/src/bin/d2/d2_config.h
@@ -0,0 +1,941 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef D2_CONFIG_H
+#define D2_CONFIG_H
+
+#include <asiolink/io_address.h>
+#include <cc/data.h>
+#include <d2/d_cfg_mgr.h>
+#include <dhcpsrv/dhcp_parsers.h>
+#include <exceptions/exceptions.h>
+
+#include <boost/foreach.hpp>
+
+#include <stdint.h>
+#include <string>
+
+namespace isc {
+namespace d2 {
+
+/// @file d2_config.h
+/// @brief A collection of classes for housing and parsing the application
+/// configuration necessary for the DHCP-DDNS application (aka D2).
+///
+/// This file contains the class declarations for the class hierarchy created
+/// from the D2 configuration and the parser classes used to create it.
+/// The application configuration consists of a set of scalar parameters,
+/// a list of TSIG keys, and two managed lists of domains: one list for
+/// forward domains and one list for reverse domains.
+///
+/// The key list consists of one or more TSIG keys, each entry described by
+/// a name, the algorithm method name, and its secret key component.
+///
+/// @todo NOTE that TSIG configuration parsing is functional, the use of
+/// TSIG Keys during the actual DNS update transactions is not. This will be
+/// implemented in a future release.
+///
+/// Each managed domain list consists of a list one or more domains and is
+/// represented by the class DdnsDomainListMgr.
+///
+/// Each domain consists of a set of scalars parameters and a list of DNS
+/// servers which support that domain. Among its scalars, is key_name, which
+/// is the name of the TSIG Key to use for with this domain. This value should
+/// map to one of the TSIG Keys in the key list. Domains are represented by
+/// the class, DdnsDomain.
+///
+/// Each server consists of a set of scalars used to describe the server such
+/// that the application can carry out DNS update exchanges with it. Servers
+/// are represented by the class, DnsServerInfo.
+///
+/// The configuration specification for use with BIND10 is detailed in the file
+/// dhcp-ddns.spec.
+///
+/// The parsing class hierarchy reflects this same scheme. Working top down:
+///
+/// A DdnsDomainListMgrParser parses a managed domain list entry. It handles
+/// any scalars which belong to the manager as well as creating and invoking a
+/// DdnsDomainListParser to parse its list of domain entries.
+///
+/// A DdnsDomainLiatParser creates and invokes DdnsDomainListParser for each
+/// domain entry in its list.
+///
+/// A DdnsDomainParser handles the scalars which belong to the domain as well as
+/// creating and invoking a DnsSeverInfoListParser to parse its list of server
+/// entries.
+///
+/// A DnsServerInfoListParser creates and invokes a DnsServerInfoParser for
+/// each server entry in its list.
+///
+/// A DdnsServerInfoParser handles the scalars which belong to the server.
+/// The following is sample configuration in JSON form with extra spacing
+/// for clarity:
+///
+/// @code
+/// {
+/// "interface" : "eth1" ,
+/// "ip_address" : "192.168.1.33" ,
+/// "port" : 88 ,
+/// "tsig_keys":
+//// [
+/// {
+/// "name": "d2_key.tmark.org" ,
+/// "algorithm": "md5" ,
+/// "secret": "0123456989"
+/// }
+/// ],
+/// "forward_ddns" :
+/// {
+/// "ddns_domains":
+/// [
+/// {
+/// "name": "tmark.org." ,
+/// "key_name": "d2_key.tmark.org" ,
+/// "dns_servers" :
+/// [
+/// { "hostname": "fserver.tmark.org" },
+/// { "hostname": "f2server.tmark.org" }
+/// ]
+/// },
+/// {
+/// "name": "pub.tmark.org." ,
+/// "key_name": "d2_key.tmark.org" ,
+/// "dns_servers" :
+/// [
+/// { "hostname": "f3server.tmark.org" }
+/// ]
+/// }
+/// ]
+/// },
+/// "reverse_ddns" :
+/// {
+/// "ddns_domains":
+/// [
+/// {
+/// "name": " 0.168.192.in.addr.arpa." ,
+/// "key_name": "d2_key.tmark.org" ,
+/// "dns_servers" :
+/// [
+/// { "ip_address": "127.0.0.101" , "port": 100 }
+/// ]
+/// }
+/// ]
+/// }
+/// }
+/// @endcode
+
+/// @brief Exception thrown when the error during configuration handling
+/// occurs.
+class D2CfgError : public isc::Exception {
+public:
+ D2CfgError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Represents a TSIG Key.
+///
+/// Currently, this is simple storage class containing the basic attributes of
+/// a TSIG Key. It is intended primarily as a reference for working with
+/// actual keys and may eventually be replaced by isc::dns::TSIGKey. TSIG Key
+/// functionality at this stage is strictly limited to configuration parsing.
+/// @todo full functionality for using TSIG during DNS updates will be added
+/// in a future release.
+class TSIGKeyInfo {
+public:
+
+ /// @brief Constructor
+ ///
+ /// @param name the unique label used to identify this key
+ /// @param algorithm the name of the encryption alogirthm this key uses.
+ /// (@todo This will be a fixed list of choices)
+ /// @param secret the secret component of this key
+ TSIGKeyInfo(const std::string& name, const std::string& algorithm,
+ const std::string& secret);
+
+ /// @brief Destructor
+ virtual ~TSIGKeyInfo();
+
+ /// @brief Getter which returns the key's name.
+ ///
+ /// @return returns the name as as std::string.
+ const std::string getName() const {
+ return (name_);
+ }
+
+ /// @brief Getter which returns the key's algorithm.
+ ///
+ /// @return returns the algorithm as as std::string.
+ const std::string getAlgorithm() const {
+ return (algorithm_);
+ }
+
+ /// @brief Getter which returns the key's secret.
+ ///
+ /// @return returns the secret as as std::string.
+ const std::string getSecret() const {
+ return (secret_);
+ }
+
+private:
+ /// @brief The name of the key.
+ ///
+ /// This value is the unique identifeir thay domains use to
+ /// to specify which TSIG key they need.
+ std::string name_;
+
+ /// @brief The algorithm that should be used for this key.
+ std::string algorithm_;
+
+ /// @brief The secret value component of this key.
+ std::string secret_;
+};
+
+/// @brief Defines a pointer for TSIGKeyInfo instances.
+typedef boost::shared_ptr<TSIGKeyInfo> TSIGKeyInfoPtr;
+
+/// @brief Defines a map of TSIGKeyInfos, keyed by the name.
+typedef std::map<std::string, TSIGKeyInfoPtr> TSIGKeyInfoMap;
+
+/// @brief Defines a iterator pairing of name and TSIGKeyInfo
+typedef std::pair<std::string, TSIGKeyInfoPtr> TSIGKeyInfoMapPair;
+
+/// @brief Defines a pointer to map of TSIGkeyInfos
+typedef boost::shared_ptr<TSIGKeyInfoMap> TSIGKeyInfoMapPtr;
+
+
+/// @brief Represents a specific DNS Server.
+/// It provides information about the server's network identity and typically
+/// belongs to a list of servers supporting DNS for a given domain. It will
+/// be used to establish communications with the server to carry out DNS
+/// updates.
+class DnsServerInfo {
+public:
+
+ /// @brief defines DNS standard port value
+ static const uint32_t STANDARD_DNS_PORT = 53;
+
+ /// @brief defines an "empty" string version of an ip address.
+ static const char* EMPTY_IP_STR;
+
+
+ /// @brief Constructor
+ ///
+ /// @param hostname is the resolvable name of the server. If not blank,
+ /// then the server address should be resolved at runtime.
+ /// @param ip_address is the static IP address of the server. If hostname
+ /// is blank, then this address should be used to connect to the server.
+ /// @param port is the port number on which the server listens.
+ /// primarily meant for testing purposes. Normally, DNS traffic is on
+ /// is port 53. (NOTE the constructing code is responsible for setting
+ /// the default.)
+ /// @param enabled is a flag that indicates whether this server is
+ /// enabled for use. It defaults to true.
+ DnsServerInfo(const std::string& hostname,
+ isc::asiolink::IOAddress ip_address,
+ uint32_t port = STANDARD_DNS_PORT,
+ bool enabled=true);
+
+ /// @brief Destructor
+ virtual ~DnsServerInfo();
+
+ /// @brief Getter which returns the server's hostname.
+ ///
+ /// @return returns the hostname as as std::string.
+ const std::string getHostname() const {
+ return (hostname_);
+ }
+
+ /// @brief Getter which returns the server's port number.
+ ///
+ /// @return returns the port number as a unsigned integer.
+ uint32_t getPort() const {
+ return (port_);
+ }
+
+ /// @brief Getter which returns the server's ip_address.
+ ///
+ /// @return returns the address as an IOAddress reference.
+ const isc::asiolink::IOAddress& getIpAddress() const {
+ return (ip_address_);
+ }
+
+ /// @brief Convenience method which returns whether or not the
+ /// server is enabled.
+ ///
+ /// @return returns true if the server is enabled, false otherwise.
+ bool isEnabled() const {
+ return (enabled_);
+ }
+
+ /// @brief Sets the server's enabled flag to true.
+ void enable() {
+ enabled_ = true;
+ }
+
+ /// @brief Sets the server's enabled flag to false.
+ void disable() {
+ enabled_ = false;
+ }
+
+
+private:
+ /// @brief The resolvable name of the server. If not blank, then the
+ /// server's IP address should be dynamically resolved at runtime.
+ std::string hostname_;
+
+ /// @brief The static IP address of the server. When hostname is blank,
+ /// then this address should be used to connect to the server.
+ isc::asiolink::IOAddress ip_address_;
+
+ /// @brief The port number on which the server listens for DNS traffic.
+ uint32_t port_;
+
+ /// @param enabled is a flag that indicates whether this server is
+ /// enabled for use. It defaults to true.
+ bool enabled_;
+};
+
+/// @brief Defines a pointer for DnsServerInfo instances.
+typedef boost::shared_ptr<DnsServerInfo> DnsServerInfoPtr;
+
+/// @brief Defines a storage container for DnsServerInfo pointers.
+typedef std::vector<DnsServerInfoPtr> DnsServerInfoStorage;
+
+/// @brief Defines a pointer to DnsServerInfo storage containers.
+typedef boost::shared_ptr<DnsServerInfoStorage> DnsServerInfoStoragePtr;
+
+
+/// @brief Represents a DNS domain that is may be updated dynamically.
+/// This class specifies a DNS domain and the list of DNS servers that support
+/// it. It's primary use is to map a domain to the DNS server(s) responsible
+/// for it.
+/// @todo Currently the name entry for a domain is just an std::string. It
+/// may be worthwhile to change this to a dns::Name for purposes of better
+/// validation and matching capabilities.
+class DdnsDomain {
+public:
+ /// @brief Constructor
+ ///
+ /// @param name is the domain name of the domain.
+ /// @param key_name is the TSIG key name for use with this domain.
+ /// @param servers is the list of server(s) supporting this domain.
+ DdnsDomain(const std::string& name, const std::string& key_name,
+ DnsServerInfoStoragePtr servers);
+
+ /// @brief Destructor
+ virtual ~DdnsDomain();
+
+ /// @brief Getter which returns the domain's name.
+ ///
+ /// @return returns the name in an std::string.
+ const std::string getName() const {
+ return (name_);
+ }
+
+ /// @brief Getter which returns the domain's TSIG key name.
+ ///
+ /// @return returns the key name in an std::string.
+ const std::string getKeyName() const {
+ return (key_name_);
+ }
+
+ /// @brief Getter which returns the domain's list of servers.
+ ///
+ /// @return returns the pointer to the server storage.
+ const DnsServerInfoStoragePtr& getServers() {
+ return (servers_);
+ }
+
+private:
+ /// @brief The domain name of the domain.
+ std::string name_;
+
+ /// @brief The name of the TSIG key for use with this domain.
+ std::string key_name_;
+
+ /// @brief The list of server(s) supporting this domain.
+ DnsServerInfoStoragePtr servers_;
+};
+
+/// @brief Defines a pointer for DdnsDomain instances.
+typedef boost::shared_ptr<DdnsDomain> DdnsDomainPtr;
+
+/// @brief Defines a map of DdnsDomains, keyed by the domain name.
+typedef std::map<std::string, DdnsDomainPtr> DdnsDomainMap;
+
+/// @brief Defines a iterator pairing domain name and DdnsDomain
+typedef std::pair<std::string, DdnsDomainPtr> DdnsDomainMapPair;
+
+/// @brief Defines a pointer to DdnsDomain storage containers.
+typedef boost::shared_ptr<DdnsDomainMap> DdnsDomainMapPtr;
+
+/// @brief Provides storage for and management of a list of DNS domains.
+/// In addition to housing the domain list storage, it provides domain matching
+/// services. These services are used to match a FQDN to a domain. Currently
+/// it supports a single matching service, which will return the matching
+/// domain or a wild card domain if one is specified. The wild card domain is
+/// specified as a domain whose name is "*".
+/// As matching capabilities evolve this class is expected to expand.
+class DdnsDomainListMgr {
+public:
+ /// @brief defines the domain name for denoting the wildcard domain.
+ static const char* wildcard_domain_name_;
+
+ /// @brief Constructor
+ ///
+ /// @param name is an arbitrary label assigned to this manager.
+ DdnsDomainListMgr(const std::string& name);
+
+ /// @brief Destructor
+ virtual ~DdnsDomainListMgr ();
+
+ /// @brief Matches a given name to a domain based on a longest match
+ /// scheme.
+ ///
+ /// Given a FQDN, search the list of domains, successively removing a
+ /// sub-domain from the FQDN until a match is found. If no match is found
+ /// and the wild card domain is present in the list, then return it as the
+ /// match. If the wild card domain is the only domain in the list, then
+ /// it will be returned immediately for any FQDN.
+ ///
+ /// @param fqdn is the name for which to look.
+ /// @param domain receives the matching domain. Note that it will be reset
+ /// upon entry and only set if a match is subsequently found.
+ ///
+ /// @return returns true if a match is found, false otherwise.
+ /// @todo This is a very basic match method, which expects valid FQDNs
+ /// both as input and for the DdnsDomain::getName(). Currently both are
+ /// simple strings and there is no normalization (i.e. added trailing dots
+ /// if missing).
+ virtual bool matchDomain(const std::string& fqdn, DdnsDomainPtr& domain);
+
+ /// @brief Fetches the manager's name.
+ ///
+ /// @return returns a std::string containing the name of the manager.
+ const std::string getName() const {
+ return (name_);
+ }
+
+ /// @brief Returns the number of domains in the domain list.
+ ///
+ /// @brief returns an unsigned int containing the domain count.
+ uint32_t size() const {
+ return (domains_->size());
+ }
+
+ /// @brief Fetches the wild card domain.
+ ///
+ /// @return returns a pointer reference to the domain. The pointer will
+ /// empty if the wild card domain is not present.
+ const DdnsDomainPtr& getWildcardDomain() {
+ return (wildcard_domain_);
+ }
+
+ /// @brief Fetches the domain list.
+ ///
+ /// @return returns a pointer reference to the list of domains.
+ const DdnsDomainMapPtr &getDomains() {
+ return (domains_);
+ }
+
+ /// @brief Sets the manger's domain list to the given list of domains.
+ /// This method will scan the inbound list for the wild card domain and
+ /// set the internal wild card domain pointer accordingly.
+ void setDomains(DdnsDomainMapPtr domains);
+
+private:
+ /// @brief An arbitrary label assigned to this manager.
+ std::string name_;
+
+ /// @brief Map of the domains, keyed by name.
+ DdnsDomainMapPtr domains_;
+
+ /// @brief Pointer to the wild card domain.
+ DdnsDomainPtr wildcard_domain_;
+};
+
+/// @brief Defines a pointer for DdnsDomain instances.
+typedef boost::shared_ptr<DdnsDomainListMgr> DdnsDomainListMgrPtr;
+
+/// @brief Storage container for scalar configuration parameters.
+///
+/// This class is useful for implementing parsers for more complex configuration
+/// elements (e.g. those of item type "map"). It provides a convenient way to
+/// add storage to the parser for an arbitrary number and variety of scalar
+/// configuration items (e.g. ints, bools, strings...) without explicitly adding
+/// storage for each individual type needed by the parser.
+///
+/// This class implements a concrete version of the base class by supplying a
+/// "clone" method.
+class DScalarContext : public DCfgContextBase {
+public:
+
+ /// @brief Constructor
+ DScalarContext() {
+ };
+
+ /// @brief Destructor
+ virtual ~DScalarContext() {
+ }
+
+ /// @brief Creates a clone of a DStubContext.
+ ///
+ /// @return returns a pointer to the new clone.
+ virtual DCfgContextBasePtr clone() {
+ return (DCfgContextBasePtr(new DScalarContext(*this)));
+ }
+
+protected:
+ /// @brief Copy constructor
+ DScalarContext(const DScalarContext& rhs) : DCfgContextBase(rhs) {
+ }
+
+private:
+ /// @brief Private assignment operator, not implemented.
+ DScalarContext& operator=(const DScalarContext& rhs);
+};
+
+/// @brief Defines a pointer for DScalarContext instances.
+typedef boost::shared_ptr<DScalarContext> DScalarContextPtr;
+
+/// @brief Parser for TSIGKeyInfo
+///
+/// This class parses the configuration element "tsig_key" defined in
+/// src/bin/d2/dhcp-ddns.spec and creates an instance of a TSIGKeyInfo.
+class TSIGKeyInfoParser : public isc::dhcp::DhcpConfigParser {
+public:
+ /// @brief Constructor
+ ///
+ /// @param entry_name is an arbitrary label assigned to this configuration
+ /// definition. Since servers are specified in a list this value is likely
+ /// be something akin to "key:0", set during parsing.
+ /// @param keys is a pointer to the storage area to which the parser
+ /// should commit the newly created TSIGKeyInfo instance.
+ TSIGKeyInfoParser(const std::string& entry_name, TSIGKeyInfoMapPtr keys);
+
+ /// @brief Destructor
+ virtual ~TSIGKeyInfoParser();
+
+ /// @brief Performs the actual parsing of the given "tsig_key" element.
+ ///
+ /// The results of the parsing are retained internally for use during
+ /// commit.
+ ///
+ /// @param key_config is the "tsig_key" configuration to parse
+ virtual void build(isc::data::ConstElementPtr key_config);
+
+ /// @brief Creates a parser for the given "tsig_key" member element id.
+ ///
+ /// The key elements currently supported are(see dhcp-ddns.spec):
+ /// 1. name
+ /// 2. algorithm
+ /// 3. secret
+ ///
+ /// @param config_id is the "item_name" for a specific member element of
+ /// the "tsig_key" specification.
+ ///
+ /// @return returns a pointer to newly created parser.
+ virtual isc::dhcp::ParserPtr createConfigParser(const std::string&
+ config_id);
+
+ /// @brief Instantiates a DnsServerInfo from internal data values
+ /// saves it to the storage area pointed to by servers_.
+ virtual void commit();
+
+private:
+ /// @brief Arbitrary label assigned to this parser instance.
+ /// Since servers are specified in a list this value is likely be something
+ /// akin to "key:0", set during parsing. Primarily here for diagnostics.
+ std::string entry_name_;
+
+ /// @brief Pointer to the storage area to which the parser should commit
+ /// the newly created TSIGKeyInfo instance. This is given to us as a
+ /// constructor argument by an upper level.
+ TSIGKeyInfoMapPtr keys_;
+
+ /// @brief Local storage area for scalar parameter values. Use to hold
+ /// data until time to commit.
+ DScalarContext local_scalars_;
+};
+
+/// @brief Parser for a list of TSIGKeyInfos
+///
+/// This class parses a list of "tsig_key" configuration elements.
+/// (see src/bin/d2/dhcp-ddns.spec). The TSIGKeyInfo instances are added
+/// to the given storage upon commit.
+class TSIGKeyInfoListParser : public isc::dhcp::DhcpConfigParser {
+public:
+
+ /// @brief Constructor
+ ///
+ /// @param list_name is an arbitrary label assigned to this parser instance.
+ /// @param keys is a pointer to the storage area to which the parser
+ /// should commit the newly created TSIGKeyInfo instance.
+ TSIGKeyInfoListParser(const std::string& list_name, TSIGKeyInfoMapPtr keys);
+
+ /// @brief Destructor
+ virtual ~TSIGKeyInfoListParser();
+
+ /// @brief Performs the parsing of the given list "tsig_key" elements.
+ ///
+ /// It iterates over each key entry in the list:
+ /// 1. Instantiate a TSIGKeyInfoParser for the entry
+ /// 2. Pass the element configuration to the parser's build method
+ /// 3. Add the parser instance to local storage
+ ///
+ /// The net effect is to parse all of the key entries in the list
+ /// prepping them for commit.
+ ///
+ /// @param key_list_config is the list of "tsig_key" elements to parse.
+ virtual void build(isc::data::ConstElementPtr key_list_config);
+
+ /// @brief Iterates over the internal list of TSIGKeyInfoParsers,
+ /// invoking commit on each. This causes each parser to instantiate a
+ /// TSIGKeyInfo from its internal data values and add that that key
+ /// instance to the storage area, keys_.
+ virtual void commit();
+
+private:
+ /// @brief Arbitrary label assigned to this parser instance.
+ std::string list_name_;
+
+ /// @brief Pointer to the storage area to which the parser should commit
+ /// the list of newly created TSIGKeyInfo instances. This is given to us
+ /// as a constructor argument by an upper level.
+ TSIGKeyInfoMapPtr keys_;
+
+ /// @brief Local storage of TSIGKeyInfoParser instances
+ isc::dhcp::ParserCollection parsers_;
+};
+
+/// @brief Parser for DnsServerInfo
+///
+/// This class parses the configuration element "dns_server" defined in
+/// src/bin/d2/dhcp-ddns.spec and creates an instance of a DnsServerInfo.
+class DnsServerInfoParser : public isc::dhcp::DhcpConfigParser {
+public:
+ /// @brief Constructor
+ ///
+ /// @param entry_name is an arbitrary label assigned to this configuration
+ /// definition. Since servers are specified in a list this value is likely
+ /// be something akin to "server:0", set during parsing.
+ /// @param servers is a pointer to the storage area to which the parser
+ /// should commit the newly created DnsServerInfo instance.
+ DnsServerInfoParser(const std::string& entry_name,
+ DnsServerInfoStoragePtr servers);
+
+ /// @brief Destructor
+ virtual ~DnsServerInfoParser();
+
+ /// @brief Performs the actual parsing of the given "dns_server" element.
+ /// The results of the parsing are retained internally for use during
+ /// commit.
+ ///
+ /// @param server_config is the "dns_server" configuration to parse
+ virtual void build(isc::data::ConstElementPtr server_config);
+
+ /// @brief Creates a parser for the given "dns_server" member element id.
+ ///
+ /// The server elements currently supported are(see dhcp-ddns.spec):
+ /// 1. hostname
+ /// 2. ip_address
+ /// 3. port
+ ///
+ /// @param config_id is the "item_name" for a specific member element of
+ /// the "dns_server" specification.
+ ///
+ /// @return returns a pointer to newly created parser.
+ virtual isc::dhcp::ParserPtr createConfigParser(const std::string&
+ config_id);
+
+ /// @brief Instantiates a DnsServerInfo from internal data values
+ /// saves it to the storage area pointed to by servers_.
+ virtual void commit();
+
+private:
+ /// @brief Arbitrary label assigned to this parser instance.
+ /// Since servers are specified in a list this value is likely be something
+ /// akin to "server:0", set during parsing. Primarily here for diagnostics.
+ std::string entry_name_;
+
+ /// @brief Pointer to the storage area to which the parser should commit
+ /// the newly created DnsServerInfo instance. This is given to us as a
+ /// constructor argument by an upper level.
+ DnsServerInfoStoragePtr servers_;
+
+ /// @brief Local storage area for scalar parameter values. Use to hold
+ /// data until time to commit.
+ DScalarContext local_scalars_;
+};
+
+/// @brief Parser for a list of DnsServerInfos
+///
+/// This class parses a list of "dns_server" configuration elements.
+/// (see src/bin/d2/dhcp-ddns.spec). The DnsServerInfo instances are added
+/// to the given storage upon commit.
+class DnsServerInfoListParser : public isc::dhcp::DhcpConfigParser {
+public:
+
+ /// @brief Constructor
+ ///
+ /// @param list_name is an arbitrary label assigned to this parser instance.
+ /// @param servers is a pointer to the storage area to which the parser
+ /// should commit the newly created DnsServerInfo instance.
+ DnsServerInfoListParser(const std::string& list_name,
+ DnsServerInfoStoragePtr servers);
+
+ /// @brief Destructor
+ virtual ~DnsServerInfoListParser();
+
+ /// @brief Performs the actual parsing of the given list "dns_server"
+ /// elements.
+ /// It iterates over each server entry in the list:
+ /// 1. Instantiate a DnsServerInfoParser for the entry
+ /// 2. Pass the element configuration to the parser's build method
+ /// 3. Add the parser instance to local storage
+ ///
+ /// The net effect is to parse all of the server entries in the list
+ /// prepping them for commit.
+ ///
+ /// @param server_list_config is the list of "dns_server" elements to parse.
+ virtual void build(isc::data::ConstElementPtr server_list_config);
+
+ /// @brief Iterates over the internal list of DnsServerInfoParsers,
+ /// invoking commit on each. This causes each parser to instantiate a
+ /// DnsServerInfo from its internal data values and add that that server
+ /// instance to the storage area, servers_.
+ virtual void commit();
+
+private:
+ /// @brief Arbitrary label assigned to this parser instance.
+ std::string list_name_;
+
+ /// @brief Pointer to the storage area to which the parser should commit
+ /// the list of newly created DnsServerInfo instances. This is given to us
+ /// as a constructor argument by an upper level.
+ DnsServerInfoStoragePtr servers_;
+
+ /// @brief Local storage of DnsServerInfoParser instances
+ isc::dhcp::ParserCollection parsers_;
+};
+
+/// @brief Parser for DdnsDomain
+///
+/// This class parses the configuration element "ddns_domain" defined in
+/// src/bin/d2/dhcp-ddns.spec and creates an instance of a DdnsDomain.
+class DdnsDomainParser : public isc::dhcp::DhcpConfigParser {
+public:
+ /// @brief Constructor
+ ///
+ /// @param entry_name is an arbitrary label assigned to this configuration
+ /// definition. Since domains are specified in a list this value is likely
+ /// be something akin to "forward_ddns:0", set during parsing.
+ /// @param domains is a pointer to the storage area to which the parser
+ /// @param keys is a pointer to a map of the defined TSIG keys.
+ /// should commit the newly created DdnsDomain instance.
+ DdnsDomainParser(const std::string& entry_name, DdnsDomainMapPtr domains,
+ TSIGKeyInfoMapPtr keys);
+
+ /// @brief Destructor
+ virtual ~DdnsDomainParser();
+
+ /// @brief Performs the actual parsing of the given "ddns_domain" element.
+ /// The results of the parsing are retained internally for use during
+ /// commit.
+ ///
+ /// @param domain_config is the "ddns_domain" configuration to parse
+ virtual void build(isc::data::ConstElementPtr domain_config);
+
+ /// @brief Creates a parser for the given "ddns_domain" member element id.
+ ///
+ /// The domain elements currently supported are(see dhcp-ddns.spec):
+ /// 1. name
+ /// 2. key_name
+ /// 3. dns_servers
+ ///
+ /// @param config_id is the "item_name" for a specific member element of
+ /// the "ddns_domain" specification.
+ ///
+ /// @return returns a pointer to newly created parser.
+ virtual isc::dhcp::ParserPtr createConfigParser(const std::string&
+ config_id);
+
+ /// @brief Instantiates a DdnsDomain from internal data values
+ /// saves it to the storage area pointed to by domains_.
+ virtual void commit();
+
+private:
+
+ /// @brief Arbitrary label assigned to this parser instance.
+ std::string entry_name_;
+
+ /// @brief Pointer to the storage area to which the parser should commit
+ /// the newly created DdnsDomain instance. This is given to us as a
+ /// constructor argument by an upper level.
+ DdnsDomainMapPtr domains_;
+
+ /// @brief Pointer to the map of defined TSIG keys.
+ /// This map is passed into us and contains all of the TSIG keys defined
+ /// for this configuration. It is used to validate the key name entry of
+ /// DdnsDomains that specify one.
+ TSIGKeyInfoMapPtr keys_;
+
+ /// @brief Local storage for DnsServerInfo instances. This is passed into
+ /// DnsServerInfoListParser(s), which in turn passes it into each
+ /// DnsServerInfoParser. When the DnsServerInfoParsers "commit" they add
+ /// their server instance to this storage.
+ DnsServerInfoStoragePtr local_servers_;
+
+ /// @brief Local storage area for scalar parameter values. Use to hold
+ /// data until time to commit.
+ DScalarContext local_scalars_;
+};
+
+/// @brief Parser for a list of DdnsDomains
+///
+/// This class parses a list of "ddns_domain" configuration elements.
+/// (see src/bin/d2/dhcp-ddns.spec). The DdnsDomain instances are added
+/// to the given storage upon commit.
+class DdnsDomainListParser : public isc::dhcp::DhcpConfigParser {
+public:
+
+ /// @brief Constructor
+ ///
+ /// @param list_name is an arbitrary label assigned to this parser instance.
+ /// @param domains is a pointer to the storage area to which the parser
+ /// @param keys is a pointer to a map of the defined TSIG keys.
+ /// should commit the newly created DdnsDomain instance.
+ DdnsDomainListParser(const std::string& list_name,
+ DdnsDomainMapPtr domains, TSIGKeyInfoMapPtr keys);
+
+ /// @brief Destructor
+ virtual ~DdnsDomainListParser();
+
+ /// @brief Performs the actual parsing of the given list "ddns_domain"
+ /// elements.
+ /// It iterates over each server entry in the list:
+ /// 1. Instantiate a DdnsDomainParser for the entry
+ /// 2. Pass the element configuration to the parser's build method
+ /// 3. Add the parser instance to local storage
+ ///
+ /// The net effect is to parse all of the domain entries in the list
+ /// prepping them for commit.
+ ///
+ /// @param domain_list_config is the list of "ddns_domain" elements to
+ /// parse.
+ virtual void build(isc::data::ConstElementPtr domain_list_config);
+
+ /// @brief Iterates over the internal list of DdnsDomainParsers, invoking
+ /// commit on each. This causes each parser to instantiate a DdnsDomain
+ /// from its internal data values and add that domain instance to the
+ /// storage area, domains_.
+ virtual void commit();
+
+private:
+ /// @brief Arbitrary label assigned to this parser instance.
+ std::string list_name_;
+
+ /// @brief Pointer to the storage area to which the parser should commit
+ /// the list of newly created DdnsDomain instances. This is given to us
+ /// as a constructor argument by an upper level.
+ DdnsDomainMapPtr domains_;
+
+ /// @brief Pointer to the map of defined TSIG keys.
+ /// This map is passed into us and contains all of the TSIG keys defined
+ /// for this configuration. It is used to validate the key name entry of
+ /// DdnsDomains that specify one.
+ TSIGKeyInfoMapPtr keys_;
+
+ /// @brief Local storage of DdnsDomainParser instances
+ isc::dhcp::ParserCollection parsers_;
+};
+
+/// @brief Parser for DdnsDomainListMgr
+///
+/// This class parses the configuration elements "forward_ddns" and
+/// "reverse_ddns" as defined in src/bin/d2/dhcp-ddns.spec. It populates the
+/// given DdnsDomainListMgr with parsed information upon commit. Note that
+/// unlike other parsers, this parser does NOT instantiate the final object
+/// during the commit phase, it populates it. It must pre-exist.
+class DdnsDomainListMgrParser : public isc::dhcp::DhcpConfigParser {
+public:
+ /// @brief Constructor
+ ///
+ /// @param entry_name is an arbitrary label assigned to this configuration
+ /// definition.
+ /// @param mgr is a pointer to the DdnsDomainListMgr to populate.
+ /// @param keys is a pointer to a map of the defined TSIG keys.
+ /// @throw throws D2CfgError if mgr pointer is empty.
+ DdnsDomainListMgrParser(const std::string& entry_name,
+ DdnsDomainListMgrPtr mgr, TSIGKeyInfoMapPtr keys);
+
+ /// @brief Destructor
+ virtual ~DdnsDomainListMgrParser();
+
+ /// @brief Performs the actual parsing of the given manager element.
+ /// The results of the parsing are retained internally for use during
+ /// commit.
+ ///
+ /// @param mgr_config is the manager configuration to parse
+ virtual void build(isc::data::ConstElementPtr mgr_config);
+
+ /// @brief Creates a parser for the given manager member element id.
+ ///
+ /// The manager elements currently supported are (see dhcp-ddns.spec):
+ /// 1. ddns_domains
+ ///
+ /// @param config_id is the "item_name" for a specific member element of
+ /// the manager specification.
+ ///
+ /// @return returns a pointer to newly created parser.
+ virtual isc::dhcp::ParserPtr createConfigParser(const std::string&
+ config_id);
+
+ /// @brief Populates the DdnsDomainListMgr from internal data values
+ /// set during parsing.
+ virtual void commit();
+
+private:
+ /// @brief Arbitrary label assigned to this parser instance.
+ std::string entry_name_;
+
+ /// @brief Pointer to manager instance to which the parser should commit
+ /// the parsed data. This is given to us as a constructor argument by an
+ /// upper level.
+ DdnsDomainListMgrPtr mgr_;
+
+ /// @brief Pointer to the map of defined TSIG keys.
+ /// This map is passed into us and contains all of the TSIG keys defined
+ /// for this configuration. It is used to validate the key name entry of
+ /// DdnsDomains that specify one.
+ TSIGKeyInfoMapPtr keys_;
+
+ /// @brief Local storage for DdnsDomain instances. This is passed into a
+ /// DdnsDomainListParser(s), which in turn passes it into each
+ /// DdnsDomainParser. When the DdnsDomainParsers "commit" they add their
+ /// domain instance to this storage.
+ DdnsDomainMapPtr local_domains_;
+
+ /// @brief Local storage area for scalar parameter values. Use to hold
+ /// data until time to commit.
+ /// @todo Currently, the manager has no scalars but this is likely to
+ /// change as matching capabilities expand.
+ DScalarContext local_scalars_;
+};
+
+
+}; // end of isc::d2 namespace
+}; // end of isc namespace
+
+#endif // D2_CONFIG_H
diff --git a/src/bin/d2/d2_controller.h b/src/bin/d2/d2_controller.h
index 5ee15b1..0290f87 100644
--- a/src/bin/d2/d2_controller.h
+++ b/src/bin/d2/d2_controller.h
@@ -24,7 +24,7 @@ namespace d2 {
/// This class is the DHCP-DDNS specific derivation of DControllerBase. It
/// creates and manages an instance of the DHCP-DDNS application process,
/// D2Process.
-/// @TODO Currently, this class provides only the minimum required specialized
+/// @todo Currently, this class provides only the minimum required specialized
/// behavior to run the DHCP-DDNS service. It may very well expand as the
/// service implementation evolves. Some thought was given to making
/// DControllerBase a templated class but the labor savings versus the
diff --git a/src/bin/d2/d2_messages.mes b/src/bin/d2/d2_messages.mes
index 27778dc..5077e62 100644
--- a/src/bin/d2/d2_messages.mes
+++ b/src/bin/d2/d2_messages.mes
@@ -26,11 +26,22 @@ to establish a session with the BIND10 control channel.
A debug message listing the command (and possible arguments) received
from the BIND10 control system by the controller.
+% DCTL_CONFIG_COMPLETE server has completed configuration: %1
+This is an informational message announcing the successful processing of a
+new configuration. It is output during server startup, and when an updated
+configuration is committed by the administrator. Additional information
+may be provided.
+
% DCTL_CONFIG_LOAD_FAIL %1 configuration failed to load: %2
This critical error message indicates that the initial application
configuration has failed. The service will start, but will not
process requests until the configuration has been corrected.
+% DCTL_CONFIG_START parsing new configuration: %1
+A debug message indicating that the application process has received an
+updated configuration and has passed it to its configuration manager
+for parsing.
+
% DCTL_CONFIG_STUB %1 configuration stub handler called
This debug message is issued when the dummy handler for configuration
events is called. This only happens during initial startup.
@@ -55,7 +66,24 @@ application and will exit.
% DCTL_NOT_RUNNING %1 application instance is not running
A warning message is issued when an attempt is made to shut down the
-the application when it is not running.
+application when it is not running.
+
+% DCTL_ORDER_ERROR configuration contains more elements than the parsing order
+An error message which indicates that configuration being parsed includes
+element ids not specified the configuration manager's parse order list. This
+is a programmatic error.
+
+% DCTL_ORDER_NO_ELEMENT element: %1 is in the parsing order but is missing from the configuration
+An error message output during a configuration update. The program is
+expecting an item but has not found it in the new configuration. This may
+mean that the BIND 10 configuration database is corrupt.
+
+% DCTL_PARSER_FAIL configuration parsing failed for configuration element: %1
+On receipt of message containing details to a change of its configuration,
+the server failed to create a parser to decode the contents of the named
+configuration element, or the creation succeeded but the parsing actions
+and committal of changes failed. The reason for the failure is given in
+the message.
% DCTL_PROCESS_FAILED %1 application execution failed: %2
The controller has encountered a fatal error while running the
@@ -96,6 +124,11 @@ has been invoked.
This is a debug message issued when the Dhcp-Ddns application encounters an
unrecoverable error from within the event loop.
+% DHCP_DDNS_NO_MATCH No DNS servers match FQDN: %1
+This is warning message issued when there are no domains in the configuration
+which match the cited fully qualified domain name (FQDN). The DNS Update
+request for the FQDN cannot be processed.
+
% DHCP_DDNS_PROCESS_INIT application init invoked
This is a debug message issued when the Dhcp-Ddns application enters
its init method.
diff --git a/src/bin/d2/d2_process.cc b/src/bin/d2/d2_process.cc
index 130bcc1..44575a3 100644
--- a/src/bin/d2/d2_process.cc
+++ b/src/bin/d2/d2_process.cc
@@ -14,6 +14,7 @@
#include <config/ccsession.h>
#include <d2/d2_log.h>
+#include <d2/d2_cfg_mgr.h>
#include <d2/d2_process.h>
using namespace asio;
@@ -22,7 +23,7 @@ namespace isc {
namespace d2 {
D2Process::D2Process(const char* name, IOServicePtr io_service)
- : DProcessBase(name, io_service) {
+ : DProcessBase(name, io_service, DCfgMgrBasePtr(new D2CfgMgr())) {
};
void
@@ -60,19 +61,19 @@ D2Process::shutdown() {
isc::data::ConstElementPtr
D2Process::configure(isc::data::ConstElementPtr config_set) {
- // @TODO This is the initial implementation which simply accepts
- // any content in config_set as valid. This is sufficient to
- // allow participation as a BIND10 module, while D2 configuration support
- // is being developed.
+ // @todo This is the initial implementation passes the configuration onto
+ // the D2CfgMgr. There may be additional steps taken added to handle
+ // configuration changes but for now, assume that D2CfgMgr is handling it
+ // all.
LOG_DEBUG(dctl_logger, DBGLVL_TRACE_BASIC,
DHCP_DDNS_CONFIGURE).arg(config_set->str());
- return (isc::config::createAnswer(0, "Configuration accepted."));
+ return (getCfgMgr()->parseConfig(config_set));
}
isc::data::ConstElementPtr
D2Process::command(const std::string& command, isc::data::ConstElementPtr args){
- // @TODO This is the initial implementation. If and when D2 is extended
+ // @todo This is the initial implementation. If and when D2 is extended
// to support its own commands, this implementation must change. Otherwise
// it should reject all commands as it does now.
LOG_DEBUG(dctl_logger, DBGLVL_TRACE_BASIC,
diff --git a/src/bin/d2/d2_process.h b/src/bin/d2/d2_process.h
index 4ddf8be..0055564 100644
--- a/src/bin/d2/d2_process.h
+++ b/src/bin/d2/d2_process.h
@@ -41,7 +41,7 @@ public:
D2Process(const char* name, IOServicePtr io_service);
/// @brief Will be used after instantiation to perform initialization
- /// unique to D2. @TODO This will likely include interactions with
+ /// unique to D2. @todo This will likely include interactions with
/// QueueMgr and UpdateMgr, to prepare for request receipt and processing.
/// Current implementation successfully does nothing.
/// @throw throws a DProcessBaseError if the initialization fails.
@@ -58,7 +58,7 @@ public:
/// @brief Implements the process's shutdown processing. When invoked, it
/// should ensure that the process gracefully exits the run method.
/// Current implementation simply sets the shutdown flag monitored by the
- /// run method. @TODO this may need to expand as the implementation evolves.
+ /// run method. @todo this may need to expand as the implementation evolves.
/// @throw throws a DProcessBaseError if an error is encountered.
virtual void shutdown();
diff --git a/src/bin/d2/d_cfg_mgr.cc b/src/bin/d2/d_cfg_mgr.cc
new file mode 100644
index 0000000..a8394a9
--- /dev/null
+++ b/src/bin/d2/d_cfg_mgr.cc
@@ -0,0 +1,240 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config/ccsession.h>
+#include <d2/d2_log.h>
+#include <dhcp/libdhcp++.h>
+#include <d2/d_cfg_mgr.h>
+#include <dhcpsrv/dhcp_parsers.h>
+#include <util/encode/hex.h>
+#include <util/strutil.h>
+
+#include <boost/foreach.hpp>
+#include <boost/lexical_cast.hpp>
+#include <boost/algorithm/string.hpp>
+
+#include <limits>
+#include <iostream>
+#include <vector>
+#include <map>
+
+using namespace std;
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::data;
+using namespace isc::asiolink;
+
+namespace isc {
+namespace d2 {
+
+// *********************** DCfgContextBase *************************
+
+DCfgContextBase::DCfgContextBase():
+ boolean_values_(new BooleanStorage()),
+ uint32_values_(new Uint32Storage()),
+ string_values_(new StringStorage()) {
+ }
+
+DCfgContextBase::DCfgContextBase(const DCfgContextBase& rhs):
+ boolean_values_(new BooleanStorage(*(rhs.boolean_values_))),
+ uint32_values_(new Uint32Storage(*(rhs.uint32_values_))),
+ string_values_(new StringStorage(*(rhs.string_values_))) {
+}
+
+void
+DCfgContextBase::getParam(const std::string& name, bool& value, bool optional) {
+ try {
+ value = boolean_values_->getParam(name);
+ } catch (DhcpConfigError& ex) {
+ // If the parameter is not optional, re-throw the exception.
+ if (!optional) {
+ throw;
+ }
+ }
+}
+
+
+void
+DCfgContextBase::getParam(const std::string& name, uint32_t& value,
+ bool optional) {
+ try {
+ value = uint32_values_->getParam(name);
+ } catch (DhcpConfigError& ex) {
+ // If the parameter is not optional, re-throw the exception.
+ if (!optional) {
+ throw;
+ }
+ }
+}
+
+void
+DCfgContextBase::getParam(const std::string& name, std::string& value,
+ bool optional) {
+ try {
+ value = string_values_->getParam(name);
+ } catch (DhcpConfigError& ex) {
+ // If the parameter is not optional, re-throw the exception.
+ if (!optional) {
+ throw;
+ }
+ }
+}
+
+DCfgContextBase::~DCfgContextBase() {
+}
+
+// *********************** DCfgMgrBase *************************
+
+DCfgMgrBase::DCfgMgrBase(DCfgContextBasePtr context)
+ : parse_order_(), context_(context) {
+ if (!context_) {
+ isc_throw(DCfgMgrBaseError, "DCfgMgrBase ctor: context cannot be NULL");
+ }
+}
+
+DCfgMgrBase::~DCfgMgrBase() {
+}
+
+isc::data::ConstElementPtr
+DCfgMgrBase::parseConfig(isc::data::ConstElementPtr config_set) {
+ LOG_DEBUG(dctl_logger, DBGLVL_COMMAND,
+ DCTL_CONFIG_START).arg(config_set->str());
+
+ if (!config_set) {
+ return (isc::config::createAnswer(1,
+ std::string("Can't parse NULL config")));
+ }
+
+ // The parsers implement data inheritance by directly accessing
+ // configuration context. For this reason the data parsers must store
+ // the parsed data into context immediately. This may cause data
+ // inconsistency if the parsing operation fails after the context has been
+ // modified. We need to preserve the original context here
+ // so as we can rollback changes when an error occurs.
+ DCfgContextBasePtr original_context = context_->clone();
+
+ // Answer will hold the result returned to the caller.
+ ConstElementPtr answer;
+
+ // Holds the name of the element being parsed.
+ std::string element_id;
+
+ try {
+ // Grab a map of element_ids and their data values from the new
+ // configuration set.
+ const std::map<std::string, ConstElementPtr>& values_map =
+ config_set->mapValue();
+
+ // Use a pre-ordered list of element ids to parse the elements in a
+ // specific order if the list (parser_order_) is not empty; otherwise
+ // elements are parsed in the order the value_map presents them.
+
+ if (parse_order_.size() > 0) {
+ // For each element_id in the parse order list, look for it in the
+ // value map. If the element exists in the map, pass it and it's
+ // associated data in for parsing.
+ // If there is no matching entry in the value map an error is
+ // thrown. Note, that elements tagged as "optional" from the user
+ // perspective must still have default or empty entries in the
+ // configuration set to be parsed.
+ int parsed_count = 0;
+ std::map<std::string, ConstElementPtr>::const_iterator it;
+ BOOST_FOREACH(element_id, parse_order_) {
+ it = values_map.find(element_id);
+ if (it != values_map.end()) {
+ ++parsed_count;
+ buildAndCommit(element_id, it->second);
+ }
+ else {
+ LOG_ERROR(dctl_logger, DCTL_ORDER_NO_ELEMENT)
+ .arg(element_id);
+ isc_throw(DCfgMgrBaseError, "Element:" << element_id <<
+ " is listed in the parse order but is not "
+ " present in the configuration");
+ }
+ }
+
+ // NOTE: When using ordered parsing, the parse order list MUST
+ // include every possible element id that the value_map may contain.
+ // Entries in the map that are not in the parse order, would not be
+ // parsed. For now we will flag this as a programmatic error. One
+ // could attempt to adjust for this, by identifying such entries
+ // and parsing them either first or last but which would be correct?
+ // Better to hold the engineer accountable. So, if we parsed none
+ // or we parsed fewer than are in the map; then either the parse i
+ // order is incomplete OR the map has unsupported values.
+ if (!parsed_count ||
+ (parsed_count && ((parsed_count + 1) < values_map.size()))) {
+ LOG_ERROR(dctl_logger, DCTL_ORDER_ERROR);
+ isc_throw(DCfgMgrBaseError,
+ "Configuration contains elements not in parse order");
+ }
+ } else {
+ // Order doesn't matter so iterate over the value map directly.
+ // Pass each element and it's associated data in to be parsed.
+ ConfigPair config_pair;
+ BOOST_FOREACH(config_pair, values_map) {
+ element_id = config_pair.first;
+ buildAndCommit(element_id, config_pair.second);
+ }
+ }
+
+ // Everything was fine. Configuration set processed successfully.
+ LOG_INFO(dctl_logger, DCTL_CONFIG_COMPLETE).arg("");
+ answer = isc::config::createAnswer(0, "Configuration committed.");
+
+ } catch (const isc::Exception& ex) {
+ LOG_ERROR(dctl_logger, DCTL_PARSER_FAIL).arg(element_id).arg(ex.what());
+ answer = isc::config::createAnswer(1,
+ string("Configuration parsing failed: ") + ex.what());
+
+ // An error occurred, so make sure that we restore original context.
+ context_ = original_context;
+ return (answer);
+ }
+
+ return (answer);
+}
+
+void DCfgMgrBase::buildAndCommit(std::string& element_id,
+ isc::data::ConstElementPtr value) {
+ // Call derivation's implementation to create the appropriate parser
+ // based on the element id.
+ ParserPtr parser = createConfigParser(element_id);
+ if (!parser) {
+ isc_throw(DCfgMgrBaseError, "Could not create parser");
+ }
+
+ try {
+ // Invoke the parser's build method passing in the value. This will
+ // "convert" the Element form of value into the actual data item(s)
+ // and store them in parser's local storage.
+ parser->build(value);
+
+ // Invoke the parser's commit method. This "writes" the the data
+ // item(s) stored locally by the parser into the context. (Note that
+ // parsers are free to do more than update the context, but that is an
+ // nothing something we are concerned with here.)
+ parser->commit();
+ } catch (const isc::Exception& ex) {
+ isc_throw(DCfgMgrBaseError,
+ "Could not build and commit: " << ex.what());
+ } catch (...) {
+ isc_throw(DCfgMgrBaseError, "Non-ISC exception occurred");
+ }
+}
+
+}; // end of isc::dhcp namespace
+}; // end of isc namespace
+
diff --git a/src/bin/d2/d_cfg_mgr.h b/src/bin/d2/d_cfg_mgr.h
new file mode 100644
index 0000000..a0bd9bb
--- /dev/null
+++ b/src/bin/d2/d_cfg_mgr.h
@@ -0,0 +1,331 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef D_CFG_MGR_H
+#define D_CFG_MGR_H
+
+#include <cc/data.h>
+#include <exceptions/exceptions.h>
+#include <dhcpsrv/dhcp_parsers.h>
+
+#include <stdint.h>
+#include <string>
+
+namespace isc {
+namespace d2 {
+
+/// @brief Exception thrown if the configuration manager encounters an error.
+class DCfgMgrBaseError : public isc::Exception {
+public:
+ DCfgMgrBaseError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+class DCfgContextBase;
+/// @brief Pointer to a configuration context.
+typedef boost::shared_ptr<DCfgContextBase> DCfgContextBasePtr;
+
+/// @brief Abstract class that implements a container for configuration context.
+/// It provides a single enclosure for the storage of configuration parameters
+/// and any other context specific information that needs to be accessible
+/// during configuration parsing as well as to the application as a whole.
+/// The base class supports storage for a small set of simple data types.
+/// Derivations simply add additional storage as needed. Note that this class
+/// declares the pure virtual clone() method, its copy constructor is protected,
+/// and its copy operator is inaccessible. Derivations must supply an
+/// implementation of clone that calls the base class copy constructor.
+/// This allows the management class to perform context backup and restoration
+/// without derivation specific knowledge using logic like
+/// the following:
+///
+/// // Make a backup copy
+/// DCfgContextBasePtr backup_copy(context_->clone());
+/// :
+/// // Restore from backup
+/// context_ = backup_copy;
+///
+class DCfgContextBase {
+public:
+ /// @brief Indicator that a configuration parameter is optional.
+ static const bool OPTIONAL = true;
+ static const bool REQUIRED = false;
+
+ /// @brief Constructor
+ DCfgContextBase();
+
+ /// @brief Destructor
+ virtual ~DCfgContextBase();
+
+ /// @brief Fetches the value for a given boolean configuration parameter
+ /// from the context.
+ ///
+ /// @param name is the name of the parameter to retrieve.
+ /// @param value is an output parameter in which to return the retrieved
+ /// value.
+ /// @param optional if true, the parameter is optional and the method
+ /// will not throw if the parameter is not found in the context. The
+ /// contents of the output parameter, value, will not be altered.
+ /// It defaults to false if not specified.
+ /// @throw throws DhcpConfigError if the context does not contain the
+ /// parameter and optional is false.
+ void getParam(const std::string& name, bool& value, bool optional=false);
+
+ /// @brief Fetches the value for a given uint32_t configuration parameter
+ /// from the context.
+ ///
+ /// @param name is the name of the parameter to retrieve.
+ /// @param value is an output parameter in which to return the retrieved
+ /// value.
+ /// @param optional if true, the parameter is optional and the method
+ /// will not throw if the parameter is not found in the context. The
+ /// contents of the output parameter, value, will not be altered.
+ /// @throw throws DhcpConfigError if the context does not contain the
+ /// parameter and optional is false.
+ void getParam(const std::string& name, uint32_t& value,
+ bool optional=false);
+
+ /// @brief Fetches the value for a given string configuration parameter
+ /// from the context.
+ ///
+ /// @param name is the name of the parameter to retrieve.
+ /// @param value is an output parameter in which to return the retrieved
+ /// value.
+ /// @param optional if true, the parameter is optional and the method
+ /// will not throw if the parameter is not found in the context. The
+ /// contents of the output parameter, value, will not be altered.
+ /// @throw throws DhcpConfigError if the context does not contain the
+ /// parameter and optional is false.
+ void getParam(const std::string& name, std::string& value,
+ bool optional=false);
+
+ /// @brief Fetches the Boolean Storage. Typically used for passing
+ /// into parsers.
+ ///
+ /// @return returns a pointer to the Boolean Storage.
+ isc::dhcp::BooleanStoragePtr getBooleanStorage() {
+ return (boolean_values_);
+ }
+
+ /// @brief Fetches the uint32 Storage. Typically used for passing
+ /// into parsers.
+ ///
+ /// @return returns a pointer to the uint32 Storage.
+ isc::dhcp::Uint32StoragePtr getUint32Storage() {
+ return (uint32_values_);
+ }
+
+ /// @brief Fetches the string Storage. Typically used for passing
+ /// into parsers.
+ ///
+ /// @return returns a pointer to the string Storage.
+ isc::dhcp::StringStoragePtr getStringStorage() {
+ return (string_values_);
+ }
+
+ /// @brief Creates a clone of this context object.
+ ///
+ /// As mentioned in the the class brief, derivation must supply an
+ /// implementation that initializes the base class storage as well as its
+ /// own. Typically the derivation's clone method would return the result
+ /// of passing "*this" into its own copy constructor:
+ ///
+ /// @code
+ /// class DStubContext : public DCfgContextBase {
+ /// public:
+ /// :
+ /// // Clone calls its own copy constructor
+ /// virtual DCfgContextBasePtr clone() {
+ /// return (DCfgContextBasePtr(new DStubContext(*this)));
+ /// }
+ ///
+ /// // Note that the copy constructor calls the base class copy ctor
+ /// // then initializes its additional storage.
+ /// DStubContext(const DStubContext& rhs) : DCfgContextBase(rhs),
+ /// extra_values_(new Uint32Storage(*(rhs.extra_values_))) {
+ /// }
+ /// :
+ /// // Here's the derivation's additional storage.
+ /// isc::dhcp::Uint32StoragePtr extra_values_;
+ /// :
+ /// @endcode
+ ///
+ /// @return returns a pointer to the new clone.
+ virtual DCfgContextBasePtr clone() = 0;
+
+protected:
+ /// @brief Copy constructor for use by derivations in clone().
+ DCfgContextBase(const DCfgContextBase& rhs);
+
+private:
+ /// @brief Private assignment operator to avoid potential for slicing.
+ DCfgContextBase& operator=(const DCfgContextBase& rhs);
+
+ /// @brief Storage for boolean parameters.
+ isc::dhcp::BooleanStoragePtr boolean_values_;
+
+ /// @brief Storage for uint32 parameters.
+ isc::dhcp::Uint32StoragePtr uint32_values_;
+
+ /// @brief Storage for string parameters.
+ isc::dhcp::StringStoragePtr string_values_;
+};
+
+/// @brief Defines an unsorted, list of string Element IDs.
+typedef std::vector<std::string> ElementIdList;
+
+/// @brief Configuration Manager
+///
+/// DCfgMgrBase is an abstract class that provides the mechanisms for managing
+/// an application's configuration. This includes services for parsing sets of
+/// configuration values, storing the parsed information in its converted form,
+/// and retrieving the information on demand. It is intended to be the worker
+/// class which is handed a set of configuration values to process by upper
+/// application management layers.
+///
+/// The class presents a public method for receiving new configurations,
+/// parseConfig. This method coordinates the parsing effort as follows:
+///
+/// @code
+/// make backup copy of configuration context
+/// for each top level element in new configuration
+/// get derivation-specific parser for element
+/// run parser
+/// update context with parsed results
+/// break on error
+///
+/// if an error occurred
+/// restore configuration context from backup
+/// @endcode
+///
+/// After making a backup of the current context, it iterates over the top-level
+/// elements in the new configuration. The order in which the elements are
+/// processed is either:
+///
+/// 1. Natural order presented by the configuration set
+/// 2. Specific order determined by a list of element ids
+///
+/// This allows a derivation to specify the order in which its elements are
+/// parsed if there are dependencies between elements.
+///
+/// To parse a given element, its id is passed into createConfigParser,
+/// which returns an instance of the appropriate parser. This method is
+/// abstract so the derivation's implementation determines the type of parser
+/// created. This isolates the knowledge of specific element ids and which
+/// application specific parsers to derivation.
+///
+/// Once the parser has been created, it is used to parse the data value
+/// associated with the element id and update the context with the parsed
+/// results.
+///
+/// In the event that an error occurs, parsing is halted and the
+/// configuration context is restored from backup.
+class DCfgMgrBase {
+public:
+ /// @brief Constructor
+ ///
+ /// @param context is a pointer to the configuration context the manager
+ /// will use for storing parsed results.
+ ///
+ /// @throw throws DCfgMgrBaseError if context is null
+ DCfgMgrBase(DCfgContextBasePtr context);
+
+ /// @brief Destructor
+ virtual ~DCfgMgrBase();
+
+ /// @brief Acts as the receiver of new configurations and coordinates
+ /// the parsing as described in the class brief.
+ ///
+ /// @param config_set is a set of configuration elements to parsed.
+ ///
+ /// @return an Element that contains the results of configuration composed
+ /// of an integer status value (0 means successful, non-zero means failure),
+ /// and a string explanation of the outcome.
+ isc::data::ConstElementPtr parseConfig(isc::data::ConstElementPtr
+ config_set);
+
+ /// @brief Adds a given element id to the end of the parse order list.
+ ///
+ /// The order in which elements are retrieved from this is the order in
+ /// which they are added to the list. Derivations should use this method
+ /// to populate the parse order as part of their constructor.
+ ///
+ /// @param element_id is the string name of the element as it will appear
+ /// in the configuration set.
+ void addToParseOrder(const std::string& element_id){
+ parse_order_.push_back(element_id);
+ }
+
+ /// @brief Fetches the parse order list.
+ ///
+ /// @return returns a const reference to the list.
+ const ElementIdList& getParseOrder() const {
+ return (parse_order_);
+ }
+
+ /// @brief Fetches the configuration context.
+ ///
+ /// @return returns a pointer reference to the configuration context.
+ DCfgContextBasePtr& getContext() {
+ return (context_);
+ }
+
+protected:
+ /// @brief Create a parser instance based on an element id.
+ ///
+ /// Given an element_id returns an instance of the appropriate parser.
+ /// This method is abstract, isolating any direct knowledge of element_ids
+ /// and parsers to within the application-specific derivation.
+ ///
+ /// @param element_id is the string name of the element as it will appear
+ /// in the configuration set.
+ ///
+ /// @return returns a ParserPtr to the parser instance.
+ /// @throw throws DCfgMgrBaseError if an error occurs.
+ virtual isc::dhcp::ParserPtr
+ createConfigParser(const std::string& element_id) = 0;
+
+private:
+
+ /// @brief Parse a configuration element.
+ ///
+ /// Given an element_id and data value, instantiate the appropriate
+ /// parser, parse the data value, and commit the results.
+ ///
+ /// @param element_id is the string name of the element as it will appear
+ /// in the configuration set.
+ /// @param value is the data value to be parsed and associated with
+ /// element_id.
+ ///
+ /// @throw throws DCfgMgrBaseError if an error occurs.
+ void buildAndCommit(std::string& element_id,
+ isc::data::ConstElementPtr value);
+
+ /// @brief A list of element ids which specifies the element parsing order.
+ ///
+ /// If the list is empty, the natural order in the configuration set
+ /// it used.
+ ElementIdList parse_order_;
+
+ /// @brief Pointer to the configuration context instance.
+ DCfgContextBasePtr context_;
+};
+
+/// @brief Defines a shared pointer to DCfgMgrBase.
+typedef boost::shared_ptr<DCfgMgrBase> DCfgMgrBasePtr;
+
+
+}; // end of isc::d2 namespace
+}; // end of isc namespace
+
+#endif // D_CFG_MGR_H
diff --git a/src/bin/d2/d_controller.cc b/src/bin/d2/d_controller.cc
index b32fc45..921a07c 100644
--- a/src/bin/d2/d_controller.cc
+++ b/src/bin/d2/d_controller.cc
@@ -305,7 +305,7 @@ isc::data::ConstElementPtr
DControllerBase::updateConfig(isc::data::ConstElementPtr new_config) {
isc::data::ConstElementPtr full_config;
if (stand_alone_) {
- // @TODO Until there is a configuration manager to provide retrieval
+ // @todo Until there is a configuration manager to provide retrieval
// we'll just assume the incoming config is the full configuration set.
// It may also make more sense to isolate the controller from the
// configuration manager entirely. We could do something like
diff --git a/src/bin/d2/d_controller.h b/src/bin/d2/d_controller.h
index bf7a607..bdb02d5 100644
--- a/src/bin/d2/d_controller.h
+++ b/src/bin/d2/d_controller.h
@@ -171,7 +171,7 @@ public:
/// various configuration values. Installing the dummy handler
/// that guarantees to return success causes initial configuration
/// to be stored for the session being created and that it can
- /// be later accessed with \ref isc::ConfigData::getFullConfig.
+ /// be later accessed with \ref isc::config::ConfigData::getFullConfig.
///
/// @param new_config new configuration.
///
@@ -210,7 +210,7 @@ public:
/// implementation will merge the configuration update into the existing
/// configuration and then invoke the application process' configure method.
///
- /// @TODO This implementation is will evolve as the D2 configuration
+ /// @todo This implementation is will evolve as the D2 configuration
/// management task is implemented (trac #2957).
///
/// @param new_config is the new configuration
@@ -261,7 +261,7 @@ protected:
///
/// @param option is the option "character" from the command line, without
/// any prefixing hyphen(s)
- /// @optarg optarg is the argument value (if one) associated with the option
+ /// @param optarg is the argument value (if one) associated with the option
///
/// @return must return true if the option was valid, false is it is
/// invalid. (Note the default implementation always returns false.)
@@ -395,7 +395,7 @@ protected:
/// @brief Setter for setting the name of the controller's BIND10 spec file.
///
- /// @param value is the file name string.
+ /// @param spec_file_name the file name string.
void setSpecFileName(const std::string& spec_file_name) {
spec_file_name_ = spec_file_name;
}
diff --git a/src/bin/d2/d_process.h b/src/bin/d2/d_process.h
index 11b0a09..d1a7a55 100644
--- a/src/bin/d2/d_process.h
+++ b/src/bin/d2/d_process.h
@@ -17,6 +17,8 @@
#include <asiolink/asiolink.h>
#include <cc/data.h>
+#include <d2/d_cfg_mgr.h>
+
#include <boost/shared_ptr.hpp>
#include <exceptions/exceptions.h>
@@ -58,13 +60,21 @@ public:
/// in log statements, but otherwise arbitrary.
/// @param io_service is the io_service used by the caller for
/// asynchronous event handling.
+ /// @param cfg_mgr the configuration manager instance that handles
+ /// configuration parsing.
///
/// @throw DProcessBaseError is io_service is NULL.
- DProcessBase(const char* app_name, IOServicePtr io_service)
- : app_name_(app_name), io_service_(io_service), shut_down_flag_(false) {
+ DProcessBase(const char* app_name, IOServicePtr io_service,
+ DCfgMgrBasePtr cfg_mgr)
+ : app_name_(app_name), io_service_(io_service), shut_down_flag_(false),
+ cfg_mgr_(cfg_mgr) {
if (!io_service_) {
isc_throw (DProcessBaseError, "IO Service cannot be null");
}
+
+ if (!cfg_mgr_) {
+ isc_throw (DProcessBaseError, "CfgMgr cannot be null");
+ }
};
/// @brief May be used after instantiation to perform initialization unique
@@ -159,6 +169,13 @@ public:
io_service_->stop();
}
+ /// @brief Fetches the process's configuration manager.
+ ///
+ /// @return returns a reference to the configuration manager.
+ DCfgMgrBasePtr& getCfgMgr() {
+ return (cfg_mgr_);
+ }
+
private:
/// @brief Text label for the process. Generally used in log statements,
/// but otherwise can be arbitrary.
@@ -169,6 +186,9 @@ private:
/// @brief Boolean flag set when shutdown has been requested.
bool shut_down_flag_;
+
+ /// @brief Pointer to the configuration manager.
+ DCfgMgrBasePtr cfg_mgr_;
};
/// @brief Defines a shared pointer to DProcessBase.
diff --git a/src/bin/d2/dhcp-ddns.spec b/src/bin/d2/dhcp-ddns.spec
index 1098ada..e29bc88 100644
--- a/src/bin/d2/dhcp-ddns.spec
+++ b/src/bin/d2/dhcp-ddns.spec
@@ -1,21 +1,205 @@
+{
+"module_spec":
{
- "module_spec": {
"module_name": "DhcpDdns",
"module_description": "DHPC-DDNS Service",
"config_data": [
- ],
+ {
+ "item_name": "interface",
+ "item_type": "string",
+ "item_optional": true,
+ "item_default": "eth0"
+ },
+
+ {
+ "item_name": "ip_address",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": "127.0.0.1"
+ },
+
+ {
+ "item_name": "port",
+ "item_type": "integer",
+ "item_optional": true,
+ "item_default": 51771
+ },
+ {
+ "item_name": "tsig_keys",
+ "item_type": "list",
+ "item_optional": true,
+ "item_default": [],
+ "list_item_spec":
+ {
+ "item_name": "tsig_key",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": {"algorithm" : "hmac_md5"},
+ "map_item_spec": [
+ {
+ "item_name": "name",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": ""
+ },
+ {
+ "item_name": "algorithm",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": ""
+ },
+ {
+ "item_name": "secret",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": ""
+ }]
+ }
+ },
+ {
+ "item_name": "forward_ddns",
+ "item_type": "map",
+ "item_optional": true,
+ "item_default": {},
+ "map_item_spec": [
+ {
+ "item_name": "ddns_domains",
+ "item_type": "list",
+ "item_optional": false,
+ "item_default": [],
+ "list_item_spec":
+ {
+ "item_name": "ddns_domain",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": {},
+ "map_item_spec": [
+ {
+ "item_name": "name",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": ""
+ },
+
+ {
+ "item_name": "key_name",
+ "item_type": "string",
+ "item_optional": true,
+ "item_default": ""
+ },
+
+ {
+ "item_name": "dns_servers",
+ "item_type": "list",
+ "item_optional": false,
+ "item_default": [],
+ "list_item_spec":
+ {
+ "item_name": "dns_server",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": {},
+ "map_item_spec": [
+ {
+ "item_name": "hostname",
+ "item_type": "string",
+ "item_optional": true,
+ "item_default": ""
+ },
+ {
+ "item_name": "ip_address",
+ "item_type": "string",
+ "item_optional": true,
+ "item_default": ""
+ },
+ {
+ "item_name": "port",
+ "item_type": "integer",
+ "item_optional": true,
+ "item_default": 53
+ }]
+ }
+ }]
+ }
+ }]
+ },
+
+ {
+ "item_name": "reverse_ddns",
+ "item_type": "map",
+ "item_optional": true,
+ "item_default": {},
+ "map_item_spec": [
+ {
+ "item_name": "ddns_domains",
+ "item_type": "list",
+ "item_optional": false,
+ "item_default": [],
+ "list_item_spec":
+ {
+ "item_name": "ddns_domain",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": {},
+ "map_item_spec": [
+ {
+ "item_name": "name",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": ""
+ },
+
+ {
+ "item_name": "key_name",
+ "item_type": "string",
+ "item_optional": true,
+ "item_default": ""
+ },
+
+ {
+ "item_name": "dns_servers",
+ "item_type": "list",
+ "item_optional": false,
+ "item_default": [],
+ "list_item_spec":
+ {
+ "item_name": "dns_server",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": {},
+ "map_item_spec": [
+ {
+ "item_name": "hostname",
+ "item_type": "string",
+ "item_optional": true,
+ "item_default": ""
+ },
+ {
+ "item_name": "ip_address",
+ "item_type": "string",
+ "item_optional": true,
+ "item_default": ""
+ },
+ {
+ "item_name": "port",
+ "item_type": "integer",
+ "item_optional": true,
+ "item_default": 53
+ }]
+ }
+ }]
+ }
+ }]
+ }],
+
"commands": [
- {
- "command_name": "shutdown",
- "command_description": "Shut down the DHCP-DDNS service",
- "command_args": [
- {
- "item_name": "pid",
- "item_type": "integer",
- "item_optional": true
- }
- ]
- }
+ {
+ "command_name": "shutdown",
+ "command_description": "Shuts down DHCPv6 server.",
+ "command_args": [
+ ]
+ }
]
}
}
+
diff --git a/src/bin/d2/tests/Makefile.am b/src/bin/d2/tests/Makefile.am
index a7b37ce..14518d5 100644
--- a/src/bin/d2/tests/Makefile.am
+++ b/src/bin/d2/tests/Makefile.am
@@ -56,6 +56,9 @@ d2_unittests_SOURCES += ../d_process.h
d2_unittests_SOURCES += ../d_controller.cc ../d2_controller.h
d2_unittests_SOURCES += ../d2_process.cc ../d2_process.h
d2_unittests_SOURCES += ../d2_controller.cc ../d2_controller.h
+d2_unittests_SOURCES += ../d_cfg_mgr.cc ../d_cfg_mgr.h
+d2_unittests_SOURCES += ../d2_config.cc ../d2_config.h
+d2_unittests_SOURCES += ../d2_cfg_mgr.cc ../d2_cfg_mgr.h
d2_unittests_SOURCES += ../d2_update_message.cc ../d2_update_message.h
d2_unittests_SOURCES += ../d2_zone.cc ../d2_zone.h
d2_unittests_SOURCES += d_test_stubs.cc d_test_stubs.h
@@ -63,6 +66,8 @@ d2_unittests_SOURCES += d2_unittests.cc
d2_unittests_SOURCES += d2_process_unittests.cc
d2_unittests_SOURCES += d_controller_unittests.cc
d2_unittests_SOURCES += d2_controller_unittests.cc
+d2_unittests_SOURCES += d_cfg_mgr_unittests.cc
+d2_unittests_SOURCES += d2_cfg_mgr_unittests.cc
d2_unittests_SOURCES += d2_update_message_unittests.cc
d2_unittests_SOURCES += d2_zone_unittests.cc
nodist_d2_unittests_SOURCES = ../d2_messages.h ../d2_messages.cc
@@ -75,6 +80,7 @@ d2_unittests_LDADD += $(top_builddir)/src/lib/log/libb10-log.la
d2_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
d2_unittests_LDADD += $(top_builddir)/src/lib/cc/libb10-cc.la
d2_unittests_LDADD += $(top_builddir)/src/lib/config/libb10-cfgclient.la
+d2_unittests_LDADD += $(top_builddir)/src/lib/dhcpsrv/libb10-dhcpsrv.la
d2_unittests_LDADD += $(top_builddir)/src/lib/dns/libb10-dns++.la
endif
diff --git a/src/bin/d2/tests/d2_cfg_mgr_unittests.cc b/src/bin/d2/tests/d2_cfg_mgr_unittests.cc
new file mode 100644
index 0000000..9f5a660
--- /dev/null
+++ b/src/bin/d2/tests/d2_cfg_mgr_unittests.cc
@@ -0,0 +1,1238 @@
+// 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 <test_data_files_config.h>
+
+#include <boost/foreach.hpp>
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::d2;
+
+namespace {
+
+std::string specfile(const std::string& name) {
+ return (std::string(D2_SRC_DIR) + "/" + name);
+}
+
+/// @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(specfile("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(hostname, server->getHostname());
+ result = false;
+ }
+
+ // Check IP address.
+ if (server->getIpAddress().toText() != ip_address) {
+ EXPECT_EQ(ip_address, server->getIpAddress().toText());
+ result = false;
+ }
+
+ // Check port.
+ if (server->getPort() != port) {
+ EXPECT_EQ (port, server->getPort());
+ result = false;
+ }
+
+ return (result);
+}
+
+/// @brief Convenience function which compares the contents of the given
+/// TSIGKeyInfo 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 key is a pointer to the key to check against.
+/// @param name is the value to compare against key's name_.
+/// @param algorithm is the string value to compare against key's algorithm.
+/// @param secret is the value to compare against key's secret.
+///
+/// @return returns true if there is a match across the board, otherwise it
+/// returns false.
+bool checkKey(TSIGKeyInfoPtr key, const char* name,
+ const char *algorithm, const char* secret)
+{
+ // Return value, assume its a match.
+ bool result = true;
+ if (!key) {
+ EXPECT_TRUE(key);
+ return false;
+ }
+
+ // Check name.
+ if (key->getName() != name) {
+ EXPECT_EQ(name, key->getName());
+ result = false;
+ }
+
+ // Check algorithm.
+ if (key->getAlgorithm() != algorithm) {
+ EXPECT_EQ(algorithm, key->getAlgorithm());
+ result = false;
+ }
+
+ // Check secret.
+ if (key->getSecret() != secret) {
+ EXPECT_EQ (secret, key->getSecret());
+ result = false;
+ }
+
+ return (result);
+}
+
+/// @brief Test fixture class for testing DnsServerInfo parsing.
+class TSIGKeyInfoTest : public ConfigParseTest {
+public:
+
+ /// @brief Constructor
+ TSIGKeyInfoTest() {
+ reset();
+ }
+
+ /// @brief Destructor
+ ~TSIGKeyInfoTest() {
+ }
+
+ /// @brief Wipe out the current storage and parser and replace
+ /// them with new ones.
+ void reset() {
+ keys_.reset(new TSIGKeyInfoMap());
+ parser_.reset(new TSIGKeyInfoParser("test", keys_));
+ }
+
+ /// @brief Storage for "committing" keys.
+ TSIGKeyInfoMapPtr keys_;
+
+ /// @brief Pointer to the current parser instance.
+ isc::dhcp::ParserPtr parser_;
+};
+
+/// @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 Test fixture class for testing DDnsDomain parsing.
+class DdnsDomainTest : public ConfigParseTest {
+public:
+
+ /// @brief Constructor
+ DdnsDomainTest() {
+ reset();
+ }
+
+ /// @brief Destructor
+ ~DdnsDomainTest() {
+ }
+
+ /// @brief Wipe out the current storage and parser and replace
+ /// them with new ones.
+ void reset() {
+ keys_.reset(new TSIGKeyInfoMap());
+ domains_.reset(new DdnsDomainMap());
+ parser_.reset(new DdnsDomainParser("test", domains_, keys_));
+ }
+
+ /// @brief Add TSIGKeyInfos to the key map
+ ///
+ /// @param name the name of the key
+ /// @param algorithm the algorithm of the key
+ /// @param secret the secret value of the key
+ void addKey(const std::string& name, const std::string& algorithm,
+ const std::string& secret) {
+ TSIGKeyInfoPtr key_info(new TSIGKeyInfo(name, algorithm, secret));
+ (*keys_)[name]=key_info;
+ }
+
+ /// @brief Storage for "committing" domains.
+ DdnsDomainMapPtr domains_;
+
+ /// @brief Storage for TSIGKeys
+ TSIGKeyInfoMapPtr keys_;
+
+ /// @brief Pointer to the current parser instance.
+ isc::dhcp::ParserPtr parser_;
+};
+
+/// @brief Tests the enforcement of data validation when parsing TSIGKeyInfos.
+/// It verifies that:
+/// 1. Name cannot be blank.
+/// 2. Algorithm cannot be blank.
+/// 3. Secret cannot be blank.
+/// @TODO TSIG keys are not fully functional. Only basic validation is
+/// currently supported. This test will need to expand as they evolve.
+TEST_F(TSIGKeyInfoTest, invalidEntryTests) {
+ // Config with a blank name entry.
+ std::string config = "{"
+ " \"name\": \"\" , "
+ " \"algorithm\": \"md5\" , "
+ " \"secret\": \"0123456789\" "
+ "}";
+ ASSERT_TRUE(fromJSON(config));
+
+ // Verify that build succeeds but commit fails on blank name.
+ EXPECT_NO_THROW(parser_->build(config_set_));
+ EXPECT_THROW(parser_->commit(), D2CfgError);
+
+ // Config with a blank algorithm entry.
+ config = "{"
+ " \"name\": \"d2_key_one\" , "
+ " \"algorithm\": \"\" , "
+ " \"secret\": \"0123456789\" "
+ "}";
+
+ ASSERT_TRUE(fromJSON(config));
+
+ // Verify that build succeeds but commit fails on blank algorithm.
+ EXPECT_NO_THROW(parser_->build(config_set_));
+ EXPECT_THROW(parser_->commit(), D2CfgError);
+
+ // Config with a blank secret entry.
+ config = "{"
+ " \"name\": \"d2_key_one\" , "
+ " \"algorithm\": \"md5\" , "
+ " \"secret\": \"\" "
+ "}";
+
+ ASSERT_TRUE(fromJSON(config));
+
+ // Verify that build succeeds but commit fails on blank secret.
+ EXPECT_NO_THROW(parser_->build(config_set_));
+ EXPECT_THROW(parser_->commit(), D2CfgError);
+}
+
+/// @brief Verifies that TSIGKeyInfo parsing creates a proper TSIGKeyInfo
+/// when given a valid combination of entries.
+TEST_F(TSIGKeyInfoTest, validEntryTests) {
+ // Valid entries for TSIG key, all items are required.
+ std::string config = "{"
+ " \"name\": \"d2_key_one\" , "
+ " \"algorithm\": \"md5\" , "
+ " \"secret\": \"0123456789\" "
+ "}";
+ ASSERT_TRUE(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 keys are present
+ int count = keys_->size();
+ EXPECT_EQ(1, count);
+
+ // Find the key and retrieve it.
+ TSIGKeyInfoMap::iterator gotit = keys_->find("d2_key_one");
+ ASSERT_TRUE(gotit != keys_->end());
+ TSIGKeyInfoPtr& key = gotit->second;
+
+ // Verify the key contents.
+ EXPECT_TRUE(checkKey(key, "d2_key_one", "md5", "0123456789"));
+}
+
+/// @brief Verifies that attempting to parse an invalid list of TSIGKeyInfo
+/// entries is detected.
+TEST_F(TSIGKeyInfoTest, invalidTSIGKeyList) {
+ // Construct a list of keys with an invalid key entry.
+ std::string config = "["
+
+ " { \"name\": \"key1\" , "
+ " \"algorithm\": \"algo1\" ,"
+ " \"secret\": \"secret11\" "
+ " },"
+ " { \"name\": \"key2\" , "
+ " \"algorithm\": \"\" ,"
+ " \"secret\": \"secret12\" "
+ " },"
+ " { \"name\": \"key3\" , "
+ " \"algorithm\": \"algo3\" ,"
+ " \"secret\": \"secret13\" "
+ " }"
+ " ]";
+
+ ASSERT_TRUE(fromJSON(config));
+
+ // Create the list parser.
+ isc::dhcp::ParserPtr parser;
+ ASSERT_NO_THROW(parser.reset(new TSIGKeyInfoListParser("test", keys_)));
+
+ // 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 attempting to parse an invalid list of TSIGKeyInfo
+/// entries is detected.
+TEST_F(TSIGKeyInfoTest, duplicateTSIGKey) {
+ // Construct a list of keys with an invalid key entry.
+ std::string config = "["
+
+ " { \"name\": \"key1\" , "
+ " \"algorithm\": \"algo1\" ,"
+ " \"secret\": \"secret11\" "
+ " },"
+ " { \"name\": \"key2\" , "
+ " \"algorithm\": \"algo2\" ,"
+ " \"secret\": \"secret12\" "
+ " },"
+ " { \"name\": \"key1\" , "
+ " \"algorithm\": \"algo3\" ,"
+ " \"secret\": \"secret13\" "
+ " }"
+ " ]";
+
+ ASSERT_TRUE(fromJSON(config));
+
+ // Create the list parser.
+ isc::dhcp::ParserPtr parser;
+ ASSERT_NO_THROW(parser.reset(new TSIGKeyInfoListParser("test", keys_)));
+
+ // 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 a valid list of TSIG Keys parses correctly.
+TEST_F(TSIGKeyInfoTest, validTSIGKeyList) {
+ // Construct a valid list of keys.
+ std::string config = "["
+
+ " { \"name\": \"key1\" , "
+ " \"algorithm\": \"algo1\" ,"
+ " \"secret\": \"secret1\" "
+ " },"
+ " { \"name\": \"key2\" , "
+ " \"algorithm\": \"algo2\" ,"
+ " \"secret\": \"secret2\" "
+ " },"
+ " { \"name\": \"key3\" , "
+ " \"algorithm\": \"algo3\" ,"
+ " \"secret\": \"secret3\" "
+ " }"
+ " ]";
+
+ ASSERT_TRUE(fromJSON(config));
+
+ // Verify that the list builds and commits without errors.
+ // Create the list parser.
+ isc::dhcp::ParserPtr parser;
+ ASSERT_NO_THROW(parser.reset(new TSIGKeyInfoListParser("test", keys_)));
+ ASSERT_NO_THROW(parser->build(config_set_));
+ ASSERT_NO_THROW(parser->commit());
+
+ // Verify the correct number of keys are present
+ int count = keys_->size();
+ ASSERT_EQ(count, 3);
+
+ // Find the 1st key and retrieve it.
+ TSIGKeyInfoMap::iterator gotit = keys_->find("key1");
+ ASSERT_TRUE(gotit != keys_->end());
+ TSIGKeyInfoPtr& key = gotit->second;
+
+ // Verify the key contents.
+ EXPECT_TRUE(checkKey(key, "key1", "algo1", "secret1"));
+
+ // Find the 2nd key and retrieve it.
+ gotit = keys_->find("key2");
+ ASSERT_TRUE(gotit != keys_->end());
+ key = gotit->second;
+
+ // Verify the key contents.
+ EXPECT_TRUE(checkKey(key, "key2", "algo2", "secret2"));
+
+ // Find the 3rd key and retrieve it.
+ gotit = keys_->find("key3");
+ ASSERT_TRUE(gotit != keys_->end());
+ key = gotit->second;
+
+ // Verify the key contents.
+ EXPECT_TRUE(checkKey(key, "key3", "algo3", "secret3"));
+}
+
+/// @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, invalidEntryTests) {
+ // 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_TRUE(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_TRUE(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_TRUE(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, validEntryTests) {
+ // Valid entries for dynamic host
+ std::string config = "{ \"hostname\": \"pegasus.tmark\" }";
+ ASSERT_TRUE(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(1, count);
+
+ // 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_TRUE(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(1, count);
+
+ // 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_TRUE(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(1, count);
+
+ // 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_TRUE(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_TRUE(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(3, count);
+
+ // 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.
+/// 5. That an undefined key name is detected.
+TEST_F(DdnsDomainTest, invalidDdnsDomainEntry) {
+ // Verify that attempting to construct the parser with null storage fails.
+ DdnsDomainMapPtr domains;
+ ASSERT_THROW(isc::dhcp::ParserPtr(
+ new DdnsDomainParser("test", domains, keys_)), D2CfgError);
+
+ // 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_TRUE(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_TRUE(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_TRUE(fromJSON(config));
+
+ // Verify that the domain configuration build fails.
+ ASSERT_THROW(parser_->build(config_set_), isc::BadValue);
+
+ // Create a domain configuration without an defined key name
+ config = "{ \"name\": \"tmark.org\" , "
+ " \"key_name\": \"d2_key.tmark.org\" , "
+ " \"dns_servers\" : [ "
+ " { \"ip_address\": \"127.0.0.3\" , "
+ " \"port\": 300 } ] } ";
+ ASSERT_TRUE(fromJSON(config));
+
+ // Verify that the domain configuration build succeeds but commit fails.
+ ASSERT_NO_THROW(parser_->build(config_set_));
+ ASSERT_THROW(parser_->commit(), D2CfgError);
+}
+
+/// @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 DdnsDomainMap).
+TEST_F(DdnsDomainTest, 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_TRUE(fromJSON(config));
+
+ // Add a TSIG key to the test key map, so key validation will pass.
+ addKey("d2_key.tmark.org", "md5", "0123456789");
+
+ // 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(1, count);
+
+ // Verify that the expected domain exists and can be retrieved from
+ // the storage.
+ DdnsDomainMap::iterator gotit = domains_->find("tmark.org");
+ ASSERT_TRUE(gotit != domains_->end());
+ DdnsDomainPtr& domain = gotit->second;
+
+ // Verify the name and key_name values.
+ EXPECT_EQ("tmark.org", domain->getName());
+ EXPECT_EQ("d2_key.tmark.org", domain->getKeyName());
+
+ // 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(3, count);
+
+ // 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(DdnsDomainTest, 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_TRUE(fromJSON(config));
+
+ // Add keys to key map so key validation passes.
+ addKey("d2_key.tmark.org", "algo1", "secret1");
+ addKey("d2_key.billcat.net", "algo2", "secret2");
+
+ // Create the list parser
+ isc::dhcp::ParserPtr list_parser;
+ ASSERT_NO_THROW(list_parser.reset(
+ new DdnsDomainListParser("test", domains_, keys_)));
+
+ // Verify that the domain configuration builds and commits without error.
+ ASSERT_NO_THROW(list_parser->build(config_set_));
+ ASSERT_NO_THROW(list_parser->commit());
+
+ // Verify that the domain storage contains the correct number of domains.
+ int count = domains_->size();
+ EXPECT_EQ(2, count);
+
+ // Verify that the first domain exists and can be retrieved.
+ DdnsDomainMap::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("tmark.org", domain->getName());
+ EXPECT_EQ("d2_key.tmark.org", domain->getKeyName());
+
+ // Verify the each of the first domain's servers
+ DnsServerInfoStoragePtr servers = domain->getServers();
+ EXPECT_TRUE(servers);
+ count = servers->size();
+ EXPECT_EQ(3, count);
+
+ 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("billcat.net", domain->getName());
+ EXPECT_EQ("d2_key.billcat.net", domain->getKeyName());
+
+ // Verify the each of second domain's servers
+ servers = domain->getServers();
+ EXPECT_TRUE(servers);
+ count = servers->size();
+ EXPECT_EQ(3, count);
+
+ 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(DdnsDomainTest, 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_TRUE(fromJSON(config));
+
+ // Create the list parser
+ isc::dhcp::ParserPtr list_parser;
+ ASSERT_NO_THROW(list_parser.reset(
+ new DdnsDomainListParser("test", domains_, keys_)));
+
+ // Verify that the parse build succeeds but the commit fails.
+ ASSERT_NO_THROW(list_parser->build(config_set_));
+ ASSERT_THROW(list_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 , "
+ "\"tsig_keys\": ["
+ "{"
+ " \"name\": \"d2_key.tmark.org\" , "
+ " \"algorithm\": \"md5\" , "
+ " \"secret\": \"ssh-dont-tell\" "
+ "},"
+ "{"
+ " \"name\": \"d2_key.billcat.net\" , "
+ " \"algorithm\": \"md5\" , "
+ " \"secret\": \"ollie-ollie-in-free\" "
+ "}"
+ "],"
+ "\"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_TRUE(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 the application level scalars have the proper values.
+ std::string interface;
+ EXPECT_NO_THROW (context->getParam("interface", interface));
+ EXPECT_EQ("eth1", interface);
+
+ std::string ip_address;
+ EXPECT_NO_THROW (context->getParam("ip_address", ip_address));
+ EXPECT_EQ("192.168.1.33", ip_address);
+
+ uint32_t port = 0;
+ EXPECT_NO_THROW (context->getParam("port", port));
+ EXPECT_EQ(88, port);
+
+ // 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.
+ DdnsDomainMapPtr domains = mgr->getDomains();
+ ASSERT_TRUE(domains);
+ int count = domains->size();
+ EXPECT_EQ(2, count);
+
+ // 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.
+ DdnsDomainMapPair domain_pair;
+ BOOST_FOREACH(domain_pair, (*domains)) {
+ DdnsDomainPtr domain = domain_pair.second;
+ DnsServerInfoStoragePtr servers = domain->getServers();
+ count = servers->size();
+ EXPECT_TRUE(servers);
+ EXPECT_EQ(3, count);
+ }
+
+ // 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(2, count);
+
+ // 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(3, count);
+ }
+}
+
+/// @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 , "
+ "\"tsig_keys\": [] ,"
+ "\"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\" } "
+ " ] } "
+ "] }, "
+ "\"reverse_ddns\" : {} "
+ "}";
+
+
+ ASSERT_TRUE(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 , "
+ "\"tsig_keys\": [] ,"
+ "\"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\" } "
+ " ] } "
+ "] }, "
+ "\"reverse_ddns\" : {} "
+ " }";
+
+ ASSERT_TRUE(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 , "
+ "\"tsig_keys\": [] ,"
+ "\"forward_ddns\" : {"
+ "\"ddns_domains\": [ "
+ "{ \"name\": \"*\" , "
+ " \"dns_servers\" : [ "
+ " { \"ip_address\": \"127.0.0.1\" } "
+ " ] } "
+ "] }, "
+ "\"reverse_ddns\" : {} "
+ "}";
+
+ ASSERT_TRUE(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 , "
+ "\"tsig_keys\": [] ,"
+ "\"forward_ddns\" : {}, "
+ "\"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_TRUE(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
diff --git a/src/bin/d2/tests/d2_controller_unittests.cc b/src/bin/d2/tests/d2_controller_unittests.cc
index a2b3374..f4b4d41 100644
--- a/src/bin/d2/tests/d2_controller_unittests.cc
+++ b/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.
diff --git a/src/bin/d2/tests/d2_process_unittests.cc b/src/bin/d2/tests/d2_process_unittests.cc
index 40c85f3..c507f2c 100644
--- a/src/bin/d2/tests/d2_process_unittests.cc
+++ b/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.
diff --git a/src/bin/d2/tests/d_cfg_mgr_unittests.cc b/src/bin/d2/tests/d_cfg_mgr_unittests.cc
new file mode 100644
index 0000000..512b896
--- /dev/null
+++ b/src/bin/d2/tests/d_cfg_mgr_unittests.cc
@@ -0,0 +1,386 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config/ccsession.h>
+#include <config/module_spec.h>
+#include <dhcpsrv/dhcp_parsers.h>
+#include <d2/d_cfg_mgr.h>
+#include <d_test_stubs.h>
+
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <gtest/gtest.h>
+
+#include <config.h>
+#include <sstream>
+
+using namespace std;
+using namespace isc;
+using namespace isc::config;
+using namespace isc::d2;
+using namespace boost::posix_time;
+
+namespace {
+
+/// @brief Test Class for verifying that configuration context cannot be null
+/// during construction.
+class DCtorTestCfgMgr : public DCfgMgrBase {
+public:
+ /// @brief Constructor - Note that is passes in an empty configuration
+ /// pointer to the base class constructor.
+ DCtorTestCfgMgr() : DCfgMgrBase(DCfgContextBasePtr()) {
+ }
+
+ /// @brief Destructor
+ virtual ~DCtorTestCfgMgr() {
+ }
+
+ /// @brief Dummy implementation as this method is abstract.
+ virtual isc::dhcp::ParserPtr
+ createConfigParser(const std::string& /* element_id */) {
+ return (isc::dhcp::ParserPtr());
+ }
+};
+
+/// @brief Test fixture class for testing DCfgMgrBase class.
+/// It maintains an member instance of DStubCfgMgr and derives from
+/// ConfigParseTest fixture, thus providing methods for converting JSON
+/// strings to configuration element sets, checking parse results, and
+/// accessing the configuration context.
+class DStubCfgMgrTest : public ConfigParseTest {
+public:
+
+ /// @brief Constructor
+ DStubCfgMgrTest():cfg_mgr_(new DStubCfgMgr) {
+ }
+
+ /// @brief Destructor
+ ~DStubCfgMgrTest() {
+ }
+
+ /// @brief Convenience method which returns a DStubContextPtr to the
+ /// configuration context.
+ ///
+ /// @return returns a DStubContextPtr.
+ DStubContextPtr getStubContext() {
+ return (boost::dynamic_pointer_cast<DStubContext>
+ (cfg_mgr_->getContext()));
+ }
+
+ /// @brief Configuration manager instance.
+ DStubCfgMgrPtr cfg_mgr_;
+};
+
+///@brief Tests basic construction/destruction of configuration manager.
+/// Verifies that:
+/// 1. Proper construction succeeds.
+/// 2. Configuration context is initialized by construction.
+/// 3. Destruction works properly.
+/// 4. Construction with a null context is not allowed.
+TEST(DCfgMgrBase, construction) {
+ DCfgMgrBasePtr cfg_mgr;
+
+ // Verify that configuration manager constructions without error.
+ ASSERT_NO_THROW(cfg_mgr.reset(new DStubCfgMgr()));
+
+ // Verify that the context can be retrieved and is not null.
+ DCfgContextBasePtr context = cfg_mgr->getContext();
+ EXPECT_TRUE(context);
+
+ // Verify that the manager can be destructed without error.
+ EXPECT_NO_THROW(cfg_mgr.reset());
+
+ // Verify that an attempt to construct a manger with a null context fails.
+ ASSERT_THROW(DCtorTestCfgMgr(), DCfgMgrBaseError);
+}
+
+///@brief Tests fundamental aspects of configuration parsing.
+/// Verifies that:
+/// 1. A correctly formed simple configuration parses without error.
+/// 2. An error building the element is handled.
+/// 3. An error committing the element is handled.
+/// 4. An unknown element error is handled.
+TEST_F(DStubCfgMgrTest, basicParseTest) {
+ // Create a simple configuration.
+ string config = "{ \"test-value\": 1000 } ";
+ ASSERT_TRUE(fromJSON(config));
+
+ // Verify that we can parse a simple configuration.
+ answer_ = cfg_mgr_->parseConfig(config_set_);
+ EXPECT_TRUE(checkAnswer(0));
+
+ // Verify that an error building the element is caught and returns a
+ // failed parse result.
+ SimFailure::set(SimFailure::ftElementBuild);
+ answer_ = cfg_mgr_->parseConfig(config_set_);
+ EXPECT_TRUE(checkAnswer(1));
+
+ // Verify that an error committing the element is caught and returns a
+ // failed parse result.
+ SimFailure::set(SimFailure::ftElementCommit);
+ answer_ = cfg_mgr_->parseConfig(config_set_);
+ EXPECT_TRUE(checkAnswer(1));
+
+ // Verify that an unknown element error is caught and returns a failed
+ // parse result.
+ SimFailure::set(SimFailure::ftElementUnknown);
+ answer_ = cfg_mgr_->parseConfig(config_set_);
+ EXPECT_TRUE(checkAnswer(1));
+}
+
+///@brief Tests ordered and non-ordered element parsing
+/// This test verifies that:
+/// 1. Non-ordered parsing parses elements in the order they are presented
+/// by the configuration set (as-they-come).
+/// 2. A parse order list with too few elements is detected.
+/// 3. Ordered parsing parses the elements in the order specified by the
+/// configuration manager's parse order list.
+/// 4. A parse order list with too many elements is detected.
+TEST_F(DStubCfgMgrTest, parseOrderTest) {
+ // Element ids used for test.
+ std::string charlie("charlie");
+ std::string bravo("bravo");
+ std::string alpha("alpha");
+
+ // Create the test configuration with the elements in "random" order.
+
+ // NOTE that element sets produced by isc::data::Element::fromJSON(),
+ // are in lexical order by element_id. This means that iterating over
+ // such an element set, will present the elements in lexical order. Should
+ // this change, this test will need to be modified accordingly.
+ string config = "{ \"bravo\": 2, "
+ " \"alpha\": 1, "
+ " \"charlie\": 3 } ";
+ ASSERT_TRUE(fromJSON(config));
+
+ // Verify that non-ordered parsing, results in an as-they-come parse order.
+ // Create an expected parse order.
+ // (NOTE that iterating over Element sets produced by fromJSON() will
+ // present the elements in lexical order. Should this change, the expected
+ // order list below would need to be changed accordingly).
+ ElementIdList order_expected;
+ order_expected.push_back(alpha);
+ order_expected.push_back(bravo);
+ order_expected.push_back(charlie);
+
+ // Verify that the manager has an EMPTY parse order list. (Empty list
+ // instructs the manager to parse them as-they-come.)
+ EXPECT_EQ(0, cfg_mgr_->getParseOrder().size());
+
+ // Parse the configuration, verify it parses without error.
+ answer_ = cfg_mgr_->parseConfig(config_set_);
+ EXPECT_TRUE(checkAnswer(0));
+
+ // Verify that the parsed order matches what we expected.
+ EXPECT_TRUE(cfg_mgr_->parsed_order_ == order_expected);
+
+ // Clear the manager's parse order "memory".
+ cfg_mgr_->parsed_order_.clear();
+
+ // Create a parse order list that has too few entries. Verify that
+ // when parsing the test config, it fails.
+ cfg_mgr_->addToParseOrder(charlie);
+ // Verify the parse order list is the size we expect.
+ EXPECT_EQ(1, cfg_mgr_->getParseOrder().size());
+
+ // Verify the configuration fails.
+ answer_ = cfg_mgr_->parseConfig(config_set_);
+ EXPECT_TRUE(checkAnswer(1));
+
+ // Verify that the configuration parses correctly, when the parse order
+ // is correct. Add the needed entries to the parse order
+ cfg_mgr_->addToParseOrder(bravo);
+ cfg_mgr_->addToParseOrder(alpha);
+
+ // Verify the parse order list is the size we expect.
+ EXPECT_EQ(3, cfg_mgr_->getParseOrder().size());
+
+ // Clear the manager's parse order "memory".
+ cfg_mgr_->parsed_order_.clear();
+
+ // Verify the configuration parses without error.
+ answer_ = cfg_mgr_->parseConfig(config_set_);
+ EXPECT_TRUE(checkAnswer(0));
+
+ // Verify that the parsed order is the order we configured.
+ EXPECT_TRUE(cfg_mgr_->getParseOrder() == cfg_mgr_->parsed_order_);
+
+ // Create a parse order list that has too many entries. Verify that
+ // when parsing the test config, it fails.
+ cfg_mgr_->addToParseOrder("delta");
+
+ // Verify the parse order list is the size we expect.
+ EXPECT_EQ(4, cfg_mgr_->getParseOrder().size());
+
+ // Verify the configuration fails.
+ answer_ = cfg_mgr_->parseConfig(config_set_);
+ EXPECT_TRUE(checkAnswer(1));
+}
+
+/// @brief Tests that element ids supported by the base class as well as those
+/// added by the derived class function properly.
+/// This test verifies that:
+/// 1. Boolean parameters can be parsed and retrieved.
+/// 2. Uint32 parameters can be parsed and retrieved.
+/// 3. String parameters can be parsed and retrieved.
+/// 4. Derivation-specific parameters can be parsed and retrieved.
+/// 5. Parsing a second configuration, updates the existing context values
+/// correctly.
+TEST_F(DStubCfgMgrTest, simpleTypesTest) {
+ // Fetch a derivation specific pointer to the context.
+ DStubContextPtr context = getStubContext();
+ ASSERT_TRUE(context);
+
+ // Create a configuration with all of the parameters.
+ string config = "{ \"bool_test\": true , "
+ " \"uint32_test\": 77 , "
+ " \"string_test\": \"hmmm chewy\" , "
+ " \"extra_test\": 430 } ";
+ ASSERT_TRUE(fromJSON(config));
+
+ // Verify that the configuration parses without error.
+ answer_ = cfg_mgr_->parseConfig(config_set_);
+ ASSERT_TRUE(checkAnswer(0));
+
+ // Verify that the boolean parameter was parsed correctly by retrieving
+ // its value from the context.
+ bool actual_bool = false;
+ EXPECT_NO_THROW(context->getParam("bool_test", actual_bool));
+ EXPECT_EQ(true, actual_bool);
+
+ // Verify that the uint32 parameter was parsed correctly by retrieving
+ // its value from the context.
+ uint32_t actual_uint32 = 0;
+ EXPECT_NO_THROW(context->getParam("uint32_test", actual_uint32));
+ EXPECT_EQ(77, actual_uint32);
+
+ // Verify that the string parameter was parsed correctly by retrieving
+ // its value from the context.
+ std::string actual_string = "";
+ EXPECT_NO_THROW(context->getParam("string_test", actual_string));
+ EXPECT_EQ("hmmm chewy", actual_string);
+
+ // Verify that the "extra" parameter was parsed correctly by retrieving
+ // its value from the context.
+ uint32_t actual_extra = 0;
+ EXPECT_NO_THROW(context->getExtraParam("extra_test", actual_extra));
+ EXPECT_EQ(430, actual_extra);
+
+ // Create a configuration which "updates" all of the parameter values.
+ string config2 = "{ \"bool_test\": false , "
+ " \"uint32_test\": 88 , "
+ " \"string_test\": \"ewww yuk!\" , "
+ " \"extra_test\": 11 } ";
+ ASSERT_TRUE(fromJSON(config2));
+
+ // Verify that the configuration parses without error.
+ answer_ = cfg_mgr_->parseConfig(config_set_);
+ EXPECT_TRUE(checkAnswer(0));
+
+ // Verify that the boolean parameter was updated correctly by retrieving
+ // its value from the context.
+ actual_bool = true;
+ EXPECT_NO_THROW(context->getParam("bool_test", actual_bool));
+ EXPECT_FALSE(actual_bool);
+
+ // Verify that the uint32 parameter was updated correctly by retrieving
+ // its value from the context.
+ actual_uint32 = 0;
+ EXPECT_NO_THROW(context->getParam("uint32_test", actual_uint32));
+ EXPECT_EQ(88, actual_uint32);
+
+ // Verify that the string parameter was updated correctly by retrieving
+ // its value from the context.
+ actual_string = "";
+ EXPECT_NO_THROW(context->getParam("string_test", actual_string));
+ EXPECT_EQ("ewww yuk!", actual_string);
+
+ // Verify that the "extra" parameter was updated correctly by retrieving
+ // its value from the context.
+ actual_extra = 0;
+ EXPECT_NO_THROW(context->getExtraParam("extra_test", actual_extra));
+ EXPECT_EQ(11, actual_extra);
+}
+
+/// @brief Tests that the configuration context is preserved after failure
+/// during parsing causes a rollback.
+/// 1. Verifies configuration context rollback.
+TEST_F(DStubCfgMgrTest, rollBackTest) {
+ // Fetch a derivation specific pointer to the context.
+ DStubContextPtr context = getStubContext();
+ ASSERT_TRUE(context);
+
+ // Create a configuration with all of the parameters.
+ string config = "{ \"bool_test\": true , "
+ " \"uint32_test\": 77 , "
+ " \"string_test\": \"hmmm chewy\" , "
+ " \"extra_test\": 430 } ";
+ ASSERT_TRUE(fromJSON(config));
+
+ // Verify that the configuration parses without error.
+ answer_ = cfg_mgr_->parseConfig(config_set_);
+ EXPECT_TRUE(checkAnswer(0));
+
+ // Verify that all of parameters have the expected values.
+ bool actual_bool = false;
+ EXPECT_NO_THROW(context->getParam("bool_test", actual_bool));
+ EXPECT_EQ(true, actual_bool);
+
+ uint32_t actual_uint32 = 0;
+ EXPECT_NO_THROW(context->getParam("uint32_test", actual_uint32));
+ EXPECT_EQ(77, actual_uint32);
+
+ std::string actual_string = "";
+ EXPECT_NO_THROW(context->getParam("string_test", actual_string));
+ EXPECT_EQ("hmmm chewy", actual_string);
+
+ uint32_t actual_extra = 0;
+ EXPECT_NO_THROW(context->getExtraParam("extra_test", actual_extra));
+ EXPECT_EQ(430, actual_extra);
+
+ // Create a configuration which "updates" all of the parameter values
+ // plus one unknown at the end.
+ string config2 = "{ \"bool_test\": false , "
+ " \"uint32_test\": 88 , "
+ " \"string_test\": \"ewww yuk!\" , "
+ " \"extra_test\": 11 , "
+ " \"zeta_unknown\": 33 } ";
+ ASSERT_TRUE(fromJSON(config2));
+
+ // Force a failure on the last element
+ SimFailure::set(SimFailure::ftElementUnknown);
+ answer_ = cfg_mgr_->parseConfig(config_set_);
+ EXPECT_TRUE(checkAnswer(1));
+
+ // Refresh our local pointer.
+ context = getStubContext();
+
+ // Verify that all of parameters have the original values.
+ actual_bool = false;
+ EXPECT_NO_THROW(context->getParam("bool_test", actual_bool));
+ EXPECT_EQ(true, actual_bool);
+
+ actual_uint32 = 0;
+ EXPECT_NO_THROW(context->getParam("uint32_test", actual_uint32));
+ EXPECT_EQ(77, actual_uint32);
+
+ actual_string = "";
+ EXPECT_NO_THROW(context->getParam("string_test", actual_string));
+ EXPECT_EQ("hmmm chewy", actual_string);
+
+ actual_extra = 0;
+ EXPECT_NO_THROW(context->getExtraParam("extra_test", actual_extra));
+ EXPECT_EQ(430, actual_extra);
+}
+
+} // end of anonymous namespace
diff --git a/src/bin/d2/tests/d_test_stubs.cc b/src/bin/d2/tests/d_test_stubs.cc
index 33a2ff6..4e1cee6 100644
--- a/src/bin/d2/tests/d_test_stubs.cc
+++ b/src/bin/d2/tests/d_test_stubs.cc
@@ -21,6 +21,31 @@ using namespace asio;
namespace isc {
namespace d2 {
+const char* valid_d2_config = "{ "
+ "\"interface\" : \"eth1\" , "
+ "\"ip_address\" : \"192.168.1.33\" , "
+ "\"port\" : 88 , "
+ "\"tsig_keys\": ["
+ "{ \"name\": \"d2_key.tmark.org\" , "
+ " \"algorithm\": \"md5\" ,"
+ " \"secret\": \"0123456989\" "
+ "} ],"
+ "\"forward_ddns\" : {"
+ "\"ddns_domains\": [ "
+ "{ \"name\": \"tmark.org\" , "
+ " \"key_name\": \"d2_key.tmark.org\" , "
+ " \"dns_servers\" : [ "
+ " { \"hostname\": \"one.tmark\" } "
+ "] } ] }, "
+ "\"reverse_ddns\" : {"
+ "\"ddns_domains\": [ "
+ "{ \"name\": \" 0.168.192.in.addr.arpa.\" , "
+ " \"key_name\": \"d2_key.tmark.org\" , "
+ " \"dns_servers\" : [ "
+ " { \"ip_address\": \"127.0.0.101\" , "
+ " \"port\": 100 } ] } "
+ "] } }";
+
// Initialize the static failure flag.
SimFailure::FailureType SimFailure::failure_type_ = SimFailure::ftNoFailure;
@@ -28,7 +53,7 @@ SimFailure::FailureType SimFailure::failure_type_ = SimFailure::ftNoFailure;
const char* DStubProcess::stub_proc_command_("cool_proc_cmd");
DStubProcess::DStubProcess(const char* name, IOServicePtr io_service)
- : DProcessBase(name, io_service) {
+ : DProcessBase(name, io_service, DCfgMgrBasePtr(new DStubCfgMgr())) {
};
void
@@ -130,7 +155,7 @@ DStubController::DStubController()
if (getenv("B10_FROM_BUILD")) {
setSpecFileName(std::string(getenv("B10_FROM_BUILD")) +
- "/src/bin/d2/d2.spec");
+ "/src/bin/d2/dhcp-ddns.spec");
} else {
setSpecFileName(D2_SPECFILE_LOCATION);
}
@@ -191,5 +216,102 @@ DStubController::~DStubController() {
// Initialize controller wrapper's static instance getter member.
DControllerTest::InstanceGetter DControllerTest::instanceGetter_ = NULL;
+//************************** TestParser *************************
+
+TestParser::TestParser(const std::string& param_name):param_name_(param_name) {
+}
+
+TestParser::~TestParser(){
+}
+
+void
+TestParser::build(isc::data::ConstElementPtr new_config) {
+ if (SimFailure::shouldFailOn(SimFailure::ftElementBuild)) {
+ // Simulates an error during element data parsing.
+ isc_throw (DCfgMgrBaseError, "Simulated build exception");
+ }
+
+ value_ = new_config;
+}
+
+void
+TestParser::commit() {
+ if (SimFailure::shouldFailOn(SimFailure::ftElementCommit)) {
+ // Simulates an error while committing the parsed element data.
+ throw std::runtime_error("Simulated commit exception");
+ }
+}
+
+//************************** DStubContext *************************
+
+DStubContext::DStubContext(): extra_values_(new isc::dhcp::Uint32Storage()) {
+}
+
+DStubContext::~DStubContext() {
+}
+
+void
+DStubContext::getExtraParam(const std::string& name, uint32_t& value) {
+ value = extra_values_->getParam(name);
+}
+
+isc::dhcp::Uint32StoragePtr
+DStubContext::getExtraStorage() {
+ return (extra_values_);
+}
+
+DCfgContextBasePtr
+DStubContext::clone() {
+ return (DCfgContextBasePtr(new DStubContext(*this)));
+}
+
+DStubContext::DStubContext(const DStubContext& rhs): DCfgContextBase(rhs),
+ extra_values_(new isc::dhcp::Uint32Storage(*(rhs.extra_values_))) {
+}
+
+//************************** DStubCfgMgr *************************
+
+DStubCfgMgr::DStubCfgMgr()
+ : DCfgMgrBase(DCfgContextBasePtr(new DStubContext())) {
+}
+
+DStubCfgMgr::~DStubCfgMgr() {
+}
+
+isc::dhcp::ParserPtr
+DStubCfgMgr::createConfigParser(const std::string& element_id) {
+ isc::dhcp::DhcpConfigParser* parser = NULL;
+ DStubContextPtr context =
+ boost::dynamic_pointer_cast<DStubContext>(getContext());
+
+ if (element_id == "bool_test") {
+ parser = new isc::dhcp::BooleanParser(element_id,
+ context->getBooleanStorage());
+ } else if (element_id == "uint32_test") {
+ parser = new isc::dhcp::Uint32Parser(element_id,
+ context->getUint32Storage());
+ } else if (element_id == "string_test") {
+ parser = new isc::dhcp::StringParser(element_id,
+ context->getStringStorage());
+ } else if (element_id == "extra_test") {
+ parser = new isc::dhcp::Uint32Parser(element_id,
+ context->getExtraStorage());
+ } else {
+ // Fail only if SimFailure dictates we should. This makes it easier
+ // to test parse ordering, by permitting a wide range of element ids
+ // to "succeed" without specifically supporting them.
+ if (SimFailure::shouldFailOn(SimFailure::ftElementUnknown)) {
+ isc_throw(DCfgMgrBaseError, "Configuration parameter not supported: "
+ << element_id);
+ }
+
+ parsed_order_.push_back(element_id);
+ parser = new TestParser(element_id);
+ }
+
+ return (isc::dhcp::ParserPtr(parser));
+}
+
+
}; // namespace isc::d2
}; // namespace isc
diff --git a/src/bin/d2/tests/d_test_stubs.h b/src/bin/d2/tests/d_test_stubs.h
index e634762..60a8810 100644
--- a/src/bin/d2/tests/d_test_stubs.h
+++ b/src/bin/d2/tests/d_test_stubs.h
@@ -21,11 +21,18 @@
#include <config/ccsession.h>
#include <d2/d_controller.h>
+#include <d2/d_cfg_mgr.h>
+
#include <gtest/gtest.h>
namespace isc {
namespace d2 {
+/// @brief Provides a valid DHCP-DDNS configuration for testing basic
+/// parsing fundamentals.
+extern const char* valid_d2_config;
+
+
/// @brief Class is used to set a globally accessible value that indicates
/// a specific type of failure to simulate. Test derivations of base classes
/// can exercise error handling code paths by testing for specific SimFailure
@@ -43,7 +50,10 @@ public:
ftProcessConfigure,
ftControllerCommand,
ftProcessCommand,
- ftProcessShutdown
+ ftProcessShutdown,
+ ftElementBuild,
+ ftElementCommit,
+ ftElementUnknown
};
/// @brief Sets the SimFailure value to the given value.
@@ -80,10 +90,12 @@ public:
return (false);
}
+ /// @brief Resets the failure type to none.
static void clear() {
failure_type_ = ftNoFailure;
}
+ /// @brief Static value for holding the failure type to simulate.
static enum FailureType failure_type_;
};
@@ -433,6 +445,207 @@ public:
}
};
+/// @brief Simple parser derivation for testing the basics of configuration
+/// parsing.
+class TestParser : public isc::dhcp::DhcpConfigParser {
+public:
+
+ /// @brief Constructor
+ ///
+ /// See @ref DhcpConfigParser class for details.
+ ///
+ /// @param param_name name of the parsed parameter
+ TestParser(const std::string& param_name);
+
+ /// @brief Destructor
+ virtual ~TestParser();
+
+ /// @brief Builds parameter value.
+ ///
+ /// See @ref DhcpConfigParser class for details.
+ ///
+ /// @param new_config pointer to the new configuration
+ /// @throw throws DCfgMgrBaseError if the SimFailure is set to
+ /// ftElementBuild. This allows for the simulation of an
+ /// exception during the build portion of parsing an element.
+ virtual void build(isc::data::ConstElementPtr new_config);
+
+ /// @brief Commits the parsed value to storage.
+ ///
+ /// See @ref DhcpConfigParser class for details.
+ ///
+ /// @throw throws DCfgMgrBaseError if SimFailure is set to ftElementCommit.
+ /// This allows for the simulation of an exception during the commit
+ /// portion of parsing an element.
+ virtual void commit();
+
+private:
+ /// name of the parsed parameter
+ std::string param_name_;
+
+ /// pointer to the parsed value of the parameter
+ isc::data::ConstElementPtr value_;
+};
+
+/// @brief Test Derivation of the DCfgContextBase class.
+///
+/// This class is used to test basic functionality of configuration context.
+/// It adds an additional storage container "extra values" to mimic an
+/// application extension of configuration storage. This permits testing that
+/// both the base class content as well as the application content is
+/// correctly copied during cloning. This is vital to configuration backup
+/// and rollback during configuration parsing.
+class DStubContext : public DCfgContextBase {
+public:
+
+ /// @brief Constructor
+ DStubContext();
+
+ /// @brief Destructor
+ virtual ~DStubContext();
+
+ /// @brief Fetches the value for a given "extra" configuration parameter
+ /// from the context.
+ ///
+ /// @param name is the name of the parameter to retrieve.
+ /// @param value is an output parameter in which to return the retrieved
+ /// value.
+ /// @throw throws DhcpConfigError if the context does not contain the
+ /// parameter.
+ void getExtraParam(const std::string& name, uint32_t& value);
+
+ /// @brief Fetches the extra storage.
+ ///
+ /// @return returns a pointer to the extra storage.
+ isc::dhcp::Uint32StoragePtr getExtraStorage();
+
+ /// @brief Creates a clone of a DStubContext.
+ ///
+ /// @return returns a pointer to the new clone.
+ virtual DCfgContextBasePtr clone();
+
+protected:
+ /// @brief Copy constructor
+ DStubContext(const DStubContext& rhs);
+
+private:
+ /// @brief Private assignment operator, not implemented.
+ DStubContext& operator=(const DStubContext& rhs);
+
+ /// @brief Extra storage for uint32 parameters.
+ isc::dhcp::Uint32StoragePtr extra_values_;
+};
+
+/// @brief Defines a pointer to DStubContext.
+typedef boost::shared_ptr<DStubContext> DStubContextPtr;
+
+/// @brief Test Derivation of the DCfgMgrBase class.
+///
+/// This class is used to test basic functionality of configuration management.
+/// It supports the following configuration elements:
+///
+/// "bool_test" - Boolean element, tests parsing and committing a boolean
+/// configuration parameter.
+/// "uint32_test" - Uint32 element, tests parsing and committing a uint32_t
+/// configuration parameter.
+/// "string_test" - String element, tests parsing and committing a string
+/// configuration parameter.
+/// "extra_test" - "Extra" element, tests parsing and committing an extra
+/// configuration parameter. (This is used to demonstrate
+/// derivation's addition of storage to configuration context.
+///
+/// It also keeps track of the element ids that are parsed in the order they
+/// are parsed. This is used to test ordered and non-ordered parsing.
+class DStubCfgMgr : public DCfgMgrBase {
+public:
+ /// @brief Constructor
+ DStubCfgMgr();
+
+ /// @brief Destructor
+ virtual ~DStubCfgMgr();
+
+ /// @brief Given an element_id returns an instance of the appropriate
+ /// parser. It supports the element ids as described in the class brief.
+ ///
+ /// @param element_id is the string name of the element as it will appear
+ /// in the configuration set.
+ ///
+ /// @return returns a ParserPtr to the parser instance.
+ /// @throw throws DCfgMgrBaseError if SimFailure is ftElementUnknown.
+ virtual isc::dhcp::ParserPtr
+ createConfigParser(const std::string& element_id);
+
+ /// @brief A list for remembering the element ids in the order they were
+ /// parsed.
+ ElementIdList parsed_order_;
+};
+
+/// @brief Defines a pointer to DStubCfgMgr.
+typedef boost::shared_ptr<DStubCfgMgr> DStubCfgMgrPtr;
+
+/// @brief Test fixture base class for any fixtures which test parsing.
+/// It provides methods for converting JSON strings to configuration element
+/// sets and checking parse results
+class ConfigParseTest : public ::testing::Test {
+public:
+
+ /// @brief Constructor
+ ConfigParseTest(){
+ }
+
+ /// @brief Destructor
+ ~ConfigParseTest() {
+ }
+
+ /// @brief Converts a given JSON string into an Element set and stores the
+ /// result the member variable, config_set_.
+ ///
+ /// @param json_text contains the configuration text in JSON format to
+ /// convert.
+ /// @return returns AssertionSuccess if there were no parsing errors,
+ /// AssertionFailure otherwise.
+ ::testing::AssertionResult fromJSON(std::string& json_text) {
+ try {
+ config_set_ = isc::data::Element::fromJSON(json_text);
+ } catch (const isc::Exception &ex) {
+ return ::testing::AssertionFailure()
+ << "JSON text failed to parse:" << ex.what();
+ }
+
+ return ::testing::AssertionSuccess();
+ }
+
+
+ /// @brief Compares the status in the parse result stored in member
+ /// variable answer_ to a given value.
+ ///
+ /// @param should_be is an integer against which to compare the status.
+ ///
+ /// @return returns AssertionSuccess if there were no parsing errors,
+ /// AssertionFailure otherwise.
+ ::testing::AssertionResult checkAnswer(int should_be) {
+ int rcode = 0;
+ isc::data::ConstElementPtr comment;
+ comment = isc::config::parseAnswer(rcode, answer_);
+ if (rcode == should_be) {
+ return testing::AssertionSuccess();
+ }
+
+ return ::testing::AssertionFailure() << "checkAnswer rcode:"
+ << rcode << " comment: " << *comment << std::endl;
+ }
+
+ /// @brief Configuration set being tested.
+ isc::data::ElementPtr config_set_;
+
+ /// @brief Results of most recent element parsing.
+ isc::data::ConstElementPtr answer_;
+};
+
+/// @brief Defines a small but valid DHCP-DDNS compliant configuration for
+/// testing configuration parsing fundamentals.
+extern const char* valid_d2_config;
+
}; // namespace isc::d2
}; // namespace isc
diff --git a/src/bin/d2/tests/test_data_files_config.h.in b/src/bin/d2/tests/test_data_files_config.h.in
new file mode 100644
index 0000000..6064d3d
--- /dev/null
+++ b/src/bin/d2/tests/test_data_files_config.h.in
@@ -0,0 +1,17 @@
+// Copyright (C) 2009 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+/// @brief Path to D2 source dir so tests against the dhcp-ddns.spec file
+/// can find it reliably.
+#define D2_SRC_DIR "@abs_top_srcdir@/src/bin/d2"
diff --git a/src/lib/dhcpsrv/dhcp_parsers.cc b/src/lib/dhcpsrv/dhcp_parsers.cc
index d21538b..4d1dc73 100644
--- a/src/lib/dhcpsrv/dhcp_parsers.cc
+++ b/src/lib/dhcpsrv/dhcp_parsers.cc
@@ -79,9 +79,9 @@ DebugParser::DebugParser(const std::string& param_name)
void
DebugParser::build(ConstElementPtr new_config) {
+ value_ = new_config;
std::cout << "Build for token: [" << param_name_ << "] = ["
<< value_->str() << "]" << std::endl;
- value_ = new_config;
}
void
More information about the bind10-changes
mailing list