Commit 42d8e0ec authored by Jelte Jansen's avatar Jelte Jansen
Browse files

[master] Merge branch 'trac1207'

parents a5c587cc bb745745
......@@ -51,9 +51,9 @@ b10_auth_SOURCES += statistics.cc statistics.h
b10_auth_SOURCES += main.cc
# This is a temporary workaround for #1206, where the InMemoryClient has been
# moved to an ldopened library. We could add that library to LDADD, but that
# is nonportable. When #1207 is done this becomes moot anyway, and the
# specific workaround is not needed anymore, so we can then remove this
# line again.
# is nonportable. This should've been moot after #1207, but there is still
# one dependency; the in-memory-specific zone loader call is still in
# auth.
b10_auth_SOURCES += ${top_srcdir}/src/lib/datasrc/memory_datasrc.cc
nodist_b10_auth_SOURCES = auth_messages.h auth_messages.cc
......
......@@ -43,22 +43,19 @@ using namespace isc::datasrc;
using namespace isc::server_common::portconfig;
namespace {
// Forward declaration
AuthConfigParser*
createAuthConfigParser(AuthSrv& server, const std::string& config_id,
bool internal);
/// A derived \c AuthConfigParser class for the "datasources" configuration
/// identifier.
class DatasourcesConfig : public AuthConfigParser {
public:
DatasourcesConfig(AuthSrv& server) : server_(server) {}
DatasourcesConfig(AuthSrv& server) : server_(server)
{}
virtual void build(ConstElementPtr config_value);
virtual void commit();
private:
AuthSrv& server_;
vector<boost::shared_ptr<AuthConfigParser> > datasources_;
set<string> configured_sources_;
vector<pair<RRClass, DataSourceClientContainerPtr> > clients_;
};
/// A derived \c AuthConfigParser for the version value
......@@ -86,137 +83,60 @@ DatasourcesConfig::build(ConstElementPtr config_value) {
isc_throw(AuthConfigError, "Data source type '" <<
datasrc_type->stringValue() << "' already configured");
}
boost::shared_ptr<AuthConfigParser> datasrc_config =
boost::shared_ptr<AuthConfigParser>(
createAuthConfigParser(server_, string("datasources/") +
datasrc_type->stringValue(),
true));
datasrc_config->build(datasrc_elem);
datasources_.push_back(datasrc_config);
configured_sources_.insert(datasrc_type->stringValue());
}
}
// Apart from that it's not really easy to get at the default
// class value for the class here, it should probably really
// be a property of the instantiated data source. For now
// use hardcoded default IN.
const RRClass rrclass =
datasrc_elem->contains("class") ?
RRClass(datasrc_elem->get("class")->stringValue()) : RRClass::IN();
// Right now, we only support the in-memory data source for the
// RR class of IN. We reject other cases explicitly by hardcoded
// checks. This will soon be generalized, at which point these
// checks will also have to be cleaned up.
if (rrclass != RRClass::IN()) {
isc_throw(isc::InvalidParameter, "Unsupported data source class: "
<< rrclass);
}
if (datasrc_type->stringValue() != "memory") {
isc_throw(AuthConfigError, "Unsupported data source type: "
<< datasrc_type->stringValue());
}
void
DatasourcesConfig::commit() {
// XXX a short term workaround: clear all data sources and then reset
// to new ones so that we can remove data sources that don't exist in
// the new configuration and have been used in the server.
// This could be inefficient and requires knowledge about
// server implementation details, and isn't scalable wrt the number of
// data source types, and should eventually be improved.
// Currently memory data source for class IN is the only possibility.
server_.setInMemoryClient(RRClass::IN(), AuthSrv::InMemoryClientPtr());
// Create a new client for the specified data source and store it
// in the local vector. For now, we always build a new client
// from the scratch, and replace any existing ones with the new ones.
// We might eventually want to optimize building zones (in case of
// reloading) by selectively loading fresh zones for data source
// where zone loading is expensive (such as in-memory).
clients_.push_back(
pair<RRClass, DataSourceClientContainerPtr>(
rrclass,
DataSourceClientContainerPtr(new DataSourceClientContainer(
datasrc_type->stringValue(),
datasrc_elem))));
BOOST_FOREACH(boost::shared_ptr<AuthConfigParser> datasrc_config,
datasources_) {
datasrc_config->commit();
configured_sources_.insert(datasrc_type->stringValue());
}
}
/// A derived \c AuthConfigParser class for the memory type datasource
/// configuration. It does not correspond to the configuration syntax;
/// it's instantiated for internal use.
class MemoryDatasourceConfig : public AuthConfigParser {
public:
MemoryDatasourceConfig(AuthSrv& server) :
server_(server),
rrclass_(0) // XXX: dummy initial value
{}
virtual void build(ConstElementPtr config_value);
virtual void commit() {
server_.setInMemoryClient(rrclass_, memory_client_);
}
private:
AuthSrv& server_;
RRClass rrclass_;
AuthSrv::InMemoryClientPtr memory_client_;
};
void
MemoryDatasourceConfig::build(ConstElementPtr config_value) {
// XXX: apparently we cannot retrieve the default RR class from the
// module spec. As a temporary workaround we hardcode the default value.
ConstElementPtr rrclass_elem = config_value->get("class");
rrclass_ = RRClass(rrclass_elem ? rrclass_elem->stringValue() : "IN");
// We'd eventually optimize building zones (in case of reloading) by
// selectively loading fresh zones. Right now we simply check the
// RR class is supported by the server implementation.
server_.getInMemoryClient(rrclass_);
memory_client_ = AuthSrv::InMemoryClientPtr(new InMemoryClient());
ConstElementPtr zones_config = config_value->get("zones");
if (!zones_config) {
// XXX: Like the RR class, we cannot retrieve the default value here,
// so we assume an empty zone list in this case.
return;
}
BOOST_FOREACH(ConstElementPtr zone_config, zones_config->listValue()) {
ConstElementPtr origin = zone_config->get("origin");
const string origin_txt = origin ? origin->stringValue() : "";
if (origin_txt.empty()) {
isc_throw(AuthConfigError, "Missing zone origin");
}
ConstElementPtr file = zone_config->get("file");
const string file_txt = file ? file->stringValue() : "";
if (file_txt.empty()) {
isc_throw(AuthConfigError, "Missing zone file for zone: "
<< origin_txt);
}
// We support the traditional text type and SQLite3 backend. For the
// latter we create a client for the underlying SQLite3 data source,
// and build the in-memory zone using an iterator of the underlying
// zone.
ConstElementPtr filetype = zone_config->get("filetype");
const string filetype_txt = filetype ? filetype->stringValue() :
"text";
boost::scoped_ptr<DataSourceClientContainer> container;
if (filetype_txt == "sqlite3") {
container.reset(new DataSourceClientContainer(
"sqlite3",
Element::fromJSON("{\"database_file\": \"" +
file_txt + "\"}")));
} else if (filetype_txt != "text") {
isc_throw(AuthConfigError, "Invalid filetype for zone "
<< origin_txt << ": " << filetype_txt);
}
// Note: we don't want to have such small try-catch blocks for each
// specific error. We may eventually want to introduce some unified
// error handling framework as we have more configuration parameters.
// See bug #1627 for the relevant discussion.
InMemoryZoneFinder* imzf = NULL;
try {
imzf = new InMemoryZoneFinder(rrclass_, Name(origin_txt));
} catch (const isc::dns::NameParserException& ex) {
isc_throw(AuthConfigError, "unable to parse zone's origin: " <<
ex.what());
}
boost::shared_ptr<InMemoryZoneFinder> zone_finder(imzf);
const result::Result result = memory_client_->addZone(zone_finder);
if (result == result::EXIST) {
isc_throw(AuthConfigError, "zone "<< origin->str()
<< " already exists");
}
/*
* TODO: Once we have better reloading of configuration (something
* else than throwing everything away and loading it again), we will
* need the load method to be split into some kind of build and
* commit/abort parts.
*/
if (filetype_txt == "text") {
zone_finder->load(file_txt);
} else {
zone_finder->load(*container->getInstance().getIterator(
Name(origin_txt)));
}
DatasourcesConfig::commit() {
// As noted in build(), the current implementation only supports the
// in-memory data source for class IN, and build() should have ensured
// it. So, depending on the vector is empty or not, we either clear
// or install an in-memory data source for the server.
//
// When we generalize it, we'll somehow install all data source clients
// built in the vector, clearing deleted ones from the server.
if (clients_.empty()) {
server_.setInMemoryClient(RRClass::IN(),
DataSourceClientContainerPtr());
} else {
server_.setInMemoryClient(clients_.front().first,
clients_.front().second);
}
}
......@@ -314,13 +234,10 @@ private:
*/
AddrListPtr rollbackAddresses_;
};
} // end of unnamed namespace
// This is a generalized version of create function that can create
// an AuthConfigParser object for "internal" use.
AuthConfigParser*
createAuthConfigParser(AuthSrv& server, const std::string& config_id,
bool internal)
{
createAuthConfigParser(AuthSrv& server, const std::string& config_id) {
// For the initial implementation we use a naive if-else blocks for
// simplicity. In future we'll probably generalize it using map-like
// data structure, and may even provide external register interface so
......@@ -329,8 +246,6 @@ createAuthConfigParser(AuthSrv& server, const std::string& config_id,
return (new DatasourcesConfig(server));
} else if (config_id == "statistics-interval") {
return (new StatisticsIntervalConfig(server));
} else if (internal && config_id == "datasources/memory") {
return (new MemoryDatasourceConfig(server));
} else if (config_id == "listen_on") {
return (new ListenAddressConfig(server));
} else if (config_id == "_commit_throw") {
......@@ -351,12 +266,6 @@ createAuthConfigParser(AuthSrv& server, const std::string& config_id,
config_id);
}
}
} // end of unnamed namespace
AuthConfigParser*
createAuthConfigParser(AuthSrv& server, const std::string& config_id) {
return (createAuthConfigParser(server, config_id, false));
}
void
configureAuthServer(AuthSrv& server, ConstElementPtr config_set) {
......
......@@ -141,7 +141,7 @@ public:
/// In-memory data source. Currently class IN only for simplicity.
const RRClass memory_client_class_;
AuthSrv::InMemoryClientPtr memory_client_;
isc::datasrc::DataSourceClientContainerPtr memory_client_container_;
/// Hot spot cache
isc::datasrc::HotCache cache_;
......@@ -389,34 +389,46 @@ AuthSrv::getConfigSession() const {
return (impl_->config_session_);
}
AuthSrv::InMemoryClientPtr
AuthSrv::getInMemoryClient(const RRClass& rrclass) {
// XXX: for simplicity, we only support the IN class right now.
isc::datasrc::DataSourceClientContainerPtr
AuthSrv::getInMemoryClientContainer(const RRClass& rrclass) {
if (rrclass != impl_->memory_client_class_) {
isc_throw(InvalidParameter,
"Memory data source is not supported for RR class "
<< rrclass);
}
return (impl_->memory_client_);
return (impl_->memory_client_container_);
}
isc::datasrc::DataSourceClient*
AuthSrv::getInMemoryClient(const RRClass& rrclass) {
if (hasInMemoryClient()) {
return (&getInMemoryClientContainer(rrclass)->getInstance());
} else {
return (NULL);
}
}
bool
AuthSrv::hasInMemoryClient() const {
return (impl_->memory_client_container_);
}
void
AuthSrv::setInMemoryClient(const isc::dns::RRClass& rrclass,
InMemoryClientPtr memory_client)
DataSourceClientContainerPtr memory_client)
{
// XXX: see above
if (rrclass != impl_->memory_client_class_) {
isc_throw(InvalidParameter,
"Memory data source is not supported for RR class "
<< rrclass);
} else if (!impl_->memory_client_ && memory_client) {
} else if (!impl_->memory_client_container_ && memory_client) {
LOG_DEBUG(auth_logger, DBG_AUTH_OPS, AUTH_MEM_DATASRC_ENABLED)
.arg(rrclass);
} else if (impl_->memory_client_ && !memory_client) {
} else if (impl_->memory_client_container_ && !memory_client) {
LOG_DEBUG(auth_logger, DBG_AUTH_OPS, AUTH_MEM_DATASRC_DISABLED)
.arg(rrclass);
}
impl_->memory_client_ = memory_client;
impl_->memory_client_container_ = memory_client;
}
uint32_t
......@@ -585,10 +597,12 @@ AuthSrvImpl::processNormalQuery(const IOMessage& io_message, Message& message,
// If a memory data source is configured call the separate
// Query::process()
const ConstQuestionPtr question = *message.beginQuestion();
if (memory_client_ && memory_client_class_ == question->getClass()) {
if (memory_client_container_ &&
memory_client_class_ == question->getClass()) {
const RRType& qtype = question->getType();
const Name& qname = question->getName();
query_.process(*memory_client_, qname, qtype, message, dnssec_ok);
query_.process(memory_client_container_->getInstance(),
qname, qtype, message, dnssec_ok);
} else {
datasrc::Query query(message, cache_, dnssec_ok);
data_sources_.doQuery(query);
......
......@@ -17,12 +17,9 @@
#include <string>
// For InMemoryClientPtr below. This should be a temporary definition until
// we reorganize the data source framework.
#include <boost/shared_ptr.hpp>
#include <cc/data.h>
#include <config/ccsession.h>
#include <datasrc/factory.h>
#include <dns/message.h>
#include <dns/opcode.h>
#include <util/buffer.h>
......@@ -40,9 +37,6 @@
#include <auth/statistics.h>
namespace isc {
namespace datasrc {
class InMemoryClient;
}
namespace xfr {
class AbstractXfroutClient;
}
......@@ -235,19 +229,14 @@ public:
///
void setXfrinSession(isc::cc::AbstractSession* xfrin_session);
/// A shared pointer type for \c InMemoryClient.
///
/// This is defined inside the \c AuthSrv class as it's supposed to be
/// a short term interface until we integrate the in-memory and other
/// data source frameworks.
typedef boost::shared_ptr<isc::datasrc::InMemoryClient> InMemoryClientPtr;
/// An immutable shared pointer type for \c InMemoryClient.
typedef boost::shared_ptr<const isc::datasrc::InMemoryClient>
ConstInMemoryClientPtr;
/// Returns the in-memory data source configured for the \c AuthSrv,
/// if any.
/// if any, as a pointer.
///
/// This is mostly a convenience function around
/// \c getInMemoryClientContainer, which saves the caller the step
/// of having to call getInstance().
/// The pointer is of course only valid as long as the container
/// exists.
///
/// The in-memory data source is configured per RR class. However,
/// the data source may not be available for all RR classes.
......@@ -262,24 +251,48 @@ 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.
InMemoryClientPtr getInMemoryClient(const isc::dns::RRClass& rrclass);
isc::datasrc::DataSourceClient* getInMemoryClient(
const isc::dns::RRClass& rrclass);
/// Returns the DataSourceClientContainer of the in-memory datasource
///
/// \exception InvalidParameter if the given class does not match
/// the one in the memory data source, or if the memory
/// datasource has not been set (callers can check with
/// \c hasMemoryDataSource())
///
/// \param rrclass The RR class of the requested in-memory data source.
/// \return A shared pointer to the in-memory data source, if configured;
/// otherwise an empty shared pointer.
isc::datasrc::DataSourceClientContainerPtr getInMemoryClientContainer(
const isc::dns::RRClass& rrclass);
/// Checks if the in-memory data source has been set.
///
/// Right now, only one datasource at a time is effectively supported.
/// This is a helper method to check whether it is the in-memory one.
/// This is mostly useful for current testing, and is expected to be
/// removed (or changed in behaviour) soon, when the general
/// multi-data-source framework is completed.
///
/// \return True if the in-memory datasource has been set.
bool hasInMemoryClient() const;
/// Sets or replaces the in-memory data source of the specified RR class.
///
/// As noted in \c getInMemoryClient(), some RR classes may not be
/// supported, in which case an exception of class \c InvalidParameter
/// will be thrown.
/// Some RR classes may not be supported, in which case an exception
/// of class \c InvalidParameter will be thrown.
/// This method never throws an exception otherwise.
///
/// If there is already an in memory data source configured, it will be
/// replaced with the newly specified one.
/// \c memory_datasrc can be NULL, in which case it will (re)disable the
/// in-memory data source.
/// \c memory_client can be an empty shared pointer, in which case it
/// will (re)disable the in-memory data source.
///
/// \param rrclass The RR class of the in-memory data source to be set.
/// \param memory_client A (shared) pointer to \c InMemoryClient to be set.
void setInMemoryClient(const isc::dns::RRClass& rrclass,
InMemoryClientPtr memory_client);
isc::datasrc::DataSourceClientContainerPtr memory_client);
/// \brief Set the communication session with Statistics.
///
......
......@@ -210,8 +210,8 @@ private:
const RRClass zone_class =
class_elem ? RRClass(class_elem->stringValue()) : RRClass::IN();
AuthSrv::InMemoryClientPtr datasrc(server.
getInMemoryClient(zone_class));
isc::datasrc::DataSourceClient* datasrc(
server.getInMemoryClient(zone_class));
if (datasrc == NULL) {
isc_throw(AuthCommandError, "Memory data source is disabled");
}
......@@ -223,13 +223,16 @@ private:
const Name origin = Name(origin_elem->stringValue());
// Get the current zone
const InMemoryClient::FindResult result = datasrc->findZone(origin);
const DataSourceClient::FindResult result = datasrc->findZone(origin);
if (result.code != result::SUCCESS) {
isc_throw(AuthCommandError, "Zone " << origin <<
" is not found in data source");
}
old_zone_finder_ = boost::dynamic_pointer_cast<InMemoryZoneFinder>(
// 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);
return (true);
......
......@@ -44,9 +44,9 @@ run_unittests_SOURCES += statistics_unittest.cc
run_unittests_SOURCES += run_unittests.cc
# This is a temporary workaround for #1206, where the InMemoryClient has been
# moved to an ldopened library. We could add that library to LDADD, but that
# is nonportable. When #1207 is done this becomes moot anyway, and the
# specific workaround is not needed anymore, so we can then remove this
# line again.
# is nonportable. This should've been moot after #1207, but there is still
# one dependency; the in-memory-specific zone loader call is still in
# auth.
run_unittests_SOURCES += ${top_srcdir}/src/lib/datasrc/memory_datasrc.cc
......
......@@ -17,6 +17,7 @@
#include <vector>
#include <boost/shared_ptr.hpp>
#include <boost/scoped_ptr.hpp>
#include <gtest/gtest.h>
......@@ -89,6 +90,13 @@ protected:
server.setStatisticsSession(&statistics_session);
}
~AuthSrvTest() {
// Clear the message now; depending on the RTTI implementation,
// type information may be lost if the message is cleared
// automatically later, so as a precaution we do it now.
parse_message->clear(Message::PARSE);
}
virtual void processMessage() {
// If processMessage has been called before, parse_message needs
// to be reset. If it hasn't, there's no harm in doing so
......@@ -830,16 +838,23 @@ TEST_F(AuthSrvTest, updateConfigFail) {
QR_FLAG | AA_FLAG, 1, 1, 1, 0);
}
TEST_F(AuthSrvTest, updateWithInMemoryClient) {
TEST_F(AuthSrvTest,
#ifdef USE_STATIC_LINK
DISABLED_updateWithInMemoryClient
#else
updateWithInMemoryClient
#endif
)
{
// Test configuring memory data source. Detailed test cases are covered
// in the configuration tests. We only check the AuthSrv interface here.
// By default memory data source isn't enabled
EXPECT_EQ(AuthSrv::InMemoryClientPtr(), server.getInMemoryClient(rrclass));
EXPECT_FALSE(server.hasInMemoryClient());
updateConfig(&server,
"{\"datasources\": [{\"type\": \"memory\"}]}", true);
// after successful configuration, we should have one (with empty zoneset).
ASSERT_NE(AuthSrv::InMemoryClientPtr(), server.getInMemoryClient(rrclass));
EXPECT_TRUE(server.hasInMemoryClient());
EXPECT_EQ(0, server.getInMemoryClient(rrclass)->getZoneCount());
// The memory data source is empty, should return REFUSED rcode.
......@@ -851,13 +866,20 @@ TEST_F(AuthSrvTest, updateWithInMemoryClient) {
opcode.getCode(), QR_FLAG, 1, 0, 0, 0);
}
TEST_F(AuthSrvTest, queryWithInMemoryClientNoDNSSEC) {
TEST_F(AuthSrvTest,
#ifdef USE_STATIC_LINK
DISABLED_queryWithInMemoryClientNoDNSSEC
#else
queryWithInMemoryClientNoDNSSEC
#endif
)
{
// In this example, we do simple check that query is handled from the
// query handler class, and confirm it returns no error and a non empty
// answer section. Detailed examination on the response content
// for various types of queries are tested in the query tests.
updateConfig(&server, CONFIG_INMEMORY_EXAMPLE, true);
ASSERT_NE(AuthSrv::InMemoryClientPtr(), server.getInMemoryClient(rrclass));
EXPECT_TRUE(server.hasInMemoryClient());
EXPECT_EQ(1, server.getInMemoryClient(rrclass)->getZoneCount());
createDataFromFile("nsec3query_nodnssec_fromWire.wire");
......@@ -869,12 +891,19 @@ TEST_F(AuthSrvTest, queryWithInMemoryClientNoDNSSEC) {
opcode.getCode(), QR_FLAG | AA_FLAG, 1, 1, 2, 1);
}
TEST_F(AuthSrvTest, queryWithInMemoryClientDNSSEC) {
TEST_F(AuthSrvTest,
#ifdef USE_STATIC_LINK
DISABLED_queryWithInMemoryClientDNSSEC
#else
queryWithInMemoryClientDNSSEC
#endif
)
{
// Similar to the previous test, but the query has the DO bit on.
// The response should contain RRSIGs, and should have more RRs than
// the previous case.
updateConfig(&server, CONFIG_INMEMORY_EXAMPLE, true);
ASSERT_NE(AuthSrv::InMemoryClientPtr(), server.getInMemoryClient(rrclass));
EXPECT_TRUE(server.hasInMemoryClient());
EXPECT_EQ(1, server.getInMemoryClient(rrclass)->getZoneCount());
createDataFromFile("nsec3query_fromWire.wire");
......@@ -886,7 +915,14 @@ TEST_F(AuthSrvTest, queryWithInMemoryClientDNSSEC) {
opcode.getCode(), QR_FLAG | AA_FLAG, 1, 2, 3, 3);
}
TEST_F(AuthSrvTest, chQueryWithInMemoryClient) {
TEST_F(AuthSrvTest,
#ifdef USE_STATIC_LINK
DISABLED_chQueryWithInMemoryClient
#else
chQueryWithInMemoryClient
#endif
)
{
// Configure memory data source for class IN
updateConfig(&server, "{\"datasources\": "
"[{\"class\": \"IN\", \"type\": \"memory\"}]}", true);
......@@ -1108,7 +1144,7 @@ TEST_F(AuthSrvTest, processNormalQuery_reuseRenderer2) {
//
namespace {
/// A the possible methods to throw in, either in FakeInMemoryClient or
/// The possible methods to throw in, either in FakeClient or
/// FakeZoneFinder
enum ThrowWhen {
THROW_NEVER,
......@@ -1132,10 +1168,10 @@ checkThrow(ThrowWhen method, ThrowWhen throw_at, bool isc_exception) {
}
}
/// \brief proxy class for the ZoneFinder returned by the InMemoryClient
/// proxied by FakeInMemoryClient
/// \brief proxy class for the ZoneFinder returned by the Client
/// proxied by FakeClient
///
/// See the documentation for FakeInMemoryClient for more information,