Commit 5b378593 authored by Michal 'vorner' Vaner's avatar Michal 'vorner' Vaner

Merge #1793

parents da4d8df9 6e05014a
......@@ -18,6 +18,7 @@
#include <cc/data.h>
#include <datasrc/memory_datasrc.h>
#include <datasrc/factory.h>
#include <config/ccsession.h>
#include <exceptions/exceptions.h>
#include <dns/rrclass.h>
......@@ -149,26 +150,39 @@ public:
return;
}
const ConstElementPtr zone_config = getZoneConfig(server);
// 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.
const ConstElementPtr type(zone_config->get("filetype"));
boost::shared_ptr<InMemoryZoneFinder> zone_finder(
new InMemoryZoneFinder(old_zone_finder->getClass(),
old_zone_finder->getOrigin()));
zone_finder->load(old_zone_finder->getFileName());
old_zone_finder->swap(*zone_finder);
new InMemoryZoneFinder(old_zone_finder_->getClass(),
old_zone_finder_->getOrigin()));
if (type && type->stringValue() == "sqlite3") {
scoped_ptr<DataSourceClientContainer>
container(new DataSourceClientContainer("sqlite3",
Element::fromJSON(
"{\"database_file\": \"" +
zone_config->get("file")->stringValue() + "\"}")));
zone_finder->load(*container->getInstance().getIterator(
old_zone_finder_->getOrigin()));
} else {
zone_finder->load(old_zone_finder_->getFileName());
}
old_zone_finder_->swap(*zone_finder);
LOG_DEBUG(auth_logger, DBG_AUTH_OPS, AUTH_LOAD_ZONE)
.arg(zone_finder->getOrigin()).arg(zone_finder->getClass());
}
private:
// zone finder to be updated with the new file.
boost::shared_ptr<InMemoryZoneFinder> old_zone_finder;
boost::shared_ptr<InMemoryZoneFinder> old_zone_finder_;
// A helper private method to parse and validate command parameters.
// On success, it sets 'old_zone_finder' to the zone to be updated.
// On success, it sets 'old_zone_finder_' 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) {
......@@ -193,10 +207,11 @@ private:
}
ConstElementPtr class_elem = args->get("class");
const RRClass zone_class = class_elem ?
RRClass(class_elem->stringValue()) : RRClass::IN();
const RRClass zone_class =
class_elem ? RRClass(class_elem->stringValue()) : RRClass::IN();
AuthSrv::InMemoryClientPtr datasrc(server.getInMemoryClient(zone_class));
AuthSrv::InMemoryClientPtr datasrc(server.
getInMemoryClient(zone_class));
if (datasrc == NULL) {
isc_throw(AuthCommandError, "Memory data source is disabled");
}
......@@ -205,7 +220,7 @@ private:
if (!origin_elem) {
isc_throw(AuthCommandError, "Zone origin is missing");
}
const Name origin(origin_elem->stringValue());
const Name origin = Name(origin_elem->stringValue());
// Get the current zone
const InMemoryClient::FindResult result = datasrc->findZone(origin);
......@@ -214,11 +229,70 @@ private:
" is not found in data source");
}
old_zone_finder = boost::dynamic_pointer_cast<InMemoryZoneFinder>(
old_zone_finder_ = boost::dynamic_pointer_cast<InMemoryZoneFinder>(
result.zone_finder);
return (true);
}
ConstElementPtr getZoneConfig(const AuthSrv &server) {
if (!server.getConfigSession()) {
// FIXME: This is a hack to make older tests pass. We should
// update these tests as well sometime and remove this hack.
// (note that under normal situation, the
// server.getConfigSession() does not return NULL)
// We provide an empty map, which means no configuration --
// defaults.
return (ConstElementPtr(new MapElement()));
}
// Find the config corresponding to the zone.
// We expect the configuration to be valid, as we have it and we
// accepted it before, therefore it must be validated.
const ConstElementPtr config(server.getConfigSession()->
getValue("datasources"));
ConstElementPtr zone_list;
// Unfortunately, we need to walk the list to find the correct data
// source.
// TODO: Make it named sets. These lists are uncomfortable.
for (size_t i(0); i < config->size(); ++i) {
// We use the getValue to get defaults as well
const ConstElementPtr dsrc_config(config->get(i));
const ConstElementPtr class_config(dsrc_config->get("class"));
const string class_type(class_config ?
class_config->stringValue() : "IN");
// It is in-memory and our class matches.
// FIXME: Is it allowed to have two datasources for the same
// type and class at once? It probably would not work now
// anyway and we may want to change the configuration of
// datasources somehow.
if (dsrc_config->get("type")->stringValue() == "memory" &&
RRClass(class_type) == old_zone_finder_->getClass()) {
zone_list = dsrc_config->get("zones");
break;
}
}
if (!zone_list) {
isc_throw(AuthCommandError,
"Corresponding data source configuration was not found");
}
// Now we need to walk the zones and find the correct one.
for (size_t i(0); i < zone_list->size(); ++i) {
const ConstElementPtr zone_config(zone_list->get(i));
if (Name(zone_config->get("origin")->stringValue()) ==
old_zone_finder_->getOrigin()) {
// The origins are the same, so we consider this config to be
// for the zone.
return (zone_config);
}
}
isc_throw(AuthCommandError,
"Corresponding zone configuration was not found");
}
};
// The factory of command objects.
......
......@@ -14,6 +14,8 @@
#include <config.h>
#include "datasrc_util.h"
#include <auth/auth_srv.h>
#include <auth/auth_config.h>
#include <auth/command.h>
......@@ -50,8 +52,10 @@ using namespace isc::data;
using namespace isc::datasrc;
using namespace isc::config;
using namespace isc::testutils;
using namespace isc::auth::unittest;
namespace {
class AuthCommandTest : public ::testing::Test {
protected:
AuthCommandTest() :
......@@ -246,6 +250,129 @@ TEST_F(AuthCommandTest, loadZone) {
newZoneChecks(server_);
}
// This test uses dynamic load of a data source module, and won't work when
// statically linked.
TEST_F(AuthCommandTest,
#ifdef USE_STATIC_LINK
DISABLED_loadZoneSQLite3
#else
loadZoneSQLite3
#endif
)
{
const char* const SPEC_FILE = AUTH_OBJ_DIR "/auth.spec";
// Prepare the database first
const string test_db = TEST_DATA_BUILDDIR "/auth_test.sqlite3.copied";
const string bad_db = TEST_DATA_BUILDDIR "/does-not-exist.sqlite3";
stringstream ss("example.org. 3600 IN SOA . . 0 0 0 0 0\n");
createSQLite3DB(RRClass::IN(), Name("example.org"), test_db.c_str(), ss);
// Then store a config of the zone to the auth server
// This omits many config options of the auth server, but these are
// not read now.
isc::testutils::MockSession session;
// The session should not take care of anything or start anything, we
// need it only to hold the config we're going to put into it.
ModuleCCSession module_session(SPEC_FILE, session, NULL, NULL, false,
false);
// This describes the data source in the configuration
const ElementPtr
map(Element::fromJSON("{\"datasources\": ["
" {"
" \"type\": \"memory\","
" \"zones\": ["
" {"
" \"origin\": \"example.org\","
" \"file\": \"" + test_db + "\","
" \"filetype\": \"sqlite3\""
" }"
" ]"
" }"
"]}"));
module_session.setLocalConfig(map);
server_.setConfigSession(&module_session);
// The loadzone command needs the zone to be already loaded, because
// it is used for reloading only
AuthSrv::InMemoryClientPtr dsrc(new InMemoryClient());
dsrc->addZone(ZoneFinderPtr(new InMemoryZoneFinder(RRClass::IN(),
Name("example.org"))));
server_.setInMemoryClient(RRClass::IN(), dsrc);
// Now send the command to reload it
result_ = execAuthServerCommand(server_, "loadzone",
Element::fromJSON("{\"origin\": \"example.org\"}"));
checkAnswer(0);
// Get the zone and look if there are data in it (the original one was
// empty)
ASSERT_TRUE(server_.getInMemoryClient(RRClass::IN()));
EXPECT_EQ(ZoneFinder::SUCCESS, server_.getInMemoryClient(RRClass::IN())->
findZone(Name("example.org")).zone_finder->
find(Name("example.org"), RRType::SOA())->code);
// Some error cases. First, the zone has no configuration.
dsrc->addZone(ZoneFinderPtr(new InMemoryZoneFinder(RRClass::IN(),
Name("example.com"))));
result_ = execAuthServerCommand(server_, "loadzone",
Element::fromJSON("{\"origin\": \"example.com\"}"));
checkAnswer(1);
// The previous zone is not hurt in any way
EXPECT_EQ(ZoneFinder::SUCCESS, server_.getInMemoryClient(RRClass::IN())->
findZone(Name("example.org")).zone_finder->
find(Name("example.org"), RRType::SOA())->code);
module_session.setLocalConfig(Element::fromJSON("{\"datasources\": []}"));
result_ = execAuthServerCommand(server_, "loadzone",
Element::fromJSON("{\"origin\": \"example.org\"}"));
checkAnswer(1);
// The previous zone is not hurt in any way
EXPECT_EQ(ZoneFinder::SUCCESS, server_.getInMemoryClient(RRClass::IN())->
findZone(Name("example.org")).zone_finder->
find(Name("example.org"), RRType::SOA())->code);
// Configure an unreadable zone. Should fail, but leave the original zone
// data there
const ElementPtr
mapBad(Element::fromJSON("{\"datasources\": ["
" {"
" \"type\": \"memory\","
" \"zones\": ["
" {"
" \"origin\": \"example.org\","
" \"file\": \"" + bad_db + "\","
" \"filetype\": \"sqlite3\""
" }"
" ]"
" }"
"]}"));
module_session.setLocalConfig(mapBad);
result_ = execAuthServerCommand(server_, "loadzone",
Element::fromJSON("{\"origin\": \"example.com\"}"));
checkAnswer(1);
// The previous zone is not hurt in any way
EXPECT_EQ(ZoneFinder::SUCCESS, server_.getInMemoryClient(RRClass::IN())->
findZone(Name("example.org")).zone_finder->
find(Name("example.org"), RRType::SOA())->code);
// Broken configuration (not valid against the spec)
const ElementPtr
broken(Element::fromJSON("{\"datasources\": ["
" {"
" \"type\": \"memory\","
" \"zones\": [[]]"
" }"
"]}"));
module_session.setLocalConfig(broken);
checkAnswer(1);
// The previous zone is not hurt in any way
EXPECT_EQ(ZoneFinder::SUCCESS, server_.getInMemoryClient(RRClass::IN())->
findZone(Name("example.org")).zone_finder->
find(Name("example.org"), RRType::SOA())->code);
}
TEST_F(AuthCommandTest, loadBrokenZone) {
configureZones(server_);
......
......@@ -24,10 +24,7 @@ namespace isc {
namespace auth {
namespace unittest {
// Here we define utility modules for the convenience of tests that create
// a data source client according to the specified conditions.
/// \brief Create an SQLite3 data source client from a stream.
/// \brief Create an SQLite3 database file for a given zone from a stream.
///
/// This function creates an SQLite3 DB file for the specified zone
/// with specified content. The zone will be created in the given
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment