From: Michal 'vorner' Vaner Date: Sat, 30 Jun 2012 19:52:35 +0000 (+0200) Subject: Merge #2046 X-Git-Tag: trac2351_base~97^2~7^2~2^2~21^2~2^2~8 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=5fba01f28ca9b4a07417b40c0fc016eed2cdc7fa;p=thirdparty%2Fkea.git Merge #2046 With integration into the rest. Conflicts: src/lib/datasrc/client_list.h --- 5fba01f28ca9b4a07417b40c0fc016eed2cdc7fa diff --cc src/bin/auth/datasrc_configurator.h index 7eaf50a894,0000000000..6197d340ac mode 100644,000000..100644 --- a/src/bin/auth/datasrc_configurator.h +++ b/src/bin/auth/datasrc_configurator.h @@@ -1,221 -1,0 +1,221 @@@ +// 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 +#include +#include + +#include + +/// \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 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 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 Map; + typedef std::pair RollbackPair; + typedef std::pair + RollbackConfiguration; + // Some structures to be able to perform a rollback + std::vector rollback_sets; + std::vector rollback_configurations; + try { + // Get the configuration and current state. + const Map& map(config->mapValue()); + const std::vector + activeVector(server_->getClientListClasses()); + std::set 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); ++ 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::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::const_iterator + it(rollback_sets.begin()); it != rollback_sets.end(); ++it) { + server_->setClientList(it->first, it->second); + } + for (typename std::vector::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 +isc::config::ModuleCCSession* +DataSourceConfiguratorGeneric::session_(NULL); + +template +Server* DataSourceConfiguratorGeneric::server_(NULL); + +/// \brief Concrete version of DataSourceConfiguratorGeneric for the +/// use in authoritative server. +typedef DataSourceConfiguratorGeneric + DataSourceConfigurator; + +#endif diff --cc src/bin/auth/tests/auth_srv_unittest.cc index 6f16023f8b,0a41f5d574..f00adfc472 --- a/src/bin/auth/tests/auth_srv_unittest.cc +++ b/src/bin/auth/tests/auth_srv_unittest.cc @@@ -1678,36 -1639,4 +1678,36 @@@ TEST_F(AuthSrvTest, DDNSForwardClose) EXPECT_FALSE(ddns_forwarder.isConnected()); } +// 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(), + server.getClientList(RRClass::IN())); + // Put something in. + const boost::shared_ptr - list(new isc::datasrc::ConfigurableClientList()); ++ list(new isc::datasrc::ConfigurableClientList(RRClass::IN())); + const boost::shared_ptr - list2(new 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 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()); + // 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())); +} + } diff --cc src/bin/auth/tests/datasrc_configurator_unittest.cc index 9f92638a24,0000000000..c7b90a73a3 mode 100644,000000..100644 --- a/src/bin/auth/tests/datasrc_configurator_unittest.cc +++ b/src/bin/auth/tests/datasrc_configurator_unittest.cc @@@ -1,286 -1,0 +1,286 @@@ +// 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 + +#include +#include + +#include +#include +#include + +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() : ++ 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 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 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 getClientListClasses() const { + vector result; + for (map::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 mccs; + const string specfile; + map 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()); +} + +} diff --cc src/lib/datasrc/client_list.h index 5c883353d1,75dae74ac6..d196f39427 --- a/src/lib/datasrc/client_list.h +++ b/src/lib/datasrc/client_list.h @@@ -186,8 -187,11 +187,12 @@@ typedef boost::shared_ptrconfigure(*elem, true); ++ 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); ++ list_->configure(elem, false); + EXPECT_EQ(0, list_->getDataSources().size()); + } + }