With integration into the rest.
Conflicts:
src/lib/datasrc/client_list.h
--- /dev/null
- list.reset(new List);
+// Copyright (C) 2012 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.
+
+#ifndef DATASRC_CONFIGURATOR_H
+#define DATASRC_CONFIGURATOR_H
+
+#include "auth_srv.h"
+
+#include <datasrc/client_list.h>
+#include <config/ccsession.h>
+#include <cc/data.h>
+
+#include <set>
+
+/// \brief A class to configure the authoritative server's data source lists
+///
+/// This will hook into the data_sources module configuration and it will
+/// keep the local copy of data source clients in the list in the authoritative
+/// server.
+///
+/// The class is slightly unusual. Due to some technical limitations, the hook
+/// needs to be static method. Therefore it is not possible to create instances
+/// of the class.
+///
+/// Also, the class is a template. This is simply because of easier testing.
+/// You don't need to pay attention to it, use the DataSourceConfigurator
+/// type alias instead.
+template<class Server, class List>
+class DataSourceConfiguratorGeneric {
+private:
+ /// \brief Disallow creation of instances
+ DataSourceConfiguratorGeneric();
+ /// \brief Internal method to hook into the ModuleCCSession
+ ///
+ /// It simply calls reconfigure.
+ static void reconfigureInternal(const std::string&,
+ isc::data::ConstElementPtr config,
+ const isc::config::ConfigData&)
+ {
+ reconfigure(config);
+ }
+ static Server* server_;
+ static isc::config::ModuleCCSession* session_;
+ typedef boost::shared_ptr<List> ListPtr;
+public:
+ /// \brief Initializes the class.
+ ///
+ /// This configures which session and server should be used.
+ /// It hooks to the session now and downloads the configuration.
+ /// It is synchronous (it may block for some time).
+ ///
+ /// Note that you need to call deinit before the server or
+ /// session dies, otherwise it might access them after they
+ /// are destroyed.
+ ///
+ /// \param session The session to hook into and to access the configuration
+ /// through.
+ /// \param server It is the server to configure.
+ /// \throw isc::InvalidOperation if this is called when already initialized.
+ /// \throw isc::InvalidParameter if any of the parameters is NULL
+ /// \throw isc::config::ModuleCCError if the remote configuration is not
+ /// available for some reason.
+ static void init(isc::config::ModuleCCSession *session,
+ Server *server)
+ {
+ if (session == NULL) {
+ isc_throw(isc::InvalidParameter, "The session must not be NULL");
+ }
+ if (server == NULL) {
+ isc_throw(isc::InvalidParameter, "The server must not be NULL");
+ }
+ if (server_ != NULL) {
+ isc_throw(isc::InvalidOperation,
+ "The configurator is already initialized");
+ }
+ server_ = server;
+ session_ = session;
+ session->addRemoteConfig("data_sources", reconfigureInternal, false);
+ }
+ /// \brief Deinitializes the class.
+ ///
+ /// This detaches from the session and removes the server from internal
+ /// storage. The current configuration in the server is preserved.
+ ///
+ /// This can be called even if it is not initialized currently. You
+ /// can initialize it again after this.
+ static void deinit() {
+ if (session_ != NULL) {
+ session_->removeRemoteConfig("data_sources");
+ }
+ session_ = NULL;
+ server_ = NULL;
+ }
+ /// \brief Reads new configuration and replaces the old one.
+ ///
+ /// It instructs the server to replace the lists with new ones as needed.
+ /// You don't need to call it directly (but you could, though the benefit
+ /// is unkown and it would be questionable at least). It is called
+ /// automatically on normal updates.
+ ///
+ /// \param config The configuration value to parse. It is in the form
+ /// as an update from the config manager.
+ /// \throw InvalidOperation if it is called when not initialized.
+ static void reconfigure(const isc::data::ConstElementPtr& config) {
+ if (server_ == NULL) {
+ isc_throw(isc::InvalidOperation,
+ "Can't reconfigure while not inited");
+ }
+ typedef std::map<std::string, isc::data::ConstElementPtr> Map;
+ typedef std::pair<isc::dns::RRClass, ListPtr> RollbackPair;
+ typedef std::pair<isc::dns::RRClass, isc::data::ConstElementPtr>
+ RollbackConfiguration;
+ // Some structures to be able to perform a rollback
+ std::vector<RollbackPair> rollback_sets;
+ std::vector<RollbackConfiguration> rollback_configurations;
+ try {
+ // Get the configuration and current state.
+ const Map& map(config->mapValue());
+ const std::vector<isc::dns::RRClass>
+ activeVector(server_->getClientListClasses());
+ std::set<isc::dns::RRClass> active(activeVector.begin(),
+ activeVector.end());
+ // Go through the configuration and change everything.
+ for (Map::const_iterator it(map.begin()); it != map.end(); ++it) {
+ isc::dns::RRClass rrclass(it->first);
+ active.erase(rrclass);
+ ListPtr list(server_->getClientList(rrclass));
+ bool need_set(false);
+ if (list) {
+ rollback_configurations.
+ push_back(RollbackConfiguration(rrclass,
+ list->getConfiguration()));
+ } else {
++ list.reset(new List(rrclass));
+ need_set = true;
+ rollback_sets.push_back(RollbackPair(rrclass, ListPtr()));
+ }
+ list->configure(it->second, true);
+ if (need_set) {
+ server_->setClientList(rrclass, list);
+ }
+ }
+ // Remove the ones that are not in the configuration.
+ for (std::set<isc::dns::RRClass>::iterator it(active.begin());
+ it != active.end(); ++it) {
+ // There seems to be no way the setClientList could throw.
+ // But this is just to make sure in case it did to restore
+ // the original.
+ rollback_sets.push_back(
+ RollbackPair(*it, server_->getClientList(*it)));
+ server_->setClientList(*it, ListPtr());
+ }
+ } catch (...) {
+ // Perform a rollback of the changes. The old configuration should
+ // work.
+ for (typename std::vector<RollbackPair>::const_iterator
+ it(rollback_sets.begin()); it != rollback_sets.end(); ++it) {
+ server_->setClientList(it->first, it->second);
+ }
+ for (typename std::vector<RollbackConfiguration>::const_iterator
+ it(rollback_configurations.begin());
+ it != rollback_configurations.end(); ++it) {
+ server_->getClientList(it->first)->configure(it->second, true);
+ }
+ throw;
+ }
+ }
+ /// \brief Version of reconfigure for easier testing.
+ ///
+ /// This method can be used to reconfigure a server without first
+ /// initializing the configurator. This does not need a session.
+ /// Otherwise, it acts the same as reconfigure.
+ ///
+ /// This is not meant for production code. Do not use there.
+ ///
+ /// \param server The server to configure.
+ /// \param config The config to use.
+ /// \throw isc::InvalidOperation if the configurator is initialized.
+ /// \throw anything that reconfigure does.
+ static void testReconfigure(Server* server,
+ const isc::data::ConstElementPtr& config)
+ {
+ if (server_ != NULL) {
+ isc_throw(isc::InvalidOperation, "Currently initialized.");
+ }
+ try {
+ server_ = server;
+ reconfigure(config);
+ server_ = NULL;
+ } catch (...) {
+ server_ = NULL;
+ throw;
+ }
+ }
+};
+
+template<class Server, class List>
+isc::config::ModuleCCSession*
+DataSourceConfiguratorGeneric<Server, List>::session_(NULL);
+
+template<class Server, class List>
+Server* DataSourceConfiguratorGeneric<Server, List>::server_(NULL);
+
+/// \brief Concrete version of DataSourceConfiguratorGeneric for the
+/// use in authoritative server.
+typedef DataSourceConfiguratorGeneric<AuthSrv,
+ isc::datasrc::ConfigurableClientList>
+ DataSourceConfigurator;
+
+#endif
EXPECT_FALSE(ddns_forwarder.isConnected());
}
- list(new isc::datasrc::ConfigurableClientList());
+// Check the client list accessors
+TEST_F(AuthSrvTest, clientList) {
+ // The lists don't exist. Therefore, the list of RRClasses is empty.
+ // We also have no IN list.
+ EXPECT_TRUE(server.getClientListClasses().empty());
+ EXPECT_EQ(boost::shared_ptr<const isc::datasrc::ClientList>(),
+ server.getClientList(RRClass::IN()));
+ // Put something in.
+ const boost::shared_ptr<isc::datasrc::ConfigurableClientList>
- list2(new isc::datasrc::ConfigurableClientList());
++ list(new isc::datasrc::ConfigurableClientList(RRClass::IN()));
+ const boost::shared_ptr<isc::datasrc::ConfigurableClientList>
++ list2(new isc::datasrc::ConfigurableClientList(RRClass::CH()));
+ server.setClientList(RRClass::IN(), list);
+ server.setClientList(RRClass::CH(), list2);
+ // There are two things in the list and they are IN and CH
+ vector<RRClass> classes(server.getClientListClasses());
+ ASSERT_EQ(2, classes.size());
+ EXPECT_EQ(RRClass::IN(), classes[0]);
+ EXPECT_EQ(RRClass::CH(), classes[1]);
+ // And the lists can be retrieved.
+ EXPECT_EQ(list, server.getClientList(RRClass::IN()));
+ EXPECT_EQ(list2, server.getClientList(RRClass::CH()));
+ // Remove one of them
+ server.setClientList(RRClass::CH(),
+ boost::shared_ptr<isc::datasrc::ConfigurableClientList>());
+ // This really got deleted, including the class.
+ classes = server.getClientListClasses();
+ ASSERT_EQ(1, classes.size());
+ EXPECT_EQ(RRClass::IN(), classes[0]);
+ EXPECT_EQ(list, server.getClientList(RRClass::IN()));
+}
+
}
--- /dev/null
- FakeList() :
+// Copyright (C) 2012 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 <auth/datasrc_configurator.h>
+
+#include <config/tests/fake_session.h>
+#include <config/ccsession.h>
+
+#include <gtest/gtest.h>
+#include <memory>
+#include <boost/shared_ptr.hpp>
+
+using namespace isc;
+using namespace isc::cc;
+using namespace isc::config;
+using namespace isc::data;
+using namespace isc::dns;
+using namespace std;
+using namespace boost;
+
+namespace {
+
+class DatasrcConfiguratorTest;
+
+class FakeList {
+public:
++ FakeList(const RRClass&) :
+ configuration_(new ListElement)
+ {}
+ void configure(const ConstElementPtr& configuration, bool allow_cache) {
+ EXPECT_TRUE(allow_cache);
+ conf_ = configuration->get(0)->get("type")->stringValue();
+ configuration_ = configuration;
+ }
+ const string& getConf() const {
+ return (conf_);
+ }
+ ConstElementPtr getConfiguration() const {
+ return (configuration_);
+ }
+private:
+ string conf_;
+ ConstElementPtr configuration_;
+};
+
+typedef shared_ptr<FakeList> ListPtr;
+
+// We use the test fixture as both parameters, this makes it possible
+// to easily fake all needed methods and look that they were called.
+typedef DataSourceConfiguratorGeneric<DatasrcConfiguratorTest,
+ FakeList> Configurator;
+
+class DatasrcConfiguratorTest : public ::testing::Test {
+public:
+ // These pretend to be the server
+ ListPtr getClientList(const RRClass& rrclass) {
+ log_ += "get " + rrclass.toText() + "\n";
+ return (lists_[rrclass]);
+ }
+ void setClientList(const RRClass& rrclass, const ListPtr& list) {
+ log_ += "set " + rrclass.toText() + " " +
+ (list ? list->getConf() : "") + "\n";
+ lists_[rrclass] = list;
+ }
+ vector<RRClass> getClientListClasses() const {
+ vector<RRClass> result;
+ for (map<RRClass, ListPtr>::const_iterator it(lists_.begin());
+ it != lists_.end(); ++it) {
+ result.push_back(it->first);
+ }
+ return (result);
+ }
+protected:
+ DatasrcConfiguratorTest() :
+ session(ElementPtr(new ListElement), ElementPtr(new ListElement),
+ ElementPtr(new ListElement)),
+ specfile(string(TEST_OWN_DATA_DIR) + "/spec.spec")
+ {
+ initSession();
+ }
+ void initSession() {
+ session.getMessages()->add(createAnswer());
+ mccs.reset(new ModuleCCSession(specfile, session, NULL, NULL, false,
+ false));
+ }
+ void TearDown() {
+ // Make sure no matter what we did, it is cleaned up.
+ Configurator::deinit();
+ }
+ void init(const ElementPtr& config = ElementPtr()) {
+ session.getMessages()->
+ add(createAnswer(0,
+ moduleSpecFromFile(string(PLUGIN_DATA_PATH) +
+ "/datasrc.spec").
+ getFullSpec()));
+ if (config) {
+ session.getMessages()->add(createAnswer(0, config));
+ } else {
+ session.getMessages()->
+ add(createAnswer(0, ElementPtr(new MapElement)));
+ }
+ Configurator::init(mccs.get(), this);
+ }
+ void SetUp() {
+ init();
+ }
+ void doInInit() {
+ const ElementPtr
+ config(Element::fromJSON("{\"IN\": [{\"type\": \"xxx\"}]}"));
+ session.addMessage(createCommand("config_update", config), "data_sources",
+ "*");
+ mccs->checkCommand();
+ // Check it called the correct things (check that there's no IN yet and
+ // set a new one.
+ EXPECT_EQ("get IN\nset IN xxx\n", log_);
+ }
+ FakeSession session;
+ auto_ptr<ModuleCCSession> mccs;
+ const string specfile;
+ map<RRClass, ListPtr> lists_;
+ string log_;
+};
+
+// Check the initialization (and deinitialization)
+TEST_F(DatasrcConfiguratorTest, initialization) {
+ // It can't be initialized again
+ EXPECT_THROW(init(), InvalidOperation);
+ EXPECT_TRUE(session.haveSubscription("data_sources", "*"));
+ // Deinitialize to make the tests reasonable
+ Configurator::deinit();
+ EXPECT_FALSE(session.haveSubscription("data_sources", "*"));
+ // We can't reconfigure now (not even manually)
+ EXPECT_THROW(Configurator::reconfigure(ElementPtr(new MapElement())),
+ InvalidOperation);
+ // If one of them is NULL, it does not work
+ EXPECT_THROW(Configurator::init(NULL, this), InvalidParameter);
+ EXPECT_FALSE(session.haveSubscription("data_sources", "*"));
+ EXPECT_THROW(Configurator::init(mccs.get(), NULL), InvalidParameter);
+ EXPECT_FALSE(session.haveSubscription("data_sources", "*"));
+ // But we can initialize it again now
+ EXPECT_NO_THROW(init());
+ EXPECT_TRUE(session.haveSubscription("data_sources", "*"));
+}
+
+// Push there a configuration with a single list.
+TEST_F(DatasrcConfiguratorTest, createList) {
+ doInInit();
+}
+
+TEST_F(DatasrcConfiguratorTest, modifyList) {
+ // First, initialize the list
+ doInInit();
+ // And now change the configuration of the list
+ const ElementPtr
+ config(Element::fromJSON("{\"IN\": [{\"type\": \"yyy\"}]}"));
+ session.addMessage(createCommand("config_update", config), "data_sources",
+ "*");
+ log_ = "";
+ mccs->checkCommand();
+ // This one does not set
+ EXPECT_EQ("get IN\n", log_);
+ // But this should contain the yyy configuration
+ EXPECT_EQ("yyy", lists_[RRClass::IN()]->getConf());
+}
+
+// Check we can have multiple lists at once
+TEST_F(DatasrcConfiguratorTest, multiple) {
+ const ElementPtr
+ config(Element::fromJSON("{\"IN\": [{\"type\": \"yyy\"}], "
+ "\"CH\": [{\"type\": \"xxx\"}]}"));
+ session.addMessage(createCommand("config_update", config), "data_sources",
+ "*");
+ mccs->checkCommand();
+ // This one does not set
+ EXPECT_EQ("get CH\nset CH xxx\nget IN\nset IN yyy\n", log_);
+ // We should have both there
+ EXPECT_EQ("yyy", lists_[RRClass::IN()]->getConf());
+ EXPECT_EQ("xxx", lists_[RRClass::CH()]->getConf());
+ EXPECT_EQ(2, lists_.size());
+}
+
+// Check we can add another one later and the old one does not get
+// overwritten.
+//
+// It's almost like above, but we initialize first with single-list
+// config.
+TEST_F(DatasrcConfiguratorTest, updateAdd) {
+ doInInit();
+ const ElementPtr
+ config(Element::fromJSON("{\"IN\": [{\"type\": \"yyy\"}], "
+ "\"CH\": [{\"type\": \"xxx\"}]}"));
+ session.addMessage(createCommand("config_update", config), "data_sources",
+ "*");
+ log_ = "";
+ mccs->checkCommand();
+ // This one does not set
+ EXPECT_EQ("get CH\nset CH xxx\nget IN\n", log_);
+ // But this should contain the yyy configuration
+ EXPECT_EQ("xxx", lists_[RRClass::CH()]->getConf());
+ EXPECT_EQ("yyy", lists_[RRClass::IN()]->getConf());
+ EXPECT_EQ(2, lists_.size());
+}
+
+// We delete a class list in this test.
+TEST_F(DatasrcConfiguratorTest, updateDelete) {
+ doInInit();
+ const ElementPtr
+ config(Element::fromJSON("{}"));
+ session.addMessage(createCommand("config_update", config), "data_sources",
+ "*");
+ log_ = "";
+ mccs->checkCommand();
+ EXPECT_EQ("get IN\nset IN \n", log_);
+ EXPECT_FALSE(lists_[RRClass::IN()]);
+}
+
+// Check that we can rollback an addition if something else fails
+TEST_F(DatasrcConfiguratorTest, rollbackAddition) {
+ doInInit();
+ // The configuration is wrong. However, the CH one will get done first.
+ const ElementPtr
+ config(Element::fromJSON("{\"IN\": [{\"type\": 13}], "
+ "\"CH\": [{\"type\": \"xxx\"}]}"));
+ session.addMessage(createCommand("config_update", config), "data_sources",
+ "*");
+ log_ = "";
+ // It does not throw, as it is handled in the ModuleCCSession.
+ // Throwing from the reconfigure is checked in other tests.
+ EXPECT_NO_THROW(mccs->checkCommand());
+ // Anyway, the result should not contain CH now and the original IN should
+ // be there.
+ EXPECT_EQ("xxx", lists_[RRClass::IN()]->getConf());
+ EXPECT_FALSE(lists_[RRClass::CH()]);
+}
+
+// Check that we can rollback a deletion if something else fails
+TEST_F(DatasrcConfiguratorTest, rollbackDeletion) {
+ doInInit();
+ // Put the CH there
+ const ElementPtr
+ config1(Element::fromJSON("{\"IN\": [{\"type\": \"yyy\"}], "
+ "\"CH\": [{\"type\": \"xxx\"}]}"));
+ Configurator::reconfigure(config1);
+ const ElementPtr
+ config2(Element::fromJSON("{\"IN\": [{\"type\": 13}]}"));
+ // This would delete CH. However, the IN one fails.
+ // As the deletions happen after the additions/settings
+ // and there's no known way to cause an exception during the
+ // deletions, it is not a true rollback, but the result should
+ // be the same.
+ EXPECT_THROW(Configurator::reconfigure(config2), TypeError);
+ EXPECT_EQ("yyy", lists_[RRClass::IN()]->getConf());
+ EXPECT_EQ("xxx", lists_[RRClass::CH()]->getConf());
+}
+
+// Check that we can roll back configuration change if something
+// fails later on.
+TEST_F(DatasrcConfiguratorTest, rollbackConfiguration) {
+ doInInit();
+ // Put the CH there
+ const ElementPtr
+ config1(Element::fromJSON("{\"IN\": [{\"type\": \"yyy\"}], "
+ "\"CH\": [{\"type\": \"xxx\"}]}"));
+ Configurator::reconfigure(config1);
+ // Now, the CH happens first. But nevertheless, it should be
+ // restored to the previoeus version.
+ const ElementPtr
+ config2(Element::fromJSON("{\"IN\": [{\"type\": 13}], "
+ "\"CH\": [{\"type\": \"yyy\"}]}"));
+ EXPECT_THROW(Configurator::reconfigure(config2), TypeError);
+ EXPECT_EQ("yyy", lists_[RRClass::IN()]->getConf());
+ EXPECT_EQ("xxx", lists_[RRClass::CH()]->getConf());
+}
+
+}
/// inherited except for tests.
class ConfigurableClientList : public ClientList {
public:
- ConfigurableClientList() :
+ /// \brief Constructor
+ ///
+ /// \param rrclass For which class the list should work.
+ ConfigurableClientList(const isc::dns::RRClass &rrclass) :
- rrclass_(rrclass)
++ rrclass_(rrclass),
+ configuration_(new isc::data::ListElement)
{}
/// \brief Exception thrown when there's an error in configuration.
class ConfigurationError : public Exception {
/// it might be, so it is just made public (there's no real reason to
/// hide it).
const DataSources& getDataSources() const { return (data_sources_); }
+ private:
+ const isc::dns::RRClass rrclass_;
++ /// \brief Currently active configuration.
++ isc::data::ConstElementPtr configuration_;
};
} // namespace datasrc
checkDS(0, "test_type", "{}", false);
}
- list_->configure(*elem, true);
+ TEST_F(ListTest, masterFiles) {
+ const ConstElementPtr elem(Element::fromJSON("["
+ "{"
+ " \"type\": \"MasterFiles\","
+ " \"cache-enable\": true,"
+ " \"params\": {"
+ " \".\": \"" TEST_DATA_DIR "/root.zone\""
+ " }"
+ "}]"));
- list_->configure(*elem, false);
++ list_->configure(elem, true);
+
+ // It has only the cache
+ EXPECT_EQ(NULL, list_->getDataSources()[0].data_src_client_);
+
+ // And it can search
+ positiveResult(list_->find(Name(".")), ds_[0], Name("."), true, "com",
+ true);
+
+ // If cache is not enabled, nothing is loaded
++ list_->configure(elem, false);
+ EXPECT_EQ(0, list_->getDataSources().size());
+ }
+
}