Browse Source

[master] Merge branch 'trac2833'

JINMEI Tatuya 12 years ago
parent
commit
613a57b599

+ 16 - 84
src/bin/auth/tests/auth_srv_unittest.cc

@@ -78,7 +78,6 @@ using namespace isc::asiolink;
 using namespace isc::testutils;
 using namespace isc::server_common::portconfig;
 using namespace isc::auth::unittest;
-using isc::datasrc::memory::ZoneTableSegment;
 using isc::UnitTestUtil;
 using boost::scoped_ptr;
 using isc::auth::statistics::Counters;
@@ -264,8 +263,6 @@ updateDatabase(AuthSrv& server, const char* params) {
     installDataSrcClientLists(server, configureDataSource(config));
 }
 
-// Note: if with_static is set to true, the corresponding test should be
-// disabled in case of USE_STATIC_LINK.
 void
 updateInMemory(AuthSrv& server, const char* origin, const char* filename,
                bool with_static = true)
@@ -280,8 +277,9 @@ updateInMemory(AuthSrv& server, const char* origin, const char* filename,
         "}]";
     if (with_static) {
         spec_txt += ", \"CH\": [{"
-        "   \"type\": \"static\","
-        "   \"params\": \"" + string(STATIC_DSRC_FILE) + "\""
+        "   \"type\": \"MasterFiles\","
+        "   \"cache-enable\": true,"
+        "   \"params\": {\"BIND\": \"" + string(STATIC_DSRC_FILE) + "\"}"
             "}]";
     }
     spec_txt += "}";
@@ -290,14 +288,13 @@ updateInMemory(AuthSrv& server, const char* origin, const char* filename,
     installDataSrcClientLists(server, configureDataSource(config));
 }
 
-// Note: tests using this function should be disabled in case of
-// USE_STATIC_LINK.
 void
 updateBuiltin(AuthSrv& server) {
     const ConstElementPtr config(Element::fromJSON("{"
         "\"CH\": [{"
-        "   \"type\": \"static\","
-        "   \"params\": \"" + string(STATIC_DSRC_FILE) + "\""
+        "   \"type\": \"MasterFiles\","
+        "   \"cache-enable\": true,"
+        "   \"params\": {\"BIND\": \"" + string(STATIC_DSRC_FILE) + "\"}"
         "}]}"));
     installDataSrcClientLists(server, configureDataSource(config));
 }
@@ -750,11 +747,7 @@ TEST_F(AuthSrvTest, notify) {
     checkStatisticsCounters(stats_after, expect);
 }
 
-#ifdef USE_STATIC_LINK
-TEST_F(AuthSrvTest, DISABLED_notifyForCHClass) {
-#else
 TEST_F(AuthSrvTest, notifyForCHClass) {
-#endif
     // Same as the previous test, but for the CH RRClass (so we install the
     // builtin (static) data source.
     updateBuiltin(server);
@@ -1003,11 +996,7 @@ TEST_F(AuthSrvTest, notifyNotAuthNoClass) {
 
 // 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");
@@ -1065,11 +1054,7 @@ TEST_F(AuthSrvTest, TSIGSigned) {
 // 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."),
@@ -1097,11 +1082,7 @@ TEST_F(AuthSrvTest, builtInQueryViaDNSServer) {
 
 // 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."),
@@ -1118,11 +1099,7 @@ TEST_F(AuthSrvTest, builtInQuery) {
 }
 
 // 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
+TEST_F(AuthSrvTest, iqueryViaDNSServer) {
     updateBuiltin(server);
     createDataFromFile("iquery_fromWire.wire");
     (*server.getDNSLookupProvider())(*io_message, parse_message,
@@ -1233,11 +1210,7 @@ TEST_F(AuthSrvTest, updateWithInMemoryClient) {
                 opcode.getCode(), QR_FLAG, 1, 0, 0, 0);
 }
 
-#ifdef USE_STATIC_LINK
-TEST_F(AuthSrvTest, DISABLED_queryWithInMemoryClientNoDNSSEC) {
-#else
 TEST_F(AuthSrvTest, queryWithInMemoryClientNoDNSSEC) {
-#endif
     // 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
@@ -1253,11 +1226,7 @@ TEST_F(AuthSrvTest, queryWithInMemoryClientNoDNSSEC) {
                 opcode.getCode(), QR_FLAG | AA_FLAG, 1, 1, 2, 1);
 }
 
-#ifdef USE_STATIC_LINK
-TEST_F(AuthSrvTest, DISABLED_queryWithInMemoryClientDNSSEC) {
-#else
 TEST_F(AuthSrvTest, queryWithInMemoryClientDNSSEC) {
-#endif
     // 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.
@@ -1272,14 +1241,7 @@ TEST_F(AuthSrvTest, queryWithInMemoryClientDNSSEC) {
                 opcode.getCode(), QR_FLAG | AA_FLAG, 1, 2, 3, 3);
 }
 
-TEST_F(AuthSrvTest,
-#ifdef USE_STATIC_LINK
-       DISABLED_chQueryWithInMemoryClient
-#else
-       chQueryWithInMemoryClient
-#endif
-    )
-{
+TEST_F(AuthSrvTest, chQueryWithInMemoryClient) {
     // Set up the in-memory
     updateInMemory(server, "example.", CONFIG_INMEMORY_EXAMPLE);
 
@@ -1752,9 +1714,7 @@ public:
              real_list, ThrowWhen throw_when, bool isc_exception,
              ConstRRsetPtr fake_rrset = ConstRRsetPtr()) :
         ConfigurableClientList(RRClass::IN()),
-        real_(real_list),
-        config_(Element::fromJSON("{}")),
-        ztable_segment_(ZoneTableSegment::create(*config_, RRClass::IN()))
+        real_(real_list)
     {
         BOOST_FOREACH(const DataSourceInfo& info, real_->getDataSources()) {
              const isc::datasrc::DataSourceClientPtr
@@ -1766,13 +1726,13 @@ public:
              data_sources_.push_back(
                  DataSourceInfo(client.get(),
                                 isc::datasrc::DataSourceClientContainerPtr(),
-                                false, RRClass::IN(), ztable_segment_, ""));
+                                boost::shared_ptr<
+                                isc::datasrc::internal::CacheConfig>(),
+                                RRClass::IN(), ""));
         }
     }
 private:
     const boost::shared_ptr<isc::datasrc::ConfigurableClientList> real_;
-    const ConstElementPtr config_;
-    boost::shared_ptr<ZoneTableSegment> ztable_segment_;
     vector<isc::datasrc::DataSourceClientPtr> clients_;
 };
 
@@ -1782,14 +1742,7 @@ private:
 //
 // Set the proxies to never throw, this should have the same result as
 // queryWithInMemoryClientNoDNSSEC, and serves to test the two proxy classes
-TEST_F(AuthSrvTest,
-#ifdef USE_STATIC_LINK
-       DISABLED_queryWithInMemoryClientProxy
-#else
-       queryWithInMemoryClientProxy
-#endif
-    )
-{
+TEST_F(AuthSrvTest, queryWithInMemoryClientProxy) {
     // Set real inmem client to proxy
     updateInMemory(server, "example.", CONFIG_INMEMORY_EXAMPLE);
     boost::shared_ptr<isc::datasrc::ConfigurableClientList> list;
@@ -1836,14 +1789,7 @@ setupThrow(AuthSrv& server, ThrowWhen throw_when, bool isc_exception,
     mgr.setDataSrcClientLists(lists);
 }
 
-TEST_F(AuthSrvTest,
-#ifdef USE_STATIC_LINK
-       DISABLED_queryWithThrowingProxyServfails
-#else
-       queryWithThrowingProxyServfails
-#endif
-    )
-{
+TEST_F(AuthSrvTest, queryWithThrowingProxyServfails) {
     // Test the common cases, all of which should simply return SERVFAIL
     // Use THROW_NEVER as end marker
     ThrowWhen throws[] = { THROW_AT_FIND_ZONE,
@@ -1867,14 +1813,7 @@ TEST_F(AuthSrvTest,
 
 // Throw isc::Exception in getClass(). (Currently?) getClass is not called
 // in the processMessage path, so this should result in a normal answer
-TEST_F(AuthSrvTest,
-#ifdef USE_STATIC_LINK
-       DISABLED_queryWithInMemoryClientProxyGetClass
-#else
-       queryWithInMemoryClientProxyGetClass
-#endif
-    )
-{
+TEST_F(AuthSrvTest, queryWithInMemoryClientProxyGetClass) {
     createDataFromFile("nsec3query_nodnssec_fromWire.wire");
     setupThrow(server, THROW_AT_GET_CLASS, true);
 
@@ -1887,14 +1826,7 @@ TEST_F(AuthSrvTest,
                 opcode.getCode(), QR_FLAG | AA_FLAG, 1, 1, 2, 1);
 }
 
-TEST_F(AuthSrvTest,
-#ifdef USE_STATIC_LINK
-       DISABLED_queryWithThrowingInToWire
-#else
-       queryWithThrowingInToWire
-#endif
-    )
-{
+TEST_F(AuthSrvTest, queryWithThrowingInToWire) {
     // Set up a faked data source.  It will return an empty RRset for the
     // query.
     ConstRRsetPtr empty_rrset(new RRset(Name("foo.example"),

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

@@ -18,9 +18,9 @@
                     ],
                     "CH": [
                         {
-                            "type": "static",
-                            "cache-enable": false,
-                            "params": "@@STATIC_ZONE_FILE@@"
+                            "type": "MasterFiles",
+                            "cache-enable": true,
+                            "params": {"BIND": "@@STATIC_ZONE_FILE@@"}
                         }
                     ]
                 },

+ 3 - 0
src/bin/cfgmgr/plugins/tests/Makefile.am

@@ -11,6 +11,8 @@ LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryp
 endif
 
 # test using command-line arguments, so use check-local target instead of TESTS
+# We need to set B10_FROM_BUILD as some of the tests need to create lock file
+# for logging.
 check-local:
 if ENABLE_PYTHON_COVERAGE
 	touch $(abs_top_srcdir)/.coverage
@@ -20,6 +22,7 @@ endif
 	for pytest in $(PYTESTS) ; do \
 	echo Running test: $$pytest ; \
 	B10_TEST_PLUGIN_DIR=$(abs_srcdir)/..:$(abs_builddir)/.. \
+	B10_FROM_BUILD=$(abs_top_builddir) \
 	PYTHONPATH=$(COMMON_PYTHON_PATH):$(abs_top_builddir)/src/bin/cfgmgr:$(abs_top_builddir)/src/lib/dns/python/.libs \
 	$(LIBRARY_PATH_PLACEHOLDER) \
 	$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \

+ 2 - 7
src/lib/datasrc/Makefile.am

@@ -39,10 +39,11 @@ libb10_datasrc_la_SOURCES += master_loader_callbacks.h
 libb10_datasrc_la_SOURCES += master_loader_callbacks.cc
 libb10_datasrc_la_SOURCES += rrset_collection_base.h rrset_collection_base.cc
 libb10_datasrc_la_SOURCES += zone_loader.h zone_loader.cc
+libb10_datasrc_la_SOURCES += cache_config.h cache_config.cc
 nodist_libb10_datasrc_la_SOURCES = datasrc_messages.h datasrc_messages.cc
 libb10_datasrc_la_LDFLAGS = -no-undefined -version-info 1:0:1
 
-pkglib_LTLIBRARIES = sqlite3_ds.la static_ds.la
+pkglib_LTLIBRARIES = sqlite3_ds.la
 
 sqlite3_ds_la_SOURCES = sqlite3_accessor.h sqlite3_accessor.cc
 sqlite3_ds_la_SOURCES += sqlite3_accessor_link.cc
@@ -53,12 +54,6 @@ sqlite3_ds_la_LIBADD = $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
 sqlite3_ds_la_LIBADD += libb10-datasrc.la
 sqlite3_ds_la_LIBADD += $(SQLITE_LIBS)
 
-static_ds_la_SOURCES = static_datasrc_link.cc
-static_ds_la_SOURCES += static_datasrc.h
-static_ds_la_LDFLAGS = -module -avoid-version
-static_ds_la_LIBADD = $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
-static_ds_la_LIBADD += libb10-datasrc.la
-
 libb10_datasrc_la_LIBADD = $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
 libb10_datasrc_la_LIBADD += $(top_builddir)/src/lib/dns/libb10-dns++.la
 libb10_datasrc_la_LIBADD += $(top_builddir)/src/lib/log/libb10-log.la

+ 113 - 0
src/lib/datasrc/cache_config.cc

@@ -0,0 +1,113 @@
+// Copyright (C) 2013  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 <datasrc/cache_config.h>
+#include <datasrc/client.h>
+#include <datasrc/memory/load_action.h>
+#include <dns/name.h>
+#include <cc/data.h>
+#include <exceptions/exceptions.h>
+
+#include <map>
+#include <string>
+
+using namespace isc::data;
+
+namespace isc {
+namespace datasrc {
+namespace internal {
+
+namespace {
+bool
+getEnabledFromConf(const Element& conf) {
+    return (conf.contains("cache-enable") &&
+            conf.get("cache-enable")->boolValue());
+}
+
+std::string
+getSegmentTypeFromConf(const Element& conf) {
+    // If cache-zones is not explicitly configured, use the default type.
+    // (Ideally we should retrieve the default from the spec).
+    if (!conf.contains("cache-type")) {
+        return ("local");
+    }
+    return (conf.get("cache-type")->stringValue());
+}
+}
+
+CacheConfig::CacheConfig(const std::string& datasrc_type,
+                         const DataSourceClient* datasrc_client,
+                         const Element& datasrc_conf,
+                         bool allowed) :
+    enabled_(allowed && getEnabledFromConf(datasrc_conf)),
+    segment_type_(getSegmentTypeFromConf(datasrc_conf)),
+    datasrc_client_(datasrc_client)
+{
+    ConstElementPtr params = datasrc_conf.get("params");
+    if (!params) {
+        params.reset(new NullElement());
+    }
+    if (datasrc_type == "MasterFiles") {
+        if (datasrc_client_) {
+            isc_throw(InvalidParameter,
+                      "data source client is given for MasterFiles");
+        }
+
+        if (!enabled_) {
+            isc_throw(CacheConfigError,
+                      "The cache must be enabled for the MasterFiles type");
+        }
+
+        typedef std::map<std::string, ConstElementPtr> ZoneToFile;
+        const ZoneToFile& zone_to_file = params->mapValue();
+        ZoneToFile::const_iterator const it_end = zone_to_file.end();
+        for (ZoneToFile::const_iterator it = zone_to_file.begin();
+             it != it_end;
+             ++it)
+        {
+            zone_config_[dns::Name(it->first)] = it->second->stringValue();
+        }
+    } else {
+        if (!datasrc_client_) {
+            isc_throw(InvalidParameter,
+                      "data source client is missing for data source type: "
+                      << datasrc_type);
+        }
+        if (!enabled_) {
+            return;
+        }
+
+        if (!datasrc_conf.contains("cache-zones")) {
+            isc_throw(NotImplemented, "Auto-detection of zones "
+                      "to cache is not yet implemented, supply "
+                      "cache-zones parameter");
+            // TODO: Auto-detect list of all zones in the
+            // data source.
+        }
+
+        const ConstElementPtr zones = datasrc_conf.get("cache-zones");
+        for (size_t i = 0; i < zones->size(); ++i) {
+            const dns::Name zone_name(zones->get(i)->stringValue());
+            if (!zone_config_.insert(Zones::value_type(zone_name,
+                                                       "")).second) {
+                isc_throw(CacheConfigError, "Duplicate cache zone: " <<
+                          zone_name);
+            }
+        }
+    }
+}
+
+} // namespace internal
+} // namespace datasrc
+} // namespace isc

+ 172 - 0
src/lib/datasrc/cache_config.h

@@ -0,0 +1,172 @@
+// Copyright (C) 2013  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_CACHE_CONFIG_H
+#define DATASRC_CACHE_CONFIG_H
+
+#include <exceptions/exceptions.h>
+
+#include <dns/name.h>
+#include <cc/data.h>
+#include <datasrc/memory/load_action.h>
+
+#include <boost/noncopyable.hpp>
+
+#include <map>
+#include <string>
+
+namespace isc {
+namespace datasrc {
+class DataSourceClient;
+
+namespace internal {
+
+/// \brief Exception thrown for configuration error related to in-memory cache.
+class CacheConfigError : public Exception {
+public:
+    CacheConfigError(const char* file, size_t line, const char* what) :
+        Exception(file, line, what)
+    {}
+};
+
+/// \brief Configuration for in-memory cache of a data source.
+///
+/// This class understands and validates the configuration parameters for
+/// \c DataSourceClient related to in-memory cache, and converts it to native,
+/// type-safe objects for the convenience of the user of this class.
+/// Specifically, it allows the user to get the underlying memory segment
+/// type for the cache as a string and to iterate over zone names to be
+/// cached in memory.
+///
+/// It also provides unified interface for getting \c memory::LoadAction
+/// object that can be used for loading zones, regardless of the underlying
+/// data source properties, i.e., whether it's special "MasterFiles" type
+/// or other generic data sources.
+/// NOTE: this part will be done in #2834.
+///
+/// This class is publicly defined so it can be tested directly, but
+/// it's essentially private to the \c ConfigurableClientList class.
+/// It's therefore defined in an "internal" namespace, and isn't expected
+/// to be used by other classes or user applications.  Likewise, this file
+/// is not expected to be installed with other publicly usable header files.
+///
+/// It's defined as noncopyable, simply because it's not expected to be
+/// copied in the intended usage for \c ConfigurableClientList.  Prohibiting
+/// copies will help avoid unexpected disruption due to accidental copy and
+/// sharing internal resources as a result of that.
+class CacheConfig : boost::noncopyable {
+public:
+    /// \brief Constructor.
+    ///
+    /// It performs the following validation on the given configuration:
+    /// - For the "MasterFiles" type
+    ///   - datasrc_client_ must not be provided (must be null); throws
+    ///     InvalidParameter otherwise.
+    ///   - cache must be enabled: "cache-enable" configuration item exists
+    ///     and is true, and allowed parameter is true, too; throws
+    ///     CacheConfigError otherwise.
+    ///   - "params" configuration item must be provided and of a map type,
+    ///     and each map entry maps a string to another string; throws
+    ///     data::TypeError otherwise.
+    ///   - the key string of each map entry must be a valid textual
+    ///     representation of a domain name.  Otherwise corresponding
+    ///     exception from the dns::Name class will be thrown.
+    /// - For other types
+    ///   - datasrc_client_ must be provided (must not be null); throws
+    ///     InvalidParameter otherwise.
+    ///   - (Unless cache is disabled) "cache-zones" configuration item must
+    ///     exist and must be a list of strings; throws data::TypeError
+    ///     otherwise.
+    ///   - Each string value of cache-zones entries must be a valid textual
+    ///     representation of a domain name.  Otherwise corresponding
+    ///     exception from the dns::Name class will be thrown.
+    ///   - Names in the list must not have duplicates;
+    ///     throws CacheConfigError otherwise.
+    ///
+    /// For other data source types than "MasterFiles", cache can be disabled.
+    /// In this case cache-zones configuration item is simply ignored, even
+    /// it contains an error that would otherwise trigger an exception.
+    ///
+    /// The specified set of zones (directly in "params" in case of
+    /// "MasterFile", and specified in "cache-zones" for others) can be
+    /// empty.
+    ///
+    /// This constructor also identifies the underlying memory segment type
+    /// used for the cache.  It's given via the "cache-type" configuration
+    /// item if defined; otherwise it defaults to "local".
+    ///
+    /// \throw InvalidParameter Program error at the caller side rather than
+    /// in the configuration (see above)
+    /// \throw CacheConfigError There is a semantics error in the given
+    /// configuration (see above)
+    /// \throw data::TypeError Invalid type of data is found in the
+    /// configuration (see above)
+    /// \throw Other Exceptions from the dns::Name class when conversion from
+    /// text fails (see above)
+    ///
+    /// \param datasrc_type Type of data source. This must be the "type"
+    /// value of the data source configuration.
+    /// \param datasrc_client Client of the underlying data source for the
+    /// cache, if it's used; for MasterFiles types it's null.
+    /// \param datasrc_conf Configuration element for the data source.
+    /// This must be the value of, e.g., data_sources/classes/IN[0] of
+    /// BIND 10 configuration.
+    /// \param allowed Whether in-memory cache is allowed by the process.
+    /// This must be derived from the allow_cache parameter of
+    /// \c ConfigurableClientList::configure().
+    CacheConfig(const std::string& datasrc_type,
+                const DataSourceClient* datasrc_client,
+                const data::Element& datasrc_conf,
+                bool allowed);
+
+    /// \brief Return if the cache is enabled.
+    ///
+    /// The cache is considered enabled iff the "cache-enable" configuration
+    /// item (given on construction) existed and was set to true, and
+    /// the \c allowed parameter to the constructor was true.
+    ///
+    /// \throw None
+    bool isEnabled() const { return (enabled_); }
+
+    /// \brief Return the memory segment type to be used for the zone table.
+    ///
+    /// \throw None
+    const std::string& getSegmentType() const { return (segment_type_); }
+
+    /// \todo the following definition is tentative, mainly for tests.
+    /// In #2834 we'll (probably) extend it to be a custom iterator so
+    /// the caller can iterate over the whole set of zones, loading the
+    /// content in memory.
+    typedef std::map<dns::Name, std::string>::const_iterator ConstZoneIterator;
+    ConstZoneIterator begin() const { return (zone_config_.begin()); }
+    ConstZoneIterator end() const { return (zone_config_.end()); }
+
+private:
+    const bool enabled_; // if the use of in-memory zone table is enabled
+    const std::string segment_type_;
+    // client of underlying data source, will be NULL for MasterFile datasrc
+    const DataSourceClient* datasrc_client_;
+
+    typedef std::map<dns::Name, std::string> Zones;
+    Zones zone_config_;
+};
+}
+}
+}
+
+#endif  // DATASRC_CACHE_CONFIG_H
+
+// Local Variables:
+// mode: c++
+// End:

+ 47 - 74
src/lib/datasrc/client_list.cc

@@ -13,16 +13,17 @@
 // PERFORMANCE OF THIS SOFTWARE.
 
 
-#include "client_list.h"
-#include "exceptions.h"
-#include "client.h"
-#include "factory.h"
-#include "memory/memory_client.h"
-#include "memory/zone_table_segment.h"
-#include "memory/zone_writer.h"
-#include "memory/zone_data_loader.h"
-#include "memory/zone_data_updater.h"
-#include "logger.h"
+#include <datasrc/client_list.h>
+#include <datasrc/exceptions.h>
+#include <datasrc/client.h>
+#include <datasrc/factory.h>
+#include <datasrc/cache_config.h>
+#include <datasrc/memory/memory_client.h>
+#include <datasrc/memory/zone_table_segment.h>
+#include <datasrc/memory/zone_writer.h>
+#include <datasrc/memory/zone_data_loader.h>
+#include <datasrc/memory/zone_data_updater.h>
+#include <datasrc/logger.h>
 #include <dns/masterload.h>
 #include <util/memory_segment_local.h>
 
@@ -47,28 +48,18 @@ namespace datasrc {
 
 ConfigurableClientList::DataSourceInfo::DataSourceInfo(
     DataSourceClient* data_src_client,
-    const DataSourceClientContainerPtr& container, bool has_cache,
-    const RRClass& rrclass, const shared_ptr<ZoneTableSegment>& segment,
-    const string& name) :
+    const DataSourceClientContainerPtr& container,
+    boost::shared_ptr<internal::CacheConfig> cache_conf,
+    const RRClass& rrclass, const string& name) :
     data_src_client_(data_src_client),
     container_(container),
-    name_(name)
+    name_(name),
+    cache_conf_(cache_conf)
 {
-    if (has_cache) {
-        cache_.reset(new InMemoryClient(segment, rrclass));
-        ztable_segment_ = segment;
-    }
-}
-
-ConfigurableClientList::DataSourceInfo::DataSourceInfo(
-    const RRClass& rrclass, const shared_ptr<ZoneTableSegment>& segment,
-    bool has_cache, const string& name) :
-    data_src_client_(NULL),
-    name_(name)
-{
-    if (has_cache) {
-        cache_.reset(new InMemoryClient(segment, rrclass));
-        ztable_segment_ = segment;
+    if (cache_conf_ && cache_conf_->isEnabled()) {
+        ztable_segment_.reset(ZoneTableSegment::create(
+                                  rrclass, cache_conf_->getSegmentType()));
+        cache_.reset(new InMemoryClient(ztable_segment_, rrclass));
     }
 }
 
@@ -94,8 +85,6 @@ ConfigurableClientList::configure(const ConstElementPtr& config,
     size_t i(0); // Outside of the try to be able to access it in the catch
     try {
         vector<DataSourceInfo> new_data_sources;
-        shared_ptr<ZoneTableSegment> ztable_segment(
-            ZoneTableSegment::create(*config, rrclass_));
         set<string> used_names;
         for (; i < config->size(); ++i) {
             // Extract the parameters
@@ -110,59 +99,35 @@ ConfigurableClientList::configure(const ConstElementPtr& config,
             if (paramConf == ConstElementPtr()) {
                 paramConf.reset(new NullElement());
             }
-            const bool want_cache(allow_cache &&
-                                  dconf->contains("cache-enable") &&
-                                  dconf->get("cache-enable")->boolValue());
             // Get the name (either explicit, or guess)
             const ConstElementPtr name_elem(dconf->get("name"));
             const string name(name_elem ? name_elem->stringValue() : type);
             if (!used_names.insert(name).second) {
-                isc_throw(ConfigurationError, "Duplicit name in client list: "
+                isc_throw(ConfigurationError, "Duplicate name in client list: "
                           << name);
             }
 
-            if (type == "MasterFiles") {
-                // In case the cache is not allowed, we just skip the master
-                // files (at least for now)
-                if (!allow_cache) {
-                    // We're not going to load these zones. Issue warnings about it.
-                    const map<string, ConstElementPtr>
-                        zones_files(paramConf->mapValue());
-                    for (map<string, ConstElementPtr>::const_iterator
-                         it(zones_files.begin()); it != zones_files.end();
-                         ++it) {
-                        LOG_WARN(logger, DATASRC_LIST_NOT_CACHED).
-                            arg(it->first).arg(rrclass_);
-                    }
-                    continue;
-                }
-                if (!want_cache) {
-                    isc_throw(ConfigurationError, "The cache must be enabled "
-                              "for the MasterFiles type");
-                }
-                new_data_sources.push_back(DataSourceInfo(rrclass_,
-                                                          ztable_segment,
-                                                          true, name));
-            } else {
-                // Ask the factory to create the data source for us
-                const DataSourcePair ds(this->getDataSourceClient(type,
-                                                                  paramConf));
-                // And put it into the vector
-                new_data_sources.push_back(DataSourceInfo(ds.first, ds.second,
-                                                          want_cache, rrclass_,
-                                                          ztable_segment,
-                                                          name));
+            // Create a client for the underling data source via factory.
+            // If it's our internal type of data source, this is essentially
+            // no-op.  In the latter case, it's of no use unless cache is
+            // allowed; we simply skip building it in that case.
+            const DataSourcePair dsrc_pair = getDataSourceClient(type,
+                                                                 paramConf);
+            if (!allow_cache && !dsrc_pair.first) {
+                LOG_WARN(logger, DATASRC_LIST_NOT_CACHED).
+                    arg(name).arg(rrclass_);
+                continue;
             }
 
-            if (want_cache) {
-                if (!dconf->contains("cache-zones") && type != "MasterFiles") {
-                    isc_throw(isc::NotImplemented, "Auto-detection of zones "
-                              "to cache is not yet implemented, supply "
-                              "cache-zones parameter");
-                    // TODO: Auto-detect list of all zones in the
-                    // data source.
-                }
+            boost::shared_ptr<internal::CacheConfig> cache_conf(
+                new internal::CacheConfig(type, dsrc_pair.first, *dconf,
+                                          allow_cache));
+            new_data_sources.push_back(DataSourceInfo(dsrc_pair.first,
+                                                      dsrc_pair.second,
+                                                      cache_conf, rrclass_,
+                                                      name));
 
+            if (cache_conf->isEnabled()) {
                 // List the zones we are loading
                 vector<string> zones_origins;
                 if (type == "MasterFiles") {
@@ -184,6 +149,7 @@ ConfigurableClientList::configure(const ConstElementPtr& config,
                     cache(new_data_sources.back().cache_);
                 const DataSourceClient* const
                     client(new_data_sources.back().data_src_client_);
+
                 for (vector<string>::const_iterator it(zones_origins.begin());
                      it != zones_origins.end(); ++it) {
                     const Name origin(*it);
@@ -227,6 +193,9 @@ ConfigurableClientList::configure(const ConstElementPtr& config,
     } catch (const TypeError& te) {
         isc_throw(ConfigurationError, "Malformed configuration at data source "
                   "no. " << i << ": " << te.what());
+    } catch (const internal::CacheConfigError& ex) {
+        // convert to the "public" exception type.
+        isc_throw(ConfigurationError, ex.what());
     }
 }
 
@@ -470,6 +439,10 @@ ConfigurableClientList::getDataSourceClient(const string& type,
                                             const ConstElementPtr&
                                             configuration)
 {
+    if (type == "MasterFiles") {
+        return (DataSourcePair(0, DataSourceClientContainerPtr()));
+    }
+
     DataSourceClientContainerPtr
         container(new DataSourceClientContainer(type, configuration));
     return (DataSourcePair(&container->getInstance(), container));

+ 18 - 11
src/lib/datasrc/client_list.h

@@ -46,6 +46,10 @@ class InMemoryClient;
 class ZoneWriter;
 }
 
+namespace internal {
+class CacheConfig;
+}
+
 /// \brief Segment status of the cache
 ///
 /// Describes the status in which the memory segment for the in-memory cache of
@@ -397,19 +401,11 @@ public:
     ///
     /// \todo The content yet to be defined.
     struct DataSourceInfo {
-        // Plays a role of default constructor too (for vector)
-        DataSourceInfo(const dns::RRClass& rrclass,
-                       const boost::shared_ptr
-                           <isc::datasrc::memory::ZoneTableSegment>&
-                               ztable_segment,
-                       bool has_cache = false,
-                       const std::string& name = std::string());
         DataSourceInfo(DataSourceClient* data_src_client,
                        const DataSourceClientContainerPtr& container,
-                       bool has_cache, const dns::RRClass& rrclass,
-                       const boost::shared_ptr
-                           <isc::datasrc::memory::ZoneTableSegment>&
-                               ztable_segment, const std::string& name);
+                       boost::shared_ptr<internal::CacheConfig> cache_conf,
+                       const dns::RRClass& rrclass,
+                       const std::string& name);
         DataSourceClient* data_src_client_;
         DataSourceClientContainerPtr container_;
 
@@ -422,6 +418,10 @@ public:
         boost::shared_ptr<memory::InMemoryClient> cache_;
         boost::shared_ptr<memory::ZoneTableSegment> ztable_segment_;
         std::string name_;
+    private:
+        // this is kept private for now.  When it needs to be accessed,
+        // we'll add a read-only getter method.
+        boost::shared_ptr<internal::CacheConfig> cache_conf_;
     };
 
     /// \brief The collection of data sources.
@@ -441,6 +441,13 @@ public:
     /// Also, derived classes could want to create the data source clients
     /// in a different way, though inheriting this class is not recommended.
     ///
+    /// Some types of data sources can be internal to the \c ClientList
+    /// implementation and do not require a corresponding dynamic module
+    /// loaded via \c DataSourceClientContainer.  In such a case, this method
+    /// simply returns a pair of null pointers.  It will help the caller reduce
+    /// type dependent processing.  Currently, "MasterFiles" is considered to
+    /// be this type of data sources.
+    ///
     /// The parameters are the same as of the constructor.
     /// \return Pair containing both the data source client and the container.
     ///     The container might be NULL in the derived class, it is

+ 6 - 6
src/lib/datasrc/datasrc_messages.mes

@@ -321,12 +321,12 @@ not contain RRs the requested type.  AN NXRRSET indication is returned.
 A debug message indicating that a query for the given name and RR type is being
 processed.
 
-% DATASRC_LIST_NOT_CACHED zone %1/%2 not cached, cache disabled globally. Will not be available.
-The process disabled caching of RR data completely. However, the given zone
-is provided as a master file and it can be served from memory cache only.
-Therefore, the zone will not be available for this process. If this is
-a problem, you should move the zone to some database backend (sqlite3, for
-example) and use it from there.
+% DATASRC_LIST_NOT_CACHED zones in data source %1 for class %2 not cached, cache disabled globally. Will not be available.
+The process disabled caching of RR data completely. However, this data source
+is provided from a master file and it can be served from memory cache only.
+Therefore, the entire data source will not be available for this process. If
+this is a problem, you should configure the zones of that data source to some
+database backend (sqlite3, for example) and use it from there.
 
 % DATASRC_LOAD_FROM_FILE_ERROR Error loading zone %1: %2
 An error was found in the zone data when it was being loaded from a

+ 11 - 7
src/lib/datasrc/memory/zone_table_segment.cc

@@ -15,6 +15,8 @@
 #include <datasrc/memory/zone_table_segment.h>
 #include <datasrc/memory/zone_table_segment_local.h>
 
+#include <string>
+
 using namespace isc::dns;
 
 namespace isc {
@@ -22,13 +24,15 @@ namespace datasrc {
 namespace memory {
 
 ZoneTableSegment*
-ZoneTableSegment::create(const isc::data::Element&, const RRClass& rrclass) {
-    /// FIXME: For now, we always return ZoneTableSegmentLocal. This
-    /// should be updated eventually to parse the passed Element
-    /// argument and construct a corresponding ZoneTableSegment
-    /// implementation.
-
-    return (new ZoneTableSegmentLocal(rrclass));
+ZoneTableSegment::create(const RRClass& rrclass, const std::string& type) {
+    // This will be a few sequences of if-else and hardcoded.  Not really
+    // sophisticated, but we don't expect to have too many types at the moment.
+    // Until that it becomes a real issue we won't be too smart.
+    if (type == "local") {
+        return (new ZoneTableSegmentLocal(rrclass));
+    }
+    isc_throw(UnknownSegmentType, "Zone table segment type not supported: "
+              << type);
 }
 
 void

+ 27 - 21
src/lib/datasrc/memory/zone_table_segment.h

@@ -15,15 +15,20 @@
 #ifndef ZONE_TABLE_SEGMENT_H
 #define ZONE_TABLE_SEGMENT_H
 
+#include <exceptions/exceptions.h>
+
 #include <dns/rrclass.h>
+
 #include <datasrc/memory/zone_table.h>
-#include "load_action.h"
+#include <datasrc/memory/load_action.h>
+
 #include <cc/data.h>
 #include <util/memory_segment.h>
 
 #include <boost/interprocess/offset_ptr.hpp>
 
-#include <stdlib.h>
+#include <cstdlib>
+#include <string>
 
 namespace isc {
 // Some forward declarations
@@ -35,6 +40,15 @@ namespace datasrc {
 namespace memory {
 class ZoneWriter;
 
+/// \brief Exception thrown when unknown or unsupported type of zone table
+/// segment is specified.
+class UnknownSegmentType : public Exception {
+public:
+    UnknownSegmentType(const char* file, size_t line, const char* what) :
+        Exception(file, line, what)
+    {}
+};
+
 /// \brief Memory-management independent entry point that contains a
 /// pointer to a zone table in memory.
 ///
@@ -97,26 +111,14 @@ public:
     /// dynamically-allocated object. The caller is responsible for
     /// destroying it with \c ZoneTableSegment::destroy().
     ///
-    /// FIXME: For now, we always return ZoneTableSegmentLocal
-    /// regardless of the passed \c config.
+    /// \throw UnknownSegmentType The memory segment type specified in
+    /// \c config is not known or not supported in this implementation.
     ///
-    /// \param config The configuration based on which a derived object
-    ///               is returned.
-    /// \return Returns a ZoneTableSegment object
-    static ZoneTableSegment* create(const isc::data::Element& config,
-                                    const isc::dns::RRClass& rrclass);
-
-    /// \brief Temporary/Testing version of create.
-    ///
-    /// This exists as a temporary solution during the migration phase
-    /// towards using the ZoneTableSegment. It doesn't take a config,
-    /// but a memory segment instead. If you can, you should use the
-    /// other version, this one will be gone soon.
-    ///
-    /// \param segment The memory segment to use.
-    /// \return Returns a new ZoneTableSegment object.
-    /// \todo Remove this method.
-    static ZoneTableSegment* create(isc::util::MemorySegment& segment);
+    /// \param rrclass The RR class of the zones to be maintained in the table.
+    /// \param type The memory segment type used for the zone table segment.
+    /// \return Returns a ZoneTableSegment object of the specified type.
+    static ZoneTableSegment* create(const isc::dns::RRClass& rrclass,
+                                    const std::string& type);
 
     /// \brief Destroy a ZoneTableSegment
     ///
@@ -147,3 +149,7 @@ public:
 } // namespace isc
 
 #endif // ZONE_TABLE_SEGMENT_H
+
+// Local Variables:
+// mode: c++
+// End:

+ 0 - 50
src/lib/datasrc/static_datasrc.h

@@ -1,50 +0,0 @@
-// Copyright (C) 2013  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_STATIC_H
-#define DATASRC_STATIC_H
-
-#include <datasrc/database.h>
-#include <cc/data.h>
-
-#include <string>
-
-namespace isc {
-namespace datasrc {
-
-/// \brief Creates an instance of the static datasource client
-///
-/// Currently the configuration passed here must be a StringElement,
-/// containing the path to a zone file for the BIND./CH zone.
-///
-/// \param config The configuration for the datasource instance (see above)
-/// \param error This string will be set to an error message if an error occurs
-///              during initialization
-/// \return An instance of the static datasource client, or NULL if there was
-///         an error
-extern "C" DataSourceClient* createInstance(isc::data::ConstElementPtr config,
-                                            std::string& error);
-
-/// \brief Destroy the instance created by createInstance()
-extern "C" void destroyInstance(DataSourceClient* instance);
-
-}
-}
-
-#endif  // DATASRC_STATIC_H
-
-// Local Variables:
-// mode: c++
-// End:

+ 0 - 68
src/lib/datasrc/static_datasrc_link.cc

@@ -1,68 +0,0 @@
-// 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 "client.h"
-#include "static_datasrc.h"
-#include <datasrc/memory/memory_client.h>
-#include <datasrc/memory/zone_table_segment.h>
-
-#include <cc/data.h>
-#include <dns/rrclass.h>
-
-#include <memory>
-#include <exception>
-
-using namespace isc::data;
-using namespace isc::dns;
-using namespace boost;
-using namespace std;
-
-namespace isc {
-namespace datasrc {
-
-DataSourceClient*
-createInstance(ConstElementPtr config, string& error) {
-    try {
-        // FIXME: Fix the config that should be passed to
-        // ZoneTableSegment::create() when it actually uses the config
-        // to do something.
-        shared_ptr<memory::ZoneTableSegment> ztable_segment(
-            memory::ZoneTableSegment::create(isc::data::NullElement(),
-                                             RRClass::CH()));
-        // Create the data source
-        auto_ptr<memory::InMemoryClient> client
-            (new memory::InMemoryClient(ztable_segment, RRClass::CH()));
-
-        // Fill it with data
-        const string path(config->stringValue());
-        client->load(Name("BIND"), path);
-
-        return (client.release());
-    }
-    catch (const std::exception& e) {
-        error = e.what();
-    }
-    catch (...) {
-        error = "Unknown exception";
-    }
-    return (NULL);
-}
-
-void
-destroyInstance(DataSourceClient* instance) {
-    delete instance;
-}
-
-}
-}

+ 2 - 0
src/lib/datasrc/tests/Makefile.am

@@ -48,6 +48,7 @@ common_ldadd += $(GTEST_LDADD) $(SQLITE_LIBS)
 run_unittests_SOURCES = $(common_sources)
 
 run_unittests_SOURCES += test_client.h test_client.cc
+run_unittests_SOURCES += mock_client.h mock_client.cc
 run_unittests_SOURCES += logger_unittest.cc
 run_unittests_SOURCES += client_unittest.cc
 run_unittests_SOURCES += database_unittest.h database_unittest.cc
@@ -58,6 +59,7 @@ run_unittests_SOURCES += faked_nsec3.h faked_nsec3.cc
 run_unittests_SOURCES += client_list_unittest.cc
 run_unittests_SOURCES += master_loader_callbacks_test.cc
 run_unittests_SOURCES += zone_loader_unittest.cc
+run_unittests_SOURCES += cache_config_unittest.cc
 
 # We need the actual module implementation in the tests (they are not part
 # of libdatasrc)

+ 241 - 0
src/lib/datasrc/tests/cache_config_unittest.cc

@@ -0,0 +1,241 @@
+// Copyright (C) 2013  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 <datasrc/cache_config.h>
+#include <datasrc/tests/mock_client.h>
+
+#include <cc/data.h>
+#include <dns/name.h>
+
+#include <gtest/gtest.h>
+
+#include <iterator>             // for std::distance
+
+using namespace isc::datasrc;
+using namespace isc::data;
+using namespace isc::dns;
+using isc::datasrc::unittest::MockDataSourceClient;
+using isc::datasrc::internal::CacheConfig;
+using isc::datasrc::internal::CacheConfigError;
+
+namespace {
+
+const char* zones[] = {
+    "example.org.",
+    "example.com.",
+    NULL
+};
+
+class CacheConfigTest : public ::testing::Test {
+protected:
+    CacheConfigTest() :
+        mock_client_(zones),
+        master_config_(Element::fromJSON(
+                           "{\"cache-enable\": true,"
+                           " \"params\": "
+                           "  {\".\": \"" TEST_DATA_DIR "/root.zone\"}"
+                           "}")),
+        mock_config_(Element::fromJSON("{\"cache-enable\": true,"
+                                       " \"cache-zones\": [\".\"]}"))
+    {}
+
+    MockDataSourceClient mock_client_;
+    const ConstElementPtr master_config_; // valid config for MasterFiles
+    const ConstElementPtr mock_config_; // valid config for MasterFiles
+};
+
+size_t
+countZones(const CacheConfig& cache_config) {
+    return (std::distance(cache_config.begin(), cache_config.end()));
+}
+
+TEST_F(CacheConfigTest, constructMasterFiles) {
+    // A simple case: configuring a MasterFiles table with a single zone
+    const CacheConfig cache_conf("MasterFiles", 0, *master_config_, true);
+    EXPECT_EQ(1, countZones(cache_conf));
+
+    // With multiple zones.  Note that the constructor doesn't check if the
+    // file exists, so they can be anything.
+    const ConstElementPtr config_elem_multi(
+        Element::fromJSON("{\"cache-enable\": true,"
+                          " \"params\": "
+                          "{\"example.com\": \"file1\","
+                          " \"example.org\": \"file2\","
+                          " \"example.info\": \"file3\"}"
+                          "}"));
+    EXPECT_EQ(3, countZones(CacheConfig("MasterFiles", 0, *config_elem_multi,
+                                        true)));
+
+    // A bit unusual, but acceptable case: empty parameters, so no zones.
+    EXPECT_EQ(0, countZones(
+                  CacheConfig("MasterFiles", 0,
+                              *Element::fromJSON("{\"cache-enable\": true,"
+                                                 " \"params\": {}}"), true)));
+}
+
+TEST_F(CacheConfigTest, badConstructMasterFiles) {
+    // no "params"
+    EXPECT_THROW(CacheConfig("MasterFiles", 0,
+                             *Element::fromJSON("{\"cache-enable\": true}"),
+                             true),
+                 isc::data::TypeError);
+
+    // no "cache-enable"
+    EXPECT_THROW(CacheConfig("MasterFiles", 0,
+                             *Element::fromJSON("{\"params\": {}}"), true),
+                 CacheConfigError);
+    // cache disabled for MasterFiles
+    EXPECT_THROW(CacheConfig("MasterFiles", 0,
+                             *Element::fromJSON("{\"cache-enable\": false,"
+                                                " \"params\": {}}"), true),
+                 CacheConfigError);
+    // cache enabled but not "allowed"
+    EXPECT_THROW(CacheConfig("MasterFiles", 0,
+                             *Element::fromJSON("{\"cache-enable\": false,"
+                                                " \"params\": {}}"), false),
+                 CacheConfigError);
+    // type error for cache-enable
+    EXPECT_THROW(CacheConfig("MasterFiles", 0,
+                             *Element::fromJSON("{\"cache-enable\": 1,"
+                                                " \"params\": {}}"), true),
+                 isc::data::TypeError);
+
+    // "params" is not a map
+    EXPECT_THROW(CacheConfig("MasterFiles", 0,
+                             *Element::fromJSON("{\"cache-enable\": true,"
+                                                " \"params\": []}"), true),
+                 isc::data::TypeError);
+
+    // bogus zone name
+    const ConstElementPtr bad_config(Element::fromJSON(
+                                         "{\"cache-enable\": true,"
+                                         " \"params\": "
+                                         "{\"bad..name\": \"file1\"}}"));
+    EXPECT_THROW(CacheConfig("MasterFiles", 0, *bad_config, true),
+                 isc::dns::EmptyLabel);
+
+    // file name is not a string
+    const ConstElementPtr bad_config2(Element::fromJSON(
+                                          "{\"cache-enable\": true,"
+                                          " \"params\": {\".\": 1}}"));
+    EXPECT_THROW(CacheConfig("MasterFiles", 0, *bad_config2, true),
+                 isc::data::TypeError);
+
+    // Specify data source client (must be null for MasterFiles)
+    EXPECT_THROW(CacheConfig("MasterFiles", &mock_client_,
+                             *Element::fromJSON("{\"cache-enable\": true,"
+                                                " \"params\": {}}"), true),
+                 isc::InvalidParameter);
+}
+
+TEST_F(CacheConfigTest, constructWithMock) {
+    // Performing equivalent set of tests as constructMasterFiles
+
+    // Configure with a single zone.
+    const CacheConfig cache_conf("mock", &mock_client_, *mock_config_, true);
+    EXPECT_EQ(1, countZones(cache_conf));
+    EXPECT_TRUE(cache_conf.isEnabled());
+
+    // Configure with multiple zones.
+    const ConstElementPtr config_elem_multi(
+        Element::fromJSON("{\"cache-enable\": true,"
+                          " \"cache-zones\": "
+                          "[\"example.com\", \"example.org\",\"example.info\"]"
+                          "}"));
+    EXPECT_EQ(3, countZones(CacheConfig("mock", &mock_client_,
+                                        *config_elem_multi, true)));
+
+    // Empty
+    EXPECT_EQ(0, countZones(
+                  CacheConfig("mock", &mock_client_,
+                              *Element::fromJSON("{\"cache-enable\": true,"
+                                                 " \"cache-zones\": []}"),
+                              true)));
+
+    // disabled.  value of cache-zones are ignored.
+    const ConstElementPtr config_elem_disabled(
+        Element::fromJSON("{\"cache-enable\": false,"
+                          " \"cache-zones\": [\"example.com\"]}"));
+    EXPECT_FALSE(CacheConfig("mock", &mock_client_, *config_elem_disabled,
+                             true).isEnabled());
+    // enabled but not "allowed".  same effect.
+    EXPECT_FALSE(CacheConfig("mock", &mock_client_,
+                             *Element::fromJSON("{\"cache-enable\": true,"
+                                                " \"cache-zones\": []}"),
+                             false).isEnabled());
+}
+
+TEST_F(CacheConfigTest, badConstructWithMock) {
+    // no "cache-zones" (may become valid in future, but for now "notimp")
+    EXPECT_THROW(CacheConfig("mock", &mock_client_,
+                             *Element::fromJSON("{\"cache-enable\": true}"),
+                             true),
+                 isc::NotImplemented);
+
+    // "cache-zones" is not a list
+    EXPECT_THROW(CacheConfig("mock", &mock_client_,
+                             *Element::fromJSON("{\"cache-enable\": true,"
+                                                " \"cache-zones\": {}}"),
+                             true),
+                 isc::data::TypeError);
+
+    // "cache-zone" entry is not a string
+    EXPECT_THROW(CacheConfig("mock", &mock_client_,
+                             *Element::fromJSON("{\"cache-enable\": true,"
+                                                " \"cache-zones\": [1]}"),
+                             true),
+                 isc::data::TypeError);
+
+    // bogus zone name
+    const ConstElementPtr bad_config(Element::fromJSON(
+                                         "{\"cache-enable\": true,"
+                                         " \"cache-zones\": [\"bad..\"]}"));
+    EXPECT_THROW(CacheConfig("mock", &mock_client_, *bad_config, true),
+                 isc::dns::EmptyLabel);
+
+    // duplicate zone name (note that comparison is case insensitive)
+    const ConstElementPtr dup_config(Element::fromJSON(
+                                         "{\"cache-enable\": true,"
+                                         " \"cache-zones\": "
+                                         " [\"example\", \"EXAMPLE\"]}"));
+    EXPECT_THROW(CacheConfig("mock", &mock_client_, *dup_config, true),
+                 CacheConfigError);
+
+    // datasrc is null
+    EXPECT_THROW(CacheConfig("mock", 0, *mock_config_, true),
+                 isc::InvalidParameter);
+}
+
+TEST_F(CacheConfigTest, getSegmentType) {
+    // Default type
+    EXPECT_EQ("local",
+              CacheConfig("MasterFiles", 0,
+                          *master_config_, true).getSegmentType());
+
+    // If we explicitly configure it, that value should be used.
+    ConstElementPtr config(Element::fromJSON("{\"cache-enable\": true,"
+                                             " \"cache-type\": \"mapped\","
+                                             " \"params\": {}}" ));
+    EXPECT_EQ("mapped",
+              CacheConfig("MasterFiles", 0, *config, true).getSegmentType());
+
+    // Wrong types: should be rejected at construction time
+    ConstElementPtr badconfig(Element::fromJSON("{\"cache-enable\": true,"
+                                                " \"cache-type\": 1,"
+                                                " \"params\": {}}"));
+    EXPECT_THROW(CacheConfig("MasterFiles", 0, *badconfig, true),
+                 isc::data::TypeError);
+}
+
+}

+ 10 - 160
src/lib/datasrc/tests/client_list_unittest.cc

@@ -21,6 +21,8 @@
 #include <datasrc/memory/zone_finder.h>
 #include <datasrc/memory/zone_writer.h>
 
+#include <datasrc/tests/mock_client.h>
+
 #include <dns/rrclass.h>
 #include <dns/rrttl.h>
 #include <dns/rdataclass.h>
@@ -33,6 +35,7 @@
 #include <fstream>
 
 using namespace isc::datasrc;
+using isc::datasrc::unittest::MockDataSourceClient;
 using isc::datasrc::memory::InMemoryClient;
 using isc::datasrc::memory::ZoneTableSegment;
 using isc::datasrc::memory::InMemoryZoneFinder;
@@ -45,162 +48,6 @@ using namespace std;
 
 namespace {
 
-// A test data source. It pretends it has some zones.
-class MockDataSourceClient : public DataSourceClient {
-public:
-    class Finder : public ZoneFinder {
-    public:
-        Finder(const Name& origin) :
-            origin_(origin)
-        {}
-        Name getOrigin() const { return (origin_); }
-        // The rest is not to be called, so just have them
-        RRClass getClass() const {
-            isc_throw(isc::NotImplemented, "Not implemented");
-        }
-        shared_ptr<Context> find(const Name&, const RRType&,
-                                 const FindOptions)
-        {
-            isc_throw(isc::NotImplemented, "Not implemented");
-        }
-        shared_ptr<Context> findAll(const Name&,
-                                    vector<ConstRRsetPtr>&,
-                                    const FindOptions)
-        {
-            isc_throw(isc::NotImplemented, "Not implemented");
-        }
-        FindNSEC3Result findNSEC3(const Name&, bool) {
-            isc_throw(isc::NotImplemented, "Not implemented");
-        }
-    private:
-        Name origin_;
-    };
-    class Iterator : public ZoneIterator {
-    public:
-        Iterator(const Name& origin, bool include_a) :
-            origin_(origin),
-            soa_(new RRset(origin_, 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));
-            rrsets_.push_back(soa_);
-
-            RRsetPtr rrset(new RRset(origin_, RRClass::IN(), RRType::NS(),
-                                     RRTTL(3600)));
-            rrset->addRdata(rdata::generic::NS(Name::ROOT_NAME()));
-            rrsets_.push_back(rrset);
-
-            if (include_a) {
-                 // Dummy A rrset. This is used for checking zone data
-                 // after reload.
-                 rrset.reset(new RRset(Name("tstzonedata").concatenate(origin_),
-                                       RRClass::IN(), RRType::A(),
-                                       RRTTL(3600)));
-                 rrset->addRdata(rdata::in::A("192.0.2.1"));
-                 rrsets_.push_back(rrset);
-            }
-
-            rrsets_.push_back(ConstRRsetPtr());
-
-            it_ = rrsets_.begin();
-        }
-        virtual isc::dns::ConstRRsetPtr getNextRRset() {
-            ConstRRsetPtr result = *it_;
-            ++it_;
-            return (result);
-        }
-        virtual isc::dns::ConstRRsetPtr getSOA() const {
-            return (soa_);
-        }
-    private:
-        const Name origin_;
-        const RRsetPtr soa_;
-        std::vector<ConstRRsetPtr> rrsets_;
-        std::vector<ConstRRsetPtr>::const_iterator it_;
-    };
-    // Constructor from a list of zones.
-    MockDataSourceClient(const char* zone_names[]) :
-        have_a_(true), use_baditerator_(true)
-    {
-        for (const char** zone(zone_names); *zone; ++zone) {
-            zones.insert(Name(*zone));
-        }
-    }
-    // Constructor from configuration. The list of zones will be empty, but
-    // it will keep the configuration inside for further inspection.
-    MockDataSourceClient(const string& type,
-                         const ConstElementPtr& configuration) :
-        type_(type),
-        configuration_(configuration),
-        have_a_(true), use_baditerator_(true)
-    {
-        EXPECT_NE("MasterFiles", type) << "MasterFiles is a special case "
-            "and it never should be created as a data source client";
-        if (configuration_->getType() == Element::list) {
-            for (size_t i(0); i < configuration_->size(); ++i) {
-                zones.insert(Name(configuration_->get(i)->stringValue()));
-            }
-        }
-    }
-    virtual FindResult findZone(const Name& name) const {
-        if (zones.empty()) {
-            return (FindResult(result::NOTFOUND, ZoneFinderPtr()));
-        }
-        set<Name>::const_iterator it(zones.upper_bound(name));
-        if (it == zones.begin()) {
-            return (FindResult(result::NOTFOUND, ZoneFinderPtr()));
-        }
-        --it;
-        NameComparisonResult compar(it->compare(name));
-        const ZoneFinderPtr finder(new Finder(*it));
-        switch (compar.getRelation()) {
-            case NameComparisonResult::EQUAL:
-                return (FindResult(result::SUCCESS, finder));
-            case NameComparisonResult::SUPERDOMAIN:
-                return (FindResult(result::PARTIALMATCH, finder));
-            default:
-                return (FindResult(result::NOTFOUND, ZoneFinderPtr()));
-        }
-    }
-    // These methods are not used. They just need to be there to have
-    // complete vtable.
-    virtual ZoneUpdaterPtr getUpdater(const Name&, bool, bool) const {
-        isc_throw(isc::NotImplemented, "Not implemented");
-    }
-    virtual pair<ZoneJournalReader::Result, ZoneJournalReaderPtr>
-        getJournalReader(const Name&, uint32_t, uint32_t) const
-    {
-        isc_throw(isc::NotImplemented, "Not implemented");
-    }
-    virtual ZoneIteratorPtr getIterator(const Name& name, bool) const {
-        if (use_baditerator_ && name == Name("noiter.org")) {
-            isc_throw(isc::NotImplemented, "Asked not to be implemented");
-        } else if (use_baditerator_ && name == Name("null.org")) {
-            return (ZoneIteratorPtr());
-        } else {
-            FindResult result(findZone(name));
-            if (result.code == isc::datasrc::result::SUCCESS) {
-                return (ZoneIteratorPtr(new Iterator(name, have_a_)));
-            } else {
-                isc_throw(DataSourceError, "No such zone");
-            }
-        }
-    }
-    void disableA() { have_a_ = false; }
-    void disableBadIterator() { use_baditerator_ = false; }
-    const string type_;
-    const ConstElementPtr configuration_;
-private:
-    set<Name> zones;
-    bool have_a_; // control the iterator behavior whether to include A record
-    bool use_baditerator_; // whether to use bogus zone iterators for tests
-};
-
-
 // The test version is the same as the normal version. We, however, add
 // some methods to dig directly in the internals, for the tests.
 class TestedList : public ConfigurableClientList {
@@ -219,6 +66,9 @@ public:
         if (type == "error") {
             isc_throw(DataSourceError, "The error data source type");
         }
+        if (type == "MasterFiles") {
+            return (DataSourcePair(0, DataSourceClientContainerPtr()));
+        }
         shared_ptr<MockDataSourceClient>
             ds(new MockDataSourceClient(type, configuration));
         // Make sure it is deleted when the test list is deleted.
@@ -269,8 +119,7 @@ public:
             "   \"params\": [\"example.org\", \"example.com\", "
             "                \"noiter.org\", \"null.org\"]"
             "}]")),
-        config_(Element::fromJSON("{}")),
-        ztable_segment_(ZoneTableSegment::create(*config_, rrclass_))
+        ztable_segment_(ZoneTableSegment::create(rrclass_, "local"))
     {
         for (size_t i(0); i < ds_count; ++ i) {
             shared_ptr<MockDataSourceClient>
@@ -278,7 +127,8 @@ public:
             ds_.push_back(ds);
             ds_info_.push_back(ConfigurableClientList::DataSourceInfo(
                                    ds.get(), DataSourceClientContainerPtr(),
-                                   false, rrclass_, ztable_segment_, ""));
+                                   boost::shared_ptr<internal::CacheConfig>(),
+                                   rrclass_, ""));
         }
     }
 
@@ -382,7 +232,7 @@ public:
     const ClientList::FindResult negative_result_;
     vector<shared_ptr<MockDataSourceClient> > ds_;
     vector<ConfigurableClientList::DataSourceInfo> ds_info_;
-    const ConstElementPtr config_elem_, config_elem_zones_, config_;
+    const ConstElementPtr config_elem_, config_elem_zones_;
     shared_ptr<ZoneTableSegment> ztable_segment_;
 };
 

+ 0 - 52
src/lib/datasrc/tests/factory_unittest.cc

@@ -28,8 +28,6 @@ using namespace isc::datasrc;
 using namespace isc::data;
 
 std::string SQLITE_DBFILE_EXAMPLE_ORG = TEST_DATA_DIR "/example.org.sqlite3";
-const std::string STATIC_DS_FILE = TEST_DATA_DIR "/static.zone";
-const std::string STATIC_BAD_DS_FILE = TEST_DATA_DIR "/static-bad.zone";
 const std::string ROOT_ZONE_FILE = TEST_DATA_DIR "/root.zone";
 
 namespace {
@@ -166,55 +164,5 @@ TEST(FactoryTest, badType) {
                                            DataSourceError);
 }
 
-// Check the static data source can be loaded.
-TEST(FactoryTest, staticDS) {
-    // The only configuration is the file to load.
-    const ConstElementPtr config(new StringElement(STATIC_DS_FILE));
-    // Get the data source
-    DataSourceClientContainer dsc("static", config);
-    // And try getting something out to see if it really works.
-    DataSourceClient::FindResult
-        result(dsc.getInstance().findZone(isc::dns::Name("BIND")));
-    ASSERT_EQ(result::SUCCESS, result.code);
-    EXPECT_EQ(isc::dns::Name("BIND"), result.zone_finder->getOrigin());
-    EXPECT_EQ(isc::dns::RRClass::CH(), result.zone_finder->getClass());
-    const isc::dns::ConstRRsetPtr
-        version(result.zone_finder->find(isc::dns::Name("VERSION.BIND"),
-                                         isc::dns::RRType::TXT())->rrset);
-    ASSERT_NE(isc::dns::ConstRRsetPtr(), version);
-    EXPECT_EQ(isc::dns::Name("VERSION.BIND"), version->getName());
-    EXPECT_EQ(isc::dns::RRClass::CH(), version->getClass());
-    EXPECT_EQ(isc::dns::RRType::TXT(), version->getType());
-}
-
-// Check that file not containing BIND./CH is rejected
-TEST(FactoryTest, staticDSBadFile) {
-    // The only configuration is the file to load.
-    const ConstElementPtr config(new StringElement(STATIC_BAD_DS_FILE));
-    // See it does not want the file
-    EXPECT_THROW(DataSourceClientContainer("static", config), DataSourceError);
-}
-
-// Check that some bad configs are rejected
-TEST(FactoryTest, staticDSBadConfig) {
-    const char* configs[] = {
-        // The file does not exist
-        "\"/does/not/exist\"",
-        // Bad types
-        "null",
-        "42",
-        "{}",
-        "[]",
-        "true",
-        NULL
-    };
-    for (const char** config(configs); *config; ++config) {
-        SCOPED_TRACE(*config);
-        EXPECT_THROW(DataSourceClientContainer("static",
-                                               Element::fromJSON(*config)),
-                     DataSourceError);
-    }
-}
-
 } // end anonymous namespace
 

+ 5 - 2
src/lib/datasrc/tests/memory/zone_table_segment_unittest.cc

@@ -31,8 +31,7 @@ namespace {
 class ZoneTableSegmentTest : public ::testing::Test {
 protected:
     ZoneTableSegmentTest() :
-        ztable_segment_(ZoneTableSegment::create(isc::data::NullElement(),
-                                                 RRClass::IN()))
+        ztable_segment_(ZoneTableSegment::create(RRClass::IN(), "local"))
     {}
 
     void TearDown() {
@@ -47,6 +46,10 @@ protected:
 TEST_F(ZoneTableSegmentTest, create) {
     // By default, a local zone table segment is created.
     EXPECT_NE(static_cast<void*>(NULL), ztable_segment_);
+
+    // Unknown types of segment are rejected.
+    EXPECT_THROW(ZoneTableSegment::create(RRClass::IN(), "unknown"),
+                 UnknownSegmentType);
 }
 
 // Helper function to check const and non-const methods.

+ 1 - 6
src/lib/datasrc/tests/memory/zone_writer_unittest.cc

@@ -16,7 +16,6 @@
 #include <datasrc/memory/zone_table_segment_local.h>
 #include <datasrc/memory/zone_data.h>
 
-#include <cc/data.h>
 #include <dns/rrclass.h>
 #include <dns/name.h>
 
@@ -38,11 +37,7 @@ class TestException {};
 class ZoneWriterLocalTest : public ::testing::Test {
 public:
     ZoneWriterLocalTest() :
-        // FIXME: The NullElement probably isn't the best one, but we don't
-        // know how the config will look, so it just fills the argument
-        // (which is currently ignored)
-        segment_(ZoneTableSegment::create(isc::data::NullElement(),
-                                          RRClass::IN())),
+        segment_(ZoneTableSegment::create(RRClass::IN(), "local")),
         writer_(new
             ZoneWriterLocal(dynamic_cast<ZoneTableSegmentLocal*>(segment_.
                                                                  get()),

+ 197 - 0
src/lib/datasrc/tests/mock_client.cc

@@ -0,0 +1,197 @@
+// 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 <datasrc/tests/mock_client.h>
+#include <datasrc/client.h>
+#include <datasrc/result.h>
+#include <datasrc/zone_iterator.h>
+#include <datasrc/data_source.h>
+
+#include <dns/name.h>
+#include <dns/rrclass.h>
+#include <dns/rrset.h>
+#include <dns/rrttl.h>
+#include <dns/rdataclass.h>
+
+#include <cc/data.h>
+
+#include <gtest/gtest.h>
+
+#include <boost/shared_ptr.hpp>
+
+#include <vector>
+#include <set>
+#include <string>
+
+using namespace isc::dns;
+
+using boost::shared_ptr;
+using std::vector;
+using std::string;
+using std::set;
+
+namespace isc {
+namespace datasrc {
+namespace unittest {
+
+namespace {
+class Finder : public ZoneFinder {
+public:
+    Finder(const Name& origin) :
+        origin_(origin)
+    {}
+    Name getOrigin() const { return (origin_); }
+    // The rest is not to be called, so just have them
+    RRClass getClass() const {
+        isc_throw(isc::NotImplemented, "Not implemented");
+    }
+    shared_ptr<Context> find(const Name&, const RRType&,
+                             const FindOptions)
+    {
+        isc_throw(isc::NotImplemented, "Not implemented");
+    }
+    shared_ptr<Context> findAll(const Name&,
+                                vector<ConstRRsetPtr>&,
+                                const FindOptions)
+    {
+        isc_throw(isc::NotImplemented, "Not implemented");
+    }
+    FindNSEC3Result findNSEC3(const Name&, bool) {
+        isc_throw(isc::NotImplemented, "Not implemented");
+    }
+private:
+    Name origin_;
+};
+
+class Iterator : public ZoneIterator {
+public:
+    Iterator(const Name& origin, bool include_a) :
+        origin_(origin),
+        soa_(new RRset(origin_, 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));
+        rrsets_.push_back(soa_);
+
+        RRsetPtr rrset(new RRset(origin_, RRClass::IN(), RRType::NS(),
+                                 RRTTL(3600)));
+        rrset->addRdata(rdata::generic::NS(Name::ROOT_NAME()));
+        rrsets_.push_back(rrset);
+
+        if (include_a) {
+            // Dummy A rrset. This is used for checking zone data
+            // after reload.
+            rrset.reset(new RRset(Name("tstzonedata").concatenate(origin_),
+                                  RRClass::IN(), RRType::A(),
+                                  RRTTL(3600)));
+            rrset->addRdata(rdata::in::A("192.0.2.1"));
+            rrsets_.push_back(rrset);
+        }
+
+        rrsets_.push_back(ConstRRsetPtr());
+
+        it_ = rrsets_.begin();
+    }
+    virtual isc::dns::ConstRRsetPtr getNextRRset() {
+        ConstRRsetPtr result = *it_;
+        ++it_;
+        return (result);
+    }
+    virtual isc::dns::ConstRRsetPtr getSOA() const {
+        return (soa_);
+    }
+private:
+    const Name origin_;
+    const RRsetPtr soa_;
+    vector<ConstRRsetPtr> rrsets_;
+    vector<ConstRRsetPtr>::const_iterator it_;
+};
+}
+
+// A test data source. It pretends it has some zones.
+
+MockDataSourceClient::MockDataSourceClient(const char* zone_names[]) :
+    have_a_(true), use_baditerator_(true)
+{
+    for (const char** zone(zone_names); *zone; ++zone) {
+        zones.insert(Name(*zone));
+    }
+}
+
+// Constructor from configuration. The list of zones will be empty, but
+// it will keep the configuration inside for further inspection.
+MockDataSourceClient::MockDataSourceClient(
+    const string& type,
+    const data::ConstElementPtr& configuration) :
+    type_(type),
+    configuration_(configuration),
+    have_a_(true), use_baditerator_(true)
+{
+    EXPECT_NE("MasterFiles", type) << "MasterFiles is a special case "
+        "and it never should be created as a data source client";
+    if (configuration_->getType() == data::Element::list) {
+        for (size_t i(0); i < configuration_->size(); ++i) {
+            zones.insert(Name(configuration_->get(i)->stringValue()));
+        }
+    }
+}
+
+DataSourceClient::FindResult
+MockDataSourceClient::findZone(const Name& name) const {
+    if (zones.empty()) {
+        return (FindResult(result::NOTFOUND, ZoneFinderPtr()));
+    }
+    set<Name>::const_iterator it(zones.upper_bound(name));
+    if (it == zones.begin()) {
+        return (FindResult(result::NOTFOUND, ZoneFinderPtr()));
+    }
+    --it;
+    NameComparisonResult compar(it->compare(name));
+    const ZoneFinderPtr finder(new Finder(*it));
+    switch (compar.getRelation()) {
+    case NameComparisonResult::EQUAL:
+        return (FindResult(result::SUCCESS, finder));
+    case NameComparisonResult::SUPERDOMAIN:
+        return (FindResult(result::PARTIALMATCH, finder));
+    default:
+        return (FindResult(result::NOTFOUND, ZoneFinderPtr()));
+    }
+}
+
+// These methods are not used. They just need to be there to have
+// complete vtable.
+
+ZoneIteratorPtr
+MockDataSourceClient::getIterator(const Name& name, bool) const {
+    if (use_baditerator_ && name == Name("noiter.org")) {
+        isc_throw(isc::NotImplemented, "Asked not to be implemented");
+    } else if (use_baditerator_ && name == Name("null.org")) {
+        return (ZoneIteratorPtr());
+    } else {
+        FindResult result(findZone(name));
+        if (result.code == isc::datasrc::result::SUCCESS) {
+            return (ZoneIteratorPtr(new Iterator(name, have_a_)));
+        } else {
+            isc_throw(DataSourceError, "No such zone");
+        }
+    }
+}
+
+} // end of unittest
+} // end of datasrc
+} // end of isc

+ 71 - 0
src/lib/datasrc/tests/mock_client.h

@@ -0,0 +1,71 @@
+// 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 <datasrc/client.h>
+
+#include <dns/dns_fwd.h>
+#include <dns/rrset.h>
+
+#include <cc/data.h>
+
+#include <boost/shared_ptr.hpp>
+
+#include <set>
+#include <vector>
+
+namespace isc {
+namespace datasrc {
+namespace unittest {
+
+// A test data source. It pretends it has some zones.
+class MockDataSourceClient : public DataSourceClient {
+public:
+    // Constructor from a list of zones.
+    MockDataSourceClient(const char* zone_names[]);
+
+    // Constructor from configuration. The list of zones will be empty, but
+    // it will keep the configuration inside for further inspection.
+    MockDataSourceClient(const std::string& type,
+                         const data::ConstElementPtr& configuration);
+
+    virtual FindResult findZone(const dns::Name& name) const;
+    // These methods are not used. They just need to be there to have
+    // complete vtable.
+    virtual ZoneUpdaterPtr getUpdater(const dns::Name&, bool, bool) const {
+        isc_throw(isc::NotImplemented, "Not implemented");
+    }
+    virtual std::pair<ZoneJournalReader::Result, ZoneJournalReaderPtr>
+    getJournalReader(const dns::Name&, uint32_t, uint32_t) const
+    {
+        isc_throw(isc::NotImplemented, "Not implemented");
+    }
+    virtual ZoneIteratorPtr getIterator(const dns::Name& name, bool) const;
+    void disableA() { have_a_ = false; }
+    void disableBadIterator() { use_baditerator_ = false; }
+    const std::string type_;
+    const data::ConstElementPtr configuration_;
+
+private:
+    std::set<dns::Name> zones;
+    bool have_a_; // control the iterator behavior whether to include A record
+    bool use_baditerator_; // whether to use bogus zone iterators for tests
+};
+
+} // end of unittest
+} // end of datasrc
+} // end of isc
+
+// Local Variables:
+// mode: c++
+// End:

+ 2 - 4
src/lib/datasrc/tests/zone_finder_context_unittest.cc

@@ -63,11 +63,9 @@ typedef DataSourceClientPtr (*ClientCreator)(RRClass, const Name&);
 
 // Creator for the in-memory client to be tested
 DataSourceClientPtr
-createInMemoryClient(RRClass zclass, const Name& zname)
-{
-    const ElementPtr config(Element::fromJSON("{}"));
+createInMemoryClient(RRClass zclass, const Name& zname) {
     shared_ptr<ZoneTableSegment> ztable_segment(
-        ZoneTableSegment::create(*config, zclass));
+        ZoneTableSegment::create(zclass, "local"));
     shared_ptr<InMemoryClient> client(new InMemoryClient(ztable_segment,
                                                          zclass));
     client->load(zname, TEST_ZONE_FILE);

+ 28 - 14
src/lib/datasrc/tests/zone_loader_unittest.cc

@@ -31,6 +31,7 @@
 #include <boost/shared_ptr.hpp>
 #include <boost/scoped_ptr.hpp>
 #include <boost/foreach.hpp>
+
 #include <string>
 #include <vector>
 
@@ -287,13 +288,26 @@ MockClient::getUpdater(const Name& name, bool replace, bool journaling) const {
 class ZoneLoaderTest : public ::testing::Test {
 protected:
     ZoneLoaderTest() :
-        rrclass_(RRClass::IN()),
-        ztable_segment_(memory::ZoneTableSegment::
-                        create(isc::data::NullElement(), rrclass_)),
-        source_client_(ztable_segment_, rrclass_)
-    {}
+        rrclass_(RRClass::IN())
+    {
+        // Use ROOT_NAME as a placeholder; it will be ignored if filename is
+        // null.
+        prepareSource(Name::ROOT_NAME(), NULL);
+    }
     void prepareSource(const Name& zone, const char* filename) {
-        source_client_.load(zone, string(TEST_DATA_DIR) + "/" + filename);
+        // Cleanup the existing data in the right order
+        source_client_.reset();
+        ztable_segment_.reset();
+
+        // (re)configure zone table, then (re)construct the in-memory client
+        // with it.
+        ztable_segment_.reset(memory::ZoneTableSegment::create(rrclass_,
+                                                               "local"));
+        source_client_.reset(new memory::InMemoryClient(ztable_segment_,
+                                                        rrclass_));
+        if (filename) {
+            source_client_->load(zone, string(TEST_DATA_DIR) + "/" + filename);
+        }
     }
 private:
     const RRClass rrclass_;
@@ -305,7 +319,7 @@ private:
     // But the shared pointer won't let us, will it?
     shared_ptr<memory::ZoneTableSegment> ztable_segment_;
 protected:
-    memory::InMemoryClient source_client_;
+    boost::scoped_ptr<memory::InMemoryClient> source_client_;
     // This one is mocked. It will help us see what is happening inside.
     // Also, mocking it is simpler than setting up an sqlite3 client.
     MockClient destination_client_;
@@ -314,7 +328,7 @@ protected:
 // Use the loader to load an unsigned zone.
 TEST_F(ZoneLoaderTest, copyUnsigned) {
     prepareSource(Name::ROOT_NAME(), "root.zone");
-    ZoneLoader loader(destination_client_, Name::ROOT_NAME(), source_client_);
+    ZoneLoader loader(destination_client_, Name::ROOT_NAME(), *source_client_);
     // It gets the updater directly in the constructor
     ASSERT_EQ(1, destination_client_.provided_updaters_.size());
     EXPECT_EQ(Name::ROOT_NAME(), destination_client_.provided_updaters_[0]);
@@ -355,7 +369,7 @@ TEST_F(ZoneLoaderTest, copyUnsigned) {
 // Try loading incrementally.
 TEST_F(ZoneLoaderTest, copyUnsignedIncremental) {
     prepareSource(Name::ROOT_NAME(), "root.zone");
-    ZoneLoader loader(destination_client_, Name::ROOT_NAME(), source_client_);
+    ZoneLoader loader(destination_client_, Name::ROOT_NAME(), *source_client_);
 
     // Try loading few RRs first.
     loader.loadIncremental(10);
@@ -390,7 +404,7 @@ TEST_F(ZoneLoaderTest, copyUnsignedIncremental) {
 TEST_F(ZoneLoaderTest, copySigned) {
     prepareSource(Name("example.org"), "example.org.nsec3-signed");
     ZoneLoader loader(destination_client_, Name("example.org"),
-                      source_client_);
+                      *source_client_);
     loader.load();
 
     // All the RRs are there, including the ones in NSEC3 namespace
@@ -416,13 +430,13 @@ TEST_F(ZoneLoaderTest, copyMissingDestination) {
     destination_client_.missing_zone_ = true;
     prepareSource(Name::ROOT_NAME(), "root.zone");
     EXPECT_THROW(ZoneLoader(destination_client_, Name::ROOT_NAME(),
-                            source_client_), DataSourceError);
+                            *source_client_), DataSourceError);
 }
 
 // If the source zone does not exist, it throws
 TEST_F(ZoneLoaderTest, copyMissingSource) {
     EXPECT_THROW(ZoneLoader(destination_client_, Name::ROOT_NAME(),
-                            source_client_), DataSourceError);
+                            *source_client_), DataSourceError);
 }
 
 // The class of the source and destination are different
@@ -430,7 +444,7 @@ TEST_F(ZoneLoaderTest, classMismatch) {
     destination_client_.rrclass_ = RRClass::CH();
     prepareSource(Name::ROOT_NAME(), "root.zone");
     EXPECT_THROW(ZoneLoader(destination_client_, Name::ROOT_NAME(),
-                            source_client_), isc::InvalidParameter);
+                            *source_client_), isc::InvalidParameter);
 }
 
 // Load an unsigned zone, all at once
@@ -593,7 +607,7 @@ TEST_F(ZoneLoaderTest, loadCheckWarn) {
 TEST_F(ZoneLoaderTest, copyCheckWarn) {
     prepareSource(Name("example.org"), "checkwarn.zone");
     ZoneLoader loader(destination_client_, Name("example.org"),
-                      source_client_);
+                      *source_client_);
     EXPECT_TRUE(loader.loadIncremental(10));
     // The messages go to the log. We don't have an easy way to examine them.
     // But the zone was committed and contains all 3 RRs

+ 12 - 11
src/lib/python/isc/datasrc/tests/datasrc_test.py

@@ -33,8 +33,11 @@ WRITE_ZONE_DB_FILE = TESTDATA_WRITE_PATH + "rwtest.sqlite3.copied"
 
 READ_ZONE_DB_CONFIG = "{ \"database_file\": \"" + READ_ZONE_DB_FILE + "\" }"
 WRITE_ZONE_DB_CONFIG = "{ \"database_file\": \"" + WRITE_ZONE_DB_FILE + "\"}"
-
-STATIC_ZONE_CONFIG = '"' + TESTDATA_PATH + "static.zone" + '"'
+# In-memory data source must be built via client list.
+INMEMORY_ZONE_CONFIG = '''[{
+   "type": "MasterFiles",
+   "cache-enable": true,
+   "params": {"example.com": "''' + TESTDATA_PATH + 'example.com"}}]'
 
 def add_rrset(rrset_list, name, rrclass, rrtype, ttl, rdatas):
     rrset_to_add = isc.dns.RRset(name, rrclass, rrtype, ttl)
@@ -563,11 +566,10 @@ class DataSrcUpdater(unittest.TestCase):
         self.assertEqual(updater_refs, sys.getrefcount(updater))
 
     def test_two_modules(self):
-        # load two modules, and check if they don't interfere; as the
-        # memory datasource module no longer exists, we check the static
-        # datasource instead (as that uses the memory datasource
-        # anyway).
-        dsc_static = isc.datasrc.DataSourceClient("static", STATIC_ZONE_CONFIG)
+        # load two modules, and check if they don't interfere.
+        clist = isc.datasrc.ConfigurableClientList(isc.dns.RRClass.IN)
+        clist.configure(INMEMORY_ZONE_CONFIG, True)
+        dsc_static = clist.find(isc.dns.Name("example.com"), True, False)[0]
         dsc_sql = isc.datasrc.DataSourceClient("sqlite3", READ_ZONE_DB_CONFIG)
 
         # check if exceptions are working
@@ -724,10 +726,9 @@ class DataSrcUpdater(unittest.TestCase):
         self.assertTrue(dsc.delete_zone(zone_name))
 
     def test_create_zone_not_implemented(self):
-        # As the memory datasource module no longer exists, we check the
-        # static datasource instead (as that uses the memory datasource
-        # anyway).
-        dsc = isc.datasrc.DataSourceClient("static", STATIC_ZONE_CONFIG)
+        clist = isc.datasrc.ConfigurableClientList(isc.dns.RRClass.IN)
+        clist.configure(INMEMORY_ZONE_CONFIG, True)
+        dsc = clist.find(isc.dns.Name("example.com"), True, False)[0]
         self.assertRaises(isc.datasrc.NotImplemented, dsc.create_zone,
                           isc.dns.Name("example.com"))
 

+ 14 - 12
src/lib/python/isc/datasrc/tests/zone_loader_test.py

@@ -34,7 +34,11 @@ ORIG_DB_FILE = TESTDATA_PATH + '/example.com.sqlite3'
 DB_FILE = TESTDATA_WRITE_PATH + '/zoneloadertest.sqlite3'
 DB_CLIENT_CONFIG = '{ "database_file": "' + DB_FILE + '" }'
 DB_SOURCE_CLIENT_CONFIG = '{ "database_file": "' + SOURCE_DB_FILE + '" }'
-STATIC_ZONE_CONFIG = '"' + TESTDATA_PATH + "/static.zone" + '"'
+# In-memory data source must be built via client list.
+INMEMORY_ZONE_CONFIG = '''[{
+   "type": "MasterFiles",
+   "cache-enable": true,
+   "params": {"example.com": "''' + TESTDATA_PATH + '/example.com"}}]'
 
 ORIG_SOA_TXT = 'example.com. 3600 IN SOA master.example.com. ' +\
                'admin.example.com. 1234 3600 1800 2419200 7200\n'
@@ -216,15 +220,11 @@ class ZoneLoaderTests(unittest.TestCase):
                           self.client, zone_name, self.source_client)
 
     def test_no_ds_load_support(self):
-        # As the memory datasource module no longer exists, we check the
-        # static datasource instead (as that uses the memory datasource
-        # anyway).
-        #
-        # This may change in the future, but ATM, the static ds does not
-        # support the API the zone loader uses (it has direct load
-        # calls).
-        inmem_client = isc.datasrc.DataSourceClient('static',
-                                                    STATIC_ZONE_CONFIG);
+        # This may change in the future, but ATM, in-memory ds does not
+        # support the API the zone loader uses.
+        clist = isc.datasrc.ConfigurableClientList(isc.dns.RRClass.IN)
+        clist.configure(INMEMORY_ZONE_CONFIG, True)
+        inmem_client = clist.find(isc.dns.Name("example.com"), True, False)[0]
         self.assertRaises(isc.datasrc.NotImplemented,
                           isc.datasrc.ZoneLoader,
                           inmem_client, self.test_name, self.test_file)
@@ -239,8 +239,10 @@ class ZoneLoaderTests(unittest.TestCase):
         # For ds->ds loading, wrong class is detected upon construction
         # Need a bit of the extended setup for CH source client
         clientlist = isc.datasrc.ConfigurableClientList(isc.dns.RRClass.CH)
-        clientlist.configure('[ { "type": "static", "params": "' +
-                             STATIC_ZONE_FILE +'" } ]', False)
+        clientlist.configure('[ { "type": "MasterFiles", ' +
+                             '"cache-enable": true, ' +
+                             '"params": {"BIND": "' +
+                             STATIC_ZONE_FILE + '" }} ]', True)
         self.source_client, _, _ = clientlist.find(isc.dns.Name("bind."),
                                                    False, False)
         self.assertRaises(isc.dns.InvalidParameter, isc.datasrc.ZoneLoader,