Browse Source

initial implementaiton for trac #456: reloding in memory zone

git-svn-id: svn://bind10.isc.org/svn/bind10/branches/trac467@4116 e5f2f494-b856-4b98-b285-d166d9295462
JINMEI Tatuya 14 years ago
parent
commit
2a507893c9

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

@@ -40,6 +40,7 @@ b10_auth_SOURCES = query.cc query.h
 b10_auth_SOURCES += auth_srv.cc auth_srv.h
 b10_auth_SOURCES += change_user.cc change_user.h
 b10_auth_SOURCES += config.cc config.h
+b10_auth_SOURCES += command.cc command.h
 b10_auth_SOURCES += common.h
 b10_auth_SOURCES += statistics.cc statistics.h
 b10_auth_SOURCES += main.cc

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

@@ -64,6 +64,24 @@
         "command_name": "sendstats",
         "command_description": "Send data to a statistics module at once",
         "command_args": []
+      },
+      {
+        "command_name": "loadzone",
+        "command_description": "TBD",
+        "command_args": [
+          {
+            "item_name": "class", "item_type": "string",
+            "item_optional": true, "item_default": "IN"
+          },
+	  {
+            "item_name": "origin", "item_type": "string",
+            "item_optional": false, "item_default": ""
+          },
+	  {
+            "item_name": "datasrc", "item_type": "string",
+            "item_optional": true, "item_default": "memory"
+          }
+        ]
       }
     ]
   }

+ 11 - 2
src/bin/auth/auth_srv.cc

@@ -195,11 +195,20 @@ private:
 
 AuthSrv::AuthSrv(const bool use_cache, AbstractXfroutClient& xfrout_client) :
     impl_(new AuthSrvImpl(use_cache, xfrout_client)),
+    io_service_(NULL),
     checkin_(new ConfigChecker(this)),
     dns_lookup_(new MessageLookup(this)),
     dns_answer_(new MessageAnswer(this))
 {}
 
