Parcourir la source

Merge #2862

Auth now recognizes the mapped segments and maps them.
Michal 'vorner' Vaner il y a 11 ans
Parent
commit
ed713618c1

+ 4 - 2
doc/design/datasrc/data-source-classes.txt

@@ -236,7 +236,8 @@ image::auth-mapped.png[Sequence diagram for auth server using mapped memory segm
    argument and the segment mode of `READ_ONLY`.
    argument and the segment mode of `READ_ONLY`.
    Note that the auth module handles the command argument as mostly
    Note that the auth module handles the command argument as mostly
    opaque data; it's not expected to deal with details of segment
    opaque data; it's not expected to deal with details of segment
-   type-specific behavior.
+   type-specific behavior. If the reset fails, auth aborts (as there's
+   no clear way to handle the failure).
 
 
 6. `ConfigurableClientList::resetMemorySegment()` subsequently calls
 6. `ConfigurableClientList::resetMemorySegment()` subsequently calls
    `reset()` method on the corresponding `ZoneTableSegment` with the
    `reset()` method on the corresponding `ZoneTableSegment` with the
@@ -254,7 +255,8 @@ image::auth-mapped.png[Sequence diagram for auth server using mapped memory segm
    underlying memory segment is swapped with a new one.  The old
    underlying memory segment is swapped with a new one.  The old
    memory segment object is destroyed.  Note that
    memory segment object is destroyed.  Note that
    this "destroy" just means unmapping the memory region; the data
    this "destroy" just means unmapping the memory region; the data
-   stored in the file are intact.
+   stored in the file are intact. Again, if mapping fails, auth
+   aborts.
 
 
 8. If the auth module happens to receive a reload command from other
 8. If the auth module happens to receive a reload command from other
    module, it could call
    module, it could call

+ 24 - 0
src/bin/auth/auth_messages.mes

@@ -145,6 +145,30 @@ reconfigure, and has now started this process.
 The thread for maintaining data source clients has finished reconfiguring
 The thread for maintaining data source clients has finished reconfiguring
 the data source clients, and is now running with the new configuration.
 the data source clients, and is now running with the new configuration.
 
 
+% AUTH_DATASRC_CLIENTS_BUILDER_SEGMENT_BAD_CLASS invalid RRclass %1 at segment update
+A memory segment update message was sent to the authoritative
+server. But the class contained there is invalid. This means that the
+system is in an inconsistent state and the authoritative server aborts
+to minimize the problem. This is likely caused by a bug in the code.
+
+% AUTH_DATASRC_CLIENTS_BUILDER_SEGMENT_ERROR error updating the memory segment: %1
+The authoritative server tried to update the memory segment, but the update
+failed. The authoritative server aborts to avoid system inconsistency. This is
+likely caused by a bug in the code.
+
+% AUTH_DATASRC_CLIENTS_BUILDER_SEGMENT_NO_DATASRC there's no data source named %2 in class %1
+The authoritative server was asked to update the memory segment of the
+given data source, but no data source by that name was found. The
+authoritative server aborts because this indicates that the system is in
+an inconsistent state. This is likely caused by a bug in the code.
+
+% AUTH_DATASRC_CLIENTS_BUILDER_SEGMENT_UNKNOWN_CLASS unknown class %1 at segment update
+A memory segment update message was sent to the authoritative
+server. The class name for which the update should happen is valid, but
+no client lists are configured for that class. The system is in an
+inconsistent state and the authoritative server aborts. This may be
+caused by a bug in the code.
+
 % AUTH_DATASRC_CLIENTS_BUILDER_STARTED data source builder thread started
 % AUTH_DATASRC_CLIENTS_BUILDER_STARTED data source builder thread started
 A separate thread for maintaining data source clients has been started.
 A separate thread for maintaining data source clients has been started.
 
 

+ 64 - 0
src/bin/auth/auth_srv.cc

@@ -306,6 +306,8 @@ public:
                       MessageAttributes& stats_attrs,
                       MessageAttributes& stats_attrs,
                       const bool done);
                       const bool done);
 
 
+    /// Are we currently subscribed to the SegmentReader group?
+    bool readers_group_subscribed_;
 private:
 private:
     bool xfrout_connected_;
     bool xfrout_connected_;
     AbstractXfroutClient& xfrout_client_;
     AbstractXfroutClient& xfrout_client_;
