BIND 10 trac1176, updated. 30df43575158b0cb294ec49a8463fe8b49593e62 Merge branch #1150
BIND 10 source code commits
bind10-changes at lists.isc.org
Thu Aug 25 12:48:09 UTC 2011
The branch, trac1176 has been updated
via 30df43575158b0cb294ec49a8463fe8b49593e62 (commit)
via 4c0accf0a591b0422c84216150e1b9b4e008609e (commit)
via 1f051716bce3d7aa2545722ba41958df9758cadc (commit)
via 10553ed4ebb5b949ae74d277d398d2e8a3909ea5 (commit)
via d916aef6af6bb8506b1ff4756054a1697410982f (commit)
via 4700bada6282f5ad10b53cd8ca7cc03b8fea791d (commit)
via ef64723fe9638f8d56f58fba44a149ac620eadd9 (commit)
via 5de6f9658f745e05361242042afd518b444d7466 (commit)
via 3f847f9d35bf2bf9ee0d957ea1aa9ffb27a32cdb (commit)
via 43da3c6c1cc7cb5fcb1dbe2f983a53e883408d1b (commit)
via 27b3488b71a5c3b95652eab2720497d6d055346e (commit)
via 1921e1297dfcb878b9417edefe4d87639c827948 (commit)
via 4ff5e524a7f79ad7f4513ebed3ca0990392263af (commit)
via 38d1a8aa943424e1a0de0503ee8aa961a95d0e14 (commit)
via 4579a2a9f43a38144539447bb5076bfcbaf8b6d8 (commit)
via 08b5add9a6e405342c0c8bc3bdf5d552ed45df0e (commit)
via a176724d99c073f8e547dea2675a5b7d1df70515 (commit)
via a9b769b8bf12e2922e385c62ce337fb723731699 (commit)
via b4ae924f504e9749989059a14e6a5dc830c99e81 (commit)
via 2384bcf387e93435658ec1ab92addbf28c9ab640 (commit)
via 1d314b2544b8af8a936c90e00a0dbbb605410952 (commit)
via e3c81bd07046903b4b3bff8325024aafcdb35cba (commit)
via 9001f1db99dfff10957dc2a971e7466a496f0f2f (commit)
via 616fb3be8c0b3c266eaf0aa4ae399918fc7992ef (commit)
from 087c6def9087019640a437b63c782a5c22de1feb (commit)
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 30df43575158b0cb294ec49a8463fe8b49593e62
Merge: 1f051716bce3d7aa2545722ba41958df9758cadc 4c0accf0a591b0422c84216150e1b9b4e008609e
Author: Michal 'vorner' Vaner <michal.vaner at nic.cz>
Date: Thu Aug 25 14:12:00 2011 +0200
Merge branch #1150
commit 4c0accf0a591b0422c84216150e1b9b4e008609e
Author: Michal 'vorner' Vaner <michal.vaner at nic.cz>
Date: Thu Aug 25 14:09:40 2011 +0200
[1150] Make regenerated scripts executable
When they are regenerated by make (not during initial configure run),
they were not set executable, which was useless. This way they are set
executable twice during the initial configure-make-make check, but at
last it works.
commit 1f051716bce3d7aa2545722ba41958df9758cadc
Merge: d916aef6af6bb8506b1ff4756054a1697410982f 27b3488b71a5c3b95652eab2720497d6d055346e
Author: Michal 'vorner' Vaner <michal.vaner at nic.cz>
Date: Thu Aug 25 12:03:32 2011 +0200
Merge branch #1066
commit 10553ed4ebb5b949ae74d277d398d2e8a3909ea5
Author: Michal 'vorner' Vaner <michal.vaner at nic.cz>
Date: Thu Aug 25 11:54:24 2011 +0200
[1150] Fix generated/nongenerated tests
The folder contains both generated and directly written files, so we
need to handle them separately, because of when we have different
builddir than srcdir.
commit d916aef6af6bb8506b1ff4756054a1697410982f
Merge: 4700bada6282f5ad10b53cd8ca7cc03b8fea791d ef64723fe9638f8d56f58fba44a149ac620eadd9
Author: Jelte Jansen <jelte at isc.org>
Date: Thu Aug 25 11:14:50 2011 +0200
Merge branch 'master' of ssh://git.bind10.isc.org/var/bind10/git/bind10
commit 4700bada6282f5ad10b53cd8ca7cc03b8fea791d
Author: Jelte Jansen <jelte at isc.org>
Date: Thu Aug 25 11:14:42 2011 +0200
[master] changelog entry
commit ef64723fe9638f8d56f58fba44a149ac620eadd9
Author: JINMEI Tatuya <jinmei at isc.org>
Date: Thu Aug 25 01:26:42 2011 -0700
[master] clarified the expected behavior of DatabaseAccessor::getNext()
after it returns false.
okayed on jabber, and I don't think we need a separate ticket/branch for
this as it's just a small documentation update.
commit 5de6f9658f745e05361242042afd518b444d7466
Merge: 3f847f9d35bf2bf9ee0d957ea1aa9ffb27a32cdb 38d1a8aa943424e1a0de0503ee8aa961a95d0e14
Author: Jelte Jansen <jelte at isc.org>
Date: Thu Aug 25 09:39:59 2011 +0200
Merge branch 'trac326'
commit 3f847f9d35bf2bf9ee0d957ea1aa9ffb27a32cdb
Merge: 087c6def9087019640a437b63c782a5c22de1feb 1921e1297dfcb878b9417edefe4d87639c827948
Author: Jelte Jansen <jelte at isc.org>
Date: Thu Aug 25 09:26:24 2011 +0200
Merge branch 'trac1174'
commit 43da3c6c1cc7cb5fcb1dbe2f983a53e883408d1b
Author: Michal 'vorner' Vaner <michal.vaner at nic.cz>
Date: Wed Aug 24 14:15:10 2011 +0200
[1150] Use noinst_SCRIPTS on generated scripts
Instead of EXTRA_DIST, this allows make to regenerate them on .in
change.
commit 27b3488b71a5c3b95652eab2720497d6d055346e
Merge: 4ff5e524a7f79ad7f4513ebed3ca0990392263af 087c6def9087019640a437b63c782a5c22de1feb
Author: Michal 'vorner' Vaner <michal.vaner at nic.cz>
Date: Wed Aug 24 12:02:48 2011 +0200
Merge branch 'master' into #1066
Just to bring in changes and solve conflicts, there was some refactoring
elsewhere.
Conflicts:
src/lib/datasrc/database.cc
src/lib/datasrc/datasrc_messages.mes
src/lib/datasrc/tests/database_unittest.cc
commit 4ff5e524a7f79ad7f4513ebed3ca0990392263af
Author: Jelte Jansen <jelte at isc.org>
Date: Tue Aug 23 13:54:20 2011 +0200
[1066] add one test and typo in comment
commit b4ae924f504e9749989059a14e6a5dc830c99e81
Author: Michal 'vorner' Vaner <michal.vaner at nic.cz>
Date: Wed Aug 17 15:23:21 2011 +0200
[1066] Logging about wildcards
commit 2384bcf387e93435658ec1ab92addbf28c9ab640
Author: Michal 'vorner' Vaner <michal.vaner at nic.cz>
Date: Wed Aug 17 13:43:01 2011 +0200
[1066] More wildcard cornercases
Wildcard match below NS with GLUE_OK mode
Empty non-terminal asterisk
commit 1d314b2544b8af8a936c90e00a0dbbb605410952
Author: Michal 'vorner' Vaner <michal.vaner at nic.cz>
Date: Wed Aug 17 11:49:00 2011 +0200
[1066] Cancel wildcard match
commit e3c81bd07046903b4b3bff8325024aafcdb35cba
Author: Michal 'vorner' Vaner <michal.vaner at nic.cz>
Date: Mon Aug 15 15:55:14 2011 +0200
[1066] Basic wildcard matching
Still, some canceling needs to be done properly.
commit 9001f1db99dfff10957dc2a971e7466a496f0f2f
Author: Michal 'vorner' Vaner <michal.vaner at nic.cz>
Date: Mon Aug 15 13:00:28 2011 +0200
[1066] Tests for some basic wildcards
commit 616fb3be8c0b3c266eaf0aa4ae399918fc7992ef
Merge: 44bd4bc6dc7df56905071933a542e00e91f84837 39a0a5c65d0802f40ab428474b1e6d981a91fbce
Author: Michal 'vorner' Vaner <michal.vaner at nic.cz>
Date: Wed Aug 17 10:44:14 2011 +0200
Merge branch #1065 into master
Conflicts:
src/lib/datasrc/tests/database_unittest.cc
-----------------------------------------------------------------------
Summary of changes:
ChangeLog | 6 +
src/bin/bind10/tests/Makefile.am | 2 +
src/bin/cfgmgr/tests/Makefile.am | 4 +-
src/bin/tests/Makefile.am | 1 +
src/bin/xfrout/tests/Makefile.am | 3 +-
src/lib/datasrc/database.cc | 140 ++++++++++++++---
src/lib/datasrc/database.h | 26 +++-
src/lib/datasrc/datasrc_messages.mes | 27 ++++
src/lib/datasrc/sqlite3_accessor.cc | 97 ++++++++++--
src/lib/datasrc/sqlite3_accessor.h | 4 +-
src/lib/datasrc/sqlite3_datasrc.cc | 95 ++++++++++--
src/lib/datasrc/tests/Makefile.am | 1 +
src/lib/datasrc/tests/database_unittest.cc | 164 ++++++++++++++++++--
src/lib/datasrc/tests/sqlite3_accessor_unittest.cc | 77 +++++++++-
src/lib/python/isc/datasrc/sqlite3_ds.py | 84 +++++++----
.../python/isc/datasrc/tests/sqlite3_ds_test.py | 50 ++++++-
src/lib/python/isc/log/tests/Makefile.am | 18 ++-
17 files changed, 682 insertions(+), 117 deletions(-)
-----------------------------------------------------------------------
diff --git a/ChangeLog b/ChangeLog
index c7e6d62..a71c8a8 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,9 @@
+285. [bug] jelte
+ sqlite3 data source: fixed a race condition on initial startup,
+ when the database has not been initialized yet, and multiple
+ processes are trying to do so, resulting in one of them failing.
+ (Trac #326, git 5de6f9658f745e05361242042afd518b444d7466)
+
284. [bug] jerry
b10-zonemgr: zonemgr will not terminate on empty zones, it will
log a warning and try to do zone transfer for them.
diff --git a/src/bin/bind10/tests/Makefile.am b/src/bin/bind10/tests/Makefile.am
index d9e012f..f388ba1 100644
--- a/src/bin/bind10/tests/Makefile.am
+++ b/src/bin/bind10/tests/Makefile.am
@@ -2,6 +2,7 @@ PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
#PYTESTS = args_test.py bind10_test.py
# NOTE: this has a generated test found in the builddir
PYTESTS = bind10_test.py
+noinst_SCRIPTS = $(PYTESTS)
# If necessary (rare cases), explicitly specify paths to dynamic libraries
# required by loadable python modules.
@@ -19,6 +20,7 @@ if ENABLE_PYTHON_COVERAGE
endif
for pytest in $(PYTESTS) ; do \
echo Running test: $$pytest ; \
+ chmod +x $(abs_builddir)/$$pytest ; \
$(LIBRARY_PATH_PLACEHOLDER) \
env PYTHONPATH=$(abs_top_srcdir)/src/lib/python:$(abs_top_builddir)/src/lib/python:$(abs_top_srcdir)/src/bin:$(abs_top_builddir)/src/bin/bind10:$(abs_top_builddir)/src/lib/util/io/.libs \
BIND10_MSGQ_SOCKET_FILE=$(abs_top_builddir)/msgq_socket \
diff --git a/src/bin/cfgmgr/tests/Makefile.am b/src/bin/cfgmgr/tests/Makefile.am
index bd67241..99f8cc9 100644
--- a/src/bin/cfgmgr/tests/Makefile.am
+++ b/src/bin/cfgmgr/tests/Makefile.am
@@ -1,7 +1,8 @@
PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
PYTESTS = b10-cfgmgr_test.py
-EXTRA_DIST = $(PYTESTS) testdata/plugins/testplugin.py
+noinst_SCRIPTS = $(PYTESTS)
+EXTRA_DIST = testdata/plugins/testplugin.py
# If necessary (rare cases), explicitly specify paths to dynamic libraries
# required by loadable python modules.
@@ -19,6 +20,7 @@ if ENABLE_PYTHON_COVERAGE
endif
for pytest in $(PYTESTS) ; do \
echo Running test: $$pytest ; \
+ chmod +x $(abs_builddir)/$$pytest ; \
env TESTDATA_PATH=$(abs_srcdir)/testdata \
$(LIBRARY_PATH_PLACEHOLDER) \
env PYTHONPATH=$(abs_top_srcdir)/src/lib/python:$(abs_top_builddir)/src/lib/python:$(abs_top_builddir)/src/bin/cfgmgr:$(abs_top_builddir)/src/lib/python/isc/config \
diff --git a/src/bin/tests/Makefile.am b/src/bin/tests/Makefile.am
index 56ff68b..034152c 100644
--- a/src/bin/tests/Makefile.am
+++ b/src/bin/tests/Makefile.am
@@ -20,6 +20,7 @@ if ENABLE_PYTHON_COVERAGE
endif
for pytest in $(PYTESTS) ; do \
echo Running test: $$pytest ; \
+ chmod +x $(abs_builddir)/$$pytest ; \
$(LIBRARY_PATH_PLACEHOLDER) \
env PYTHONPATH=$(abs_top_srcdir)/src/lib/python:$(abs_top_builddir)/src/lib/python:$(abs_top_builddir)/src/lib/dns/python/.libs \
$(PYCOVERAGE_RUN) $(abs_builddir)/$$pytest || exit ; \
diff --git a/src/bin/xfrout/tests/Makefile.am b/src/bin/xfrout/tests/Makefile.am
index 99f4843..2f1e2ea 100644
--- a/src/bin/xfrout/tests/Makefile.am
+++ b/src/bin/xfrout/tests/Makefile.am
@@ -1,6 +1,6 @@
PYCOVERAGE_RUN=@PYCOVERAGE_RUN@
PYTESTS = xfrout_test.py
-EXTRA_DIST = $(PYTESTS)
+noinst_SCRIPTS = $(PYTESTS)
# If necessary (rare cases), explicitly specify paths to dynamic libraries
# required by loadable python modules.
@@ -18,6 +18,7 @@ if ENABLE_PYTHON_COVERAGE
endif
for pytest in $(PYTESTS) ; do \
echo Running test: $$pytest ; \
+ chmod +x $(abs_builddir)/$$pytest ; \
$(LIBRARY_PATH_PLACEHOLDER) \
env PYTHONPATH=$(abs_top_builddir)/src/bin/xfrout:$(abs_top_srcdir)/src/lib/python:$(abs_top_builddir)/src/lib/python:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/util/io/.libs \
$(PYCOVERAGE_RUN) $(abs_builddir)/$$pytest || exit ; \
diff --git a/src/lib/datasrc/database.cc b/src/lib/datasrc/database.cc
index c2c0a4b..347e3ab 100644
--- a/src/lib/datasrc/database.cc
+++ b/src/lib/datasrc/database.cc
@@ -49,7 +49,7 @@ DatabaseClient::DatabaseClient(boost::shared_ptr<DatabaseAccessor>
DataSourceClient::FindResult
DatabaseClient::findZone(const Name& name) const {
- std::pair<bool, int> zone(database_->getZone(name));
+ std::pair<bool, int> zone(database_->getZone(name.toText()));
// Try exact first
if (zone.first) {
return (FindResult(result::SUCCESS,
@@ -60,7 +60,7 @@ DatabaseClient::findZone(const Name& name) const {
// 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);
+ zone = database_->getZone(superdomain.toText());
if (zone.first) {
return (FindResult(result::PARTIALMATCH,
ZoneFinderPtr(new Finder(database_,
@@ -175,7 +175,8 @@ std::pair<bool, isc::dns::RRsetPtr>
DatabaseClient::Finder::getRRset(const isc::dns::Name& name,
const isc::dns::RRType* type,
bool want_cname, bool want_dname,
- bool want_ns)
+ bool want_ns,
+ const isc::dns::Name* construct_name)
{
RRsigStore sig_store;
bool records_found = false;
@@ -191,6 +192,9 @@ DatabaseClient::Finder::getRRset(const isc::dns::Name& name,
}
std::string columns[DatabaseAccessor::COLUMN_COUNT];
+ if (construct_name == NULL) {
+ construct_name = &name;
+ }
while (context->getNext(columns)) {
if (!records_found) {
records_found = true;
@@ -225,7 +229,8 @@ DatabaseClient::Finder::getRRset(const isc::dns::Name& name,
isc_throw(DataSourceError, "NS found together with data"
" in non-apex domain " + name.toText());
}
- addOrCreate(result_rrset, name, getClass(), cur_type, cur_ttl,
+ addOrCreate(result_rrset, *construct_name, getClass(),
+ cur_type, cur_ttl,
columns[DatabaseAccessor::RDATA_COLUMN],
*database_);
} else if (type != NULL && cur_type == *type) {
@@ -238,7 +243,8 @@ DatabaseClient::Finder::getRRset(const isc::dns::Name& name,
isc_throw(DataSourceError, "NS found together with data"
" in non-apex domain " + name.toText());
}
- addOrCreate(result_rrset, name, getClass(), cur_type, cur_ttl,
+ addOrCreate(result_rrset, *construct_name, getClass(),
+ cur_type, cur_ttl,
columns[DatabaseAccessor::RDATA_COLUMN],
*database_);
} else if (want_cname && cur_type == isc::dns::RRType::CNAME()) {
@@ -248,7 +254,8 @@ DatabaseClient::Finder::getRRset(const isc::dns::Name& name,
isc_throw(DataSourceError, "CNAME found but it is not "
"the only record for " + name.toText());
}
- addOrCreate(result_rrset, name, getClass(), cur_type, cur_ttl,
+ addOrCreate(result_rrset, *construct_name, getClass(),
+ cur_type, cur_ttl,
columns[DatabaseAccessor::RDATA_COLUMN],
*database_);
} else if (want_dname && cur_type == isc::dns::RRType::DNAME()) {
@@ -258,7 +265,8 @@ DatabaseClient::Finder::getRRset(const isc::dns::Name& name,
isc_throw(DataSourceError, "DNAME with multiple RRs in " +
name.toText());
}
- addOrCreate(result_rrset, name, getClass(), cur_type, cur_ttl,
+ addOrCreate(result_rrset, *construct_name, getClass(),
+ cur_type, cur_ttl,
columns[DatabaseAccessor::RDATA_COLUMN],
*database_);
} else if (cur_type == isc::dns::RRType::RRSIG()) {
@@ -292,6 +300,20 @@ DatabaseClient::Finder::getRRset(const isc::dns::Name& name,
return (std::pair<bool, isc::dns::RRsetPtr>(records_found, result_rrset));
}
+bool
+DatabaseClient::Finder::hasSubdomains(const std::string& name) {
+ // Request the context
+ DatabaseAccessor::IteratorContextPtr
+ context(database_->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);
+ }
+
+ std::string columns[DatabaseAccessor::COLUMN_COUNT];
+ return (context->getNext(columns));
+}
+
ZoneFinder::FindResult
DatabaseClient::Finder::find(const isc::dns::Name& name,
const isc::dns::RRType& type,
@@ -307,20 +329,35 @@ DatabaseClient::Finder::find(const isc::dns::Name& name,
std::pair<bool, isc::dns::RRsetPtr> found;
logger.debug(DBG_TRACE_DETAILED, DATASRC_DATABASE_FIND_RECORDS)
.arg(database_->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;
// First, do we have any kind of delegation (NS/DNAME) here?
- const Name origin(getOrigin());
- const size_t origin_label_count(origin.getLabelCount());
- const size_t current_label_count(name.getLabelCount());
+ Name origin(getOrigin());
+ size_t origin_label_count(origin.getLabelCount());
+ // Number of labels in the last known non-empty domain
+ size_t last_known(origin_label_count);
+ size_t current_label_count(name.getLabelCount());
// This is how many labels we remove to get origin
- const size_t remove_labels(current_label_count - origin_label_count);
+ size_t remove_labels(current_label_count - origin_label_count);
// Now go trough all superdomains from origin down
for (int i(remove_labels); i > 0; --i) {
- const Name superdomain(name.split(i));
+ Name superdomain(name.split(i));
// Look if there's NS or DNAME (but ignore the NS in origin)
found = getRRset(superdomain, NULL, false, true,
i != remove_labels && !glue_ok);
+ if (found.first) {
+ // It contains some RRs, so it exists.
+ last_known = superdomain.getLabelCount();
+ // In case we are in GLUE_OK, we want to store the highest
+ // encountered RRset.
+ if (glue_ok && !first_ns && i != remove_labels) {
+ first_ns = getRRset(superdomain, NULL, false, false,
+ true).second;
+ }
+ }
if (found.second) {
// We found something redirecting somewhere else
// (it can be only NS or DNAME here)
@@ -360,18 +397,73 @@ DatabaseClient::Finder::find(const isc::dns::Name& name,
}
if (!result_rrset && !records_found) {
- // Request the context
- DatabaseAccessor::IteratorContextPtr
- context(database_->getRecords(name.toText(), 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.toText());
- }
-
- std::string columns[DatabaseAccessor::COLUMN_COUNT];
- if (context->getNext(columns)) {
+ // Nothing lives here.
+ // But check if something lives below this
+ // domain and if so, pretend something is here as well.
+ if (hasSubdomains(name.toText())) {
+ LOG_DEBUG(logger, DBG_TRACE_DETAILED,
+ DATASRC_DATABASE_FOUND_EMPTY_NONTERMINAL).
+ arg(database_->getDBName()).arg(name);
records_found = true;
+ } else {
+ // It's not empty non-terminal. So check for wildcards.
+ // We remove labels one by one and look for the wildcard there.
+ // Go up to first non-empty domain.
+
+ remove_labels = current_label_count - last_known;
+ Name star("*");
+ for (size_t i(1); i <= remove_labels; ++ i) {
+ // Construct the name with *
+ // TODO: Once the underlying DatabaseAccessor takes
+ // string, do the concatenation on strings, not
+ // Names
+ Name superdomain(name.split(i));
+ Name wildcard(star.concatenate(superdomain));
+ // TODO What do we do about DNAME here?
+ found = getRRset(wildcard, &type, true, false, true,
+ &name);
+ if (found.first) {
+ if (first_ns) {
+ // In case we are under NS, we don't
+ // wildcard-match, but return delegation
+ result_rrset = first_ns;
+ result_status = DELEGATION;
+ records_found = true;
+ // We pretend to switch to non-glue_ok mode
+ glue_ok = false;
+ LOG_DEBUG(logger, DBG_TRACE_DETAILED,
+ DATASRC_DATABASE_WILDCARD_CANCEL_NS).
+ arg(database_->getDBName()).arg(wildcard).
+ arg(first_ns->getName());
+ } else if (!hasSubdomains(name.split(i - 1).toText()))
+ {
+ // Nothing we added as part of the * can exist
+ // directly, as we go up only to first existing
+ // domain, but it could be empty non-terminal. In
+ // that case, we need to cancel the match.
+ records_found = true;
+ result_rrset = found.second;
+ LOG_DEBUG(logger, DBG_TRACE_DETAILED,
+ DATASRC_DATABASE_WILDCARD).
+ arg(database_->getDBName()).arg(wildcard).
+ arg(name);
+ } else {
+ LOG_DEBUG(logger, DBG_TRACE_DETAILED,
+ DATASRC_DATABASE_WILDCARD_CANCEL_SUB).
+ arg(database_->getDBName()).arg(wildcard).
+ arg(name).arg(superdomain);
+ }
+ break;
+ } else if (hasSubdomains(wildcard.toText())) {
+ // Empty non-terminal asterisk
+ records_found = true;
+ LOG_DEBUG(logger, DBG_TRACE_DETAILED,
+ DATASRC_DATABASE_WILDCARD_EMPTY).
+ arg(database_->getDBName()).arg(wildcard).
+ arg(name);
+ break;
+ }
+ }
}
}
}
@@ -488,7 +580,7 @@ private:
ZoneIteratorPtr
DatabaseClient::getIterator(const isc::dns::Name& name) const {
// Get the zone
- std::pair<bool, int> zone(database_->getZone(name));
+ std::pair<bool, int> zone(database_->getZone(name.toText()));
if (!zone.first) {
// No such zone, can't continue
isc_throw(DataSourceError, "Zone " + name.toText() +
diff --git a/src/lib/datasrc/database.h b/src/lib/datasrc/database.h
index 16d742d..1120a3e 100644
--- a/src/lib/datasrc/database.h
+++ b/src/lib/datasrc/database.h
@@ -88,7 +88,8 @@ public:
* It is not specified if and what implementation of this method may throw,
* so code should expect anything.
*
- * \param name The name of the zone's apex to be looked up.
+ * \param name The (fully qualified) domain name of the zone's apex to be
+ * looked up.
* \return The first part of the result indicates if a matching zone
* was found. In case it was, the second part is internal zone ID.
* This one will be passed to methods finding data in the zone.
@@ -96,7 +97,7 @@ public:
* be returned - the ID is only passed back to the database as
* an opaque handle.
*/
- virtual std::pair<bool, int> getZone(const isc::dns::Name& name) const = 0;
+ virtual std::pair<bool, int> getZone(const std::string& name) const = 0;
/**
* \brief This holds the internal context of ZoneIterator for databases
@@ -136,6 +137,11 @@ public:
* definition already known to the caller (it already passes it as
* an argument to getRecords()).
*
+ * Once this function returns false, any subsequent call to it should
+ * result in false. The implementation of a derived class must ensure
+ * it doesn't cause any disruption due to that such as a crash or
+ * exception.
+ *
* \note The order of RRs is not strictly set, but the RRs for single
* RRset must not be interleaved with any other RRs (eg. RRsets must be
* "together").
@@ -369,6 +375,9 @@ public:
* DataSourceError.
* \param want_ns This allows redirection by NS to be returned. If
* any other data is met as well, DataSourceError is thrown.
+ * \param construct_name If set to non-NULL, the resulting RRset will
+ * be constructed for this name instead of the queried one. This
+ * is useful for wildcards.
* \note It may happen that some of the above error conditions are not
* detected in some circumstances. The goal here is not to validate
* the domain in DB, but to avoid bad behaviour resulting from
@@ -386,7 +395,18 @@ public:
type,
bool want_cname,
bool want_dname,
- bool want_ns);
+ bool want_ns, const
+ isc::dns::Name*
+ construct_name = NULL);
+ /**
+ * \brief Checks if something lives below this domain.
+ *
+ * This looks if there's any subdomain of the given name. It can be
+ * used to test if domain is empty non-terminal.
+ *
+ * \param name The domain to check.
+ */
+ bool hasSubdomains(const std::string& name);
};
/**
* \brief Find a zone in the database
diff --git a/src/lib/datasrc/datasrc_messages.mes b/src/lib/datasrc/datasrc_messages.mes
index 659d2bd..5f92407 100644
--- a/src/lib/datasrc/datasrc_messages.mes
+++ b/src/lib/datasrc/datasrc_messages.mes
@@ -87,6 +87,11 @@ When searching for a domain, the program met a DNAME redirection to a different
place in the domain space at the given domain name. It will return that one
instead.
+% DATASRC_DATABASE_FOUND_EMPTY_NONTERMINAL empty non-terminal %2 in %1
+The domain name doesn't have any RRs, so it doesn't exist in the database.
+However, it has a subdomain, so it exists in the DNS address space. So we
+return NXRRSET instead of NXDOMAIN.
+
% DATASRC_DATABASE_FOUND_NXDOMAIN search in datasource %1 resulted in NXDOMAIN for %2/%3/%4
The data returned by the database backend did not contain any data for the given
domain name, class and type.
@@ -117,6 +122,28 @@ were found to be different. This isn't allowed on the wire and is considered
an error, so we set it to the lowest value we found (but we don't modify the
database). The data in database should be checked and fixed.
+% DATASRC_DATABASE_WILDCARD constructing RRset %3 from wildcard %2 in %1
+The database doesn't contain directly matching domain, but it does contain a
+wildcard one which is being used to synthesize the answer.
+
+% DATASRC_DATABASE_WILDCARD_CANCEL_NS canceled wildcard match on %2 because %3 contains NS in %1
+The database was queried to provide glue data and it didn't find direct match.
+It could create it from given wildcard, but matching wildcards is forbidden
+under a zone cut, which was found. Therefore the delegation will be returned
+instead.
+
+% DATASRC_DATABASE_WILDCARD_CANCEL_SUB wildcard %2 can't be used to construct %3 because %4 exists in %1
+The answer could be constructed using the wildcard, but the given subdomain
+exists, therefore this name is something like empty non-terminal (actually,
+from the protocol point of view, it is empty non-terminal, but the code
+discovers it differently).
+
+% DATASRC_DATABASE_WILDCARD_EMPTY implicit wildcard %2 used to construct %3 in %1
+The given wildcard exists implicitly in the domainspace, as empty nonterminal
+(eg. there's something like subdomain.*.example.org, so *.example.org exists
+implicitly, but is empty). This will produce NXRRSET, because the constructed
+domain is empty as well as the wildcard.
+
% DATASRC_DO_QUERY handling query for '%1/%2'
A debug message indicating that a query for the given name and RR type is being
processed.
diff --git a/src/lib/datasrc/sqlite3_accessor.cc b/src/lib/datasrc/sqlite3_accessor.cc
index 092d919..3cbaeab 100644
--- a/src/lib/datasrc/sqlite3_accessor.cc
+++ b/src/lib/datasrc/sqlite3_accessor.cc
@@ -21,6 +21,8 @@
#include <boost/lexical_cast.hpp>
+#define SQLITE_SCHEMA_VERSION 1
+
namespace isc {
namespace datasrc {
@@ -121,6 +123,8 @@ 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
@@ -177,29 +181,90 @@ prepare(sqlite3* const db, const char* const statement) {
return (prepared);
}
-void
-checkAndSetupSchema(Initializer* initializer) {
- sqlite3* const db = initializer->params_.db_;
+// 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() {
+ struct timespec req;
+ req.tv_sec = 0;
+ req.tv_nsec = 100000000;
+ nanosleep(&req, NULL);
+}
+// returns the schema version if the schema version table exists
+// returns -1 if it does not
+int check_schema_version(sqlite3* db) {
sqlite3_stmt* prepared = NULL;
- if (sqlite3_prepare_v2(db, "SELECT version FROM schema_version", -1,
- &prepared, NULL) == SQLITE_OK &&
- sqlite3_step(prepared) == SQLITE_ROW) {
- initializer->params_.version_ = sqlite3_column_int(prepared, 0);
- sqlite3_finalize(prepared);
- } else {
- logger.info(DATASRC_SQLITE_SETUP);
- if (prepared != NULL) {
- sqlite3_finalize(prepared);
+ // 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);
+ if (rc == SQLITE_ERROR) {
+ // this is the error that is returned when the table does not
+ // exist
+ return (-1);
+ } else if (rc == SQLITE_OK) {
+ break;
+ } else if (rc != SQLITE_BUSY || i == 50) {
+ isc_throw(SQLite3Error, "Unable to prepare version query: "
+ << rc << " " << sqlite3_errmsg(db));
+ }
+ do_sleep();
+ }
+ if (sqlite3_step(prepared) != SQLITE_ROW) {
+ isc_throw(SQLite3Error,
+ "Unable to query version: " << sqlite3_errmsg(db));
+ }
+ int version = sqlite3_column_int(prepared, 0);
+ sqlite3_finalize(prepared);
+ return (version);
+}
+
+// return db version
+int create_database(sqlite3* db) {
+ // try to get an exclusive lock. Once that is obtained, do the version
+ // check *again*, just in case this process was racing another
+ //
+ // try for 5 secs (50*0.1)
+ int rc;
+ logger.info(DATASRC_SQLITE_SETUP);
+ for (size_t i = 0; i < 50; ++i) {
+ rc = sqlite3_exec(db, "BEGIN EXCLUSIVE TRANSACTION", NULL, NULL,
+ NULL);
+ if (rc == SQLITE_OK) {
+ break;
+ } else if (rc != SQLITE_BUSY || i == 50) {
+ isc_throw(SQLite3Error, "Unable to acquire exclusive lock "
+ "for database creation: " << sqlite3_errmsg(db));
}
+ do_sleep();
+ }
+ int schema_version = check_schema_version(db);
+ if (schema_version == -1) {
for (int i = 0; SCHEMA_LIST[i] != NULL; ++i) {
if (sqlite3_exec(db, SCHEMA_LIST[i], NULL, NULL, NULL) !=
SQLITE_OK) {
isc_throw(SQLite3Error,
- "Failed to set up schema " << SCHEMA_LIST[i]);
+ "Failed to set up schema " << SCHEMA_LIST[i]);
}
}
+ sqlite3_exec(db, "COMMIT TRANSACTION", NULL, NULL, NULL);
+ return (SQLITE_SCHEMA_VERSION);
+ } else {
+ return (schema_version);
+ }
+}
+
+void
+checkAndSetupSchema(Initializer* initializer) {
+ sqlite3* const db = initializer->params_.db_;
+
+ int schema_version = check_schema_version(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
@@ -280,14 +345,14 @@ SQLite3Database::close(void) {
}
std::pair<bool, int>
-SQLite3Database::getZone(const isc::dns::Name& name) const {
+SQLite3Database::getZone(const std::string& name) const {
int rc;
// 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.toText().c_str(),
- -1, SQLITE_TRANSIENT);
+ rc = sqlite3_bind_text(dbparameters_->q_zone_, 1, name.c_str(),
+ -1, SQLITE_STATIC);
if (rc != SQLITE_OK) {
isc_throw(SQLite3Error, "Could not bind " << name <<
" to SQL statement (zone)");
diff --git a/src/lib/datasrc/sqlite3_accessor.h b/src/lib/datasrc/sqlite3_accessor.h
index cc5c657..bcbf358 100644
--- a/src/lib/datasrc/sqlite3_accessor.h
+++ b/src/lib/datasrc/sqlite3_accessor.h
@@ -87,11 +87,11 @@ public:
*
* \exception SQLite3Error if something about the database is broken.
*
- * \param name The name of zone to look up
+ * \param name The (fully qualified) domain name of zone to look up
* \return The pair contains if the lookup was successful in the first
* element and the zone id in the second if it was.
*/
- virtual std::pair<bool, int> getZone(const isc::dns::Name& name) const;
+ virtual std::pair<bool, int> getZone(const std::string& name) const;
/** \brief Look up all resource records for a name
*
diff --git a/src/lib/datasrc/sqlite3_datasrc.cc b/src/lib/datasrc/sqlite3_datasrc.cc
index 18ee929..03b057c 100644
--- a/src/lib/datasrc/sqlite3_datasrc.cc
+++ b/src/lib/datasrc/sqlite3_datasrc.cc
@@ -26,6 +26,8 @@
#include <dns/rrset.h>
#include <dns/rrsetlist.h>
+#define SQLITE_SCHEMA_VERSION 1
+
using namespace std;
using namespace isc::dns;
using namespace isc::dns::rdata;
@@ -77,6 +79,8 @@ 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";
const char* const q_record_str = "SELECT rdtype, ttl, sigtype, rdata "
@@ -254,7 +258,7 @@ Sqlite3DataSrc::findRecords(const Name& name, const RRType& rdtype,
}
break;
}
-
+
sqlite3_reset(query);
sqlite3_clear_bindings(query);
@@ -295,7 +299,7 @@ Sqlite3DataSrc::findRecords(const Name& name, const RRType& rdtype,
//
sqlite3_reset(dbparameters->q_count_);
sqlite3_clear_bindings(dbparameters->q_count_);
-
+
rc = sqlite3_bind_int(dbparameters->q_count_, 1, zone_id);
if (rc != SQLITE_OK) {
isc_throw(Sqlite3Error, "Could not bind zone ID " << zone_id <<
@@ -653,29 +657,90 @@ prepare(sqlite3* const db, const char* const statement) {
return (prepared);
}
-void
-checkAndSetupSchema(Sqlite3Initializer* initializer) {
- sqlite3* const db = initializer->params_.db_;
+// 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() {
+ struct timespec req;
+ req.tv_sec = 0;
+ req.tv_nsec = 100000000;
+ nanosleep(&req, NULL);
+}
+// returns the schema version if the schema version table exists
+// returns -1 if it does not
+int check_schema_version(sqlite3* db) {
sqlite3_stmt* prepared = NULL;
- if (sqlite3_prepare_v2(db, "SELECT version FROM schema_version", -1,
- &prepared, NULL) == SQLITE_OK &&
- sqlite3_step(prepared) == SQLITE_ROW) {
- initializer->params_.version_ = sqlite3_column_int(prepared, 0);
- sqlite3_finalize(prepared);
- } else {
- logger.info(DATASRC_SQLITE_SETUP);
- if (prepared != NULL) {
- sqlite3_finalize(prepared);
+ // 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);
+ if (rc == SQLITE_ERROR) {
+ // this is the error that is returned when the table does not
+ // exist
+ return (-1);
+ } else if (rc == SQLITE_OK) {
+ break;
+ } else if (rc != SQLITE_BUSY || i == 50) {
+ isc_throw(Sqlite3Error, "Unable to prepare version query: "
+ << rc << " " << sqlite3_errmsg(db));
}
+ do_sleep();
+ }
+ if (sqlite3_step(prepared) != SQLITE_ROW) {
+ isc_throw(Sqlite3Error,
+ "Unable to query version: " << sqlite3_errmsg(db));
+ }
+ int version = sqlite3_column_int(prepared, 0);
+ sqlite3_finalize(prepared);
+ return (version);
+}
+
+// return db version
+int create_database(sqlite3* db) {
+ // try to get an exclusive lock. Once that is obtained, do the version
+ // check *again*, just in case this process was racing another
+ //
+ // try for 5 secs (50*0.1)
+ int rc;
+ logger.info(DATASRC_SQLITE_SETUP);
+ for (size_t i = 0; i < 50; ++i) {
+ rc = sqlite3_exec(db, "BEGIN EXCLUSIVE TRANSACTION", NULL, NULL,
+ NULL);
+ if (rc == SQLITE_OK) {
+ break;
+ } else if (rc != SQLITE_BUSY || i == 50) {
+ isc_throw(Sqlite3Error, "Unable to acquire exclusive lock "
+ "for database creation: " << sqlite3_errmsg(db));
+ }
+ do_sleep();
+ }
+ int schema_version = check_schema_version(db);
+ if (schema_version == -1) {
for (int i = 0; SCHEMA_LIST[i] != NULL; ++i) {
if (sqlite3_exec(db, SCHEMA_LIST[i], NULL, NULL, NULL) !=
SQLITE_OK) {
isc_throw(Sqlite3Error,
- "Failed to set up schema " << SCHEMA_LIST[i]);
+ "Failed to set up schema " << SCHEMA_LIST[i]);
}
}
+ sqlite3_exec(db, "COMMIT TRANSACTION", NULL, NULL, NULL);
+ return (SQLITE_SCHEMA_VERSION);
+ } else {
+ return (schema_version);
+ }
+}
+
+void
+checkAndSetupSchema(Sqlite3Initializer* initializer) {
+ sqlite3* const db = initializer->params_.db_;
+
+ int schema_version = check_schema_version(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);
initializer->params_.q_record_ = prepare(db, q_record_str);
diff --git a/src/lib/datasrc/tests/Makefile.am b/src/lib/datasrc/tests/Makefile.am
index a913818..eb0e3ad 100644
--- a/src/lib/datasrc/tests/Makefile.am
+++ b/src/lib/datasrc/tests/Makefile.am
@@ -3,6 +3,7 @@ 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_CXXFLAGS = $(B10_CXXFLAGS)
diff --git a/src/lib/datasrc/tests/database_unittest.cc b/src/lib/datasrc/tests/database_unittest.cc
index 823c2bc..13bda70 100644
--- a/src/lib/datasrc/tests/database_unittest.cc
+++ b/src/lib/datasrc/tests/database_unittest.cc
@@ -44,14 +44,14 @@ public:
NopAccessor() : database_name_("mock_database")
{ }
- virtual std::pair<bool, int> getZone(const Name& name) const {
- if (name == Name("example.org")) {
+ virtual std::pair<bool, int> getZone(const std::string& name) const {
+ if (name == "example.org.") {
return (std::pair<bool, int>(true, 42));
- } else if (name == Name("null.example.org")) {
+ } else if (name == "null.example.org.") {
return (std::pair<bool, int>(true, 13));
- } else if (name == Name("empty.example.org")) {
+ } else if (name == "empty.example.org.") {
return (std::pair<bool, int>(true, 0));
- } else if (name == Name("bad.example.org")) {
+ } else if (name == "bad.example.org.") {
return (std::pair<bool, int>(true, -1));
} else {
return (std::pair<bool, int>(false, 0));
@@ -458,6 +458,20 @@ private:
// 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");
+ 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.");
}
};
@@ -670,12 +684,12 @@ doFindTest(shared_ptr<DatabaseClient::Finder> finder,
ZoneFinder::FindResult result =
finder->find(name, type, NULL, options);
ASSERT_EQ(expected_result, result.code) << name << " " << type;
- if (expected_rdatas.size() > 0) {
+ if (!expected_rdatas.empty()) {
checkRRset(result.rrset, expected_name != Name(".") ? expected_name :
name, finder->getClass(), expected_type, expected_ttl,
expected_rdatas);
- if (expected_sig_rdatas.size() > 0) {
+ if (!expected_sig_rdatas.empty()) {
checkRRset(result.rrset->getRRsig(), expected_name != Name(".") ?
expected_name : name, finder->getClass(),
isc::dns::RRType::RRSIG(), expected_ttl,
@@ -1074,6 +1088,16 @@ TEST_F(DatabaseClientTest, findDelegation) {
DataSourceError);
}
+TEST_F(DatabaseClientTest, emptyDomain) {
+ shared_ptr<DatabaseClient::Finder> finder(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_);
+}
+
// Glue-OK mode. Just go trough NS delegations down (but not trough
// DNAME) and pretend it is not there.
TEST_F(DatabaseClientTest, glueOK) {
@@ -1133,15 +1157,127 @@ TEST_F(DatabaseClientTest, glueOK) {
ZoneFinder::FIND_GLUE_OK);
}
-TEST_F(DatabaseClientTest, empty) {
+TEST_F(DatabaseClientTest, wildcard) {
shared_ptr<DatabaseClient::Finder> finder(getFinder());
- // Check empty domain
- // 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_);
+ // First, simple wildcard match
+ expected_rdatas_.push_back("192.0.2.5");
+ doFindTest(finder, isc::dns::Name("a.wild.example.org"),
+ isc::dns::RRType::A(), isc::dns::RRType::A(),
+ isc::dns::RRTTL(3600), ZoneFinder::SUCCESS, 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::SUCCESS, expected_rdatas_,
+ expected_sig_rdatas_);
+ expected_rdatas_.clear();
+ doFindTest(finder, isc::dns::Name("a.wild.example.org"),
+ isc::dns::RRType::AAAA(), isc::dns::RRType::AAAA(),
+ isc::dns::RRTTL(3600), ZoneFinder::NXRRSET, expected_rdatas_,
+ 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::NXRRSET, expected_rdatas_,
+ expected_sig_rdatas_);
+
+ // Direct request for thi wildcard
+ expected_rdatas_.push_back("192.0.2.5");
+ 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();
+ 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 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_);
+ // 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,
+ 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_);
+ // 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_);
+ // 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"),
+ isc::dns::RRType::AAAA(), isc::dns::RRType::AAAA(),
+ isc::dns::RRTTL(3600), ZoneFinder::SUCCESS,
+ expected_rdatas_, expected_sig_rdatas_);
+ 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_,
+ 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_,
+ isc::dns::Name("delegatedwild.example.org"),
+ ZoneFinder::FIND_GLUE_OK);
+
+ expected_rdatas_.clear();
+ expected_rdatas_.push_back("192.0.2.5");
+ // These are direct matches
+ const char* positive_names[] = {
+ "wild.*.foo.example.org.",
+ "wild.*.foo.*.bar.example.org.",
+ 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_);
+ }
+
+ // These are wildcard matches against empty nonterminal asterisk
+ expected_rdatas_.clear();
+ const char* negative_names[] = {
+ "a.foo.example.org.",
+ "*.foo.example.org.",
+ "foo.example.org.",
+ "wild.bar.foo.example.org.",
+ "baz.foo.*.bar.example.org",
+ "baz.foo.baz.bar.example.org",
+ "*.foo.baz.bar.example.org",
+ "*.foo.*.bar.example.org",
+ "foo.*.bar.example.org",
+ "*.bar.example.org",
+ "bar.example.org",
+ 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_);
+ }
}
TEST_F(DatabaseClientTest, getOrigin) {
diff --git a/src/lib/datasrc/tests/sqlite3_accessor_unittest.cc b/src/lib/datasrc/tests/sqlite3_accessor_unittest.cc
index 2526f98..b91f3aa 100644
--- a/src/lib/datasrc/tests/sqlite3_accessor_unittest.cc
+++ b/src/lib/datasrc/tests/sqlite3_accessor_unittest.cc
@@ -19,6 +19,8 @@
#include <gtest/gtest.h>
#include <boost/scoped_ptr.hpp>
+#include <fstream>
+#include <sqlite3.h>
using namespace isc::datasrc;
using isc::data::ConstElementPtr;
@@ -43,6 +45,10 @@ std::string SQLITE_DBFILE_EXAMPLE_ORG = TEST_DATA_DIR "/example.org.sqlite3";
// The "nodir", a non existent directory, is inserted for this purpose.
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";
+
// Opening works (the content is tested in different tests)
TEST(SQLite3Open, common) {
EXPECT_NO_THROW(SQLite3Database db(SQLITE_DBFILE_EXAMPLE,
@@ -83,25 +89,25 @@ public:
// This zone exists in the data, so it should be found
TEST_F(SQLite3Access, getZone) {
- std::pair<bool, int> result(db->getZone(Name("example.com")));
+ std::pair<bool, int> result(db->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(Name("sub.example.com")).first);
+ EXPECT_FALSE(db->getZone("sub.example.com.").first);
}
// This zone is not there at all
TEST_F(SQLite3Access, noZone) {
- EXPECT_FALSE(db->getZone(Name("example.org")).first);
+ EXPECT_FALSE(db->getZone("example.org.").first);
}
// This zone is there, but in different class
TEST_F(SQLite3Access, noClass) {
initAccessor(SQLITE_DBFILE_EXAMPLE, RRClass::CH());
- EXPECT_FALSE(db->getZone(Name("example.com")).first);
+ EXPECT_FALSE(db->getZone("example.com.").first);
}
// This tests the iterator context
@@ -109,7 +115,7 @@ TEST_F(SQLite3Access, 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(Name("example.org")));
+ const std::pair<bool, int> zone_info(db->getZone("example.org."));
ASSERT_TRUE(zone_info.first);
// Get the iterator context
@@ -223,7 +229,7 @@ checkRecordRow(const std::string columns[],
}
TEST_F(SQLite3Access, getRecords) {
- const std::pair<bool, int> zone_info(db->getZone(Name("example.com")));
+ const std::pair<bool, int> zone_info(db->getZone("example.com."));
ASSERT_TRUE(zone_info.first);
const int zone_id = zone_info.second;
@@ -339,4 +345,63 @@ TEST_F(SQLite3Access, getRecords) {
EXPECT_FALSE(context->getNext(columns));
}
+// Test fixture for creating a db that automatically deletes it before start,
+// and when done
+class SQLite3Create : public ::testing::Test {
+public:
+ SQLite3Create() {
+ remove(SQLITE_NEW_DBFILE);
+ }
+
+ ~SQLite3Create() {
+ remove(SQLITE_NEW_DBFILE);
+ }
+};
+
+bool exists(const char* filename) {
+ std::ifstream f(filename);
+ return (f != NULL);
+}
+
+TEST_F(SQLite3Create, creationtest) {
+ ASSERT_FALSE(exists(SQLITE_NEW_DBFILE));
+ // Should simply be created
+ SQLite3Database db(SQLITE_NEW_DBFILE, RRClass::IN());
+ ASSERT_TRUE(exists(SQLITE_NEW_DBFILE));
+}
+
+TEST_F(SQLite3Create, emptytest) {
+ ASSERT_FALSE(exists(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());
+
+ sqlite3_close(db);
+
+ // should work now that we closed it
+ SQLite3Database db3(SQLITE_NEW_DBFILE, RRClass::IN());
+}
+
+TEST_F(SQLite3Create, lockedtest) {
+ ASSERT_FALSE(exists(SQLITE_NEW_DBFILE));
+
+ // open one manually
+ sqlite3* db;
+ ASSERT_EQ(SQLITE_OK, sqlite3_open(SQLITE_NEW_DBFILE, &db));
+ 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()),
+ SQLite3Error);
+
+ sqlite3_exec(db, "ROLLBACK TRANSACTION", NULL, NULL, NULL);
+
+ // should work now that we closed it
+ SQLite3Database db3(SQLITE_NEW_DBFILE, RRClass::IN());
+}
+
} // end anonymous namespace
diff --git a/src/lib/python/isc/datasrc/sqlite3_ds.py b/src/lib/python/isc/datasrc/sqlite3_ds.py
index a77645a..fd63741 100644
--- a/src/lib/python/isc/datasrc/sqlite3_ds.py
+++ b/src/lib/python/isc/datasrc/sqlite3_ds.py
@@ -33,44 +33,63 @@ def create(cur):
Arguments:
cur - sqlite3 cursor.
"""
- cur.execute("CREATE TABLE schema_version (version INTEGER NOT NULL)")
- cur.execute("INSERT INTO schema_version VALUES (1)")
- cur.execute("""CREATE TABLE zones (id INTEGER PRIMARY KEY,
- name STRING NOT NULL COLLATE NOCASE,
- rdclass STRING NOT NULL COLLATE NOCASE DEFAULT 'IN',
- dnssec BOOLEAN NOT NULL DEFAULT 0)""")
- cur.execute("CREATE INDEX zones_byname ON zones (name)")
- cur.execute("""CREATE TABLE records (id INTEGER PRIMARY KEY,
- zone_id INTEGER NOT NULL,
- name STRING NOT NULL COLLATE NOCASE,
- rname STRING NOT NULL COLLATE NOCASE,
- ttl INTEGER NOT NULL,
- rdtype STRING NOT NULL COLLATE NOCASE,
- sigtype STRING COLLATE NOCASE,
- rdata STRING NOT NULL)""")
- cur.execute("CREATE INDEX records_byname ON records (name)")
- cur.execute("CREATE INDEX records_byrname ON records (rname)")
- cur.execute("""CREATE TABLE nsec3 (id INTEGER PRIMARY KEY,
- zone_id INTEGER NOT NULL,
- hash STRING NOT NULL COLLATE NOCASE,
- owner STRING NOT NULL COLLATE NOCASE,
- ttl INTEGER NOT NULL,
- rdtype STRING NOT NULL COLLATE NOCASE,
- rdata STRING NOT NULL)""")
- cur.execute("CREATE INDEX nsec3_byhash ON nsec3 (hash)")
-
-def open(dbfile):
+ # We are creating the database because it apparently had not been at
+ # the time we tried to read from it. However, another process may have
+ # had the same idea, resulting in a potential race condition.
+ # Therefore, we obtain an exclusive lock before we create anything
+ # When we have it, we check *again* whether the database has been
+ # initialized. If not, we do so.
+
+ # If the database is perpetually locked, it'll time out automatically
+ # and we just let it fail.
+ cur.execute("BEGIN EXCLUSIVE TRANSACTION")
+ try:
+ cur.execute("SELECT version FROM schema_version")
+ row = cur.fetchone()
+ except sqlite3.OperationalError:
+ cur.execute("CREATE TABLE schema_version (version INTEGER NOT NULL)")
+ cur.execute("INSERT INTO schema_version VALUES (1)")
+ cur.execute("""CREATE TABLE zones (id INTEGER PRIMARY KEY,
+ name STRING NOT NULL COLLATE NOCASE,
+ rdclass STRING NOT NULL COLLATE NOCASE DEFAULT 'IN',
+ dnssec BOOLEAN NOT NULL DEFAULT 0)""")
+ cur.execute("CREATE INDEX zones_byname ON zones (name)")
+ cur.execute("""CREATE TABLE records (id INTEGER PRIMARY KEY,
+ zone_id INTEGER NOT NULL,
+ name STRING NOT NULL COLLATE NOCASE,
+ rname STRING NOT NULL COLLATE NOCASE,
+ ttl INTEGER NOT NULL,
+ rdtype STRING NOT NULL COLLATE NOCASE,
+ sigtype STRING COLLATE NOCASE,
+ rdata STRING NOT NULL)""")
+ cur.execute("CREATE INDEX records_byname ON records (name)")
+ cur.execute("CREATE INDEX records_byrname ON records (rname)")
+ cur.execute("""CREATE TABLE nsec3 (id INTEGER PRIMARY KEY,
+ zone_id INTEGER NOT NULL,
+ hash STRING NOT NULL COLLATE NOCASE,
+ owner STRING NOT NULL COLLATE NOCASE,
+ ttl INTEGER NOT NULL,
+ rdtype STRING NOT NULL COLLATE NOCASE,
+ rdata STRING NOT NULL)""")
+ cur.execute("CREATE INDEX nsec3_byhash ON nsec3 (hash)")
+ row = [1]
+ cur.execute("COMMIT TRANSACTION")
+ return row
+
+def open(dbfile, connect_timeout=5.0):
""" Open a database, if the database is not yet set up, call create
to do so. It may raise Sqlite3DSError if failed to open sqlite3
database file or find bad database schema version in the database.
Arguments:
dbfile - the filename for the sqlite3 database.
+ connect_timeout - timeout for opening the database or acquiring locks
+ defaults to sqlite3 module's default of 5.0 seconds
Return sqlite3 connection, sqlite3 cursor.
"""
try:
- conn = sqlite3.connect(dbfile)
+ conn = sqlite3.connect(dbfile, timeout=connect_timeout)
cur = conn.cursor()
except Exception as e:
fail = "Failed to open " + dbfile + ": " + e.args[0]
@@ -80,10 +99,13 @@ def open(dbfile):
try:
cur.execute("SELECT version FROM schema_version")
row = cur.fetchone()
- except:
- create(cur)
- conn.commit()
- row = [1]
+ except sqlite3.OperationalError:
+ # temporarily disable automatic transactions so
+ # we can do our own
+ iso_lvl = conn.isolation_level
+ conn.isolation_level = None
+ row = create(cur)
+ conn.isolation_level = iso_lvl
if row == None or row[0] != 1:
raise Sqlite3DSError("Bad database schema version")
diff --git a/src/lib/python/isc/datasrc/tests/sqlite3_ds_test.py b/src/lib/python/isc/datasrc/tests/sqlite3_ds_test.py
index 707994f..10c61cf 100644
--- a/src/lib/python/isc/datasrc/tests/sqlite3_ds_test.py
+++ b/src/lib/python/isc/datasrc/tests/sqlite3_ds_test.py
@@ -23,8 +23,9 @@ TESTDATA_PATH = os.environ['TESTDATA_PATH'] + os.sep
TESTDATA_WRITE_PATH = os.environ['TESTDATA_WRITE_PATH'] + os.sep
READ_ZONE_DB_FILE = TESTDATA_PATH + "example.com.sqlite3"
-WRITE_ZONE_DB_FILE = TESTDATA_WRITE_PATH + "example.com.out.sqlite3"
BROKEN_DB_FILE = TESTDATA_PATH + "brokendb.sqlite3"
+WRITE_ZONE_DB_FILE = TESTDATA_WRITE_PATH + "example.com.out.sqlite3"
+NEW_DB_FILE = TESTDATA_WRITE_PATH + "new_db.sqlite3"
def example_reader():
my_zone = [
@@ -91,5 +92,52 @@ class TestSqlite3_ds(unittest.TestCase):
# and make sure lock does not stay
sqlite3_ds.load(WRITE_ZONE_DB_FILE, ".", example_reader)
+class NewDBFile(unittest.TestCase):
+ def tearDown(self):
+ # remove the created database after every test
+ if (os.path.exists(NEW_DB_FILE)):
+ os.remove(NEW_DB_FILE)
+
+ def setUp(self):
+ # remove the created database before every test too, just
+ # in case a test got aborted half-way, and cleanup didn't occur
+ if (os.path.exists(NEW_DB_FILE)):
+ os.remove(NEW_DB_FILE)
+
+ def test_new_db(self):
+ self.assertFalse(os.path.exists(NEW_DB_FILE))
+ sqlite3_ds.open(NEW_DB_FILE)
+ self.assertTrue(os.path.exists(NEW_DB_FILE))
+
+ def test_new_db_locked(self):
+ self.assertFalse(os.path.exists(NEW_DB_FILE))
+ con = sqlite3.connect(NEW_DB_FILE);
+ con.isolation_level = None
+ cur = con.cursor()
+ cur.execute("BEGIN IMMEDIATE TRANSACTION")
+
+ # load should now fail, since the database is locked,
+ # and the open() call needs an exclusive lock
+ self.assertRaises(sqlite3.OperationalError,
+ sqlite3_ds.open, NEW_DB_FILE, 0.1)
+
+ con.rollback()
+ cur.close()
+ con.close()
+ self.assertTrue(os.path.exists(NEW_DB_FILE))
+
+ # now that we closed our connection, load should work again
+ sqlite3_ds.open(NEW_DB_FILE)
+
+ # the database should now have been created, and a new load should
+ # not require an exclusive lock anymore, so we lock it again
+ con = sqlite3.connect(NEW_DB_FILE);
+ cur = con.cursor()
+ cur.execute("BEGIN IMMEDIATE TRANSACTION")
+ sqlite3_ds.open(NEW_DB_FILE, 0.1)
+ con.rollback()
+ cur.close()
+ con.close()
+
if __name__ == '__main__':
unittest.main()
diff --git a/src/lib/python/isc/log/tests/Makefile.am b/src/lib/python/isc/log/tests/Makefile.am
index 6bb67de..a23887c 100644
--- a/src/lib/python/isc/log/tests/Makefile.am
+++ b/src/lib/python/isc/log/tests/Makefile.am
@@ -1,6 +1,8 @@
PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
-PYTESTS = log_test.py
-EXTRA_DIST = $(PYTESTS) log_console.py.in console.out check_output.sh
+PYTESTS_GEN = log_console.py
+PYTESTS_NOGEN = log_test.py
+noinst_SCRIPTS = $(PYTESTS_GEN)
+EXTRA_DIST = console.out check_output.sh $(PYTESTS_NOGEN)
# If necessary (rare cases), explicitly specify paths to dynamic libraries
# required by loadable python modules.
@@ -10,7 +12,9 @@ LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cc/.
endif
# test using command-line arguments, so use check-local target instead of TESTS
+# We need to run the cycle twice, because once the files are in builddir, once in srcdir
check-local:
+ chmod +x $(abs_builddir)/log_console.py
$(LIBRARY_PATH_PLACEHOLDER) \
env PYTHONPATH=$(abs_top_srcdir)/src/lib/python:$(abs_top_builddir)/src/lib/python:$(abs_top_builddir)/src/lib/python/isc/log \
$(abs_srcdir)/check_output.sh $(abs_builddir)/log_console.py $(abs_srcdir)/console.out
@@ -19,10 +23,18 @@ if ENABLE_PYTHON_COVERAGE
rm -f .coverage
${LN_S} $(abs_top_srcdir)/.coverage .coverage
endif
- for pytest in $(PYTESTS) ; do \
+ for pytest in $(PYTESTS_NOGEN) ; do \
echo Running test: $$pytest ; \
$(LIBRARY_PATH_PLACEHOLDER) \
env PYTHONPATH=$(abs_top_srcdir)/src/lib/python:$(abs_top_builddir)/src/lib/python:$(abs_top_builddir)/src/lib/python/isc/log:$(abs_top_builddir)/src/lib/log/python/.libs \
B10_TEST_PLUGIN_DIR=$(abs_top_srcdir)/src/bin/cfgmgr/plugins \
$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
+ done ; \
+ for pytest in $(PYTESTS_GEN) ; do \
+ echo Running test: $$pytest ; \
+ chmod +x $(abs_builddir)/$$pytest ; \
+ $(LIBRARY_PATH_PLACEHOLDER) \
+ env PYTHONPATH=$(abs_top_srcdir)/src/lib/python:$(abs_top_builddir)/src/lib/python:$(abs_top_builddir)/src/lib/python/isc/log:$(abs_top_builddir)/src/lib/log/python/.libs \
+ B10_TEST_PLUGIN_DIR=$(abs_top_srcdir)/src/bin/cfgmgr/plugins \
+ $(PYCOVERAGE_RUN) $(abs_builddir)/$$pytest || exit ; \
done
More information about the bind10-changes
mailing list