]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
Merge #2046
authorMichal 'vorner' Vaner <michal.vaner@nic.cz>
Sat, 30 Jun 2012 19:52:35 +0000 (21:52 +0200)
committerMichal 'vorner' Vaner <michal.vaner@nic.cz>
Sat, 30 Jun 2012 19:52:35 +0000 (21:52 +0200)
With integration into the rest.

Conflicts:
src/lib/datasrc/client_list.h

1  2 
src/bin/auth/datasrc_configurator.h
src/bin/auth/tests/auth_srv_unittest.cc
src/bin/auth/tests/datasrc_configurator_unittest.cc
src/lib/datasrc/client_list.cc
src/lib/datasrc/client_list.h
src/lib/datasrc/tests/client_list_unittest.cc

index 7eaf50a89402712c863af48050e0272265c4e8df,0000000000000000000000000000000000000000..6197d340ac88b3bfbbe40daadfceba16dbabc052
mode 100644,000000..100644
--- /dev/null
@@@ -1,221 -1,0 +1,221 @@@
-                     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
index 6f16023f8bbd91fc8245e5d5b6568fe3bfb35150,0a41f5d57482b8078fe731e687b5688f32a8222d..f00adfc47267718c1a52ebf0a84a41bce6db978a
@@@ -1678,36 -1639,4 +1678,36 @@@ TEST_F(AuthSrvTest, DDNSForwardClose) 
      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()));
 +}
 +
  }
index 9f92638a24553f7e90de599973de50f1ea2270ef,0000000000000000000000000000000000000000..c7b90a73a3214a8b0de04baafdbacb50ce147a5e
mode 100644,000000..100644
--- /dev/null
@@@ -1,286 -1,0 +1,286 @@@
-     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());
 +}
 +
 +}
Simple merge
index 5c883353d1a7b53c3b7a3f4c3463f089dfe8d714,75dae74ac6afd38996572a0a6b3fcc17afb24f98..d196f39427bc7fcfe764a0861e4c254f8290b41c
@@@ -186,8 -187,11 +187,12 @@@ typedef boost::shared_ptr<const ClientL
  /// 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 {
@@@ -302,6 -288,8 +299,10 @@@ public
      /// 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
index b0f319cb40e7f8ce19ed71c4c07c99be7c2f2960,8625d33bee4e8bdfe99681d15e93c025f2d34793..84a1fe8e94e5e0291cbca206fa1809087a42669c
@@@ -674,4 -723,27 +727,27 @@@ TEST_F(ListTest, badCache) 
      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());
+ }
  }