@@ -322,6 +324,7 @@ AuthSrvImpl::AuthSrvImpl(AbstractXfroutClient& xfrout_client,
     datasrc_clients_mgr_(io_service_),
     datasrc_clients_mgr_(io_service_),
     ddns_base_forwarder_(ddns_forwarder),
     ddns_base_forwarder_(ddns_forwarder),
     ddns_forwarder_(NULL),
     ddns_forwarder_(NULL),
+    readers_group_subscribed_(false),
     xfrout_connected_(false),
     xfrout_connected_(false),
     xfrout_client_(xfrout_client)
     xfrout_client_(xfrout_client)
 {}
 {}
@@ -939,3 +942,64 @@ void
 AuthSrv::setTCPRecvTimeout(size_t timeout) {
 AuthSrv::setTCPRecvTimeout(size_t timeout) {
     dnss_->setTCPRecvTimeout(timeout);
     dnss_->setTCPRecvTimeout(timeout);
 }
 }
+
+namespace {
+
+bool
+hasMappedSegment(auth::DataSrcClientsMgr& mgr) {
+    auth::DataSrcClientsMgr::Holder holder(mgr);
+    const std::vector<dns::RRClass>& classes(holder.getClasses());
+    BOOST_FOREACH(const dns::RRClass& rrclass, classes) {
+        const boost::shared_ptr<datasrc::ConfigurableClientList>&
+            list(holder.findClientList(rrclass));
+        const std::vector<DataSourceStatus>& states(list->getStatus());
+        BOOST_FOREACH(const datasrc::DataSourceStatus& status, states) {
+            if (status.getSegmentState() != datasrc::SEGMENT_UNUSED &&
+                status.getSegmentType() == "mapped")
+                // We use some segment and it's not a local one, so it
+                // must be remote.
+                return true;
+        }
+    }
+    // No remote segment found in any of the lists
+    return false;
+}
+
+}
+
+void
+AuthSrv::listsReconfigured() {
+    const bool has_remote = hasMappedSegment(impl_->datasrc_clients_mgr_);
+    if (has_remote && !impl_->readers_group_subscribed_) {
+        impl_->config_session_->subscribe("SegmentReader");
+        impl_->config_session_->
+            setUnhandledCallback(boost::bind(&AuthSrv::foreignCommand, this,
+                                             _1, _2, _3));
+        impl_->readers_group_subscribed_ = true;
+    } else if (!has_remote && impl_->readers_group_subscribed_) {
+        impl_->config_session_->unsubscribe("SegmentReader");
+        impl_->config_session_->
+            setUnhandledCallback(isc::config::ModuleCCSession::
+                                 UnhandledCallback());
+        impl_->readers_group_subscribed_ = false;
+    }
+}
+
+void
+AuthSrv::reconfigureDone(ConstElementPtr params) {
+    // ACK the segment
+    impl_->config_session_->
+        groupSendMsg(isc::config::createCommand("segment_info_update_ack",
+                                                params), "MemMgr");
+}
+
+void
+AuthSrv::foreignCommand(const std::string& command, const std::string&,
+                        const ConstElementPtr& params)
+{
+    if (command == "segment_info_update") {
+        impl_->datasrc_clients_mgr_.
+            segmentInfoUpdate(params, boost::bind(&AuthSrv::reconfigureDone,
+                                                  this, params));
+    }
+}

+ 11 - 0
src/bin/auth/auth_srv.h

@@ -273,7 +273,18 @@ public:
     /// open forever.
     /// open forever.
     void setTCPRecvTimeout(size_t timeout);
     void setTCPRecvTimeout(size_t timeout);
 
 
+    /// \brief Notify the authoritative server that the client lists were
+    ///     reconfigured.
+    ///
+    /// This is to be called when the work thread finishes reconfiguration
+    /// of the data sources. It involeves some book keeping and asking the
+    /// memory manager for segments, if some are remotely mapped.
+    void listsReconfigured();
+
 private:
 private:
+    void reconfigureDone(isc::data::ConstElementPtr request);
+    void foreignCommand(const std::string& command, const std::string&,
+                        const isc::data::ConstElementPtr& params);
     AuthSrvImpl* impl_;
     AuthSrvImpl* impl_;
     isc::asiolink::SimpleCallback* checkin_;
     isc::asiolink::SimpleCallback* checkin_;
     isc::asiodns::DNSLookup* dns_lookup_;
     isc::asiodns::DNSLookup* dns_lookup_;

+ 91 - 1
src/bin/auth/datasrc_clients_mgr.h

@@ -81,6 +81,7 @@ enum CommandID {
     LOADZONE,     ///< Load a new version of zone into a memory,
     LOADZONE,     ///< Load a new version of zone into a memory,
                   ///  the argument to the command is a map containing 'class'
                   ///  the argument to the command is a map containing 'class'
                   ///  and 'origin' elements, both should have been validated.
                   ///  and 'origin' elements, both should have been validated.
+    SEGMENT_INFO_UPDATE, ///< The memory manager sent an update about segments.
     SHUTDOWN,     ///< Shutdown the builder; no argument
     SHUTDOWN,     ///< Shutdown the builder; no argument
     NUM_COMMANDS
     NUM_COMMANDS
 };
 };
@@ -212,6 +213,24 @@ public:
                 return (it->second);
                 return (it->second);
             }
             }
         }
         }
+        /// \brief Return list of classes that are present.
+        ///
+        /// Get the list of classes for which there's a client list. It is
+        /// returned in form of a vector, copied from the internals. As the
+        /// number of classes in there is expected to be small, it is not
+        /// a performance issue.
+        ///
+        /// \return The list of classes.
+        /// \throw std::bad_alloc for problems allocating the result.
+        std::vector<dns::RRClass> getClasses() const {
+            std::vector<dns::RRClass> result;
+            for (ClientListsMap::const_iterator it =
+                 mgr_.clients_map_->begin(); it != mgr_.clients_map_->end();
+                 ++it) {
+                result.push_back(it->first);
+            }
+            return (result);
+        }
     private:
     private:
         DataSrcClientsMgrBase& mgr_;
         DataSrcClientsMgrBase& mgr_;
         typename MutexType::Locker locker_;
         typename MutexType::Locker locker_;
