Commit cce2a00a authored by Michal 'vorner' Vaner's avatar Michal 'vorner' Vaner
Browse files

[1067] DatabaseClient part of iteration

The implementation of the ZoneIterator for DatabaseClient (it isn't
publicly visible, it is hidden in the .cc file) and tests for it.
parent 07cd1647
......@@ -13,11 +13,18 @@
// PERFORMANCE OF THIS SOFTWARE.
#include <datasrc/database.h>
#include <datasrc/data_source.h>
#include <datasrc/iterator.h>
#include <exceptions/exceptions.h>
#include <dns/name.h>
#include <dns/rrclass.h>
#include <dns/rrttl.h>
using isc::dns::Name;
#include <string>
using namespace isc::dns;
using std::string;
namespace isc {
namespace datasrc {
......@@ -81,5 +88,94 @@ DatabaseClient::Finder::getClass() const {
return isc::dns::RRClass::IN();
}
namespace {
/*
* This needs, beside of converting all data from textual representation, group
* together rdata of the same RRsets. To do this, we hold one row of data ahead
* of iteration. When we get a request to provide data, we create it from this
* data and load a new one. If it is to be put to the same rrset, we add it.
* Otherwise we just return what we have and keep the row as the one ahead
* for next time.
*/
class Iterator : public ZoneIterator {
public:
Iterator(const DatabaseConnection::IteratorContextPtr& context,
const RRClass& rrclass) :
context_(context),
class_(rrclass),
ready_(true)
{
// Prepare data for the next time
getData();
}
virtual isc::dns::ConstRRsetPtr getNextRRset() {
if (!ready_) {
isc_throw(isc::Unexpected, "Iterating past the zone end");
}
if (!data_ready_) {
// At the end of zone
ready_ = false;
return (ConstRRsetPtr());
}
string nameStr(name_), rtypeStr(rtype_);
int ttl(ttl_);
Name name(nameStr);
RRType rtype(rtypeStr);
RRsetPtr rrset(new RRset(name, class_, rtype, RRTTL(ttl)));
while (data_ready_ && name_ == nameStr && rtypeStr == rtype_) {
if (ttl_ != ttl) {
isc_throw(DataSourceError, "TTLs in rrset " + nameStr + "/" +
rtypeStr + " differ");
}
rrset->addRdata(rdata::createRdata(rtype, class_, rdata_));
getData();
}
return (rrset);
}
private:
// Load next row of data
void getData() {
data_ready_ = context_->getNext(name_, rtype_, ttl_, rdata_);
}
// The context
const DatabaseConnection::IteratorContextPtr context_;
// Class of the zone
RRClass class_;
// Status
bool ready_, data_ready_;
// Data of the next row
string name_, rtype_, rdata_;
int ttl_;
};
}
ZoneIteratorPtr
DatabaseClient::getIterator(const isc::dns::Name& name) const {
// Get the zone
std::pair<bool, int> zone(connection_->getZone(name));
if (!zone.first) {
// No such zone, can't continue
isc_throw(DataSourceError, "Zone " + name.toText() +
" can not be iterated, because it doesn't exist "
"in this data source");
}
// Request the context
DatabaseConnection::IteratorContextPtr
context(connection_->getIteratorContext(name, zone.second));
// It must not return NULL, that's a bug of the implementation
if (context == DatabaseConnection::IteratorContextPtr()) {
isc_throw(isc::Unexpected, "Iterator context null at " +
name.toText());
}
// Create the iterator and return it
// TODO: Once #1062 is merged with this, we need to get the
// actual zone class from the connection, as the DatabaseClient
// doesn't know it and the iterator needs it (so it wouldn't query
// it each time)
return (ZoneIteratorPtr(new Iterator(context, RRClass::IN())));
}
}
}
......@@ -271,7 +271,8 @@ public:
*
* \exception DataSourceError if the zone doesn't exist.
* \exception isc::NotImplemented if the underlying DatabaseConnection
* doesn't implement iteration.
* doesn't implement iteration. But in case it is not implemented
* and the zone doesn't exist, DataSourceError is thrown.
* \exception Anything else the underlying DatabaseConnection might
* want to throw.
* \param name The origin of the zone to iterate.
......
......@@ -15,36 +15,157 @@
#include <gtest/gtest.h>
#include <dns/name.h>
#include <dns/rrttl.h>
#include <exceptions/exceptions.h>
#include <datasrc/database.h>
#include <datasrc/data_source.h>
#include <datasrc/iterator.h>
using namespace isc::datasrc;
using namespace std;
using namespace boost;
using isc::dns::Name;
using namespace isc::dns;
namespace {
/*
* A virtual database connection that pretends it contains single zone --
* example.org.
* A connection with minimum implementation, keeping the original
* "NotImplemented" methods.
*/
class MockConnection : public DatabaseConnection {
class NopConnection : public DatabaseConnection {
public:
virtual std::pair<bool, int> getZone(const Name& name) const {
if (name == Name("example.org")) {
return (std::pair<bool, int>(true, 42));
} else if (name == Name("null.example.org")) {
return (std::pair<bool, int>(true, 13));
} else if (name == Name("empty.example.org")) {
return (std::pair<bool, int>(true, 0));
} else if (name == Name("bad.example.org")) {
return (std::pair<bool, int>(true, -1));
} else {
return (std::pair<bool, int>(false, 0));
}
}
};
/*
* A virtual database connection that pretends it contains single zone --
* example.org.
*
* It has the same getZone method as NopConnection, but it provides
* implementation of the optional functionality.
*/
class MockConnection : public NopConnection {
private:
class MockIteratorContext : public IteratorContext {
private:
int step;
public:
MockIteratorContext() :
step(0)
{ }
virtual bool getNext(string& name, string& rtype, int& ttl,
string& data)
{
switch (step ++) {
case 0:
name = "example.org";
rtype = "SOA";
ttl = 300;
data = "ns1.example.org. admin.example.org. "
"1234 3600 1800 2419200 7200";
return (true);
case 1:
name = "x.example.org";
rtype = "A";
ttl = 300;
data = "192.0.2.1";
return (true);
case 2:
name = "x.example.org";
rtype = "A";
ttl = 300;
data = "192.0.2.2";
return (true);
case 3:
name = "x.example.org";
rtype = "AAAA";
ttl = 300;
data = "2001:db8::1";
return (true);
case 4:
name = "x.example.org";
rtype = "AAAA";
ttl = 300;
data = "2001:db8::2";
return (true);
default:
ADD_FAILURE() <<
"Request past the end of iterator context";
case 5:
return (false);
}
}
};
class EmptyIteratorContext : public IteratorContext {
public:
virtual bool getNext(string&, string&, int&, string&) {
return (false);
}
};
class BadIteratorContext : public IteratorContext {
private:
int step;
public:
BadIteratorContext() :
step(0)
{ }
virtual bool getNext(string& name, string& rtype, int& ttl,
string& data)
{
switch (step ++) {
case 0:
name = "x.example.org";
rtype = "A";
ttl = 300;
data = "192.0.2.1";
return (true);
case 1:
name = "x.example.org";
rtype = "A";
ttl = 301;
data = "192.0.2.2";
return (true);
default:
ADD_FAILURE() <<
"Request past the end of iterator context";
case 2:
return (false);
}
}
};
public:
virtual IteratorContextPtr getIteratorContext(const Name&, int id) const {
if (id == 42) {
return (IteratorContextPtr(new MockIteratorContext()));
} else if (id == 13) {
return (IteratorContextPtr());
} else if (id == 0) {
return (IteratorContextPtr(new EmptyIteratorContext()));
} else if (id == -1) {
return (IteratorContextPtr(new BadIteratorContext()));
} else {
isc_throw(isc::Unexpected, "Unknown zone ID");
}
}
};
// This tests the default getIteratorContext behaviour, throwing NotImplemented
TEST(DatabaseConnectionTest, getIteratorContext) {
// The parameters don't matter
EXPECT_THROW(MockConnection().getIteratorContext(Name("."), 1),
EXPECT_THROW(NopConnection().getIteratorContext(Name("."), 1),
isc::NotImplemented);
}
......@@ -103,4 +224,91 @@ TEST_F(DatabaseClientTest, noConnException) {
isc::InvalidParameter);
}
// If the zone doesn't exist, exception is thrown
TEST_F(DatabaseClientTest, noZoneIterator) {
EXPECT_THROW(client_->getIterator(Name("example.com")), DataSourceError);
}
// If the zone doesn't exist and iteration is not implemented, it still throws
// the exception it doesn't exist
TEST_F(DatabaseClientTest, noZoneNotImplementedIterator) {
EXPECT_THROW(DatabaseClient(auto_ptr<DatabaseConnection>(
new NopConnection())).getIterator(Name("example.com")),
DataSourceError);
}
TEST_F(DatabaseClientTest, notImplementedIterator) {
EXPECT_THROW(DatabaseClient(auto_ptr<DatabaseConnection>(
new NopConnection())).getIterator(Name("example.org")),
isc::NotImplemented);
}
// Pretend a bug in the connection and pass NULL as the context
// Should not crash, but gracefully throw
TEST_F(DatabaseClientTest, nullIteratorContext) {
EXPECT_THROW(client_->getIterator(Name("null.example.org")),
isc::Unexpected);
}
// It doesn't crash or anything if the zone is completely empty
TEST_F(DatabaseClientTest, emptyIterator) {
ZoneIteratorPtr it(client_->getIterator(Name("empty.example.org")));
EXPECT_EQ(ConstRRsetPtr(), it->getNextRRset());
// This is past the end, it should throw
EXPECT_THROW(it->getNextRRset(), isc::Unexpected);
}
// Iterate trough a zone
TEST_F(DatabaseClientTest, iterator) {
ZoneIteratorPtr it(client_->getIterator(Name("example.org")));
ConstRRsetPtr rrset(it->getNextRRset());
ASSERT_NE(ConstRRsetPtr(), rrset);
EXPECT_EQ(Name("example.org"), rrset->getName());
EXPECT_EQ(RRClass::IN(), rrset->getClass());
EXPECT_EQ(RRType::SOA(), rrset->getType());
EXPECT_EQ(RRTTL(300), rrset->getTTL());
RdataIteratorPtr rit(rrset->getRdataIterator());
ASSERT_FALSE(rit->isLast());
rit->next();
EXPECT_TRUE(rit->isLast());
rrset = it->getNextRRset();
ASSERT_NE(ConstRRsetPtr(), rrset);
EXPECT_EQ(Name("x.example.org"), rrset->getName());
EXPECT_EQ(RRClass::IN(), rrset->getClass());
EXPECT_EQ(RRType::A(), rrset->getType());
EXPECT_EQ(RRTTL(300), rrset->getTTL());
rit = rrset->getRdataIterator();
ASSERT_FALSE(rit->isLast());
EXPECT_EQ("192.0.2.1", rit->getCurrent().toText());
rit->next();
ASSERT_FALSE(rit->isLast());
EXPECT_EQ("192.0.2.2", rit->getCurrent().toText());
rit->next();
EXPECT_TRUE(rit->isLast());
rrset = it->getNextRRset();
ASSERT_NE(ConstRRsetPtr(), rrset);
EXPECT_EQ(Name("x.example.org"), rrset->getName());
EXPECT_EQ(RRClass::IN(), rrset->getClass());
EXPECT_EQ(RRType::AAAA(), rrset->getType());
EXPECT_EQ(RRTTL(300), rrset->getTTL());
EXPECT_EQ(ConstRRsetPtr(), it->getNextRRset());
rit = rrset->getRdataIterator();
ASSERT_FALSE(rit->isLast());
EXPECT_EQ("2001:db8::1", rit->getCurrent().toText());
rit->next();
ASSERT_FALSE(rit->isLast());
EXPECT_EQ("2001:db8::2", rit->getCurrent().toText());
rit->next();
EXPECT_TRUE(rit->isLast());
}
// This has inconsistent TTL in the set (the rest, like nonsense in
// the data is handled in rdata itself).
TEST_F(DatabaseClientTest, badIterator) {
ZoneIteratorPtr it(client_->getIterator(Name("bad.example.org")));
EXPECT_THROW(it->getNextRRset(), DataSourceError);
}
}
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