mirror of
https://gitlab.isc.org/isc-projects/kea
synced 2025-08-29 13:07:50 +00:00
[#1710] Added lifetimes to v6 client classes
kea-dhcp6 client classes now support valid and preferred lifetime triplets. Need to update ARM and CB yet. /doc/examples/kea6/all-keys.json Updated /src/bin/dhcp6/dhcp6_lexer.ll b/src/bin/dhcp6/dhcp6_lexer.ll Added preferred and valid lifetime parameters to client class /src/bin/dhcp6/dhcp6_parser.yy Added preferred and valid lifetime parameters to client class src/bin/dhcp6/tests/config_parser_unittest.cc TEST_F(Dhcp6ParserTest, clientClassValidPreferredLifetime) - new test /src/bin/dhcp6/tests/parser_unittest.cc Added use of EXPECT_NO_THROW_LOG to ease finding JSON errors /src/lib/dhcpsrv/alloc_engine.* AllocEngine::getLifetimes6() - new function Lease6Ptr AllocEngine::createLease6() - calls new function /src/lib/dhcpsrv/client_class_def.* /src/lib/dhcpsrv/parsers/client_class_def_parser.cc ClientClassDef - added support for preferred lifetime triplet /src/lib/dhcpsrv/tests/alloc_engine6_unittest.cc TEST_F(AllocEngine6Test, getValidLifetime) TEST_F(AllocEngine6Test, getPreferredLifetime) - new tests /src/lib/dhcpsrv/tests/client_class_def_parser_unittest.cc TEST_F(ClientClassDefParserTest, preferredLifetimeTests) - new test /src/lib/dhcpsrv/tests/client_class_def_unittest.cc updated tests
This commit is contained in:
parent
5400f23dea
commit
d98770b09c
@ -22,7 +22,25 @@
|
||||
|
||||
// Class selection expression. The DHCP packet is assigned to this
|
||||
// class when the given expression evaluates to true.
|
||||
"test": "member('HA_server1')"
|
||||
"test": "member('HA_server1')",
|
||||
|
||||
// Class valid lifetime.
|
||||
"valid-lifetime": 6000,
|
||||
|
||||
// Class min valid lifetime.
|
||||
"min-valid-lifetime": 4000,
|
||||
|
||||
// Class max valid lifetime.
|
||||
"max-valid-lifetime": 8000,
|
||||
|
||||
// Class preferred lifetime.
|
||||
"preferred-lifetime": 7000,
|
||||
|
||||
// Class min preferred lifetime.
|
||||
"min-preferred-lifetime": 5000,
|
||||
|
||||
// Class max preferred lifetime.
|
||||
"max-preferred-lifetime": 9000
|
||||
},
|
||||
{
|
||||
// Second class name.
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -832,6 +832,7 @@ ControlCharacterFill [^"\\]|\\["\\/bfnrtu]
|
||||
case isc::dhcp::Parser6Context::DHCP6:
|
||||
case isc::dhcp::Parser6Context::SUBNET6:
|
||||
case isc::dhcp::Parser6Context::SHARED_NETWORK:
|
||||
case isc::dhcp::Parser6Context::CLIENT_CLASSES:
|
||||
return isc::dhcp::Dhcp6Parser::make_PREFERRED_LIFETIME(driver.loc_);
|
||||
default:
|
||||
return isc::dhcp::Dhcp6Parser::make_STRING("preferred-lifetime", driver.loc_);
|
||||
@ -843,6 +844,7 @@ ControlCharacterFill [^"\\]|\\["\\/bfnrtu]
|
||||
case isc::dhcp::Parser6Context::DHCP6:
|
||||
case isc::dhcp::Parser6Context::SUBNET6:
|
||||
case isc::dhcp::Parser6Context::SHARED_NETWORK:
|
||||
case isc::dhcp::Parser6Context::CLIENT_CLASSES:
|
||||
return isc::dhcp::Dhcp6Parser::make_MIN_PREFERRED_LIFETIME(driver.loc_);
|
||||
default:
|
||||
return isc::dhcp::Dhcp6Parser::make_STRING("min-preferred-lifetime", driver.loc_);
|
||||
@ -854,6 +856,7 @@ ControlCharacterFill [^"\\]|\\["\\/bfnrtu]
|
||||
case isc::dhcp::Parser6Context::DHCP6:
|
||||
case isc::dhcp::Parser6Context::SUBNET6:
|
||||
case isc::dhcp::Parser6Context::SHARED_NETWORK:
|
||||
case isc::dhcp::Parser6Context::CLIENT_CLASSES:
|
||||
return isc::dhcp::Dhcp6Parser::make_MAX_PREFERRED_LIFETIME(driver.loc_);
|
||||
default:
|
||||
return isc::dhcp::Dhcp6Parser::make_STRING("max-preferred-lifetime", driver.loc_);
|
||||
@ -865,6 +868,7 @@ ControlCharacterFill [^"\\]|\\["\\/bfnrtu]
|
||||
case isc::dhcp::Parser6Context::DHCP6:
|
||||
case isc::dhcp::Parser6Context::SUBNET6:
|
||||
case isc::dhcp::Parser6Context::SHARED_NETWORK:
|
||||
case isc::dhcp::Parser6Context::CLIENT_CLASSES:
|
||||
return isc::dhcp::Dhcp6Parser::make_VALID_LIFETIME(driver.loc_);
|
||||
default:
|
||||
return isc::dhcp::Dhcp6Parser::make_STRING("valid-lifetime", driver.loc_);
|
||||
@ -876,6 +880,7 @@ ControlCharacterFill [^"\\]|\\["\\/bfnrtu]
|
||||
case isc::dhcp::Parser6Context::DHCP6:
|
||||
case isc::dhcp::Parser6Context::SUBNET6:
|
||||
case isc::dhcp::Parser6Context::SHARED_NETWORK:
|
||||
case isc::dhcp::Parser6Context::CLIENT_CLASSES:
|
||||
return isc::dhcp::Dhcp6Parser::make_MIN_VALID_LIFETIME(driver.loc_);
|
||||
default:
|
||||
return isc::dhcp::Dhcp6Parser::make_STRING("min-valid-lifetime", driver.loc_);
|
||||
@ -887,6 +892,7 @@ ControlCharacterFill [^"\\]|\\["\\/bfnrtu]
|
||||
case isc::dhcp::Parser6Context::DHCP6:
|
||||
case isc::dhcp::Parser6Context::SUBNET6:
|
||||
case isc::dhcp::Parser6Context::SHARED_NETWORK:
|
||||
case isc::dhcp::Parser6Context::CLIENT_CLASSES:
|
||||
return isc::dhcp::Dhcp6Parser::make_MAX_VALID_LIFETIME(driver.loc_);
|
||||
default:
|
||||
return isc::dhcp::Dhcp6Parser::make_STRING("max-valid-lifetime", driver.loc_);
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@ -2271,6 +2271,12 @@ client_class_param: client_class_name
|
||||
| option_data_list
|
||||
| user_context
|
||||
| comment
|
||||
| preferred_lifetime
|
||||
| min_preferred_lifetime
|
||||
| max_preferred_lifetime
|
||||
| valid_lifetime
|
||||
| min_valid_lifetime
|
||||
| max_valid_lifetime
|
||||
| unknown_map_entry
|
||||
;
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
// A Bison parser, made by GNU Bison 3.7.6.
|
||||
// A Bison parser, made by GNU Bison 3.7.2.
|
||||
|
||||
// Locations for Bison parsers in C++
|
||||
|
||||
// Copyright (C) 2002-2015, 2018-2021 Free Software Foundation, Inc.
|
||||
// Copyright (C) 2002-2015, 2018-2020 Free Software Foundation, Inc.
|
||||
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
@ -15,7 +15,7 @@
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// As a special exception, you may create a larger work that contains
|
||||
// part or all of the Bison parser skeleton and distribute that work
|
||||
|
@ -29,6 +29,7 @@
|
||||
#include <hooks/hooks_manager.h>
|
||||
#include <process/config_ctl_info.h>
|
||||
#include <stats/stats_mgr.h>
|
||||
#include <testutils/gtest_utils.h>
|
||||
#include <testutils/log_utils.h>
|
||||
#include <util/chrono_time_utils.h>
|
||||
|
||||
@ -8090,4 +8091,66 @@ TEST_F(Dhcp6ParserTest, multiThreadingSettings) {
|
||||
<< " actual: " << *(cfg) << std::endl;
|
||||
}
|
||||
|
||||
// Verifies that client class definitions may specify
|
||||
// valid and preferred lifetime triplets.
|
||||
TEST_F(Dhcp6ParserTest, clientClassValidPreferredLifetime) {
|
||||
string config = "{ " + genIfaceConfig() + "," +
|
||||
"\"client-classes\" : [ \n"
|
||||
" { \n"
|
||||
" \"name\": \"one\", \n"
|
||||
" \"min-valid-lifetime\": 1000, \n"
|
||||
" \"valid-lifetime\": 2000, \n"
|
||||
" \"max-valid-lifetime\": 3000, \n"
|
||||
" \"min-preferred-lifetime\": 4000, \n"
|
||||
" \"preferred-lifetime\": 5000, \n"
|
||||
" \"max-preferred-lifetime\": 6000 \n"
|
||||
" }, \n"
|
||||
" { \n"
|
||||
" \"name\": \"two\" \n"
|
||||
" } \n"
|
||||
"], \n"
|
||||
"\"subnet6\": [ { \n"
|
||||
" \"pools\": [ { \"pool\": \"2001:db8:1::1 - 2001:db8:1::ffff\" } ],"
|
||||
" \"subnet\": \"2001:db8:1::/64\""
|
||||
" } ] \n"
|
||||
"} \n";
|
||||
|
||||
ConstElementPtr json;
|
||||
ASSERT_NO_THROW_LOG(json = parseDHCP6(config));
|
||||
extractConfig(config);
|
||||
|
||||
ConstElementPtr status;
|
||||
ASSERT_NO_THROW_LOG(status = configureDhcp6Server(srv_, json));
|
||||
ASSERT_TRUE(status);
|
||||
checkResult(status, 0);
|
||||
|
||||
// We check staging config because CfgMgr::commit hasn't been executed.
|
||||
ClientClassDictionaryPtr dictionary;
|
||||
dictionary = CfgMgr::instance().getStagingCfg()->getClientClassDictionary();
|
||||
ASSERT_TRUE(dictionary);
|
||||
EXPECT_EQ(2, dictionary->getClasses()->size());
|
||||
|
||||
// Execute the commit
|
||||
ASSERT_NO_THROW(CfgMgr::instance().commit());
|
||||
|
||||
// Verify that after commit, the current config has the correct dictionary
|
||||
dictionary = CfgMgr::instance().getCurrentCfg()->getClientClassDictionary();
|
||||
ASSERT_TRUE(dictionary);
|
||||
EXPECT_EQ(2, dictionary->getClasses()->size());
|
||||
|
||||
ClientClassDefPtr class_def = dictionary->findClass("one");
|
||||
ASSERT_TRUE(class_def);
|
||||
EXPECT_EQ(class_def->getValid().getMin(), 1000);
|
||||
EXPECT_EQ(class_def->getValid().get(), 2000);
|
||||
EXPECT_EQ(class_def->getValid().getMax(), 3000);
|
||||
|
||||
EXPECT_EQ(class_def->getPreferred().getMin(), 4000);
|
||||
EXPECT_EQ(class_def->getPreferred().get(), 5000);
|
||||
EXPECT_EQ(class_def->getPreferred().getMax(), 6000);
|
||||
|
||||
class_def = dictionary->findClass("two");
|
||||
ASSERT_TRUE(class_def);
|
||||
EXPECT_TRUE(class_def->getValid().unspecified());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
@ -10,6 +10,7 @@
|
||||
#include <dhcpsrv/parsers/simple_parser6.h>
|
||||
#include <testutils/io_utils.h>
|
||||
#include <testutils/user_context_utils.h>
|
||||
#include <testutils/gtest_utils.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <fstream>
|
||||
#include <set>
|
||||
@ -245,7 +246,7 @@ void testFile(const std::string& fname) {
|
||||
|
||||
cout << "Parsing file " << fname << "(" << decommented << ")" << endl;
|
||||
|
||||
EXPECT_NO_THROW(json = Element::fromJSONFile(decommented, true));
|
||||
EXPECT_NO_THROW_LOG(json = Element::fromJSONFile(decommented, true));
|
||||
reference_json = moveComments(json);
|
||||
|
||||
// remove the temporary file
|
||||
|
@ -1906,6 +1906,64 @@ AllocEngine::reuseExpiredLease(Lease6Ptr& expired, ClientContext6& ctx,
|
||||
return (expired);
|
||||
}
|
||||
|
||||
void
|
||||
AllocEngine::getLifetimes6(ClientContext6& ctx, uint32_t& preferred, uint32_t& valid) {
|
||||
// If the triplets are specified in one of our classes use it.
|
||||
// We use the first one we find for each lifetime.
|
||||
Triplet<uint32_t> candidate_preferred;
|
||||
Triplet<uint32_t> candidate_valid;
|
||||
const ClientClasses classes = ctx.query_->getClasses();
|
||||
if (!classes.empty()) {
|
||||
// Let's get class definitions
|
||||
const ClientClassDictionaryPtr& dict =
|
||||
CfgMgr::instance().getCurrentCfg()->getClientClassDictionary();
|
||||
|
||||
// Iterate over the assigned class defintions.
|
||||
int cnt = 0;
|
||||
for (ClientClasses::const_iterator name = classes.cbegin();
|
||||
name != classes.cend() && cnt < 2; ++name) {
|
||||
ClientClassDefPtr cl = dict->findClass(*name);
|
||||
if (candidate_preferred.unspecified() &&
|
||||
(cl && (!cl->getPreferred().unspecified()))) {
|
||||
candidate_preferred = cl->getPreferred();
|
||||
++cnt;
|
||||
}
|
||||
|
||||
if (candidate_valid.unspecified() &&
|
||||
(cl && (!cl->getValid().unspecified()))) {
|
||||
candidate_valid = cl->getValid();
|
||||
++cnt;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If no classes specified preferred lifetime, get it from the subnet.
|
||||
if (!candidate_preferred) {
|
||||
candidate_preferred = ctx.subnet_->getPreferred();
|
||||
}
|
||||
|
||||
// If no classes specified valid lifetime, get it from the subnet.
|
||||
if (!candidate_valid) {
|
||||
candidate_valid = ctx.subnet_->getValid();
|
||||
}
|
||||
|
||||
// Set the outbound parameters to the values we have so far.
|
||||
preferred = candidate_preferred;
|
||||
valid = candidate_valid;
|
||||
|
||||
// If client requested either value, use the requested value(s) bounded by
|
||||
// the candidate triplet(s).
|
||||
if (!ctx.currentIA().hints_.empty()) {
|
||||
if (ctx.currentIA().hints_[0].getPreferred()) {
|
||||
preferred = candidate_preferred.get(ctx.currentIA().hints_[0].getPreferred());
|
||||
}
|
||||
|
||||
if (ctx.currentIA().hints_[0].getValid()) {
|
||||
valid = candidate_valid.get(ctx.currentIA().hints_[0].getValid());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Lease6Ptr AllocEngine::createLease6(ClientContext6& ctx,
|
||||
const IOAddress& addr,
|
||||
uint8_t prefix_len,
|
||||
@ -1915,18 +1973,10 @@ Lease6Ptr AllocEngine::createLease6(ClientContext6& ctx,
|
||||
prefix_len = 128; // non-PD lease types must be always /128
|
||||
}
|
||||
|
||||
uint32_t preferred = ctx.subnet_->getPreferred();
|
||||
if (!ctx.currentIA().hints_.empty() &&
|
||||
ctx.currentIA().hints_[0].getPreferred()) {
|
||||
preferred = ctx.currentIA().hints_[0].getPreferred();
|
||||
preferred = ctx.subnet_->getPreferred().get(preferred);
|
||||
}
|
||||
uint32_t valid = ctx.subnet_->getValid();
|
||||
if (!ctx.currentIA().hints_.empty() &&
|
||||
ctx.currentIA().hints_[0].getValid()) {
|
||||
valid = ctx.currentIA().hints_[0].getValid();
|
||||
valid = ctx.subnet_->getValid().get(valid);
|
||||
}
|
||||
uint32_t preferred = 0;
|
||||
uint32_t valid = 0;
|
||||
getLifetimes6(ctx, preferred, valid);
|
||||
|
||||
Lease6Ptr lease(new Lease6(ctx.currentIA().type_, addr, ctx.duid_,
|
||||
ctx.currentIA().iaid_, preferred,
|
||||
valid, ctx.subnet_->getID(),
|
||||
|
@ -996,6 +996,25 @@ public:
|
||||
return (IPv6Resrv(IPv6Resrv::TYPE_PD, lease.addr_, lease.prefixlen_));
|
||||
}
|
||||
|
||||
public:
|
||||
/// @brief Determines the preferred and valid v6 lease lifetimes.
|
||||
///
|
||||
/// A candidate triplet for both preferred and valid lifetimes will be
|
||||
/// selected from the first class matched to the query which defines the
|
||||
/// value or from the subnet if none do. Classes are searched in the order
|
||||
/// they are assigned to the query.
|
||||
///
|
||||
/// If the client requested a lifetime IA hint, then the
|
||||
/// lifetime values returned will be the requested values bounded by
|
||||
/// the candidate triplets. If the client did not request a value, then
|
||||
/// it simply returns the candidate triplet's default value.
|
||||
///
|
||||
/// @param ctx client context that passes all necessary information. See
|
||||
/// @ref ClientContext6 for details.
|
||||
/// @param [out] preferred set to the preferred lifetime that should be used.
|
||||
/// @param [out] valid set to the valid lifetime that should be used.
|
||||
static void getLifetimes6(ClientContext6& ctx, uint32_t& preferred,
|
||||
uint32_t& valid);
|
||||
private:
|
||||
|
||||
/// @brief Creates a lease and inserts it in LeaseMgr if necessary
|
||||
|
@ -24,7 +24,7 @@ ClientClassDef::ClientClassDef(const std::string& name,
|
||||
: UserContext(), CfgToElement(), StampedElement(), name_(name),
|
||||
match_expr_(match_expr), required_(false), depend_on_known_(false),
|
||||
cfg_option_(cfg_option), next_server_(asiolink::IOAddress::IPV4_ZERO_ADDRESS()),
|
||||
valid_() {
|
||||
valid_(), preferred_() {
|
||||
|
||||
// Name can't be blank
|
||||
if (name_.empty()) {
|
||||
@ -44,7 +44,7 @@ ClientClassDef::ClientClassDef(const ClientClassDef& rhs)
|
||||
match_expr_(ExpressionPtr()), test_(rhs.test_), required_(rhs.required_),
|
||||
depend_on_known_(rhs.depend_on_known_), cfg_option_(new CfgOption()),
|
||||
next_server_(rhs.next_server_), sname_(rhs.sname_),
|
||||
filename_(rhs.filename_), valid_(rhs.valid_) {
|
||||
filename_(rhs.filename_), valid_(rhs.valid_), preferred_(rhs.preferred_) {
|
||||
|
||||
if (rhs.match_expr_) {
|
||||
match_expr_.reset(new Expression());
|
||||
@ -180,16 +180,33 @@ ClientClassDef:: toElement() const {
|
||||
}
|
||||
// Set option-data
|
||||
result->set("option-data", cfg_option_->toElement());
|
||||
if (family != AF_INET) {
|
||||
// Other parameters are DHCPv4 specific
|
||||
return (result);
|
||||
}
|
||||
|
||||
if (family == AF_INET) {
|
||||
// V4 only
|
||||
// Set next-server
|
||||
result->set("next-server", Element::create(next_server_.toText()));
|
||||
// Set server-hostname
|
||||
result->set("server-hostname", Element::create(sname_));
|
||||
// Set boot-file-name
|
||||
result->set("boot-file-name", Element::create(filename_));
|
||||
} else {
|
||||
// V6 only
|
||||
// Set preferred-lifetime
|
||||
if (!preferred_.unspecified()) {
|
||||
result->set("preferred-lifetime",
|
||||
Element::create(static_cast<long long>(preferred_.get())));
|
||||
}
|
||||
|
||||
if (preferred_.getMin() < preferred_.get()) {
|
||||
result->set("min-preferred-lifetime",
|
||||
Element::create(static_cast<long long>(preferred_.getMin())));
|
||||
}
|
||||
|
||||
if (preferred_.getMax() > preferred_.get()) {
|
||||
result->set("max-preferred-lifetime",
|
||||
Element::create(static_cast<long long>(preferred_.getMax())));
|
||||
}
|
||||
}
|
||||
|
||||
// Set valid-lifetime
|
||||
if (!valid_.unspecified()) {
|
||||
@ -244,7 +261,8 @@ ClientClassDictionary::addClass(const std::string& name,
|
||||
asiolink::IOAddress next_server,
|
||||
const std::string& sname,
|
||||
const std::string& filename,
|
||||
const Triplet<uint32_t>& valid) {
|
||||
const Triplet<uint32_t>& valid,
|
||||
const Triplet<uint32_t>& preferred) {
|
||||
ClientClassDefPtr cclass(new ClientClassDef(name, match_expr, cfg_option));
|
||||
cclass->setTest(test);
|
||||
cclass->setRequired(required);
|
||||
@ -255,6 +273,7 @@ ClientClassDictionary::addClass(const std::string& name,
|
||||
cclass->setSname(sname);
|
||||
cclass->setFilename(filename);
|
||||
cclass->setValid(valid);
|
||||
cclass->setPreferred(preferred);
|
||||
addClass(cclass);
|
||||
}
|
||||
|
||||
|
@ -208,6 +208,20 @@ public:
|
||||
valid_ = valid;
|
||||
}
|
||||
|
||||
/// @brief Return preferred-lifetime value
|
||||
///
|
||||
/// @return a triplet containing the preferred lifetime.
|
||||
Triplet<uint32_t> getPreferred() const {
|
||||
return (preferred_);
|
||||
}
|
||||
|
||||
/// @brief Sets new preferred lifetime
|
||||
///
|
||||
/// @param preferred New valid lifetime in seconds.
|
||||
void setPreferred(const Triplet<uint32_t>& preferred) {
|
||||
preferred_ = preferred;
|
||||
}
|
||||
|
||||
/// @brief Unparse a configuration object
|
||||
///
|
||||
/// @return a pointer to unparsed configuration
|
||||
@ -266,6 +280,9 @@ private:
|
||||
|
||||
/// @brief a Triplet (min/default/max) holding allowed valid lifetime values
|
||||
Triplet<uint32_t> valid_;
|
||||
|
||||
/// @brief a Triplet (min/default/max) holding allowed preferred lifetime values
|
||||
Triplet<uint32_t> preferred_;
|
||||
};
|
||||
|
||||
/// @brief a pointer to an ClientClassDef
|
||||
@ -309,6 +326,7 @@ public:
|
||||
/// @param sname server-name value for this class (optional)
|
||||
/// @param filename boot-file-name value for this class (optional)
|
||||
/// @param valid valid-lifetime triplet (optional)
|
||||
/// @param preferred preferred-lifetime triplet (optional)
|
||||
///
|
||||
/// @throw DuplicateClientClassDef if class already exists within the
|
||||
/// dictionary. See @ref dhcp::ClientClassDef::ClientClassDef() for
|
||||
@ -321,7 +339,8 @@ public:
|
||||
asiolink::IOAddress next_server = asiolink::IOAddress("0.0.0.0"),
|
||||
const std::string& sname = std::string(),
|
||||
const std::string& filename = std::string(),
|
||||
const Triplet<uint32_t>&valid = Triplet<uint32_t>());
|
||||
const Triplet<uint32_t>&valid = Triplet<uint32_t>(),
|
||||
const Triplet<uint32_t>&preferred = Triplet<uint32_t>());
|
||||
|
||||
/// @brief Adds a new class to the list
|
||||
///
|
||||
|
@ -205,6 +205,12 @@ ClientClassDefParser::parse(ClientClassDictionaryPtr& class_dictionary,
|
||||
// Parse valid lifetime triplet.
|
||||
Triplet<uint32_t> valid_lft = parseIntTriplet(class_def_cfg, "valid-lifetime");
|
||||
|
||||
Triplet<uint32_t> preferred_lft;
|
||||
if (family != AF_INET) {
|
||||
// Parse preferred lifetime triplet.
|
||||
preferred_lft = parseIntTriplet(class_def_cfg, "preferred-lifetime");
|
||||
}
|
||||
|
||||
// Sanity checks on built-in classes
|
||||
for (auto bn : builtinNames) {
|
||||
if (name == bn) {
|
||||
@ -232,7 +238,8 @@ ClientClassDefParser::parse(ClientClassDictionaryPtr& class_dictionary,
|
||||
try {
|
||||
class_dictionary->addClass(name, match_expr, test, required,
|
||||
depend_on_known, options, defs,
|
||||
user_context, next_server, sname, filename, valid_lft);
|
||||
user_context, next_server, sname, filename,
|
||||
valid_lft, preferred_lft);
|
||||
} catch (const std::exception& ex) {
|
||||
std::ostringstream s;
|
||||
s << "Can't add class: " << ex.what();
|
||||
@ -257,7 +264,11 @@ ClientClassDefParser::checkParametersSupported(const ConstElementPtr& class_def_
|
||||
"test",
|
||||
"option-data",
|
||||
"user-context",
|
||||
"only-if-required" };
|
||||
"only-if-required",
|
||||
"valid-lifetime",
|
||||
"min-valid-lifetime",
|
||||
"max-valid-lifetime" };
|
||||
|
||||
|
||||
// The v4 client class supports additional parameters.
|
||||
static std::set<std::string> supported_params_v4 = { "option-def",
|
||||
@ -265,12 +276,17 @@ ClientClassDefParser::checkParametersSupported(const ConstElementPtr& class_def_
|
||||
"server-hostname",
|
||||
"boot-file-name" };
|
||||
|
||||
// The v6 client class supports additional parameters.
|
||||
static std::set<std::string> supported_params_v6 = { "preferred-lifetime",
|
||||
"min-preferred-lifetime",
|
||||
"max-preferred-lifetime" };
|
||||
|
||||
// Iterate over the specified parameters and check if they are all supported.
|
||||
for (auto name_value_pair : class_def_cfg->mapValue()) {
|
||||
if ((supported_params.count(name_value_pair.first) > 0) ||
|
||||
((family == AF_INET) && (supported_params_v4.count(name_value_pair.first) > 0))) {
|
||||
((family == AF_INET) && (supported_params_v4.count(name_value_pair.first) > 0)) ||
|
||||
((family != AF_INET) && (supported_params_v6.count(name_value_pair.first) > 0))) {
|
||||
continue;
|
||||
|
||||
} else {
|
||||
isc_throw(DhcpConfigError, "unsupported client class parameter '"
|
||||
<< name_value_pair.first << "'");
|
||||
|
@ -5238,6 +5238,262 @@ TEST_F(AllocEngine6Test, renewCacheHostname6) {
|
||||
detailCompareLease(lease, from_mgr);
|
||||
}
|
||||
|
||||
// Verifies that AllocEngine::getLifetimes6() returns the appropriate
|
||||
// valid lifetime value based on the context content.
|
||||
TEST_F(AllocEngine6Test, getValidLifetime) {
|
||||
boost::scoped_ptr<AllocEngine> engine;
|
||||
ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
|
||||
ASSERT_TRUE(engine);
|
||||
|
||||
// Let's make three classes, two with valid-lifetime and one without,
|
||||
// and add them to the dictionary.
|
||||
ClientClassDictionaryPtr dictionary =
|
||||
CfgMgr::instance().getStagingCfg()->getClientClassDictionary();
|
||||
|
||||
ClientClassDefPtr class_def(new ClientClassDef("valid_one", ExpressionPtr()));
|
||||
Triplet<uint32_t> valid_one(50, 100, 150);
|
||||
class_def->setValid(valid_one);
|
||||
dictionary->addClass(class_def);
|
||||
|
||||
class_def.reset(new ClientClassDef("valid_two", ExpressionPtr()));
|
||||
Triplet<uint32_t>valid_two(200, 250, 300);
|
||||
class_def->setValid(valid_two);
|
||||
dictionary->addClass(class_def);
|
||||
|
||||
class_def.reset(new ClientClassDef("valid_unspec", ExpressionPtr()));
|
||||
dictionary->addClass(class_def);
|
||||
|
||||
// Commit our class changes.
|
||||
CfgMgr::instance().commit();
|
||||
|
||||
// Update the subnet's triplet to something more useful.
|
||||
subnet_->setValid(Triplet<uint32_t>(500, 1000, 1500));
|
||||
|
||||
// Describes a test scenario.
|
||||
struct Scenario {
|
||||
std::string desc_; // descriptive text for logging
|
||||
std::vector<std::string> classes_; // class list of assigned classes
|
||||
uint32_t requested_lft_; // use as option 51 is > 0
|
||||
uint32_t exp_valid_; // expected lifetime
|
||||
};
|
||||
|
||||
// Scenarios to test.
|
||||
std::vector<Scenario> scenarios = {
|
||||
{
|
||||
"no classes, no hint",
|
||||
{},
|
||||
0,
|
||||
subnet_->getValid()
|
||||
},
|
||||
{
|
||||
"no classes, hint",
|
||||
{},
|
||||
subnet_->getValid().getMin() + 50,
|
||||
subnet_->getValid().getMin() + 50
|
||||
},
|
||||
{
|
||||
"no classes, hint too small",
|
||||
{},
|
||||
subnet_->getValid().getMin() - 50,
|
||||
subnet_->getValid().getMin()
|
||||
},
|
||||
{
|
||||
"no classes, hint too big",
|
||||
{},
|
||||
subnet_->getValid().getMax() + 50,
|
||||
subnet_->getValid().getMax()
|
||||
},
|
||||
{
|
||||
"class unspecified, no hint",
|
||||
{ "valid_unspec" },
|
||||
0,
|
||||
subnet_->getValid()
|
||||
},
|
||||
{
|
||||
"from last class, no hint",
|
||||
{ "valid_unspec", "valid_one" },
|
||||
0,
|
||||
valid_one.get()
|
||||
},
|
||||
{
|
||||
"from first class, no hint",
|
||||
{ "valid_two", "valid_one" },
|
||||
0,
|
||||
valid_two.get()
|
||||
},
|
||||
{
|
||||
"class plus hint",
|
||||
{ "valid_one" },
|
||||
valid_one.getMin() + 25,
|
||||
valid_one.getMin() + 25
|
||||
},
|
||||
{
|
||||
"class plus hint too small",
|
||||
{ "valid_one" },
|
||||
valid_one.getMin() - 25,
|
||||
valid_one.getMin()
|
||||
},
|
||||
{
|
||||
"class plus hint too big",
|
||||
{ "valid_one" },
|
||||
valid_one.getMax() + 25,
|
||||
valid_one.getMax()
|
||||
}
|
||||
};
|
||||
|
||||
// Iterate over the scenarios and verify the correct outcome.
|
||||
for (auto scenario : scenarios) {
|
||||
SCOPED_TRACE(scenario.desc_); {
|
||||
// Create a context;
|
||||
AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "", true,
|
||||
Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234)));
|
||||
// Add client classes (if any)
|
||||
for (auto class_name : scenario.classes_) {
|
||||
ctx.query_->addClass(class_name);
|
||||
}
|
||||
|
||||
// Add hint
|
||||
ctx.currentIA().iaid_ = iaid_;
|
||||
|
||||
// prefix,prefixlen, preferred, valid
|
||||
ctx.currentIA().addHint(IOAddress("::"), 128, 0, scenario.requested_lft_);
|
||||
|
||||
Lease6Ptr lease;
|
||||
ASSERT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx)));
|
||||
ASSERT_TRUE(lease);
|
||||
EXPECT_EQ(lease->valid_lft_, scenario.exp_valid_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Verifies that AllocEngine::getLifetimes6() returns the appropriate
|
||||
// preferred lifetime value based on the context content.
|
||||
TEST_F(AllocEngine6Test, getPreferredLifetime) {
|
||||
boost::scoped_ptr<AllocEngine> engine;
|
||||
ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
|
||||
ASSERT_TRUE(engine);
|
||||
|
||||
// Let's make three classes, two with preferred-lifetime and one without,
|
||||
// and add them to the dictionary.
|
||||
ClientClassDictionaryPtr dictionary =
|
||||
CfgMgr::instance().getStagingCfg()->getClientClassDictionary();
|
||||
|
||||
ClientClassDefPtr class_def(new ClientClassDef("preferred_one", ExpressionPtr()));
|
||||
Triplet<uint32_t> preferred_one(50, 100, 150);
|
||||
class_def->setPreferred(preferred_one);
|
||||
dictionary->addClass(class_def);
|
||||
|
||||
class_def.reset(new ClientClassDef("preferred_two", ExpressionPtr()));
|
||||
Triplet<uint32_t>preferred_two(200, 250, 300);
|
||||
class_def->setPreferred(preferred_two);
|
||||
dictionary->addClass(class_def);
|
||||
|
||||
class_def.reset(new ClientClassDef("preferred_unspec", ExpressionPtr()));
|
||||
dictionary->addClass(class_def);
|
||||
|
||||
// Commit our class changes.
|
||||
CfgMgr::instance().commit();
|
||||
|
||||
// Update the subnet's triplet to something more useful.
|
||||
subnet_->setPreferred(Triplet<uint32_t>(500, 1000, 1500));
|
||||
|
||||
// Describes a test scenario.
|
||||
struct Scenario {
|
||||
std::string desc_; // descriptive text for logging
|
||||
std::vector<std::string> classes_; // class list of assigned classes
|
||||
uint32_t requested_lft_; // use as option 51 is > 0
|
||||
uint32_t exp_preferred_; // expected lifetime
|
||||
};
|
||||
|
||||
// Scenarios to test.
|
||||
std::vector<Scenario> scenarios = {
|
||||
{
|
||||
"no classes, no hint",
|
||||
{},
|
||||
0,
|
||||
subnet_->getPreferred()
|
||||
},
|
||||
{
|
||||
"no classes, hint",
|
||||
{},
|
||||
subnet_->getPreferred().getMin() + 50,
|
||||
subnet_->getPreferred().getMin() + 50
|
||||
},
|
||||
{
|
||||
"no classes, hint too small",
|
||||
{},
|
||||
subnet_->getPreferred().getMin() - 50,
|
||||
subnet_->getPreferred().getMin()
|
||||
},
|
||||
{
|
||||
"no classes, hint too big",
|
||||
{},
|
||||
subnet_->getPreferred().getMax() + 50,
|
||||
subnet_->getPreferred().getMax()
|
||||
},
|
||||
{
|
||||
"class unspecified, no hint",
|
||||
{ "preferred_unspec" },
|
||||
0,
|
||||
subnet_->getPreferred()
|
||||
},
|
||||
{
|
||||
"from last class, no hint",
|
||||
{ "preferred_unspec", "preferred_one" },
|
||||
0,
|
||||
preferred_one.get()
|
||||
},
|
||||
{
|
||||
"from first class, no hint",
|
||||
{ "preferred_two", "preferred_one" },
|
||||
0,
|
||||
preferred_two.get()
|
||||
},
|
||||
{
|
||||
"class plus hint",
|
||||
{ "preferred_one" },
|
||||
preferred_one.getMin() + 25,
|
||||
preferred_one.getMin() + 25
|
||||
},
|
||||
{
|
||||
"class plus hint too small",
|
||||
{ "preferred_one" },
|
||||
preferred_one.getMin() - 25,
|
||||
preferred_one.getMin()
|
||||
},
|
||||
{
|
||||
"class plus hint too big",
|
||||
{ "preferred_one" },
|
||||
preferred_one.getMax() + 25,
|
||||
preferred_one.getMax()
|
||||
}
|
||||
};
|
||||
|
||||
// Iterate over the scenarios and verify the correct outcome.
|
||||
for (auto scenario : scenarios) {
|
||||
SCOPED_TRACE(scenario.desc_); {
|
||||
// Create a context;
|
||||
AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "", true,
|
||||
Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234)));
|
||||
// Add client classes (if any)
|
||||
for (auto class_name : scenario.classes_) {
|
||||
ctx.query_->addClass(class_name);
|
||||
}
|
||||
|
||||
// Add hint
|
||||
ctx.currentIA().iaid_ = iaid_;
|
||||
|
||||
// prefix,prefixlen, preferred, preferred
|
||||
ctx.currentIA().addHint(IOAddress("::"), 128, scenario.requested_lft_, 0);
|
||||
|
||||
Lease6Ptr lease;
|
||||
ASSERT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx)));
|
||||
ASSERT_TRUE(lease);
|
||||
EXPECT_EQ(lease->preferred_lft_, scenario.exp_preferred_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
} // namespace dhcp
|
||||
} // namespace isc
|
||||
|
@ -1313,7 +1313,7 @@ TEST_F(ClientClassDefListParserTest, dropCheckError) {
|
||||
EXPECT_NO_THROW(parseClientClassDefList(cfg_text, AF_INET6));
|
||||
}
|
||||
|
||||
// Verify the ability to configure lease lifetime triplet.
|
||||
// Verify the ability to configure valid lifetime triplet.
|
||||
TEST_F(ClientClassDefParserTest, validLifetimeTests) {
|
||||
|
||||
struct Scenario {
|
||||
@ -1374,4 +1374,68 @@ TEST_F(ClientClassDefParserTest, validLifetimeTests) {
|
||||
}
|
||||
}
|
||||
|
||||
// Verify the ability to configure lease preferred lifetime triplet.
|
||||
TEST_F(ClientClassDefParserTest, preferredLifetimeTests) {
|
||||
|
||||
struct Scenario {
|
||||
std::string desc_;
|
||||
std::string cfg_txt_;
|
||||
Triplet<uint32_t> exp_triplet_;
|
||||
};
|
||||
|
||||
std::vector<Scenario> scenarios = {
|
||||
{
|
||||
"unspecified",
|
||||
"",
|
||||
Triplet<uint32_t>()
|
||||
},
|
||||
{
|
||||
"preferred only",
|
||||
"\"preferred-lifetime\": 100",
|
||||
Triplet<uint32_t>(100)
|
||||
},
|
||||
{
|
||||
"min only",
|
||||
"\"min-preferred-lifetime\": 50",
|
||||
Triplet<uint32_t>(50, 50, 50)
|
||||
},
|
||||
{
|
||||
"max only",
|
||||
"\"max-preferred-lifetime\": 75",
|
||||
Triplet<uint32_t>(75, 75, 75)
|
||||
},
|
||||
{
|
||||
"all three",
|
||||
"\"min-preferred-lifetime\": 25,"
|
||||
"\"preferred-lifetime\": 50,"
|
||||
"\"max-preferred-lifetime\": 75",
|
||||
Triplet<uint32_t>(25, 50, 75)
|
||||
}
|
||||
};
|
||||
|
||||
for (auto scenario : scenarios) {
|
||||
SCOPED_TRACE(scenario.desc_); {
|
||||
std::stringstream oss;
|
||||
oss << "{ \"name\": \"foo\"";
|
||||
if (!scenario.cfg_txt_.empty()) {
|
||||
oss << ",\n" << scenario.cfg_txt_;
|
||||
}
|
||||
oss << "\n}\n";
|
||||
|
||||
ClientClassDefPtr class_def;
|
||||
ASSERT_NO_THROW_LOG(class_def = parseClientClassDef(oss.str(), AF_INET6));
|
||||
ASSERT_TRUE(class_def);
|
||||
if (scenario.exp_triplet_.unspecified()) {
|
||||
EXPECT_TRUE(class_def->getPreferred().unspecified());
|
||||
} else {
|
||||
EXPECT_EQ(class_def->getPreferred(), scenario.exp_triplet_);
|
||||
EXPECT_EQ(class_def->getPreferred().getMin(), scenario.exp_triplet_.getMin());
|
||||
EXPECT_EQ(class_def->getPreferred().get(), scenario.exp_triplet_.get());
|
||||
EXPECT_EQ(class_def->getPreferred().getMax(), scenario.exp_triplet_.getMax());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} // end of anonymous namespace
|
||||
|
@ -83,6 +83,7 @@ TEST(ClientClassDef, copyConstruction) {
|
||||
cclass->setSname("ufo");
|
||||
cclass->setFilename("ufo.efi");
|
||||
cclass->setValid(Triplet<uint32_t>(10, 20, 30));
|
||||
cclass->setPreferred(Triplet<uint32_t>(11, 21, 31));
|
||||
|
||||
// Copy the client class.
|
||||
boost::scoped_ptr<ClientClassDef> cclass_copy;
|
||||
@ -105,6 +106,9 @@ TEST(ClientClassDef, copyConstruction) {
|
||||
EXPECT_EQ(cclass->getValid().get(), cclass_copy->getValid().get());
|
||||
EXPECT_EQ(cclass->getValid().getMin(), cclass_copy->getValid().getMin());
|
||||
EXPECT_EQ(cclass->getValid().getMax(), cclass_copy->getValid().getMax());
|
||||
EXPECT_EQ(cclass->getPreferred().get(), cclass_copy->getPreferred().get());
|
||||
EXPECT_EQ(cclass->getPreferred().getMin(), cclass_copy->getPreferred().getMin());
|
||||
EXPECT_EQ(cclass->getPreferred().getMax(), cclass_copy->getPreferred().getMax());
|
||||
|
||||
// Ensure that the option was copied into a new structure.
|
||||
ASSERT_TRUE(cclass_copy->getCfgOption());
|
||||
|
Loading…
x
Reference in New Issue
Block a user