Commit 2a507893 authored by JINMEI Tatuya's avatar JINMEI Tatuya

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
parent 3c8c2f81
......@@ -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
......
......@@ -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"
}
]
}
]
}
......
......@@ -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,
......
......@@ -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.
///
......
// 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());
}
// 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:
......@@ -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
......
......@@ -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
......
......@@ -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);
}
}
// 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) {