summaryrefslogtreecommitdiffstats
path: root/src/bin/d2/tests/d2_simple_parser_unittest.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/bin/d2/tests/d2_simple_parser_unittest.cc')
-rw-r--r--src/bin/d2/tests/d2_simple_parser_unittest.cc1194
1 files changed, 1194 insertions, 0 deletions
diff --git a/src/bin/d2/tests/d2_simple_parser_unittest.cc b/src/bin/d2/tests/d2_simple_parser_unittest.cc
new file mode 100644
index 0000000..b236469
--- /dev/null
+++ b/src/bin/d2/tests/d2_simple_parser_unittest.cc
@@ -0,0 +1,1194 @@
+// Copyright (C) 2017-2021 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.com/MPL/2.0/.
+
+#include <config.h>
+#include <gtest/gtest.h>
+#include <cc/data.h>
+#include <d2/tests/parser_unittest.h>
+#include <d2srv/d2_simple_parser.h>
+#include <testutils/test_to_element.h>
+
+#include <boost/lexical_cast.hpp>
+
+using namespace isc;
+using namespace isc::data;
+using namespace isc::d2;
+using namespace isc::test;
+
+namespace {
+
+/// @brief Checks if specified element matches the given integer default
+///
+/// @param element defaulted element to check
+/// @param deflt SimpleDefault which supplied the default value
+void checkIntegerValue(const ConstElementPtr& element,
+ const SimpleDefault& deflt) {
+ ASSERT_TRUE(element);
+
+ // Verify it is an integer.
+ ASSERT_EQ(Element::integer, element->getType());
+
+ // Turn default value string into an int.
+ int64_t default_value = 0;
+ ASSERT_NO_THROW(default_value = boost::lexical_cast<int64_t>(deflt.value_));
+
+ // Verify it has the expected value.
+ EXPECT_EQ(default_value, element->intValue());
+}
+
+/// @brief Checks if specified element matches the given boolean default
+///
+/// @param element defaulted element to check
+/// @param deflt SimpleDefault which supplied the default value
+void checkBooleanValue(const ConstElementPtr& element,
+ const SimpleDefault& deflt) {
+ ASSERT_TRUE(element);
+
+ // Verify it is a bool.
+ ASSERT_EQ(Element::boolean, element->getType());
+
+ // Turn default value string into a bool.
+ bool default_value = false;
+ ASSERT_NO_THROW(boost::lexical_cast<bool>(deflt.value_));
+
+ // Verify it has the expected value.
+ EXPECT_EQ(default_value, element->boolValue());
+}
+
+/// @brief Checks if specified element matches the given string default
+///
+/// @param element defaulted element to check
+/// @param deflt SimpleDefault which supplied the default value
+void checkStringValue(const ConstElementPtr& element,
+ const SimpleDefault& deflt) {
+ ASSERT_TRUE(element);
+
+ // Verify it's a string
+ ASSERT_EQ(Element::string, element->getType());
+
+ // Verify it has the expected value
+ EXPECT_EQ(deflt.value_, element->stringValue());
+ }
+
+/// TSIGKeyInfo against the given set of values, and that the TSIGKey
+/// member points to a key.
+///
+/// @param key is a pointer to the TSIGKeyInfo instance to verify
+/// @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 std::string& name,
+ const std::string& algorithm, const std::string& secret,
+ uint32_t digestbits = 0) {
+ // Return value, assume its a match.
+ return (((key) &&
+ (key->getName() == name) &&
+ (key->getAlgorithm() == algorithm) &&
+ (key->getDigestbits() == digestbits) &&
+ (key->getSecret() == secret) &&
+ (key->getTSIGKey())));
+}
+
+/// @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 generated 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 Base class test fixture for testing JSON and element parsing
+/// for D2 configuration elements. It combines the three phases of
+/// configuration parsing normally orchestrated by D2CfgMgr:
+/// 1. Submit the JSON text to the JSON parser
+/// 2. Add defaults to the element tree produced by the JSON parser
+/// 3. Pass the element tree into the appropriate SimpleParser derivation
+/// to parse the element tree into D2 objects.
+class D2SimpleParserTest : public ::testing::Test {
+public:
+ /// @brief Constructor
+ ///
+ /// @param parser_type specifies the parsing starting point at which
+ /// the JSON parser should begin. It defaults to PARSER_JSON. See @c
+ /// D2ParserContext::ParserType for all possible values.
+ D2SimpleParserTest(const D2ParserContext::ParserType&
+ parser_type = D2ParserContext::PARSER_JSON)
+ : parser_type_(parser_type) {
+ reset();
+ }
+
+ /// @brief Destructor
+ virtual ~D2SimpleParserTest() {
+ reset();
+ }
+
+ /// @brief Parses JSON text and compares the results against an expected
+ /// outcome.
+ ///
+ /// The JSON text is submitted to the D2ParserContext for parsing. Any
+ /// errors emitted here are caught and compared against the expected
+ /// error or flagged as unexpected.
+ /// Next, the virtual method, setDefaults()is invoked. his method should
+ /// be used by derivations to add default values to the element tree
+ /// produced by the JSON parser.
+ /// Lastly, it passes the element tree into the virtual method,
+ /// parseElement(). This method should be used by derivations to create
+ /// the appropriate element parser to parse the element tree into the
+ /// appropriate D2 object(s).
+ ///
+ /// @param json JSON text to parse
+ /// @param exp_error exact text of the error message expected or ""
+ /// if parsing should succeed.
+ ::testing::AssertionResult parseOrFail(const std::string& json,
+ const std::string& exp_error) {
+ try {
+ // Free up objects created by previous invocation
+ reset();
+
+ // Submit JSON text to JSON parser. We convert the result to
+ // a mutable element tree to allow defaults to be added.
+ D2ParserContext context;
+ data::ElementPtr elem = boost::const_pointer_cast<Element>
+ (context.parseString(json, parser_type_));
+ // Add any defaults
+ setDefaults(elem);
+
+ // Now parse the element tree into object(s).
+ parseElement(elem);
+ } catch (const std::exception& ex) {
+ std::string caught_error = ex.what();
+ if (exp_error.empty()) {
+ return ::testing::AssertionFailure()
+ << "Unexpected error: " << caught_error
+ << "\n json: [" << json << "]";
+ }
+
+ if (exp_error != caught_error) {
+ return ::testing::AssertionFailure()
+ << "Wrong error detected, expected: "
+ << exp_error << ", got: " << caught_error
+ << "\n json: [" << json << "]";
+ }
+ return ::testing::AssertionSuccess();
+ }
+
+ if (!exp_error.empty()) {
+ return ::testing::AssertionFailure()
+ << "Unexpected parsing success "
+ << exp_error << "\n json: [" << json << "]";
+ }
+
+ return ::testing::AssertionSuccess();
+ }
+
+
+protected:
+ /// @brief Free up objects created by element parsing
+ /// This method is invoked at the beginning of @c parseOrFail() to
+ /// ensure any D2 object(s) that were created by a prior invocation are
+ /// destroyed. This permits parsing to be conducted more than once
+ /// in the same test.
+ virtual void reset(){};
+
+ /// @brief Adds default values to the given element tree
+ ///
+ /// Derivations are expected to use the appropriate methods in
+ /// D2SimpleParser to add defaults values.
+ ///
+ /// @param config element tree in which defaults should be added
+ /// @return the number of default items added to the tree
+ virtual size_t setDefaults(data::ElementPtr config) {
+ static_cast<void>(config);
+ return (0);
+ }
+
+ /// @brief Parses a given element tree into D2 object(s)
+ ///
+ /// Derivations are expected to create the appropriate element
+ /// parser and pass it the element tree for parsing. Any object(s)
+ /// created should likely be saved for content verification
+ /// outside of this method.
+ ///
+ /// @param config element tree to parse
+ virtual void parseElement(data::ConstElementPtr config) {
+ static_cast<void>(config);
+ }
+
+ D2ParserContext::ParserType parser_type_;
+};
+
+/// @brief Convenience macros for calling parseOrFail
+#define PARSE_OK(a) EXPECT_TRUE((parseOrFail(a, "")))
+#define PARSE_FAIL(a,b) EXPECT_TRUE((parseOrFail(a, b)))
+
+// This test checks if global defaults are properly set for D2.
+TEST_F(D2SimpleParserTest, globalD2Defaults) {
+
+ ElementPtr empty = isc::d2::test::parseJSON("{ }");
+ size_t num = 0;
+
+ EXPECT_NO_THROW(num = D2SimpleParser::setAllDefaults(empty));
+
+ // We expect 5 parameters to be inserted.
+ EXPECT_EQ(num, 8);
+
+ // Let's go over all parameters we have defaults for.
+ BOOST_FOREACH(SimpleDefault deflt, D2SimpleParser::D2_GLOBAL_DEFAULTS) {
+ ConstElementPtr x;
+ ASSERT_NO_THROW(x = empty->get(deflt.name_));
+
+ EXPECT_TRUE(x);
+ if (x) {
+ if (deflt.type_ == Element::integer) {
+ checkIntegerValue(x, deflt);
+ } else if (deflt.type_ == Element::boolean) {
+ checkBooleanValue(x, deflt);
+ } else if (deflt.type_ == Element::string) {
+ checkStringValue(x, deflt);
+ } else {
+ // add them if we need to. Like what do you if it's a map?
+ ADD_FAILURE() << "default type not supported:" << deflt.name_
+ << " ,type: " << deflt.type_;
+ }
+ }
+ }
+}
+
+/// @brief Test fixture class for testing TSIGKeyInfo parsing.
+class TSIGKeyInfoParserTest : public D2SimpleParserTest {
+public:
+ /// @brief Constructor
+ TSIGKeyInfoParserTest()
+ : D2SimpleParserTest(D2ParserContext::PARSER_TSIG_KEY) {
+ }
+
+ /// @brief Free up the keys created by parsing
+ virtual void reset() {
+ key_.reset();
+ };
+
+ /// @brief Destructor
+ virtual ~TSIGKeyInfoParserTest() {
+ reset();
+ };
+
+ /// @brief Adds TSIG Key default values to the given TSIG Key element
+ ///
+ /// @param config TSIG Key element to which defaults should be added
+ ///
+ /// @return the number of default items added to the tree
+ size_t setDefaults(data::ElementPtr config) {
+ return (SimpleParser::setDefaults(config, D2SimpleParser::
+ TSIG_KEY_DEFAULTS));
+ }
+
+ /// @brief Attempts to parse the given element into a TSIGKeyInfo
+ ///
+ /// Assumes the given element is a Map containing the attributes for
+ /// a TSIG Key. If parsing is successful the new TSIGKeyInfo instance
+ /// is retained in the member, key_;
+ ///
+ /// @param config element to parse
+ void parseElement(data::ConstElementPtr config) {
+ TSIGKeyInfoParser parser;
+ key_ = parser.parse(config);
+ }
+
+ /// @brief Retains the TSIGKeyInfo created by a successful parsing
+ TSIGKeyInfoPtr key_;
+};
+
+
+/// @brief Test fixture class for testing TSIGKeyInfo list parsing.
+class TSIGKeyInfoListParserTest : public D2SimpleParserTest {
+public:
+ /// @brief Constructor
+ TSIGKeyInfoListParserTest()
+ : D2SimpleParserTest(D2ParserContext::PARSER_TSIG_KEYS) {
+ }
+
+ /// @brief Destructor
+ virtual ~TSIGKeyInfoListParserTest() {
+ reset();
+ }
+
+ /// @brief Free up the keys created by parsing
+ virtual void reset() {
+ keys_.reset();
+ };
+
+ /// @brief Adds TSIG Key default values to a list of TSIG Key elements
+ ///
+ /// @param config list of TSIG Key elements to which defaults should be
+ /// added
+ ///
+ /// @return the number of default items added to the tree
+ size_t setDefaults(data::ElementPtr config) {
+ return (SimpleParser::setListDefaults(config, D2SimpleParser::
+ TSIG_KEY_DEFAULTS));
+ }
+
+ /// @brief Attempts to parse the given element into a list of TSIGKeyInfos
+ ///
+ /// Assumes the given element is a list containing one or more TSIG Keys
+ /// elements. If parsing is successful the list of TSIGKeyInfo instances
+ /// is retained in the member, keys_;
+ ///
+ /// @param config element to parse
+ void parseElement(data::ConstElementPtr config) {
+ TSIGKeyInfoListParser parser;
+ keys_ = parser.parse(config);
+ }
+
+ /// @brief Retains the TSIGKeyInfos created by a successful parsing
+ TSIGKeyInfoMapPtr keys_;
+};
+
+/// @brief Test fixture class for testing DnsServerInfo parsing.
+class DnsServerInfoParserTest : public D2SimpleParserTest {
+public:
+ /// @brief Constructor
+ DnsServerInfoParserTest()
+ : D2SimpleParserTest(D2ParserContext::PARSER_DNS_SERVER) {
+ }
+
+ /// @brief Destructor
+ virtual ~DnsServerInfoParserTest() {
+ reset();
+ }
+
+ /// @brief Free up the server created by parsing
+ virtual void reset() {
+ server_.reset();
+ }
+
+ /// @brief Adds DNS Server default values to the given DNS Server element
+ ///
+ /// @param config DNS Server element to which defaults should be added
+ ///
+ /// @return the number of default items added to the tree
+ virtual size_t setDefaults(data::ElementPtr config) {
+ return (SimpleParser::setDefaults(config, D2SimpleParser::
+ DNS_SERVER_DEFAULTS));
+ }
+
+ /// @brief Attempts to parse the given element into a DnsServerInfo
+ ///
+ /// Assumes the given element is a map containing the attributes for
+ /// a DNS Server. If parsing is successful the new DnsServerInfo instance
+ /// is retained in the member, server_;
+ ///
+ /// @param config element to parse
+ virtual void parseElement(data::ConstElementPtr config) {
+ DnsServerInfoParser parser;
+ std::string domain = "{ \"key-name\": \"\" }";
+ server_ = parser.parse(config, Element::fromJSON(domain), {});
+ }
+
+ /// @brief Retains the DnsServerInfo created by a successful parsing
+ DnsServerInfoPtr server_;
+};
+
+/// @brief Test fixture class for testing DnsServerInfoList parsing.
+class DnsServerInfoListParserTest : public D2SimpleParserTest {
+public:
+ /// @brief Constructor
+ DnsServerInfoListParserTest()
+ : D2SimpleParserTest(D2ParserContext::PARSER_DNS_SERVERS) {
+ }
+
+ /// @brief Destructor
+ virtual ~DnsServerInfoListParserTest() {
+ reset();
+ }
+
+ /// @brief Free up the servers created by parsing
+ virtual void reset() {
+ servers_.reset();
+ }
+
+ /// @brief Adds DNS Server default values to a list of DNS Server elements
+ ///
+ /// @param config list of DNS Server elements to which defaults should be
+ /// added
+ ///
+ /// @return the number of default items added to the tree
+ virtual size_t setDefaults(data::ElementPtr config) {
+ return (SimpleParser::setListDefaults(config, D2SimpleParser::
+ DNS_SERVER_DEFAULTS));
+ }
+
+ /// @brief Attempts to parse the given element into a list of DnsServerInfos
+ ///
+ /// Assumes the given element is a list containing one or more DNS Servers
+ /// elements. If parsing is successful the list of DnsServerInfo instances
+ /// is retained in the member, keys_;
+ ///
+ /// @param config element to parse
+ virtual void parseElement(data::ConstElementPtr config) {
+ DnsServerInfoListParser parser;
+ std::string domain = "{ \"key-name\": \"\" }";
+ servers_ = parser.parse(config, Element::fromJSON(domain), {});
+ }
+
+ /// @brief Retains the DnsServerInfos created by a successful parsing
+ DnsServerInfoStoragePtr servers_;
+};
+
+
+/// @brief Test fixture class for testing DDnsDomain parsing.
+class DdnsDomainParserTest : public D2SimpleParserTest {
+public:
+
+ /// @brief Constructor
+ DdnsDomainParserTest(const D2ParserContext::ParserType& parser_type
+ = D2ParserContext::PARSER_DDNS_DOMAIN)
+ : D2SimpleParserTest(parser_type), keys_(new TSIGKeyInfoMap()) {
+ }
+
+ /// @brief Destructor
+ virtual ~DdnsDomainParserTest() {
+ reset();
+ }
+
+ /// @brief Free up the domain created by parsing
+ virtual void reset() {
+ domain_.reset();
+ }
+
+ /// @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 Adds DDNS Domain values to the given DDNS Domain element
+ ///
+ /// @param config DDNS Domain element to which defaults should be added
+ ///
+ /// @return the number of default items added to the tree
+ virtual size_t setDefaults(data::ElementPtr config) {
+ return (D2SimpleParser::setDdnsDomainDefaults(config, D2SimpleParser::
+ DDNS_DOMAIN_DEFAULTS));
+ }
+
+ /// @brief Attempts to parse the given element into a DdnsDomain
+ ///
+ /// Assumes the given element is a map containing the attributes for
+ /// a DDNS Domain. If parsing is successful the new DdnsDomain instance
+ /// is retained in the member, server_;
+ ///
+ /// @param config element to parse
+ virtual void parseElement(data::ConstElementPtr config) {
+ DdnsDomainParser parser;
+ domain_ = parser.parse(config, keys_);
+ }
+
+ /// @brief Retains the DdnsDomain created by a successful parsing
+ DdnsDomainPtr domain_;
+
+ /// @brief Storage for TSIGKeys, used by DdnsDomainParser to validate
+ /// domain keys
+ TSIGKeyInfoMapPtr keys_;
+};
+
+class DdnsDomainListParserTest : public DdnsDomainParserTest {
+public:
+ /// @brief Constructor
+ DdnsDomainListParserTest()
+ // We need the list context type to parse lists correctly
+ : DdnsDomainParserTest(D2ParserContext::PARSER_DDNS_DOMAINS) {
+ }
+
+ /// @brief Destructor
+ virtual ~DdnsDomainListParserTest() {
+ reset();
+ }
+
+ /// @brief Free up domains created by parsing
+ virtual void reset() {
+ domains_.reset();
+ }
+
+ /// @brief Adds DDNS Domain default values to a list of DDNS Domain elements
+ ///
+ /// @param config list of DDNS Domain elements to which defaults should be
+ /// added
+ ///
+ /// @return the number of default items added to the tree
+ virtual size_t setDefaults(data::ElementPtr config) {
+ size_t cnt = 0;
+ // We don't use SimpleParser::setListDefaults() as this does
+ // not handle sub-lists or sub-maps
+ BOOST_FOREACH(ElementPtr domain, config->listValue()) {
+ cnt += D2SimpleParser::
+ setDdnsDomainDefaults(domain, D2SimpleParser::
+ DDNS_DOMAIN_DEFAULTS);
+ }
+
+ return (cnt);
+ }
+
+ /// @brief Attempts to parse the given element into a list of DdnsDomains
+ ///
+ /// Assumes the given element is a list containing one or more DDNS Domains
+ /// elements. If parsing is successful the list of DdnsDomain instances
+ /// is retained in the member, keys_;
+ ///
+ /// @param config element to parse
+ virtual void parseElement(data::ConstElementPtr config) {
+ DdnsDomainListParser parser;
+ domains_ = parser.parse(config, keys_);
+ }
+
+ /// @brief Retains the DdnsDomains created by a successful parsing
+ DdnsDomainMapPtr domains_;
+};
+
+/// @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.
+TEST_F(TSIGKeyInfoParserTest, invalidEntry) {
+
+ // Name cannot be blank.
+ std::string config = "{"
+ " \"name\": \"\" , "
+ " \"algorithm\": \"HMAC-MD5\" , "
+ " \"secret\": \"LSWXnfkKZjdPJI5QxlpnfQ==\" "
+ "}";
+ PARSE_FAIL(config, "<string>:1.9: TSIG key name cannot be blank");
+
+ // Algorithm cannot be be blank.
+ config = "{"
+ " \"name\": \"d2_key_one\" , "
+ " \"algorithm\": \"\" , "
+ " \"secret\": \"LSWXnfkKZjdPJI5QxlpnfQ==\" "
+ "}";
+ PARSE_FAIL(config, "<string>:1.38: TSIG key algorithm cannot be blank");
+
+ // Algorithm must be a valid algorithm
+ config = "{"
+ " \"name\": \"d2_key_one\" , "
+ " \"algorithm\": \"bogus\" , "
+ " \"secret\": \"LSWXnfkKZjdPJI5QxlpnfQ==\" "
+ "}";
+ PARSE_FAIL(config, "tsig-key : Unknown TSIG Key algorithm:"
+ " bogus (<string>:1:40)");
+
+ // Secret cannot be blank
+ config = "{"
+ " \"name\": \"d2_key_one\" , "
+ " \"algorithm\": \"HMAC-MD5\" , "
+ " \"secret\": \"\" "
+ "}";
+ PARSE_FAIL(config, "<string>:1.62: TSIG key secret cannot be blank");
+
+ // Secret must be valid for algorithm
+ config = "{"
+ " \"name\": \"d2_key_one\" , "
+ " \"algorithm\": \"HMAC-MD5\" , "
+ " \"digest-bits\": 120 , "
+ " \"secret\": \"bogus\" "
+ "}";
+ PARSE_FAIL(config, "Cannot make D2TsigKey: Incomplete input for base64:"
+ " bogus (<string>:1:1)");
+}
+
+
+/// @brief Verifies that TSIGKeyInfo parsing creates a proper TSIGKeyInfo
+/// when given a valid combination of entries.
+TEST_F(TSIGKeyInfoParserTest, validEntry) {
+ // Valid entries for TSIG key, all items are required.
+ std::string config = "{"
+ " \"name\": \"d2_key_one\" , "
+ " \"algorithm\": \"HMAC-MD5\" , "
+ " \"digest-bits\": 120 , "
+ " \"secret\": \"dGhpcyBrZXkgd2lsbCBtYXRjaA==\" "
+ "}";
+ // Verify that it parses.
+ PARSE_OK(config);
+ ASSERT_TRUE(key_);
+
+ // Verify the key contents.
+ EXPECT_TRUE(checkKey(key_, "d2_key_one", "HMAC-MD5",
+ "dGhpcyBrZXkgd2lsbCBtYXRjaA==", 120));
+
+ // Verify unparsing.
+ runToElementTest<TSIGKeyInfo>(config, *key_);
+}
+
+/// @brief Verifies that attempting to parse an invalid list of TSIGKeyInfo
+/// entries is detected.
+TEST_F(TSIGKeyInfoListParserTest, invalidTSIGKeyList) {
+ // Construct a list of keys with an invalid key entry.
+ std::string config = "["
+ " { \"name\": \"key1\" , "
+ " \"algorithm\": \"HMAC-MD5\" ,"
+ " \"digest-bits\": 120 , "
+ " \"secret\": \"GWG/Xfbju4O2iXGqkSu4PQ==\" "
+ " },"
+ // this entry has an invalid algorithm
+ " { \"name\": \"key2\" , "
+ " \"algorithm\": \"\" ,"
+ " \"digest-bits\": 120 , "
+ " \"secret\": \"GWG/Xfbju4O2iXGqkSu4PQ==\" "
+ " },"
+ " { \"name\": \"key3\" , "
+ " \"algorithm\": \"HMAC-MD5\" ,"
+ " \"secret\": \"GWG/Xfbju4O2iXGqkSu4PQ==\" "
+ " }"
+ " ]";
+
+ PARSE_FAIL(config, "<string>:1.151: TSIG key algorithm cannot be blank");
+}
+
+/// @brief Verifies that attempting to parse an invalid list of TSIGKeyInfo
+/// entries is detected.
+TEST_F(TSIGKeyInfoListParserTest, duplicateTSIGKey) {
+ // Construct a list of keys with an invalid key entry.
+ std::string config = "["
+ " { \"name\": \"key1\" , "
+ " \"algorithm\": \"HMAC-MD5\" ,"
+ " \"digest-bits\": 120 , "
+ " \"secret\": \"GWG/Xfbju4O2iXGqkSu4PQ==\" "
+ " },"
+ " { \"name\": \"key2\" , "
+ " \"algorithm\": \"HMAC-MD5\" ,"
+ " \"digest-bits\": 120 , "
+ " \"secret\": \"GWG/Xfbju4O2iXGqkSu4PQ==\" "
+ " },"
+ " { \"name\": \"key1\" , "
+ " \"algorithm\": \"HMAC-MD5\" ,"
+ " \"secret\": \"GWG/Xfbju4O2iXGqkSu4PQ==\" "
+ " }"
+ " ]";
+
+ PARSE_FAIL(config,
+ "Duplicate TSIG key name specified : key1 (<string>:1:239)");
+}
+
+/// @brief Verifies a valid list of TSIG Keys parses correctly.
+/// Also verifies that all of the supported algorithm names work.
+TEST_F(TSIGKeyInfoListParserTest, validTSIGKeyList) {
+ // Construct a valid list of keys.
+ std::string config = "["
+ " { \"name\": \"key1\" , "
+ " \"algorithm\": \"HMAC-MD5\" ,"
+ " \"digest-bits\": 80 , "
+ " \"secret\": \"dGhpcyBrZXkgd2lsbCBtYXRjaA==\" "
+ " },"
+ " { \"name\": \"key2\" , "
+ " \"algorithm\": \"HMAC-SHA1\" ,"
+ " \"digest-bits\": 80 , "
+ " \"secret\": \"dGhpcyBrZXkgd2lsbCBtYXRjaA==\" "
+ " },"
+ " { \"name\": \"key3\" , "
+ " \"algorithm\": \"HMAC-SHA256\" ,"
+ " \"digest-bits\": 128 , "
+ " \"secret\": \"dGhpcyBrZXkgd2lsbCBtYXRjaA==\" "
+ " },"
+ " { \"name\": \"key4\" , "
+ " \"algorithm\": \"HMAC-SHA224\" ,"
+ " \"digest-bits\": 112 , "
+ " \"secret\": \"dGhpcyBrZXkgd2lsbCBtYXRjaA==\" "
+ " },"
+ " { \"name\": \"key5\" , "
+ " \"algorithm\": \"HMAC-SHA384\" ,"
+ " \"digest-bits\": 192 , "
+ " \"secret\": \"dGhpcyBrZXkgd2lsbCBtYXRjaA==\" "
+ " },"
+ " { \"name\": \"key6\" , "
+ " \"algorithm\": \"HMAC-SHA512\" ,"
+ " \"digest-bits\": 256 , "
+ " \"secret\": \"dGhpcyBrZXkgd2lsbCBtYXRjaA==\" "
+ " }"
+ " ]";
+
+ PARSE_OK(config);
+ ASSERT_TRUE(keys_);
+
+ std::string ref_secret = "dGhpcyBrZXkgd2lsbCBtYXRjaA==";
+ // Verify the correct number of keys are present
+ int count = keys_->size();
+ ASSERT_EQ(6, count);
+
+ // 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", TSIGKeyInfo::HMAC_MD5_STR,
+ ref_secret, 80));
+
+ // 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", TSIGKeyInfo::HMAC_SHA1_STR,
+ ref_secret, 80));
+
+ // 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", TSIGKeyInfo::HMAC_SHA256_STR,
+ ref_secret, 128));
+
+ // Find the 4th key and retrieve it.
+ gotit = keys_->find("key4");
+ ASSERT_TRUE(gotit != keys_->end());
+ key = gotit->second;
+
+ // Verify the key contents.
+ EXPECT_TRUE(checkKey(key, "key4", TSIGKeyInfo::HMAC_SHA224_STR,
+ ref_secret, 112));
+
+ // Find the 5th key and retrieve it.
+ gotit = keys_->find("key5");
+ ASSERT_TRUE(gotit != keys_->end());
+ key = gotit->second;
+
+ // Verify the key contents.
+ EXPECT_TRUE(checkKey(key, "key5", TSIGKeyInfo::HMAC_SHA384_STR,
+ ref_secret, 192));
+
+ // Find the 6th key and retrieve it.
+ gotit = keys_->find("key6");
+ ASSERT_TRUE(gotit != keys_->end());
+ key = gotit->second;
+
+ // Verify the key contents.
+ EXPECT_TRUE(checkKey(key, "key6", TSIGKeyInfo::HMAC_SHA512_STR,
+ ref_secret, 256));
+}
+
+/// @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(DnsServerInfoParserTest, invalidEntry) {
+ // Create a config in which both host and ip address are supplied.
+ // Verify that parsing fails.
+ std::string config = "{ \"hostname\": \"pegasus.example\", "
+ " \"ip-address\": \"127.0.0.1\", "
+ " \"port\": 100} ";
+ PARSE_FAIL(config, "<string>:1.13: hostname is not yet supported");
+
+
+ // Neither host nor ip address supplied
+ // Verify that builds fails.
+ config = "{ \"hostname\": \"\", "
+ " \"ip-address\": \"\", "
+ " \"port\": 100} ";
+ PARSE_FAIL(config, "Dns Server must specify one or the other"
+ " of hostname or IP address (<string>:1:1)");
+
+ // Create a config with a negative port number.
+ // Verify that build fails.
+ config = "{ \"hostname\": \"\", "
+ " \"ip-address\": \"192.168.5.6\" ,"
+ " \"port\": -100 }";
+ PARSE_FAIL(config, "<string>:1.60-63: port must be greater than zero but less than 65536");
+}
+
+
+/// @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(DnsServerInfoParserTest, validEntry) {
+ /// @todo When resolvable hostname is supported you'll need this test.
+ /// // Valid entries for dynamic host
+ /// std::string config = "{ \"hostname\": \"pegasus.example\" }";
+ /// 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.example",
+ /// DnsServerInfo::EMPTY_IP_STR,
+ /// DnsServerInfo::STANDARD_DNS_PORT));
+
+ /// // Start over for a new test.
+ /// reset();
+
+ // Valid entries for static ip
+ std::string config = " { \"hostname\" : \"\", "
+ " \"ip-address\": \"127.0.0.1\" , "
+ " \"port\": 100 }";
+ PARSE_OK(config);
+ ASSERT_TRUE(server_);
+ EXPECT_TRUE(checkServer(server_, "", "127.0.0.1", 100));
+
+ // Verify unparsing.
+ runToElementTest<DnsServerInfo>(config, *server_);
+
+ // Valid entries for static ip, no port
+ // This will fail without invoking set defaults
+ config = " { \"ip-address\": \"192.168.2.5\" }";
+ PARSE_OK(config);
+ ASSERT_TRUE(server_);
+ 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(DnsServerInfoListParserTest, invalidServerList) {
+ // Construct a list of servers with an invalid server entry.
+ std::string config = "[ { \"ip-address\": \"127.0.0.1\" }, "
+ "{ \"ip-address\": \"\" }, "
+ "{ \"ip-address\": \"127.0.0.2\" } ]";
+ PARSE_FAIL(config, "Dns Server must specify one or the other"
+ " of hostname or IP address (<string>:1:34)");
+ ASSERT_FALSE(servers_);
+}
+
+/// @brief Verifies that a list of DnsServerInfo entries parses correctly given
+/// a valid configuration.
+TEST_F(DnsServerInfoListParserTest, validServerList) {
+ // Create a valid list of servers.
+ std::string config = "[ { \"ip-address\": \"127.0.0.1\" }, "
+ "{ \"ip-address\": \"127.0.0.2\" }, "
+ "{ \"ip-address\": \"127.0.0.3\" } ]";
+ PARSE_OK(config);
+
+ // Verify that the server storage contains the correct number of servers.
+ ASSERT_EQ(3, servers_->size());
+
+ // Verify the first server exists and has the correct values.
+ DnsServerInfoPtr server = (*servers_)[0];
+ EXPECT_TRUE(checkServer(server, "", "127.0.0.1",
+ DnsServerInfo::STANDARD_DNS_PORT));
+
+ // Verify the second server exists and has the correct values.
+ server = (*servers_)[1];
+ EXPECT_TRUE(checkServer(server, "", "127.0.0.2",
+ DnsServerInfo::STANDARD_DNS_PORT));
+
+ // Verify the third server exists and has the correct values.
+ server = (*servers_)[2];
+ EXPECT_TRUE(checkServer(server, "", "127.0.0.3",
+ 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 may not be empty.
+/// 4. That a mal-formed server entry is detected.
+/// 5. That an undefined key name is detected.
+TEST_F(DdnsDomainParserTest, invalidDomain) {
+ // Create a domain configuration without a name
+ std::string config = "{ \"key-name\": \"d2_key.example.com\" , "
+ " \"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 } ] } ";
+ PARSE_FAIL(config, "missing parameter 'name' (<string>:1:1)");
+
+ // Create a domain configuration with an empty server list.
+ config = "{ \"name\": \"example.com\" , "
+ " \"key-name\": \"\" , "
+ " \"dns-servers\" : [ "
+ " ] } ";
+ PARSE_FAIL(config, "<string>:1.69: syntax error, unexpected ], expecting {");
+
+ // Create a domain configuration with a mal-formed server entry.
+ config = "{ \"name\": \"example.com\" , "
+ " \"key-name\": \"\" , "
+ " \"dns-servers\" : [ "
+ " { \"ip-address\": \"127.0.0.3\" , "
+ " \"port\": -1 } ] } ";
+ PARSE_FAIL(config, "<string>:1.111-112: port must be greater than zero but less than 65536");
+
+ // Create a domain configuration without an defined key name
+ config = "{ \"name\": \"example.com\" , "
+ " \"key-name\": \"d2_key.example.com\" , "
+ " \"dns-servers\" : [ "
+ " { \"ip-address\": \"127.0.0.3\" , "
+ " \"port\": 300 } ] } ";
+ PARSE_FAIL(config, "DdnsDomain : specifies"
+ " an undefined key: d2_key.example.com (<string>:1:41)");
+}
+
+/// @brief Verifies the basics of parsing of a DdnsDomain.
+TEST_F(DdnsDomainParserTest, validDomain) {
+ // Add a TSIG key to the test key map, so key validation will pass.
+ addKey("d2_key.example.com", "HMAC-MD5", "GWG/Xfbju4O2iXGqkSu4PQ==");
+
+ // Create a valid domain configuration entry containing three valid
+ // servers.
+ std::string config =
+ "{ \"name\": \"example.com\" , "
+ " \"key-name\": \"d2_key.example.com\" , "
+ " \"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 } ] } ";
+ PARSE_OK(config);
+
+ // Domain should exist
+ ASSERT_TRUE(domain_);
+
+ // Verify the name and key_name values.
+ EXPECT_EQ("example.com", domain_->getName());
+ EXPECT_EQ("d2_key.example.com", domain_->getKeyName());
+
+ // Verify that the server list exists and contains the correct number of
+ // servers.
+ const DnsServerInfoStoragePtr& servers = domain_->getServers();
+ ASSERT_TRUE(servers);
+ EXPECT_EQ(3, servers->size());
+
+ // Fetch each server and verify its contents.
+ DnsServerInfoPtr server = (*servers)[0];
+ ASSERT_TRUE(server);
+
+ EXPECT_TRUE(checkServer(server, "", "127.0.0.1", 100));
+ ASSERT_TRUE(server->getTSIGKeyInfo());
+ EXPECT_TRUE(server->getTSIGKeyInfo()->getTSIGKey());
+
+ server = (*servers)[1];
+ ASSERT_TRUE(server);
+
+ EXPECT_TRUE(checkServer(server, "", "127.0.0.2", 200));
+ ASSERT_TRUE(server->getTSIGKeyInfo());
+ EXPECT_TRUE(server->getTSIGKeyInfo()->getTSIGKey());
+
+ server = (*servers)[2];
+ ASSERT_TRUE(server);
+
+ EXPECT_TRUE(checkServer(server, "", "127.0.0.3", 300));
+ ASSERT_TRUE(server->getTSIGKeyInfo());
+ EXPECT_TRUE(server->getTSIGKeyInfo()->getTSIGKey());
+
+ // Verify unparsing.
+ ElementPtr json;
+ ASSERT_NO_THROW(json = Element::fromJSON(config));
+ ConstElementPtr servers_json;
+ ASSERT_NO_THROW(servers_json = json->get("dns-servers"));
+ ASSERT_TRUE(servers_json);
+ ASSERT_EQ(Element::list, servers_json->getType());
+ for (size_t i = 0; i < servers_json->size(); ++i) {
+ ElementPtr server_json;
+ ASSERT_NO_THROW(server_json = servers_json->getNonConst(i));
+ ASSERT_NO_THROW(server_json->set("hostname",
+ Element::create(std::string())));
+ }
+ runToElementTest<DdnsDomain>(json, *domain_);
+}
+
+/// @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(DdnsDomainListParserTest, validList) {
+ // Add keys to key map so key validation passes.
+ addKey("d2_key.example.com", "HMAC-MD5", "GWG/Xfbju4O2iXGqkSu4PQ==");
+ addKey("d2_key.billcat.net", "HMAC-MD5", "GWG/Xfbju4O2iXGqkSu4PQ==");
+
+ // Create a valid domain list configuration, with two domains
+ // that have three servers each.
+ std::string config =
+ "[ "
+ "{ \"name\": \"example.com\" , "
+ " \"key-name\": \"d2_key.example.com\" , "
+ " \"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 } ] } "
+ "] ";
+
+ // Verify that the domain list parses without error.
+ PARSE_OK(config);
+ ASSERT_TRUE(domains_);
+ EXPECT_EQ(2, domains_->size());
+
+ // Verify that the first domain exists and can be retrieved.
+ DdnsDomainMap::iterator gotit = domains_->find("example.com");
+ ASSERT_TRUE(gotit != domains_->end());
+ DdnsDomainPtr& domain = gotit->second;
+
+ // Verify the name and key_name values of the first domain.
+ EXPECT_EQ("example.com", domain->getName());
+ EXPECT_EQ("d2_key.example.com", domain->getKeyName());
+
+ // Verify the each of the first domain's servers
+ DnsServerInfoStoragePtr servers = domain->getServers();
+ ASSERT_TRUE(servers);
+ EXPECT_EQ(3, servers->size());
+
+ DnsServerInfoPtr server = (*servers)[0];
+ ASSERT_TRUE(server);
+ EXPECT_TRUE(checkServer(server, "", "127.0.0.1", 100));
+
+ // Verify the TSIGKeyInfo name and that the actual key was created
+ ASSERT_TRUE(server->getTSIGKeyInfo());
+ EXPECT_EQ(domain->getKeyName(), server->getKeyName());
+ EXPECT_EQ(domain->getKeyName(), server->getTSIGKeyInfo()->getName());
+ EXPECT_TRUE(server->getTSIGKeyInfo()->getTSIGKey());
+
+ server = (*servers)[1];
+ ASSERT_TRUE(server);
+ EXPECT_TRUE(checkServer(server, "", "127.0.0.2", 200));
+ ASSERT_TRUE(server->getTSIGKeyInfo());
+ EXPECT_EQ(domain->getKeyName(), server->getKeyName());
+ EXPECT_EQ(domain->getKeyName(), server->getTSIGKeyInfo()->getName());
+ EXPECT_TRUE(server->getTSIGKeyInfo()->getTSIGKey());
+
+ server = (*servers)[2];
+ ASSERT_TRUE(server);
+ EXPECT_TRUE(checkServer(server, "", "127.0.0.3", 300));
+ ASSERT_TRUE(server->getTSIGKeyInfo());
+ EXPECT_EQ(domain->getKeyName(), server->getKeyName());
+ EXPECT_EQ(domain->getKeyName(), server->getTSIGKeyInfo()->getName());
+ EXPECT_TRUE(server->getTSIGKeyInfo()->getTSIGKey());
+
+ // 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();
+ ASSERT_TRUE(servers);
+ EXPECT_EQ(3, servers->size());
+
+ server = (*servers)[0];
+ ASSERT_TRUE(server);
+ EXPECT_TRUE(checkServer(server, "", "127.0.0.4", 400));
+ ASSERT_TRUE(server->getTSIGKeyInfo());
+ EXPECT_EQ(domain->getKeyName(), server->getKeyName());
+ EXPECT_EQ(domain->getKeyName(), server->getTSIGKeyInfo()->getName());
+ EXPECT_TRUE(server->getTSIGKeyInfo()->getTSIGKey());
+
+ server = (*servers)[1];
+ ASSERT_TRUE(server);
+ EXPECT_TRUE(checkServer(server, "", "127.0.0.5", 500));
+ ASSERT_TRUE(server->getTSIGKeyInfo());
+ EXPECT_EQ(domain->getKeyName(), server->getKeyName());
+ EXPECT_EQ(domain->getKeyName(), server->getTSIGKeyInfo()->getName());
+ EXPECT_TRUE(server->getTSIGKeyInfo()->getTSIGKey());
+
+ server = (*servers)[2];
+ ASSERT_TRUE(server);
+ EXPECT_TRUE(checkServer(server, "", "127.0.0.6", 600));
+ ASSERT_TRUE(server->getTSIGKeyInfo());
+ EXPECT_EQ(domain->getKeyName(), server->getKeyName());
+ EXPECT_EQ(domain->getKeyName(), server->getTSIGKeyInfo()->getName());
+ EXPECT_TRUE(server->getTSIGKeyInfo()->getTSIGKey());
+}
+
+/// @brief Tests that a domain list configuration cannot contain duplicates.
+TEST_F(DdnsDomainListParserTest, duplicateDomain) {
+ // Create a domain list configuration that contains two domains with
+ // the same name.
+ std::string config =
+ "[ "
+ "{ \"name\": \"example.com\" , "
+ " \"dns-servers\" : [ "
+ " { \"ip-address\": \"127.0.0.3\" , "
+ " \"port\": 300 } ] } "
+ ", "
+ "{ \"name\": \"example.com\" , "
+ " \"dns-servers\" : [ "
+ " { \"ip-address\": \"127.0.0.3\" , "
+ " \"port\": 300 } ] } "
+ "] ";
+ // Verify that the parsing fails.
+ PARSE_FAIL(config,
+ "Duplicate domain specified:example.com (<string>:1:115)");
+}
+
+}