dbaccess_parser_unittest.cc 21 KB

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