Browse Source

[master] Merge branch 'trac1207'

Jelte Jansen 13 years ago
parent
commit
42d8e0ec9a

+ 3 - 3
src/bin/auth/Makefile.am

@@ -51,9 +51,9 @@ b10_auth_SOURCES += statistics.cc statistics.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
-# is nonportable. When #1207 is done this becomes moot anyway, and the
-# specific workaround is not needed anymore, so we can then remove this
-# line again.
+# is nonportable. This should've been moot after #1207, but there is still
+# one dependency; the in-memory-specific zone loader call is still in
+# auth.
 b10_auth_SOURCES += ${top_srcdir}/src/lib/datasrc/memory_datasrc.cc
 
 nodist_b10_auth_SOURCES = auth_messages.h auth_messages.cc

+ 52 - 143
src/bin/auth/auth_config.cc

@@ -43,22 +43,19 @@ using namespace isc::datasrc;
 using namespace isc::server_common::portconfig;
 
 namespace {
-// Forward declaration
-AuthConfigParser*
-createAuthConfigParser(AuthSrv& server, const std::string& config_id,
-                       bool internal);
-
 /// A derived \c AuthConfigParser class for the "datasources" configuration
 /// identifier.
 class DatasourcesConfig : public AuthConfigParser {
 public:
-    DatasourcesConfig(AuthSrv& server) : server_(server) {}
+    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
@@ -86,137 +83,60 @@ DatasourcesConfig::build(ConstElementPtr config_value) {
             isc_throw(AuthConfigError, "Data source type '" <<
                       datasrc_type->stringValue() << "' already configured");
         }
-        
-        boost::shared_ptr<AuthConfigParser> datasrc_config =
-            boost::shared_ptr<AuthConfigParser>(
-                createAuthConfigParser(server_, string("datasources/") +
-                                       datasrc_type->stringValue(),
-                                       true));
-        datasrc_config->build(datasrc_elem);
-        datasources_.push_back(datasrc_config);
 
-        configured_sources_.insert(datasrc_type->stringValue());
-    }
-}
+        // 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());
+        }
 
-void
-DatasourcesConfig::commit() {
-    // XXX a short term workaround: clear all data sources and then reset
-    // to new ones so that we can remove data sources that don't exist in
-    // the new configuration and have been used in the server.
-    // This could be inefficient and requires knowledge about
-    // server implementation details, and isn't scalable wrt the number of
-    // data source types, and should eventually be improved.
-    // Currently memory data source for class IN is the only possibility.
-    server_.setInMemoryClient(RRClass::IN(), AuthSrv::InMemoryClientPtr());
+        // 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))));
 
-    BOOST_FOREACH(boost::shared_ptr<AuthConfigParser> datasrc_config,
-                  datasources_) {
-        datasrc_config->commit();
+        configured_sources_.insert(datasrc_type->stringValue());
     }
 }
 
-/// A derived \c AuthConfigParser class for the memory type datasource
-/// configuration.  It does not correspond to the configuration syntax;
-/// it's instantiated for internal use.
-class MemoryDatasourceConfig : public AuthConfigParser {
-public:
-    MemoryDatasourceConfig(AuthSrv& server) :
-        server_(server),
-        rrclass_(0)              // XXX: dummy initial value
-    {}
-    virtual void build(ConstElementPtr config_value);
-    virtual void commit() {
-        server_.setInMemoryClient(rrclass_, memory_client_);
-    }
-private:
-    AuthSrv& server_;
-    RRClass rrclass_;
-    AuthSrv::InMemoryClientPtr memory_client_;
-};
-
 void
-MemoryDatasourceConfig::build(ConstElementPtr config_value) {
-    // XXX: apparently we cannot retrieve the default RR class from the
-    // module spec.  As a temporary workaround we hardcode the default value.
-    ConstElementPtr rrclass_elem = config_value->get("class");
-    rrclass_ = RRClass(rrclass_elem ? rrclass_elem->stringValue() : "IN");
-
-    // We'd eventually optimize building zones (in case of reloading) by
-    // selectively loading fresh zones.  Right now we simply check the
-    // RR class is supported by the server implementation.
-    server_.getInMemoryClient(rrclass_);
-    memory_client_ = AuthSrv::InMemoryClientPtr(new InMemoryClient());
-
-    ConstElementPtr zones_config = config_value->get("zones");
-    if (!zones_config) {
-        // XXX: Like the RR class, we cannot retrieve the default value here,
-        // so we assume an empty zone list in this case.
-        return;
-    }
-
-    BOOST_FOREACH(ConstElementPtr zone_config, zones_config->listValue()) {
-        ConstElementPtr origin = zone_config->get("origin");
-        const string origin_txt = origin ? origin->stringValue() : "";
-        if (origin_txt.empty()) {
-            isc_throw(AuthConfigError, "Missing zone origin");
-        }
-        ConstElementPtr file = zone_config->get("file");
-        const string file_txt = file ? file->stringValue() : "";
-        if (file_txt.empty()) {
-            isc_throw(AuthConfigError, "Missing zone file for zone: "
-                      << origin_txt);
-        }
-
-        // We support the traditional text type and SQLite3 backend.  For the
-        // latter we create a client for the underlying SQLite3 data source,
-        // and build the in-memory zone using an iterator of the underlying
-        // zone.
-        ConstElementPtr filetype = zone_config->get("filetype");
-        const string filetype_txt = filetype ? filetype->stringValue() :
-            "text";
-        boost::scoped_ptr<DataSourceClientContainer> container;
-        if (filetype_txt == "sqlite3") {
-            container.reset(new DataSourceClientContainer(
-                                "sqlite3",
-                                Element::fromJSON("{\"database_file\": \"" +
-                                                  file_txt + "\"}")));
-        } else if (filetype_txt != "text") {
-            isc_throw(AuthConfigError, "Invalid filetype for zone "
-                      << origin_txt << ": " << filetype_txt);
-        }
-
-        // Note: we don't want to have such small try-catch blocks for each
-        // specific error.  We may eventually want to introduce some unified
-        // error handling framework as we have more configuration parameters.
-        // See bug #1627 for the relevant discussion.
-        InMemoryZoneFinder* imzf = NULL;
-        try {
-            imzf = new InMemoryZoneFinder(rrclass_, Name(origin_txt));
-        } catch (const isc::dns::NameParserException& ex) {
-            isc_throw(AuthConfigError, "unable to parse zone's origin: " <<
-                      ex.what());
-        }
-
-        boost::shared_ptr<InMemoryZoneFinder> zone_finder(imzf);
-        const result::Result result = memory_client_->addZone(zone_finder);
-        if (result == result::EXIST) {
-            isc_throw(AuthConfigError, "zone "<< origin->str()
-                      << " already exists");
-        }
-
-        /*
-         * TODO: Once we have better reloading of configuration (something
-         * else than throwing everything away and loading it again), we will
-         * need the load method to be split into some kind of build and
-         * commit/abort parts.
-         */
-        if (filetype_txt == "text") {
-            zone_finder->load(file_txt);
-        } else {
-            zone_finder->load(*container->getInstance().getIterator(
-                                  Name(origin_txt)));
-        }
+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);
     }
 }
 