+void
+AuthSrv::stop() {
+    if (io_service_ == NULL) {
+        throw FatalError("Assumption failure; server is stopped before start");
+    }
+    io_service_->stop();
+}
+
 AuthSrv::~AuthSrv() {
     delete impl_;
     delete checkin_;
@@ -299,8 +308,8 @@ AuthSrv::getConfigSession() const {
     return (impl_->config_session_);
 }
 
-AuthSrv::ConstMemoryDataSrcPtr
-AuthSrv::getMemoryDataSrc(const RRClass& rrclass) const {
+AuthSrv::MemoryDataSrcPtr
+AuthSrv::getMemoryDataSrc(const RRClass& rrclass) {
     // XXX: for simplicity, we only support the IN class right now.
     if (rrclass != impl_->memory_datasrc_class_) {
         isc_throw(InvalidParameter,

+ 10 - 2
src/bin/auth/auth_srv.h

@@ -87,6 +87,15 @@ public:
     ~AuthSrv();
     //@}
 
+    /// Stop the server.
+    ///
+    /// It stops the internal event loop of the server and subsequently
+    /// returns the control to the top level context.
+    /// The server must have been associated with an \c IOService object;
+    /// otherwise an exception of \c FatalError will be thrown.
+    /// This method never throws an exception otherwise.
+    void stop();
+
     /// \brief Process an incoming DNS message, then signal 'server' to resume 
     ///
     /// A DNS query (or other message) has been received by a \c DNSServer
@@ -265,8 +274,7 @@ 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.
-    ConstMemoryDataSrcPtr
-    getMemoryDataSrc(const isc::dns::RRClass& rrclass) const;
+    MemoryDataSrcPtr getMemoryDataSrc(const isc::dns::RRClass& rrclass);
 
     /// Sets or replaces the in-memory data source of the specified RR class.
     ///

+ 252 - 0
src/bin/auth/command.cc

@@ -0,0 +1,252 @@
+// Copyright (C) 2010  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <string>
+
+#include <boost/scoped_ptr.hpp>
+#include <boost/shared_ptr.hpp>
+
+#include <exceptions/exceptions.h>
+
+#include <dns/rrclass.h>
+
+#include <cc/data.h>
+
+#include <datasrc/memory_datasrc.h>
+
+#include <config/ccsession.h>
+
+#include <auth/auth_srv.h>
+#include <auth/command.h>
+
+using namespace std;
+using boost::shared_ptr;
+using boost::scoped_ptr;
+using namespace isc::dns;
+using namespace isc::data;
+using namespace isc::datasrc;
+using namespace isc::config;
+
+namespace {
+/// An exception that is thrown if an error occurs while handling a command
+/// on an \c AuthSrv object.
+///
+/// Currently it's only used internally, since \c execAuthServerCommand()
+/// (which is the only interface to this module) catches all \c isc::
+/// exceptions and converts them.
+class AuthCommandError : public isc::Exception {
+public:
+    AuthCommandError(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) {}
+};
+
+/// An abstract base class that represents a single command identifier
+/// for an \c AuthSrv object.
+///
+/// Each of derived classes of \c AuthCommand, which are hidden inside the
+/// implementation, corresponds to a command executed on \c AuthSrv, such as
+/// "shutdown".  The derived class is responsible to execute the corresponding
+/// command with the given command arguments (if any) in its \c exec()
+/// method.
+///
+/// In the initial implementation the existence of the command classes is
+/// hidden inside the implementation since the only public interface is
+/// \c execAuthServerCommand(), which does not expose this class.
+/// In future, we may want to make this framework more dynamic, i.e.,
+/// registering specific derived classes run time outside of this
+/// implementation.  If and when that happens the definition of the abstract
+/// class will be published.
+class AuthCommand {
+    ///
+    /// \name Constructors and Destructor
+    ///
+    /// Note: The copy constructor and the assignment operator are
+    /// intentionally defined as private to make it explicit that this is a
+    /// pure base class.
+    //@{
+private:
+    AuthCommand(const AuthCommand& source);
+    AuthCommand& operator=(const AuthCommand& source);
+protected:
+    /// \brief The default constructor.
+    ///
+    /// This is intentionally defined as \c protected as this base class should
+    /// never be instantiated (except as part of a derived class).
+    AuthCommand() {}
+public:
+    /// The destructor.
+    virtual ~AuthCommand() {}
+    //@}
+
+    /// Execute a single control command.
+    ///
+    /// Specific derived methods can throw exceptions.  When called via
+    /// \c execAuthServerCommand(), all BIND 10 exceptions are caught
+    /// and converted into an error code.
+    /// The derived method may also throw an exception of class
+    /// \c AuthCommandError when it encounters an internal error, such as
+    /// semantics error on the command arguments.
+    ///
+    /// \param server The \c AuthSrv object on which the command is executed.
+    /// \param args Command specific argument.
+    virtual void exec(AuthSrv& server, isc::data::ConstElementPtr args) = 0;
+};
+
+// Handle the "shutdown" command.  No argument is assumed.
+class ShutdownCommand : public AuthCommand {
+public:
+    virtual void exec(AuthSrv& server, isc::data::ConstElementPtr) {
+        server.stop();
+    }
+};
+
+// Handle the "sendstats" command.  No argument is assumed.
+class SendStatsCommand : public AuthCommand {
+public:
+    virtual void exec(AuthSrv& server, isc::data::ConstElementPtr) {
+        if (server.getVerbose()) {
+            cerr << "[b10-auth] command 'sendstats' received" << endl;
+        }
+        server.submitStatistics();
+    }
+};
+
+// Handle the "loadzone" command.
+class LoadZoneCommand : public AuthCommand {
+public:
+    virtual void exec(AuthSrv& server, isc::data::ConstElementPtr args) {
+        // parse and validate the args.
+        if (!validate(server, args)) {
+            return;
+        }
+
+        // Load a new zone and replace the current zone with the new one.
+        // TODO: eventually this should be incremental or done in some way
+        // that doesn't block other server operations.
+        // TODO: we may (should?) want to check the "last load time" and
+        // the timestamp of the file and skip loading if the file isn't newer.
+        shared_ptr<MemoryZone> newzone(new MemoryZone(oldzone->getClass(),
+                                                      oldzone->getOrigin()));
+        newzone->load(oldzone->getFileName());
+        oldzone->swap(*newzone);
+
+        if (server.getVerbose()) {
+            cerr << "[b10-auth] Loaded zone '" << newzone->getOrigin()
+                 << "'/" << newzone->getClass() << endl;
+        }
+    }
+
+private:
+    shared_ptr<MemoryZone> oldzone; // zone to be updated with the new file.
+
+    // A helper private method to parse and validate command parameters.
+    // On success, it sets 'oldzone' to the zone to be updated.
+    // It returns true if everything is okay; and false if the command is
+    // valid but there's no need for further process.
+    bool validate(AuthSrv& server, isc::data::ConstElementPtr args) {
+        if (args == NULL) {
+            isc_throw(AuthCommandError, "Null argument");
+        }
+
+        // In this initial implementation, we assume memory data source
+        // for class IN by default.
+        ConstElementPtr datasrc_elem = args->get("datasrc");
+        if (datasrc_elem) {
+            if (datasrc_elem->stringValue() == "sqlite3") {
+                if (server.getVerbose()) {
+                    cerr << "[b10-auth] Nothing to do for loading sqlite3"
+                         << endl;
+                }
+                return (false);
+            } else if (datasrc_elem->stringValue() != "memory") {
+                // (note: at this point it's guaranteed that datasrc_elem
+                // is of string type)
+                isc_throw(AuthCommandError,
+                          "Data source type " << datasrc_elem->stringValue()
+                          << " is not supported");
+            }
+        }
+
+        ConstElementPtr class_elem = args->get("class");
+        const RRClass zone_class = class_elem ?
+            RRClass(class_elem->stringValue()) : RRClass::IN();
+
+        AuthSrv::MemoryDataSrcPtr datasrc(server.getMemoryDataSrc(zone_class));
+        if (datasrc == NULL) {
+            isc_throw(AuthCommandError, "Memory data source is disabled");
+        }
+
+        ConstElementPtr origin_elem = args->get("origin");
+        if (!origin_elem) {
+            isc_throw(AuthCommandError, "Zone origin is missing");
+        }
+        const Name origin(origin_elem->stringValue());
+
+        // Get the current zone
+        const MemoryDataSrc::FindResult result = datasrc->findZone(origin);
+        if (result.code != result::SUCCESS) {
+            isc_throw(AuthCommandError, "Zone " << origin <<
+                      " is not found in data source");
+        }
+
+        oldzone = boost::dynamic_pointer_cast<MemoryZone>(result.zone);
+
+        return (true);
+    }
+};
+
+// The factory of command objects.
+AuthCommand*
+createAuthCommand(const string& command_id) {
+    // For the initial implementation we use a naive if-else blocks
+    // (see also createAuthConfigParser())
+    if (command_id == "shutdown") {
+        return (new ShutdownCommand());
+    } else if (command_id == "sendstats") {
+        return (new SendStatsCommand());
+    } else if (command_id == "loadzone") {
+        return (new LoadZoneCommand());
+    } else if (false && command_id == "_throw_exception") {
+        // This is for testing purpose only and should not appear in the
+        // actual configuration syntax.
+        // XXX: ModuleCCSession doesn't seem to validate commands (unlike
+        // config), so we should disable this case for now.
+        throw runtime_error("throwing for test");
+    }
+
+    isc_throw(AuthCommandError, "Unknown command identifier: " << command_id);
+}
+} // end of unnamed namespace
+
+ConstElementPtr
+execAuthServerCommand(AuthSrv& server, const string& command_id,
+                      ConstElementPtr args)
+{
+    if (server.getVerbose()) {
+        cerr << "[b10-auth] Received '" << command_id << "' command" << endl;
+    }
+
+    try {
+        scoped_ptr<AuthCommand>(createAuthCommand(command_id))->exec(server,
+                                                                     args);
+    } catch (const isc::Exception& ex) {
+        if (server.getVerbose()) {
+            cerr << "[b10-auth] Command '" << command_id
+                 << "' execution failed: " << ex.what() << endl;
+        }
+        return (createAnswer(1, ex.what()));
+    }
+
+    return (createAnswer());
+}

+ 61 - 0
src/bin/auth/command.h

@@ -0,0 +1,61 @@
+// Copyright (C) 2010  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <string>
+
+#include <cc/data.h>
+
+#ifndef __COMMAND_H
+#define __COMMAND_H 1
+
+class AuthSrv;
+
+/// Execute a control command on \c AuthSrv
+///
+/// It executes the operation identified by \c command_id with arguments
+/// \c args on \c server, and returns the result in the form of the standard
+/// command/config answer message (see \c isc::config::createAnswer()).
+///
+/// This method internally performs minimal validation on \c command_id and
+/// \c args to not cause a surprising result such as a crash, but it is
+/// generally expected that the caller has performed syntax level validation
+/// based on the command specification for the authoritative server.
+/// For example, the caller is responsible \c command_id is a valid command
+/// name for the authoritative server.
+///
+/// The execution of the command may internally trigger an exception; for
+/// instance, if a string that is expected to be a valid domain name is bogus,
+/// the underlying DNS library will throw an exception.  These internal
+/// exceptions will be caught inside this function, and will be converted
+/// to a return value with a non 0 error code.
+/// However, exceptions thrown outside of BIND 10 modules (including standard
+/// exceptions) are expected to be handled at a higher layer, and will be
+/// propagated to the caller.  In particular if memory allocation fails and
+/// \c std::bad_alloc is thrown it will be propagated.
+///
+/// \param server The \c AuthSrv object on which the command is executed.
+/// \param command_id The identifier of the command (such as "shutdown")
+/// \param args Command specific argument in a map type of
+/// \c isc::data::Element, or a \c NULL \c ElementPtr if the argument isn't
+/// specified.
+/// \return The result of the command operation.
+isc::data::ConstElementPtr
+execAuthServerCommand(AuthSrv& server, const std::string& command_id,
+                      isc::data::ConstElementPtr args);
+
+#endif // __COMMAND_H
+
+// Local Variables:
+// mode: c++
+// End:

+ 3 - 17
src/bin/auth/main.cc

@@ -42,6 +42,7 @@
 #include <auth/spec_config.h>
 #include <auth/common.h>
 #include <auth/config.h>
+#include <auth/command.h>
 #include <auth/change_user.h>
 #include <auth/auth_srv.h>
 #include <asiolink/asiolink.h>
@@ -80,23 +81,8 @@ my_config_handler(ConstElementPtr new_config) {
 
 ConstElementPtr
 my_command_handler(const string& command, ConstElementPtr args) {
-    ConstElementPtr answer = createAnswer();
-
-    if (command == "print_message") {
-        cout << args << endl;
-        /* let's add that message to our answer as well */
-        answer = createAnswer(0, args);
-    } else if (command == "shutdown") {
-        io_service.stop();
-    } else if (command == "sendstats") {
-        if (verbose_mode) {
-            cerr << "[b10-auth] command 'sendstats' received" << endl;
-        }
-        assert(auth_server != NULL);
-        auth_server->submitStatistics();
-    }
-
-    return (answer);
+    assert(auth_server != NULL);
+    return (execAuthServerCommand(*auth_server, command, args));
 }
 
 void

+ 4 - 1
src/bin/auth/tests/Makefile.am

@@ -2,8 +2,9 @@ AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
 AM_CPPFLAGS += -I$(top_builddir)/src/lib/dns -I$(top_srcdir)/src/bin
 AM_CPPFLAGS += -I$(top_builddir)/src/lib/cc
 AM_CPPFLAGS += $(BOOST_INCLUDES)
-AM_CPPFLAGS += -DTEST_DATA_DIR=\"$(top_srcdir)/src/lib/testutils/testdata\"
+AM_CPPFLAGS += -DTEST_DATA_DIR=\"$(abs_top_srcdir)/src/lib/testutils/testdata\"
 AM_CPPFLAGS += -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/lib/testutils/testdata\"
+AM_CPPFLAGS += -DINSTALL_PROG=\"$(abs_top_srcdir)/install-sh\"
 
 AM_CXXFLAGS = $(B10_CXXFLAGS)
 
@@ -22,9 +23,11 @@ run_unittests_SOURCES += ../auth_srv.h ../auth_srv.cc
 run_unittests_SOURCES += ../query.h ../query.cc
 run_unittests_SOURCES += ../change_user.h ../change_user.cc
 run_unittests_SOURCES += ../config.h ../config.cc
+run_unittests_SOURCES += ../command.h ../command.cc
 run_unittests_SOURCES += ../statistics.h ../statistics.cc
 run_unittests_SOURCES += auth_srv_unittest.cc
 run_unittests_SOURCES += config_unittest.cc
+run_unittests_SOURCES += command_unittest.cc
 run_unittests_SOURCES += query_unittest.cc
 run_unittests_SOURCES += change_user_unittest.cc
 run_unittests_SOURCES += statistics_unittest.cc

+ 12 - 1
src/bin/auth/tests/auth_srv_unittest.cc

@@ -30,9 +30,11 @@
 
 #include <datasrc/memory_datasrc.h>
 #include <auth/auth_srv.h>
-#include <testutils/srv_unittest.h>
+#include <auth/common.h>
 #include <auth/statistics.h>
 
+#include <testutils/srv_unittest.h>
+
 using namespace isc::cc;
 using namespace isc::dns;
 using namespace isc::dns::rdata;
@@ -628,4 +630,13 @@ TEST_F(AuthSrvTest, queryCounterUnexpected) {
                                        response_obuffer, &dnsserv),
                  isc::Unexpected);
 }
+
+TEST_F(AuthSrvTest, stop) {
+    // normal case is covered in command_unittest.cc.  we should primarily
+    // test it here, but the current design of the stop test takes time,
+    // so we consolidate the cases in the command tests.
+
+    // stop before start is prohibited.
+    EXPECT_THROW(server.stop(), FatalError);
+}
 }

+ 292 - 0
src/bin/auth/tests/command_unittest.cc

@@ -0,0 +1,292 @@
+// Copyright (C) 2010  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <cassert>
+#include <cstdlib>
+#include <string>
+#include <stdexcept>
+
+#include <boost/bind.hpp>
+
+#include <gtest/gtest.h>
+
+#include <dns/name.h>
+#include <dns/rrclass.h>
+#include <dns/rrtype.h>
+
+#include <cc/data.h>
+
+#include <config/ccsession.h>
+
+#include <datasrc/memory_datasrc.h>
+
+#include <auth/auth_srv.h>
+#include <auth/config.h>
+#include <auth/command.h>
+
+#include <asiolink/asiolink.h>
+
+#include <testutils/mockups.h>
+
+using namespace std;
+using namespace isc::dns;
+using namespace isc::data;
+using namespace isc::datasrc;
+using namespace isc::config;
+
+namespace {
+class AuthConmmandTest : public ::testing::Test {
+protected:
+    AuthConmmandTest() : server(false, xfrout), rcode(-1) {
+        server.setStatisticsSession(&statistics_session);
+    }
+    void checkAnswer(const int expected_code) {
+        parseAnswer(rcode, result);
+        EXPECT_EQ(expected_code, rcode);
+    }
+    MockSession statistics_session;
+    MockXfroutClient xfrout;
+    AuthSrv server;
+    AuthSrv::ConstMemoryDataSrcPtr memory_datasrc;
+    ConstElementPtr result;
+    int rcode;
+public:
+    void stopServer();          // need to be public for boost::bind
+};
+
+TEST_F(AuthConmmandTest, unknownCommand) {
+    result = execAuthServerCommand(server, "no_such_command",
+                                   ConstElementPtr());
+    parseAnswer(rcode, result);
+    EXPECT_EQ(1, rcode);
+}
+
+TEST_F(AuthConmmandTest, DISABLED_unexpectedException) {
+    // execAuthServerCommand() won't catch standard exceptions.
+    // Skip this test for now: ModuleCCSession doesn't seem to validate
+    // commands.
+    EXPECT_THROW(execAuthServerCommand(server, "_throw_exception",
+                                       ConstElementPtr()),
+                 runtime_error);
+}
+
+TEST_F(AuthConmmandTest, sendStatistics) {
+    result = execAuthServerCommand(server, "sendstats", ConstElementPtr());
+    // Just check some message has been sent.  Detailed tests specific to
+    // statistics are done in its own tests.
+    EXPECT_EQ("Stats", statistics_session.getMessageDest());
+    checkAnswer(0);
+}
+
+void
+AuthConmmandTest::stopServer() {
+    result = execAuthServerCommand(server, "shutdown", ConstElementPtr());
+    parseAnswer(rcode, result);
+    assert(rcode == 0); // make sure the test stops when something is wrong
+}
+
+TEST_F(AuthConmmandTest, shutdown) {
+    asiolink::IOService io_service;
+    asiolink::IntervalTimer itimer(io_service);
+    server.setIOService(io_service);
+    itimer.setupTimer(boost::bind(&AuthConmmandTest::stopServer, this), 1);
+    io_service.run();
+    EXPECT_EQ(0, rcode);
+}
+
+// A helper function commonly used for the "loadzone" command tests.
+// It configures the server with a memory data source containing two
+// zones, and checks the zones are correctly loaded.
+void
+zoneChecks(AuthSrv& server) {
+    EXPECT_TRUE(server.getMemoryDataSrc(RRClass::IN()));
+    EXPECT_EQ(Zone::SUCCESS, server.getMemoryDataSrc(RRClass::IN())->
+              findZone(Name("ns.test1.example")).zone->
+              find(Name("ns.test1.example"), RRType::A()).code);
+    EXPECT_EQ(Zone::NXRRSET, server.getMemoryDataSrc(RRClass::IN())->
+              findZone(Name("ns.test1.example")).zone->
+              find(Name("ns.test1.example"), RRType::AAAA()).code);
+    EXPECT_EQ(Zone::SUCCESS, server.getMemoryDataSrc(RRClass::IN())->
+              findZone(Name("ns.test2.example")).zone->
+              find(Name("ns.test2.example"), RRType::A()).code);
+    EXPECT_EQ(Zone::NXRRSET, server.getMemoryDataSrc(RRClass::IN())->
+              findZone(Name("ns.test2.example")).zone->
+              find(Name("ns.test2.example"), RRType::AAAA()).code);
+}
+
+void
+configureZones(AuthSrv& server) {
+    ASSERT_EQ(0, system(INSTALL_PROG " " TEST_DATA_BUILDDIR "/test1.zone.in "
+                        TEST_DATA_BUILDDIR "/test1.zone.copied"));
+    ASSERT_EQ(0, system(INSTALL_PROG " " TEST_DATA_BUILDDIR "/test2.zone.in "
+                        TEST_DATA_BUILDDIR "/test2.zone.copied"));
+    configureAuthServer(server, Element::fromJSON(
+                            "{\"datasources\": "
+                            " [{\"type\": \"memory\","
+                            "   \"zones\": "
+                            "[{\"origin\": \"test1.example\","
+                            "  \"file\": \""
+                               TEST_DATA_BUILDDIR "/test1.zone.copied\"},"
+                            " {\"origin\": \"test2.example\","
+                            "  \"file\": \""
+                               TEST_DATA_BUILDDIR "/test2.zone.copied\"}"
+                            "]}]}"));
+    zoneChecks(server);
+}
+
+void
+newZoneChecks(AuthSrv& server) {
+    EXPECT_TRUE(server.getMemoryDataSrc(RRClass::IN()));
+    EXPECT_EQ(Zone::SUCCESS, server.getMemoryDataSrc(RRClass::IN())->
+              findZone(Name("ns.test1.example")).zone->
+              find(Name("ns.test1.example"), RRType::A()).code);
+    // now test1.example should have ns/AAAA
+    EXPECT_EQ(Zone::SUCCESS, server.getMemoryDataSrc(RRClass::IN())->
+              findZone(Name("ns.test1.example")).zone->
+              find(Name("ns.test1.example"), RRType::AAAA()).code);
+
+    // test2.example shouldn't change
+    EXPECT_EQ(Zone::SUCCESS, server.getMemoryDataSrc(RRClass::IN())->
+              findZone(Name("ns.test2.example")).zone->
+              find(Name("ns.test2.example"), RRType::A()).code);
+    EXPECT_EQ(Zone::NXRRSET, server.getMemoryDataSrc(RRClass::IN())->
+              findZone(Name("ns.test2.example")).zone->
+              find(Name("ns.test2.example"), RRType::AAAA()).code);
+}
+
+TEST_F(AuthConmmandTest, loadZone) {
+    configureZones(server);
+
+    ASSERT_EQ(0, system(INSTALL_PROG " " TEST_DATA_BUILDDIR
+                        "/test1-new.zone.in "
+                        TEST_DATA_BUILDDIR "/test1.zone.copied"));
+    ASSERT_EQ(0, system(INSTALL_PROG " " TEST_DATA_BUILDDIR
+                        "/test2-new.zone.in "
+                        TEST_DATA_BUILDDIR "/test2.zone.copied"));
+
+    result = execAuthServerCommand(server, "loadzone",
+                                   Element::fromJSON(
+                                       "{\"origin\": \"test1.example\"}"));
+    checkAnswer(0);
+    newZoneChecks(server);
+}
+
+TEST_F(AuthConmmandTest, loadBrokenZone) {
+    configureZones(server);
+
+    ASSERT_EQ(0, system(INSTALL_PROG " " TEST_DATA_BUILDDIR
+                        "/test1-broken.zone.in "
+                        TEST_DATA_BUILDDIR "/test1.zone.copied"));
+    result = execAuthServerCommand(server, "loadzone",
+                                   Element::fromJSON(
+                                       "{\"origin\": \"test1.example\"}"));
+    checkAnswer(1);
+    zoneChecks(server);     // zone shouldn't be replaced
+}
+
+TEST_F(AuthConmmandTest, loadUnreadableZone) {
+    configureZones(server);
+
+    // install the zone file as unreadable
+    ASSERT_EQ(0, system(INSTALL_PROG " -m 000 " TEST_DATA_BUILDDIR
+                        "/test1.zone.in "
+                        TEST_DATA_BUILDDIR "/test1.zone.copied"));
+    result = execAuthServerCommand(server, "loadzone",
+                                   Element::fromJSON(
+                                       "{\"origin\": \"test1.example\"}"));
+    checkAnswer(1);
+    zoneChecks(server);     // zone shouldn't be replaced
+}
+
+TEST_F(AuthConmmandTest, loadZoneWithoutDataSrc) {
+    // try to execute load command without configuring the zone beforehand.
+    // it should fail.
+    result = execAuthServerCommand(server, "loadzone",
+                                   Element::fromJSON(
+                                       "{\"origin\": \"test1.example\"}"));
+    checkAnswer(1);
+}
+
+TEST_F(AuthConmmandTest, loadSqlite3DataSrc) {
+    // For sqlite3 data source we don't have to do anything (the data source
+    // (re)loads itself automatically)
+    result = execAuthServerCommand(server, "loadzone",
+                                   Element::fromJSON(
+                                       "{\"origin\": \"test1.example\","
+                                       " \"datasrc\": \"sqlite3\"}"));
+    checkAnswer(0);
+}
+
+TEST_F(AuthConmmandTest, loadZoneInvalidParams) {
+    configureZones(server);
+
+    // null arg
+    result = execAuthServerCommand(server, "loadzone", ElementPtr());
+    checkAnswer(1);
+
+    // zone class is bogus
+    result = execAuthServerCommand(server, "loadzone",
+                                   Element::fromJSON(
+                                       "{\"origin\": \"test1.example\","
+                                       " \"class\": \"no_such_class\"}"));
+    checkAnswer(1);
+
+    result = execAuthServerCommand(server, "loadzone",
+                                   Element::fromJSON(
+                                       "{\"origin\": \"test1.example\","
+                                       " \"class\": 1}"));
+    checkAnswer(1);
+
+    // unsupported zone class
+    result = execAuthServerCommand(server, "loadzone",
+                                   Element::fromJSON(
+                                       "{\"origin\": \"test1.example\","
+                                       " \"class\": \"CH\"}"));
+    checkAnswer(1);
+
+    // unsupported data source class
+    result = execAuthServerCommand(server, "loadzone",
+                                   Element::fromJSON(
+                                       "{\"origin\": \"test1.example\","
+                                       " \"datasrc\": \"not supported\"}"));
+    checkAnswer(1);
+
+    // data source is bogus
+    result = execAuthServerCommand(server, "loadzone",
+                                   Element::fromJSON(
+                                       "{\"origin\": \"test1.example\","
+                                       " \"datasrc\": 0}"));
+    checkAnswer(1);
+
+    // origin is missing
+    result = execAuthServerCommand(server, "loadzone",
+                                   Element::fromJSON("{}"));
+    checkAnswer(1);
+
+    // zone doesn't exist in the data source
+    result = execAuthServerCommand(server, "loadzone",
+                                   Element::fromJSON("{\"origin\": \"xx\"}"));
+    checkAnswer(1);
+
+    // origin is bogus
+    result = execAuthServerCommand(server, "loadzone",
+                                   Element::fromJSON(
+                                       "{\"origin\": \"...\"}"));
+    checkAnswer(1);
+
+    result = execAuthServerCommand(server, "loadzone",
+                                   Element::fromJSON("{\"origin\": 10}"));
+    checkAnswer(1);
+}
+}

+ 12 - 0
src/lib/datasrc/memory_datasrc.cc

@@ -40,6 +40,7 @@ struct MemoryZone::MemoryZoneImpl {
     // Information about the zone
     RRClass zone_class_;
     Name origin_;
+    string file_name_;
 
     // Some type aliases
     /*
@@ -275,10 +276,21 @@ MemoryZone::load(const string& filename) {
     masterLoad(filename.c_str(), getOrigin(), getClass(),
         boost::bind(&MemoryZoneImpl::addFromLoad, impl_, _1, &tmp));
     // If it went well, put it inside
+    impl_->file_name_ = filename;
     tmp.swap(impl_->domains_);
     // And let the old data die with tmp
 }
 
+void
+MemoryZone::swap(MemoryZone& zone) {
+    std::swap(impl_, zone.impl_);
+}
+
+const string
+MemoryZone::getFileName() const {
+    return (impl_->file_name_);
+}
+
 /// Implementation details for \c MemoryDataSrc hidden from the public
 /// interface.
 ///

+ 27 - 6
src/lib/datasrc/memory_datasrc.h

@@ -15,6 +15,8 @@
 #ifndef __MEMORY_DATA_SOURCE_H
 #define __MEMORY_DATA_SOURCE_H 1
 
+#include <string>
+
 #include <datasrc/zonetable.h>
 
 namespace isc {
@@ -98,6 +100,20 @@ public:
         { }
     };
 
+    /// Return the master file name of the zone
+    ///
+    /// This method returns the name of the zone's master file to be loaded.
+    /// The returned string will be an empty unless the zone has successfully
+    /// loaded a zone.
+    ///
+    /// This method should normally not throw an exception.  But the creation
+    /// of the return string may involve a resource allocation, and if it
+    /// fails, the corresponding standard exception will be thrown.
+    ///
+    /// \return The name of the zone file loaded in the zone, or an empty
+    /// string if the zone hasn't loaded any file.
+    const std::string getFileName() const;
+
     /// \brief Load zone from masterfile.
     ///
     /// This loads data from masterfile specified by filename. It replaces
@@ -122,6 +138,15 @@ public:
     ///     This will probably be needed when a better implementation of
     ///     configuration reloading is written.
     void load(const std::string& filename);
+
+    /// Exchanges the content of \c this zone with that of the given \c zone.
+    ///
+    /// This method never throws an exception.
+    ///
+    /// \param zone Another \c MemoryZone object which is to be swapped with
+    /// \c this zone.
+    void swap(MemoryZone& zone);
+
 private:
     /// \name Hidden private data
     //@{
@@ -158,10 +183,6 @@ private:
 /// The findZone() method takes a domain name and returns the best matching \c
 /// MemoryZone in the form of (Boost) shared pointer, so that it can provide
 /// the general interface for all data sources.
-///
-/// Currently, \c FindResult::zone is immutable for safety.
-/// In future versions we may want to make it changeable.  For example,
-/// we may want to allow configuration update on an existing zone.
 class MemoryDataSrc {
 public:
     /// \brief A helper structure to represent the search result of
@@ -180,11 +201,11 @@ public:
     /// See the description of \c find() for the semantics of the member
     /// variables.
     struct FindResult {
-        FindResult(result::Result param_code, const ConstZonePtr param_zone) :
+        FindResult(result::Result param_code, const ZonePtr param_zone) :
             code(param_code), zone(param_zone)
         {}
         const result::Result code;
-        const ConstZonePtr zone;
+        const ZonePtr zone;
     };
 
     ///

+ 54 - 0
src/lib/datasrc/tests/memory_datasrc_unittest.cc

@@ -405,4 +405,58 @@ TEST_F(MemoryZoneTest, load) {
         MasterLoadError);
 }
 
+TEST_F(MemoryZoneTest, swap) {
+    // build one zone with some data
+    MemoryZone zone1(class_, origin_);
+    EXPECT_EQ(result::SUCCESS, zone1.add(rr_ns_));
+    EXPECT_EQ(result::SUCCESS, zone1.add(rr_ns_aaaa_));
+
+    // build another zone of a different RR class with some other data
+    const Name other_origin("version.bind");
+    ASSERT_NE(origin_, other_origin); // make sure these two are different
+    MemoryZone zone2(RRClass::CH(), other_origin);
+    EXPECT_EQ(result::SUCCESS,
+              zone2.add(RRsetPtr(new RRset(Name("version.bind"),
+                                           RRClass::CH(), RRType::TXT(),
+                                           RRTTL(0)))));
+
+    zone1.swap(zone2);
+    EXPECT_EQ(other_origin, zone1.getOrigin());
+    EXPECT_EQ(origin_, zone2.getOrigin());
+    EXPECT_EQ(RRClass::CH(), zone1.getClass());
+    EXPECT_EQ(RRClass::IN(), zone2.getClass());
+    // make sure the zone data is swapped, too
+    findTest(origin_, RRType::NS(), Zone::NXDOMAIN, false, ConstRRsetPtr(),
+             &zone1);
+    findTest(other_origin, RRType::TXT(), Zone::SUCCESS, false,
+             ConstRRsetPtr(), &zone1);
+    findTest(origin_, RRType::NS(), Zone::SUCCESS, false, ConstRRsetPtr(),
+             &zone2);
+    findTest(other_origin, RRType::TXT(), Zone::NXDOMAIN, false,
+             ConstRRsetPtr(), &zone2);
+}
+
+TEST_F(MemoryZoneTest, getFileName) {
+    // for an empty zone the file name should also be empty.
+    EXPECT_TRUE(zone_.getFileName().empty());
+
+    // if loading a zone fails the file name shouldn't be set.
+    EXPECT_THROW(zone_.load(TEST_DATA_DIR "/root.zone"), MasterLoadError);
+    EXPECT_TRUE(zone_.getFileName().empty());
+
+    // after a successful load, the specified file name should be set
+    MemoryZone rootzone(class_, Name("."));
+    EXPECT_NO_THROW(rootzone.load(TEST_DATA_DIR "/root.zone"));
+    EXPECT_EQ(TEST_DATA_DIR "/root.zone", rootzone.getFileName());
+    // overriding load, which will fail
+    EXPECT_THROW(rootzone.load(TEST_DATA_DIR "/duplicate_rrset.zone"),
+                 MasterLoadError);
+    // the file name should be intact.
+    EXPECT_EQ(TEST_DATA_DIR "/root.zone", rootzone.getFileName());
+
+    // After swap, file names should also be swapped.
+    zone_.swap(rootzone);
+    EXPECT_EQ(TEST_DATA_DIR "/root.zone", zone_.getFileName());
+    EXPECT_TRUE(rootzone.getFileName().empty());
+}
 }

+ 2 - 2
src/lib/datasrc/zonetable.cc

@@ -85,12 +85,12 @@ struct ZoneTable::ZoneTableImpl {
                 break;
             // We have no data there, so translate the pointer to NULL as well
             case ZoneTree::NOTFOUND:
-                return (FindResult(result::NOTFOUND, ConstZonePtr()));
+                return (FindResult(result::NOTFOUND, ZonePtr()));
             // Can Not Happen
             default:
                 assert(0);
                 // Because of warning
-                return (FindResult(result::NOTFOUND, ConstZonePtr()));
+                return (FindResult(result::NOTFOUND, ZonePtr()));
         }
 
         // Can Not Happen (remember, NOTFOUND is handled)

+ 2 - 2
src/lib/datasrc/zonetable.h

@@ -41,11 +41,11 @@ namespace datasrc {
 class ZoneTable {
 public:
     struct FindResult {
-        FindResult(result::Result param_code, const ConstZonePtr param_zone) :
+        FindResult(result::Result param_code, const ZonePtr param_zone) :
             code(param_code), zone(param_zone)
         {}
         const result::Result code;
-        const ConstZonePtr zone;
+        const ZonePtr zone;
     };
     ///
     /// \name Constructors and Destructor.

+ 7 - 1
src/lib/testutils/testdata/Makefile.am

@@ -1,4 +1,4 @@
-CLEANFILES = *.wire
+CLEANFILES = *.wire *.copied
 
 BUILT_SOURCES = badExampleQuery_fromWire.wire examplequery_fromWire.wire
 BUILT_SOURCES += iqueryresponse_fromWire.wire multiquestion_fromWire.wire
@@ -25,5 +25,11 @@ EXTRA_DIST += example.com.zone example.net.zone example.org.zone example.zone
 EXTRA_DIST += example.com
 EXTRA_DIST += example.sqlite3
 
+EXTRA_DIST += test1.zone.in
+EXTRA_DIST += test1-new.zone.in
+EXTRA_DIST += test1-broken.zone.in
+EXTRA_DIST += test2.zone.in
+EXTRA_DIST += test2-new.zone.in
+
 .spec.wire:
 	$(abs_top_builddir)/src/lib/dns/tests/testdata/gen-wiredata.py -o $@ $<