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

[2545] Parse and use csv-format parameter to create options.

parent 17641de2
......@@ -24,6 +24,7 @@
#include <dhcpsrv/triplet.h>
#include <log/logger_support.h>
#include <util/encode/hex.h>
#include <util/strutil.h>
#include <boost/algorithm/string.hpp>
#include <boost/foreach.hpp>
......@@ -41,8 +42,17 @@ using namespace std;
using namespace isc::data;
using namespace isc::asiolink;
namespace isc {
namespace dhcp {
namespace {
class DhcpConfigParser;
/// @brief a pointer to configuration parser
typedef boost::shared_ptr<DhcpConfigParser> ParserPtr;
/// @brief a collection of parsers
///
/// This container is used to store pointer to parsers for a given scope.
typedef std::vector<ParserPtr> ParserCollection;
/// @brief an auxiliary type used for storing an element name and its parser
typedef pair<string, ConstElementPtr> ConfigPair;
......@@ -65,12 +75,12 @@ typedef std::map<string, string> StringStorage;
///
/// This type is used as intermediate storage, when pools are parsed, but there is
/// no subnet object created yet to store them.
typedef std::vector<Pool6Ptr> PoolStorage;
typedef std::vector<isc::dhcp::Pool6Ptr> PoolStorage;
/// @brief Collection of option descriptors. This container allows searching for
/// options using the option code or persistency flag. This is useful when merging
/// existing options with newly configured options.
typedef Subnet::OptionContainer OptionStorage;
typedef isc::dhcp::Subnet::OptionContainer OptionStorage;
/// @brief Global uint32 parameters that will be used as defaults.
Uint32Storage uint32_defaults;
......@@ -81,6 +91,78 @@ StringStorage string_defaults;
/// @brief Global storage for options that will be used as defaults.
OptionStorage option_defaults;
/// @brief Base abstract class for all DHCPv6 parsers
///
/// Each instance of a class derived from this class parses one specific config
/// element. Sometimes elements are simple (e.g. a string) and sometimes quite
/// complex (e.g. a subnet). In such case, it is likely that a parser will
/// spawn child parsers to parse child elements in the configuration.
/// @todo: Merge this class with Dhcp4ConfigParser in src/bin/dhcp4
class DhcpConfigParser {
///
/// \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
/// pure base class.
//@{
private:
DhcpConfigParser(const DhcpConfigParser& source);
DhcpConfigParser& operator=(const DhcpConfigParser& source);
protected:
/// \brief The default constructor.
///
/// This is intentionally defined as \c protected as this base class should
/// never be instantiated (except as part of a derived class).
DhcpConfigParser() {}
public:
/// The destructor.
virtual ~DhcpConfigParser() {}
//@}
/// \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
/// apply to the server.
///
/// 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
/// configuration value is normally syntactically validated, but the
/// \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.
///
/// Preparing a configuration value will often require resource
/// allocation. If it fails, it may throw a corresponding standard
/// exception.
///
/// This method is not expected to be called more than once in the
/// 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
/// corresponding to the derived class.
virtual void build(isc::data::ConstElementPtr config_value) = 0;
/// \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().
/// 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 configureDhcp6Server(), the caller will convert the
/// exception as a fatal error.
///
/// This method is expected to be called after \c build(), and only once.
/// The result is undefined otherwise.
virtual void commit() = 0;
};
/// @brief a dummy configuration parser
///
......@@ -140,15 +222,30 @@ private:
ConstElementPtr value_;
};
class BooleanParser : public DhcpConfigParser {
public:
BooleanParser(const std::string& param_name)
: storage_(NULL), param_name_(param_name) {
: storage_(NULL),
param_name_(param_name),
value_(false) {
}
virtual void build(ConstElementPtr value) {
std::cout << value->str() << std::endl;
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::InvalidOperation, "parser logic error:"
<< "empty parameter name provided");
}
value_ = (value->str() == "true") ? true : false;
(*storage_)[param_name_] = value_;
}
virtual void commit() { }
......@@ -166,8 +263,15 @@ private:
std::string param_name_;
bool value_;
};
}
namespace isc {
namespace dhcp {
/// @brief Configuration parser for uint32 parameters
///
/// This class is a generic parser that is able to handle any uint32 integer
......@@ -601,7 +705,7 @@ public:
}
} else {
isc_throw(Dhcp6ConfigError,
"Parser error: option-data parameter not supported: "
"parser error: option-data parameter not supported: "
<< param.first);
}
parser->build(param.second);
......@@ -695,16 +799,20 @@ private:
}
// Get option data from the configuration database ('data' field).
// Option data is specified by the user as case insensitive string
// of hexadecimal digits for each option.
std::string option_data = getStringParam("data");
// Transform string of hexadecimal digits into binary format.
const std::string option_data = getStringParam("data");
const bool csv_format = getBooleanParam("csv-format");
std::vector<uint8_t> binary;
try {
util::encode::decodeHex(option_data, binary);
} catch (...) {
isc_throw(Dhcp6ConfigError, "Parser error: option data is not a valid"
<< " string of hexadecimal digits: " << option_data);
std::vector<std::string> data_tokens;
if (csv_format) {
data_tokens = isc::util::str::tokens(option_data, ",");
} else {
// Transform string of hexadecimal digits into binary format.
try {
util::encode::decodeHex(option_data, binary);
} catch (...) {
isc_throw(Dhcp6ConfigError, "Parser error: option data is not a valid"
<< " string of hexadecimal digits: " << option_data);
}
}
// Get all existing DHCPv6 option definitions. The one that matches
// our option will be picked and used to create it.
......@@ -724,6 +832,12 @@ private:
<< " for the same option code. This will be supported once"
<< " there option spaces are implemented.");
} else if (num_defs == 0) {
if (csv_format) {
isc_throw(Dhcp6ConfigError, "the CSV option data format can be"
" used to specify values for an option that has a"
" definition. The option with code " << option_code
<< " does not have a definition.");
}
// @todo We have a limited set of option definitions intiialized at the moment.
// In the future we want to initialize option definitions for all options.
// Consequently an error will be issued if an option definition does not exist
......@@ -741,7 +855,9 @@ private:
// use it to create the option instance.
const OptionDefinitionPtr& def = *(range.first);
try {
OptionPtr option = def->optionFactory(Option::V6, option_code, binary);
OptionPtr option = csv_format ?
def->optionFactory(Option::V6, option_code, data_tokens) :
def->optionFactory(Option::V6, option_code, binary);
Subnet::OptionDescriptor desc(option, false);
option_descriptor_.option = option;
option_descriptor_.persistent = false;
......@@ -760,7 +876,7 @@ private:
std::string getStringParam(const std::string& param_id) const {
StringStorage::const_iterator param = string_values_.find(param_id);
if (param == string_values_.end()) {
isc_throw(Dhcp6ConfigError, "Parser error: option-data parameter"
isc_throw(Dhcp6ConfigError, "parser error: option-data parameter"
<< " '" << param_id << "' not specified");
}
return (param->second);
......@@ -773,7 +889,16 @@ private:
uint32_t getUint32Param(const std::string& param_id) const {
Uint32Storage::const_iterator param = uint32_values_.find(param_id);
if (param == uint32_values_.end()) {
isc_throw(Dhcp6ConfigError, "Parser error: option-data parameter"
isc_throw(Dhcp6ConfigError, "parser error: option-data parameter"
<< " '" << param_id << "' not specified");
}
return (param->second);
}
bool getBooleanParam(const std::string& param_id) const {
BooleanStorage::const_iterator param = boolean_values_.find(param_id);
if (param == boolean_values_.end()) {
isc_throw(Dhcp6ConfigError, "parser error: option-data parameter"
<< " '" << param_id << "' not specified");
}
return (param->second);
......
......@@ -41,86 +41,6 @@ public:
: isc::Exception(file, line, what) {}
};
/// @brief Base abstract class for all DHCPv6 parsers
///
/// Each instance of a class derived from this class parses one specific config
/// element. Sometimes elements are simple (e.g. a string) and sometimes quite
/// complex (e.g. a subnet). In such case, it is likely that a parser will
/// spawn child parsers to parse child elements in the configuration.
/// @todo: Merge this class with Dhcp4ConfigParser in src/bin/dhcp4
class DhcpConfigParser {
///
/// \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
/// pure base class.
//@{
private:
DhcpConfigParser(const DhcpConfigParser& source);
DhcpConfigParser& operator=(const DhcpConfigParser& source);
protected:
/// \brief The default constructor.
///
/// This is intentionally defined as \c protected as this base class should
/// never be instantiated (except as part of a derived class).
DhcpConfigParser() {}
public:
/// The destructor.
virtual ~DhcpConfigParser() {}
//@}
/// \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
/// apply to the server.
///
/// 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
/// configuration value is normally syntactically validated, but the
/// \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.
///
/// Preparing a configuration value will often require resource
/// allocation. If it fails, it may throw a corresponding standard
/// exception.
///
/// This method is not expected to be called more than once in the
/// 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
/// corresponding to the derived class.
virtual void build(isc::data::ConstElementPtr config_value) = 0;
/// \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().
/// 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 configureDhcp6Server(), the caller will convert the
/// exception as a fatal error.
///
/// This method is expected to be called after \c build(), and only once.
/// The result is undefined otherwise.
virtual void commit() = 0;
};
/// @brief a pointer to configuration parser
typedef boost::shared_ptr<DhcpConfigParser> ParserPtr;
/// @brief a collection of parsers
///
/// This container is used to store pointer to parsers for a given scope.
typedef std::vector<ParserPtr> ParserCollection;
/// \brief Configure an \c Dhcpv6Srv object with a set of configuration values.
///
......
......@@ -69,14 +69,22 @@ public:
params["name"] = param_value;
params["code"] = "80";
params["data"] = "AB CDEF0105";
params["csv-format"] = "False";
} else if (parameter == "code") {
params["name"] = "option_foo";
params["code"] = param_value;
params["data"] = "AB CDEF0105";
params["csv-format"] = "False";
} else if (parameter == "data") {
params["name"] = "option_foo";
params["code"] = "80";
params["data"] = param_value;
params["csv-format"] = "False";
} else if (parameter == "csv-format") {
params["name"] = "option_foo";
params["code"] = "80";
params["data"] = "AB CDEF0105";
params["csv-format"] = param_value;
}
return (createConfigWithOption(params));
}
......@@ -103,9 +111,11 @@ public:
if (param.first == "name") {
stream << "\"name\": \"" << param.second << "\"";
} else if (param.first == "code") {
stream << "\"code\": " << param.second << "";
stream << "\"code\": " << param.second;;
} else if (param.first == "data") {
stream << "\"data\": \"" << param.second << "\"";
} else if (param.first == "csv-format") {
stream << "\"csv-format\": " << param.second;
}
}
stream <<
......@@ -425,12 +435,14 @@ TEST_F(Dhcp6ParserTest, optionDataDefaults) {
"\"option-data\": [ {"
" \"name\": \"option_foo\","
" \"code\": 100,"
" \"data\": \"AB CDEF0105\""
" \"data\": \"AB CDEF0105\","
" \"csv-format\": False"
" },"
" {"
" \"name\": \"option_foo2\","
" \"code\": 101,"
" \"data\": \"01\""
" \"data\": \"01\","
" \"csv-format\": False"
" } ],"
"\"subnet6\": [ { "
" \"pool\": [ \"2001:db8:1::/80\" ],"
......@@ -504,12 +516,14 @@ TEST_F(Dhcp6ParserTest, optionDataInSingleSubnet) {
" \"option-data\": [ {"
" \"name\": \"option_foo\","
" \"code\": 100,"
" \"data\": \"AB CDEF0105\""
" \"data\": \"AB CDEF0105\","
" \"csv-format\": False"
" },"
" {"
" \"name\": \"option_foo2\","
" \"code\": 101,"
" \"data\": \"01\""
" \"data\": \"01\","
" \"csv-format\": False"
" } ]"
" } ],"
"\"valid-lifetime\": 4000 }";
......@@ -566,7 +580,8 @@ TEST_F(Dhcp6ParserTest, optionDataInMultipleSubnets) {
" \"option-data\": [ {"
" \"name\": \"option_foo\","
" \"code\": 100,"
" \"data\": \"0102030405060708090A\""
" \"data\": \"0102030405060708090A\","
" \"csv-format\": False"
" } ]"
" },"
" {"
......@@ -575,7 +590,8 @@ TEST_F(Dhcp6ParserTest, optionDataInMultipleSubnets) {
" \"option-data\": [ {"
" \"name\": \"option_foo2\","
" \"code\": 101,"
" \"data\": \"FFFEFDFCFB\""
" \"data\": \"FFFEFDFCFB\","
" \"csv-format\": False"
" } ]"
" } ],"
"\"valid-lifetime\": 4000 }";
......@@ -703,6 +719,7 @@ TEST_F(Dhcp6ParserTest, optionDataLowerCase) {
EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
ASSERT_TRUE(x);
comment_ = parseAnswer(rcode_, x);
std::cout << comment_->str() << std::endl;
ASSERT_EQ(0, rcode_);
Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
......@@ -737,6 +754,7 @@ TEST_F(Dhcp6ParserTest, stdOptionData) {
// Option code 3 means OPTION_IA_NA.
params["code"] = "3";
params["data"] = "ABCDEF01 02030405 06070809";
params["csv-format"] = "False";
std::string config = createConfigWithOption(params);
ElementPtr json = Element::fromJSON(config);
......
......@@ -341,12 +341,14 @@ TEST_F(Dhcpv6SrvTest, advertiseOptions) {
" \"name\": \"OPTION_DNS_SERVERS\","
" \"code\": 23,"
" \"data\": \"2001 0DB8 1234 FFFF 0000 0000 0000 0001"
"2001 0DB8 1234 FFFF 0000 0000 0000 0002\""
"2001 0DB8 1234 FFFF 0000 0000 0000 0002\","
" \"csv-format\": False"
" },"
" {"
" \"name\": \"OPTION_FOO\","
" \"code\": 1000,"
" \"data\": \"1234\""
" \"data\": \"1234\","
" \"csv-format\": False"
" } ]"
" } ],"
"\"valid-lifetime\": 4000 }";
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment