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