// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC") // // Permission to use, copy, modify, and/or distribute this software for any // purpose with or without fee is hereby granted, provided that the above // copyright notice and this permission notice appear in all copies. // // THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH // REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY // AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, // INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM // LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace isc::datasrc; using isc::datasrc::unittest::MockDataSourceClient; using isc::datasrc::memory::InMemoryClient; using isc::datasrc::memory::ZoneTableSegment; using isc::datasrc::memory::InMemoryZoneFinder; using namespace isc::data; using namespace isc::dns; // don't import the entire boost namespace. It will unexpectedly hide uintXX_t // for some systems. using boost::shared_ptr; using namespace std; namespace { // The test version is the same as the normal version. We, however, add // some methods to dig directly in the internals, for the tests. class TestedList : public ConfigurableClientList { public: TestedList(const RRClass& rrclass) : ConfigurableClientList(rrclass) {} DataSources& getDataSources() { return (data_sources_); } // Overwrite the list's method to get a data source with given type // and configuration. We mock the data source and don't create the // container. This is just to avoid some complexity in the tests. virtual DataSourcePair getDataSourceClient(const string& type, const ConstElementPtr& configuration) { if (type == "error") { isc_throw(DataSourceError, "The error data source type"); } if (type == "MasterFiles") { return (DataSourcePair(0, DataSourceClientContainerPtr())); } shared_ptr ds(new MockDataSourceClient(type, configuration)); // Make sure it is deleted when the test list is deleted. to_delete_.push_back(ds); return (DataSourcePair(ds.get(), DataSourceClientContainerPtr())); } private: // Hold list of data sources created internally, so they are preserved // until the end of the test and then deleted. vector > to_delete_; }; const char* ds_zones[][3] = { { "example.org.", "example.com.", NULL }, { "sub.example.org.", NULL, NULL }, { NULL, NULL, NULL }, { "sub.example.org.", NULL, NULL } }; const size_t ds_count = (sizeof(ds_zones) / sizeof(*ds_zones)); class ListTest : public ::testing::Test { public: ListTest() : rrclass_(RRClass::IN()), // The empty list corresponds to a list with no elements inside list_(new TestedList(rrclass_)), config_elem_(Element::fromJSON("[" "{" " \"type\": \"test_type\"," " \"params\": {}" "}]")), config_elem_zones_(Element::fromJSON("[" "{" " \"type\": \"test_type\"," " \"params\": [\"example.org\", \"example.com\", " " \"noiter.org\", \"null.org\"]" "}]")), ztable_segment_(ZoneTableSegment::create(rrclass_, "local")) { for (size_t i(0); i < ds_count; ++ i) { shared_ptr ds(new MockDataSourceClient(ds_zones[i])); ds_.push_back(ds); ds_info_.push_back(ConfigurableClientList::DataSourceInfo( ds.get(), DataSourceClientContainerPtr(), boost::shared_ptr(), rrclass_, "")); } } // Install a "fake" cached zone using a temporary underlying data source // client. If 'enabled' is set to false, emulate a disabled cache, in // which case there will be no data in memory. void prepareCache(size_t index, const Name& zone, bool enabled = true) { ConfigurableClientList::DataSourceInfo& dsrc_info = list_->getDataSources()[index]; MockDataSourceClient* mock_client = static_cast(dsrc_info.data_src_client_); // Disable some default features of the mock to distinguish the // temporary case from normal case. mock_client->disableA(); mock_client->disableBadIterator(); // Build new cache config to load the specified zone, and replace // the data source info with the new config. ConstElementPtr cache_conf_elem = Element::fromJSON("{\"type\": \"mock\"," " \"cache-enable\": " + string(enabled ? "true," : "false,") + " \"cache-zones\": " " [\"" + zone.toText() + "\"]}"); boost::shared_ptr cache_conf( new internal::CacheConfig("mock", mock_client, *cache_conf_elem, true)); dsrc_info = ConfigurableClientList::DataSourceInfo( dsrc_info.data_src_client_, dsrc_info.container_, cache_conf, rrclass_, dsrc_info.name_); // Load the data into the zone table. if (enabled) { boost::scoped_ptr writer( new memory::ZoneWriter( *dsrc_info.ztable_segment_, cache_conf->getLoadAction(rrclass_, zone), zone, rrclass_)); writer->load(); writer->install(); writer->cleanup(); // not absolutely necessary, but just in case } // On completion of load revert to the previous state of underlying // data source. mock_client->enableA(); mock_client->enableBadIterator(); } // Check the positive result is as we expect it. void positiveResult(const ClientList::FindResult& result, const shared_ptr& dsrc, const Name& name, bool exact, const char* test, bool from_cache = false) { SCOPED_TRACE(test); ASSERT_NE(ZoneFinderPtr(), result.finder_); EXPECT_EQ(name, result.finder_->getOrigin()); EXPECT_EQ(exact, result.exact_match_); // If it is a positive result, there's something to keep // alive, even when we don't know what it is. // Any better idea how to test it actually keeps the thing // alive? EXPECT_NE(shared_ptr(), result.life_keeper_); if (from_cache) { EXPECT_NE(shared_ptr(), boost::dynamic_pointer_cast( result.finder_)) << "Finder is not from cache"; EXPECT_TRUE(NULL != dynamic_cast(result.dsrc_client_)); } else { EXPECT_EQ(dsrc.get(), result.dsrc_client_); } } // Configure the list with multiple data sources, according to // some configuration. It uses the index as parameter, to be able to // loop through the configurations. void multiConfiguration(size_t index) { list_->getDataSources().clear(); switch (index) { case 2: list_->getDataSources().push_back(ds_info_[2]); // The ds_[2] is empty. We just check that it doesn't confuse // us. Fall through to the case 0. case 0: list_->getDataSources().push_back(ds_info_[0]); list_->getDataSources().push_back(ds_info_[1]); break; case 1: // The other order list_->getDataSources().push_back(ds_info_[1]); list_->getDataSources().push_back(ds_info_[0]); break; case 3: list_->getDataSources().push_back(ds_info_[1]); list_->getDataSources().push_back(ds_info_[0]); // It is the same as ds_[1], but we take from the first one. // The first one to match is the correct one. list_->getDataSources().push_back(ds_info_[3]); break; default: FAIL() << "Unknown configuration index " << index; } } void checkDS(size_t index, const string& type, const string& params, bool cache) const { ASSERT_GT(list_->getDataSources().size(), index); MockDataSourceClient* ds(dynamic_cast( list_->getDataSources()[index].data_src_client_)); // Comparing with NULL does not work ASSERT_NE(ds, static_cast(NULL)); EXPECT_EQ(type, ds->type_); EXPECT_TRUE(Element::fromJSON(params)->equals(*ds->configuration_)); EXPECT_EQ(cache, list_->getDataSources()[index].cache_ != shared_ptr()); } ConfigurableClientList::CacheStatus doReload( const Name& origin, const string& datasrc_name = ""); const RRClass rrclass_; shared_ptr list_; const ClientList::FindResult negative_result_; vector > ds_; vector ds_info_; const ConstElementPtr config_elem_, config_elem_zones_; shared_ptr ztable_segment_; }; // Test the test itself TEST_F(ListTest, selfTest) { EXPECT_EQ(result::SUCCESS, ds_[0]->findZone(Name("example.org")).code); EXPECT_EQ(result::PARTIALMATCH, ds_[0]->findZone(Name("sub.example.org")).code); EXPECT_EQ(result::NOTFOUND, ds_[0]->findZone(Name("org")).code); EXPECT_EQ(result::NOTFOUND, ds_[1]->findZone(Name("example.org")).code); EXPECT_EQ(result::NOTFOUND, ds_[0]->findZone(Name("aaa")).code); EXPECT_EQ(result::NOTFOUND, ds_[0]->findZone(Name("zzz")).code); // Nothing to keep alive here. EXPECT_EQ(shared_ptr(), negative_result_.life_keeper_); } // Test the list we create with empty configuration is, in fact, empty TEST_F(ListTest, emptyList) { EXPECT_TRUE(list_->getDataSources().empty()); } // Check the values returned by a find on an empty list. It should be // a negative answer (nothing found) no matter if we want an exact or inexact // match. TEST_F(ListTest, emptySearch) { // No matter what we try, we don't get an answer. // Note: we don't have operator<< for the result class, so we cannot use // EXPECT_EQ. Same for other similar cases. EXPECT_TRUE(negative_result_ == list_->find(Name("example.org"), false, false)); EXPECT_TRUE(negative_result_ == list_->find(Name("example.org"), false, true)); EXPECT_TRUE(negative_result_ == list_->find(Name("example.org"), true, false)); EXPECT_TRUE(negative_result_ == list_->find(Name("example.org"), true, true)); } // Put a single data source inside the list and check it can find an // exact match if there's one. TEST_F(ListTest, singleDSExactMatch) { list_->getDataSources().push_back(ds_info_[0]); // This zone is not there EXPECT_TRUE(negative_result_ == list_->find(Name("org."), true)); // But this one is, so check it. positiveResult(list_->find(Name("example.org"), true), ds_[0], Name("example.org"), true, "Exact match"); // When asking for a sub zone of a zone there, we get nothing // (we want exact match, this would be partial one) EXPECT_TRUE(negative_result_ == list_->find(Name("sub.example.org."), true)); } // When asking for a partial match, we get all that the exact one, but more. TEST_F(ListTest, singleDSBestMatch) { list_->getDataSources().push_back(ds_info_[0]); // This zone is not there EXPECT_TRUE(negative_result_ == list_->find(Name("org."))); // But this one is, so check it. positiveResult(list_->find(Name("example.org")), ds_[0], Name("example.org"), true, "Exact match"); // When asking for a sub zone of a zone there, we get the parent // one. positiveResult(list_->find(Name("sub.example.org.")), ds_[0], Name("example.org"), false, "Subdomain match"); } const char* const test_names[] = { "Sub second", "Sub first", "With empty", "With a duplicity" }; TEST_F(ListTest, multiExactMatch) { // Run through all the multi-configurations for (size_t i(0); i < sizeof(test_names) / sizeof(*test_names); ++i) { SCOPED_TRACE(test_names[i]); multiConfiguration(i); // Something that is nowhere there EXPECT_TRUE(negative_result_ == list_->find(Name("org."), true)); // This one is there exactly. positiveResult(list_->find(Name("example.org"), true), ds_[0], Name("example.org"), true, "Exact match"); // This one too, but in a different data source. positiveResult(list_->find(Name("sub.example.org."), true), ds_[1], Name("sub.example.org"), true, "Subdomain match"); // But this one is in neither data source. EXPECT_TRUE(negative_result_ == list_->find(Name("sub.example.com."), true)); } } TEST_F(ListTest, multiBestMatch) { // Run through all the multi-configurations for (size_t i(0); i < 4; ++ i) { SCOPED_TRACE(test_names[i]); multiConfiguration(i); // Something that is nowhere there EXPECT_TRUE(negative_result_ == list_->find(Name("org."))); // This one is there exactly. positiveResult(list_->find(Name("example.org")), ds_[0], Name("example.org"), true, "Exact match"); // This one too, but in a different data source. positiveResult(list_->find(Name("sub.example.org.")), ds_[1], Name("sub.example.org"), true, "Subdomain match"); // But this one is in neither data source. But it is a subdomain // of one of the zones in the first data source. positiveResult(list_->find(Name("sub.example.com.")), ds_[0], Name("example.com."), false, "Subdomain in com"); } } // Check the configuration is empty when the list is empty TEST_F(ListTest, configureEmpty) { const ConstElementPtr elem(new ListElement); list_->configure(elem, true); EXPECT_TRUE(list_->getDataSources().empty()); // Check the exact configuration is preserved EXPECT_EQ(elem, list_->getConfiguration()); } // Check we can get multiple data sources and they are in the right order. TEST_F(ListTest, configureMulti) { const ConstElementPtr elem(Element::fromJSON("[" "{" " \"type\": \"type1\"," " \"cache-enable\": false," " \"params\": {}" "}," "{" " \"type\": \"type2\"," " \"cache-enable\": false," " \"params\": {}" "}]" )); list_->configure(elem, true); EXPECT_EQ(2, list_->getDataSources().size()); checkDS(0, "type1", "{}", false); checkDS(1, "type2", "{}", false); // Check the exact configuration is preserved EXPECT_EQ(elem, list_->getConfiguration()); } // Check we can pass whatever we want to the params TEST_F(ListTest, configureParams) { const char* params[] = { "true", "false", "null", "\"hello\"", "42", "[]", "{}", NULL }; for (const char** param(params); *param; ++param) { SCOPED_TRACE(*param); ConstElementPtr elem(Element::fromJSON(string("[" "{" " \"type\": \"t\"," " \"cache-enable\": false," " \"params\": ") + *param + "}]")); list_->configure(elem, true); EXPECT_EQ(1, list_->getDataSources().size()); checkDS(0, "t", *param, false); } } TEST_F(ListTest, status) { EXPECT_TRUE(list_->getStatus().empty()); const ConstElementPtr elem(Element::fromJSON("[" "{" " \"type\": \"type1\"," " \"cache-enable\": false," " \"params\": {}" "}," "{" " \"type\": \"type2\"," " \"cache-enable\": true," " \"cache-zones\": []," " \"name\": \"Test name\"," " \"params\": {}" "}]" )); list_->configure(elem, true); const vector statuses(list_->getStatus()); ASSERT_EQ(2, statuses.size()); EXPECT_EQ("type1", statuses[0].getName()); EXPECT_EQ(SEGMENT_UNUSED, statuses[0].getSegmentState()); EXPECT_THROW(statuses[0].getSegmentType(), isc::InvalidOperation); EXPECT_EQ("Test name", statuses[1].getName()); EXPECT_EQ(SEGMENT_INUSE, statuses[1].getSegmentState()); EXPECT_EQ("local", statuses[1].getSegmentType()); } TEST_F(ListTest, wrongConfig) { const char* configs[] = { // A lot of stuff missing from there "[{\"type\": \"test_type\", \"params\": 13}, {}]", // Some bad types completely "{}", "true", "42", "null", "[{\"type\": \"test_type\", \"params\": 13}, true]", "[{\"type\": \"test_type\", \"params\": 13}, []]", "[{\"type\": \"test_type\", \"params\": 13}, 42]", // Bad type of type "[{\"type\": \"test_type\", \"params\": 13}, {\"type\": 42}]", "[{\"type\": \"test_type\", \"params\": 13}, {\"type\": true}]", "[{\"type\": \"test_type\", \"params\": 13}, {\"type\": null}]", "[{\"type\": \"test_type\", \"params\": 13}, {\"type\": []}]", "[{\"type\": \"test_type\", \"params\": 13}, {\"type\": {}}]", // Bad type of cache-enable "[{\"type\": \"test_type\", \"params\": 13}, " "{\"type\": \"x\", \"cache-enable\": 13, \"cache-zones\": []}]", "[{\"type\": \"test_type\", \"params\": 13}, " "{\"type\": \"x\", \"cache-enable\": \"xx\", \"cache-zones\": []}]", "[{\"type\": \"test_type\", \"params\": 13}, " "{\"type\": \"x\", \"cache-enable\": [], \"cache-zones\": []}]", "[{\"type\": \"test_type\", \"params\": 13}, " "{\"type\": \"x\", \"cache-enable\": {}, \"cache-zones\": []}]", "[{\"type\": \"test_type\", \"params\": 13}, " "{\"type\": \"x\", \"cache-enable\": null, \"cache-zones\": []}]", // Bad type of cache-zones "[{\"type\": \"test_type\", \"params\": 13}, " "{\"type\": \"x\", \"cache-enable\": true, \"cache-zones\": \"x\"}]", "[{\"type\": \"test_type\", \"params\": 13}, " "{\"type\": \"x\", \"cache-enable\": true, \"cache-zones\": true}]", "[{\"type\": \"test_type\", \"params\": 13}, " "{\"type\": \"x\", \"cache-enable\": true, \"cache-zones\": null}]", "[{\"type\": \"test_type\", \"params\": 13}, " "{\"type\": \"x\", \"cache-enable\": true, \"cache-zones\": 13}]", "[{\"type\": \"test_type\", \"params\": 13}, " "{\"type\": \"x\", \"cache-enable\": true, \"cache-zones\": {}}]", // Some bad inputs for MasterFiles special case // It must have the cache enabled "[{\"type\": \"test_type\", \"params\": 13}, " "{\"type\": \"MasterFiles\", \"cache-enable\": false," "\"params\": {}}]", // No cache-zones allowed here "[{\"type\": \"test_type\", \"params\": 13}, " "{\"type\": \"MasterFiles\", \"cache-enable\": true," "\"param\": {}, \"cache-zones\": []}]", // Some bad types of params "[{\"type\": \"test_type\", \"params\": 13}, " "{\"type\": \"MasterFiles\", \"cache-enable\": false," "\"params\": []}]", "[{\"type\": \"test_type\", \"params\": 13}, " "{\"type\": \"MasterFiles\", \"cache-enable\": false," "\"params\": 13}]", "[{\"type\": \"test_type\", \"params\": 13}, " "{\"type\": \"MasterFiles\", \"cache-enable\": false," "\"params\": true}]", "[{\"type\": \"test_type\", \"params\": 13}, " "{\"type\": \"MasterFiles\", \"cache-enable\": false," "\"params\": null}]", "[{\"type\": \"test_type\", \"params\": 13}, " "{\"type\": \"MasterFiles\", \"cache-enable\": false," "\"params\": \"x\"}]", "[{\"type\": \"test_type\", \"params\": 13}, " "{\"type\": \"MasterFiles\", \"cache-enable\": false," "\"params\": {\".\": 13}}]", "[{\"type\": \"test_type\", \"params\": 13}, " "{\"type\": \"MasterFiles\", \"cache-enable\": false," "\"params\": {\".\": true}}]", "[{\"type\": \"test_type\", \"params\": 13}, " "{\"type\": \"MasterFiles\", \"cache-enable\": false," "\"params\": {\".\": null}}]", "[{\"type\": \"test_type\", \"params\": 13}, " "{\"type\": \"MasterFiles\", \"cache-enable\": false," "\"params\": {\".\": []}}]", "[{\"type\": \"test_type\", \"params\": 13}, " "{\"type\": \"MasterFiles\", \"cache-enable\": false," "\"params\": {\".\": {}}}]", NULL }; // Put something inside to see it survives the exception list_->configure(config_elem_, true); checkDS(0, "test_type", "{}", false); for (const char** config(configs); *config; ++config) { SCOPED_TRACE(*config); ConstElementPtr elem(Element::fromJSON(*config)); EXPECT_THROW(list_->configure(elem, true), ConfigurableClientList::ConfigurationError); // Still untouched checkDS(0, "test_type", "{}", false); EXPECT_EQ(1, list_->getDataSources().size()); } } // The param thing defaults to null. Cache is not used yet. TEST_F(ListTest, defaults) { const ConstElementPtr elem(Element::fromJSON("[" "{" " \"type\": \"type1\"" "}]")); list_->configure(elem, true); EXPECT_EQ(1, list_->getDataSources().size()); checkDS(0, "type1", "null", false); } // Check we can call the configure multiple times, to change the configuration TEST_F(ListTest, reconfigure) { const ConstElementPtr empty(new ListElement); list_->configure(config_elem_, true); checkDS(0, "test_type", "{}", false); list_->configure(empty, true); EXPECT_TRUE(list_->getDataSources().empty()); list_->configure(config_elem_, true); checkDS(0, "test_type", "{}", false); } // Make sure the data source error exception from the factory is propagated TEST_F(ListTest, dataSrcError) { const ConstElementPtr elem(Element::fromJSON("[" "{" " \"type\": \"error\"" "}]")); list_->configure(config_elem_, true); checkDS(0, "test_type", "{}", false); EXPECT_THROW(list_->configure(elem, true), DataSourceError); checkDS(0, "test_type", "{}", false); } // Check we can get the cache TEST_F(ListTest, configureCacheEmpty) { const ConstElementPtr elem(Element::fromJSON("[" "{" " \"type\": \"type1\"," " \"cache-enable\": true," " \"cache-zones\": []," " \"params\": {}" "}," "{" " \"type\": \"type2\"," " \"cache-enable\": false," " \"cache-zones\": []," " \"params\": {}" "}]" )); list_->configure(elem, true); EXPECT_EQ(2, list_->getDataSources().size()); checkDS(0, "type1", "{}", true); checkDS(1, "type2", "{}", false); } // But no cache if we disallow it globally TEST_F(ListTest, configureCacheDisabled) { const ConstElementPtr elem(Element::fromJSON("[" "{" " \"type\": \"type1\"," " \"cache-enable\": true," " \"cache-zones\": []," " \"params\": {}" "}," "{" " \"type\": \"type2\"," " \"cache-enable\": false," " \"cache-zones\": []," " \"params\": {}" "}]" )); list_->configure(elem, false); EXPECT_EQ(2, list_->getDataSources().size()); checkDS(0, "type1", "{}", false); checkDS(1, "type2", "{}", false); } // Put some zones into the cache TEST_F(ListTest, cacheZones) { const ConstElementPtr elem(Element::fromJSON("[" "{" " \"type\": \"type1\"," " \"cache-enable\": true," " \"cache-zones\": [\"example.org\", \"example.com\"]," " \"params\": [\"example.org\", \"example.com\", \"exmaple.cz\"]" "}]")); list_->configure(elem, true); checkDS(0, "type1", "[\"example.org\", \"example.com\", \"exmaple.cz\"]", true); const shared_ptr cache(list_->getDataSources()[0].cache_); EXPECT_EQ(2, cache->getZoneCount()); EXPECT_EQ(result::SUCCESS, cache->findZone(Name("example.org")).code); EXPECT_EQ(result::SUCCESS, cache->findZone(Name("example.com")).code); EXPECT_EQ(result::NOTFOUND, cache->findZone(Name("example.cz")).code); EXPECT_EQ(RRClass::IN(), cache->findZone(Name("example.org")).zone_finder->getClass()); // These are cached and answered from the cache positiveResult(list_->find(Name("example.com.")), ds_[0], Name("example.com."), true, "com", true); positiveResult(list_->find(Name("example.org.")), ds_[0], Name("example.org."), true, "org", true); positiveResult(list_->find(Name("sub.example.com.")), ds_[0], Name("example.com."), false, "Subdomain of com", true); // For now, the ones not cached are ignored. EXPECT_TRUE(negative_result_ == list_->find(Name("example.cz."))); } // Check the caching handles misbehaviour from the data source and // misconfiguration gracefully TEST_F(ListTest, badCache) { list_->configure(config_elem_, true); checkDS(0, "test_type", "{}", false); // First, the zone is not in the data source. configure() should still // succeed, and the existence zone should be cached. const ConstElementPtr elem1(Element::fromJSON("[" "{" " \"type\": \"test_type\"," " \"cache-enable\": true," " \"cache-zones\": [\"example.org\", \"example.com\"]," " \"params\": [\"example.org\"]" "}]")); list_->configure(elem1, true); // shouldn't cause disruption checkDS(0, "test_type", "[\"example.org\"]", true); const shared_ptr cache(list_->getDataSources()[0].cache_); EXPECT_EQ(1, cache->getZoneCount()); EXPECT_EQ(result::SUCCESS, cache->findZone(Name("example.org")).code); // Now, the zone doesn't give an iterator const ConstElementPtr elem2(Element::fromJSON("[" "{" " \"type\": \"type1\"," " \"cache-enable\": true," " \"cache-zones\": [\"noiter.org\"]," " \"params\": [\"noiter.org\"]" "}]")); EXPECT_THROW(list_->configure(elem2, true), isc::NotImplemented); checkDS(0, "test_type", "[\"example.org\"]", true); // Now, the zone returns NULL iterator const ConstElementPtr elem3(Element::fromJSON("[" "{" " \"type\": \"type1\"," " \"cache-enable\": true," " \"cache-zones\": [\"null.org\"]," " \"params\": [\"null.org\"]" "}]")); EXPECT_THROW(list_->configure(elem3, true), isc::Unexpected); checkDS(0, "test_type", "[\"example.org\"]", true); // The autodetection of zones is not enabled const ConstElementPtr elem4(Element::fromJSON("[" "{" " \"type\": \"type1\"," " \"cache-enable\": true," " \"params\": [\"example.org\"]" "}]")); EXPECT_THROW(list_->configure(elem4, true), isc::NotImplemented); checkDS(0, "test_type", "[\"example.org\"]", true); } // This test relies on the property of mapped type of cache. TEST_F(ListTest, #ifdef USE_SHARED_MEMORY cacheInNonWritableSegment #else DISABLED_cacheInNonWritableSegment #endif ) { // Initializing data source with non writable zone table memory segment // is possible. Loading is just postponed const ConstElementPtr elem(Element::fromJSON("[" "{" " \"type\": \"test_type\"," " \"cache-enable\": true," " \"cache-type\": \"mapped\"," " \"cache-zones\": [\"example.org\"]," " \"params\": [\"example.org\"]" "}]")); list_->configure(elem, true); // no disruption checkDS(0, "test_type", "[\"example.org\"]", true); const shared_ptr cache(list_->getDataSources()[0].cache_); // Likewise, reload attempt will fail. EXPECT_EQ(ConfigurableClientList::CACHE_NOT_WRITABLE, doReload(Name("example.org"))); } TEST_F(ListTest, masterFiles) { const ConstElementPtr elem(Element::fromJSON("[" "{" " \"type\": \"MasterFiles\"," " \"cache-enable\": true," " \"params\": {" " \".\": \"" TEST_DATA_DIR "/root.zone\"" " }" "}]")); list_->configure(elem, true); // It has only the cache EXPECT_EQ(static_cast(NULL), list_->getDataSources()[0].data_src_client_); // And it can search positiveResult(list_->find(Name(".")), ds_[0], Name("."), true, "com", true); // If cache is not enabled, nothing is loaded list_->configure(elem, false); EXPECT_EQ(0, list_->getDataSources().size()); } // Test the names are set correctly and collission is detected. TEST_F(ListTest, names) { // Explicit name const ConstElementPtr elem1(Element::fromJSON("[" "{" " \"type\": \"MasterFiles\"," " \"cache-enable\": true," " \"params\": {" " \".\": \"" TEST_DATA_DIR "/root.zone\"" " }," " \"name\": \"Whatever\"" "}]")); list_->configure(elem1, true); EXPECT_EQ("Whatever", list_->getDataSources()[0].name_); // Default name const ConstElementPtr elem2(Element::fromJSON("[" "{" " \"type\": \"MasterFiles\"," " \"cache-enable\": true," " \"params\": {" " \".\": \"" TEST_DATA_DIR "/root.zone\"" " }" "}]")); list_->configure(elem2, true); EXPECT_EQ("MasterFiles", list_->getDataSources()[0].name_); // Collission const ConstElementPtr elem3(Element::fromJSON("[" "{" " \"type\": \"MasterFiles\"," " \"cache-enable\": true," " \"params\": {" " \".\": \"" TEST_DATA_DIR "/root.zone\"" " }" "}," "{" " \"type\": \"MasterFiles\"," " \"cache-enable\": true," " \"params\": {" " \".\": \"" TEST_DATA_DIR "/root.zone\"" " }," " \"name\": \"MasterFiles\"" "}]")); EXPECT_THROW(list_->configure(elem3, true), ConfigurableClientList::ConfigurationError); } TEST_F(ListTest, BadMasterFile) { // Configuration should succeed, and the good zones in the list // below should be loaded. No bad zones should be loaded. const ConstElementPtr elem(Element::fromJSON("[" "{" " \"type\": \"MasterFiles\"," " \"cache-enable\": true," " \"params\": {" // good zone " \"example.com.\": \"" TEST_DATA_DIR "/example.com.flattened\"," // bad zone (empty file) " \"example.net.\": \"" TEST_DATA_DIR "/example.net-empty\"," // bad zone (data doesn't validate: see the file for details) " \"example.edu.\": \"" TEST_DATA_DIR "/example.edu-broken\"," // bad zone (file doesn't exist) " \"example.info.\": \"" TEST_DATA_DIR "/example.info-nonexist\"," // bad zone (data doesn't match the zone name) " \"foo.bar.\": \"" TEST_DATA_DIR "/example.org.nsec3-signed\"," // good zone " \".\": \"" TEST_DATA_DIR "/root.zone\"" " }" "}]")); EXPECT_NO_THROW({ // This should not throw even if there are any zone loading // errors. list_->configure(elem, true); }); positiveResult(list_->find(Name("example.com."), true), ds_[0], Name("example.com."), true, "example.com", true); EXPECT_TRUE(negative_result_ == list_->find(Name("example.org."), true)); EXPECT_TRUE(negative_result_ == list_->find(Name("foo.bar"), true)); EXPECT_TRUE(negative_result_ == list_->find(Name("example.net."), true)); EXPECT_TRUE(negative_result_ == list_->find(Name("example.edu."), true)); EXPECT_TRUE(negative_result_ == list_->find(Name("example.info."), true)); positiveResult(list_->find(Name(".")), ds_[0], Name("."), true, "root", true); } ConfigurableClientList::CacheStatus ListTest::doReload(const Name& origin, const string& datasrc_name) { ConfigurableClientList::ZoneWriterPair result(list_->getCachedZoneWriter(origin, datasrc_name)); if (result.first == ConfigurableClientList::ZONE_SUCCESS) { // Can't use ASSERT_NE here, it would want to return(), which // it can't in non-void function. if (result.second) { result.second->load(); result.second->install(); result.second->cleanup(); } else { ADD_FAILURE() << "getCachedZoneWriter returned ZONE_SUCCESS, " "but the writer is NULL"; } } else { EXPECT_EQ(static_cast(NULL), result.second.get()); } return (result.first); } // Test we can reload a zone TEST_F(ListTest, reloadSuccess) { list_->configure(config_elem_zones_, true); const Name name("example.org"); prepareCache(0, name); // The cache currently contains a tweaked version of zone, which // doesn't have "tstzonedata" A record. So the lookup should result // in NXDOMAIN. EXPECT_EQ(ZoneFinder::NXDOMAIN, list_->find(name).finder_-> find(Name("tstzonedata").concatenate(name), RRType::A())->code); // Now reload the full zone. It should be there now. EXPECT_EQ(ConfigurableClientList::ZONE_SUCCESS, doReload(name)); EXPECT_EQ(ZoneFinder::SUCCESS, list_->find(name).finder_-> find(Name("tstzonedata").concatenate(name), RRType::A())->code); } // The cache is not enabled. The load should be rejected. TEST_F(ListTest, reloadNotAllowed) { list_->configure(config_elem_zones_, false); const Name name("example.org"); // We put the cache in even when not enabled. This won't confuse the thing. prepareCache(0, name); // See the reloadSuccess test. This should result in NXDOMAIN. EXPECT_EQ(ZoneFinder::NXDOMAIN, list_->find(name).finder_-> find(Name("tstzonedata").concatenate(name), RRType::A())->code); // Now reload. It should reject it. EXPECT_EQ(ConfigurableClientList::CACHE_DISABLED, doReload(name)); // Nothing changed here EXPECT_EQ(ZoneFinder::NXDOMAIN, list_->find(name).finder_-> find(Name("tstzonedata").concatenate(name), RRType::A())->code); } // Similar to the previous case, but the cache is disabled in config. TEST_F(ListTest, reloadNotEnabled) { list_->configure(config_elem_zones_, true); const Name name("example.org"); // We put the cache, actually disabling it. prepareCache(0, name, false); // In this case we cannot really look up due to the limitation of // the mock implementation. We only check reload fails. EXPECT_EQ(ConfigurableClientList::ZONE_NOT_CACHED, doReload(name)); } // Test several cases when the zone does not exist TEST_F(ListTest, reloadNoSuchZone) { list_->configure(config_elem_zones_, true); const Name name("example.org"); // We put the cache in even when not enabled. This won't confuse the // reload method, as that one looks at the real state of things, not // at the configuration. prepareCache(0, Name("example.com")); // Not in the data sources EXPECT_EQ(ConfigurableClientList::ZONE_NOT_FOUND, doReload(Name("exmaple.cz"))); // If it's not configured to be cached, it won't be reloaded. EXPECT_EQ(ConfigurableClientList::ZONE_NOT_CACHED, doReload(name)); // Partial match EXPECT_EQ(ConfigurableClientList::ZONE_NOT_FOUND, doReload(Name("sub.example.com"))); // Nothing changed here - these zones don't exist EXPECT_EQ(static_cast(NULL), list_->find(name).dsrc_client_); EXPECT_EQ(static_cast(NULL), list_->find(Name("example.cz")).dsrc_client_); EXPECT_EQ(static_cast(NULL), list_->find(Name("sub.example.com"), true).dsrc_client_); // Not reloaded, so A record shouldn't be visible yet. EXPECT_EQ(ZoneFinder::NXDOMAIN, list_->find(Name("example.com")).finder_-> find(Name("tstzonedata.example.com"), RRType::A())->code); } // Check we gracefully reject reloading (i.e. no exception) when a zone // disappeared in the underlying data source when we want to reload it TEST_F(ListTest, reloadZoneGone) { list_->configure(config_elem_zones_, true); const Name name("example.org"); // We put in a cache for non-existent zone. This emulates being loaded // and then the zone disappearing. We prefill the cache, so we can check // it. prepareCache(0, name); // The (cached) zone contains zone's SOA EXPECT_EQ(ZoneFinder::SUCCESS, list_->find(name).finder_->find(name, RRType::SOA())->code); // Remove the zone from the data source. static_cast( list_->getDataSources()[0].data_src_client_)->eraseZone(name); // The zone is not there, so reload doesn't take place. EXPECT_EQ(ConfigurableClientList::ZONE_NOT_FOUND, doReload(name)); // The (cached) zone is not hurt. EXPECT_EQ(ZoneFinder::SUCCESS, list_->find(name).finder_->find(name, RRType::SOA())->code); } TEST_F(ListTest, reloadNewZone) { // Test the case where a zone to be cached originally doesn't exist // in the underlying data source and is added later. reload() will // succeed once it's available in the data source. const ConstElementPtr elem(Element::fromJSON("[" "{" " \"type\": \"test_type\"," " \"cache-enable\": true," " \"cache-zones\": [\"example.org\", \"example.com\"]," " \"params\": [\"example.org\"]" "}]")); list_->configure(elem, true); checkDS(0, "test_type", "[\"example.org\"]", true); // no example.com // We can't reload it either EXPECT_EQ(ConfigurableClientList::ZONE_NOT_FOUND, doReload(Name("example.com"))); // If we add the zone, we can now reload it EXPECT_TRUE(static_cast( list_->getDataSources()[0].data_src_client_)-> insertZone(Name("example.com"))); EXPECT_EQ(ConfigurableClientList::ZONE_SUCCESS, doReload(Name("example.com"))); } // The underlying data source throws. Check we don't modify the state. TEST_F(ListTest, reloadZoneThrow) { list_->configure(config_elem_zones_, true); const Name name("noiter.org"); prepareCache(0, name); // The zone contains stuff now EXPECT_EQ(ZoneFinder::SUCCESS, list_->find(name).finder_->find(name, RRType::SOA())->code); // The iterator throws, so abort the reload. EXPECT_THROW(doReload(name), isc::NotImplemented); // The zone is not hurt. EXPECT_EQ(ZoneFinder::SUCCESS, list_->find(name).finder_->find(name, RRType::SOA())->code); } TEST_F(ListTest, reloadNullIterator) { list_->configure(config_elem_zones_, true); const Name name("null.org"); prepareCache(0, name); // The zone contains stuff now EXPECT_EQ(ZoneFinder::SUCCESS, list_->find(name).finder_->find(name, RRType::SOA())->code); // The iterator throws, so abort the reload. EXPECT_THROW(doReload(name), isc::Unexpected); // The zone is not hurt. EXPECT_EQ(ZoneFinder::SUCCESS, list_->find(name).finder_->find(name, RRType::SOA())->code); } // Test we can reload the master files too (special-cased) TEST_F(ListTest, reloadMasterFile) { const char* const install_cmd = INSTALL_PROG " -c " TEST_DATA_DIR "/root.zone " TEST_DATA_BUILDDIR "/root.zone.copied"; if (system(install_cmd) != 0) { // any exception will do, this is failure in test setup, but // nice to show the command that fails, and shouldn't be caught isc_throw(isc::Exception, "Error setting up; command failed: " << install_cmd); } const ConstElementPtr elem(Element::fromJSON("[" "{" " \"type\": \"MasterFiles\"," " \"cache-enable\": true," " \"params\": {" " \".\": \"" TEST_DATA_BUILDDIR "/root.zone.copied\"" " }" "}]")); list_->configure(elem, true); // Add a record that is not in the zone EXPECT_EQ(ZoneFinder::NXDOMAIN, list_->find(Name(".")).finder_->find(Name("nosuchdomain"), RRType::TXT())->code); ofstream f; f.open(TEST_DATA_BUILDDIR "/root.zone.copied", ios::out | ios::app); f << "nosuchdomain.\t\t3600\tIN\tTXT\ttest" << std::endl; f.close(); // Do the reload. EXPECT_EQ(ConfigurableClientList::ZONE_SUCCESS, doReload(Name("."))); // It is here now. EXPECT_EQ(ZoneFinder::SUCCESS, list_->find(Name(".")).finder_->find(Name("nosuchdomain"), RRType::TXT())->code); } TEST_F(ListTest, reloadByDataSourceName) { // We use three data sources (and their clients). 2nd and 3rd have // the same name of the zones. const ConstElementPtr config_elem = Element::fromJSON( "[{\"type\": \"test_type1\", \"params\": [\"example.org\"]}," " {\"type\": \"test_type2\", \"params\": [\"example.com\"]}," " {\"type\": \"test_type3\", \"params\": [\"example.com\"]}]"); list_->configure(config_elem, true); // Prepare in-memory cache for the 1st and 2nd data sources. prepareCache(0, Name("example.org")); prepareCache(1, Name("example.com")); // Normal case: both zone name and data source name matches. // See the reloadSuccess test about the NXDOMAIN/SUCCESS checks. EXPECT_EQ(ZoneFinder::NXDOMAIN, list_->find(Name("tstzonedata.example.com")).finder_-> find(Name("tstzonedata.example.com"), RRType::A())->code); EXPECT_EQ(ConfigurableClientList::ZONE_SUCCESS, doReload(Name("example.com"), "test_type2")); EXPECT_EQ(ZoneFinder::SUCCESS, list_->find(Name("tstzonedata.example.com")).finder_-> find(Name("tstzonedata.example.com"), RRType::A())->code); // The specified zone exists in the first entry of the list, but a // different data source name is specified (in which the specified zone // doesn't exist), so reloading should fail, and the cache status of the // first data source shouldn't change. EXPECT_EQ(ZoneFinder::NXDOMAIN, list_->find(Name("tstzonedata.example.org")).finder_-> find(Name("tstzonedata.example.org"), RRType::A())->code); EXPECT_EQ(ConfigurableClientList::ZONE_NOT_FOUND, doReload(Name("example.org"), "test_type2")); EXPECT_EQ(ZoneFinder::NXDOMAIN, list_->find(Name("tstzonedata.example.org")).finder_-> find(Name("tstzonedata.example.org"), RRType::A())->code); // Likewise, if a specific data source is given, normal name matching // isn't suppressed and the 3rd data source will be used. There cache // is disabled, so reload should fail due to "not cached". EXPECT_EQ(ConfigurableClientList::ZONE_NOT_CACHED, doReload(Name("example.com"), "test_type3")); // specified name of data source doesn't exist. EXPECT_EQ(ConfigurableClientList::DATASRC_NOT_FOUND, doReload(Name("example.org"), "test_type4")); } // Check the status holds data TEST(DataSourceStatus, status) { const DataSourceStatus status("Test", SEGMENT_INUSE, "local"); EXPECT_EQ("Test", status.getName()); EXPECT_EQ(SEGMENT_INUSE, status.getSegmentState()); EXPECT_EQ("local", status.getSegmentType()); const DataSourceStatus status_unused("Unused", SEGMENT_UNUSED, ""); EXPECT_EQ("Unused", status_unused.getName()); EXPECT_EQ(SEGMENT_UNUSED, status_unused.getSegmentState()); EXPECT_THROW(status_unused.getSegmentType(), isc::InvalidOperation); } }