dbaccess_parser_unittest.cc 19 KB

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