]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#3463] Added BindingVariableMgr
authorThomas Markwalder <tmark@isc.org>
Tue, 28 Jan 2025 15:13:46 +0000 (10:13 -0500)
committerThomas Markwalder <tmark@isc.org>
Tue, 18 Feb 2025 18:54:19 +0000 (18:54 +0000)
Library can now be configured with binding variables.

/src/hooks/dhcp/lease_cmds/binding_variables.*
    Adding BindingVariableMgr class

/src/hooks/dhcp/lease_cmds/lease_cmds_callouts.cc
    Adding manager singleton, created and configured
    in load()

/src/hooks/dhcp/lease_cmds/libloadtests/load_unload_unittests.cc
    Added valid configuration to load tests

/src/hooks/dhcp/lease_cmds/tests/binding_variables_unittest.cc
    TEST(BindingVariableMgrTest, validConfigure)
    TEST(BindingVariableMgrTest, clearOnConfigure)
    TEST(BindingVariableMgrTest, invalidConfigure) - new tests

/src/lib/dhcpsrv/testutils/lib_load_test_fixture.h
    Added check of load/unload lib functions to catch
    config errors.

src/hooks/dhcp/lease_cmds/binding_variables.cc
src/hooks/dhcp/lease_cmds/binding_variables.h
src/hooks/dhcp/lease_cmds/lease_cmds_callouts.cc
src/hooks/dhcp/lease_cmds/libloadtests/load_unload_unittests.cc
src/hooks/dhcp/lease_cmds/tests/binding_variables_unittest.cc
src/lib/dhcpsrv/testutils/lib_load_test_fixture.h

index e3a39ee95adc6ece2202f36ea7bd8aab65054c58..d2d8b03d39df1eaec0eb06de2f4055efcd63e6ae 100644 (file)
@@ -111,7 +111,7 @@ BindingVariableCache::BindingVariableCache()
 }
 
 bool
-BindingVariableCache::cacheVariable(BindingVariablePtr variable) {
+BindingVariableCache::add(BindingVariablePtr variable) {
     util::MultiThreadingLock lock(*mutex_);
     auto retpair = variables_.push_back(variable);
     return(retpair.second);
@@ -185,5 +185,41 @@ BindingVariableCache::getBySource(const BindingVariable::Source& source) {
     return (var_list);
 }
 
+BindingVariableMgr::BindingVariableMgr(uint32_t family)
+    : family_(family) {
+    if (family_ != AF_INET && family_ != AF_INET6) {
+        isc_throw (BadValue, "BindingVariableMgr - invalid family: " << family_);
+    }
+
+    cache_.reset(new BindingVariableCache());
+}
+
+void
+BindingVariableMgr::configure(data::ConstElementPtr config) {
+    //  Always wipe the cache.
+    cache_->clear();
+
+    ConstElementPtr elem = config->get("binding-variables");
+    if (!elem) {
+        // Nothing to do.
+        return;
+    }
+
+    // binding-variables should be a list.
+    if (elem->getType() != Element::list) {
+        isc_throw(DhcpConfigError, "'binding-variables' must be a list");
+    }
+
+    // iterate over the list adding variables to the cache
+    for (auto const& var_elem : elem->listValue()) {
+        try {
+            BindingVariablePtr variable = BindingVariable::parse(var_elem, family_);
+            cache_->add(variable);
+        } catch (const std::exception& ex) {
+            isc_throw(DhcpConfigError, "cannot add BindingVariable to cache: " << ex.what());
+        }
+    }
+}
+
 } // end of namespace lease_cmds
 } // end of namespace isc
index d1c9055c3b0951af82fb0e33cfc0e9d062db88a6..ee37ecce145260f8066b63b3289f38144f706c5a 100644 (file)
@@ -111,7 +111,7 @@ public:
         return (source_);
     }
 