@@ -314,13 +234,10 @@ private:
      */
     AddrListPtr rollbackAddresses_;
 };
+} // end of unnamed namespace
 
-// This is a generalized version of create function that can create
-// an AuthConfigParser object for "internal" use.
 AuthConfigParser*
-createAuthConfigParser(AuthSrv& server, const std::string& config_id,
-                       bool internal)
-{
+createAuthConfigParser(AuthSrv& server, const std::string& config_id) {
     // For the initial implementation we use a naive if-else blocks for
     // simplicity.  In future we'll probably generalize it using map-like
     // data structure, and may even provide external register interface so
@@ -329,8 +246,6 @@ createAuthConfigParser(AuthSrv& server, const std::string& config_id,
         return (new DatasourcesConfig(server));
     } else if (config_id == "statistics-interval") {
         return (new StatisticsIntervalConfig(server));
-    } else if (internal && config_id == "datasources/memory") {
-        return (new MemoryDatasourceConfig(server));
     } else if (config_id == "listen_on") {
         return (new ListenAddressConfig(server));
     } else if (config_id == "_commit_throw") {
@@ -351,12 +266,6 @@ createAuthConfigParser(AuthSrv& server, const std::string& config_id,
                   config_id);
     }
 }
-} // end of unnamed namespace
-
-AuthConfigParser*
-createAuthConfigParser(AuthSrv& server, const std::string& config_id) {
-    return (createAuthConfigParser(server, config_id, false));
-}
 
 void
 configureAuthServer(AuthSrv& server, ConstElementPtr config_set) {

+ 26 - 12
src/bin/auth/auth_srv.cc

@@ -141,7 +141,7 @@ public:
 
     /// In-memory data source.  Currently class IN only for simplicity.
     const RRClass memory_client_class_;
-    AuthSrv::InMemoryClientPtr memory_client_;
+    isc::datasrc::DataSourceClientContainerPtr memory_client_container_;
 
     /// Hot spot cache
     isc::datasrc::HotCache cache_;
@@ -389,34 +389,46 @@ AuthSrv::getConfigSession() const {
     return (impl_->config_session_);
 }
 
-AuthSrv::InMemoryClientPtr
-AuthSrv::getInMemoryClient(const RRClass& rrclass) {
-    // XXX: for simplicity, we only support the IN class right now.
+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_);
+    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,
-                           InMemoryClientPtr memory_client)
+                           DataSourceClientContainerPtr memory_client)
 {
-    // XXX: see above
     if (rrclass != impl_->memory_client_class_) {
         isc_throw(InvalidParameter,
                   "Memory data source is not supported for RR class "
                   << rrclass);
-    } else if (!impl_->memory_client_ && memory_client) {
+    } 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_ && !memory_client) {
+    } else if (impl_->memory_client_container_ && !memory_client) {
         LOG_DEBUG(auth_logger, DBG_AUTH_OPS, AUTH_MEM_DATASRC_DISABLED)
                   .arg(rrclass);
     }
-    impl_->memory_client_ = memory_client;
+    impl_->memory_client_container_ = memory_client;
 }
 
 uint32_t
