Browse Source

[master] Merge branch 'trac2212-2'

JINMEI Tatuya 12 years ago
parent
commit
9281b972a8

+ 14 - 0
src/bin/auth/auth_messages.mes

@@ -61,6 +61,15 @@ the message.
 A debug message, showing when the separate thread for maintaining data
 A debug message, showing when the separate thread for maintaining data
 source clients receives a command from the manager.
 source clients receives a command from the manager.
 
 
+% AUTH_DATASRC_CLIENTS_BUILDER_COMMAND_ERROR command execution failure: %1
+The separate thread for maintaining data source clients failed to complete a
+command given by the main thread.  In most cases this is some kind of
+configuration or temporary error such as an attempt to load a non-existent
+zone or a temporary DB connection failure.  So the event is just logged and
+the thread keeps running.  In some rare cases, however, this may indicate an
+internal bug and it may be better to restart the entire program, so the log
+message should be carefully examined.
+
 % AUTH_DATASRC_CLIENTS_BUILDER_FAILED data source builder thread stopped due to an exception: %1
 % AUTH_DATASRC_CLIENTS_BUILDER_FAILED data source builder thread stopped due to an exception: %1
 The separate thread for maintaining data source clients has been
 The separate thread for maintaining data source clients has been
 terminated due to some uncaught exception.  When this happens, the
 terminated due to some uncaught exception.  When this happens, the
@@ -79,6 +88,11 @@ indicate some run time failure than program errors.  As in the other
 failure case, the thread terminates the entire process immediately
 failure case, the thread terminates the entire process immediately
 after logging this message.
 after logging this message.
 
 
+% AUTH_DATASRC_CLIENTS_BUILDER_LOAD_ZONE loaded zone %1/%2
+This debug message is issued when the separate thread for maintaining data
+source clients successfully loaded the named zone of the named class as a
+result of the 'loadzone' command.
+
 % AUTH_DATASRC_CLIENTS_BUILDER_RECONFIGURE_CONFIG_ERROR Error in data source configuration: %1
 % AUTH_DATASRC_CLIENTS_BUILDER_RECONFIGURE_CONFIG_ERROR Error in data source configuration: %1
 The thread for maintaining data source clients has received a command to
 The thread for maintaining data source clients has received a command to
 reconfigure, but the parameter data (the new configuration) contains an
 reconfigure, but the parameter data (the new configuration) contains an

+ 124 - 2
src/bin/auth/datasrc_clients_mgr.h

@@ -27,6 +27,7 @@
 
 
 #include <datasrc/data_source.h>
 #include <datasrc/data_source.h>
 #include <datasrc/client_list.h>
 #include <datasrc/client_list.h>
+#include <datasrc/memory/zone_writer.h>
 
 
 #include <auth/auth_log.h>
 #include <auth/auth_log.h>
 #include <auth/datasrc_config.h>
 #include <auth/datasrc_config.h>
@@ -37,6 +38,7 @@
 #include <boost/noncopyable.hpp>
 #include <boost/noncopyable.hpp>
 
 
 #include <exception>
 #include <exception>
+#include <cassert>
 #include <list>
 #include <list>
 #include <utility>
 #include <utility>
 
 
@@ -55,6 +57,9 @@ enum CommandID {
     RECONFIGURE,  ///< Reconfigure the datasource client lists,
     RECONFIGURE,  ///< Reconfigure the datasource client lists,
                   ///  the argument to the command is the full new
                   ///  the argument to the command is the full new
                   ///  datasources configuration.
                   ///  datasources configuration.
+    LOADZONE,     ///< Load a new version of zone into a memory,
+                  ///  the argument to the command is a map containing 'class'
+                  ///  and 'origin' elements, both should have been validated.
     SHUTDOWN,     ///< Shutdown the builder; no argument
     SHUTDOWN,     ///< Shutdown the builder; no argument
     NUM_COMMANDS
     NUM_COMMANDS
 };
 };