-    /// @brief Fetches the variable's protocol family. 
+    /// @brief Fetches the variable's protocol family.
     ///
     /// @return Family of the packet i.e AF_INET or AF_INET6.
     uint32_t getFamily() const {
@@ -208,7 +208,7 @@ public:
     ///
     /// @param variable pointer to the variable to store.
     /// @return true if the variable was added, false otherwise.
-    bool cacheVariable(BindingVariablePtr variable);
+    bool add(BindingVariablePtr variable);
 
     /// @brief Delete all the entries in the cache.
     void clear();
@@ -250,6 +250,64 @@ private:
 /// @brief Defines a shared pointer to a BindingVariableCache.
 typedef boost::shared_ptr<BindingVariableCache> BindingVariableCachePtr;
 
+/// @brief Singleton which warehouses the configuraed binding variables,
+/// and evaluation of variables for a given lease and packet pair.
+class BindingVariableMgr {
+public:
+    /// @brief Constructor
+    ///
+    /// @param family Protocol family of the expression, either
+    /// AF_INET or AF_INET6.
+    explicit BindingVariableMgr(uint32_t family_);
+
+    /// @brief Destructor;
+    ~BindingVariableMgr() = default;
+
+    /// @brief Configures the manager based on the given configuration.
+    ///
+    /// This will clear the binding variable cache and then repopulate it
+    /// by parsing the configuration.  It expects to see a list of one or
+    /// more binding variable maps similar to the following:
+    ///
+    /// @code
+    /// "binding-variables": [{
+    ///     "name": "domain-name",
+    ///     "expression": "option[15].text",
+    ///      "source": "response"
+    ///  },{
+    ///     "name": "opt-222",
+    ///     "expression": "hexstring(option[222].hex, ':')"
+    ///     "source": "query"
+    ///  ..
+    ///  }]
+    ///
+    /// @endcode
+    /// @param config JSON element tree containing the binding-variable list.
+    /// @throw DhcpConfigError if the configuration is invalid.
+    void configure(data::ConstElementPtr config);
+
+    /// @todo - place holder
+    // bool evaluateVariables(LeasePtr lease, PktPtr query, PktPtr response);
+
+    /// @brief Fetches the current variables cache.
+    ///
+    /// @return Pointer to BindingVariableCache.
+    BindingVariableCachePtr getCache() {
+        return(cache_);
+    }
+
+private:
+    /// @brief Protocol family AF_INET or AF_INET6.
+    uint32_t family_;
+
+    /// @brief Currently configured set of binding variables.
+    BindingVariableCachePtr cache_;
+};
+
+/// @brief Defines a shared pointer to a BindingVariableMgr.
+typedef boost::shared_ptr<BindingVariableMgr> BindingVariableMgrPtr;
+
+
 } // end of namespace lease_cmds
 } // end of namespace isc
 #endif
index 7d431b40dd983592523fc47db38c9ed050f3cf2f..5c09e978cc81603f7b8c0318dab1777094bb45ab 100644 (file)
@@ -12,6 +12,7 @@
 
 #include <lease_cmds.h>
 #include <lease_cmds_log.h>
+#include <binding_variables.h>
 #include <cc/command_interpreter.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <hooks/hooks.h>
@@ -24,6 +25,16 @@ using namespace isc::hooks;
 using namespace isc::process;
 using namespace isc::lease_cmds;
 
+namespace isc {
+namespace lease_cmds {
+
+/// @brief Singleton that manages configured binding variables.
+BindingVariableMgrPtr binding_var_mgr;
+
+} // end of namespace lease_cmds
+} // end of namespace isc
+
+
 extern "C" {
 
 /// @brief This is a command callout for 'lease4-add' command.
@@ -339,6 +350,15 @@ int load(LibraryHandle& handle) {
     handle.registerCommandCallout("lease4-write", lease4_write);
     handle.registerCommandCallout("lease6-write", lease6_write);
 
+    // Instantiate the binding-variables manager singleton.
+    binding_var_mgr.reset(new BindingVariableMgr(family));
+
+    // Configure binding variable manager using the hook library's parameters.
+    ConstElementPtr json = handle.getParameters();
+    if (json) {
+        binding_var_mgr->configure(json);
+    }
+
     LOG_INFO(lease_cmds_logger, LEASE_CMDS_INIT_OK);
     return (0);
 }
index 0d0bab6c584e30f9b54990373a81582f4d302414..fb4aca05b5082dd9c69e86e700b86a64ba09610c 100644 (file)
@@ -38,16 +38,36 @@ public:
     virtual ~LeaseCmdsCbLibLoadTest() {
         unloadLibraries();
     }
+
+    /// @brief Creates a set of configuration parameters valid for the library.
+    /// Note the expressions are protocol agnostic for simplicity.
+    virtual isc::data::ElementPtr validConfigParams() {
+        std::string valid_config =
+        R"({"binding-variables":[
+            {
+                "name": "one",
+                "expression": "'just a string'",
+                "source": "query"
+            },
+            {
+                "name": "two",
+                "expression": "'another string'",
+                "source": "response"
+            } ]})";
+
+        // Convert JSON texts to Element map.
+        return (Element::fromJSON(valid_config));
+    }
 };
 
 // Simple V4 test that checks the library can be loaded and unloaded several times.
 TEST_F(LeaseCmdsCbLibLoadTest, validLoad4) {
-    validDaemonTest("kea-dhcp4");
+    validDaemonTest("kea-dhcp4", AF_INET, valid_params_);
 }
 
 // Simple V6 test that checks the library can be loaded and unloaded several times.
 TEST_F(LeaseCmdsCbLibLoadTest, validLoad6) {
-    validDaemonTest("kea-dhcp6", AF_INET6);
+    validDaemonTest("kea-dhcp6", AF_INET6, valid_params_);
 }
 
 // Simple test that checks the library cannot by loaded by invalid daemons.
