BIND 10 trac1177, updated. c35f6b15bb6b703154e05399266dd2051ef9cfa9 [1177] Implement previous name for SQLite3
BIND 10 source code commits
bind10-changes at lists.isc.org
Fri Sep 9 11:04:12 UTC 2011
The branch, trac1177 has been updated
discards 56f6a1ea6ef70f8dbed5083ae3b8f8e264e7c225 (commit)
discards 11d759dbbfb1eaf61b184caf2aba37eeca48c23d (commit)
discards 91e77326e2f33dea91529a9157d46c27507f0a1a (commit)
discards 94fc0adc1b01f6a0632bb07a4c882e0cdf876741 (commit)
discards eb64a4df2e3b5c509a3611282779e333dbc31fc3 (commit)
discards d9fc1dbf6f63a93131cd78a11656cd150999d0fb (commit)
discards e9eb3b16a185becb8b68c0f603ea919e301a9127 (commit)
discards 2708caa4c3cb28141f8ac222421c487f21fc2bfb (commit)
discards 8926772852309c3f73399f2af013bca396a36bd3 (commit)
via c35f6b15bb6b703154e05399266dd2051ef9cfa9 (commit)
via 3f2864bf1271ca525858cf3e1fa641e3496eec59 (commit)
via f8720ba467d8e107c512160a5502caf9be58a425 (commit)
via 38af8a4225e8c82564758e8a5629da438220bc87 (commit)
via c5e0db2b7d8fbdb13548e01310f623f131ea0e9c (commit)
via 26c7bfe851f00422beb442a77d25cc0887557b79 (commit)
via f5239632a06383f2b4f6825cb6a006ceb8bea417 (commit)
via 680f05c35753bf1f70392d25b1e6310cf46476ce (commit)
via b12351c21ee92a13536aa89331cc73bd166dbe5f (commit)
via 2e1dceedf6a4f661a8d7e57757b28f9f6cb1a9b3 (commit)
via df69ad0d0231218610f68ecb2b1953ae7f28fa68 (commit)
via 5b713ea8e5fd35fdb1ab7ff953e010ef9b60f98c (commit)
via 02b2e71bdc1564f4272869bb5676727af809870f (commit)
via 8d1942a3b7516e8161b7f54888da2a4a4d27484e (commit)
via 856ff83ad2b97c136de1103a421547bdcb332e74 (commit)
via 7cc9b08f18967fa1a694f5b7e320aad62d0d3e88 (commit)
via 25e56e5d1bc9197e882e3a42285d0efad21a51f2 (commit)
via 87d2a8766e610a0dece7d86268ac9be4122d6d82 (commit)
via 64ac0166d5ea3b565f500f8a770dfa4d7d9f6a28 (commit)
via c86612cd4120b9ad3d00978c04ea252e7d501e44 (commit)
via c1c2ddf5be4556e6e8cd52a314ddd6d026c7e540 (commit)
via ba50f189eced101999efb96672179aa1024204e9 (commit)
via 6906362bebdbe7e0de66f2c8d10a00bd34911121 (commit)
via 83a58b817e5c0432d543b66208f502b059fdbe13 (commit)
via 40126733cc69634035b0cca3a0c90ee3a606ea3b (commit)
via bcafb8b98d5df77108a83a6bd8b7746f7c2616d7 (commit)
via 4ef59f25a452f934408a9ba837cea9b7fab0be48 (commit)
via 3d069e2745070bc23f14c845cb7d8116d919f0da (commit)
via 230df584722d08705f2cb3b99940b764b1cb7865 (commit)
via fda403b09887a24403c3a90d7ad6c95288f2d641 (commit)
via 748c3e1aeb833012a19b651af7d98757a8ffc50f (commit)
via a0e04c0ad837b4b42caf139573f2a95c86cdac76 (commit)
via fcb2409598d37e2078076cf43794ef6c445ac22f (commit)
via c6d2a365580709981852007cd0a9a3b32afaa5c3 (commit)
via da8bfe82aa18a67b1a99fa459f48cea89ee2a41a (commit)
via 7980a6c8e598d34f5f733f5c6c3ca83c0a0f1187 (commit)
via 9c62a36b0ebf9ff4ef3dad1f4d91195d301348ed (commit)
via 2ec9338d84714ea670ee888f1edf5a4ad220ea9a (commit)
via 1d907966f7f0fe7089efe46d8b808d9115f0d167 (commit)
via 93327a85ea63f7043c49a0af2384a1e274ab1dda (commit)
via 75e756cdf9d5b08e859afac5cef38bd818a90e60 (commit)
via 778bd1be6ced7f4a135e2a6bcc7414c4e4bdc27d (commit)
via 8fe581570c2ef4f881762f4f22ef4f66c1063491 (commit)
via 2812fa5cb0c2013ef1696888651390aa71a76b4a (commit)
via 255bf5b18e2b0e28a65062e87dc2d1212376bfc2 (commit)
via e2ada81cd2a090f707147abdb73a90d44db2f2b0 (commit)
via 0953b3f5d7ed1b4a25362f9a2d1a41eeeda8efa6 (commit)
via ecf3f4b962026aa9094ee321b03ee32df2fdf1d2 (commit)
via df047c5ccb5c81f9a3d36f7fc38a19bc7c8f2ac2 (commit)
via a7346d50ae5389ce37e35a7131f0f218663b8c68 (commit)
via ad91831c938430b6d4a8fd7bfae517a0f1e327c1 (commit)
via ad36c799ff07d47ebd5c861c63e9feef50408e34 (commit)
via c5e0ebf85ef50e61457f3b99a05109a92b328573 (commit)
via 8216d5dbe1ef23d56ba589fe1de619a601bada4b (commit)
via 1c834de994f51a1fb98add648dad49abfea2c403 (commit)
via dc9318330acbd36e07ad5a4e8a68c9a6e2430543 (commit)
via e4a17a0630a6460090c5cdb562e02ba992a74fa8 (commit)
via 4c98f3dc47545794daccd4978103f6b98236ad82 (commit)
via 2dfa0983e4680f321a3d4f1bd0d826abd88f455c (commit)
via d60bb44c243f27053589b5501529b0001404373f (commit)
via 92dcac243a4a2924bab85d1519a0c7a20853f9cc (commit)
via 2bd6dc4ac6ac61705517df297320fa79b308b9e3 (commit)
via 13e236a3d647d15858b061c7d96288bf7407e090 (commit)
via a7fe0d5982813f092f8a497d350620c02b995649 (commit)
via d71b7da05d3e1a82047e35c2720c759bdc0fb44f (commit)
via a577b387b7e5c9c8afd371767fccc85009e84485 (commit)
via 8e82cd7374cda9ef55f88504a94d31b06d7e1bd4 (commit)
via 1351cb42c92cd415003adf6234d96507c8a6d2db (commit)
via 575bd3ec2fe918257cb448eee8ebbff269d85431 (commit)
This update added new revisions after undoing existing revisions. That is
to say, the old revision is not a strict subset of the new revision. This
situation occurs when you --force push a change and generate a repository
containing something like this:
* -- * -- B -- O -- O -- O (56f6a1ea6ef70f8dbed5083ae3b8f8e264e7c225)
\
N -- N -- N (c35f6b15bb6b703154e05399266dd2051ef9cfa9)
When this happens we assume that you've already had alert emails for all
of the O revisions, and so we here report only the revisions in the N
branch from the common base, B.
Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.
- Log -----------------------------------------------------------------
commit c35f6b15bb6b703154e05399266dd2051ef9cfa9
Author: Michal 'vorner' Vaner <michal.vaner at nic.cz>
Date: Fri Sep 9 13:03:29 2011 +0200
[1177] Implement previous name for SQLite3
commit 3f2864bf1271ca525858cf3e1fa641e3496eec59
Author: Michal 'vorner' Vaner <michal.vaner at nic.cz>
Date: Fri Sep 9 12:07:52 2011 +0200
[1177] Pass reversed name to the Accessor
commit f8720ba467d8e107c512160a5502caf9be58a425
Author: Michal 'vorner' Vaner <michal.vaner at nic.cz>
Date: Fri Sep 9 10:54:34 2011 +0200
[1177] Test for the SQLite3 find previous
commit 38af8a4225e8c82564758e8a5629da438220bc87
Author: Michal 'vorner' Vaner <michal.vaner at nic.cz>
Date: Thu Sep 8 15:34:21 2011 +0200
[1177] The findPreviousName
But only if the underlying DB supports it. The SQLite3 one needs to be
still implemented.
commit c5e0db2b7d8fbdb13548e01310f623f131ea0e9c
Author: Michal 'vorner' Vaner <michal.vaner at nic.cz>
Date: Thu Sep 8 14:40:41 2011 +0200
[1177] Test for the previous name in DB
commit 26c7bfe851f00422beb442a77d25cc0887557b79
Author: Michal 'vorner' Vaner <michal.vaner at nic.cz>
Date: Thu Sep 8 12:50:53 2011 +0200
[1177] Function definiton for finding previous name
It will be needed for DNSSEC logic. Not implemented nor tested:
* The InMemory just throws NotImplemented
* The Database one will be done in next commits
commit f5239632a06383f2b4f6825cb6a006ceb8bea417
Author: Michal 'vorner' Vaner <michal.vaner at nic.cz>
Date: Thu Sep 8 11:57:44 2011 +0200
[1177] Implement result statuses for DNSSEC
commit 680f05c35753bf1f70392d25b1e6310cf46476ce
Author: Michal 'vorner' Vaner <michal.vaner at nic.cz>
Date: Thu Sep 8 11:42:20 2011 +0200
[1177] Tests for wildcard result codes
commit b12351c21ee92a13536aa89331cc73bd166dbe5f
Author: Michal 'vorner' Vaner <michal.vaner at nic.cz>
Date: Thu Sep 8 11:30:13 2011 +0200
[1177] New Result statuses
To distinguish wildcard results, because then the logic will have to
query for another NSEC
commit 2e1dceedf6a4f661a8d7e57757b28f9f6cb1a9b3
Author: Michal 'vorner' Vaner <michal.vaner at nic.cz>
Date: Thu Sep 8 10:59:13 2011 +0200
[1177] NSEC in case of normal NXRRSET
commit df69ad0d0231218610f68ecb2b1953ae7f28fa68
Author: Michal 'vorner' Vaner <michal.vaner at nic.cz>
Date: Wed Sep 7 15:02:07 2011 +0200
[1177] Tests for NXRRSET matches
Normal one and wildcard one. Empty-nonterminal is not included here.
commit 5b713ea8e5fd35fdb1ab7ff953e010ef9b60f98c
Author: Michal 'vorner' Vaner <michal.vaner at nic.cz>
Date: Tue Sep 6 14:17:08 2011 +0200
[1177] Refactor getRRset
That one was getting really hairy and it wouldn't serve it's purpose in
the following work.
Currently it takes set of types we're interested in and returns all of
them, the logic to decide which one to take is left to the caller. This
simplifies the method and doesn't hurt the rest much, as the rest needed
to know what to ask for anyway.
Also, it allows getting NSEC or NSEC3 records in the same database
search almost for free.
-----------------------------------------------------------------------
Summary of changes:
configure.ac | 7 +
doc/Doxyfile | 4 +-
src/bin/auth/tests/query_unittest.cc | 19 +-
src/bin/xfrin/b10-xfrin.8 | 6 +-
src/bin/xfrin/b10-xfrin.xml | 4 +-
src/lib/datasrc/client.h | 63 +-
src/lib/datasrc/database.cc | 235 ++-
src/lib/datasrc/database.h | 329 +++-
src/lib/datasrc/datasrc_messages.mes | 35 +
src/lib/datasrc/memory_datasrc.cc | 6 +
src/lib/datasrc/memory_datasrc.h | 11 +
src/lib/datasrc/sqlite3_accessor.cc | 555 ++++--
src/lib/datasrc/sqlite3_accessor.h | 60 +-
src/lib/datasrc/tests/Makefile.am | 8 +-
src/lib/datasrc/tests/client_unittest.cc | 3 +
src/lib/datasrc/tests/database_unittest.cc | 2263 ++++++++++++++------
src/lib/datasrc/tests/memory_datasrc_unittest.cc | 6 +-
src/lib/datasrc/tests/sqlite3_accessor_unittest.cc | 446 ++++-
src/lib/datasrc/tests/testdata/Makefile.am | 6 +
.../{example2.com.sqlite3 => rwtest.sqlite3} | Bin 11264 -> 11264 bytes
src/lib/datasrc/zone.h | 276 +++-
src/lib/dns/rdata/generic/rrsig_46.cc | 2 +-
src/lib/dns/rdata/generic/rrsig_46.h | 2 +-
23 files changed, 3286 insertions(+), 1060 deletions(-)
create mode 100644 src/lib/datasrc/tests/testdata/Makefile.am
copy src/lib/datasrc/tests/testdata/{example2.com.sqlite3 => rwtest.sqlite3} (67%)
-----------------------------------------------------------------------
diff --git a/configure.ac b/configure.ac
index 6e129b6..6ae71bf 100644
--- a/configure.ac
+++ b/configure.ac
@@ -12,6 +12,12 @@ AC_PROG_CXX
# Libtool configuration
#
+
+# libtool cannot handle spaces in paths, so exit early if there is one
+if [ test `echo $PWD | grep -c ' '` != "0" ]; then
+ AC_MSG_ERROR([BIND 10 cannot be built in a directory that contains spaces, because of libtool limitations. Please change the directory name, or use a symbolic link that does not contain spaces.])
+fi
+
# On FreeBSD (and probably some others), clang++ does not meet an autoconf
# assumption in identifying libtool configuration regarding shared library:
# the configure script will execute "$CC -shared $CFLAGS/$CXXFLAGS -v" and
@@ -856,6 +862,7 @@ AC_CONFIG_FILES([Makefile
src/lib/exceptions/tests/Makefile
src/lib/datasrc/Makefile
src/lib/datasrc/tests/Makefile
+ src/lib/datasrc/tests/testdata/Makefile
src/lib/xfr/Makefile
src/lib/log/Makefile
src/lib/log/compiler/Makefile
diff --git a/doc/Doxyfile b/doc/Doxyfile
index ceb806f..71b0738 100644
--- a/doc/Doxyfile
+++ b/doc/Doxyfile
@@ -568,8 +568,8 @@ WARN_LOGFILE =
# directories like "/usr/src/myproject". Separate the files or directories
# with spaces.
-INPUT = ../src/lib/cc ../src/lib/config \
- ../src/lib/cryptolink ../src/lib/dns ../src/lib/datasrc \
+INPUT = ../src/lib/exceptions ../src/lib/cc \
+ ../src/lib/config ../src/lib/cryptolink ../src/lib/dns ../src/lib/datasrc \
../src/bin/auth ../src/bin/resolver ../src/lib/bench ../src/lib/log \
../src/lib/log/compiler ../src/lib/asiolink/ ../src/lib/nsas \
../src/lib/testutils ../src/lib/cache ../src/lib/server_common/ \
diff --git a/src/bin/auth/tests/query_unittest.cc b/src/bin/auth/tests/query_unittest.cc
index d65cc10..4b8f013 100644
--- a/src/bin/auth/tests/query_unittest.cc
+++ b/src/bin/auth/tests/query_unittest.cc
@@ -111,7 +111,8 @@ public:
dname_name_("dname.example.com"),
has_SOA_(true),
has_apex_NS_(true),
- rrclass_(RRClass::IN())
+ rrclass_(RRClass::IN()),
+ include_rrsig_anyway_(false)
{
stringstream zone_stream;
zone_stream << soa_txt << zone_ns_txt << ns_addrs_txt <<
@@ -137,6 +138,9 @@ public:
// the apex NS.
void setApexNSFlag(bool on) { has_apex_NS_ = on; }
+ // Turn this on if you want it to return RRSIGs regardless of FIND_GLUE_OK
+ void setIncludeRRSIGAnyway(bool on) { include_rrsig_anyway_ = on; }
+
private:
typedef map<RRType, ConstRRsetPtr> RRsetStore;
typedef map<Name, RRsetStore> Domains;
@@ -181,6 +185,7 @@ private:
ConstRRsetPtr delegation_rrset_;
ConstRRsetPtr dname_rrset_;
const RRClass rrclass_;
+ bool include_rrsig_anyway_;
};
ZoneFinder::FindResult
@@ -219,6 +224,7 @@ MockZoneFinder::find(const Name& name, const RRType& type,
// Strip whatever signature there is in case DNSSEC is not required
// Just to make sure the Query asks for it when it is needed
if (options & ZoneFinder::FIND_DNSSEC ||
+ include_rrsig_anyway_ ||
!found_rrset->second->getRRsig()) {
rrset = found_rrset->second;
} else {
@@ -342,6 +348,17 @@ TEST_F(QueryTest, exactMatch) {
www_a_txt, zone_ns_txt, ns_addrs_txt);
}
+TEST_F(QueryTest, exactMatchIgnoreSIG) {
+ // Check that we do not include the RRSIG when not requested even when
+ // we receive it from the data source.
+ mock_finder->setIncludeRRSIGAnyway(true);
+ Query query(memory_client, qname, qtype, response);
+ EXPECT_NO_THROW(query.process());
+ // find match rrset
+ responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
+ www_a_txt, zone_ns_txt, ns_addrs_txt);
+}
+
TEST_F(QueryTest, dnssecPositive) {
// Just like exactMatch, but the signatures should be included as well
Query query(memory_client, qname, qtype, response, true);
diff --git a/src/bin/xfrin/b10-xfrin.8 b/src/bin/xfrin/b10-xfrin.8
index 7f73213..54dbe7c 100644
--- a/src/bin/xfrin/b10-xfrin.8
+++ b/src/bin/xfrin/b10-xfrin.8
@@ -2,12 +2,12 @@
.\" Title: b10-xfrin
.\" Author: [FIXME: author] [see http://docbook.sf.net/el/author]
.\" Generator: DocBook XSL Stylesheets v1.75.2 <http://docbook.sf.net/>
-.\" Date: May 19, 2011
+.\" Date: September 8, 2011
.\" Manual: BIND10
.\" Source: BIND10
.\" Language: English
.\"
-.TH "B10\-XFRIN" "8" "May 19, 2011" "BIND10" "BIND10"
+.TH "B10\-XFRIN" "8" "September 8, 2011" "BIND10" "BIND10"
.\" -----------------------------------------------------------------
.\" * set default formatting
.\" -----------------------------------------------------------------
@@ -61,7 +61,7 @@ receives its configurations from
.PP
The configurable settings are:
.PP
-\fItransfers\-in\fR
+\fItransfers_in\fR
defines the maximum number of inbound zone transfers that can run concurrently\&. The default is 10\&.
.PP
diff --git a/src/bin/xfrin/b10-xfrin.xml b/src/bin/xfrin/b10-xfrin.xml
index 17840fe..d45e15f 100644
--- a/src/bin/xfrin/b10-xfrin.xml
+++ b/src/bin/xfrin/b10-xfrin.xml
@@ -20,7 +20,7 @@
<refentry>
<refentryinfo>
- <date>May 19, 2011</date>
+ <date>September 8, 2011</date>
</refentryinfo>
<refmeta>
@@ -92,7 +92,7 @@ in separate zonemgr process.
The configurable settings are:
</para>
- <para><varname>transfers-in</varname>
+ <para><varname>transfers_in</varname>
defines the maximum number of inbound zone transfers
that can run concurrently. The default is 10.
</para>
diff --git a/src/lib/datasrc/client.h b/src/lib/datasrc/client.h
index c43092d..6a7ae04 100644
--- a/src/lib/datasrc/client.h
+++ b/src/lib/datasrc/client.h
@@ -80,8 +80,8 @@ typedef boost::shared_ptr<ZoneIterator> ZoneIteratorPtr;
/// disruption with a naive copy it's prohibited explicitly. For the expected
/// usage of the client classes the restriction should be acceptable.
///
-/// \todo This class is not complete. It needs more factory methods, for
-/// accessing the whole zone, updating it, loading it, etc.
+/// \todo This class is still not complete. It will need more factory methods,
+/// e.g. for (re)loading a zone.
class DataSourceClient : boost::noncopyable {
public:
/// \brief A helper structure to represent the search result of
@@ -180,6 +180,65 @@ public:
isc_throw(isc::NotImplemented,
"Data source doesn't support iteration");
}
+
+ /// Return an updater to make updates to a specific zone.
+ ///
+ /// The RR class of the zone is the one that the client is expected to
+ /// handle (see the detailed description of this class).
+ ///
+ /// If the specified zone is not found via the client, a NULL pointer
+ /// will be returned; in other words a completely new zone cannot be
+ /// created using an updater. It must be created beforehand (even if
+ /// it's an empty placeholder) in a way specific to the underlying data
+ /// source.
+ ///
+ /// Conceptually, the updater will trigger a separate transaction for
+ /// subsequent updates to the zone within the context of the updater
+ /// (the actual implementation of the "transaction" may vary for the
+ /// specific underlying data source). Until \c commit() is performed
+ /// on the updater, the intermediate updates won't affect the results
+ /// of other methods (and the result of the object's methods created
+ /// by other factory methods). Likewise, if the updater is destructed
+ /// without performing \c commit(), the intermediate updates will be
+ /// effectively canceled and will never affect other methods.
+ ///
+ /// If the underlying data source allows concurrent updates, this method
+ /// can be called multiple times while the previously returned updater(s)
+ /// are still active. In this case each updater triggers a different
+ /// "transaction". Normally it would be for different zones for such a
+ /// case as handling multiple incoming AXFR streams concurrently, but
+ /// this interface does not even prohibit an attempt of getting more than
+ /// one updater for the same zone, as long as the underlying data source
+ /// allows such an operation (and any conflict resolution is left to the
+ /// specific derived class implementation).
+ ///
+ /// If \c replace is true, any existing RRs of the zone will be
+ /// deleted on successful completion of updates (after \c commit() on
+ /// the updater); if it's false, the existing RRs will be
+ /// intact unless explicitly deleted by \c deleteRRset() on the updater.
+ ///
+ /// A data source can be "read only" or can prohibit partial updates.
+ /// In such cases this method will result in an \c isc::NotImplemented
+ /// exception unconditionally or when \c replace is false).
+ ///
+ /// \note To avoid throwing the exception accidentally with a lazy
+ /// implementation, we still keep this method pure virtual without
+ /// an implementation. All derived classes must explicitly define this
+ /// method, even if it simply throws the NotImplemented exception.
+ ///
+ /// \exception NotImplemented The underlying data source does not support
+ /// updates.
+ /// \exception DataSourceError Internal error in the underlying data
+ /// source.
+ /// \exception std::bad_alloc Resource allocation failure.
+ ///
+ /// \param name The zone name to be updated
+ /// \param replace Whether to delete existing RRs before making updates
+ ///
+ /// \return A pointer to the updater; it will be NULL if the specified
+ /// zone isn't found.
+ virtual ZoneUpdaterPtr getUpdater(const isc::dns::Name& name,
+ bool replace) const = 0;
};
}
}
diff --git a/src/lib/datasrc/database.cc b/src/lib/datasrc/database.cc
index 2c021c1..f82e641 100644
--- a/src/lib/datasrc/database.cc
+++ b/src/lib/datasrc/database.cc
@@ -12,6 +12,7 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
+#include <string>
#include <vector>
#include <datasrc/database.h>
@@ -21,6 +22,8 @@
#include <exceptions/exceptions.h>
#include <dns/name.h>
#include <dns/rrclass.h>
+#include <dns/rrttl.h>
+#include <dns/rrset.h>
#include <dns/rdata.h>
#include <dns/rdataclass.h>
@@ -29,19 +32,20 @@
#include <boost/foreach.hpp>
-#include <string>
-
using namespace isc::dns;
-using std::string;
+using namespace std;
+using boost::shared_ptr;
+using namespace isc::dns::rdata;
namespace isc {
namespace datasrc {
-DatabaseClient::DatabaseClient(boost::shared_ptr<DatabaseAccessor>
- database) :
- database_(database)
+DatabaseClient::DatabaseClient(RRClass rrclass,
+ boost::shared_ptr<DatabaseAccessor>
+ accessor) :
+ rrclass_(rrclass), accessor_(accessor)
{
- if (database_.get() == NULL) {
+ if (!accessor_) {
isc_throw(isc::InvalidParameter,
"No database provided to DatabaseClient");
}
@@ -49,21 +53,21 @@ DatabaseClient::DatabaseClient(boost::shared_ptr<DatabaseAccessor>
DataSourceClient::FindResult
DatabaseClient::findZone(const Name& name) const {
- std::pair<bool, int> zone(database_->getZone(name.toText()));
+ std::pair<bool, int> zone(accessor_->getZone(name.toText()));
// Try exact first
if (zone.first) {
return (FindResult(result::SUCCESS,
- ZoneFinderPtr(new Finder(database_,
+ ZoneFinderPtr(new Finder(accessor_,
zone.second, name))));
}
// Then super domains
// Start from 1, as 0 is covered above
for (size_t i(1); i < name.getLabelCount(); ++i) {
isc::dns::Name superdomain(name.split(i));
- zone = database_->getZone(superdomain.toText());
+ zone = accessor_->getZone(superdomain.toText());
if (zone.first) {
return (FindResult(result::PARTIALMATCH,
- ZoneFinderPtr(new Finder(database_,
+ ZoneFinderPtr(new Finder(accessor_,
zone.second,
superdomain))));
}
@@ -72,10 +76,9 @@ DatabaseClient::findZone(const Name& name) const {
return (FindResult(result::NOTFOUND, ZoneFinderPtr()));
}
-DatabaseClient::Finder::Finder(boost::shared_ptr<DatabaseAccessor>
- database, int zone_id,
- const isc::dns::Name& origin) :
- database_(database),
+DatabaseClient::Finder::Finder(boost::shared_ptr<DatabaseAccessor> accessor,
+ int zone_id, const isc::dns::Name& origin) :
+ accessor_(accessor),
zone_id_(zone_id),
origin_(origin)
{ }
@@ -181,7 +184,7 @@ DatabaseClient::Finder::getRRsets(const Name& name, const WantedTypes& types,
// Request the context
DatabaseAccessor::IteratorContextPtr
- context(database_->getRecords(name.toText(), zone_id_));
+ context(accessor_->getRecords(name.toText(), zone_id_));
// It must not return NULL, that's a bug of the implementation
if (!context) {
isc_throw(isc::Unexpected, "Iterator context null at " +
@@ -233,7 +236,7 @@ DatabaseClient::Finder::getRRsets(const Name& name, const WantedTypes& types,
addOrCreate(result[cur_type], *construct_name, getClass(),
cur_type, cur_ttl,
columns[DatabaseAccessor::RDATA_COLUMN],
- *database_);
+ *accessor_);
}
if (cur_type == RRType::CNAME()) {
@@ -279,7 +282,7 @@ bool
DatabaseClient::Finder::hasSubdomains(const std::string& name) {
// Request the context
DatabaseAccessor::IteratorContextPtr
- context(database_->getRecords(name, zone_id_, true));
+ context(accessor_->getRecords(name, zone_id_, true));
// It must not return NULL, that's a bug of the implementation
if (!context) {
isc_throw(isc::Unexpected, "Iterator context null at " + name);
@@ -319,7 +322,7 @@ DatabaseClient::Finder::find(const isc::dns::Name& name,
ZoneFinder::Result result_status = SUCCESS;
FoundRRsets found;
logger.debug(DBG_TRACE_DETAILED, DATASRC_DATABASE_FIND_RECORDS)
- .arg(database_->getDBName()).arg(name).arg(type);
+ .arg(accessor_->getDBName()).arg(name).arg(type);
// In case we are in GLUE_OK mode and start matching wildcards,
// we can't do it under NS, so we store it here to check
isc::dns::RRsetPtr first_ns;
@@ -360,7 +363,7 @@ DatabaseClient::Finder::find(const isc::dns::Name& name,
// delegation in apex
LOG_DEBUG(logger, DBG_TRACE_DETAILED,
DATASRC_DATABASE_FOUND_DELEGATION).
- arg(database_->getDBName()).arg(superdomain);
+ arg(accessor_->getDBName()).arg(superdomain);
result_rrset = nsi->second;
result_status = DELEGATION;
// No need to go lower, found
@@ -369,7 +372,7 @@ DatabaseClient::Finder::find(const isc::dns::Name& name,
// Very similar with DNAME
LOG_DEBUG(logger, DBG_TRACE_DETAILED,
DATASRC_DATABASE_FOUND_DNAME).
- arg(database_->getDBName()).arg(superdomain);
+ arg(accessor_->getDBName()).arg(superdomain);
result_rrset = dni->second;
result_status = DNAME;
if (result_rrset->getRdataCount() != 1) {
@@ -400,7 +403,7 @@ DatabaseClient::Finder::find(const isc::dns::Name& name,
// There's a delegation at the exact node.
LOG_DEBUG(logger, DBG_TRACE_DETAILED,
DATASRC_DATABASE_FOUND_DELEGATION_EXACT).
- arg(database_->getDBName()).arg(name);
+ arg(accessor_->getDBName()).arg(name);
result_status = DELEGATION;
result_rrset = nsi->second;
} else if (type != isc::dns::RRType::CNAME() &&
@@ -423,7 +426,7 @@ DatabaseClient::Finder::find(const isc::dns::Name& name,
if (hasSubdomains(name.toText())) {
LOG_DEBUG(logger, DBG_TRACE_DETAILED,
DATASRC_DATABASE_FOUND_EMPTY_NONTERMINAL).
- arg(database_->getDBName()).arg(name);
+ arg(accessor_->getDBName()).arg(name);
records_found = true;
} else {
// It's not empty non-terminal. So check for wildcards.
@@ -457,7 +460,7 @@ DatabaseClient::Finder::find(const isc::dns::Name& name,
glue_ok = false;
LOG_DEBUG(logger, DBG_TRACE_DETAILED,
DATASRC_DATABASE_WILDCARD_CANCEL_NS).
- arg(database_->getDBName()).arg(wildcard).
+ arg(accessor_->getDBName()).arg(wildcard).
arg(first_ns->getName());
} else if (!hasSubdomains(name.split(i - 1).toText()))
{
@@ -504,12 +507,12 @@ DatabaseClient::Finder::find(const isc::dns::Name& name,
LOG_DEBUG(logger, DBG_TRACE_DETAILED,
DATASRC_DATABASE_WILDCARD).
- arg(database_->getDBName()).arg(wildcard).
+ arg(accessor_->getDBName()).arg(wildcard).
arg(name);
} else {
LOG_DEBUG(logger, DBG_TRACE_DETAILED,
DATASRC_DATABASE_WILDCARD_CANCEL_SUB).
- arg(database_->getDBName()).arg(wildcard).
+ arg(accessor_->getDBName()).arg(wildcard).
arg(name).arg(superdomain);
}
break;
@@ -518,7 +521,7 @@ DatabaseClient::Finder::find(const isc::dns::Name& name,
records_found = true;
LOG_DEBUG(logger, DBG_TRACE_DETAILED,
DATASRC_DATABASE_WILDCARD_EMPTY).
- arg(database_->getDBName()).arg(wildcard).
+ arg(accessor_->getDBName()).arg(wildcard).
arg(name);
break;
}
@@ -542,13 +545,13 @@ DatabaseClient::Finder::find(const isc::dns::Name& name,
if (records_found) {
logger.debug(DBG_TRACE_DETAILED,
DATASRC_DATABASE_FOUND_NXRRSET)
- .arg(database_->getDBName()).arg(name)
+ .arg(accessor_->getDBName()).arg(name)
.arg(getClass()).arg(type);
result_status = NXRRSET;
} else {
logger.debug(DBG_TRACE_DETAILED,
DATASRC_DATABASE_FOUND_NXDOMAIN)
- .arg(database_->getDBName()).arg(name)
+ .arg(accessor_->getDBName()).arg(name)
.arg(getClass()).arg(type);
result_status = NXDOMAIN;
}
@@ -556,14 +559,15 @@ DatabaseClient::Finder::find(const isc::dns::Name& name,
} else {
logger.debug(DBG_TRACE_DETAILED,
DATASRC_DATABASE_FOUND_RRSET)
- .arg(database_->getDBName()).arg(*result_rrset);
+ .arg(accessor_->getDBName()).arg(*result_rrset);
}
return (FindResult(result_status, result_rrset));
}
Name
DatabaseClient::Finder::findPreviousName(const Name& name) const {
- return (Name(database_->findPreviousName(zone_id_, name.toText())));
+ return (Name(accessor_->findPreviousName(zone_id_,
+ name.reverse().toText())));
}
Name
@@ -656,7 +660,7 @@ private:
ZoneIteratorPtr
DatabaseClient::getIterator(const isc::dns::Name& name) const {
// Get the zone
- std::pair<bool, int> zone(database_->getZone(name.toText()));
+ std::pair<bool, int> zone(accessor_->getZone(name.toText()));
if (!zone.first) {
// No such zone, can't continue
isc_throw(DataSourceError, "Zone " + name.toText() +
@@ -665,7 +669,7 @@ DatabaseClient::getIterator(const isc::dns::Name& name) const {
}
// Request the context
DatabaseAccessor::IteratorContextPtr
- context(database_->getAllRecords(zone.second));
+ context(accessor_->getAllRecords(zone.second));
// It must not return NULL, that's a bug of the implementation
if (context == DatabaseAccessor::IteratorContextPtr()) {
isc_throw(isc::Unexpected, "Iterator context null at " +
@@ -681,5 +685,170 @@ DatabaseClient::getIterator(const isc::dns::Name& name) const {
return (ZoneIteratorPtr(new DatabaseIterator(context, RRClass::IN())));
}
+//
+// Zone updater using some database system as the underlying data source.
+//
+class DatabaseUpdater : public ZoneUpdater {
+public:
+ DatabaseUpdater(shared_ptr<DatabaseAccessor> accessor, int zone_id,
+ const Name& zone_name, const RRClass& zone_class) :
+ committed_(false), accessor_(accessor), zone_id_(zone_id),
+ db_name_(accessor->getDBName()), zone_name_(zone_name.toText()),
+ zone_class_(zone_class),
+ finder_(new DatabaseClient::Finder(accessor_, zone_id_, zone_name))
+ {
+ logger.debug(DBG_TRACE_DATA, DATASRC_DATABASE_UPDATER_CREATED)
+ .arg(zone_name_).arg(zone_class_).arg(db_name_);
+ }
+
+ virtual ~DatabaseUpdater() {
+ if (!committed_) {
+ try {
+ accessor_->rollbackUpdateZone();
+ logger.info(DATASRC_DATABASE_UPDATER_ROLLBACK)
+ .arg(zone_name_).arg(zone_class_).arg(db_name_);
+ } catch (const DataSourceError& e) {
+ // We generally expect that rollback always succeeds, and
+ // it should in fact succeed in a way we execute it. But
+ // as the public API allows rollbackUpdateZone() to fail and
+ // throw, we should expect it. Obviously we cannot re-throw
+ // it. The best we can do is to log it as a critical error.
+ logger.error(DATASRC_DATABASE_UPDATER_ROLLBACKFAIL)
+ .arg(zone_name_).arg(zone_class_).arg(db_name_)
+ .arg(e.what());
+ }
+ }
+
+ logger.debug(DBG_TRACE_DATA, DATASRC_DATABASE_UPDATER_DESTROYED)
+ .arg(zone_name_).arg(zone_class_).arg(db_name_);
+ }
+
+ virtual ZoneFinder& getFinder() { return (*finder_); }
+
+ virtual void addRRset(const RRset& rrset);
+ virtual void deleteRRset(const RRset& rrset);
+ virtual void commit();
+
+private:
+ bool committed_;
+ shared_ptr<DatabaseAccessor> accessor_;
+ const int zone_id_;
+ const string db_name_;
+ const string zone_name_;
+ const RRClass zone_class_;
+ boost::scoped_ptr<DatabaseClient::Finder> finder_;
+};
+
+void
+DatabaseUpdater::addRRset(const RRset& rrset) {
+ if (committed_) {
+ isc_throw(DataSourceError, "Add attempt after commit to zone: "
+ << zone_name_ << "/" << zone_class_);
+ }
+ if (rrset.getClass() != zone_class_) {
+ isc_throw(DataSourceError, "An RRset of a different class is being "
+ << "added to " << zone_name_ << "/" << zone_class_ << ": "
+ << rrset.toText());
+ }
+ if (rrset.getRRsig()) {
+ isc_throw(DataSourceError, "An RRset with RRSIG is being added to "
+ << zone_name_ << "/" << zone_class_ << ": "
+ << rrset.toText());
+ }
+
+ RdataIteratorPtr it = rrset.getRdataIterator();
+ if (it->isLast()) {
+ isc_throw(DataSourceError, "An empty RRset is being added for "
+ << rrset.getName() << "/" << zone_class_ << "/"
+ << rrset.getType());
+ }
+
+ string columns[DatabaseAccessor::ADD_COLUMN_COUNT]; // initialized with ""
+ columns[DatabaseAccessor::ADD_NAME] = rrset.getName().toText();
+ columns[DatabaseAccessor::ADD_REV_NAME] =
+ rrset.getName().reverse().toText();
+ columns[DatabaseAccessor::ADD_TTL] = rrset.getTTL().toText();
+ columns[DatabaseAccessor::ADD_TYPE] = rrset.getType().toText();
+ for (; !it->isLast(); it->next()) {
+ if (rrset.getType() == RRType::RRSIG()) {
+ // XXX: the current interface (based on the current sqlite3
+ // data source schema) requires a separate "sigtype" column,
+ // even though it won't be used in a newer implementation.
+ // We should eventually clean up the schema design and simplify
+ // the interface, but until then we have to conform to the schema.
+ const generic::RRSIG& rrsig_rdata =
+ dynamic_cast<const generic::RRSIG&>(it->getCurrent());
+ columns[DatabaseAccessor::ADD_SIGTYPE] =
+ rrsig_rdata.typeCovered().toText();
+ }
+ columns[DatabaseAccessor::ADD_RDATA] = it->getCurrent().toText();
+ accessor_->addRecordToZone(columns);
+ }
+}
+
+void
+DatabaseUpdater::deleteRRset(const RRset& rrset) {
+ if (committed_) {
+ isc_throw(DataSourceError, "Delete attempt after commit on zone: "
+ << zone_name_ << "/" << zone_class_);
+ }
+ if (rrset.getClass() != zone_class_) {
+ isc_throw(DataSourceError, "An RRset of a different class is being "
+ << "deleted from " << zone_name_ << "/" << zone_class_
+ << ": " << rrset.toText());
+ }
+ if (rrset.getRRsig()) {
+ isc_throw(DataSourceError, "An RRset with RRSIG is being deleted from "
+ << zone_name_ << "/" << zone_class_ << ": "
+ << rrset.toText());
+ }
+
+ RdataIteratorPtr it = rrset.getRdataIterator();
+ if (it->isLast()) {
+ isc_throw(DataSourceError, "An empty RRset is being deleted for "
+ << rrset.getName() << "/" << zone_class_ << "/"
+ << rrset.getType());
+ }
+
+ string params[DatabaseAccessor::DEL_PARAM_COUNT]; // initialized with ""
+ params[DatabaseAccessor::DEL_NAME] = rrset.getName().toText();
+ params[DatabaseAccessor::DEL_TYPE] = rrset.getType().toText();
+ for (; !it->isLast(); it->next()) {
+ params[DatabaseAccessor::DEL_RDATA] = it->getCurrent().toText();
+ accessor_->deleteRecordInZone(params);
+ }
+}
+
+void
+DatabaseUpdater::commit() {
+ if (committed_) {
+ isc_throw(DataSourceError, "Duplicate commit attempt for "
+ << zone_name_ << "/" << zone_class_ << " on "
+ << db_name_);
+ }
+ accessor_->commitUpdateZone();
+ committed_ = true; // make sure the destructor won't trigger rollback
+
+ // We release the accessor immediately after commit is completed so that
+ // we don't hold the possible internal resource any longer.
+ accessor_.reset();
+
+ logger.debug(DBG_TRACE_DATA, DATASRC_DATABASE_UPDATER_COMMIT)
+ .arg(zone_name_).arg(zone_class_).arg(db_name_);
+}
+
+// The updater factory
+ZoneUpdaterPtr
+DatabaseClient::getUpdater(const isc::dns::Name& name, bool replace) const {
+ shared_ptr<DatabaseAccessor> update_accessor(accessor_->clone());
+ const std::pair<bool, int> zone(update_accessor->startUpdateZone(
+ name.toText(), replace));
+ if (!zone.first) {
+ return (ZoneUpdaterPtr());
+ }
+
+ return (ZoneUpdaterPtr(new DatabaseUpdater(update_accessor, zone.second,
+ name, rrclass_)));
+}
}
}
diff --git a/src/lib/datasrc/database.h b/src/lib/datasrc/database.h
index c6c435b..c9c2bc5 100644
--- a/src/lib/datasrc/database.h
+++ b/src/lib/datasrc/database.h
@@ -15,6 +15,14 @@
#ifndef __DATABASE_DATASRC_H
#define __DATABASE_DATASRC_H
+#include <string>
+
+#include <boost/scoped_ptr.hpp>
+
+#include <dns/rrclass.h>
+#include <dns/rrclass.h>
+#include <dns/rrset.h>
+
#include <datasrc/client.h>
#include <dns/name.h>
@@ -74,12 +82,45 @@ public:
};
/**
+ * Definitions of the fields to be passed to addRecordToZone().
+ *
+ * Each derived implementation of addRecordToZone() should expect
+ * the "columns" vector to be filled with the values as described in this
+ * enumeration, in this order.
+ */
+ enum AddRecordColumns {
+ ADD_NAME = 0, ///< The owner name of the record (a domain name)
+ ADD_REV_NAME = 1, ///< Reversed name of NAME (used for DNSSEC)
+ ADD_TTL = 2, ///< The TTL of the record (in numeric form)
+ ADD_TYPE = 3, ///< The RRType of the record (A/NS/TXT etc.)
+ ADD_SIGTYPE = 4, ///< For RRSIG records, this contains the RRTYPE
+ ///< the RRSIG covers.
+ ADD_RDATA = 5, ///< Full text representation of the record's RDATA
+ ADD_COLUMN_COUNT = 6 ///< Number of columns
+ };
+
+ /**
+ * Definitions of the fields to be passed to deleteRecordInZone().
+ *
+ * Each derived implementation of deleteRecordInZone() should expect
+ * the "params" vector to be filled with the values as described in this
+ * enumeration, in this order.
+ */
+ enum DeleteRecordParams {
+ DEL_NAME = 0, ///< The owner name of the record (a domain name)
+ DEL_TYPE = 1, ///< The RRType of the record (A/NS/TXT etc.)
+ DEL_RDATA = 2, ///< Full text representation of the record's RDATA
+ DEL_PARAM_COUNT = 3 ///< Number of parameters
+ };
+
+ /**
* \brief Destructor
*
* It is empty, but needs a virtual one, since we will use the derived
* classes in polymorphic way.
*/
virtual ~DatabaseAccessor() { }
+
/**
* \brief Retrieve a zone identifier
*
@@ -208,6 +249,218 @@ public:
*/
virtual IteratorContextPtr getAllRecords(int id) const = 0;
+ /// Start a transaction for updating a zone.
+ ///
+ /// Each derived class version of this method starts a database
+ /// transaction to make updates to the given name of zone (whose class was
+ /// specified at the construction of the class).
+ ///
+ /// If \c replace is true, any existing records of the zone will be
+ /// deleted on successful completion of updates (after
+ /// \c commitUpdateZone()); if it's false, the existing records will be
+ /// intact unless explicitly deleted by \c deleteRecordInZone().
+ ///
+ /// A single \c DatabaseAccessor instance can perform at most one update
+ /// transaction; a duplicate call to this method before
+ /// \c commitUpdateZone() or \c rollbackUpdateZone() will result in
+ /// a \c DataSourceError exception. If multiple update attempts need
+ /// to be performed concurrently (and if the underlying database allows
+ /// such operation), separate \c DatabaseAccessor instance must be
+ /// created.
+ ///
+ /// \note The underlying database may not allow concurrent updates to
+ /// the same database instance even if different "connections" (or
+ /// something similar specific to the database implementation) are used
+ /// for different sets of updates. For example, it doesn't seem to be
+ /// possible for SQLite3 unless different databases are used. MySQL
+ /// allows concurrent updates to different tables of the same database,
+ /// but a specific operation may block others. As such, this interface
+ /// doesn't require derived classes to allow concurrent updates with
+ /// multiple \c DatabaseAccessor instances; however, the implementation
+ /// is encouraged to do the best for making it more likely to succeed
+ /// as long as the underlying database system allows concurrent updates.
+ ///
+ /// This method returns a pair of \c bool and \c int. Its first element
+ /// indicates whether the given name of zone is found. If it's false,
+ /// the transaction isn't considered to be started; a subsequent call to
+ /// this method with an existing zone name should succeed. Likewise,
+ /// if a call to this method results in an exception, the transaction
+ /// isn't considered to be started. Note also that if the zone is not
+ /// found this method doesn't try to create a new one in the database.
+ /// It must have been created by some other means beforehand.
+ ///
+ /// The second element is the internal zone ID used for subsequent
+ /// updates. Depending on implementation details of the actual derived
+ /// class method, it may be different from the one returned by
+ /// \c getZone(); for example, a specific implementation may use a
+ /// completely new zone ID when \c replace is true.
+ ///
+ /// \exception DataSourceError Duplicate call to this method, or some
+ /// internal database related error.
+ ///
+ /// \param zone_name A string representation of the zone name to be updated
+ /// \param replace Whether to replace the entire zone (see above)
+ ///
+ /// \return A pair of bool and int, indicating whether the specified zone
+ /// exists and (if so) the zone ID to be used for the update, respectively.
+ virtual std::pair<bool, int> startUpdateZone(const std::string& zone_name,
+ bool replace) = 0;
+
+ /// Add a single record to the zone to be updated.
+ ///
+ /// This method provides a simple interface to insert a new record
+ /// (a database "row") to the zone in the update context started by
+ /// \c startUpdateZone(). The zone to which the record to be added
+ /// is the one specified at the time of the \c startUpdateZone() call.
+ ///
+ /// A successful call to \c startUpdateZone() must have preceded to
+ /// this call; otherwise a \c DataSourceError exception will be thrown.
+ ///
+ /// The row is defined as a vector of strings that has exactly
+ /// ADD_COLUMN_COUNT number of elements. See AddRecordColumns for
+ /// the semantics of each element.
+ ///
+ /// Derived class methods are not required to check whether the given
+ /// values in \c columns are valid in terms of the expected semantics;
+ /// in general, it's the caller's responsibility.
+ /// For example, TTLs would normally be expected to be a textual
+ /// representation of decimal numbers, but this interface doesn't require
+ /// the implementation to perform this level of validation. It may check
+ /// the values, however, and in that case if it detects an error it
+ /// should throw a \c DataSourceError exception.
+ ///
+ /// Likewise, derived class methods are not required to detect any
+ /// duplicate record that is already in the zone.
+ ///
+ /// \note The underlying database schema may not have a trivial mapping
+ /// from this style of definition of rows to actual database records.
+ /// It's the implementation's responsibility to implement the mapping
+ /// in the actual derived method.
+ ///
+ /// \exception DataSourceError Invalid call without starting a transaction,
+ /// or other internal database error.
+ ///
+ /// \param columns An array of strings that defines a record to be added
+ /// to the zone.
+ virtual void addRecordToZone(
+ const std::string (&columns)[ADD_COLUMN_COUNT]) = 0;
+
+ /// Delete a single record from the zone to be updated.
+ ///
+ /// This method provides a simple interface to delete a record
+ /// (a database "row") from the zone in the update context started by
+ /// \c startUpdateZone(). The zone from which the record to be deleted
+ /// is the one specified at the time of the \c startUpdateZone() call.
+ ///
+ /// A successful call to \c startUpdateZone() must have preceded to
+ /// this call; otherwise a \c DataSourceError exception will be thrown.
+ ///
+ /// The record to be deleted is specified by a vector of strings that has
+ /// exactly DEL_PARAM_COUNT number of elements. See DeleteRecordParams
+ /// for the semantics of each element.
+ ///
+ /// \note In IXFR, TTL may also be specified, but we intentionally
+ /// ignore that in this interface, because it's not guaranteed
+ /// that all records have the same TTL (unlike the RRset
+ /// assumption) and there can even be multiple records for the
+ /// same name, type and rdata with different TTLs. If we only
+ /// delete one of them, subsequent lookup will still return a
+ /// positive answer, which would be confusing. It's a higher
+ /// layer's responsibility to check if there is at least one
+ /// record in the database that has the given TTL.
+ ///
+ /// Like \c addRecordToZone, derived class methods are not required to
+ /// validate the semantics of the given parameters or to check if there
+ /// is a record that matches the specified parameter; if there isn't
+ /// it simply ignores the result.
+ ///
+ /// \exception DataSourceError Invalid call without starting a transaction,
+ /// or other internal database error.
+ ///
+ /// \param params An array of strings that defines a record to be deleted
+ /// from the zone.
+ virtual void deleteRecordInZone(
+ const std::string (¶ms)[DEL_PARAM_COUNT]) = 0;
+
+ /// Commit updates to the zone.
+ ///
+ /// This method completes a transaction of making updates to the zone
+ /// in the context started by startUpdateZone.
+ ///
+ /// A successful call to \c startUpdateZone() must have preceded to
+ /// this call; otherwise a \c DataSourceError exception will be thrown.
+ /// Once this method successfully completes, the transaction isn't
+ /// considered to exist any more. So a new transaction can now be
+ /// started. On the other hand, a duplicate call to this method after
+ /// a successful completion of it is invalid and should result in
+ /// a \c DataSourceError exception.
+ ///
+ /// If some internal database error happens, a \c DataSourceError
+ /// exception must be thrown. In that case the transaction is still
+ /// considered to be valid; the caller must explicitly rollback it
+ /// or (if it's confident that the error is temporary) try to commit it
+ /// again.
+ ///
+ /// \exception DataSourceError Call without a transaction, duplicate call
+ /// to the method or internal database error.
+ virtual void commitUpdateZone() = 0;
+
+ /// Rollback updates to the zone made so far.
+ ///
+ /// This method rollbacks a transaction of making updates to the zone
+ /// in the context started by startUpdateZone. When it succeeds
+ /// (it normally should, but see below), the underlying database should
+ /// be reverted to the point before performing the corresponding
+ /// \c startUpdateZone().
+ ///
+ /// A successful call to \c startUpdateZone() must have preceded to
+ /// this call; otherwise a \c DataSourceError exception will be thrown.
+ /// Once this method successfully completes, the transaction isn't
+ /// considered to exist any more. So a new transaction can now be
+ /// started. On the other hand, a duplicate call to this method after
+ /// a successful completion of it is invalid and should result in
+ /// a \c DataSourceError exception.
+ ///
+ /// Normally this method should not fail. But it may not always be
+ /// possible to guarantee it depending on the characteristics of the
+ /// underlying database system. So this interface doesn't require the
+ /// actual implementation for the error free property. But if a specific
+ /// implementation of this method can fail, it is encouraged to document
+ /// when that can happen with its implication.
+ ///
+ /// \exception DataSourceError Call without a transaction, duplicate call
+ /// to the method or internal database error.
+ virtual void rollbackUpdateZone() = 0;
+
+ /// Clone the accessor with the same configuration.
+ ///
+ /// Each derived class implementation of this method will create a new
+ /// accessor of the same derived class with the same configuration
+ /// (such as the database server address) as that of the caller object
+ /// and return it.
+ ///
+ /// Note that other internal states won't be copied to the new accessor
+ /// even though the name of "clone" may indicate so. For example, even
+ /// if the calling accessor is in the middle of a update transaction,
+ /// the new accessor will not start a transaction to trace the same
+ /// updates.
+ ///
+ /// The intended use case of cloning is to create a separate context
+ /// where a specific set of database operations can be performed
+ /// independently from the original accessor. The updater will use it
+ /// so that multiple updaters can be created concurrently even if the
+ /// underlying database system doesn't allow running multiple transactions
+ /// in a single database connection.
+ ///
+ /// The underlying database system may not support the functionality
+ /// that would be needed to implement this method. For example, it
+ /// may not allow a single thread (or process) to have more than one
+ /// database connections. In such a case the derived class implementation
+ /// should throw a \c DataSourceError exception.
+ ///
+ /// \return A shared pointer to the cloned accessor.
+ virtual boost::shared_ptr<DatabaseAccessor> clone() = 0;
+
/**
* \brief Returns a string identifying this dabase backend
*
@@ -228,7 +481,7 @@ public:
* This is used in DatabaseClient::findPreviousName and does more
* or less the real work, except for working on strings.
*
- * \param name The name to ask for previous of.
+ * \param rname The name to ask for previous of, in reversed form.
* \param zone_id The zone to look through.
* \return The previous name.
*
@@ -236,7 +489,7 @@ public:
* \throw NotImplemented if this database doesn't support DNSSEC.
*/
virtual std::string findPreviousName(int zone_id,
- const std::string& name) const = 0;
+ const std::string& rname) const = 0;
};
/**
@@ -258,16 +511,19 @@ public:
/**
* \brief Constructor
*
- * It initializes the client with a database.
+ * It initializes the client with a database via the given accessor.
*
- * \exception isc::InvalidParameter if database is NULL. It might throw
+ * \exception isc::InvalidParameter if accessor is NULL. It might throw
* standard allocation exception as well, but doesn't throw anything else.
*
- * \param database The database to use to get data. As the parameter
- * suggests, the client takes ownership of the database and will
- * delete it when itself deleted.
+ * \param rrclass The RR class of the zones that this client will handle.
+ * \param accessor The accessor to the database to use to get data.
+ * As the parameter suggests, the client takes ownership of the accessor
+ * and will delete it when itself deleted.
*/
- DatabaseClient(boost::shared_ptr<DatabaseAccessor> database);
+ DatabaseClient(isc::dns::RRClass rrclass,
+ boost::shared_ptr<DatabaseAccessor> accessor);
+
/**
* \brief Corresponding ZoneFinder implementation
*
@@ -363,49 +619,27 @@ public:
* applications shouldn't need it.
*/
int zone_id() const { return (zone_id_); }
+
/**
- * \brief The database.
+ * \brief The database accessor.
*
- * This function provides the database stored inside as
+ * This function provides the database accessor stored inside as
* passed to the constructor. This is meant for testing purposes and
* normal applications shouldn't need it.
*/
- const DatabaseAccessor& database() const {
- return (*database_);
+ const DatabaseAccessor& getAccessor() const {
+ return (*accessor_);
}
private:
- boost::shared_ptr<DatabaseAccessor> database_;
+ boost::shared_ptr<DatabaseAccessor> accessor_;
const int zone_id_;
const isc::dns::Name origin_;
+ //
/// \brief Shortcut name for the result of getRRsets
typedef std::pair<bool, std::map<dns::RRType, dns::RRsetPtr> >
FoundRRsets;
/// \brief Just shortcut for set of types
typedef std::set<dns::RRType> WantedTypes;
- /**
- * \brief Searches the database for RRsets for the given domain.
- *
- * It searches the given domain and provides all RRsets it can find
- * and are listed as wanted.
- *
- * It also performs some of the checks, where one type isn't allowed
- * to coexist with another one and throws in case they are found.
- *
- * \param name The domain to search.
- * \param types Set of types that should be returned, if found.
- * \param check_ns Should it perform a check if NS lives together
- * with something? It should be false in apex, as the NS there
- * can live together with anything, but false everywhere else.
- * \param construct_name If not null, it overrides the name for
- * which the result RRsets are constructed. This is useful for
- * wildcards.
- * \result The first part of the pair informs wheather there are
- * any RRs in the domain (even ones that are not in types).
- * The second part is map holding found RRsets (the ones that
- * are in types but are not found are not present, not even as
- * NULL pointers).
- * \thrown DataSourceError if two types that can't coexist are found.
- */
FoundRRsets getRRsets(const dns::Name& name, const WantedTypes& types,
bool check_ns,
const dns::Name* construct_name = NULL);
@@ -419,6 +653,7 @@ public:
*/
bool hasSubdomains(const std::string& name);
};
+
/**
* \brief Find a zone in the database
*
@@ -454,12 +689,26 @@ public:
*/
virtual ZoneIteratorPtr getIterator(const isc::dns::Name& name) const;
+ /// This implementation internally clones the accessor from the one
+ /// used in the client and starts a separate transaction using the cloned
+ /// accessor. The returned updater will be able to work separately from
+ /// the original client.
+ virtual ZoneUpdaterPtr getUpdater(const isc::dns::Name& name,
+ bool replace) const;
+
private:
- /// \brief Our database.
- const boost::shared_ptr<DatabaseAccessor> database_;
+ /// \brief The RR class that this client handles.
+ const isc::dns::RRClass rrclass_;
+
+ /// \brief The accessor to our database.
+ const boost::shared_ptr<DatabaseAccessor> accessor_;
};
}
}
-#endif
+#endif // __DATABASE_DATASRC_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/datasrc/datasrc_messages.mes b/src/lib/datasrc/datasrc_messages.mes
index 5f92407..efb88fd 100644
--- a/src/lib/datasrc/datasrc_messages.mes
+++ b/src/lib/datasrc/datasrc_messages.mes
@@ -590,3 +590,38 @@ data source.
% DATASRC_UNEXPECTED_QUERY_STATE unexpected query state
This indicates a programming error. An internal task of unknown type was
generated.
+
+% DATASRC_DATABASE_UPDATER_CREATED zone updater created for '%1/%2' on %3
+Debug information. A zone updater object is created to make updates to
+the shown zone on the shown backend database.
+
+% DATASRC_DATABASE_UPDATER_DESTROYED zone updater destroyed for '%1/%2' on %3
+Debug information. A zone updater object is destroyed, either successfully
+or after failure of, making updates to the shown zone on the shown backend
+database.
+
+%DATASRC_DATABASE_UPDATER_ROLLBACK zone updates roll-backed for '%1/%2' on %3
+A zone updater is being destroyed without committing the changes.
+This would typically mean the update attempt was aborted due to some
+error, but may also be a bug of the application that forgets committing
+the changes. The intermediate changes made through the updater won't
+be applied to the underlying database. The zone name, its class, and
+the underlying database name are shown in the log message.
+
+%DATASRC_DATABASE_UPDATER_ROLLBACKFAIL failed to roll back zone updates for '%1/%2' on %3: %4
+A zone updater is being destroyed without committing the changes to
+the database, and attempts to rollback incomplete updates, but it
+unexpectedly fails. The higher level implementation does not expect
+it to fail, so this means either a serious operational error in the
+underlying data source (such as a system failure of a database) or
+software bug in the underlying data source implementation. In either
+case if this message is logged the administrator should carefully
+examine the underlying data source to see what exactly happens and
+whether the data is still valid. The zone name, its class, and the
+underlying database name as well as the error message thrown from the
+database module are shown in the log message.
+
+% DATASRC_DATABASE_UPDATER_COMMIT updates committed for '%1/%2' on %3
+Debug information. A set of updates to a zone has been successfully
+committed to the corresponding database backend. The zone name,
+its class and the database name are printed.
diff --git a/src/lib/datasrc/memory_datasrc.cc b/src/lib/datasrc/memory_datasrc.cc
index 4cbaf91..2e94b67 100644
--- a/src/lib/datasrc/memory_datasrc.cc
+++ b/src/lib/datasrc/memory_datasrc.cc
@@ -17,6 +17,8 @@
#include <boost/shared_ptr.hpp>
#include <boost/bind.hpp>
+#include <exceptions/exceptions.h>
+
#include <dns/name.h>
#include <dns/rrclass.h>
#include <dns/rrsetlist.h>
@@ -799,5 +801,9 @@ InMemoryClient::getIterator(const Name& name) const {
return (ZoneIteratorPtr(new MemoryIterator(zone->impl_->domains_, name)));
}
+ZoneUpdaterPtr
+InMemoryClient::getUpdater(const isc::dns::Name&, bool) const {
+ isc_throw(isc::NotImplemented, "Update attempt on in memory data source");
+}
} // end of namespace datasrc
} // end of namespace dns
diff --git a/src/lib/datasrc/memory_datasrc.h b/src/lib/datasrc/memory_datasrc.h
index d89b3dd..95f589a 100644
--- a/src/lib/datasrc/memory_datasrc.h
+++ b/src/lib/datasrc/memory_datasrc.h
@@ -272,6 +272,17 @@ public:
/// \brief Implementation of the getIterator method
virtual ZoneIteratorPtr getIterator(const isc::dns::Name& name) const;
+ /// In-memory data source is read-only, so this derived method will
+ /// result in a NotImplemented exception.
+ ///
+ /// \note We plan to use a database-based data source as a backend
+ /// persistent storage for an in-memory data source. When it's
+ /// implemented we may also want to allow the user of the in-memory client
+ /// to update via its updater (this may or may not be a good idea and
+ /// is subject to further discussions).
+ virtual ZoneUpdaterPtr getUpdater(const isc::dns::Name& name,
+ bool replace) const;
+
private:
// TODO: Do we still need the PImpl if nobody should manipulate this class
// directly any more (it should be handled through DataSourceClient)?
diff --git a/src/lib/datasrc/sqlite3_accessor.cc b/src/lib/datasrc/sqlite3_accessor.cc
index 023ba69..a5ac4c7 100644
--- a/src/lib/datasrc/sqlite3_accessor.cc
+++ b/src/lib/datasrc/sqlite3_accessor.cc
@@ -14,31 +14,128 @@
#include <sqlite3.h>
+#include <string>
+#include <vector>
+
+#include <boost/foreach.hpp>
+
#include <datasrc/sqlite3_accessor.h>
#include <datasrc/logger.h>
#include <datasrc/data_source.h>
#include <util/filename.h>
-#include <boost/lexical_cast.hpp>
+using namespace std;
#define SQLITE_SCHEMA_VERSION 1
namespace isc {
namespace datasrc {
+// The following enum and char* array define the SQL statements commonly
+// used in this implementation. Corresponding prepared statements (of
+// type sqlite3_stmt*) are maintained in the statements_ array of the
+// SQLite3Parameters structure.
+
+enum StatementID {
+ ZONE = 0,
+ ANY = 1,
+ ANY_SUB = 2,
+ BEGIN = 3,
+ COMMIT = 4,
+ ROLLBACK = 5,
+ DEL_ZONE_RECORDS = 6,
+ ADD_RECORD = 7,
+ DEL_RECORD = 8,
+ ITERATE = 9,
+ FIND_PREVIOUS = 10,
+ FIND_PREVIOUS_WRAP = 11,
+ NUM_STATEMENTS = 12
+};
+
+const char* const text_statements[NUM_STATEMENTS] = {
+ // note for ANY and ITERATE: the order of the SELECT values is
+ // specifically chosen to match the enum values in RecordColumns
+ "SELECT id FROM zones WHERE name=?1 AND rdclass = ?2", // ZONE
+ "SELECT rdtype, ttl, sigtype, rdata FROM records " // ANY
+ "WHERE zone_id=?1 AND name=?2",
+ "SELECT rdtype, ttl, sigtype, rdata " // ANY_SUB
+ "FROM records WHERE zone_id=?1 AND name LIKE (\"%.\" || ?2)",
+ "BEGIN", // BEGIN
+ "COMMIT", // COMMIT
+ "ROLLBACK", // ROLLBACK
+ "DELETE FROM records WHERE zone_id=?1", // DEL_ZONE_RECORDS
+ "INSERT INTO records " // ADD_RECORD
+ "(zone_id, name, rname, ttl, rdtype, sigtype, rdata) "
+ "VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)",
+ "DELETE FROM records WHERE zone_id=?1 AND name=?2 " // DEL_RECORD
+ "AND rdtype=?3 AND rdata=?4",
+ "SELECT rdtype, ttl, sigtype, rdata, name FROM records " // ITERATE
+ "WHERE zone_id = ?1 ORDER BY name, rdtype",
+ "SELECT name FROM records " // FIND_PREVIOUS
+ "WHERE zone_id=?1 AND rdtype = 'NSEC' AND "
+ "rname < $2 ORDER BY rname DESC LIMIT 1", // FIND_PREVIOUS_WRAP
+ "SELECT name FROM records "
+ "WHERE zone_id = ?1 AND rdtype = 'NSEC' "
+ "ORDER BY rname DESC LIMIT 1"
+};
+
struct SQLite3Parameters {
SQLite3Parameters() :
- db_(NULL), version_(-1),
- q_zone_(NULL)
- {}
+ db_(NULL), version_(-1), updating_zone(false), updated_zone_id(-1)
+ {
+ for (int i = 0; i < NUM_STATEMENTS; ++i) {
+ statements_[i] = NULL;
+ }
+ }
+
sqlite3* db_;
int version_;
- sqlite3_stmt* q_zone_;
+ sqlite3_stmt* statements_[NUM_STATEMENTS];
+ bool updating_zone; // whether or not updating the zone
+ int updated_zone_id; // valid only when updating_zone is true
};
-SQLite3Database::SQLite3Database(const std::string& filename,
+// This is a helper class to encapsulate the code logic of executing
+// a specific SQLite3 statement, ensuring the corresponding prepared
+// statement is always reset whether the execution is completed successfully
+// or it results in an exception.
+// Note that an object of this class is intended to be used for "ephemeral"
+// 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.
+class StatementProcessor {
+public:
+ // desc will be used on failure in the what() message of the resulting
+ // DataSourceError exception.
+ StatementProcessor(SQLite3Parameters& dbparameters, StatementID stmt_id,
+ const char* desc) :
+ dbparameters_(dbparameters), stmt_id_(stmt_id), desc_(desc)
+ {
+ sqlite3_clear_bindings(dbparameters_.statements_[stmt_id_]);
+ }
+
+ ~StatementProcessor() {
+ sqlite3_reset(dbparameters_.statements_[stmt_id_]);
+ }
+
+ void exec() {
+ if (sqlite3_step(dbparameters_.statements_[stmt_id_]) != SQLITE_DONE) {
+ sqlite3_reset(dbparameters_.statements_[stmt_id_]);
+ isc_throw(DataSourceError, "failed to " << desc_ << ": " <<
+ sqlite3_errmsg(dbparameters_.db_));
+ }
+ }
+
+private:
+ SQLite3Parameters& dbparameters_;
+ const StatementID stmt_id_;
+ const char* const desc_;
+};
+
+SQLite3Accessor::SQLite3Accessor(const std::string& filename,
const isc::dns::RRClass& rrclass) :
dbparameters_(new SQLite3Parameters),
+ filename_(filename),
class_(rrclass.toText()),
database_name_("sqlite3_" +
isc::util::Filename(filename).nameAndExtension())
@@ -48,6 +145,25 @@ SQLite3Database::SQLite3Database(const std::string& filename,
open(filename);
}
+SQLite3Accessor::SQLite3Accessor(const std::string& filename,
+ const string& rrclass) :
+ dbparameters_(new SQLite3Parameters),
+ filename_(filename),
+ class_(rrclass),
+ database_name_("sqlite3_" +
+ isc::util::Filename(filename).nameAndExtension())
+{
+ LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_SQLITE_NEWCONN);
+
+ open(filename);
+}
+
+boost::shared_ptr<DatabaseAccessor>
+SQLite3Accessor::clone() {
+ return (boost::shared_ptr<DatabaseAccessor>(new SQLite3Accessor(filename_,
+ class_)));
+}
+
namespace {
// This is a helper class to initialize a Sqlite3 DB safely. An object of
@@ -60,34 +176,10 @@ namespace {
class Initializer {
public:
~Initializer() {
- if (params_.q_zone_ != NULL) {
- sqlite3_finalize(params_.q_zone_);
- }
- // we do NOT finalize q_current_ - that is just a pointer to one of
- // the other statements, not a separate one.
- /*
- if (params_.q_record_ != NULL) {
- sqlite3_finalize(params_.q_record_);
+ for (int i = 0; i < NUM_STATEMENTS; ++i) {
+ sqlite3_finalize(params_.statements_[i]);
}
- if (params_.q_addrs_ != NULL) {
- sqlite3_finalize(params_.q_addrs_);
- }
- if (params_.q_referral_ != NULL) {
- sqlite3_finalize(params_.q_referral_);
- }
- if (params_.q_count_ != NULL) {
- sqlite3_finalize(params_.q_count_);
- }
- if (params_.q_previous_ != NULL) {
- sqlite3_finalize(params_.q_previous_);
- }
- if (params_.q_nsec3_ != NULL) {
- sqlite3_finalize(params_.q_nsec3_);
- }
- if (params_.q_prevnsec3_ != NULL) {
- sqlite3_finalize(params_.q_prevnsec3_);
- }
- */
+
if (params_.db_ != NULL) {
sqlite3_close(params_.db_);
}
@@ -123,54 +215,6 @@ const char* const SCHEMA_LIST[] = {
NULL
};
-const char* const q_version_str = "SELECT version FROM schema_version";
-
-const char* const q_zone_str = "SELECT id FROM zones WHERE name=?1 AND rdclass = ?2";
-
-// note that the order of the SELECT values is specifically chosen to match
-// the enum values in RecordColumns
-const char* const q_any_str = "SELECT rdtype, ttl, sigtype, rdata "
- "FROM records WHERE zone_id=?1 AND name=?2";
-
-const char* const q_any_sub_str = "SELECT rdtype, ttl, sigtype, rdata "
- "FROM records WHERE zone_id=?1 AND name LIKE (\"%.\" || ?2)";
-
-// note that the order of the SELECT values is specifically chosen to match
-// the enum values in RecordColumns
-const char* const q_iterate_str = "SELECT rdtype, ttl, sigtype, rdata, name FROM records "
- "WHERE zone_id = ?1 "
- "ORDER BY name, rdtype";
-
-/* TODO: Prune the statements, not everything will be needed maybe?
-const char* const q_record_str = "SELECT rdtype, ttl, sigtype, rdata "
- "FROM records WHERE zone_id=?1 AND name=?2 AND "
- "((rdtype=?3 OR sigtype=?3) OR "
- "(rdtype='CNAME' OR sigtype='CNAME') OR "
- "(rdtype='NS' OR sigtype='NS'))";
-
-const char* const q_addrs_str = "SELECT rdtype, ttl, sigtype, rdata "
- "FROM records WHERE zone_id=?1 AND name=?2 AND "
- "(rdtype='A' OR sigtype='A' OR rdtype='AAAA' OR sigtype='AAAA')";
-
-const char* const q_referral_str = "SELECT rdtype, ttl, sigtype, rdata FROM "
- "records WHERE zone_id=?1 AND name=?2 AND"
- "(rdtype='NS' OR sigtype='NS' OR rdtype='DS' OR sigtype='DS' OR "
- "rdtype='DNAME' OR sigtype='DNAME')";
-
-const char* const q_count_str = "SELECT COUNT(*) FROM records "
- "WHERE zone_id=?1 AND rname LIKE (?2 || '%');";
-
-const char* const q_previous_str = "SELECT name FROM records "
- "WHERE zone_id=?1 AND rdtype = 'NSEC' AND "
- "rname < $2 ORDER BY rname DESC LIMIT 1";
-
-const char* const q_nsec3_str = "SELECT rdtype, ttl, rdata FROM nsec3 "
- "WHERE zone_id = ?1 AND hash = $2";
-
-const char* const q_prevnsec3_str = "SELECT hash FROM nsec3 "
- "WHERE zone_id = ?1 AND hash <= $2 ORDER BY hash DESC LIMIT 1";
- */
-
sqlite3_stmt*
prepare(sqlite3* const db, const char* const statement) {
sqlite3_stmt* prepared = NULL;
@@ -184,7 +228,7 @@ prepare(sqlite3* const db, const char* const statement) {
// small function to sleep for 0.1 seconds, needed when waiting for
// exclusive database locks (which should only occur on startup, and only
// when the database has not been created yet)
-void do_sleep() {
+void doSleep() {
struct timespec req;
req.tv_sec = 0;
req.tv_nsec = 100000000;
@@ -193,13 +237,14 @@ void do_sleep() {
// returns the schema version if the schema version table exists
// returns -1 if it does not
-int check_schema_version(sqlite3* db) {
+int checkSchemaVersion(sqlite3* db) {
sqlite3_stmt* prepared = NULL;
// At this point in time, the database might be exclusively locked, in
// which case even prepare() will return BUSY, so we may need to try a
// few times
for (size_t i = 0; i < 50; ++i) {
- int rc = sqlite3_prepare_v2(db, q_version_str, -1, &prepared, NULL);
+ int rc = sqlite3_prepare_v2(db, "SELECT version FROM schema_version",
+ -1, &prepared, NULL);
if (rc == SQLITE_ERROR) {
// this is the error that is returned when the table does not
// exist
@@ -210,7 +255,7 @@ int check_schema_version(sqlite3* db) {
isc_throw(SQLite3Error, "Unable to prepare version query: "
<< rc << " " << sqlite3_errmsg(db));
}
- do_sleep();
+ doSleep();
}
if (sqlite3_step(prepared) != SQLITE_ROW) {
isc_throw(SQLite3Error,
@@ -238,9 +283,9 @@ int create_database(sqlite3* db) {
isc_throw(SQLite3Error, "Unable to acquire exclusive lock "
"for database creation: " << sqlite3_errmsg(db));
}
- do_sleep();
+ doSleep();
}
- int schema_version = check_schema_version(db);
+ int schema_version = checkSchemaVersion(db);
if (schema_version == -1) {
for (int i = 0; SCHEMA_LIST[i] != NULL; ++i) {
if (sqlite3_exec(db, SCHEMA_LIST[i], NULL, NULL, NULL) !=
@@ -260,28 +305,21 @@ void
checkAndSetupSchema(Initializer* initializer) {
sqlite3* const db = initializer->params_.db_;
- int schema_version = check_schema_version(db);
+ int schema_version = checkSchemaVersion(db);
if (schema_version != SQLITE_SCHEMA_VERSION) {
schema_version = create_database(db);
}
initializer->params_.version_ = schema_version;
- initializer->params_.q_zone_ = prepare(db, q_zone_str);
- /* TODO: Yet unneeded statements
- initializer->params_.q_record_ = prepare(db, q_record_str);
- initializer->params_.q_addrs_ = prepare(db, q_addrs_str);
- initializer->params_.q_referral_ = prepare(db, q_referral_str);
- initializer->params_.q_count_ = prepare(db, q_count_str);
- 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);
- */
+ for (int i = 0; i < NUM_STATEMENTS; ++i) {
+ initializer->params_.statements_[i] = prepare(db, text_statements[i]);
+ }
}
}
void
-SQLite3Database::open(const std::string& name) {
+SQLite3Accessor::open(const std::string& name) {
LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_SQLITE_CONNOPEN).arg(name);
if (dbparameters_->db_ != NULL) {
// There shouldn't be a way to trigger this anyway
@@ -298,7 +336,7 @@ SQLite3Database::open(const std::string& name) {
initializer.move(dbparameters_.get());
}
-SQLite3Database::~SQLite3Database() {
+SQLite3Accessor::~SQLite3Accessor() {
LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_SQLITE_DROPCONN);
if (dbparameters_->db_ != NULL) {
close();
@@ -306,7 +344,7 @@ SQLite3Database::~SQLite3Database() {
}
void
-SQLite3Database::close(void) {
+SQLite3Accessor::close(void) {
LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_SQLITE_CONNCLOSE);
if (dbparameters_->db_ == NULL) {
isc_throw(DataSourceError,
@@ -314,104 +352,105 @@ SQLite3Database::close(void) {
}
// XXX: sqlite3_finalize() could fail. What should we do in that case?
- sqlite3_finalize(dbparameters_->q_zone_);
- dbparameters_->q_zone_ = NULL;
-
- /* TODO: Once they are needed or not, uncomment or drop
- sqlite3_finalize(dbparameters->q_record_);
- dbparameters->q_record_ = NULL;
-
- sqlite3_finalize(dbparameters->q_addrs_);
- dbparameters->q_addrs_ = NULL;
-
- sqlite3_finalize(dbparameters->q_referral_);
- dbparameters->q_referral_ = NULL;
-
- sqlite3_finalize(dbparameters->q_count_);
- dbparameters->q_count_ = NULL;
-
- sqlite3_finalize(dbparameters->q_previous_);
- dbparameters->q_previous_ = NULL;
-
- sqlite3_finalize(dbparameters->q_prevnsec3_);
- dbparameters->q_prevnsec3_ = NULL;
-
- sqlite3_finalize(dbparameters->q_nsec3_);
- dbparameters->q_nsec3_ = NULL;
- */
+ for (int i = 0; i < NUM_STATEMENTS; ++i) {
+ sqlite3_finalize(dbparameters_->statements_[i]);
+ dbparameters_->statements_[i] = NULL;
+ }
sqlite3_close(dbparameters_->db_);
dbparameters_->db_ = NULL;
}
std::pair<bool, int>
-SQLite3Database::getZone(const std::string& name) const {
+SQLite3Accessor::getZone(const std::string& name) const {
int rc;
+ sqlite3_stmt* const stmt = dbparameters_->statements_[ZONE];
// Take the statement (simple SELECT id FROM zones WHERE...)
// and prepare it (bind the parameters to it)
- sqlite3_reset(dbparameters_->q_zone_);
- rc = sqlite3_bind_text(dbparameters_->q_zone_, 1, name.c_str(),
- -1, SQLITE_STATIC);
+ sqlite3_reset(stmt);
+ rc = sqlite3_bind_text(stmt, 1, name.c_str(), -1, SQLITE_STATIC);
if (rc != SQLITE_OK) {
isc_throw(SQLite3Error, "Could not bind " << name <<
" to SQL statement (zone)");
}
- rc = sqlite3_bind_text(dbparameters_->q_zone_, 2, class_.c_str(), -1,
- SQLITE_STATIC);
+ rc = sqlite3_bind_text(stmt, 2, class_.c_str(), -1, SQLITE_STATIC);
if (rc != SQLITE_OK) {
isc_throw(SQLite3Error, "Could not bind " << class_ <<
" to SQL statement (zone)");
}
// Get the data there and see if it found anything
- rc = sqlite3_step(dbparameters_->q_zone_);
- std::pair<bool, int> result;
+ rc = sqlite3_step(stmt);
if (rc == SQLITE_ROW) {
- result = std::pair<bool, int>(true,
- sqlite3_column_int(dbparameters_->
- q_zone_, 0));
- return (result);
+ const int zone_id = sqlite3_column_int(stmt, 0);
+ sqlite3_reset(stmt);
+ return (pair<bool, int>(true, zone_id));
} else if (rc == SQLITE_DONE) {
- result = std::pair<bool, int>(false, 0);
// Free resources
- sqlite3_reset(dbparameters_->q_zone_);
- return (result);
+ sqlite3_reset(stmt);
+ return (pair<bool, int>(false, 0));
}
+ sqlite3_reset(stmt);
isc_throw(DataSourceError, "Unexpected failure in sqlite3_step: " <<
- sqlite3_errmsg(dbparameters_->db_));
+ sqlite3_errmsg(dbparameters_->db_));
// Compilers might not realize isc_throw always throws
return (std::pair<bool, int>(false, 0));
}
-class SQLite3Database::Context : public DatabaseAccessor::IteratorContext {
+namespace {
+
+// Conversion to plain char
+const char*
+convertToPlainCharInternal(const unsigned char* ucp, sqlite3 *db) {
+ if (ucp == NULL) {
+ // The field can really be NULL, in which case we return an
+ // empty string, or sqlite may have run out of memory, in
+ // which case we raise an error
+ if (sqlite3_errcode(db) == SQLITE_NOMEM) {
+ isc_throw(DataSourceError,
+ "Sqlite3 backend encountered a memory allocation "
+ "error in sqlite3_column_text()");
+ } else {
+ return ("");
+ }
+ }
+ const void* p = ucp;
+ return (static_cast<const char*>(p));
+}
+
+}
+class SQLite3Accessor::Context : public DatabaseAccessor::IteratorContext {
public:
// Construct an iterator for all records. When constructed this
// way, the getNext() call will copy all fields
- Context(const boost::shared_ptr<const SQLite3Database>& database, int id) :
+ Context(const boost::shared_ptr<const SQLite3Accessor>& accessor, int id) :
iterator_type_(ITT_ALL),
- database_(database),
+ accessor_(accessor),
statement_(NULL),
name_("")
{
// We create the statement now and then just keep getting data from it
- statement_ = prepare(database->dbparameters_->db_, q_iterate_str);
+ statement_ = prepare(accessor->dbparameters_->db_,
+ text_statements[ITERATE]);
bindZoneId(id);
}
// Construct an iterator for records with a specific name. When constructed
// this way, the getNext() call will copy all fields except name
- Context(const boost::shared_ptr<const SQLite3Database>& database, int id,
+ Context(const boost::shared_ptr<const SQLite3Accessor>& accessor, int id,
const std::string& name, bool subdomains) :
iterator_type_(ITT_NAME),
- database_(database),
+ accessor_(accessor),
statement_(NULL),
name_(name)
+
{
// We create the statement now and then just keep getting data from it
- statement_ = prepare(database->dbparameters_->db_,
- subdomains ? q_any_sub_str : q_any_str);
+ statement_ = prepare(accessor->dbparameters_->db_,
+ subdomains ? text_statements[ANY_SUB] :
+ text_statements[ANY]);
bindZoneId(id);
bindName(name_);
}
@@ -438,7 +477,7 @@ public:
} else if (rc != SQLITE_DONE) {
isc_throw(DataSourceError,
"Unexpected failure in sqlite3_step: " <<
- sqlite3_errmsg(database_->dbparameters_->db_));
+ sqlite3_errmsg(accessor_->dbparameters_->db_));
}
finalize();
return (false);
@@ -467,14 +506,14 @@ private:
finalize();
isc_throw(SQLite3Error, "Could not bind int " << zone_id <<
" to SQL statement: " <<
- sqlite3_errmsg(database_->dbparameters_->db_));
+ sqlite3_errmsg(accessor_->dbparameters_->db_));
}
}
void bindName(const std::string& name) {
if (sqlite3_bind_text(statement_, 2, name.c_str(), -1,
- SQLITE_STATIC) != SQLITE_OK) {
- const char* errmsg = sqlite3_errmsg(database_->dbparameters_->db_);
+ SQLITE_TRANSIENT) != SQLITE_OK) {
+ const char* errmsg = sqlite3_errmsg(accessor_->dbparameters_->db_);
finalize();
isc_throw(SQLite3Error, "Could not bind text '" << name <<
"' to SQL statement: " << errmsg);
@@ -492,31 +531,18 @@ private:
// In case sqlite3_column_text() returns NULL, we just make it an
// empty string, unless it was caused by a memory error
const char* convertToPlainChar(const unsigned char* ucp) {
- if (ucp == NULL) {
- // The field can really be NULL, in which case we return an
- // empty string, or sqlite may have run out of memory, in
- // which case we raise an error
- if (sqlite3_errcode(database_->dbparameters_->db_)
- == SQLITE_NOMEM) {
- isc_throw(DataSourceError,
- "Sqlite3 backend encountered a memory allocation "
- "error in sqlite3_column_text()");
- } else {
- return ("");
- }
- }
- const void* p = ucp;
- return (static_cast<const char*>(p));
+ return (convertToPlainCharInternal(ucp,
+ accessor_->dbparameters_->db_));
}
const IteratorType iterator_type_;
- boost::shared_ptr<const SQLite3Database> database_;
+ boost::shared_ptr<const SQLite3Accessor> accessor_;
sqlite3_stmt *statement_;
const std::string name_;
};
DatabaseAccessor::IteratorContextPtr
-SQLite3Database::getRecords(const std::string& name, int id,
+SQLite3Accessor::getRecords(const std::string& name, int id,
bool subdomains) const
{
return (IteratorContextPtr(new Context(shared_from_this(), id, name,
@@ -524,13 +550,194 @@ SQLite3Database::getRecords(const std::string& name, int id,
}
DatabaseAccessor::IteratorContextPtr
-SQLite3Database::getAllRecords(int id) const {
+SQLite3Accessor::getAllRecords(int id) const {
return (IteratorContextPtr(new Context(shared_from_this(), id)));
}
+pair<bool, int>
+SQLite3Accessor::startUpdateZone(const string& zone_name, const bool replace) {
+ if (dbparameters_->updating_zone) {
+ isc_throw(DataSourceError,
+ "duplicate zone update on SQLite3 data source");
+ }
+
+ const pair<bool, int> zone_info(getZone(zone_name));
+ if (!zone_info.first) {
+ return (zone_info);
+ }
+
+ StatementProcessor(*dbparameters_, BEGIN,
+ "start an SQLite3 transaction").exec();
+
+ if (replace) {
+ try {
+ StatementProcessor delzone_exec(*dbparameters_, DEL_ZONE_RECORDS,
+ "delete zone records");
+
+ sqlite3_clear_bindings(
+ dbparameters_->statements_[DEL_ZONE_RECORDS]);
+ if (sqlite3_bind_int(dbparameters_->statements_[DEL_ZONE_RECORDS],
+ 1, zone_info.second) != SQLITE_OK) {
+ isc_throw(DataSourceError,
+ "failed to bind SQLite3 parameter: " <<
+ sqlite3_errmsg(dbparameters_->db_));
+ }
+
+ 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
+ // is still possible with this accessor.
+ StatementProcessor(*dbparameters_, ROLLBACK,
+ "rollback an SQLite3 transaction").exec();
+ throw;
+ }
+ }
+
+ dbparameters_->updating_zone = true;
+ dbparameters_->updated_zone_id = zone_info.second;
+
+ return (zone_info);
+}
+
+void
+SQLite3Accessor::commitUpdateZone() {
+ if (!dbparameters_->updating_zone) {
+ isc_throw(DataSourceError, "committing zone update on SQLite3 "
+ "data source without transaction");
+ }
+
+ StatementProcessor(*dbparameters_, COMMIT,
+ "commit an SQLite3 transaction").exec();
+ dbparameters_->updating_zone = false;
+ dbparameters_->updated_zone_id = -1;
+}
+
+void
+SQLite3Accessor::rollbackUpdateZone() {
+ if (!dbparameters_->updating_zone) {
+ isc_throw(DataSourceError, "rolling back zone update on SQLite3 "
+ "data source without transaction");
+ }
+
+ StatementProcessor(*dbparameters_, ROLLBACK,
+ "rollback an SQLite3 transaction").exec();
+ dbparameters_->updating_zone = false;
+ dbparameters_->updated_zone_id = -1;
+}
+
+namespace {
+// Commonly used code sequence for adding/deleting record
+template <typename COLUMNS_TYPE>
+void
+doUpdate(SQLite3Parameters& dbparams, StatementID stmt_id,
+ COLUMNS_TYPE update_params, const char* exec_desc)
+{
+ sqlite3_stmt* const stmt = dbparams.statements_[stmt_id];
+ StatementProcessor executer(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_));
+ }
+ const size_t column_count =
+ sizeof(update_params) / sizeof(update_params[0]);
+ for (int i = 0; i < column_count; ++i) {
+ if (sqlite3_bind_text(stmt, ++param_id, update_params[i].c_str(), -1,
+ SQLITE_TRANSIENT) != SQLITE_OK) {
+ isc_throw(DataSourceError, "failed to bind SQLite3 parameter: " <<
+ sqlite3_errmsg(dbparams.db_));
+ }
+ }
+ executer.exec();
+}
+}
+
+void
+SQLite3Accessor::addRecordToZone(const string (&columns)[ADD_COLUMN_COUNT]) {
+ if (!dbparameters_->updating_zone) {
+ isc_throw(DataSourceError, "adding record to SQLite3 "
+ "data source without transaction");
+ }
+ doUpdate<const string (&)[DatabaseAccessor::ADD_COLUMN_COUNT]>(
+ *dbparameters_, ADD_RECORD, columns, "add record to zone");
+}
+
+void
+SQLite3Accessor::deleteRecordInZone(const string (¶ms)[DEL_PARAM_COUNT]) {
+ if (!dbparameters_->updating_zone) {
+ isc_throw(DataSourceError, "deleting record in SQLite3 "
+ "data source without transaction");
+ }
+ doUpdate<const string (&)[DatabaseAccessor::DEL_PARAM_COUNT]>(
+ *dbparameters_, DEL_RECORD, params, "delete record from zone");
+}
+
std::string
-SQLite3Database::findPreviousName(int , const std::string&) const {
- return (std::string()); // TODO Test and implement
+SQLite3Accessor::findPreviousName(int zone_id, const std::string& rname)
+ const
+{
+ sqlite3_reset(dbparameters_->statements_[FIND_PREVIOUS]);
+ sqlite3_clear_bindings(dbparameters_->statements_[FIND_PREVIOUS]);
+
+ int rc = sqlite3_bind_int(dbparameters_->statements_[FIND_PREVIOUS], 1,
+ zone_id);
+ if (rc != SQLITE_OK) {
+ isc_throw(SQLite3Error, "Could not bind zone ID " << zone_id <<
+ " to SQL statement (find previous)");
+ }
+ rc = sqlite3_bind_text(dbparameters_->statements_[FIND_PREVIOUS], 2,
+ rname.c_str(), -1, SQLITE_STATIC);
+ if (rc != SQLITE_OK) {
+ isc_throw(SQLite3Error, "Could not bind name " << rname <<
+ " to SQL statement (find previous)");
+ }
+
+ std::string result;
+ rc = sqlite3_step(dbparameters_->statements_[FIND_PREVIOUS]);
+ if (rc == SQLITE_ROW) {
+ // We found it
+ result = convertToPlainCharInternal(sqlite3_column_text(dbparameters_->
+ statements_[FIND_PREVIOUS], 0), dbparameters_->db_);
+ }
+ sqlite3_reset(dbparameters_->statements_[FIND_PREVIOUS]);
+
+ if (rc == SQLITE_DONE) {
+ // Nothing previous, wrap around (is it needed for anything?
+ // Well, just for completeness)
+ sqlite3_reset(dbparameters_->statements_[FIND_PREVIOUS_WRAP]);
+ sqlite3_clear_bindings(dbparameters_->statements_[FIND_PREVIOUS_WRAP]);
+
+ int rc = sqlite3_bind_int(
+ dbparameters_->statements_[FIND_PREVIOUS_WRAP], 1, zone_id);
+ if (rc != SQLITE_OK) {
+ isc_throw(SQLite3Error, "Could not bind zone ID " << zone_id <<
+ " to SQL statement (find previous wrap)");
+ }
+
+ rc = sqlite3_step(dbparameters_->statements_[FIND_PREVIOUS_WRAP]);
+ if (rc == SQLITE_ROW) {
+ // We found it
+ result =
+ convertToPlainCharInternal(sqlite3_column_text(dbparameters_->
+ statements_[FIND_PREVIOUS_WRAP], 0), dbparameters_->db_);
+ }
+ sqlite3_reset(dbparameters_->statements_[FIND_PREVIOUS_WRAP]);
+
+ if (rc == SQLITE_DONE) {
+ // No NSEC records, this DB doesn't support DNSSEC
+ isc_throw(isc::NotImplemented, "The zone doesn't support DNSSEC");
+ }
+ }
+
+ if (rc != SQLITE_ROW && rc != SQLITE_DONE) {
+ // Some kind of error
+ isc_throw(SQLite3Error, "Could get data for previous name");
+ }
+
+ return (result);
}
}
diff --git a/src/lib/datasrc/sqlite3_accessor.h b/src/lib/datasrc/sqlite3_accessor.h
index 59b3fbb..c4bacad 100644
--- a/src/lib/datasrc/sqlite3_accessor.h
+++ b/src/lib/datasrc/sqlite3_accessor.h
@@ -53,8 +53,8 @@ struct SQLite3Parameters;
* According to the design, it doesn't interpret the data in any way, it just
* provides unified access to the DB.
*/
-class SQLite3Database : public DatabaseAccessor,
- public boost::enable_shared_from_this<SQLite3Database> {
+class SQLite3Accessor : public DatabaseAccessor,
+ public boost::enable_shared_from_this<SQLite3Accessor> {
public:
/**
* \brief Constructor
@@ -69,14 +69,29 @@ public:
* file can contain multiple classes of data, single database can
* provide only one class).
*/
- SQLite3Database(const std::string& filename,
+ SQLite3Accessor(const std::string& filename,
const isc::dns::RRClass& rrclass);
+
+ /**
+ * \brief Constructor
+ *
+ * Same as the other version, but takes rrclass as a bare string.
+ * we should obsolete the other version and unify the constructor to
+ * this version; the SQLite3Accessor is expected to be "dumb" and
+ * shouldn't care about DNS specific information such as RRClass.
+ */
+ SQLite3Accessor(const std::string& filename, const std::string& rrclass);
+
/**
* \brief Destructor
*
* Closes the database.
*/
- ~SQLite3Database();
+ ~SQLite3Accessor();
+
+ /// This implementation internally opens a new sqlite3 database for the
+ /// same file name specified in the constructor of the original accessor.
+ virtual boost::shared_ptr<DatabaseAccessor> clone();
/**
* \brief Look up a zone
@@ -121,6 +136,33 @@ public:
*/
virtual IteratorContextPtr getAllRecords(int id) const;
+ virtual std::pair<bool, int> startUpdateZone(const std::string& zone_name,
+ bool replace);
+
+ /// \note we are quite impatient here: it's quite possible that the COMMIT
+ /// fails due to other process performing SELECT on the same database
+ /// (consider the case where COMMIT is done by xfrin or dynamic update
+ /// server while an authoritative server is busy reading the DB).
+ /// In a future version we should probably need to introduce some retry
+ /// attempt and/or increase timeout before giving up the COMMIT, even
+ /// if it still doesn't guarantee 100% success. Right now this
+ /// implementation throws a \c DataSourceError exception in such a case.
+ virtual void commitUpdateZone();
+
+ /// \note In SQLite3 rollback can fail if there's another unfinished
+ /// statement is performed for the same database structure.
+ /// Although it's not expected to happen in our expected usage, it's not
+ /// guaranteed to be prevented at the API level. If it ever happens, this
+ /// method throws a \c DataSourceError exception. It should be
+ /// considered a bug of the higher level application program.
+ virtual void rollbackUpdateZone();
+
+ virtual void addRecordToZone(
+ const std::string (&columns)[ADD_COLUMN_COUNT]);
+
+ virtual void deleteRecordInZone(
+ const std::string (¶ms)[DEL_PARAM_COUNT]);
+
/// The SQLite3 implementation of this method returns a string starting
/// with a fixed prefix of "sqlite3_" followed by the DB file name
/// removing any path name. For example, for the DB file
@@ -129,12 +171,14 @@ public:
virtual const std::string& getDBName() const { return (database_name_); }
/// \brief Concrete implementation of the pure virtual method
- virtual std::string findPreviousName(int zone_id, const std::string& name)
+ virtual std::string findPreviousName(int zone_id, const std::string& rname)
const;
private:
/// \brief Private database data
boost::scoped_ptr<SQLite3Parameters> dbparameters_;
+ /// \brief The filename of the DB (necessary for clone())
+ const std::string filename_;
/// \brief The class for which the queries are done
const std::string class_;
/// \brief Opens the database
@@ -150,4 +194,8 @@ private:
}
}
-#endif
+#endif // __DATASRC_SQLITE3_CONNECTION_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/datasrc/tests/Makefile.am b/src/lib/datasrc/tests/Makefile.am
index eb0e3ad..48cbc76 100644
--- a/src/lib/datasrc/tests/Makefile.am
+++ b/src/lib/datasrc/tests/Makefile.am
@@ -1,9 +1,12 @@
+SUBDIRS = . testdata
+
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 += $(BOOST_INCLUDES)
AM_CPPFLAGS += $(SQLITE_CFLAGS)
-AM_CPPFLAGS += -DTEST_DATA_DIR=\"$(srcdir)/testdata\"
-AM_CPPFLAGS += -DTEST_DATA_BUILD_DIR=\"$(builddir)/testdata\"
+AM_CPPFLAGS += -DTEST_DATA_DIR=\"$(abs_srcdir)/testdata\"
+AM_CPPFLAGS += -DTEST_DATA_BUILDDIR=\"$(abs_builddir)/testdata\"
+AM_CPPFLAGS += -DINSTALL_PROG=\"$(abs_top_srcdir)/install-sh\"
AM_CXXFLAGS = $(B10_CXXFLAGS)
@@ -62,3 +65,4 @@ EXTRA_DIST += testdata/sql1.example.com.signed
EXTRA_DIST += testdata/sql2.example.com.signed
EXTRA_DIST += testdata/test-root.sqlite3
EXTRA_DIST += testdata/test.sqlite3
+EXTRA_DIST += testdata/rwtest.sqlite3
diff --git a/src/lib/datasrc/tests/client_unittest.cc b/src/lib/datasrc/tests/client_unittest.cc
index 1a88f18..5b2c91a 100644
--- a/src/lib/datasrc/tests/client_unittest.cc
+++ b/src/lib/datasrc/tests/client_unittest.cc
@@ -32,6 +32,9 @@ public:
virtual FindResult findZone(const isc::dns::Name&) const {
return (FindResult(result::NOTFOUND, ZoneFinderPtr()));
}
+ virtual ZoneUpdaterPtr getUpdater(const isc::dns::Name&, bool) const {
+ return (ZoneUpdaterPtr());
+ }
};
class ClientTest : public ::testing::Test {
diff --git a/src/lib/datasrc/tests/database_unittest.cc b/src/lib/datasrc/tests/database_unittest.cc
index 6a8fac7..337b8c5 100644
--- a/src/lib/datasrc/tests/database_unittest.cc
+++ b/src/lib/datasrc/tests/database_unittest.cc
@@ -12,6 +12,8 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
+#include <boost/foreach.hpp>
+
#include <gtest/gtest.h>
#include <dns/name.h>
@@ -23,6 +25,7 @@
#include <datasrc/zone.h>
#include <datasrc/data_source.h>
#include <datasrc/iterator.h>
+#include <datasrc/sqlite3_accessor.h>
#include <testutils/dnsmessage_test.h>
@@ -35,6 +38,145 @@ using namespace isc::dns;
namespace {
+// Imaginary zone IDs used in the mock accessor below.
+const int READONLY_ZONE_ID = 42;
+const int WRITABLE_ZONE_ID = 4200;
+
+// Commonly used test data
+const char* const TEST_RECORDS[][5] = {
+ // some plain data
+ {"www.example.org.", "A", "3600", "", "192.0.2.1"},
+ {"www.example.org.", "AAAA", "3600", "", "2001:db8::1"},
+ {"www.example.org.", "AAAA", "3600", "", "2001:db8::2"},
+ {"www.example.org.", "NSEC", "3600", "", "www2.example.org. A AAAA NSEC RRSIG"},
+ {"www.example.org.", "RRSIG", "3600", "", "NSEC 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},
+
+ {"www2.example.org.", "A", "3600", "", "192.0.2.1"},
+ {"www2.example.org.", "AAAA", "3600", "", "2001:db8::1"},
+ {"www2.example.org.", "A", "3600", "", "192.0.2.2"},
+
+ {"cname.example.org.", "CNAME", "3600", "", "www.example.org."},
+
+ // some DNSSEC-'signed' data
+ {"signed1.example.org.", "A", "3600", "", "192.0.2.1"},
+ {"signed1.example.org.", "RRSIG", "3600", "", "A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},
+
+ {"signed1.example.org.", "RRSIG", "3600", "", "A 5 3 3600 20000101000000 20000201000000 12346 example.org. FAKEFAKEFAKE"},
+ {"signed1.example.org.", "AAAA", "3600", "", "2001:db8::1"},
+ {"signed1.example.org.", "AAAA", "3600", "", "2001:db8::2"},
+ {"signed1.example.org.", "RRSIG", "3600", "", "AAAA 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},
+
+ {"signedcname1.example.org.", "CNAME", "3600", "", "www.example.org."},
+ {"signedcname1.example.org.", "RRSIG", "3600", "", "CNAME 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},
+
+ // special case might fail; sig is for cname, which isn't there (should be ignored)
+ // (ignoring of 'normal' other type is done above by www.)
+ {"acnamesig1.example.org.", "A", "3600", "", "192.0.2.1"},
+ {"acnamesig1.example.org.", "RRSIG", "3600", "", "A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},
+ {"acnamesig1.example.org.", "RRSIG", "3600", "", "CNAME 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},
+
+ // let's pretend we have a database that is not careful
+ // about the order in which it returns data
+ {"signed2.example.org.", "RRSIG", "3600", "", "A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},
+ {"signed2.example.org.", "AAAA", "3600", "", "2001:db8::2"},
+ {"signed2.example.org.", "RRSIG", "3600", "", "A 5 3 3600 20000101000000 20000201000000 12346 example.org. FAKEFAKEFAKE"},
+ {"signed2.example.org.", "A", "3600", "", "192.0.2.1"},
+ {"signed2.example.org.", "RRSIG", "3600", "", "AAAA 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},
+ {"signed2.example.org.", "AAAA", "3600", "", "2001:db8::1"},
+
+ {"signedcname2.example.org.", "RRSIG", "3600", "", "CNAME 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},
+ {"signedcname2.example.org.", "CNAME", "3600", "", "www.example.org."},
+
+ {"acnamesig2.example.org.", "RRSIG", "3600", "", "CNAME 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},
+ {"acnamesig2.example.org.", "A", "3600", "", "192.0.2.1"},
+ {"acnamesig2.example.org.", "RRSIG", "3600", "", "A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},
+
+ {"acnamesig3.example.org.", "RRSIG", "3600", "", "CNAME 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},
+ {"acnamesig3.example.org.", "RRSIG", "3600", "", "A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},
+ {"acnamesig3.example.org.", "A", "3600", "", "192.0.2.1"},
+
+ {"ttldiff1.example.org.", "A", "3600", "", "192.0.2.1"},
+ {"ttldiff1.example.org.", "A", "360", "", "192.0.2.2"},
+
+ {"ttldiff2.example.org.", "A", "360", "", "192.0.2.1"},
+ {"ttldiff2.example.org.", "A", "3600", "", "192.0.2.2"},
+
+ // also add some intentionally bad data
+ {"badcname1.example.org.", "A", "3600", "", "192.0.2.1"},
+ {"badcname1.example.org.", "CNAME", "3600", "", "www.example.org."},
+
+ {"badcname2.example.org.", "CNAME", "3600", "", "www.example.org."},
+ {"badcname2.example.org.", "A", "3600", "", "192.0.2.1"},
+
+ {"badcname3.example.org.", "CNAME", "3600", "", "www.example.org."},
+ {"badcname3.example.org.", "CNAME", "3600", "", "www.example2.org."},
+
+ {"badrdata.example.org.", "A", "3600", "", "bad"},
+
+ {"badtype.example.org.", "BAD_TYPE", "3600", "", "192.0.2.1"},
+
+ {"badttl.example.org.", "A", "badttl", "", "192.0.2.1"},
+
+ {"badsig.example.org.", "A", "badttl", "", "192.0.2.1"},
+ {"badsig.example.org.", "RRSIG", "3600", "", "A 5 3 3600 somebaddata 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},
+
+ {"badsigtype.example.org.", "A", "3600", "", "192.0.2.1"},
+ {"badsigtype.example.org.", "RRSIG", "3600", "TXT", "A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},
+
+ // Data for testing delegation (with NS and DNAME)
+ {"delegation.example.org.", "NS", "3600", "", "ns.example.com."},
+ {"delegation.example.org.", "NS", "3600", "",
+ "ns.delegation.example.org."},
+ {"delegation.example.org.", "RRSIG", "3600", "", "NS 5 3 3600 "
+ "20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},
+ {"ns.delegation.example.org.", "A", "3600", "", "192.0.2.1"},
+ {"deep.below.delegation.example.org.", "A", "3600", "", "192.0.2.1"},
+
+ {"dname.example.org.", "A", "3600", "", "192.0.2.1"},
+ {"dname.example.org.", "DNAME", "3600", "", "dname.example.com."},
+ {"dname.example.org.", "RRSIG", "3600", "",
+ "DNAME 5 3 3600 20000101000000 20000201000000 12345 "
+ "example.org. FAKEFAKEFAKE"},
+
+ {"below.dname.example.org.", "A", "3600", "", "192.0.2.1"},
+
+ // Broken NS
+ {"brokenns1.example.org.", "A", "3600", "", "192.0.2.1"},
+ {"brokenns1.example.org.", "NS", "3600", "", "ns.example.com."},
+
+ {"brokenns2.example.org.", "NS", "3600", "", "ns.example.com."},
+ {"brokenns2.example.org.", "A", "3600", "", "192.0.2.1"},
+
+ // Now double DNAME, to test failure mode
+ {"baddname.example.org.", "DNAME", "3600", "", "dname1.example.com."},
+ {"baddname.example.org.", "DNAME", "3600", "", "dname2.example.com."},
+
+ // Put some data into apex (including NS) so we can check our NS
+ // doesn't break anything
+ {"example.org.", "NS", "3600", "", "ns.example.com."},
+ {"example.org.", "A", "3600", "", "192.0.2.1"},
+ {"example.org.", "RRSIG", "3600", "", "NS 5 3 3600 20000101000000 "
+ "20000201000000 12345 example.org. FAKEFAKEFAKE"},
+
+ // This is because of empty domain test
+ {"a.b.example.org.", "A", "3600", "", "192.0.2.1"},
+
+ // Something for wildcards
+ {"*.wild.example.org.", "A", "3600", "", "192.0.2.5"},
+ {"*.wild.example.org.", "RRSIG", "3600", "A", "A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},
+ {"*.wild.example.org.", "NSEC", "3600", "", "cancel.here.wild.example.org. A NSEC RRSIG"},
+ {"*.wild.example.org.", "RRSIG", "3600", "", "NSEC 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},
+ {"cancel.here.wild.example.org.", "AAAA", "3600", "", "2001:db8::5"},
+ {"delegatedwild.example.org.", "NS", "3600", "", "ns.example.com."},
+ {"*.delegatedwild.example.org.", "A", "3600", "", "192.0.2.5"},
+ {"wild.*.foo.example.org.", "A", "3600", "", "192.0.2.5"},
+ {"wild.*.foo.*.bar.example.org.", "A", "3600", "", "192.0.2.5"},
+ // For finding previous, this one is the last one in the zone
+ {"zzz.example.org.", "NSEC", "3600", "", "example.org NSEC"},
+
+ {NULL, NULL, NULL, NULL, NULL},
+};
+
/*
* An accessor with minimum implementation, keeping the original
* "NotImplemented" methods.
@@ -46,7 +188,7 @@ public:
virtual std::pair<bool, int> getZone(const std::string& name) const {
if (name == "example.org.") {
- return (std::pair<bool, int>(true, 42));
+ return (std::pair<bool, int>(true, READONLY_ZONE_ID));
} else if (name == "null.example.org.") {
return (std::pair<bool, int>(true, 13));
} else if (name == "empty.example.org.") {
@@ -58,6 +200,19 @@ public:
}
}
+ virtual shared_ptr<DatabaseAccessor> clone() {
+ return (shared_ptr<DatabaseAccessor>()); // bogus data, but unused
+ }
+
+ virtual std::pair<bool, int> startUpdateZone(const std::string&, bool) {
+ // return dummy value. unused anyway.
+ return (pair<bool, int>(true, 0));
+ }
+ virtual void commitUpdateZone() {}
+ virtual void rollbackUpdateZone() {}
+ virtual void addRecordToZone(const string (&)[ADD_COLUMN_COUNT]) {}
+ virtual void deleteRecordInZone(const string (&)[DEL_PARAM_COUNT]) {}
+
virtual const std::string& getDBName() const {
return (database_name_);
}
@@ -67,12 +222,12 @@ public:
{
isc_throw(isc::NotImplemented,
"This database datasource can't be iterated");
- };
+ }
virtual IteratorContextPtr getAllRecords(int) const {
isc_throw(isc::NotImplemented,
"This database datasource can't be iterated");
- };
+ }
virtual std::string findPreviousName(int, const std::string&) const {
isc_throw(isc::NotImplemented,
@@ -84,18 +239,34 @@ private:
};
/*
- * A virtual database connection that pretends it contains single zone --
+ * A virtual database accessor that pretends it contains single zone --
* example.org.
*
* It has the same getZone method as NopConnection, but it provides
* implementation of the optional functionality.
*/
class MockAccessor : public NopAccessor {
+ // Type of mock database "row"s
+ typedef std::map<std::string, std::vector< std::vector<std::string> > >
+ Domains;
+
public:
- MockAccessor()
- {
+ MockAccessor() : rollbacked_(false) {
+ readonly_records_ = &readonly_records_master_;
+ update_records_ = &update_records_master_;
+ empty_records_ = &empty_records_master_;
fillData();
}
+
+ virtual shared_ptr<DatabaseAccessor> clone() {
+ shared_ptr<MockAccessor> cloned_accessor(new MockAccessor());
+ cloned_accessor->readonly_records_ = &readonly_records_master_;
+ cloned_accessor->update_records_ = &update_records_master_;
+ cloned_accessor->empty_records_ = &empty_records_master_;
+ latest_clone_ = cloned_accessor;
+ return (cloned_accessor);
+ }
+
private:
class MockNameIteratorContext : public IteratorContext {
public:
@@ -114,32 +285,27 @@ private:
throw std::exception();
}
- if (zone_id == 42) {
- if (subdomains) {
- cur_name.clear();
- // Just walk everything and check if it is a subdomain.
- // If it is, just copy all data from there.
- for (Domains::const_iterator
- i(mock_accessor.records.begin());
- i != mock_accessor.records.end(); ++ i) {
- Name local(i->first);
- if (local.compare(isc::dns::Name(name)).
- getRelation() ==
- isc::dns::NameComparisonResult::SUBDOMAIN) {
- cur_name.insert(cur_name.end(), i->second.begin(),
- i->second.end());
- }
- }
- } else {
+ cur_record_ = 0;
+ const Domains& cur_records = mock_accessor.getMockRecords(zone_id);
+ if (cur_records.count(name) > 0) {
// we're not aiming for efficiency in this test, simply
// copy the relevant vector from records
- if (mock_accessor.records.count(searched_name_) > 0) {
- cur_name = mock_accessor.records.find(searched_name_)->
- second;
- } else {
- cur_name.clear();
+ cur_name = cur_records.find(name)->second;
+ } else if (subdomains) {
+ cur_name.clear();
+ // Just walk everything and check if it is a subdomain.
+ // If it is, just copy all data from there.
+ for (Domains::const_iterator i(cur_records.begin());
+ i != cur_records.end(); ++i) {
+ const Name local(i->first);
+ if (local.compare(Name(name)).getRelation() ==
+ isc::dns::NameComparisonResult::SUBDOMAIN) {
+ cur_name.insert(cur_name.end(), i->second.begin(),
+ i->second.end());
}
}
+ } else {
+ cur_name.clear();
}
}
@@ -254,7 +420,7 @@ private:
};
public:
virtual IteratorContextPtr getAllRecords(int id) const {
- if (id == 42) {
+ if (id == READONLY_ZONE_ID) {
return (IteratorContextPtr(new MockIteratorContext()));
} else if (id == 13) {
return (IteratorContextPtr());
@@ -270,15 +436,110 @@ public:
virtual IteratorContextPtr getRecords(const std::string& name, int id,
bool subdomains) const
{
- if (id == 42) {
- return (IteratorContextPtr(new MockNameIteratorContext(*this, id,
- name, subdomains)));
+ if (id == READONLY_ZONE_ID || id == WRITABLE_ZONE_ID) {
+ return (IteratorContextPtr(
+ new MockNameIteratorContext(*this, id, name,
+ subdomains)));
} else {
isc_throw(isc::Unexpected, "Unknown zone ID");
}
}
- virtual std::string findPreviousName(int id, const std::string& name)
+ virtual pair<bool, int> startUpdateZone(const std::string& zone_name,
+ bool replace)
+ {
+ const pair<bool, int> zone_info = getZone(zone_name);
+ if (!zone_info.first) {
+ return (pair<bool, int>(false, 0));
+ }
+
+ // Prepare the record set for update. If replacing the existing one,
+ // we use an empty set; otherwise we use a writable copy of the
+ // original.
+ if (replace) {
+ update_records_->clear();
+ } else {
+ *update_records_ = *readonly_records_;
+ }
+
+ return (pair<bool, int>(true, WRITABLE_ZONE_ID));
+ }
+ virtual void commitUpdateZone() {
+ *readonly_records_ = *update_records_;
+ }
+ virtual void rollbackUpdateZone() {
+ // Special hook: if something with a name of "throw.example.org"
+ // has been added, trigger an imaginary unexpected event with an
+ // exception.
+ if (update_records_->count("throw.example.org.") > 0) {
+ isc_throw(DataSourceError, "unexpected failure in rollback");
+ }
+
+ rollbacked_ = true;
+ }
+ virtual void addRecordToZone(const string (&columns)[ADD_COLUMN_COUNT]) {
+ // Copy the current value to cur_name. If it doesn't exist,
+ // operator[] will create a new one.
+ cur_name_ = (*update_records_)[columns[DatabaseAccessor::ADD_NAME]];
+
+ vector<string> record_columns;
+ record_columns.push_back(columns[DatabaseAccessor::ADD_TYPE]);
+ record_columns.push_back(columns[DatabaseAccessor::ADD_TTL]);
+ record_columns.push_back(columns[DatabaseAccessor::ADD_SIGTYPE]);
+ record_columns.push_back(columns[DatabaseAccessor::ADD_RDATA]);
+ record_columns.push_back(columns[DatabaseAccessor::ADD_NAME]);
+
+ // copy back the added entry
+ cur_name_.push_back(record_columns);
+ (*update_records_)[columns[DatabaseAccessor::ADD_NAME]] = cur_name_;
+
+ // remember this one so that test cases can check it.
+ copy(columns, columns + DatabaseAccessor::ADD_COLUMN_COUNT,
+ columns_lastadded_);
+ }
+
+ // Helper predicate class used in deleteRecordInZone().
+ struct deleteMatch {
+ deleteMatch(const string& type, const string& rdata) :
+ type_(type), rdata_(rdata)
+ {}
+ bool operator()(const vector<string>& row) const {
+ return (row[0] == type_ && row[3] == rdata_);
+ }
+ const string& type_;
+ const string& rdata_;
+ };
+
+ virtual void deleteRecordInZone(const string (¶ms)[DEL_PARAM_COUNT]) {
+ vector<vector<string> >& records =
+ (*update_records_)[params[DatabaseAccessor::DEL_NAME]];
+ records.erase(remove_if(records.begin(), records.end(),
+ deleteMatch(
+ params[DatabaseAccessor::DEL_TYPE],
+ params[DatabaseAccessor::DEL_RDATA])),
+ records.end());
+ if (records.empty()) {
+ (*update_records_).erase(params[DatabaseAccessor::DEL_NAME]);
+ }
+ }
+
+ //
+ // Helper methods to keep track of some update related activities
+ //
+ bool isRollbacked() const {
+ return (rollbacked_);
+ }
+
+ const string* getLastAdded() const {
+ return (columns_lastadded_);
+ }
+
+ // This allows the test code to get the accessor used in an update context
+ shared_ptr<const MockAccessor> getLatestClone() const {
+ return (latest_clone_);
+ }
+
+ virtual std::string findPreviousName(int id, const std::string& rname)
const
{
// Hardcoded for now, but we could compute it from the data
@@ -286,9 +547,10 @@ public:
if (id == -1) {
isc_throw(isc::NotImplemented, "Test not implemented behaviour");
} else if (id == 42) {
- if (name == "example.org.") {
+ if (rname == "org.example.") {
return ("zzz.example.org.");
- } else if (name == "www2.example.org.") {
+ } else if (rname == "org.example.www2." ||
+ rname == "org.example.www1.") {
return ("www.example.org.");
} else {
isc_throw(isc::Unexpected, "Unexpected name");
@@ -299,42 +561,74 @@ public:
}
private:
- typedef std::map<std::string, std::vector< std::vector<std::string> > >
- Domains;
+ // The following member variables are storage and/or update work space
+ // of the test zone. The "master"s are the real objects that contain
+ // the data, and they are shared among all accessors cloned from
+ // an initially created one. The pointer members allow the sharing.
+ // "readonly" is for normal lookups. "update" is the workspace for
+ // updates. When update starts it will be initialized either as an
+ // empty set (when replacing the entire zone) or as a copy of the
+ // "readonly" one. "empty" is a sentinel to produce negative results.
+ Domains readonly_records_master_;
+ Domains* readonly_records_;
+ Domains update_records_master_;
+ Domains* update_records_;
+ const Domains empty_records_master_;
+ const Domains* empty_records_;
+
// used as temporary storage during the building of the fake data
- Domains records;
+
// used as temporary storage after searchForRecord() and during
// getNextRecord() calls, as well as during the building of the
// fake data
- std::vector< std::vector<std::string> > cur_name;
+ std::vector< std::vector<std::string> > cur_name_;
+
+ // The columns that were most recently added via addRecordToZone()
+ string columns_lastadded_[ADD_COLUMN_COUNT];
+
+ // Whether rollback operation has been performed for the database.
+ // Not useful except for purely testing purpose.
+ bool rollbacked_;
+
+ // Remember the mock accessor that was last cloned
+ boost::shared_ptr<MockAccessor> latest_clone_;
+
+ const Domains& getMockRecords(int zone_id) const {
+ if (zone_id == READONLY_ZONE_ID) {
+ return (*readonly_records_);
+ } else if (zone_id == WRITABLE_ZONE_ID) {
+ return (*update_records_);
+ }
+ return (*empty_records_);
+ }
// Adds one record to the current name in the database
// The actual data will not be added to 'records' until
// addCurName() is called
- void addRecord(const std::string& name,
- const std::string& type,
+ void addRecord(const std::string& type,
+ const std::string& ttl,
const std::string& sigtype,
const std::string& rdata) {
std::vector<std::string> columns;
- columns.push_back(name);
columns.push_back(type);
+ columns.push_back(ttl);
columns.push_back(sigtype);
columns.push_back(rdata);
- cur_name.push_back(columns);
+ cur_name_.push_back(columns);
}
// Adds all records we just built with calls to addRecords
- // to the actual fake database. This will clear cur_name,
+ // to the actual fake database. This will clear cur_name_,
// so we can immediately start adding new records.
void addCurName(const std::string& name) {
- ASSERT_EQ(0, records.count(name));
+ ASSERT_EQ(0, readonly_records_->count(name));
// Append the name to all of them
for (std::vector<std::vector<std::string> >::iterator
- i(cur_name.begin()); i != cur_name.end(); ++ i) {
+ i(cur_name_.begin()); i != cur_name_.end(); ++ i) {
i->push_back(name);
}
- records[name] = cur_name;
- cur_name.clear();
+ (*readonly_records_)[name] = cur_name_;
+ cur_name_.clear();
}
// Fills the database with zone data.
@@ -348,166 +642,17 @@ private:
// might not come in 'normal' order)
// It shall immediately fail if you try to add the same name twice.
void fillData() {
- // some plain data
- addRecord("A", "3600", "", "192.0.2.1");
- addRecord("AAAA", "3600", "", "2001:db8::1");
- addRecord("AAAA", "3600", "", "2001:db8::2");
- addRecord("NSEC", "3600", "", "www2.example.org. A AAAA NSEC RRSIG");
- addRecord("RRSIG", "3600", "", "A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
- addRecord("RRSIG", "3600", "", "AAAA 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
- addRecord("RRSIG", "3600", "", "NSEC 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
- addCurName("www.example.org.");
-
- addRecord("A", "3600", "", "192.0.2.1");
- addRecord("AAAA", "3600", "", "2001:db8::1");
- addRecord("A", "3600", "", "192.0.2.2");
- addCurName("www2.example.org.");
-
- addRecord("CNAME", "3600", "", "www.example.org.");
- addCurName("cname.example.org.");
-
- // some DNSSEC-'signed' data
- addRecord("A", "3600", "", "192.0.2.1");
- addRecord("RRSIG", "3600", "", "A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
- addRecord("RRSIG", "3600", "", "A 5 3 3600 20000101000000 20000201000000 12346 example.org. FAKEFAKEFAKE");
- addRecord("AAAA", "3600", "", "2001:db8::1");
- addRecord("AAAA", "3600", "", "2001:db8::2");
- addRecord("RRSIG", "3600", "", "AAAA 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
- addCurName("signed1.example.org.");
- addRecord("CNAME", "3600", "", "www.example.org.");
- addRecord("RRSIG", "3600", "", "CNAME 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
- addCurName("signedcname1.example.org.");
- // special case might fail; sig is for cname, which isn't there (should be ignored)
- // (ignoring of 'normal' other type is done above by www.)
- addRecord("A", "3600", "", "192.0.2.1");
- addRecord("RRSIG", "3600", "", "A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
- addRecord("RRSIG", "3600", "", "CNAME 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
- addCurName("acnamesig1.example.org.");
-
- // let's pretend we have a database that is not careful
- // about the order in which it returns data
- addRecord("RRSIG", "3600", "", "A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
- addRecord("AAAA", "3600", "", "2001:db8::2");
- addRecord("RRSIG", "3600", "", "A 5 3 3600 20000101000000 20000201000000 12346 example.org. FAKEFAKEFAKE");
- addRecord("A", "3600", "", "192.0.2.1");
- addRecord("RRSIG", "3600", "", "AAAA 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
- addRecord("AAAA", "3600", "", "2001:db8::1");
- addCurName("signed2.example.org.");
- addRecord("RRSIG", "3600", "", "CNAME 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
- addRecord("CNAME", "3600", "", "www.example.org.");
- addCurName("signedcname2.example.org.");
-
- addRecord("RRSIG", "3600", "", "CNAME 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
- addRecord("A", "3600", "", "192.0.2.1");
- addRecord("RRSIG", "3600", "", "A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
- addCurName("acnamesig2.example.org.");
-
- addRecord("RRSIG", "3600", "", "CNAME 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
- addRecord("RRSIG", "3600", "", "A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
- addRecord("A", "3600", "", "192.0.2.1");
- addCurName("acnamesig3.example.org.");
-
- addRecord("A", "3600", "", "192.0.2.1");
- addRecord("A", "360", "", "192.0.2.2");
- addCurName("ttldiff1.example.org.");
- addRecord("A", "360", "", "192.0.2.1");
- addRecord("A", "3600", "", "192.0.2.2");
- addCurName("ttldiff2.example.org.");
-
- // also add some intentionally bad data
- addRecord("A", "3600", "", "192.0.2.1");
- addRecord("CNAME", "3600", "", "www.example.org.");
- addCurName("badcname1.example.org.");
-
- addRecord("CNAME", "3600", "", "www.example.org.");
- addRecord("A", "3600", "", "192.0.2.1");
- addCurName("badcname2.example.org.");
-
- addRecord("CNAME", "3600", "", "www.example.org.");
- addRecord("CNAME", "3600", "", "www.example2.org.");
- addCurName("badcname3.example.org.");
-
- addRecord("A", "3600", "", "bad");
- addCurName("badrdata.example.org.");
-
- addRecord("BAD_TYPE", "3600", "", "192.0.2.1");
- addCurName("badtype.example.org.");
-
- addRecord("A", "badttl", "", "192.0.2.1");
- addCurName("badttl.example.org.");
-
- addRecord("A", "badttl", "", "192.0.2.1");
- addRecord("RRSIG", "3600", "", "A 5 3 3600 somebaddata 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
- addCurName("badsig.example.org.");
-
- addRecord("A", "3600", "", "192.0.2.1");
- addRecord("RRSIG", "3600", "TXT", "A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
- addCurName("badsigtype.example.org.");
-
- // Data for testing delegation (with NS and DNAME)
- addRecord("NS", "3600", "", "ns.example.com.");
- addRecord("NS", "3600", "", "ns.delegation.example.org.");
- addRecord("RRSIG", "3600", "", "NS 5 3 3600 20000101000000 "
- "20000201000000 12345 example.org. FAKEFAKEFAKE");
- addCurName("delegation.example.org.");
- addRecord("A", "3600", "", "192.0.2.1");
- addCurName("ns.delegation.example.org.");
- addRecord("A", "3600", "", "192.0.2.1");
- addCurName("deep.below.delegation.example.org.");
-
- addRecord("A", "3600", "", "192.0.2.1");
- addRecord("DNAME", "3600", "", "dname.example.com.");
- addRecord("RRSIG", "3600", "", "DNAME 5 3 3600 20000101000000 "
- "20000201000000 12345 example.org. FAKEFAKEFAKE");
- addCurName("dname.example.org.");
- addRecord("A", "3600", "", "192.0.2.1");
- addCurName("below.dname.example.org.");
-
- // Broken NS
- addRecord("A", "3600", "", "192.0.2.1");
- addRecord("NS", "3600", "", "ns.example.com.");
- addCurName("brokenns1.example.org.");
- addRecord("NS", "3600", "", "ns.example.com.");
- addRecord("A", "3600", "", "192.0.2.1");
- addCurName("brokenns2.example.org.");
-
- // Now double DNAME, to test failure mode
- addRecord("DNAME", "3600", "", "dname1.example.com.");
- addRecord("DNAME", "3600", "", "dname2.example.com.");
- addCurName("baddname.example.org.");
-
- // Put some data into apex (including NS) so we can check our NS
- // doesn't break anything
- addRecord("NS", "3600", "", "ns.example.com.");
- addRecord("A", "3600", "", "192.0.2.1");
- addRecord("RRSIG", "3600", "", "NS 5 3 3600 20000101000000 "
- "20000201000000 12345 example.org. FAKEFAKEFAKE");
- addCurName("example.org.");
-
- // This is because of empty domain test
- addRecord("A", "3600", "", "192.0.2.1");
- addCurName("a.b.example.org.");
-
- // Something for wildcards
- addRecord("A", "3600", "", "192.0.2.5");
- addRecord("NSEC", "3600", "", "cancel.here.wild.example.org. A NSEC "
- "RRSIG");
- addRecord("RRSIG", "3600", "", "A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
- addRecord("RRSIG", "3600", "", "NSEC 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
- addCurName("*.wild.example.org.");
- addRecord("AAAA", "3600", "", "2001:db8::5");
- addCurName("cancel.here.wild.example.org.");
- addRecord("NS", "3600", "", "ns.example.com.");
- addCurName("delegatedwild.example.org.");
- addRecord("A", "3600", "", "192.0.2.5");
- addCurName("*.delegatedwild.example.org.");
- addRecord("A", "3600", "", "192.0.2.5");
- addCurName("wild.*.foo.example.org.");
- addRecord("A", "3600", "", "192.0.2.5");
- addCurName("wild.*.foo.*.bar.example.org.");
- // For finding previous, this one is the last in zone
- addRecord("NSEC", "3600", "", "example.org NSEC");
- addCurName("zzz.example.org");
+ const char* prev_name = NULL;
+ for (int i = 0; TEST_RECORDS[i][0] != NULL; ++i) {
+ if (prev_name != NULL &&
+ strcmp(prev_name, TEST_RECORDS[i][0]) != 0) {
+ addCurName(prev_name);
+ }
+ prev_name = TEST_RECORDS[i][0];
+ addRecord(TEST_RECORDS[i][1], TEST_RECORDS[i][2],
+ TEST_RECORDS[i][3], TEST_RECORDS[i][4]);
+ }
+ addCurName(prev_name);
}
};
@@ -524,24 +669,51 @@ TEST(DatabaseConnectionTest, getAllRecords) {
isc::NotImplemented);
}
+// This test fixture is templated so that we can share (most of) the test
+// cases with different types of data sources. Note that in test cases
+// we need to use 'this' to refer to member variables of the test class.
+template <typename ACCESSOR_TYPE>
class DatabaseClientTest : public ::testing::Test {
public:
- DatabaseClientTest() {
+ DatabaseClientTest() : zname_("example.org"), qname_("www.example.org"),
+ qclass_(RRClass::IN()), qtype_(RRType::A()),
+ rrttl_(3600)
+ {
createClient();
+
+ // set up the commonly used finder.
+ DataSourceClient::FindResult zone(client_->findZone(zname_));
+ assert(zone.code == result::SUCCESS);
+ finder_ = dynamic_pointer_cast<DatabaseClient::Finder>(
+ zone.zone_finder);
+
+ // Test IN/A RDATA to be added in update tests. Intentionally using
+ // different data than the initial data configured in the MockAccessor.
+ rrset_.reset(new RRset(qname_, qclass_, qtype_, rrttl_));
+ rrset_->addRdata(rdata::createRdata(rrset_->getType(),
+ rrset_->getClass(), "192.0.2.2"));
+
+ // And its RRSIG. Also different from the configured one.
+ rrsigset_.reset(new RRset(qname_, qclass_, RRType::RRSIG(),
+ rrttl_));
+ rrsigset_->addRdata(rdata::createRdata(rrsigset_->getType(),
+ rrsigset_->getClass(),
+ "A 5 3 0 20000101000000 "
+ "20000201000000 0 example.org. "
+ "FAKEFAKEFAKE"));
}
+
/*
* We initialize the client from a function, so we can call it multiple
* times per test.
*/
void createClient() {
- current_database_ = new MockAccessor();
- client_.reset(new DatabaseClient(shared_ptr<DatabaseAccessor>(
- current_database_)));
+ current_accessor_ = new ACCESSOR_TYPE();
+ is_mock_ = (dynamic_cast<MockAccessor*>(current_accessor_) != NULL);
+ client_.reset(new DatabaseClient(qclass_,
+ shared_ptr<ACCESSOR_TYPE>(
+ current_accessor_)));
}
- // Will be deleted by client_, just keep the current value for comparison.
- MockAccessor* current_database_;
- shared_ptr<DatabaseClient> client_;
- const std::string database_name_;
/**
* Check the zone finder is a valid one and references the zone ID and
@@ -553,89 +725,195 @@ public:
dynamic_pointer_cast<DatabaseClient::Finder>(zone.zone_finder));
ASSERT_NE(shared_ptr<DatabaseClient::Finder>(), finder) <<
"Wrong type of finder";
- EXPECT_EQ(42, finder->zone_id());
- EXPECT_EQ(current_database_, &finder->database());
+ if (is_mock_) {
+ EXPECT_EQ(READONLY_ZONE_ID, finder->zone_id());
+ }
+ EXPECT_EQ(current_accessor_, &finder->getAccessor());
}
shared_ptr<DatabaseClient::Finder> getFinder() {
- DataSourceClient::FindResult zone(
- client_->findZone(Name("example.org")));
+ DataSourceClient::FindResult zone(client_->findZone(zname_));
EXPECT_EQ(result::SUCCESS, zone.code);
shared_ptr<DatabaseClient::Finder> finder(
dynamic_pointer_cast<DatabaseClient::Finder>(zone.zone_finder));
- EXPECT_EQ(42, finder->zone_id());
+ if (is_mock_) {
+ EXPECT_EQ(READONLY_ZONE_ID, finder->zone_id());
+ }
return (finder);
}
+ // Helper methods for update tests
+ bool isRollbacked(bool expected = false) const {
+ if (is_mock_) {
+ const MockAccessor& mock_accessor =
+ dynamic_cast<const MockAccessor&>(*update_accessor_);
+ return (mock_accessor.isRollbacked());
+ } else {
+ return (expected);
+ }
+ }
+
+ void checkLastAdded(const char* const expected[]) const {
+ if (is_mock_) {
+ const MockAccessor* mock_accessor =
+ dynamic_cast<const MockAccessor*>(current_accessor_);
+ for (int i = 0; i < DatabaseAccessor::ADD_COLUMN_COUNT; ++i) {
+ EXPECT_EQ(expected[i],
+ mock_accessor->getLatestClone()->getLastAdded()[i]);
+ }
+ }
+ }
+
+ void setUpdateAccessor() {
+ if (is_mock_) {
+ const MockAccessor* mock_accessor =
+ dynamic_cast<const MockAccessor*>(current_accessor_);
+ update_accessor_ = mock_accessor->getLatestClone();
+ }
+ }
+
+ // Some tests only work for MockAccessor. We remember whether our accessor
+ // is of that type.
+ bool is_mock_;
+
+ // Will be deleted by client_, just keep the current value for comparison.
+ ACCESSOR_TYPE* current_accessor_;
+ shared_ptr<DatabaseClient> client_;
+ const std::string database_name_;
+
+ // The zone finder of the test zone commonly used in various tests.
+ shared_ptr<DatabaseClient::Finder> finder_;
+
+ // Some shortcut variables for commonly used test parameters
+ const Name zname_; // the zone name stored in the test data source
+ const Name qname_; // commonly used name to be found
+ const RRClass qclass_; // commonly used RR class used with qname
+ const RRType qtype_; // commonly used RR type used with qname
+ const RRTTL rrttl_; // commonly used RR TTL
+ RRsetPtr rrset_; // for adding/deleting an RRset
+ RRsetPtr rrsigset_; // for adding/deleting an RRset
+
+ // update related objects to be tested
+ ZoneUpdaterPtr updater_;
+ shared_ptr<const DatabaseAccessor> update_accessor_;
+
+ // placeholders
+ const std::vector<std::string> empty_rdatas_; // for NXRRSET/NXDOMAIN
std::vector<std::string> expected_rdatas_;
std::vector<std::string> expected_sig_rdatas_;
};
-TEST_F(DatabaseClientTest, zoneNotFound) {
- DataSourceClient::FindResult zone(client_->findZone(Name("example.com")));
+class TestSQLite3Accessor : public SQLite3Accessor {
+public:
+ TestSQLite3Accessor() : SQLite3Accessor(
+ TEST_DATA_BUILDDIR "/rwtest.sqlite3.copied",
+ RRClass::IN())
+ {
+ startUpdateZone("example.org.", true);
+ string columns[ADD_COLUMN_COUNT];
+ for (int i = 0; TEST_RECORDS[i][0] != NULL; ++i) {
+ columns[ADD_NAME] = TEST_RECORDS[i][0];
+ columns[ADD_REV_NAME] = Name(columns[ADD_NAME]).reverse().toText();
+ columns[ADD_TYPE] = TEST_RECORDS[i][1];
+ columns[ADD_TTL] = TEST_RECORDS[i][2];
+ columns[ADD_SIGTYPE] = TEST_RECORDS[i][3];
+ columns[ADD_RDATA] = TEST_RECORDS[i][4];
+
+ addRecordToZone(columns);
+ }
+ commitUpdateZone();
+ }
+};
+
+// The following two lines instantiate test cases with concrete accessor
+// classes to be tested.
+typedef ::testing::Types<MockAccessor, TestSQLite3Accessor> TestAccessorTypes;
+TYPED_TEST_CASE(DatabaseClientTest, TestAccessorTypes);
+
+// In some cases the entire test fixture is for the mock accessor only.
+// We use the usual TEST_F for them with the corresponding specialized class
+// to make the code simpler.
+typedef DatabaseClientTest<MockAccessor> MockDatabaseClientTest;
+
+TYPED_TEST(DatabaseClientTest, zoneNotFound) {
+ DataSourceClient::FindResult zone(
+ this->client_->findZone(Name("example.com")));
EXPECT_EQ(result::NOTFOUND, zone.code);
}
-TEST_F(DatabaseClientTest, exactZone) {
- DataSourceClient::FindResult zone(client_->findZone(Name("example.org")));
+TYPED_TEST(DatabaseClientTest, exactZone) {
+ DataSourceClient::FindResult zone(
+ this->client_->findZone(Name("example.org")));
EXPECT_EQ(result::SUCCESS, zone.code);
- checkZoneFinder(zone);
+ this->checkZoneFinder(zone);
}
-TEST_F(DatabaseClientTest, superZone) {
- DataSourceClient::FindResult zone(client_->findZone(Name(
+TYPED_TEST(DatabaseClientTest, superZone) {
+ DataSourceClient::FindResult zone(this->client_->findZone(Name(
"sub.example.org")));
EXPECT_EQ(result::PARTIALMATCH, zone.code);
- checkZoneFinder(zone);
+ this->checkZoneFinder(zone);
}
-TEST_F(DatabaseClientTest, noAccessorException) {
+// This test doesn't depend on derived accessor class, so we use TEST().
+TEST(GenericDatabaseClientTest, noAccessorException) {
// We need a dummy variable here; some compiler would regard it a mere
// declaration instead of an instantiation and make the test fail.
- EXPECT_THROW(DatabaseClient dummy((shared_ptr<DatabaseAccessor>())),
+ EXPECT_THROW(DatabaseClient dummy(RRClass::IN(),
+ shared_ptr<DatabaseAccessor>()),
isc::InvalidParameter);
}
// If the zone doesn't exist, exception is thrown
-TEST_F(DatabaseClientTest, noZoneIterator) {
- EXPECT_THROW(client_->getIterator(Name("example.com")), DataSourceError);
+TYPED_TEST(DatabaseClientTest, noZoneIterator) {
+ EXPECT_THROW(this->client_->getIterator(Name("example.com")),
+ DataSourceError);
}
// If the zone doesn't exist and iteration is not implemented, it still throws
// the exception it doesn't exist
-TEST_F(DatabaseClientTest, noZoneNotImplementedIterator) {
- EXPECT_THROW(DatabaseClient(boost::shared_ptr<DatabaseAccessor>(
- new NopAccessor())).getIterator(Name("example.com")),
+TEST(GenericDatabaseClientTest, noZoneNotImplementedIterator) {
+ EXPECT_THROW(DatabaseClient(RRClass::IN(),
+ boost::shared_ptr<DatabaseAccessor>(
+ new NopAccessor())).getIterator(
+ Name("example.com")),
DataSourceError);
}
-TEST_F(DatabaseClientTest, notImplementedIterator) {
- EXPECT_THROW(DatabaseClient(shared_ptr<DatabaseAccessor>(
+TEST(GenericDatabaseClientTest, notImplementedIterator) {
+ EXPECT_THROW(DatabaseClient(RRClass::IN(), shared_ptr<DatabaseAccessor>(
new NopAccessor())).getIterator(Name("example.org")),
isc::NotImplemented);
}
// Pretend a bug in the connection and pass NULL as the context
-// Should not crash, but gracefully throw
-TEST_F(DatabaseClientTest, nullIteratorContext) {
- EXPECT_THROW(client_->getIterator(Name("null.example.org")),
+// Should not crash, but gracefully throw. Works for the mock accessor only.
+TEST_F(MockDatabaseClientTest, nullIteratorContext) {
+ EXPECT_THROW(this->client_->getIterator(Name("null.example.org")),
isc::Unexpected);
}
-// It doesn't crash or anything if the zone is completely empty
-TEST_F(DatabaseClientTest, emptyIterator) {
- ZoneIteratorPtr it(client_->getIterator(Name("empty.example.org")));
+// It doesn't crash or anything if the zone is completely empty.
+// Works for the mock accessor only.
+TEST_F(MockDatabaseClientTest, emptyIterator) {
+ ZoneIteratorPtr it(this->client_->getIterator(Name("empty.example.org")));
EXPECT_EQ(ConstRRsetPtr(), it->getNextRRset());
// This is past the end, it should throw
EXPECT_THROW(it->getNextRRset(), isc::Unexpected);
}
-// Iterate trough a zone
-TEST_F(DatabaseClientTest, iterator) {
- ZoneIteratorPtr it(client_->getIterator(Name("example.org")));
+// Iterate through a zone
+TYPED_TEST(DatabaseClientTest, iterator) {
+ ZoneIteratorPtr it(this->client_->getIterator(Name("example.org")));
ConstRRsetPtr rrset(it->getNextRRset());
ASSERT_NE(ConstRRsetPtr(), rrset);
+
+ // The rest of the checks work only for the mock accessor.
+ if (!this->is_mock_) {
+ return;
+ }
+
EXPECT_EQ(Name("example.org"), rrset->getName());
EXPECT_EQ(RRClass::IN(), rrset->getClass());
EXPECT_EQ(RRType::SOA(), rrset->getType());
@@ -678,10 +956,10 @@ TEST_F(DatabaseClientTest, iterator) {
}
// This has inconsistent TTL in the set (the rest, like nonsense in
-// the data is handled in rdata itself).
-TEST_F(DatabaseClientTest, badIterator) {
+// the data is handled in rdata itself). Works for the mock accessor only.
+TEST_F(MockDatabaseClientTest, badIterator) {
// It should not throw, but get the lowest one of them
- ZoneIteratorPtr it(client_->getIterator(Name("bad.example.org")));
+ ZoneIteratorPtr it(this->client_->getIterator(Name("bad.example.org")));
EXPECT_EQ(it->getNextRRset()->getTTL(), isc::dns::RRTTL(300));
}
@@ -705,7 +983,7 @@ checkRRset(isc::dns::ConstRRsetPtr rrset,
}
void
-doFindTest(shared_ptr<DatabaseClient::Finder> finder,
+doFindTest(ZoneFinder& finder,
const isc::dns::Name& name,
const isc::dns::RRType& type,
const isc::dns::RRType& expected_type,
@@ -718,16 +996,16 @@ doFindTest(shared_ptr<DatabaseClient::Finder> finder,
{
SCOPED_TRACE("doFindTest " + name.toText() + " " + type.toText());
ZoneFinder::FindResult result =
- finder->find(name, type, NULL, options);
+ finder.find(name, type, NULL, options);
ASSERT_EQ(expected_result, result.code) << name << " " << type;
if (!expected_rdatas.empty() && result.rrset) {
checkRRset(result.rrset, expected_name != Name(".") ? expected_name :
- name, finder->getClass(), expected_type, expected_ttl,
+ name, finder.getClass(), expected_type, expected_ttl,
expected_rdatas);
if (!expected_sig_rdatas.empty() && result.rrset->getRRsig()) {
checkRRset(result.rrset->getRRsig(), expected_name != Name(".") ?
- expected_name : name, finder->getClass(),
+ expected_name : name, finder.getClass(),
isc::dns::RRType::RRSIG(), expected_ttl,
expected_sig_rdatas);
} else if (expected_sig_rdatas.empty()) {
@@ -742,559 +1020,520 @@ doFindTest(shared_ptr<DatabaseClient::Finder> finder,
}
}
-TEST_F(DatabaseClientTest, find) {
- shared_ptr<DatabaseClient::Finder> finder(getFinder());
-
- expected_rdatas_.clear();
- expected_sig_rdatas_.clear();
- expected_rdatas_.push_back("192.0.2.1");
- expected_sig_rdatas_.push_back("A 5 3 3600 20000101000000 20000201000000 "
- "12345 example.org. FAKEFAKEFAKE");
- doFindTest(finder, isc::dns::Name("www.example.org."),
- isc::dns::RRType::A(), isc::dns::RRType::A(),
- isc::dns::RRTTL(3600),
- ZoneFinder::SUCCESS,
- expected_rdatas_, expected_sig_rdatas_);
-
- expected_rdatas_.clear();
- expected_sig_rdatas_.clear();
- expected_rdatas_.push_back("192.0.2.1");
- expected_rdatas_.push_back("192.0.2.2");
- doFindTest(finder, isc::dns::Name("www2.example.org."),
- isc::dns::RRType::A(), isc::dns::RRType::A(),
- isc::dns::RRTTL(3600),
- ZoneFinder::SUCCESS,
- expected_rdatas_, expected_sig_rdatas_);
-
- expected_rdatas_.clear();
- expected_sig_rdatas_.clear();
- expected_rdatas_.push_back("2001:db8::1");
- expected_rdatas_.push_back("2001:db8::2");
- expected_sig_rdatas_.push_back("AAAA 5 3 3600 20000101000000 "
- "20000201000000 12345 example.org. "
- "FAKEFAKEFAKE");
- doFindTest(finder, isc::dns::Name("www.example.org."),
+TYPED_TEST(DatabaseClientTest, find) {
+ shared_ptr<DatabaseClient::Finder> finder(this->getFinder());
+
+ this->expected_rdatas_.clear();
+ this->expected_sig_rdatas_.clear();
+ this->expected_rdatas_.push_back("192.0.2.1");
+ doFindTest(*finder, isc::dns::Name("www.example.org."),
+ this->qtype_, this->qtype_, this->rrttl_, ZoneFinder::SUCCESS,
+ this->expected_rdatas_, this->expected_sig_rdatas_);
+
+ this->expected_rdatas_.clear();
+ this->expected_sig_rdatas_.clear();
+ this->expected_rdatas_.push_back("192.0.2.1");
+ this->expected_rdatas_.push_back("192.0.2.2");
+ doFindTest(*finder, isc::dns::Name("www2.example.org."),
+ this->qtype_, this->qtype_, this->rrttl_, ZoneFinder::SUCCESS,
+ this->expected_rdatas_, this->expected_sig_rdatas_);
+
+ this->expected_rdatas_.clear();
+ this->expected_sig_rdatas_.clear();
+ this->expected_rdatas_.push_back("2001:db8::1");
+ this->expected_rdatas_.push_back("2001:db8::2");
+ doFindTest(*finder, isc::dns::Name("www.example.org."),
isc::dns::RRType::AAAA(), isc::dns::RRType::AAAA(),
- isc::dns::RRTTL(3600),
+ this->rrttl_,
ZoneFinder::SUCCESS,
- expected_rdatas_, expected_sig_rdatas_);
+ this->expected_rdatas_, this->expected_sig_rdatas_);
- expected_rdatas_.clear();
- expected_sig_rdatas_.clear();
- doFindTest(finder, isc::dns::Name("www.example.org."),
+ this->expected_rdatas_.clear();
+ this->expected_sig_rdatas_.clear();
+ doFindTest(*finder, isc::dns::Name("www.example.org."),
isc::dns::RRType::TXT(), isc::dns::RRType::TXT(),
- isc::dns::RRTTL(3600),
+ this->rrttl_,
ZoneFinder::NXRRSET,
- expected_rdatas_, expected_sig_rdatas_);
-
- expected_rdatas_.clear();
- expected_sig_rdatas_.clear();
- expected_rdatas_.push_back("www.example.org.");
- doFindTest(finder, isc::dns::Name("cname.example.org."),
- isc::dns::RRType::A(), isc::dns::RRType::CNAME(),
- isc::dns::RRTTL(3600),
- ZoneFinder::CNAME,
- expected_rdatas_, expected_sig_rdatas_);
-
- expected_rdatas_.clear();
- expected_sig_rdatas_.clear();
- expected_rdatas_.push_back("www.example.org.");
- doFindTest(finder, isc::dns::Name("cname.example.org."),
+ this->expected_rdatas_, this->expected_sig_rdatas_);
+
+ this->expected_rdatas_.clear();
+ this->expected_sig_rdatas_.clear();
+ this->expected_rdatas_.push_back("www.example.org.");
+ doFindTest(*finder, isc::dns::Name("cname.example.org."),
+ this->qtype_, isc::dns::RRType::CNAME(), this->rrttl_,
+ ZoneFinder::CNAME, this->expected_rdatas_,
+ this->expected_sig_rdatas_);
+
+ this->expected_rdatas_.clear();
+ this->expected_sig_rdatas_.clear();
+ this->expected_rdatas_.push_back("www.example.org.");
+ doFindTest(*finder, isc::dns::Name("cname.example.org."),
isc::dns::RRType::CNAME(), isc::dns::RRType::CNAME(),
- isc::dns::RRTTL(3600),
- ZoneFinder::SUCCESS,
- expected_rdatas_, expected_sig_rdatas_);
-
- expected_rdatas_.clear();
- expected_sig_rdatas_.clear();
- doFindTest(finder, isc::dns::Name("doesnotexist.example.org."),
- isc::dns::RRType::A(), isc::dns::RRType::A(),
- isc::dns::RRTTL(3600),
- ZoneFinder::NXDOMAIN,
- expected_rdatas_, expected_sig_rdatas_);
-
- expected_rdatas_.clear();
- expected_sig_rdatas_.clear();
- expected_rdatas_.push_back("192.0.2.1");
- expected_sig_rdatas_.push_back("A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
- expected_sig_rdatas_.push_back("A 5 3 3600 20000101000000 20000201000000 12346 example.org. FAKEFAKEFAKE");
- doFindTest(finder, isc::dns::Name("signed1.example.org."),
- isc::dns::RRType::A(), isc::dns::RRType::A(),
- isc::dns::RRTTL(3600),
- ZoneFinder::SUCCESS,
- expected_rdatas_, expected_sig_rdatas_);
-
- expected_rdatas_.clear();
- expected_sig_rdatas_.clear();
- expected_rdatas_.push_back("2001:db8::1");
- expected_rdatas_.push_back("2001:db8::2");
- expected_sig_rdatas_.push_back("AAAA 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
- doFindTest(finder, isc::dns::Name("signed1.example.org."),
+ this->rrttl_, ZoneFinder::SUCCESS, this->expected_rdatas_,
+ this->expected_sig_rdatas_);
+
+ this->expected_rdatas_.clear();
+ this->expected_sig_rdatas_.clear();
+ doFindTest(*finder, isc::dns::Name("doesnotexist.example.org."),
+ this->qtype_, this->qtype_, this->rrttl_, ZoneFinder::NXDOMAIN,
+ this->expected_rdatas_, this->expected_sig_rdatas_);
+
+ this->expected_rdatas_.clear();
+ this->expected_sig_rdatas_.clear();
+ this->expected_rdatas_.push_back("192.0.2.1");
+ this->expected_sig_rdatas_.push_back("A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
+ this->expected_sig_rdatas_.push_back("A 5 3 3600 20000101000000 20000201000000 12346 example.org. FAKEFAKEFAKE");
+ doFindTest(*finder, isc::dns::Name("signed1.example.org."),
+ this->qtype_, this->qtype_, this->rrttl_, ZoneFinder::SUCCESS,
+ this->expected_rdatas_, this->expected_sig_rdatas_);
+
+ this->expected_rdatas_.clear();
+ this->expected_sig_rdatas_.clear();
+ this->expected_rdatas_.push_back("2001:db8::1");
+ this->expected_rdatas_.push_back("2001:db8::2");
+ this->expected_sig_rdatas_.push_back("AAAA 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
+ doFindTest(*finder, isc::dns::Name("signed1.example.org."),
isc::dns::RRType::AAAA(), isc::dns::RRType::AAAA(),
- isc::dns::RRTTL(3600),
- ZoneFinder::SUCCESS,
- expected_rdatas_, expected_sig_rdatas_);
-
- expected_rdatas_.clear();
- expected_sig_rdatas_.clear();
- doFindTest(finder, isc::dns::Name("signed1.example.org."),
- isc::dns::RRType::TXT(), isc::dns::RRType::TXT(),
- isc::dns::RRTTL(3600),
- ZoneFinder::NXRRSET,
- expected_rdatas_, expected_sig_rdatas_);
-
- expected_rdatas_.clear();
- expected_sig_rdatas_.clear();
- expected_rdatas_.push_back("www.example.org.");
- expected_sig_rdatas_.push_back("CNAME 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
- doFindTest(finder, isc::dns::Name("signedcname1.example.org."),
- isc::dns::RRType::A(), isc::dns::RRType::CNAME(),
- isc::dns::RRTTL(3600),
- ZoneFinder::CNAME,
- expected_rdatas_, expected_sig_rdatas_);
-
- expected_rdatas_.clear();
- expected_sig_rdatas_.clear();
- expected_rdatas_.push_back("192.0.2.1");
- expected_sig_rdatas_.push_back("A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
- expected_sig_rdatas_.push_back("A 5 3 3600 20000101000000 20000201000000 12346 example.org. FAKEFAKEFAKE");
- doFindTest(finder, isc::dns::Name("signed2.example.org."),
- isc::dns::RRType::A(), isc::dns::RRType::A(),
- isc::dns::RRTTL(3600),
- ZoneFinder::SUCCESS,
- expected_rdatas_, expected_sig_rdatas_);
-
- expected_rdatas_.clear();
- expected_sig_rdatas_.clear();
- expected_rdatas_.push_back("2001:db8::2");
- expected_rdatas_.push_back("2001:db8::1");
- expected_sig_rdatas_.push_back("AAAA 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
- doFindTest(finder, isc::dns::Name("signed2.example.org."),
+ this->rrttl_, ZoneFinder::SUCCESS, this->expected_rdatas_,
+ this->expected_sig_rdatas_);
+
+ this->expected_rdatas_.clear();
+ this->expected_sig_rdatas_.clear();
+ doFindTest(*finder, isc::dns::Name("signed1.example.org."),
+ isc::dns::RRType::TXT(), isc::dns::RRType::TXT(), this->rrttl_,
+ ZoneFinder::NXRRSET, this->expected_rdatas_,
+ this->expected_sig_rdatas_);
+
+ this->expected_rdatas_.clear();
+ this->expected_sig_rdatas_.clear();
+ this->expected_rdatas_.push_back("www.example.org.");
+ this->expected_sig_rdatas_.push_back("CNAME 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
+ doFindTest(*finder, isc::dns::Name("signedcname1.example.org."),
+ this->qtype_, isc::dns::RRType::CNAME(), this->rrttl_,
+ ZoneFinder::CNAME, this->expected_rdatas_,
+ this->expected_sig_rdatas_);
+
+ this->expected_rdatas_.clear();
+ this->expected_sig_rdatas_.clear();
+ this->expected_rdatas_.push_back("192.0.2.1");
+ this->expected_sig_rdatas_.push_back("A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
+ this->expected_sig_rdatas_.push_back("A 5 3 3600 20000101000000 20000201000000 12346 example.org. FAKEFAKEFAKE");
+ doFindTest(*finder, isc::dns::Name("signed2.example.org."),
+ this->qtype_, this->qtype_, this->rrttl_, ZoneFinder::SUCCESS,
+ this->expected_rdatas_, this->expected_sig_rdatas_);
+
+ this->expected_rdatas_.clear();
+ this->expected_sig_rdatas_.clear();
+ this->expected_rdatas_.push_back("2001:db8::2");
+ this->expected_rdatas_.push_back("2001:db8::1");
+ this->expected_sig_rdatas_.push_back("AAAA 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
+ doFindTest(*finder, isc::dns::Name("signed2.example.org."),
isc::dns::RRType::AAAA(), isc::dns::RRType::AAAA(),
- isc::dns::RRTTL(3600),
- ZoneFinder::SUCCESS,
- expected_rdatas_, expected_sig_rdatas_);
-
- expected_rdatas_.clear();
- expected_sig_rdatas_.clear();
- doFindTest(finder, isc::dns::Name("signed2.example.org."),
- isc::dns::RRType::TXT(), isc::dns::RRType::TXT(),
- isc::dns::RRTTL(3600),
- ZoneFinder::NXRRSET,
- expected_rdatas_, expected_sig_rdatas_);
-
- expected_rdatas_.clear();
- expected_sig_rdatas_.clear();
- expected_rdatas_.push_back("www.example.org.");
- expected_sig_rdatas_.push_back("CNAME 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
- doFindTest(finder, isc::dns::Name("signedcname2.example.org."),
- isc::dns::RRType::A(), isc::dns::RRType::CNAME(),
- isc::dns::RRTTL(3600),
- ZoneFinder::CNAME,
- expected_rdatas_, expected_sig_rdatas_);
-
-
- expected_rdatas_.clear();
- expected_sig_rdatas_.clear();
- expected_rdatas_.push_back("192.0.2.1");
- expected_sig_rdatas_.push_back("A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
- doFindTest(finder, isc::dns::Name("acnamesig1.example.org."),
- isc::dns::RRType::A(), isc::dns::RRType::A(),
- isc::dns::RRTTL(3600),
- ZoneFinder::SUCCESS,
- expected_rdatas_, expected_sig_rdatas_);
-
- expected_rdatas_.clear();
- expected_sig_rdatas_.clear();
- expected_rdatas_.push_back("192.0.2.1");
- expected_sig_rdatas_.push_back("A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
- doFindTest(finder, isc::dns::Name("acnamesig2.example.org."),
- isc::dns::RRType::A(), isc::dns::RRType::A(),
- isc::dns::RRTTL(3600),
- ZoneFinder::SUCCESS,
- expected_rdatas_, expected_sig_rdatas_);
-
- expected_rdatas_.clear();
- expected_sig_rdatas_.clear();
- expected_rdatas_.push_back("192.0.2.1");
- expected_sig_rdatas_.push_back("A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
- doFindTest(finder, isc::dns::Name("acnamesig3.example.org."),
- isc::dns::RRType::A(), isc::dns::RRType::A(),
- isc::dns::RRTTL(3600),
- ZoneFinder::SUCCESS,
- expected_rdatas_, expected_sig_rdatas_);
-
- expected_rdatas_.clear();
- expected_sig_rdatas_.clear();
- expected_rdatas_.push_back("192.0.2.1");
- expected_rdatas_.push_back("192.0.2.2");
- doFindTest(finder, isc::dns::Name("ttldiff1.example.org."),
- isc::dns::RRType::A(), isc::dns::RRType::A(),
- isc::dns::RRTTL(360),
- ZoneFinder::SUCCESS,
- expected_rdatas_, expected_sig_rdatas_);
-
- expected_rdatas_.clear();
- expected_sig_rdatas_.clear();
- expected_rdatas_.push_back("192.0.2.1");
- expected_rdatas_.push_back("192.0.2.2");
- doFindTest(finder, isc::dns::Name("ttldiff2.example.org."),
- isc::dns::RRType::A(), isc::dns::RRType::A(),
- isc::dns::RRTTL(360),
- ZoneFinder::SUCCESS,
- expected_rdatas_, expected_sig_rdatas_);
-
+ this->rrttl_, ZoneFinder::SUCCESS, this->expected_rdatas_,
+ this->expected_sig_rdatas_);
+
+ this->expected_rdatas_.clear();
+ this->expected_sig_rdatas_.clear();
+ doFindTest(*finder, isc::dns::Name("signed2.example.org."),
+ isc::dns::RRType::TXT(), isc::dns::RRType::TXT(), this->rrttl_,
+ ZoneFinder::NXRRSET, this->expected_rdatas_,
+ this->expected_sig_rdatas_);
+
+ this->expected_rdatas_.clear();
+ this->expected_sig_rdatas_.clear();
+ this->expected_rdatas_.push_back("www.example.org.");
+ this->expected_sig_rdatas_.push_back("CNAME 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
+ doFindTest(*finder, isc::dns::Name("signedcname2.example.org."),
+ this->qtype_, isc::dns::RRType::CNAME(), this->rrttl_,
+ ZoneFinder::CNAME, this->expected_rdatas_,
+ this->expected_sig_rdatas_);
+
+ this->expected_rdatas_.clear();
+ this->expected_sig_rdatas_.clear();
+ this->expected_rdatas_.push_back("192.0.2.1");
+ this->expected_sig_rdatas_.push_back("A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
+ doFindTest(*finder, isc::dns::Name("acnamesig1.example.org."),
+ this->qtype_, this->qtype_, this->rrttl_, ZoneFinder::SUCCESS,
+ this->expected_rdatas_, this->expected_sig_rdatas_);
+
+ this->expected_rdatas_.clear();
+ this->expected_sig_rdatas_.clear();
+ this->expected_rdatas_.push_back("192.0.2.1");
+ this->expected_sig_rdatas_.push_back("A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
+ doFindTest(*finder, isc::dns::Name("acnamesig2.example.org."),
+ this->qtype_, this->qtype_, this->rrttl_, ZoneFinder::SUCCESS,
+ this->expected_rdatas_, this->expected_sig_rdatas_);
+
+ this->expected_rdatas_.clear();
+ this->expected_sig_rdatas_.clear();
+ this->expected_rdatas_.push_back("192.0.2.1");
+ this->expected_sig_rdatas_.push_back("A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
+ doFindTest(*finder, isc::dns::Name("acnamesig3.example.org."),
+ this->qtype_, this->qtype_, this->rrttl_, ZoneFinder::SUCCESS,
+ this->expected_rdatas_, this->expected_sig_rdatas_);
+
+ this->expected_rdatas_.clear();
+ this->expected_sig_rdatas_.clear();
+ this->expected_rdatas_.push_back("192.0.2.1");
+ this->expected_rdatas_.push_back("192.0.2.2");
+ doFindTest(*finder, isc::dns::Name("ttldiff1.example.org."),
+ this->qtype_, this->qtype_, isc::dns::RRTTL(360),
+ ZoneFinder::SUCCESS, this->expected_rdatas_,
+ this->expected_sig_rdatas_);
+
+ this->expected_rdatas_.clear();
+ this->expected_sig_rdatas_.clear();
+ this->expected_rdatas_.push_back("192.0.2.1");
+ this->expected_rdatas_.push_back("192.0.2.2");
+ doFindTest(*finder, isc::dns::Name("ttldiff2.example.org."),
+ this->qtype_, this->qtype_, isc::dns::RRTTL(360),
+ ZoneFinder::SUCCESS, this->expected_rdatas_,
+ this->expected_sig_rdatas_);
EXPECT_THROW(finder->find(isc::dns::Name("badcname1.example.org."),
- isc::dns::RRType::A(),
+ this->qtype_,
NULL, ZoneFinder::FIND_DEFAULT),
DataSourceError);
EXPECT_THROW(finder->find(isc::dns::Name("badcname2.example.org."),
- isc::dns::RRType::A(),
+ this->qtype_,
NULL, ZoneFinder::FIND_DEFAULT),
DataSourceError);
EXPECT_THROW(finder->find(isc::dns::Name("badcname3.example.org."),
- isc::dns::RRType::A(),
+ this->qtype_,
NULL, ZoneFinder::FIND_DEFAULT),
DataSourceError);
EXPECT_THROW(finder->find(isc::dns::Name("badrdata.example.org."),
- isc::dns::RRType::A(),
+ this->qtype_,
NULL, ZoneFinder::FIND_DEFAULT),
DataSourceError);
EXPECT_THROW(finder->find(isc::dns::Name("badtype.example.org."),
- isc::dns::RRType::A(),
+ this->qtype_,
NULL, ZoneFinder::FIND_DEFAULT),
DataSourceError);
EXPECT_THROW(finder->find(isc::dns::Name("badttl.example.org."),
- isc::dns::RRType::A(),
+ this->qtype_,
NULL, ZoneFinder::FIND_DEFAULT),
DataSourceError);
EXPECT_THROW(finder->find(isc::dns::Name("badsig.example.org."),
- isc::dns::RRType::A(),
+ this->qtype_,
NULL, ZoneFinder::FIND_DEFAULT),
DataSourceError);
// Trigger the hardcoded exceptions and see if find() has cleaned up
- EXPECT_THROW(finder->find(isc::dns::Name("dsexception.in.search."),
- isc::dns::RRType::A(),
- NULL, ZoneFinder::FIND_DEFAULT),
- DataSourceError);
- EXPECT_THROW(finder->find(isc::dns::Name("iscexception.in.search."),
- isc::dns::RRType::A(),
- NULL, ZoneFinder::FIND_DEFAULT),
- isc::Exception);
- EXPECT_THROW(finder->find(isc::dns::Name("basicexception.in.search."),
- isc::dns::RRType::A(),
- NULL, ZoneFinder::FIND_DEFAULT),
- std::exception);
-
- EXPECT_THROW(finder->find(isc::dns::Name("dsexception.in.getnext."),
- isc::dns::RRType::A(),
- NULL, ZoneFinder::FIND_DEFAULT),
- DataSourceError);
- EXPECT_THROW(finder->find(isc::dns::Name("iscexception.in.getnext."),
- isc::dns::RRType::A(),
- NULL, ZoneFinder::FIND_DEFAULT),
- isc::Exception);
- EXPECT_THROW(finder->find(isc::dns::Name("basicexception.in.getnext."),
- isc::dns::RRType::A(),
- NULL, ZoneFinder::FIND_DEFAULT),
- std::exception);
+ if (this->is_mock_) {
+ EXPECT_THROW(finder->find(isc::dns::Name("dsexception.in.search."),
+ this->qtype_,
+ NULL, ZoneFinder::FIND_DEFAULT),
+ DataSourceError);
+ EXPECT_THROW(finder->find(isc::dns::Name("iscexception.in.search."),
+ this->qtype_,
+ NULL, ZoneFinder::FIND_DEFAULT),
+ isc::Exception);
+ EXPECT_THROW(finder->find(isc::dns::Name("basicexception.in.search."),
+ this->qtype_,
+ NULL, ZoneFinder::FIND_DEFAULT),
+ std::exception);
+ EXPECT_THROW(finder->find(isc::dns::Name("dsexception.in.getnext."),
+ this->qtype_,
+ NULL, ZoneFinder::FIND_DEFAULT),
+ DataSourceError);
+ EXPECT_THROW(finder->find(isc::dns::Name("iscexception.in.getnext."),
+ this->qtype_,
+ NULL, ZoneFinder::FIND_DEFAULT),
+ isc::Exception);
+ EXPECT_THROW(finder->find(isc::dns::Name("basicexception.in.getnext."),
+ this->qtype_,
+ NULL, ZoneFinder::FIND_DEFAULT),
+ std::exception);
+ }
// This RRSIG has the wrong sigtype field, which should be
// an error if we decide to keep using that field
// Right now the field is ignored, so it does not error
- expected_rdatas_.clear();
- expected_sig_rdatas_.clear();
- expected_rdatas_.push_back("192.0.2.1");
- expected_sig_rdatas_.push_back("A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
- doFindTest(finder, isc::dns::Name("badsigtype.example.org."),
- isc::dns::RRType::A(), isc::dns::RRType::A(),
- isc::dns::RRTTL(3600),
- ZoneFinder::SUCCESS,
- expected_rdatas_, expected_sig_rdatas_);
+ this->expected_rdatas_.clear();
+ this->expected_sig_rdatas_.clear();
+ this->expected_rdatas_.push_back("192.0.2.1");
+ this->expected_sig_rdatas_.push_back("A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
+ doFindTest(*finder, isc::dns::Name("badsigtype.example.org."),
+ this->qtype_, this->qtype_, this->rrttl_, ZoneFinder::SUCCESS,
+ this->expected_rdatas_, this->expected_sig_rdatas_);
}
-TEST_F(DatabaseClientTest, findDelegation) {
- shared_ptr<DatabaseClient::Finder> finder(getFinder());
+TYPED_TEST(DatabaseClientTest, findDelegation) {
+ shared_ptr<DatabaseClient::Finder> finder(this->getFinder());
// The apex should not be considered delegation point and we can access
// data
- expected_rdatas_.clear();
- expected_sig_rdatas_.clear();
- expected_rdatas_.push_back("192.0.2.1");
- doFindTest(finder, isc::dns::Name("example.org."),
- isc::dns::RRType::A(), isc::dns::RRType::A(),
- isc::dns::RRTTL(3600), ZoneFinder::SUCCESS, expected_rdatas_,
- expected_sig_rdatas_);
-
- expected_rdatas_.clear();
- expected_rdatas_.push_back("ns.example.com.");
- expected_sig_rdatas_.push_back("NS 5 3 3600 20000101000000 20000201000000 "
+ this->expected_rdatas_.clear();
+ this->expected_sig_rdatas_.clear();
+ this->expected_rdatas_.push_back("192.0.2.1");
+ doFindTest(*finder, isc::dns::Name("example.org."),
+ this->qtype_, this->qtype_,
+ this->rrttl_, ZoneFinder::SUCCESS, this->expected_rdatas_,
+ this->expected_sig_rdatas_);
+
+ this->expected_rdatas_.clear();
+ this->expected_rdatas_.push_back("ns.example.com.");
+ this->expected_sig_rdatas_.push_back("NS 5 3 3600 20000101000000 20000201000000 "
"12345 example.org. FAKEFAKEFAKE");
- doFindTest(finder, isc::dns::Name("example.org."),
+ doFindTest(*finder, isc::dns::Name("example.org."),
isc::dns::RRType::NS(), isc::dns::RRType::NS(),
- isc::dns::RRTTL(3600), ZoneFinder::SUCCESS, expected_rdatas_,
- expected_sig_rdatas_);
+ this->rrttl_, ZoneFinder::SUCCESS, this->expected_rdatas_,
+ this->expected_sig_rdatas_);
// Check when we ask for something below delegation point, we get the NS
// (Both when the RRset there exists and doesn't)
- expected_rdatas_.clear();
- expected_sig_rdatas_.clear();
- expected_rdatas_.push_back("ns.example.com.");
- expected_rdatas_.push_back("ns.delegation.example.org.");
- expected_sig_rdatas_.push_back("NS 5 3 3600 20000101000000 20000201000000 "
+ this->expected_rdatas_.clear();
+ this->expected_sig_rdatas_.clear();
+ this->expected_rdatas_.push_back("ns.example.com.");
+ this->expected_rdatas_.push_back("ns.delegation.example.org.");
+ this->expected_sig_rdatas_.push_back("NS 5 3 3600 20000101000000 20000201000000 "
"12345 example.org. FAKEFAKEFAKE");
- doFindTest(finder, isc::dns::Name("ns.delegation.example.org."),
- isc::dns::RRType::A(), isc::dns::RRType::NS(),
- isc::dns::RRTTL(3600), ZoneFinder::DELEGATION, expected_rdatas_,
- expected_sig_rdatas_,
+ doFindTest(*finder, isc::dns::Name("ns.delegation.example.org."),
+ this->qtype_, isc::dns::RRType::NS(),
+ this->rrttl_, ZoneFinder::DELEGATION, this->expected_rdatas_,
+ this->expected_sig_rdatas_,
isc::dns::Name("delegation.example.org."));
- doFindTest(finder, isc::dns::Name("ns.delegation.example.org."),
+ doFindTest(*finder, isc::dns::Name("ns.delegation.example.org."),
isc::dns::RRType::AAAA(), isc::dns::RRType::NS(),
- isc::dns::RRTTL(3600), ZoneFinder::DELEGATION, expected_rdatas_,
- expected_sig_rdatas_,
+ this->rrttl_, ZoneFinder::DELEGATION, this->expected_rdatas_,
+ this->expected_sig_rdatas_,
isc::dns::Name("delegation.example.org."));
- doFindTest(finder, isc::dns::Name("deep.below.delegation.example.org."),
+ doFindTest(*finder, isc::dns::Name("deep.below.delegation.example.org."),
isc::dns::RRType::AAAA(), isc::dns::RRType::NS(),
- isc::dns::RRTTL(3600), ZoneFinder::DELEGATION, expected_rdatas_,
- expected_sig_rdatas_,
+ this->rrttl_, ZoneFinder::DELEGATION, this->expected_rdatas_,
+ this->expected_sig_rdatas_,
isc::dns::Name("delegation.example.org."));
// Even when we check directly at the delegation point, we should get
// the NS
- doFindTest(finder, isc::dns::Name("delegation.example.org."),
+ doFindTest(*finder, isc::dns::Name("delegation.example.org."),
isc::dns::RRType::AAAA(), isc::dns::RRType::NS(),
- isc::dns::RRTTL(3600), ZoneFinder::DELEGATION, expected_rdatas_,
- expected_sig_rdatas_);
+ this->rrttl_, ZoneFinder::DELEGATION, this->expected_rdatas_,
+ this->expected_sig_rdatas_);
// And when we ask direcly for the NS, we should still get delegation
- doFindTest(finder, isc::dns::Name("delegation.example.org."),
+ doFindTest(*finder, isc::dns::Name("delegation.example.org."),
isc::dns::RRType::NS(), isc::dns::RRType::NS(),
- isc::dns::RRTTL(3600), ZoneFinder::DELEGATION, expected_rdatas_,
- expected_sig_rdatas_);
+ this->rrttl_, ZoneFinder::DELEGATION, this->expected_rdatas_,
+ this->expected_sig_rdatas_);
// Now test delegation. If it is below the delegation point, we should get
// the DNAME (the one with data under DNAME is invalid zone, but we test
// the behaviour anyway just to make sure)
- expected_rdatas_.clear();
- expected_rdatas_.push_back("dname.example.com.");
- expected_sig_rdatas_.clear();
- expected_sig_rdatas_.push_back("DNAME 5 3 3600 20000101000000 "
+ this->expected_rdatas_.clear();
+ this->expected_rdatas_.push_back("dname.example.com.");
+ this->expected_sig_rdatas_.clear();
+ this->expected_sig_rdatas_.push_back("DNAME 5 3 3600 20000101000000 "
"20000201000000 12345 example.org. "
"FAKEFAKEFAKE");
- doFindTest(finder, isc::dns::Name("below.dname.example.org."),
- isc::dns::RRType::A(), isc::dns::RRType::DNAME(),
- isc::dns::RRTTL(3600), ZoneFinder::DNAME, expected_rdatas_,
- expected_sig_rdatas_, isc::dns::Name("dname.example.org."));
- doFindTest(finder, isc::dns::Name("below.dname.example.org."),
+ doFindTest(*finder, isc::dns::Name("below.dname.example.org."),
+ this->qtype_, isc::dns::RRType::DNAME(),
+ this->rrttl_, ZoneFinder::DNAME, this->expected_rdatas_,
+ this->expected_sig_rdatas_, isc::dns::Name("dname.example.org."));
+ doFindTest(*finder, isc::dns::Name("below.dname.example.org."),
isc::dns::RRType::AAAA(), isc::dns::RRType::DNAME(),
- isc::dns::RRTTL(3600), ZoneFinder::DNAME, expected_rdatas_,
- expected_sig_rdatas_, isc::dns::Name("dname.example.org."));
- doFindTest(finder, isc::dns::Name("really.deep.below.dname.example.org."),
+ this->rrttl_, ZoneFinder::DNAME, this->expected_rdatas_,
+ this->expected_sig_rdatas_, isc::dns::Name("dname.example.org."));
+ doFindTest(*finder, isc::dns::Name("really.deep.below.dname.example.org."),
isc::dns::RRType::AAAA(), isc::dns::RRType::DNAME(),
- isc::dns::RRTTL(3600), ZoneFinder::DNAME, expected_rdatas_,
- expected_sig_rdatas_, isc::dns::Name("dname.example.org."));
+ this->rrttl_, ZoneFinder::DNAME, this->expected_rdatas_,
+ this->expected_sig_rdatas_, isc::dns::Name("dname.example.org."));
// Asking direcly for DNAME should give SUCCESS
- doFindTest(finder, isc::dns::Name("dname.example.org."),
+ doFindTest(*finder, isc::dns::Name("dname.example.org."),
isc::dns::RRType::DNAME(), isc::dns::RRType::DNAME(),
- isc::dns::RRTTL(3600), ZoneFinder::SUCCESS, expected_rdatas_,
- expected_sig_rdatas_);
+ this->rrttl_, ZoneFinder::SUCCESS, this->expected_rdatas_,
+ this->expected_sig_rdatas_);
// But we don't delegate at DNAME point
- expected_rdatas_.clear();
- expected_rdatas_.push_back("192.0.2.1");
- expected_sig_rdatas_.clear();
- doFindTest(finder, isc::dns::Name("dname.example.org."),
- isc::dns::RRType::A(), isc::dns::RRType::A(),
- isc::dns::RRTTL(3600), ZoneFinder::SUCCESS, expected_rdatas_,
- expected_sig_rdatas_);
- expected_rdatas_.clear();
- doFindTest(finder, isc::dns::Name("dname.example.org."),
+ this->expected_rdatas_.clear();
+ this->expected_rdatas_.push_back("192.0.2.1");
+ this->expected_sig_rdatas_.clear();
+ doFindTest(*finder, isc::dns::Name("dname.example.org."),
+ this->qtype_, this->qtype_,
+ this->rrttl_, ZoneFinder::SUCCESS, this->expected_rdatas_,
+ this->expected_sig_rdatas_);
+ this->expected_rdatas_.clear();
+ doFindTest(*finder, isc::dns::Name("dname.example.org."),
isc::dns::RRType::AAAA(), isc::dns::RRType::AAAA(),
- isc::dns::RRTTL(3600), ZoneFinder::NXRRSET, expected_rdatas_,
- expected_sig_rdatas_);
+ this->rrttl_, ZoneFinder::NXRRSET, this->expected_rdatas_,
+ this->expected_sig_rdatas_);
// This is broken dname, it contains two targets
EXPECT_THROW(finder->find(isc::dns::Name("below.baddname.example.org."),
- isc::dns::RRType::A(), NULL,
+ this->qtype_, NULL,
ZoneFinder::FIND_DEFAULT),
DataSourceError);
// Broken NS - it lives together with something else
EXPECT_THROW(finder->find(isc::dns::Name("brokenns1.example.org."),
- isc::dns::RRType::A(), NULL,
+ this->qtype_, NULL,
ZoneFinder::FIND_DEFAULT),
DataSourceError);
EXPECT_THROW(finder->find(isc::dns::Name("brokenns2.example.org."),
- isc::dns::RRType::A(), NULL,
+ this->qtype_, NULL,
ZoneFinder::FIND_DEFAULT),
DataSourceError);
}
-TEST_F(DatabaseClientTest, emptyDomain) {
- shared_ptr<DatabaseClient::Finder> finder(getFinder());
+TYPED_TEST(DatabaseClientTest, emptyDomain) {
+ shared_ptr<DatabaseClient::Finder> finder(this->getFinder());
// This domain doesn't exist, but a subdomain of it does.
// Therefore we should pretend the domain is there, but contains no RRsets
- doFindTest(finder, isc::dns::Name("b.example.org."), isc::dns::RRType::A(),
- isc::dns::RRType::A(), isc::dns::RRTTL(3600),
- ZoneFinder::NXRRSET, expected_rdatas_, expected_sig_rdatas_);
+ doFindTest(*finder, isc::dns::Name("b.example.org."), this->qtype_,
+ this->qtype_, this->rrttl_, ZoneFinder::NXRRSET,
+ this->expected_rdatas_, this->expected_sig_rdatas_);
}
-// Glue-OK mode. Just go trough NS delegations down (but not trough
+// Glue-OK mode. Just go through NS delegations down (but not through
// DNAME) and pretend it is not there.
-TEST_F(DatabaseClientTest, glueOK) {
- shared_ptr<DatabaseClient::Finder> finder(getFinder());
+TYPED_TEST(DatabaseClientTest, glueOK) {
+ shared_ptr<DatabaseClient::Finder> finder(this->getFinder());
- expected_rdatas_.clear();
- expected_sig_rdatas_.clear();
- doFindTest(finder, isc::dns::Name("ns.delegation.example.org."),
+ this->expected_rdatas_.clear();
+ this->expected_sig_rdatas_.clear();
+ doFindTest(*finder, isc::dns::Name("ns.delegation.example.org."),
isc::dns::RRType::AAAA(), isc::dns::RRType::AAAA(),
- isc::dns::RRTTL(3600), ZoneFinder::NXRRSET,
- expected_rdatas_, expected_sig_rdatas_,
+ this->rrttl_, ZoneFinder::NXRRSET,
+ this->expected_rdatas_, this->expected_sig_rdatas_,
isc::dns::Name("ns.delegation.example.org."),
ZoneFinder::FIND_GLUE_OK);
- doFindTest(finder, isc::dns::Name("nothere.delegation.example.org."),
+ doFindTest(*finder, isc::dns::Name("nothere.delegation.example.org."),
isc::dns::RRType::AAAA(), isc::dns::RRType::AAAA(),
- isc::dns::RRTTL(3600), ZoneFinder::NXDOMAIN,
- expected_rdatas_, expected_sig_rdatas_,
+ this->rrttl_, ZoneFinder::NXDOMAIN,
+ this->expected_rdatas_, this->expected_sig_rdatas_,
isc::dns::Name("nothere.delegation.example.org."),
ZoneFinder::FIND_GLUE_OK);
- expected_rdatas_.push_back("192.0.2.1");
- doFindTest(finder, isc::dns::Name("ns.delegation.example.org."),
- isc::dns::RRType::A(), isc::dns::RRType::A(),
- isc::dns::RRTTL(3600), ZoneFinder::SUCCESS,
- expected_rdatas_, expected_sig_rdatas_,
+ this->expected_rdatas_.push_back("192.0.2.1");
+ doFindTest(*finder, isc::dns::Name("ns.delegation.example.org."),
+ this->qtype_, this->qtype_,
+ this->rrttl_, ZoneFinder::SUCCESS,
+ this->expected_rdatas_, this->expected_sig_rdatas_,
isc::dns::Name("ns.delegation.example.org."),
ZoneFinder::FIND_GLUE_OK);
- expected_rdatas_.clear();
- expected_rdatas_.push_back("ns.example.com.");
- expected_rdatas_.push_back("ns.delegation.example.org.");
- expected_sig_rdatas_.clear();
- expected_sig_rdatas_.push_back("NS 5 3 3600 20000101000000 "
+ this->expected_rdatas_.clear();
+ this->expected_rdatas_.push_back("ns.example.com.");
+ this->expected_rdatas_.push_back("ns.delegation.example.org.");
+ this->expected_sig_rdatas_.clear();
+ this->expected_sig_rdatas_.push_back("NS 5 3 3600 20000101000000 "
"20000201000000 12345 example.org. "
"FAKEFAKEFAKE");
// When we request the NS, it should be SUCCESS, not DELEGATION
// (different in GLUE_OK)
- doFindTest(finder, isc::dns::Name("delegation.example.org."),
+ doFindTest(*finder, isc::dns::Name("delegation.example.org."),
isc::dns::RRType::NS(), isc::dns::RRType::NS(),
- isc::dns::RRTTL(3600), ZoneFinder::SUCCESS,
- expected_rdatas_, expected_sig_rdatas_,
+ this->rrttl_, ZoneFinder::SUCCESS,
+ this->expected_rdatas_, this->expected_sig_rdatas_,
isc::dns::Name("delegation.example.org."),
ZoneFinder::FIND_GLUE_OK);
- expected_rdatas_.clear();
- expected_rdatas_.push_back("dname.example.com.");
- expected_sig_rdatas_.clear();
- expected_sig_rdatas_.push_back("DNAME 5 3 3600 20000101000000 "
+ this->expected_rdatas_.clear();
+ this->expected_rdatas_.push_back("dname.example.com.");
+ this->expected_sig_rdatas_.clear();
+ this->expected_sig_rdatas_.push_back("DNAME 5 3 3600 20000101000000 "
"20000201000000 12345 example.org. "
"FAKEFAKEFAKE");
- doFindTest(finder, isc::dns::Name("below.dname.example.org."),
- isc::dns::RRType::A(), isc::dns::RRType::DNAME(),
- isc::dns::RRTTL(3600), ZoneFinder::DNAME, expected_rdatas_,
- expected_sig_rdatas_, isc::dns::Name("dname.example.org."),
- ZoneFinder::FIND_GLUE_OK);
- doFindTest(finder, isc::dns::Name("below.dname.example.org."),
+ doFindTest(*finder, isc::dns::Name("below.dname.example.org."),
+ this->qtype_, isc::dns::RRType::DNAME(),
+ this->rrttl_, ZoneFinder::DNAME, this->expected_rdatas_,
+ this->expected_sig_rdatas_,
+ isc::dns::Name("dname.example.org."), ZoneFinder::FIND_GLUE_OK);
+ doFindTest(*finder, isc::dns::Name("below.dname.example.org."),
isc::dns::RRType::AAAA(), isc::dns::RRType::DNAME(),
- isc::dns::RRTTL(3600), ZoneFinder::DNAME, expected_rdatas_,
- expected_sig_rdatas_, isc::dns::Name("dname.example.org."),
- ZoneFinder::FIND_GLUE_OK);
+ this->rrttl_, ZoneFinder::DNAME, this->expected_rdatas_,
+ this->expected_sig_rdatas_,
+ isc::dns::Name("dname.example.org."), ZoneFinder::FIND_GLUE_OK);
}
-TEST_F(DatabaseClientTest, wildcard) {
- shared_ptr<DatabaseClient::Finder> finder(getFinder());
+TYPED_TEST(DatabaseClientTest, wildcard) {
+ shared_ptr<DatabaseClient::Finder> finder(this->getFinder());
// First, simple wildcard match
// Check also that the RRSIG is added from the wildcard (not modified)
- expected_rdatas_.push_back("192.0.2.5");
- expected_sig_rdatas_.push_back("A 5 3 3600 20000101000000 20000201000000 "
- "12345 example.org. FAKEFAKEFAKE");
- doFindTest(finder, isc::dns::Name("a.wild.example.org"),
- isc::dns::RRType::A(), isc::dns::RRType::A(),
- isc::dns::RRTTL(3600), ZoneFinder::WILDCARD, expected_rdatas_,
- expected_sig_rdatas_);
- doFindTest(finder, isc::dns::Name("b.a.wild.example.org"),
- isc::dns::RRType::A(), isc::dns::RRType::A(),
- isc::dns::RRTTL(3600), ZoneFinder::WILDCARD, expected_rdatas_,
- expected_sig_rdatas_);
- expected_rdatas_.clear();
- expected_sig_rdatas_.clear();
- doFindTest(finder, isc::dns::Name("a.wild.example.org"),
+ this->expected_rdatas_.push_back("192.0.2.5");
+ this->expected_sig_rdatas_.push_back("A 5 3 3600 20000101000000 "
+ "20000201000000 12345 example.org. "
+ "FAKEFAKEFAKE");
+ doFindTest(*finder, isc::dns::Name("a.wild.example.org"),
+ this->qtype_, this->qtype_, this->rrttl_,
+ ZoneFinder::WILDCARD, this->expected_rdatas_,
+ this->expected_sig_rdatas_);
+ doFindTest(*finder, isc::dns::Name("b.a.wild.example.org"),
+ this->qtype_, this->qtype_, this->rrttl_, ZoneFinder::WILDCARD,
+ this->expected_rdatas_, this->expected_sig_rdatas_);
+ this->expected_rdatas_.clear();
+ this->expected_sig_rdatas_.clear();
+ doFindTest(*finder, isc::dns::Name("a.wild.example.org"),
isc::dns::RRType::AAAA(), isc::dns::RRType::AAAA(),
- isc::dns::RRTTL(3600), ZoneFinder::WILDCARD_NXRRSET,
- expected_rdatas_, expected_sig_rdatas_);
- doFindTest(finder, isc::dns::Name("b.a.wild.example.org"),
+ this->rrttl_, ZoneFinder::WILDCARD_NXRRSET,
+ this->expected_rdatas_, this->expected_sig_rdatas_);
+ doFindTest(*finder, isc::dns::Name("b.a.wild.example.org"),
isc::dns::RRType::AAAA(), isc::dns::RRType::AAAA(),
- isc::dns::RRTTL(3600), ZoneFinder::WILDCARD_NXRRSET,
- expected_rdatas_, expected_sig_rdatas_);
-
- // Direct request for thi wildcard
- expected_rdatas_.push_back("192.0.2.5");
- expected_sig_rdatas_.push_back("A 5 3 3600 20000101000000 20000201000000 "
- "12345 example.org. FAKEFAKEFAKE");
- doFindTest(finder, isc::dns::Name("*.wild.example.org"),
- isc::dns::RRType::A(), isc::dns::RRType::A(),
- isc::dns::RRTTL(3600), ZoneFinder::SUCCESS, expected_rdatas_,
- expected_sig_rdatas_);
- expected_rdatas_.clear();
- expected_sig_rdatas_.clear();
- doFindTest(finder, isc::dns::Name("*.wild.example.org"),
+ this->rrttl_, ZoneFinder::WILDCARD_NXRRSET,
+ this->expected_rdatas_, this->expected_sig_rdatas_);
+
+ // Direct request for this wildcard
+ this->expected_rdatas_.push_back("192.0.2.5");
+ this->expected_sig_rdatas_.push_back("A 5 3 3600 20000101000000 "
+ "20000201000000 12345 example.org. "
+ "FAKEFAKEFAKE");
+ doFindTest(*finder, isc::dns::Name("*.wild.example.org"),
+ this->qtype_, this->qtype_, this->rrttl_, ZoneFinder::SUCCESS,
+ this->expected_rdatas_, this->expected_sig_rdatas_);
+ this->expected_rdatas_.clear();
+ this->expected_sig_rdatas_.clear();
+ doFindTest(*finder, isc::dns::Name("*.wild.example.org"),
isc::dns::RRType::AAAA(), isc::dns::RRType::AAAA(),
- isc::dns::RRTTL(3600), ZoneFinder::NXRRSET, expected_rdatas_,
- expected_sig_rdatas_);
+ this->rrttl_, ZoneFinder::NXRRSET, this->expected_rdatas_,
+ this->expected_sig_rdatas_);
// This is nonsense, but check it doesn't match by some stupid accident
- doFindTest(finder, isc::dns::Name("a.*.wild.example.org"),
- isc::dns::RRType::A(), isc::dns::RRType::A(),
- isc::dns::RRTTL(3600), ZoneFinder::NXDOMAIN,
- expected_rdatas_, expected_sig_rdatas_);
+ doFindTest(*finder, isc::dns::Name("a.*.wild.example.org"),
+ this->qtype_, this->qtype_, this->rrttl_, ZoneFinder::NXDOMAIN,
+ this->expected_rdatas_, this->expected_sig_rdatas_);
// These should be canceled, since it is below a domain which exitsts
- doFindTest(finder, isc::dns::Name("nothing.here.wild.example.org"),
- isc::dns::RRType::A(), isc::dns::RRType::A(),
- isc::dns::RRTTL(3600), ZoneFinder::NXDOMAIN,
- expected_rdatas_, expected_sig_rdatas_);
- doFindTest(finder, isc::dns::Name("cancel.here.wild.example.org"),
- isc::dns::RRType::A(), isc::dns::RRType::A(),
- isc::dns::RRTTL(3600), ZoneFinder::NXRRSET,
- expected_rdatas_, expected_sig_rdatas_);
- doFindTest(finder,
+ doFindTest(*finder, isc::dns::Name("nothing.here.wild.example.org"),
+ this->qtype_, this->qtype_, this->rrttl_, ZoneFinder::NXDOMAIN,
+ this->expected_rdatas_, this->expected_sig_rdatas_);
+ doFindTest(*finder, isc::dns::Name("cancel.here.wild.example.org"),
+ this->qtype_, this->qtype_, this->rrttl_, ZoneFinder::NXRRSET,
+ this->expected_rdatas_, this->expected_sig_rdatas_);
+ doFindTest(*finder,
isc::dns::Name("below.cancel.here.wild.example.org"),
- isc::dns::RRType::A(), isc::dns::RRType::A(),
- isc::dns::RRTTL(3600), ZoneFinder::NXDOMAIN,
- expected_rdatas_, expected_sig_rdatas_);
+ this->qtype_, this->qtype_, this->rrttl_, ZoneFinder::NXDOMAIN,
+ this->expected_rdatas_, this->expected_sig_rdatas_);
// And this should be just plain empty non-terminal domain, check
// the wildcard doesn't hurt it
- doFindTest(finder, isc::dns::Name("here.wild.example.org"),
- isc::dns::RRType::A(), isc::dns::RRType::A(),
- isc::dns::RRTTL(3600), ZoneFinder::NXRRSET, expected_rdatas_,
- expected_sig_rdatas_);
+ doFindTest(*finder, isc::dns::Name("here.wild.example.org"),
+ this->qtype_, this->qtype_, this->rrttl_, ZoneFinder::NXRRSET,
+ this->expected_rdatas_, this->expected_sig_rdatas_);
// Also make sure that the wildcard doesn't hurt the original data
// below the wildcard
- expected_rdatas_.push_back("2001:db8::5");
- doFindTest(finder, isc::dns::Name("cancel.here.wild.example.org"),
+ this->expected_rdatas_.push_back("2001:db8::5");
+ doFindTest(*finder, isc::dns::Name("cancel.here.wild.example.org"),
isc::dns::RRType::AAAA(), isc::dns::RRType::AAAA(),
- isc::dns::RRTTL(3600), ZoneFinder::SUCCESS,
- expected_rdatas_, expected_sig_rdatas_);
- expected_rdatas_.clear();
+ this->rrttl_, ZoneFinder::SUCCESS,
+ this->expected_rdatas_, this->expected_sig_rdatas_);
+ this->expected_rdatas_.clear();
// How wildcard go together with delegation
- expected_rdatas_.push_back("ns.example.com.");
- doFindTest(finder, isc::dns::Name("below.delegatedwild.example.org"),
- isc::dns::RRType::A(), isc::dns::RRType::NS(),
- isc::dns::RRTTL(3600), ZoneFinder::DELEGATION, expected_rdatas_,
- expected_sig_rdatas_,
+ this->expected_rdatas_.push_back("ns.example.com.");
+ doFindTest(*finder, isc::dns::Name("below.delegatedwild.example.org"),
+ this->qtype_, isc::dns::RRType::NS(), this->rrttl_,
+ ZoneFinder::DELEGATION, this->expected_rdatas_,
+ this->expected_sig_rdatas_,
isc::dns::Name("delegatedwild.example.org"));
// FIXME: This doesn't look logically OK, GLUE_OK should make it transparent,
// so the match should either work or be canceled, but return NXDOMAIN
- doFindTest(finder, isc::dns::Name("below.delegatedwild.example.org"),
- isc::dns::RRType::A(), isc::dns::RRType::NS(),
- isc::dns::RRTTL(3600), ZoneFinder::DELEGATION, expected_rdatas_,
- expected_sig_rdatas_,
+ doFindTest(*finder, isc::dns::Name("below.delegatedwild.example.org"),
+ this->qtype_, isc::dns::RRType::NS(), this->rrttl_,
+ ZoneFinder::DELEGATION, this->expected_rdatas_,
+ this->expected_sig_rdatas_,
isc::dns::Name("delegatedwild.example.org"),
ZoneFinder::FIND_GLUE_OK);
- expected_rdatas_.clear();
- expected_rdatas_.push_back("192.0.2.5");
+ this->expected_rdatas_.clear();
+ this->expected_rdatas_.push_back("192.0.2.5");
// These are direct matches
const char* positive_names[] = {
"wild.*.foo.example.org.",
@@ -1302,14 +1541,14 @@ TEST_F(DatabaseClientTest, wildcard) {
NULL
};
for (const char** name(positive_names); *name != NULL; ++ name) {
- doFindTest(finder, isc::dns::Name(*name), isc::dns::RRType::A(),
- isc::dns::RRType::A(), isc::dns::RRTTL(3600),
- ZoneFinder::SUCCESS, expected_rdatas_,
- expected_sig_rdatas_);
+ doFindTest(*finder, isc::dns::Name(*name), this->qtype_,
+ this->qtype_, this->rrttl_, ZoneFinder::SUCCESS,
+ this->expected_rdatas_,
+ this->expected_sig_rdatas_);
}
// These are wildcard matches against empty nonterminal asterisk
- expected_rdatas_.clear();
+ this->expected_rdatas_.clear();
const char* negative_names[] = {
"a.foo.example.org.",
"*.foo.example.org.",
@@ -1325,78 +1564,684 @@ TEST_F(DatabaseClientTest, wildcard) {
NULL
};
for (const char** name(negative_names); *name != NULL; ++ name) {
- doFindTest(finder, isc::dns::Name(*name), isc::dns::RRType::A(),
- isc::dns::RRType::A(), isc::dns::RRTTL(3600),
- ZoneFinder::NXRRSET, expected_rdatas_,
- expected_sig_rdatas_);
+ doFindTest(*finder, isc::dns::Name(*name), this->qtype_,
+ this->qtype_, this->rrttl_, ZoneFinder::NXRRSET,
+ this->expected_rdatas_, this->expected_sig_rdatas_);
// FIXME: What should be returned in this case? How does the
// DNSSEC logic handle it?
}
}
-TEST_F(DatabaseClientTest, NXRRSET_NSEC) {
+TYPED_TEST(DatabaseClientTest, NXRRSET_NSEC) {
// The domain exists, but doesn't have this RRType
// So we should get it's NSEC
- shared_ptr<DatabaseClient::Finder> finder(getFinder());
+ shared_ptr<DatabaseClient::Finder> finder(this->getFinder());
- expected_rdatas_.push_back("www2.example.org. A AAAA NSEC RRSIG");
- expected_sig_rdatas_.push_back("NSEC 5 3 3600 20000101000000 "
- "20000201000000 12345 example.org. "
- "FAKEFAKEFAKE");
- doFindTest(finder, isc::dns::Name("www.example.org."),
+ this->expected_rdatas_.push_back("www2.example.org. A AAAA NSEC RRSIG");
+ this->expected_sig_rdatas_.push_back("NSEC 5 3 3600 20000101000000 "
+ "20000201000000 12345 example.org. "
+ "FAKEFAKEFAKE");
+ doFindTest(*finder, isc::dns::Name("www.example.org."),
isc::dns::RRType::TXT(), isc::dns::RRType::NSEC(),
isc::dns::RRTTL(3600),
ZoneFinder::NXRRSET,
- expected_rdatas_, expected_sig_rdatas_, Name::ROOT_NAME(),
- ZoneFinder::FIND_DNSSEC);
+ this->expected_rdatas_, this->expected_sig_rdatas_,
+ Name::ROOT_NAME(), ZoneFinder::FIND_DNSSEC);
}
-TEST_F(DatabaseClientTest, wildcardNXRRSET_NSEC) {
+TYPED_TEST(DatabaseClientTest, wildcardNXRRSET_NSEC) {
// The domain exists, but doesn't have this RRType
// So we should get it's NSEC
//
// The user will have to query us again to get the correct
// answer (eg. prove there's not an exact match)
- shared_ptr<DatabaseClient::Finder> finder(getFinder());
+ shared_ptr<DatabaseClient::Finder> finder(this->getFinder());
- expected_rdatas_.push_back("cancel.here.wild.example.org. A NSEC RRSIG");
- expected_sig_rdatas_.push_back("NSEC 5 3 3600 20000101000000 "
- "20000201000000 12345 example.org. "
- "FAKEFAKEFAKE");
+ this->expected_rdatas_.push_back("cancel.here.wild.example.org. A NSEC "
+ "RRSIG");
+ this->expected_sig_rdatas_.push_back("NSEC 5 3 3600 20000101000000 "
+ "20000201000000 12345 example.org. "
+ "FAKEFAKEFAKE");
// Note that the NSEC name should NOT be synthesized.
- doFindTest(finder, isc::dns::Name("a.wild.example.org."),
+ doFindTest(*finder, isc::dns::Name("a.wild.example.org."),
isc::dns::RRType::TXT(), isc::dns::RRType::NSEC(),
isc::dns::RRTTL(3600),
ZoneFinder::WILDCARD_NXRRSET,
- expected_rdatas_, expected_sig_rdatas_,
+ this->expected_rdatas_, this->expected_sig_rdatas_,
Name("*.wild.example.org"), ZoneFinder::FIND_DNSSEC);
}
-TEST_F(DatabaseClientTest, getOrigin) {
- DataSourceClient::FindResult zone(client_->findZone(Name("example.org")));
+TYPED_TEST(DatabaseClientTest, getOrigin) {
+ DataSourceClient::FindResult
+ zone(this->client_->findZone(Name("example.org")));
ASSERT_EQ(result::SUCCESS, zone.code);
shared_ptr<DatabaseClient::Finder> finder(
dynamic_pointer_cast<DatabaseClient::Finder>(zone.zone_finder));
- EXPECT_EQ(42, finder->zone_id());
- EXPECT_EQ(isc::dns::Name("example.org"), finder->getOrigin());
+ if (this->is_mock_) {
+ EXPECT_EQ(READONLY_ZONE_ID, finder->zone_id());
+ }
+ EXPECT_EQ(this->zname_, finder->getOrigin());
+}
+
+TYPED_TEST(DatabaseClientTest, updaterFinder) {
+ this->updater_ = this->client_->getUpdater(this->zname_, false);
+ ASSERT_TRUE(this->updater_);
+
+ // If this update isn't replacing the zone, the finder should work
+ // just like the normal find() case.
+ if (this->is_mock_) {
+ DatabaseClient::Finder& finder = dynamic_cast<DatabaseClient::Finder&>(
+ this->updater_->getFinder());
+ EXPECT_EQ(WRITABLE_ZONE_ID, finder.zone_id());
+ }
+ this->expected_rdatas_.clear();
+ this->expected_rdatas_.push_back("192.0.2.1");
+ doFindTest(this->updater_->getFinder(), this->qname_,
+ this->qtype_, this->qtype_, this->rrttl_, ZoneFinder::SUCCESS,
+ this->expected_rdatas_, this->empty_rdatas_);
+
+ // When replacing the zone, the updater's finder shouldn't see anything
+ // in the zone until something is added.
+ this->updater_.reset();
+ this->updater_ = this->client_->getUpdater(this->zname_, true);
+ ASSERT_TRUE(this->updater_);
+ if (this->is_mock_) {
+ DatabaseClient::Finder& finder = dynamic_cast<DatabaseClient::Finder&>(
+ this->updater_->getFinder());
+ EXPECT_EQ(WRITABLE_ZONE_ID, finder.zone_id());
+ }
+ doFindTest(this->updater_->getFinder(), this->qname_, this->qtype_,
+ this->qtype_, this->rrttl_, ZoneFinder::NXDOMAIN,
+ this->empty_rdatas_, this->empty_rdatas_);
+}
+
+TYPED_TEST(DatabaseClientTest, flushZone) {
+ // A simple update case: flush the entire zone
+ shared_ptr<DatabaseClient::Finder> finder(this->getFinder());
+
+ // Before update, the name exists.
+ EXPECT_EQ(ZoneFinder::SUCCESS, finder->find(this->qname_,
+ this->qtype_).code);
+
+ // start update in the replace mode. the normal finder should still
+ // be able to see the record, but the updater's finder shouldn't.
+ this->updater_ = this->client_->getUpdater(this->zname_, true);
+ this->setUpdateAccessor();
+ EXPECT_EQ(ZoneFinder::SUCCESS,
+ finder->find(this->qname_, this->qtype_).code);
+ EXPECT_EQ(ZoneFinder::NXDOMAIN,
+ this->updater_->getFinder().find(this->qname_,
+ this->qtype_).code);
+
+ // commit the update. now the normal finder shouldn't see it.
+ this->updater_->commit();
+ EXPECT_EQ(ZoneFinder::NXDOMAIN, finder->find(this->qname_,
+ this->qtype_).code);
+
+ // Check rollback wasn't accidentally performed.
+ EXPECT_FALSE(this->isRollbacked());
+}
+
+TYPED_TEST(DatabaseClientTest, updateCancel) {
+ // similar to the previous test, but destruct the updater before commit.
+
+ ZoneFinderPtr finder = this->client_->findZone(this->zname_).zone_finder;
+ EXPECT_EQ(ZoneFinder::SUCCESS, finder->find(this->qname_,
+ this->qtype_).code);
+
+ this->updater_ = this->client_->getUpdater(this->zname_, true);
+ this->setUpdateAccessor();
+ EXPECT_EQ(ZoneFinder::NXDOMAIN,
+ this->updater_->getFinder().find(this->qname_,
+ this->qtype_).code);
+ // DB should not have been rolled back yet.
+ EXPECT_FALSE(this->isRollbacked());
+ this->updater_.reset(); // destruct without commit
+
+ // reset() should have triggered rollback (although it doesn't affect
+ // anything to the mock accessor implementation except for the result of
+ // isRollbacked())
+ EXPECT_TRUE(this->isRollbacked(true));
+ EXPECT_EQ(ZoneFinder::SUCCESS, finder->find(this->qname_,
+ this->qtype_).code);
+}
+
+TYPED_TEST(DatabaseClientTest, exceptionFromRollback) {
+ this->updater_ = this->client_->getUpdater(this->zname_, true);
+
+ this->rrset_.reset(new RRset(Name("throw.example.org"), this->qclass_,
+ this->qtype_, this->rrttl_));
+ this->rrset_->addRdata(rdata::createRdata(this->rrset_->getType(),
+ this->rrset_->getClass(),
+ "192.0.2.1"));
+ this->updater_->addRRset(*this->rrset_);
+ // destruct without commit. The added name will result in an exception
+ // in the MockAccessor's rollback method. It shouldn't be propagated.
+ EXPECT_NO_THROW(this->updater_.reset());
+}
+
+TYPED_TEST(DatabaseClientTest, duplicateCommit) {
+ // duplicate commit. should result in exception.
+ this->updater_ = this->client_->getUpdater(this->zname_, true);
+ this->updater_->commit();
+ EXPECT_THROW(this->updater_->commit(), DataSourceError);
+}
+
+TYPED_TEST(DatabaseClientTest, addRRsetToNewZone) {
+ // Add a single RRset to a fresh empty zone
+ this->updater_ = this->client_->getUpdater(this->zname_, true);
+ this->updater_->addRRset(*this->rrset_);
+
+ this->expected_rdatas_.clear();
+ this->expected_rdatas_.push_back("192.0.2.2");
+ {
+ SCOPED_TRACE("add RRset");
+ doFindTest(this->updater_->getFinder(), this->qname_, this->qtype_,
+ this->qtype_, this->rrttl_, ZoneFinder::SUCCESS,
+ this->expected_rdatas_, this->empty_rdatas_);
+ }
+
+ // Similar to the previous case, but with RRSIG
+ this->updater_.reset();
+ this->updater_ = this->client_->getUpdater(this->zname_, true);
+ this->updater_->addRRset(*this->rrset_);
+ this->updater_->addRRset(*this->rrsigset_);
+
+ // confirm the expected columns were passed to the accessor (if checkable).
+ const char* const rrsig_added[] = {
+ "www.example.org.", "org.example.www.", "3600", "RRSIG", "A",
+ "A 5 3 0 20000101000000 20000201000000 0 example.org. FAKEFAKEFAKE"
+ };
+ this->checkLastAdded(rrsig_added);
+
+ this->expected_sig_rdatas_.clear();
+ this->expected_sig_rdatas_.push_back(
+ rrsig_added[DatabaseAccessor::ADD_RDATA]);
+ {
+ SCOPED_TRACE("add RRset with RRSIG");
+ doFindTest(this->updater_->getFinder(), this->qname_, this->qtype_,
+ this->qtype_, this->rrttl_, ZoneFinder::SUCCESS,
+ this->expected_rdatas_, this->expected_sig_rdatas_);
+ }
+
+ // Add the non RRSIG RRset again, to see the attempt of adding RRSIG
+ // causes any unexpected effect, in particular, whether the SIGTYPE
+ // field might remain.
+ this->updater_->addRRset(*this->rrset_);
+ const char* const rrset_added[] = {
+ "www.example.org.", "org.example.www.", "3600", "A", "", "192.0.2.2"
+ };
+ this->checkLastAdded(rrset_added);
+}
+
+TYPED_TEST(DatabaseClientTest, addRRsetToCurrentZone) {
+ // Similar to the previous test, but not replacing the existing data.
+ shared_ptr<DatabaseClient::Finder> finder(this->getFinder());
+
+ this->updater_ = this->client_->getUpdater(this->zname_, false);
+ this->updater_->addRRset(*this->rrset_);
+
+ // We should see both old and new data.
+ this->expected_rdatas_.clear();
+ this->expected_rdatas_.push_back("192.0.2.1");
+ this->expected_rdatas_.push_back("192.0.2.2");
+ {
+ SCOPED_TRACE("add RRset");
+ doFindTest(this->updater_->getFinder(), this->qname_, this->qtype_,
+ this->qtype_, this->rrttl_, ZoneFinder::SUCCESS,
+ this->expected_rdatas_, this->empty_rdatas_);
+ }
+ this->updater_->commit();
+ {
+ SCOPED_TRACE("add RRset after commit");
+ doFindTest(*finder, this->qname_, this->qtype_, this->qtype_,
+ this->rrttl_, ZoneFinder::SUCCESS, this->expected_rdatas_,
+ this->empty_rdatas_);
+ }
+}
+
+TYPED_TEST(DatabaseClientTest, addMultipleRRs) {
+ // Similar to the previous case, but the added RRset contains multiple
+ // RRs.
+ this->updater_ = this->client_->getUpdater(this->zname_, false);
+ this->rrset_->addRdata(rdata::createRdata(this->rrset_->getType(),
+ this->rrset_->getClass(),
+ "192.0.2.3"));
+ this->updater_->addRRset(*this->rrset_);
+ this->expected_rdatas_.clear();
+ this->expected_rdatas_.push_back("192.0.2.1");
+ this->expected_rdatas_.push_back("192.0.2.2");
+ this->expected_rdatas_.push_back("192.0.2.3");
+ {
+ SCOPED_TRACE("add multiple RRs");
+ doFindTest(this->updater_->getFinder(), this->qname_, this->qtype_,
+ this->qtype_, this->rrttl_, ZoneFinder::SUCCESS,
+ this->expected_rdatas_, this->empty_rdatas_);
+ }
+}
+
+TYPED_TEST(DatabaseClientTest, addRRsetOfLargerTTL) {
+ // Similar to the previous one, but the TTL of the added RRset is larger
+ // than that of the existing record. The finder should use the smaller
+ // one.
+ this->updater_ = this->client_->getUpdater(this->zname_, false);
+ this->rrset_->setTTL(RRTTL(7200));
+ this->updater_->addRRset(*this->rrset_);
+
+ this->expected_rdatas_.clear();
+ this->expected_rdatas_.push_back("192.0.2.1");
+ this->expected_rdatas_.push_back("192.0.2.2");
+ {
+ SCOPED_TRACE("add RRset of larger TTL");
+ doFindTest(this->updater_->getFinder(), this->qname_, this->qtype_,
+ this->qtype_, this->rrttl_, ZoneFinder::SUCCESS,
+ this->expected_rdatas_, this->empty_rdatas_);
+ }
+}
+
+TYPED_TEST(DatabaseClientTest, addRRsetOfSmallerTTL) {
+ // Similar to the previous one, but the added RRset has a smaller TTL.
+ // The added TTL should be used by the finder.
+ this->updater_ = this->client_->getUpdater(this->zname_, false);
+ this->rrset_->setTTL(RRTTL(1800));
+ this->updater_->addRRset(*this->rrset_);
+
+ this->expected_rdatas_.clear();
+ this->expected_rdatas_.push_back("192.0.2.1");
+ this->expected_rdatas_.push_back("192.0.2.2");
+ {
+ SCOPED_TRACE("add RRset of smaller TTL");
+ doFindTest(this->updater_->getFinder(), this->qname_, this->qtype_,
+ this->qtype_, RRTTL(1800), ZoneFinder::SUCCESS,
+ this->expected_rdatas_, this->empty_rdatas_);
+ }
+}
+
+TYPED_TEST(DatabaseClientTest, addSameRR) {
+ // Add the same RR as that is already in the data source.
+ // Currently the add interface doesn't try to suppress the duplicate,
+ // neither does the finder. We may want to revisit it in future versions.
+
+ this->updater_ = this->client_->getUpdater(this->zname_, false);
+ this->rrset_.reset(new RRset(this->qname_, this->qclass_, this->qtype_,
+ this->rrttl_));
+ this->rrset_->addRdata(rdata::createRdata(this->rrset_->getType(),
+ this->rrset_->getClass(),
+ "192.0.2.1"));
+ this->updater_->addRRset(*this->rrset_);
+ this->expected_rdatas_.clear();
+ this->expected_rdatas_.push_back("192.0.2.1");
+ this->expected_rdatas_.push_back("192.0.2.1");
+ {
+ SCOPED_TRACE("add same RR");
+ doFindTest(this->updater_->getFinder(), this->qname_, this->qtype_,
+ this->qtype_, this->rrttl_, ZoneFinder::SUCCESS,
+ this->expected_rdatas_, this->empty_rdatas_);
+ }
+}
+
+TYPED_TEST(DatabaseClientTest, addDeviantRR) {
+ this->updater_ = this->client_->getUpdater(this->zname_, false);
+
+ // RR class mismatch. This should be detected and rejected.
+ this->rrset_.reset(new RRset(this->qname_, RRClass::CH(), RRType::TXT(),
+ this->rrttl_));
+ this->rrset_->addRdata(rdata::createRdata(this->rrset_->getType(),
+ this->rrset_->getClass(),
+ "test text"));
+ EXPECT_THROW(this->updater_->addRRset(*this->rrset_), DataSourceError);
+
+ // Out-of-zone owner name. At a higher level this should be rejected,
+ // but it doesn't happen in this interface.
+ this->rrset_.reset(new RRset(Name("example.com"), this->qclass_,
+ this->qtype_, this->rrttl_));
+ this->rrset_->addRdata(rdata::createRdata(this->rrset_->getType(),
+ this->rrset_->getClass(),
+ "192.0.2.100"));
+ this->updater_->addRRset(*this->rrset_);
+
+ this->expected_rdatas_.clear();
+ this->expected_rdatas_.push_back("192.0.2.100");
+ {
+ // Note: with the find() implementation being more strict about
+ // zone cuts, this test may fail. Then the test should be updated.
+ SCOPED_TRACE("add out-of-zone RR");
+ doFindTest(this->updater_->getFinder(), Name("example.com"),
+ this->qtype_, this->qtype_, this->rrttl_,
+ ZoneFinder::SUCCESS, this->expected_rdatas_,
+ this->empty_rdatas_);
+ }
+}
+
+TYPED_TEST(DatabaseClientTest, addEmptyRRset) {
+ this->updater_ = this->client_->getUpdater(this->zname_, false);
+ this->rrset_.reset(new RRset(this->qname_, this->qclass_, this->qtype_,
+ this->rrttl_));
+ EXPECT_THROW(this->updater_->addRRset(*this->rrset_), DataSourceError);
+}
+
+TYPED_TEST(DatabaseClientTest, addAfterCommit) {
+ this->updater_ = this->client_->getUpdater(this->zname_, false);
+ this->updater_->commit();
+ EXPECT_THROW(this->updater_->addRRset(*this->rrset_), DataSourceError);
+}
+
+TYPED_TEST(DatabaseClientTest, addRRsetWithRRSIG) {
+ this->updater_ = this->client_->getUpdater(this->zname_, false);
+ this->rrset_->addRRsig(*this->rrsigset_);
+ EXPECT_THROW(this->updater_->addRRset(*this->rrset_), DataSourceError);
}
-TEST_F(DatabaseClientTest, previous) {
- shared_ptr<DatabaseClient::Finder> finder(getFinder());
+TYPED_TEST(DatabaseClientTest, deleteRRset) {
+ shared_ptr<DatabaseClient::Finder> finder(this->getFinder());
+
+ this->rrset_.reset(new RRset(this->qname_, this->qclass_, this->qtype_,
+ this->rrttl_));
+ this->rrset_->addRdata(rdata::createRdata(this->rrset_->getType(),
+ this->rrset_->getClass(),
+ "192.0.2.1"));
+
+ // Delete one RR from an RRset
+ this->updater_ = this->client_->getUpdater(this->zname_, false);
+ this->updater_->deleteRRset(*this->rrset_);
+
+ // Delete the only RR of a name
+ this->rrset_.reset(new RRset(Name("cname.example.org"), this->qclass_,
+ RRType::CNAME(), this->rrttl_));
+ this->rrset_->addRdata(rdata::createRdata(this->rrset_->getType(),
+ this->rrset_->getClass(),
+ "www.example.org"));
+ this->updater_->deleteRRset(*this->rrset_);
+
+ // The this->updater_ finder should immediately see the deleted results.
+ {
+ SCOPED_TRACE("delete RRset");
+ doFindTest(this->updater_->getFinder(), this->qname_, this->qtype_,
+ this->qtype_, this->rrttl_, ZoneFinder::NXRRSET,
+ this->empty_rdatas_, this->empty_rdatas_);
+ doFindTest(this->updater_->getFinder(), Name("cname.example.org"),
+ this->qtype_, this->qtype_, this->rrttl_,
+ ZoneFinder::NXDOMAIN, this->empty_rdatas_,
+ this->empty_rdatas_);
+ }
+
+ // before committing the change, the original finder should see the
+ // original record.
+ {
+ SCOPED_TRACE("delete RRset before commit");
+ this->expected_rdatas_.push_back("192.0.2.1");
+ doFindTest(*finder, this->qname_, this->qtype_, this->qtype_,
+ this->rrttl_, ZoneFinder::SUCCESS, this->expected_rdatas_,
+ this->empty_rdatas_);
+
+ this->expected_rdatas_.clear();
+ this->expected_rdatas_.push_back("www.example.org.");
+ doFindTest(*finder, Name("cname.example.org"), this->qtype_,
+ RRType::CNAME(), this->rrttl_, ZoneFinder::CNAME,
+ this->expected_rdatas_, this->empty_rdatas_);
+ }
+
+ // once committed, the record should be removed from the original finder's
+ // view, too.
+ this->updater_->commit();
+ {
+ SCOPED_TRACE("delete RRset after commit");
+ doFindTest(*finder, this->qname_, this->qtype_, this->qtype_,
+ this->rrttl_, ZoneFinder::NXRRSET, this->empty_rdatas_,
+ this->empty_rdatas_);
+ doFindTest(*finder, Name("cname.example.org"), this->qtype_,
+ this->qtype_, this->rrttl_, ZoneFinder::NXDOMAIN,
+ this->empty_rdatas_, this->empty_rdatas_);
+ }
+}
+
+TYPED_TEST(DatabaseClientTest, deleteRRsetToNXDOMAIN) {
+ // similar to the previous case, but it removes the only record of the
+ // given name. a subsequent find() should result in NXDOMAIN.
+ this->rrset_.reset(new RRset(Name("cname.example.org"), this->qclass_,
+ RRType::CNAME(), this->rrttl_));
+ this->rrset_->addRdata(rdata::createRdata(this->rrset_->getType(),
+ this->rrset_->getClass(),
+ "www.example.org"));
+
+ this->updater_ = this->client_->getUpdater(this->zname_, false);
+ this->updater_->deleteRRset(*this->rrset_);
+ {
+ SCOPED_TRACE("delete RRset to NXDOMAIN");
+ doFindTest(this->updater_->getFinder(), Name("cname.example.org"),
+ this->qtype_, this->qtype_, this->rrttl_,
+ ZoneFinder::NXDOMAIN, this->empty_rdatas_,
+ this->empty_rdatas_);
+ }
+}
+
+TYPED_TEST(DatabaseClientTest, deleteMultipleRRs) {
+ this->rrset_.reset(new RRset(this->qname_, this->qclass_, RRType::AAAA(),
+ this->rrttl_));
+ this->rrset_->addRdata(rdata::createRdata(this->rrset_->getType(),
+ this->rrset_->getClass(),
+ "2001:db8::1"));
+ this->rrset_->addRdata(rdata::createRdata(this->rrset_->getType(),
+ this->rrset_->getClass(),
+ "2001:db8::2"));
+
+ this->updater_ = this->client_->getUpdater(this->zname_, false);
+ this->updater_->deleteRRset(*this->rrset_);
+
+ {
+ SCOPED_TRACE("delete multiple RRs");
+ doFindTest(this->updater_->getFinder(), this->qname_, RRType::AAAA(),
+ this->qtype_, this->rrttl_, ZoneFinder::NXRRSET,
+ this->empty_rdatas_, this->empty_rdatas_);
+ }
+}
+
+TYPED_TEST(DatabaseClientTest, partialDelete) {
+ this->rrset_.reset(new RRset(this->qname_, this->qclass_, RRType::AAAA(),
+ this->rrttl_));
+ this->rrset_->addRdata(rdata::createRdata(this->rrset_->getType(),
+ this->rrset_->getClass(),
+ "2001:db8::1"));
+ // This does not exist in the test data source:
+ this->rrset_->addRdata(rdata::createRdata(this->rrset_->getType(),
+ this->rrset_->getClass(),
+ "2001:db8::3"));
+
+ // deleteRRset should succeed "silently", and subsequent find() should
+ // find the remaining RR.
+ this->updater_ = this->client_->getUpdater(this->zname_, false);
+ this->updater_->deleteRRset(*this->rrset_);
+ {
+ SCOPED_TRACE("partial delete");
+ this->expected_rdatas_.push_back("2001:db8::2");
+ doFindTest(this->updater_->getFinder(), this->qname_, RRType::AAAA(),
+ RRType::AAAA(), this->rrttl_, ZoneFinder::SUCCESS,
+ this->expected_rdatas_, this->empty_rdatas_);
+ }
+}
+
+TYPED_TEST(DatabaseClientTest, deleteNoMatch) {
+ // similar to the previous test, but there's not even a match in the
+ // specified RRset. Essentially there's no difference in the result.
+ this->updater_ = this->client_->getUpdater(this->zname_, false);
+ this->updater_->deleteRRset(*this->rrset_);
+ {
+ SCOPED_TRACE("delete no match");
+ this->expected_rdatas_.push_back("192.0.2.1");
+ doFindTest(this->updater_->getFinder(), this->qname_, this->qtype_,
+ this->qtype_, this->rrttl_, ZoneFinder::SUCCESS,
+ this->expected_rdatas_, this->empty_rdatas_);
+ }
+}
+
+TYPED_TEST(DatabaseClientTest, deleteWithDifferentTTL) {
+ // Our delete interface simply ignores TTL (may change in a future version)
+ this->rrset_.reset(new RRset(this->qname_, this->qclass_, this->qtype_,
+ RRTTL(1800)));
+ this->rrset_->addRdata(rdata::createRdata(this->rrset_->getType(),
+ this->rrset_->getClass(),
+ "192.0.2.1"));
+ this->updater_ = this->client_->getUpdater(this->zname_, false);
+ this->updater_->deleteRRset(*this->rrset_);
+ {
+ SCOPED_TRACE("delete RRset with a different TTL");
+ doFindTest(this->updater_->getFinder(), this->qname_, this->qtype_,
+ this->qtype_, this->rrttl_, ZoneFinder::NXRRSET,
+ this->empty_rdatas_, this->empty_rdatas_);
+ }
+}
+
+TYPED_TEST(DatabaseClientTest, deleteDeviantRR) {
+ this->updater_ = this->client_->getUpdater(this->zname_, false);
+
+ // RR class mismatch. This should be detected and rejected.
+ this->rrset_.reset(new RRset(this->qname_, RRClass::CH(), RRType::TXT(),
+ this->rrttl_));
+ this->rrset_->addRdata(rdata::createRdata(this->rrset_->getType(),
+ this->rrset_->getClass(),
+ "test text"));
+ EXPECT_THROW(this->updater_->deleteRRset(*this->rrset_), DataSourceError);
+
+ // Out-of-zone owner name. At a higher level this should be rejected,
+ // but it doesn't happen in this interface.
+ this->rrset_.reset(new RRset(Name("example.com"), this->qclass_,
+ this->qtype_, this->rrttl_));
+ this->rrset_->addRdata(rdata::createRdata(this->rrset_->getType(),
+ this->rrset_->getClass(),
+ "192.0.2.100"));
+ EXPECT_NO_THROW(this->updater_->deleteRRset(*this->rrset_));
+}
+
+TYPED_TEST(DatabaseClientTest, deleteAfterCommit) {
+ this->updater_ = this->client_->getUpdater(this->zname_, false);
+ this->updater_->commit();
+ EXPECT_THROW(this->updater_->deleteRRset(*this->rrset_), DataSourceError);
+}
+
+TYPED_TEST(DatabaseClientTest, deleteEmptyRRset) {
+ this->updater_ = this->client_->getUpdater(this->zname_, false);
+ this->rrset_.reset(new RRset(this->qname_, this->qclass_, this->qtype_,
+ this->rrttl_));
+ EXPECT_THROW(this->updater_->deleteRRset(*this->rrset_), DataSourceError);
+}
+
+TYPED_TEST(DatabaseClientTest, deleteRRsetWithRRSIG) {
+ this->updater_ = this->client_->getUpdater(this->zname_, false);
+ this->rrset_->addRRsig(*this->rrsigset_);
+ EXPECT_THROW(this->updater_->deleteRRset(*this->rrset_), DataSourceError);
+}
+
+TYPED_TEST(DatabaseClientTest, compoundUpdate) {
+ // This test case performs an arbitrary chosen add/delete operations
+ // in a single update transaction. Essentially there is nothing new to
+ // test here, but there may be some bugs that was overlooked and can
+ // only happen in the compound update scenario, so we test it.
+
+ this->updater_ = this->client_->getUpdater(this->zname_, false);
+
+ // add a new RR to an existing RRset
+ this->updater_->addRRset(*this->rrset_);
+ this->expected_rdatas_.clear();
+ this->expected_rdatas_.push_back("192.0.2.1");
+ this->expected_rdatas_.push_back("192.0.2.2");
+ doFindTest(this->updater_->getFinder(), this->qname_, this->qtype_,
+ this->qtype_, this->rrttl_, ZoneFinder::SUCCESS,
+ this->expected_rdatas_, this->empty_rdatas_);
+
+ // delete an existing RR
+ this->rrset_.reset(new RRset(Name("www.example.org"), this->qclass_,
+ this->qtype_, this->rrttl_));
+ this->rrset_->addRdata(rdata::createRdata(this->rrset_->getType(),
+ this->rrset_->getClass(),
+ "192.0.2.1"));
+ this->updater_->deleteRRset(*this->rrset_);
+ this->expected_rdatas_.clear();
+ this->expected_rdatas_.push_back("192.0.2.2");
+ doFindTest(this->updater_->getFinder(), this->qname_, this->qtype_,
+ this->qtype_, this->rrttl_, ZoneFinder::SUCCESS,
+ this->expected_rdatas_, this->empty_rdatas_);
+
+ // re-add it
+ this->updater_->addRRset(*this->rrset_);
+ this->expected_rdatas_.push_back("192.0.2.1");
+ doFindTest(this->updater_->getFinder(), this->qname_, this->qtype_,
+ this->qtype_, this->rrttl_, ZoneFinder::SUCCESS,
+ this->expected_rdatas_, this->empty_rdatas_);
+
+ // add a new RR with a new name
+ const Name newname("newname.example.org");
+ const RRType newtype(RRType::AAAA());
+ doFindTest(this->updater_->getFinder(), newname, newtype, newtype,
+ this->rrttl_, ZoneFinder::NXDOMAIN, this->empty_rdatas_,
+ this->empty_rdatas_);
+ this->rrset_.reset(new RRset(newname, this->qclass_, newtype,
+ this->rrttl_));
+ this->rrset_->addRdata(rdata::createRdata(this->rrset_->getType(),
+ this->rrset_->getClass(),
+ "2001:db8::10"));
+ this->rrset_->addRdata(rdata::createRdata(this->rrset_->getType(),
+ this->rrset_->getClass(),
+ "2001:db8::11"));
+ this->updater_->addRRset(*this->rrset_);
+ this->expected_rdatas_.clear();
+ this->expected_rdatas_.push_back("2001:db8::10");
+ this->expected_rdatas_.push_back("2001:db8::11");
+ doFindTest(this->updater_->getFinder(), newname, newtype, newtype,
+ this->rrttl_, ZoneFinder::SUCCESS, this->expected_rdatas_,
+ this->empty_rdatas_);
+
+ // delete one RR from the previous set
+ this->rrset_.reset(new RRset(newname, this->qclass_, newtype,
+ this->rrttl_));
+ this->rrset_->addRdata(rdata::createRdata(this->rrset_->getType(),
+ this->rrset_->getClass(),
+ "2001:db8::11"));
+ this->updater_->deleteRRset(*this->rrset_);
+ this->expected_rdatas_.clear();
+ this->expected_rdatas_.push_back("2001:db8::10");
+ doFindTest(this->updater_->getFinder(), newname, newtype, newtype,
+ this->rrttl_, ZoneFinder::SUCCESS, this->expected_rdatas_,
+ this->empty_rdatas_);
+
+ // Commit the changes, confirm the entire changes applied.
+ this->updater_->commit();
+ shared_ptr<DatabaseClient::Finder> finder(this->getFinder());
+ this->expected_rdatas_.clear();
+ this->expected_rdatas_.push_back("192.0.2.2");
+ this->expected_rdatas_.push_back("192.0.2.1");
+ doFindTest(*finder, this->qname_, this->qtype_, this->qtype_, this->rrttl_,
+ ZoneFinder::SUCCESS, this->expected_rdatas_,
+ this->empty_rdatas_);
+
+ this->expected_rdatas_.clear();
+ this->expected_rdatas_.push_back("2001:db8::10");
+ doFindTest(*finder, newname, newtype, newtype, this->rrttl_,
+ ZoneFinder::SUCCESS, this->expected_rdatas_,
+ this->empty_rdatas_);
+}
+
+TYPED_TEST(DatabaseClientTest, previous) {
+ shared_ptr<DatabaseClient::Finder> finder(this->getFinder());
EXPECT_EQ(Name("www.example.org."),
finder->findPreviousName(Name("www2.example.org.")));
// Check wrap around
EXPECT_EQ(Name("zzz.example.org."),
finder->findPreviousName(Name("example.org.")));
- // Check it doesn't crash or anything if the underlying DB throws
- DataSourceClient::FindResult
- zone(client_->findZone(Name("bad.example.org")));
- finder = dynamic_pointer_cast<DatabaseClient::Finder>(zone.zone_finder);
-
- EXPECT_THROW(finder->findPreviousName(Name("bad.example.org")),
- isc::NotImplemented);
+ // Check a name that doesn't exist there
+ EXPECT_EQ(Name("www.example.org."),
+ finder->findPreviousName(Name("www1.example.org.")));
+ if (this->is_mock_) { // We can't really force the DB to throw
+ // Check it doesn't crash or anything if the underlying DB throws
+ DataSourceClient::FindResult
+ zone(this->client_->findZone(Name("bad.example.org")));
+ finder =
+ dynamic_pointer_cast<DatabaseClient::Finder>(zone.zone_finder);
+
+ EXPECT_THROW(finder->findPreviousName(Name("bad.example.org")),
+ isc::NotImplemented);
+ }
}
}
diff --git a/src/lib/datasrc/tests/memory_datasrc_unittest.cc b/src/lib/datasrc/tests/memory_datasrc_unittest.cc
index 715b18e..2b854db 100644
--- a/src/lib/datasrc/tests/memory_datasrc_unittest.cc
+++ b/src/lib/datasrc/tests/memory_datasrc_unittest.cc
@@ -197,6 +197,11 @@ TEST_F(InMemoryClientTest, getZoneCount) {
EXPECT_EQ(2, memory_client.getZoneCount());
}
+TEST_F(InMemoryClientTest, startUpdateZone) {
+ EXPECT_THROW(memory_client.getUpdater(Name("example.org"), false),
+ isc::NotImplemented);
+}
+
// A helper callback of masterLoad() used in InMemoryZoneFinderTest.
void
setRRset(RRsetPtr rrset, vector<RRsetPtr*>::iterator& it) {
@@ -1105,5 +1110,4 @@ TEST_F(InMemoryZoneFinderTest, getFileName) {
EXPECT_EQ(TEST_DATA_DIR "/root.zone", zone_finder_.getFileName());
EXPECT_TRUE(rootzone.getFileName().empty());
}
-
}
diff --git a/src/lib/datasrc/tests/sqlite3_accessor_unittest.cc b/src/lib/datasrc/tests/sqlite3_accessor_unittest.cc
index b91f3aa..afc1638 100644
--- a/src/lib/datasrc/tests/sqlite3_accessor_unittest.cc
+++ b/src/lib/datasrc/tests/sqlite3_accessor_unittest.cc
@@ -11,6 +11,10 @@
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
+
+#include <algorithm>
+#include <vector>
+
#include <datasrc/sqlite3_accessor.h>
#include <datasrc/data_source.h>
@@ -22,7 +26,9 @@
#include <fstream>
#include <sqlite3.h>
+using namespace std;
using namespace isc::datasrc;
+using boost::shared_ptr;
using isc::data::ConstElementPtr;
using isc::data::Element;
using isc::dns::RRClass;
@@ -47,82 +53,81 @@ std::string SQLITE_DBFILE_NOTEXIST = TEST_DATA_DIR "/nodir/notexist";
// new db file, we don't need this to be a std::string, and given the
// raw calls we use it in a const char* is more convenient
-const char* SQLITE_NEW_DBFILE = TEST_DATA_BUILD_DIR "/newdb.sqlite3";
+const char* SQLITE_NEW_DBFILE = TEST_DATA_BUILDDIR "/newdb.sqlite3";
// Opening works (the content is tested in different tests)
TEST(SQLite3Open, common) {
- EXPECT_NO_THROW(SQLite3Database db(SQLITE_DBFILE_EXAMPLE,
- RRClass::IN()));
+ EXPECT_NO_THROW(SQLite3Accessor accessor(SQLITE_DBFILE_EXAMPLE,
+ RRClass::IN()));
}
// The file can't be opened
TEST(SQLite3Open, notExist) {
- EXPECT_THROW(SQLite3Database db(SQLITE_DBFILE_NOTEXIST,
- RRClass::IN()), SQLite3Error);
+ EXPECT_THROW(SQLite3Accessor accessor(SQLITE_DBFILE_NOTEXIST,
+ RRClass::IN()), SQLite3Error);
}
// It rejects broken DB
TEST(SQLite3Open, brokenDB) {
- EXPECT_THROW(SQLite3Database db(SQLITE_DBFILE_BROKENDB,
- RRClass::IN()), SQLite3Error);
+ EXPECT_THROW(SQLite3Accessor accessor(SQLITE_DBFILE_BROKENDB,
+ RRClass::IN()), SQLite3Error);
}
// Test we can create the schema on the fly
TEST(SQLite3Open, memoryDB) {
- EXPECT_NO_THROW(SQLite3Database db(SQLITE_DBFILE_MEMORY,
- RRClass::IN()));
+ EXPECT_NO_THROW(SQLite3Accessor accessor(SQLITE_DBFILE_MEMORY,
+ RRClass::IN()));
}
// Test fixture for querying the db
-class SQLite3Access : public ::testing::Test {
+class SQLite3AccessorTest : public ::testing::Test {
public:
- SQLite3Access() {
+ SQLite3AccessorTest() {
initAccessor(SQLITE_DBFILE_EXAMPLE, RRClass::IN());
}
// So it can be re-created with different data
void initAccessor(const std::string& filename, const RRClass& rrclass) {
- db.reset(new SQLite3Database(filename, rrclass));
+ accessor.reset(new SQLite3Accessor(filename, rrclass));
}
- // The tested db
- boost::shared_ptr<SQLite3Database> db;
+ // The tested accessor
+ boost::shared_ptr<SQLite3Accessor> accessor;
};
// This zone exists in the data, so it should be found
-TEST_F(SQLite3Access, getZone) {
- std::pair<bool, int> result(db->getZone("example.com."));
+TEST_F(SQLite3AccessorTest, getZone) {
+ std::pair<bool, int> result(accessor->getZone("example.com."));
EXPECT_TRUE(result.first);
EXPECT_EQ(1, result.second);
}
// But it should find only the zone, nothing below it
-TEST_F(SQLite3Access, subZone) {
- EXPECT_FALSE(db->getZone("sub.example.com.").first);
+TEST_F(SQLite3AccessorTest, subZone) {
+ EXPECT_FALSE(accessor->getZone("sub.example.com.").first);
}
// This zone is not there at all
-TEST_F(SQLite3Access, noZone) {
- EXPECT_FALSE(db->getZone("example.org.").first);
+TEST_F(SQLite3AccessorTest, noZone) {
+ EXPECT_FALSE(accessor->getZone("example.org.").first);
}
// This zone is there, but in different class
-TEST_F(SQLite3Access, noClass) {
+TEST_F(SQLite3AccessorTest, noClass) {
initAccessor(SQLITE_DBFILE_EXAMPLE, RRClass::CH());
- EXPECT_FALSE(db->getZone("example.com.").first);
+ EXPECT_FALSE(accessor->getZone("example.com.").first);
}
// This tests the iterator context
-TEST_F(SQLite3Access, iterator) {
+TEST_F(SQLite3AccessorTest, iterator) {
// Our test zone is conveniently small, but not empty
initAccessor(SQLITE_DBFILE_EXAMPLE_ORG, RRClass::IN());
- const std::pair<bool, int> zone_info(db->getZone("example.org."));
+ const std::pair<bool, int> zone_info(accessor->getZone("example.org."));
ASSERT_TRUE(zone_info.first);
// Get the iterator context
DatabaseAccessor::IteratorContextPtr
- context(db->getAllRecords(zone_info.second));
- ASSERT_NE(DatabaseAccessor::IteratorContextPtr(),
- context);
+ context(accessor->getAllRecords(zone_info.second));
+ ASSERT_NE(DatabaseAccessor::IteratorContextPtr(), context);
std::string data[DatabaseAccessor::COLUMN_COUNT];
// Get and check the first and only record
@@ -202,13 +207,13 @@ TEST_F(SQLite3Access, iterator) {
}
TEST(SQLite3Open, getDBNameExample2) {
- SQLite3Database db(SQLITE_DBFILE_EXAMPLE2, RRClass::IN());
- EXPECT_EQ(SQLITE_DBNAME_EXAMPLE2, db.getDBName());
+ SQLite3Accessor accessor(SQLITE_DBFILE_EXAMPLE2, RRClass::IN());
+ EXPECT_EQ(SQLITE_DBNAME_EXAMPLE2, accessor.getDBName());
}
TEST(SQLite3Open, getDBNameExampleROOT) {
- SQLite3Database db(SQLITE_DBFILE_EXAMPLE_ROOT, RRClass::IN());
- EXPECT_EQ(SQLITE_DBNAME_EXAMPLE_ROOT, db.getDBName());
+ SQLite3Accessor accessor(SQLITE_DBFILE_EXAMPLE_ROOT, RRClass::IN());
+ EXPECT_EQ(SQLITE_DBNAME_EXAMPLE_ROOT, accessor.getDBName());
}
// Simple function to cound the number of records for
@@ -228,8 +233,8 @@ checkRecordRow(const std::string columns[],
EXPECT_EQ(field4, columns[DatabaseAccessor::NAME_COLUMN]);
}
-TEST_F(SQLite3Access, getRecords) {
- const std::pair<bool, int> zone_info(db->getZone("example.com."));
+TEST_F(SQLite3AccessorTest, getRecords) {
+ const std::pair<bool, int> zone_info(accessor->getZone("example.com."));
ASSERT_TRUE(zone_info.first);
const int zone_id = zone_info.second;
@@ -238,14 +243,14 @@ TEST_F(SQLite3Access, getRecords) {
std::string columns[DatabaseAccessor::COLUMN_COUNT];
DatabaseAccessor::IteratorContextPtr
- context(db->getRecords("foo.bar", 1));
+ context(accessor->getRecords("foo.bar", 1));
ASSERT_NE(DatabaseAccessor::IteratorContextPtr(),
context);
EXPECT_FALSE(context->getNext(columns));
checkRecordRow(columns, "", "", "", "", "");
// now try some real searches
- context = db->getRecords("foo.example.com.", zone_id);
+ context = accessor->getRecords("foo.example.com.", zone_id);
ASSERT_TRUE(context->getNext(columns));
checkRecordRow(columns, "CNAME", "3600", "",
"cnametest.example.org.", "");
@@ -261,12 +266,13 @@ TEST_F(SQLite3Access, getRecords) {
"NSEC 5 3 7200 20100322084538 20100220084538 33495 "
"example.com. FAKEFAKEFAKEFAKE", "");
EXPECT_FALSE(context->getNext(columns));
+
// with no more records, the array should not have been modified
checkRecordRow(columns, "RRSIG", "7200", "NSEC",
"NSEC 5 3 7200 20100322084538 20100220084538 33495 "
"example.com. FAKEFAKEFAKEFAKE", "");
- context = db->getRecords("example.com.", zone_id);
+ context = accessor->getRecords("example.com.", zone_id);
ASSERT_TRUE(context->getNext(columns));
checkRecordRow(columns, "SOA", "3600", "",
"master.example.com. admin.example.com. "
@@ -336,15 +342,34 @@ TEST_F(SQLite3Access, getRecords) {
// Try searching for subdomain
// There's foo.bar.example.com in the data
- context = db->getRecords("bar.example.com.", zone_id, true);
+ context = accessor->getRecords("bar.example.com.", zone_id, true);
ASSERT_TRUE(context->getNext(columns));
checkRecordRow(columns, "A", "3600", "", "192.0.2.1", "");
EXPECT_FALSE(context->getNext(columns));
// But we shouldn't match mix.example.com here
- context = db->getRecords("ix.example.com.", zone_id, true);
+ context = accessor->getRecords("ix.example.com.", zone_id, true);
EXPECT_FALSE(context->getNext(columns));
}
+TEST_F(SQLite3AccessorTest, findPrevious) {
+ EXPECT_EQ("dns01.example.com.",
+ accessor->findPreviousName(1, "com.example.dns02."));
+ // A name that doesn't exist
+ EXPECT_EQ("dns01.example.com.",
+ accessor->findPreviousName(1, "com.example.dns01x."));
+ // Wrap around
+ EXPECT_EQ("www.example.com.",
+ accessor->findPreviousName(1, "com.example."));
+}
+
+TEST_F(SQLite3AccessorTest, findPreviousNoData) {
+ // This one doesn't hold any NSEC records, so it shouldn't work
+ // The underlying DB/data don't support DNSSEC, so it's not implemented
+ // (does it make sense? Or different exception here?)
+ EXPECT_THROW(accessor->findPreviousName(3, "com.example."),
+ isc::NotImplemented);
+}
+
// Test fixture for creating a db that automatically deletes it before start,
// and when done
class SQLite3Create : public ::testing::Test {
@@ -358,36 +383,35 @@ public:
}
};
-bool exists(const char* filename) {
- std::ifstream f(filename);
- return (f != NULL);
+bool isReadable(const char* filename) {
+ return (std::ifstream(filename).is_open());
}
TEST_F(SQLite3Create, creationtest) {
- ASSERT_FALSE(exists(SQLITE_NEW_DBFILE));
+ ASSERT_FALSE(isReadable(SQLITE_NEW_DBFILE));
// Should simply be created
- SQLite3Database db(SQLITE_NEW_DBFILE, RRClass::IN());
- ASSERT_TRUE(exists(SQLITE_NEW_DBFILE));
+ SQLite3Accessor accessor(SQLITE_NEW_DBFILE, RRClass::IN());
+ ASSERT_TRUE(isReadable(SQLITE_NEW_DBFILE));
}
TEST_F(SQLite3Create, emptytest) {
- ASSERT_FALSE(exists(SQLITE_NEW_DBFILE));
+ ASSERT_FALSE(isReadable(SQLITE_NEW_DBFILE));
// open one manualle
sqlite3* db;
ASSERT_EQ(SQLITE_OK, sqlite3_open(SQLITE_NEW_DBFILE, &db));
// empty, but not locked, so creating it now should work
- SQLite3Database db2(SQLITE_NEW_DBFILE, RRClass::IN());
+ SQLite3Accessor accessor2(SQLITE_NEW_DBFILE, RRClass::IN());
sqlite3_close(db);
// should work now that we closed it
- SQLite3Database db3(SQLITE_NEW_DBFILE, RRClass::IN());
+ SQLite3Accessor accessor3(SQLITE_NEW_DBFILE, RRClass::IN());
}
TEST_F(SQLite3Create, lockedtest) {
- ASSERT_FALSE(exists(SQLITE_NEW_DBFILE));
+ ASSERT_FALSE(isReadable(SQLITE_NEW_DBFILE));
// open one manually
sqlite3* db;
@@ -395,13 +419,335 @@ TEST_F(SQLite3Create, lockedtest) {
sqlite3_exec(db, "BEGIN EXCLUSIVE TRANSACTION", NULL, NULL, NULL);
// should not be able to open it
- EXPECT_THROW(SQLite3Database db2(SQLITE_NEW_DBFILE, RRClass::IN()),
+ EXPECT_THROW(SQLite3Accessor accessor2(SQLITE_NEW_DBFILE, RRClass::IN()),
SQLite3Error);
sqlite3_exec(db, "ROLLBACK TRANSACTION", NULL, NULL, NULL);
// should work now that we closed it
- SQLite3Database db3(SQLITE_NEW_DBFILE, RRClass::IN());
+ SQLite3Accessor accessor3(SQLITE_NEW_DBFILE, RRClass::IN());
+}
+
+TEST_F(SQLite3AccessorTest, clone) {
+ shared_ptr<DatabaseAccessor> cloned = accessor->clone();
+ EXPECT_EQ(accessor->getDBName(), cloned->getDBName());
+
+ // The cloned accessor should have a separate connection and search
+ // context, so it should be able to perform search in concurrent with
+ // the original accessor.
+ string columns1[DatabaseAccessor::COLUMN_COUNT];
+ string columns2[DatabaseAccessor::COLUMN_COUNT];
+
+ const std::pair<bool, int> zone_info1(
+ accessor->getZone("example.com."));
+ DatabaseAccessor::IteratorContextPtr iterator1 =
+ accessor->getRecords("foo.example.com.", zone_info1.second);
+ const std::pair<bool, int> zone_info2(
+ accessor->getZone("example.com."));
+ DatabaseAccessor::IteratorContextPtr iterator2 =
+ cloned->getRecords("foo.example.com.", zone_info2.second);
+
+ ASSERT_TRUE(iterator1->getNext(columns1));
+ checkRecordRow(columns1, "CNAME", "3600", "", "cnametest.example.org.",
+ "");
+
+ ASSERT_TRUE(iterator2->getNext(columns2));
+ checkRecordRow(columns2, "CNAME", "3600", "", "cnametest.example.org.",
+ "");
+}
+
+//
+// Commonly used data for update tests
+//
+const char* const common_expected_data[] = {
+ // Test record already stored in the tested sqlite3 DB file.
+ "foo.bar.example.com.", "com.example.bar.foo.", "3600", "A", "",
+ "192.0.2.1"
+};
+const char* const new_data[] = {
+ // Newly added data commonly used by some of the tests below
+ "newdata.example.com.", "com.example.newdata.", "3600", "A", "",
+ "192.0.2.1"
+};
+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"
+};
+
+class SQLite3Update : public SQLite3AccessorTest {
+protected:
+ SQLite3Update() {
+ // Note: if "installing" the test file fails some of the subsequent
+ // tests would fail.
+ const char *install_cmd = INSTALL_PROG " " TEST_DATA_DIR
+ "/test.sqlite3 " TEST_DATA_BUILDDIR
+ "/test.sqlite3.copied";
+ if (system(install_cmd) != 0) {
+ // any exception will do, this is failure in test setup, but nice
+ // to show the command that fails, and shouldn't be caught
+ isc_throw(isc::Exception,
+ "Error setting up; command failed: " << install_cmd);
+ };
+ initAccessor(TEST_DATA_BUILDDIR "/test.sqlite3.copied", RRClass::IN());
+ zone_id = accessor->getZone("example.com.").second;
+ another_accessor.reset(new SQLite3Accessor(
+ TEST_DATA_BUILDDIR "/test.sqlite3.copied",
+ RRClass::IN()));
+ expected_stored.push_back(common_expected_data);
+ }
+
+ int zone_id;
+ std::string get_columns[DatabaseAccessor::COLUMN_COUNT];
+ std::string add_columns[DatabaseAccessor::ADD_COLUMN_COUNT];
+ std::string del_params[DatabaseAccessor::DEL_PARAM_COUNT];
+
+ vector<const char* const*> expected_stored; // placeholder for checkRecords
+ vector<const char* const*> empty_stored; // indicate no corresponding data
+
+ // Another accessor, emulating one running on a different process/thread
+ shared_ptr<SQLite3Accessor> another_accessor;
+ DatabaseAccessor::IteratorContextPtr iterator;
+};
+
+void
+checkRecords(SQLite3Accessor& accessor, int zone_id, const std::string& name,
+ vector<const char* const*> expected_rows)
+{
+ DatabaseAccessor::IteratorContextPtr iterator =
+ accessor.getRecords(name, 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)[3], (*it)[2], (*it)[4], (*it)[5], "");
+ ++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.
+
+ checkRecords(*accessor, zone_id, "foo.bar.example.com.", expected_stored);
+ zone_id = accessor->startUpdateZone("example.com.", false).second;
+ checkRecords(*accessor, zone_id, "foo.bar.example.com.", expected_stored);
+ accessor->commitUpdateZone();
+ checkRecords(*accessor, zone_id, "foo.bar.example.com.", expected_stored);
+}
+
+TEST_F(SQLite3Update, flushZone) {
+ // With 'replace' being true startUpdateZone() will flush the existing
+ // zone content.
+
+ checkRecords(*accessor, zone_id, "foo.bar.example.com.", expected_stored);
+ zone_id = accessor->startUpdateZone("example.com.", true).second;
+ checkRecords(*accessor, zone_id, "foo.bar.example.com.", empty_stored);
+ accessor->commitUpdateZone();
+ checkRecords(*accessor, zone_id, "foo.bar.example.com.", empty_stored);
+}
+
+TEST_F(SQLite3Update, readWhileUpdate) {
+ zone_id = accessor->startUpdateZone("example.com.", true).second;
+ checkRecords(*accessor, zone_id, "foo.bar.example.com.", empty_stored);
+
+ // Until commit is done, the other accessor should see the old data
+ checkRecords(*another_accessor, zone_id, "foo.bar.example.com.",
+ expected_stored);
+
+ // Once the changes are committed, the other accessor will see the new
+ // data.
+ accessor->commitUpdateZone();
+ checkRecords(*another_accessor, zone_id, "foo.bar.example.com.",
+ empty_stored);
+}
+
+TEST_F(SQLite3Update, rollback) {
+ zone_id = accessor->startUpdateZone("example.com.", true).second;
+ checkRecords(*accessor, zone_id, "foo.bar.example.com.", empty_stored);
+
+ // Rollback will revert the change made by startUpdateZone(, true).
+ accessor->rollbackUpdateZone();
+ checkRecords(*accessor, zone_id, "foo.bar.example.com.", expected_stored);
}
+TEST_F(SQLite3Update, rollbackFailure) {
+ // This test emulates a rare scenario of making rollback attempt fail.
+ // The iterator is paused in the middle of getting records, which prevents
+ // the rollback operation at the end of the test.
+
+ string columns[DatabaseAccessor::COLUMN_COUNT];
+ iterator = accessor->getRecords("example.com.", zone_id);
+ EXPECT_TRUE(iterator->getNext(columns));
+
+ accessor->startUpdateZone("example.com.", true);
+ EXPECT_THROW(accessor->rollbackUpdateZone(), DataSourceError);
+}
+
+TEST_F(SQLite3Update, commitConflict) {
+ // Start reading the DB by another accessor. We should stop at a single
+ // call to getNextRecord() to keep holding the lock.
+ iterator = another_accessor->getRecords("foo.example.com.", zone_id);
+ EXPECT_TRUE(iterator->getNext(get_columns));
+
+ // Due to getNextRecord() above, the other accessor holds a DB lock,
+ // which will prevent commit.
+ zone_id = accessor->startUpdateZone("example.com.", true).second;
+ checkRecords(*accessor, zone_id, "foo.bar.example.com.", empty_stored);
+ EXPECT_THROW(accessor->commitUpdateZone(), DataSourceError);
+ accessor->rollbackUpdateZone(); // rollback should still succeed
+
+ checkRecords(*accessor, zone_id, "foo.bar.example.com.", expected_stored);
+}
+
+TEST_F(SQLite3Update, updateConflict) {
+ // Similar to the previous case, but this is a conflict with another
+ // update attempt. Note that these two accessors modify disjoint sets
+ // of data; sqlite3 only has a coarse-grained lock so we cannot allow
+ // these updates to run concurrently.
+ EXPECT_TRUE(another_accessor->startUpdateZone("sql1.example.com.",
+ true).first);
+ EXPECT_THROW(accessor->startUpdateZone("example.com.", true),
+ DataSourceError);
+ checkRecords(*accessor, zone_id, "foo.bar.example.com.", expected_stored);
+
+ // Once we rollback the other attempt of change, we should be able to
+ // start and commit the transaction using the main accessor.
+ another_accessor->rollbackUpdateZone();
+ accessor->startUpdateZone("example.com.", true);
+ accessor->commitUpdateZone();
+}
+
+TEST_F(SQLite3Update, duplicateUpdate) {
+ accessor->startUpdateZone("example.com.", false);
+ EXPECT_THROW(accessor->startUpdateZone("example.com.", false),
+ DataSourceError);
+}
+
+TEST_F(SQLite3Update, commitWithoutTransaction) {
+ EXPECT_THROW(accessor->commitUpdateZone(), DataSourceError);
+}
+
+TEST_F(SQLite3Update, rollbackWithoutTransaction) {
+ EXPECT_THROW(accessor->rollbackUpdateZone(), DataSourceError);
+}
+
+TEST_F(SQLite3Update, addRecord) {
+ // Before update, there should be no record for this name
+ checkRecords(*accessor, zone_id, "newdata.example.com.", empty_stored);
+
+ zone_id = accessor->startUpdateZone("example.com.", false).second;
+ copy(new_data, new_data + DatabaseAccessor::ADD_COLUMN_COUNT,
+ add_columns);
+ accessor->addRecordToZone(add_columns);
+
+ expected_stored.clear();
+ expected_stored.push_back(new_data);
+ checkRecords(*accessor, zone_id, "newdata.example.com.", expected_stored);
+
+ // Commit the change, and confirm the new data is still there.
+ accessor->commitUpdateZone();
+ checkRecords(*accessor, zone_id, "newdata.example.com.", expected_stored);
+}
+
+TEST_F(SQLite3Update, addThenRollback) {
+ zone_id = accessor->startUpdateZone("example.com.", false).second;
+ copy(new_data, new_data + DatabaseAccessor::ADD_COLUMN_COUNT,
+ add_columns);
+ accessor->addRecordToZone(add_columns);
+
+ expected_stored.clear();
+ expected_stored.push_back(new_data);
+ checkRecords(*accessor, zone_id, "newdata.example.com.", expected_stored);
+
+ accessor->rollbackUpdateZone();
+ checkRecords(*accessor, zone_id, "newdata.example.com.", empty_stored);
+}
+
+TEST_F(SQLite3Update, duplicateAdd) {
+ const char* const dup_data[] = {
+ "foo.bar.example.com.", "com.example.bar.foo.", "3600", "A", "",
+ "192.0.2.1"
+ };
+ expected_stored.clear();
+ expected_stored.push_back(dup_data);
+ checkRecords(*accessor, zone_id, "foo.bar.example.com.", expected_stored);
+
+ // Adding exactly the same data. As this backend is "dumb", another
+ // row of the same content will be inserted.
+ copy(dup_data, dup_data + DatabaseAccessor::ADD_COLUMN_COUNT,
+ add_columns);
+ zone_id = accessor->startUpdateZone("example.com.", false).second;
+ accessor->addRecordToZone(add_columns);
+ expected_stored.push_back(dup_data);
+ checkRecords(*accessor, zone_id, "foo.bar.example.com.", expected_stored);
+}
+
+TEST_F(SQLite3Update, invalidAdd) {
+ // An attempt of add before an explicit start of transaction
+ EXPECT_THROW(accessor->addRecordToZone(add_columns), DataSourceError);
+}
+
+TEST_F(SQLite3Update, deleteRecord) {
+ zone_id = accessor->startUpdateZone("example.com.", false).second;
+
+ checkRecords(*accessor, zone_id, "foo.bar.example.com.", expected_stored);
+
+ copy(deleted_data, deleted_data + DatabaseAccessor::DEL_PARAM_COUNT,
+ del_params);
+ accessor->deleteRecordInZone(del_params);
+ checkRecords(*accessor, zone_id, "foo.bar.example.com.", empty_stored);
+
+ // Commit the change, and confirm the deleted data still isn't there.
+ accessor->commitUpdateZone();
+ checkRecords(*accessor, zone_id, "foo.bar.example.com.", empty_stored);
+}
+
+TEST_F(SQLite3Update, deleteThenRollback) {
+ zone_id = accessor->startUpdateZone("example.com.", false).second;
+
+ copy(deleted_data, deleted_data + DatabaseAccessor::DEL_PARAM_COUNT,
+ del_params);
+ accessor->deleteRecordInZone(del_params);
+ checkRecords(*accessor, zone_id, "foo.bar.example.com.", empty_stored);
+
+ // Rollback the change, and confirm the data still exists.
+ accessor->rollbackUpdateZone();
+ checkRecords(*accessor, zone_id, "foo.bar.example.com.", expected_stored);
+}
+
+TEST_F(SQLite3Update, deleteNonexistent) {
+ zone_id = accessor->startUpdateZone("example.com.", false).second;
+ copy(deleted_data, deleted_data + DatabaseAccessor::DEL_PARAM_COUNT,
+ del_params);
+
+ // Replace the name with a non existent one, then try to delete it.
+ // nothing should happen.
+ del_params[DatabaseAccessor::DEL_NAME] = "no-such-name.example.com.";
+ checkRecords(*accessor, zone_id, "no-such-name.example.com.",
+ empty_stored);
+ accessor->deleteRecordInZone(del_params);
+ checkRecords(*accessor, zone_id, "no-such-name.example.com.",
+ empty_stored);
+
+ // Name exists but the RR type is different. Delete attempt shouldn't
+ // delete only by name.
+ copy(deleted_data, deleted_data + DatabaseAccessor::DEL_PARAM_COUNT,
+ del_params);
+ del_params[DatabaseAccessor::DEL_TYPE] = "AAAA";
+ accessor->deleteRecordInZone(del_params);
+ checkRecords(*accessor, zone_id, "foo.bar.example.com.", expected_stored);
+
+ // Similar to the previous case, but RDATA is different.
+ copy(deleted_data, deleted_data + DatabaseAccessor::DEL_PARAM_COUNT,
+ del_params);
+ del_params[DatabaseAccessor::DEL_RDATA] = "192.0.2.2";
+ accessor->deleteRecordInZone(del_params);
+ checkRecords(*accessor, zone_id, "foo.bar.example.com.", expected_stored);
+}
+
+TEST_F(SQLite3Update, invalidDelete) {
+ // An attempt of delete before an explicit start of transaction
+ EXPECT_THROW(accessor->deleteRecordInZone(del_params), DataSourceError);
+}
} // end anonymous namespace
diff --git a/src/lib/datasrc/tests/testdata/Makefile.am b/src/lib/datasrc/tests/testdata/Makefile.am
new file mode 100644
index 0000000..64ae955
--- /dev/null
+++ b/src/lib/datasrc/tests/testdata/Makefile.am
@@ -0,0 +1,6 @@
+CLEANFILES = *.copied
+BUILT_SOURCES = rwtest.sqlite3.copied
+
+# We use install-sh with the -m option to make sure it's writable
+rwtest.sqlite3.copied: $(srcdir)/rwtest.sqlite3
+ $(top_srcdir)/install-sh -m 644 $(srcdir)/rwtest.sqlite3 $@
diff --git a/src/lib/datasrc/tests/testdata/rwtest.sqlite3 b/src/lib/datasrc/tests/testdata/rwtest.sqlite3
new file mode 100644
index 0000000..ce95a1d
Binary files /dev/null and b/src/lib/datasrc/tests/testdata/rwtest.sqlite3 differ
diff --git a/src/lib/datasrc/zone.h b/src/lib/datasrc/zone.h
index 722f23c..61fd5fb 100644
--- a/src/lib/datasrc/zone.h
+++ b/src/lib/datasrc/zone.h
@@ -15,46 +15,39 @@
#ifndef __ZONE_H
#define __ZONE_H 1
-#include <datasrc/result.h>
+#include <dns/rrset.h>
#include <dns/rrsetlist.h>
+#include <datasrc/result.h>
+
namespace isc {
namespace datasrc {
-/// \brief The base class for a single authoritative zone
+/// \brief The base class to search a zone for RRsets
///
-/// The \c Zone class is an abstract base class for representing
-/// a DNS zone as part of data source.
+/// The \c ZoneFinder class is an abstract base class for representing
+/// an object that performs DNS lookups in a specific zone accessible via
+/// a data source. In general, different types of data sources (in-memory,
+/// database-based, etc) define their own derived classes of \c ZoneFinder,
+/// implementing ways to retrieve the required data through the common
+/// interfaces declared in the base class. Each concrete \c ZoneFinder
+/// object is therefore (conceptually) associated with a specific zone
+/// of one specific data source instance.
///
-/// At the moment this is provided mainly for making the \c ZoneTable class
-/// and the authoritative query logic testable, and only provides a minimal
-/// set of features.
-/// This is why this class is defined in the same header file, but it may
-/// have to move to a separate header file when we understand what is
-/// necessary for this class for actual operation.
+/// The origin name and the RR class of the associated zone are available
+/// via the \c getOrigin() and \c getClass() methods, respectively.
///
-/// The idea is to provide a specific derived zone class for each data
-/// source, beginning with in memory one. At that point the derived classes
-/// will have more specific features. For example, they will maintain
-/// information about the location of a zone file, whether it's loaded in
-/// memory, etc.
+/// The most important method of this class is \c find(), which performs
+/// the lookup for a given domain and type. See the description of the
+/// method for details.
///
-/// It's not yet clear how the derived zone classes work with various other
-/// data sources when we integrate these components, but one possibility is
-/// something like this:
-/// - If the underlying database such as some variant of SQL doesn't have an
-/// explicit representation of zones (as part of public interface), we can
-/// probably use a "default" zone class that simply encapsulates the
-/// corresponding data source and calls a common "find" like method.
-/// - Some data source may want to specialize it by inheritance as an
-/// optimization. For example, in the current schema design of the sqlite3
-/// data source, its (derived) zone class would contain the information of
-/// the "zone ID".
-///
-/// <b>Note:</b> Unlike some other abstract base classes we don't name the
-/// class beginning with "Abstract". This is because we want to have
-/// commonly used definitions such as \c Result and \c ZoneFinderPtr, and we
-/// want to make them look more intuitive.
+/// \note It's not clear whether we should request that a zone finder form a
+/// "transaction", that is, whether to ensure the finder is not susceptible
+/// to changes made by someone else than the creator of the finder. If we
+/// don't request that, for example, two different lookup results for the
+/// same name and type can be different if other threads or programs make
+/// updates to the zone between the lookups. We should revisit this point
+/// as we gain more experiences.
class ZoneFinder {
public:
/// Result codes of the \c find() method.
@@ -259,8 +252,225 @@ typedef boost::shared_ptr<ZoneFinder> ZoneFinderPtr;
/// \brief A pointer-like type pointing to a \c ZoneFinder object.
typedef boost::shared_ptr<const ZoneFinder> ConstZoneFinderPtr;
-}
-}
+/// The base class to make updates to a single zone.
+///
+/// On construction, each derived class object will start a "transaction"
+/// for making updates to a specific zone (this means a constructor of
+/// a derived class would normally take parameters to identify the zone
+/// to be updated). The underlying realization of a "transaction" will differ
+/// for different derived classes; if it uses a general purpose database
+/// as a backend, it will involve performing some form of "begin transaction"
+/// statement for the database.
+///
+/// Updates (adding or deleting RRs) are made via \c addRRset() and
+/// \c deleteRRset() methods. Until the \c commit() method is called the
+/// changes are local to the updater object. For example, they won't be
+/// visible via a \c ZoneFinder object except the one returned by the
+/// updater's own \c getFinder() method. The \c commit() completes the
+/// transaction and makes the changes visible to others.
+///
+/// This class does not provide an explicit "rollback" interface. If
+/// something wrong or unexpected happens during the updates and the
+/// caller wants to cancel the intermediate updates, the caller should
+/// simply destruct the updater object without calling \c commit().
+/// The destructor is supposed to perform the "rollback" operation,
+/// depending on the internal details of the derived class.
+///
+/// \note This initial implementation provides a quite simple interface of
+/// adding and deleting RRs (see the description of the related methods).
+/// It may be revisited as we gain more experiences.
+class ZoneUpdater {
+protected:
+ /// The default constructor.
+ ///
+ /// This is intentionally defined as protected to ensure that this base
+ /// class is never instantiated directly.
+ ZoneUpdater() {}
+
+public:
+ /// The destructor
+ ///
+ /// Each derived class implementation must ensure that if \c commit()
+ /// has not been performed by the time of the call to it, then it
+ /// "rollbacks" the updates made via the updater so far.
+ virtual ~ZoneUpdater() {}
+
+ /// Return a finder for the zone being updated.
+ ///
+ /// The returned finder provides the functionalities of \c ZoneFinder
+ /// for the zone as updates are made via the updater. That is, before
+ /// making any update, the finder will be able to find all RRsets that
+ /// exist in the zone at the time the updater is created. If RRsets
+ /// are added or deleted via \c addRRset() or \c deleteRRset(),
+ /// this finder will find the added ones or miss the deleted ones
+ /// respectively.
+ ///
+ /// The finder returned by this method is effective only while the updates
+ /// are performed, i.e., from the construction of the corresponding
+ /// updater until \c commit() is performed or the updater is destructed
+ /// without commit. The result of a subsequent call to this method (or
+ /// the use of the result) after that is undefined.
+ ///
+ /// \return A reference to a \c ZoneFinder for the updated zone
+ virtual ZoneFinder& getFinder() = 0;
+
+ /// Add an RRset to a zone via the updater
+ ///
+ /// This may be revisited in a future version, but right now the intended
+ /// behavior of this method is simple: It "naively" adds the specified
+ /// RRset to the zone specified on creation of the updater.
+ /// It performs minimum level of validation on the specified RRset:
+ /// - Whether the RR class is identical to that for the zone to be updated
+ /// - Whether the RRset is not empty, i.e., it has at least one RDATA
+ /// - Whether the RRset is not associated with an RRSIG, i.e.,
+ /// whether \c getRRsig() on the RRset returns a NULL pointer.
+ ///
+ /// and otherwise does not check any oddity. For example, it doesn't
+ /// check whether the owner name of the specified RRset is a subdomain
+ /// of the zone's origin; it doesn't care whether or not there is already
+ /// an RRset of the same name and RR type in the zone, and if there is,
+ /// whether any of the existing RRs have duplicate RDATA with the added
+ /// ones. If these conditions matter the calling application must examine
+ /// the existing data beforehand using the \c ZoneFinder returned by
+ /// \c getFinder().
+ ///
+ /// The validation requirement on the associated RRSIG is temporary.
+ /// If we find it more reasonable and useful to allow adding a pair of
+ /// RRset and its RRSIG RRset as we gain experiences with the interface,
+ /// we may remove this restriction. Until then we explicitly check it
+ /// to prevent accidental misuse.
+ ///
+ /// Conceptually, on successful call to this method, the zone will have
+ /// the specified RRset, and if there is already an RRset of the same
+ /// name and RR type, these two sets will be "merged". "Merged" means
+ /// that a subsequent call to \c ZoneFinder::find() for the name and type
+ /// will result in success and the returned RRset will contain all
+ /// previously existing and newly added RDATAs with the TTL being the
+ /// minimum of the two RRsets. The underlying representation of the
+ /// "merged" RRsets may vary depending on the characteristic of the
+ /// underlying data source. For example, if it uses a general purpose
+ /// database that stores each RR of the same RRset separately, it may
+ /// simply be a larger sets of RRs based on both the existing and added
+ /// RRsets; the TTLs of the RRs may be different within the database, and
+ /// there may even be duplicate RRs in different database rows. As long
+ /// as the RRset returned via \c ZoneFinder::find() conforms to the
+ /// concept of "merge", the actual internal representation is up to the
+ /// implementation.
+ ///
+ /// This method must not be called once commit() is performed. If it
+ /// calls after \c commit() the implementation must throw a
+ /// \c DataSourceError exception.
+ ///
+ /// \todo As noted above we may have to revisit the design details as we
+ /// gain experiences:
+ ///
+ /// - we may want to check (and maybe reject) if there is already a
+ /// duplicate RR (that has the same RDATA).
+ /// - we may want to check (and maybe reject) if there is already an
+ /// RRset of the same name and RR type with different TTL
+ /// - we may even want to check if there is already any RRset of the
+ /// same name and RR type.
+ /// - we may want to add an "options" parameter that can control the
+ /// above points
+ /// - we may want to have this method return a value containing the
+ /// information on whether there's a duplicate, etc.
+ ///
+ /// \exception DataSourceError Called after \c commit(), RRset is invalid
+ /// (see above), internal data source error
+ /// \exception std::bad_alloc Resource allocation failure
+ ///
+ /// \param rrset The RRset to be added
+ virtual void addRRset(const isc::dns::RRset& rrset) = 0;
+
+ /// Delete an RRset from a zone via the updater
+ ///
+ /// Like \c addRRset(), the detailed semantics and behavior of this method
+ /// may have to be revisited in a future version. The following are
+ /// based on the initial implementation decisions.
+ ///
+ /// On successful completion of this method, it will remove from the zone
+ /// the RRs of the specified owner name and RR type that match one of
+ /// the RDATAs of the specified RRset. There are several points to be
+ /// noted:
+ /// - Existing RRs that don't match any of the specified RDATAs will
+ /// remain in the zone.
+ /// - Any RRs of the specified RRset that doesn't exist in the zone will
+ /// simply be ignored; the implementation of this method is not supposed
+ /// to check that condition.
+ /// - The TTL of the RRset is ignored; matching is only performed by
+ /// the owner name, RR type and RDATA
+ ///
+ /// Ignoring the TTL may not look sensible, but it's based on the
+ /// observation that it will result in more intuitive result, especially
+ /// when the underlying data source is a general purpose database.
+ /// See also \c DatabaseAccessor::deleteRecordInZone() on this point.
+ /// It also matches the dynamic update protocol (RFC2136), where TTLs
+ /// are ignored when deleting RRs.
+ ///
+ /// \note Since the TTL is ignored, this method could take the RRset
+ /// to be deleted as a tuple of name, RR type, and a list of RDATAs.
+ /// But in practice, it's quite likely that the caller has the RRset
+ /// in the form of the \c RRset object (e.g., extracted from a dynamic
+ /// update request message), so this interface would rather be more
+ /// convenient. If it turns out not to be true we can change or extend
+ /// the method signature.
+ ///
+ /// This method performs minimum level of validation on the specified
+ /// RRset:
+ /// - Whether the RR class is identical to that for the zone to be updated
+ /// - Whether the RRset is not empty, i.e., it has at least one RDATA
+ /// - Whether the RRset is not associated with an RRSIG, i.e.,
+ /// whether \c getRRsig() on the RRset returns a NULL pointer.
+ ///
+ /// This method must not be called once commit() is performed. If it
+ /// calls after \c commit() the implementation must throw a
+ /// \c DataSourceError exception.
+ ///
+ /// \todo As noted above we may have to revisit the design details as we
+ /// gain experiences:
+ ///
+ /// - we may want to check (and maybe reject) if some or all of the RRs
+ /// for the specified RRset don't exist in the zone
+ /// - we may want to allow an option to "delete everything" for specified
+ /// name and/or specified name + RR type.
+ /// - as mentioned above, we may want to include the TTL in matching the
+ /// deleted RRs
+ /// - we may want to add an "options" parameter that can control the
+ /// above points
+ /// - we may want to have this method return a value containing the
+ /// information on whether there's any RRs that are specified but don't
+ /// exit, the number of actually deleted RRs, etc.
+ ///
+ /// \exception DataSourceError Called after \c commit(), RRset is invalid
+ /// (see above), internal data source error
+ /// \exception std::bad_alloc Resource allocation failure
+ ///
+ /// \param rrset The RRset to be deleted
+ virtual void deleteRRset(const isc::dns::RRset& rrset) = 0;
+
+ /// Commit the updates made in the updater to the zone
+ ///
+ /// This method completes the "transaction" started at the creation
+ /// of the updater. After successful completion of this method, the
+ /// updates will be visible outside the scope of the updater.
+ /// The actual internal behavior will defer for different derived classes.
+ /// For a derived class with a general purpose database as a backend,
+ /// for example, this method would perform a "commit" statement for the
+ /// database.
+ ///
+ /// This operation can only be performed at most once. A duplicate call
+ /// must result in a DatasourceError exception.
+ ///
+ /// \exception DataSourceError Duplicate call of the method,
+ /// internal data source error
+ virtual void commit() = 0;
+};
+
+/// \brief A pointer-like type pointing to a \c ZoneUpdater object.
+typedef boost::shared_ptr<ZoneUpdater> ZoneUpdaterPtr;
+
+} // end of datasrc
+} // end of isc
#endif // __ZONE_H
diff --git a/src/lib/dns/rdata/generic/rrsig_46.cc b/src/lib/dns/rdata/generic/rrsig_46.cc
index fc8e340..59ff030 100644
--- a/src/lib/dns/rdata/generic/rrsig_46.cc
+++ b/src/lib/dns/rdata/generic/rrsig_46.cc
@@ -244,7 +244,7 @@ RRSIG::compare(const Rdata& other) const {
}
const RRType&
-RRSIG::typeCovered() {
+RRSIG::typeCovered() const {
return (impl_->covered_);
}
diff --git a/src/lib/dns/rdata/generic/rrsig_46.h b/src/lib/dns/rdata/generic/rrsig_46.h
index b8e6306..b32c17f 100644
--- a/src/lib/dns/rdata/generic/rrsig_46.h
+++ b/src/lib/dns/rdata/generic/rrsig_46.h
@@ -40,7 +40,7 @@ public:
~RRSIG();
// specialized methods
- const RRType& typeCovered();
+ const RRType& typeCovered() const;
private:
RRSIGImpl* impl_;
};
More information about the bind10-changes
mailing list