@@ -290,7 +295,23 @@ namespace datasrc_clientmgr_internal {
 /// threads or locks.
 /// threads or locks.
 template <typename MutexType, typename CondVarType>
 template <typename MutexType, typename CondVarType>
 class DataSrcClientsBuilderBase : boost::noncopyable {
 class DataSrcClientsBuilderBase : boost::noncopyable {
+private:
+    typedef std::map<dns::RRClass,
+                     boost::shared_ptr<datasrc::ConfigurableClientList> >
+    ClientListsMap;
+
 public:
 public:
+    /// \brief Internal errors in handling commands.
+    ///
+    /// This exception is expected to be caught within the
+    /// \c DataSrcClientsBuilder implementation, but is defined as public
+    /// so tests can be checked it.
+    class InternalCommandError : public isc::Exception {
+    public:
+        InternalCommandError(const char* file, size_t line, const char* what) :
+            isc::Exception(file, line, what) {}
+    };
+
     /// \brief Constructor.
     /// \brief Constructor.
     ///
     ///
     /// It simply sets up a local copy of shared data with the manager.
     /// It simply sets up a local copy of shared data with the manager.
@@ -365,6 +386,11 @@ private:
         }
         }
     }
     }
 
 
+    void doLoadZone(const isc::data::ConstElementPtr& arg);
+    boost::shared_ptr<datasrc::memory::ZoneWriter> getZoneWriter(
+        datasrc::ConfigurableClientList& client_list,
+        const dns::RRClass& rrclass, const dns::Name& origin);
+
     // The following are shared with the manager
     // The following are shared with the manager
     std::list<Command>* command_queue_;
     std::list<Command>* command_queue_;
     CondVarType* cond_;
     CondVarType* cond_;
@@ -397,7 +423,13 @@ DataSrcClientsBuilderBase<MutexType, CondVarType>::run() {
             } // the lock is released here.
             } // the lock is released here.
 
 
             while (keep_running && !current_commands.empty()) {
             while (keep_running && !current_commands.empty()) {
-                keep_running = handleCommand(current_commands.front());
+                try {
+                    keep_running = handleCommand(current_commands.front());;
+                } catch (const InternalCommandError& e) {
+                    LOG_ERROR(auth_logger,
+                              AUTH_DATASRC_CLIENTS_BUILDER_COMMAND_ERROR).
+                        arg(e.what());
+                }
                 current_commands.pop_front();
                 current_commands.pop_front();
             }
             }
         }
         }
@@ -426,7 +458,7 @@ DataSrcClientsBuilderBase<MutexType, CondVarType>::handleCommand(
     }
     }
 
 
     const boost::array<const char*, NUM_COMMANDS> command_desc = {
     const boost::array<const char*, NUM_COMMANDS> command_desc = {
-        {"NOOP", "RECONFIGURE", "SHUTDOWN"}
+        {"NOOP", "RECONFIGURE", "LOADZONE", "SHUTDOWN"}
     };
     };
     LOG_DEBUG(auth_logger, DBGLVL_TRACE_BASIC,
     LOG_DEBUG(auth_logger, DBGLVL_TRACE_BASIC,
               AUTH_DATASRC_CLIENTS_BUILDER_COMMAND).arg(command_desc.at(cid));
               AUTH_DATASRC_CLIENTS_BUILDER_COMMAND).arg(command_desc.at(cid));
@@ -434,6 +466,9 @@ DataSrcClientsBuilderBase<MutexType, CondVarType>::handleCommand(
     case RECONFIGURE:
     case RECONFIGURE:
         doReconfigure(command.second);
         doReconfigure(command.second);
         break;
         break;
+    case LOADZONE:
+        doLoadZone(command.second);
+        break;
     case SHUTDOWN:
     case SHUTDOWN:
         return (false);
         return (false);
     case NOOP:
     case NOOP:
@@ -444,6 +479,93 @@ DataSrcClientsBuilderBase<MutexType, CondVarType>::handleCommand(
     }
     }
     return (true);
     return (true);
 }
 }
