} else if ((config_id.compare("subnet") == 0) ||
(config_id.compare("interface") == 0) ||
(config_id.compare("client-class") == 0) ||
- (config_id.compare("next-server") == 0)) {
+ (config_id.compare("next-server") == 0) ||
+ (config_id.compare("reservation-mode") == 0)) {
parser = new StringParser(config_id, string_values_);
} else if (config_id.compare("pools") == 0) {
parser = new Pools4ListParser(config_id, pools_);
EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
checkResult(x, 1);
+}
+
+/// The goal of this test is to verify that Host Reservation modes can be
+/// specified on a per-subnet basis.
+TEST_F(Dhcp4ParserTest, hostReservationPerSubnet) {
+
+ /// - Configuration:
+ /// - only addresses (no prefixes)
+ /// - 4 subnets with:
+ /// - 192.0.2.0/24 (all reservations enabled)
+ /// - 192.0.3.0/24 (out-of-pool reservations)
+ /// - 192.0.4.0/24 (reservations disabled)
+ /// - 192.0.5.0/24 (reservations not specified)
+ const char* hr_config =
+ "{ \"interfaces\": [ \"*\" ],"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"192.0.2.0/24\" } ],"
+ " \"subnet\": \"192.0.2.0/24\", "
+ " \"reservation-mode\": \"all\","
+ " \"interface\": \"eth0\""
+ " },"
+ " {"
+ " \"pools\": [ { \"pool\": \"192.0.3.0/24\" } ],"
+ " \"subnet\": \"192.0.3.0/24\", "
+ " \"reservation-mode\": \"out-of-pool\","
+ " \"interface\": \"eth1\""
+ " },"
+ " {"
+ " \"pools\": [ { \"pool\": \"192.0.4.0/24\" } ],"
+ " \"subnet\": \"192.0.4.0/24\", "
+ " \"reservation-mode\": \"disabled\","
+ " \"interface\": \"eth1\""
+ " },"
+ " {"
+ " \"pools\": [ { \"pool\": \"192.0.5.0/24\" } ],"
+ " \"subnet\": \"192.0.5.0/24\", "
+ " \"interface\": \"eth1\""
+ " } ],"
+ "\"valid-lifetime\": 4000 }";
+ ElementPtr json = Element::fromJSON(hr_config);
+ CfgMgr::instance().clear();
+ ConstElementPtr result;
+ EXPECT_NO_THROW(result = configureDhcp4Server(*srv_, json));
+
+ // returned value should be 0 (success)
+ checkResult(result, 0);
+ const Subnet4Collection* subnets =
+ CfgMgr::instance().getStagingCfg()->getCfgSubnets4()->getAll();
+ ASSERT_TRUE(subnets);
+ ASSERT_EQ(4, subnets->size()); // We expect 4 subnets
+
+ // Check subnet-ids of each subnet (it should be monotonously increasing)
+ EXPECT_EQ(Subnet::HR_ALL, subnets->at(0)->getHostReservationMode());
+ EXPECT_EQ(Subnet::HR_OUT_OF_POOL, subnets->at(1)->getHostReservationMode());
+ EXPECT_EQ(Subnet::HR_DISABLED, subnets->at(2)->getHostReservationMode());
+ EXPECT_EQ(Subnet::HR_ALL, subnets->at(3)->getHostReservationMode());
}
+
+
}
} else if ((config_id.compare("subnet") == 0) ||
(config_id.compare("interface") == 0) ||
(config_id.compare("client-class") == 0) ||
- (config_id.compare("interface-id") == 0)) {
+ (config_id.compare("interface-id") == 0) ||
+ (config_id.compare("reservation-mode") == 0)) {
parser = new StringParser(config_id, string_values_);
} else if (config_id.compare("pools") == 0) {
parser = new Pools6ListParser(config_id, pools_);
checkResult(status, 1);
}
+/// The goal of this test is to verify that Host Reservation modes can be
+/// specified on a per-subnet basis.
+TEST_F(Dhcp6ParserTest, hostReservationPerSubnet) {
+
+ /// - Configuration:
+ /// - only addresses (no prefixes)
+ /// - 4 subnets with:
+ /// - 2001:db8:1::/64 (all reservations enabled)
+ /// - 2001:db8:2::/64 (out-of-pool reservations)
+ /// - 2001:db8:3::/64 (reservations disabled)
+ /// - 2001:db8:3::/64 (reservations not specified)
+ const char* HR_CONFIG =
+ "{ \"interfaces\": [ \"*\" ],"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"reservation-mode\": \"all\","
+ " \"interface\": \"eth0\""
+ " },"
+ " {"
+ " \"pools\": [ { \"pool\": \"2001:db8:2::/64\" } ],"
+ " \"subnet\": \"2001:db8:2::/48\", "
+ " \"reservation-mode\": \"out-of-pool\","
+ " \"interface\": \"eth1\""
+ " },"
+ " {"
+ " \"pools\": [ { \"pool\": \"2001:db8:3::/64\" } ],"
+ " \"subnet\": \"2001:db8:3::/48\", "
+ " \"reservation-mode\": \"disabled\","
+ " \"interface\": \"eth1\""
+ " },"
+ " {"
+ " \"pools\": [ { \"pool\": \"2001:db8:4::/64\" } ],"
+ " \"subnet\": \"2001:db8:4::/48\", "
+ " \"interface\": \"eth1\""
+ " } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ ConstElementPtr status;
+
+ EXPECT_NO_THROW(status = configureDhcp6Server(srv_,
+ Element::fromJSON(HR_CONFIG)));
+
+ // returned value should be 0 (success)
+ checkResult(status, 0);
+ CfgMgr::instance().commit();
+
+ const Subnet6Collection* subnets =
+ CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
+ ASSERT_TRUE(subnets);
+ ASSERT_EQ(4, subnets->size()); // We expect 4 subnets
+
+ // Check subnet-ids of each subnet (it should be monotonously increasing)
+ EXPECT_EQ(Subnet::HR_ALL, subnets->at(0)->getHostReservationMode());
+ EXPECT_EQ(Subnet::HR_OUT_OF_POOL, subnets->at(1)->getHostReservationMode());
+ EXPECT_EQ(Subnet::HR_DISABLED, subnets->at(2)->getHostReservationMode());
+ EXPECT_EQ(Subnet::HR_ALL, subnets->at(3)->getHostReservationMode());
+}
+
};
/// In-pool reservations: Check if this address is reserved for someone
/// else. There is no need to check for whom it is reserved, because if
/// it has been reserved for us we would have already allocated a lease.
- if (hr_mode == Subnet::HR_IN_POOL &&
+ if (hr_mode == Subnet::HR_ALL &&
HostMgr::instance().get6(ctx.subnet_->getID(), candidate)) {
// Don't allocate.
}
}
+Subnet::HRMode
+HRModeFromText(const std::string& txt) {
+ if ( (txt.compare("disabled") == 0) ||
+ (txt.compare("off") == 0) ) {
+ return (Subnet::HR_DISABLED);
+ } else if (txt.compare("out-of-pool") == 0) {
+ return (Subnet::HR_OUT_OF_POOL);
+ } else if (txt.compare("all") == 0) {
+ return (Subnet::HR_ALL);
+ } else {
+ isc_throw(BadValue, "Can't convert '" << txt
+ << "' into any valid reservation-mode values");
+ }
+}
+
void
SubnetConfigParser::createSubnet() {
std::string subnet_txt;
// iface not mandatory so swallow the exception
}
+ std::string hr_mode;
+ try {
+ hr_mode = string_values_->getParam("reservation-mode");
+ } catch (const DhcpConfigError &) {
+ // Host reservation mode is not mandatory, so don't complain
+ }
+
+ try {
+ // This may throw if the user didn't specify one of allowed values
+ if (!hr_mode.empty()) {
+ subnet_->setHostReservationMode(HRModeFromText(hr_mode));
+ }
+ } catch (const BadValue& ex) {
+ isc_throw(DhcpConfigError, "Failed to process specified value "
+ " of reservation-mode parameter: " << ex.what()
+ << string_values_->getPosition("reservation-mode"));
+ }
+
if (!iface.empty()) {
if (!IfaceMgr::instance().getIface(iface)) {
isc_throw(DhcpConfigError, "Specified interface name " << iface
last_allocated_ia_(lastAddrInPrefix(prefix, len)),
last_allocated_ta_(lastAddrInPrefix(prefix, len)),
last_allocated_pd_(lastAddrInPrefix(prefix, len)), relay_(relay),
- cfg_option_(new CfgOption())
-
+ host_reservation_mode_(HR_ALL), cfg_option_(new CfgOption())
{
if ((prefix.isV6() && len > 128) ||
(prefix.isV4() && len > 32)) {
/// there is a non-trivial performance penalty for it, as the
/// AllocEngine code has to check whether there are reservations, even
/// when dealing with reservations from within the dynamic pools.
- HR_IN_POOL
+ HR_ALL
} HRMode;
/// Pointer to the RelayInfo structure
/// not in the dynamic pool). HR may also be completely disabled for
/// performance reasons.
///
- /// @todo: implement this.
- ///
/// @return whether in-pool host reservations are allowed.
HRMode
getHostReservationMode() {
- return (Subnet::HR_IN_POOL);
+ return (host_reservation_mode_);
+ }
+
+ /// @brief Sets host reservation mode.
+ ///
+ /// See @getHostReservationMode for details.
+ ///
+ /// @param mode mode to be set
+ void setHostReservationMode(HRMode mode) {
+ host_reservation_mode_ = mode;
}
+ /// @brief Attempts to convert text representation to HRMode enum
+ ///
+ /// @throw BadValue if the text cannot be converted
+ ///
+ /// @param text representation for conversion
+ /// @return one of allowed HRMode values
+ static HRMode HRModeFromText(const std::string& txt);
+
protected:
/// @brief Returns all pools (non-const variant)
///
/// so it may be a while until we support this.
ClientClasses white_list_;
+ /// @brief Specifies host reservation mode
+ ///
+ /// See @ref HRMode type for details.
+ HRMode host_reservation_mode_;
private:
/// @brief Pointer to the option data configuration for this subnet.