Browse Source

[3467] Option code, name and space are now optional.

Marcin Siodelski 10 years ago
parent
commit
65ceb1cac5

+ 5 - 2
src/lib/dhcp/option6_addrlst.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2012, 2014 Internet Systems Consortium, Inc. ("ISC")
 //
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
 // purpose with or without fee is hereby granted, provided that the above
@@ -17,7 +17,7 @@
 
 
 #include <asiolink/io_address.h>
 #include <asiolink/io_address.h>
 #include <dhcp/option.h>
 #include <dhcp/option.h>
-
+#include <boost/shared_ptr.hpp>
 #include <vector>
 #include <vector>
 
 
 namespace isc {
 namespace isc {
@@ -94,6 +94,9 @@ protected:
     AddressContainer addrs_;
     AddressContainer addrs_;
 };
 };
 
 
+/// @brief Pointer to the @c Option6AddrLst object.
+typedef boost::shared_ptr<Option6AddrLst> Option6AddrLstPtr;
+
 } // isc::dhcp namespace
 } // isc::dhcp namespace
 } // isc namespace
 } // isc namespace
 
 

+ 21 - 1
src/lib/dhcpsrv/cfg_option_def.cc

@@ -138,7 +138,27 @@ CfgOptionDef::get(const std::string& option_space,
     return (OptionDefinitionPtr());
     return (OptionDefinitionPtr());
 }
 }
 
 
-
+OptionDefinitionPtr
+CfgOptionDef::get(const std::string& option_space,
+                  const std::string& option_name) const {
+    // Get the pointer to collection of the option definitions that belong
+    // to the particular option space.
+    OptionDefContainerPtr defs = getAll(option_space);
+    // If there are any option definitions for this option space, get the
+    // one that has the specified option name.
+    if (defs && !defs->empty()) {
+        const OptionDefContainerNameIndex& idx = defs->get<2>();
+        const OptionDefContainerNameRange& range = idx.equal_range(option_name);
+        // If there is more than one definition matching the option name,
+        // return the first one. In fact, it shouldn't happen that we have
+        // more than one because we check for duplicates when we add them.
+        if (std::distance(range.first, range.second) > 0) {
+            return (*range.first);
+        }
+    }
+    // Nothing found. Return NULL pointer.
+    return (OptionDefinitionPtr());
+}
 
 
 } // end of namespace isc::dhcp
 } // end of namespace isc::dhcp
 } // end of namespace isc
 } // end of namespace isc

+ 10 - 0
src/lib/dhcpsrv/cfg_option_def.h

@@ -110,6 +110,16 @@ public:
     OptionDefinitionPtr get(const std::string& option_space,
     OptionDefinitionPtr get(const std::string& option_space,
                             const uint16_t option_code) const;
                             const uint16_t option_code) const;
 
 
+    /// @brief Return option definition for the particular option space and name.
+    ///
+    /// @param option_space pption space.
+    /// @param option_name option name.
+    ///
+    /// @return An option definition or NULL pointer if option definition
+    /// has not been found.
+    OptionDefinitionPtr get(const std::string& option_space,
+                            const std::string& option_name) const;
+
 private:
 private:
 
 
     /// @brief A collection of option definitions.
     /// @brief A collection of option definitions.

+ 38 - 25
src/lib/dhcpsrv/dhcp_parsers.cc

@@ -388,11 +388,7 @@ OptionDataParser::extractName(ConstElementPtr parent) const {
         return (OptionalValue<std::string>(name));
         return (OptionalValue<std::string>(name));
     }
     }
 
 