+
+template <typename MutexType, typename CondVarType>
+void
+DataSrcClientsBuilderBase<MutexType, CondVarType>::doLoadZone(
+    const isc::data::ConstElementPtr& arg)
+{
+    // We assume some basic level validation as this method can only be
+    // called via the manager in practice.  manager is expected to do the
+    // minimal validation.
+    assert(arg);
+    assert(arg->get("class"));
+    assert(arg->get("origin"));
+
+    const dns::RRClass rrclass(arg->get("class")->stringValue());
+    const dns::Name origin(arg->get("origin")->stringValue());
+    ClientListsMap::iterator found = (*clients_map_)->find(rrclass);
+    if (found == (*clients_map_)->end()) {
+        isc_throw(InternalCommandError, "failed to load a zone " << origin <<
+                  "/" << rrclass << ": not configured for the class");
+    }
+
+    boost::shared_ptr<datasrc::ConfigurableClientList> client_list =
+        found->second;
+    assert(client_list);
+
+    try {
+        boost::shared_ptr<datasrc::memory::ZoneWriter> zwriter =
+            getZoneWriter(*client_list, rrclass, origin);
+
+        zwriter->load(); // this can take time but doesn't cause a race
+        {   // install() can cause a race and must be in a critical section
+            typename MutexType::Locker locker(*map_mutex_);
+            zwriter->install();
+        }
+        LOG_DEBUG(auth_logger, DBG_AUTH_OPS,
+                  AUTH_DATASRC_CLIENTS_BUILDER_LOAD_ZONE)
+            .arg(origin).arg(rrclass);
+
+        // same as load(). We could let the destructor do it, but do it
+        // ourselves explicitly just in case.
+        zwriter->cleanup();
+    } catch (const InternalCommandError& ex) {
+        throw;     // this comes from getZoneWriter.  just let it go through.
+    } catch (const isc::Exception& ex) {
+        // We catch our internal exceptions (which will be just ignored) and
+        // propagated others (which should generally be considered fatal and
+        // will make the thread terminate)
+        isc_throw(InternalCommandError, "failed to load a zone " << origin <<
+                  "/" << rrclass << ": error occurred in reload: " <<
+                  ex.what());
+    }
+}
+
+// A dedicated subroutine of doLoadZone().  Separated just for keeping the
+// main method concise.
+template <typename MutexType, typename CondVarType>
+boost::shared_ptr<datasrc::memory::ZoneWriter>
+DataSrcClientsBuilderBase<MutexType, CondVarType>::getZoneWriter(
+    datasrc::ConfigurableClientList& client_list,
+    const dns::RRClass& rrclass, const dns::Name& origin)
+{
+    const datasrc::ConfigurableClientList::ZoneWriterPair writerpair =
+        client_list.getCachedZoneWriter(origin);
+
+    switch (writerpair.first) {
+    case datasrc::ConfigurableClientList::ZONE_RELOADED: // XXX misleading name
+        assert(writerpair.second);
+        return (writerpair.second);
+    case datasrc::ConfigurableClientList::ZONE_NOT_FOUND:
+        isc_throw(InternalCommandError, "failed to load zone " << origin
+                  << "/" << rrclass << ": not found in any configured "
+                  "data source.");
+    case datasrc::ConfigurableClientList::ZONE_NOT_CACHED:
+        isc_throw(InternalCommandError, "failed to load zone " << origin
+                  << "/" << rrclass << ": not served from memory");
+    case datasrc::ConfigurableClientList::CACHE_DISABLED:
+        // This is an internal error. Auth server must have the cache
+        // enabled.
+        isc_throw(InternalCommandError, "failed to load zone " << origin
+                  << "/" << rrclass << ": internal failure, in-memory cache "
+                  "is somehow disabled");
+    }
+
+    // all cases above should either return or throw, but some compilers
+    // still need a return statement
+    return (boost::shared_ptr<datasrc::memory::ZoneWriter>());
+}
 } // namespace datasrc_clientmgr_internal
 } // namespace datasrc_clientmgr_internal
 
 
 /// \brief Shortcut type for normal data source clients manager.
 /// \brief Shortcut type for normal data source clients manager.

+ 340 - 13
src/bin/auth/tests/datasrc_clients_builder_unittest.cc

@@ -12,36 +12,62 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 // PERFORMANCE OF THIS SOFTWARE.
 
 
+#include <util/unittests/check_valgrind.h>
+
+#include <dns/name.h>
+#include <dns/rrclass.h>
+
 #include <cc/data.h>
 #include <cc/data.h>
 
 
+#include <datasrc/client.h>
+#include <datasrc/factory.h>
+
 #include <auth/datasrc_clients_mgr.h>
 #include <auth/datasrc_clients_mgr.h>
+#include <auth/datasrc_config.h>
+
+#include <testutils/dnsmessage_test.h>
+
 #include "test_datasrc_clients_mgr.h"
 #include "test_datasrc_clients_mgr.h"
+#include "datasrc_util.h"
 
 
 #include <gtest/gtest.h>
 #include <gtest/gtest.h>
 
 
 #include <boost/function.hpp>
 #include <boost/function.hpp>
 
 
+#include <cstdlib>
+#include <string>
+#include <sstream>
+
 using isc::data::ConstElementPtr;
 using isc::data::ConstElementPtr;
+using namespace isc::dns;
+using namespace isc::data;
 using namespace isc::datasrc;
 using namespace isc::datasrc;
 using namespace isc::auth::datasrc_clientmgr_internal;
 using namespace isc::auth::datasrc_clientmgr_internal;
+using namespace isc::auth::unittest;
+using namespace isc::testutils;
 
 
 namespace {
 namespace {
 class DataSrcClientsBuilderTest : public ::testing::Test {
 class DataSrcClientsBuilderTest : public ::testing::Test {
 protected:
 protected:
     DataSrcClientsBuilderTest() :
     DataSrcClientsBuilderTest() :
+        clients_map(new std::map<RRClass,
+                    boost::shared_ptr<ConfigurableClientList> >),
         builder(&command_queue, &cond, &queue_mutex, &clients_map, &map_mutex),
         builder(&command_queue, &cond, &queue_mutex, &clients_map, &map_mutex),
-        cond(command_queue, delayed_command_queue),
+        cond(command_queue, delayed_command_queue), rrclass(RRClass::IN()),
         shutdown_cmd(SHUTDOWN, ConstElementPtr()),
         shutdown_cmd(SHUTDOWN, ConstElementPtr()),
         noop_cmd(NOOP, ConstElementPtr())
         noop_cmd(NOOP, ConstElementPtr())
     {}
     {}
 
 
-    TestDataSrcClientsBuilder builder;
+    void configureZones();      // used for loadzone related tests
+
+    ClientListMapPtr clients_map; // configured clients
     std::list<Command> command_queue; // test command queue
     std::list<Command> command_queue; // test command queue
     std::list<Command> delayed_command_queue; // commands available after wait
     std::list<Command> delayed_command_queue; // commands available after wait
-    ClientListMapPtr clients_map; // configured clients
+    TestDataSrcClientsBuilder builder;
     TestCondVar cond;
     TestCondVar cond;
     TestMutex queue_mutex;
     TestMutex queue_mutex;
     TestMutex map_mutex;
     TestMutex map_mutex;
+    const RRClass rrclass;
     const Command shutdown_cmd;
     const Command shutdown_cmd;
     const Command noop_cmd;
     const Command noop_cmd;
 };
 };
@@ -74,13 +100,20 @@ TEST_F(DataSrcClientsBuilderTest, exception) {
     // them.  Right now, we simply abort to prevent the system from running
     // them.  Right now, we simply abort to prevent the system from running
     // with half-broken state.  Eventually we should introduce a better
     // with half-broken state.  Eventually we should introduce a better
     // error handling.
     // error handling.
-    command_queue.push_back(noop_cmd);
+    if (!isc::util::unittests::runningOnValgrind()) {
-    queue_mutex.throw_from_noop = TestMutex::EXCLASS;
+        command_queue.push_back(noop_cmd);
-    EXPECT_DEATH_IF_SUPPORTED({builder.run();}, "");
+        queue_mutex.throw_from_noop = TestMutex::EXCLASS;
+        EXPECT_DEATH_IF_SUPPORTED({builder.run();}, "");
+
+        command_queue.push_back(noop_cmd);
+        queue_mutex.throw_from_noop = TestMutex::INTEGER;
+        EXPECT_DEATH_IF_SUPPORTED({builder.run();}, "");
+    }
 
 
     command_queue.push_back(noop_cmd);
     command_queue.push_back(noop_cmd);
-    queue_mutex.throw_from_noop = TestMutex::INTEGER;
+    command_queue.push_back(shutdown_cmd); // we need to stop the loop
-    EXPECT_DEATH_IF_SUPPORTED({builder.run();}, "");
+    queue_mutex.throw_from_noop = TestMutex::INTERNAL;
+    builder.run();
 }
 }
 
 
 TEST_F(DataSrcClientsBuilderTest, condWait) {
 TEST_F(DataSrcClientsBuilderTest, condWait) {
@@ -106,10 +139,10 @@ TEST_F(DataSrcClientsBuilderTest, reconfigure) {
     Command reconfig_cmd(RECONFIGURE, ConstElementPtr());
     Command reconfig_cmd(RECONFIGURE, ConstElementPtr());
 
 
     // Initially, no clients should be there
     // Initially, no clients should be there
-    EXPECT_EQ(ClientListMapPtr(), clients_map);
+    EXPECT_TRUE(clients_map->empty());
 
 
     // A config that doesn't do much except be accepted
     // A config that doesn't do much except be accepted
-    ConstElementPtr good_config = isc::data::Element::fromJSON(
+    ConstElementPtr good_config = Element::fromJSON(
         "{"
         "{"
         "\"IN\": [{"
         "\"IN\": [{"
         "   \"type\": \"MasterFiles\","
         "   \"type\": \"MasterFiles\","
@@ -121,7 +154,7 @@ TEST_F(DataSrcClientsBuilderTest, reconfigure) {
 
 
     // A configuration that is 'correct' in the top-level, but contains
     // A configuration that is 'correct' in the top-level, but contains
     // bad data for the type it specifies
     // bad data for the type it specifies
-    ConstElementPtr bad_config = isc::data::Element::fromJSON(
+    ConstElementPtr bad_config = Element::fromJSON(
         "{"
         "{"
         "\"IN\": [{"
         "\"IN\": [{"
         "   \"type\": \"MasterFiles\","
         "   \"type\": \"MasterFiles\","
@@ -142,7 +175,7 @@ TEST_F(DataSrcClientsBuilderTest, reconfigure) {
     // If a 'bad' command argument got here, the config validation should
     // If a 'bad' command argument got here, the config validation should
     // have failed already, but still, the handler should return true,
     // have failed already, but still, the handler should return true,
     // and the clients_map should not be updated.
     // and the clients_map should not be updated.
-    reconfig_cmd.second = isc::data::Element::create("{ \"foo\": \"bar\" }");
+    reconfig_cmd.second = Element::create("{ \"foo\": \"bar\" }");
     EXPECT_TRUE(builder.handleCommand(reconfig_cmd));
     EXPECT_TRUE(builder.handleCommand(reconfig_cmd));
     EXPECT_EQ(working_config_clients, clients_map);
     EXPECT_EQ(working_config_clients, clients_map);
     // Building failed, so map mutex should not have been locked again
     // Building failed, so map mutex should not have been locked again
@@ -173,7 +206,7 @@ TEST_F(DataSrcClientsBuilderTest, reconfigure) {
     EXPECT_EQ(2, map_mutex.lock_count);
     EXPECT_EQ(2, map_mutex.lock_count);
 
 
     // And finally, try an empty config to disable all datasource clients
     // And finally, try an empty config to disable all datasource clients
-    reconfig_cmd.second = isc::data::Element::createMap();
+    reconfig_cmd.second = Element::createMap();
     EXPECT_TRUE(builder.handleCommand(reconfig_cmd));
     EXPECT_TRUE(builder.handleCommand(reconfig_cmd));
     EXPECT_EQ(0, clients_map->size());
     EXPECT_EQ(0, clients_map->size());
     EXPECT_EQ(3, map_mutex.lock_count);
     EXPECT_EQ(3, map_mutex.lock_count);
@@ -193,4 +226,298 @@ TEST_F(DataSrcClientsBuilderTest, badCommand) {
                  isc::Unexpected);
                  isc::Unexpected);
 }
 }
 
 
+// A helper function commonly used for the "loadzone" command tests.
+// It configures the given data source client lists with a memory data source
+// containing two zones, and checks the zones are correctly loaded.
+void
+zoneChecks(ClientListMapPtr clients_map, RRClass rrclass) {
+    EXPECT_EQ(ZoneFinder::SUCCESS, clients_map->find(rrclass)->second->
+              find(Name("ns.test1.example")).finder_->
+              find(Name("ns.test1.example"), RRType::A())->code);
+    EXPECT_EQ(ZoneFinder::NXRRSET, clients_map->find(rrclass)->second->
+              find(Name("ns.test1.example")).finder_->
+              find(Name("ns.test1.example"), RRType::AAAA())->code);
+    EXPECT_EQ(ZoneFinder::SUCCESS, clients_map->find(rrclass)->second->
+              find(Name("ns.test2.example")).finder_->
+              find(Name("ns.test2.example"), RRType::A())->code);
+    EXPECT_EQ(ZoneFinder::NXRRSET, clients_map->find(rrclass)->second->
+              find(Name("ns.test2.example")).finder_->
+              find(Name("ns.test2.example"), RRType::AAAA())->code);
+}
+
+// Another helper that checks after completing loadzone command.
+void
+newZoneChecks(ClientListMapPtr clients_map, RRClass rrclass) {
+    EXPECT_EQ(ZoneFinder::SUCCESS, clients_map->find(rrclass)->second->
+              find(Name("ns.test1.example")).finder_->
+              find(Name("ns.test1.example"), RRType::A())->code);
+    // now test1.example should have ns/AAAA
+    EXPECT_EQ(ZoneFinder::SUCCESS, clients_map->find(rrclass)->second->
+              find(Name("ns.test1.example")).finder_->
+              find(Name("ns.test1.example"), RRType::AAAA())->code);
+
+    // test2.example shouldn't change
+    EXPECT_EQ(ZoneFinder::SUCCESS, clients_map->find(rrclass)->second->
+              find(Name("ns.test2.example")).finder_->
+              find(Name("ns.test2.example"), RRType::A())->code);
+    EXPECT_EQ(ZoneFinder::NXRRSET,
+              clients_map->find(rrclass)->second->
+              find(Name("ns.test2.example")).finder_->
+              find(Name("ns.test2.example"), RRType::AAAA())->code);
+}
+
+void
+DataSrcClientsBuilderTest::configureZones() {
+    ASSERT_EQ(0, std::system(INSTALL_PROG " -c " TEST_DATA_DIR "/test1.zone.in "
+                             TEST_DATA_BUILDDIR "/test1.zone.copied"));
+    ASSERT_EQ(0, std::system(INSTALL_PROG " -c " TEST_DATA_DIR "/test2.zone.in "
+                             TEST_DATA_BUILDDIR "/test2.zone.copied"));
+
+    const ConstElementPtr config(
+        Element::fromJSON(
+            "{"
+            "\"IN\": [{"
+            "   \"type\": \"MasterFiles\","
+            "   \"params\": {"
+            "       \"test1.example\": \"" +
+            std::string(TEST_DATA_BUILDDIR "/test1.zone.copied") + "\","
+            "       \"test2.example\": \"" +
+            std::string(TEST_DATA_BUILDDIR "/test2.zone.copied") + "\""
+            "   },"
+            "   \"cache-enable\": true"
+            "}]}"));
+    clients_map = configureDataSource(config);
+    zoneChecks(clients_map, rrclass);
+}
+
+TEST_F(DataSrcClientsBuilderTest, loadZone) {
+    // pre test condition checks
+    EXPECT_EQ(0, map_mutex.lock_count);
+    EXPECT_EQ(0, map_mutex.unlock_count);
+
+    configureZones();
+
+    EXPECT_EQ(0, system(INSTALL_PROG " -c " TEST_DATA_DIR
+                        "/test1-new.zone.in "
+                        TEST_DATA_BUILDDIR "/test1.zone.copied"));
+    EXPECT_EQ(0, system(INSTALL_PROG " -c " TEST_DATA_DIR
+                        "/test2-new.zone.in "
+                        TEST_DATA_BUILDDIR "/test2.zone.copied"));
+
+    const Command loadzone_cmd(LOADZONE, Element::fromJSON(
+                                   "{\"class\": \"IN\","
+                                   " \"origin\": \"test1.example\"}"));
+    EXPECT_TRUE(builder.handleCommand(loadzone_cmd));
+    EXPECT_EQ(1, map_mutex.lock_count); // we should have acquired the lock
+    EXPECT_EQ(1, map_mutex.unlock_count); // and released it.
+
+    newZoneChecks(clients_map, rrclass);
+}
+
+TEST_F(DataSrcClientsBuilderTest,
+#ifdef USE_STATIC_LINK
+       DISABLED_loadZoneSQLite3
+#else
+       loadZoneSQLite3
+#endif
+    )
+{
+    // Prepare the database first
+    const std::string test_db = TEST_DATA_BUILDDIR "/auth_test.sqlite3.copied";
+    std::stringstream ss("example.org. 3600 IN SOA . . 0 0 0 0 0\n");
+    createSQLite3DB(rrclass, Name("example.org"), test_db.c_str(), ss);
+    // This describes the data source in the configuration
+    const ConstElementPtr config(Element::fromJSON("{"
+        "\"IN\": [{"
+        "    \"type\": \"sqlite3\","
+        "    \"params\": {\"database_file\": \"" + test_db + "\"},"
+        "    \"cache-enable\": true,"
+        "    \"cache-zones\": [\"example.org\"]"
+        "}]}"));
+    clients_map = configureDataSource(config);
+
+    // Check that the A record at www.example.org does not exist
+    EXPECT_EQ(ZoneFinder::NXDOMAIN,
+              clients_map->find(rrclass)->second->
+              find(Name("example.org")).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);
+    sql_updater->addRRset(
+        *textToRRset("www.example.org. 60 IN A 192.0.2.1"));
+    sql_updater->commit();
+
+    EXPECT_EQ(ZoneFinder::NXDOMAIN,
+              clients_map->find(rrclass)->second->
+              find(Name("example.org")).finder_->
+              find(Name("www.example.org"), RRType::A())->code);
+
+    // Now send the command to reload it
+    const Command loadzone_cmd(LOADZONE, Element::fromJSON(
+                                   "{\"class\": \"IN\","
+                                   " \"origin\": \"example.org\"}"));
+    EXPECT_TRUE(builder.handleCommand(loadzone_cmd));
+    // And now it should be present too.
+    EXPECT_EQ(ZoneFinder::SUCCESS,
+              clients_map->find(rrclass)->second->
+              find(Name("example.org")).finder_->
+              find(Name("www.example.org"), RRType::A())->code);
+
+    // An error case: the zone has no configuration. (note .com here)
+    const Command nozone_cmd(LOADZONE, Element::fromJSON(
+                                 "{\"class\": \"IN\","
+                                 " \"origin\": \"example.com\"}"));
+    EXPECT_THROW(builder.handleCommand(nozone_cmd),
+                 TestDataSrcClientsBuilder::InternalCommandError);
+    // The previous zone is not hurt in any way
+    EXPECT_EQ(ZoneFinder::SUCCESS, clients_map->find(rrclass)->second->
+              find(Name("example.org")).finder_->
+              find(Name("example.org"), RRType::SOA())->code);
+
+    // attempt of reloading a zone but in-memory cache is disabled.
+    const ConstElementPtr config2(Element::fromJSON("{"
+        "\"IN\": [{"
+        "    \"type\": \"sqlite3\","
+        "    \"params\": {\"database_file\": \"" + test_db + "\"},"
+        "    \"cache-enable\": false,"
+        "    \"cache-zones\": [\"example.org\"]"
+        "}]}"));
+    clients_map = configureDataSource(config2);
+    EXPECT_THROW(builder.handleCommand(
+                     Command(LOADZONE, Element::fromJSON(
+                                 "{\"class\": \"IN\","
+                                 " \"origin\": \"example.org\"}"))),
+                 TestDataSrcClientsBuilder::InternalCommandError);
+
+    // basically impossible case: in-memory cache is completely disabled.
+    // In this implementation of manager-builder, this should never happen,
+    // but it catches it like other configuration error and keeps going.
+    clients_map->clear();
+    boost::shared_ptr<ConfigurableClientList> nocache_list(
+        new ConfigurableClientList(rrclass));
+    nocache_list->configure(
+        Element::fromJSON(
+            "[{\"type\": \"sqlite3\","
+            "  \"params\": {\"database_file\": \"" + test_db + "\"},"
+            "  \"cache-enable\": true,"
+            "  \"cache-zones\": [\"example.org\"]"
+            "}]"), false);           // false = disable cache
+    (*clients_map)[rrclass] = nocache_list;
+    EXPECT_THROW(builder.handleCommand(
+                     Command(LOADZONE, Element::fromJSON(
+                                 "{\"class\": \"IN\","
+                                 " \"origin\": \"example.org\"}"))),
+                 TestDataSrcClientsBuilder::InternalCommandError);
+}
+
+TEST_F(DataSrcClientsBuilderTest, loadBrokenZone) {
+    configureZones();
+
+    ASSERT_EQ(0, std::system(INSTALL_PROG " -c " TEST_DATA_DIR
+                             "/test1-broken.zone.in "
+                             TEST_DATA_BUILDDIR "/test1.zone.copied"));
+    // there's an error in the new zone file.  reload will be rejected.
+    const Command loadzone_cmd(LOADZONE, Element::fromJSON(
+                                   "{\"class\": \"IN\","
+                                   " \"origin\": \"test1.example\"}"));
+    EXPECT_THROW(builder.handleCommand(loadzone_cmd),
+                 TestDataSrcClientsBuilder::InternalCommandError);
+    zoneChecks(clients_map, rrclass);     // zone shouldn't be replaced
+}
+
+TEST_F(DataSrcClientsBuilderTest, loadUnreadableZone) {
+    configureZones();
+
+    // install the zone file as unreadable
+    ASSERT_EQ(0, std::system(INSTALL_PROG " -c -m 000 " TEST_DATA_DIR
+                             "/test1.zone.in "
+                             TEST_DATA_BUILDDIR "/test1.zone.copied"));
+    const Command loadzone_cmd(LOADZONE, Element::fromJSON(
+                                   "{\"class\": \"IN\","
+                                   " \"origin\": \"test1.example\"}"));
+    EXPECT_THROW(builder.handleCommand(loadzone_cmd),
+                 TestDataSrcClientsBuilder::InternalCommandError);
+    zoneChecks(clients_map, rrclass);     // zone shouldn't be replaced
+}
+
+TEST_F(DataSrcClientsBuilderTest, loadZoneWithoutDataSrc) {
+    // try to execute load command without configuring the zone beforehand.
+    // it should fail.
+    EXPECT_THROW(builder.handleCommand(
+                     Command(LOADZONE,
+                             Element::fromJSON(
+                                 "{\"class\": \"IN\", "
+                                 " \"origin\": \"test1.example\"}"))),
+                 TestDataSrcClientsBuilder::InternalCommandError);
+}
+
+TEST_F(DataSrcClientsBuilderTest, loadZoneInvalidParams) {
+    configureZones();
+
+    if (!isc::util::unittests::runningOnValgrind()) {
+        // null arg: this causes assertion failure
+        EXPECT_DEATH_IF_SUPPORTED({
+                builder.handleCommand(Command(LOADZONE, ElementPtr()));
+            }, "");
+    }
+
+    // zone class is bogus (note that this shouldn't happen except in tests)
+    EXPECT_THROW(builder.handleCommand(
+                     Command(LOADZONE,
+                             Element::fromJSON(
+                                 "{\"origin\": \"test1.example\","
+                                 " \"class\": \"no_such_class\"}"))),
+                 InvalidRRClass);
+
+    // not a string
+    EXPECT_THROW(builder.handleCommand(
+                     Command(LOADZONE,
+                             Element::fromJSON(
+                                 "{\"origin\": \"test1.example\","
+                                 " \"class\": 1}"))),
+                 isc::data::TypeError);
+
+    // class or origin is missing: result in assertion failure
+    if (!isc::util::unittests::runningOnValgrind()) {
+        EXPECT_DEATH_IF_SUPPORTED({
+                builder.handleCommand(
+                    Command(LOADZONE,
+                            Element::fromJSON(
+                                "{\"origin\": \"test1.example\"}")));
+            }, "");
+        EXPECT_DEATH_IF_SUPPORTED({
+                builder.handleCommand(Command(LOADZONE,
+                                              Element::fromJSON(
+                                                  "{\"class\": \"IN\"}")));
+            }, "");
+    }
+
+    // zone doesn't exist in the data source
+    EXPECT_THROW(
+        builder.handleCommand(
+            Command(LOADZONE,
+                    Element::fromJSON(
+                        "{\"class\": \"IN\", \"origin\": \"xx\"}"))),
+        TestDataSrcClientsBuilder::InternalCommandError);
+
+    // origin is bogus
+    EXPECT_THROW(builder.handleCommand(
+                     Command(LOADZONE,
+                             Element::fromJSON(
+                                 "{\"class\": \"IN\", \"origin\": \"...\"}"))),
+                 EmptyLabel);
+    EXPECT_THROW(builder.handleCommand(
+                     Command(LOADZONE,
+                             Element::fromJSON(
+                                 "{\"origin\": 10, \"class\": 1}"))),
+                 isc::data::TypeError);
+}
+
 } // unnamed namespace
 } // unnamed namespace

+ 2 - 0
src/bin/auth/tests/test_datasrc_clients_mgr.cc

@@ -50,6 +50,8 @@ TestDataSrcClientsBuilder::doNoop() {
         isc_throw(Exception, "test exception");
         isc_throw(Exception, "test exception");
     case TestMutex::INTEGER:
     case TestMutex::INTEGER:
         throw 42;
         throw 42;
+    case TestMutex::INTERNAL:
+        isc_throw(InternalCommandError, "internal error, should be ignored");
     }
     }
 }
 }
 } // namespace datasrc_clientmgr_internal
 } // namespace datasrc_clientmgr_internal

+ 2 - 1
src/bin/auth/tests/test_datasrc_clients_mgr.h

@@ -43,7 +43,8 @@ public:
     // None: no throw from specialized doNoop()
     // None: no throw from specialized doNoop()
     // EXCLASS: throw some exception class object
     // EXCLASS: throw some exception class object
     // INTEGER: throw an integer
     // INTEGER: throw an integer
-    enum ExceptionFromNoop { NONE, EXCLASS, INTEGER };
+    // INTERNAL: internal error (shouldn't terminate the thread)
+    enum ExceptionFromNoop { NONE, EXCLASS, INTEGER, INTERNAL };
 
 
     TestMutex() : lock_count(0), unlock_count(0), noop_count(0),
     TestMutex() : lock_count(0), unlock_count(0), noop_count(0),
                   throw_from_noop(NONE)
                   throw_from_noop(NONE)