client_list_unittest.cc 39 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993
  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/client_list.h>
  15. #include <datasrc/client.h>
  16. #include <datasrc/iterator.h>
  17. #include <datasrc/data_source.h>
  18. #include <datasrc/memory/memory_client.h>
  19. #include <datasrc/memory/zone_table_segment.h>
  20. #include <datasrc/memory/zone_finder.h>
  21. #include <dns/rrclass.h>
  22. #include <dns/rrttl.h>
  23. #include <dns/rdataclass.h>
  24. #include <gtest/gtest.h>
  25. #include <set>
  26. #include <fstream>
  27. using namespace isc::datasrc;
  28. using isc::datasrc::memory::InMemoryClient;
  29. using isc::datasrc::memory::ZoneTableSegment;
  30. using isc::datasrc::memory::InMemoryZoneFinder;
  31. using namespace isc::data;
  32. using namespace isc::dns;
  33. using namespace boost;
  34. using namespace std;
  35. namespace {
  36. // A test data source. It pretends it has some zones.
  37. class MockDataSourceClient : public DataSourceClient {
  38. public:
  39. class Finder : public ZoneFinder {
  40. public:
  41. Finder(const Name& origin) :
  42. origin_(origin)
  43. {}
  44. Name getOrigin() const { return (origin_); }
  45. // The rest is not to be called, so just have them
  46. RRClass getClass() const {
  47. isc_throw(isc::NotImplemented, "Not implemented");
  48. }
  49. shared_ptr<Context> find(const Name&, const RRType&,
  50. const FindOptions)
  51. {
  52. isc_throw(isc::NotImplemented, "Not implemented");
  53. }
  54. shared_ptr<Context> findAll(const Name&,
  55. vector<ConstRRsetPtr>&,
  56. const FindOptions)
  57. {
  58. isc_throw(isc::NotImplemented, "Not implemented");
  59. }
  60. FindNSEC3Result findNSEC3(const Name&, bool) {
  61. isc_throw(isc::NotImplemented, "Not implemented");
  62. }
  63. private:
  64. Name origin_;
  65. };
  66. class Iterator : public ZoneIterator {
  67. public:
  68. Iterator(const Name& origin, bool include_ns) :
  69. origin_(origin),
  70. soa_(new RRset(origin_, RRClass::IN(), RRType::SOA(),
  71. RRTTL(3600)))
  72. {
  73. // The RData here is bogus, but it is not used to anything. There
  74. // just needs to be some.
  75. soa_->addRdata(rdata::generic::SOA(Name::ROOT_NAME(),
  76. Name::ROOT_NAME(),
  77. 0, 0, 0, 0, 0));
  78. rrsets_.push_back(soa_);
  79. if (include_ns) {
  80. ns_.reset(new RRset(origin_, RRClass::IN(), RRType::NS(),
  81. RRTTL(3600)));
  82. ns_->addRdata(rdata::generic::NS(Name::ROOT_NAME()));
  83. rrsets_.push_back(ns_);
  84. }
  85. rrsets_.push_back(ConstRRsetPtr());
  86. it_ = rrsets_.begin();
  87. }
  88. virtual isc::dns::ConstRRsetPtr getNextRRset() {
  89. ConstRRsetPtr result = *it_;
  90. ++it_;
  91. return (result);
  92. }
  93. virtual isc::dns::ConstRRsetPtr getSOA() const {
  94. return (soa_);
  95. }
  96. private:
  97. const Name origin_;
  98. const RRsetPtr soa_;
  99. RRsetPtr ns_;
  100. std::vector<ConstRRsetPtr> rrsets_;
  101. std::vector<ConstRRsetPtr>::const_iterator it_;
  102. };
  103. // Constructor from a list of zones.
  104. MockDataSourceClient(const char* zone_names[]) :
  105. have_ns_(true), use_baditerator_(true)
  106. {
  107. for (const char** zone(zone_names); *zone; ++zone) {
  108. zones.insert(Name(*zone));
  109. }
  110. }
  111. // Constructor from configuration. The list of zones will be empty, but
  112. // it will keep the configuration inside for further inspection.
  113. MockDataSourceClient(const string& type,
  114. const ConstElementPtr& configuration) :
  115. type_(type),
  116. configuration_(configuration),
  117. have_ns_(true), use_baditerator_(true)
  118. {
  119. EXPECT_NE("MasterFiles", type) << "MasterFiles is a special case "
  120. "and it never should be created as a data source client";
  121. if (configuration_->getType() == Element::list) {
  122. for (size_t i(0); i < configuration_->size(); ++i) {
  123. zones.insert(Name(configuration_->get(i)->stringValue()));
  124. }
  125. }
  126. }
  127. virtual FindResult findZone(const Name& name) const {
  128. if (zones.empty()) {
  129. return (FindResult(result::NOTFOUND, ZoneFinderPtr()));
  130. }
  131. set<Name>::const_iterator it(zones.upper_bound(name));
  132. if (it == zones.begin()) {
  133. return (FindResult(result::NOTFOUND, ZoneFinderPtr()));
  134. }
  135. --it;
  136. NameComparisonResult compar(it->compare(name));
  137. const ZoneFinderPtr finder(new Finder(*it));
  138. switch (compar.getRelation()) {
  139. case NameComparisonResult::EQUAL:
  140. return (FindResult(result::SUCCESS, finder));
  141. case NameComparisonResult::SUPERDOMAIN:
  142. return (FindResult(result::PARTIALMATCH, finder));
  143. default:
  144. return (FindResult(result::NOTFOUND, ZoneFinderPtr()));
  145. }
  146. }
  147. // These methods are not used. They just need to be there to have
  148. // complete vtable.
  149. virtual ZoneUpdaterPtr getUpdater(const Name&, bool, bool) const {
  150. isc_throw(isc::NotImplemented, "Not implemented");
  151. }
  152. virtual pair<ZoneJournalReader::Result, ZoneJournalReaderPtr>
  153. getJournalReader(const Name&, uint32_t, uint32_t) const
  154. {
  155. isc_throw(isc::NotImplemented, "Not implemented");
  156. }
  157. virtual ZoneIteratorPtr getIterator(const Name& name, bool) const {
  158. if (use_baditerator_ && name == Name("noiter.org")) {
  159. isc_throw(isc::NotImplemented, "Asked not to be implemented");
  160. } else if (use_baditerator_ && name == Name("null.org")) {
  161. return (ZoneIteratorPtr());
  162. } else {
  163. FindResult result(findZone(name));
  164. if (result.code == isc::datasrc::result::SUCCESS) {
  165. return (ZoneIteratorPtr(new Iterator(name, have_ns_)));
  166. } else {
  167. isc_throw(DataSourceError, "No such zone");
  168. }
  169. }
  170. }
  171. void disableNS() { have_ns_ = false; }
  172. void disableBadIterator() { use_baditerator_ = false; }
  173. const string type_;
  174. const ConstElementPtr configuration_;
  175. private:
  176. set<Name> zones;
  177. bool have_ns_; // control the iterator behavior wrt whether to include NS
  178. bool use_baditerator_; // whether to use bogus zone iterators for tests
  179. };
  180. // The test version is the same as the normal version. We, however, add
  181. // some methods to dig directly in the internals, for the tests.
  182. class TestedList : public ConfigurableClientList {
  183. public:
  184. TestedList(const RRClass& rrclass) :
  185. ConfigurableClientList(rrclass)
  186. {}
  187. DataSources& getDataSources() { return (data_sources_); }
  188. // Overwrite the list's method to get a data source with given type
  189. // and configuration. We mock the data source and don't create the
  190. // container. This is just to avoid some complexity in the tests.
  191. virtual DataSourcePair getDataSourceClient(const string& type,
  192. const ConstElementPtr&
  193. configuration)
  194. {
  195. if (type == "error") {
  196. isc_throw(DataSourceError, "The error data source type");
  197. }
  198. shared_ptr<MockDataSourceClient>
  199. ds(new MockDataSourceClient(type, configuration));
  200. // Make sure it is deleted when the test list is deleted.
  201. to_delete_.push_back(ds);
  202. return (DataSourcePair(ds.get(), DataSourceClientContainerPtr()));
  203. }
  204. private:
  205. // Hold list of data sources created internally, so they are preserved
  206. // until the end of the test and then deleted.
  207. vector<shared_ptr<MockDataSourceClient> > to_delete_;
  208. };
  209. const char* ds_zones[][3] = {
  210. {
  211. "example.org.",
  212. "example.com.",
  213. NULL
  214. },
  215. {
  216. "sub.example.org.",
  217. NULL, NULL
  218. },
  219. {
  220. NULL, NULL, NULL
  221. },
  222. {
  223. "sub.example.org.",
  224. NULL, NULL
  225. }
  226. };
  227. const size_t ds_count = (sizeof(ds_zones) / sizeof(*ds_zones));
  228. class ListTest : public ::testing::Test {
  229. public:
  230. ListTest() :
  231. rrclass_(RRClass::IN()),
  232. // The empty list corresponds to a list with no elements inside
  233. list_(new TestedList(rrclass_)),
  234. config_elem_(Element::fromJSON("["
  235. "{"
  236. " \"type\": \"test_type\","
  237. " \"params\": {}"
  238. "}]")),
  239. config_elem_zones_(Element::fromJSON("["
  240. "{"
  241. " \"type\": \"test_type\","
  242. " \"params\": [\"example.org\", \"example.com\", "
  243. " \"noiter.org\", \"null.org\"]"
  244. "}]")),
  245. config_(Element::fromJSON("{}")),
  246. segment_(ZoneTableSegment::create(*config_))
  247. {
  248. for (size_t i(0); i < ds_count; ++ i) {
  249. shared_ptr<MockDataSourceClient>
  250. ds(new MockDataSourceClient(ds_zones[i]));
  251. ds_.push_back(ds);
  252. ds_info_.push_back(ConfigurableClientList::DataSourceInfo(
  253. ds.get(), DataSourceClientContainerPtr(),
  254. false, rrclass_, segment_));
  255. }
  256. }
  257. // Install a "fake" cached zone using a temporary underlying data source
  258. // client.
  259. void prepareCache(size_t index, const Name& zone) {
  260. // Prepare the temporary data source client
  261. const char* zones[2];
  262. const std::string zonename_txt = zone.toText();
  263. zones[0] = zonename_txt.c_str();
  264. zones[1] = NULL;
  265. MockDataSourceClient mock_client(zones);
  266. // Disable some default features of the mock to distinguish the
  267. // temporary case from normal case.
  268. mock_client.disableNS();
  269. mock_client.disableBadIterator();
  270. // Create cache from the temporary data source, and push it to the
  271. // client list.
  272. const shared_ptr<InMemoryClient> cache(new InMemoryClient(segment_,
  273. rrclass_));
  274. cache->load(zone, *mock_client.getIterator(zone, false));
  275. ConfigurableClientList::DataSourceInfo& dsrc_info =
  276. list_->getDataSources()[index];
  277. dsrc_info.cache_ = cache;
  278. }
  279. // Check the positive result is as we expect it.
  280. void positiveResult(const ClientList::FindResult& result,
  281. const shared_ptr<MockDataSourceClient>& dsrc,
  282. const Name& name, bool exact,
  283. const char* test, bool from_cache = false)
  284. {
  285. SCOPED_TRACE(test);
  286. ASSERT_NE(ZoneFinderPtr(), result.finder_);
  287. EXPECT_EQ(name, result.finder_->getOrigin());
  288. EXPECT_EQ(exact, result.exact_match_);
  289. // If it is a positive result, there's something to keep
  290. // alive, even when we don't know what it is.
  291. // Any better idea how to test it actually keeps the thing
  292. // alive?
  293. EXPECT_NE(shared_ptr<ClientList::FindResult::LifeKeeper>(),
  294. result.life_keeper_);
  295. if (from_cache) {
  296. EXPECT_NE(shared_ptr<InMemoryZoneFinder>(),
  297. dynamic_pointer_cast<InMemoryZoneFinder>(
  298. result.finder_)) << "Finder is not from cache";
  299. EXPECT_TRUE(NULL !=
  300. dynamic_cast<InMemoryClient*>(result.dsrc_client_));
  301. } else {
  302. EXPECT_EQ(dsrc.get(), result.dsrc_client_);
  303. }
  304. }
  305. // Configure the list with multiple data sources, according to
  306. // some configuration. It uses the index as parameter, to be able to
  307. // loop through the configurations.
  308. void multiConfiguration(size_t index) {
  309. list_->getDataSources().clear();
  310. switch (index) {
  311. case 2:
  312. list_->getDataSources().push_back(ds_info_[2]);
  313. // The ds_[2] is empty. We just check that it doesn't confuse
  314. // us. Fall through to the case 0.
  315. case 0:
  316. list_->getDataSources().push_back(ds_info_[0]);
  317. list_->getDataSources().push_back(ds_info_[1]);
  318. break;
  319. case 1:
  320. // The other order
  321. list_->getDataSources().push_back(ds_info_[1]);
  322. list_->getDataSources().push_back(ds_info_[0]);
  323. break;
  324. case 3:
  325. list_->getDataSources().push_back(ds_info_[1]);
  326. list_->getDataSources().push_back(ds_info_[0]);
  327. // It is the same as ds_[1], but we take from the first one.
  328. // The first one to match is the correct one.
  329. list_->getDataSources().push_back(ds_info_[3]);
  330. break;
  331. default:
  332. FAIL() << "Unknown configuration index " << index;
  333. }
  334. }
  335. void checkDS(size_t index, const string& type, const string& params,
  336. bool cache) const
  337. {
  338. ASSERT_GT(list_->getDataSources().size(), index);
  339. MockDataSourceClient* ds(dynamic_cast<MockDataSourceClient*>(
  340. list_->getDataSources()[index].data_src_client_));
  341. // Comparing with NULL does not work
  342. ASSERT_NE(ds, static_cast<const MockDataSourceClient*>(NULL));
  343. EXPECT_EQ(type, ds->type_);
  344. EXPECT_TRUE(Element::fromJSON(params)->equals(*ds->configuration_));
  345. EXPECT_EQ(cache, list_->getDataSources()[index].cache_ !=
  346. shared_ptr<InMemoryClient>());
  347. }
  348. const RRClass rrclass_;
  349. shared_ptr<TestedList> list_;
  350. const ClientList::FindResult negative_result_;
  351. vector<shared_ptr<MockDataSourceClient> > ds_;
  352. vector<ConfigurableClientList::DataSourceInfo> ds_info_;
  353. const ConstElementPtr config_elem_, config_elem_zones_, config_;
  354. shared_ptr<ZoneTableSegment> segment_;
  355. };
  356. // Test the test itself
  357. TEST_F(ListTest, selfTest) {
  358. EXPECT_EQ(result::SUCCESS, ds_[0]->findZone(Name("example.org")).code);
  359. EXPECT_EQ(result::PARTIALMATCH,
  360. ds_[0]->findZone(Name("sub.example.org")).code);
  361. EXPECT_EQ(result::NOTFOUND, ds_[0]->findZone(Name("org")).code);
  362. EXPECT_EQ(result::NOTFOUND, ds_[1]->findZone(Name("example.org")).code);
  363. EXPECT_EQ(result::NOTFOUND, ds_[0]->findZone(Name("aaa")).code);
  364. EXPECT_EQ(result::NOTFOUND, ds_[0]->findZone(Name("zzz")).code);
  365. // Nothing to keep alive here.
  366. EXPECT_EQ(shared_ptr<ClientList::FindResult::LifeKeeper>(),
  367. negative_result_.life_keeper_);
  368. }
  369. // Test the list we create with empty configuration is, in fact, empty
  370. TEST_F(ListTest, emptyList) {
  371. EXPECT_TRUE(list_->getDataSources().empty());
  372. }
  373. // Check the values returned by a find on an empty list. It should be
  374. // a negative answer (nothing found) no matter if we want an exact or inexact
  375. // match.
  376. TEST_F(ListTest, emptySearch) {
  377. // No matter what we try, we don't get an answer.
  378. // Note: we don't have operator<< for the result class, so we cannot use
  379. // EXPECT_EQ. Same for other similar cases.
  380. EXPECT_TRUE(negative_result_ == list_->find(Name("example.org"), false,
  381. false));
  382. EXPECT_TRUE(negative_result_ == list_->find(Name("example.org"), false,
  383. true));
  384. EXPECT_TRUE(negative_result_ == list_->find(Name("example.org"), true,
  385. false));
  386. EXPECT_TRUE(negative_result_ == list_->find(Name("example.org"), true,
  387. true));
  388. }
  389. // Put a single data source inside the list and check it can find an
  390. // exact match if there's one.
  391. TEST_F(ListTest, singleDSExactMatch) {
  392. list_->getDataSources().push_back(ds_info_[0]);
  393. // This zone is not there
  394. EXPECT_TRUE(negative_result_ == list_->find(Name("org."), true));
  395. // But this one is, so check it.
  396. positiveResult(list_->find(Name("example.org"), true), ds_[0],
  397. Name("example.org"), true, "Exact match");
  398. // When asking for a sub zone of a zone there, we get nothing
  399. // (we want exact match, this would be partial one)
  400. EXPECT_TRUE(negative_result_ == list_->find(Name("sub.example.org."),
  401. true));
  402. }
  403. // When asking for a partial match, we get all that the exact one, but more.
  404. TEST_F(ListTest, singleDSBestMatch) {
  405. list_->getDataSources().push_back(ds_info_[0]);
  406. // This zone is not there
  407. EXPECT_TRUE(negative_result_ == list_->find(Name("org.")));
  408. // But this one is, so check it.
  409. positiveResult(list_->find(Name("example.org")), ds_[0],
  410. Name("example.org"), true, "Exact match");
  411. // When asking for a sub zone of a zone there, we get the parent
  412. // one.
  413. positiveResult(list_->find(Name("sub.example.org.")), ds_[0],
  414. Name("example.org"), false, "Subdomain match");
  415. }
  416. const char* const test_names[] = {
  417. "Sub second",
  418. "Sub first",
  419. "With empty",
  420. "With a duplicity"
  421. };
  422. TEST_F(ListTest, multiExactMatch) {
  423. // Run through all the multi-configurations
  424. for (size_t i(0); i < sizeof(test_names) / sizeof(*test_names); ++i) {
  425. SCOPED_TRACE(test_names[i]);
  426. multiConfiguration(i);
  427. // Something that is nowhere there
  428. EXPECT_TRUE(negative_result_ == list_->find(Name("org."), true));
  429. // This one is there exactly.
  430. positiveResult(list_->find(Name("example.org"), true), ds_[0],
  431. Name("example.org"), true, "Exact match");
  432. // This one too, but in a different data source.
  433. positiveResult(list_->find(Name("sub.example.org."), true), ds_[1],
  434. Name("sub.example.org"), true, "Subdomain match");
  435. // But this one is in neither data source.
  436. EXPECT_TRUE(negative_result_ ==
  437. list_->find(Name("sub.example.com."), true));
  438. }
  439. }
  440. TEST_F(ListTest, multiBestMatch) {
  441. // Run through all the multi-configurations
  442. for (size_t i(0); i < 4; ++ i) {
  443. SCOPED_TRACE(test_names[i]);
  444. multiConfiguration(i);
  445. // Something that is nowhere there
  446. EXPECT_TRUE(negative_result_ == list_->find(Name("org.")));
  447. // This one is there exactly.
  448. positiveResult(list_->find(Name("example.org")), ds_[0],
  449. Name("example.org"), true, "Exact match");
  450. // This one too, but in a different data source.
  451. positiveResult(list_->find(Name("sub.example.org.")), ds_[1],
  452. Name("sub.example.org"), true, "Subdomain match");
  453. // But this one is in neither data source. But it is a subdomain
  454. // of one of the zones in the first data source.
  455. positiveResult(list_->find(Name("sub.example.com.")), ds_[0],
  456. Name("example.com."), false, "Subdomain in com");
  457. }
  458. }
  459. // Check the configuration is empty when the list is empty
  460. TEST_F(ListTest, configureEmpty) {
  461. const ConstElementPtr elem(new ListElement);
  462. list_->configure(elem, true);
  463. EXPECT_TRUE(list_->getDataSources().empty());
  464. // Check the exact configuration is preserved
  465. EXPECT_EQ(elem, list_->getConfiguration());
  466. }
  467. // Check we can get multiple data sources and they are in the right order.
  468. TEST_F(ListTest, configureMulti) {
  469. const ConstElementPtr elem(Element::fromJSON("["
  470. "{"
  471. " \"type\": \"type1\","
  472. " \"cache\": \"off\","
  473. " \"params\": {}"
  474. "},"
  475. "{"
  476. " \"type\": \"type2\","
  477. " \"cache\": \"off\","
  478. " \"params\": {}"
  479. "}]"
  480. ));
  481. list_->configure(elem, true);
  482. EXPECT_EQ(2, list_->getDataSources().size());
  483. checkDS(0, "type1", "{}", false);
  484. checkDS(1, "type2", "{}", false);
  485. // Check the exact configuration is preserved
  486. EXPECT_EQ(elem, list_->getConfiguration());
  487. }
  488. // Check we can pass whatever we want to the params
  489. TEST_F(ListTest, configureParams) {
  490. const char* params[] = {
  491. "true",
  492. "false",
  493. "null",
  494. "\"hello\"",
  495. "42",
  496. "[]",
  497. "{}",
  498. NULL
  499. };
  500. for (const char** param(params); *param; ++param) {
  501. SCOPED_TRACE(*param);
  502. ConstElementPtr elem(Element::fromJSON(string("["
  503. "{"
  504. " \"type\": \"t\","
  505. " \"cache\": \"off\","
  506. " \"params\": ") + *param +
  507. "}]"));
  508. list_->configure(elem, true);
  509. EXPECT_EQ(1, list_->getDataSources().size());
  510. checkDS(0, "t", *param, false);
  511. }
  512. }
  513. TEST_F(ListTest, wrongConfig) {
  514. const char* configs[] = {
  515. // A lot of stuff missing from there
  516. "[{\"type\": \"test_type\", \"params\": 13}, {}]",
  517. // Some bad types completely
  518. "{}",
  519. "true",
  520. "42",
  521. "null",
  522. "[{\"type\": \"test_type\", \"params\": 13}, true]",
  523. "[{\"type\": \"test_type\", \"params\": 13}, []]",
  524. "[{\"type\": \"test_type\", \"params\": 13}, 42]",
  525. // Bad type of type
  526. "[{\"type\": \"test_type\", \"params\": 13}, {\"type\": 42}]",
  527. "[{\"type\": \"test_type\", \"params\": 13}, {\"type\": true}]",
  528. "[{\"type\": \"test_type\", \"params\": 13}, {\"type\": null}]",
  529. "[{\"type\": \"test_type\", \"params\": 13}, {\"type\": []}]",
  530. "[{\"type\": \"test_type\", \"params\": 13}, {\"type\": {}}]",
  531. // Bad type of cache-enable
  532. "[{\"type\": \"test_type\", \"params\": 13}, "
  533. "{\"type\": \"x\", \"cache-enable\": 13, \"cache-zones\": []}]",
  534. "[{\"type\": \"test_type\", \"params\": 13}, "
  535. "{\"type\": \"x\", \"cache-enable\": \"xx\", \"cache-zones\": []}]",
  536. "[{\"type\": \"test_type\", \"params\": 13}, "
  537. "{\"type\": \"x\", \"cache-enable\": [], \"cache-zones\": []}]",
  538. "[{\"type\": \"test_type\", \"params\": 13}, "
  539. "{\"type\": \"x\", \"cache-enable\": {}, \"cache-zones\": []}]",
  540. "[{\"type\": \"test_type\", \"params\": 13}, "
  541. "{\"type\": \"x\", \"cache-enable\": null, \"cache-zones\": []}]",
  542. // Bad type of cache-zones
  543. "[{\"type\": \"test_type\", \"params\": 13}, "
  544. "{\"type\": \"x\", \"cache-enable\": true, \"cache-zones\": \"x\"}]",
  545. "[{\"type\": \"test_type\", \"params\": 13}, "
  546. "{\"type\": \"x\", \"cache-enable\": true, \"cache-zones\": true}]",
  547. "[{\"type\": \"test_type\", \"params\": 13}, "
  548. "{\"type\": \"x\", \"cache-enable\": true, \"cache-zones\": null}]",
  549. "[{\"type\": \"test_type\", \"params\": 13}, "
  550. "{\"type\": \"x\", \"cache-enable\": true, \"cache-zones\": 13}]",
  551. "[{\"type\": \"test_type\", \"params\": 13}, "
  552. "{\"type\": \"x\", \"cache-enable\": true, \"cache-zones\": {}}]",
  553. // Some bad inputs for MasterFiles special case
  554. // It must have the cache enabled
  555. "[{\"type\": \"test_type\", \"params\": 13}, "
  556. "{\"type\": \"MasterFiles\", \"cache-enable\": false,"
  557. "\"params\": {}}]",
  558. // No cache-zones allowed here
  559. "[{\"type\": \"test_type\", \"params\": 13}, "
  560. "{\"type\": \"MasterFiles\", \"cache-enable\": true,"
  561. "\"param\": {}, \"cache-zones\": []}]",
  562. // Some bad types of params
  563. "[{\"type\": \"test_type\", \"params\": 13}, "
  564. "{\"type\": \"MasterFiles\", \"cache-enable\": false,"
  565. "\"params\": []}]",
  566. "[{\"type\": \"test_type\", \"params\": 13}, "
  567. "{\"type\": \"MasterFiles\", \"cache-enable\": false,"
  568. "\"params\": 13}]",
  569. "[{\"type\": \"test_type\", \"params\": 13}, "
  570. "{\"type\": \"MasterFiles\", \"cache-enable\": false,"
  571. "\"params\": true}]",
  572. "[{\"type\": \"test_type\", \"params\": 13}, "
  573. "{\"type\": \"MasterFiles\", \"cache-enable\": false,"
  574. "\"params\": null}]",
  575. "[{\"type\": \"test_type\", \"params\": 13}, "
  576. "{\"type\": \"MasterFiles\", \"cache-enable\": false,"
  577. "\"params\": \"x\"}]",
  578. "[{\"type\": \"test_type\", \"params\": 13}, "
  579. "{\"type\": \"MasterFiles\", \"cache-enable\": false,"
  580. "\"params\": {\".\": 13}}]",
  581. "[{\"type\": \"test_type\", \"params\": 13}, "
  582. "{\"type\": \"MasterFiles\", \"cache-enable\": false,"
  583. "\"params\": {\".\": true}}]",
  584. "[{\"type\": \"test_type\", \"params\": 13}, "
  585. "{\"type\": \"MasterFiles\", \"cache-enable\": false,"
  586. "\"params\": {\".\": null}}]",
  587. "[{\"type\": \"test_type\", \"params\": 13}, "
  588. "{\"type\": \"MasterFiles\", \"cache-enable\": false,"
  589. "\"params\": {\".\": []}}]",
  590. "[{\"type\": \"test_type\", \"params\": 13}, "
  591. "{\"type\": \"MasterFiles\", \"cache-enable\": false,"
  592. "\"params\": {\".\": {}}}]",
  593. NULL
  594. };
  595. // Put something inside to see it survives the exception
  596. list_->configure(config_elem_, true);
  597. checkDS(0, "test_type", "{}", false);
  598. for (const char** config(configs); *config; ++config) {
  599. SCOPED_TRACE(*config);
  600. ConstElementPtr elem(Element::fromJSON(*config));
  601. EXPECT_THROW(list_->configure(elem, true),
  602. ConfigurableClientList::ConfigurationError);
  603. // Still untouched
  604. checkDS(0, "test_type", "{}", false);
  605. EXPECT_EQ(1, list_->getDataSources().size());
  606. }
  607. }
  608. // The param thing defaults to null. Cache is not used yet.
  609. TEST_F(ListTest, defaults) {
  610. const ConstElementPtr elem(Element::fromJSON("["
  611. "{"
  612. " \"type\": \"type1\""
  613. "}]"));
  614. list_->configure(elem, true);
  615. EXPECT_EQ(1, list_->getDataSources().size());
  616. checkDS(0, "type1", "null", false);
  617. }
  618. // Check we can call the configure multiple times, to change the configuration
  619. TEST_F(ListTest, reconfigure) {
  620. const ConstElementPtr empty(new ListElement);
  621. list_->configure(config_elem_, true);
  622. checkDS(0, "test_type", "{}", false);
  623. list_->configure(empty, true);
  624. EXPECT_TRUE(list_->getDataSources().empty());
  625. list_->configure(config_elem_, true);
  626. checkDS(0, "test_type", "{}", false);
  627. }
  628. // Make sure the data source error exception from the factory is propagated
  629. TEST_F(ListTest, dataSrcError) {
  630. const ConstElementPtr elem(Element::fromJSON("["
  631. "{"
  632. " \"type\": \"error\""
  633. "}]"));
  634. list_->configure(config_elem_, true);
  635. checkDS(0, "test_type", "{}", false);
  636. EXPECT_THROW(list_->configure(elem, true), DataSourceError);
  637. checkDS(0, "test_type", "{}", false);
  638. }
  639. // Check we can get the cache
  640. TEST_F(ListTest, configureCacheEmpty) {
  641. const ConstElementPtr elem(Element::fromJSON("["
  642. "{"
  643. " \"type\": \"type1\","
  644. " \"cache-enable\": true,"
  645. " \"cache-zones\": [],"
  646. " \"params\": {}"
  647. "},"
  648. "{"
  649. " \"type\": \"type2\","
  650. " \"cache-enable\": false,"
  651. " \"cache-zones\": [],"
  652. " \"params\": {}"
  653. "}]"
  654. ));
  655. list_->configure(elem, true);
  656. EXPECT_EQ(2, list_->getDataSources().size());
  657. checkDS(0, "type1", "{}", true);
  658. checkDS(1, "type2", "{}", false);
  659. }
  660. // But no cache if we disallow it globally
  661. TEST_F(ListTest, configureCacheDisabled) {
  662. const ConstElementPtr elem(Element::fromJSON("["
  663. "{"
  664. " \"type\": \"type1\","
  665. " \"cache-enable\": true,"
  666. " \"cache-zones\": [],"
  667. " \"params\": {}"
  668. "},"
  669. "{"
  670. " \"type\": \"type2\","
  671. " \"cache-enable\": false,"
  672. " \"cache-zones\": [],"
  673. " \"params\": {}"
  674. "}]"
  675. ));
  676. list_->configure(elem, false);
  677. EXPECT_EQ(2, list_->getDataSources().size());
  678. checkDS(0, "type1", "{}", false);
  679. checkDS(1, "type2", "{}", false);
  680. }
  681. // Put some zones into the cache
  682. TEST_F(ListTest, cacheZones) {
  683. const ConstElementPtr elem(Element::fromJSON("["
  684. "{"
  685. " \"type\": \"type1\","
  686. " \"cache-enable\": true,"
  687. " \"cache-zones\": [\"example.org\", \"example.com\"],"
  688. " \"params\": [\"example.org\", \"example.com\", \"exmaple.cz\"]"
  689. "}]"));
  690. list_->configure(elem, true);
  691. checkDS(0, "type1", "[\"example.org\", \"example.com\", \"exmaple.cz\"]",
  692. true);
  693. const shared_ptr<InMemoryClient> cache(list_->getDataSources()[0].cache_);
  694. EXPECT_EQ(2, cache->getZoneCount());
  695. EXPECT_EQ(result::SUCCESS, cache->findZone(Name("example.org")).code);
  696. EXPECT_EQ(result::SUCCESS, cache->findZone(Name("example.com")).code);
  697. EXPECT_EQ(result::NOTFOUND, cache->findZone(Name("example.cz")).code);
  698. EXPECT_EQ(RRClass::IN(),
  699. cache->findZone(Name("example.org")).zone_finder->getClass());
  700. // These are cached and answered from the cache
  701. positiveResult(list_->find(Name("example.com.")), ds_[0],
  702. Name("example.com."), true, "com", true);
  703. positiveResult(list_->find(Name("example.org.")), ds_[0],
  704. Name("example.org."), true, "org", true);
  705. positiveResult(list_->find(Name("sub.example.com.")), ds_[0],
  706. Name("example.com."), false, "Subdomain of com", true);
  707. // For now, the ones not cached are ignored.
  708. EXPECT_TRUE(negative_result_ == list_->find(Name("example.cz.")));
  709. }
  710. // Check the caching handles misbehaviour from the data source and
  711. // misconfiguration gracefully
  712. TEST_F(ListTest, badCache) {
  713. list_->configure(config_elem_, true);
  714. checkDS(0, "test_type", "{}", false);
  715. // First, the zone is not in the data source
  716. const ConstElementPtr elem1(Element::fromJSON("["
  717. "{"
  718. " \"type\": \"type1\","
  719. " \"cache-enable\": true,"
  720. " \"cache-zones\": [\"example.org\"],"
  721. " \"params\": []"
  722. "}]"));
  723. EXPECT_THROW(list_->configure(elem1, true),
  724. ConfigurableClientList::ConfigurationError);
  725. checkDS(0, "test_type", "{}", false);
  726. // Now, the zone doesn't give an iterator
  727. const ConstElementPtr elem2(Element::fromJSON("["
  728. "{"
  729. " \"type\": \"type1\","
  730. " \"cache-enable\": true,"
  731. " \"cache-zones\": [\"noiter.org\"],"
  732. " \"params\": [\"noiter.org\"]"
  733. "}]"));
  734. EXPECT_THROW(list_->configure(elem2, true), isc::NotImplemented);
  735. checkDS(0, "test_type", "{}", false);
  736. // Now, the zone returns NULL iterator
  737. const ConstElementPtr elem3(Element::fromJSON("["
  738. "{"
  739. " \"type\": \"type1\","
  740. " \"cache-enable\": true,"
  741. " \"cache-zones\": [\"null.org\"],"
  742. " \"params\": [\"null.org\"]"
  743. "}]"));
  744. EXPECT_THROW(list_->configure(elem3, true), isc::Unexpected);
  745. checkDS(0, "test_type", "{}", false);
  746. // The autodetection of zones is not enabled
  747. const ConstElementPtr elem4(Element::fromJSON("["
  748. "{"
  749. " \"type\": \"type1\","
  750. " \"cache-enable\": true,"
  751. " \"params\": [\"example.org\"]"
  752. "}]"));
  753. EXPECT_THROW(list_->configure(elem4, true), isc::NotImplemented);
  754. checkDS(0, "test_type", "{}", false);
  755. }
  756. TEST_F(ListTest, masterFiles) {
  757. const ConstElementPtr elem(Element::fromJSON("["
  758. "{"
  759. " \"type\": \"MasterFiles\","
  760. " \"cache-enable\": true,"
  761. " \"params\": {"
  762. " \".\": \"" TEST_DATA_DIR "/root.zone\""
  763. " }"
  764. "}]"));
  765. list_->configure(elem, true);
  766. // It has only the cache
  767. EXPECT_EQ(static_cast<const DataSourceClient*>(NULL),
  768. list_->getDataSources()[0].data_src_client_);
  769. // And it can search
  770. positiveResult(list_->find(Name(".")), ds_[0], Name("."), true, "com",
  771. true);
  772. // If cache is not enabled, nothing is loaded
  773. list_->configure(elem, false);
  774. EXPECT_EQ(0, list_->getDataSources().size());
  775. }
  776. TEST_F(ListTest, BadMasterFile) {
  777. // Configure two zone correctly, and one with the wrong origin
  778. // (resulting in an out-of-zone data error)
  779. // Configuration should succeed, and the correct zones should
  780. // be loaded. Neither the 'bad' origin or the zone it used
  781. // should be loaded
  782. const ConstElementPtr elem(Element::fromJSON("["
  783. "{"
  784. " \"type\": \"MasterFiles\","
  785. " \"cache-enable\": true,"
  786. " \"params\": {"
  787. " \"example.com.\": \"" TEST_DATA_DIR "/example.com.flattened\","
  788. " \"foo.bar.\": \"" TEST_DATA_DIR "/example.org.nsec3-signed\","
  789. " \".\": \"" TEST_DATA_DIR "/root.zone\""
  790. " }"
  791. "}]"));
  792. list_->configure(elem, true);
  793. positiveResult(list_->find(Name("example.com."), true), ds_[0],
  794. Name("example.com."), true, "example.com", true);
  795. EXPECT_TRUE(negative_result_ == list_->find(Name("example.org."), true));
  796. EXPECT_TRUE(negative_result_ == list_->find(Name("foo.bar"), true));
  797. positiveResult(list_->find(Name(".")), ds_[0], Name("."), true, "root",
  798. true);
  799. }
  800. // Test we can reload a zone
  801. TEST_F(ListTest, reloadSuccess) {
  802. list_->configure(config_elem_zones_, true);
  803. const Name name("example.org");
  804. prepareCache(0, name);
  805. // The cache currently contains a tweaked version of zone, which doesn't
  806. // have apex NS. So the lookup should result in NXRRSET.
  807. EXPECT_EQ(ZoneFinder::NXRRSET,
  808. list_->find(name).finder_->find(name, RRType::NS())->code);
  809. // Now reload the full zone. It should be there now.
  810. EXPECT_EQ(ConfigurableClientList::ZONE_RELOADED, list_->reload(name));
  811. EXPECT_EQ(ZoneFinder::SUCCESS,
  812. list_->find(name).finder_->find(name, RRType::NS())->code);
  813. }
  814. // The cache is not enabled. The load should be rejected.
  815. TEST_F(ListTest, reloadNotEnabled) {
  816. list_->configure(config_elem_zones_, false);
  817. const Name name("example.org");
  818. // We put the cache in even when not enabled. This won't confuse the thing.
  819. prepareCache(0, name);
  820. // See the reloadSuccess test. This should result in NXRRSET.
  821. EXPECT_EQ(ZoneFinder::NXRRSET,
  822. list_->find(name).finder_->find(name, RRType::NS())->code);
  823. // Now reload. It should reject it.
  824. EXPECT_EQ(ConfigurableClientList::CACHE_DISABLED, list_->reload(name));
  825. // Nothing changed here
  826. EXPECT_EQ(ZoneFinder::NXRRSET,
  827. list_->find(name).finder_->find(name, RRType::NS())->code);
  828. }
  829. // Test several cases when the zone does not exist
  830. TEST_F(ListTest, reloadNoSuchZone) {
  831. list_->configure(config_elem_zones_, true);
  832. const Name name("example.org");
  833. // We put the cache in even when not enabled. This won't confuse the
  834. // reload method, as that one looks at the real state of things, not
  835. // at the configuration.
  836. prepareCache(0, Name("example.com"));
  837. // Not in the data sources
  838. EXPECT_EQ(ConfigurableClientList::ZONE_NOT_FOUND,
  839. list_->reload(Name("example.cz")));
  840. // Not cached
  841. EXPECT_EQ(ConfigurableClientList::ZONE_NOT_FOUND, list_->reload(name));
  842. // Partial match
  843. EXPECT_EQ(ConfigurableClientList::ZONE_NOT_FOUND,
  844. list_->reload(Name("sub.example.com")));
  845. // Nothing changed here - these zones don't exist
  846. EXPECT_EQ(static_cast<isc::datasrc::DataSourceClient*>(NULL),
  847. list_->find(name).dsrc_client_);
  848. EXPECT_EQ(static_cast<isc::datasrc::DataSourceClient*>(NULL),
  849. list_->find(Name("example.cz")).dsrc_client_);
  850. EXPECT_EQ(static_cast<isc::datasrc::DataSourceClient*>(NULL),
  851. list_->find(Name("sub.example.com"), true).dsrc_client_);
  852. // Not reloaded, so NS shouldn't be visible yet.
  853. EXPECT_EQ(ZoneFinder::NXRRSET,
  854. list_->find(Name("example.com")).finder_->
  855. find(Name("example.com"), RRType::NS())->code);
  856. }
  857. // Check we gracefuly throw an exception when a zone disappeared in
  858. // the underlying data source when we want to reload it
  859. TEST_F(ListTest, reloadZoneGone) {
  860. list_->configure(config_elem_, true);
  861. const Name name("example.org");
  862. // We put in a cache for non-existant zone. This emulates being loaded
  863. // and then the zone disappearing. We prefill the cache, so we can check
  864. // it.
  865. prepareCache(0, name);
  866. // The (cached) zone contains zone's SOA
  867. EXPECT_EQ(ZoneFinder::SUCCESS,
  868. list_->find(name).finder_->find(name, RRType::SOA())->code);
  869. // The zone is not there, so abort the reload.
  870. EXPECT_THROW(list_->reload(name), DataSourceError);
  871. // The (cached) zone is not hurt.
  872. EXPECT_EQ(ZoneFinder::SUCCESS,
  873. list_->find(name).finder_->find(name, RRType::SOA())->code);
  874. }
  875. // The underlying data source throws. Check we don't modify the state.
  876. TEST_F(ListTest, reloadZoneThrow) {
  877. list_->configure(config_elem_zones_, true);
  878. const Name name("noiter.org");
  879. prepareCache(0, name);
  880. // The zone contains stuff now
  881. EXPECT_EQ(ZoneFinder::SUCCESS,
  882. list_->find(name).finder_->find(name, RRType::SOA())->code);
  883. // The iterator throws, so abort the reload.
  884. EXPECT_THROW(list_->reload(name), isc::NotImplemented);
  885. // The zone is not hurt.
  886. EXPECT_EQ(ZoneFinder::SUCCESS,
  887. list_->find(name).finder_->find(name, RRType::SOA())->code);
  888. }
  889. TEST_F(ListTest, reloadNullIterator) {
  890. list_->configure(config_elem_zones_, true);
  891. const Name name("null.org");
  892. prepareCache(0, name);
  893. // The zone contains stuff now
  894. EXPECT_EQ(ZoneFinder::SUCCESS,
  895. list_->find(name).finder_->find(name, RRType::SOA())->code);
  896. // The iterator throws, so abort the reload.
  897. EXPECT_THROW(list_->reload(name), isc::Unexpected);
  898. // The zone is not hurt.
  899. EXPECT_EQ(ZoneFinder::SUCCESS,
  900. list_->find(name).finder_->find(name, RRType::SOA())->code);
  901. }
  902. // Test we can reload the master files too (special-cased)
  903. TEST_F(ListTest, reloadMasterFile) {
  904. const char* const install_cmd = INSTALL_PROG " -c " TEST_DATA_DIR
  905. "/root.zone " TEST_DATA_BUILDDIR "/root.zone.copied";
  906. if (system(install_cmd) != 0) {
  907. // any exception will do, this is failure in test setup, but
  908. // nice to show the command that fails, and shouldn't be caught
  909. isc_throw(isc::Exception,
  910. "Error setting up; command failed: " << install_cmd);
  911. }
  912. const ConstElementPtr elem(Element::fromJSON("["
  913. "{"
  914. " \"type\": \"MasterFiles\","
  915. " \"cache-enable\": true,"
  916. " \"params\": {"
  917. " \".\": \"" TEST_DATA_BUILDDIR "/root.zone.copied\""
  918. " }"
  919. "}]"));
  920. list_->configure(elem, true);
  921. // Add a record that is not in the zone
  922. EXPECT_EQ(ZoneFinder::NXDOMAIN,
  923. list_->find(Name(".")).finder_->find(Name("nosuchdomain"),
  924. RRType::TXT())->code);
  925. ofstream f;
  926. f.open(TEST_DATA_BUILDDIR "/root.zone.copied", ios::out | ios::app);
  927. f << "nosuchdomain.\t\t3600\tIN\tTXT\ttest" << std::endl;
  928. f.close();
  929. // Do the reload.
  930. EXPECT_EQ(ConfigurableClientList::ZONE_RELOADED, list_->reload(Name(".")));
  931. // It is here now.
  932. EXPECT_EQ(ZoneFinder::SUCCESS,
  933. list_->find(Name(".")).finder_->find(Name("nosuchdomain"),
  934. RRType::TXT())->code);
  935. }
  936. }