-    if (name.empty()) {
-        isc_throw(DhcpConfigError, "option name is empty ("
-                  << string_values_->getPosition("name", parent) << ")");
-
-    } else if (name.find(" ") != std::string::npos) {
+    if (name.find(" ") != std::string::npos) {
         isc_throw(DhcpConfigError, "invalid option name '" << name
         isc_throw(DhcpConfigError, "invalid option name '" << name
                   << "', space character is not allowed ("
                   << "', space character is not allowed ("
                   << string_values_->getPosition("name", parent) << ")");
                   << string_values_->getPosition("name", parent) << ")");
@@ -465,18 +461,17 @@ OptionDataParser::extractSpace() const {
     return (space);
     return (space);
 }
 }
 
 
-
+template<typename SearchKey>
 OptionDefinitionPtr
 OptionDefinitionPtr
 OptionDataParser::findOptionDefinition(const std::string& option_space,
 OptionDataParser::findOptionDefinition(const std::string& option_space,
-                                       const uint32_t option_code) const {
+                                       const SearchKey& search_key) const {
     const Option::Universe u = address_family_ == AF_INET ?
     const Option::Universe u = address_family_ == AF_INET ?
         Option::V4 : Option::V6;
         Option::V4 : Option::V6;
     OptionDefinitionPtr def;
     OptionDefinitionPtr def;
 
 
-    if (((option_space == DHCP4_OPTION_SPACE) ||
-         (option_space == DHCP6_OPTION_SPACE)) &&
-        LibDHCP::isStandardOption(u, option_code)) {
-        def = LibDHCP::getOptionDef(u, option_code);
+    if ((option_space == DHCP4_OPTION_SPACE) ||
+        (option_space == DHCP6_OPTION_SPACE)) {
+        def = LibDHCP::getOptionDef(u, search_key);
 
 
     }
     }
 
 
@@ -485,14 +480,14 @@ OptionDataParser::findOptionDefinition(const std::string& option_space,
         // definition.
         // definition.
         uint32_t vendor_id = CfgOption::optionSpaceToVendorId(option_space);
         uint32_t vendor_id = CfgOption::optionSpaceToVendorId(option_space);
         if (vendor_id) {
         if (vendor_id) {
-            def = LibDHCP::getVendorOptionDef(u, vendor_id, option_code);
+            def = LibDHCP::getVendorOptionDef(u, vendor_id, search_key);
         }
         }
     }
     }
 
 
     if (!def) {
     if (!def) {
         // Check if this is an option specified by a user.
         // Check if this is an option specified by a user.
         def = CfgMgr::instance().getStagingCfg()->getCfgOptionDef()
         def = CfgMgr::instance().getStagingCfg()->getCfgOptionDef()
-            ->get(option_space, option_code);
+            ->get(option_space, search_key);
     }
     }
 
 
     return (def);
     return (def);
@@ -509,18 +504,40 @@ OptionDataParser::createOption(ConstElementPtr option_data) {
     std::string data_param = extractData();
     std::string data_param = extractData();
     std::string space_param = extractSpace();
     std::string space_param = extractSpace();
 
 
-    // Try to find a corresponding option definition.
-    OptionDefinitionPtr def = findOptionDefinition(space_param, code_param);
+    // Require that option code or option name is specified.
+    if (!code_param.isSpecified() && !name_param.isSpecified()) {
+        isc_throw(DhcpConfigError, "option data configuration requires one of"
+                  " 'code' or 'name' parameters to be specified"
+                  << " (" << option_data->getPosition() << ")");
+    }
+
+    // Try to find a corresponding option definition using option code or
+    // option name.
+    OptionDefinitionPtr def = code_param.isSpecified() ?
+        findOptionDefinition(space_param, code_param) :
+        findOptionDefinition(space_param, name_param);
 
 
     // If there is no definition, the user must not explicitly enable the
     // If there is no definition, the user must not explicitly enable the
     // use of csv-format.
     // use of csv-format.
-    if (!def && csv_format_param.isSpecified() && csv_format_param) {
+    if (!def) {
+        // If explicitly requested that the CSV format is to be used,
+        // the option definition is a must.
+        if (csv_format_param.isSpecified() && csv_format_param) {
             isc_throw(DhcpConfigError, "definition for the option '"
             isc_throw(DhcpConfigError, "definition for the option '"
                       << space_param << "." << name_param
                       << space_param << "." << name_param
                       << "' having code '" << code_param
                       << "' having code '" << code_param
                       << "' does not exist ("
                       << "' does not exist ("
                       << string_values_->getPosition("name", option_data)
                       << string_values_->getPosition("name", option_data)
                       << ")");
                       << ")");
+
+        // If there is no option definition and the option code is not specified
+        // we have no means to find the option code.
+        } else if (name_param.isSpecified() && !code_param.isSpecified()) {
+            isc_throw(DhcpConfigError, "definition for the option '"
+                      << space_param << "." << name_param
+                      << " does not exist ("
+                      << string_values_->getPosition("name", option_data));
+        }
     }
     }
 
 
     // Transform string of hexadecimal digits into binary format.
     // Transform string of hexadecimal digits into binary format.
@@ -574,12 +591,8 @@ OptionDataParser::createOption(ConstElementPtr option_data) {
 
 
     } else {
     } else {
 
 
-        // Option name should match the definition. The option name
-        // may seem to be redundant but in the future we may want
-        // to reference options and definitions using their names
-        // and/or option codes so keeping the option name in the
-        // definition of option value makes sense.
-        if (def->getName() != name_param.get()) {
+        // Option name is specified it should match the name in the definition.
+        if (name_param.isSpecified() && (def->getName() != name_param.get())) {
             isc_throw(DhcpConfigError, "specified option name '"
             isc_throw(DhcpConfigError, "specified option name '"
                       << name_param << "' does not match the "
                       << name_param << "' does not match the "
                       << "option definition: '" << space_param
                       << "option definition: '" << space_param
@@ -593,8 +606,8 @@ OptionDataParser::createOption(ConstElementPtr option_data) {
         try {
         try {
             OptionPtr option =
             OptionPtr option =
                 !csv_format_param.isSpecified() || csv_format_param ?
                 !csv_format_param.isSpecified() || csv_format_param ?
-                def->optionFactory(universe, code_param, data_tokens) :
-                def->optionFactory(universe, code_param, binary);
+                def->optionFactory(universe, def->getCode(), data_tokens) :
+                def->optionFactory(universe, def->getCode(), binary);
             OptionDescriptor desc(option, false);
             OptionDescriptor desc(option, false);
             option_descriptor_.option_ = option;
             option_descriptor_.option_ = option;
             option_descriptor_.persistent_ = false;
             option_descriptor_.persistent_ = false;
@@ -602,7 +615,7 @@ OptionDataParser::createOption(ConstElementPtr option_data) {
         } catch (const isc::Exception& ex) {
         } catch (const isc::Exception& ex) {
             isc_throw(DhcpConfigError, "option data does not match"
             isc_throw(DhcpConfigError, "option data does not match"
                       << " option definition (space: " << space_param
                       << " option definition (space: " << space_param
-                      << ", code: " << code_param << "): "
+                      << ", code: " << def->getCode() << "): "
                       << ex.what() << " ("
                       << ex.what() << " ("
                       << string_values_->getPosition("data", option_data)
                       << string_values_->getPosition("data", option_data)
                       << ")");
                       << ")");

+ 9 - 8
src/lib/dhcpsrv/dhcp_parsers.h

@@ -544,8 +544,7 @@ public:
 
 
     /// @brief virtual destructor to ensure orderly destruction of derivations.
     /// @brief virtual destructor to ensure orderly destruction of derivations.
     virtual ~OptionDataParser(){};
     virtual ~OptionDataParser(){};
-
-protected:
+private:
 
 
     /// @brief Finds an option definition within an option space
     /// @brief Finds an option definition within an option space
     ///
     ///
@@ -553,16 +552,18 @@ protected:
     /// option defintion within the option defintion storage.
     /// option defintion within the option defintion storage.
     ///
     ///
     /// @param option_space name of the parameter option space
     /// @param option_space name of the parameter option space
-    /// @param option_code numeric value of the parameter to find
+    /// @param search_key an option code or name to be used to lookup the
+    /// option definition.
+    /// @tparam A numeric type for searching using an option code or the
+    /// string for searching using the option name.
+    ///
     /// @return OptionDefintionPtr of the option defintion or an
     /// @return OptionDefintionPtr of the option defintion or an
     /// empty OptionDefinitionPtr if not found.
     /// empty OptionDefinitionPtr if not found.
     /// @throw DhcpConfigError if the option space requested is not valid
     /// @throw DhcpConfigError if the option space requested is not valid
     /// for this server.
     /// for this server.
-    virtual OptionDefinitionPtr
-    findOptionDefinition(const std::string& option_space,
-                         const uint32_t option_code) const;
-
-private:
+    template<typename SearchKey>
+    OptionDefinitionPtr findOptionDefinition(const std::string& option_space,
+                                             const SearchKey& search_key) const;
 
 
     /// @brief Create option instance.
     /// @brief Create option instance.
     ///
     ///

+ 12 - 1
src/lib/dhcpsrv/tests/cfg_option_def_unittest.cc

@@ -122,7 +122,7 @@ TEST(CfgOptionDefTest, getAll) {
 }
 }
 
 
 // This test verifies that single option definition is correctly
 // This test verifies that single option definition is correctly
-// returned with getOptionDef function.
+// returned with get function.
 TEST(CfgOptionDefTest, get) {
 TEST(CfgOptionDefTest, get) {
     CfgOptionDef cfg;
     CfgOptionDef cfg;
     // Create a set of option definitions with codes between 100 and 109.
     // Create a set of option definitions with codes between 100 and 109.
@@ -160,6 +160,12 @@ TEST(CfgOptionDefTest, get) {
         option_name << "option-" << code;
         option_name << "option-" << code;
         EXPECT_EQ(option_name.str(), def->getName());
         EXPECT_EQ(option_name.str(), def->getName());
         EXPECT_EQ(code, def->getCode());
         EXPECT_EQ(code, def->getCode());
+
+        // Try to get the same option definition using an option name as
+        // a key.
+        def = cfg.get("isc", option_name.str());
+        ASSERT_TRUE(def);
+        EXPECT_EQ(code, def->getCode());
     }
     }
 
 
     // Check that the option codes are valid.
     // Check that the option codes are valid.
@@ -172,7 +178,12 @@ TEST(CfgOptionDefTest, get) {
         std::ostringstream option_name;
         std::ostringstream option_name;
         option_name << "option-other-" << code;
         option_name << "option-other-" << code;
         EXPECT_EQ(option_name.str(), def->getName());
         EXPECT_EQ(option_name.str(), def->getName());
+        EXPECT_EQ(code, def->getCode());
 
 
+        // Try to get the same option definition using an option name as
+        // a key.
+        def = cfg.get("abcde", option_name.str());
+        ASSERT_TRUE(def);
         EXPECT_EQ(code, def->getCode());
         EXPECT_EQ(code, def->getCode());
     }
     }
 
 

+ 82 - 3
src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc

@@ -18,6 +18,7 @@
 #include <dhcp/option.h>
 #include <dhcp/option.h>
 #include <dhcp/option_custom.h>
 #include <dhcp/option_custom.h>
 #include <dhcp/option_int.h>
 #include <dhcp/option_int.h>
+#include <dhcp/option6_addrlst.h>
 #include <dhcp/tests/iface_mgr_test_config.h>
 #include <dhcp/tests/iface_mgr_test_config.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/subnet.h>
 #include <dhcpsrv/subnet.h>
@@ -404,7 +405,6 @@ public:
             ConstElementPtr status = parseElementSet(json);
             ConstElementPtr status = parseElementSet(json);
             ConstElementPtr comment = parseAnswer(rcode_, status);
             ConstElementPtr comment = parseAnswer(rcode_, status);
             error_text_ = comment->stringValue();
             error_text_ = comment->stringValue();
-            std::cout << error_text_ << std::endl;
             // If error was reported, the error string should contain
             // If error was reported, the error string should contain
             // position of the data element which caused failure.
             // position of the data element which caused failure.
             if (rcode_ != 0) {
             if (rcode_ != 0) {
@@ -557,7 +557,7 @@ TEST_F(ParseConfigTest, basicOptionDataTest) {
 // This test checks behavior of the configuration parser for option data
 // This test checks behavior of the configuration parser for option data
 // for different values of csv-format parameter and when there is an option
 // for different values of csv-format parameter and when there is an option
 // definition present.
 // definition present.
-TEST_F(ParseConfigTest, csvFormatWithOptionDef) {
+TEST_F(ParseConfigTest, optionDataCSVFormatWithOptionDef) {
     std::string config =
     std::string config =
         "{ \"option-data\": [ {"
         "{ \"option-data\": [ {"
         "    \"name\": \"swap-server\","
         "    \"name\": \"swap-server\","
@@ -624,7 +624,7 @@ TEST_F(ParseConfigTest, csvFormatWithOptionDef) {
 // This test checks behavior of the configuration parser for option data
 // This test checks behavior of the configuration parser for option data
 // for different values of csv-format parameter and when there is no
 // for different values of csv-format parameter and when there is no
 // option definition.
 // option definition.
-TEST_F(ParseConfigTest, csvFormatNoOptionDef) {
+TEST_F(ParseConfigTest, optionDataCSVFormatNoOptionDef) {
     // This option doesn't have any definition. It is ok to use such
     // This option doesn't have any definition. It is ok to use such
     // an option but the data should be specified in hex, not as CSV.
     // an option but the data should be specified in hex, not as CSV.
     // Note that the parser will by default use the CSV format for the
     // Note that the parser will by default use the CSV format for the
@@ -698,6 +698,85 @@ TEST_F(ParseConfigTest, csvFormatNoOptionDef) {
     EXPECT_EQ(0x56, opt->getData()[2]);
     EXPECT_EQ(0x56, opt->getData()[2]);
 }
 }
 
 
+// This test verifies that the option name is not mandatory, if the option
+// code has been specified.
+TEST_F(ParseConfigTest, optionDataNoName) {
+    std::string config =
+        "{ \"option-data\": [ {"
+        "    \"space\": \"dhcp6\","
+        "    \"code\": 23,"
+        "    \"csv-format\": True,"
+        "    \"data\": \"2001:db8:1::1\""
+        " } ]"
+        "}";
+    int rcode = 0;
+    ASSERT_NO_THROW(rcode = parseConfiguration(config));
+    EXPECT_EQ(0, rcode);
+    Option6AddrLstPtr opt = boost::dynamic_pointer_cast<
+        Option6AddrLst>(getOptionPtr("dhcp6", 23));
+    ASSERT_TRUE(opt);
+    ASSERT_EQ(1, opt->getAddresses().size());
+    EXPECT_EQ( "2001:db8:1::1", opt->getAddresses()[0].toText());
+}
+
+// This test verifies that the option code is not mandatory, if the option
+// name has been specified.
+TEST_F(ParseConfigTest, optionDataNoCode) {
+    std::string config =
+        "{ \"option-data\": [ {"
+        "    \"space\": \"dhcp6\","
+        "    \"name\": \"dns-servers\","
+        "    \"csv-format\": True,"
+        "    \"data\": \"2001:db8:1::1\""
+        " } ]"
+        "}";
+    int rcode = 0;
+    ASSERT_NO_THROW(rcode = parseConfiguration(config));
+    EXPECT_EQ(0, rcode);
+    Option6AddrLstPtr opt = boost::dynamic_pointer_cast<
+        Option6AddrLst>(getOptionPtr("dhcp6", 23));
+    ASSERT_TRUE(opt);
+    ASSERT_EQ(1, opt->getAddresses().size());
+    EXPECT_EQ( "2001:db8:1::1", opt->getAddresses()[0].toText());
+}
+
+// This test verifies that the option data configuration with a minimal
+// set of parameters works as expected.
+TEST_F(ParseConfigTest, optionDataMinimal) {
+    std::string config =
+        "{ \"option-data\": [ {"
+        "    \"name\": \"dns-servers\","
+        "    \"data\": \"2001:db8:1::10\""
+        " } ]"
+        "}";
+    int rcode = 0;
+    ASSERT_NO_THROW(rcode = parseConfiguration(config));
+    EXPECT_EQ(0, rcode);
+    Option6AddrLstPtr opt = boost::dynamic_pointer_cast<
+        Option6AddrLst>(getOptionPtr("dhcp6", 23));
+    ASSERT_TRUE(opt);
+    ASSERT_EQ(1, opt->getAddresses().size());
+    EXPECT_EQ( "2001:db8:1::10", opt->getAddresses()[0].toText());
+
+    CfgMgr::instance().clear();
+    // This time using an option code.
+    config =
+        "{ \"option-data\": [ {"
+        "    \"code\": 23,"
+        "    \"data\": \"2001:db8:1::20\""
+        " } ]"
+        "}";
+    rcode = 0;
+    ASSERT_NO_THROW(rcode = parseConfiguration(config));
+    EXPECT_EQ(0, rcode);
+    opt = boost::dynamic_pointer_cast<Option6AddrLst>(getOptionPtr("dhcp6",
+                                                                   23));
+    ASSERT_TRUE(opt);
+    ASSERT_EQ(1, opt->getAddresses().size());
+    EXPECT_EQ( "2001:db8:1::20", opt->getAddresses()[0].toText());
+}
+
+
 };  // Anonymous namespace
 };  // Anonymous namespace
 
 
 /// These tests check basic operation of the HooksLibrariesParser.
 /// These tests check basic operation of the HooksLibrariesParser.