index 697c4bb0e5f181c2a69f7123002abd0951e20af3..3dcdd6d8116a0917e0fce35d825ef34168f80a12 100644 (file)
@@ -58,6 +58,9 @@ TEST(BindingVariableTest, validConstructor) {
         },
         {
            __LINE__,  "my-var", valid_v6_exp, BindingVariable::RESPONSE, AF_INET6
+        },
+        {
+           __LINE__,  "my-var", "'woo hoo'", BindingVariable::RESPONSE, AF_INET6
         }
     };
 
@@ -303,7 +306,7 @@ TEST(BindingVariableCacheTest, basics) {
                                                               AF_INET6)));
 
     for (auto const& ref_iter : ref_list) {
-        ASSERT_NO_THROW_LOG(cache->cacheVariable(ref_iter));
+        ASSERT_NO_THROW_LOG(cache->add(ref_iter));
     }
 
     // Make sure getAll() returns all four in order added.
@@ -379,7 +382,7 @@ TEST(BindingVariableCacheTest, duplicateEntries) {
                                                 AF_INET));
 
     bool add_flag;
-    ASSERT_NO_THROW_LOG(add_flag = cache->cacheVariable(var1));
+    ASSERT_NO_THROW_LOG(add_flag = cache->add(var1));
     EXPECT_TRUE(add_flag);
     EXPECT_EQ(cache->size(), 1);
 