@@ -585,10 +597,12 @@ AuthSrvImpl::processNormalQuery(const IOMessage& io_message, Message& message,
         // If a memory data source is configured call the separate
         // Query::process()
         const ConstQuestionPtr question = *message.beginQuestion();
-        if (memory_client_ && memory_client_class_ == question->getClass()) {
+        if (memory_client_container_ &&
+            memory_client_class_ == question->getClass()) {
             const RRType& qtype = question->getType();
             const Name& qname = question->getName();
-            query_.process(*memory_client_, qname, qtype, message, dnssec_ok);
+            query_.process(memory_client_container_->getInstance(),
+                           qname, qtype, message, dnssec_ok);
         } else {
             datasrc::Query query(message, cache_, dnssec_ok);
             data_sources_.doQuery(query);

+ 39 - 26
src/bin/auth/auth_srv.h

@@ -17,12 +17,9 @@
 
 #include <string>
 
-// For InMemoryClientPtr below.  This should be a temporary definition until
-// we reorganize the data source framework.
-#include <boost/shared_ptr.hpp>
-
 #include <cc/data.h>
 #include <config/ccsession.h>
+#include <datasrc/factory.h>
 #include <dns/message.h>
 #include <dns/opcode.h>
 #include <util/buffer.h>
@@ -40,9 +37,6 @@
 #include <auth/statistics.h>
 
 namespace isc {
-namespace datasrc {
-class InMemoryClient;
-}
 namespace xfr {
 class AbstractXfroutClient;
 }
@@ -235,19 +229,14 @@ public:
     ///
     void setXfrinSession(isc::cc::AbstractSession* xfrin_session);
 
-    /// A shared pointer type for \c InMemoryClient.
-    ///
-    /// This is defined inside the \c AuthSrv class as it's supposed to be
-    /// a short term interface until we integrate the in-memory and other
-    /// data source frameworks.
-    typedef boost::shared_ptr<isc::datasrc::InMemoryClient> InMemoryClientPtr;
-
-    /// An immutable shared pointer type for \c InMemoryClient.
-    typedef boost::shared_ptr<const isc::datasrc::InMemoryClient>
-    ConstInMemoryClientPtr;
-
     /// Returns the in-memory data source configured for the \c AuthSrv,
-    /// if any.
+    /// 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.
@@ -262,24 +251,48 @@ public:
     /// \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.
-    InMemoryClientPtr getInMemoryClient(const isc::dns::RRClass& rrclass);
+    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.
     ///
-    /// As noted in \c getInMemoryClient(), some RR classes may not be
-    /// supported, in which case an exception of class \c InvalidParameter
-    /// will be thrown.
+    /// 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_datasrc can be NULL, in which case it will (re)disable the
-    /// in-memory data source.
+    /// \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,
-                           InMemoryClientPtr memory_client);
+        isc::datasrc::DataSourceClientContainerPtr memory_client);
 
     /// \brief Set the communication session with Statistics.
     ///

+ 7 - 4
src/bin/auth/command.cc

@@ -210,8 +210,8 @@ private:
         const RRClass zone_class =
             class_elem ? RRClass(class_elem->stringValue()) : RRClass::IN();
 
-        AuthSrv::InMemoryClientPtr datasrc(server.
-                                           getInMemoryClient(zone_class));
+        isc::datasrc::DataSourceClient* datasrc(
+            server.getInMemoryClient(zone_class));
         if (datasrc == NULL) {
             isc_throw(AuthCommandError, "Memory data source is disabled");
         }
@@ -223,13 +223,16 @@ private:
         const Name origin = Name(origin_elem->stringValue());
 
         // Get the current zone
-        const InMemoryClient::FindResult result = datasrc->findZone(origin);
+        const DataSourceClient::FindResult result = datasrc->findZone(origin);
         if (result.code != result::SUCCESS) {
             isc_throw(AuthCommandError, "Zone " << origin <<
                       " is not found in data source");
         }
 
-        old_zone_finder_ = boost::dynamic_pointer_cast<InMemoryZoneFinder>(
+        // 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);

+ 3 - 3
src/bin/auth/tests/Makefile.am

@@ -44,9 +44,9 @@ run_unittests_SOURCES += statistics_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
-# is nonportable. When #1207 is done this becomes moot anyway, and the
-# specific workaround is not needed anymore, so we can then remove this
-# line again.
+# is nonportable. This should've been moot after #1207, but there is still
+# one dependency; the in-memory-specific zone loader call is still in
+# auth.
 run_unittests_SOURCES += ${top_srcdir}/src/lib/datasrc/memory_datasrc.cc
 
 

+ 141 - 38
src/bin/auth/tests/auth_srv_unittest.cc

@@ -17,6 +17,7 @@
 #include <vector>
 
 #include <boost/shared_ptr.hpp>
+#include <boost/scoped_ptr.hpp>
 
 #include <gtest/gtest.h>
 
@@ -89,6 +90,13 @@ protected:
         server.setStatisticsSession(&statistics_session);
     }
 
+    ~AuthSrvTest() {
+        // Clear the message now; depending on the RTTI implementation,
+        // type information may be lost if the message is cleared
+        // automatically later, so as a precaution we do it now.
+        parse_message->clear(Message::PARSE);
+    }
+
     virtual void processMessage() {
         // If processMessage has been called before, parse_message needs
         // to be reset. If it hasn't, there's no harm in doing so
@@ -830,16 +838,23 @@ TEST_F(AuthSrvTest, updateConfigFail) {
                 QR_FLAG | AA_FLAG, 1, 1, 1, 0);
 }
 
-TEST_F(AuthSrvTest, updateWithInMemoryClient) {
+TEST_F(AuthSrvTest,
+#ifdef USE_STATIC_LINK
+       DISABLED_updateWithInMemoryClient
+#else
+       updateWithInMemoryClient
+#endif
+    )
+{
     // 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_EQ(AuthSrv::InMemoryClientPtr(), server.getInMemoryClient(rrclass));
+    EXPECT_FALSE(server.hasInMemoryClient());
     updateConfig(&server,
                  "{\"datasources\": [{\"type\": \"memory\"}]}", true);
     // after successful configuration, we should have one (with empty zoneset).
-    ASSERT_NE(AuthSrv::InMemoryClientPtr(), server.getInMemoryClient(rrclass));
+    EXPECT_TRUE(server.hasInMemoryClient());
     EXPECT_EQ(0, server.getInMemoryClient(rrclass)->getZoneCount());
 
     // The memory data source is empty, should return REFUSED rcode.
@@ -851,13 +866,20 @@ TEST_F(AuthSrvTest, updateWithInMemoryClient) {
                 opcode.getCode(), QR_FLAG, 1, 0, 0, 0);
 }
 
-TEST_F(AuthSrvTest, queryWithInMemoryClientNoDNSSEC) {
+TEST_F(AuthSrvTest,
+#ifdef USE_STATIC_LINK
+       DISABLED_queryWithInMemoryClientNoDNSSEC
+#else
+       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
     // for various types of queries are tested in the query tests.
     updateConfig(&server, CONFIG_INMEMORY_EXAMPLE, true);
-    ASSERT_NE(AuthSrv::InMemoryClientPtr(), server.getInMemoryClient(rrclass));
+    EXPECT_TRUE(server.hasInMemoryClient());
     EXPECT_EQ(1, server.getInMemoryClient(rrclass)->getZoneCount());
 
     createDataFromFile("nsec3query_nodnssec_fromWire.wire");
@@ -869,12 +891,19 @@ TEST_F(AuthSrvTest, queryWithInMemoryClientNoDNSSEC) {
                 opcode.getCode(), QR_FLAG | AA_FLAG, 1, 1, 2, 1);
 }
 
-TEST_F(AuthSrvTest, queryWithInMemoryClientDNSSEC) {
+TEST_F(AuthSrvTest,
+#ifdef USE_STATIC_LINK
+       DISABLED_queryWithInMemoryClientDNSSEC
+#else
+       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.
     updateConfig(&server, CONFIG_INMEMORY_EXAMPLE, true);
-    ASSERT_NE(AuthSrv::InMemoryClientPtr(), server.getInMemoryClient(rrclass));
+    EXPECT_TRUE(server.hasInMemoryClient());
     EXPECT_EQ(1, server.getInMemoryClient(rrclass)->getZoneCount());
 
     createDataFromFile("nsec3query_fromWire.wire");
@@ -886,7 +915,14 @@ TEST_F(AuthSrvTest, queryWithInMemoryClientDNSSEC) {
                 opcode.getCode(), QR_FLAG | AA_FLAG, 1, 2, 3, 3);
 }
 
-TEST_F(AuthSrvTest, chQueryWithInMemoryClient) {
+TEST_F(AuthSrvTest,
+#ifdef USE_STATIC_LINK
+       DISABLED_chQueryWithInMemoryClient
+#else
+       chQueryWithInMemoryClient
+#endif
+    )
+{
     // Configure memory data source for class IN
     updateConfig(&server, "{\"datasources\": "
                  "[{\"class\": \"IN\", \"type\": \"memory\"}]}", true);
@@ -1108,7 +1144,7 @@ TEST_F(AuthSrvTest, processNormalQuery_reuseRenderer2) {
 //
 namespace {
 
-/// A the possible methods to throw in, either in FakeInMemoryClient or
+/// The possible methods to throw in, either in FakeClient or
 /// FakeZoneFinder
 enum ThrowWhen {
     THROW_NEVER,
@@ -1132,10 +1168,10 @@ checkThrow(ThrowWhen method, ThrowWhen throw_at, bool isc_exception) {
     }
 }
 
-/// \brief proxy class for the ZoneFinder returned by the InMemoryClient
-///        proxied by FakeInMemoryClient
+/// \brief proxy class for the ZoneFinder returned by the Client
+///        proxied by FakeClient
 ///
-/// See the documentation for FakeInMemoryClient for more information,
+/// See the documentation for FakeClient for more information,
 /// all methods simply check whether they should throw, and if not, call
 /// their proxied equivalent.
 class FakeZoneFinder : public isc::datasrc::ZoneFinder {
@@ -1208,15 +1244,15 @@ private:
     ConstRRsetPtr fake_rrset_;
 };
 
-/// \brief Proxy InMemoryClient that can throw exceptions at specified times
+/// \brief Proxy FakeClient that can throw exceptions at specified times
 ///
-/// It is based on the memory client since that one is easy to override
-/// (with setInMemoryClient) with the current design of AuthSrv.
-class FakeInMemoryClient : public isc::datasrc::InMemoryClient {
+/// Currently it is used as an 'InMemoryClient' using setInMemoryClient,
+/// but it is in effect a general datasource client.
+class FakeClient : public isc::datasrc::DataSourceClient {
 public:
     /// \brief Create a proxy memory client
     ///
-    /// \param real_client The real in-memory client to proxy
+    /// \param real_client The real (in-memory) client to proxy
     /// \param throw_when if set to any value other than never, that is
     ///        the method that will throw an exception (either in this
     ///        class or the related FakeZoneFinder)
@@ -1224,10 +1260,10 @@ public:
     ///                      throw std::exception
     /// \param fake_rrset If non NULL, it will be used as an answer to
     /// find() for that name and type.
-    FakeInMemoryClient(AuthSrv::InMemoryClientPtr real_client,
-                       ThrowWhen throw_when, bool isc_exception,
-                       ConstRRsetPtr fake_rrset = ConstRRsetPtr()) :
-        real_client_(real_client),
+    FakeClient(isc::datasrc::DataSourceClientContainerPtr real_client,
+               ThrowWhen throw_when, bool isc_exception,
+               ConstRRsetPtr fake_rrset = ConstRRsetPtr()) :
+        real_client_ptr_(real_client),
         throw_when_(throw_when),
         isc_exception_(isc_exception),
         fake_rrset_(fake_rrset)
@@ -1242,7 +1278,8 @@ public:
     virtual FindResult
     findZone(const isc::dns::Name& name) const {
         checkThrow(THROW_AT_FIND_ZONE, throw_when_, isc_exception_);
-        const FindResult result = real_client_->findZone(name);
+        const FindResult result =
+            real_client_ptr_->getInstance().findZone(name);
         return (FindResult(result.code, isc::datasrc::ZoneFinderPtr(
                                         new FakeZoneFinder(result.zone_finder,
                                                            throw_when_,
@@ -1250,28 +1287,74 @@ public:
                                                            fake_rrset_))));
     }
 
+    isc::datasrc::ZoneUpdaterPtr
+    getUpdater(const isc::dns::Name&, bool, bool) const {
+        isc_throw(isc::NotImplemented,
+                  "Update attempt on in fake data source");
+    }
+    std::pair<isc::datasrc::ZoneJournalReader::Result,
+              isc::datasrc::ZoneJournalReaderPtr>
+    getJournalReader(const isc::dns::Name&, uint32_t, uint32_t) const {
+        isc_throw(isc::NotImplemented, "Journaling isn't supported for "
+                  "fake data source");
+    }
 private:
-    AuthSrv::InMemoryClientPtr real_client_;
+    const isc::datasrc::DataSourceClientContainerPtr real_client_ptr_;
     ThrowWhen throw_when_;
     bool isc_exception_;
     ConstRRsetPtr fake_rrset_;
 };
 
+class FakeContainer : public isc::datasrc::DataSourceClientContainer {
+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)
+    ///
+    /// 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_);
+    }
+
+private:
+    const boost::scoped_ptr<isc::datasrc::DataSourceClient> client_;
+};
+
 } // end anonymous namespace for throwing proxy classes
 
 // Test for the tests
 //
 // 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, queryWithInMemoryClientProxy) {
+TEST_F(AuthSrvTest,
+#ifdef USE_STATIC_LINK
+       DISABLED_queryWithInMemoryClientProxy
+#else
+       queryWithInMemoryClientProxy
+#endif
+    )
+{
     // Set real inmem client to proxy
     updateConfig(&server, CONFIG_INMEMORY_EXAMPLE, true);
+    EXPECT_TRUE(server.hasInMemoryClient());
 
-    AuthSrv::InMemoryClientPtr fake_client(
-        new FakeInMemoryClient(server.getInMemoryClient(rrclass),
-                               THROW_NEVER, false));
-    ASSERT_NE(AuthSrv::InMemoryClientPtr(), server.getInMemoryClient(rrclass));
-    server.setInMemoryClient(rrclass, fake_client);
+    isc::datasrc::DataSourceClientContainerPtr fake_client_container(
+        new FakeContainer(server.getInMemoryClientContainer(rrclass),
+                          THROW_NEVER, false));
+    server.setInMemoryClient(rrclass, fake_client_container);
 
     createDataFromFile("nsec3query_nodnssec_fromWire.wire");
     server.processMessage(*io_message, *parse_message, *response_obuffer,
@@ -1297,17 +1380,23 @@ setupThrow(AuthSrv* server, const char *config, ThrowWhen throw_when,
 
     // Set it to throw on findZone(), this should result in
     // SERVFAIL on any exception
-    AuthSrv::InMemoryClientPtr fake_client(
-        new FakeInMemoryClient(
-            server->getInMemoryClient(isc::dns::RRClass::IN()),
+    isc::datasrc::DataSourceClientContainerPtr fake_client_container(
+        new FakeContainer(
+            server->getInMemoryClientContainer(isc::dns::RRClass::IN()),
             throw_when, isc_exception, rrset));
 
-    ASSERT_NE(AuthSrv::InMemoryClientPtr(),
-              server->getInMemoryClient(isc::dns::RRClass::IN()));
-    server->setInMemoryClient(isc::dns::RRClass::IN(), fake_client);
+    ASSERT_TRUE(server->hasInMemoryClient());
+    server->setInMemoryClient(isc::dns::RRClass::IN(), fake_client_container);
 }
 
-TEST_F(AuthSrvTest, queryWithThrowingProxyServfails) {
+TEST_F(AuthSrvTest,
+#ifdef USE_STATIC_LINK
+       DISABLED_queryWithThrowingProxyServfails
+#else
+       queryWithThrowingProxyServfails
+#endif
+    )
+{
     // Test the common cases, all of which should simply return SERVFAIL
     // Use THROW_NEVER as end marker
     ThrowWhen throws[] = { THROW_AT_FIND_ZONE,
@@ -1331,7 +1420,14 @@ TEST_F(AuthSrvTest, queryWithThrowingProxyServfails) {
 
 // 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, queryWithInMemoryClientProxyGetClass) {
+TEST_F(AuthSrvTest,
+#ifdef USE_STATIC_LINK
+       DISABLED_queryWithInMemoryClientProxyGetClass
+#else
+       queryWithInMemoryClientProxyGetClass
+#endif
+    )
+{
     createDataFromFile("nsec3query_nodnssec_fromWire.wire");
     setupThrow(&server, CONFIG_INMEMORY_EXAMPLE, THROW_AT_GET_CLASS, true);
 
@@ -1344,7 +1440,14 @@ TEST_F(AuthSrvTest, queryWithInMemoryClientProxyGetClass) {
                 opcode.getCode(), QR_FLAG | AA_FLAG, 1, 1, 2, 1);
 }
 
-TEST_F(AuthSrvTest, queryWithThrowingInToWire) {
+TEST_F(AuthSrvTest,
+#ifdef USE_STATIC_LINK
+       DISABLED_queryWithThrowingInToWire
+#else
+       queryWithThrowingInToWire
+#endif
+    )
+{
     // Set up a faked data source.  It will return an empty RRset for the
     // query.
     ConstRRsetPtr empty_rrset(new RRset(Name("foo.example"),

+ 73 - 24
src/bin/auth/tests/command_unittest.cc

@@ -23,6 +23,7 @@
 #include <dns/name.h>
 #include <dns/rrclass.h>
 #include <dns/rrtype.h>
+#include <dns/rrttl.h>
 
 #include <cc/data.h>
 
@@ -68,7 +69,7 @@ protected:
     }
     void checkAnswer(const int expected_code) {
         parseAnswer(rcode_, result_);
-        EXPECT_EQ(expected_code, rcode_);
+        EXPECT_EQ(expected_code, rcode_) << result_->str();
     }
     MockSession statistics_session_;
     MockXfroutClient xfrout_;
@@ -233,7 +234,14 @@ newZoneChecks(AuthSrv& server) {
               find(Name("ns.test2.example"), RRType::AAAA())->code);
 }
 
-TEST_F(AuthCommandTest, loadZone) {
+TEST_F(AuthCommandTest,
+#ifdef USE_STATIC_LINK
+       DISABLED_loadZone
+#else
+       loadZone
+#endif
+    )
+{
     configureZones(server_);
 
     ASSERT_EQ(0, system(INSTALL_PROG " -c " TEST_DATA_DIR
@@ -250,8 +258,6 @@ TEST_F(AuthCommandTest, loadZone) {
     newZoneChecks(server_);
 }
 
-// This test uses dynamic load of a data source module, and won't work when
-// statically linked.
 TEST_F(AuthCommandTest,
 #ifdef USE_STATIC_LINK
        DISABLED_loadZoneSQLite3
@@ -289,36 +295,57 @@ TEST_F(AuthCommandTest,
                               "      }"
                               "    ]"
                               "  }"
-                              "]}"));
+                              "],"
+                              " \"database_file\": \"" + test_db + "\""
+                              "}"));
     module_session.setLocalConfig(map);
     server_.setConfigSession(&module_session);
 
-    // The loadzone command needs the zone to be already loaded, because
-    // it is used for reloading only
-    AuthSrv::InMemoryClientPtr dsrc(new InMemoryClient());
-    dsrc->addZone(ZoneFinderPtr(new InMemoryZoneFinder(RRClass::IN(),
-                                                       Name("example.org"))));
-    server_.setInMemoryClient(RRClass::IN(), dsrc);
+    server_.updateConfig(map);
+
+    // 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->
+              find(Name("www.example.org"), RRType::A())->code);
+
+    // Add the record to the underlying sqlite database, by loading
+    // it as a separate datasource, and updating it
+    ConstElementPtr sql_cfg = Element::fromJSON("{ \"type\": \"sqlite3\","
+                                                "\"database_file\": \""
+                                                + test_db + "\"}");
+    DataSourceClientContainer sql_ds("sqlite3", sql_cfg);
+    ZoneUpdaterPtr sql_updater =
+        sql_ds.getInstance().getUpdater(Name("example.org"), false);
+    RRsetPtr rrset(new RRset(Name("www.example.org."), RRClass::IN(),
+                             RRType::A(), RRTTL(60)));
+    rrset->addRdata(rdata::createRdata(rrset->getType(),
+                                       rrset->getClass(),
+                                       "192.0.2.1"));
+    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->
+              find(Name("www.example.org"), RRType::A())->code);
 
     // Now send the command to reload it
     result_ = execAuthServerCommand(server_, "loadzone",
-        Element::fromJSON("{\"origin\": \"example.org\"}"));
+                                    Element::fromJSON(
+                                        "{\"origin\": \"example.org\"}"));
     checkAnswer(0);
 
-    // Get the zone and look if there are data in it (the original one was
-    // empty)
-    ASSERT_TRUE(server_.getInMemoryClient(RRClass::IN()));
+    // And now it should be present too.
     EXPECT_EQ(ZoneFinder::SUCCESS, server_.getInMemoryClient(RRClass::IN())->
               findZone(Name("example.org")).zone_finder->
-              find(Name("example.org"), RRType::SOA())->code);
+              find(Name("www.example.org"), RRType::A())->code);
 
-    // Some error cases. First, the zone has no configuration.
-    dsrc->addZone(ZoneFinderPtr(new InMemoryZoneFinder(RRClass::IN(),
-                                                       Name("example.com"))));
+    // Some error cases. First, the zone has no configuration. (note .com here)
     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->
@@ -326,7 +353,8 @@ TEST_F(AuthCommandTest,
 
     module_session.setLocalConfig(Element::fromJSON("{\"datasources\": []}"));
     result_ = execAuthServerCommand(server_, "loadzone",
-        Element::fromJSON("{\"origin\": \"example.org\"}"));
+                                    Element::fromJSON(
+                                        "{\"origin\": \"example.org\"}"));
     checkAnswer(1);
 
     // The previous zone is not hurt in any way
@@ -373,7 +401,14 @@ TEST_F(AuthCommandTest,
               find(Name("example.org"), RRType::SOA())->code);
 }
 
-TEST_F(AuthCommandTest, loadBrokenZone) {
+TEST_F(AuthCommandTest,
+#ifdef USE_STATIC_LINK
+       DISABLED_loadBrokenZone
+#else
+       loadBrokenZone
+#endif
+    )
+{
     configureZones(server_);
 
     ASSERT_EQ(0, system(INSTALL_PROG " -c " TEST_DATA_DIR
@@ -386,7 +421,14 @@ TEST_F(AuthCommandTest, loadBrokenZone) {
     zoneChecks(server_);     // zone shouldn't be replaced
 }
 
-TEST_F(AuthCommandTest, loadUnreadableZone) {
+TEST_F(AuthCommandTest,
+#ifdef USE_STATIC_LINK
+       DISABLED_loadUnreadableZone
+#else
+       loadUnreadableZone
+#endif
+    )
+{
     configureZones(server_);
 
     // install the zone file as unreadable
@@ -419,7 +461,14 @@ TEST_F(AuthCommandTest, loadSqlite3DataSrc) {
     checkAnswer(0);
 }
 
-TEST_F(AuthCommandTest, loadZoneInvalidParams) {
+TEST_F(AuthCommandTest,
+#ifdef USE_STATIC_LINK
+       DISABLED_loadZoneInvalidParams
+#else
+       loadZoneInvalidParams
+#endif
+    )
+{
     configureZones(server_);
 
     // null arg

+ 95 - 24
src/bin/auth/tests/config_unittest.cc

@@ -69,13 +69,20 @@ private:
     isc::testutils::TestSocketRequestor sock_requestor_;
 };
 
-TEST_F(AuthConfigTest, datasourceConfig) {
+TEST_F(AuthConfigTest,
+#ifdef USE_STATIC_LINK
+       DISABLED_datasourceConfig
+#else
+       datasourceConfig
+#endif
+    )
+{
     // By default, we don't have any in-memory data source.
-    EXPECT_EQ(AuthSrv::InMemoryClientPtr(), server.getInMemoryClient(rrclass));
+    EXPECT_FALSE(server.hasInMemoryClient());
     configureAuthServer(server, Element::fromJSON(
                             "{\"datasources\": [{\"type\": \"memory\"}]}"));
     // after successful configuration, we should have one (with empty zoneset).
-    ASSERT_NE(AuthSrv::InMemoryClientPtr(), server.getInMemoryClient(rrclass));
+    EXPECT_TRUE(server.hasInMemoryClient());
     EXPECT_EQ(0, server.getInMemoryClient(rrclass)->getZoneCount());
 }
 
@@ -96,7 +103,7 @@ TEST_F(AuthConfigTest, versionConfig) {
 }
 
 TEST_F(AuthConfigTest, exceptionGuarantee) {
-    EXPECT_EQ(AuthSrv::InMemoryClientPtr(), server.getInMemoryClient(rrclass));
+    EXPECT_FALSE(server.hasInMemoryClient());
     // This configuration contains an invalid item, which will trigger
     // an exception.
     EXPECT_THROW(configureAuthServer(
@@ -106,7 +113,7 @@ TEST_F(AuthConfigTest, exceptionGuarantee) {
                          " \"no_such_config_var\": 1}")),
                  AuthConfigError);
     // The server state shouldn't change
-    EXPECT_EQ(AuthSrv::InMemoryClientPtr(), server.getInMemoryClient(rrclass));
+    EXPECT_FALSE(server.hasInMemoryClient());
 }
 
 TEST_F(AuthConfigTest, exceptionConversion) {
@@ -176,25 +183,46 @@ protected:
 TEST_F(MemoryDatasrcConfigTest, addZeroDataSrc) {
     parser->build(Element::fromJSON("[]"));
     parser->commit();
-    EXPECT_EQ(AuthSrv::InMemoryClientPtr(), server.getInMemoryClient(rrclass));
+    EXPECT_FALSE(server.hasInMemoryClient());
 }
 
-TEST_F(MemoryDatasrcConfigTest, addEmpty) {
+TEST_F(MemoryDatasrcConfigTest,
+#ifdef USE_STATIC_LINK
+       DISABLED_addEmpty
+#else
+       addEmpty
+#endif
+    )
+{
     // By default, we don't have any in-memory data source.
-    EXPECT_EQ(AuthSrv::InMemoryClientPtr(), server.getInMemoryClient(rrclass));
+    EXPECT_FALSE(server.hasInMemoryClient());
     parser->build(Element::fromJSON("[{\"type\": \"memory\"}]"));
     parser->commit();
     EXPECT_EQ(0, server.getInMemoryClient(rrclass)->getZoneCount());
 }
 
-TEST_F(MemoryDatasrcConfigTest, addZeroZone) {
+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, addOneZone) {
+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\","
@@ -245,7 +273,14 @@ TEST_F(MemoryDatasrcConfigTest,
                  DataSourceError);
 }
 
-TEST_F(MemoryDatasrcConfigTest, addOneWithFiletypeText) {
+TEST_F(MemoryDatasrcConfigTest,
+#ifdef USE_STATIC_LINK
+       DISABLED_addOneWithFiletypeText
+#else
+       addOneWithFiletypeText
+#endif
+    )
+{
     // Explicitly specifying "text" is okay.
     parser->build(Element::fromJSON(
                       "[{\"type\": \"memory\","
@@ -257,7 +292,14 @@ TEST_F(MemoryDatasrcConfigTest, addOneWithFiletypeText) {
     EXPECT_EQ(1, server.getInMemoryClient(rrclass)->getZoneCount());
 }
 
-TEST_F(MemoryDatasrcConfigTest, addMultiZones) {
+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\","
@@ -273,7 +315,14 @@ TEST_F(MemoryDatasrcConfigTest, addMultiZones) {
     EXPECT_EQ(3, server.getInMemoryClient(rrclass)->getZoneCount());
 }
 
-TEST_F(MemoryDatasrcConfigTest, replace) {
+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\","
@@ -304,7 +353,14 @@ TEST_F(MemoryDatasrcConfigTest, replace) {
                   Name("example.com")).code);
 }
 
-TEST_F(MemoryDatasrcConfigTest, exception) {
+TEST_F(MemoryDatasrcConfigTest,
+#ifdef USE_STATIC_LINK
+       DISABLED_exception
+#else
+       exception
+#endif
+    )
+{
     // Load a zone
     EXPECT_NO_THROW(parser->build(Element::fromJSON(
                       "[{\"type\": \"memory\","
@@ -328,7 +384,8 @@ TEST_F(MemoryDatasrcConfigTest, exception) {
                       "/example.org.zone\"},"
                       "              {\"origin\": \"example.net\","
                       "               \"file\": \"" TEST_DATA_DIR
-                      "/nonexistent.zone\"}]}]")), isc::dns::MasterLoadError);
+                      "/nonexistent.zone\"}]}]")),
+                 isc::datasrc::DataSourceError);
     // As that one throwed exception, it is not expected from us to
     // commit it
 
@@ -339,7 +396,14 @@ TEST_F(MemoryDatasrcConfigTest, exception) {
                   Name("example.com")).code);
 }
 
-TEST_F(MemoryDatasrcConfigTest, remove) {
+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\","
@@ -352,7 +416,7 @@ TEST_F(MemoryDatasrcConfigTest, remove) {
     parser = createAuthConfigParser(server, "datasources"); 
     EXPECT_NO_THROW(parser->build(Element::fromJSON("[]")));
     EXPECT_NO_THROW(parser->commit());
-    EXPECT_EQ(AuthSrv::InMemoryClientPtr(), server.getInMemoryClient(rrclass));
+    EXPECT_FALSE(server.hasInMemoryClient());
 }
 
 TEST_F(MemoryDatasrcConfigTest, addDuplicateZones) {
@@ -365,7 +429,7 @@ TEST_F(MemoryDatasrcConfigTest, addDuplicateZones) {
                          "              {\"origin\": \"example.com\","
                          "               \"file\": \"" TEST_DATA_DIR
                          "/example.com.zone\"}]}]")),
-                 AuthConfigError);
+                 DataSourceError);
 }
 
 TEST_F(MemoryDatasrcConfigTest, addBadZone) {
@@ -374,35 +438,35 @@ TEST_F(MemoryDatasrcConfigTest, addBadZone) {
                      Element::fromJSON(
                          "[{\"type\": \"memory\","
                          "  \"zones\": [{}]}]")),
-                 AuthConfigError);
+                 DataSourceError);
 
     // origin is missing
     EXPECT_THROW(parser->build(
                      Element::fromJSON(
                          "[{\"type\": \"memory\","
                          "  \"zones\": [{\"file\": \"example.zone\"}]}]")),
-                 AuthConfigError);
+                 DataSourceError);
 
     // file is missing
     EXPECT_THROW(parser->build(
                      Element::fromJSON(
                          "[{\"type\": \"memory\","
                          "  \"zones\": [{\"origin\": \"example.com\"}]}]")),
-                 AuthConfigError);
+                 DataSourceError);
 
     // missing zone file
     EXPECT_THROW(parser->build(
                      Element::fromJSON(
                          "[{\"type\": \"memory\","
                          "  \"zones\": [{\"origin\": \"example.com\"}]}]")),
-                 AuthConfigError);
+                 DataSourceError);
 
     // bogus origin name
     EXPECT_THROW(parser->build(Element::fromJSON(
                       "[{\"type\": \"memory\","
                       "  \"zones\": [{\"origin\": \"example..com\","
                       "               \"file\": \"example.zone\"}]}]")),
-                 AuthConfigError);
+                 DataSourceError);
 
     // bogus RR class name
     EXPECT_THROW(parser->build(
@@ -423,7 +487,14 @@ TEST_F(MemoryDatasrcConfigTest, addBadZone) {
                  isc::InvalidParameter);
 }
 
-TEST_F(MemoryDatasrcConfigTest, badDatasrcType) {
+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\"}]")),

+ 16 - 0
src/lib/datasrc/client.h

@@ -362,6 +362,22 @@ public:
     virtual std::pair<ZoneJournalReader::Result, ZoneJournalReaderPtr>
     getJournalReader(const isc::dns::Name& zone, uint32_t begin_serial,
                      uint32_t end_serial) const = 0;
+
+    /// Return the number of zones currently known to this datasource
+    ///
+    /// This is an optional convenience method, currently only implemented
+    /// by the InMemory datasource. By default, it throws NotImplemented
+    ///
+    /// \exception NotImplemented Thrown if this method is not supported
+    ///            by the datasource
+    ///
+    /// \note This is a tentative API, and this method may likely to be
+    ///       removed in the near future.
+    /// \return The number of zones known to this datasource
+    virtual unsigned int getZoneCount() const {
+        isc_throw(isc::NotImplemented,
+                  "Data source doesn't support getZoneCount");
+    }
 };
 }
 }

+ 2 - 0
src/lib/datasrc/factory.cc

@@ -23,6 +23,8 @@
 
 #include <datasrc/logger.h>
 
+#include <exceptions/exceptions.h>
+
 #include <dlfcn.h>
 #include <cstdlib>
 

+ 18 - 5
src/lib/datasrc/factory.h

@@ -15,14 +15,14 @@
 #ifndef __DATA_SOURCE_FACTORY_H
 #define __DATA_SOURCE_FACTORY_H 1
 
-#include <boost/noncopyable.hpp>
-
 #include <datasrc/data_source.h>
 #include <datasrc/client.h>
-#include <exceptions/exceptions.h>
 
 #include <cc/data.h>
 
+#include <boost/noncopyable.hpp>
+#include <boost/shared_ptr.hpp>
+
 namespace isc {
 namespace datasrc {
 
@@ -134,6 +134,13 @@ private:
 ///
 /// extern "C" void destroyInstance(isc::data::DataSourceClient* instance);
 /// \endcode
+///
+/// \note This class is relatively recent, and its design is not yet fully
+/// formed. We may want to split this into an abstract base container
+/// class, and a derived 'dyload' class, and perhaps then add non-dynamic
+/// derived classes as well. Currently, the class is actually derived in some
+/// of the tests, which is rather unclean (as this class as written is really
+/// intended to be used directly).
 class DataSourceClientContainer : boost::noncopyable {
 public:
     /// \brief Constructor
@@ -157,13 +164,13 @@ public:
                               isc::data::ConstElementPtr config);
 
     /// \brief Destructor
-    ~DataSourceClientContainer();
+    virtual ~DataSourceClientContainer();
 
     /// \brief Accessor to the instance
     ///
     /// \return Reference to the DataSourceClient instance contained in this
     ///         container.
-    DataSourceClient& getInstance() { return (*instance_); }
+    virtual DataSourceClient& getInstance() { return (*instance_); }
 
 private:
     DataSourceClient* instance_;
@@ -171,6 +178,12 @@ private:
     LibraryContainer ds_lib_;
 };
 
+///
+/// Shared pointer type for datasource client containers
+///
+typedef boost::shared_ptr<DataSourceClientContainer>
+    DataSourceClientContainerPtr;
+
 } // end namespace datasrc
 } // end namespace isc
 #endif  // DATA_SOURCE_FACTORY_H

+ 1 - 1
src/lib/datasrc/memory_datasrc.h

@@ -282,7 +282,7 @@ public:
     /// This method never throws an exception.
     ///
     /// \return The number of zones stored in the client.
-    unsigned int getZoneCount() const;
+    virtual unsigned int getZoneCount() const;
 
     /// Add a zone (in the form of \c ZoneFinder) to the \c InMemoryClient.
     ///

+ 120 - 16
src/lib/datasrc/memory_datasrc_link.cc

@@ -17,9 +17,13 @@
 #include <dns/rrclass.h>
 
 #include <datasrc/client.h>
+#include <datasrc/factory.h>
 #include <datasrc/memory_datasrc.h>
 
+#include <exceptions/exceptions.h>
+
 #include <boost/foreach.hpp>
+#include <boost/scoped_ptr.hpp>
 
 #include <string>
 
@@ -29,6 +33,14 @@ using namespace isc::data;
 namespace isc {
 namespace datasrc {
 
+/// This exception is raised if there is an error in the configuration
+/// that has been passed; missing information, duplicate values, etc.
+class InMemoryConfigError : public isc::Exception {
+public:
+    InMemoryConfigError(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) {}
+};
+
 namespace {
 // convencience function to add an error message to a list of those
 // (TODO: move functions like these to some util lib?)
@@ -49,14 +61,14 @@ checkConfigElementString(ConstElementPtr config, const std::string& name,
                  "Config for memory backend does not contain a '"
                  +name+
                  "' value");
-        return false;
+        return (false);
     } else if (!config->get(name) ||
                config->get(name)->getType() != Element::string) {
         addError(errors, "value of " + name +
                  " in memory backend config is not a string");
-        return false;
+        return (false);
     } else {
-        return true;
+        return (true);
     }
 }
 
@@ -112,24 +124,26 @@ checkConfig(ConstElementPtr config, ElementPtr errors) {
                 result = false;
             }
         }
-        if (!checkConfigElementString(config, "class", errors)) {
-            result = false;
-        } else {
-            try {
-                RRClass rrc(config->get("class")->stringValue());
-            } catch (const isc::Exception& rrce) {
-                addError(errors,
-                         "Error parsing class config for memory backend: " +
-                         std::string(rrce.what()));
+        if (config->contains("class")) {
+            if (!checkConfigElementString(config, "class", errors)) {
                 result = false;
+            } else {
+                try {
+                    RRClass rrc(config->get("class")->stringValue());
+                } catch (const isc::Exception& rrce) {
+                    addError(errors,
+                             "Error parsing class config for memory backend: " +
+                             std::string(rrce.what()));
+                    result = false;
+                }
             }
         }
         if (!config->contains("zones")) {
-            addError(errors, "No 'zones' element in memory backend config");
-            result = false;
+            // Assume empty list of zones
         } else if (!config->get("zones") ||
                    config->get("zones")->getType() != Element::list) {
-            addError(errors, "'zones' element in memory backend config is not a list");
+            addError(errors,
+                     "'zones' element in memory backend config is not a list");
             result = false;
         } else {
             BOOST_FOREACH(ConstElementPtr zone_config,
@@ -144,6 +158,91 @@ checkConfig(ConstElementPtr config, ElementPtr errors) {
     return (result);
 }
 
+// Apply the given config to the just-initialized client
+// client must be freshly allocated, and config_value should have been
+// checked by the caller
+void
+applyConfig(isc::datasrc::InMemoryClient& client,
+            isc::data::ConstElementPtr config_value)
+{
+    // XXX: We have lost the context to get to the default values here,
+    // as a temporary workaround we hardcode the IN class here.
+    isc::dns::RRClass rrclass = RRClass::IN();
+    if (config_value->contains("class")) {
+        rrclass = RRClass(config_value->get("class")->stringValue());
+    }
+    ConstElementPtr zones_config = config_value->get("zones");
+    if (!zones_config) {
+        // XXX: Like the RR class, we cannot retrieve the default value here,
+        // so we assume an empty zone list in this case.
+        return;
+    }
+
+    BOOST_FOREACH(ConstElementPtr zone_config, zones_config->listValue()) {
+        ConstElementPtr origin = zone_config->get("origin");
+        const std::string origin_txt = origin ? origin->stringValue() : "";
+        if (origin_txt.empty()) {
+            isc_throw(InMemoryConfigError, "Missing zone origin");
+        }
+        ConstElementPtr file = zone_config->get("file");
+        const std::string file_txt = file ? file->stringValue() : "";
+        if (file_txt.empty()) {
+            isc_throw(InMemoryConfigError, "Missing zone file for zone: "
+                      << origin_txt);
+        }
+
+        // We support the traditional text type and SQLite3 backend.  For the
+        // latter we create a client for the underlying SQLite3 data source,
+        // and build the in-memory zone using an iterator of the underlying
+        // zone.
+        ConstElementPtr filetype = zone_config->get("filetype");
+        const std::string filetype_txt = filetype ? filetype->stringValue() :
+            "text";
+        boost::scoped_ptr<DataSourceClientContainer> container;
+        if (filetype_txt == "sqlite3") {
+            container.reset(new DataSourceClientContainer(
+                                "sqlite3",
+                                Element::fromJSON("{\"database_file\": \"" +
+                                                  file_txt + "\"}")));
+        } else if (filetype_txt != "text") {
+            isc_throw(InMemoryConfigError, "Invalid filetype for zone "
+                      << origin_txt << ": " << filetype_txt);
+        }
+
+        // Note: we don't want to have such small try-catch blocks for each
+        // specific error.  We may eventually want to introduce some unified
+        // error handling framework as we have more configuration parameters.
+        // See bug #1627 for the relevant discussion.
+        InMemoryZoneFinder* imzf = NULL;
+        try {
+            imzf = new InMemoryZoneFinder(rrclass, Name(origin_txt));
+        } catch (const isc::dns::NameParserException& ex) {
+            isc_throw(InMemoryConfigError, "unable to parse zone's origin: " <<
+                      ex.what());
+        }
+
+        boost::shared_ptr<InMemoryZoneFinder> zone_finder(imzf);
+        const result::Result result = client.addZone(zone_finder);
+        if (result == result::EXIST) {
+            isc_throw(InMemoryConfigError, "zone "<< origin->str()
+                      << " already exists");
+        }
+
+        /*
+         * TODO: Once we have better reloading of configuration (something
+         * else than throwing everything away and loading it again), we will
+         * need the load method to be split into some kind of build and
+         * commit/abort parts.
+         */
+        if (filetype_txt == "text") {
+            zone_finder->load(file_txt);
+        } else {
+            zone_finder->load(*container->getInstance().getIterator(
+                                  Name(origin_txt)));
+        }
+    }
+}
+
 } // end unnamed namespace
 
 DataSourceClient *
@@ -154,7 +253,12 @@ createInstance(isc::data::ConstElementPtr config, std::string& error) {
         return (NULL);
     }
     try {
-        return (new isc::datasrc::InMemoryClient());
+        std::auto_ptr<InMemoryClient> client(new isc::datasrc::InMemoryClient());
+        applyConfig(*client, config);
+        return (client.release());
+    } catch (const isc::Exception& isce) {
+        error = isce.what();
+        return (NULL);
     } catch (const std::exception& exc) {
         error = std::string("Error creating memory datasource: ") + exc.what();
         return (NULL);

+ 4 - 0
src/lib/datasrc/tests/client_unittest.cc

@@ -56,4 +56,8 @@ TEST_F(ClientTest, defaultIterator) {
     EXPECT_THROW(client_.getIterator(Name(".")), isc::NotImplemented);
 }
 
+TEST_F(ClientTest, defaultGetZoneCount) {
+    EXPECT_THROW(client_.getZoneCount(), isc::NotImplemented);
+}
+
 }

+ 3 - 4
src/lib/datasrc/tests/factory_unittest.cc

@@ -188,8 +188,8 @@ TEST(FactoryTest, memoryClient) {
                  DataSourceError);
 
     config->set("type", Element::create("memory"));
-    ASSERT_THROW(DataSourceClientContainer("memory", config),
-                 DataSourceError);
+    // no config at all should result in a default empty memory client
+    ASSERT_NO_THROW(DataSourceClientContainer("memory", config));
 
     config->set("class", ElementPtr());
     ASSERT_THROW(DataSourceClientContainer("memory", config),
@@ -204,8 +204,7 @@ TEST(FactoryTest, memoryClient) {
                  DataSourceError);
 
     config->set("class", Element::create("IN"));
-    ASSERT_THROW(DataSourceClientContainer("memory", config),
-                 DataSourceError);
+    ASSERT_NO_THROW(DataSourceClientContainer("memory", config));
 
     config->set("zones", ElementPtr());
     ASSERT_THROW(DataSourceClientContainer("memory", config),