dbaccess_parser_unittest.cc 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605
  1. // Copyright (C) 2012-2016 Internet Systems Consortium, Inc. ("ISC")
  2. //
  3. // This Source Code Form is subject to the terms of the Mozilla Public
  4. // License, v. 2.0. If a copy of the MPL was not distributed with this
  5. // file, You can obtain one at http://mozilla.org/MPL/2.0/.
  6. #include <config.h>
  7. #include <cc/command_interpreter.h>
  8. #include <dhcpsrv/lease_mgr_factory.h>
  9. #include <dhcpsrv/parsers/dbaccess_parser.h>
  10. #include <dhcpsrv/testutils/mysql_schema.h>
  11. #include <dhcpsrv/host_mgr.h>
  12. #include <log/logger_support.h>
  13. #include <gtest/gtest.h>
  14. #include <map>
  15. #include <string>
  16. using namespace std;
  17. using namespace isc;
  18. using namespace isc::dhcp;
  19. using namespace isc::dhcp::test;
  20. using namespace isc::data;
  21. using namespace isc::config;
  22. namespace {
  23. /// @brief Database Access Parser test fixture class
  24. class DbAccessParserTest : public ::testing::Test {
  25. public:
  26. /// @brief Constructor
  27. ///
  28. /// Just make sure that the lease database is closed before every test
  29. /// (the first in particular).
  30. DbAccessParserTest() {
  31. LeaseMgrFactory::destroy();
  32. }
  33. /// @brief Destructor
  34. ///
  35. /// Just make sure that the lease database is closed after every test
  36. /// (the last in particular).
  37. ///
  38. /// As some of the tests have the side-effect of altering the logging
  39. /// settings (when the parser's "build" method is called), ensure that
  40. /// the logging is reset to the default after each test completes.
  41. ~DbAccessParserTest() {
  42. LeaseMgrFactory::destroy();
  43. isc::log::setDefaultLoggingOutput();
  44. }
  45. /// @brief Build JSON String
  46. ///
  47. /// Given a array of "const char*" strings representing in order, keyword,
  48. /// value, keyword, value, ... and terminated by a NULL, return a string
  49. /// that represents the JSON map for the keywords and values.
  50. ///
  51. /// E.g. given the array of strings: alpha, one, beta, two, NULL, it would
  52. /// return the string '{ "alpha": "one", "beta": "two" }'
  53. ///
  54. /// @param keyval Array of "const char*" strings in the order keyword,
  55. /// value, keyword, value ... A NULL entry terminates the list.
  56. ///
  57. /// @return JSON map for the keyword value array.
  58. std::string toJson(const char* keyval[]) {
  59. const std::string quote = "\"";
  60. const std::string colon = ":";
  61. const std::string space = " ";
  62. string result = "{ ";
  63. for (size_t i = 0; keyval[i] != NULL; i+= 2) {
  64. // Get the value. This should not be NULL. As ASSERT_NE will
  65. // cause a return - which gives compilation problems as a return
  66. // statement is expected to return a string - use EXPECT_NE and
  67. // explicitly return if the expected array is incorrect.
  68. EXPECT_NE(static_cast<const char*>(NULL), keyval[i + 1]) <<
  69. "Supplied reference keyword/value list does not contain values "
  70. "for all keywords";
  71. if (keyval[i + 1] == NULL) {
  72. return (std::string(""));
  73. }
  74. // Add the separating comma if not the first.
  75. if (i != 0) {
  76. result += ", ";
  77. }
  78. // Add the keyword and value - make sure that they are quoted.
  79. // The parameters which are not quoted are persist, readonly and
  80. // lfc-interval as they are boolean and integer respectively.
  81. result += quote + keyval[i] + quote + colon + space;
  82. if (!quoteValue(std::string(keyval[i]))) {
  83. result += keyval[i + 1];
  84. } else {
  85. result += quote + keyval[i + 1] + quote;
  86. }
  87. }
  88. // Add the terminating brace
  89. result += " }";
  90. return (result);
  91. }
  92. /// @brief Check for Keywords
  93. ///
  94. /// Takes a database access string and checks it against a list of keywords
  95. /// and values. It checks that:
  96. ///
  97. /// a. Every keyword in the string appears once and only once in the
  98. /// list.
  99. /// b. Every keyword in the list appears in the string.
  100. /// c. Every keyword's value is the same as that in the string.
  101. ///
  102. /// To parse the access string, we use the parsing function in the
  103. /// DHCP lease manager.
  104. ///
  105. /// @param trace_string String that will be used to set the value of a
  106. /// SCOPED_TRACE for this call.
  107. /// @param dbaccess set of database access parameters to check
  108. /// @param keyval Array of "const char*" strings in the order keyword,
  109. /// value, keyword, value ... A NULL entry terminates the list.
  110. void checkAccessString(const char* trace_string,
  111. const DbAccessParser::StringPairMap& parameters,
  112. const char* keyval[]) {
  113. SCOPED_TRACE(trace_string);
  114. // Construct a map of keyword value pairs.
  115. std::map<string, string> expected;
  116. size_t expected_count = 0;
  117. for (size_t i = 0; keyval[i] != NULL; i += 2) {
  118. // Get the value. This should not be NULL
  119. ASSERT_NE(static_cast<const char*>(NULL), keyval[i + 1]) <<
  120. "Supplied reference keyword/value list does not contain values "
  121. "for all keywords";
  122. expected[keyval[i]] = keyval[i + 1];
  123. // One more keyword processed
  124. ++expected_count;
  125. }
  126. // Check no duplicates in the test set of reference keywords.
  127. ASSERT_EQ(expected_count, expected.size()) <<
  128. "Supplied reference keyword/value list contains duplicate keywords";
  129. // The passed parameter map should have the same number of entries as
  130. // the reference set of keywords.
  131. EXPECT_EQ(expected_count, parameters.size());
  132. // Check that the keywords and keyword values are the same: loop
  133. // through the keywords in the database access string.
  134. for (DatabaseConnection::ParameterMap::const_iterator actual = parameters.begin();
  135. actual != parameters.end(); ++actual) {
  136. // Does the keyword exist in the set of expected keywords?
  137. std::map<string, string>::iterator corresponding =
  138. expected.find(actual->first);
  139. ASSERT_TRUE(corresponding != expected.end());
  140. // Keyword exists, is the value the same?
  141. EXPECT_EQ(corresponding->second, actual->second);
  142. }
  143. }
  144. private:
  145. /// @brief Checks if the value of the specified parameter should be
  146. /// quoted in the configuration.
  147. ///
  148. /// @param parameter A parameter for which it should be checked whether
  149. /// the value should be quoted or not.
  150. ///
  151. /// @return true if the value of the parameter should be quoted.
  152. bool quoteValue(const std::string& parameter) const {
  153. return ((parameter != "persist") && (parameter != "lfc-interval") &&
  154. (parameter != "connect-timeout") &&
  155. (parameter != "readonly"));
  156. }
  157. };
  158. /// @brief Version of parser with protected methods public
  159. ///
  160. /// Some of the methods in DbAccessParser are not required to be public in Kea.
  161. /// Instead of being declared "private", they are declared "protected" so that
  162. /// they can be accessed through a derived class in the unit tests.
  163. class TestDbAccessParser : public DbAccessParser {
  164. public:
  165. /// @brief Constructor
  166. ///
  167. /// @brief Keyword/value collection of database access parameters
  168. TestDbAccessParser(const std::string& param_name, DbAccessParser::DBType type)
  169. : DbAccessParser(param_name, type)
  170. {}
  171. /// @brief Destructor
  172. virtual ~TestDbAccessParser()
  173. {}
  174. /// Allow use of superclass's protected functions.
  175. using DbAccessParser::getDbAccessParameters;
  176. using DbAccessParser::getDbAccessString;
  177. /// @brief Get database access parameters
  178. ///
  179. /// Used in testing to check that the configuration information has been
  180. /// parsed corrected.
  181. ///
  182. /// @return Map of keyword/value pairs representing database access
  183. /// information.
  184. const StringPairMap& getDbAccessParameters() const {
  185. return (DbAccessParser::getDbAccessParameters());
  186. }
  187. /// @brief Construct database access string
  188. ///
  189. /// Constructs the database access string from the stored parameters.
  190. ///
  191. /// @return Database access string
  192. std::string getDbAccessString() const {
  193. return (DbAccessParser::getDbAccessString());
  194. }
  195. };
  196. // Check that the parser works with a simple configuration.
  197. TEST_F(DbAccessParserTest, validTypeMemfile) {
  198. const char* config[] = {"type", "memfile",
  199. NULL};
  200. string json_config = toJson(config);
  201. ConstElementPtr json_elements = Element::fromJSON(json_config);
  202. EXPECT_TRUE(json_elements);
  203. TestDbAccessParser parser("lease-database", DbAccessParser::LEASE_DB);
  204. EXPECT_NO_THROW(parser.build(json_elements));
  205. checkAccessString("Valid memfile", parser.getDbAccessParameters(), config);
  206. }
  207. // Check that the parser works with a simple configuration that
  208. // includes empty elements.
  209. TEST_F(DbAccessParserTest, emptyKeyword) {
  210. const char* config[] = {"type", "memfile",
  211. "name", "",
  212. NULL};
  213. string json_config = toJson(config);
  214. ConstElementPtr json_elements = Element::fromJSON(json_config);
  215. EXPECT_TRUE(json_elements);
  216. TestDbAccessParser parser("lease-database", DbAccessParser::LEASE_DB);
  217. EXPECT_NO_THROW(parser.build(json_elements));
  218. checkAccessString("Valid memfile", parser.getDbAccessParameters(), config);
  219. }
  220. // Check that the parser works with more complex configuration when
  221. // lease file path is specified for DHCPv4.
  222. TEST_F(DbAccessParserTest, persistV4Memfile) {
  223. const char* config[] = {"type", "memfile",
  224. "persist", "true",
  225. "name", "/opt/kea/var/kea-leases4.csv",
  226. NULL};
  227. string json_config = toJson(config);
  228. ConstElementPtr json_elements = Element::fromJSON(json_config);
  229. EXPECT_TRUE(json_elements);
  230. TestDbAccessParser parser("lease-database", DbAccessParser::LEASE_DB);
  231. EXPECT_NO_THROW(parser.build(json_elements));
  232. checkAccessString("Valid memfile", parser.getDbAccessParameters(),
  233. config);
  234. }
  235. // Check that the parser works with more complex configuration when
  236. // lease file path is specified for DHCPv6.
  237. TEST_F(DbAccessParserTest, persistV6Memfile) {
  238. const char* config[] = {"type", "memfile",
  239. "persist", "true",
  240. "name", "/opt/kea/var/kea-leases6.csv",
  241. NULL};
  242. string json_config = toJson(config);
  243. ConstElementPtr json_elements = Element::fromJSON(json_config);
  244. EXPECT_TRUE(json_elements);
  245. TestDbAccessParser parser("lease-database", DbAccessParser::LEASE_DB);
  246. EXPECT_NO_THROW(parser.build(json_elements));
  247. checkAccessString("Valid memfile", parser.getDbAccessParameters(),
  248. config);
  249. }
  250. // This test checks that the parser accepts the valid value of the
  251. // lfc-interval parameter.
  252. TEST_F(DbAccessParserTest, validLFCInterval) {
  253. const char* config[] = {"type", "memfile",
  254. "name", "/opt/kea/var/kea-leases6.csv",
  255. "lfc-interval", "3600",
  256. NULL};
  257. string json_config = toJson(config);
  258. ConstElementPtr json_elements = Element::fromJSON(json_config);
  259. EXPECT_TRUE(json_elements);
  260. TestDbAccessParser parser("lease-database", DbAccessParser::LEASE_DB);
  261. EXPECT_NO_THROW(parser.build(json_elements));
  262. checkAccessString("Valid LFC Interval", parser.getDbAccessParameters(),
  263. config);
  264. }
  265. // This test checks that the parser rejects the negative value of the
  266. // lfc-interval parameter.
  267. TEST_F(DbAccessParserTest, negativeLFCInterval) {
  268. const char* config[] = {"type", "memfile",
  269. "name", "/opt/kea/var/kea-leases6.csv",
  270. "lfc-interval", "-1",
  271. NULL};
  272. string json_config = toJson(config);
  273. ConstElementPtr json_elements = Element::fromJSON(json_config);
  274. EXPECT_TRUE(json_elements);
  275. TestDbAccessParser parser("lease-database", DbAccessParser::LEASE_DB);
  276. EXPECT_THROW(parser.build(json_elements), BadValue);
  277. }
  278. // This test checks that the parser rejects the too large (greater than
  279. // the max uint32_t) value of the lfc-interval parameter.
  280. TEST_F(DbAccessParserTest, largeLFCInterval) {
  281. const char* config[] = {"type", "memfile",
  282. "name", "/opt/kea/var/kea-leases6.csv",
  283. "lfc-interval", "4294967296",
  284. NULL};
  285. string json_config = toJson(config);
  286. ConstElementPtr json_elements = Element::fromJSON(json_config);
  287. EXPECT_TRUE(json_elements);
  288. TestDbAccessParser parser("lease-database", DbAccessParser::LEASE_DB);
  289. EXPECT_THROW(parser.build(json_elements), BadValue);
  290. }
  291. // This test checks that the parser accepts the valid value of the
  292. // timeout parameter.
  293. TEST_F(DbAccessParserTest, validTimeout) {
  294. const char* config[] = {"type", "memfile",
  295. "name", "/opt/kea/var/kea-leases6.csv",
  296. "connect-timeout", "3600",
  297. NULL};
  298. string json_config = toJson(config);
  299. ConstElementPtr json_elements = Element::fromJSON(json_config);
  300. EXPECT_TRUE(json_elements);
  301. TestDbAccessParser parser("lease-database", DbAccessParser::LEASE_DB);
  302. EXPECT_NO_THROW(parser.build(json_elements));
  303. checkAccessString("Valid timeout", parser.getDbAccessParameters(),
  304. config);
  305. }
  306. // This test checks that the parser rejects the negative value of the
  307. // timeout parameter.
  308. TEST_F(DbAccessParserTest, negativeTimeout) {
  309. const char* config[] = {"type", "memfile",
  310. "name", "/opt/kea/var/kea-leases6.csv",
  311. "connect-timeout", "-1",
  312. NULL};
  313. string json_config = toJson(config);
  314. ConstElementPtr json_elements = Element::fromJSON(json_config);
  315. EXPECT_TRUE(json_elements);
  316. TestDbAccessParser parser("lease-database", DbAccessParser::LEASE_DB);
  317. EXPECT_THROW(parser.build(json_elements), BadValue);
  318. }
  319. // This test checks that the parser rejects a too large (greater than
  320. // the max uint32_t) value of the timeout parameter.
  321. TEST_F(DbAccessParserTest, largeTimeout) {
  322. const char* config[] = {"type", "memfile",
  323. "name", "/opt/kea/var/kea-leases6.csv",
  324. "connect-timeout", "4294967296",
  325. NULL};
  326. string json_config = toJson(config);
  327. ConstElementPtr json_elements = Element::fromJSON(json_config);
  328. EXPECT_TRUE(json_elements);
  329. TestDbAccessParser parser("lease-database", DbAccessParser::LEASE_DB);
  330. EXPECT_THROW(parser.build(json_elements), BadValue);
  331. }
  332. // Check that the parser works with a valid MySQL configuration
  333. TEST_F(DbAccessParserTest, validTypeMysql) {
  334. const char* config[] = {"type", "mysql",
  335. "host", "erewhon",
  336. "user", "kea",
  337. "password", "keapassword",
  338. "name", "keatest",
  339. NULL};
  340. string json_config = toJson(config);
  341. ConstElementPtr json_elements = Element::fromJSON(json_config);
  342. EXPECT_TRUE(json_elements);
  343. TestDbAccessParser parser("lease-database", DbAccessParser::LEASE_DB);
  344. EXPECT_NO_THROW(parser.build(json_elements));
  345. checkAccessString("Valid mysql", parser.getDbAccessParameters(), config);
  346. }
  347. // A missing 'type' keyword should cause an exception to be thrown.
  348. TEST_F(DbAccessParserTest, missingTypeKeyword) {
  349. const char* config[] = {"host", "erewhon",
  350. "user", "kea",
  351. "password", "keapassword",
  352. "name", "keatest",
  353. NULL};
  354. string json_config = toJson(config);
  355. ConstElementPtr json_elements = Element::fromJSON(json_config);
  356. EXPECT_TRUE(json_elements);
  357. TestDbAccessParser parser("lease-database", DbAccessParser::LEASE_DB);
  358. EXPECT_THROW(parser.build(json_elements), TypeKeywordMissing);
  359. }
  360. // Check that the factory function works.
  361. TEST_F(DbAccessParserTest, factory) {
  362. // Check that the parser is built through the factory.
  363. boost::scoped_ptr<DhcpConfigParser> parser(
  364. DbAccessParser::factory("lease-database")
  365. );
  366. EXPECT_TRUE(parser);
  367. DbAccessParser* dbap = dynamic_cast<DbAccessParser*>(parser.get());
  368. EXPECT_NE(static_cast<DbAccessParser*>(NULL), dbap);
  369. }
  370. // Check reconfiguration. Checks that incremental changes applied to the
  371. // database configuration are incremental.
  372. TEST_F(DbAccessParserTest, incrementalChanges) {
  373. const char* config1[] = {"type", "memfile",
  374. NULL};
  375. // Applying config2 will cause a wholesale change.
  376. const char* config2[] = {"type", "mysql",
  377. "host", "erewhon",
  378. "user", "kea",
  379. "password", "keapassword",
  380. "name", "keatest",
  381. NULL};
  382. // Applying incremental2 should cause a change to config3.
  383. const char* incremental2[] = {"user", "me",
  384. "password", "meagain",
  385. NULL};
  386. const char* config3[] = {"type", "mysql",
  387. "host", "erewhon",
  388. "user", "me",
  389. "password", "meagain",
  390. "name", "keatest",
  391. NULL};
  392. // incremental3 will cause an exception. There should be no change
  393. // to the returned value.
  394. const char* incremental3[] = {"type", "invalid",
  395. "user", "you",
  396. "password", "youagain",
  397. NULL};
  398. // incremental4 is a compatible change and should cause a transition
  399. // to config4.
  400. const char* incremental4[] = {"user", "them",
  401. "password", "",
  402. NULL};
  403. const char* config4[] = {"type", "mysql",
  404. "host", "erewhon",
  405. "user", "them",
  406. "password", "",
  407. "name", "keatest",
  408. NULL};
  409. TestDbAccessParser parser("lease-database", DbAccessParser::LEASE_DB);
  410. // First configuration string should cause a representation of that string
  411. // to be held.
  412. string json_config = toJson(config1);
  413. ConstElementPtr json_elements = Element::fromJSON(json_config);
  414. EXPECT_TRUE(json_elements);
  415. EXPECT_NO_THROW(parser.build(json_elements));
  416. checkAccessString("Initial configuration", parser.getDbAccessParameters(),
  417. config1);
  418. // Applying a wholesale change will cause the access string to change
  419. // to a representation of the new configuration.
  420. json_config = toJson(config2);
  421. json_elements = Element::fromJSON(json_config);
  422. EXPECT_TRUE(json_elements);
  423. EXPECT_NO_THROW(parser.build(json_elements));
  424. checkAccessString("Subsequent configuration", parser.getDbAccessParameters(),
  425. config2);
  426. // Applying an incremental change will cause the representation to change
  427. // incrementally.
  428. json_config = toJson(incremental2);
  429. json_elements = Element::fromJSON(json_config);
  430. EXPECT_TRUE(json_elements);
  431. EXPECT_NO_THROW(parser.build(json_elements));
  432. checkAccessString("Incremental configuration", parser.getDbAccessParameters(),
  433. config3);
  434. // Applying the next incremental change should cause an exception to be
  435. // thrown and there be no change to the access string.
  436. json_config = toJson(incremental3);
  437. json_elements = Element::fromJSON(json_config);
  438. EXPECT_TRUE(json_elements);
  439. EXPECT_THROW(parser.build(json_elements), BadValue);
  440. checkAccessString("Incompatible incremental change", parser.getDbAccessParameters(),
  441. config3);
  442. // Applying an incremental change will cause the representation to change
  443. // incrementally.
  444. json_config = toJson(incremental4);
  445. json_elements = Element::fromJSON(json_config);
  446. EXPECT_TRUE(json_elements);
  447. EXPECT_NO_THROW(parser.build(json_elements));
  448. checkAccessString("Compatible incremental change", parser.getDbAccessParameters(),
  449. config4);
  450. }
  451. // Check that the database access string is constructed correctly.
  452. TEST_F(DbAccessParserTest, getDbAccessString) {
  453. const char* config[] = {"type", "mysql",
  454. "host", "" ,
  455. "name", "keatest",
  456. NULL};
  457. string json_config = toJson(config);
  458. ConstElementPtr json_elements = Element::fromJSON(json_config);
  459. EXPECT_TRUE(json_elements);
  460. TestDbAccessParser parser("lease-database", DbAccessParser::LEASE_DB);
  461. EXPECT_NO_THROW(parser.build(json_elements));
  462. // Get the database access string
  463. std::string dbaccess = parser.getDbAccessString();
  464. // String should be either "type=mysql name=keatest" or
  465. // "name=keatest type=mysql". The "host" entry is null, so should not be
  466. // output.
  467. EXPECT_EQ(dbaccess, "name=keatest type=mysql");
  468. }
  469. // Check that the configuration is accepted for the valid value
  470. // of "readonly".
  471. TEST_F(DbAccessParserTest, validReadOnly) {
  472. const char* config[] = {"type", "mysql",
  473. "user", "keatest",
  474. "password", "keatest",
  475. "name", "keatest",
  476. "readonly", "true",
  477. NULL};
  478. string json_config = toJson(config);
  479. ConstElementPtr json_elements = Element::fromJSON(json_config);
  480. EXPECT_TRUE(json_elements);
  481. TestDbAccessParser parser("lease-database", DbAccessParser::LEASE_DB);
  482. EXPECT_NO_THROW(parser.build(json_elements));
  483. checkAccessString("Valid readonly parameter",
  484. parser.getDbAccessParameters(),
  485. config);
  486. }
  487. // Check that for the invalid value of the "readonly" parameter
  488. // an exception is thrown.
  489. TEST_F(DbAccessParserTest, invalidReadOnly) {
  490. const char* config[] = {"type", "mysql",
  491. "user", "keatest",
  492. "password", "keatest",
  493. "name", "keatest",
  494. "readonly", "1",
  495. NULL};
  496. string json_config = toJson(config);
  497. ConstElementPtr json_elements = Element::fromJSON(json_config);
  498. EXPECT_TRUE(json_elements);
  499. TestDbAccessParser parser("lease-database", DbAccessParser::LEASE_DB);
  500. EXPECT_THROW(parser.build(json_elements), BadValue);
  501. }
  502. }; // Anonymous namespace