]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#3463] Implementd leases4_committed callout
authorThomas Markwalder <tmark@isc.org>
Fri, 31 Jan 2025 16:29:20 +0000 (11:29 -0500)
committerThomas Markwalder <tmark@isc.org>
Tue, 18 Feb 2025 18:54:19 +0000 (18:54 +0000)
/src/hooks/dhcp/lease_cmds/binding_variables.cc
    BindingVariableMgr::evaluateVariables()
    - Add a throw if query,response, or lease are empty

/src/hooks/dhcp/lease_cmds/lease_cmds.*
    LeaseCmdsImpl::leases4Committed()
    LeaseCmds::leases4Committed()
    - implemented callout handler

/src/hooks/dhcp/lease_cmds/lease_cmds_callouts.cc
    leases4_committed(CalloutHandle& handle) - new callout

/src/hooks/dhcp/lease_cmds/lease_cmds_messages.mes
    LEASE_CMDS_LEASES4_COMMITTED_NOTHING_TO_UPDATE
    LEASE_CMDS_LEASES4_COMMITTED_FAILED processing - new messages

/src/hooks/dhcp/lease_cmds/tests/lease_cmds4_unittest.cc
    void Lease4CmdsTest::testValidLeases4Committed()
    TEST_F(Lease4CmdsTest, validLeases4Committed)
    TEST_F(Lease4CmdsTest, validLeases4CommittedMultiThreading)
    - new tests

src/hooks/dhcp/lease_cmds/binding_variables.cc
src/hooks/dhcp/lease_cmds/binding_variables.h
src/hooks/dhcp/lease_cmds/lease_cmds.cc
src/hooks/dhcp/lease_cmds/lease_cmds.h
src/hooks/dhcp/lease_cmds/lease_cmds_callouts.cc
src/hooks/dhcp/lease_cmds/lease_cmds_messages.cc
src/hooks/dhcp/lease_cmds/lease_cmds_messages.h
src/hooks/dhcp/lease_cmds/lease_cmds_messages.mes
src/hooks/dhcp/lease_cmds/tests/binding_variables_unittest.cc
src/hooks/dhcp/lease_cmds/tests/lease_cmds4_unittest.cc

index a0e7b5fb78773b712c0d71eade5369b043b50478..09e45a4976e4de0767c4fc35528b41980f0adc5d 100644 (file)
@@ -29,7 +29,7 @@ BindingVariable::BindingVariable(const std::string& name,
         isc_throw(BadValue, "BindingVariable - name cannot be empty");
     }
 
