"only-if-required": true,
// Class selection expression.
- "test": "member('ALL')"
+ "test": "member('ALL')",
+
+ // Template class flag.
+ "template-class": false
}
],
"only-if-required": true,
// Class selection expression.
- "test": "member('ALL')"
+ "test": "member('ALL')",
+
+ // Template class flag.
+ "template-class": false
}
],
case TSIG_KEYS:
return ("tsig-keys");
case ALGORITHM:
- return("algorithm");
+ return ("algorithm");
case DIGEST_BITS:
- return("digest-bits");
+ return ("digest-bits");
case SECRET:
- return("secret");
+ return ("secret");
case FORWARD_DDNS:
- return("forward-ddns");
+ return ("forward-ddns");
case REVERSE_DDNS:
- return("reverse-ddns");
+ return ("reverse-ddns");
case DDNS_DOMAIN:
- return("ddns-domain");
+ return ("ddns-domain");
case DDNS_DOMAINS:
- return("ddns-domains");
+ return ("ddns-domains");
case DNS_SERVER:
- return("dns-server");
+ return ("dns-server");
case DNS_SERVERS:
- return("dns-servers");
+ return ("dns-servers");
case CONTROL_SOCKET:
- return("control-socket");
+ return ("control-socket");
case LOGGERS:
return ("loggers");
case OUTPUT_OPTIONS:
}
}
+\"template-class\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::CLIENT_CLASSES:
+ return isc::dhcp::Dhcp4Parser::make_TEMPLATE_CLASS(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("template-class", driver.loc_);
+ }
+}
+
\"only-if-required\" {
switch(driver.ctx_) {
case isc::dhcp::Parser4Context::CLIENT_CLASSES:
extern const isc::log::MessageID DHCP4_DEFERRED_OPTION_UNPACK_FAIL = "DHCP4_DEFERRED_OPTION_UNPACK_FAIL";
extern const isc::log::MessageID DHCP4_DEVELOPMENT_VERSION = "DHCP4_DEVELOPMENT_VERSION";
extern const isc::log::MessageID DHCP4_DHCP4O6_BAD_PACKET = "DHCP4_DHCP4O6_BAD_PACKET";
+extern const isc::log::MessageID DHCP4_DHCP4O6_HOOK_SUBNET4_SELECT_DROP = "DHCP4_DHCP4O6_HOOK_SUBNET4_SELECT_DROP";
+extern const isc::log::MessageID DHCP4_DHCP4O6_HOOK_SUBNET4_SELECT_SKIP = "DHCP4_DHCP4O6_HOOK_SUBNET4_SELECT_SKIP";
extern const isc::log::MessageID DHCP4_DHCP4O6_PACKET_RECEIVED = "DHCP4_DHCP4O6_PACKET_RECEIVED";
extern const isc::log::MessageID DHCP4_DHCP4O6_PACKET_SEND = "DHCP4_DHCP4O6_PACKET_SEND";
extern const isc::log::MessageID DHCP4_DHCP4O6_PACKET_SEND_FAIL = "DHCP4_DHCP4O6_PACKET_SEND_FAIL";
extern const isc::log::MessageID DHCP4_DHCP4O6_RECEIVE_FAIL = "DHCP4_DHCP4O6_RECEIVE_FAIL";
extern const isc::log::MessageID DHCP4_DHCP4O6_RECEIVING = "DHCP4_DHCP4O6_RECEIVING";
extern const isc::log::MessageID DHCP4_DHCP4O6_RESPONSE_DATA = "DHCP4_DHCP4O6_RESPONSE_DATA";
+extern const isc::log::MessageID DHCP4_DHCP4O6_SUBNET_DATA = "DHCP4_DHCP4O6_SUBNET_DATA";
+extern const isc::log::MessageID DHCP4_DHCP4O6_SUBNET_SELECTED = "DHCP4_DHCP4O6_SUBNET_SELECTED";
+extern const isc::log::MessageID DHCP4_DHCP4O6_SUBNET_SELECTION_FAILED = "DHCP4_DHCP4O6_SUBNET_SELECTION_FAILED";
extern const isc::log::MessageID DHCP4_DYNAMIC_RECONFIGURATION = "DHCP4_DYNAMIC_RECONFIGURATION";
extern const isc::log::MessageID DHCP4_DYNAMIC_RECONFIGURATION_FAIL = "DHCP4_DYNAMIC_RECONFIGURATION_FAIL";
extern const isc::log::MessageID DHCP4_DYNAMIC_RECONFIGURATION_SUCCESS = "DHCP4_DYNAMIC_RECONFIGURATION_SUCCESS";
"DHCP4_DEFERRED_OPTION_UNPACK_FAIL", "An error unpacking the deferred option %1: %2",
"DHCP4_DEVELOPMENT_VERSION", "This software is a development branch of Kea. It is not recommended for production use.",
"DHCP4_DHCP4O6_BAD_PACKET", "received malformed DHCPv4o6 packet: %1",
+ "DHCP4_DHCP4O6_HOOK_SUBNET4_SELECT_DROP", "%1: packet was dropped, because a callout set the next step to 'drop'",
+ "DHCP4_DHCP4O6_HOOK_SUBNET4_SELECT_SKIP", "%1: no subnet was selected, because a callout set the next skip flag",
"DHCP4_DHCP4O6_PACKET_RECEIVED", "received DHCPv4o6 packet from DHCPv4 server (type %1) for %2 on interface %3",
"DHCP4_DHCP4O6_PACKET_SEND", "%1: trying to send packet %2 (type %3) to %4 port %5 on interface %6 encapsulating %7: %8 (type %9)",
"DHCP4_DHCP4O6_PACKET_SEND_FAIL", "%1: failed to send DHCPv4o6 packet: %2",
"DHCP4_DHCP4O6_RECEIVE_FAIL", "failed to receive DHCPv4o6: %1",
"DHCP4_DHCP4O6_RECEIVING", "receiving DHCPv4o6 packet from DHCPv6 server",
"DHCP4_DHCP4O6_RESPONSE_DATA", "%1: responding with packet %2 (type %3), packet details: %4",
+ "DHCP4_DHCP4O6_SUBNET_DATA", "%1: the selected subnet details: %2",
+ "DHCP4_DHCP4O6_SUBNET_SELECTED", "%1: the subnet with ID %2 was selected for client assignments",
+ "DHCP4_DHCP4O6_SUBNET_SELECTION_FAILED", "%1: failed to select subnet for the client",
"DHCP4_DYNAMIC_RECONFIGURATION", "initiate server reconfiguration using file: %1, after receiving SIGHUP signal or config-reload command",
"DHCP4_DYNAMIC_RECONFIGURATION_FAIL", "dynamic server reconfiguration failed with file: %1",
"DHCP4_DYNAMIC_RECONFIGURATION_SUCCESS", "dynamic server reconfiguration succeeded with file: %1",
extern const isc::log::MessageID DHCP4_DEFERRED_OPTION_UNPACK_FAIL;
extern const isc::log::MessageID DHCP4_DEVELOPMENT_VERSION;
extern const isc::log::MessageID DHCP4_DHCP4O6_BAD_PACKET;
+extern const isc::log::MessageID DHCP4_DHCP4O6_HOOK_SUBNET4_SELECT_DROP;
+extern const isc::log::MessageID DHCP4_DHCP4O6_HOOK_SUBNET4_SELECT_SKIP;
extern const isc::log::MessageID DHCP4_DHCP4O6_PACKET_RECEIVED;
extern const isc::log::MessageID DHCP4_DHCP4O6_PACKET_SEND;
extern const isc::log::MessageID DHCP4_DHCP4O6_PACKET_SEND_FAIL;
extern const isc::log::MessageID DHCP4_DHCP4O6_RECEIVE_FAIL;
extern const isc::log::MessageID DHCP4_DHCP4O6_RECEIVING;
extern const isc::log::MessageID DHCP4_DHCP4O6_RESPONSE_DATA;
+extern const isc::log::MessageID DHCP4_DHCP4O6_SUBNET_DATA;
+extern const isc::log::MessageID DHCP4_DHCP4O6_SUBNET_SELECTED;
+extern const isc::log::MessageID DHCP4_DHCP4O6_SUBNET_SELECTION_FAILED;
extern const isc::log::MessageID DHCP4_DYNAMIC_RECONFIGURATION;
extern const isc::log::MessageID DHCP4_DYNAMIC_RECONFIGURATION_FAIL;
extern const isc::log::MessageID DHCP4_DYNAMIC_RECONFIGURATION_SUCCESS;
The argument specifies the client and transaction identification
information.
+% DHCP4_DHCP4O6_HOOK_SUBNET4_SELECT_DROP %1: packet was dropped, because a callout set the next step to 'drop'
+This debug message is printed when a callout installed on the
+subnet4_select hook point sets the next step to 'drop' value. For this particular hook
+point, the setting to that value instructs the server to drop the received
+packet. The argument specifies the client and transaction identification
+information.
+
+% DHCP4_DHCP4O6_HOOK_SUBNET4_SELECT_SKIP %1: no subnet was selected, because a callout set the next skip flag
+This debug message is printed when a callout installed on the
+subnet4_select hook point sets the next step to SKIP value. For this particular hook
+point, the setting of the flag instructs the server not to choose a
+subnet, an action that severely limits further processing; the server
+will be only able to offer global options - no addresses will be assigned.
+The argument specifies the client and transaction identification
+information.
+
% DHCP4_INFORM_DIRECT_REPLY %1: DHCPACK in reply to the DHCPINFORM will be sent directly to %2 over %3
This debug message is issued when the DHCPACK will be sent directly to the
client, rather than via a relay. The first argument contains the client
% DHCP4_SRV_DHCP4O6_ERROR error stopping IO with DHCPv4o6 during shutdown: %1
This error message indicates that during shutdown, an error occurred while
-stopping IO between the DHCPv4 server and the DHCPv6o6 server. This is
+stopping IO between the DHCPv4 server and the DHCPv4o6 server. This is
probably due to a programmatic error is not likely to impact either server
upon restart. The reason for the failure is given within the message.
transaction identification information. The second arguments
includes the subnet details.
+% DHCP4_DHCP4O6_SUBNET_DATA %1: the selected subnet details: %2
+This debug message includes the details of the subnet selected for
+the client. The first argument includes the client and the
+transaction identification information. The second arguments
+includes the subnet details.
+
% DHCP4_SUBNET_DYNAMICALLY_CHANGED %1: changed selected subnet %2 to subnet %3 from shared network %4 for client assignments
This debug message indicates that the server is using another subnet
than initially selected for client assignments. This newly selected
The argument includes the client and the transaction identification
information.
+% DHCP4_DHCP4O6_SUBNET_SELECTED %1: the subnet with ID %2 was selected for client assignments
+This is a debug message noting the selection of a subnet to be used for
+address and option assignment. Subnet selection is one of the early
+steps in the processing of incoming client message. The first
+argument includes the client and the transaction identification
+information. The second argument holds the selected subnet id.
+
+% DHCP4_DHCP4O6_SUBNET_SELECTION_FAILED %1: failed to select subnet for the client
+This debug message indicates that the server failed to select the
+subnet for the client which has sent a message to the server.
+The server will not be able to offer any lease to the client and
+will drop its message if the received message was DHCPDISCOVER,
+and will send DHCPNAK if the received message was DHCPREQUEST.
+The argument includes the client and the transaction identification
+information.
+
% DHCP4_TESTING_MODE_SEND_TO_SOURCE_ENABLED All packets will be send to source address of an incoming packet - use only for testing
This message is printed then KEA_TEST_SEND_RESPONSES_TO_SOURCE
environment variable is set. It's causing Kea to send packets to
CLIENT_CLASSES "client-classes"
REQUIRE_CLIENT_CLASSES "require-client-classes"
TEST "test"
+ TEMPLATE_CLASS "template-class"
ONLY_IF_REQUIRED "only-if-required"
CLIENT_CLASS "client-class"
client_class_param: client_class_name
| client_class_test
+ | client_class_template_class
| only_if_required
| option_def_list
| option_data_list
ctx.leave();
};
+client_class_template_class: TEMPLATE_CLASS COLON BOOLEAN {
+ ctx.unique("template-class", ctx.loc2pos(@1));
+ ElementPtr b(new BoolElement($3, ctx.loc2pos(@3)));
+ ctx.stack_.back()->set("template-class", b);
+};
+
only_if_required: ONLY_IF_REQUIRED COLON BOOLEAN {
ctx.unique("only-if-required", ctx.loc2pos(@1));
ElementPtr b(new BoolElement($3, ctx.loc2pos(@3)));
// Perform second pass of classification.
evaluateClasses(query, true);
- const ClientClasses& classes = query_->getClasses();
+ const ClientClasses& classes = query_->getClassesAndSubClasses();
if (!classes.empty()) {
LOG_DEBUG(dhcp4_logger, DBG_DHCP4_BASIC, DHCP4_CLASS_ASSIGNED)
.arg(query_->getLabel())
if ((*it)->getDependOnKnown() != depend_on_known) {
continue;
}
- // Evaluate the expression which can return false (no match),
- // true (match) or raise an exception (error)
- try {
- bool status = evaluateBool(*expr_ptr, *pkt);
- if (status) {
- LOG_INFO(options4_logger, EVAL_RESULT)
- .arg((*it)->getName())
- .arg(status);
- // Matching: add the class
- pkt->addClass((*it)->getName());
- } else {
- LOG_DEBUG(options4_logger, DBG_DHCP4_DETAIL, EVAL_RESULT)
- .arg((*it)->getName())
- .arg(status);
- }
- } catch (const Exception& ex) {
- LOG_ERROR(options4_logger, EVAL_RESULT)
- .arg((*it)->getName())
- .arg(ex.what());
- } catch (...) {
- LOG_ERROR(options4_logger, EVAL_RESULT)
- .arg((*it)->getName())
- .arg("get exception?");
- }
+ (*it)->test(pkt, expr_ptr);
}
}
isc::dhcp::Subnet4Ptr
Dhcpv4Srv::selectSubnet4o6(const Pkt4Ptr& query, bool& drop,
bool sanity_only) const {
-
Subnet4Ptr subnet;
SubnetSelector selector;
// be severely limited (i.e. only global options will be assigned)
if (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_SKIP) {
LOG_DEBUG(hooks_logger, DBG_DHCP4_HOOKS,
- DHCP4_HOOK_SUBNET4_SELECT_SKIP)
+ DHCP4_DHCP4O6_HOOK_SUBNET4_SELECT_SKIP)
.arg(query->getLabel());
return (Subnet4Ptr());
}
// skip case so no subnet will be selected.
if (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_DROP) {
LOG_DEBUG(hooks_logger, DBG_DHCP4_HOOKS,
- DHCP4_HOOK_SUBNET4_SELECT_DROP)
+ DHCP4_DHCP4O6_HOOK_SUBNET4_SELECT_DROP)
.arg(query->getLabel());
drop = true;
return (Subnet4Ptr());
if (subnet) {
// Log at higher debug level that subnet has been found.
- LOG_DEBUG(packet4_logger, DBG_DHCP4_BASIC_DATA, DHCP4_SUBNET_SELECTED)
+ LOG_DEBUG(packet4_logger, DBG_DHCP4_BASIC_DATA,
+ DHCP4_DHCP4O6_SUBNET_SELECTED)
.arg(query->getLabel())
.arg(subnet->getID());
// Log detailed information about the selected subnet at the
// lower debug level.
- LOG_DEBUG(packet4_logger, DBG_DHCP4_DETAIL_DATA, DHCP4_SUBNET_DATA)
+ LOG_DEBUG(packet4_logger, DBG_DHCP4_DETAIL_DATA,
+ DHCP4_DHCP4O6_SUBNET_DATA)
.arg(query->getLabel())
.arg(subnet->toText());
} else {
LOG_DEBUG(packet4_logger, DBG_DHCP4_DETAIL,
- DHCP4_SUBNET_SELECTION_FAILED)
+ DHCP4_DHCP4O6_SUBNET_SELECTION_FAILED)
.arg(query->getLabel());
}
}
// Each class in the incoming packet
- const ClientClasses& classes = ex.getQuery()->getClasses();
+ const ClientClasses& classes = ex.getQuery()->getClassesAndTemplates();
for (ClientClasses::const_iterator cclass = classes.cbegin();
cclass != classes.cend(); ++cclass) {
// Find the client class definition for this class
Lease4Ptr lease;
+ auto const& classes = query->getClassesAndSubClasses();
+
// We used to issue a separate query (two actually: one for client-id
// and another one for hw-addr for) each subnet in the shared network.
// That was horribly inefficient if the client didn't have any lease
break;
} else {
- s = s->getNextSubnet(original_subnet, query->getClasses());
+ s = s->getNextSubnet(original_subnet, classes);
}
}
}
break;
} else {
- s = s->getNextSubnet(original_subnet, query->getClasses());
+ s = s->getNextSubnet(original_subnet, classes);
}
}
}
// Step 2: Try to set the values based on classes.
// Any values defined in classes will override those from subnet level.
- const ClientClasses classes = query->getClasses();
+ const ClientClasses classes = query->getClassesAndTemplates();
if (!classes.empty()) {
// Let's get class definitions
}
// Check if the server identifier is configured at client class level.
- const ClientClasses& classes = query->getClasses();
+ const ClientClasses& classes = query->getClassesAndTemplates();
for (ClientClasses::const_iterator cclass = classes.cbegin();
cclass != classes.cend(); ++cclass) {
// Find the client class definition for this class
void Dhcpv4Srv::requiredClassify(Dhcpv4Exchange& ex) {
// First collect required classes
Pkt4Ptr query = ex.getQuery();
- ClientClasses classes = query->getClasses(true);
+ ClientClasses classes = query->getClassesAndTemplates(true);
Subnet4Ptr subnet = ex.getContext()->subnet_;
if (subnet) {
BOOST_FOREACH(const uint16_t& code, query->getDeferredOptions()) {
OptionDefinitionPtr def;
// Iterate on client classes
- const ClientClasses& classes = query->getClasses();
+ const ClientClasses& classes = query->getClassesAndTemplates();
for (ClientClasses::const_iterator cclass = classes.cbegin();
cclass != classes.cend(); ++cclass) {
// Get the client class definition for this class
// in the structure returned.
OptionPtr option = range.first->option_;
ASSERT_TRUE(option);
- // Option object returned for here is expected to be Option6IA
+ // Option object returned for here is expected to be Option4AddrLst
// which is derived from Option. This class is dedicated to
- // represent standard option IA_NA.
+ // represent standard option DHO_NIS_SERVERS.
boost::shared_ptr<Option4AddrLst> option_addrs =
boost::dynamic_pointer_cast<Option4AddrLst>(option);
// If cast is unsuccessful than option returned was of a
- // different type than Option6IA. This is wrong.
+ // different type than Option4AddrLst. This is wrong.
ASSERT_TRUE(option_addrs);
// Get addresses from the option.
EXPECT_TRUE(class_def->getValid().unspecified());
}
+// Verifies that simple list of valid template classes parses and
+// is staged for commit.
+TEST_F(Dhcp4ParserTest, templateClientClassValidLifetime) {
+ string config = "{ " + genIfaceConfig() + "," +
+ "\"client-classes\" : [ \n"
+ " { \n"
+ " \"name\": \"one\", \n"
+ " \"min-valid-lifetime\": 1000, \n"
+ " \"valid-lifetime\": 2000, \n"
+ " \"max-valid-lifetime\": 3000, \n"
+ " \"template-class\": true, \n"
+ " }, \n"
+ " { \n"
+ " \"name\": \"two\", \n"
+ " \"template-class\": true \n"
+ " } \n"
+ "], \n"
+ "\"subnet4\": [ { \n"
+ " \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ], \n"
+ " \"subnet\": \"192.0.2.0/24\" \n"
+ " } ] \n"
+ "} \n";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW_LOG(json = parseDHCP4(config));
+ extractConfig(config);
+
+ ConstElementPtr status;
+ ASSERT_NO_THROW_LOG(status = configureDhcp4Server(*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);
+ ASSERT_TRUE(dynamic_cast<TemplateClientClassDef*>(class_def.get()));
+ EXPECT_EQ(class_def->getValid().getMin(), 1000);
+ EXPECT_EQ(class_def->getValid().get(), 2000);
+ EXPECT_EQ(class_def->getValid().getMax(), 3000);
+
+ class_def = dictionary->findClass("two");
+ ASSERT_TRUE(class_def);
+ ASSERT_TRUE(dynamic_cast<TemplateClientClassDef*>(class_def.get()));
+ EXPECT_TRUE(class_def->getValid().unspecified());
+}
// Test verifies that regular configuration does not provide any user context
// in the address pool.
return (DdnsParamsPtr(new DdnsParams()));
}
- return(CfgMgr::instance().getCurrentCfg()->getDdnsParams(subnet_));
+ return (CfgMgr::instance().getCurrentCfg()->getDdnsParams(subnet_));
}
// Create a lease to be used by various tests.
/// @return An std::string contained the generated FQDN.
std::string generatedNameFromAddress(const IOAddress& addr,
const bool trailing_dot = true) {
- return(CfgMgr::instance().getD2ClientMgr()
- .generateFqdn(addr, *getDdnsParams(), trailing_dot));
+ return (CfgMgr::instance().getD2ClientMgr()
+ .generateFqdn(addr, *getDdnsParams(), trailing_dot));
}
// Get the Client FQDN Option from the given message.
cerr << "(fixture ctor) unloadLibraries failed" << endl;
}
- // Allocate new DHCPv6 Server
+ // Allocate new DHCPv4 Server
srv_ = new NakedDhcpv4Srv(0);
// clear static buffers
srv_->fakeReceive(discover);
// Server will now process to run its normal loop, but instead of calling
- // IfaceMgr::receive6(), it will read all packets from the list set by
+ // IfaceMgr::receive4(), it will read all packets from the list set by
// fakeReceive()
// In particular, it should call registered buffer4_receive callback.
srv_->run();
srv_->fakeReceive(discover);
// Server will now process to run its normal loop, but instead of calling
- // IfaceMgr::receive6(), it will read all packets from the list set by
+ // IfaceMgr::receive4(), it will read all packets from the list set by
// fakeReceive()
// In particular, it should call registered pkt4_receive callback.
srv_->run();
srv_->fakeReceive(discover);
// Server will now process to run its normal loop, but instead of calling
- // IfaceMgr::receive6(), it will read all packets from the list set by
+ // IfaceMgr::receive4(), it will read all packets from the list set by
// fakeReceive()
// In particular, it should call registered pkt4_receive callback.
srv_->run();
string config_empty = "";
// This config does not have mandatory Dhcp4 element
- string config_v4 = "{ \"Dhcp6\": { \"interfaces\": [ \"*\" ],"
+ string config_v6 = "{ \"Dhcp6\": { \"interfaces\": [ \"*\" ],"
"\"preferred-lifetime\": 3000,"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
EXPECT_THROW(srv->init(TEST_FILE), BadValue);
// Now try to load a config that does not have Dhcp4 component.
- writeFile(TEST_FILE, config_v4);
+ writeFile(TEST_FILE, config_v6);
EXPECT_THROW(srv->init(TEST_FILE), BadValue);
// Now try to load a config with Dhcp4 full of nonsense.
checkBoolValue(def, "array", false);
}
-};
-};
-};
+}
+}
+}
}
}
+\"template-class\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser6Context::CLIENT_CLASSES:
+ return isc::dhcp::Dhcp6Parser::make_TEMPLATE_CLASS(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp6Parser::make_STRING("template-class", driver.loc_);
+ }
+}
+
\"only-if-required\" {
switch(driver.ctx_) {
case isc::dhcp::Parser6Context::CLIENT_CLASSES:
CLIENT_CLASSES "client-classes"
REQUIRE_CLIENT_CLASSES "require-client-classes"
TEST "test"
+ TEMPLATE_CLASS "template-class"
ONLY_IF_REQUIRED "only-if-required"
CLIENT_CLASS "client-class"
client_class_param: client_class_name
| client_class_test
+ | client_class_template_class
| only_if_required
| option_data_list
| user_context
ctx.leave();
};
+client_class_template_class: TEMPLATE_CLASS COLON BOOLEAN {
+ ctx.unique("template-class", ctx.loc2pos(@1));
+ ElementPtr b(new BoolElement($3, ctx.loc2pos(@3)));
+ ctx.stack_.back()->set("template-class", b);
+};
+
only_if_required: ONLY_IF_REQUIRED COLON BOOLEAN {
ctx.unique("only-if-required", ctx.loc2pos(@1));
ElementPtr b(new BoolElement($3, ctx.loc2pos(@3)));
// Perform second pass of classification.
evaluateClasses(pkt, true);
+ const ClientClasses& classes = pkt->getClassesAndSubClasses();
+ if (!classes.empty()) {
+ LOG_DEBUG(dhcp6_logger, DBG_DHCP6_BASIC, DHCP6_CLASS_ASSIGNED)
+ .arg(pkt->getLabel())
+ .arg(classes.toText());
+ }
+
// Check the DROP special class.
if (pkt->inClass("DROP")) {
LOG_DEBUG(packet6_logger, DBGLVL_PKT_HANDLING, DHCP6_PACKET_DROP_DROP_CLASS2)
}
// Each class in the incoming packet
- const ClientClasses& classes = question->getClasses();
+ const ClientClasses& classes = question->getClassesAndTemplates();
for (ClientClasses::const_iterator cclass = classes.cbegin();
cclass != classes.cend(); ++cclass) {
// Find the client class definition for this class
if ((*it)->getDependOnKnown() != depend_on_known) {
continue;
}
- // Evaluate the expression which can return false (no match),
- // true (match) or raise an exception (error)
- try {
- bool status = evaluateBool(*expr_ptr, *pkt);
- if (status) {
- LOG_INFO(dhcp6_logger, EVAL_RESULT)
- .arg((*it)->getName())
- .arg(status);
- // Matching: add the class
- pkt->addClass((*it)->getName());
- } else {
- LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL, EVAL_RESULT)
- .arg((*it)->getName())
- .arg(status);
- }
- } catch (const Exception& ex) {
- LOG_ERROR(dhcp6_logger, EVAL_RESULT)
- .arg((*it)->getName())
- .arg(ex.what());
- } catch (...) {
- LOG_ERROR(dhcp6_logger, EVAL_RESULT)
- .arg((*it)->getName())
- .arg("get exception?");
- }
+ (*it)->test(pkt, expr_ptr);
}
}
}
}
- const ClientClasses& classes = pkt->getClasses();
+ const ClientClasses& classes = pkt->getClassesAndSubClasses();
if (!classes.empty()) {
LOG_DEBUG(dhcp6_logger, DBG_DHCP6_BASIC, DHCP6_CLASS_ASSIGNED)
.arg(pkt->getLabel())
void
Dhcpv6Srv::requiredClassify(const Pkt6Ptr& pkt, AllocEngine::ClientContext6& ctx) {
// First collect required classes
- ClientClasses classes = pkt->getClasses(true);
+ ClientClasses classes = pkt->getClassesAndTemplates(true);
Subnet6Ptr subnet = ctx.subnet_;
if (subnet) {
/// in the future.
///
/// @param cfg - the parsed structure
- /// @param global global Dhcp4 scope
+ /// @param global global Dhcp6 scope
/// @throw DhcpConfigError in case of issues found
void
sanityChecks(const SrvConfigPtr& cfg, const ConstElementPtr& global) {
if (sock_cfg) {
// This will create a control socket and install the external
// socket in IfaceMgr. That socket will be monitored when
- // Dhcp4Srv::receivePacket() calls IfaceMgr::receive4() and
+ // Dhcp6Srv::receivePacket() calls IfaceMgr::receive6() and
// callback in CommandMgr will be called, if necessary.
isc::config::CommandMgr::instance().openCommandSocket(sock_cfg);
}
EXPECT_TRUE(class_def->getValid().unspecified());
}
+// Verifies that client class definitions may specify
+// valid and preferred lifetime triplets.
+TEST_F(Dhcp6ParserTest, templateClientClassValidPreferredLifetime) {
+ 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"
+ " \"template-class\": true \n"
+ " }, \n"
+ " { \n"
+ " \"name\": \"two\", \n"
+ " \"template-class\": true \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);
+ ASSERT_TRUE(dynamic_cast<TemplateClientClassDef*>(class_def.get()));
+ 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);
+ ASSERT_TRUE(dynamic_cast<TemplateClientClassDef*>(class_def.get()));
+ EXPECT_TRUE(class_def->getValid().unspecified());
+}
+
} // namespace
return (DdnsParamsPtr(new DdnsParams()));
}
- return(CfgMgr::instance().getCurrentCfg()->getDdnsParams(subnet_));
+ return (CfgMgr::instance().getCurrentCfg()->getDdnsParams(subnet_));
}
/// @brief Construct the DHCPv6 Client FQDN option using flags and
ASSERT_TRUE(option);
// we should have appropriate default value set. See
- // SimpleParser4::OPTION4_DEFAULTS for a list of default values.
+ // SimpleParser6::OPTION6_DEFAULTS for a list of default values.
checkStringValue(option, "space", "dhcp6");
checkBoolValue(option, "csv-format", true);
}
ASSERT_TRUE(def);
// we should have appropriate default value set. See
- // SimpleParser4::OPTION4_DEFAULTS for a list of default values.
+ // SimpleParser6::OPTION6_DEFAULTS for a list of default values.
checkStringValue(def, "record-types", "");
checkStringValue(def, "space", "dhcp6");
checkStringValue(def, "encapsulate", "");
checkBoolValue(def, "array", false);
}
-
-};
+}
/// 1 otherwise.
int lease4_add(CalloutHandle& handle) {
LeaseCmds lease_cmds;
- return(lease_cmds.leaseAddHandler(handle));
+ return (lease_cmds.leaseAddHandler(handle));
}
/// @brief This is a command callout for 'lease6-add' command.
/// 1 otherwise.
int lease6_add(CalloutHandle& handle) {
LeaseCmds lease_cmds;
- return(lease_cmds.leaseAddHandler(handle));
+ return (lease_cmds.leaseAddHandler(handle));
}
/// @brief This is a command callout for 'lease6-bulk-apply' command.
/// 1 otherwise.
int lease4_get(CalloutHandle& handle) {
LeaseCmds lease_cmds;
- return(lease_cmds.leaseGetHandler(handle));
+ return (lease_cmds.leaseGetHandler(handle));
}
/// @brief This is a command callout for 'lease6-get' command.
/// 1 otherwise.
int lease6_get(CalloutHandle& handle) {
LeaseCmds lease_cmds;
- return(lease_cmds.leaseGetHandler(handle));
+ return (lease_cmds.leaseGetHandler(handle));
}
/// @brief This is a command callout for 'lease4-get-all' command.
/// 1 otherwise.
int lease4_del(CalloutHandle& handle) {
LeaseCmds lease_cmds;
- return(lease_cmds.lease4DelHandler(handle));
+ return (lease_cmds.lease4DelHandler(handle));
}
/// @brief This is a command callout for 'lease6-del' command.
/// 1 otherwise.
int lease6_del(CalloutHandle& handle) {
LeaseCmds lease_cmds;
- return(lease_cmds.lease6DelHandler(handle));
+ return (lease_cmds.lease6DelHandler(handle));
}
/// @brief This is a command callout for 'lease4-update' command.
/// 1 otherwise.
int lease4_update(CalloutHandle& handle) {
LeaseCmds lease_cmds;
- return(lease_cmds.lease4UpdateHandler(handle));
+ return (lease_cmds.lease4UpdateHandler(handle));
}
/// @brief This is a command callout for 'lease6-update' command.
/// 1 otherwise.
int lease6_update(CalloutHandle& handle) {
LeaseCmds lease_cmds;
- return(lease_cmds.lease6UpdateHandler(handle));
+ return (lease_cmds.lease6UpdateHandler(handle));
}
/// @brief This is a command callout for 'lease4-wipe' command.
/// 1 otherwise.
int lease4_wipe(CalloutHandle& handle) {
LeaseCmds lease_cmds;
- return(lease_cmds.lease4WipeHandler(handle));
+ return (lease_cmds.lease4WipeHandler(handle));
}
/// @brief This is a command callout for 'lease6-wipe' command.
/// 1 otherwise.
int lease6_wipe(CalloutHandle& handle) {
LeaseCmds lease_cmds;
- return(lease_cmds.lease6WipeHandler(handle));
+ return (lease_cmds.lease6WipeHandler(handle));
}
/// @brief This is a command callout for 'lease4-resend-ddns' command.
/// 1 otherwise.
int lease4_resend_ddns(CalloutHandle& handle) {
LeaseCmds lease_cmds;
- return(lease_cmds.lease4ResendDdnsHandler(handle));
+ return (lease_cmds.lease4ResendDdnsHandler(handle));
}
/// @brief This is a command callout for 'lease6-resend-ddns' command.
/// 1 otherwise.
int lease6_resend_ddns(CalloutHandle& handle) {
LeaseCmds lease_cmds;
- return(lease_cmds.lease6ResendDdnsHandler(handle));
+ return (lease_cmds.lease6ResendDdnsHandler(handle));
}
/// @brief This is a command callout for 'lease4-write' command.
<< index << ", is invalid");
}
- return(tagged_statements[index]);
+ return (tagged_statements[index]);
}
PgSqlConfigBackendDHCPv4::PgSqlConfigBackendDHCPv4(const DatabaseConnection::ParameterMap& parameters)
<< index << ", is invalid");
}
- return(tagged_statements[index]);
+ return (tagged_statements[index]);
}
PgSqlConfigBackendDHCPv6::PgSqlConfigBackendDHCPv6(const DatabaseConnection::ParameterMap& parameters)
uint64_t
PgSqlConfigBackendImpl::updateDeleteQuery(size_t index,
const PsqlBindArray& in_bindings) {
- return(conn_.updateDeleteQuery(getStatement(index), in_bindings));
+ return (conn_.updateDeleteQuery(getStatement(index), in_bindings));
}
PgSqlConfigBackendImpl::ScopedAuditRevision::ScopedAuditRevision(
Lease4Ptr lease4(new Lease4(IOAddress("192.168.0.1"), hwaddr, clientid,
2, 3, 4, false, false, "test.hostname"));
- return(lease4);
+ return (lease4);
}
/// @brief Generate a valid Lease6.
/// 1 otherwise.
int stat_lease4_get(CalloutHandle& handle) {
StatCmds stat_cmds;
- return(stat_cmds.statLease4GetHandler(handle));
+ return (stat_cmds.statLease4GetHandler(handle));
}
/// @brief This is a command callout for 'stat-lease6-get' command.
/// 1 otherwise.
int stat_lease6_get(CalloutHandle& handle) {
StatCmds stat_cmds;
- return(stat_cmds.statLease6GetHandler(handle));
+ return (stat_cmds.statLease6GetHandler(handle));
}
/// @brief This function is called when the library is loaded.
std::string getV6AddrStr(Pkt6Ptr response) {
OptionPtr tmp = response->getOption(D6O_IA_NA);
if (tmp) {
- return(getAddrStrIA_NA(tmp));
+ return (getAddrStrIA_NA(tmp));
}
// IA_NA not there so try IA_PD
isc_throw (isc::BadValue, "Response has neither IA_NA nor IA_PD");
}
- return(getAddrStrIA_PD(tmp));
+ return (getAddrStrIA_PD(tmp));
}
/// @brief Stringify the lease address in an D6O_IA_NA option set
// We got something, try to make a user out of it.
if (file_.gcount() > 0) {
- return(makeUser(buf));
+ return (makeUser(buf));
}
}
max_row_errors = param.second->intValue();
values_copy[param.first] =
boost::lexical_cast<std::string>(max_row_errors);
+
} else {
// all remaining string parameters
#include <cc/data.h>
#include <boost/multi_index_container.hpp>
+#include <boost/multi_index/member.hpp>
#include <boost/multi_index/hashed_index.hpp>
#include <boost/multi_index/identity.hpp>
#include <boost/multi_index/ordered_index.hpp>
/// though they reside in the dhcp directory.
namespace isc {
-
namespace dhcp {
/// @brief Defines a single class name.
>
> ClientClassContainer;
+ /// @brief Defines a single subclass (template class instantiation).
+ struct SubClass {
+ /// @brief Constructor.
+ SubClass(const ClientClass& template_class, const ClientClass& subclass) :
+ template_class_(template_class), subclass_(subclass) {
+ }
+
+ /// @brief The template class name.
+ ClientClass template_class_;
+
+ /// @brief The subclass name.
+ ClientClass subclass_;
+ };
+
+ /// @brief Tag for the sequence index.
+ struct SubClassSequenceTag { };
+
+ /// @brief Tag for the name index.
+ struct SubClassNameTag { };
+
+ /// @brief the subclass multi-index.
+ typedef boost::multi_index_container<
+ SubClass,
+ boost::multi_index::indexed_by<
+ // First index is the sequence one.
+ boost::multi_index::sequenced<
+ boost::multi_index::tag<SubClassSequenceTag>
+ >,
+ // Second index is the name hash one.
+ boost::multi_index::ordered_unique<
+ boost::multi_index::tag<SubClassNameTag>,
+ boost::multi_index::member<SubClass, ClientClass, &SubClass::subclass_>
+ >
+ >
+ > SubClassContainer;
+
/// @brief Container for storing client class names
///
/// Both a list to iterate on it in insert order and unordered
///
/// @param class_names A string containing a client classes separated
/// with commas. The class names are trimmed before insertion to the set.
- ClientClasses(const ClientClass& class_names);
+ ClientClasses(const std::string& class_names);
/// @brief Insert an element.
///
ClientClassContainer container_;
};
}
-
}
#endif /* CLASSIFY_H */
}
bool
-Pkt::inClass(const std::string& client_class) {
- return (classes_.contains(client_class));
+Pkt::inClass(const ClientClass& client_class) {
+ if (classes_.contains(client_class)) {
+ return (true);
+ }
+ auto const& idx = subclasses_.get<SubClassNameTag>();
+ if (idx.count(client_class)) {
+ return (true);
+ }
+ return (false);
}
void
-Pkt::addClass(const std::string& client_class, bool required) {
+Pkt::addClass(const ClientClass& client_class, bool required) {
// Always have ALL first.
if (classes_.empty()) {
classes_.insert("ALL");
}
}
+void
+Pkt::addSubClass(const ClientClass& template_class, const ClientClass& subclass) {
+ auto const& idx = subclasses_.get<SubClassNameTag>();
+ if (idx.count(subclass)) {
+ return;
+ }
+ subclasses_.push_back(SubClass(template_class, subclass));
+}
+
void
Pkt::updateTimestamp() {
timestamp_ = boost::posix_time::microsec_clock::universal_time();
/// @note This buffer is only valid till object that returned it exists.
///
/// @return reference to output buffer
- isc::util::OutputBuffer& getBuffer() { return (buffer_out_); };
+ isc::util::OutputBuffer& getBuffer() {
+ return (buffer_out_);
+ }
/// @brief Adds an option to this packet.
///
/// @brief Sets transaction-id value.
///
/// @param transid transaction-id to be set.
- void setTransid(uint32_t transid) { transid_ = transid; }
+ void setTransid(uint32_t transid) {
+ transid_ = transid;
+ }
/// @brief Returns value of transaction-id field.
///
/// @return transaction-id
- uint32_t getTransid() const { return (transid_); };
+ uint32_t getTransid() const {
+ return (transid_);
+ }
/// @brief Checks whether a client belongs to a given class.
///
void addClass(const isc::dhcp::ClientClass& client_class,
bool required = false);
+ /// @brief Adds packet to a specified subclass.
+ ///
+ /// A packet can be added to the same subclass repeatedly. Any additional
+ /// attempts to add to a subclass the packet already belongs to, will be
+ /// ignored silently.
+ ///
+ /// @note It is a matter of naming convention. Conceptually, the server
+ /// processes a stream of packets, with some packets belonging to given
+ /// subclasses. From that perspective, this method adds a packet to specified
+ /// subclass. Implementation wise, it looks the opposite - the subclass name
+ /// is added to the packet. Perhaps the most appropriate name for this
+ /// method would be associateWithSubClass()? But that seems overly long,
+ /// so I decided to stick with addSubClass().
+ /// @note The template class name is used to access the class definition.
+ ///
+ /// @param template_class name of the template class to be added
+ /// @param subclass name of the subclass to be added
+ void addSubClass(const isc::dhcp::ClientClass& template_class,
+ const isc::dhcp::ClientClass& subclass);
+
/// @brief Returns the class set
///
/// @note This should be used only to iterate over the class set.
return (!required ? classes_ : required_classes_);
}
+ /// @brief Returns the subclass set
+ ///
+ /// @note This should be used only to iterate over the subclass set.
+ const SubClassContainer& getSubClasses() const {
+ return (subclasses_);
+ }
+
+ /// @brief Returns the class set including template classes associated with
+ /// subclasses
+ ///
+ /// @note This should be used only to iterate over the class set.
+ /// @note Template classes are always last.
+ /// @param required return classes or required to be evaluated classes.
+ /// @return if required is false (the default) the classes the
+ /// packet belongs to else the classes which are required to be
+ /// evaluated.
+ const ClientClasses getClassesAndTemplates(bool required = false) const {
+ ClientClasses result = getClasses(required);
+ for (auto const& sclass : subclasses_) {
+ result.insert(sclass.template_class_);
+ }
+ return (result);
+ }
+
+ /// @brief Returns the class set including template classes associated with
+ /// subclasses
+ ///
+ /// @note This should be used only to iterate over the class set.
+ /// @note SubClasses are always last.
+ /// @param required return classes or required to be evaluated classes.
+ /// @return if required is false (the default) the classes the
+ /// packet belongs to else the classes which are required to be
+ /// evaluated.
+ const ClientClasses getClassesAndSubClasses(bool required = false) const {
+ ClientClasses result = getClasses(required);
+ for (auto const& sclass : subclasses_) {
+ result.insert(sclass.subclass_);
+ }
+ return (result);
+ }
+
/// @brief Unparsed data (in received packets).
///
/// @warning This public member is accessed by derived
/// @param ifindex specifies interface index.
void setIndex(int ifindex) {
ifindex_ = ifindex;
- };
+ }
/// @brief Resets interface index to negative value.
void resetIndex() {
/// @return interface index
int getIndex() const {
return (ifindex_);
- };
+ }
/// @brief Checks if interface index has been set.
///
/// going to be transmitted.
///
/// @return interface name
- std::string getIface() const { return (iface_); };
+ std::string getIface() const {
+ return (iface_);
+ }
/// @brief Sets interface name.
///
/// going to be transmitted.
///
/// @param iface The interface name
- void setIface(const std::string& iface) { iface_ = iface; };
+ void setIface(const std::string& iface) {
+ iface_ = iface;
+ }
/// @brief Sets remote hardware address.
///
/// This field is public, so the code outside of Pkt4 or Pkt6 class can
/// iterate over existing classes. Having it public also solves the problem
/// of returned reference lifetime. It is preferred to use @ref inClass and
- /// @ref addClass should be used to operate on this field.
+ /// @ref addClass to operate on this field.
ClientClasses classes_;
/// @brief Classes which are required to be evaluated.
/// and if evaluation status is true added to the previous collection.
ClientClasses required_classes_;
+ /// @brief SubClasses this packet belongs to.
+ ///
+ /// This field is public, so the code outside of Pkt4 or Pkt6 class can
+ /// iterate over existing classes. Having it public also solves the problem
+ /// of returned reference lifetime. It is preferred to use @ref inClass and
+ /// @ref addSubClass to operate on this field.
+ SubClassContainer subclasses_;
+
/// @brief Collection of options present in this message.
///
/// @warning This public member is accessed by derived
EXPECT_FALSE(pkt.inClass(DOCSIS3_CLASS_EROUTER));
EXPECT_FALSE(pkt.inClass(DOCSIS3_CLASS_MODEM));
EXPECT_TRUE(pkt.getClasses().empty());
+ EXPECT_TRUE(pkt.getSubClasses().empty());
// Add to the first class
pkt.addClass(DOCSIS3_CLASS_EROUTER);
EXPECT_TRUE(pkt.inClass(DOCSIS3_CLASS_EROUTER));
EXPECT_FALSE(pkt.inClass(DOCSIS3_CLASS_MODEM));
ASSERT_FALSE(pkt.getClasses().empty());
+ EXPECT_TRUE(pkt.getSubClasses().empty());
// Add to a second class
pkt.addClass(DOCSIS3_CLASS_MODEM);
EXPECT_NO_THROW(pkt.addClass("foo"));
EXPECT_NO_THROW(pkt.addClass("foo"));
EXPECT_NO_THROW(pkt.addClass("foo"));
+ EXPECT_TRUE(pkt.getSubClasses().empty());
// Check that the packet belongs to 'foo'
EXPECT_TRUE(pkt.inClass("foo"));
// Default values (do not belong to any class)
EXPECT_TRUE(pkt.getClasses(true).empty());
+ EXPECT_TRUE(pkt.getSubClasses().empty());
// Add to the first class
pkt.addClass(DOCSIS3_CLASS_EROUTER, true);
EXPECT_EQ(1, pkt.getClasses(true).size());
+ EXPECT_TRUE(pkt.getSubClasses().empty());
// Add to a second class
pkt.addClass(DOCSIS3_CLASS_MODEM, true);
EXPECT_EQ(2, pkt.getClasses(true).size());
+ EXPECT_TRUE(pkt.getSubClasses().empty());
EXPECT_TRUE(pkt.getClasses(true).contains(DOCSIS3_CLASS_EROUTER));
EXPECT_TRUE(pkt.getClasses(true).contains(DOCSIS3_CLASS_MODEM));
EXPECT_FALSE(pkt.getClasses(true).contains("foo"));
EXPECT_NO_THROW(pkt.addClass("foo", true));
EXPECT_NO_THROW(pkt.addClass("foo", true));
EXPECT_NO_THROW(pkt.addClass("foo", true));
+ EXPECT_TRUE(pkt.getSubClasses().empty());
// Check that the packet belongs to 'foo'
EXPECT_TRUE(pkt.getClasses(true).contains("foo"));
}
+// Tests whether a packet can be assigned to a subclass and later
+// checked if it belongs to a given subclass
+TEST_F(Pkt4Test, templateClasses) {
+ Pkt4 pkt(DHCPOFFER, 1234);
+
+ // Default values (do not belong to any subclass)
+ EXPECT_FALSE(pkt.inClass("eth0"));
+ EXPECT_FALSE(pkt.inClass("interface-id0"));
+ EXPECT_TRUE(pkt.getClasses().empty());
+ EXPECT_TRUE(pkt.getSubClasses().empty());
+
+ // Add to the first subclass
+ pkt.addSubClass("template-interface-name", "eth0");
+ EXPECT_TRUE(pkt.inClass("eth0"));
+ EXPECT_FALSE(pkt.inClass("interface-id0"));
+ ASSERT_TRUE(pkt.getClasses().empty());
+ ASSERT_FALSE(pkt.getSubClasses().empty());
+
+ // Add to a second subclass
+ pkt.addSubClass("template-interface-id", "interface-id0");
+ EXPECT_TRUE(pkt.inClass("eth0"));
+ EXPECT_TRUE(pkt.inClass("interface-id0"));
+
+ // Check that it's ok to add to the same subclass repeatedly
+ EXPECT_NO_THROW(pkt.addSubClass("template-foo", "bar"));
+ EXPECT_NO_THROW(pkt.addSubClass("template-foo", "bar"));
+ EXPECT_NO_THROW(pkt.addSubClass("template-bar", "bar"));
+
+ // Check that the packet belongs to 'bar'
+ EXPECT_TRUE(pkt.inClass("bar"));
+}
+
// Tests whether MAC can be obtained and that MAC sources are not
// confused.
TEST_F(Pkt4Test, getMAC) {
EXPECT_FALSE(pkt.inClass(DOCSIS3_CLASS_EROUTER));
EXPECT_FALSE(pkt.inClass(DOCSIS3_CLASS_MODEM));
EXPECT_TRUE(pkt.getClasses().empty());
+ EXPECT_TRUE(pkt.getSubClasses().empty());
// Add to the first class
pkt.addClass(DOCSIS3_CLASS_EROUTER);
EXPECT_TRUE(pkt.inClass(DOCSIS3_CLASS_EROUTER));
EXPECT_FALSE(pkt.inClass(DOCSIS3_CLASS_MODEM));
ASSERT_FALSE(pkt.getClasses().empty());
+ EXPECT_TRUE(pkt.getSubClasses().empty());
// Add to a second class
pkt.addClass(DOCSIS3_CLASS_MODEM);
EXPECT_NO_THROW(pkt.addClass("foo"));
EXPECT_NO_THROW(pkt.addClass("foo"));
EXPECT_NO_THROW(pkt.addClass("foo"));
+ EXPECT_TRUE(pkt.getSubClasses().empty());
// Check that the packet belongs to 'foo'
EXPECT_TRUE(pkt.inClass("foo"));
// Default values (do not belong to any class)
EXPECT_TRUE(pkt.getClasses(true).empty());
+ EXPECT_TRUE(pkt.getSubClasses().empty());
// Add to the first class
pkt.addClass(DOCSIS3_CLASS_EROUTER, true);
EXPECT_EQ(1, pkt.getClasses(true).size());
+ EXPECT_TRUE(pkt.getSubClasses().empty());
// Add to a second class
pkt.addClass(DOCSIS3_CLASS_MODEM, true);
EXPECT_EQ(2, pkt.getClasses(true).size());
+ EXPECT_TRUE(pkt.getSubClasses().empty());
EXPECT_TRUE(pkt.getClasses(true).contains(DOCSIS3_CLASS_EROUTER));
EXPECT_TRUE(pkt.getClasses(true).contains(DOCSIS3_CLASS_MODEM));
EXPECT_FALSE(pkt.getClasses(true).contains("foo"));
EXPECT_NO_THROW(pkt.addClass("foo", true));
EXPECT_NO_THROW(pkt.addClass("foo", true));
EXPECT_NO_THROW(pkt.addClass("foo", true));
+ EXPECT_TRUE(pkt.getSubClasses().empty());
// Check that the packet belongs to 'foo'
EXPECT_TRUE(pkt.getClasses(true).contains("foo"));
}
+// Tests whether a packet can be assigned to a subclass and later
+// checked if it belongs to a given subclass
+TEST_F(Pkt6Test, templateClasses) {
+ Pkt6 pkt(DHCPV6_ADVERTISE, 1234);
+
+ // Default values (do not belong to any subclass)
+ EXPECT_FALSE(pkt.inClass("eth0"));
+ EXPECT_FALSE(pkt.inClass("interface-id0"));
+ EXPECT_TRUE(pkt.getClasses().empty());
+ EXPECT_TRUE(pkt.getSubClasses().empty());
+
+ // Add to the first subclass
+ pkt.addSubClass("template-interface-name", "eth0");
+ EXPECT_TRUE(pkt.inClass("eth0"));
+ EXPECT_FALSE(pkt.inClass("interface-id0"));
+ ASSERT_TRUE(pkt.getClasses().empty());
+ ASSERT_FALSE(pkt.getSubClasses().empty());
+
+ // Add to a second subclass
+ pkt.addSubClass("template-interface-id", "interface-id0");
+ EXPECT_TRUE(pkt.inClass("eth0"));
+ EXPECT_TRUE(pkt.inClass("interface-id0"));
+
+ // Check that it's ok to add to the same subclass repeatedly
+ EXPECT_NO_THROW(pkt.addSubClass("template-foo", "bar"));
+ EXPECT_NO_THROW(pkt.addSubClass("template-foo", "bar"));
+ EXPECT_NO_THROW(pkt.addSubClass("template-bar", "bar"));
+
+ // Check that the packet belongs to 'bar'
+ EXPECT_TRUE(pkt.inClass("bar"));
+}
+
// Tests whether MAC can be obtained and that MAC sources are not
// confused.
TEST_F(Pkt6Test, getMAC) {
// If the subnet belongs to a shared network we will be iterating
// over the subnets that belong to this shared network.
Subnet6Ptr current_subnet = ctx.subnet_;
- while (current_subnet) {
+ auto const& classes = ctx.query_->getClassesAndSubClasses();
- if (current_subnet->clientSupported(ctx.query_->getClasses())) {
+ while (current_subnet) {
+ if (current_subnet->clientSupported(classes)) {
if (check_subnet) {
if (current_subnet->inPool(lease_type, address)) {
return (true);
}
} else {
- if (current_subnet->inPool(lease_type, address,
- ctx.query_->getClasses())) {
+ if (current_subnet->inPool(lease_type, address, classes)) {
return (true);
}
}
}
}
+ auto const& classes = ctx.query_->getClassesAndSubClasses();
+
// We can only search for the reservation if a subnet has been selected.
while (subnet) {
// Only makes sense to get reservations if the client has access
// to the class and host reservations are enabled for this subnet.
- if (subnet->clientSupported(ctx.query_->getClasses()) &&
- subnet->getReservationsInSubnet()) {
+ if (subnet->clientSupported(classes) && subnet->getReservationsInSubnet()) {
// Iterate over configured identifiers in the order of preference
// and try to use each of them to search for the reservations.
if (use_single_query) {
// We need to get to the next subnet if this is a shared network. If it
// is not (a plain subnet), getNextSubnet will return NULL and we're
// done here.
- subnet = subnet->getNextSubnet(ctx.subnet_, ctx.query_->getClasses());
+ subnet = subnet->getNextSubnet(ctx.subnet_, classes);
}
}
CalloutHandle::CalloutNextStep callout_status = CalloutHandle::NEXT_STEP_CONTINUE;
- for (; subnet; subnet = subnet->getNextSubnet(original_subnet)) {
+ auto const& classes = ctx.query_->getClassesAndSubClasses();
- if (!subnet->clientSupported(ctx.query_->getClasses())) {
+ for (; subnet; subnet = subnet->getNextSubnet(original_subnet)) {
+ if (!subnet->clientSupported(classes)) {
continue;
}
// check if the hint is in pool and is available
// This is equivalent of subnet->inPool(hint), but returns the pool
pool = boost::dynamic_pointer_cast<Pool6>
- (subnet->getPool(ctx.currentIA().type_, ctx.query_->getClasses(),
- hint));
+ (subnet->getPool(ctx.currentIA().type_, classes, hint));
// check if the pool is allowed
- if (!pool || !pool->clientSupported(ctx.query_->getClasses())) {
+ if (!pool || !pool->clientSupported(classes)) {
continue;
}
uint64_t subnets_with_unavail_pools = 0;
for (; subnet; subnet = subnet->getNextSubnet(original_subnet)) {
-
- if (!subnet->clientSupported(ctx.query_->getClasses())) {
+ if (!subnet->clientSupported(classes)) {
continue;
}
// - we find an address for which the lease has expired
// - we exhaust number of tries
uint64_t possible_attempts =
- subnet->getPoolCapacity(ctx.currentIA().type_,
- ctx.query_->getClasses());
+ subnet->getPoolCapacity(ctx.currentIA().type_, classes);
// If the number of tries specified in the allocation engine constructor
// is set to 0 (unlimited) or the pools capacity is lower than that number,
++total_attempts;
IOAddress candidate = allocator->pickAddress(subnet,
- ctx.query_->getClasses(),
+ classes,
ctx.duid_,
hint);
// The first step is to find out prefix length. It is 128 for
if (ctx.currentIA().type_ == Lease::TYPE_PD) {
pool = boost::dynamic_pointer_cast<Pool6>(
subnet->getPool(ctx.currentIA().type_,
- ctx.query_->getClasses(),
+ classes,
candidate));
if (pool) {
prefix_len = pool->getLength();
static_cast<int64_t>(1));
}
- const ClientClasses& classes = ctx.query_->getClasses();
if (!classes.empty()) {
LOG_WARN(alloc_engine_logger, ALLOC_ENGINE_V6_ALLOC_FAIL_CLASSES)
.arg(ctx.query_->getLabel())
// There is no lease for a reservation in this IA. So, let's now iterate
// over reservations specified and try to allocate one of them for the IA.
+ auto const& classes = ctx.query_->getClassesAndSubClasses();
for (Subnet6Ptr subnet = ctx.subnet_; subnet;
subnet = subnet->getNextSubnet(ctx.subnet_)) {
SubnetID subnet_id = subnet->getID();
// No hosts for this subnet or the subnet not supported.
- if (!subnet->clientSupported(ctx.query_->getClasses()) ||
- ctx.hosts_.count(subnet_id) == 0) {
+ if (!subnet->clientSupported(classes) || ctx.hosts_.count(subnet_id) == 0) {
continue;
}
// 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();
+ const ClientClasses classes = ctx.query_->getClassesAndTemplates();
if (!classes.empty()) {
// Let's get class definitions
const ClientClassDictionaryPtr& dict =
CfgMgr::instance().getCurrentCfg()->getClientClassDictionary();
- // Iterate over the assigned class defintions.
+ // Iterate over the assigned class definitions.
int have_both = 0;
for (auto name = classes.cbegin();
name != classes.cend() && have_both < 2; ++name) {
// is not permitted by subnet client classification, delete it.
if (!(ctx.hasGlobalReservation(makeIPv6Resrv(*lease))) &&
(((lease->type_ != Lease::TYPE_PD) && !ctx.subnet_->inRange(lease->addr_)) ||
- !ctx.subnet_->clientSupported(ctx.query_->getClasses()))) {
+ !ctx.subnet_->clientSupported(ctx.query_->getClassesAndSubClasses()))) {
// Oh dear, the lease is no longer valid. We need to get rid of it.
// Remove this lease from LeaseMgr
// No address reservation found here, so let's try another subnet
// within the same shared network.
- subnet = subnet->getNextSubnet(ctx.subnet_, ctx.query_->getClasses());
+ subnet = subnet->getNextSubnet(ctx.subnet_, ctx.query_->getClassesAndSubClasses());
}
return (false);
Subnet4Ptr original_subnet = ctx.subnet_;
+ auto const& classes = ctx.query_->getClassesAndSubClasses();
+
// Client identifier is optional. First check if we can try to lookup
// by client-id.
bool try_clientid_lookup = (ctx.clientid_ &&
- SharedNetwork4::subnetsIncludeMatchClientId(original_subnet,
- ctx.query_->getClasses()));
+ SharedNetwork4::subnetsIncludeMatchClientId(original_subnet, classes));
// If it is possible to use client identifier to try to find client's lease.
if (try_clientid_lookup) {
// Iterate over the subnets within the shared network to see if any client's
// lease belongs to them.
for (Subnet4Ptr subnet = original_subnet; subnet;
- subnet = subnet->getNextSubnet(original_subnet,
- ctx.query_->getClasses())) {
+ subnet = subnet->getNextSubnet(original_subnet, classes)) {
// If client identifier has been supplied and the server wasn't
// explicitly configured to ignore client identifiers for this subnet
Lease4Collection leases_hw_address = lease_mgr.getLease4(*ctx.hwaddr_);
for (Subnet4Ptr subnet = original_subnet; subnet;
- subnet = subnet->getNextSubnet(original_subnet,
- ctx.query_->getClasses())) {
+ subnet = subnet->getNextSubnet(original_subnet, classes)) {
ClientIdPtr client_id;
if (subnet->getMatchClientId()) {
client_id = ctx.clientid_;
// If the subnet belongs to a shared network we will be iterating
// over the subnets that belong to this shared network.
Subnet4Ptr current_subnet = ctx.subnet_;
- while (current_subnet) {
+ auto const& classes = ctx.query_->getClassesAndSubClasses();
- if (current_subnet->inPool(Lease::TYPE_V4, address,
- ctx.query_->getClasses())) {
+ while (current_subnet) {
+ if (current_subnet->inPool(Lease::TYPE_V4, address, classes)) {
// We found a subnet that this address belongs to, so it
// seems that this subnet is the good candidate for allocation.
// Let's update the selected subnet.
return (true);
}
- current_subnet = current_subnet->getNextSubnet(ctx.subnet_,
- ctx.query_->getClasses());
+ current_subnet = current_subnet->getNextSubnet(ctx.subnet_, classes);
}
return (false);
// subnets allowed for this client within the shared network, we
// can't allocate a lease.
Subnet4Ptr subnet = ctx.subnet_;
- if (subnet && !subnet->clientSupported(ctx.query_->getClasses())) {
- ctx.subnet_ = subnet->getNextSubnet(subnet, ctx.query_->getClasses());
+ auto const& classes = ctx.query_->getClassesAndSubClasses();
+ if (subnet && !subnet->clientSupported(classes)) {
+ ctx.subnet_ = subnet->getNextSubnet(subnet, classes);
}
try {
}
}
+ auto const& classes = ctx.query_->getClassesAndSubClasses();
// We can only search for the reservation if a subnet has been selected.
while (subnet) {
// Only makes sense to get reservations if the client has access
// to the class and host reservations are enabled for this subnet.
- if (subnet->clientSupported(ctx.query_->getClasses()) &&
- subnet->getReservationsInSubnet()) {
+ if (subnet->clientSupported(classes) && subnet->getReservationsInSubnet()) {
// Iterate over configured identifiers in the order of preference
// and try to use each of them to search for the reservations.
if (use_single_query) {
// We need to get to the next subnet if this is a shared network. If it
// is not (a plain subnet), getNextSubnet will return NULL and we're
// done here.
- subnet = subnet->getNextSubnet(ctx.subnet_, ctx.query_->getClasses());
+ subnet = subnet->getNextSubnet(ctx.subnet_, classes);
}
}
// If the triplet is specified in one of our classes use it.
// We use the first one we find.
Triplet<uint32_t> candidate_lft;
- const ClientClasses classes = ctx.query_->getClasses();
+ const ClientClasses classes = ctx.query_->getClassesAndTemplates();
if (!classes.empty()) {
// Let's get class definitions
const ClientClassDictionaryPtr& dict =
CfgMgr::instance().getCurrentCfg()->getClientClassDictionary();
- // Iterate over the assigned class defintions.
+ // Iterate over the assigned class definitions.
for (ClientClasses::const_iterator name = classes.cbegin();
name != classes.cend(); ++name) {
ClientClassDefPtr cl = dict->findClass(*name);
// no matching pools for the client.
uint64_t subnets_with_unavail_pools = 0;
+ auto const& classes = ctx.query_->getClassesAndSubClasses();
+
while (subnet) {
ClientIdPtr client_id;
if (subnet->getMatchClientId()) {
}
uint64_t possible_attempts =
- subnet->getPoolCapacity(Lease::TYPE_V4,
- ctx.query_->getClasses());
+ subnet->getPoolCapacity(Lease::TYPE_V4, classes);
// If the number of tries specified in the allocation engine constructor
// is set to 0 (unlimited) or the pools capacity is lower than that number,
++total_attempts;
IOAddress candidate = allocator->pickAddress(subnet,
- ctx.query_->getClasses(),
+ classes,
client_id,
ctx.requested_address_);
// First check for reservation when it is the choice.
// This pointer may be set to NULL if hooks set SKIP status.
if (subnet) {
- subnet = subnet->getNextSubnet(original_subnet, ctx.query_->getClasses());
+ subnet = subnet->getNextSubnet(original_subnet, classes);
if (subnet) {
ctx.subnet_ = subnet;
static_cast<int64_t>(1));
}
- const ClientClasses& classes = ctx.query_->getClasses();
if (!classes.empty()) {
LOG_WARN(alloc_engine_logger, ALLOC_ENGINE_V4_ALLOC_FAIL_CLASSES)
.arg(ctx.query_->getLabel())
#include <config.h>
#include <eval/dependency.h>
+#include <eval/evaluate.h>
+#include <eval/eval_log.h>
#include <dhcpsrv/client_class_def.h>
#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/dhcpsrv_log.h>
#include <dhcpsrv/parsers/client_class_def_parser.h>
#include <boost/foreach.hpp>
cfg_option_ = cfg_option;
}
+void
+ClientClassDef::test(PktPtr pkt, const ExpressionPtr& expr_ptr) {
+ // Evaluate the expression which can return false (no match),
+ // true (match) or raise an exception (error)
+ try {
+ bool status = evaluateBool(*expr_ptr, *pkt);
+ if (status) {
+ LOG_INFO(dhcpsrv_logger, EVAL_RESULT)
+ .arg(getName())
+ .arg(status);
+ // Matching: add the class
+ pkt->addClass(getName());
+ } else {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, EVAL_RESULT)
+ .arg(getName())
+ .arg(status);
+ }
+ } catch (const Exception& ex) {
+ LOG_ERROR(dhcpsrv_logger, EVAL_RESULT)
+ .arg(getName())
+ .arg(ex.what());
+ } catch (...) {
+ LOG_ERROR(dhcpsrv_logger, EVAL_RESULT)
+ .arg(getName())
+ .arg("get exception?");
+ }
+}
+
+TemplateClientClassDef::TemplateClientClassDef(const std::string& name,
+ const ExpressionPtr& match_expr,
+ const CfgOptionPtr& options) :
+ ClientClassDef(name, match_expr, options) {
+}
+
+void
+TemplateClientClassDef::test(PktPtr pkt, const ExpressionPtr& expr_ptr) {
+ // Evaluate the expression which can return false (no match),
+ // true (match) or raise an exception (error)
+ try {
+ std::string subclass = evaluateString(*expr_ptr, *pkt);
+ if (!subclass.empty()) {
+ LOG_INFO(dhcpsrv_logger, EVAL_RESULT)
+ .arg(getName())
+ .arg(subclass);
+ // Matching: add the subclass
+ pkt->addSubClass(getName(), subclass);
+ }
+ } catch (const Exception& ex) {
+ LOG_ERROR(dhcpsrv_logger, EVAL_RESULT)
+ .arg(getName())
+ .arg(ex.what());
+ } catch (...) {
+ LOG_ERROR(dhcpsrv_logger, EVAL_RESULT)
+ .arg(getName())
+ .arg("get exception?");
+ }
+}
+
bool
ClientClassDef::dependOnClass(const std::string& name) const {
return (isc::dhcp::dependOnClass(match_expr_, name));
}
ElementPtr
-ClientClassDef:: toElement() const {
+ClientClassDef::toElement() const {
uint16_t family = CfgMgr::instance().getFamily();
ElementPtr result = Element::createMap();
// Set user-context
return (result);
}
+ElementPtr
+TemplateClientClassDef::toElement() const {
+ auto const& result = ClientClassDef::toElement();
+ result->set("template-class", Element::create(true));
+ return (result);
+}
+
std::ostream& operator<<(std::ostream& os, const ClientClassDef& x) {
os << "ClientClassDef:" << x.getName();
return (os);
const std::string& sname,
const std::string& filename,
const util::Triplet<uint32_t>& valid,
- const util::Triplet<uint32_t>& preferred) {
- ClientClassDefPtr cclass(new ClientClassDef(name, match_expr, cfg_option));
+ const util::Triplet<uint32_t>& preferred,
+ bool is_template) {
+ ClientClassDefPtr cclass;
+ if (is_template) {
+ cclass.reset(new TemplateClientClassDef(name, match_expr, cfg_option));
+ } else {
+ cclass.reset(new ClientClassDef(name, match_expr, cfg_option));
+ }
cclass->setTest(test);
cclass->setRequired(required);
cclass->setDependOnKnown(depend_on_known);
if (!c->getTest().empty()) {
ExpressionPtr match_expr = boost::make_shared<Expression>();
ExpressionParser parser;
- parser.parse(match_expr, Element::create(c->getTest()), family);
+ EvalContext::ParserType parser_type = EvalContext::PARSER_BOOL;
+ if (dynamic_cast<TemplateClientClassDef*>(c.get())) {
+ parser_type = EvalContext::PARSER_STRING;
+ }
+ parser.parse(match_expr, Element::create(c->getTest()), family,
+ EvalContext::acceptAll, parser_type);
expressions.push(match_expr);
}
}
ClientClassDef(const std::string& name, const ExpressionPtr& match_expr,
const CfgOptionPtr& options = CfgOptionPtr());
-
/// Copy constructor
ClientClassDef(const ClientClassDef& rhs);
preferred_ = preferred;
}
+ /// @brief Test method which checks if the packet belongs to the class
+ ///
+ /// If the packet belongs to the class, the class is added to the packet.
+ ///
+ /// @param pkt The packet checked if it belongs to the class.
+ virtual void test(PktPtr pkt, const ExpressionPtr& expr_ptr);
+
/// @brief Unparse a configuration object
///
/// @return a pointer to unparsed configuration
util::Triplet<uint32_t> preferred_;
};
+class TemplateClientClassDef : public ClientClassDef {
+public:
+ /// @brief Constructor
+ ///
+ /// @param name Name to assign to this class
+ /// @param match_expr Expression the class will use to determine membership
+ /// @param options Collection of options members should be given
+ TemplateClientClassDef(const std::string& name, const ExpressionPtr& match_expr,
+ const CfgOptionPtr& options = CfgOptionPtr());
+
+ /// @brief Test method which checks if the packet belongs to the class
+ ///
+ /// If the packet belongs to the class, the class is added to the packet.
+ ///
+ /// @param pkt The packet checked if it belongs to the class.
+ virtual void test(PktPtr pkt, const ExpressionPtr& expr_ptr) override;
+
+ /// @brief Unparse a configuration object
+ ///
+ /// @return a pointer to unparsed configuration
+ virtual isc::data::ElementPtr toElement() const;
+};
+
/// @brief a pointer to an ClientClassDef
typedef boost::shared_ptr<ClientClassDef> ClientClassDefPtr;
const std::string& sname = std::string(),
const std::string& filename = std::string(),
const util::Triplet<uint32_t>&valid = util::Triplet<uint32_t>(),
- const util::Triplet<uint32_t>&preferred = util::Triplet<uint32_t>());
+ const util::Triplet<uint32_t>&preferred = util::Triplet<uint32_t>(),
+ bool is_template = false);
/// @brief Adds a new class to the list
///
lease_file.reset(new LeaseFileType(filename));
LeaseFileLoader::load<LeaseObjectType>(*lease_file, storage,
max_row_errors, false);
- conversion_needed = conversion_needed || lease_file->needsConversion();
+ conversion_needed = conversion_needed || lease_file->needsConversion();
return (conversion_needed);
}
query->start();
}
- return(query);
+ return (query);
}
LeaseStatsQueryPtr
query->start();
}
- return(query);
+ return (query);
}
LeaseStatsQueryPtr
query->start();
}
- return(query);
+ return (query);
}
LeaseStatsQueryPtr
query->start();
}
- return(query);
+ return (query);
}
LeaseStatsQueryPtr
query->start();
}
- return(query);
+ return (query);
}
LeaseStatsQueryPtr
query->start();
}
- return(query);
+ return (query);
}
size_t
const Lease::Type& ltype /* = Lease::TYPE_V4*/) const {
if (MultiThreadingMgr::instance().getMode()) {
std::lock_guard<std::mutex> lock(*mutex_);
- return(class_lease_counter_.getClassCount(client_class, ltype));
+ return (class_lease_counter_.getClassCount(client_class, ltype));
} else {
- return(class_lease_counter_.getClassCount(client_class, ltype));
+ return (class_lease_counter_.getClassCount(client_class, ltype));
}
}
void
Memfile_LeaseMgr::clearClassLeaseCounts() {
- return(class_lease_counter_.clear());
+ return (class_lease_counter_.clear());
}
std::string
ExpressionParser::parse(ExpressionPtr& expression,
ConstElementPtr expression_cfg,
uint16_t family,
- EvalContext::CheckDefined check_defined) {
+ EvalContext::CheckDefined check_defined,
+ EvalContext::ParserType parser_type) {
if (expression_cfg->getType() != Element::string) {
isc_throw(DhcpConfigError, "expression ["
<< expression_cfg->str() << "] must be a string, at ("
try {
EvalContext eval_ctx(family == AF_INET ? Option::V4 : Option::V6,
check_defined);
- eval_ctx.parseString(value);
+ eval_ctx.parseString(value, parser_type);
expression.reset(new Expression());
*expression = eval_ctx.expression;
} catch (const std::exception& ex) {
// Append position if there is a failure.
isc_throw(DhcpConfigError,
"expression: [" << value
- << "] error: " << ex.what() << " at ("
- << expression_cfg->getPosition() << ")");
+ << "] error: " << ex.what() << " at ("
+ << expression_cfg->getPosition() << ")");
}
}
<< getPosition("name", class_def_cfg) << ")");
}
+ EvalContext::ParserType parser_type = EvalContext::PARSER_BOOL;
+
+ // Let's try to parse the template-class flag
+ bool is_template = false;
+ if (class_def_cfg->contains("template-class")) {
+ is_template = getBoolean(class_def_cfg, "template-class");
+ if (is_template) {
+ parser_type = EvalContext::PARSER_STRING;
+ }
+ }
+
// Parse matching expression
ExpressionPtr match_expr;
ConstElementPtr test_cfg = class_def_cfg->get("test");
depend_on_known,
cclass));
};
- parser.parse(match_expr, test_cfg, family, check_defined);
+ parser.parse(match_expr, test_cfg, family, check_defined, parser_type);
test = test_cfg->stringValue();
}
<< filename.length() << " ("
<< getPosition("boot-file-name", class_def_cfg) << ")");
}
-
}
// Parse valid lifetime triplet.
class_dictionary->addClass(name, match_expr, test, required,
depend_on_known, options, defs,
user_context, next_server, sname, filename,
- valid_lft, preferred_lft);
+ valid_lft, preferred_lft, is_template);
} catch (const std::exception& ex) {
std::ostringstream s;
s << "Can't add class: " << ex.what();
"only-if-required",
"valid-lifetime",
"min-valid-lifetime",
- "max-valid-lifetime" };
-
+ "max-valid-lifetime",
+ "template-class"};
// The v4 client class supports additional parameters.
static std::set<std::string> supported_params_v4 = { "option-def",
/// @param expression_cfg the configuration entry to be parsed.
/// @param family the address family of the expression.
/// @param check_defined a closure to check if a client class is defined.
+ /// @param parser_type the expected type of the evaluated expression.
///
/// @throw DhcpConfigError if parsing was unsuccessful.
void parse(ExpressionPtr& expression,
isc::data::ConstElementPtr expression_cfg,
uint16_t family,
- isc::eval::EvalContext::CheckDefined check_defined =
- isc::eval::EvalContext::acceptAll);
+ isc::eval::EvalContext::CheckDefined check_defined = isc::eval::EvalContext::acceptAll,
+ isc::eval::EvalContext::ParserType parser_type = isc::eval::EvalContext::PARSER_BOOL);
};
/// @brief Parser for a single client class definition.
#include <dhcp/option_int.h>
#include <dhcpsrv/shared_network.h>
#include <dhcpsrv/host_mgr.h>
+#include <dhcpsrv/parsers/client_class_def_parser.h>
#include <dhcpsrv/tests/alloc_engine_utils.h>
#include <dhcpsrv/testutils/test_utils.h>
-#include <testutils/gtest_utils.h>
+#include <eval/eval_context.h>
#include <hooks/hooks_manager.h>
#include <hooks/callout_handle.h>
#include <stats/stats_mgr.h>
+#include <testutils/gtest_utils.h>
#if defined HAVE_MYSQL
#include <mysql/testutils/mysql_schema.h>
}
}
+// Verifies that AllocEngine::getValidLft(ctx4) returns the appropriate
+// lifetime value based on the context content.
+TEST_F(AllocEngine4Test, getTemplateClassValidLft4) {
+ AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 0, false);
+
+ // Let's make three classes, two with valid-lifetime and one without,
+ // and add them to the dictionary.
+ ClientClassDictionaryPtr dictionary = CfgMgr::instance().getStagingCfg()->getClientClassDictionary();
+ ExpressionPtr match_expr;
+ ExpressionParser parser;
+
+ ElementPtr test_cfg = Element::create("'valid_one_value'");
+ parser.parse(match_expr, test_cfg, AF_INET, EvalContext::acceptAll, EvalContext::PARSER_STRING);
+
+ ClientClassDefPtr class_def(new TemplateClientClassDef("valid_one", match_expr));
+ Triplet<uint32_t> valid_one(50, 100, 150);
+ class_def->setValid(valid_one);
+ dictionary->addClass(class_def);
+
+ test_cfg = Element::create("'valid_two_value'");
+ parser.parse(match_expr, test_cfg, AF_INET, EvalContext::acceptAll, EvalContext::PARSER_STRING);
+
+ class_def.reset(new TemplateClientClassDef("valid_two", match_expr));
+ Triplet<uint32_t>valid_two(200, 250, 300);
+ class_def->setValid(valid_two);
+ dictionary->addClass(class_def);
+
+ test_cfg = Element::create("'valid_unspec_value'");
+ parser.parse(match_expr, test_cfg, AF_INET, EvalContext::acceptAll, EvalContext::PARSER_STRING);
+
+ class_def.reset(new TemplateClientClassDef("valid_unspec", match_expr));
+ 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 = {
+ {
+ "BOOTP",
+ { "BOOTP" },
+ 0,
+ Lease::INFINITY_LFT
+ },
+ {
+ "no classes, no option",
+ {},
+ 0,
+ subnet_->getValid()
+ },
+ {
+ "no classes, option",
+ {},
+ subnet_->getValid().getMin() + 50,
+ subnet_->getValid().getMin() + 50
+ },
+ {
+ "no classes, option too small",
+ {},
+ subnet_->getValid().getMin() - 50,
+ subnet_->getValid().getMin()
+ },
+ {
+ "no classes, option too big",
+ {},
+ subnet_->getValid().getMax() + 50,
+ subnet_->getValid().getMax()
+ },
+ {
+ "class unspecified, no option",
+ { "valid_unspec" },
+ 0,
+ subnet_->getValid()
+ },
+ {
+ "from last class, no option",
+ { "valid_unspec", "valid_one" },
+ 0,
+ valid_one.get()
+ },
+ {
+ "from first class, no option",
+ { "valid_two", "valid_one" },
+ 0,
+ valid_two.get()
+ },
+ {
+ "class plus option",
+ { "valid_one" },
+ valid_one.getMin() + 25,
+ valid_one.getMin() + 25
+ },
+ {
+ "class plus option too small",
+ { "valid_one" },
+ valid_one.getMin() - 25,
+ valid_one.getMin()
+ },
+ {
+ "class plus option 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::ClientContext4 ctx(subnet_, ClientIdPtr(), hwaddr_,
+ IOAddress("0.0.0.0"), false, false,
+ "", false);
+ ctx.query_.reset(new Pkt4(DHCPDISCOVER, 1234));
+
+ // Add client classes (if any)
+ for (auto class_name : scenario.classes_) {
+ if (class_name == "BOOTP") {
+ ctx.query_->addClass(class_name);
+ } else {
+ ctx.query_->addSubClass(class_name, class_name + "_value");
+ }
+ }
+
+ // Add client option (if one)
+ if (scenario.requested_lft_) {
+ OptionUint32Ptr opt(new OptionUint32(Option::V4, DHO_DHCP_LEASE_TIME,
+ scenario.requested_lft_));
+ ctx.query_->addOption(opt);
+ }
+
+ Lease4Ptr lease = engine.allocateLease4(ctx);
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(lease->valid_lft_, scenario.exp_valid_);
+ }
+ }
+}
+
// This test checks that deleteRelease handles BOOTP leases.
TEST_F(AllocEngine4Test, bootpDelete) {
boost::scoped_ptr<AllocEngine> engine;
#include <dhcp/pkt4.h>
#include <dhcp/pkt6.h>
#include <dhcpsrv/host_mgr.h>
+#include <dhcpsrv/parsers/client_class_def_parser.h>
#include <dhcpsrv/tests/alloc_engine_utils.h>
#include <dhcpsrv/testutils/test_utils.h>
+#include <eval/eval_context.h>
#include <stats/stats_mgr.h>
#include <testutils/gtest_utils.h>
// Let's make three classes, two with valid-lifetime and one without,
// and add them to the dictionary.
- ClientClassDictionaryPtr dictionary =
- CfgMgr::instance().getStagingCfg()->getClientClassDictionary();
+ ClientClassDictionaryPtr dictionary = CfgMgr::instance().getStagingCfg()->getClientClassDictionary();
ClientClassDefPtr class_def(new ClientClassDef("valid_one", ExpressionPtr()));
Triplet<uint32_t> valid_one(50, 100, 150);
}
}
+// Verifies that AllocEngine::getLifetimes6() returns the appropriate
+// valid lifetime value based on the context content.
+TEST_F(AllocEngine6Test, getTemplateClassValidLifetime) {
+ 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();
+ ExpressionPtr match_expr;
+ ExpressionParser parser;
+
+ ElementPtr test_cfg = Element::create("'valid_one_value'");
+ parser.parse(match_expr, test_cfg, AF_INET6, EvalContext::acceptAll, EvalContext::PARSER_STRING);
+
+ ClientClassDefPtr class_def(new TemplateClientClassDef("valid_one", match_expr));
+ Triplet<uint32_t> valid_one(50, 100, 150);
+ class_def->setValid(valid_one);
+ dictionary->addClass(class_def);
+
+ test_cfg = Element::create("'valid_two_value'");
+ parser.parse(match_expr, test_cfg, AF_INET6, EvalContext::acceptAll, EvalContext::PARSER_STRING);
+
+ class_def.reset(new TemplateClientClassDef("valid_two", match_expr));
+ Triplet<uint32_t>valid_two(200, 250, 300);
+ class_def->setValid(valid_two);
+ dictionary->addClass(class_def);
+
+ test_cfg = Element::create("'valid_unspec_value'");
+ parser.parse(match_expr, test_cfg, AF_INET6, EvalContext::acceptAll, EvalContext::PARSER_STRING);
+
+ class_def.reset(new TemplateClientClassDef("valid_unspec", match_expr));
+ 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) {
// Let's make three classes, two with preferred-lifetime and one without,
// and add them to the dictionary.
- ClientClassDictionaryPtr dictionary =
- CfgMgr::instance().getStagingCfg()->getClientClassDictionary();
+ ClientClassDictionaryPtr dictionary = CfgMgr::instance().getStagingCfg()->getClientClassDictionary();
ClientClassDefPtr class_def(new ClientClassDef("preferred_one", ExpressionPtr()));
Triplet<uint32_t> preferred_one(50, 100, 150);
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
+ uint32_t exp_preferred_; // expected lifetime
};
// Scenarios to test.
Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234)));
// Add client classes (if any)
for (auto class_name : scenario.classes_) {
- ctx.query_->addClass(class_name);
+ ctx.query_->addSubClass(class_name, class_name + "_value");
+ }
+
+ // 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_);
+ }
+ }
+}
+
+// Verifies that AllocEngine::getLifetimes6() returns the appropriate
+// preferred lifetime value based on the context content.
+TEST_F(AllocEngine6Test, getTemplateClassPreferredLifetime) {
+ 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();
+ ExpressionPtr match_expr;
+ ExpressionParser parser;
+
+ ElementPtr test_cfg = Element::create("'preferred_one_value'");
+ parser.parse(match_expr, test_cfg, AF_INET6, EvalContext::acceptAll, EvalContext::PARSER_STRING);
+
+ ClientClassDefPtr class_def(new TemplateClientClassDef("preferred_one", match_expr));
+ Triplet<uint32_t> preferred_one(50, 100, 150);
+ class_def->setPreferred(preferred_one);
+ dictionary->addClass(class_def);
+
+ test_cfg = Element::create("'preferred_two_value'");
+ parser.parse(match_expr, test_cfg, AF_INET6, EvalContext::acceptAll, EvalContext::PARSER_STRING);
+
+ class_def.reset(new TemplateClientClassDef("preferred_two", match_expr));
+ Triplet<uint32_t>preferred_two(200, 250, 300);
+ class_def->setPreferred(preferred_two);
+ dictionary->addClass(class_def);
+
+ test_cfg = Element::create("'preferred_unspec_value'");
+ parser.parse(match_expr, test_cfg, AF_INET6, EvalContext::acceptAll, EvalContext::PARSER_STRING);
+
+ class_def.reset(new TemplateClientClassDef("preferred_unspec", match_expr));
+ 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_->addSubClass(class_name, class_name + "_value");
}
// Add hint
/// evaluated.
/// @param option_string String data to be placed in the hostname
/// option, being placed in the packet used for evaluation.
+ /// @param parser_type the expected type of the evaluated expression.
/// @tparam Type of the packet: @c Pkt4 or @c Pkt6.
template<typename PktType>
void testValidExpression(uint16_t family,
const std::string& expression,
- const std::string& option_string) {
+ const std::string& option_string,
+ EvalContext::ParserType parser_type = EvalContext::PARSER_BOOL) {
ExpressionPtr parsed_expr;
ExpressionParser parser;
ElementPtr config_element = Element::fromJSON(expression);
// Expression should parse.
- ASSERT_NO_THROW(parser.parse(parsed_expr, config_element, family));
+ ASSERT_NO_THROW(parser.parse(parsed_expr, config_element, family, EvalContext::acceptAll, parser_type));
// Parsed expression should exist.
ASSERT_TRUE(parsed_expr);
message_type = DHCPV6_SOLICIT;
}
boost::shared_ptr<PktType> pkt(new PktType(message_type, 123));
- EXPECT_FALSE(evaluateBool(*parsed_expr, *pkt));
+ if (parser_type == EvalContext::PARSER_BOOL) {
+ EXPECT_FALSE(evaluateBool(*parsed_expr, *pkt));
+ } else {
+ EXPECT_TRUE(evaluateString(*parsed_expr, *pkt).empty());
+ }
// Now add the option so it will pass. Use a standard option carrying a
// single string value, i.e. hostname for DHCPv4 and bootfile url for
}
OptionPtr opt(new OptionString(universe, option_type, option_string));
pkt->addOption(opt);
- EXPECT_TRUE(evaluateBool(*parsed_expr, *pkt));
+ if (parser_type == EvalContext::PARSER_BOOL) {
+ EXPECT_TRUE(evaluateBool(*parsed_expr, *pkt));
+ } else {
+ EXPECT_EQ(evaluateString(*parsed_expr, *pkt), option_string);
+ }
}
};
"hundred4");
}
+// Verifies that given a valid expression, the ExpressionParser
+// produces an Expression which can be evaluated against a v4 packet.
+TEST_F(ExpressionParserTest, templateValidExpression4) {
+ testValidExpression<Pkt4>(AF_INET,
+ "\"option[12].text\"",
+ "hundred4", EvalContext::PARSER_STRING);
+}
+
// Verifies that the option name can be used in the evaluated expression.
TEST_F(ExpressionParserTest, validExpressionWithOptionName4) {
testValidExpression<Pkt4>(AF_INET,
"hundred4");
}
+// Verifies that the option name can be used in the evaluated expression.
+TEST_F(ExpressionParserTest, templateValidExpressionWithOptionName4) {
+ testValidExpression<Pkt4>(AF_INET,
+ "\"option[host-name].text\"",
+ "hundred4", EvalContext::PARSER_STRING);
+}
+
// Verifies that given a valid expression using .hex operator for option, the
// ExpressionParser produces an Expression which can be evaluated against
// a v4 packet.
"hundred4");
}
+// Verifies that given a valid expression using .hex operator for option, the
+// ExpressionParser produces an Expression which can be evaluated against
+// a v4 packet.
+TEST_F(ExpressionParserTest, templateValidExpressionWithHex4) {
+ testValidExpression<Pkt4>(AF_INET,
+ "\"option[12].hex\"",
+ "hundred4", EvalContext::PARSER_STRING);
+}
+
// Verifies that the option name can be used together with .hex operator in
// the evaluated expression.
TEST_F(ExpressionParserTest, validExpressionWithOptionNameAndHex4) {
"hundred4");
}
+// Verifies that the option name can be used together with .hex operator in
+// the evaluated expression.
+TEST_F(ExpressionParserTest, templateValidExpressionWithOptionNameAndHex4) {
+ testValidExpression<Pkt6>(AF_INET,
+ "\"option[host-name].text\"",
+ "hundred4", EvalContext::PARSER_STRING);
+}
+
// Verifies that given a valid expression, the ExpressionParser
// produces an Expression which can be evaluated against a v6 packet.
TEST_F(ExpressionParserTest, validExpression6) {
"hundred6");
}
+// Verifies that given a valid expression, the ExpressionParser
+// produces an Expression which can be evaluated against a v6 packet.
+TEST_F(ExpressionParserTest, templateValidExpression6) {
+ testValidExpression<Pkt6>(AF_INET6,
+ "\"option[59].text\"",
+ "hundred6", EvalContext::PARSER_STRING);
+}
+
// Verifies that the option name can be used in the evaluated expression.
TEST_F(ExpressionParserTest, validExpressionWithOptionName6) {
testValidExpression<Pkt6>(AF_INET6,
"hundred6");
}
+// Verifies that the option name can be used in the evaluated expression.
+TEST_F(ExpressionParserTest, templateValidExpressionWithOptionName6) {
+ testValidExpression<Pkt6>(AF_INET6,
+ "\"option[bootfile-url].text\"",
+ "hundred6", EvalContext::PARSER_STRING);
+}
+
// Verifies that given a valid expression using .hex operator for option, the
// ExpressionParser produces an Expression which can be evaluated against
// a v6 packet.
"hundred6");
}
+// Verifies that given a valid expression using .hex operator for option, the
+// ExpressionParser produces an Expression which can be evaluated against
+// a v6 packet.
+TEST_F(ExpressionParserTest, templateValidExpressionWithHex6) {
+ testValidExpression<Pkt6>(AF_INET6,
+ "\"option[59].hex\"",
+ "hundred6", EvalContext::PARSER_STRING);
+}
+
// Verifies that the option name can be used together with .hex operator in
// the evaluated expression.
TEST_F(ExpressionParserTest, validExpressionWithOptionNameAndHex6) {
"hundred6");
}
+// Verifies that the option name can be used together with .hex operator in
+// the evaluated expression.
+TEST_F(ExpressionParserTest, templateValidExpressionWithOptionNameAndHex6) {
+ testValidExpression<Pkt6>(AF_INET6,
+ "\"option[bootfile-url].text\"",
+ "hundred6", EvalContext::PARSER_STRING);
+}
+
// Verifies that an the ExpressionParser only accepts StringElements.
TEST_F(ExpressionParserTest, invalidExpressionElement) {
// This will create an integer element should fail.
" \"option-data\": [ ],\n"
" \"user-context\": { },\n"
" \"only-if-required\": false,\n"
+ " \"valid-lifetime\": 1000,\n"
+ " \"min-valid-lifetime\": 1000,\n"
+ " \"max-valid-lifetime\": 1000,\n"
" \"next-server\": \"192.0.2.3\",\n"
+ " \"template-class\": true,\n"
" \"server-hostname\": \"myhost\",\n"
" \"boot-file-name\": \"efi\""
"}\n";
" \"test\": \"member('ALL')\","
" \"option-data\": [ ],\n"
" \"user-context\": { },\n"
- " \"only-if-required\": false\n"
+ " \"only-if-required\": false,\n"
+ " \"template-class\": true,\n"
+ " \"preferred-lifetime\": 800,\n"
+ " \"min-preferred-lifetime\": 800,\n"
+ " \"max-preferred-lifetime\": 800\n"
"}\n";
ElementPtr config_element = Element::fromJSON(cfg_text);
}
}
+// Verifies that the function checking if specified client class parameters
+// are supported throws if DHCPv6 specific parameters are specified for the
+// DHCPv4 client class.
+TEST_F(ClientClassDefParserTest, checkParams6Unsupported4) {
+ std::string cfg_text;
+
+ {
+ SCOPED_TRACE("preferred-lifetime");
+ cfg_text =
+ "{\n"
+ " \"name\": \"foo\","
+ " \"test\": \"member('ALL')\","
+ " \"preferred-lifetime\": 800,\n"
+ " \"option-data\": [ ],\n"
+ " \"user-context\": { },\n"
+ " \"only-if-required\": false\n"
+ "}\n";
+
+ testClassParamsUnsupported(cfg_text, AF_INET);
+ }
+
+ {
+ SCOPED_TRACE("min-preferred-lifetime");
+ cfg_text =
+ "{\n"
+ " \"name\": \"foo\","
+ " \"test\": \"member('ALL')\","
+ " \"min-preferred-lifetime\": 800,\n"
+ " \"option-data\": [ ],\n"
+ " \"user-context\": { },\n"
+ " \"only-if-required\": false\n"
+ "}\n";
+
+ testClassParamsUnsupported(cfg_text, AF_INET);
+ }
+
+ {
+ SCOPED_TRACE("max-preferred-lifetime");
+ cfg_text =
+ "{\n"
+ " \"name\": \"foo\","
+ " \"test\": \"member('ALL')\","
+ " \"max-preferred-lifetime\": 800,\n"
+ " \"option-data\": [ ],\n"
+ " \"user-context\": { },\n"
+ " \"only-if-required\": false\n"
+ "}\n";
+
+ testClassParamsUnsupported(cfg_text, AF_INET);
+ }
+}
+
// Verifies that the function checking if specified DHCPv4 client class
// parameters are supported throws if one of the parameters is not recognized.
TEST_F(ClientClassDefParserTest, checkParams4Unsupported) {
// Verifies you can create a class with a name, expression,
// but no options.
-// @todo same with AF_INET6
-TEST_F(ClientClassDefParserTest, nameAndExpressionClass) {
+TEST_F(ClientClassDefParserTest, nameAndExpressionClass4) {
std::string test = "option[100].text == 'works right'";
std::string cfg_text =
EXPECT_TRUE(evaluateBool(*match_expr, *pkt4));
}
+// Verifies you can create a class with a name, expression,
+// but no options.
+TEST_F(ClientClassDefParserTest, templateNameAndExpressionClass4) {
+
+ std::string test = "option[100].text";
+ std::string cfg_text =
+ "{ \n"
+ " \"name\": \"class_one\", \n"
+ " \"template-class\": true, \n"
+ " \"test\": \"" + test + "\" \n"
+ "} \n";
+
+ ClientClassDefPtr cclass;
+ ASSERT_NO_THROW(cclass = parseClientClassDef(cfg_text, AF_INET));
+ ASSERT_TRUE(dynamic_cast<TemplateClientClassDef*>(cclass.get()));
+
+ // We should find our class.
+ ASSERT_TRUE(cclass);
+ EXPECT_EQ("class_one", cclass->getName());
+
+ // CfgOption should be a non-null pointer but there
+ // should be no options. Currently there's no good
+ // way to test that there no options.
+ CfgOptionPtr cfg_option;
+ cfg_option = cclass->getCfgOption();
+ ASSERT_TRUE(cfg_option);
+ OptionContainerPtr oc;
+ ASSERT_TRUE(oc = cclass->getCfgOption()->getAll(DHCP4_OPTION_SPACE));
+ EXPECT_EQ(0, oc->size());
+
+ // Verify we can retrieve the expression
+ ExpressionPtr match_expr = cclass->getMatchExpr();
+ ASSERT_TRUE(match_expr);
+
+ // Verify the original expression was saved.
+ EXPECT_EQ(test, cclass->getTest());
+
+ // Build a packet that will fail evaluation.
+ Pkt4Ptr pkt4(new Pkt4(DHCPDISCOVER, 123));
+ EXPECT_TRUE(evaluateString(*match_expr, *pkt4).empty());
+
+ // Now add the option so it will pass.
+ OptionPtr opt(new OptionString(Option::V4, 100, "works right"));
+ pkt4->addOption(opt);
+ EXPECT_EQ(evaluateString(*match_expr, *pkt4), "works right");
+}
+
+// Verifies you can create a class with a name, expression,
+// but no options.
+TEST_F(ClientClassDefParserTest, nameAndExpressionClass6) {
+
+ std::string test = "option[59].text == 'works right'";
+ std::string cfg_text =
+ "{ \n"
+ " \"name\": \"class_one\", \n"
+ " \"test\": \"" + test + "\" \n"
+ "} \n";
+
+ ClientClassDefPtr cclass;
+ ASSERT_NO_THROW(cclass = parseClientClassDef(cfg_text, AF_INET6));
+
+ // We should find our class.
+ ASSERT_TRUE(cclass);
+ EXPECT_EQ("class_one", cclass->getName());
+
+ // CfgOption should be a non-null pointer but there
+ // should be no options. Currently there's no good
+ // way to test that there no options.
+ CfgOptionPtr cfg_option;
+ cfg_option = cclass->getCfgOption();
+ ASSERT_TRUE(cfg_option);
+ OptionContainerPtr oc;
+ ASSERT_TRUE(oc = cclass->getCfgOption()->getAll(DHCP6_OPTION_SPACE));
+ EXPECT_EQ(0, oc->size());
+
+ // Verify we can retrieve the expression
+ ExpressionPtr match_expr = cclass->getMatchExpr();
+ ASSERT_TRUE(match_expr);
+
+ // Verify the original expression was saved.
+ EXPECT_EQ(test, cclass->getTest());
+
+ // Build a packet that will fail evaluation.
+ Pkt6Ptr pkt6(new Pkt6(DHCPV6_SOLICIT, 123));
+ EXPECT_FALSE(evaluateBool(*match_expr, *pkt6));
+
+ // Now add the option so it will pass.
+ OptionPtr opt(new OptionString(Option::V6, 59, "works right"));
+ pkt6->addOption(opt);
+ EXPECT_TRUE(evaluateBool(*match_expr, *pkt6));
+}
+
+// Verifies you can create a class with a name, expression,
+// but no options.
+TEST_F(ClientClassDefParserTest, templateNameAndExpressionClass6) {
+
+ std::string test = "option[59].text";
+ std::string cfg_text =
+ "{ \n"
+ " \"name\": \"class_one\", \n"
+ " \"template-class\": true, \n"
+ " \"test\": \"" + test + "\" \n"
+ "} \n";
+
+ ClientClassDefPtr cclass;
+ ASSERT_NO_THROW(cclass = parseClientClassDef(cfg_text, AF_INET6));
+ ASSERT_TRUE(dynamic_cast<TemplateClientClassDef*>(cclass.get()));
+
+ // We should find our class.
+ ASSERT_TRUE(cclass);
+ EXPECT_EQ("class_one", cclass->getName());
+
+ // CfgOption should be a non-null pointer but there
+ // should be no options. Currently there's no good
+ // way to test that there no options.
+ CfgOptionPtr cfg_option;
+ cfg_option = cclass->getCfgOption();
+ ASSERT_TRUE(cfg_option);
+ OptionContainerPtr oc;
+ ASSERT_TRUE(oc = cclass->getCfgOption()->getAll(DHCP6_OPTION_SPACE));
+ EXPECT_EQ(0, oc->size());
+
+ // Verify we can retrieve the expression
+ ExpressionPtr match_expr = cclass->getMatchExpr();
+ ASSERT_TRUE(match_expr);
+
+ // Verify the original expression was saved.
+ EXPECT_EQ(test, cclass->getTest());
+
+ // Build a packet that will fail evaluation.
+ Pkt6Ptr pkt6(new Pkt6(DHCPV6_SOLICIT, 123));
+ EXPECT_TRUE(evaluateString(*match_expr, *pkt6).empty());
+
+ // Now add the option so it will pass.
+ OptionPtr opt(new OptionString(Option::V6, 59, "works right"));
+ pkt6->addOption(opt);
+ EXPECT_EQ(evaluateString(*match_expr, *pkt6), "works right");
+}
+
// Verifies you can create a class with a name and options,
// but no expression.
-// @todo same with AF_INET6
-TEST_F(ClientClassDefParserTest, nameAndOptionsClass) {
+TEST_F(ClientClassDefParserTest, nameAndOptionsClass4) {
std::string cfg_text =
"{ \n"
ASSERT_FALSE(cclass->getMatchExpr());
}
+// Verifies you can create a class with a name and options,
+// but no expression.
+TEST_F(ClientClassDefParserTest, nameAndOptionsClass6) {
+
+ std::string cfg_text =
+ "{ \n"
+ " \"name\": \"MICROSOFT\", \n"
+ " \"option-data\": [ \n"
+ " { \n"
+ " \"name\": \"sip-server-addr\", \n"
+ " \"code\": 22, \n"
+ " \"space\": \"dhcp6\", \n"
+ " \"csv-format\": true, \n"
+ " \"data\": \"2003:db8::1, 2003:db8::2\" \n"
+ " } \n"
+ " ] \n"
+ "} \n";
+
+ ClientClassDefPtr cclass;
+ ASSERT_NO_THROW(cclass = parseClientClassDef(cfg_text, AF_INET6));
+
+ // We should find our class.
+ ASSERT_TRUE(cclass);
+ EXPECT_EQ("MICROSOFT", cclass->getName());
+
+ // Our one option should exist.
+ OptionDescriptor od = cclass->getCfgOption()->get(DHCP6_OPTION_SPACE, 22);
+ ASSERT_TRUE(od.option_);
+ EXPECT_EQ(22, od.option_->getType());
+
+ // Verify we have no expression
+ ASSERT_FALSE(cclass->getMatchExpr());
+}
// Verifies you can create a class with a name, expression,
// and options.
-// @todo same with AF_INET6
-TEST_F(ClientClassDefParserTest, basicValidClass) {
+TEST_F(ClientClassDefParserTest, basicValidClass4) {
std::string test = "option[100].text == 'booya'";
std::string cfg_text =
EXPECT_TRUE(evaluateBool(*match_expr, *pkt4));
}
+// Verifies you can create a class with a name, expression,
+// and options.
+TEST_F(ClientClassDefParserTest, templateBasicValidClass4) {
+
+ std::string test = "option[100].text";
+ std::string cfg_text =
+ "{ \n"
+ " \"name\": \"MICROSOFT\", \n"
+ " \"test\": \"" + test + "\", \n"
+ " \"template-class\": true, \n"
+ " \"option-data\": [ \n"
+ " { \n"
+ " \"name\": \"domain-name-servers\", \n"
+ " \"code\": 6, \n"
+ " \"space\": \"dhcp4\", \n"
+ " \"csv-format\": true, \n"
+ " \"data\": \"192.0.2.1, 192.0.2.2\" \n"
+ " } \n"
+ " ] \n"
+ "} \n";
+
+ ClientClassDefPtr cclass;
+ ASSERT_NO_THROW(cclass = parseClientClassDef(cfg_text, AF_INET));
+ ASSERT_TRUE(dynamic_cast<TemplateClientClassDef*>(cclass.get()));
+
+ // We should find our class.
+ ASSERT_TRUE(cclass);
+ EXPECT_EQ("MICROSOFT", cclass->getName());
+
+ // Our one option should exist.
+ OptionDescriptor od = cclass->getCfgOption()->get(DHCP4_OPTION_SPACE, 6);
+ ASSERT_TRUE(od.option_);
+ EXPECT_EQ(6, od.option_->getType());
+
+ // Verify we can retrieve the expression
+ ExpressionPtr match_expr = cclass->getMatchExpr();
+ ASSERT_TRUE(match_expr);
+
+ // Verify the original expression was saved.
+ EXPECT_EQ(test, cclass->getTest());
+
+ // Build a packet that will fail evaluation.
+ Pkt4Ptr pkt4(new Pkt4(DHCPDISCOVER, 123));
+ EXPECT_TRUE(evaluateString(*match_expr, *pkt4).empty());
+
+ // Now add the option so it will pass.
+ OptionPtr opt(new OptionString(Option::V4, 100, "booya"));
+ pkt4->addOption(opt);
+ EXPECT_EQ(evaluateString(*match_expr, *pkt4), "booya");
+}
+
+// Verifies you can create a class with a name, expression,
+// and options.
+TEST_F(ClientClassDefParserTest, basicValidClass6) {
+
+ std::string test = "option[59].text == 'booya'";
+ std::string cfg_text =
+ "{ \n"
+ " \"name\": \"MICROSOFT\", \n"
+ " \"test\": \"" + test + "\", \n"
+ " \"option-data\": [ \n"
+ " { \n"
+ " \"name\": \"sip-server-addr\", \n"
+ " \"code\": 22, \n"
+ " \"space\": \"dhcp6\", \n"
+ " \"csv-format\": true, \n"
+ " \"data\": \"2003:db8::1, 2003:db8::2\" \n"
+ " } \n"
+ " ] \n"
+ "} \n";
+
+ ClientClassDefPtr cclass;
+ ASSERT_NO_THROW(cclass = parseClientClassDef(cfg_text, AF_INET6));
+
+ // We should find our class.
+ ASSERT_TRUE(cclass);
+ EXPECT_EQ("MICROSOFT", cclass->getName());
+
+ // Our one option should exist.
+ OptionDescriptor od = cclass->getCfgOption()->get(DHCP6_OPTION_SPACE, 22);
+ ASSERT_TRUE(od.option_);
+ EXPECT_EQ(22, od.option_->getType());
+
+ // Verify we can retrieve the expression
+ ExpressionPtr match_expr = cclass->getMatchExpr();
+ ASSERT_TRUE(match_expr);
+
+ // Verify the original expression was saved.
+ EXPECT_EQ(test, cclass->getTest());
+
+ // Build a packet that will fail evaluation.
+ Pkt6Ptr pkt6(new Pkt6(DHCPV6_SOLICIT, 123));
+ EXPECT_FALSE(evaluateBool(*match_expr, *pkt6));
+
+ // Now add the option so it will pass.
+ OptionPtr opt(new OptionString(Option::V6, 59, "booya"));
+ pkt6->addOption(opt);
+ EXPECT_TRUE(evaluateBool(*match_expr, *pkt6));
+}
+
+// Verifies you can create a class with a name, expression,
+// and options.
+TEST_F(ClientClassDefParserTest, templateBasicValidClass6) {
+
+ std::string test = "option[59].text";
+ std::string cfg_text =
+ "{ \n"
+ " \"name\": \"MICROSOFT\", \n"
+ " \"test\": \"" + test + "\", \n"
+ " \"template-class\": true, \n"
+ " \"option-data\": [ \n"
+ " { \n"
+ " \"name\": \"sip-server-addr\", \n"
+ " \"code\": 22, \n"
+ " \"space\": \"dhcp6\", \n"
+ " \"csv-format\": true, \n"
+ " \"data\": \"2003:db8::1, 2003:db8::2\" \n"
+ " } \n"
+ " ] \n"
+ "} \n";
+
+ ClientClassDefPtr cclass;
+ ASSERT_NO_THROW(cclass = parseClientClassDef(cfg_text, AF_INET6));
+ ASSERT_TRUE(dynamic_cast<TemplateClientClassDef*>(cclass.get()));
+
+ // We should find our class.
+ ASSERT_TRUE(cclass);
+ EXPECT_EQ("MICROSOFT", cclass->getName());
+
+ // Our one option should exist.
+ OptionDescriptor od = cclass->getCfgOption()->get(DHCP6_OPTION_SPACE, 22);
+ ASSERT_TRUE(od.option_);
+ EXPECT_EQ(22, od.option_->getType());
+
+ // Verify we can retrieve the expression
+ ExpressionPtr match_expr = cclass->getMatchExpr();
+ ASSERT_TRUE(match_expr);
+
+ // Verify the original expression was saved.
+ EXPECT_EQ(test, cclass->getTest());
+
+ // Build a packet that will fail evaluation.
+ Pkt6Ptr pkt6(new Pkt6(DHCPV6_SOLICIT, 123));
+ EXPECT_TRUE(evaluateString(*match_expr, *pkt6).empty());
+
+ // Now add the option so it will pass.
+ OptionPtr opt(new OptionString(Option::V6, 59, "booya"));
+ pkt6->addOption(opt);
+ EXPECT_EQ(evaluateString(*match_expr, *pkt6), "booya");
+}
+
// Verifies that a class with no name, fails to parse.
TEST_F(ClientClassDefParserTest, noClassName) {
DhcpConfigError);
}
+// Verifies that a class with an invalid expression, fails to parse.
+TEST_F(ClientClassDefParserTest, templateInvalidExpression) {
+ std::string cfg_text =
+ "{ \n"
+ " \"name\": \"one\", \n"
+ " \"template-class\": true, \n"
+ " \"test\": 777 \n"
+ "} \n";
+
+ ClientClassDefPtr cclass;
+ ASSERT_THROW(cclass = parseClientClassDef(cfg_text, AF_INET6),
+ DhcpConfigError);
+}
+
// Verifies that a class with invalid option-def, fails to parse.
TEST_F(ClientClassDefParserTest, invalidOptionDef) {
std::string cfg_text =
DhcpConfigError);
}
-
// Verifies that a valid list of client classes will parse.
TEST_F(ClientClassDefListParserTest, simpleValidList) {
std::string cfg_text =
EXPECT_FALSE(cclass);
}
+// Verifies that a valid list of client classes will parse.
+TEST_F(ClientClassDefListParserTest, templateSimpleValidList) {
+ std::string cfg_text =
+ "[ \n"
+ " { \n"
+ " \"name\": \"one\", \n"
+ " \"template-class\": true \n"
+ " }, \n"
+ " { \n"
+ " \"name\": \"two\", \n"
+ " \"template-class\": true \n"
+ " }, \n"
+ " { \n"
+ " \"name\": \"three\", \n"
+ " \"template-class\": true \n"
+ " } \n"
+ "] \n";
+
+ // Parsing the list should succeed.
+ ClientClassDictionaryPtr dictionary;
+ ASSERT_NO_THROW(dictionary = parseClientClassDefList(cfg_text, AF_INET6));
+ ASSERT_TRUE(dictionary);
+
+ // We should have three classes in the dictionary.
+ EXPECT_EQ(3, dictionary->getClasses()->size());
+
+ // Make sure we can find all three.
+ ClientClassDefPtr cclass;
+ ASSERT_NO_THROW(cclass = dictionary->findClass("one"));
+ ASSERT_TRUE(cclass);
+ ASSERT_TRUE(dynamic_cast<TemplateClientClassDef*>(cclass.get()));
+ EXPECT_EQ("one", cclass->getName());
+
+ ASSERT_NO_THROW(cclass = dictionary->findClass("two"));
+ ASSERT_TRUE(cclass);
+ ASSERT_TRUE(dynamic_cast<TemplateClientClassDef*>(cclass.get()));
+ EXPECT_EQ("two", cclass->getName());
+
+ ASSERT_NO_THROW(cclass = dictionary->findClass("three"));
+ ASSERT_TRUE(cclass);
+ ASSERT_TRUE(dynamic_cast<TemplateClientClassDef*>(cclass.get()));
+ EXPECT_EQ("three", cclass->getName());
+
+ // For good measure, make sure we can't find a non-existent class.
+ ASSERT_NO_THROW(cclass = dictionary->findClass("bogus"));
+ EXPECT_FALSE(cclass);
+}
+
// Verifies that class list containing a duplicate class entries, fails
// to parse.
TEST_F(ClientClassDefListParserTest, duplicateClass) {
DhcpConfigError);
}
+// Verifies that class list containing a duplicate class entries, fails
+// to parse.
+TEST_F(ClientClassDefListParserTest, templateDuplicateClass) {
+ std::string cfg_text =
+ "[ \n"
+ " { \n"
+ " \"name\": \"one\", \n"
+ " \"template-class\": true \n"
+ " }, \n"
+ " { \n"
+ " \"name\": \"two\", \n"
+ " \"template-class\": true \n"
+ " }, \n"
+ " { \n"
+ " \"name\": \"two\", \n"
+ " \"template-class\": true \n"
+ " } \n"
+ "] \n";
+
+ ClientClassDictionaryPtr dictionary;
+ ASSERT_THROW(dictionary = parseClientClassDefList(cfg_text, AF_INET),
+ DhcpConfigError);
+}
+
// Test verifies that without any class specified, the fixed fields have their
// default, empty value.
-// @todo same with AF_INET6
-TEST_F(ClientClassDefParserTest, noFixedFields) {
+TEST_F(ClientClassDefParserTest, noFixedFields4) {
std::string cfg_text =
"{ \n"
ASSERT_TRUE(cfg->getAll(DHCP4_OPTION_SPACE)->empty());
}
+// Test verifies that without any class specified, the fixed fields have their
+// default, empty value.
+TEST_F(ClientClassDefParserTest, noFixedFields6) {
+
+ std::string cfg_text =
+ "{ \n"
+ " \"name\": \"MICROSOFT\", \n"
+ " \"option-data\": [ \n"
+ " { \n"
+ " \"name\": \"sip-server-addr\", \n"
+ " \"data\": \"2003:db8::1, 2003:db8::2\" \n"
+ " } \n"
+ " ] \n"
+ "} \n";
+
+ ClientClassDefPtr cclass;
+ ASSERT_NO_THROW(cclass = parseClientClassDef(cfg_text, AF_INET6));
+
+ // We should find our class.
+ ASSERT_TRUE(cclass);
+
+ // And it should not have any fixed fields set
+ EXPECT_EQ(0, cclass->getPreferred());
+ EXPECT_EQ(0, cclass->getPreferred().getMin());
+ EXPECT_EQ(0, cclass->getPreferred().getMax());
+
+ // Nor option definitions
+ CfgOptionDefPtr cfg = cclass->getCfgOptionDef();
+ ASSERT_TRUE(cfg->getAll(DHCP6_OPTION_SPACE)->empty());
+}
+
// Test verifies option-def for a bad option fails to parse.
TEST_F(ClientClassDefParserTest, badOptionDef) {
std::string cfg_text =
EXPECT_EQ(0, std::memcmp(expected, &opt->getData()[0], 4));
}
-
// Test verifies that it is possible to define next-server field and it
// is actually set in the class properly.
TEST_F(ClientClassDefParserTest, nextServer) {
EXPECT_THROW(parseClientClassDef(cfg_too_long, AF_INET), DhcpConfigError);
}
-
// Test verifies that it is possible to define boot-file-name field and it
// is actually set in the class properly.
TEST_F(ClientClassDefParserTest, filename) {
std::string cfg_text =
"{ \n"
" \"name\": \"one\", \n"
- " \"user-context\": \"i am not a map\" \n"
+ " \"user-context\": \"i am not a map\" \n"
"} \n";
ClientClassDefPtr cclass;
ASSERT_THROW_MSG(cclass = parseClientClassDef(cfg_text, AF_INET),
- DhcpConfigError, "User context has to be a map (<string>:3:20)");
+ DhcpConfigError, "User context has to be a map (<string>:3:21)");
}
} // end of anonymous namespace
// Verifies copy constructor and equality tools (methods/operators)
TEST(ClientClassDef, copyAndEquality) {
-
boost::scoped_ptr<ClientClassDef> cclass;
ExpressionPtr expr;
CfgOptionPtr test_options;
// The allocated Expression pointers should not match
EXPECT_TRUE(cclass->getMatchExpr().get() !=
- cclass2->getMatchExpr().get());
+ cclass2->getMatchExpr().get());
// The allocated CfgOption pointers should not match
EXPECT_TRUE(cclass->getCfgOption().get() !=
- cclass2->getCfgOption().get());
+ cclass2->getCfgOption().get());
// Verify the equality tools reflect that the classes are equal.
EXPECT_TRUE(cclass->equals(*cclass2));
EXPECT_FALSE(cclass->dependOnClass("bar"));
}
-
// Tests the basic operation of ClientClassDictionary
// This includes adding, finding, and removing classes
TEST(ClientClassDictionary, basics) {
ASSERT_NO_THROW(dictionary->addClass("cc2", expr, "", false,
false, cfg_option));
- // Verify duplicate add attempt throws
+ // Verify duplicate add attempt throws using regular class
ASSERT_THROW(dictionary->addClass("cc2", expr, "", false,
false, cfg_option),
DuplicateClientClassDef);
+ // Verify duplicate add attempt throws using template class
+ ASSERT_THROW(dictionary->addClass("cc2", expr, "", false,
+ false, cfg_option, CfgOptionDefPtr(),
+ ConstElementPtr(), IOAddress("0.0.0.0"),
+ "", "", Triplet<uint32_t>(),
+ Triplet<uint32_t>(), true),
+ DuplicateClientClassDef);
+
// Verify that you cannot add a class with no name.
ASSERT_THROW(dictionary->addClass("", expr, "", false,
false, cfg_option),
EXPECT_EQ(filename, cclass->getFilename());
}
-
// Verifies the unparse method of option class definitions
TEST(ClientClassDef, unparseDef) {
CfgMgr::instance().setFamily(AF_INET);
option->getData());
}
+// Tests basic construction of TemplateClientClassDef
+TEST(TemplateClientClassDef, construction) {
+ boost::scoped_ptr<TemplateClientClassDef> cclass;
+
+ std::string name = "class1";
+ ExpressionPtr expr;
+ CfgOptionPtr cfg_option;
+
+ // Classes cannot have blank names
+ ASSERT_THROW(cclass.reset(new TemplateClientClassDef("", expr, cfg_option)),
+ BadValue);
+
+ // Verify we can create a class with a name, expression, and no cfg_option
+ ASSERT_NO_THROW(cclass.reset(new TemplateClientClassDef(name, expr)));
+ EXPECT_EQ(name, cclass->getName());
+ ASSERT_FALSE(cclass->getMatchExpr());
+ EXPECT_FALSE(cclass->getCfgOptionDef());
+
+ // Verify we get an empty collection of cfg_option
+ cfg_option = cclass->getCfgOption();
+ ASSERT_TRUE(cfg_option);
+ EXPECT_TRUE(cfg_option->empty());
+
+ // Verify we don't depend on something.
+ EXPECT_FALSE(cclass->dependOnClass("foobar"));
+ EXPECT_FALSE(cclass->dependOnClass(""));
+}
+
+// Test that template client class is copied using the copy constructor.
+TEST(TemplateClientClassDef, copyConstruction) {
+ auto expr = boost::make_shared<Expression>();
+
+ auto cfg_option = boost::make_shared<CfgOption>();
+ auto option = boost::make_shared<Option>(Option::V6, 1024);
+ cfg_option->add(option, false, DHCP6_OPTION_SPACE);
+
+ auto option_def = boost::make_shared<OptionDefinition>("foo", 1024, "dhcp6", "empty");
+ CfgOptionDefPtr cfg_option_def = boost::make_shared<CfgOptionDef>();
+ cfg_option_def->add(option_def);
+
+ boost::scoped_ptr<TemplateClientClassDef> cclass;
+ ASSERT_NO_THROW(cclass.reset(new TemplateClientClassDef("class1", expr, cfg_option)));
+ cclass->setId(123);
+ cclass->setContext(data::Element::create("my-context"));
+ cclass->setCfgOptionDef(cfg_option_def);
+ cclass->setTest("member('KNOWN')");
+ cclass->setRequired(true);
+ cclass->setDependOnKnown(true);
+ cclass->setNextServer(IOAddress("1.2.3.4"));
+ 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<TemplateClientClassDef> cclass_copy;
+ ASSERT_NO_THROW(cclass_copy.reset(new TemplateClientClassDef(*cclass)));
+
+ // Ensure that class data was copied.
+ EXPECT_EQ(cclass->getName(), cclass_copy->getName());
+ EXPECT_EQ(cclass->getId(), cclass_copy->getId());
+ ASSERT_TRUE(cclass_copy->getContext());
+ ASSERT_EQ(data::Element::string, cclass_copy->getContext()->getType());
+ EXPECT_EQ("my-context", cclass_copy->getContext()->stringValue());
+ ASSERT_TRUE(cclass->getMatchExpr());
+ EXPECT_NE(cclass_copy->getMatchExpr(), cclass->getMatchExpr());
+ EXPECT_EQ(cclass->getTest(), cclass_copy->getTest());
+ EXPECT_EQ(cclass->getRequired(), cclass_copy->getRequired());
+ EXPECT_EQ(cclass->getDependOnKnown(), cclass_copy->getDependOnKnown());
+ EXPECT_EQ(cclass->getNextServer().toText(), cclass_copy->getNextServer().toText());
+ EXPECT_EQ(cclass->getSname(), cclass_copy->getSname());
+ EXPECT_EQ(cclass->getFilename(), cclass_copy->getFilename());
+ 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());
+ EXPECT_NE(cclass_copy->getCfgOption(), cclass->getCfgOption());
+ EXPECT_TRUE(cclass_copy->getCfgOption()->get("dhcp6", 1024).option_);
+
+ // Ensure that the option definition was copied into a new structure.
+ ASSERT_TRUE(cclass_copy->getCfgOptionDef());
+ EXPECT_NE(cclass_copy->getCfgOptionDef(), cclass->getCfgOptionDef());
+ EXPECT_TRUE(cclass_copy->getCfgOptionDef()->get("dhcp6", 1024));
+}
+
+// Tests options operations. Note we just do the basics
+// as CfgOption is heavily tested elsewhere.
+TEST(TemplateClientClassDef, cfgOptionBasics) {
+ boost::scoped_ptr<ClientClassDef> cclass;
+
+ std::string name = "class1";
+ ExpressionPtr expr;
+ CfgOptionPtr test_options;
+ CfgOptionPtr class_options;
+ OptionPtr opt;
+
+ // First construct the class with empty option pointer
+ ASSERT_NO_THROW(cclass.reset(new ClientClassDef(name, expr, test_options)));
+
+ // We should get back a collection with no entries,
+ // not an empty collection pointer
+ class_options = cclass->getCfgOption();
+ ASSERT_TRUE(class_options);
+
+ // Create an option container and add some options
+ OptionPtr option;
+ test_options.reset(new CfgOption());
+ option.reset(new Option(Option::V4, 17, OptionBuffer(10, 0xFF)));
+ ASSERT_NO_THROW(test_options->add(option, false, DHCP4_OPTION_SPACE));
+
+ option.reset(new Option(Option::V6, 101, OptionBuffer(10, 0xFF)));
+ ASSERT_NO_THROW(test_options->add(option, false, "isc"));
+
+ option.reset(new Option(Option::V6, 100, OptionBuffer(10, 0xFF)));
+ ASSERT_NO_THROW(test_options->add(option, false, DHCP6_OPTION_SPACE));
+
+ // Now remake the client class with cfg_option
+ ASSERT_NO_THROW(cclass.reset(new ClientClassDef(name, expr, test_options)));
+ class_options = cclass->getCfgOption();
+ ASSERT_TRUE(class_options);
+
+ // Now make sure we can find all the options
+ OptionDescriptor opt_desc = class_options->get(DHCP4_OPTION_SPACE,17);
+ ASSERT_TRUE(opt_desc.option_);
+ EXPECT_EQ(17, opt_desc.option_->getType());
+
+ opt_desc = class_options->get("isc",101);
+ ASSERT_TRUE(opt_desc.option_);
+ EXPECT_EQ(101, opt_desc.option_->getType());
+
+ opt_desc = class_options->get(DHCP6_OPTION_SPACE,100);
+ ASSERT_TRUE(opt_desc.option_);
+ EXPECT_EQ(100, opt_desc.option_->getType());
+}
+
+// Verifies copy constructor and equality tools (methods/operators)
+TEST(TemplateClientClassDef, copyAndEquality) {
+ boost::scoped_ptr<TemplateClientClassDef> cclass;
+ ExpressionPtr expr;
+ CfgOptionPtr test_options;
+ OptionPtr opt;
+
+ // Make an expression
+ expr.reset(new Expression());
+ TokenPtr token(new TokenString("boo"));
+ expr->push_back(token);
+
+ // Create an option container with an option
+ OptionPtr option;
+ test_options.reset(new CfgOption());
+ option.reset(new Option(Option::V4, 17, OptionBuffer(10, 0xFF)));
+ ASSERT_NO_THROW(test_options->add(option, false, DHCP4_OPTION_SPACE));
+
+ // Now remake the client class with cfg_option
+ ASSERT_NO_THROW(cclass.reset(new TemplateClientClassDef("class_one", expr,
+ test_options)));
+
+ // Now lets make a copy of it.
+ boost::scoped_ptr<TemplateClientClassDef> cclass2;
+ ASSERT_NO_THROW(cclass2.reset(new TemplateClientClassDef(*cclass)));
+
+ // The allocated Expression pointers should not match
+ EXPECT_TRUE(cclass->getMatchExpr().get() !=
+ cclass2->getMatchExpr().get());
+
+ // The allocated CfgOption pointers should not match
+ EXPECT_TRUE(cclass->getCfgOption().get() !=
+ cclass2->getCfgOption().get());
+
+ // Verify the equality tools reflect that the classes are equal.
+ EXPECT_TRUE(cclass->equals(*cclass2));
+ EXPECT_TRUE(*cclass == *cclass2);
+ EXPECT_FALSE(*cclass != *cclass2);
+
+ // Verify the required flag is enough to make classes not equal.
+ EXPECT_FALSE(cclass->getRequired());
+ cclass2->setRequired(true);
+ EXPECT_TRUE(cclass2->getRequired());
+ EXPECT_FALSE(*cclass == *cclass2);
+ EXPECT_TRUE(*cclass != *cclass2);
+ cclass2->setRequired(false);
+ EXPECT_TRUE(*cclass == *cclass2);
+
+ // Verify the depend on known flag is enough to make classes not equal.
+ EXPECT_FALSE(cclass->getDependOnKnown());
+ cclass2->setDependOnKnown(true);
+ EXPECT_TRUE(cclass2->getDependOnKnown());
+ EXPECT_FALSE(*cclass == *cclass2);
+ EXPECT_TRUE(*cclass != *cclass2);
+
+ // Make a class that differs from the first class only by name and
+ // verify that the equality tools reflect that the classes are not equal.
+ ASSERT_NO_THROW(cclass2.reset(new TemplateClientClassDef("class_two", expr,
+ test_options)));
+ EXPECT_FALSE(cclass->equals(*cclass2));
+ EXPECT_FALSE(*cclass == *cclass2);
+ EXPECT_TRUE(*cclass != *cclass2);
+
+ // Make a class with the same name and options, but no expression
+ // verify that the equality tools reflect that the classes are not equal.
+ expr.reset();
+ ASSERT_NO_THROW(cclass2.reset(new TemplateClientClassDef("class_one", expr,
+ test_options)));
+ EXPECT_FALSE(cclass->equals(*cclass2));
+ EXPECT_FALSE(*cclass == *cclass2);
+ EXPECT_TRUE(*cclass != *cclass2);
+
+ // Make a class with the same name and options, but different expression,
+ // verify that the equality tools reflect that the classes are not equal.
+ expr.reset(new Expression());
+ token.reset(new TokenString("yah"));
+ expr->push_back(token);
+ ASSERT_NO_THROW(cclass2.reset(new TemplateClientClassDef("class_one", expr,
+ test_options)));
+ EXPECT_FALSE(cclass->equals(*cclass2));
+ EXPECT_FALSE(*cclass == *cclass2);
+ EXPECT_TRUE(*cclass != *cclass2);
+
+ // Make a class that with same name, expression and options, but
+ // different option definitions, verify that the equality tools reflect
+ // that the equality tools reflect that the classes are not equal.
+ ASSERT_NO_THROW(cclass2.reset(new TemplateClientClassDef(*cclass)));
+ EXPECT_TRUE(cclass->equals(*cclass2));
+ OptionDefinitionPtr def = LibDHCP::getOptionDef(DHCP4_OPTION_SPACE,
+ DHO_VENDOR_ENCAPSULATED_OPTIONS);
+ EXPECT_FALSE(def);
+ def = LibDHCP::getLastResortOptionDef(DHCP4_OPTION_SPACE, DHO_VENDOR_ENCAPSULATED_OPTIONS);
+ EXPECT_TRUE(def);
+ CfgOptionDefPtr cfg(new CfgOptionDef());
+ ASSERT_NO_THROW(cfg->add(def));
+ cclass2->setCfgOptionDef(cfg);
+ EXPECT_FALSE(cclass->equals(*cclass2));
+ EXPECT_FALSE(*cclass == *cclass2);
+ EXPECT_TRUE(*cclass != *cclass2);
+
+ // Make a class with same name and expression, but no options
+ // verify that the equality tools reflect that the classes are not equal.
+ test_options.reset(new CfgOption());
+ ASSERT_NO_THROW(cclass2.reset(new TemplateClientClassDef("class_one", expr,
+ test_options)));
+ EXPECT_FALSE(cclass->equals(*cclass2));
+ EXPECT_FALSE(*cclass == *cclass2);
+ EXPECT_TRUE(*cclass != *cclass2);
+
+ // Make a class that with same name and expression, but different options
+ // verify that the equality tools reflect that the classes are not equal.
+ option.reset(new Option(Option::V4, 20, OptionBuffer(10, 0xFF)));
+ ASSERT_NO_THROW(test_options->add(option, false, DHCP4_OPTION_SPACE));
+ ASSERT_NO_THROW(cclass2.reset(new TemplateClientClassDef("class_one", expr,
+ test_options)));
+ EXPECT_FALSE(cclass->equals(*cclass2));
+ EXPECT_FALSE(*cclass == *cclass2);
+ EXPECT_TRUE(*cclass != *cclass2);
+}
+
+// Tests dependency.
+TEST(TemplateClientClassDef, dependency) {
+ boost::scoped_ptr<TemplateClientClassDef> cclass;
+
+ ExpressionPtr expr;
+
+ // Make an expression
+ expr.reset(new Expression());
+ TokenPtr token(new TokenMember("foo"));
+ expr->push_back(token);
+
+ ASSERT_NO_THROW(cclass.reset(new TemplateClientClassDef("class", expr)));
+ EXPECT_TRUE(cclass->dependOnClass("foo"));
+ EXPECT_FALSE(cclass->dependOnClass("bar"));
+}
+
+// Tests the basic operation of ClientClassDictionary
+// This includes adding, finding, and removing classes
+TEST(ClientClassDictionary, templateBasics) {
+ ClientClassDictionaryPtr dictionary;
+ ClientClassDefPtr cclass;
+ ExpressionPtr expr;
+ CfgOptionPtr cfg_option;
+
+ // Verify constructor doesn't throw
+ ASSERT_NO_THROW(dictionary.reset(new ClientClassDictionary()));
+
+ // Verify we can fetch a pointer the list of classes and
+ // that we start with no classes defined
+ const ClientClassDefListPtr classes = dictionary->getClasses();
+ ASSERT_TRUE(classes);
+ EXPECT_EQ(0, classes->size());
+ EXPECT_TRUE(classes->empty());
+
+ // Verify that we can add classes with both addClass variants
+ // First addClass(name, expression, cfg_option)
+ ASSERT_NO_THROW(dictionary->addClass("cc1", expr, "", false,
+ false, cfg_option, CfgOptionDefPtr(),
+ ConstElementPtr(), IOAddress("0.0.0.0"),
+ "", "", Triplet<uint32_t>(),
+ Triplet<uint32_t>(), true));
+ ASSERT_NO_THROW(dictionary->addClass("cc2", expr, "", false,
+ false, cfg_option, CfgOptionDefPtr(),
+ ConstElementPtr(), IOAddress("0.0.0.0"),
+ "", "", Triplet<uint32_t>(),
+ Triplet<uint32_t>(), true));
+
+ // Verify duplicate add attempt throws using template class
+ ASSERT_THROW(dictionary->addClass("cc2", expr, "", false,
+ false, cfg_option, CfgOptionDefPtr(),
+ ConstElementPtr(), IOAddress("0.0.0.0"),
+ "", "", Triplet<uint32_t>(),
+ Triplet<uint32_t>(), true),
+ DuplicateClientClassDef);
+
+ // Verify duplicate add attempt throws using regular class
+ ASSERT_THROW(dictionary->addClass("cc2", expr, "", false,
+ false, cfg_option),
+ DuplicateClientClassDef);
+
+ // Verify that you cannot add a class with no name.
+ ASSERT_THROW(dictionary->addClass("", expr, "", false,
+ false, cfg_option, CfgOptionDefPtr(),
+ ConstElementPtr(), IOAddress("0.0.0.0"),
+ "", "", Triplet<uint32_t>(),
+ Triplet<uint32_t>(), true),
+ BadValue);
+
+ // Now with addClass(class pointer)
+ ASSERT_NO_THROW(cclass.reset(new TemplateClientClassDef("cc3", expr, cfg_option)));
+ ASSERT_NO_THROW(dictionary->addClass(cclass));
+
+ // Verify duplicate add attempt throws
+ ASSERT_THROW(dictionary->addClass(cclass), DuplicateClientClassDef);
+
+ // Verify that you cannot add empty class pointer
+ cclass.reset();
+ ASSERT_THROW(dictionary->addClass(cclass), BadValue);
+
+ // Map should show 3 entries.
+ EXPECT_EQ(3, classes->size());
+ EXPECT_FALSE(classes->empty());
+
+ // Removing client class by id of 0 should be no-op.
+ ASSERT_NO_THROW(dictionary->removeClass(0));
+ EXPECT_EQ(3, classes->size());
+ EXPECT_FALSE(classes->empty());
+
+ // Verify we can find them all.
+ ASSERT_NO_THROW(cclass = dictionary->findClass("cc1"));
+ ASSERT_TRUE(cclass);
+ ASSERT_TRUE(dynamic_cast<TemplateClientClassDef*>(cclass.get()));
+ EXPECT_EQ("cc1", cclass->getName());
+ cclass->setId(1);
+
+ ASSERT_NO_THROW(cclass = dictionary->findClass("cc2"));
+ ASSERT_TRUE(cclass);
+ ASSERT_TRUE(dynamic_cast<TemplateClientClassDef*>(cclass.get()));
+ EXPECT_EQ("cc2", cclass->getName());
+ cclass->setId(2);
+
+ ASSERT_NO_THROW(cclass = dictionary->findClass("cc3"));
+ ASSERT_TRUE(cclass);
+ ASSERT_TRUE(dynamic_cast<TemplateClientClassDef*>(cclass.get()));
+ EXPECT_EQ("cc3", cclass->getName());
+ cclass->setId(3);
+
+ // Verify the looking for non-existing returns empty pointer
+ ASSERT_NO_THROW(cclass = dictionary->findClass("bogus"));
+ EXPECT_FALSE(cclass);
+
+ // Verify that we can remove a class
+ ASSERT_NO_THROW(dictionary->removeClass("cc3"));
+ EXPECT_EQ(2, classes->size());
+ EXPECT_FALSE(classes->empty());
+
+ // Shouldn't be able to find anymore
+ ASSERT_NO_THROW(cclass = dictionary->findClass("cc3"));
+ EXPECT_FALSE(cclass);
+
+ // Verify that we can attempt to remove a non-existing class
+ // without harm.
+ ASSERT_NO_THROW(dictionary->removeClass("cc3"));
+ EXPECT_EQ(2, classes->size());
+ EXPECT_FALSE(classes->empty());
+
+ // Verify that we can remove client class by id.
+ ASSERT_NO_THROW(dictionary->removeClass(2));
+ EXPECT_EQ(1, classes->size());
+ EXPECT_FALSE(classes->empty());
+ ASSERT_NO_THROW(cclass = dictionary->findClass("cc2"));
+ EXPECT_FALSE(cclass);
+}
+
+// Verifies copy constructor and equality tools (methods/operators)
+TEST(ClientClassDictionary, templateCopyAndEquality) {
+ ClientClassDictionaryPtr dictionary;
+ ClientClassDictionaryPtr dictionary2;
+ ClientClassDefPtr cclass;
+ ExpressionPtr expr;
+ CfgOptionPtr options;
+
+ dictionary.reset(new ClientClassDictionary());
+ ASSERT_NO_THROW(dictionary->addClass("one", expr, "", false,
+ false, options, CfgOptionDefPtr(),
+ ConstElementPtr(), IOAddress("0.0.0.0"),
+ "", "", Triplet<uint32_t>(),
+ Triplet<uint32_t>(), true));
+ ASSERT_NO_THROW(dictionary->addClass("two", expr, "", false,
+ false, options, CfgOptionDefPtr(),
+ ConstElementPtr(), IOAddress("0.0.0.0"),
+ "", "", Triplet<uint32_t>(),
+ Triplet<uint32_t>(), true));
+ ASSERT_NO_THROW(dictionary->addClass("three", expr, "", false,
+ false, options, CfgOptionDefPtr(),
+ ConstElementPtr(), IOAddress("0.0.0.0"),
+ "", "", Triplet<uint32_t>(),
+ Triplet<uint32_t>(), true));
+
+ // Copy constructor should succeed.
+ ASSERT_NO_THROW(dictionary2.reset(new ClientClassDictionary(*dictionary)));
+
+ // Allocated class list pointers should not be equal
+ EXPECT_NE(dictionary->getClasses().get(), dictionary2->getClasses().get());
+
+ // Equality tools should reflect that the dictionaries are equal.
+ EXPECT_TRUE(dictionary->equals(*dictionary2));
+ EXPECT_TRUE(*dictionary == *dictionary2);
+ EXPECT_FALSE(*dictionary != *dictionary2);
+
+ // Remove a class from dictionary2.
+ ASSERT_NO_THROW(dictionary2->removeClass("two"));
+
+ // Equality tools should reflect that the dictionaries are not equal.
+ EXPECT_FALSE(dictionary->equals(*dictionary2));
+ EXPECT_FALSE(*dictionary == *dictionary2);
+ EXPECT_TRUE(*dictionary != *dictionary2);
+
+ // Create an empty dictionary.
+ dictionary2.reset(new ClientClassDictionary());
+
+ // Equality tools should reflect that the dictionaries are not equal.
+ EXPECT_FALSE(dictionary->equals(*dictionary2));
+ EXPECT_FALSE(*dictionary == *dictionary2);
+ EXPECT_TRUE(*dictionary != *dictionary2);
+}
+
+// Verify that client class dictionaries are deep-copied.
+TEST(ClientClassDictionary, templateCopy) {
+ ClientClassDictionary dictionary;
+ ExpressionPtr expr;
+ CfgOptionPtr options;
+
+ // Get a client class dictionary and fill it.
+ ASSERT_NO_THROW(dictionary.addClass("one", expr, "", false,
+ false, options, CfgOptionDefPtr(),
+ ConstElementPtr(), IOAddress("0.0.0.0"),
+ "", "", Triplet<uint32_t>(),
+ Triplet<uint32_t>(), true));
+ ASSERT_NO_THROW(dictionary.addClass("two", expr, "", false,
+ false, options, CfgOptionDefPtr(),
+ ConstElementPtr(), IOAddress("0.0.0.0"),
+ "", "", Triplet<uint32_t>(),
+ Triplet<uint32_t>(), true));
+ ASSERT_NO_THROW(dictionary.addClass("three", expr, "", false,
+ false, options, CfgOptionDefPtr(),
+ ConstElementPtr(), IOAddress("0.0.0.0"),
+ "", "", Triplet<uint32_t>(),
+ Triplet<uint32_t>(), true));
+
+ // Make a copy with a copy constructor. Expect it to be a deep copy.
+ ClientClassDictionary dictionary_copy(dictionary);
+ ASSERT_NO_THROW(dictionary.removeClass("one"));
+ ASSERT_NO_THROW(dictionary.removeClass("two"));
+ ASSERT_NO_THROW(dictionary.removeClass("three"));
+ EXPECT_TRUE(dictionary.empty());
+ EXPECT_FALSE(dictionary_copy.empty());
+
+ // Refill the client class dictionary.
+ ASSERT_NO_THROW(dictionary.addClass("one", expr, "", false,
+ false, options, CfgOptionDefPtr(),
+ ConstElementPtr(), IOAddress("0.0.0.0"),
+ "", "", Triplet<uint32_t>(),
+ Triplet<uint32_t>(), true));
+ ASSERT_NO_THROW(dictionary.addClass("two", expr, "", false,
+ false, options, CfgOptionDefPtr(),
+ ConstElementPtr(), IOAddress("0.0.0.0"),
+ "", "", Triplet<uint32_t>(),
+ Triplet<uint32_t>(), true));
+ ASSERT_NO_THROW(dictionary.addClass("three", expr, "", false,
+ false, options, CfgOptionDefPtr(),
+ ConstElementPtr(), IOAddress("0.0.0.0"),
+ "", "", Triplet<uint32_t>(),
+ Triplet<uint32_t>(), true));
+
+ // Make a copy with operator=. Expect it to be a deep copy.
+ dictionary_copy = dictionary;
+ ASSERT_NO_THROW(dictionary.removeClass("one"));
+ ASSERT_NO_THROW(dictionary.removeClass("two"));
+ ASSERT_NO_THROW(dictionary.removeClass("three"));
+ EXPECT_TRUE(dictionary.empty());
+ EXPECT_FALSE(dictionary_copy.empty());
+}
+
+// Tests dependency.
+TEST(ClientClassDictionary, templateDependency) {
+ ClientClassDictionaryPtr dictionary(new ClientClassDictionary());
+ ExpressionPtr expr;
+ CfgOptionPtr cfg_option;
+
+ // Make an expression depending on forward class.
+ ExpressionPtr expr1;
+ expr1.reset(new Expression());
+ TokenPtr token1(new TokenMember("cc2"));
+ expr1->push_back(token1);
+
+ ASSERT_NO_THROW(dictionary->addClass("cc1", expr1, "", false,
+ false, cfg_option, CfgOptionDefPtr(),
+ ConstElementPtr(), IOAddress("0.0.0.0"),
+ "", "", Triplet<uint32_t>(),
+ Triplet<uint32_t>(), true));
+
+ // Make an expression depending on first class.
+ ExpressionPtr expr2;
+ expr2.reset(new Expression());
+ TokenPtr token2(new TokenMember("cc1"));
+ expr2->push_back(token2);
+
+ ASSERT_NO_THROW(dictionary->addClass("cc2", expr2, "", false,
+ false, cfg_option));
+
+ // Make expression with dependency.
+ ASSERT_NO_THROW(dictionary->addClass("cc3", expr, "", false,
+ false, cfg_option, CfgOptionDefPtr(),
+ ConstElementPtr(), IOAddress("0.0.0.0"),
+ "", "", Triplet<uint32_t>(),
+ Triplet<uint32_t>(), true));
+
+ ExpressionPtr expr3;
+ expr3.reset(new Expression());
+ TokenPtr token3(new TokenMember("cc3"));
+ expr3->push_back(token3);
+
+ ASSERT_NO_THROW(dictionary->addClass("cc4", expr3, "", false,
+ false, cfg_option));
+
+ // Not matching dependency does not match.
+ string depend;
+ EXPECT_FALSE(dictionary->dependOnClass("foobar", depend));
+ EXPECT_TRUE(depend.empty());
+
+ // Forward dependency is ignored.
+ depend = "";
+ EXPECT_FALSE(dictionary->dependOnClass("cc2", depend));
+ EXPECT_TRUE(depend.empty());
+
+ // Backward dependency is detected.
+ depend = "";
+ EXPECT_TRUE(dictionary->dependOnClass("cc3", depend));
+ EXPECT_EQ("cc4", depend);
+}
+
+// Tests that match expressions are set for all client classes in the
+// dictionary.
+TEST(ClientClassDictionary, templateInitMatchExpr) {
+ ClientClassDictionaryPtr dictionary(new ClientClassDictionary());
+ ExpressionPtr expr;
+ CfgOptionPtr cfg_option;
+
+ // Add several classes.
+ ASSERT_NO_THROW(dictionary->addClass("foo", expr, "", false,
+ false, cfg_option, CfgOptionDefPtr(),
+ ConstElementPtr(), IOAddress("0.0.0.0"),
+ "", "", Triplet<uint32_t>(),
+ Triplet<uint32_t>(), true));
+ ASSERT_NO_THROW(dictionary->addClass("bar", expr, "'template'", false,
+ false, cfg_option, CfgOptionDefPtr(),
+ ConstElementPtr(), IOAddress("0.0.0.0"),
+ "", "", Triplet<uint32_t>(),
+ Triplet<uint32_t>(), true));
+ ASSERT_NO_THROW(dictionary->addClass("baz", expr, "'foo' + 'template'", false,
+ false, cfg_option, CfgOptionDefPtr(),
+ ConstElementPtr(), IOAddress("0.0.0.0"),
+ "", "", Triplet<uint32_t>(),
+ Triplet<uint32_t>(), true));
+
+ // Create match expressions for all of them.
+ ASSERT_NO_THROW(dictionary->initMatchExpr(AF_INET));
+
+ // Ensure that the expressions were created only if 'test' is not empty.
+ auto classes = *(dictionary->getClasses());
+ EXPECT_FALSE(classes[0]->getMatchExpr());
+
+ EXPECT_TRUE(classes[1]->getMatchExpr());
+ EXPECT_EQ(1, classes[1]->getMatchExpr()->size());
+
+ EXPECT_TRUE(classes[2]->getMatchExpr());
+ EXPECT_EQ(3, classes[2]->getMatchExpr()->size());
+}
+
+// Tests that an error is returned when any of the test expressions is
+// invalid, and that no expressions are initialized if there is an error
+// for a single expression.
+TEST(ClientClassDictionary, templateInitMatchExprError) {
+ ClientClassDictionaryPtr dictionary(new ClientClassDictionary());
+ ExpressionPtr expr;
+ CfgOptionPtr cfg_option;
+
+ // Add several classes. One of them has invalid test expression.
+ ASSERT_NO_THROW(dictionary->addClass("foo", expr, "'template'", false,
+ false, cfg_option, CfgOptionDefPtr(),
+ ConstElementPtr(), IOAddress("0.0.0.0"),
+ "", "", Triplet<uint32_t>(),
+ Triplet<uint32_t>(), true));
+ ASSERT_NO_THROW(dictionary->addClass("bar", expr, "wrong expression", false,
+ false, cfg_option, CfgOptionDefPtr(),
+ ConstElementPtr(), IOAddress("0.0.0.0"),
+ "", "", Triplet<uint32_t>(),
+ Triplet<uint32_t>(), true));
+ ASSERT_NO_THROW(dictionary->addClass("baz", expr, "'foo'", false,
+ false, cfg_option, CfgOptionDefPtr(),
+ ConstElementPtr(), IOAddress("0.0.0.0"),
+ "", "", Triplet<uint32_t>(),
+ Triplet<uint32_t>(), true));
+
+ // An attempt to initialize match expressions should fail because the
+ // test expression for the second class is invalid.
+ ASSERT_THROW(dictionary->initMatchExpr(AF_INET), std::exception);
+
+ // Ensure that no classes have their match expressions modified.
+ for (auto c : (*dictionary->getClasses())) {
+ EXPECT_FALSE(c->getMatchExpr());
+ }
+}
+
+// Tests the default constructor regarding fixed fields
+TEST(TemplateClientClassDef, fixedFieldsDefaults) {
+ boost::scoped_ptr<TemplateClientClassDef> cclass;
+
+ std::string name = "class1";
+ ExpressionPtr expr;
+ CfgOptionPtr cfg_option;
+
+ // Classes cannot have blank names
+ ASSERT_THROW(cclass.reset(new TemplateClientClassDef("", expr, cfg_option)),
+ BadValue);
+
+ // Verify we can create a class with a name, expression, and no cfg_option
+ ASSERT_NO_THROW(cclass.reset(new TemplateClientClassDef(name, expr)));
+
+ // Let's checks that it doesn't return any nonsense
+ EXPECT_FALSE(cclass->getRequired());
+ EXPECT_FALSE(cclass->getDependOnKnown());
+ EXPECT_FALSE(cclass->getCfgOptionDef());
+ string empty;
+ ASSERT_EQ(IOAddress("0.0.0.0"), cclass->getNextServer());
+ EXPECT_EQ(empty, cclass->getSname());
+ EXPECT_EQ(empty, cclass->getFilename());
+}
+
+// Tests basic operations of fixed fields
+TEST(TemplateClientClassDef, fixedFieldsBasics) {
+ boost::scoped_ptr<TemplateClientClassDef> cclass;
+
+ std::string name = "class1";
+ ExpressionPtr expr;
+ CfgOptionPtr cfg_option;
+
+ // Classes cannot have blank names
+ ASSERT_THROW(cclass.reset(new TemplateClientClassDef("", expr, cfg_option)),
+ BadValue);
+
+ // Verify we can create a class with a name, expression, and no cfg_option
+ ASSERT_NO_THROW(cclass.reset(new TemplateClientClassDef(name, expr)));
+
+ cclass->setRequired(true);
+ cclass->setDependOnKnown(true);
+
+ string sname = "This is a very long string that can be a server name";
+ string filename = "this-is-a-slightly-longish-name-of-a-file.txt";
+
+ cclass->setNextServer(IOAddress("1.2.3.4"));
+ cclass->setSname(sname);
+ cclass->setFilename(filename);
+
+ // Let's checks that it doesn't return any nonsense
+ EXPECT_TRUE(cclass->getRequired());
+ EXPECT_TRUE(cclass->getDependOnKnown());
+ EXPECT_EQ(IOAddress("1.2.3.4"), cclass->getNextServer());
+ EXPECT_EQ(sname, cclass->getSname());
+ EXPECT_EQ(filename, cclass->getFilename());
+}
+
+// Verifies the unparse method of option class definitions
+TEST(TemplateClientClassDef, unparseDef) {
+ CfgMgr::instance().setFamily(AF_INET);
+ boost::scoped_ptr<TemplateClientClassDef> cclass;
+
+ // Get a client class definition and fill it
+ std::string name = "class1";
+ ExpressionPtr expr;
+ ASSERT_NO_THROW(cclass.reset(new TemplateClientClassDef(name, expr)));
+ std::string test = "option[12].text == 'foo'";
+ cclass->setTest(test);
+ std::string comment = "bar";
+ std::string user_context = "{ \"comment\": \"" + comment + "\", ";
+ user_context += "\"bar\": 1 }";
+ cclass->setContext(isc::data::Element::fromJSON(user_context));
+ cclass->setRequired(true);
+ // The depend on known flag in not visible
+ cclass->setDependOnKnown(true);
+ std::string next_server = "1.2.3.4";
+ cclass->setNextServer(IOAddress(next_server));
+ std::string sname = "my-server.example.com";
+ cclass->setSname(sname);
+ std::string filename = "/boot/kernel";
+ cclass->setFilename(filename);
+
+ // Unparse it
+ std::string expected = "{\n"
+ "\"name\": \"" + name + "\",\n"
+ "\"test\": \"" + test + "\",\n"
+ "\"only-if-required\": true,\n"
+ "\"next-server\": \"" + next_server + "\",\n"
+ "\"server-hostname\": \"" + sname + "\",\n"
+ "\"template-class\": true,\n"
+ "\"boot-file-name\": \"" + filename + "\",\n"
+ "\"option-data\": [ ],\n"
+ "\"user-context\": { \"bar\": 1,\n"
+ "\"comment\": \"" + comment + "\" } }\n";
+ runToElementTest<TemplateClientClassDef>(expected, *cclass);
+}
+
+// Verifies the unparse method of client class dictionaries
+TEST(ClientClassDictionary, templateUnparseDict) {
+ CfgMgr::instance().setFamily(AF_INET);
+ ClientClassDictionaryPtr dictionary;
+ ExpressionPtr expr;
+ CfgOptionPtr options;
+
+ // Get a client class dictionary and fill it
+ dictionary.reset(new ClientClassDictionary());
+ ASSERT_NO_THROW(dictionary->addClass("one", expr, "", false,
+ false, options, CfgOptionDefPtr(),
+ ConstElementPtr(), IOAddress("0.0.0.0"),
+ "", "", Triplet<uint32_t>(),
+ Triplet<uint32_t>(), true));
+ ASSERT_NO_THROW(dictionary->addClass("two", expr, "", false,
+ false, options, CfgOptionDefPtr(),
+ ConstElementPtr(), IOAddress("0.0.0.0"),
+ "", "", Triplet<uint32_t>(),
+ Triplet<uint32_t>(), true));
+ ASSERT_NO_THROW(dictionary->addClass("three", expr, "", false,
+ false, options, CfgOptionDefPtr(),
+ ConstElementPtr(), IOAddress("0.0.0.0"),
+ "", "", Triplet<uint32_t>(),
+ Triplet<uint32_t>(), true));
+
+ // Unparse it
+ auto add_defaults =
+ [](std::string name) {
+ return ("{\n"
+ "\"name\": \"" + name + "\",\n"
+ "\"next-server\": \"0.0.0.0\",\n"
+ "\"server-hostname\": \"\",\n"
+ "\"template-class\": true,\n"
+ "\"boot-file-name\": \"\",\n"
+ "\"option-data\": [ ] }");
+ };
+
+ std::string expected = "[\n" +
+ add_defaults("one") + ",\n" +
+ add_defaults("two") + ",\n" +
+ add_defaults("three") + "]\n";
+
+ runToElementTest<ClientClassDictionary>(expected, *dictionary);
+}
+
+// Tests that options have been created for all client classes in the
+// dictionary.
+TEST(ClientClassDictionary, templateCreateOptions) {
+ ClientClassDictionaryPtr dictionary(new ClientClassDictionary());
+ ExpressionPtr expr;
+ CfgOptionPtr cfg_option;
+
+ // First class has no options.
+ ASSERT_NO_THROW(dictionary->addClass("foo", expr, "", false,
+ false, cfg_option, CfgOptionDefPtr(),
+ ConstElementPtr(), IOAddress("0.0.0.0"),
+ "", "", Triplet<uint32_t>(),
+ Triplet<uint32_t>(), true));
+
+ // Make some options for the second class.
+ cfg_option.reset(new CfgOption());
+ OptionPtr option = Option::create(Option::V4, DHO_BOOT_FILE_NAME);
+ OptionDescriptorPtr desc = OptionDescriptor::create(option, true, "bogus-file.txt");
+ desc->space_name_ = DHCP4_OPTION_SPACE;
+ cfg_option->add(*desc, desc->space_name_);
+
+ option = Option::create(Option::V4, DHO_TFTP_SERVER_NAME);
+ desc = OptionDescriptor::create(option, true, "bogus-tftp-server");
+ desc->space_name_ = DHCP4_OPTION_SPACE;
+ cfg_option->add(*desc, desc->space_name_);
+
+ // Add the second class with options.
+ ASSERT_NO_THROW(dictionary->addClass("bar", expr, "", false,
+ false, cfg_option, CfgOptionDefPtr(),
+ ConstElementPtr(), IOAddress("0.0.0.0"),
+ "", "", Triplet<uint32_t>(),
+ Triplet<uint32_t>(), true));
+
+ // Make sure first class has no options.
+ ASSERT_TRUE(dictionary->getClasses());
+ auto classes = *(dictionary->getClasses());
+ auto options = classes[0]->getCfgOption();
+ ASSERT_TRUE(options->empty());
+
+ // Make sure second class has both options but their
+ // data buffers are empty.
+ options = classes[1]->getCfgOption();
+ ASSERT_FALSE(options->empty());
+
+ auto option_desc = options->get("dhcp4", DHO_BOOT_FILE_NAME);
+ ASSERT_TRUE(option_desc.option_);
+ ASSERT_TRUE(option_desc.option_->getData().empty());
+
+ option_desc = options->get("dhcp4", DHO_TFTP_SERVER_NAME);
+ ASSERT_TRUE(option_desc.option_);
+ ASSERT_TRUE(option_desc.option_->getData().empty());
+
+ // Now create match expressions for all of them.
+ auto cfg_def = CfgMgr::instance().getCurrentCfg()->getCfgOptionDef();
+ ASSERT_NO_THROW(dictionary->createOptions(cfg_def));
+
+ // Make sure first class still has no options.
+ classes = *(dictionary->getClasses());
+ options = classes[0]->getCfgOption();
+ ASSERT_TRUE(options->empty());
+
+ // Make sure second class has both options and that their
+ // data buffers are now correctly populated.
+ options = classes[1]->getCfgOption();
+ ASSERT_FALSE(options->empty());
+
+ option_desc = options->get("dhcp4", DHO_BOOT_FILE_NAME);
+ option = option_desc.option_;
+ ASSERT_TRUE(option);
+ EXPECT_EQ(OptionBuffer(option_desc.formatted_value_.begin(), option_desc.formatted_value_.end()),
+ option->getData());
+
+ option_desc = options->get("dhcp4", DHO_TFTP_SERVER_NAME);
+ option = option_desc.option_;
+ ASSERT_TRUE(option);
+ EXPECT_EQ(OptionBuffer(option_desc.formatted_value_.begin(), option_desc.formatted_value_.end()),
+ option->getData());
+}
+
} // end of anonymous namespace
/// @return true if the client class is defined, false if not
bool isClientClassDefined(const ClientClass& client_class);
- private:
+private:
/// @brief Flag determining scanner debugging.
bool trace_scanning_;
/// This method is used only in tests.
/// @return type of the field.
FieldType getType() {
- return(type_);
+ return (type_);
}
private: