Browse Source

[fdunparse2] Some fixes, did D2, began CA

Francis Dupont 8 years ago
parent
commit
406bb64b20

+ 1 - 0
configure.ac

@@ -1686,6 +1686,7 @@ AC_CONFIG_FILES([Makefile
                  src/bin/agent/Makefile
                  src/bin/agent/tests/Makefile
                  src/bin/agent/tests/ca_process_tests.sh
+                 src/bin/agent/tests/test_libraries.h
                  src/bin/d2/Makefile
                  src/bin/d2/tests/Makefile
                  src/bin/d2/tests/d2_process_tests.sh

+ 28 - 0
src/bin/agent/ca_cfg_mgr.cc

@@ -158,6 +158,34 @@ CtrlAgentCfgContext::setControlSocketInfo(const isc::data::ConstElementPtr& cont
     ctrl_sockets_[static_cast<uint8_t>(type)] = control_socket;
 }
 
+ElementPtr
+CtrlAgentCfgContext::toElement() const {
+    ElementPtr result = Element::createMap();
+    // Set http-host
+    result->set("http-host", Element::create(http_host_));
+    // Set http-port
+    result->set("http-port",
+                Element::create(static_cast<int64_t>(http_port_)));
+    // hooks-libraries
+    result->set("hooks-libraries", hooks_config_.toElement());
+    // control-sockets
+    ElementPtr control_sockets = Element::createMap();
+    // dhcp4-server
+    if (ctrl_sockets_[TYPE_DHCP4]) {
+        control_sockets->set("dhcp4-server", ctrl_sockets_[TYPE_DHCP4]);
+    }
+    // dhcp6-server
+    if (ctrl_sockets_[TYPE_DHCP6]) {
+        control_sockets->set("dhcp6-server", ctrl_sockets_[TYPE_DHCP6]);
+    }
+    // d2-server
+    if (ctrl_sockets_[TYPE_D2]) {
+        control_sockets->set("d2-server", ctrl_sockets_[TYPE_D2]);
+    }
+    result->set("control-sockets", control_sockets);
+
+    return (result);
+}
 
 } // namespace isc::agent
 } // namespace isc

+ 11 - 0
src/bin/agent/ca_cfg_mgr.h

@@ -116,6 +116,17 @@ public:
         return (hooks_config_);
     }
 
+    /// @brief Unparse a configuration objet
+    ///
+    /// Returns an element which must parse into the same objet, i.e.
+    /// @code
+    /// for all valid config C parse(parse(C)->toElement()) == parse(C)
+    /// @endcode
+    ///
+    /// @return a pointer to a configuration which can be parsed into
+    /// the initial configuration object
+    virtual isc::data::ElementPtr toElement() const;
+
 private:
 
     /// @brief Private copy constructor

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

@@ -13,6 +13,8 @@
 
 #include <boost/foreach.hpp>
 
