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.
}
bool
-BindingVariableCache::cacheVariable(BindingVariablePtr variable) {
+BindingVariableCache::add(BindingVariablePtr variable) {
util::MultiThreadingLock lock(*mutex_);
auto retpair = variables_.push_back(variable);
return(retpair.second);
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
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 {
///
/// @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();
/// @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
#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>
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.
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);
}
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.
},
{
__LINE__, "my-var", valid_v6_exp, BindingVariable::RESPONSE, AF_INET6
+ },
+ {
+ __LINE__, "my-var", "'woo hoo'", BindingVariable::RESPONSE, AF_INET6
}
};
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.
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);
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);
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
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.