Commit adf283b9 authored by Marcin Siodelski's avatar Marcin Siodelski
Browse files

[2317] Added option definition config parser for the DHCPv6 server.

parent 5477d367
......@@ -40,12 +40,24 @@
#include <stdint.h>
using namespace std;
using namespace isc;
using namespace isc::data;
using namespace isc::dhcp;
using namespace isc::asiolink;
namespace {
// Forward declarations of some of the parser classes.
// They are used to define pointer types for these classes.
class BooleanParser;
class StringParser;
class Uint32Parser;
// Pointers to various parser objects.
typedef boost::shared_ptr<BooleanParser> BooleanParserPtr;
typedef boost::shared_ptr<StringParser> StringParserPtr;
typedef boost::shared_ptr<Uint32Parser> Uint32ParserPtr;
/// @brief Auxiliary type used for storing an element name and its parser.
typedef pair<string, ConstElementPtr> ConfigPair;
......@@ -64,6 +76,10 @@ typedef std::map<string, uint32_t> Uint32Storage;
/// @brief Collection of elements that store string values.
typedef std::map<string, string> StringStorage;
/// @brief Storage for option definitions.
typedef OptionSpaceContainer<OptionDefContainer,
OptionDefinitionPtr> OptionDefStorage;
/// @brief Collection of address pools.
///
/// This type is used as intermediate storage, when pools are parsed, but there is
......@@ -943,6 +959,251 @@ public:
ParserCollection parsers_;
};
/// @brief Parser for a single option definition.
///
/// This parser creates an instance of a single option definition.
class OptionDefParser: DhcpConfigParser {
public:
/// @brief Constructor.
///
/// This constructor sets the pointer to the option definitions
/// storage to NULL. It must be set to point to the actual storage
/// before \ref build is called.
OptionDefParser(const std::string&)
: storage_(NULL) {
}
/// @brief Parses an entry that describes single option definition.
///
/// @param option_def a configuration entry to be parsed.
///
/// @throw DhcpConfigError if parsing was unsuccessful.
void build(ConstElementPtr option_def) {
if (storage_ == NULL) {
isc_throw(DhcpConfigError, "parser logic error: storage must be set"
" before parsing option definition data");
}
// Parse the elements that make up the option definition.
BOOST_FOREACH(ConfigPair param, option_def->mapValue()) {
std::string entry(param.first);
ParserPtr parser;
if (entry == "name" || entry == "type" ||
entry == "record-types" || entry == "space") {
StringParserPtr
str_parser(dynamic_cast<StringParser*>(StringParser::factory(entry)));
if (str_parser) {
str_parser->setStorage(&string_values_);
parser = str_parser;
}
} else if (entry == "code") {
Uint32ParserPtr
code_parser(dynamic_cast<Uint32Parser*>(Uint32Parser::factory(entry)));
if (code_parser) {
code_parser->setStorage(&uint32_values_);
parser = code_parser;
}
} else if (entry == "array") {
BooleanParserPtr
array_parser(dynamic_cast<BooleanParser*>(BooleanParser::factory(entry)));
if (array_parser) {
array_parser->setStorage(&boolean_values_);
parser = array_parser;
}
} else {
isc_throw(DhcpConfigError, "invalid parameter: " << entry);
}
parser->build(param.second);
parser->commit();
}
// Create an instance of option definition.
createOptionDef();
// Get all items we collected so far for the particular option space.
OptionDefContainerPtr defs = storage_->getItems(option_space_name_);
// Check if there are any items with option code the same as the
// one specified for the definition we are now creating.
const OptionDefContainerTypeIndex& idx = defs->get<1>();
const OptionDefContainerTypeRange& range =
idx.equal_range(option_definition_->getCode());
// If there are any items with this option code already we need
// to issue an error because we don't allow duplicates for
// option definitions within an option space.
if (std::distance(range.first, range.second) > 0) {
isc_throw(DhcpConfigError, "duplicated option definition for"
<< " code '" << option_definition_->getCode() << "'");
}
}
/// @brief Stores the parsed option definition in a storage.
void commit() {
// @todo validate option space name once 2313 is merged.
if (storage_ && option_definition_) {
storage_->addItem(option_definition_, option_space_name_);
}
}
/// @brief Sets a pointer to a storage.
///
/// The newly created instance of an option definition will be
/// added to a storage given by the argument.
///
/// @param storage pointer to a storage where the option definition
/// will be added to.
void setStorage(OptionDefStorage* storage) {
storage_ = storage;
}
private:
/// @brief Create option definition from the parsed parameters.
void createOptionDef() {
// Get the option space name and validate it.
std::string space = getParam<std::string>("space", string_values_);
// @todo uncomment the code below when the #2313 is merged.
/* if (!OptionSpace::validateName()) {
isc_throw(DhcpConfigError, "invalid option space name '"
<< space << "'");
} */
// Get other parameters that are needed to create the
// option definition.
std::string name = getParam<std::string>("name", string_values_);
uint32_t code = getParam<uint32_t>("code", uint32_values_);
std::string type = getParam<std::string>("type", string_values_);
bool array_type = getParam<bool>("array", boolean_values_);
OptionDefinitionPtr def(new OptionDefinition(name, code,
type, array_type));
// The record-types field may carry a list of comma separated names
// of data types that form a record.
std::string record_types = getParam<std::string>("record-types",
string_values_);
// Split the list of record types into tokens.
std::vector<std::string> record_tokens =
isc::util::str::tokens(record_types, ",");
// Iterate over each token and add a record typy into
// option definition.
BOOST_FOREACH(std::string record_type, record_tokens) {
try {
boost::trim(record_type);
if (!record_type.empty()) {
def->addRecordField(record_type);
}
} catch (const Exception& ex) {
isc_throw(DhcpConfigError, "invalid record type values"
<< " specified for the option definition: "
<< ex.what());
}
}
// Check the option definition parameters are valid.
try {
def->validate();
} catch (const isc::Exception ex) {
isc_throw(DhcpConfigError, "invalid option definition"
<< " parameters" << ex.what());
}
// Option definition has been created successfully.
option_space_name_ = space;
option_definition_ = def;
}
/// Instance of option definition being created by this parser.
OptionDefinitionPtr option_definition_;
std::string option_space_name_;
/// Pointer to a storage where the option definition will be
/// added when \ref commit is called.
OptionDefStorage* storage_;
/// Storage for boolean values.
BooleanStorage boolean_values_;
/// Storage for string values.
StringStorage string_values_;
/// Storage for uint32 values.
Uint32Storage uint32_values_;
};
/// @brief Parser for a list of option definitions.
///
/// This parser iterates over all configuration entries that define
/// option definitions and creates instances of these definitions.
/// If the parsing is successful, the collection of created definitions
/// is put into the provided storage.
class OptionDefListParser : DhcpConfigParser {
public:
/// @brief Constructor.
///
/// This constructor initializes the pointer to option definitions
/// storage to NULL value. This pointer has to be set to point to
/// the actual storage before the \ref build function is called.
OptionDefListParser(const std::string&) {
}
/// @brief Parse configuration entries.
///
/// This function parses configuration entries and creates instances
/// of option definitions.
///
/// @param option_def_list pointer to an element that holds entries
/// that define option definitions.
/// @throw DhcpConfigError if configuration parsing fails.
void build(ConstElementPtr option_def_list) {
if (!option_def_list) {
isc_throw(DhcpConfigError, "parser error: a pointer to a list of"
<< " option definitions is NULL");
}
BOOST_FOREACH(ConstElementPtr option_def, option_def_list->listValue()) {
boost::shared_ptr<OptionDefParser>
parser(new OptionDefParser("single-option-def"));
parser->setStorage(&option_defs_local_);
parser->build(option_def);
parser->commit();
}
}
/// @brief Stores option definitions in the provided storage.
void commit() {
CfgMgr& cfg_mgr = CfgMgr::instance();
cfg_mgr.deleteOptionDefs();
// We need to move option definitions from the temporary
// storage to the global storage.
BOOST_FOREACH(std::string space_name,
option_defs_local_.getOptionSpaceNames()) {
BOOST_FOREACH(OptionDefinitionPtr def,
*option_defs_local_.getItems(space_name)) {
assert(def);
cfg_mgr.addOptionDef(def, space_name);
}
}
}
/// @brief Create an OptionDefListParser object.
///
/// @param param_name configuration entry holding option definitions.
///
/// @return OptionDefListParser object.
static DhcpConfigParser* factory(const std::string& param_name) {
return (new OptionDefListParser(param_name));
}
private:
/// Temporary storage for option definitions. It holds option
/// definitions before \ref commit is called.
OptionDefStorage option_defs_local_;
};
/// @brief this class parses a single subnet
///
/// This class parses the whole subnet definition. It creates parsers
......@@ -1310,6 +1571,7 @@ DhcpConfigParser* createGlobalDhcpConfigParser(const std::string& config_id) {
factories["interface"] = InterfaceListConfigParser::factory;
factories["subnet6"] = Subnets6ListConfigParser::factory;
factories["option-data"] = OptionDataListParser::factory;
factories["option-def"] = OptionDefListParser::factory;
factories["version"] = StringParser::factory;
FactoryMap::iterator f = factories.find(config_id);
......
......@@ -40,6 +40,56 @@
"item_default": 4000
},
{ "item_name": "option-def",
"item_type": "list",
"item_optional": false,
"item_default": [],
"list_item_spec":
{
"item_name": "single-option-def",
"item_type": "map",
"item_optional": false,
"item_default": {},
"map_item_spec": [
{
"item_name": "name",
"item_type": "string",
"item_optional": false,
"item_default": ""
},
{ "item_name": "code",
"item_type": "integer",
"item_optional": false,
"item_default": 0,
},
{ "item_name": "type",
"item_type": "string",
"item_optional": false,
"item_default": "",
},
{ "item_name": "array",
"item_type": "boolean",
"item_optional": false,
"item_default": False
},
{ "item_name": "record_types",
"item_type": "string",
"item_optional": false,
"item_default": "",
},
{ "item_name": "space",
"item_type": "string",
"item_optional": false,
"item_default": ""
} ]
}
},
{ "item_name": "option-data",
"item_type": "list",
"item_optional": false,
......
......@@ -53,6 +53,15 @@ public:
resetConfiguration();
};
// Checks if config_result (result of DHCP server configuration) has
// expected code (0 for success, other for failures).
// Also stores result in rcode_ and comment_.
void checkResult(ConstElementPtr status, int expected_code) {
ASSERT_TRUE(status);
comment_ = parseAnswer(rcode_, status);
EXPECT_EQ(expected_code, rcode_);
}
/// @brief Create the simple configuration with single option.
///
/// This function allows to set one of the parameters that configure
......@@ -144,6 +153,7 @@ public:
"\"renew-timer\": 1000, "
"\"valid-lifetime\": 4000, "
"\"subnet6\": [ ], "
"\"option-def\": [ ], "
"\"option-data\": [ ] }";
try {
......@@ -427,6 +437,363 @@ TEST_F(Dhcp6ParserTest, poolPrefixLen) {
EXPECT_EQ(4000, subnet->getValid());
}
// The goal of this test is to check whether an option definition
// that defines an option carrying an IPv6 address can be created.
TEST_F(Dhcp6ParserTest, optionDefIpv6Address) {
// Configuration string.
std::string config =
"{ \"option-def\": [ {"
" \"name\": \"foo\","
" \"code\": 100,"
" \"type\": \"ipv6-address\","
" \"array\": False,"
" \"record-types\": \"\","
" \"space\": \"isc\""
" } ]"
"}";
ElementPtr json = Element::fromJSON(config);
// Make sure that the particular option definition does not exist.
OptionDefinitionPtr def = CfgMgr::instance().getOptionDef("isc", 100);
ASSERT_FALSE(def);
// Use the configuration string to create new option definition.
ConstElementPtr status;
EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
ASSERT_TRUE(status);
// The option definition should now be available in the CfgMgr.
def = CfgMgr::instance().getOptionDef("isc", 100);
ASSERT_TRUE(def);
// Verify that the option definition data is valid.
EXPECT_EQ("foo", def->getName());
EXPECT_EQ(100, def->getCode());
EXPECT_FALSE(def->getArrayType());
EXPECT_EQ(OPT_IPV6_ADDRESS_TYPE, def->getType());
}
// The goal of this test is to check whether an option definiiton
// that defines an option carrying a record of data fields can
// be created.
TEST_F(Dhcp6ParserTest, optionDefRecord) {
// Configuration string.
std::string config =
"{ \"option-def\": [ {"
" \"name\": \"foo\","
" \"code\": 100,"
" \"type\": \"record\","
" \"array\": False,"
" \"record-types\": \"uint16, ipv4-address, ipv6-address, string\","
" \"space\": \"isc\""
" } ]"
"}";
ElementPtr json = Element::fromJSON(config);
// Make sure that the particular option definition does not exist.
OptionDefinitionPtr def = CfgMgr::instance().getOptionDef("isc", 100);
ASSERT_FALSE(def);
// Use the configuration string to create new option definition.
ConstElementPtr status;
EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
ASSERT_TRUE(status);
checkResult(status, 0);
// The option definition should now be available in the CfgMgr.
def = CfgMgr::instance().getOptionDef("isc", 100);
ASSERT_TRUE(def);
// Check the option data.
EXPECT_EQ("foo", def->getName());
EXPECT_EQ(100, def->getCode());
EXPECT_EQ(OPT_RECORD_TYPE, def->getType());
EXPECT_FALSE(def->getArrayType());
// The option comprises the record of data fields. Verify that all
// fields are present and they are of the expected types.
const OptionDefinition::RecordFieldsCollection& record_fields =
def->getRecordFields();
ASSERT_EQ(4, record_fields.size());
EXPECT_EQ(OPT_UINT16_TYPE, record_fields[0]);
EXPECT_EQ(OPT_IPV4_ADDRESS_TYPE, record_fields[1]);
EXPECT_EQ(OPT_IPV6_ADDRESS_TYPE, record_fields[2]);
EXPECT_EQ(OPT_STRING_TYPE, record_fields[3]);
}
// The goal of this test is to verify that multiple option definitions
// can be created.
TEST_F(Dhcp6ParserTest, optionDefMultiple) {
// Configuration string.
std::string config =
"{ \"option-def\": [ {"
" \"name\": \"foo\","
" \"code\": 100,"
" \"type\": \"uint32\","
" \"array\": False,"
" \"record-types\": \"\","
" \"space\": \"isc\""
" },"
" {"
" \"name\": \"foo-2\","
" \"code\": 101,"
" \"type\": \"ipv4-address\","
" \"array\": False,"
" \"record-types\": \"\","
" \"space\": \"isc\""
" } ]"
"}";
ElementPtr json = Element::fromJSON(config);
// Make sure that the option definitions do not exist yet.
ASSERT_FALSE(CfgMgr::instance().getOptionDef("isc", 100));
ASSERT_FALSE(CfgMgr::instance().getOptionDef("isc", 101));
// Use the configuration string to create new option definitions.
ConstElementPtr status;
EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
ASSERT_TRUE(status);
checkResult(status, 0);
// Check the first definition we have created.
OptionDefinitionPtr def1 = CfgMgr::instance().getOptionDef("isc", 100);
ASSERT_TRUE(def1);
// Check the option data.
EXPECT_EQ("foo", def1->getName());
EXPECT_EQ(100, def1->getCode());
EXPECT_EQ(OPT_UINT32_TYPE, def1->getType());
EXPECT_FALSE(def1->getArrayType());
// Check the second option definition we have created.
OptionDefinitionPtr def2 = CfgMgr::instance().getOptionDef("isc", 101);
ASSERT_TRUE(def2);
// Check the option data.
EXPECT_EQ("foo-2", def2->getName());
EXPECT_EQ(101, def2->getCode());
EXPECT_EQ(OPT_IPV4_ADDRESS_TYPE, def2->getType());
EXPECT_FALSE(def2->getArrayType());
}
// The goal of this test is to verify that the duplicated option
// definition is not accepted.
TEST_F(Dhcp6ParserTest, optionDefDuplicate) {
// Configuration string. Both option definitions have
// the same code and belong to the same option space.
// This configuration should not be accepted.
std::string config =
"{ \"option-def\": [ {"
" \"name\": \"foo\","
" \"code\": 100,"
" \"type\": \"uint32\","
" \"array\": False,"
" \"record-types\": \"\","
" \"space\": \"isc\""
" },"
" {"
" \"name\": \"foo-2\","
" \"code\": 100,"
" \"type\": \"ipv4-address\","
" \"array\": False,"
" \"record-types\": \"\","
" \"space\": \"isc\""
" } ]"
"}";
ElementPtr json = Element::fromJSON(config);
// Make sure that the option definition does not exist yet.
ASSERT_FALSE(CfgMgr::instance().getOptionDef("isc", 100));
// Use the configuration string to create new option definitions.
ConstElementPtr status;
EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
ASSERT_TRUE(status);
checkResult(status, 1);
}
// The goal of this test is to verify that the option definition
// comprising an array of uint32 values can be created.
TEST_F(Dhcp6ParserTest, optionDefArray) {
// Configuration string. Created option definition should
// comprise an array of uint32 values.
std::string config =
"{ \"option-def\": [ {"
" \"name\": \"foo\","
" \"code\": 100,"
" \"type\": \"uint32\","
" \"array\": True,"
" \"record-types\": \"\","
" \"space\": \"isc\""
" } ]"
"}";
ElementPtr json = Element::fromJSON(config);
// Make sure that the particular option definition does not exist.
OptionDefinitionPtr def = CfgMgr::instance().getOptionDef("isc", 100);
ASSERT_FALSE(def);
// Use the configuration string to create new option definition.
ConstElementPtr status;
EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
ASSERT_TRUE(status);
checkResult(status, 0);
// The option definition should now be available in the CfgMgr.
def = CfgMgr::instance().getOptionDef("isc", 100);
ASSERT_TRUE(def);
// Check the option data.
EXPECT_EQ("foo", def->getName());
EXPECT_EQ(100, def->getCode());
EXPECT_EQ(OPT_UINT32_TYPE, def->getType());
EXPECT_TRUE(def->getArrayType());
}
/// The purpose of this test is to verify that the option definition
/// with invalid name is not accepted.
TEST_F(Dhcp6ParserTest, optionDefInvalidName) {
// Configuration string. The option name is invalid as it
// contains the % character.
std::string config =
"{ \"option-def\": [ {"
" \"name\": \"invalid%name\","
" \"code\": 100,"
" \"type\": \"string\","
" \"array\": False,"
" \"record-types\": \"\","
" \"space\": \"isc\""
" } ]"
"}";
ElementPtr json = Element::fromJSON(config);