Commit 7197d4f5 authored by Michal 'vorner' Vaner's avatar Michal 'vorner' Vaner
Browse files

Merge #2044

parents 1fc2b06b afddf882
......@@ -36,6 +36,7 @@ libdatasrc_la_SOURCES += client.h iterator.h
libdatasrc_la_SOURCES += database.h database.cc
libdatasrc_la_SOURCES += factory.h factory.cc
libdatasrc_la_SOURCES += client_list.h client_list.cc
libdatasrc_la_SOURCES += memory_datasrc.h memory_datasrc.cc
nodist_libdatasrc_la_SOURCES = datasrc_messages.h datasrc_messages.cc
libdatasrc_la_LDFLAGS = -no-undefined -version-info 1:0:1
......@@ -49,14 +50,12 @@ sqlite3_ds_la_LIBADD = $(top_builddir)/src/lib/exceptions/libexceptions.la
sqlite3_ds_la_LIBADD += libdatasrc.la
sqlite3_ds_la_LIBADD += $(SQLITE_LIBS)
memory_ds_la_SOURCES = memory_datasrc.h memory_datasrc.cc
memory_ds_la_SOURCES += memory_datasrc_link.cc
memory_ds_la_SOURCES = memory_datasrc_link.cc
memory_ds_la_LDFLAGS = -module -avoid-version
memory_ds_la_LIBADD = $(top_builddir)/src/lib/exceptions/libexceptions.la
memory_ds_la_LIBADD += libdatasrc.la
static_ds_la_SOURCES = memory_datasrc.h memory_datasrc.cc
static_ds_la_SOURCES += static_datasrc_link.cc
static_ds_la_SOURCES = static_datasrc_link.cc
static_ds_la_LDFLAGS = -module -avoid-version
static_ds_la_LIBADD = $(top_builddir)/src/lib/exceptions/libexceptions.la
static_ds_la_LIBADD += libdatasrc.la
......
......@@ -15,19 +15,32 @@
#include "client_list.h"
#include "client.h"
#include "factory.h"
#include "memory_datasrc.h"
#include <memory>
#include <boost/foreach.hpp>
using namespace isc::data;
using namespace isc::dns;
using namespace std;
using namespace boost;
namespace isc {
namespace datasrc {
ConfigurableClientList::DataSourceInfo::DataSourceInfo(
DataSourceClient* data_src_client,
const DataSourceClientContainerPtr& container, bool hasCache) :
data_src_client_(data_src_client),
container_(container)
{
if (hasCache) {
cache_.reset(new InMemoryClient);
}
}
void
ConfigurableClientList::configure(const Element& config, bool) {
// TODO: Implement the cache
ConfigurableClientList::configure(const Element& config, bool allow_cache) {
// TODO: Implement recycling from the old configuration.
size_t i(0); // Outside of the try to be able to access it in the catch
try {
......@@ -49,8 +62,48 @@ ConfigurableClientList::configure(const Element& config, bool) {
// Ask the factory to create the data source for us
const DataSourcePair ds(this->getDataSourceClient(type,
paramConf));
const bool want_cache(allow_cache &&
dconf->contains("cache-enable") &&
dconf->get("cache-enable")->boolValue());
// And put it into the vector
new_data_sources.push_back(DataSourceInfo(ds.first, ds.second));
new_data_sources.push_back(DataSourceInfo(ds.first, ds.second,
want_cache));
if (want_cache) {
if (!dconf->contains("cache-zones")) {
isc_throw(isc::NotImplemented, "Auto-detection of zones "
"to cache is not yet implemented, supply "
"cache-zones parameter");
// TODO: Auto-detect list of all zones in the
// data source.
}
const ConstElementPtr zones(dconf->get("cache-zones"));
const shared_ptr<InMemoryClient>
cache(new_data_sources.back().cache_);
const DataSourceClient* const
client(new_data_sources.back().data_src_client_);
for (size_t i(0); i < zones->size(); ++i) {
const Name origin(zones->get(i)->stringValue());
const DataSourceClient::FindResult
zone(client->findZone(origin));
if (zone.code != result::SUCCESS) {
// The data source does not contain the zone, it can't
// be cached.
isc_throw(ConfigurationError, "Unable to cache "
"non-existent zone " << origin);
}
shared_ptr<InMemoryZoneFinder>
finder(new
InMemoryZoneFinder(zone.zone_finder->getClass(),
origin));
ZoneIteratorPtr iterator(client->getIterator(origin));
if (!iterator) {
isc_throw(isc::Unexpected, "Got NULL iterator for "
"zone " << origin);
}
finder->load(*iterator);
cache->addZone(finder);
}
}
}
// If everything is OK up until now, we have the new configuration
// ready. So just put it there and let the old one die when we exit
......@@ -88,14 +141,11 @@ ConfigurableClientList::find(const dns::Name& name, bool want_exact_match,
} candidate;
BOOST_FOREACH(const DataSourceInfo& info, data_sources_) {
// TODO: Once we have support for the caches, consider them too here
// somehow. This would probably get replaced by a function, that
// checks if there's a cache available, if it is, checks the loaded
// zones and zones expected to be in the real data source. If it is
// the cached one, provide the cached one. If it is in the external
// data source, use the datasource and don't provide the finder yet.
const DataSourceClient::FindResult result(
info.data_src_client_->findZone(name));
DataSourceClient* client(info.cache_ ? info.cache_.get() :
info.data_src_client_);
const DataSourceClient::FindResult result(client->findZone(name));
// TODO: Once we mark the zones that are not loaded, but are present
// in the data source somehow, check them too.
switch (result.code) {
case result::SUCCESS:
// If we found an exact match, we have no hope to getting
......@@ -103,7 +153,7 @@ ConfigurableClientList::find(const dns::Name& name, bool want_exact_match,
// TODO: In case we have only the datasource and not the finder
// and the need_updater parameter is true, get the zone there.
return (FindResult(info.data_src_client_, result.zone_finder,
return (FindResult(client, result.zone_finder,
true));
case result::PARTIALMATCH:
if (!want_exact_match) {
......@@ -124,7 +174,7 @@ ConfigurableClientList::find(const dns::Name& name, bool want_exact_match,
if (labels > candidate.matched_labels ||
!candidate.matched) {
// This one is strictly better. Replace it.
candidate.datasrc_client = info.data_src_client_;
candidate.datasrc_client = client;
candidate.finder = result.zone_finder;
candidate.matched_labels = labels;
candidate.matched = true;
......
......@@ -33,6 +33,7 @@ typedef boost::shared_ptr<DataSourceClient> DataSourceClientPtr;
class DataSourceClientContainer;
typedef boost::shared_ptr<DataSourceClientContainer>
DataSourceClientContainerPtr;
class InMemoryClient;
/// \brief The list of data source clients.
///
......@@ -212,6 +213,11 @@ public:
/// client.
/// \throw ConfigurationError if the configuration is invalid in some
/// sense.
/// \throw Unexpected if something misbehaves (like the data source
/// returning NULL iterator).
/// \throw NotImplemented if the auto-detection of list of zones is
/// needed.
/// \throw Whatever is propagated from within the data source.
void configure(const data::Element& configuration, bool allow_cache);
/// \brief Implementation of the ClientList::find.
......@@ -231,12 +237,11 @@ public:
data_src_client_(NULL)
{}
DataSourceInfo(DataSourceClient* data_src_client,
const DataSourceClientContainerPtr& container) :
data_src_client_(data_src_client),
container_(container)
{}
const DataSourceClientContainerPtr& container,
bool hasCache);
DataSourceClient* data_src_client_;
DataSourceClientContainerPtr container_;
boost::shared_ptr<InMemoryClient> cache_;
};
/// \brief The collection of data sources.
......
......@@ -67,7 +67,6 @@ run_unittests_SOURCES += client_list_unittest.cc
# We need the actual module implementation in the tests (they are not part
# of libdatasrc)
run_unittests_SOURCES += $(top_srcdir)/src/lib/datasrc/sqlite3_accessor.cc
run_unittests_SOURCES += $(top_srcdir)/src/lib/datasrc/memory_datasrc.cc
run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
......
......@@ -14,9 +14,13 @@
#include <datasrc/client_list.h>
#include <datasrc/client.h>
#include <datasrc/iterator.h>
#include <datasrc/data_source.h>
#include <datasrc/memory_datasrc.h>
#include <dns/rrclass.h>
#include <dns/rrttl.h>
#include <dns/rdataclass.h>
#include <gtest/gtest.h>
......@@ -41,7 +45,7 @@ public:
Name getOrigin() const { return (origin_); }
// The rest is not to be called, so just have them
RRClass getClass() const {
isc_throw(isc::NotImplemented, "Not implemented");
return (RRClass::IN());
}
shared_ptr<Context> find(const Name&, const RRType&,
const FindOptions)
......@@ -60,6 +64,35 @@ public:
private:
Name origin_;
};
class Iterator : public ZoneIterator {
public:
Iterator(const Name& origin) :
origin_(origin),
finished_(false),
soa_(new RRset(origin_, RRClass::IN(), RRType::SOA(), RRTTL(3600)))
{
// The RData here is bogus, but it is not used to anything. There
// just needs to be some.
soa_->addRdata(rdata::generic::SOA(Name::ROOT_NAME(),
Name::ROOT_NAME(),
0, 0, 0, 0, 0));
}
virtual isc::dns::ConstRRsetPtr getNextRRset() {
if (finished_) {
return (ConstRRsetPtr());
} else {
finished_ = true;
return (soa_);
}
}
virtual isc::dns::ConstRRsetPtr getSOA() const {
return (soa_);
}
private:
const Name origin_;
bool finished_;
const isc::dns::RRsetPtr soa_;
};
// Constructor from a list of zones.
MockDataSourceClient(const char* zone_names[]) {
for (const char** zone(zone_names); *zone; ++zone) {
......@@ -72,7 +105,13 @@ public:
const ConstElementPtr& configuration) :
type_(type),
configuration_(configuration)
{}
{
if (configuration_->getType() == Element::list) {
for (size_t i(0); i < configuration_->size(); ++i) {
zones.insert(Name(configuration_->get(i)->stringValue()));
}
}
}
virtual FindResult findZone(const Name& name) const {
if (zones.empty()) {
return (FindResult(result::NOTFOUND, ZoneFinderPtr()));
......@@ -103,6 +142,15 @@ public:
{
isc_throw(isc::NotImplemented, "Not implemented");
}
virtual ZoneIteratorPtr getIterator(const Name& name, bool) const {
if (name == Name("noiter.org")) {
isc_throw(isc::NotImplemented, "Asked not to be implemented");
} else if (name == Name("null.org")) {
return (ZoneIteratorPtr());
} else {
return (ZoneIteratorPtr(new Iterator(name)));
}
}
const string type_;
const ConstElementPtr configuration_;
private:
......@@ -166,7 +214,6 @@ public:
config_elem_(Element::fromJSON("["
"{"
" \"type\": \"test_type\","
" \"cache\": \"off\","
" \"params\": {}"
"}]"))
{
......@@ -175,20 +222,28 @@ public:
ds(new MockDataSourceClient(ds_zones[i]));
ds_.push_back(ds);
ds_info_.push_back(ConfigurableClientList::DataSourceInfo(ds.get(),
DataSourceClientContainerPtr()));
DataSourceClientContainerPtr(), false));
}
}
// Check the positive result is as we expect it.
void positiveResult(const ClientList::FindResult& result,
const shared_ptr<MockDataSourceClient>& dsrc,
const Name& name, bool exact,
const char* test)
const char* test, bool from_cache = false)
{
SCOPED_TRACE(test);
EXPECT_EQ(dsrc.get(), result.dsrc_client_);
ASSERT_NE(ZoneFinderPtr(), result.finder_);
EXPECT_EQ(name, result.finder_->getOrigin());
EXPECT_EQ(exact, result.exact_match_);
if (from_cache) {
EXPECT_NE(shared_ptr<InMemoryZoneFinder>(),
dynamic_pointer_cast<InMemoryZoneFinder>(
result.finder_)) << "Finder is not from cache";
EXPECT_TRUE(NULL !=
dynamic_cast<InMemoryClient*>(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
......@@ -220,7 +275,8 @@ public:
FAIL() << "Unknown configuration index " << index;
}
}
void checkDS(size_t index, const string& type, const string& params) const
void checkDS(size_t index, const string& type, const string& params,
bool cache) const
{
ASSERT_GT(list_->getDataSources().size(), index);
MockDataSourceClient* ds(dynamic_cast<MockDataSourceClient*>(
......@@ -230,6 +286,8 @@ public:
ASSERT_NE(ds, static_cast<const MockDataSourceClient*>(NULL));
EXPECT_EQ(type, ds->type_);
EXPECT_TRUE(Element::fromJSON(params)->equals(*ds->configuration_));
EXPECT_EQ(cache, list_->getDataSources()[index].cache_ !=
shared_ptr<InMemoryClient>());
}
shared_ptr<TestedList> list_;
const ClientList::FindResult negativeResult_;
......@@ -349,14 +407,14 @@ TEST_F(ListTest, multiBestMatch) {
// Check the configuration is empty when the list is empty
TEST_F(ListTest, configureEmpty) {
ConstElementPtr elem(new ListElement);
const ConstElementPtr elem(new ListElement);
list_->configure(*elem, true);
EXPECT_TRUE(list_->getDataSources().empty());
}
// Check we can get multiple data sources and they are in the right order.
TEST_F(ListTest, configureMulti) {
ConstElementPtr elem(Element::fromJSON("["
const ConstElementPtr elem(Element::fromJSON("["
"{"
" \"type\": \"type1\","
" \"cache\": \"off\","
......@@ -370,8 +428,8 @@ TEST_F(ListTest, configureMulti) {
));
list_->configure(*elem, true);
EXPECT_EQ(2, list_->getDataSources().size());
checkDS(0, "type1", "{}");
checkDS(1, "type2", "{}");
checkDS(0, "type1", "{}", false);
checkDS(1, "type2", "{}", false);
}
// Check we can pass whatever we want to the params
......@@ -396,7 +454,7 @@ TEST_F(ListTest, configureParams) {
"}]"));
list_->configure(*elem, true);
EXPECT_EQ(1, list_->getDataSources().size());
checkDS(0, "t", *param);
checkDS(0, "t", *param, false);
}
}
......@@ -418,55 +476,198 @@ TEST_F(ListTest, wrongConfig) {
"[{\"type\": \"test_type\", \"params\": 13}, {\"type\": null}]",
"[{\"type\": \"test_type\", \"params\": 13}, {\"type\": []}]",
"[{\"type\": \"test_type\", \"params\": 13}, {\"type\": {}}]",
// TODO: Once cache is supported, add some invalid cache values
// 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\": {}}]",
NULL
};
// Put something inside to see it survives the exception
list_->configure(*config_elem_, true);
checkDS(0, "test_type", "{}");
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", "{}");
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) {
ConstElementPtr elem(Element::fromJSON("["
const ConstElementPtr elem(Element::fromJSON("["
"{"
" \"type\": \"type1\""
"}]"));
list_->configure(*elem, true);
EXPECT_EQ(1, list_->getDataSources().size());
checkDS(0, "type1", "null");
checkDS(0, "type1", "null", false);
}
// Check we can call the configure multiple times, to change the configuration
TEST_F(ListTest, reconfigure) {
ConstElementPtr empty(new ListElement);
const ConstElementPtr empty(new ListElement);
list_->configure(*config_elem_, true);
checkDS(0, "test_type", "{}");
checkDS(0, "test_type", "{}", false);
list_->configure(*empty, true);
EXPECT_TRUE(list_->getDataSources().empty());
list_->configure(*config_elem_, true);
checkDS(0, "test_type", "{}");
checkDS(0, "test_type", "{}", false);
}
// Make sure the data source error exception from the factory is propagated
TEST_F(ListTest, dataSrcError) {
ConstElementPtr elem(Element::fromJSON("["
const ConstElementPtr elem(Element::fromJSON("["
"{"
" \"type\": \"error\""
"}]"));
list_->configure(*config_elem_, true);
checkDS(0, "test_type", "{}");
checkDS(0, "test_type", "{}", false);
EXPECT_THROW(list_->configure(*elem, true), DataSourceError);
checkDS(0, "test_type", "{}");
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<InMemoryClient> 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);
// 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(negativeResult_ == 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
const ConstElementPtr elem1(Element::fromJSON("["
"{"
" \"type\": \"type1\","
" \"cache-enable\": true,"
" \"cache-zones\": [\"example.org\"],"
" \"params\": []"
"}]"));
EXPECT_THROW(list_->configure(*elem1, true),
ConfigurableClientList::ConfigurationError);
checkDS(0, "test_type", "{}", false);
// 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", "{}", false);
// 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", "{}", false);
// 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", "{}", false);
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment