Browse Source

Merge #1976

Finally! This one was big.

With some minor updates to catch up to the changes after merge.

Conflicts:
	src/bin/auth/auth_srv.cc
	src/bin/auth/auth_srv.h
	src/bin/auth/tests/auth_srv_unittest.cc
	src/lib/datasrc/client_list.cc
	src/lib/datasrc/tests/client_list_unittest.cc
Michal 'vorner' Vaner 12 years ago
parent
commit
0d4685b3e7
67 changed files with 2084 additions and 1789 deletions
  1. 1 0
      configure.ac
  2. 1 0
      src/bin/auth/Makefile.am
  3. 0 4
      src/bin/auth/auth.spec.pre.in
  4. 9 90
      src/bin/auth/auth_config.cc
  5. 51 146
      src/bin/auth/auth_srv.cc
  6. 29 103
      src/bin/auth/auth_srv.h
  7. 0 13
      src/bin/auth/b10-auth.xml
  8. 7 31
      src/bin/auth/benchmarks/query_bench.cc
  9. 30 137
      src/bin/auth/command.cc
  10. 223 0
      src/bin/auth/datasrc_configurator.h
  11. 12 8
      src/bin/auth/main.cc
  12. 27 27
      src/bin/auth/query.cc
  13. 9 11
      src/bin/auth/query.h
  14. 5 0
      src/bin/auth/tests/Makefile.am
  15. 286 211
      src/bin/auth/tests/auth_srv_unittest.cc
  16. 78 185
      src/bin/auth/tests/command_unittest.cc
  17. 5 383
      src/bin/auth/tests/config_unittest.cc
  18. 298 0
      src/bin/auth/tests/datasrc_configurator_unittest.cc
  19. 122 86
      src/bin/auth/tests/query_unittest.cc
  20. 1 0
      src/bin/auth/tests/testdata/Makefile.am
  21. 6 0
      src/bin/auth/tests/testdata/spec.spec
  22. 0 11
      src/bin/bind10/bind10.xml
  23. 2 7
      src/bin/bind10/bind10_src.py.in
  24. 0 2
      src/bin/bind10/tests/bind10_test.py.in
  25. 6 3
      src/bin/cfgmgr/plugins/Makefile.am
  26. 66 0
      src/bin/cfgmgr/plugins/datasrc.spec.pre.in
  27. 36 0
      src/bin/cfgmgr/plugins/datasrc_config_plugin.py
  28. 1 1
      src/bin/cfgmgr/plugins/tests/Makefile.am
  29. 36 0
      src/bin/cfgmgr/plugins/tests/datasrc_test.py
  30. 17 21
      src/bin/ddns/tests/ddns_test.py
  31. 7 143
      src/bin/xfrin/tests/xfrin_test.py
  32. 1 1
      src/bin/xfrin/xfrin.py.in
  33. 3 4
      src/bin/xfrin/xfrin_messages.mes
  34. 1 1
      src/lib/config/module_spec.cc
  35. 1 0
      src/lib/config/tests/module_spec_unittests.cc
  36. 1 0
      src/lib/config/tests/testdata/Makefile.am
  37. 13 0
      src/lib/config/tests/testdata/spec40.spec
  38. 97 35
      src/lib/datasrc/client_list.cc
  39. 54 2
      src/lib/datasrc/client_list.h
  40. 2 0
      src/lib/datasrc/static.zone.pre
  41. 197 22
      src/lib/datasrc/tests/client_list_unittest.cc
  42. 1 1
      src/lib/python/isc/datasrc/configurableclientlist_python.cc
  43. 10 43
      src/lib/python/isc/server_common/auth_command.py
  44. 0 6
      src/lib/python/isc/server_common/server_common_messages.mes
  45. 12 0
      tests/lettuce/configurations/ddns/ddns.config.orig
  46. 16 13
      tests/lettuce/configurations/ddns/noddns.config.orig
  47. 12 0
      tests/lettuce/configurations/example.org.config.orig
  48. 28 1
      tests/lettuce/configurations/example.org.inmem.config
  49. 12 0
      tests/lettuce/configurations/example2.org.config
  50. 16 8
      tests/lettuce/configurations/inmemory_over_sqlite3/secondary.conf
  51. 53 1
      tests/lettuce/configurations/ixfr-out/testset1-config.db
  52. 10 0
      tests/lettuce/configurations/multi_instance/multi_auth.config.orig
  53. 12 0
      tests/lettuce/configurations/no_db_file.config
  54. 36 1
      tests/lettuce/configurations/nsec3/nsec3_auth.config
  55. 16 9
      tests/lettuce/configurations/xfrin/inmem_slave.conf
  56. 10 0
      tests/lettuce/configurations/xfrin/retransfer_master.conf
  57. 10 0
      tests/lettuce/configurations/xfrin/retransfer_slave.conf
  58. 10 0
      tests/lettuce/configurations/xfrin/retransfer_slave_notify.conf
  59. 6 6
      tests/lettuce/features/example.feature
  60. 1 1
      tests/lettuce/features/terrain/bind10_control.py
  61. 20 0
      tests/system/bindctl/nsx1/b10-config.db.template.in
  62. 3 3
      tests/system/bindctl/tests.sh
  63. 5 2
      tests/system/glue/example.good
  64. 20 0
      tests/system/glue/nsx1/b10-config.db.in
  65. 0 2
      tests/system/glue/nsx1/root.db
  66. 6 4
      tests/system/glue/tests.sh
  67. 18 0
      tests/system/ixfr/b10-config.db.in

+ 1 - 0
configure.ac