@@ -381,6 +400,36 @@ public:
         sendCommand(datasrc_clientmgr_internal::LOADZONE, args, callback);
         sendCommand(datasrc_clientmgr_internal::LOADZONE, args, callback);
     }
     }
 
 
+    void segmentInfoUpdate(const data::ConstElementPtr& args,
+                           const datasrc_clientmgr_internal::FinishedCallback&
+                           callback =
+                           datasrc_clientmgr_internal::FinishedCallback()) {
+        // Some minimal validation
+        if (!args) {
+            isc_throw(CommandError, "segmentInfoUpdate argument empty");
+        }
+        if (args->getType() != isc::data::Element::map) {
+            isc_throw(CommandError, "segmentInfoUpdate argument not a map");
+        }
+        const char* params[] = {
+            "data-source-name",
+            "data-source-class",
+            "segment-params",
+            NULL
+        };
+        for (const char** param = params; *param; ++param) {
+            if (!args->contains(*param)) {
+                isc_throw(CommandError,
+                          "segmentInfoUpdate argument has no '" << param <<
+                          "' value");
+            }
+        }
+
+
+        sendCommand(datasrc_clientmgr_internal::SEGMENT_INFO_UPDATE, args,
+                    callback);
+    }
+
 private:
 private:
     // This is expected to be called at the end of the destructor.  It
     // This is expected to be called at the end of the destructor.  It
     // actually does nothing, but provides a customization point for
     // actually does nothing, but provides a customization point for
@@ -577,6 +626,44 @@ private:
         }
         }
     }
     }
 
 
+    void doSegmentUpdate(const isc::data::ConstElementPtr& arg) {
+        try {
+            const isc::dns::RRClass
+                rrclass(arg->get("data-source-class")->stringValue());
+            const std::string&
+                name(arg->get("data-source-name")->stringValue());
+            const isc::data::ConstElementPtr& segment_params =
+                arg->get("segment-params");
+            typename MutexType::Locker locker(*map_mutex_);
+            const boost::shared_ptr<isc::datasrc::ConfigurableClientList>&
+                list = (**clients_map_)[rrclass];
+            if (!list) {
+                LOG_FATAL(auth_logger,
+                          AUTH_DATASRC_CLIENTS_BUILDER_SEGMENT_UNKNOWN_CLASS)
+                    .arg(rrclass);
+                std::terminate();
+            }
+            if (!list->resetMemorySegment(name,
+                    isc::datasrc::memory::ZoneTableSegment::READ_ONLY,
+                    segment_params)) {
+                LOG_FATAL(auth_logger,
+                          AUTH_DATASRC_CLIENTS_BUILDER_SEGMENT_NO_DATASRC)
+                    .arg(rrclass).arg(name);
+                std::terminate();
+            }
+        } catch (const isc::dns::InvalidRRClass& irce) {
+            LOG_FATAL(auth_logger,
+                      AUTH_DATASRC_CLIENTS_BUILDER_SEGMENT_BAD_CLASS)
+                .arg(arg->get("data-source-class"));
+            std::terminate();
+        } catch (const isc::Exception& e) {
+            LOG_FATAL(auth_logger,
+                      AUTH_DATASRC_CLIENTS_BUILDER_SEGMENT_ERROR)
+                .arg(e.what());
+            std::terminate();
+        }
+    }
+
     void doLoadZone(const isc::data::ConstElementPtr& arg);
     void doLoadZone(const isc::data::ConstElementPtr& arg);
     boost::shared_ptr<datasrc::memory::ZoneWriter> getZoneWriter(
     boost::shared_ptr<datasrc::memory::ZoneWriter> getZoneWriter(
         datasrc::ConfigurableClientList& client_list,
         datasrc::ConfigurableClientList& client_list,
@@ -676,7 +763,7 @@ DataSrcClientsBuilderBase<MutexType, CondVarType>::handleCommand(
     }
     }
 
 
     const boost::array<const char*, NUM_COMMANDS> command_desc = {
     const boost::array<const char*, NUM_COMMANDS> command_desc = {
-        {"NOOP", "RECONFIGURE", "LOADZONE", "SHUTDOWN"}
+        {"NOOP", "RECONFIGURE", "LOADZONE", "SEGMENT_INFO_UPDATE", "SHUTDOWN"}
     };
     };
     LOG_DEBUG(auth_logger, DBGLVL_TRACE_BASIC,
     LOG_DEBUG(auth_logger, DBGLVL_TRACE_BASIC,
               AUTH_DATASRC_CLIENTS_BUILDER_COMMAND).arg(command_desc.at(cid));
               AUTH_DATASRC_CLIENTS_BUILDER_COMMAND).arg(command_desc.at(cid));
@@ -687,6 +774,9 @@ DataSrcClientsBuilderBase<MutexType, CondVarType>::handleCommand(
     case LOADZONE:
     case LOADZONE:
         doLoadZone(command.params);
         doLoadZone(command.params);
         break;
         break;
+    case SEGMENT_INFO_UPDATE:
+        doSegmentUpdate(command.params);
+        break;
     case SHUTDOWN:
     case SHUTDOWN:
         return (false);
         return (false);
     case NOOP:
     case NOOP:

+ 4 - 2
src/bin/auth/main.cc

@@ -109,9 +109,11 @@ datasrcConfigHandler(AuthSrv* server, bool* first_time,
         assert(config_session != NULL);
         assert(config_session != NULL);
         *first_time = false;
         *first_time = false;
         server->getDataSrcClientsMgr().reconfigure(
         server->getDataSrcClientsMgr().reconfigure(
-            config_session->getRemoteConfigValue("data_sources", "classes"));
+            config_session->getRemoteConfigValue("data_sources", "classes"),
+            boost::bind(&AuthSrv::listsReconfigured, server));
     } else if (config->contains("classes")) {
     } else if (config->contains("classes")) {
-        server->getDataSrcClientsMgr().reconfigure(config->get("classes"));
+        server->getDataSrcClientsMgr().reconfigure(config->get("classes"),
+            boost::bind(&AuthSrv::listsReconfigured, server));
     }
     }
 }
 }
 
 

+ 44 - 2
src/bin/auth/tests/auth_srv_unittest.cc

@@ -39,6 +39,9 @@
 #include <auth/statistics_items.h>
 #include <auth/statistics_items.h>
 #include <auth/datasrc_config.h>
 #include <auth/datasrc_config.h>
 
 
+#include <config/tests/fake_session.h>
+#include <config/ccsession.h>
+
 #include <util/unittests/mock_socketsession.h>
 #include <util/unittests/mock_socketsession.h>
 #include <dns/tests/unittest_util.h>
 #include <dns/tests/unittest_util.h>
 #include <testutils/dnsmessage_test.h>
 #include <testutils/dnsmessage_test.h>
@@ -265,14 +268,15 @@ updateDatabase(AuthSrv& server, const char* params) {
 
 
 void
 void
 updateInMemory(AuthSrv& server, const char* origin, const char* filename,
 updateInMemory(AuthSrv& server, const char* origin, const char* filename,
-               bool with_static = true)
+               bool with_static = true, bool mapped = false)
 {
 {
     string spec_txt = "{"
     string spec_txt = "{"
         "\"IN\": [{"
         "\"IN\": [{"
         "   \"type\": \"MasterFiles\","
         "   \"type\": \"MasterFiles\","
         "   \"params\": {"
         "   \"params\": {"
         "       \"" + string(origin) + "\": \"" + string(filename) + "\""
         "       \"" + string(origin) + "\": \"" + string(filename) + "\""
-        "   },"
+        "   }," +
+        string(mapped ? "\"cache-type\": \"mapped\"," : "") +
         "   \"cache-enable\": true"
         "   \"cache-enable\": true"
         "}]";
         "}]";
     if (with_static) {
     if (with_static) {
@@ -2138,4 +2142,42 @@ TEST_F(AuthSrvTest, loadZoneCommand) {
     sendCommand(server, "loadzone", args, 0);
     sendCommand(server, "loadzone", args, 0);
 }
 }
 
 
+// Test that the auth server subscribes to the segment readers group when
+// there's a remotely mapped segment.
+TEST_F(AuthSrvTest, postReconfigure) {
+    FakeSession session(ElementPtr(new ListElement),
+                        ElementPtr(new ListElement),
+                        ElementPtr(new ListElement));
+    const string specfile(string(TEST_OWN_DATA_DIR) + "/spec.spec");
+    session.getMessages()->add(isc::config::createAnswer());
+    isc::config::ModuleCCSession mccs(specfile, session, NULL, NULL, false,
+                                      false);
+    server.setConfigSession(&mccs);
+    // First, no lists are there, so no reason to subscribe
+    server.listsReconfigured();
+    EXPECT_FALSE(session.haveSubscription("SegmentReader", "*"));
+    // Enable remote segment
+    updateInMemory(server, "example.", CONFIG_INMEMORY_EXAMPLE, false, true);
+    {
+        DataSrcClientsMgr &mgr(server.getDataSrcClientsMgr());
+        DataSrcClientsMgr::Holder holder(mgr);
+        EXPECT_EQ(SEGMENT_WAITING, holder.findClientList(RRClass::IN())->
+                  getStatus()[0].getSegmentState());
+    }
+    server.listsReconfigured();
+    EXPECT_TRUE(session.haveSubscription("SegmentReader", "*"));
+    // Set the segment to local again
+    updateInMemory(server, "example.", CONFIG_INMEMORY_EXAMPLE);
+    {
+        DataSrcClientsMgr &mgr(server.getDataSrcClientsMgr());
+        DataSrcClientsMgr::Holder holder(mgr);
+        EXPECT_EQ(SEGMENT_INUSE, holder.findClientList(RRClass::IN())->
+                  getStatus()[0].getSegmentState());
+        EXPECT_EQ("local", holder.findClientList(RRClass::IN())->
+                  getStatus()[0].getSegmentType());
+    }
+    server.listsReconfigured();
+    EXPECT_FALSE(session.haveSubscription("SegmentReader", "*"));
+}
+
 }
 }

+ 102 - 0
src/bin/auth/tests/datasrc_clients_builder_unittest.cc

@@ -648,4 +648,106 @@ TEST_F(DataSrcClientsBuilderTest,
                  TestDataSrcClientsBuilder::InternalCommandError);
                  TestDataSrcClientsBuilder::InternalCommandError);
 }
 }
 
 
