From: Marcin Siodelski Date: Thu, 20 Dec 2012 11:22:16 +0000 (+0100) Subject: [2545] Parse and use csv-format parameter to create options. X-Git-Tag: bind10-1.0.0-rc-release~99^2~21^2~14 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=bdb56ee0da86148611e50095c9d70dfe4e4f4b7f;p=thirdparty%2Fkea.git [2545] Parse and use csv-format parameter to create options. --- diff --git a/src/bin/dhcp6/config_parser.cc b/src/bin/dhcp6/config_parser.cc index a83155ddff..02ca0b5a25 100644 --- a/src/bin/dhcp6/config_parser.cc +++ b/src/bin/dhcp6/config_parser.cc @@ -24,6 +24,7 @@ #include #include #include +#include #include #include @@ -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 ParserPtr; + +/// @brief a collection of parsers +/// +/// This container is used to store pointer to parsers for a given scope. +typedef std::vector ParserCollection; /// @brief an auxiliary type used for storing an element name and its parser typedef pair ConfigPair; @@ -65,12 +75,12 @@ typedef std::map 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 PoolStorage; +typedef std::vector 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 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 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); diff --git a/src/bin/dhcp6/config_parser.h b/src/bin/dhcp6/config_parser.h index ed44bb9e3b..647fff2d71 100644 --- a/src/bin/dhcp6/config_parser.h +++ b/src/bin/dhcp6/config_parser.h @@ -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 ParserPtr; - -/// @brief a collection of parsers -/// -/// This container is used to store pointer to parsers for a given scope. -typedef std::vector ParserCollection; - /// \brief Configure an \c Dhcpv6Srv object with a set of configuration values. /// diff --git a/src/bin/dhcp6/tests/config_parser_unittest.cc b/src/bin/dhcp6/tests/config_parser_unittest.cc index 525384d67f..abb4ff62c3 100644 --- a/src/bin/dhcp6/tests/config_parser_unittest.cc +++ b/src/bin/dhcp6/tests/config_parser_unittest.cc @@ -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); diff --git a/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc b/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc index 4c02dde689..a15c13dab1 100644 --- a/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc +++ b/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc @@ -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 }";