mirror of
https://gitlab.isc.org/isc-projects/kea
synced 2025-08-31 14:05:33 +00:00
[master] Merge branch 'trac1891'
This commit is contained in:
@@ -66,14 +66,16 @@ enum StatementID {
|
||||
ITERATE = 9,
|
||||
FIND_PREVIOUS = 10,
|
||||
ADD_RECORD_DIFF = 11,
|
||||
GET_RECORD_DIFF = 12, // This is temporary for testing "add diff"
|
||||
LOW_DIFF_ID = 13,
|
||||
HIGH_DIFF_ID = 14,
|
||||
DIFF_RECS = 15,
|
||||
NSEC3 = 16,
|
||||
NSEC3_PREVIOUS = 17,
|
||||
NSEC3_LAST = 18,
|
||||
NUM_STATEMENTS = 19
|
||||
LOW_DIFF_ID = 12,
|
||||
HIGH_DIFF_ID = 13,
|
||||
DIFF_RECS = 14,
|
||||
NSEC3 = 15,
|
||||
NSEC3_PREVIOUS = 16,
|
||||
NSEC3_LAST = 17,
|
||||
ADD_NSEC3_RECORD = 18,
|
||||
DEL_ZONE_NSEC3_RECORDS = 19,
|
||||
DEL_NSEC3_RECORD = 20,
|
||||
NUM_STATEMENTS = 21
|
||||
};
|
||||
|
||||
const char* const text_statements[NUM_STATEMENTS] = {
|
||||
@@ -117,8 +119,6 @@ const char* const text_statements[NUM_STATEMENTS] = {
|
||||
"INSERT INTO diffs " // ADD_RECORD_DIFF
|
||||
"(zone_id, version, operation, name, rrtype, ttl, rdata) "
|
||||
"VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)",
|
||||
"SELECT name, rrtype, ttl, rdata, version, operation " // GET_RECORD_DIFF
|
||||
"FROM diffs WHERE zone_id = ?1 ORDER BY id, operation",
|
||||
|
||||
// Two statements to select the lowest ID and highest ID in a set of
|
||||
// differences.
|
||||
@@ -135,19 +135,27 @@ const char* const text_statements[NUM_STATEMENTS] = {
|
||||
"WHERE zone_id=?1 AND id>=?2 and id<=?3 "
|
||||
"ORDER BY id ASC",
|
||||
|
||||
// Query to get the NSEC3 records
|
||||
// NSEC3: Query to get the NSEC3 records
|
||||
//
|
||||
// The "1" in SELECT is for positioning the rdata column to the
|
||||
// expected position, so we can reuse the same code as for other
|
||||
// lookups.
|
||||
"SELECT rdtype, ttl, 1, rdata FROM nsec3 WHERE zone_id=?1 AND "
|
||||
"hash=?2",
|
||||
// For getting the previous NSEC3 hash
|
||||
// NSEC3_PREVIOUS: For getting the previous NSEC3 hash
|
||||
"SELECT DISTINCT hash FROM nsec3 WHERE zone_id=?1 AND hash < ?2 "
|
||||
"ORDER BY hash DESC LIMIT 1",
|
||||
// And for wrap-around
|
||||
// NSEC3_LAST: And for wrap-around
|
||||
"SELECT DISTINCT hash FROM nsec3 WHERE zone_id=?1 "
|
||||
"ORDER BY hash DESC LIMIT 1",
|
||||
// ADD_NSEC3_RECORD: Add NSEC3-related (NSEC3 or NSEC3-covering RRSIG) RR
|
||||
"INSERT INTO nsec3 (zone_id, hash, owner, ttl, rdtype, rdata) "
|
||||
"VALUES (?1, ?2, ?3, ?4, ?5, ?6)",
|
||||
// DEL_ZONE_NSEC3_RECORDS: delete all NSEC3-related records from the zone
|
||||
"DELETE FROM nsec3 WHERE zone_id=?1",
|
||||
// DEL_NSEC3_RECORD: delete specified NSEC3-related records
|
||||
"DELETE FROM nsec3 WHERE zone_id=?1 AND hash=?2 "
|
||||
"AND rdtype=?3 AND rdata=?4"
|
||||
};
|
||||
|
||||
struct SQLite3Parameters {
|
||||
@@ -196,6 +204,7 @@ struct SQLite3Parameters {
|
||||
bool in_transaction; // whether or not a transaction has been started
|
||||
bool updating_zone; // whether or not updating the zone
|
||||
int updated_zone_id; // valid only when in_transaction is true
|
||||
string updated_zone_origin_; // ditto, and only needed to handle NSEC3s
|
||||
private:
|
||||
// statements_ are private and must be accessed via getStatement() outside
|
||||
// of this structure.
|
||||
@@ -210,6 +219,10 @@ private:
|
||||
// statement, which is completed with a single "step" (normally within a
|
||||
// single call to an SQLite3Database method). In particular, it cannot be
|
||||
// used for "SELECT" variants, which generally expect multiple matching rows.
|
||||
//
|
||||
// The bindXXX methods are straightforward wrappers for the corresponding
|
||||
// sqlite3_bind_xxx functions that make bindings with the given parameters
|
||||
// on the statement maintained in this class.
|
||||
class StatementProcessor {
|
||||
public:
|
||||
// desc will be used on failure in the what() message of the resulting
|
||||
@@ -226,6 +239,33 @@ public:
|
||||
sqlite3_reset(stmt_);
|
||||
}
|
||||
|
||||
void bindInt(int index, int val) {
|
||||
if (sqlite3_bind_int(stmt_, index, val) != SQLITE_OK) {
|
||||
isc_throw(DataSourceError,
|
||||
"failed to bind SQLite3 parameter: " <<
|
||||
sqlite3_errmsg(dbparameters_.db_));
|
||||
}
|
||||
}
|
||||
|
||||
void bindInt64(int index, sqlite3_int64 val) {
|
||||
if (sqlite3_bind_int64(stmt_, index, val) != SQLITE_OK) {
|
||||
isc_throw(DataSourceError,
|
||||
"failed to bind SQLite3 parameter: " <<
|
||||
sqlite3_errmsg(dbparameters_.db_));
|
||||
}
|
||||
}
|
||||
|
||||
// For simplicity, we assume val is a NULL-terminated string, and the
|
||||
// entire non NUL characters are to be bound. The destructor parameter
|
||||
// is normally either SQLITE_TRANSIENT or SQLITE_STATIC.
|
||||
void bindText(int index, const char* val, void(*destructor)(void*)) {
|
||||
if (sqlite3_bind_text(stmt_, index, val, -1, destructor)
|
||||
!= SQLITE_OK) {
|
||||
isc_throw(DataSourceError, "failed to bind SQLite3 parameter: " <<
|
||||
sqlite3_errmsg(dbparameters_.db_));
|
||||
}
|
||||
}
|
||||
|
||||
void exec() {
|
||||
if (sqlite3_step(stmt_) != SQLITE_DONE) {
|
||||
sqlite3_reset(stmt_);
|
||||
@@ -1021,19 +1061,22 @@ SQLite3Accessor::startUpdateZone(const string& zone_name, const bool replace) {
|
||||
"start an SQLite3 update transaction").exec();
|
||||
|
||||
if (replace) {
|
||||
// First, clear all current data from tables.
|
||||
typedef pair<StatementID, const char* const> StatementSpec;
|
||||
const StatementSpec delzone_stmts[] =
|
||||
{ StatementSpec(DEL_ZONE_RECORDS, "delete zone records"),
|
||||
StatementSpec(DEL_ZONE_NSEC3_RECORDS,
|
||||
"delete zone NSEC3 records") };
|
||||
try {
|
||||
StatementProcessor delzone_exec(*dbparameters_, DEL_ZONE_RECORDS,
|
||||
"delete zone records");
|
||||
|
||||
sqlite3_stmt* stmt = dbparameters_->getStatement(DEL_ZONE_RECORDS);
|
||||
sqlite3_clear_bindings(stmt);
|
||||
if (sqlite3_bind_int(stmt, 1, zone_info.second) != SQLITE_OK) {
|
||||
isc_throw(DataSourceError,
|
||||
"failed to bind SQLite3 parameter: " <<
|
||||
sqlite3_errmsg(dbparameters_->db_));
|
||||
for (size_t i = 0;
|
||||
i < sizeof(delzone_stmts) / sizeof(delzone_stmts[0]);
|
||||
++i) {
|
||||
StatementProcessor delzone_proc(*dbparameters_,
|
||||
delzone_stmts[i].first,
|
||||
delzone_stmts[i].second);
|
||||
delzone_proc.bindInt(1, zone_info.second);
|
||||
delzone_proc.exec();
|
||||
}
|
||||
|
||||
delzone_exec.exec();
|
||||
} catch (const DataSourceError&) {
|
||||
// Once we start a transaction, if something unexpected happens
|
||||
// we need to rollback the transaction so that a subsequent update
|
||||
@@ -1047,6 +1090,7 @@ SQLite3Accessor::startUpdateZone(const string& zone_name, const bool replace) {
|
||||
dbparameters_->in_transaction = true;
|
||||
dbparameters_->updating_zone = true;
|
||||
dbparameters_->updated_zone_id = zone_info.second;
|
||||
dbparameters_->updated_zone_origin_ = zone_name;
|
||||
|
||||
return (zone_info);
|
||||
}
|
||||
@@ -1073,7 +1117,9 @@ SQLite3Accessor::commit() {
|
||||
StatementProcessor(*dbparameters_, COMMIT,
|
||||
"commit an SQLite3 transaction").exec();
|
||||
dbparameters_->in_transaction = false;
|
||||
dbparameters_->updating_zone = false;
|
||||
dbparameters_->updated_zone_id = -1;
|
||||
dbparameters_->updated_zone_origin_.clear();
|
||||
}
|
||||
|
||||
void
|
||||
@@ -1086,7 +1132,9 @@ SQLite3Accessor::rollback() {
|
||||
StatementProcessor(*dbparameters_, ROLLBACK,
|
||||
"rollback an SQLite3 transaction").exec();
|
||||
dbparameters_->in_transaction = false;
|
||||
dbparameters_->updating_zone = false;
|
||||
dbparameters_->updated_zone_id = -1;
|
||||
dbparameters_->updated_zone_origin_.clear();
|
||||
}
|
||||
|
||||
namespace {
|
||||
@@ -1096,29 +1144,19 @@ void
|
||||
doUpdate(SQLite3Parameters& dbparams, StatementID stmt_id,
|
||||
COLUMNS_TYPE update_params, const char* exec_desc)
|
||||
{
|
||||
sqlite3_stmt* const stmt = dbparams.getStatement(stmt_id);
|
||||
StatementProcessor executer(dbparams, stmt_id, exec_desc);
|
||||
StatementProcessor proc(dbparams, stmt_id, exec_desc);
|
||||
|
||||
int param_id = 0;
|
||||
if (sqlite3_bind_int(stmt, ++param_id, dbparams.updated_zone_id)
|
||||
!= SQLITE_OK) {
|
||||
isc_throw(DataSourceError, "failed to bind SQLite3 parameter: " <<
|
||||
sqlite3_errmsg(dbparams.db_));
|
||||
}
|
||||
proc.bindInt(++param_id, dbparams.updated_zone_id);
|
||||
const size_t column_count =
|
||||
sizeof(update_params) / sizeof(update_params[0]);
|
||||
for (int i = 0; i < column_count; ++i) {
|
||||
// The old sqlite3 data source API assumes NULL for an empty column.
|
||||
// We need to provide compatibility at least for now.
|
||||
if (sqlite3_bind_text(stmt, ++param_id,
|
||||
update_params[i].empty() ? NULL :
|
||||
update_params[i].c_str(),
|
||||
-1, SQLITE_TRANSIENT) != SQLITE_OK) {
|
||||
isc_throw(DataSourceError, "failed to bind SQLite3 parameter: " <<
|
||||
sqlite3_errmsg(dbparams.db_));
|
||||
}
|
||||
proc.bindText(++param_id, update_params[i].empty() ? NULL :
|
||||
update_params[i].c_str(), SQLITE_TRANSIENT);
|
||||
}
|
||||
executer.exec();
|
||||
proc.exec();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1128,15 +1166,32 @@ SQLite3Accessor::addRecordToZone(const string (&columns)[ADD_COLUMN_COUNT]) {
|
||||
isc_throw(DataSourceError, "adding record to SQLite3 "
|
||||
"data source without transaction");
|
||||
}
|
||||
doUpdate<const string (&)[DatabaseAccessor::ADD_COLUMN_COUNT]>(
|
||||
doUpdate<const string (&)[ADD_COLUMN_COUNT]>(
|
||||
*dbparameters_, ADD_RECORD, columns, "add record to zone");
|
||||
}
|
||||
|
||||
void
|
||||
SQLite3Accessor::addNSEC3RecordToZone(
|
||||
const string (&/*columns*/)[ADD_NSEC3_COLUMN_COUNT])
|
||||
const string (&columns)[ADD_NSEC3_COLUMN_COUNT])
|
||||
{
|
||||
isc_throw(NotImplemented, "not yet implemented");
|
||||
if (!dbparameters_->updating_zone) {
|
||||
isc_throw(DataSourceError, "adding NSEC3-related record to SQLite3 "
|
||||
"data source without transaction");
|
||||
}
|
||||
|
||||
// XXX: the current implementation of SQLite3 schema requires the 'owner'
|
||||
// column, and the current implementation of getAllRecords() relies on it,
|
||||
// while the addNSEC3RecordToZone interface doesn't provide it explicitly.
|
||||
// We should revisit it at the design level, but for now we internally
|
||||
// convert the given parameter to satisfy the internal requirements.
|
||||
const string sqlite3_columns[ADD_NSEC3_COLUMN_COUNT + 1] =
|
||||
{ columns[ADD_NSEC3_HASH],
|
||||
columns[ADD_NSEC3_HASH] + "." + dbparameters_->updated_zone_origin_,
|
||||
columns[ADD_NSEC3_TTL],
|
||||
columns[ADD_NSEC3_TYPE], columns[ADD_NSEC3_RDATA] };
|
||||
doUpdate<const string (&)[ADD_NSEC3_COLUMN_COUNT + 1]>(
|
||||
*dbparameters_, ADD_NSEC3_RECORD, sqlite3_columns,
|
||||
"add NSEC3 record to zone");
|
||||
}
|
||||
|
||||
void
|
||||
@@ -1145,15 +1200,21 @@ SQLite3Accessor::deleteRecordInZone(const string (¶ms)[DEL_PARAM_COUNT]) {
|
||||
isc_throw(DataSourceError, "deleting record in SQLite3 "
|
||||
"data source without transaction");
|
||||
}
|
||||
doUpdate<const string (&)[DatabaseAccessor::DEL_PARAM_COUNT]>(
|
||||
doUpdate<const string (&)[DEL_PARAM_COUNT]>(
|
||||
*dbparameters_, DEL_RECORD, params, "delete record from zone");
|
||||
}
|
||||
|
||||
void
|
||||
SQLite3Accessor::deleteNSEC3RecordInZone(
|
||||
const string (&/*params*/)[DEL_PARAM_COUNT])
|
||||
const string (¶ms)[DEL_PARAM_COUNT])
|
||||
{
|
||||
isc_throw(NotImplemented, "not yet implemented");
|
||||
if (!dbparameters_->updating_zone) {
|
||||
isc_throw(DataSourceError, "deleting NSEC3-related record in SQLite3 "
|
||||
"data source without transaction");
|
||||
}
|
||||
doUpdate<const string (&)[DEL_PARAM_COUNT]>(
|
||||
*dbparameters_, DEL_NSEC3_RECORD, params,
|
||||
"delete NSEC3 record from zone");
|
||||
}
|
||||
|
||||
void
|
||||
@@ -1171,33 +1232,16 @@ SQLite3Accessor::addRecordDiff(int zone_id, uint32_t serial,
|
||||
<< dbparameters_->updated_zone_id);
|
||||
}
|
||||
|
||||
sqlite3_stmt* const stmt = dbparameters_->getStatement(ADD_RECORD_DIFF);
|
||||
StatementProcessor executer(*dbparameters_, ADD_RECORD_DIFF,
|
||||
"add record diff");
|
||||
StatementProcessor proc(*dbparameters_, ADD_RECORD_DIFF,
|
||||
"add record diff");
|
||||
int param_id = 0;
|
||||
if (sqlite3_bind_int(stmt, ++param_id, zone_id)
|
||||
!= SQLITE_OK) {
|
||||
isc_throw(DataSourceError, "failed to bind SQLite3 parameter: " <<
|
||||
sqlite3_errmsg(dbparameters_->db_));
|
||||
}
|
||||
if (sqlite3_bind_int64(stmt, ++param_id, serial)
|
||||
!= SQLITE_OK) {
|
||||
isc_throw(DataSourceError, "failed to bind SQLite3 parameter: " <<
|
||||
sqlite3_errmsg(dbparameters_->db_));
|
||||
}
|
||||
if (sqlite3_bind_int(stmt, ++param_id, operation)
|
||||
!= SQLITE_OK) {
|
||||
isc_throw(DataSourceError, "failed to bind SQLite3 parameter: " <<
|
||||
sqlite3_errmsg(dbparameters_->db_));
|
||||
}
|
||||
proc.bindInt(++param_id, zone_id);
|
||||
proc.bindInt64(++param_id, serial);
|
||||
proc.bindInt(++param_id, operation);
|
||||
for (int i = 0; i < DIFF_PARAM_COUNT; ++i) {
|
||||
if (sqlite3_bind_text(stmt, ++param_id, params[i].c_str(),
|
||||
-1, SQLITE_TRANSIENT) != SQLITE_OK) {
|
||||
isc_throw(DataSourceError, "failed to bind SQLite3 parameter: " <<
|
||||
sqlite3_errmsg(dbparameters_->db_));
|
||||
}
|
||||
proc.bindText(++param_id, params[i].c_str(), SQLITE_TRANSIENT);
|
||||
}
|
||||
executer.exec();
|
||||
proc.exec();
|
||||
}
|
||||
|
||||
std::string
|
||||
|
@@ -3002,10 +3002,9 @@ TYPED_TEST(DatabaseClientTest, addRRsetToNewZone) {
|
||||
this->checkLastAdded(rrset_added);
|
||||
}
|
||||
|
||||
// Below we define a set of NSEC3 update tests. 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.
|
||||
|
||||
//
|
||||
// Below we define a set of NSEC3 update tests.
|
||||
//
|
||||
// Commonly used data for NSEC3 update tests below.
|
||||
const char* const nsec3_hash = "1BB7SO0452U1QHL98UISNDD9218GELR5";
|
||||
const char* const nsec3_rdata = "1 1 12 AABBCCDD "
|
||||
@@ -3045,7 +3044,7 @@ nsec3Check(const vector<ConstRRsetPtr>& expected_rrsets,
|
||||
actual_rrsets.begin(), actual_rrsets.end());
|
||||
}
|
||||
|
||||
TEST_F(MockDatabaseClientTest, addDeleteNSEC3InZone) {
|
||||
TYPED_TEST(DatabaseClientTest, addDeleteNSEC3InZone) {
|
||||
// Add one NSEC3 RR to the zone, delete it, and add another one.
|
||||
this->updater_ = this->client_->getUpdater(this->zname_, true);
|
||||
const ConstRRsetPtr nsec3_rrset =
|
||||
@@ -3066,7 +3065,7 @@ TEST_F(MockDatabaseClientTest, addDeleteNSEC3InZone) {
|
||||
*this->current_accessor_);
|
||||
}
|
||||
|
||||
TEST_F(MockDatabaseClientTest, addDeleteNSEC3AndRRSIGToZone) {
|
||||
TYPED_TEST(DatabaseClientTest, addDeleteNSEC3AndRRSIGToZone) {
|
||||
// Add one NSEC3 RR and its RRSIG to the zone, delete the RRSIG and add
|
||||
// a new one.
|
||||
this->updater_ = this->client_->getUpdater(this->zname_, true);
|
||||
@@ -3639,10 +3638,7 @@ TYPED_TEST(DatabaseClientTest, journal) {
|
||||
this->checkJournal(expected);
|
||||
}
|
||||
|
||||
// At the moment this only works for the mock accessor. Once sqlite3
|
||||
// accessor supports updating NSEC3, this should be merged to the previous
|
||||
// test
|
||||
TEST_F(MockDatabaseClientTest, journalForNSEC3) {
|
||||
TYPED_TEST(DatabaseClientTest, journalForNSEC3) {
|
||||
// Similar to the previous test, but adding/deleting NSEC3 RRs, just to
|
||||
// confirm that NSEC3 is not special for managing diffs.
|
||||
const ConstRRsetPtr nsec3_rrset =
|
||||
|
@@ -28,6 +28,19 @@ namespace isc {
|
||||
namespace datasrc {
|
||||
namespace test {
|
||||
|
||||
// Constant data definitions
|
||||
|
||||
const char* const nsec3_common = " 300 IN NSEC3 1 1 12 aabbccdd "
|
||||
"2T7B4G4VSA5SMI47K61MV5BV1A22BOJR A RRSIG";
|
||||
const char* const nsec3_rrsig_common = " 300 IN RRSIG NSEC3 5 3 3600 "
|
||||
"20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE";
|
||||
const char* const apex_hash = "0P9MHAVEQVM6T7VBL5LOP2U3T2RP3TOM";
|
||||
const char* const apex_hash_lower = "0p9mhaveqvm6t7vbl5lop2u3t2rp3tom";
|
||||
const char* const ns1_hash = "2T7B4G4VSA5SMI47K61MV5BV1A22BOJR";
|
||||
const char* const w_hash = "01UDEMVP1J2F7EG6JEBPS17VP3N8I58H";
|
||||
const char* const xyw_hash = "2vptu5timamqttgl4luu9kg21e0aor3s";
|
||||
const char* const zzz_hash = "R53BQ7CC2UVMUBFU5OCMM6PERS9TK9EN";
|
||||
|
||||
class TestNSEC3HashCreator::TestNSEC3Hash : public NSEC3Hash {
|
||||
private:
|
||||
typedef map<Name, string> NSEC3HashMap;
|
||||
|
@@ -31,26 +31,24 @@ namespace test {
|
||||
//
|
||||
// Commonly used NSEC3 suffix. It's incorrect to use it for all NSEC3s, but
|
||||
// doesn't matter for the purpose of our tests.
|
||||
const char* const nsec3_common = " 300 IN NSEC3 1 1 12 aabbccdd "
|
||||
"2T7B4G4VSA5SMI47K61MV5BV1A22BOJR A RRSIG";
|
||||
extern const char* const nsec3_common;
|
||||
// Likewise, common RRSIG suffix for NSEC3s.
|
||||
const char* const nsec3_rrsig_common = " 300 IN RRSIG NSEC3 5 3 3600 "
|
||||
"20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE";
|
||||
extern const char* const nsec3_rrsig_common;
|
||||
|
||||
// Some faked NSEC3 hash values commonly used in tests and the faked NSEC3Hash
|
||||
// object.
|
||||
//
|
||||
// For apex (example.org)
|
||||
const char* const apex_hash = "0P9MHAVEQVM6T7VBL5LOP2U3T2RP3TOM";
|
||||
const char* const apex_hash_lower = "0p9mhaveqvm6t7vbl5lop2u3t2rp3tom";
|
||||
extern const char* const apex_hash;
|
||||
extern const char* const apex_hash_lower;
|
||||
// For ns1.example.org
|
||||
const char* const ns1_hash = "2T7B4G4VSA5SMI47K61MV5BV1A22BOJR";
|
||||
extern const char* const ns1_hash;
|
||||
// For w.example.org
|
||||
const char* const w_hash = "01UDEMVP1J2F7EG6JEBPS17VP3N8I58H";
|
||||
extern const char* const w_hash;
|
||||
// For x.y.w.example.org (lower-cased)
|
||||
const char* const xyw_hash = "2vptu5timamqttgl4luu9kg21e0aor3s";
|
||||
extern const char* const xyw_hash;
|
||||
// For zzz.example.org.
|
||||
const char* const zzz_hash = "R53BQ7CC2UVMUBFU5OCMM6PERS9TK9EN";
|
||||
extern const char* const zzz_hash;
|
||||
|
||||
// A simple faked NSEC3 hash calculator with a dedicated creator for it.
|
||||
//
|
||||
@@ -83,4 +81,8 @@ performNSEC3Test(ZoneFinder &finder);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
#endif // FAKED_NSEC3_H
|
||||
|
||||
// Local Variables:
|
||||
// mode: c++
|
||||
// End:
|
||||
|
@@ -12,8 +12,7 @@
|
||||
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
#include "faked_nsec3.h"
|
||||
|
||||
#include <datasrc/sqlite3_accessor.h>
|
||||
|
||||
@@ -21,14 +20,20 @@
|
||||
|
||||
#include <dns/rrclass.h>
|
||||
|
||||
#include <sqlite3.h>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <boost/lexical_cast.hpp>
|
||||
#include <boost/scoped_ptr.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
#include <fstream>
|
||||
#include <sqlite3.h>
|
||||
|
||||
using namespace std;
|
||||
using namespace isc::datasrc;
|
||||
using namespace isc::datasrc::test;
|
||||
using boost::lexical_cast;
|
||||
using isc::data::ConstElementPtr;
|
||||
using isc::data::Element;
|
||||
@@ -463,11 +468,11 @@ TEST(SQLite3Open, getDBNameExampleROOT) {
|
||||
// Simple function to match records
|
||||
void
|
||||
checkRecordRow(const std::string columns[],
|
||||
const std::string& field0,
|
||||
const std::string& field1,
|
||||
const std::string& field2,
|
||||
const std::string& field3,
|
||||
const std::string& field4)
|
||||
const std::string& field0, // for type
|
||||
const std::string& field1, // for TTL
|
||||
const std::string& field2, // for "sigtype"
|
||||
const std::string& field3, // for rdata
|
||||
const std::string& field4) // for name
|
||||
{
|
||||
EXPECT_EQ(field0, columns[DatabaseAccessor::TYPE_COLUMN]);
|
||||
EXPECT_EQ(field1, columns[DatabaseAccessor::TTL_COLUMN]);
|
||||
@@ -736,6 +741,23 @@ const char* const deleted_data[] = {
|
||||
// Existing data to be removed commonly used by some of the tests below
|
||||
"foo.bar.example.com.", "A", "192.0.2.1"
|
||||
};
|
||||
const char* const nsec3_data[DatabaseAccessor::ADD_NSEC3_COLUMN_COUNT] = {
|
||||
// example NSEC3 parameters. Using "apex_hash" just as a convenient
|
||||
// shortcut; otherwise it has nothing to do with the zone apex for the
|
||||
// purpose of this test.
|
||||
apex_hash, "3600", "NSEC3",
|
||||
"1 1 12 AABBCCDD 2T7B4G4VSA5SMI47K61MV5BV1A22BOJR NS SOA"
|
||||
};
|
||||
const char* const nsec3_sig_data[DatabaseAccessor::ADD_NSEC3_COLUMN_COUNT] = {
|
||||
ns1_hash, "3600", "RRSIG",
|
||||
"NSEC3 5 3 3600 20000101000000 20000201000000 12345 "
|
||||
"example.com. FAKEFAKEFAKE"
|
||||
};
|
||||
const char* const nsec3_deleted_data[] = {
|
||||
// Delete parameters for nsec3_data
|
||||
apex_hash, nsec3_data[DatabaseAccessor::ADD_NSEC3_TYPE],
|
||||
nsec3_data[DatabaseAccessor::ADD_NSEC3_RDATA]
|
||||
};
|
||||
|
||||
class SQLite3Update : public SQLite3AccessorTest {
|
||||
protected:
|
||||
@@ -762,6 +784,7 @@ protected:
|
||||
int zone_id;
|
||||
std::string get_columns[DatabaseAccessor::COLUMN_COUNT];
|
||||
std::string add_columns[DatabaseAccessor::ADD_COLUMN_COUNT];
|
||||
std::string add_nsec3_columns[DatabaseAccessor::ADD_NSEC3_COLUMN_COUNT];
|
||||
std::string del_params[DatabaseAccessor::DEL_PARAM_COUNT];
|
||||
std::string diff_params[DatabaseAccessor::DIFF_PARAM_COUNT];
|
||||
|
||||
@@ -789,6 +812,28 @@ checkRecords(SQLite3Accessor& accessor, int zone_id, const std::string& name,
|
||||
EXPECT_TRUE(it == expected_rows.end());
|
||||
}
|
||||
|
||||
// Similar to the previous one, but checking transactions on the nsec3 table.
|
||||
void
|
||||
checkNSEC3Records(SQLite3Accessor& accessor, int zone_id,
|
||||
const std::string& hash,
|
||||
vector<const char* const*> expected_rows)
|
||||
{
|
||||
DatabaseAccessor::IteratorContextPtr iterator =
|
||||
accessor.getNSEC3Records(hash, zone_id);
|
||||
std::string columns[DatabaseAccessor::COLUMN_COUNT];
|
||||
vector<const char* const*>::const_iterator it = expected_rows.begin();
|
||||
while (iterator->getNext(columns)) {
|
||||
ASSERT_TRUE(it != expected_rows.end());
|
||||
checkRecordRow(columns, (*it)[DatabaseAccessor::ADD_NSEC3_TYPE],
|
||||
(*it)[DatabaseAccessor::ADD_NSEC3_TTL],
|
||||
"", // sigtype, should always be empty
|
||||
(*it)[DatabaseAccessor::ADD_NSEC3_RDATA],
|
||||
""); // name, always empty
|
||||
++it;
|
||||
}
|
||||
EXPECT_TRUE(it == expected_rows.end());
|
||||
}
|
||||
|
||||
TEST_F(SQLite3Update, emptyUpdate) {
|
||||
// If we do nothing between start and commit, the zone content
|
||||
// should be intact.
|
||||
@@ -811,6 +856,26 @@ TEST_F(SQLite3Update, flushZone) {
|
||||
checkRecords(*accessor, zone_id, "foo.bar.example.com.", empty_stored);
|
||||
}
|
||||
|
||||
TEST_F(SQLite3Update, flushZoneWithNSEC3) {
|
||||
// Similar to the previous case, but make sure the separate nsec3 table
|
||||
// is also cleared. We first need to add something to the table.
|
||||
zone_id = accessor->startUpdateZone("example.com.", false).second;
|
||||
copy(nsec3_data, nsec3_data + DatabaseAccessor::ADD_NSEC3_COLUMN_COUNT,
|
||||
add_nsec3_columns);
|
||||
accessor->addNSEC3RecordToZone(add_nsec3_columns);
|
||||
accessor->commit();
|
||||
|
||||
// Confirm it surely exists.
|
||||
expected_stored.clear();
|
||||
expected_stored.push_back(nsec3_data);
|
||||
checkNSEC3Records(*accessor, zone_id, apex_hash, expected_stored);
|
||||
|
||||
// Then starting zone replacement. the NSEC3 record should have been
|
||||
// removed.
|
||||
zone_id = accessor->startUpdateZone("example.com.", true).second;
|
||||
checkNSEC3Records(*accessor, zone_id, apex_hash, empty_stored);
|
||||
}
|
||||
|
||||
TEST_F(SQLite3Update, readWhileUpdate) {
|
||||
zone_id = accessor->startUpdateZone("example.com.", true).second;
|
||||
checkRecords(*accessor, zone_id, "foo.bar.example.com.", empty_stored);
|
||||
@@ -924,6 +989,62 @@ TEST_F(SQLite3Update, addRecord) {
|
||||
checkRecords(*accessor, zone_id, "newdata.example.com.", expected_stored);
|
||||
}
|
||||
|
||||
TEST_F(SQLite3Update, addNSEC3Record) {
|
||||
// Similar to the previous test, but for NSEC3-related records
|
||||
checkRecords(*accessor, zone_id, apex_hash, empty_stored);
|
||||
checkRecords(*accessor, zone_id, ns1_hash, empty_stored);
|
||||
|
||||
zone_id = accessor->startUpdateZone("example.com.", false).second;
|
||||
// Add an NSEC3
|
||||
copy(nsec3_data, nsec3_data + DatabaseAccessor::ADD_NSEC3_COLUMN_COUNT,
|
||||
add_nsec3_columns);
|
||||
accessor->addNSEC3RecordToZone(add_nsec3_columns);
|
||||
|
||||
// Add an RRSIG for NSEC3
|
||||
copy(nsec3_sig_data,
|
||||
nsec3_sig_data + DatabaseAccessor::ADD_NSEC3_COLUMN_COUNT,
|
||||
add_nsec3_columns);
|
||||
accessor->addNSEC3RecordToZone(add_nsec3_columns);
|
||||
|
||||
// Check the stored data, before and after commit().
|
||||
for (size_t i = 0; i < 2; ++i) {
|
||||
expected_stored.clear();
|
||||
expected_stored.push_back(nsec3_data);
|
||||
checkNSEC3Records(*accessor, zone_id, apex_hash, expected_stored);
|
||||
|
||||
expected_stored.clear();
|
||||
expected_stored.push_back(nsec3_sig_data);
|
||||
checkNSEC3Records(*accessor, zone_id, ns1_hash, expected_stored);
|
||||
|
||||
if (i == 0) { // make sure commit() happens only once
|
||||
accessor->commit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(SQLite3Update, nsec3IteratorOnAdd) {
|
||||
// This test checks if an added NSEC3 record will appear in the iterator
|
||||
// result, meeting the expectation of addNSEC3RecordToZone.
|
||||
// Specifically, it checks if the name column is filled with the complete
|
||||
// owner name.
|
||||
|
||||
// We'll replace the zone, and add one NSEC3 record, and only that one.
|
||||
zone_id = accessor->startUpdateZone("example.com.", true).second;
|
||||
copy(nsec3_data, nsec3_data + DatabaseAccessor::ADD_NSEC3_COLUMN_COUNT,
|
||||
add_nsec3_columns);
|
||||
accessor->addNSEC3RecordToZone(add_nsec3_columns);
|
||||
accessor->commit();
|
||||
|
||||
// the zone should contain only one record we just added.
|
||||
DatabaseAccessor::IteratorContextPtr context =
|
||||
accessor->getAllRecords(zone_id);
|
||||
string data[DatabaseAccessor::COLUMN_COUNT];
|
||||
EXPECT_TRUE(context->getNext(data));
|
||||
EXPECT_EQ(string(apex_hash) + ".example.com.",
|
||||
data[DatabaseAccessor::NAME_COLUMN]);
|
||||
EXPECT_FALSE(context->getNext(data));
|
||||
}
|
||||
|
||||
TEST_F(SQLite3Update, addThenRollback) {
|
||||
zone_id = accessor->startUpdateZone("example.com.", false).second;
|
||||
copy(new_data, new_data + DatabaseAccessor::ADD_COLUMN_COUNT,
|
||||
@@ -934,7 +1055,11 @@ TEST_F(SQLite3Update, addThenRollback) {
|
||||
expected_stored.push_back(new_data);
|
||||
checkRecords(*accessor, zone_id, "newdata.example.com.", expected_stored);
|
||||
|
||||
// Rollback the transaction, and confirm the zone reverts to the previous
|
||||
// state. We also start another update to check if the accessor can be
|
||||
// reused for a new update after rollback.
|
||||
accessor->rollback();
|
||||
zone_id = accessor->startUpdateZone("example.com.", false).second;
|
||||
checkRecords(*accessor, zone_id, "newdata.example.com.", empty_stored);
|
||||
}
|
||||
|
||||
@@ -960,6 +1085,12 @@ TEST_F(SQLite3Update, duplicateAdd) {
|
||||
TEST_F(SQLite3Update, invalidAdd) {
|
||||
// An attempt of add before an explicit start of transaction
|
||||
EXPECT_THROW(accessor->addRecordToZone(add_columns), DataSourceError);
|
||||
|
||||
// Same for addNSEC3.
|
||||
copy(nsec3_data, nsec3_data + DatabaseAccessor::ADD_NSEC3_COLUMN_COUNT,
|
||||
add_nsec3_columns);
|
||||
EXPECT_THROW(accessor->addNSEC3RecordToZone(add_nsec3_columns),
|
||||
DataSourceError);
|
||||
}
|
||||
|
||||
TEST_F(SQLite3Update, deleteRecord) {
|
||||
@@ -977,6 +1108,32 @@ TEST_F(SQLite3Update, deleteRecord) {
|
||||
checkRecords(*accessor, zone_id, "foo.bar.example.com.", empty_stored);
|
||||
}
|
||||
|
||||
TEST_F(SQLite3Update, deleteNSEC3Record) {
|
||||
// Similar to the previous test, but for NSEC3.
|
||||
zone_id = accessor->startUpdateZone("example.com.", false).second;
|
||||
checkNSEC3Records(*accessor, zone_id, apex_hash, empty_stored);
|
||||
|
||||
// We first need to add some record.
|
||||
copy(nsec3_data, nsec3_data + DatabaseAccessor::ADD_NSEC3_COLUMN_COUNT,
|
||||
add_nsec3_columns);
|
||||
accessor->addNSEC3RecordToZone(add_nsec3_columns);
|
||||
|
||||
// Now it should exist.
|
||||
expected_stored.clear();
|
||||
expected_stored.push_back(nsec3_data);
|
||||
checkNSEC3Records(*accessor, zone_id, apex_hash, expected_stored);
|
||||
|
||||
// Delete it, and confirm that.
|
||||
copy(nsec3_deleted_data,
|
||||
nsec3_deleted_data + DatabaseAccessor::DEL_PARAM_COUNT, del_params);
|
||||
accessor->deleteNSEC3RecordInZone(del_params);
|
||||
checkNSEC3Records(*accessor, zone_id, apex_hash, empty_stored);
|
||||
|
||||
// Commit the change, and confirm the deleted data still isn't there.
|
||||
accessor->commit();
|
||||
checkNSEC3Records(*accessor, zone_id, apex_hash, empty_stored);
|
||||
}
|
||||
|
||||
TEST_F(SQLite3Update, deleteThenRollback) {
|
||||
zone_id = accessor->startUpdateZone("example.com.", false).second;
|
||||
|
||||
@@ -1023,6 +1180,10 @@ TEST_F(SQLite3Update, deleteNonexistent) {
|
||||
TEST_F(SQLite3Update, invalidDelete) {
|
||||
// An attempt of delete before an explicit start of transaction
|
||||
EXPECT_THROW(accessor->deleteRecordInZone(del_params), DataSourceError);
|
||||
|
||||
// Same for NSEC3.
|
||||
EXPECT_THROW(accessor->deleteNSEC3RecordInZone(del_params),
|
||||
DataSourceError);
|
||||
}
|
||||
|
||||
TEST_F(SQLite3Update, emptyTransaction) {
|
||||
|
Binary file not shown.
@@ -58,7 +58,7 @@ class TransferResult(object):
|
||||
if len(line) > 0 and line[0] != ';':
|
||||
self.records.append(line)
|
||||
|
||||
@step('An AXFR transfer of ([\w.]+)(?: from ([^:]+)(?::([0-9]+))?)?')
|
||||
@step('An AXFR transfer of ([\w.]+)(?: from ([^:]+|\[[0-9a-fA-F:]+\])(?::([0-9]+))?)?')
|
||||
def perform_axfr(step, zone_name, address, port):
|
||||
"""
|
||||
Perform an AXFR transfer, and store the result as an instance of
|
||||
@@ -72,6 +72,8 @@ def perform_axfr(step, zone_name, address, port):
|
||||
"""
|
||||
if address is None:
|
||||
address = "127.0.0.1"
|
||||
# convert [IPv6_addr] to IPv6_addr:
|
||||
address = re.sub(r"\[(.+)\]", r"\1", address)
|
||||
if port is None:
|
||||
port = 47806
|
||||
args = [ 'dig', 'AXFR', '@' + str(address), '-p', str(port), zone_name ]
|
||||
|
@@ -21,3 +21,11 @@ Feature: Xfrin
|
||||
When I send bind10 the command Xfrin retransfer example.org IN ::1 47807
|
||||
Then wait for new bind10 stderr message XFRIN_TRANSFER_SUCCESS not XFRIN_XFR_PROCESS_FAILURE
|
||||
A query for www.example.org should have rcode NOERROR
|
||||
|
||||
# The transferred zone should have 11 non-NSEC3 RRs and 1 NSEC3 RR.
|
||||
# The following check will get these by AXFR, so the total # of RRs
|
||||
# should be 13, counting the duplicated SOA.
|
||||
# At this point we can confirm both in and out of AXFR for a zone
|
||||
# containing an NSEC3 RR.
|
||||
When I do an AXFR transfer of example.org from ::1 47807
|
||||
Then transfer result should have 13 rrs
|
||||
|
Reference in New Issue
Block a user