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

Merge #1793

parents da4d8df9 6e05014a
...@@ -18,6 +18,7 @@ ...@@ -18,6 +18,7 @@
#include <cc/data.h> #include <cc/data.h>
#include <datasrc/memory_datasrc.h> #include <datasrc/memory_datasrc.h>
#include <datasrc/factory.h>
#include <config/ccsession.h> #include <config/ccsession.h>
#include <exceptions/exceptions.h> #include <exceptions/exceptions.h>
#include <dns/rrclass.h> #include <dns/rrclass.h>
...@@ -149,26 +150,39 @@ public: ...@@ -149,26 +150,39 @@ public:
return; return;
} }
const ConstElementPtr zone_config = getZoneConfig(server);
// Load a new zone and replace the current zone with the new one. // Load a new zone and replace the current zone with the new one.
// TODO: eventually this should be incremental or done in some way // TODO: eventually this should be incremental or done in some way
// that doesn't block other server operations. // that doesn't block other server operations.
// TODO: we may (should?) want to check the "last load time" and // 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. // 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( boost::shared_ptr<InMemoryZoneFinder> zone_finder(
new InMemoryZoneFinder(old_zone_finder->getClass(), new InMemoryZoneFinder(old_zone_finder_->getClass(),
old_zone_finder->getOrigin())); old_zone_finder_->getOrigin()));
zone_finder->load(old_zone_finder->getFileName()); if (type && type->stringValue() == "sqlite3") {
old_zone_finder->swap(*zone_finder); 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) LOG_DEBUG(auth_logger, DBG_AUTH_OPS, AUTH_LOAD_ZONE)
.arg(zone_finder->getOrigin()).arg(zone_finder->getClass()); .arg(zone_finder->getOrigin()).arg(zone_finder->getClass());
} }
private: private:
// zone finder to be updated with the new file. // 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. // 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 // It returns true if everything is okay; and false if the command is
// valid but there's no need for further process. // valid but there's no need for further process.
bool validate(AuthSrv& server, isc::data::ConstElementPtr args) { bool validate(AuthSrv& server, isc::data::ConstElementPtr args) {
...@@ -193,10 +207,11 @@ private: ...@@ -193,10 +207,11 @@ private:
} }
ConstElementPtr class_elem = args->get("class"); ConstElementPtr class_elem = args->get("class");
const RRClass zone_class = class_elem ? const RRClass zone_class =
RRClass(class_elem->stringValue()) : RRClass::IN(); 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) { if (datasrc == NULL) {
isc_throw(AuthCommandError, "Memory data source is disabled"); isc_throw(AuthCommandError, "Memory data source is disabled");
} }
...@@ -205,7 +220,7 @@ private: ...@@ -205,7 +220,7 @@ private:
if (!origin_elem) { if (!origin_elem) {
isc_throw(AuthCommandError, "Zone origin is missing"); isc_throw(AuthCommandError, "Zone origin is missing");
} }
const Name origin(origin_elem->stringValue()); const Name origin = Name(origin_elem->stringValue());
// Get the current zone // Get the current zone
const InMemoryClient::FindResult result = datasrc->findZone(origin); const InMemoryClient::FindResult result = datasrc->findZone(origin);
...@@ -214,11 +229,70 @@ private: ...@@ -214,11 +229,70 @@ private:
" is not found in data source"); " 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); result.zone_finder);
return (true); 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. // The factory of command objects.
......
...@@ -14,6 +14,8 @@ ...@@ -14,6 +14,8 @@
#include <config.h> #include <config.h>
#include "datasrc_util.h"
#include <auth/auth_srv.h> #include <auth/auth_srv.h>
#include <auth/auth_config.h> #include <auth/auth_config.h>
#include <auth/command.h> #include <auth/command.h>
...@@ -50,8 +52,10 @@ using namespace isc::data; ...@@ -50,8 +52,10 @@ using namespace isc::data;
using namespace isc::datasrc; using namespace isc::datasrc;
using namespace isc::config; using namespace isc::config;
using namespace isc::testutils; using namespace isc::testutils;
using namespace isc::auth::unittest;
namespace { namespace {
class AuthCommandTest : public ::testing::Test { class AuthCommandTest : public ::testing::Test {
protected: protected:
AuthCommandTest() : AuthCommandTest() :
...@@ -246,6 +250,129 @@ TEST_F(AuthCommandTest, loadZone) { ...@@ -246,6 +250,129 @@ TEST_F(AuthCommandTest, loadZone) {
newZoneChecks(server_); 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) { TEST_F(AuthCommandTest, loadBrokenZone) {
configureZones(server_); configureZones(server_);
......
...@@ -24,10 +24,7 @@ namespace isc { ...@@ -24,10 +24,7 @@ namespace isc {
namespace auth { namespace auth {
namespace unittest { namespace unittest {
// Here we define utility modules for the convenience of tests that create /// \brief Create an SQLite3 database file for a given zone from a stream.
// a data source client according to the specified conditions.
/// \brief Create an SQLite3 data source client from a stream.
/// ///
/// This function creates an SQLite3 DB file for the specified zone /// This function creates an SQLite3 DB file for the specified zone
/// with specified content. The zone will be created in the given /// 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