client_class_def_parser_unittest.cc 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469
  1. // Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
  2. //
  3. // Permission to use, copy, modify, and/or distribute this software for any
  4. // purpose with or without fee is hereby granted, provided that the above
  5. // copyright notice and this permission notice appear in all copies.
  6. //
  7. // THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
  8. // REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
  9. // AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
  10. // INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
  11. // LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
  12. // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
  13. // PERFORMANCE OF THIS SOFTWARE.
  14. #include <config.h>
  15. #include <cc/data.h>
  16. #include <dhcpsrv/cfgmgr.h>
  17. #include <dhcpsrv/parsers/client_class_def_parser.h>
  18. #include <gtest/gtest.h>
  19. #include <sstream>
  20. #include <stdint.h>
  21. #include <string>
  22. /// @file client_class_def_parser_unittest.cc Unit tests for client class
  23. /// definition parsing.
  24. using namespace isc::data;
  25. using namespace isc::dhcp;
  26. namespace {
  27. /// @brief Test fixture class for @c ClientClassDefParser.
  28. class ClientClassDefParserTest : public ::testing::Test {
  29. protected:
  30. /// @brief Convenience method for parsing a configuration
  31. ///
  32. /// Attempt to parse a given client class defintion.
  33. ///
  34. /// @param config - JSON string containing the client class configuration
  35. /// to parse.
  36. /// @param universe - the universe in which the parsing context should
  37. /// occur.
  38. /// @return Returns a pointer to class instance created, or NULL if
  39. /// for some unforeseen reason it wasn't created in the local dictionary
  40. /// @throw indirectly, execptions convertring the JSON text to elements,
  41. /// or by the parsing itself are not caught
  42. ClientClassDefPtr parseClientClassDef(const std::string& config,
  43. Option::Universe universe) {
  44. // Create local dicitonary to which the parser add the class.
  45. ClientClassDictionaryPtr dictionary(new ClientClassDictionary());
  46. // Create the "global" context for the parser.
  47. ParserContextPtr context(new ParserContext(universe));
  48. // Turn config into elements. This may emit exceptions.
  49. ElementPtr config_element = Element::fromJSON(config);
  50. // Parse the configuration. This may emit exceptions.
  51. ClientClassDefParser parser("", dictionary, context);
  52. parser.build(config_element);
  53. // If we didn't throw, then return the first and only class
  54. ClientClassDefMapPtr classes = dictionary->getClasses();
  55. ClientClassDefMap::iterator it = classes->begin();
  56. if (it != classes->end()) {
  57. return (*it).second;
  58. }
  59. // Return NULL if for some reason the class doesn't exist.
  60. return (ClientClassDefPtr());
  61. }
  62. };
  63. /// @brief Test fixture class for @c ClientClassDefListParser.
  64. class ClientClassDefListParserTest : public ::testing::Test {
  65. protected:
  66. /// @brief Convenience method for parsing a list of client class
  67. /// definitions.
  68. ///
  69. /// Attempt to parse a given list of client class defintions into a
  70. /// ClientClassDictionary.
  71. ///
  72. /// @param config - JSON string containing the list of definitions to parse.
  73. /// @param universe - the universe in which the parsing context should
  74. /// occur.
  75. /// @return Returns a pointer to class dictionary created
  76. /// @throw indirectly, execptions convertring the JSON text to elements,
  77. /// or by the parsing itself are not caught
  78. ClientClassDictionaryPtr parseClientClassDefList(const std::string& config,
  79. Option::Universe universe)
  80. {
  81. // Create the "global" context for the parser.
  82. ParserContextPtr context(new ParserContext(universe));
  83. // Turn config into elements. This may emit exceptions.
  84. ElementPtr config_element = Element::fromJSON(config);
  85. // Parse the configuration. This may emit exceptions.
  86. ClientClassDefListParser parser("", context);
  87. parser.build(config_element);
  88. // Commit should push it to CfgMgr staging
  89. parser.commit();
  90. // Return the parser's local dicationary
  91. return (parser.local_dictionary_);
  92. }
  93. };
  94. // Verifies basic operation of an ExpressionParser. Until we tie
  95. // this into the actual Bison parsing there's not much to test.
  96. TEST(ExpressionParserTest, simpleStringExpression) {
  97. ParserContextPtr context(new ParserContext(Option::V4));
  98. ExpressionParserPtr parser;
  99. ExpressionPtr parsed_expr;
  100. // Turn config into elements. This may emit exceptions.
  101. std::string cfg_txt = "\"astring\"";
  102. ElementPtr config_element = Element::fromJSON(cfg_txt);
  103. // Create the parser.
  104. ASSERT_NO_THROW(parser.reset(new ExpressionParser("", parsed_expr,
  105. context)));
  106. // Expression should parse and commit.
  107. ASSERT_NO_THROW(parser->build(config_element));
  108. ASSERT_NO_THROW(parser->commit());
  109. // Parsed expression should exist.
  110. ASSERT_TRUE(parsed_expr);
  111. // Evaluate it. For now the result will be the
  112. // expression string as dummy ExpressionParser
  113. // just makes an expression of one TokenString
  114. // containing the expression string itself.
  115. ValueStack vstack;
  116. Pkt4Ptr pkt4(new Pkt4(DHCPDISCOVER, 12345));
  117. (*parsed_expr)[0]->evaluate(*pkt4, vstack);
  118. EXPECT_EQ(vstack.top(), "\"astring\"");
  119. }
  120. // Verifies that given an invalid expression, the Expression parser
  121. // will throw a DhdpConfigError. Note this is not intended to be
  122. // an exhaustive test or expression syntax. It is simply to ensure
  123. // that if the parser fails, it does so properly. For now, since
  124. // our parser is a dummy parser which only checks that it's given
  125. // Element::string so send it an integer.
  126. TEST(ExpressionParserTest, invalidExpression) {
  127. ParserContextPtr context(new ParserContext(Option::V4));
  128. ExpressionParserPtr parser;
  129. ExpressionPtr parsed_expr;
  130. // Turn config into elements.
  131. std::string cfg_txt = "777";
  132. ElementPtr config_element = Element::fromJSON(cfg_txt);
  133. // Create the parser.
  134. ASSERT_NO_THROW(parser.reset(new ExpressionParser("", parsed_expr,
  135. context)));
  136. // Expressionn build() should fail.
  137. ASSERT_THROW(parser->build(config_element), DhcpConfigError);
  138. }
  139. // Verifies you can create a class with only a name
  140. // Whether that's useful or not, remains to be seen.
  141. // For now the class allows it.
  142. TEST_F(ClientClassDefParserTest, nameOnlyValid) {
  143. std::string cfg_text =
  144. "{ \n"
  145. " \"name\": \"MICROSOFT\" \n"
  146. "} \n";
  147. ClientClassDefPtr cclass;
  148. ASSERT_NO_THROW(cclass = parseClientClassDef(cfg_text, Option::V4));
  149. // We should find our class.
  150. ASSERT_TRUE(cclass);
  151. EXPECT_EQ("MICROSOFT", cclass->getName());
  152. // CfgOption should be a non-null pointer but there
  153. // should be no options. Currently there's no good
  154. // way to test that there no options.
  155. CfgOptionPtr cfg_option;
  156. cfg_option = cclass->getCfgOption();
  157. ASSERT_TRUE(cfg_option);
  158. OptionContainerPtr oc;
  159. ASSERT_TRUE(oc = cclass->getCfgOption()->getAll("dhcp4"));
  160. EXPECT_EQ(0, oc->size());
  161. // Verify we have no expression.
  162. ASSERT_FALSE(cclass->getMatchExpr());
  163. }
  164. // Verifies you can create a class with a name, expression,
  165. // but no options.
  166. TEST_F(ClientClassDefParserTest, nameAndExpressionClass) {
  167. std::string cfg_text =
  168. "{ \n"
  169. " \"name\": \"MICROSOFT\", \n"
  170. " \"test\": \"vendor-class-identifier == 'MSFT'\" \n"
  171. "} \n";
  172. ClientClassDefPtr cclass;
  173. ASSERT_NO_THROW(cclass = parseClientClassDef(cfg_text, Option::V4));
  174. // We should find our class.
  175. ASSERT_TRUE(cclass);
  176. EXPECT_EQ("MICROSOFT", cclass->getName());
  177. // CfgOption should be a non-null pointer but there
  178. // should be no options. Currently there's no good
  179. // way to test that there no options.
  180. CfgOptionPtr cfg_option;
  181. cfg_option = cclass->getCfgOption();
  182. ASSERT_TRUE(cfg_option);
  183. OptionContainerPtr oc;
  184. ASSERT_TRUE(oc = cclass->getCfgOption()->getAll("dhcp4"));
  185. EXPECT_EQ(0, oc->size());
  186. // Verify we can retrieve the expression
  187. ExpressionPtr match_expr = cclass->getMatchExpr();
  188. ASSERT_TRUE(match_expr);
  189. // Evaluate it. For now the result will be the
  190. // expression string as dummy ExpressionParser
  191. // just makes an expression of one TokenString
  192. // containing the expression string itself.
  193. ValueStack vstack;
  194. Pkt4Ptr pkt4;
  195. pkt4.reset(new Pkt4(DHCPDISCOVER, 12345));
  196. (*match_expr)[0]->evaluate(*pkt4, vstack);
  197. EXPECT_EQ(vstack.top(), "\"vendor-class-identifier == 'MSFT'\"");
  198. }
  199. // Verifies you can create a class with a name and options,
  200. // but no expression.
  201. TEST_F(ClientClassDefParserTest, nameAndOptionsClass) {
  202. std::string cfg_text =
  203. "{ \n"
  204. " \"name\": \"MICROSOFT\", \n"
  205. " \"option-data\": [ \n"
  206. " { \n"
  207. " \"name\": \"domain-name-servers\", \n"
  208. " \"code\": 6, \n"
  209. " \"space\": \"dhcp4\", \n"
  210. " \"csv-format\": true, \n"
  211. " \"data\": \"192.0.2.1, 192.0.2.2\" \n"
  212. " } \n"
  213. " ] \n"
  214. "} \n";
  215. ClientClassDefPtr cclass;
  216. ASSERT_NO_THROW(cclass = parseClientClassDef(cfg_text, Option::V4));
  217. // We should find our class.
  218. ASSERT_TRUE(cclass);
  219. EXPECT_EQ("MICROSOFT", cclass->getName());
  220. // Our one option should exist.
  221. OptionDescriptor od = cclass->getCfgOption()->get("dhcp4", 6);
  222. ASSERT_TRUE(od.option_);
  223. EXPECT_EQ(6, od.option_->getType());
  224. // Verify we have no expression
  225. ASSERT_FALSE(cclass->getMatchExpr());
  226. }
  227. // Verifies you can create a class with a name, expression,
  228. // and options.
  229. TEST_F(ClientClassDefParserTest, basicValidClass) {
  230. std::string cfg_text =
  231. "{ \n"
  232. " \"name\": \"MICROSOFT\", \n"
  233. " \"test\": \"vendor-class-identifier == 'MSFT'\", \n"
  234. " \"option-data\": [ \n"
  235. " { \n"
  236. " \"name\": \"domain-name-servers\", \n"
  237. " \"code\": 6, \n"
  238. " \"space\": \"dhcp4\", \n"
  239. " \"csv-format\": true, \n"
  240. " \"data\": \"192.0.2.1, 192.0.2.2\" \n"
  241. " } \n"
  242. " ] \n"
  243. "} \n";
  244. ClientClassDefPtr cclass;
  245. ASSERT_NO_THROW(cclass = parseClientClassDef(cfg_text, Option::V4));
  246. // We should find our class.
  247. ASSERT_TRUE(cclass);
  248. EXPECT_EQ("MICROSOFT", cclass->getName());
  249. // Our one option should exist.
  250. OptionDescriptor od = cclass->getCfgOption()->get("dhcp4", 6);
  251. ASSERT_TRUE(od.option_);
  252. EXPECT_EQ(6, od.option_->getType());
  253. // Verify we can retrieve the expression
  254. ExpressionPtr match_expr = cclass->getMatchExpr();
  255. ASSERT_TRUE(match_expr);
  256. // Evaluate it. For now the result will be the
  257. // expression string as dummy ExpressionParser
  258. // just makes an expression of one TokenString
  259. // containing the expression string itself.
  260. ValueStack vstack;
  261. Pkt4Ptr pkt4;
  262. pkt4.reset(new Pkt4(DHCPDISCOVER, 12345));
  263. (*match_expr)[0]->evaluate(*pkt4, vstack);
  264. EXPECT_EQ(vstack.top(), "\"vendor-class-identifier == 'MSFT'\"");
  265. }
  266. // Verifies that a class with no name, fails to parse.
  267. TEST_F(ClientClassDefParserTest, noClassName) {
  268. std::string cfg_text =
  269. "{ \n"
  270. " \"test\": \"vendor-class-identifier == 'MSFT'\", \n"
  271. " \"option-data\": [ \n"
  272. " { \n"
  273. " \"name\": \"domain-name-servers\", \n"
  274. " \"code\": 6, \n"
  275. " \"space\": \"dhcp4\", \n"
  276. " \"csv-format\": true, \n"
  277. " \"data\": \"192.0.2.1, 192.0.2.2\" \n"
  278. " } \n"
  279. " ] \n"
  280. "} \n";
  281. ClientClassDefPtr cclass;
  282. ASSERT_THROW(cclass = parseClientClassDef(cfg_text, Option::V4),
  283. DhcpConfigError);
  284. }
  285. // Verifies that a class with an unknown element, fails to parse.
  286. TEST_F(ClientClassDefParserTest, unknownElement) {
  287. std::string cfg_text =
  288. "{ \n"
  289. " \"name\": \"one\", \n"
  290. " \"bogus\": \"bad\" \n"
  291. "} \n";
  292. ClientClassDefPtr cclass;
  293. ASSERT_THROW(cclass = parseClientClassDef(cfg_text, Option::V4),
  294. DhcpConfigError);
  295. }
  296. // Verifies that a class with an invalid expression, fails to parse.
  297. TEST_F(ClientClassDefParserTest, invalidExpression) {
  298. std::string cfg_text =
  299. "{ \n"
  300. " \"name\": \"one\", \n"
  301. " \"test\": 777 \n"
  302. "} \n";
  303. ClientClassDefPtr cclass;
  304. ASSERT_THROW(cclass = parseClientClassDef(cfg_text, Option::V4),
  305. DhcpConfigError);
  306. }
  307. // Verifies that a class with invalid option-data, fails to parse.
  308. TEST_F(ClientClassDefParserTest, invalidOptionData) {
  309. std::string cfg_text =
  310. "{ \n"
  311. " \"name\": \"one\", \n"
  312. " \"option-data\": [ \n"
  313. " { \"bogus\": \"bad\" } \n"
  314. " ] \n"
  315. "} \n";
  316. ClientClassDefPtr cclass;
  317. ASSERT_THROW(cclass = parseClientClassDef(cfg_text, Option::V4),
  318. DhcpConfigError);
  319. }
  320. // Verifies that a valid list of client classes will parse.
  321. TEST_F(ClientClassDefListParserTest, simpleValidList) {
  322. std::string cfg_text =
  323. "[ \n"
  324. " { \n"
  325. " \"name\": \"one\" \n"
  326. " }, \n"
  327. " { \n"
  328. " \"name\": \"two\" \n"
  329. " }, \n"
  330. " { \n"
  331. " \"name\": \"three\" \n"
  332. " } \n"
  333. "] \n";
  334. // Parsing the list should succeed.
  335. ClientClassDictionaryPtr dictionary;
  336. ASSERT_NO_THROW(dictionary = parseClientClassDefList(cfg_text, Option::V4));
  337. ASSERT_TRUE(dictionary);
  338. // We should have three classes in the dictionary.
  339. EXPECT_EQ(3, dictionary->getClasses()->size());
  340. // Make sure we can find all three.
  341. ClientClassDefPtr cclass;
  342. ASSERT_NO_THROW(cclass = dictionary->findClass("one"));
  343. ASSERT_TRUE(cclass);
  344. EXPECT_EQ("one", cclass->getName());
  345. ASSERT_NO_THROW(cclass = dictionary->findClass("two"));
  346. ASSERT_TRUE(cclass);
  347. EXPECT_EQ("two", cclass->getName());
  348. ASSERT_NO_THROW(cclass = dictionary->findClass("three"));
  349. ASSERT_TRUE(cclass);
  350. EXPECT_EQ("three", cclass->getName());
  351. // For good measure, make sure we can't find a non-existant class.
  352. ASSERT_NO_THROW(cclass = dictionary->findClass("bogus"));
  353. EXPECT_FALSE(cclass);
  354. // Verify that the dictionary was pushed to the CfgMgr's staging config.
  355. SrvConfigPtr staging = CfgMgr::instance().getStagingCfg();
  356. ASSERT_TRUE(staging);
  357. ClientClassDictionaryPtr staged_dictionary = staging->getClientClassDictionary();
  358. ASSERT_TRUE(staged_dictionary);
  359. EXPECT_TRUE(*staged_dictionary == *dictionary);
  360. }
  361. // Verifies that class list containing a duplicate class entries, fails
  362. // to parse.
  363. TEST_F(ClientClassDefListParserTest, duplicateClass) {
  364. std::string cfg_text =
  365. "[ \n"
  366. " { \n"
  367. " \"name\": \"one\" \n"
  368. " }, \n"
  369. " { \n"
  370. " \"name\": \"two\" \n"
  371. " }, \n"
  372. " { \n"
  373. " \"name\": \"two\" \n"
  374. " } \n"
  375. "] \n";
  376. ClientClassDictionaryPtr dictionary;
  377. ASSERT_THROW(dictionary = parseClientClassDefList(cfg_text, Option::V4),
  378. DhcpConfigError);
  379. }
  380. // Verifies that a class list containing an invalid class entry, fails to
  381. // parse.
  382. TEST_F(ClientClassDefListParserTest, invalidClass) {
  383. std::string cfg_text =
  384. "[ \n"
  385. " { \n"
  386. " \"name\": \"one\", \n"
  387. " \"bogus\": \"bad\" \n"
  388. " } \n"
  389. "] \n";
  390. ClientClassDictionaryPtr dictionary;
  391. ASSERT_THROW(dictionary = parseClientClassDefList(cfg_text, Option::V4),
  392. DhcpConfigError);
  393. }
  394. } // end of anonymous namespace