mirror of
https://gitlab.isc.org/isc-projects/kea
synced 2025-09-05 16:35:23 +00:00
- removed white spaces at EOL - add a spaece after commas - variable naming convention (use underscored lower cased chars) - indentation
3478 lines
156 KiB
C++
3478 lines
156 KiB
C++
// Copyright (C) 2011 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 <stdlib.h>
|
|
|
|
#include <boost/shared_ptr.hpp>
|
|
#include <boost/lexical_cast.hpp>
|
|
|
|
#include <gtest/gtest.h>
|
|
|
|
#include <exceptions/exceptions.h>
|
|
|
|
#include <dns/name.h>
|
|
#include <dns/rrttl.h>
|
|
#include <dns/rrset.h>
|
|
#include <exceptions/exceptions.h>
|
|
|
|
#include <datasrc/database.h>
|
|
#include <datasrc/zone.h>
|
|
#include <datasrc/data_source.h>
|
|
#include <datasrc/iterator.h>
|
|
#include <datasrc/sqlite3_accessor.h>
|
|
|
|
#include <testutils/dnsmessage_test.h>
|
|
|
|
#include <map>
|
|
#include <vector>
|
|
|
|
using namespace isc::datasrc;
|
|
using namespace std;
|
|
// don't import the entire boost namespace. It will unexpectedly hide uint32_t
|
|
// for some systems.
|
|
using boost::dynamic_pointer_cast;
|
|
using boost::lexical_cast;
|
|
using namespace isc::dns;
|
|
|
|
namespace {
|
|
|
|
// Imaginary zone IDs used in the mock accessor below.
|
|
const int READONLY_ZONE_ID = 42;
|
|
const int WRITABLE_ZONE_ID = 4200;
|
|
|
|
// Commonly used test data
|
|
const char* const TEST_RECORDS[][5] = {
|
|
// some plain data
|
|
{"www.example.org.", "A", "3600", "", "192.0.2.1"},
|
|
{"www.example.org.", "AAAA", "3600", "", "2001:db8::1"},
|
|
{"www.example.org.", "AAAA", "3600", "", "2001:db8::2"},
|
|
{"www.example.org.", "NSEC", "3600", "", "www2.example.org. A AAAA NSEC RRSIG"},
|
|
{"www.example.org.", "RRSIG", "3600", "", "NSEC 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},
|
|
|
|
{"www2.example.org.", "A", "3600", "", "192.0.2.1"},
|
|
{"www2.example.org.", "AAAA", "3600", "", "2001:db8::1"},
|
|
{"www2.example.org.", "A", "3600", "", "192.0.2.2"},
|
|
|
|
{"cname.example.org.", "CNAME", "3600", "", "www.example.org."},
|
|
|
|
// some DNSSEC-'signed' data
|
|
{"signed1.example.org.", "A", "3600", "", "192.0.2.1"},
|
|
{"signed1.example.org.", "RRSIG", "3600", "", "A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},
|
|
|
|
{"signed1.example.org.", "RRSIG", "3600", "", "A 5 3 3600 20000101000000 20000201000000 12346 example.org. FAKEFAKEFAKE"},
|
|
{"signed1.example.org.", "AAAA", "3600", "", "2001:db8::1"},
|
|
{"signed1.example.org.", "AAAA", "3600", "", "2001:db8::2"},
|
|
{"signed1.example.org.", "RRSIG", "3600", "", "AAAA 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},
|
|
|
|
{"signedcname1.example.org.", "CNAME", "3600", "", "www.example.org."},
|
|
{"signedcname1.example.org.", "RRSIG", "3600", "", "CNAME 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},
|
|
|
|
// special case might fail; sig is for cname, which isn't there (should be ignored)
|
|
// (ignoring of 'normal' other type is done above by www.)
|
|
{"acnamesig1.example.org.", "A", "3600", "", "192.0.2.1"},
|
|
{"acnamesig1.example.org.", "RRSIG", "3600", "", "A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},
|
|
{"acnamesig1.example.org.", "RRSIG", "3600", "", "CNAME 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},
|
|
|
|
// let's pretend we have a database that is not careful
|
|
// about the order in which it returns data
|
|
{"signed2.example.org.", "RRSIG", "3600", "", "A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},
|
|
{"signed2.example.org.", "AAAA", "3600", "", "2001:db8::2"},
|
|
{"signed2.example.org.", "RRSIG", "3600", "", "A 5 3 3600 20000101000000 20000201000000 12346 example.org. FAKEFAKEFAKE"},
|
|
{"signed2.example.org.", "A", "3600", "", "192.0.2.1"},
|
|
{"signed2.example.org.", "RRSIG", "3600", "", "AAAA 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},
|
|
{"signed2.example.org.", "AAAA", "3600", "", "2001:db8::1"},
|
|
|
|
{"signedcname2.example.org.", "RRSIG", "3600", "", "CNAME 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},
|
|
{"signedcname2.example.org.", "CNAME", "3600", "", "www.example.org."},
|
|
|
|
{"acnamesig2.example.org.", "RRSIG", "3600", "", "CNAME 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},
|
|
{"acnamesig2.example.org.", "A", "3600", "", "192.0.2.1"},
|
|
{"acnamesig2.example.org.", "RRSIG", "3600", "", "A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},
|
|
|
|
{"acnamesig3.example.org.", "RRSIG", "3600", "", "CNAME 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},
|
|
{"acnamesig3.example.org.", "RRSIG", "3600", "", "A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},
|
|
{"acnamesig3.example.org.", "A", "3600", "", "192.0.2.1"},
|
|
|
|
{"ttldiff1.example.org.", "A", "3600", "", "192.0.2.1"},
|
|
{"ttldiff1.example.org.", "A", "360", "", "192.0.2.2"},
|
|
|
|
{"ttldiff2.example.org.", "A", "360", "", "192.0.2.1"},
|
|
{"ttldiff2.example.org.", "A", "3600", "", "192.0.2.2"},
|
|
|
|
// also add some intentionally bad data
|
|
{"badcname1.example.org.", "A", "3600", "", "192.0.2.1"},
|
|
{"badcname1.example.org.", "CNAME", "3600", "", "www.example.org."},
|
|
|
|
{"badcname2.example.org.", "CNAME", "3600", "", "www.example.org."},
|
|
{"badcname2.example.org.", "A", "3600", "", "192.0.2.1"},
|
|
|
|
{"badcname3.example.org.", "CNAME", "3600", "", "www.example.org."},
|
|
{"badcname3.example.org.", "CNAME", "3600", "", "www.example2.org."},
|
|
|
|
{"badrdata.example.org.", "A", "3600", "", "bad"},
|
|
|
|
{"badtype.example.org.", "BAD_TYPE", "3600", "", "192.0.2.1"},
|
|
|
|
{"badttl.example.org.", "A", "badttl", "", "192.0.2.1"},
|
|
|
|
{"badsig.example.org.", "A", "badttl", "", "192.0.2.1"},
|
|
{"badsig.example.org.", "RRSIG", "3600", "", "A 5 3 3600 somebaddata 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},
|
|
|
|
{"badsigtype.example.org.", "A", "3600", "", "192.0.2.1"},
|
|
{"badsigtype.example.org.", "RRSIG", "3600", "TXT", "A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},
|
|
|
|
// Data for testing delegation (with NS and DNAME)
|
|
{"delegation.example.org.", "NS", "3600", "", "ns.example.com."},
|
|
{"delegation.example.org.", "NS", "3600", "",
|
|
"ns.delegation.example.org."},
|
|
{"delegation.example.org.", "DS", "3600", "", "1 RSAMD5 2 abcd"},
|
|
{"delegation.example.org.", "RRSIG", "3600", "", "NS 5 3 3600 "
|
|
"20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},
|
|
{"ns.delegation.example.org.", "A", "3600", "", "192.0.2.1"},
|
|
{"deep.below.delegation.example.org.", "A", "3600", "", "192.0.2.1"},
|
|
|
|
{"dname.example.org.", "A", "3600", "", "192.0.2.1"},
|
|
{"dname.example.org.", "DNAME", "3600", "", "dname.example.com."},
|
|
{"dname.example.org.", "RRSIG", "3600", "",
|
|
"DNAME 5 3 3600 20000101000000 20000201000000 12345 "
|
|
"example.org. FAKEFAKEFAKE"},
|
|
|
|
{"below.dname.example.org.", "A", "3600", "", "192.0.2.1"},
|
|
|
|
// Broken NS
|
|
{"brokenns1.example.org.", "A", "3600", "", "192.0.2.1"},
|
|
{"brokenns1.example.org.", "NS", "3600", "", "ns.example.com."},
|
|
|
|
{"brokenns2.example.org.", "NS", "3600", "", "ns.example.com."},
|
|
{"brokenns2.example.org.", "A", "3600", "", "192.0.2.1"},
|
|
|
|
// Now double DNAME, to test failure mode
|
|
{"baddname.example.org.", "DNAME", "3600", "", "dname1.example.com."},
|
|
{"baddname.example.org.", "DNAME", "3600", "", "dname2.example.com."},
|
|
|
|
// Put some data into apex (including NS) so we can check our NS
|
|
// doesn't break anything
|
|
{"example.org.", "SOA", "3600", "", "ns1.example.org. admin.example.org. "
|
|
"1234 3600 1800 2419200 7200" },
|
|
{"example.org.", "NS", "3600", "", "ns.example.com."},
|
|
{"example.org.", "A", "3600", "", "192.0.2.1"},
|
|
{"example.org.", "NSEC", "3600", "", "acnamesig1.example.org. NS A NSEC RRSIG"},
|
|
{"example.org.", "RRSIG", "3600", "", "SOA 5 3 3600 20000101000000 "
|
|
"20000201000000 12345 example.org. FAKEFAKEFAKE"},
|
|
{"example.org.", "RRSIG", "3600", "", "NSEC 5 3 3600 20000101000000 "
|
|
"20000201000000 12345 example.org. FAKEFAKEFAKE"},
|
|
{"example.org.", "RRSIG", "3600", "", "NS 5 3 3600 20000101000000 "
|
|
"20000201000000 12345 example.org. FAKEFAKEFAKE"},
|
|
|
|
// This is because of empty domain test
|
|
{"a.b.example.org.", "A", "3600", "", "192.0.2.1"},
|
|
|
|
// Something for wildcards
|
|
{"*.wild.example.org.", "A", "3600", "", "192.0.2.5"},
|
|
{"*.wild.example.org.", "RRSIG", "3600", "A", "A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},
|
|
{"*.wild.example.org.", "NSEC", "3600", "", "cancel.here.wild.example.org. A NSEC RRSIG"},
|
|
{"*.wild.example.org.", "RRSIG", "3600", "", "NSEC 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},
|
|
{"cancel.here.wild.example.org.", "AAAA", "3600", "", "2001:db8::5"},
|
|
{"delegatedwild.example.org.", "NS", "3600", "", "ns.example.com."},
|
|
{"*.delegatedwild.example.org.", "A", "3600", "", "192.0.2.5"},
|
|
{"wild.*.foo.example.org.", "A", "3600", "", "192.0.2.5"},
|
|
{"wild.*.foo.*.bar.example.org.", "A", "3600", "", "192.0.2.5"},
|
|
{"wild.*.foo.*.bar.example.org.", "NSEC", "3600", "",
|
|
"brokenns1.example.org. A NSEC"},
|
|
{"bao.example.org.", "NSEC", "3600", "", "wild.*.foo.*.bar.example.org. NSEC"},
|
|
{"*.cnamewild.example.org.", "CNAME", "3600", "", "www.example.org."},
|
|
{"*.dnamewild.example.org.", "DNAME", "3600", "", "dname.example.com."},
|
|
{"*.nswild.example.org.", "NS", "3600", "", "ns.example.com."},
|
|
// For NSEC empty non-terminal
|
|
{"l.example.org.", "NSEC", "3600", "", "empty.nonterminal.example.org. NSEC"},
|
|
{"empty.nonterminal.example.org.", "A", "3600", "", "192.0.2.1"},
|
|
// Invalid rdata
|
|
{"invalidrdata.example.org.", "A", "3600", "", "Bunch of nonsense"},
|
|
{"invalidrdata2.example.org.", "A", "3600", "", "192.0.2.1"},
|
|
{"invalidrdata2.example.org.", "RRSIG", "3600", "", "Nonsense"},
|
|
|
|
{NULL, NULL, NULL, NULL, NULL},
|
|
};
|
|
|
|
/*
|
|
* An accessor with minimum implementation, keeping the original
|
|
* "NotImplemented" methods.
|
|
*/
|
|
class NopAccessor : public DatabaseAccessor {
|
|
public:
|
|
NopAccessor() : database_name_("mock_database")
|
|
{ }
|
|
|
|
virtual std::pair<bool, int> getZone(const std::string& name) const {
|
|
if (name == "example.org.") {
|
|
return (std::pair<bool, int>(true, READONLY_ZONE_ID));
|
|
} else if (name == "null.example.org.") {
|
|
return (std::pair<bool, int>(true, 13));
|
|
} else if (name == "empty.example.org.") {
|
|
return (std::pair<bool, int>(true, 0));
|
|
} else if (name == "bad.example.org.") {
|
|
return (std::pair<bool, int>(true, -1));
|
|
} else {
|
|
return (std::pair<bool, int>(false, 0));
|
|
}
|
|
}
|
|
|
|
virtual boost::shared_ptr<DatabaseAccessor> clone() {
|
|
// This accessor is stateless, so we can simply return a new instance.
|
|
return (boost::shared_ptr<DatabaseAccessor>(new NopAccessor));
|
|
}
|
|
|
|
virtual std::pair<bool, int> startUpdateZone(const std::string&, bool) {
|
|
// return dummy value. unused anyway.
|
|
return (pair<bool, int>(true, 0));
|
|
}
|
|
virtual void startTransaction() {}
|
|
virtual void commit() {}
|
|
virtual void rollback() {}
|
|
virtual void addRecordToZone(const string (&)[ADD_COLUMN_COUNT]) {}
|
|
virtual void deleteRecordInZone(const string (&)[DEL_PARAM_COUNT]) {}
|
|
virtual void addRecordDiff(int, uint32_t, DiffOperation,
|
|
const std::string (&)[DIFF_PARAM_COUNT]) {}
|
|
|
|
virtual const std::string& getDBName() const {
|
|
return (database_name_);
|
|
}
|
|
|
|
virtual IteratorContextPtr getRecords(const std::string&, int, bool)
|
|
const
|
|
{
|
|
isc_throw(isc::NotImplemented,
|
|
"This database datasource can't be iterated");
|
|
}
|
|
|
|
virtual IteratorContextPtr getAllRecords(int) const {
|
|
isc_throw(isc::NotImplemented,
|
|
"This database datasource can't be iterated");
|
|
}
|
|
|
|
virtual IteratorContextPtr getDiffs(int, uint32_t, uint32_t) const {
|
|
isc_throw(isc::NotImplemented,
|
|
"This database datasource doesn't support diffs");
|
|
}
|
|
|
|
virtual std::string findPreviousName(int, const std::string&) const {
|
|
isc_throw(isc::NotImplemented,
|
|
"This data source doesn't support DNSSEC");
|
|
}
|
|
private:
|
|
const std::string database_name_;
|
|
|
|
};
|
|
|
|
/**
|
|
* Single journal entry in the mock database.
|
|
*
|
|
* All the members there are public for simplicity, as it only stores data.
|
|
* We use the implicit constructor and operator. The members can't be const
|
|
* because of the assignment operator (used in the vectors).
|
|
*/
|
|
struct JournalEntry {
|
|
JournalEntry(int id, uint32_t serial,
|
|
DatabaseAccessor::DiffOperation operation,
|
|
const std::string (&data)[DatabaseAccessor::DIFF_PARAM_COUNT])
|
|
: id_(id), serial_(serial), operation_(operation)
|
|
{
|
|
data_[DatabaseAccessor::DIFF_NAME] = data[DatabaseAccessor::DIFF_NAME];
|
|
data_[DatabaseAccessor::DIFF_TYPE] = data[DatabaseAccessor::DIFF_TYPE];
|
|
data_[DatabaseAccessor::DIFF_TTL] = data[DatabaseAccessor::DIFF_TTL];
|
|
data_[DatabaseAccessor::DIFF_RDATA] =
|
|
data[DatabaseAccessor::DIFF_RDATA];
|
|
}
|
|
JournalEntry(int id, uint32_t serial,
|
|
DatabaseAccessor::DiffOperation operation,
|
|
const std::string& name, const std::string& type,
|
|
const std::string& ttl, const std::string& rdata):
|
|
id_(id), serial_(serial), operation_(operation)
|
|
{
|
|
data_[DatabaseAccessor::DIFF_NAME] = name;
|
|
data_[DatabaseAccessor::DIFF_TYPE] = type;
|
|
data_[DatabaseAccessor::DIFF_TTL] = ttl;
|
|
data_[DatabaseAccessor::DIFF_RDATA] = rdata;
|
|
}
|
|
int id_;
|
|
uint32_t serial_;
|
|
DatabaseAccessor::DiffOperation operation_;
|
|
std::string data_[DatabaseAccessor::DIFF_PARAM_COUNT];
|
|
bool operator==(const JournalEntry& other) const {
|
|
for (size_t i = 0; i < DatabaseAccessor::DIFF_PARAM_COUNT; ++ i) {
|
|
if (data_[i] != other.data_[i]) {
|
|
return false;
|
|
}
|
|
}
|
|
// No need to check data here, checked above
|
|
return (id_ == other.id_ && serial_ == other.serial_ &&
|
|
operation_ == other.operation_);
|
|
}
|
|
};
|
|
|
|
/*
|
|
* A virtual database accessor 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 MockAccessor : public NopAccessor {
|
|
// Type of mock database "row"s. This is a map whose keys are the
|
|
// own names. We internally sort them by the name comparison order.
|
|
struct NameCompare : public binary_function<string, string, bool> {
|
|
bool operator()(const string& n1, const string& n2) const {
|
|
return (Name(n1).compare(Name(n2)).getOrder() < 0);
|
|
}
|
|
};
|
|
typedef std::map<std::string,
|
|
std::vector< std::vector<std::string> >,
|
|
NameCompare > Domains;
|
|
|
|
public:
|
|
MockAccessor() : rollbacked_(false), did_transaction_(false) {
|
|
readonly_records_ = &readonly_records_master_;
|
|
update_records_ = &update_records_master_;
|
|
empty_records_ = &empty_records_master_;
|
|
journal_entries_ = &journal_entries_master_;
|
|
fillData();
|
|
}
|
|
|
|
virtual boost::shared_ptr<DatabaseAccessor> clone() {
|
|
boost::shared_ptr<MockAccessor> cloned_accessor(new MockAccessor());
|
|
cloned_accessor->readonly_records_ = &readonly_records_master_;
|
|
cloned_accessor->update_records_ = &update_records_master_;
|
|
cloned_accessor->empty_records_ = &empty_records_master_;
|
|
cloned_accessor->journal_entries_ = &journal_entries_master_;
|
|
latest_clone_ = cloned_accessor;
|
|
return (cloned_accessor);
|
|
}
|
|
|
|
virtual void startTransaction() {
|
|
// Currently we only use this transaction for simple read-only
|
|
// operations. So we just make a local copy of the data (we don't
|
|
// care about what happens after commit() or rollback()).
|
|
// Obviously as a consequence, if a test case tries to make multiple
|
|
// transactions on a single mock accessor it will fail.
|
|
|
|
// Check any attempt of multiple transactions
|
|
if (did_transaction_) {
|
|
isc_throw(isc::Unexpected, "MockAccessor::startTransaction() "
|
|
"called multiple times - likely a bug in the test");
|
|
}
|
|
|
|
readonly_records_copy_ = *readonly_records_;
|
|
readonly_records_ = &readonly_records_copy_;
|
|
did_transaction_ = true;
|
|
}
|
|
|
|
private:
|
|
class MockNameIteratorContext : public IteratorContext {
|
|
public:
|
|
MockNameIteratorContext(const MockAccessor& mock_accessor, int zone_id,
|
|
const std::string& name, bool subdomains) :
|
|
searched_name_(name), cur_record_(0)
|
|
{
|
|
// 'hardcoded' names to trigger exceptions
|
|
// On these names some exceptions are thrown, to test the robustness
|
|
// of the find() method.
|
|
if (searched_name_ == "dsexception.example.org.") {
|
|
isc_throw(DataSourceError, "datasource exception on search");
|
|
} else if (searched_name_ == "iscexception.example.org.") {
|
|
isc_throw(isc::Exception, "isc exception on search");
|
|
} else if (searched_name_ == "basicexception.example.org.") {
|
|
throw std::exception();
|
|
}
|
|
|
|
cur_record_ = 0;
|
|
const Domains& cur_records = mock_accessor.getMockRecords(zone_id);
|
|
if (cur_records.count(name) > 0) {
|
|
// we're not aiming for efficiency in this test, simply
|
|
// copy the relevant vector from records
|
|
cur_name = cur_records.find(name)->second;
|
|
} else if (subdomains) {
|
|
cur_name.clear();
|
|
// Just walk everything and check if it is a subdomain.
|
|
// If it is, just copy all data from there.
|
|
for (Domains::const_iterator i = cur_records.begin();
|
|
i != cur_records.end(); ++i) {
|
|
const Name local(i->first);
|
|
if (local.compare(Name(name)).getRelation() ==
|
|
isc::dns::NameComparisonResult::SUBDOMAIN) {
|
|
cur_name.insert(cur_name.end(), i->second.begin(),
|
|
i->second.end());
|
|
}
|
|
}
|
|
} else {
|
|
cur_name.clear();
|
|
}
|
|
}
|
|
|
|
virtual bool getNext(std::string (&columns)[COLUMN_COUNT]) {
|
|
if (searched_name_ == "dsexception.getnext.example.org.") {
|
|
isc_throw(DataSourceError, "datasource exception on getnextrecord");
|
|
} else if (searched_name_ == "iscexception.getnext.example.org.") {
|
|
isc_throw(isc::Exception, "isc exception on getnextrecord");
|
|
} else if (searched_name_ ==
|
|
"basicexception.getnext.example.org.") {
|
|
throw std::exception();
|
|
}
|
|
|
|
if (cur_record_ < cur_name.size()) {
|
|
for (size_t i = 0; i < COLUMN_COUNT; ++i) {
|
|
columns[i] = cur_name[cur_record_][i];
|
|
}
|
|
cur_record_++;
|
|
return (true);
|
|
} else {
|
|
return (false);
|
|
}
|
|
}
|
|
|
|
private:
|
|
const std::string searched_name_;
|
|
size_t cur_record_;
|
|
std::vector< std::vector<std::string> > cur_name;
|
|
};
|
|
|
|
class MockIteratorContext : public IteratorContext {
|
|
private:
|
|
int step;
|
|
const Domains& domains_;
|
|
public:
|
|
MockIteratorContext(const Domains& domains) :
|
|
step(0), domains_(domains)
|
|
{ }
|
|
virtual bool getNext(string (&data)[COLUMN_COUNT]) {
|
|
// A special case: if the given set of domains is already empty,
|
|
// we always return false.
|
|
if (domains_.empty()) {
|
|
return (false);
|
|
}
|
|
|
|
// Return faked data for tests
|
|
switch (step ++) {
|
|
case 0:
|
|
data[DatabaseAccessor::NAME_COLUMN] = "example.org";
|
|
data[DatabaseAccessor::TYPE_COLUMN] = "A";
|
|
data[DatabaseAccessor::TTL_COLUMN] = "3600";
|
|
data[DatabaseAccessor::RDATA_COLUMN] = "192.0.2.1";
|
|
return (true);
|
|
case 1:
|
|
data[DatabaseAccessor::NAME_COLUMN] = "example.org";
|
|
data[DatabaseAccessor::TYPE_COLUMN] = "SOA";
|
|
data[DatabaseAccessor::TTL_COLUMN] = "3600";
|
|
data[DatabaseAccessor::RDATA_COLUMN] = "ns1.example.org. admin.example.org. "
|
|
"1234 3600 1800 2419200 7200";
|
|
return (true);
|
|
case 2:
|
|
data[DatabaseAccessor::NAME_COLUMN] = "x.example.org";
|
|
data[DatabaseAccessor::TYPE_COLUMN] = "A";
|
|
data[DatabaseAccessor::TTL_COLUMN] = "300";
|
|
data[DatabaseAccessor::RDATA_COLUMN] = "192.0.2.1";
|
|
return (true);
|
|
case 3:
|
|
data[DatabaseAccessor::NAME_COLUMN] = "x.example.org";
|
|
data[DatabaseAccessor::TYPE_COLUMN] = "A";
|
|
data[DatabaseAccessor::TTL_COLUMN] = "300";
|
|
data[DatabaseAccessor::RDATA_COLUMN] = "192.0.2.2";
|
|
return (true);
|
|
case 4:
|
|
data[DatabaseAccessor::NAME_COLUMN] = "x.example.org";
|
|
data[DatabaseAccessor::TYPE_COLUMN] = "AAAA";
|
|
data[DatabaseAccessor::TTL_COLUMN] = "300";
|
|
data[DatabaseAccessor::RDATA_COLUMN] = "2001:db8::1";
|
|
return (true);
|
|
case 5:
|
|
data[DatabaseAccessor::NAME_COLUMN] = "x.example.org";
|
|
data[DatabaseAccessor::TYPE_COLUMN] = "AAAA";
|
|
data[DatabaseAccessor::TTL_COLUMN] = "300";
|
|
data[DatabaseAccessor::RDATA_COLUMN] = "2001:db8::2";
|
|
return (true);
|
|
case 6:
|
|
data[DatabaseAccessor::NAME_COLUMN] = "ttldiff.example.org";
|
|
data[DatabaseAccessor::TYPE_COLUMN] = "A";
|
|
data[DatabaseAccessor::TTL_COLUMN] = "300";
|
|
data[DatabaseAccessor::RDATA_COLUMN] = "192.0.2.1";
|
|
return (true);
|
|
case 7:
|
|
data[DatabaseAccessor::NAME_COLUMN] = "ttldiff.example.org";
|
|
data[DatabaseAccessor::TYPE_COLUMN] = "A";
|
|
data[DatabaseAccessor::TTL_COLUMN] = "600";
|
|
data[DatabaseAccessor::RDATA_COLUMN] = "192.0.2.2";
|
|
return (true);
|
|
default:
|
|
ADD_FAILURE() <<
|
|
"Request past the end of iterator context";
|
|
case 8:
|
|
return (false);
|
|
}
|
|
}
|
|
};
|
|
class EmptyIteratorContext : public IteratorContext {
|
|
public:
|
|
virtual bool getNext(string(&)[COLUMN_COUNT]) {
|
|
return (false);
|
|
}
|
|
};
|
|
class BadIteratorContext : public IteratorContext {
|
|
private:
|
|
int step;
|
|
public:
|
|
BadIteratorContext() :
|
|
step(0)
|
|
{ }
|
|
virtual bool getNext(string (&data)[COLUMN_COUNT]) {
|
|
switch (step ++) {
|
|
case 0:
|
|
data[DatabaseAccessor::NAME_COLUMN] = "x.example.org";
|
|
data[DatabaseAccessor::TYPE_COLUMN] = "A";
|
|
data[DatabaseAccessor::TTL_COLUMN] = "300";
|
|
data[DatabaseAccessor::RDATA_COLUMN] = "192.0.2.1";
|
|
return (true);
|
|
case 1:
|
|
data[DatabaseAccessor::NAME_COLUMN] = "x.example.org";
|
|
data[DatabaseAccessor::TYPE_COLUMN] = "A";
|
|
data[DatabaseAccessor::TTL_COLUMN] = "301";
|
|
data[DatabaseAccessor::RDATA_COLUMN] = "192.0.2.2";
|
|
return (true);
|
|
default:
|
|
ADD_FAILURE() <<
|
|
"Request past the end of iterator context";
|
|
case 2:
|
|
return (false);
|
|
}
|
|
}
|
|
};
|
|
class MockDiffIteratorContext : public IteratorContext {
|
|
const vector<JournalEntry> diffs_;
|
|
vector<JournalEntry>::const_iterator it_;
|
|
public:
|
|
MockDiffIteratorContext(const vector<JournalEntry>& diffs) :
|
|
diffs_(diffs), it_(diffs_.begin())
|
|
{}
|
|
virtual bool getNext(string (&data)[COLUMN_COUNT]) {
|
|
if (it_ == diffs_.end()) {
|
|
return (false);
|
|
}
|
|
data[DatabaseAccessor::NAME_COLUMN] =
|
|
(*it_).data_[DatabaseAccessor::DIFF_NAME];
|
|
data[DatabaseAccessor::TYPE_COLUMN] =
|
|
(*it_).data_[DatabaseAccessor::DIFF_TYPE];
|
|
data[DatabaseAccessor::TTL_COLUMN] =
|
|
(*it_).data_[DatabaseAccessor::DIFF_TTL];
|
|
data[DatabaseAccessor::RDATA_COLUMN] =
|
|
(*it_).data_[DatabaseAccessor::DIFF_RDATA];
|
|
++it_;
|
|
return (true);
|
|
}
|
|
};
|
|
public:
|
|
virtual IteratorContextPtr getAllRecords(int id) const {
|
|
if (id == READONLY_ZONE_ID) {
|
|
return (IteratorContextPtr(new MockIteratorContext(
|
|
*readonly_records_)));
|
|
} 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");
|
|
}
|
|
}
|
|
|
|
virtual IteratorContextPtr getRecords(const std::string& name, int id,
|
|
bool subdomains) const
|
|
{
|
|
if (id == READONLY_ZONE_ID || id == WRITABLE_ZONE_ID) {
|
|
return (IteratorContextPtr(
|
|
new MockNameIteratorContext(*this, id, name,
|
|
subdomains)));
|
|
} else {
|
|
// This iterator is bogus, but for the cases tested below that's
|
|
// sufficient.
|
|
return (IteratorContextPtr(
|
|
new MockNameIteratorContext(*this, READONLY_ZONE_ID,
|
|
name, subdomains)));
|
|
}
|
|
}
|
|
|
|
virtual pair<bool, int> startUpdateZone(const std::string& zone_name,
|
|
bool replace)
|
|
{
|
|
const pair<bool, int> zone_info = getZone(zone_name);
|
|
if (!zone_info.first) {
|
|
return (pair<bool, int>(false, 0));
|
|
}
|
|
|
|
// Prepare the record set for update. If replacing the existing one,
|
|
// we use an empty set; otherwise we use a writable copy of the
|
|
// original.
|
|
if (replace) {
|
|
update_records_->clear();
|
|
} else {
|
|
*update_records_ = *readonly_records_;
|
|
}
|
|
|
|
if (zone_name == "bad.example.org.") {
|
|
return (pair<bool, int>(true, -1));
|
|
} else if (zone_name == "null.example.org.") {
|
|
return (pair<bool, int>(true, 13));
|
|
} else {
|
|
return (pair<bool, int>(true, WRITABLE_ZONE_ID));
|
|
}
|
|
}
|
|
virtual void commit() {
|
|
*readonly_records_ = *update_records_;
|
|
}
|
|
virtual void rollback() {
|
|
// Special hook: if something with a name of "throw.example.org"
|
|
// has been added, trigger an imaginary unexpected event with an
|
|
// exception.
|
|
if (update_records_->count("throw.example.org.") > 0) {
|
|
isc_throw(DataSourceError, "unexpected failure in rollback");
|
|
}
|
|
|
|
rollbacked_ = true;
|
|
}
|
|
virtual void addRecordToZone(const string (&columns)[ADD_COLUMN_COUNT]) {
|
|
// Copy the current value to cur_name. If it doesn't exist,
|
|
// operator[] will create a new one.
|
|
cur_name_ = (*update_records_)[columns[DatabaseAccessor::ADD_NAME]];
|
|
|
|
vector<string> record_columns;
|
|
record_columns.push_back(columns[DatabaseAccessor::ADD_TYPE]);
|
|
record_columns.push_back(columns[DatabaseAccessor::ADD_TTL]);
|
|
record_columns.push_back(columns[DatabaseAccessor::ADD_SIGTYPE]);
|
|
record_columns.push_back(columns[DatabaseAccessor::ADD_RDATA]);
|
|
record_columns.push_back(columns[DatabaseAccessor::ADD_NAME]);
|
|
|
|
// copy back the added entry
|
|
cur_name_.push_back(record_columns);
|
|
(*update_records_)[columns[DatabaseAccessor::ADD_NAME]] = cur_name_;
|
|
|
|
// remember this one so that test cases can check it.
|
|
copy(columns, columns + DatabaseAccessor::ADD_COLUMN_COUNT,
|
|
columns_lastadded_);
|
|
}
|
|
|
|
// Helper predicate class used in deleteRecordInZone().
|
|
struct deleteMatch {
|
|
deleteMatch(const string& type, const string& rdata) :
|
|
type_(type), rdata_(rdata)
|
|
{}
|
|
bool operator()(const vector<string>& row) const {
|
|
return (row[0] == type_ && row[3] == rdata_);
|
|
}
|
|
const string& type_;
|
|
const string& rdata_;
|
|
};
|
|
|
|
virtual void deleteRecordInZone(const string (¶ms)[DEL_PARAM_COUNT]) {
|
|
vector<vector<string> >& records =
|
|
(*update_records_)[params[DatabaseAccessor::DEL_NAME]];
|
|
records.erase(remove_if(records.begin(), records.end(),
|
|
deleteMatch(
|
|
params[DatabaseAccessor::DEL_TYPE],
|
|
params[DatabaseAccessor::DEL_RDATA])),
|
|
records.end());
|
|
if (records.empty()) {
|
|
(*update_records_).erase(params[DatabaseAccessor::DEL_NAME]);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Helper methods to keep track of some update related activities
|
|
//
|
|
bool isRollbacked() const {
|
|
return (rollbacked_);
|
|
}
|
|
|
|
const string* getLastAdded() const {
|
|
return (columns_lastadded_);
|
|
}
|
|
|
|
// This allows the test code to get the accessor used in an update context
|
|
boost::shared_ptr<const MockAccessor> getLatestClone() const {
|
|
return (latest_clone_);
|
|
}
|
|
|
|
virtual std::string findPreviousName(int id, const std::string& rname)
|
|
const
|
|
{
|
|
if (id == -1) {
|
|
isc_throw(isc::NotImplemented, "Test not implemented behaviour");
|
|
} else if (id == READONLY_ZONE_ID) {
|
|
// For some specific names we intentionally return broken or
|
|
// unexpected result.
|
|
if (rname == "org.example.badnsec2.") {
|
|
return ("badnsec1.example.org.");
|
|
} else if (rname == "org.example.brokenname.") {
|
|
return ("brokenname...example.org.");
|
|
} else if (rname == "org.example.notimplnsec." ||
|
|
rname == "org.example.wild.here.") {
|
|
isc_throw(isc::NotImplemented, "Not implemented in this test");
|
|
}
|
|
|
|
// For the general case, we search for the first name N in the
|
|
// domains that meets N >= reverse(rname) using lower_bound.
|
|
// The "previous name" is the name of the previous entry of N.
|
|
// Note that Domains are internally sorted by the Name comparison
|
|
// order. Due to the API requirement we are given a reversed
|
|
// name (rname), so we need to reverse it again to convert it
|
|
// to the original name.
|
|
Domains::const_iterator it(readonly_records_->lower_bound(
|
|
Name(rname).reverse().toText()));
|
|
if (it == readonly_records_->begin()) {
|
|
isc_throw(isc::Unexpected, "Unexpected name");
|
|
}
|
|
if (it == readonly_records_->end()) {
|
|
return ((*readonly_records_->rbegin()).first);
|
|
}
|
|
return ((*(--it)).first);
|
|
} else {
|
|
isc_throw(isc::Unexpected, "Unknown zone ID");
|
|
}
|
|
}
|
|
virtual void addRecordDiff(int id, uint32_t serial,
|
|
DiffOperation operation,
|
|
const std::string (&data)[DIFF_PARAM_COUNT])
|
|
{
|
|
if (id == 13) { // The null zone doesn't support journaling
|
|
isc_throw(isc::NotImplemented, "Test not implemented behaviour");
|
|
} else if (id == -1) { // Bad zone throws
|
|
isc_throw(DataSourceError, "Test error");
|
|
} else {
|
|
journal_entries_->push_back(JournalEntry(id, serial, operation,
|
|
data));
|
|
}
|
|
}
|
|
|
|
virtual IteratorContextPtr getDiffs(int id, uint32_t start,
|
|
uint32_t end) const
|
|
{
|
|
vector<JournalEntry> selected_jnl;
|
|
|
|
for (vector<JournalEntry>::const_iterator it =
|
|
journal_entries_->begin();
|
|
it != journal_entries_->end(); ++it)
|
|
{
|
|
// For simplicity we assume this method is called for the
|
|
// "readonly" zone possibly after making updates on the "writable"
|
|
// copy and committing them.
|
|
if (id != READONLY_ZONE_ID) {
|
|
continue;
|
|
}
|
|
|
|
// Note: the following logic is not 100% accurate in terms of
|
|
// serial number arithmetic; we prefer brevity for testing.
|
|
// Skip until we see the starting serial. Once we started
|
|
// recording this condition is ignored (to support wrap-around
|
|
// case). Also, it ignores the RR type; it only checks the
|
|
// versions.
|
|
if ((*it).serial_ < start && selected_jnl.empty()) {
|
|
continue;
|
|
}
|
|
if ((*it).serial_ > end) { // gone over the end serial. we're done.
|
|
break;
|
|
}
|
|
selected_jnl.push_back(*it);
|
|
}
|
|
|
|
// Check if we've found the requested range. If not, throw.
|
|
if (selected_jnl.empty() || selected_jnl.front().serial_ != start ||
|
|
selected_jnl.back().serial_ != end) {
|
|
isc_throw(NoSuchSerial, "requested diff range is not found");
|
|
}
|
|
|
|
return (IteratorContextPtr(new MockDiffIteratorContext(selected_jnl)));
|
|
}
|
|
|
|
// Check the journal is as expected and clear the journal
|
|
void checkJournal(const std::vector<JournalEntry> &expected) const {
|
|
std::vector<JournalEntry> journal;
|
|
// Clean the journal, but keep local copy to check
|
|
journal.swap(*journal_entries_);
|
|
ASSERT_EQ(expected.size(), journal.size());
|
|
for (size_t i = 0; i < expected.size(); ++ i) {
|
|
EXPECT_TRUE(expected[i] == journal[i]);
|
|
}
|
|
}
|
|
|
|
private:
|
|
// The following member variables are storage and/or update work space
|
|
// of the test zone. The "master"s are the real objects that contain
|
|
// the data, and they are shared among all accessors cloned from
|
|
// an initially created one. The "copy" data will be used for read-only
|
|
// transaction. The pointer members allow the sharing.
|
|
// "readonly" is for normal lookups. "update" is the workspace for
|
|
// updates. When update starts it will be initialized either as an
|
|
// empty set (when replacing the entire zone) or as a copy of the
|
|
// "readonly" one. "empty" is a sentinel to produce negative results.
|
|
Domains readonly_records_master_;
|
|
Domains readonly_records_copy_;
|
|
Domains* readonly_records_;
|
|
Domains update_records_master_;
|
|
Domains* update_records_;
|
|
const Domains empty_records_master_;
|
|
const Domains* empty_records_;
|
|
|
|
// The journal data
|
|
std::vector<JournalEntry> journal_entries_master_;
|
|
std::vector<JournalEntry>* journal_entries_;
|
|
|
|
// used as temporary storage after searchForRecord() and during
|
|
// getNextRecord() calls, as well as during the building of the
|
|
// fake data
|
|
std::vector< std::vector<std::string> > cur_name_;
|
|
|
|
// The columns that were most recently added via addRecordToZone()
|
|
string columns_lastadded_[ADD_COLUMN_COUNT];
|
|
|
|
// Whether rollback operation has been performed for the database.
|
|
// Not useful except for purely testing purpose.
|
|
bool rollbacked_;
|
|
|
|
// Remember the mock accessor that was last cloned
|
|
boost::shared_ptr<MockAccessor> latest_clone_;
|
|
|
|
// Internal flag for duplicate check
|
|
bool did_transaction_;
|
|
|
|
const Domains& getMockRecords(int zone_id) const {
|
|
if (zone_id == READONLY_ZONE_ID) {
|
|
return (*readonly_records_);
|
|
} else if (zone_id == WRITABLE_ZONE_ID) {
|
|
return (*update_records_);
|
|
}
|
|
return (*empty_records_);
|
|
}
|
|
|
|
// Adds one record to the current name in the database
|
|
// The actual data will not be added to 'records' until
|
|
// addCurName() is called
|
|
void addRecord(const std::string& type,
|
|
const std::string& ttl,
|
|
const std::string& sigtype,
|
|
const std::string& rdata) {
|
|
std::vector<std::string> columns;
|
|
columns.push_back(type);
|
|
columns.push_back(ttl);
|
|
columns.push_back(sigtype);
|
|
columns.push_back(rdata);
|
|
cur_name_.push_back(columns);
|
|
}
|
|
|
|
// Adds all records we just built with calls to addRecords
|
|
// to the actual fake database. This will clear cur_name_,
|
|
// so we can immediately start adding new records.
|
|
void addCurName(const std::string& name) {
|
|
ASSERT_EQ(0, readonly_records_->count(name));
|
|
// Append the name to all of them
|
|
for (std::vector<std::vector<std::string> >::iterator
|
|
i = cur_name_.begin(); i != cur_name_.end(); ++ i) {
|
|
i->push_back(name);
|
|
}
|
|
(*readonly_records_)[name] = cur_name_;
|
|
cur_name_.clear();
|
|
}
|
|
|
|
// Fills the database with zone data.
|
|
// This method constructs a number of resource records (with addRecord),
|
|
// which will all be added for one domain name to the fake database
|
|
// (with addCurName). So for instance the first set of calls create
|
|
// data for the name 'www.example.org', which will consist of one A RRset
|
|
// of one record, and one AAAA RRset of two records.
|
|
// The order in which they are added is the order in which getNextRecord()
|
|
// will return them (so we can test whether find() etc. support data that
|
|
// might not come in 'normal' order)
|
|
// It shall immediately fail if you try to add the same name twice.
|
|
void fillData() {
|
|
const char* prev_name = NULL;
|
|
for (int i = 0; TEST_RECORDS[i][0] != NULL; ++i) {
|
|
if (prev_name != NULL &&
|
|
strcmp(prev_name, TEST_RECORDS[i][0]) != 0) {
|
|
addCurName(prev_name);
|
|
}
|
|
prev_name = TEST_RECORDS[i][0];
|
|
addRecord(TEST_RECORDS[i][1], TEST_RECORDS[i][2],
|
|
TEST_RECORDS[i][3], TEST_RECORDS[i][4]);
|
|
}
|
|
addCurName(prev_name);
|
|
}
|
|
};
|
|
|
|
// This tests the default getRecords behaviour, throwing NotImplemented
|
|
TEST(DatabaseConnectionTest, getRecords) {
|
|
EXPECT_THROW(NopAccessor().getRecords(".", 1, false),
|
|
isc::NotImplemented);
|
|
}
|
|
|
|
// This tests the default getAllRecords behaviour, throwing NotImplemented
|
|
TEST(DatabaseConnectionTest, getAllRecords) {
|
|
// The parameters don't matter
|
|
EXPECT_THROW(NopAccessor().getAllRecords(1),
|
|
isc::NotImplemented);
|
|
}
|
|
|
|
// This test fixture is templated so that we can share (most of) the test
|
|
// cases with different types of data sources. Note that in test cases
|
|
// we need to use 'this' to refer to member variables of the test class.
|
|
template <typename ACCESSOR_TYPE>
|
|
class DatabaseClientTest : public ::testing::Test {
|
|
public:
|
|
DatabaseClientTest() : zname_("example.org"), qname_("www.example.org"),
|
|
qclass_(RRClass::IN()), qtype_(RRType::A()),
|
|
rrttl_(3600)
|
|
{
|
|
createClient();
|
|
|
|
// set up the commonly used finder.
|
|
DataSourceClient::FindResult zone(client_->findZone(zname_));
|
|
assert(zone.code == result::SUCCESS);
|
|
finder_ = dynamic_pointer_cast<DatabaseClient::Finder>(
|
|
zone.zone_finder);
|
|
|
|
// Test IN/A RDATA to be added in update tests. Intentionally using
|
|
// different data than the initial data configured in the MockAccessor.
|
|
rrset_.reset(new RRset(qname_, qclass_, qtype_, rrttl_));
|
|
rrset_->addRdata(rdata::createRdata(rrset_->getType(),
|
|
rrset_->getClass(), "192.0.2.2"));
|
|
soa_.reset(new RRset(zname_, qclass_, RRType::SOA(), rrttl_));
|
|
soa_->addRdata(rdata::createRdata(soa_->getType(), soa_->getClass(),
|
|
"ns1.example.org. admin.example.org. "
|
|
"1234 3600 1800 2419200 7200"));
|
|
|
|
// And its RRSIG. Also different from the configured one.
|
|
rrsigset_.reset(new RRset(qname_, qclass_, RRType::RRSIG(),
|
|
rrttl_));
|
|
rrsigset_->addRdata(rdata::createRdata(rrsigset_->getType(),
|
|
rrsigset_->getClass(),
|
|
"A 5 3 0 20000101000000 "
|
|
"20000201000000 0 example.org. "
|
|
"FAKEFAKEFAKE"));
|
|
}
|
|
|
|
/*
|
|
* We initialize the client from a function, so we can call it multiple
|
|
* times per test.
|
|
*/
|
|
void createClient() {
|
|
// To make sure we always have empty diffs table at the beginning of
|
|
// each test, we re-install the writable data source here.
|
|
// Note: this is SQLite3 specific and a waste (though otherwise
|
|
// harmless) for other types of data sources. If and when we support
|
|
// more types of data sources in this test framework, we should
|
|
// probably move this to some specialized templated method specific
|
|
// to SQLite3 (or for even a longer term we should add an API to
|
|
// purge the diffs table).
|
|
const char* const install_cmd = INSTALL_PROG " " TEST_DATA_DIR
|
|
"/rwtest.sqlite3 " TEST_DATA_BUILDDIR
|
|
"/rwtest.sqlite3.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);
|
|
}
|
|
|
|
current_accessor_ = new ACCESSOR_TYPE();
|
|
is_mock_ = (dynamic_cast<MockAccessor*>(current_accessor_) != NULL);
|
|
client_.reset(new DatabaseClient(qclass_,
|
|
boost::shared_ptr<ACCESSOR_TYPE>(
|
|
current_accessor_)));
|
|
}
|
|
|
|
/**
|
|
* Check the zone finder is a valid one and references the zone ID and
|
|
* database available here.
|
|
*/
|
|
void checkZoneFinder(const DataSourceClient::FindResult& zone) {
|
|
ASSERT_NE(ZoneFinderPtr(), zone.zone_finder) << "No zone finder";
|
|
boost::shared_ptr<DatabaseClient::Finder> finder(
|
|
dynamic_pointer_cast<DatabaseClient::Finder>(zone.zone_finder));
|
|
ASSERT_NE(boost::shared_ptr<DatabaseClient::Finder>(), finder) <<
|
|
"Wrong type of finder";
|
|
if (is_mock_) {
|
|
EXPECT_EQ(READONLY_ZONE_ID, finder->zone_id());
|
|
}
|
|
EXPECT_EQ(current_accessor_, &finder->getAccessor());
|
|
}
|
|
|
|
boost::shared_ptr<DatabaseClient::Finder> getFinder() {
|
|
DataSourceClient::FindResult zone(client_->findZone(zname_));
|
|
EXPECT_EQ(result::SUCCESS, zone.code);
|
|
boost::shared_ptr<DatabaseClient::Finder> finder(
|
|
dynamic_pointer_cast<DatabaseClient::Finder>(zone.zone_finder));
|
|
if (is_mock_) {
|
|
EXPECT_EQ(READONLY_ZONE_ID, finder->zone_id());
|
|
}
|
|
|
|
return (finder);
|
|
}
|
|
|
|
// Helper methods for update tests
|
|
bool isRollbacked(bool expected = false) const {
|
|
if (is_mock_) {
|
|
const MockAccessor& mock_accessor =
|
|
dynamic_cast<const MockAccessor&>(*update_accessor_);
|
|
return (mock_accessor.isRollbacked());
|
|
} else {
|
|
return (expected);
|
|
}
|
|
}
|
|
|
|
void checkLastAdded(const char* const expected[]) const {
|
|
if (is_mock_) {
|
|
const MockAccessor* mock_accessor =
|
|
dynamic_cast<const MockAccessor*>(current_accessor_);
|
|
for (int i = 0; i < DatabaseAccessor::ADD_COLUMN_COUNT; ++i) {
|
|
EXPECT_EQ(expected[i],
|
|
mock_accessor->getLatestClone()->getLastAdded()[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
void setUpdateAccessor() {
|
|
if (is_mock_) {
|
|
const MockAccessor* mock_accessor =
|
|
dynamic_cast<const MockAccessor*>(current_accessor_);
|
|
update_accessor_ = mock_accessor->getLatestClone();
|
|
}
|
|
}
|
|
|
|
void checkJournal(const vector<JournalEntry>& expected) {
|
|
if (is_mock_) {
|
|
const MockAccessor* mock_accessor =
|
|
dynamic_cast<const MockAccessor*>(current_accessor_);
|
|
mock_accessor->checkJournal(expected);
|
|
} else {
|
|
// For other generic databases, retrieve the diff using the
|
|
// reader class and compare the resulting sequence of RRset.
|
|
// For simplicity we only consider the case where the expected
|
|
// sequence is not empty.
|
|
ASSERT_FALSE(expected.empty());
|
|
const Name zone_name(expected.front().
|
|
data_[DatabaseAccessor::DIFF_NAME]);
|
|
ZoneJournalReaderPtr jnl_reader =
|
|
client_->getJournalReader(zone_name,
|
|
expected.front().serial_,
|
|
expected.back().serial_).second;
|
|
ASSERT_TRUE(jnl_reader);
|
|
ConstRRsetPtr rrset;
|
|
vector<JournalEntry>::const_iterator it = expected.begin();
|
|
for (rrset = jnl_reader->getNextDiff();
|
|
rrset && it != expected.end();
|
|
rrset = jnl_reader->getNextDiff(), ++it) {
|
|
typedef DatabaseAccessor Accessor;
|
|
RRsetPtr expected_rrset(
|
|
new RRset(Name((*it).data_[Accessor::DIFF_NAME]),
|
|
qclass_,
|
|
RRType((*it).data_[Accessor::DIFF_TYPE]),
|
|
RRTTL((*it).data_[Accessor::DIFF_TTL])));
|
|
expected_rrset->addRdata(
|
|
rdata::createRdata(expected_rrset->getType(),
|
|
expected_rrset->getClass(),
|
|
(*it).data_[Accessor::DIFF_RDATA]));
|
|
isc::testutils::rrsetCheck(expected_rrset, rrset);
|
|
}
|
|
// We should have examined all entries of both expected and
|
|
// actual data.
|
|
EXPECT_TRUE(it == expected.end());
|
|
ASSERT_FALSE(rrset);
|
|
}
|
|
}
|
|
|
|
// Some tests only work for MockAccessor. We remember whether our accessor
|
|
// is of that type.
|
|
bool is_mock_;
|
|
|
|
// Will be deleted by client_, just keep the current value for comparison.
|
|
ACCESSOR_TYPE* current_accessor_;
|
|
boost::shared_ptr<DatabaseClient> client_;
|
|
const std::string database_name_;
|
|
|
|
// The zone finder of the test zone commonly used in various tests.
|
|
boost::shared_ptr<DatabaseClient::Finder> finder_;
|
|
|
|
// Some shortcut variables for commonly used test parameters
|
|
const Name zname_; // the zone name stored in the test data source
|
|
const Name qname_; // commonly used name to be found
|
|
const RRClass qclass_; // commonly used RR class used with qname
|
|
const RRType qtype_; // commonly used RR type used with qname
|
|
const RRTTL rrttl_; // commonly used RR TTL
|
|
RRsetPtr rrset_; // for adding/deleting an RRset
|
|
RRsetPtr rrsigset_; // for adding/deleting an RRset
|
|
RRsetPtr soa_; // for adding/deleting an RRset
|
|
|
|
// update related objects to be tested
|
|
ZoneUpdaterPtr updater_;
|
|
boost::shared_ptr<const DatabaseAccessor> update_accessor_;
|
|
|
|
// placeholders
|
|
const std::vector<std::string> empty_rdatas_; // for NXRRSET/NXDOMAIN
|
|
std::vector<std::string> expected_rdatas_;
|
|
std::vector<std::string> expected_sig_rdatas_;
|
|
};
|
|
|
|
class TestSQLite3Accessor : public SQLite3Accessor {
|
|
public:
|
|
TestSQLite3Accessor() : SQLite3Accessor(
|
|
TEST_DATA_BUILDDIR "/rwtest.sqlite3.copied", "IN")
|
|
{
|
|
startUpdateZone("example.org.", true);
|
|
string columns[ADD_COLUMN_COUNT];
|
|
for (int i = 0; TEST_RECORDS[i][0] != NULL; ++i) {
|
|
columns[ADD_NAME] = TEST_RECORDS[i][0];
|
|
columns[ADD_REV_NAME] = Name(columns[ADD_NAME]).reverse().toText();
|
|
columns[ADD_TYPE] = TEST_RECORDS[i][1];
|
|
columns[ADD_TTL] = TEST_RECORDS[i][2];
|
|
columns[ADD_SIGTYPE] = TEST_RECORDS[i][3];
|
|
columns[ADD_RDATA] = TEST_RECORDS[i][4];
|
|
|
|
addRecordToZone(columns);
|
|
}
|
|
commit();
|
|
}
|
|
};
|
|
|
|
// The following two lines instantiate test cases with concrete accessor
|
|
// classes to be tested.
|
|
// XXX: clang++ installed on our FreeBSD buildbot cannot complete compiling
|
|
// this file, seemingly due to the size of the code. We'll consider more
|
|
// complete workaround, but for a short term workaround we'll reduce the
|
|
// number of tested accessor classes (thus reducing the amount of code
|
|
// to be compiled) for this particular environment.
|
|
#if defined(__clang__) && defined(__FreeBSD__)
|
|
typedef ::testing::Types<MockAccessor> TestAccessorTypes;
|
|
#else
|
|
typedef ::testing::Types<MockAccessor, TestSQLite3Accessor> TestAccessorTypes;
|
|
#endif
|
|
|
|
TYPED_TEST_CASE(DatabaseClientTest, TestAccessorTypes);
|
|
|
|
// In some cases the entire test fixture is for the mock accessor only.
|
|
// We use the usual TEST_F for them with the corresponding specialized class
|
|
// to make the code simpler.
|
|
typedef DatabaseClientTest<MockAccessor> MockDatabaseClientTest;
|
|
|
|
TYPED_TEST(DatabaseClientTest, zoneNotFound) {
|
|
DataSourceClient::FindResult zone(
|
|
this->client_->findZone(Name("example.com")));
|
|
EXPECT_EQ(result::NOTFOUND, zone.code);
|
|
}
|
|
|
|
TYPED_TEST(DatabaseClientTest, exactZone) {
|
|
DataSourceClient::FindResult zone(
|
|
this->client_->findZone(Name("example.org")));
|
|
EXPECT_EQ(result::SUCCESS, zone.code);
|
|
this->checkZoneFinder(zone);
|
|
}
|
|
|
|
TYPED_TEST(DatabaseClientTest, superZone) {
|
|
DataSourceClient::FindResult zone(this->client_->findZone(Name(
|
|
"sub.example.org")));
|
|
EXPECT_EQ(result::PARTIALMATCH, zone.code);
|
|
this->checkZoneFinder(zone);
|
|
}
|
|
|
|
// This test doesn't depend on derived accessor class, so we use TEST().
|
|
TEST(GenericDatabaseClientTest, noAccessorException) {
|
|
// We need a dummy variable here; some compiler would regard it a mere
|
|
// declaration instead of an instantiation and make the test fail.
|
|
EXPECT_THROW(DatabaseClient dummy(RRClass::IN(),
|
|
boost::shared_ptr<DatabaseAccessor>()),
|
|
isc::InvalidParameter);
|
|
}
|
|
|
|
// If the zone doesn't exist, exception is thrown
|
|
TYPED_TEST(DatabaseClientTest, noZoneIterator) {
|
|
EXPECT_THROW(this->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(GenericDatabaseClientTest, noZoneNotImplementedIterator) {
|
|
EXPECT_THROW(DatabaseClient(RRClass::IN(),
|
|
boost::shared_ptr<DatabaseAccessor>(
|
|
new NopAccessor())).getIterator(
|
|
Name("example.com")),
|
|
DataSourceError);
|
|
}
|
|
|
|
TEST(GenericDatabaseClientTest, notImplementedIterator) {
|
|
EXPECT_THROW(DatabaseClient(RRClass::IN(),
|
|
boost::shared_ptr<DatabaseAccessor>(
|
|
new NopAccessor())).getIterator(Name("example.org")),
|
|
isc::NotImplemented);
|
|
}
|
|
|
|
// Pretend a bug in the connection and pass NULL as the context
|
|
// Should not crash, but gracefully throw. Works for the mock accessor only.
|
|
TEST_F(MockDatabaseClientTest, nullIteratorContext) {
|
|
EXPECT_THROW(this->client_->getIterator(Name("null.example.org")),
|
|
isc::Unexpected);
|
|
}
|
|
|
|
// It doesn't crash or anything if the zone is completely empty.
|
|
// Works for the mock accessor only.
|
|
TEST_F(MockDatabaseClientTest, emptyIterator) {
|
|
ZoneIteratorPtr it(this->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);
|
|
}
|
|
|
|
// checks if the given rrset matches the
|
|
// given name, class, type and rdatas
|
|
void
|
|
checkRRset(isc::dns::ConstRRsetPtr rrset,
|
|
const isc::dns::Name& name,
|
|
const isc::dns::RRClass& rrclass,
|
|
const isc::dns::RRType& rrtype,
|
|
const isc::dns::RRTTL& rrttl,
|
|
const std::vector<std::string>& rdatas) {
|
|
isc::dns::RRsetPtr expected_rrset(
|
|
new isc::dns::RRset(name, rrclass, rrtype, rrttl));
|
|
for (unsigned int i = 0; i < rdatas.size(); ++i) {
|
|
expected_rrset->addRdata(
|
|
isc::dns::rdata::createRdata(rrtype, rrclass,
|
|
rdatas[i]));
|
|
}
|
|
isc::testutils::rrsetCheck(expected_rrset, rrset);
|
|
}
|
|
|
|
// Iterate through a zone
|
|
TYPED_TEST(DatabaseClientTest, iterator) {
|
|
ZoneIteratorPtr it(this->client_->getIterator(Name("example.org")));
|
|
ConstRRsetPtr rrset(it->getNextRRset());
|
|
ASSERT_NE(ConstRRsetPtr(), rrset);
|
|
|
|
// The first name should be the zone origin.
|
|
EXPECT_EQ(this->zname_, rrset->getName());
|
|
|
|
// The rest of the checks work only for the mock accessor.
|
|
if (!this->is_mock_) {
|
|
return;
|
|
}
|
|
|
|
this->expected_rdatas_.clear();
|
|
this->expected_rdatas_.push_back("192.0.2.1");
|
|
checkRRset(rrset, Name("example.org"), this->qclass_, RRType::A(),
|
|
this->rrttl_, this->expected_rdatas_);
|
|
|
|
rrset = it->getNextRRset();
|
|
this->expected_rdatas_.clear();
|
|
this->expected_rdatas_.push_back("ns1.example.org. admin.example.org. "
|
|
"1234 3600 1800 2419200 7200");
|
|
checkRRset(rrset, Name("example.org"), this->qclass_, RRType::SOA(),
|
|
this->rrttl_, this->expected_rdatas_);
|
|
|
|
rrset = it->getNextRRset();
|
|
this->expected_rdatas_.clear();
|
|
this->expected_rdatas_.push_back("192.0.2.1");
|
|
this->expected_rdatas_.push_back("192.0.2.2");
|
|
checkRRset(rrset, Name("x.example.org"), this->qclass_, RRType::A(),
|
|
RRTTL(300), this->expected_rdatas_);
|
|
|
|
rrset = it->getNextRRset();
|
|
this->expected_rdatas_.clear();
|
|
this->expected_rdatas_.push_back("2001:db8::1");
|
|
this->expected_rdatas_.push_back("2001:db8::2");
|
|
checkRRset(rrset, Name("x.example.org"), this->qclass_, RRType::AAAA(),
|
|
RRTTL(300), this->expected_rdatas_);
|
|
|
|
rrset = it->getNextRRset();
|
|
ASSERT_NE(ConstRRsetPtr(), rrset);
|
|
this->expected_rdatas_.clear();
|
|
this->expected_rdatas_.push_back("192.0.2.1");
|
|
this->expected_rdatas_.push_back("192.0.2.2");
|
|
checkRRset(rrset, Name("ttldiff.example.org"), this->qclass_, RRType::A(),
|
|
RRTTL(300), this->expected_rdatas_);
|
|
|
|
EXPECT_EQ(ConstRRsetPtr(), it->getNextRRset());
|
|
}
|
|
|
|
// This has inconsistent TTL in the set (the rest, like nonsense in
|
|
// the data is handled in rdata itself). Works for the mock accessor only.
|
|
TEST_F(MockDatabaseClientTest, badIterator) {
|
|
// It should not throw, but get the lowest one of them
|
|
ZoneIteratorPtr it(this->client_->getIterator(Name("bad.example.org")));
|
|
EXPECT_EQ(it->getNextRRset()->getTTL(), isc::dns::RRTTL(300));
|
|
}
|
|
|
|
TYPED_TEST(DatabaseClientTest, getSOAFromIterator) {
|
|
vector<string> soa_data;
|
|
soa_data.push_back("ns1.example.org. admin.example.org. "
|
|
"1234 3600 1800 2419200 7200");
|
|
|
|
ZoneIteratorPtr it(this->client_->getIterator(this->zname_));
|
|
ASSERT_TRUE(it);
|
|
checkRRset(it->getSOA(), this->zname_, this->qclass_, RRType::SOA(),
|
|
this->rrttl_, soa_data);
|
|
|
|
// Iterate over the zone until we find an SOA. Although there's a broken
|
|
// RDATA that would trigger an exception in getNextRRset(), we should
|
|
// reach the SOA as the sequence should be sorted and the SOA is at
|
|
// the origin name (which has no bogus data).
|
|
ConstRRsetPtr rrset;
|
|
while ((rrset = it->getNextRRset()) != ConstRRsetPtr() &&
|
|
rrset->getType() != RRType::SOA()) {
|
|
;
|
|
}
|
|
ASSERT_TRUE(rrset);
|
|
// It should be identical to the result of getSOA().
|
|
isc::testutils::rrsetCheck(it->getSOA(), rrset);
|
|
}
|
|
|
|
TYPED_TEST(DatabaseClientTest, noSOAFromIterator) {
|
|
// First, empty the zone.
|
|
this->updater_ = this->client_->getUpdater(this->zname_, true);
|
|
this->updater_->commit();
|
|
|
|
// Then getSOA() should return NULL.
|
|
ZoneIteratorPtr it(this->client_->getIterator(this->zname_));
|
|
ASSERT_TRUE(it);
|
|
EXPECT_FALSE(it->getSOA());
|
|
}
|
|
|
|
TYPED_TEST(DatabaseClientTest, iterateThenUpdate) {
|
|
ZoneIteratorPtr it(this->client_->getIterator(this->zname_));
|
|
ASSERT_TRUE(it);
|
|
|
|
// Try to empty the zone after getting the iterator. Depending on the
|
|
// underlying data source, it may result in an exception due to the
|
|
// transaction for the iterator. In either case the integrity of the
|
|
// iterator result should be reserved.
|
|
try {
|
|
this->updater_ = this->client_->getUpdater(this->zname_, true);
|
|
this->updater_->commit();
|
|
|
|
// Confirm at least it doesn't contain any SOA
|
|
EXPECT_EQ(ZoneFinder::NXDOMAIN,
|
|
this->getFinder()->find(this->zname_, RRType::SOA())->code);
|
|
} catch (const DataSourceError&) {}
|
|
|
|
ConstRRsetPtr rrset;
|
|
while ((rrset = it->getNextRRset()) != ConstRRsetPtr() &&
|
|
rrset->getType() != RRType::SOA()) {
|
|
;
|
|
}
|
|
ASSERT_TRUE(rrset);
|
|
// It should be identical to the result of getSOA().
|
|
isc::testutils::rrsetCheck(it->getSOA(), rrset);
|
|
}
|
|
|
|
TYPED_TEST(DatabaseClientTest, updateThenIterateThenUpdate) {
|
|
// First clear the zone.
|
|
this->updater_ = this->client_->getUpdater(this->zname_, true);
|
|
this->updater_->commit();
|
|
|
|
// Then iterate over it. It should immediately reach the end, at which
|
|
// point the transaction should be committed.
|
|
ZoneIteratorPtr it(this->client_->getIterator(this->zname_));
|
|
ASSERT_TRUE(it);
|
|
EXPECT_FALSE(it->getNextRRset());
|
|
|
|
// So another update attempt should succeed, too.
|
|
this->updater_ = this->client_->getUpdater(this->zname_, true);
|
|
this->updater_->commit();
|
|
}
|
|
|
|
TYPED_TEST(DatabaseClientTest, updateAfterDeleteIterator) {
|
|
// Similar to the previous case, but we delete the iterator in the
|
|
// middle of zone. The transaction should be canceled (actually no
|
|
// different from commit though) at that point.
|
|
ZoneIteratorPtr it(this->client_->getIterator(this->zname_));
|
|
ASSERT_TRUE(it);
|
|
EXPECT_TRUE(it->getNextRRset());
|
|
it.reset();
|
|
|
|
// So another update attempt should succeed.
|
|
this->updater_ = this->client_->getUpdater(this->zname_, true);
|
|
this->updater_->commit();
|
|
}
|
|
|
|
void
|
|
doFindTest(ZoneFinder& finder,
|
|
const isc::dns::Name& name,
|
|
const isc::dns::RRType& type,
|
|
const isc::dns::RRType& expected_type,
|
|
const isc::dns::RRTTL expected_ttl,
|
|
ZoneFinder::Result expected_result,
|
|
const std::vector<std::string>& expected_rdatas,
|
|
const std::vector<std::string>& expected_sig_rdatas,
|
|
ZoneFinder::FindResultFlags expected_flags =
|
|
ZoneFinder::RESULT_DEFAULT,
|
|
const isc::dns::Name& expected_name = isc::dns::Name::ROOT_NAME(),
|
|
const ZoneFinder::FindOptions options = ZoneFinder::FIND_DEFAULT)
|
|
{
|
|
SCOPED_TRACE("doFindTest " + name.toText() + " " + type.toText());
|
|
ConstZoneFinderContextPtr result = finder.find(name, type, options);
|
|
ASSERT_EQ(expected_result, result->code) << name << " " << type;
|
|
EXPECT_EQ((expected_flags & ZoneFinder::RESULT_WILDCARD) != 0,
|
|
result->isWildcard());
|
|
EXPECT_EQ((expected_flags & ZoneFinder::RESULT_NSEC_SIGNED) != 0,
|
|
result->isNSECSigned());
|
|
EXPECT_EQ((expected_flags & ZoneFinder::RESULT_NSEC3_SIGNED) != 0,
|
|
result->isNSEC3Signed());
|
|
if (!expected_rdatas.empty() && result->rrset) {
|
|
checkRRset(result->rrset, expected_name != Name(".") ? expected_name :
|
|
name, finder.getClass(), expected_type, expected_ttl,
|
|
expected_rdatas);
|
|
|
|
if (!expected_sig_rdatas.empty() && result->rrset->getRRsig()) {
|
|
checkRRset(result->rrset->getRRsig(), expected_name != Name(".") ?
|
|
expected_name : name, finder.getClass(),
|
|
isc::dns::RRType::RRSIG(), expected_ttl,
|
|
expected_sig_rdatas);
|
|
} else if (expected_sig_rdatas.empty()) {
|
|
EXPECT_EQ(isc::dns::RRsetPtr(), result->rrset->getRRsig());
|
|
} else {
|
|
ADD_FAILURE() << "Missing RRSIG";
|
|
}
|
|
} else if (expected_rdatas.empty()) {
|
|
EXPECT_EQ(isc::dns::RRsetPtr(), result->rrset);
|
|
} else {
|
|
ADD_FAILURE() << "Missing result";
|
|
}
|
|
}
|
|
|
|
void
|
|
doFindAllTestResult(ZoneFinder& finder, const isc::dns::Name& name,
|
|
ZoneFinder::Result expected_result,
|
|
const isc::dns::RRType expected_type,
|
|
std::vector<std::string> expected_rdata,
|
|
const isc::dns::Name& expected_name =
|
|
isc::dns::Name::ROOT_NAME(),
|
|
const ZoneFinder::FindOptions options =
|
|
ZoneFinder::FIND_DEFAULT)
|
|
{
|
|
SCOPED_TRACE("All test for " + name.toText());
|
|
std::vector<ConstRRsetPtr> target;
|
|
ConstZoneFinderContextPtr result(finder.findAll(name, target, options));
|
|
EXPECT_TRUE(target.empty());
|
|
EXPECT_EQ(expected_result, result->code);
|
|
EXPECT_EQ(expected_type, result->rrset->getType());
|
|
RdataIteratorPtr it(result->rrset->getRdataIterator());
|
|
std::vector<std::string> rdata;
|
|
while (!it->isLast()) {
|
|
rdata.push_back(it->getCurrent().toText());
|
|
it->next();
|
|
}
|
|
std::sort(rdata.begin(), rdata.end());
|
|
std::sort(expected_rdata.begin(), expected_rdata.end());
|
|
ASSERT_EQ(expected_rdata.size(), rdata.size());
|
|
for (size_t i(0); i < expected_rdata.size(); ++ i) {
|
|
EXPECT_EQ(expected_rdata[i], rdata[i]);
|
|
}
|
|
EXPECT_TRUE(expected_rdata == rdata);
|
|
EXPECT_EQ(expected_name == isc::dns::Name::ROOT_NAME() ? name :
|
|
expected_name, result->rrset->getName());
|
|
}
|
|
|
|
// When asking for an RRset where RRs somehow have different TTLs, it should
|
|
// convert to the lowest one.
|
|
TEST_F(MockDatabaseClientTest, ttldiff) {
|
|
ZoneIteratorPtr it(this->client_->getIterator(Name("example.org")));
|
|
// Walk through the full iterator, we should see 1 rrset with name
|
|
// ttldiff1.example.org., and two rdatas. Same for ttldiff2
|
|
Name name("ttldiff.example.org.");
|
|
bool found = false;
|
|
//bool found2 = false;
|
|
ConstRRsetPtr rrset = it->getNextRRset();
|
|
while(rrset != ConstRRsetPtr()) {
|
|
if (rrset->getName() == name) {
|
|
ASSERT_FALSE(found);
|
|
ASSERT_EQ(2, rrset->getRdataCount());
|
|
ASSERT_EQ(RRTTL(300), rrset->getTTL());
|
|
found = true;
|
|
}
|
|
rrset = it->getNextRRset();
|
|
}
|
|
ASSERT_TRUE(found);
|
|
}
|
|
|
|
// Unless we ask for individual RRs in our iterator request. In that case
|
|
// every RR should go into its own 'rrset'
|
|
TEST_F(MockDatabaseClientTest, ttldiff_separate_rrs) {
|
|
ZoneIteratorPtr it(this->client_->getIterator(Name("example.org"), true));
|
|
|
|
// Walk through the full iterator, we should see 1 rrset with name
|
|
// ttldiff1.example.org., and two rdatas. Same for ttldiff2
|
|
Name name("ttldiff.example.org.");
|
|
int found1 = false;
|
|
int found2 = false;
|
|
ConstRRsetPtr rrset = it->getNextRRset();
|
|
while(rrset != ConstRRsetPtr()) {
|
|
if (rrset->getName() == name) {
|
|
ASSERT_EQ(1, rrset->getRdataCount());
|
|
// We should find 1 'rrset' with TTL 300 and one with TTL 600
|
|
if (rrset->getTTL() == RRTTL(300)) {
|
|
ASSERT_FALSE(found1);
|
|
found1 = true;
|
|
} else if (rrset->getTTL() == RRTTL(600)) {
|
|
ASSERT_FALSE(found2);
|
|
found2 = true;
|
|
} else {
|
|
FAIL() << "Found unexpected TTL: " <<
|
|
rrset->getTTL().toText();
|
|
}
|
|
}
|
|
rrset = it->getNextRRset();
|
|
}
|
|
ASSERT_TRUE(found1);
|
|
ASSERT_TRUE(found2);
|
|
}
|
|
|
|
TYPED_TEST(DatabaseClientTest, find) {
|
|
boost::shared_ptr<DatabaseClient::Finder> finder(this->getFinder());
|
|
|
|
this->expected_rdatas_.clear();
|
|
this->expected_sig_rdatas_.clear();
|
|
this->expected_rdatas_.push_back("192.0.2.1");
|
|
doFindTest(*finder, isc::dns::Name("www.example.org."),
|
|
this->qtype_, this->qtype_, this->rrttl_, ZoneFinder::SUCCESS,
|
|
this->expected_rdatas_, this->expected_sig_rdatas_);
|
|
|
|
this->expected_rdatas_.clear();
|
|
this->expected_sig_rdatas_.clear();
|
|
this->expected_rdatas_.push_back("192.0.2.1");
|
|
this->expected_rdatas_.push_back("192.0.2.2");
|
|
doFindTest(*finder, isc::dns::Name("www2.example.org."),
|
|
this->qtype_, this->qtype_, this->rrttl_, ZoneFinder::SUCCESS,
|
|
this->expected_rdatas_, this->expected_sig_rdatas_);
|
|
|
|
this->expected_rdatas_.clear();
|
|
this->expected_sig_rdatas_.clear();
|
|
this->expected_rdatas_.push_back("2001:db8::1");
|
|
this->expected_rdatas_.push_back("2001:db8::2");
|
|
doFindTest(*finder, isc::dns::Name("www.example.org."),
|
|
isc::dns::RRType::AAAA(), isc::dns::RRType::AAAA(),
|
|
this->rrttl_,
|
|
ZoneFinder::SUCCESS,
|
|
this->expected_rdatas_, this->expected_sig_rdatas_);
|
|
|
|
this->expected_rdatas_.clear();
|
|
this->expected_sig_rdatas_.clear();
|
|
doFindTest(*finder, isc::dns::Name("www.example.org."),
|
|
isc::dns::RRType::TXT(), isc::dns::RRType::TXT(),
|
|
this->rrttl_,
|
|
ZoneFinder::NXRRSET,
|
|
this->expected_rdatas_, this->expected_sig_rdatas_);
|
|
|
|
this->expected_rdatas_.clear();
|
|
this->expected_sig_rdatas_.clear();
|
|
this->expected_rdatas_.push_back("www.example.org.");
|
|
doFindTest(*finder, isc::dns::Name("cname.example.org."),
|
|
this->qtype_, isc::dns::RRType::CNAME(), this->rrttl_,
|
|
ZoneFinder::CNAME, this->expected_rdatas_,
|
|
this->expected_sig_rdatas_);
|
|
|
|
this->expected_rdatas_.clear();
|
|
this->expected_sig_rdatas_.clear();
|
|
this->expected_rdatas_.push_back("www.example.org.");
|
|
doFindTest(*finder, isc::dns::Name("cname.example.org."),
|
|
isc::dns::RRType::CNAME(), isc::dns::RRType::CNAME(),
|
|
this->rrttl_, ZoneFinder::SUCCESS, this->expected_rdatas_,
|
|
this->expected_sig_rdatas_);
|
|
|
|
this->expected_rdatas_.clear();
|
|
this->expected_sig_rdatas_.clear();
|
|
doFindTest(*finder, isc::dns::Name("doesnotexist.example.org."),
|
|
this->qtype_, this->qtype_, this->rrttl_, ZoneFinder::NXDOMAIN,
|
|
this->expected_rdatas_, this->expected_sig_rdatas_);
|
|
|
|
this->expected_rdatas_.clear();
|
|
this->expected_sig_rdatas_.clear();
|
|
this->expected_rdatas_.push_back("192.0.2.1");
|
|
this->expected_sig_rdatas_.push_back("A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
|
|
this->expected_sig_rdatas_.push_back("A 5 3 3600 20000101000000 20000201000000 12346 example.org. FAKEFAKEFAKE");
|
|
doFindTest(*finder, isc::dns::Name("signed1.example.org."),
|
|
this->qtype_, this->qtype_, this->rrttl_, ZoneFinder::SUCCESS,
|
|
this->expected_rdatas_, this->expected_sig_rdatas_);
|
|
|
|
this->expected_rdatas_.clear();
|
|
this->expected_sig_rdatas_.clear();
|
|
this->expected_rdatas_.push_back("2001:db8::1");
|
|
this->expected_rdatas_.push_back("2001:db8::2");
|
|
this->expected_sig_rdatas_.push_back("AAAA 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
|
|
doFindTest(*finder, isc::dns::Name("signed1.example.org."),
|
|
isc::dns::RRType::AAAA(), isc::dns::RRType::AAAA(),
|
|
this->rrttl_, ZoneFinder::SUCCESS, this->expected_rdatas_,
|
|
this->expected_sig_rdatas_);
|
|
|
|
this->expected_rdatas_.clear();
|
|
this->expected_sig_rdatas_.clear();
|
|
doFindTest(*finder, isc::dns::Name("signed1.example.org."),
|
|
isc::dns::RRType::TXT(), isc::dns::RRType::TXT(), this->rrttl_,
|
|
ZoneFinder::NXRRSET, this->expected_rdatas_,
|
|
this->expected_sig_rdatas_);
|
|
|
|
this->expected_rdatas_.clear();
|
|
this->expected_sig_rdatas_.clear();
|
|
this->expected_rdatas_.push_back("www.example.org.");
|
|
this->expected_sig_rdatas_.push_back("CNAME 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
|
|
doFindTest(*finder, isc::dns::Name("signedcname1.example.org."),
|
|
this->qtype_, isc::dns::RRType::CNAME(), this->rrttl_,
|
|
ZoneFinder::CNAME, this->expected_rdatas_,
|
|
this->expected_sig_rdatas_);
|
|
|
|
this->expected_rdatas_.clear();
|
|
this->expected_sig_rdatas_.clear();
|
|
this->expected_rdatas_.push_back("192.0.2.1");
|
|
this->expected_sig_rdatas_.push_back("A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
|
|
this->expected_sig_rdatas_.push_back("A 5 3 3600 20000101000000 20000201000000 12346 example.org. FAKEFAKEFAKE");
|
|
doFindTest(*finder, isc::dns::Name("signed2.example.org."),
|
|
this->qtype_, this->qtype_, this->rrttl_, ZoneFinder::SUCCESS,
|
|
this->expected_rdatas_, this->expected_sig_rdatas_);
|
|
|
|
this->expected_rdatas_.clear();
|
|
this->expected_sig_rdatas_.clear();
|
|
this->expected_rdatas_.push_back("2001:db8::2");
|
|
this->expected_rdatas_.push_back("2001:db8::1");
|
|
this->expected_sig_rdatas_.push_back("AAAA 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
|
|
doFindTest(*finder, isc::dns::Name("signed2.example.org."),
|
|
isc::dns::RRType::AAAA(), isc::dns::RRType::AAAA(),
|
|
this->rrttl_, ZoneFinder::SUCCESS, this->expected_rdatas_,
|
|
this->expected_sig_rdatas_);
|
|
|
|
this->expected_rdatas_.clear();
|
|
this->expected_sig_rdatas_.clear();
|
|
doFindTest(*finder, isc::dns::Name("signed2.example.org."),
|
|
isc::dns::RRType::TXT(), isc::dns::RRType::TXT(), this->rrttl_,
|
|
ZoneFinder::NXRRSET, this->expected_rdatas_,
|
|
this->expected_sig_rdatas_);
|
|
|
|
this->expected_rdatas_.clear();
|
|
this->expected_sig_rdatas_.clear();
|
|
this->expected_rdatas_.push_back("www.example.org.");
|
|
this->expected_sig_rdatas_.push_back("CNAME 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
|
|
doFindTest(*finder, isc::dns::Name("signedcname2.example.org."),
|
|
this->qtype_, isc::dns::RRType::CNAME(), this->rrttl_,
|
|
ZoneFinder::CNAME, this->expected_rdatas_,
|
|
this->expected_sig_rdatas_);
|
|
|
|
this->expected_rdatas_.clear();
|
|
this->expected_sig_rdatas_.clear();
|
|
this->expected_rdatas_.push_back("192.0.2.1");
|
|
this->expected_sig_rdatas_.push_back("A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
|
|
doFindTest(*finder, isc::dns::Name("acnamesig1.example.org."),
|
|
this->qtype_, this->qtype_, this->rrttl_, ZoneFinder::SUCCESS,
|
|
this->expected_rdatas_, this->expected_sig_rdatas_);
|
|
|
|
this->expected_rdatas_.clear();
|
|
this->expected_sig_rdatas_.clear();
|
|
this->expected_rdatas_.push_back("192.0.2.1");
|
|
this->expected_sig_rdatas_.push_back("A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
|
|
doFindTest(*finder, isc::dns::Name("acnamesig2.example.org."),
|
|
this->qtype_, this->qtype_, this->rrttl_, ZoneFinder::SUCCESS,
|
|
this->expected_rdatas_, this->expected_sig_rdatas_);
|
|
|
|
this->expected_rdatas_.clear();
|
|
this->expected_sig_rdatas_.clear();
|
|
this->expected_rdatas_.push_back("192.0.2.1");
|
|
this->expected_sig_rdatas_.push_back("A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
|
|
doFindTest(*finder, isc::dns::Name("acnamesig3.example.org."),
|
|
this->qtype_, this->qtype_, this->rrttl_, ZoneFinder::SUCCESS,
|
|
this->expected_rdatas_, this->expected_sig_rdatas_);
|
|
|
|
this->expected_rdatas_.clear();
|
|
this->expected_sig_rdatas_.clear();
|
|
this->expected_rdatas_.push_back("192.0.2.1");
|
|
this->expected_rdatas_.push_back("192.0.2.2");
|
|
doFindTest(*finder, isc::dns::Name("ttldiff1.example.org."),
|
|
this->qtype_, this->qtype_, isc::dns::RRTTL(360),
|
|
ZoneFinder::SUCCESS, this->expected_rdatas_,
|
|
this->expected_sig_rdatas_);
|
|
|
|
this->expected_rdatas_.clear();
|
|
this->expected_sig_rdatas_.clear();
|
|
this->expected_rdatas_.push_back("192.0.2.1");
|
|
this->expected_rdatas_.push_back("192.0.2.2");
|
|
doFindTest(*finder, isc::dns::Name("ttldiff2.example.org."),
|
|
this->qtype_, this->qtype_, isc::dns::RRTTL(360),
|
|
ZoneFinder::SUCCESS, this->expected_rdatas_,
|
|
this->expected_sig_rdatas_);
|
|
|
|
EXPECT_THROW(finder->find(isc::dns::Name("badcname1.example.org."),
|
|
this->qtype_,
|
|
ZoneFinder::FIND_DEFAULT),
|
|
DataSourceError);
|
|
EXPECT_THROW(finder->find(isc::dns::Name("badcname2.example.org."),
|
|
this->qtype_,
|
|
ZoneFinder::FIND_DEFAULT),
|
|
DataSourceError);
|
|
EXPECT_THROW(finder->find(isc::dns::Name("badcname3.example.org."),
|
|
this->qtype_,
|
|
ZoneFinder::FIND_DEFAULT),
|
|
DataSourceError);
|
|
EXPECT_THROW(finder->find(isc::dns::Name("badrdata.example.org."),
|
|
this->qtype_,
|
|
ZoneFinder::FIND_DEFAULT),
|
|
DataSourceError);
|
|
EXPECT_THROW(finder->find(isc::dns::Name("badtype.example.org."),
|
|
this->qtype_,
|
|
ZoneFinder::FIND_DEFAULT),
|
|
DataSourceError);
|
|
EXPECT_THROW(finder->find(isc::dns::Name("badttl.example.org."),
|
|
this->qtype_,
|
|
ZoneFinder::FIND_DEFAULT),
|
|
DataSourceError);
|
|
EXPECT_THROW(finder->find(isc::dns::Name("badsig.example.org."),
|
|
this->qtype_,
|
|
ZoneFinder::FIND_DEFAULT),
|
|
DataSourceError);
|
|
|
|
// Trigger the hardcoded exceptions and see if find() has cleaned up
|
|
if (this->is_mock_) {
|
|
EXPECT_THROW(finder->find(Name("dsexception.example.org."),
|
|
this->qtype_,
|
|
ZoneFinder::FIND_DEFAULT),
|
|
DataSourceError);
|
|
EXPECT_THROW(finder->find(Name("iscexception.example.org."),
|
|
this->qtype_,
|
|
ZoneFinder::FIND_DEFAULT),
|
|
isc::Exception);
|
|
EXPECT_THROW(finder->find(Name("basicexception.example.org."),
|
|
this->qtype_,
|
|
ZoneFinder::FIND_DEFAULT),
|
|
std::exception);
|
|
EXPECT_THROW(finder->find(Name("dsexception.getnext.example.org"),
|
|
this->qtype_,
|
|
ZoneFinder::FIND_DEFAULT),
|
|
DataSourceError);
|
|
EXPECT_THROW(finder->find(Name("iscexception.getnext.example.org."),
|
|
this->qtype_,
|
|
ZoneFinder::FIND_DEFAULT),
|
|
isc::Exception);
|
|
EXPECT_THROW(finder->find(Name("basicexception.getnext.example.org."),
|
|
this->qtype_,
|
|
ZoneFinder::FIND_DEFAULT),
|
|
std::exception);
|
|
}
|
|
|
|
// This RRSIG has the wrong sigtype field, which should be
|
|
// an error if we decide to keep using that field
|
|
// Right now the field is ignored, so it does not error
|
|
this->expected_rdatas_.clear();
|
|
this->expected_sig_rdatas_.clear();
|
|
this->expected_rdatas_.push_back("192.0.2.1");
|
|
this->expected_sig_rdatas_.push_back("A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
|
|
doFindTest(*finder, isc::dns::Name("badsigtype.example.org."),
|
|
this->qtype_, this->qtype_, this->rrttl_, ZoneFinder::SUCCESS,
|
|
this->expected_rdatas_, this->expected_sig_rdatas_);
|
|
}
|
|
|
|
TYPED_TEST(DatabaseClientTest, findOutOfZone) {
|
|
// If the query name is out-of-zone it should result in NXDOMAIN
|
|
boost::shared_ptr<DatabaseClient::Finder> finder(this->getFinder());
|
|
vector<ConstRRsetPtr> target;
|
|
|
|
// Superdomain
|
|
doFindTest(*finder, Name("org"), this->qtype_, this->qtype_,
|
|
this->rrttl_, ZoneFinder::NXDOMAIN,
|
|
this->empty_rdatas_, this->empty_rdatas_);
|
|
EXPECT_EQ(ZoneFinder::NXDOMAIN, finder->findAll(Name("org"), target)->code);
|
|
// sharing a common ancestor
|
|
doFindTest(*finder, Name("noexample.org"), this->qtype_, this->qtype_,
|
|
this->rrttl_, ZoneFinder::NXDOMAIN,
|
|
this->empty_rdatas_, this->empty_rdatas_);
|
|
EXPECT_EQ(ZoneFinder::NXDOMAIN, finder->findAll(Name("noexample.org"),
|
|
target)->code);
|
|
// totally unrelated domain, smaller number of labels
|
|
doFindTest(*finder, Name("com"), this->qtype_, this->qtype_,
|
|
this->rrttl_, ZoneFinder::NXDOMAIN,
|
|
this->empty_rdatas_, this->empty_rdatas_);
|
|
EXPECT_EQ(ZoneFinder::NXDOMAIN, finder->findAll(Name("com"), target)->code);
|
|
// totally unrelated domain, same number of labels
|
|
doFindTest(*finder, Name("example.com"), this->qtype_, this->qtype_,
|
|
this->rrttl_, ZoneFinder::NXDOMAIN,
|
|
this->empty_rdatas_, this->empty_rdatas_);
|
|
EXPECT_EQ(ZoneFinder::NXDOMAIN, finder->findAll(Name("example.com"),
|
|
target)->code);
|
|
// totally unrelated domain, larger number of labels
|
|
doFindTest(*finder, Name("more.example.com"), this->qtype_, this->qtype_,
|
|
this->rrttl_, ZoneFinder::NXDOMAIN,
|
|
this->empty_rdatas_, this->empty_rdatas_);
|
|
EXPECT_EQ(ZoneFinder::NXDOMAIN, finder->findAll(Name("more.example.com"),
|
|
target)->code);
|
|
}
|
|
|
|
TYPED_TEST(DatabaseClientTest, findDelegation) {
|
|
boost::shared_ptr<DatabaseClient::Finder> finder(this->getFinder());
|
|
|
|
// The apex should not be considered delegation point and we can access
|
|
// data
|
|
this->expected_rdatas_.clear();
|
|
this->expected_sig_rdatas_.clear();
|
|
this->expected_rdatas_.push_back("192.0.2.1");
|
|
doFindTest(*finder, isc::dns::Name("example.org."),
|
|
this->qtype_, this->qtype_,
|
|
this->rrttl_, ZoneFinder::SUCCESS, this->expected_rdatas_,
|
|
this->expected_sig_rdatas_);
|
|
|
|
this->expected_rdatas_.clear();
|
|
this->expected_rdatas_.push_back("ns.example.com.");
|
|
this->expected_sig_rdatas_.push_back("NS 5 3 3600 20000101000000 20000201000000 "
|
|
"12345 example.org. FAKEFAKEFAKE");
|
|
doFindTest(*finder, isc::dns::Name("example.org."),
|
|
isc::dns::RRType::NS(), isc::dns::RRType::NS(),
|
|
this->rrttl_, ZoneFinder::SUCCESS, this->expected_rdatas_,
|
|
this->expected_sig_rdatas_);
|
|
|
|
// Check when we ask for something below delegation point, we get the NS
|
|
// (Both when the RRset there exists and doesn't)
|
|
this->expected_rdatas_.clear();
|
|
this->expected_sig_rdatas_.clear();
|
|
this->expected_rdatas_.push_back("ns.example.com.");
|
|
this->expected_rdatas_.push_back("ns.delegation.example.org.");
|
|
this->expected_sig_rdatas_.push_back("NS 5 3 3600 20000101000000 20000201000000 "
|
|
"12345 example.org. FAKEFAKEFAKE");
|
|
doFindTest(*finder, isc::dns::Name("ns.delegation.example.org."),
|
|
this->qtype_, isc::dns::RRType::NS(),
|
|
this->rrttl_, ZoneFinder::DELEGATION, this->expected_rdatas_,
|
|
this->expected_sig_rdatas_, ZoneFinder::RESULT_DEFAULT,
|
|
isc::dns::Name("delegation.example.org."));
|
|
doFindTest(*finder, isc::dns::Name("ns.delegation.example.org."),
|
|
isc::dns::RRType::AAAA(), isc::dns::RRType::NS(),
|
|
this->rrttl_, ZoneFinder::DELEGATION, this->expected_rdatas_,
|
|
this->expected_sig_rdatas_, ZoneFinder::RESULT_DEFAULT,
|
|
isc::dns::Name("delegation.example.org."));
|
|
doFindTest(*finder, isc::dns::Name("deep.below.delegation.example.org."),
|
|
isc::dns::RRType::AAAA(), isc::dns::RRType::NS(),
|
|
this->rrttl_, ZoneFinder::DELEGATION, this->expected_rdatas_,
|
|
this->expected_sig_rdatas_, ZoneFinder::RESULT_DEFAULT,
|
|
isc::dns::Name("delegation.example.org."));
|
|
|
|
// Even when we check directly at the delegation point, we should get
|
|
// the NS
|
|
doFindTest(*finder, isc::dns::Name("delegation.example.org."),
|
|
isc::dns::RRType::AAAA(), isc::dns::RRType::NS(),
|
|
this->rrttl_, ZoneFinder::DELEGATION, this->expected_rdatas_,
|
|
this->expected_sig_rdatas_);
|
|
|
|
// And when we ask direcly for the NS, we should still get delegation
|
|
doFindTest(*finder, isc::dns::Name("delegation.example.org."),
|
|
isc::dns::RRType::NS(), isc::dns::RRType::NS(),
|
|
this->rrttl_, ZoneFinder::DELEGATION, this->expected_rdatas_,
|
|
this->expected_sig_rdatas_);
|
|
|
|
// Now test delegation. If it is below the delegation point, we should get
|
|
// the DNAME (the one with data under DNAME is invalid zone, but we test
|
|
// the behaviour anyway just to make sure)
|
|
this->expected_rdatas_.clear();
|
|
this->expected_rdatas_.push_back("dname.example.com.");
|
|
this->expected_sig_rdatas_.clear();
|
|
this->expected_sig_rdatas_.push_back("DNAME 5 3 3600 20000101000000 "
|
|
"20000201000000 12345 example.org. "
|
|
"FAKEFAKEFAKE");
|
|
doFindTest(*finder, isc::dns::Name("below.dname.example.org."),
|
|
this->qtype_, isc::dns::RRType::DNAME(),
|
|
this->rrttl_, ZoneFinder::DNAME, this->expected_rdatas_,
|
|
this->expected_sig_rdatas_, ZoneFinder::RESULT_DEFAULT,
|
|
isc::dns::Name("dname.example.org."));
|
|
doFindTest(*finder, isc::dns::Name("below.dname.example.org."),
|
|
isc::dns::RRType::AAAA(), isc::dns::RRType::DNAME(),
|
|
this->rrttl_, ZoneFinder::DNAME, this->expected_rdatas_,
|
|
this->expected_sig_rdatas_, ZoneFinder::RESULT_DEFAULT,
|
|
isc::dns::Name("dname.example.org."));
|
|
doFindTest(*finder, isc::dns::Name("really.deep.below.dname.example.org."),
|
|
isc::dns::RRType::AAAA(), isc::dns::RRType::DNAME(),
|
|
this->rrttl_, ZoneFinder::DNAME, this->expected_rdatas_,
|
|
this->expected_sig_rdatas_, ZoneFinder::RESULT_DEFAULT,
|
|
isc::dns::Name("dname.example.org."));
|
|
|
|
// Asking direcly for DNAME should give SUCCESS
|
|
doFindTest(*finder, isc::dns::Name("dname.example.org."),
|
|
isc::dns::RRType::DNAME(), isc::dns::RRType::DNAME(),
|
|
this->rrttl_, ZoneFinder::SUCCESS, this->expected_rdatas_,
|
|
this->expected_sig_rdatas_);
|
|
|
|
// But we don't delegate at DNAME point
|
|
this->expected_rdatas_.clear();
|
|
this->expected_rdatas_.push_back("192.0.2.1");
|
|
this->expected_sig_rdatas_.clear();
|
|
doFindTest(*finder, isc::dns::Name("dname.example.org."),
|
|
this->qtype_, this->qtype_,
|
|
this->rrttl_, ZoneFinder::SUCCESS, this->expected_rdatas_,
|
|
this->expected_sig_rdatas_);
|
|
this->expected_rdatas_.clear();
|
|
doFindTest(*finder, isc::dns::Name("dname.example.org."),
|
|
isc::dns::RRType::AAAA(), isc::dns::RRType::AAAA(),
|
|
this->rrttl_, ZoneFinder::NXRRSET, this->expected_rdatas_,
|
|
this->expected_sig_rdatas_);
|
|
|
|
// This is broken dname, it contains two targets
|
|
EXPECT_THROW(finder->find(isc::dns::Name("below.baddname.example.org."),
|
|
this->qtype_,
|
|
ZoneFinder::FIND_DEFAULT),
|
|
DataSourceError);
|
|
|
|
// Broken NS - it lives together with something else
|
|
EXPECT_THROW(finder->find(isc::dns::Name("brokenns1.example.org."),
|
|
this->qtype_,
|
|
ZoneFinder::FIND_DEFAULT),
|
|
DataSourceError);
|
|
EXPECT_THROW(finder->find(isc::dns::Name("brokenns2.example.org."),
|
|
this->qtype_,
|
|
ZoneFinder::FIND_DEFAULT),
|
|
DataSourceError);
|
|
}
|
|
|
|
TYPED_TEST(DatabaseClientTest, emptyDomain) {
|
|
boost::shared_ptr<DatabaseClient::Finder> finder(this->getFinder());
|
|
|
|
// This domain doesn't exist, but a subdomain of it does.
|
|
// Therefore we should pretend the domain is there, but contains no RRsets
|
|
doFindTest(*finder, isc::dns::Name("b.example.org."), this->qtype_,
|
|
this->qtype_, this->rrttl_, ZoneFinder::NXRRSET,
|
|
this->expected_rdatas_, this->expected_sig_rdatas_);
|
|
}
|
|
|
|
// Glue-OK mode. Just go through NS delegations down (but not through
|
|
// DNAME) and pretend it is not there.
|
|
TYPED_TEST(DatabaseClientTest, glueOK) {
|
|
boost::shared_ptr<DatabaseClient::Finder> finder(this->getFinder());
|
|
|
|
this->expected_rdatas_.clear();
|
|
this->expected_sig_rdatas_.clear();
|
|
doFindTest(*finder, isc::dns::Name("ns.delegation.example.org."),
|
|
isc::dns::RRType::AAAA(), isc::dns::RRType::AAAA(),
|
|
this->rrttl_, ZoneFinder::NXRRSET,
|
|
this->expected_rdatas_, this->expected_sig_rdatas_,
|
|
ZoneFinder::RESULT_DEFAULT,
|
|
isc::dns::Name("ns.delegation.example.org."),
|
|
ZoneFinder::FIND_GLUE_OK);
|
|
doFindTest(*finder, isc::dns::Name("nothere.delegation.example.org."),
|
|
isc::dns::RRType::AAAA(), isc::dns::RRType::AAAA(),
|
|
this->rrttl_, ZoneFinder::NXDOMAIN,
|
|
this->expected_rdatas_, this->expected_sig_rdatas_,
|
|
ZoneFinder::RESULT_DEFAULT,
|
|
isc::dns::Name("nothere.delegation.example.org."),
|
|
ZoneFinder::FIND_GLUE_OK);
|
|
this->expected_rdatas_.push_back("192.0.2.1");
|
|
doFindTest(*finder, isc::dns::Name("ns.delegation.example.org."),
|
|
this->qtype_, this->qtype_,
|
|
this->rrttl_, ZoneFinder::SUCCESS,
|
|
this->expected_rdatas_, this->expected_sig_rdatas_,
|
|
ZoneFinder::RESULT_DEFAULT,
|
|
isc::dns::Name("ns.delegation.example.org."),
|
|
ZoneFinder::FIND_GLUE_OK);
|
|
this->expected_rdatas_.clear();
|
|
this->expected_rdatas_.push_back("ns.example.com.");
|
|
this->expected_rdatas_.push_back("ns.delegation.example.org.");
|
|
this->expected_sig_rdatas_.clear();
|
|
this->expected_sig_rdatas_.push_back("NS 5 3 3600 20000101000000 "
|
|
"20000201000000 12345 example.org. "
|
|
"FAKEFAKEFAKE");
|
|
// When we request the NS, it should be SUCCESS, not DELEGATION
|
|
// (different in GLUE_OK)
|
|
doFindTest(*finder, isc::dns::Name("delegation.example.org."),
|
|
isc::dns::RRType::NS(), isc::dns::RRType::NS(),
|
|
this->rrttl_, ZoneFinder::SUCCESS,
|
|
this->expected_rdatas_, this->expected_sig_rdatas_,
|
|
ZoneFinder::RESULT_DEFAULT,
|
|
isc::dns::Name("delegation.example.org."),
|
|
ZoneFinder::FIND_GLUE_OK);
|
|
this->expected_rdatas_.clear();
|
|
this->expected_rdatas_.push_back("dname.example.com.");
|
|
this->expected_sig_rdatas_.clear();
|
|
this->expected_sig_rdatas_.push_back("DNAME 5 3 3600 20000101000000 "
|
|
"20000201000000 12345 example.org. "
|
|
"FAKEFAKEFAKE");
|
|
doFindTest(*finder, isc::dns::Name("below.dname.example.org."),
|
|
this->qtype_, isc::dns::RRType::DNAME(),
|
|
this->rrttl_, ZoneFinder::DNAME, this->expected_rdatas_,
|
|
this->expected_sig_rdatas_, ZoneFinder::RESULT_DEFAULT,
|
|
isc::dns::Name("dname.example.org."), ZoneFinder::FIND_GLUE_OK);
|
|
doFindTest(*finder, isc::dns::Name("below.dname.example.org."),
|
|
isc::dns::RRType::AAAA(), isc::dns::RRType::DNAME(),
|
|
this->rrttl_, ZoneFinder::DNAME, this->expected_rdatas_,
|
|
this->expected_sig_rdatas_, ZoneFinder::RESULT_DEFAULT,
|
|
isc::dns::Name("dname.example.org."), ZoneFinder::FIND_GLUE_OK);
|
|
}
|
|
|
|
TYPED_TEST(DatabaseClientTest, wildcard) {
|
|
boost::shared_ptr<DatabaseClient::Finder> finder(this->getFinder());
|
|
|
|
// First, simple wildcard match
|
|
// Check also that the RRSIG is added from the wildcard (not modified)
|
|
this->expected_rdatas_.push_back("192.0.2.5");
|
|
this->expected_sig_rdatas_.push_back("A 5 3 3600 20000101000000 "
|
|
"20000201000000 12345 example.org. "
|
|
"FAKEFAKEFAKE");
|
|
doFindTest(*finder, isc::dns::Name("a.wild.example.org"),
|
|
this->qtype_, this->qtype_, this->rrttl_,
|
|
ZoneFinder::SUCCESS, this->expected_rdatas_,
|
|
this->expected_sig_rdatas_, ZoneFinder::RESULT_WILDCARD);
|
|
doFindTest(*finder, isc::dns::Name("b.a.wild.example.org"),
|
|
this->qtype_, this->qtype_, this->rrttl_, ZoneFinder::SUCCESS,
|
|
this->expected_rdatas_, this->expected_sig_rdatas_,
|
|
ZoneFinder::RESULT_WILDCARD);
|
|
this->expected_rdatas_.clear();
|
|
this->expected_sig_rdatas_.clear();
|
|
doFindTest(*finder, isc::dns::Name("a.wild.example.org"),
|
|
isc::dns::RRType::AAAA(), isc::dns::RRType::AAAA(),
|
|
this->rrttl_, ZoneFinder::NXRRSET,
|
|
this->expected_rdatas_, this->expected_sig_rdatas_,
|
|
ZoneFinder::RESULT_WILDCARD);
|
|
doFindTest(*finder, isc::dns::Name("b.a.wild.example.org"),
|
|
isc::dns::RRType::AAAA(), isc::dns::RRType::AAAA(),
|
|
this->rrttl_, ZoneFinder::NXRRSET,
|
|
this->expected_rdatas_, this->expected_sig_rdatas_,
|
|
ZoneFinder::RESULT_WILDCARD);
|
|
|
|
// Direct request for this wildcard
|
|
this->expected_rdatas_.push_back("192.0.2.5");
|
|
this->expected_sig_rdatas_.push_back("A 5 3 3600 20000101000000 "
|
|
"20000201000000 12345 example.org. "
|
|
"FAKEFAKEFAKE");
|
|
doFindTest(*finder, isc::dns::Name("*.wild.example.org"),
|
|
this->qtype_, this->qtype_, this->rrttl_, ZoneFinder::SUCCESS,
|
|
this->expected_rdatas_, this->expected_sig_rdatas_);
|
|
this->expected_rdatas_.clear();
|
|
this->expected_sig_rdatas_.clear();
|
|
doFindTest(*finder, isc::dns::Name("*.wild.example.org"),
|
|
isc::dns::RRType::AAAA(), isc::dns::RRType::AAAA(),
|
|
this->rrttl_, ZoneFinder::NXRRSET, this->expected_rdatas_,
|
|
this->expected_sig_rdatas_);
|
|
// This is nonsense, but check it doesn't match by some stupid accident
|
|
doFindTest(*finder, isc::dns::Name("a.*.wild.example.org"),
|
|
this->qtype_, this->qtype_, this->rrttl_, ZoneFinder::NXDOMAIN,
|
|
this->expected_rdatas_, this->expected_sig_rdatas_);
|
|
// These should be canceled, since it is below a domain which exitsts
|
|
doFindTest(*finder, isc::dns::Name("nothing.here.wild.example.org"),
|
|
this->qtype_, this->qtype_, this->rrttl_, ZoneFinder::NXDOMAIN,
|
|
this->expected_rdatas_, this->expected_sig_rdatas_);
|
|
doFindTest(*finder, isc::dns::Name("cancel.here.wild.example.org"),
|
|
this->qtype_, this->qtype_, this->rrttl_, ZoneFinder::NXRRSET,
|
|
this->expected_rdatas_, this->expected_sig_rdatas_);
|
|
doFindTest(*finder,
|
|
isc::dns::Name("below.cancel.here.wild.example.org"),
|
|
this->qtype_, this->qtype_, this->rrttl_, ZoneFinder::NXDOMAIN,
|
|
this->expected_rdatas_, this->expected_sig_rdatas_);
|
|
// And this should be just plain empty non-terminal domain, check
|
|
// the wildcard doesn't hurt it
|
|
doFindTest(*finder, isc::dns::Name("here.wild.example.org"),
|
|
this->qtype_, this->qtype_, this->rrttl_, ZoneFinder::NXRRSET,
|
|
this->expected_rdatas_, this->expected_sig_rdatas_);
|
|
// Also make sure that the wildcard doesn't hurt the original data
|
|
// below the wildcard
|
|
this->expected_rdatas_.push_back("2001:db8::5");
|
|
doFindTest(*finder, isc::dns::Name("cancel.here.wild.example.org"),
|
|
isc::dns::RRType::AAAA(), isc::dns::RRType::AAAA(),
|
|
this->rrttl_, ZoneFinder::SUCCESS,
|
|
this->expected_rdatas_, this->expected_sig_rdatas_);
|
|
this->expected_rdatas_.clear();
|
|
|
|
// How wildcard go together with delegation
|
|
this->expected_rdatas_.push_back("ns.example.com.");
|
|
doFindTest(*finder, isc::dns::Name("below.delegatedwild.example.org"),
|
|
this->qtype_, isc::dns::RRType::NS(), this->rrttl_,
|
|
ZoneFinder::DELEGATION, this->expected_rdatas_,
|
|
this->expected_sig_rdatas_, ZoneFinder::RESULT_DEFAULT,
|
|
isc::dns::Name("delegatedwild.example.org"));
|
|
// FIXME: This doesn't look logically OK, GLUE_OK should make it transparent,
|
|
// so the match should either work or be canceled, but return NXDOMAIN
|
|
doFindTest(*finder, isc::dns::Name("below.delegatedwild.example.org"),
|
|
this->qtype_, isc::dns::RRType::NS(), this->rrttl_,
|
|
ZoneFinder::DELEGATION, this->expected_rdatas_,
|
|
this->expected_sig_rdatas_, ZoneFinder::RESULT_DEFAULT,
|
|
isc::dns::Name("delegatedwild.example.org"),
|
|
ZoneFinder::FIND_GLUE_OK);
|
|
|
|
this->expected_rdatas_.clear();
|
|
this->expected_rdatas_.push_back("192.0.2.5");
|
|
// These are direct matches
|
|
const char* positive_names[] = {
|
|
"wild.*.foo.example.org.",
|
|
"wild.*.foo.*.bar.example.org.",
|
|
NULL
|
|
};
|
|
for (const char** name = positive_names; *name != NULL; ++ name) {
|
|
doFindTest(*finder, isc::dns::Name(*name), this->qtype_,
|
|
this->qtype_, this->rrttl_, ZoneFinder::SUCCESS,
|
|
this->expected_rdatas_,
|
|
this->expected_sig_rdatas_);
|
|
}
|
|
|
|
// These are wildcard matches against empty nonterminal asterisk
|
|
this->expected_rdatas_.clear();
|
|
const char* negative_names[] = {
|
|
"a.foo.example.org.",
|
|
"wild.bar.foo.example.org.",
|
|
"baz.foo.*.bar.example.org",
|
|
"baz.foo.baz.bar.example.org",
|
|
"*.foo.baz.bar.example.org",
|
|
NULL
|
|
};
|
|
// Unless FIND_DNSSEC is specified, this is no different from other
|
|
// NXRRSET case.
|
|
for (const char** name = negative_names; *name != NULL; ++ name) {
|
|
doFindTest(*finder, isc::dns::Name(*name), this->qtype_,
|
|
this->qtype_, this->rrttl_, ZoneFinder::NXRRSET,
|
|
this->expected_rdatas_, this->expected_sig_rdatas_,
|
|
ZoneFinder::RESULT_WILDCARD);
|
|
}
|
|
|
|
// With FIND_DNSSEC, it should have NSEC_SIGNED flag.
|
|
const char* negative_dnssec_names[] = {
|
|
"a.bar.example.org.",
|
|
"foo.baz.bar.example.org.",
|
|
"a.foo.bar.example.org.",
|
|
NULL
|
|
};
|
|
this->expected_rdatas_.clear();
|
|
this->expected_rdatas_.push_back("wild.*.foo.*.bar.example.org. NSEC");
|
|
this->expected_sig_rdatas_.clear();
|
|
for (const char** name = negative_dnssec_names; *name != NULL; ++ name) {
|
|
doFindTest(*finder, isc::dns::Name(*name), this->qtype_,
|
|
RRType::NSEC(), this->rrttl_, ZoneFinder::NXRRSET,
|
|
this->expected_rdatas_, this->expected_sig_rdatas_,
|
|
ZoneFinder::RESULT_WILDCARD | ZoneFinder::RESULT_NSEC_SIGNED,
|
|
Name("bao.example.org."), ZoneFinder::FIND_DNSSEC);
|
|
}
|
|
|
|
// CNAME on a wildcard. Maybe not so common, but not disallowed.
|
|
this->expected_rdatas_.clear();
|
|
this->expected_rdatas_.push_back("www.example.org.");
|
|
this->expected_sig_rdatas_.clear();
|
|
doFindTest(*finder, isc::dns::Name("a.cnamewild.example.org."),
|
|
isc::dns::RRType::TXT(), isc::dns::RRType::CNAME(),
|
|
this->rrttl_, ZoneFinder::CNAME,
|
|
this->expected_rdatas_, this->expected_sig_rdatas_,
|
|
ZoneFinder::RESULT_WILDCARD);
|
|
|
|
// DNAME on a wildcard. In our implementation we ignore DNAMEs on a
|
|
// wildcard, but at a higher level we say the behavior is "unspecified".
|
|
// rfc2672bis strongly discourages the mixture of DNAME and wildcard
|
|
// (with SHOULD NOT).
|
|
doFindTest(*finder, Name("a.dnamewild.example.org."),
|
|
this->qtype_, this->qtype_, this->rrttl_,
|
|
ZoneFinder::NXRRSET, this->empty_rdatas_,
|
|
this->empty_rdatas_, ZoneFinder::RESULT_WILDCARD);
|
|
|
|
// Some strange things in the wild node
|
|
this->expected_rdatas_.clear();
|
|
this->expected_rdatas_.push_back("ns.example.com.");
|
|
doFindTest(*finder, isc::dns::Name("a.nswild.example.org."),
|
|
isc::dns::RRType::TXT(), isc::dns::RRType::NS(),
|
|
this->rrttl_, ZoneFinder::DELEGATION,
|
|
this->expected_rdatas_, this->empty_rdatas_,
|
|
ZoneFinder::RESULT_WILDCARD);
|
|
}
|
|
|
|
TYPED_TEST(DatabaseClientTest, noWildcard) {
|
|
// Tests with the NO_WILDCARD flag.
|
|
|
|
boost::shared_ptr<DatabaseClient::Finder> finder(this->getFinder());
|
|
|
|
// This would match *.wild.example.org, but with NO_WILDCARD should
|
|
// result in NXDOMAIN.
|
|
this->expected_rdatas_.push_back("cancel.here.wild.example.org. A "
|
|
"NSEC RRSIG");
|
|
this->expected_sig_rdatas_.push_back("NSEC 5 3 3600 20000101000000 "
|
|
"20000201000000 12345 example.org. "
|
|
"FAKEFAKEFAKE");
|
|
doFindTest(*finder, isc::dns::Name("a.wild.example.org"),
|
|
RRType::NSEC(), RRType::NSEC(), this->rrttl_,
|
|
ZoneFinder::NXDOMAIN, this->expected_rdatas_,
|
|
this->expected_sig_rdatas_, ZoneFinder::RESULT_NSEC_SIGNED,
|
|
Name("*.wild.example.org."),
|
|
ZoneFinder::FIND_DNSSEC | ZoneFinder::NO_WILDCARD);
|
|
|
|
// Should be the same without FIND_DNSSEC (but in this case no RRsets
|
|
// will be returned)
|
|
doFindTest(*finder, isc::dns::Name("a.wild.example.org"),
|
|
RRType::NSEC(), RRType::NSEC(), this->rrttl_,
|
|
ZoneFinder::NXDOMAIN, this->empty_rdatas_,
|
|
this->empty_rdatas_, ZoneFinder::RESULT_DEFAULT,
|
|
Name::ROOT_NAME(), // name is dummy
|
|
ZoneFinder::NO_WILDCARD);
|
|
|
|
// Same for wildcard empty non terminal.
|
|
this->expected_rdatas_.clear();
|
|
this->expected_rdatas_.push_back("brokenns1.example.org. A NSEC");
|
|
doFindTest(*finder, isc::dns::Name("a.bar.example.org"),
|
|
RRType::NSEC(), RRType::NSEC(), this->rrttl_,
|
|
ZoneFinder::NXDOMAIN, this->expected_rdatas_,
|
|
this->empty_rdatas_, ZoneFinder::RESULT_NSEC_SIGNED,
|
|
Name("wild.*.foo.*.bar.example.org"),
|
|
ZoneFinder::FIND_DNSSEC | ZoneFinder::NO_WILDCARD);
|
|
|
|
// Search for a wildcard name with NO_WILDCARD. There should be no
|
|
// difference. This is, for example, necessary to provide non existence
|
|
// of matching wildcard for isnx.nonterminal.example.org.
|
|
this->expected_rdatas_.clear();
|
|
this->expected_rdatas_.push_back("empty.nonterminal.example.org. NSEC");
|
|
doFindTest(*finder, isc::dns::Name("*.nonterminal.example.org"),
|
|
RRType::NSEC(), RRType::NSEC(), this->rrttl_,
|
|
ZoneFinder::NXDOMAIN, this->expected_rdatas_,
|
|
this->empty_rdatas_, ZoneFinder::RESULT_NSEC_SIGNED,
|
|
Name("l.example.org"),
|
|
ZoneFinder::FIND_DNSSEC | ZoneFinder::NO_WILDCARD);
|
|
|
|
// On the other hand, if there's exact match for the wildcard name
|
|
// it should be found regardless of NO_WILDCARD.
|
|
this->expected_rdatas_.clear();
|
|
this->expected_rdatas_.push_back("192.0.2.5");
|
|
this->expected_sig_rdatas_.clear();
|
|
this->expected_sig_rdatas_.push_back("A 5 3 3600 20000101000000 "
|
|
"20000201000000 12345 example.org. "
|
|
"FAKEFAKEFAKE");
|
|
doFindTest(*finder, isc::dns::Name("*.wild.example.org"),
|
|
this->qtype_, this->qtype_, this->rrttl_,
|
|
ZoneFinder::SUCCESS, this->expected_rdatas_,
|
|
this->expected_sig_rdatas_, ZoneFinder::RESULT_DEFAULT,
|
|
Name("*.wild.example.org"), ZoneFinder::NO_WILDCARD);
|
|
}
|
|
|
|
TYPED_TEST(DatabaseClientTest, NXRRSET_NSEC) {
|
|
// The domain exists, but doesn't have this RRType
|
|
// So we should get its NSEC
|
|
boost::shared_ptr<DatabaseClient::Finder> finder(this->getFinder());
|
|
|
|
this->expected_rdatas_.push_back("www2.example.org. A AAAA NSEC RRSIG");
|
|
this->expected_sig_rdatas_.push_back("NSEC 5 3 3600 20000101000000 "
|
|
"20000201000000 12345 example.org. "
|
|
"FAKEFAKEFAKE");
|
|
doFindTest(*finder, isc::dns::Name("www.example.org."),
|
|
isc::dns::RRType::TXT(), isc::dns::RRType::NSEC(),
|
|
this->rrttl_, ZoneFinder::NXRRSET,
|
|
this->expected_rdatas_, this->expected_sig_rdatas_,
|
|
ZoneFinder::RESULT_NSEC_SIGNED, Name::ROOT_NAME(),
|
|
ZoneFinder::FIND_DNSSEC);
|
|
}
|
|
|
|
TYPED_TEST(DatabaseClientTest, wildcardNXRRSET_NSEC) {
|
|
// The domain exists, but doesn't have this RRType
|
|
// So we should get its NSEC
|
|
//
|
|
// The user will have to query us again to get the correct
|
|
// answer (eg. prove there's not an exact match)
|
|
boost::shared_ptr<DatabaseClient::Finder> finder(this->getFinder());
|
|
|
|
this->expected_rdatas_.push_back("cancel.here.wild.example.org. A NSEC "
|
|
"RRSIG");
|
|
this->expected_sig_rdatas_.push_back("NSEC 5 3 3600 20000101000000 "
|
|
"20000201000000 12345 example.org. "
|
|
"FAKEFAKEFAKE");
|
|
// Note that the NSEC name should NOT be synthesized.
|
|
doFindTest(*finder, isc::dns::Name("a.wild.example.org."),
|
|
isc::dns::RRType::TXT(), isc::dns::RRType::NSEC(),
|
|
this->rrttl_, ZoneFinder::NXRRSET,
|
|
this->expected_rdatas_, this->expected_sig_rdatas_,
|
|
ZoneFinder::RESULT_WILDCARD | ZoneFinder::RESULT_NSEC_SIGNED,
|
|
Name("*.wild.example.org"), ZoneFinder::FIND_DNSSEC);
|
|
}
|
|
|
|
TYPED_TEST(DatabaseClientTest,dbNegativeCaseFind){
|
|
//ZoneFinder::find() for negative case should show whether the zone is
|
|
//signed by NSEC or NSEC3,that is good for upper layer caller.
|
|
|
|
//First off, add an NSEC3PARAM RR
|
|
this->updater_ = this->client_->getUpdater(this->zname_, false);
|
|
this->rrset_.reset(new RRset(this->zname_, this->qclass_,
|
|
isc::dns::RRType::NSEC3PARAM(),
|
|
this->rrttl_));
|
|
this->rrset_->addRdata(rdata::createRdata(this->rrset_->getType(),
|
|
this->rrset_->getClass(),
|
|
"1 0 12 aabbccdd"));
|
|
this->updater_->addRRset(*this->rrset_);
|
|
this->expected_rdatas_.clear();
|
|
this->expected_sig_rdatas_.clear();
|
|
//If there is a NSEC3PARM RRset, the result of find() function should
|
|
//contain RESULT_NSEC3_SIGNED flag when NXDOMAIN or NXRRSET.
|
|
doFindTest(this->updater_->getFinder(),
|
|
isc::dns::Name("doesnotexist.example.org."),
|
|
this->qtype_, this->qtype_, this->rrttl_,
|
|
ZoneFinder::NXDOMAIN, this->expected_rdatas_,
|
|
this->expected_sig_rdatas_,
|
|
ZoneFinder::RESULT_NSEC3_SIGNED);
|
|
}
|
|
|
|
TYPED_TEST(DatabaseClientTest, NXDOMAIN_NSEC) {
|
|
// The domain doesn't exist, so we must get the right NSEC
|
|
boost::shared_ptr<DatabaseClient::Finder> finder(this->getFinder());
|
|
|
|
this->expected_rdatas_.push_back("www2.example.org. A AAAA NSEC RRSIG");
|
|
this->expected_sig_rdatas_.push_back("NSEC 5 3 3600 20000101000000 "
|
|
"20000201000000 12345 example.org. "
|
|
"FAKEFAKEFAKE");
|
|
doFindTest(*finder, isc::dns::Name("www1.example.org."),
|
|
isc::dns::RRType::TXT(), isc::dns::RRType::NSEC(),
|
|
this->rrttl_, ZoneFinder::NXDOMAIN,
|
|
this->expected_rdatas_, this->expected_sig_rdatas_,
|
|
ZoneFinder::RESULT_NSEC_SIGNED, Name("www.example.org."),
|
|
ZoneFinder::FIND_DNSSEC);
|
|
this->expected_rdatas_.clear();
|
|
this->expected_rdatas_.push_back("acnamesig1.example.org. NS A NSEC RRSIG");
|
|
// This tests it works correctly in apex (there was a bug, where a check
|
|
// for NS-alone was there and it would throw).
|
|
doFindTest(*finder, isc::dns::Name("aa.example.org."),
|
|
isc::dns::RRType::TXT(), isc::dns::RRType::NSEC(),
|
|
this->rrttl_, ZoneFinder::NXDOMAIN,
|
|
this->expected_rdatas_, this->expected_sig_rdatas_,
|
|
ZoneFinder::RESULT_NSEC_SIGNED, Name("example.org."),
|
|
ZoneFinder::FIND_DNSSEC);
|
|
|
|
// Check that if the DB doesn't support it, the exception from there
|
|
// is not propagated and it only does not include the NSEC
|
|
if (!this->is_mock_) {
|
|
return; // We don't make the real DB to throw
|
|
}
|
|
EXPECT_NO_THROW(doFindTest(*finder,
|
|
isc::dns::Name("notimplnsec.example.org."),
|
|
isc::dns::RRType::TXT(),
|
|
isc::dns::RRType::NSEC(), this->rrttl_,
|
|
ZoneFinder::NXDOMAIN, this->empty_rdatas_,
|
|
this->empty_rdatas_,
|
|
ZoneFinder::RESULT_DEFAULT,
|
|
Name::ROOT_NAME(), ZoneFinder::FIND_DNSSEC));
|
|
}
|
|
|
|
TYPED_TEST(DatabaseClientTest, emptyNonterminalNSEC) {
|
|
// Same as NXDOMAIN_NSEC, but with empty non-terminal
|
|
boost::shared_ptr<DatabaseClient::Finder> finder(this->getFinder());
|
|
|
|
this->expected_rdatas_.push_back("empty.nonterminal.example.org. NSEC");
|
|
doFindTest(*finder, isc::dns::Name("nonterminal.example.org."),
|
|
isc::dns::RRType::TXT(), isc::dns::RRType::NSEC(), this->rrttl_,
|
|
ZoneFinder::NXRRSET, this->expected_rdatas_,
|
|
this->expected_sig_rdatas_,
|
|
ZoneFinder::RESULT_NSEC_SIGNED, Name("l.example.org."),
|
|
ZoneFinder::FIND_DNSSEC);
|
|
|
|
// Check that if the DB doesn't support it, the exception from there
|
|
// is not propagated and it only does not include the NSEC
|
|
if (!this->is_mock_) {
|
|
return; // We don't make the real DB to throw
|
|
}
|
|
EXPECT_NO_THROW(doFindTest(*finder,
|
|
isc::dns::Name("here.wild.example.org."),
|
|
isc::dns::RRType::TXT(),
|
|
isc::dns::RRType::NSEC(),
|
|
this->rrttl_, ZoneFinder::NXRRSET,
|
|
this->empty_rdatas_, this->empty_rdatas_,
|
|
ZoneFinder::RESULT_DEFAULT,
|
|
Name::ROOT_NAME(), ZoneFinder::FIND_DNSSEC));
|
|
}
|
|
|
|
TYPED_TEST(DatabaseClientTest, anyFromFind) {
|
|
// Find will reject answering an ANY query
|
|
EXPECT_THROW(this->getFinder()->find(isc::dns::Name("www2.example.org."),
|
|
RRType::ANY()), isc::Unexpected);
|
|
}
|
|
|
|
// Test the findAll method.
|
|
TYPED_TEST(DatabaseClientTest, getAll) {
|
|
// The domain doesn't exist, so we must get the right NSEC
|
|
boost::shared_ptr<DatabaseClient::Finder> finder(this->getFinder());
|
|
|
|
// It should act the same on the "failures"
|
|
std::vector<ConstRRsetPtr> target;
|
|
EXPECT_EQ(ZoneFinder::NXDOMAIN,
|
|
finder->findAll(isc::dns::Name("nothere.example.org."),
|
|
target)->code);
|
|
EXPECT_TRUE(target.empty());
|
|
EXPECT_EQ(ZoneFinder::NXRRSET,
|
|
finder->findAll(isc::dns::Name("here.wild.example.org."),
|
|
target)->code);
|
|
this->expected_rdatas_.push_back("ns.delegation.example.org.");
|
|
this->expected_rdatas_.push_back("ns.example.com.");
|
|
doFindAllTestResult(*finder, isc::dns::Name("xx.delegation.example.org."),
|
|
ZoneFinder::DELEGATION, RRType::NS(),
|
|
this->expected_rdatas_,
|
|
isc::dns::Name("delegation.example.org."));
|
|
this->expected_rdatas_.clear();
|
|
this->expected_rdatas_.push_back("www.example.org.");
|
|
doFindAllTestResult(*finder, isc::dns::Name("cname.example.org"),
|
|
ZoneFinder::CNAME, RRType::CNAME(),
|
|
this->expected_rdatas_);
|
|
this->expected_rdatas_.clear();
|
|
this->expected_rdatas_.push_back("dname.example.com.");
|
|
doFindAllTestResult(*finder, isc::dns::Name("a.dname.example.org"),
|
|
ZoneFinder::DNAME, RRType::DNAME(),
|
|
this->expected_rdatas_,
|
|
isc::dns::Name("dname.example.org."));
|
|
// It should get the data on success
|
|
EXPECT_EQ(ZoneFinder::SUCCESS,
|
|
finder->findAll(isc::dns::Name("www2.example.org."),
|
|
target)->code);
|
|
ASSERT_EQ(2, target.size());
|
|
size_t a_idx(target[1]->getType() == RRType::A());
|
|
EXPECT_EQ(RRType::A(), target[a_idx]->getType());
|
|
std::string previous;
|
|
size_t count(0);
|
|
for (RdataIteratorPtr it(target[a_idx]->getRdataIterator());
|
|
!it->isLast(); it->next()) {
|
|
count ++;
|
|
EXPECT_NE(previous, it->getCurrent().toText());
|
|
EXPECT_TRUE(it->getCurrent().toText() == "192.0.2.1" ||
|
|
it->getCurrent().toText() == "192.0.2.2");
|
|
previous = it->getCurrent().toText();
|
|
}
|
|
EXPECT_EQ(2, count);
|
|
EXPECT_EQ(RRType::AAAA(), target[1 - a_idx]->getType());
|
|
RdataIteratorPtr it(target[1 - a_idx]->getRdataIterator());
|
|
ASSERT_FALSE(it->isLast());
|
|
EXPECT_EQ("2001:db8::1", it->getCurrent().toText());
|
|
it->next();
|
|
EXPECT_TRUE(it->isLast());
|
|
|
|
// And on wildcard. Check the signatures as well.
|
|
target.clear();
|
|
ConstZoneFinderContextPtr result =
|
|
finder->findAll(Name("a.wild.example.org"), target,
|
|
ZoneFinder::FIND_DNSSEC);
|
|
EXPECT_EQ(ZoneFinder::SUCCESS, result->code);
|
|
EXPECT_TRUE(result->isWildcard());
|
|
EXPECT_TRUE(result->isNSECSigned());
|
|
EXPECT_FALSE(result->isNSEC3Signed());
|
|
ASSERT_EQ(2, target.size());
|
|
a_idx = target[1]->getType() == RRType::A();
|
|
EXPECT_EQ(RRType::A(), target[a_idx]->getType());
|
|
it = target[a_idx]->getRdataIterator();
|
|
ASSERT_FALSE(it->isLast());
|
|
EXPECT_EQ("192.0.2.5", it->getCurrent().toText());
|
|
it->next();
|
|
EXPECT_TRUE(it->isLast());
|
|
ConstRRsetPtr sig(target[a_idx]->getRRsig());
|
|
ASSERT_TRUE(sig);
|
|
EXPECT_EQ(RRType::RRSIG(), sig->getType());
|
|
EXPECT_EQ("A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE",
|
|
sig->getRdataIterator()->getCurrent().toText());
|
|
EXPECT_EQ(RRType::NSEC(), target[1 - a_idx]->getType());
|
|
it = target[1 - a_idx]->getRdataIterator();
|
|
ASSERT_FALSE(it->isLast());
|
|
EXPECT_EQ("cancel.here.wild.example.org. A RRSIG NSEC",
|
|
it->getCurrent().toText());
|
|
it->next();
|
|
EXPECT_TRUE(it->isLast());
|
|
sig = target[1 - a_idx]->getRRsig();
|
|
ASSERT_TRUE(sig);
|
|
EXPECT_EQ(RRType::RRSIG(), sig->getType());
|
|
EXPECT_EQ("NSEC 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE",
|
|
sig->getRdataIterator()->getCurrent().toText());
|
|
}
|
|
|
|
TYPED_TEST(DatabaseClientTest, getOrigin) {
|
|
DataSourceClient::FindResult
|
|
zone(this->client_->findZone(Name("example.org")));
|
|
ASSERT_EQ(result::SUCCESS, zone.code);
|
|
boost::shared_ptr<DatabaseClient::Finder> finder(
|
|
dynamic_pointer_cast<DatabaseClient::Finder>(zone.zone_finder));
|
|
if (this->is_mock_) {
|
|
EXPECT_EQ(READONLY_ZONE_ID, finder->zone_id());
|
|
}
|
|
EXPECT_EQ(this->zname_, finder->getOrigin());
|
|
}
|
|
|
|
TYPED_TEST(DatabaseClientTest, updaterFinder) {
|
|
this->updater_ = this->client_->getUpdater(this->zname_, false);
|
|
ASSERT_TRUE(this->updater_);
|
|
|
|
// If this update isn't replacing the zone, the finder should work
|
|
// just like the normal find() case.
|
|
if (this->is_mock_) {
|
|
DatabaseClient::Finder& finder = dynamic_cast<DatabaseClient::Finder&>(
|
|
this->updater_->getFinder());
|
|
EXPECT_EQ(WRITABLE_ZONE_ID, finder.zone_id());
|
|
}
|
|
this->expected_rdatas_.clear();
|
|
this->expected_rdatas_.push_back("192.0.2.1");
|
|
doFindTest(this->updater_->getFinder(), this->qname_,
|
|
this->qtype_, this->qtype_, this->rrttl_, ZoneFinder::SUCCESS,
|
|
this->expected_rdatas_, this->empty_rdatas_);
|
|
|
|
// When replacing the zone, the updater's finder shouldn't see anything
|
|
// in the zone until something is added.
|
|
this->updater_.reset();
|
|
this->updater_ = this->client_->getUpdater(this->zname_, true);
|
|
ASSERT_TRUE(this->updater_);
|
|
if (this->is_mock_) {
|
|
DatabaseClient::Finder& finder = dynamic_cast<DatabaseClient::Finder&>(
|
|
this->updater_->getFinder());
|
|
EXPECT_EQ(WRITABLE_ZONE_ID, finder.zone_id());
|
|
}
|
|
doFindTest(this->updater_->getFinder(), this->qname_, this->qtype_,
|
|
this->qtype_, this->rrttl_, ZoneFinder::NXDOMAIN,
|
|
this->empty_rdatas_, this->empty_rdatas_);
|
|
}
|
|
|
|
TYPED_TEST(DatabaseClientTest, flushZone) {
|
|
// A simple update case: flush the entire zone
|
|
boost::shared_ptr<DatabaseClient::Finder> finder(this->getFinder());
|
|
|
|
// Before update, the name exists.
|
|
EXPECT_EQ(ZoneFinder::SUCCESS, finder->find(this->qname_,
|
|
this->qtype_)->code);
|
|
|
|
// start update in the replace mode. the normal finder should still
|
|
// be able to see the record, but the updater's finder shouldn't.
|
|
this->updater_ = this->client_->getUpdater(this->zname_, true);
|
|
this->setUpdateAccessor();
|
|
EXPECT_EQ(ZoneFinder::SUCCESS,
|
|
finder->find(this->qname_, this->qtype_)->code);
|
|
EXPECT_EQ(ZoneFinder::NXDOMAIN,
|
|
this->updater_->getFinder().find(this->qname_,
|
|
this->qtype_)->code);
|
|
|
|
// commit the update. now the normal finder shouldn't see it.
|
|
this->updater_->commit();
|
|
EXPECT_EQ(ZoneFinder::NXDOMAIN, finder->find(this->qname_,
|
|
this->qtype_)->code);
|
|
|
|
// Check rollback wasn't accidentally performed.
|
|
EXPECT_FALSE(this->isRollbacked());
|
|
}
|
|
|
|
TYPED_TEST(DatabaseClientTest, updateCancel) {
|
|
// similar to the previous test, but destruct the updater before commit.
|
|
|
|
ZoneFinderPtr finder = this->client_->findZone(this->zname_).zone_finder;
|
|
EXPECT_EQ(ZoneFinder::SUCCESS, finder->find(this->qname_,
|
|
this->qtype_)->code);
|
|
|
|
this->updater_ = this->client_->getUpdater(this->zname_, true);
|
|
this->setUpdateAccessor();
|
|
EXPECT_EQ(ZoneFinder::NXDOMAIN,
|
|
this->updater_->getFinder().find(this->qname_,
|
|
this->qtype_)->code);
|
|
// DB should not have been rolled back yet.
|
|
EXPECT_FALSE(this->isRollbacked());
|
|
this->updater_.reset(); // destruct without commit
|
|
|
|
// reset() should have triggered rollback (although it doesn't affect
|
|
// anything to the mock accessor implementation except for the result of
|
|
// isRollbacked())
|
|
EXPECT_TRUE(this->isRollbacked(true));
|
|
EXPECT_EQ(ZoneFinder::SUCCESS, finder->find(this->qname_,
|
|
this->qtype_)->code);
|
|
}
|
|
|
|
TYPED_TEST(DatabaseClientTest, exceptionFromRollback) {
|
|
this->updater_ = this->client_->getUpdater(this->zname_, true);
|
|
|
|
this->rrset_.reset(new RRset(Name("throw.example.org"), this->qclass_,
|
|
this->qtype_, this->rrttl_));
|
|
this->rrset_->addRdata(rdata::createRdata(this->rrset_->getType(),
|
|
this->rrset_->getClass(),
|
|
"192.0.2.1"));
|
|
this->updater_->addRRset(*this->rrset_);
|
|
// destruct without commit. The added name will result in an exception
|
|
// in the MockAccessor's rollback method. It shouldn't be propagated.
|
|
EXPECT_NO_THROW(this->updater_.reset());
|
|
}
|
|
|
|
TYPED_TEST(DatabaseClientTest, duplicateCommit) {
|
|
// duplicate commit. should result in exception.
|
|
this->updater_ = this->client_->getUpdater(this->zname_, true);
|
|
this->updater_->commit();
|
|
EXPECT_THROW(this->updater_->commit(), DataSourceError);
|
|
}
|
|
|
|
TYPED_TEST(DatabaseClientTest, addRRsetToNewZone) {
|
|
// Add a single RRset to a fresh empty zone
|
|
this->updater_ = this->client_->getUpdater(this->zname_, true);
|
|
this->updater_->addRRset(*this->rrset_);
|
|
|
|
this->expected_rdatas_.clear();
|
|
this->expected_rdatas_.push_back("192.0.2.2");
|
|
{
|
|
SCOPED_TRACE("add RRset");
|
|
doFindTest(this->updater_->getFinder(), this->qname_, this->qtype_,
|
|
this->qtype_, this->rrttl_, ZoneFinder::SUCCESS,
|
|
this->expected_rdatas_, this->empty_rdatas_);
|
|
}
|
|
|
|
// Similar to the previous case, but with RRSIG
|
|
this->updater_.reset();
|
|
this->updater_ = this->client_->getUpdater(this->zname_, true);
|
|
this->updater_->addRRset(*this->rrset_);
|
|
this->updater_->addRRset(*this->rrsigset_);
|
|
|
|
// confirm the expected columns were passed to the accessor (if checkable).
|
|
const char* const rrsig_added[] = {
|
|
"www.example.org.", "org.example.www.", "3600", "RRSIG", "A",
|
|
"A 5 3 0 20000101000000 20000201000000 0 example.org. FAKEFAKEFAKE"
|
|
};
|
|
this->checkLastAdded(rrsig_added);
|
|
|
|
this->expected_sig_rdatas_.clear();
|
|
this->expected_sig_rdatas_.push_back(
|
|
rrsig_added[DatabaseAccessor::ADD_RDATA]);
|
|
{
|
|
SCOPED_TRACE("add RRset with RRSIG");
|
|
doFindTest(this->updater_->getFinder(), this->qname_, this->qtype_,
|
|
this->qtype_, this->rrttl_, ZoneFinder::SUCCESS,
|
|
this->expected_rdatas_, this->expected_sig_rdatas_);
|
|
}
|
|
|
|
// Add the non RRSIG RRset again, to see the attempt of adding RRSIG
|
|
// causes any unexpected effect, in particular, whether the SIGTYPE
|
|
// field might remain.
|
|
this->updater_->addRRset(*this->rrset_);
|
|
const char* const rrset_added[] = {
|
|
"www.example.org.", "org.example.www.", "3600", "A", "", "192.0.2.2"
|
|
};
|
|
this->checkLastAdded(rrset_added);
|
|
}
|
|
|
|
TYPED_TEST(DatabaseClientTest, addRRsetToCurrentZone) {
|
|
// Similar to the previous test, but not replacing the existing data.
|
|
boost::shared_ptr<DatabaseClient::Finder> finder(this->getFinder());
|
|
|
|
this->updater_ = this->client_->getUpdater(this->zname_, false);
|
|
this->updater_->addRRset(*this->rrset_);
|
|
|
|
// We should see both old and new data.
|
|
this->expected_rdatas_.clear();
|
|
this->expected_rdatas_.push_back("192.0.2.1");
|
|
this->expected_rdatas_.push_back("192.0.2.2");
|
|
{
|
|
SCOPED_TRACE("add RRset");
|
|
doFindTest(this->updater_->getFinder(), this->qname_, this->qtype_,
|
|
this->qtype_, this->rrttl_, ZoneFinder::SUCCESS,
|
|
this->expected_rdatas_, this->empty_rdatas_);
|
|
}
|
|
this->updater_->commit();
|
|
{
|
|
SCOPED_TRACE("add RRset after commit");
|
|
doFindTest(*finder, this->qname_, this->qtype_, this->qtype_,
|
|
this->rrttl_, ZoneFinder::SUCCESS, this->expected_rdatas_,
|
|
this->empty_rdatas_);
|
|
}
|
|
}
|
|
|
|
TYPED_TEST(DatabaseClientTest, addMultipleRRs) {
|
|
// Similar to the previous case, but the added RRset contains multiple
|
|
// RRs.
|
|
this->updater_ = this->client_->getUpdater(this->zname_, false);
|
|
this->rrset_->addRdata(rdata::createRdata(this->rrset_->getType(),
|
|
this->rrset_->getClass(),
|
|
"192.0.2.3"));
|
|
this->updater_->addRRset(*this->rrset_);
|
|
this->expected_rdatas_.clear();
|
|
this->expected_rdatas_.push_back("192.0.2.1");
|
|
this->expected_rdatas_.push_back("192.0.2.2");
|
|
this->expected_rdatas_.push_back("192.0.2.3");
|
|
{
|
|
SCOPED_TRACE("add multiple RRs");
|
|
doFindTest(this->updater_->getFinder(), this->qname_, this->qtype_,
|
|
this->qtype_, this->rrttl_, ZoneFinder::SUCCESS,
|
|
this->expected_rdatas_, this->empty_rdatas_);
|
|
}
|
|
}
|
|
|
|
TYPED_TEST(DatabaseClientTest, addRRsetOfLargerTTL) {
|
|
// Similar to the previous one, but the TTL of the added RRset is larger
|
|
// than that of the existing record. The finder should use the smaller
|
|
// one.
|
|
this->updater_ = this->client_->getUpdater(this->zname_, false);
|
|
this->rrset_->setTTL(RRTTL(7200));
|
|
this->updater_->addRRset(*this->rrset_);
|
|
|
|
this->expected_rdatas_.clear();
|
|
this->expected_rdatas_.push_back("192.0.2.1");
|
|
this->expected_rdatas_.push_back("192.0.2.2");
|
|
{
|
|
SCOPED_TRACE("add RRset of larger TTL");
|
|
doFindTest(this->updater_->getFinder(), this->qname_, this->qtype_,
|
|
this->qtype_, this->rrttl_, ZoneFinder::SUCCESS,
|
|
this->expected_rdatas_, this->empty_rdatas_);
|
|
}
|
|
}
|
|
|
|
TYPED_TEST(DatabaseClientTest, addRRsetOfSmallerTTL) {
|
|
// Similar to the previous one, but the added RRset has a smaller TTL.
|
|
// The added TTL should be used by the finder.
|
|
this->updater_ = this->client_->getUpdater(this->zname_, false);
|
|
this->rrset_->setTTL(RRTTL(1800));
|
|
this->updater_->addRRset(*this->rrset_);
|
|
|
|
this->expected_rdatas_.clear();
|
|
this->expected_rdatas_.push_back("192.0.2.1");
|
|
this->expected_rdatas_.push_back("192.0.2.2");
|
|
{
|
|
SCOPED_TRACE("add RRset of smaller TTL");
|
|
doFindTest(this->updater_->getFinder(), this->qname_, this->qtype_,
|
|
this->qtype_, RRTTL(1800), ZoneFinder::SUCCESS,
|
|
this->expected_rdatas_, this->empty_rdatas_);
|
|
}
|
|
}
|
|
|
|
TYPED_TEST(DatabaseClientTest, addSameRR) {
|
|
// Add the same RR as that is already in the data source.
|
|
// Currently the add interface doesn't try to suppress the duplicate,
|
|
// neither does the finder. We may want to revisit it in future versions.
|
|
|
|
this->updater_ = this->client_->getUpdater(this->zname_, false);
|
|
this->rrset_.reset(new RRset(this->qname_, this->qclass_, this->qtype_,
|
|
this->rrttl_));
|
|
this->rrset_->addRdata(rdata::createRdata(this->rrset_->getType(),
|
|
this->rrset_->getClass(),
|
|
"192.0.2.1"));
|
|
this->updater_->addRRset(*this->rrset_);
|
|
this->expected_rdatas_.clear();
|
|
this->expected_rdatas_.push_back("192.0.2.1");
|
|
this->expected_rdatas_.push_back("192.0.2.1");
|
|
{
|
|
SCOPED_TRACE("add same RR");
|
|
doFindTest(this->updater_->getFinder(), this->qname_, this->qtype_,
|
|
this->qtype_, this->rrttl_, ZoneFinder::SUCCESS,
|
|
this->expected_rdatas_, this->empty_rdatas_);
|
|
}
|
|
}
|
|
|
|
TYPED_TEST(DatabaseClientTest, addDeviantRR) {
|
|
this->updater_ = this->client_->getUpdater(this->zname_, false);
|
|
|
|
// RR class mismatch. This should be detected and rejected.
|
|
this->rrset_.reset(new RRset(this->qname_, RRClass::CH(), RRType::TXT(),
|
|
this->rrttl_));
|
|
this->rrset_->addRdata(rdata::createRdata(this->rrset_->getType(),
|
|
this->rrset_->getClass(),
|
|
"test text"));
|
|
EXPECT_THROW(this->updater_->addRRset(*this->rrset_), DataSourceError);
|
|
|
|
// Out-of-zone owner name. At a higher level this should be rejected,
|
|
// but it doesn't happen in this interface.
|
|
this->rrset_.reset(new RRset(Name("example.com"), this->qclass_,
|
|
this->qtype_, this->rrttl_));
|
|
this->rrset_->addRdata(rdata::createRdata(this->rrset_->getType(),
|
|
this->rrset_->getClass(),
|
|
"192.0.2.100"));
|
|
this->updater_->addRRset(*this->rrset_);
|
|
|
|
this->expected_rdatas_.clear();
|
|
this->expected_rdatas_.push_back("192.0.2.100");
|
|
{
|
|
// Note: find() rejects out-of-zone query name with NXDOMAIN
|
|
// regardless of whether adding the RR succeeded, so this check
|
|
// actually doesn't confirm it.
|
|
SCOPED_TRACE("add out-of-zone RR");
|
|
doFindTest(this->updater_->getFinder(), Name("example.com"),
|
|
this->qtype_, this->qtype_, this->rrttl_,
|
|
ZoneFinder::NXDOMAIN, this->empty_rdatas_,
|
|
this->empty_rdatas_);
|
|
}
|
|
}
|
|
|
|
TYPED_TEST(DatabaseClientTest, addEmptyRRset) {
|
|
this->updater_ = this->client_->getUpdater(this->zname_, false);
|
|
this->rrset_.reset(new RRset(this->qname_, this->qclass_, this->qtype_,
|
|
this->rrttl_));
|
|
EXPECT_THROW(this->updater_->addRRset(*this->rrset_), DataSourceError);
|
|
}
|
|
|
|
TYPED_TEST(DatabaseClientTest, addAfterCommit) {
|
|
this->updater_ = this->client_->getUpdater(this->zname_, false);
|
|
this->updater_->commit();
|
|
EXPECT_THROW(this->updater_->addRRset(*this->rrset_), DataSourceError);
|
|
}
|
|
|
|
TYPED_TEST(DatabaseClientTest, addRRsetWithRRSIG) {
|
|
this->updater_ = this->client_->getUpdater(this->zname_, false);
|
|
this->rrset_->addRRsig(*this->rrsigset_);
|
|
EXPECT_THROW(this->updater_->addRRset(*this->rrset_), DataSourceError);
|
|
}
|
|
|
|
TYPED_TEST(DatabaseClientTest, deleteRRset) {
|
|
boost::shared_ptr<DatabaseClient::Finder> finder(this->getFinder());
|
|
|
|
this->rrset_.reset(new RRset(this->qname_, this->qclass_, this->qtype_,
|
|
this->rrttl_));
|
|
this->rrset_->addRdata(rdata::createRdata(this->rrset_->getType(),
|
|
this->rrset_->getClass(),
|
|
"192.0.2.1"));
|
|
|
|
// Delete one RR from an RRset
|
|
this->updater_ = this->client_->getUpdater(this->zname_, false);
|
|
this->updater_->deleteRRset(*this->rrset_);
|
|
|
|
// Delete the only RR of a name
|
|
this->rrset_.reset(new RRset(Name("cname.example.org"), this->qclass_,
|
|
RRType::CNAME(), this->rrttl_));
|
|
this->rrset_->addRdata(rdata::createRdata(this->rrset_->getType(),
|
|
this->rrset_->getClass(),
|
|
"www.example.org"));
|
|
this->updater_->deleteRRset(*this->rrset_);
|
|
|
|
// The this->updater_ finder should immediately see the deleted results.
|
|
{
|
|
SCOPED_TRACE("delete RRset");
|
|
doFindTest(this->updater_->getFinder(), this->qname_, this->qtype_,
|
|
this->qtype_, this->rrttl_, ZoneFinder::NXRRSET,
|
|
this->empty_rdatas_, this->empty_rdatas_);
|
|
doFindTest(this->updater_->getFinder(), Name("cname.example.org"),
|
|
this->qtype_, this->qtype_, this->rrttl_,
|
|
ZoneFinder::NXDOMAIN, this->empty_rdatas_,
|
|
this->empty_rdatas_);
|
|
}
|
|
|
|
// before committing the change, the original finder should see the
|
|
// original record.
|
|
{
|
|
SCOPED_TRACE("delete RRset before commit");
|
|
this->expected_rdatas_.push_back("192.0.2.1");
|
|
doFindTest(*finder, this->qname_, this->qtype_, this->qtype_,
|
|
this->rrttl_, ZoneFinder::SUCCESS, this->expected_rdatas_,
|
|
this->empty_rdatas_);
|
|
|
|
this->expected_rdatas_.clear();
|
|
this->expected_rdatas_.push_back("www.example.org.");
|
|
doFindTest(*finder, Name("cname.example.org"), this->qtype_,
|
|
RRType::CNAME(), this->rrttl_, ZoneFinder::CNAME,
|
|
this->expected_rdatas_, this->empty_rdatas_);
|
|
}
|
|
|
|
// once committed, the record should be removed from the original finder's
|
|
// view, too.
|
|
this->updater_->commit();
|
|
{
|
|
SCOPED_TRACE("delete RRset after commit");
|
|
doFindTest(*finder, this->qname_, this->qtype_, this->qtype_,
|
|
this->rrttl_, ZoneFinder::NXRRSET, this->empty_rdatas_,
|
|
this->empty_rdatas_);
|
|
doFindTest(*finder, Name("cname.example.org"), this->qtype_,
|
|
this->qtype_, this->rrttl_, ZoneFinder::NXDOMAIN,
|
|
this->empty_rdatas_, this->empty_rdatas_);
|
|
}
|
|
}
|
|
|
|
TYPED_TEST(DatabaseClientTest, deleteRRsetToNXDOMAIN) {
|
|
// similar to the previous case, but it removes the only record of the
|
|
// given name. a subsequent find() should result in NXDOMAIN.
|
|
this->rrset_.reset(new RRset(Name("cname.example.org"), this->qclass_,
|
|
RRType::CNAME(), this->rrttl_));
|
|
this->rrset_->addRdata(rdata::createRdata(this->rrset_->getType(),
|
|
this->rrset_->getClass(),
|
|
"www.example.org"));
|
|
|
|
this->updater_ = this->client_->getUpdater(this->zname_, false);
|
|
this->updater_->deleteRRset(*this->rrset_);
|
|
{
|
|
SCOPED_TRACE("delete RRset to NXDOMAIN");
|
|
doFindTest(this->updater_->getFinder(), Name("cname.example.org"),
|
|
this->qtype_, this->qtype_, this->rrttl_,
|
|
ZoneFinder::NXDOMAIN, this->empty_rdatas_,
|
|
this->empty_rdatas_);
|
|
}
|
|
}
|
|
|
|
TYPED_TEST(DatabaseClientTest, deleteMultipleRRs) {
|
|
this->rrset_.reset(new RRset(this->qname_, this->qclass_, RRType::AAAA(),
|
|
this->rrttl_));
|
|
this->rrset_->addRdata(rdata::createRdata(this->rrset_->getType(),
|
|
this->rrset_->getClass(),
|
|
"2001:db8::1"));
|
|
this->rrset_->addRdata(rdata::createRdata(this->rrset_->getType(),
|
|
this->rrset_->getClass(),
|
|
"2001:db8::2"));
|
|
|
|
this->updater_ = this->client_->getUpdater(this->zname_, false);
|
|
this->updater_->deleteRRset(*this->rrset_);
|
|
|
|
{
|
|
SCOPED_TRACE("delete multiple RRs");
|
|
doFindTest(this->updater_->getFinder(), this->qname_, RRType::AAAA(),
|
|
this->qtype_, this->rrttl_, ZoneFinder::NXRRSET,
|
|
this->empty_rdatas_, this->empty_rdatas_);
|
|
}
|
|
}
|
|
|
|
TYPED_TEST(DatabaseClientTest, partialDelete) {
|
|
this->rrset_.reset(new RRset(this->qname_, this->qclass_, RRType::AAAA(),
|
|
this->rrttl_));
|
|
this->rrset_->addRdata(rdata::createRdata(this->rrset_->getType(),
|
|
this->rrset_->getClass(),
|
|
"2001:db8::1"));
|
|
// This does not exist in the test data source:
|
|
this->rrset_->addRdata(rdata::createRdata(this->rrset_->getType(),
|
|
this->rrset_->getClass(),
|
|
"2001:db8::3"));
|
|
|
|
// deleteRRset should succeed "silently", and subsequent find() should
|
|
// find the remaining RR.
|
|
this->updater_ = this->client_->getUpdater(this->zname_, false);
|
|
this->updater_->deleteRRset(*this->rrset_);
|
|
{
|
|
SCOPED_TRACE("partial delete");
|
|
this->expected_rdatas_.push_back("2001:db8::2");
|
|
doFindTest(this->updater_->getFinder(), this->qname_, RRType::AAAA(),
|
|
RRType::AAAA(), this->rrttl_, ZoneFinder::SUCCESS,
|
|
this->expected_rdatas_, this->empty_rdatas_);
|
|
}
|
|
}
|
|
|
|
TYPED_TEST(DatabaseClientTest, deleteNoMatch) {
|
|
// similar to the previous test, but there's not even a match in the
|
|
// specified RRset. Essentially there's no difference in the result.
|
|
this->updater_ = this->client_->getUpdater(this->zname_, false);
|
|
this->updater_->deleteRRset(*this->rrset_);
|
|
{
|
|
SCOPED_TRACE("delete no match");
|
|
this->expected_rdatas_.push_back("192.0.2.1");
|
|
doFindTest(this->updater_->getFinder(), this->qname_, this->qtype_,
|
|
this->qtype_, this->rrttl_, ZoneFinder::SUCCESS,
|
|
this->expected_rdatas_, this->empty_rdatas_);
|
|
}
|
|
}
|
|
|
|
TYPED_TEST(DatabaseClientTest, deleteWithDifferentTTL) {
|
|
// Our delete interface simply ignores TTL (may change in a future version)
|
|
this->rrset_.reset(new RRset(this->qname_, this->qclass_, this->qtype_,
|
|
RRTTL(1800)));
|
|
this->rrset_->addRdata(rdata::createRdata(this->rrset_->getType(),
|
|
this->rrset_->getClass(),
|
|
"192.0.2.1"));
|
|
this->updater_ = this->client_->getUpdater(this->zname_, false);
|
|
this->updater_->deleteRRset(*this->rrset_);
|
|
{
|
|
SCOPED_TRACE("delete RRset with a different TTL");
|
|
doFindTest(this->updater_->getFinder(), this->qname_, this->qtype_,
|
|
this->qtype_, this->rrttl_, ZoneFinder::NXRRSET,
|
|
this->empty_rdatas_, this->empty_rdatas_);
|
|
}
|
|
}
|
|
|
|
TYPED_TEST(DatabaseClientTest, deleteDeviantRR) {
|
|
this->updater_ = this->client_->getUpdater(this->zname_, false);
|
|
|
|
// RR class mismatch. This should be detected and rejected.
|
|
this->rrset_.reset(new RRset(this->qname_, RRClass::CH(), RRType::TXT(),
|
|
this->rrttl_));
|
|
this->rrset_->addRdata(rdata::createRdata(this->rrset_->getType(),
|
|
this->rrset_->getClass(),
|
|
"test text"));
|
|
EXPECT_THROW(this->updater_->deleteRRset(*this->rrset_), DataSourceError);
|
|
|
|
// Out-of-zone owner name. At a higher level this should be rejected,
|
|
// but it doesn't happen in this interface.
|
|
this->rrset_.reset(new RRset(Name("example.com"), this->qclass_,
|
|
this->qtype_, this->rrttl_));
|
|
this->rrset_->addRdata(rdata::createRdata(this->rrset_->getType(),
|
|
this->rrset_->getClass(),
|
|
"192.0.2.100"));
|
|
EXPECT_NO_THROW(this->updater_->deleteRRset(*this->rrset_));
|
|
}
|
|
|
|
TYPED_TEST(DatabaseClientTest, deleteAfterCommit) {
|
|
this->updater_ = this->client_->getUpdater(this->zname_, false);
|
|
this->updater_->commit();
|
|
EXPECT_THROW(this->updater_->deleteRRset(*this->rrset_), DataSourceError);
|
|
}
|
|
|
|
TYPED_TEST(DatabaseClientTest, deleteEmptyRRset) {
|
|
this->updater_ = this->client_->getUpdater(this->zname_, false);
|
|
this->rrset_.reset(new RRset(this->qname_, this->qclass_, this->qtype_,
|
|
this->rrttl_));
|
|
EXPECT_THROW(this->updater_->deleteRRset(*this->rrset_), DataSourceError);
|
|
}
|
|
|
|
TYPED_TEST(DatabaseClientTest, deleteRRsetWithRRSIG) {
|
|
this->updater_ = this->client_->getUpdater(this->zname_, false);
|
|
this->rrset_->addRRsig(*this->rrsigset_);
|
|
EXPECT_THROW(this->updater_->deleteRRset(*this->rrset_), DataSourceError);
|
|
}
|
|
|
|
TYPED_TEST(DatabaseClientTest, compoundUpdate) {
|
|
// This test case performs an arbitrary chosen add/delete operations
|
|
// in a single update transaction. Essentially there is nothing new to
|
|
// test here, but there may be some bugs that was overlooked and can
|
|
// only happen in the compound update scenario, so we test it.
|
|
|
|
this->updater_ = this->client_->getUpdater(this->zname_, false);
|
|
|
|
// add a new RR to an existing RRset
|
|
this->updater_->addRRset(*this->rrset_);
|
|
this->expected_rdatas_.clear();
|
|
this->expected_rdatas_.push_back("192.0.2.1");
|
|
this->expected_rdatas_.push_back("192.0.2.2");
|
|
doFindTest(this->updater_->getFinder(), this->qname_, this->qtype_,
|
|
this->qtype_, this->rrttl_, ZoneFinder::SUCCESS,
|
|
this->expected_rdatas_, this->empty_rdatas_);
|
|
|
|
// delete an existing RR
|
|
this->rrset_.reset(new RRset(Name("www.example.org"), this->qclass_,
|
|
this->qtype_, this->rrttl_));
|
|
this->rrset_->addRdata(rdata::createRdata(this->rrset_->getType(),
|
|
this->rrset_->getClass(),
|
|
"192.0.2.1"));
|
|
this->updater_->deleteRRset(*this->rrset_);
|
|
this->expected_rdatas_.clear();
|
|
this->expected_rdatas_.push_back("192.0.2.2");
|
|
doFindTest(this->updater_->getFinder(), this->qname_, this->qtype_,
|
|
this->qtype_, this->rrttl_, ZoneFinder::SUCCESS,
|
|
this->expected_rdatas_, this->empty_rdatas_);
|
|
|
|
// re-add it
|
|
this->updater_->addRRset(*this->rrset_);
|
|
this->expected_rdatas_.push_back("192.0.2.1");
|
|
doFindTest(this->updater_->getFinder(), this->qname_, this->qtype_,
|
|
this->qtype_, this->rrttl_, ZoneFinder::SUCCESS,
|
|
this->expected_rdatas_, this->empty_rdatas_);
|
|
|
|
// add a new RR with a new name
|
|
const Name newname("newname.example.org");
|
|
const RRType newtype(RRType::AAAA());
|
|
doFindTest(this->updater_->getFinder(), newname, newtype, newtype,
|
|
this->rrttl_, ZoneFinder::NXDOMAIN, this->empty_rdatas_,
|
|
this->empty_rdatas_);
|
|
this->rrset_.reset(new RRset(newname, this->qclass_, newtype,
|
|
this->rrttl_));
|
|
this->rrset_->addRdata(rdata::createRdata(this->rrset_->getType(),
|
|
this->rrset_->getClass(),
|
|
"2001:db8::10"));
|
|
this->rrset_->addRdata(rdata::createRdata(this->rrset_->getType(),
|
|
this->rrset_->getClass(),
|
|
"2001:db8::11"));
|
|
this->updater_->addRRset(*this->rrset_);
|
|
this->expected_rdatas_.clear();
|
|
this->expected_rdatas_.push_back("2001:db8::10");
|
|
this->expected_rdatas_.push_back("2001:db8::11");
|
|
doFindTest(this->updater_->getFinder(), newname, newtype, newtype,
|
|
this->rrttl_, ZoneFinder::SUCCESS, this->expected_rdatas_,
|
|
this->empty_rdatas_);
|
|
|
|
// delete one RR from the previous set
|
|
this->rrset_.reset(new RRset(newname, this->qclass_, newtype,
|
|
this->rrttl_));
|
|
this->rrset_->addRdata(rdata::createRdata(this->rrset_->getType(),
|
|
this->rrset_->getClass(),
|
|
"2001:db8::11"));
|
|
this->updater_->deleteRRset(*this->rrset_);
|
|
this->expected_rdatas_.clear();
|
|
this->expected_rdatas_.push_back("2001:db8::10");
|
|
doFindTest(this->updater_->getFinder(), newname, newtype, newtype,
|
|
this->rrttl_, ZoneFinder::SUCCESS, this->expected_rdatas_,
|
|
this->empty_rdatas_);
|
|
|
|
// Commit the changes, confirm the entire changes applied.
|
|
this->updater_->commit();
|
|
boost::shared_ptr<DatabaseClient::Finder> finder(this->getFinder());
|
|
this->expected_rdatas_.clear();
|
|
this->expected_rdatas_.push_back("192.0.2.2");
|
|
this->expected_rdatas_.push_back("192.0.2.1");
|
|
doFindTest(*finder, this->qname_, this->qtype_, this->qtype_, this->rrttl_,
|
|
ZoneFinder::SUCCESS, this->expected_rdatas_,
|
|
this->empty_rdatas_);
|
|
|
|
this->expected_rdatas_.clear();
|
|
this->expected_rdatas_.push_back("2001:db8::10");
|
|
doFindTest(*finder, newname, newtype, newtype, this->rrttl_,
|
|
ZoneFinder::SUCCESS, this->expected_rdatas_,
|
|
this->empty_rdatas_);
|
|
}
|
|
|
|
TYPED_TEST(DatabaseClientTest, previous) {
|
|
boost::shared_ptr<DatabaseClient::Finder> finder(this->getFinder());
|
|
|
|
EXPECT_EQ(Name("www.example.org."),
|
|
finder->findPreviousName(Name("www2.example.org.")));
|
|
// Check a name that doesn't exist there
|
|
EXPECT_EQ(Name("www.example.org."),
|
|
finder->findPreviousName(Name("www1.example.org.")));
|
|
if (this->is_mock_) { // We can't really force the DB to throw
|
|
// Check it doesn't crash or anything if the underlying DB throws
|
|
DataSourceClient::FindResult
|
|
zone(this->client_->findZone(Name("bad.example.org")));
|
|
finder =
|
|
dynamic_pointer_cast<DatabaseClient::Finder>(zone.zone_finder);
|
|
|
|
EXPECT_THROW(finder->findPreviousName(Name("bad.example.org")),
|
|
isc::NotImplemented);
|
|
} else {
|
|
// No need to test this on mock one, because we test only that
|
|
// the exception gets through
|
|
|
|
// A name before the origin
|
|
EXPECT_THROW(finder->findPreviousName(Name("example.com")),
|
|
isc::NotImplemented);
|
|
}
|
|
}
|
|
|
|
TYPED_TEST(DatabaseClientTest, invalidRdata) {
|
|
boost::shared_ptr<DatabaseClient::Finder> finder(this->getFinder());
|
|
|
|
EXPECT_THROW(finder->find(Name("invalidrdata.example.org."), RRType::A()),
|
|
DataSourceError);
|
|
EXPECT_THROW(finder->find(Name("invalidrdata2.example.org."), RRType::A()),
|
|
DataSourceError);
|
|
}
|
|
|
|
TEST_F(MockDatabaseClientTest, missingNSEC) {
|
|
boost::shared_ptr<DatabaseClient::Finder> finder(this->getFinder());
|
|
|
|
/*
|
|
* FIXME: For now, we can't really distinguish this bogus input
|
|
* from not-signed zone so we can't throw. But once we can,
|
|
* enable the original test.
|
|
*/
|
|
#if 0
|
|
EXPECT_THROW(finder->find(Name("badnsec2.example.org."), RRType::A(), NULL,
|
|
ZoneFinder::FIND_DNSSEC),
|
|
DataSourceError);
|
|
#endif
|
|
doFindTest(*finder, Name("badnsec2.example.org."), RRType::A(),
|
|
RRType::A(), this->rrttl_, ZoneFinder::NXDOMAIN,
|
|
this->expected_rdatas_, this->expected_sig_rdatas_);
|
|
}
|
|
|
|
TEST_F(MockDatabaseClientTest, badName) {
|
|
boost::shared_ptr<DatabaseClient::Finder> finder(this->getFinder());
|
|
|
|
EXPECT_THROW(finder->findPreviousName(Name("brokenname.example.org.")),
|
|
DataSourceError);
|
|
}
|
|
|
|
/*
|
|
* Test correct use of the updater with a journal.
|
|
*/
|
|
TYPED_TEST(DatabaseClientTest, journal) {
|
|
this->updater_ = this->client_->getUpdater(this->zname_, false, true);
|
|
this->updater_->deleteRRset(*this->soa_);
|
|
this->updater_->deleteRRset(*this->rrset_);
|
|
this->soa_.reset(new RRset(this->zname_, this->qclass_, RRType::SOA(),
|
|
this->rrttl_));
|
|
this->soa_->addRdata(rdata::createRdata(this->soa_->getType(),
|
|
this->soa_->getClass(),
|
|
"ns1.example.org. "
|
|
"admin.example.org. "
|
|
"1235 3600 1800 2419200 7200"));
|
|
this->updater_->addRRset(*this->soa_);
|
|
this->updater_->addRRset(*this->rrset_);
|
|
ASSERT_NO_THROW(this->updater_->commit());
|
|
std::vector<JournalEntry> expected;
|
|
expected.push_back(JournalEntry(WRITABLE_ZONE_ID, 1234,
|
|
DatabaseAccessor::DIFF_DELETE,
|
|
"example.org.", "SOA", "3600",
|
|
"ns1.example.org. admin.example.org. "
|
|
"1234 3600 1800 2419200 7200"));
|
|
expected.push_back(JournalEntry(WRITABLE_ZONE_ID, 1234,
|
|
DatabaseAccessor::DIFF_DELETE,
|
|
"www.example.org.", "A", "3600",
|
|
"192.0.2.2"));
|
|
expected.push_back(JournalEntry(WRITABLE_ZONE_ID, 1235,
|
|
DatabaseAccessor::DIFF_ADD,
|
|
"example.org.", "SOA", "3600",
|
|
"ns1.example.org. admin.example.org. "
|
|
"1235 3600 1800 2419200 7200"));
|
|
expected.push_back(JournalEntry(WRITABLE_ZONE_ID, 1235,
|
|
DatabaseAccessor::DIFF_ADD,
|
|
"www.example.org.", "A", "3600",
|
|
"192.0.2.2"));
|
|
this->checkJournal(expected);
|
|
}
|
|
|
|
/*
|
|
* Push multiple delete-add sequences. Checks it is allowed and all is
|
|
* saved.
|
|
*/
|
|
TYPED_TEST(DatabaseClientTest, journalMultiple) {
|
|
std::vector<JournalEntry> expected;
|
|
this->updater_ = this->client_->getUpdater(this->zname_, false, true);
|
|
std::string soa_rdata = "ns1.example.org. admin.example.org. "
|
|
"1234 3600 1800 2419200 7200";
|
|
for (size_t i = 1; i < 100; ++ i) {
|
|
// Remove the old SOA
|
|
this->updater_->deleteRRset(*this->soa_);
|
|
expected.push_back(JournalEntry(WRITABLE_ZONE_ID, 1234 + i - 1,
|
|
DatabaseAccessor::DIFF_DELETE,
|
|
"example.org.", "SOA", "3600",
|
|
soa_rdata));
|
|
// Create a new SOA
|
|
soa_rdata = "ns1.example.org. admin.example.org. " +
|
|
lexical_cast<std::string>(1234 + i) + " 3600 1800 2419200 7200";
|
|
this->soa_.reset(new RRset(this->zname_, this->qclass_, RRType::SOA(),
|
|
this->rrttl_));
|
|
this->soa_->addRdata(rdata::createRdata(this->soa_->getType(),
|
|
this->soa_->getClass(),
|
|
soa_rdata));
|
|
// Add the new SOA
|
|
this->updater_->addRRset(*this->soa_);
|
|
expected.push_back(JournalEntry(WRITABLE_ZONE_ID, 1234 + i,
|
|
DatabaseAccessor::DIFF_ADD,
|
|
"example.org.", "SOA", "3600",
|
|
soa_rdata));
|
|
}
|
|
ASSERT_NO_THROW(this->updater_->commit());
|
|
// Check the journal contains everything.
|
|
this->checkJournal(expected);
|
|
}
|
|
|
|
/*
|
|
* Test passing a forbidden sequence to it and expect it to throw.
|
|
*
|
|
* Note that we implicitly test in different testcases (these for add and
|
|
* delete) that if the journaling is false, it doesn't expect the order.
|
|
*
|
|
* In this test we don't check with the real databases as this case shouldn't
|
|
* contain backend specific behavior.
|
|
*/
|
|
TEST_F(MockDatabaseClientTest, journalBadSequence) {
|
|
std::vector<JournalEntry> expected;
|
|
{
|
|
SCOPED_TRACE("Delete A before SOA");
|
|
this->updater_ = this->client_->getUpdater(this->zname_, false, true);
|
|
EXPECT_THROW(this->updater_->deleteRRset(*this->rrset_),
|
|
isc::BadValue);
|
|
// Make sure the journal is empty now
|
|
this->checkJournal(expected);
|
|
}
|
|
|
|
{
|
|
SCOPED_TRACE("Add before delete");
|
|
this->updater_ = this->client_->getUpdater(this->zname_, false, true);
|
|
EXPECT_THROW(this->updater_->addRRset(*this->soa_), isc::BadValue);
|
|
// Make sure the journal is empty now
|
|
this->checkJournal(expected);
|
|
}
|
|
|
|
{
|
|
SCOPED_TRACE("Add A before SOA");
|
|
this->updater_ = this->client_->getUpdater(this->zname_, false, true);
|
|
// So far OK
|
|
EXPECT_NO_THROW(this->updater_->deleteRRset(*this->soa_));
|
|
// But we miss the add SOA here
|
|
EXPECT_THROW(this->updater_->addRRset(*this->rrset_), isc::BadValue);
|
|
// Make sure the journal contains only the first one
|
|
expected.push_back(JournalEntry(WRITABLE_ZONE_ID, 1234,
|
|
DatabaseAccessor::DIFF_DELETE,
|
|
"example.org.", "SOA", "3600",
|
|
"ns1.example.org. admin.example.org. "
|
|
"1234 3600 1800 2419200 7200"));
|
|
this->checkJournal(expected);
|
|
}
|
|
|
|
{
|
|
SCOPED_TRACE("Commit before add");
|
|
this->updater_ = this->client_->getUpdater(this->zname_, false, true);
|
|
// So far OK
|
|
EXPECT_NO_THROW(this->updater_->deleteRRset(*this->soa_));
|
|
// Commit at the wrong time
|
|
EXPECT_THROW(updater_->commit(), isc::BadValue);
|
|
current_accessor_->checkJournal(expected);
|
|
}
|
|
|
|
{
|
|
SCOPED_TRACE("Delete two SOAs");
|
|
this->updater_ = this->client_->getUpdater(this->zname_, false, true);
|
|
// So far OK
|
|
EXPECT_NO_THROW(this->updater_->deleteRRset(*this->soa_));
|
|
// Delete the SOA again
|
|
EXPECT_THROW(this->updater_->deleteRRset(*this->soa_), isc::BadValue);
|
|
this->checkJournal(expected);
|
|
}
|
|
|
|
{
|
|
SCOPED_TRACE("Add two SOAs");
|
|
this->updater_ = this->client_->getUpdater(this->zname_, false, true);
|
|
// So far OK
|
|
EXPECT_NO_THROW(this->updater_->deleteRRset(*this->soa_));
|
|
// Still OK
|
|
EXPECT_NO_THROW(this->updater_->addRRset(*this->soa_));
|
|
// But this one is added again
|
|
EXPECT_THROW(this->updater_->addRRset(*this->soa_), isc::BadValue);
|
|
expected.push_back(JournalEntry(WRITABLE_ZONE_ID, 1234,
|
|
DatabaseAccessor::DIFF_ADD,
|
|
"example.org.", "SOA", "3600",
|
|
"ns1.example.org. admin.example.org. "
|
|
"1234 3600 1800 2419200 7200"));
|
|
this->checkJournal(expected);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Test it rejects to store journals when we request it together with
|
|
* erasing the whole zone.
|
|
*/
|
|
TYPED_TEST(DatabaseClientTest, journalOnErase) {
|
|
EXPECT_THROW(this->client_->getUpdater(this->zname_, true, true),
|
|
isc::BadValue);
|
|
}
|
|
|
|
/*
|
|
* Check that exception is propagated when the journal is not implemented.
|
|
*/
|
|
TEST_F(MockDatabaseClientTest, journalNotImplemented) {
|
|
updater_ = client_->getUpdater(Name("null.example.org"), false, true);
|
|
EXPECT_THROW(updater_->deleteRRset(*soa_), isc::NotImplemented);
|
|
soa_.reset(new RRset(zname_, qclass_, RRType::SOA(), rrttl_));
|
|
soa_->addRdata(rdata::createRdata(soa_->getType(), soa_->getClass(),
|
|
"ns1.example.org. admin.example.org. "
|
|
"1234 3600 1800 2419201 7200"));
|
|
EXPECT_THROW(updater_->addRRset(*soa_), isc::NotImplemented);
|
|
}
|
|
|
|
/*
|
|
* Test that different exceptions are propagated.
|
|
*/
|
|
TEST_F(MockDatabaseClientTest, journalException) {
|
|
updater_ = client_->getUpdater(Name("bad.example.org"), false, true);
|
|
EXPECT_THROW(updater_->deleteRRset(*soa_), DataSourceError);
|
|
}
|
|
|
|
//
|
|
// Tests for the ZoneJournalReader
|
|
//
|
|
|
|
// Install a simple, commonly used diff sequence: making an update from one
|
|
// SOA to another. Return the end SOA RRset for the convenience of the caller.
|
|
ConstRRsetPtr
|
|
makeSimpleDiff(DataSourceClient& client, const Name& zname,
|
|
const RRClass& rrclass, ConstRRsetPtr begin_soa)
|
|
{
|
|
ZoneUpdaterPtr updater = client.getUpdater(zname, false, true);
|
|
updater->deleteRRset(*begin_soa);
|
|
RRsetPtr soa_end(new RRset(zname, rrclass, RRType::SOA(), RRTTL(3600)));
|
|
soa_end->addRdata(rdata::createRdata(RRType::SOA(), rrclass,
|
|
"ns1.example.org. admin.example.org. "
|
|
"1235 3600 1800 2419200 7200"));
|
|
updater->addRRset(*soa_end);
|
|
updater->commit();
|
|
|
|
return (soa_end);
|
|
}
|
|
|
|
TYPED_TEST(DatabaseClientTest, journalReader) {
|
|
// Check the simple case made by makeSimpleDiff.
|
|
ConstRRsetPtr soa_end = makeSimpleDiff(*this->client_, this->zname_,
|
|
this->qclass_, this->soa_);
|
|
pair<ZoneJournalReader::Result, ZoneJournalReaderPtr> result =
|
|
this->client_->getJournalReader(this->zname_, 1234, 1235);
|
|
EXPECT_EQ(ZoneJournalReader::SUCCESS, result.first);
|
|
ZoneJournalReaderPtr jnl_reader = result.second;
|
|
ASSERT_TRUE(jnl_reader);
|
|
ConstRRsetPtr rrset = jnl_reader->getNextDiff();
|
|
ASSERT_TRUE(rrset);
|
|
isc::testutils::rrsetCheck(this->soa_, rrset);
|
|
rrset = jnl_reader->getNextDiff();
|
|
ASSERT_TRUE(rrset);
|
|
isc::testutils::rrsetCheck(soa_end, rrset);
|
|
rrset = jnl_reader->getNextDiff();
|
|
ASSERT_FALSE(rrset);
|
|
|
|
// Once it reaches the end of the sequence, further read attempt will
|
|
// result in exception.
|
|
EXPECT_THROW(jnl_reader->getNextDiff(), isc::InvalidOperation);
|
|
}
|
|
|
|
TYPED_TEST(DatabaseClientTest, readLargeJournal) {
|
|
// Similar to journalMultiple, but check that at a higher level.
|
|
|
|
this->updater_ = this->client_->getUpdater(this->zname_, false, true);
|
|
|
|
vector<ConstRRsetPtr> expected;
|
|
for (size_t i = 0; i < 100; ++i) {
|
|
// Create the old SOA and remove it, and record it in the expected list
|
|
RRsetPtr rrset1(new RRset(this->zname_, this->qclass_, RRType::SOA(),
|
|
this->rrttl_));
|
|
string soa_rdata = "ns1.example.org. admin.example.org. " +
|
|
lexical_cast<std::string>(1234 + i) + " 3600 1800 2419200 7200";
|
|
rrset1->addRdata(rdata::createRdata(RRType::SOA(), this->qclass_,
|
|
soa_rdata));
|
|
this->updater_->deleteRRset(*rrset1);
|
|
expected.push_back(rrset1);
|
|
|
|
// Create a new SOA, add it, and record it.
|
|
RRsetPtr rrset2(new RRset(this->zname_, this->qclass_, RRType::SOA(),
|
|
this->rrttl_));
|
|
soa_rdata = "ns1.example.org. admin.example.org. " +
|
|
lexical_cast<std::string>(1234 + i + 1) +
|
|
" 3600 1800 2419200 7200";
|
|
rrset2->addRdata(rdata::createRdata(RRType::SOA(), this->qclass_,
|
|
soa_rdata));
|
|
this->updater_->addRRset(*rrset2);
|
|
expected.push_back(rrset2);
|
|
}
|
|
this->updater_->commit();
|
|
|
|
ZoneJournalReaderPtr jnl_reader(this->client_->getJournalReader(
|
|
this->zname_, 1234, 1334).second);
|
|
ConstRRsetPtr actual;
|
|
int i = 0;
|
|
while ((actual = jnl_reader->getNextDiff()) != NULL) {
|
|
isc::testutils::rrsetCheck(expected.at(i++), actual);
|
|
}
|
|
EXPECT_EQ(expected.size(), i); // we should have eaten all expected data
|
|
}
|
|
|
|
TYPED_TEST(DatabaseClientTest, readJournalForNoRange) {
|
|
makeSimpleDiff(*this->client_, this->zname_, this->qclass_, this->soa_);
|
|
|
|
// The specified range does not exist in the diff storage. The factory
|
|
// method should result in NO_SUCH_VERSION
|
|
pair<ZoneJournalReader::Result, ZoneJournalReaderPtr> result =
|
|
this->client_->getJournalReader(this->zname_, 1200, 1235);
|
|
EXPECT_EQ(ZoneJournalReader::NO_SUCH_VERSION, result.first);
|
|
EXPECT_FALSE(result.second);
|
|
}
|
|
|
|
TYPED_TEST(DatabaseClientTest, journalReaderForNXZone) {
|
|
pair<ZoneJournalReader::Result, ZoneJournalReaderPtr> result =
|
|
this->client_->getJournalReader(Name("nosuchzone"), 0, 1);
|
|
EXPECT_EQ(ZoneJournalReader::NO_SUCH_ZONE, result.first);
|
|
EXPECT_FALSE(result.second);
|
|
}
|
|
|
|
// A helper function for journalWithBadData. It installs a simple diff
|
|
// from one serial (of 'begin') to another ('begin' + 1), tweaking a specified
|
|
// field of data with some invalid value.
|
|
void
|
|
installBadDiff(MockAccessor& accessor, uint32_t begin,
|
|
DatabaseAccessor::DiffRecordParams modify_param,
|
|
const char* const data)
|
|
{
|
|
string data1[] = {"example.org.", "SOA", "3600", "ns. root. 1 1 1 1 1"};
|
|
string data2[] = {"example.org.", "SOA", "3600", "ns. root. 2 1 1 1 1"};
|
|
data1[modify_param] = data;
|
|
accessor.addRecordDiff(READONLY_ZONE_ID, begin,
|
|
DatabaseAccessor::DIFF_DELETE, data1);
|
|
accessor.addRecordDiff(READONLY_ZONE_ID, begin + 1,
|
|
DatabaseAccessor::DIFF_ADD, data2);
|
|
}
|
|
|
|
TEST_F(MockDatabaseClientTest, journalWithBadData) {
|
|
MockAccessor& mock_accessor =
|
|
dynamic_cast<MockAccessor&>(*current_accessor_);
|
|
|
|
// One of the fields from the data source is broken as an RR parameter.
|
|
// The journal reader should still be constructed, but getNextDiff()
|
|
// should result in exception.
|
|
installBadDiff(mock_accessor, 1, DatabaseAccessor::DIFF_NAME,
|
|
"example..org");
|
|
installBadDiff(mock_accessor, 3, DatabaseAccessor::DIFF_TYPE,
|
|
"bad-rrtype");
|
|
installBadDiff(mock_accessor, 5, DatabaseAccessor::DIFF_TTL,
|
|
"bad-ttl");
|
|
installBadDiff(mock_accessor, 7, DatabaseAccessor::DIFF_RDATA,
|
|
"bad rdata");
|
|
EXPECT_THROW(this->client_->getJournalReader(this->zname_, 1, 2).
|
|
second->getNextDiff(), DataSourceError);
|
|
EXPECT_THROW(this->client_->getJournalReader(this->zname_, 3, 4).
|
|
second->getNextDiff(), DataSourceError);
|
|
EXPECT_THROW(this->client_->getJournalReader(this->zname_, 5, 6).
|
|
second->getNextDiff(), DataSourceError);
|
|
EXPECT_THROW(this->client_->getJournalReader(this->zname_, 7, 8).
|
|
second->getNextDiff(), DataSourceError);
|
|
}
|
|
|
|
}
|