+// Test the SEGMENT_INFO_UPDATE command. This test is little bit
+// indirect. It doesn't seem possible to fake the client list inside
+// easily. So we create a real image to load and load it. Then we check
+// the segment is used.
+TEST_F(DataSrcClientsBuilderTest,
+#ifdef USE_SHARED_MEMORY
+       segmentInfoUpdate
+#else
+       DISABLED_segmentInfoUpdate
+#endif
+      )
+{
+    // First, prepare the file image to be mapped
+    const ConstElementPtr config = Element::fromJSON(
+        "{"
+        "\"IN\": [{"
+        "   \"type\": \"MasterFiles\","
+        "   \"params\": {"
+        "       \"test1.example\": \""
+        TEST_DATA_BUILDDIR "/test1.zone.copied\"},"
+        "   \"cache-enable\": true,"
+        "   \"cache-type\": \"mapped\""
+        "}]}");
+    const ConstElementPtr segment_config = Element::fromJSON(
+        "{"
+        "  \"mapped-file\": \""
+        TEST_DATA_BUILDDIR "/test1.zone.image" "\"}");
+    clients_map = configureDataSource(config);
+    {
+        const boost::shared_ptr<ConfigurableClientList> list =
+            (*clients_map)[RRClass::IN()];
+        list->resetMemorySegment("MasterFiles",
+                                 memory::ZoneTableSegment::CREATE,
+                                 segment_config);
+        const ConfigurableClientList::ZoneWriterPair result =
+            list->getCachedZoneWriter(isc::dns::Name("test1.example"), false,
+                                      "MasterFiles");
+        ASSERT_EQ(ConfigurableClientList::ZONE_SUCCESS, result.first);
+        result.second->load();
+        result.second->install();
+        // not absolutely necessary, but just in case
+        result.second->cleanup();
+    } // Release this list. That will release the file with the image too,
+      // so we can map it read only from somewhere else.
+
+    // Create a new map, with the same configuration, but without the segments
+    // set
+    clients_map = configureDataSource(config);
+    const boost::shared_ptr<ConfigurableClientList> list =
+        (*clients_map)[RRClass::IN()];
+    EXPECT_EQ(SEGMENT_WAITING, list->getStatus()[0].getSegmentState());
+    // Send the command
+    const ElementPtr command_args = Element::fromJSON(
+        "{"
+        "  \"data-source-name\": \"MasterFiles\","
+        "  \"data-source-class\": \"IN\""
+        "}");
+    command_args->set("segment-params", segment_config);
+    builder.handleCommand(Command(SEGMENT_INFO_UPDATE, command_args,
+                                  FinishedCallback()));
+    // The segment is now used.
+    EXPECT_EQ(SEGMENT_INUSE, list->getStatus()[0].getSegmentState());
+
+    // Some invalid inputs (wrong class, different name of data source, etc).
+
+    // Copy the confing and modify
+    const ElementPtr bad_name = Element::fromJSON(command_args->toWire());
+    // Set bad name
+    bad_name->set("data-source-name", Element::create("bad"));
+    EXPECT_DEATH_IF_SUPPORTED({
+        builder.handleCommand(Command(SEGMENT_INFO_UPDATE, bad_name,
+                                      FinishedCallback()));
+    }, "");
+
+    // Another copy with wrong class
+    const ElementPtr bad_class = Element::fromJSON(command_args->toWire());
+    // Set bad class
+    bad_class->set("data-source-class", Element::create("bad"));
+    // Aborts (we are out of sync somehow).
+    EXPECT_DEATH_IF_SUPPORTED({
+        builder.handleCommand(Command(SEGMENT_INFO_UPDATE, bad_class,
+                                      FinishedCallback()));
+    }, "");
+
+    // Class CH is valid, but not present.
+    bad_class->set("data-source-class", Element::create("CH"));
+    EXPECT_DEATH_IF_SUPPORTED({
+        builder.handleCommand(Command(SEGMENT_INFO_UPDATE, bad_class,
+                                      FinishedCallback()));
+    }, "");
+
+    // And break the segment params
+    const ElementPtr bad_params = Element::fromJSON(command_args->toWire());
+    bad_params->set("segment-params",
+                    Element::fromJSON("{\"mapped-file\": \"/bad/file\"}"));
+
+    EXPECT_DEATH_IF_SUPPORTED({
+        builder.handleCommand(Command(SEGMENT_INFO_UPDATE, bad_class,
+                                      FinishedCallback()));
+    }, "");
+}
+
 } // unnamed namespace
 } // unnamed namespace

+ 27 - 0
src/bin/auth/tests/datasrc_clients_mgr_unittest.cc

