/// @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_
+ isc_throw(BadValue, "BindingVariable - '" << name_
<< "' expression_str cannot be empty");
}
if (family_ != AF_INET && family_ != AF_INET6) {
- isc_throw(BadValue, "BindingVariable - '" << name_
+ isc_throw(BadValue, "BindingVariable - '" << name_
<< "', invalid family: " << family_);
}
}
}
+const data::SimpleKeywords
+BindingVariable::CONFIG_KEYWORDS =
+{
+ {"name", Element::string},
+ {"expression", Element::string},
+ {"source", Element::string}
+};
+
+BindingVariablePtr
+BindingVariable::parse(data::ConstElementPtr config, uint16_t family) {
+ // Note checkKeywords() will throw DhcpConfigError if there is a problem.
+ SimpleParser::checkKeywords(CONFIG_KEYWORDS, config);
+
+ // Parse members.
+ std::string name = SimpleParser::getString(config, "name");;
+ std::string expression = SimpleParser::getString(config, "expression");;
+ std::string source_str = SimpleParser::getString(config, "source");;
+
+ try {
+ Source source;
+ if (source_str == "query") {
+ source = QUERY;
+ } else if (source_str == "response") {
+ source = RESPONSE;
+ } else {
+ isc_throw(BadValue, "invalid source '" << source_str
+ << "', must be either 'query' or 'response'");
+ }
+
+ // Attempt to create the variable.
+ return (BindingVariablePtr(new BindingVariable(name, expression, source, family)));
+ } catch (const std::exception& ex) {
+ isc_throw(DhcpConfigError, "invalid config: " << ex.what());
+ }
+}
+
std::string
BindingVariable::evaluate(PktPtr packet) const {
try {
} catch (const std::exception& ex) {
isc_throw(BadValue, "BindingVariable - " << name_ << ", error evaluating expression: ["
<< expression_str_ << "] : " << ex.what());
- }
+ }
}
ElementPtr
BindingVariable::toElement() const {
ElementPtr map = Element::createMap();
map->set("name", Element::create(name_));
- map->set("expression_str", Element::create(expression_str_));
+ map->set("expression", Element::create(expression_str_));
map->set("source", Element::create((source_ == QUERY ? "query" : "response")));
return (map);
}
-BindingVariableCache::BindingVariableCache()
+BindingVariableCache::BindingVariableCache()
: variables_(), mutex_(new std::mutex) {
}
return(retpair.second);
}
-void
+void
BindingVariableCache::clear() {
util::MultiThreadingLock lock(*mutex_);
// Discard contents.
return (var_list);
}
-
+
} // end of namespace lease_cmds
} // end of namespace isc
#include <cc/base_stamped_element.h>
#include <cc/cfg_to_element.h>
#include <cc/data.h>
+#include <cc/simple_parser.h>
#include <eval/evaluate.h>
#include <eval/token.h>
#include <dhcp/pkt.h>
namespace isc {
namespace lease_cmds {
+
+// Forward declaration for pointer.
+class BindingVariable;
+
+/// @brief Defines a shared pointer to a BindingVariable.
+typedef boost::shared_ptr<BindingVariable> BindingVariablePtr;
+
/// @brief Embodies a named expression, whose output when
/// evaluated can be stored in a lease's user-context.
class BindingVariable : public isc::data::CfgToElement {
RESPONSE
};
+ /// @brief List of valid configurable parameters for a BindingVariable.
+ static const data::SimpleKeywords CONFIG_KEYWORDS;
+
/// @brief Constructor
///
/// @param name name of the variable, must be unique. Used
/// @brief Destructor
virtual ~BindingVariable() = default;
+ /// @brief Parses configuration elements into a BindingVarable instance.
+ ///
+ /// @param config Map Element containing parameters for a single binding variable.
+ /// @param family Protocol family of the variable, either AF_INET or AF_INET6.
+ /// @return Pointer to the newly created BindingVariable instacne.
+ /// @throw DhcpConfigError if configuration parameters are invalid.
+ static BindingVariablePtr parse(data::ConstElementPtr config, uint16_t family);
+
/// @brief Evaluate the variable against the given packet.
///
/// @param packet Pointer to the target packet.
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;
using namespace std;
using namespace isc;
+using namespace isc::dhcp;
using namespace isc::data;
using namespace isc::test;
namespace {
+#define SCOPED_LINE(line) \
+ std::stringstream ss; \
+ ss << "Scenario at line: " << line; \
+ SCOPED_TRACE(ss.str());
+
/// @brief Test BindingVariable valid construction scenarios.
TEST(BindingVariableTest, validConstructor) {
BindingVariablePtr bv;
};
for (auto const& scenario : scenarios) {
+ SCOPED_LINE(scenario.line_);
ASSERT_NO_THROW_LOG(bv.reset(new BindingVariable(scenario.name_,
scenario.expression_str_,
scenario.source_,
};
for (auto const& scenario : scenarios) {
+ SCOPED_LINE(scenario.line_);
ASSERT_THROW_MSG(bv.reset(new BindingVariable(scenario.name_,
scenario.expression_str_,
BindingVariable::QUERY,
ASSERT_NO_THROW_LOG(elem = bv->toElement());
std::stringstream ss;
elem->toJSON(ss);
- std::string expected_json = "{ \"expression_str\": \"pkt4.mac\","
+ std::string expected_json = "{ \"expression\": \"pkt4.mac\","
" \"name\": \"myvar\", \"source\": \"query\" }";
EXPECT_EQ(ss.str(), expected_json);
}
+/// @brief Checks BindingVariable::parse with valid scenarios.
+TEST(BindingVariableTest, validParsing) {
+ struct Scenario {
+ uint32_t line_;
+ std::string config_;
+ uint32_t family_;
+ std::string expected_json_;
+ };
+
+ std::list<Scenario> scenarios = {
+ {
+ __LINE__,
+ R"({ "name": "one", "expression" : "pkt4.mac", "source": "query"})",
+ AF_INET,
+ "{ \"expression\": \"pkt4.mac\", \"name\": \"one\", \"source\": \"query\" }"
+ },
+ {
+ __LINE__,
+ R"({ "name": "two", "expression" : "pkt4.mac", "source": "response"})",
+ AF_INET,
+ "{ \"expression\": \"pkt4.mac\", \"name\": \"two\", \"source\": \"response\" }"
+ },
+ {
+ __LINE__,
+ R"({ "name": "three", "expression" : "pkt6.transid", "source": "query"})",
+ AF_INET6,
+ "{ \"expression\": \"pkt6.transid\", \"name\": \"three\", \"source\": \"query\" }"
+ },
+ {
+ __LINE__,
+ R"({ "name": "four", "expression" : "pkt6.transid", "source": "response"})",
+ AF_INET6,
+ "{ \"expression\": \"pkt6.transid\", \"name\": \"four\", \"source\": \"response\" }"
+ }
+ };
+
+ for (auto const& scenario : scenarios) {
+ SCOPED_LINE(scenario.line_);
+ BindingVariablePtr var;
+ ElementPtr config;
+ ASSERT_NO_THROW_LOG(config = Element::fromJSON(scenario.config_));
+ ASSERT_NO_THROW_LOG(var = BindingVariable::parse(config, scenario.family_));
+ ASSERT_TRUE(var);
+ std::stringstream js;
+ var->toElement()->toJSON(js);
+ EXPECT_EQ(js.str(), scenario.expected_json_);
+ }
+}
+
+/// @brief Checks BindingVariable::parse with invalid scenarios.
+TEST(BindingVariableTest, invalidParsing) {
+ struct Scenario {
+ uint32_t line_;
+ std::string config_;
+ uint32_t family_;
+ std::string expected_error_;
+ };
+
+ std::list<Scenario> scenarios = {
+ {
+ __LINE__,
+ R"({ "name": "", "expression" : "pkt4.mac", "source": "query"})",
+ AF_INET,
+ "invalid config: BindingVariable - name cannot be empty"
+ },
+ {
+ __LINE__,
+ R"({ "name": "myvar", "expression" : "", "source": "response"})",
+ AF_INET,
+ "invalid config: BindingVariable - 'myvar' expression_str cannot be empty"
+ },
+ {
+ __LINE__,
+ R"({ "name": "myvar", "expression" : "pkt5.bogus", "source": "query"})",
+ AF_INET,
+ "invalid config: BindingVariable - 'myvar', error parsing expression:"
+ " 'pkt5.bogus' : <string>:1.4: syntax error, unexpected integer, expecting ."
+ },
+ {
+ __LINE__,
+ R"({ "name": "myvar", "expression" : "pkt4.mac", "source": "BOGUS"})",
+ AF_INET,
+ "invalid config: invalid source 'BOGUS', must be either 'query' or 'response'"
+ },
+ {
+ __LINE__,
+ R"({ "name": "myvar", "expression" : "pkt4.mac", "source": "query", "bogus" : "foo" })",
+ AF_INET,
+ "spurious 'bogus' parameter"
+ }
+ };
+
+ for (auto const& scenario : scenarios) {
+ SCOPED_LINE(scenario.line_);
+ ElementPtr config;
+ ASSERT_NO_THROW_LOG(config = Element::fromJSON(scenario.config_));
+ ASSERT_THROW_MSG(BindingVariable::parse(config, scenario.family_),
+ DhcpConfigError, scenario.expected_error_);
+ }
+}
+
/// @brief Verifies basic operation of the cache including
/// construction, all getters and cache clearing.
TEST(BindingVariableCacheTest, basics) {