+using namespace isc::asiolink;
+using namespace isc::data;
 using namespace isc::process;
 
 namespace isc {
@@ -51,6 +53,50 @@ D2CfgContext::D2CfgContext(const D2CfgContext& rhs) : DCfgContextBase(rhs) {
 D2CfgContext::~D2CfgContext() {
 }
 
+ElementPtr
+D2CfgContext::toElement() const {
+    ElementPtr d2 = Element::createMap();
+    // Set ip-address
+    const IOAddress& ip_address = d2_params_->getIpAddress();
+    d2->set("ip-address", Element::create(ip_address.toText()));
+    // Set port
+    size_t port = d2_params_->getPort();
+    d2->set("port", Element::create(static_cast<int64_t>(port)));
+    // Set dns-server-timeout
+    size_t dns_server_timeout = d2_params_->getDnsServerTimeout();
+    d2->set("dns-server-timeout",
+            Element::create(static_cast<int64_t>(dns_server_timeout)));
+    // Set ncr-protocol
+    const dhcp_ddns::NameChangeProtocol& ncr_protocol =
+        d2_params_->getNcrProtocol();
+    d2->set("ncr-protocol",
+            Element::create(dhcp_ddns::ncrProtocolToString(ncr_protocol)));
+    // Set ncr-format
+    const dhcp_ddns::NameChangeFormat& ncr_format = d2_params_->getNcrFormat();
+    d2->set("ncr-format",
+            Element::create(dhcp_ddns::ncrFormatToString(ncr_format)));
+    // Set forward-ddns
+    ElementPtr forward_ddns = Element::createMap();
+    forward_ddns->set("ddns-domains", forward_mgr_->toElement());
+    d2->set("forward-ddns", forward_ddns);
+    // Set reverse-ddns
+    ElementPtr reverse_ddns = Element::createMap();
+    reverse_ddns->set("ddns-domains", reverse_mgr_->toElement());
+    d2->set("reverse-ddns", reverse_ddns);
+    // Set tsig-keys
+    ElementPtr tsig_keys = Element::createList();
+    for (TSIGKeyInfoMap::const_iterator key = keys_->begin();
+         key != keys_->end(); ++key) {
+        tsig_keys->add(key->second->toElement());
+    }
+    d2->set("tsig-keys", tsig_keys);
+    // Set DhcpDdns
+    ElementPtr result = Element::createMap();
+    result->set("DhcpDdns", d2);
+
+    return (result);
+}
+
 // *********************** D2CfgMgr  *************************
 
 const char* D2CfgMgr::IPV4_REV_ZONE_SUFFIX = "in-addr.arpa.";

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

@@ -90,6 +90,11 @@ public:
         keys_ = keys;
     }
 
+    /// @brief Unparse a configuration objet
+    ///
+    /// @return a pointer to a configuration
+    virtual isc::data::ElementPtr toElement() const;
+
 protected:
     /// @brief Copy constructor for use by derivations in clone().
     D2CfgContext(const D2CfgContext& rhs);

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

@@ -20,6 +20,7 @@
 #include <string>
 
 using namespace isc::process;
+using namespace isc::data;
 
 namespace isc {
 namespace d2 {
@@ -180,6 +181,22 @@ TSIGKeyInfo::remakeKey() {
     }
 }
 
+ElementPtr
+TSIGKeyInfo::toElement() const {
+    ElementPtr result = Element::createMap();
+    // Set name
+    result->set("name", Element::create(name_));
+    // Set algorithm
+    result->set("algorithm", Element::create(algorithm_));
+    // Set secret
+    result->set("secret", Element::create(secret_));
+    // Set digest-bits
+    result->set("digest-bits",
+                Element::create(static_cast<int64_t>(digestbits_)));
+
+    return (result);
+}
+
 // *********************** DnsServerInfo  *************************
 DnsServerInfo::DnsServerInfo(const std::string& hostname,
                              isc::asiolink::IOAddress ip_address, uint32_t port,
@@ -198,6 +215,19 @@ DnsServerInfo::toText() const {
     return (stream.str());
 }
 
+ElementPtr
+DnsServerInfo::toElement() const {
+    ElementPtr result = Element::createMap();
+    // Set hostname
+    result->set("hostname", Element::create(hostname_));
+    // Set ip-address
+    result->set("ip-address", Element::create(ip_address_.toText()));
+    // Set port
+    result->set("port", Element::create(static_cast<int64_t>(port_)));
+
+    return (result);
+}
+
 
 std::ostream&
 operator<<(std::ostream& os, const DnsServerInfo& server) {
@@ -226,6 +256,30 @@ DdnsDomain::getKeyName() const {
     return ("");
 }
 
+ElementPtr
+DdnsDomain::toElement() const {
+    ElementPtr result = Element::createMap();
+    // Set name
+    result->set("name", Element::create(name_));
+    // Set servers
+    ElementPtr servers = Element::createList();
+    for (DnsServerInfoStorage::const_iterator server = servers_->begin();
+         server != servers_->end(); ++server) {
+        ElementPtr dns_server = (*server)->toElement();
+        servers->add(dns_server);
+    }
+    // the dns server list may not be empty
+    if (!servers->empty()) {
+        result->set("dns-servers", servers);
+    }
+    // Set key-name
+    if (tsig_key_info_) {
+        result->set("key-name", Element::create(tsig_key_info_->getName()));
+    }
+
+    return (result);
+}
+
 // *********************** DdnsDomainLstMgr  *************************
 
 const char* DdnsDomainListMgr::wildcard_domain_name_ = "*";
@@ -321,6 +375,19 @@ DdnsDomainListMgr::matchDomain(const std::string& fqdn, DdnsDomainPtr& domain) {
     return (true);
 }
 
+ElementPtr
+DdnsDomainListMgr::toElement() const {
+    ElementPtr result = Element::createList();
+    // Iterate on ddns domains
+    for (DdnsDomainMap::const_iterator domain = domains_->begin();
+         domain != domains_->end(); ++domain) {
+        ElementPtr ddns_domain = domain->second->toElement();
+        result->add(ddns_domain);
+    }
+
+    return (result);
+}
+
 // *************************** PARSERS ***********************************
 
 // *********************** TSIGKeyInfoParser  *************************

+ 32 - 4
src/bin/d2/d2_config.h

@@ -10,6 +10,7 @@
 #include <asiolink/io_service.h>
 #include <cc/data.h>
 #include <cc/simple_parser.h>
+#include <cc/cfg_to_element.h>
 #include <dhcpsrv/parsers/dhcp_parsers.h>
 #include <dns/tsig.h>
 #include <exceptions/exceptions.h>
@@ -255,7 +256,7 @@ typedef boost::shared_ptr<D2Params> D2ParamsPtr;
 /// instance of the actual key (@ref isc::dns::TSIGKey) that can be used
 /// by the IO layer for signing and verifying messages.
 ///
-class TSIGKeyInfo {
+class TSIGKeyInfo : public isc::data::CfgToElement {
 public:
     /// @brief Defines string values for the supported TSIG algorithms
     //@{
@@ -356,6 +357,11 @@ public:
     static const dns::Name& stringToAlgorithmName(const std::string&
                                                   algorithm_id);
 
+    /// @brief Unparse a configuration objet
+    ///
+    /// @return a pointer to a configuration
+    virtual isc::data::ElementPtr toElement() const;
+
 private:
     /// @brief Creates the actual TSIG key instance member
     ///
@@ -405,7 +411,7 @@ typedef boost::shared_ptr<TSIGKeyInfoMap> TSIGKeyInfoMapPtr;
 /// 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 {
+class DnsServerInfo : public isc::data::CfgToElement {
 public:
     /// @brief defines DNS standard port value
     static const uint32_t STANDARD_DNS_PORT = 53;
@@ -472,6 +478,11 @@ public:
     /// @brief Returns a text representation for the server.
     std::string toText() const;
 
+    /// @brief Unparse a configuration objet
+    ///
+    /// @return a pointer to a configuration
+    virtual isc::data::ElementPtr toElement() const;
+
 
 private:
     /// @brief The resolvable name of the server. If not blank, then the
@@ -510,7 +521,7 @@ typedef boost::shared_ptr<DnsServerInfoStorage> DnsServerInfoStoragePtr;
 /// @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 {
+class DdnsDomain : public isc::data::CfgToElement {
 public:
     /// @brief Constructor
     ///
@@ -553,6 +564,11 @@ public:
         return (tsig_key_info_);
     }
 
+    /// @brief Unparse a configuration objet
+    ///
+    /// @return a pointer to a configuration
+    virtual isc::data::ElementPtr toElement() const;
+
 private:
     /// @brief The domain name of the domain.
     std::string name_;
@@ -588,7 +604,7 @@ typedef boost::shared_ptr<DdnsDomainMap> DdnsDomainMapPtr;
 /// specify the wild card domain as the only forward domain. All forward DNS
 /// updates would be sent to that one list of servers, regardless of the FQDN.
 /// As matching capabilities evolve this class is expected to expand.
-class DdnsDomainListMgr {
+class DdnsDomainListMgr : public isc::data::CfgToElement {
 public:
     /// @brief defines the domain name for denoting the wildcard domain.
     static const char* wildcard_domain_name_;
@@ -655,6 +671,11 @@ public:
     /// set the internal wild card domain pointer accordingly.
     void setDomains(DdnsDomainMapPtr domains);
 
+    /// @brief Unparse a configuration objet
+    ///
+    /// @return a pointer to a configuration
+    virtual isc::data::ElementPtr toElement() const;
+
 private:
     /// @brief An arbitrary label assigned to this manager.
     std::string name_;
@@ -697,6 +718,13 @@ public:
         return (process::DCfgContextBasePtr(new DScalarContext(*this)));
     }
 
+    /// @brief Unparse a configuration objet
+    ///
+    /// @return a pointer to a configuration
+    virtual isc::data::ElementPtr toElement() const {
+        isc_throw(isc::NotImplemented, "DScalarContext::ElementPtr");
+    }
+
 protected:
     /// @brief Copy constructor
     DScalarContext(const DScalarContext& rhs) : DCfgContextBase(rhs) {

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

@@ -6,7 +6,7 @@ SHTESTS += d2_process_tests.sh
 noinst_SCRIPTS = d2_process_tests.sh
 
 EXTRA_DIST  = d2_process_tests.sh.in
-EXTRA_DIST += testdata/d2_cfg_tests.json
+EXTRA_DIST += testdata/d2_cfg_tests.json testdata/get_config.json
 
 # test using command-line arguments, so use check-local target instead of TESTS
 check-local:
@@ -58,6 +58,7 @@ d2_unittests_SOURCES += nc_trans_unittests.cc
 d2_unittests_SOURCES += d2_controller_unittests.cc
 d2_unittests_SOURCES += d2_simple_parser_unittest.cc
 d2_unittests_SOURCES += parser_unittest.cc parser_unittest.h
+d2_unittests_SOURCES += get_config_unittest.cc
 
 d2_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
 d2_unittests_LDFLAGS = $(AM_LDFLAGS) $(CRYPTO_LDFLAGS)

+ 260 - 0
src/bin/d2/tests/get_config_unittest.cc

@@ -0,0 +1,260 @@
+// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <cc/data.h>
+#include <cc/command_interpreter.h>
+#include <process/testutils/d_test_stubs.h>
+#include <d2/d2_config.h>
+#include <d2/d2_cfg_mgr.h>
+#include <d2/tests/parser_unittest.h>
+#include <gtest/gtest.h>
+
+#include <iostream>
+#include <fstream>
+#include <string>
+#include <sstream>
+
+#include "test_data_files_config.h"
+
+using namespace isc::config;
+using namespace isc::d2;
+using namespace isc::data;
+using namespace isc::process;
+
+namespace {
+
+/// @name How to generate the testdata/get_config.json file
+///
+/// Define GENERATE_ACTION and recompile. Run d2_unittests on
+/// D2GetConfigTest redirecting the standard error to a temporary
+/// file, e.g. by
+/// @code
+///    ./d2_unittests --gtest_filter="D2GetConfig*" > /dev/null 2> u
+/// @endcode
+///
+/// Update testdata/get_config.json using the temporary file content,
+/// recompile without GENERATE_ACTION.
+
+/// @brief the generate action
+/// false means do nothing, true means unparse extracted configurations
+#ifdef GENERATE_ACTION
+const bool generate_action = true;
+#else
+const bool generate_action = false;
+#endif
+
+/// @brief Read a file into a string
+std::string
+readFile(const std::string& file_path) {
+    std::ifstream ifs(file_path);
+    if (!ifs.is_open()) {
+        ADD_FAILURE() << "readFile cannot open " << file_path;
+        isc_throw(isc::Unexpected, "readFile cannot open " << file_path);
+    }
+    std::string lines;
+    std::string line;
+    while (std::getline(ifs, line)) {
+        lines += line + "\n";
+    }
+    ifs.close();
+    return (lines);
+}
+
+/// @brief Runs parser in JSON mode
+ElementPtr
+parseJSON(const std::string& in,  bool verbose = false) {
+    try {
+        D2ParserContext ctx;
+        return (ctx.parseString(in, D2ParserContext::PARSER_JSON));
+    } catch (const std::exception& ex) {
+        if (verbose) {
+            std::cout << "EXCEPTION: " << ex.what() << std::endl;
+        }
+        throw;
+    }
+}
+
+/// @brief Runs parser in DHCPDDNS mode
+ElementPtr
+parseDHCPDDNS(const std::string& in,  bool verbose = false) {
+    try {
+        D2ParserContext ctx;
+        return (ctx.parseString(in, D2ParserContext::PARSER_DHCPDDNS));
+    } catch (const std::exception& ex) {
+        if (verbose) {
+            std::cout << "EXCEPTION: " << ex.what() << std::endl;
+        }
+        throw;
+    }
+}
+
+}
+
+/// Test fixture class
+class D2GetConfigTest : public ConfigParseTest {
+public:
+    D2GetConfigTest()
+    : rcode_(-1) {
+        srv_.reset(new D2CfgMgr());
+        // Create fresh context.
+        resetConfiguration();
+    }
+
+    ~D2GetConfigTest() {
+        resetConfiguration();
+    }
+
+    /// @brief Parse and Execute configuration
+    ///
+    /// Parses a configuration and executes a configuration of the server.
+    /// If the operation fails, the current test will register a failure.
+    ///
+    /// @param config Configuration to parse
+    /// @param operation Operation being performed.  In the case of an error,
+    ///        the error text will include the string "unable to <operation>.".
+    ///
+    /// @return true if the configuration succeeded, false if not.
+    bool
+    executeConfiguration(const std::string& config, const char* operation) {
+        // try JSON parser
+        ConstElementPtr json;
+        try {
+            json = parseJSON(config, true);
+        } catch (const std::exception& ex) {
+            ADD_FAILURE() << "invalid JSON for " << operation
+                          << " failed with " << ex.what()
+                          << " on\n" << config << "\n";
+            return (false);
+        }
+
+        // try DHCPDDNS parser
+        try {
+            json = parseDHCPDDNS(config, true);
+        } catch (...) {
+            ADD_FAILURE() << "parsing failed for " << operation
+                          << " on\n" << prettyPrint(json) << "\n";
+            return (false);
+        }
+
+        // get DhcpDdns element
+        ConstElementPtr d2 = json->get("DhcpDdns");
+        if (!d2) {
+            ADD_FAILURE() << "cannot get DhcpDdns for " << operation
+                          << " on\n" << prettyPrint(json) << "\n";
+            return (false);
+        }
+
+        // try DHCPDDNS configure
+        ConstElementPtr status;
+        try {
+            status = srv_->parseConfig(d2);
+        } catch (const std::exception& ex) {
+            ADD_FAILURE() << "configure for " << operation
+                          << " failed with " << ex.what()
+                          << " on\n" << prettyPrint(json) << "\n";
+            return (false);
+        }
+
+        // The status object must not be NULL
+        if (!status) {
+            ADD_FAILURE() << "configure for " << operation
+                          << " returned null on\n"
+                          << prettyPrint(json) << "\n";
+            return (false);
+        }
+
+        // Returned value should be 0 (configuration success)
+        comment_ = parseAnswer(rcode_, status);
+        if (rcode_ != 0) {
+            string reason = "";
+            if (comment_) {
+                reason = string(" (") + comment_->stringValue() + string(")");
+            }
+            ADD_FAILURE() << "configure for " << operation
+                          << " returned error code "
+                          << rcode_ << reason << " on\n"
+                          << prettyPrint(json) << "\n";
+            return (false);
+        }
+        return (true);
+    }
+
+    /// @brief Reset configuration database.
+    ///
+    /// This function resets configuration data base by
+    /// removing control sockets and domain lists. Reset must
+    /// be performed after each test to make sure that
+    /// contents of the database do not affect result of
+    /// subsequent tests.
+    void resetConfiguration() {
+        string config = "{ \"DhcpDdns\": {"
+            " \"ip-address\": \"127.0.0.1\","
+            " \"port\": 53001,"
+            " \"dns-server-timeout\": 100,"
+            " \"ncr-protocol\": \"UDP\","
+            " \"ncr-format\": \"JSON\","
+            " \"tsig-keys\": [ ],"
+            " \"forward-ddns\": { },"
+            " \"reverse-ddns\": { } } }";
+        EXPECT_TRUE(executeConfiguration(config, "reset config"));
+    }
+
+    boost::scoped_ptr<D2CfgMgr> srv_;  ///< D2 server under test
+    int rcode_;                        ///< Return code from element parsing
+    ConstElementPtr comment_;          ///< Reason for parse fail
+};
+
+/// Test a configuration
+TEST_F(D2GetConfigTest, sample1) {
+
+    // get the sample1 configuration
+    std::string sample1_file = string(CFG_EXAMPLES) + "/" + "sample1.json";
+    std::string config;
+    ASSERT_NO_THROW(config = readFile(sample1_file));
+
+    // get the expected configuration
+    std::string expected_file =
+        std::string(D2_TEST_DATA_DIR) + "/" + "get_config.json";
+    std::string expected;
+    ASSERT_NO_THROW(expected = readFile(expected_file));
+
+    // execute the sample configuration
+    ASSERT_TRUE(executeConfiguration(config, "sample1 config"));
+
+    // unparse it
+    D2CfgContextPtr context = srv_->getD2CfgContext();
+    ConstElementPtr unparsed;
+    ASSERT_NO_THROW(unparsed = context->toElement());
+
+    // dump if wanted else check
+    if (generate_action) {
+        std::cerr << "/ Generated Configuration (remove this line)\n";
+        ASSERT_NO_THROW(expected = prettyPrint(unparsed));
+        prettyPrint(unparsed, std::cerr, 0, 4);
+        std::cerr << "\n";
+    } else {
+        ConstElementPtr json;
+        ASSERT_NO_THROW(json = parseDHCPDDNS(expected, true));
+        EXPECT_TRUE(isEquivalent(unparsed, json));
+        std::string current = prettyPrint(unparsed, 0, 4) + "\n";
+        EXPECT_EQ(expected, current);
+        if (expected != current) {
+            expected = current;
+        }
+    }
+
+    // execute the d2 configuration
+    EXPECT_TRUE(executeConfiguration(expected, "unparsed config"));
+
+    // is it a fixed point?
+    D2CfgContextPtr context2 = srv_->getD2CfgContext();
+    ConstElementPtr unparsed2;
+    ASSERT_NO_THROW(unparsed2 = context2->toElement());
+    ASSERT_TRUE(unparsed2);
+    EXPECT_TRUE(isEquivalent(unparsed, unparsed2));
+}

+ 74 - 0
src/bin/d2/tests/testdata/get_config.json

@@ -0,0 +1,74 @@
+{
+    "DhcpDdns": {
+        "dns-server-timeout": 1000,
+        "forward-ddns": {
+            "ddns-domains": [
+                {
+                    "dns-servers": [
+                        {
+                            "hostname": "",
+                            "ip-address": "172.16.1.1",
+                            "port": 53
+                        }
+                    ],
+                    "key-name": "d2.md5.key",
+                    "name": "four.example.com."
+                },
+                {
+                    "dns-servers": [
+                        {
+                            "hostname": "",
+                            "ip-address": "2001:db8:1::10",
+                            "port": 7802
+                        }
+                    ],
+                    "name": "six.example.com."
+                }
+            ]
+        },
+        "ip-address": "172.16.1.10",
+        "ncr-format": "JSON",
+        "ncr-protocol": "UDP",
+        "port": 53001,
+        "reverse-ddns": {
+            "ddns-domains": [
+                {
+                    "dns-servers": [
+                        {
+                            "hostname": "",
+                            "ip-address": "172.16.1.1",
+                            "port": 53001
+                        },
+                        {
+                            "hostname": "",
+                            "ip-address": "192.168.2.10",
+                            "port": 53
+                        }
+                    ],
+                    "key-name": "d2.sha1.key",
+                    "name": "2.0.192.in-addr.arpa."
+                }
+            ]
+        },
+        "tsig-keys": [
+            {
+                "algorithm": "HMAC-MD5",
+                "digest-bits": 0,
+                "name": "d2.md5.key",
+                "secret": "LSWXnfkKZjdPJI5QxlpnfQ=="
+            },
+            {
+                "algorithm": "HMAC-SHA1",
+                "digest-bits": 0,
+                "name": "d2.sha1.key",
+                "secret": "hRrp29wzUv3uzSNRLlY68w=="
+            },
+            {
+                "algorithm": "HMAC-SHA512",
+                "digest-bits": 256,
+                "name": "d2.sha512.key",
+                "secret": "/4wklkm04jeH4anx2MKGJLcya+ZLHldL5d6mK+4q6UXQP7KJ9mS2QG29hh0SJR4LA0ikxNJTUMvir42gLx6fGQ=="
+            }
+        ]
+    }
+}

File diff suppressed because it is too large
+ 5535 - 2
src/bin/dhcp4/tests/get_config_unittest.cc


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

@@ -367,7 +367,7 @@ CfgSubnets4::toElement() const {
         // Set client-class
         const ClientClasses& cclasses = (*subnet)->getClientClasses();
         if (cclasses.size() > 1) {
-            isc_throw(ToElementError, "client-classes has too many items: "
+            isc_throw(ToElementError, "client-class has too many items: "
                       << cclasses.size());
         } else if (!cclasses.empty()) {
             map->set("client-class", Element::create(*cclasses.cbegin()));

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

@@ -392,7 +392,7 @@ CfgSubnets6::toElement() const {
         // Set client-class
         const ClientClasses& cclasses = (*subnet)->getClientClasses();
         if (cclasses.size() > 1) {
-            isc_throw(ToElementError, "client-classes has too many items: "
+            isc_throw(ToElementError, "client-class has too many items: "
                       << cclasses.size());
         } else if (!cclasses.empty()) {
             map->set("client-class", Element::create(*cclasses.cbegin()));

+ 10 - 2
src/lib/dhcpsrv/client_class_def.cc

@@ -5,6 +5,7 @@
 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 #include <dhcpsrv/client_class_def.h>
+#include <dhcpsrv/cfgmgr.h>
 #include <boost/foreach.hpp>
 
 using namespace isc::data;
@@ -112,13 +113,20 @@ ClientClassDef::equals(const ClientClassDef& other) const {
 
 ElementPtr
 ClientClassDef:: toElement() const {
+    uint16_t family = CfgMgr::instance().getFamily();
     ElementPtr result = Element::createMap();
     // Set name
     result->set("name", Element::create(name_));
-    // Set original match expression
-    result->set("test", Element::create(test_));
+    // Set original match expression (empty string won't parse)
+    if (!test_.empty()) {
+        result->set("test", Element::create(test_));
+    }
     // Set option-data
     result->set("option-data", cfg_option_->toElement());
+    if (family != AF_INET) {
+        // Other parameters are DHCPv4 specific
+        return (result);
+    }
     // Set next-server
     result->set("next-server", Element::create(next_server_.toText()));
     // Set server-hostname

+ 17 - 12
src/lib/dhcpsrv/logging_info.cc

@@ -23,6 +23,22 @@ LoggingDestination::equals(const LoggingDestination& other) const {
             flush_ == other.flush_);
 }
 
+ElementPtr
+LoggingDestination::toElement() const {
+    ElementPtr result = Element::createMap();
+
+    // Set output
+    result->set("output", Element::create(output_));
+    // Set maxver
+    result->set("maxver", Element::create(maxver_));
+    // Set maxsize
+    result->set("maxsize", Element::create(static_cast<long long>(maxsize_)));
+    // Set flush
+    result->set("flush", Element::create(flush_));
+
+    return(result);
+}
+
 LoggingInfo::LoggingInfo()
     : name_("kea"), severity_(isc::log::INFO), debuglevel_(0) {
     // If configuration Manager is in the verbose mode, we need to modify the
@@ -148,18 +164,7 @@ LoggingInfo::toElement() const {
     for (std::vector<LoggingDestination>::const_iterator dest =
              destinations_.cbegin();
          dest != destinations_.cend(); ++dest) {
-        ElementPtr map = Element::createMap();
-        // Set output
-        map->set("output", Element::create(dest->output_));
-        // Set maxver
-        map->set("maxver", Element::create(dest->maxver_));
-        // Set maxsize
-        map->set("maxsize",
-                 Element::create(static_cast<long long>(dest->maxsize_)));
-        // Set flush
-        map->set("flush", Element::create(dest->flush_));
-        // Push on output option list
-        options->add(map);
+        options->add(dest->toElement());
     }
     result->set("output_options", options);
     // Set severity

+ 6 - 1
src/lib/dhcpsrv/logging_info.h

@@ -19,7 +19,7 @@ namespace dhcp {
 /// @brief Defines single logging destination
 ///
 /// This structure is used to keep log4cplus configuration parameters.
-struct LoggingDestination {
+struct LoggingDestination : public isc::data::CfgToElement {
 
     /// @brief defines logging destination output
     ///
@@ -47,6 +47,11 @@ struct LoggingDestination {
     LoggingDestination()
         : output_("stdout"), maxver_(1), maxsize_(204800), flush_(true) {
     }
+
+    /// @brief Unparse a configuration objet
+    ///
+    /// @return a pointer to unparsed configuration
+    virtual isc::data::ElementPtr toElement() const;
 };
 
 /// @brief structure that describes one logging entry

+ 10 - 9
src/lib/dhcpsrv/srv_config.cc

@@ -306,15 +306,16 @@ SrvConfig::toElement() const {
     // Set DhcpX
     result->set(family == AF_INET ? "Dhcp4" : "Dhcp6", dhcp);
 
-    // Logging global map (skip if loggers is empty)
-    ElementPtr logging = Element::createMap();
-    // Set loggers list
-    ElementPtr loggers = Element::createList();
-    for (LoggingInfoStorage::const_iterator logger = logging_info_.cbegin();
-         logger != logging_info_.cend(); ++logger) {
-        loggers->add(logger->toElement());
-    }
-    if (!loggers->empty()) {
+    // Logging global map (skip if empty)
+    if (!logging_info_.empty()) {
+        ElementPtr logging = Element::createMap();
+        // Set loggers list
+        ElementPtr loggers = Element::createList();
+        for (LoggingInfoStorage::const_iterator logger =
+                 logging_info_.cbegin();
+             logger != logging_info_.cend(); ++logger) {
+            loggers->add(logger->toElement());
+        }
         logging->set("loggers", loggers);
         result->set("Logging", logging);
     }

+ 3 - 1
src/lib/dhcpsrv/tests/client_class_def_unittest.cc

@@ -6,6 +6,7 @@
 
 #include <config.h>
 #include <dhcpsrv/client_class_def.h>
+#include <dhcpsrv/cfgmgr.h>
 #include <dhcp/option_space.h>
 #include <testutils/test_to_element.h>
 #include <exceptions/exceptions.h>
@@ -357,6 +358,7 @@ TEST(ClientClassDef, fixedFieldsBasics) {
 
 // Verifies the unparse method of option class definitions
 TEST(ClientClassDef, unparseDef) {
+    CfgMgr::instance().setFamily(AF_INET);
     boost::scoped_ptr<ClientClassDef> cclass;
 
     // Get a client class definition and fill it
@@ -385,6 +387,7 @@ TEST(ClientClassDef, unparseDef) {
 
 // Verifies the unparse method of client class dictionaries
 TEST(ClientClassDictionary, unparseDict) {
+    CfgMgr::instance().setFamily(AF_INET);
     ClientClassDictionaryPtr dictionary;
     ExpressionPtr expr;
     CfgOptionPtr options;
@@ -400,7 +403,6 @@ TEST(ClientClassDictionary, unparseDict) {
         [](std::string name) {
             return ("{\n"
                     "\"name\": \"" + name + "\",\n"
-                    "\"test\": \"\",\n"
                     "\"next-server\": \"0.0.0.0\",\n"
                     "\"server-hostname\": \"\",\n"
                     "\"boot-file-name\": \"\",\n"

+ 13 - 1
src/lib/process/d_cfg_mgr.h

@@ -8,6 +8,7 @@
 #define D_CFG_MGR_H
 
 #include <cc/data.h>
+#include <cc/cfg_to_element.h>
 #include <exceptions/exceptions.h>
 #include <dhcpsrv/parsers/dhcp_parsers.h>
 
@@ -57,7 +58,7 @@ typedef boost::shared_ptr<DCfgContextBase> DCfgContextBasePtr;
 ///    // Restore from backup
 ///    context_ = backup_copy;
 ///
-class DCfgContextBase {
+class DCfgContextBase : public isc::data::CfgToElement {
 public:
     /// @brief Indicator that a configuration parameter is optional.
     static const bool OPTIONAL = true;
@@ -180,6 +181,17 @@ public:
     /// @return returns a pointer to the new clone.
     virtual DCfgContextBasePtr clone() = 0;
 
+    /// @brief Unparse a configuration objet
+    ///
+    /// Returns an element which must parse into the same objet, i.e.
+    /// @code
+    /// for all valid config C parse(parse(C)->toElement()) == parse(C)
+    /// @endcode
+    ///
+    /// @return a pointer to a configuration which can be parsed into
+    /// the initial configuration object
+    virtual isc::data::ElementPtr toElement() const = 0;
+
 protected:
     /// @brief Copy constructor for use by derivations in clone().
     DCfgContextBase(const DCfgContextBase& rhs);

+ 2 - 2
src/lib/process/tests/d_cfg_mgr_unittests.cc

@@ -54,8 +54,8 @@ public:
 /// @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.
+/// strings to configuration element sets, checking parse results,
+/// accessing the configuration context and trying to unparse.
 class DStubCfgMgrTest : public ConfigParseTest {
 public:
 

+ 5 - 0
src/lib/process/testutils/d_test_stubs.cc

@@ -342,6 +342,11 @@ DStubContext::DStubContext(const DStubContext& rhs): DCfgContextBase(rhs),
     object_values_(new ObjectStorage(*(rhs.object_values_))) {
 }
 
+isc::data::ElementPtr
+DStubContext::toElement() const {
+    return (isc::data::Element::createMap());
+}
+
 //************************** DStubCfgMgr *************************
 
 DStubCfgMgr::DStubCfgMgr()

+ 5 - 0
src/lib/process/testutils/d_test_stubs.h

@@ -646,6 +646,11 @@ private:
 
     /// @brief Stores non-scalar configuration elements
     ObjectStoragePtr object_values_;
+
+    /// @brief Unparse a configuration objet
+    ///
+    /// @return a pointer to a configuration
+    virtual isc::data::ElementPtr toElement() const;
 };
 
 /// @brief Defines a pointer to DStubContext.