2
0
mirror of https://gitlab.isc.org/isc-projects/kea synced 2025-08-29 13:07:50 +00:00

[2545] Separated build and commit phase for all DHCPv4 config parsers.

... also added new BooleanParser.
This commit is contained in:
Marcin Siodelski 2012-12-21 12:53:31 +01:00
parent 3c702b8966
commit a32568b7b3
3 changed files with 328 additions and 152 deletions

View File

@ -58,6 +58,9 @@ typedef Dhcp4ConfigParser* ParserFactory(const std::string& config_id);
/// @brief a collection of factories that creates parsers for specified element names
typedef std::map<std::string, ParserFactory*> FactoryMap;
/// @brief Storage for parsed boolean values.
typedef std::map<string, bool> BooleanStorage;
/// @brief a collection of pools
///
/// That type is used as intermediate storage, when pools are parsed, but there is
@ -87,7 +90,7 @@ OptionStorage option_defaults;
/// @todo: Merge this class with DhcpConfigParser in src/bin/dhcp6
class Dhcp4ConfigParser {
///
/// \name Constructors and Destructor
/// @name Constructors and Destructor
///
/// Note: The copy constructor and the assignment operator are
/// intentionally defined as private to make it explicit that this is a
@ -101,9 +104,9 @@ private:
Dhcp4ConfigParser(const Dhcp4ConfigParser& source);
Dhcp4ConfigParser& operator=(const Dhcp4ConfigParser& source);
protected:
/// \brief The default constructor.
/// @brief The default constructor.
///
/// This is intentionally defined as \c protected as this base class should
/// This is intentionally defined as @c protected as this base class should
/// never be instantiated (except as part of a derived class).
Dhcp4ConfigParser() {}
public:
@ -111,7 +114,7 @@ public:
virtual ~Dhcp4ConfigParser() {}
//@}
/// \brief Prepare configuration value.
/// @brief Prepare configuration value.
///
/// This method parses the "value part" of the configuration identifier
/// that corresponds to this derived class and prepares a new value to
@ -119,11 +122,11 @@ public:
///
/// This method must validate the given value both in terms of syntax
/// and semantics of the configuration, so that the server will be
/// validly configured at the time of \c commit(). Note: the given
/// validly configured at the time of @c commit(). Note: the given
/// configuration value is normally syntactically validated, but the
/// \c build() implementation must also expect invalid input. If it
/// @c build() implementation must also expect invalid input. If it
/// detects an error it may throw an exception of a derived class
/// of \c isc::Exception.
/// of @c isc::Exception.
///
/// Preparing a configuration value will often require resource
/// allocation. If it fails, it may throw a corresponding standard
@ -133,23 +136,23 @@ public:
/// life of the object. Although multiple calls are not prohibited
/// by the interface, the behavior is undefined.
///
/// \param config_value The configuration value for the identifier
/// @param config_value The configuration value for the identifier
/// corresponding to the derived class.
virtual void build(isc::data::ConstElementPtr config_value) = 0;
/// \brief Apply the prepared configuration value to the server.
/// @brief Apply the prepared configuration value to the server.
///
/// This method is expected to be exception free, and, as a consequence,
/// it should normally not involve resource allocation.
/// Typically it would simply perform exception free assignment or swap
/// operation on the value prepared in \c build().
/// operation on the value prepared in @c build().
/// In some cases, however, it may be very difficult to meet this
/// condition in a realistic way, while the failure case should really
/// be very rare. In such a case it may throw, and, if the parser is
/// called via \c configureDhcp4Server(), the caller will convert the
/// called via @c configureDhcp4Server(), the caller will convert the
/// exception as a fatal error.
///
/// This method is expected to be called after \c build(), and only once.
/// This method is expected to be called after @c build(), and only once.
/// The result is undefined otherwise.
virtual void commit() = 0;
};
@ -165,7 +168,7 @@ public:
/// @brief Constructor
///
/// See \ref Dhcp4ConfigParser class for details.
/// See @ref Dhcp4ConfigParser class for details.
///
/// @param param_name name of the parsed parameter
DebugParser(const std::string& param_name)
@ -174,7 +177,7 @@ public:
/// @brief builds parameter value
///
/// See \ref Dhcp4ConfigParser class for details.
/// See @ref Dhcp4ConfigParser class for details.
///
/// @param new_config pointer to the new configuration
virtual void build(ConstElementPtr new_config) {
@ -188,7 +191,7 @@ public:
/// This is a method required by base class. It pretends to apply the
/// configuration, but in fact it only prints the parameter out.
///
/// See \ref Dhcp4ConfigParser class for details.
/// See @ref Dhcp4ConfigParser class for details.
virtual void commit() {
// Debug message. The whole DebugParser class is used only for parser
// debugging, and is not used in production code. It is very convenient
@ -212,6 +215,80 @@ private:
ConstElementPtr value_;
};
/// @brief A boolean value parser.
///
/// This parser handles configuration values of the boolean type.
/// Parsed values are stored in a provided storage. If no storage
/// is provided then the build function throws an exception.
class BooleanParser : public Dhcp4ConfigParser {
public:
/// @brief Constructor.
///
/// @param param_name name of the parameter.
BooleanParser(const std::string& param_name)
: storage_(NULL),
param_name_(param_name),
value_(false) {
}
/// @brief Parse a boolean value.
///
/// @param value a value to be parsed.
///
/// @throw isc::InvalidOperation if a storage has not been set
/// prior to calling this function
/// @throw isc::dhcp::Dhcp4ConfigError if a provided parameter's
/// name is empty.
virtual void build(ConstElementPtr value) {
if (storage_ == NULL) {
isc_throw(isc::InvalidOperation, "parser logic error:"
<< " storage for the " << param_name_
<< " value has not been set");
} else if (param_name_.empty()) {
isc_throw(isc::dhcp::Dhcp4ConfigError, "parser logic error:"
<< "empty parameter name provided");
}
// The Config Manager checks if user specified a
// valid value for a boolean parameter: True or False.
// It is then ok to assume that if str() does not return
// 'true' the value is 'false'.
value_ = (value->str() == "true") ? true : false;
}
/// @brief Put a parsed value to the storage.
virtual void commit() {
if (storage_ != NULL && !param_name_.empty()) {
(*storage_)[param_name_] = value_;
}
}
/// @brief Create an instance of the boolean parser.
///
/// @param param_name name of the parameter for which the
/// parser is created.
static Dhcp4ConfigParser* Factory(const std::string& param_name) {
return (new BooleanParser(param_name));
}
/// @brief Set the storage for parsed value.
///
/// This function must be called prior to calling build.
///
/// @param storage a pointer to the storage where parsed data
/// is to be stored.
void setStorage(BooleanStorage* storage) {
storage_ = storage;
}
private:
/// Pointer to the storage where parsed value is stored.
BooleanStorage* storage_;
/// Name of the parameter which value is parsed with this parser.
std::string param_name_;
/// Parsed value.
bool value_;
};
/// @brief Configuration parser for uint32 parameters
///
/// This class is a generic parser that is able to handle any uint32 integer
@ -219,27 +296,31 @@ private:
/// (uint32_defaults). If used in smaller scopes (e.g. to parse parameters
/// in subnet config), it can be pointed to a different storage, using
/// setStorage() method. This class follows the parser interface, laid out
/// in its base class, \ref Dhcp4ConfigParser.
/// in its base class, @ref Dhcp4ConfigParser.
///
/// For overview of usability of this generic purpose parser, see
/// \ref dhcpv4ConfigInherit page.
/// @ref dhcpv4ConfigInherit page.
class Uint32Parser : public Dhcp4ConfigParser {
public:
/// @brief constructor for Uint32Parser
/// @param param_name name of the configuration parameter being parsed
Uint32Parser(const std::string& param_name)
:storage_(&uint32_defaults), param_name_(param_name) {
: storage_(&uint32_defaults),
param_name_(param_name) {
}
/// @brief builds parameter value
///
/// Parses configuration entry and stores it in a storage. See
/// \ref setStorage() for details.
/// @brief Parses configuration configuration parameter as uint32_t.
///
/// @param value pointer to the content of parsed values
/// @throw BadValue if supplied value could not be base to uint32_t
/// or the parameter name is empty.
virtual void build(ConstElementPtr value) {
if (param_name_.empty()) {
isc_throw(BadValue, "parser logic error:"
<< "empty parameter name provided");
}
int64_t check;
string x = value->str();
try {
@ -259,19 +340,15 @@ public:
// value is small enough to fit
value_ = static_cast<uint32_t>(check);
(*storage_)[param_name_] = value_;
}
/// @brief does nothing
///
/// This method is required for all parsers. The value itself
/// is not commited anywhere. Higher level parsers are expected to
/// use values stored in the storage, e.g. renew-timer for a given
/// subnet is stored in subnet-specific storage. It is not commited
/// here, but is rather used by \ref Subnet4ConfigParser when constructing
/// the subnet.
/// @brief Stores the parsed uint32_t value in a storage.
virtual void commit() {
if (storage_ != NULL && !param_name_.empty()) {
// If a given parameter already exists in the storage we override
// its value. If it doesn't we insert a new element.
(*storage_)[param_name_] = value_;
}
}
/// @brief factory that constructs Uint32Parser objects
@ -283,7 +360,7 @@ public:
/// @brief sets storage for value of this parameter
///
/// See \ref dhcpv4ConfigInherit for details.
/// See @ref dhcpv4ConfigInherit for details.
///
/// @param storage pointer to the storage container
void setStorage(Uint32Storage* storage) {
@ -308,10 +385,10 @@ private:
/// (string_defaults). If used in smaller scopes (e.g. to parse parameters
/// in subnet config), it can be pointed to a different storage, using
/// setStorage() method. This class follows the parser interface, laid out
/// in its base class, \ref Dhcp4ConfigParser.
/// in its base class, @ref Dhcp4ConfigParser.
///
/// For overview of usability of this generic purpose parser, see
/// \ref dhcpv4ConfigInherit page.
/// @ref dhcpv4ConfigInherit page.
class StringParser : public Dhcp4ConfigParser {
public:
@ -324,25 +401,21 @@ public:
/// @brief parses parameter value
///
/// Parses configuration entry and stores it in storage. See
/// \ref setStorage() for details.
/// @ref setStorage() for details.
///
/// @param value pointer to the content of parsed values
virtual void build(ConstElementPtr value) {
value_ = value->str();
boost::erase_all(value_, "\"");
(*storage_)[param_name_] = value_;
}
/// @brief does nothing
///
/// This method is required for all parser. The value itself
/// is not commited anywhere. Higher level parsers are expected to
/// use values stored in the storage, e.g. renew-timer for a given
/// subnet is stored in subnet-specific storage. It is not commited
/// here, but is rather used by its parent parser when constructing
/// an object, e.g. the subnet.
/// @brief Stores the parsed value in a storage.
virtual void commit() {
if (storage_ != NULL && !param_name_.empty()) {
// If a given parameter already exists in the storage we override
// its value. If it doesn't we insert a new element.
(*storage_)[param_name_] = value_;
}
}
/// @brief factory that constructs StringParser objects
@ -499,7 +572,7 @@ public:
}
Pool4Ptr pool(new Pool4(addr, len));
pools_->push_back(pool);
local_pools_.push_back(pool);
continue;
}
@ -512,7 +585,7 @@ public:
Pool4Ptr pool(new Pool4(min, max));
pools_->push_back(pool);
local_pools_.push_back(pool);
continue;
}
@ -531,12 +604,17 @@ public:
pools_ = storage;
}
/// @brief does nothing.
///
/// This method is required for all parsers. The value itself
/// is not commited anywhere. Higher level parsers (for subnet) are expected
/// to use values stored in the storage.
virtual void commit() {}
/// @brief Stores the parsed values in a storage provided
/// by an upper level parser.
virtual void commit() {
if (pools_) {
// local_pools_ holds the values produced by the build function.
// At this point parsing should have completed successfuly so
// we can append new data to the supplied storage.
pools_->insert(pools_->end(), local_pools_.begin(),
local_pools_.end());
}
}
/// @brief factory that constructs PoolParser objects
///
@ -551,6 +629,9 @@ private:
/// That is typically a storage somewhere in Subnet parser
/// (an upper level parser).
PoolStorage* pools_;
/// A temporary storage for pools configuration. It is a
/// storage where pools are stored by build function.
PoolStorage local_pools_;
};
/// @brief Parser for option data value.
@ -628,6 +709,13 @@ public:
<< param.first);
}
parser->build(param.second);
// Before we can create an option we need to get the data from
// the child parsers. The only way to do it is to invoke commit
// on them so as they store the values in appropriate storages
// that this class provided to them. Note that this will not
// modify values stored in the global storages so the configuration
// will remain consistent even parsing fails somewhere further on.
parser->commit();
}
// Try to create the option instance.
createOption();
@ -935,7 +1023,20 @@ public:
isc_throw(Dhcp4ConfigError, "failed to find suitable parser");
}
}
// Ok, we now have subnet parsed
// In order to create new subnet we need to get the data out
// of the child parsers first. The only way to do it is to
// invoke commit on them because it will make them write
// parsed data into storages we have supplied.
// Note that triggering commits on child parsers does not
// affect global data because we supplied pointers to storages
// local to this object. Thus, even if this method fails
// later on, the configuration remains consistent.
BOOST_FOREACH(ParserPtr parser, parsers_) {
parser->commit();
}
// Create a subnet.
createSubnet();
}
/// @brief commits received configuration.
@ -946,80 +1047,9 @@ public:
/// objects. Subnet4 are then added to DHCP CfgMgr.
/// @throw Dhcp4ConfigError if there are any issues encountered during commit
void commit() {
// Invoke commit on all sub-data parsers.
BOOST_FOREACH(ParserPtr parser, parsers_) {
parser->commit();
if (subnet_) {
CfgMgr::instance().addSubnet4(subnet_);
}
StringStorage::const_iterator it = string_values_.find("subnet");
if (it == string_values_.end()) {
isc_throw(Dhcp4ConfigError,
"Mandatory subnet definition in subnet missing");
}
string subnet_txt = it->second;
boost::erase_all(subnet_txt, " ");
boost::erase_all(subnet_txt, "\t");
size_t pos = subnet_txt.find("/");
if (pos == string::npos) {
isc_throw(Dhcp4ConfigError,
"Invalid subnet syntax (prefix/len expected):" << it->second);
}
IOAddress addr(subnet_txt.substr(0, pos));
uint8_t len = boost::lexical_cast<unsigned int>(subnet_txt.substr(pos + 1));
Triplet<uint32_t> t1 = getParam("renew-timer");
Triplet<uint32_t> t2 = getParam("rebind-timer");
Triplet<uint32_t> valid = getParam("valid-lifetime");
/// @todo: Convert this to logger once the parser is working reliably
stringstream tmp;
tmp << addr.toText() << "/" << (int)len
<< " with params t1=" << t1 << ", t2=" << t2 << ", valid=" << valid;
LOG_INFO(dhcp4_logger, DHCP4_CONFIG_NEW_SUBNET).arg(tmp.str());
Subnet4Ptr subnet(new Subnet4(addr, len, t1, t2, valid));
for (PoolStorage::iterator it = pools_.begin(); it != pools_.end(); ++it) {
subnet->addPool4(*it);
}
const Subnet::OptionContainer& options = subnet->getOptions();
const Subnet::OptionContainerTypeIndex& idx = options.get<1>();
// Add subnet specific options.
BOOST_FOREACH(Subnet::OptionDescriptor desc, options_) {
Subnet::OptionContainerTypeRange range = idx.equal_range(desc.option->getType());
if (std::distance(range.first, range.second) > 0) {
LOG_WARN(dhcp4_logger, DHCP4_CONFIG_OPTION_DUPLICATE)
.arg(desc.option->getType()).arg(addr.toText());
}
subnet->addOption(desc.option);
}
// Check all global options and add them to the subnet object if
// they have been configured in the global scope. If they have been
// configured in the subnet scope we don't add global option because
// the one configured in the subnet scope always takes precedence.
BOOST_FOREACH(Subnet::OptionDescriptor desc, option_defaults) {
// Get all options specified locally in the subnet and having
// code equal to global option's code.
Subnet::OptionContainerTypeRange range = idx.equal_range(desc.option->getType());
// @todo: In the future we will be searching for options using either
// an option code or namespace. Currently we have only the option
// code available so if there is at least one option found with the
// specific code we don't add the globally configured option.
// @todo with this code the first globally configured option
// with the given code will be added to a subnet. We may
// want to issue a warning about dropping the configuration of
// a global option if one already exsists.
if (std::distance(range.first, range.second) == 0) {
subnet->addOption(desc.option);
}
}
CfgMgr::instance().addSubnet4(subnet);
}
private:
@ -1058,6 +1088,79 @@ private:
return (false);
}
/// @brief Create a new subnet using a data from child parsers.
///
/// @throw isc::dhcp::Dhcp4ConfigError if subnet configuration parsing failed.
void createSubnet() {
StringStorage::const_iterator it = string_values_.find("subnet");
if (it == string_values_.end()) {
isc_throw(Dhcp4ConfigError,
"Mandatory subnet definition in subnet missing");
}
string subnet_txt = it->second;
boost::erase_all(subnet_txt, " ");
boost::erase_all(subnet_txt, "\t");
size_t pos = subnet_txt.find("/");
if (pos == string::npos) {
isc_throw(Dhcp4ConfigError,
"Invalid subnet syntax (prefix/len expected):" << it->second);
}
IOAddress addr(subnet_txt.substr(0, pos));
uint8_t len = boost::lexical_cast<unsigned int>(subnet_txt.substr(pos + 1));
Triplet<uint32_t> t1 = getParam("renew-timer");
Triplet<uint32_t> t2 = getParam("rebind-timer");
Triplet<uint32_t> valid = getParam("valid-lifetime");
/// @todo: Convert this to logger once the parser is working reliably
stringstream tmp;
tmp << addr.toText() << "/" << (int)len
<< " with params t1=" << t1 << ", t2=" << t2 << ", valid=" << valid;
LOG_INFO(dhcp4_logger, DHCP4_CONFIG_NEW_SUBNET).arg(tmp.str());
subnet_.reset(new Subnet4(addr, len, t1, t2, valid));
for (PoolStorage::iterator it = pools_.begin(); it != pools_.end(); ++it) {
subnet_->addPool4(*it);
}
const Subnet::OptionContainer& options = subnet_->getOptions();
const Subnet::OptionContainerTypeIndex& idx = options.get<1>();
// Add subnet specific options.
BOOST_FOREACH(Subnet::OptionDescriptor desc, options_) {
Subnet::OptionContainerTypeRange range = idx.equal_range(desc.option->getType());
if (std::distance(range.first, range.second) > 0) {
LOG_WARN(dhcp4_logger, DHCP4_CONFIG_OPTION_DUPLICATE)
.arg(desc.option->getType()).arg(addr.toText());
}
subnet_->addOption(desc.option);
}
// Check all global options and add them to the subnet object if
// they have been configured in the global scope. If they have been
// configured in the subnet scope we don't add global option because
// the one configured in the subnet scope always takes precedence.
BOOST_FOREACH(Subnet::OptionDescriptor desc, option_defaults) {
// Get all options specified locally in the subnet and having
// code equal to global option's code.
Subnet::OptionContainerTypeRange range = idx.equal_range(desc.option->getType());
// @todo: In the future we will be searching for options using either
// an option code or namespace. Currently we have only the option
// code available so if there is at least one option found with the
// specific code we don't add the globally configured option.
// @todo with this code the first globally configured option
// with the given code will be added to a subnet. We may
// want to issue a warning about dropping the configuration of
// a global option if one already exsists.
if (std::distance(range.first, range.second) == 0) {
subnet_->addOption(desc.option);
}
}
}
/// @brief creates parsers for entries in subnet definition
///
/// @todo Add subnet-specific things here (e.g. subnet-specific options)
@ -1134,6 +1237,9 @@ private:
/// parsers are stored here
ParserCollection parsers_;
/// @brief Pointer to the created subnet object.
isc::dhcp::Subnet4Ptr subnet_;
};
/// @brief this class parses list of subnets
@ -1195,6 +1301,7 @@ public:
/// @brief collection of subnet parsers.
ParserCollection subnets_;
};
} // anonymous namespace
@ -1246,42 +1353,111 @@ configureDhcp4Server(Dhcpv4Srv& , ConstElementPtr config_set) {
LOG_DEBUG(dhcp4_logger, DBG_DHCP4_COMMAND, DHCP4_CONFIG_START).arg(config_set->str());
ParserCollection parsers;
// Some of the values specified in the configuration depend on
// other values. Typically, the values in the subnet6 structure
// depend on the global values. Thus we need to make sure that
// the global values are processed first and that they can be
// accessed by the subnet6 parsers. We separate parsers that
// should process data first (independent_parsers) from those
// that must process data when the independent data is already
// processed (dependent_parsers).
ParserCollection independent_parsers;
ParserCollection dependent_parsers;
// The subnet parsers implement data inheritance by directly
// accesing global storages. For this reason the global data
// parsers must store the parsed data into global storages
// immediatelly. This may cause data inconsistency if the
// parsing operation fails after the global storage have been
// already modified. We need to preserve the original global
// data here so as we can rollback changes when an error occurs.
Uint32Storage uint32_local(uint32_defaults);
StringStorage string_local(string_defaults);
OptionStorage option_local(option_defaults);
// answer will hold the result.
ConstElementPtr answer;
// rollback informs whether error occured and original data
// have to be restored to global storages.
bool rollback = false;
try {
// Iterate over all independent parsers first (all but subnet6)
// and try to parse the data.
BOOST_FOREACH(ConfigPair config_pair, config_set->mapValue()) {
ParserPtr parser(createGlobalDhcp4ConfigParser(config_pair.first));
parser->build(config_pair.second);
parsers.push_back(parser);
if (config_pair.first != "subnet4") {
independent_parsers.push_back(parser);
parser->build(config_pair.second);
// The commit operation here may modify the global storage
// but we need it so as the subnet6 parser can access the
// parsed data.
parser->commit();
}
}
// Process dependent configuration data.
BOOST_FOREACH(ConfigPair config_pair, config_set->mapValue()) {
ParserPtr parser(createGlobalDhcp4ConfigParser(config_pair.first));
if (config_pair.first == "subnet4") {
dependent_parsers.push_back(parser);
parser->build(config_pair.second);
}
}
} catch (const isc::Exception& ex) {
ConstElementPtr answer = isc::config::createAnswer(1,
string("Configuration parsing failed: ") + ex.what());
return (answer);
answer =
isc::config::createAnswer(1, string("Configuration parsing failed: ") + ex.what());
// An error occured, so make sure that we restore original data.
rollback = true;
} catch (...) {
// for things like bad_cast in boost::lexical_cast
ConstElementPtr answer = isc::config::createAnswer(1,
string("Configuration parsing failed"));
answer =
isc::config::createAnswer(1, string("Configuration parsing failed"));
// An error occured, so make sure that we restore original data.
rollback = true;
}
try {
BOOST_FOREACH(ParserPtr parser, parsers) {
parser->commit();
// So far so good, there was no parsing error so let's commit the
// configuration. This will add created subnets and option values into
// the server's configuration.
// This operation should be exception safe but let's make sure.
if (!rollback) {
try {
BOOST_FOREACH(ParserPtr parser, dependent_parsers) {
parser->commit();
}
}
catch (const isc::Exception& ex) {
answer =
isc::config::createAnswer(2, string("Configuration commit failed: ") + ex.what());
rollback = true;
} catch (...) {
// for things like bad_cast in boost::lexical_cast
answer =
isc::config::createAnswer(2, string("Configuration commit failed"));
rollback = true;
}
}
catch (const isc::Exception& ex) {
ConstElementPtr answer = isc::config::createAnswer(2,
string("Configuration commit failed: ") + ex.what());
// Rollback changes as the configuration parsing failed.
if (rollback) {
std::swap(uint32_defaults, uint32_local);
std::swap(string_defaults, string_local);
std::swap(option_defaults, option_local);
return (answer);
} catch (...) {
// for things like bad_cast in boost::lexical_cast
ConstElementPtr answer = isc::config::createAnswer(2,
string("Configuration commit failed"));
}
LOG_INFO(dhcp4_logger, DHCP4_CONFIG_COMPLETE).arg(config_details);
ConstElementPtr answer = isc::config::createAnswer(0, "Configuration commited.");
// Everything was fine. Configuration is successful.
answer = isc::config::createAnswer(0, "Configuration commited.");
return (answer);
}

View File

@ -389,9 +389,9 @@ TEST_F(Dhcp4ParserTest, poolOutOfSubnet) {
EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
// returned value must be 2 (values error)
// returned value must be 1 (values error)
// as the pool does not belong to that subnet
checkResult(status, 2);
checkResult(status, 1);
}
// Goal of this test is to verify if pools can be defined

View File

@ -77,8 +77,8 @@ void Subnet4::addPool4(const Pool4Ptr& pool) {
if (!inRange(first_addr) || !inRange(last_addr)) {
isc_throw(BadValue, "Pool4 (" << first_addr.toText() << "-" << last_addr.toText()
<< " does not belong in this (" << prefix_ << "/" << prefix_len_
<< ") subnet4");
<< " does not belong in this (" << prefix_.toText() << "/"
<< static_cast<int>(prefix_len_) << ") subnet4");
}
/// @todo: Check that pools do not overlap