Commit 7d5399cb authored by Michal 'vorner' Vaner's avatar Michal 'vorner' Vaner
Browse files

Merge remote-tracking branch 'origin/trac1976-cont-2' into work/merge

parents 9cef6836 e0b54d0f
......@@ -125,10 +125,6 @@
{
"item_name": "origin", "item_type": "string",
"item_optional": false, "item_default": ""
},
{
"item_name": "datasrc", "item_type": "string",
"item_optional": true, "item_default": "memory"
}
]
}
......
......@@ -17,8 +17,7 @@
#include <auth/auth_srv.h>
#include <cc/data.h>
#include <datasrc/memory_datasrc.h>
#include <datasrc/factory.h>
#include <datasrc/client_list.h>
#include <config/ccsession.h>
#include <exceptions/exceptions.h>
#include <dns/rrclass.h>
......@@ -144,161 +143,74 @@ public:
// Handle the "loadzone" command.
class LoadZoneCommand : public AuthCommand {
public:
LoadZoneCommand() :
zone_class_(RRClass::IN()), // We need to have something to compile
origin_(Name::ROOT_NAME())
{}
virtual void exec(AuthSrv& server, isc::data::ConstElementPtr args) {
// parse and validate the args.
if (!validate(server, args)) {
if (!validate(args)) {
return;
}
const ConstElementPtr zone_config = getZoneConfig(server);
const boost::shared_ptr<isc::datasrc::ConfigurableClientList>
list(server.getClientList(zone_class_));
// 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()));
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());
if (!list) {
isc_throw(AuthCommandError, "There's no client list for "
"class " << zone_class_);
}
switch (list->reload(origin_)) {
case ConfigurableClientList::ZONE_RELOADED:
// Everything worked fine.
LOG_DEBUG(auth_logger, DBG_AUTH_OPS, AUTH_LOAD_ZONE)
.arg(zone_class_).arg(origin_);
return;
case ConfigurableClientList::ZONE_NOT_FOUND:
isc_throw(AuthCommandError, "Zone " << origin_ << "/" <<
zone_class_ << " was not found in any configure "
"data source. Configure it first.");
case ConfigurableClientList::ZONE_NOT_CACHED:
isc_throw(AuthCommandError, "Zone " << origin_ << "/" <<
zone_class_ << " is not served from memory, but "
"direcly from the data source. It is not possible "
"to reload into memory, configure it to be cached "
"first.");
case ConfigurableClientList::CACHE_DISABLED:
// This is an internal error. Auth server must have the cache
// enabled.
isc_throw(isc::Unexpected, "Cache disabled in client list of "
"class " << zone_class_);
}
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_;
// Parsed arguments
RRClass zone_class_;
Name origin_;
// A helper private method to parse and validate command parameters.
// 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& , isc::data::ConstElementPtr ) {
#if 0
TODO: Rewrite
bool validate(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") {
LOG_DEBUG(auth_logger, DBG_AUTH_OPS, AUTH_SQLITE3);
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();
isc::datasrc::DataSourceClient* datasrc(
server.getInMemoryClient(zone_class));
if (datasrc == NULL) {
isc_throw(AuthCommandError, "Memory data source is disabled");
}
zone_class_ = class_elem ? RRClass(class_elem->stringValue()) :
RRClass::IN();
ConstElementPtr origin_elem = args->get("origin");
if (!origin_elem) {
isc_throw(AuthCommandError, "Zone origin is missing");
}
const Name origin = Name(origin_elem->stringValue());
// Get the current zone
const DataSourceClient::FindResult result = datasrc->findZone(origin);
if (result.code != result::SUCCESS) {
isc_throw(AuthCommandError, "Zone " << origin <<
" is not found in data source");
}
// It would appear that dynamic_cast does not work on all systems;
// it seems to confuse the RTTI system, resulting in NULL return
// values. So we use the more dangerous static_pointer_cast here.
old_zone_finder_ = boost::static_pointer_cast<InMemoryZoneFinder>(
result.zone_finder);
origin_ = Name(origin_elem->stringValue());
#endif
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.
......@@ -310,11 +222,8 @@ createAuthCommand(const string& command_id) {
return (new ShutdownCommand());
} else if (command_id == "sendstats") {
return (new SendStatsCommand());
#if 0
// FIXME: The loadzone command will use #2046
} else if (command_id == "loadzone") {
return (new LoadZoneCommand());
#endif
} else if (false && command_id == "_throw_exception") {
// This is for testing purpose only and should not appear in the
// actual configuration syntax.
......
......@@ -19,6 +19,7 @@
#include <auth/auth_srv.h>
#include <auth/auth_config.h>
#include <auth/command.h>
#include <auth/datasrc_configurator.h>
#include <dns/name.h>
#include <dns/rrclass.h>
......@@ -69,7 +70,9 @@ protected:
{
server_.setStatisticsSession(&statistics_session_);
}
void checkAnswer(const int expected_code) {
void checkAnswer(const int expected_code, const char* name = "") {
SCOPED_TRACE(name);
parseAnswer(rcode_, result_);
EXPECT_EQ(expected_code, rcode_) << result_->str();
}
......@@ -177,29 +180,24 @@ TEST_F(AuthCommandTest, shutdownIncorrectPID) {
EXPECT_EQ(0, rcode_);
}
#if 0
TODO: This needs to be rewritten
Also, reenable everywhere
// 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.getInMemoryClient(RRClass::IN()));
EXPECT_EQ(ZoneFinder::SUCCESS, server.getInMemoryClient(RRClass::IN())->
findZone(Name("ns.test1.example")).zone_finder->
EXPECT_EQ(ZoneFinder::SUCCESS, server.getClientList(RRClass::IN())->
find(Name("ns.test1.example")).finder_->
find(Name("ns.test1.example"), RRType::A())->code);
EXPECT_EQ(ZoneFinder::NXRRSET, server.getInMemoryClient(RRClass::IN())->
findZone(Name("ns.test1.example")).zone_finder->
EXPECT_EQ(ZoneFinder::NXRRSET, server.getClientList(RRClass::IN())->
find(Name("ns.test1.example")).finder_->
find(Name("ns.test1.example"), RRType::AAAA())->code);
EXPECT_EQ(ZoneFinder::SUCCESS, server.getInMemoryClient(RRClass::IN())->
findZone(Name("ns.test2.example")).zone_finder->
EXPECT_EQ(ZoneFinder::SUCCESS, server.getClientList(RRClass::IN())->
find(Name("ns.test2.example")).finder_->
find(Name("ns.test2.example"), RRType::A())->code);
EXPECT_EQ(ZoneFinder::NXRRSET, server.getInMemoryClient(RRClass::IN())->
findZone(Name("ns.test2.example")).zone_finder->
EXPECT_EQ(ZoneFinder::NXRRSET, server.getClientList(RRClass::IN())->
find(Name("ns.test2.example")).finder_->
find(Name("ns.test2.example"), RRType::AAAA())->code);
}
#endif
void
configureZones(AuthSrv& server) {
......@@ -207,51 +205,44 @@ configureZones(AuthSrv& server) {
TEST_DATA_BUILDDIR "/test1.zone.copied"));
ASSERT_EQ(0, system(INSTALL_PROG " -c " TEST_DATA_DIR "/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\"}"
"]}]}"));
//TODO: zoneChecks(server);
const ConstElementPtr config(Element::fromJSON("{"
"\"IN\": [{"
" \"type\": \"MasterFiles\","
" \"params\": {"
" \"test1.example\": \"" +
string(TEST_DATA_BUILDDIR "/test1.zone.copied") + "\","
" \"test2.example\": \"" +
string(TEST_DATA_BUILDDIR "/test2.zone.copied") + "\""
" },"
" \"cache-enable\": true"
"}]}"));
DataSourceConfigurator::testReconfigure(&server, config);
zoneChecks(server);
}
#if 0
TODO: This needs to be rewritten and re-enabled
void
newZoneChecks(AuthSrv& server) {
EXPECT_TRUE(server.getInMemoryClient(RRClass::IN()));
EXPECT_EQ(ZoneFinder::SUCCESS, server.getInMemoryClient(RRClass::IN())->
findZone(Name("ns.test1.example")).zone_finder->
EXPECT_EQ(ZoneFinder::SUCCESS, server.getClientList(RRClass::IN())->
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, server.getInMemoryClient(RRClass::IN())->
findZone(Name("ns.test1.example")).zone_finder->
EXPECT_EQ(ZoneFinder::SUCCESS, server.getClientList(RRClass::IN())->
find(Name("ns.test1.example")).finder_->
find(Name("ns.test1.example"), RRType::AAAA())->code);
// test2.example shouldn't change
EXPECT_EQ(ZoneFinder::SUCCESS, server.getInMemoryClient(RRClass::IN())->
findZone(Name("ns.test2.example")).zone_finder->
EXPECT_EQ(ZoneFinder::SUCCESS, server.getClientList(RRClass::IN())->
find(Name("ns.test2.example")).finder_->
find(Name("ns.test2.example"), RRType::A())->code);
EXPECT_EQ(ZoneFinder::NXRRSET, server.getInMemoryClient(RRClass::IN())->
findZone(Name("ns.test2.example")).zone_finder->
EXPECT_EQ(ZoneFinder::NXRRSET, server.getClientList(RRClass::IN())->
find(Name("ns.test2.example")).finder_->
find(Name("ns.test2.example"), RRType::AAAA())->code);
}
#endif
TEST_F(AuthCommandTest,
#ifdef USE_STATIC_LINK
DISABLED_loadZone
#else
DISABLED_loadZone // Needs #2046
#endif
)
{
TEST_F(AuthCommandTest, loadZone) {
configureZones(server_);
ASSERT_EQ(0, system(INSTALL_PROG " -c " TEST_DATA_DIR
......@@ -265,62 +256,36 @@ TEST_F(AuthCommandTest,
Element::fromJSON(
"{\"origin\": \"test1.example\"}"));
checkAnswer(0);
//TODO: newZoneChecks(server_);
newZoneChecks(server_);
}
TEST_F(AuthCommandTest,
#ifdef USE_STATIC_LINK
DISABLED_loadZoneSQLite3
#else
DISABLED_loadZoneSQLite3 // Needs #2044
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\""
" }"
" ]"
" }"
"],"
" \"database_file\": \"" + test_db + "\""
"}"));
module_session.setLocalConfig(map);
server_.setConfigSession(&module_session);
server_.updateConfig(map);
#if 0
FIXME: This needs to be done slightly differently
const ConstElementPtr config(Element::fromJSON("{"
"\"IN\": [{"
" \"type\": \"sqlite3\","
" \"params\": {\"database_file\": \"" + test_db + "\"},"
" \"cache-enable\": true,"
" \"cache-zones\": [\"example.org\"]"
"}]}"));
DataSourceConfigurator::testReconfigure(&server_, config);
// Check that the A record at www.example.org does not exist
ASSERT_TRUE(server_.hasInMemoryClient());
EXPECT_EQ(ZoneFinder::NXDOMAIN, server_.getInMemoryClient(RRClass::IN())->
findZone(Name("example.org")).zone_finder->
EXPECT_EQ(ZoneFinder::NXDOMAIN, server_.getClientList(RRClass::IN())->
find(Name("example.org")).finder_->
find(Name("www.example.org"), RRType::A())->code);
#endif
// Add the record to the underlying sqlite database, by loading
// it as a separate datasource, and updating it
......@@ -338,107 +303,52 @@ TEST_F(AuthCommandTest,
sql_updater->addRRset(*rrset);
sql_updater->commit();
#if 0
TODO:
// This new record is in the database now, but should not be in the
// memory-datasource yet, so check again
EXPECT_EQ(ZoneFinder::NXDOMAIN, server_.getInMemoryClient(RRClass::IN())->
findZone(Name("example.org")).zone_finder->
EXPECT_EQ(ZoneFinder::NXDOMAIN, server_.getClientList(RRClass::IN())->
find(Name("example.org")).finder_->
find(Name("www.example.org"), RRType::A())->code);
#endif
// Now send the command to reload it
result_ = execAuthServerCommand(server_, "loadzone",
Element::fromJSON(
"{\"origin\": \"example.org\"}"));
checkAnswer(0);
checkAnswer(0, "Successfull load");
#if 0
// And now it should be present too.
EXPECT_EQ(ZoneFinder::SUCCESS, server_.getInMemoryClient(RRClass::IN())->
findZone(Name("example.org")).zone_finder->
EXPECT_EQ(ZoneFinder::SUCCESS, server_.getClientList(RRClass::IN())->
find(Name("example.org")).finder_->
find(Name("www.example.org"), RRType::A())->code);
#endif
// Some error cases. First, the zone has no configuration. (note .com here)
result_ = execAuthServerCommand(server_, "loadzone",
Element::fromJSON("{\"origin\": \"example.com\"}"));
checkAnswer(1);
#if 0
FIXME
checkAnswer(1, "example.com");
// The previous zone is not hurt in any way
EXPECT_EQ(ZoneFinder::SUCCESS, server_.getInMemoryClient(RRClass::IN())->
findZone(Name("example.org")).zone_finder->
EXPECT_EQ(ZoneFinder::SUCCESS, server_.getClientList(RRClass::IN())->
find(Name("example.org")).finder_->
find(Name("example.org"), RRType::SOA())->code);
#endif
module_session.setLocalConfig(Element::fromJSON("{\"datasources\": []}"));
result_ = execAuthServerCommand(server_, "loadzone",
Element::fromJSON(
"{\"origin\": \"example.org\"}"));
checkAnswer(1);
const ConstElementPtr config2(Element::fromJSON("{"
"\"IN\": [{"
" \"type\": \"sqlite3\","
" \"params\": {\"database_file\": \"" + bad_db + "\"},"
" \"cache-enable\": true,"
" \"cache-zones\": [\"example.com\"]"
"}]}"));
EXPECT_THROW(DataSourceConfigurator::testReconfigure(&server_, config2),
ConfigurableClientList::ConfigurationError);
#if 0
FIXME
// 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);
#endif
// 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);
#if 0
FIXME
// 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);
#endif
checkAnswer(1, "Unreadable");
// Broken configuration (not valid against the spec)
const ElementPtr
broken(Element::fromJSON("{\"datasources\": ["
" {"
" \"type\": \"memory\","
" \"zones\": [[]]"
" }"
"]}"));
module_session.setLocalConfig(broken);
checkAnswer(1);
#if 0
FIXME
// The previous zone is not hurt in any way
EXPECT_EQ(ZoneFinder::SUCCESS, server_.getInMemoryClient(RRClass::IN())->
findZone(Name("example.org")).zone_finder->
EXPECT_EQ(ZoneFinder::SUCCESS, server_.getClientList(RRClass::IN())->
find(Name("example.org")).finder_->
find(Name("example.org"), RRType::SOA())->code);
#endif
}
TEST_F(AuthCommandTest,
#ifdef USE_STATIC_LINK
DISABLED_loadBrokenZone
#else
DISABLED_loadBrokenZone // Needs #2046
#endif
)
{
TEST_F(AuthCommandTest, loadBrokenZone) {
configureZones(server_);
ASSERT_EQ(0, system(INSTALL_PROG " -c " TEST_DATA_DIR
......@@ -448,17 +358,10 @@ TEST_F(AuthCommandTest,
Element::fromJSON(
"{\"origin\": \"test1.example\"}"));
checkAnswer(1);
//zoneChecks(server_); // zone shouldn't be replaced
zoneChecks(server_); // zone shouldn't be replaced
}
TEST_F(AuthCommandTest,
#ifdef USE_STATIC_LINK
DISABLED_loadUnreadableZone
#else
DISABLED_loadUnreadableZone // Needs #2046
#endif
)
{
TEST_F(AuthCommandTest, loadUnreadableZone) {
configureZones(server_);
// install the zone file as unreadable
......@@ -469,7 +372,7 @@ TEST_F(AuthCommandTest,
Element::fromJSON(
"{\"origin\": \"test1.example\"}"));
checkAnswer(1);
//zoneChecks(server_); // zone shouldn't be replaced
zoneChecks(server_); // zone shouldn't be replaced
}
TEST_F(AuthCommandTest, loadZoneWithoutDataSrc) {
......@@ -481,72 +384,45 @@ TEST_F(AuthCommandTest, loadZoneWithoutDataSrc) {
checkAnswer(1);
}
TEST_F(AuthCommandTest,
#ifdef USE_STATIC_LINK
DISABLED_loadZoneInvalidParams
#else
DISABLED_loadZoneInvalidParams // Needs #2046
#endif
)
{
TEST_F(AuthCommandTest, loadZoneInvalidParams) {