dbaccess_parser_unittest.cc 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599
  1. // Copyright (C) 2012-2017 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 "parse" 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(DbAccessParser::DBType type)
  169. : DbAccessParser(type)
  170. {}
  171. /// @brief Destructor
  172. virtual ~TestDbAccessParser()
  173. {}
  174. /// @brief Parse configuration value
  175. void parse(ConstElementPtr database_config) {
  176. CfgDbAccessPtr cfg_db(new CfgDbAccess());
  177. DbAccessParser::parse(cfg_db, database_config);
  178. }
  179. /// Allow use of superclass's protected functions.
  180. using DbAccessParser::getDbAccessParameters;
  181. using DbAccessParser::getDbAccessString;
  182. /// @brief Get database access parameters
  183. ///
  184. /// Used in testing to check that the configuration information has been
  185. /// parsed corrected.
  186. ///
  187. /// @return Map of keyword/value pairs representing database access
  188. /// information.
  189. const StringPairMap& getDbAccessParameters() const {
  190. return (DbAccessParser::getDbAccessParameters());
  191. }
  192. /// @brief Construct database access string
  193. ///
  194. /// Constructs the database access string from the stored parameters.
  195. ///
  196. /// @return Database access string
  197. std::string getDbAccessString() const {
  198. return (DbAccessParser::getDbAccessString());
  199. }
  200. };
  201. // Check that the parser works with a simple configuration.
  202. TEST_F(DbAccessParserTest, validTypeMemfile) {
  203. const char* config[] = {"type", "memfile",
  204. NULL};
  205. string json_config = toJson(config);
  206. ConstElementPtr json_elements = Element::fromJSON(json_config);
  207. EXPECT_TRUE(json_elements);
  208. TestDbAccessParser parser(DbAccessParser::LEASE_DB);
  209. EXPECT_NO_THROW(parser.parse(json_elements));
  210. checkAccessString("Valid memfile", parser.getDbAccessParameters(), config);
  211. }
  212. // Check that the parser works with a simple configuration that
  213. // includes empty elements.
  214. TEST_F(DbAccessParserTest, emptyKeyword) {
  215. const char* config[] = {"type", "memfile",
  216. "name", "",
  217. NULL};
  218. string json_config = toJson(config);
  219. ConstElementPtr json_elements = Element::fromJSON(json_config);
  220. EXPECT_TRUE(json_elements);
  221. TestDbAccessParser parser(DbAccessParser::LEASE_DB);
  222. EXPECT_NO_THROW(parser.parse(json_elements));
  223. checkAccessString("Valid memfile", parser.getDbAccessParameters(), config);
  224. }
  225. // Check that the parser works with more complex configuration when
  226. // lease file path is specified for DHCPv4.
  227. TEST_F(DbAccessParserTest, persistV4Memfile) {
  228. const char* config[] = {"type", "memfile",
  229. "persist", "true",
  230. "name", "/opt/kea/var/kea-leases4.csv",
  231. NULL};
  232. string json_config = toJson(config);
  233. ConstElementPtr json_elements = Element::fromJSON(json_config);
  234. EXPECT_TRUE(json_elements);
  235. TestDbAccessParser parser(DbAccessParser::LEASE_DB);
  236. EXPECT_NO_THROW(parser.parse(json_elements));
  237. checkAccessString("Valid memfile", parser.getDbAccessParameters(),
  238. config);
  239. }
  240. // Check that the parser works with more complex configuration when
  241. // lease file path is specified for DHCPv6.
  242. TEST_F(DbAccessParserTest, persistV6Memfile) {
  243. const char* config[] = {"type", "memfile",
  244. "persist", "true",
  245. "name", "/opt/kea/var/kea-leases6.csv",
  246. NULL};
  247. string json_config = toJson(config);
  248. ConstElementPtr json_elements = Element::fromJSON(json_config);
  249. EXPECT_TRUE(json_elements);
  250. TestDbAccessParser parser(DbAccessParser::LEASE_DB);
  251. EXPECT_NO_THROW(parser.parse(json_elements));
  252. checkAccessString("Valid memfile", parser.getDbAccessParameters(),
  253. config);
  254. }
  255. // This test checks that the parser accepts the valid value of the
  256. // lfc-interval parameter.
  257. TEST_F(DbAccessParserTest, validLFCInterval) {
  258. const char* config[] = {"type", "memfile",
  259. "name", "/opt/kea/var/kea-leases6.csv",
  260. "lfc-interval", "3600",
  261. NULL};
  262. string json_config = toJson(config);
  263. ConstElementPtr json_elements = Element::fromJSON(json_config);
  264. EXPECT_TRUE(json_elements);
  265. TestDbAccessParser parser(DbAccessParser::LEASE_DB);
  266. EXPECT_NO_THROW(parser.parse(json_elements));
  267. checkAccessString("Valid LFC Interval", parser.getDbAccessParameters(),
  268. config);
  269. }
  270. // This test checks that the parser rejects the negative value of the
  271. // lfc-interval parameter.
  272. TEST_F(DbAccessParserTest, negativeLFCInterval) {
  273. const char* config[] = {"type", "memfile",
  274. "name", "/opt/kea/var/kea-leases6.csv",
  275. "lfc-interval", "-1",
  276. NULL};
  277. string json_config = toJson(config);
  278. ConstElementPtr json_elements = Element::fromJSON(json_config);
  279. EXPECT_TRUE(json_elements);
  280. TestDbAccessParser parser(DbAccessParser::LEASE_DB);
  281. EXPECT_THROW(parser.parse(json_elements), BadValue);
  282. }
  283. // This test checks that the parser rejects the too large (greater than
  284. // the max uint32_t) value of the lfc-interval parameter.
  285. TEST_F(DbAccessParserTest, largeLFCInterval) {
  286. const char* config[] = {"type", "memfile",
  287. "name", "/opt/kea/var/kea-leases6.csv",
  288. "lfc-interval", "4294967296",
  289. NULL};
  290. string json_config = toJson(config);
  291. ConstElementPtr json_elements = Element::fromJSON(json_config);
  292. EXPECT_TRUE(json_elements);
  293. TestDbAccessParser parser(DbAccessParser::LEASE_DB);
  294. EXPECT_THROW(parser.parse(json_elements), BadValue);
  295. }
  296. // This test checks that the parser accepts the valid value of the
  297. // timeout parameter.
  298. TEST_F(DbAccessParserTest, validTimeout) {
  299. const char* config[] = {"type", "memfile",
  300. "name", "/opt/kea/var/kea-leases6.csv",
  301. "connect-timeout", "3600",
  302. NULL};
  303. string json_config = toJson(config);
  304. ConstElementPtr json_elements = Element::fromJSON(json_config);
  305. EXPECT_TRUE(json_elements);
  306. TestDbAccessParser parser(DbAccessParser::LEASE_DB);
  307. EXPECT_NO_THROW(parser.parse(json_elements));
  308. checkAccessString("Valid timeout", parser.getDbAccessParameters(),
  309. config);
  310. }
  311. // This test checks that the parser rejects the negative value of the
  312. // timeout parameter.
  313. TEST_F(DbAccessParserTest, negativeTimeout) {
  314. const char* config[] = {"type", "memfile",
  315. "name", "/opt/kea/var/kea-leases6.csv",
  316. "connect-timeout", "-1",
  317. NULL};
  318. string json_config = toJson(config);
  319. ConstElementPtr json_elements = Element::fromJSON(json_config);
  320. EXPECT_TRUE(json_elements);
  321. TestDbAccessParser parser(DbAccessParser::LEASE_DB);
  322. EXPECT_THROW(parser.parse(json_elements), BadValue);
  323. }
  324. // This test checks that the parser rejects a too large (greater than
  325. // the max uint32_t) value of the timeout parameter.
  326. TEST_F(DbAccessParserTest, largeTimeout) {
  327. const char* config[] = {"type", "memfile",
  328. "name", "/opt/kea/var/kea-leases6.csv",
  329. "connect-timeout", "4294967296",
  330. NULL};
  331. string json_config = toJson(config);
  332. ConstElementPtr json_elements = Element::fromJSON(json_config);
  333. EXPECT_TRUE(json_elements);
  334. TestDbAccessParser parser(DbAccessParser::LEASE_DB);
  335. EXPECT_THROW(parser.parse(json_elements), BadValue);
  336. }
  337. // Check that the parser works with a valid MySQL configuration
  338. TEST_F(DbAccessParserTest, validTypeMysql) {
  339. const char* config[] = {"type", "mysql",
  340. "host", "erewhon",
  341. "user", "kea",
  342. "password", "keapassword",
  343. "name", "keatest",
  344. NULL};
  345. string json_config = toJson(config);
  346. ConstElementPtr json_elements = Element::fromJSON(json_config);
  347. EXPECT_TRUE(json_elements);
  348. TestDbAccessParser parser(DbAccessParser::LEASE_DB);
  349. EXPECT_NO_THROW(parser.parse(json_elements));
  350. checkAccessString("Valid mysql", parser.getDbAccessParameters(), config);
  351. }
  352. // A missing 'type' keyword should cause an exception to be thrown.
  353. TEST_F(DbAccessParserTest, missingTypeKeyword) {
  354. const char* config[] = {"host", "erewhon",
  355. "user", "kea",
  356. "password", "keapassword",
  357. "name", "keatest",
  358. NULL};
  359. string json_config = toJson(config);
  360. ConstElementPtr json_elements = Element::fromJSON(json_config);
  361. EXPECT_TRUE(json_elements);
  362. TestDbAccessParser parser(DbAccessParser::LEASE_DB);
  363. EXPECT_THROW(parser.parse(json_elements), TypeKeywordMissing);
  364. }
  365. // Check reconfiguration. Checks that incremental changes applied to the
  366. // database configuration are incremental.
  367. TEST_F(DbAccessParserTest, incrementalChanges) {
  368. const char* config1[] = {"type", "memfile",
  369. NULL};
  370. // Applying config2 will cause a wholesale change.
  371. const char* config2[] = {"type", "mysql",
  372. "host", "erewhon",
  373. "user", "kea",
  374. "password", "keapassword",
  375. "name", "keatest",
  376. NULL};
  377. // Applying incremental2 should cause a change to config3.
  378. const char* incremental2[] = {"user", "me",
  379. "password", "meagain",
  380. NULL};
  381. const char* config3[] = {"type", "mysql",
  382. "host", "erewhon",
  383. "user", "me",
  384. "password", "meagain",
  385. "name", "keatest",
  386. NULL};
  387. // incremental3 will cause an exception. There should be no change
  388. // to the returned value.
  389. const char* incremental3[] = {"type", "invalid",
  390. "user", "you",
  391. "password", "youagain",
  392. NULL};
  393. // incremental4 is a compatible change and should cause a transition
  394. // to config4.
  395. const char* incremental4[] = {"user", "them",
  396. "password", "",
  397. NULL};
  398. const char* config4[] = {"type", "mysql",
  399. "host", "erewhon",
  400. "user", "them",
  401. "password", "",
  402. "name", "keatest",
  403. NULL};
  404. TestDbAccessParser parser(DbAccessParser::LEASE_DB);
  405. // First configuration string should cause a representation of that string
  406. // to be held.
  407. string json_config = toJson(config1);
  408. ConstElementPtr json_elements = Element::fromJSON(json_config);
  409. EXPECT_TRUE(json_elements);
  410. EXPECT_NO_THROW(parser.parse(json_elements));
  411. checkAccessString("Initial configuration", parser.getDbAccessParameters(),
  412. config1);
  413. // Applying a wholesale change will cause the access string to change
  414. // to a representation of the new configuration.
  415. json_config = toJson(config2);
  416. json_elements = Element::fromJSON(json_config);
  417. EXPECT_TRUE(json_elements);
  418. EXPECT_NO_THROW(parser.parse(json_elements));
  419. checkAccessString("Subsequent configuration", parser.getDbAccessParameters(),
  420. config2);
  421. // Applying an incremental change will cause the representation to change
  422. // incrementally.
  423. json_config = toJson(incremental2);
  424. json_elements = Element::fromJSON(json_config);
  425. EXPECT_TRUE(json_elements);
  426. EXPECT_NO_THROW(parser.parse(json_elements));
  427. checkAccessString("Incremental configuration", parser.getDbAccessParameters(),
  428. config3);
  429. // Applying the next incremental change should cause an exception to be
  430. // thrown and there be no change to the access string.
  431. json_config = toJson(incremental3);
  432. json_elements = Element::fromJSON(json_config);
  433. EXPECT_TRUE(json_elements);
  434. EXPECT_THROW(parser.parse(json_elements), BadValue);
  435. checkAccessString("Incompatible incremental change", parser.getDbAccessParameters(),
  436. config3);
  437. // Applying an incremental change will cause the representation to change
  438. // incrementally.
  439. json_config = toJson(incremental4);
  440. json_elements = Element::fromJSON(json_config);
  441. EXPECT_TRUE(json_elements);
  442. EXPECT_NO_THROW(parser.parse(json_elements));
  443. checkAccessString("Compatible incremental change", parser.getDbAccessParameters(),
  444. config4);
  445. }
  446. // Check that the database access string is constructed correctly.
  447. TEST_F(DbAccessParserTest, getDbAccessString) {
  448. const char* config[] = {"type", "mysql",
  449. "host", "" ,
  450. "name", "keatest",
  451. NULL};
  452. string json_config = toJson(config);
  453. ConstElementPtr json_elements = Element::fromJSON(json_config);
  454. EXPECT_TRUE(json_elements);
  455. TestDbAccessParser parser(DbAccessParser::LEASE_DB);
  456. EXPECT_NO_THROW(parser.parse(json_elements));
  457. // Get the database access string
  458. std::string dbaccess = parser.getDbAccessString();
  459. // String should be either "type=mysql name=keatest" or
  460. // "name=keatest type=mysql". The "host" entry is null, so should not be
  461. // output.
  462. EXPECT_EQ(dbaccess, "name=keatest type=mysql");
  463. }
  464. // Check that the configuration is accepted for the valid value
  465. // of "readonly".
  466. TEST_F(DbAccessParserTest, validReadOnly) {
  467. const char* config[] = {"type", "mysql",
  468. "user", "keatest",
  469. "password", "keatest",
  470. "name", "keatest",
  471. "readonly", "true",
  472. NULL};
  473. string json_config = toJson(config);
  474. ConstElementPtr json_elements = Element::fromJSON(json_config);
  475. EXPECT_TRUE(json_elements);
  476. TestDbAccessParser parser(DbAccessParser::LEASE_DB);
  477. EXPECT_NO_THROW(parser.parse(json_elements));
  478. checkAccessString("Valid readonly parameter",
  479. parser.getDbAccessParameters(),
  480. config);
  481. }
  482. // Check that for the invalid value of the "readonly" parameter
  483. // an exception is thrown.
  484. TEST_F(DbAccessParserTest, invalidReadOnly) {
  485. const char* config[] = {"type", "mysql",
  486. "user", "keatest",
  487. "password", "keatest",
  488. "name", "keatest",
  489. "readonly", "1",
  490. NULL};
  491. string json_config = toJson(config);
  492. ConstElementPtr json_elements = Element::fromJSON(json_config);
  493. EXPECT_TRUE(json_elements);
  494. TestDbAccessParser parser(DbAccessParser::LEASE_DB);
  495. EXPECT_THROW(parser.parse(json_elements), BadValue);
  496. }
  497. }; // Anonymous namespace