@@ -156,6 +156,7 @@ TEST(DataSrcClientsMgrTest, holder) {
         TestDataSrcClientsMgr::Holder holder(mgr);
         TestDataSrcClientsMgr::Holder holder(mgr);
         EXPECT_FALSE(holder.findClientList(RRClass::IN()));
         EXPECT_FALSE(holder.findClientList(RRClass::IN()));
         EXPECT_FALSE(holder.findClientList(RRClass::CH()));
         EXPECT_FALSE(holder.findClientList(RRClass::CH()));
+        EXPECT_TRUE(holder.getClasses().empty());
         // map should be protected here
         // map should be protected here
         EXPECT_EQ(1, FakeDataSrcClientsBuilder::map_mutex->lock_count);
         EXPECT_EQ(1, FakeDataSrcClientsBuilder::map_mutex->lock_count);
         EXPECT_EQ(0, FakeDataSrcClientsBuilder::map_mutex->unlock_count);
         EXPECT_EQ(0, FakeDataSrcClientsBuilder::map_mutex->unlock_count);
@@ -174,6 +175,7 @@ TEST(DataSrcClientsMgrTest, holder) {
         TestDataSrcClientsMgr::Holder holder(mgr);
         TestDataSrcClientsMgr::Holder holder(mgr);
         EXPECT_TRUE(holder.findClientList(RRClass::IN()));
         EXPECT_TRUE(holder.findClientList(RRClass::IN()));
         EXPECT_TRUE(holder.findClientList(RRClass::CH()));
         EXPECT_TRUE(holder.findClientList(RRClass::CH()));
+        EXPECT_EQ(2, holder.getClasses().size());
     }
     }
     // We need to clear command queue by hand
     // We need to clear command queue by hand
     FakeDataSrcClientsBuilder::command_queue->clear();
     FakeDataSrcClientsBuilder::command_queue->clear();
@@ -188,6 +190,7 @@ TEST(DataSrcClientsMgrTest, holder) {
         TestDataSrcClientsMgr::Holder holder(mgr);
         TestDataSrcClientsMgr::Holder holder(mgr);
         EXPECT_TRUE(holder.findClientList(RRClass::IN()));
         EXPECT_TRUE(holder.findClientList(RRClass::IN()));
         EXPECT_FALSE(holder.findClientList(RRClass::CH()));
         EXPECT_FALSE(holder.findClientList(RRClass::CH()));
+        EXPECT_EQ(RRClass::IN(), holder.getClasses()[0]);
     }
     }
 
 
     // Duplicate lock acquisition is prohibited (only test mgr can detect
     // Duplicate lock acquisition is prohibited (only test mgr can detect
@@ -245,6 +248,30 @@ TEST(DataSrcClientsMgrTest, reload) {
     EXPECT_EQ(3, FakeDataSrcClientsBuilder::command_queue->size());
     EXPECT_EQ(3, FakeDataSrcClientsBuilder::command_queue->size());
 }
 }
 
 
+TEST(DataSrcClientsMgrTest, segmentUpdate) {
+    TestDataSrcClientsMgr mgr;
+    EXPECT_TRUE(FakeDataSrcClientsBuilder::started);
+    EXPECT_TRUE(FakeDataSrcClientsBuilder::command_queue->empty());
+
+    isc::data::ElementPtr args =
+        isc::data::Element::fromJSON("{\"data-source-class\": \"IN\","
+                                     " \"data-source-name\": \"sqlite3\","
+                                     " \"segment-params\": {}}");
+    mgr.segmentInfoUpdate(args);
+    EXPECT_EQ(1, FakeDataSrcClientsBuilder::command_queue->size());
+    // Some invalid inputs
+    EXPECT_THROW(mgr.segmentInfoUpdate(isc::data::Element::fromJSON(
+        "{\"data-source-class\": \"IN\","
+        " \"data-source-name\": \"sqlite3\"}")), CommandError);
+    EXPECT_THROW(mgr.segmentInfoUpdate(isc::data::Element::fromJSON(
+        "{\"data-source-name\": \"sqlite3\","
+        " \"segment-params\": {}}")), CommandError);
+    EXPECT_THROW(mgr.segmentInfoUpdate(isc::data::Element::fromJSON(
+        "{\"data-source-class\": \"IN\","
+        " \"segment-params\": {}}")), CommandError);
+    EXPECT_EQ(1, FakeDataSrcClientsBuilder::command_queue->size());
+}
+
 void
 void
 callback(bool* called, int *tag_target, int tag_value) {
 callback(bool* called, int *tag_target, int tag_value) {
     *called = true;
     *called = true;

+ 2 - 0
src/lib/config/ccsession.cc

@@ -595,6 +595,8 @@ ModuleCCSession::checkModuleCommand(const std::string& cmd_str,
                                  "Command given but no "
                                  "Command given but no "
                                  "command handler for module"));
                                  "command handler for module"));
         }
         }
+    } else if (unhandled_callback_) {
+        unhandled_callback_(cmd_str, target_module, arg);
     }
     }
     return (ElementPtr());
     return (ElementPtr());
 }
 }

+ 46 - 0
src/lib/config/ccsession.h

@@ -575,6 +575,50 @@ public:
     /// \param id The id of request as returned by groupRecvMsgAsync.
     /// \param id The id of request as returned by groupRecvMsgAsync.
     void cancelAsyncRecv(const AsyncRecvRequestID& id);
     void cancelAsyncRecv(const AsyncRecvRequestID& id);
 
 