@@ -389,7 +392,7 @@ TEST(BindingVariableCacheTest, duplicateEntries) {
     ASSERT_EQ(found_var->getSource(), BindingVariable::QUERY);
 
     // Adding a duplicate should fail.
-    ASSERT_NO_THROW_LOG(add_flag = cache->cacheVariable(var2));
+    ASSERT_NO_THROW_LOG(add_flag = cache->add(var2));
     EXPECT_FALSE(add_flag);
     EXPECT_EQ(cache->size(), 1);
 
@@ -398,4 +401,170 @@ TEST(BindingVariableCacheTest, duplicateEntries) {
     ASSERT_EQ(found_var->getSource(), BindingVariable::QUERY);
 }
 
+/// @brief Tests BindingVariableMgr::configure() with valid
+/// configuration scenarios.
+TEST(BindingVariableMgrTest, validConfigure) {
+    struct Scenario {
+        uint32_t line_;
+        uint32_t family_;
+        std::string config_;
+        std::list<std::string> expected_vars_;
+    };
+
+    std::list<Scenario> scenarios = {
+    {
+        __LINE__,
+        AF_INET,
+        R"({})",
+        {}
+    },{
+        __LINE__,
+        AF_INET,
+        R"({"binding-variables":[]})",
+        {}
+    },{
+        __LINE__,
+        AF_INET,
+        R"({"binding-variables":[
+            {
+                "name": "one",
+                "expression": "pkt4.mac",
+                "source": "query"
+            },
+            {
+                "name": "two",
+                "expression": "pkt4.mac",
+                "source": "response"
+            } ]})",
+        { "one", "two" }
+    },{
+        __LINE__,
+        AF_INET6,
+        R"({"binding-variables":[
+            {
+                "name": "three",
+                "expression": "pkt6.transid",
+                "source": "query"
+            },
+            {
+                "name": "four",
+                "expression": "pkt6.transid",
+                "source": "response"
+            } ]})",
+        { "three", "four" }
+    }};
+
+    for (auto const& scenario : scenarios) {
+        SCOPED_LINE(scenario.line_);
+        BindingVariableMgrPtr mgr;
+        ASSERT_NO_THROW_LOG(mgr.reset(new BindingVariableMgr(scenario.family_)));
+        ConstElementPtr config;
+        ASSERT_NO_THROW_LOG(config = Element::fromJSON(scenario.config_));
+        ASSERT_NO_THROW_LOG(mgr->configure(config));
+        auto cache = mgr->getCache();
+        ASSERT_TRUE(cache);
+        ASSERT_EQ(cache->size(), scenario.expected_vars_.size());
+        for (auto const& exp_name : scenario.expected_vars_) {
+            auto const& found_var = cache->getByName(exp_name);
+            ASSERT_TRUE(found_var);
+            EXPECT_EQ(found_var->getName(), exp_name);
+        }
+    }
+}
+
+/// @brief Verifies that BindingVariableMgr::configure() clears the
+/// cache first.
+TEST(BindingVariableMgrTest, clearOnConfigure) {
+    std::string cfg1 = 
+        R"({"binding-variables":[
+            {
+                "name": "one",
+                "expression": "pkt4.mac",
+                "source": "query"
+            },
+            {
+                "name": "two",
+                "expression": "pkt4.mac",
+                "source": "response"
+            } ]})";
+
+    std::string cfg2 = 
+        R"({"binding-variables":[
+            {
+                "name": "three",
+                "expression": "pkt4.mac",
+                "source": "query"
+            },
+            {
+                "name": "four",
+                "expression": "pkt4.mac",
+                "source": "response"
+            } ]})";
+
+    BindingVariableMgrPtr mgr;
+    ASSERT_NO_THROW_LOG(mgr.reset(new BindingVariableMgr(AF_INET)));
+
+    ConstElementPtr config;
+    ASSERT_NO_THROW_LOG(config = Element::fromJSON(cfg1));
+    ASSERT_NO_THROW_LOG(mgr->configure(config));
+    auto cache = mgr->getCache();
+    ASSERT_TRUE(cache);
+    ASSERT_EQ(cache->size(), 2);
+    ASSERT_TRUE(cache->getByName("one"));
+    ASSERT_TRUE(cache->getByName("two"));
+
+    ASSERT_NO_THROW_LOG(config = Element::fromJSON(cfg2));
+    ASSERT_NO_THROW_LOG(mgr->configure(config));
+    cache = mgr->getCache();
+    ASSERT_TRUE(cache);
+    ASSERT_EQ(cache->size(), 2);
+    ASSERT_FALSE(cache->getByName("one"));
+    ASSERT_FALSE(cache->getByName("two"));
+    ASSERT_TRUE(cache->getByName("three"));
+    ASSERT_TRUE(cache->getByName("four"));
+}
+
+/// @brief Tests BindingVariableMgr::configure() with valid
+/// configuration scenarios.
+TEST(BindingVariableMgrTest, invalidConfigure) {
+    struct Scenario {
+        uint32_t line_;
+        uint32_t family_;
+        std::string config_;
+        std::string expected_error_;
+    };
+
+    std::list<Scenario> scenarios = {
+    {
+        __LINE__,
+        AF_INET,
+        R"({"binding-variables": "bogus"})",
+        "'binding-variables' must be a list"
+    },{
+        __LINE__,
+        AF_INET,
+        R"({"binding-variables":[
+            {
+                "name": "",
+                "expression": "pkt4.mac",
+                "source": "query"
+            } ]})",
+        "cannot add BindingVariable to cache: invalid config:"
+        " BindingVariable - name cannot be empty"
+    }};
+
+    for (auto const& scenario : scenarios) {
+        SCOPED_LINE(scenario.line_);
+        BindingVariableMgrPtr mgr;
+        ASSERT_NO_THROW_LOG(mgr.reset(new BindingVariableMgr(scenario.family_)));
+        ConstElementPtr config;
+        ASSERT_NO_THROW_LOG(config = Element::fromJSON(scenario.config_));
+        ASSERT_THROW_MSG(mgr->configure(config), DhcpConfigError,
+                         scenario.expected_error_);
+        auto cache = mgr->getCache();
+        ASSERT_TRUE(cache);
+        EXPECT_EQ(cache->size(), 0);
+    }
+}
+
 } // end of anonymous namespace
index 11f03b320146d781a2c599c8f6f62df00d5d2e83..df1d8f83675a1ce9dd187b105c13b119bed389e1 100644 (file)
@@ -94,11 +94,17 @@ public:
         ASSERT_NO_THROW_LOG(addLibrary(lib_so_name_, params));
 
         // Should be able to load and unload the library more than once.
-        ASSERT_NO_THROW_LOG(loadLibraries());
-        ASSERT_NO_THROW_LOG(unloadLibraries());
-
-        ASSERT_NO_THROW_LOG(loadLibraries());
-        ASSERT_NO_THROW_LOG(unloadLibraries());
+        // We test the return to catch things like configuration errors.
+        bool ret;
+        ASSERT_NO_THROW_LOG(ret = loadLibraries());
+        ASSERT_TRUE(ret);
+        ASSERT_NO_THROW_LOG(ret = unloadLibraries());
+        ASSERT_TRUE(ret);
+
+        ASSERT_NO_THROW_LOG(ret = loadLibraries());
+        ASSERT_TRUE(ret);
+        ASSERT_NO_THROW_LOG(ret = unloadLibraries());
+        ASSERT_TRUE(ret);
     }
 
     /// @brief Verifies that an invalid daemon cannot load the library.