-    /// @todo If we add socpes we may wish to allow higher order
+    /// @todo If we add scopes we may wish to allow higher order
     /// scopes to override lower scopes with empty expressions.
     if (expression_str_.empty()) {
         isc_throw(BadValue, "BindingVariable - '" << name_
@@ -223,10 +223,14 @@ BindingVariableMgr::configure(data::ConstElementPtr config) {
 
 bool
 BindingVariableMgr::evaluateVariables(PktPtr query, PktPtr response, LeasePtr lease) {
+    if (!query || !response || !lease) {
+        isc_throw(BadValue, "evaluateVariables - missing query, response, and/or lease");
+    }
+
     if (!cache_->size()) {
         /// @todo If the lease has binding-variables in its context from a prior
         /// update, but config changed and now there are none defined, should we
-        /// removed them from the lease?
+        /// removed them from the lease? For now, we'll leave them.
         return(false);
     }
 
index 786743f4d14375b0c640eff9976d488013e85f45..e7f7af1322957d46230a544c259541d7dd5698b2 100644 (file)
@@ -289,6 +289,9 @@ public:
 
     /// @brief Evaluates the binding variables for a given lease and packet pair.
     ///
+    /// Iiterates over the variables in the cache evaluating each on and added its
+    /// name and value to the context.
+    ///
     /// @param query Client packet which produced the lease. Variables whose source
     /// is "query" will be evaluated against this packet.
     /// @param response Server response conveying the lease. Variables whose source
index b0baf990796443c544ac231c7c5f762f3ec0152c..43a940c3dde349534bbe30eb8180bac5c2dc1f4f 100644 (file)
@@ -43,6 +43,7 @@ using namespace isc::asiolink;
 using namespace isc::hooks;
 using namespace isc::stats;
 using namespace isc::util;
+using namespace isc::log;
 using namespace std;
 
 namespace isc {
@@ -471,6 +472,19 @@ public:
         }
         return (isc->get("relay-info"));
     }
+
+    /// @brief leases4-committed hookpoint handler.
+    ///
+    /// Evaluates the binding variables (if any), and updates the given lease's
+    /// user-context accordingly.  This includes updating the lease in the lease
+    /// back end.
+    ///
+    /// @param handle Callout context - which is expected to contain the query4, response4,
+    /// and leases4 argumeents.
+    /// @param mgr Pointer to the BindingVariableMgr singleton.
+    /// @throw 
+    static void leases4Committed(CalloutHandle& callout_handle, 
+                                 BindingVariableMgrPtr mgr);
 };
 
 void
@@ -2698,6 +2712,42 @@ LeaseCmdsImpl::leaseWriteHandler(CalloutHandle& handle) {
     return (0);
 }
 
+void
+LeaseCmdsImpl::leases4Committed(CalloutHandle& callout_handle,
+                                BindingVariableMgrPtr mgr) {
+    Pkt4Ptr query;
+    Pkt4Ptr response;
+    Lease4CollectionPtr leases;
+
+    // Get the necessary arguments.
+    callout_handle.getArgument("query4", query);
+    callout_handle.getArgument("response4", response);
+    callout_handle.getArgument("leases4", leases);
+
+    // In some cases we may have no leases, e.g. DHCPNAK.
+    if (leases->empty()) {
+        LOG_DEBUG(lease_cmds_logger, DBGLVL_TRACE_BASIC, 
+                  LEASE_CMDS_LEASES4_COMMITTED_NOTHING_TO_UPDATE)
+            .arg(query->getLabel());
+        return;
+    }
+
+    Lease4Ptr lease = (*leases)[0];
+    try {
+        if (mgr->evaluateVariables(query, response, lease)) {
+            LeaseMgrFactory::instance().updateLease4(lease);
+        }
+    } catch (const NoSuchLease&) {
+        isc_throw(LeaseCmdsConflict, "failed to update"
+                  " the lease with address " << lease->addr_ << 
+                  " either because the lease has been"
+                  " deleted or it has changed in the database");
+    } catch (const std::exception& ex) {
+        isc_throw(Unexpected, "evaluating binding variables failed for "
+                  << query->getLabel() << ", :" << ex.what());
+    }
+}
+
 int
 LeaseCmds::leaseAddHandler(CalloutHandle& handle) {
     return (impl_->leaseAddHandler(handle));
@@ -2793,5 +2843,12 @@ LeaseCmds::leaseWriteHandler(CalloutHandle& handle) {
 LeaseCmds::LeaseCmds() : impl_(new LeaseCmdsImpl()) {
 }
 
+
+void
+LeaseCmds::leases4Committed(CalloutHandle& callout_handle,
+                            BindingVariableMgrPtr mgr) {
+    impl_->leases4Committed(callout_handle, mgr);
+}
+
 } // end of namespace lease_cmds
 } // end of namespace isc
index 1ea9e534de35b02176dbe12c7762cb8a7020c467..b0ed786ab72d877012fed2165c72bfab055e431e 100644 (file)
@@ -8,6 +8,7 @@
 #define LEASE_CMDS_H
 
 #include <cc/data.h>
+#include <binding_variables.h>
 #include <hooks/hooks.h>
 
 #include <boost/shared_ptr.hpp>
@@ -605,6 +606,19 @@ public:
     int
     leaseWriteHandler(hooks::CalloutHandle& handle);
 
+    /// @brief leases4_committed hookpoint handler.
+    ///
+    /// Evaluates the binding variables (if any), and updates the given lease's
+    /// user-context accordingly.  This includes updating the lease in the lease
+    /// back end.
+    ///
+    /// @param handle Callout context - which is expected to contain the query4, response4,
+    /// and leases4 argumeents.
+    /// @param mgr Pointer to the BindingVariableMgr singleton.
+    void 
+    leases4Committed(hooks::CalloutHandle& callout_handle, 
+                     BindingVariableMgrPtr mgr);
+
 private:
     /// Pointer to the actual implementation
     boost::shared_ptr<LeaseCmdsImpl> impl_;
index 5c09e978cc81603f7b8c0318dab1777094bb45ab..c3c8a99b2350018e3b2e56bec04f1aaac0a9ea0d 100644 (file)
@@ -378,4 +378,26 @@ int multi_threading_compatible() {
     return (1);
 }
 
+/// @brief leases4_committed callout implementation.
+///
+/// @param handle callout handle.
+int leases4_committed(CalloutHandle& handle) {
+    CalloutHandle::CalloutNextStep status = handle.getStatus();
+    if (status == CalloutHandle::NEXT_STEP_DROP ||
+        status == CalloutHandle::NEXT_STEP_SKIP) {
+        return (0);
+    }
+
+    try {
+        LeaseCmds lease_cmds;
+        lease_cmds.leases4Committed(handle, binding_var_mgr);
+    } catch (const std::exception& ex) {
+        LOG_ERROR(lease_cmds_logger, LEASE_CMDS_LEASES4_COMMITTED_FAILED)
+            .arg(ex.what());
+        return (1);
+    }
+
+    return (0);
+}
+
 } // end extern "C"
index 2732de09f225ea81048ddc06d766a3208ff20c4b..c13da0ca194a55daa90a6560f094434b4644373a 100644 (file)
@@ -20,6 +20,8 @@ extern const isc::log::MessageID LEASE_CMDS_DEL6_FAILED = "LEASE_CMDS_DEL6_FAILE
 extern const isc::log::MessageID LEASE_CMDS_GET4_FAILED = "LEASE_CMDS_GET4_FAILED";
 extern const isc::log::MessageID LEASE_CMDS_GET6_FAILED = "LEASE_CMDS_GET6_FAILED";
 extern const isc::log::MessageID LEASE_CMDS_INIT_OK = "LEASE_CMDS_INIT_OK";
+extern const isc::log::MessageID LEASE_CMDS_LEASES4_COMMITTED_FAILED = "LEASE_CMDS_LEASES4_COMMITTED_FAILED";
+extern const isc::log::MessageID LEASE_CMDS_LEASES4_COMMITTED_NOTHING_TO_UPDATE = "LEASE_CMDS_LEASES4_COMMITTED_NOTHING_TO_UPDATE";
 extern const isc::log::MessageID LEASE_CMDS_RESEND_DDNS4 = "LEASE_CMDS_RESEND_DDNS4";
 extern const isc::log::MessageID LEASE_CMDS_RESEND_DDNS4_FAILED = "LEASE_CMDS_RESEND_DDNS4_FAILED";
 extern const isc::log::MessageID LEASE_CMDS_RESEND_DDNS6 = "LEASE_CMDS_RESEND_DDNS6";
@@ -56,6 +58,8 @@ const char* values[] = {
     "LEASE_CMDS_GET4_FAILED", "lease4-get command failed (parameters: %1, reason: %2)",
     "LEASE_CMDS_GET6_FAILED", "lease6-get command failed (parameters: %1, reason: %2)",
     "LEASE_CMDS_INIT_OK", "loading Lease Commands hooks library successful",
+    "LEASE_CMDS_LEASES4_COMMITTED_FAILED", "processing error occured evaluating binding variables for %1, %2",
+    "LEASE_CMDS_LEASES4_COMMITTED_NOTHING_TO_UPDATE", "query %1, has no active lease.",
     "LEASE_CMDS_RESEND_DDNS4", "lease4-resend-ddns command successful: %1",
     "LEASE_CMDS_RESEND_DDNS4_FAILED", "lease4-resend-ddns command failed: %1",
     "LEASE_CMDS_RESEND_DDNS6", "lease6-resend-ddns command successful: %1",
index 90efb4596f79e91bae64c2dba18d10140b56466a..056f935b354fad74a971db87db3ffe45b228242a 100644 (file)
@@ -21,6 +21,8 @@ extern const isc::log::MessageID LEASE_CMDS_DEL6_FAILED;
 extern const isc::log::MessageID LEASE_CMDS_GET4_FAILED;
 extern const isc::log::MessageID LEASE_CMDS_GET6_FAILED;
 extern const isc::log::MessageID LEASE_CMDS_INIT_OK;
+extern const isc::log::MessageID LEASE_CMDS_LEASES4_COMMITTED_FAILED;
+extern const isc::log::MessageID LEASE_CMDS_LEASES4_COMMITTED_NOTHING_TO_UPDATE;
 extern const isc::log::MessageID LEASE_CMDS_RESEND_DDNS4;
 extern const isc::log::MessageID LEASE_CMDS_RESEND_DDNS4_FAILED;
 extern const isc::log::MessageID LEASE_CMDS_RESEND_DDNS6;
index f83e259d2caa4ed4b4fc44e0b77ccdc320c5ca0a..44985368da078c344b84697b9c97e36eb0b33140 100644 (file)
@@ -138,3 +138,12 @@ The lease6-wipe command is deprecated and it will be removed in the future.
 % LEASE_CMDS_WIPE6_FAILED lease6-wipe command failed (parameters: %1, reason: %2)
 The lease6-wipe command has failed. Both the reason as well as the
 parameters passed are logged.
+
+% LEASE_CMDS_LEASES4_COMMITTED_NOTHING_TO_UPDATE query %1, has no active lease.
+This debug log is emitted when the leaes4-committed handler is invoked and
+there is no active lease associated with the query. 
+
+% LEASE_CMDS_LEASES4_COMMITTED_FAILED processing error occured evaluating binding variables for %1, %2
+This debug log is emitted when an error occurs in  the leaes4-committed 
+handler is invoked. The arguemntes detail the query and the error. This
+error means binding-variable values were not added/updated for the lease.
index 9c949305a897a7c54afc9621e2f761a848d14ce7..184cd47e475b573d28478e9e05cd8abf0caf0156 100644 (file)
@@ -6,7 +6,6 @@
 
 #include <config.h>
 
-
 #include <binding_variables.h>
 #include <exceptions/exceptions.h>
 #include <cc/data.h>
@@ -14,6 +13,7 @@
 #include <dhcp/pkt4.h>
 #include <dhcp/pkt6.h>
 #include <dhcpsrv/lease.h>
+
 #include <testutils/gtest_utils.h>
 #include <testutils/user_context_utils.h>
 #include <testutils/multi_threading_utils.h>
@@ -26,6 +26,7 @@ using namespace isc::dhcp;
 using namespace isc::data;
 using namespace isc::test;
 using namespace isc::asiolink;
+using namespace isc::hooks;
 
 using namespace isc::lease_cmds;
 
index 4dcc6efc868f07d41990d8ae111cc3990314e748..aff427c228d3f51313d458f36b8c1d1a47d29b4e 100644 (file)
 #include <dhcpsrv/resource_handler.h>
 #include <cc/command_interpreter.h>
 #include <cc/data.h>
+#include <lease_cmds.h>
 #include <lease_cmds_unittest.h>
 #include <stats/stats_mgr.h>
 #include <testutils/user_context_utils.h>
 #include <testutils/multi_threading_utils.h>
+#include <testutils/gtest_utils.h>
 
 #include <gtest/gtest.h>
 
@@ -36,6 +38,7 @@ using namespace isc::dhcp_ddns;
 using namespace isc::asiolink;
 using namespace isc::stats;
 using namespace isc::test;
+using namespace isc::lease_cmds;
 
 namespace {
 
@@ -444,6 +447,10 @@ public:
 
     /// @brief Check that lease4-write works as expected.
     void testLease4Write();
+
+    /// @brief Check that lease4-committed handler works as expected with
+    /// valid inputs.
+    void testValidLeases4Committed();
 };
 
 void Lease4CmdsTest::testLease4AddMissingParams() {
@@ -3482,6 +3489,141 @@ void Lease4CmdsTest::testLease4Write() {
     testCommand(txt, CONTROL_RESULT_ERROR, exp_rsp);
 }
 
+#define SCOPED_LINE(line) \
+    std::stringstream ss; \
+    ss << "Scenario at line: " << line; \
+    SCOPED_TRACE(ss.str());
+
+void Lease4CmdsTest::testValidLeases4Committed() {
+    // Initialize lease manager (false = v4, true = add leases)
+    initLeaseMgr(false, true);
+
+    struct Scenario {
+        uint32_t line_;
+        std::string config_;
+        std::string orig_context_;
+        std::string exp_context_;
+    };
+
+    std::list<Scenario> scenarios = {
+    {
+        // No variables configured, nothing in lease context.
+        __LINE__,
+        R"({})",
+        R"({})",
+        R"({})"
+    },{
+        // lease context has no binding-variables, two configured
+        __LINE__,
+        R"^({"binding-variables":[
+            {
+                "name": "hwaddr",
+                "expression": "hexstring(pkt4.mac,':')",
+                "source": "query"
+            },
+            {
+                "name": "yiaddr",
+                "expression": "addrtotext(pkt4.yiaddr)",
+                "source": "response"
+            }]})^",
+        R"({})",
+        R"({"ISC":{
+            "binding-variables":{
+                "hwaddr": "01:02:03:04:05:06",
+                "yiaddr": "192.0.2.1"
+            }
+        }})",
+    },
+    {
+        // lease context has binding-variables, none configured
+        // Current logic leaves lease untouched.
+        __LINE__,
+        R"({})",
+        R"({"ISC":{
+            "binding-variables":{
+                "hwaddr": "01:02:03:04:05:06",
+                "yiaddr": "192.0.2.1"
+            }
+        }})",
+        R"({"ISC":{
+            "binding-variables":{
+                "hwaddr": "01:02:03:04:05:06",
+                "yiaddr": "192.0.2.1"
+            }
+        }})",
+    },
+    {
+        // Evaluated variable value is an empty string.
+        __LINE__,
+        R"^({"binding-variables":[
+            {
+                "name": "hwaddr",
+                "expression": "''",
+                "source": "query"
+            }]})^",
+        R"({"ISC":{
+            "binding-variables":{
+                "hwaddr": "01:02:03:04:05:06"
+            }
+        }})",
+        R"({"ISC":{
+            "binding-variables":{
+                "hwaddr": ""
+            }
+        }})",
+    }};
+
+    // Create packet pair and lease.
+    Pkt4Ptr query(new Pkt4(DHCPDISCOVER, 1234));
+    query->setHWAddr(1, 6, { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 });
+
+    Pkt4Ptr response(new Pkt4(DHCPOFFER, 1234));
+    IOAddress yiaddr("192.0.2.1");
+    response->setYiaddr(yiaddr);
+
+    // Iterater over scenarios.
+    for (auto const& scenario : scenarios) {
+        SCOPED_LINE(scenario.line_);
+
+        // Create and configure the manager.
+        BindingVariableMgrPtr mgr;
+        ASSERT_NO_THROW_LOG(mgr.reset(new BindingVariableMgr(AF_INET)));
+        ConstElementPtr config;
+        ASSERT_NO_THROW_LOG(config = Element::fromJSON(scenario.config_));
+        ASSERT_NO_THROW_LOG(mgr->configure(config));
+
+        // Fetch the lease and set its user-context to the original content.
+        Lease4Ptr orig_lease = lmptr_->getLease4(yiaddr);
+        ASSERT_TRUE(orig_lease);
+        ConstElementPtr orig_context;
+        ASSERT_NO_THROW_LOG(orig_context = Element::fromJSON(scenario.orig_context_));
+        orig_lease->setContext(orig_context);
+        ASSERT_NO_THROW_LOG(lmptr_->updateLease4(orig_lease));
+
+        Lease4CollectionPtr leases(new Lease4Collection());
+        leases->push_back(orig_lease);
+
+        // Create a callout handle and add the expected arguments.
+        CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
+        callout_handle->setArgument("query4", query);
+        callout_handle->setArgument("response4", response);
+        callout_handle->setArgument("leases4", leases);
+
+        // Invoke the leases4Committed handler.
+        LeaseCmds cmds;
+        ASSERT_NO_THROW_LOG(cmds.leases4Committed(*callout_handle, mgr));
+
+        // Fetch the lease.
+        Lease4Ptr after_lease = lmptr_->getLease4(yiaddr);
+        ASSERT_TRUE(after_lease);
+
+        // Context contents should match the expected context content.
+        ConstElementPtr exp_context;
+        ASSERT_NO_THROW_LOG(exp_context = Element::fromJSON(scenario.exp_context_));
+        ASSERT_EQ(*(after_lease->getContext()), *exp_context);
+    }
+}
+
 TEST_F(Lease4CmdsTest, lease4AddMissingParams) {
     testLease4AddMissingParams();
 }
@@ -4210,4 +4352,13 @@ TEST_F(Lease4CmdsTest, lease4WriteMultiThreading) {
     testLease4Write();
 }
 
+TEST_F(Lease4CmdsTest, validLeases4Committed) {
+    testValidLeases4Committed();
+}
+
+TEST_F(Lease4CmdsTest, validLeases4CommittedMultiThreading) {
+    MultiThreadingTest mt(true);
+    testValidLeases4Committed();
+}
+
 } // end of anonymous namespace