2
0
mirror of https://gitlab.isc.org/isc-projects/kea synced 2025-08-31 14:05:33 +00:00

[#3463] Added BindingVariable classes to lease cmds

/src/hooks/dhcp/lease_cmds/Makefile.am
    added new files

/src/hooks/dhcp/lease_cmds/binding_variables.cc
/src/hooks/dhcp/lease_cmds/binding_variables.h
     new files

/src/hooks/dhcp/lease_cmds/tests/Makefile.am
    added new file

/src/hooks/dhcp/lease_cmds/tests/binding_variables_unittest.cc
    new file
This commit is contained in:
Thomas Markwalder
2025-01-23 15:11:37 -05:00
parent 16306026e3
commit 24de4fa534
5 changed files with 623 additions and 1 deletions

View File

@@ -20,6 +20,7 @@ liblease_cmds_la_SOURCES += lease_cmds_exceptions.h
liblease_cmds_la_SOURCES += lease_parser.h lease_parser.cc
liblease_cmds_la_SOURCES += lease_cmds_log.cc lease_cmds_log.h
liblease_cmds_la_SOURCES += lease_cmds_messages.cc lease_cmds_messages.h
liblease_cmds_la_SOURCES += binding_variables.h binding_variables.cc
liblease_cmds_la_SOURCES += version.cc
liblease_cmds_la_CXXFLAGS = $(AM_CXXFLAGS)

View File

@@ -0,0 +1,154 @@
// Copyright (C) 2025 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Kea Hooks Basic
// Commercial End User License Agreement v2.0. See COPYING file in the premium/
// directory.
#include <config.h>
#include <binding_variables.h>
#include <iostream>
#include <cc/data.h>
#include <eval/eval_context.h>
#include <util/multi_threading_mgr.h>
using namespace isc::dhcp;
using namespace isc::data;
namespace isc {
namespace lease_cmds {
BindingVariable::BindingVariable(const std::string& name,
const std::string& expression_str,
const Source& source,
uint32_t family)
: name_(name), expression_str_(expression_str), source_(source),
family_(family) {
if (name_.empty()) {
isc_throw(BadValue, "BindingVariable - name cannot be empty");
}
/// @todo If we add socpes we may wish to allow higher order
/// scopes to override lower scopes with empty expressions.
if (expression_str_.empty()) {
isc_throw(BadValue, "BindingVariable - '" << name_
<< "' expression_str cannot be empty");
}
if (family_ != AF_INET && family_ != AF_INET6) {
isc_throw(BadValue, "BindingVariable - '" << name_
<< "', invalid family: " << family_);
}
try {
EvalContext eval_ctx(family_ == AF_INET ? Option::V4 : Option::V6);
eval_ctx.parseString(expression_str_, EvalContext::PARSER_STRING);
expression_.reset(new Expression(eval_ctx.expression_));
} catch (const std::exception& ex) {
isc_throw(BadValue, "BindingVariable - '" << name_ << "', error parsing expression: '"
<< expression_str_ << "' : " << ex.what());
}
}
std::string
BindingVariable::evaluate(PktPtr packet) const {
try {
return (evaluateString(*expression_, *packet));
} catch (const std::exception& ex) {
isc_throw(BadValue, "BindingVariable - " << name_ << ", error evaluating expression: ["
<< expression_str_ << "] : " << ex.what());
}
}
/// @todo Not sure we need CfgElement derivation
ElementPtr
BindingVariable::toElement() const {
ElementPtr map = Element::createMap();
map->set("name", Element::create(name_));
map->set("expression_str", Element::create(expression_str_));
map->set("source", Element::create((source_ == QUERY ? "query" : "response")));
// family_ is contextual
return (map);
}
BindingVariableCache::BindingVariableCache()
: variables_(), mutex_(new std::mutex) {
}
void
BindingVariableCache::cacheVariable(BindingVariablePtr variable) {
util::MultiThreadingLock lock(*mutex_);
variables_.push_back(variable);
}
void
BindingVariableCache::clear() {
util::MultiThreadingLock lock(*mutex_);
// Discard contents.
// We use modification time to remember the last time we flushed.
variables_.clear();
updateModificationTime();
}
size_t
BindingVariableCache::size() {
util::MultiThreadingLock lock(*mutex_);
return (variables_.size());
}
boost::posix_time::ptime
BindingVariableCache::getLastFlushTime() {
util::MultiThreadingLock lock(*mutex_);
return (BaseStampedElement::getModificationTime());
}
/// @brief Tag for the name index.
//struct VariableNameTag { };
/// @brief Tag for the source index.
//struct VariableSourceTag { };
BindingVariableListPtr
BindingVariableCache::getAll() {
util::MultiThreadingLock lock(*mutex_);
BindingVariableListPtr var_list(new BindingVariableList());
const auto& index = variables_.get<VariableSequenceTag>();
for (auto const& variable : index) {
/// For now we'll return the pointer, w/o making a copy
/// of the varaiable itself. We never updates variables
/// so we should be OK.
var_list->push_back(variable);
}
return (var_list);
}
BindingVariablePtr
BindingVariableCache::getByName(const std::string& name) {
util::MultiThreadingLock lock(*mutex_);
const auto& index = variables_.get<VariableNameTag>();
auto var_iter = index.find(name);
return (var_iter == index.end() ? BindingVariablePtr() : *var_iter);
}
BindingVariableListPtr
BindingVariableCache::getBySource(const BindingVariable::Source& source) {
util::MultiThreadingLock lock(*mutex_);
BindingVariableListPtr var_list(new BindingVariableList());
const auto& index = variables_.get<VariableSourceTag>();
auto lower_limit = index.lower_bound(source);
auto upper_limit = index.upper_bound(source);
for (auto var_iter = lower_limit; var_iter != upper_limit; ++var_iter) {
var_list->push_back(*var_iter);
}
return (var_list);
}
} // end of namespace lease_cmds
} // end of namespace isc

View File

@@ -0,0 +1,231 @@
// Copyright (C) 2025 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Kea Hooks Basic
// Commercial End User License Agreement v2.0. See COPYING file in the premium/
// directory.
#ifndef BINDING_VARIABLES_H
#define BINDING_VARIABLES_H
#include <cc/base_stamped_element.h>
#include <cc/cfg_to_element.h>
#include <cc/data.h>
#include <eval/evaluate.h>
#include <eval/token.h>
#include <dhcp/pkt.h>
#include <boost/scoped_ptr.hpp>
#include <boost/multi_index_container.hpp>
#include <boost/multi_index/mem_fun.hpp>
#include <boost/multi_index/hashed_index.hpp>
#include <boost/multi_index/ordered_index.hpp>
#include <boost/multi_index/sequenced_index.hpp>
namespace isc {
namespace lease_cmds {
/// @brief Embodies a named expression, whose output when
/// evaluated can be stored in a lease's user-context.
class BindingVariable : public isc::data::CfgToElement {
public:
/// @brief Specifies the packet that the expression should be
/// evaluated against.
enum Source {
QUERY,
RESPONSE
};
/// @brief Constructor
///
/// @param name name of the variable, must be unique. Used
/// both as the key and as the label for the value in the output.
/// @param expression_str Evaluation expression text.
/// @param source Source packet the expression should be
/// evaluated against, either QUERY or RESPONSE.
/// @param family Protocol family of the expression, either
/// AF_INET or AF_INET6.
///
/// During construction the expression string is parsed for the
/// protocol family.
/// @throw BadValue if name if empty, or expression string fails
/// to parse.
explicit BindingVariable(const std::string& name,
const std::string& expression_str,
const Source& source,
uint32_t family);
/// @brief Destructor
virtual ~BindingVariable() = default;
/// @brief Evaluate the variable against the given packet.
///
/// @param packet Pointer to the target packet.
/// @return string result of the evaluation.
/// @throw BadValue if an evaluation error occurs.
std::string evaluate(dhcp::PktPtr packet) const;
/// @brief Fetches the variable's name.
///
/// @return string containing the name.
std::string getName() const {
return (name_);
}
/// @brief Fetches the variable's pre-parsed expression string.
///
/// @return string containing the expression.
std::string getExpressionStr() const {
return (expression_str_);
}
/// @brief Fetches the variable's parsed expression.
///
/// @return pointer to the expression.
dhcp::ExpressionPtr getExpression() const {
return (expression_);
}
/// @brief Fetches the variable's packet source.
///
/// @return Source of the packet.
Source getSource() const {
return (source_);
}
/// @brief Fetches the variable's protocol family.
///
/// @return Family of the packet i.e AF_INET or AF_INET6.
uint32_t getFamily() const {
return (family_);
}
/// @todo Not sure we need CfgElement derivation
virtual data::ElementPtr toElement() const;
private:
/// @param source Source packet the expression should be
/// evaluated against, either QUERY or RESPONSE.
/// @param family Protocol family of the expression, either
/// @brief name of the variable.
std::string name_;
/// @brief Evaluation expression text.
std::string expression_str_;
/// @brief Source packet the expression should be evaluated against.
Source source_;
/// @brief Protocol family AF_INET or AF_INET6.
uint32_t family_;
/// @brief Parsed evaluation expression.
dhcp::ExpressionPtr expression_;
};
/// @brief Defines a shared pointer to a BindingVariable.
typedef boost::shared_ptr<BindingVariable> BindingVariablePtr;
/// @brief Defines a list of BindingVariablePtr instances.
typedef std::list<BindingVariablePtr> BindingVariableList;
/// @brief Defines a pointer to a list of BindingVariablePtrs.
typedef boost::shared_ptr<BindingVariableList> BindingVariableListPtr;
/// @brief Tag for the sequence index.
struct VariableSequenceTag { };
/// @brief Tag for the name index.
struct VariableNameTag { };
/// @brief Tag for the source index.
struct VariableSourceTag { };
/// @brief the client class multi-index.
typedef boost::multi_index_container<
BindingVariablePtr,
boost::multi_index::indexed_by<
// First index is by sequence. -- Do we need this one?
boost::multi_index::sequenced<
boost::multi_index::tag<VariableSequenceTag>
>,
// Second index is by name.
boost::multi_index::hashed_unique<
boost::multi_index::tag<VariableNameTag>,
boost::multi_index::const_mem_fun<BindingVariable,
std::string,
&BindingVariable::getName>
>,
// Third index is by source.
boost::multi_index::ordered_non_unique<
boost::multi_index::tag<VariableSourceTag>,
boost::multi_index::const_mem_fun<BindingVariable,
BindingVariable::Source,
&BindingVariable::getSource>
>
>
> BindingVariableContainer;
/// @brief BindingVariableCache stores binding variables.
///
/// Wrapper around the variable container that provides
/// thread-safe access and time-stamped management. The
/// later is available if/when supported scopes beyond
/// global are added.
class BindingVariableCache : public data::BaseStampedElement {
public:
/// @brief Constructor
BindingVariableCache();
/// @brief Destructor
virtual ~BindingVariableCache() = default;
/// @brief Adds (or replaces) the variable in the cache.
///
/// @param variable pointer to the variable to store.
void cacheVariable(BindingVariablePtr variable);
/// @brief Delete all the entries in the cache.
void clear();
/// @brief Returns number of entries in the cache.
size_t size();
/// @brief Returns the last time the cache was flushed (or
/// the time it was created if it has never been flushed).
boost::posix_time::ptime getLastFlushTime();
/// @brief Fetches all of the binding variables in the order
/// they were added to the cache.
///
/// @return Pointer to a list of the BindingVariables.
BindingVariableListPtr getAll();
/// @brief Fetches a binding variable by name
///
/// @return A pointer to the variable or an empty pointer
/// if no match is found.
BindingVariablePtr getByName(const std::string& name);
/// @brief Fetches all of the binding variables in the order
/// they were added to the cache that use a specific source.
///
/// @return Pointer to a list of the BindingVariables.
BindingVariableListPtr getBySource(const BindingVariable::Source& source);
private:
/// @brief Variable storage container.
BindingVariableContainer variables_;
/// @brief The mutex used to protect internal state.
const boost::scoped_ptr<std::mutex> mutex_;
};
/// @brief Defines a shared pointer to a BindingVariableCache.
typedef boost::shared_ptr<BindingVariableCache> BindingVariableCachePtr;
} // end of namespace lease_cmds
} // end of namespace isc
#endif

View File

@@ -30,6 +30,7 @@ lease_cmds_unittests_SOURCES = run_unittests.cc
lease_cmds_unittests_SOURCES += lease_cmds_unittest.h lease_cmds_unittest.cc
lease_cmds_unittests_SOURCES += lease_cmds4_unittest.cc
lease_cmds_unittests_SOURCES += lease_cmds6_unittest.cc
lease_cmds_unittests_SOURCES += binding_variables_unittest.cc
lease_cmds_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES) $(LOG4CPLUS_INCLUDES)
@@ -37,7 +38,8 @@ lease_cmds_unittests_LDFLAGS = $(AM_LDFLAGS) $(CRYPTO_LDFLAGS) $(GTEST_LDFLAGS)
lease_cmds_unittests_CXXFLAGS = $(AM_CXXFLAGS)
lease_cmds_unittests_LDADD = $(top_builddir)/src/lib/dhcpsrv/libkea-dhcpsrv.la
lease_cmds_unittests_LDADD = $(top_builddir)/src/hooks/dhcp/lease_cmds/liblease_cmds.la
lease_cmds_unittests_LDADD += $(top_builddir)/src/lib/dhcpsrv/libkea-dhcpsrv.la
lease_cmds_unittests_LDADD += $(top_builddir)/src/lib/process/libkea-process.la
lease_cmds_unittests_LDADD += $(top_builddir)/src/lib/eval/libkea-eval.la
lease_cmds_unittests_LDADD += $(top_builddir)/src/lib/dhcp_ddns/libkea-dhcp_ddns.la