+    /// \brief Subscribe to a group
+    ///
+    /// Wrapper around the CCSession::subscribe.
+    void subscribe(const std::string& group) {
+        session_.subscribe(group, isc::cc::CC_INSTANCE_WILDCARD);
+    }
+
+    /// \brief Unsubscribe from a group.
+    ///
+    /// Wrapper around the CCSession::unsubscribe.
+    void unsubscribe(const std::string& group) {
+        session_.unsubscribe(group, isc::cc::CC_INSTANCE_WILDCARD);
+    }
+
+    /// \brief Callback type for unhandled commands
+    ///
+    /// The type of functions that are not handled by the ModuleCCSession
+    /// because they are not aimed at the module.
+    ///
+    /// The parameters are:
+    /// - Name of the command.
+    /// - The module it was aimed for (may be empty).
+    /// - The parameters of the command.
+    typedef boost::function<void (const std::string&, const std::string&,
+                                  const isc::data::ConstElementPtr&)>
+        UnhandledCallback;
+
+    /// \brief Register a callback for messages sent to foreign modules.
+    ///
+    /// Usually, a command aimed at foreign module (or sent directly)
+    /// is discarded. By registering a callback here, these can be
+    /// examined.
+    ///
+    /// \note A callback overwrites the previous one set.
+    /// \todo This is a temporary, unclean, solution. A more generic
+    ///     one needs to be designed. Also, a solution that is able
+    ///     to send an answer would be great.
+    ///
+    /// \param callback The new callback to use. It may be an empty
+    ///     function.
+    void setUnhandledCallback(const UnhandledCallback& callback) {
+        unhandled_callback_ = callback;
+    }
+
 private:
 private:
     ModuleSpec readModuleSpecification(const std::string& filename);
     ModuleSpec readModuleSpecification(const std::string& filename);
     void startCheck();
     void startCheck();
@@ -624,6 +668,8 @@ private:
                             isc::data::ConstElementPtr new_config);
                             isc::data::ConstElementPtr new_config);
 
 
     ModuleSpec fetchRemoteSpec(const std::string& module, bool is_filename);
     ModuleSpec fetchRemoteSpec(const std::string& module, bool is_filename);
+
+    UnhandledCallback unhandled_callback_;
 };
 };
 
 
 /// \brief Default handler for logging config updates
 /// \brief Default handler for logging config updates

+ 37 - 0
src/lib/config/tests/ccsession_unittests.cc

@@ -157,6 +157,17 @@ TEST_F(CCSessionTest, notifyNoParams) {
             session.getMsgQueue()->get(1)->toWire();
             session.getMsgQueue()->get(1)->toWire();
 }
 }
 
 
+// Try to subscribe and unsubscribe once again
+TEST_F(CCSessionTest, subscribe) {
+    ModuleCCSession mccs(ccspecfile("spec1.spec"), session, NULL, NULL, false,
+                         false);
+    EXPECT_FALSE(session.haveSubscription("A group", "*"));
+    mccs.subscribe("A group");
+    EXPECT_TRUE(session.haveSubscription("A group", "*"));
+    mccs.unsubscribe("A group");
+    EXPECT_FALSE(session.haveSubscription("A group", "*"));
+}
+
 TEST_F(CCSessionTest, createAnswer) {
 TEST_F(CCSessionTest, createAnswer) {
     ConstElementPtr answer;
     ConstElementPtr answer;
     answer = createAnswer();
     answer = createAnswer();
@@ -686,6 +697,16 @@ TEST_F(CCSessionTest, remoteConfig) {
     }
     }
 }
 }
 
 
