123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469 |
- // Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
- //
- // Permission to use, copy, modify, and/or distribute this software for any
- // purpose with or without fee is hereby granted, provided that the above
- // copyright notice and this permission notice appear in all copies.
- //
- // THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
- // REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
- // AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
- // INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
- // LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
- // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
- // PERFORMANCE OF THIS SOFTWARE.
- #include <config.h>
- #include <cc/data.h>
- #include <dhcpsrv/cfgmgr.h>
- #include <dhcpsrv/parsers/client_class_def_parser.h>
- #include <gtest/gtest.h>
- #include <sstream>
- #include <stdint.h>
- #include <string>
- /// @file client_class_def_parser_unittest.cc Unit tests for client class
- /// definition parsing.
- using namespace isc::data;
- using namespace isc::dhcp;
- namespace {
- /// @brief Test fixture class for @c ClientClassDefParser.
- class ClientClassDefParserTest : public ::testing::Test {
- protected:
- /// @brief Convenience method for parsing a configuration
- ///
- /// Attempt to parse a given client class defintion.
- ///
- /// @param config - JSON string containing the client class configuration
- /// to parse.
- /// @param universe - the universe in which the parsing context should
- /// occur.
- /// @return Returns a pointer to class instance created, or NULL if
- /// for some unforeseen reason it wasn't created in the local dictionary
- /// @throw indirectly, execptions convertring the JSON text to elements,
- /// or by the parsing itself are not caught
- ClientClassDefPtr parseClientClassDef(const std::string& config,
- Option::Universe universe) {
- // Create local dicitonary to which the parser add the class.
- ClientClassDictionaryPtr dictionary(new ClientClassDictionary());
- // Create the "global" context for the parser.
- ParserContextPtr context(new ParserContext(universe));
- // Turn config into elements. This may emit exceptions.
- ElementPtr config_element = Element::fromJSON(config);
- // Parse the configuration. This may emit exceptions.
- ClientClassDefParser parser("", dictionary, context);
- parser.build(config_element);
- // If we didn't throw, then return the first and only class
- ClientClassDefMapPtr classes = dictionary->getClasses();
- ClientClassDefMap::iterator it = classes->begin();
- if (it != classes->end()) {
- return (*it).second;
- }
- // Return NULL if for some reason the class doesn't exist.
- return (ClientClassDefPtr());
- }
- };
- /// @brief Test fixture class for @c ClientClassDefListParser.
- class ClientClassDefListParserTest : public ::testing::Test {
- protected:
- /// @brief Convenience method for parsing a list of client class
- /// definitions.
- ///
- /// Attempt to parse a given list of client class defintions into a
- /// ClientClassDictionary.
- ///
- /// @param config - JSON string containing the list of definitions to parse.
- /// @param universe - the universe in which the parsing context should
- /// occur.
- /// @return Returns a pointer to class dictionary created
- /// @throw indirectly, execptions convertring the JSON text to elements,
- /// or by the parsing itself are not caught
- ClientClassDictionaryPtr parseClientClassDefList(const std::string& config,
- Option::Universe universe)
- {
- // Create the "global" context for the parser.
- ParserContextPtr context(new ParserContext(universe));
- // Turn config into elements. This may emit exceptions.
- ElementPtr config_element = Element::fromJSON(config);
- // Parse the configuration. This may emit exceptions.
- ClientClassDefListParser parser("", context);
- parser.build(config_element);
- // Commit should push it to CfgMgr staging
- parser.commit();
- // Return the parser's local dicationary
- return (parser.local_dictionary_);
- }
- };
- // Verifies basic operation of an ExpressionParser. Until we tie
- // this into the actual Bison parsing there's not much to test.
- TEST(ExpressionParserTest, simpleStringExpression) {
- ParserContextPtr context(new ParserContext(Option::V4));
- ExpressionParserPtr parser;
- ExpressionPtr parsed_expr;
- // Turn config into elements. This may emit exceptions.
- std::string cfg_txt = "\"astring\"";
- ElementPtr config_element = Element::fromJSON(cfg_txt);
- // Create the parser.
- ASSERT_NO_THROW(parser.reset(new ExpressionParser("", parsed_expr,
- context)));
- // Expression should parse and commit.
- ASSERT_NO_THROW(parser->build(config_element));
- ASSERT_NO_THROW(parser->commit());
- // Parsed expression should exist.
- ASSERT_TRUE(parsed_expr);
- // Evaluate it. For now the result will be the
- // expression string as dummy ExpressionParser
- // just makes an expression of one TokenString
- // containing the expression string itself.
- ValueStack vstack;
- Pkt4Ptr pkt4(new Pkt4(DHCPDISCOVER, 12345));
- (*parsed_expr)[0]->evaluate(*pkt4, vstack);
- EXPECT_EQ(vstack.top(), "\"astring\"");
- }
- // Verifies that given an invalid expression, the Expression parser
- // will throw a DhdpConfigError. Note this is not intended to be
- // an exhaustive test or expression syntax. It is simply to ensure
- // that if the parser fails, it does so properly. For now, since
- // our parser is a dummy parser which only checks that it's given
- // Element::string so send it an integer.
- TEST(ExpressionParserTest, invalidExpression) {
- ParserContextPtr context(new ParserContext(Option::V4));
- ExpressionParserPtr parser;
- ExpressionPtr parsed_expr;
- // Turn config into elements.
- std::string cfg_txt = "777";
- ElementPtr config_element = Element::fromJSON(cfg_txt);
- // Create the parser.
- ASSERT_NO_THROW(parser.reset(new ExpressionParser("", parsed_expr,
- context)));
- // Expressionn build() should fail.
- ASSERT_THROW(parser->build(config_element), DhcpConfigError);
- }
- // Verifies you can create a class with only a name
- // Whether that's useful or not, remains to be seen.
- // For now the class allows it.
- TEST_F(ClientClassDefParserTest, nameOnlyValid) {
- std::string cfg_text =
- "{ \n"
- " \"name\": \"MICROSOFT\" \n"
- "} \n";
- ClientClassDefPtr cclass;
- ASSERT_NO_THROW(cclass = parseClientClassDef(cfg_text, Option::V4));
- // We should find our class.
- ASSERT_TRUE(cclass);
- EXPECT_EQ("MICROSOFT", cclass->getName());
- // CfgOption should be a non-null pointer but there
- // should be no options. Currently there's no good
- // way to test that there no options.
- CfgOptionPtr cfg_option;
- cfg_option = cclass->getCfgOption();
- ASSERT_TRUE(cfg_option);
- OptionContainerPtr oc;
- ASSERT_TRUE(oc = cclass->getCfgOption()->getAll("dhcp4"));
- EXPECT_EQ(0, oc->size());
- // Verify we have no expression.
- ASSERT_FALSE(cclass->getMatchExpr());
- }
- // Verifies you can create a class with a name, expression,
- // but no options.
- TEST_F(ClientClassDefParserTest, nameAndExpressionClass) {
- std::string cfg_text =
- "{ \n"
- " \"name\": \"MICROSOFT\", \n"
- " \"test\": \"vendor-class-identifier == 'MSFT'\" \n"
- "} \n";
- ClientClassDefPtr cclass;
- ASSERT_NO_THROW(cclass = parseClientClassDef(cfg_text, Option::V4));
- // We should find our class.
- ASSERT_TRUE(cclass);
- EXPECT_EQ("MICROSOFT", cclass->getName());
- // CfgOption should be a non-null pointer but there
- // should be no options. Currently there's no good
- // way to test that there no options.
- CfgOptionPtr cfg_option;
- cfg_option = cclass->getCfgOption();
- ASSERT_TRUE(cfg_option);
- OptionContainerPtr oc;
- ASSERT_TRUE(oc = cclass->getCfgOption()->getAll("dhcp4"));
- EXPECT_EQ(0, oc->size());
- // Verify we can retrieve the expression
- ExpressionPtr match_expr = cclass->getMatchExpr();
- ASSERT_TRUE(match_expr);
- // Evaluate it. For now the result will be the
- // expression string as dummy ExpressionParser
- // just makes an expression of one TokenString
- // containing the expression string itself.
- ValueStack vstack;
- Pkt4Ptr pkt4;
- pkt4.reset(new Pkt4(DHCPDISCOVER, 12345));
- (*match_expr)[0]->evaluate(*pkt4, vstack);
- EXPECT_EQ(vstack.top(), "\"vendor-class-identifier == 'MSFT'\"");
- }
- // Verifies you can create a class with a name and options,
- // but no expression.
- TEST_F(ClientClassDefParserTest, nameAndOptionsClass) {
- std::string cfg_text =
- "{ \n"
- " \"name\": \"MICROSOFT\", \n"
- " \"option-data\": [ \n"
- " { \n"
- " \"name\": \"domain-name-servers\", \n"
- " \"code\": 6, \n"
- " \"space\": \"dhcp4\", \n"
- " \"csv-format\": true, \n"
- " \"data\": \"192.0.2.1, 192.0.2.2\" \n"
- " } \n"
- " ] \n"
- "} \n";
- ClientClassDefPtr cclass;
- ASSERT_NO_THROW(cclass = parseClientClassDef(cfg_text, Option::V4));
- // We should find our class.
- ASSERT_TRUE(cclass);
- EXPECT_EQ("MICROSOFT", cclass->getName());
- // Our one option should exist.
- OptionDescriptor od = cclass->getCfgOption()->get("dhcp4", 6);
- ASSERT_TRUE(od.option_);
- EXPECT_EQ(6, od.option_->getType());
- // Verify we have no expression
- ASSERT_FALSE(cclass->getMatchExpr());
- }
- // Verifies you can create a class with a name, expression,
- // and options.
- TEST_F(ClientClassDefParserTest, basicValidClass) {
- std::string cfg_text =
- "{ \n"
- " \"name\": \"MICROSOFT\", \n"
- " \"test\": \"vendor-class-identifier == 'MSFT'\", \n"
- " \"option-data\": [ \n"
- " { \n"
- " \"name\": \"domain-name-servers\", \n"
- " \"code\": 6, \n"
- " \"space\": \"dhcp4\", \n"
- " \"csv-format\": true, \n"
- " \"data\": \"192.0.2.1, 192.0.2.2\" \n"
- " } \n"
- " ] \n"
- "} \n";
- ClientClassDefPtr cclass;
- ASSERT_NO_THROW(cclass = parseClientClassDef(cfg_text, Option::V4));
- // We should find our class.
- ASSERT_TRUE(cclass);
- EXPECT_EQ("MICROSOFT", cclass->getName());
- // Our one option should exist.
- OptionDescriptor od = cclass->getCfgOption()->get("dhcp4", 6);
- ASSERT_TRUE(od.option_);
- EXPECT_EQ(6, od.option_->getType());
- // Verify we can retrieve the expression
- ExpressionPtr match_expr = cclass->getMatchExpr();
- ASSERT_TRUE(match_expr);
- // Evaluate it. For now the result will be the
- // expression string as dummy ExpressionParser
- // just makes an expression of one TokenString
- // containing the expression string itself.
- ValueStack vstack;
- Pkt4Ptr pkt4;
- pkt4.reset(new Pkt4(DHCPDISCOVER, 12345));
- (*match_expr)[0]->evaluate(*pkt4, vstack);
- EXPECT_EQ(vstack.top(), "\"vendor-class-identifier == 'MSFT'\"");
- }
- // Verifies that a class with no name, fails to parse.
- TEST_F(ClientClassDefParserTest, noClassName) {
- std::string cfg_text =
- "{ \n"
- " \"test\": \"vendor-class-identifier == 'MSFT'\", \n"
- " \"option-data\": [ \n"
- " { \n"
- " \"name\": \"domain-name-servers\", \n"
- " \"code\": 6, \n"
- " \"space\": \"dhcp4\", \n"
- " \"csv-format\": true, \n"
- " \"data\": \"192.0.2.1, 192.0.2.2\" \n"
- " } \n"
- " ] \n"
- "} \n";
- ClientClassDefPtr cclass;
- ASSERT_THROW(cclass = parseClientClassDef(cfg_text, Option::V4),
- DhcpConfigError);
- }
- // Verifies that a class with an unknown element, fails to parse.
- TEST_F(ClientClassDefParserTest, unknownElement) {
- std::string cfg_text =
- "{ \n"
- " \"name\": \"one\", \n"
- " \"bogus\": \"bad\" \n"
- "} \n";
- ClientClassDefPtr cclass;
- ASSERT_THROW(cclass = parseClientClassDef(cfg_text, Option::V4),
- DhcpConfigError);
- }
- // Verifies that a class with an invalid expression, fails to parse.
- TEST_F(ClientClassDefParserTest, invalidExpression) {
- std::string cfg_text =
- "{ \n"
- " \"name\": \"one\", \n"
- " \"test\": 777 \n"
- "} \n";
- ClientClassDefPtr cclass;
- ASSERT_THROW(cclass = parseClientClassDef(cfg_text, Option::V4),
- DhcpConfigError);
- }
- // Verifies that a class with invalid option-data, fails to parse.
- TEST_F(ClientClassDefParserTest, invalidOptionData) {
- std::string cfg_text =
- "{ \n"
- " \"name\": \"one\", \n"
- " \"option-data\": [ \n"
- " { \"bogus\": \"bad\" } \n"
- " ] \n"
- "} \n";
- ClientClassDefPtr cclass;
- ASSERT_THROW(cclass = parseClientClassDef(cfg_text, Option::V4),
- DhcpConfigError);
- }
- // Verifies that a valid list of client classes will parse.
- TEST_F(ClientClassDefListParserTest, simpleValidList) {
- std::string cfg_text =
- "[ \n"
- " { \n"
- " \"name\": \"one\" \n"
- " }, \n"
- " { \n"
- " \"name\": \"two\" \n"
- " }, \n"
- " { \n"
- " \"name\": \"three\" \n"
- " } \n"
- "] \n";
- // Parsing the list should succeed.
- ClientClassDictionaryPtr dictionary;
- ASSERT_NO_THROW(dictionary = parseClientClassDefList(cfg_text, Option::V4));
- ASSERT_TRUE(dictionary);
- // We should have three classes in the dictionary.
- EXPECT_EQ(3, dictionary->getClasses()->size());
- // Make sure we can find all three.
- ClientClassDefPtr cclass;
- ASSERT_NO_THROW(cclass = dictionary->findClass("one"));
- ASSERT_TRUE(cclass);
- EXPECT_EQ("one", cclass->getName());
- ASSERT_NO_THROW(cclass = dictionary->findClass("two"));
- ASSERT_TRUE(cclass);
- EXPECT_EQ("two", cclass->getName());
- ASSERT_NO_THROW(cclass = dictionary->findClass("three"));
- ASSERT_TRUE(cclass);
- EXPECT_EQ("three", cclass->getName());
- // For good measure, make sure we can't find a non-existant class.
- ASSERT_NO_THROW(cclass = dictionary->findClass("bogus"));
- EXPECT_FALSE(cclass);
- // Verify that the dictionary was pushed to the CfgMgr's staging config.
- SrvConfigPtr staging = CfgMgr::instance().getStagingCfg();
- ASSERT_TRUE(staging);
- ClientClassDictionaryPtr staged_dictionary = staging->getClientClassDictionary();
- ASSERT_TRUE(staged_dictionary);
- EXPECT_TRUE(*staged_dictionary == *dictionary);
- }
- // Verifies that class list containing a duplicate class entries, fails
- // to parse.
- TEST_F(ClientClassDefListParserTest, duplicateClass) {
- std::string cfg_text =
- "[ \n"
- " { \n"
- " \"name\": \"one\" \n"
- " }, \n"
- " { \n"
- " \"name\": \"two\" \n"
- " }, \n"
- " { \n"
- " \"name\": \"two\" \n"
- " } \n"
- "] \n";
- ClientClassDictionaryPtr dictionary;
- ASSERT_THROW(dictionary = parseClientClassDefList(cfg_text, Option::V4),
- DhcpConfigError);
- }
- // Verifies that a class list containing an invalid class entry, fails to
- // parse.
- TEST_F(ClientClassDefListParserTest, invalidClass) {
- std::string cfg_text =
- "[ \n"
- " { \n"
- " \"name\": \"one\", \n"
- " \"bogus\": \"bad\" \n"
- " } \n"
- "] \n";
- ClientClassDictionaryPtr dictionary;
- ASSERT_THROW(dictionary = parseClientClassDefList(cfg_text, Option::V4),
- DhcpConfigError);
- }
- } // end of anonymous namespace
|