View File

@@ -0,0 +1,234 @@
// Copyright (C) 2025 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
#include <config.h>
#include <binding_variables.h>
#include <exceptions/exceptions.h>
#include <cc/data.h>
#include <testutils/gtest_utils.h>
#include <testutils/user_context_utils.h>
#include <testutils/multi_threading_utils.h>
#include <gtest/gtest.h>
using namespace std;
using namespace isc;
using namespace isc::data;
using namespace isc::test;
using namespace isc::lease_cmds;
namespace {
/// @brief Test BindingVariable valid construction scenarios.
TEST(BindingVariableTest, validConstructor) {
BindingVariablePtr bv;
struct Scenario {
uint32_t line_;
std::string name_;
std::string expression_str_;
BindingVariable::Source source_;
uint32_t family_;
};
std::string valid_v4_exp = "pkt4.mac";
std::string valid_v6_exp = "pkt6.transid";
std::list<Scenario> scenarios = {
{
__LINE__, "my-var", valid_v4_exp, BindingVariable::QUERY ,AF_INET
},
{
__LINE__, "my-var", valid_v4_exp, BindingVariable::RESPONSE ,AF_INET
},
{
__LINE__, "my-var", valid_v6_exp, BindingVariable::QUERY, AF_INET6
},
{
__LINE__, "my-var", valid_v6_exp, BindingVariable::RESPONSE, AF_INET6
}
};
for (auto const& scenario : scenarios) {
ASSERT_NO_THROW_LOG(bv.reset(new BindingVariable(scenario.name_,
scenario.expression_str_,
scenario.source_,
scenario.family_)));
ASSERT_TRUE(bv);
EXPECT_EQ(bv->getName(), scenario.name_);
EXPECT_EQ(bv->getExpressionStr(), scenario.expression_str_);
ASSERT_TRUE(bv->getExpression());
EXPECT_EQ(bv->getSource(), scenario.source_);
EXPECT_EQ(bv->getFamily(), scenario.family_);
}
}
/// @brief Test BindingVariable invalid construction scenarios.
TEST(BindingVariableTest, invalidConstructor) {
BindingVariablePtr bv;
struct Scenario {
uint32_t line_;
std::string name_;
std::string expression_str_;
uint32_t family_;
std::string expected_error_;
};
std::string valid_v4_exp = "pkt4.mac";
std::string valid_v6_exp = "pkt6.transid";
std::list<Scenario> scenarios = {
{
__LINE__, "", valid_v4_exp, AF_INET,
"BindingVariable - name cannot be empty"
},
{
__LINE__, "my-var", "", AF_INET,
"BindingVariable - 'my-var' expression_str cannot be empty"
},
{
__LINE__, "my-var", "bogus + stuff", AF_INET,
"BindingVariable - 'my-var', error parsing expression: "
"'bogus + stuff' : <string>:1.1: Invalid character: b"
},
{
__LINE__, "my-var", valid_v4_exp, 99,
"BindingVariable - 'my-var', invalid family: 99"
},
{
__LINE__, "my-var", valid_v4_exp, AF_INET6,
"BindingVariable - 'my-var', error parsing expression: "
"'pkt4.mac' : <string>:1.1-4: pkt4 can only be used in DHCPv4."
},
{
__LINE__, "my-var", valid_v6_exp, AF_INET,
"BindingVariable - 'my-var', error parsing expression: "
"'pkt6.transid' : <string>:1.1-4: pkt6 can only be used in DHCPv6."
}
};
for (auto const& scenario : scenarios) {
ASSERT_THROW_MSG(bv.reset(new BindingVariable(scenario.name_,
scenario.expression_str_,
BindingVariable::QUERY,
scenario.family_)),
BadValue, scenario.expected_error_);
}
}
TEST(BindingVariableCacheTest, basics) {
auto ref_time = boost::posix_time::second_clock::local_time();
// Create a new cache.
BindingVariableCachePtr cache(new BindingVariableCache());
// Verify last flush time has been set to approximately now.
EXPECT_GE(cache->getLastFlushTime(), ref_time);
ref_time = cache->getLastFlushTime();
// Ensure getters return empty lists or pointers without harm.
BindingVariableListPtr var_list;
ASSERT_NO_THROW_LOG(var_list = cache->getAll());
ASSERT_TRUE(var_list);
EXPECT_EQ(var_list->size(), 0);
BindingVariablePtr var;
ASSERT_NO_THROW_LOG(var = cache->getByName("foo"));
ASSERT_FALSE(var);
ASSERT_NO_THROW_LOG(var_list = cache->getBySource(BindingVariable::QUERY));
ASSERT_TRUE(var_list);
EXPECT_EQ(var_list->size(), 0);
ASSERT_NO_THROW_LOG(var_list = cache->getBySource(BindingVariable::RESPONSE));
ASSERT_TRUE(var_list);
EXPECT_EQ(var_list->size(), 0);
// Add four variables.
std::string valid_v6_exp = "pkt6.transid";
BindingVariableList ref_list;
ref_list.push_back(BindingVariablePtr(new BindingVariable("one", valid_v6_exp,
BindingVariable::QUERY,
AF_INET6)));
ref_list.push_back(BindingVariablePtr(new BindingVariable("two", valid_v6_exp,
BindingVariable::RESPONSE,
AF_INET6)));
ref_list.push_back(BindingVariablePtr(new BindingVariable("three", valid_v6_exp,
BindingVariable::RESPONSE,
AF_INET6)));
ref_list.push_back(BindingVariablePtr(new BindingVariable("four", valid_v6_exp,
BindingVariable::QUERY,
AF_INET6)));
for (auto const& ref_iter : ref_list) {
ASSERT_NO_THROW_LOG(cache->cacheVariable(ref_iter));
}
// Make sure getAll() returns all four in order added.
ASSERT_NO_THROW_LOG(var_list = cache->getAll());
ASSERT_TRUE(var_list);
EXPECT_EQ(var_list->size(), 4);
auto var_iter = var_list->begin();
for (auto const& ref_iter : ref_list) {
EXPECT_EQ((*var_iter)->getName(), ref_iter->getName());
EXPECT_EQ((*var_iter)->getSource(), ref_iter->getSource());
++var_iter;
}
// Make sure getByName() can return each one.
for (auto const& ref_iter : ref_list) {
ASSERT_NO_THROW_LOG(var = cache->getByName(ref_iter->getName()));
ASSERT_TRUE(var);
EXPECT_EQ(var->getName(), ref_iter->getName());
}
// Make sure getBySource() works for QUERY.
ASSERT_NO_THROW_LOG(var_list = cache->getBySource(BindingVariable::QUERY));
ASSERT_TRUE(var_list);
ASSERT_EQ(var_list->size(), 2);
var_iter = var_list->begin();
for (auto const& ref_iter : ref_list) {
if (ref_iter->getSource() == BindingVariable::QUERY) {
EXPECT_EQ((*var_iter)->getName(), ref_iter->getName());
++var_iter;
}
}
// Make sure getBySource() works for RESPONSE.
ASSERT_NO_THROW_LOG(var_list = cache->getBySource(BindingVariable::RESPONSE));
ASSERT_TRUE(var_list);
ASSERT_EQ(var_list->size(), 2);
var_iter = var_list->begin();
for (auto const& ref_iter : ref_list) {
if (ref_iter->getSource() == BindingVariable::RESPONSE) {
EXPECT_EQ((*var_iter)->getName(), ref_iter->getName());
++var_iter;
}
}
// Make sure last flush time hasn't been touched.
EXPECT_EQ(cache->getLastFlushTime(), ref_time);
// Sleep 1s so we can check flush time gets updated.
usleep(1000000);
ASSERT_NO_THROW_LOG(cache->clear());
EXPECT_EQ(cache->size(), 0);
EXPECT_GT(cache->getLastFlushTime(), ref_time);
}
} // end of anonymous namespace