[svn] commit: r3268 - in /branches/trac374/src/lib/datasrc: ./ tests/ tests/testdata/
BIND 10 source code commits
bind10-changes at lists.isc.org
Tue Oct 19 08:16:09 UTC 2010
Author: jelte
Date: Tue Oct 19 08:16:09 2010
New Revision: 3268
Log:
c++ support for writable datasources from branches/trac232
Added:
branches/trac374/src/lib/datasrc/tests/testdata/ixfr_bad_remove_nonexisting.rrs
branches/trac374/src/lib/datasrc/tests/testdata/ixfr_ok.rrs
branches/trac374/src/lib/datasrc/tests/testdata/update1.packet
branches/trac374/src/lib/datasrc/tests/testdata/update2.packet
branches/trac374/src/lib/datasrc/tests/testdata/update3.packet
branches/trac374/src/lib/datasrc/tests/testdata/update4.packet
branches/trac374/src/lib/datasrc/tests/testdata/update5.packet
branches/trac374/src/lib/datasrc/tests/testdata/update6.packet
branches/trac374/src/lib/datasrc/tests/testdata/update7.packet
Modified:
branches/trac374/src/lib/datasrc/data_source.cc
branches/trac374/src/lib/datasrc/data_source.h
branches/trac374/src/lib/datasrc/sqlite3_datasrc.cc
branches/trac374/src/lib/datasrc/sqlite3_datasrc.h
branches/trac374/src/lib/datasrc/tests/Makefile.am
branches/trac374/src/lib/datasrc/tests/sqlite3_unittest.cc
Modified: branches/trac374/src/lib/datasrc/data_source.cc
==============================================================================
--- branches/trac374/src/lib/datasrc/data_source.cc (original)
+++ branches/trac374/src/lib/datasrc/data_source.cc Tue Oct 19 08:16:09 2010
@@ -32,6 +32,7 @@
#include <dns/buffer.h>
#include <dns/message.h>
#include <dns/name.h>
+#include <dns/opcode.h>
#include <dns/rcode.h>
#include <dns/rdataclass.h>
#include <dns/rrset.h>
@@ -1255,6 +1256,368 @@
return (NOT_IMPLEMENTED);
}
+DataSrc::WriteResult
+DataSrc::startTransaction(DataSrcTransaction& transaction UNUSED_PARAM,
+ const bool create_zone UNUSED_PARAM)
+{
+ return (W_NOT_IMPLEMENTED);
+}
+
+DataSrc::WriteResult
+DataSrc::commitTransaction(DataSrcTransaction& transaction UNUSED_PARAM)
+{
+ return (W_NOT_IMPLEMENTED);
+}
+
+DataSrc::WriteResult
+DataSrc::rollbackTransaction(DataSrcTransaction& transaction UNUSED_PARAM)
+{
+ return (W_NOT_IMPLEMENTED);
+}
+
+DataSrc::WriteResult
+DataSrc::addRRset(DataSrcTransaction& transaction UNUSED_PARAM,
+ const isc::dns::ConstRRsetPtr rrset UNUSED_PARAM)
+{
+ return (W_NOT_IMPLEMENTED);
+}
+
+DataSrc::WriteResult
+DataSrc::delRRset(DataSrcTransaction& transaction UNUSED_PARAM,
+ isc::dns::ConstRRsetPtr rrset UNUSED_PARAM)
+{
+ return (W_NOT_IMPLEMENTED);
+}
+
+DataSrc::WriteResult
+DataSrc::delZone(DataSrcTransaction& transaction UNUSED_PARAM)
+{
+ return (W_NOT_IMPLEMENTED);
+}
+
+DataSrc::WriteResult
+DataSrc::replaceZone(DataSrcTransaction& transaction UNUSED_PARAM,
+ isc::dns::RRsetPtr (*nextRRset)(void*, void*) UNUSED_PARAM,
+ void* arg1 UNUSED_PARAM, void* arg2 UNUSED_PARAM)
+{
+ return (W_NOT_IMPLEMENTED);
+}
+
+static bool
+equalRRsets(ConstRRsetPtr a, ConstRRsetPtr b) {
+ if (a->getName() != b->getName() ||
+ a->getClass() != b->getClass() ||
+ a->getType() != b->getType() ||
+ a->getRdataCount() != b->getRdataCount()) {
+ return (false);
+ }
+ RdataIteratorPtr ita = a->getRdataIterator();
+ RdataIteratorPtr itb = b->getRdataIterator();
+ itb->first();
+ for (ita->first(); !ita->isLast(); ita->next()) {
+ if (ita->getCurrent().compare(itb->getCurrent()) != 0) {
+ return (false);
+ }
+ itb->next();
+ }
+ return (true);
+}
+
+bool
+DataSrc::haveRRset(DataSrcTransaction& transaction UNUSED_PARAM,
+ ConstRRsetPtr rrset) {
+ RRsetList rrset_list;
+ DataSrc::Result result;
+ uint32_t flags = 0;
+
+ if (rrset->getClass() == RRClass::ANY() || rrset->getClass() == RRClass::NONE()) {
+ result = findExactRRset(rrset->getName(), RRClass::IN(),
+ rrset->getType(), rrset_list, flags, NULL);
+ } else {
+ result = findExactRRset(rrset->getName(), rrset->getClass(),
+ rrset->getType(), rrset_list, flags, NULL);
+ }
+ if (result != DataSrc::SUCCESS || rrset_list.size() == 0) {
+ return (false);
+ }
+ if (rrset->getRdataCount() > 0) {
+ return (equalRRsets(rrset, *(rrset_list.begin())));
+ } else {
+ return ((*(rrset_list.begin()))->getRdataCount() > 0);
+ }
+}
+
+isc::dns::Rcode
+DataSrc::updateCheckPrerequisite(DataSrcTransaction& transaction,
+ ConstRRsetPtr prereq)
+{
+ // section 3.2 of RFC2136
+ if (prereq->getClass() == RRClass::ANY()) {
+ if (prereq->getTTL().getValue() != 0 ||
+ prereq->getRdataCount() != 0) {
+ return (Rcode::FORMERR());
+ }
+ if (prereq->getType() == RRType::ANY()) {
+ if (!haveRRset(transaction, prereq)) {
+ return (Rcode::NXDOMAIN());
+ }
+ } else if (!haveRRset(transaction, prereq)) {
+ return (Rcode::NXRRSET());
+ }
+ } else if (prereq->getClass() == RRClass::NONE()) {
+ if (prereq->getTTL().getValue() != 0 ||
+ prereq->getRdataCount() != 0) {
+ return (Rcode::FORMERR());
+ }
+ if (prereq->getType() == RRType::ANY()) {
+ if (haveRRset(transaction, prereq)) {
+ return (Rcode::YXDOMAIN());
+ }
+ } else if (haveRRset(transaction, prereq)) {
+ return (Rcode::YXRRSET());
+ }
+ } else if (prereq->getClass() == transaction.getZoneClass()) {
+ if (prereq->getTTL().getValue() != 0) {
+ return (Rcode::FORMERR());
+ }
+ // 3.2.3 talks about rebuilding sets, but we already have full rrsets
+ if (!haveRRset(transaction, prereq)) {
+ return (Rcode::NXRRSET());
+ }
+ } else {
+ return (Rcode::FORMERR());
+ }
+
+ return (Rcode::NOERROR());
+}
+
+Rcode
+DataSrc::updateProcessUpdate(DataSrcTransaction& transaction,
+ isc::dns::RRsetPtr update)
+{
+ // The RFC says to pre-scan them, but since we use a transaction
+ // we can roll back, we can process the RRsets one at a time
+
+ // TODO, NOTZONE check
+
+ RRType update_type = update->getType();
+ if (update->getClass() != RRClass::ANY()) {
+ // do we have a direct check in rrtype to see if a specific
+ // type is known and not a meta type?
+ if (update_type == RRType::ANY() ||
+ update_type == RRType::IXFR() ||
+ update_type == RRType::AXFR()) {
+ return (Rcode::FORMERR());
+ }
+ } else if (update->getClass() == RRClass::ANY()) {
+ if (update->getTTL().getValue() != 0) {
+ return (Rcode::FORMERR());
+ }
+ if (update_type == RRType::IXFR() ||
+ update_type == RRType::AXFR()) {
+ return (Rcode::FORMERR());
+ }
+ if (update->getRdataCount() > 0) {
+ return (Rcode::FORMERR());
+ }
+ } else if (update->getClass() == RRClass::NONE()) {
+ if (update->getTTL().getValue() != 0) {
+ return (Rcode::FORMERR());
+ }
+ }
+
+ // Most types are blindly added, but some require special handling
+ if (update_type == RRType::SOA()) {
+ // check serial and delete old
+ RRsetList soa_list;
+ uint32_t flags = 0;
+ if (findExactRRset(update->getName(),
+ update->getClass(),
+ update_type,
+ soa_list,
+ flags,
+ NULL) != SUCCESS ||
+ soa_list.size() != 1) {
+ return (Rcode::SERVFAIL());
+ } else {
+ // TODO: no serial arithmetic yet?
+ if (delRRset(transaction, *(soa_list.begin())) != W_SUCCESS) {
+ return (Rcode::SERVFAIL());
+ }
+ if (addRRset(transaction, update) != W_SUCCESS) {
+ return (Rcode::SERVFAIL());
+ }
+ }
+ // which other types need special handling? CNAME, WKS,...
+ // addRRset and delRRset should do 'the right thing' regarding
+ // types (any/none/specific) and rdata count
+ } else {
+ if (update->getClass() == RRClass::ANY() ||
+ update->getClass() == RRClass::NONE()) {
+ if (delRRset(transaction, update) != W_SUCCESS) {
+ return (Rcode::SERVFAIL());
+ }
+ } else {
+ if (addRRset(transaction, update) != W_SUCCESS) {
+ return (Rcode::SERVFAIL());
+ }
+ }
+ }
+ return (Rcode::NOERROR());
+}
+
+isc::dns::RRsetPtr
+callbackHelperRRsetIterator(void* arg1, void* arg2)
+{
+ RRsetIterator* cur = static_cast<RRsetIterator*>(arg1);
+ RRsetIterator* end = static_cast<RRsetIterator*>(arg2);
+
+ if (cur == NULL || end == NULL || *cur == *end) {
+ return RRsetPtr();
+ } else {
+ RRsetPtr result = **cur;
+ (*cur)++;
+ return (result);
+ }
+}
+
+isc::dns::RRsetPtr
+callbackHelperRRsetVector(void *arg1,
+ void *arg2)
+{
+ std::vector<RRsetPtr>* v = static_cast<std::vector<RRsetPtr>* >(arg1);
+ size_t* i = static_cast<size_t*>(arg2);
+
+ if (v && i && *i < v->size()) {
+ RRsetPtr result = ((*v)[(*i)++]);
+ return (result);
+ } else {
+ return (RRsetPtr());
+ }
+}
+
+DataSrc::Result
+DataSrc::doIXFR(DataSrcTransaction& transaction UNUSED_PARAM,
+ isc::dns::RRsetPtr (*nextRRset)(void*, void*),
+ void* arg1, void* arg2)
+{
+ if (transaction.getState() != DataSrcTransaction::RUNNING) {
+ return (DataSrc::ERROR);
+ }
+
+ RRsetPtr final_soa = nextRRset(arg1, arg2);
+ if (!final_soa) {
+ return (DataSrc::ERROR);
+ }
+ RRsetPtr first_soa = nextRRset(arg1, arg2);
+ if (!final_soa) {
+ return (DataSrc::ERROR);
+ }
+
+ RRsetPtr next_rrset, last_rrset;
+
+ if (first_soa->getType() == RRType::SOA()) {
+ if (!haveRRset(transaction, first_soa)) {
+ return (DataSrc::ERROR);
+ }
+ } else {
+ // Not a SOA, then this must be an AXFR-type response
+ replaceZone(transaction, NULL);
+
+ while (next_rrset = nextRRset(arg1, arg2)) {
+ addRRset(transaction, next_rrset);
+ }
+ return (DataSrc::SUCCESS);
+ }
+ bool deleting = true;
+
+ while (next_rrset = nextRRset(arg1, arg2)) {
+
+ // If we see a SOA, it means we are switching operations (either
+ // we start deleting or adding depending on what we were doing
+ // before.
+ // We don't delete the actual SOA itself,
+ if (next_rrset->getType() == RRType::SOA()) {
+ // TODO: check if serial has increased compared to the last soa we saw
+ deleting = !deleting;
+ last_rrset = next_rrset;
+ } else {
+ if (deleting) {
+ // check if rrset exists, if not, something is very wrong, abort
+ if (haveRRset(transaction, next_rrset)) {
+ delRRset(transaction, next_rrset);
+ } else {
+ return (DataSrc::ERROR);
+ }
+ } else {
+ addRRset(transaction, next_rrset);
+ }
+ }
+ }
+ if (equalRRsets(last_rrset, final_soa)) {
+ // Finally replace the SOA
+ delRRset(transaction, first_soa);
+ addRRset(transaction, final_soa);
+ return (DataSrc::SUCCESS);
+ } else {
+ return (DataSrc::ERROR);
+ }
+ return (NOT_IMPLEMENTED);
+}
+
+
+DataSrc::Result
+DataSrc::doUpdate(DataSrcTransaction& transaction UNUSED_PARAM,
+ isc::dns::Message& msg UNUSED_PARAM) {
+ if (msg.getOpcode() != isc::dns::Opcode::UPDATE()) {
+ return ERROR;
+ }
+
+ // hmz, zone already in transaction. should we do transaction here?
+ // (and not as an argument) for now, simply check it
+ if (msg.getRRCount(isc::dns::Section::QUESTION()) != 1) {
+ return ERROR;
+ }
+ QuestionPtr question = *(msg.beginQuestion());
+ if (question->getName() != transaction.getZoneName()) {
+ return ERROR;
+ }
+ if (question->getType() != isc::dns::RRType::SOA()) {
+ return ERROR;
+ }
+ if (question->getClass() != transaction.getZoneClass()) {
+ return ERROR;
+ }
+
+ // check the prerequisites
+ RRsetIterator it;
+ for (it = msg.beginSection(isc::dns::Section::ANSWER());
+ it != msg.endSection(isc::dns::Section::ANSWER());
+ it++) {
+ RRsetPtr cur_prereq = *it;
+ isc::dns::Rcode prereq_result = updateCheckPrerequisite(transaction, cur_prereq);
+ if (prereq_result != Rcode::NOERROR()) {
+ msg.clear(Message::RENDER);
+ msg.setRcode(prereq_result);
+ return ERROR;
+ }
+ }
+
+ for (it = msg.beginSection(isc::dns::Section::AUTHORITY());
+ it != msg.endSection(isc::dns::Section::AUTHORITY());
+ it++) {
+ RRsetPtr cur_update = *it;
+ Rcode result = updateProcessUpdate(transaction, cur_update);
+ if (result != Rcode::NOERROR()) {
+ return ERROR;
+ }
+ }
+
+ // do we need to do anything with additional?
+
+ return SUCCESS;
+}
+
DataSrc::Result
MetaDataSrc::findRRset(const isc::dns::Name& qname UNUSED_PARAM,
const isc::dns::RRClass& qclass UNUSED_PARAM,
@@ -1313,5 +1676,63 @@
return (NOT_IMPLEMENTED);
}
-}
-}
+DataSrcTransaction::~DataSrcTransaction() {
+ try {
+ if (getState() == RUNNING) {
+ _data_source->rollbackTransaction(*this);
+ }
+ } catch (...) {
+ // if we have an exception-free logging system we might
+ // want to log something. Otherwise ignore
+ }
+}
+
+DataSrc::WriteResult
+MetaDataSrc::startTransaction(DataSrcTransaction& transaction UNUSED_PARAM,
+ const bool create_zone UNUSED_PARAM)
+{
+ return (W_NOT_IMPLEMENTED);
+}
+
+DataSrc::WriteResult
+MetaDataSrc::commitTransaction(DataSrcTransaction& transaction UNUSED_PARAM)
+{
+ return (W_NOT_IMPLEMENTED);
+}
+
+DataSrc::WriteResult
+MetaDataSrc::rollbackTransaction(DataSrcTransaction& transaction UNUSED_PARAM)
+{
+ return (W_NOT_IMPLEMENTED);
+}
+
+DataSrc::WriteResult
+MetaDataSrc::addRRset(DataSrcTransaction& transaction UNUSED_PARAM,
+ isc::dns::ConstRRsetPtr rrset UNUSED_PARAM)
+{
+ return (W_NOT_IMPLEMENTED);
+}
+
+DataSrc::WriteResult
+MetaDataSrc::delRRset(DataSrcTransaction& transaction UNUSED_PARAM,
+ isc::dns::ConstRRsetPtr rrset UNUSED_PARAM)
+{
+ return (W_NOT_IMPLEMENTED);
+}
+
+DataSrc::WriteResult
+MetaDataSrc::delZone(DataSrcTransaction& transaction UNUSED_PARAM)
+{
+ return (W_NOT_IMPLEMENTED);
+}
+
+DataSrc::WriteResult
+MetaDataSrc::replaceZone(DataSrcTransaction& transaction UNUSED_PARAM,
+ isc::dns::RRsetPtr (*nextRRset)(void*, void*) UNUSED_PARAM,
+ void* arg1 UNUSED_PARAM, void* arg2 UNUSED_PARAM)
+{
+ return (W_NOT_IMPLEMENTED);
+}
+
+}
+}
Modified: branches/trac374/src/lib/datasrc/data_source.h
==============================================================================
--- branches/trac374/src/lib/datasrc/data_source.h (original)
+++ branches/trac374/src/lib/datasrc/data_source.h Tue Oct 19 08:16:09 2010
@@ -27,7 +27,9 @@
#include <dns/name.h>
#include <dns/rrclass.h>
+#include <dns/rrset.h>
#include <cc/data.h>
+#include <dns/message.h>
namespace isc {
@@ -55,6 +57,50 @@
isc::Exception(file, line, what) {}
};
+/// \brief Helper function for use as ready-made callback argument
+/// (for instance for DataSrc::replaceZone and DataSrc::doIXFR)
+/// This function takes a pointer to an RRsetIterator (arg1) and a
+/// pointer to an RRsetIterator end value (arg2).
+/// On each call, the next RRsetPtr in the iterator is returned, and
+/// the iterator is advanced. If the last element is reached, and
+/// empty RRsetPtr is returned.
+///
+/// Example usage:
+///
+/// \code
+/// isc::dns::Message ixfr_response = some_other_function();
+/// isc::dns::RRsetIterator begin = ixfr_response.beginSection(isc::dns::Section::ANSWER());
+/// isc::dns::RRsetIterator end = ixfr_response.endSection(isc::dns::Section::ANSWER());
+/// data_source.doIXFR(transaction,
+/// isc::datasrc::callbackHelperRRsetIterator,
+/// &begin, &end);
+/// \endcode
+///
+/// \param arg1 pointer to an RRsetIterator object, for instance as
+/// returned by isc::dns::message::beginSection(). This
+/// iterator is advanced on each call to this function
+/// \param arg2 pointer to an RRsetIterator end object, for instance
+/// as returned by isc::dns::message::endSection().
+/// \return The RRsetPtr as pointed to by the iterator in arg1, or
+/// an empty RRsetPtr if the end is reached.
+isc::dns::RRsetPtr
+callbackHelperRRsetIterator(void* arg1, void* arg2);
+
+/// \brief Helper function for use as ready-made callback argument
+/// (for instance for DataSrc::replaceZone and DataSrc::doIXFR)
+/// This function takes a pointer to an std::vector<RRsetPtr> (arg1)
+/// and a pointer to a size_t value (arg2).
+/// On each call, the RRsetPtr in the vector at arg1 at the position
+/// at arg2 is returned, and arg2 is incremented with 1.
+/// If the size_t value at arg2 is is equal to or greater than the
+/// size of the vector, an empty ElementPtr() is returned.
+///
+/// \param arg1, pointer to an std::vector<RRsetPtr> object
+/// \param arg2, pointer to a size_t value. This value is incremented
+/// on each call to this function
+/// \return The RRsetPtr in the vector *arg1 at position *arg2.
+isc::dns::RRsetPtr
+callbackHelperRRsetVector(void* arg1, void* arg2);
class AbstractDataSrc {
///
@@ -80,6 +126,17 @@
SUCCESS,
ERROR,
NOT_IMPLEMENTED
+ };
+
+ // For write-related methods, we have a separate list of
+ // return values, since the errors that can occur are different
+ enum WriteResult {
+ W_SUCCESS, // ok
+ W_ERROR, // general (code) error
+ W_DB_ERROR, // (write) error in the backend
+ W_NOT_IMPLEMENTED, // write functions not implemented
+ W_NO_SUCH_ZONE, // zone not known to this data source
+ W_NO_SUCH_DATA // attempt to delete nonexistant data
};
// These flags indicate conditions encountered while processing a query.
@@ -159,6 +216,115 @@
virtual Result findCoveringNSEC3(const isc::dns::Name& zonename,
std::string& hash,
isc::dns::RRsetList& target) const = 0;
+
+};
+
+///
+/// \brief The \c DataSrcTransaction serves as a data container and
+/// resource holder for transactions on writable data sources.
+///
+/// Instantiating this class does not actually start a transaction.
+/// Rather, it is passed to the actual data source's startTransaction()
+/// method, which starts the transaction and fills this object with
+/// the data that data source needs in order to perform the write
+/// commands later. When this transaction object is destroyed, and
+/// the transaction has been started, but not committed (with
+/// commitTransaction()), rollbackTransaction() in the
+/// data source is called. That way, we can make sure unfinished
+/// transactions do not linger.
+///
+/// To help prevent logic errors in the use of the transaction object,
+/// it keeps a 'state', which is one of INIT, RUNNING, or DONE.
+/// startTransaction() in a data source MUST check that state is INIT,
+/// and set it to RUNNING (on success, of course).
+/// Any actual operation within the transaction MUST check the state is
+/// RUNNING.
+/// commitTransaction() and rollbackTransaction() MUST check the state
+/// is RUNNING, and set it to DONE.
+///
+/// Apart from holding the resource for the open transaction, this
+/// class is also intended to store data-source specific data during
+/// that transaction. One example is that for the Sqlite3-data source,
+/// it stores the zone_id value. Datasources can store arbitrary data
+/// in the ElementPtr() returned by getData().
+///
+/// This class always stores some basic information, given at
+/// instantiation time. Apart from the getters and setters for these,
+/// and the desctructor, this class should not have any methods that
+/// contain logic.
+///
+class DataSrcTransaction {
+public:
+ enum states { INIT, RUNNING, DONE };
+
+ /// \brief Constructs a DataSrcTransaction object
+ ///
+ /// Constructs a DataSrcTransaction object for use with
+ /// the given data source, for operations on the given zone name,
+ /// in the given zone class. If the data source is a meta data
+ /// source that supports searching for the real one, the internal
+ /// data source pointer can be set to the data source that actually
+ /// holds the zone, but this will not happen until the transaction
+ /// is actually started (by the data sources startTransaction()
+ /// method).
+ ///
+ /// After construction, and upon success, the transaction state will
+ /// be INIT.
+ ///
+ /// \param data_source The data source to perform operations on
+ /// \param zone_name The name of the zone to perform operations on
+ /// \param zone_class The RRClass of the zone to perform operations on.
+ ///
+ explicit DataSrcTransaction(DataSrc *data_source,
+ const isc::dns::Name& zone_name,
+ const isc::dns::RRClass& zone_class) :
+ _zone_name(zone_name),
+ _zone_class(zone_class),
+ _data_source(data_source),
+ _state(INIT) {};
+ ~DataSrcTransaction();
+
+ /// \brief Returns the current state of the transaction
+ /// \return state
+ states getState() { return (_state); };
+
+ /// \brief Sets the state of the transaction
+ /// \param state The new state
+ void setState(states state) { _state = state; };
+
+ /// \brief Returns the base ElementPtr of the arbitrary
+ /// datasource-specific data.
+ /// The exact contents of this ptr are determined by the
+ /// data source, and no assumptions about its contents should
+ /// be made by the caller.
+ /// \return ElementPtr containing transaction data
+ isc::data::ElementPtr getData() { return (_data); };
+
+ /// \brief Sets the base ElementsPtr of the arbitrary
+ /// datasource-specific data. This function should only be called
+ /// by the data source, in the startTransaction() method.
+ /// \param data The ElementPtr containing data
+ void setData(isc::data::ElementPtr data) { _data = data; };
+
+ /// \brief Returns the name of the zone this transaction is about
+ /// \return zone name
+ const isc::dns::Name getZoneName() { return (_zone_name); };
+
+ /// \brief Returns the RRClass of the zone this transaction is about
+ /// \return RRClass
+ const isc::dns::RRClass getZoneClass() { return (_zone_class); };
+
+ /// \brief Returns a pointer to the data source the zone was
+ /// found in.
+ /// \return DataSrc* pointer
+ DataSrc* getDataSource() { return (_data_source); };
+
+private:
+ isc::data::ElementPtr _data;
+ isc::dns::Name _zone_name;
+ isc::dns::RRClass _zone_class;
+ DataSrc* _data_source;
+ states _state;
};
// Base class for a DNS Data Source
@@ -224,6 +390,169 @@
std::string& hash,
isc::dns::RRsetList& target) const = 0;
+ //
+ // Writable data sources
+ //
+
+ // For datasource that support writing, overwrite these
+ // methods. By default they will return NOT_IMPLEMENTED.
+ ///
+ /// \name Low-level functions for writable data sources.
+ /// The default functions for these return W_NOT_IMPLEMENTED
+ /// A data source that supports writes MUST override these methods.
+ ///
+ //@{
+ /// \brief Start a transaction
+ /// The given transaction must be in state INIT.
+ /// On success, the transaction must be in state RUNNING.
+ /// The data source checks for the zone, starts a backend
+ /// transaction, and sets any data it will need later in the given
+ /// transaction object.
+ /// \param transaction, a transaction that has been initialized for
+ /// a zone held by this data source.
+ /// \param create_zone, if set to true, and the zone does not exist,
+ /// it is created within this transaction. If false, and
+ /// the zone does not exist, W_NO_SUCH_ZONE is returned
+ /// (defaults to false)
+ /// \return W_SUCCESS on success, error value otherwise
+ virtual WriteResult startTransaction(DataSrcTransaction& transaction,
+ const bool create_zone = false);
+
+ /// \brief Commit the changes made in the given transaction
+ /// The transaction object must be in state RUNNING.
+ /// On successs, the transaction will be in state DONE.
+ /// \param transaction to be committed.
+ /// \return W_SUCCESS on success, error value otherwise
+ virtual WriteResult commitTransaction(DataSrcTransaction& transaction);
+
+ /// \brief Roll back the changes made in the given transaction
+ /// The transaction object must be in state RUNNING.
+ /// On successs, the transaction will be in state DONE.
+ /// \param transaction to be rolled back.
+ /// \return W_SUCCESS on success, error value otherwise
+ virtual WriteResult rollbackTransaction(DataSrcTransaction& transaction);
+
+ /// \brief Add an RRset to the zone.
+ /// \param transaction The transaction in which this addition is
+ /// performed.
+ /// \param RRsetPtr The RRset to add
+ /// \param W_SUCCESS on success.
+ virtual WriteResult addRRset(DataSrcTransaction& transaction,
+ isc::dns::ConstRRsetPtr rrset);
+
+ /// \brief Delete an RRset from the zone.
+ /// If the given RRset contains no Rdata parts, all RRs with
+ /// the name, type and class of the given rrset are deleted.
+ /// Otherwise every RR in the given RRset is deleted.
+ /// \param transaction The transaction in which this deletion is
+ /// performed.
+ /// \param RRsetPtr The RRset to delete
+ /// \param W_SUCCESS on success.
+ virtual WriteResult delRRset(DataSrcTransaction& transaction,
+ isc::dns::ConstRRsetPtr rrset);
+
+ /// \brief Remove an entire zone from the data source
+ /// Removes all records for the zone, and the zone itself from
+ /// the datasource.
+ /// \param transaction The transaction in which this deletion is
+ /// performed.
+ /// \return W_SUCCESS on success
+ virtual WriteResult delZone(DataSrcTransaction& transaction);
+
+ /// \brief Replace the contents of the zone by the rrsets returned
+ /// by the given function callback.
+ /// The provided function should return exactly one RRsetPtr on
+ /// each call.
+ /// This deletes *all* current RRsets in the zone.
+ ///
+ /// Note: Developers do not necessarily need to write a callback
+ /// function. For instance, if you have an std::vector of rrsets,
+ /// you can use the helper function callbackHelperRRsetVector. See its
+ /// description for more information on how to do this.
+ ///
+ /// \param transaction The transaction to perform the operations on
+ /// \param nextRRset Function that returns the next RRsetPtr on
+ /// each call to it
+ /// \param arg1 This will be passed as the first argument to the
+ /// nextRRset function (defaults to NULL)
+ /// \param arg2 This will be passed as the second argument to the
+ /// nextRRset function (defaults to NULL)
+ /// \return W_SUCCESS on success
+ virtual WriteResult replaceZone(DataSrcTransaction& transaction,
+ isc::dns::RRsetPtr (*nextRRset)(void*, void*),
+ void* arg1 = NULL, void* arg2 = NULL);
+ //@}
+
+ /// \name High-level functions for writable data sources
+ /// These functions have a default implementation, and do not
+ /// need to be overridden. They can be, if the data source can make
+ /// use of specific knowledge about the backend.
+
+ //@{
+ /// \brief Checks whether an RRset exists
+ /// If the given RRset is of class NONE or ANY, it will check if
+ /// there are rrs for class IN.
+ /// If the given rrset has 0 rdata records; any RR with the given
+ /// name, class and type will result in a positive result,
+ /// otherwise the rrset in the data source must be exactly the
+ /// same.
+ ///
+ /// \param transaction The transaction in which the check is
+ /// performed
+ /// \param rrset The rrset to check for
+ /// \return true if the given rrset is equal to the one in the
+ /// database, or if the given rrset has a 0 rdata count,
+ /// and there are records in the database with the same
+ /// name, class and type. False otherwise.
+ virtual bool haveRRset(DataSrcTransaction& transaction,
+ isc::dns::ConstRRsetPtr rrset);
+
+ /// \brief Checks a prerequisite as described in section 3.2 of
+ /// RFC2136
+ /// \param transaction The transaction to perform the check in
+ /// \param prereq the prerequisite RRset
+ /// \return The DNS Rcode as defined by the rules in RFC2136
+ virtual isc::dns::Rcode updateCheckPrerequisite(DataSrcTransaction& transaction,
+ isc::dns::ConstRRsetPtr prereq);
+
+ /// \brief Handles a single update as described in section 3.4 of
+ /// RFC2136
+ /// \param transaction The transaction to perform the update
+ /// \param update the update RRset
+ /// \return The DNS Rcode as defined by the rules in RFC2136
+ virtual isc::dns::Rcode updateProcessUpdate(DataSrcTransaction& transaction,
+ isc::dns::RRsetPtr update);
+
+ /// \brief Perform an IXFR operation with data provided by the
+ /// given function.
+ /// The given function should return one RRset at a time, which
+ /// are processed as described in RFC1995
+ ///
+ /// Note: if you have an isc::dns::Message object that contains
+ /// a complete IXFR response, you can use the helper function
+ /// callbackHelperRRsetIterator() here. See the documentation
+ /// of that function on how to use it.
+ ///
+ /// \param transaction the transaction to perform the operation in
+ /// \param nextRRset Function that returns the next RRsetPtr on
+ /// each call to it
+ /// \param arg1 This will be passed as the first argument to the
+ /// nextRRset function (defaults to NULL)
+ /// \param arg2 This will be passed as the second argument to the
+ /// nextRRset function (defaults to NULL)
+ /// \return SUCCESS on success, ERROR on failure.
+ virtual Result doIXFR(DataSrcTransaction& transaction,
+ isc::dns::RRsetPtr (*nextRRset)(void*, void*),
+ void* arg1 = NULL, void* arg2 = NULL);
+
+ /// \brief Perform a DNS Dynamic update as desribed in RFC2136
+ /// \param transaction the transaction to perform the udpate in
+ /// \param msg the DNS UPDATE msg to handle
+ /// \return SUCCESS on success, ERROR on failure.
+ virtual Result doUpdate(DataSrcTransaction& transaction,
+ isc::dns::Message& msg);
+ //@}
+
private:
isc::dns::RRClass rrclass;
};
@@ -292,6 +621,24 @@
std::string& hash,
isc::dns::RRsetList& target) const;
+ //
+ // Writable data sources
+ //
+ // see comments at DataSrc
+ virtual WriteResult startTransaction(DataSrcTransaction& transaction,
+ const bool create_zone);
+ virtual WriteResult commitTransaction(DataSrcTransaction& transaction);
+ virtual WriteResult rollbackTransaction(DataSrcTransaction& transaction);
+
+ virtual WriteResult addRRset(DataSrcTransaction& transaction,
+ isc::dns::ConstRRsetPtr rrset);
+ virtual WriteResult delRRset(DataSrcTransaction& transaction,
+ isc::dns::ConstRRsetPtr rrset);
+ virtual WriteResult delZone(DataSrcTransaction& transaction);
+ virtual WriteResult replaceZone(DataSrcTransaction& transaction,
+ isc::dns::RRsetPtr (*nextRRset)(void*, void*),
+ void* arg1 = NULL, void* arg2 = NULL);
+ // end of writable data sources part
private:
std::vector<ConstDataSrcPtr> data_sources;
};
Modified: branches/trac374/src/lib/datasrc/sqlite3_datasrc.cc
==============================================================================
--- branches/trac374/src/lib/datasrc/sqlite3_datasrc.cc (original)
+++ branches/trac374/src/lib/datasrc/sqlite3_datasrc.cc Tue Oct 19 08:16:09 2010
@@ -14,6 +14,8 @@
// $Id$
+#include <config.h>
+
#include <string>
#include <sstream>
@@ -27,6 +29,10 @@
#include <dns/rrset.h>
#include <dns/rrsetlist.h>
+#include <cc/data.h>
+
+#include <boost/foreach.hpp>
+
using namespace std;
using namespace isc::dns;
using namespace isc::dns::rdata;
@@ -38,7 +44,8 @@
Sqlite3Parameters() : db_(NULL), version_(-1),
q_zone_(NULL), q_record_(NULL), q_addrs_(NULL), q_referral_(NULL),
q_any_(NULL), q_count_(NULL), q_previous_(NULL), q_nsec3_(NULL),
- q_prevnsec3_(NULL)
+ q_prevnsec3_(NULL), w_add_rr_(NULL), w_add_zone_(NULL), w_del_rr_(NULL),
+ w_del_all_(NULL), w_del_zone_(NULL)
{}
sqlite3* db_;
int version_;
@@ -51,6 +58,11 @@
sqlite3_stmt* q_previous_;
sqlite3_stmt* q_nsec3_;
sqlite3_stmt* q_prevnsec3_;
+ sqlite3_stmt* w_add_rr_;
+ sqlite3_stmt* w_add_zone_;
+ sqlite3_stmt* w_del_rr_;
+ sqlite3_stmt* w_del_all_;
+ sqlite3_stmt* w_del_zone_;
};
namespace {
@@ -111,7 +123,30 @@
const char* const q_prevnsec3_str = "SELECT hash FROM nsec3 "
"WHERE zone_id = ?1 AND hash <= $2 ORDER BY hash DESC LIMIT 1";
-}
+const char* const w_add_rr_str = "INSERT INTO records "
+ "(zone_id, name, rname, ttl, rdtype, sigtype, rdata) "
+ "VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)";
+
+const char* const w_add_zone_str = "INSERT INTO zones "
+ "(name, rdclass) "
+ "VALUES (?1, ?2)";
+
+const char* const w_del_rr_str = "DELETE FROM records WHERE "
+ "zone_id = ?1 AND name = ?2 AND rdtype LIKE ?3 AND rdata LIKE ?4";
+
+const char* const w_del_all_str = "DELETE FROM records WHERE zone_id = ?1";
+
+const char* const w_del_zone_str = "DELETE FROM zones WHERE id = ?1";
+
+const char* const w_start_transaction_str = "BEGIN TRANSACTION";
+
+const char* const w_commit_transaction_str = "COMMIT TRANSACTION";
+
+const char* const w_rollback_transaction_str = "ROLLBACK TRANSACTION";
+
+
+}
+
//
// Find the exact zone match. Return -1 if not found, or the zone's
@@ -605,6 +640,12 @@
if (params_.q_prevnsec3_ != NULL) {
sqlite3_finalize(params_.q_prevnsec3_);
}
+ if (params_.w_add_rr_ != NULL) {
+ sqlite3_finalize(params_.w_add_rr_);
+ }
+ if (params_.w_del_rr_ != NULL) {
+ sqlite3_finalize(params_.w_del_rr_);
+ }
if (params_.db_ != NULL) {
sqlite3_close(params_.db_);
}
@@ -658,6 +699,12 @@
initializer->params_.q_previous_ = prepare(db, q_previous_str);
initializer->params_.q_nsec3_ = prepare(db, q_nsec3_str);
initializer->params_.q_prevnsec3_ = prepare(db, q_prevnsec3_str);
+ initializer->params_.w_add_rr_ = prepare(db, w_add_rr_str);
+ initializer->params_.w_add_zone_ = prepare(db, w_add_zone_str);
+ initializer->params_.w_del_rr_ = prepare(db, w_del_rr_str);
+ initializer->params_.w_del_all_ = prepare(db, w_del_all_str);
+ initializer->params_.w_del_zone_ = prepare(db, w_del_zone_str);
+
}
}
@@ -672,8 +719,9 @@
Sqlite3Initializer initializer;
- if (sqlite3_open(name.c_str(), &initializer.params_.db_) != 0) {
- isc_throw(Sqlite3Error, "Cannot open SQLite database file: " << name);
+ int sq_result = sqlite3_open(name.c_str(), &initializer.params_.db_);
+ if (sq_result != 0) {
+ isc_throw(Sqlite3Error, "Cannot open SQLite database file " << name << ": error " << sq_result << " (" << sqlite3_errmsg(dbparameters->db_) << ")");
}
checkAndSetupSchema(&initializer);
@@ -718,11 +766,454 @@
sqlite3_finalize(dbparameters->q_nsec3_);
dbparameters->q_nsec3_ = NULL;
+ sqlite3_finalize(dbparameters->w_add_rr_);
+ dbparameters->w_add_rr_ = NULL;
+
+ sqlite3_finalize(dbparameters->w_add_zone_);
+ dbparameters->w_add_zone_ = NULL;
+
+ sqlite3_finalize(dbparameters->w_del_rr_);
+ dbparameters->w_del_rr_ = NULL;
+
+ sqlite3_finalize(dbparameters->w_del_all_);
+ dbparameters->w_del_rr_ = NULL;
+
+ sqlite3_finalize(dbparameters->w_del_zone_);
+ dbparameters->w_del_rr_ = NULL;
+
sqlite3_close(dbparameters->db_);
dbparameters->db_ = NULL;
return (SUCCESS);
}
-}
-}
+DataSrc::WriteResult
+Sqlite3DataSrc::startTransaction(DataSrcTransaction& transaction, bool new_zone)
+{
+ if (transaction.getState() != DataSrcTransaction::INIT) {
+ return DataSrc::W_ERROR;
+ }
+ int result = sqlite3_exec(dbparameters->db_, w_start_transaction_str, NULL, NULL, NULL);
+ if (result == SQLITE_OK) {
+ int zone_id = hasExactZone(transaction.getZoneName().toText().c_str());
+ if (zone_id <= 0) {
+ if (new_zone) {
+ DataSrc::WriteResult add_zone_res =
+ addZone(transaction.getZoneName(), isc::dns::RRClass::IN());
+ if (add_zone_res != DataSrc::W_SUCCESS) {
+ return add_zone_res;
+ }
+ zone_id = hasExactZone(transaction.getZoneName().toText().c_str());
+ } else {
+ return DataSrc::W_NO_SUCH_ZONE;
+ }
+ }
+ isc::data::ElementPtr trans_data = isc::data::Element::createMap();
+ trans_data->set("zone_id", isc::data::Element::create(zone_id));
+ transaction.setData(trans_data);
+ transaction.setState(DataSrcTransaction::RUNNING);
+ return DataSrc::W_SUCCESS;
+ } else {
+ return DataSrc::W_DB_ERROR;
+ }
+}
+
+DataSrc::WriteResult
+Sqlite3DataSrc::commitTransaction(DataSrcTransaction& transaction UNUSED_PARAM)
+{
+ if (transaction.getState() != DataSrcTransaction::RUNNING) {
+ return DataSrc::W_ERROR;
+ }
+
+ int result = sqlite3_exec(dbparameters->db_, w_commit_transaction_str, NULL, NULL, NULL);
+
+ if (result == SQLITE_OK) {
+ transaction.setState(DataSrcTransaction::DONE);
+ return DataSrc::W_SUCCESS;
+ } else {
+ return DataSrc::W_DB_ERROR;
+ }
+}
+
+DataSrc::WriteResult
+Sqlite3DataSrc::rollbackTransaction(DataSrcTransaction& transaction)
+{
+ if (transaction.getState() != DataSrcTransaction::RUNNING) {
+ return DataSrc::W_ERROR;
+ }
+ int result = sqlite3_exec(dbparameters->db_, w_rollback_transaction_str, NULL, NULL, NULL);
+
+ if (result == SQLITE_OK) {
+ transaction.setState(DataSrcTransaction::DONE);
+ return DataSrc::W_SUCCESS;
+ } else {
+ return DataSrc::W_DB_ERROR;
+ }
+}
+
+DataSrc::WriteResult
+Sqlite3DataSrc::addZone(const isc::dns::Name& name,
+ const isc::dns::RRClass& rrclass)
+{
+ sqlite3_stmt* query = dbparameters->w_add_zone_;
+
+ sqlite3_reset(query);
+ sqlite3_clear_bindings(query);
+
+ const string s_name = name.toText();
+ int rc;
+ rc = sqlite3_bind_text(query, 1, s_name.c_str(), -1, SQLITE_TRANSIENT);
+ if (rc != SQLITE_OK) {
+ isc_throw(Sqlite3Error, "Could not bind name " << s_name <<
+ " to SQL statement (query)");
+ }
+
+ rc = sqlite3_bind_text(query, 2, rrclass.toText().c_str(), -1,
+ SQLITE_TRANSIENT);
+ if (rc != SQLITE_OK) {
+ isc_throw(Sqlite3Error, "Could not bind zone class " <<
+ rrclass.toText() << " to SQL statement (query)");
+ }
+
+ int result = sqlite3_step(query);
+
+ sqlite3_reset(query);
+
+ if (result == SQLITE_DONE) {
+ return DataSrc::W_SUCCESS;
+ } else {
+ return DataSrc::W_DB_ERROR;
+ }
+}
+
+DataSrc::WriteResult
+Sqlite3DataSrc::addRR(int zone_id,
+ const isc::dns::Name& name,
+ const isc::dns::RRType& rrtype,
+ const isc::dns::RRTTL& rrttl,
+ const isc::dns::rdata::Rdata& rdata)
+{
+
+ sqlite3_stmt* query = dbparameters->w_add_rr_;
+
+ sqlite3_reset(query);
+ sqlite3_clear_bindings(query);
+
+ int rc;
+ rc = sqlite3_bind_int(query, 1, zone_id);
+ if (rc != SQLITE_OK) {
+ isc_throw(Sqlite3Error, "Could not bind zone ID " << zone_id <<
+ " to SQL statement (query)");
+ }
+ const string s_name = name.toText();
+ rc = sqlite3_bind_text(query, 2, s_name.c_str(), -1, SQLITE_TRANSIENT);
+ if (rc != SQLITE_OK) {
+ isc_throw(Sqlite3Error, "Could not bind name " << s_name <<
+ " to SQL statement (query)");
+ }
+
+ // reverse name
+ const string r_name = name.reverse().toText();
+ rc = sqlite3_bind_text(query, 3, r_name.c_str(), -1, SQLITE_TRANSIENT);
+ if (rc != SQLITE_OK) {
+ isc_throw(Sqlite3Error, "Could not bind name " << s_name <<
+ " to SQL statement (query)");
+ }
+
+ rc = sqlite3_bind_int(query, 4, rrttl.getValue());
+ if (rc != SQLITE_OK) {
+ isc_throw(Sqlite3Error, "Could not bind RR TTL " <<
+ rrttl.toText() << " to SQL statement (query)");
+ }
+
+ rc = sqlite3_bind_text(query, 5, rrtype.toText().c_str(), -1,
+ SQLITE_TRANSIENT);
+ if (rc != SQLITE_OK) {
+ isc_throw(Sqlite3Error, "Could not bind RR type " <<
+ rrtype.toText() << " to SQL statement (query)");
+ }
+
+ // TODO: sigtype
+
+ rc = sqlite3_bind_text(query, 7, rdata.toText().c_str(), -1,
+ SQLITE_TRANSIENT);
+ if (rc != SQLITE_OK) {
+ isc_throw(Sqlite3Error, "Could not bind RR rdata " <<
+ rdata.toText() << " to SQL statement (query)");
+ }
+
+ int result = sqlite3_step(query);
+
+ sqlite3_reset(query);
+
+ if (result == SQLITE_DONE) {
+ return DataSrc::W_SUCCESS;
+ } else {
+ return DataSrc::W_DB_ERROR;
+ }
+
+}
+
+DataSrc::WriteResult
+Sqlite3DataSrc::addRRset(DataSrcTransaction& transaction,
+ isc::dns::ConstRRsetPtr rrset)
+{
+ if (transaction.getState() != DataSrcTransaction::RUNNING) {
+ return DataSrc::W_ERROR;
+ }
+ if (rrset->getRdataCount() == 0) {
+ return DataSrc::W_ERROR;
+ }
+ int zone_id = transaction.getData()->get("zone_id")->intValue();
+ RdataIteratorPtr rdp = rrset->getRdataIterator();
+ rdp->first();
+
+ DataSrc::WriteResult result = DataSrc::W_SUCCESS;
+ do {
+ result = addRR(zone_id, rrset->getName(), rrset->getType(),
+ rrset->getTTL(), rdp->getCurrent());
+ if (result != DataSrc::W_SUCCESS) {
+ return result;
+ } else {
+ rdp->next();
+ }
+ } while (!rdp->isLast());
+ return result;
+}
+
+DataSrc::WriteResult
+Sqlite3DataSrc::delRR(int zone_id,
+ const isc::dns::Name& name,
+ const isc::dns::RRType& rrtype)
+{
+ sqlite3_stmt* query = dbparameters->w_del_rr_;
+
+ sqlite3_reset(query);
+ sqlite3_clear_bindings(query);
+
+ int rc;
+ rc = sqlite3_bind_int(query, 1, zone_id);
+ if (rc != SQLITE_OK) {
+ isc_throw(Sqlite3Error, "Could not bind zone ID " << zone_id <<
+ " to SQL statement (query)");
+ }
+ const string s_name = name.toText();
+ rc = sqlite3_bind_text(query, 2, s_name.c_str(), -1, SQLITE_TRANSIENT);
+ if (rc != SQLITE_OK) {
+ isc_throw(Sqlite3Error, "Could not bind name " << s_name <<
+ " to SQL statement (query)");
+ }
+
+ if (rrtype == RRType::ANY()) {
+ rc = sqlite3_bind_text(query, 3, "%", -1,
+ SQLITE_STATIC);
+ } else {
+ rc = sqlite3_bind_text(query, 3, rrtype.toText().c_str(), -1,
+ SQLITE_TRANSIENT);
+ }
+ if (rc != SQLITE_OK) {
+ isc_throw(Sqlite3Error, "Could not bind RR type " <<
+ rrtype.toText() << " to SQL statement (query)");
+ }
+
+ rc = sqlite3_bind_text(query, 4, "%", -1,
+ SQLITE_STATIC);
+ if (rc != SQLITE_OK) {
+ isc_throw(Sqlite3Error, "Could not bind '%' rdata "
+ " to SQL statement (query)");
+ }
+
+ int result = sqlite3_step(query);
+ int deleted_rows = sqlite3_total_changes(dbparameters->db_);
+
+ sqlite3_reset(query);
+
+ if (result == SQLITE_DONE) {
+ if (deleted_rows > 0) {
+ return DataSrc::W_SUCCESS;
+ } else {
+ return DataSrc::W_NO_SUCH_DATA;
+ }
+ } else {
+ return DataSrc::W_DB_ERROR;
+ }
+}
+
+DataSrc::WriteResult
+Sqlite3DataSrc::delRR(int zone_id,
+ const isc::dns::Name& name,
+ const isc::dns::RRType& rrtype,
+ const isc::dns::rdata::Rdata& rdata)
+{
+ sqlite3_stmt* query = dbparameters->w_del_rr_;
+
+ sqlite3_reset(query);
+ sqlite3_clear_bindings(query);
+
+ int rc;
+ rc = sqlite3_bind_int(query, 1, zone_id);
+ if (rc != SQLITE_OK) {
+ isc_throw(Sqlite3Error, "Could not bind zone ID " << zone_id <<
+ " to SQL statement (query)");
+ }
+ const string s_name = name.toText();
+ rc = sqlite3_bind_text(query, 2, s_name.c_str(), -1, SQLITE_TRANSIENT);
+ if (rc != SQLITE_OK) {
+ isc_throw(Sqlite3Error, "Could not bind name " << s_name <<
+ " to SQL statement (query)");
+ }
+
+ rc = sqlite3_bind_text(query, 3, rrtype.toText().c_str(), -1,
+ SQLITE_TRANSIENT);
+ if (rc != SQLITE_OK) {
+ isc_throw(Sqlite3Error, "Could not bind RR type " <<
+ rrtype.toText() << " to SQL statement (query)");
+ }
+
+ rc = sqlite3_bind_text(query, 4, rdata.toText().c_str(), -1,
+ SQLITE_TRANSIENT);
+ if (rc != SQLITE_OK) {
+ isc_throw(Sqlite3Error, "Could not bind RR rdata " <<
+ rdata.toText() << " to SQL statement (query)");
+ }
+
+ int result = sqlite3_step(query);
+ int deleted_rows = sqlite3_total_changes(dbparameters->db_);
+
+ sqlite3_reset(query);
+
+ if (result == SQLITE_DONE) {
+ if (deleted_rows > 0) {
+ return DataSrc::W_SUCCESS;
+ } else {
+ return DataSrc::W_NO_SUCH_DATA;
+ }
+ } else {
+ return DataSrc::W_DB_ERROR;
+ }
+}
+
+DataSrc::WriteResult
+Sqlite3DataSrc::delAll(int zone_id)
+{
+ sqlite3_stmt* query = dbparameters->w_del_all_;
+
+ sqlite3_reset(query);
+ sqlite3_clear_bindings(query);
+
+ int rc;
+ rc = sqlite3_bind_int(query, 1, zone_id);
+ if (rc != SQLITE_OK) {
+ isc_throw(Sqlite3Error, "Could not bind zone ID " << zone_id <<
+ " to SQL statement (query)");
+ }
+
+ int result = sqlite3_step(query);
+
+ sqlite3_reset(query);
+
+ if (result == SQLITE_DONE) {
+ return DataSrc::W_SUCCESS;
+ } else {
+ return DataSrc::W_DB_ERROR;
+ }
+}
+
+DataSrc::WriteResult
+Sqlite3DataSrc::replaceZone(DataSrcTransaction& transaction,
+ isc::dns::RRsetPtr (*nextRRset)(void*, void*),
+ void* arg1, void* arg2)
+{
+ if (transaction.getState() != DataSrcTransaction::RUNNING) {
+ return DataSrc::W_ERROR;
+ }
+
+ int zone_id = transaction.getData()->get("zone_id")->intValue();
+
+ DataSrc::WriteResult result = delAll(zone_id);
+ if (result != DataSrc::W_SUCCESS) {
+ return result;
+ }
+
+ if (!nextRRset) {
+ return DataSrc::W_SUCCESS;
+ }
+ RRsetPtr next_rrset = nextRRset(arg1, arg2);
+ while (next_rrset) {
+ result = addRRset(transaction, next_rrset);
+ if (result != DataSrc::W_SUCCESS) {
+ return result;
+ }
+ next_rrset = nextRRset(arg1, arg2);
+ }
+ return DataSrc::W_SUCCESS;
+}
+
+DataSrc::WriteResult
+Sqlite3DataSrc::delZone(DataSrcTransaction& transaction)
+{
+ if (transaction.getState() != DataSrcTransaction::RUNNING) {
+ return DataSrc::W_ERROR;
+ }
+
+ int zone_id = transaction.getData()->get("zone_id")->intValue();
+
+ DataSrc::WriteResult result = delAll(zone_id);
+ if (result != DataSrc::W_SUCCESS) {
+ return result;
+ }
+
+ sqlite3_stmt* query = dbparameters->w_del_zone_;
+
+ sqlite3_reset(query);
+ sqlite3_clear_bindings(query);
+
+ int rc;
+ rc = sqlite3_bind_int(query, 1, zone_id);
+ if (rc != SQLITE_OK) {
+ isc_throw(Sqlite3Error, "Could not bind zone ID " << zone_id <<
+ " to SQL statement (query)");
+ }
+
+ int q_result = sqlite3_step(query);
+
+ sqlite3_reset(query);
+
+ if (q_result == SQLITE_DONE) {
+ return DataSrc::W_SUCCESS;
+ } else {
+ return DataSrc::W_DB_ERROR;
+ }
+}
+
+DataSrc::WriteResult
+Sqlite3DataSrc::delRRset(DataSrcTransaction& transaction,
+ isc::dns::ConstRRsetPtr rrset)
+{
+ if (transaction.getState() != DataSrcTransaction::RUNNING) {
+ return DataSrc::W_ERROR;
+ }
+
+ int zone_id = transaction.getData()->get("zone_id")->intValue();
+
+ DataSrc::WriteResult result = DataSrc::W_SUCCESS;
+
+ if (rrset->getRdataCount() > 0) {
+ RdataIteratorPtr rdp = rrset->getRdataIterator();
+ rdp->first();
+ do {
+ result = delRR(zone_id, rrset->getName(), rrset->getType(), rdp->getCurrent());
+ if (result != DataSrc::W_SUCCESS) {
+ return result;
+ } else {
+ rdp->next();
+ }
+ } while (!rdp->isLast());
+ } else {
+ result = delRR(zone_id, rrset->getName(), rrset->getType());
+ }
+ return result;
+}
+
+}
+}
Modified: branches/trac374/src/lib/datasrc/sqlite3_datasrc.h
==============================================================================
--- branches/trac374/src/lib/datasrc/sqlite3_datasrc.h (original)
+++ branches/trac374/src/lib/datasrc/sqlite3_datasrc.h Tue Oct 19 08:16:09 2010
@@ -22,6 +22,12 @@
#include <exceptions/exceptions.h>
#include <datasrc/data_source.h>
+
+#include <dns/name.h>
+#include <dns/rrttl.h>
+#include <dns/rdata.h>
+#include <dns/rrset.h>
+#include <dns/message.h>
namespace isc {
@@ -98,6 +104,40 @@
Result init(const isc::data::ConstElementPtr config);
Result close();
+
+ // write access
+
+ WriteResult startTransaction(DataSrcTransaction& transaction,
+ bool create_zone = false);
+ WriteResult commitTransaction(DataSrcTransaction& transaction);
+ WriteResult rollbackTransaction(DataSrcTransaction& transaction);
+
+ WriteResult addRRset(DataSrcTransaction& transaction,
+ isc::dns::ConstRRsetPtr rrset);
+ WriteResult delRRset(DataSrcTransaction& transaction,
+ isc::dns::ConstRRsetPtr rrset);
+ WriteResult replaceZone(DataSrcTransaction& transaction,
+ isc::dns::RRsetPtr (*nextRRset)(void*, void*),
+ void* arg1 = NULL, void* arg2 = NULL);
+ WriteResult delZone(DataSrcTransaction& transaction);
+
+private:
+ DataSrc::WriteResult addRR(int zone_id,
+ const isc::dns::Name& name,
+ const isc::dns::RRType& rrtype,
+ const isc::dns::RRTTL& rrttl,
+ const isc::dns::rdata::Rdata& rdata);
+ DataSrc::WriteResult addZone(const isc::dns::Name& name,
+ const isc::dns::RRClass& rrclass);
+ DataSrc::WriteResult delRR(int zone_id,
+ const isc::dns::Name& name,
+ const isc::dns::RRType& rrtype);
+ DataSrc::WriteResult delRR(int zone_id,
+ const isc::dns::Name& name,
+ const isc::dns::RRType& rrtype,
+ const isc::dns::rdata::Rdata& rdata);
+ DataSrc::WriteResult delAll(int zone_id);
+
private:
enum Mode {
NORMAL,
Modified: branches/trac374/src/lib/datasrc/tests/Makefile.am
==============================================================================
--- branches/trac374/src/lib/datasrc/tests/Makefile.am (original)
+++ branches/trac374/src/lib/datasrc/tests/Makefile.am Tue Oct 19 08:16:09 2010
@@ -1,6 +1,8 @@
AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
AM_CPPFLAGS += -I$(top_builddir)/src/lib/dns -I$(top_srcdir)/src/lib/dns
AM_CPPFLAGS += -DTEST_DATA_DIR=\"$(srcdir)/testdata\"
+AM_CPPFLAGS += -DTEST_DATA_OUT_DIR=\"$(builddir)/testdata\"
+AM_CPPFLAGS += -DINSTALL_PROG=\"$(top_srcdir)/install-sh\"
AM_CXXFLAGS = $(B10_CXXFLAGS)
@@ -46,3 +48,5 @@
EXTRA_DIST += testdata/sql2.example.com.signed
EXTRA_DIST += testdata/test-root.sqlite3
EXTRA_DIST += testdata/test.sqlite3
+EXTRA_DIST += testdata/ixfr_ok.rrs
+EXTRA_DIST += testdata/ixfr_bad_remove_nonexisting.rrs
Modified: branches/trac374/src/lib/datasrc/tests/sqlite3_unittest.cc
==============================================================================
--- branches/trac374/src/lib/datasrc/tests/sqlite3_unittest.cc (original)
+++ branches/trac374/src/lib/datasrc/tests/sqlite3_unittest.cc Tue Oct 19 08:16:09 2010
@@ -14,18 +14,23 @@
// $Id$
+#include <config.h>
+
#include <stdint.h>
#include <algorithm>
#include <string>
#include <vector>
+#include <fstream>
#include <sqlite3.h>
#include <gtest/gtest.h>
#include <dns/name.h>
#include <dns/message.h>
+#include <dns/opcode.h>
#include <dns/rdata.h>
+#include <dns/rcode.h>
#include <dns/rrclass.h>
#include <dns/rrtype.h>
#include <dns/rdataclass.h>
@@ -53,6 +58,8 @@
"{ \"database_file\": \"" TEST_DATA_DIR "/brokendb.sqlite3\"}");
ConstElementPtr SQLITE_DBFILE_MEMORY = Element::fromJSON(
"{ \"database_file\": \":memory:\"}");
+ElementPtr SQLITE_DBFILE_WRITE = Element::fromJSON(
+ "{ \"database_file\": \"" TEST_DATA_OUT_DIR "/write_test.sqlite3\"}");
// The following file must be non existent and must be non"creatable";
// the sqlite3 library will try to create a new DB file if it doesn't exist,
@@ -102,7 +109,8 @@
Sqlite3DataSourceTest() : rrclass(RRClass::IN()),
rrclass_notmatch(RRClass::CH()),
rrtype(RRType::A()), rrttl(RRTTL(3600)),
- find_flags(0), rrset_matched(0), rrset_count(0)
+ find_flags(0), rrset_matched(0), rrset_count(0),
+ zone_name("example.com")
{
data_source.init(SQLITE_DBFILE_EXAMPLE);
@@ -172,6 +180,13 @@
nsec3_sig_data.push_back("NSEC3 5 4 7200 20100410172647 "
"20100311172647 63192 sql2.example.com. "
+ nsec3_signature);
+
+ // initialize data for writable tests
+ new_rrset = RRsetPtr(new RRset(Name("new_rr.example.com"),
+ RRClass::IN(),
+ RRType::A(),
+ RRTTL(3600)));
+ new_rrset->addRdata(createRdata(RRType::A(), RRClass::IN(), "192.0.2.3"));
}
Sqlite3DataSrc data_source;
// we allow these to be modified in the test
@@ -227,6 +242,10 @@
vector<string> child_sig_data;
vector<string> nsec3_data;
vector<string> nsec3_sig_data;
+
+ // common data for writable tests
+ Name zone_name;
+ RRsetPtr new_rrset;
void findReferralRRsetCommon(const Name& qname, const RRClass& qclass);
void findAddressRRsetCommon(const RRClass& qclass);
@@ -935,4 +954,962 @@
findAddressRRsetCommon(RRClass::ANY());
}
-}
+//
+// We copy one of the example databases to a writable one
+// (the source one may not be writable, and we modify it, so if
+// one of the tests fail halfway, we want to restart with a clean
+// database next time)
+//
+int
+install_writable_database()
+{
+ return system(INSTALL_PROG " " TEST_DATA_DIR "/test.sqlite3 " TEST_DATA_OUT_DIR "/write_test.sqlite3" );
+}
+
+TEST_F(Sqlite3DataSourceTest, Transactions) {
+ // first writables test, reset database
+ ASSERT_EQ(0, install_writable_database());
+
+ // use our copied writable datasource db
+ ASSERT_EQ(DataSrc::SUCCESS, data_source.close());
+ ASSERT_EQ(DataSrc::SUCCESS, data_source.init(SQLITE_DBFILE_WRITE));
+
+ DataSrcTransaction transaction(&data_source, zone_name, RRClass::IN());
+ EXPECT_EQ(DataSrcTransaction::INIT, transaction.getState());
+
+ // start doing things without calling startTransaction()
+ EXPECT_EQ(DataSrc::W_ERROR,
+ data_source.commitTransaction(transaction));
+ EXPECT_EQ(DataSrc::W_ERROR,
+ data_source.rollbackTransaction(transaction));
+ EXPECT_EQ(DataSrc::W_ERROR,
+ data_source.addRRset(transaction, new_rrset));
+ RRsetIterator rrset_it = RRsetIterator();
+ EXPECT_EQ(DataSrc::W_ERROR,
+ data_source.replaceZone(transaction, NULL));
+ EXPECT_EQ(DataSrc::W_ERROR,
+ data_source.delZone(transaction));
+ EXPECT_EQ(DataSrc::W_ERROR,
+ data_source.delRRset(transaction, new_rrset));
+ // need rrsetit for doIXFR (but doIXFR should probably be moved up to datasource itself)
+
+ EXPECT_EQ(DataSrc::W_SUCCESS,
+ data_source.startTransaction(transaction));
+ EXPECT_EQ(DataSrcTransaction::RUNNING, transaction.getState());
+ EXPECT_EQ(DataSrc::W_ERROR,
+ data_source.startTransaction(transaction));
+ EXPECT_EQ(DataSrc::W_SUCCESS,
+ data_source.rollbackTransaction(transaction));
+ EXPECT_EQ(DataSrcTransaction::DONE, transaction.getState());
+
+ // state now 'done', everything should error again
+ EXPECT_EQ(DataSrc::W_ERROR,
+ data_source.commitTransaction(transaction));
+ EXPECT_EQ(DataSrc::W_ERROR,
+ data_source.rollbackTransaction(transaction));
+ EXPECT_EQ(DataSrc::W_ERROR,
+ data_source.addRRset(transaction, new_rrset));
+ EXPECT_EQ(DataSrc::W_ERROR,
+ data_source.replaceZone(transaction, NULL));
+ EXPECT_EQ(DataSrc::W_ERROR,
+ data_source.delZone(transaction));
+ EXPECT_EQ(DataSrc::W_ERROR,
+ data_source.delRRset(transaction, new_rrset));
+}
+
+TEST_F(Sqlite3DataSourceTest, addRRset) {
+
+ // use our copied writable datasource db
+ ASSERT_EQ(DataSrc::SUCCESS, data_source.close());
+ ASSERT_EQ(DataSrc::SUCCESS, data_source.init(SQLITE_DBFILE_WRITE));
+
+ // check whether our new record does not exist
+ EXPECT_EQ(DataSrc::SUCCESS,
+ data_source.findAddrs(new_rrset->getName(), new_rrset->getClass(),
+ result_sets, find_flags, &zone_name));
+ EXPECT_EQ(DataSrc::NAME_NOT_FOUND, find_flags);
+
+ // add it, but roll back
+ DataSrcTransaction transaction1(&data_source, zone_name, RRClass::IN());
+ EXPECT_EQ(DataSrc::W_SUCCESS,
+ data_source.startTransaction(transaction1));
+ EXPECT_EQ(DataSrc::W_SUCCESS,
+ data_source.addRRset(transaction1, new_rrset));
+ EXPECT_EQ(DataSrc::W_SUCCESS,
+ data_source.rollbackTransaction(transaction1));
+
+ // check whether our new record does not exist
+ EXPECT_EQ(DataSrc::SUCCESS,
+ data_source.findAddrs(new_rrset->getName(), new_rrset->getClass(),
+ result_sets, find_flags, &zone_name));
+ EXPECT_EQ(DataSrc::NAME_NOT_FOUND, find_flags);
+
+ // add it with commit
+ DataSrcTransaction transaction2(&data_source, zone_name, RRClass::IN());
+ EXPECT_EQ(DataSrc::W_SUCCESS,
+ data_source.startTransaction(transaction2));
+ EXPECT_EQ(DataSrc::W_SUCCESS,
+ data_source.addRRset(transaction2, new_rrset));
+ EXPECT_EQ(DataSrc::W_SUCCESS,
+ data_source.commitTransaction(transaction2));
+
+ // now check whether it does exist
+ EXPECT_EQ(DataSrc::SUCCESS,
+ data_source.findAddrs(new_rrset->getName(), new_rrset->getClass(),
+ result_sets, find_flags, &zone_name));
+ EXPECT_EQ(DataSrc::SUCCESS, find_flags);
+ EXPECT_EQ(result_sets.size(), 1);
+}
+
+TEST_F(Sqlite3DataSourceTest, delRRset) {
+
+ // use our copied writable datasource db
+ ASSERT_EQ(DataSrc::SUCCESS, data_source.close());
+ ASSERT_EQ(DataSrc::SUCCESS, data_source.init(SQLITE_DBFILE_WRITE));
+
+ // check whether our new record exists (added by previous test)
+ EXPECT_EQ(DataSrc::SUCCESS,
+ data_source.findAddrs(new_rrset->getName(), new_rrset->getClass(),
+ result_sets, find_flags, &zone_name));
+ EXPECT_EQ(DataSrc::SUCCESS, find_flags);
+ EXPECT_EQ(result_sets.size(), 1);
+
+ // add it, but roll back
+ DataSrcTransaction transaction1(&data_source, zone_name, RRClass::IN());
+ EXPECT_EQ(DataSrc::W_SUCCESS,
+ data_source.startTransaction(transaction1));
+ EXPECT_EQ(DataSrc::W_SUCCESS,
+ data_source.delRRset(transaction1, new_rrset));
+ EXPECT_EQ(DataSrc::W_SUCCESS,
+ data_source.rollbackTransaction(transaction1));
+
+ // check whether our new record still exists
+ EXPECT_EQ(DataSrc::SUCCESS,
+ data_source.findAddrs(new_rrset->getName(), new_rrset->getClass(),
+ result_sets, find_flags, &zone_name));
+ EXPECT_EQ(DataSrc::SUCCESS, find_flags);
+ EXPECT_EQ(result_sets.size(), 1);
+
+ // add it, and commit
+ DataSrcTransaction transaction2(&data_source, zone_name, RRClass::IN());
+ EXPECT_EQ(DataSrc::W_SUCCESS,
+ data_source.startTransaction(transaction2));
+ EXPECT_EQ(DataSrc::W_SUCCESS,
+ data_source.delRRset(transaction2, new_rrset));
+ EXPECT_EQ(DataSrc::W_SUCCESS,
+ data_source.commitTransaction(transaction2));
+
+ // now check whether it does not exist now
+ EXPECT_EQ(DataSrc::SUCCESS,
+ data_source.findAddrs(new_rrset->getName(), new_rrset->getClass(),
+ result_sets, find_flags, &zone_name));
+ EXPECT_EQ(DataSrc::NAME_NOT_FOUND, find_flags);
+}
+
+TEST_F(Sqlite3DataSourceTest, replaceZone_container) {
+ // reset database
+ ASSERT_EQ(0, install_writable_database());
+
+ // use our copied writable datasource db
+ ASSERT_EQ(DataSrc::SUCCESS, data_source.close());
+ ASSERT_EQ(DataSrc::SUCCESS, data_source.init(SQLITE_DBFILE_WRITE));
+
+ // check whether an A exists
+ EXPECT_EQ(DataSrc::SUCCESS,
+ data_source.findRRset(www_name, RRClass::IN(), RRType::A(),
+ result_sets, find_flags, &zone_name));
+ EXPECT_EQ(DataSrc::SUCCESS, find_flags);
+ EXPECT_EQ(1, result_sets.size());
+
+ // Replace them, roll back
+ DataSrcTransaction transaction1(&data_source, zone_name, RRClass::IN());
+ ASSERT_EQ(DataSrc::W_SUCCESS, data_source.startTransaction(transaction1));
+ ASSERT_EQ(DataSrc::W_SUCCESS, data_source.replaceZone(transaction1, NULL));
+ ASSERT_EQ(DataSrc::W_SUCCESS, data_source.rollbackTransaction(transaction1));
+
+ // check whether it still exists
+ EXPECT_EQ(DataSrc::SUCCESS,
+ data_source.findRRset(www_name, RRClass::IN(), RRType::A(),
+ result_sets, find_flags, &zone_name));
+ EXPECT_EQ(DataSrc::SUCCESS, find_flags);
+ EXPECT_EQ(1, result_sets.size());
+
+ // Replace them, commit
+ DataSrcTransaction transaction2(&data_source, zone_name, RRClass::IN());
+ ASSERT_EQ(DataSrc::W_SUCCESS, data_source.startTransaction(transaction2));
+ ASSERT_EQ(DataSrc::W_SUCCESS, data_source.replaceZone(transaction2, NULL));
+ ASSERT_EQ(DataSrc::W_SUCCESS, data_source.commitTransaction(transaction2));
+
+ // check whether it's gone now
+ EXPECT_EQ(DataSrc::SUCCESS,
+ data_source.findRRset(www_name, RRClass::IN(), RRType::A(),
+ result_sets, find_flags, &zone_name));
+ EXPECT_EQ(DataSrc::NAME_NOT_FOUND, find_flags);
+ EXPECT_EQ(1, result_sets.size());
+
+}
+
+RRsetPtr
+getRRsetCallback_empty(void *arg1 UNUSED_PARAM,
+ void* arg2 UNUSED_PARAM) {
+ return RRsetPtr();
+}
+
+TEST_F(Sqlite3DataSourceTest, replaceZone_callback_empty) {
+ // reset database
+ ASSERT_EQ(0, install_writable_database());
+
+ // use our copied writable datasource db
+ ASSERT_EQ(DataSrc::SUCCESS, data_source.close());
+ ASSERT_EQ(DataSrc::SUCCESS, data_source.init(SQLITE_DBFILE_WRITE));
+
+ // check whether an A exists
+ EXPECT_EQ(DataSrc::SUCCESS,
+ data_source.findRRset(www_name, RRClass::IN(), RRType::A(),
+ result_sets, find_flags, &zone_name));
+ EXPECT_EQ(DataSrc::SUCCESS, find_flags);
+ EXPECT_EQ(1, result_sets.size());
+
+ // Replace them, roll back
+ DataSrcTransaction transaction1(&data_source, zone_name, RRClass::IN());
+ ASSERT_EQ(DataSrc::W_SUCCESS, data_source.startTransaction(transaction1));
+ ASSERT_EQ(DataSrc::W_SUCCESS, data_source.replaceZone(transaction1, getRRsetCallback_empty));
+ ASSERT_EQ(DataSrc::W_SUCCESS, data_source.rollbackTransaction(transaction1));
+
+ // check whether it still exists
+ EXPECT_EQ(DataSrc::SUCCESS,
+ data_source.findRRset(www_name, RRClass::IN(), RRType::A(),
+ result_sets, find_flags, &zone_name));
+ EXPECT_EQ(DataSrc::SUCCESS, find_flags);
+ EXPECT_EQ(1, result_sets.size());
+
+ // Replace them, commit
+ DataSrcTransaction transaction2(&data_source, zone_name, RRClass::IN());
+ ASSERT_EQ(DataSrc::W_SUCCESS, data_source.startTransaction(transaction2));
+ ASSERT_EQ(DataSrc::W_SUCCESS, data_source.replaceZone(transaction2, getRRsetCallback_empty));
+ ASSERT_EQ(DataSrc::W_SUCCESS, data_source.commitTransaction(transaction2));
+
+ // check whether it's gone now
+ EXPECT_EQ(DataSrc::SUCCESS,
+ data_source.findRRset(www_name, RRClass::IN(), RRType::A(),
+ result_sets, find_flags, &zone_name));
+ EXPECT_EQ(DataSrc::NAME_NOT_FOUND, find_flags);
+ EXPECT_EQ(1, result_sets.size());
+
+}
+
+TEST_F(Sqlite3DataSourceTest, replaceZone_callback_vector) {
+ std::vector<RRsetPtr> rrsets;
+
+ size_t i = 0;
+
+ // reset database
+ ASSERT_EQ(0, install_writable_database());
+
+ // use our copied writable datasource db
+ ASSERT_EQ(DataSrc::SUCCESS, data_source.close());
+ ASSERT_EQ(DataSrc::SUCCESS, data_source.init(SQLITE_DBFILE_WRITE));
+
+ // Let's take the existing SOA for now
+ RRsetList soa_rrset;
+ EXPECT_EQ(DataSrc::SUCCESS,
+ data_source.findExactRRset(zone_name, RRClass::IN(), RRType::SOA(),
+ soa_rrset, find_flags, &zone_name));
+ ASSERT_EQ(DataSrc::SUCCESS, find_flags);
+ ASSERT_EQ(1, soa_rrset.size());
+
+ rrsets.push_back(soa_rrset.findRRset(RRType::SOA(), RRClass::IN()));
+ rrsets.push_back(new_rrset);
+
+ // Replace them, roll back
+ DataSrcTransaction transaction1(&data_source, zone_name, RRClass::IN());
+ ASSERT_EQ(DataSrc::W_SUCCESS, data_source.startTransaction(transaction1));
+ ASSERT_EQ(DataSrc::W_SUCCESS, data_source.replaceZone(transaction1, isc::datasrc::callbackHelperRRsetVector, &rrsets, &i));
+ ASSERT_EQ(DataSrc::W_SUCCESS, data_source.rollbackTransaction(transaction1));
+
+ // check whether it still exists
+ EXPECT_EQ(DataSrc::SUCCESS,
+ data_source.findRRset(www_name, RRClass::IN(), RRType::A(),
+ result_sets, find_flags, &zone_name));
+ EXPECT_EQ(DataSrc::SUCCESS, find_flags);
+ EXPECT_EQ(1, result_sets.size());
+
+ // Replace them, commit
+ i = 0;
+ DataSrcTransaction transaction2(&data_source, zone_name, RRClass::IN());
+ ASSERT_EQ(DataSrc::W_SUCCESS, data_source.startTransaction(transaction2));
+ ASSERT_EQ(DataSrc::W_SUCCESS, data_source.replaceZone(transaction2, isc::datasrc::callbackHelperRRsetVector, &rrsets, &i));
+ ASSERT_EQ(DataSrc::W_SUCCESS, data_source.commitTransaction(transaction2));
+
+ // check whether the original rrs are gone now
+ EXPECT_EQ(DataSrc::SUCCESS,
+ data_source.findRRset(www_name, RRClass::IN(), RRType::A(),
+ result_sets, find_flags, &zone_name));
+ EXPECT_EQ(DataSrc::NAME_NOT_FOUND, find_flags);
+ EXPECT_EQ(1, result_sets.size());
+
+ // and check if the new record exists
+ EXPECT_EQ(DataSrc::SUCCESS,
+ data_source.findExactRRset(new_rrset->getName(), new_rrset->getClass(), new_rrset->getType(),
+ result_sets, find_flags, &zone_name));
+ //std::cout << "[XX] done, exit" << std::endl;
+ //data_source.close();
+ //exit(0);
+ ASSERT_EQ(DataSrc::SUCCESS, find_flags);
+ ASSERT_EQ(1, result_sets.size());
+}
+
+TEST_F(Sqlite3DataSourceTest, delZone) {
+ // reset database
+ ASSERT_EQ(0, install_writable_database());
+
+ // use our copied writable datasource db
+ ASSERT_EQ(DataSrc::SUCCESS, data_source.close());
+ ASSERT_EQ(DataSrc::SUCCESS, data_source.init(SQLITE_DBFILE_WRITE));
+
+ // check whether an A exists
+ EXPECT_EQ(DataSrc::SUCCESS,
+ data_source.findRRset(www_name, RRClass::IN(), RRType::A(),
+ result_sets, find_flags, &zone_name));
+ EXPECT_EQ(DataSrc::SUCCESS, find_flags);
+ EXPECT_EQ(1, result_sets.size());
+
+ // Delete zone, roll back
+ DataSrcTransaction transaction1(&data_source, zone_name, RRClass::IN());
+ ASSERT_EQ(DataSrc::W_SUCCESS, data_source.startTransaction(transaction1));
+ ASSERT_EQ(DataSrc::W_SUCCESS, data_source.delZone(transaction1));
+ ASSERT_EQ(DataSrc::W_SUCCESS, data_source.rollbackTransaction(transaction1));
+
+ // check whether it still exists
+ EXPECT_EQ(DataSrc::SUCCESS,
+ data_source.findRRset(www_name, RRClass::IN(), RRType::A(),
+ result_sets, find_flags, &zone_name));
+ EXPECT_EQ(DataSrc::SUCCESS, find_flags);
+ EXPECT_EQ(1, result_sets.size());
+
+ // Delete zone, commit
+ DataSrcTransaction transaction2(&data_source, zone_name, RRClass::IN());
+ ASSERT_EQ(DataSrc::W_SUCCESS, data_source.startTransaction(transaction2));
+ ASSERT_EQ(DataSrc::W_SUCCESS, data_source.delZone(transaction2));
+ ASSERT_EQ(DataSrc::W_SUCCESS, data_source.commitTransaction(transaction2));
+
+ // check whether it's gone now
+ EXPECT_EQ(DataSrc::SUCCESS,
+ data_source.findRRset(www_name, RRClass::IN(), RRType::A(),
+ result_sets, find_flags, &zone_name));
+ EXPECT_EQ(DataSrc::NO_SUCH_ZONE, find_flags);
+ EXPECT_EQ(1, result_sets.size());
+
+ // try to start a transaction for the zone that is now gone
+ DataSrcTransaction transaction3(&data_source, zone_name, RRClass::IN());
+ EXPECT_EQ(DataSrc::W_NO_SUCH_ZONE, data_source.startTransaction(transaction3));
+
+}
+
+
+static QuestionPtr
+questionFromFile(ifstream& file)
+{
+ string s;
+ file >> s;
+ if (s == "") {
+ return QuestionPtr();
+ }
+ if (s.size() > 0 && s[0] == ';') {
+ std::string ignore_rest_of_line;
+ getline(file, ignore_rest_of_line);
+ return QuestionPtr();
+ }
+ Name n(s);
+ file >> s;
+ RRClass rrclass(s);
+ file >> s;
+ RRType rrtype(s);
+ string line;
+ getline(file, line);
+
+ QuestionPtr question(new Question(n, rrclass, rrtype));
+ return question;
+}
+
+static RRsetPtr
+rrsetFromFile(ifstream& file)
+{
+ string s;
+ file >> s;
+ if (s == "") {
+ return RRsetPtr();
+ }
+ if (s.size() > 0 && s[0] == ';') {
+ std::string ignore_rest_of_line;
+ getline(file, ignore_rest_of_line);
+ return RRsetPtr();
+ }
+ Name n(s);
+
+ file >> s;
+ RRTTL ttl(s);
+
+ file >> s;
+ RRClass rrclass(0);
+ if (s == "ANY") {
+ rrclass = RRClass::ANY();
+ } else if (s == "NONE") {
+ rrclass = RRClass::NONE();
+ } else {
+ rrclass = RRClass(s);
+ }
+
+ file >> s;
+ RRType rrtype(0);
+ if (s == "ANY") {
+ rrtype = RRType::ANY();
+ } else {
+ rrtype = RRType(s);
+ }
+
+ RRsetPtr rrset = RRsetPtr(new RRset(n,
+ rrclass,
+ rrtype,
+ ttl));
+ string line;
+ getline(file, line);
+ while (line[0] == ' ' || line[0] == '\t') {
+ line.erase(0, 1);
+ }
+
+ if (line != "") {
+ RdataPtr rdata = createRdata(rrtype, rrclass, line);
+ rrset->addRdata(rdata);
+ }
+ return rrset;
+}
+
+// initialize the message with flags and codes,
+// read a list of rrs and put them in that order in the answer
+// section of the given message
+
+// reads rrsets from the given file into the given section
+// stops at eof or if empty line or line starting with ; is
+// read
+// returns 1 if empty line/comment has been seen
+// returns 0 if eof reached
+
+int
+rrsetsFromFile(ifstream& file, Message& msg, Section section)
+{
+ RRsetPtr prev_rrset = RRsetPtr();
+ while (! file.eof() ) {
+ RRsetPtr rrset = rrsetFromFile(file);
+ if (!rrset) {
+ if (prev_rrset) {
+ msg.addRRset(section, prev_rrset);
+ }
+ return 1;
+ }
+ if (prev_rrset) {
+ if (rrset) {
+ if (prev_rrset->getName() == rrset->getName() &&
+ prev_rrset->getType() == rrset->getType() &&
+ prev_rrset->getType() != RRType::SOA() &&
+ prev_rrset->getClass() == rrset->getClass()) {
+ RdataIteratorPtr it = rrset->getRdataIterator();
+ for (it->first(); !it->isLast(); it->next()) {
+ prev_rrset->addRdata(it->getCurrent());
+ }
+ rrset = RRsetPtr();
+ }
+ }
+ msg.addRRset(section, prev_rrset);
+ prev_rrset = rrset;
+ } else {
+ prev_rrset = rrset;
+ }
+ }
+ return 0;
+}
+
+int
+rrsetsFromFile(ifstream& file, std::vector<RRsetPtr>& container)
+{
+ RRsetPtr prev_rrset = RRsetPtr();
+ while (! file.eof() ) {
+ RRsetPtr rrset = rrsetFromFile(file);
+ if (!rrset) {
+ if (prev_rrset) {
+ container.push_back(prev_rrset);
+ }
+ return 1;
+ }
+ if (prev_rrset) {
+ if (rrset) {
+ if (prev_rrset->getName() == rrset->getName() &&
+ prev_rrset->getType() == rrset->getType() &&
+ prev_rrset->getType() != RRType::SOA() &&
+ prev_rrset->getClass() == rrset->getClass()) {
+ RdataIteratorPtr it = rrset->getRdataIterator();
+ for (it->first(); !it->isLast(); it->next()) {
+ prev_rrset->addRdata(it->getCurrent());
+ }
+ rrset = RRsetPtr();
+ }
+ }
+ container.push_back(prev_rrset);
+ prev_rrset = rrset;
+ } else {
+ prev_rrset = rrset;
+ }
+ }
+ return 0;
+}
+
+int
+rrsetsFromFile(const char* file_name, std::vector<RRsetPtr>& container)
+{
+ ifstream myfile(file_name);
+ if (myfile.is_open()) {
+ rrsetsFromFile(myfile, container);
+ myfile.close();
+ } else {
+ return -1;
+ }
+ return 0;
+}
+
+static int
+ixfrFromFile(Message& m, const char* file_name) {
+ ifstream myfile(file_name);
+
+ m.setHeaderFlag(MessageFlag::AA());
+ m.setHeaderFlag(MessageFlag::QR());
+ m.setQid(1234);
+ m.setOpcode(isc::dns::Opcode::QUERY());
+ m.setRcode(isc::dns::Rcode::NOERROR());
+ if (myfile.is_open()) {
+ rrsetsFromFile(myfile, m, Section::ANSWER());
+ myfile.close();
+ } else {
+ return -1;
+ }
+ return 0;
+}
+
+static int
+updateFromFile(Message& m, const char* file_name) {
+ ifstream myfile(file_name);
+
+ m.clear(Message::RENDER);
+ m.setQid(1234);
+ m.setOpcode(isc::dns::Opcode::UPDATE());
+ m.setRcode(isc::dns::Rcode::NOERROR());
+ int stage = 1;
+ QuestionPtr question;
+ if (myfile.is_open()) {
+ while (! myfile.eof() ) {
+ switch (stage) {
+ case 1:
+ question = questionFromFile(myfile);
+ if (question) {
+ m.addQuestion(question);
+ } else {
+ stage++;
+ }
+ break;
+ case 2:
+ rrsetsFromFile(myfile, m, Section::ANSWER());
+ rrsetsFromFile(myfile, m, Section::AUTHORITY());
+ rrsetsFromFile(myfile, m, Section::ADDITIONAL());
+ }
+ }
+ } else {
+ return -1;
+ }
+ return 0;
+}
+
+// check a single rrset for exact match including rdata
+static void
+checkSingleRRset(const DataSrc& data_source,
+ const std::string& expected,
+ const Name& name,
+ const RRClass& rrclass,
+ const RRType& rrtype)
+{
+ RRsetList result_sets;
+ uint32_t find_flags;
+
+ EXPECT_EQ(DataSrc::SUCCESS,
+ data_source.findRRset(name, rrclass, rrtype,
+ result_sets, find_flags, &zone_name));
+
+ //EXPECT_EQ(DataSrc::SUCCESS, find_flags);
+ if (expected == "") {
+ // noerror/nodata gives an rdatalen=0 rrset back?
+ if (result_sets.size() == 1) {
+ EXPECT_EQ(0, (*result_sets.begin())->getRdataCount());
+ } else {
+ EXPECT_EQ(0, result_sets.size());
+ }
+ } else {
+ EXPECT_EQ(1, result_sets.size());
+ EXPECT_EQ(expected, (*result_sets.begin())->toText());
+ }
+}
+
+/*
+TEST_F(Sqlite3DataSourceTest, ixfr_ok_message) {
+ // reset database
+ ASSERT_EQ(0, install_writable_database());
+
+ // use our copied writable datasource db
+ ASSERT_EQ(DataSrc::SUCCESS, data_source.close());
+ ASSERT_EQ(DataSrc::SUCCESS, data_source.init(SQLITE_DBFILE_WRITE));
+
+ checkSingleRRset(data_source,
+ "www.example.com. 3600 IN A 192.0.2.1\n",
+ www_name, RRClass::IN(), RRType::A());
+
+ Message ixfr_msg(Message::RENDER);
+ ASSERT_EQ(0, ixfrFromFile(ixfr_msg, TEST_DATA_DIR "/ixfr_ok.rrs"));
+
+ // do IXFR, roll back
+ DataSrcTransaction transaction1(&data_source, zone_name, RRClass::IN());
+ ASSERT_EQ(DataSrc::W_SUCCESS, data_source.startTransaction(transaction1));
+ EXPECT_EQ(DataSrc::SUCCESS,
+ data_source.doIXFR(transaction1,
+ ixfr_msg.beginSection(Section::ANSWER()),
+ ixfr_msg.endSection(Section::ANSWER()))
+ );
+ ASSERT_EQ(DataSrc::W_SUCCESS, data_source.rollbackTransaction(transaction1));
+
+ checkSingleRRset(data_source,
+ "www.example.com. 3600 IN A 192.0.2.1\n",
+ www_name, RRClass::IN(), RRType::A());
+
+ // do IXFR, commit
+ DataSrcTransaction transaction2(&data_source, zone_name, RRClass::IN());
+ ASSERT_EQ(DataSrc::W_SUCCESS, data_source.startTransaction(transaction2));
+ EXPECT_EQ(DataSrc::SUCCESS,
+ data_source.doIXFR(transaction2,
+ ixfr_msg.beginSection(Section::ANSWER()),
+ ixfr_msg.endSection(Section::ANSWER()))
+ );
+ ASSERT_EQ(DataSrc::W_SUCCESS, data_source.commitTransaction(transaction2));
+
+ checkSingleRRset(data_source,
+ "www.example.com. 3600 IN A 192.0.2.3\n",
+ www_name, RRClass::IN(), RRType::A());
+
+ // same IXFR should now fail, because the start serial doesn't match
+ DataSrcTransaction transaction3(&data_source, zone_name, RRClass::IN());
+ ASSERT_EQ(DataSrc::W_SUCCESS, data_source.startTransaction(transaction3));
+ EXPECT_EQ(DataSrc::ERROR,
+ data_source.doIXFR(transaction3,
+ ixfr_msg.beginSection(Section::ANSWER()),
+ ixfr_msg.endSection(Section::ANSWER()))
+ );
+ ASSERT_EQ(DataSrc::W_SUCCESS, data_source.rollbackTransaction(transaction3));
+}
+*/
+
+TEST_F(Sqlite3DataSourceTest, ixfr_ok_message) {
+ // reset database
+ ASSERT_EQ(0, install_writable_database());
+
+ // use our copied writable datasource db
+ ASSERT_EQ(DataSrc::SUCCESS, data_source.close());
+ ASSERT_EQ(DataSrc::SUCCESS, data_source.init(SQLITE_DBFILE_WRITE));
+
+ checkSingleRRset(data_source,
+ "www.example.com. 3600 IN A 192.0.2.1\n",
+ www_name, RRClass::IN(), RRType::A());
+
+ Message ixfr_msg(Message::RENDER);
+ ASSERT_EQ(0, ixfrFromFile(ixfr_msg, TEST_DATA_DIR "/ixfr_ok.rrs"));
+
+ // do IXFR, roll back
+ DataSrcTransaction transaction1(&data_source, zone_name, RRClass::IN());
+ ASSERT_EQ(DataSrc::W_SUCCESS, data_source.startTransaction(transaction1));
+ RRsetIterator begin, end;
+ begin = ixfr_msg.beginSection(Section::ANSWER());
+ end = ixfr_msg.endSection(Section::ANSWER());
+ EXPECT_EQ(DataSrc::SUCCESS,
+ data_source.doIXFR(transaction1,
+ callbackHelperRRsetIterator,
+ &begin,
+ &end)
+ );
+ ASSERT_EQ(DataSrc::W_SUCCESS, data_source.rollbackTransaction(transaction1));
+
+ checkSingleRRset(data_source,
+ "www.example.com. 3600 IN A 192.0.2.1\n",
+ www_name, RRClass::IN(), RRType::A());
+
+ // do IXFR, commit
+ DataSrcTransaction transaction2(&data_source, zone_name, RRClass::IN());
+ ASSERT_EQ(DataSrc::W_SUCCESS, data_source.startTransaction(transaction2));
+ begin = ixfr_msg.beginSection(Section::ANSWER());
+ end = ixfr_msg.endSection(Section::ANSWER());
+ EXPECT_EQ(DataSrc::SUCCESS,
+ data_source.doIXFR(transaction2,
+ isc::datasrc::callbackHelperRRsetIterator,
+ &begin,
+ &end)
+ );
+ ASSERT_EQ(DataSrc::W_SUCCESS, data_source.commitTransaction(transaction2));
+
+ checkSingleRRset(data_source,
+ "www.example.com. 3600 IN A 192.0.2.3\n",
+ www_name, RRClass::IN(), RRType::A());
+
+ // same IXFR should now fail, because the start serial doesn't match
+ DataSrcTransaction transaction3(&data_source, zone_name, RRClass::IN());
+ ASSERT_EQ(DataSrc::W_SUCCESS, data_source.startTransaction(transaction3));
+ begin = ixfr_msg.beginSection(Section::ANSWER());
+ end = ixfr_msg.endSection(Section::ANSWER());
+ EXPECT_EQ(DataSrc::ERROR,
+ data_source.doIXFR(transaction3,
+ isc::datasrc::callbackHelperRRsetIterator,
+ &begin,
+ &end)
+ );
+ ASSERT_EQ(DataSrc::W_SUCCESS, data_source.rollbackTransaction(transaction3));
+}
+
+TEST_F(Sqlite3DataSourceTest, ixfr_ok_function) {
+ // reset database
+ ASSERT_EQ(0, install_writable_database());
+
+ // use our copied writable datasource db
+ ASSERT_EQ(DataSrc::SUCCESS, data_source.close());
+ ASSERT_EQ(DataSrc::SUCCESS, data_source.init(SQLITE_DBFILE_WRITE));
+
+ checkSingleRRset(data_source,
+ "www.example.com. 3600 IN A 192.0.2.1\n",
+ www_name, RRClass::IN(), RRType::A());
+
+ std::vector<RRsetPtr> rrsets;
+ ASSERT_EQ(0, rrsetsFromFile(TEST_DATA_DIR "/ixfr_ok.rrs", rrsets));
+
+ // do IXFR, roll back
+ size_t i = 0;
+ DataSrcTransaction transaction1(&data_source, zone_name, RRClass::IN());
+ ASSERT_EQ(DataSrc::W_SUCCESS, data_source.startTransaction(transaction1));
+ ASSERT_EQ(DataSrc::SUCCESS,
+ data_source.doIXFR(transaction1,
+ isc::datasrc::callbackHelperRRsetVector, &rrsets, &i)
+ );
+ ASSERT_EQ(DataSrc::W_SUCCESS, data_source.rollbackTransaction(transaction1));
+
+ checkSingleRRset(data_source,
+ "www.example.com. 3600 IN A 192.0.2.1\n",
+ www_name, RRClass::IN(), RRType::A());
+
+ // do IXFR, commit
+ i = 0;
+ DataSrcTransaction transaction2(&data_source, zone_name, RRClass::IN());
+ ASSERT_EQ(DataSrc::W_SUCCESS, data_source.startTransaction(transaction2));
+ ASSERT_EQ(DataSrc::SUCCESS,
+ data_source.doIXFR(transaction2,
+ isc::datasrc::callbackHelperRRsetVector, &rrsets, &i)
+ );
+ ASSERT_EQ(DataSrc::W_SUCCESS, data_source.commitTransaction(transaction2));
+
+ checkSingleRRset(data_source,
+ "www.example.com. 3600 IN A 192.0.2.3\n",
+ www_name, RRClass::IN(), RRType::A());
+
+ // same IXFR should now fail, because the start serial doesn't match
+ i = 0;
+ DataSrcTransaction transaction3(&data_source, zone_name, RRClass::IN());
+ ASSERT_EQ(DataSrc::W_SUCCESS, data_source.startTransaction(transaction3));
+ ASSERT_EQ(DataSrc::ERROR,
+ data_source.doIXFR(transaction3,
+ isc::datasrc::callbackHelperRRsetVector, &rrsets, &i)
+ );
+ ASSERT_EQ(DataSrc::W_SUCCESS, data_source.rollbackTransaction(transaction3));
+}
+
+TEST_F(Sqlite3DataSourceTest, ixfr_bad) {
+ // reset database
+ ASSERT_EQ(0, install_writable_database());
+
+ // use our copied writable datasource db
+ ASSERT_EQ(DataSrc::SUCCESS, data_source.close());
+ ASSERT_EQ(DataSrc::SUCCESS, data_source.init(SQLITE_DBFILE_WRITE));
+
+ // a bad ixfr, tries to remove a non-existant RR
+ Message ixfr_msg(Message::RENDER);
+ ASSERT_EQ(0, ixfrFromFile(ixfr_msg, TEST_DATA_DIR
+ "/ixfr_bad_remove_nonexisting.rrs"));
+ DataSrcTransaction transaction1(&data_source, zone_name, RRClass::IN());
+ ASSERT_EQ(DataSrc::W_SUCCESS, data_source.startTransaction(transaction1));
+ RRsetIterator begin, end;
+ begin = ixfr_msg.beginSection(Section::ANSWER());
+ end = ixfr_msg.endSection(Section::ANSWER());
+ EXPECT_EQ(DataSrc::ERROR,
+ data_source.doIXFR(transaction1,
+ isc::datasrc::callbackHelperRRsetIterator,
+ &begin,
+ &end)
+ );
+ ASSERT_EQ(DataSrc::W_SUCCESS, data_source.rollbackTransaction(transaction1));
+}
+
+TEST_F(Sqlite3DataSourceTest, dynamic_update) {
+ // reset database
+ ASSERT_EQ(0, install_writable_database());
+
+ // use our copied writable datasource db
+ ASSERT_EQ(DataSrc::SUCCESS, data_source.close());
+ ASSERT_EQ(DataSrc::SUCCESS, data_source.init(SQLITE_DBFILE_WRITE));
+
+ Message update_msg(Message::RENDER);
+
+ DataSrcTransaction transaction1(&data_source, zone_name, RRClass::IN());
+ ASSERT_EQ(DataSrc::W_SUCCESS, data_source.startTransaction(transaction1));
+ update_msg.setQid(1234);
+
+ // bad qtype
+ update_msg.setOpcode(isc::dns::Opcode::QUERY());
+ ASSERT_EQ(DataSrc::ERROR, data_source.doUpdate(transaction1, update_msg));
+
+ update_msg.setOpcode(isc::dns::Opcode::UPDATE());
+
+ // no/bad question section
+ ASSERT_EQ(DataSrc::ERROR, data_source.doUpdate(transaction1, update_msg));
+
+ QuestionPtr bad_question(new Question(Name("example.com"), RRClass::CH(), RRType::A()));
+ update_msg.addQuestion(bad_question);
+ ASSERT_EQ(DataSrc::ERROR, data_source.doUpdate(transaction1, update_msg));
+
+ // removeQuestion not implemented, reset msg
+ // checks, we delete one set, one name, one rr, add a set, and add to a set
+ // delete ip46 A rrset
+ // delete all from www.example.com
+ // delete 1 mx record
+ // add newaddr.example.com 192.0.2.21
+ // add dns04.example.com
+
+ // check if they are (not) there first
+ checkSingleRRset(data_source,
+ "ip46.example.com. 3600 IN A 192.0.2.1\n",
+ Name("ip46.example.com"), RRClass::IN(), RRType::A());
+ checkSingleRRset(data_source,
+ "www.example.com. 3600 IN A 192.0.2.1\n",
+ www_name, RRClass::IN(), RRType::A());
+ checkSingleRRset(data_source,
+ "example.com. 3600 IN MX 10 mail.example.com.\n"
+ "example.com. 3600 IN MX 20 mail.subzone.example.com.\n",
+ Name("example.com"), RRClass::IN(), RRType::MX());
+ checkSingleRRset(data_source,
+ "",
+ Name("newaddr.example.com"), RRClass::IN(), RRType::A());
+ checkSingleRRset(data_source,
+ "example.com. 3600 IN NS dns01.example.com.\n"
+ "example.com. 3600 IN NS dns02.example.com.\n"
+ "example.com. 3600 IN NS dns03.example.com.\n"
+ ,
+ Name("example.com"), RRClass::IN(), RRType::NS());
+
+ ASSERT_EQ(0, updateFromFile(update_msg, TEST_DATA_DIR "/update1.packet"));
+ ASSERT_EQ(DataSrc::SUCCESS, data_source.doUpdate(transaction1, update_msg));
+
+ ASSERT_EQ(DataSrc::W_SUCCESS, data_source.commitTransaction(transaction1));
+ checkSingleRRset(data_source,
+ "",
+ Name("ip46.example.com"), RRClass::IN(), RRType::A());
+ checkSingleRRset(data_source,
+ "",
+ www_name, RRClass::IN(), RRType::A());
+ checkSingleRRset(data_source,
+ "example.com. 3600 IN MX 20 mail.subzone.example.com.\n",
+ Name("example.com"), RRClass::IN(), RRType::MX());
+ checkSingleRRset(data_source,
+ "newaddr.example.com. 3600 IN A 192.0.2.12\n",
+ Name("newaddr.example.com"), RRClass::IN(), RRType::A());
+ checkSingleRRset(data_source,
+ "example.com. 3600 IN NS dns01.example.com.\n"
+ "example.com. 3600 IN NS dns02.example.com.\n"
+ "example.com. 3600 IN NS dns03.example.com.\n"
+ "example.com. 3600 IN NS dns04.example.com.\n"
+ ,
+ Name("example.com"), RRClass::IN(), RRType::NS());
+
+}
+
+TEST_F(Sqlite3DataSourceTest, dynamic_update_bad_class) {
+ // reset database
+ ASSERT_EQ(0, install_writable_database());
+
+ // use our copied writable datasource db
+ ASSERT_EQ(DataSrc::SUCCESS, data_source.close());
+ ASSERT_EQ(DataSrc::SUCCESS, data_source.init(SQLITE_DBFILE_WRITE));
+
+ Message update_msg(Message::RENDER);
+
+ DataSrcTransaction transaction1(&data_source, zone_name, RRClass::CH());
+ ASSERT_EQ(DataSrc::W_SUCCESS, data_source.startTransaction(transaction1));
+ update_msg.setQid(1234);
+
+ update_msg.setOpcode(isc::dns::Opcode::UPDATE());
+
+ // no/bad question section
+ ASSERT_EQ(DataSrc::ERROR, data_source.doUpdate(transaction1, update_msg));
+
+}
+
+TEST_F(Sqlite3DataSourceTest, dynamic_update_prereq_fails) {
+ // reset database
+ ASSERT_EQ(0, install_writable_database());
+
+ // use our copied writable datasource db
+ ASSERT_EQ(DataSrc::SUCCESS, data_source.close());
+ ASSERT_EQ(DataSrc::SUCCESS, data_source.init(SQLITE_DBFILE_WRITE));
+
+ Message update_msg(Message::RENDER);
+
+ DataSrcTransaction transaction1(&data_source, zone_name, RRClass::IN());
+ ASSERT_EQ(DataSrc::W_SUCCESS, data_source.startTransaction(transaction1));
+
+ ASSERT_EQ(0, updateFromFile(update_msg, TEST_DATA_DIR "/update2.packet"));
+ ASSERT_EQ(DataSrc::ERROR, data_source.doUpdate(transaction1, update_msg));
+ ASSERT_EQ(Rcode::NXRRSET(), update_msg.getRcode());
+
+ ASSERT_EQ(0, updateFromFile(update_msg, TEST_DATA_DIR "/update3.packet"));
+ ASSERT_EQ(DataSrc::ERROR, data_source.doUpdate(transaction1, update_msg));
+ ASSERT_EQ(Rcode::NXDOMAIN(), update_msg.getRcode());
+
+ ASSERT_EQ(0, updateFromFile(update_msg, TEST_DATA_DIR "/update4.packet"));
+ ASSERT_EQ(DataSrc::ERROR, data_source.doUpdate(transaction1, update_msg));
+ ASSERT_EQ(Rcode::NXRRSET(), update_msg.getRcode());
+
+ ASSERT_EQ(0, updateFromFile(update_msg, TEST_DATA_DIR "/update5.packet"));
+ ASSERT_EQ(DataSrc::ERROR, data_source.doUpdate(transaction1, update_msg));
+ ASSERT_EQ(Rcode::YXRRSET(), update_msg.getRcode());
+
+ ASSERT_EQ(0, updateFromFile(update_msg, TEST_DATA_DIR "/update6.packet"));
+ ASSERT_EQ(DataSrc::ERROR, data_source.doUpdate(transaction1, update_msg));
+ ASSERT_EQ(Rcode::YXDOMAIN(), update_msg.getRcode());
+
+}
+
+TEST_F(Sqlite3DataSourceTest, dynamic_update_bad_update) {
+ // reset database
+ ASSERT_EQ(0, install_writable_database());
+
+ // use our copied writable datasource db
+ ASSERT_EQ(DataSrc::SUCCESS, data_source.close());
+ ASSERT_EQ(DataSrc::SUCCESS, data_source.init(SQLITE_DBFILE_WRITE));
+
+ Message update_msg(Message::RENDER);
+
+ DataSrcTransaction transaction1(&data_source, zone_name, RRClass::IN());
+ ASSERT_EQ(DataSrc::W_SUCCESS, data_source.startTransaction(transaction1));
+
+ ASSERT_EQ(0, updateFromFile(update_msg, TEST_DATA_DIR "/update7.packet"));
+ ASSERT_EQ(DataSrc::ERROR, data_source.doUpdate(transaction1, update_msg));
+
+}
+
+}
More information about the bind10-changes
mailing list