Commit f93cd951 authored by JINMEI Tatuya's avatar JINMEI Tatuya

[master] Merge branch 'trac1781'

parents c3be9fa4 a318061e
......@@ -1415,49 +1415,132 @@ DatabaseUpdater::validateAddOrDelete(const char* const op_str,
}
}
// 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_;
};
namespace {
// A shared shortcut to detect if the given type of RDATA is NSEC3 or
// RRSIG covering NSEC3. RRSIG for NSEC3 should go to the (conceptual)
// separate namespace, so we need to check the covered type.
// Note: in principle the type covered should be the same for
// all RDATA, but the RRset interface doesn't ensure that condition.
// So we explicitly check that for every RDATA below.
bool
isNSEC3KindType(RRType rrtype, const Rdata& rdata) {
if (rrtype == RRType::NSEC3()) {
return (true);
}
if (rrtype == RRType::RRSIG() &&
dynamic_cast<const generic::RRSIG&>(rdata).typeCovered() ==
RRType::NSEC3())
{
return (true);
}
return (false);
}
}
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()) {
const Rdata& rdata = it->getCurrent();
const bool nsec3_type = isNSEC3KindType(rrset.getType(), rdata);
string sigtype;
if (rrset.getType() == RRType::RRSIG()) {
// XXX: the current interface (based on the current sqlite3
// data source schema) requires a separate "sigtype" column,
// even though it won't be used in a newer implementation.
// We should eventually clean up the schema design and simplify
// 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 = dynamic_cast<const generic::RRSIG&>(rdata).
typeCovered().toText();
}
columns[Accessor::ADD_RDATA] = it->getCurrent().toText();
const string& rdata_txt = rdata.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_txt };
accessor_->addRecordDiff(zone_id_, serial_.getValue(),
Accessor::DIFF_ADD, journal);
}
accessor_->addRecordToZone(columns);
if (nsec3_type) {
const string nsec3_columns[Accessor::ADD_NSEC3_COLUMN_COUNT] =
{ cvtr.getNSEC3Name(), cvtr.getTTL(), cvtr.getType(),
rdata_txt };
accessor_->addNSEC3RecordToZone(nsec3_columns);
} else {
const string columns[Accessor::ADD_COLUMN_COUNT] =
{ cvtr.getName(), cvtr.getRevName(), cvtr.getTTL(),
cvtr.getType(), sigtype, rdata_txt };
accessor_->addRecordToZone(columns);
}
}
}
......@@ -1472,15 +1555,7 @@ DatabaseUpdater::deleteRRset(const AbstractRRset& rrset) {
validateAddOrDelete("delete", rrset, ADD, DELETE);
RdataIteratorPtr it = rrset.getRdataIterator();
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_ =
......@@ -1488,14 +1563,27 @@ DatabaseUpdater::deleteRRset(const AbstractRRset& rrset) {
getSerial();
}
}
RRParameterConverter cvtr(rrset);
for (; !it->isLast(); it->next()) {
params[Accessor::DEL_RDATA] = it->getCurrent().toText();
const Rdata& rdata = it->getCurrent();
const bool nsec3_type = isNSEC3KindType(rrset.getType(), rdata);
const string& rdata_txt = it->getCurrent().toText();
if (journaling_) {
journal[Accessor::DIFF_RDATA] = params[Accessor::DEL_RDATA];
const string journal[Accessor::DIFF_PARAM_COUNT] =
{ cvtr.getName(), cvtr.getType(), cvtr.getTTL(), rdata_txt };
accessor_->addRecordDiff(zone_id_, serial_.getValue(),
Accessor::DIFF_DELETE, journal);
}
accessor_->deleteRecordInZone(params);
const string params[Accessor::DEL_PARAM_COUNT] =
{ nsec3_type ? cvtr.getNSEC3Name() : cvtr.getName(),
cvtr.getType(), rdata_txt };
if (nsec3_type) {
accessor_->deleteNSEC3RecordInZone(params);
} else {
accessor_->deleteRecordInZone(params);
}
}
}
......
......@@ -95,13 +95,36 @@ public:
ADD_COLUMN_COUNT = 6 ///< Number of columns
};
/// \brief Definitions of the fields to be passed to addNSEC3RecordToZone()
///
/// Each derived implementation of addNSEC3RecordToZone() should expect
/// the "columns" array to be filled with the values as described in this
/// enumeration, in this order.
///
/// Note that there is no "reversed name" column. Since the conceptual
/// separate namespace for NSEC3 is very simplified and essentially only
/// consists of a single-label names, there is no need for using reversed
/// names to identify the "previous hash".
enum AddNSEC3RecordColumns {
ADD_NSEC3_HASH = 0, ///< The hash (1st) label of the owner name,
///< excluding the dot character
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 for NSEC3)
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()
/// and deleteNSEC3RecordInZone()
///
/// Each derived implementation of deleteRecordInZone() should expect
/// the "params" array to be filled with the values as described in this
/// enumeration, in this order.
enum DeleteRecordParams {
DEL_NAME = 0, ///< The owner name of the record (a domain name)
///< or the hash label for deleteNSEC3RecordInZone()
DEL_TYPE = 1, ///< The RRType of the record (A/NS/TXT etc.)
DEL_RDATA = 2, ///< Full text representation of the record's RDATA
DEL_PARAM_COUNT = 3 ///< Number of parameters
......@@ -432,6 +455,46 @@ public:
virtual void addRecordToZone(
const std::string (&columns)[ADD_COLUMN_COUNT]) = 0;
/// \brief Add a single NSEC3-related record to the zone to be updated.
///
/// This method is similar to \c addRecordToZone(), but is expected to
/// be only used for NSEC3 RRs or RRSIG RRs that cover NSEC3. In terms
/// of the DNS protocol, these types of RRs reside in a separate space
/// of the zone. While this interface does not mandate a specific way
/// of implementing the separate namespaces in the underlying database,
/// it would be more convenient for the underlying implementation if the
/// interfaces are separated; for example, the implementation does not
/// have to examine the given data to identify the appropriate namespace.
///
/// An implementation may choose to skip providing this interface if the
/// zones managed by that data source are known to not support NSEC3.
/// In that case the implementation should throw the
/// \c isc::NotImplemented exception.
///
/// Note that the \c ADD_NSEC3_HASH column of \c columns is expected to
/// store only the hash label, not the entire owner name. This is similar
/// to the \c hash parameter of \c getNSEC3Records().
///
/// The RRs to be added using this method are expected to be limited to
/// NSEC3 or RRSIG RRs that cover NSEC3, but it's generally assumed to
/// be the caller's responsibility to ensure that; the implementation
/// is not required to check that condition. The result of adding
/// unexpected type of RRs (and the result of subsequent lookups) is
/// undefined.
///
/// Other general notes for \c addRecordToZone() also apply to this
/// method.
///
/// \exception DataSourceError Invalid call without starting a transaction,
/// or other internal database error.
/// \exception isc::NotImplemented in case the database does not support
/// NSEC3
///
/// \param columns An array of strings that defines a record to be added
/// to the NSEC3 namespace of the zone.
virtual void addNSEC3RecordToZone(
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
......@@ -469,6 +532,31 @@ public:
virtual void deleteRecordInZone(
const std::string (&params)[DEL_PARAM_COUNT]) = 0;
/// \brief Delete a single NSEC3-related record from the zone to be
/// updated.
///
/// This method is similar to \c deleteRecordInZone(), but is expected to
/// be only used for NSEC3 RRs or RRSIG RRs that cover NSEC3. The
/// relationship between these two methods is similar to that between
/// \c addRecordToZone() and \c addNSEC3RecordToZone(), and the same
/// notes apply to this method.
///
/// This method uses the same set of parameters to specify the record
/// to be deleted as \c deleteRecordInZone(), but the \c DEL_NAME column
/// is expected to only store the hash label of the owner name.
/// This is the same as \c ADD_NSEC3_HASH column for
/// \c addNSEC3RecordToZone().
///
/// \exception DataSourceError Invalid call without starting a transaction,
/// or other internal database error.
/// \exception isc::NotImplemented in case the database does not support
/// NSEC3
///
/// \param params An array of strings that defines a record to be deleted
/// from the NSEC3 namespace of the zone.
virtual void deleteNSEC3RecordInZone(
const std::string (&params)[DEL_PARAM_COUNT]) = 0;
/// \brief Start a general transaction.
///
/// Each derived class version of this method starts a database
......
......@@ -1132,6 +1132,13 @@ SQLite3Accessor::addRecordToZone(const string (&columns)[ADD_COLUMN_COUNT]) {
*dbparameters_, ADD_RECORD, columns, "add record to zone");
}
void
SQLite3Accessor::addNSEC3RecordToZone(
const string (&/*columns*/)[ADD_NSEC3_COLUMN_COUNT])
{
isc_throw(NotImplemented, "not yet implemented");
}
void
SQLite3Accessor::deleteRecordInZone(const string (&params)[DEL_PARAM_COUNT]) {
if (!dbparameters_->updating_zone) {
......@@ -1142,6 +1149,13 @@ SQLite3Accessor::deleteRecordInZone(const string (&params)[DEL_PARAM_COUNT]) {
*dbparameters_, DEL_RECORD, params, "delete record from zone");
}
void
SQLite3Accessor::deleteNSEC3RecordInZone(
const string (&/*params*/)[DEL_PARAM_COUNT])
{
isc_throw(NotImplemented, "not yet implemented");
}
void
SQLite3Accessor::addRecordDiff(int zone_id, uint32_t serial,
DiffOperation operation,
......
......@@ -214,9 +214,15 @@ public:
virtual void addRecordToZone(
const std::string (&columns)[ADD_COLUMN_COUNT]);
virtual void addNSEC3RecordToZone(
const std::string (&columns)[ADD_NSEC3_COLUMN_COUNT]);
virtual void deleteRecordInZone(
const std::string (&params)[DEL_PARAM_COUNT]);
virtual void deleteNSEC3RecordInZone(
const std::string (&params)[DEL_PARAM_COUNT]);
/// This derived version of the method prepares an SQLite3 statement
/// for adding the diff first time it's called, and if it fails throws
// an \c SQLite3Error exception.
......
......@@ -14,15 +14,9 @@
#include "faked_nsec3.h"
#include <stdlib.h>
#include <boost/shared_ptr.hpp>
#include <boost/lexical_cast.hpp>
#include <gtest/gtest.h>
#include <exceptions/exceptions.h>
#include <dns/masterload.h>
#include <dns/name.h>
#include <dns/rrttl.h>
#include <dns/rrset.h>
......@@ -37,6 +31,13 @@
#include <testutils/dnsmessage_test.h>
#include <gtest/gtest.h>
#include <boost/bind.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/lexical_cast.hpp>
#include <cstdlib>
#include <map>
#include <vector>
......@@ -47,6 +48,7 @@ using namespace std;
using boost::dynamic_pointer_cast;
using boost::lexical_cast;
using namespace isc::dns;
using namespace isc::testutils;
using namespace isc::datasrc::test;
namespace {
......@@ -261,7 +263,10 @@ public:
virtual void commit() {}
virtual void rollback() {}
virtual void addRecordToZone(const string (&)[ADD_COLUMN_COUNT]) {}
virtual void addNSEC3RecordToZone(const string (&)[ADD_NSEC3_COLUMN_COUNT])
{}
virtual void deleteRecordInZone(const string (&)[DEL_PARAM_COUNT]) {}
virtual void deleteNSEC3RecordInZone(const string (&)[DEL_PARAM_COUNT]) {}
virtual void addRecordDiff(int, uint32_t, DiffOperation,
const std::string (&)[DIFF_PARAM_COUNT]) {}
......@@ -374,6 +379,8 @@ public:
MockAccessor() : rollbacked_(false), did_transaction_(false) {
readonly_records_ = &readonly_records_master_;
update_records_ = &update_records_master_;
nsec3_namespace_ = &nsec3_namespace_master_;
update_nsec3_namespace_ = &update_nsec3_namespace_master_;
empty_records_ = &empty_records_master_;
journal_entries_ = &journal_entries_master_;
fillData();
......@@ -383,6 +390,9 @@ public:
boost::shared_ptr<MockAccessor> cloned_accessor(new MockAccessor());
cloned_accessor->readonly_records_ = &readonly_records_master_;
cloned_accessor->update_records_ = &update_records_master_;
cloned_accessor->nsec3_namespace_ = &nsec3_namespace_master_;
cloned_accessor->update_nsec3_namespace_ =
&update_nsec3_namespace_master_;
cloned_accessor->empty_records_ = &empty_records_master_;
cloned_accessor->journal_entries_ = &journal_entries_master_;
latest_clone_ = cloned_accessor;
......@@ -649,8 +659,8 @@ public:
virtual IteratorContextPtr getNSEC3Records(const std::string& hash,
int) const
{
Domains::const_iterator it(nsec3_namespace_.find(hash));
if (it == nsec3_namespace_.end()) {
Domains::const_iterator it(nsec3_namespace_->find(hash));
if (it == nsec3_namespace_->end()) {
return (IteratorContextPtr(new EmptyIteratorContext()));
} else {
return (IteratorContextPtr(new DomainIterator(it->second)));
......@@ -670,8 +680,10 @@ public:
// original.
if (replace) {
update_records_->clear();
update_nsec3_namespace_->clear();
} else {
*update_records_ = *readonly_records_;
*update_nsec3_namespace_ = nsec3_namespace_master_;
}
if (zone_name == "bad.example.org.") {
......@@ -684,7 +696,9 @@ public:
}
virtual void commit() {
*readonly_records_ = *update_records_;
*nsec3_namespace_ = *update_nsec3_namespace_;
}
virtual void rollback() {
// Special hook: if something with a name of "throw.example.org"
// has been added, trigger an imaginary unexpected event with an
......@@ -695,27 +709,54 @@ public:
rollbacked_ = true;
}
virtual void addRecordToZone(const string (&columns)[ADD_COLUMN_COUNT]) {
private:
// Common subroutine for addRecordToZone and addNSEC3RecordToZone.
void addRecord(Domains& domains,
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]];
cur_name_ = domains[columns[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]);
record_columns.push_back(columns[ADD_TYPE]);
record_columns.push_back(columns[ADD_TTL]);
record_columns.push_back(columns[ADD_SIGTYPE]);
record_columns.push_back(columns[ADD_RDATA]);
record_columns.push_back(columns[ADD_NAME]);
// copy back the added entry
cur_name_.push_back(record_columns);
(*update_records_)[columns[DatabaseAccessor::ADD_NAME]] = cur_name_;
domains[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_);
}
public:
virtual void addRecordToZone(const string (&columns)[ADD_COLUMN_COUNT]) {
addRecord(*update_records_, columns);
}
virtual void addNSEC3RecordToZone(
const string (&columns)[ADD_NSEC3_COLUMN_COUNT])
{
// Convert the NSEC3 parameters in the normal (non NSEC3) style so
// we can share the merge code, and then update using addRecord().
string normal_columns[ADD_COLUMN_COUNT];
normal_columns[ADD_TYPE] = columns[ADD_NSEC3_TYPE];
normal_columns[ADD_TTL] = columns[ADD_NSEC3_TTL];
normal_columns[ADD_SIGTYPE] = "";
normal_columns[ADD_RDATA] = columns[ADD_NSEC3_RDATA];
normal_columns[ADD_NAME] = columns[ADD_NSEC3_HASH];
addRecord(*update_nsec3_namespace_, normal_columns);
}
private:
// Helper predicate class used in deleteRecordInZone().
struct deleteMatch {
deleteMatch(const string& type, const string& rdata) :
......@@ -728,19 +769,33 @@ public:
const string& rdata_;
};
virtual void deleteRecordInZone(const string (&params)[DEL_PARAM_COUNT]) {
// Common subroutine for deleteRecordinZone and deleteNSEC3RecordInZone.
void deleteRecord(Domains& domains,
const string (&params)[DEL_PARAM_COUNT])
{
vector<vector<string> >& records =
(*update_records_)[params[DatabaseAccessor::DEL_NAME]];
domains[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]);
domains.erase(params[DatabaseAccessor::DEL_NAME]);
}
}
public:
virtual void deleteRecordInZone(const string (&params)[DEL_PARAM_COUNT]) {
deleteRecord(*update_records_, params);
}
virtual void deleteNSEC3RecordInZone(
const string (&params)[DEL_PARAM_COUNT])
{
deleteRecord(*update_nsec3_namespace_, params);
}
//
// Helper methods to keep track of some update related activities
//
......@@ -799,13 +854,13 @@ public:
{
// TODO: Provide some broken data, but it is not known yet how broken
// they'll have to be.
Domains::const_iterator it(nsec3_namespace_.lower_bound(hash));
Domains::const_iterator it(nsec3_namespace_->lower_bound(hash));
// We got just after the one we want
if (it == nsec3_namespace_.begin()) {
if (it == nsec3_namespace_->begin()) {
// Hmm, we got something really small. So we wrap around.
// This is one after the last, so after decreasing it we'll get
// the biggest.
it = nsec3_namespace_.end();
it = nsec3_namespace_->end();
}
return ((--it)->first);
}
......@@ -889,12 +944,13 @@ private:
Domains* readonly_records_;
Domains update_records_master_;
Domains* update_records_;
Domains nsec3_namespace_master_;
Domains* nsec3_namespace_;
Domains update_nsec3_namespace_master_;
Domains* update_nsec3_namespace_;
const Domains empty_records_master_;
const Domains* empty_records_;
// The NSEC3 namespace. The above trick will be added once it is needed.
Domains nsec3_namespace_;
// The journal data
std::vector<JournalEntry> journal_entries_master_;
std::vector<JournalEntry>* journal_entries_;
......@@ -959,13 +1015,13 @@ private:
// the NSEC3 namespace. You don't provide the full name, only
// the hash part.
void addCurHash(const std::string& hash) {
ASSERT_EQ(0, nsec3_namespace_.count(hash));
ASSERT_EQ(0, nsec3_namespace_->count(hash));
// 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(hash);
}
nsec3_namespace_[hash] = cur_name_;
(*nsec3_namespace_)[hash] = cur_name_;
cur_name_.clear();
}
......@@ -1207,7 +1263,7 @@ public:
rdata::createRdata(expected_rrset->getType(),
expected_rrset->getClass(),
(*it).data_[Accessor::DIFF_RDATA]));
isc::testutils::rrsetCheck(expected_rrset, rrset);
rrsetCheck(expected_rrset, rrset);
}
// We should have examined all entries of both expected and
// actual data.
......@@ -1376,7 +1432,7 @@ checkRRset(isc::dns::ConstRRsetPtr rrset,
isc::dns::rdata::createRdata(rrtype, rrclass,
rdatas[i]));
}
isc::testutils::rrsetCheck(expected_rrset, rrset);
rrsetCheck(expected_rrset, rrset);
}
// Iterate through a zone, common case
......@@ -1512,7 +1568,7 @@ TYPED_TEST(DatabaseClientTest, getSOAFromIterator) {
}
ASSERT_TRUE(rrset);
// It should be identical to the result of getSOA().
isc::testutils::rrsetCheck(it->getSOA(), rrset);
rrsetCheck(it->getSOA(), rrset);
}
TYPED_TEST(DatabaseClientTest, noSOAFromIterator) {
......@@ -1550,7 +1606,7 @@ TYPED_TEST(DatabaseClientTest, iterateThenUpdate) {
}
ASSERT_TRUE(rrset);
// It should be identical to the result of getSOA().
isc::testutils::rrsetCheck(it->getSOA(), rrset);
rrsetCheck(it->getSOA(), rrset);
}
TYPED_TEST(DatabaseClientTest, updateThenIterateThenUpdate) {
......@@ -2946,6 +3002,97 @@ TYPED_TEST(DatabaseClientTest, addRRsetToNewZone) {