this is the first step to support updating the NSEC3 namespace of a zone.
to help support various cases in addRRset() in a less expensive way,
introduced a helper RRParameterConverter class.
one simple test case was added to confirm the behavior.
}
}
+// This is a helper class used in adding/deleting RRsets to/from a database.
+// The purpose of this class is to provide conversion interface from various
+// parameters of the RRset to corresponding textual representations that the
+// underlying database interface expects. The necessary parameters and how
+// to convert them depend on several things, such as whether it's NSEC3 related
+// or not, or whether journaling is requested. In order to avoid unnecessary
+// conversion, this class also performs the conversion in a lazy manner.
+// Also, in order to avoid redundant conversion when the conversion is
+// requested for the same parameter multiple times, it remembers the
+// conversion result first time, and reuses it for subsequent requests
+// (this implicitly assumes copying std::string objects is not very expensive;
+// this is often the case in some common implementations that have
+// copy-on-write semantics for the string class).
+class RRParameterConverter {
+public:
+ RRParameterConverter(const AbstractRRset& rrset) : rrset_(rrset)
+ {}
+ const string& getName() {
+ if (name_.empty()) {
+ name_ = rrset_.getName().toText();
+ }
+ return (name_);
+ }
+ const string& getNSEC3Name() {
+ if (nsec3_name_.empty()) {
+ nsec3_name_ = rrset_.getName().split(0, 1).toText(true);
+ }
+ return (nsec3_name_);
+ }
+ const string& getRevName() {
+ if (revname_.empty()) {
+ revname_ = rrset_.getName().reverse().toText();
+ }
+ return (revname_);
+ }
+ const string& getTTL() {
+ if (ttl_.empty()) {
+ ttl_ = rrset_.getTTL().toText();
+ }
+ return (ttl_);
+ }
+ const string& getType() {
+ if (type_.empty()) {
+ type_ = rrset_.getType().toText();
+ }
+ return (type_);
+ }
+
+private:
+ string name_;
+ string nsec3_name_;
+ string revname_;
+ string ttl_;
+ string type_;
+ const AbstractRRset& rrset_;
+};
+
void
DatabaseUpdater::addRRset(const AbstractRRset& rrset) {
validateAddOrDelete("add", rrset, DELETE, ADD);
// It's guaranteed rrset has at least one RDATA at this point.
RdataIteratorPtr it = rrset.getRdataIterator();
-
- 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()).
+ serial_ = dynamic_cast<const generic::SOA&>(it->getCurrent()).
getSerial();
}
}
+
+ RRParameterConverter cvtr(rrset);
for (; !it->isLast(); it->next()) {
+ string sigtype;
if (rrset.getType() == RRType::RRSIG()) {
// XXX: the current interface (based on the current sqlite3
// data source schema) requires a separate "sigtype" column,
// 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[Accessor::ADD_SIGTYPE] =
- rrsig_rdata.typeCovered().toText();
+ sigtype = rrsig_rdata.typeCovered().toText();
}
- columns[Accessor::ADD_RDATA] = it->getCurrent().toText();
+ const string rdata = it->getCurrent().toText();
if (journaling_) {
- journal[Accessor::DIFF_RDATA] = columns[Accessor::ADD_RDATA];
+ const string journal[Accessor::DIFF_PARAM_COUNT] =
+ { cvtr.getName(), cvtr.getType(), cvtr.getTTL(), rdata };
accessor_->addRecordDiff(zone_id_, serial_.getValue(),
Accessor::DIFF_ADD, journal);
}
- accessor_->addRecordToZone(columns);
+ if (rrset.getType() == RRType::NSEC3()) {
+ const string nsec3_columns[Accessor::ADD_NSEC3_COLUMN_COUNT] =
+ { cvtr.getNSEC3Name(), cvtr.getTTL(), cvtr.getType(), rdata };
+ accessor_->addRecordToNSEC3Zone(nsec3_columns);
+ } else {
+ const string columns[Accessor::ADD_COLUMN_COUNT] =
+ { cvtr.getName(), cvtr.getRevName(), cvtr.getTTL(),
+ cvtr.getType(), sigtype, rdata };
+ accessor_->addRecordToZone(columns);
+ }
}
}
ADD_COLUMN_COUNT = 6 ///< Number of columns
};
+ enum AddNSEC3RecordColumns {
+ ADD_NSEC3_HASH = 0, ///< The owner name of the record (a domain name)
+ ADD_NSEC3_TTL = 1, ///< The TTL of the record (in numeric form)
+ ADD_NSEC3_TYPE = 2, ///< The RRType of the record (either NSEC3 or
+ ///< RRSIG)
+ ADD_NSEC3_RDATA = 3, ///< Full text representation of the record's
+ ///< RDATA
+ ADD_NSEC3_COLUMN_COUNT = 4 ///< Number of columns
+ };
+
/// \brief Definitions of the fields to be passed to deleteRecordInZone()
///
/// Each derived implementation of deleteRecordInZone() should expect
virtual void addRecordToZone(
const std::string (&columns)[ADD_COLUMN_COUNT]) = 0;
+ virtual void addRecordToNSEC3Zone(
+ const std::string (&columns)[ADD_NSEC3_COLUMN_COUNT]) = 0;
+
/// \brief Delete a single record from the zone to be updated.
///
/// This method provides a simple interface to delete a record
/// This is used to find previous NSEC3 hashes, to find covering NSEC3 in
/// case none match exactly.
///
- /// In case a hash before before the lowest or the lowest is provided,
+ /// In case a hash before the lowest or the lowest is provided,
/// this should return the largest one in the zone (NSEC3 needs a
/// wrap-around semantics).
///
*dbparameters_, ADD_RECORD, columns, "add record to zone");
}
+void
+SQLite3Accessor::addRecordToNSEC3Zone(
+ const string (&/*columns*/)[ADD_NSEC3_COLUMN_COUNT])
+{
+ isc_throw(NotImplemented, "not yet implemented");
+}
+
void
SQLite3Accessor::deleteRecordInZone(const string (¶ms)[DEL_PARAM_COUNT]) {
if (!dbparameters_->updating_zone) {
virtual void addRecordToZone(
const std::string (&columns)[ADD_COLUMN_COUNT]);
+ virtual void addRecordToNSEC3Zone(
+ const std::string (&columns)[ADD_NSEC3_COLUMN_COUNT]);
+
virtual void deleteRecordInZone(
const std::string (¶ms)[DEL_PARAM_COUNT]);
#include <exceptions/exceptions.h>
+#include <dns/masterload.h>
#include <dns/name.h>
#include <dns/rrttl.h>
#include <dns/rrset.h>
#include <gtest/gtest.h>
+#include <boost/bind.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/lexical_cast.hpp>
virtual void commit() {}
virtual void rollback() {}
virtual void addRecordToZone(const string (&)[ADD_COLUMN_COUNT]) {}
+ virtual void addRecordToNSEC3Zone(const string (&)[ADD_NSEC3_COLUMN_COUNT])
+ {}
virtual void deleteRecordInZone(const string (&)[DEL_PARAM_COUNT]) {}
virtual void addRecordDiff(int, uint32_t, DiffOperation,
const std::string (&)[DIFF_PARAM_COUNT]) {}
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
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.
columns_lastadded_);
}
+ virtual void addRecordToNSEC3Zone(
+ const string (&columns)[ADD_NSEC3_COLUMN_COUNT])
+ {
+ columns_lastadded_[ADD_NAME] = columns[ADD_NSEC3_HASH];
+ columns_lastadded_[ADD_REV_NAME].clear();
+ columns_lastadded_[ADD_TTL] = columns[ADD_NSEC3_TTL];
+ columns_lastadded_[ADD_TYPE] = columns[ADD_NSEC3_TYPE];
+ columns_lastadded_[ADD_SIGTYPE].clear();
+ columns_lastadded_[ADD_RDATA] = columns[ADD_NSEC3_RDATA];
+ }
+
// Helper predicate class used in deleteRecordInZone().
struct deleteMatch {
deleteMatch(const string& type, const string& rdata) :
this->checkLastAdded(rrset_added);
}
+// TODO: maybe we should share this stuff
+// A helper callback of masterLoad() used in InMemoryZoneFinderTest.
+void
+setRRset(RRsetPtr rrset, vector<RRsetPtr*>::iterator& it) {
+ *(*it) = rrset;
+ ++it;
+}
+
+ConstRRsetPtr
+textToRRset(const string& text_rrset, const RRClass& rrclass = RRClass::IN()) {
+ stringstream ss(text_rrset);
+ RRsetPtr rrset;
+ vector<RRsetPtr*> rrsets;
+ rrsets.push_back(&rrset);
+ masterLoad(ss, Name::ROOT_NAME(), rrclass,
+ boost::bind(setRRset, _1, rrsets.begin()));
+ return (rrset);
+}
+
+// Right now this only works for the mock DB, but the plan is to make it
+// a TYPED_TEST to share the case with SQLite3 implementation, too.
+TEST_F(MockDatabaseClientTest, addNSEC3ToZone) {
+ const char* const nsec3_hash = "1BB7SO0452U1QHL98UISNDD9218GELR5";
+ const char* const nsec3_rdata = "1 1 12 AABBCCDD "
+ "2T7B4G4VSA5SMI47K61MV5BV1A22BOJR NS SOA RRSIG NSEC3PARAM";
+
+ this->updater_ = this->client_->getUpdater(this->zname_, true);
+ this->updater_->addRRset(
+ *textToRRset(string(nsec3_hash) + ".example.org. 3600 IN NSEC3 " +
+ string(nsec3_rdata)));
+ this->updater_->commit();
+ const char* const rrset_added[] = {
+ nsec3_hash, "", "3600", "NSEC3", "", nsec3_rdata };
+ 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());