// 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() : 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()); } }