BIND 10 master, updated. 498677a8877e4894fad598f9ec99974c414ef58c Changelog for #1331
BIND 10 source code commits
bind10-changes at lists.isc.org
Wed Nov 16 07:49:06 UTC 2011
The branch, master has been updated
via 498677a8877e4894fad598f9ec99974c414ef58c (commit)
via 713160c9bed3d991a00b2ea5e7e3e7714d79625d (commit)
via e9e0f96594eec741393fa197c1d91362c96109e1 (commit)
via 96e0aa96b5a2fd31833e9afe64bb8e4cc34e23c5 (commit)
via 43de15e4b0bd0094910ecc4f4365744cb6c1eeab (commit)
via 0d94cca23a4f22d1bb953d62d38358a8b0e49f01 (commit)
via 4215dabae27f7b9b089ff8fafef2ba5425062fc5 (commit)
via 219879a5c8d6cb361d6d6f91d88c199930560994 (commit)
via 7003eecf6f7792d140e74bac444fb00eb7b8415b (commit)
via 7c6c725225eb89d9911b28aff0c6d80152e26aaf (commit)
via 6da32eaece41f360a87388c44528dca979c10ab0 (commit)
via 3dcdc74a5e0f8cb7fd0c6a3f6dee480e30199f03 (commit)
via 7fb9faf4602b6b4feff4c940942c12be838a8153 (commit)
via d60907a85ba3f762b81189588d1b7317b95e0521 (commit)
via b88b05b2a779554a0e3c345933104d42046fffaa (commit)
via 046729c74341bb2ed1e6f60f81470cf6a6883000 (commit)
via 36db2f897ac139ca9b71ccee07a7b1ba1e3aee7b (commit)
via 573abf93bec24753aebb5a6c70d8f50def521879 (commit)
via d287d9c92ecfb59d2c9f525cf79c7bb5167984f6 (commit)
from 84290dae3201ee83c8e4aad6f7e2b181d708811e (commit)
Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.
- Log -----------------------------------------------------------------
commit 498677a8877e4894fad598f9ec99974c414ef58c
Author: Michal 'vorner' Vaner <michal.vaner at nic.cz>
Date: Wed Nov 16 08:47:12 2011 +0100
Changelog for #1331
commit 713160c9bed3d991a00b2ea5e7e3e7714d79625d
Merge: 84290dae3201ee83c8e4aad6f7e2b181d708811e e9e0f96594eec741393fa197c1d91362c96109e1
Author: Michal 'vorner' Vaner <michal.vaner at nic.cz>
Date: Wed Nov 16 08:26:17 2011 +0100
Merge #1331
-----------------------------------------------------------------------
Summary of changes:
ChangeLog | 6 +
src/lib/datasrc/client.h | 22 +++-
src/lib/datasrc/database.cc | 161 ++++++++++++-----
src/lib/datasrc/database.h | 3 +-
src/lib/datasrc/memory_datasrc.cc | 2 +-
src/lib/datasrc/memory_datasrc.h | 3 +-
src/lib/datasrc/tests/client_unittest.cc | 4 +-
src/lib/datasrc/tests/database_unittest.cc | 268 +++++++++++++++++++++++++++-
src/lib/datasrc/zone.h | 14 ++
src/lib/dns/rdata/generic/soa_6.cc | 6 +
src/lib/dns/rdata/generic/soa_6.h | 2 +
src/lib/dns/tests/rdata_soa_unittest.cc | 5 +
12 files changed, 446 insertions(+), 50 deletions(-)
-----------------------------------------------------------------------
diff --git a/ChangeLog b/ChangeLog
index 877bc8f..29fa4ca 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,9 @@
+317. [func] vorner
+ datasrc: the getUpdater method of DataSourceClient supports an optional
+ 'journaling' parameter to indicate the generated updater to store diffs.
+ The database based derived class implements this extension.
+ (Trac #1331, git 713160c9bed3d991a00b2ea5e7e3e7714d79625d)
+
316. [func]* vorner
The configuration of what parts of the system run is more flexible now.
Everything that should run must have an entry in Boss/components.
diff --git a/src/lib/datasrc/client.h b/src/lib/datasrc/client.h
index 2c3f709..5fd0af5 100644
--- a/src/lib/datasrc/client.h
+++ b/src/lib/datasrc/client.h
@@ -272,6 +272,22 @@ public:
/// In such cases this method will result in an \c isc::NotImplemented
/// exception unconditionally or when \c replace is false).
///
+ /// If \c journaling is true, the data source should store a journal
+ /// of changes. These can be used later on by, for example, IXFR-out.
+ /// However, the parameter is a hint only. It might be unable to store
+ /// them and they would be silently discarded. Or it might need to
+ /// store them no matter what (for example a git-based data source would
+ /// store journal implicitly). When the \c journaling is true, it
+ /// requires that the following update be formatted as IXFR transfer
+ /// (SOA to be removed, bunch of RRs to be removed, SOA to be added,
+ /// bunch of RRs to be added, and possibly repeated). However, it is not
+ /// required that the updater checks that. If it is false, it must not
+ /// require so and must accept any order of changes.
+ ///
+ /// We don't support erasing the whole zone (by replace being true) and
+ /// saving a journal at the same time. In such situation, BadValue is
+ /// thrown.
+ ///
/// \note To avoid throwing the exception accidentally with a lazy
/// implementation, we still keep this method pure virtual without
/// an implementation. All derived classes must explicitly define this
@@ -282,14 +298,18 @@ public:
/// \exception DataSourceError Internal error in the underlying data
/// source.
/// \exception std::bad_alloc Resource allocation failure.
+ /// \exception BadValue if both replace and journaling are true.
///
/// \param name The zone name to be updated
/// \param replace Whether to delete existing RRs before making updates
+ /// \param journaling The zone updater should store a journal of the
+ /// changes.
///
/// \return A pointer to the updater; it will be NULL if the specified
/// zone isn't found.
virtual ZoneUpdaterPtr getUpdater(const isc::dns::Name& name,
- bool replace) const = 0;
+ bool replace, bool journaling = false)
+ const = 0;
};
}
}
diff --git a/src/lib/datasrc/database.cc b/src/lib/datasrc/database.cc
index f06cdc0..5000680 100644
--- a/src/lib/datasrc/database.cc
+++ b/src/lib/datasrc/database.cc
@@ -838,10 +838,12 @@ DatabaseClient::getIterator(const isc::dns::Name& name,
class DatabaseUpdater : public ZoneUpdater {
public:
DatabaseUpdater(shared_ptr<DatabaseAccessor> accessor, int zone_id,
- const Name& zone_name, const RRClass& zone_class) :
+ const Name& zone_name, const RRClass& zone_class,
+ bool journaling) :
committed_(false), accessor_(accessor), zone_id_(zone_id),
db_name_(accessor->getDBName()), zone_name_(zone_name.toText()),
- zone_class_(zone_class),
+ zone_class_(zone_class), journaling_(journaling),
+ diff_phase_(NOT_STARTED),
finder_(new DatabaseClient::Finder(accessor_, zone_id_, zone_name))
{
logger.debug(DBG_TRACE_DATA, DATASRC_DATABASE_UPDATER_CREATED)
@@ -877,45 +879,97 @@ public:
virtual void commit();
private:
+ // A short cut typedef only for making the code shorter.
+ typedef DatabaseAccessor Accessor;
+
bool committed_;
shared_ptr<DatabaseAccessor> accessor_;
const int zone_id_;
const string db_name_;
const string zone_name_;
const RRClass zone_class_;
+ const bool journaling_;
+ // For the journals
+ enum DiffPhase {
+ NOT_STARTED,
+ DELETE,
+ ADD
+ };
+ DiffPhase diff_phase_;
+ uint32_t serial_;
boost::scoped_ptr<DatabaseClient::Finder> finder_;
+
+ // This is a set of validation checks commonly used for addRRset() and
+ // deleteRRset to minimize duplicate code logic and to make the main
+ // code concise.
+ void validateAddOrDelete(const char* const op_str, const RRset& rrset,
+ DiffPhase prev_phase,
+ DiffPhase current_phase) const;
};
void
-DatabaseUpdater::addRRset(const RRset& rrset) {
+DatabaseUpdater::validateAddOrDelete(const char* const op_str,
+ const RRset& rrset,
+ DiffPhase prev_phase,
+ DiffPhase current_phase) const
+{
if (committed_) {
- isc_throw(DataSourceError, "Add attempt after commit to zone: "
+ isc_throw(DataSourceError, op_str << " attempt after commit to zone: "
<< zone_name_ << "/" << zone_class_);
}
+ if (rrset.getRdataCount() == 0) {
+ isc_throw(DataSourceError, op_str << " attempt with an empty RRset: "
+ << rrset.getName() << "/" << zone_class_ << "/"
+ << rrset.getType());
+ }
if (rrset.getClass() != zone_class_) {
- isc_throw(DataSourceError, "An RRset of a different class is being "
- << "added to " << zone_name_ << "/" << zone_class_ << ": "
+ isc_throw(DataSourceError, op_str << " attempt for a different class "
+ << zone_name_ << "/" << zone_class_ << ": "
<< rrset.toText());
}
if (rrset.getRRsig()) {
- isc_throw(DataSourceError, "An RRset with RRSIG is being added to "
+ isc_throw(DataSourceError, op_str << " attempt for RRset with RRSIG "
<< zone_name_ << "/" << zone_class_ << ": "
<< rrset.toText());
}
+ if (journaling_) {
+ const RRType rrtype(rrset.getType());
+ if (rrtype == RRType::SOA() && diff_phase_ != prev_phase) {
+ isc_throw(isc::BadValue, op_str << " attempt in an invalid "
+ << "diff phase: " << diff_phase_ << ", rrset: " <<
+ rrset.toText());
+ }
+ if (rrtype != RRType::SOA() && diff_phase_ != current_phase) {
+ isc_throw(isc::BadValue, "diff state change by non SOA: "
+ << rrset.toText());
+ }
+ }
+}
+
+void
+DatabaseUpdater::addRRset(const RRset& rrset) {
+ validateAddOrDelete("add", rrset, DELETE, ADD);
+ // It's guaranteed rrset has at least one RDATA at this point.
RdataIteratorPtr it = rrset.getRdataIterator();
- if (it->isLast()) {
- isc_throw(DataSourceError, "An empty RRset is being added for "
- << rrset.getName() << "/" << zone_class_ << "/"
- << rrset.getType());
- }
- string columns[DatabaseAccessor::ADD_COLUMN_COUNT]; // initialized with ""
- columns[DatabaseAccessor::ADD_NAME] = rrset.getName().toText();
- columns[DatabaseAccessor::ADD_REV_NAME] =
- rrset.getName().reverse().toText();
- columns[DatabaseAccessor::ADD_TTL] = rrset.getTTL().toText();
- columns[DatabaseAccessor::ADD_TYPE] = rrset.getType().toText();
+ string columns[Accessor::ADD_COLUMN_COUNT]; // initialized with ""
+ columns[Accessor::ADD_NAME] = rrset.getName().toText();
+ columns[Accessor::ADD_REV_NAME] = rrset.getName().reverse().toText();
+ columns[Accessor::ADD_TTL] = rrset.getTTL().toText();
+ columns[Accessor::ADD_TYPE] = rrset.getType().toText();
+ string journal[Accessor::DIFF_PARAM_COUNT];
+ if (journaling_) {
+ journal[Accessor::DIFF_NAME] = columns[Accessor::ADD_NAME];
+ journal[Accessor::DIFF_TYPE] = columns[Accessor::ADD_TYPE];
+ journal[Accessor::DIFF_TTL] = columns[Accessor::ADD_TTL];
+ diff_phase_ = ADD;
+ if (rrset.getType() == RRType::SOA()) {
+ serial_ =
+ dynamic_cast<const generic::SOA&>(it->getCurrent()).
+ getSerial();
+ }
+ }
for (; !it->isLast(); it->next()) {
if (rrset.getType() == RRType::RRSIG()) {
// XXX: the current interface (based on the current sqlite3
@@ -925,43 +979,53 @@ DatabaseUpdater::addRRset(const RRset& rrset) {
// the interface, but until then we have to conform to the schema.
const generic::RRSIG& rrsig_rdata =
dynamic_cast<const generic::RRSIG&>(it->getCurrent());
- columns[DatabaseAccessor::ADD_SIGTYPE] =
+ columns[Accessor::ADD_SIGTYPE] =
rrsig_rdata.typeCovered().toText();
}
- columns[DatabaseAccessor::ADD_RDATA] = it->getCurrent().toText();
+ columns[Accessor::ADD_RDATA] = it->getCurrent().toText();
+ if (journaling_) {
+ journal[Accessor::DIFF_RDATA] = columns[Accessor::ADD_RDATA];
+ accessor_->addRecordDiff(zone_id_, serial_, Accessor::DIFF_ADD,
+ journal);
+ }
accessor_->addRecordToZone(columns);
}
}
void
DatabaseUpdater::deleteRRset(const RRset& rrset) {
- if (committed_) {
- isc_throw(DataSourceError, "Delete attempt after commit on zone: "
- << zone_name_ << "/" << zone_class_);
- }
- if (rrset.getClass() != zone_class_) {
- isc_throw(DataSourceError, "An RRset of a different class is being "
- << "deleted from " << zone_name_ << "/" << zone_class_
- << ": " << rrset.toText());
- }
- if (rrset.getRRsig()) {
- isc_throw(DataSourceError, "An RRset with RRSIG is being deleted from "
- << zone_name_ << "/" << zone_class_ << ": "
- << rrset.toText());
+ // If this is the first operation, pretend we are starting a new delete
+ // sequence after adds. This will simplify the validation below.
+ if (diff_phase_ == NOT_STARTED) {
+ diff_phase_ = ADD;
}
+ validateAddOrDelete("delete", rrset, ADD, DELETE);
+
RdataIteratorPtr it = rrset.getRdataIterator();
- if (it->isLast()) {
- isc_throw(DataSourceError, "An empty RRset is being deleted for "
- << rrset.getName() << "/" << zone_class_ << "/"
- << rrset.getType());
- }
- string params[DatabaseAccessor::DEL_PARAM_COUNT]; // initialized with ""
- params[DatabaseAccessor::DEL_NAME] = rrset.getName().toText();
- params[DatabaseAccessor::DEL_TYPE] = rrset.getType().toText();
+ string params[Accessor::DEL_PARAM_COUNT]; // initialized with ""
+ params[Accessor::DEL_NAME] = rrset.getName().toText();
+ params[Accessor::DEL_TYPE] = rrset.getType().toText();
+ string journal[Accessor::DIFF_PARAM_COUNT];
+ if (journaling_) {
+ journal[Accessor::DIFF_NAME] = params[Accessor::DEL_NAME];
+ journal[Accessor::DIFF_TYPE] = params[Accessor::DEL_TYPE];
+ journal[Accessor::DIFF_TTL] = rrset.getTTL().toText();
+ diff_phase_ = DELETE;
+ if (rrset.getType() == RRType::SOA()) {
+ serial_ =
+ dynamic_cast<const generic::SOA&>(it->getCurrent()).
+ getSerial();
+ }
+ }
for (; !it->isLast(); it->next()) {
- params[DatabaseAccessor::DEL_RDATA] = it->getCurrent().toText();
+ params[Accessor::DEL_RDATA] = it->getCurrent().toText();
+ if (journaling_) {
+ journal[Accessor::DIFF_RDATA] = params[Accessor::DEL_RDATA];
+ accessor_->addRecordDiff(zone_id_, serial_, Accessor::DIFF_DELETE,
+ journal);
+ }
accessor_->deleteRecordInZone(params);
}
}
@@ -973,6 +1037,9 @@ DatabaseUpdater::commit() {
<< zone_name_ << "/" << zone_class_ << " on "
<< db_name_);
}
+ if (journaling_ && diff_phase_ == DELETE) {
+ isc_throw(isc::BadValue, "Update sequence not complete");
+ }
accessor_->commit();
committed_ = true; // make sure the destructor won't trigger rollback
@@ -986,7 +1053,13 @@ DatabaseUpdater::commit() {
// The updater factory
ZoneUpdaterPtr
-DatabaseClient::getUpdater(const isc::dns::Name& name, bool replace) const {
+DatabaseClient::getUpdater(const isc::dns::Name& name, bool replace,
+ bool journaling) const
+{
+ if (replace && journaling) {
+ isc_throw(isc::BadValue, "Can't store journal and replace the whole "
+ "zone at the same time");
+ }
shared_ptr<DatabaseAccessor> update_accessor(accessor_->clone());
const std::pair<bool, int> zone(update_accessor->startUpdateZone(
name.toText(), replace));
@@ -995,7 +1068,7 @@ DatabaseClient::getUpdater(const isc::dns::Name& name, bool replace) const {
}
return (ZoneUpdaterPtr(new DatabaseUpdater(update_accessor, zone.second,
- name, rrclass_)));
+ name, rrclass_, journaling)));
}
}
}
diff --git a/src/lib/datasrc/database.h b/src/lib/datasrc/database.h
index b3fda6d..765fabc 100644
--- a/src/lib/datasrc/database.h
+++ b/src/lib/datasrc/database.h
@@ -878,7 +878,8 @@ public:
/// accessor. The returned updater will be able to work separately from
/// the original client.
virtual ZoneUpdaterPtr getUpdater(const isc::dns::Name& name,
- bool replace) const;
+ bool replace,
+ bool journaling = false) const;
private:
/// \brief The RR class that this client handles.
diff --git a/src/lib/datasrc/memory_datasrc.cc b/src/lib/datasrc/memory_datasrc.cc
index 6c0f589..765b50f 100644
--- a/src/lib/datasrc/memory_datasrc.cc
+++ b/src/lib/datasrc/memory_datasrc.cc
@@ -815,7 +815,7 @@ InMemoryClient::getIterator(const Name& name, bool) const {
}
ZoneUpdaterPtr
-InMemoryClient::getUpdater(const isc::dns::Name&, bool) const {
+InMemoryClient::getUpdater(const isc::dns::Name&, bool, bool) const {
isc_throw(isc::NotImplemented, "Update attempt on in memory data source");
}
diff --git a/src/lib/datasrc/memory_datasrc.h b/src/lib/datasrc/memory_datasrc.h
index 1b6c120..15e7058 100644
--- a/src/lib/datasrc/memory_datasrc.h
+++ b/src/lib/datasrc/memory_datasrc.h
@@ -284,7 +284,8 @@ public:
/// to update via its updater (this may or may not be a good idea and
/// is subject to further discussions).
virtual ZoneUpdaterPtr getUpdater(const isc::dns::Name& name,
- bool replace) const;
+ bool replace, bool journaling = false)
+ const;
private:
// TODO: Do we still need the PImpl if nobody should manipulate this class
diff --git a/src/lib/datasrc/tests/client_unittest.cc b/src/lib/datasrc/tests/client_unittest.cc
index 5b2c91a..ade6fc7 100644
--- a/src/lib/datasrc/tests/client_unittest.cc
+++ b/src/lib/datasrc/tests/client_unittest.cc
@@ -32,7 +32,9 @@ public:
virtual FindResult findZone(const isc::dns::Name&) const {
return (FindResult(result::NOTFOUND, ZoneFinderPtr()));
}
- virtual ZoneUpdaterPtr getUpdater(const isc::dns::Name&, bool) const {
+ virtual ZoneUpdaterPtr getUpdater(const isc::dns::Name&, bool, bool)
+ const
+ {
return (ZoneUpdaterPtr());
}
};
diff --git a/src/lib/datasrc/tests/database_unittest.cc b/src/lib/datasrc/tests/database_unittest.cc
index 1a471bf..b74bb27 100644
--- a/src/lib/datasrc/tests/database_unittest.cc
+++ b/src/lib/datasrc/tests/database_unittest.cc
@@ -13,6 +13,7 @@
// PERFORMANCE OF THIS SOFTWARE.
#include <boost/shared_ptr.hpp>
+#include <boost/lexical_cast.hpp>
#include <gtest/gtest.h>
@@ -37,6 +38,7 @@ using namespace std;
// for some systems.
using boost::shared_ptr;
using boost::dynamic_pointer_cast;
+using boost::lexical_cast;
using namespace isc::dns;
namespace {
@@ -264,6 +266,52 @@ private:
};
+/**
+ * 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.
@@ -288,6 +336,7 @@ public:
readonly_records_ = &readonly_records_master_;
update_records_ = &update_records_master_;
empty_records_ = &empty_records_master_;
+ journal_entries_ = &journal_entries_master_;
fillData();
}
@@ -296,6 +345,7 @@ public:
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);
}
@@ -544,7 +594,13 @@ public:
*update_records_ = *readonly_records_;
}
- return (pair<bool, int>(true, WRITABLE_ZONE_ID));
+ 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_;
@@ -658,6 +714,30 @@ public:
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));
+ }
+ }
+
+ // Check the journal is as expected and clear the journal
+ void checkJournal(const std::vector<JournalEntry> &expected) {
+ 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
@@ -677,6 +757,10 @@ private:
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
@@ -794,6 +878,10 @@ public:
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(),
@@ -895,6 +983,7 @@ public:
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_;
@@ -2703,4 +2792,181 @@ TEST_F(MockDatabaseClientTest, badName) {
DataSourceError);
}
+/*
+ * Test correct use of the updater with a journal.
+ */
+TEST_F(MockDatabaseClientTest, journal) {
+ updater_ = client_->getUpdater(zname_, false, true);
+ updater_->deleteRRset(*soa_);
+ updater_->deleteRRset(*rrset_);
+ soa_.reset(new RRset(zname_, qclass_, RRType::SOA(), rrttl_));
+ soa_->addRdata(rdata::createRdata(soa_->getType(), soa_->getClass(),
+ "ns1.example.org. admin.example.org. "
+ "1235 3600 1800 2419200 7200"));
+ updater_->addRRset(*soa_);
+ updater_->addRRset(*rrset_);
+ ASSERT_NO_THROW(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"));
+ current_accessor_->checkJournal(expected);
+}
+
+/*
+ * Push multiple delete-add sequences. Checks it is allowed and all is
+ * saved.
+ */
+TEST_F(MockDatabaseClientTest, journalMultiple) {
+ std::vector<JournalEntry> expected;
+ updater_ = client_->getUpdater(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
+ updater_->deleteRRset(*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";
+ soa_.reset(new RRset(zname_, qclass_, RRType::SOA(), rrttl_));
+ soa_->addRdata(rdata::createRdata(soa_->getType(), soa_->getClass(),
+ soa_rdata));
+ // Add the new SOA
+ updater_->addRRset(*soa_);
+ expected.push_back(JournalEntry(WRITABLE_ZONE_ID, 1234 + i,
+ DatabaseAccessor::DIFF_ADD,
+ "example.org.", "SOA", "3600",
+ soa_rdata));
+ }
+ ASSERT_NO_THROW(updater_->commit());
+ // Check the journal contains everything.
+ current_accessor_->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.
+ */
+TEST_F(MockDatabaseClientTest, journalBadSequence) {
+ std::vector<JournalEntry> expected;
+ {
+ SCOPED_TRACE("Delete A before SOA");
+ updater_ = client_->getUpdater(zname_, false, true);
+ EXPECT_THROW(updater_->deleteRRset(*rrset_), isc::BadValue);
+ // Make sure the journal is empty now
+ current_accessor_->checkJournal(expected);
+ }
+
+ {
+ SCOPED_TRACE("Add before delete");
+ updater_ = client_->getUpdater(zname_, false, true);
+ EXPECT_THROW(updater_->addRRset(*soa_), isc::BadValue);
+ // Make sure the journal is empty now
+ current_accessor_->checkJournal(expected);
+ }
+
+ {
+ SCOPED_TRACE("Add A before SOA");
+ updater_ = client_->getUpdater(zname_, false, true);
+ // So far OK
+ EXPECT_NO_THROW(updater_->deleteRRset(*soa_));
+ // But we miss the add SOA here
+ EXPECT_THROW(updater_->addRRset(*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"));
+ current_accessor_->checkJournal(expected);
+ }
+
+ {
+ SCOPED_TRACE("Commit before add");
+ updater_ = client_->getUpdater(zname_, false, true);
+ // So far OK
+ EXPECT_NO_THROW(updater_->deleteRRset(*soa_));
+ // Commit at the wrong time
+ EXPECT_THROW(updater_->commit(), isc::BadValue);
+ current_accessor_->checkJournal(expected);
+ }
+
+ {
+ SCOPED_TRACE("Delete two SOAs");
+ updater_ = client_->getUpdater(zname_, false, true);
+ // So far OK
+ EXPECT_NO_THROW(updater_->deleteRRset(*soa_));
+ // Delete the SOA again
+ EXPECT_THROW(updater_->deleteRRset(*soa_), isc::BadValue);
+ current_accessor_->checkJournal(expected);
+ }
+
+ {
+ SCOPED_TRACE("Add two SOAs");
+ updater_ = client_->getUpdater(zname_, false, true);
+ // So far OK
+ EXPECT_NO_THROW(updater_->deleteRRset(*soa_));
+ // Still OK
+ EXPECT_NO_THROW(updater_->addRRset(*soa_));
+ // But this one is added again
+ EXPECT_THROW(updater_->addRRset(*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"));
+ current_accessor_->checkJournal(expected);
+ }
+}
+
+/*
+ * Test it rejects to store journals when we request it together with
+ * erasing the whole zone.
+ */
+TEST_F(MockDatabaseClientTest, journalOnErase) {
+ EXPECT_THROW(client_->getUpdater(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);
+}
+
}
diff --git a/src/lib/datasrc/zone.h b/src/lib/datasrc/zone.h
index fa1c744..f824636 100644
--- a/src/lib/datasrc/zone.h
+++ b/src/lib/datasrc/zone.h
@@ -438,6 +438,10 @@ public:
/// calls after \c commit() the implementation must throw a
/// \c DataSourceError exception.
///
+ /// If journaling was requested when getting this updater, it will reject
+ /// to add the RRset if the squence doesn't look like and IXFR (see
+ /// DataSourceClient::getUpdater). In such case isc::BadValue is thrown.
+ ///
/// \todo As noted above we may have to revisit the design details as we
/// gain experiences:
///
@@ -454,6 +458,8 @@ public:
///
/// \exception DataSourceError Called after \c commit(), RRset is invalid
/// (see above), internal data source error
+ /// \exception isc::BadValue Journaling is enabled and the current RRset
+ /// doesn't fit into the IXFR sequence (see above).
/// \exception std::bad_alloc Resource allocation failure
///
/// \param rrset The RRset to be added
@@ -503,6 +509,10 @@ public:
/// calls after \c commit() the implementation must throw a
/// \c DataSourceError exception.
///
+ /// If journaling was requested when getting this updater, it will reject
+ /// to add the RRset if the squence doesn't look like and IXFR (see
+ /// DataSourceClient::getUpdater). In such case isc::BadValue is thrown.
+ ///
/// \todo As noted above we may have to revisit the design details as we
/// gain experiences:
///
@@ -520,6 +530,8 @@ public:
///
/// \exception DataSourceError Called after \c commit(), RRset is invalid
/// (see above), internal data source error
+ /// \exception isc::BadValue Journaling is enabled and the current RRset
+ /// doesn't fit into the IXFR sequence (see above).
/// \exception std::bad_alloc Resource allocation failure
///
/// \param rrset The RRset to be deleted
@@ -540,6 +552,8 @@ public:
///
/// \exception DataSourceError Duplicate call of the method,
/// internal data source error
+ /// \exception isc::BadValue Journaling is enabled and the update is not
+ /// complete IXFR sequence.
virtual void commit() = 0;
};
diff --git a/src/lib/dns/rdata/generic/soa_6.cc b/src/lib/dns/rdata/generic/soa_6.cc
index 7ecd84f..875a957 100644
--- a/src/lib/dns/rdata/generic/soa_6.cc
+++ b/src/lib/dns/rdata/generic/soa_6.cc
@@ -106,6 +106,12 @@ SOA::toWire(AbstractMessageRenderer& renderer) const {
renderer.writeData(numdata_, sizeof(numdata_));
}
+uint32_t
+SOA::getSerial() const {
+ InputBuffer b(numdata_, sizeof(numdata_));
+ return (b.readUint32());
+}
+
string
SOA::toText() const {
InputBuffer b(numdata_, sizeof(numdata_));
diff --git a/src/lib/dns/rdata/generic/soa_6.h b/src/lib/dns/rdata/generic/soa_6.h
index 3f6185e..4c6b6ec 100644
--- a/src/lib/dns/rdata/generic/soa_6.h
+++ b/src/lib/dns/rdata/generic/soa_6.h
@@ -34,6 +34,8 @@ public:
SOA(const Name& mname, const Name& rname, uint32_t serial,
uint32_t refresh, uint32_t retry, uint32_t expire,
uint32_t minimum);
+ /// \brief Returns the serial stored in the SOA.
+ uint32_t getSerial() const;
private:
/// Note: this is a prototype version; we may reconsider
/// this representation later.
diff --git a/src/lib/dns/tests/rdata_soa_unittest.cc b/src/lib/dns/tests/rdata_soa_unittest.cc
index 63fe1f7..17498eb 100644
--- a/src/lib/dns/tests/rdata_soa_unittest.cc
+++ b/src/lib/dns/tests/rdata_soa_unittest.cc
@@ -74,4 +74,9 @@ TEST_F(Rdata_SOA_Test, toText) {
EXPECT_EQ("ns.example.com. root.example.com. "
"2010012601 3600 300 3600000 1200", rdata_soa.toText());
}
+
+TEST_F(Rdata_SOA_Test, getSerial) {
+ EXPECT_EQ(2010012601, rdata_soa.getSerial());
+}
+
}
More information about the bind10-changes
mailing list