+void
+callback(std::string* command, std::string* target, ConstElementPtr *params,
+         const std::string& command_real, const std::string& target_real,
+         const ConstElementPtr& params_real)
+{
+    *command = command_real;
+    *target = target_real;
+    *params = params_real;
+}
+
 TEST_F(CCSessionTest, ignoreRemoteConfigCommands) {
 TEST_F(CCSessionTest, ignoreRemoteConfigCommands) {
     // client will ask for config
     // client will ask for config
     session.getMessages()->add(createAnswer(0, el("{  }")));
     session.getMessages()->add(createAnswer(0, el("{  }")));
@@ -721,6 +742,22 @@ TEST_F(CCSessionTest, ignoreRemoteConfigCommands) {
     result = mccs.checkCommand();
     result = mccs.checkCommand();
     EXPECT_EQ(0, session.getMsgQueue()->size());
     EXPECT_EQ(0, session.getMsgQueue()->size());
     EXPECT_EQ(0, result);
     EXPECT_EQ(0, result);
+
+    // Check that we can get the ignored commands by registering a callback
+    std::string command, target;
+    ConstElementPtr params;
+    mccs.setUnhandledCallback(boost::bind(&callback, &command, &target,
+                                          &params, _1, _2, _3));
+    session.addMessage(el("{ \"command\": [ \"good_command\","
+                          "{\"param\": true} ] }"), "Spec1", "*");
+    EXPECT_EQ(1, session.getMsgQueue()->size());
+    result = mccs.checkCommand();
+    EXPECT_EQ(0, session.getMsgQueue()->size());
+    EXPECT_EQ(0, result);
+
+    EXPECT_EQ("good_command", command);
+    EXPECT_EQ("Spec1", target);
+    EXPECT_TRUE(params && el("{\"param\": true}")->equals(*params));
 }
 }
 
 
 TEST_F(CCSessionTest, initializationFail) {
 TEST_F(CCSessionTest, initializationFail) {

+ 3 - 2
src/lib/datasrc/client_list.cc

@@ -330,7 +330,7 @@ ConfigurableClientList::findInternal(MutableResult& candidate,
     // and the need_updater parameter is true, get the zone there.
     // and the need_updater parameter is true, get the zone there.
 }
 }
 
 
-void
+bool
 ConfigurableClientList::resetMemorySegment
 ConfigurableClientList::resetMemorySegment
     (const std::string& datasrc_name,
     (const std::string& datasrc_name,
      ZoneTableSegment::MemorySegmentOpenMode mode,
      ZoneTableSegment::MemorySegmentOpenMode mode,
@@ -340,9 +340,10 @@ ConfigurableClientList::resetMemorySegment
         if (info.name_ == datasrc_name) {
         if (info.name_ == datasrc_name) {
             ZoneTableSegment& segment = *info.ztable_segment_;
             ZoneTableSegment& segment = *info.ztable_segment_;
             segment.reset(mode, config_params);
             segment.reset(mode, config_params);
-            break;
+            return true;
         }
         }
     }
     }
+    return false;
 }
 }
 
 
 ConfigurableClientList::ZoneWriterPair
 ConfigurableClientList::ZoneWriterPair

+ 3 - 4
src/lib/datasrc/client_list.h

@@ -385,7 +385,8 @@ public:
     /// \param datasrc_name The name of the data source whose segment to reset
     /// \param datasrc_name The name of the data source whose segment to reset
     /// \param mode The open mode for the new memory segment
     /// \param mode The open mode for the new memory segment
     /// \param config_params The configuration for the new memory segment.
     /// \param config_params The configuration for the new memory segment.
-    void resetMemorySegment
+    /// \return If the data source was found and reset.
+    bool resetMemorySegment
         (const std::string& datasrc_name,
         (const std::string& datasrc_name,
          memory::ZoneTableSegment::MemorySegmentOpenMode mode,
          memory::ZoneTableSegment::MemorySegmentOpenMode mode,
          isc::data::ConstElementPtr config_params);
          isc::data::ConstElementPtr config_params);
@@ -453,8 +454,6 @@ public:
                             bool want_finder = true) const;
                             bool want_finder = true) const;
 
 
     /// \brief This holds one data source client and corresponding information.
     /// \brief This holds one data source client and corresponding information.
-    ///
-    /// \todo The content yet to be defined.
     struct DataSourceInfo {
     struct DataSourceInfo {
         DataSourceInfo(DataSourceClient* data_src_client,
         DataSourceInfo(DataSourceClient* data_src_client,
                        const DataSourceClientContainerPtr& container,
                        const DataSourceClientContainerPtr& container,
@@ -526,7 +525,7 @@ public:
     /// This may throw standard exceptions, such as std::bad_alloc. Otherwise,
     /// This may throw standard exceptions, such as std::bad_alloc. Otherwise,
     /// it is exception free.
     /// it is exception free.
     std::vector<DataSourceStatus> getStatus() const;
     std::vector<DataSourceStatus> getStatus() const;
-public:
+
     /// \brief Access to the data source clients.
     /// \brief Access to the data source clients.
     ///
     ///
     /// It can be used to examine the loaded list of data sources clients
     /// It can be used to examine the loaded list of data sources clients

+ 9 - 1
src/lib/datasrc/tests/client_list_unittest.cc

@@ -368,7 +368,8 @@ public:
                        const std::string& datasrc_name,
                        const std::string& datasrc_name,
                        ZoneTableSegment::MemorySegmentOpenMode mode,
                        ZoneTableSegment::MemorySegmentOpenMode mode,
                        ConstElementPtr config_params) {
                        ConstElementPtr config_params) {
-        list.resetMemorySegment(datasrc_name, mode, config_params);
+        EXPECT_TRUE(list.resetMemorySegment(datasrc_name, mode,
+                                            config_params));
     }
     }
     virtual std::string getType() {
     virtual std::string getType() {
         return ("mapped");
         return ("mapped");
@@ -383,6 +384,13 @@ INSTANTIATE_TEST_CASE_P(ListTestMapped, ListTest,
 
 
 #endif
 #endif
 
 
+// Calling reset on empty list finds no data and returns false.
+TEST_P(ListTest, emptyReset) {
+    EXPECT_FALSE(list_->resetMemorySegment("Something",
+                                           memory::ZoneTableSegment::CREATE,
+                                           Element::create()));
+}
+
 // Test the test itself
 // Test the test itself
 TEST_P(ListTest, selfTest) {
 TEST_P(ListTest, selfTest) {
     EXPECT_EQ(result::SUCCESS, ds_[0]->findZone(Name("example.org")).code);
     EXPECT_EQ(result::SUCCESS, ds_[0]->findZone(Name("example.org")).code);