container_unittest.cc 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476
  1. // Copyright (C) 2012 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 <datasrc/container.h>
  15. #include <datasrc/client.h>
  16. #include <datasrc/data_source.h>
  17. #include <dns/rrclass.h>
  18. #include <gtest/gtest.h>
  19. #include <set>
  20. using namespace isc::datasrc;
  21. using namespace isc::data;
  22. using namespace isc::dns;
  23. using namespace boost;
  24. using namespace std;
  25. namespace {
  26. // A test data source. It pretends it has some zones.
  27. class MockDataSourceClient : public DataSourceClient {
  28. public:
  29. class Finder : public ZoneFinder {
  30. public:
  31. Finder(const Name& origin) :
  32. origin_(origin)
  33. { }
  34. Name getOrigin() const { return (origin_); }
  35. // The rest is not to be called, so just have them
  36. RRClass getClass() const {
  37. isc_throw(isc::NotImplemented, "Not implemented");
  38. }
  39. shared_ptr<Context> find(const Name&, const RRType&,
  40. const FindOptions)
  41. {
  42. isc_throw(isc::NotImplemented, "Not implemented");
  43. }
  44. shared_ptr<Context> findAll(const Name&,
  45. vector<ConstRRsetPtr>&,
  46. const FindOptions)
  47. {
  48. isc_throw(isc::NotImplemented, "Not implemented");
  49. }
  50. FindNSEC3Result findNSEC3(const Name&, bool) {
  51. isc_throw(isc::NotImplemented, "Not implemented");
  52. }
  53. Name findPreviousName(const Name&) const {
  54. isc_throw(isc::NotImplemented, "Not implemented");
  55. }
  56. private:
  57. Name origin_;
  58. };
  59. // Constructor from a list of zones.
  60. MockDataSourceClient(const char* zone_names[]) {
  61. for (const char** zone(zone_names); *zone; ++ zone) {
  62. zones.insert(Name(*zone));
  63. }
  64. }
  65. // Constructor from configuration. The list of zones will be empty, but
  66. // it will keep the configuration inside for further inspection.
  67. MockDataSourceClient(const string& type,
  68. const ConstElementPtr& configuration) :
  69. type_(type),
  70. configuration_(configuration)
  71. { }
  72. virtual FindResult findZone(const Name& name) const {
  73. if (zones.empty()) {
  74. return (FindResult(result::NOTFOUND, ZoneFinderPtr()));
  75. }
  76. set<Name>::const_iterator it(zones.upper_bound(name));
  77. if (it == zones.begin()) {
  78. return (FindResult(result::NOTFOUND, ZoneFinderPtr()));
  79. }
  80. -- it;
  81. NameComparisonResult compar(it->compare(name));
  82. const ZoneFinderPtr finder(new Finder(*it));
  83. switch (compar.getRelation()) {
  84. case NameComparisonResult::EQUAL:
  85. return (FindResult(result::SUCCESS, finder));
  86. case NameComparisonResult::SUPERDOMAIN:
  87. return (FindResult(result::PARTIALMATCH, finder));
  88. default:
  89. return (FindResult(result::NOTFOUND, ZoneFinderPtr()));
  90. }
  91. }
  92. // These methods are not used. They just need to be there to have
  93. // complete vtable.
  94. virtual ZoneUpdaterPtr getUpdater(const Name&, bool, bool) const {
  95. isc_throw(isc::NotImplemented, "Not implemented");
  96. }
  97. virtual pair<ZoneJournalReader::Result, ZoneJournalReaderPtr>
  98. getJournalReader(const Name&, uint32_t, uint32_t) const
  99. {
  100. isc_throw(isc::NotImplemented, "Not implemented");
  101. }
  102. const string type_;
  103. const ConstElementPtr configuration_;
  104. private:
  105. set<Name> zones;
  106. };
  107. // The test version is the same as the normal version. We, however, add
  108. // some methods to dig directly in the internals, for the tests.
  109. class TestedContainer : public ConfigurableContainer {
  110. public:
  111. DataSources& getDataSources() { return (data_sources_); }
  112. // Overwrite the containers method to get a data source with given type
  113. // and configuration. We mock the data source and don't create the
  114. // container. This is just to avoid some complexity in the tests.
  115. virtual DataSourcePair getDataSource(const string& type,
  116. const ConstElementPtr& configuration)
  117. {
  118. if (type == "error") {
  119. isc_throw(DataSourceError, "The error data source type");
  120. }
  121. shared_ptr<MockDataSourceClient>
  122. ds(new MockDataSourceClient(type, configuration));
  123. // Make sure it is deleted when the test container is deleted.
  124. to_delete_.push_back(ds);
  125. return (DataSourcePair(ds.get(), DataSourceClientContainerPtr()));
  126. }
  127. private:
  128. // Hold list of data sources created internally, so they are preserved
  129. // until the end of the test and then deleted.
  130. vector<shared_ptr<MockDataSourceClient> > to_delete_;
  131. };
  132. const char* ds_zones[][3] = {
  133. {
  134. "example.org.",
  135. "example.com.",
  136. NULL
  137. },
  138. {
  139. "sub.example.org.",
  140. NULL, NULL
  141. },
  142. {
  143. NULL, NULL, NULL
  144. },
  145. {
  146. "sub.example.org.",
  147. NULL, NULL
  148. }
  149. };
  150. const size_t ds_count = (sizeof (ds_zones) / sizeof (*ds_zones));
  151. class ContainerTest : public ::testing::Test {
  152. public:
  153. ContainerTest() :
  154. // The empty list corresponds to a container with no elements inside
  155. container_(new TestedContainer()),
  156. config_elem_(Element::fromJSON("["
  157. "{"
  158. " \"type\": \"test_type\","
  159. " \"cache\": \"off\","
  160. " \"params\": {}"
  161. "}]"))
  162. {
  163. for (size_t i(0); i < ds_count; ++ i) {
  164. shared_ptr<MockDataSourceClient>
  165. ds(new MockDataSourceClient(ds_zones[i]));
  166. ds_.push_back(ds);
  167. ds_info_.push_back(ConfigurableContainer::DataSourceInfo(ds.get(),
  168. DataSourceClientContainerPtr()));
  169. }
  170. }
  171. // Check the positive result is as we expect it.
  172. void positiveResult(const Container::FindResult& result,
  173. const shared_ptr<MockDataSourceClient>& dsrc,
  174. const Name& name, bool exact,
  175. const char* test)
  176. {
  177. SCOPED_TRACE(test);
  178. EXPECT_EQ(dsrc.get(), result.datasrc_);
  179. ASSERT_NE(ZoneFinderPtr(), result.finder_);
  180. EXPECT_EQ(name, result.finder_->getOrigin());
  181. EXPECT_EQ(name.getLabelCount(), result.matched_labels_);
  182. EXPECT_EQ(exact, result.exact_match_);
  183. }
  184. // Configure the container with multiple data sources, according to
  185. // some configuration. It uses the index as parameter, to be able to
  186. // loop through the configurations.
  187. void multiConfiguration(size_t index) {
  188. container_->getDataSources().clear();
  189. switch (index) {
  190. case 2:
  191. container_->getDataSources().push_back(ds_info_[2]);
  192. // The ds_[2] is empty. We just check that it doesn't confuse
  193. // us. Fall through to the case 0.
  194. case 0:
  195. container_->getDataSources().push_back(ds_info_[0]);
  196. container_->getDataSources().push_back(ds_info_[1]);
  197. break;
  198. case 1:
  199. // The other order
  200. container_->getDataSources().push_back(ds_info_[1]);
  201. container_->getDataSources().push_back(ds_info_[0]);
  202. break;
  203. case 3:
  204. container_->getDataSources().push_back(ds_info_[1]);
  205. container_->getDataSources().push_back(ds_info_[0]);
  206. // It is the same as ds_[1], but we take from the first one.
  207. // The first one to match is the correct one.
  208. container_->getDataSources().push_back(ds_info_[3]);
  209. break;
  210. default:
  211. FAIL() << "Unknown configuration index " << index;
  212. }
  213. }
  214. void checkDS(size_t index, const string& type, const string& params) {
  215. ASSERT_GT(container_->getDataSources().size(), index);
  216. MockDataSourceClient* ds(dynamic_cast<MockDataSourceClient*>(
  217. container_->getDataSources()[index].data_src_));
  218. // Comparing with NULL does not work
  219. ASSERT_NE(ds, static_cast<const MockDataSourceClient*>(NULL));
  220. EXPECT_EQ(type, ds->type_);
  221. EXPECT_TRUE(Element::fromJSON(params)->equals(*ds->configuration_));
  222. }
  223. shared_ptr<TestedContainer> container_;
  224. const Container::FindResult negativeResult_;
  225. vector<shared_ptr<MockDataSourceClient> > ds_;
  226. vector<ConfigurableContainer::DataSourceInfo> ds_info_;
  227. const ConstElementPtr config_elem_;
  228. };
  229. // Test the test itself
  230. TEST_F(ContainerTest, selfTest) {
  231. EXPECT_EQ(result::SUCCESS, ds_[0]->findZone(Name("example.org")).code);
  232. EXPECT_EQ(result::PARTIALMATCH,
  233. ds_[0]->findZone(Name("sub.example.org")).code);
  234. EXPECT_EQ(result::NOTFOUND, ds_[0]->findZone(Name("org")).code);
  235. EXPECT_EQ(result::NOTFOUND, ds_[1]->findZone(Name("example.org")).code);
  236. EXPECT_EQ(result::NOTFOUND, ds_[0]->findZone(Name("aaa")).code);
  237. EXPECT_EQ(result::NOTFOUND, ds_[0]->findZone(Name("zzz")).code);
  238. }
  239. // Test the container we create with empty configuration is, in fact, empty
  240. TEST_F(ContainerTest, emptyContainer) {
  241. EXPECT_TRUE(container_->getDataSources().empty());
  242. }
  243. // Check the values returned by a find on an empty container. It should be
  244. // a negative answer (nothing found) no matter if we want an exact or inexact
  245. // match.
  246. TEST_F(ContainerTest, emptySearch) {
  247. // No matter what we try, we don't get an answer.
  248. // Note: we don't have operator<< for the result class, so we cannot use
  249. // EXPECT_EQ. Same for other similar cases.
  250. EXPECT_TRUE(negativeResult_ == container_->find(Name("example.org"),
  251. false, false));
  252. EXPECT_TRUE(negativeResult_ == container_->find(Name("example.org"),
  253. false, true));
  254. EXPECT_TRUE(negativeResult_ == container_->find(Name("example.org"), true,
  255. false));
  256. EXPECT_TRUE(negativeResult_ == container_->find(Name("example.org"), true,
  257. true));
  258. }
  259. // Put a single data source inside the container and check it can find an
  260. // exact match if there's one.
  261. TEST_F(ContainerTest, singleDSExactMatch) {
  262. container_->getDataSources().push_back(ds_info_[0]);
  263. // This zone is not there
  264. EXPECT_TRUE(negativeResult_ == container_->find(Name("org."), true));
  265. // But this one is, so check it.
  266. positiveResult(container_->find(Name("example.org"), true),
  267. ds_[0], Name("example.org"), true, "Exact match");
  268. // When asking for a sub zone of a zone there, we get nothing
  269. // (we want exact match, this would be partial one)
  270. EXPECT_TRUE(negativeResult_ == container_->find(Name("sub.example.org."),
  271. true));
  272. }
  273. // When asking for a partial match, we get all that the exact one, but more.
  274. TEST_F(ContainerTest, singleDSBestMatch) {
  275. container_->getDataSources().push_back(ds_info_[0]);
  276. // This zone is not there
  277. EXPECT_TRUE(negativeResult_ == container_->find(Name("org.")));
  278. // But this one is, so check it.
  279. positiveResult(container_->find(Name("example.org")),
  280. ds_[0], Name("example.org"), true, "Exact match");
  281. // When asking for a sub zone of a zone there, we get the parent
  282. // one.
  283. positiveResult(container_->find(Name("sub.example.org.")),
  284. ds_[0], Name("example.org"), false, "Subdomain match");
  285. }
  286. const char* test_names[] = {
  287. "Sub second",
  288. "Sub first",
  289. "With empty",
  290. "With a duplicity"
  291. };
  292. TEST_F(ContainerTest, multiExactMatch) {
  293. // Run through all the multi-configurations
  294. for (size_t i(0); i < sizeof(test_names) / sizeof(*test_names); ++ i) {
  295. SCOPED_TRACE(test_names[i]);
  296. multiConfiguration(i);
  297. // Something that is nowhere there
  298. EXPECT_TRUE(negativeResult_ == container_->find(Name("org."), true));
  299. // This one is there exactly.
  300. positiveResult(container_->find(Name("example.org"), true),
  301. ds_[0], Name("example.org"), true, "Exact match");
  302. // This one too, but in a different data source.
  303. positiveResult(container_->find(Name("sub.example.org."), true),
  304. ds_[1], Name("sub.example.org"), true,
  305. "Subdomain match");
  306. // But this one is in neither data source.
  307. EXPECT_TRUE(negativeResult_ ==
  308. container_->find(Name("sub.example.com."), true));
  309. }
  310. }
  311. TEST_F(ContainerTest, multiBestMatch) {
  312. // Run through all the multi-configurations
  313. for (size_t i(0); i < 4; ++ i) {
  314. SCOPED_TRACE(test_names[i]);
  315. multiConfiguration(i);
  316. // Something that is nowhere there
  317. EXPECT_TRUE(negativeResult_ == container_->find(Name("org.")));
  318. // This one is there exactly.
  319. positiveResult(container_->find(Name("example.org")),
  320. ds_[0], Name("example.org"), true, "Exact match");
  321. // This one too, but in a different data source.
  322. positiveResult(container_->find(Name("sub.example.org.")),
  323. ds_[1], Name("sub.example.org"), true,
  324. "Subdomain match");
  325. // But this one is in neither data source. But it is a subdomain
  326. // of one of the zones in the first data source.
  327. positiveResult(container_->find(Name("sub.example.com.")),
  328. ds_[0], Name("example.com."), false,
  329. "Subdomain in com");
  330. }
  331. }
  332. // Check the configuration is empty when the list is empty
  333. TEST_F(ContainerTest, configureEmpty) {
  334. ConstElementPtr elem(new ListElement);
  335. container_->configure(*elem, true);
  336. EXPECT_TRUE(container_->getDataSources().empty());
  337. }
  338. // Check we can get multiple data sources and they are in the right order.
  339. TEST_F(ContainerTest, configureMulti) {
  340. ConstElementPtr elem(Element::fromJSON("["
  341. "{"
  342. " \"type\": \"type1\","
  343. " \"cache\": \"off\","
  344. " \"params\": {}"
  345. "},"
  346. "{"
  347. " \"type\": \"type2\","
  348. " \"cache\": \"off\","
  349. " \"params\": {}"
  350. "}]"
  351. ));
  352. container_->configure(*elem, true);
  353. EXPECT_EQ(2, container_->getDataSources().size());
  354. checkDS(0, "type1", "{}");
  355. checkDS(1, "type2", "{}");
  356. }
  357. // Check we can pass whatever we want to the params
  358. TEST_F(ContainerTest, configureParams) {
  359. const char* params[] = {
  360. "true",
  361. "false",
  362. "null",
  363. "\"hello\"",
  364. "42",
  365. "[]",
  366. "{}",
  367. NULL
  368. };
  369. for (const char** param(params); *param; ++param) {
  370. SCOPED_TRACE(*param);
  371. ConstElementPtr elem(Element::fromJSON(string("["
  372. "{"
  373. " \"type\": \"t\","
  374. " \"cache\": \"off\","
  375. " \"params\": ") + *param +
  376. "}]"));
  377. container_->configure(*elem, true);
  378. EXPECT_EQ(1, container_->getDataSources().size());
  379. checkDS(0, "t", *param);
  380. }
  381. }
  382. TEST_F(ContainerTest, wrongConfig) {
  383. const char* configs[] = {
  384. // A lot of stuff missing from there
  385. "[{\"type\": \"test_type\", \"params\": 13}, {}]",
  386. // Some bad types completely
  387. "{}",
  388. "true",
  389. "42",
  390. "null",
  391. "[{\"type\": \"test_type\", \"params\": 13}, true]",
  392. "[{\"type\": \"test_type\", \"params\": 13}, []]",
  393. "[{\"type\": \"test_type\", \"params\": 13}, 42]",
  394. // Bad type of type
  395. "[{\"type\": \"test_type\", \"params\": 13}, {\"type\": 42}]",
  396. "[{\"type\": \"test_type\", \"params\": 13}, {\"type\": true}]",
  397. "[{\"type\": \"test_type\", \"params\": 13}, {\"type\": null}]",
  398. "[{\"type\": \"test_type\", \"params\": 13}, {\"type\": []}]",
  399. "[{\"type\": \"test_type\", \"params\": 13}, {\"type\": {}}]",
  400. // TODO: Once cache is supported, add some invalid cache values
  401. NULL
  402. };
  403. // Put something inside to see it survives the exception
  404. container_->configure(*config_elem_, true);
  405. checkDS(0, "test_type", "{}");
  406. for (const char** config(configs); *config; ++config) {
  407. SCOPED_TRACE(*config);
  408. ConstElementPtr elem(Element::fromJSON(*config));
  409. EXPECT_THROW(container_->configure(*elem, true),
  410. ConfigurableContainer::ConfigurationError);
  411. // Still untouched
  412. checkDS(0, "test_type", "{}");
  413. EXPECT_EQ(1, container_->getDataSources().size());
  414. }
  415. }
  416. // The param thing defaults to null. Cache is not used yet.
  417. TEST_F(ContainerTest, defaults) {
  418. ConstElementPtr elem(Element::fromJSON("["
  419. "{"
  420. " \"type\": \"type1\""
  421. "}]"));
  422. container_->configure(*elem, true);
  423. EXPECT_EQ(1, container_->getDataSources().size());
  424. checkDS(0, "type1", "null");
  425. }
  426. // Check we can call the configure multiple times, to change the configuration
  427. TEST_F(ContainerTest, reconfigure) {
  428. ConstElementPtr empty(new ListElement);
  429. container_->configure(*config_elem_, true);
  430. checkDS(0, "test_type", "{}");
  431. container_->configure(*empty, true);
  432. EXPECT_TRUE(container_->getDataSources().empty());
  433. container_->configure(*config_elem_, true);
  434. checkDS(0, "test_type", "{}");
  435. }
  436. // Make sure the data source error exception from the factory is propagated
  437. TEST_F(ContainerTest, dataSrcError) {
  438. ConstElementPtr elem(Element::fromJSON("["
  439. "{"
  440. " \"type\": \"error\""
  441. "}]"));
  442. container_->configure(*config_elem_, true);
  443. checkDS(0, "test_type", "{}");
  444. EXPECT_THROW(container_->configure(*elem, true), DataSourceError);
  445. checkDS(0, "test_type", "{}");
  446. }
  447. }