@@ -1142,6 +1142,7 @@ AC_CONFIG_FILES([Makefile
 AC_OUTPUT([doc/version.ent
            src/bin/cfgmgr/b10-cfgmgr.py
            src/bin/cfgmgr/tests/b10-cfgmgr_test.py
+           src/bin/cfgmgr/plugins/datasrc.spec.pre
            src/bin/cmdctl/cmdctl.py
            src/bin/cmdctl/run_b10-cmdctl.sh
            src/bin/cmdctl/tests/cmdctl_test

+ 1 - 0
src/bin/auth/Makefile.am

@@ -48,6 +48,7 @@ b10_auth_SOURCES += auth_config.cc auth_config.h
 b10_auth_SOURCES += command.cc command.h
 b10_auth_SOURCES += common.h common.cc
 b10_auth_SOURCES += statistics.cc statistics.h
+b10_auth_SOURCES += datasrc_configurator.h
 b10_auth_SOURCES += main.cc
 # This is a temporary workaround for #1206, where the InMemoryClient has been
 # moved to an ldopened library. We could add that library to LDADD, but that

+ 0 - 4
src/bin/auth/auth.spec.pre.in

@@ -125,10 +125,6 @@
 	  {
             "item_name": "origin", "item_type": "string",
             "item_optional": false, "item_default": ""
-          },
-	  {
-            "item_name": "datasrc", "item_type": "string",
-            "item_optional": true, "item_default": "memory"
           }
         ]
       },

+ 9 - 90
src/bin/auth/auth_config.cc

@@ -43,20 +43,6 @@ using namespace isc::datasrc;
 using namespace isc::server_common::portconfig;
 
 namespace {
-/// A derived \c AuthConfigParser class for the "datasources" configuration
-/// identifier.
-class DatasourcesConfig : public AuthConfigParser {
-public:
-    DatasourcesConfig(AuthSrv& server) : server_(server)
-    {}
-    virtual void build(ConstElementPtr config_value);
-    virtual void commit();
-private:
-    AuthSrv& server_;
-    vector<boost::shared_ptr<AuthConfigParser> > datasources_;
-    set<string> configured_sources_;
-    vector<pair<RRClass, DataSourceClientContainerPtr> > clients_;
-};
 
 /// A derived \c AuthConfigParser for the version value
 /// (which is not used at this moment)
@@ -67,79 +53,6 @@ public:
     virtual void commit() {};
 };
 
-void
-DatasourcesConfig::build(ConstElementPtr config_value) {
-    BOOST_FOREACH(ConstElementPtr datasrc_elem, config_value->listValue()) {
-        // The caller is supposed to perform syntax-level checks, but we'll
-        // do minimum level of validation ourselves so that we won't crash due
-        // to a buggy application.
-        ConstElementPtr datasrc_type = datasrc_elem->get("type");
-        if (!datasrc_type) {
-            isc_throw(AuthConfigError, "Missing data source type");
-        }
-
-        if (configured_sources_.find(datasrc_type->stringValue()) !=
-            configured_sources_.end()) {
-            isc_throw(AuthConfigError, "Data source type '" <<
-                      datasrc_type->stringValue() << "' already configured");
-        }
-
-        // Apart from that it's not really easy to get at the default
-        // class value for the class here, it should probably really
-        // be a property of the instantiated data source. For now
-        // use hardcoded default IN.
-        const RRClass rrclass =
-            datasrc_elem->contains("class") ?
-            RRClass(datasrc_elem->get("class")->stringValue()) : RRClass::IN();
-
-        // Right now, we only support the in-memory data source for the
-        // RR class of IN.  We reject other cases explicitly by hardcoded
-        // checks.  This will soon be generalized, at which point these
-        // checks will also have to be cleaned up.
-        if (rrclass != RRClass::IN()) {
-            isc_throw(isc::InvalidParameter, "Unsupported data source class: "
-                      << rrclass);
-        }
-        if (datasrc_type->stringValue() != "memory") {
-            isc_throw(AuthConfigError, "Unsupported data source type: "
-                      << datasrc_type->stringValue());
-        }
-
-        // Create a new client for the specified data source and store it
-        // in the local vector.  For now, we always build a new client
-        // from the scratch, and replace any existing ones with the new ones.
-        // We might eventually want to optimize building zones (in case of
-        // reloading) by selectively loading fresh zones for data source
-        // where zone loading is expensive (such as in-memory).
-        clients_.push_back(
-            pair<RRClass, DataSourceClientContainerPtr>(
-                rrclass,
-                DataSourceClientContainerPtr(new DataSourceClientContainer(
-                                                 datasrc_type->stringValue(),
-                                                 datasrc_elem))));
-
-        configured_sources_.insert(datasrc_type->stringValue());
-    }
-}
-
-void
-DatasourcesConfig::commit() {
-    // As noted in build(), the current implementation only supports the
-    // in-memory data source for class IN, and build() should have ensured
-    // it.  So, depending on the vector is empty or not, we either clear
-    // or install an in-memory data source for the server.
-    //
-    // When we generalize it, we'll somehow install all data source clients
-    // built in the vector, clearing deleted ones from the server.
-    if (clients_.empty()) {
-        server_.setInMemoryClient(RRClass::IN(),
-                                  DataSourceClientContainerPtr());
-    } else {
-        server_.setInMemoryClient(clients_.front().first,
-                                  clients_.front().second);
-    }
-}
-
 /// A derived \c AuthConfigParser class for the "statistics-internal"
 /// configuration identifier.
 class StatisticsIntervalConfig : public AuthConfigParser {
@@ -242,9 +155,7 @@ createAuthConfigParser(AuthSrv& server, const std::string& config_id) {
     // simplicity.  In future we'll probably generalize it using map-like
     // data structure, and may even provide external register interface so
     // that it can be dynamically customized.
-    if (config_id == "datasources") {
-        return (new DatasourcesConfig(server));
-    } else if (config_id == "statistics-interval") {
+    if (config_id == "statistics-interval") {
         return (new StatisticsIntervalConfig(server));
     } else if (config_id == "listen_on") {
         return (new ListenAddressConfig(server));
@@ -261,6 +172,14 @@ createAuthConfigParser(AuthSrv& server, const std::string& config_id) {
         // later be used to mark backwards incompatible changes in the
         // config data
         return (new VersionConfig());
+    } else if (config_id == "datasources") {
+        // TODO: Ignored for now, since the value is probably used by
+        // other modules. Once they have been removed from there, remove
+        // it from here and the spec file.
+
+        // We need to return something. The VersionConfig is empty now,
+        // so we may abuse that one, as it is a short-term solution only.
+        return (new VersionConfig());
     } else {
         isc_throw(AuthConfigError, "Unknown configuration identifier: " <<
                   config_id);

+ 51 - 146
src/bin/auth/auth_srv.cc

@@ -43,9 +43,9 @@
 
 #include <datasrc/query.h>
 #include <datasrc/data_source.h>
-#include <datasrc/memory_datasrc.h>
 #include <datasrc/static_datasrc.h>
 #include <datasrc/sqlite3_datasrc.h>
+#include <datasrc/client_list.h>
 
 #include <xfr/xfrout_client.h>
 
@@ -222,10 +222,9 @@ private:
     AuthSrvImpl(const AuthSrvImpl& source);
     AuthSrvImpl& operator=(const AuthSrvImpl& source);
 public:
-    AuthSrvImpl(const bool use_cache, AbstractXfroutClient& xfrout_client,
+    AuthSrvImpl(AbstractXfroutClient& xfrout_client,
                 BaseSocketSessionForwarder& ddns_forwarder);
     ~AuthSrvImpl();
-    isc::data::ConstElementPtr setDbFile(isc::data::ConstElementPtr config);
 
     bool processNormalQuery(const IOMessage& io_message, Message& message,
                             OutputBuffer& buffer,
@@ -248,13 +247,6 @@ public:
     ModuleCCSession* config_session_;
     AbstractSession* xfrin_session_;
 
-    /// In-memory data source.  Currently class IN only for simplicity.
-    const RRClass memory_client_class_;
-    isc::datasrc::DataSourceClientContainerPtr memory_client_container_;
-
-    /// Hot spot cache
-    isc::datasrc::HotCache cache_;
-
     /// Interval timer for periodic submission of statistics counters.
     IntervalTimer statistics_timer_;
 
@@ -267,6 +259,21 @@ public:
     /// The TSIG keyring
     const boost::shared_ptr<TSIGKeyRing>* keyring_;
 
+    /// The client list
+    map<RRClass, boost::shared_ptr<ConfigurableClientList> > client_lists_;
+
+    boost::shared_ptr<ConfigurableClientList> getClientList(const RRClass&
+                                                            rrclass)
+    {
+        const map<RRClass, boost::shared_ptr<ConfigurableClientList> >::
+            const_iterator it(client_lists_.find(rrclass));
+        if (it == client_lists_.end()) {
+            return (boost::shared_ptr<ConfigurableClientList>());
+        } else {
+            return (it->second);
+        }
+    }
+
     /// Bind the ModuleSpec object in config_session_ with
     /// isc:config::ModuleSpec::validateStatistics.
     void registerStatisticsValidator();
@@ -296,14 +303,6 @@ public:
                       bool done);
 
 private:
-    std::string db_file_;
-
-    MetaDataSrc data_sources_;
-    /// We keep a pointer to the currently running sqlite datasource
-    /// so that we can specifically remove that one should the database
-    /// file change
-    ConstDataSrcPtr cur_datasrc_;
-
     bool xfrout_connected_;
     AbstractXfroutClient& xfrout_client_;
 
@@ -316,12 +315,10 @@ private:
     auth::Query query_;
 };
 
-AuthSrvImpl::AuthSrvImpl(const bool use_cache,
-                         AbstractXfroutClient& xfrout_client,
+AuthSrvImpl::AuthSrvImpl(AbstractXfroutClient& xfrout_client,
                          BaseSocketSessionForwarder& ddns_forwarder) :
     config_session_(NULL),
     xfrin_session_(NULL),
-    memory_client_class_(RRClass::IN()),
     statistics_timer_(io_service_),
     counters_(),
     keyring_(NULL),
@@ -329,17 +326,7 @@ AuthSrvImpl::AuthSrvImpl(const bool use_cache,
     ddns_forwarder_(NULL),
     xfrout_connected_(false),
     xfrout_client_(xfrout_client)
-{
-    // cur_datasrc_ is automatically initialized by the default constructor,
-    // effectively being an empty (sqlite) data source.  once ccsession is up
-    // the datasource will be set by the configuration setting
-
-    // add static data source
-    data_sources_.addDataSrc(ConstDataSrcPtr(new StaticDataSrc));
-
-    // enable or disable the cache
-    cache_.setEnabled(use_cache);
-}
+{}
 
 AuthSrvImpl::~AuthSrvImpl() {
     if (xfrout_connected_) {
@@ -398,11 +385,10 @@ private:
     AuthSrv* server_;
 };
 
-AuthSrv::AuthSrv(const bool use_cache,
-                 isc::xfr::AbstractXfroutClient& xfrout_client,
+AuthSrv::AuthSrv(isc::xfr::AbstractXfroutClient& xfrout_client,
                  isc::util::io::BaseSocketSessionForwarder& ddns_forwarder)
 {
-    impl_ = new AuthSrvImpl(use_cache, xfrout_client, ddns_forwarder);
+    impl_ = new AuthSrvImpl(xfrout_client, ddns_forwarder);
     checkin_ = new ConfigChecker(this);
     dns_lookup_ = new MessageLookup(this);
     dns_answer_ = new MessageAnswer(this);
@@ -482,16 +468,6 @@ AuthSrv::getIOService() {
 }
 
 void
-AuthSrv::setCacheSlots(const size_t slots) {
-    impl_->cache_.setSlots(slots);
-}
-
-size_t
-AuthSrv::getCacheSlots() const {
-    return (impl_->cache_.getSlots());
-}
-
-void
 AuthSrv::setXfrinSession(AbstractSession* xfrin_session) {
     impl_->xfrin_session_ = xfrin_session;
 }
@@ -512,48 +488,6 @@ AuthSrv::getConfigSession() const {
     return (impl_->config_session_);
 }
 
-isc::datasrc::DataSourceClientContainerPtr
-AuthSrv::getInMemoryClientContainer(const RRClass& rrclass) {
-    if (rrclass != impl_->memory_client_class_) {
-        isc_throw(InvalidParameter,
-                  "Memory data source is not supported for RR class "
-                  << rrclass);
-    }
-    return (impl_->memory_client_container_);
-}
-
-isc::datasrc::DataSourceClient*
-AuthSrv::getInMemoryClient(const RRClass& rrclass) {
-    if (hasInMemoryClient()) {
-        return (&getInMemoryClientContainer(rrclass)->getInstance());
-    } else {
-        return (NULL);
-    }
-}
-
-bool
-AuthSrv::hasInMemoryClient() const {
-    return (impl_->memory_client_container_);
-}
-
-void
-AuthSrv::setInMemoryClient(const isc::dns::RRClass& rrclass,
-                           DataSourceClientContainerPtr memory_client)
-{
-    if (rrclass != impl_->memory_client_class_) {
-        isc_throw(InvalidParameter,
-                  "Memory data source is not supported for RR class "
-                  << rrclass);
-    } else if (!impl_->memory_client_container_ && memory_client) {
-        LOG_DEBUG(auth_logger, DBG_AUTH_OPS, AUTH_MEM_DATASRC_ENABLED)
-                  .arg(rrclass);
-    } else if (impl_->memory_client_container_ && !memory_client) {
-        LOG_DEBUG(auth_logger, DBG_AUTH_OPS, AUTH_MEM_DATASRC_DISABLED)
-                  .arg(rrclass);
-    }
-    impl_->memory_client_container_ = memory_client;
-}
-
 uint32_t
 AuthSrv::getStatisticsTimerInterval() const {
     return (impl_->statistics_timer_.getInterval() / 1000);
@@ -725,18 +659,16 @@ AuthSrvImpl::processNormalQuery(const IOMessage& io_message, Message& message,
     }
 
     try {
-        // If a memory data source is configured call the separate
-        // Query::process()
         const ConstQuestionPtr question = *message.beginQuestion();
-        if (memory_client_container_ &&
-            memory_client_class_ == question->getClass()) {
+        const boost::shared_ptr<datasrc::ClientList>
+            list(getClientList(question->getClass()));
+        if (list) {
             const RRType& qtype = question->getType();
             const Name& qname = question->getName();
-            query_.process(memory_client_container_->getInstance(),
-                           qname, qtype, message, dnssec_ok);
+            query_.process(*list, qname, qtype, message, dnssec_ok);
         } else {
-            datasrc::Query query(message, cache_, dnssec_ok);
-            data_sources_.doQuery(query);
+            makeErrorMessage(renderer_, message, buffer, Rcode::REFUSED());
+            return (true);
         }
     } catch (const Exception& ex) {
         LOG_ERROR(auth_logger, AUTH_PROCESS_FAIL).arg(ex.what());
@@ -926,56 +858,6 @@ AuthSrvImpl::validateStatistics(isc::data::ConstElementPtr data) const {
             data, true));
 }
 
-ConstElementPtr
-AuthSrvImpl::setDbFile(ConstElementPtr config) {
-    ConstElementPtr answer = isc::config::createAnswer();
-
-    if (config && config->contains("database_file")) {
-        db_file_ = config->get("database_file")->stringValue();
-    } else if (config_session_ != NULL) {
-        bool is_default;
-        string item("database_file");
-        ConstElementPtr value = config_session_->getValue(is_default, item);
-        ElementPtr final = Element::createMap();
-
-        // If the value is the default, and we are running from
-        // a specific directory ('from build'), we need to use
-        // a different value than the default (which may not exist)
-        // (btw, this should not be done here in the end, i think
-        //  the from-source script should have a check for this,
-        //  but for that we need offline access to config, so for
-        //  now this is a decent solution)
-        if (is_default && getenv("B10_FROM_BUILD")) {
-            value = Element::create(string(getenv("B10_FROM_BUILD")) +
-                                    "/bind10_zones.sqlite3");
-        }
-        final->set(item, value);
-        config = final;
-
-        db_file_ = value->stringValue();
-    } else {
-        return (answer);
-    }
-    LOG_DEBUG(auth_logger, DBG_AUTH_OPS, AUTH_DATA_SOURCE).arg(db_file_);
-
-    // create SQL data source
-    // Note: the following step is tricky to be exception-safe and to ensure
-    // exception guarantee: We first need to perform all operations that can
-    // fail, while acquiring resources in the RAII manner.  We then perform
-    // delete and swap operations which should not fail.
-    DataSrcPtr datasrc_ptr(DataSrcPtr(new Sqlite3DataSrc));
-    datasrc_ptr->init(config);
-    data_sources_.addDataSrc(datasrc_ptr);
-
-    // The following code should be exception free.
-    if (cur_datasrc_ != NULL) {
-        data_sources_.removeDataSrc(cur_datasrc_);
-    }
-    cur_datasrc_ = datasrc_ptr;
-
-    return (answer);
-}
-
 void
 AuthSrvImpl::resumeServer(DNSServer* server, Message& message, bool done) {
     if (done) {
@@ -992,7 +874,7 @@ AuthSrv::updateConfig(ConstElementPtr new_config) {
         if (new_config) {
             configureAuthServer(*this, new_config);
         }
-        return (impl_->setDbFile(new_config));
+        return (isc::config::createAnswer());
     } catch (const isc::Exception& error) {
         LOG_ERROR(auth_logger, AUTH_CONFIG_UPDATE_FAIL).arg(error.what());
         return (isc::config::createAnswer(1, error.what()));
@@ -1056,4 +938,27 @@ AuthSrv::destroyDDNSForwarder() {
     }
 }
 
+void
+AuthSrv::setClientList(const RRClass& rrclass,
+                       const boost::shared_ptr<ConfigurableClientList>& list) {
+    if (list) {
+        impl_->client_lists_[rrclass] = list;
+    } else {
+        impl_->client_lists_.erase(rrclass);
+    }
+}
+boost::shared_ptr<ConfigurableClientList>
+AuthSrv::getClientList(const RRClass& rrclass) {
+    return (impl_->getClientList(rrclass));
+}
 
+vector<RRClass>
+AuthSrv::getClientListClasses() const {
+    vector<RRClass> result;
+    for (map<RRClass, boost::shared_ptr<ConfigurableClientList> >::
+         const_iterator it(impl_->client_lists_.begin());
+         it != impl_->client_lists_.end(); ++it) {
+        result.push_back(it->first);
+    }
+    return (result);
+}

+ 29 - 103
src/bin/auth/auth_srv.h

@@ -43,7 +43,7 @@ class BaseSocketSessionForwarder;
 }
 }
 namespace datasrc {
-class InMemoryClient;
+class ConfigurableClientList;
 }
 namespace xfr {
 class AbstractXfroutClient;
@@ -92,13 +92,11 @@ private:
 public:
     /// The constructor.
     ///
-    /// \param use_cache Whether to enable hot spot cache for lookup results.
     /// \param xfrout_client Communication interface with a separate xfrout
     /// process.  It's normally a reference to an xfr::XfroutClient object,
     /// but can refer to a local mock object for testing (or other
     /// experimental) purposes.
-    AuthSrv(const bool use_cache,
-            isc::xfr::AbstractXfroutClient& xfrout_client,
+    AuthSrv(isc::xfr::AbstractXfroutClient& xfrout_client,
             isc::util::io::BaseSocketSessionForwarder& ddns_forwarder);
     ~AuthSrv();
     //@}
@@ -129,20 +127,7 @@ public:
                         isc::util::OutputBuffer& buffer,
                         isc::asiodns::DNSServer* server);
 
-    /// \brief Updates the data source for the \c AuthSrv object.
-    ///
-    /// This method installs or replaces the data source that the \c AuthSrv
-    /// object refers to for query processing.
-    /// Although the method name is generic, the only thing it does is to
-    /// update the data source information.
-    /// If there is a data source installed, it will be replaced with the
-    /// new one.
-    ///
-    /// In the current implementation, the SQLite data source and InMemoryClient
-    /// are assumed.
-    /// We can enable memory data source and get the path of SQLite database by
-    /// the \c config parameter.  If we disabled memory data source, the SQLite
-    /// data source will be used.
+    /// \brief Updates the configuration for the \c AuthSrv object.
     ///
     /// On success this method returns a data \c Element (in the form of a
     /// pointer like object) indicating the successful result,
@@ -200,26 +185,6 @@ public:
     /// \brief Return pointer to the Checkin callback function
     isc::asiolink::SimpleCallback* getCheckinProvider() const { return (checkin_); }
 
-    /// \brief Set or update the size (number of slots) of hot spot cache.
-    ///
-    /// If the specified size is 0, it means the size will be unlimited.
-    /// The specified size is recorded even if the cache is disabled; the
-    /// new size will be effective when the cache is enabled.
-    ///
-    /// This method never throws an exception.
-    ///
-    /// \param slots The number of cache slots.
-    void setCacheSlots(const size_t slots);
-
-    /// \brief Get the current size (number of slots) of hot spot cache.
-    ///
-    /// It always returns the recorded size regardless of the cache is enabled.
-    ///
-    /// This method never throws an exception.
-    ///
-    /// \return The current number of cache slots.
-    size_t getCacheSlots() const;
-
     /// \brief Set the communication session with a separate process for
     /// outgoing zone transfers.
     ///
@@ -238,71 +203,6 @@ public:
     ///
     void setXfrinSession(isc::cc::AbstractSession* xfrin_session);
 
-    /// Returns the in-memory data source configured for the \c AuthSrv,
-    /// if any, as a pointer.
-    ///
-    /// This is mostly a convenience function around
-    /// \c getInMemoryClientContainer, which saves the caller the step
-    /// of having to call getInstance().
-    /// The pointer is of course only valid as long as the container
-    /// exists.
-    ///
-    /// The in-memory data source is configured per RR class.  However,
-    /// the data source may not be available for all RR classes.
-    /// If it is not available for the specified RR class, an exception of
-    /// class \c InvalidParameter will be thrown.
-    /// This method never throws an exception otherwise.
-    ///
-    /// Even for supported RR classes, the in-memory data source is not
-    /// configured by default.  In that case a NULL (shared) pointer will
-    /// be returned.
-    ///
-    /// \param rrclass The RR class of the requested in-memory data source.
-    /// \return A pointer to the in-memory data source, if configured;
-    /// otherwise NULL.
-    isc::datasrc::DataSourceClient* getInMemoryClient(
-        const isc::dns::RRClass& rrclass);
-
-    /// Returns the DataSourceClientContainer of the in-memory datasource
-    ///
-    /// \exception InvalidParameter if the given class does not match
-    ///            the one in the memory data source, or if the memory
-    ///            datasource has not been set (callers can check with
-    ///            \c hasMemoryDataSource())
-    ///
-    /// \param rrclass The RR class of the requested in-memory data source.
-    /// \return A shared pointer to the in-memory data source, if configured;
-    /// otherwise an empty shared pointer.
-    isc::datasrc::DataSourceClientContainerPtr getInMemoryClientContainer(
-        const isc::dns::RRClass& rrclass);
-
-    /// Checks if the in-memory data source has been set.
-    ///
-    /// Right now, only one datasource at a time is effectively supported.
-    /// This is a helper method to check whether it is the in-memory one.
-    /// This is mostly useful for current testing, and is expected to be
-    /// removed (or changed in behaviour) soon, when the general
-    /// multi-data-source framework is completed.
-    ///
-    /// \return True if the in-memory datasource has been set.
-    bool hasInMemoryClient() const;
-
-    /// Sets or replaces the in-memory data source of the specified RR class.
-    ///
-    /// Some RR classes may not be supported, in which case an exception
-    /// of class \c InvalidParameter will be thrown.
-    /// This method never throws an exception otherwise.
-    ///
-    /// If there is already an in memory data source configured, it will be
-    /// replaced with the newly specified one.
-    /// \c memory_client can be an empty shared pointer, in which case it
-    /// will (re)disable the in-memory data source.
-    ///
-    /// \param rrclass The RR class of the in-memory data source to be set.
-    /// \param memory_client A (shared) pointer to \c InMemoryClient to be set.
-    void setInMemoryClient(const isc::dns::RRClass& rrclass,
-        isc::datasrc::DataSourceClientContainerPtr memory_client);
-
     /// \brief Set the communication session with Statistics.
     ///
     /// This function never throws an exception as far as
@@ -437,6 +337,32 @@ public:
     /// If there was no forwarder yet, this method does nothing.
     void destroyDDNSForwarder();
 
+    /// \brief Sets the currently used list for data sources of given
+    ///     class.
+    ///
+    /// Replaces the internally used client list with a new one. Other
+    /// classes are not changed.
+    ///
+    /// \param rrclass The class to modify.
+    /// \param list Shared pointer to the client list. If it is NULL,
+    ///     the list is removed instead.
+    void setClientList(const isc::dns::RRClass& rrclass, const
+                       boost::shared_ptr<isc::datasrc::ConfigurableClientList>&
+                       list);
+
+    /// \brief Returns the currently used client list for the class.
+    ///
+    /// \param rrclass The class for which to get the list.
+    /// \return The list, or NULL if no list is set for the class.
+    boost::shared_ptr<isc::datasrc::ConfigurableClientList>
+        getClientList(const isc::dns::RRClass& rrclass);
+
+    /// \brief Returns a list of classes that have a client list.
+    ///
+    /// \return List of classes for which a non-NULL client list
+    ///     has been set by setClientList.
+    std::vector<isc::dns::RRClass> getClientListClasses() const;
+
 private:
     AuthSrvImpl* impl_;
     isc::asiolink::SimpleCallback* checkin_;

+ 0 - 13
src/bin/auth/b10-auth.xml

@@ -44,7 +44,6 @@
   <refsynopsisdiv>
     <cmdsynopsis>
       <command>b10-auth</command>
-      <arg><option>-n</option></arg>
       <arg><option>-v</option></arg>
     </cmdsynopsis>
   </refsynopsisdiv>
@@ -80,18 +79,6 @@
 
     <variablelist>
       <varlistentry>
-        <term><option>-n</option></term>
-        <listitem><para>
-          Do not cache answers in memory.
-          The default is to use the cache for faster responses.
-	  The cache keeps the most recent 30,000 answers (positive
-	  and negative) in memory for 30 seconds (instead of querying
-	  the data source, such as SQLite3 database, each time).
-        </para></listitem>
-<!-- TODO: this is SQLite3 only -->
-      </varlistentry>
-
-      <varlistentry>
         <term><option>-v</option></term>
         <listitem><para>
 	  Enable verbose logging mode. This enables logging of

+ 7 - 31
src/bin/auth/benchmarks/query_bench.cc

@@ -77,10 +77,9 @@ protected:
 private:
     typedef boost::shared_ptr<const IOEndpoint> IOEndpointPtr;
 protected:
-    QueryBenchMark(const bool enable_cache,
-                   const BenchQueries& queries, Message& query_message,
+    QueryBenchMark(const BenchQueries& queries, Message& query_message,
                    OutputBuffer& buffer) :
-        server_(new AuthSrv(enable_cache, xfrout_client, ddns_forwarder)),
+        server_(new AuthSrv(xfrout_client, ddns_forwarder)),
         queries_(queries),
         query_message_(query_message),
         buffer_(buffer),
@@ -119,17 +118,12 @@ private:
 
 class Sqlite3QueryBenchMark  : public QueryBenchMark {
 public:
-    Sqlite3QueryBenchMark(const int cache_slots,
-                          const char* const datasrc_file,
+    Sqlite3QueryBenchMark(const char* const datasrc_file,
                           const BenchQueries& queries,
                           Message& query_message,
                           OutputBuffer& buffer) :
-        QueryBenchMark(cache_slots >= 0 ? true : false, queries,
-                       query_message, buffer)
+        QueryBenchMark(queries, query_message, buffer)
     {
-        if (cache_slots >= 0) {
-            server_->setCacheSlots(cache_slots);
-        }
         server_->updateConfig(Element::fromJSON("{\"database_file\": \"" +
                                                 string(datasrc_file) + "\"}"));
     }
@@ -142,7 +136,7 @@ public:
                           const BenchQueries& queries,
                           Message& query_message,
                           OutputBuffer& buffer) :
-        QueryBenchMark(false, queries, query_message, buffer)
+        QueryBenchMark(queries, query_message, buffer)
     {
         configureAuthServer(*server_,
                             Element::fromJSON(
@@ -274,27 +268,9 @@ main(int argc, char* argv[]) {
 
     switch (datasrc_type) {
     case SQLITE3:
-        cout << "Benchmark enabling Hot Spot Cache with unlimited slots "
-             << endl;
-        BenchMark<Sqlite3QueryBenchMark>(
-            iteration, Sqlite3QueryBenchMark(0, datasrc_file, queries,
-                                             message, buffer));
-
-        cout << "Benchmark enabling Hot Spot Cache with 10*#queries slots "
-             << endl;
-        BenchMark<Sqlite3QueryBenchMark>(
-            iteration, Sqlite3QueryBenchMark(10 * queries.size(), datasrc_file,
-                                             queries, message, buffer));
-
-        cout << "Benchmark enabling Hot Spot Cache with #queries/2 slots "
-             << endl;
-        BenchMark<Sqlite3QueryBenchMark>(
-            iteration, Sqlite3QueryBenchMark(queries.size() / 2, datasrc_file,
-                                             queries, message, buffer));
-
-        cout << "Benchmark disabling Hot Spot Cache" << endl;
+        cout << "Benchmark with SQLite3" << endl;
         BenchMark<Sqlite3QueryBenchMark>(
-            iteration, Sqlite3QueryBenchMark(-1, datasrc_file, queries,
+            iteration, Sqlite3QueryBenchMark(datasrc_file, queries,
                                              message, buffer));
         break;
     case MEMORY:

+ 30 - 137
src/bin/auth/command.cc

@@ -17,8 +17,7 @@
 #include <auth/auth_srv.h>
 
 #include <cc/data.h>
-#include <datasrc/memory_datasrc.h>
-#include <datasrc/factory.h>
+#include <datasrc/client_list.h>
 #include <config/ccsession.h>
 #include <exceptions/exceptions.h>
 #include <dns/rrclass.h>
@@ -159,156 +158,50 @@ public:
 class LoadZoneCommand : public AuthCommand {
 public:
     virtual void exec(AuthSrv& server, isc::data::ConstElementPtr args) {
-        // parse and validate the args.
-        if (!validate(server, args)) {
-            return;
-        }
-
-        const ConstElementPtr zone_config = getZoneConfig(server);
-
-        // Load a new zone and replace the current zone with the new one.
-        // TODO: eventually this should be incremental or done in some way
-        // that doesn't block other server operations.
-        // TODO: we may (should?) want to check the "last load time" and
-        // the timestamp of the file and skip loading if the file isn't newer.
-        const ConstElementPtr type(zone_config->get("filetype"));
-        boost::shared_ptr<InMemoryZoneFinder> zone_finder(
-            new InMemoryZoneFinder(old_zone_finder_->getClass(),
-                                   old_zone_finder_->getOrigin()));
-        if (type && type->stringValue() == "sqlite3") {
-            scoped_ptr<DataSourceClientContainer>
-                container(new DataSourceClientContainer("sqlite3",
-                                                        Element::fromJSON(
-                    "{\"database_file\": \"" +
-                    zone_config->get("file")->stringValue() + "\"}")));
-            zone_finder->load(*container->getInstance().getIterator(
-                old_zone_finder_->getOrigin()));
-        } else {
-            zone_finder->load(old_zone_finder_->getFileName());
-        }
-        old_zone_finder_->swap(*zone_finder);
-        LOG_DEBUG(auth_logger, DBG_AUTH_OPS, AUTH_LOAD_ZONE)
-                  .arg(zone_finder->getOrigin()).arg(zone_finder->getClass());
-    }
-
-private:
-    // zone finder to be updated with the new file.
-    boost::shared_ptr<InMemoryZoneFinder> old_zone_finder_;
-
-    // A helper private method to parse and validate command parameters.
-    // On success, it sets 'old_zone_finder_' to the zone to be updated.
-    // It returns true if everything is okay; and false if the command is
-    // valid but there's no need for further process.
-    bool validate(AuthSrv& server, isc::data::ConstElementPtr args) {
         if (args == NULL) {
             isc_throw(AuthCommandError, "Null argument");
         }
 
-        // In this initial implementation, we assume memory data source
-        // for class IN by default.
-        ConstElementPtr datasrc_elem = args->get("datasrc");
-        if (datasrc_elem) {
-            if (datasrc_elem->stringValue() == "sqlite3") {
-                LOG_DEBUG(auth_logger, DBG_AUTH_OPS, AUTH_SQLITE3);
-                return (false);
-            } else if (datasrc_elem->stringValue() != "memory") {
-                // (note: at this point it's guaranteed that datasrc_elem
-                // is of string type)
-                isc_throw(AuthCommandError,
-                          "Data source type " << datasrc_elem->stringValue()
-                          << " is not supported");
-            }
-        }
-
         ConstElementPtr class_elem = args->get("class");
-        const RRClass zone_class =
-            class_elem ? RRClass(class_elem->stringValue()) : RRClass::IN();
-
-        isc::datasrc::DataSourceClient* datasrc(
-            server.getInMemoryClient(zone_class));
-        if (datasrc == NULL) {
-            isc_throw(AuthCommandError, "Memory data source is disabled");
-        }
+        RRClass zone_class(class_elem ? RRClass(class_elem->stringValue()) :
+            RRClass::IN());
 
         ConstElementPtr origin_elem = args->get("origin");
         if (!origin_elem) {
             isc_throw(AuthCommandError, "Zone origin is missing");
         }
-        const Name origin = Name(origin_elem->stringValue());
+        Name origin(origin_elem->stringValue());
 
-        // Get the current zone
-        const DataSourceClient::FindResult result = datasrc->findZone(origin);
-        if (result.code != result::SUCCESS) {
-            isc_throw(AuthCommandError, "Zone " << origin <<
-                      " is not found in data source");
-        }
-
-        // It would appear that dynamic_cast does not work on all systems;
-        // it seems to confuse the RTTI system, resulting in NULL return
-        // values. So we use the more dangerous static_pointer_cast here.
-        old_zone_finder_ = boost::static_pointer_cast<InMemoryZoneFinder>(
-            result.zone_finder);
-
-        return (true);
-    }
+        const boost::shared_ptr<isc::datasrc::ConfigurableClientList>
+            list(server.getClientList(zone_class));
 
-    ConstElementPtr getZoneConfig(const AuthSrv &server) {
-        if (!server.getConfigSession()) {
-            // FIXME: This is a hack to make older tests pass. We should
-            // update these tests as well sometime and remove this hack.
-            // (note that under normal situation, the
-            // server.getConfigSession() does not return NULL)
-
-            // We provide an empty map, which means no configuration --
-            // defaults.
-            return (ConstElementPtr(new MapElement()));
+        if (!list) {
+            isc_throw(AuthCommandError, "There's no client list for "
+                      "class " << zone_class);
         }
 
-        // Find the config corresponding to the zone.
-        // We expect the configuration to be valid, as we have it and we
-        // accepted it before, therefore it must be validated.
-        const ConstElementPtr config(server.getConfigSession()->
-                                     getValue("datasources"));
-        ConstElementPtr zone_list;
-        // Unfortunately, we need to walk the list to find the correct data
-        // source.
-        // TODO: Make it named sets. These lists are uncomfortable.
-        for (size_t i(0); i < config->size(); ++i) {
-            // We use the getValue to get defaults as well
-            const ConstElementPtr dsrc_config(config->get(i));
-            const ConstElementPtr class_config(dsrc_config->get("class"));
-            const string class_type(class_config ?
-                                    class_config->stringValue() : "IN");
-            // It is in-memory and our class matches.
-            // FIXME: Is it allowed to have two datasources for the same
-            // type and class at once? It probably would not work now
-            // anyway and we may want to change the configuration of
-            // datasources somehow.
-            if (dsrc_config->get("type")->stringValue() == "memory" &&
-                RRClass(class_type) == old_zone_finder_->getClass()) {
-                zone_list = dsrc_config->get("zones");
-                break;
-            }
-        }
-
-        if (!zone_list) {
-            isc_throw(AuthCommandError,
-                      "Corresponding data source configuration was not found");
-        }
-
-        // Now we need to walk the zones and find the correct one.
-        for (size_t i(0); i < zone_list->size(); ++i) {
-            const ConstElementPtr zone_config(zone_list->get(i));
-            if (Name(zone_config->get("origin")->stringValue()) ==
-                old_zone_finder_->getOrigin()) {
-                // The origins are the same, so we consider this config to be
-                // for the zone.
-                return (zone_config);
-            }
+        switch (list->reload(origin)) {
+            case ConfigurableClientList::ZONE_RELOADED:
+                // Everything worked fine.
+                LOG_DEBUG(auth_logger, DBG_AUTH_OPS, AUTH_LOAD_ZONE)
+                    .arg(zone_class).arg(origin);
+                return;
+            case ConfigurableClientList::ZONE_NOT_FOUND:
+                isc_throw(AuthCommandError, "Zone " << origin << "/" <<
+                          zone_class << " was not found in any configured "
+                          "data source. Configure it first.");
+            case ConfigurableClientList::ZONE_NOT_CACHED:
+                isc_throw(AuthCommandError, "Zone " << origin << "/" <<
+                          zone_class << " is not served from memory, but "
+                          "direcly from the data source. It is not possible "
+                          "to reload it into memory. Configure it to be cached "
+                          "first.");
+            case ConfigurableClientList::CACHE_DISABLED:
+                // This is an internal error. Auth server must have the cache
+                // enabled.
+                isc_throw(isc::Unexpected, "Cache disabled in client list of "
+                          "class " << zone_class);
         }
-
-        isc_throw(AuthCommandError,
-                  "Corresponding zone configuration was not found");
     }
 };
 

+ 223 - 0
src/bin/auth/datasrc_configurator.h

@@ -0,0 +1,223 @@
+// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef DATASRC_CONFIGURATOR_H
+#define DATASRC_CONFIGURATOR_H
+
+#include "auth_srv.h"
+
+#include <datasrc/client_list.h>
+#include <config/ccsession.h>
+#include <cc/data.h>
+
+#include <set>
+
+/// \brief A class to configure the authoritative server's data source lists
+///
+/// This will hook into the data_sources module configuration and it will
+/// keep the local copy of data source clients in the list in the authoritative
+/// server.
+///
+/// The class is slightly unusual. Due to some technical limitations, the hook
+/// needs to be static method. Therefore it is not possible to create instances
+/// of the class.
+///
+/// Also, the class is a template. This is simply because of easier testing.
+/// You don't need to pay attention to it, use the DataSourceConfigurator
+/// type alias instead.
+template<class Server, class List>
+class DataSourceConfiguratorGeneric {
+private:
+    /// \brief Disallow creation of instances
+    DataSourceConfiguratorGeneric();
+    /// \brief Internal method to hook into the ModuleCCSession
+    ///
+    /// It simply calls reconfigure.
+    static void reconfigureInternal(const std::string&,
+                                    isc::data::ConstElementPtr config,
+                                    const isc::config::ConfigData&)
+    {
+        if (config->contains("classes")) {
+            reconfigure(config->get("classes"));
+        }
+    }
+    static Server* server_;
+    static isc::config::ModuleCCSession* session_;
+    typedef boost::shared_ptr<List> ListPtr;
+public:
+    /// \brief Initializes the class.
+    ///
+    /// This configures which session and server should be used.
+    /// It hooks to the session now and downloads the configuration.
+    /// It is synchronous (it may block for some time).
+    ///
+    /// Note that you need to call cleanup before the server or
+    /// session dies, otherwise it might access them after they
+    /// are destroyed.
+    ///
+    /// \param session The session to hook into and to access the configuration
+    ///     through.
+    /// \param server It is the server to configure.
+    /// \throw isc::InvalidOperation if this is called when already initialized.
+    /// \throw isc::InvalidParameter if any of the parameters is NULL
+    /// \throw isc::config::ModuleCCError if the remote configuration is not
+    ///     available for some reason.
+    static void init(isc::config::ModuleCCSession *session,
+                     Server *server)
+    {
+        if (session == NULL) {
+            isc_throw(isc::InvalidParameter, "The session must not be NULL");
+        }
+        if (server == NULL) {
+            isc_throw(isc::InvalidParameter, "The server must not be NULL");
+        }
+        if (server_ != NULL) {
+            isc_throw(isc::InvalidOperation,
+                      "The configurator is already initialized");
+        }
+        server_ = server;
+        session_ = session;
+        session->addRemoteConfig("data_sources", reconfigureInternal, false);
+    }
+    /// \brief Deinitializes the class.
+    ///
+    /// This detaches from the session and removes the server from internal
+    /// storage. The current configuration in the server is preserved.
+    ///
+    /// This can be called even if it is not initialized currently. You
+    /// can initialize it again after this.
+    static void cleanup() {
+        if (session_ != NULL) {
+            session_->removeRemoteConfig("data_sources");
+        }
+        session_ = NULL;
+        server_ = NULL;
+    }
+    /// \brief Reads new configuration and replaces the old one.
+    ///
+    /// It instructs the server to replace the lists with new ones as needed.
+    /// You don't need to call it directly (but you could, though the benefit
+    /// is unknown and it would be questionable at least). It is called
+    /// automatically on normal updates.
+    ///
+    /// \param config The configuration value to parse. It is in the form
+    ///     as an update from the config manager.
+    /// \throw InvalidOperation if it is called when not initialized.
+    static void reconfigure(const isc::data::ConstElementPtr& config) {
+        if (server_ == NULL) {
+            isc_throw(isc::InvalidOperation,
+                      "Can't reconfigure while not initialized by init()");
+        }
+        typedef std::map<std::string, isc::data::ConstElementPtr> Map;
+        typedef std::pair<isc::dns::RRClass, ListPtr> RollbackPair;
+        typedef std::pair<isc::dns::RRClass, isc::data::ConstElementPtr>
+            RollbackConfiguration;
+        // Some structures to be able to perform a rollback
+        std::vector<RollbackPair> rollback_sets;
+        std::vector<RollbackConfiguration> rollback_configurations;
+        try {
+            // Get the configuration and current state.
+            const Map& map(config->mapValue());
+            const std::vector<isc::dns::RRClass>
+                activeVector(server_->getClientListClasses());
+            std::set<isc::dns::RRClass> active(activeVector.begin(),
+                                               activeVector.end());
+            // Go through the configuration and change everything.
+            for (Map::const_iterator it(map.begin()); it != map.end(); ++it) {
+                isc::dns::RRClass rrclass(it->first);
+                active.erase(rrclass);
+                ListPtr list(server_->getClientList(rrclass));
+                bool need_set(false);
+                if (list) {
+                    rollback_configurations.
+                        push_back(RollbackConfiguration(rrclass,
+                            list->getConfiguration()));
+                } else {
+                    list.reset(new List(rrclass));
+                    need_set = true;
+                    rollback_sets.push_back(RollbackPair(rrclass, ListPtr()));
+                }
+                list->configure(it->second, true);
+                if (need_set) {
+                    server_->setClientList(rrclass, list);
+                }
+            }
+            // Remove the ones that are not in the configuration.
+            for (std::set<isc::dns::RRClass>::iterator it(active.begin());
+                 it != active.end(); ++it) {
+                // There seems to be no way the setClientList could throw.
+                // But this is just to make sure in case it did to restore
+                // the original.
+                rollback_sets.push_back(
+                    RollbackPair(*it, server_->getClientList(*it)));
+                server_->setClientList(*it, ListPtr());
+            }
+        } catch (...) {
+            // Perform a rollback of the changes. The old configuration should
+            // work.
+            for (typename std::vector<RollbackPair>::const_iterator
+                 it(rollback_sets.begin()); it != rollback_sets.end(); ++it) {
+                server_->setClientList(it->first, it->second);
+            }
+            for (typename std::vector<RollbackConfiguration>::const_iterator
+                 it(rollback_configurations.begin());
+                 it != rollback_configurations.end(); ++it) {
+                server_->getClientList(it->first)->configure(it->second, true);
+            }
+            throw;
+        }
+    }
+    /// \brief Version of reconfigure for easier testing.
+    ///
+    /// This method can be used to reconfigure a server without first
+    /// initializing the configurator. This does not need a session.
+    /// Otherwise, it acts the same as reconfigure.
+    ///
+    /// This is not meant for production code. Do not use there.
+    ///
+    /// \param server The server to configure.
+    /// \param config The config to use.
+    /// \throw isc::InvalidOperation if the configurator is initialized.
+    /// \throw anything that reconfigure does.
+    static void testReconfigure(Server* server,
+                                const isc::data::ConstElementPtr& config)
+    {
+        if (server_ != NULL) {
+            isc_throw(isc::InvalidOperation, "Currently initialized.");
+        }
+        try {
+            server_ = server;
+            reconfigure(config);
+            server_ = NULL;
+        } catch (...) {
+            server_ = NULL;
+            throw;
+        }
+    }
+};
+
+template<class Server, class List>
+isc::config::ModuleCCSession*
+DataSourceConfiguratorGeneric<Server, List>::session_(NULL);
+
+template<class Server, class List>
+Server* DataSourceConfiguratorGeneric<Server, List>::server_(NULL);
+
+/// \brief Concrete version of DataSourceConfiguratorGeneric for the
+///     use in authoritative server.
+typedef DataSourceConfiguratorGeneric<AuthSrv,
+        isc::datasrc::ConfigurableClientList>
+    DataSourceConfigurator;
+
+#endif

+ 12 - 8
src/bin/auth/main.cc

@@ -45,6 +45,7 @@
 #include <auth/command.h>
 #include <auth/auth_srv.h>
 #include <auth/auth_log.h>
+#include <auth/datasrc_configurator.h>
 #include <asiodns/asiodns.h>
 #include <asiolink/asiolink.h>
 #include <log/logger_support.h>
@@ -84,9 +85,8 @@ my_command_handler(const string& command, ConstElementPtr args) {
 
 void
 usage() {
-    cerr << "Usage:  b10-auth [-u user] [-nv]"
+    cerr << "Usage:  b10-auth [-v]"
          << endl;
-    cerr << "\t-n: do not cache answers in memory" << endl;
     cerr << "\t-v: verbose logging (debug-level)" << endl;
     exit(1);
 }
@@ -96,14 +96,10 @@ usage() {
 int
 main(int argc, char* argv[]) {
     int ch;
-    bool cache = true;
     bool verbose = false;
 
     while ((ch = getopt(argc, argv, ":nu:v")) != -1) {
         switch (ch) {
-        case 'n':
-            cache = false;
-            break;
         case 'v':
             verbose = true;
             break;
@@ -142,7 +138,7 @@ main(int argc, char* argv[]) {
             specfile = string(AUTH_SPECFILE_LOCATION);
         }
 
-        auth_server = new AuthSrv(cache, xfrout_client, ddns_forwarder);
+        auth_server = new AuthSrv(xfrout_client, ddns_forwarder);
         LOG_INFO(auth_logger, AUTH_SERVER_CREATED);
 
         SimpleCallback* checkin = auth_server->getCheckinProvider();
@@ -204,6 +200,14 @@ main(int argc, char* argv[]) {
         isc::server_common::initKeyring(*config_session);
         auth_server->setTSIGKeyRing(&isc::server_common::keyring);
 
+        // Start the data source configuration
+        DataSourceConfigurator::init(config_session, auth_server);
+        // HACK: The default is not passed to the handler. This one will
+        // get the default (or, current value). Further updates will work
+        // the usual way.
+        DataSourceConfigurator::reconfigure(
+            config_session->getRemoteConfigValue("data_sources", "classes"));
+
         // Now start asynchronous read.
         config_session->start();
         LOG_DEBUG(auth_logger, DBG_AUTH_START, AUTH_CONFIG_CHANNEL_STARTED);
@@ -217,7 +221,6 @@ main(int argc, char* argv[]) {
         cc_session->group_sendmsg(
             isc::config::createCommand(AUTH_STARTED_NOTIFICATION), "DDNS");
         io_service.run();
-
     } catch (const std::exception& ex) {
         LOG_FATAL(auth_logger, AUTH_SERVER_FAILED).arg(ex.what());
         ret = 1;
@@ -231,6 +234,7 @@ main(int argc, char* argv[]) {
         xfrin_session->disconnect();
     }
 
+    DataSourceConfigurator::cleanup();
     delete statistics_session;
     delete xfrin_session;
     delete config_session;

+ 27 - 27
src/bin/auth/query.cc

@@ -19,6 +19,7 @@
 #include <dns/rdataclass.h>
 
 #include <datasrc/client.h>
+#include <datasrc/client_list.h>
 
 #include <auth/query.h>
 
@@ -341,17 +342,17 @@ namespace {
 // the qname consists of a single label, which also means it's the root name),
 // we should search the deepest zone we have (which should be the root zone;
 // otherwise it's a query error).
-DataSourceClient::FindResult
-findZone(const DataSourceClient& client, const Name& qname, RRType qtype) {
+ClientList::FindResult
+findZone(const ClientList& list, const Name& qname, RRType qtype) {
     if (qtype != RRType::DS() || qname.getLabelCount() == 1) {
-        return (client.findZone(qname));
+        return (list.find(qname));
     }
-    return (client.findZone(qname.split(1)));
+    return (list.find(qname.split(1)));
 }
 }
 
 void
-Query::process(datasrc::DataSourceClient& datasrc_client,
+Query::process(datasrc::ClientList& client_list,
                const isc::dns::Name& qname, const isc::dns::RRType& qtype,
                isc::dns::Message& response, bool dnssec)
 {
@@ -360,19 +361,18 @@ Query::process(datasrc::DataSourceClient& datasrc_client,
     QueryCleaner cleaner(*this);
 
     // Set up query parameters for the rest of the (internal) methods
-    initialize(datasrc_client, qname, qtype, response, dnssec);
+    initialize(client_list, qname, qtype, response, dnssec);
 
     // Found a zone which is the nearest ancestor to QNAME
-    const DataSourceClient::FindResult result = findZone(*datasrc_client_,
-                                                         *qname_, *qtype_);
+    const ClientList::FindResult result = findZone(*client_list_, *qname_,
+                                                   *qtype_);
 
     // If we have no matching authoritative zone for the query name, return
     // REFUSED.  In short, this is to be compatible with BIND 9, but the
     // background discussion is not that simple.  See the relevant topic
     // at the BIND 10 developers's ML:
     // https://lists.isc.org/mailman/htdig/bind10-dev/2010-December/001633.html
-    if (result.code != result::SUCCESS &&
-        result.code != result::PARTIALMATCH) {
+    if (result.dsrc_client_ == NULL) {
         // If we tried to find a "parent zone" for a DS query and failed,
         // we may still have authority at the child side.  If we do, the query
         // has to be handled there.
@@ -384,7 +384,7 @@ Query::process(datasrc::DataSourceClient& datasrc_client,
         response_->setRcode(Rcode::REFUSED());
         return;
     }
-    ZoneFinder& zfinder = *result.zone_finder;
+    ZoneFinder& zfinder = *result.finder_;
 
     // We have authority for a zone that contain the query name (possibly
     // indirectly via delegation).  Look into the zone.
@@ -457,7 +457,7 @@ Query::process(datasrc::DataSourceClient& datasrc_client,
             // If the answer is a result of wildcard substitution,
             // add a proof that there's no closer name.
             if (dnssec_ && db_context->isWildcard()) {
-                addWildcardProof(*result.zone_finder, *db_context);
+                addWildcardProof(*result.finder_, *db_context);
             }
             break;
         case ZoneFinder::SUCCESS:
@@ -475,17 +475,17 @@ Query::process(datasrc::DataSourceClient& datasrc_client,
             // section.
             // Checking the findZone() is a lightweight check to see if
             // qname is the zone origin.
-            if (result.code != result::SUCCESS ||
+            if (!result.exact_match_ ||
                 db_context->code != ZoneFinder::SUCCESS ||
                 (*qtype_ != RRType::NS() && !qtype_is_any))
             {
-                addAuthAdditional(*result.zone_finder, additionals_);
+                addAuthAdditional(*result.finder_, additionals_);
             }
 
             // If the answer is a result of wildcard substitution,
             // add a proof that there's no closer name.
             if (dnssec_ && db_context->isWildcard()) {
-                addWildcardProof(*result.zone_finder, *db_context);
+                addWildcardProof(*result.finder_, *db_context);
             }
             break;
         case ZoneFinder::DELEGATION:
@@ -505,12 +505,12 @@ Query::process(datasrc::DataSourceClient& datasrc_client,
             // If DNSSEC is requested, see whether there is a DS
             // record for this delegation.
             if (dnssec_) {
-                addDS(*result.zone_finder, db_context->rrset->getName());
+                addDS(*result.finder_, db_context->rrset->getName());
             }
             break;
         case ZoneFinder::NXDOMAIN:
             response_->setRcode(Rcode::NXDOMAIN());
-            addSOA(*result.zone_finder);
+            addSOA(*result.finder_);
             if (dnssec_) {
                 if (db_context->isNSECSigned() && db_context->rrset) {
                     addNXDOMAINProofByNSEC(zfinder, db_context->rrset);
@@ -520,7 +520,7 @@ Query::process(datasrc::DataSourceClient& datasrc_client,
             }
             break;
         case ZoneFinder::NXRRSET:
-            addSOA(*result.zone_finder);
+            addSOA(*result.finder_);
             if (dnssec_) {
                 addNXRRsetProof(zfinder, *db_context);
             }
@@ -538,11 +538,11 @@ Query::process(datasrc::DataSourceClient& datasrc_client,
 }
 
 void
-Query::initialize(datasrc::DataSourceClient& datasrc_client,
+Query::initialize(datasrc::ClientList& client_list,
                   const isc::dns::Name& qname, const isc::dns::RRType& qtype,
                   isc::dns::Message& response, bool dnssec)
 {
-    datasrc_client_ = &datasrc_client;
+    client_list_ = &client_list;
     qname_ = &qname;
     qtype_ = &qtype;
     response_ = &response;
@@ -553,7 +553,7 @@ Query::initialize(datasrc::DataSourceClient& datasrc_client,
 
 void
 Query::reset() {
-    datasrc_client_ = NULL;
+    client_list_ = NULL;
     qname_ = NULL;
     qtype_ = NULL;
     response_ = NULL;
@@ -565,10 +565,10 @@ Query::reset() {
 
 bool
 Query::processDSAtChild() {
-    const DataSourceClient::FindResult zresult =
-        datasrc_client_->findZone(*qname_);
+    const ClientList::FindResult zresult =
+        client_list_->find(*qname_, true);
 
-    if (zresult.code != result::SUCCESS) {
+    if (zresult.dsrc_client_ == NULL) {
         return (false);
     }
 
@@ -583,12 +583,12 @@ Query::processDSAtChild() {
     // by seeing the SOA.
     response_->setHeaderFlag(Message::HEADERFLAG_AA);
     response_->setRcode(Rcode::NOERROR());
-    addSOA(*zresult.zone_finder);
+    addSOA(*zresult.finder_);
     ConstZoneFinderContextPtr ds_context =
-        zresult.zone_finder->find(*qname_, RRType::DS(), dnssec_opt_);
+        zresult.finder_->find(*qname_, RRType::DS(), dnssec_opt_);
     if (ds_context->code == ZoneFinder::NXRRSET) {
         if (dnssec_) {
-            addNXRRsetProof(*zresult.zone_finder, *ds_context);
+            addNXRRsetProof(*zresult.finder_, *ds_context);
         }
     }
 

+ 9 - 11
src/bin/auth/query.h

@@ -32,7 +32,7 @@ class RRset;
 }
 
 namespace datasrc {
-class DataSourceClient;
+class ClientList;
 }
 
 namespace auth {
@@ -55,8 +55,6 @@ namespace auth {
 ///   separate attribute setter.
 /// - likewise, we'll eventually need to do per zone access control, for which
 ///   we need querier's information such as its IP address.
-/// - datasrc_client and response may better be parameters to process() instead
-///   of the constructor.
 ///
 /// <b>Note:</b> The class name is intentionally the same as the one used in
 /// the datasrc library.  This is because the plan is to eventually merge
@@ -240,14 +238,14 @@ private:
     /// This is the first step of the process() method, and initializes
     /// the member data
     ///
-    /// \param datasrc_client The datasource wherein the answer to the query is
-    /// to be found.
+    /// \param client_list The datasource list wherein the answer to the query
+    /// is to be found.
     /// \param qname The query name
     /// \param qtype The RR type of the query
     /// \param response The response message to store the answer to the query.
     /// \param dnssec If the answer should include signatures and NSEC/NSEC3 if
     ///     possible.
-    void initialize(datasrc::DataSourceClient& datasrc_client,
+    void initialize(datasrc::ClientList& client_list,
                     const isc::dns::Name& qname, const isc::dns::RRType& qtype,
                     isc::dns::Message& response, bool dnssec = false);
 
@@ -281,7 +279,7 @@ public:
     /// Query parameters will be set by the call to process()
     ///
     Query() :
-        datasrc_client_(NULL), qname_(NULL), qtype_(NULL),
+        client_list_(NULL), qname_(NULL), qtype_(NULL),
         dnssec_(false), dnssec_opt_(isc::datasrc::ZoneFinder::FIND_DEFAULT),
         response_(NULL)
     {
@@ -318,14 +316,14 @@ public:
     /// shouldn't happen in real-life (as BadZone means wrong data, it should
     /// have been rejected upon loading).
     ///
-    /// \param datasrc_client The datasource wherein the answer to the query is
-    /// to be found.
+    /// \param client_list The datasource list wherein the answer to the query
+    /// is to be found.
     /// \param qname The query name
     /// \param qtype The RR type of the query
     /// \param response The response message to store the answer to the query.
     /// \param dnssec If the answer should include signatures and NSEC/NSEC3 if
     ///     possible.
-    void process(datasrc::DataSourceClient& datasrc_client,
+    void process(datasrc::ClientList& client_list,
                  const isc::dns::Name& qname, const isc::dns::RRType& qtype,
                  isc::dns::Message& response, bool dnssec = false);
 
@@ -483,7 +481,7 @@ public:
     };
 
 private:
-    const isc::datasrc::DataSourceClient* datasrc_client_;
+    const isc::datasrc::ClientList* client_list_;
     const isc::dns::Name* qname_;
     const isc::dns::RRType* qtype_;
     bool dnssec_;

+ 5 - 0
src/bin/auth/tests/Makefile.am

@@ -5,7 +5,10 @@ AM_CPPFLAGS += -I$(top_builddir)/src/lib/cc
 AM_CPPFLAGS += $(BOOST_INCLUDES)
 AM_CPPFLAGS += -DAUTH_OBJ_DIR=\"$(abs_top_builddir)/src/bin/auth\"
 AM_CPPFLAGS += -DTEST_DATA_DIR=\"$(abs_top_srcdir)/src/lib/testutils/testdata\"
+AM_CPPFLAGS += -DTEST_OWN_DATA_DIR=\"$(abs_top_srcdir)/src/bin/auth/tests/testdata\"
 AM_CPPFLAGS += -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/lib/testutils/testdata\"
+AM_CPPFLAGS += -DDSRC_DIR=\"$(abs_top_builddir)/src/lib/datasrc\"
+AM_CPPFLAGS += -DPLUGIN_DATA_PATH=\"$(abs_top_srcdir)/src/bin/cfgmgr/plugins\"
 AM_CPPFLAGS += -DINSTALL_PROG=\"$(abs_top_srcdir)/install-sh\"
 
 AM_CXXFLAGS = $(B10_CXXFLAGS)
@@ -44,6 +47,7 @@ run_unittests_SOURCES += command_unittest.cc
 run_unittests_SOURCES += common_unittest.cc
 run_unittests_SOURCES += query_unittest.cc
 run_unittests_SOURCES += statistics_unittest.cc
+run_unittests_SOURCES += datasrc_configurator_unittest.cc
 run_unittests_SOURCES += run_unittests.cc
 # This is a temporary workaround for #1206, where the InMemoryClient has been
 # moved to an ldopened library. We could add that library to LDADD, but that
@@ -72,6 +76,7 @@ run_unittests_LDADD += $(top_builddir)/src/lib/server_common/libserver_common.la
 run_unittests_LDADD += $(top_builddir)/src/lib/nsas/libnsas.la
 run_unittests_LDADD += $(top_builddir)/src/lib/util/unittests/libutil_unittests.la
 run_unittests_LDADD += $(top_builddir)/src/lib/statistics/libstatistics.la
+run_unittests_LDADD += $(top_builddir)/src/lib/config/tests/libfake_session.la
 run_unittests_LDADD += $(GTEST_LDADD)
 run_unittests_LDADD += $(SQLITE_LIBS)
 

+ 286 - 211
src/bin/auth/tests/auth_srv_unittest.cc

@@ -30,10 +30,12 @@
 #include <server_common/keyring.h>
 
 #include <datasrc/memory_datasrc.h>
+#include <datasrc/client_list.h>
 #include <auth/auth_srv.h>
 #include <auth/command.h>
 #include <auth/common.h>
 #include <auth/statistics.h>
+#include <auth/datasrc_configurator.h>
 
 #include <util/unittests/mock_socketsession.h>
 #include <dns/tests/unittest_util.h>
@@ -48,6 +50,7 @@
 #include <boost/lexical_cast.hpp>
 #include <boost/shared_ptr.hpp>
 #include <boost/scoped_ptr.hpp>
+#include <boost/foreach.hpp>
 
 #include <vector>
 
@@ -79,19 +82,17 @@ const char* const CONFIG_TESTDB =
 const char* const BADCONFIG_TESTDB =
     "{ \"database_file\": \"" TEST_DATA_DIR "/nodir/notexist\"}";
 
+const char* const STATIC_DSRC_FILE = DSRC_DIR "/static.zone";
+
 // This is a configuration that uses the in-memory data source containing
 // a signed example zone.
-const char* const CONFIG_INMEMORY_EXAMPLE =
-    "{\"datasources\": [{\"type\": \"memory\","
-    "\"zones\": [{\"origin\": \"example\","
-    "\"file\": \"" TEST_DATA_DIR "/rfc5155-example.zone.signed\"}]}]}";
+const char* const CONFIG_INMEMORY_EXAMPLE = TEST_DATA_DIR "/rfc5155-example.zone.signed";
 
 class AuthSrvTest : public SrvTestBase {
 protected:
     AuthSrvTest() :
         dnss_(),
-        server(true, xfrout, ddns_forwarder),
-        rrclass(RRClass::IN()),
+        server(xfrout, ddns_forwarder),
         // The empty string is expected value of the parameter of
         // requestSocket, not the app_name (there's no fallback, it checks
         // the empty string is passed).
@@ -183,7 +184,6 @@ protected:
     MockXfroutClient xfrout;
     MockSocketSessionForwarder ddns_forwarder;
     AuthSrv server;
-    const RRClass rrclass;
     vector<uint8_t> response_data;
     AddressList address_store_;
     TestSocketRequestor sock_requestor_;
@@ -194,7 +194,8 @@ protected:
 // by default.  The resulting wire-format data will be stored in 'data'.
 void
 createBuiltinVersionResponse(const qid_t qid, vector<uint8_t>& data) {
-    const Name version_name("version.bind");
+    const Name version_name("VERSION.BIND.");
+    const Name apex_name("BIND.");
     Message message(Message::RENDER);
 
     UnitTestUtil::createRequestMessage(message, Opcode::QUERY(),
@@ -207,9 +208,9 @@ createBuiltinVersionResponse(const qid_t qid, vector<uint8_t>& data) {
     rrset_version->addRdata(generic::TXT(PACKAGE_STRING));
     message.addRRset(Message::SECTION_ANSWER, rrset_version);
 
-    RRsetPtr rrset_version_ns = RRsetPtr(new RRset(version_name, RRClass::CH(),
+    RRsetPtr rrset_version_ns = RRsetPtr(new RRset(apex_name, RRClass::CH(),
                                                    RRType::NS(), RRTTL(0)));
-    rrset_version_ns->addRdata(generic::NS(version_name));
+    rrset_version_ns->addRdata(generic::NS(apex_name));
     message.addRRset(Message::SECTION_AUTHORITY, rrset_version_ns);
 
     MessageRenderer renderer;
@@ -221,69 +222,18 @@ createBuiltinVersionResponse(const qid_t qid, vector<uint8_t>& data) {
                 renderer.getLength());
 }
 
-// In the following tests we confirm the response data is rendered in
-// wire format in the expected way.
-
-// The most primitive check: checking the result of the processMessage()
-// method
-TEST_F(AuthSrvTest, builtInQuery) {
+// We did not configure any client lists. Therefore it should be REFUSED
+TEST_F(AuthSrvTest, noClientList) {
     UnitTestUtil::createRequestMessage(request_message, Opcode::QUERY(),
                                        default_qid, Name("version.bind"),
                                        RRClass::CH(), RRType::TXT());
     createRequestPacket(request_message, IPPROTO_UDP);
     server.processMessage(*io_message, *parse_message, *response_obuffer,
                           &dnsserv);
-    createBuiltinVersionResponse(default_qid, response_data);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
-                        response_obuffer->getData(),
-                        response_obuffer->getLength(),
-                        &response_data[0], response_data.size());
-    checkAllRcodeCountersZeroExcept(Rcode::NOERROR(), 1);
-}
-
-// Same test emulating the UDPServer class behavior (defined in libasiolink).
-// This is not a good test in that it assumes internal implementation details
-// of UDPServer, but we've encountered a regression due to the introduction
-// of that class, so we add a test for that case to prevent such a regression
-// in future.
-// Besides, the generalization of UDPServer is probably too much for the
-// authoritative only server in terms of performance, and it's quite likely
-// we need to drop it for the authoritative server implementation.
-// At that point we can drop this test, too.
-TEST_F(AuthSrvTest, builtInQueryViaDNSServer) {
-    UnitTestUtil::createRequestMessage(request_message, Opcode::QUERY(),
-                                       default_qid, Name("version.bind"),
-                                       RRClass::CH(), RRType::TXT());
-    createRequestPacket(request_message, IPPROTO_UDP);
-
-    (*server.getDNSLookupProvider())(*io_message, parse_message,
-                                     response_message,
-                                     response_obuffer, &dnsserv);
-    (*server.getDNSAnswerProvider())(*io_message, parse_message,
-                                     response_message, response_obuffer);
-
-    createBuiltinVersionResponse(default_qid, response_data);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
-                        response_obuffer->getData(),
-                        response_obuffer->getLength(),
-                        &response_data[0], response_data.size());
-}
-
-// Same type of test as builtInQueryViaDNSServer but for an error response.
-TEST_F(AuthSrvTest, iqueryViaDNSServer) {
-    createDataFromFile("iquery_fromWire.wire");
-    (*server.getDNSLookupProvider())(*io_message, parse_message,
-                                     response_message,
-                                     response_obuffer, &dnsserv);
-    (*server.getDNSAnswerProvider())(*io_message, parse_message,
-                                     response_message, response_obuffer);
 
-    UnitTestUtil::readWireData("iquery_response_fromWire.wire",
-                               response_data);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
-                        response_obuffer->getData(),
-                        response_obuffer->getLength(),
-                        &response_data[0], response_data.size());
+    EXPECT_TRUE(dnsserv.hasAnswer());
+    headerCheck(*parse_message, default_qid, Rcode::REFUSED(),
+                opcode.getCode(), QR_FLAG, 1, 0, 0, 0);
 }
 
 // Unsupported requests.  Should result in NOTIMP.
@@ -350,43 +300,6 @@ TEST_F(AuthSrvTest, AXFRSuccess) {
     checkAllRcodeCountersZero();
 }
 
-// Try giving the server a TSIG signed request and see it can anwer signed as
-// well
-TEST_F(AuthSrvTest, TSIGSigned) {
-    // Prepare key, the client message, etc
-    const TSIGKey key("key:c2VjcmV0Cg==:hmac-sha1");
-    TSIGContext context(key);
-    UnitTestUtil::createRequestMessage(request_message, opcode, default_qid,
-                                       Name("version.bind"), RRClass::CH(),
-                                       RRType::TXT());
-    createRequestPacket(request_message, IPPROTO_UDP, &context);
-
-    // Run the message through the server
-    boost::shared_ptr<TSIGKeyRing> keyring(new TSIGKeyRing);
-    keyring->add(key);
-    server.setTSIGKeyRing(&keyring);
-    server.processMessage(*io_message, *parse_message, *response_obuffer,
-                          &dnsserv);
-
-    // What did we get?
-    EXPECT_TRUE(dnsserv.hasAnswer());
-    headerCheck(*parse_message, default_qid, Rcode::NOERROR(),
-                opcode.getCode(), QR_FLAG | AA_FLAG, 1, 1, 1, 0);
-    // We need to parse the message ourself, or getTSIGRecord won't work
-    InputBuffer ib(response_obuffer->getData(), response_obuffer->getLength());
-    Message m(Message::PARSE);
-    m.fromWire(ib);
-
-    const TSIGRecord* tsig = m.getTSIGRecord();
-    ASSERT_TRUE(tsig != NULL) << "Missing TSIG signature";
-    TSIGError error(context.verify(tsig, response_obuffer->getData(),
-                                   response_obuffer->getLength()));
-    EXPECT_EQ(TSIGError::NOERROR(), error) <<
-        "The server signed the response, but it doesn't seem to be valid";
-
-    checkAllRcodeCountersZeroExcept(Rcode::NOERROR(), 1);
-}
-
 // Give the server a signed request, but don't give it the key. It will
 // not be able to verify it, returning BADKEY
 TEST_F(AuthSrvTest, TSIGSignedBadKey) {
@@ -827,9 +740,172 @@ updateConfig(AuthSrv* server, const char* const config_data,
         "Bad result from updateConfig: " << result->str();
 }
 
+void
+updateDatabase(AuthSrv* server, const char* params) {
+    const ConstElementPtr config(Element::fromJSON("{"
+        "\"IN\": [{"
+        "    \"type\": \"sqlite3\","
+        "    \"params\": " + string(params) +
+        "}]}"));
+    DataSourceConfigurator::testReconfigure(server, config);
+}
+
+void
+updateInMemory(AuthSrv* server, const char* origin, const char* filename) {
+    const ConstElementPtr config(Element::fromJSON("{"
+        "\"IN\": [{"
+        "   \"type\": \"MasterFiles\","
+        "   \"params\": {"
+        "       \"" + string(origin) + "\": \"" + string(filename) + "\""
+        "   },"
+        "   \"cache-enable\": true"
+        "}],"
+        "\"CH\": [{"
+        "   \"type\": \"static\","
+        "   \"params\": \"" + string(STATIC_DSRC_FILE) + "\""
+        "}]}"));
+    DataSourceConfigurator::testReconfigure(server, config);
+}
+
+void
+updateBuiltin(AuthSrv* server) {
+    const ConstElementPtr config(Element::fromJSON("{"
+        "\"CH\": [{"
+        "   \"type\": \"static\","
+        "   \"params\": \"" + string(STATIC_DSRC_FILE) + "\""
+        "}]}"));
+    DataSourceConfigurator::testReconfigure(server, config);
+}
+
+// Try giving the server a TSIG signed request and see it can anwer signed as
+// well
+#ifdef USE_STATIC_LINK
+TEST_F(AuthSrvTest, DISABLED_TSIGSigned) { // Needs builtin
+#else
+TEST_F(AuthSrvTest, TSIGSigned) {
+#endif
+    // Prepare key, the client message, etc
+    updateBuiltin(&server);
+    const TSIGKey key("key:c2VjcmV0Cg==:hmac-sha1");
+    TSIGContext context(key);
+    UnitTestUtil::createRequestMessage(request_message, opcode, default_qid,
+                                       Name("VERSION.BIND."), RRClass::CH(),
+                                       RRType::TXT());
+    createRequestPacket(request_message, IPPROTO_UDP, &context);
+
+    // Run the message through the server
+    boost::shared_ptr<TSIGKeyRing> keyring(new TSIGKeyRing);
+    keyring->add(key);
+    server.setTSIGKeyRing(&keyring);
+    server.processMessage(*io_message, *parse_message, *response_obuffer,
+                          &dnsserv);
+
+    // What did we get?
+    EXPECT_TRUE(dnsserv.hasAnswer());
+    headerCheck(*parse_message, default_qid, Rcode::NOERROR(),
+                opcode.getCode(), QR_FLAG | AA_FLAG, 1, 1, 1, 0);
+    // We need to parse the message ourself, or getTSIGRecord won't work
+    InputBuffer ib(response_obuffer->getData(), response_obuffer->getLength());
+    Message m(Message::PARSE);
+    m.fromWire(ib);
+
+    const TSIGRecord* tsig = m.getTSIGRecord();
+    ASSERT_TRUE(tsig != NULL) << "Missing TSIG signature";
+    TSIGError error(context.verify(tsig, response_obuffer->getData(),
+                                   response_obuffer->getLength()));
+    EXPECT_EQ(TSIGError::NOERROR(), error) <<
+        "The server signed the response, but it doesn't seem to be valid";
+
+    checkAllRcodeCountersZeroExcept(Rcode::NOERROR(), 1);
+}
+
+// Same test emulating the UDPServer class behavior (defined in libasiolink).
+// This is not a good test in that it assumes internal implementation details
+// of UDPServer, but we've encountered a regression due to the introduction
+// of that class, so we add a test for that case to prevent such a regression
+// in future.
+// Besides, the generalization of UDPServer is probably too much for the
+// authoritative only server in terms of performance, and it's quite likely
+// we need to drop it for the authoritative server implementation.
+// At that point we can drop this test, too.
+#ifdef USE_STATIC_LINK
+TEST_F(AuthSrvTest, DISABLED_builtInQueryViaDNSServer) {
+#else
+TEST_F(AuthSrvTest, builtInQueryViaDNSServer) {
+#endif
+    updateBuiltin(&server);
+    UnitTestUtil::createRequestMessage(request_message, Opcode::QUERY(),
+                                       default_qid, Name("VERSION.BIND."),
+                                       RRClass::CH(), RRType::TXT());
+    createRequestPacket(request_message, IPPROTO_UDP);
+
+    (*server.getDNSLookupProvider())(*io_message, parse_message,
+                                     response_message,
+                                     response_obuffer, &dnsserv);
+    (*server.getDNSAnswerProvider())(*io_message, parse_message,
+                                     response_message, response_obuffer);
+
+    createBuiltinVersionResponse(default_qid, response_data);
+    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
+                        response_obuffer->getData(),
+                        response_obuffer->getLength(),
+                        &response_data[0], response_data.size());
+}
+
+// In the following tests we confirm the response data is rendered in
+// wire format in the expected way.
+
+// The most primitive check: checking the result of the processMessage()
+// method
+#ifdef USE_STATIC_LINK
+TEST_F(AuthSrvTest, DISABLED_builtInQuery) {
+#else
+TEST_F(AuthSrvTest, builtInQuery) {
+#endif
+    updateBuiltin(&server);
+    UnitTestUtil::createRequestMessage(request_message, Opcode::QUERY(),
+                                       default_qid, Name("VERSION.BIND."),
+                                       RRClass::CH(), RRType::TXT());
+    createRequestPacket(request_message, IPPROTO_UDP);
+    server.processMessage(*io_message, *parse_message, *response_obuffer,
+                          &dnsserv);
+    createBuiltinVersionResponse(default_qid, response_data);
+    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
+                        response_obuffer->getData(),
+                        response_obuffer->getLength(),
+                        &response_data[0], response_data.size());
+    checkAllRcodeCountersZeroExcept(Rcode::NOERROR(), 1);
+}
+
+// Same type of test as builtInQueryViaDNSServer but for an error response.
+#ifdef USE_STATIC_LINK
+TEST_F(AuthSrvTest, DISABLED_iqueryViaDNSServer) { // Needs builtin
+#else
+TEST_F(AuthSrvTest, iqueryViaDNSServer) { // Needs builtin
+#endif
+    updateBuiltin(&server);
+    createDataFromFile("iquery_fromWire.wire");
+    (*server.getDNSLookupProvider())(*io_message, parse_message,
+                                     response_message,
+                                     response_obuffer, &dnsserv);
+    (*server.getDNSAnswerProvider())(*io_message, parse_message,
+                                     response_message, response_obuffer);
+
+    UnitTestUtil::readWireData("iquery_response_fromWire.wire",
+                               response_data);
+    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
+                        response_obuffer->getData(),
+                        response_obuffer->getLength(),
+                        &response_data[0], response_data.size());
+}
+
 // Install a Sqlite3 data source with testing data.
+#ifdef USE_STATIC_LINK
+TEST_F(AuthSrvTest, DISABLED_updateConfig) {
+#else
 TEST_F(AuthSrvTest, updateConfig) {
-    updateConfig(&server, CONFIG_TESTDB, true);
+#endif
+    updateDatabase(&server, CONFIG_TESTDB);
 
     // query for existent data in the installed data source.  The resulting
     // response should have the AA flag on, and have an RR in each answer
@@ -842,8 +918,12 @@ TEST_F(AuthSrvTest, updateConfig) {
                 QR_FLAG | AA_FLAG, 1, 1, 1, 0);
 }
 
+#ifdef USE_STATIC_LINK
+TEST_F(AuthSrvTest, DISABLED_datasourceFail) {
+#else
 TEST_F(AuthSrvTest, datasourceFail) {
-    updateConfig(&server, CONFIG_TESTDB, true);
+#endif
+    updateDatabase(&server, CONFIG_TESTDB);
 
     // This query will hit a corrupted entry of the data source (the zoneload
     // tool and the data source itself naively accept it).  This will result
@@ -857,40 +937,40 @@ TEST_F(AuthSrvTest, datasourceFail) {
                 opcode.getCode(), QR_FLAG, 1, 0, 0, 0);
 }
 
+#ifdef USE_STATIC_LINK
+TEST_F(AuthSrvTest, DISABLED_updateConfigFail) {
+#else
 TEST_F(AuthSrvTest, updateConfigFail) {
+#endif
     // First, load a valid data source.
-    updateConfig(&server, CONFIG_TESTDB, true);
+    updateDatabase(&server, CONFIG_TESTDB);
 
     // Next, try to update it with a non-existent one.  This should fail.
-    updateConfig(&server, BADCONFIG_TESTDB, false);
+    EXPECT_THROW(updateDatabase(&server, BADCONFIG_TESTDB),
+                 isc::datasrc::DataSourceError);
 
     // The original data source should still exist.
     createDataFromFile("examplequery_fromWire.wire");
     server.processMessage(*io_message, *parse_message, *response_obuffer,
                           &dnsserv);
     EXPECT_TRUE(dnsserv.hasAnswer());
-    headerCheck(*parse_message, default_qid, Rcode::NOERROR(), opcode.getCode(),
-                QR_FLAG | AA_FLAG, 1, 1, 1, 0);
+    headerCheck(*parse_message, default_qid, Rcode::NOERROR(),
+                opcode.getCode(), QR_FLAG | AA_FLAG, 1, 1, 1, 0);
 }
 
-TEST_F(AuthSrvTest,
-#ifdef USE_STATIC_LINK
-       DISABLED_updateWithInMemoryClient
-#else
-       updateWithInMemoryClient
-#endif
-    )
-{
+TEST_F(AuthSrvTest, updateWithInMemoryClient) {
     // Test configuring memory data source.  Detailed test cases are covered
     // in the configuration tests.  We only check the AuthSrv interface here.
 
-    // By default memory data source isn't enabled
-    EXPECT_FALSE(server.hasInMemoryClient());
-    updateConfig(&server,
-                 "{\"datasources\": [{\"type\": \"memory\"}]}", true);
+    // Create an empty in-memory
+    const ConstElementPtr config(Element::fromJSON("{"
+        "\"IN\": [{"
+        "   \"type\": \"MasterFiles\","
+        "   \"params\": {},"
+        "   \"cache-enable\": true"
+        "}]}"));
+    DataSourceConfigurator::testReconfigure(&server, config);
     // after successful configuration, we should have one (with empty zoneset).
-    EXPECT_TRUE(server.hasInMemoryClient());
-    EXPECT_EQ(0, server.getInMemoryClient(rrclass)->getZoneCount());
 
     // The memory data source is empty, should return REFUSED rcode.
     createDataFromFile("examplequery_fromWire.wire");
@@ -901,21 +981,12 @@ TEST_F(AuthSrvTest,
                 opcode.getCode(), QR_FLAG, 1, 0, 0, 0);
 }
 
-TEST_F(AuthSrvTest,
-#ifdef USE_STATIC_LINK
-       DISABLED_queryWithInMemoryClientNoDNSSEC
-#else
-       queryWithInMemoryClientNoDNSSEC
-#endif
-    )
-{
+TEST_F(AuthSrvTest, queryWithInMemoryClientNoDNSSEC) {
     // In this example, we do simple check that query is handled from the
     // query handler class, and confirm it returns no error and a non empty
     // answer section.  Detailed examination on the response content
     // for various types of queries are tested in the query tests.
-    updateConfig(&server, CONFIG_INMEMORY_EXAMPLE, true);
-    EXPECT_TRUE(server.hasInMemoryClient());
-    EXPECT_EQ(1, server.getInMemoryClient(rrclass)->getZoneCount());
+    updateInMemory(&server, "example.", CONFIG_INMEMORY_EXAMPLE);
 
     createDataFromFile("nsec3query_nodnssec_fromWire.wire");
     server.processMessage(*io_message, *parse_message, *response_obuffer,
@@ -926,20 +997,11 @@ TEST_F(AuthSrvTest,
                 opcode.getCode(), QR_FLAG | AA_FLAG, 1, 1, 2, 1);
 }
 
-TEST_F(AuthSrvTest,
-#ifdef USE_STATIC_LINK
-       DISABLED_queryWithInMemoryClientDNSSEC
-#else
-       queryWithInMemoryClientDNSSEC
-#endif
-    )
-{
+TEST_F(AuthSrvTest, queryWithInMemoryClientDNSSEC) {
     // Similar to the previous test, but the query has the DO bit on.
     // The response should contain RRSIGs, and should have more RRs than
     // the previous case.
-    updateConfig(&server, CONFIG_INMEMORY_EXAMPLE, true);
-    EXPECT_TRUE(server.hasInMemoryClient());
-    EXPECT_EQ(1, server.getInMemoryClient(rrclass)->getZoneCount());
+    updateInMemory(&server, "example.", CONFIG_INMEMORY_EXAMPLE);
 
     createDataFromFile("nsec3query_fromWire.wire");
     server.processMessage(*io_message, *parse_message, *response_obuffer,
@@ -958,13 +1020,12 @@ TEST_F(AuthSrvTest,
 #endif
     )
 {
-    // Configure memory data source for class IN
-    updateConfig(&server, "{\"datasources\": "
-                 "[{\"class\": \"IN\", \"type\": \"memory\"}]}", true);
+    // Set up the in-memory
+    updateInMemory(&server, "example.", CONFIG_INMEMORY_EXAMPLE);
 
     // This shouldn't affect the result of class CH query
     UnitTestUtil::createRequestMessage(request_message, Opcode::QUERY(),
-                                       default_qid, Name("version.bind"),
+                                       default_qid, Name("VERSION.BIND."),
                                        RRClass::CH(), RRType::TXT());
     createRequestPacket(request_message, IPPROTO_UDP);
     server.processMessage(*io_message, *parse_message, *response_obuffer,
@@ -974,16 +1035,6 @@ TEST_F(AuthSrvTest,
                 opcode.getCode(), QR_FLAG | AA_FLAG, 1, 1, 1, 0);
 }
 
-TEST_F(AuthSrvTest, cacheSlots) {
-    // simple check for the get/set operations
-    server.setCacheSlots(10);    // 10 = arbitrary choice
-    EXPECT_EQ(10, server.getCacheSlots());
-
-    // 0 is a valid size
-    server.setCacheSlots(0);
-    EXPECT_EQ(00, server.getCacheSlots());
-}
-
 // Submit UDP normal query and check query counter
 TEST_F(AuthSrvTest, queryCounterUDPNormal) {
     // The counter should be initialized to 0.
@@ -1290,7 +1341,7 @@ public:
     ///                      throw std::exception
     /// \param fake_rrset If non NULL, it will be used as an answer to
     /// find() for that name and type.
-    FakeClient(isc::datasrc::DataSourceClientContainerPtr real_client,
+    FakeClient(const DataSourceClient* real_client,
                ThrowWhen throw_when, bool isc_exception,
                ConstRRsetPtr fake_rrset = ConstRRsetPtr()) :
         real_client_ptr_(real_client),
@@ -1309,7 +1360,7 @@ public:
     findZone(const isc::dns::Name& name) const {
         checkThrow(THROW_AT_FIND_ZONE, throw_when_, isc_exception_);
         const FindResult result =
-            real_client_ptr_->getInstance().findZone(name);
+            real_client_ptr_->findZone(name);
         return (FindResult(result.code, isc::datasrc::ZoneFinderPtr(
                                         new FakeZoneFinder(result.zone_finder,
                                                            throw_when_,
@@ -1329,38 +1380,39 @@ public:
                   "fake data source");
     }
 private:
-    const isc::datasrc::DataSourceClientContainerPtr real_client_ptr_;
+    const DataSourceClient* real_client_ptr_;
     ThrowWhen throw_when_;
     bool isc_exception_;
     ConstRRsetPtr fake_rrset_;
 };
 
-class FakeContainer : public isc::datasrc::DataSourceClientContainer {
+class FakeList : public isc::datasrc::ConfigurableClientList {
 public:
-    /// \brief Creates a fake container for the given in-memory client
-    ///
-    /// The initializer creates a fresh instance of a memory datasource,
-    /// which is ignored for the rest (but we do not allow 'null' containers
-    /// atm, and this is only needed in these tests, this may be changed
-    /// if we generalize the container class a bit more)
+    /// \brief Creates a fake list for the given in-memory client
     ///
-    /// It will also create a FakeClient, with the given arguments, which
-    /// is actually used when the instance is requested.
-    FakeContainer(isc::datasrc::DataSourceClientContainerPtr real_client,
-                  ThrowWhen throw_when, bool isc_exception,
-                  ConstRRsetPtr fake_rrset = ConstRRsetPtr()) :
-        DataSourceClientContainer("memory",
-                                  Element::fromJSON("{\"type\": \"memory\"}")),
-        client_(new FakeClient(real_client, throw_when, isc_exception,
-                               fake_rrset))
-    {}
-
-    isc::datasrc::DataSourceClient& getInstance() {
-        return (*client_);
+    /// It will create a FakeClient for each client in the original list,
+    /// with the given arguments, which is used when searching for the
+    /// corresponding data source.
+    FakeList(const boost::shared_ptr<isc::datasrc::ConfigurableClientList>
+             real_list, ThrowWhen throw_when, bool isc_exception,
+             ConstRRsetPtr fake_rrset = ConstRRsetPtr()) :
+        ConfigurableClientList(RRClass::IN()),
+        real_(real_list)
+    {
+        BOOST_FOREACH(const DataSourceInfo& info, real_->getDataSources()) {
+             const isc::datasrc::DataSourceClientPtr
+                 client(new FakeClient(info.data_src_client_ != NULL ?
+                                       info.data_src_client_ :
+                                       info.cache_.get(),
+                                       throw_when, isc_exception, fake_rrset));
+             clients_.push_back(client);
+             data_sources_.push_back(DataSourceInfo(client.get(),
+                 isc::datasrc::DataSourceClientContainerPtr(), false));
+        }
     }
-
 private:
-    const boost::scoped_ptr<isc::datasrc::DataSourceClient> client_;
+    const boost::shared_ptr<isc::datasrc::ConfigurableClientList> real_;
+    vector<isc::datasrc::DataSourceClientPtr> clients_;
 };
 
 } // end anonymous namespace for throwing proxy classes
@@ -1378,13 +1430,11 @@ TEST_F(AuthSrvTest,
     )
 {
     // Set real inmem client to proxy
-    updateConfig(&server, CONFIG_INMEMORY_EXAMPLE, true);
-    EXPECT_TRUE(server.hasInMemoryClient());
-
-    isc::datasrc::DataSourceClientContainerPtr fake_client_container(
-        new FakeContainer(server.getInMemoryClientContainer(rrclass),
-                          THROW_NEVER, false));
-    server.setInMemoryClient(rrclass, fake_client_container);
+    updateInMemory(&server, "example.", CONFIG_INMEMORY_EXAMPLE);
+    boost::shared_ptr<isc::datasrc::ConfigurableClientList>
+        list(new FakeList(server.getClientList(RRClass::IN()), THROW_NEVER,
+                          false));
+    server.setClientList(RRClass::IN(), list);
 
     createDataFromFile("nsec3query_nodnssec_fromWire.wire");
     server.processMessage(*io_message, *parse_message, *response_obuffer,
@@ -1402,21 +1452,15 @@ TEST_F(AuthSrvTest,
 // If non null rrset is given, it will be passed to the proxy so it can
 // return some faked response.
 void
-setupThrow(AuthSrv* server, const char *config, ThrowWhen throw_when,
-           bool isc_exception, ConstRRsetPtr rrset = ConstRRsetPtr())
+setupThrow(AuthSrv* server, ThrowWhen throw_when, bool isc_exception,
+           ConstRRsetPtr rrset = ConstRRsetPtr())
 {
-    // Set real inmem client to proxy
-    updateConfig(server, config, true);
-
-    // Set it to throw on findZone(), this should result in
-    // SERVFAIL on any exception
-    isc::datasrc::DataSourceClientContainerPtr fake_client_container(
-        new FakeContainer(
-            server->getInMemoryClientContainer(isc::dns::RRClass::IN()),
-            throw_when, isc_exception, rrset));
+    updateInMemory(server, "example.", CONFIG_INMEMORY_EXAMPLE);
 
-    ASSERT_TRUE(server->hasInMemoryClient());
-    server->setInMemoryClient(isc::dns::RRClass::IN(), fake_client_container);
+    boost::shared_ptr<isc::datasrc::ConfigurableClientList>
+        list(new FakeList(server->getClientList(RRClass::IN()), throw_when,
+                          isc_exception, rrset));
+    server->setClientList(RRClass::IN(), list);
 }
 
 TEST_F(AuthSrvTest,
@@ -1439,11 +1483,11 @@ TEST_F(AuthSrvTest,
                                              RRClass::IN(), RRType::TXT());
     for (ThrowWhen* when(throws); *when != THROW_NEVER; ++when) {
         createRequestPacket(request_message, IPPROTO_UDP);
-        setupThrow(&server, CONFIG_INMEMORY_EXAMPLE, *when, true);
+        setupThrow(&server, *when, true);
         processAndCheckSERVFAIL();
         // To be sure, check same for non-isc-exceptions
         createRequestPacket(request_message, IPPROTO_UDP);
-        setupThrow(&server, CONFIG_INMEMORY_EXAMPLE, *when, false);
+        setupThrow(&server, *when, false);
         processAndCheckSERVFAIL();
     }
 }
@@ -1459,7 +1503,7 @@ TEST_F(AuthSrvTest,
     )
 {
     createDataFromFile("nsec3query_nodnssec_fromWire.wire");
-    setupThrow(&server, CONFIG_INMEMORY_EXAMPLE, THROW_AT_GET_CLASS, true);
+    setupThrow(&server, THROW_AT_GET_CLASS, true);
 
     // getClass is not called so it should just answer
     server.processMessage(*io_message, *parse_message, *response_obuffer,
@@ -1483,8 +1527,7 @@ TEST_F(AuthSrvTest,
     ConstRRsetPtr empty_rrset(new RRset(Name("foo.example"),
                                         RRClass::IN(), RRType::TXT(),
                                         RRTTL(0)));
-    setupThrow(&server, CONFIG_INMEMORY_EXAMPLE, THROW_NEVER, true,
-               empty_rrset);
+    setupThrow(&server, THROW_NEVER, true, empty_rrset);
 
     // Repeat the query processing two times.  Due to the faked RRset,
     // toWire() should throw, and it should result in SERVFAIL.
@@ -1627,7 +1670,7 @@ TEST_F(AuthSrvTest, DDNSForwardPushFail) {
 }
 
 TEST_F(AuthSrvTest, DDNSForwardClose) {
-    scoped_ptr<AuthSrv> tmp_server(new AuthSrv(true, xfrout, ddns_forwarder));
+    scoped_ptr<AuthSrv> tmp_server(new AuthSrv(xfrout, ddns_forwarder));
     tmp_server->createDDNSForwarder();
     UnitTestUtil::createRequestMessage(request_message, Opcode::UPDATE(),
                                        default_qid, Name("example.com"),
@@ -1660,7 +1703,7 @@ TEST_F(AuthSrvTest, DDNSForwardCreateDestroy) {
     // that the ddns_forwarder is connected when the 'start_ddns_forwarder'
     // command has been sent, and that it is no longer connected and auth
     // returns NOTIMP after the stop_ddns_forwarding command is sent.
-    scoped_ptr<AuthSrv> tmp_server(new AuthSrv(true, xfrout, ddns_forwarder));
+    scoped_ptr<AuthSrv> tmp_server(new AuthSrv(xfrout, ddns_forwarder));
 
     // Prepare update message to send
     UnitTestUtil::createRequestMessage(request_message, Opcode::UPDATE(),
@@ -1724,4 +1767,36 @@ TEST_F(AuthSrvTest, DDNSForwardCreateDestroy) {
                 Opcode::UPDATE().getCode(), QR_FLAG, 0, 0, 0, 0);
 }
 
+// Check the client list accessors
+TEST_F(AuthSrvTest, clientList) {
+    // The lists don't exist. Therefore, the list of RRClasses is empty.
+    // We also have no IN list.
+    EXPECT_TRUE(server.getClientListClasses().empty());
+    EXPECT_EQ(boost::shared_ptr<const isc::datasrc::ClientList>(),
+              server.getClientList(RRClass::IN()));
+    // Put something in.
+    const boost::shared_ptr<isc::datasrc::ConfigurableClientList>
+        list(new isc::datasrc::ConfigurableClientList(RRClass::IN()));
+    const boost::shared_ptr<isc::datasrc::ConfigurableClientList>
+        list2(new isc::datasrc::ConfigurableClientList(RRClass::CH()));
+    server.setClientList(RRClass::IN(), list);
+    server.setClientList(RRClass::CH(), list2);
+    // There are two things in the list and they are IN and CH
+    vector<RRClass> classes(server.getClientListClasses());
+    ASSERT_EQ(2, classes.size());
+    EXPECT_EQ(RRClass::IN(), classes[0]);
+    EXPECT_EQ(RRClass::CH(), classes[1]);
+    // And the lists can be retrieved.
+    EXPECT_EQ(list, server.getClientList(RRClass::IN()));
+    EXPECT_EQ(list2, server.getClientList(RRClass::CH()));
+    // Remove one of them
+    server.setClientList(RRClass::CH(),
+        boost::shared_ptr<isc::datasrc::ConfigurableClientList>());
+    // This really got deleted, including the class.
+    classes = server.getClientListClasses();
+    ASSERT_EQ(1, classes.size());
+    EXPECT_EQ(RRClass::IN(), classes[0]);
+    EXPECT_EQ(list, server.getClientList(RRClass::IN()));
+}
+
 }

+ 78 - 185
src/bin/auth/tests/command_unittest.cc

@@ -19,6 +19,7 @@
 #include <auth/auth_srv.h>
 #include <auth/auth_config.h>
 #include <auth/command.h>
+#include <auth/datasrc_configurator.h>
 
 #include <dns/name.h>
 #include <dns/rrclass.h>
@@ -62,14 +63,16 @@ namespace {
 class AuthCommandTest : public ::testing::Test {
 protected:
     AuthCommandTest() :
-        server_(false, xfrout_, ddns_forwarder_),
+        server_(xfrout_, ddns_forwarder_),
         rcode_(-1),
         expect_rcode_(0),
         itimer_(server_.getIOService())
     {
         server_.setStatisticsSession(&statistics_session_);
     }
-    void checkAnswer(const int expected_code) {
+    void checkAnswer(const int expected_code, const char* name = "") {
+        SCOPED_TRACE(name);
+
         parseAnswer(rcode_, result_);
         EXPECT_EQ(expected_code, rcode_) << result_->str();
     }
@@ -182,18 +185,17 @@ TEST_F(AuthCommandTest, shutdownIncorrectPID) {
 // zones, and checks the zones are correctly loaded.
 void
 zoneChecks(AuthSrv& server) {
-    EXPECT_TRUE(server.getInMemoryClient(RRClass::IN()));
-    EXPECT_EQ(ZoneFinder::SUCCESS, server.getInMemoryClient(RRClass::IN())->
-              findZone(Name("ns.test1.example")).zone_finder->
+    EXPECT_EQ(ZoneFinder::SUCCESS, server.getClientList(RRClass::IN())->
+              find(Name("ns.test1.example")).finder_->
               find(Name("ns.test1.example"), RRType::A())->code);
-    EXPECT_EQ(ZoneFinder::NXRRSET, server.getInMemoryClient(RRClass::IN())->
-              findZone(Name("ns.test1.example")).zone_finder->
+    EXPECT_EQ(ZoneFinder::NXRRSET, server.getClientList(RRClass::IN())->
+              find(Name("ns.test1.example")).finder_->
               find(Name("ns.test1.example"), RRType::AAAA())->code);
-    EXPECT_EQ(ZoneFinder::SUCCESS, server.getInMemoryClient(RRClass::IN())->
-              findZone(Name("ns.test2.example")).zone_finder->
+    EXPECT_EQ(ZoneFinder::SUCCESS, server.getClientList(RRClass::IN())->
+              find(Name("ns.test2.example")).finder_->
               find(Name("ns.test2.example"), RRType::A())->code);
-    EXPECT_EQ(ZoneFinder::NXRRSET, server.getInMemoryClient(RRClass::IN())->
-              findZone(Name("ns.test2.example")).zone_finder->
+    EXPECT_EQ(ZoneFinder::NXRRSET, server.getClientList(RRClass::IN())->
+              find(Name("ns.test2.example")).finder_->
               find(Name("ns.test2.example"), RRType::AAAA())->code);
 }
 
@@ -203,48 +205,44 @@ configureZones(AuthSrv& server) {
                         TEST_DATA_BUILDDIR "/test1.zone.copied"));
     ASSERT_EQ(0, system(INSTALL_PROG " -c " TEST_DATA_DIR "/test2.zone.in "
                         TEST_DATA_BUILDDIR "/test2.zone.copied"));
-    configureAuthServer(server, Element::fromJSON(
-                            "{\"datasources\": "
-                            " [{\"type\": \"memory\","
-                            "   \"zones\": "
-                            "[{\"origin\": \"test1.example\","
-                            "  \"file\": \""
-                               TEST_DATA_BUILDDIR "/test1.zone.copied\"},"
-                            " {\"origin\": \"test2.example\","
-                            "  \"file\": \""
-                               TEST_DATA_BUILDDIR "/test2.zone.copied\"}"
-                            "]}]}"));
+
+    const ConstElementPtr config(Element::fromJSON("{"
+        "\"IN\": [{"
+        "   \"type\": \"MasterFiles\","
+        "   \"params\": {"
+        "       \"test1.example\": \"" +
+                string(TEST_DATA_BUILDDIR "/test1.zone.copied") + "\","
+        "       \"test2.example\": \"" +
+                string(TEST_DATA_BUILDDIR "/test2.zone.copied") + "\""
+        "   },"
+        "   \"cache-enable\": true"
+        "}]}"));
+
+    DataSourceConfigurator::testReconfigure(&server, config);
+
     zoneChecks(server);
 }
 
 void
 newZoneChecks(AuthSrv& server) {
-    EXPECT_TRUE(server.getInMemoryClient(RRClass::IN()));
-    EXPECT_EQ(ZoneFinder::SUCCESS, server.getInMemoryClient(RRClass::IN())->
-              findZone(Name("ns.test1.example")).zone_finder->
+    EXPECT_EQ(ZoneFinder::SUCCESS, server.getClientList(RRClass::IN())->
+              find(Name("ns.test1.example")).finder_->
               find(Name("ns.test1.example"), RRType::A())->code);
     // now test1.example should have ns/AAAA
-    EXPECT_EQ(ZoneFinder::SUCCESS, server.getInMemoryClient(RRClass::IN())->
-              findZone(Name("ns.test1.example")).zone_finder->
+    EXPECT_EQ(ZoneFinder::SUCCESS, server.getClientList(RRClass::IN())->
+              find(Name("ns.test1.example")).finder_->
               find(Name("ns.test1.example"), RRType::AAAA())->code);
 
     // test2.example shouldn't change
-    EXPECT_EQ(ZoneFinder::SUCCESS, server.getInMemoryClient(RRClass::IN())->
-              findZone(Name("ns.test2.example")).zone_finder->
+    EXPECT_EQ(ZoneFinder::SUCCESS, server.getClientList(RRClass::IN())->
+              find(Name("ns.test2.example")).finder_->
               find(Name("ns.test2.example"), RRType::A())->code);
-    EXPECT_EQ(ZoneFinder::NXRRSET, server.getInMemoryClient(RRClass::IN())->
-              findZone(Name("ns.test2.example")).zone_finder->
+    EXPECT_EQ(ZoneFinder::NXRRSET, server.getClientList(RRClass::IN())->
+              find(Name("ns.test2.example")).finder_->
               find(Name("ns.test2.example"), RRType::AAAA())->code);
 }
 
-TEST_F(AuthCommandTest,
-#ifdef USE_STATIC_LINK
-       DISABLED_loadZone
-#else
-       loadZone
-#endif
-    )
-{
+TEST_F(AuthCommandTest, loadZone) {
     configureZones(server_);
 
     ASSERT_EQ(0, system(INSTALL_PROG " -c " TEST_DATA_DIR
@@ -269,47 +267,24 @@ TEST_F(AuthCommandTest,
 #endif
     )
 {
-    const char* const SPEC_FILE = AUTH_OBJ_DIR "/auth.spec";
-
     // Prepare the database first
     const string test_db = TEST_DATA_BUILDDIR "/auth_test.sqlite3.copied";
     const string bad_db = TEST_DATA_BUILDDIR "/does-not-exist.sqlite3";
     stringstream ss("example.org. 3600 IN SOA . . 0 0 0 0 0\n");
     createSQLite3DB(RRClass::IN(), Name("example.org"), test_db.c_str(), ss);
-
-    // Then store a config of the zone to the auth server
-    // This omits many config options of the auth server, but these are
-    // not read now.
-    isc::testutils::MockSession session;
-    // The session should not take care of anything or start anything, we
-    // need it only to hold the config we're going to put into it.
-    ModuleCCSession module_session(SPEC_FILE, session, NULL, NULL, false,
-                                  false);
     // This describes the data source in the configuration
-    const ElementPtr
-        map(Element::fromJSON("{\"datasources\": ["
-                              "  {"
-                              "    \"type\": \"memory\","
-                              "    \"zones\": ["
-                              "      {"
-                              "        \"origin\": \"example.org\","
-                              "        \"file\": \"" + test_db + "\","
-                              "        \"filetype\": \"sqlite3\""
-                              "      }"
-                              "    ]"
-                              "  }"
-                              "],"
-                              " \"database_file\": \"" + test_db + "\""
-                              "}"));
-    module_session.setLocalConfig(map);
-    server_.setConfigSession(&module_session);
-
-    server_.updateConfig(map);
+    const ConstElementPtr config(Element::fromJSON("{"
+        "\"IN\": [{"
+        "    \"type\": \"sqlite3\","
+        "    \"params\": {\"database_file\": \"" + test_db + "\"},"
+        "    \"cache-enable\": true,"
+        "    \"cache-zones\": [\"example.org\"]"
+        "}]}"));
+    DataSourceConfigurator::testReconfigure(&server_, config);
 
     // Check that the A record at www.example.org does not exist
-    ASSERT_TRUE(server_.hasInMemoryClient());
-    EXPECT_EQ(ZoneFinder::NXDOMAIN, server_.getInMemoryClient(RRClass::IN())->
-              findZone(Name("example.org")).zone_finder->
+    EXPECT_EQ(ZoneFinder::NXDOMAIN, server_.getClientList(RRClass::IN())->
+              find(Name("example.org")).finder_->
               find(Name("www.example.org"), RRType::A())->code);
 
     // Add the record to the underlying sqlite database, by loading
@@ -328,90 +303,52 @@ TEST_F(AuthCommandTest,
     sql_updater->addRRset(*rrset);
     sql_updater->commit();
 
-    // This new record is in the database now, but should not be in the
-    // memory-datasource yet, so check again
-    EXPECT_EQ(ZoneFinder::NXDOMAIN, server_.getInMemoryClient(RRClass::IN())->
-              findZone(Name("example.org")).zone_finder->
+    EXPECT_EQ(ZoneFinder::NXDOMAIN, server_.getClientList(RRClass::IN())->
+              find(Name("example.org")).finder_->
               find(Name("www.example.org"), RRType::A())->code);
 
     // Now send the command to reload it
     result_ = execAuthServerCommand(server_, "loadzone",
                                     Element::fromJSON(
                                         "{\"origin\": \"example.org\"}"));
-    checkAnswer(0);
+    checkAnswer(0, "Successful load");
 
     // And now it should be present too.
-    EXPECT_EQ(ZoneFinder::SUCCESS, server_.getInMemoryClient(RRClass::IN())->
-              findZone(Name("example.org")).zone_finder->
+    EXPECT_EQ(ZoneFinder::SUCCESS, server_.getClientList(RRClass::IN())->
+              find(Name("example.org")).finder_->
               find(Name("www.example.org"), RRType::A())->code);
 
     // Some error cases. First, the zone has no configuration. (note .com here)
     result_ = execAuthServerCommand(server_, "loadzone",
         Element::fromJSON("{\"origin\": \"example.com\"}"));
-    checkAnswer(1);
+    checkAnswer(1, "example.com");
+
     // The previous zone is not hurt in any way
-    EXPECT_EQ(ZoneFinder::SUCCESS, server_.getInMemoryClient(RRClass::IN())->
-              findZone(Name("example.org")).zone_finder->
+    EXPECT_EQ(ZoneFinder::SUCCESS, server_.getClientList(RRClass::IN())->
+              find(Name("example.org")).finder_->
               find(Name("example.org"), RRType::SOA())->code);
 
-    module_session.setLocalConfig(Element::fromJSON("{\"datasources\": []}"));
-    result_ = execAuthServerCommand(server_, "loadzone",
-                                    Element::fromJSON(
-                                        "{\"origin\": \"example.org\"}"));
-    checkAnswer(1);
+    const ConstElementPtr config2(Element::fromJSON("{"
+        "\"IN\": [{"
+        "    \"type\": \"sqlite3\","
+        "    \"params\": {\"database_file\": \"" + bad_db + "\"},"
+        "    \"cache-enable\": true,"
+        "    \"cache-zones\": [\"example.com\"]"
+        "}]}"));
+    EXPECT_THROW(DataSourceConfigurator::testReconfigure(&server_, config2),
+                 ConfigurableClientList::ConfigurationError);
 
-    // The previous zone is not hurt in any way
-    EXPECT_EQ(ZoneFinder::SUCCESS, server_.getInMemoryClient(RRClass::IN())->
-              findZone(Name("example.org")).zone_finder->
-              find(Name("example.org"), RRType::SOA())->code);
-    // Configure an unreadable zone. Should fail, but leave the original zone
-    // data there
-    const ElementPtr
-        mapBad(Element::fromJSON("{\"datasources\": ["
-                                 "  {"
-                                 "    \"type\": \"memory\","
-                                 "    \"zones\": ["
-                                 "      {"
-                                 "        \"origin\": \"example.org\","
-                                 "        \"file\": \"" + bad_db + "\","
-                                 "        \"filetype\": \"sqlite3\""
-                                 "      }"
-                                 "    ]"
-                                 "  }"
-                                 "]}"));
-    module_session.setLocalConfig(mapBad);
     result_ = execAuthServerCommand(server_, "loadzone",
         Element::fromJSON("{\"origin\": \"example.com\"}"));
-    checkAnswer(1);
-    // The previous zone is not hurt in any way
-    EXPECT_EQ(ZoneFinder::SUCCESS, server_.getInMemoryClient(RRClass::IN())->
-              findZone(Name("example.org")).zone_finder->
-              find(Name("example.org"), RRType::SOA())->code);
+    checkAnswer(1, "Unreadable");
 
-    // Broken configuration (not valid against the spec)
-    const ElementPtr
-        broken(Element::fromJSON("{\"datasources\": ["
-                                 "  {"
-                                 "    \"type\": \"memory\","
-                                 "    \"zones\": [[]]"
-                                 "  }"
-                                 "]}"));
-    module_session.setLocalConfig(broken);
-    checkAnswer(1);
     // The previous zone is not hurt in any way
-    EXPECT_EQ(ZoneFinder::SUCCESS, server_.getInMemoryClient(RRClass::IN())->
-              findZone(Name("example.org")).zone_finder->
+    EXPECT_EQ(ZoneFinder::SUCCESS, server_.getClientList(RRClass::IN())->
+              find(Name("example.org")).finder_->
               find(Name("example.org"), RRType::SOA())->code);
 }
 
-TEST_F(AuthCommandTest,
-#ifdef USE_STATIC_LINK
-       DISABLED_loadBrokenZone
-#else
-       loadBrokenZone
-#endif
-    )
-{
+TEST_F(AuthCommandTest, loadBrokenZone) {
     configureZones(server_);
 
     ASSERT_EQ(0, system(INSTALL_PROG " -c " TEST_DATA_DIR
@@ -424,14 +361,7 @@ TEST_F(AuthCommandTest,
     zoneChecks(server_);     // zone shouldn't be replaced
 }
 
-TEST_F(AuthCommandTest,
-#ifdef USE_STATIC_LINK
-       DISABLED_loadUnreadableZone
-#else
-       loadUnreadableZone
-#endif
-    )
-{
+TEST_F(AuthCommandTest, loadUnreadableZone) {
     configureZones(server_);
 
     // install the zone file as unreadable
@@ -454,82 +384,45 @@ TEST_F(AuthCommandTest, loadZoneWithoutDataSrc) {
     checkAnswer(1);
 }
 
-TEST_F(AuthCommandTest, loadSqlite3DataSrc) {
-    // For sqlite3 data source we don't have to do anything (the data source
-    // (re)loads itself automatically)
-    result_ = execAuthServerCommand(server_, "loadzone",
-                                    Element::fromJSON(
-                                        "{\"origin\": \"test1.example\","
-                                        " \"datasrc\": \"sqlite3\"}"));
-    checkAnswer(0);
-}
-
-TEST_F(AuthCommandTest,
-#ifdef USE_STATIC_LINK
-       DISABLED_loadZoneInvalidParams
-#else
-       loadZoneInvalidParams
-#endif
-    )
-{
+TEST_F(AuthCommandTest, loadZoneInvalidParams) {
     configureZones(server_);
 
     // null arg
     result_ = execAuthServerCommand(server_, "loadzone", ElementPtr());
-    checkAnswer(1);
+    checkAnswer(1, "Null arg");
 
     // zone class is bogus
     result_ = execAuthServerCommand(server_, "loadzone",
                                     Element::fromJSON(
                                         "{\"origin\": \"test1.example\","
                                         " \"class\": \"no_such_class\"}"));
-    checkAnswer(1);
+    checkAnswer(1, "No such class");
 
     result_ = execAuthServerCommand(server_, "loadzone",
                                     Element::fromJSON(
                                         "{\"origin\": \"test1.example\","
                                         " \"class\": 1}"));
-    checkAnswer(1);
+    checkAnswer(1, "Integral class");
 
-    // unsupported zone class
-    result_ = execAuthServerCommand(server_, "loadzone",
-                                    Element::fromJSON(
-                                        "{\"origin\": \"test1.example\","
-                                        " \"class\": \"CH\"}"));
-    checkAnswer(1);
-
-    // unsupported data source class
-    result_ = execAuthServerCommand(server_, "loadzone",
-                                    Element::fromJSON(
-                                        "{\"origin\": \"test1.example\","
-                                        " \"datasrc\": \"not supported\"}"));
-    checkAnswer(1);
-
-    // data source is bogus
-    result_ = execAuthServerCommand(server_, "loadzone",
-                                    Element::fromJSON(
-                                        "{\"origin\": \"test1.example\","
-                                        " \"datasrc\": 0}"));
-    checkAnswer(1);
 
     // origin is missing
     result_ = execAuthServerCommand(server_, "loadzone",
                                     Element::fromJSON("{}"));
-    checkAnswer(1);
+    checkAnswer(1, "Missing origin");
 
     // zone doesn't exist in the data source
     result_ = execAuthServerCommand(server_, "loadzone",
                                     Element::fromJSON("{\"origin\": \"xx\"}"));
-    checkAnswer(1);
+    checkAnswer(1, "No such zone");
 
     // origin is bogus
     result_ = execAuthServerCommand(server_, "loadzone",
                                     Element::fromJSON(
                                         "{\"origin\": \"...\"}"));
-    checkAnswer(1);
+    checkAnswer(1, "Wrong name");
 
     result_ = execAuthServerCommand(server_, "loadzone",
                                     Element::fromJSON("{\"origin\": 10}"));
-    checkAnswer(1);
+    checkAnswer(1, "Integral name");
 }
 }

+ 5 - 383
src/bin/auth/tests/config_unittest.cc

@@ -54,7 +54,7 @@ protected:
     AuthConfigTest() :
         dnss_(),
         rrclass(RRClass::IN()),
-        server(true, xfrout, ddns_forwarder),
+        server(xfrout, ddns_forwarder),
         // The empty string is expected value of the parameter of
         // requestSocket, not the app_name (there's no fallback, it checks
         // the empty string is passed).
@@ -72,32 +72,6 @@ private:
     isc::testutils::TestSocketRequestor sock_requestor_;
 };
 
-TEST_F(AuthConfigTest,
-#ifdef USE_STATIC_LINK
-       DISABLED_datasourceConfig
-#else
-       datasourceConfig
-#endif
-    )
-{
-    // By default, we don't have any in-memory data source.
-    EXPECT_FALSE(server.hasInMemoryClient());
-    configureAuthServer(server, Element::fromJSON(
-                            "{\"datasources\": [{\"type\": \"memory\"}]}"));
-    // after successful configuration, we should have one (with empty zoneset).
-    EXPECT_TRUE(server.hasInMemoryClient());
-    EXPECT_EQ(0, server.getInMemoryClient(rrclass)->getZoneCount());
-}
-
-TEST_F(AuthConfigTest, databaseConfig) {
-    // right now, "database_file" is handled separately, so the parser
-    // doesn't recognize it, but it shouldn't throw an exception due to that.
-    EXPECT_NO_THROW(configureAuthServer(
-                        server,
-                        Element::fromJSON(
-                            "{\"database_file\": \"should_be_ignored\"}")));
-}
-
 TEST_F(AuthConfigTest, versionConfig) {
     // make sure it does not throw on 'version'
     EXPECT_NO_THROW(configureAuthServer(
@@ -106,32 +80,17 @@ TEST_F(AuthConfigTest, versionConfig) {
 }
 
 TEST_F(AuthConfigTest, exceptionGuarantee) {
-    EXPECT_FALSE(server.hasInMemoryClient());
+    server.setStatisticsTimerInterval(1234);
+    EXPECT_EQ(1234, server.getStatisticsTimerInterval());
     // This configuration contains an invalid item, which will trigger
     // an exception.
     EXPECT_THROW(configureAuthServer(
                      server,
                      Element::fromJSON(
-                         "{\"datasources\": [{\"type\": \"memory\"}], "
-                         " \"no_such_config_var\": 1}")),
+                         "{ \"no_such_config_var\": 1}")),
                  AuthConfigError);
     // The server state shouldn't change
-    EXPECT_FALSE(server.hasInMemoryClient());
-}
-
-TEST_F(AuthConfigTest, exceptionConversion) {
-    // This configuration contains a bogus RR class, which will trigger an
-    // exception from libdns++.  configureAuthServer() should convert this
-    // to AuthConfigError and rethrow the converted one.
-    EXPECT_THROW(configureAuthServer(
-                     server,
-                     Element::fromJSON(
-                         "{\"datasources\": "
-                         " [{\"type\": \"memory\","
-                         "   \"class\": \"BADCLASS\","
-                         "   \"zones\": [{\"origin\": \"example.com\","
-                         "                \"file\": \"example.zone\"}]}]}")),
-                 AuthConfigError);
+    EXPECT_EQ(1234, server.getStatisticsTimerInterval());
 }
 
 TEST_F(AuthConfigTest, badConfig) {
@@ -172,343 +131,6 @@ TEST_F(AuthConfigTest, listenAddressConfig) {
     EXPECT_EQ(DNSService::SERVER_SYNC_OK, dnss_.getUDPFdParams().at(1).options);
 }
 
-class MemoryDatasrcConfigTest : public AuthConfigTest {
-protected:
-    MemoryDatasrcConfigTest() :
-        parser(createAuthConfigParser(server, "datasources"))
-    {}
-    ~MemoryDatasrcConfigTest() {
-        delete parser;
-    }
-    AuthConfigParser* parser;
-};
-
-TEST_F(MemoryDatasrcConfigTest, addZeroDataSrc) {
-    parser->build(Element::fromJSON("[]"));
-    parser->commit();
-    EXPECT_FALSE(server.hasInMemoryClient());
-}
-
-TEST_F(MemoryDatasrcConfigTest,
-#ifdef USE_STATIC_LINK
-       DISABLED_addEmpty
-#else
-       addEmpty
-#endif
-    )
-{
-    // By default, we don't have any in-memory data source.
-    EXPECT_FALSE(server.hasInMemoryClient());
-    parser->build(Element::fromJSON("[{\"type\": \"memory\"}]"));
-    parser->commit();
-    EXPECT_EQ(0, server.getInMemoryClient(rrclass)->getZoneCount());
-}
-
-TEST_F(MemoryDatasrcConfigTest,
-#ifdef USE_STATIC_LINK
-       DISABLED_addZeroZone
-#else
-       addZeroZone
-#endif
-    )
-{
-    parser->build(Element::fromJSON("[{\"type\": \"memory\","
-                                    "  \"zones\": []}]"));
-    parser->commit();
-    EXPECT_EQ(0, server.getInMemoryClient(rrclass)->getZoneCount());
-}
-
-TEST_F(MemoryDatasrcConfigTest,
-#ifdef USE_STATIC_LINK
-       DISABLED_addOneZone
-#else
-       addOneZone
-#endif
-    )
-{
-    EXPECT_NO_THROW(parser->build(Element::fromJSON(
-                      "[{\"type\": \"memory\","
-                      "  \"zones\": [{\"origin\": \"example.com\","
-                      "               \"file\": \"" TEST_DATA_DIR
-                      "/example.zone\"}]}]")));
-    EXPECT_NO_THROW(parser->commit());
-    EXPECT_EQ(1, server.getInMemoryClient(rrclass)->getZoneCount());
-    // Check it actually loaded something
-    EXPECT_EQ(ZoneFinder::SUCCESS, server.getInMemoryClient(rrclass)->findZone(
-        Name("ns.example.com.")).zone_finder->find(Name("ns.example.com."),
-        RRType::A())->code);
-}
-
-// This test uses dynamic load of a data source module, and won't work when
-// statically linked.
-TEST_F(MemoryDatasrcConfigTest,
-#ifdef USE_STATIC_LINK
-       DISABLED_addOneWithFiletypeSQLite3
-#else
-       addOneWithFiletypeSQLite3
-#endif
-    )
-{
-    const string test_db = TEST_DATA_BUILDDIR "/auth_test.sqlite3.copied";
-    stringstream ss("example.org. 3600 IN SOA . . 0 0 0 0 0\n");
-    createSQLite3DB(rrclass, Name("example.org"), test_db.c_str(), ss);
-
-    // In-memory with an SQLite3 data source as the backend.
-    parser->build(Element::fromJSON(
-                      "[{\"type\": \"memory\","
-                      "  \"zones\": [{\"origin\": \"example.org\","
-                      "               \"file\": \""
-                      + test_db +  "\","
-                      "               \"filetype\": \"sqlite3\"}]}]"));
-    parser->commit();
-    EXPECT_EQ(1, server.getInMemoryClient(rrclass)->getZoneCount());
-
-    // Failure case: the specified zone doesn't exist in the DB file.
-    delete parser;
-    parser = createAuthConfigParser(server, "datasources");
-    EXPECT_THROW(parser->build(
-                     Element::fromJSON(
-                         "[{\"type\": \"memory\","
-                         "  \"zones\": [{\"origin\": \"example.com\","
-                         "               \"file\": \""
-                         + test_db +  "\","
-                         "               \"filetype\": \"sqlite3\"}]}]")),
-                 DataSourceError);
-}
-
-TEST_F(MemoryDatasrcConfigTest,
-#ifdef USE_STATIC_LINK
-       DISABLED_addOneWithFiletypeText
-#else
-       addOneWithFiletypeText
-#endif
-    )
-{
-    // Explicitly specifying "text" is okay.
-    parser->build(Element::fromJSON(
-                      "[{\"type\": \"memory\","
-                      "  \"zones\": [{\"origin\": \"example.com\","
-                      "               \"file\": \""
-                      TEST_DATA_DIR "/example.zone\","
-                      "               \"filetype\": \"text\"}]}]"));
-    parser->commit();
-    EXPECT_EQ(1, server.getInMemoryClient(rrclass)->getZoneCount());
-}
-
-TEST_F(MemoryDatasrcConfigTest,
-#ifdef USE_STATIC_LINK
-       DISABLED_addMultiZones
-#else
-       addMultiZones
-#endif
-    )
-{
-    EXPECT_NO_THROW(parser->build(Element::fromJSON(
-                      "[{\"type\": \"memory\","
-                      "  \"zones\": [{\"origin\": \"example.com\","
-                      "               \"file\": \"" TEST_DATA_DIR
-                      "/example.zone\"},"
-                      "              {\"origin\": \"example.org\","
-                      "               \"file\": \"" TEST_DATA_DIR
-                      "/example.org.zone\"},"
-                      "              {\"origin\": \"example.net\","
-                      "               \"file\": \"" TEST_DATA_DIR
-                      "/example.net.zone\"}]}]")));
-    EXPECT_NO_THROW(parser->commit());
-    EXPECT_EQ(3, server.getInMemoryClient(rrclass)->getZoneCount());
-}
-
-TEST_F(MemoryDatasrcConfigTest,
-#ifdef USE_STATIC_LINK
-       DISABLED_replace
-#else
-       replace
-#endif
-    )
-{
-    EXPECT_NO_THROW(parser->build(Element::fromJSON(
-                      "[{\"type\": \"memory\","
-                      "  \"zones\": [{\"origin\": \"example.com\","
-                      "               \"file\": \"" TEST_DATA_DIR
-                      "/example.zone\"}]}]")));
-    EXPECT_NO_THROW(parser->commit());
-    EXPECT_EQ(1, server.getInMemoryClient(rrclass)->getZoneCount());
-    EXPECT_EQ(isc::datasrc::result::SUCCESS,
-              server.getInMemoryClient(rrclass)->findZone(
-                  Name("example.com")).code);
-
-    // create a new parser, and install a new set of configuration.  It
-    // should replace the old one.
-    delete parser;
-    parser = createAuthConfigParser(server, "datasources"); 
-    EXPECT_NO_THROW(parser->build(Element::fromJSON(
-                      "[{\"type\": \"memory\","
-                      "  \"zones\": [{\"origin\": \"example.org\","
-                      "               \"file\": \"" TEST_DATA_DIR
-                      "/example.org.zone\"},"
-                      "              {\"origin\": \"example.net\","
-                      "               \"file\": \"" TEST_DATA_DIR
-                      "/example.net.zone\"}]}]")));
-    EXPECT_NO_THROW(parser->commit());
-    EXPECT_EQ(2, server.getInMemoryClient(rrclass)->getZoneCount());
-    EXPECT_EQ(isc::datasrc::result::NOTFOUND,
-              server.getInMemoryClient(rrclass)->findZone(
-                  Name("example.com")).code);
-}
-
-TEST_F(MemoryDatasrcConfigTest,
-#ifdef USE_STATIC_LINK
-       DISABLED_exception
-#else
-       exception
-#endif
-    )
-{
-    // Load a zone
-    EXPECT_NO_THROW(parser->build(Element::fromJSON(
-                      "[{\"type\": \"memory\","
-                      "  \"zones\": [{\"origin\": \"example.com\","
-                      "               \"file\": \"" TEST_DATA_DIR
-                      "/example.zone\"}]}]")));
-    EXPECT_NO_THROW(parser->commit());
-    EXPECT_EQ(1, server.getInMemoryClient(rrclass)->getZoneCount());
-    EXPECT_EQ(isc::datasrc::result::SUCCESS,
-              server.getInMemoryClient(rrclass)->findZone(
-                  Name("example.com")).code);
-
-    // create a new parser, and try to load something. It will throw,
-    // the given master file should not exist
-    delete parser;
-    parser = createAuthConfigParser(server, "datasources");
-    EXPECT_THROW(parser->build(Element::fromJSON(
-                      "[{\"type\": \"memory\","
-                      "  \"zones\": [{\"origin\": \"example.org\","
-                      "               \"file\": \"" TEST_DATA_DIR
-                      "/example.org.zone\"},"
-                      "              {\"origin\": \"example.net\","
-                      "               \"file\": \"" TEST_DATA_DIR
-                      "/nonexistent.zone\"}]}]")),
-                 isc::datasrc::DataSourceError);
-    // As that one throwed exception, it is not expected from us to
-    // commit it
-
-    // The original should be untouched
-    EXPECT_EQ(1, server.getInMemoryClient(rrclass)->getZoneCount());
-    EXPECT_EQ(isc::datasrc::result::SUCCESS,
-              server.getInMemoryClient(rrclass)->findZone(
-                  Name("example.com")).code);
-}
-
-TEST_F(MemoryDatasrcConfigTest,
-#ifdef USE_STATIC_LINK
-       DISABLED_remove
-#else
-       remove
-#endif
-    )
-{
-    EXPECT_NO_THROW(parser->build(Element::fromJSON(
-                      "[{\"type\": \"memory\","
-                      "  \"zones\": [{\"origin\": \"example.com\","
-                      "               \"file\": \"" TEST_DATA_DIR
-                      "/example.zone\"}]}]")));
-    EXPECT_NO_THROW(parser->commit());
-    EXPECT_EQ(1, server.getInMemoryClient(rrclass)->getZoneCount());
-
-    delete parser;
-    parser = createAuthConfigParser(server, "datasources"); 
-    EXPECT_NO_THROW(parser->build(Element::fromJSON("[]")));
-    EXPECT_NO_THROW(parser->commit());
-    EXPECT_FALSE(server.hasInMemoryClient());
-}
-
-TEST_F(MemoryDatasrcConfigTest, addDuplicateZones) {
-    EXPECT_THROW(parser->build(
-                     Element::fromJSON(
-                         "[{\"type\": \"memory\","
-                         "  \"zones\": [{\"origin\": \"example.com\","
-                         "               \"file\": \"" TEST_DATA_DIR
-                         "/example.zone\"},"
-                         "              {\"origin\": \"example.com\","
-                         "               \"file\": \"" TEST_DATA_DIR
-                         "/example.com.zone\"}]}]")),
-                 DataSourceError);
-}
-
-TEST_F(MemoryDatasrcConfigTest, addBadZone) {
-    // origin and file are missing
-    EXPECT_THROW(parser->build(
-                     Element::fromJSON(
-                         "[{\"type\": \"memory\","
-                         "  \"zones\": [{}]}]")),
-                 DataSourceError);
-
-    // origin is missing
-    EXPECT_THROW(parser->build(
-                     Element::fromJSON(
-                         "[{\"type\": \"memory\","
-                         "  \"zones\": [{\"file\": \"example.zone\"}]}]")),
-                 DataSourceError);
-
-    // file is missing
-    EXPECT_THROW(parser->build(
-                     Element::fromJSON(
-                         "[{\"type\": \"memory\","
-                         "  \"zones\": [{\"origin\": \"example.com\"}]}]")),
-                 DataSourceError);
-
-    // missing zone file
-    EXPECT_THROW(parser->build(
-                     Element::fromJSON(
-                         "[{\"type\": \"memory\","
-                         "  \"zones\": [{\"origin\": \"example.com\"}]}]")),
-                 DataSourceError);
-
-    // bogus origin name
-    EXPECT_THROW(parser->build(Element::fromJSON(
-                      "[{\"type\": \"memory\","
-                      "  \"zones\": [{\"origin\": \"example..com\","
-                      "               \"file\": \"example.zone\"}]}]")),
-                 DataSourceError);
-
-    // bogus RR class name
-    EXPECT_THROW(parser->build(
-                     Element::fromJSON(
-                         "[{\"type\": \"memory\","
-                         "  \"class\": \"BADCLASS\","
-                         "  \"zones\": [{\"origin\": \"example.com\","
-                         "               \"file\": \"example.zone\"}]}]")),
-                 InvalidRRClass);
-
-    // valid RR class, but not currently supported
-    EXPECT_THROW(parser->build(
-                     Element::fromJSON(
-                         "[{\"type\": \"memory\","
-                         "  \"class\": \"CH\","
-                         "  \"zones\": [{\"origin\": \"example.com\","
-                         "               \"file\": \"example.zone\"}]}]")),
-                 isc::InvalidParameter);
-}
-
-TEST_F(MemoryDatasrcConfigTest,
-#ifdef USE_STATIC_LINK
-       DISABLED_badDatasrcType
-#else
-       badDatasrcType
-#endif
-    )
-{
-    EXPECT_THROW(parser->build(Element::fromJSON("[{\"type\": \"badsrc\"}]")),
-                 AuthConfigError);
-    EXPECT_THROW(parser->build(Element::fromJSON("[{\"notype\": \"memory\"}]")),
-                 AuthConfigError);
-    EXPECT_THROW(parser->build(Element::fromJSON("[{\"type\": 1}]")),
-                                      isc::data::TypeError);
-    EXPECT_THROW(parser->build(Element::fromJSON("[{\"type\": \"memory\"},"
-                                                 " {\"type\": \"memory\"}]")),
-                 AuthConfigError);
-}
-
 class StatisticsIntervalConfigTest : public AuthConfigTest {
 protected:
     StatisticsIntervalConfigTest() :

+ 298 - 0
src/bin/auth/tests/datasrc_configurator_unittest.cc

@@ -0,0 +1,298 @@
+// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <auth/datasrc_configurator.h>
+
+#include <config/tests/fake_session.h>
+#include <config/ccsession.h>
+
+#include <gtest/gtest.h>
+#include <memory>
+#include <boost/shared_ptr.hpp>
+
+using namespace isc;
+using namespace isc::cc;
+using namespace isc::config;
+using namespace isc::data;
+using namespace isc::dns;
+using namespace std;
+using namespace boost;
+
+namespace {
+
+class DatasrcConfiguratorTest;
+
+class FakeList {
+public:
+    FakeList(const RRClass&) :
+        configuration_(new ListElement)
+    {}
+    void configure(const ConstElementPtr& configuration, bool allow_cache) {
+        EXPECT_TRUE(allow_cache);
+        conf_ = configuration->get(0)->get("type")->stringValue();
+        configuration_ = configuration;
+    }
+    const string& getConf() const {
+        return (conf_);
+    }
+    ConstElementPtr getConfiguration() const {
+        return (configuration_);
+    }
+private:
+    string conf_;
+    ConstElementPtr configuration_;
+};
+
+typedef shared_ptr<FakeList> ListPtr;
+
+// We use the test fixture as both parameters, this makes it possible
+// to easily fake all needed methods and look that they were called.
+typedef DataSourceConfiguratorGeneric<DatasrcConfiguratorTest,
+        FakeList> Configurator;
+
+class DatasrcConfiguratorTest : public ::testing::Test {
+public:
+    // These pretend to be the server
+    ListPtr getClientList(const RRClass& rrclass) {
+        log_ += "get " + rrclass.toText() + "\n";
+        return (lists_[rrclass]);
+    }
+    void setClientList(const RRClass& rrclass, const ListPtr& list) {
+        log_ += "set " + rrclass.toText() + " " +
+            (list ? list->getConf() : "") + "\n";
+        lists_[rrclass] = list;
+    }
+    vector<RRClass> getClientListClasses() const {
+        vector<RRClass> result;
+        for (map<RRClass, ListPtr>::const_iterator it(lists_.begin());
+             it != lists_.end(); ++it) {
+            result.push_back(it->first);
+        }
+        return (result);
+    }
+protected:
+    DatasrcConfiguratorTest() :
+        session(ElementPtr(new ListElement), ElementPtr(new ListElement),
+                ElementPtr(new ListElement)),
+        specfile(string(TEST_OWN_DATA_DIR) + "/spec.spec")
+    {
+        initSession();
+    }
+    void initSession() {
+        session.getMessages()->add(createAnswer());
+        mccs.reset(new ModuleCCSession(specfile, session, NULL, NULL, false,
+                                       false));
+    }
+    void TearDown() {
+        // Make sure no matter what we did, it is cleaned up.
+        Configurator::cleanup();
+    }
+    void init(const ElementPtr& config = ElementPtr()) {
+        session.getMessages()->
+            add(createAnswer(0,
+                             moduleSpecFromFile(string(PLUGIN_DATA_PATH) +
+                                                "/datasrc.spec").
+                             getFullSpec()));
+        if (config) {
+            session.getMessages()->add(createAnswer(0, config));
+        } else {
+            session.getMessages()->
+                add(createAnswer(0, ElementPtr(new MapElement)));
+        }
+        Configurator::init(mccs.get(), this);
+    }
+    void SetUp() {
+        init();
+    }
+    ElementPtr buildConfig(const string& config) const {
+        const ElementPtr internal(Element::fromJSON(config));
+        const ElementPtr external(Element::fromJSON("{\"version\": 1}"));
+        external->set("classes", internal);
+        return (external);
+    }
+    void initializeINList() {
+        const ElementPtr
+            config(buildConfig("{\"IN\": [{\"type\": \"xxx\"}]}"));
+        session.addMessage(createCommand("config_update", config), "data_sources",
+                           "*");
+        mccs->checkCommand();
+        // Check it called the correct things (check that there's no IN yet and
+        // set a new one.
+        EXPECT_EQ("get IN\nset IN xxx\n", log_);
+        EXPECT_EQ(1, lists_.size());
+    }
+    FakeSession session;
+    auto_ptr<ModuleCCSession> mccs;
+    const string specfile;
+    map<RRClass, ListPtr> lists_;
+    string log_;
+};
+
+// Check the initialization (and cleanup)
+TEST_F(DatasrcConfiguratorTest, initialization) {
+    // It can't be initialized again
+    EXPECT_THROW(init(), InvalidOperation);
+    EXPECT_TRUE(session.haveSubscription("data_sources", "*"));
+    // Deinitialize to make the tests reasonable
+    Configurator::cleanup();
+    EXPECT_FALSE(session.haveSubscription("data_sources", "*"));
+    // We can't reconfigure now (not even manually)
+    EXPECT_THROW(Configurator::reconfigure(ElementPtr(new MapElement())),
+                 InvalidOperation);
+    // If one of them is NULL, it does not work
+    EXPECT_THROW(Configurator::init(NULL, this), InvalidParameter);
+    EXPECT_FALSE(session.haveSubscription("data_sources", "*"));
+    EXPECT_THROW(Configurator::init(mccs.get(), NULL), InvalidParameter);
+    EXPECT_FALSE(session.haveSubscription("data_sources", "*"));
+    // But we can initialize it again now
+    EXPECT_NO_THROW(init());
+    EXPECT_TRUE(session.haveSubscription("data_sources", "*"));
+}
+
+// Push there a configuration with a single list.
+TEST_F(DatasrcConfiguratorTest, createList) {
+    initializeINList();
+}
+
+TEST_F(DatasrcConfiguratorTest, modifyList) {
+    // First, initialize the list
+    initializeINList();
+    // And now change the configuration of the list
+    const ElementPtr
+        config(buildConfig("{\"IN\": [{\"type\": \"yyy\"}]}"));
+    session.addMessage(createCommand("config_update", config), "data_sources",
+                       "*");
+    log_ = "";
+    mccs->checkCommand();
+    // This one does not set
+    EXPECT_EQ("get IN\n", log_);
+    // But this should contain the yyy configuration
+    EXPECT_EQ("yyy", lists_[RRClass::IN()]->getConf());
+    EXPECT_EQ(1, lists_.size());
+}
+
+// Check we can have multiple lists at once
+TEST_F(DatasrcConfiguratorTest, multiple) {
+    const ElementPtr
+        config(buildConfig("{\"IN\": [{\"type\": \"yyy\"}], "
+                                 "\"CH\": [{\"type\": \"xxx\"}]}"));
+    session.addMessage(createCommand("config_update", config), "data_sources",
+                       "*");
+    mccs->checkCommand();
+    // We have set commands for both classes.
+    EXPECT_EQ("get CH\nset CH xxx\nget IN\nset IN yyy\n", log_);
+    // We should have both there
+    EXPECT_EQ("yyy", lists_[RRClass::IN()]->getConf());
+    EXPECT_EQ("xxx", lists_[RRClass::CH()]->getConf());
+    EXPECT_EQ(2, lists_.size());
+}
+
+// Check we can add another one later and the old one does not get
+// overwritten.
+//
+// It's almost like above, but we initialize first with single-list
+// config.
+TEST_F(DatasrcConfiguratorTest, updateAdd) {
+    initializeINList();
+    const ElementPtr
+        config(buildConfig("{\"IN\": [{\"type\": \"yyy\"}], "
+                           "\"CH\": [{\"type\": \"xxx\"}]}"));
+    session.addMessage(createCommand("config_update", config), "data_sources",
+                       "*");
+    log_ = "";
+    mccs->checkCommand();
+    // The CH is set, IN not
+    EXPECT_EQ("get CH\nset CH xxx\nget IN\n", log_);
+    // But this should contain the yyy configuration
+    EXPECT_EQ("xxx", lists_[RRClass::CH()]->getConf());
+    EXPECT_EQ("yyy", lists_[RRClass::IN()]->getConf());
+    EXPECT_EQ(2, lists_.size());
+}
+
+// We delete a class list in this test.
+TEST_F(DatasrcConfiguratorTest, updateDelete) {
+    initializeINList();
+    const ElementPtr
+        config(buildConfig("{}"));
+    session.addMessage(createCommand("config_update", config), "data_sources",
+                       "*");
+    log_ = "";
+    mccs->checkCommand();
+    EXPECT_EQ("get IN\nset IN \n", log_);
+    EXPECT_FALSE(lists_[RRClass::IN()]);
+    // In real auth server, the NULL one would be removed. However, we just
+    // store it, so the IN bucket is still in there. This checks there's nothing
+    // else.
+    EXPECT_EQ(1, lists_.size());
+}
+
+// Check that we can rollback an addition if something else fails
+TEST_F(DatasrcConfiguratorTest, rollbackAddition) {
+    initializeINList();
+    // The configuration is wrong. However, the CH one will get done first.
+    const ElementPtr
+        config(buildConfig("{\"IN\": [{\"type\": 13}], "
+                           "\"CH\": [{\"type\": \"xxx\"}]}"));
+    session.addMessage(createCommand("config_update", config), "data_sources",
+                       "*");
+    log_ = "";
+    // It does not throw, as it is handled in the ModuleCCSession.
+    // Throwing from the reconfigure is checked in other tests.
+    EXPECT_NO_THROW(mccs->checkCommand());
+    // Anyway, the result should not contain CH now and the original IN should
+    // be there.
+    EXPECT_EQ("xxx", lists_[RRClass::IN()]->getConf());
+    EXPECT_FALSE(lists_[RRClass::CH()]);
+}
+
+// Check that we can rollback a deletion if something else fails
+TEST_F(DatasrcConfiguratorTest, rollbackDeletion) {
+    initializeINList();
+    // Put the CH there
+    const ElementPtr
+        config1(Element::fromJSON("{\"IN\": [{\"type\": \"yyy\"}], "
+                                  "\"CH\": [{\"type\": \"xxx\"}]}"));
+    Configurator::reconfigure(config1);
+    const ElementPtr
+        config2(Element::fromJSON("{\"IN\": [{\"type\": 13}]}"));
+    // This would delete CH. However, the IN one fails.
+    // As the deletions happen after the additions/settings
+    // and there's no known way to cause an exception during the
+    // deletions, it is not a true rollback, but the result should
+    // be the same.
+    EXPECT_THROW(Configurator::reconfigure(config2), TypeError);
+    EXPECT_EQ("yyy", lists_[RRClass::IN()]->getConf());
+    EXPECT_EQ("xxx", lists_[RRClass::CH()]->getConf());
+}
+
+// Check that we can roll back configuration change if something
+// fails later on.
+TEST_F(DatasrcConfiguratorTest, rollbackConfiguration) {
+    initializeINList();
+    // Put the CH there
+    const ElementPtr
+        config1(Element::fromJSON("{\"IN\": [{\"type\": \"yyy\"}], "
+                                  "\"CH\": [{\"type\": \"xxx\"}]}"));
+    Configurator::reconfigure(config1);
+    // Now, the CH happens first. But nevertheless, it should be
+    // restored to the previoeus version.
+    const ElementPtr
+        config2(Element::fromJSON("{\"IN\": [{\"type\": 13}], "
+                                  "\"CH\": [{\"type\": \"yyy\"}]}"));
+    EXPECT_THROW(Configurator::reconfigure(config2), TypeError);
+    EXPECT_EQ("yyy", lists_[RRClass::IN()]->getConf());
+    EXPECT_EQ("xxx", lists_[RRClass::CH()]->getConf());
+}
+
+}

+ 122 - 86
src/bin/auth/tests/query_unittest.cc

@@ -32,6 +32,7 @@
 #include <dns/rdataclass.h>
 
 #include <datasrc/memory_datasrc.h>
+#include <datasrc/client_list.h>
 
 #include <auth/query.h>
 
@@ -48,6 +49,37 @@ using namespace isc::testutils;
 
 namespace {
 
+// Simple wrapper for a sincle data source client.
+// The list simply delegates all the answers to the single
+// client.
+class SingletonList : public ClientList {
+public:
+    SingletonList(DataSourceClient& client) :
+        client_(client)
+    {}
+    virtual FindResult find(const Name& zone, bool exact, bool) const {
+        DataSourceClient::FindResult result(client_.findZone(zone));
+        // We don't complicate the tests with real life keepers, but we
+        // need to put something to the parameter anyway.
+        const boost::shared_ptr<ClientList::FindResult::LifeKeeper> keeper;
+        switch (result.code) {
+            case result::SUCCESS:
+                return (FindResult(&client_, result.zone_finder, true,
+                                   keeper));
+            case result::PARTIALMATCH:
+                if (!exact) {
+                    return (FindResult(&client_, result.zone_finder, false,
+                                       keeper));
+                }
+            default:
+                return (FindResult());
+        }
+    }
+private:
+    DataSourceClient& client_;
+};
+
+
 // This is the content of the mock zone (see below).
 // It's a sequence of textual RRs that is supposed to be parsed by
 // dns::masterLoad().  Some of the RRs are also used as the expected
@@ -875,6 +907,7 @@ MockZoneFinder::find(const Name& name, const RRType& type,
 class QueryTest : public ::testing::Test {
 protected:
     QueryTest() :
+        list(memory_client),
         qname(Name("www.example.com")), qclass(RRClass::IN()),
         qtype(RRType::A()), response(Message::RENDER),
         qid(response.getQid()), query_code(Opcode::QUERY().getCode()),
@@ -898,6 +931,8 @@ protected:
     // (originally named MemoryDataSrc) and was tested with it, so we keep
     // it like this for now.
     InMemoryClient memory_client;
+    // A wrapper client list to wrap the single data source.
+    SingletonList list;
     const Name qname;
     const RRClass qclass;
     const RRType qtype;
@@ -949,20 +984,21 @@ TEST_F(QueryTest, noZone) {
     // There's no zone in the memory datasource.  So the response should have
     // REFUSED.
     InMemoryClient empty_memory_client;
-    EXPECT_NO_THROW(query.process(empty_memory_client, qname, qtype,
+    SingletonList empty_list(empty_memory_client);
+    EXPECT_NO_THROW(query.process(empty_list, qname, qtype,
                                   response));
     EXPECT_EQ(Rcode::REFUSED(), response.getRcode());
 }
 
 TEST_F(QueryTest, exactMatch) {
-    EXPECT_NO_THROW(query.process(memory_client, qname, qtype, response));
+    EXPECT_NO_THROW(query.process(list, qname, qtype, response));
     // find match rrset
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
                   www_a_txt, zone_ns_txt, ns_addrs_txt);
 }
 
 TEST_F(QueryTest, exactMatchMultipleQueries) {
-    EXPECT_NO_THROW(query.process(memory_client, qname, qtype, response));
+    EXPECT_NO_THROW(query.process(list, qname, qtype, response));
     // find match rrset
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
                   www_a_txt, zone_ns_txt, ns_addrs_txt);
@@ -971,7 +1007,7 @@ TEST_F(QueryTest, exactMatchMultipleQueries) {
     response.clear(isc::dns::Message::RENDER);
     response.setRcode(Rcode::NOERROR());
     response.setOpcode(Opcode::QUERY());
-    EXPECT_NO_THROW(query.process(memory_client, qname, qtype, response));
+    EXPECT_NO_THROW(query.process(list, qname, qtype, response));
     // find match rrset
     SCOPED_TRACE("Second query");
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
@@ -982,7 +1018,7 @@ TEST_F(QueryTest, exactMatchIgnoreSIG) {
     // Check that we do not include the RRSIG when not requested even when
     // we receive it from the data source.
     mock_finder->setIncludeRRSIGAnyway(true);
-    EXPECT_NO_THROW(query.process(memory_client, qname, qtype, response));
+    EXPECT_NO_THROW(query.process(list, qname, qtype, response));
     // find match rrset
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
                   www_a_txt, zone_ns_txt, ns_addrs_txt);
@@ -990,7 +1026,7 @@ TEST_F(QueryTest, exactMatchIgnoreSIG) {
 
 TEST_F(QueryTest, dnssecPositive) {
     // Just like exactMatch, but the signatures should be included as well
-    EXPECT_NO_THROW(query.process(memory_client, qname, qtype, response,
+    EXPECT_NO_THROW(query.process(list, qname, qtype, response,
                                   true));
     // find match rrset
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 4, 6,
@@ -1009,7 +1045,7 @@ TEST_F(QueryTest, dnssecPositive) {
 TEST_F(QueryTest, exactAddrMatch) {
     // find match rrset, omit additional data which has already been provided
     // in the answer section from the additional.
-    EXPECT_NO_THROW(query.process(memory_client,
+    EXPECT_NO_THROW(query.process(list,
                                   Name("noglue.example.com"),
                                   qtype, response));
 
@@ -1022,7 +1058,7 @@ TEST_F(QueryTest, exactAddrMatch) {
 TEST_F(QueryTest, apexNSMatch) {
     // find match rrset, omit authority data which has already been provided
     // in the answer section from the authority section.
-    EXPECT_NO_THROW(query.process(memory_client, Name("example.com"),
+    EXPECT_NO_THROW(query.process(list, Name("example.com"),
                                   RRType::NS(), response));
 
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 3, 0, 3,
@@ -1033,7 +1069,7 @@ TEST_F(QueryTest, apexNSMatch) {
 TEST_F(QueryTest, exactAnyMatch) {
     // find match rrset, omit additional data which has already been provided
     // in the answer section from the additional.
-    EXPECT_NO_THROW(query.process(memory_client, Name("noglue.example.com"),
+    EXPECT_NO_THROW(query.process(list, Name("noglue.example.com"),
                                   RRType::ANY(), response));
 
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 3, 2,
@@ -1047,7 +1083,7 @@ TEST_F(QueryTest, exactAnyMatch) {
 TEST_F(QueryTest, apexAnyMatch) {
     // find match rrset, omit additional data which has already been provided
     // in the answer section from the additional.
-    EXPECT_NO_THROW(query.process(memory_client, Name("example.com"),
+    EXPECT_NO_THROW(query.process(list, Name("example.com"),
                                   RRType::ANY(), response));
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 5, 0, 3,
                   (string(soa_txt) + string(zone_ns_txt) +
@@ -1056,7 +1092,7 @@ TEST_F(QueryTest, apexAnyMatch) {
 }
 
 TEST_F(QueryTest, mxANYMatch) {
-    EXPECT_NO_THROW(query.process(memory_client, Name("mx.example.com"),
+    EXPECT_NO_THROW(query.process(list, Name("mx.example.com"),
                                   RRType::ANY(), response));
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 4, 3, 4,
                   (string(mx_txt) + string(nsec_mx_txt)).c_str(), zone_ns_txt,
@@ -1064,14 +1100,14 @@ TEST_F(QueryTest, mxANYMatch) {
 }
 
 TEST_F(QueryTest, glueANYMatch) {
-    EXPECT_NO_THROW(query.process(memory_client, Name("delegation.example.com"),
+    EXPECT_NO_THROW(query.process(list, Name("delegation.example.com"),
                                   RRType::ANY(), response));
     responseCheck(response, Rcode::NOERROR(), 0, 0, 4, 3,
                   NULL, delegation_txt, ns_addrs_txt);
 }
 
 TEST_F(QueryTest, nodomainANY) {
-    EXPECT_NO_THROW(query.process(memory_client, Name("nxdomain.example.com"),
+    EXPECT_NO_THROW(query.process(list, Name("nxdomain.example.com"),
                                   RRType::ANY(), response));
     responseCheck(response, Rcode::NXDOMAIN(), AA_FLAG, 0, 1, 0,
                   NULL, soa_txt, NULL, mock_finder->getOrigin());
@@ -1084,13 +1120,13 @@ TEST_F(QueryTest, noApexNS) {
     // Disable apex NS record
     mock_finder->setApexNSFlag(false);
 
-    EXPECT_THROW(query.process(memory_client, Name("noglue.example.com"), qtype,
+    EXPECT_THROW(query.process(list, Name("noglue.example.com"), qtype,
                                response), Query::NoApexNS);
     // We don't look into the response, as it threw
 }
 
 TEST_F(QueryTest, delegation) {
-    EXPECT_NO_THROW(query.process(memory_client,
+    EXPECT_NO_THROW(query.process(list,
                                   Name("delegation.example.com"),
                                   qtype, response));
 
@@ -1102,7 +1138,7 @@ TEST_F(QueryTest, delegationWithDNSSEC) {
     // Similar to the previous one, but with requesting DNSSEC.
     // In this case the parent zone would behave as unsigned, so the result
     // should be just like non DNSSEC delegation.
-    query.process(memory_client, Name("www.nosec-delegation.example.com"),
+    query.process(list, Name("www.nosec-delegation.example.com"),
                   qtype, response, true);
 
     responseCheck(response, Rcode::NOERROR(), 0, 0, 1, 0,
@@ -1110,7 +1146,7 @@ TEST_F(QueryTest, delegationWithDNSSEC) {
 }
 
 TEST_F(QueryTest, secureDelegation) {
-    EXPECT_NO_THROW(query.process(memory_client,
+    EXPECT_NO_THROW(query.process(list,
                                   Name("foo.signed-delegation.example.com"),
                                   qtype, response, true));
 
@@ -1125,7 +1161,7 @@ TEST_F(QueryTest, secureDelegation) {
 }
 
 TEST_F(QueryTest, secureUnsignedDelegation) {
-    EXPECT_NO_THROW(query.process(memory_client,
+    EXPECT_NO_THROW(query.process(list,
                                   Name("foo.unsigned-delegation.example.com"),
                                   qtype, response, true));
 
@@ -1146,7 +1182,7 @@ TEST_F(QueryTest, secureUnsignedDelegationWithNSEC3) {
     mock_finder->setNSEC3Flag(true);
     mock_finder->addRecord(unsigned_delegation_nsec3_txt);
 
-    query.process(memory_client,
+    query.process(list,
                   Name("foo.unsigned-delegation.example.com"),
                   qtype, response, true);
 
@@ -1165,7 +1201,7 @@ TEST_F(QueryTest, secureUnsignedDelegationWithNSEC3OptOut) {
     // Similar to the previous case, but the delegation is an optout.
     mock_finder->setNSEC3Flag(true);
 
-    query.process(memory_client,
+    query.process(list,
                   Name("foo.unsigned-delegation.example.com"),
                   qtype, response, true);
 
@@ -1190,20 +1226,20 @@ TEST_F(QueryTest, secureUnsignedDelegationWithNSEC3OptOut) {
 TEST_F(QueryTest, badSecureDelegation) {
     // Test whether exception is raised if DS query at delegation results in
     // something different than SUCCESS or NXRRSET
-    EXPECT_THROW(query.process(memory_client,
+    EXPECT_THROW(query.process(list,
                                Name("bad-delegation.example.com"),
                                qtype, response, true), Query::BadDS);
 
     // But only if DNSSEC is requested (it shouldn't even try to look for
     // the DS otherwise)
-    EXPECT_NO_THROW(query.process(memory_client,
+    EXPECT_NO_THROW(query.process(list,
                                   Name("bad-delegation.example.com"),
                                   qtype, response));
 }
 
 
 TEST_F(QueryTest, nxdomain) {
-    EXPECT_NO_THROW(query.process(memory_client,
+    EXPECT_NO_THROW(query.process(list,
                                   Name("nxdomain.example.com"), qtype,
                                   response));
     responseCheck(response, Rcode::NXDOMAIN(), AA_FLAG, 0, 1, 0,
@@ -1214,7 +1250,7 @@ TEST_F(QueryTest, nxdomainWithNSEC) {
     // NXDOMAIN with DNSSEC proof.  We should have SOA, NSEC that proves
     // NXDOMAIN and NSEC that proves nonexistence of matching wildcard,
     // as well as their RRSIGs.
-    EXPECT_NO_THROW(query.process(memory_client,
+    EXPECT_NO_THROW(query.process(list,
                                   Name("nxdomain.example.com"), qtype,
                                   response, true));
     responseCheck(response, Rcode::NXDOMAIN(), AA_FLAG, 0, 6, 0,
@@ -1235,7 +1271,7 @@ TEST_F(QueryTest, nxdomainWithNSEC2) {
     // is derived from the next domain of the NSEC that proves NXDOMAIN, and
     // the NSEC to provide the non existence of wildcard is different from
     // the first NSEC.
-    query.process(memory_client, Name("(.no.example.com"), qtype, response,
+    query.process(list, Name("(.no.example.com"), qtype, response,
                   true);
     responseCheck(response, Rcode::NXDOMAIN(), AA_FLAG, 0, 6, 0,
                   NULL, (string(soa_txt) +
@@ -1253,7 +1289,7 @@ TEST_F(QueryTest, nxdomainWithNSEC2) {
 TEST_F(QueryTest, nxdomainWithNSECDuplicate) {
     // See comments about nz_txt.  In this case we only need one NSEC,
     // which proves both NXDOMAIN and the non existence of wildcard.
-    query.process(memory_client, Name("nx.no.example.com"), qtype, response,
+    query.process(list, Name("nx.no.example.com"), qtype, response,
                   true);
     responseCheck(response, Rcode::NXDOMAIN(), AA_FLAG, 0, 4, 0,
                   NULL, (string(soa_txt) +
@@ -1270,7 +1306,7 @@ TEST_F(QueryTest, nxdomainBadNSEC1) {
     mock_finder->setNSECResult(Name("badnsec.example.com"),
                                ZoneFinder::NXDOMAIN,
                                mock_finder->dname_rrset_);
-    EXPECT_THROW(query.process(memory_client, Name("badnsec.example.com"),
+    EXPECT_THROW(query.process(list, Name("badnsec.example.com"),
                                qtype, response, true),
                  std::bad_cast);
 }
@@ -1280,7 +1316,7 @@ TEST_F(QueryTest, nxdomainBadNSEC2) {
     mock_finder->setNSECResult(Name("emptynsec.example.com"),
                                ZoneFinder::NXDOMAIN,
                                mock_finder->empty_nsec_rrset_);
-    EXPECT_THROW(query.process(memory_client, Name("emptynsec.example.com"),
+    EXPECT_THROW(query.process(list, Name("emptynsec.example.com"),
                                qtype, response, true),
                  Query::BadNSEC);
 }
@@ -1290,7 +1326,7 @@ TEST_F(QueryTest, nxdomainBadNSEC3) {
     mock_finder->setNSECResult(Name("*.example.com"),
                                ZoneFinder::SUCCESS,
                                mock_finder->dname_rrset_);
-    EXPECT_THROW(query.process(memory_client, Name("nxdomain.example.com"),
+    EXPECT_THROW(query.process(list, Name("nxdomain.example.com"),
                                qtype, response, true),
                  Query::BadNSEC);
 }
@@ -1299,7 +1335,7 @@ TEST_F(QueryTest, nxdomainBadNSEC4) {
     // "no-wildcard proof" doesn't return RRset.
     mock_finder->setNSECResult(Name("*.example.com"),
                                ZoneFinder::NXDOMAIN, ConstRRsetPtr());
-    EXPECT_THROW(query.process(memory_client, Name("nxdomain.example.com"),
+    EXPECT_THROW(query.process(list, Name("nxdomain.example.com"),
                                qtype, response, true),
                  Query::BadNSEC);
 }
@@ -1310,7 +1346,7 @@ TEST_F(QueryTest, nxdomainBadNSEC5) {
                                ZoneFinder::NXDOMAIN,
                                mock_finder->dname_rrset_);
     // This is a bit odd, but we'll simply include the returned RRset.
-    query.process(memory_client, Name("nxdomain.example.com"), qtype,
+    query.process(list, Name("nxdomain.example.com"), qtype,
                   response, true);
     responseCheck(response, Rcode::NXDOMAIN(), AA_FLAG, 0, 6, 0,
                   NULL, (string(soa_txt) +
@@ -1330,13 +1366,13 @@ TEST_F(QueryTest, nxdomainBadNSEC6) {
     mock_finder->setNSECResult(Name("*.example.com"),
                                ZoneFinder::NXDOMAIN,
                                mock_finder->empty_nsec_rrset_);
-    EXPECT_THROW(query.process(memory_client, Name("nxdomain.example.com"),
+    EXPECT_THROW(query.process(list, Name("nxdomain.example.com"),
                                qtype, response, true),
                  Query::BadNSEC);
 }
 
 TEST_F(QueryTest, nxrrset) {
-    EXPECT_NO_THROW(query.process(memory_client, Name("www.example.com"),
+    EXPECT_NO_THROW(query.process(list, Name("www.example.com"),
                                   RRType::TXT(), response));
 
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 1, 0,
@@ -1346,7 +1382,7 @@ TEST_F(QueryTest, nxrrset) {
 TEST_F(QueryTest, nxrrsetWithNSEC) {
     // NXRRSET with DNSSEC proof.  We should have SOA, NSEC that proves the
     // NXRRSET and their RRSIGs.
-    query.process(memory_client, Name("www.example.com"), RRType::TXT(),
+    query.process(list, Name("www.example.com"), RRType::TXT(),
                   response, true);
 
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 4, 0, NULL,
@@ -1367,7 +1403,7 @@ TEST_F(QueryTest, emptyNameWithNSEC) {
     // exact match), so we only need one NSEC.
     // From the point of the Query::process(), this is actually no different
     // from the other NXRRSET case, but we check that explicitly just in case.
-    query.process(memory_client, Name("no.example.com"), RRType::A(),
+    query.process(list, Name("no.example.com"), RRType::A(),
                   response, true);
 
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 4, 0, NULL,
@@ -1383,7 +1419,7 @@ TEST_F(QueryTest, nxrrsetWithoutNSEC) {
     // NXRRSET with DNSSEC proof requested, but there's no NSEC at that node.
     // This is an unexpected event (if the zone is supposed to be properly
     // signed with NSECs), but we accept and ignore the oddity.
-    query.process(memory_client, Name("nonsec.example.com"), RRType::TXT(),
+    query.process(list, Name("nonsec.example.com"), RRType::TXT(),
                   response, true);
 
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 2, 0, NULL,
@@ -1395,7 +1431,7 @@ TEST_F(QueryTest, nxrrsetWithoutNSEC) {
 TEST_F(QueryTest, wildcardNSEC) {
     // The qname matches *.wild.example.com.  The response should contain
     // an NSEC that proves the non existence of a closer name.
-    query.process(memory_client, Name("www.wild.example.com"), RRType::A(),
+    query.process(list, Name("www.wild.example.com"), RRType::A(),
                   response, true);
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 6, 6,
                   (string(wild_txt).replace(0, 1, "www") +
@@ -1415,7 +1451,7 @@ TEST_F(QueryTest, wildcardNSEC) {
 TEST_F(QueryTest, CNAMEwildNSEC) {
     // Similar to the previous case, but the matching wildcard record is
     // CNAME.
-    query.process(memory_client, Name("www.cnamewild.example.com"),
+    query.process(list, Name("www.cnamewild.example.com"),
                   RRType::A(), response, true);
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 2, 0,
                   (string(cnamewild_txt).replace(0, 1, "www") +
@@ -1438,7 +1474,7 @@ TEST_F(QueryTest, wildcardNSEC3) {
     // of identifying the next closer name.
     mock_finder->addRecord(nsec3_atwild_txt);
 
-    query.process(memory_client, Name("x.y.wild.example.com"), RRType::A(),
+    query.process(list, Name("x.y.wild.example.com"), RRType::A(),
                   response, true);
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 6, 6,
                   (string(wild_txt).replace(0, 1, "x.y") +
@@ -1463,7 +1499,7 @@ TEST_F(QueryTest, CNAMEwildNSEC3) {
     mock_finder->setNSEC3Flag(true);
     mock_finder->addRecord(nsec3_atcnamewild_txt);
 
-    query.process(memory_client, Name("www.cnamewild.example.com"),
+    query.process(list, Name("www.cnamewild.example.com"),
                   RRType::A(), response, true);
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 2, 0,
                   (string(cnamewild_txt).replace(0, 1, "www") +
@@ -1486,7 +1522,7 @@ TEST_F(QueryTest, badWildcardNSEC3) {
                                       ConstRRsetPtr());
     mock_finder->setNSEC3Result(&nsec3);
 
-    EXPECT_THROW(query.process(memory_client, Name("www.wild.example.com"),
+    EXPECT_THROW(query.process(list, Name("www.wild.example.com"),
                                RRType::A(), response, true),
                  Query::BadNSEC3);
 }
@@ -1497,7 +1533,7 @@ TEST_F(QueryTest, badWildcardProof1) {
     mock_finder->setNSECResult(Name("www.wild.example.com"),
                                ZoneFinder::SUCCESS,
                                mock_finder->dname_rrset_);
-    EXPECT_THROW(query.process(memory_client, Name("www.wild.example.com"),
+    EXPECT_THROW(query.process(list, Name("www.wild.example.com"),
                                RRType::A(), response, true),
                  Query::BadNSEC);
 }
@@ -1506,7 +1542,7 @@ TEST_F(QueryTest, badWildcardProof2) {
     // "wildcard proof" doesn't return RRset.
     mock_finder->setNSECResult(Name("www.wild.example.com"),
                                ZoneFinder::NXDOMAIN, ConstRRsetPtr());
-    EXPECT_THROW(query.process(memory_client, Name("www.wild.example.com"),
+    EXPECT_THROW(query.process(list, Name("www.wild.example.com"),
                                RRType::A(), response, true),
                  Query::BadNSEC);
 }
@@ -1516,7 +1552,7 @@ TEST_F(QueryTest, badWildcardProof3) {
     mock_finder->setNSECResult(Name("www.wild.example.com"),
                                ZoneFinder::NXDOMAIN,
                                mock_finder->empty_nsec_rrset_);
-    EXPECT_THROW(query.process(memory_client, Name("www.wild.example.com"),
+    EXPECT_THROW(query.process(list, Name("www.wild.example.com"),
                                RRType::A(), response, true),
                  Query::BadNSEC);
 }
@@ -1525,7 +1561,7 @@ TEST_F(QueryTest, wildcardNxrrsetWithDuplicateNSEC) {
     // NXRRSET on WILDCARD with DNSSEC proof.  We should have SOA, NSEC that
     // proves the NXRRSET and their RRSIGs. In this case we only need one NSEC,
     // which proves both NXDOMAIN and the non existence RRSETs of wildcard.
-    query.process(memory_client, Name("www.wild.example.com"), RRType::TXT(),
+    query.process(list, Name("www.wild.example.com"), RRType::TXT(),
                   response, true);
 
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 4, 0, NULL,
@@ -1542,7 +1578,7 @@ TEST_F(QueryTest, wildcardNxrrsetWithNSEC) {
     // proves the NXRRSET and their RRSIGs. In this case we need two NSEC RRs,
     // one proves NXDOMAIN and the other proves non existence RRSETs of
     // wildcard.
-    query.process(memory_client, Name("www1.uwild.example.com"),
+    query.process(list, Name("www1.uwild.example.com"),
                   RRType::TXT(), response, true);
 
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 6, 0, NULL,
@@ -1565,7 +1601,7 @@ TEST_F(QueryTest, wildcardNxrrsetWithNSEC3) {
     mock_finder->addRecord(nsec3_uwild_txt);
     mock_finder->setNSEC3Flag(true);
 
-    query.process(memory_client, Name("www1.uwild.example.com"),
+    query.process(list, Name("www1.uwild.example.com"),
                   RRType::TXT(), response, true);
 
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 8, 0, NULL,
@@ -1599,7 +1635,7 @@ TEST_F(QueryTest, wildcardNxrrsetWithNSEC3Collision) {
                                       ConstRRsetPtr());
     mock_finder->setNSEC3Result(&nsec3);
 
-    EXPECT_THROW(query.process(memory_client, Name("www1.uwild.example.com"),
+    EXPECT_THROW(query.process(list, Name("www1.uwild.example.com"),
                                RRType::TXT(), response, true),
                  Query::BadNSEC3);
 }
@@ -1616,7 +1652,7 @@ TEST_F(QueryTest, wildcardNxrrsetWithNSEC3Broken) {
     mock_finder->addRecord(nsec3_wild_txt);
     mock_finder->addRecord(nsec3_uwild_txt);
 
-    EXPECT_THROW(query.process(memory_client, Name("www1.uwild.example.com"),
+    EXPECT_THROW(query.process(list, Name("www1.uwild.example.com"),
                                RRType::TXT(), response, true),
                  Query::BadNSEC3);
 }
@@ -1625,7 +1661,7 @@ TEST_F(QueryTest, wildcardEmptyWithNSEC) {
     // Empty WILDCARD with DNSSEC proof.  We should have SOA, NSEC that proves
     // the NXDOMAIN and their RRSIGs. In this case we need two NSEC RRs,
     // one proves NXDOMAIN and the other proves non existence wildcard.
-    query.process(memory_client, Name("a.t.example.com"), RRType::A(),
+    query.process(list, Name("a.t.example.com"), RRType::A(),
                   response, true);
 
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 6, 0, NULL,
@@ -1649,19 +1685,19 @@ TEST_F(QueryTest, noSOA) {
     mock_finder->setSOAFlag(false);
 
     // The NX Domain
-    EXPECT_THROW(query.process(memory_client, Name("nxdomain.example.com"),
+    EXPECT_THROW(query.process(list, Name("nxdomain.example.com"),
                                qtype, response), Query::NoSOA);
     // Of course, we don't look into the response, as it throwed
 
     // NXRRSET
-    EXPECT_THROW(query.process(memory_client, Name("nxrrset.example.com"),
+    EXPECT_THROW(query.process(list, Name("nxrrset.example.com"),
                                qtype, response), Query::NoSOA);
 }
 
 TEST_F(QueryTest, noMatchZone) {
     // there's a zone in the memory datasource but it doesn't match the qname.
     // should result in REFUSED.
-    query.process(memory_client, Name("example.org"), qtype, response);
+    query.process(list, Name("example.org"), qtype, response);
     EXPECT_EQ(Rcode::REFUSED(), response.getRcode());
 }
 
@@ -1672,7 +1708,7 @@ TEST_F(QueryTest, noMatchZone) {
  * A record, other to unknown out of zone one.
  */
 TEST_F(QueryTest, MX) {
-    query.process(memory_client, Name("mx.example.com"), RRType::MX(),
+    query.process(list, Name("mx.example.com"), RRType::MX(),
                   response);
 
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 3, 3, 4,
@@ -1686,7 +1722,7 @@ TEST_F(QueryTest, MX) {
  * This should not trigger the additional processing for the exchange.
  */
 TEST_F(QueryTest, MXAlias) {
-    query.process(memory_client, Name("cnamemx.example.com"), RRType::MX(),
+    query.process(list, Name("cnamemx.example.com"), RRType::MX(),
                   response);
 
     // there shouldn't be no additional RRs for the exchanges (we have 3
@@ -1706,7 +1742,7 @@ TEST_F(QueryTest, MXAlias) {
  * returned.
  */
 TEST_F(QueryTest, CNAME) {
-    query.process(memory_client, Name("cname.example.com"), RRType::A(),
+    query.process(list, Name("cname.example.com"), RRType::A(),
                   response);
 
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 0, 0,
@@ -1716,7 +1752,7 @@ TEST_F(QueryTest, CNAME) {
 TEST_F(QueryTest, explicitCNAME) {
     // same owner name as the CNAME test but explicitly query for CNAME RR.
     // expect the same response as we don't provide a full chain yet.
-    query.process(memory_client, Name("cname.example.com"), RRType::CNAME(),
+    query.process(list, Name("cname.example.com"), RRType::CNAME(),
                   response);
 
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
@@ -1728,7 +1764,7 @@ TEST_F(QueryTest, CNAME_NX_RRSET) {
     // note: with chaining, what should be expected is not trivial:
     // BIND 9 returns the CNAME in answer and SOA in authority, no additional.
     // NSD returns the CNAME, NS in authority, A/AAAA for NS in additional.
-    query.process(memory_client, Name("cname.example.com"), RRType::TXT(),
+    query.process(list, Name("cname.example.com"), RRType::TXT(),
                   response);
 
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 0, 0,
@@ -1737,7 +1773,7 @@ TEST_F(QueryTest, CNAME_NX_RRSET) {
 
 TEST_F(QueryTest, explicitCNAME_NX_RRSET) {
     // same owner name as the NXRRSET test but explicitly query for CNAME RR.
-    query.process(memory_client, Name("cname.example.com"), RRType::CNAME(),
+    query.process(list, Name("cname.example.com"), RRType::CNAME(),
                   response);
 
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
@@ -1751,7 +1787,7 @@ TEST_F(QueryTest, CNAME_NX_DOMAIN) {
     // RCODE being NXDOMAIN.
     // NSD returns the CNAME, NS in authority, A/AAAA for NS in additional,
     // RCODE being NOERROR.
-    query.process(memory_client, Name("cnamenxdom.example.com"), RRType::A(),
+    query.process(list, Name("cnamenxdom.example.com"), RRType::A(),
                   response);
 
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 0, 0,
@@ -1760,7 +1796,7 @@ TEST_F(QueryTest, CNAME_NX_DOMAIN) {
 
 TEST_F(QueryTest, explicitCNAME_NX_DOMAIN) {
     // same owner name as the NXDOMAIN test but explicitly query for CNAME RR.
-    query.process(memory_client, Name("cnamenxdom.example.com"),
+    query.process(list, Name("cnamenxdom.example.com"),
                   RRType::CNAME(), response);
 
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
@@ -1776,7 +1812,7 @@ TEST_F(QueryTest, CNAME_OUT) {
      * Then the same test should be done with .org included there and
      * see what it does (depends on what we want to do)
      */
-    query.process(memory_client, Name("cnameout.example.com"), RRType::A(),
+    query.process(list, Name("cnameout.example.com"), RRType::A(),
                   response);
 
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 0, 0,
@@ -1785,7 +1821,7 @@ TEST_F(QueryTest, CNAME_OUT) {
 
 TEST_F(QueryTest, explicitCNAME_OUT) {
     // same owner name as the OUT test but explicitly query for CNAME RR.
-    query.process(memory_client, Name("cnameout.example.com"), RRType::CNAME(),
+    query.process(list, Name("cnameout.example.com"), RRType::CNAME(),
                   response);
 
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
@@ -1801,7 +1837,7 @@ TEST_F(QueryTest, explicitCNAME_OUT) {
  * pointing to NXRRSET and NXDOMAIN cases (similarly as with CNAME).
  */
 TEST_F(QueryTest, DNAME) {
-    query.process(memory_client, Name("www.dname.example.com"), RRType::A(),
+    query.process(list, Name("www.dname.example.com"), RRType::A(),
                   response);
 
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 0, 0,
@@ -1817,7 +1853,7 @@ TEST_F(QueryTest, DNAME) {
  * DNAME.
  */
 TEST_F(QueryTest, DNAME_ANY) {
-    query.process(memory_client, Name("www.dname.example.com"), RRType::ANY(),
+    query.process(list, Name("www.dname.example.com"), RRType::ANY(),
                   response);
 
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 0, 0,
@@ -1826,7 +1862,7 @@ TEST_F(QueryTest, DNAME_ANY) {
 
 // Test when we ask for DNAME explicitly, it does no synthetizing.
 TEST_F(QueryTest, explicitDNAME) {
-    query.process(memory_client, Name("dname.example.com"), RRType::DNAME(),
+    query.process(list, Name("dname.example.com"), RRType::DNAME(),
                   response);
 
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
@@ -1838,7 +1874,7 @@ TEST_F(QueryTest, explicitDNAME) {
  * the CNAME, it should return the RRset.
  */
 TEST_F(QueryTest, DNAME_A) {
-    query.process(memory_client, Name("dname.example.com"), RRType::A(),
+    query.process(list, Name("dname.example.com"), RRType::A(),
                   response);
 
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
@@ -1850,7 +1886,7 @@ TEST_F(QueryTest, DNAME_A) {
  * It should not synthetize the CNAME.
  */
 TEST_F(QueryTest, DNAME_NX_RRSET) {
-    EXPECT_NO_THROW(query.process(memory_client, Name("dname.example.com"),
+    EXPECT_NO_THROW(query.process(list, Name("dname.example.com"),
                     RRType::TXT(), response));
 
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 1, 0,
@@ -1870,7 +1906,7 @@ TEST_F(QueryTest, LongDNAME) {
         "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
         "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
         "dname.example.com.");
-    EXPECT_NO_THROW(query.process(memory_client, longname, RRType::A(),
+    EXPECT_NO_THROW(query.process(list, longname, RRType::A(),
                     response));
 
     responseCheck(response, Rcode::YXDOMAIN(), AA_FLAG, 1, 0, 0,
@@ -1889,7 +1925,7 @@ TEST_F(QueryTest, MaxLenDNAME) {
         "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
         "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
         "dname.example.com.");
-    EXPECT_NO_THROW(query.process(memory_client, longname, RRType::A(),
+    EXPECT_NO_THROW(query.process(list, longname, RRType::A(),
                     response));
 
     // Check the answer is OK
@@ -2075,7 +2111,7 @@ TEST_F(QueryTest, dsAboveDelegation) {
 
     // The following will succeed only if the search goes to the parent
     // zone, not the child one we added above.
-    EXPECT_NO_THROW(query.process(memory_client,
+    EXPECT_NO_THROW(query.process(list,
                                   Name("delegation.example.com"),
                                   RRType::DS(), response, true));
 
@@ -2099,7 +2135,7 @@ TEST_F(QueryTest, dsAboveDelegationNoData) {
 
     // The following will succeed only if the search goes to the parent
     // zone, not the child one we added above.
-    EXPECT_NO_THROW(query.process(memory_client,
+    EXPECT_NO_THROW(query.process(list,
                                   Name("unsigned-delegation.example.com"),
                                   RRType::DS(), response, true));
 
@@ -2117,7 +2153,7 @@ TEST_F(QueryTest, dsAboveDelegationNoData) {
 // when it happens to be sent to the child zone, as described in RFC 4035,
 // section 3.1.4.1. The example is inspired by the B.8. example from the RFC.
 TEST_F(QueryTest, dsBelowDelegation) {
-    EXPECT_NO_THROW(query.process(memory_client, Name("example.com"),
+    EXPECT_NO_THROW(query.process(list, Name("example.com"),
                                   RRType::DS(), response, true));
 
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 4, 0, NULL,
@@ -2134,7 +2170,7 @@ TEST_F(QueryTest, dsBelowDelegation) {
 // In our implementation NSEC/NSEC3 isn't attached in this case.
 TEST_F(QueryTest, dsBelowDelegationWithDS) {
     mock_finder->addRecord(zone_ds_txt); // add the DS to the child's apex
-    EXPECT_NO_THROW(query.process(memory_client, Name("example.com"),
+    EXPECT_NO_THROW(query.process(list, Name("example.com"),
                                   RRType::DS(), response, true));
 
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 2, 0, NULL,
@@ -2147,7 +2183,7 @@ TEST_F(QueryTest, dsBelowDelegationWithDS) {
 // server.  It should just like the "noZone" test case, but DS query involves
 // special processing, so we test it explicitly.
 TEST_F(QueryTest, dsNoZone) {
-    query.process(memory_client, Name("example"), RRType::DS(), response,
+    query.process(list, Name("example"), RRType::DS(), response,
                   true);
     responseCheck(response, Rcode::REFUSED(), 0, 0, 0, 0, NULL, NULL, NULL);
 }
@@ -2155,7 +2191,7 @@ TEST_F(QueryTest, dsNoZone) {
 // DS query for a "grandchild" zone.  This should result in normal
 // delegation (unless this server also has authority of the grandchild zone).
 TEST_F(QueryTest, dsAtGrandParent) {
-    query.process(memory_client, Name("grand.delegation.example.com"),
+    query.process(list, Name("grand.delegation.example.com"),
                   RRType::DS(), response, true);
     responseCheck(response, Rcode::NOERROR(), 0, 0, 6, 6, NULL,
                   (string(delegation_txt) + string(delegation_ds_txt) +
@@ -2174,7 +2210,7 @@ TEST_F(QueryTest, dsAtGrandParentAndChild) {
     const Name childname("grand.delegation.example.com");
     memory_client.addZone(ZoneFinderPtr(
                               new AlternateZoneFinder(childname)));
-    query.process(memory_client, childname, RRType::DS(), response, true);
+    query.process(list, childname, RRType::DS(), response, true);
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 4, 0, NULL,
                   (childname.toText() + " 3600 IN SOA . . 0 0 0 0 0\n" +
                    childname.toText() + " 3600 IN RRSIG " +
@@ -2192,7 +2228,7 @@ TEST_F(QueryTest, dsAtRoot) {
     // Pretend to be a root server.
     memory_client.addZone(ZoneFinderPtr(
                               new AlternateZoneFinder(Name::ROOT_NAME())));
-    query.process(memory_client, Name::ROOT_NAME(), RRType::DS(), response,
+    query.process(list, Name::ROOT_NAME(), RRType::DS(), response,
                   true);
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 4, 0, NULL,
                   (string(". 3600 IN SOA . . 0 0 0 0 0\n") +
@@ -2209,7 +2245,7 @@ TEST_F(QueryTest, dsAtRootWithDS) {
     memory_client.addZone(ZoneFinderPtr(
                               new AlternateZoneFinder(Name::ROOT_NAME(),
                                                       true)));
-    query.process(memory_client, Name::ROOT_NAME(), RRType::DS(), response,
+    query.process(list, Name::ROOT_NAME(), RRType::DS(), response,
                   true);
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 2, 0,
                   (string(". 3600 IN DS 57855 5 1 49FD46E6C4B45C55D4AC69CBD"
@@ -2226,7 +2262,7 @@ TEST_F(QueryTest, nxrrsetWithNSEC3) {
 
     // NXRRSET with DNSSEC proof.  We should have SOA, NSEC3 that proves the
     // NXRRSET and their RRSIGs.
-    query.process(memory_client, Name("www.example.com"), RRType::TXT(),
+    query.process(list, Name("www.example.com"), RRType::TXT(),
                   response, true);
 
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 4, 0, NULL,
@@ -2249,7 +2285,7 @@ TEST_F(QueryTest, nxrrsetMissingNSEC3) {
                                       ConstRRsetPtr());
     mock_finder->setNSEC3Result(&nsec3);
 
-    EXPECT_THROW(query.process(memory_client, Name("www.example.com"),
+    EXPECT_THROW(query.process(list, Name("www.example.com"),
                                RRType::TXT(), response, true),
                  Query::BadNSEC3);
 }
@@ -2260,7 +2296,7 @@ TEST_F(QueryTest, nxrrsetWithNSEC3_ds_exact) {
 
     // This delegation has no DS, but does have a matching NSEC3 record
     // (See RFC5155 section 7.2.4)
-    query.process(memory_client, Name("unsigned-delegation.example.com."),
+    query.process(list, Name("unsigned-delegation.example.com."),
                   RRType::DS(), response, true);
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 4, 0, NULL,
                   (string(soa_txt) + string("example.com. 3600 IN RRSIG ") +
@@ -2282,7 +2318,7 @@ TEST_F(QueryTest, nxrrsetWithNSEC3_ds_no_exact) {
     // 'next closer' should have opt-out set, though that is not
     // actually checked)
     // (See RFC5155 section 7.2.4)
-    query.process(memory_client, Name("unsigned-delegation-optout.example.com."),
+    query.process(list, Name("unsigned-delegation-optout.example.com."),
                   RRType::DS(), response, true);
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 6, 0, NULL,
                   (string(soa_txt) + string("example.com. 3600 IN RRSIG ") +
@@ -2309,7 +2345,7 @@ TEST_F(QueryTest, nxdomainWithNSEC3Proof) {
     // This will be the covering NSEC3 for the possible wildcard
     mock_finder->addRecord(unsigned_delegation_nsec3_txt);
 
-    query.process(memory_client, Name("nxdomain.example.com"), qtype,
+    query.process(list, Name("nxdomain.example.com"), qtype,
                   response, true);
     responseCheck(response, Rcode::NXDOMAIN(), AA_FLAG, 0, 8, 0, NULL,
                   // SOA + its RRSIG
@@ -2344,7 +2380,7 @@ TEST_F(QueryTest, nxdomainWithBadNextNSEC3Proof) {
                                       ConstRRsetPtr());
     mock_finder->setNSEC3Result(&nsec3);
 
-    EXPECT_THROW(query.process(memory_client, Name("nxdomain.example.com"),
+    EXPECT_THROW(query.process(list, Name("nxdomain.example.com"),
                                RRType::TXT(), response, true),
                  Query::BadNSEC3);
 }
@@ -2363,7 +2399,7 @@ TEST_F(QueryTest, nxdomainWithBadWildcardNSEC3Proof) {
                                       ConstRRsetPtr());
     mock_finder->setNSEC3Result(&nsec3, &wname);
 
-    EXPECT_THROW(query.process(memory_client, Name("nxdomain.example.com"), qtype,
+    EXPECT_THROW(query.process(list, Name("nxdomain.example.com"), qtype,
                                response, true),
                  Query::BadNSEC3);
 }

+ 1 - 0
src/bin/auth/tests/testdata/Makefile.am

@@ -18,6 +18,7 @@ EXTRA_DIST += shortquestion_fromWire
 EXTRA_DIST += shortresponse_fromWire
 EXTRA_DIST += simplequery_fromWire.spec
 EXTRA_DIST += simpleresponse_fromWire.spec
+EXTRA_DIST += spec.spec
 
 EXTRA_DIST += example.com
 EXTRA_DIST += example.sqlite3

+ 6 - 0
src/bin/auth/tests/testdata/spec.spec

@@ -0,0 +1,6 @@
+{
+    "module_spec": {
+        "module_name": "test"
+    }
+}
+

+ 0 - 11
src/bin/bind10/bind10.xml

@@ -47,7 +47,6 @@
       <arg><option>-c <replaceable>config-filename</replaceable></option></arg>
       <arg><option>-i</option></arg>
       <arg><option>-m <replaceable>file</replaceable></option></arg>
-      <arg><option>-n</option></arg>
       <arg><option>-p <replaceable>data_path</replaceable></option></arg>
       <arg><option>-u <replaceable>user</replaceable></option></arg>
       <arg><option>-v</option></arg>
@@ -57,7 +56,6 @@
       <arg><option>--config-file</option> <replaceable>config-filename</replaceable></arg>
       <arg><option>--data-path</option> <replaceable>directory</replaceable></arg>
       <arg><option>--msgq-socket-file <replaceable>file</replaceable></option></arg>
-      <arg><option>--no-cache</option></arg>
       <arg><option>--no-kill</option></arg>
       <arg><option>--pid-file</option> <replaceable>filename</replaceable></arg>
       <arg><option>--pretty-name <replaceable>name</replaceable></option></arg>
@@ -169,15 +167,6 @@
       </varlistentry>
 
       <varlistentry>
-        <term><option>-n</option>, <option>--no-cache</option></term>
-        <listitem>
-	  <para>Disables the hot-spot caching used by the
-	    <citerefentry><refentrytitle>b10-auth</refentrytitle><manvolnum>8</manvolnum></citerefentry>
-	  daemon.</para>
-        </listitem>
-      </varlistentry>
-
-      <varlistentry>
         <term><option>-i</option>, <option>--no-kill</option></term>
         <listitem>
 	  <para>When this option is passed, <command>bind10</command>

+ 2 - 7
src/bin/bind10/bind10_src.py.in

@@ -168,7 +168,7 @@ class BoB:
     """Boss of BIND class."""
     
     def __init__(self, msgq_socket_file=None, data_path=None,
-                 config_filename=None, clear_config=False, nocache=False,
+                 config_filename=None, clear_config=False,
                  verbose=False, nokill=False, setuid=None, setgid=None,
                  username=None, cmdctl_port=None, wait_time=10):
         """
@@ -192,7 +192,6 @@ class BoB:
         self.ccs = None
         self.curproc = None
         self.msgq_socket_file = msgq_socket_file
-        self.nocache = nocache
         self.component_config = {}
         # Some time in future, it may happen that a single component has
         # multple processes (like a pipeline-like component). If so happens,
@@ -568,8 +567,6 @@ class BoB:
         if self.uid is not None and self.__started:
             logger.warn(BIND10_START_AS_NON_ROOT_AUTH)
         authargs = ['b10-auth']
-        if self.nocache:
-            authargs += ['-n']
         if self.verbose:
             authargs += ['-v']
 
@@ -1052,8 +1049,6 @@ def parse_args(args=sys.argv[1:], Parser=OptionParser):
     parser.add_option("-m", "--msgq-socket-file", dest="msgq_socket_file",
                       type="string", default=None,
                       help="UNIX domain socket file the b10-msgq daemon will use")
-    parser.add_option("-n", "--no-cache", action="store_true", dest="nocache",
-                      default=False, help="disable hot-spot cache in authoritative DNS server")
     parser.add_option("-i", "--no-kill", action="store_true", dest="nokill",
                       default=False, help="do not send SIGTERM and SIGKILL signals to modules during shutdown")
     parser.add_option("-u", "--user", dest="user", type="string", default=None,
@@ -1208,7 +1203,7 @@ def main():
         # Go bob!
         boss_of_bind = BoB(options.msgq_socket_file, options.data_path,
                            options.config_file, options.clear_config,
-                           options.nocache, options.verbose, options.nokill,
+                           options.verbose, options.nokill,
                            setuid, setgid, username, options.cmdctl_port,
                            options.wait_time)
         startup_result = boss_of_bind.startup()

+ 0 - 2
src/bin/bind10/tests/bind10_test.py.in

@@ -349,7 +349,6 @@ class TestBoB(unittest.TestCase):
         self.assertEqual(bob.runnable, False)
         self.assertEqual(bob.uid, None)
         self.assertEqual(bob.username, None)
-        self.assertEqual(bob.nocache, False)
         self.assertIsNone(bob._socket_cache)
 
     def test_set_creator(self):
@@ -377,7 +376,6 @@ class TestBoB(unittest.TestCase):
         self.assertEqual(bob.runnable, False)
         self.assertEqual(bob.uid, None)
         self.assertEqual(bob.username, None)
-        self.assertEqual(bob.nocache, False)
 
     def test_command_handler(self):
         class DummySession():

+ 6 - 3
src/bin/cfgmgr/plugins/Makefile.am

@@ -2,13 +2,16 @@ SUBDIRS = tests
 
 EXTRA_DIST = README logging.spec tsig_keys.spec
 
+datasrc.spec: datasrc.spec.pre
+	$(SED) -e "s|@@PKGDATADIR@@|$(pkgdatadir)|" datasrc.spec.pre >$@
+
 config_plugindir = @prefix@/share/@PACKAGE@/config_plugins
-config_plugin_DATA = logging.spec tsig_keys.spec
+config_plugin_DATA = logging.spec tsig_keys.spec datasrc.spec
 
-python_PYTHON = b10logging.py tsig_keys.py
+python_PYTHON = b10logging.py tsig_keys.py datasrc_config_plugin.py
 pythondir = $(config_plugindir)
 
-CLEANFILES = b10logging.pyc tsig_keys.pyc
+CLEANFILES = b10logging.pyc tsig_keys.pyc datasrc.spec
 CLEANDIRS = __pycache__
 
 clean-local:

+ 66 - 0
src/bin/cfgmgr/plugins/datasrc.spec.pre.in

@@ -0,0 +1,66 @@
+{
+    "module_spec": {
+        "module_name": "data_sources",
+        "module_description": "The sources of authoritative DNS data",
+        "config_data": [
+            {
+                "item_name": "classes",
+                "item_type": "named_set",
+                "item_optional": false,
+                "item_default": {
+                    "CH": [
+                        {
+                            "type": "static",
+                            "cache-enable": false,
+                            "params": "@@PKGDATADIR@@/static.zone"
+                        }
+                    ]
+                },
+                "named_set_item_spec": {
+                    "item_name": "class",
+                    "item_type": "list",
+                    "item_optional": false,
+                    "item_default": [],
+                    "list_item_spec": {
+                        "item_name": "source",
+                        "item_type": "map",
+                        "item_optional": false,
+                        "item_default": {},
+                        "map_item_spec": [
+                            {
+                                "item_name": "type",
+                                "item_type": "string",
+                                "item_optional": false,
+                                "item_default": ""
+                            },
+                            {
+                                "item_name": "params",
+                                "item_type": "any",
+                                "item_optional": false,
+                                "item_default": null
+                            },
+                            {
+                                "item_name": "cache-enable",
+                                "item_type": "boolean",
+                                "item_optional": false,
+                                "item_default": false
+                            },
+                            {
+                                "item_name": "cache-zones",
+                                "item_type": "list",
+                                "item_optional": true,
+                                "list_item_spec": {
+                                    "item_name": "zone",
+                                    "item_type": "string",
+                                    "item_optional": false,
+                                    "item_default": ""
+                                }
+                            }
+                        ]
+                    }
+                }
+            }
+        ],
+        "commands": []
+    }
+}

+ 36 - 0
src/bin/cfgmgr/plugins/datasrc_config_plugin.py

@@ -0,0 +1,36 @@
+# Copyright (C) 2012  Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+from isc.config.module_spec import module_spec_from_file
+from isc.util.file import path_search
+from bind10_config import PLUGIN_PATHS
+spec = module_spec_from_file(path_search('datasrc.spec', PLUGIN_PATHS))
+
+def check(config):
+    """
+    Check the configuration.
+    """
+    # TODO: Once we have solved ticket #2051, create the list and
+    # fill it with the configuration. We probably want to have some way
+    # to not load the data sources, just the configuration. It could
+    # be hacked together by subclassing ConfigurableClientList and
+    # having empty getDataSource method. But it looks like a hack and it
+    # won't really check the params configuration.
+    #
+    # For now, we let everything pass.
+    return None
+
+def load():
+    return (spec, check)

+ 1 - 1
src/bin/cfgmgr/plugins/tests/Makefile.am

@@ -1,5 +1,5 @@
 PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
-PYTESTS = tsig_keys_test.py logging_test.py
+PYTESTS = tsig_keys_test.py logging_test.py datasrc_test.py
 
 EXTRA_DIST = $(PYTESTS)
 

+ 36 - 0
src/bin/cfgmgr/plugins/tests/datasrc_test.py

@@ -0,0 +1,36 @@
+# Copyright (C) 2011  Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+# Make sure we can load the module, put it into path
+import sys
+import os
+sys.path.extend(os.environ["B10_TEST_PLUGIN_DIR"].split(':'))
+
+import datasrc_config_plugin
+import unittest
+
+class DatasrcTest(unittest.TestCase):
+    def test_load(self):
+        """
+        Checks the entry point returns the correct values.
+        """
+        (spec, check) = datasrc_config_plugin.load()
+        # It returns the checking function
+        self.assertEqual(check, datasrc_config_plugin.check)
+        # The plugin stores it's spec
+        self.assertEqual(spec, datasrc_config_plugin.spec)
+
+if __name__ == '__main__':
+        unittest.main()

+ 17 - 21
src/bin/ddns/tests/ddns_test.py

@@ -1136,7 +1136,7 @@ class TestDDNSSession(unittest.TestCase):
         num_rrsets = len(self.__req_message.get_section(SECTION_PREREQUISITE))
         self.assertEqual(2, num_rrsets)
 
-    def check_session_msg(self, result, expect_recv=1, notify_auth=False):
+    def check_session_msg(self, result, expect_recv=2):
         '''Check post update communication with other modules.'''
         # iff the update succeeds, b10-ddns should tell interested other
         # modules the information about the update zone.  Possible modules
@@ -1145,32 +1145,28 @@ class TestDDNSSession(unittest.TestCase):
         #                         'zone_class', <updated_zone_class>}]}
         # for auth, it should be:
         # {'command': ['loadzone', {'origin': <updated_zone_name>,
-        #                           'class', <updated_zone_class>,
-        #                           'datasrc', <datasrc type, should be
-        #                                       "memory" in practice>}]}
+        #                           'class', <updated_zone_class>}]}
         # and expect an answer by calling group_recvmsg().
         #
         # expect_recv indicates the expected number of calls to
-        # group_recvmsg(), which is normally 1, but can be 0 if send fails;
+        # group_recvmsg(), which is normally 2, but can be 0 if send fails;
         # if the message is to be sent
         if result == UPDATE_SUCCESS:
-            expected_sentmsg = 2 if notify_auth else 1
+            expected_sentmsg = 2
             self.assertEqual(expected_sentmsg,
                              len(self.__cc_session._sent_msg))
             self.assertEqual(expect_recv, self.__cc_session._recvmsg_called)
             msg_cnt = 0
-            if notify_auth:
-                sent_msg, sent_group = self.__cc_session._sent_msg[msg_cnt]
-                sent_cmd = sent_msg['command']
-                self.assertEqual('Auth', sent_group)
-                self.assertEqual('loadzone', sent_cmd[0])
-                self.assertEqual(3, len(sent_cmd[1]))
-                self.assertEqual(TEST_ZONE_NAME.to_text(),
-                                 sent_cmd[1]['origin'])
-                self.assertEqual(TEST_RRCLASS.to_text(),
-                                 sent_cmd[1]['class'])
-                self.assertEqual('memory', sent_cmd[1]['datasrc'])
-                msg_cnt += 1
+            sent_msg, sent_group = self.__cc_session._sent_msg[msg_cnt]
+            sent_cmd = sent_msg['command']
+            self.assertEqual('Auth', sent_group)
+            self.assertEqual('loadzone', sent_cmd[0])
+            self.assertEqual(2, len(sent_cmd[1]))
+            self.assertEqual(TEST_ZONE_NAME.to_text(),
+                             sent_cmd[1]['origin'])
+            self.assertEqual(TEST_RRCLASS.to_text(),
+                             sent_cmd[1]['class'])
+            msg_cnt += 1
             sent_msg, sent_group = self.__cc_session._sent_msg[msg_cnt]
             sent_cmd = sent_msg['command']
             self.assertEqual('Xfrout', sent_group)
@@ -1267,21 +1263,21 @@ class TestDDNSSession(unittest.TestCase):
             [{'type': 'memory', 'class': 'IN', 'zones': [
                     {'origin': TEST_ZONE_NAME_STR, 'filetype': 'sqlite3'}]}]
         self.check_session()
-        self.check_session_msg(UPDATE_SUCCESS, expect_recv=2, notify_auth=True)
+        self.check_session_msg(UPDATE_SUCCESS)
 
         # Let sendmsg() raise an exception.  The first exception shouldn't
         # stop sending the second message.  There's just no recv calls.
         self.__cc_session.clear_msg()
         self.__cc_session._sendmsg_exception = SessionError('send error')
         self.check_session()
-        self.check_session_msg(UPDATE_SUCCESS, expect_recv=0, notify_auth=True)
+        self.check_session_msg(UPDATE_SUCCESS, expect_recv=0)
 
         # Likewise, in the case recvmsg() raises (and there should be recv
         # calls in this case)
         self.__cc_session.clear_msg()
         self.__cc_session._recvmsg_exception = SessionError('recv error')
         self.check_session()
-        self.check_session_msg(UPDATE_SUCCESS, expect_recv=2, notify_auth=True)
+        self.check_session_msg(UPDATE_SUCCESS)
 
     def test_session_with_config(self):
         '''Check a session with more realistic config setups.

+ 7 - 143
src/bin/xfrin/tests/xfrin_test.py

@@ -2739,18 +2739,8 @@ class TestMain(unittest.TestCase):
 
 class TestXfrinProcessMockCC:
     def __init__(self):
-        self.get_called = False
-        self.get_called_correctly = False
         self.config = []
 
-    def get_remote_config_value(self, module, identifier):
-        self.get_called = True
-        if module == 'Auth' and identifier == 'datasources':
-            self.get_called_correctly = True
-            return (self.config, False)
-        else:
-            return (None, True)
-
 class TestXfrinProcessMockCCSession:
     def __init__(self):
         self.send_called = False
@@ -2869,22 +2859,17 @@ class TestXfrinProcess(unittest.TestCase):
         # Create a connection for each attempt
         self.assertEqual(len(transfers), self.__created_connections)
         self.assertEqual([published], self.__published)
-        if published == XFRIN_OK:
-            self.assertTrue(self._module_cc.get_called)
-            self.assertTrue(self._module_cc.get_called_correctly)
-        else:
-            self.assertFalse(self._module_cc.get_called)
-            self.assertFalse(self._module_cc.get_called_correctly)
 
     def test_ixfr_ok(self):
         """
         Everything OK the first time, over IXFR.
         """
         self.__do_test([XFRIN_OK], [RRType.IXFR()], RRType.IXFR())
-        self.assertFalse(self._send_cc_session.send_called)
-        self.assertFalse(self._send_cc_session.send_called_correctly)
-        self.assertFalse(self._send_cc_session.recv_called)
-        self.assertFalse(self._send_cc_session.recv_called_correctly)
+        # Check there was loadzone command
+        self.assertTrue(self._send_cc_session.send_called)
+        self.assertTrue(self._send_cc_session.send_called_correctly)
+        self.assertTrue(self._send_cc_session.recv_called)
+        self.assertTrue(self._send_cc_session.recv_called_correctly)
 
     def test_axfr_ok(self):
         """
@@ -2916,137 +2901,16 @@ class TestXfrinProcess(unittest.TestCase):
         self.__do_test([XFRIN_FAIL, XFRIN_FAIL],
                        [RRType.IXFR(), RRType.AXFR()], RRType.IXFR())
 
-    def test_inmem_ok(self):
+    def test_send_loadzone(self):
         """
-        Inmem configuration where all the configuration is just right
-        for loadzone to be sent to b10-auth (origin is the name received
-        by xfrin, filetype is sqlite3, type is memory and class is the
-        one received by xfrin).
+        Check the loadzone command is sent after successful transfer.
         """
-        self._module_cc.config = [{'zones': [{'origin': 'example.org', 'filetype': 'sqlite3',
-                                              'file': 'data/inmem-xfrin.sqlite3'}],
-                                   'type': 'memory', 'class': 'IN'}]
         self.__do_test([XFRIN_OK], [RRType.IXFR()], RRType.IXFR())
         self.assertTrue(self._send_cc_session.send_called)
         self.assertTrue(self._send_cc_session.send_called_correctly)
         self.assertTrue(self._send_cc_session.recv_called)
         self.assertTrue(self._send_cc_session.recv_called_correctly)
 
-    def test_inmem_datasource_type_not_memory(self):
-        """
-        Inmem configuration where the datasource type is not memory. In
-        this case, loadzone should not be sent to b10-auth.
-        """
-        self._module_cc.config = [{'zones': [{'origin': 'example.org', 'filetype': 'sqlite3',
-                                              'file': 'data/inmem-xfrin.sqlite3'}],
-                                   'type': 'punched-card', 'class': 'IN'}]
-        self.__do_test([XFRIN_OK], [RRType.IXFR()], RRType.IXFR())
-        self.assertFalse(self._send_cc_session.send_called)
-        self.assertFalse(self._send_cc_session.send_called_correctly)
-        self.assertFalse(self._send_cc_session.recv_called)
-        self.assertFalse(self._send_cc_session.recv_called_correctly)
-
-    def test_inmem_datasource_type_is_missing(self):
-        """
-        Inmem configuration where the datasource type is missing. In
-        this case, loadzone should not be sent to b10-auth.
-        """
-        self._module_cc.config = [{'zones': [{'origin': 'example.org', 'filetype': 'sqlite3',
-                                              'file': 'data/inmem-xfrin.sqlite3'}],
-                                   'class': 'IN'}]
-        self.__do_test([XFRIN_OK], [RRType.IXFR()], RRType.IXFR())
-        self.assertFalse(self._send_cc_session.send_called)
-        self.assertFalse(self._send_cc_session.send_called_correctly)
-        self.assertFalse(self._send_cc_session.recv_called)
-        self.assertFalse(self._send_cc_session.recv_called_correctly)
-
-    def test_inmem_backend_type_not_sqlite3(self):
-        """
-        Inmem configuration where the datasource backing file is not of
-        type sqlite3. In this case, loadzone should not be sent to
-        b10-auth.
-        """
-        self._module_cc.config = [{'zones': [{'origin': 'example.org', 'filetype': 'postgresql',
-                                              'file': 'data/inmem-xfrin.db'}],
-                                   'type': 'memory', 'class': 'IN'}]
-        self.__do_test([XFRIN_OK], [RRType.IXFR()], RRType.IXFR())
-        self.assertFalse(self._send_cc_session.send_called)
-        self.assertFalse(self._send_cc_session.send_called_correctly)
-        self.assertFalse(self._send_cc_session.recv_called)
-        self.assertFalse(self._send_cc_session.recv_called_correctly)
-
-    def test_inmem_backend_type_is_missing(self):
-        """
-        Inmem configuration where the datasource backing file type is
-        not set. In this case, loadzone should not be sent to b10-auth.
-        """
-        self._module_cc.config = [{'zones': [{'origin': 'example.org',
-                                              'file': 'data/inmem-xfrin'}],
-                                   'type': 'memory', 'class': 'IN'}]
-        self.__do_test([XFRIN_OK], [RRType.IXFR()], RRType.IXFR())
-        self.assertFalse(self._send_cc_session.send_called)
-        self.assertFalse(self._send_cc_session.send_called_correctly)
-        self.assertFalse(self._send_cc_session.recv_called)
-        self.assertFalse(self._send_cc_session.recv_called_correctly)
-
-    def test_inmem_class_is_different(self):
-        """
-        Inmem configuration where the datasource class does not match
-        the received class. In this case, loadzone should not be sent to
-        b10-auth.
-        """
-        self._module_cc.config = [{'zones': [{'origin': 'example.org', 'filetype': 'sqlite3',
-                                              'file': 'data/inmem-xfrin.sqlite3'}],
-                                   'type': 'memory', 'class': 'XX'}]
-        self.__do_test([XFRIN_OK], [RRType.IXFR()], RRType.IXFR())
-        self.assertFalse(self._send_cc_session.send_called)
-        self.assertFalse(self._send_cc_session.send_called_correctly)
-        self.assertFalse(self._send_cc_session.recv_called)
-        self.assertFalse(self._send_cc_session.recv_called_correctly)
-
-    def test_inmem_class_is_missing(self):
-        """
-        Inmem configuration where the datasource class is missing. In
-        this case, we assume the IN class and loadzone may be sent to
-        b10-auth if everything else matches.
-        """
-        self._module_cc.config = [{'zones': [{'origin': 'example.org', 'filetype': 'sqlite3',
-                                              'file': 'data/inmem-xfrin.sqlite3'}],
-                                   'type': 'memory'}]
-        self.__do_test([XFRIN_OK], [RRType.IXFR()], RRType.IXFR())
-        self.assertTrue(self._send_cc_session.send_called)
-        self.assertTrue(self._send_cc_session.send_called_correctly)
-        self.assertTrue(self._send_cc_session.recv_called)
-        self.assertTrue(self._send_cc_session.recv_called_correctly)
-
-    def test_inmem_name_doesnt_match(self):
-        """
-        Inmem configuration where the origin does not match the received
-        name. In this case, loadzone should not be sent to b10-auth.
-        """
-        self._module_cc.config = [{'zones': [{'origin': 'isc.org', 'filetype': 'sqlite3',
-                                              'file': 'data/inmem-xfrin.sqlite3'}],
-                                   'type': 'memory', 'class': 'IN'}]
-        self.__do_test([XFRIN_OK], [RRType.IXFR()], RRType.IXFR())
-        self.assertFalse(self._send_cc_session.send_called)
-        self.assertFalse(self._send_cc_session.send_called_correctly)
-        self.assertFalse(self._send_cc_session.recv_called)
-        self.assertFalse(self._send_cc_session.recv_called_correctly)
-
-    def test_inmem_name_is_missing(self):
-        """
-        Inmem configuration where the origin is missing. In this case,
-        loadzone should not be sent to b10-auth.
-        """
-        self._module_cc.config = [{'zones': [{'filetype': 'sqlite3',
-                                              'file': 'data/inmem-xfrin.sqlite3'}],
-                                   'type': 'memory', 'class': 'IN'}]
-        self.__do_test([XFRIN_OK], [RRType.IXFR()], RRType.IXFR())
-        self.assertFalse(self._send_cc_session.send_called)
-        self.assertFalse(self._send_cc_session.send_called_correctly)
-        self.assertFalse(self._send_cc_session.recv_called)
-        self.assertFalse(self._send_cc_session.recv_called_correctly)
-
 class TestFormatting(unittest.TestCase):
     # If the formatting functions are moved to a more general library
     # (ticket #1379), these tests should be moved with them.

+ 1 - 1
src/bin/xfrin/xfrin.py.in

@@ -1256,7 +1256,7 @@ def _do_auth_loadzone(server, zone_name, zone_class):
     if msg is not None:
         param = msg['command'][1]
         logger.debug(DBG_XFRIN_TRACE, XFRIN_AUTH_LOADZONE, param["origin"],
-                     param["class"], param["datasrc"])
+                     param["class"])
         seq = server._send_cc_session.group_sendmsg(msg, AUTH_MODULE_NAME)
         answer, env = server._send_cc_session.group_recvmsg(False, seq)
 

+ 3 - 4
src/bin/xfrin/xfrin_messages.mes

@@ -15,10 +15,9 @@
 # No namespace declaration - these constants go in the global namespace
 # of the xfrin messages python module.
 
-% XFRIN_AUTH_LOADZONE sending Auth loadzone for origin=%1, class=%2, datasrc=%3
-There was a successful zone transfer, and the zone is served by b10-auth
-in the in-memory data source using sqlite3 as a backend. We send the
-"loadzone" command for the zone to b10-auth.
+% XFRIN_AUTH_LOADZONE sending Auth loadzone for origin=%1, class=%2
+There was a successful zone transfer.  We send the "loadzone" command for the
+zone to b10-auth.
 
 % XFRIN_AXFR_INCONSISTENT_SOA AXFR SOAs are inconsistent for %1: %2 expected, %3 received
 The serial fields of the first and last SOAs of AXFR (including AXFR-style

+ 1 - 1
src/lib/config/module_spec.cc

@@ -37,7 +37,7 @@ check_leaf_item(ConstElementPtr spec, const std::string& name,
                 Element::types type, bool mandatory)
 {
     if (spec->contains(name)) {
-        if (spec->get(name)->getType() == type) {
+        if (type == Element::any || spec->get(name)->getType() == type) {
             return;
         } else {
             isc_throw(ModuleSpecError,

+ 1 - 0
src/lib/config/tests/module_spec_unittests.cc

@@ -110,6 +110,7 @@ TEST(ModuleSpec, SpecfileItems) {
                    "item_default not of type map");
     moduleSpecError("spec15.spec",
                    "badname is not a valid type name");
+    EXPECT_NO_THROW(moduleSpecFromFile(specfile("spec40.spec")));
 }
 
 TEST(ModuleSpec, SpecfileConfigData) {

+ 1 - 0
src/lib/config/tests/testdata/Makefile.am

@@ -66,3 +66,4 @@ EXTRA_DIST += spec36.spec
 EXTRA_DIST += spec37.spec
 EXTRA_DIST += spec38.spec
 EXTRA_DIST += spec39.spec
+EXTRA_DIST += spec40.spec

+ 13 - 0
src/lib/config/tests/testdata/spec40.spec

@@ -0,0 +1,13 @@
+{
+  "module_spec": {
+    "module_name": "Spec40",
+    "config_data": [
+      { "item_name": "item1",
+        "item_type": "any",
+        "item_optional": false,
+        "item_default": "asdf"
+      }
+    ]
+  }
+}
+

+ 97 - 35
src/lib/datasrc/client_list.cc

@@ -49,14 +49,19 @@ ConfigurableClientList::DataSourceInfo::DataSourceInfo(bool has_cache) :
 }
 
 void
-ConfigurableClientList::configure(const Element& config, bool allow_cache) {
+ConfigurableClientList::configure(const ConstElementPtr& config,
+                                  bool allow_cache)
+{
+    if (!config) {
+        isc_throw(isc::BadValue, "NULL configuration passed");
+    }
     // TODO: Implement recycling from the old configuration.
     size_t i(0); // Outside of the try to be able to access it in the catch
     try {
         vector<DataSourceInfo> new_data_sources;
-        for (; i < config.size(); ++i) {
+        for (; i < config->size(); ++i) {
             // Extract the parameters
-            const ConstElementPtr dconf(config.get(i));
+            const ConstElementPtr dconf(config->get(i));
             const ConstElementPtr typeElem(dconf->get("type"));
             if (typeElem == ConstElementPtr()) {
                 isc_throw(ConfigurationError, "Missing the type option in "
@@ -161,6 +166,8 @@ ConfigurableClientList::configure(const Element& config, bool allow_cache) {
         // ready. So just put it there and let the old one die when we exit
         // the scope.
         data_sources_.swap(new_data_sources);
+        configuration_ = config;
+        allow_cache_ = allow_cache;
     } catch (const TypeError& te) {
         isc_throw(ConfigurationError, "Malformed configuration at data source "
                   "no. " << i << ": " << te.what());
@@ -188,44 +195,57 @@ private:
 };
 
 boost::shared_ptr<ClientList::FindResult::LifeKeeper>
-genKeeper(const ConfigurableClientList::DataSourceInfo& info) {
-    if (info.cache_) {
+genKeeper(const ConfigurableClientList::DataSourceInfo* info) {
+    if (info == NULL) {
+        return (boost::shared_ptr<ClientList::FindResult::LifeKeeper>());
+    }
+    if (info->cache_) {
         return (boost::shared_ptr<ClientList::FindResult::LifeKeeper>(
-            new CacheKeeper(info.cache_)));
+            new CacheKeeper(info->cache_)));
     } else {
         return (boost::shared_ptr<ClientList::FindResult::LifeKeeper>(
-            new ContainerKeeper(info.container_)));
+            new ContainerKeeper(info->container_)));
     }
 }
 
 }
 
+// We have this class as a temporary storage, as the FindResult can't be
+// assigned.
+struct ConfigurableClientList::MutableResult {
+    MutableResult() :
+        datasrc_client(NULL),
+        matched_labels(0),
+        matched(false),
+        exact(false),
+        info(NULL)
+    {}
+    DataSourceClient* datasrc_client;
+    ZoneFinderPtr finder;
+    uint8_t matched_labels;
+    bool matched;
+    bool exact;
+    const DataSourceInfo* info;
+    operator FindResult() const {
+        // Conversion to the right result.
+        return (FindResult(datasrc_client, finder, exact, genKeeper(info)));
+    }
+};
+
 ClientList::FindResult
 ConfigurableClientList::find(const dns::Name& name, bool want_exact_match,
-                            bool) const
+                             bool want_finder) const
 {
-    // Nothing found yet.
-    //
-    // We have this class as a temporary storage, as the FindResult can't be
-    // assigned.
-    struct MutableResult {
-        MutableResult() :
-            datasrc_client(NULL),
-            matched_labels(0),
-            matched(false)
-        {}
-        DataSourceClient* datasrc_client;
-        ZoneFinderPtr finder;
-        uint8_t matched_labels;
-        bool matched;
-        boost::shared_ptr<FindResult::LifeKeeper> keeper;
-        operator FindResult() const {
-            // Conversion to the right result. If we return this, there was
-            // a partial match at best.
-            return (FindResult(datasrc_client, finder, false, keeper));
-        }
-    } candidate;
+    MutableResult result;
+    findInternal(result, name, want_exact_match, want_finder);
+    return (result);
+}
 
+void
+ConfigurableClientList::findInternal(MutableResult& candidate,
+                                     const dns::Name& name,
+                                     bool want_exact_match, bool) const
+{
     BOOST_FOREACH(const DataSourceInfo& info, data_sources_) {
         DataSourceClient* client(info.cache_ ? info.cache_.get() :
                                  info.data_src_client_);
@@ -239,8 +259,12 @@ ConfigurableClientList::find(const dns::Name& name, bool want_exact_match,
 
                 // TODO: In case we have only the datasource and not the finder
                 // and the need_updater parameter is true, get the zone there.
-                return (FindResult(client, result.zone_finder,
-                                   true, genKeeper(info)));
+                candidate.datasrc_client = client;
+                candidate.finder = result.zone_finder;
+                candidate.matched = true;
+                candidate.exact = true;
+                candidate.info = &info;
+                return;
             case result::PARTIALMATCH:
                 if (!want_exact_match) {
                     // In case we have a partial match, check if it is better
@@ -264,7 +288,7 @@ ConfigurableClientList::find(const dns::Name& name, bool want_exact_match,
                         candidate.finder = result.zone_finder;
                         candidate.matched_labels = labels;
                         candidate.matched = true;
-                        candidate.keeper = genKeeper(info);
+                        candidate.info = &info;
                     }
                 }
                 break;
@@ -276,10 +300,48 @@ ConfigurableClientList::find(const dns::Name& name, bool want_exact_match,
 
     // TODO: In case we have only the datasource and not the finder
     // and the need_updater parameter is true, get the zone there.
+}
 
-    // Return the partial match we have. In case we didn't want a partial
-    // match, this surely contains the original empty result.
-    return (candidate);
+ConfigurableClientList::ReloadResult
+ConfigurableClientList::reload(const Name& name) {
+    if (!allow_cache_) {
+        return (CACHE_DISABLED);
+    }
+    // Try to find the correct zone.
+    MutableResult result;
+    findInternal(result, name, true, true);
+    if (!result.finder) {
+        return (ZONE_NOT_FOUND);
+    }
+    // Try to convert the finder to in-memory one. If it is the cache,
+    // it should work.
+    shared_ptr<InMemoryZoneFinder>
+        finder(dynamic_pointer_cast<InMemoryZoneFinder>(result.finder));
+    const DataSourceInfo* info(result.info);
+    // It is of a different type or there's no cache.
+    if (!info->cache_ || !finder) {
+        return (ZONE_NOT_CACHED);
+    }
+    DataSourceClient* client(info->data_src_client_);
+    if (client) {
+        // Now do the final reload. If it does not exist in client,
+        // DataSourceError is thrown, which is exactly the result what we
+        // want, so no need to handle it.
+        ZoneIteratorPtr iterator(client->getIterator(name));
+        if (!iterator) {
+            isc_throw(isc::Unexpected, "Null iterator from " << name);
+        }
+        finder->load(*iterator);
+    } else {
+        // The MasterFiles special case
+        const string filename(finder->getFileName());
+        if (filename.empty()) {
+            isc_throw(isc::Unexpected, "Confused about missing both filename "
+                      "and data source");
+        }
+        finder->load(filename);
+    }
+    return (ZONE_RELOADED);
 }
 
 // NOTE: This function is not tested, it would be complicated. However, the

+ 54 - 2
src/lib/datasrc/client_list.h

@@ -210,7 +210,9 @@ public:
     ///
     /// \param rrclass For which class the list should work.
     ConfigurableClientList(const isc::dns::RRClass &rrclass) :
-        rrclass_(rrclass)
+        rrclass_(rrclass),
+        configuration_(new isc::data::ListElement),
+        allow_cache_(false)
     {}
     /// \brief Exception thrown when there's an error in configuration.
     class ConfigurationError : public Exception {
@@ -239,12 +241,44 @@ public:
     ///     client.
     /// \throw ConfigurationError if the configuration is invalid in some
     ///     sense.
+    /// \throw BadValue if configuration is NULL
     /// \throw Unexpected if something misbehaves (like the data source
     ///     returning NULL iterator).
     /// \throw NotImplemented if the auto-detection of list of zones is
     ///     needed.
     /// \throw Whatever is propagated from within the data source.
-    void configure(const data::Element& configuration, bool allow_cache);
+    void configure(const isc::data::ConstElementPtr& configuration,
+                   bool allow_cache);
+
+    /// \brief Returns the currently active configuration.
+    ///
+    /// In case configure was not called yet, it returns an empty
+    /// list, which corresponds to the default content.
+    const isc::data::ConstElementPtr& getConfiguration() const {
+        return (configuration_);
+    }
+
+    /// \brief Result of the reload() method.
+    enum ReloadResult {
+        CACHE_DISABLED,     ///< The cache is not enabled in this list.
+        ZONE_NOT_CACHED,    ///< Zone is served directly, not from cache.
+        ZONE_NOT_FOUND,     ///< Zone does not exist or not cached.
+        ZONE_RELOADED       ///< The zone was successfully reloaded.
+    };
+
+    /// \brief Reloads a cached zone.
+    ///
+    /// This method finds a zone which is loaded into a cache and reloads it.
+    /// This may be used to renew the cache when the underlying data source
+    /// changes.
+    ///
+    /// \param zone The origin of the zone to reload.
+    /// \return A status if the command worked.
+    /// \throw DataSourceError or anything else that the data source
+    ///      containing the zone might throw is propagated.
+    /// \throw DataSourceError if something unexpected happens, like when
+    ///      the original data source no longer contains the cached zone.
+    ReloadResult reload(const dns::Name& zone);
 
     /// \brief Implementation of the ClientList::find.
     virtual FindResult find(const dns::Name& zone,
@@ -308,7 +342,25 @@ public:
     /// hide it).
     const DataSources& getDataSources() const { return (data_sources_); }
 private:
+    struct MutableResult;
+    /// \brief Internal implementation of find.
+    ///
+    /// The class itself needs to do some internal searches in other methods,
+    /// so the implementation is shared.
+    ///
+    /// The result is returned as parameter because MutableResult is not
+    /// defined in the header file.
+    ///
+    /// If there's no match, the result is not modified. Therefore, this
+    /// expects to get a fresh result object each time it is called, not
+    /// to reuse it.
+    void findInternal(MutableResult& result, const dns::Name& name,
+                      bool want_exact_match, bool want_finder) const;
     const isc::dns::RRClass rrclass_;
+    /// \brief Currently active configuration.
+    isc::data::ConstElementPtr configuration_;
+    /// \brief The last set value of allow_cache.
+    bool allow_cache_;
 };
 
 } // namespace datasrc

+ 2 - 0
src/lib/datasrc/static.zone.pre

@@ -6,7 +6,9 @@
 ;;
 ;; in the bindctl.
 
+;; This is here mostly for technical reasons.
 BIND.           0   CH  SOA bind. authors.bind. 0 28800 7200 604800 86400
+BIND.           0   CH  NS  BIND.
 
 VERSION.BIND.   0   CH  TXT "@@VERSION_STRING@@"
 ;; HOSTNAME.BIND    0   CH  TXT "localhost"

+ 197 - 22
src/lib/datasrc/tests/client_list_unittest.cc

@@ -25,6 +25,7 @@
 #include <gtest/gtest.h>
 
 #include <set>
+#include <fstream>
 
 using namespace isc::datasrc;
 using namespace isc::data;
@@ -225,6 +226,12 @@ public:
             "{"
             "   \"type\": \"test_type\","
             "   \"params\": {}"
+            "}]")),
+        config_elem_zones_(Element::fromJSON("["
+            "{"
+            "   \"type\": \"test_type\","
+            "   \"params\": [\"example.org\", \"example.com\", "
+            "                \"noiter.org\", \"null.org\"]"
             "}]"))
     {
         for (size_t i(0); i < ds_count; ++ i) {
@@ -235,6 +242,25 @@ public:
                 DataSourceClientContainerPtr(), false));
         }
     }
+    void prepareCache(size_t index, const Name& zone, bool prefill = false) {
+        const shared_ptr<InMemoryClient> cache(new InMemoryClient());
+        const shared_ptr<InMemoryZoneFinder>
+            finder(new InMemoryZoneFinder(RRClass::IN(), zone));
+        if (prefill) {
+            RRsetPtr soa(new RRset(zone, RRClass::IN(), RRType::SOA(),
+                                   RRTTL(3600)));
+            // The RData here is bogus, but it is not used to anything. There
+            // just needs to be some.
+            soa->addRdata(rdata::generic::SOA(Name::ROOT_NAME(),
+                                              Name::ROOT_NAME(),
+                                              0, 0, 0, 0, 0));
+            finder->add(soa);
+        }
+        // If we don't do prefill, we leave the zone empty. This way,
+        // we can check when it was reloaded.
+        cache->addZone(finder);
+        list_->getDataSources()[index].cache_ = cache;
+    }
     // Check the positive result is as we expect it.
     void positiveResult(const ClientList::FindResult& result,
                         const shared_ptr<MockDataSourceClient>& dsrc,
@@ -309,7 +335,7 @@ public:
     const ClientList::FindResult negativeResult_;
     vector<shared_ptr<MockDataSourceClient> > ds_;
     vector<ConfigurableClientList::DataSourceInfo> ds_info_;
-    const ConstElementPtr config_elem_;
+    const ConstElementPtr config_elem_, config_elem_zones_;
 };
 
 // Test the test itself
@@ -427,8 +453,10 @@ TEST_F(ListTest, multiBestMatch) {
 // Check the configuration is empty when the list is empty
 TEST_F(ListTest, configureEmpty) {
     const ConstElementPtr elem(new ListElement);
-    list_->configure(*elem, true);
+    list_->configure(elem, true);
     EXPECT_TRUE(list_->getDataSources().empty());
+    // Check the exact configuration is preserved
+    EXPECT_EQ(elem, list_->getConfiguration());
 }
 
 // Check we can get multiple data sources and they are in the right order.
@@ -445,10 +473,12 @@ TEST_F(ListTest, configureMulti) {
         "   \"params\": {}"
         "}]"
     ));
-    list_->configure(*elem, true);
+    list_->configure(elem, true);
     EXPECT_EQ(2, list_->getDataSources().size());
     checkDS(0, "type1", "{}", false);
     checkDS(1, "type2", "{}", false);
+    // Check the exact configuration is preserved
+    EXPECT_EQ(elem, list_->getConfiguration());
 }
 
 // Check we can pass whatever we want to the params
@@ -471,7 +501,7 @@ TEST_F(ListTest, configureParams) {
             "   \"cache\": \"off\","
             "   \"params\": ") + *param +
             "}]"));
-        list_->configure(*elem, true);
+        list_->configure(elem, true);
         EXPECT_EQ(1, list_->getDataSources().size());
         checkDS(0, "t", *param, false);
     }
@@ -561,12 +591,12 @@ TEST_F(ListTest, wrongConfig) {
         NULL
     };
     // Put something inside to see it survives the exception
-    list_->configure(*config_elem_, true);
+    list_->configure(config_elem_, true);
     checkDS(0, "test_type", "{}", false);
     for (const char** config(configs); *config; ++config) {
         SCOPED_TRACE(*config);
         ConstElementPtr elem(Element::fromJSON(*config));
-        EXPECT_THROW(list_->configure(*elem, true),
+        EXPECT_THROW(list_->configure(elem, true),
                      ConfigurableClientList::ConfigurationError);
         // Still untouched
         checkDS(0, "test_type", "{}", false);
@@ -580,7 +610,7 @@ TEST_F(ListTest, defaults) {
         "{"
         "   \"type\": \"type1\""
         "}]"));
-    list_->configure(*elem, true);
+    list_->configure(elem, true);
     EXPECT_EQ(1, list_->getDataSources().size());
     checkDS(0, "type1", "null", false);
 }
@@ -588,11 +618,11 @@ TEST_F(ListTest, defaults) {
 // Check we can call the configure multiple times, to change the configuration
 TEST_F(ListTest, reconfigure) {
     const ConstElementPtr empty(new ListElement);
-    list_->configure(*config_elem_, true);
+    list_->configure(config_elem_, true);
     checkDS(0, "test_type", "{}", false);
-    list_->configure(*empty, true);
+    list_->configure(empty, true);
     EXPECT_TRUE(list_->getDataSources().empty());
-    list_->configure(*config_elem_, true);
+    list_->configure(config_elem_, true);
     checkDS(0, "test_type", "{}", false);
 }
 
@@ -602,9 +632,9 @@ TEST_F(ListTest, dataSrcError) {
         "{"
         "   \"type\": \"error\""
         "}]"));
-    list_->configure(*config_elem_, true);
+    list_->configure(config_elem_, true);
     checkDS(0, "test_type", "{}", false);
-    EXPECT_THROW(list_->configure(*elem, true), DataSourceError);
+    EXPECT_THROW(list_->configure(elem, true), DataSourceError);
     checkDS(0, "test_type", "{}", false);
 }
 
@@ -624,7 +654,7 @@ TEST_F(ListTest, configureCacheEmpty) {
         "   \"params\": {}"
         "}]"
     ));
-    list_->configure(*elem, true);
+    list_->configure(elem, true);
     EXPECT_EQ(2, list_->getDataSources().size());
     checkDS(0, "type1", "{}", true);
     checkDS(1, "type2", "{}", false);
@@ -646,7 +676,7 @@ TEST_F(ListTest, configureCacheDisabled) {
         "   \"params\": {}"
         "}]"
     ));
-    list_->configure(*elem, false);
+    list_->configure(elem, false);
     EXPECT_EQ(2, list_->getDataSources().size());
     checkDS(0, "type1", "{}", false);
     checkDS(1, "type2", "{}", false);
@@ -661,7 +691,7 @@ TEST_F(ListTest, cacheZones) {
         "   \"cache-zones\": [\"example.org\", \"example.com\"],"
         "   \"params\": [\"example.org\", \"example.com\", \"exmaple.cz\"]"
         "}]"));
-    list_->configure(*elem, true);
+    list_->configure(elem, true);
     checkDS(0, "type1", "[\"example.org\", \"example.com\", \"exmaple.cz\"]",
             true);
 
@@ -688,7 +718,7 @@ TEST_F(ListTest, cacheZones) {
 // Check the caching handles misbehaviour from the data source and
 // misconfiguration gracefully
 TEST_F(ListTest, badCache) {
-    list_->configure(*config_elem_, true);
+    list_->configure(config_elem_, true);
     checkDS(0, "test_type", "{}", false);
     // First, the zone is not in the data source
     const ConstElementPtr elem1(Element::fromJSON("["
@@ -698,7 +728,7 @@ TEST_F(ListTest, badCache) {
         "   \"cache-zones\": [\"example.org\"],"
         "   \"params\": []"
         "}]"));
-    EXPECT_THROW(list_->configure(*elem1, true),
+    EXPECT_THROW(list_->configure(elem1, true),
                  ConfigurableClientList::ConfigurationError);
     checkDS(0, "test_type", "{}", false);
     // Now, the zone doesn't give an iterator
@@ -709,7 +739,7 @@ TEST_F(ListTest, badCache) {
         "   \"cache-zones\": [\"noiter.org\"],"
         "   \"params\": [\"noiter.org\"]"
         "}]"));
-    EXPECT_THROW(list_->configure(*elem2, true), isc::NotImplemented);
+    EXPECT_THROW(list_->configure(elem2, true), isc::NotImplemented);
     checkDS(0, "test_type", "{}", false);
     // Now, the zone returns NULL iterator
     const ConstElementPtr elem3(Element::fromJSON("["
@@ -719,7 +749,7 @@ TEST_F(ListTest, badCache) {
         "   \"cache-zones\": [\"null.org\"],"
         "   \"params\": [\"null.org\"]"
         "}]"));
-    EXPECT_THROW(list_->configure(*elem3, true), isc::Unexpected);
+    EXPECT_THROW(list_->configure(elem3, true), isc::Unexpected);
     checkDS(0, "test_type", "{}", false);
     // The autodetection of zones is not enabled
     const ConstElementPtr elem4(Element::fromJSON("["
@@ -728,7 +758,7 @@ TEST_F(ListTest, badCache) {
         "   \"cache-enable\": true,"
         "   \"params\": [\"example.org\"]"
         "}]"));
-    EXPECT_THROW(list_->configure(*elem4, true), isc::NotImplemented);
+    EXPECT_THROW(list_->configure(elem4, true), isc::NotImplemented);
     checkDS(0, "test_type", "{}", false);
 }
 
@@ -741,7 +771,7 @@ TEST_F(ListTest, masterFiles) {
         "       \".\": \"" TEST_DATA_DIR "/root.zone\""
         "   }"
         "}]"));
-    list_->configure(*elem, true);
+    list_->configure(elem, true);
 
     // It has only the cache
     EXPECT_EQ(static_cast<const DataSourceClient*>(NULL),
@@ -752,8 +782,153 @@ TEST_F(ListTest, masterFiles) {
                    true);
 
     // If cache is not enabled, nothing is loaded
-    list_->configure(*elem, false);
+    list_->configure(elem, false);
     EXPECT_EQ(0, list_->getDataSources().size());
 }
 
+// Test we can reload a zone
+TEST_F(ListTest, reloadSuccess) {
+    list_->configure(config_elem_zones_, true);
+    Name name("example.org");
+    prepareCache(0, name);
+    // Not there yet. It would be NXDOMAIN, but it is in apex and
+    // it returns NXRRSET instead.
+    EXPECT_EQ(ZoneFinder::NXRRSET,
+              list_->find(name).finder_->find(name, RRType::SOA())->code);
+    // Now reload. It should be there now.
+    EXPECT_EQ(ConfigurableClientList::ZONE_RELOADED, list_->reload(name));
+    EXPECT_EQ(ZoneFinder::SUCCESS,
+              list_->find(name).finder_->find(name, RRType::SOA())->code);
+}
+
+// The cache is not enabled. The load should be rejected.
+TEST_F(ListTest, reloadNotEnabled) {
+    list_->configure(config_elem_zones_, false);
+    Name name("example.org");
+    // We put the cache in even when not enabled. This won't confuse the thing.
+    prepareCache(0, name);
+    // Not there yet. It would be NXDOMAIN, but it is in apex and
+    // it returns NXRRSET instead.
+    EXPECT_EQ(ZoneFinder::NXRRSET,
+              list_->find(name).finder_->find(name, RRType::SOA())->code);
+    // Now reload. It should reject it.
+    EXPECT_EQ(ConfigurableClientList::CACHE_DISABLED, list_->reload(name));
+    // Nothing changed here
+    EXPECT_EQ(ZoneFinder::NXRRSET,
+              list_->find(name).finder_->find(name, RRType::SOA())->code);
+}
+
+// Test several cases when the zone does not exist
+TEST_F(ListTest, reloadNoSuchZone) {
+    list_->configure(config_elem_zones_, true);
+    Name name("example.org");
+    // We put the cache in even when not enabled. This won't confuse the
+    // reload method, as that one looks at the real state of things, not
+    // at the configuration.
+    prepareCache(0, Name("example.com"));
+    // Not in the data sources
+    EXPECT_EQ(ConfigurableClientList::ZONE_NOT_FOUND,
+              list_->reload(Name("example.cz")));
+    // Not cached
+    EXPECT_EQ(ConfigurableClientList::ZONE_NOT_FOUND, list_->reload(name));
+    // Partial match
+    EXPECT_EQ(ConfigurableClientList::ZONE_NOT_FOUND,
+              list_->reload(Name("sub.example.com")));
+    // Nothing changed here - these zones don't exist
+    EXPECT_EQ(static_cast<isc::datasrc::DataSourceClient*>(NULL),
+              list_->find(name).dsrc_client_);
+    EXPECT_EQ(static_cast<isc::datasrc::DataSourceClient*>(NULL),
+              list_->find(Name("example.cz")).dsrc_client_);
+    EXPECT_EQ(static_cast<isc::datasrc::DataSourceClient*>(NULL),
+              list_->find(Name("sub.example.com"), true).dsrc_client_);
+    // Not reloaded
+    EXPECT_EQ(ZoneFinder::NXRRSET,
+              list_->find(Name("example.com")).finder_->
+              find(Name("example.com"), RRType::SOA())->code);
+}
+
+// Check we gracefuly throw an exception when a zone disappeared in
+// the underlying data source when we want to reload it
+TEST_F(ListTest, reloadZoneGone) {
+    list_->configure(config_elem_, true);
+    Name name("example.org");
+    // We put in a cache for non-existant zone. This emulates being loaded
+    // and then the zone disappearing. We prefill the cache, so we can check
+    // it.
+    prepareCache(0, name, true);
+    // The zone contains something
+    EXPECT_EQ(ZoneFinder::SUCCESS,
+              list_->find(name).finder_->find(name, RRType::SOA())->code);
+    // The zone is not there, so abort the reload.
+    EXPECT_THROW(list_->reload(name), DataSourceError);
+    // The zone is not hurt.
+    EXPECT_EQ(ZoneFinder::SUCCESS,
+              list_->find(name).finder_->find(name, RRType::SOA())->code);
+}
+
+// The underlying data source throws. Check we don't modify the state.
+TEST_F(ListTest, reloadZoneThrow) {
+    list_->configure(config_elem_zones_, true);
+    Name name("noiter.org");
+    prepareCache(0, name, true);
+    // The zone contains stuff now
+    EXPECT_EQ(ZoneFinder::SUCCESS,
+              list_->find(name).finder_->find(name, RRType::SOA())->code);
+    // The iterator throws, so abort the reload.
+    EXPECT_THROW(list_->reload(name), isc::NotImplemented);
+    // The zone is not hurt.
+    EXPECT_EQ(ZoneFinder::SUCCESS,
+              list_->find(name).finder_->find(name, RRType::SOA())->code);
+}
+
+TEST_F(ListTest, reloadNullIterator) {
+    list_->configure(config_elem_zones_, true);
+    Name name("null.org");
+    prepareCache(0, name, true);
+    // The zone contains stuff now
+    EXPECT_EQ(ZoneFinder::SUCCESS,
+              list_->find(name).finder_->find(name, RRType::SOA())->code);
+    // The iterator throws, so abort the reload.
+    EXPECT_THROW(list_->reload(name), isc::Unexpected);
+    // The zone is not hurt.
+    EXPECT_EQ(ZoneFinder::SUCCESS,
+              list_->find(name).finder_->find(name, RRType::SOA())->code);
+}
+
+// Test we can reload the master files too (special-cased)
+TEST_F(ListTest, reloadMasterFile) {
+    const char* const install_cmd = INSTALL_PROG " -c " TEST_DATA_DIR
+        "/root.zone " TEST_DATA_BUILDDIR "/root.zone.copied";
+    if (system(install_cmd) != 0) {
+        // any exception will do, this is failure in test setup, but
+        // nice to show the command that fails, and shouldn't be caught
+        isc_throw(isc::Exception,
+          "Error setting up; command failed: " << install_cmd);
+    }
+
+    const ConstElementPtr elem(Element::fromJSON("["
+        "{"
+        "   \"type\": \"MasterFiles\","
+        "   \"cache-enable\": true,"
+        "   \"params\": {"
+        "       \".\": \"" TEST_DATA_BUILDDIR "/root.zone.copied\""
+        "   }"
+        "}]"));
+    list_->configure(elem, true);
+    // Add a record that is not in the zone
+    EXPECT_EQ(ZoneFinder::NXDOMAIN,
+              list_->find(Name(".")).finder_->find(Name("nosuchdomain"),
+                                                   RRType::TXT())->code);
+    ofstream f;
+    f.open(TEST_DATA_BUILDDIR "/root.zone.copied", ios::out | ios::app);
+    f << "nosuchdomain.\t\t3600\tIN\tTXT\ttest" << std::endl;
+    f.close();
+    // Do the reload.
+    EXPECT_EQ(ConfigurableClientList::ZONE_RELOADED, list_->reload(Name(".")));
+    // It is here now.
+    EXPECT_EQ(ZoneFinder::SUCCESS,
+              list_->find(Name(".")).finder_->find(Name("nosuchdomain"),
+                                                   RRType::TXT())->code);
+}
+
 }

+ 1 - 1
src/lib/python/isc/datasrc/configurableclientlist_python.cc

@@ -95,7 +95,7 @@ ConfigurableClientList_configure(PyObject* po_self, PyObject* args) {
         if (PyArg_ParseTuple(args, "si", &configuration, &allow_cache)) {
             const isc::data::ConstElementPtr
                 element(isc::data::Element::fromJSON(string(configuration)));
-            self->cppobj->configure(*element, allow_cache);
+            self->cppobj->configure(element, allow_cache);
             Py_RETURN_NONE;
         } else {
             return (NULL);

+ 10 - 43
src/lib/python/isc/server_common/auth_command.py

@@ -26,9 +26,7 @@ AUTH_MODULE_NAME = 'Auth'
 def auth_loadzone_command(module_cc, zone_name, zone_class):
     '''Create a 'loadzone' command with a given zone for Auth server.
 
-    This function checks the Auth module configuration to see if it
-    servers a given zone via an in-memory data source on top of SQLite3
-    data source, and, if so, generate an inter-module command for Auth
+    This function generates an inter-module command for Auth
     to force it to reload the zone.
 
     Parameters:
@@ -38,9 +36,7 @@ def auth_loadzone_command(module_cc, zone_name, zone_class):
     zone_class (isc.dns.RRClass): the RR class of the zone to be possibly
       reloaded.
 
-    Return: a CC command message for the reload if the zone is found;
-      otherwise None.
-
+    Return: a CC command message for the reload.
     '''
     # Note: this function was originally a dedicated subroutine of xfrin,
     # but was moved here so it can be shared by some other modules
@@ -50,41 +46,12 @@ def auth_loadzone_command(module_cc, zone_name, zone_class):
     # deprecated (which is a more likely scenario).  For this reason, the
     # corresponding tests were still kept in xfrin.
 
-    datasources, is_default =\
-        module_cc.get_remote_config_value(AUTH_MODULE_NAME, "datasources")
-    if is_default:
-        return None
-    for d in datasources:
-        if "type" not in d:
-            continue
-        try:
-            if "class" in d:
-                dclass = RRClass(d["class"])
-            else:
-                dclass = RRClass("IN")
-        except InvalidRRClass as err:
-            logger.info(PYSERVER_COMMON_AUTH_CONFIG_RRCLASS_ERROR, err)
-            continue
-
-        if d["type"].lower() == "memory" and dclass == zone_class:
-            for zone in d["zones"]:
-                if "filetype" not in zone:
-                    continue
-                if "origin" not in zone:
-                    continue
-                if "filetype" not in zone:
-                    continue
-                try:
-                    name = Name(zone["origin"])
-                except (EmptyLabel, TooLongLabel, BadLabelType, BadEscape,
-                        TooLongName, IncompleteName):
-                    logger.info(PYSERVER_COMMON_AUTH_CONFIG_NAME_PARSER_ERROR,
-                                err)
-                    continue
+    # Note: The function got very simplified by #1976. There's plan to move
+    # to notification-driven approach, at which point the function would
+    # be changed a lot.
 
-                if zone["filetype"].lower() == "sqlite3" and name == zone_name:
-                    param = {"origin": zone_name.to_text(),
-                             "class": zone_class.to_text(),
-                             "datasrc": d["type"]}
-                    return create_command("loadzone", param)
-    return None
+    param = {
+        "origin": zone_name.to_text(),
+        "class": zone_class.to_text()
+    }
+    return create_command("loadzone", param)

+ 0 - 6
src/lib/python/isc/server_common/server_common_messages.mes

@@ -21,12 +21,6 @@
 # have that at this moment. So when adding a message, make sure that
 # the name is not already used in src/lib/config/config_messages.mes
 
-% PYSERVER_COMMON_AUTH_CONFIG_NAME_PARSER_ERROR Invalid name when parsing Auth configuration: %1
-There was an invalid name when parsing Auth configuration.
-
-% PYSERVER_COMMON_AUTH_CONFIG_RRCLASS_ERROR Invalid RRClass when parsing Auth configuration: %1
-There was an invalid RR class when parsing Auth configuration.
-
 % PYSERVER_COMMON_DNS_TCP_SEND_DONE completed sending TCP message to %1 (%2 bytes in total)
 Debug message.  A complete DNS message has been successfully
 transmitted over a TCP connection, possibly after multiple send

+ 12 - 0
tests/lettuce/configurations/ddns/ddns.config.orig

@@ -27,6 +27,18 @@
             }
         ]
     },
+    "data_sources": {
+        "classes": {
+            "IN": [
+                {
+                    "type": "sqlite3",
+                    "params": {
+                        "database_file": "data/ddns/example.org.sqlite3"
+                    }
+                }
+            ]
+        }
+    },
     "Boss": {
         "components": {
             "b10-xfrout": {

+ 16 - 13
tests/lettuce/configurations/ddns/noddns.config.orig

@@ -17,21 +17,24 @@
                 "port": 47806,
                 "address": "127.0.0.1"
             }
-        ],
-        "datasources": [
-            {
-                "type": "memory",
-                "class": "IN",
-                "zones": [
-                    {
-                        "origin": "example.org",
-                        "filetype": "sqlite3",
-                        "file": "data/ddns/example.org.sqlite3"
-                    }
-                ]
-            }
         ]
     },
+    "data_sources": {
+        "classes": {
+            "IN": [
+                {
+                    "type": "sqlite3",
+                    "params": {
+                        "database_file": "data/ddns/example.org.sqlite3"
+                    },
+                    "cache-enable": true,
+                    "cache-zones": [
+                        "example.org"
+                    ]
+                }
+            ]
+        }
+    },
     "Boss": {
         "components": {
             "b10-xfrout": {"kind": "dispensable"},

+ 12 - 0
tests/lettuce/configurations/example.org.config.orig

@@ -14,6 +14,18 @@
             "address": "127.0.0.1"
         } ]
     },
+    "data_sources": {
+        "classes": {
+            "IN": [
+                {
+                    "type": "sqlite3",
+                    "params": {
+                        "database_file": "data/example.org.sqlite3"
+                    }
+                }
+            ]
+        }
+    },
     "Boss": {
         "components": {
             "b10-auth": { "kind": "needed", "special": "auth" },

+ 28 - 1
tests/lettuce/configurations/example.org.inmem.config

@@ -1,4 +1,31 @@
-{"version": 2, "Logging": {"loggers": [{"severity": "DEBUG", "name": "*", "debuglevel": 99}]}, "Auth": {"database_file": "", "listen_on": [{"port": 47806, "address": "127.0.0.1"}], "datasources": [{"zones": [{"origin": "example.org", "file": "data/example.org"}], "type": "memory"}]},
+{
+    "version": 2,
+    "Logging": {
+        "loggers": [{
+            "severity": "DEBUG",
+            "name": "*",
+            "debuglevel": 99
+        }]
+    },
+    "Auth": {
+        "listen_on": [{
+            "port": 47806,
+            "address": "127.0.0.1"
+        }]
+    },
+    "data_sources": {
+        "classes": {
+            "IN": [
+                {
+                    "type": "MasterFiles",
+                    "cache-enable": true,
+                    "params": {
+                        "example.org": "data/example.org"
+                    }
+                }
+            ]
+        }
+    },
     "Boss": {
         "components": {
             "b10-auth": { "kind": "needed", "special": "auth" },

+ 12 - 0
tests/lettuce/configurations/example2.org.config

@@ -15,6 +15,18 @@
             "address": "::1"
         } ]
     },
+    "data_sources": {
+        "classes": {
+            "IN": [
+                {
+                    "type": "sqlite3",
+                    "params": {
+                        "database_file": "data/example.org.sqlite3"
+                    }
+                }
+            ]
+        }
+    },
     "Boss": {
         "components": {
             "b10-auth": { "kind": "needed", "special": "auth" },

+ 16 - 8
tests/lettuce/configurations/inmemory_over_sqlite3/secondary.conf

@@ -8,19 +8,27 @@
         } ]
     },
     "Auth": {
-        "datasources": [ {
-            "type": "memory",
-            "zones": [ {
-                "origin": "example.org",
-                "file": "data/example.org.sqlite3",
-	        "filetype": "sqlite3"
-            } ]
-	} ],
         "listen_on": [ {
             "port": 47806,
             "address": "127.0.0.1"
         } ]
     },
+    "data_sources": {
+        "classes": {
+            "IN": [
+                {
+                    "type": "sqlite3",
+                    "params": {
+                        "database_file": "data/example.org.sqlite3"
+                    },
+                    "cache-enable": true,
+                    "cache-zones": [
+                        "example.org"
+                    ]
+                }
+            ]
+        }
+    },
     "Boss": {
         "components": {
             "b10-auth": { "kind": "needed", "special": "auth" },

+ 53 - 1
tests/lettuce/configurations/ixfr-out/testset1-config.db

@@ -1,4 +1,56 @@
-{"Xfrin": {"zones": [{"use_ixfr": true, "class": "IN", "name": "example.com.", "master_addr": "178.18.82.80"}]}, "version": 2, "Logging": {"loggers": [{"debuglevel": 99, "severity": "DEBUG", "output_options": [{"output": "stderr", "flush": true}], "name": "*"}]}, "Auth": {"database_file": "data/ixfr-out/zones.sqlite3", "listen_on": [{"port": 47806, "address": "::"}, {"port": 47806, "address": "0.0.0.0"}]},
+{
+    "Xfrin": {
+        "zones": [
+            {
+                "use_ixfr": true,
+                "class": "IN",
+                "name": "example.com.",
+                "master_addr": "178.18.82.80"
+            }
+        ]
+    },
+    "version": 2,
+    "Logging": {
+        "loggers":
+            [
+                {
+                    "debuglevel": 99,
+                    "severity": "DEBUG",
+                    "output_options": [
+                        {
+                            "output": "stderr",
+                            "flush": true
+                        }
+                    ],
+                    "name": "*"
+                }
+        ]
+    },
+    "Auth": {
+        "database_file": "data/ixfr-out/zones.sqlite3",
+        "listen_on": [
+            {
+                "port": 47806,
+                "address": "::"
+            },
+            {
+                "port": 47806,
+                "address": "0.0.0.0"
+            }
+        ]
+    },
+    "data_sources": {
+        "classes": {
+            "IN": [
+                {
+                    "type": "sqlite3",
+                    "params": {
+                        "database_file": "data/ixfr-out/zones.sqlite3"
+                    }
+                }
+            ]
+        }
+    },
     "Boss": {
         "components": {
             "b10-auth": { "kind": "needed", "special": "auth" },

+ 10 - 0
tests/lettuce/configurations/multi_instance/multi_auth.config.orig

@@ -14,6 +14,16 @@
             "address": "127.0.0.1"
         } ]
     },
+    "data_sources": {
+        "classes": {
+            "IN": [{
+                "type": "sqlite3",
+                "params": {
+                    "database_file": "data/test_nonexistent_db.sqlite3"
+                }
+            }]
+        }
+    },
     "Boss": {
         "components": {
             "b10-auth-2": {"kind": "dispensable", "special": "auth"},

+ 12 - 0
tests/lettuce/configurations/no_db_file.config

@@ -15,6 +15,18 @@
             "address": "127.0.0.1"
         } ]
     },
+    "data_sources": {
+        "classes": {
+            "IN": [
+                {
+                    "type": "sqlite3",
+                    "params": {
+                        "database_file": "data/test_nonexistent_db.sqlite3"
+                    }
+                }
+            ]
+        }
+    },
     "Boss": {
         "components": {
             "b10-auth": { "kind": "needed", "special": "auth" },

+ 36 - 1
tests/lettuce/configurations/nsec3/nsec3_auth.config

@@ -1 +1,36 @@
-{"version": 2, "Logging": {"loggers": [{"severity": "DEBUG", "name": "*", "debuglevel": 99}]}, "Auth": {"datasources": [{"zones": [{"origin": "example.", "file": "configurations/nsec3/rfc5155-example.zone.signed"}], "type": "memory"}], "listen_on": [{"port": 47806, "address": "0.0.0.0"}]}, "Boss": {"components": {"b10-auth": {"kind": "needed", "special": "auth"}, "b10-cmdctl": {"kind": "needed", "special": "cmdctl"}}}}
+{
+    "version": 2,
+    "Logging": {
+        "loggers": [
+            {
+                "severity": "DEBUG",
+                "name": "*", "debuglevel": 99
+            }
+        ]
+    },
+    "Auth": {
+        "datasources": [
+            {"zones": [{"origin": "example.", "file": "configurations/nsec3/rfc5155-example.zone.signed"}], "type": "memory"}],
+            "listen_on": [{"port": 47806, "address": "0.0.0.0"}
+        ]
+    },
+    "data_sources": {
+        "classes": {
+            "IN": [
+                {
+                    "type": "MasterFiles",
+                    "cache-enable": true,
+                    "params": {
+                        "example.": "configurations/nsec3/rfc5155-example.zone.signed"
+                    }
+                }
+            ]
+        }
+    },
+    "Boss": {
+        "components": {
+            "b10-auth": {"kind": "needed", "special": "auth"},
+            "b10-cmdctl": {"kind": "needed", "special": "cmdctl"}
+        }
+    }
+}

+ 16 - 9
tests/lettuce/configurations/xfrin/inmem_slave.conf

@@ -9,20 +9,27 @@
     },
     "Auth": {
         "database_file": "data/inmem-xfrin.sqlite3",
-        "datasources": [ {
-            "type": "memory",
-            "class": "IN",
-            "zones": [ {
-                "origin": "example.org",
-                "file": "data/inmem-xfrin.sqlite3",
-                "filetype": "sqlite3"
-            } ]
-        } ],
         "listen_on": [ {
             "address": "::1",
             "port": 47806
         } ]
     },
+    "data_sources": {
+        "classes": {
+            "IN": [
+                {
+                    "type": "sqlite3",
+                    "params": {
+                        "database_file": "data/inmem-xfrin.sqlite3"
+                    },
+                    "cache-enable": true,
+                    "cache-zones": [
+                        "example.org"
+                    ]
+                }
+            ]
+        }
+    },
     "Boss": {
         "components": {
             "b10-auth": { "kind": "needed", "special": "auth" },

+ 10 - 0
tests/lettuce/configurations/xfrin/retransfer_master.conf

@@ -14,6 +14,16 @@
             "port": 47807
         } ]
     },
+    "data_sources": {
+        "classes": {
+            "IN": [{
+                "type": "sqlite3",
+                "params": {
+                    "database_file": "data/example.org.sqlite3"
+                }
+            }]
+        }
+    },
     "Xfrout": {
         "zone_config": [ {
             "origin": "example.org"

+ 10 - 0
tests/lettuce/configurations/xfrin/retransfer_slave.conf

@@ -14,6 +14,16 @@
             "port": 47806
         } ]
     },
+    "data_sources": {
+        "classes": {
+            "IN": [{
+                "type": "sqlite3",
+                "params": {
+                    "database_file": "data/test_nonexistent_db.sqlite3"
+                }
+            }]
+        }
+    },
     "Boss": {
         "components": {
             "b10-auth": { "kind": "needed", "special": "auth" },

+ 10 - 0
tests/lettuce/configurations/xfrin/retransfer_slave_notify.conf

@@ -14,6 +14,16 @@
             "port": 47806
         } ]
     },
+    "data_sources": {
+        "classes": {
+            "IN": [{
+                "type": "sqlite3",
+                "params": {
+                    "database_file": "data/xfrin-notify.sqlite3"
+                }
+            }]
+        }
+    },
     "Xfrin": {
         "zones": [ {
             "name": "example.org",

+ 6 - 6
tests/lettuce/features/example.feature

@@ -160,12 +160,12 @@ Feature: Example feature
 
         A query for www.example.org should have rcode NOERROR
         Wait for new bind10 stderr message AUTH_SEND_NORMAL_RESPONSE
-        Then set bind10 configuration Auth/database_file to data/empty_db.sqlite3
-        And wait for new bind10 stderr message DATASRC_SQLITE_OPEN
+        Then set bind10 configuration data_sources/classes/IN[0]/params to {"database_file": "data/empty_db.sqlite3"}
+        And wait for new bind10 stderr message DATASRC_SQLITE_CONNOPEN
         A query for www.example.org should have rcode REFUSED
         Wait for new bind10 stderr message AUTH_SEND_NORMAL_RESPONSE
-        Then set bind10 configuration Auth/database_file to data/example.org.sqlite3
-        And wait for new bind10 stderr message DATASRC_SQLITE_OPEN
+        Then set bind10 configuration data_sources/classes/IN[0]/params to {"database_file": "data/example.org.sqlite3"}
+        And wait for new bind10 stderr message DATASRC_SQLITE_CONNOPEN
         A query for www.example.org should have rcode NOERROR
 
     Scenario: two bind10 instances
@@ -186,8 +186,8 @@ Feature: Example feature
         The SOA serial for example.org at 127.0.0.1:47806 should be 1234
         The SOA serial for example.org at ::1:47807 should be 1234
 
-        Then set bind10 configuration Auth/database_file to data/empty_db.sqlite3
-        And wait for bind10_one stderr message DATASRC_SQLITE_OPEN
+        Then set bind10 configuration data_sources/classes/IN[0]/params to {"database_file": "data/empty_db.sqlite3"}
+        And wait for bind10_one stderr message DATASRC_SQLITE_CONNOPEN
 
         A query for www.example.org to 127.0.0.1:47806 should have rcode REFUSED
         A query for www.example.org to [::1]:47807 should have rcode NOERROR

+ 1 - 1
tests/lettuce/features/terrain/bind10_control.py

@@ -52,7 +52,7 @@ def start_bind10(step, config_file, cmdctl_port, msgq_sockfile, process_name):
     It will also fail if there is a running process with the given process_name
     already.
     """
-    args = [ 'bind10', '-n', '-v' ]
+    args = [ 'bind10', '-v' ]
     if config_file is not None:
         args.append('-p')
         args.append("configurations/")

+ 20 - 0
tests/system/bindctl/nsx1/b10-config.db.template.in

@@ -3,5 +3,25 @@
    "listen_on": [{"address": "10.53.0.1", "port": 53210}],
    "database_file": "@abs_builddir@/zone.sqlite3",
    "statistics-interval": 1
+ },
+ "data_sources": {
+    "classes": {
+        "IN": [{
+            "type": "sqlite3",
+            "params": {
+                "database_file": "@abs_builddir@/zone.sqlite3"
+            }
+        }]
+    }
+ },
+ "Logging": {
+     "loggers": [
+        {
+            "name": "*",
+            "severity": "DEBUG",
+            "output_options": [],
+            "debuglevel": 99
+        }
+     ]
  }
 }

+ 3 - 3
tests/system/bindctl/tests.sh

@@ -108,9 +108,9 @@ if [ $status != 0 ]; then echo "I:failed"; fi
 n=`expr $n + 1`
 
 echo "I:Changing the data source from sqlite3 to in-memory ($n)"
-DATASRC_SPEC='[{"type": "memory", "zones": [{"origin": "com","file":'
-DATASRC_SPEC="${DATASRC_SPEC} \"${TEST_TOP}/bindctl/nsx1/example-normalized.db\"}]}]"
-echo "config set Auth/datasources ${DATASRC_SPEC}
+DATASRC_SPEC='{"type": "MasterFiles", "cache-enable": true, "params": {"com":'
+DATASRC_SPEC="${DATASRC_SPEC} \"${TEST_TOP}/bindctl/nsx1/example-normalized.db\"}}"
+echo "config set data_sources/classes/IN[0] ${DATASRC_SPEC}
 config commit
 quit
 " | $RUN_BINDCTL \

+ 5 - 2
tests/system/glue/example.good

@@ -15,5 +15,8 @@ example.			172800	IN	NS	NS.example.
 ;; ADDITIONAL SECTION:
 NS.example.		172800	IN	A	192.0.2.1
 NS.example.		172800	IN	A	192.0.2.2
-NS1.example.COM.	172800	IN	A	192.0.2.101
-NS1.example.COM.		172800	IN	AAAA	2001:db8::1
+NS1.example.COM.	172800	IN	A	192.0.2.3
+;; These are not used now - they are in a different master file
+;; than the answer.
+; NS1.example.COM.	172800	IN	A	192.0.2.101
+; NS1.example.COM.		172800	IN	AAAA	2001:db8::1

+ 20 - 0
tests/system/glue/nsx1/b10-config.db.in

@@ -3,6 +3,26 @@
    "listen_on": [{"address": "10.53.0.1", "port": 53210}],
    "database_file": "@abs_builddir@/zone.sqlite3"
  },
+ "data_sources": {
+    "classes": {
+        "IN": [{
+            "type": "sqlite3",
+            "params": {
+                "database_file": "@abs_builddir@/zone.sqlite3"
+            }
+        }]
+    }
+ },
+ "Logging": {
+     "loggers": [
+        {
+            "name": "*",
+            "severity": "DEBUG",
+            "output_options": [],
+            "debuglevel": 99
+        }
+     ]
+ },
  "Boss": {
    "components": {
      "b10-auth": {"kind": "needed", "special": "auth" },

+ 0 - 2
tests/system/glue/nsx1/root.db

@@ -37,8 +37,6 @@ example.			172800	IN	NS	NS.example.
 example.			172800	IN	NS	NS1.example.COM.
 NS.example.			172800	IN	A	192.0.2.1
 NS.example.			172800	IN	A	192.0.2.2
-; this "glue" is below a zone cut for com.  BIND 9 still uses it for
-; the delegation to example.  BIND 10 (with sqlite3 data source) doesn't.
 NS1.example.COM.		172800	IN	A	192.0.2.3
 
 ;

+ 6 - 4
tests/system/glue/tests.sh

@@ -38,10 +38,12 @@ $DIG +norec @10.53.0.1 -p 53210 foo.bar.example. A >dig.out.$n || status=1
 $PERL $DIGCOMP example.good dig.out.$n || status=1
 n=`expr $n + 1`
 
-echo "I:testing that we find glue A RRs we are authoritative for ($n)"
-$DIG +norec @10.53.0.1 -p 53210 foo.bar.example.org. a >dig.out.$n || status=1
-$PERL $DIGCOMP auth.good dig.out.$n || status=1
-n=`expr $n + 1`
+# Disabling this test, as it checks for looking up glue in a different zone
+# finder than the answer is from. This is not supported now.
+#echo "I:testing that we find glue A RRs we are authoritative for ($n)"
+#$DIG +norec @10.53.0.1 -p 53210 foo.bar.example.org. a >dig.out.$n || status=1
+#$PERL $DIGCOMP auth.good dig.out.$n || status=1
+#n=`expr $n + 1`
 
 # We cannot do this test for BIND 10 because b10-auth doesn't act as a
 # recursive (caching) server (by design)

+ 18 - 0
tests/system/ixfr/b10-config.db.in

@@ -14,6 +14,24 @@
         }],
         "database_file": "@abs_builddir@/zone.sqlite3"
     },
+    "data_sources": {
+        "classes": {
+            "IN": [{
+                "type": "sqlite3",
+                "params": {
+                    "database_file": "@abs_builddir@/zone.sqlite3"
+                }
+            }]
+        }
+    },
+    "Logging": {
+        "loggers": [{
+            "name": "*",
+            "severity": "DEBUG",
+            "output_options": [],
+            "debuglevel": 99
+        }]
+    },
     "Zonemgr": {
         "secondary_zones": [{
             "name": "example.",