dbaccess_parser_unittest.cc 16 KB

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