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

[2957] This is the second of a two part commit for D2CfgMgr. It Adds a

collection of classes for housing and parsing the application configuration
necessary for the DHCP-DDNS application. This rounds out the D2CfgMgr initial
implementation.

New files:
   src/bin/d2/d2_config.cc
   src/bin/d2/d2_config.h
   src/bin/d2/tests/d2_cfg_mgr_unittests.cc
parent dac0b87d
......@@ -53,6 +53,7 @@ b10_dhcp_ddns_SOURCES += d2_process.cc d2_process.h
b10_dhcp_ddns_SOURCES += d_controller.cc d_controller.h
b10_dhcp_ddns_SOURCES += d2_controller.cc d2_controller.h
b10_dhcp_ddns_SOURCES += d_cfg_mgr.cc d_cfg_mgr.h
b10_dhcp_ddns_SOURCES += d2_config.cc d2_config.h
b10_dhcp_ddns_SOURCES += d2_cfg_mgr.cc d2_cfg_mgr.h
nodist_b10_dhcp_ddns_SOURCES = d2_messages.h d2_messages.cc
......
......@@ -15,6 +15,8 @@
#include <d2/d2_log.h>
#include <d2/d2_cfg_mgr.h>
#include <boost/foreach.hpp>
using namespace std;
using namespace isc;
using namespace isc::dhcp;
......@@ -26,12 +28,21 @@ namespace d2 {
// *********************** D2CfgContext *************************
D2CfgContext::D2CfgContext() {
// @TODO - initialize D2 specific storage
D2CfgContext::D2CfgContext()
: forward_mgr_(new DdnsDomainListMgr("forward_mgr")),
reverse_mgr_(new DdnsDomainListMgr("reverse_mgr")) {
}
D2CfgContext::D2CfgContext(const D2CfgContext& rhs) : DCfgContextBase(rhs)
/* @TODO copy D2 specific storage */ {
D2CfgContext::D2CfgContext(const D2CfgContext& rhs) : DCfgContextBase(rhs) {
if (rhs.forward_mgr_) {
forward_mgr_.reset(new DdnsDomainListMgr(rhs.forward_mgr_->getName()));
forward_mgr_->setDomains(rhs.forward_mgr_->getDomains());
}
if (rhs.reverse_mgr_) {
reverse_mgr_.reset(new DdnsDomainListMgr(rhs.reverse_mgr_->getName()));
reverse_mgr_->setDomains(rhs.reverse_mgr_->getDomains());
}
}
D2CfgContext::~D2CfgContext() {
......@@ -45,12 +56,60 @@ D2CfgMgr::D2CfgMgr() : DCfgMgrBase(DCfgContextBasePtr(new D2CfgContext())) {
D2CfgMgr::~D2CfgMgr() {
}
bool
D2CfgMgr::matchForward(const std::string& fqdn, DdnsDomainPtr& domain) {
if (fqdn == "") {
// This is a programmatic error and should not happen.
isc_throw (D2CfgError, "matchForward passed an empty fqdn");
}
// Fetch the forward manager from the D2 context.
DdnsDomainListMgrPtr& mgr = getD2CfgContext()->getForwardMgr();
// Call the manager's match method and return the result.
return (mgr->matchDomain(fqdn, domain));
}
bool
D2CfgMgr::matchReverse(const std::string& fqdn, DdnsDomainPtr& domain) {
if (fqdn == "") {
// This is a programmatic error and should not happen.
isc_throw (D2CfgError, "matchReverse passed a null or empty fqdn");
}
// Fetch the reverse manager from the D2 context.
DdnsDomainListMgrPtr& mgr = getD2CfgContext()->getReverseMgr();
// Call the manager's match method and return the result.
return (mgr->matchDomain(fqdn, domain));
}
isc::dhcp::ParserPtr
D2CfgMgr::createConfigParser(const std::string& element_id) {
// @TODO This is only enough implementation for integration.
// This will expand to support the top level D2 elements.
// For now we will simply return a debug parser for everything.
return (isc::dhcp::ParserPtr(new isc::dhcp::DebugParser(element_id)));
D2CfgMgr::createConfigParser(const std::string& config_id) {
// Get D2 specific context.
D2CfgContextPtr context = getD2CfgContext();
// Create parser instance based on element_id.
DhcpConfigParser* parser = NULL;
if ((config_id == "interface") ||
(config_id == "ip_address")) {
parser = new StringParser(config_id, context->getStringStorage());
} else if (config_id == "port") {
parser = new Uint32Parser(config_id, context->getUint32Storage());
} else if (config_id == "forward_ddns") {
parser = new DdnsDomainListMgrParser("forward_mgr",
context->getForwardMgr());
} else if (config_id == "reverse_ddns") {
parser = new DdnsDomainListMgrParser("reverse_mgr",
context->getReverseMgr());
} else {
isc_throw(NotImplemented,
"parser error: D2CfgMgr parameter not supported: "
<< config_id);
}
return (isc::dhcp::ParserPtr(parser));
}
}; // end of isc::dhcp namespace
......
......@@ -12,9 +12,11 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
#include <asiolink/io_address.h>
#include <cc/data.h>
#include <exceptions/exceptions.h>
#include <d2/d_cfg_mgr.h>
#include <d2/d2_config.h>
#include <stdint.h>
#include <string>
......@@ -28,11 +30,9 @@ namespace d2 {
/// @brief DHCP-DDNS Configuration Context
/// Implements the storage container for configuration context.
/// It provides a single enclosure for the storage of configuration parameters
/// and any other context specific information that needs to be accessible
/// and any other DHCP-DDNS specific information that needs to be accessible
/// during configuration parsing as well as to the application as a whole.
/// @TODO - add in storage of D2 specific storage like domain-to-dns_server
/// mapping. This is the initial implementation necessary to integrate
/// configuration management into D2.
/// It is derived from the context base class, DCfgContextBase.
class D2CfgContext : public DCfgContextBase {
public:
/// @brief Constructor
......@@ -42,11 +42,26 @@ public:
virtual ~D2CfgContext();
/// @brief Creates a clone of this context object.
///
/// @return returns a raw pointer to the new clone.
virtual D2CfgContext* clone() {
return (new D2CfgContext(*this));
}
/// @brief Fetches the forward DNS domain list manager.
///
/// @return returns a pointer reference to the forward manager.
DdnsDomainListMgrPtr& getForwardMgr() {
return (forward_mgr_);
}
/// @brief Fetches the reverse DNS domain list manager.
///
/// @return returns a pointer reference to the reverse manager.
DdnsDomainListMgrPtr& getReverseMgr() {
return (reverse_mgr_);
}
protected:
/// @brief Copy constructor for use by derivations in clone().
D2CfgContext(const D2CfgContext& rhs);
......@@ -55,9 +70,17 @@ private:
/// @brief Private assignment operator to avoid potential for slicing.
D2CfgContext& operator=(const D2CfgContext& rhs);
/// @TODO storage for DNS domain-server mapping will be added here
/// @brief Forward domain list manager.
DdnsDomainListMgrPtr forward_mgr_;
/// @brief Reverse domain list manager.
DdnsDomainListMgrPtr reverse_mgr_;
};
/// @brief Defines a pointer for DdnsDomain instances.
typedef boost::shared_ptr<DdnsDomainListMgr> DdnsDomainListMgrPtr;
/// @brief Pointer to a configuration context.
typedef boost::shared_ptr<D2CfgContext> D2CfgContextPtr;
......@@ -67,7 +90,6 @@ typedef boost::shared_ptr<D2CfgContext> D2CfgContextPtr;
/// configuration. This includes services for parsing sets of configuration
/// values, storing the parsed information in its converted form,
/// and retrieving the information on demand.
/// @TODO add in D2 specific parsing
class D2CfgMgr : public DCfgMgrBase {
public:
/// @brief Constructor
......@@ -85,13 +107,43 @@ public:
return (boost::dynamic_pointer_cast<D2CfgContext>(getContext()));
}
/// @brief Matches a given FQDN to a forward domain.
///
/// This calls the matchDomain method of the forward domain manager to
/// match the given FQDN to a forward domain.
///
/// @param fqdn is the name for which to look.
/// @param domain receives the matching domain. Note that it will be reset
/// upon entry and only set if a match is subsequently found.
///
/// @return returns true if a match is found, false otherwise.
/// @throw throws D2CfgError if given an invalid fqdn.
bool matchForward(const std::string& fdqn, DdnsDomainPtr &domain);
/// @brief Matches a given FQDN to a reverse domain.
///
/// This calls the matchDomain method of the reverse domain manager to
/// match the given FQDN to a forward domain.
///
/// @param fqdn is the name for which to look.
/// @param domain receives the matching domain. Note that it will be reset
/// upon entry and only set if a match is subsequently found.
///
/// @return returns true if a match is found, false otherwise.
/// @throw throws D2CfgError if given an invalid fqdn.
bool matchReverse(const std::string& fdqn, DdnsDomainPtr &domain);
protected:
/// @brief Given an element_id returns an instance of the appropriate
/// parser.
/// @TODO The initial implementation simply returns a DebugParser for any
/// element_id value. This is sufficient to integrate configuration
/// management into D2. Specific parsers will be added as the DHCP-DDNS
/// specific configuration is constructed.
///
/// It is responsible for top-level or outermost DHCP-DDNS configuration
/// elements (see dhcp-ddns.spec):
/// 1. interface
/// 2. ip_address
/// 3. port
/// 4. forward_ddns
/// 5. reverse_ddns
///
/// @param element_id is the string name of the element as it will appear
/// in the configuration set.
......
// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
#include <d2/d2_log.h>
#include <d2/d2_cfg_mgr.h>
#include <dhcpsrv/dhcp_parsers.h>
#include <exceptions/exceptions.h>
#include <asiolink/io_error.h>
#include <boost/foreach.hpp>
#include <boost/lexical_cast.hpp>
namespace isc {
namespace d2 {
// *********************** DnsServerInfo *************************
const uint32_t DnsServerInfo::standard_dns_port = 53;
const char* DnsServerInfo::empty_ip_str = "0.0.0.0";
DnsServerInfo::DnsServerInfo(const std::string& hostname,
isc::asiolink::IOAddress ip_address, uint32_t port,
bool enabled)
:hostname_(hostname), ip_address_(ip_address), port_(port),
enabled_(enabled) {
}
DnsServerInfo::~DnsServerInfo() {
}
// *********************** DdnsDomain *************************
DdnsDomain::DdnsDomain(const std::string& name, const std::string& key_name,
DnsServerInfoStoragePtr servers)
: name_(name), key_name_(key_name), servers_(servers) {
}
DdnsDomain::~DdnsDomain() {
}
// *********************** DdnsDomainLstMgr *************************
DdnsDomainListMgr::DdnsDomainListMgr(const std::string& name) : name_(name),
domains_(new DdnsDomainStorage()) {
}
DdnsDomainListMgr::~DdnsDomainListMgr () {
}
void
DdnsDomainListMgr::setDomains(DdnsDomainStoragePtr domains) {
if (!domains) {
isc_throw(D2CfgError,
"DdnsDomainListMgr::setDomains: Domain list may not be null");
}
domains_ = domains;
// Iterate over the domain list lookfing for the wild card domain. If
// present, set the member variable to remember it. This saves us from
// having to look for it every time we match.
DdnsDomainPtrPair domain_pair;
BOOST_FOREACH(domain_pair, (*domains_)) {
DdnsDomainPtr domain = domain_pair.second;
const std::string& domain_name = domain->getName();
if (domain_name == "*") {
wildcard_domain_ = domain;
break;
}
}
}
bool
DdnsDomainListMgr::matchDomain(const std::string& fqdn, DdnsDomainPtr& domain) {
// Clear the return parameter.
domain.reset();
// First check the case of one domain to rule them all.
if ((size() == 1) && (wildcard_domain_)) {
domain = wildcard_domain_;
return (true);
}
// Start with the longest version of the fqdn and search the list.
// Continue looking for shorter versions of fqdn so long as no match is
// found.
// @TODO This can surely be optimized, time permitting.
std::string match_name = fqdn;
std::size_t start_pos = 0;
while (start_pos != std::string::npos) {
match_name = match_name.substr(start_pos, std::string::npos);
DdnsDomainStorage::iterator gotit = domains_->find(match_name);
if (gotit != domains_->end()) {
domain = gotit->second;
break;
}
start_pos = match_name.find_first_of(".");
if (start_pos != std::string::npos) {
start_pos++;
}
}
if (!domain) {
// There's no match. If they specified a wild card domain use it
// otherwise there's no domain for this entry.
if (wildcard_domain_) {
domain = wildcard_domain_;
} else {
LOG_WARN(dctl_logger, DHCP_DDNS_NO_MATCH).arg(fqdn);
return (false);
}
}
return (true);
}
// *************************** PARSERS ***********************************
// *********************** DnsServerInfoParser *************************
DnsServerInfoParser::DnsServerInfoParser(const std::string& entry_name,
DnsServerInfoStoragePtr servers)
: entry_name_(entry_name), servers_(servers), local_scalars_() {
if (!servers_) {
isc_throw(D2CfgError, "DdnsServerInfoParser ctor:"
" server storage cannot be null");
}
}
DnsServerInfoParser::~DnsServerInfoParser() {
}
void
DnsServerInfoParser::build(isc::data::ConstElementPtr server_config) {
isc::dhcp::ConfigPair config_pair;
// For each element in the server configuration:
// 1. Create a parser for the element.
// 2. Invoke the parser's build method passing in the element's
// configuration.
// 3. Invoke the parser's commit method to store the element's parsed
// data to the parser's local storage.
BOOST_FOREACH (config_pair, server_config->mapValue()) {
isc::dhcp::ParserPtr parser(createConfigParser(config_pair.first));
parser->build(config_pair.second);
parser->commit();
}
}
isc::dhcp::ParserPtr
DnsServerInfoParser::createConfigParser(const std::string& config_id) {
DhcpConfigParser* parser = NULL;
// Based on the configuration id of the element, create the appropriate
// parser. Scalars are set to use the parser's local scalar storage.
if ((config_id == "hostname") ||
(config_id == "ip_address")) {
parser = new isc::dhcp::StringParser(config_id,
local_scalars_.getStringStorage());
} else if (config_id == "port") {
parser = new isc::dhcp::Uint32Parser(config_id,
local_scalars_.getUint32Storage());
} else {
isc_throw(NotImplemented,
"parser error: DnsServerInfo parameter not supported: "
<< config_id);
}
// Return the new parser instance.
return (isc::dhcp::ParserPtr(parser));
}
void
DnsServerInfoParser::commit() {
std::string hostname;
std::string ip_address;
uint32_t port = DnsServerInfo::standard_dns_port;
// Fetch the server configuration's paresed scalar values from parser's
// local storage.
local_scalars_.getParam("hostname", hostname, DCfgContextBase::optional_);
local_scalars_.getParam("ip_address", ip_address,
DCfgContextBase::optional_);
local_scalars_.getParam("port", port, DCfgContextBase::optional_);
// The configuration cannot specify both hostname and ip_address.
if ((hostname != "") && (ip_address != "")) {
isc_throw(D2CfgError,
"Dns Server cannot specify both hostname and static IP address");
}
// The configuration must specify one or the other.
if ((hostname == "") && (ip_address == "")) {
isc_throw(D2CfgError,
"Dns Server must specify either hostname or static IP address");
}
DnsServerInfo* serverInfo = NULL;
if (hostname != "") {
// When hostname is specified, create a valid, blank IOAddress and
// then create the DnsServerInfo.
isc::asiolink::IOAddress io_addr(DnsServerInfo::empty_ip_str);
serverInfo = new DnsServerInfo(hostname, io_addr, port);
} else {
try {
// Create an IOAddress from the IP address string given and then
// create the DnsServerInfo.
isc::asiolink::IOAddress io_addr(ip_address);
serverInfo = new DnsServerInfo(hostname, io_addr, port);
} catch (const isc::asiolink::IOError& ex) {
isc_throw(D2CfgError, "Invalid IP address:" << ip_address);
}
}
// Add the new DnsServerInfo to the server storage.
servers_->push_back(DnsServerInfoPtr(serverInfo));
}
// *********************** DnsServerInfoListParser *************************
DnsServerInfoListParser::DnsServerInfoListParser(const std::string& list_name,
DnsServerInfoStoragePtr servers)
:list_name_(list_name), servers_(servers), parsers_() {
if (!servers_) {
isc_throw(D2CfgError, "DdnsServerInfoListParser ctor:"
" server storage cannot be null");
}
}
DnsServerInfoListParser::~DnsServerInfoListParser(){
}
void
DnsServerInfoListParser::
build(isc::data::ConstElementPtr server_list){
int i = 0;
isc::data::ConstElementPtr server_config;
// For each server element in the server list:
// 1. Create a parser for the server element.
// 2. Invoke the parser's build method passing in the server's
// configuration.
// 3. Add the parser to a local collection of parsers.
BOOST_FOREACH(server_config, server_list->listValue()) {
// Create a name for the parser based on its position in the list.
std::string entry_name = boost::lexical_cast<std::string>(i++);
isc::dhcp::ParserPtr parser(new DnsServerInfoParser(entry_name,
servers_));
parser->build(server_config);
parsers_.push_back(parser);
}
}
void
DnsServerInfoListParser::commit() {
// Domains must have at least one server.
if (parsers_.size() == 0) {
isc_throw (D2CfgError, "Server List must contain at least one server");
}
// Invoke commit on each server parser. This will cause each one to
// create it's server instance and commit it to storage.
BOOST_FOREACH(isc::dhcp::ParserPtr parser, parsers_) {
parser->commit();
}
}
// *********************** DdnsDomainParser *************************
DdnsDomainParser::DdnsDomainParser(const std::string& entry_name,
DdnsDomainStoragePtr domains)
: entry_name_(entry_name), domains_(domains),
local_servers_(new DnsServerInfoStorage()), local_scalars_() {
if (!domains_) {
isc_throw(D2CfgError,
"DdnsDomainParser ctor, domain storage cannot be null");
}
}
DdnsDomainParser::~DdnsDomainParser() {
}
void
DdnsDomainParser::build(isc::data::ConstElementPtr domain_config) {
// For each element in the domain configuration:
// 1. Create a parser for the element.
// 2. Invoke the parser's build method passing in the element's
// configuration.
// 3. Invoke the parser's commit method to store the element's parsed
// data to the parser's local storage.
isc::dhcp::ConfigPair config_pair;
BOOST_FOREACH(config_pair, domain_config->mapValue()) {
isc::dhcp::ParserPtr parser(createConfigParser(config_pair.first));
parser->build(config_pair.second);
parser->commit();
}
}
isc::dhcp::ParserPtr
DdnsDomainParser::createConfigParser(const std::string& config_id) {
DhcpConfigParser* parser = NULL;
// Based on the configuration id of the element, create the appropriate
// parser. Scalars are set to use the parser's local scalar storage.
if ((config_id == "name") ||
(config_id == "key_name")) {
parser = new isc::dhcp::StringParser(config_id,
local_scalars_.getStringStorage());
} else if (config_id == "dns_servers") {
// Server list parser is given in our local server storage. It will pass
// this down to its server parsers and is where they will write their
// server instances upon commit.
parser = new DnsServerInfoListParser(config_id, local_servers_);
} else {
isc_throw(NotImplemented,
"parser error: DdnsDomain parameter not supported: "
<< config_id);
}
// Return the new domain parser instance.
return (isc::dhcp::ParserPtr(parser));
}
void
DdnsDomainParser::commit() {
std::string name;
std::string key_name;
// Domain name is not optional. The get will throw if its not there.
local_scalars_.getParam("name", name);
// Blank domain names are not allowed.
if (name == "") {
isc_throw(D2CfgError, "Domain name cannot be blank");
}
// Currently, the premise is that domain storage is always empty
// prior to parsing so always adding domains never replacing them.
// Duplicates are not allowed and should be flagged as a configuration
// error.
if (domains_->find(name) != domains_->end()) {
isc_throw(D2CfgError, "Duplicate domain specified:" << name);
}
// Key name is optional and for now, unused. It is intended to be
// used as the name of the TSIG key this domain should use.
local_scalars_.getParam("key_name", key_name, DCfgContextBase::optional_);
// Instantiate the new domain and add it to domain storage.
DdnsDomainPtr domain(new DdnsDomain(name, key_name, local_servers_));
// Add the new domain to the domain storage.
(*domains_)[name]=domain;
}
// *********************** DdnsDomainListParser *************************
DdnsDomainListParser::DdnsDomainListParser(const std::string& list_name,
DdnsDomainStoragePtr domains)
:list_name_(list_name), domains_(domains), parsers_() {
if (!domains_) {
isc_throw(D2CfgError, "DdnsDomainListParser ctor:"
" domain storage cannot be null");
}
}
DdnsDomainListParser::~DdnsDomainListParser(){
}
void
DdnsDomainListParser::
build(isc::data::ConstElementPtr domain_list){
// For each domain element in the domain list:
// 1. Create a parser for the domain element.
// 2. Invoke the parser's build method passing in the domain's
// configuration.