Commit 04cb6e8a authored by Marcin Siodelski's avatar Marcin Siodelski

[3589] Option data configuration parsers store parsed data into cfgmgr.

parent 08c3a4ae
......@@ -42,72 +42,6 @@ using namespace isc::asiolink;
namespace {
/// @brief Parser for DHCP4 option data value.
///
/// This parser parses configuration entries that specify value of
/// a single option specific to DHCP4. It provides the DHCP4-specific
/// implementation of the abstract class OptionDataParser.
class Dhcp4OptionDataParser : public OptionDataParser {
public:
/// @brief Constructor.
///
/// @param dummy first param, option names are always "Dhcp4/option-data[n]"
/// @param options is the option storage in which to store the parsed option
/// upon "commit".
/// @param global_context is a pointer to the global context which
/// stores global scope parameters, options, option defintions.
Dhcp4OptionDataParser(const std::string&,
OptionStoragePtr options, ParserContextPtr global_context)
:OptionDataParser("", options, global_context) {
}
/// @brief static factory method for instantiating Dhcp4OptionDataParsers
///
/// @param param_name name of the parameter to be parsed.
/// @param options storage where the parameter value is to be stored.
/// @param global_context is a pointer to the global context which
/// stores global scope parameters, options, option defintions.
/// @return returns a pointer to a new OptionDataParser. Caller is
/// is responsible for deleting it when it is no longer needed.
static OptionDataParser* factory(const std::string& param_name,
OptionStoragePtr options, ParserContextPtr global_context) {
return (new Dhcp4OptionDataParser(param_name, options, global_context));
}
protected:
/// @brief Finds an option definition within the server's option space
///
/// Given an option space and an option code, find the correpsonding
/// option defintion within the server's option defintion storage.
///
/// @param option_space name of the parameter option space
/// @param option_code numeric value of the parameter to find
/// @return OptionDefintionPtr of the option defintion or an
/// empty OptionDefinitionPtr if not found.
/// @throw DhcpConfigError if the option space requested is not valid
/// for this server.
virtual OptionDefinitionPtr findServerSpaceOptionDefinition (
std::string& option_space, uint32_t option_code) {
OptionDefinitionPtr def;
if (option_space == "dhcp4" &&
LibDHCP::isStandardOption(Option::V4, option_code)) {
def = LibDHCP::getOptionDef(Option::V4, option_code);
} else if (option_space == "dhcp6") {
isc_throw(DhcpConfigError, "'dhcp6' option space name is reserved"
<< " for DHCPv6 server");
} else {
// Check if this is a vendor-option. If it is, get vendor-specific
// definition.
uint32_t vendor_id = CfgOption::optionSpaceToVendorId(option_space);
if (vendor_id) {
def = LibDHCP::getVendorOptionDef(Option::V4, vendor_id, option_code);
}
}
return (def);
}
};
/// @brief Parser for IPv4 pool definitions.
///
/// This is the IPv4 derivation of the PoolParser class and handles pool
......@@ -245,9 +179,7 @@ protected:
} else if (config_id.compare("relay") == 0) {
parser = new RelayInfoParser(config_id, relay_info_, Option::V4);
} else if (config_id.compare("option-data") == 0) {
parser = new OptionDataListParser(config_id, options_,
global_context_,
Dhcp4OptionDataParser::factory);
parser = new OptionDataListParser(config_id, options_, AF_INET);
} else {
isc_throw(NotImplemented, "unsupported parameter: " << config_id);
}
......@@ -448,10 +380,7 @@ namespace dhcp {
} else if (config_id.compare("subnet4") == 0) {
parser = new Subnets4ListConfigParser(config_id);
} else if (config_id.compare("option-data") == 0) {
parser = new OptionDataListParser(config_id,
globalContext()->options_,
globalContext(),
Dhcp4OptionDataParser::factory);
parser = new OptionDataListParser(config_id, CfgOptionPtr(), AF_INET);
} else if (config_id.compare("option-def") == 0) {
parser = new OptionDefListParser(config_id, globalContext());
} else if ((config_id.compare("version") == 0) ||
......
......@@ -56,73 +56,6 @@ typedef boost::shared_ptr<BooleanParser> BooleanParserPtr;
typedef boost::shared_ptr<StringParser> StringParserPtr;
typedef boost::shared_ptr<Uint32Parser> Uint32ParserPtr;
/// @brief Parser for DHCP6 option data value.
///
/// This parser parses configuration entries that specify value of
/// a single option specific to DHCP6. It provides the DHCP6-specific
/// implementation of the abstract class OptionDataParser.
class Dhcp6OptionDataParser : public OptionDataParser {
public:
/// @brief Constructor.
///
/// @param dummy first param, option names are always "Dhcp6/option-data[n]"
/// @param options is the option storage in which to store the parsed option
/// upon "commit".
/// @param global_context is a pointer to the global context which
/// stores global scope parameters, options, option defintions.
Dhcp6OptionDataParser(const std::string&, OptionStoragePtr options,
ParserContextPtr global_context)
:OptionDataParser("", options, global_context) {
}
/// @brief static factory method for instantiating Dhcp4OptionDataParsers
///
/// @param param_name name of the parameter to be parsed.
/// @param options storage where the parameter value is to be stored.
/// @param global_context is a pointer to the global context which
/// stores global scope parameters, options, option defintions.
/// @return returns a pointer to a new OptionDataParser. Caller is
/// is responsible for deleting it when it is no longer needed.
static OptionDataParser* factory(const std::string& param_name,
OptionStoragePtr options, ParserContextPtr global_context) {
return (new Dhcp6OptionDataParser(param_name, options, global_context));
}
protected:
/// @brief Finds an option definition within the server's option space
///
/// Given an option space and an option code, find the correpsonding
/// option defintion within the server's option defintion storage.
///
/// @param option_space name of the parameter option space
/// @param option_code numeric value of the parameter to find
/// @return OptionDefintionPtr of the option defintion or an
/// empty OptionDefinitionPtr if not found.
/// @throw DhcpConfigError if the option space requested is not valid
/// for this server.
virtual OptionDefinitionPtr findServerSpaceOptionDefinition (
std::string& option_space, uint32_t option_code) {
OptionDefinitionPtr def;
if (option_space == "dhcp6" &&
LibDHCP::isStandardOption(Option::V6, option_code)) {
def = LibDHCP::getOptionDef(Option::V6, option_code);
} else if (option_space == "dhcp4") {
isc_throw(DhcpConfigError, "'dhcp4' option space name is reserved"
<< " for DHCPv4 server");
} else {
// Check if this is a vendor-option. If it is, get vendor-specific
// definition.
uint32_t vendor_id = CfgOption::optionSpaceToVendorId(option_space);
if (vendor_id) {
def = LibDHCP::getVendorOptionDef(Option::V6, vendor_id, option_code);
}
}
return (def);
}
};
/// @brief Parser for IPv6 pool definitions.
///
/// This is the IPv6 derivation of the PoolParser class and handles pool
......@@ -456,9 +389,7 @@ protected:
} else if (config_id.compare("pd-pools") == 0) {
parser = new PdPoolListParser(config_id, pools_);
} else if (config_id.compare("option-data") == 0) {
parser = new OptionDataListParser(config_id, options_,
global_context_,
Dhcp6OptionDataParser::factory);
parser = new OptionDataListParser(config_id, options_, AF_INET6);
} else {
isc_throw(NotImplemented, "unsupported parameter: " << config_id);
}
......@@ -667,10 +598,7 @@ namespace dhcp {
} else if (config_id.compare("subnet6") == 0) {
parser = new Subnets6ListConfigParser(config_id);
} else if (config_id.compare("option-data") == 0) {
parser = new OptionDataListParser(config_id,
globalContext()->options_,
globalContext(),
Dhcp6OptionDataParser::factory);
parser = new OptionDataListParser(config_id, CfgOptionPtr(), AF_INET6);
} else if (config_id.compare("option-def") == 0) {
parser = new OptionDefListParser(config_id, globalContext());
} else if (config_id.compare("version") == 0) {
......
......@@ -2741,7 +2741,11 @@ TEST_F(Dhcp6ParserTest, vendorOptionsCsv) {
// The goal of this test is to verify that the standard option can
// be configured to encapsulate multiple other options.
TEST_F(Dhcp6ParserTest, stdOptionDataEncapsulate) {
/// @todo This test is currently disabled because it relies on the option
/// 17 which is treated differently than all other options. There are no
/// other standard options used by Kea which would encapsulate other
/// options and for which values could be configured here.
TEST_F(Dhcp6ParserTest, DISABLED_stdOptionDataEncapsulate) {
// The configuration is two stage process in this test.
// In the first stahe we create definitions of suboptions
......
......@@ -69,6 +69,33 @@ CfgOption::copy(CfgOption& other) const {
other = new_cfg;
}
void
CfgOption::encapsulate() {
// Append sub-options to the top level "dhcp4" option space.
encapsulateInternal(DHCP4_OPTION_SPACE);
// Append sub-options to the top level "dhcp6" option space.
encapsulateInternal(DHCP6_OPTION_SPACE);
}
void
CfgOption::encapsulateInternal(const std::string& option_space) {
OptionContainerPtr options = getAll(option_space);
for (OptionContainer::const_iterator opt = options->begin();
opt != options->end(); ++opt) {
const std::string& encap_space = opt->option->getEncapsulatedSpace();
if (!encap_space.empty()) {
OptionContainerPtr encap_options = getAll(encap_space);
for (OptionContainer::const_iterator encap_opt =
encap_options->begin(); encap_opt != encap_options->end();
++encap_opt) {
if (!opt->option->getOption(encap_opt->option->getType())) {
opt->option->addOption(encap_opt->option);
}
}
}
}
}
template <typename Selector>
void
CfgOption::mergeInternal(const OptionSpaceContainer<OptionContainer,
......
......@@ -269,6 +269,14 @@ public:
/// @param [out] other An object to copy the configuration to.
void copy(CfgOption& other) const;
/// @brief Appends encapsulated options to top-level options.
///
/// This method iterates over the top-level options (from "dhcp4"
/// and "dhcp6" option space) and checks which option spaces these
/// options encapsulate. For each encapsulated option space, the
/// options from this option space are appended to top-level options.
void encapsulate();
/// @brief Returns all options for the specified option space.
///
/// This method will not return vendor options, i.e. having option space
......@@ -332,6 +340,12 @@ public:
private:
/// @brief Appends encapsulated options to the options in an option space.
///
/// @param option_space Name of the option space containing optionn to
/// which encapsulated options are appended.
void encapsulateInternal(const std::string& option_space);
/// @brief Merges data from two option containers.
///
/// This method merges options from one option container to another
......
......@@ -43,7 +43,6 @@ ParserContext::ParserContext(Option::Universe universe):
boolean_values_(new BooleanStorage()),
uint32_values_(new Uint32Storage()),
string_values_(new StringStorage()),
options_(new OptionStorage()),
hooks_libraries_(),
universe_(universe)
{
......@@ -53,7 +52,6 @@ ParserContext::ParserContext(const ParserContext& rhs):
boolean_values_(),
uint32_values_(),
string_values_(),
options_(),
hooks_libraries_(),
universe_(rhs.universe_)
{
......@@ -77,7 +75,6 @@ ParserContext::copyContext(const ParserContext& ctx) {
copyContextPointer(ctx.boolean_values_, boolean_values_);
copyContextPointer(ctx.uint32_values_, uint32_values_);
copyContextPointer(ctx.string_values_, string_values_);
copyContextPointer(ctx.options_, options_);
copyContextPointer(ctx.hooks_libraries_, hooks_libraries_);
// Copy universe.
universe_ = ctx.universe_;
......@@ -281,20 +278,16 @@ HooksLibrariesParser::getLibraries(std::vector<std::string>& libraries,
}
// **************************** OptionDataParser *************************
OptionDataParser::OptionDataParser(const std::string&, OptionStoragePtr options,
ParserContextPtr global_context)
OptionDataParser::OptionDataParser(const std::string&, const CfgOptionPtr& cfg,
const uint16_t address_family)
: boolean_values_(new BooleanStorage()),
string_values_(new StringStorage()), uint32_values_(new Uint32Storage()),
options_(options), option_descriptor_(false),
global_context_(global_context) {
if (!options_) {
isc_throw(isc::dhcp::DhcpConfigError, "parser logic error: "
<< "options storage may not be NULL");
}
if (!global_context_) {
isc_throw(isc::dhcp::DhcpConfigError, "parser logic error: "
<< "context may may not be NULL");
string_values_(new StringStorage()), uint32_values_(new Uint32Storage()),
option_descriptor_(false), cfg_(cfg),
address_family_(address_family) {
// If configuration not specified, then it is a global configuration
// scope.
if (!cfg_) {
cfg_ = CfgMgr::instance().getStagingCfg()->getCfgOption();
}
}
......@@ -333,39 +326,57 @@ OptionDataParser::build(ConstElementPtr option_data_entries) {
// Try to create the option instance.
createOption(option_data_entries);
}
void
OptionDataParser::commit() {
if (!option_descriptor_.option) {
// Before we can commit the new option should be configured. If it is
// not than somebody must have called commit() before build().
isc_throw(isc::InvalidOperation,
"parser logic error: no option has been configured and"
" thus there is nothing to commit. Has build() been called?");
}
uint16_t opt_type = option_descriptor_.option->getType();
OptionContainerPtr options = options_->getItems(option_space_);
// The getItems() should never return NULL pointer. If there are no
// options configured for the particular option space a pointer
// to an empty container should be returned.
assert(options);
OptionContainerTypeIndex& idx = options->get<1>();
// Try to find options with the particular option code in the main
// storage. If found, remove these options because they will be
// replaced with new one.
OptionContainerTypeRange range = idx.equal_range(opt_type);
if (std::distance(range.first, range.second) > 0) {
idx.erase(range.first, range.second);
cfg_->add(option_descriptor_.option, option_descriptor_.persistent,
option_space_);
}
void
OptionDataParser::commit() {
// Does nothing
}
OptionDefinitionPtr
OptionDataParser::findServerSpaceOptionDefinition(const std::string& option_space,
const uint32_t option_code) const {
const Option::Universe u = address_family_ == AF_INET ?
Option::V4 : Option::V6;
if ((option_space == DHCP4_OPTION_SPACE) && (u == Option::V6)) {
isc_throw(DhcpConfigError, "'" << DHCP4_OPTION_SPACE
<< "' option space name is reserved for DHCPv4 server");
} else if ((option_space == DHCP6_OPTION_SPACE) && (u == Option::V4)) {
isc_throw(DhcpConfigError, "'" << DHCP6_OPTION_SPACE
<< "' option space name is reserved for DHCPv6 server");
}
// Append new option to the main storage.
options_->addItem(option_descriptor_, option_space_);
OptionDefinitionPtr def;
if (((option_space == DHCP4_OPTION_SPACE) || (option_space == DHCP6_OPTION_SPACE)) &&
LibDHCP::isStandardOption(u, option_code)) {
def = LibDHCP::getOptionDef(u, option_code);
} else {
// Check if this is a vendor-option. If it is, get vendor-specific
// definition.
uint32_t vendor_id = CfgOption::optionSpaceToVendorId(option_space);
if (vendor_id) {
def = LibDHCP::getVendorOptionDef(u, vendor_id, option_code);
}
}
return (def);
}
void
OptionDataParser::createOption(ConstElementPtr option_data) {
const Option::Universe universe = address_family_ == AF_INET ?
Option::V4 : Option::V6;
// Check if mandatory parameters are specified.
uint32_t code;
std::string name;
......@@ -379,8 +390,8 @@ OptionDataParser::createOption(ConstElementPtr option_data) {
ex.what() << "(" << option_data->getPosition() << ")");
}
// Check parameters having default values.
std::string space = string_values_->getOptionalParam("space",
global_context_->universe_ == Option::V4 ? "dhcp4" : "dhcp6");
std::string space = string_values_->getOptionalParam("space", universe == Option::V4 ?
"dhcp4" : "dhcp6");
bool csv_format = boolean_values_->getOptionalParam("csv-format", false);
// Option code is held in the uint32_t storage but is supposed to
......@@ -391,14 +402,14 @@ OptionDataParser::createOption(ConstElementPtr option_data) {
isc_throw(DhcpConfigError, "option code must not be zero "
"(" << uint32_values_->getPosition("code") << ")");
} else if (global_context_->universe_ == Option::V4 &&
} else if (universe == Option::V4 &&
code > std::numeric_limits<uint8_t>::max()) {
isc_throw(DhcpConfigError, "invalid option code '" << code
<< "', it must not exceed '"
<< static_cast<int>(std::numeric_limits<uint8_t>::max())
<< "' (" << uint32_values_->getPosition("code") << ")");
} else if (global_context_->universe_ == Option::V6 &&
} else if (universe == Option::V6 &&
code > std::numeric_limits<uint16_t>::max()) {
isc_throw(DhcpConfigError, "invalid option code '" << code
<< "', it must not exceed '"
......@@ -443,18 +454,7 @@ OptionDataParser::createOption(ConstElementPtr option_data) {
// need to search for its definition among user-configured
// options. They are expected to be in the global storage
// already.
OptionDefContainerPtr defs =
CfgMgr::instance().getStagingCfg()->getCfgOptionDef()->getAll(space);
// The getItems() should never return the NULL pointer. If there are
// no option definitions for the particular option space a pointer
// to an empty container should be returned.
assert(defs);
const OptionDefContainerTypeIndex& idx = defs->get<1>();
OptionDefContainerTypeRange range = idx.equal_range(code);
if (std::distance(range.first, range.second) > 0) {
def = *range.first;
}
def = CfgMgr::instance().getStagingCfg()->getCfgOptionDef()->get(space, code);
// It's ok if we don't have option format if the option is
// specified as hex
......@@ -510,8 +510,8 @@ OptionDataParser::createOption(ConstElementPtr option_data) {
// for all options. Consequently an error will be issued if an option
// definition does not exist for a particular option code. For now it is
// ok to create generic option if definition does not exist.
OptionPtr option(new Option(global_context_->universe_,
static_cast<uint16_t>(code), binary));
OptionPtr option(new Option(universe,
static_cast<uint16_t>(code), binary));
// The created option is stored in option_descriptor_ class member
// until the commit stage when it is inserted into the main storage.
// If an option with the same code exists in main storage already the
......@@ -537,13 +537,12 @@ OptionDataParser::createOption(ConstElementPtr option_data) {
// an instance of our option.
try {
OptionPtr option = csv_format ?
def->optionFactory(global_context_->universe_,
code, data_tokens) :
def->optionFactory(global_context_->universe_,
code, binary);
def->optionFactory(universe, code, data_tokens) :
def->optionFactory(universe, code, binary);
OptionDescriptor desc(option, false);
option_descriptor_.option = option;
option_descriptor_.persistent = false;
} catch (const isc::Exception& ex) {
isc_throw(DhcpConfigError, "option data does not match"
<< " option definition (space: " << space
......@@ -559,39 +558,18 @@ OptionDataParser::createOption(ConstElementPtr option_data) {
// **************************** OptionDataListParser *************************
OptionDataListParser::OptionDataListParser(const std::string&,
OptionStoragePtr options, ParserContextPtr global_context,
OptionDataParserFactory* optionDataParserFactory)
: options_(options), local_options_(new OptionStorage()),
global_context_(global_context),
optionDataParserFactory_(optionDataParserFactory) {
if (!options_) {
isc_throw(isc::dhcp::DhcpConfigError, "parser logic error: "
<< "options storage may not be NULL");
}
if (!options_) {
isc_throw(isc::dhcp::DhcpConfigError, "parser logic error: "
<< "context may not be NULL");
}
if (!optionDataParserFactory_) {
isc_throw(isc::dhcp::DhcpConfigError, "parser logic error: "
<< "option data parser factory may not be NULL");
}
const CfgOptionPtr& cfg,
const uint16_t address_family)
: cfg_(cfg), address_family_(address_family) {
}
void
OptionDataListParser::build(ConstElementPtr option_data_list) {
BOOST_FOREACH(ConstElementPtr option_value, option_data_list->listValue()) {
boost::shared_ptr<OptionDataParser>
parser((*optionDataParserFactory_)("option-data",
local_options_, global_context_));
parser(new OptionDataParser("option-data", cfg_, address_family_));
// options_ member will hold instances of all options thus
// each OptionDataParser takes it as a storage.
// Build the instance of a single option.
parser->build(option_value);
// Store a parser as it will be used to commit.
parsers_.push_back(parser);
}
}
......@@ -601,11 +579,6 @@ OptionDataListParser::commit() {
BOOST_FOREACH(ParserPtr parser, parsers_) {
parser->commit();
}
// Parsing was successful and we have all configured
// options in local storage. We can now replace old values
// with new values.
std::swap(*local_options_, *options_);
}
// ******************************** OptionDefParser ****************************
......@@ -977,15 +950,16 @@ SubnetConfigParser::SubnetConfigParser(const std::string&,
ParserContextPtr global_context,
const isc::asiolink::IOAddress& default_addr)
: uint32_values_(new Uint32Storage()), string_values_(new StringStorage()),
pools_(new PoolStorage()), options_(new OptionStorage()),
global_context_(global_context),
relay_info_(new isc::dhcp::Subnet::RelayInfo(default_addr)) {
pools_(new PoolStorage()), global_context_(global_context),
relay_info_(new isc::dhcp::Subnet::RelayInfo(default_addr)),
options_(new CfgOption()) {
// The first parameter should always be "subnet", but we don't check
// against that here in case some wants to reuse this parser somewhere.
if (!global_context_) {
isc_throw(isc::dhcp::DhcpConfigError, "parser logic error: "
<< "context storage may not be NULL");
}
}
void
......@@ -1027,61 +1001,6 @@ SubnetConfigParser::build(ConstElementPtr subnet) {
}
}
void
SubnetConfigParser::appendSubOptions(const std::string& option_space,
OptionPtr& option) {
// Only non-NULL options are stored in option container.
// If this option pointer is NULL this is a serious error.
assert(option);
OptionDefinitionPtr def;
if (isServerStdOption(option_space, option->getType())) {
def = getServerStdOptionDefinition(option->getType());
// Definitions for some of the standard options hasn't been
// implemented so it is ok to leave here.
if (!def) {
return;
}
} else {
OptionDefContainerPtr defs = CfgMgr::instance().getStagingCfg()
->getCfgOptionDef()->getAll(option_space);
const OptionDefContainerTypeIndex& idx = defs->get<1>();
const OptionDefContainerTypeRange& range =
idx.equal_range(option->getType());
// There is no definition so we have to leave.
if (std::distance(range.first, range.second) == 0) {
return;
}
def = *range.first;
// If the definition exists, it must be non-NULL.
// Otherwise it is a programming error.
assert(def);
}
// We need to get option definition for the particular option space
// and code. This definition holds the information whether our
// option encapsulates any option space.
// Get the encapsulated option space name.
std::string encapsulated_space = def->getEncapsulatedSpace();
// If option space name is empty it means that our option does not
// encapsulate any option space (does not include sub-options).
if (!encapsulated_space.empty()) {
// Get the sub-options that belong to the encapsulated
// option space.
const OptionContainerPtr sub_opts =
global_context_->options_->getItems(encapsulated_space);
// Append sub-options to the option.
BOOST_FOREACH(OptionDescriptor desc, *sub_opts) {
if (desc.option) {
option->addOption(desc.option);
}
}
}
}
void
SubnetConfigParser::createSubnet() {
std::string subnet_txt;
......@@ -1146,64 +1065,12 @@ SubnetConfigParser::createSubnet() {
subnet_->setIface(iface);
}
// We are going to move configured options to the Subnet object.
// Configured options reside in the container where options
// are grouped by space names. Thus we need to get all space names
// and iterate over all options that belong to them.
std::list<std::string> space_names = options_->getOptionSpaceNames();
BOOST_FOREACH(std::string option_space, space_names) {
// Get all options within a particular option space.
BOOST_FOREACH(OptionDescriptor desc,
*options_->getItems(option_space)) {
// The pointer should be non-NULL. The validation is expected
// to be performed by the OptionDataParser before adding an
// option descriptor to the container.
assert(desc.option);
// We want to check whether an option with the particular
// option code has been already added. If so, we want
// to issue a warning.
OptionDescriptor existing_desc =
subnet_->getCfgOption()->get("option_space", desc.option->getType());
if (existing_desc.option) {
duplicate_option_warning(desc.option->getType(), addr);
}
// Add sub-options (if any).